From 8e472c90285dcbcb2d6af3d3d785431026f89a28 Mon Sep 17 00:00:00 2001 From: Steve Ramage Date: Fri, 23 Oct 2026 18:18:00 -0700 Subject: [PATCH 1/5] feat: add grammar validators for traffic-control qdisc/class options (qdisc batch) Adds grammars for 24 systemd-networkd tc parsers ([CAKE], [TokenBucketFilter], [NetworkEmulator], [HierarchyTokenBucket(Class)], [FairQueueing(ControlledDelay)], [GenericRandomEarlyDetection], [TrivialLinkEqualizer], [QuickFairQueueingClass], [PIE], [EnhancedTransmissionSelection], [DeficitRoundRobinSchedulerClass], [BFIFO]), each grounded on systemd 0f0682b773. New shared helpers in BatchQdiscCombinators.kt model the C leaf parsers more faithfully than the existing terminals: safe_atou* (base 0 incl. 0b/0o/+), parse_sec (compound forms like 1min30s), parse_size (multi-element, value-range checked) and parse_permyriad, plus a case-insensitive parse_boolean. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../semanticdata/optionvalues/AiGenerated.kt | 26 ++ .../ai/ConfigParseBfifoSizeOptionValue.kt | 29 ++ .../ai/ConfigParseCakeAckFilterOptionValue.kt | 30 ++ .../ai/ConfigParseCakeBandwidthOptionValue.kt | 29 ++ ...igParseCakeFlowIsolationModeOptionValue.kt | 28 ++ .../ai/ConfigParseDrrSizeOptionValue.kt | 30 ++ .../ai/ConfigParseEtsPrioOptionValue.kt | 40 ++ .../ai/ConfigParseEtsQuantaOptionValue.kt | 41 ++ .../ai/ConfigParseFqCodelBoolOptionValue.kt | 29 ++ .../ai/ConfigParseFqCodelU32OptionValue.kt | 31 ++ .../ai/ConfigParseFqCodelUsecOptionValue.kt | 35 ++ .../ai/ConfigParseFqMaxRateOptionValue.kt | 32 ++ .../ai/ConfigParseFqUsecOptionValue.kt | 32 ++ .../ai/ConfigParseGredBoolOptionValue.kt | 29 ++ .../ai/ConfigParseGredU32OptionValue.kt | 38 ++ .../ai/ConfigParseHtbClassRateOptionValue.kt | 30 ++ .../ai/ConfigParseHtbClassU32OptionValue.kt | 29 ++ .../ConfigParseHtbDefaultClassOptionValue.kt | 29 ++ .../ai/ConfigParseHtbU32OptionValue.kt | 29 ++ .../ai/ConfigParseNetemDelayOptionValue.kt | 33 ++ .../ai/ConfigParseNetemRateOptionValue.kt | 34 ++ .../ConfigParsePiePacketLimitOptionValue.kt | 30 ++ .../ai/ConfigParseQfqMaxPacketOptionValue.kt | 32 ++ .../ai/ConfigParseTbfRateOptionValue.kt | 32 ++ .../ai/ConfigParseTeqlIdOptionValue.kt | 31 ++ .../grammar/BatchQdiscCombinators.kt | 406 ++++++++++++++++++ .../ai/ConfigParseBfifoSizeOptionValueTest.kt | 52 +++ ...ConfigParseCakeAckFilterOptionValueTest.kt | 55 +++ ...ConfigParseCakeBandwidthOptionValueTest.kt | 53 +++ ...rseCakeFlowIsolationModeOptionValueTest.kt | 52 +++ .../ai/ConfigParseDrrSizeOptionValueTest.kt | 51 +++ .../ai/ConfigParseEtsPrioOptionValueTest.kt | 51 +++ .../ai/ConfigParseEtsQuantaOptionValueTest.kt | 54 +++ .../ConfigParseFqCodelBoolOptionValueTest.kt | 58 +++ .../ConfigParseFqCodelU32OptionValueTest.kt | 76 ++++ .../ConfigParseFqCodelUsecOptionValueTest.kt | 110 +++++ .../ai/ConfigParseFqMaxRateOptionValueTest.kt | 51 +++ .../ai/ConfigParseFqUsecOptionValueTest.kt | 51 +++ .../ai/ConfigParseGredBoolOptionValueTest.kt | 58 +++ .../ai/ConfigParseGredU32OptionValueTest.kt | 68 +++ .../ConfigParseHtbClassRateOptionValueTest.kt | 78 ++++ .../ConfigParseHtbClassU32OptionValueTest.kt | 57 +++ ...nfigParseHtbDefaultClassOptionValueTest.kt | 53 +++ .../ai/ConfigParseHtbU32OptionValueTest.kt | 57 +++ .../ConfigParseNetemDelayOptionValueTest.kt | 86 ++++ .../ai/ConfigParseNetemRateOptionValueTest.kt | 76 ++++ ...onfigParsePiePacketLimitOptionValueTest.kt | 57 +++ .../ConfigParseQfqMaxPacketOptionValueTest.kt | 55 +++ .../ai/ConfigParseTbfRateOptionValueTest.kt | 78 ++++ .../ai/ConfigParseTeqlIdOptionValueTest.kt | 49 +++ 50 files changed, 2680 insertions(+) create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseBfifoSizeOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseCakeAckFilterOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseCakeBandwidthOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseCakeFlowIsolationModeOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDrrSizeOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseEtsPrioOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseEtsQuantaOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseFqCodelBoolOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseFqCodelU32OptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseFqCodelUsecOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseFqMaxRateOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseFqUsecOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseGredBoolOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseGredU32OptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseHtbClassRateOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseHtbClassU32OptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseHtbDefaultClassOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseHtbU32OptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseNetemDelayOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseNetemRateOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParsePiePacketLimitOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseQfqMaxPacketOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseTbfRateOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseTeqlIdOptionValue.kt create mode 100644 src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/BatchQdiscCombinators.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseBfifoSizeOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseCakeAckFilterOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseCakeBandwidthOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseCakeFlowIsolationModeOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDrrSizeOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseEtsPrioOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseEtsQuantaOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseFqCodelBoolOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseFqCodelU32OptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseFqCodelUsecOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseFqMaxRateOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseFqUsecOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseGredBoolOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseGredU32OptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseHtbClassRateOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseHtbClassU32OptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseHtbDefaultClassOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseHtbU32OptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseNetemDelayOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseNetemRateOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParsePiePacketLimitOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseQfqMaxPacketOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseTbfRateOptionValueTest.kt create mode 100644 src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseTeqlIdOptionValueTest.kt diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/AiGenerated.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/AiGenerated.kt index 36704512..efd18b58 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,32 @@ 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: qdisc + Validator("config_parse_fq_codel_usec", "QDISC_KIND_FQ_CODEL") to ConfigParseFqCodelUsecOptionValue() as OptionValueInformation, + Validator("config_parse_tbf_rate", "QDISC_KIND_TBF") to ConfigParseTbfRateOptionValue() as OptionValueInformation, + Validator("config_parse_netem_rate", "QDISC_KIND_NETEM") to ConfigParseNetemRateOptionValue() as OptionValueInformation, + Validator("config_parse_netem_delay", "QDISC_KIND_NETEM") to ConfigParseNetemDelayOptionValue() as OptionValueInformation, + Validator("config_parse_htb_class_rate", "TCLASS_KIND_HTB") to ConfigParseHtbClassRateOptionValue() as OptionValueInformation, + Validator("config_parse_gred_u32", "QDISC_KIND_GRED") to ConfigParseGredU32OptionValue() as OptionValueInformation, + Validator("config_parse_fq_codel_u32", "QDISC_KIND_FQ_CODEL") to ConfigParseFqCodelU32OptionValue() as OptionValueInformation, + Validator("config_parse_teql_id", "QDISC_KIND_TEQL") to ConfigParseTeqlIdOptionValue() as OptionValueInformation, + Validator("config_parse_qfq_max_packet", "TCLASS_KIND_QFQ") to ConfigParseQfqMaxPacketOptionValue() as OptionValueInformation, + Validator("config_parse_pie_packet_limit", "QDISC_KIND_PIE") to ConfigParsePiePacketLimitOptionValue() as OptionValueInformation, + Validator("config_parse_htb_u32", "QDISC_KIND_HTB") to ConfigParseHtbU32OptionValue() as OptionValueInformation, + Validator("config_parse_htb_default_class", "QDISC_KIND_HTB") to ConfigParseHtbDefaultClassOptionValue() as OptionValueInformation, + Validator("config_parse_htb_class_u32", "TCLASS_KIND_HTB") to ConfigParseHtbClassU32OptionValue() as OptionValueInformation, + Validator("config_parse_gred_bool", "QDISC_KIND_GRED") to ConfigParseGredBoolOptionValue() as OptionValueInformation, + Validator("config_parse_fq_usec", "QDISC_KIND_FQ") to ConfigParseFqUsecOptionValue() as OptionValueInformation, + Validator("config_parse_fq_max_rate", "QDISC_KIND_FQ") to ConfigParseFqMaxRateOptionValue() as OptionValueInformation, + Validator("config_parse_fq_codel_bool", "QDISC_KIND_FQ_CODEL") to ConfigParseFqCodelBoolOptionValue() as OptionValueInformation, + Validator("config_parse_ets_quanta", "QDISC_KIND_ETS") to ConfigParseEtsQuantaOptionValue() as OptionValueInformation, + Validator("config_parse_ets_prio", "QDISC_KIND_ETS") to ConfigParseEtsPrioOptionValue() as OptionValueInformation, + Validator("config_parse_drr_size", "TCLASS_KIND_DRR") to ConfigParseDrrSizeOptionValue() as OptionValueInformation, + Validator("config_parse_cake_flow_isolation_mode", "QDISC_KIND_CAKE") to ConfigParseCakeFlowIsolationModeOptionValue() as OptionValueInformation, + Validator("config_parse_cake_bandwidth", "QDISC_KIND_CAKE") to ConfigParseCakeBandwidthOptionValue() as OptionValueInformation, + Validator("config_parse_cake_ack_filter", "QDISC_KIND_CAKE") to ConfigParseCakeAckFilterOptionValue() as OptionValueInformation, + Validator("config_parse_bfifo_size", "QDISC_KIND_BFIFO") to ConfigParseBfifoSizeOptionValue() as OptionValueInformation, + ) return allValidators diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseBfifoSizeOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseBfifoSizeOptionValue.kt new file mode 100644 index 00000000..b6c02e8e --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseBfifoSizeOptionValue.kt @@ -0,0 +1,29 @@ +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.* + +/* + * [BFIFO] LimitBytes= (.network) + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#LimitBytes= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/tc/fifo.c#L103-L160 config_parse_bfifo_size (QDISC_KIND_BFIFO) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L157-L283 parse_size + * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-parse-util.c#L108-L176 TEST(parse_size_iec) + * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-parse-util.c#L178-L246 TEST(parse_size_si) + * tests https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-qdisc-htb-fifo.network#L42 LimitBytes=1000000 + */ + +/** + * Validator for [BFIFO] LimitBytes=. + * + * C function: config_parse_bfifo_size (QDISC_KIND_BFIFO). An empty assignment resets the setting and is + * handled before this grammar runs. + * + * A byte size as parse_size(rvalue, 1024) reads it (see [parseSize]) that must not exceed UINT32_MAX: + * above it the parser logs "Invalid 'LimitBytes=', ignoring assignment". So `4G` is rejected. + */ +class ConfigParseBfifoSizeOptionValue : SimpleGrammarOptionValues( + "config_parse_bfifo_size", + SequenceCombinator(parseSize(1024, maxInclusive = SIZE_UINT32_MAX), EOF()), +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseCakeAckFilterOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseCakeAckFilterOptionValue.kt new file mode 100644 index 00000000..e20f1cf3 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseCakeAckFilterOptionValue.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.* + +/* + * [CAKE] AckFilter= (.network) + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#AckFilter= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/tc/cake.c#L680-L730 config_parse_cake_ack_filter (QDISC_KIND_CAKE) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/network/tc/cake.c#L672-L678 cake_ack_filter_table + DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/string-table.c#L32-L43 string_table_lookup_from_string_with_boolean + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L23-L46 parse_boolean (case-insensitive) + * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-parse-util.c#L13-L37 TEST(parse_boolean) — "YES", "TRUE", "ON" accepted + * tests https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-qdisc-cake.network#L25 AckFilter=aggressive + */ + +/** + * Validator for [CAKE] AckFilter=. + * + * C function: config_parse_cake_ack_filter (QDISC_KIND_CAKE). An empty assignment resets the setting and is + * handled before this grammar runs. + * + * A boolean (parse_boolean() is tried first, case-insensitively: `yes` -> filter, `no` -> off) or the + * table word `aggressive`, which is matched exactly (case-sensitive). + */ +class ConfigParseCakeAckFilterOptionValue : SimpleGrammarOptionValues( + "config_parse_cake_ack_filter", + SequenceCombinator(AlternativeCombinator(FlexibleLiteralChoiceTerminal("aggressive"), QDISC_BOOLEAN), EOF()), +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseCakeBandwidthOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseCakeBandwidthOptionValue.kt new file mode 100644 index 00000000..e6299ca0 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseCakeBandwidthOptionValue.kt @@ -0,0 +1,29 @@ +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.* + +/* + * [CAKE] Bandwidth= (.network) + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#Bandwidth= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/tc/cake.c#L141-L193 config_parse_cake_bandwidth (QDISC_KIND_CAKE) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L157-L283 parse_size + * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-parse-util.c#L108-L176 TEST(parse_size_iec) + * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-parse-util.c#L178-L246 TEST(parse_size_si) + * tests https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-qdisc-cake.network#L12 Bandwidth=500M + */ + +/** + * Validator for [CAKE] Bandwidth=. + * + * C function: config_parse_cake_bandwidth (QDISC_KIND_CAKE). An empty assignment resets the setting and is + * handled before this grammar runs. + * + * A rate in bits per second as parse_size(rvalue, 1000) reads it (see [parseSize]); the result is + * divided by 8 and stored as a uint64 with no further bound. + */ +class ConfigParseCakeBandwidthOptionValue : SimpleGrammarOptionValues( + "config_parse_cake_bandwidth", + SequenceCombinator(parseSize(1000), EOF()), +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseCakeFlowIsolationModeOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseCakeFlowIsolationModeOptionValue.kt new file mode 100644 index 00000000..ca8ecee8 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseCakeFlowIsolationModeOptionValue.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.* + +/* + * [CAKE] FlowIsolationMode= (.network) + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#FlowIsolationMode= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/tc/cake.c#L442-L492 config_parse_cake_flow_isolation_mode (QDISC_KIND_CAKE) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/network/tc/cake.c#L429-L438 cake_flow_isolation_mode_table + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/network/tc/cake.c#L440 DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING (exact, case-sensitive) + * tests https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-qdisc-cake.network#L18 FlowIsolationMode=dual-dst-host + */ + +/** + * Validator for [CAKE] FlowIsolationMode=. + * + * C function: config_parse_cake_flow_isolation_mode (QDISC_KIND_CAKE). An empty assignment resets the setting and is + * handled before this grammar runs. + * + * One of `none`, `src-host`, `dst-host`, `hosts`, `flows`, `dual-src-host`, `dual-dst-host`, `triple`, + * matched exactly (string_table_lookup is case-sensitive, and this table has no boolean fallback). + */ +class ConfigParseCakeFlowIsolationModeOptionValue : SimpleGrammarOptionValues( + "config_parse_cake_flow_isolation_mode", + SequenceCombinator(FlexibleLiteralChoiceTerminal("none", "src-host", "dst-host", "hosts", "flows", "dual-src-host", "dual-dst-host", "triple"), EOF()), +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDrrSizeOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDrrSizeOptionValue.kt new file mode 100644 index 00000000..cd52278d --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseDrrSizeOptionValue.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.* + +/* + * [DeficitRoundRobinSchedulerClass] QuantumBytes= (.network) + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#QuantumBytes= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/tc/drr.c#L45-L102 config_parse_drr_size (TCLASS_KIND_DRR) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L157-L283 parse_size + * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-parse-util.c#L108-L176 TEST(parse_size_iec) + * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-parse-util.c#L178-L246 TEST(parse_size_si) + * tests https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-qdisc-drr.network#L16 QuantumBytes=2000 + */ + +/** + * Validator for [DeficitRoundRobinSchedulerClass] QuantumBytes=. + * + * C function: config_parse_drr_size (TCLASS_KIND_DRR). An empty assignment resets the setting and is + * handled before this grammar runs. + * + * A byte size as parse_size(rvalue, 1024) reads it (see [parseSize]) that must not exceed UINT32_MAX: + * above it the parser logs "Invalid 'QuantumBytes=', ignoring assignment". So `4G` (4294967296) is + * rejected and `4095M` is accepted. + */ +class ConfigParseDrrSizeOptionValue : SimpleGrammarOptionValues( + "config_parse_drr_size", + SequenceCombinator(parseSize(1024, maxInclusive = SIZE_UINT32_MAX), EOF()), +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseEtsPrioOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseEtsPrioOptionValue.kt new file mode 100644 index 00000000..62cd5cf4 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseEtsPrioOptionValue.kt @@ -0,0 +1,40 @@ +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.* + +/* + * [EnhancedTransmissionSelection] PriorityMap= (.network) + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#PriorityMap= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/tc/ets.c#L227-L302 config_parse_ets_prio (QDISC_KIND_ETS) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L429-L475 safe_atou_full + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L403-L427 mangle_base + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.h#L45-L47 safe_atou8 + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/include/uapi/linux/pkt_sched.h#L28 TC_PRIO_MAX = 15 + * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-parse-util.c#L489 safe_atou16("+12345") accepted + * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-parse-util.c#L466-L470 0o6 / 0B101 prefixes (safe_atolli, same mangle_base) + * tests https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-qdisc-ets.network#L18-L21 PriorityMap=8 7 6 5 / PriorityMap= / PriorityMap=3 4 5 + */ + +private val ETS_BAND = safeAtou(256) + +/** + * Validator for [EnhancedTransmissionSelection] PriorityMap=. + * + * C function: config_parse_ets_prio (QDISC_KIND_ETS). An empty assignment resets the setting and is + * handled before this grammar runs. + * + * A whitespace-separated list of band numbers, each read with safe_atou8 (base 0, 0…255), at most + * TC_PRIO_MAX + 1 (16) entries per line. Out-of-range or unparsable entries are logged and skipped by + * the C, and so are flagged here; so is a 17th entry. + * + * Lenient: enhanced_transmission_selection_verify later rejects the whole section if any entry is not + * below the number of bands (at most 16), so values 16…255 can never actually work; they are accepted + * here because the man page documents 0…255 and that is what the parser itself checks. The 16-entry + * cap is cumulative across lines; only the per-line count is checked. + */ +class ConfigParseEtsPrioOptionValue : SimpleGrammarOptionValues( + "config_parse_ets_prio", + SequenceCombinator(ETS_BAND, Repeat(SequenceCombinator(WhitespaceTerminal(), ETS_BAND), 0, 15), EOF()), +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseEtsQuantaOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseEtsQuantaOptionValue.kt new file mode 100644 index 00000000..c6d3a587 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseEtsQuantaOptionValue.kt @@ -0,0 +1,41 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import java.math.BigInteger +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.* + +/* + * [EnhancedTransmissionSelection] QuantumBytes= (.network) + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#QuantumBytes= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/tc/ets.c#L144-L225 config_parse_ets_quanta (QDISC_KIND_ETS) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L157-L283 parse_size + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/include/uapi/linux/pkt_sched.h#L1210 TCQ_ETS_MAX_BANDS = 16 + * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-parse-util.c#L108-L176 TEST(parse_size_iec) + * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-parse-util.c#L178-L246 TEST(parse_size_si) + * tests https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-qdisc-ets.network#L14-L17 QuantumBytes=2 4 6 / QuantumBytes= / QuantumBytes=1001 1002 1003 + */ + +private val ETS_QUANTUM = parseSize(1024, minInclusive = BigInteger.ONE, maxInclusive = SIZE_UINT32_MAX, allowInnerWhitespace = false) + +/** + * Validator for [EnhancedTransmissionSelection] QuantumBytes=. + * + * C function: config_parse_ets_quanta (QDISC_KIND_ETS). An empty assignment resets the setting and is + * handled before this grammar runs. + * + * A whitespace-separated list (extract_first_word, no flags) of byte sizes, each read with + * parse_size(word, 1024) and required to be 1…4294967295; at most TCQ_ETS_MAX_BANDS (16) entries. A + * zero, oversized or unparsable entry is logged and skipped by the C ("ignoring assignment"), and so is + * flagged here; so is a 17th entry on one line. + * + * Because the list is split on whitespace first, each entry is a single word: `1K` or `1K512B` is one + * entry, but `1K 512B` is two. + * + * Lenient: the 16-entry cap is cumulative across repeated QuantumBytes= lines (until an empty + * assignment resets the list); only the per-line count is checked. + */ +class ConfigParseEtsQuantaOptionValue : SimpleGrammarOptionValues( + "config_parse_ets_quanta", + SequenceCombinator(ETS_QUANTUM, Repeat(SequenceCombinator(WhitespaceTerminal(), ETS_QUANTUM), 0, 15), EOF()), +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseFqCodelBoolOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseFqCodelBoolOptionValue.kt new file mode 100644 index 00000000..7ed1c3ba --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseFqCodelBoolOptionValue.kt @@ -0,0 +1,29 @@ +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.* + +/* + * [FairQueueingControlledDelay] ECN= (.network) + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#ECN= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/tc/fq-codel.c#L222-L265 config_parse_fq_codel_bool (QDISC_KIND_FQ_CODEL) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L48-L64 parse_tristate_full + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L23-L46 parse_boolean (STRCASE_IN_SET: case-insensitive) + * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-parse-util.c#L13-L37 TEST(parse_boolean) — "YES", "TRUE", "ON" accepted + * tests https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-qdisc-fq_codel.network#L18 ECN=yes + */ + +/** + * Validator for [FairQueueingControlledDelay] ECN=. + * + * C function: config_parse_fq_codel_bool (QDISC_KIND_FQ_CODEL). An empty assignment resets the setting and is + * handled before this grammar runs. + * + * A boolean via parse_tristate(): any parse_boolean() spelling, case-insensitively. Empty resets to + * "unset" (handled before the grammar). + */ +class ConfigParseFqCodelBoolOptionValue : SimpleGrammarOptionValues( + "config_parse_fq_codel_bool", + SequenceCombinator(QDISC_BOOLEAN, EOF()), +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseFqCodelU32OptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseFqCodelU32OptionValue.kt new file mode 100644 index 00000000..e36dda26 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseFqCodelU32OptionValue.kt @@ -0,0 +1,31 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.* + +/* + * [FairQueueingControlledDelay] PacketLimit=, Flows= (.network) + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#PacketLimit= + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#Flows= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/tc/fq-codel.c#L97-L155 config_parse_fq_codel_u32 (QDISC_KIND_FQ_CODEL) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L429-L475 safe_atou_full + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L403-L427 mangle_base + * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-parse-util.c#L489 safe_atou16("+12345") accepted + * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-parse-util.c#L466-L470 0o6 / 0B101 prefixes (safe_atolli, same mangle_base) + * tests https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-qdisc-fq_codel.network#L12-L14 PacketLimit=20480, Flows=2048 + */ + +/** + * Validator for [FairQueueingControlledDelay] PacketLimit=, Flows=. + * + * C function: config_parse_fq_codel_u32 (QDISC_KIND_FQ_CODEL). An empty assignment resets the setting and is + * handled before this grammar runs. + * + * An unsigned 32-bit integer read with safe_atou32 (base 0: decimal, `0x` hex, `0o`/leading-zero + * octal, `0b` binary, optional `+`), 0…4294967295. No further bound in the parser. + */ +class ConfigParseFqCodelU32OptionValue : SimpleGrammarOptionValues( + "config_parse_fq_codel_u32", + safeAtou32Value(), +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseFqCodelUsecOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseFqCodelUsecOptionValue.kt new file mode 100644 index 00000000..f097eca3 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseFqCodelUsecOptionValue.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.* + +/* + * [FairQueueingControlledDelay] TargetSec=, IntervalSec=, CEThresholdSec= (.network) + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#TargetSec= + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#IntervalSec= + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#CEThresholdSec= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/tc/fq-codel.c#L157-L220 config_parse_fq_codel_usec (QDISC_KIND_FQ_CODEL) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/time-util.c#L1289 parse_sec + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/time-util.c#L1198-L1287 parse_time + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/time-util.c#L1147-L1196 extract_multiplier + * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-time-util.c#L21-L84 TEST(parse_sec) + * tests https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-qdisc-fq_codel.network#L15-L19 TargetSec=10ms, IntervalSec=200ms, CEThresholdSec=100ms + */ + +/** + * Validator for [FairQueueingControlledDelay] TargetSec=, IntervalSec=, CEThresholdSec=. + * + * C function: config_parse_fq_codel_usec (QDISC_KIND_FQ_CODEL). An empty assignment resets the setting and is + * handled before this grammar runs. + * + * A time span as parse_sec() reads it: `infinity`, or one or more `number[unit]` elements such as + * `10ms`, `1min30s`, `5s 500ms`, `.5s`, `+3.1s` (see [parseSec]). There is no extra bound: the parsed + * usec_t is stored as is (CEThresholdSec= additionally treats an empty value as USEC_INFINITY). + * + * Lenient: values that overflow usec_t (parse_time returns ERANGE) are not range-checked. + */ +class ConfigParseFqCodelUsecOptionValue : SimpleGrammarOptionValues( + "config_parse_fq_codel_usec", + SequenceCombinator(parseSec(), EOF()), +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseFqMaxRateOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseFqMaxRateOptionValue.kt new file mode 100644 index 00000000..381bbb81 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseFqMaxRateOptionValue.kt @@ -0,0 +1,32 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import java.math.BigInteger +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.* + +/* + * [FairQueueing] MaximumRate= (.network) + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#MaximumRate= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/tc/fq.c#L344-L402 config_parse_fq_max_rate (QDISC_KIND_FQ) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L157-L283 parse_size + * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-parse-util.c#L108-L176 TEST(parse_size_iec) + * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-parse-util.c#L178-L246 TEST(parse_size_si) + * tests https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-qdisc-fq.network#L16 MaximumRate=1M + */ + +/** + * Validator for [FairQueueing] MaximumRate=. + * + * C function: config_parse_fq_max_rate (QDISC_KIND_FQ). An empty assignment resets the setting and is + * handled before this grammar runs. + * + * A rate in bits per second as parse_size(rvalue, 1000) reads it (see [parseSize]) whose byte rate + * (value / 8) must fit a uint32: above it the parser logs "Specified 'MaximumRate=' is too large, + * ignoring assignment". So the largest accepted value is 34359738367 bits/s (≈ 34.35G); `34G` is + * legal and `35G` is not. + */ +class ConfigParseFqMaxRateOptionValue : SimpleGrammarOptionValues( + "config_parse_fq_max_rate", + SequenceCombinator(parseSize(1000, maxInclusive = SIZE_UINT32_MAX * BigInteger.valueOf(8) + BigInteger.valueOf(7)), EOF()), +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseFqUsecOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseFqUsecOptionValue.kt new file mode 100644 index 00000000..fdb37288 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseFqUsecOptionValue.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.* + +/* + * [FairQueueing] CEThresholdSec= (.network) + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#CEThresholdSec= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/tc/fq.c#L284-L342 config_parse_fq_usec (QDISC_KIND_FQ) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/time-util.c#L1289 parse_sec + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/time-util.c#L1198-L1287 parse_time + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/time-util.c#L1147-L1196 extract_multiplier + * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-time-util.c#L21-L84 TEST(parse_sec) + * tests https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-qdisc-fq.network#L20 CEThresholdSec=100ms + */ + +/** + * Validator for [FairQueueing] CEThresholdSec=. + * + * C function: config_parse_fq_usec (QDISC_KIND_FQ). An empty assignment resets the setting and is + * handled before this grammar runs. + * + * A time span as parse_sec() reads it (see [parseSec]) that must not exceed UINT32_MAX microseconds + * (about 71min 34.967s): above it the parser logs "Specified 'CEThresholdSec=' is too large, ignoring + * assignment". In particular `infinity` (USEC_INFINITY) is rejected — leave the value empty to get the + * "unset/infinity" default. + */ +class ConfigParseFqUsecOptionValue : SimpleGrammarOptionValues( + "config_parse_fq_usec", + SequenceCombinator(parseSec(UINT32_MAX_USEC), EOF()), +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseGredBoolOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseGredBoolOptionValue.kt new file mode 100644 index 00000000..98b7483d --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseGredBoolOptionValue.kt @@ -0,0 +1,29 @@ +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.* + +/* + * [GenericRandomEarlyDetection] GenericRIO= (.network) + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#GenericRIO= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/tc/gred.c#L134-L177 config_parse_gred_bool (QDISC_KIND_GRED) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L48-L64 parse_tristate_full + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L23-L46 parse_boolean (STRCASE_IN_SET: case-insensitive) + * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-parse-util.c#L13-L37 TEST(parse_boolean) — "YES", "TRUE", "ON" accepted + * tests https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-qdisc-gred.network#L14 GenericRIO=yes + */ + +/** + * Validator for [GenericRandomEarlyDetection] GenericRIO=. + * + * C function: config_parse_gred_bool (QDISC_KIND_GRED). An empty assignment resets the setting and is + * handled before this grammar runs. + * + * A boolean via parse_tristate(): any parse_boolean() spelling (`yes`/`no`/`1`/`0`/`true`/`false`/ + * `on`/`off`/`y`/`n`/`t`/`f`), case-insensitively. Empty resets to "unset" (handled before the grammar). + */ +class ConfigParseGredBoolOptionValue : SimpleGrammarOptionValues( + "config_parse_gred_bool", + SequenceCombinator(QDISC_BOOLEAN, EOF()), +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseGredU32OptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseGredU32OptionValue.kt new file mode 100644 index 00000000..121ccab3 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseGredU32OptionValue.kt @@ -0,0 +1,38 @@ +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.* + +/* + * [GenericRandomEarlyDetection] VirtualQueues=, DefaultVirtualQueue= (.network) + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#VirtualQueues= + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#DefaultVirtualQueue= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/tc/gred.c#L68-L133 config_parse_gred_u32 (QDISC_KIND_GRED) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L429-L475 safe_atou_full + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L403-L427 mangle_base + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/include/uapi/linux/pkt_sched.h#L305 MAX_DPs = 16 + * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-parse-util.c#L489 safe_atou16("+12345") accepted + * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-parse-util.c#L466-L470 0o6 / 0B101 prefixes (safe_atolli, same mangle_base) + * tests https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-qdisc-gred.network#L12-L13 VirtualQueues=12, DefaultVirtualQueue=10 + */ + +/** + * Validator for [GenericRandomEarlyDetection] VirtualQueues=, DefaultVirtualQueue=. + * + * C function: config_parse_gred_u32 (QDISC_KIND_GRED). An empty assignment resets the setting and is + * handled before this grammar runs. + * + * An unsigned integer read with safe_atou32 (base 0: decimal, `0x` hex, `0o`/leading-zero octal, `0b` + * binary, optional `+`) that must not exceed MAX_DPs (16). Above 16 the parser logs "Invalid ..., ignoring + * assignment" (although, at 0f0682b773, it still stores the value — a C bug; the kernel refuses more than + * 16 DPs anyway), so it is flagged here. + * + * Lenient: both keys share the parser, so 0..16 is accepted for both, although VirtualQueues=0 and + * DefaultVirtualQueue=16 always fail generic_random_early_detection_verify (DefaultVirtualQueue= must + * be less than VirtualQueues=). The man page documents VirtualQueues= as 1…16. + */ +class ConfigParseGredU32OptionValue : SimpleGrammarOptionValues( + "config_parse_gred_u32", + SequenceCombinator(safeAtou(17), EOF()), +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseHtbClassRateOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseHtbClassRateOptionValue.kt new file mode 100644 index 00000000..4f027c87 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseHtbClassRateOptionValue.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.* + +/* + * [HierarchyTokenBucketClass] Rate=, CeilRate= (.network) + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#Rate= + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#CeilRate= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/tc/htb.c#L378-L436 config_parse_htb_class_rate (TCLASS_KIND_HTB) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L157-L283 parse_size + * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-parse-util.c#L108-L176 TEST(parse_size_iec) + * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-parse-util.c#L178-L246 TEST(parse_size_si) + * tests https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-qdisc-htb-fifo.network#L19-L20 Rate=1M, CeilRate=0.5M + */ + +/** + * Validator for [HierarchyTokenBucketClass] Rate=, CeilRate=. + * + * C function: config_parse_htb_class_rate (TCLASS_KIND_HTB). An empty assignment resets the setting and is + * handled before this grammar runs. + * + * A rate in bits per second as parse_size(rvalue, 1000) reads it (see [parseSize]): `1M`, `0.5M`, + * `1G 500M`, `125000`. The result is divided by 8 and stored as a uint64; no further bound. + */ +class ConfigParseHtbClassRateOptionValue : SimpleGrammarOptionValues( + "config_parse_htb_class_rate", + SequenceCombinator(parseSize(1000), EOF()), +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseHtbClassU32OptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseHtbClassU32OptionValue.kt new file mode 100644 index 00000000..77805dbc --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseHtbClassU32OptionValue.kt @@ -0,0 +1,29 @@ +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.* + +/* + * [HierarchyTokenBucketClass] Priority= (.network) + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#Priority= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/tc/htb.c#L242-L293 config_parse_htb_class_u32 (TCLASS_KIND_HTB) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L429-L475 safe_atou_full + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L403-L427 mangle_base + * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-parse-util.c#L489 safe_atou16("+12345") accepted + * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-parse-util.c#L466-L470 0o6 / 0B101 prefixes (safe_atolli, same mangle_base) + * tests https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-qdisc-htb-fifo.network#L18 Priority=1 + */ + +/** + * Validator for [HierarchyTokenBucketClass] Priority=. + * + * C function: config_parse_htb_class_u32 (TCLASS_KIND_HTB). An empty assignment resets the setting and is + * handled before this grammar runs. + * + * An unsigned 32-bit integer read with safe_atou32 (base 0), 0…4294967295. No further bound. + */ +class ConfigParseHtbClassU32OptionValue : SimpleGrammarOptionValues( + "config_parse_htb_class_u32", + safeAtou32Value(), +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseHtbDefaultClassOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseHtbDefaultClassOptionValue.kt new file mode 100644 index 00000000..282c4a73 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseHtbDefaultClassOptionValue.kt @@ -0,0 +1,29 @@ +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.* + +/* + * [HierarchyTokenBucket] DefaultClass= (.network) + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#DefaultClass= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/tc/htb.c#L48-L98 config_parse_htb_default_class (QDISC_KIND_HTB) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.h#L61-L64 safe_atou32_full(rvalue, 16, ..) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L429-L475 safe_atou_full (explicit base: mangle_base is skipped) + * tests https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-qdisc-htb-fifo.network#L12 DefaultClass=30 (i.e. 0x30) + */ + +/** + * Validator for [HierarchyTokenBucket] DefaultClass=. + * + * C function: config_parse_htb_default_class (QDISC_KIND_HTB). An empty assignment resets the setting and is + * handled before this grammar runs. + * + * The default class's minor id, always read as HEXADECIMAL (safe_atou32_full(.., 16)): `30` is 0x30, `0x30` + * is the same, `ff` is legal and `0b1`/`0o7` are not (with an explicit base, mangle_base does not run). + * Range 0…0xffffffff. + */ +class ConfigParseHtbDefaultClassOptionValue : SimpleGrammarOptionValues( + "config_parse_htb_default_class", + SequenceCombinator(safeAtou(UINT32_LIMIT, base = 16), EOF()), +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseHtbU32OptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseHtbU32OptionValue.kt new file mode 100644 index 00000000..d7d4c78e --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseHtbU32OptionValue.kt @@ -0,0 +1,29 @@ +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.* + +/* + * [HierarchyTokenBucket] RateToQuantum= (.network) + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#RateToQuantum= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/tc/htb.c#L100-L150 config_parse_htb_u32 (QDISC_KIND_HTB) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L429-L475 safe_atou_full + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L403-L427 mangle_base + * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-parse-util.c#L489 safe_atou16("+12345") accepted + * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-parse-util.c#L466-L470 0o6 / 0B101 prefixes (safe_atolli, same mangle_base) + * tests https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-qdisc-htb-fifo.network#L13 RateToQuantum=20 + */ + +/** + * Validator for [HierarchyTokenBucket] RateToQuantum=. + * + * C function: config_parse_htb_u32 (QDISC_KIND_HTB). An empty assignment resets the setting and is + * handled before this grammar runs. + * + * An unsigned 32-bit integer read with safe_atou32 (base 0), 0…4294967295. No further bound. + */ +class ConfigParseHtbU32OptionValue : SimpleGrammarOptionValues( + "config_parse_htb_u32", + safeAtou32Value(), +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseNetemDelayOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseNetemDelayOptionValue.kt new file mode 100644 index 00000000..c4a7f91c --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseNetemDelayOptionValue.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.* + +/* + * [NetworkEmulator] DelaySec=, DelayJitterSec= (.network) + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#DelaySec= + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#DelayJitterSec= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/tc/netem.c#L51-L110 config_parse_netem_delay (QDISC_KIND_NETEM) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/time-util.c#L1289 parse_sec + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/time-util.c#L1198-L1287 parse_time + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/time-util.c#L1147-L1196 extract_multiplier + * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-time-util.c#L21-L84 TEST(parse_sec) + * tests https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-qdisc-netem.network#L12-L13 DelaySec=50ms, DelayJitterSec=10ms + */ + +/** + * Validator for [NetworkEmulator] DelaySec=, DelayJitterSec=. + * + * C function: config_parse_netem_delay (QDISC_KIND_NETEM). An empty assignment resets the setting and is + * handled before this grammar runs. + * + * A time span as parse_sec() reads it (see [parseSec]). No further bound in the parser. + * + * Lenient: a delay too large for the kernel's tick conversion only fails later, in + * network_emulator_fill_message, and is not checked here. + */ +class ConfigParseNetemDelayOptionValue : SimpleGrammarOptionValues( + "config_parse_netem_delay", + SequenceCombinator(parseSec(), EOF()), +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseNetemRateOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseNetemRateOptionValue.kt new file mode 100644 index 00000000..def2f4a4 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseNetemRateOptionValue.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.* + +/* + * [NetworkEmulator] LossRate=, DuplicateRate= (.network) + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#LossRate= + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#DuplicateRate= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/tc/netem.c#L112-L170 config_parse_netem_rate (QDISC_KIND_NETEM) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/network/tc/tc-util.c#L63-L75 parse_tc_percent + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/percent-util.c#L135-L157 parse_permyriad / parse_permyriad_unbounded + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/percent-util.c#L57-L99 parse_parts_value_with_hundredths_place + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L497-L519 safe_atoi (base 0) + * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-percent-util.c#L87-L131 TEST(parse_permyriad) + * tests https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-qdisc-netem.network#L15 LossRate=20% + */ + +/** + * Validator for [NetworkEmulator] LossRate=, DuplicateRate=. + * + * C function: config_parse_netem_rate (QDISC_KIND_NETEM). An empty assignment resets the setting and is + * handled before this grammar runs. + * + * A percentage as parse_tc_percent() -> parse_permyriad() reads it: an integer followed by `%` (up to + * two decimals), `‰` (up to one decimal) or `‱` (no decimals), at most 100% (see [TC_PERCENT]). A + * bare number without a symbol is rejected. The integer part goes through safe_atoi (base 0), so + * `0x10%` is legal; that corner is accepted. + */ +class ConfigParseNetemRateOptionValue : SimpleGrammarOptionValues( + "config_parse_netem_rate", + SequenceCombinator(TC_PERCENT, EOF()), +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParsePiePacketLimitOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParsePiePacketLimitOptionValue.kt new file mode 100644 index 00000000..64e0f28c --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParsePiePacketLimitOptionValue.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.* + +/* + * [PIE] PacketLimit= (.network) + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#PacketLimit= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/tc/pie.c#L40-L90 config_parse_pie_packet_limit (QDISC_KIND_PIE) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L429-L475 safe_atou_full + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L403-L427 mangle_base + * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-parse-util.c#L489 safe_atou16("+12345") accepted + * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-parse-util.c#L466-L470 0o6 / 0B101 prefixes (safe_atolli, same mangle_base) + * tests https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-qdisc-pie.network#L12 PacketLimit=200000 + */ + +/** + * Validator for [PIE] PacketLimit=. + * + * C function: config_parse_pie_packet_limit (QDISC_KIND_PIE). An empty assignment resets the setting and is + * handled before this grammar runs. + * + * An unsigned 32-bit integer read with safe_atou32 (base 0), 0…4294967295. No further bound in the + * parser (the man page's "1…4294967294" is not enforced by it). + */ +class ConfigParsePiePacketLimitOptionValue : SimpleGrammarOptionValues( + "config_parse_pie_packet_limit", + safeAtou32Value(), +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseQfqMaxPacketOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseQfqMaxPacketOptionValue.kt new file mode 100644 index 00000000..5b8f40de --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseQfqMaxPacketOptionValue.kt @@ -0,0 +1,32 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import java.math.BigInteger +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.* + +/* + * [QuickFairQueueingClass] MaxPacketBytes= (.network) + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#MaxPacketBytes= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/tc/qfq.c#L115-L173 config_parse_qfq_max_packet (TCLASS_KIND_QFQ) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L157-L283 parse_size + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/network/tc/qfq.c#L14-L15 QFQ_MIN_MAX_PACKET 512, QFQ_MAX_MAX_PACKET 1<<16 + * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-parse-util.c#L108-L176 TEST(parse_size_iec) + * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-parse-util.c#L178-L246 TEST(parse_size_si) + * tests https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-qdisc-qfq.network#L17 MaxPacketBytes=16000 + */ + +/** + * Validator for [QuickFairQueueingClass] MaxPacketBytes=. + * + * C function: config_parse_qfq_max_packet (TCLASS_KIND_QFQ). An empty assignment resets the setting and is + * handled before this grammar runs. + * + * A byte size as parse_size(rvalue, 1024) reads it (IEC suffixes `K`/`M`/…, see [parseSize]) that + * must lie in 512…65536 bytes (QFQ_MIN_MAX_PACKET…QFQ_MAX_MAX_PACKET); outside it the parser logs + * "Invalid ..., ignoring assignment". So `64K` is accepted and `65K` or `511` are not. + */ +class ConfigParseQfqMaxPacketOptionValue : SimpleGrammarOptionValues( + "config_parse_qfq_max_packet", + SequenceCombinator(parseSize(1024, BigInteger.valueOf(512), BigInteger.valueOf(65_536)), EOF()), +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseTbfRateOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseTbfRateOptionValue.kt new file mode 100644 index 00000000..4a8bd014 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseTbfRateOptionValue.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.* + +/* + * [TokenBucketFilter] Rate=, PeakRate= (.network) + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#Rate= + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#PeakRate= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/tc/tbf.c#L184-L243 config_parse_tbf_rate (QDISC_KIND_TBF) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L157-L283 parse_size + * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-parse-util.c#L108-L176 TEST(parse_size_iec) + * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-parse-util.c#L178-L246 TEST(parse_size_si) + * tests https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-qdisc-tbf.network#L12-L16 Rate=1G, PeakRate=100G + */ + +/** + * Validator for [TokenBucketFilter] Rate=, PeakRate=. + * + * C function: config_parse_tbf_rate (QDISC_KIND_TBF). An empty assignment resets the setting and is + * handled before this grammar runs. + * + * A rate in bits per second as parse_size(rvalue, 1000) reads it: a number with an optional SI suffix + * `K`/`M`/`G`/`T`/`P`/`E` (upper case only, powers of 1000) or `B`, optionally several descending + * elements (`1G 500M`). The result is divided by 8 and stored as a uint64; there is no further bound. + * Note `10Mbit` / `10mbit` (tc(8) spellings) are NOT accepted by systemd. + */ +class ConfigParseTbfRateOptionValue : SimpleGrammarOptionValues( + "config_parse_tbf_rate", + SequenceCombinator(parseSize(1000), EOF()), +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseTeqlIdOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseTeqlIdOptionValue.kt new file mode 100644 index 00000000..6c71b9cf --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseTeqlIdOptionValue.kt @@ -0,0 +1,31 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.* + +/* + * [TrivialLinkEqualizer] Id= (.network) + * + * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#Id= + * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/tc/teql.c#L44-L100 config_parse_teql_id (QDISC_KIND_TEQL) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L429-L475 safe_atou_full + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L403-L427 mangle_base + * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-parse-util.c#L489 safe_atou16("+12345") accepted + * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-parse-util.c#L466-L470 0o6 / 0B101 prefixes (safe_atolli, same mangle_base) + * tests https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-qdisc-teql.network#L12 Id=1 + */ + +/** + * Validator for [TrivialLinkEqualizer] Id=. + * + * C function: config_parse_teql_id (QDISC_KIND_TEQL). An empty assignment resets the setting and is + * handled before this grammar runs. + * + * An unsigned integer read with safe_atou (base 0) that must not exceed INT_MAX: above it the parser + * logs "'Id=' is too large, ignoring assignment" (and, at 0f0682b773, still stores it — the qdisc kind + * then becomes e.g. "teql4294967295"), so 0…2147483647 is accepted. + */ +class ConfigParseTeqlIdOptionValue : SimpleGrammarOptionValues( + "config_parse_teql_id", + SequenceCombinator(safeAtou(2_147_483_648L), EOF()), +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/BatchQdiscCombinators.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/BatchQdiscCombinators.kt new file mode 100644 index 00000000..4e98eeb2 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/BatchQdiscCombinators.kt @@ -0,0 +1,406 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar + +import java.math.BigDecimal +import java.math.BigInteger + +/* + * Shared grammars for the systemd-networkd traffic-control validators (.network [QDisc]-family and + * [...Class] sections, systemd src/network/tc/), written for the 2026-10 qdisc batch. + * + * Everything here is pinned to systemd 0f0682b773 (systemd-build/build/last_commit_hash): + * https://github.com/systemd/systemd/blob/0f0682b773 + * + * The tc parsers lean on four leaf helpers, each modelled here as closely to the C as the combinator + * framework allows: + * + * - safe_atou / safe_atou32 / safe_atou8 / safe_atou32_full(.., 16) -> [SafeAtouTerminal] + * https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L429 + * - parse_sec / parse_time -> [parseSec] + * https://github.com/systemd/systemd/blob/0f0682b773/src/basic/time-util.c#L1198 + * - parse_size(.., 1000 | 1024) -> [parseSize] + * https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L157 + * - parse_permyriad (via parse_tc_percent) -> [TC_PERCENT] + * https://github.com/systemd/systemd/blob/0f0682b773/src/basic/percent-util.c#L149 + * + * Several of these are more faithful than the older shared terminals in Combinators.kt (TIME_VALUE + * rejects "1min30s"; ByteSizeTerminal rejects "1G 512M"; UnsignedNumberTerminal rejects "0b101" and + * "+5"; BOOLEAN is case-sensitive although parse_boolean is not). They live in this file only so the + * batch does not collide with parallel edits to Combinators.kt; see RECOMMENDATIONS.md for promoting + * them. + */ + +// --------------------------------------------------------------------------------------------------- +// Generic helpers + +private val OPT_WS = ZeroOrOne(WhitespaceTerminal()) + +/** + * Wraps [inner] and additionally requires the text it matched to satisfy [accept]. + * + * Used for value-level range checks that no single terminal can see — e.g. a parse_size() value + * written as several elements (`1G 512M`), where the bound applies to the sum. Matching (and so + * syntax highlighting and error localisation) is delegated to [inner] unchanged; only validity is + * narrowed. When [accept] returns null ("could not evaluate") the value is accepted: the grammar has + * already decided it is well-formed, and an evaluator gap must never flag a valid value. + * + * Empty matches are never accepted, so a grammar built only of optional parts (like [parseSize]'s + * chain of optional elements) cannot match the empty string. + */ +class ValueChecked(private val inner: Combinator, private val accept: (String) -> Boolean?) : Combinator { + + private fun ok(text: String): Boolean = accept(text) ?: true + + override fun SyntacticMatch(value: String, offset: Int): MatchResult { + val m = inner.SyntacticMatch(value, offset) + return if (m.matchResult == offset) NoMatch.copy(longestMatch = offset) else m + } + + override fun SemanticMatch(value: String, offset: Int): MatchResult { + val m = inner.SemanticMatch(value, offset) + if (m.matchResult == -1) return m + if (m.matchResult == offset) return NoMatch.copy(longestMatch = offset) + return if (ok(value.substring(offset, m.matchResult))) m else NoMatch.copy(longestMatch = offset) + } + + override fun parse(value: String, offset: Int): Sequence = + inner.parse(value, offset).map { step -> + when (step) { + is Stuck -> step + is Parse -> when { + step.end == offset -> Stuck(offset, setOf(this)) + ok(value.substring(offset, step.end)) -> step + // Out of range: keep the tokens (so the span still highlights) but mark them invalid. + else -> Parse(step.end, step.tokens.map { it.copy(valid = false) }, step.regions) + } + } + } + + override fun toStringIndented(indent: Int): String = " ".repeat(indent) + "Checked(\n" + inner.toStringIndented(indent + 1) + "\n" + " ".repeat(indent) + ")" + + override fun toString(): String = toStringIndented(0) +} + +/** + * The boolean spellings parse_boolean() accepts, matched case-insensitively as the C does + * (STRCASE_IN_SET, https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L23; + * test-parse-util.c asserts "YES", "TRUE", "ON", "N" … are accepted). + */ +val QDISC_BOOLEAN = FlexibleLiteralChoiceTerminal(*BOOLEAN_SPELLINGS, ignoreCase = true) + +// --------------------------------------------------------------------------------------------------- +// safe_atou* + +/** + * An unsigned integer as systemd's `safe_atou*_full()` reads it + * (https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L429-L475). + * + * With [base] 0 (`safe_atou`, `safe_atou8`, `safe_atou32`, …) that is: + * - decimal, C octal (`0755`) or `0x` hex — strtoul(base 0); + * - Python-style `0b101` / `0o17` — mangle_base(), parse-util.c#L403; + * - an optional leading `+` (strtoul accepts it; test-parse-util.c asserts `+12345`), but not in front + * of `0b`/`0o` (mangle_base only looks at the first character); + * - a leading `-` only when the value is zero (`-0`): safe_atou_full returns ERANGE for `l != 0 && + * s[0] == '-'`. + * + * With [base] 16 (`safe_atou32_full(.., 16)`) the digits are always hexadecimal, with an optional `0x`. + * + * The result must lie in `[minInclusive, maxExclusive)`. + */ +class SafeAtouTerminal( + private val minInclusive: Long, + private val maxExclusive: Long, + private val base: Int = 0, +) : TerminalCombinator { + + private val shape: Regex = when (base) { + 0 -> Regex("""[+-]?(?:0[xX][0-9a-fA-F]+|0[bBoO][0-9a-zA-Z]+|[0-9]+)""") + 16 -> Regex("""[+-]?(?:0[xX])?[0-9a-fA-F]+""") + else -> throw IllegalArgumentException("unsupported base $base") + } + + private fun span(value: String, offset: Int): String? = shape.matchAt(value, offset)?.value + + private fun valueOf(text: String): BigInteger? { + var s = text + val negative = s.startsWith("-") + val signed = negative || s.startsWith("+") + if (signed) s = s.substring(1) + val n: BigInteger? = try { + when { + base == 16 -> BigInteger(if (s.startsWith("0x") || s.startsWith("0X")) s.substring(2) else s, 16) + s.startsWith("0x") || s.startsWith("0X") -> BigInteger(s.substring(2), 16) + s.startsWith("0b") || s.startsWith("0B") -> if (signed) null else BigInteger(s.substring(2), 2) + s.startsWith("0o") || s.startsWith("0O") -> if (signed) null else BigInteger(s.substring(2), 8) + s.length > 1 && s.startsWith("0") -> BigInteger(s.substring(1), 8) + else -> BigInteger(s, 10) + } + } catch (e: NumberFormatException) { + null + } + if (n == null) return null + if (negative && n.signum() != 0) return null + return n + } + + private fun inRange(text: String): Boolean = + valueOf(text)?.let { it >= BigInteger.valueOf(minInclusive) && it < BigInteger.valueOf(maxExclusive) } ?: false + + override fun SyntacticMatch(value: String, offset: Int): MatchResult { + val text = span(value, offset) ?: return NoMatch + return MatchResult(listOf(text), offset + text.length, listOf(this), offset + text.length) + } + + override fun SemanticMatch(value: String, offset: Int): MatchResult { + val text = span(value, offset) ?: return NoMatch + if (!inRange(text)) return NoMatch.copy(longestMatch = offset) + return MatchResult(listOf(text), offset + text.length, listOf(this), offset + text.length) + } + + override fun parse(value: String, offset: Int): Sequence { + val text = span(value, offset) ?: return sequenceOf(Stuck(offset, setOf(this))) + return sequenceOf(Parse(offset + text.length, listOf(ParsedToken(offset, offset + text.length, text, this, inRange(text))))) + } + + override fun toString(): String = "SafeAtou($minInclusive,$maxExclusive,base=$base)" + + override fun toStringIndented(indent: Int): String = toString() +} + +/** One past UINT32_MAX, the exclusive bound for every `safe_atou32` value. */ +const val UINT32_LIMIT: Long = 4_294_967_296L + +/** One past UINT32_MAX as a BigInteger, for the size/time range checks. */ +private val UINT32_MAX_BIG: BigInteger = BigInteger.valueOf(UINT32_LIMIT - 1) + +/** ULLONG_MAX: parse_size() and parse_time() return ERANGE above it. */ +private val UINT64_MAX_BIG: BigInteger = BigInteger.ONE.shiftLeft(64).subtract(BigInteger.ONE) + +/** A [SafeAtouTerminal] coloured as a literal. */ +fun safeAtou(maxExclusive: Long, minInclusive: Long = 0L, base: Int = 0): Combinator = + Labeled(Role.LITERAL, SafeAtouTerminal(minInclusive, maxExclusive, base)) + +/** A whole value that is one safe_atou32() number. */ +fun safeAtou32Value(): Combinator = SequenceCombinator(safeAtou(UINT32_LIMIT), EOF()) + +// --------------------------------------------------------------------------------------------------- +// parse_sec / parse_time + +// extract_multiplier()'s table, in table order: the C takes the first entry the text starts with, +// and a regex alternation does the same (time-util.c#L1147-L1196). +private const val SEC_SUFFIX_ALTERNATION = + "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" + +private val SEC_SUFFIX = Labeled(Role.ENUM, RegexTerminal(SEC_SUFFIX_ALTERNATION, SEC_SUFFIX_ALTERNATION)) + +// strtoll() digits with an optional '+', then an optional '.digits'; or a bare '.digits' (".5s" is +// legal: strtoll consumes nothing, and the '.' branch takes over). "5." and "+.5" are not. +private const val SEC_NUMBER_RE = """\+?[0-9]+(?:\.[0-9]+)?|\.[0-9]+""" +private val SEC_NUMBER = Labeled(Role.LITERAL, RegexTerminal(SEC_NUMBER_RE, SEC_NUMBER_RE)) + +// After a number comes either a unit (optionally surrounded by whitespace), or whitespace, or the end. +// A unit may be followed directly by the next number ("5s500ms", "3.1s.2"); a bare number may not +// ("12.34.56" is EINVAL). +private val SEC_ELEMENT = SequenceCombinator( + SEC_NUMBER, + AlternativeCombinator( + SequenceCombinator(OPT_WS, SEC_SUFFIX, OPT_WS), + WhitespaceTerminal(), + EOF(), + ), +) + +private val PARSE_SEC = AlternativeCombinator( + LiteralChoiceTerminal("infinity"), + OneOrMore(SEC_ELEMENT), +) + +private val USEC_INFINITY: BigInteger = UINT64_MAX_BIG + +private val SEC_MULTIPLIERS: Map = run { + val sec = 1_000_000L + val minute = 60 * sec + val hour = 60 * minute + val day = 24 * hour + mapOf( + "seconds" to sec, "second" to sec, "sec" to sec, "s" to sec, + "minutes" to minute, "minute" to minute, "min" to minute, "m" to minute, + "months" to 2_629_800L * sec, "month" to 2_629_800L * sec, "M" to 2_629_800L * sec, + "msec" to 1000L, "ms" to 1000L, + "hours" to hour, "hour" to hour, "hr" to hour, "h" to hour, + "days" to day, "day" to day, "d" to day, + "weeks" to 7 * day, "week" to 7 * day, "w" to 7 * day, + "years" to 31_557_600L * sec, "year" to 31_557_600L * sec, "y" to 31_557_600L * sec, + "usec" to 1L, "us" to 1L, "μs" to 1L, "µs" to 1L, + ).mapValues { BigInteger.valueOf(it.value) } +} + +private val SEC_EVAL_ELEMENT = Regex("""\s*\+?([0-9]*)(?:\.([0-9]+))?\s*($SEC_SUFFIX_ALTERNATION)?""") + +/** parse_time(t, USEC_PER_SEC) as a value in microseconds, or null if [text] is not evaluable. */ +fun evaluateParseSec(text: String): BigInteger? { + val t = text.trim() + if (t == "infinity") return USEC_INFINITY + var pos = 0 + var total = BigInteger.ZERO + while (pos < t.length) { + while (pos < t.length && t[pos].isWhitespace()) pos++ + if (pos >= t.length) break + val m = SEC_EVAL_ELEMENT.matchAt(t, pos) ?: return null + if (m.value.isEmpty()) return null + pos += m.value.length + val multiplier = m.groupValues[3].takeIf { it.isNotEmpty() }?.let { SEC_MULTIPLIERS[it] } ?: SEC_MULTIPLIERS.getValue("s") + val whole = m.groupValues[1].ifEmpty { "0" } + total += BigInteger(whole) * multiplier + // The C adds the fractional digits one by one, each times a truncated multiplier/10^k. + var step = multiplier / BigInteger.TEN + for (digit in m.groupValues[2]) { + total += BigInteger.valueOf((digit - '0').toLong()) * step + step /= BigInteger.TEN + } + } + return total +} + +/** + * A whole parse_sec() value: `infinity`, or one or more `number[unit]` elements. + * + * Mirrors parse_time() (https://github.com/systemd/systemd/blob/0f0682b773/src/basic/time-util.c#L1198-L1287) + * including the forms its unit test asserts are legal (test-time-util.c#L21): `5s500ms`, `.22s`, + * `+3.1s`, `3.1s.2`, `3.1 .2`, `23µs`; and rejects what it asserts are not: `5.`, `.s`, `-5s`, + * `3.s`, `3. 1s`, `12.34.56`, `infinity .7`. + * + * @param maxUsec when set, the value (in microseconds, `infinity` counting as USEC_INFINITY) must not + * exceed it. + */ +fun parseSec(maxUsec: BigInteger? = null): Combinator = + ValueChecked(PARSE_SEC) { text -> + evaluateParseSec(text)?.let { it <= (maxUsec ?: USEC_INFINITY) } + } + +/** [maxUsec] for parsers that refuse anything above UINT32_MAX microseconds (and so `infinity`). */ +val UINT32_MAX_USEC: BigInteger = UINT32_MAX_BIG + +// --------------------------------------------------------------------------------------------------- +// parse_size + +// strtoull(base 10) digits with an optional '+', then an optional '.' with optional digits: "3." and +// "10.M" are legal (parse-util.c "Ignore failure. E.g. 10.M is valid"); ".5" is not. +private const val SIZE_NUMBER_RE = """\+?[0-9]+(?:\.[0-9]*)?""" +private val SIZE_NUMBER = Labeled(Role.LITERAL, RegexTerminal(SIZE_NUMBER_RE, SIZE_NUMBER_RE)) + +private val SIZE_SUFFIXES = listOf("E", "P", "T", "G", "M", "K", "B") + +private fun sizeChain(allowInnerWhitespace: Boolean): Combinator { + val parts = mutableListOf() + for (suffix in SIZE_SUFFIXES) { + val element = if (allowInnerWhitespace) { + SequenceCombinator(SIZE_NUMBER, OPT_WS, LiteralChoiceTerminal(suffix), OPT_WS) + } else { + SequenceCombinator(SIZE_NUMBER, LiteralChoiceTerminal(suffix)) + } + parts.add(ZeroOrOne(element)) + } + // The "" entry of the table: a bare number, which can only be the last element. + parts.add(ZeroOrOne(SIZE_NUMBER)) + return SequenceCombinator(*parts.toTypedArray()) +} + +private val SIZE_EVAL_ELEMENT = Regex("""\s*\+?([0-9]+)(?:\.([0-9]*))?\s*([EPTGMKB]?)\s*""") + +/** parse_size(text, base) as a byte count, or null if [text] is not evaluable. */ +fun evaluateParseSize(text: String, base: Int): BigInteger? { + val factorBase = BigInteger.valueOf(base.toLong()) + var pos = 0 + var total = BigInteger.ZERO + while (pos < text.length) { + val m = SIZE_EVAL_ELEMENT.matchAt(text, pos) ?: return null + if (m.value.isEmpty()) return null + pos += m.value.length + val power = when (m.groupValues[3]) { + "E" -> 6; "P" -> 5; "T" -> 4; "G" -> 3; "M" -> 2; "K" -> 1; else -> 0 + } + val factor = factorBase.pow(power) + total += BigInteger(m.groupValues[1]) * factor + val frac = m.groupValues[2] + if (frac.isNotEmpty()) total += BigDecimal("0.$frac").multiply(BigDecimal(factor)).toBigInteger() + } + return total +} + +/** + * A parse_size() value (https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L157-L283): + * one or more `number[.fraction][suffix]` elements whose suffixes strictly descend through + * `E P T G M K B ""` — `4M 11.5K`, `3.5G3B` and `4T3G3B` are legal; `3B3.5G`, `12P12P`, `12X`, `5k` + * (suffixes are upper-case only) and `-1` are not (test-parse-util.c#L108, #L178). A bare number (the + * `""` entry) must be the last element. + * + * @param base 1000 (SI, for rates) or 1024 (IEC, for sizes); only affects the range check. + * @param minInclusive / maxInclusive bounds on the resulting byte (or bit) count. The upper bound + * defaults to ULLONG_MAX, above which parse_size() itself returns ERANGE. + * @param allowInnerWhitespace false for an element of a whitespace-separated list (each word is + * parsed on its own, so a space would split the size into two words). + */ +fun parseSize( + base: Int, + minInclusive: BigInteger = BigInteger.ZERO, + maxInclusive: BigInteger = UINT64_MAX_BIG, + allowInnerWhitespace: Boolean = true, +): Combinator = + ValueChecked(sizeChain(allowInnerWhitespace)) { text -> + evaluateParseSize(text, base)?.let { it >= minInclusive && it <= maxInclusive } + } + +/** UINT32_MAX, the cap several tc size parsers apply after parse_size(). */ +val SIZE_UINT32_MAX: BigInteger = UINT32_MAX_BIG + +// --------------------------------------------------------------------------------------------------- +// parse_permyriad / parse_tc_percent + +// The integer part goes through safe_atoi() (base 0, mangle_base) — so `0x10%` is 16% — and must not +// be negative (`-0` is fine). Its magnitude is capped by the 100% check below, so an int-sized bound +// on the terminal itself is enough. +private val PCT_INT = safeAtou(Int.MAX_VALUE.toLong() + 1) + +/** + * A percentage as parse_permyriad() reads it + * (https://github.com/systemd/systemd/blob/0f0682b773/src/basic/percent-util.c#L135-L157): an integer + * followed by `%` (up to two decimals), `‰` (up to one decimal) or `‱` (no decimals), at most 100%. + * test-percent-util.c#L87 asserts `55.53%`, `555.5‰` and `1000‱` legal and `3.212%`, `3.22‰`, + * `3.2‱`, `107%`, `-7%` and a bare `50` illegal. + */ +val TC_PERCENT: Combinator = ValueChecked( + AlternativeCombinator( + SequenceCombinator(PCT_INT, ZeroOrOne(SequenceCombinator(DOT, RegexTerminal("[0-9]{1,2}", "[0-9]{1,2}"))), LiteralChoiceTerminal("%")), + SequenceCombinator(PCT_INT, ZeroOrOne(SequenceCombinator(DOT, RegexTerminal("[0-9]", "[0-9]"))), LiteralChoiceTerminal("‰")), + SequenceCombinator(PCT_INT, LiteralChoiceTerminal("‱")), + ), +) { text -> evaluatePermyriad(text)?.let { it in 0..10_000 } } + +private val PCT_EVAL = Regex("""(.*?)(?:\.([0-9]+))?([%‰‱])""") + +/** parse_permyriad_unbounded(text), or null if not evaluable (sign/base handled like safe_atoi). */ +fun evaluatePermyriad(text: String): Long? { + val m = PCT_EVAL.matchEntire(text) ?: return null + var intPart = m.groupValues[1] + val negative = intPart.startsWith("-") + if (negative || intPart.startsWith("+")) intPart = intPart.substring(1) + val n = try { + when { + intPart.startsWith("0x") || intPart.startsWith("0X") -> intPart.substring(2).toLong(16) + intPart.startsWith("0b") || intPart.startsWith("0B") -> intPart.substring(2).toLong(2) + intPart.startsWith("0o") || intPart.startsWith("0O") -> intPart.substring(2).toLong(8) + intPart.length > 1 && intPart.startsWith("0") -> intPart.substring(1).toLong(8) + else -> intPart.toLong() + } + } catch (e: NumberFormatException) { + return null + } + if (negative && n != 0L) return -1 + val frac = m.groupValues[2] + return when (m.groupValues[3]) { + "%" -> n * 100 + (frac.padEnd(2, '0').toLongOrNull() ?: 0) + "‰" -> n * 10 + (frac.toLongOrNull() ?: 0) + else -> n + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseBfifoSizeOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseBfifoSizeOptionValueTest.kt new file mode 100644 index 00000000..27920cc0 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseBfifoSizeOptionValueTest.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 ConfigParseBfifoSizeOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // Fixture values from systemd test/test-network/conf plus tricky spellings the C parser accepts. + // language="unit file (systemd)" + val file = """ + [BFIFO] + LimitBytes=1000000 + LimitBytes=1K + LimitBytes=1M + LimitBytes=1.5M + LimitBytes=4095M + LimitBytes=4294967295 + LimitBytes=3G 1023M + LimitBytes= + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [BFIFO] + LimitBytes=4G + LimitBytes=4294967296 + LimitBytes=1k + LimitBytes=-1 + LimitBytes=abc + LimitBytes=1M 1M + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(6, highlights) + } + +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseCakeAckFilterOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseCakeAckFilterOptionValueTest.kt new file mode 100644 index 00000000..cfd96ef5 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseCakeAckFilterOptionValueTest.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 ConfigParseCakeAckFilterOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // Fixture values from systemd test/test-network/conf plus tricky spellings the C parser accepts. + // language="unit file (systemd)" + val file = """ + [CAKE] + AckFilter=aggressive + AckFilter=yes + AckFilter=no + AckFilter=true + AckFilter=false + AckFilter=1 + AckFilter=0 + AckFilter=on + AckFilter=off + AckFilter=YES + AckFilter=No + AckFilter= + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [CAKE] + AckFilter=Aggressive + AckFilter=maybe + AckFilter=aggresive + AckFilter=2 + AckFilter=filter + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(5, highlights) + } + +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseCakeBandwidthOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseCakeBandwidthOptionValueTest.kt new file mode 100644 index 00000000..2dcedf88 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseCakeBandwidthOptionValueTest.kt @@ -0,0 +1,53 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseCakeBandwidthOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // Fixture values from systemd test/test-network/conf plus tricky spellings the C parser accepts. + // language="unit file (systemd)" + val file = """ + [CAKE] + Bandwidth=500M + Bandwidth=1G + Bandwidth=100K + Bandwidth=1.5M + Bandwidth=1G 500M + Bandwidth=100000 + Bandwidth= + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [CAKE] + Bandwidth=10Mbit + Bandwidth=1k + Bandwidth=-1M + Bandwidth=3B3.5G + Bandwidth=12P12P + Bandwidth=1M 1M + Bandwidth=abc + Bandwidth=.5M + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(8, highlights) + } + +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseCakeFlowIsolationModeOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseCakeFlowIsolationModeOptionValueTest.kt new file mode 100644 index 00000000..a97157ad --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseCakeFlowIsolationModeOptionValueTest.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 ConfigParseCakeFlowIsolationModeOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // Fixture values from systemd test/test-network/conf plus tricky spellings the C parser accepts. + // language="unit file (systemd)" + val file = """ + [CAKE] + FlowIsolationMode=none + FlowIsolationMode=src-host + FlowIsolationMode=dst-host + FlowIsolationMode=hosts + FlowIsolationMode=flows + FlowIsolationMode=dual-src-host + FlowIsolationMode=dual-dst-host + FlowIsolationMode=triple + FlowIsolationMode= + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [CAKE] + FlowIsolationMode=None + FlowIsolationMode=dual + FlowIsolationMode=triple-isolate + FlowIsolationMode=src_host + FlowIsolationMode=yes + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(5, highlights) + } + +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDrrSizeOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDrrSizeOptionValueTest.kt new file mode 100644 index 00000000..21931169 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseDrrSizeOptionValueTest.kt @@ -0,0 +1,51 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseDrrSizeOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // Fixture values from systemd test/test-network/conf plus tricky spellings the C parser accepts. + // language="unit file (systemd)" + val file = """ + [DeficitRoundRobinSchedulerClass] + QuantumBytes=2000 + QuantumBytes=1K + QuantumBytes=1.5K + QuantumBytes=4095M + QuantumBytes=4294967295 + QuantumBytes=3G 1023M + QuantumBytes= + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [DeficitRoundRobinSchedulerClass] + QuantumBytes=4G + QuantumBytes=4294967296 + QuantumBytes=1k + QuantumBytes=-1 + QuantumBytes=abc + QuantumBytes=1K 1K + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(6, highlights) + } + +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseEtsPrioOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseEtsPrioOptionValueTest.kt new file mode 100644 index 00000000..0adc257e --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseEtsPrioOptionValueTest.kt @@ -0,0 +1,51 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseEtsPrioOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // Fixture values from systemd test/test-network/conf plus tricky spellings the C parser accepts. + // language="unit file (systemd)" + val file = """ + [EnhancedTransmissionSelection] + PriorityMap=8 7 6 5 + PriorityMap=3 4 5 + PriorityMap=6 7 + PriorityMap=0 + PriorityMap=255 + PriorityMap=0x0f 017 0b1 + PriorityMap=0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + PriorityMap= + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [EnhancedTransmissionSelection] + PriorityMap=256 + PriorityMap=-1 + PriorityMap=1,2 + PriorityMap=a b + PriorityMap=0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 0 + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(5, highlights) + } + +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseEtsQuantaOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseEtsQuantaOptionValueTest.kt new file mode 100644 index 00000000..e836feb7 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseEtsQuantaOptionValueTest.kt @@ -0,0 +1,54 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseEtsQuantaOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // Fixture values from systemd test/test-network/conf plus tricky spellings the C parser accepts. + // language="unit file (systemd)" + val file = """ + [EnhancedTransmissionSelection] + QuantumBytes=2 4 6 + QuantumBytes=1001 1002 1003 + QuantumBytes=1004 1005 + QuantumBytes=1K + QuantumBytes=1K512B + QuantumBytes=1.5K 2M + QuantumBytes=4294967295 + QuantumBytes=1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 + QuantumBytes= + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [EnhancedTransmissionSelection] + QuantumBytes=0 + QuantumBytes=4G + QuantumBytes=1 0 2 + QuantumBytes=1K 512 B + QuantumBytes=abc + QuantumBytes=1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 + QuantumBytes=1k + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(7, highlights) + } + +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseFqCodelBoolOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseFqCodelBoolOptionValueTest.kt new file mode 100644 index 00000000..36c35211 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseFqCodelBoolOptionValueTest.kt @@ -0,0 +1,58 @@ +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 ConfigParseFqCodelBoolOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // Fixture values from systemd test/test-network/conf plus tricky spellings the C parser accepts. + // language="unit file (systemd)" + val file = """ + [FairQueueingControlledDelay] + ECN=yes + ECN=no + ECN=1 + ECN=0 + ECN=true + ECN=false + ECN=on + ECN=off + ECN=y + ECN=n + ECN=t + ECN=f + ECN=YES + ECN=False + ECN=On + ECN= + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [FairQueueingControlledDelay] + ECN=maybe + ECN=2 + ECN=enable + ECN=nope + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(4, highlights) + } + +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseFqCodelU32OptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseFqCodelU32OptionValueTest.kt new file mode 100644 index 00000000..c4b622b4 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseFqCodelU32OptionValueTest.kt @@ -0,0 +1,76 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseFqCodelU32OptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // Fixture values from systemd test/test-network/conf plus tricky spellings the C parser accepts. + // language="unit file (systemd)" + val file = """ + [FairQueueingControlledDelay] + PacketLimit=0 + Flows=0 + PacketLimit=1 + Flows=1 + PacketLimit=20480 + Flows=20480 + PacketLimit=4294967295 + Flows=4294967295 + PacketLimit=0x10 + Flows=0x10 + PacketLimit=0o17 + Flows=0o17 + PacketLimit=0b101 + Flows=0b101 + PacketLimit=017 + Flows=017 + PacketLimit=+5 + Flows=+5 + PacketLimit=-0 + Flows=-0 + PacketLimit= + Flows= + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [FairQueueingControlledDelay] + PacketLimit=4294967296 + Flows=4294967296 + PacketLimit=-1 + Flows=-1 + PacketLimit=08 + Flows=08 + PacketLimit=0x + Flows=0x + PacketLimit=1.5 + Flows=1.5 + PacketLimit=abc + Flows=abc + PacketLimit=+0b1 + Flows=+0b1 + PacketLimit=12 13 + Flows=12 13 + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(16, highlights) + } + +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseFqCodelUsecOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseFqCodelUsecOptionValueTest.kt new file mode 100644 index 00000000..04c21018 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseFqCodelUsecOptionValueTest.kt @@ -0,0 +1,110 @@ +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 ConfigParseFqCodelUsecOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // Fixture values from systemd test/test-network/conf plus tricky spellings the C parser accepts. + // language="unit file (systemd)" + val file = """ + [FairQueueingControlledDelay] + TargetSec=10ms + IntervalSec=10ms + CEThresholdSec=10ms + TargetSec=200ms + IntervalSec=200ms + CEThresholdSec=200ms + TargetSec=1min30s + IntervalSec=1min30s + CEThresholdSec=1min30s + TargetSec=5s 500ms + IntervalSec=5s 500ms + CEThresholdSec=5s 500ms + TargetSec=5s500ms + IntervalSec=5s500ms + CEThresholdSec=5s500ms + TargetSec=.5s + IntervalSec=.5s + CEThresholdSec=.5s + TargetSec=+3.1s + IntervalSec=+3.1s + CEThresholdSec=+3.1s + TargetSec=3.1s.2 + IntervalSec=3.1s.2 + CEThresholdSec=3.1s.2 + TargetSec=3.1 .2 + IntervalSec=3.1 .2 + CEThresholdSec=3.1 .2 + TargetSec=1.5h + IntervalSec=1.5h + CEThresholdSec=1.5h + TargetSec=23us + IntervalSec=23us + CEThresholdSec=23us + TargetSec=23µs + IntervalSec=23µs + CEThresholdSec=23µs + TargetSec=2 weeks + IntervalSec=2 weeks + CEThresholdSec=2 weeks + TargetSec=100 + IntervalSec=100 + CEThresholdSec=100 + TargetSec=infinity + IntervalSec=infinity + CEThresholdSec=infinity + TargetSec= + IntervalSec= + CEThresholdSec= + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [FairQueueingControlledDelay] + TargetSec=5. + IntervalSec=5. + CEThresholdSec=5. + TargetSec=-5s + IntervalSec=-5s + CEThresholdSec=-5s + TargetSec=3.s + IntervalSec=3.s + CEThresholdSec=3.s + TargetSec=12.34.56 + IntervalSec=12.34.56 + CEThresholdSec=12.34.56 + TargetSec=5mins + IntervalSec=5mins + CEThresholdSec=5mins + TargetSec=10 foo + IntervalSec=10 foo + CEThresholdSec=10 foo + TargetSec=infinity 5s + IntervalSec=infinity 5s + CEThresholdSec=infinity 5s + TargetSec=Infinity + IntervalSec=Infinity + CEThresholdSec=Infinity + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(24, highlights) + } + +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseFqMaxRateOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseFqMaxRateOptionValueTest.kt new file mode 100644 index 00000000..dc9792fe --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseFqMaxRateOptionValueTest.kt @@ -0,0 +1,51 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseFqMaxRateOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // Fixture values from systemd test/test-network/conf plus tricky spellings the C parser accepts. + // language="unit file (systemd)" + val file = """ + [FairQueueing] + MaximumRate=1M + MaximumRate=0.5G + MaximumRate=34G + MaximumRate=34359738367 + MaximumRate=34359738367B + MaximumRate=1G 500M + MaximumRate= + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [FairQueueing] + MaximumRate=34359738368 + MaximumRate=35G + MaximumRate=1T + MaximumRate=10mbit + MaximumRate=-1M + MaximumRate=1M 2G + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(6, highlights) + } + +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseFqUsecOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseFqUsecOptionValueTest.kt new file mode 100644 index 00000000..818697c5 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseFqUsecOptionValueTest.kt @@ -0,0 +1,51 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseFqUsecOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // Fixture values from systemd test/test-network/conf plus tricky spellings the C parser accepts. + // language="unit file (systemd)" + val file = """ + [FairQueueing] + CEThresholdSec=100ms + CEThresholdSec=1s + CEThresholdSec=4294967295us + CEThresholdSec=71min 34s + CEThresholdSec=1h 11min 34.967295s + CEThresholdSec=0 + CEThresholdSec= + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [FairQueueing] + CEThresholdSec=4294967296us + CEThresholdSec=72min + CEThresholdSec=infinity + CEThresholdSec=1h 12min + CEThresholdSec=5x + CEThresholdSec=-1s + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(6, highlights) + } + +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseGredBoolOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseGredBoolOptionValueTest.kt new file mode 100644 index 00000000..363f3137 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseGredBoolOptionValueTest.kt @@ -0,0 +1,58 @@ +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 ConfigParseGredBoolOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // Fixture values from systemd test/test-network/conf plus tricky spellings the C parser accepts. + // language="unit file (systemd)" + val file = """ + [GenericRandomEarlyDetection] + GenericRIO=yes + GenericRIO=no + GenericRIO=1 + GenericRIO=0 + GenericRIO=true + GenericRIO=false + GenericRIO=on + GenericRIO=off + GenericRIO=y + GenericRIO=n + GenericRIO=t + GenericRIO=f + GenericRIO=YES + GenericRIO=False + GenericRIO=On + GenericRIO= + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [GenericRandomEarlyDetection] + GenericRIO=maybe + GenericRIO=2 + GenericRIO=enable + GenericRIO=nope + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(4, highlights) + } + +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseGredU32OptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseGredU32OptionValueTest.kt new file mode 100644 index 00000000..663fb67b --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseGredU32OptionValueTest.kt @@ -0,0 +1,68 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseGredU32OptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // Fixture values from systemd test/test-network/conf plus tricky spellings the C parser accepts. + // language="unit file (systemd)" + val file = """ + [GenericRandomEarlyDetection] + VirtualQueues=12 + DefaultVirtualQueue=12 + VirtualQueues=10 + DefaultVirtualQueue=10 + VirtualQueues=0 + DefaultVirtualQueue=0 + VirtualQueues=16 + DefaultVirtualQueue=16 + VirtualQueues=0x10 + DefaultVirtualQueue=0x10 + VirtualQueues=0b1 + DefaultVirtualQueue=0b1 + VirtualQueues=020 + DefaultVirtualQueue=020 + VirtualQueues=+3 + DefaultVirtualQueue=+3 + VirtualQueues= + DefaultVirtualQueue= + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [GenericRandomEarlyDetection] + VirtualQueues=17 + DefaultVirtualQueue=17 + VirtualQueues=0x11 + DefaultVirtualQueue=0x11 + VirtualQueues=-1 + DefaultVirtualQueue=-1 + VirtualQueues=abc + DefaultVirtualQueue=abc + VirtualQueues=4294967296 + DefaultVirtualQueue=4294967296 + VirtualQueues=1.0 + DefaultVirtualQueue=1.0 + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(12, highlights) + } + +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseHtbClassRateOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseHtbClassRateOptionValueTest.kt new file mode 100644 index 00000000..a4f3475b --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseHtbClassRateOptionValueTest.kt @@ -0,0 +1,78 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseHtbClassRateOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // Fixture values from systemd test/test-network/conf plus tricky spellings the C parser accepts. + // language="unit file (systemd)" + val file = """ + [HierarchyTokenBucketClass] + Rate=1M + CeilRate=1M + Rate=0.5M + CeilRate=0.5M + Rate=1G + CeilRate=1G + Rate=100G + CeilRate=100G + Rate=1G 500M + CeilRate=1G 500M + Rate=3.5G3B + CeilRate=3.5G3B + Rate=4T3G3B + CeilRate=4T3G3B + Rate=10.M + CeilRate=10.M + Rate=125000 + CeilRate=125000 + Rate=+5K + CeilRate=+5K + Rate=1000 B + CeilRate=1000 B + Rate= + CeilRate= + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [HierarchyTokenBucketClass] + Rate=10Mbit + CeilRate=10Mbit + Rate=1k + CeilRate=1k + Rate=-1M + CeilRate=-1M + Rate=3B3.5G + CeilRate=3B3.5G + Rate=12P12P + CeilRate=12P12P + Rate=1M 1M + CeilRate=1M 1M + Rate=abc + CeilRate=abc + Rate=.5M + CeilRate=.5M + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(16, highlights) + } + +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseHtbClassU32OptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseHtbClassU32OptionValueTest.kt new file mode 100644 index 00000000..57e501ac --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseHtbClassU32OptionValueTest.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 ConfigParseHtbClassU32OptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // Fixture values from systemd test/test-network/conf plus tricky spellings the C parser accepts. + // language="unit file (systemd)" + val file = """ + [HierarchyTokenBucketClass] + Priority=0 + Priority=1 + Priority=20480 + Priority=4294967295 + Priority=0x10 + Priority=0o17 + Priority=0b101 + Priority=017 + Priority=+5 + Priority=-0 + Priority= + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [HierarchyTokenBucketClass] + Priority=4294967296 + Priority=-1 + Priority=08 + Priority=0x + Priority=1.5 + Priority=abc + Priority=+0b1 + Priority=12 13 + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(8, highlights) + } + +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseHtbDefaultClassOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseHtbDefaultClassOptionValueTest.kt new file mode 100644 index 00000000..ee8804d3 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseHtbDefaultClassOptionValueTest.kt @@ -0,0 +1,53 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseHtbDefaultClassOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // Fixture values from systemd test/test-network/conf plus tricky spellings the C parser accepts. + // language="unit file (systemd)" + val file = """ + [HierarchyTokenBucket] + DefaultClass=30 + DefaultClass=0x30 + DefaultClass=ff + DefaultClass=FFFF + DefaultClass=ffffffff + DefaultClass=0 + DefaultClass=+a + DefaultClass=0b1 + DefaultClass= + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [HierarchyTokenBucket] + DefaultClass=100000000 + DefaultClass=0x100000000 + DefaultClass=0o7 + DefaultClass=g + DefaultClass=-1 + DefaultClass=1 2 + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(6, highlights) + } + +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseHtbU32OptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseHtbU32OptionValueTest.kt new file mode 100644 index 00000000..12ee3c24 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseHtbU32OptionValueTest.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 ConfigParseHtbU32OptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // Fixture values from systemd test/test-network/conf plus tricky spellings the C parser accepts. + // language="unit file (systemd)" + val file = """ + [HierarchyTokenBucket] + RateToQuantum=0 + RateToQuantum=1 + RateToQuantum=20480 + RateToQuantum=4294967295 + RateToQuantum=0x10 + RateToQuantum=0o17 + RateToQuantum=0b101 + RateToQuantum=017 + RateToQuantum=+5 + RateToQuantum=-0 + RateToQuantum= + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [HierarchyTokenBucket] + RateToQuantum=4294967296 + RateToQuantum=-1 + RateToQuantum=08 + RateToQuantum=0x + RateToQuantum=1.5 + RateToQuantum=abc + RateToQuantum=+0b1 + RateToQuantum=12 13 + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(8, highlights) + } + +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseNetemDelayOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseNetemDelayOptionValueTest.kt new file mode 100644 index 00000000..37b91e32 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseNetemDelayOptionValueTest.kt @@ -0,0 +1,86 @@ +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 ConfigParseNetemDelayOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // Fixture values from systemd test/test-network/conf plus tricky spellings the C parser accepts. + // language="unit file (systemd)" + val file = """ + [NetworkEmulator] + DelaySec=10ms + DelayJitterSec=10ms + DelaySec=200ms + DelayJitterSec=200ms + DelaySec=1min30s + DelayJitterSec=1min30s + DelaySec=5s 500ms + DelayJitterSec=5s 500ms + DelaySec=5s500ms + DelayJitterSec=5s500ms + DelaySec=.5s + DelayJitterSec=.5s + DelaySec=+3.1s + DelayJitterSec=+3.1s + DelaySec=3.1s.2 + DelayJitterSec=3.1s.2 + DelaySec=3.1 .2 + DelayJitterSec=3.1 .2 + DelaySec=1.5h + DelayJitterSec=1.5h + DelaySec=23us + DelayJitterSec=23us + DelaySec=23µs + DelayJitterSec=23µs + DelaySec=2 weeks + DelayJitterSec=2 weeks + DelaySec=100 + DelayJitterSec=100 + DelaySec=infinity + DelayJitterSec=infinity + DelaySec= + DelayJitterSec= + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [NetworkEmulator] + DelaySec=5. + DelayJitterSec=5. + DelaySec=-5s + DelayJitterSec=-5s + DelaySec=3.s + DelayJitterSec=3.s + DelaySec=12.34.56 + DelayJitterSec=12.34.56 + DelaySec=5mins + DelayJitterSec=5mins + DelaySec=10 foo + DelayJitterSec=10 foo + DelaySec=infinity 5s + DelayJitterSec=infinity 5s + DelaySec=Infinity + DelayJitterSec=Infinity + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(16, highlights) + } + +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseNetemRateOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseNetemRateOptionValueTest.kt new file mode 100644 index 00000000..c8c22a8a --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseNetemRateOptionValueTest.kt @@ -0,0 +1,76 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseNetemRateOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // Fixture values from systemd test/test-network/conf plus tricky spellings the C parser accepts. + // language="unit file (systemd)" + val file = """ + [NetworkEmulator] + LossRate=20% + DuplicateRate=20% + LossRate=0% + DuplicateRate=0% + LossRate=100% + DuplicateRate=100% + LossRate=55.5% + DuplicateRate=55.5% + LossRate=55.53% + DuplicateRate=55.53% + LossRate=555.5‰ + DuplicateRate=555.5‰ + LossRate=1000‰ + DuplicateRate=1000‰ + LossRate=1000‱ + DuplicateRate=1000‱ + LossRate=0x10% + DuplicateRate=0x10% + LossRate= + DuplicateRate= + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [NetworkEmulator] + LossRate=20 + DuplicateRate=20 + LossRate=107% + DuplicateRate=107% + LossRate=-7% + DuplicateRate=-7% + LossRate=3.212% + DuplicateRate=3.212% + LossRate=3.22‰ + DuplicateRate=3.22‰ + LossRate=3.2‱ + DuplicateRate=3.2‱ + LossRate=abc% + DuplicateRate=abc% + LossRate=% + DuplicateRate=% + LossRate=20 % + DuplicateRate=20 % + """.trimIndent() + + setupFileInEditor("file.network", 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/ConfigParsePiePacketLimitOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParsePiePacketLimitOptionValueTest.kt new file mode 100644 index 00000000..844e7d91 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParsePiePacketLimitOptionValueTest.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 ConfigParsePiePacketLimitOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // Fixture values from systemd test/test-network/conf plus tricky spellings the C parser accepts. + // language="unit file (systemd)" + val file = """ + [PIE] + PacketLimit=0 + PacketLimit=1 + PacketLimit=20480 + PacketLimit=4294967295 + PacketLimit=0x10 + PacketLimit=0o17 + PacketLimit=0b101 + PacketLimit=017 + PacketLimit=+5 + PacketLimit=-0 + PacketLimit= + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [PIE] + PacketLimit=4294967296 + PacketLimit=-1 + PacketLimit=08 + PacketLimit=0x + PacketLimit=1.5 + PacketLimit=abc + PacketLimit=+0b1 + PacketLimit=12 13 + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(8, highlights) + } + +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseQfqMaxPacketOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseQfqMaxPacketOptionValueTest.kt new file mode 100644 index 00000000..5fd5c2b8 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseQfqMaxPacketOptionValueTest.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 ConfigParseQfqMaxPacketOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // Fixture values from systemd test/test-network/conf plus tricky spellings the C parser accepts. + // language="unit file (systemd)" + val file = """ + [QuickFairQueueingClass] + MaxPacketBytes=512 + MaxPacketBytes=16000 + MaxPacketBytes=8000 + MaxPacketBytes=65536 + MaxPacketBytes=64K + MaxPacketBytes=1K + MaxPacketBytes=0.5K + MaxPacketBytes=1K 512B + MaxPacketBytes=63K1023B + MaxPacketBytes= + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [QuickFairQueueingClass] + MaxPacketBytes=511 + MaxPacketBytes=65537 + MaxPacketBytes=65K + MaxPacketBytes=1M + MaxPacketBytes=0 + MaxPacketBytes=16000b + MaxPacketBytes=abc + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(7, highlights) + } + +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseTbfRateOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseTbfRateOptionValueTest.kt new file mode 100644 index 00000000..59fd41fd --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseTbfRateOptionValueTest.kt @@ -0,0 +1,78 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +class ConfigParseTbfRateOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // Fixture values from systemd test/test-network/conf plus tricky spellings the C parser accepts. + // language="unit file (systemd)" + val file = """ + [TokenBucketFilter] + Rate=1M + PeakRate=1M + Rate=0.5M + PeakRate=0.5M + Rate=1G + PeakRate=1G + Rate=100G + PeakRate=100G + Rate=1G 500M + PeakRate=1G 500M + Rate=3.5G3B + PeakRate=3.5G3B + Rate=4T3G3B + PeakRate=4T3G3B + Rate=10.M + PeakRate=10.M + Rate=125000 + PeakRate=125000 + Rate=+5K + PeakRate=+5K + Rate=1000 B + PeakRate=1000 B + Rate= + PeakRate= + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [TokenBucketFilter] + Rate=10Mbit + PeakRate=10Mbit + Rate=1k + PeakRate=1k + Rate=-1M + PeakRate=-1M + Rate=3B3.5G + PeakRate=3B3.5G + Rate=12P12P + PeakRate=12P12P + Rate=1M 1M + PeakRate=1M 1M + Rate=abc + PeakRate=abc + Rate=.5M + PeakRate=.5M + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(16, highlights) + } + +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseTeqlIdOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseTeqlIdOptionValueTest.kt new file mode 100644 index 00000000..943323c3 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseTeqlIdOptionValueTest.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 ConfigParseTeqlIdOptionValueTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // Fixture values from systemd test/test-network/conf plus tricky spellings the C parser accepts. + // language="unit file (systemd)" + val file = """ + [TrivialLinkEqualizer] + Id=0 + Id=1 + Id=2147483647 + Id=0x7fffffff + Id=0o7 + Id= + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [TrivialLinkEqualizer] + Id=2147483648 + Id=4294967295 + Id=-1 + Id=abc + Id=teql1 + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(5, highlights) + } + +} From a55bfb66572ad365f916fbd6371f5b081c886f96 Mon Sep 17 00:00:00 2001 From: Steve Ramage Date: Fri, 23 Oct 2026 18:37:31 -0700 Subject: [PATCH 2/5] fix: mirror parse_time ERANGE in qdisc parseSec and correct evidence anchors (qdisc batch) Reject parse_sec values that parse_time() refuses with ERANGE (integer part above LLONG_MAX, or a finite total reaching USEC_INFINITY), fix the HTB DefaultClass KDoc on 0b1, and correct off-by-one source anchors found in review. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../ai/ConfigParseEtsPrioOptionValue.kt | 3 ++- .../ai/ConfigParseFqCodelU32OptionValue.kt | 2 +- .../ai/ConfigParseFqCodelUsecOptionValue.kt | 5 ++-- .../ai/ConfigParseFqUsecOptionValue.kt | 2 +- .../ai/ConfigParseGredU32OptionValue.kt | 2 +- .../ai/ConfigParseHtbClassU32OptionValue.kt | 2 +- .../ConfigParseHtbDefaultClassOptionValue.kt | 5 ++-- .../ai/ConfigParseHtbU32OptionValue.kt | 2 +- .../ai/ConfigParseNetemDelayOptionValue.kt | 2 +- .../ai/ConfigParseNetemRateOptionValue.kt | 4 +-- .../ConfigParsePiePacketLimitOptionValue.kt | 2 +- .../ai/ConfigParseTeqlIdOptionValue.kt | 2 +- .../grammar/BatchQdiscCombinators.kt | 26 +++++++++++++++---- .../ConfigParseFqCodelUsecOptionValueTest.kt | 22 +++++++++++++++- .../ConfigParseNetemDelayOptionValueTest.kt | 16 +++++++++++- 15 files changed, 75 insertions(+), 22 deletions(-) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseEtsPrioOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseEtsPrioOptionValue.kt index 62cd5cf4..b6667b83 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseEtsPrioOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseEtsPrioOptionValue.kt @@ -8,9 +8,10 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram * * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#PriorityMap= * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/tc/ets.c#L227-L302 config_parse_ets_prio (QDISC_KIND_ETS) - * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L429-L475 safe_atou_full + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L429-L478 safe_atou_full * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L403-L427 mangle_base * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.h#L45-L47 safe_atou8 + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L583-L597 safe_atou8_full (rejects > UINT8_MAX) * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/include/uapi/linux/pkt_sched.h#L28 TC_PRIO_MAX = 15 * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-parse-util.c#L489 safe_atou16("+12345") accepted * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-parse-util.c#L466-L470 0o6 / 0B101 prefixes (safe_atolli, same mangle_base) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseFqCodelU32OptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseFqCodelU32OptionValue.kt index e36dda26..85ef2b47 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseFqCodelU32OptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseFqCodelU32OptionValue.kt @@ -9,7 +9,7 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#PacketLimit= * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#Flows= * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/tc/fq-codel.c#L97-L155 config_parse_fq_codel_u32 (QDISC_KIND_FQ_CODEL) - * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L429-L475 safe_atou_full + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L429-L478 safe_atou_full * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L403-L427 mangle_base * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-parse-util.c#L489 safe_atou16("+12345") accepted * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-parse-util.c#L466-L470 0o6 / 0B101 prefixes (safe_atolli, same mangle_base) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseFqCodelUsecOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseFqCodelUsecOptionValue.kt index f097eca3..c6ba5d11 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseFqCodelUsecOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseFqCodelUsecOptionValue.kt @@ -13,7 +13,7 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/time-util.c#L1289 parse_sec * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/time-util.c#L1198-L1287 parse_time * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/time-util.c#L1147-L1196 extract_multiplier - * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-time-util.c#L21-L84 TEST(parse_sec) + * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-time-util.c#L21-L83 TEST(parse_sec) * tests https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-qdisc-fq_codel.network#L15-L19 TargetSec=10ms, IntervalSec=200ms, CEThresholdSec=100ms */ @@ -27,7 +27,8 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram * `10ms`, `1min30s`, `5s 500ms`, `.5s`, `+3.1s` (see [parseSec]). There is no extra bound: the parsed * usec_t is stored as is (CEThresholdSec= additionally treats an empty value as USEC_INFINITY). * - * Lenient: values that overflow usec_t (parse_time returns ERANGE) are not range-checked. + * Overflow is rejected exactly as parse_time() does (ERANGE): an integer part above LLONG_MAX, or a finite + * total reaching USEC_INFINITY (e.g. `18446744073709551615us`). */ class ConfigParseFqCodelUsecOptionValue : SimpleGrammarOptionValues( "config_parse_fq_codel_usec", diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseFqUsecOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseFqUsecOptionValue.kt index fdb37288..d48539d4 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseFqUsecOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseFqUsecOptionValue.kt @@ -11,7 +11,7 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/time-util.c#L1289 parse_sec * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/time-util.c#L1198-L1287 parse_time * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/time-util.c#L1147-L1196 extract_multiplier - * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-time-util.c#L21-L84 TEST(parse_sec) + * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-time-util.c#L21-L83 TEST(parse_sec) * tests https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-qdisc-fq.network#L20 CEThresholdSec=100ms */ diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseGredU32OptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseGredU32OptionValue.kt index 121ccab3..1ec4d134 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseGredU32OptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseGredU32OptionValue.kt @@ -9,7 +9,7 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#VirtualQueues= * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#DefaultVirtualQueue= * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/tc/gred.c#L68-L133 config_parse_gred_u32 (QDISC_KIND_GRED) - * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L429-L475 safe_atou_full + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L429-L478 safe_atou_full * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L403-L427 mangle_base * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/include/uapi/linux/pkt_sched.h#L305 MAX_DPs = 16 * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-parse-util.c#L489 safe_atou16("+12345") accepted diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseHtbClassU32OptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseHtbClassU32OptionValue.kt index 77805dbc..8d3a26f6 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseHtbClassU32OptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseHtbClassU32OptionValue.kt @@ -8,7 +8,7 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram * * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#Priority= * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/tc/htb.c#L242-L293 config_parse_htb_class_u32 (TCLASS_KIND_HTB) - * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L429-L475 safe_atou_full + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L429-L478 safe_atou_full * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L403-L427 mangle_base * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-parse-util.c#L489 safe_atou16("+12345") accepted * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-parse-util.c#L466-L470 0o6 / 0B101 prefixes (safe_atolli, same mangle_base) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseHtbDefaultClassOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseHtbDefaultClassOptionValue.kt index 282c4a73..5da8a943 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseHtbDefaultClassOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseHtbDefaultClassOptionValue.kt @@ -9,7 +9,7 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#DefaultClass= * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/tc/htb.c#L48-L98 config_parse_htb_default_class (QDISC_KIND_HTB) * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.h#L61-L64 safe_atou32_full(rvalue, 16, ..) - * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L429-L475 safe_atou_full (explicit base: mangle_base is skipped) + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L429-L478 safe_atou_full (explicit base: mangle_base is skipped) * tests https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-qdisc-htb-fifo.network#L12 DefaultClass=30 (i.e. 0x30) */ @@ -20,7 +20,8 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram * handled before this grammar runs. * * The default class's minor id, always read as HEXADECIMAL (safe_atou32_full(.., 16)): `30` is 0x30, `0x30` - * is the same, `ff` is legal and `0b1`/`0o7` are not (with an explicit base, mangle_base does not run). + * is the same, `ff` is legal, `0b1` is read as hex 0xb1, and `0o7` is rejected (with an explicit base, + * mangle_base does not run). * Range 0…0xffffffff. */ class ConfigParseHtbDefaultClassOptionValue : SimpleGrammarOptionValues( diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseHtbU32OptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseHtbU32OptionValue.kt index d7d4c78e..4a38e76c 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseHtbU32OptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseHtbU32OptionValue.kt @@ -8,7 +8,7 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram * * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#RateToQuantum= * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/tc/htb.c#L100-L150 config_parse_htb_u32 (QDISC_KIND_HTB) - * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L429-L475 safe_atou_full + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L429-L478 safe_atou_full * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L403-L427 mangle_base * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-parse-util.c#L489 safe_atou16("+12345") accepted * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-parse-util.c#L466-L470 0o6 / 0B101 prefixes (safe_atolli, same mangle_base) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseNetemDelayOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseNetemDelayOptionValue.kt index c4a7f91c..1989c2a5 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseNetemDelayOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseNetemDelayOptionValue.kt @@ -12,7 +12,7 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/time-util.c#L1289 parse_sec * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/time-util.c#L1198-L1287 parse_time * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/time-util.c#L1147-L1196 extract_multiplier - * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-time-util.c#L21-L84 TEST(parse_sec) + * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-time-util.c#L21-L83 TEST(parse_sec) * tests https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-qdisc-netem.network#L12-L13 DelaySec=50ms, DelayJitterSec=10ms */ diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseNetemRateOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseNetemRateOptionValue.kt index def2f4a4..64e87776 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseNetemRateOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseNetemRateOptionValue.kt @@ -13,8 +13,8 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/percent-util.c#L135-L157 parse_permyriad / parse_permyriad_unbounded * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/percent-util.c#L57-L99 parse_parts_value_with_hundredths_place * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L497-L519 safe_atoi (base 0) - * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-percent-util.c#L87-L131 TEST(parse_permyriad) - * tests https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-qdisc-netem.network#L15 LossRate=20% + * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-percent-util.c#L87-L130 TEST(parse_permyriad) + * tests https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-qdisc-netem.network#L14 LossRate=20% */ /** diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParsePiePacketLimitOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParsePiePacketLimitOptionValue.kt index 64e0f28c..fca68956 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParsePiePacketLimitOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParsePiePacketLimitOptionValue.kt @@ -8,7 +8,7 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram * * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#PacketLimit= * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/tc/pie.c#L40-L90 config_parse_pie_packet_limit (QDISC_KIND_PIE) - * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L429-L475 safe_atou_full + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L429-L478 safe_atou_full * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L403-L427 mangle_base * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-parse-util.c#L489 safe_atou16("+12345") accepted * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-parse-util.c#L466-L470 0o6 / 0B101 prefixes (safe_atolli, same mangle_base) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseTeqlIdOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseTeqlIdOptionValue.kt index 6c71b9cf..1056e731 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseTeqlIdOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseTeqlIdOptionValue.kt @@ -8,7 +8,7 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram * * man https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html#Id= * parser https://github.com/systemd/systemd/blob/0f0682b773/src/network/tc/teql.c#L44-L100 config_parse_teql_id (QDISC_KIND_TEQL) - * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L429-L475 safe_atou_full + * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L429-L478 safe_atou_full * leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L403-L427 mangle_base * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-parse-util.c#L489 safe_atou16("+12345") accepted * tests https://github.com/systemd/systemd/blob/0f0682b773/src/test/test-parse-util.c#L466-L470 0o6 / 0B101 prefixes (safe_atolli, same mangle_base) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/BatchQdiscCombinators.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/BatchQdiscCombinators.kt index 4e98eeb2..89d25ba9 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/BatchQdiscCombinators.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/BatchQdiscCombinators.kt @@ -92,7 +92,7 @@ val QDISC_BOOLEAN = FlexibleLiteralChoiceTerminal(*BOOLEAN_SPELLINGS, ignoreCase /** * An unsigned integer as systemd's `safe_atou*_full()` reads it - * (https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L429-L475). + * (https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L429-L478). * * With [base] 0 (`safe_atou`, `safe_atou8`, `safe_atou32`, …) that is: * - decimal, C octal (`0755`) or `0x` hex — strtoul(base 0); @@ -217,6 +217,11 @@ private val PARSE_SEC = AlternativeCombinator( private val USEC_INFINITY: BigInteger = UINT64_MAX_BIG +private val LLONG_MAX_BIG: BigInteger = BigInteger.valueOf(Long.MAX_VALUE) + +/** Returned by [evaluateParseSec] when parse_time() would fail with ERANGE; above every accepted bound. */ +private val PARSE_SEC_ERANGE: BigInteger = USEC_INFINITY + BigInteger.ONE + private val SEC_MULTIPLIERS: Map = run { val sec = 1_000_000L val minute = 60 * sec @@ -237,7 +242,11 @@ private val SEC_MULTIPLIERS: Map = run { private val SEC_EVAL_ELEMENT = Regex("""\s*\+?([0-9]*)(?:\.([0-9]+))?\s*($SEC_SUFFIX_ALTERNATION)?""") -/** parse_time(t, USEC_PER_SEC) as a value in microseconds, or null if [text] is not evaluable. */ +/** + * parse_time(t, USEC_PER_SEC) as a value in microseconds, or null if [text] is not evaluable. Overflow + * (C's ERANGE: an integer part above LLONG_MAX, or a finite total reaching USEC_INFINITY) yields a value + * greater than USEC_INFINITY so that every bound check rejects it. + */ fun evaluateParseSec(text: String): BigInteger? { val t = text.trim() if (t == "infinity") return USEC_INFINITY @@ -250,12 +259,19 @@ fun evaluateParseSec(text: String): BigInteger? { if (m.value.isEmpty()) return null pos += m.value.length val multiplier = m.groupValues[3].takeIf { it.isNotEmpty() }?.let { SEC_MULTIPLIERS[it] } ?: SEC_MULTIPLIERS.getValue("s") - val whole = m.groupValues[1].ifEmpty { "0" } - total += BigInteger(whole) * multiplier + val whole = BigInteger(m.groupValues[1].ifEmpty { "0" }) + // strtoll() sets ERANGE above LLONG_MAX; then `(usec_t) l >= USEC_INFINITY / multiplier` and + // `k >= USEC_INFINITY - usec` are ERANGE too, so a finite total can never reach USEC_INFINITY. + if (whole > LLONG_MAX_BIG || whole >= USEC_INFINITY / multiplier) return PARSE_SEC_ERANGE + val k = whole * multiplier + if (k >= USEC_INFINITY - total) return PARSE_SEC_ERANGE + total += k // The C adds the fractional digits one by one, each times a truncated multiplier/10^k. var step = multiplier / BigInteger.TEN for (digit in m.groupValues[2]) { - total += BigInteger.valueOf((digit - '0').toLong()) * step + val kf = BigInteger.valueOf((digit - '0').toLong()) * step + if (kf >= USEC_INFINITY - total) return PARSE_SEC_ERANGE + total += kf step /= BigInteger.TEN } } diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseFqCodelUsecOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseFqCodelUsecOptionValueTest.kt index 04c21018..8ceaf4d8 100644 --- a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseFqCodelUsecOptionValueTest.kt +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseFqCodelUsecOptionValueTest.kt @@ -54,6 +54,13 @@ class ConfigParseFqCodelUsecOptionValueTest : AbstractUnitFileTest() { TargetSec=100 IntervalSec=100 CEThresholdSec=100 + # Largest finite values parse_time() accepts (total < USEC_INFINITY, each integer <= LLONG_MAX). + TargetSec=9223372036854775807us 9223372036854775807us + IntervalSec=9223372036854775807us 9223372036854775807us + CEThresholdSec=9223372036854775807us 9223372036854775807us + TargetSec=18446744073708s + IntervalSec=18446744073708s + CEThresholdSec=18446744073708s TargetSec=infinity IntervalSec=infinity CEThresholdSec=infinity @@ -98,13 +105,26 @@ class ConfigParseFqCodelUsecOptionValueTest : AbstractUnitFileTest() { TargetSec=Infinity IntervalSec=Infinity CEThresholdSec=Infinity + # parse_time() ERANGE: integer above LLONG_MAX, or total reaching USEC_INFINITY. + TargetSec=9223372036854775808us + IntervalSec=9223372036854775808us + CEThresholdSec=9223372036854775808us + TargetSec=18446744073709551615us + IntervalSec=18446744073709551615us + CEThresholdSec=18446744073709551615us + TargetSec=9223372036854775807us 9223372036854775807us 1us + IntervalSec=9223372036854775807us 9223372036854775807us 1us + CEThresholdSec=9223372036854775807us 9223372036854775807us 1us + TargetSec=18446744073709s + IntervalSec=18446744073709s + CEThresholdSec=18446744073709s """.trimIndent() setupFileInEditor("file.network", file) enableInspection(InvalidValueInspection::class.java) val highlights = myFixture.doHighlighting() - assertSize(24, highlights) + assertSize(36, highlights) } } diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseNetemDelayOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseNetemDelayOptionValueTest.kt index 37b91e32..b9573441 100644 --- a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseNetemDelayOptionValueTest.kt +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseNetemDelayOptionValueTest.kt @@ -40,6 +40,11 @@ class ConfigParseNetemDelayOptionValueTest : AbstractUnitFileTest() { DelayJitterSec=2 weeks DelaySec=100 DelayJitterSec=100 + # Largest finite values parse_time() accepts (total < USEC_INFINITY, each integer <= LLONG_MAX). + DelaySec=9223372036854775807us 9223372036854775807us + DelayJitterSec=9223372036854775807us 9223372036854775807us + DelaySec=18446744073708s + DelayJitterSec=18446744073708s DelaySec=infinity DelayJitterSec=infinity DelaySec= @@ -74,13 +79,22 @@ class ConfigParseNetemDelayOptionValueTest : AbstractUnitFileTest() { DelayJitterSec=infinity 5s DelaySec=Infinity DelayJitterSec=Infinity + # parse_time() ERANGE: integer above LLONG_MAX, or total reaching USEC_INFINITY. + DelaySec=9223372036854775808us + DelayJitterSec=9223372036854775808us + DelaySec=18446744073709551615us + DelayJitterSec=18446744073709551615us + DelaySec=9223372036854775807us 9223372036854775807us 1us + DelayJitterSec=9223372036854775807us 9223372036854775807us 1us + DelaySec=18446744073709s + DelayJitterSec=18446744073709s """.trimIndent() setupFileInEditor("file.network", file) enableInspection(InvalidValueInspection::class.java) val highlights = myFixture.doHighlighting() - assertSize(16, highlights) + assertSize(24, highlights) } } From 7d0f1c4ab6eaf7bb2f9cf147708a4ffe8ded13da Mon Sep 17 00:00:00 2001 From: Steve Ramage Date: Fri, 23 Oct 2026 19:07:10 -0700 Subject: [PATCH 3/5] refactor: give qdisc batch grammar helpers family-unique names Several batch branches declared public helpers with the same name in the shared grammar package (SafeAtouTerminal, UINT32_LIMIT, safeAtou, SafeAtoNumberTerminal, BOOLEAN_ANY_CASE, COALESCED_COLONS). Each branch compiled alone but any two merged together would not. Prefix them per family so the batch PRs can merge in any order; consolidating them into Combinators.kt is left for a follow-up. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../ai/ConfigParseEtsPrioOptionValue.kt | 2 +- .../ai/ConfigParseGredU32OptionValue.kt | 2 +- .../ConfigParseHtbDefaultClassOptionValue.kt | 2 +- .../ai/ConfigParseTeqlIdOptionValue.kt | 2 +- .../grammar/BatchQdiscCombinators.kt | 18 +++++++++--------- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseEtsPrioOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseEtsPrioOptionValue.kt index b6667b83..e13762a0 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseEtsPrioOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseEtsPrioOptionValue.kt @@ -18,7 +18,7 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram * tests https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-qdisc-ets.network#L18-L21 PriorityMap=8 7 6 5 / PriorityMap= / PriorityMap=3 4 5 */ -private val ETS_BAND = safeAtou(256) +private val ETS_BAND = qdiscSafeAtou(256) /** * Validator for [EnhancedTransmissionSelection] PriorityMap=. diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseGredU32OptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseGredU32OptionValue.kt index 1ec4d134..3731ef75 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseGredU32OptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseGredU32OptionValue.kt @@ -34,5 +34,5 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram */ class ConfigParseGredU32OptionValue : SimpleGrammarOptionValues( "config_parse_gred_u32", - SequenceCombinator(safeAtou(17), EOF()), + SequenceCombinator(qdiscSafeAtou(17), EOF()), ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseHtbDefaultClassOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseHtbDefaultClassOptionValue.kt index 5da8a943..e63ae173 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseHtbDefaultClassOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseHtbDefaultClassOptionValue.kt @@ -26,5 +26,5 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram */ class ConfigParseHtbDefaultClassOptionValue : SimpleGrammarOptionValues( "config_parse_htb_default_class", - SequenceCombinator(safeAtou(UINT32_LIMIT, base = 16), EOF()), + SequenceCombinator(qdiscSafeAtou(QDISC_UINT32_LIMIT, base = 16), EOF()), ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseTeqlIdOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseTeqlIdOptionValue.kt index 1056e731..3b75146e 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseTeqlIdOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseTeqlIdOptionValue.kt @@ -27,5 +27,5 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram */ class ConfigParseTeqlIdOptionValue : SimpleGrammarOptionValues( "config_parse_teql_id", - SequenceCombinator(safeAtou(2_147_483_648L), EOF()), + SequenceCombinator(qdiscSafeAtou(2_147_483_648L), EOF()), ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/BatchQdiscCombinators.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/BatchQdiscCombinators.kt index 89d25ba9..b2740af5 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/BatchQdiscCombinators.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/BatchQdiscCombinators.kt @@ -13,7 +13,7 @@ import java.math.BigInteger * The tc parsers lean on four leaf helpers, each modelled here as closely to the C as the combinator * framework allows: * - * - safe_atou / safe_atou32 / safe_atou8 / safe_atou32_full(.., 16) -> [SafeAtouTerminal] + * - safe_atou / safe_atou32 / safe_atou8 / safe_atou32_full(.., 16) -> [QdiscSafeAtouTerminal] * https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L429 * - parse_sec / parse_time -> [parseSec] * https://github.com/systemd/systemd/blob/0f0682b773/src/basic/time-util.c#L1198 @@ -106,7 +106,7 @@ val QDISC_BOOLEAN = FlexibleLiteralChoiceTerminal(*BOOLEAN_SPELLINGS, ignoreCase * * The result must lie in `[minInclusive, maxExclusive)`. */ -class SafeAtouTerminal( +class QdiscSafeAtouTerminal( private val minInclusive: Long, private val maxExclusive: Long, private val base: Int = 0, @@ -167,20 +167,20 @@ class SafeAtouTerminal( } /** One past UINT32_MAX, the exclusive bound for every `safe_atou32` value. */ -const val UINT32_LIMIT: Long = 4_294_967_296L +const val QDISC_UINT32_LIMIT: Long = 4_294_967_296L /** One past UINT32_MAX as a BigInteger, for the size/time range checks. */ -private val UINT32_MAX_BIG: BigInteger = BigInteger.valueOf(UINT32_LIMIT - 1) +private val UINT32_MAX_BIG: BigInteger = BigInteger.valueOf(QDISC_UINT32_LIMIT - 1) /** ULLONG_MAX: parse_size() and parse_time() return ERANGE above it. */ private val UINT64_MAX_BIG: BigInteger = BigInteger.ONE.shiftLeft(64).subtract(BigInteger.ONE) -/** A [SafeAtouTerminal] coloured as a literal. */ -fun safeAtou(maxExclusive: Long, minInclusive: Long = 0L, base: Int = 0): Combinator = - Labeled(Role.LITERAL, SafeAtouTerminal(minInclusive, maxExclusive, base)) +/** A [QdiscSafeAtouTerminal] coloured as a literal. */ +fun qdiscSafeAtou(maxExclusive: Long, minInclusive: Long = 0L, base: Int = 0): Combinator = + Labeled(Role.LITERAL, QdiscSafeAtouTerminal(minInclusive, maxExclusive, base)) /** A whole value that is one safe_atou32() number. */ -fun safeAtou32Value(): Combinator = SequenceCombinator(safeAtou(UINT32_LIMIT), EOF()) +fun safeAtou32Value(): Combinator = SequenceCombinator(qdiscSafeAtou(QDISC_UINT32_LIMIT), EOF()) // --------------------------------------------------------------------------------------------------- // parse_sec / parse_time @@ -376,7 +376,7 @@ val SIZE_UINT32_MAX: BigInteger = UINT32_MAX_BIG // The integer part goes through safe_atoi() (base 0, mangle_base) — so `0x10%` is 16% — and must not // be negative (`-0` is fine). Its magnitude is capped by the 100% check below, so an int-sized bound // on the terminal itself is enough. -private val PCT_INT = safeAtou(Int.MAX_VALUE.toLong() + 1) +private val PCT_INT = qdiscSafeAtou(Int.MAX_VALUE.toLong() + 1) /** * A percentage as parse_permyriad() reads it From b0acb951af6203a848489c001b23c7090c7cfc85 Mon Sep 17 00:00:00 2001 From: Steve Ramage Date: Sat, 24 Oct 2026 07:44:30 -0700 Subject: [PATCH 4/5] refactor: use the shared case-insensitive BOOLEAN in qdisc validators 242.x's BOOLEAN now ignores case like parse_boolean(), so the branch's own QDISC_BOOLEAN copy is gone. AckFilter= is written as an explicit PreferValidAlternativeCombinator(names, BOOLEAN) with the table's no/yes entries left to the boolean branch. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../ai/ConfigParseCakeAckFilterOptionValue.kt | 11 +++++++++-- .../ai/ConfigParseFqCodelBoolOptionValue.kt | 2 +- .../optionvalues/ai/ConfigParseGredBoolOptionValue.kt | 2 +- .../optionvalues/grammar/BatchQdiscCombinators.kt | 9 +-------- .../ai/ConfigParseCakeAckFilterOptionValueTest.kt | 9 ++++++++- .../ai/ConfigParseFqCodelBoolOptionValueTest.kt | 8 +++++++- .../ai/ConfigParseGredBoolOptionValueTest.kt | 8 +++++++- 7 files changed, 34 insertions(+), 15 deletions(-) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseCakeAckFilterOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseCakeAckFilterOptionValue.kt index e20f1cf3..67911f6d 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseCakeAckFilterOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseCakeAckFilterOptionValue.kt @@ -22,9 +22,16 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram * handled before this grammar runs. * * A boolean (parse_boolean() is tried first, case-insensitively: `yes` -> filter, `no` -> off) or the - * table word `aggressive`, which is matched exactly (case-sensitive). + * table word `aggressive`, which is matched exactly (case-sensitive streq). The table's `no`/`yes` + * entries are not listed as names: parse_boolean() already takes them, in any case. */ class ConfigParseCakeAckFilterOptionValue : SimpleGrammarOptionValues( "config_parse_cake_ack_filter", - SequenceCombinator(AlternativeCombinator(FlexibleLiteralChoiceTerminal("aggressive"), QDISC_BOOLEAN), EOF()), + SequenceCombinator( + PreferValidAlternativeCombinator( + FlexibleLiteralChoiceTerminal("aggressive"), + BOOLEAN, + ), + EOF(), + ), ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseFqCodelBoolOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseFqCodelBoolOptionValue.kt index 7ed1c3ba..b04ad462 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseFqCodelBoolOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseFqCodelBoolOptionValue.kt @@ -25,5 +25,5 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram */ class ConfigParseFqCodelBoolOptionValue : SimpleGrammarOptionValues( "config_parse_fq_codel_bool", - SequenceCombinator(QDISC_BOOLEAN, EOF()), + SequenceCombinator(BOOLEAN, EOF()), ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseGredBoolOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseGredBoolOptionValue.kt index 98b7483d..7fa893b7 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseGredBoolOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseGredBoolOptionValue.kt @@ -25,5 +25,5 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram */ class ConfigParseGredBoolOptionValue : SimpleGrammarOptionValues( "config_parse_gred_bool", - SequenceCombinator(QDISC_BOOLEAN, EOF()), + SequenceCombinator(BOOLEAN, EOF()), ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/BatchQdiscCombinators.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/BatchQdiscCombinators.kt index b2740af5..d05a3591 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/BatchQdiscCombinators.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/BatchQdiscCombinators.kt @@ -24,7 +24,7 @@ import java.math.BigInteger * * Several of these are more faithful than the older shared terminals in Combinators.kt (TIME_VALUE * rejects "1min30s"; ByteSizeTerminal rejects "1G 512M"; UnsignedNumberTerminal rejects "0b101" and - * "+5"; BOOLEAN is case-sensitive although parse_boolean is not). They live in this file only so the + * "+5"). They live in this file only so the * batch does not collide with parallel edits to Combinators.kt; see RECOMMENDATIONS.md for promoting * them. */ @@ -80,13 +80,6 @@ class ValueChecked(private val inner: Combinator, private val accept: (String) - override fun toString(): String = toStringIndented(0) } -/** - * The boolean spellings parse_boolean() accepts, matched case-insensitively as the C does - * (STRCASE_IN_SET, https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L23; - * test-parse-util.c asserts "YES", "TRUE", "ON", "N" … are accepted). - */ -val QDISC_BOOLEAN = FlexibleLiteralChoiceTerminal(*BOOLEAN_SPELLINGS, ignoreCase = true) - // --------------------------------------------------------------------------------------------------- // safe_atou* diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseCakeAckFilterOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseCakeAckFilterOptionValueTest.kt index cfd96ef5..78625491 100644 --- a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseCakeAckFilterOptionValueTest.kt +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseCakeAckFilterOptionValueTest.kt @@ -23,6 +23,11 @@ class ConfigParseCakeAckFilterOptionValueTest : AbstractUnitFileTest() { AckFilter=off AckFilter=YES AckFilter=No + AckFilter=Yes + AckFilter=TRUE + AckFilter=oFf + AckFilter=tRUE + AckFilter=N AckFilter= """.trimIndent() @@ -43,13 +48,15 @@ class ConfigParseCakeAckFilterOptionValueTest : AbstractUnitFileTest() { AckFilter=aggresive AckFilter=2 AckFilter=filter + AckFilter=AGGRESSIVE + AckFilter=aggressivE """.trimIndent() setupFileInEditor("file.network", file) enableInspection(InvalidValueInspection::class.java) val highlights = myFixture.doHighlighting() - assertSize(5, highlights) + assertSize(7, highlights) } } diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseFqCodelBoolOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseFqCodelBoolOptionValueTest.kt index 36c35211..7163924a 100644 --- a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseFqCodelBoolOptionValueTest.kt +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseFqCodelBoolOptionValueTest.kt @@ -27,6 +27,10 @@ class ConfigParseFqCodelBoolOptionValueTest : AbstractUnitFileTest() { ECN=YES ECN=False ECN=On + ECN=Yes + ECN=TRUE + ECN=oFf + ECN=Y ECN= """.trimIndent() @@ -46,13 +50,15 @@ class ConfigParseFqCodelBoolOptionValueTest : AbstractUnitFileTest() { ECN=2 ECN=enable ECN=nope + ECN=Maybe + ECN=ENABLE """.trimIndent() setupFileInEditor("file.network", file) enableInspection(InvalidValueInspection::class.java) val highlights = myFixture.doHighlighting() - assertSize(4, highlights) + assertSize(6, highlights) } } diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseGredBoolOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseGredBoolOptionValueTest.kt index 363f3137..0f81298a 100644 --- a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseGredBoolOptionValueTest.kt +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseGredBoolOptionValueTest.kt @@ -27,6 +27,10 @@ class ConfigParseGredBoolOptionValueTest : AbstractUnitFileTest() { GenericRIO=YES GenericRIO=False GenericRIO=On + GenericRIO=Yes + GenericRIO=TRUE + GenericRIO=oFf + GenericRIO=Y GenericRIO= """.trimIndent() @@ -46,13 +50,15 @@ class ConfigParseGredBoolOptionValueTest : AbstractUnitFileTest() { GenericRIO=2 GenericRIO=enable GenericRIO=nope + GenericRIO=Maybe + GenericRIO=ENABLE """.trimIndent() setupFileInEditor("file.network", file) enableInspection(InvalidValueInspection::class.java) val highlights = myFixture.doHighlighting() - assertSize(4, highlights) + assertSize(6, highlights) } } From e720ad76d5f431d5928664e04a9c5805b19411dd Mon Sep 17 00:00:00 2001 From: Steve Ramage Date: Sat, 24 Oct 2026 11:34:01 -0700 Subject: [PATCH 5/5] refactor: use the shared SafeAtoTerminal in qdisc validators (#563) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the branch's local safe_atou copy (QdiscSafeAtouTerminal, qdiscSafeAtou, safeAtou32Value, QDISC_UINT32_LIMIT) with SafeAtoTerminal, written out at each call site. PriorityMap= entries are words from extract_first_word(), so they use acceptsWhitespace = false. The parse_tc_percent integer part (PCT_INT) is now a signed SafeAtoTerminal, matching safe_atoi(), and evaluatePermyriad() reads it through valueOf(). Verdict changes, all checked with sd-check at 0f0682b773: - now accepted for the safe_atou32/safe_atou values (FQ-CoDel PacketLimit= and Flows=, HTB RateToQuantum=, HTB class Priority=, PIE PacketLimit=, GRED VirtualQueues=/DefaultVirtualQueue=, TEQL Id=): 0b+1, 0b-0, 0b 1, 0o 7, 0b +1, 0b -0, and a leading vertical tab - now accepted for PriorityMap=: 0b+1, 0b-0 - now accepted for LossRate=/DuplicateRate=: 0b 1%, 0b+1%, 0b-0%, 0b 1.5%, 0o 7‰ Co-Authored-By: Claude Opus 5.5 (1M context) --- .../ai/ConfigParseEtsPrioOptionValue.kt | 14 +- .../ai/ConfigParseFqCodelU32OptionValue.kt | 17 +- .../ai/ConfigParseGredU32OptionValue.kt | 15 +- .../ai/ConfigParseHtbClassU32OptionValue.kt | 17 +- .../ConfigParseHtbDefaultClassOptionValue.kt | 16 +- .../ai/ConfigParseHtbU32OptionValue.kt | 17 +- .../ConfigParsePiePacketLimitOptionValue.kt | 17 +- .../ai/ConfigParseTeqlIdOptionValue.kt | 17 +- .../grammar/BatchQdiscCombinators.kt | 145 ++++-------------- .../ai/ConfigParseEtsPrioOptionValueTest.kt | 9 +- .../ConfigParseFqCodelU32OptionValueTest.kt | 22 ++- .../ai/ConfigParseGredU32OptionValueTest.kt | 22 ++- .../ConfigParseHtbClassU32OptionValueTest.kt | 13 +- ...nfigParseHtbDefaultClassOptionValueTest.kt | 4 +- .../ai/ConfigParseHtbU32OptionValueTest.kt | 13 +- .../ai/ConfigParseNetemRateOptionValueTest.kt | 20 ++- ...onfigParsePiePacketLimitOptionValueTest.kt | 13 +- .../ai/ConfigParseTeqlIdOptionValueTest.kt | 13 +- 18 files changed, 261 insertions(+), 143 deletions(-) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseEtsPrioOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseEtsPrioOptionValue.kt index e13762a0..2e27d6cb 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseEtsPrioOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseEtsPrioOptionValue.kt @@ -2,6 +2,7 @@ package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.* +import java.math.BigInteger /* * [EnhancedTransmissionSelection] PriorityMap= (.network) @@ -18,7 +19,15 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram * tests https://github.com/systemd/systemd/blob/0f0682b773/test/test-network/conf/25-qdisc-ets.network#L18-L21 PriorityMap=8 7 6 5 / PriorityMap= / PriorityMap=3 4 5 */ -private val ETS_BAND = qdiscSafeAtou(256) +private val ETS_BAND = SafeAtoTerminal( + signed = false, + base = 0, + flags = emptySet(), + minInclusive = BigInteger.ZERO, + maxInclusive = SafeAtoTerminal.UINT8_MAX, + acceptsWhitespace = false, + acceptsMinus = true, +) /** * Validator for [EnhancedTransmissionSelection] PriorityMap=. @@ -27,7 +36,8 @@ private val ETS_BAND = qdiscSafeAtou(256) * handled before this grammar runs. * * A whitespace-separated list of band numbers, each read with safe_atou8 (base 0, 0…255), at most - * TC_PRIO_MAX + 1 (16) entries per line. Out-of-range or unparsable entries are logged and skipped by + * TC_PRIO_MAX + 1 (16) entries per line. Each entry is a word from extract_first_word(), so the number + * cannot contain a blank: `0b+1` is 1, but `0b 1` is the words `0b` (invalid) and `1`. Out-of-range or unparsable entries are logged and skipped by * the C, and so are flagged here; so is a 17th entry. * * Lenient: enhanced_transmission_selection_verify later rejects the whole section if any entry is not diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseFqCodelU32OptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseFqCodelU32OptionValue.kt index 85ef2b47..6357d525 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseFqCodelU32OptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseFqCodelU32OptionValue.kt @@ -2,6 +2,7 @@ package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.* +import java.math.BigInteger /* * [FairQueueingControlledDelay] PacketLimit=, Flows= (.network) @@ -23,9 +24,21 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram * handled before this grammar runs. * * An unsigned 32-bit integer read with safe_atou32 (base 0: decimal, `0x` hex, `0o`/leading-zero - * octal, `0b` binary, optional `+`), 0…4294967295. No further bound in the parser. + * octal, `0b` binary, optional `+`), 0…4294967295. No further bound in the parser. strtoul starts + * afresh after a `0b`/`0o` prefix, so `0b+1` and `0b 1` are 1 (see [SafeAtoTerminal]). */ class ConfigParseFqCodelU32OptionValue : SimpleGrammarOptionValues( "config_parse_fq_codel_u32", - safeAtou32Value(), + SequenceCombinator( + SafeAtoTerminal( + signed = false, + base = 0, + flags = emptySet(), + minInclusive = BigInteger.ZERO, + maxInclusive = SafeAtoTerminal.UINT32_MAX, + acceptsWhitespace = true, + acceptsMinus = true, + ), + EOF(), + ), ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseGredU32OptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseGredU32OptionValue.kt index 3731ef75..fbf483a5 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseGredU32OptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseGredU32OptionValue.kt @@ -24,7 +24,7 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram * handled before this grammar runs. * * An unsigned integer read with safe_atou32 (base 0: decimal, `0x` hex, `0o`/leading-zero octal, `0b` - * binary, optional `+`) that must not exceed MAX_DPs (16). Above 16 the parser logs "Invalid ..., ignoring + * binary, optional `+`; `0b+1` and `0b 1` are 1, see [SafeAtoTerminal]) that must not exceed MAX_DPs (16). Above 16 the parser logs "Invalid ..., ignoring * assignment" (although, at 0f0682b773, it still stores the value — a C bug; the kernel refuses more than * 16 DPs anyway), so it is flagged here. * @@ -34,5 +34,16 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram */ class ConfigParseGredU32OptionValue : SimpleGrammarOptionValues( "config_parse_gred_u32", - SequenceCombinator(qdiscSafeAtou(17), EOF()), + SequenceCombinator( + SafeAtoTerminal( + signed = false, + base = 0, + flags = emptySet(), + minInclusive = 0L, + maxInclusive = 16L, + acceptsWhitespace = true, + acceptsMinus = true, + ), + EOF(), + ), ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseHtbClassU32OptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseHtbClassU32OptionValue.kt index 8d3a26f6..da722eda 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseHtbClassU32OptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseHtbClassU32OptionValue.kt @@ -2,6 +2,7 @@ package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.* +import java.math.BigInteger /* * [HierarchyTokenBucketClass] Priority= (.network) @@ -21,9 +22,21 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram * C function: config_parse_htb_class_u32 (TCLASS_KIND_HTB). An empty assignment resets the setting and is * handled before this grammar runs. * - * An unsigned 32-bit integer read with safe_atou32 (base 0), 0…4294967295. No further bound. + * An unsigned 32-bit integer read with safe_atou32 (base 0, so `0b+1` and `0b 1` are 1; see + * [SafeAtoTerminal]), 0…4294967295. No further bound. */ class ConfigParseHtbClassU32OptionValue : SimpleGrammarOptionValues( "config_parse_htb_class_u32", - safeAtou32Value(), + SequenceCombinator( + SafeAtoTerminal( + signed = false, + base = 0, + flags = emptySet(), + minInclusive = BigInteger.ZERO, + maxInclusive = SafeAtoTerminal.UINT32_MAX, + acceptsWhitespace = true, + acceptsMinus = true, + ), + EOF(), + ), ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseHtbDefaultClassOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseHtbDefaultClassOptionValue.kt index e63ae173..9ab738f3 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseHtbDefaultClassOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseHtbDefaultClassOptionValue.kt @@ -2,6 +2,7 @@ package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.* +import java.math.BigInteger /* * [HierarchyTokenBucket] DefaultClass= (.network) @@ -21,10 +22,21 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram * * The default class's minor id, always read as HEXADECIMAL (safe_atou32_full(.., 16)): `30` is 0x30, `0x30` * is the same, `ff` is legal, `0b1` is read as hex 0xb1, and `0o7` is rejected (with an explicit base, - * mangle_base does not run). + * mangle_base does not run). strtoul skips a leading vertical tab, so `\v5` is 5 (see [SafeAtoTerminal]). * Range 0…0xffffffff. */ class ConfigParseHtbDefaultClassOptionValue : SimpleGrammarOptionValues( "config_parse_htb_default_class", - SequenceCombinator(qdiscSafeAtou(QDISC_UINT32_LIMIT, base = 16), EOF()), + SequenceCombinator( + SafeAtoTerminal( + signed = false, + base = 16, + flags = emptySet(), + minInclusive = BigInteger.ZERO, + maxInclusive = SafeAtoTerminal.UINT32_MAX, + acceptsWhitespace = true, + acceptsMinus = true, + ), + EOF(), + ), ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseHtbU32OptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseHtbU32OptionValue.kt index 4a38e76c..5470f63a 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseHtbU32OptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseHtbU32OptionValue.kt @@ -2,6 +2,7 @@ package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.* +import java.math.BigInteger /* * [HierarchyTokenBucket] RateToQuantum= (.network) @@ -21,9 +22,21 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram * C function: config_parse_htb_u32 (QDISC_KIND_HTB). An empty assignment resets the setting and is * handled before this grammar runs. * - * An unsigned 32-bit integer read with safe_atou32 (base 0), 0…4294967295. No further bound. + * An unsigned 32-bit integer read with safe_atou32 (base 0, so `0b+1` and `0b 1` are 1; see + * [SafeAtoTerminal]), 0…4294967295. No further bound. */ class ConfigParseHtbU32OptionValue : SimpleGrammarOptionValues( "config_parse_htb_u32", - safeAtou32Value(), + SequenceCombinator( + SafeAtoTerminal( + signed = false, + base = 0, + flags = emptySet(), + minInclusive = BigInteger.ZERO, + maxInclusive = SafeAtoTerminal.UINT32_MAX, + acceptsWhitespace = true, + acceptsMinus = true, + ), + EOF(), + ), ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParsePiePacketLimitOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParsePiePacketLimitOptionValue.kt index fca68956..1e4354ac 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParsePiePacketLimitOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParsePiePacketLimitOptionValue.kt @@ -2,6 +2,7 @@ package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.* +import java.math.BigInteger /* * [PIE] PacketLimit= (.network) @@ -21,10 +22,22 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram * C function: config_parse_pie_packet_limit (QDISC_KIND_PIE). An empty assignment resets the setting and is * handled before this grammar runs. * - * An unsigned 32-bit integer read with safe_atou32 (base 0), 0…4294967295. No further bound in the + * An unsigned 32-bit integer read with safe_atou32 (base 0, so `0b+1` and `0b 1` are 1; see + * [SafeAtoTerminal]), 0…4294967295. No further bound in the * parser (the man page's "1…4294967294" is not enforced by it). */ class ConfigParsePiePacketLimitOptionValue : SimpleGrammarOptionValues( "config_parse_pie_packet_limit", - safeAtou32Value(), + SequenceCombinator( + SafeAtoTerminal( + signed = false, + base = 0, + flags = emptySet(), + minInclusive = BigInteger.ZERO, + maxInclusive = SafeAtoTerminal.UINT32_MAX, + acceptsWhitespace = true, + acceptsMinus = true, + ), + EOF(), + ), ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseTeqlIdOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseTeqlIdOptionValue.kt index 3b75146e..47b289ab 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseTeqlIdOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseTeqlIdOptionValue.kt @@ -2,6 +2,7 @@ package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.* +import java.math.BigInteger /* * [TrivialLinkEqualizer] Id= (.network) @@ -21,11 +22,23 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram * C function: config_parse_teql_id (QDISC_KIND_TEQL). An empty assignment resets the setting and is * handled before this grammar runs. * - * An unsigned integer read with safe_atou (base 0) that must not exceed INT_MAX: above it the parser + * An unsigned integer read with safe_atou (base 0, so `0b+1` and `0b 1` are 1; see [SafeAtoTerminal]) + * that must not exceed INT_MAX: above it the parser * logs "'Id=' is too large, ignoring assignment" (and, at 0f0682b773, still stores it — the qdisc kind * then becomes e.g. "teql4294967295"), so 0…2147483647 is accepted. */ class ConfigParseTeqlIdOptionValue : SimpleGrammarOptionValues( "config_parse_teql_id", - SequenceCombinator(qdiscSafeAtou(2_147_483_648L), EOF()), + SequenceCombinator( + SafeAtoTerminal( + signed = false, + base = 0, + flags = emptySet(), + minInclusive = BigInteger.ZERO, + maxInclusive = SafeAtoTerminal.INT32_MAX, + acceptsWhitespace = true, + acceptsMinus = true, + ), + EOF(), + ), ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/BatchQdiscCombinators.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/BatchQdiscCombinators.kt index d05a3591..e1797519 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/BatchQdiscCombinators.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/BatchQdiscCombinators.kt @@ -10,11 +10,11 @@ import java.math.BigInteger * Everything here is pinned to systemd 0f0682b773 (systemd-build/build/last_commit_hash): * https://github.com/systemd/systemd/blob/0f0682b773 * - * The tc parsers lean on four leaf helpers, each modelled here as closely to the C as the combinator - * framework allows: + * The tc parsers lean on four leaf helpers. safe_atou / safe_atou32 / safe_atou8 / + * safe_atou32_full(.., 16) (https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L429) + * are the shared [SafeAtoTerminal], written out at each call site. The other three are modelled here as + * closely to the C as the combinator framework allows: * - * - safe_atou / safe_atou32 / safe_atou8 / safe_atou32_full(.., 16) -> [QdiscSafeAtouTerminal] - * https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L429 * - parse_sec / parse_time -> [parseSec] * https://github.com/systemd/systemd/blob/0f0682b773/src/basic/time-util.c#L1198 * - parse_size(.., 1000 | 1024) -> [parseSize] @@ -22,10 +22,9 @@ import java.math.BigInteger * - parse_permyriad (via parse_tc_percent) -> [TC_PERCENT] * https://github.com/systemd/systemd/blob/0f0682b773/src/basic/percent-util.c#L149 * - * Several of these are more faithful than the older shared terminals in Combinators.kt (TIME_VALUE - * rejects "1min30s"; ByteSizeTerminal rejects "1G 512M"; UnsignedNumberTerminal rejects "0b101" and - * "+5"). They live in this file only so the - * batch does not collide with parallel edits to Combinators.kt; see RECOMMENDATIONS.md for promoting + * These are more faithful than the older shared terminals in Combinators.kt (TIME_VALUE rejects + * "1min30s"; ByteSizeTerminal rejects "1G 512M"). They live in this file only so the batch does not + * collide with parallel edits to Combinators.kt; see RECOMMENDATIONS.md for promoting * them. */ @@ -81,99 +80,13 @@ class ValueChecked(private val inner: Combinator, private val accept: (String) - } // --------------------------------------------------------------------------------------------------- -// safe_atou* +// Bounds -/** - * An unsigned integer as systemd's `safe_atou*_full()` reads it - * (https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L429-L478). - * - * With [base] 0 (`safe_atou`, `safe_atou8`, `safe_atou32`, …) that is: - * - decimal, C octal (`0755`) or `0x` hex — strtoul(base 0); - * - Python-style `0b101` / `0o17` — mangle_base(), parse-util.c#L403; - * - an optional leading `+` (strtoul accepts it; test-parse-util.c asserts `+12345`), but not in front - * of `0b`/`0o` (mangle_base only looks at the first character); - * - a leading `-` only when the value is zero (`-0`): safe_atou_full returns ERANGE for `l != 0 && - * s[0] == '-'`. - * - * With [base] 16 (`safe_atou32_full(.., 16)`) the digits are always hexadecimal, with an optional `0x`. - * - * The result must lie in `[minInclusive, maxExclusive)`. - */ -class QdiscSafeAtouTerminal( - private val minInclusive: Long, - private val maxExclusive: Long, - private val base: Int = 0, -) : TerminalCombinator { - - private val shape: Regex = when (base) { - 0 -> Regex("""[+-]?(?:0[xX][0-9a-fA-F]+|0[bBoO][0-9a-zA-Z]+|[0-9]+)""") - 16 -> Regex("""[+-]?(?:0[xX])?[0-9a-fA-F]+""") - else -> throw IllegalArgumentException("unsupported base $base") - } - - private fun span(value: String, offset: Int): String? = shape.matchAt(value, offset)?.value - - private fun valueOf(text: String): BigInteger? { - var s = text - val negative = s.startsWith("-") - val signed = negative || s.startsWith("+") - if (signed) s = s.substring(1) - val n: BigInteger? = try { - when { - base == 16 -> BigInteger(if (s.startsWith("0x") || s.startsWith("0X")) s.substring(2) else s, 16) - s.startsWith("0x") || s.startsWith("0X") -> BigInteger(s.substring(2), 16) - s.startsWith("0b") || s.startsWith("0B") -> if (signed) null else BigInteger(s.substring(2), 2) - s.startsWith("0o") || s.startsWith("0O") -> if (signed) null else BigInteger(s.substring(2), 8) - s.length > 1 && s.startsWith("0") -> BigInteger(s.substring(1), 8) - else -> BigInteger(s, 10) - } - } catch (e: NumberFormatException) { - null - } - if (n == null) return null - if (negative && n.signum() != 0) return null - return n - } - - private fun inRange(text: String): Boolean = - valueOf(text)?.let { it >= BigInteger.valueOf(minInclusive) && it < BigInteger.valueOf(maxExclusive) } ?: false - - override fun SyntacticMatch(value: String, offset: Int): MatchResult { - val text = span(value, offset) ?: return NoMatch - return MatchResult(listOf(text), offset + text.length, listOf(this), offset + text.length) - } - - override fun SemanticMatch(value: String, offset: Int): MatchResult { - val text = span(value, offset) ?: return NoMatch - if (!inRange(text)) return NoMatch.copy(longestMatch = offset) - return MatchResult(listOf(text), offset + text.length, listOf(this), offset + text.length) - } - - override fun parse(value: String, offset: Int): Sequence { - val text = span(value, offset) ?: return sequenceOf(Stuck(offset, setOf(this))) - return sequenceOf(Parse(offset + text.length, listOf(ParsedToken(offset, offset + text.length, text, this, inRange(text))))) - } - - override fun toString(): String = "SafeAtou($minInclusive,$maxExclusive,base=$base)" - - override fun toStringIndented(indent: Int): String = toString() -} - -/** One past UINT32_MAX, the exclusive bound for every `safe_atou32` value. */ -const val QDISC_UINT32_LIMIT: Long = 4_294_967_296L - -/** One past UINT32_MAX as a BigInteger, for the size/time range checks. */ -private val UINT32_MAX_BIG: BigInteger = BigInteger.valueOf(QDISC_UINT32_LIMIT - 1) +/** UINT32_MAX, for the size/time range checks. */ +private val UINT32_MAX_BIG: BigInteger = SafeAtoTerminal.UINT32_MAX /** ULLONG_MAX: parse_size() and parse_time() return ERANGE above it. */ -private val UINT64_MAX_BIG: BigInteger = BigInteger.ONE.shiftLeft(64).subtract(BigInteger.ONE) - -/** A [QdiscSafeAtouTerminal] coloured as a literal. */ -fun qdiscSafeAtou(maxExclusive: Long, minInclusive: Long = 0L, base: Int = 0): Combinator = - Labeled(Role.LITERAL, QdiscSafeAtouTerminal(minInclusive, maxExclusive, base)) - -/** A whole value that is one safe_atou32() number. */ -fun safeAtou32Value(): Combinator = SequenceCombinator(qdiscSafeAtou(QDISC_UINT32_LIMIT), EOF()) +private val UINT64_MAX_BIG: BigInteger = SafeAtoTerminal.UINT64_MAX // --------------------------------------------------------------------------------------------------- // parse_sec / parse_time @@ -366,10 +279,20 @@ val SIZE_UINT32_MAX: BigInteger = UINT32_MAX_BIG // --------------------------------------------------------------------------------------------------- // parse_permyriad / parse_tc_percent -// The integer part goes through safe_atoi() (base 0, mangle_base) — so `0x10%` is 16% — and must not -// be negative (`-0` is fine). Its magnitude is capped by the 100% check below, so an int-sized bound -// on the terminal itself is enough. -private val PCT_INT = qdiscSafeAtou(Int.MAX_VALUE.toLong() + 1) +// The integer part goes through safe_atoi() (base 0, mangle_base), so `0x10%` is 16% and `0b 1%` is 1%, +// and parse_parts_value_*() return ERANGE when it is negative (`-0` is fine, and so is `0b-0`). Its +// magnitude is capped by the 100% check below, so int's range on the terminal itself is enough. The +// whole value reaches safe_atoi() up to the '.' or the symbol, so whitespace can reach the parser. +// https://github.com/systemd/systemd/blob/0f0682b773/src/basic/percent-util.c#L7-L23 +private val PCT_INT = SafeAtoTerminal( + signed = true, + base = 0, + flags = emptySet(), + minInclusive = 0L, + maxInclusive = Int.MAX_VALUE.toLong(), + acceptsWhitespace = true, + acceptsMinus = true, +) /** * A percentage as parse_permyriad() reads it @@ -388,24 +311,10 @@ val TC_PERCENT: Combinator = ValueChecked( private val PCT_EVAL = Regex("""(.*?)(?:\.([0-9]+))?([%‰‱])""") -/** parse_permyriad_unbounded(text), or null if not evaluable (sign/base handled like safe_atoi). */ +/** parse_permyriad_unbounded(text), or null if not evaluable (the integer part is read like [PCT_INT]). */ fun evaluatePermyriad(text: String): Long? { val m = PCT_EVAL.matchEntire(text) ?: return null - var intPart = m.groupValues[1] - val negative = intPart.startsWith("-") - if (negative || intPart.startsWith("+")) intPart = intPart.substring(1) - val n = try { - when { - intPart.startsWith("0x") || intPart.startsWith("0X") -> intPart.substring(2).toLong(16) - intPart.startsWith("0b") || intPart.startsWith("0B") -> intPart.substring(2).toLong(2) - intPart.startsWith("0o") || intPart.startsWith("0O") -> intPart.substring(2).toLong(8) - intPart.length > 1 && intPart.startsWith("0") -> intPart.substring(1).toLong(8) - else -> intPart.toLong() - } - } catch (e: NumberFormatException) { - return null - } - if (negative && n != 0L) return -1 + val n = PCT_INT.valueOf(m.groupValues[1])?.toLong() ?: return null val frac = m.groupValues[2] return when (m.groupValues[3]) { "%" -> n * 100 + (frac.padEnd(2, '0').toLongOrNull() ?: 0) diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseEtsPrioOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseEtsPrioOptionValueTest.kt index 0adc257e..5796bcb5 100644 --- a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseEtsPrioOptionValueTest.kt +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseEtsPrioOptionValueTest.kt @@ -9,6 +9,7 @@ class ConfigParseEtsPrioOptionValueTest : AbstractUnitFileTest() { @Test fun testValidValues() { // Fixture values from systemd test/test-network/conf plus tricky spellings the C parser accepts. + // sd-check at 0f0682b773 accepts 0b+1 and 0b-0 (a sign may follow the 0b prefix). // language="unit file (systemd)" val file = """ [EnhancedTransmissionSelection] @@ -18,6 +19,8 @@ class ConfigParseEtsPrioOptionValueTest : AbstractUnitFileTest() { PriorityMap=0 PriorityMap=255 PriorityMap=0x0f 017 0b1 + PriorityMap=0b+1 + PriorityMap=1 0b-0 2 PriorityMap=0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 PriorityMap= """.trimIndent() @@ -31,6 +34,7 @@ class ConfigParseEtsPrioOptionValueTest : AbstractUnitFileTest() { @Test fun testInvalidValues() { + // sd-check at 0f0682b773 rejects 0b 1 and 0b +1 (the list splits on blanks, so the word is "0b") and 0b-1 (ERANGE). // language="unit file (systemd)" val file = """ [EnhancedTransmissionSelection] @@ -39,13 +43,16 @@ class ConfigParseEtsPrioOptionValueTest : AbstractUnitFileTest() { PriorityMap=1,2 PriorityMap=a b PriorityMap=0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 0 + PriorityMap=0b 1 + PriorityMap=0b +1 + PriorityMap=0b-1 """.trimIndent() setupFileInEditor("file.network", file) enableInspection(InvalidValueInspection::class.java) val highlights = myFixture.doHighlighting() - assertSize(5, highlights) + assertSize(8, highlights) } } diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseFqCodelU32OptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseFqCodelU32OptionValueTest.kt index c4b622b4..bd8d3047 100644 --- a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseFqCodelU32OptionValueTest.kt +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseFqCodelU32OptionValueTest.kt @@ -9,6 +9,7 @@ class ConfigParseFqCodelU32OptionValueTest : AbstractUnitFileTest() { @Test fun testValidValues() { // Fixture values from systemd test/test-network/conf plus tricky spellings the C parser accepts. + // sd-check at 0f0682b773 accepts these: strtoul starts afresh after the 0b/0o prefix, so a sign or blank may follow it. // language="unit file (systemd)" val file = """ [FairQueueingControlledDelay] @@ -32,6 +33,18 @@ class ConfigParseFqCodelU32OptionValueTest : AbstractUnitFileTest() { Flows=+5 PacketLimit=-0 Flows=-0 + PacketLimit=0b+1 + Flows=0b+1 + PacketLimit=0b-0 + Flows=0b-0 + PacketLimit=0b 1 + Flows=0b 1 + PacketLimit=0o 7 + Flows=0o 7 + PacketLimit=0b +1 + Flows=0b +1 + PacketLimit=0b -0 + Flows=0b -0 PacketLimit= Flows= """.trimIndent() @@ -45,6 +58,7 @@ class ConfigParseFqCodelU32OptionValueTest : AbstractUnitFileTest() { @Test fun testInvalidValues() { + // sd-check at 0f0682b773 rejects these: 0b-1 and 0b -1 are -1 (ERANGE), -0b0 is -0 followed by junk (EINVAL). // language="unit file (systemd)" val file = """ [FairQueueingControlledDelay] @@ -64,13 +78,19 @@ class ConfigParseFqCodelU32OptionValueTest : AbstractUnitFileTest() { Flows=+0b1 PacketLimit=12 13 Flows=12 13 + PacketLimit=0b-1 + Flows=0b-1 + PacketLimit=0b -1 + Flows=0b -1 + PacketLimit=-0b0 + Flows=-0b0 """.trimIndent() setupFileInEditor("file.network", file) enableInspection(InvalidValueInspection::class.java) val highlights = myFixture.doHighlighting() - assertSize(16, highlights) + assertSize(22, highlights) } } diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseGredU32OptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseGredU32OptionValueTest.kt index 663fb67b..657eee48 100644 --- a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseGredU32OptionValueTest.kt +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseGredU32OptionValueTest.kt @@ -9,6 +9,7 @@ class ConfigParseGredU32OptionValueTest : AbstractUnitFileTest() { @Test fun testValidValues() { // Fixture values from systemd test/test-network/conf plus tricky spellings the C parser accepts. + // sd-check at 0f0682b773 accepts these: strtoul starts afresh after the 0b/0o prefix, so a sign or blank may follow it. // language="unit file (systemd)" val file = """ [GenericRandomEarlyDetection] @@ -28,6 +29,18 @@ class ConfigParseGredU32OptionValueTest : AbstractUnitFileTest() { DefaultVirtualQueue=020 VirtualQueues=+3 DefaultVirtualQueue=+3 + VirtualQueues=0b+1 + DefaultVirtualQueue=0b+1 + VirtualQueues=0b-0 + DefaultVirtualQueue=0b-0 + VirtualQueues=0b 1 + DefaultVirtualQueue=0b 1 + VirtualQueues=0o 7 + DefaultVirtualQueue=0o 7 + VirtualQueues=0b +1 + DefaultVirtualQueue=0b +1 + VirtualQueues=0b -0 + DefaultVirtualQueue=0b -0 VirtualQueues= DefaultVirtualQueue= """.trimIndent() @@ -41,6 +54,7 @@ class ConfigParseGredU32OptionValueTest : AbstractUnitFileTest() { @Test fun testInvalidValues() { + // sd-check at 0f0682b773 rejects these: 0b-1 and 0b -1 are -1 (ERANGE), -0b0 is -0 followed by junk (EINVAL). // language="unit file (systemd)" val file = """ [GenericRandomEarlyDetection] @@ -56,13 +70,19 @@ class ConfigParseGredU32OptionValueTest : AbstractUnitFileTest() { DefaultVirtualQueue=4294967296 VirtualQueues=1.0 DefaultVirtualQueue=1.0 + VirtualQueues=0b-1 + DefaultVirtualQueue=0b-1 + VirtualQueues=0b -1 + DefaultVirtualQueue=0b -1 + VirtualQueues=-0b0 + DefaultVirtualQueue=-0b0 """.trimIndent() setupFileInEditor("file.network", file) enableInspection(InvalidValueInspection::class.java) val highlights = myFixture.doHighlighting() - assertSize(12, highlights) + assertSize(18, highlights) } } diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseHtbClassU32OptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseHtbClassU32OptionValueTest.kt index 57e501ac..6401d690 100644 --- a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseHtbClassU32OptionValueTest.kt +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseHtbClassU32OptionValueTest.kt @@ -9,6 +9,7 @@ class ConfigParseHtbClassU32OptionValueTest : AbstractUnitFileTest() { @Test fun testValidValues() { // Fixture values from systemd test/test-network/conf plus tricky spellings the C parser accepts. + // sd-check at 0f0682b773 accepts these: strtoul starts afresh after the 0b/0o prefix, so a sign or blank may follow it. // language="unit file (systemd)" val file = """ [HierarchyTokenBucketClass] @@ -22,6 +23,12 @@ class ConfigParseHtbClassU32OptionValueTest : AbstractUnitFileTest() { Priority=017 Priority=+5 Priority=-0 + Priority=0b+1 + Priority=0b-0 + Priority=0b 1 + Priority=0o 7 + Priority=0b +1 + Priority=0b -0 Priority= """.trimIndent() @@ -34,6 +41,7 @@ class ConfigParseHtbClassU32OptionValueTest : AbstractUnitFileTest() { @Test fun testInvalidValues() { + // sd-check at 0f0682b773 rejects these: 0b-1 and 0b -1 are -1 (ERANGE), -0b0 is -0 followed by junk (EINVAL). // language="unit file (systemd)" val file = """ [HierarchyTokenBucketClass] @@ -45,13 +53,16 @@ class ConfigParseHtbClassU32OptionValueTest : AbstractUnitFileTest() { Priority=abc Priority=+0b1 Priority=12 13 + Priority=0b-1 + Priority=0b -1 + Priority=-0b0 """.trimIndent() setupFileInEditor("file.network", file) enableInspection(InvalidValueInspection::class.java) val highlights = myFixture.doHighlighting() - assertSize(8, highlights) + assertSize(11, highlights) } } diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseHtbDefaultClassOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseHtbDefaultClassOptionValueTest.kt index ee8804d3..c97135b9 100644 --- a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseHtbDefaultClassOptionValueTest.kt +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseHtbDefaultClassOptionValueTest.kt @@ -41,13 +41,15 @@ class ConfigParseHtbDefaultClassOptionValueTest : AbstractUnitFileTest() { DefaultClass=g DefaultClass=-1 DefaultClass=1 2 + DefaultClass=0b 1 + DefaultClass=0x 1 """.trimIndent() setupFileInEditor("file.network", file) enableInspection(InvalidValueInspection::class.java) val highlights = myFixture.doHighlighting() - assertSize(6, highlights) + assertSize(8, highlights) } } diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseHtbU32OptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseHtbU32OptionValueTest.kt index 12ee3c24..a09ee769 100644 --- a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseHtbU32OptionValueTest.kt +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseHtbU32OptionValueTest.kt @@ -9,6 +9,7 @@ class ConfigParseHtbU32OptionValueTest : AbstractUnitFileTest() { @Test fun testValidValues() { // Fixture values from systemd test/test-network/conf plus tricky spellings the C parser accepts. + // sd-check at 0f0682b773 accepts these: strtoul starts afresh after the 0b/0o prefix, so a sign or blank may follow it. // language="unit file (systemd)" val file = """ [HierarchyTokenBucket] @@ -22,6 +23,12 @@ class ConfigParseHtbU32OptionValueTest : AbstractUnitFileTest() { RateToQuantum=017 RateToQuantum=+5 RateToQuantum=-0 + RateToQuantum=0b+1 + RateToQuantum=0b-0 + RateToQuantum=0b 1 + RateToQuantum=0o 7 + RateToQuantum=0b +1 + RateToQuantum=0b -0 RateToQuantum= """.trimIndent() @@ -34,6 +41,7 @@ class ConfigParseHtbU32OptionValueTest : AbstractUnitFileTest() { @Test fun testInvalidValues() { + // sd-check at 0f0682b773 rejects these: 0b-1 and 0b -1 are -1 (ERANGE), -0b0 is -0 followed by junk (EINVAL). // language="unit file (systemd)" val file = """ [HierarchyTokenBucket] @@ -45,13 +53,16 @@ class ConfigParseHtbU32OptionValueTest : AbstractUnitFileTest() { RateToQuantum=abc RateToQuantum=+0b1 RateToQuantum=12 13 + RateToQuantum=0b-1 + RateToQuantum=0b -1 + RateToQuantum=-0b0 """.trimIndent() setupFileInEditor("file.network", file) enableInspection(InvalidValueInspection::class.java) val highlights = myFixture.doHighlighting() - assertSize(8, highlights) + assertSize(11, highlights) } } diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseNetemRateOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseNetemRateOptionValueTest.kt index c8c22a8a..5a4ca6ee 100644 --- a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseNetemRateOptionValueTest.kt +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseNetemRateOptionValueTest.kt @@ -9,6 +9,7 @@ class ConfigParseNetemRateOptionValueTest : AbstractUnitFileTest() { @Test fun testValidValues() { // Fixture values from systemd test/test-network/conf plus tricky spellings the C parser accepts. + // sd-check at 0f0682b773 accepts the 0b/0o spellings: the integer part goes through safe_atoi(). // language="unit file (systemd)" val file = """ [NetworkEmulator] @@ -30,6 +31,18 @@ class ConfigParseNetemRateOptionValueTest : AbstractUnitFileTest() { DuplicateRate=1000‱ LossRate=0x10% DuplicateRate=0x10% + LossRate=0b 1% + DuplicateRate=0b 1% + LossRate=0b+1% + DuplicateRate=0b+1% + LossRate=0b-0% + DuplicateRate=0b-0% + LossRate=-0% + DuplicateRate=-0% + LossRate=0b 1.5% + DuplicateRate=0b 1.5% + LossRate=0o 7‰ + DuplicateRate=0o 7‰ LossRate= DuplicateRate= """.trimIndent() @@ -43,6 +56,7 @@ class ConfigParseNetemRateOptionValueTest : AbstractUnitFileTest() { @Test fun testInvalidValues() { + // sd-check at 0f0682b773 rejects 0b-1% and 0b -1% (the integer part is -1: ERANGE). // language="unit file (systemd)" val file = """ [NetworkEmulator] @@ -64,13 +78,17 @@ class ConfigParseNetemRateOptionValueTest : AbstractUnitFileTest() { DuplicateRate=% LossRate=20 % DuplicateRate=20 % + LossRate=0b-1% + DuplicateRate=0b-1% + LossRate=0b -1% + DuplicateRate=0b -1% """.trimIndent() setupFileInEditor("file.network", file) enableInspection(InvalidValueInspection::class.java) val highlights = myFixture.doHighlighting() - assertSize(18, highlights) + assertSize(22, highlights) } } diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParsePiePacketLimitOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParsePiePacketLimitOptionValueTest.kt index 844e7d91..298306d2 100644 --- a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParsePiePacketLimitOptionValueTest.kt +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParsePiePacketLimitOptionValueTest.kt @@ -9,6 +9,7 @@ class ConfigParsePiePacketLimitOptionValueTest : AbstractUnitFileTest() { @Test fun testValidValues() { // Fixture values from systemd test/test-network/conf plus tricky spellings the C parser accepts. + // sd-check at 0f0682b773 accepts these: strtoul starts afresh after the 0b/0o prefix, so a sign or blank may follow it. // language="unit file (systemd)" val file = """ [PIE] @@ -22,6 +23,12 @@ class ConfigParsePiePacketLimitOptionValueTest : AbstractUnitFileTest() { PacketLimit=017 PacketLimit=+5 PacketLimit=-0 + PacketLimit=0b+1 + PacketLimit=0b-0 + PacketLimit=0b 1 + PacketLimit=0o 7 + PacketLimit=0b +1 + PacketLimit=0b -0 PacketLimit= """.trimIndent() @@ -34,6 +41,7 @@ class ConfigParsePiePacketLimitOptionValueTest : AbstractUnitFileTest() { @Test fun testInvalidValues() { + // sd-check at 0f0682b773 rejects these: 0b-1 and 0b -1 are -1 (ERANGE), -0b0 is -0 followed by junk (EINVAL). // language="unit file (systemd)" val file = """ [PIE] @@ -45,13 +53,16 @@ class ConfigParsePiePacketLimitOptionValueTest : AbstractUnitFileTest() { PacketLimit=abc PacketLimit=+0b1 PacketLimit=12 13 + PacketLimit=0b-1 + PacketLimit=0b -1 + PacketLimit=-0b0 """.trimIndent() setupFileInEditor("file.network", file) enableInspection(InvalidValueInspection::class.java) val highlights = myFixture.doHighlighting() - assertSize(8, highlights) + assertSize(11, highlights) } } diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseTeqlIdOptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseTeqlIdOptionValueTest.kt index 943323c3..55c99220 100644 --- a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseTeqlIdOptionValueTest.kt +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/ConfigParseTeqlIdOptionValueTest.kt @@ -9,6 +9,7 @@ class ConfigParseTeqlIdOptionValueTest : AbstractUnitFileTest() { @Test fun testValidValues() { // Fixture values from systemd test/test-network/conf plus tricky spellings the C parser accepts. + // sd-check at 0f0682b773 accepts these: strtoul starts afresh after the 0b/0o prefix, so a sign or blank may follow it. // language="unit file (systemd)" val file = """ [TrivialLinkEqualizer] @@ -17,6 +18,12 @@ class ConfigParseTeqlIdOptionValueTest : AbstractUnitFileTest() { Id=2147483647 Id=0x7fffffff Id=0o7 + Id=0b+1 + Id=0b-0 + Id=0b 1 + Id=0o 7 + Id=0b +1 + Id=0b -0 Id= """.trimIndent() @@ -29,6 +36,7 @@ class ConfigParseTeqlIdOptionValueTest : AbstractUnitFileTest() { @Test fun testInvalidValues() { + // sd-check at 0f0682b773 rejects these: 0b-1 and 0b -1 are -1 (ERANGE), -0b0 is -0 followed by junk (EINVAL). // language="unit file (systemd)" val file = """ [TrivialLinkEqualizer] @@ -37,13 +45,16 @@ class ConfigParseTeqlIdOptionValueTest : AbstractUnitFileTest() { Id=-1 Id=abc Id=teql1 + Id=0b-1 + Id=0b -1 + Id=-0b0 """.trimIndent() setupFileInEditor("file.network", file) enableInspection(InvalidValueInspection::class.java) val highlights = myFixture.doHighlighting() - assertSize(5, highlights) + assertSize(8, highlights) } }