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..8cafbacf 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/AiGenerated.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/AiGenerated.kt @@ -413,6 +413,47 @@ fun getAllAIGeneratedValidators(): Map { Validator("config_parse_socket_service", "0") to ConfigParseSocketServiceOptionValue() as OptionValueInformation, Validator("config_parse_service_sockets", "0") to ConfigParseServiceSocketsOptionValue() as OptionValueInformation, + // Batch 2026-10: netdev-link + // systemd-networkd .netdev (src/network/netdev/) and udev .link (src/udev/net/) settings, pinned to + // systemd 0f0682b773. Every key using each (parser, ltype) pair was checked in the gperf files; where + // a parser branches on the key name (vxlan_address) the grammar is the union and says so. + Validator("config_parse_badadv_bandwidth", "0") to ConfigParseBadadvBandwidthOptionValue() as OptionValueInformation, + Validator("config_parse_vxlan_address", "0") to ConfigParseVxlanAddressOptionValue() as OptionValueInformation, + Validator("config_parse_wireguard_peer_key_file", "0") to ConfigParseWireguardPeerKeyFileOptionValue() as OptionValueInformation, + Validator("config_parse_vlanprotocol", "0") to ConfigParseVlanprotocolOptionValue() as OptionValueInformation, + Validator("config_parse_vlan_qos_maps", "0") to ConfigParseVlanQosMapsOptionValue() as OptionValueInformation, + Validator("config_parse_macsec_key_id", "0") to ConfigParseMacsecKeyIdOptionValue() as OptionValueInformation, + Validator("config_parse_macsec_key_file", "0") to ConfigParseMacsecKeyFileOptionValue() as OptionValueInformation, + Validator("config_parse_cpu_set", "0") to ConfigParseCpuSetOptionValue() as OptionValueInformation, + Validator("config_parse_wol_password", "0") to ConfigParseWolPasswordOptionValue() as OptionValueInformation, + Validator("config_parse_wireguard_route_table", "0") to ConfigParseWireguardRouteTableOptionValue() as OptionValueInformation, + Validator("config_parse_wireguard_private_key_file", "0") to ConfigParseWireguardPrivateKeyFileOptionValue() as OptionValueInformation, + Validator("config_parse_wireguard_private_key", "0") to ConfigParseWireguardPrivateKeyOptionValue() as OptionValueInformation, + Validator("config_parse_wireguard_peer_route_table", "0") to ConfigParseWireguardPeerRouteTableOptionValue() as OptionValueInformation, + Validator("config_parse_wireguard_endpoint", "0") to ConfigParseWireguardEndpointOptionValue() as OptionValueInformation, + Validator("config_parse_wireguard_allowed_ips", "0") to ConfigParseWireguardAllowedIpsOptionValue() as OptionValueInformation, + Validator("config_parse_vrf_table", "0") to ConfigParseVrfTableOptionValue() as OptionValueInformation, + Validator("config_parse_udev_property", "0") to ConfigParseUdevPropertyOptionValue() as OptionValueInformation, + Validator("config_parse_rps_cpu_mask", "0") to ConfigParseRpsCpuMaskOptionValue() as OptionValueInformation, + Validator("config_parse_port_range", "0") to ConfigParsePortRangeOptionValue() as OptionValueInformation, + Validator("config_parse_port", "0") to ConfigParsePortOptionValue() as OptionValueInformation, + Validator("config_parse_mac_address_policy", "0") to ConfigParseMacAddressPolicyOptionValue() as OptionValueInformation, + Validator("config_parse_l2tp_tunnel_remote_address", "0") to ConfigParseL2tpTunnelRemoteAddressOptionValue() as OptionValueInformation, + Validator("config_parse_l2tp_tunnel_local_address", "0") to ConfigParseL2tpTunnelLocalAddressOptionValue() as OptionValueInformation, + Validator("config_parse_l2tp_session_name", "0") to ConfigParseL2tpSessionNameOptionValue() as OptionValueInformation, + Validator("config_parse_irq_affinity_policy", "0") to ConfigParseIrqAffinityPolicyOptionValue() as OptionValueInformation, + Validator("config_parse_irq_affinity_numa", "0") to ConfigParseIrqAffinityNumaOptionValue() as OptionValueInformation, + Validator("config_parse_ipv6_flowlabel", "0") to ConfigParseIpv6FlowlabelOptionValue() as OptionValueInformation, + Validator("config_parse_ipoib_pkey", "0") to ConfigParseIpoibPkeyOptionValue() as OptionValueInformation, + Validator("config_parse_ifnames", "IFNAME_VALID_ALTERNATIVE") to ConfigParseIfnamesOptionValue() as OptionValueInformation, + Validator("config_parse_ifalias", "0") to ConfigParseIfaliasOptionValue() as OptionValueInformation, + Validator("config_parse_geneve_address", "0") to ConfigParseGeneveAddressOptionValue() as OptionValueInformation, + Validator("config_parse_default_port_vlanid", "0") to ConfigParseDefaultPortVlanidOptionValue() as OptionValueInformation, + Validator("config_parse_advertise", "0") to ConfigParseAdvertiseOptionValue() as OptionValueInformation, + // RxCoalesceCQENSec= registers config_parse_coalesce_sec with ltype `true` (parse_nsec instead of + // parse_sec); the `0` entry above (ConfigParseCoalesceSecOptionValue) covers the parse_sec keys. + Validator("config_parse_coalesce_sec", "true") to ConfigParseCoalesceNsecOptionValue() as OptionValueInformation, + ) return allValidators diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseAdvertiseOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseAdvertiseOptionValue.kt new file mode 100644 index 00000000..d5d929ee --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseAdvertiseOptionValue.kt @@ -0,0 +1,75 @@ +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 +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.WhitespaceTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.ZeroOrMore + +/* + * [Link] Advertise= and [EnergyEfficientEthernet] LinkMode= in a .link file. + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.link.html#Advertise= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/shared/ethtool-util.c#L1125-L1177 config_parse_advertise (0) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/shared/ethtool-util.c#L162-L168 ethtool_link_mode_bit_table (DEFINE_STRING_TABLE_LOOKUP) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/shared/ethtool-link-mode.py (generates the table from enum ethtool_link_mode_bit_indices) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/include/uapi/linux/ethtool.h#L1966-L2109 enum ethtool_link_mode_bit_indices + * linux https://man7.org/linux/man-pages/man8/ethtool.8.html + */ + +/** + * The link-mode names ethtool-link-mode.py derives from the vendored kernel header: each + * `ETHTOOL_LINK_MODE__BIT` lower-cased with `_` turned into `-`, and `autoneg` renamed + * `autonegotiation`. 125 names at 0f0682b773. + */ +private val LINK_MODE = FlexibleLiteralChoiceTerminal( + "10baset-half", "10baset-full", "100baset-half", "100baset-full", "1000baset-half", + "1000baset-full", "autonegotiation", "tp", "aui", "mii", "fibre", "bnc", "10000baset-full", + "pause", "asym-pause", "2500basex-full", "backplane", "1000basekx-full", "10000basekx4-full", + "10000basekr-full", "10000baser-fec", "20000basemld2-full", "20000basekr2-full", + "40000basekr4-full", "40000basecr4-full", "40000basesr4-full", "40000baselr4-full", + "56000basekr4-full", "56000basecr4-full", "56000basesr4-full", "56000baselr4-full", + "25000basecr-full", "25000basekr-full", "25000basesr-full", "50000basecr2-full", + "50000basekr2-full", "100000basekr4-full", "100000basesr4-full", "100000basecr4-full", + "100000baselr4-er4-full", "50000basesr2-full", "1000basex-full", "10000basecr-full", + "10000basesr-full", "10000baselr-full", "10000baselrm-full", "10000baseer-full", + "2500baset-full", "5000baset-full", "fec-none", "fec-rs", "fec-baser", "50000basekr-full", + "50000basesr-full", "50000basecr-full", "50000baselr-er-fr-full", "50000basedr-full", + "100000basekr2-full", "100000basesr2-full", "100000basecr2-full", "100000baselr2-er2-fr2-full", + "100000basedr2-full", "200000basekr4-full", "200000basesr4-full", "200000baselr4-er4-fr4-full", + "200000basedr4-full", "200000basecr4-full", "100baset1-full", "1000baset1-full", + "400000basekr8-full", "400000basesr8-full", "400000baselr8-er8-fr8-full", "400000basedr8-full", + "400000basecr8-full", "fec-llrs", "100000basekr-full", "100000basesr-full", + "100000baselr-er-fr-full", "100000basecr-full", "100000basedr-full", "200000basekr2-full", + "200000basesr2-full", "200000baselr2-er2-fr2-full", "200000basedr2-full", "200000basecr2-full", + "400000basekr4-full", "400000basesr4-full", "400000baselr4-er4-fr4-full", "400000basedr4-full", + "400000basecr4-full", "100basefx-half", "100basefx-full", "10baset1l-full", "800000basecr8-full", + "800000basekr8-full", "800000basedr8-full", "800000basedr8-2-full", "800000basesr8-full", + "800000basevr8-full", "10baset1s-full", "10baset1s-half", "10baset1s-p2mp-half", + "10baset1brr-full", "200000basecr-full", "200000basekr-full", "200000basedr-full", + "200000basedr-2-full", "200000basesr-full", "200000basevr-full", "400000basecr2-full", + "400000basekr2-full", "400000basedr2-full", "400000basedr2-2-full", "400000basesr2-full", + "400000basevr2-full", "800000basecr4-full", "800000basekr4-full", "800000basedr4-full", + "800000basedr4-2-full", "800000basesr4-full", "800000basevr4-full", "1600000basecr8-full", + "1600000basekr8-full", "1600000basedr8-full", "1600000basedr8-2-full" +) + +/** + * Validator for `[Link] Advertise=` and `[EnergyEfficientEthernet] LinkMode=` (.link). + * + * C function: config_parse_advertise. A whitespace-separated list (extract_first_word, no unquoting) of + * ethtool link-mode names, each looked up exactly (case-sensitive) in ethtool_link_mode_bit_table — + * speed/duplex modes like `1000baset-full` as well as the non-speed bits (`autonegotiation`, `pause`, + * `asym-pause`, `tp`, `fibre`, `fec-rs`, …). + * + * The table is generated at build time from the kernel's enum ethtool_link_mode_bit_indices in + * systemd's vendored copy of linux/ethtool.h (src/include/uapi, which meson puts first on the include + * path). That is wider than the ethtool-link-mode.xml table in the plugin's generated man data, which + * lists 102 names; this follows the header the C is built against. An unknown name is logged and + * skipped by udev; an empty assignment clears the list. + */ +class ConfigParseAdvertiseOptionValue : SimpleGrammarOptionValues( + "config_parse_advertise", + SequenceCombinator(LINK_MODE, ZeroOrMore(SequenceCombinator(WhitespaceTerminal(), LINK_MODE)), EOF()) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseBadadvBandwidthOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseBadadvBandwidthOptionValue.kt new file mode 100644 index 00000000..4d6c601d --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseBadadvBandwidthOptionValue.kt @@ -0,0 +1,52 @@ +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.NetdevLinkCheckedSpan +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.netdevLinkParseSizeSi +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.siSize +import java.math.BigInteger + +/* + * [BatmanAdvanced] GatewayBandwidthDown= / GatewayBandwidthUp= in a .netdev file. + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.netdev.html#GatewayBandwidthDown= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/netdev/batadv.c#L53-L89 config_parse_badadv_bandwidth (0) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/network/netdev/batadv.c#L81-L84 (the 100 kbit uint32_t range warning) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L157-L283 parse_size (base 1000) + * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-parse-util.c#L178 TEST(parse_size_si) + * tests https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-batadv.netdev#L14-L15 + */ + +/** + * Validator for `[BatmanAdvanced] GatewayBandwidthDown=` and `GatewayBandwidthUp=` (.netdev), plus the + * misspelled `GatewayBandwithDown=` / `GatewayBandwithUp=` that systemd still accepts as aliases. + * + * C function: config_parse_badadv_bandwidth, which hands the whole value to parse_size(rvalue, 1000, …): + * a number with an optional case-sensitive SI suffix (`K`, `M`, `G`, `T`, `P`, `E`, `B`), a + * fraction allowed, and several elements with strictly descending suffixes (`1G 500M`) summed. See + * [siSize]. + * + * The total is stored in units of 100 kbit in a uint32_t, so a total of 4294967296 × 100000 + * (429496729600000, e.g. `12P` or `3E 2P`) or more is flagged. systemd logs that case ("is outside of + * 0...429496729500000 range") and then stores the total truncated to 32 bits, which is never what was + * meant, so it is treated as invalid here. Totals that overflow parse_size itself (`20E`) are refused + * outright. Confirmed against systemd's parser built at 0f0682b773: `429496729599999` is silent, + * `429496729600000` warns. Any other invalid value is logged and ignored by systemd. + * + * There is no isempty() branch, so an empty `GatewayBandwidthDown=` or `GatewayBandwidthUp=` is a parse error in systemd (logged and ignored), not + * a reset. The plugin never runs a grammar on an empty value, so it is not flagged here. + */ +class ConfigParseBadadvBandwidthOptionValue : SimpleGrammarOptionValues( + "config_parse_badadv_bandwidth", + SequenceCombinator( + NetdevLinkCheckedSpan(siSize(), "total < 429496729600000") { text -> + netdevLinkParseSizeSi(text)?.let { it < BATADV_BANDWIDTH_LIMIT } == true + }, + EOF() + ) +) + +/** `k/1000/100 > UINT32_MAX` (batadv.c#L81) first holds at this total. */ +private val BATADV_BANDWIDTH_LIMIT: BigInteger = BigInteger.valueOf(4_294_967_296L) * BigInteger.valueOf(100_000L) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseCoalesceNsecOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseCoalesceNsecOptionValue.kt new file mode 100644 index 00000000..5eaf1d1d --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseCoalesceNsecOptionValue.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.EOF +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.NSEC_DURATION_FINITE +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator + +/* + * [Link] RxCoalesceCQENSec= in a .link file. + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.link.html#RxCoalesceCQENSec= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/shared/ethtool-util.c#L1370-L1415 config_parse_coalesce_sec (ltype "true", i.e. nsec) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/time-util.c#L1374-L1466 parse_nsec + * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-time-util.c#L141 TEST(parse_nsec) + */ + +/** + * Validator for `[Link] RxCoalesceCQENSec=` (.link) — the one key that registers config_parse_coalesce_sec + * with ltype `true`, which switches the parser from parse_sec() to parse_nsec(). + * + * parse_nsec reads one or more `number[unit]` elements, a bare number meaning nanoseconds (`500`, + * `1us`, `2.5`, `1s500ms`, `.7`). See [NSEC_DURATION_FINITE]. + * + * `infinity` parses, but config_parse_coalesce_sec then refuses any result above UINT32_MAX ("Too large + * … value"), and NSEC_INFINITY is UINT64_MAX — so `infinity` is never a usable value here and is + * rejected. The same UINT32_MAX ceiling (about 4.29 seconds in nanoseconds) applies to every finite value + * too, but that compares the computed sum and is not modelled. An invalid value is logged and ignored. + */ +class ConfigParseCoalesceNsecOptionValue : SimpleGrammarOptionValues( + "config_parse_coalesce_sec", + SequenceCombinator(NSEC_DURATION_FINITE, EOF()) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseCpuSetOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseCpuSetOptionValue.kt new file mode 100644 index 00000000..1e2210b0 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseCpuSetOptionValue.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.CPU_SET +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.EOF +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator + +/* + * [Link] IRQAffinity= (.link) and [Exec] CPUAffinity= (.nspawn). + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.link.html#IRQAffinity= + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.nspawn.html#CPUAffinity= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/shared/cpu-set-util.c#L220-L307 config_parse_cpu_set (0) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L305-L340 parse_range + * 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/shared/cpu-set-util.c#L186-L206 cpu_set_add_range (CPU_SET_MAX_NCPU) + * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-cpu-set-util.c#L36 TEST(parse_cpu_set) + * linux https://man7.org/linux/man-pages/man2/sched_setaffinity.2.html + */ + +/** + * Validator for `[Link] IRQAffinity=` (.link) and `[Exec] CPUAffinity=` (.nspawn). + * + * C function: config_parse_cpu_set. CPU indices and `N-M` ranges, separated by any mix of whitespace + * and commas (separators coalesce; leading/trailing ones are fine), each word optionally quoted. Each + * bound is a safe_atou() number (so hex works) below CPU_SET_MAX_NCPU = 8192. See [CPU_SET]. + * + * Lenient on one point: a reversed range such as `3-0` is rejected by systemd after parsing (it + * compares the numbers); that is not modelled. An empty assignment clears the set. + */ +class ConfigParseCpuSetOptionValue : SimpleGrammarOptionValues( + "config_parse_cpu_set", + SequenceCombinator(CPU_SET, EOF()) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDefaultPortVlanidOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDefaultPortVlanidOptionValue.kt new file mode 100644 index 00000000..4d75d13e --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDefaultPortVlanidOptionValue.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.EOF +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.FlexibleLiteralChoiceTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SafeAtoTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator + +/* + * [Bridge] DefaultPVID= in a .netdev file. + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.netdev.html#DefaultPVID= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/shared/vlan-util.c#L44-L67 config_parse_default_port_vlanid (0) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/shared/vlan-util.c#L8-L23 parse_vlanid (safe_atou16, vlanid_is_valid) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/shared/vlan-util.h#L7-L13 VLANID_MAX (4094) + * tests https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-bridge.netdev#L13 DefaultPVID=9 + */ + +/** + * Validator for `[Bridge] DefaultPVID=` (.netdev). + * + * C function: config_parse_default_port_vlanid. Either `none` (no PVID) or a VLAN id read by + * parse_vlanid (safe_atou16, so hex, octal and `0b`/`0o` work) and checked by vlanid_is_valid, i.e. 0…4094. + * + * The man page says "an integer in the range 1…4094", but vlanid_is_valid only checks `id <= 4094`, so + * `0` is accepted (and stored as 0, the same as `none`). This follows the C. An invalid value is logged + * and ignored by systemd. + * + * There is no isempty() branch, so an empty `DefaultPVID=` is a parse error in systemd (logged and ignored), not + * a reset. The plugin never runs a grammar on an empty value, so it is not flagged here. + */ +class ConfigParseDefaultPortVlanidOptionValue : SimpleGrammarOptionValues( + "config_parse_default_port_vlanid", + SequenceCombinator( + AlternativeCombinator( + FlexibleLiteralChoiceTerminal("none"), + SafeAtoTerminal( + signed = false, + base = 0, + flags = emptySet(), + minInclusive = 0L, + maxInclusive = 4094L, + acceptsWhitespace = true, + acceptsMinus = true, + ), + ), + EOF() + ) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseGeneveAddressOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseGeneveAddressOptionValue.kt new file mode 100644 index 00000000..941564c0 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseGeneveAddressOptionValue.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.IP_ADDR +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.NetdevLinkCheckedSpan +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.netdevLinkIsMulticast + +/* + * [GENEVE] Remote= in a .netdev file. + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.netdev.html#Remote= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/netdev/geneve.c#L124-L163 config_parse_geneve_address (0) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/in-addr-util.c#L531-L551 in_addr_from_string_auto + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/in-addr-util.c#L103-L113 in_addr_is_multicast + * linux https://man7.org/linux/man-pages/man3/inet_pton.3.html + */ + +/** + * Validator for `[GENEVE] Remote=` (.netdev). + * + * C function: config_parse_geneve_address: an IPv4 or IPv6 literal (in_addr_from_string_auto → + * inet_pton) that is not a multicast address (224.0.0.0/4, ff00::/8). An invalid value is logged and + * ignored by systemd. + */ +class ConfigParseGeneveAddressOptionValue : SimpleGrammarOptionValues( + "config_parse_geneve_address", + SequenceCombinator(NetdevLinkCheckedSpan(IP_ADDR, "not multicast") { !netdevLinkIsMulticast(it) }, EOF()) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIfaliasOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIfaliasOptionValue.kt new file mode 100644 index 00000000..b4b05b86 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIfaliasOptionValue.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.RegexTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator + +/* + * [Link] Alias= in a .link file. + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.link.html#Alias= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/udev/net/link-config.c#L1944-L1980 config_parse_ifalias (0) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/include/uapi/linux/if.h#L33 IFALIASZ (256) + */ + +/** + * Validator for `[Link] Alias=` (.link). + * + * C function: config_parse_ifalias. The value is free text, but it must be ASCII (ascii_is_valid) and + * shorter than IFALIASZ = 256 bytes, so at most 255 characters. Anything containing a non-ASCII + * character (`Café uplink`) or longer than that is logged and ignored by udev. An empty assignment + * clears the alias. + */ +class ConfigParseIfaliasOptionValue : SimpleGrammarOptionValues( + "config_parse_ifalias", + SequenceCombinator(RegexTerminal("[\\s\\S]+", "[\\x00-\\x7F]{1,255}"), EOF()) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIfnamesOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIfnamesOptionValue.kt new file mode 100644 index 00000000..a3f6805b --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIfnamesOptionValue.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.ALTERNATIVE_INTERFACE_NAME +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.WhitespaceTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.ZeroOrMore + +/* + * [Link] AlternativeName= (.link) and [HSR] Ports= (.netdev). + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.link.html#AlternativeName= + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.netdev.html#Ports= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/shared/conf-parser.c#L1651-L1702 config_parse_ifnames (IFNAME_VALID_ALTERNATIVE) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/socket-util.c#L785-L830 ifname_valid_full + * tests https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-hsr.netdev#L7 Ports=test1 dummy98 + * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-socket-util.c#L29 TEST(ifname_valid) + */ + +/** + * Validator for `[Link] AlternativeName=` (.link) and `[HSR] Ports=` (.netdev). + * + * C function: config_parse_ifnames with ltype IFNAME_VALID_ALTERNATIVE: a whitespace-separated list + * (extract_first_word, no unquoting) of interface names, each checked by ifname_valid_full with + * IFNAME_VALID_ALTERNATIVE — up to 127 printable ASCII characters other than `:`, `/` and `%`, not + * entirely digits, and not `.`, `..`, `all` or `default`. See [ALTERNATIVE_INTERFACE_NAME]. An + * invalid name is logged and skipped; an empty assignment clears the list. + */ +class ConfigParseIfnamesOptionValue : SimpleGrammarOptionValues( + "config_parse_ifnames", + SequenceCombinator( + ALTERNATIVE_INTERFACE_NAME, + ZeroOrMore(SequenceCombinator(WhitespaceTerminal(), ALTERNATIVE_INTERFACE_NAME)), + EOF() + ) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIpoibPkeyOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIpoibPkeyOptionValue.kt new file mode 100644 index 00000000..b75098ef --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIpoibPkeyOptionValue.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.EOF +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SafeAtoTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator + +/* + * [IPoIB] PartitionKey= in a .netdev file. + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.netdev.html#PartitionKey= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/netdev/ipoib.c#L100-L140 config_parse_ipoib_pkey (0) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L599-L613 safe_atou16_full (base 0) + */ + +/** + * Validator for `[IPoIB] PartitionKey=` (.netdev). + * + * C function: config_parse_ipoib_pkey. A uint16 read with safe_atou16() (so `0x8001` works, which is + * how partition keys are usually written) in 1…0xffff, except 0x8000 (the full-membership bit with an + * empty key). Spelled as two ranges either side of 0x8000. An empty assignment unsets it; an invalid + * value is logged and ignored by systemd. + */ +class ConfigParseIpoibPkeyOptionValue : SimpleGrammarOptionValues( + "config_parse_ipoib_pkey", + SequenceCombinator( + AlternativeCombinator( + SafeAtoTerminal( + signed = false, + base = 0, + flags = emptySet(), + minInclusive = 1L, + maxInclusive = 0x7fffL, + acceptsWhitespace = true, + acceptsMinus = true, + ), + SafeAtoTerminal( + signed = false, + base = 0, + flags = emptySet(), + minInclusive = 0x8001L, + maxInclusive = 0xffffL, + acceptsWhitespace = true, + acceptsMinus = true, + ), + ), + EOF() + ) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIpv6FlowlabelOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIpv6FlowlabelOptionValue.kt new file mode 100644 index 00000000..2b7c51d3 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIpv6FlowlabelOptionValue.kt @@ -0,0 +1,43 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.AlternativeCombinator +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.EOF +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.FlexibleLiteralChoiceTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SafeAtoTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator + +/* + * [Tunnel] IPv6FlowLabel= in a .netdev file. + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.netdev.html#IPv6FlowLabel= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/netdev/tunnel.c#L907-L942 config_parse_ipv6_flowlabel (0) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/shared/conf-parser.c#L2151-L2182 config_parse_unsigned_bounded → safe_atou_bounded + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L429-L478 safe_atou_full (base 0) + */ + +/** + * Validator for `[Tunnel] IPv6FlowLabel=` (.netdev). + * + * C function: config_parse_ipv6_flowlabel. Either `inherit` (copy the inner packet's flow label) or a + * 20-bit flow label 0…0xFFFFF read by config_parse_uint32_bounded (safe_atou, so hex works, as the man + * page's own range notation suggests). An out-of-range value is logged and ignored by systemd. + */ +class ConfigParseIpv6FlowlabelOptionValue : SimpleGrammarOptionValues( + "config_parse_ipv6_flowlabel", + SequenceCombinator( + AlternativeCombinator( + FlexibleLiteralChoiceTerminal("inherit"), + SafeAtoTerminal( + signed = false, + base = 0, + flags = emptySet(), + minInclusive = 0L, + maxInclusive = 0xfffffL, + acceptsWhitespace = true, + acceptsMinus = true, + ), + ), + EOF() + ) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIrqAffinityNumaOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIrqAffinityNumaOptionValue.kt new file mode 100644 index 00000000..ac5afd41 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIrqAffinityNumaOptionValue.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.AlternativeCombinator +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.EOF +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.FlexibleLiteralChoiceTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SafeAtoTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator + +/* + * [Link] IRQAffinityNUMA= in a .link file. + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.link.html#IRQAffinityNUMA= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/udev/net/link-config.c#L2157-L2201 config_parse_irq_affinity_numa (0) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/udev/net/link-config.h#L35-L36 IRQ_AFFINITY_NUMA_UNSET / IRQ_AFFINITY_NUMA_LOCAL + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L429-L478 safe_atou_full (base 0) + */ + +/** + * Validator for `[Link] IRQAffinityNUMA=` (.link). + * + * C function: config_parse_irq_affinity_numa. Either `local` (the device's own NUMA node) or a NUMA + * node number read with safe_atou() (so hex works). UINT_MAX and UINT_MAX - 1 are reserved as the + * "unset" and "local" markers, so the number must be at most 4294967293. An empty assignment unsets it. + * An out-of-range number is logged and ignored by udev. + */ +class ConfigParseIrqAffinityNumaOptionValue : SimpleGrammarOptionValues( + "config_parse_irq_affinity_numa", + SequenceCombinator( + AlternativeCombinator( + FlexibleLiteralChoiceTerminal("local"), + SafeAtoTerminal( + signed = false, + base = 0, + flags = emptySet(), + minInclusive = 0L, + maxInclusive = 4_294_967_293L, + acceptsWhitespace = true, + acceptsMinus = true, + ), + ), + EOF() + ) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIrqAffinityPolicyOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIrqAffinityPolicyOptionValue.kt new file mode 100644 index 00000000..726caac6 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseIrqAffinityPolicyOptionValue.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.FlexibleLiteralChoiceTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator + +/* + * [Link] IRQAffinityPolicy= in a .link file. + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.link.html#IRQAffinityPolicy= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/udev/net/link-config.c#L2209-L2213 config_parse_irq_affinity_policy (0), DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/udev/net/link-config.c#L2203-L2206 irq_affinity_policy_table + */ + +/** + * Validator for `[Link] IRQAffinityPolicy=` (.link). + * + * C function: config_parse_irq_affinity_policy, generated by DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT over + * irq_affinity_policy_table: exactly `single` or `spread` (case-sensitive). An empty assignment unsets + * it. An invalid value is logged and ignored by udev. + */ +class ConfigParseIrqAffinityPolicyOptionValue : SimpleGrammarOptionValues( + "config_parse_irq_affinity_policy", + SequenceCombinator(FlexibleLiteralChoiceTerminal("single", "spread"), EOF()) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseL2tpSessionNameOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseL2tpSessionNameOptionValue.kt new file mode 100644 index 00000000..c063fc5f --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseL2tpSessionNameOptionValue.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.INTERFACE_NAME +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator + +/* + * [L2TPSession] Name= in a .netdev file. + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.netdev.html#Name= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/netdev/l2tp-tunnel.c#L727-L765 config_parse_l2tp_session_name (0) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/socket-util.c#L785-L830 ifname_valid_full (flags 0, via ifname_valid) + * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-socket-util.c#L29 TEST(ifname_valid) + */ + +/** + * Validator for `[L2TPSession] Name=` (.netdev). + * + * C function: config_parse_l2tp_session_name, which requires ifname_valid(): 1…15 printable ASCII + * characters other than `:`, `/` and `%`, not entirely digits, and not `.`, `..`, `all` or + * `default`. See [INTERFACE_NAME]. An invalid value is logged and ignored by systemd. + * + * There is no isempty() branch, so an empty `Name=` is a parse error in systemd (logged and ignored), not + * a reset. The plugin never runs a grammar on an empty value, so it is not flagged here. + */ +class ConfigParseL2tpSessionNameOptionValue : SimpleGrammarOptionValues( + "config_parse_l2tp_session_name", + SequenceCombinator(INTERFACE_NAME, EOF()) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseL2tpTunnelLocalAddressOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseL2tpTunnelLocalAddressOptionValue.kt new file mode 100644 index 00000000..94ce597e --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseL2tpTunnelLocalAddressOptionValue.kt @@ -0,0 +1,51 @@ +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.ALTERNATIVE_INTERFACE_NAME +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.IPV4_ADDR +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.IPV6_ADDR +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.RegexTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.ZeroOrOne + +/* + * [L2TP] Local= in a .netdev file. + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.netdev.html#Local= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/netdev/l2tp-tunnel.c#L477-L569 config_parse_l2tp_tunnel_local_address (0) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/network/netdev/l2tp-tunnel.c#L38-L42 l2tp_local_address_type_table + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/extract-word.c#L10-L188 extract_first_word ("@", no flags; leading-separator skip at #L39-L56) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/socket-util.c#L785-L830 ifname_valid_full (IFNAME_VALID_ALTERNATIVE) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/in-addr-util.c#L531-L551 in_addr_from_string_auto + */ + +/** + * Validator for `[L2TP] Local=` (.netdev). + * + * C function: config_parse_l2tp_tunnel_local_address. `ADDRESS[@INTERFACE]`, where ADDRESS is one of + * the l2tp_local_address_type_table names `auto`, `static`, `dynamic` or an IPv4/IPv6 literal, and the + * optional interface (everything after the first `@`) must pass ifname_valid_full with + * IFNAME_VALID_ALTERNATIVE (up to 127 characters). + * + * The `@` is cut with extract_first_word(…, "@", 0), which coalesces a run of separators, so `auto@@eth0` + * and even a trailing `auto@` (no interface) are accepted; it also skips leading separators, so `@auto` + * is `auto` (a lone `@` is an empty word and refused). Not modelled: systemd refuses the null + * address and an address family that conflicts with `Remote=`. An invalid value is logged and ignored. + */ +class ConfigParseL2tpTunnelLocalAddressOptionValue : SimpleGrammarOptionValues( + "config_parse_l2tp_tunnel_local_address", + SequenceCombinator( + // extract_first_word() skips leading separators, so `@auto` reads as `auto`. + ZeroOrOne(RegexTerminal("@+", "@+")), + AlternativeCombinator( + IPV6_ADDR, + IPV4_ADDR, + FlexibleLiteralChoiceTerminal("auto", "static", "dynamic"), + ), + ZeroOrOne(SequenceCombinator(RegexTerminal("@+", "@+"), ZeroOrOne(ALTERNATIVE_INTERFACE_NAME))), + EOF() + ) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseL2tpTunnelRemoteAddressOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseL2tpTunnelRemoteAddressOptionValue.kt new file mode 100644 index 00000000..97aa2f17 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseL2tpTunnelRemoteAddressOptionValue.kt @@ -0,0 +1,28 @@ +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.IP_ADDR +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator + +/* + * [L2TP] Remote= in a .netdev file. + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.netdev.html#Remote= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/netdev/l2tp-tunnel.c#L571-L623 config_parse_l2tp_tunnel_remote_address (0) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/in-addr-util.c#L531-L551 in_addr_from_string_auto + * linux https://man7.org/linux/man-pages/man3/inet_pton.3.html + */ + +/** + * Validator for `[L2TP] Remote=` (.netdev). + * + * C function: config_parse_l2tp_tunnel_remote_address: an IPv4 or IPv6 literal (in_addr_from_string_auto + * → inet_pton). Not modelled: systemd also refuses the null address (`0.0.0.0`, `::`) and an address + * whose family differs from an earlier `Local=`; both depend on the parsed value or on other keys. An + * invalid value is logged and ignored by systemd. + */ +class ConfigParseL2tpTunnelRemoteAddressOptionValue : SimpleGrammarOptionValues( + "config_parse_l2tp_tunnel_remote_address", + SequenceCombinator(IP_ADDR, EOF()) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseMacAddressPolicyOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseMacAddressPolicyOptionValue.kt new file mode 100644 index 00000000..d96abc78 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseMacAddressPolicyOptionValue.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.FlexibleLiteralChoiceTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator + +/* + * [Link] MACAddressPolicy= in a .link file. + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.link.html#MACAddressPolicy= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/udev/net/link-config.c#L2145-L2149 config_parse_mac_address_policy (0), DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/udev/net/link-config.c#L2138-L2142 mac_address_policy_table + */ + +/** + * Validator for `[Link] MACAddressPolicy=` (.link). + * + * C function: config_parse_mac_address_policy, generated by DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT over + * mac_address_policy_table: exactly one of `persistent`, `random` or `none` (case-sensitive). An empty + * assignment selects the default, `none`. An invalid value is logged and ignored by udev. + */ +class ConfigParseMacAddressPolicyOptionValue : SimpleGrammarOptionValues( + "config_parse_mac_address_policy", + SequenceCombinator(FlexibleLiteralChoiceTerminal("persistent", "random", "none"), EOF()) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseMacsecKeyFileOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseMacsecKeyFileOptionValue.kt new file mode 100644 index 00000000..9d999f8c --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseMacsecKeyFileOptionValue.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.NON_API_VFS_ABSOLUTE_PATH +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator + +/* + * [MACsecTransmitAssociation] / [MACsecReceiveAssociation] KeyFile= in a .netdev file. + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.netdev.html#KeyFile= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/netdev/macsec.c#L770-L821 config_parse_macsec_key_file (0) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/shared/parse-helpers.c#L34-L88 path_simplify_and_warn (PATH_CHECK_ABSOLUTE|PATH_CHECK_NON_API_VFS) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/mountpoint-util.c#L721-L726 path_below_api_vfs + */ + +/** + * Validator for `KeyFile=` in both `[MACsecTransmitAssociation]` and `[MACsecReceiveAssociation]` (.netdev). + * + * C function: config_parse_macsec_key_file. The value is strdup'd verbatim — no specifier expansion and no unquoting, so + * `%` and quotes are ordinary characters and a path may contain spaces — and checked with + * path_simplify_and_warn(PATH_CHECK_ABSOLUTE|PATH_CHECK_NON_API_VFS): it must be absolute, must not keep + * a `..` component after simplification, and must not be at or below `/dev`, `/sys` or `/proc`. + * See [NON_API_VFS_ABSOLUTE_PATH]. An empty assignment clears the setting. An invalid value is logged + * and ignored by systemd. + */ +class ConfigParseMacsecKeyFileOptionValue : SimpleGrammarOptionValues( + "config_parse_macsec_key_file", + SequenceCombinator(NON_API_VFS_ABSOLUTE_PATH, EOF()) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseMacsecKeyIdOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseMacsecKeyIdOptionValue.kt new file mode 100644 index 00000000..8b0af0d6 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseMacsecKeyIdOptionValue.kt @@ -0,0 +1,44 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.EOF +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.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.WhitespaceTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.ZeroOrOne + +/* + * [MACsecTransmitAssociation] / [MACsecReceiveAssociation] KeyId= in a .netdev file. + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.netdev.html#KeyId= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/netdev/macsec.c#L823-L879 config_parse_macsec_key_id (0) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/hexdecoct.c#L109-L158 unhexmem_full + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/include/uapi/linux/if_macsec.h#L23 MACSEC_KEYID_LEN (16) + * tests https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-macsec.netdev#L12 KeyId=01 … KeyId=0203040506070809 + * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-hexdecoct.c#L110 TEST(unhexmem) + */ + +// One byte: two hex digits. unhex_next() skips whitespace around every digit, so a space may even +// split a byte. +private val KEY_ID_BYTE = RegexTerminal("[0-9a-fA-F][ \\t]*[0-9a-fA-F]", "[0-9a-fA-F][ \\t]*[0-9a-fA-F]") + +/** + * Validator for `KeyId=` in `[MACsecTransmitAssociation]` and `[MACsecReceiveAssociation]` (.netdev). + * + * C function: config_parse_macsec_key_id, which decodes the value with unhexmem(): an even number of + * hex digits (case-insensitive, whitespace between digits ignored) encoding at most MACSEC_KEYID_LEN = + * 16 bytes, i.e. 2 to 32 digits. Shorter ids are zero-padded. + * + * The man page still says "Takes a number between 0-255", but the parser is hex-only: `KeyId=1` (odd + * length) and `KeyId=255` are refused, while systemd's own fixtures write `KeyId=01` and + * `KeyId=0203040506070809`. This follows the C. An invalid value is logged and ignored by systemd. + */ +class ConfigParseMacsecKeyIdOptionValue : SimpleGrammarOptionValues( + "config_parse_macsec_key_id", + SequenceCombinator( + KEY_ID_BYTE, + Repeat(SequenceCombinator(ZeroOrOne(WhitespaceTerminal()), KEY_ID_BYTE), 0, 15), + EOF() + ) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParsePortOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParsePortOptionValue.kt new file mode 100644 index 00000000..9c1bf25f --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParsePortOptionValue.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.FlexibleLiteralChoiceTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator + +/* + * [Link] Port= in a .link file. + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.link.html#Port= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/shared/ethtool-util.c#L84 config_parse_port (0), DEFINE_CONFIG_PARSE_ENUM + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/shared/ethtool-util.c#L75-L81 port_table + */ + +/** + * Validator for `[Link] Port=` (.link). + * + * C function: config_parse_port, generated by DEFINE_CONFIG_PARSE_ENUM over port_table: exactly one of + * `tp`, `aui`, `mii`, `fibre` or `bnc` (case-sensitive string-table lookup). An invalid value is + * logged and ignored by udev. + */ +class ConfigParsePortOptionValue : SimpleGrammarOptionValues( + "config_parse_port", + SequenceCombinator(FlexibleLiteralChoiceTerminal("tp", "aui", "mii", "fibre", "bnc"), EOF()) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParsePortRangeOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParsePortRangeOptionValue.kt new file mode 100644 index 00000000..ec880400 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParsePortRangeOptionValue.kt @@ -0,0 +1,61 @@ +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.HYPHEN +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 + +/* + * [VXLAN] PortRange= in a .netdev file. + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.netdev.html#PortRange= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/netdev/vxlan.c#L299-L324 config_parse_port_range (0) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L736-L760 parse_ip_port_range (allow_zero=false) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L305-L340 parse_range + */ + +/** + * Validator for `[VXLAN] PortRange=` (.netdev). + * + * C function: config_parse_port_range → parse_ip_port_range(…, allow_zero = false): a single port or a + * `low-high` range, each bound a safe_atou() number (hex, octal and `0b`/`0o` work) in 1…65535. + * parse_range() cuts the whole value at the first `-`, so the lower bound never sees a minus (`-0` is an empty + * word) and the upper bound is the rest. Whitespace inside the value reaches safe_atou(), which skips it before + * the number, so `1000- 2000`, `0b 1-5` and `1-0b 1` are legal (but `1000 -2000` is not: the lower word keeps + * its trailing space). + * A range with `high < low` is refused by systemd after parsing; that compares numbers and is not + * modelled. An invalid value is logged and ignored by systemd. There is no isempty() branch, so `PortRange=` + * is a parse error in systemd rather than a reset; the plugin never runs a grammar on an empty value, so it + * is not flagged here. + */ +class ConfigParsePortRangeOptionValue : SimpleGrammarOptionValues( + "config_parse_port_range", + SequenceCombinator( + SafeAtoTerminal( + signed = false, + base = 0, + flags = emptySet(), + minInclusive = 1L, + maxInclusive = 65_535L, + acceptsWhitespace = true, + acceptsMinus = false, + ), + ZeroOrOne( + SequenceCombinator( + HYPHEN, + SafeAtoTerminal( + signed = false, + base = 0, + flags = emptySet(), + minInclusive = 1L, + maxInclusive = 65_535L, + acceptsWhitespace = true, + acceptsMinus = true, + ), + ), + ), + EOF(), + ) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseRpsCpuMaskOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseRpsCpuMaskOptionValue.kt new file mode 100644 index 00000000..ad9921de --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseRpsCpuMaskOptionValue.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.CPU_SET +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] ReceivePacketSteeringCPUMask= in a .link file. + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.link.html#ReceivePacketSteeringCPUMask= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/udev/net/link-config.c#L2095-L2136 config_parse_rps_cpu_mask (0) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/shared/cpu-set-util.c#L220-L307 config_parse_cpu_set + * tests https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-rps-cpu-multi.link#L6-L7 ReceivePacketSteeringCPUMask=0 1 / =2,3 + * tests https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/24-rps-cpu-invalid.link#L6 ReceivePacketSteeringCPUMask=0 3 8-invalid (deliberately invalid) + */ + +/** + * Validator for `[Link] ReceivePacketSteeringCPUMask=` (.link). + * + * C function: config_parse_rps_cpu_mask. The whole value `disable` (an empty mask) or `all` (every + * online CPU), compared with streq, or else a CPU set exactly as config_parse_cpu_set reads it — see + * [CPU_SET]. An empty assignment clears the mask. An invalid word is logged and skipped by systemd. + */ +class ConfigParseRpsCpuMaskOptionValue : SimpleGrammarOptionValues( + "config_parse_rps_cpu_mask", + AlternativeCombinator( + SequenceCombinator(FlexibleLiteralChoiceTerminal("disable", "all"), EOF()), + SequenceCombinator(CPU_SET, EOF()), + ) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUdevPropertyOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUdevPropertyOptionValue.kt new file mode 100644 index 00000000..81994454 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUdevPropertyOptionValue.kt @@ -0,0 +1,115 @@ +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.ExtractedWordTerminal +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 +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.WhitespaceTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.WordEnd +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.ZeroOrMore +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.ZeroOrOne + +/* + * [Link] Property= in a .link file. + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.link.html#Property= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/udev/net/link-config.c#L1810-L1878 config_parse_udev_property (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/shared/specifier.c#L33-L105 specifier_printf (link_specifier_table) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/env-util.c#L81-L102 env_assignment_is_valid (via udev_property_assignment_is_valid) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/libsystemd/sd-device/device-util.c#L219-L237 device_property_can_set + * tests https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/85-unmanaged.link#L18 Property=ID_NET_MANAGED_BY=io.systemd.Network + */ + +// The properties udev reserves for itself (device_property_can_set), refused as assignment targets, plus +// the kernel's SYNTH_ARG_ prefix. The trailing lookahead makes each reserved word match only as the whole +// name, so `DEVPATHX` or `TAGS_EXTRA` are still fine. +private const val RESERVED_PROPERTY = + """(?!(?:ACTION|SEQNUM|SYNTH_UUID|DEVPATH|DEVPATH_OLD|SUBSYSTEM|DEVTYPE|DRIVER|MODALIAS|DEVNAME|DEVMODE|""" + + """DEVUID|DEVGID|MAJOR|MINOR|DISKSEQ|PARTN|IFINDEX|INTERFACE|INTERFACE_OLD|DEVLINKS|TAGS|CURRENT_TAGS|""" + + """USEC_INITIALIZED|UDEV_DATABASE_VERSION)(?![A-Za-z0-9_%]))(?!SYNTH_ARG_)""" + +// A link_specifier_table specifier (COMMON_SYSTEM_SPECIFIERS + COMMON_TMP_SPECIFIERS). `%%` is left out on +// purpose: it expands to a literal `%`, which no property name may contain. +private const val SPECIFIER = """%[aAbBHlqmMovwWTV]""" + +// The property name: env_name_is_valid — bash-style, i.e. letters, digits and `_`, not starting with a +// digit. Specifiers are expanded before the check, so one may appear in the name too. +private val PROPERTY_NAME = RegexTerminal( + """[A-Za-z0-9_%]+""", + """$RESERVED_PROPERTY(?:[A-Za-z_]|$SPECIFIER)(?:[A-Za-z0-9_]|$SPECIFIER)*""", +) + +private val EQUALS = LiteralChoiceTerminal("=") + +// The value, in its raw (still-quoted, still-escaped) form. env_value_is_valid only requires valid UTF-8, +// so any character is fine; whitespace ends the word unless it is quoted or backslash-escaped. +private val RAW_VALUE = RegexTerminal( + """(?:[^\s"'\\]|\\[\s\S]|"(?:[^"\\]|\\[\s\S])*"|'(?:[^'\\]|\\[\s\S])*')+""", + """(?:[^\s"'\\]|\\[\s\S]|"(?:[^"\\]|\\[\s\S])*"|'(?:[^'\\]|\\[\s\S])*')+""", +) +private val DOUBLE_QUOTED_INNER = RegexTerminal("""(?:[^"\\]|\\[\s\S])+""", """(?:[^"\\]|\\[\s\S])+""") +private val SINGLE_QUOTED_INNER = RegexTerminal("""(?:[^'\\]|\\[\s\S])+""", """(?:[^'\\]|\\[\s\S])+""") + +private fun quotedAssignment(quote: String, inner: RegexTerminal) = SequenceCombinator( + LiteralChoiceTerminal(quote), PROPERTY_NAME, EQUALS, ZeroOrOne(inner), LiteralChoiceTerminal(quote), ZeroOrOne(RAW_VALUE), +) + +// The name check of PROPERTY_NAME, applied to an already unquoted and unescaped word. +private val UNQUOTED_PROPERTY_NAME = Regex("""$RESERVED_PROPERTY(?:[A-Za-z_]|$SPECIFIER)(?:[A-Za-z0-9_]|$SPECIFIER)*""") + +private fun assignmentNameIsValid(word: String): Boolean { + val eq = word.indexOf('=') + return eq > 0 && UNQUOTED_PROPERTY_NAME.matches(word.substring(0, eq)) +} + +// One word: NAME=VALUE, either plain (the value may still contain quoted runs) or with the whole +// assignment quoted from the start of the name. Anything else extract_first_word could turn into a valid +// assignment — a quote or C escape inside the name (`F"OO"=bar`, `"FOO"=bar`, `FOO\x41=1`) — falls +// through to the ExtractedWordTerminal, which unquotes and unescapes the word first and then applies the +// same name check. WordEnd stops the classic engine committing to a structured branch that only matched +// a prefix of the word. +private val PROPERTY_ASSIGNMENT = AlternativeCombinator( + SequenceCombinator( + AlternativeCombinator( + quotedAssignment("\"", DOUBLE_QUOTED_INNER), + quotedAssignment("'", SINGLE_QUOTED_INNER), + SequenceCombinator(PROPERTY_NAME, EQUALS, ZeroOrOne(RAW_VALUE)), + ), + WordEnd("""\s"""), + ), + ExtractedWordTerminal("""\s""", cunescape = true, "udev property assignment", ::assignmentNameIsValid), +) + +/** + * Validator for `[Link] Property=` (.link). + * + * C function: config_parse_udev_property. A whitespace-separated list of `NAME=VALUE` udev property + * assignments. Each word is split with extract_first_word(…, EXTRACT_CUNESCAPE|EXTRACT_UNQUOTE), so it may + * be quoted (`"FOO=bar baz"` or `FOO="bar baz"`) or contain C escapes; specifiers from the link specifier + * table (`%H`, `%v`, …) are expanded; then the assignment must pass env_assignment_is_valid — a + * bash-style name (letters, digits, `_`, not starting with a digit), `=`, and any UTF-8 value, which may be + * empty — and the name must not be one udev reserves (device_property_can_set: `ACTION`, `DEVPATH`, + * `INTERFACE`, `TAGS`, …, or anything starting `SYNTH_ARG_`). + * + * Quotes and C escapes may appear anywhere in the word, including inside the name (`F"OO"=bar`, + * `"FOO"=bar`, `FOO\x41=1` are all `FOO…=…` after extract_first_word); such words are unquoted and + * unescaped before the name is checked. + * + * Lenient corners: in the common `NAME=VALUE` spelling the escapes inside the value are not checked + * against cunescape_one's list (an unknown escape such as `\q` makes systemd ignore the whole + * assignment), the value's UTF-8 validity is not checked, and an unknown `%` specifier in the value is + * not flagged. An invalid word is logged and + * skipped by udev; an empty assignment clears the list. + */ +class ConfigParseUdevPropertyOptionValue : SimpleGrammarOptionValues( + "config_parse_udev_property", + SequenceCombinator( + PROPERTY_ASSIGNMENT, + ZeroOrMore(SequenceCombinator(WhitespaceTerminal(), PROPERTY_ASSIGNMENT)), + EOF() + ) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseVlanQosMapsOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseVlanQosMapsOptionValue.kt new file mode 100644 index 00000000..66fccd7e --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseVlanQosMapsOptionValue.kt @@ -0,0 +1,103 @@ +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.ExtractedWordTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.HYPHEN +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.LiteralChoiceTerminal +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.WordEnd +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.netdevLinkParseRangeAccepts +import java.math.BigInteger + +/* + * [VLAN] EgressQOSMaps= / IngressQOSMaps= in a .netdev file. + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.netdev.html#EgressQOSMaps= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/netdev/vlan.c#L120-L180 config_parse_vlan_qos_maps (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/parse-util.c#L305-L340 parse_range + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L429-L478 safe_atou_full (base 0) + */ + +// parse_range() bounds as safe_atou() reads them (any unsigned int). The word was split on whitespace, so no +// whitespace reaches safe_atou(). The lower bound is cut at the first `-`, so it never sees a minus; the upper +// bound is the rest of the word, which may (`1--0`). +private val QOS_LOWER = SafeAtoTerminal( + signed = false, + base = 0, + flags = emptySet(), + minInclusive = BigInteger.ZERO, + maxInclusive = SafeAtoTerminal.UINT32_MAX, + acceptsWhitespace = false, + acceptsMinus = false, +) +private val QOS_UPPER = SafeAtoTerminal( + signed = false, + base = 0, + flags = emptySet(), + minInclusive = BigInteger.ZERO, + maxInclusive = SafeAtoTerminal.UINT32_MAX, + acceptsWhitespace = false, + acceptsMinus = true, +) +private val QOS_RANGE = SequenceCombinator(QOS_LOWER, ZeroOrOne(SequenceCombinator(HYPHEN, QOS_UPPER))) + +// The same bounds for a word whose quotes and escapes have been removed: quoted or escaped whitespace does reach +// safe_atou(). +private val QOS_UNQUOTED_LOWER = SafeAtoTerminal( + signed = false, + base = 0, + flags = emptySet(), + minInclusive = BigInteger.ZERO, + maxInclusive = SafeAtoTerminal.UINT32_MAX, + acceptsWhitespace = true, + acceptsMinus = false, +) +private val QOS_UNQUOTED_UPPER = SafeAtoTerminal( + signed = false, + base = 0, + flags = emptySet(), + minInclusive = BigInteger.ZERO, + maxInclusive = SafeAtoTerminal.UINT32_MAX, + acceptsWhitespace = true, + acceptsMinus = true, +) + +// One word: the structured `from[-to]` grammar, bare or quoted as a whole, for highlighting; then an +// [ExtractedWordTerminal] fallback for quotes and C escapes anywhere in the word. +private val QOS_MAPPING = AlternativeCombinator( + SequenceCombinator( + AlternativeCombinator( + SequenceCombinator(LiteralChoiceTerminal("\""), QOS_RANGE, LiteralChoiceTerminal("\"")), + SequenceCombinator(LiteralChoiceTerminal("'"), QOS_RANGE, LiteralChoiceTerminal("'")), + QOS_RANGE, + ), + WordEnd("""\s"""), + ), + ExtractedWordTerminal("""\s""", cunescape = true, "range 0..4294967295") { word -> + netdevLinkParseRangeAccepts(word, QOS_UNQUOTED_LOWER, QOS_UNQUOTED_UPPER) + }, +) + +/** + * Validator for `[VLAN] EgressQOSMaps=` and `IngressQOSMaps=` (.netdev). + * + * C function: config_parse_vlan_qos_maps. A whitespace-separated list of words (each may be quoted, and + * may contain C escapes — the quotes and escapes are removed before parsing, see [ExtractedWordTerminal]); + * every word goes to parse_range(), so it is `from-to` or a single number meaning `from == to`, each + * side an unsigned int as safe_atou() reads it ([SafeAtoTerminal]: decimal, `0x` hex, octal, `0b`/`0o`). + * + * The man page says each integer is in 1…4294967294 and that `from` must be >= `to`; the parser + * enforces neither (it stores whatever parse_range returns), so this follows the C and accepts the full + * unsigned range and any order. A malformed word is logged and skipped by systemd. + */ +class ConfigParseVlanQosMapsOptionValue : SimpleGrammarOptionValues( + "config_parse_vlan_qos_maps", + SequenceCombinator(QOS_MAPPING, ZeroOrMore(SequenceCombinator(WhitespaceTerminal(), QOS_MAPPING)), EOF()) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseVlanprotocolOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseVlanprotocolOptionValue.kt new file mode 100644 index 00000000..1eafd4bc --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseVlanprotocolOptionValue.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.FlexibleLiteralChoiceTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator + +/* + * [VLAN] Protocol= and [Bridge] VLANProtocol= in a .netdev file. + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.netdev.html#Protocol= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/shared/conf-parser.c#L1832-L1862 config_parse_vlanprotocol (0) + * tests https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-bridge.netdev#L17 + */ + +/** + * Validator for `[VLAN] Protocol=` and `[Bridge] VLANProtocol=` (.netdev). + * + * C function: config_parse_vlanprotocol, which compares the value with STR_IN_SET against exactly four + * spellings: `802.1q` / `802.1Q` (ETH_P_8021Q) and `802.1ad` / `802.1AD` (ETH_P_8021AD). Mixed case + * such as `802.1Ad` is refused. An empty assignment resets to the kernel default. + */ +class ConfigParseVlanprotocolOptionValue : SimpleGrammarOptionValues( + "config_parse_vlanprotocol", + SequenceCombinator(FlexibleLiteralChoiceTerminal("802.1q", "802.1Q", "802.1ad", "802.1AD"), EOF()) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseVrfTableOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseVrfTableOptionValue.kt new file mode 100644 index 00000000..fbb9ea6a --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseVrfTableOptionValue.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.ROUTE_TABLE_FROM_STRING + +/* + * [VRF] Table= in a .netdev file. + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.netdev.html#Table= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/netdev/vrf.c#L10-L39 config_parse_vrf_table (0) + * 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#L427-L431 route_table_table (default, main, local) + * 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) + */ + +/** + * Validator for `[VRF] Table=` (.netdev). + * + * C function: config_parse_vrf_table → manager_get_route_table_from_string: `default`, `main`, + * `local`, a name defined with `RouteTable=` in networkd.conf, or a non-zero uint32 (safe_atou32, so hex, + * octal and `0b`/`0o` work; `0` is refused with ERANGE). See [ROUTE_TABLE_FROM_STRING]. + * + * Because table names are user-defined (networkd.conf can even define `my\ table:100`), any value without + * `:` that is not all digits is accepted as a potential name. What is flagged: `0`, out-of-range or + * malformed all-digit numbers (`00`, `08`), and anything containing `:` (`name:number` is networkd.conf + * syntax, not a table reference). An invalid value is logged and ignored by systemd. + * + * There is no isempty() branch, so an empty `Table=` is a parse error in systemd (logged and ignored), not + * a reset. The plugin never runs a grammar on an empty value, so it is not flagged here. + */ +class ConfigParseVrfTableOptionValue : SimpleGrammarOptionValues( + "config_parse_vrf_table", + ROUTE_TABLE_FROM_STRING +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseVxlanAddressOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseVxlanAddressOptionValue.kt new file mode 100644 index 00000000..f70ff004 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseVxlanAddressOptionValue.kt @@ -0,0 +1,74 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import com.intellij.codeInspection.ProblemsHolder +import net.sjrx.intellij.plugins.systemdunitfiles.psi.UnitFilePropertyType +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.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.GrammarOptionValue +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.IPV4_ADDR +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.IPV6_ADDR +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.NetdevLinkCheckedSpan +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.netdevLinkIsMulticast + +/* + * [VXLAN] Group= / Local= / Remote= in a .netdev file. + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.netdev.html#Local= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/netdev/vxlan.c#L220-L297 config_parse_vxlan_address (0) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/network/netdev/netdev-util.c#L9-L16 netdev_local_address_type_table + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/in-addr-util.c#L531-L551 in_addr_from_string_auto + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/in-addr-util.c#L103-L113 in_addr_is_multicast + * linux https://man7.org/linux/man-pages/man3/inet_pton.3.html + */ + +private const val VXLAN_VALIDATOR = "config_parse_vxlan_address" + +private val VXLAN_LOCAL_TYPE = FlexibleLiteralChoiceTerminal("ipv4_link_local", "ipv6_link_local", "dhcp4", "dhcp6", "slaac", "dhcp_pd") + +/** An address literal that is (or, with [multicast] false, is not) a multicast address. */ +private fun vxlanAddress(multicast: Boolean): Combinator = AlternativeCombinator( + NetdevLinkCheckedSpan(IPV6_ADDR, if (multicast) "multicast" else "not multicast") { netdevLinkIsMulticast(it) == multicast }, + NetdevLinkCheckedSpan(IPV4_ADDR, if (multicast) "multicast" else "not multicast") { netdevLinkIsMulticast(it) == multicast }, +) + +/** `Group=`: a multicast address. */ +private val VXLAN_GROUP = GrammarOptionValue(VXLAN_VALIDATOR, SequenceCombinator(vxlanAddress(multicast = true), EOF())) + +/** `Remote=`: a unicast address. */ +private val VXLAN_REMOTE = GrammarOptionValue(VXLAN_VALIDATOR, SequenceCombinator(vxlanAddress(multicast = false), EOF())) + +/** `Local=`: a local-address type name, or a unicast address. Addresses first, so the name terminal cannot take a prefix. */ +private val VXLAN_LOCAL = GrammarOptionValue(VXLAN_VALIDATOR, SequenceCombinator(AlternativeCombinator(vxlanAddress(multicast = false), VXLAN_LOCAL_TYPE), EOF())) + +/** + * Validator for `[VXLAN] Group=`, `Local=` and `Remote=` (.netdev). + * + * C function: config_parse_vxlan_address, which branches on the key name. The value is an IPv4 or IPv6 + * literal (in_addr_from_string_auto → inet_pton). For `Local=` only, one of the + * netdev_local_address_type_table names (`ipv4_link_local`, `ipv6_link_local`, `dhcp4`, `dhcp6`, `slaac`, + * `dhcp_pd`) is accepted first, telling networkd to pick up the address that mechanism assigned. `Group=` + * must be a multicast address; `Local=` and `Remote=` must not be. + * + * Validators are looked up by (parser, ltype), not by key, so the key-specific rules are applied by + * choosing a per-key grammar in [generateProblemDescriptors]. [combinator] (used for colouring and + * completion) stays the union of the three. All of this was confirmed against systemd's parser built at + * 0f0682b773: `Group=dhcp4`, `Remote=slaac`, `Group=192.168.1.1`, `Remote=239.1.1.1` and + * `Local=239.1.1.1` are each logged and ignored. + */ +class ConfigParseVxlanAddressOptionValue : SimpleGrammarOptionValues( + VXLAN_VALIDATOR, + SequenceCombinator(AlternativeCombinator(IPV6_ADDR, IPV4_ADDR, VXLAN_LOCAL_TYPE), EOF()) +) { + override fun generateProblemDescriptors(property: UnitFilePropertyType, holder: ProblemsHolder) { + when (property.key) { + "Group" -> VXLAN_GROUP.generateProblemDescriptors(property, holder) + "Remote" -> VXLAN_REMOTE.generateProblemDescriptors(property, holder) + "Local" -> VXLAN_LOCAL.generateProblemDescriptors(property, holder) + else -> super.generateProblemDescriptors(property, holder) + } + } +} diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseWireguardAllowedIpsOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseWireguardAllowedIpsOptionValue.kt new file mode 100644 index 00000000..eb33a27d --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseWireguardAllowedIpsOptionValue.kt @@ -0,0 +1,44 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.EOF +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.IP_ADDR_AND_SAFE_PREFIX +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.RegexTerminal +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 + +/* + * [WireGuardPeer] AllowedIPs= in a .netdev file. + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.netdev.html#AllowedIPs= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/netdev/wireguard.c#L673-L749 config_parse_wireguard_allowed_ips (0) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/extract-word.c#L10-L188 extract_first_word ("," WHITESPACE, no flags) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/in-addr-util.c#L845-L897 in_addr_prefix_from_string_auto_full + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/in-addr-util.c#L779-L797 in_addr_parse_prefixlen (safe_atou8) + * tests https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-wireguard.netdev#L15 AllowedIPs=fd31:bf08:57cb::/48,192.168.26.3/24 + */ + +// One extract_first_word() separator run; "," and whitespace both separate and runs coalesce. +private val ALLOWED_IPS_SEPARATOR = RegexTerminal("[\\s,]+", "[\\s,]+") + +/** + * Validator for `[WireGuardPeer] AllowedIPs=` (.netdev). + * + * C function: config_parse_wireguard_allowed_ips. A list of IPv4/IPv6 addresses with optional prefix + * lengths, separated by commas and/or whitespace. Each entry goes to in_addr_prefix_from_string_auto: a + * missing length means the full width, `/0` is allowed, and the length is a safe_atou8 number, so `/0x18` + * works (see [IP_ADDR_AND_SAFE_PREFIX]). Separators coalesce, and leading or trailing ones are harmless. + * An address that is not properly masked (`192.168.26.3/24`) is accepted and masked with a warning. + * An invalid entry is logged and skipped by systemd; an empty assignment clears the list. + */ +class ConfigParseWireguardAllowedIpsOptionValue : SimpleGrammarOptionValues( + "config_parse_wireguard_allowed_ips", + SequenceCombinator( + ZeroOrOne(ALLOWED_IPS_SEPARATOR), + IP_ADDR_AND_SAFE_PREFIX, + ZeroOrMore(SequenceCombinator(ALLOWED_IPS_SEPARATOR, IP_ADDR_AND_SAFE_PREFIX)), + ZeroOrOne(ALLOWED_IPS_SEPARATOR), + EOF() + ) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseWireguardEndpointOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseWireguardEndpointOptionValue.kt new file mode 100644 index 00000000..2526f660 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseWireguardEndpointOptionValue.kt @@ -0,0 +1,81 @@ +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.COLON +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.CREDENTIAL_REFERENCE +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.IPV6_ADDR +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.SafeAtoTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.ZeroOrOne + +/* + * [WireGuardPeer] Endpoint= in a .netdev file. + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.netdev.html#Endpoint= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/netdev/wireguard.c#L751-L862 config_parse_wireguard_endpoint (0) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/shared/socket-netlink.c#L220-L354 in_addr_port_ifindex_name_from_string_auto (no ifindex, no server name) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L718-L734 parse_ip_port + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/shared/dns-domain.c#L24-L150 dns_label_unescape (via dns_name_is_valid) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/shared/creds-util.c#L156-L180 read_credential (credential_name_valid) + * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-socket-netlink.c#L356 TEST(in_addr_port_ifindex_name_from_string_auto) + * tests https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-wireguard.netdev#L17 Endpoint=@network.wireguard.peer0.endpoint + */ + +// parse_ip_port: safe_atou16_full(…, SAFE_ATO_REFUSE_LEADING_WHITESPACE) (base 0), then 0 is refused. The port is +// everything after the last `:`, so whitespace can reach the parser: `\v80` and `0b 1` are accepted, ` 80` is not. +private val ENDPOINT_PORT = SafeAtoTerminal( + signed = false, + base = 0, + flags = setOf(SafeAtoTerminal.Flag.REFUSE_LEADING_WHITESPACE), + minInclusive = 1L, + maxInclusive = 65_535L, + acceptsWhitespace = true, + acceptsMinus = true, +) + +// The hostname fallback: everything up to the LAST colon (strrchr), checked with dns_name_is_valid(): +// dot-separated labels of at most 63 characters, each character printable (>= ' ', not DEL) or one of +// the escapes `\.`, `\\` or `\DDD` with DDD at most 255 (dns_label_unescape refuses `k > 255`). A single trailing dot is allowed, and dns_name_is_valid() also +// accepts the root name — `.` or nothing at all — so `:51820` passes the parser too. +private const val DNS_LABEL = """(?:[^.\\\x00-\x1F\x7F]|\\[.\\]|\\(?:[01][0-9]{2}|2[0-4][0-9]|25[0-5])){1,63}""" +private val ENDPOINT_HOST = RegexTerminal( + """[^\x00-\x1F\x7F]+(?=:[^:]*$)""", + """(?:$DNS_LABEL(?:\.$DNS_LABEL)*\.?|\.)(?=:[^:]*$)""", +) + +/** + * Validator for `[WireGuardPeer] Endpoint=` (.netdev). + * + * C function: config_parse_wireguard_endpoint. In the order systemd tries them: + * + * - `@name` reads the endpoint from a credential; + * - in_addr_port_ifindex_name_from_string_auto with no interface and no server name: `[IPv6]:port`, + * `IPv4:port`, or — because that helper makes the port optional — a bare IPv4 or IPv6 address (the + * man page says a port is required; the parser stores port 0); + * - otherwise `host:port`, split at the last colon, where the host only has to pass + * dns_name_is_valid(), which is permissive (any printable characters, labels up to 63 bytes). + * + * The port is a parse_ip_port() number, 1…65535 (safe_atou16 base 0, so hex, octal and `0b`/`0o` work, and + * a leading space is refused while a leading `\v` is not). Because the host + * fallback is so permissive, something like `[fe80::1%eth0]:51820` is accepted (as a hostname), as it + * is by systemd at parse time. What is flagged: a missing or invalid port, empty labels, over-long labels + * and control characters. Lenient: the overall name length (DNS_HOSTNAME_MAX, enforced by + * dns_name_concat) and the 63-byte label limit, which is counted here in characters, not UTF-8 bytes. An + * invalid value is logged and ignored by systemd. + */ +class ConfigParseWireguardEndpointOptionValue : SimpleGrammarOptionValues( + "config_parse_wireguard_endpoint", + AlternativeCombinator( + SequenceCombinator(CREDENTIAL_REFERENCE, EOF()), + SequenceCombinator(LiteralChoiceTerminal("["), IPV6_ADDR, LiteralChoiceTerminal("]"), COLON, ENDPOINT_PORT, EOF()), + SequenceCombinator(IPV4_ADDR, COLON, ENDPOINT_PORT, EOF()), + SequenceCombinator(IPV6_ADDR, EOF()), + SequenceCombinator(IPV4_ADDR, EOF()), + SequenceCombinator(ZeroOrOne(ENDPOINT_HOST), COLON, ENDPOINT_PORT, EOF()), + ) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseWireguardPeerKeyFileOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseWireguardPeerKeyFileOptionValue.kt new file mode 100644 index 00000000..daf299e9 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseWireguardPeerKeyFileOptionValue.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.NON_API_VFS_ABSOLUTE_PATH +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator + +/* + * [WireGuardPeer] PublicKeyFile= / PresharedKeyFile= in a .netdev file. + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.netdev.html#PublicKeyFile= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/netdev/wireguard.c#L623-L671 config_parse_wireguard_peer_key_file (0) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/shared/parse-helpers.c#L34-L88 path_simplify_and_warn (PATH_CHECK_ABSOLUTE|PATH_CHECK_NON_API_VFS) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/mountpoint-util.c#L721-L726 path_below_api_vfs + */ + +/** + * Validator for `[WireGuardPeer] PublicKeyFile=` and `PresharedKeyFile=` (.netdev). + * + * C function: config_parse_wireguard_peer_key_file. The value is strdup'd verbatim — no specifier expansion and no unquoting, so + * `%` and quotes are ordinary characters and a path may contain spaces — and checked with + * path_simplify_and_warn(PATH_CHECK_ABSOLUTE|PATH_CHECK_NON_API_VFS): it must be absolute, must not keep + * a `..` component after simplification, and must not be at or below `/dev`, `/sys` or `/proc`. + * See [NON_API_VFS_ABSOLUTE_PATH]. An empty assignment clears the setting. An invalid value is logged + * and ignored by systemd. + */ +class ConfigParseWireguardPeerKeyFileOptionValue : SimpleGrammarOptionValues( + "config_parse_wireguard_peer_key_file", + SequenceCombinator(NON_API_VFS_ABSOLUTE_PATH, EOF()) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseWireguardPeerRouteTableOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseWireguardPeerRouteTableOptionValue.kt new file mode 100644 index 00000000..14ab1c47 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseWireguardPeerRouteTableOptionValue.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.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.ROUTE_TABLE_FROM_STRING +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator + +/* + * [WireGuardPeer] RouteTable= in a .netdev file. + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.netdev.html#RouteTable= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/netdev/wireguard.c#L941-L990 config_parse_wireguard_peer_route_table (0) + * 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#L427-L431 route_table_table (default, main, local) + * 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) + * tests https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-wireguard.netdev#L10 + */ + +/** + * Validator for `[WireGuardPeer] RouteTable=` (.netdev). + * + * C function: config_parse_wireguard_peer_route_table. A value that parse_boolean() reads as false (`no`, `off`, `0`, …) + * disables route configuration; anything else is a route table for manager_get_route_table_from_string: + * `default`, `main`, `local`, a name defined with `RouteTable=` in networkd.conf, or a non-zero uint32 + * (safe_atou32, so hex, octal and `0b`/`0o` work). See [ROUTE_TABLE_FROM_STRING]. An empty assignment falls back to the `[WireGuard]` section's table. + * + * Because table names are user-defined (networkd.conf can even define `my\ table:100`), any value without + * `:` that is not all digits is accepted as a potential name — including `yes`, which is only valid if + * networkd.conf defines it. What is flagged: out-of-range or malformed all-digit numbers (`4294967296`, + * `08`) and anything containing `:` (`name:number` is networkd.conf syntax, not a table reference). An + * invalid value is logged and ignored by systemd. + */ +class ConfigParseWireguardPeerRouteTableOptionValue : SimpleGrammarOptionValues( + "config_parse_wireguard_peer_route_table", + AlternativeCombinator( + SequenceCombinator(BOOLEAN_FALSE, EOF()), + ROUTE_TABLE_FROM_STRING, + ) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseWireguardPrivateKeyFileOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseWireguardPrivateKeyFileOptionValue.kt new file mode 100644 index 00000000..4e818fc7 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseWireguardPrivateKeyFileOptionValue.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.NON_API_VFS_ABSOLUTE_PATH +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator + +/* + * [WireGuard] PrivateKeyFile= in a .netdev file. + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.netdev.html#PrivateKeyFile= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/netdev/wireguard.c#L563-L591 config_parse_wireguard_private_key_file (0) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/shared/parse-helpers.c#L34-L88 path_simplify_and_warn (PATH_CHECK_ABSOLUTE|PATH_CHECK_NON_API_VFS) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/mountpoint-util.c#L721-L726 path_below_api_vfs + */ + +/** + * Validator for `[WireGuard] PrivateKeyFile=` (.netdev). + * + * C function: config_parse_wireguard_private_key_file. The value is strdup'd verbatim — no specifier expansion and no unquoting, so + * `%` and quotes are ordinary characters and a path may contain spaces — and checked with + * path_simplify_and_warn(PATH_CHECK_ABSOLUTE|PATH_CHECK_NON_API_VFS): it must be absolute, must not keep + * a `..` component after simplification, and must not be at or below `/dev`, `/sys` or `/proc`. + * See [NON_API_VFS_ABSOLUTE_PATH]. An empty assignment clears the setting. An invalid value is logged + * and ignored by systemd. + */ +class ConfigParseWireguardPrivateKeyFileOptionValue : SimpleGrammarOptionValues( + "config_parse_wireguard_private_key_file", + SequenceCombinator(NON_API_VFS_ABSOLUTE_PATH, EOF()) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseWireguardPrivateKeyOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseWireguardPrivateKeyOptionValue.kt new file mode 100644 index 00000000..db993546 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseWireguardPrivateKeyOptionValue.kt @@ -0,0 +1,35 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.AlternativeCombinator +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.CREDENTIAL_REFERENCE +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.WIREGUARD_BASE64_KEY + +/* + * [WireGuard] PrivateKey= in a .netdev file. + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.netdev.html#PrivateKey= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/netdev/wireguard.c#L546-L561 config_parse_wireguard_private_key (0) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/network/netdev/wireguard.c#L488-L544 wireguard_decode_key_and_warn + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/hexdecoct.c#L755-L851 unbase64mem_full + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/hexdecoct.c#L528-L555 unbase64char (also accepts the URL-safe '-' and '_') + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/shared/creds-util.c#L156-L180 read_credential (credential_name_valid) + * tests https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-wireguard-23-peers.netdev#L12 PrivateKey=EEGlnEPYJV//kbvvIqxKkQwOiS+UENyPncC4bF46ong= + * tests https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-wireguard.netdev.d/private-key.conf#L2 (same key, drop-in) + */ + +/** + * Validator for `[WireGuard] PrivateKey=` (.netdev). + * + * C function: config_parse_wireguard_private_key → wireguard_decode_key_and_warn. Either `@name`, + * which reads the key from the named credential (the name must pass credential_name_valid), or the key + * itself in base64, which must decode to exactly 32 bytes: 43 characters plus one mandatory `=` of + * padding, the 43rd character restricted to those whose low two bits are clear. See + * [WIREGUARD_BASE64_KEY]. An invalid value is logged and ignored by systemd. + */ +class ConfigParseWireguardPrivateKeyOptionValue : SimpleGrammarOptionValues( + "config_parse_wireguard_private_key", + SequenceCombinator(AlternativeCombinator(CREDENTIAL_REFERENCE, WIREGUARD_BASE64_KEY), EOF()) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseWireguardRouteTableOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseWireguardRouteTableOptionValue.kt new file mode 100644 index 00000000..0c1e49e5 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseWireguardRouteTableOptionValue.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.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.ROUTE_TABLE_FROM_STRING +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator + +/* + * [WireGuard] RouteTable= in a .netdev file. + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.netdev.html#RouteTable= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/netdev/wireguard.c#L905-L939 config_parse_wireguard_route_table (0) + * 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#L427-L431 route_table_table (default, main, local) + * 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) + * tests https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-wireguard.netdev#L10 + */ + +/** + * Validator for `[WireGuard] RouteTable=` (.netdev). + * + * C function: config_parse_wireguard_route_table. A value that parse_boolean() reads as false (`no`, `off`, `0`, …) + * disables route configuration; anything else is a route table for manager_get_route_table_from_string: + * `default`, `main`, `local`, a name defined with `RouteTable=` in networkd.conf, or a non-zero uint32 + * (safe_atou32, so hex, octal and `0b`/`0o` work). See [ROUTE_TABLE_FROM_STRING]. An empty assignment also disables it. + * + * Because table names are user-defined (networkd.conf can even define `my\ table:100`), any value without + * `:` that is not all digits is accepted as a potential name — including `yes`, which is only valid if + * networkd.conf defines it. What is flagged: out-of-range or malformed all-digit numbers (`4294967296`, + * `08`) and anything containing `:` (`name:number` is networkd.conf syntax, not a table reference). An + * invalid value is logged and ignored by systemd. + */ +class ConfigParseWireguardRouteTableOptionValue : SimpleGrammarOptionValues( + "config_parse_wireguard_route_table", + AlternativeCombinator( + SequenceCombinator(BOOLEAN_FALSE, EOF()), + ROUTE_TABLE_FROM_STRING, + ) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseWolPasswordOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseWolPasswordOptionValue.kt new file mode 100644 index 00000000..761800da --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseWolPasswordOptionValue.kt @@ -0,0 +1,35 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.AlternativeCombinator +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.EOF +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.MAC_ADDRESS +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SAFE_ABSOLUTE_PATH +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator + +/* + * [Link] WakeOnLanPassword= in a .link file. + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.link.html#WakeOnLanPassword= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/udev/net/link-config.c#L2050-L2093 config_parse_wol_password (0) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/udev/net/link-config.c#L134-L155 link_parse_wol_password → parse_ether_addr + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/ether-addr-util.c#L282-L295 parse_ether_addr (parse_hw_addr_full, ETH_ALEN) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/path-util.h#L152-L154 path_is_safe + */ + +/** + * Validator for `[Link] WakeOnLanPassword=` (.link). + * + * C function: config_parse_wol_password. If the value is an absolute path that path_is_safe() accepts + * (no `..` component) it names a file to read the password from; otherwise it must be the 6-byte + * SecureOn password written like an Ethernet address (parse_ether_addr: `00:11:22:33:44:55`, + * `00-11-22-33-44-55` or `0011.2233.4455`). The path is taken verbatim (no specifiers, spaces are + * ordinary characters). An invalid value is logged and ignored by systemd. + */ +class ConfigParseWolPasswordOptionValue : SimpleGrammarOptionValues( + "config_parse_wol_password", + AlternativeCombinator( + SequenceCombinator(MAC_ADDRESS, EOF()), + SequenceCombinator(SAFE_ABSOLUTE_PATH, EOF()), + ) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/BatchNetdevLinkCombinators.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/BatchNetdevLinkCombinators.kt new file mode 100644 index 00000000..10ad2f15 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/BatchNetdevLinkCombinators.kt @@ -0,0 +1,517 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar + +import java.math.BigInteger + +/* + * Shared grammars for the systemd-networkd .netdev (src/network/netdev/) and udev .link (src/udev/net/) + * validators added in the 2026-10 netdev-link batch. + * + * Everything here is pinned to systemd 0f0682b773, the commit recorded in + * systemd-build/build/last_commit_hash. Browse it at https://github.com/systemd/systemd/blob/0f0682b773. + * + * These live in their own file (rather than Combinators.kt) only because several batches were written + * in parallel. Several are general-purpose and are good candidates for promotion — see the batch's + * RECOMMENDATIONS.md. + */ + +// --------------------------------------------------------------------------------------------------- +// parse_range() — src/basic/parse-util.c. Its bounds are safe_atou() numbers, modelled by [SafeAtoTerminal]. + +/** + * Whether parse_range() (https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L305-L340) + * accepts the already-extracted [word]: the text before the first `-` must satisfy [lower] and, if there is a `-`, + * the non-empty rest must satisfy [upper]. A predicate for an [ExtractedWordTerminal], whose words have had their + * quotes and escapes removed first; the structured grammar spells the same check out with the terminals. + */ +fun netdevLinkParseRangeAccepts(word: String, lower: SafeAtoTerminal, upper: SafeAtoTerminal): Boolean { + val dash = word.indexOf('-') + if (dash < 0) return lower.valueOf(word) != null + val upperText = word.substring(dash + 1) + return lower.valueOf(word.substring(0, dash)) != null && upperText.isNotEmpty() && upper.valueOf(upperText) != null +} + + +// --------------------------------------------------------------------------------------------------- +// Words as extract_first_word() (src/basic/extract-word.c) returns them. + +/** + * A zero-width check that the input ends here or continues with one of [separators] (a regex character + * class body such as `\s,`). Put after a structured word so that, under the classic first-match engine, + * an [AlternativeCombinator] does not commit to a branch that matched only a prefix of the word. + */ +class WordEnd(separators: String) : Combinator { + private val separator = Regex("[$separators]") + + private fun at(value: String, offset: Int): Boolean = + offset == value.length || separator.matchesAt(value, offset) + + override fun SyntacticMatch(value: String, offset: Int): MatchResult = + if (at(value, offset)) MatchResult(emptyList(), offset, emptyList(), offset) else NoMatch + + override fun SemanticMatch(value: String, offset: Int): MatchResult = SyntacticMatch(value, offset) + + override fun parse(value: String, offset: Int): Sequence = + if (at(value, offset)) sequenceOf(Parse(offset, emptyList())) else sequenceOf(Stuck(offset, setOf(this))) + + override fun toStringIndented(indent: Int): String = "WordEnd" +} + +/** + * One raw word of an extract_first_word(…, separators, EXTRACT_UNQUOTE[|EXTRACT_CUNESCAPE]) list, checked + * the way systemd checks it: AFTER the quotes (which may open and close anywhere in the word, as in + * `0-"3"`) and backslash escapes are removed. + * + * It matches a run of non-separator characters, backslash escapes and balanced quoted runs (quoted runs + * may contain separators) — but ONLY when that word contains a quote or backslash, [extractWord] succeeds + * on it and [check] accepts the result. Use it as the last alternative after a structured grammar for the + * common spelling: the common spelling keeps its highlighting and error localization, and this only + * rescues the exotic quoting and escaping systemd also accepts. + * + * It deliberately has no "syntactically right but semantically wrong" state. Under the classic engine + * GrammarOptionValue locates a semantic error by lining the semantic match's longestMatch up against the + * syntactic tokens; a whole-word token that only fails semantically would be misaligned with the + * structured branch's partial progress and produce an out-of-range highlight. So an invalid word simply + * does not match, and the value is reported as not matching the expected format. + * + * @param separators a regex character-class body for the separator set, e.g. `\s` or `\s,` + * @param cunescape whether the caller passes EXTRACT_CUNESCAPE + */ +class ExtractedWordTerminal( + separators: String, + private val cunescape: Boolean, + private val description: String, + private val check: (String) -> Boolean, +) : TerminalCombinator { + + private val shape = Regex("""(?:[^$separators"'\\]|\\[\s\S]|"(?:[^"\\]|\\[\s\S])*"|'(?:[^'\\]|\\[\s\S])*')+""") + + /** The word at [offset] if it is one this terminal accepts, else null. */ + private fun word(value: String, offset: Int): String? { + val text = shape.matchAt(value, offset)?.value ?: return null + if (text.none { it == '"' || it == '\'' || it == '\\' }) return null + return text.takeIf { extractWord(it, cunescape)?.let(check) == true } + } + + override fun SyntacticMatch(value: String, offset: Int): MatchResult { + val text = word(value, offset) ?: return NoMatch + return MatchResult(listOf(text), offset + text.length, listOf(this), offset + text.length) + } + + override fun SemanticMatch(value: String, offset: Int): MatchResult = SyntacticMatch(value, offset) + + override fun parse(value: String, offset: Int): Sequence { + val text = word(value, offset) ?: return sequenceOf(Stuck(offset, setOf(this))) + return sequenceOf(Parse(offset + text.length, listOf(ParsedToken(offset, offset + text.length, text, this, true)))) + } + + override fun toString(): String = "ExtractedWord($description)" +} + +/** + * The text extract_first_word() returns for the single raw word [raw] under EXTRACT_UNQUOTE (plus + * EXTRACT_CUNESCAPE when [cunescape]), or null where it fails: an unbalanced quote, a trailing + * backslash, or — with [cunescape] — an escape cunescape_one() (src/basic/escape.c) refuses. Without + * [cunescape], a backslash simply makes the next character literal. + */ +fun extractWord(raw: String, cunescape: Boolean): String? { + val out = StringBuilder() + var quote: Char? = null + var i = 0 + while (i < raw.length) { + val c = raw[i] + if (c == '\\') { + if (i + 1 >= raw.length) return null + if (cunescape) { + val (text, length) = cunescapeOne(raw, i + 1) ?: return null + out.append(text) + i += 1 + length + } else { + out.append(raw[i + 1]) + i += 2 + } + continue + } + when { + quote != null && c == quote -> quote = null + quote == null && (c == '"' || c == '\'') -> quote = c + else -> out.append(c) + } + i++ + } + return if (quote == null) out.toString() else null +} + +/** cunescape_one(…, accept_nul = false) on the escape starting at [p]: the decoded text and its length. */ +private fun cunescapeOne(s: String, p: Int): Pair? { + fun digits(count: Int, radix: Int): Int? = + if (p + 1 + count > s.length) null else s.substring(p + 1, p + 1 + count).let { d -> if (d.all { Character.digit(it, radix) >= 0 }) d.toInt(radix) else null } + + return when (val c = s[p]) { + 'a' -> "\u0007" to 1 + 'b' -> "\b" to 1 + 'f' -> "\u000C" to 1 + 'n' -> "\n" to 1 + 'r' -> "\r" to 1 + 't' -> "\t" to 1 + 'v' -> "\u000B" to 1 + '\\', '"', '\'' -> c.toString() to 1 + 's' -> " " to 1 + 'x' -> digits(2, 16)?.takeIf { it != 0 }?.let { it.toChar().toString() to 3 } + 'u' -> digits(4, 16)?.takeIf { it != 0 && it !in 0xD800..0xDFFF }?.let { it.toChar().toString() to 5 } + 'U' -> { + if (p + 9 > s.length) return null + val v = s.substring(p + 1, p + 9).toLongOrNull(16) ?: return null + if (v == 0L || v > 0x10FFFF || v in 0xD800..0xDFFF) return null + String(Character.toChars(v.toInt())) to 9 + } + in '0'..'7' -> { + if (p + 3 > s.length) return null + val d = s.substring(p, p + 3) + if (!d.all { it in '0'..'7' }) return null + val v = d.toInt(8) + if (v == 0 || v > 255) null else v.toChar().toString() to 3 + } + else -> null + } +} + +// --------------------------------------------------------------------------------------------------- +// CPU sets — config_parse_cpu_set (src/shared/cpu-set-util.c). + +// One extract_first_word() separator run: any mix of whitespace and commas (separators coalesce). +private val CPU_SET_SEPARATOR = RegexTerminal("""[\s,]+""", """[\s,]+""") + +// parse_range() bounds as safe_atou() reads them, below CPU_SET_MAX_NCPU (8192, cpu_set_add_range). The word was +// split on whitespace and commas, so no whitespace can reach safe_atou(). The lower bound is cut at the first `-` +// by parse_range(), so it never sees a minus; the upper bound is the rest of the word, which may (`0--0`). +private val CPU_SET_LOWER = SafeAtoTerminal( + signed = false, + base = 0, + flags = emptySet(), + minInclusive = 0L, + maxInclusive = 8191L, + acceptsWhitespace = false, + acceptsMinus = false, +) +private val CPU_SET_UPPER = SafeAtoTerminal( + signed = false, + base = 0, + flags = emptySet(), + minInclusive = 0L, + maxInclusive = 8191L, + acceptsWhitespace = false, + acceptsMinus = true, +) +private val CPU_SET_RANGE = SequenceCombinator(CPU_SET_LOWER, ZeroOrOne(SequenceCombinator(HYPHEN, CPU_SET_UPPER))) + +// The same bounds for a word whose quotes have been removed: quoted whitespace does reach safe_atou() (`" 3"`). +private val CPU_SET_UNQUOTED_LOWER = SafeAtoTerminal( + signed = false, + base = 0, + flags = emptySet(), + minInclusive = 0L, + maxInclusive = 8191L, + acceptsWhitespace = true, + acceptsMinus = false, +) +private val CPU_SET_UNQUOTED_UPPER = SafeAtoTerminal( + signed = false, + base = 0, + flags = emptySet(), + minInclusive = 0L, + maxInclusive = 8191L, + acceptsWhitespace = true, + acceptsMinus = true, +) + +// One word: the structured `N[-M]` grammar, bare or quoted as a whole, for highlighting; then an +// [ExtractedWordTerminal] fallback for quotes and escapes anywhere in the word (`0-"3"`, `'0'-'3'`). +private val CPU_SET_ITEM = AlternativeCombinator( + SequenceCombinator( + AlternativeCombinator( + SequenceCombinator(LiteralChoiceTerminal("\""), CPU_SET_RANGE, LiteralChoiceTerminal("\"")), + SequenceCombinator(LiteralChoiceTerminal("'"), CPU_SET_RANGE, LiteralChoiceTerminal("'")), + CPU_SET_RANGE, + ), + WordEnd("""\s,"""), + ), + ExtractedWordTerminal("""\s,""", cunescape = false, "range 0..8191") { word -> + netdevLinkParseRangeAccepts(word, CPU_SET_UNQUOTED_LOWER, CPU_SET_UNQUOTED_UPPER) + }, +) + +/** + * A CPU set: CPU indices and `N-M` ranges separated by whitespace and/or commas. + * + * config_parse_cpu_set splits with extract_first_word(…, WHITESPACE ",", EXTRACT_UNQUOTE), so separators + * coalesce, leading and trailing separators are harmless (`0, 1, 2, 3, 4, 5, 6, 7, 63, ` is in + * test-cpu-set-util.c), and a word may be quoted — anywhere in the word, not only around it (`0-"3"` is + * `0-3`; the [ExtractedWordTerminal] fallback handles that). Each word goes to parse_range(), whose bounds are + * safe_atou() numbers ([SafeAtoTerminal]: hex, octal, `0b`/`0o`) — and cpu_set_add_range() refuses anything at or above + * CPU_SET_MAX_NCPU (8192). A range whose lower end exceeds its upper end (`3-0`) is rejected by systemd + * after parsing; that compares numbers and is not modelled. + */ +val CPU_SET: Combinator = SequenceCombinator( + ZeroOrOne(CPU_SET_SEPARATOR), + CPU_SET_ITEM, + ZeroOrMore(SequenceCombinator(CPU_SET_SEPARATOR, CPU_SET_ITEM)), + ZeroOrOne(CPU_SET_SEPARATOR), +) + + +// --------------------------------------------------------------------------------------------------- +// Sizes — parse_size() (src/basic/parse-util.c). + +// strtoull(…, 10) after parse_size has skipped whitespace: an optional `+` (a `-` is refused), digits, +// then an optional `.` and fraction. parse_size steps over the `.` unconditionally and ignores a +// missing fraction ("E.g. 10.M is valid"), so `10.` and `10.M` are both legal. +// Labeled so the number colours as a literal, like ByteSizeTerminal does. +private val SIZE_NUMBER = Labeled(Role.LITERAL, RegexTerminal("""\+?[0-9]+(?:\.[0-9]*)?""", """\+?[0-9]+(?:\.[0-9]*)?""")) + +/** + * A parse_size() value with base 1000 (the SI table: `E`, `P`, `T`, `G`, `M`, `K`, `B` and bare). + * + * parse_size accepts a run of `number[suffix]` elements whose suffixes strictly descend through the + * table (`start_pos = i + 1`), with optional whitespace before each number and between a number and its + * suffix. The bare (suffix-less) element is the last table entry, so it can only come last. That + * ordering is spelled out structurally: [sizeChain] for table index `i` offers every suffix from `i` + * onwards, each optionally followed by a chain starting after it. + * + * Suffixes are case-sensitive: `1k` is not a size. + */ +fun siSize(): Combinator = sizeChain(listOf("E", "P", "T", "G", "M", "K", "B")) + +private fun sizeChain(suffixes: List): Combinator { + val ws = ZeroOrOne(WhitespaceTerminal()) + // Built from the last suffix backwards so each chain reuses the shorter one after it. + var chainAfter: Combinator? = null + val chains = ArrayList() + // The bare element (no suffix) is always the final alternative of every chain. + val bare: Combinator = SequenceCombinator(ws, SIZE_NUMBER) + for (i in suffixes.indices.reversed()) { + val element = SequenceCombinator(ws, SIZE_NUMBER, ws, LiteralChoiceTerminal(suffixes[i])) + val withRest = if (chainAfter == null) SequenceCombinator(element, ZeroOrOne(bare)) + else SequenceCombinator(element, ZeroOrOne(chainAfter)) + chains.add(0, withRest) + chainAfter = AlternativeCombinator(*chains.toTypedArray(), bare) + } + return chainAfter ?: bare +} + + +// --------------------------------------------------------------------------------------------------- +// Durations — parse_nsec() (src/basic/time-util.c). + +// strtoll(…, 10) as parse_nsec drives it: an optional `+` (a leading `-` is refused), then digits with +// an optional fraction — or a bare fraction like `.5`, which parse_nsec accepts because it checks for +// `.` before it checks whether strtoll consumed anything. A trailing `.` with no digits (`3.`) is refused. +private val DURATION_NUMBER = Labeled( + Role.LITERAL, + RegexTerminal("""\+?[0-9]+(?:\.[0-9]+)?|\.[0-9]+""", """\+?[0-9]+(?:\.[0-9]+)?|\.[0-9]+"""), +) + +/** extract_nsec_multiplier's table (minus its empty default entry, which is the bare-number case). */ +private val NSEC_SUFFIX = LiteralChoiceTerminal( + "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", "μs", "µs", "nsec", "ns", +) + +/** + * A parse_nsec() duration WITHOUT the `infinity` spelling: one or more `number[unit]` elements, an + * element without a unit meaning nanoseconds. Whitespace may separate elements and may sit between a + * number and its unit; an element with a unit may be followed directly by the next one (`1s500ms`). + * + * Lenient on one corner: parse_nsec refuses a bare number followed immediately by a `.` fraction + * (`12.34.56`), which this grammar would read as `12.34` + `.56`. + */ +val NSEC_DURATION_FINITE: Combinator = run { + val ws = ZeroOrOne(WhitespaceTerminal()) + val element = AlternativeCombinator( + SequenceCombinator(DURATION_NUMBER, ws, NSEC_SUFFIX), + DURATION_NUMBER, + ) + SequenceCombinator(element, ZeroOrMore(SequenceCombinator(ws, element))) +} + + +// --------------------------------------------------------------------------------------------------- +// Paths. + +// Rejects a `..` component anywhere (see NO_DOT_DOT in Combinators.kt). +private const val NO_DOT_DOT_COMPONENT = """(?!(?:[\s\S]*/)?\.\.(?:/|$))""" + +/** + * An absolute path checked by path_simplify_and_warn(…, PATH_CHECK_ABSOLUTE|PATH_CHECK_NON_API_VFS) + * (src/shared/parse-helpers.c), taken verbatim — no specifier expansion, no unquoting — as the netdev + * `*KeyFile=` settings do. After path_simplify() the path must be normalized (so no `..` component) and + * must not be at or below one of the API VFS mounts `/dev`, `/sys`, `/proc` (path_below_api_vfs, which + * compares whole components, so `/devices` is fine). Because path_simplify() runs first, `.` components + * and repeated slashes in front of those names do not hide them (`/./dev/shm/key`, `//sys`). Spaces + * are ordinary path characters. Lenient: path_is_valid()'s NAME_MAX/PATH_MAX length limits. + */ +val NON_API_VFS_ABSOLUTE_PATH = RegexTerminal( + """[\s\S]+""", + """(?!/(?:\.?/)*(?:dev|sys|proc)(?:/|$))$NO_DOT_DOT_COMPONENT/[\s\S]*""", +) + +/** + * An absolute path accepted by path_is_absolute() && path_is_safe() (src/basic/path-util.h): it starts + * with `/` and has no `..` component. Taken verbatim. + */ +val SAFE_ABSOLUTE_PATH = RegexTerminal( + """/[\s\S]*""", + """$NO_DOT_DOT_COMPONENT/[\s\S]*""", +) + + +// --------------------------------------------------------------------------------------------------- +// WireGuard keys and credentials — wireguard_decode_key_and_warn (src/network/netdev/wireguard.c) → +// unbase64mem_full (src/basic/hexdecoct.c). + +/** + * A 32-byte WireGuard key in base64, as unbase64mem_full() decodes it. + * + * 32 bytes is ten full 4-character groups plus a final 3-character group padded with one `=`; the + * padding is mandatory — without it unbase64_next() runs out of input mid-group and fails. The third + * character of a padded group must have its low two bits clear (`c & 3`), which limits the 43rd + * character to `AEIMQUYcgkosw048`. unbase64char() accepts the URL-safe alphabet too (`-` for `+`, + * `_` for `/`), and unbase64_next() skips whitespace anywhere in the input. + */ +val WIREGUARD_BASE64_KEY = RegexTerminal( + """[A-Za-z0-9+/_=\-]+(?:[ \t]+[A-Za-z0-9+/_=\-]+)*""", + """(?:[A-Za-z0-9+/_\-][ \t]*){42}[AEIMQUYcgkosw048][ \t]*=""", +) + +/** + * `@name` — a credential reference resolved by read_credential(), which refuses any name that fails + * credential_name_valid() (src/shared/creds-util.c). [CONDITION_CREDENTIAL] models that check. + */ +val CREDENTIAL_REFERENCE: Combinator = SequenceCombinator(LiteralChoiceTerminal("@"), CONDITION_CREDENTIAL) + + +// --------------------------------------------------------------------------------------------------- +// Address prefixes — in_addr_prefix_from_string_auto_full (src/basic/in-addr-util.c). + +/** + * An IPv4 or IPv6 address with an optional `/prefixlen`, as in_addr_prefix_from_string_auto (mode + * PREFIXLEN_FULL) reads it. Like [IP_ADDR_AND_ANY_PREFIX], but the length goes through + * in_addr_parse_prefixlen → safe_atou8, so it is a base-0 number (`/0x18` is 24, `/0b+1000` is 8) up to the + * family width. Its only user, AllowedIPs=, splits the list on whitespace first, so no whitespace reaches + * safe_atou8 (`/0b 1` is the two words `…/0b` and `1`). IPv6 is tried first so the IPv4 branch never strands + * part of an IPv6 literal. + */ +val IP_ADDR_AND_SAFE_PREFIX: Combinator = AlternativeCombinator( + SequenceCombinator( + IPV6_ADDR, + ZeroOrOne( + SequenceCombinator( + CIDR_SEPARATOR, + SafeAtoTerminal( + signed = false, + base = 0, + flags = emptySet(), + minInclusive = 0L, + maxInclusive = 128L, + acceptsWhitespace = false, + acceptsMinus = true, + ), + ), + ), + ), + SequenceCombinator( + IPV4_ADDR, + ZeroOrOne( + SequenceCombinator( + CIDR_SEPARATOR, + SafeAtoTerminal( + signed = false, + base = 0, + flags = emptySet(), + minInclusive = 0L, + maxInclusive = 32L, + acceptsWhitespace = false, + acceptsMinus = true, + ), + ), + ), + ), +) + + +// --------------------------------------------------------------------------------------------------- +// Checks on a whole matched span — for rules systemd applies to the parsed value rather than its shape. + +/** + * Matches exactly what [inner] matches, but the match is only semantically valid when [check] accepts + * the matched text. For limits systemd applies after parsing, which a grammar cannot spell out: a + * magnitude bound on a parse_size() total, or "must (not) be a multicast address". + * + * Under the parse engine a rejected span marks its first token invalid; under the classic engine the + * semantic match fails with no progress at the span's start. Either way the first token of the span is + * highlighted, and the value keeps the structured grammar's colouring. + */ +class NetdevLinkCheckedSpan( + private val inner: Combinator, + private val description: String, + private val check: (String) -> Boolean, +) : Combinator { + + override fun SyntacticMatch(value: String, offset: Int): MatchResult = inner.SyntacticMatch(value, offset) + + override fun SemanticMatch(value: String, offset: Int): MatchResult { + val match = inner.SemanticMatch(value, offset) + if (match.matchResult == -1 || check(value.substring(offset, match.matchResult))) return match + 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.isNotEmpty() && !check(value.substring(offset, step.end))) { + step.copy(tokens = listOf(step.tokens.first().copy(valid = false)) + step.tokens.drop(1)) + } else { + step + } + } + + override fun toString(): String = "Checked($description)" + + override fun toStringIndented(indent: Int): String = " ".repeat(indent) + "Checked($description,\n" + inner.toStringIndented(indent + 1) + ")" +} + +/** + * in_addr_is_multicast() (src/basic/in-addr-util.c) on an address literal [IP_ADDR] has matched: IPv4 + * in 224.0.0.0/4 (IN_MULTICAST), IPv6 in ff00::/8 (IN6_IS_ADDR_MULTICAST). The first IPv6 byte is 0xff + * only when the first group is written with four hex digits starting `ff` — `ff::1` is 0x00ff::1, and an + * address starting `::` (including IPv4-mapped `::ffff:239.1.1.1`) has a zero first byte. + */ +fun netdevLinkIsMulticast(address: String): Boolean { + if (':' in address) { + val first = address.substringBefore(':') + return first.length == 4 && first.startsWith("ff", ignoreCase = true) + } + return address.substringBefore('.').toIntOrNull() in 224..239 +} + +/** + * The total parse_size(text, 1000, …) (src/basic/parse-util.c) computes for a value [siSize] has + * matched: each element contributes `l * factor + (unsigned long long) (frac * factor)`. Exact decimal + * arithmetic stands in for the C double, which only matters within a unit of the truncated fraction. + * Null if [text] does not have [siSize]'s shape. + */ +fun netdevLinkParseSizeSi(text: String): BigInteger? { + val factors = mapOf("E" to 18, "P" to 15, "T" to 12, "G" to 9, "M" to 6, "K" to 3, "B" to 0, "" to 0) + val element = Regex("""\s*\+?([0-9]+)(?:\.([0-9]*))?\s*([EPTGMKB]?)""") + var total = BigInteger.ZERO + var pos = 0 + while (pos < text.length) { + val m = element.matchAt(text, pos) ?: return null + if (m.value.isEmpty()) return null + val factor = BigInteger.TEN.pow(factors.getValue(m.groupValues[3])) + val fraction = m.groupValues[2].takeIf { it.isNotEmpty() } + ?.let { java.math.BigDecimal("0.$it").multiply(java.math.BigDecimal(factor)).toBigInteger() } ?: BigInteger.ZERO + total += BigInteger(m.groupValues[1]) * factor + fraction + pos = m.range.last + 1 + } + return total +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseAdvertiseOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseAdvertiseOptionValueTest.kt new file mode 100644 index 00000000..e8af0df8 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseAdvertiseOptionValueTest.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 ConfigParseAdvertiseOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [Link] + Advertise=1000baset-full + Advertise=10baset-half 100baset-full autonegotiation pause asym-pause + Advertise=10000baser-fec fec-rs + Advertise=1600000basedr8-2-full + """.trimIndent() + + setupFileInEditor("file.link", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [Link] + Advertise=hoge foo + Advertise=1000BaseT-Full + Advertise=autoneg + Advertise=1000baset-full,100baset-full + """.trimIndent() + + setupFileInEditor("file.link", 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/ConfigParseBadadvBandwidthOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseBadadvBandwidthOptionValueTest.kt new file mode 100644 index 00000000..8b5eef20 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseBadadvBandwidthOptionValueTest.kt @@ -0,0 +1,67 @@ +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 ConfigParseBadadvBandwidthOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [BatmanAdvanced] + GatewayBandwidthDown=205M + GatewayBandwidthUp=2G + GatewayBandwidthDown=1000 + GatewayBandwidthUp=1.5G + GatewayBandwidthDown=10.M + GatewayBandwidthUp=3. K + GatewayBandwidthDown=4 M 11.5K + GatewayBandwidthUp=3.5G3B + GatewayBandwidthDown=4T3G3B + GatewayBandwidthUp=+5K + GatewayBandwidthDown=429496729599999 + GatewayBandwidthUp=429T 496G 729M 599K 999 + GatewayBandwidthDown=429.496729599999T + GatewayBandwithDown=100K + GatewayBandwithUp=1T + GatewayBandwidthDown=111.4 + """.trimIndent() + + setupFileInEditor("file.netdev", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [BatmanAdvanced] + GatewayBandwidthDown=5k + GatewayBandwidthUp=-1 + GatewayBandwidthDown=12X + GatewayBandwidthUp=3B3.5G + GatewayBandwidthDown=12P12P + GatewayBandwidthUp=3. 0 K + GatewayBandwidthDown=fast + GatewayBandwithUp=1 Mbit + GatewayBandwidthDown=12P + GatewayBandwidthUp=3E 2P + GatewayBandwidthDown=429496729600000 + GatewayBandwidthUp=429.4967296T + GatewayBandwidthDown=0.5P + GatewayBandwidthUp=20E + GatewayBandwidthDown=18446744073709551616 + """.trimIndent() + + setupFileInEditor("file.netdev", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(15, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseCoalesceNsecOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseCoalesceNsecOptionValueTest.kt new file mode 100644 index 00000000..e22f59a9 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseCoalesceNsecOptionValueTest.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 ConfigParseCoalesceNsecOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [Link] + RxCoalesceCQENSec=500 + RxCoalesceCQENSec=1us + RxCoalesceCQENSec=2.5 + RxCoalesceCQENSec=1s500ms + RxCoalesceCQENSec=.7 + RxCoalesceCQENSec=3.1 sec .2 sec + RxCoalesceCQENSec=+3.1s + RxCoalesceCQENSec=3.1s.2 + RxCoalesceCQENSec=100 ns + RxCoalesceCQENSec=1µs + """.trimIndent() + + setupFileInEditor("file.link", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [Link] + RxCoalesceCQENSec=infinity + RxCoalesceCQENSec=-5 + RxCoalesceCQENSec=5. + RxCoalesceCQENSec=3.s + RxCoalesceCQENSec=xyz + RxCoalesceCQENSec=12..34 + """.trimIndent() + + setupFileInEditor("file.link", 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/ConfigParseCpuSetOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseCpuSetOptionValueTest.kt new file mode 100644 index 00000000..06a3074c --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseCpuSetOptionValueTest.kt @@ -0,0 +1,63 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseCpuSetOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [Link] + IRQAffinity= + IRQAffinity=0 + IRQAffinity=1 2 4 + IRQAffinity=0-3,8-11 + IRQAffinity=0, 1, 2, 3, 4, 5, 6, 7, 63, + IRQAffinity=8 '9' 10 "11" + IRQAffinity=0-3 8-11, + IRQAffinity=8000-8191 + IRQAffinity=0x0-0x3 + IRQAffinity=,0 + IRQAffinity=0-"3" + IRQAffinity='0'-'3', 4 + IRQAffinity=0b+1 + IRQAffinity=0-0b+1 + IRQAffinity=0-0b-0 + IRQAffinity="0b 1" + """.trimIndent() + + setupFileInEditor("file.link", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [Link] + IRQAffinity=0 1 2 3 garbage + IRQAffinity=0-3 8-garbage + IRQAffinity=8192 + IRQAffinity=1- + IRQAffinity=-1 + IRQAffinity=0 1 2 3 "4 5 6 7 + IRQAffinity=-0 + IRQAffinity=0-"9000" + IRQAffinity=0b-0 + IRQAffinity=0b 1 + IRQAffinity=0-0b 1 + """.trimIndent() + + setupFileInEditor("file.link", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(11, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDefaultPortVlanidOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDefaultPortVlanidOptionValueTest.kt new file mode 100644 index 00000000..44b4c1b6 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDefaultPortVlanidOptionValueTest.kt @@ -0,0 +1,50 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseDefaultPortVlanidOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [Bridge] + DefaultPVID=9 + DefaultPVID=none + DefaultPVID=1 + DefaultPVID=4094 + DefaultPVID=0 + DefaultPVID=0xFFE + DefaultPVID=0b 1 + DefaultPVID=0b+1 + DefaultPVID=0o 7 + DefaultPVID=0b -0 + """.trimIndent() + + setupFileInEditor("file.netdev", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [Bridge] + DefaultPVID=4095 + DefaultPVID=None + DefaultPVID=-1 + DefaultPVID=1-10 + """.trimIndent() + + setupFileInEditor("file.netdev", 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/ConfigParseGeneveAddressOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseGeneveAddressOptionValueTest.kt new file mode 100644 index 00000000..124aa131 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseGeneveAddressOptionValueTest.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 ConfigParseGeneveAddressOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [GENEVE] + Remote=10.0.0.2 + Remote=2001:db8::1 + Remote=223.255.255.255 + Remote=240.0.0.1 + Remote=ff::1 + """.trimIndent() + + setupFileInEditor("file.netdev", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [GENEVE] + Remote=10.0.0 + Remote=geneve.example.com + Remote=10.0.0.2/24 + Remote=239.1.1.1 + Remote=ff02::1 + """.trimIndent() + + setupFileInEditor("file.netdev", 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/ConfigParseIfaliasOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseIfaliasOptionValueTest.kt new file mode 100644 index 00000000..4fb1e872 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseIfaliasOptionValueTest.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 ConfigParseIfaliasOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [Link] + Alias= + Alias=uplink to core switch + Alias=eth0 (management) + Alias=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + """.trimIndent() + + setupFileInEditor("file.link", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [Link] + Alias=Café uplink + Alias=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + """.trimIndent() + + setupFileInEditor("file.link", 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/ConfigParseIfnamesOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseIfnamesOptionValueTest.kt new file mode 100644 index 00000000..8cf16f95 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseIfnamesOptionValueTest.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 ConfigParseIfnamesOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [Link] + AlternativeName=dummy98-2-altname + AlternativeName=dummyalt hogehogehogehogehogehoge + AlternativeName=a-very-long-alternative-interface-name-that-is-fine + AlternativeName=sim99 + """.trimIndent() + + setupFileInEditor("file.link", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [Link] + AlternativeName=eth:0 + AlternativeName=all + AlternativeName=123 + AlternativeName=ok bad/name + """.trimIndent() + + setupFileInEditor("file.link", 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/ConfigParseIpoibPkeyOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseIpoibPkeyOptionValueTest.kt new file mode 100644 index 00000000..7b252817 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseIpoibPkeyOptionValueTest.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 ConfigParseIpoibPkeyOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [IPoIB] + PartitionKey=1 + PartitionKey=0x8001 + PartitionKey=0xffff + PartitionKey=0x7fff + PartitionKey=32769 + PartitionKey=0b1 + PartitionKey=0b 1 + PartitionKey=0b+1 + """.trimIndent() + + setupFileInEditor("file.netdev", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [IPoIB] + PartitionKey=0 + PartitionKey=0x8000 + PartitionKey=32768 + PartitionKey=0x10000 + PartitionKey=default + """.trimIndent() + + setupFileInEditor("file.netdev", 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/ConfigParseIpv6FlowlabelOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseIpv6FlowlabelOptionValueTest.kt new file mode 100644 index 00000000..e115519f --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseIpv6FlowlabelOptionValueTest.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 ConfigParseIpv6FlowlabelOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [Tunnel] + IPv6FlowLabel=inherit + IPv6FlowLabel=0 + IPv6FlowLabel=1048575 + IPv6FlowLabel=0xFFFFF + IPv6FlowLabel=0x12345 + IPv6FlowLabel=0b 1 + IPv6FlowLabel=0b-0 + """.trimIndent() + + setupFileInEditor("file.netdev", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [Tunnel] + IPv6FlowLabel=1048576 + IPv6FlowLabel=0x100000 + IPv6FlowLabel=Inherit + IPv6FlowLabel=-1 + """.trimIndent() + + setupFileInEditor("file.netdev", 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/ConfigParseIrqAffinityNumaOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseIrqAffinityNumaOptionValueTest.kt new file mode 100644 index 00000000..2b368796 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseIrqAffinityNumaOptionValueTest.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 ConfigParseIrqAffinityNumaOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [Link] + IRQAffinityNUMA=local + IRQAffinityNUMA=0 + IRQAffinityNUMA=3 + IRQAffinityNUMA=0x1 + IRQAffinityNUMA=4294967293 + IRQAffinityNUMA=0b 1 + IRQAffinityNUMA=0b+1 + """.trimIndent() + + setupFileInEditor("file.link", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [Link] + IRQAffinityNUMA=4294967294 + IRQAffinityNUMA=-1 + IRQAffinityNUMA=remote + IRQAffinityNUMA=1.5 + IRQAffinityNUMA=Local + """.trimIndent() + + setupFileInEditor("file.link", 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/ConfigParseIrqAffinityPolicyOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseIrqAffinityPolicyOptionValueTest.kt new file mode 100644 index 00000000..32344a7a --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseIrqAffinityPolicyOptionValueTest.kt @@ -0,0 +1,40 @@ +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 ConfigParseIrqAffinityPolicyOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [Link] + IRQAffinityPolicy=single + IRQAffinityPolicy=spread + """.trimIndent() + + setupFileInEditor("file.link", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [Link] + IRQAffinityPolicy=Single + IRQAffinityPolicy=balanced + """.trimIndent() + + setupFileInEditor("file.link", 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/ConfigParseL2tpSessionNameOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseL2tpSessionNameOptionValueTest.kt new file mode 100644 index 00000000..651d76a0 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseL2tpSessionNameOptionValueTest.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 ConfigParseL2tpSessionNameOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [L2TPSession] + Name=l2tp-ses1 + Name=sess.a + Name=1a + Name=abcdefghijklmno + """.trimIndent() + + setupFileInEditor("file.netdev", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [L2TPSession] + Name=abcdefghijklmnop + Name=123 + Name=all + Name=a:b + Name=a/b + """.trimIndent() + + setupFileInEditor("file.netdev", 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/ConfigParseL2tpTunnelLocalAddressOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseL2tpTunnelLocalAddressOptionValueTest.kt new file mode 100644 index 00000000..daa73eba --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseL2tpTunnelLocalAddressOptionValueTest.kt @@ -0,0 +1,57 @@ +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 ConfigParseL2tpTunnelLocalAddressOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [L2TP] + Local=192.168.30.100 + Local=2001:db8::1 + Local=auto + Local=static + Local=dynamic + Local=auto@eth0 + Local=dynamic@test1 + Local=192.168.30.100@eth0 + Local=auto@@eth0 + Local=auto@ + Local=static@a-very-long-alternative-interface-name + Local=@auto + Local=@@192.168.30.100@eth0 + """.trimIndent() + + setupFileInEditor("file.netdev", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [L2TP] + Local=any + Local=auto@all + Local=auto@eth/0 + Local=192.168.30.256 + Local=auto@123 + Local=@ + Local=Auto + Local=STATIC@eth0 + """.trimIndent() + + setupFileInEditor("file.netdev", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(8, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseL2tpTunnelRemoteAddressOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseL2tpTunnelRemoteAddressOptionValueTest.kt new file mode 100644 index 00000000..6d7d654d --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseL2tpTunnelRemoteAddressOptionValueTest.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 ConfigParseL2tpTunnelRemoteAddressOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [L2TP] + Remote=192.168.30.101 + Remote=2001:db8::2 + """.trimIndent() + + setupFileInEditor("file.netdev", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [L2TP] + Remote=auto + Remote=192.168.30 + Remote=peer.example.com + """.trimIndent() + + setupFileInEditor("file.netdev", 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/ConfigParseMacAddressPolicyOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseMacAddressPolicyOptionValueTest.kt new file mode 100644 index 00000000..54f08a4c --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseMacAddressPolicyOptionValueTest.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 ConfigParseMacAddressPolicyOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [Link] + MACAddressPolicy=persistent + MACAddressPolicy=random + MACAddressPolicy=none + """.trimIndent() + + setupFileInEditor("file.link", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [Link] + MACAddressPolicy=Persistent + MACAddressPolicy=keep + MACAddressPolicy=yes + """.trimIndent() + + setupFileInEditor("file.link", 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/ConfigParseMacsecKeyFileOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseMacsecKeyFileOptionValueTest.kt new file mode 100644 index 00000000..97c1be19 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseMacsecKeyFileOptionValueTest.kt @@ -0,0 +1,43 @@ +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 ConfigParseMacsecKeyFileOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [MACsecTransmitAssociation] + KeyFile=/etc/systemd/network/macsec.key + [MACsecReceiveAssociation] + KeyFile=/run/keys/rx key.hex + """.trimIndent() + + setupFileInEditor("file.netdev", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [MACsecTransmitAssociation] + KeyFile=macsec.key + KeyFile=/sys/kernel/key + [MACsecReceiveAssociation] + KeyFile=/etc/keys/../secret + """.trimIndent() + + setupFileInEditor("file.netdev", 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/ConfigParseMacsecKeyIdOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseMacsecKeyIdOptionValueTest.kt new file mode 100644 index 00000000..7be9e45d --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseMacsecKeyIdOptionValueTest.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 ConfigParseMacsecKeyIdOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [MACsecTransmitAssociation] + KeyId=01 + KeyId=0203 + KeyId=0203040506070809 + KeyId=ABCDEF + KeyId=00112233445566778899aabbccddeeff + KeyId=01 02 03 + [MACsecReceiveAssociation] + KeyId=020304 + KeyId=ff + """.trimIndent() + + setupFileInEditor("file.netdev", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [MACsecTransmitAssociation] + KeyId=1 + KeyId=255 + KeyId=0 + KeyId=0x01 + KeyId=00112233445566778899aabbccddeeff00 + [MACsecReceiveAssociation] + KeyId=zz + """.trimIndent() + + setupFileInEditor("file.netdev", 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/ConfigParsePortOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParsePortOptionValueTest.kt new file mode 100644 index 00000000..0be26b67 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParsePortOptionValueTest.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 ConfigParsePortOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [Link] + Port=tp + Port=aui + Port=mii + Port=fibre + Port=bnc + """.trimIndent() + + setupFileInEditor("file.link", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [Link] + Port=fiber + Port=TP + Port=da + """.trimIndent() + + setupFileInEditor("file.link", 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/ConfigParsePortRangeOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParsePortRangeOptionValueTest.kt new file mode 100644 index 00000000..96e428c8 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParsePortRangeOptionValueTest.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 ConfigParsePortRangeOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [VXLAN] + PortRange=1-65535 + PortRange=4789 + PortRange=0x100-0x200 + PortRange=32768-61000 + PortRange=1000- 2000 + PortRange=0b 1-5 + PortRange=0b+1-5 + PortRange=1-0b 1 + """.trimIndent() + + setupFileInEditor("file.netdev", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [VXLAN] + PortRange=0-100 + PortRange=1-65536 + PortRange=100:200 + PortRange=-5 + PortRange=5- + PortRange=1000 -2000 + PortRange=+0b1-2 + PortRange=-0b0-2 + PortRange=5--0 + PortRange=-0-5 + PortRange=0b-0 + PortRange=1-0b -1 + """.trimIndent() + + setupFileInEditor("file.netdev", 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/ConfigParseRpsCpuMaskOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseRpsCpuMaskOptionValueTest.kt new file mode 100644 index 00000000..6a8927d8 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseRpsCpuMaskOptionValueTest.kt @@ -0,0 +1,50 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseRpsCpuMaskOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [Link] + ReceivePacketSteeringCPUMask=0 1 + ReceivePacketSteeringCPUMask=2,3 + ReceivePacketSteeringCPUMask=all + ReceivePacketSteeringCPUMask=disable + ReceivePacketSteeringCPUMask=0-7 + ReceivePacketSteeringCPUMask=1 + ReceivePacketSteeringCPUMask=0-"3" + """.trimIndent() + + setupFileInEditor("file.link", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [Link] + ReceivePacketSteeringCPUMask=invalid + ReceivePacketSteeringCPUMask=0 3 8-invalid + ReceivePacketSteeringCPUMask=all 1 + ReceivePacketSteeringCPUMask=none + ReceivePacketSteeringCPUMask=-0 + ReceivePacketSteeringCPUMask=All + ReceivePacketSteeringCPUMask=Disable + """.trimIndent() + + setupFileInEditor("file.link", 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/ConfigParseUdevPropertyOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUdevPropertyOptionValueTest.kt new file mode 100644 index 00000000..3e701359 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseUdevPropertyOptionValueTest.kt @@ -0,0 +1,60 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseUdevPropertyOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [Link] + Property=ID_NET_MANAGED_BY=io.systemd.Network + Property=ID_NET_NAME_MAC=* + Property=FOO=bar BAZ=qux + Property=FOO= + Property="FOO=bar baz" + Property=FOO="bar baz" + Property='FOO=bar baz' X=1 + Property=HOST_%H=1 + Property=_X1=a=b + Property=DEVPATHX=1 + Property=TAGS_EXTRA=1 + Property="FOO"=bar + Property=F"OO"=bar + Property=FOO\x41=1 + Property='ID_X'=1 "Y"=2 + """.trimIndent() + + setupFileInEditor("file.link", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [Link] + Property=FOO + Property=1FOO=bar + Property=INTERFACE=eth0 + Property=SYNTH_ARG_X=1 + Property=FOO-BAR=1 + Property=ACTION=add + Property="ACTION"=add + Property=FOO\q=1 + Property=F"OO=bar + """.trimIndent() + + setupFileInEditor("file.link", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(9, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseVlanQosMapsOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseVlanQosMapsOptionValueTest.kt new file mode 100644 index 00000000..bd29db52 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseVlanQosMapsOptionValueTest.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 ConfigParseVlanQosMapsOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [VLAN] + EgressQOSMaps=21-7 45-5 + IngressQOSMaps=1-1 + EgressQOSMaps=5 + IngressQOSMaps=0x10-0x3 "7-2" '8-1' + EgressQOSMaps=4294967295-7 + IngressQOSMaps=1-7 2-6 + EgressQOSMaps=1-"2" "1"-2 3 + IngressQOSMaps=5--0 + EgressQOSMaps=0-7 + IngressQOSMaps=1-5 + EgressQOSMaps=4294967295-1 + EgressQOSMaps=0b+1-2 + EgressQOSMaps=1-0b+10 + """.trimIndent() + + setupFileInEditor("file.netdev", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [VLAN] + EgressQOSMaps=21:7 + IngressQOSMaps=1- + EgressQOSMaps=-5 + IngressQOSMaps=1-2-3 + EgressQOSMaps=4294967296-1 + IngressQOSMaps=a-b + EgressQOSMaps=-0 + IngressQOSMaps=1-"2 + EgressQOSMaps=0b 1-2 + EgressQOSMaps=0b-0 + """.trimIndent() + + setupFileInEditor("file.netdev", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(10, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseVlanprotocolOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseVlanprotocolOptionValueTest.kt new file mode 100644 index 00000000..10f5f748 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseVlanprotocolOptionValueTest.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 ConfigParseVlanprotocolOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [VLAN] + Protocol=802.1q + Protocol=802.1Q + Protocol=802.1ad + Protocol=802.1AD + [Bridge] + VLANProtocol=802.1ad + VLANProtocol=802.1Q + """.trimIndent() + + setupFileInEditor("file.netdev", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [VLAN] + Protocol=802.1Ad + Protocol=8021q + Protocol=qinq + [Bridge] + VLANProtocol=802.1x + """.trimIndent() + + setupFileInEditor("file.netdev", 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/ConfigParseVrfTableOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseVrfTableOptionValueTest.kt new file mode 100644 index 00000000..db886ccc --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseVrfTableOptionValueTest.kt @@ -0,0 +1,60 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseVrfTableOptionValueTest : AbstractUnitFileTest() { + + // Values with whitespace are possible table names (networkd.conf can define `my\ table:100`), so they are not + // flagged even though sd-check, which loads no networkd.conf, reports them. `0b 1`, `0b+1` and `0o 7` are + // safe_atou32 numbers (sd-check accepts them). + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [VRF] + Table=42 + Table=main + Table=local + Table=default + Table=0x2a + Table=custom-table + Table=4294967295 + Table=yes + Table=0x0 + Table=mian + Table=main 42 + Table=1 2 + Table=my table + Table=0b 1 + Table=0b+1 + Table=0o 7 + """.trimIndent() + + setupFileInEditor("file.netdev", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [VRF] + Table=0 + Table=4294967296 + Table=08 + Table=vrf:42 + Table=040000000000 + """.trimIndent() + + setupFileInEditor("file.netdev", 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/ConfigParseVxlanAddressOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseVxlanAddressOptionValueTest.kt new file mode 100644 index 00000000..590ef709 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseVxlanAddressOptionValueTest.kt @@ -0,0 +1,72 @@ +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 ConfigParseVxlanAddressOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [VXLAN] + Group=239.1.1.1 + Group=ff02::42 + Local=192.168.1.1 + Local=fe80::1 + Local=dhcp4 + Local=ipv6_link_local + Local=slaac + Local=dhcp_pd + Remote=10.0.0.2 + Remote=2001:db8::1 + Remote=::ffff:10.0.0.2 + Group=FF05::2 + Group=224.0.0.1 + Group=239.255.255.255 + Remote=223.255.255.255 + Remote=fe80::1 + Local=::ffff:239.1.1.1 + Local=dhcp6 + """.trimIndent() + + setupFileInEditor("file.netdev", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [VXLAN] + Group=239.1.1 + Local=dhcp + Remote=example.com + Remote=10.0.0.256 + Local=192.168.1.1/24 + Group=fe80::1::2 + Group=dhcp4 + Remote=slaac + Group=192.168.1.1 + Group=240.0.0.1 + Group=ff::1 + Group=::ffff:239.1.1.1 + Remote=239.1.1.1 + Remote=ff02::1 + Local=239.1.1.1 + Local=ff02::1 + Local=DHCP4 + Local=Slaac + """.trimIndent() + + setupFileInEditor("file.netdev", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(18, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseWireguardAllowedIpsOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseWireguardAllowedIpsOptionValueTest.kt new file mode 100644 index 00000000..497fd9dc --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseWireguardAllowedIpsOptionValueTest.kt @@ -0,0 +1,55 @@ +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 ConfigParseWireguardAllowedIpsOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [WireGuardPeer] + AllowedIPs= + AllowedIPs=fd31:bf08:57cb::/48,192.168.26.3/24 + AllowedIPs=0.0.0.0/0 + AllowedIPs=::/0 + AllowedIPs=10.0.0.1 + AllowedIPs=fd8d:4d6d:3ccb:0500:900c:d437:ec27:8822/128 + AllowedIPs=10.0.0.0/8, 192.168.0.0/16 + AllowedIPs=10.0.0.0/8 fd00::/8, + AllowedIPs=10.0.0.0/0x8 fd00::/0100 + AllowedIPs=10.0.0.0/0b+1000 + AllowedIPs=10.0.0.0/0o10 + """.trimIndent() + + setupFileInEditor("file.netdev", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [WireGuardPeer] + AllowedIPs=10.0.0.0/33 + AllowedIPs=fd00::/129 + AllowedIPs=10.0.0.0/8;192.168.0.0/16 + AllowedIPs=any + AllowedIPs=10.0.0/8 + AllowedIPs=10.0.0.0/ + AllowedIPs=10.0.0.0/0b 1 + AllowedIPs=10.0.0.0/ 8 + """.trimIndent() + + setupFileInEditor("file.netdev", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(8, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseWireguardEndpointOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseWireguardEndpointOptionValueTest.kt new file mode 100644 index 00000000..fe53bfba --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseWireguardEndpointOptionValueTest.kt @@ -0,0 +1,61 @@ +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 ConfigParseWireguardEndpointOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [WireGuardPeer] + Endpoint=192.168.27.3:51820 + Endpoint=[2001:db8::1]:51820 + Endpoint=wg.example.com:51820 + Endpoint=@network.wireguard.peer0.endpoint + Endpoint=192.168.27.3 + Endpoint=fe80::1 + Endpoint=vpn.example.org.:0x1F90 + Endpoint=[fe80::1%eth0]:51820 + Endpoint=host:65535 + Endpoint=a\255b.example.com:51820 + Endpoint=:51820 + Endpoint=192.168.27.3:0b+1 + Endpoint=192.168.27.3:0b 1 + Endpoint=[2001:db8::1]:0o 7 + Endpoint=host.example:0b 1 + """.trimIndent() + + setupFileInEditor("file.netdev", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [WireGuardPeer] + Endpoint=wg.example.com + Endpoint=192.168.27.3:0 + Endpoint=192.168.27.3:65536 + Endpoint=wg..example.com:51820 + Endpoint=[2001:db8::1]: + Endpoint=wg.example.com:port + Endpoint=a\999b:51820 + Endpoint=192.168.27.3: 80 + Endpoint=host.example: 80 + Endpoint=192.168.27.3:0b -0 + """.trimIndent() + + setupFileInEditor("file.netdev", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(10, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseWireguardPeerKeyFileOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseWireguardPeerKeyFileOptionValueTest.kt new file mode 100644 index 00000000..65f151e7 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseWireguardPeerKeyFileOptionValueTest.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 ConfigParseWireguardPeerKeyFileOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [WireGuardPeer] + PublicKeyFile=/run/systemd/network/25-wireguard-public-key.txt + PresharedKeyFile=/etc/systemd/network/wg psk.key + PresharedKeyFile=/etc//systemd/./network/psk/ + PublicKeyFile=/devices/key + PublicKeyFile=/run/credentials/%i.key + """.trimIndent() + + setupFileInEditor("file.netdev", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [WireGuardPeer] + PublicKeyFile=relative/key + PresharedKeyFile=/etc/../root/psk + PublicKeyFile=/proc/self/environ + PresharedKeyFile=/dev/stdin + PublicKeyFile=%d/key + PresharedKeyFile=//sys/key + """.trimIndent() + + setupFileInEditor("file.netdev", 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/ConfigParseWireguardPeerRouteTableOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseWireguardPeerRouteTableOptionValueTest.kt new file mode 100644 index 00000000..bbc40d13 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseWireguardPeerRouteTableOptionValueTest.kt @@ -0,0 +1,63 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseWireguardPeerRouteTableOptionValueTest : AbstractUnitFileTest() { + + // Values with whitespace are possible table names (networkd.conf can define `my\ table:100`), so they are not + // flagged even though sd-check, which loads no networkd.conf, reports them. `0b 1`, `0b+1` and `0o 7` are + // safe_atou32 numbers (sd-check accepts them). + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [WireGuardPeer] + RouteTable=off + RouteTable=no + RouteTable=0 + RouteTable=false + RouteTable=OFF + RouteTable=No + RouteTable=fAlSe + RouteTable=F + RouteTable=1234 + RouteTable=main + RouteTable=local + RouteTable=default + RouteTable=0x10 + RouteTable=4294967295 + RouteTable=custom + RouteTable=vpn-table + RouteTable=main table + RouteTable=0b 1 + RouteTable=0b+1 + RouteTable=0o 7 + """.trimIndent() + + setupFileInEditor("file.netdev", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [WireGuardPeer] + RouteTable=4294967296 + RouteTable=08 + RouteTable=custom:100 + RouteTable=00 + """.trimIndent() + + setupFileInEditor("file.netdev", 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/ConfigParseWireguardPrivateKeyFileOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseWireguardPrivateKeyFileOptionValueTest.kt new file mode 100644 index 00000000..68830390 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseWireguardPrivateKeyFileOptionValueTest.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 ConfigParseWireguardPrivateKeyFileOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [WireGuard] + PrivateKeyFile=/run/systemd/network/25-wireguard-private-key.txt + PrivateKeyFile=/etc/wireguard/my key + PrivateKeyFile=/./devices/key + PrivateKeyFile=/.dev/key + """.trimIndent() + + setupFileInEditor("file.netdev", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [WireGuard] + PrivateKeyFile=wg.key + PrivateKeyFile=/proc/1/environ + PrivateKeyFile=/etc/wireguard/../../shadow + PrivateKeyFile=/./dev/shm/key + PrivateKeyFile=//sys/key + """.trimIndent() + + setupFileInEditor("file.netdev", 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/ConfigParseWireguardPrivateKeyOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseWireguardPrivateKeyOptionValueTest.kt new file mode 100644 index 00000000..85839cff --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseWireguardPrivateKeyOptionValueTest.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 ConfigParseWireguardPrivateKeyOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [WireGuard] + PrivateKey=EEGlnEPYJV//kbvvIqxKkQwOiS+UENyPncC4bF46ong= + PrivateKey=Eor9QBsIoUG6C3ZKsKdqm4Vkt0n2N7qpSh2LzphQeho= + PrivateKey=@network.wireguard.private + PrivateKey=EEGlnEPYJV__kbvvIqxKkQwOiS-UENyPncC4bF46ong= + PrivateKey=EEGlnEPYJV//kbvvIqxK kQwOiS+UENyPncC4bF46ong= + """.trimIndent() + + setupFileInEditor("file.netdev", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [WireGuard] + PrivateKey=EEGlnEPYJV//kbvvIqxKkQwOiS+UENyPncC4bF46ong + PrivateKey=EEGlnEPYJV//kbvvIqxKkQwOiS+UENyPncC4bF46onh= + PrivateKey=short= + PrivateKey=@bad/name + PrivateKey=EEGlnEPYJV//kbvvIqxKkQwOiS+UENyPncC4bF46ong== + """.trimIndent() + + setupFileInEditor("file.netdev", 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/ConfigParseWireguardRouteTableOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseWireguardRouteTableOptionValueTest.kt new file mode 100644 index 00000000..b8212f90 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseWireguardRouteTableOptionValueTest.kt @@ -0,0 +1,64 @@ +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 ConfigParseWireguardRouteTableOptionValueTest : AbstractUnitFileTest() { + + // Values with whitespace are possible table names (networkd.conf can define `my\ table:100`), so they are not + // flagged even though sd-check, which loads no networkd.conf, reports them. `0b 1`, `0b+1` and `0o 7` are + // safe_atou32 numbers (sd-check accepts them). + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [WireGuard] + RouteTable=off + RouteTable=no + RouteTable=0 + RouteTable=false + RouteTable=OFF + RouteTable=No + RouteTable=fAlSe + RouteTable=F + RouteTable=1234 + RouteTable=main + RouteTable=local + RouteTable=default + RouteTable=0x10 + RouteTable=4294967295 + RouteTable=custom + RouteTable=vpn-table + RouteTable=yes + RouteTable=main table + RouteTable=0b 1 + RouteTable=0b+1 + RouteTable=0o 7 + """.trimIndent() + + setupFileInEditor("file.netdev", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [WireGuard] + RouteTable=4294967296 + RouteTable=08 + RouteTable=custom:100 + RouteTable=00 + """.trimIndent() + + setupFileInEditor("file.netdev", 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/ConfigParseWolPasswordOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseWolPasswordOptionValueTest.kt new file mode 100644 index 00000000..78a342df --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseWolPasswordOptionValueTest.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 ConfigParseWolPasswordOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [Link] + WakeOnLanPassword=00:11:22:33:44:55 + WakeOnLanPassword=00-11-22-33-44-55 + WakeOnLanPassword=0011.2233.4455 + WakeOnLanPassword=/etc/systemd/network/wol.pass + WakeOnLanPassword=/run/credentials/wol pass + """.trimIndent() + + setupFileInEditor("file.link", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [Link] + WakeOnLanPassword=00:11:22:33:44 + WakeOnLanPassword=secret + WakeOnLanPassword=/etc/../wol.pass + WakeOnLanPassword=00:11:22:33:44:55:66:77 + """.trimIndent() + + setupFileInEditor("file.link", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(4, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/BatchNetdevLinkCombinatorsTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/BatchNetdevLinkCombinatorsTest.kt new file mode 100644 index 00000000..5148c1f5 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/BatchNetdevLinkCombinatorsTest.kt @@ -0,0 +1,153 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar + +import org.junit.Assert.assertEquals +import org.junit.Test + +/** + * Grammar-level tests for the shared helpers in BatchNetdevLinkCombinators.kt, run against BOTH matching + * engines: the classic SyntacticMatch/SemanticMatch pair and the list-of-successes [validate]. The cases + * come from systemd's own unit tests at 0f0682b773 (test-parse-util.c, test-time-util.c, + * test-cpu-set-util.c) where one exists, otherwise from sd-check. The integer and route-table terminals + * themselves are covered by SafeAtoTerminalTest and RouteTableTest. + */ +class BatchNetdevLinkCombinatorsTest { + + private fun classicAccepts(grammar: Combinator, value: String): Boolean = + grammar.SyntacticMatch(value, 0).matchResult == value.length && grammar.SemanticMatch(value, 0).matchResult == value.length + + private fun assertAccepts(grammar: Combinator, expected: Boolean, vararg values: String) { + val whole = SequenceCombinator(grammar, EOF()) + for (value in values) { + assertEquals("classic engine on '$value'", expected, classicAccepts(whole, value)) + assertEquals("parse engine on '$value'", expected, whole.validate(value) == ParseOutcome.Valid) + } + } + + @Test + fun testSiSizeMatchesParseSizeSiTests() { + // TEST(parse_size_si) successes. + assertAccepts(siSize(), true, "111", "111.4", "112 B", "112.6 B", "3.5 K", "3. K", "3.0 K", "4 M 11.5K", + "3.5G3B", "3.5G 4B", "4T3G3B", "4 T 3 G 3 B", "12P", "3E 2P", "1000E", "10.M", "+5K") + // TEST(parse_size_si) failures (bar 1000E, an overflow this grammar does not compute). + assertAccepts(siSize(), false, "3. 0 K", "3B3.5G", "3B3G4T", "12P12P", "12X", "12.5X", "12.5e3", "-1", "-1000E", + "-10B 20K", "1k", "1 2") + } + + @Test + fun testNsecDurationMatchesParseNsecTests() { + // TEST(parse_nsec) successes, minus the two "infinity" spellings this grammar deliberately excludes. + assertAccepts(NSEC_DURATION_FINITE, true, "5s", "5s500ms", "5s 500ms", "5.5s", "5.5s 0.5ms", ".22s", ".50y", + "2.5", ".7", "+3.1s", "3.1s.2", "3.1 .2s", "3.1 sec .2 sec", "3.1 sec 1.2 sec", "1.111111111111y", "100 ns", "1µs") + assertAccepts(NSEC_DURATION_FINITE, false, "xyz", ".", "5.", ".s", "-5s", "-0.3s", "0.-0s", "3.-0s", "3.+1s", + "3. 1s", "3.s", "12..34", "..1234", "1234..", "infinity") + } + + @Test + fun testCpuSetMatchesParseCpuSetTests() { + // TEST(parse_cpu_set) successes. + assertAccepts(CPU_SET, true, "0", "1 2 4", "0 1 2 3 8 9 10 11", "8 '9' 10 \"11\"", "0,1,2,3 8,9,10,11", + "0, 1, 2, 3, 4, 5, 6, 7, 63, ", "0-3,8-11", "36-39,44-47", "64-71", "0-3 8-11, ", "0-7 4-11", "0,2 4-11", + "8000-8191", "0x0-0x3", ",0") + // TEST(parse_cpu_set) failures (bar "3-0", a reversed range this grammar does not compare). + assertAccepts(CPU_SET, false, "0 1 2 3 garbage", "0-3 8-garbage", "0 1 2 3 \"4 5 6 7 ", "8192", "1-", "-1") + // EXTRACT_UNQUOTE removes quotes anywhere in a word, and without EXTRACT_CUNESCAPE a backslash just + // makes the next character literal. + assertAccepts(CPU_SET, true, "0-\"3\"", "'0'-'3'", "\"0\"-3, 4", "0-\\3", "\" 3\"", "1-\"0x3\"") + // The lower bound of a range is cut at the first `-`, so `-0` is an empty word there. + assertAccepts(CPU_SET, false, "-0", "-0-3", "0-\"9000\"", "\"0-3", "\"3 \"", "0-\"\"", "0-", "0- 3") + + // The bounds are SafeAtoTerminal numbers in a word split on whitespace and commas (sd-check, IRQAffinity=): + // a sign after 0b/0o is fine, but `0b 1` is two words and the lower bound of `0b-0` is the bare word `0b`. + assertAccepts(CPU_SET, true, "0b+1", "0-0b+1", "0-0b-0", "0o17", "\"0b 1\"") + assertAccepts(CPU_SET, false, "0b-0", "0b 1", "0-0b 1", "0b-1-3") + } + + @Test + fun testParseRangeAcceptsSplitsAtTheFirstDash() { + val lower = SafeAtoTerminal( + signed = false, + base = 0, + flags = emptySet(), + minInclusive = 0L, + maxInclusive = 99L, + acceptsWhitespace = true, + acceptsMinus = false, + ) + val upper = SafeAtoTerminal( + signed = false, + base = 0, + flags = emptySet(), + minInclusive = 0L, + maxInclusive = 99L, + acceptsWhitespace = true, + acceptsMinus = true, + ) + for (word in listOf("5", "5-7", "5--0", "0x5-+7", "0", " 5", "5- 7", "0b 1-0b+1")) { + assertEquals("parse_range accepts <$word>", true, netdevLinkParseRangeAccepts(word, lower, upper)) + } + for (word in listOf("", "-0", "-5", "5-", "5 -7", "100", "5-100", "5-0b-1", "0b-0")) { + assertEquals("parse_range refuses <$word>", false, netdevLinkParseRangeAccepts(word, lower, upper)) + } + } + + @Test + fun testExtractWordUnquotesAndUnescapes() { + assertEquals("FOO=bar baz", extractWord("\"FOO=bar baz\"", true)) + assertEquals("FOO=bar", extractWord("F\"OO\"=bar", true)) + assertEquals("FOOA=1", extractWord("FOO\\x41=1", true)) + assertEquals("a b", extractWord("a\\sb", true)) + assertEquals("a\u00e9", extractWord("a\\u00e9", true)) + assertEquals("a\u0001", extractWord("a\\001", true)) + assertEquals(null, extractWord("FOO\\q", true)) + assertEquals(null, extractWord("a\\x00", true)) + assertEquals(null, extractWord("a\\400", true)) + assertEquals(null, extractWord("a\\uD800", true)) + assertEquals(null, extractWord("\"abc", true)) + assertEquals(null, extractWord("abc\\", false)) + assertEquals("aq", extractWord("a\\q", false)) + } + + @Test + fun testKeyFilePathRefusesApiVfsAndDotDot() { + assertAccepts(NON_API_VFS_ABSOLUTE_PATH, true, "/etc/key", "/etc/my key", "/devices/x", "/sysroot/key", "/etc//a/./b/", + "/./devices/x", "/.dev/x", "/..dev/x") + assertAccepts(NON_API_VFS_ABSOLUTE_PATH, false, "key", "/dev", "/dev/null", "//sys/x", "/proc/1/environ", "/a/../b", "/a/..", + "/./dev/shm/key", "/.//./proc", "//./sys/", "/dev/") + } + + @Test + fun testWireguardKeyNeedsPaddingAndAValidLastCharacter() { + assertAccepts(WIREGUARD_BASE64_KEY, true, "EEGlnEPYJV//kbvvIqxKkQwOiS+UENyPncC4bF46ong=", + "EEGlnEPYJV__kbvvIqxKkQwOiS-UENyPncC4bF46ong=", "EEGlnEPYJV//kbvvIqxK kQwOiS+UENyPncC4bF46ong =") + assertAccepts(WIREGUARD_BASE64_KEY, false, "EEGlnEPYJV//kbvvIqxKkQwOiS+UENyPncC4bF46ong", + "EEGlnEPYJV//kbvvIqxKkQwOiS+UENyPncC4bF46onh=", "EEGlnEPYJV//kbvvIqxKkQwOiS+UENyPncC4bF46ong==", "AAAA") + } + + @Test + fun testSafePrefixLengthIsASafeAtou8WithinTheFamilyWidth() { + // sd-check (AllowedIPs=): `/0b+1000`, `/0o10` and `0.0.0.0/0b-0` are accepted; `/0b-1` is refused, and so are + // `/0b 1` and `/ 8`, which the list splits into two words. + assertAccepts(IP_ADDR_AND_SAFE_PREFIX, true, "10.0.0.0/8", "10.0.0.0/0x8", "10.0.0.0/0b+1000", "10.0.0.0/0o10", + "0.0.0.0/0b-0", "10.0.0.1", "fd00::/0x40", "::/128") + assertAccepts(IP_ADDR_AND_SAFE_PREFIX, false, "10.0.0.0/33", "10.0.0.0/0b-1", "10.0.0.0/0b 1", "10.0.0.0/ 8", + "fd00::/129", "10.0.0.0/") + } + + @Test + fun testCheckedSpanRejectsWhatItsCheckRefusesOnBothEngines() { + val multicast = NetdevLinkCheckedSpan(IP_ADDR, "multicast") { netdevLinkIsMulticast(it) } + assertAccepts(multicast, true, "224.0.0.1", "239.255.255.255", "ff02::1", "FF05::2") + assertAccepts(multicast, false, "223.255.255.255", "240.0.0.1", "ff::1", "::ffff:239.1.1.1", "10.0.0.1", "239.1.1") + } + + @Test + fun testParseSizeSiTotals() { + assertEquals(java.math.BigInteger("12000000000000000"), netdevLinkParseSizeSi("12P")) + assertEquals(java.math.BigInteger("3002000000000000000"), netdevLinkParseSizeSi("3E 2P")) + assertEquals(java.math.BigInteger("4011500"), netdevLinkParseSizeSi("4 M 11.5K")) + assertEquals(java.math.BigInteger("10000000"), netdevLinkParseSizeSi("10.M")) + assertEquals(java.math.BigInteger("111"), netdevLinkParseSizeSi("111.4")) + assertEquals(java.math.BigInteger("5000"), netdevLinkParseSizeSi("+5K")) + assertEquals(null, netdevLinkParseSizeSi("5k")) + } +}