diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/AiGenerated.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/AiGenerated.kt index 36704512..78621074 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/AiGenerated.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/AiGenerated.kt @@ -413,6 +413,47 @@ fun getAllAIGeneratedValidators(): Map { Validator("config_parse_socket_service", "0") to ConfigParseSocketServiceOptionValue() as OptionValueInformation, Validator("config_parse_service_sockets", "0") to ConfigParseServiceSocketsOptionValue() as OptionValueInformation, + // Batch 2026-10: network-dhcp-misc + Validator("config_parse_dhcp_option_tlv", "0") to ConfigParseDhcpOptionTlvOptionValue() as OptionValueInformation, + Validator("config_parse_dhcp_option", "true") to ConfigParseDhcpOptionTypedOptionValue() as OptionValueInformation, + Validator("config_parse_dhcp6_send_option", "0") to ConfigParseDhcp6SendOptionOptionValue() as OptionValueInformation, + Validator("config_parse_uplink", "0") to ConfigParseUplinkOptionValue() as OptionValueInformation, + Validator("config_parse_fdb_interface", "0") to ConfigParseFdbInterfaceOptionValue() as OptionValueInformation, + Validator("config_parse_mud_url", "0") to ConfigParseMudUrlOptionValue() as OptionValueInformation, + Validator("config_parse_resolve_support", "0") to ConfigParseResolveSupportOptionValue() as OptionValueInformation, + Validator("config_parse_dns_over_tls_mode", "0") to ConfigParseDnsOverTlsModeOptionValue() as OptionValueInformation, + Validator("config_parse_dhcp_pd_prefix_route_type", "0") to ConfigParseDhcpPdPrefixRouteTypeOptionValue() as OptionValueInformation, + Validator("config_parse_lldp_multicast_mode", "0") to ConfigParseLldpMulticastModeOptionValue() as OptionValueInformation, + Validator("config_parse_activation_policy", "0") to ConfigParseActivationPolicyOptionValue() as OptionValueInformation, + Validator("config_parse_dhcp_relay_interface_mode", "0") to ConfigParseDhcpRelayInterfaceModeOptionValue() as OptionValueInformation, + Validator("config_parse_dhcp_fallback_lease_lifetime", "0") to ConfigParseDhcpFallbackLeaseLifetimeOptionValue() as OptionValueInformation, + Validator("config_parse_ipv4ll", "0") to ConfigParseIpv4llOptionValue() as OptionValueInformation, + Validator("config_parse_ignore_carrier_loss", "0") to ConfigParseIgnoreCarrierLossOptionValue() as OptionValueInformation, + Validator("config_parse_dhcp_server_ipv6_only_preferred", "0") to ConfigParseDhcpServerIpv6OnlyPreferredOptionValue() as OptionValueInformation, + Validator("config_parse_bridge_vlan_id", "0") to ConfigParseBridgeVlanIdOptionValue() as OptionValueInformation, + Validator("config_parse_link_group", "0") to ConfigParseLinkGroupOptionValue() as OptionValueInformation, + Validator("config_parse_dhcp_pd_subnet_id", "0") to ConfigParseDhcpPdSubnetIdOptionValue() as OptionValueInformation, + Validator("config_parse_required_for_online", "0") to ConfigParseRequiredForOnlineOptionValue() as OptionValueInformation, + Validator("config_parse_dhcp_server_address", "0") to ConfigParseDhcpServerAddressOptionValue() as OptionValueInformation, + Validator("config_parse_ipv4ll_address", "0") to ConfigParseIpv4llAddressOptionValue() as OptionValueInformation, + Validator("config_parse_dhcp_label", "0") to ConfigParseDhcpLabelOptionValue() as OptionValueInformation, + Validator("config_parse_dhcp_static_lease_hostname", "0") to ConfigParseDhcpStaticLeaseHostnameOptionValue() as OptionValueInformation, + Validator("config_parse_domains", "0") to ConfigParseDomainsOptionValue() as OptionValueInformation, + Validator("config_parse_dnssec_negative_trust_anchors", "0") to ConfigParseDnssecNegativeTrustAnchorsOptionValue() as OptionValueInformation, + Validator("config_parse_ntp", "0") to ConfigParseNtpOptionValue() as OptionValueInformation, + Validator("config_parse_radv_dns", "0") to ConfigParseRadvDnsOptionValue() as OptionValueInformation, + Validator("config_parse_dhcp_request_options", "AF_INET") to ConfigParseDhcpRequestOptionsOptionValue() as OptionValueInformation, + Validator("config_parse_dhcp_request_options", "AF_INET6") to ConfigParseDhcpRequestOptionsOptionValue() as OptionValueInformation, + Validator("config_parse_dhcp4_user_class", "0") to ConfigParseDhcp4UserClassOptionValue() as OptionValueInformation, + Validator("config_parse_dhcp6_user_or_vendor_class", "0") to ConfigParseDhcp6UserOrVendorClassOptionValue() as OptionValueInformation, + Validator("config_parse_duid_type", "0") to ConfigParseDuidTypeOptionValue("config_parse_duid_type") as OptionValueInformation, + Validator("config_parse_network_duid_type", "0") to ConfigParseDuidTypeOptionValue("config_parse_network_duid_type") as OptionValueInformation, + Validator("config_parse_duid_rawdata", "0") to ConfigParseDuidRawdataOptionValue("config_parse_duid_rawdata") as OptionValueInformation, + Validator("config_parse_network_duid_rawdata", "0") to ConfigParseDuidRawdataOptionValue("config_parse_network_duid_rawdata") as OptionValueInformation, + Validator("config_parse_dhcp_or_ra_route_table", "NETWORK_CONFIG_SOURCE_DHCP4") to ConfigParseDhcpOrRaRouteTableOptionValue() as OptionValueInformation, + Validator("config_parse_dhcp_or_ra_route_table", "NETWORK_CONFIG_SOURCE_DHCP6") to ConfigParseDhcpOrRaRouteTableOptionValue() as OptionValueInformation, + Validator("config_parse_dhcp_or_ra_route_table", "NETWORK_CONFIG_SOURCE_NDISC") to ConfigParseDhcpOrRaRouteTableOptionValue() as OptionValueInformation, + ) return allValidators diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseActivationPolicyOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseActivationPolicyOptionValue.kt new file mode 100644 index 00000000..98285bf0 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseActivationPolicyOptionValue.kt @@ -0,0 +1,27 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.EOF +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.FlexibleLiteralChoiceTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator + +/* + * [Link] ActivationPolicy= (.network). + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#ActivationPolicy= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/networkd-network.c#L1201 config_parse_activation_policy (0), DEFINE_CONFIG_PARSE_ENUM + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/network/networkd-network.c#L1191-L1198 activation_policy_table + * tests https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-activation-policy.network.d/always-down.conf#L2 + */ + +/** + * Validator for `[Link] ActivationPolicy=`: exactly one of `up`, `always-up`, `manual`, `always-down`, + * `down`, `bound` (plain DEFINE_STRING_TABLE_LOOKUP — case-sensitive, no boolean). + */ +class ConfigParseActivationPolicyOptionValue : SimpleGrammarOptionValues( + "config_parse_activation_policy", + SequenceCombinator( + FlexibleLiteralChoiceTerminal("up", "always-up", "manual", "always-down", "down", "bound"), + EOF(), + ) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseBridgeVlanIdOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseBridgeVlanIdOptionValue.kt new file mode 100644 index 00000000..c9df8f0a --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseBridgeVlanIdOptionValue.kt @@ -0,0 +1,43 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.AlternativeCombinator +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.BOOLEAN_FALSE +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.EOF +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SafeAtoTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator + +/* + * [BridgeVLAN] PVID= (.network). + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#PVID= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/networkd-bridge-vlan.c#L341-L381 config_parse_bridge_vlan_id (0) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/shared/vlan-util.c#L8-L23 parse_vlanid (safe_atou16, <= VLANID_MAX = 4094) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L23-L46 parse_boolean + * tests https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/26-bridge-vlan-master.network.d/30-override.conf#L3 + * https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/26-bridge-vlan-master.network#L9 + */ + +/** + * Validator for `[BridgeVLAN] PVID=` (config_parse_bridge_vlan_id): a *false* boolean (removes the PVID; + * a true one falls through to parse_vlanid and is refused), or a VLAN id 0…4094 read by safe_atou16 + * (base 0, so `0x10` is legal). + */ +class ConfigParseBridgeVlanIdOptionValue : SimpleGrammarOptionValues( + "config_parse_bridge_vlan_id", + AlternativeCombinator( + SequenceCombinator( + SafeAtoTerminal( + signed = false, + base = 0, + flags = emptySet(), + minInclusive = 0L, + maxInclusive = 4_094L, + acceptsWhitespace = true, + acceptsMinus = true, + ), + EOF(), + ), + SequenceCombinator(BOOLEAN_FALSE, EOF()), + ) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcp4UserClassOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcp4UserClassOptionValue.kt new file mode 100644 index 00000000..922bad0d --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcp4UserClassOptionValue.kt @@ -0,0 +1,35 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.CUNESCAPE_UNQUOTE_WORD +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.NETWORK_DHCP_MISC_NONEMPTY_UNQUOTE_WORD +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.EOF +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.wordList + +/* + * [DHCPv4] UserClass= (and the old [DHCP] UserClass=) (.network). + * + * Note: the plugin reports the deprecated [DHCP] section as not allowed in .network files, so this + * validator never actually runs for keys there; they are listed only because the gperf shares the parser. + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#UserClass= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/networkd-dhcp-common.c#L655-L698 config_parse_dhcp4_user_class (0) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/extract-word.c#L10-L188 extract_first_word (EXTRACT_CUNESCAPE|EXTRACT_UNQUOTE) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/escape.c#L104-L296 cunescape_one + */ + +/** + * Validator for `[DHCPv4] UserClass=`: a whitespace-separated list of user-class strings, split by + * extract_first_word with EXTRACT_CUNESCAPE|EXTRACT_UNQUOTE — so a class may be quoted to contain spaces + * and may use C escapes ([CUNESCAPE_UNQUOTE_WORD]). An unknown escape, a trailing backslash or an + * unterminated quote fails the assignment. + * + * Each class must also be 1…255 bytes long once unquoted/unescaped. An empty one (`""`, `''`) is + * dropped with a warning and flagged ([NETWORK_DHCP_MISC_NONEMPTY_UNQUOTE_WORD]); the upper bound is not + * modelled (lenient). + */ +class ConfigParseDhcp4UserClassOptionValue : SimpleGrammarOptionValues( + "config_parse_dhcp4_user_class", + SequenceCombinator(wordList(NETWORK_DHCP_MISC_NONEMPTY_UNQUOTE_WORD), EOF()) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcp6SendOptionOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcp6SendOptionOptionValue.kt new file mode 100644 index 00000000..d2ffe214 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcp6SendOptionOptionValue.kt @@ -0,0 +1,90 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.AlternativeCombinator +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.NETWORK_DHCP_MISC_COALESCED_COLONS +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.COLON +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.EOF +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.KeyOrSectionDispatchingGrammarOptionValue +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SafeAtoTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.ZeroOrMore +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.dhcpOptionTypedData +import java.math.BigInteger + +/* + * [DHCPv6] SendOption= / SendVendorOption= (.network). + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#SendOption= + * https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#SendVendorOption= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/networkd-dhcp-common.c#L745-L942 config_parse_dhcp6_send_option (0) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/network/networkd-dhcp-common.c#L781 streq(lvalue, "SendVendorOption") — the key decides the shape + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/network/networkd-dhcp-common.c#L1176-L1183 dhcp_option_data_type_table + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/extract-word.c#L10-L188 extract_first_word (":" separator, coalescing) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L599-L613 safe_atou16_full (base 0) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L429-L478 safe_atou_full (safe_atou32, base 0) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/in-addr-util.c#L517-L529 in_addr_from_string (inet_pton) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/escape.c#L104-L296 cunescape_one + * linux https://man7.org/linux/man-pages/man3/inet_pton.3.html + */ + +// safe_atou16/safe_atou32 skip leading whitespace, and extract_first_word(…, ":", 0) leaves any that +// follows a colon inside the next word, so `123: 1:string:x` is fine: hence acceptsWhitespace = true. +// CODE is safe_atou16 with `u16 < 1 || u16 >= UINT16_MAX` refused, so 1…65534. +private val CODE_TYPE_DATA = SequenceCombinator( + SafeAtoTerminal( + signed = false, + base = 0, + flags = emptySet(), + minInclusive = 1L, + maxInclusive = 65_534L, + acceptsWhitespace = true, + acceptsMinus = true, + ), + NETWORK_DHCP_MISC_COALESCED_COLONS, + dhcpOptionTypedData(), + EOF(), +) + +private val SEND_OPTION = SequenceCombinator(ZeroOrMore(COLON), CODE_TYPE_DATA) + +private val SEND_VENDOR_OPTION = SequenceCombinator( + ZeroOrMore(COLON), + SafeAtoTerminal( + signed = false, + base = 0, + flags = emptySet(), + minInclusive = BigInteger.ZERO, + maxInclusive = SafeAtoTerminal.UINT32_MAX, + acceptsWhitespace = true, + acceptsMinus = true, + ), + NETWORK_DHCP_MISC_COALESCED_COLONS, + CODE_TYPE_DATA, +) + +/** + * Validator for `[DHCPv6] SendOption=` (`CODE:TYPE:DATA`) and `[DHCPv6] SendVendorOption=` + * (`ENTERPRISE:CODE:TYPE:DATA`), both parsed by config_parse_dhcp6_send_option. + * + * The parser branches on the *key name*: only for SendVendorOption= does it first read an enterprise + * identifier (safe_atou32, any uint32 — the man page says 1…4294967294 but C accepts 0 and 4294967295 + * too). The validator is registered per (parser, ltype) but dispatches on the key it is checking, so + * `SendOption=` takes exactly the three-field form and `SendVendorOption=` exactly the four-field one + * (`SendVendorOption=12:string:x` is refused, as systemd's safe_atou16("string") refuses it). + * + * CODE is safe_atou16 in 1…65534 — the C check is `u16 < 1 || u16 >= UINT16_MAX`, so 65535 is refused + * although the man page says "1…65536". The TYPE:DATA tail is [dhcpOptionTypedData]; all six types are + * accepted. Every separator coalesces, so leading and doubled colons are harmless. + */ +class ConfigParseDhcp6SendOptionOptionValue : KeyOrSectionDispatchingGrammarOptionValue( + VALIDATOR_NAME, + AlternativeCombinator(SEND_VENDOR_OPTION, SEND_OPTION), + { key, _ -> if (key == "SendVendorOption") VENDOR else PLAIN }, +) { + private companion object { + const val VALIDATOR_NAME = "config_parse_dhcp6_send_option" + val PLAIN = SimpleGrammarOptionValues(VALIDATOR_NAME, SEND_OPTION) + val VENDOR = SimpleGrammarOptionValues(VALIDATOR_NAME, SEND_VENDOR_OPTION) + } +} diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcp6UserOrVendorClassOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcp6UserOrVendorClassOptionValue.kt new file mode 100644 index 00000000..d28876f8 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcp6UserOrVendorClassOptionValue.kt @@ -0,0 +1,27 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.NETWORK_DHCP_MISC_NONEMPTY_UNQUOTE_WORD +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.EOF +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.wordList + +/* + * [DHCPv6] UserClass= / VendorClass= (.network). + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#UserClass= + * https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#VendorClass= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/networkd-dhcp-common.c#L700-L743 config_parse_dhcp6_user_or_vendor_class (0) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/extract-word.c#L10-L188 extract_first_word (EXTRACT_CUNESCAPE|EXTRACT_UNQUOTE) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/escape.c#L104-L296 cunescape_one + */ + +/** + * Validator for `[DHCPv6] UserClass=` and `[DHCPv6] VendorClass=`: the same quoted/escaped word list as + * [ConfigParseDhcp4UserClassOptionValue], with a 1…65535-byte length limit per entry: empty + * entries are flagged, the upper bound is not modelled. + */ +class ConfigParseDhcp6UserOrVendorClassOptionValue : SimpleGrammarOptionValues( + "config_parse_dhcp6_user_or_vendor_class", + SequenceCombinator(wordList(NETWORK_DHCP_MISC_NONEMPTY_UNQUOTE_WORD), EOF()) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpFallbackLeaseLifetimeOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpFallbackLeaseLifetimeOptionValue.kt new file mode 100644 index 00000000..d1c7154b --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpFallbackLeaseLifetimeOptionValue.kt @@ -0,0 +1,22 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.EOF +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.FlexibleLiteralChoiceTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator + +/* + * [DHCPv4] FallbackLeaseLifetimeSec= (.network). + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#FallbackLeaseLifetimeSec= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/networkd-dhcp4.c#L2098-L2133 config_parse_dhcp_fallback_lease_lifetime (0) + */ + +/** + * Validator for `[DHCPv4] FallbackLeaseLifetimeSec=`. Despite the `Sec` suffix this is not a time span: + * the parser accepts only `forever` or `infinity` (STR_IN_SET, case-sensitive), as the man page says. + */ +class ConfigParseDhcpFallbackLeaseLifetimeOptionValue : SimpleGrammarOptionValues( + "config_parse_dhcp_fallback_lease_lifetime", + SequenceCombinator(FlexibleLiteralChoiceTerminal("forever", "infinity"), EOF()) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpLabelOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpLabelOptionValue.kt new file mode 100644 index 00000000..5e5678a2 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpLabelOptionValue.kt @@ -0,0 +1,24 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.EOF +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.RegexTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator + +/* + * [DHCPv4] Label= (.network). + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#Label= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/networkd-dhcp4.c#L2135-L2165 config_parse_dhcp_label (0) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/socket-util.c#L832-L852 address_label_valid + */ + +/** + * Validator for `[DHCPv4] Label=` (config_parse_dhcp_label → address_label_valid): 1…15 characters + * (shorter than IFNAMSIZ), each printable ASCII 0x20…0x7E — so, unlike an interface name, a label may + * contain `:`, `/`, `%` and inner spaces, and may be all digits. + */ +class ConfigParseDhcpLabelOptionValue : SimpleGrammarOptionValues( + "config_parse_dhcp_label", + SequenceCombinator(RegexTerminal("""[\s\S]+""", """[\x20-\x7E]{1,15}\z"""), EOF()) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpOptionTlvOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpOptionTlvOptionValue.kt new file mode 100644 index 00000000..fa2dd445 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpOptionTlvOptionValue.kt @@ -0,0 +1,62 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.NETWORK_DHCP_MISC_COALESCED_COLONS +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.COLON +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.EOF +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SafeAtoTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.ZeroOrMore +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.dhcpOptionTypedData + +/* + * [DHCPv4] SendOption= / SendVendorOption=, [DHCPServer] SendOption= / SendVendorOption=, + * [DHCPRelay] ExtraOption= (.network and networkd.conf). + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#SendOption= + * https://www.freedesktop.org/software/systemd/man/latest/networkd.conf.html#ExtraOption= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/networkd-dhcp-common.c#L1062-L1108 config_parse_dhcp_option_tlv (0) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/network/networkd-dhcp-common.c#L944-L1060 config_parse_dhcp_option (TYPE:DATA tail, check_length=false) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/network/networkd-dhcp-common.c#L1176-L1183 dhcp_option_data_type_table + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/extract-word.c#L10-L188 extract_first_word (":" separator, coalescing) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L583-L597 safe_atou8_full (base 0) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/in-addr-util.c#L517-L529 in_addr_from_string (inet_pton) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/escape.c#L104-L296 cunescape_one + * tests https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-dhcp-client-ipv4-only.network#L21 + * linux https://man7.org/linux/man-pages/man3/inet_pton.3.html + */ + +/** + * Validator for the `CODE:TYPE:DATA` raw-option settings parsed by config_parse_dhcp_option_tlv: + * `[DHCPv4] SendOption=`, `[DHCPv4] SendVendorOption=`, `[DHCPServer] SendOption=`, + * `[DHCPServer] SendVendorOption=` and `[DHCPRelay] ExtraOption=` (in both .network files and + * networkd.conf). + * + * CODE is read by safe_atou8 (so `0x0c` and `014` are 12 too, and leading whitespace, `0b 1` or `\v5` are + * fine because the field is cut on `:`, not on whitespace) and must be 1…254. The rest is handed to + * config_parse_dhcp_option and has the [dhcpOptionTypedData] shape: `uint8`/`uint16`/`uint32` take a + * number in range, `ipv4address`/`ipv6address` a bare address, `string` any C-escaped text (which may + * itself contain colons). Every separator is extract_first_word(…, ":", 0), so leading and repeated + * colons are coalesced and accepted. + * + * The man page lists only uint8/uint16/uint32/ipv4address/string for [DHCPv4]; the C parser shares + * the full type table, so `ipv6address` is accepted everywhere (C wins). + */ +class ConfigParseDhcpOptionTlvOptionValue : SimpleGrammarOptionValues( + "config_parse_dhcp_option_tlv", + SequenceCombinator( + ZeroOrMore(COLON), + SafeAtoTerminal( + signed = false, + base = 0, + flags = emptySet(), + minInclusive = 1L, + maxInclusive = 254L, + acceptsWhitespace = true, + acceptsMinus = true, + ), + NETWORK_DHCP_MISC_COALESCED_COLONS, + dhcpOptionTypedData(), + EOF(), + ) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpOptionTypedOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpOptionTypedOptionValue.kt new file mode 100644 index 00000000..7b2c14d5 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpOptionTypedOptionValue.kt @@ -0,0 +1,44 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.COLON +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.EOF +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.ZeroOrMore +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.dhcpOptionTypedData + +/* + * [DHCPRelay] CircuitId= / RemoteId= / VirtualSubnetSelection= and the deprecated + * [DHCPServer] RelayAgentCircuitId= / RelayAgentRemoteId= (.network; RemoteId= also in networkd.conf). + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#CircuitId= + * https://www.freedesktop.org/software/systemd/man/latest/networkd.conf.html#RemoteId= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/networkd-dhcp-common.c#L944-L1060 config_parse_dhcp_option (true = check_length) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/network/networkd-dhcp-common.c#L1176-L1183 dhcp_option_data_type_table + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/extract-word.c#L10-L188 extract_first_word (":" separator, coalescing) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L429-L478 safe_atou_full (base 0) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/in-addr-util.c#L517-L529 in_addr_from_string (inet_pton) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/escape.c#L104-L296 cunescape_one + * tests https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-dhcp-relay-downstream.network#L13-L14 + * https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-agent-bridge.network#L12 + * linux https://man7.org/linux/man-pages/man3/inet_pton.3.html + */ + +/** + * Validator for config_parse_dhcp_option with ltype `true` (the gperf writes `/* check_length= */ true`): + * `[DHCPRelay] CircuitId=`, `RemoteId=`, `VirtualSubnetSelection=` and the deprecated + * `[DHCPServer] RelayAgentCircuitId=` / `RelayAgentRemoteId=`. + * + * The value is `TYPE:DATA` — see [dhcpOptionTypedData]: `uint8`/`uint16`/`uint32` with a number in range + * (base 0, so hex/octal are legal), `ipv4address`/`ipv6address` with a bare address, or `string` with any + * C-escaped text. With check_length set, systemd additionally refuses a string longer than 255 bytes once + * unescaped; that length limit is not modelled (lenient). + */ +class ConfigParseDhcpOptionTypedOptionValue : SimpleGrammarOptionValues( + "config_parse_dhcp_option", + SequenceCombinator( + ZeroOrMore(COLON), + dhcpOptionTypedData(), + EOF(), + ) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpOrRaRouteTableOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpOrRaRouteTableOptionValue.kt new file mode 100644 index 00000000..4a8a1a75 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpOrRaRouteTableOptionValue.kt @@ -0,0 +1,36 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.ROUTE_TABLE_FROM_STRING + +/* + * [DHCPv4] RouteTable=, [DHCPv6] RouteTable=, [IPv6AcceptRA] RouteTable= (and the old [DHCP] RouteTable=) (.network). + * + * Note: the plugin reports the deprecated [DHCP] section as not allowed in .network files, so this validator never + * actually runs for the key there; it is listed only because the gperf shares the parser. + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#RouteTable=num + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/networkd-dhcp-common.c#L560-L606 config_parse_dhcp_or_ra_route_table (NETWORK_CONFIG_SOURCE_DHCP4 / _DHCP6 / _NDISC) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/network/networkd-route-util.c#L435-L464 manager_get_route_table_from_string + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/network/networkd-route-util.c#L496-L616 config_parse_route_table_names (what a user-defined name may be) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L429-L478 safe_atou_full (safe_atou32, base 0) + * tests https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-dhcp-client-ipv4-only.network#L17 + */ + +/** + * Validator for `RouteTable=` in [DHCPv4], [DHCPv6] and [IPv6AcceptRA] (config_parse_dhcp_or_ra_route_table). The + * ltype only picks which Network field receives the table. The parser hands the whole value to + * manager_get_route_table_from_string() and adds nothing of its own: no special empty value and no extra bounds. + * So the grammar is the shared [ROUTE_TABLE_FROM_STRING]: `default`, `main` or `local` (case-sensitive), a table + * number 1…4294967295 read by safe_atou32 (so `0x10`, `010` and `0b 1` are numbers too, and `0` is refused), or + * anything networkd.conf could have defined as a table name (non-empty, no `:`, not all digits). + * + * The name branch is lenient by design: networkd.conf is not visible to the plugin, so `mytable` is accepted + * although systemd refuses it when networkd.conf does not define it. What is flagged is what systemd refuses + * whatever networkd.conf says: `0`, `00`, `08`, `4294967296` and anything containing `:` (checked with + * systemd's parser at 0f0682b773 on all three keys). + */ +class ConfigParseDhcpOrRaRouteTableOptionValue : SimpleGrammarOptionValues( + "config_parse_dhcp_or_ra_route_table", + ROUTE_TABLE_FROM_STRING, +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpPdPrefixRouteTypeOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpPdPrefixRouteTypeOptionValue.kt new file mode 100644 index 00000000..0dc04000 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpPdPrefixRouteTypeOptionValue.kt @@ -0,0 +1,30 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.EOF +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.FlexibleLiteralChoiceTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator + +/* + * [DHCPv4] UnassignedSubnetPolicy=, [DHCPv6] UnassignedSubnetPolicy= (.network). + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#UnassignedSubnetPolicy= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/networkd-dhcp-prefix-delegation.c#L1472-L1518 config_parse_dhcp_pd_prefix_route_type (0) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/network/networkd-route-util.c#L341-L356 route_type_table / route_type_from_string + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/network/networkd-route-util.c#L47-L49 route_type_is_reject + */ + +/** + * Validator for `UnassignedSubnetPolicy=` (config_parse_dhcp_pd_prefix_route_type). + * + * Accepts `none`, or a route type that route_type_is_reject() accepts: `unreachable`, `prohibit`, + * `blackhole` or `throw`. Other route types (`unicast`, `local`, …) parse but are then refused. No + * boolean; case-sensitive (streq / string table). + */ +class ConfigParseDhcpPdPrefixRouteTypeOptionValue : SimpleGrammarOptionValues( + "config_parse_dhcp_pd_prefix_route_type", + SequenceCombinator( + FlexibleLiteralChoiceTerminal("none", "unreachable", "prohibit", "blackhole", "throw"), + EOF(), + ) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpPdSubnetIdOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpPdSubnetIdOptionValue.kt new file mode 100644 index 00000000..81c70072 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpPdSubnetIdOptionValue.kt @@ -0,0 +1,48 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.AlternativeCombinator +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.EOF +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.FlexibleLiteralChoiceTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SafeAtoTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator +import java.math.BigInteger + +/* + * [DHCPPrefixDelegation] SubnetId= (and the old [DHCPv6PrefixDelegation] SubnetId=) (.network). + * + * Note: the plugin reports the deprecated [DHCPv6PrefixDelegation] section as not allowed in .network files, so this + * validator never actually runs for keys there; they are listed only because the gperf shares the parser. + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#SubnetId= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/networkd-dhcp-prefix-delegation.c#L1428-L1470 config_parse_dhcp_pd_subnet_id (0) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L522-L558 safe_atollu_full (safe_atoux64: explicit base 16) + * tests https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-dhcp6pd-upstream.network#L16 + */ + +/** + * Validator for `SubnetId=` (config_parse_dhcp_pd_subnet_id): `auto`, or a *hexadecimal* number (always + * base 16 — `SubnetId=10` means 0x10 — with an optional `0x`) no larger than INT64_MAX, i.e. + * 0x7fffffffffffffff. + * + * `auto` comes first, but `a`, `add` etc. are hex numbers too; each branch ends in EOF so the classic + * engine can't strand one in the other. + */ +class ConfigParseDhcpPdSubnetIdOptionValue : SimpleGrammarOptionValues( + "config_parse_dhcp_pd_subnet_id", + AlternativeCombinator( + SequenceCombinator(FlexibleLiteralChoiceTerminal("auto"), EOF()), + SequenceCombinator( + SafeAtoTerminal( + signed = false, + base = 16, + flags = emptySet(), + minInclusive = BigInteger.ZERO, + maxInclusive = SafeAtoTerminal.INT64_MAX, + acceptsWhitespace = true, + acceptsMinus = true, + ), + EOF(), + ), + ) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpRelayInterfaceModeOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpRelayInterfaceModeOptionValue.kt new file mode 100644 index 00000000..ed768a8c --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpRelayInterfaceModeOptionValue.kt @@ -0,0 +1,24 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.EOF +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.FlexibleLiteralChoiceTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator + +/* + * [Network] DHCPRelay= (.network). + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#DHCPRelay= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/networkd-dhcp-relay.c#L376-L380 config_parse_dhcp_relay_interface_mode (0), DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/network/networkd-dhcp-relay.c#L369-L372 dhcp_relay_interface_mode_table + * tests https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-dhcp-relay-downstream.network + */ + +/** + * Validator for `[Network] DHCPRelay=`: `upstream` or `downstream` (case-sensitive, no boolean). The + * empty assignment resets to "unset" and never reaches the grammar. + */ +class ConfigParseDhcpRelayInterfaceModeOptionValue : SimpleGrammarOptionValues( + "config_parse_dhcp_relay_interface_mode", + SequenceCombinator(FlexibleLiteralChoiceTerminal("upstream", "downstream"), EOF()) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpRequestOptionsOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpRequestOptionsOptionValue.kt new file mode 100644 index 00000000..dfbccf8d --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpRequestOptionsOptionValue.kt @@ -0,0 +1,44 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.EOF +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SafeAtoTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.wordList + +/* + * [DHCPv4] RequestOptions= (AF_INET), [DHCPv6] RequestOptions= (AF_INET6) (.network). + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#RequestOptions= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/networkd-dhcp-common.c#L1110-L1174 config_parse_dhcp_request_options (AF_INET / AF_INET6) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/extract-word.c#L10-L188 extract_first_word (whitespace, flags 0) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L429-L478 safe_atou_full (safe_atou32, base 0) + */ + +/** + * Validator for `RequestOptions=` in both [DHCPv4] and [DHCPv6] (config_parse_dhcp_request_options; + * the two ltypes only pick which set is filled): a whitespace-separated list of option codes, each read + * by safe_atou32 and required to be 1…254 — for DHCPv6 too, although its option space is 16-bit; the C + * check is `i < 1 || i >= UINT8_MAX` for both families. + * + * Each code is a word from a whitespace split, so whitespace never reaches safe_atou32 + * (acceptsWhitespace = false): `RequestOptions=0b 1` is the words `0b` and `1`, and `0b` is refused + * ("DHCP request option is invalid", checked with systemd's parser at 0f0682b773). + */ +class ConfigParseDhcpRequestOptionsOptionValue : SimpleGrammarOptionValues( + "config_parse_dhcp_request_options", + SequenceCombinator( + wordList( + SafeAtoTerminal( + signed = false, + base = 0, + flags = emptySet(), + minInclusive = 1L, + maxInclusive = 254L, + acceptsWhitespace = false, + acceptsMinus = true, + ), + ), + EOF(), + ) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpServerAddressOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpServerAddressOptionValue.kt new file mode 100644 index 00000000..664b198d --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpServerAddressOptionValue.kt @@ -0,0 +1,59 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.CIDR_SEPARATOR +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.EOF +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.NetworkDhcpMiscCheckedSpan +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.IPV4_ADDR +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SafeAtoTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.ZeroOrOne + +/* + * [DHCPServer] ServerAddress= (.network). + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#ServerAddress= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/networkd-dhcp-server.c#L889-L932 config_parse_dhcp_server_address (0) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/in-addr-util.c#L799-L843 in_addr_prefix_from_string (AF_INET) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/network/networkd-dhcp-server.c#L922-L927 localhost / link-local → ignored + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/in-addr-util.c#L43-L47 in4_addr_is_link_local (169.254.0.0/16) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/in-addr-util.c#L121-L126 in4_addr_is_localhost (127.0.0.0/8) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/in-addr-util.c#L779-L797 in_addr_parse_prefixlen (safe_atou8, <= 32) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/in-addr-util.c#L517-L529 in_addr_from_string (inet_pton) + * tests https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-dhcp-server.network + * linux https://man7.org/linux/man-pages/man3/inet_pton.3.html + */ + +/** + * Validator for `[DHCPServer] ServerAddress=`: an IPv4 address with an optional `/PREFIXLEN` of 0…32 + * (read by safe_atou8, so base-0 spellings like `0x18` are legal, and whitespace after the `/` reaches + * the parser and is skipped), e.g. `192.168.5.1/24`. + * + * A localhost (127.0.0.0/8) or link-local (169.254.0.0/16) address is flagged: systemd ignores it with + * "DHCP server address cannot be a localhost or link-local address" (checked with systemd's parser at + * 0f0682b773). + */ +class ConfigParseDhcpServerAddressOptionValue : SimpleGrammarOptionValues( + "config_parse_dhcp_server_address", + SequenceCombinator( + NetworkDhcpMiscCheckedSpan(IPV4_ADDR, "NotLoopbackOrLinkLocal") { addr -> + val o = addr.split('.').map { it.toIntOrNull() } + !(o.getOrNull(0) == 127 || (o.getOrNull(0) == 169 && o.getOrNull(1) == 254)) + }, + ZeroOrOne( + SequenceCombinator( + CIDR_SEPARATOR, + SafeAtoTerminal( + signed = false, + base = 0, + flags = emptySet(), + minInclusive = 0L, + maxInclusive = 32L, + acceptsWhitespace = true, + acceptsMinus = true, + ), + ), + ), + EOF(), + ) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpServerIpv6OnlyPreferredOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpServerIpv6OnlyPreferredOptionValue.kt new file mode 100644 index 00000000..e90e3194 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpServerIpv6OnlyPreferredOptionValue.kt @@ -0,0 +1,39 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.EOF +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.NetworkDhcpMiscCheckedSpan +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.networkDhcpMiscParseSecUsec +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.PARSE_SEC_TIMESPAN + +/* + * [DHCPServer] IPv6OnlyPreferredSec= (.network). + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#IPv6OnlyPreferredSec= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/networkd-dhcp-server.c#L934-L974 config_parse_dhcp_server_ipv6_only_preferred (0) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/network/networkd-dhcp-server.c#L966-L970 t < MIN_V6ONLY_WAIT_USEC && !network_test_mode_enabled() + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/libsystemd-network/dhcp-protocol.h#L15 MIN_V6ONLY_WAIT_USEC (300 s) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/time-util.c#L1289-L1291 parse_sec + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/time-util.c#L1198-L1287 parse_time + * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-time-util.c#L21-L82 TEST(parse_sec) + * tests https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-dhcp-server-ipv6-only-mode.network#L16 + */ + +/** + * Validator for `[DHCPServer] IPv6OnlyPreferredSec=`: a time span as parse_sec() reads it (`30min`, + * `1h30min`, `.5h`, `infinity`) — see [PARSE_SEC_TIMESPAN]. + * + * The duration must also be at least MIN_V6ONLY_WAIT_USEC, 300 s (`5min`, `300`, `4min 60s`); anything + * shorter, `0` included, is ignored with "Invalid [DHCPServer] IPv6OnlyPreferredSec=" (checked with + * systemd's parser at 0f0682b773). The only exception is networkd's test mode + * (`SYSTEMD_NETWORK_TEST_MODE`), which systemd's own test suite uses for its `20s` fixture; that is not a + * production setting, so short values are flagged. + */ +class ConfigParseDhcpServerIpv6OnlyPreferredOptionValue : SimpleGrammarOptionValues( + "config_parse_dhcp_server_ipv6_only_preferred", + SequenceCombinator( + NetworkDhcpMiscCheckedSpan(PARSE_SEC_TIMESPAN, "AtLeast300s") { (networkDhcpMiscParseSecUsec(it) ?: Long.MAX_VALUE) >= 300_000_000L }, + EOF(), + ) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpStaticLeaseHostnameOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpStaticLeaseHostnameOptionValue.kt new file mode 100644 index 00000000..78069204 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpStaticLeaseHostnameOptionValue.kt @@ -0,0 +1,26 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.EOF +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.LDH_DNS_NAME +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator + +/* + * [DHCPServerStaticLease] Hostname= (.network). + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#Hostname= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/networkd-dhcp-server-static-lease.c#L222-L262 config_parse_dhcp_static_lease_hostname (0) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/shared/dns-domain.h#L38-L47 dns_name_is_valid_ldh (DNS_LABEL_LDH|DNS_LABEL_NO_ESCAPES) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/shared/dns-domain.c#L24-L150 dns_label_unescape + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/hostname-util.c#L42-L48 valid_ldh_char + */ + +/** + * Validator for `[DHCPServerStaticLease] Hostname=`: a DNS name made of LDH labels — letters, digits and + * hyphens, no leading or trailing hyphen, 1…63 characters each — joined by dots, with an optional single + * trailing dot; no escapes (see [LDH_DNS_NAME]). The 253-character total is not enforced. + */ +class ConfigParseDhcpStaticLeaseHostnameOptionValue : SimpleGrammarOptionValues( + "config_parse_dhcp_static_lease_hostname", + SequenceCombinator(LDH_DNS_NAME, EOF()) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDnsOverTlsModeOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDnsOverTlsModeOptionValue.kt new file mode 100644 index 00000000..cbcd0a40 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDnsOverTlsModeOptionValue.kt @@ -0,0 +1,34 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.BOOLEAN +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.EOF +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.FlexibleLiteralChoiceTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.PreferValidAlternativeCombinator +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator + +/* + * [Network] DNSOverTLS= (.network) and [Resolve] DNSOverTLS= (resolved.conf). + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#DNSOverTLS= + * https://www.freedesktop.org/software/systemd/man/latest/resolved.conf.html#DNSOverTLS= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/shared/resolve-util.c#L10 config_parse_dns_over_tls_mode (0), DEFINE_CONFIG_PARSE_ENUM + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/shared/resolve-util.c#L26-L31 dns_over_tls_mode_table (DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/string-table.c#L32-L43 string_table_lookup_from_string_with_boolean + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L23-L46 parse_boolean + */ + +/** + * Validator for `DNSOverTLS=` (config_parse_dns_over_tls_mode): `opportunistic`, or any boolean + * (parse_boolean first, case-insensitively; then the case-sensitive table `no`/`opportunistic`/`yes`). + */ +class ConfigParseDnsOverTlsModeOptionValue : SimpleGrammarOptionValues( + "config_parse_dns_over_tls_mode", + SequenceCombinator( + PreferValidAlternativeCombinator( + FlexibleLiteralChoiceTerminal("opportunistic"), + BOOLEAN, + ), + EOF(), + ) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDnssecNegativeTrustAnchorsOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDnssecNegativeTrustAnchorsOptionValue.kt new file mode 100644 index 00000000..8c9c26f3 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDnssecNegativeTrustAnchorsOptionValue.kt @@ -0,0 +1,27 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.DNS_NAME_WORD +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.EOF +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.wordList + +/* + * [Network] DNSSECNegativeTrustAnchors= (.network). + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#DNSSECNegativeTrustAnchors= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/networkd-dns.c#L270-L319 config_parse_dnssec_negative_trust_anchors (0) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/extract-word.c#L10-L188 extract_first_word (whitespace, flags 0) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/shared/dns-domain.h#L26-L36 dns_name_is_valid + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/shared/dns-domain.c#L24-L150 dns_label_unescape + */ + +/** + * Validator for `[Network] DNSSECNegativeTrustAnchors=`: a whitespace-separated list of DNS names, each + * checked by dns_name_is_valid() (non-LDH: any printable label characters, single dots, optional + * trailing dot, `.` for the root). See [DNS_NAME_WORD]. + */ +class ConfigParseDnssecNegativeTrustAnchorsOptionValue : SimpleGrammarOptionValues( + "config_parse_dnssec_negative_trust_anchors", + SequenceCombinator(wordList(DNS_NAME_WORD), EOF()) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDomainsOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDomainsOptionValue.kt new file mode 100644 index 00000000..b17cdd76 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDomainsOptionValue.kt @@ -0,0 +1,55 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.DNS_NAME_WORD +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.NetworkDhcpMiscCheckedSpan +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.networkDhcpMiscIsLocalhost +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.EOF +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.LiteralChoiceTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.ZeroOrOne +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.wordList + +/* + * [Network] Domains= (.network). + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#Domains= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/networkd-dns.c#L134-L209 config_parse_domains (0) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/extract-word.c#L10-L188 extract_first_word (whitespace, flags 0) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/shared/dns-domain.c#L742-L749 dns_name_is_root + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/shared/dns-domain.c#L24-L150 dns_label_unescape (via dns_name_normalize, no LDH) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/hostname-util.c#L145-L161 is_localhost + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/network/networkd-dns.c#L192-L197 is_localhost(domain) → ignored + * tests https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-state-file-tests.network#L13 + * https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/24-search-domain.network#L9 + */ + +// One Domains= word: an optional `~` (route-only), then the root domain `.`, the wildcard `*`, or a +// DNS name. `~` on its own is the root routing domain (dns_name_is_root("") is true). `*` needs no +// branch of its own: it is already a valid one-label non-LDH DNS name, and a separate `*` literal would +// strand the classic engine on a label such as `*foo`. +// +// The localhost check runs on the whole word, minus exactly one leading `~` as in the C: checking only +// the name part would let the parse engine read `~localhost` as a one-label name that starts with `~`. +private val DOMAIN_WORD = NetworkDhcpMiscCheckedSpan( + SequenceCombinator( + ZeroOrOne(LiteralChoiceTerminal("~")), + ZeroOrOne(DNS_NAME_WORD), + ), + "NotLocalhost", +) { !networkDhcpMiscIsLocalhost(it.removePrefix("~")) } + +/** + * Validator for `[Network] Domains=` (config_parse_domains): a whitespace-separated list of search + * domains, each optionally prefixed with `~` to make it a routing-only domain; `.`, `~.` and `*` select + * the root/wildcard routing domain. Names are validated as non-LDH DNS names ([DNS_NAME_WORD]), so + * underscores and most punctuation are fine but empty labels (`a..b`, `.a`) are not. + * + * `localhost`, `localhost.localdomain` and their subdomains (is_localhost, any case, with or without `~`) + * are flagged: systemd ignores them with "'localhost' domain may not be configured as search or route + * domain" (checked with systemd's parser at 0f0682b773). + */ +class ConfigParseDomainsOptionValue : SimpleGrammarOptionValues( + "config_parse_domains", + SequenceCombinator(wordList(DOMAIN_WORD), EOF()) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDuidRawdataOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDuidRawdataOptionValue.kt new file mode 100644 index 00000000..e54a791c --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDuidRawdataOptionValue.kt @@ -0,0 +1,57 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.NETWORK_DHCP_MISC_COALESCED_COLONS +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.COLON +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.EOF +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.RegexTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.Repeat +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.ZeroOrMore +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.ZeroOrOne + +/* + * DUIDRawData= — [DHCPv6] DUIDRawData= (.network, config_parse_duid_rawdata), [DHCPv4] DUIDRawData= and + * the old [DHCP] DUIDRawData= (.network, config_parse_network_duid_rawdata), and [DHCPv4]/[DHCPv6] + * DUIDRawData= in networkd.conf (config_parse_duid_rawdata). + * + * Note: the plugin reports the deprecated [DHCP] section as not allowed in .network files, so this + * validator never actually runs for keys there; they are listed only because the gperf shares the parser. + * + * man https://www.freedesktop.org/software/systemd/man/latest/networkd.conf.html#DUIDRawData= + * https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#DUIDRawData= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/networkd-dhcp-common.c#L1321-L1392 config_parse_duid_rawdata (0) + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/networkd-dhcp-common.c#L1418-L1439 config_parse_network_duid_rawdata (0) → config_parse_duid_rawdata + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/extract-word.c#L10-L188 extract_first_word (":" separator, coalescing) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/libsystemd-network/dhcp-duid-internal.h#L26 MAX_DUID_DATA_LEN (128) + * tests https://github.com/systemd/systemd/blob/0f0682b773/src/network/test-networkd-conf.c#L108-L122 + * https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-dhcp-client-ipv6-only-custom-client-identifier.network#L15 + */ + +// One byte: one or two hex digits (`0` is 0x00, `ab` is 0xab). The shape is wider so `000` / `abc` +// highlight as a single bad byte. +private val DUID_BYTE = RegexTerminal("""[0-9a-zA-Z]+""", """[0-9a-fA-F]{1,2}(?![0-9a-zA-Z])""") + +/** + * Validator for `DUIDRawData=`, registered for both config_parse_duid_rawdata and + * config_parse_network_duid_rawdata. + * + * Colon-separated bytes `NN:NN:…`, each one or two hex digits, at most MAX_DUID_DATA_LEN = 128 of them. + * The separator is extract_first_word(…, ":", 0), so runs of colons coalesce and leading/trailing colons + * are accepted — test-networkd-conf.c accepts `00:11:22:` and `11::`, and rejects `000:11:22`, + * `00:111:22` and `abcdef`. A value of nothing but colons parses to an empty DUID and is accepted too. + */ +class ConfigParseDuidRawdataOptionValue(validatorName: String = "config_parse_duid_rawdata") : SimpleGrammarOptionValues( + validatorName, + SequenceCombinator( + ZeroOrMore(COLON), + ZeroOrOne( + SequenceCombinator( + DUID_BYTE, + Repeat(SequenceCombinator(NETWORK_DHCP_MISC_COALESCED_COLONS, DUID_BYTE), 0, 127), + ZeroOrMore(COLON), + ), + ), + EOF(), + ) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDuidTypeOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDuidTypeOptionValue.kt new file mode 100644 index 00000000..8fe24079 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDuidTypeOptionValue.kt @@ -0,0 +1,92 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.AlternativeCombinator +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.NETWORK_DHCP_MISC_COALESCED_COLONS +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.COLON +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.Combinator +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.EOF +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.FlexibleLiteralChoiceTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.RegexTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SafeAtoTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.ZeroOrMore + +/* + * DUIDType= — [DHCPv6] DUIDType= (.network, config_parse_duid_type), [DHCPv4] DUIDType= and the old + * [DHCP] DUIDType= (.network, config_parse_network_duid_type), and [DHCPv4]/[DHCPv6] DUIDType= in + * networkd.conf (config_parse_duid_type). + * + * Note: the plugin reports the deprecated [DHCP] section as not allowed in .network files, so this + * validator never actually runs for keys there; they are listed only because the gperf shares the parser. + * + * man https://www.freedesktop.org/software/systemd/man/latest/networkd.conf.html#DUIDType= + * https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#DUIDType= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/networkd-dhcp-common.c#L1195-L1272 config_parse_duid_type (0) + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/networkd-dhcp-common.c#L1298-L1319 config_parse_network_duid_type (0) → config_parse_duid_type + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/network/networkd-dhcp-common.c#L1187-L1193 duid_type_table + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/extract-word.c#L10-L188 extract_first_word (":" separator, coalescing) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L599-L613 safe_atou16_full (base 0) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/time-util.c#L1083-L1145 parse_timestamp + * tests https://github.com/systemd/systemd/blob/0f0682b773/src/network/test-networkd-conf.c#L25-L37 + * https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-dhcp-client-ipv6-only-custom-client-identifier.network#L14 + */ + +private val DUID_TYPE_NAME = FlexibleLiteralChoiceTerminal("link-layer-time", "vendor", "link-layer", "uuid") + +// parse_timestamp() takes the whole remainder (it may itself contain spaces and colons, e.g. +// "2000-01-01 00:00:00 UTC", "@1700000000", "yesterday"); its grammar is large and is not modelled. +private val DUID_TIMESTAMP = RegexTerminal(""".+""", """.+""") + +private fun duidType(): Combinator = SequenceCombinator( + ZeroOrMore(COLON), + AlternativeCombinator( + // Only DUID-LLT (type 1) may carry a time; spelled either by name or as the number 1. + SequenceCombinator(FlexibleLiteralChoiceTerminal("link-layer-time"), NETWORK_DHCP_MISC_COALESCED_COLONS, DUID_TIMESTAMP, EOF()), + SequenceCombinator( + SafeAtoTerminal( + signed = false, + base = 0, + flags = emptySet(), + minInclusive = 1L, + maxInclusive = 1L, + acceptsWhitespace = true, + acceptsMinus = true, + ), + NETWORK_DHCP_MISC_COALESCED_COLONS, + DUID_TIMESTAMP, + EOF(), + ), + SequenceCombinator(DUID_TYPE_NAME, ZeroOrMore(COLON), EOF()), + SequenceCombinator( + SafeAtoTerminal( + signed = false, + base = 0, + flags = emptySet(), + minInclusive = 0L, + maxInclusive = 65_535L, + acceptsWhitespace = true, + acceptsMinus = true, + ), + ZeroOrMore(COLON), + EOF(), + ), + ), +) + +/** + * Validator for `DUIDType=`, registered for both config_parse_duid_type and config_parse_network_duid_type + * (the latter only forwards to the former, twice). + * + * The value is `TYPE[:TIME]`. TYPE is one of `link-layer-time`, `vendor`, `link-layer`, `uuid` + * (case-sensitive) or any number 0…65535 read by safe_atou16 (so `0x42`, as systemd's own test data + * writes, is legal). A `:TIME` suffix — anything parse_timestamp() accepts — is allowed only when the type + * is DUID-LLT, i.e. `link-layer-time` or the number 1; `vendor:2000-01-01` is refused, as + * test-networkd-conf.c checks. The separator coalesces, so leading or trailing colons are harmless. + * + * The timestamp text itself is accepted as-is (lenient). + */ +class ConfigParseDuidTypeOptionValue(validatorName: String = "config_parse_duid_type") : SimpleGrammarOptionValues( + validatorName, + duidType(), +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseFdbInterfaceOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseFdbInterfaceOptionValue.kt new file mode 100644 index 00000000..4ac2c4a0 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseFdbInterfaceOptionValue.kt @@ -0,0 +1,24 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.EOF +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.IFNAME_OR_IFINDEX +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator + +/* + * [BridgeFDB] OutgoingInterface= (.network). + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#OutgoingInterface= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/networkd-bridge-fdb.c#L485-L539 config_parse_fdb_interface (0) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L112-L124 parse_ifindex + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/socket-util.c#L785-L830 ifname_valid_full (IFNAME_VALID_ALTERNATIVE) + */ + +/** + * Validator for `[BridgeFDB] OutgoingInterface=` (config_parse_fdb_interface): a positive interface + * index, or an alternative interface name of up to 127 characters — see [IFNAME_OR_IFINDEX]. + */ +class ConfigParseFdbInterfaceOptionValue : SimpleGrammarOptionValues( + "config_parse_fdb_interface", + SequenceCombinator(IFNAME_OR_IFINDEX, EOF()) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIgnoreCarrierLossOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIgnoreCarrierLossOptionValue.kt new file mode 100644 index 00000000..beb82af7 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIgnoreCarrierLossOptionValue.kt @@ -0,0 +1,33 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.AlternativeCombinator +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.BOOLEAN +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.EOF +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.PARSE_SEC_TIMESPAN + +/* + * [Network] IgnoreCarrierLoss= (.network). + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#IgnoreCarrierLoss= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/networkd-network.c#L1096-L1138 config_parse_ignore_carrier_loss (0) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L23-L46 parse_boolean + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/time-util.c#L1289-L1291 parse_sec + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/time-util.c#L1198-L1287 parse_time + * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-time-util.c#L21-L82 TEST(parse_sec) + * tests https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-bridge99-ignore-carrier-loss.network#L9 + */ + +/** + * Validator for `[Network] IgnoreCarrierLoss=`: a boolean (case-insensitive; yes = infinity, no = 0), or + * a time span as parse_sec() reads it (`5s`, `1min 30s`, `1h30min`, `.5s`, `infinity`) — see + * [PARSE_SEC_TIMESPAN]. + */ +class ConfigParseIgnoreCarrierLossOptionValue : SimpleGrammarOptionValues( + "config_parse_ignore_carrier_loss", + AlternativeCombinator( + SequenceCombinator(BOOLEAN, EOF()), + SequenceCombinator(PARSE_SEC_TIMESPAN, EOF()), + ) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIpv4llAddressOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIpv4llAddressOptionValue.kt new file mode 100644 index 00000000..6b213a61 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIpv4llAddressOptionValue.kt @@ -0,0 +1,41 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.DOT +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.EOF +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.IntegerTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.Labeled +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.Role +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator + +/* + * [Network] IPv4LLStartAddress= (.network). + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#IPv4LLStartAddress= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/networkd-ipv4ll.c#L389-L429 config_parse_ipv4ll_address (0) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/in-addr-util.c#L517-L529 in_addr_from_string (inet_pton, AF_INET) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/in-addr-util.c#L49-L61 in4_addr_is_link_local_dynamic + * tests https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-dhcp-client-with-ipv4ll.network#L9 + * linux https://man7.org/linux/man-pages/man3/inet_pton.3.html + */ + +/** + * Validator for `[Network] IPv4LLStartAddress=`: an IPv4 address usable for dynamic IPv4 link-local + * assignment — inside 169.254.0.0/16 but outside its first and last /24 (RFC 3927 §2.1), i.e. + * 169.254.1.0 … 169.254.254.255. + * + * Every octet matches any number so a wrong address still highlights as an address; the ranges are + * then enforced per octet. + */ +class ConfigParseIpv4llAddressOptionValue : SimpleGrammarOptionValues( + "config_parse_ipv4ll_address", + SequenceCombinator( + Labeled( + Role.LITERAL, + SequenceCombinator( + IntegerTerminal(169, 170), DOT, IntegerTerminal(254, 255), DOT, IntegerTerminal(1, 255), DOT, IntegerTerminal(0, 256), + ), + ), + EOF(), + ) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIpv4llOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIpv4llOptionValue.kt new file mode 100644 index 00000000..722e0840 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIpv4llOptionValue.kt @@ -0,0 +1,23 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.BOOLEAN +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.EOF +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator + +/* + * [Network] IPv4LL= (.network; deprecated in favour of LinkLocalAddressing=). + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#LinkLocalAddressing= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/networkd-ipv4ll.c#L348-L387 config_parse_ipv4ll (0) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L23-L46 parse_boolean + */ + +/** + * Validator for the deprecated `[Network] IPv4LL=`: a boolean (parse_boolean, case-insensitive). systemd + * logs a deprecation warning for every assignment; the value itself is still applied. + */ +class ConfigParseIpv4llOptionValue : SimpleGrammarOptionValues( + "config_parse_ipv4ll", + SequenceCombinator(BOOLEAN, EOF()) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseLinkGroupOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseLinkGroupOptionValue.kt new file mode 100644 index 00000000..e235d344 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseLinkGroupOptionValue.kt @@ -0,0 +1,35 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.EOF +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SafeAtoTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator + +/* + * [Link] Group= (.network). + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#Group= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/networkd-network.c#L1054-L1094 config_parse_link_group (0) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L497-L520 safe_atoi (safe_atoi32, base 0 + mangle_base) + */ + +/** + * Validator for `[Link] Group=` (config_parse_link_group): an integer 0…2147483647, read by safe_atoi32 + * — so `0x10`, `0o17`, `0b101`, `010` (octal) and `+5` are all legal, and negatives are refused by the + * explicit `group < 0` check. + */ +class ConfigParseLinkGroupOptionValue : SimpleGrammarOptionValues( + "config_parse_link_group", + SequenceCombinator( + SafeAtoTerminal( + signed = true, + base = 0, + flags = emptySet(), + minInclusive = 0L, + maxInclusive = 2_147_483_647L, + acceptsWhitespace = true, + acceptsMinus = true, + ), + EOF(), + ) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseLldpMulticastModeOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseLldpMulticastModeOptionValue.kt new file mode 100644 index 00000000..1891dd2d --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseLldpMulticastModeOptionValue.kt @@ -0,0 +1,34 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.BOOLEAN +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.EOF +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.FlexibleLiteralChoiceTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.PreferValidAlternativeCombinator +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator + +/* + * [Network] EmitLLDP= (.network). + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#EmitLLDP= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/networkd-lldp-tx.c#L134-L174 config_parse_lldp_multicast_mode (0) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/network/networkd-lldp-tx.c#L126-L132 lldp_multicast_mode_table + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L23-L46 parse_boolean + * tests https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/23-emit-lldp.network#L7 + * https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/26-bridge-vlan-slave-issue-20373.network#L9 + */ + +/** + * Validator for `EmitLLDP=` (config_parse_lldp_multicast_mode): a boolean (case-insensitive; true means + * `nearest-bridge`) or one of `nearest-bridge`, `non-tpmr-bridge`, `customer-bridge` (case-sensitive). + */ +class ConfigParseLldpMulticastModeOptionValue : SimpleGrammarOptionValues( + "config_parse_lldp_multicast_mode", + SequenceCombinator( + PreferValidAlternativeCombinator( + FlexibleLiteralChoiceTerminal("nearest-bridge", "non-tpmr-bridge", "customer-bridge"), + BOOLEAN, + ), + EOF(), + ) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseMudUrlOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseMudUrlOptionValue.kt new file mode 100644 index 00000000..504f609b --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseMudUrlOptionValue.kt @@ -0,0 +1,43 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.C_ESCAPE_REGEX +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.EOF +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.LiteralChoiceTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.RegexTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator + +/* + * [DHCPv4] MUDURL=, [DHCPv6] MUDURL=, [LLDP] MUDURL= (.network). + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#MUDURL= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/networkd-util.c#L199-L238 config_parse_mud_url (0) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/web-util.c#L10-L24 http_url_is_valid + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/escape.c#L104-L296 cunescape_one (via cunescape, flags 0) + */ + +// After the scheme: at least one character, every one of them ASCII (ascii_is_valid), with backslashes +// only as valid C escapes (the whole value goes through cunescape first). +private val MUD_URL_REST = RegexTerminal( + """(?:[^\\]|\\[\s\S]?)+""", + """(?:[\x01-\x5B\x5D-\x7F]|$C_ESCAPE_REGEX)+\z""", +) + +/** + * Validator for the three `MUDURL=` settings (config_parse_mud_url). + * + * The value is cunescape()d and must then satisfy http_url_is_valid(): it starts with `http://` or + * `https://` (case-sensitive, STARTSWITH_SET), has something after the scheme, and is pure ASCII. The + * 255-byte limit on the unescaped URL is not modelled, and an escape that decodes to a non-ASCII byte + * (`\xff`) is accepted although systemd would then reject it (both lenient). + */ +class ConfigParseMudUrlOptionValue : SimpleGrammarOptionValues( + "config_parse_mud_url", + SequenceCombinator( + // An exact LiteralChoiceTerminal, not a Flexible one: the flexible shape regex would run on into the + // host name ("http://e") and never match a choice under the list-of-successes engine. + LiteralChoiceTerminal("http://", "https://"), + MUD_URL_REST, + EOF(), + ) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseNtpOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseNtpOptionValue.kt new file mode 100644 index 00000000..12533e1a --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseNtpOptionValue.kt @@ -0,0 +1,32 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.DNS_NAME_WORD +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.EOF +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.wordList + +/* + * [Network] NTP= (.network). + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#NTP= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/networkd-ntp.c#L48-L104 config_parse_ntp (0) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/extract-word.c#L10-L188 extract_first_word (whitespace, flags 0) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/shared/dns-domain.c#L1331-L1341 dns_name_is_valid_or_address + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/shared/dns-domain.c#L24-L150 dns_label_unescape + * tests https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-state-file-tests.network#L12 + */ + +/** + * Validator for `[Network] NTP=` (config_parse_ntp): a whitespace-separated list of NTP servers, each a + * DNS name or an IP address (dns_name_is_valid_or_address). + * + * Every IP literal systemd accepts here is also a syntactically valid non-LDH DNS name (`192.168.5.1` is + * four labels, `2001:db8::1` one label containing colons), so the grammar is simply a list of + * [DNS_NAME_WORD]s; what it catches is empty labels (`pool..ntp.org`, `.org`), double trailing dots, + * over-long labels and stray control characters. The MAX_NTP_SERVERS cap is not modelled. + */ +class ConfigParseNtpOptionValue : SimpleGrammarOptionValues( + "config_parse_ntp", + SequenceCombinator(wordList(DNS_NAME_WORD), EOF()) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseRadvDnsOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseRadvDnsOptionValue.kt new file mode 100644 index 00000000..5be47953 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseRadvDnsOptionValue.kt @@ -0,0 +1,35 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.AlternativeCombinator +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.EOF +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.IPV6_ADDR +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.LiteralChoiceTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.wordList + +/* + * [IPv6SendRA] DNS= (and the old [IPv6PrefixDelegation] DNS=) (.network). + * + * Note: the plugin reports the deprecated [IPv6PrefixDelegation] section as not allowed in .network files, so this + * validator never actually runs for keys there; they are listed only because the gperf shares the parser. + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#DNS= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/networkd-radv.c#L1285-L1347 config_parse_radv_dns (0) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/extract-word.c#L10-L188 extract_first_word (whitespace, flags 0) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/in-addr-util.c#L517-L529 in_addr_from_string (AF_INET6, inet_pton) + * tests https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-ipv6-prefix.network#L10 + * linux https://man7.org/linux/man-pages/man3/inet_pton.3.html + */ + +/** + * Validator for `[IPv6SendRA] DNS=` (config_parse_radv_dns): a whitespace-separated list of IPv6 + * addresses (inet_pton AF_INET6 — no brackets, ports or `%scope`) or the special word `_link_local`. + * + * systemd also drops the null address (`::`) with a warning; spellings of it are not singled out here + * (lenient). + */ +class ConfigParseRadvDnsOptionValue : SimpleGrammarOptionValues( + "config_parse_radv_dns", + SequenceCombinator(wordList(AlternativeCombinator(LiteralChoiceTerminal("_link_local"), IPV6_ADDR)), EOF()) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseRequiredForOnlineOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseRequiredForOnlineOptionValue.kt new file mode 100644 index 00000000..6fa9ef75 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseRequiredForOnlineOptionValue.kt @@ -0,0 +1,46 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.AlternativeCombinator +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.BOOLEAN +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.COLON +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.EOF +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.FlexibleLiteralChoiceTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.ZeroOrOne + +/* + * [Link] RequiredForOnline= (.network). + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#RequiredForOnline= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/networkd-network.c#L1010-L1052 config_parse_required_for_online (0) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/libsystemd/sd-network/network-util.c#L94-L137 parse_operational_state_range + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/libsystemd/sd-network/network-util.c#L44-L56 link_operstate_table + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L23-L46 parse_boolean + * tests https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-state-file-tests.network#L6 + * https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-activation-policy.network.d/required-yes.conf#L2 + */ + +// link_operstate_table, compared with streq (case-sensitive). +private val OPERSTATE = FlexibleLiteralChoiceTerminal( + "missing", "off", "no-carrier", "dormant", "degraded-carrier", "carrier", "degraded", "enslaved", "routable", +) + +/** + * Validator for `[Link] RequiredForOnline=` (config_parse_required_for_online). + * + * Tried first as an operational-state range — `MIN`, `MIN:`, `MIN:MAX` or `:MAX` over the names in + * link_operstate_table (split on the first `:`; a missing MIN means `missing`, a missing MAX means + * `routable`) — and failing that as a boolean (case-insensitive). + * + * parse_operational_state_range also refuses a range whose MIN is above its MAX (`routable:off`); that + * compares table positions rather than spelling and is not modelled (lenient). + */ +class ConfigParseRequiredForOnlineOptionValue : SimpleGrammarOptionValues( + "config_parse_required_for_online", + AlternativeCombinator( + SequenceCombinator(OPERSTATE, ZeroOrOne(SequenceCombinator(COLON, ZeroOrOne(OPERSTATE))), EOF()), + SequenceCombinator(COLON, OPERSTATE, EOF()), + SequenceCombinator(BOOLEAN, EOF()), + ) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseResolveSupportOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseResolveSupportOptionValue.kt new file mode 100644 index 00000000..5f43fc67 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseResolveSupportOptionValue.kt @@ -0,0 +1,37 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.BOOLEAN +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.EOF +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.FlexibleLiteralChoiceTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.PreferValidAlternativeCombinator +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator + +/* + * [Network] LLMNR= / MulticastDNS= (.network) and [Resolve] LLMNR= / MulticastDNS= (resolved.conf). + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#LLMNR= + * https://www.freedesktop.org/software/systemd/man/latest/resolved.conf.html#LLMNR= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/shared/resolve-util.c#L8 config_parse_resolve_support (0), DEFINE_CONFIG_PARSE_ENUM + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/shared/resolve-util.c#L12-L17 resolve_support_table (DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/string-table.c#L32-L43 string_table_lookup_from_string_with_boolean + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L23-L46 parse_boolean + * tests https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-state-file-tests.network#L14-L15 + */ + +/** + * Validator for `LLMNR=` and `MulticastDNS=` (config_parse_resolve_support): `resolve`, or any boolean. + * + * The lookup tries parse_boolean() first — case-insensitive, all twelve spellings — and only then the + * case-sensitive table (`no`, `yes`, `resolve`), so `Yes` is fine but `Resolve` is not. + */ +class ConfigParseResolveSupportOptionValue : SimpleGrammarOptionValues( + "config_parse_resolve_support", + SequenceCombinator( + PreferValidAlternativeCombinator( + FlexibleLiteralChoiceTerminal("resolve"), + BOOLEAN, + ), + EOF(), + ) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUplinkOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUplinkOptionValue.kt new file mode 100644 index 00000000..552d3792 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUplinkOptionValue.kt @@ -0,0 +1,49 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.AlternativeCombinator +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.EOF +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.FlexibleLiteralChoiceTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.IFNAME_OR_IFINDEX +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.KeyOrSectionDispatchingGrammarOptionValue +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator + +/* + * [DHCPServer] UplinkInterface=, [DHCPPrefixDelegation] UplinkInterface=, [IPv6SendRA] UplinkInterface=. + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#UplinkInterface= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/networkd-dhcp-common.c#L1441-L1516 config_parse_uplink (0) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/network/networkd-dhcp-common.c#L1463-L1475 section → accept_none (:none vs :self) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L112-L124 parse_ifindex + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/socket-util.c#L785-L830 ifname_valid_full (IFNAME_VALID_ALTERNATIVE) + * tests https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-dhcp6pd-upstream.network#L15 + * https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-dhcp-server-downstream.network#L13 + */ + +private fun uplink(vararg special: String) = AlternativeCombinator( + SequenceCombinator(FlexibleLiteralChoiceTerminal(*special), EOF()), + SequenceCombinator(IFNAME_OR_IFINDEX, EOF()), +) + +/** + * Validator for the three `UplinkInterface=` settings, all parsed by config_parse_uplink. + * + * Accepts `:auto`, a positive interface index, or an alternative interface name (see + * [IFNAME_OR_IFINDEX]), plus one section-specific word: `:none` in [DHCPServer] and [IPv6SendRA], `:self` + * in [DHCPPrefixDelegation] (the C sets `accept_none = false` for that section and its deprecated + * [DHCPv6PrefixDelegation] alias, which the plugin does not recognise as a section). The validator + * dispatches on the section of the property it checks. The special values are compared with streq, so + * they are case-sensitive. A `:none` in [DHCPPrefixDelegation] then fails ifname_valid_full (`:` is not + * a name character) and is ignored with a warning — flagged here. + */ +class ConfigParseUplinkOptionValue : KeyOrSectionDispatchingGrammarOptionValue( + VALIDATOR_NAME, + uplink(":auto", ":none", ":self"), + { _, section -> if (section == "DHCPPrefixDelegation" || section == "DHCPv6PrefixDelegation") SELF else NONE }, +) { + private companion object { + const val VALIDATOR_NAME = "config_parse_uplink" + val NONE = SimpleGrammarOptionValues(VALIDATOR_NAME, uplink(":auto", ":none")) + val SELF = SimpleGrammarOptionValues(VALIDATOR_NAME, uplink(":auto", ":self")) + } +} diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/BatchNetworkDhcpMiscCombinators.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/BatchNetworkDhcpMiscCombinators.kt new file mode 100644 index 00000000..315ea413 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/BatchNetworkDhcpMiscCombinators.kt @@ -0,0 +1,436 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar + +import com.intellij.codeInspection.ProblemsHolder +import com.intellij.psi.util.PsiTreeUtil +import net.sjrx.intellij.plugins.systemdunitfiles.psi.UnitFilePropertyType +import net.sjrx.intellij.plugins.systemdunitfiles.psi.UnitFileSectionGroups +import java.math.BigInteger + +/* + * Shared grammar pieces for the network-dhcp-misc validator batch (systemd-networkd DHCPv4/DHCPv6/DHCP + * server/DUID/DNS/NTP/LLDP and assorted [Network]/[Link] settings). + * + * Everything here is pinned to systemd 0f0682b773 (systemd-build/build/last_commit_hash); browse it at + * https://github.com/systemd/systemd/blob/0f0682b773. These live in their own file rather than in + * Combinators.kt so that parallel batches don't collide; several of them are candidates for promotion + * (see the batch's RECOMMENDATIONS.md). + * + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L23-L46 parse_boolean + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L112-L124 parse_ifindex + * + * Integers (safe_atou8/16/32, safe_atoi, safe_atoux64, parse_ifindex) are read with the shared [SafeAtoTerminal]. + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/escape.c#L104-L296 cunescape_one + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/extract-word.c#L10-L188 extract_first_word + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/socket-util.c#L785-L830 ifname_valid_full + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/shared/dns-domain.c#L24-L150 dns_label_unescape + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/shared/dns-domain.c#L304-L387 dns_name_concat + */ + +/** One or more `:` — extract_first_word(…, ":", 0) coalesces runs of its separator. */ +val NETWORK_DHCP_MISC_COALESCED_COLONS = OneOrMore(COLON) + +/** + * One C escape sequence as cunescape_one() accepts it: `\a \b \f \n \r \t \v \\ \" \' \s`, `\xHH`, + * `\uHHHH`, `\UHHHHHHHH`, or a three-digit octal `\OOO` no greater than 0377. (cunescape_one also refuses + * NUL unless asked, UTF-16 surrogates and out-of-range code points; those corners are left lenient.) + */ +const val C_ESCAPE_REGEX = """\\(?:[abfnrtv\\"'s]|x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8}|[0-3][0-7]{2})""" + +/** + * A run of text that cunescape() must be able to decode: any characters, with every backslash starting a + * valid [C_ESCAPE_REGEX]. A stray or trailing backslash is an error (no UNESCAPE_RELAX). Non-empty. + */ +val C_ESCAPED_TEXT = RegexTerminal("""(?:[^\\]|\\[\s\S]?)+""", """(?:[^\\]|$C_ESCAPE_REGEX)+\z""") + +/** + * One word as extract_first_word(…, NULL, EXTRACT_CUNESCAPE|EXTRACT_UNQUOTE) splits it: unquoted text, + * `"…"` and `'…'` sections (which may contain whitespace) glued together, with backslash escapes — + * inside or outside quotes — decoded by cunescape_one. An escape that cunescape_one doesn't know (`\q`, + * `\ `), a trailing backslash or an unterminated quote fails the whole assignment. + */ +val CUNESCAPE_UNQUOTE_WORD = RegexTerminal( + """(?:[^\s"'\\]|\\[\s\S]?|"(?:[^"\\]|\\[\s\S]?)*"?|'(?:[^'\\]|\\[\s\S]?)*'?)+""", + """(?:[^\s"'\\]|$C_ESCAPE_REGEX|"(?:[^"\\]|$C_ESCAPE_REGEX)*"|'(?:[^'\\]|$C_ESCAPE_REGEX)*')+(?=\s|\z)""", +) + +/** + * [CUNESCAPE_UNQUOTE_WORD] minus the words that unquote to the empty string (`""`, `''`, `""''`): + * config_parse_dhcp4_user_class and config_parse_dhcp6_user_or_vendor_class drop those with "The length of + * the … entry '' is not in the range 1…255/65535, ignoring" (checked with systemd's parser at 0f0682b773). + * The upper length bound is not modelled (lenient). + * + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/network/networkd-dhcp-common.c#L687-L692 len == 0 check (DHCPv4) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/network/networkd-dhcp-common.c#L732-L737 len == 0 check (DHCPv6) + */ +val NETWORK_DHCP_MISC_NONEMPTY_UNQUOTE_WORD: Combinator = NetworkDhcpMiscCheckedSpan(CUNESCAPE_UNQUOTE_WORD, "NonEmptyWord") { + !NETWORK_DHCP_MISC_EMPTY_QUOTES.matches(it) +} + +private val NETWORK_DHCP_MISC_EMPTY_QUOTES = Regex("""(?:""|'')+""") + +/** A whitespace-separated list of [word]s (the value arrives stripped, so no leading/trailing space). */ +fun wordList(word: Combinator): Combinator = SequenceCombinator( + word, + ZeroOrMore(SequenceCombinator(WhitespaceTerminal(), word)), +) + +// A note on the RegexTerminals below: each semantic regex either matches the *whole* span its syntactic +// regex matched or nothing (trailing lookaheads / \z). Under the classic engine a semantic match that +// stops short inside the syntactic span misaligns GrammarOptionValue's token bookkeeping and can index +// past the end of its token list (seen as an IndexOutOfBoundsException on DUIDRawData=000:11:22). + +// --------------------------------------------------------------------------------------------------- +// Interface name or index — `parse_ifindex(s) > 0 || ifname_valid_full(s, IFNAME_VALID_ALTERNATIVE)`, +// the pattern used by config_parse_uplink and config_parse_fdb_interface. +private const val IFNAME_CHAR = """[\x21-\x7E&&[^:/%]]""" + +private val IFNAME_ALTERNATIVE = Regex("""(?!(?:\.\.?|all|default)\z)$IFNAME_CHAR{1,127}""") + +/** + * A terminal that takes the span [shape] matches and accepts it iff [valid] says so, for leaves whose + * acceptance is a computation rather than a regular language. + */ +class CheckedTerminal(private val shape: Regex, private val name: String, private val valid: (String) -> Boolean) : TerminalCombinator { + + private fun span(value: String, offset: Int): String? = shape.matchAt(value, offset)?.value?.takeIf { it.isNotEmpty() } + + override fun SyntacticMatch(value: String, offset: Int): MatchResult { + val text = span(value, offset) ?: return NoMatch + return MatchResult(listOf(text), offset + text.length, listOf(this), offset + text.length) + } + + override fun SemanticMatch(value: String, offset: Int): MatchResult { + val text = span(value, offset) ?: return NoMatch + if (!valid(text)) return NoMatch.copy(longestMatch = offset) + return MatchResult(listOf(text), offset + text.length, listOf(this), offset + text.length) + } + + override fun parse(value: String, offset: Int): Sequence { + val text = span(value, offset) ?: return sequenceOf(Stuck(offset, setOf(this))) + return sequenceOf(Parse(offset + text.length, listOf(ParsedToken(offset, offset + text.length, text, this, valid(text))))) + } + + override fun toString(): String = name +} + +/** + * A positive interface index, or an alternative interface name: `parse_ifindex(s) > 0 || + * ifname_valid_full(s, IFNAME_VALID_ALTERNATIVE)`. + * + * parse_ifindex() is safe_atoi() (base 0) plus `> 0`, so `010` is index 8, `0x10` is 16 and `+5` is 5, while + * `0`, `08` and `99999999999` are refused. ifname_valid_full() then takes 1…127 printable ASCII characters + * other than `:`, `/`, `%`, but not `.`, `..`, `all`, `default`, or a spelling made only of digits (those + * failed parse_ifindex above, so they are zero, too large or malformed octal). + * + * The span is the whole value, whitespace included, because safe_atoi sees all of it: `0b 1`, `0o 7` and `\v5` + * are indexes 1, 7 and 5, while `eth 0` and `0b 0` are refused (checked with systemd's parser at 0f0682b773). + */ +val IFNAME_OR_IFINDEX = CheckedTerminal(Regex("""[\s\S]+"""), "IfnameOrIfindex") { text -> + SafeAtoTerminal( + signed = true, + base = 0, + flags = emptySet(), + minInclusive = 1L, + maxInclusive = Int.MAX_VALUE.toLong(), + acceptsWhitespace = true, + acceptsMinus = true, + ).valueOf(text) != null || + (!text.all { it in '0'..'9' } && IFNAME_ALTERNATIVE.matches(text)) +} + +// --------------------------------------------------------------------------------------------------- +// DNS names. + +// A character that may appear unescaped in a non-LDH DNS label (dns_label_unescape without +// DNS_LABEL_LDH): anything >= 0x20 except DEL, the `.` separator and the `\` escape introducer. In a +// whitespace-separated word list whitespace is also excluded (it ends the word). +private const val DNS_PLAIN = """[^\s.\\\p{Cntrl}]""" + +/** + * One non-LDH DNS label inside a whitespace-separated word. + * + * The words are cut by extract_first_word(…, NULL, 0), which strips one level of backslash (a raw `\x` + * becomes `x`), and the result then goes through dns_label_unescape, which treats a remaining `\` as the + * start of a `\\`, `\.` or `\DDD` (000…255) escape. So in the raw text: + * - `\x` for any `x` other than a backslash is just `x` (accepted as label content); + * - a doubled backslash `\\` survives as `\` and must start a valid dns_label_unescape escape: it must + * be followed by `\\` or `\.` (an escaped backslash/dot), by three digits no greater than 255, or by + * the `.` separator (raw `foo\\.bar` is `foo\.bar`, one label with a literal dot — accepted either + * way). `foo\\bar`, `foo\\256` and a trailing `foo\\` are refused, as systemd refuses them. + * + * Still lenient: a raw `\.` is treated as label content although extract_first_word turns it into a + * separator, so `a\..b` and `\.a` (empty labels once unescaped) are accepted. A label of 64 or more + * plain characters is refused: that is over DNS_LABEL_MAX whatever follows. + * + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/shared/dns-domain.c#L60-L100 dns_label_unescape escapes + */ +val DNS_WORD_LABEL = RegexTerminal( + """(?:[^\s.\\]|\\[\s\S])+""", + """(?!$DNS_PLAIN{64})(?:$DNS_PLAIN|\\[^\\]|\\\\(?:\\[\\.]|[01][0-9]{2}|2[0-4][0-9]|25[0-5]|(?=\.)))+(?=[\s.]|\z)""", +) + +/** + * A DNS name as dns_name_is_valid() accepts it, written as one whitespace-separated word: labels joined + * by single dots with an optional single trailing dot, or the root domain `.` on its own. Empty labels + * (`a..b`, `.a`) and a double trailing dot are rejected, as in dns_label_unescape. The 253-character + * whole-name limit is not enforced. + */ +val DNS_NAME_WORD = AlternativeCombinator( + SequenceCombinator(DNS_WORD_LABEL, ZeroOrMore(SequenceCombinator(DOT, DNS_WORD_LABEL)), ZeroOrOne(DOT)), + DOT, +) + +/** + * is_localhost(): `localhost`, `localhost.localdomain` and any name ending in `.localhost` or + * `.localhost.localdomain`, case-insensitively, with or without a trailing dot. [name] is a raw + * [DNS_NAME_WORD]; single-backslash escapes are dropped first, as extract_first_word drops them. + * + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/hostname-util.c#L145-L161 is_localhost + */ +fun networkDhcpMiscIsLocalhost(name: String): Boolean { + val n = name.replace(Regex("""\\(.)"""), "$1").lowercase().removeSuffix(".") + return n == "localhost" || n == "localhost.localdomain" || n.endsWith(".localhost") || n.endsWith(".localhost.localdomain") +} + +/** An LDH label: letters, digits and inner hyphens, 1…63 characters (dns_label_unescape with DNS_LABEL_LDH). */ +val LDH_DNS_LABEL = RegexTerminal( + """[A-Za-z0-9-]+""", + """[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?(?![A-Za-z0-9-])""", +) + +/** + * A DNS name as dns_name_is_valid_ldh() accepts it: [LDH_DNS_LABEL]s joined by single dots, an optional + * trailing dot, no escapes (DNS_LABEL_NO_ESCAPES), or the root domain `.`. The 253-character limit is not + * enforced. + */ +val LDH_DNS_NAME = AlternativeCombinator( + SequenceCombinator(LDH_DNS_LABEL, ZeroOrMore(SequenceCombinator(DOT, LDH_DNS_LABEL)), ZeroOrOne(DOT)), + DOT, +) + +// --------------------------------------------------------------------------------------------------- +// Typed DHCP option payloads — config_parse_dhcp_option / the tail of config_parse_dhcp6_send_option. + +/** + * [IPV6_ADDR] minus the two forms that begin with `::` (`::1`, `::ffff:1.2.3.4`, `::`) — for positions + * where a preceding coalescing `:` separator has already swallowed any leading colons. + */ +val IPV6_ADDR_NO_LEADING_COLON = Labeled(Role.LITERAL, tag = SemanticTag.IPV6, inner = AlternativeCombinator( + IPV6_IPV4_SUFFIX_FULL, + IPV6_IPV4_SUFFIX_ONE_HEXTET_BEFORE_ZERO_COMP, + IPV6_IPV4_SUFFIX_TWO_HEXTET_BEFORE_ZERO_COMP, + IPV6_IPV4_SUFFIX_THREE_HEXTET_BEFORE_ZERO_COMP, + IPV6_IPV4_SUFFIX_FOUR_HEXTET_BEFORE_ZERO_COMP, + IPV6_IPV4_SUFFIX_FIVE_HEXTET_BEFORE_ZERO_COMP, + IPV6_FULL_SPECIFIED, + IPV6_SEVEN_HEXTET_BEFORE_ZERO_COMP, + IPV6_SIX_HEXTET_BEFORE_ZERO_COMP, + IPV6_FIVE_HEXTET_BEFORE_ZERO_COMP, + IPV6_FOUR_HEXTET_BEFORE_ZERO_COMP, + IPV6_THREE_HEXTET_BEFORE_ZERO_COMP, + IPV6_TWO_HEXTET_BEFORE_ZERO_COMP, + IPV6_ONE_HEXTET_BEFORE_ZERO_COMP, +)) + +/** + * `TYPE:DATA`, the shape config_parse_dhcp_option() parses and config_parse_dhcp_option_tlv() / + * config_parse_dhcp6_send_option() parse after their numeric prefix(es). TYPE is one of + * dhcp_option_data_type_table and decides how the remainder is read: + * + * - `uint8` / `uint16` / `uint32` — safe_atou8/16/32 ([SafeAtoTerminal], base 0); + * - `ipv4address` / `ipv6address` — inet_pton() of that family, no prefix, no scope; + * - `string` — cunescape(…, UNESCAPE_ACCEPT_NUL): anything, with valid C escapes; may contain `:`. + * + * The separator is extract_first_word(…, ":", 0), which coalesces a run of colons, so `uint8::5` is + * fine. That coalescing also eats the leading colons of the data itself: for `ipv6address` an address + * beginning with `::` therefore cannot be written at all (`ipv6address:::1` hands inet_pton just `1` + * and fails), while `ipv6address::2001:db8::1` is fine. Hence [IPV6_ADDR_NO_LEADING_COLON] after + * the colon run. Likewise a `string` value cannot start with `:`. + * + * The number after `uintN:` is a field cut on `:`, so whitespace reaches safe_atou_full, which skips it + * (acceptsWhitespace = true). The string length check (`check_length`, <= 255 bytes once unescaped) is not + * modelled. + */ +fun dhcpOptionTypedData(): Combinator = AlternativeCombinator( + SequenceCombinator( + LiteralChoiceTerminal("uint8"), + NETWORK_DHCP_MISC_COALESCED_COLONS, + SafeAtoTerminal( + signed = false, + base = 0, + flags = emptySet(), + minInclusive = BigInteger.ZERO, + maxInclusive = SafeAtoTerminal.UINT8_MAX, + acceptsWhitespace = true, + acceptsMinus = true, + ), + ), + SequenceCombinator( + LiteralChoiceTerminal("uint16"), + NETWORK_DHCP_MISC_COALESCED_COLONS, + SafeAtoTerminal( + signed = false, + base = 0, + flags = emptySet(), + minInclusive = BigInteger.ZERO, + maxInclusive = SafeAtoTerminal.UINT16_MAX, + acceptsWhitespace = true, + acceptsMinus = true, + ), + ), + SequenceCombinator( + LiteralChoiceTerminal("uint32"), + NETWORK_DHCP_MISC_COALESCED_COLONS, + SafeAtoTerminal( + signed = false, + base = 0, + flags = emptySet(), + minInclusive = BigInteger.ZERO, + maxInclusive = SafeAtoTerminal.UINT32_MAX, + acceptsWhitespace = true, + acceptsMinus = true, + ), + ), + SequenceCombinator(LiteralChoiceTerminal("ipv4address"), NETWORK_DHCP_MISC_COALESCED_COLONS, IPV4_ADDR), + SequenceCombinator(LiteralChoiceTerminal("ipv6address"), NETWORK_DHCP_MISC_COALESCED_COLONS, IPV6_ADDR_NO_LEADING_COLON), + SequenceCombinator(LiteralChoiceTerminal("string"), NETWORK_DHCP_MISC_COALESCED_COLONS, C_ESCAPED_TEXT), +) + +// --------------------------------------------------------------------------------------------------- +// Time spans — parse_sec() / parse_time(…, USEC_PER_SEC). +// +// leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/time-util.c#L1146-L1196 extract_multiplier +// leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/time-util.c#L1198-L1287 parse_time +// tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-time-util.c + +// extract_multiplier's table, in table order; the first prefix that matches wins (startswith, no +// longest-match), hence the atomic group. Note: no `ns`/`nsec` — those belong to parse_nsec only. +private const val PARSE_SEC_UNIT_REGEX = + "(?>seconds|second|sec|s|minutes|minute|min|months|month|M|msec|ms|m|hours|hour|hr|h|days|day|d|weeks|week|w|years|year|y|usec|us|\u03bcs|\u00b5s)" + +/** + * One number of a parse_time() element: strtoll() digits (an optional `+`, never `-`) with an optional + * `.FRACTION`, or a bare `.FRACTION` (strtoll converts nothing, then the `.` branch runs: `.5s` is + * legal). A trailing `.` without digits (`5.`, `5.s`) is refused. + * + * The lookahead encodes the one adjacency rule of parse_time(): when no unit follows a number, the next + * element must be separated by whitespace (`5.5.5` and `5+5` fail, `5 .5` and `5s.5` are fine). So a + * number may only be followed by whitespace, the end, or the first letter of a unit. + */ +private val PARSE_SEC_NUMBER = RegexTerminal( + """\+?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]*)""", + """(?:\+?[0-9]+(?:\.[0-9]+)?|\.[0-9]+)(?=[ \t\n\r]|\z|[smMhdwyu\u03bc\u00b5])""", +) + +private val PARSE_SEC_UNIT = RegexTerminal(PARSE_SEC_UNIT_REGEX, PARSE_SEC_UNIT_REGEX) + +private val PARSE_SEC_ELEMENT = SequenceCombinator( + PARSE_SEC_NUMBER, + ZeroOrOne(SequenceCombinator(ZeroOrOne(WhitespaceTerminal()), PARSE_SEC_UNIT)), +) + +/** + * A time span exactly as parse_sec() reads it: `infinity`, or one or more `NUMBER [UNIT]` elements whose + * values are summed, with optional whitespace anywhere between a number and its unit and between + * elements — elements may also be written back to back after a unit (`1h30min`, `55s500ms`, `5s.5`). + * The default unit is seconds. A `-` anywhere at the start of an element is refused (`-0` too). + * + * Overflow (a sum >= USEC_INFINITY) is not modelled. The shared TIME_VALUE in Combinators.kt is both + * stricter (it needs whitespace between elements and a digit before `.`) and looser (it accepts the + * `ns`/`nsec` units parse_sec doesn't know); this one follows the C. + */ +val PARSE_SEC_TIMESPAN = AlternativeCombinator( + LiteralChoiceTerminal("infinity"), + SequenceCombinator(PARSE_SEC_ELEMENT, ZeroOrMore(SequenceCombinator(ZeroOrOne(WhitespaceTerminal()), PARSE_SEC_ELEMENT))), +) + +private val NETWORK_DHCP_MISC_PARSE_SEC_MULTIPLIERS: Map = mapOf( + "seconds" to 1_000_000L, "second" to 1_000_000L, "sec" to 1_000_000L, "s" to 1_000_000L, + "minutes" to 60_000_000L, "minute" to 60_000_000L, "min" to 60_000_000L, "m" to 60_000_000L, + "months" to 2_629_800_000_000L, "month" to 2_629_800_000_000L, "M" to 2_629_800_000_000L, + "msec" to 1_000L, "ms" to 1_000L, + "hours" to 3_600_000_000L, "hour" to 3_600_000_000L, "hr" to 3_600_000_000L, "h" to 3_600_000_000L, + "days" to 86_400_000_000L, "day" to 86_400_000_000L, "d" to 86_400_000_000L, + "weeks" to 604_800_000_000L, "week" to 604_800_000_000L, "w" to 604_800_000_000L, + "years" to 31_557_600_000_000L, "year" to 31_557_600_000_000L, "y" to 31_557_600_000_000L, + "usec" to 1L, "us" to 1L, "\u03bcs" to 1L, "\u00b5s" to 1L, +) + +private val NETWORK_DHCP_MISC_PARSE_SEC_STEP = + Regex("""[ \t\n\r]*\+?([0-9]*(?:\.[0-9]*)?)[ \t\n\r]*($PARSE_SEC_UNIT_REGEX)?""") + +/** + * The duration, in microseconds, that parse_sec() computes for a [PARSE_SEC_TIMESPAN]-shaped [text] + * (the elements summed, the default unit seconds, `infinity` as [Long.MAX_VALUE]), or null if [text] + * isn't of that shape. Fractions are evaluated exactly rather than digit-by-digit as the C does; the two + * differ by less than a microsecond per element. + */ +fun networkDhcpMiscParseSecUsec(text: String): Long? { + if (text == "infinity") return Long.MAX_VALUE + var total = java.math.BigDecimal.ZERO + var i = 0 + while (i < text.length) { + val m = NETWORK_DHCP_MISC_PARSE_SEC_STEP.matchAt(text, i) ?: return null + val number = m.groupValues[1] + if (m.value.isEmpty() || number.isEmpty() || number == ".") return null + val multiplier = m.groups[2]?.value?.let { NETWORK_DHCP_MISC_PARSE_SEC_MULTIPLIERS[it] ?: return null } ?: 1_000_000L + total += java.math.BigDecimal(if (number.startsWith(".")) "0$number" else number.trimEnd('.')) * java.math.BigDecimal(multiplier) + i = m.range.last + 1 + } + return total.toBigInteger().min(java.math.BigInteger.valueOf(Long.MAX_VALUE)).toLong() +} + +/** + * Wraps [inner] and additionally requires the whole span it matched to satisfy [valid] — for rules + * that are a computation over a structured value (a minimum duration, an excluded address range, a + * reserved name) rather than part of its shape. Matching, colouring and completion are [inner]'s; when + * [valid] fails, the first token of the span is reported as the semantic error (both engines). + */ +class NetworkDhcpMiscCheckedSpan( + private val inner: Combinator, + private val name: String, + private val valid: (String) -> Boolean, +) : Combinator { + + override fun SyntacticMatch(value: String, offset: Int): MatchResult = inner.SyntacticMatch(value, offset) + + override fun SemanticMatch(value: String, offset: Int): MatchResult { + val m = inner.SemanticMatch(value, offset) + if (m.matchResult == -1 || valid(value.substring(offset, m.matchResult))) return m + return NoMatch.copy(longestMatch = offset) + } + + override fun parse(value: String, offset: Int): Sequence = inner.parse(value, offset).map { step -> + if (step !is Parse || step.tokens.isEmpty() || valid(value.substring(offset, step.end))) step + else step.copy(tokens = listOf(step.tokens[0].copy(valid = false)) + step.tokens.drop(1)) + } + + override fun toString(): String = "$name(${inner})" + + override fun toStringIndented(indent: Int): String = " ".repeat(indent) + "$name(\n" + inner.toStringIndented(indent + 1) + "\n" + " ".repeat(indent) + ")" +} + +// --------------------------------------------------------------------------------------------------- +// Parsers whose accepted syntax depends on the key or section (they test `lvalue` / `section`). + +/** + * A grammar validator for a (parser, ltype) pair whose C code branches on the key name or the section + * name — e.g. config_parse_dhcp6_send_option (`streq(lvalue, "SendVendorOption")`) and + * config_parse_uplink (`:none` vs `:self` by section). + * + * The registration is still per (parser, ltype), but [generateProblemDescriptors] receives the PSI + * property, so it asks [select] for the grammar that applies to that (key, section) and validates with + * it. [combinator] (the [lenientUnion] of all shapes) is what the key-independent features — value + * colouring, completion, the deprecation annotator — see; it should accept the union of the shapes. + */ +open class KeyOrSectionDispatchingGrammarOptionValue( + validatorName: String, + lenientUnion: Combinator, + private val select: (key: String, section: String?) -> GrammarOptionValue, +) : GrammarOptionValue(validatorName, lenientUnion) { + + override fun generateProblemDescriptors(property: UnitFilePropertyType, holder: ProblemsHolder) { + val section = PsiTreeUtil.getParentOfType(property, UnitFileSectionGroups::class.java)?.sectionName + select(property.key, section).generateProblemDescriptors(property, holder) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseActivationPolicyOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseActivationPolicyOptionValueTest.kt new file mode 100644 index 00000000..f210dba0 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseActivationPolicyOptionValueTest.kt @@ -0,0 +1,45 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseActivationPolicyOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [Link] + ActivationPolicy=up + ActivationPolicy=always-up + ActivationPolicy=manual + ActivationPolicy=always-down + ActivationPolicy=down + ActivationPolicy=bound + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [Link] + ActivationPolicy=yes + ActivationPolicy=Up + ActivationPolicy=always + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(3, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseBridgeVlanIdOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseBridgeVlanIdOptionValueTest.kt new file mode 100644 index 00000000..491e3fee --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseBridgeVlanIdOptionValueTest.kt @@ -0,0 +1,51 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseBridgeVlanIdOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [BridgeVLAN] + PVID=1020 + PVID=0 + PVID=4094 + PVID=0x10 + PVID=no + PVID=False + PVID=oFf + PVID=FALSE + PVID= + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [BridgeVLAN] + PVID=4095 + PVID=yes + PVID=-1 + PVID=10-20 + PVID=TRUE + PVID=Yes + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(6, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcp4UserClassOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcp4UserClassOptionValueTest.kt new file mode 100644 index 00000000..06c3ac1f --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcp4UserClassOptionValueTest.kt @@ -0,0 +1,51 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseDhcp4UserClassOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [DHCPv4] + UserClass=foo + UserClass=foo bar baz + UserClass="with space" 'single quoted' + UserClass=esc\x41ped tab\tbed + UserClass=mid"dle quo"te + UserClass="a" + UserClass= + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [DHCPv4] + UserClass="unterminated + UserClass=bad\qescape + UserClass=bad\ escaped-space + UserClass="" + UserClass='' + UserClass=a "" + UserClass="" b + UserClass=""'' + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(8, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcp6SendOptionOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcp6SendOptionOptionValueTest.kt new file mode 100644 index 00000000..322b6ba9 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcp6SendOptionOptionValueTest.kt @@ -0,0 +1,63 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseDhcp6SendOptionOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [DHCPv6] + SendOption=1:string:\x41 + SendOption=65534:uint8:1 + SendOption=0x10:uint16:0x1234 + SendOption=23:ipv6address:2001:db8::53 + SendOption=23:ipv4address:10.0.0.1 + SendOption=12:uint32:4294967295 + SendOption=1:ipv6address::2001:db8::1 + SendOption=1:uint8:0b+1 + SendVendorOption=1: 2:uint8:0b 1 + SendVendorOption=123: 1:string:x + SendVendorOption=123:1:string:hello + SendVendorOption=0:1:uint8:2 + SendVendorOption=4294967295:65534:string:x + SendVendorOption= + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // An unknown TYPE (`bytes`, `int8`, `text`) makes systemd drop the WHOLE .network file ("Failed to parse + // file: Invalid argument"): DHCPOptionDataType has no negative enumerator, so the C's `type < 0` check + // never fires and the switch's `default: return -EINVAL` does (sd-check, 0f0682b773). + // language="unit file (systemd)" + val file = """ + [DHCPv6] + SendOption=0:string:x + SendOption=65535:string:x + SendOption=1:uint8: + SendOption=1:ipv6address:52:54:00:b9:b5:61 + SendOption=1:string:\U + SendVendorOption=4294967296:1:string:x + SendVendorOption=123:1:bytes:x + SendVendorOption=12:string:x + SendOption=123:1:string:x + SendOption=1:ipv6address:::1 + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(10, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcp6UserOrVendorClassOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcp6UserOrVendorClassOptionValueTest.kt new file mode 100644 index 00000000..2f1a3ff0 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcp6UserOrVendorClassOptionValueTest.kt @@ -0,0 +1,44 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseDhcp6UserOrVendorClassOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [DHCPv6] + UserClass=foo bar + UserClass="quoted value" + VendorClass=vendor-1 vendor\s2 + VendorClass= + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [DHCPv6] + UserClass='unterminated + VendorClass=bad\zescape + UserClass="" + VendorClass="" + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(4, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcpFallbackLeaseLifetimeOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcpFallbackLeaseLifetimeOptionValueTest.kt new file mode 100644 index 00000000..284222df --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcpFallbackLeaseLifetimeOptionValueTest.kt @@ -0,0 +1,42 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseDhcpFallbackLeaseLifetimeOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [DHCPv4] + FallbackLeaseLifetimeSec=forever + FallbackLeaseLifetimeSec=infinity + FallbackLeaseLifetimeSec= + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [DHCPv4] + FallbackLeaseLifetimeSec=5min + FallbackLeaseLifetimeSec=never + FallbackLeaseLifetimeSec=Infinity + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(3, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcpLabelOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcpLabelOptionValueTest.kt new file mode 100644 index 00000000..3c72db9e --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcpLabelOptionValueTest.kt @@ -0,0 +1,44 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseDhcpLabelOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [DHCPv4] + Label=label1 + Label=eth0:dhcp + Label=4444 + Label=a b/c%d + Label=exactly15chars! + Label= + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [DHCPv4] + Label=sixteen-chars-xx + Label=café + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(2, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcpOptionTlvOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcpOptionTlvOptionValueTest.kt new file mode 100644 index 00000000..86c9b32f --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcpOptionTlvOptionValueTest.kt @@ -0,0 +1,76 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseDhcpOptionTlvOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // [DHCPRelay] ExtraOption= shares this parser, but the plugin's section data doesn't know [DHCPRelay] + // yet, so it can't be exercised here. + // language="unit file (systemd)" + val file = """ + [DHCPv4] + SendOption=12:string:test + SendOption=1:uint8:255 + SendOption=0x0c:uint16:0xffff + SendOption=254:uint32:4294967295 + SendOption=014:ipv4address:192.168.1.1 + SendOption=17:ipv6address:2001:db8::1 + SendOption=60:string:has:colons:inside + SendOption=60:string:tab\there\x41\u00e9\101 é + SendOption=12::uint8::5 + SendOption=12:uint8: 7 + SendOption=12:uint8:0b+1 + SendOption=12:uint8:0b 1 + SendOption=12:ipv6address::2001:db8::1 + SendOption=12:ipv6address:::2001:db8::1 + SendOption= + SendVendorOption=43:string:vendor + [DHCPServer] + SendOption=42:ipv4address:10.0.0.1 + SendVendorOption=43:uint8:1 + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // An unknown TYPE (`bytes`, `int8`, `text`) makes systemd drop the WHOLE .network file ("Failed to parse + // file: Invalid argument"): DHCPOptionDataType has no negative enumerator, so the C's `type < 0` check + // never fires and the switch's `default: return -EINVAL` does (sd-check, 0f0682b773). + // language="unit file (systemd)" + val file = """ + [DHCPv4] + SendOption=0:string:test + SendOption=255:string:test + SendOption=12:uint8:256 + SendOption=12:uint16:65536 + SendOption=12:int8:5 + SendOption=12:ipv4address:300.1.1.1 + SendOption=12:string: + SendOption=12:string:bad\qescape + SendOption=12 + SendOption=abc:string:x + [DHCPServer] + SendVendorOption=12:ipv6address:1.2.3.4 + SendVendorOption=12:uint32:-1 + SendOption=12:ipv6address:::1 + SendOption=12:ipv6address:: + SendOption=12:uint8:0b-1 + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(15, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcpOptionTypedOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcpOptionTypedOptionValueTest.kt new file mode 100644 index 00000000..c8caec50 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcpOptionTypedOptionValueTest.kt @@ -0,0 +1,60 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseDhcpOptionTypedOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // [DHCPRelay] CircuitId=/RemoteId=/VirtualSubnetSelection= share this parser, but the plugin's + // section data doesn't know [DHCPRelay] yet; the deprecated [DHCPServer] spellings exercise it. + // language="unit file (systemd)" + val file = """ + [DHCPServer] + RelayAgentCircuitId=string:sample_circuit_id + RelayAgentRemoteId=string:aabbccdd + RelayAgentRemoteId=uint8:0xff + RelayAgentRemoteId=uint16:65535 + RelayAgentRemoteId=uint32:0 + RelayAgentRemoteId=ipv4address:192.0.2.1 + RelayAgentRemoteId=ipv6address:fe80::1 + RelayAgentRemoteId=ipv6address::2001:db8::1 + RelayAgentRemoteId=string:with\x00nul\ttab + RelayAgentRemoteId=::string:leading-colons + RelayAgentCircuitId= + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // An unknown TYPE (`bytes`, `int8`, `text`) makes systemd drop the WHOLE .network file ("Failed to parse + // file: Invalid argument"): DHCPOptionDataType has no negative enumerator, so the C's `type < 0` check + // never fires and the switch's `default: return -EINVAL` does (sd-check, 0f0682b773). + // language="unit file (systemd)" + val file = """ + [DHCPServer] + RelayAgentCircuitId=sample_circuit_id + RelayAgentCircuitId=text:hello + RelayAgentCircuitId=uint8:256 + RelayAgentCircuitId=uint16:-1 + RelayAgentCircuitId=ipv4address:192.0.2.1/24 + RelayAgentCircuitId=string + RelayAgentRemoteId=ipv6address:::1 + RelayAgentRemoteId=ipv6address::ffff:192.0.2.1 + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(8, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcpOrRaRouteTableOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcpOrRaRouteTableOptionValueTest.kt new file mode 100644 index 00000000..7023d304 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcpOrRaRouteTableOptionValueTest.kt @@ -0,0 +1,77 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +/** + * RouteTable= in [DHCPv4], [DHCPv6] and [IPv6AcceptRA] (config_parse_dhcp_or_ra_route_table). Every value was checked + * with systemd's parser at 0f0682b773 on all three keys, which agree. `mytable` and `Main` are rejected there only + * because sd-check loads no networkd.conf; they are accepted here on purpose (a networkd.conf RouteTable=NAME:NUMBER + * could define them). + */ +class ConfigParseDhcpOrRaRouteTableOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [DHCPv4] + RouteTable=211 + RouteTable=main + RouteTable=default + RouteTable=local + RouteTable=4294967295 + RouteTable=0x10 + RouteTable=010 + RouteTable=0b 1 + RouteTable=mytable + RouteTable=Main + RouteTable= + [DHCPv6] + RouteTable=211 + RouteTable=local + RouteTable=0x10 + RouteTable=mytable + [IPv6AcceptRA] + RouteTable=211 + RouteTable=main + RouteTable=0b 1 + RouteTable=mytable + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [DHCPv4] + RouteTable=0 + RouteTable=00 + RouteTable=08 + RouteTable=4294967296 + RouteTable=a:b + RouteTable=1:2 + [DHCPv6] + RouteTable=0 + RouteTable=4294967296 + RouteTable=a:b + [IPv6AcceptRA] + RouteTable=0 + RouteTable=08 + RouteTable=1:2 + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(12, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcpPdPrefixRouteTypeOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcpPdPrefixRouteTypeOptionValueTest.kt new file mode 100644 index 00000000..898c56f5 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcpPdPrefixRouteTypeOptionValueTest.kt @@ -0,0 +1,48 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseDhcpPdPrefixRouteTypeOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [DHCPv4] + UnassignedSubnetPolicy=none + UnassignedSubnetPolicy=unreachable + UnassignedSubnetPolicy= + [DHCPv6] + UnassignedSubnetPolicy=blackhole + UnassignedSubnetPolicy=prohibit + UnassignedSubnetPolicy=throw + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [DHCPv4] + UnassignedSubnetPolicy=unicast + UnassignedSubnetPolicy=no + [DHCPv6] + UnassignedSubnetPolicy=local + UnassignedSubnetPolicy=Blackhole + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(4, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcpPdSubnetIdOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcpPdSubnetIdOptionValueTest.kt new file mode 100644 index 00000000..61dc584e --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcpPdSubnetIdOptionValueTest.kt @@ -0,0 +1,49 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseDhcpPdSubnetIdOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // SubnetId= is always hexadecimal (safe_atoux64), so `ff` and `add` are numbers. + // language="unit file (systemd)" + val file = """ + [DHCPPrefixDelegation] + SubnetId=auto + SubnetId=0 + SubnetId=10 + SubnetId=ff + SubnetId=0xabcd + SubnetId=add + SubnetId=7fffffffffffffff + SubnetId= + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [DHCPPrefixDelegation] + SubnetId=8000000000000000 + SubnetId=Auto + SubnetId=xyz + SubnetId=1g + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(4, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcpRelayInterfaceModeOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcpRelayInterfaceModeOptionValueTest.kt new file mode 100644 index 00000000..47179f70 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcpRelayInterfaceModeOptionValueTest.kt @@ -0,0 +1,42 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseDhcpRelayInterfaceModeOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [Network] + DHCPRelay=upstream + DHCPRelay=downstream + DHCPRelay= + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [Network] + DHCPRelay=yes + DHCPRelay=both + DHCPRelay=Upstream + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(3, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcpRequestOptionsOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcpRequestOptionsOptionValueTest.kt new file mode 100644 index 00000000..43659785 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcpRequestOptionsOptionValueTest.kt @@ -0,0 +1,51 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseDhcpRequestOptionsOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [DHCPv4] + RequestOptions=59 60 + RequestOptions=17 + RequestOptions=1 254 0xfe 012 + RequestOptions=0b+1 0b1 + RequestOptions= + [DHCPv6] + RequestOptions=23 24 + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [DHCPv4] + RequestOptions=0 + RequestOptions=255 + RequestOptions=59,60 + RequestOptions=0b 1 + RequestOptions=0o 7 + [DHCPv6] + RequestOptions=dns + RequestOptions=0b-0 + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(7, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcpServerAddressOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcpServerAddressOptionValueTest.kt new file mode 100644 index 00000000..4284b30e --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcpServerAddressOptionValueTest.kt @@ -0,0 +1,54 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseDhcpServerAddressOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [DHCPServer] + ServerAddress=192.168.5.1/24 + ServerAddress=0.0.0.0/24 + ServerAddress=10.0.0.1 + ServerAddress=10.0.0.1/0 + ServerAddress=10.0.0.1/32 + ServerAddress=10.0.0.1/0x18 + ServerAddress=10.0.0.1/ 24 + ServerAddress=169.253.255.255/16 + ServerAddress=128.0.0.1 + ServerAddress= + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [DHCPServer] + ServerAddress=192.168.5.1/33 + ServerAddress=2001:db8::1/64 + ServerAddress=192.168.5 + ServerAddress=server.example.com + ServerAddress=127.0.0.1/8 + ServerAddress=127.255.0.1 + ServerAddress=169.254.1.1/16 + ServerAddress=169.254.0.0/16 + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(8, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcpServerIpv6OnlyPreferredOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcpServerIpv6OnlyPreferredOptionValueTest.kt new file mode 100644 index 00000000..a797d59b --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcpServerIpv6OnlyPreferredOptionValueTest.kt @@ -0,0 +1,78 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseDhcpServerIpv6OnlyPreferredOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // Anything >= MIN_V6ONLY_WAIT_USEC (300s) is accepted by systemd's parser (sd-check, 0f0682b773). + // language="unit file (systemd)" + val file = """ + [DHCPServer] + IPv6OnlyPreferredSec=5min + IPv6OnlyPreferredSec=5m + IPv6OnlyPreferredSec=4min 60s + IPv6OnlyPreferredSec=300000000us + IPv6OnlyPreferredSec=1M + IPv6OnlyPreferredSec=300 + IPv6OnlyPreferredSec=1h 5min + IPv6OnlyPreferredSec=1h30min + IPv6OnlyPreferredSec=.5h + IPv6OnlyPreferredSec=10min5s + IPv6OnlyPreferredSec=infinity + IPv6OnlyPreferredSec= + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [DHCPServer] + IPv6OnlyPreferredSec=yes + IPv6OnlyPreferredSec=5 fortnights + IPv6OnlyPreferredSec=-5min + IPv6OnlyPreferredSec=1h. + IPv6OnlyPreferredSec=1.2.3 + IPv6OnlyPreferredSec=Infinity + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(6, highlights) + } + + @Test + fun testBelowMinimumIsInvalid() { + // systemd: "Invalid [DHCPServer] IPv6OnlyPreferredSec=, ignoring assignment" for every value below + // 300s outside its test mode (sd-check, 0f0682b773). 20s is systemd's own test-suite value. + // language="unit file (systemd)" + val file = """ + [DHCPServer] + IPv6OnlyPreferredSec=20s + IPv6OnlyPreferredSec=0 + IPv6OnlyPreferredSec=299 + IPv6OnlyPreferredSec=299.999999s + IPv6OnlyPreferredSec=4min + IPv6OnlyPreferredSec=299999999us + IPv6OnlyPreferredSec=0.0833333h + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(7, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcpStaticLeaseHostnameOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcpStaticLeaseHostnameOptionValueTest.kt new file mode 100644 index 00000000..e6781889 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcpStaticLeaseHostnameOptionValueTest.kt @@ -0,0 +1,49 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseDhcpStaticLeaseHostnameOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [DHCPServerStaticLease] + Hostname=test-hostname + Hostname=fqdn.example.com + Hostname=fqdn.example.com. + Hostname=0123 + Hostname=a + Hostname=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.com + Hostname= + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [DHCPServerStaticLease] + Hostname=-leading + Hostname=trailing- + Hostname=under_score + Hostname=double..dot + Hostname=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + Hostname=with\.escape + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(6, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDnsOverTlsModeOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDnsOverTlsModeOptionValueTest.kt new file mode 100644 index 00000000..17f9289e --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDnsOverTlsModeOptionValueTest.kt @@ -0,0 +1,47 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseDnsOverTlsModeOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [Network] + DNSOverTLS=opportunistic + DNSOverTLS=yes + DNSOverTLS=no + DNSOverTLS=TRUE + DNSOverTLS=1 + DNSOverTLS=Yes + DNSOverTLS=oFf + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [Network] + DNSOverTLS=Opportunistic + DNSOverTLS=strict + DNSOverTLS=allow-downgrade + DNSOverTLS=OPPORTUNISTIC + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(4, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDnssecNegativeTrustAnchorsOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDnssecNegativeTrustAnchorsOptionValueTest.kt new file mode 100644 index 00000000..bb226b27 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDnssecNegativeTrustAnchorsOptionValueTest.kt @@ -0,0 +1,48 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseDnssecNegativeTrustAnchorsOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [Network] + DNSSECNegativeTrustAnchors=example.com + DNSSECNegativeTrustAnchors=test local. corp.example + DNSSECNegativeTrustAnchors=. + DNSSECNegativeTrustAnchors=_tcp.example + DNSSECNegativeTrustAnchors=foo\.bar + DNSSECNegativeTrustAnchors=foo\\\.bar + DNSSECNegativeTrustAnchors=localhost + DNSSECNegativeTrustAnchors= + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [Network] + DNSSECNegativeTrustAnchors=a..b + DNSSECNegativeTrustAnchors=good.example .. + DNSSECNegativeTrustAnchors=~example.com.. + DNSSECNegativeTrustAnchors=foo\\bar + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(4, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDomainsOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDomainsOptionValueTest.kt new file mode 100644 index 00000000..7c876458 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDomainsOptionValueTest.kt @@ -0,0 +1,65 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseDomainsOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [Network] + Domains=example.com + Domains=hogehoge ~foofoo + Domains=one two three four five six seven eight nine ten + Domains=~. + Domains=~ + Domains=. + Domains=* + Domains=~* + Domains=example.com. + Domains=_srv.example.com + Domains=foo\.bar + Domains=foo\\\\bar + Domains=foo\\.bar + Domains=foo\\\.bar + Domains=foo\\065bar + Domains=localhost.example.com + Domains= + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [Network] + Domains=example..com + Domains=.example.com + Domains=example.com.. + Domains=ok.example xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.com + Domains=localhost + Domains=foo.localhost + Domains=~localhost + Domains=Foo.LocalHost. + Domains=localhost.localdomain + Domains=x.localhost.localdomain + Domains=foo\\bar + Domains=foo\\256bar + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(12, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDuidRawdataOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDuidRawdataOptionValueTest.kt new file mode 100644 index 00000000..d9e9db1d --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDuidRawdataOptionValueTest.kt @@ -0,0 +1,54 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseDuidRawdataOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // [DHCPv6] uses config_parse_duid_rawdata and [DHCPv4] config_parse_network_duid_rawdata. + // Accepted/rejected spellings mirror systemd's test-networkd-conf.c. + // language="unit file (systemd)" + val file = """ + [DHCPv6] + DUIDRawData=00:00:ab:11:f9:2a:c2:77:29:f9:5c:00 + DUIDRawData=00:11:22:33:44:55:66:77 + DUIDRawData=00:11:22: + DUIDRawData=0:1:2:3:4:5:6:7 + DUIDRawData=11:: + DUIDRawData=ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab + DUIDRawData= + [DHCPv4] + DUIDRawData=AB:cd + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [DHCPv6] + DUIDRawData=000:11:22 + DUIDRawData=00:111:22 + DUIDRawData=abcdef + DUIDRawData=ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab + [DHCPv4] + DUIDRawData=00:gg + DUIDRawData=00: 11 + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(6, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDuidTypeOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDuidTypeOptionValueTest.kt new file mode 100644 index 00000000..703d8e4e --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDuidTypeOptionValueTest.kt @@ -0,0 +1,56 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseDuidTypeOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // [DHCPv6] uses config_parse_duid_type and [DHCPv4] config_parse_network_duid_type; same grammar. + // Values from systemd's test-networkd-conf.c and test-network fixtures. + // language="unit file (systemd)" + val file = """ + [DHCPv6] + DUIDType=link-layer-time + DUIDType=link-layer-time:2000-01-01 00:00:00 UTC + DUIDType=vendor + DUIDType=link-layer + DUIDType=uuid + DUIDType=0x42 + DUIDType=1:2000-01-01 00:00:00 UTC + DUIDType=vendor: + [DHCPv4] + DUIDType=uuid + DUIDType=link-layer-time:@1700000000 + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [DHCPv6] + DUIDType=foo + DUIDType=vendor:2000-01-01 00:00:00 UTC + DUIDType=uuid:2000-01-01 00:00:00 UTC + DUIDType=65536 + [DHCPv4] + DUIDType=Vendor + DUIDType=2:2000-01-01 + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(6, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseFdbInterfaceOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseFdbInterfaceOptionValueTest.kt new file mode 100644 index 00000000..d98cb471 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseFdbInterfaceOptionValueTest.kt @@ -0,0 +1,53 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseFdbInterfaceOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [BridgeFDB] + OutgoingInterface=test1 + OutgoingInterface=dummy98 + OutgoingInterface=7 + OutgoingInterface=010 + OutgoingInterface=0b 1 + OutgoingInterface=0o 7 + OutgoingInterface=an-alternative-name-longer-than-fifteen + OutgoingInterface= + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [BridgeFDB] + OutgoingInterface=0 + OutgoingInterface=default + OutgoingInterface=a%d + OutgoingInterface=.. + OutgoingInterface=08 + OutgoingInterface=eth 0 + OutgoingInterface=0b -1 + OutgoingInterface=4294967297 + OutgoingInterface=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(9, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseIgnoreCarrierLossOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseIgnoreCarrierLossOptionValueTest.kt new file mode 100644 index 00000000..6f104590 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseIgnoreCarrierLossOptionValueTest.kt @@ -0,0 +1,68 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseIgnoreCarrierLossOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [Network] + IgnoreCarrierLoss=true + IgnoreCarrierLoss=false + IgnoreCarrierLoss=No + IgnoreCarrierLoss=Yes + IgnoreCarrierLoss=TRUE + IgnoreCarrierLoss=oFf + IgnoreCarrierLoss=5s + IgnoreCarrierLoss=1min 30s + IgnoreCarrierLoss=infinity + IgnoreCarrierLoss=0 + IgnoreCarrierLoss=1h30min + IgnoreCarrierLoss=55s500ms + IgnoreCarrierLoss=.5s + IgnoreCarrierLoss=5s.5 + IgnoreCarrierLoss=+3.1s + IgnoreCarrierLoss=3.1 sec .2 sec + IgnoreCarrierLoss=2.5 + IgnoreCarrierLoss=23µs + IgnoreCarrierLoss=5 min + IgnoreCarrierLoss= + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [Network] + IgnoreCarrierLoss=sometimes + IgnoreCarrierLoss=5 parsecs + IgnoreCarrierLoss=-1s + IgnoreCarrierLoss=5. + IgnoreCarrierLoss=12.34.56 + IgnoreCarrierLoss=3.s + IgnoreCarrierLoss=3. 1s + IgnoreCarrierLoss=..1234 + IgnoreCarrierLoss=5ns + IgnoreCarrierLoss=infinity .7 + IgnoreCarrierLoss=5+5 + IgnoreCarrierLoss=Infinity + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(12, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseIpv4llAddressOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseIpv4llAddressOptionValueTest.kt new file mode 100644 index 00000000..93e6e58f --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseIpv4llAddressOptionValueTest.kt @@ -0,0 +1,45 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseIpv4llAddressOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [Network] + IPv4LLStartAddress=169.254.133.11 + IPv4LLStartAddress=169.254.1.0 + IPv4LLStartAddress=169.254.254.255 + IPv4LLStartAddress= + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [Network] + IPv4LLStartAddress=169.254.0.5 + IPv4LLStartAddress=169.254.255.1 + IPv4LLStartAddress=192.168.1.1 + IPv4LLStartAddress=169.254.1.256 + IPv4LLStartAddress=fe80::1 + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(5, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseIpv4llOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseIpv4llOptionValueTest.kt new file mode 100644 index 00000000..5189d038 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseIpv4llOptionValueTest.kt @@ -0,0 +1,46 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseIpv4llOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [Network] + IPv4LL=yes + IPv4LL=no + IPv4LL=True + IPv4LL=Yes + IPv4LL=TRUE + IPv4LL=Off + IPv4LL=oFf + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [Network] + IPv4LL=ipv4 + IPv4LL=both + IPv4LL=Yess + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(3, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseLinkGroupOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseLinkGroupOptionValueTest.kt new file mode 100644 index 00000000..b1df989c --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseLinkGroupOptionValueTest.kt @@ -0,0 +1,49 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseLinkGroupOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [Link] + Group=0 + Group=2147483647 + Group=0x10 + Group=010 + Group=0b101 + Group=0o17 + Group=+5 + Group= + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [Link] + Group=-1 + Group=2147483648 + Group=root + Group=08 + Group=1.5 + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(5, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseLldpMulticastModeOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseLldpMulticastModeOptionValueTest.kt new file mode 100644 index 00000000..8ea15587 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseLldpMulticastModeOptionValueTest.kt @@ -0,0 +1,50 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseLldpMulticastModeOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [Network] + EmitLLDP=nearest-bridge + EmitLLDP=non-tpmr-bridge + EmitLLDP=customer-bridge + EmitLLDP=yes + EmitLLDP=no + EmitLLDP=On + EmitLLDP=Yes + EmitLLDP=TRUE + EmitLLDP=oFf + EmitLLDP= + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [Network] + EmitLLDP=nearest + EmitLLDP=Customer-Bridge + EmitLLDP=routers-only + EmitLLDP=Nearest-Bridge + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(4, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseMudUrlOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseMudUrlOptionValueTest.kt new file mode 100644 index 00000000..176ab8e1 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseMudUrlOptionValueTest.kt @@ -0,0 +1,51 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseMudUrlOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [DHCPv4] + MUDURL=https://www.example.com/mud.json + MUDURL=http://example.com/a b + MUDURL=https://example.com/\x41 + MUDURL= + [DHCPv6] + MUDURL=https://example.com/ + [LLDP] + MUDURL=http://x + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [DHCPv4] + MUDURL=ftp://example.com/mud.json + MUDURL=https:// + MUDURL=HTTPS://example.com/ + [DHCPv6] + MUDURL=https://example.com/café + [LLDP] + MUDURL=https://example.com/\q + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(5, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseNtpOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseNtpOptionValueTest.kt new file mode 100644 index 00000000..1de65ec4 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseNtpOptionValueTest.kt @@ -0,0 +1,54 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseNtpOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [Network] + NTP=0.fedora.pool.ntp.org 1.fedora.pool.ntp.org + NTP=192.168.5.1 + NTP=2001:db8::123 + NTP=ntp.example.com. + NTP=time + NTP=. + NTP=foo\.bar + NTP=foo\qbar + NTP=foo\065bar + NTP=foo\\\\bar + NTP=foo\\.bar + NTP=foo\\065bar + NTP=localhost.localdomain + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [Network] + NTP=pool..ntp.org + NTP=.ntp.org + NTP=ok.example bad..example + NTP=foo\\bar + NTP=foo\\256bar + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(5, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseRadvDnsOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseRadvDnsOptionValueTest.kt new file mode 100644 index 00000000..50460140 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseRadvDnsOptionValueTest.kt @@ -0,0 +1,45 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseRadvDnsOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [IPv6SendRA] + DNS=_link_local 2002:da8:1:0::1 + DNS=2001:db8:1:1::2 + DNS=fe80::1 fe80::2 + DNS=::ffff:192.0.2.1 + DNS= + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [IPv6SendRA] + DNS=192.168.1.1 + DNS=[2001:db8::1]:53 + DNS=_link_locals + DNS=2001:db8::1%eth0 + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(4, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseRequiredForOnlineOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseRequiredForOnlineOptionValueTest.kt new file mode 100644 index 00000000..09199dbb --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseRequiredForOnlineOptionValueTest.kt @@ -0,0 +1,56 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseRequiredForOnlineOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [Link] + RequiredForOnline=routable + RequiredForOnline=no + RequiredForOnline=yes + RequiredForOnline=False + RequiredForOnline=Yes + RequiredForOnline=TRUE + RequiredForOnline=oFf + RequiredForOnline=degraded-carrier + RequiredForOnline=carrier:routable + RequiredForOnline=off: + RequiredForOnline=:degraded + RequiredForOnline=no-carrier + RequiredForOnline=enslaved + RequiredForOnline= + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [Link] + RequiredForOnline=online + RequiredForOnline=: + RequiredForOnline=routable:bogus + RequiredForOnline=carrier:routable:off + RequiredForOnline=Routable + RequiredForOnline=No-Carrier + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(6, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseResolveSupportOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseResolveSupportOptionValueTest.kt new file mode 100644 index 00000000..4a73ffd2 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseResolveSupportOptionValueTest.kt @@ -0,0 +1,51 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseResolveSupportOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [Network] + LLMNR=resolve + LLMNR=yes + LLMNR=no + LLMNR=Yes + LLMNR=0 + MulticastDNS=resolve + MulticastDNS=true + MulticastDNS=OFF + MulticastDNS=TRUE + MulticastDNS=oFf + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [Network] + LLMNR=Resolve + MulticastDNS=RESOLVE + LLMNR=maybe + MulticastDNS=allow-downgrade + MulticastDNS=yes no + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(5, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUplinkOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUplinkOptionValueTest.kt new file mode 100644 index 00000000..4e076f37 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUplinkOptionValueTest.kt @@ -0,0 +1,69 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseUplinkOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // `:none` is only accepted in [DHCPServer]/[IPv6SendRA] and `:self` only in [DHCPPrefixDelegation]. + // language="unit file (systemd)" + val file = """ + [DHCPServer] + UplinkInterface=:auto + UplinkInterface=:none + UplinkInterface=dummy98 + UplinkInterface= + [DHCPPrefixDelegation] + UplinkInterface=:self + UplinkInterface=auto + UplinkInterface=veth99 + UplinkInterface=12 + UplinkInterface=1eth + [IPv6SendRA] + UplinkInterface=this-is-a-long-alternative-interface-name + UplinkInterface=0x10 + UplinkInterface=:none + UplinkInterface=010 + UplinkInterface=2147483647 + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [DHCPServer] + UplinkInterface=:bogus + UplinkInterface=0 + UplinkInterface=eth0:1 + [DHCPPrefixDelegation] + UplinkInterface=all + UplinkInterface=eth/0 + [IPv6SendRA] + UplinkInterface=eth0 eth1 + UplinkInterface=:self + UplinkInterface=99999999999 + UplinkInterface=2147483648 + UplinkInterface=08 + [DHCPServer] + UplinkInterface=:self + [DHCPPrefixDelegation] + UplinkInterface=:none + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(12, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/NetworkDhcpMiscSafeAtoVerdictTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/NetworkDhcpMiscSafeAtoVerdictTest.kt new file mode 100644 index 00000000..72e9b038 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/NetworkDhcpMiscSafeAtoVerdictTest.kt @@ -0,0 +1,97 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai.ConfigParseBridgeVlanIdOptionValue +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai.ConfigParseDhcp6SendOptionOptionValue +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai.ConfigParseDhcpOptionTlvOptionValue +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai.ConfigParseDhcpOrRaRouteTableOptionValue +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai.ConfigParseDhcpPdSubnetIdOptionValue +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai.ConfigParseDhcpRequestOptionsOptionValue +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai.ConfigParseDhcpServerAddressOptionValue +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai.ConfigParseDuidTypeOptionValue +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai.ConfigParseFdbInterfaceOptionValue +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai.ConfigParseLinkGroupOptionValue +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai.ConfigParseUplinkOptionValue +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.Combinator +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.GrammarEngineVerdict +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotEquals +import org.junit.Test + +/** + * Verdicts of the network-dhcp-misc validators for values that are awkward to write in a unit-file fixture + * (a vertical tab, a no-break space) or whose verdict changed when the batch's own integer terminal was replaced by + * the shared SafeAtoTerminal (#563). Checked on both grammar engines at once. + * + * Every value here was run through systemd's real parsers at 0f0682b773 (steve-local/systemd-tools/bin/sd-check, + * one value per .network file). + */ +class NetworkDhcpMiscSafeAtoVerdictTest { + + private val vt = "\u000B" + private val nbsp = " " + + private fun accepted(grammar: Combinator, vararg values: String) { + for (value in values) { + assertEquals("classic engine on <$value>", GrammarEngineVerdict.VALID, GrammarEngineVerdict.classic(grammar, value)) + assertEquals("parse engine on <$value>", GrammarEngineVerdict.VALID, GrammarEngineVerdict.parseEngine(grammar, value)) + } + } + + private fun rejected(grammar: Combinator, vararg values: String) { + for (value in values) { + assertNotEquals("classic engine on <$value>", GrammarEngineVerdict.VALID, GrammarEngineVerdict.classic(grammar, value)) + assertNotEquals("parse engine on <$value>", GrammarEngineVerdict.VALID, GrammarEngineVerdict.parseEngine(grammar, value)) + } + } + + @Test + fun testWholeValueIntegersAcceptLeadingVerticalTab() { + // strtoul skips C isspace(), which includes \v; config_parse only strips " \t\n\r". + accepted(ConfigParseBridgeVlanIdOptionValue().combinator, "${vt}5", "0b${vt}1") + accepted(ConfigParseLinkGroupOptionValue().combinator, "${vt}5", "0b${vt}1") + accepted(ConfigParseDhcpPdSubnetIdOptionValue().combinator, "${vt}5", "-0", "0b1") + accepted(ConfigParseDuidTypeOptionValue().combinator, "${vt}5", "0b 1:2000-01-01") + accepted(ConfigParseDhcpOrRaRouteTableOptionValue().combinator, "${vt}5", "0b 1") + } + + @Test + fun testSignedGroupRefusesNegativeAfterPrefix() { + rejected(ConfigParseLinkGroupOptionValue().combinator, "0b -1", "0b-1") + } + + @Test + fun testUnsignedWrapAround() { + // strtoul negates modulo 2^64 when the '-' is not the first character, so this is 1 (DUID-LLT). + accepted(ConfigParseDuidTypeOptionValue().combinator, "0b -" + "1".repeat(64)) + } + + @Test + fun testFieldsAfterColonOrSlashTakeCWhitespaceOnly() { + accepted(ConfigParseDhcpServerAddressOptionValue().combinator, "192.168.5.1/${vt}24", "192.168.5.1/ 24", "192.168.5.1/0b 11000") + rejected(ConfigParseDhcpServerAddressOptionValue().combinator, "192.168.5.1/${nbsp}24") + + accepted(ConfigParseDhcpOptionTlvOptionValue().combinator, "12:uint8:${vt}1", "${vt}12:uint8:1", "0b 1100:uint8:1", "12:uint16: 0b 1") + rejected(ConfigParseDhcpOptionTlvOptionValue().combinator, "${nbsp}12:uint8:1", "12:uint8:${nbsp}1") + + accepted(ConfigParseDhcp6SendOptionOptionValue().combinator, "${vt}12:uint8:1", "${vt}1:12:uint8:1") + rejected(ConfigParseDhcp6SendOptionOptionValue().combinator, "${nbsp}12:uint8:1", "${nbsp}1:12:uint8:1") + + rejected(ConfigParseDuidTypeOptionValue().combinator, "${nbsp}1") + } + + @Test + fun testRequestOptionsSplitsOnWhitespaceBeforeParsing() { + // Each word reaches safe_atou32 alone, so "0b 1" is the invalid word "0b" followed by "1". + rejected(ConfigParseDhcpRequestOptionsOptionValue().combinator, "0b 1", "0o 7", "0b +1", "0b -0", "0b-0") + accepted(ConfigParseDhcpRequestOptionsOptionValue().combinator, "0b+1", "1 0b1 0x10 010") + } + + @Test + fun testInterfaceIndexIsTheWholeValue() { + // parse_ifindex() is safe_atoi on the whole value, whitespace included. + for (grammar in listOf(ConfigParseFdbInterfaceOptionValue().combinator, ConfigParseUplinkOptionValue().combinator)) { + accepted(grammar, "0b 1", "${vt}5", "0o 7", "0x10", "+5", "0x" + "0".repeat(130) + "1") + rejected(grammar, "eth 0", "0b 0", "0b -1", "eth${nbsp}0") + } + } +}