From a46709e3518938d7586448074be9420cc68d65d6 Mon Sep 17 00:00:00 2001 From: Steve Ramage Date: Fri, 23 Oct 2026 18:28:36 -0700 Subject: [PATCH 1/7] feat: add grammar validators for networkd DHCP/DUID/DNS/NTP/LLDP settings (network-dhcp-misc batch) Adds 33 grammar validators (36 registrations) for systemd-networkd .network settings that previously had no value validation: DHCPv4/DHCPv6/DHCP-server raw options, DUID type and raw data, uplink/outgoing interfaces, MUD URLs, Domains=/NTP=/DNSSECNegativeTrustAnchors=, RequiredForOnline=, and assorted enums. Each is modelled on the C parser at systemd 0f0682b773 and documented with man/parser/leaf/test links; shared pieces (safe_ato* numbers with 0b/0o/sign handling, case-insensitive booleans, C-escape words, DNS names, typed DHCP option payloads) live in BatchNetworkDhcpMiscCombinators.kt. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../semanticdata/optionvalues/AiGenerated.kt | 38 +++ .../ConfigParseActivationPolicyOptionValue.kt | 27 ++ .../ai/ConfigParseBridgeVlanIdOptionValue.kt | 32 +++ .../ConfigParseDhcp4UserClassOptionValue.kt | 30 +++ .../ConfigParseDhcp6SendOptionOptionValue.kt | 50 ++++ ...gParseDhcp6UserOrVendorClassOptionValue.kt | 26 ++ ...rseDhcpFallbackLeaseLifetimeOptionValue.kt | 22 ++ .../ai/ConfigParseDhcpLabelOptionValue.kt | 24 ++ .../ai/ConfigParseDhcpOptionTlvOptionValue.kt | 53 ++++ .../ConfigParseDhcpOptionTypedOptionValue.kt | 44 +++ ...igParseDhcpPdPrefixRouteTypeOptionValue.kt | 30 +++ .../ConfigParseDhcpPdSubnetIdOptionValue.kt | 33 +++ ...gParseDhcpRelayInterfaceModeOptionValue.kt | 24 ++ ...onfigParseDhcpRequestOptionsOptionValue.kt | 27 ++ ...ConfigParseDhcpServerAddressOptionValue.kt | 37 +++ ...eDhcpServerIpv6OnlyPreferredOptionValue.kt | 27 ++ ...ParseDhcpStaticLeaseHostnameOptionValue.kt | 26 ++ .../ConfigParseDnsOverTlsModeOptionValue.kt | 31 +++ ...seDnssecNegativeTrustAnchorsOptionValue.kt | 27 ++ .../ai/ConfigParseDomainsOptionValue.kt | 44 +++ .../ai/ConfigParseDuidRawdataOptionValue.kt | 54 ++++ .../ai/ConfigParseDuidTypeOptionValue.kt | 64 +++++ .../ai/ConfigParseFdbInterfaceOptionValue.kt | 24 ++ ...ConfigParseIgnoreCarrierLossOptionValue.kt | 30 +++ .../ai/ConfigParseIpv4llAddressOptionValue.kt | 41 +++ .../ai/ConfigParseIpv4llOptionValue.kt | 23 ++ .../ai/ConfigParseLinkGroupOptionValue.kt | 24 ++ ...ConfigParseLldpMulticastModeOptionValue.kt | 31 +++ .../ai/ConfigParseMudUrlOptionValue.kt | 43 +++ .../ai/ConfigParseNtpOptionValue.kt | 32 +++ .../ai/ConfigParseRadvDnsOptionValue.kt | 32 +++ ...ConfigParseRequiredForOnlineOptionValue.kt | 46 ++++ .../ConfigParseResolveSupportOptionValue.kt | 34 +++ .../ai/ConfigParseUplinkOptionValue.kt | 36 +++ .../BatchNetworkDhcpMiscCombinators.kt | 252 ++++++++++++++++++ ...figParseActivationPolicyOptionValueTest.kt | 45 ++++ .../ConfigParseBridgeVlanIdOptionValueTest.kt | 47 ++++ ...onfigParseDhcp4UserClassOptionValueTest.kt | 45 ++++ ...nfigParseDhcp6SendOptionOptionValueTest.kt | 53 ++++ ...seDhcp6UserOrVendorClassOptionValueTest.kt | 42 +++ ...hcpFallbackLeaseLifetimeOptionValueTest.kt | 42 +++ .../ai/ConfigParseDhcpLabelOptionValueTest.kt | 44 +++ ...ConfigParseDhcpOptionTlvOptionValueTest.kt | 66 +++++ ...nfigParseDhcpOptionTypedOptionValueTest.kt | 54 ++++ ...rseDhcpPdPrefixRouteTypeOptionValueTest.kt | 48 ++++ ...onfigParseDhcpPdSubnetIdOptionValueTest.kt | 49 ++++ ...seDhcpRelayInterfaceModeOptionValueTest.kt | 42 +++ ...gParseDhcpRequestOptionsOptionValueTest.kt | 47 ++++ ...igParseDhcpServerAddressOptionValueTest.kt | 47 ++++ ...pServerIpv6OnlyPreferredOptionValueTest.kt | 46 ++++ ...eDhcpStaticLeaseHostnameOptionValueTest.kt | 49 ++++ ...onfigParseDnsOverTlsModeOptionValueTest.kt | 44 +++ ...ssecNegativeTrustAnchorsOptionValueTest.kt | 44 +++ .../ai/ConfigParseDomainsOptionValueTest.kt | 51 ++++ .../ConfigParseDuidRawdataOptionValueTest.kt | 54 ++++ .../ai/ConfigParseDuidTypeOptionValueTest.kt | 56 ++++ .../ConfigParseFdbInterfaceOptionValueTest.kt | 46 ++++ ...igParseIgnoreCarrierLossOptionValueTest.kt | 47 ++++ ...ConfigParseIpv4llAddressOptionValueTest.kt | 45 ++++ .../ai/ConfigParseIpv4llOptionValueTest.kt | 41 +++ .../ai/ConfigParseLinkGroupOptionValueTest.kt | 49 ++++ ...igParseLldpMulticastModeOptionValueTest.kt | 46 ++++ .../ai/ConfigParseMudUrlOptionValueTest.kt | 51 ++++ .../ai/ConfigParseNtpOptionValueTest.kt | 44 +++ .../ai/ConfigParseRadvDnsOptionValueTest.kt | 45 ++++ ...igParseRequiredForOnlineOptionValueTest.kt | 52 ++++ ...onfigParseResolveSupportOptionValueTest.kt | 48 ++++ .../ai/ConfigParseUplinkOptionValueTest.kt | 59 ++++ 68 files changed, 3006 insertions(+) create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseActivationPolicyOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseBridgeVlanIdOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcp4UserClassOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcp6SendOptionOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcp6UserOrVendorClassOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpFallbackLeaseLifetimeOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpLabelOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpOptionTlvOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpOptionTypedOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpPdPrefixRouteTypeOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpPdSubnetIdOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpRelayInterfaceModeOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpRequestOptionsOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpServerAddressOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpServerIpv6OnlyPreferredOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpStaticLeaseHostnameOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDnsOverTlsModeOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDnssecNegativeTrustAnchorsOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDomainsOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDuidRawdataOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDuidTypeOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseFdbInterfaceOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIgnoreCarrierLossOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIpv4llAddressOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIpv4llOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseLinkGroupOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseLldpMulticastModeOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseMudUrlOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseNtpOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseRadvDnsOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseRequiredForOnlineOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseResolveSupportOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUplinkOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/BatchNetworkDhcpMiscCombinators.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseActivationPolicyOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseBridgeVlanIdOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcp4UserClassOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcp6SendOptionOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcp6UserOrVendorClassOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcpFallbackLeaseLifetimeOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcpLabelOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcpOptionTlvOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcpOptionTypedOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcpPdPrefixRouteTypeOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcpPdSubnetIdOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcpRelayInterfaceModeOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcpRequestOptionsOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcpServerAddressOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcpServerIpv6OnlyPreferredOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcpStaticLeaseHostnameOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDnsOverTlsModeOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDnssecNegativeTrustAnchorsOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDomainsOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDuidRawdataOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDuidTypeOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseFdbInterfaceOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseIgnoreCarrierLossOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseIpv4llAddressOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseIpv4llOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseLinkGroupOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseLldpMulticastModeOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseMudUrlOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseNtpOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseRadvDnsOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseRequiredForOnlineOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseResolveSupportOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUplinkOptionValueTest.kt 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..c6019fa8 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,44 @@ 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, + ) 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..aaa32eb0 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseBridgeVlanIdOptionValue.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.AlternativeCombinator +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.BOOLEAN_FALSE_ANY_CASE +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.EOF +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SafeAtoNumberTerminal +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(SafeAtoNumberTerminal(0, 4095), EOF()), + SequenceCombinator(BOOLEAN_FALSE_ANY_CASE, 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..9737a363 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcp4UserClassOptionValue.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.CUNESCAPE_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). + * + * 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 `""` or an over-long one is + * dropped with a warning); those length checks are not modelled (lenient). + */ +class ConfigParseDhcp4UserClassOptionValue : SimpleGrammarOptionValues( + "config_parse_dhcp4_user_class", + SequenceCombinator(wordList(CUNESCAPE_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..f209f25d --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcp6SendOptionOptionValue.kt @@ -0,0 +1,50 @@ +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.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.SafeAtoNumberTerminal +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 + +/* + * [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#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 + */ + +private val DHCP6_OPTION_CODE = SafeAtoNumberTerminal(1, 65_535L) + +/** + * 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). Validators are keyed by (parser, ltype) only, so both shapes are accepted for both keys + * (lenient; see QUESTIONS.md). 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. + */ +class ConfigParseDhcp6SendOptionOptionValue : SimpleGrammarOptionValues( + "config_parse_dhcp6_send_option", + SequenceCombinator( + ZeroOrMore(COLON), + AlternativeCombinator( + SequenceCombinator(SafeAtoNumberTerminal(0, 4_294_967_296L), COALESCED_COLONS, DHCP6_OPTION_CODE, COALESCED_COLONS, dhcpOptionTypedData(), EOF()), + SequenceCombinator(DHCP6_OPTION_CODE, COALESCED_COLONS, dhcpOptionTypedData(), EOF()), + ), + ) +) 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..10845b93 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcp6UserOrVendorClassOptionValue.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.CUNESCAPE_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 (not modelled). + */ +class ConfigParseDhcp6UserOrVendorClassOptionValue : SimpleGrammarOptionValues( + "config_parse_dhcp6_user_or_vendor_class", + SequenceCombinator(wordList(CUNESCAPE_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..885fd8ae --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpOptionTlvOptionValue.kt @@ -0,0 +1,53 @@ +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.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.SafeAtoNumberTerminal +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 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), + SafeAtoNumberTerminal(1, 255), + 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/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..1a5c4901 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpPdSubnetIdOptionValue.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.EOF +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.FlexibleLiteralChoiceTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SafeAtoNumberTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator + +/* + * [DHCPPrefixDelegation] SubnetId= (and the old [DHCPv6PrefixDelegation] SubnetId=) (.network). + * + * 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(SafeAtoNumberTerminal(0, Long.MAX_VALUE, hexOnly = 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..1fd31910 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpRequestOptionsOptionValue.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.SafeAtoNumberTerminal +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. + */ +class ConfigParseDhcpRequestOptionsOptionValue : SimpleGrammarOptionValues( + "config_parse_dhcp_request_options", + SequenceCombinator(wordList(SafeAtoNumberTerminal(1, 255)), 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..4bd4ea3b --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpServerAddressOptionValue.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.CIDR_SEPARATOR +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.EOF +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.IPV4_ADDR +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SafeAtoNumberTerminal +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/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), e.g. `192.168.5.1/24`. + * + * systemd then also refuses a localhost (127.0.0.0/8) or link-local (169.254.0.0/16) address; that + * range check is not modelled (lenient). + */ +class ConfigParseDhcpServerAddressOptionValue : SimpleGrammarOptionValues( + "config_parse_dhcp_server_address", + SequenceCombinator( + IPV4_ADDR, + ZeroOrOne(SequenceCombinator(CIDR_SEPARATOR, SafeAtoNumberTerminal(0, 33))), + 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..3c661340 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpServerIpv6OnlyPreferredOptionValue.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.SequenceCombinator +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.TIME_VALUE + +/* + * [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/basic/time-util.c#L1289-L1291 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 (parse_sec). + * + * systemd also refuses anything below MIN_V6ONLY_WAIT_USEC (300s) unless networkd runs in its test + * mode — systemd's own test suite uses `20s`. That magnitude check depends on the parsed duration and on + * a runtime flag, so it is not modelled (lenient). + */ +class ConfigParseDhcpServerIpv6OnlyPreferredOptionValue : SimpleGrammarOptionValues( + "config_parse_dhcp_server_ipv6_only_preferred", + SequenceCombinator(TIME_VALUE, 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..faeb20bd --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDnsOverTlsModeOptionValue.kt @@ -0,0 +1,31 @@ +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_ANY_CASE +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] 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", + AlternativeCombinator( + SequenceCombinator(FlexibleLiteralChoiceTerminal("opportunistic"), EOF()), + SequenceCombinator(BOOLEAN_ANY_CASE, 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..12081830 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDomainsOptionValue.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.DNS_NAME_WORD +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 + * 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`. +private val DOMAIN_WORD = SequenceCombinator( + ZeroOrOne(LiteralChoiceTerminal("~")), + ZeroOrOne(DNS_NAME_WORD), +) + +/** + * 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. + * + * systemd also ignores `localhost` and `*.localhost` here (is_localhost); that is not modelled (lenient). + */ +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..263ed818 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDuidRawdataOptionValue.kt @@ -0,0 +1,54 @@ +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.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). + * + * 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(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..59cb6708 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDuidTypeOptionValue.kt @@ -0,0 +1,64 @@ +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.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.SafeAtoNumberTerminal +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). + * + * 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"), COALESCED_COLONS, DUID_TIMESTAMP, EOF()), + SequenceCombinator(SafeAtoNumberTerminal(1, 2), COALESCED_COLONS, DUID_TIMESTAMP, EOF()), + SequenceCombinator(DUID_TYPE_NAME, ZeroOrMore(COLON), EOF()), + SequenceCombinator(SafeAtoNumberTerminal(0, 65_536), 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..7a187eb7 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIgnoreCarrierLossOptionValue.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.AlternativeCombinator +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.BOOLEAN_ANY_CASE +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.TIME_VALUE + +/* + * [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 + * 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`, `infinity`). + */ +class ConfigParseIgnoreCarrierLossOptionValue : SimpleGrammarOptionValues( + "config_parse_ignore_carrier_loss", + AlternativeCombinator( + SequenceCombinator(BOOLEAN_ANY_CASE, EOF()), + SequenceCombinator(TIME_VALUE, 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..b4e83ffa --- /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_ANY_CASE +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_ANY_CASE, 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..7f5e7eae --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseLinkGroupOptionValue.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.SafeAtoNumberTerminal +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(SafeAtoNumberTerminal(0, 2_147_483_648L, signed = 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..e909e54f --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseLldpMulticastModeOptionValue.kt @@ -0,0 +1,31 @@ +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_ANY_CASE +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] 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", + AlternativeCombinator( + SequenceCombinator(FlexibleLiteralChoiceTerminal("nearest-bridge", "non-tpmr-bridge", "customer-bridge"), EOF()), + SequenceCombinator(BOOLEAN_ANY_CASE, 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..87bbec6f --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseRadvDnsOptionValue.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.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). + * + * 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..fecc4217 --- /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_ANY_CASE +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_ANY_CASE, 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..b4ca4ef9 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseResolveSupportOptionValue.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.AlternativeCombinator +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.BOOLEAN_ANY_CASE +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] 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", + AlternativeCombinator( + SequenceCombinator(FlexibleLiteralChoiceTerminal("resolve"), EOF()), + SequenceCombinator(BOOLEAN_ANY_CASE, 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..01ffc5f6 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUplinkOptionValue.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.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.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/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 + */ + +/** + * Validator for the three `UplinkInterface=` settings, all parsed by config_parse_uplink. + * + * Accepts `:auto`, `:none` (DHCPServer / IPv6SendRA only), `:self` (DHCPPrefixDelegation only), a + * positive interface index, or an alternative interface name (see [IFNAME_OR_IFINDEX]). Which of + * `:none` / `:self` is legal depends on the *section*, which the (parser, ltype) registration can't see, + * so both are accepted everywhere (lenient). The special values are compared with streq, so they are + * case-sensitive. + */ +class ConfigParseUplinkOptionValue : SimpleGrammarOptionValues( + "config_parse_uplink", + AlternativeCombinator( + SequenceCombinator(FlexibleLiteralChoiceTerminal(":auto", ":none", ":self"), EOF()), + SequenceCombinator(IFNAME_OR_IFINDEX, EOF()), + ) +) 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..9a0dafee --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/BatchNetworkDhcpMiscCombinators.kt @@ -0,0 +1,252 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar + +/* + * 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#L403-L427 mangle_base + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L429-L478 safe_atou_full + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L497-L520 safe_atoi + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L522-L558 safe_atollu_full + * 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/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 + */ + +/** + * A boolean as parse_boolean() actually reads it: the twelve spellings of [BOOLEAN_SPELLINGS], compared + * with STRCASE_IN_SET, i.e. case-insensitively (`Yes`, `TRUE` and `Off` are all accepted). The shared + * [BOOLEAN] is case-sensitive and therefore flags those; this one doesn't. + */ +val BOOLEAN_ANY_CASE = FlexibleLiteralChoiceTerminal(*BOOLEAN_SPELLINGS, ignoreCase = true) + +/** Only the false half of parse_boolean(), case-insensitively — see [BOOLEAN_FALSE] and [BOOLEAN_ANY_CASE]. */ +val BOOLEAN_FALSE_ANY_CASE = FlexibleLiteralChoiceTerminal("0", "no", "n", "false", "f", "off", ignoreCase = true) + +/** + * An integer spelled the way systemd's `safe_ato*` helpers read one, range-checked in the base it is + * actually written in. + * + * With base 0 (safe_atou, safe_atou8, safe_atou16, safe_atou32, safe_atoi, safe_atoi32) the helper first + * runs mangle_base(), which understands Python-style `0b`/`0o` prefixes, and then hands the string to + * strtoul()/strtol() with base 0, which understands `0x` hex and leading-zero octal. strtoul() also takes + * an optional sign: `+5` is 5, and for the unsigned helpers `-0` is 0 while any other negative number is + * -ERANGE. Note mangle_base() runs *before* the sign is looked at, so `+0b1` is not binary (strtoul stops + * at the `b` and the helper fails) whereas `+0x1` is fine. + * + * With [hexOnly] (safe_atoux64 and friends, which pass an explicit base 16) every number is hexadecimal, + * with an optional `0x` that strtoull itself tolerates. + * + * Leading whitespace — which strtoul skips — is NOT consumed here; model it in the enclosing grammar + * where it can actually occur (unit-file values arrive stripped). + * + * [UnsignedNumberTerminal] covers the common subset (decimal/hex/octal, no sign, no `0b`/`0o`); this one + * exists because the batch needed the signed form and the extra prefixes. + */ +class SafeAtoNumberTerminal( + private val minInclusive: Long, + private val maxExclusive: Long, + private val signed: Boolean = false, + private val hexOnly: Boolean = false, +) : TerminalCombinator { + + private val shape = if (hexOnly) HEX_SHAPE else BASE0_SHAPE + + private fun span(value: String, offset: Int): String? = shape.matchAt(value, offset)?.value + + /** The value [text] denotes, or null when strtoul would reject it or it overflows a Long. */ + private fun valueOf(text: String): Long? { + var s = text + var negative = false + if (s.startsWith("+") || s.startsWith("-")) { + negative = s[0] == '-' + s = s.substring(1) + } + val magnitude: Long = magnitudeOf(s, signPresent = text != s) ?: return null + if (!negative) return magnitude + if (signed) return -magnitude + // strtoul happily negates; safe_atou_full then refuses anything but -0. + return if (magnitude == 0L) 0L else null + } + + private fun magnitudeOf(s: String, signPresent: Boolean): Long? { + if (hexOnly) return (if (s.startsWith("0x") || s.startsWith("0X")) s.substring(2) else s).toLongOrNull(16) + return when { + s.length > 2 && (s.startsWith("0x") || s.startsWith("0X")) -> s.substring(2).toLongOrNull(16) + // mangle_base only sees an unsigned string, so `+0b1` / `-0o7` fail in systemd too. + s.length > 2 && (s.startsWith("0b") || s.startsWith("0B")) -> if (signPresent) null else s.substring(2).toLongOrNull(2) + s.length > 2 && (s.startsWith("0o") || s.startsWith("0O")) -> if (signPresent) null else s.substring(2).toLongOrNull(8) + s.length > 1 && s.startsWith("0") -> s.substring(1).toLongOrNull(8) + else -> s.toLongOrNull() + } + } + + // A maxExclusive of Long.MAX_VALUE means "anything that fits a signed 64-bit integer", so that + // INT64_MAX itself (the SubnetId= ceiling) stays expressible; larger spellings already fail valueOf(). + private fun inRange(text: String): Boolean = valueOf(text)?.let { + it >= minInclusive && (maxExclusive == Long.MAX_VALUE || it < maxExclusive) + } ?: false + + 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 (!inRange(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, inRange(text))))) + } + + override fun toString(): String = "SafeAto($minInclusive,$maxExclusive${if (signed) ",signed" else ""}${if (hexOnly) ",hex" else ""})" + + private companion object { + // Prefixed forms before plain digits so "0x10" isn't read as the single digit 0. + val BASE0_SHAPE = Regex("""[+-]?(?:0[xX][0-9a-fA-F]+|0[bBoO][0-9]+|[0-9]+)""") + val HEX_SHAPE = Regex("""[+-]?(?:0[xX])?[0-9a-fA-F]+""") + } +} + +/** One or more `:` — extract_first_word(…, ":", 0) coalesces runs of its separator. */ +val 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)""", +) + +/** 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&&[^:/%]]""" + +/** + * A positive interface index, or an alternative interface name (ifname_valid_full with + * IFNAME_VALID_ALTERNATIVE: 1…127 printable ASCII characters other than `:`, `/`, `%`; not `.`, `..`, + * `all`, `default`, or all digits). An all-digit spelling is therefore valid exactly when it is a + * positive index. The digit branch carries a `(?!\S)` guard so that a name like `1eth` is still offered + * to the name branch rather than stopping at its leading digit. + */ +val IFNAME_OR_IFINDEX = RegexTerminal( + """\S+""", + """(?:[0-9]*[1-9][0-9]*(?!\S))|(?!(?:[0-9]+|\.\.?|all|default)(?!$IFNAME_CHAR))$IFNAME_CHAR{1,127}(?!\S)""", +) + +// --------------------------------------------------------------------------------------------------- +// 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 further `\` as the + * start of a `\\`, `\.` or `\DDD` escape. Modelling both layers exactly isn't worth it, so any + * backslash-plus-character is accepted as label content (lenient). A label of 64 or more plain characters + * is refused: that is over DNS_LABEL_MAX whatever follows. + */ +val DNS_WORD_LABEL = RegexTerminal( + """(?:[^\s.\\]|\\[\s\S])+""", + """(?!$DNS_PLAIN{64})(?:$DNS_PLAIN|\\[\s\S])+(?=[\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, +) + +/** 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. + +/** + * `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 (base 0, leading whitespace skipped); + * - `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. For `ipv6address` systemd would also swallow the leading colons of the address itself (so + * `ipv6address:::1` actually fails there); the grammar takes a single colon and accepts it — lenient. + * The string length check (`check_length`, <= 255 bytes once unescaped) is not modelled. + */ +fun dhcpOptionTypedData(): Combinator = AlternativeCombinator( + SequenceCombinator(LiteralChoiceTerminal("uint8"), COALESCED_COLONS, ZeroOrOne(WhitespaceTerminal()), SafeAtoNumberTerminal(0, 256L)), + SequenceCombinator(LiteralChoiceTerminal("uint16"), COALESCED_COLONS, ZeroOrOne(WhitespaceTerminal()), SafeAtoNumberTerminal(0, 65_536L)), + SequenceCombinator(LiteralChoiceTerminal("uint32"), COALESCED_COLONS, ZeroOrOne(WhitespaceTerminal()), SafeAtoNumberTerminal(0, 4_294_967_296L)), + SequenceCombinator(LiteralChoiceTerminal("ipv4address"), COALESCED_COLONS, IPV4_ADDR), + SequenceCombinator(LiteralChoiceTerminal("ipv6address"), COLON, IPV6_ADDR), + SequenceCombinator(LiteralChoiceTerminal("string"), COALESCED_COLONS, C_ESCAPED_TEXT), +) 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..073d6151 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseBridgeVlanIdOptionValueTest.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 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= + """.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 + """.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/ConfigParseDhcp4UserClassOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcp4UserClassOptionValueTest.kt new file mode 100644 index 00000000..6c46f2b6 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcp4UserClassOptionValueTest.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 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= + """.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 + """.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/ConfigParseDhcp6SendOptionOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcp6SendOptionOptionValueTest.kt new file mode 100644 index 00000000..824353a5 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcp6SendOptionOptionValueTest.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 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 + 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() { + // 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 + """.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/ConfigParseDhcp6UserOrVendorClassOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcp6UserOrVendorClassOptionValueTest.kt new file mode 100644 index 00000000..a8ed1821 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcp6UserOrVendorClassOptionValueTest.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 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 + """.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/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..7d466f7d --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcpOptionTlvOptionValueTest.kt @@ -0,0 +1,66 @@ +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= + 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() { + // 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 + """.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/ConfigParseDhcpOptionTypedOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcpOptionTypedOptionValueTest.kt new file mode 100644 index 00000000..54843dc0 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcpOptionTypedOptionValueTest.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 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=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() { + // 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 + """.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/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..a65e1dd1 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcpRequestOptionsOptionValueTest.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 ConfigParseDhcpRequestOptionsOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [DHCPv4] + RequestOptions=59 60 + RequestOptions=17 + RequestOptions=1 254 0xfe 012 + 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 + [DHCPv6] + RequestOptions=dns + """.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/ConfigParseDhcpServerAddressOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcpServerAddressOptionValueTest.kt new file mode 100644 index 00000000..6a46d449 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcpServerAddressOptionValueTest.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 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= + """.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 + """.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/ConfigParseDhcpServerIpv6OnlyPreferredOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcpServerIpv6OnlyPreferredOptionValueTest.kt new file mode 100644 index 00000000..a7ef05af --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcpServerIpv6OnlyPreferredOptionValueTest.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 ConfigParseDhcpServerIpv6OnlyPreferredOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // systemd refuses values under 300s outside its test mode; that magnitude check isn't modelled, + // so systemd's own test value of 20s is accepted. + // language="unit file (systemd)" + val file = """ + [DHCPServer] + IPv6OnlyPreferredSec=20s + IPv6OnlyPreferredSec=5min + IPv6OnlyPreferredSec=300 + IPv6OnlyPreferredSec=1h 5min + 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 + """.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/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..806f1c07 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDnsOverTlsModeOptionValueTest.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 ConfigParseDnsOverTlsModeOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [Network] + DNSOverTLS=opportunistic + DNSOverTLS=yes + DNSOverTLS=no + DNSOverTLS=TRUE + DNSOverTLS=1 + """.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 + """.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/ConfigParseDnssecNegativeTrustAnchorsOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDnssecNegativeTrustAnchorsOptionValueTest.kt new file mode 100644 index 00000000..72f50490 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDnssecNegativeTrustAnchorsOptionValueTest.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 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= + """.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.. + """.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/ConfigParseDomainsOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDomainsOptionValueTest.kt new file mode 100644 index 00000000..177eb5a0 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDomainsOptionValueTest.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 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= + """.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 + """.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/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..50246211 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseFdbInterfaceOptionValueTest.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 ConfigParseFdbInterfaceOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [BridgeFDB] + OutgoingInterface=test1 + OutgoingInterface=dummy98 + OutgoingInterface=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=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + """.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/ConfigParseIgnoreCarrierLossOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseIgnoreCarrierLossOptionValueTest.kt new file mode 100644 index 00000000..4c2f6192 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseIgnoreCarrierLossOptionValueTest.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 ConfigParseIgnoreCarrierLossOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [Network] + IgnoreCarrierLoss=true + IgnoreCarrierLoss=false + IgnoreCarrierLoss=No + IgnoreCarrierLoss=5s + IgnoreCarrierLoss=1min 30s + IgnoreCarrierLoss=infinity + IgnoreCarrierLoss=0 + 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 + """.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/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..36f4143f --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseIpv4llOptionValueTest.kt @@ -0,0 +1,41 @@ +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 + """.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 + """.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/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..800141af --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseLldpMulticastModeOptionValueTest.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 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= + """.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 + """.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/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..0cea0ba5 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseNtpOptionValueTest.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 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 + """.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 + """.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/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..7d8bb598 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseRequiredForOnlineOptionValueTest.kt @@ -0,0 +1,52 @@ +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=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 + """.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/ConfigParseResolveSupportOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseResolveSupportOptionValueTest.kt new file mode 100644 index 00000000..b8fdb90a --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseResolveSupportOptionValueTest.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 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 + """.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 + LLMNR=maybe + MulticastDNS=allow-downgrade + MulticastDNS=yes no + """.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/ConfigParseUplinkOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUplinkOptionValueTest.kt new file mode 100644 index 00000000..8e853987 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUplinkOptionValueTest.kt @@ -0,0 +1,59 @@ +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 meaningful for [DHCPServer]/[IPv6SendRA] and `:self` only for + // [DHCPPrefixDelegation]; the validator can't see the section, so both are accepted everywhere. + // 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 + """.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 + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(6, highlights) + } +} From e9587a607ad5e8fc61f30707b5c9337394aba573 Mon Sep 17 00:00:00 2001 From: Steve Ramage Date: Fri, 23 Oct 2026 18:59:29 -0700 Subject: [PATCH 2/7] fix: correct network-dhcp-misc validators after review (network-dhcp-misc batch) - IgnoreCarrierLoss= / IPv6OnlyPreferredSec=: replace the shared TIME_VALUE with a structural PARSE_SEC_TIMESPAN that follows parse_time() exactly (1h30min, 55s500ms, .5s and 5s.5 were flagged). - DHCP raw options: after `ipv6address` take the whole coalesced colon run, then an address not starting with ':' (ipv6address::2001:db8::1 was flagged, ipv6address:::1 was accepted). - [DHCPv6] SendOption=/SendVendorOption= and UplinkInterface= now dispatch on key / section, as the C does, via KeyOrSectionDispatchingGrammarOptionValue. - Interface indexes are range- and octal-checked like parse_ifindex. - safe_ato numbers: allow whitespace after '/' or ':' and sign/whitespace after a 0b/0o prefix. - Note in KDoc that deprecated sections are not recognised by the plugin. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../ConfigParseDhcp4UserClassOptionValue.kt | 3 + .../ConfigParseDhcp6SendOptionOptionValue.kt | 52 +++-- .../ai/ConfigParseDhcpOptionTlvOptionValue.kt | 3 + .../ConfigParseDhcpPdSubnetIdOptionValue.kt | 3 + ...ConfigParseDhcpServerAddressOptionValue.kt | 6 +- ...eDhcpServerIpv6OnlyPreferredOptionValue.kt | 9 +- .../ai/ConfigParseDuidRawdataOptionValue.kt | 3 + .../ai/ConfigParseDuidTypeOptionValue.kt | 3 + ...ConfigParseIgnoreCarrierLossOptionValue.kt | 9 +- .../ai/ConfigParseRadvDnsOptionValue.kt | 3 + .../ai/ConfigParseUplinkOptionValue.kt | 37 ++-- .../BatchNetworkDhcpMiscCombinators.kt | 194 ++++++++++++++++-- ...nfigParseDhcp6SendOptionOptionValueTest.kt | 9 +- ...ConfigParseDhcpOptionTlvOptionValueTest.kt | 9 +- ...nfigParseDhcpOptionTypedOptionValueTest.kt | 5 +- ...igParseDhcpServerAddressOptionValueTest.kt | 1 + ...pServerIpv6OnlyPreferredOptionValueTest.kt | 9 +- .../ConfigParseFdbInterfaceOptionValueTest.kt | 5 +- ...igParseIgnoreCarrierLossOptionValueTest.kt | 19 +- .../ai/ConfigParseUplinkOptionValueTest.kt | 16 +- 20 files changed, 339 insertions(+), 59 deletions(-) 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 index 9737a363..3b59373f 100644 --- 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 @@ -9,6 +9,9 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram /* * [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) 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 index f209f25d..987450d3 100644 --- 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 @@ -5,9 +5,12 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.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.SafeAtoNumberTerminal import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.WhitespaceTerminal import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.ZeroOrMore +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.ZeroOrOne import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.dhcpOptionTypedData /* @@ -16,6 +19,7 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram * 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) @@ -27,24 +31,44 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram private val DHCP6_OPTION_CODE = SafeAtoNumberTerminal(1, 65_535L) +// 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. +private val OPTIONAL_WS = ZeroOrOne(WhitespaceTerminal()) + +private val CODE_TYPE_DATA = SequenceCombinator(OPTIONAL_WS, DHCP6_OPTION_CODE, COALESCED_COLONS, dhcpOptionTypedData(), EOF()) + +private val SEND_OPTION = SequenceCombinator(ZeroOrMore(COLON), CODE_TYPE_DATA) + +private val SEND_VENDOR_OPTION = SequenceCombinator( + ZeroOrMore(COLON), + OPTIONAL_WS, + SafeAtoNumberTerminal(0, 4_294_967_296L), + 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). Validators are keyed by (parser, ltype) only, so both shapes are accepted for both keys - * (lenient; see QUESTIONS.md). 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. + * 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 : SimpleGrammarOptionValues( - "config_parse_dhcp6_send_option", - SequenceCombinator( - ZeroOrMore(COLON), - AlternativeCombinator( - SequenceCombinator(SafeAtoNumberTerminal(0, 4_294_967_296L), COALESCED_COLONS, DHCP6_OPTION_CODE, COALESCED_COLONS, dhcpOptionTypedData(), EOF()), - SequenceCombinator(DHCP6_OPTION_CODE, COALESCED_COLONS, dhcpOptionTypedData(), EOF()), - ), - ) -) +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/ConfigParseDhcpOptionTlvOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpOptionTlvOptionValue.kt index 885fd8ae..943314d7 100644 --- 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 @@ -6,6 +6,8 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.EOF import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SafeAtoNumberTerminal 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.WhitespaceTerminal import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.ZeroOrMore import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.dhcpOptionTypedData @@ -45,6 +47,7 @@ class ConfigParseDhcpOptionTlvOptionValue : SimpleGrammarOptionValues( "config_parse_dhcp_option_tlv", SequenceCombinator( ZeroOrMore(COLON), + ZeroOrOne(WhitespaceTerminal()), SafeAtoNumberTerminal(1, 255), COALESCED_COLONS, dhcpOptionTypedData(), 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 index 1a5c4901..10aefcaa 100644 --- 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 @@ -10,6 +10,9 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram /* * [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) 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 index 4bd4ea3b..77b255d0 100644 --- 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 @@ -6,6 +6,7 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.IPV4_ADDR import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SafeAtoNumberTerminal import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.WhitespaceTerminal import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.ZeroOrOne /* @@ -22,7 +23,8 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram /** * 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), e.g. `192.168.5.1/24`. + * (read by safe_atou8, so base-0 spellings like `0x18` are legal, and whitespace after the `/` is + * skipped), e.g. `192.168.5.1/24`. * * systemd then also refuses a localhost (127.0.0.0/8) or link-local (169.254.0.0/16) address; that * range check is not modelled (lenient). @@ -31,7 +33,7 @@ class ConfigParseDhcpServerAddressOptionValue : SimpleGrammarOptionValues( "config_parse_dhcp_server_address", SequenceCombinator( IPV4_ADDR, - ZeroOrOne(SequenceCombinator(CIDR_SEPARATOR, SafeAtoNumberTerminal(0, 33))), + ZeroOrOne(SequenceCombinator(CIDR_SEPARATOR, ZeroOrOne(WhitespaceTerminal()), SafeAtoNumberTerminal(0, 33))), 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 index 3c661340..d1bf9854 100644 --- 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 @@ -3,7 +3,7 @@ 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.SequenceCombinator -import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.TIME_VALUE +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.PARSE_SEC_TIMESPAN /* * [DHCPServer] IPv6OnlyPreferredSec= (.network). @@ -11,11 +11,14 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram * 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/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 (parse_sec). + * Validator for `[DHCPServer] IPv6OnlyPreferredSec=`: a time span as parse_sec() reads it (`30min`, + * `1h30min`, `.5h`, `infinity`) — see [PARSE_SEC_TIMESPAN]. * * systemd also refuses anything below MIN_V6ONLY_WAIT_USEC (300s) unless networkd runs in its test * mode — systemd's own test suite uses `20s`. That magnitude check depends on the parsed duration and on @@ -23,5 +26,5 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram */ class ConfigParseDhcpServerIpv6OnlyPreferredOptionValue : SimpleGrammarOptionValues( "config_parse_dhcp_server_ipv6_only_preferred", - SequenceCombinator(TIME_VALUE, EOF()) + SequenceCombinator(PARSE_SEC_TIMESPAN, 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 index 263ed818..17cbd836 100644 --- 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 @@ -15,6 +15,9 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram * 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) 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 index 59cb6708..07c68eea 100644 --- 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 @@ -17,6 +17,9 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram * [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) 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 index 7a187eb7..eab25491 100644 --- 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 @@ -5,7 +5,7 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.BOOLEAN_ANY_CASE 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.TIME_VALUE +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.PARSE_SEC_TIMESPAN /* * [Network] IgnoreCarrierLoss= (.network). @@ -14,17 +14,20 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram * 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`, `infinity`). + * 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_ANY_CASE, EOF()), - SequenceCombinator(TIME_VALUE, EOF()), + SequenceCombinator(PARSE_SEC_TIMESPAN, 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 index 87bbec6f..5be47953 100644 --- 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 @@ -11,6 +11,9 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram /* * [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) 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 index 01ffc5f6..552d3792 100644 --- 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 @@ -5,6 +5,7 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram 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 /* @@ -12,25 +13,37 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram * * 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`, `:none` (DHCPServer / IPv6SendRA only), `:self` (DHCPPrefixDelegation only), a - * positive interface index, or an alternative interface name (see [IFNAME_OR_IFINDEX]). Which of - * `:none` / `:self` is legal depends on the *section*, which the (parser, ltype) registration can't see, - * so both are accepted everywhere (lenient). The special values are compared with streq, so they are - * case-sensitive. + * 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 : SimpleGrammarOptionValues( - "config_parse_uplink", - AlternativeCombinator( - SequenceCombinator(FlexibleLiteralChoiceTerminal(":auto", ":none", ":self"), EOF()), - SequenceCombinator(IFNAME_OR_IFINDEX, EOF()), - ) -) +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 index 9a0dafee..45984b7e 100644 --- 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 @@ -1,5 +1,10 @@ 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 + /* * 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). @@ -46,8 +51,10 @@ val BOOLEAN_FALSE_ANY_CASE = FlexibleLiteralChoiceTerminal("0", "no", "n", "fals * With [hexOnly] (safe_atoux64 and friends, which pass an explicit base 16) every number is hexadecimal, * with an optional `0x` that strtoull itself tolerates. * - * Leading whitespace — which strtoul skips — is NOT consumed here; model it in the enclosing grammar - * where it can actually occur (unit-file values arrive stripped). + * Leading whitespace — which safe_atou_full skips — is NOT consumed here; model it in the enclosing + * grammar (an optional [WhitespaceTerminal]) wherever it can occur: after a `:` or `/` separator, since + * unit-file values themselves arrive stripped. Whitespace *after* a `0b`/`0o` prefix, and a sign there, + * is part of this terminal (strtoul sees a fresh string after mangle_base strips the prefix). * * [UnsignedNumberTerminal] covers the common subset (decimal/hex/octal, no sign, no `0b`/`0o`); this one * exists because the batch needed the signed form and the extra prefixes. @@ -83,13 +90,30 @@ class SafeAtoNumberTerminal( return when { s.length > 2 && (s.startsWith("0x") || s.startsWith("0X")) -> s.substring(2).toLongOrNull(16) // mangle_base only sees an unsigned string, so `+0b1` / `-0o7` fail in systemd too. - s.length > 2 && (s.startsWith("0b") || s.startsWith("0B")) -> if (signPresent) null else s.substring(2).toLongOrNull(2) - s.length > 2 && (s.startsWith("0o") || s.startsWith("0O")) -> if (signPresent) null else s.substring(2).toLongOrNull(8) + s.length > 2 && (s.startsWith("0b") || s.startsWith("0B")) -> if (signPresent) null else afterPythonPrefix(s.substring(2), 2) + s.length > 2 && (s.startsWith("0o") || s.startsWith("0O")) -> if (signPresent) null else afterPythonPrefix(s.substring(2), 8) s.length > 1 && s.startsWith("0") -> s.substring(1).toLongOrNull(8) else -> s.toLongOrNull() } } + /** + * The part after a `0b`/`0o` prefix, which mangle_base hands to strtoul() as a fresh string: strtoul + * skips leading whitespace and takes a sign again, so `0b 1`, `0b+1` and `0b-0` are all accepted + * (for the unsigned helpers a negative non-zero result is -ERANGE; for safe_atoi it is just negative). + */ + private fun afterPythonPrefix(rest: String, radix: Int): Long? { + var t = rest.trimStart(' ', '\t', '\n', '\r') + var negative = false + if (t.startsWith("+") || t.startsWith("-")) { + negative = t[0] == '-' + t = t.substring(1) + } + val magnitude = t.toLongOrNull(radix) ?: return null + if (!negative) return magnitude + return if (signed) -magnitude else if (magnitude == 0L) 0L else null + } + // A maxExclusive of Long.MAX_VALUE means "anything that fits a signed 64-bit integer", so that // INT64_MAX itself (the SubnetId= ceiling) stays expressible; larger spellings already fail valueOf(). private fun inRange(text: String): Boolean = valueOf(text)?.let { @@ -116,7 +140,7 @@ class SafeAtoNumberTerminal( private companion object { // Prefixed forms before plain digits so "0x10" isn't read as the single digit 0. - val BASE0_SHAPE = Regex("""[+-]?(?:0[xX][0-9a-fA-F]+|0[bBoO][0-9]+|[0-9]+)""") + val BASE0_SHAPE = Regex("""0[bBoO][ \t\n\r]*[+-]?[0-9]+|[+-]?(?:0[xX][0-9a-fA-F]+|0[bBoO][0-9]+|[0-9]+)""") val HEX_SHAPE = Regex("""[+-]?(?:0[xX])?[0-9a-fA-F]+""") } } @@ -164,17 +188,57 @@ fun wordList(word: Combinator): Combinator = SequenceCombinator( // 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. Like + * [SafeAtoNumberTerminal], 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 +} + +/** + * True iff an all-digit [text] is a positive interface index as parse_ifindex() → safe_atoi() reads it: + * base 0, so a leading `0` means octal (`010` is 8, `08` fails), and the value must be 1…INT_MAX. + */ +private fun isPositiveIfindexDigits(text: String): Boolean { + val v = if (text.length > 1 && text[0] == '0') text.substring(1).toLongOrNull(8) else text.toLongOrNull() + return v != null && v in 1..Int.MAX_VALUE.toLong() +} + /** * A positive interface index, or an alternative interface name (ifname_valid_full with * IFNAME_VALID_ALTERNATIVE: 1…127 printable ASCII characters other than `:`, `/`, `%`; not `.`, `..`, - * `all`, `default`, or all digits). An all-digit spelling is therefore valid exactly when it is a - * positive index. The digit branch carries a `(?!\S)` guard so that a name like `1eth` is still offered - * to the name branch rather than stopping at its leading digit. + * `all`, `default`, or all digits). + * + * An all-digit spelling is therefore valid exactly when parse_ifindex() accepts it: 1…2147483647, with a + * leading `0` meaning octal (`010` is index 8; `0`, `08` and `99999999999` are refused both as an index + * and, being all digits, as a name). A spelling with any non-digit (`0x10`, `+5`, `1eth`) is a legal + * name whatever parse_ifindex makes of it, so only the name rules apply to it. */ -val IFNAME_OR_IFINDEX = RegexTerminal( - """\S+""", - """(?:[0-9]*[1-9][0-9]*(?!\S))|(?!(?:[0-9]+|\.\.?|all|default)(?!$IFNAME_CHAR))$IFNAME_CHAR{1,127}(?!\S)""", -) +val IFNAME_OR_IFINDEX = CheckedTerminal(Regex("""\S+"""), "IfnameOrIfindex") { text -> + if (text.all { it in '0'..'9' }) isPositiveIfindexDigits(text) else IFNAME_ALTERNATIVE.matches(text) +} // --------------------------------------------------------------------------------------------------- // DNS names. @@ -228,6 +292,27 @@ val LDH_DNS_NAME = AlternativeCombinator( // --------------------------------------------------------------------------------------------------- // 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 @@ -238,15 +323,92 @@ val LDH_DNS_NAME = AlternativeCombinator( * - `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. For `ipv6address` systemd would also swallow the leading colons of the address itself (so - * `ipv6address:::1` actually fails there); the grammar takes a single colon and accepts it — lenient. - * The string length check (`check_length`, <= 255 bytes once unescaped) is not modelled. + * 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:` may be preceded by whitespace (safe_atou_full skips it). The string length + * check (`check_length`, <= 255 bytes once unescaped) is not modelled. */ fun dhcpOptionTypedData(): Combinator = AlternativeCombinator( SequenceCombinator(LiteralChoiceTerminal("uint8"), COALESCED_COLONS, ZeroOrOne(WhitespaceTerminal()), SafeAtoNumberTerminal(0, 256L)), SequenceCombinator(LiteralChoiceTerminal("uint16"), COALESCED_COLONS, ZeroOrOne(WhitespaceTerminal()), SafeAtoNumberTerminal(0, 65_536L)), SequenceCombinator(LiteralChoiceTerminal("uint32"), COALESCED_COLONS, ZeroOrOne(WhitespaceTerminal()), SafeAtoNumberTerminal(0, 4_294_967_296L)), SequenceCombinator(LiteralChoiceTerminal("ipv4address"), COALESCED_COLONS, IPV4_ADDR), - SequenceCombinator(LiteralChoiceTerminal("ipv6address"), COLON, IPV6_ADDR), + SequenceCombinator(LiteralChoiceTerminal("ipv6address"), COALESCED_COLONS, IPV6_ADDR_NO_LEADING_COLON), SequenceCombinator(LiteralChoiceTerminal("string"), 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))), +) + +// --------------------------------------------------------------------------------------------------- +// 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/ConfigParseDhcp6SendOptionOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcp6SendOptionOptionValueTest.kt index 824353a5..78d47257 100644 --- 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 @@ -17,6 +17,10 @@ class ConfigParseDhcp6SendOptionOptionValueTest : AbstractUnitFileTest() { 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 @@ -42,12 +46,15 @@ class ConfigParseDhcp6SendOptionOptionValueTest : AbstractUnitFileTest() { 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(7, highlights) + assertSize(10, 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 index 7d466f7d..fef86646 100644 --- 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 @@ -23,6 +23,10 @@ class ConfigParseDhcpOptionTlvOptionValueTest : AbstractUnitFileTest() { 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] @@ -55,12 +59,15 @@ class ConfigParseDhcpOptionTlvOptionValueTest : AbstractUnitFileTest() { [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(12, highlights) + 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 index 54843dc0..5404d8eb 100644 --- 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 @@ -20,6 +20,7 @@ class ConfigParseDhcpOptionTypedOptionValueTest : AbstractUnitFileTest() { 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= @@ -43,12 +44,14 @@ class ConfigParseDhcpOptionTypedOptionValueTest : AbstractUnitFileTest() { 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(6, highlights) + assertSize(8, 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 index 6a46d449..20fed7d9 100644 --- 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 @@ -17,6 +17,7 @@ class ConfigParseDhcpServerAddressOptionValueTest : AbstractUnitFileTest() { 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= """.trimIndent() 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 index a7ef05af..25072770 100644 --- 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 @@ -17,6 +17,10 @@ class ConfigParseDhcpServerIpv6OnlyPreferredOptionValueTest : AbstractUnitFileTe IPv6OnlyPreferredSec=5min IPv6OnlyPreferredSec=300 IPv6OnlyPreferredSec=1h 5min + IPv6OnlyPreferredSec=1h30min + IPv6OnlyPreferredSec=.5h + IPv6OnlyPreferredSec=10min5s + IPv6OnlyPreferredSec=infinity IPv6OnlyPreferredSec= """.trimIndent() @@ -35,12 +39,15 @@ class ConfigParseDhcpServerIpv6OnlyPreferredOptionValueTest : AbstractUnitFileTe 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(3, highlights) + 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 index 50246211..efe6bb01 100644 --- 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 @@ -14,6 +14,7 @@ class ConfigParseFdbInterfaceOptionValueTest : AbstractUnitFileTest() { OutgoingInterface=test1 OutgoingInterface=dummy98 OutgoingInterface=7 + OutgoingInterface=010 OutgoingInterface=an-alternative-name-longer-than-fifteen OutgoingInterface= """.trimIndent() @@ -34,6 +35,8 @@ class ConfigParseFdbInterfaceOptionValueTest : AbstractUnitFileTest() { OutgoingInterface=default OutgoingInterface=a%d OutgoingInterface=.. + OutgoingInterface=08 + OutgoingInterface=4294967297 OutgoingInterface=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx """.trimIndent() @@ -41,6 +44,6 @@ class ConfigParseFdbInterfaceOptionValueTest : AbstractUnitFileTest() { enableInspection(InvalidValueInspection::class.java) val highlights = myFixture.doHighlighting() - assertSize(5, highlights) + assertSize(7, 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 index 4c2f6192..91540fab 100644 --- 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 @@ -18,6 +18,15 @@ class ConfigParseIgnoreCarrierLossOptionValueTest : AbstractUnitFileTest() { 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() @@ -36,12 +45,20 @@ class ConfigParseIgnoreCarrierLossOptionValueTest : AbstractUnitFileTest() { 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 """.trimIndent() setupFileInEditor("file.network", file) enableInspection(InvalidValueInspection::class.java) val highlights = myFixture.doHighlighting() - assertSize(3, highlights) + assertSize(11, 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 index 8e853987..4e076f37 100644 --- 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 @@ -8,8 +8,7 @@ class ConfigParseUplinkOptionValueTest : AbstractUnitFileTest() { @Test fun testValidValues() { - // `:none` is only meaningful for [DHCPServer]/[IPv6SendRA] and `:self` only for - // [DHCPPrefixDelegation]; the validator can't see the section, so both are accepted everywhere. + // `:none` is only accepted in [DHCPServer]/[IPv6SendRA] and `:self` only in [DHCPPrefixDelegation]. // language="unit file (systemd)" val file = """ [DHCPServer] @@ -26,6 +25,9 @@ class ConfigParseUplinkOptionValueTest : AbstractUnitFileTest() { [IPv6SendRA] UplinkInterface=this-is-a-long-alternative-interface-name UplinkInterface=0x10 + UplinkInterface=:none + UplinkInterface=010 + UplinkInterface=2147483647 """.trimIndent() setupFileInEditor("file.network", file) @@ -48,12 +50,20 @@ class ConfigParseUplinkOptionValueTest : AbstractUnitFileTest() { 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(6, highlights) + assertSize(12, highlights) } } From cfce4ee9e5454feae1824bbe47ade425208b12e3 Mon Sep 17 00:00:00 2001 From: Steve Ramage Date: Fri, 23 Oct 2026 19:07:10 -0700 Subject: [PATCH 3/7] refactor: give network-dhcp-misc batch grammar helpers family-unique names Several batch branches declared public helpers with the same name in the shared grammar package (SafeAtouTerminal, UINT32_LIMIT, safeAtou, SafeAtoNumberTerminal, BOOLEAN_ANY_CASE, COALESCED_COLONS). Each branch compiled alone but any two merged together would not. Prefix them per family so the batch PRs can merge in any order; consolidating them into Combinators.kt is left for a follow-up. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../ai/ConfigParseBridgeVlanIdOptionValue.kt | 4 ++-- .../ConfigParseDhcp6SendOptionOptionValue.kt | 12 +++++----- .../ai/ConfigParseDhcpOptionTlvOptionValue.kt | 8 +++---- .../ConfigParseDhcpPdSubnetIdOptionValue.kt | 4 ++-- ...onfigParseDhcpRequestOptionsOptionValue.kt | 4 ++-- ...ConfigParseDhcpServerAddressOptionValue.kt | 4 ++-- .../ConfigParseDnsOverTlsModeOptionValue.kt | 4 ++-- .../ai/ConfigParseDuidRawdataOptionValue.kt | 4 ++-- .../ai/ConfigParseDuidTypeOptionValue.kt | 10 ++++----- ...ConfigParseIgnoreCarrierLossOptionValue.kt | 4 ++-- .../ai/ConfigParseIpv4llOptionValue.kt | 4 ++-- .../ai/ConfigParseLinkGroupOptionValue.kt | 4 ++-- ...ConfigParseLldpMulticastModeOptionValue.kt | 4 ++-- ...ConfigParseRequiredForOnlineOptionValue.kt | 4 ++-- .../ConfigParseResolveSupportOptionValue.kt | 4 ++-- .../BatchNetworkDhcpMiscCombinators.kt | 22 +++++++++---------- 16 files changed, 50 insertions(+), 50 deletions(-) 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 index aaa32eb0..499359f2 100644 --- 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 @@ -4,7 +4,7 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.Simp import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.AlternativeCombinator import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.BOOLEAN_FALSE_ANY_CASE import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.EOF -import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SafeAtoNumberTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.NetworkDhcpMiscSafeAtoNumberTerminal import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator /* @@ -26,7 +26,7 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram class ConfigParseBridgeVlanIdOptionValue : SimpleGrammarOptionValues( "config_parse_bridge_vlan_id", AlternativeCombinator( - SequenceCombinator(SafeAtoNumberTerminal(0, 4095), EOF()), + SequenceCombinator(NetworkDhcpMiscSafeAtoNumberTerminal(0, 4095), EOF()), SequenceCombinator(BOOLEAN_FALSE_ANY_CASE, 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 index 987450d3..62d3c449 100644 --- 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 @@ -2,11 +2,11 @@ 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.COALESCED_COLONS +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.SafeAtoNumberTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.NetworkDhcpMiscSafeAtoNumberTerminal import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.WhitespaceTerminal import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.ZeroOrMore @@ -29,21 +29,21 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram * linux https://man7.org/linux/man-pages/man3/inet_pton.3.html */ -private val DHCP6_OPTION_CODE = SafeAtoNumberTerminal(1, 65_535L) +private val DHCP6_OPTION_CODE = NetworkDhcpMiscSafeAtoNumberTerminal(1, 65_535L) // 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. private val OPTIONAL_WS = ZeroOrOne(WhitespaceTerminal()) -private val CODE_TYPE_DATA = SequenceCombinator(OPTIONAL_WS, DHCP6_OPTION_CODE, COALESCED_COLONS, dhcpOptionTypedData(), EOF()) +private val CODE_TYPE_DATA = SequenceCombinator(OPTIONAL_WS, DHCP6_OPTION_CODE, 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), OPTIONAL_WS, - SafeAtoNumberTerminal(0, 4_294_967_296L), - COALESCED_COLONS, + NetworkDhcpMiscSafeAtoNumberTerminal(0, 4_294_967_296L), + NETWORK_DHCP_MISC_COALESCED_COLONS, CODE_TYPE_DATA, ) 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 index 943314d7..7f0a4792 100644 --- 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 @@ -1,10 +1,10 @@ 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.COALESCED_COLONS +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.SafeAtoNumberTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.NetworkDhcpMiscSafeAtoNumberTerminal 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.WhitespaceTerminal @@ -48,8 +48,8 @@ class ConfigParseDhcpOptionTlvOptionValue : SimpleGrammarOptionValues( SequenceCombinator( ZeroOrMore(COLON), ZeroOrOne(WhitespaceTerminal()), - SafeAtoNumberTerminal(1, 255), - COALESCED_COLONS, + NetworkDhcpMiscSafeAtoNumberTerminal(1, 255), + NETWORK_DHCP_MISC_COALESCED_COLONS, dhcpOptionTypedData(), 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 index 10aefcaa..11510b93 100644 --- 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 @@ -4,7 +4,7 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.Simp 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.SafeAtoNumberTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.NetworkDhcpMiscSafeAtoNumberTerminal import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator /* @@ -31,6 +31,6 @@ class ConfigParseDhcpPdSubnetIdOptionValue : SimpleGrammarOptionValues( "config_parse_dhcp_pd_subnet_id", AlternativeCombinator( SequenceCombinator(FlexibleLiteralChoiceTerminal("auto"), EOF()), - SequenceCombinator(SafeAtoNumberTerminal(0, Long.MAX_VALUE, hexOnly = true), EOF()), + SequenceCombinator(NetworkDhcpMiscSafeAtoNumberTerminal(0, Long.MAX_VALUE, hexOnly = true), 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 index 1fd31910..d98ca780 100644 --- 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 @@ -2,7 +2,7 @@ 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.SafeAtoNumberTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.NetworkDhcpMiscSafeAtoNumberTerminal import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.wordList @@ -23,5 +23,5 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram */ class ConfigParseDhcpRequestOptionsOptionValue : SimpleGrammarOptionValues( "config_parse_dhcp_request_options", - SequenceCombinator(wordList(SafeAtoNumberTerminal(1, 255)), EOF()) + SequenceCombinator(wordList(NetworkDhcpMiscSafeAtoNumberTerminal(1, 255)), 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 index 77b255d0..5a0eb0f7 100644 --- 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 @@ -4,7 +4,7 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.Simp 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.IPV4_ADDR -import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SafeAtoNumberTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.NetworkDhcpMiscSafeAtoNumberTerminal import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.WhitespaceTerminal import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.ZeroOrOne @@ -33,7 +33,7 @@ class ConfigParseDhcpServerAddressOptionValue : SimpleGrammarOptionValues( "config_parse_dhcp_server_address", SequenceCombinator( IPV4_ADDR, - ZeroOrOne(SequenceCombinator(CIDR_SEPARATOR, ZeroOrOne(WhitespaceTerminal()), SafeAtoNumberTerminal(0, 33))), + ZeroOrOne(SequenceCombinator(CIDR_SEPARATOR, ZeroOrOne(WhitespaceTerminal()), NetworkDhcpMiscSafeAtoNumberTerminal(0, 33))), 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 index faeb20bd..1830758d 100644 --- 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 @@ -2,7 +2,7 @@ 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_ANY_CASE +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.NETWORK_DHCP_MISC_BOOLEAN_ANY_CASE 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 @@ -26,6 +26,6 @@ class ConfigParseDnsOverTlsModeOptionValue : SimpleGrammarOptionValues( "config_parse_dns_over_tls_mode", AlternativeCombinator( SequenceCombinator(FlexibleLiteralChoiceTerminal("opportunistic"), EOF()), - SequenceCombinator(BOOLEAN_ANY_CASE, EOF()), + SequenceCombinator(NETWORK_DHCP_MISC_BOOLEAN_ANY_CASE, 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 index 17cbd836..e54a791c 100644 --- 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 @@ -1,7 +1,7 @@ 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.COALESCED_COLONS +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 @@ -48,7 +48,7 @@ class ConfigParseDuidRawdataOptionValue(validatorName: String = "config_parse_du ZeroOrOne( SequenceCombinator( DUID_BYTE, - Repeat(SequenceCombinator(COALESCED_COLONS, DUID_BYTE), 0, 127), + Repeat(SequenceCombinator(NETWORK_DHCP_MISC_COALESCED_COLONS, DUID_BYTE), 0, 127), ZeroOrMore(COLON), ), ), 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 index 07c68eea..68481482 100644 --- 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 @@ -2,13 +2,13 @@ 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.COALESCED_COLONS +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.SafeAtoNumberTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.NetworkDhcpMiscSafeAtoNumberTerminal import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.ZeroOrMore @@ -42,10 +42,10 @@ 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"), COALESCED_COLONS, DUID_TIMESTAMP, EOF()), - SequenceCombinator(SafeAtoNumberTerminal(1, 2), COALESCED_COLONS, DUID_TIMESTAMP, EOF()), + SequenceCombinator(FlexibleLiteralChoiceTerminal("link-layer-time"), NETWORK_DHCP_MISC_COALESCED_COLONS, DUID_TIMESTAMP, EOF()), + SequenceCombinator(NetworkDhcpMiscSafeAtoNumberTerminal(1, 2), NETWORK_DHCP_MISC_COALESCED_COLONS, DUID_TIMESTAMP, EOF()), SequenceCombinator(DUID_TYPE_NAME, ZeroOrMore(COLON), EOF()), - SequenceCombinator(SafeAtoNumberTerminal(0, 65_536), ZeroOrMore(COLON), EOF()), + SequenceCombinator(NetworkDhcpMiscSafeAtoNumberTerminal(0, 65_536), ZeroOrMore(COLON), 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 index eab25491..190497a9 100644 --- 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 @@ -2,7 +2,7 @@ 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_ANY_CASE +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.NETWORK_DHCP_MISC_BOOLEAN_ANY_CASE 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 @@ -27,7 +27,7 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram class ConfigParseIgnoreCarrierLossOptionValue : SimpleGrammarOptionValues( "config_parse_ignore_carrier_loss", AlternativeCombinator( - SequenceCombinator(BOOLEAN_ANY_CASE, EOF()), + SequenceCombinator(NETWORK_DHCP_MISC_BOOLEAN_ANY_CASE, EOF()), SequenceCombinator(PARSE_SEC_TIMESPAN, 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 index b4e83ffa..fec8df6a 100644 --- 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 @@ -1,7 +1,7 @@ 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_ANY_CASE +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.NETWORK_DHCP_MISC_BOOLEAN_ANY_CASE import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.EOF import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator @@ -19,5 +19,5 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram */ class ConfigParseIpv4llOptionValue : SimpleGrammarOptionValues( "config_parse_ipv4ll", - SequenceCombinator(BOOLEAN_ANY_CASE, EOF()) + SequenceCombinator(NETWORK_DHCP_MISC_BOOLEAN_ANY_CASE, 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 index 7f5e7eae..77c7885a 100644 --- 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 @@ -2,7 +2,7 @@ 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.SafeAtoNumberTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.NetworkDhcpMiscSafeAtoNumberTerminal import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator /* @@ -20,5 +20,5 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram */ class ConfigParseLinkGroupOptionValue : SimpleGrammarOptionValues( "config_parse_link_group", - SequenceCombinator(SafeAtoNumberTerminal(0, 2_147_483_648L, signed = true), EOF()) + SequenceCombinator(NetworkDhcpMiscSafeAtoNumberTerminal(0, 2_147_483_648L, signed = 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 index e909e54f..42ff828c 100644 --- 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 @@ -2,7 +2,7 @@ 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_ANY_CASE +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.NETWORK_DHCP_MISC_BOOLEAN_ANY_CASE 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 @@ -26,6 +26,6 @@ class ConfigParseLldpMulticastModeOptionValue : SimpleGrammarOptionValues( "config_parse_lldp_multicast_mode", AlternativeCombinator( SequenceCombinator(FlexibleLiteralChoiceTerminal("nearest-bridge", "non-tpmr-bridge", "customer-bridge"), EOF()), - SequenceCombinator(BOOLEAN_ANY_CASE, EOF()), + SequenceCombinator(NETWORK_DHCP_MISC_BOOLEAN_ANY_CASE, 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 index fecc4217..a078532b 100644 --- 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 @@ -2,7 +2,7 @@ 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_ANY_CASE +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.NETWORK_DHCP_MISC_BOOLEAN_ANY_CASE 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 @@ -41,6 +41,6 @@ class ConfigParseRequiredForOnlineOptionValue : SimpleGrammarOptionValues( AlternativeCombinator( SequenceCombinator(OPERSTATE, ZeroOrOne(SequenceCombinator(COLON, ZeroOrOne(OPERSTATE))), EOF()), SequenceCombinator(COLON, OPERSTATE, EOF()), - SequenceCombinator(BOOLEAN_ANY_CASE, EOF()), + SequenceCombinator(NETWORK_DHCP_MISC_BOOLEAN_ANY_CASE, 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 index b4ca4ef9..f0f59e76 100644 --- 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 @@ -2,7 +2,7 @@ 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_ANY_CASE +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.NETWORK_DHCP_MISC_BOOLEAN_ANY_CASE 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 @@ -29,6 +29,6 @@ class ConfigParseResolveSupportOptionValue : SimpleGrammarOptionValues( "config_parse_resolve_support", AlternativeCombinator( SequenceCombinator(FlexibleLiteralChoiceTerminal("resolve"), EOF()), - SequenceCombinator(BOOLEAN_ANY_CASE, EOF()), + SequenceCombinator(NETWORK_DHCP_MISC_BOOLEAN_ANY_CASE, EOF()), ) ) 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 index 45984b7e..1d5d0f3e 100644 --- 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 @@ -32,9 +32,9 @@ import net.sjrx.intellij.plugins.systemdunitfiles.psi.UnitFileSectionGroups * with STRCASE_IN_SET, i.e. case-insensitively (`Yes`, `TRUE` and `Off` are all accepted). The shared * [BOOLEAN] is case-sensitive and therefore flags those; this one doesn't. */ -val BOOLEAN_ANY_CASE = FlexibleLiteralChoiceTerminal(*BOOLEAN_SPELLINGS, ignoreCase = true) +val NETWORK_DHCP_MISC_BOOLEAN_ANY_CASE = FlexibleLiteralChoiceTerminal(*BOOLEAN_SPELLINGS, ignoreCase = true) -/** Only the false half of parse_boolean(), case-insensitively — see [BOOLEAN_FALSE] and [BOOLEAN_ANY_CASE]. */ +/** Only the false half of parse_boolean(), case-insensitively — see [BOOLEAN_FALSE] and [NETWORK_DHCP_MISC_BOOLEAN_ANY_CASE]. */ val BOOLEAN_FALSE_ANY_CASE = FlexibleLiteralChoiceTerminal("0", "no", "n", "false", "f", "off", ignoreCase = true) /** @@ -59,7 +59,7 @@ val BOOLEAN_FALSE_ANY_CASE = FlexibleLiteralChoiceTerminal("0", "no", "n", "fals * [UnsignedNumberTerminal] covers the common subset (decimal/hex/octal, no sign, no `0b`/`0o`); this one * exists because the batch needed the signed form and the extra prefixes. */ -class SafeAtoNumberTerminal( +class NetworkDhcpMiscSafeAtoNumberTerminal( private val minInclusive: Long, private val maxExclusive: Long, private val signed: Boolean = false, @@ -146,7 +146,7 @@ class SafeAtoNumberTerminal( } /** One or more `:` — extract_first_word(…, ":", 0) coalesces runs of its separator. */ -val COALESCED_COLONS = OneOrMore(COLON) +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`, @@ -192,7 +192,7 @@ private val IFNAME_ALTERNATIVE = Regex("""(?!(?:\.\.?|all|default)\z)$IFNAME_CHA /** * A terminal that takes the span [shape] matches and accepts it iff [valid] says so. Like - * [SafeAtoNumberTerminal], for leaves whose acceptance is a computation rather than a regular language. + * [NetworkDhcpMiscSafeAtoNumberTerminal], 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 { @@ -332,12 +332,12 @@ val IPV6_ADDR_NO_LEADING_COLON = Labeled(Role.LITERAL, tag = SemanticTag.IPV6, i * check (`check_length`, <= 255 bytes once unescaped) is not modelled. */ fun dhcpOptionTypedData(): Combinator = AlternativeCombinator( - SequenceCombinator(LiteralChoiceTerminal("uint8"), COALESCED_COLONS, ZeroOrOne(WhitespaceTerminal()), SafeAtoNumberTerminal(0, 256L)), - SequenceCombinator(LiteralChoiceTerminal("uint16"), COALESCED_COLONS, ZeroOrOne(WhitespaceTerminal()), SafeAtoNumberTerminal(0, 65_536L)), - SequenceCombinator(LiteralChoiceTerminal("uint32"), COALESCED_COLONS, ZeroOrOne(WhitespaceTerminal()), SafeAtoNumberTerminal(0, 4_294_967_296L)), - SequenceCombinator(LiteralChoiceTerminal("ipv4address"), COALESCED_COLONS, IPV4_ADDR), - SequenceCombinator(LiteralChoiceTerminal("ipv6address"), COALESCED_COLONS, IPV6_ADDR_NO_LEADING_COLON), - SequenceCombinator(LiteralChoiceTerminal("string"), COALESCED_COLONS, C_ESCAPED_TEXT), + SequenceCombinator(LiteralChoiceTerminal("uint8"), NETWORK_DHCP_MISC_COALESCED_COLONS, ZeroOrOne(WhitespaceTerminal()), NetworkDhcpMiscSafeAtoNumberTerminal(0, 256L)), + SequenceCombinator(LiteralChoiceTerminal("uint16"), NETWORK_DHCP_MISC_COALESCED_COLONS, ZeroOrOne(WhitespaceTerminal()), NetworkDhcpMiscSafeAtoNumberTerminal(0, 65_536L)), + SequenceCombinator(LiteralChoiceTerminal("uint32"), NETWORK_DHCP_MISC_COALESCED_COLONS, ZeroOrOne(WhitespaceTerminal()), NetworkDhcpMiscSafeAtoNumberTerminal(0, 4_294_967_296L)), + 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), ) // --------------------------------------------------------------------------------------------------- From 8f39463c4007599afffdfbdb8ac90dfb0294e589 Mon Sep 17 00:00:00 2001 From: Steve Ramage Date: Sat, 24 Oct 2026 06:55:42 -0700 Subject: [PATCH 4/7] fix: align network-dhcp-misc validators with systemd's parsers Checked against systemd's real parsers built at 0f0682b773 (sd-check): - [DHCPServer] IPv6OnlyPreferredSec=: flag durations below 300s (MIN_V6ONLY_WAIT_USEC); systemd ignores them outside its test mode. - [Network] Domains=: flag localhost, localhost.localdomain and their subdomains (is_localhost), with or without the ~ prefix. - [DHCPServer] ServerAddress=: flag 127.0.0.0/8 and 169.254.0.0/16. - [DHCPv4]/[DHCPv6] UserClass=, VendorClass=: flag entries that unquote to the empty string. - NTP=/Domains=/DNSSECNegativeTrustAnchors=: a doubled backslash must start a valid dns_label_unescape escape (foo\\bar, foo\\256 refused). Adds NetworkDhcpMiscCheckedSpan (a span predicate over a structured sub-grammar) and networkDhcpMiscParseSecUsec for these checks, and tests with the exact values systemd accepts and rejects. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../ConfigParseDhcp4UserClassOptionValue.kt | 8 +- ...gParseDhcp6UserOrVendorClassOptionValue.kt | 7 +- ...ConfigParseDhcpServerAddressOptionValue.kt | 14 ++- ...eDhcpServerIpv6OnlyPreferredOptionValue.kt | 17 ++- .../ai/ConfigParseDomainsOptionValue.kt | 21 +++- .../BatchNetworkDhcpMiscCombinators.kt | 112 +++++++++++++++++- ...onfigParseDhcp4UserClassOptionValueTest.kt | 8 +- ...nfigParseDhcp6SendOptionOptionValueTest.kt | 3 + ...seDhcp6UserOrVendorClassOptionValueTest.kt | 4 +- ...ConfigParseDhcpOptionTlvOptionValueTest.kt | 3 + ...nfigParseDhcpOptionTypedOptionValueTest.kt | 3 + ...igParseDhcpServerAddressOptionValueTest.kt | 8 +- ...pServerIpv6OnlyPreferredOptionValueTest.kt | 31 ++++- ...ssecNegativeTrustAnchorsOptionValueTest.kt | 6 +- .../ai/ConfigParseDomainsOptionValueTest.kt | 16 ++- .../ai/ConfigParseIpv4llOptionValueTest.kt | 3 + .../ai/ConfigParseNtpOptionValueTest.kt | 12 +- 17 files changed, 244 insertions(+), 32 deletions(-) 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 index 3b59373f..922bad0d 100644 --- 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 @@ -2,6 +2,7 @@ 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 @@ -24,10 +25,11 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram * 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 `""` or an over-long one is - * dropped with a warning); those length checks are not modelled (lenient). + * 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(CUNESCAPE_UNQUOTE_WORD), EOF()) + SequenceCombinator(wordList(NETWORK_DHCP_MISC_NONEMPTY_UNQUOTE_WORD), EOF()) ) 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 index 10845b93..d28876f8 100644 --- 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 @@ -1,7 +1,7 @@ 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 @@ -18,9 +18,10 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram /** * Validator for `[DHCPv6] UserClass=` and `[DHCPv6] VendorClass=`: the same quoted/escaped word list as - * [ConfigParseDhcp4UserClassOptionValue], with a 1…65535-byte length limit per entry (not modelled). + * [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(CUNESCAPE_UNQUOTE_WORD), EOF()) + SequenceCombinator(wordList(NETWORK_DHCP_MISC_NONEMPTY_UNQUOTE_WORD), 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 index 5a0eb0f7..48b91c07 100644 --- 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 @@ -3,6 +3,7 @@ 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.NetworkDhcpMiscSafeAtoNumberTerminal import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator @@ -15,6 +16,9 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram * 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 @@ -26,13 +30,17 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram * (read by safe_atou8, so base-0 spellings like `0x18` are legal, and whitespace after the `/` is * skipped), e.g. `192.168.5.1/24`. * - * systemd then also refuses a localhost (127.0.0.0/8) or link-local (169.254.0.0/16) address; that - * range check is not modelled (lenient). + * 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( - IPV4_ADDR, + 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, ZeroOrOne(WhitespaceTerminal()), NetworkDhcpMiscSafeAtoNumberTerminal(0, 33))), 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 index d1bf9854..e90e3194 100644 --- 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 @@ -2,6 +2,8 @@ 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 @@ -10,6 +12,8 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram * * 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) @@ -20,11 +24,16 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram * Validator for `[DHCPServer] IPv6OnlyPreferredSec=`: a time span as parse_sec() reads it (`30min`, * `1h30min`, `.5h`, `infinity`) — see [PARSE_SEC_TIMESPAN]. * - * systemd also refuses anything below MIN_V6ONLY_WAIT_USEC (300s) unless networkd runs in its test - * mode — systemd's own test suite uses `20s`. That magnitude check depends on the parsed duration and on - * a runtime flag, so it is not modelled (lenient). + * 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(PARSE_SEC_TIMESPAN, EOF()) + 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/ConfigParseDomainsOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDomainsOptionValue.kt index 12081830..b17cdd76 100644 --- 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 @@ -2,6 +2,8 @@ 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 @@ -17,6 +19,7 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram * 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 */ @@ -25,10 +28,16 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram // 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`. -private val DOMAIN_WORD = SequenceCombinator( - ZeroOrOne(LiteralChoiceTerminal("~")), - ZeroOrOne(DNS_NAME_WORD), -) +// +// 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 @@ -36,7 +45,9 @@ private val DOMAIN_WORD = SequenceCombinator( * 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. * - * systemd also ignores `localhost` and `*.localhost` here (is_localhost); that is not modelled (lenient). + * `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", 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 index 1d5d0f3e..8f23e061 100644 --- 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 @@ -172,6 +172,21 @@ val CUNESCAPE_UNQUOTE_WORD = RegexTerminal( """(?:[^\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, @@ -252,14 +267,23 @@ 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 further `\` as the - * start of a `\\`, `\.` or `\DDD` escape. Modelling both layers exactly isn't worth it, so any - * backslash-plus-character is accepted as label content (lenient). A label of 64 or more plain characters - * is refused: that is over DNS_LABEL_MAX whatever follows. + * 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|\\[\s\S])+(?=[\s.]|\z)""", + """(?!$DNS_PLAIN{64})(?:$DNS_PLAIN|\\[^\\]|\\\\(?:\\[\\.]|[01][0-9]{2}|2[0-4][0-9]|25[0-5]|(?=\.)))+(?=[\s.]|\z)""", ) /** @@ -273,6 +297,18 @@ val DNS_NAME_WORD = AlternativeCombinator( 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-]+""", @@ -388,6 +424,72 @@ val PARSE_SEC_TIMESPAN = AlternativeCombinator( 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`). 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 index 6c46f2b6..06c3ac1f 100644 --- 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 @@ -16,6 +16,7 @@ class ConfigParseDhcp4UserClassOptionValueTest : AbstractUnitFileTest() { UserClass="with space" 'single quoted' UserClass=esc\x41ped tab\tbed UserClass=mid"dle quo"te + UserClass="a" UserClass= """.trimIndent() @@ -34,12 +35,17 @@ class ConfigParseDhcp4UserClassOptionValueTest : AbstractUnitFileTest() { 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(3, highlights) + 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 index 78d47257..322b6ba9 100644 --- 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 @@ -36,6 +36,9 @@ class ConfigParseDhcp6SendOptionOptionValueTest : AbstractUnitFileTest() { @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] 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 index a8ed1821..2f1a3ff0 100644 --- 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 @@ -31,12 +31,14 @@ class ConfigParseDhcp6UserOrVendorClassOptionValueTest : AbstractUnitFileTest() [DHCPv6] UserClass='unterminated VendorClass=bad\zescape + UserClass="" + VendorClass="" """.trimIndent() setupFileInEditor("file.network", file) enableInspection(InvalidValueInspection::class.java) val highlights = myFixture.doHighlighting() - assertSize(2, highlights) + assertSize(4, 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 index fef86646..86c9b32f 100644 --- 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 @@ -43,6 +43,9 @@ class ConfigParseDhcpOptionTlvOptionValueTest : AbstractUnitFileTest() { @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] 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 index 5404d8eb..c8caec50 100644 --- 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 @@ -35,6 +35,9 @@ class ConfigParseDhcpOptionTypedOptionValueTest : AbstractUnitFileTest() { @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] 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 index 20fed7d9..4284b30e 100644 --- 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 @@ -18,6 +18,8 @@ class ConfigParseDhcpServerAddressOptionValueTest : AbstractUnitFileTest() { 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() @@ -37,12 +39,16 @@ class ConfigParseDhcpServerAddressOptionValueTest : AbstractUnitFileTest() { 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(4, highlights) + 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 index 25072770..a797d59b 100644 --- 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 @@ -8,13 +8,15 @@ class ConfigParseDhcpServerIpv6OnlyPreferredOptionValueTest : AbstractUnitFileTe @Test fun testValidValues() { - // systemd refuses values under 300s outside its test mode; that magnitude check isn't modelled, - // so systemd's own test value of 20s is accepted. + // Anything >= MIN_V6ONLY_WAIT_USEC (300s) is accepted by systemd's parser (sd-check, 0f0682b773). // language="unit file (systemd)" val file = """ [DHCPServer] - IPv6OnlyPreferredSec=20s IPv6OnlyPreferredSec=5min + IPv6OnlyPreferredSec=5m + IPv6OnlyPreferredSec=4min 60s + IPv6OnlyPreferredSec=300000000us + IPv6OnlyPreferredSec=1M IPv6OnlyPreferredSec=300 IPv6OnlyPreferredSec=1h 5min IPv6OnlyPreferredSec=1h30min @@ -50,4 +52,27 @@ class ConfigParseDhcpServerIpv6OnlyPreferredOptionValueTest : AbstractUnitFileTe 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/ConfigParseDnssecNegativeTrustAnchorsOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDnssecNegativeTrustAnchorsOptionValueTest.kt index 72f50490..bb226b27 100644 --- 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 @@ -15,6 +15,9 @@ class ConfigParseDnssecNegativeTrustAnchorsOptionValueTest : AbstractUnitFileTes DNSSECNegativeTrustAnchors=test local. corp.example DNSSECNegativeTrustAnchors=. DNSSECNegativeTrustAnchors=_tcp.example + DNSSECNegativeTrustAnchors=foo\.bar + DNSSECNegativeTrustAnchors=foo\\\.bar + DNSSECNegativeTrustAnchors=localhost DNSSECNegativeTrustAnchors= """.trimIndent() @@ -33,12 +36,13 @@ class ConfigParseDnssecNegativeTrustAnchorsOptionValueTest : AbstractUnitFileTes 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(3, highlights) + 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 index 177eb5a0..7c876458 100644 --- 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 @@ -21,6 +21,12 @@ class ConfigParseDomainsOptionValueTest : AbstractUnitFileTest() { 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() @@ -40,12 +46,20 @@ class ConfigParseDomainsOptionValueTest : AbstractUnitFileTest() { 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(4, highlights) + assertSize(12, 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 index 36f4143f..adb6ad53 100644 --- 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 @@ -14,6 +14,9 @@ class ConfigParseIpv4llOptionValueTest : AbstractUnitFileTest() { IPv4LL=yes IPv4LL=no IPv4LL=True + IPv4LL=Yes + IPv4LL=TRUE + IPv4LL=Off """.trimIndent() setupFileInEditor("file.network", file) 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 index 0cea0ba5..1de65ec4 100644 --- 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 @@ -16,6 +16,14 @@ class ConfigParseNtpOptionValueTest : AbstractUnitFileTest() { 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) @@ -33,12 +41,14 @@ class ConfigParseNtpOptionValueTest : AbstractUnitFileTest() { 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(3, highlights) + assertSize(5, highlights) } } From cd6298c9f715f5204b9ca56c7c5b2d20a6fb832f Mon Sep 17 00:00:00 2001 From: Steve Ramage Date: Sat, 24 Oct 2026 07:41:18 -0700 Subject: [PATCH 5/7] refactor: use the shared case-insensitive BOOLEAN in network-dhcp-misc validators 242.x's BOOLEAN, BOOLEAN_FALSE and BOOLEAN_TRUE now ignore case, as parse_boolean() does, so the batch's own NETWORK_DHCP_MISC_BOOLEAN_ANY_CASE and BOOLEAN_FALSE_ANY_CASE copies are gone. DNSOverTLS=, LLMNR=/MulticastDNS= and EmitLLDP= (parse_boolean first, then a case-sensitive streq lookup) now use the explicit PreferValidAlternativeCombinator(names, BOOLEAN) form. Tests gain mixed-case booleans and mixed-case names that must stay invalid, checked against systemd's parsers at 0f0682b773. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../ai/ConfigParseBridgeVlanIdOptionValue.kt | 4 ++-- .../ai/ConfigParseDnsOverTlsModeOptionValue.kt | 13 ++++++++----- .../ai/ConfigParseIgnoreCarrierLossOptionValue.kt | 4 ++-- .../optionvalues/ai/ConfigParseIpv4llOptionValue.kt | 4 ++-- .../ai/ConfigParseLldpMulticastModeOptionValue.kt | 13 ++++++++----- .../ai/ConfigParseRequiredForOnlineOptionValue.kt | 4 ++-- .../ai/ConfigParseResolveSupportOptionValue.kt | 13 ++++++++----- .../grammar/BatchNetworkDhcpMiscCombinators.kt | 10 ---------- .../ai/ConfigParseBridgeVlanIdOptionValueTest.kt | 6 +++++- .../ai/ConfigParseDnsOverTlsModeOptionValueTest.kt | 5 ++++- .../ConfigParseIgnoreCarrierLossOptionValueTest.kt | 6 +++++- .../ai/ConfigParseIpv4llOptionValueTest.kt | 4 +++- .../ConfigParseLldpMulticastModeOptionValueTest.kt | 6 +++++- .../ConfigParseRequiredForOnlineOptionValueTest.kt | 6 +++++- .../ai/ConfigParseResolveSupportOptionValueTest.kt | 5 ++++- 15 files changed, 63 insertions(+), 40 deletions(-) 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 index 499359f2..8b344ca6 100644 --- 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 @@ -2,7 +2,7 @@ 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_ANY_CASE +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.NetworkDhcpMiscSafeAtoNumberTerminal import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator @@ -27,6 +27,6 @@ class ConfigParseBridgeVlanIdOptionValue : SimpleGrammarOptionValues( "config_parse_bridge_vlan_id", AlternativeCombinator( SequenceCombinator(NetworkDhcpMiscSafeAtoNumberTerminal(0, 4095), EOF()), - SequenceCombinator(BOOLEAN_FALSE_ANY_CASE, EOF()), + SequenceCombinator(BOOLEAN_FALSE, 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 index 1830758d..cbcd0a40 100644 --- 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 @@ -1,10 +1,10 @@ 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_BOOLEAN_ANY_CASE +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 /* @@ -24,8 +24,11 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram */ class ConfigParseDnsOverTlsModeOptionValue : SimpleGrammarOptionValues( "config_parse_dns_over_tls_mode", - AlternativeCombinator( - SequenceCombinator(FlexibleLiteralChoiceTerminal("opportunistic"), EOF()), - SequenceCombinator(NETWORK_DHCP_MISC_BOOLEAN_ANY_CASE, EOF()), + SequenceCombinator( + PreferValidAlternativeCombinator( + FlexibleLiteralChoiceTerminal("opportunistic"), + BOOLEAN, + ), + 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 index 190497a9..beb82af7 100644 --- 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 @@ -2,7 +2,7 @@ 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_BOOLEAN_ANY_CASE +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 @@ -27,7 +27,7 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram class ConfigParseIgnoreCarrierLossOptionValue : SimpleGrammarOptionValues( "config_parse_ignore_carrier_loss", AlternativeCombinator( - SequenceCombinator(NETWORK_DHCP_MISC_BOOLEAN_ANY_CASE, EOF()), + SequenceCombinator(BOOLEAN, EOF()), SequenceCombinator(PARSE_SEC_TIMESPAN, 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 index fec8df6a..722e0840 100644 --- 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 @@ -1,7 +1,7 @@ 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_BOOLEAN_ANY_CASE +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 @@ -19,5 +19,5 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram */ class ConfigParseIpv4llOptionValue : SimpleGrammarOptionValues( "config_parse_ipv4ll", - SequenceCombinator(NETWORK_DHCP_MISC_BOOLEAN_ANY_CASE, EOF()) + SequenceCombinator(BOOLEAN, 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 index 42ff828c..1891dd2d 100644 --- 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 @@ -1,10 +1,10 @@ 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_BOOLEAN_ANY_CASE +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 /* @@ -24,8 +24,11 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram */ class ConfigParseLldpMulticastModeOptionValue : SimpleGrammarOptionValues( "config_parse_lldp_multicast_mode", - AlternativeCombinator( - SequenceCombinator(FlexibleLiteralChoiceTerminal("nearest-bridge", "non-tpmr-bridge", "customer-bridge"), EOF()), - SequenceCombinator(NETWORK_DHCP_MISC_BOOLEAN_ANY_CASE, EOF()), + 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/ConfigParseRequiredForOnlineOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseRequiredForOnlineOptionValue.kt index a078532b..6fa9ef75 100644 --- 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 @@ -2,7 +2,7 @@ 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_BOOLEAN_ANY_CASE +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 @@ -41,6 +41,6 @@ class ConfigParseRequiredForOnlineOptionValue : SimpleGrammarOptionValues( AlternativeCombinator( SequenceCombinator(OPERSTATE, ZeroOrOne(SequenceCombinator(COLON, ZeroOrOne(OPERSTATE))), EOF()), SequenceCombinator(COLON, OPERSTATE, EOF()), - SequenceCombinator(NETWORK_DHCP_MISC_BOOLEAN_ANY_CASE, 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 index f0f59e76..5f43fc67 100644 --- 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 @@ -1,10 +1,10 @@ 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_BOOLEAN_ANY_CASE +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 /* @@ -27,8 +27,11 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram */ class ConfigParseResolveSupportOptionValue : SimpleGrammarOptionValues( "config_parse_resolve_support", - AlternativeCombinator( - SequenceCombinator(FlexibleLiteralChoiceTerminal("resolve"), EOF()), - SequenceCombinator(NETWORK_DHCP_MISC_BOOLEAN_ANY_CASE, EOF()), + SequenceCombinator( + PreferValidAlternativeCombinator( + FlexibleLiteralChoiceTerminal("resolve"), + BOOLEAN, + ), + EOF(), ) ) 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 index 8f23e061..06da79c1 100644 --- 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 @@ -27,16 +27,6 @@ import net.sjrx.intellij.plugins.systemdunitfiles.psi.UnitFileSectionGroups * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/shared/dns-domain.c#L304-L387 dns_name_concat */ -/** - * A boolean as parse_boolean() actually reads it: the twelve spellings of [BOOLEAN_SPELLINGS], compared - * with STRCASE_IN_SET, i.e. case-insensitively (`Yes`, `TRUE` and `Off` are all accepted). The shared - * [BOOLEAN] is case-sensitive and therefore flags those; this one doesn't. - */ -val NETWORK_DHCP_MISC_BOOLEAN_ANY_CASE = FlexibleLiteralChoiceTerminal(*BOOLEAN_SPELLINGS, ignoreCase = true) - -/** Only the false half of parse_boolean(), case-insensitively — see [BOOLEAN_FALSE] and [NETWORK_DHCP_MISC_BOOLEAN_ANY_CASE]. */ -val BOOLEAN_FALSE_ANY_CASE = FlexibleLiteralChoiceTerminal("0", "no", "n", "false", "f", "off", ignoreCase = true) - /** * An integer spelled the way systemd's `safe_ato*` helpers read one, range-checked in the base it is * actually written in. 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 index 073d6151..491e3fee 100644 --- 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 @@ -17,6 +17,8 @@ class ConfigParseBridgeVlanIdOptionValueTest : AbstractUnitFileTest() { PVID=0x10 PVID=no PVID=False + PVID=oFf + PVID=FALSE PVID= """.trimIndent() @@ -36,12 +38,14 @@ class ConfigParseBridgeVlanIdOptionValueTest : AbstractUnitFileTest() { 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(4, highlights) + 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 index 806f1c07..17f9289e 100644 --- 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 @@ -16,6 +16,8 @@ class ConfigParseDnsOverTlsModeOptionValueTest : AbstractUnitFileTest() { DNSOverTLS=no DNSOverTLS=TRUE DNSOverTLS=1 + DNSOverTLS=Yes + DNSOverTLS=oFf """.trimIndent() setupFileInEditor("file.network", file) @@ -33,12 +35,13 @@ class ConfigParseDnsOverTlsModeOptionValueTest : AbstractUnitFileTest() { DNSOverTLS=Opportunistic DNSOverTLS=strict DNSOverTLS=allow-downgrade + DNSOverTLS=OPPORTUNISTIC """.trimIndent() setupFileInEditor("file.network", file) enableInspection(InvalidValueInspection::class.java) val highlights = myFixture.doHighlighting() - assertSize(3, highlights) + assertSize(4, 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 index 91540fab..6f104590 100644 --- 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 @@ -14,6 +14,9 @@ class ConfigParseIgnoreCarrierLossOptionValueTest : AbstractUnitFileTest() { IgnoreCarrierLoss=true IgnoreCarrierLoss=false IgnoreCarrierLoss=No + IgnoreCarrierLoss=Yes + IgnoreCarrierLoss=TRUE + IgnoreCarrierLoss=oFf IgnoreCarrierLoss=5s IgnoreCarrierLoss=1min 30s IgnoreCarrierLoss=infinity @@ -53,12 +56,13 @@ class ConfigParseIgnoreCarrierLossOptionValueTest : AbstractUnitFileTest() { IgnoreCarrierLoss=5ns IgnoreCarrierLoss=infinity .7 IgnoreCarrierLoss=5+5 + IgnoreCarrierLoss=Infinity """.trimIndent() setupFileInEditor("file.network", file) enableInspection(InvalidValueInspection::class.java) val highlights = myFixture.doHighlighting() - assertSize(11, highlights) + assertSize(12, 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 index adb6ad53..5189d038 100644 --- 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 @@ -17,6 +17,7 @@ class ConfigParseIpv4llOptionValueTest : AbstractUnitFileTest() { IPv4LL=Yes IPv4LL=TRUE IPv4LL=Off + IPv4LL=oFf """.trimIndent() setupFileInEditor("file.network", file) @@ -33,12 +34,13 @@ class ConfigParseIpv4llOptionValueTest : AbstractUnitFileTest() { [Network] IPv4LL=ipv4 IPv4LL=both + IPv4LL=Yess """.trimIndent() setupFileInEditor("file.network", file) enableInspection(InvalidValueInspection::class.java) val highlights = myFixture.doHighlighting() - assertSize(2, highlights) + assertSize(3, 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 index 800141af..8ea15587 100644 --- 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 @@ -17,6 +17,9 @@ class ConfigParseLldpMulticastModeOptionValueTest : AbstractUnitFileTest() { EmitLLDP=yes EmitLLDP=no EmitLLDP=On + EmitLLDP=Yes + EmitLLDP=TRUE + EmitLLDP=oFf EmitLLDP= """.trimIndent() @@ -35,12 +38,13 @@ class ConfigParseLldpMulticastModeOptionValueTest : AbstractUnitFileTest() { 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(3, highlights) + 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 index 7d8bb598..09199dbb 100644 --- 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 @@ -15,6 +15,9 @@ class ConfigParseRequiredForOnlineOptionValueTest : AbstractUnitFileTest() { RequiredForOnline=no RequiredForOnline=yes RequiredForOnline=False + RequiredForOnline=Yes + RequiredForOnline=TRUE + RequiredForOnline=oFf RequiredForOnline=degraded-carrier RequiredForOnline=carrier:routable RequiredForOnline=off: @@ -41,12 +44,13 @@ class ConfigParseRequiredForOnlineOptionValueTest : AbstractUnitFileTest() { 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(5, highlights) + 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 index b8fdb90a..4a73ffd2 100644 --- 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 @@ -19,6 +19,8 @@ class ConfigParseResolveSupportOptionValueTest : AbstractUnitFileTest() { MulticastDNS=resolve MulticastDNS=true MulticastDNS=OFF + MulticastDNS=TRUE + MulticastDNS=oFf """.trimIndent() setupFileInEditor("file.network", file) @@ -34,6 +36,7 @@ class ConfigParseResolveSupportOptionValueTest : AbstractUnitFileTest() { val file = """ [Network] LLMNR=Resolve + MulticastDNS=RESOLVE LLMNR=maybe MulticastDNS=allow-downgrade MulticastDNS=yes no @@ -43,6 +46,6 @@ class ConfigParseResolveSupportOptionValueTest : AbstractUnitFileTest() { enableInspection(InvalidValueInspection::class.java) val highlights = myFixture.doHighlighting() - assertSize(4, highlights) + assertSize(5, highlights) } } From 1341cfa036638e26381ae4fdafafa243892dd9d3 Mon Sep 17 00:00:00 2001 From: Steve Ramage Date: Sat, 24 Oct 2026 11:38:18 -0700 Subject: [PATCH 6/7] refactor: use the shared SafeAtoTerminal in network-dhcp-misc validators (#563) Replace the batch's NetworkDhcpMiscSafeAtoNumberTerminal with the shared SafeAtoTerminal, written out at every call site with inclusive bounds: PVID=, [Link] Group=, SubnetId= (base 16), DUIDType=, ServerAddress= prefix, the SendOption= codes and enterprise id, and the uint8/16/32 option payloads. Drop the optional WhitespaceTerminal in front of those numbers; the terminal models C's whitespace exactly. RequestOptions= uses acceptsWhitespace = false, since each code is a whitespace-split word. IFNAME_OR_IFINDEX now asks SafeAtoTerminal.valueOf() for parse_ifindex() and spans the whole value. Verdict changes, all confirmed with systemd's parsers at 0f0682b773: - flagged: RequestOptions= 0b 1, 0o 7, 0b +1, 0b -0; a no-break space before a number after ':' or '/' - accepted: a leading \v (whole values and after ':' or '/'), 0b\v1, the strtoul wrap-around in DUIDType=, and UplinkInterface= / OutgoingInterface= 0b 1, 0o 7, \v5 Co-Authored-By: Claude Opus 5.5 (1M context) --- .../ai/ConfigParseBridgeVlanIdOptionValue.kt | 15 +- .../ConfigParseDhcp6SendOptionOptionValue.kt | 38 +++- .../ai/ConfigParseDhcpOptionTlvOptionValue.kt | 18 +- .../ConfigParseDhcpPdSubnetIdOptionValue.kt | 16 +- ...onfigParseDhcpRequestOptionsOptionValue.kt | 21 +- ...ConfigParseDhcpServerAddressOptionValue.kt | 22 +- .../ai/ConfigParseDuidTypeOptionValue.kt | 31 ++- .../ai/ConfigParseLinkGroupOptionValue.kt | 15 +- .../BatchNetworkDhcpMiscCombinators.kt | 206 ++++++------------ ...gParseDhcpRequestOptionsOptionValueTest.kt | 6 +- .../ConfigParseFdbInterfaceOptionValueTest.kt | 6 +- .../ai/NetworkDhcpMiscSafeAtoVerdictTest.kt | 95 ++++++++ 12 files changed, 316 insertions(+), 173 deletions(-) create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/NetworkDhcpMiscSafeAtoVerdictTest.kt 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 index 8b344ca6..c9df8f0a 100644 --- 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 @@ -4,7 +4,7 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.Simp 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.NetworkDhcpMiscSafeAtoNumberTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SafeAtoTerminal import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator /* @@ -26,7 +26,18 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram class ConfigParseBridgeVlanIdOptionValue : SimpleGrammarOptionValues( "config_parse_bridge_vlan_id", AlternativeCombinator( - SequenceCombinator(NetworkDhcpMiscSafeAtoNumberTerminal(0, 4095), EOF()), + 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/ConfigParseDhcp6SendOptionOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcp6SendOptionOptionValue.kt index 62d3c449..d2ffe214 100644 --- 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 @@ -6,12 +6,11 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram 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.NetworkDhcpMiscSafeAtoNumberTerminal +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.WhitespaceTerminal import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.ZeroOrMore -import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.ZeroOrOne import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.dhcpOptionTypedData +import java.math.BigInteger /* * [DHCPv6] SendOption= / SendVendorOption= (.network). @@ -29,20 +28,37 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram * linux https://man7.org/linux/man-pages/man3/inet_pton.3.html */ -private val DHCP6_OPTION_CODE = NetworkDhcpMiscSafeAtoNumberTerminal(1, 65_535L) - // 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. -private val OPTIONAL_WS = ZeroOrOne(WhitespaceTerminal()) - -private val CODE_TYPE_DATA = SequenceCombinator(OPTIONAL_WS, DHCP6_OPTION_CODE, NETWORK_DHCP_MISC_COALESCED_COLONS, dhcpOptionTypedData(), EOF()) +// 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), - OPTIONAL_WS, - NetworkDhcpMiscSafeAtoNumberTerminal(0, 4_294_967_296L), + 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, ) 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 index 7f0a4792..fa2dd445 100644 --- 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 @@ -4,10 +4,8 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.Simp 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.NetworkDhcpMiscSafeAtoNumberTerminal +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 -import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.WhitespaceTerminal import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.ZeroOrMore import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.dhcpOptionTypedData @@ -34,7 +32,8 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram * `[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 must be 1…254. The rest is handed to + * 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 @@ -47,8 +46,15 @@ class ConfigParseDhcpOptionTlvOptionValue : SimpleGrammarOptionValues( "config_parse_dhcp_option_tlv", SequenceCombinator( ZeroOrMore(COLON), - ZeroOrOne(WhitespaceTerminal()), - NetworkDhcpMiscSafeAtoNumberTerminal(1, 255), + 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/ConfigParseDhcpPdSubnetIdOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpPdSubnetIdOptionValue.kt index 11510b93..81c70072 100644 --- 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 @@ -4,8 +4,9 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.Simp 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.NetworkDhcpMiscSafeAtoNumberTerminal +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). @@ -31,6 +32,17 @@ class ConfigParseDhcpPdSubnetIdOptionValue : SimpleGrammarOptionValues( "config_parse_dhcp_pd_subnet_id", AlternativeCombinator( SequenceCombinator(FlexibleLiteralChoiceTerminal("auto"), EOF()), - SequenceCombinator(NetworkDhcpMiscSafeAtoNumberTerminal(0, Long.MAX_VALUE, hexOnly = true), 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/ConfigParseDhcpRequestOptionsOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpRequestOptionsOptionValue.kt index d98ca780..dfbccf8d 100644 --- 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 @@ -2,7 +2,7 @@ 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.NetworkDhcpMiscSafeAtoNumberTerminal +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 @@ -20,8 +20,25 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram * 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(NetworkDhcpMiscSafeAtoNumberTerminal(1, 255)), EOF()) + 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 index 48b91c07..664b198d 100644 --- 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 @@ -5,9 +5,8 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram 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.NetworkDhcpMiscSafeAtoNumberTerminal +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.WhitespaceTerminal import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.ZeroOrOne /* @@ -27,8 +26,8 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram /** * 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 `/` is - * skipped), e.g. `192.168.5.1/24`. + * (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 @@ -41,7 +40,20 @@ class ConfigParseDhcpServerAddressOptionValue : SimpleGrammarOptionValues( val o = addr.split('.').map { it.toIntOrNull() } !(o.getOrNull(0) == 127 || (o.getOrNull(0) == 169 && o.getOrNull(1) == 254)) }, - ZeroOrOne(SequenceCombinator(CIDR_SEPARATOR, ZeroOrOne(WhitespaceTerminal()), NetworkDhcpMiscSafeAtoNumberTerminal(0, 33))), + 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/ConfigParseDuidTypeOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDuidTypeOptionValue.kt index 68481482..8fe24079 100644 --- 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 @@ -8,7 +8,7 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram 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.NetworkDhcpMiscSafeAtoNumberTerminal +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 @@ -43,9 +43,34 @@ private fun duidType(): Combinator = SequenceCombinator( 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(NetworkDhcpMiscSafeAtoNumberTerminal(1, 2), 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(NetworkDhcpMiscSafeAtoNumberTerminal(0, 65_536), ZeroOrMore(COLON), EOF()), + SequenceCombinator( + SafeAtoTerminal( + signed = false, + base = 0, + flags = emptySet(), + minInclusive = 0L, + maxInclusive = 65_535L, + acceptsWhitespace = true, + acceptsMinus = true, + ), + ZeroOrMore(COLON), + 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 index 77c7885a..e235d344 100644 --- 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 @@ -2,7 +2,7 @@ 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.NetworkDhcpMiscSafeAtoNumberTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SafeAtoTerminal import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator /* @@ -20,5 +20,16 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram */ class ConfigParseLinkGroupOptionValue : SimpleGrammarOptionValues( "config_parse_link_group", - SequenceCombinator(NetworkDhcpMiscSafeAtoNumberTerminal(0, 2_147_483_648L, signed = true), EOF()) + 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/grammar/BatchNetworkDhcpMiscCombinators.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/BatchNetworkDhcpMiscCombinators.kt index 06da79c1..315ea413 100644 --- 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 @@ -4,6 +4,7 @@ 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 @@ -15,11 +16,9 @@ import net.sjrx.intellij.plugins.systemdunitfiles.psi.UnitFileSectionGroups * (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#L403-L427 mangle_base - * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L429-L478 safe_atou_full - * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L497-L520 safe_atoi - * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L522-L558 safe_atollu_full * 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 @@ -27,114 +26,6 @@ import net.sjrx.intellij.plugins.systemdunitfiles.psi.UnitFileSectionGroups * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/shared/dns-domain.c#L304-L387 dns_name_concat */ -/** - * An integer spelled the way systemd's `safe_ato*` helpers read one, range-checked in the base it is - * actually written in. - * - * With base 0 (safe_atou, safe_atou8, safe_atou16, safe_atou32, safe_atoi, safe_atoi32) the helper first - * runs mangle_base(), which understands Python-style `0b`/`0o` prefixes, and then hands the string to - * strtoul()/strtol() with base 0, which understands `0x` hex and leading-zero octal. strtoul() also takes - * an optional sign: `+5` is 5, and for the unsigned helpers `-0` is 0 while any other negative number is - * -ERANGE. Note mangle_base() runs *before* the sign is looked at, so `+0b1` is not binary (strtoul stops - * at the `b` and the helper fails) whereas `+0x1` is fine. - * - * With [hexOnly] (safe_atoux64 and friends, which pass an explicit base 16) every number is hexadecimal, - * with an optional `0x` that strtoull itself tolerates. - * - * Leading whitespace — which safe_atou_full skips — is NOT consumed here; model it in the enclosing - * grammar (an optional [WhitespaceTerminal]) wherever it can occur: after a `:` or `/` separator, since - * unit-file values themselves arrive stripped. Whitespace *after* a `0b`/`0o` prefix, and a sign there, - * is part of this terminal (strtoul sees a fresh string after mangle_base strips the prefix). - * - * [UnsignedNumberTerminal] covers the common subset (decimal/hex/octal, no sign, no `0b`/`0o`); this one - * exists because the batch needed the signed form and the extra prefixes. - */ -class NetworkDhcpMiscSafeAtoNumberTerminal( - private val minInclusive: Long, - private val maxExclusive: Long, - private val signed: Boolean = false, - private val hexOnly: Boolean = false, -) : TerminalCombinator { - - private val shape = if (hexOnly) HEX_SHAPE else BASE0_SHAPE - - private fun span(value: String, offset: Int): String? = shape.matchAt(value, offset)?.value - - /** The value [text] denotes, or null when strtoul would reject it or it overflows a Long. */ - private fun valueOf(text: String): Long? { - var s = text - var negative = false - if (s.startsWith("+") || s.startsWith("-")) { - negative = s[0] == '-' - s = s.substring(1) - } - val magnitude: Long = magnitudeOf(s, signPresent = text != s) ?: return null - if (!negative) return magnitude - if (signed) return -magnitude - // strtoul happily negates; safe_atou_full then refuses anything but -0. - return if (magnitude == 0L) 0L else null - } - - private fun magnitudeOf(s: String, signPresent: Boolean): Long? { - if (hexOnly) return (if (s.startsWith("0x") || s.startsWith("0X")) s.substring(2) else s).toLongOrNull(16) - return when { - s.length > 2 && (s.startsWith("0x") || s.startsWith("0X")) -> s.substring(2).toLongOrNull(16) - // mangle_base only sees an unsigned string, so `+0b1` / `-0o7` fail in systemd too. - s.length > 2 && (s.startsWith("0b") || s.startsWith("0B")) -> if (signPresent) null else afterPythonPrefix(s.substring(2), 2) - s.length > 2 && (s.startsWith("0o") || s.startsWith("0O")) -> if (signPresent) null else afterPythonPrefix(s.substring(2), 8) - s.length > 1 && s.startsWith("0") -> s.substring(1).toLongOrNull(8) - else -> s.toLongOrNull() - } - } - - /** - * The part after a `0b`/`0o` prefix, which mangle_base hands to strtoul() as a fresh string: strtoul - * skips leading whitespace and takes a sign again, so `0b 1`, `0b+1` and `0b-0` are all accepted - * (for the unsigned helpers a negative non-zero result is -ERANGE; for safe_atoi it is just negative). - */ - private fun afterPythonPrefix(rest: String, radix: Int): Long? { - var t = rest.trimStart(' ', '\t', '\n', '\r') - var negative = false - if (t.startsWith("+") || t.startsWith("-")) { - negative = t[0] == '-' - t = t.substring(1) - } - val magnitude = t.toLongOrNull(radix) ?: return null - if (!negative) return magnitude - return if (signed) -magnitude else if (magnitude == 0L) 0L else null - } - - // A maxExclusive of Long.MAX_VALUE means "anything that fits a signed 64-bit integer", so that - // INT64_MAX itself (the SubnetId= ceiling) stays expressible; larger spellings already fail valueOf(). - private fun inRange(text: String): Boolean = valueOf(text)?.let { - it >= minInclusive && (maxExclusive == Long.MAX_VALUE || it < maxExclusive) - } ?: false - - 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 (!inRange(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, inRange(text))))) - } - - override fun toString(): String = "SafeAto($minInclusive,$maxExclusive${if (signed) ",signed" else ""}${if (hexOnly) ",hex" else ""})" - - private companion object { - // Prefixed forms before plain digits so "0x10" isn't read as the single digit 0. - val BASE0_SHAPE = Regex("""0[bBoO][ \t\n\r]*[+-]?[0-9]+|[+-]?(?:0[xX][0-9a-fA-F]+|0[bBoO][0-9]+|[0-9]+)""") - val HEX_SHAPE = Regex("""[+-]?(?:0[xX])?[0-9a-fA-F]+""") - } -} - /** One or more `:` — extract_first_word(…, ":", 0) coalesces runs of its separator. */ val NETWORK_DHCP_MISC_COALESCED_COLONS = OneOrMore(COLON) @@ -196,8 +87,8 @@ 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. Like - * [NetworkDhcpMiscSafeAtoNumberTerminal], for leaves whose acceptance is a computation rather than a regular language. + * 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 { @@ -223,26 +114,28 @@ class CheckedTerminal(private val shape: Regex, private val name: String, privat } /** - * True iff an all-digit [text] is a positive interface index as parse_ifindex() → safe_atoi() reads it: - * base 0, so a leading `0` means octal (`010` is 8, `08` fails), and the value must be 1…INT_MAX. - */ -private fun isPositiveIfindexDigits(text: String): Boolean { - val v = if (text.length > 1 && text[0] == '0') text.substring(1).toLongOrNull(8) else text.toLongOrNull() - return v != null && v in 1..Int.MAX_VALUE.toLong() -} - -/** - * A positive interface index, or an alternative interface name (ifname_valid_full with - * IFNAME_VALID_ALTERNATIVE: 1…127 printable ASCII characters other than `:`, `/`, `%`; not `.`, `..`, - * `all`, `default`, or all digits). + * 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). * - * An all-digit spelling is therefore valid exactly when parse_ifindex() accepts it: 1…2147483647, with a - * leading `0` meaning octal (`010` is index 8; `0`, `08` and `99999999999` are refused both as an index - * and, being all digits, as a name). A spelling with any non-digit (`0x10`, `+5`, `1eth`) is a legal - * name whatever parse_ifindex makes of it, so only the name rules apply to it. + * 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+"""), "IfnameOrIfindex") { text -> - if (text.all { it in '0'..'9' }) isPositiveIfindexDigits(text) else IFNAME_ALTERNATIVE.matches(text) +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)) } // --------------------------------------------------------------------------------------------------- @@ -344,7 +237,7 @@ val IPV6_ADDR_NO_LEADING_COLON = Labeled(Role.LITERAL, tag = SemanticTag.IPV6, i * 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 (base 0, leading whitespace skipped); + * - `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 `:`. * @@ -354,13 +247,50 @@ val IPV6_ADDR_NO_LEADING_COLON = Labeled(Role.LITERAL, tag = SemanticTag.IPV6, i * 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:` may be preceded by whitespace (safe_atou_full skips it). The string length - * check (`check_length`, <= 255 bytes once unescaped) is not modelled. + * 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, ZeroOrOne(WhitespaceTerminal()), NetworkDhcpMiscSafeAtoNumberTerminal(0, 256L)), - SequenceCombinator(LiteralChoiceTerminal("uint16"), NETWORK_DHCP_MISC_COALESCED_COLONS, ZeroOrOne(WhitespaceTerminal()), NetworkDhcpMiscSafeAtoNumberTerminal(0, 65_536L)), - SequenceCombinator(LiteralChoiceTerminal("uint32"), NETWORK_DHCP_MISC_COALESCED_COLONS, ZeroOrOne(WhitespaceTerminal()), NetworkDhcpMiscSafeAtoNumberTerminal(0, 4_294_967_296L)), + 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), 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 index a65e1dd1..43659785 100644 --- 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 @@ -14,6 +14,7 @@ class ConfigParseDhcpRequestOptionsOptionValueTest : AbstractUnitFileTest() { RequestOptions=59 60 RequestOptions=17 RequestOptions=1 254 0xfe 012 + RequestOptions=0b+1 0b1 RequestOptions= [DHCPv6] RequestOptions=23 24 @@ -34,14 +35,17 @@ class ConfigParseDhcpRequestOptionsOptionValueTest : AbstractUnitFileTest() { 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(4, highlights) + assertSize(7, 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 index efe6bb01..d98cb471 100644 --- 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 @@ -15,6 +15,8 @@ class ConfigParseFdbInterfaceOptionValueTest : AbstractUnitFileTest() { OutgoingInterface=dummy98 OutgoingInterface=7 OutgoingInterface=010 + OutgoingInterface=0b 1 + OutgoingInterface=0o 7 OutgoingInterface=an-alternative-name-longer-than-fifteen OutgoingInterface= """.trimIndent() @@ -36,6 +38,8 @@ class ConfigParseFdbInterfaceOptionValueTest : AbstractUnitFileTest() { OutgoingInterface=a%d OutgoingInterface=.. OutgoingInterface=08 + OutgoingInterface=eth 0 + OutgoingInterface=0b -1 OutgoingInterface=4294967297 OutgoingInterface=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx """.trimIndent() @@ -44,6 +48,6 @@ class ConfigParseFdbInterfaceOptionValueTest : AbstractUnitFileTest() { enableInspection(InvalidValueInspection::class.java) val highlights = myFixture.doHighlighting() - assertSize(7, highlights) + assertSize(9, 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..d99985a2 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/NetworkDhcpMiscSafeAtoVerdictTest.kt @@ -0,0 +1,95 @@ +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.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") + } + + @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") + } + } +} From 4175a8b573e7e0ddb77399b3827d894c8af947bb Mon Sep 17 00:00:00 2001 From: Steve Ramage Date: Sat, 24 Oct 2026 11:38:18 -0700 Subject: [PATCH 7/7] feat: add the DHCP/RA RouteTable= validators to network-dhcp-misc (#563) Ship config_parse_dhcp_or_ra_route_table for NETWORK_CONFIG_SOURCE_DHCP4, DHCP6 and NDISC ([DHCPv4], [DHCPv6] and [IPv6AcceptRA] RouteTable=) with the shared ROUTE_TABLE_FROM_STRING grammar. The parser adds nothing to manager_get_route_table_from_string(): no empty special case and no extra bound. Possible networkd.conf names stay accepted. sd-check agrees on all three keys. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../semanticdata/optionvalues/AiGenerated.kt | 3 + ...onfigParseDhcpOrRaRouteTableOptionValue.kt | 36 +++++++++ ...gParseDhcpOrRaRouteTableOptionValueTest.kt | 77 +++++++++++++++++++ .../ai/NetworkDhcpMiscSafeAtoVerdictTest.kt | 2 + 4 files changed, 118 insertions(+) create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDhcpOrRaRouteTableOptionValue.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDhcpOrRaRouteTableOptionValueTest.kt 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 c6019fa8..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 @@ -450,6 +450,9 @@ fun getAllAIGeneratedValidators(): Map { 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, ) 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/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/NetworkDhcpMiscSafeAtoVerdictTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/NetworkDhcpMiscSafeAtoVerdictTest.kt index d99985a2..72e9b038 100644 --- 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 @@ -3,6 +3,7 @@ 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 @@ -50,6 +51,7 @@ class NetworkDhcpMiscSafeAtoVerdictTest { 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