// The device sheet's rules for what may be typed into a field: Java's // EingabenUeberpruefung as FILIUS' settings panels use it // (JHostKonfiguration, JSwitchKonfiguration, JModemKonfiguration), measured // with tools/java-oracle's SheetRun (test/sheet.java.json). // // Java judges a text twice: while it is typed, the panel colours it red when // the pattern does not match; when the field loses the focus, the model's // setter takes it or keeps the old value — and the two disagree. Java takes // red text ("10..0.0.1", "+10.0.0.1", "-0.0.0.1", Arabic-Indic digits, a // retention time of "-5"), refuses black text (".0.0.1", a retention time // past Long's range), and a modem takes any int as its port (0, 65536, -1), // unchecked. Here one rule decides both: a text is taken exactly when it is // not marked. What is not taken is not thrown away either (Java puts the old // value back into the field without a word): the sheet keeps it, marked, // until it is mended (plan I6). // // Spaces around an address or a number are left out (Java refuses // " 10.0.0.1"; a phone's keyboard puts a space after a word). A name and an // SSID are taken as typed, spaces included, as Java takes them. import { IP_ADDRESS_PATTERN, javaIpCheck } from '@mdg-filius/core'; export type FieldRule = 'name' | 'ipAddress' | 'ipAddressOrEmpty' | 'subnetMask' | 'ssid' | 'seconds' | 'port'; /** What a field's text comes to: the value the device takes, or that it takes none. */ export type CheckedField = { readonly value: string } | { readonly invalid: true }; /** EingabenUeberpruefung.musterSubNetz. */ const SUBNET_MASK_PATTERN = /^(0*([1-9]|1?[0-9]{1,2}|2[0-4]?[0-9]|25[0-5]))\.((0*([0-9]|1?[0-9]{1,2}|2[0-4]?[0-9]|25[0-5]))\.){2}(0*([0-9]|1?[0-9]{1,2}|2[0-4]?[0-9]|25[0-5]))$/; /** EingabenUeberpruefung.musterServiceSetIdentifier: printable ASCII, at least one character. */ const SSID_PATTERN = /^[ -~]+$/; /** EingabenUeberpruefung.musterPort: 1 to 65535, no leading zero. */ const PORT_PATTERN = /^([1-9]|[1-9][0-9]{1,3}|[1-5][0-9]{1,4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$/; /** EingabenUeberpruefung.musterNurZahlen, at least one digit (Java's Long.parseLong refuses none). */ const DIGITS_PATTERN = /^[0-9]+$/; const INVALID: CheckedField = { invalid: true }; /** * An address as the panel's pattern and the model's IP.ipCheck both accept * it: neither alone (the pattern lets ".0.0.1" through, ipCheck "10..0.0.1" * and "+10.0.0.1"); the device keeps ipCheck's form ("010.0.0.1" is 10.0.0.1). */ function address(text: string, pattern: RegExp): CheckedField { const canonical = javaIpCheck(text); return canonical !== null && pattern.test(text) ? { value: canonical } : INVALID; } export function checkField(rule: FieldRule, text: string): CheckedField { switch (rule) { case 'name': return { value: text }; case 'ssid': return SSID_PATTERN.test(text) ? { value: text } : INVALID; case 'ipAddress': return address(text.trim(), IP_ADDRESS_PATTERN); case 'ipAddressOrEmpty': return text.trim() === '' ? { value: '' } : address(text.trim(), IP_ADDRESS_PATTERN); case 'subnetMask': return address(text.trim(), SUBNET_MASK_PATTERN); case 'seconds': { const digits = text.trim(); // In milliseconds the device's number must stay exact. return DIGITS_PATTERN.test(digits) && Number(digits) * 1000 <= Number.MAX_SAFE_INTEGER ? { value: String(Number(digits)) } : INVALID; } case 'port': return PORT_PATTERN.test(text.trim()) ? { value: text.trim() } : INVALID; } }