Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Interpretations

An Interpretation is a recorded reading of a Requirement that is ambiguous, silent on a case or in conflict with another, or a choice the RFC leaves to the implementation; udpstp still conforms. There are 117. Those marked as also an OB-UDPST Difference are readings OB-UDPST doesn’t share.

Load Rate Adjustment Algorithms (18)

shallow-queue-search-alternates-at-the-bottleneck

Through a queue too shallow for delay variation to reach upperThresh, Algorithm B confirms Congestion at its first overshoot below the 1 Gbps row: Fast Mode’s jump of highSpeedDelta rows past the bottleneck’s rate brings slowAdjThresh Errored Intervals back to back, and the third takes 3 × highSpeedDelta rows off, once. From then on, in Single-Step Mode, each Errored Interval takes one row off and each Clean one adds one back, and loss, the only signal such a queue gives, comes back a Trial Interval or two late, so the search settles into a cycle a few rows around the bottleneck’s rate and every Sub-Interval loses Load PDUs at the rows above it (5-8 % through 20 ms at 50 Mbit/s with the default seqErrThresh, about 2 % with 0). Above the 1 Gbps row there is no Fast Mode: the search climbs a row at a time, its first overshoot is one row, and it cycles the same way, but its Errored Intervals can come too few at a time to confirm Congestion, which it then never does: through 1 ms of queue at 2.5 Gbit/s, one or two at a time in the simulator, and in network namespaces through tbf two at a time, three in some Tests; through 20 ms, three at a time, which confirms it. The share of the Load PDUs the cycle loses in every Sub-Interval at the bottleneck’s rate grows with the row’s step against the rate: in the simulator about 2 % at 2.5 Gbit/s through 20 ms over a 10 ms round trip and 3-4 % at 1.2 Gbit/s, growing with the path’s delay at the points tested there. Above the 1 Gbps row, where the rows are 100 Mbps apart, it is often above 1 %; below it, from a few hundred Mbit/s up, near 1 %, which the path’s delay moves either way. The Client’s warning that no search found the path’s limit takes a search that never confirmed Congestion as having met it at its end: of its last 64 Trial Intervals, from the first Errored Interval among them on, at least 4 and at least 1 in 4 were errored. udpstp runs the algorithm as specified rather than adding a back-off of its own, and takes RFC 9097 §6.3’s maximum over every Sub-Interval that meets the Performance Criterion, the search’s climb included; §6.5 doesn’t say what a maximum from a Sub-Interval without congestion means in a Test with it. So where no Sub-Interval at the bottleneck’s rate meets the criterion, such a Test reports no valid maximum (ErrorStatus 49), or, where a Sub-Interval of the climb met it, at the latest the one in which the search first overshot, a Maximum IP-Layer Capacity from the climb, well below the bottleneck and with no warning: a cliff, not a bias. 1960 Mbit/s of 2.5 Gbit/s through tbf’s 20 ms in network namespaces; in the simulator 1885 Mbit/s there, Fast Mode’s last Sub-Interval, 893 Mbit/s, at 1 to 2 Gbit/s, and 287 Mbit/s at 500 Mbit/s over a 40 ms round trip, where the cycle loses 1.04 %. Either way it gives the Highest IP-Layer Capacity with its Loss Ratio; --max-loss-ratio accepts more loss.

Also an OB-UDPST Difference.
RFC: RFC 9097 §6.3, §6.5, §8.1
Requirements: RFC9097-6.3-L1, RFC9097-6.3-3, RFC9097-6.3-4, RFC9097-8.1-L11
OB-UDPST: OB-UDPST’s search takes the same steps (one row each way above the 1 Gbps row, the confirming drop only below it, Sequence Errors counted without reordering and duplicates), so while the queue’s delay stays under lowThresh it cycles the same way with the same loss: measured at 50 Mbit/s, and above the 1 Gbps row read from its code. Between lowThresh and upperThresh its Hold Intervals differ, since it judges the RTT variation by default (record delay-var-is-the-maximum-by-default). It reports the Highest IP-Layer Capacity as its maximum whatever the loss (record json-at-max-is-the-maximum), there the bottleneck’s rate.

Proving tests:

fast-mode-stops-at-the-gigabit-row

Fast Mode never passes the 1 Gbps row (row 1000). Algorithm B adds highSpeedDelta rows but stops at that row; algorithm C jumps to it when doubling would pass it. Above it, the search moves in Single-Step Mode, one row at a time, as a Test already there would. RFC 9097 §8.1’s prose says the rate is only increased by one above a high rate such as 1 Gbps; Y.1540 Amd 2 and TR-471’s flowcharts, and RFC 9097’s example pseudocode, add highSpeedDelta with no cap, so a search just below the 1 Gbps row could overshoot it by up to highSpeedDelta - 1 rows, which above row 1000 are OB-UDPST’s much larger jumbo rows. OB-UDPST caps it the same way, so results match.

RFC: RFC 9097 §8.1; RFC 9946 §5.1
Requirements: RFC9946-5.1-7, RFC9097-8.1-L12
References: docs/spec.md

Proving tests:

latched-congestion-confirmed

Congestion is confirmed by slowAdjThresh Errored Intervals back to back: any Clean or Hold Interval resets the count, following RFC 9946 §5.1(5)’s “in all of the consecutive intervals”. Congestion Confirmed is then latched, so algorithm B never leaves Single-Step Mode (RFC 9946 §7), and algorithm C clears it only in a Fast-Mode Retry. Resetting the flowchart’s SlowAdjCount instead would let algorithm B return to Fast Mode and oscillate.

Also an OB-UDPST Difference.
RFC: RFC 9946 §5.1
Requirements: RFC9946-5.1-L1
OB-UDPST: Keeps the flowchart’s single SlowAdjCount, reset only by a Fast Mode pass, so errored, hold, errored, hold, errored also confirms Congestion. OB-UDPST can confirm Congestion earlier on paths with intermittent errors, so the two can report different capacities on the same path.
References: ADR 0005

Proving tests:

confirming-drop-below-the-gigabit-row-only

The Errored Interval that confirms Congestion takes 3 × highSpeedDelta rows off, stopping at row 0, only when the row is below the 1 Gbps row, as the flowchart’s rate < HSpeedThresh and RFC 9097’s pseudocode compare it. At or above the 1 Gbps row it takes one row off, like any other Errored Interval. The comparison is on row indexes: row N is N Mbps up to row 1000.

RFC: RFC 9946 §5.1
Requirements: RFC9946-5.1-7

Proving tests:

algorithm-c-doubles-the-row-index

Algorithm C’s “Increase Sending by 2x” doubles the Sending Rate Table row index, on every second Clean Interval in Fast Mode. Row 0 goes to row 1 on the first Fast Mode pass. Row N is N Mbps up to row 1000, so doubling the row doubles the rate there.

RFC: RFC 9946 §5.1

Proving tests:

retry-thresh-grows-by-5-after-a-step-up

RetryThresh starts at 5 and grows by 5 after a Fast-Mode Retry reached from a single step up (5, 10, 15, …), as the flowchart footer’s RETRY_THRESH_ALGOC and OB-UDPST have it; a retry reached from a single step down leaves it as it is. TR-471’s table text says it doubles (5, 10, 20, 40).

RFC: RFC 9946 §5.1
References: docs/spec.md

Proving tests:

retry-count-counts-actual-single-steps

Only a single step that moves the row counts toward RetryThresh: a Clean Interval at the Test’s highest row, or an Errored Interval at row 0, is none, and neither is the confirming drop. A Lost Status Backoff that moves the row one down counts. Y.1540 Amd 2 Table B.3 counts every status report; OB-UDPST counts as we do.

RFC: RFC 9946 §5.1

Proving tests:

fast-mode-retry-resets-the-errored-run

A Fast-Mode Retry clears Congestion Confirmed and also resets the count of back-to-back Errored Intervals, as the flowchart resets SlowAdjCount. This is a reset beyond ADR 0005’s reset by any Clean or Hold Interval: a retry reached on an Errored Interval starts the next run from 0.

RFC: RFC 9946 §5.1
References: ADR 0005

Proving tests:

update-persists-across-a-fast-mode-retry

Algorithm C’s Update flag toggles on each Fast Mode pass that doesn’t jump to the 1 Gbps row, and keeps its value across a Fast-Mode Retry, so the first pass after a retry may double the row or hold it. The documents don’t say whether a retry resets it; OB-UDPST keeps it too.

RFC: RFC 9946 §5.1

Proving tests:

lost-status-backoff-is-an-errored-interval

A Lost Status Backoff counts as an Errored Interval, following RFC 9097 §8.1’s “same process” with the algorithm’s variables “in their current state”: it adds to the back-to-back run, can confirm Congestion with the one-time drop of 3 × highSpeedDelta rows, and otherwise takes one row off. RFC 9097 describes it for algorithm B only; under algorithm C a backoff that moves the row also counts toward RetryThresh, so a long feedback outage can cycle through confirmation, drop and Fast-Mode Retry. At the defaults the third backoff, about 290 ms without a Status PDU, confirms Congestion.

Also an OB-UDPST Difference.
RFC: RFC 9097 §8.1
Requirements: RFC9097-8.1-13, RFC9097-8.1-L21
OB-UDPST: Has no Lost Status Backoff: it keeps its rate until its no-traffic timeouts.
References: ADR 0005

Proving tests:

lost-status-backoff-initial-w

RFC 9097 §8.1 lets a path whose RTT is longer than trialInt start w above 0. The Server starts w at the Setup Response’s round trip (from sending it to the Activation Request’s arrival) divided by trialInt, rounded up, capped so the first Lost Status Backoff still comes before the 1 s Status PDU timeout. Without it a path with an RTT above about 140 ms backs off three times before its first Status PDU can arrive and confirms Congestion at the start. Once a Status PDU arrives, w starts over at 0.

RFC: RFC 9097 §8.1
Requirements: RFC9097-8.1-14, RFC9097-8.1-L20

Proving tests:

stale-status-pdus-count-only-as-heard

A Status PDU whose spduSeqNo isn’t greater than the last one processed, or is 0, is stale, in both roles: it isn’t fed to the Load Rate Adjustment Algorithm, can’t change the Sending Rate Structure or deliver STOP, and isn’t echoed for RTT. It still counts as heard for the Watchdog and Lost Status Backoff, its Sub-Interval statistics still count, and it is counted (DataEvent::StaleStatusPdus). RFC 9097 runs the algorithm on each “new status report” without saying whether a reordered or duplicated one is new; this rule keeps a replayed Status PDU from steering or stopping a Test.

Also an OB-UDPST Difference.
RFC: RFC 9946 §8.2
Requirements: RFC9946-8.2-4, RFC9946-8.2-L2
OB-UDPST: Feeds every Status PDU, reordered or duplicated ones included, to the algorithm in a Downstream Test.
References: docs/spec.md story 92

Proving tests:

spdu-seq-err-is-the-gap-before-the-latest

The Load Sender’s spduSeqErr is the number of Status PDUs missing just before the latest one processed, not a running total, as OB-UDPST counts it; a gap above 0xFFFE is sent as 0xFFFE. RFC 9946 §8.1 calls it “the Status PDU loss count” without saying over what span.

Also an OB-UDPST Difference.
RFC: RFC 9946 §8.1
Requirements: RFC9946-8.1-L8
OB-UDPST: Sends 0xFFFF for a reordered Status PDU; udpstp treats that one as stale and never sends 0xFFFF.

Proving tests:

clean-needs-delay-var-below-low-thresh

A Clean Interval needs DelayVar strictly below lowThresh, as RFC 9097’s pseudocode and OB-UDPST have it; the Y.1540 and TR-471 flowcharts say <=. A DelayVar equal to lowThresh makes a Hold Interval.

RFC: RFC 9946 §5.1
Requirements: RFC9946-5.1-9

Proving tests:

seq-err-thresh-always-applied

RFC 9946 §5.1(6) says Congestion MUST be indicated when Sequence Errors are detected, then recommends a threshold of 10. udpstp always applies seqErrThresh: a Trial Interval is errored only when its Sequence Errors exceed it, and 0 is honoured, so any Sequence Error is errored then. The flowcharts, RFC 9097’s pseudocode and OB-UDPST do the same.

RFC: RFC 9946 §5.1
Requirements: RFC9946-5.1-9

Proving tests:

no-delay-sample-is-never-clean

A Trial Interval without a delay sample is never clean: it is a Hold Interval unless its Sequence Errors exceed seqErrThresh. A Trial Interval in which no Load PDU arrived is one too, since its loss shows only once a later Load PDU arrives. The documents don’t cover the case; OB-UDPST also treats it as no change.

RFC: RFC 9946 §5.1
Requirements: RFC9946-5.1-9

Proving tests:

stop-status-pdus-are-not-classified

A Status PDU whose testAction is STOP is not classified and doesn’t move the rate: it ends the Test rather than reporting a Trial Interval to adjust on. This holds for the Status PDUs the Server receives in a Downstream Test and those it sends in an Upstream Test. Once the Server has sent STOP1 it classifies none, even those still marked TEST: the Test is ending, so the rate no longer matters. Everything else about the PDUs received then is processed normally (RFC 9946 §9): they count in the results, reset the Watchdog, and give sequence numbers and RTT samples.

RFC: RFC 9946 §8.2, §9
Requirements: RFC9946-9-L2

Proving tests:

ipv4-based-sending-rate-structure-sizes

The payload sizes in the Sending Rate Structure are IPv4-based (IP packet size minus 28 bytes), and a Load Sender on IPv6 sends each datagram 20 bytes smaller, unless that would leave less than a Load PDU header, so packet sizes and rates are the same on both address families. This reconciles RFC 9946 §8.1’s “use the parameters unchanged” with §6.1’s maximum IP packet sizes, which a literal reading breaks on IPv6. The IP version comes from each Test Connection’s packets, never the socket family: an IPv4-mapped peer is IPv4. OB-UDPST uses the same convention, and our Client can’t tell an OB-UDPST Server from another, so both roles always apply it.

RFC: RFC 9946 §6.1, §8.1
Requirements: RFC9946-6.1-8, RFC9946-6.1-9, RFC9946-6.1-11, RFC9946-6.1-12, RFC9946-7.1-5, RFC9946-8.1-1, RFC9946-8.1-6
References: ADR 0004

Proving tests:

The Client (20)

mc-ident-from-a-held-port

The Client draws a Test’s mcIdent from a UDP socket it holds until the Test ends, connected toward the first Server: the socket’s port, XORed with a 16-bit hash of its local address, a mapping one-to-one over the non-zero ports. No other socket on the host holds that port meanwhile, so Clients on one host never share an mcIdent at once; a Server keys a Test on the Client’s address and mcIdent, and two Clients drawing the same one at random (about a 26 % chance among 200) cost a Test: the Server merged them and refused one (MCINVPAR, MultiConnectionIndexInUse), or dropped an identical Setup Request, sent in the same second, as a replay. The replay cache is unchanged. Hosts behind one NAT have different local addresses, so their mcIdents are spread as a random draw’s; hosts sharing one private address behind it (VMs on a hypervisor’s NAT, cloned images) share the hash, and draw only as widely as the host’s local port range, about 28,000 values on Linux. If the host gives no such socket, the draw is random.

Also an OB-UDPST Difference.
RFC: RFC 9946 §6.1
Requirements: RFC9946-6.1-L3
OB-UDPST: OB-UDPST’s Client draws mcIdent at random, and its Server doesn’t check that an mcIndex is unused within a Test.
References: docs/spec.md story 93

Proving tests:

setup-response-from-another-address

The Client authorizes destinations from local Server selection before receiving PDUs. In authenticated modes a Setup Response must come from the selected Server address or an alternate IP address explicitly authorized for that Server with –allow-server-address SERVER=IP, using its Standard Port, address family and locally selected IPv6 scope. Its verified digest authorizes the Ephemeral Port, never a new IP address or interface. Other selected Servers, other DNS answers and Null Requests do not add authorization. Unauthenticated Mode still accepts only the selected Server endpoint. The Activation exchange and Data Phase use only the authorized endpoint.

Also an OB-UDPST Difference.
RFC: RFC 9946 §6.3, §7.1
Requirements: RFC9946-6.3-1, RFC9946-7.1-1
OB-UDPST: OB-UDPST’s Client connects to whatever address the Setup Response came from, unauthenticated too.
References: ADR 0007, ADR 0009, ADR 0013

Proving tests:

unverified-setup-rejection-hint

A Setup Response that fails verification is ignored and the Test initiation timer decides the outcome, so a forged datagram can’t end a genuine Test (ADR 0009). When such a response comes from the Server’s address and rejects this Test Connection’s own request, its cmdResponse is still reported, marked unverified, as a hint to why the timer expired; an unsigned AUTHINV to a mode 2 request adds the diagnostic that the Server probably doesn’t support mode 2.

Also an OB-UDPST Difference.
RFC: RFC 9946 §5.3.1, §6.3
Requirements: RFC9946-6.3-1
OB-UDPST: OB-UDPST’s Client ends the Test at once on a Setup Response that fails verification, with ErrorStatus 107 or 108.
References: ADR 0009

Proving tests:

undefined-setup-cmd-response

A verified Setup Response whose cmdResponse is neither ACKOK nor a code §12.3.5 defines as an error, CHSR_CRSP_NONE (0) included, ends the Setup attempt as an unknown response.

RFC: RFC 9946 §6.3, §12.3.5
Requirements: RFC9946-6.3-3, RFC9946-6.3-4

Proving tests:

setup-response-protocol-version

The Client ignores a Setup Response whose protocolVer isn’t 20, as it ignores one with another PDU ID or cmdRequest, except a BADVER rejection, which carries the version the Server expects (§6.2.1) and is reported as such. §6.3 says to begin with the protocol version but not what to do when it differs.

Also an OB-UDPST Difference.
RFC: RFC 9946 §6.3
Requirements: RFC9946-6.3-2
OB-UDPST: OB-UDPST’s Client checks neither the Setup Response’s protocolVer nor its cmdRequest.

Proving tests:

minimum-test-connections

A Multi-Connection Test may go on with fewer Test Connections than it asked for, down to the Minimum Test Connections the Client user sets. By default all are required, so one failed Setup aborts the Test before any Activation Request is sent. RFC 9946 §4 leaves handling connection setup successes and failures to the Client process.

RFC: RFC 9946 §4
Requirements: RFC9946-4-L1
By default: off
Setting: --min-test-connections
References: docs/spec.md stories 56 and 57

Proving tests:

max-bandwidth-split-across-test-connections

The maxBandwidth the Client user gives (--max-bandwidth) is the whole Test’s: the Client splits it equally across the Test’s Test Connections, each Setup Request stating its share rounded down to a whole Mbps, so together they state no more than was asked for, and a figure below 1 Mbps a Test Connection is refused. RFC 9946 §6.1 defines maxBandwidth as what the Client expects “during the requested test”, which in a Multi-Connection Test could mean the Test or one Test Connection; a Server compares each Setup Request’s maxBandwidth with what it has left to admit, and searches that Test Connection no higher.

Also an OB-UDPST Difference.
RFC: RFC 9946 §6.1
Requirements: RFC9946-6.1-L7, RFC9946-6.1-6
OB-UDPST: OB-UDPST’s Client splits it the same way, but states at least 1 Mbps a Test Connection rather than refusing a smaller figure, so its Test Connections may together state more than was asked for.
References: docs/spec.md story 9

Proving tests:

multi-connection-setup-deadline

Every Setup Request goes out at once and the Activation Requests go out together. The Setup phase waits for every Setup until a deadline 1 s before the Client’s time limit (or half the limit, if shorter), so the Activation exchange has time left within the Test initiation timer. Past it, Setups still unanswered are abandoned once at least the Minimum Test Connections have succeeded; with fewer, the Setup phase waits on to the time limit.

RFC: RFC 9946 §6.1
Requirements: RFC9946-6.1-2
References: docs/spec.md story 57

Proving tests:

multi-connection-parameters-differ

Each Test Connection’s parameters, as its Activation Response sets them, are compared with the first activated Test Connection’s. If they differ, the parameters aren’t acceptable for one Test, and the Client aborts it (ErrorStatus 203).

RFC: RFC 9946 §7.3
Requirements: RFC9946-7.3-L1
References: docs/spec.md story 58

Proving tests:

multi-connection-isolated-failures

A Test Connection fails when its Data Phase stops hearing its peer (the 1 s Load PDU or Status PDU timeout) or ends any way but complete. The Test goes on while at least the Minimum Test Connections haven’t failed; below it, the healthy ones are stopped. RFC 9946 §4 makes cleaning up failed connections during a Test the Client process’s responsibility.

RFC: RFC 9946 §4
Requirements: RFC9946-4-L1
References: ADR 0002, docs/spec.md story 59

Proving tests:

multi-connection-path-failure

Two or more Test Connections falling silent within three Trial Intervals of each other are a path failure, and stop the Test even when at least the Minimum Test Connections remain. The Load Rate Adjustment Algorithm ends a Test on a loss of connectivity (§4.2), and a Test’s connections to one Server share a path. Three Trial Intervals allow for a Load Sender hearing its peer once a Trial Interval, and for a lost last Status PDU.

RFC: RFC 9946 §4, §4.2
Requirements: RFC9946-4.2-L5
References: docs/spec.md story 60

Proving tests:

sub-interval-results-from-the-test-only

Only Sub-Intervals that closed while testAction was TEST are results, up to the number the Test Interval holds (testIntTime / subIntPeriod). On the Load Sender the first sisSav received for a subIntSeqNo counts, from a stale Status PDU too; later copies replace nothing. A Sub-Interval never measured, such as a final one whose Status PDU was lost, is missing, never a zero.

RFC: RFC 9097 §5.3, RFC 9946 §8.2
References: docs/spec.md

Proving tests:

duplicates-count-as-received

For the Performance Criterion and the Loss Ratio reported, the Load PDUs sent in a Sub-Interval are those received plus those lost, and duplicates count as received: “correctly received” isn’t read as excluding them. Duplicates are still reported apart from loss.

RFC: RFC 9097 §5.3, §6.3
Requirements: RFC9097-5.3-L1, RFC9097-6.3-L1

No proving test.

loss-allowance-per-test-connection

A Sub-Interval meets the Performance Criterion if it lost at most the configured ratio of the Load PDUs sent, or a single Load PDU, so one loss at a low rate doesn’t disqualify it. In a Multi-Connection Test’s aggregate the allowance is one Load PDU per active Test Connection, so the aggregate is judged no more harshly than its Test Connections alone.

RFC: RFC 9097 §6.3
Requirements: RFC9097-6.3-3

Proving tests:

empty-sub-interval-meets-no-criterion

A Sub-Interval in which no Load PDU was received meets no Performance Criterion, so it is never the Maximum IP-Layer Capacity, even when the one-lost-Load-PDU allowance covers what was lost in it: the maximum is taken over bits transmitted and correctly received that meet the PM criteria, and none were received. With nothing lost either, its Loss Ratio is 0/0, undefined rather than zero. Otherwise a gap covering a whole Sub-Interval, such as a stall just under the 1 s traffic timeout, or an Upstream Load Receiver sending Status PDUs through a silence, made an empty Sub-Interval the only one to pass when every other failed the criterion, and the Test reported 0 Mbps as its maximum where it has none (ErrorStatus 49).

RFC: RFC 9097 §6.3
Requirements: RFC9097-6.3-L1

Proving tests:

tmax-within-the-sub-interval

Tmax, the longest a Load PDU may take and still count as received, is the rest of the Sub-Interval whose lpduSeqNo gap counted it as lost, so at most one Sub-Interval: a late Load PDU cancels that loss only within that Sub-Interval. TR-471 suggests 1000 ms; tying Tmax to the Sub-Interval keeps each Sub-Interval’s loss final when it closes.

RFC: RFC 9097 §4, §5.3
Requirements: RFC9097-4-L4, RFC9097-5.3-4, RFC9097-6.3-5

Proving tests:

aggregate-clock-delta-min

A Multi-Connection Test’s aggregate Sub-Interval takes the smallest clockDeltaMin of its Test Connections, as it does rttMinimum. Several Servers’ clocks differ, so clockDeltaMin is kept only when every Test Connection goes to one Server, and is left out otherwise. Each Test Connection fixes its wall-clock offset when it activates, so a wall-clock step between activations shifts the aggregate.

Also an OB-UDPST Difference.
RFC: RFC 9946 §4, §8.2
Requirements: RFC9946-4-L1
OB-UDPST: OB-UDPST takes the smallest clockDeltaMin of all Test Connections, whatever Servers they go to.
References: docs/spec.md

Proving tests:

multi-connection-common-grid

In a Downstream Multi-Connection Test the Client’s Load Receivers share one Sub-Interval grid, starting when the first Load PDUs should arrive: when the Activation Requests go out plus the shortest Setup round trip. The Test Connections’ Sub-Intervals of one subIntSeqNo then cover the same time, and their sum is one Sub-Interval’s IP-Layer Capacity. The last Sub-Interval ends before the Servers’ STOP, half a second after the Test Interval. A single Test Connection keeps its own grid.

Also an OB-UDPST Difference.
RFC: RFC 9946 §8.2, RFC 9097 §5.3
Requirements: RFC9097-5.3-L1
OB-UDPST: OB-UDPST sums the Test Connections’ Sub-Intervals by number, each on its own grid, in both directions.
References: docs/spec.md

Proving tests:

usdir-set-on-every-upstream-setup

The Client sets CHSR_USDIR_BIT in every Upstream Setup Request, whether or not it states a maxBandwidth, since the bit says which direction the maxBandwidth field is for.

Also an OB-UDPST Difference.
RFC: RFC 9946 §6.1
Requirements: RFC9946-6.1-L8
OB-UDPST: An OB-UDPST Client sets the bit only when it states a maxBandwidth (-B); otherwise the whole field is 0.

Proving tests:

results-name-the-phase-and-f

A Test is one Phase of RFC 9097 §9’s tables: a Search Test or a Fixed-Rate Test, with F Test Connections. The text names Phase and F once in its Results Summary heading and parameters (“Downstream Search Test”, “1 Test Connection (F)”), for both the Maximum IP-Layer Capacity per Capacity Mode and the table of IP-Layer Sender Bit Rates. The whole Test’s maxima aggregate all its Test Connections; individual maxima appear under each Test Connection’s heading. The JSON names both in TestType and NumberOfConnections.

RFC: RFC 9097 §9
Requirements: RFC9097-9-3, RFC9097-9-4, RFC9097-9-10

Proving tests:

The Control Phase and Admission (39)

auth-time-window-inclusive-modulo-2-32

A received authUnixTime is acceptable within 5 s of the receiver’s wall clock, in whole seconds, both edges included. The difference is taken modulo 2^32 as a signed 32-bit value, so times either side of the field’s 2106 wraparound compare correctly.

Also an OB-UDPST Difference.
RFC: RFC 9946 §5.3.1
Requirements: RFC9946-5.3.1-4
OB-UDPST: OB-UDPST compares 64-bit times and would reject every PDU after the 2106 wraparound.
References: ADR 0009

Proving tests:

authentic-setup-outside-time-window-is-silent

A Setup Request whose digest verifies but whose authUnixTime is outside the window is dropped silently. The §6.2 note makes it invalid, which comes before §6.2.1’s rule that a valid authDigest gets a coded rejection. Only Troubleshooting Mode answers it, with AUTHTIME.

Also an OB-UDPST Difference.
RFC: RFC 9946 §6.2, §6.2.1
Requirements: RFC9946-6.2-2, RFC9946-6.2.1-3
OB-UDPST: OB-UDPST always answers with CHSR_CRSP_AUTHTIME.
References: ADR 0009

Proving tests:

activation-auth-matches-setup

An Activation Request must carry its Setup Request’s authMode and keyId, checked before the digest as part of “authMode … appropriate” (§6.2 step 3); a mismatch is dropped silently like any other verification failure.

Also an OB-UDPST Difference.
RFC: RFC 9946 §5.4.1, §6.2, §7.2.1
Requirements: RFC9946-7.2-2, RFC9946-7.2.1-6, RFC9946-5.4.1-8
OB-UDPST: OB-UDPST doesn’t check the Activation Request’s keyId; its Client always sends the Setup’s.
References: ADR 0009

Proving tests:

troubleshooting-replies-unsigned-except-authtime

In Troubleshooting Mode a Setup Request that fails authentication gets a Setup Response carrying the reason. It is unsigned, since a failed digest leaves no valid key, except AUTHTIME: its digest verified, so it is signed with the Server Authentication Key. An unknown keyId, or a key outside its lifetime, gets AUTHFAIL like a bad digest.

RFC: RFC 9946 §6.2.1
Requirements: RFC9946-6.2.1-L1
By default: off
Setting: --troubleshooting, troubleshooting in TOML
References: ADR 0009

Proving tests:

troubleshooting-replies-rate-limited

Troubleshooting Mode answers at most 10 authentication failures a second, since a spoofed request gets its reply, and the Server warns every minute while it is on. Replays stay silent. Activation Requests that fail verification stay silent even then, although §7.2.1 permits an answer.

RFC: RFC 9946 §6.2.1, §7.2.1
Requirements: RFC9946-6.2.1-L1, RFC9946-7.2.1-L4
By default: off
Setting: --troubleshooting, troubleshooting in TOML

Proving tests:

server-limits-refuse-with-connfail

The Server’s own limits are refused with CHSR_CRSP_CONNFAIL: Tests per source, Test Connections per Test (24 by default) and in all, a Source’s budget, and a socket that can’t be opened. So an mcCount above 24 isn’t refused with MCINVPAR; the 25th Test Connection gets CONNFAIL. The Admitted Rate Cap and one Test’s share of it give CAPEXC. Every Activation refusal is CHTA_CRSP_BADPARAM, the only code §12.3.9 defines.

Also an OB-UDPST Difference.
RFC: RFC 9946 §6.2.1, §7.2.1, §12.3.5
Requirements: RFC9946-6.2.1-3, RFC9946-7.2.1-L2, RFC9946-12.3.5-L1
OB-UDPST: OB-UDPST refuses an mcCount above 24 with MCINVPAR, and sends CONNFAIL only when its connection table is full or allocation fails.

Proving tests:

per-test-cap-over-the-tests-life

The Test Connections per Test cap counts over the Test’s whole life, closed ones included, so a Test kept alive by one Test Connection can’t start new measurements under it. A Test ends when none of its Test Connections is admitted any more, one waiting for its socket included; a Setup from the same address and mcIdent after that starts a new Test.

RFC: RFC 9946 §11
Requirements: RFC9946-11-4

Proving tests:

multi-connection-agreement-checks

The Test Connections of a Test (one Client address and mcIdent) must agree: mcIdent non-zero, mcIndex below mcCount and unique within the Test, the same mcCount and authMode, and the same keyId in an authenticated mode; otherwise MCINVPAR. This is stricter than §4, which lets a Server treat each Test Connection as if from a separate Client; §6.1 lets a Server use mcIdent to associate them. The Test’s other mcIndexes are never waited for.

Also an OB-UDPST Difference.
RFC: RFC 9946 §4, §6.1
Requirements: RFC9946-6.1-L1, RFC9946-6.1-L3, RFC9946-4-L2
OB-UDPST: OB-UDPST checks only mcCount at most 24 and mcIndex below mcCount, keeps no per-mcIdent state, and accepts mcIdent 0.

Proving tests:

duplicate-setup-request-dropped

A copy of an admitted Setup Request (same address, mcIdent and mcIndex) is dropped silently, not refused as an mcIndex in use, so a datagram the network duplicated can’t end the Client’s Test.

RFC: RFC 9946 §6.2.1

Proving tests:

replay-cache-for-setup-requests

The replay cache remembers a verified Setup Request’s keyId and authDigest, only once it has passed the digest and time checks and is within its Source’s budget, so a flood can’t fill it faster than the budgets allow; a copy of one refused by its budget is judged afresh once the budget refills. A copy is dropped silently. Two Clients sharing a key that send byte-identical requests (same mcIdent, parameters and second) collide: the second is dropped as a replay.

Also an OB-UDPST Difference.
RFC: RFC 9946 §5.3.1
Requirements: RFC9946-5.3.1-4
OB-UDPST: OB-UDPST has no replay cache.

Proving tests:

replay-cache-activation-keyed-on-sender

Activation Requests are remembered by keyId, authDigest and sender address: the Test Connections of a Multi-Connection Test set up in the same second send byte-identical Activation Requests. One cache serves every socket, and an Activation Request must come from its Setup’s Client IP address, so a verbatim copy sent to a sibling’s Ephemeral Port is caught; a copy spoofed from another port of that address isn’t.

Also an OB-UDPST Difference.
RFC: RFC 9946 §5.3.1
Requirements: RFC9946-5.3.1-4
OB-UDPST: OB-UDPST has no replay cache.

Proving tests:

replay-cache-lifetime-and-capacity

An entry lives 11 s on the monotonic clock: 5 s after an authUnixTime stamped up to 5 s ahead, plus the truncated second. A wall-clock step back of 5 s or more can let an expired request in again. The cache is sized from the global budget (every Setup Request it lets through in 11 s, plus four Activation Requests per Test Connection; 220k entries by default, at most about 2 million) and refuses new verified requests while full.

RFC: RFC 9946 §5.3.1
Requirements: RFC9946-5.3.1-4

Proving tests:

unverified-setup-budget

A global budget, 20000 a second by default, caps the Setup Requests that reach a KDF run and an HMAC; the rest are dropped silently. It is spent once a Key Table row is found, so unknown keyIds and keys outside their lifetimes are dropped first without spending it, and by every allowed request in Unauthenticated Mode. Activation Requests draw on no budget. It is a fixed one-second window, which admits Setup Requests that arrive together, such as a Multi-Connection Test’s, all together while it has room.

RFC: RFC 9946 §11
By default: on
Setting: --unverified-setups-per-second, unverified_setups_per_second in TOML

Proving tests:

source-budget-spent-after-the-hmac

Each Source (an IPv4 address or IPv6 /64) has a budget of authentic Setup Requests, 48 at once and 5 more a second by default, spent only after the digest verifies, so a spoofed source can’t spend another’s. A key holder still can, by signing requests from its address. Requests in Unauthenticated Mode have no HMAC and spend none. Beyond the budget, only the first refusal is answered (conformance: authentic-setup-requests-left-unanswered).

RFC: RFC 9946 §11
By default: on
Setting: --source-setup-burst, --source-setups-per-second

Proving tests:

key-setup-budget

Each Key Table row has a budget of Setup Requests, 1000 at once and 1000 more a second by default, Unauthenticated Mode counting as one row. Operator Keys spend it too. It is spent after the digest verifies and the replay check, so a spoofed request spends none, and a replayed one at most once (a request the budget refused isn’t remembered, so a captured copy can come back once), and a request over it is dropped silently, never answered, unlike the per-Source budget’s first refusal, so a key holder’s flood gets no reflection. A key holder’s flood of authentic Setup Requests then costs the Server at most its key’s budget in admissions a second, and the rest what a forged request within the unverified budget costs (a KDF run and an HMAC, no admission), so it can’t saturate the control thread and, through the Server’s shared queue, keep other keys’ Clients from starting Tests. The flooding key’s own Clients share its budget: a trust-model limit (docs/security.md).

Also an OB-UDPST Difference.
RFC: RFC 9946 §11
OB-UDPST: OB-UDPST has no per-key budget.
By default: on
Setting: --key-setup-burst, --key-setups-per-second

Proving tests:

path-approximated-by-source

The path of §4.2’s one concurrent Test per path is approximated by the source: an IPv4 address, or an IPv6 /64 (IPv4-mapped addresses count as IPv4), so link-local and Teredo Clients may share one. A Test is the Client’s IP address plus mcIdent. Only activated Tests count, so a spoofed Setup Request can’t keep a source from testing; the first Test Connection to activate checks again, and a Test that no longer fits is refused with BADPARAM.

Also an OB-UDPST Difference.
RFC: RFC 9946 §4.2
Requirements: RFC9946-4.2-1
OB-UDPST: OB-UDPST has no per-source or per-path limit.

Proving tests:

admitted-rate-cap-per-direction

The Admitted Rate Cap applies to each direction separately: Upstream and Downstream Test Connections each sum their stated maxBandwidth (CHSR_USDIR_BIT masked) against it, as a link carries its rate both ways. docs/spec.md says a cap on total admitted maxBandwidth. A Setup Request stating no maxBandwidth is refused with NOMAXBW only when the Server requires one.

Also an OB-UDPST Difference.
RFC: RFC 9946 §6.1
Requirements: RFC9946-6.1-L7, RFC9946-6.1-L8
OB-UDPST: OB-UDPST also keeps a pool per direction, but with -B set it always refuses a maxBandwidth of 0 with NOMAXBW.
By default: off
Setting: --admitted-rate-cap, --require-max-bandwidth

Proving tests:

usdir-must-match-activation-direction

With an Admitted Rate Cap set, an Activation Request whose direction differs from its Setup Request’s CHSR_USDIR_BIT is refused with BADPARAM, since bandwidth admitted for one direction can’t be used in the other. Only when the Setup stated a nonzero maxBandwidth: OB-UDPST’s Client sets the bit only then. Without a cap the bit decides nothing.

RFC: RFC 9946 §6.1, §7.2.1
Requirements: RFC9946-6.1-L8

Proving tests:

admitted-rate-reserved-at-activation

Only activated Tests hold the Admitted Rate Cap: a Test reserves its whole stated rate, in each direction its Test Connections state, when its first Test Connection activates, so a Setup never activated can’t keep other Tests out of the cap. From then on each of its admitted Test Connections holds its rate, one still waiting for its socket included. Of Tests admitted together past the cap, one that no longer fits when it activates is refused whole with BADPARAM, never left half running.

Also an OB-UDPST Difference.
RFC: RFC 9946 §6.1, §11
Requirements: RFC9946-11-4
OB-UDPST: OB-UDPST reserves the rate at Setup.

Proving tests:

max-test-share-of-the-rate-cap

A Test without an Operator Key may state at most a share of the Admitted Rate Cap, all its Test Connections together: half by default, so at least two Tests can run whatever one states. An activated Test holds its whole maxBandwidth for its whole Test Interval, whatever it sends. A Setup past the share gets CAPEXC.

Also an OB-UDPST Difference.
RFC: RFC 9946 §11
Requirements: RFC9946-11-4
OB-UDPST: OB-UDPST lets one Test take the whole cap.
By default: on
Setting: --max-test-share, max_test_share in TOML

Proving tests:

unactivated-test-connection-share

Unactivated Test Connections have a separate share outside the global Test Connection cap: half that cap by default, at least the per-Test cap, and configurable above the global cap. The Ephemeral Port range must hold their sum. Activated Tests alone reserve global places, including admitted siblings awaiting their socket or activation. The first activation reserves the whole Test; if it no longer fits, BADPARAM (TestConnections in the log) refuses it and its remaining siblings until all close. The global cap is checked at Setup too, against reserved places, so a full cap still refuses without evicting an unrelated unactivated Test. When the unactivated share is full, a Setup that would otherwise be admitted closes one Unactivated Test Connection (Event::UnactivatedTestConnectionEvicted), as a SYN cache evicts, rather than being refused, so Setup Requests never activated can’t fill the global cap. It closes the oldest, by its Setup Response, of the Key Table row holding the most Unactivated Test Connections, those of Unauthenticated Mode counting as one group; of groups holding as many, the one whose oldest is oldest, so with one group the oldest of all. Setups still waiting for their socket count toward the share and their group but aren’t closed, so a group holding the most only in those closes no one’s and the new Setup is refused, keeping the share bounded. A key flooding authentic Setup Requests that never activate therefore evicts its own, and another key’s Client, a Multi-Connection Test’s Test Connections included, keeps its place until it activates, at any round trip within the Activation Watchdog, while that key holds fewer than half the share; the oldest of all would let such a flood admitted at R a second close any Client’s about 128/R seconds after it was admitted (at the default share). It is a fair share, not a guarantee: a flood split over k groups holds the share in about k + 1 equal parts, so another key holding more than about share/(k + 1) at once loses some. The flooding key’s own Clients, and in Unauthenticated Mode every unauthenticated Client, are not protected: a trust-model limit (docs/security.md). RFC 9946 says which Test Connection to close no more than it says to close one.

Also an OB-UDPST Difference.
RFC: RFC 9946 §11
Requirements: RFC9946-11-4
OB-UDPST: OB-UDPST has no such share; unactivated Test Connections hold their slot until the 3 s watchdog.
By default: on
Setting: --max-unactivated-test-connections, max_unactivated_test_connections in TOML

Proving tests:

first-verified-activation-connects

An Activation Request must come from its Setup’s Client IP address; the port may differ, for NATs that change it. The first one that passes verification connects the Test Connection’s socket to its address and port, before any answer goes out; in Unauthenticated Mode, the first that decodes.

Also an OB-UDPST Difference.
RFC: RFC 9946 §6.2.2, §7.2
OB-UDPST: OB-UDPST accepts the Activation Request from any address and port and connects to whichever sent it.

Proving tests:

test-has-one-direction

A Test has one direction, fixed by its first accepted Activation Request; a Test Connection of the same Test asking for the other direction is refused with BADPARAM. RFC 9946 puts the direction on each Test Connection, and §7.2.1 lets the Server reject a request.

Also an OB-UDPST Difference.
RFC: RFC 9946 §4, §7.1, §7.2.1
Requirements: RFC9946-7.2.1-L2
OB-UDPST: OB-UDPST keeps no per-Test state, so one mcIdent may mix directions (its own Client never does).

Proving tests:

fixed-rate-needs-operator-key

A Fixed-Rate Test (srIndexConf a row, CHTA_SRIDX_ISSTART clear) is refused with BADPARAM unless the Test Connection’s key is an Operator Key, since a consumer MUST NOT be able to initiate one. OB-UDPST key files have no role column, so an OB-UDPST Client needs a row our TOML Key Table marks as an Operator Key.

Also an OB-UDPST Difference.
RFC: RFC 9946 §4.1
Requirements: RFC9946-4.1-3
OB-UDPST: OB-UDPST accepts a Fixed-Rate Test from anyone and never sends BADPARAM; an OB-UDPST Client given BADPARAM exits with 152.

Proving tests:

sr-index-def-ignores-isstart

srIndexConf CHTA_SRIDX_DEF asks for the Server’s default search from row 0 whatever CHTA_SRIDX_ISSTART says; it is never a Fixed-Rate Test.

RFC: RFC 9946 §7.1
Requirements: RFC9946-7.1-3, RFC9946-7.1-4

Proving tests:

sr-index-conf-beyond-the-top-row-coerced

An srIndexConf above the Server’s top row (the first row at the table’s top rate, 1069 with the default 1500-byte MTU) is coerced to it and reported, as §7.2.1’s configured maximum, for a Fixed-Rate row and, where the start-row cap allows it (an Operator Key, or max_start_row raised), a starting row.

Also an OB-UDPST Difference.
RFC: RFC 9946 §7.2.1
Requirements: RFC9946-7.2.1-3
OB-UDPST: OB-UDPST does not apply the same top-row coercion.

Proving tests:

search-capped-at-stated-max-bandwidth

The Server never attempts a rate above the Client’s stated maxBandwidth: the search tops out at the highest row within it, in both directions, with or without an Admitted Rate Cap. The CHSR_USDIR_BIT is masked and ignored for this cap. Row M offers M Mbps with IPv4 headers (ADR 0004), so over IPv6 the cap is exceeded by about 1.6%.

Also an OB-UDPST Difference.
RFC: RFC 9946 §6.1, §11
Requirements: RFC9946-6.1-6, RFC9946-11-5, RFC9946-11-L2
OB-UDPST: OB-UDPST caps the search only in a RATE_LIMITING build with -B set, so an OB-UDPST Client stating maxBandwidth measures higher against an OB-UDPST Server than against ours.
References: ADR 0004

Proving tests:

row-above-max-bandwidth-coerced

A requested starting row or Fixed-Rate row above the stated maxBandwidth’s row is coerced to it and reported as an srIndexConf coercion, not rejected.

Also an OB-UDPST Difference.
RFC: RFC 9946 §7.2.1
Requirements: RFC9946-7.2.1-3, RFC9946-7.2.1-4
OB-UDPST: OB-UDPST doesn’t tie srIndexConf to maxBandwidth; it echoes a row up to its -I maximum.

Proving tests:

consumer-start-row-capped

A Test without an Operator Key may start its search no higher than row 100 by default; a higher starting row is coerced to it and reported. It may still climb past it. Below the 1 Gbps row the algorithms keep Fast Mode and their large step down, so a search started too high on a slow link drains in about 3 s; from row 1000 Algorithm B would step down one row a Trial Interval, flooding the link for the whole Test. Operator Keys are exempt.

Also an OB-UDPST Difference.
RFC: RFC 9946 §4.2, §7.2.1, §11
Requirements: RFC9946-7.2.1-2, RFC9946-7.2.1-3, RFC9946-11-5
OB-UDPST: OB-UDPST lets any Client start at any row up to its -I maximum, the whole table by default.
By default: on
Setting: --max-start-row, max_start_row in TOML

Proving tests:

use-ow-del-var-defaults-to-true

useOwDelVar defaults to True (one-way delay variation), as RFC 9946 §7.1 says, although the TR-471 EnableIPDV it cites defaults to False.

Also an OB-UDPST Difference.
RFC: RFC 9946 §7.1
Requirements: RFC9946-7.1-L4
OB-UDPST: OB-UDPST defaults to False (RTT variation). Interop runs pin it to False.

Proving tests:

parameter-ranges-follow-y1540

The Activation parameters’ ranges come from Y.1540 Amd 2 Tables B.1 and B.2 first: thresholds 5 to 250 ms, trialInt 5 to 250 ms (TR-471 advises 20 to 250), testIntTime 5 to 60 s, subIntPeriod 100 to 10000 ms, highSpeedDelta and slowAdjThresh from 2, seqErrThresh 0 to 100. The number of Sub-Intervals isn’t bounded (Y.1540 gives 1 to 100).

RFC: RFC 9946 §7.1, §7.2.1
Requirements: RFC9946-7.2.1-L1

Proving tests:

low-thresh-above-upper-lowered

A lowThresh above upperThresh is lowered to upperThresh.

Also an OB-UDPST Difference.
RFC: RFC 9946 §7.2.1
OB-UDPST: OB-UDPST resets both thresholds to their defaults.

Proving tests:

sub-interval-shortened-to-test-interval

A subIntPeriod longer than the Test Interval is shortened to it.

Also an OB-UDPST Difference.
RFC: RFC 9946 §7.2.1
OB-UDPST: OB-UDPST resets both testIntTime and subIntPeriod to their defaults.

Proving tests:

sub-interval-whole-trial-intervals

The Server coerces subIntPeriod to the nearest whole number of Trial Intervals, within its range and the Test Interval, since a Sub-Interval closes at a Status PDU; every Sub-Interval then runs its nominal length, as RFC 9097 §5.3’s equal durations need. Of those, it takes the nearest that divides the Test Interval (I = m * dt), the shorter on a tie; when the Trial Interval doesn’t divide the Test Interval none can, and the Test Interval’s last part, shorter than a Sub-Interval, is no result.

Also an OB-UDPST Difference.
RFC: RFC 9097 §5.3; RFC 9946 §7.2.1
Requirements: RFC9097-5.3-2, RFC9097-5.3-6, RFC9097-6.3-2, RFC9097-6.3-7
OB-UDPST: OB-UDPST keeps subIntPeriod and divides by each Sub-Interval’s measured time.

Proving tests:

test-interval-over-60-s

testIntTime is at most 60 s, or an Operator Key’s own maximum. Above 60 s the Server coerces useOwDelVar to RTT variation, which clock drift doesn’t distort. A Client refuses a Test Interval above the larger of its request and 60 s.

Also an OB-UDPST Difference.
RFC: RFC 9946 §5.1, §7.2.1, §7.3
Requirements: RFC9946-7.3-L1
OB-UDPST: OB-UDPST allows up to its -t maximum, 3600 s by default, and keeps useOwDelVar.

Proving tests:

congestion-inference-within-500-ms

The Server keeps slowAdjThresh × trialInt within 500 ms by lowering slowAdjThresh (to no less than 2), not trialInt. Only the Server applies the rule, since a Client doesn’t know its key’s role. Operator Keys are exempt (conformance: operator-keys-exempt-from-500-ms-congestion-inference).

Also an OB-UDPST Difference.
RFC: RFC 9946 §5.1
Requirements: RFC9946-5.1-8
OB-UDPST: OB-UDPST doesn’t limit slowAdjThresh × trialInt.

Proving tests:

dscp-from-an-allowed-set-never-ect

The Server grants dscpEcn’s DSCP only when it is in the set the key allows (only 0 for a Consumer by default; all but CS6 and CS7 for an Operator Key), and coerces another to 0. Its ECN field is always coerced to Not-ECT, since UDPSTP isn’t an ECN-capable transport.

Also an OB-UDPST Difference.
RFC: RFC 9946 §7.1, §7.2.1
Requirements: RFC9946-7.1-2
OB-UDPST: OB-UDPST allows ECT codepoints and caps the whole octet at a configured maximum (255 by default) rather than checking a set of DSCPs.
By default: on
Setting: --allowed-dscp, --operator-allowed-dscp

Proving tests:

client-refuses-a-changed-dscp

A Client accepts a Server lowering its DSCP to 0 but ends the Test if the Server changes it to another DSCP, so its traffic goes only into the class it asked for, or best effort. ECN bits in the response are ignored; the Client sends Not-ECT.

RFC: RFC 9946 §7.3
Requirements: RFC9946-7.3-L1

Proving tests:

sending-rate-table-follows-the-server-shape

The Server serves one table shape, set by its CHSR_JUMBO_STATUS and CHSR_TRADITIONAL_MTU settings (jumbo on, traditional MTU off by default) and its Server MTU (server_mtu); a Setup Request with other modifiers is refused with BADJS or BADTMTU. Rows 1 to 1000 offer N Mbps, split as OB-UDPST splits them; rows 1001 to 1152 offer OB-UDPST’s rates above 1 Gbps, with packets no bigger than the Server MTU (more, smaller packets keep the rate). Rows after the first at the top rate repeat it, and the Server goes no higher.

Also an OB-UDPST Difference.
RFC: RFC 9946 §6.1, §6.2.1
Requirements: RFC9946-6.1-8, RFC9946-6.1-9, RFC9946-6.1-11, RFC9946-6.1-12, RFC9946-6.2.1-2
OB-UDPST: OB-UDPST’s rows above 1000 use packets up to 9000 bytes when jumbo is on, whatever the path MTU.
By default: on
Setting: --jumbo, --traditional-mtu, --server-mtu
References: ADR 0004

Proving tests:

The Data Phase (32)

receive-timeouts-stop-sending-at-1s

After 1 s without Load PDUs the Load Receiver stops sending Status PDUs, and after 1 s without Status PDUs the Load Sender stops sending Load PDUs (§5.1 items 7-9). The Watchdog ends the Test Connection 3 s after the last valid PDU, §6.1’s 1 s warning plus 2 s. ADR 0002 reconciles the two sections.

Also an OB-UDPST Difference.
RFC: RFC 9946 §5.1, §6.1
Requirements: RFC9946-5.1-11
OB-UDPST: OB-UDPST follows §6.1 alone: its 1 s timer only warns and sets rxStopped, and traffic keeps flowing until its 3 s watchdog ends the Test Connection.
References: ADR 0002

Proving tests:

rx-stopped-from-half-the-timeout

rxStopped is asserted once 0.5 s, half the 1 s receive timeout, has passed without a PDU, so the PDUs sent before an end stops carry it. §8.1 recommends 1 s, which the 1 s timeouts would never let a peer see.

Also an OB-UDPST Difference.
RFC: RFC 9946 §8.1
Requirements: RFC9946-8.1-L5
OB-UDPST: OB-UDPST asserts rxStopped from 1 s, and keeps sending until 3 s.
References: ADR 0002

Proving tests:

receive-timeout-is-final

Once a receive timeout has stopped an end sending, traffic that comes back neither restarts sending nor resets the Watchdog: §5.1’s “SHALL be closed” is read as final, over §8.1’s “until traffic is received again”.

RFC: RFC 9946 §5.1, §8.1
Requirements: RFC9946-5.1-13, RFC9946-5.1-16, RFC9946-8.1-4, RFC9946-8.1-12
References: ADR 0002

Proving tests:

receive-timeouts-run-from-data-phase-start

The Load PDU and Status PDU timeouts and the Watchdog run from the start of the Data Phase, so a peer that never sends stops this end after 1 s and ends the Test Connection after 3 s. The RFC doesn’t say when they start.

Also an OB-UDPST Difference.
RFC: RFC 9946 §5.1
Requirements: RFC9946-5.1-11
OB-UDPST: OB-UDPST also measures from activation, but only warns at 1 s.
References: ADR 0002

No proving test.

undefined-flags-are-invalid

A Load PDU or Status PDU whose testAction is above TEST_ACT_STOP2, or whose rxStopped isn’t 0 or 1, is invalid and counted, as OB-UDPST treats it. §8.1 defines the values but not what to do with others.

RFC: RFC 9946 §8.1
Requirements: RFC9946-8.1-3

Proving tests:

server-test-interval-timer-runs-half-a-second-over

The Server’s Test Interval timer sets TEST_ACT_STOP1 at testIntTime plus 0.5 s, as OB-UDPST’s does, so the last Sub-Interval completes: Sub-Intervals open at the first Status PDU, just after activation. §9 reads as exactly testIntTime.

RFC: RFC 9946 §9
Requirements: RFC9946-9-1

Proving tests:

stop-confirmation-waits

After STOP1 the Server keeps marking its PDUs TEST_ACT_STOP2 for one Trial Interval plus 1 s, waiting for the Client’s confirmation, then ends the Test Connection unconfirmed. The Client ends by its own Test Interval timer 1 s past the Server’s STOP1, so the Server’s STOP2 has time to cross the path.

Also an OB-UDPST Difference.
RFC: RFC 9946 §9
Requirements: RFC9946-9-2
OB-UDPST: An OB-UDPST Client ends by its own timer at testIntTime plus 3.5 s. An OB-UDPST Server in an Upstream Test sends only one STOP2 Status PDU.

Proving tests:

stop-test-action-per-role

The Client acts on TEST_ACT_STOP2 only: TEST_ACT_STOP1 is the Server’s local state, so the Client ignores it. The Server takes any valid testAction but TEST_ACT_TEST as the Client’s STOP, because an OB-UDPST Client that ends by its own timer sends STOP1.

Also an OB-UDPST Difference.
RFC: RFC 9946 §8.1, §9
Requirements: RFC9946-9-4, RFC9946-9-8
OB-UDPST: An OB-UDPST Client takes any testAction but TEST_ACT_TEST as the Server’s STOP.

Proving tests:

stop-ends-counting

The Client confirms a STOP once, with its next PDU, then ends. If STOP2 is its first Load PDU, no Status PDU timer exists yet, so it confirms at once. A STOP ends the Load Receiver’s counting, as in OB-UDPST: the Load PDU carrying it is not counted, and later ones are ignored.

RFC: RFC 9946 §9
Requirements: RFC9946-9-4

Proving tests:

stop-bursts-of-one-without-add-on

Once a Load Sender marks its Load PDUs TEST_ACT_STOP2, each Burst is one datagram with no Add-on: §9’s MAY for Bursts of one is read as one datagram in all.

Also an OB-UDPST Difference.
RFC: RFC 9946 §9
Requirements: RFC9946-9-3, RFC9946-9-6
OB-UDPST: OB-UDPST cuts the Burst to one datagram but keeps the Add-on.

Proving tests:

client-initiated-stop

A Client that ends a Test early (a Coercion it refuses, or DataPhase::stop) sends one PDU marked TEST_ACT_STOP2 at once and ends, within 1 s if the PDU can’t go out, so the Server ends its Test Connection at once instead of by its Watchdog. §9 defines only a Server-initiated stop. A STOP that reaches the Server before its own STOP1 ends the Test Connection as StoppedByClient; an on-path attacker setting STOP (§11) looks the same.

RFC: RFC 9946 §9, §11

Proving tests:

server-initiated-stop

When its administrator stops the Server, each active Test Connection makes a final send attempt marked TEST_ACT_STOP2 at once and ends as Stopped, without waiting for confirmation. As Load Sender, its Send Plan includes any Deferred Datagrams. If the PDU cannot be handed to the runtime, the Data Phase ends within 1 s. An end already silenced by its receive timeout sends nothing. This lets a reachable Client complete its Test instead of ending by its Watchdog. Section 9 defines the timer-triggered STOP exchange but not an administrator’s shutdown; its ordinary timer-driven behavior is unchanged.

RFC: RFC 9946 §5.1, §9
References: ADR 0014

Proving tests:

random-size-flag

OB-UDPST’s random-size flag (0x80000000 in a size field), which RFC 9946 doesn’t define, is honoured: a size is drawn once per Burst, uniformly from 52 bytes (32 over IPv6) up to the field’s low 31 bits, as OB-UDPST does. A random field whose maximum is 0 sends no datagram. OB-UDPST’s row 0 sets it on the Add-on of every Upstream search.

RFC: RFC 9946 §8.1
Requirements: RFC9946-8.1-6

Proving tests:

sending-rate-structure-only-from-test-status-pdus

The Client takes a new Sending Rate Structure only from Status PDUs marked TEST_ACT_TEST, as OB-UDPST does: a STOP Status PDU’s needn’t be one to send at.

RFC: RFC 9946 §8.1
Requirements: RFC9946-8.1-6

Proving tests:

lpdu-time-per-burst

Every Load PDU of a Send Plan carries the same lpduTime, read once just before the headers are written. lpduTime is the Test Time Base plus a wall-clock offset fixed at activation, so wall-clock steps don’t move it. OB-UDPST also stamps once per Burst, from its cached loop clock.

RFC: RFC 9946 §8.1
Requirements: RFC9946-8.1-L10
References: docs/spec.md Time model

Proving tests:

deferred-datagrams-and-withheld-bursts

Datagrams of a Send Plan the host didn’t take are Deferred Datagrams: they lead the next Send Plan (a STOP one included) with their sequence numbers and a fresh lpduTime, so host backpressure never shows as loss. A failed send defers the rest of its Send Plan. A new Burst is a Withheld Burst, counted and reported (DataEvent::BurstsWithheld), while at least as many Deferred Datagrams as it holds are waiting or it would overflow the Send Plan. The RFC doesn’t say what a Load Sender does with datagrams the host refuses.

Also an OB-UDPST Difference.
RFC: RFC 9946 §8.1
OB-UDPST: OB-UDPST drops the datagrams the host refused and, with seqNumAdjust, rolls lpduSeqNo back so they don’t count as loss.
References: ADR 0008

Proving tests:

sub-interval-boundaries

A Sub-Interval opens when the first Status PDU is sent. It closes just after the first Status PDU sent more than subIntPeriod minus trialInt/2 after it opened (whole ms, strict >), so the next Status PDU reports it. Until one has completed, subIntSeqNo is 0 and sisSav is zero. accumTime adds up each Sub-Interval’s duration rounded to ms. The RFC is silent on these; they are OB-UDPST’s, so both report the same Sub-Intervals.

RFC: RFC 9946 §8.2
Requirements: RFC9946-8.2-L3, RFC9946-8.2-L11

Proving tests:

status-pdu-grid-from-first-load-pdu

Status PDUs stay on a Trial Interval grid from the first Load PDU received, so a late one doesn’t move the later ones, and the first tiDeltaTime is measured from the first Load PDU.

Also an OB-UDPST Difference.
RFC: RFC 9946 §8.2
Requirements: RFC9946-8.2-1
OB-UDPST: OB-UDPST re-arms its Status PDU timer after each send, and measures the first tiDeltaTime from activation.

Proving tests:

received-bytes-from-datagram-length

The Load Receiver counts each Load PDU’s bytes from the datagram’s actual length, which the runtime knows even when it reads only the header, not from its udpPayload field, which §8.1 says allows accurate counts under receive truncation. A peer can’t inflate the results by stating a larger udpPayload.

Also an OB-UDPST Difference.
RFC: RFC 9946 §8.1
Requirements: RFC9946-8.1-L7
OB-UDPST: OB-UDPST sums the udpPayload fields.

Proving tests:

status-pdu-auth-fields-ignored-outside-mode-2

Outside Authentication Mode 2 a Load Sender doesn’t check a Status PDU’s keyId, authUnixTime and authDigest, which the RFC has zero there; only mode 2 authenticates Status PDUs. udpstp sends them zero.

Also an OB-UDPST Difference.
RFC: RFC 9946 §5.3.2, §6.2
Requirements: RFC9946-5.3.2-6
OB-UDPST: OB-UDPST carries other data in these fields.

Proving tests:

sequence-errors-count-load-pdus

Sequence Errors count Load PDUs, not events: a gap of n counts n lost. The Load PDUs before the first one received count as lost, none if that first lpduSeqNo is 0 or more than 1024 from 1.

RFC: RFC 9946 §8.2

Proving tests:

late-load-pdu-decrement-in-its-own-interval

An out-of-order Load PDU takes one off the loss count, as OB-UDPST does, but only in a Trial Interval or Sub-Interval that counted it as lost.

Also an OB-UDPST Difference.
RFC: RFC 9946 §8.2
Requirements: RFC9946-8.2-L9
OB-UDPST: OB-UDPST takes it off whatever interval is open, which can cancel an unrelated loss.

Proving tests:

lpdu-seq-no-modulo-2-32

lpduSeqNos are compared modulo 2^32, so counting carries on across the wrap from 4294967295 to 0. A Load PDU exactly 2^31 ahead counts as late.

Also an OB-UDPST Difference.
RFC: RFC 9946 §8.2
Requirements: RFC9946-8.2-L9
OB-UDPST: OB-UDPST doesn’t handle the wrap.

Proving tests:

ms-rounding-half-up

A span in ms is rounded half up with floor division (−0.5 ms is 0, −0.5000001 ms is −1), per Load PDU, before the minimum and the subtraction, as OB-UDPST’s tspecmsec does. §8.2 doesn’t say how to round.

RFC: RFC 9946 §8.2
Requirements: RFC9946-8.2-L10

Proving tests:

clock-delta-min-encoding

clockDeltaMin is sent as a two’s-complement 32-bit integer, as OB-UDPST sends it, and never read as “no value” (−1 is 0xFFFFFFFF). It saturates at the 32-bit limits in ms, and is 0 until the first Load PDU. The RFC gives no encoding for a negative value.

RFC: RFC 9946 §8.2
Requirements: RFC9946-8.2-L14

Proving tests:

delay-samples-from-in-order-load-pdus

Only in-order Load PDUs give delay and RTT samples, and the first one only seeds clockDeltaMin, as in OB-UDPST.

RFC: RFC 9946 §8.2

Proving tests:

wall-clock-step-drops-delay-samples

When the clock pair around a read disagrees (the wall clock stepped during it), that read’s delay and RTT samples are dropped and counted (DataEvent::DelaySamplesDropped): docs/spec.md’s “flagged” read as not trusted.

RFC: RFC 9946 §8.2
References: docs/spec.md Time model

Proving tests:

kernel-receive-timestamp-clamped

A kernel receive timestamp, a Load PDU’s or a Status PDU’s, is kept no later than the read, and no earlier than the 1 s receive timeout before it, since a datagram may have waited in the socket buffer.

RFC: RFC 9946 §8.1, §8.2
References: docs/spec.md Time model

Proving tests:

rtt-sample-rules

A Load PDU gives an RTT sample only when it echoes a new spduTime; spduTime 0 has nothing to echo. The RTT is the receive time minus spduTime minus rttRespDelay. An rttRespDelay one more than the raw RTT is rounding and gives 0, so rttMinimum may be 0 (rtt-minimum-may-be-zero); a larger one leaves the RTT as it is. Both as in OB-UDPST.

RFC: RFC 9946 §8.1, §8.2
Requirements: RFC9946-8.1-L12, RFC9946-8.2-L15

Proving tests:

zero-checksum-sent-as-ffff

A computed checksum of 0 is sent as 0xFFFF, its equal in one’s complement, so a populated field never reads as unpopulated. This applies to every PDU udpstp fills a checkSum in. §5.6 gives no substitution rule.

Also an OB-UDPST Difference.
RFC: RFC 9946 §5.6
Requirements: RFC9946-5.6-3
OB-UDPST: OB-UDPST sends 0, which its peer reads as unpopulated.

Proving tests:

broadcast-means-limited-broadcast

“Broadcast” is 255.255.255.255, also in its IPv4-mapped IPv6 form; multicast is 224.0.0.0/4 and ff00::/8. A subnet’s directed broadcast address can’t be told without its prefix length, which the core doesn’t know, and deprecated IPv4-compatible IPv6 addresses aren’t looked into. Activation Requests from a multicast or broadcast source are dropped too (docs/spec.md story 89).

RFC: RFC 9946 §6
Requirements: RFC9946-6-1, RFC9946-6-2

Proving tests:

windows-dscp-coerced-to-0

Where test traffic can’t be marked (Windows, which reserves the DS field for its QoS API), a Server allows only DSCP 0, coercing any other dscpEcn to it, and a Client asks for 0, each with a warning. dscpEcn is read as the marking the Test’s traffic actually carries, so the Activation Response never states one that isn’t sent; §7.2.1 lets the Server coerce dscpEcn.

RFC: RFC 9946 §7.1, §7.2.1
Requirements: RFC9946-7.2.1-L1
References: docs/technology.md

Proving tests:

Keys and authentication (8)

kdf-appendix-a-layout

The KDF’s fixed input is laid out as RFC 9946 Appendix A’s OpenSSL call builds it: BE32(counter), “UDPSTP”, a zero byte, the decimal authUnixTime of the first Setup Request, BE32(512). §5.4.1’s prose fits at least five layouts; Appendix A is the only one the RFC pins down, and OB-UDPST uses it too. The Shared Key goes in as raw bytes, never cut at a zero byte as Appendix A’s strlen(Kin) would; for text keys the two agree.

RFC: RFC 9946 §5.4.1
Requirements: RFC9946-5.4.1-3
References: ADR 0001

Proving tests:

key-table-lifetimes

The Server finds a key by keyId among the rows whose accept lifetime includes its own wall clock; a keyId with rows but none in its accept lifetime is an authentication failure. The Client sends with the row whose send lifetime includes its wall clock, preferring the newest SendLifetimeStart. Bounds are inclusive and no clock-skew grace is added. Rows may share a keyId only if neither their accept nor their send lifetimes overlap, so a keyId always selects at most one row. Established Test Connections keep their Derived Keys when a lifetime ends.

RFC: RFC 9946 §5.4, RFC 7210 §3
Requirements: RFC9946-5.4-3
References: ADR 0010

Proving tests:

key-table-alg-id

A Key Table row’s KDF is HMAC-SHA-256, its IANA “KeyTable KDFs” name, standing for §5.4.1’s counter-mode construction. The “KeyTable AlgIDs” registry has no value for the authDigest algorithm, so the AlgID is the unregistered name HMAC-SHA-256, mirroring the KDF. Both may be left out, and no other value is accepted. LocalKeyName is the keyId.

RFC: RFC 9946 §5.4, RFC 7210 §2
Requirements: RFC9946-5.4-3, RFC9946-5.4-4
References: ADR 0010, docs/spec.md

Proving tests:

key-table-operator-extension

A TOML Key Table row may carry operator = true, making it an Operator Key, and an Operator Key may carry max_test_interval, its own maximum Test Interval of at least 60 s. RFC 7210 has no column for either; RFC 9946 §5.4 asks for “(at least)” its fields, so these are extensions of our key file format. Operator-only Fixed-Rate Tests and Tests longer than 60 s need them.

Also an OB-UDPST Difference.
RFC: RFC 9946 §5.4, RFC 7210 §2
Requirements: RFC9946-5.4-3
OB-UDPST: OB-UDPST key files have only a keyId and a key: no role and no per-key Test Interval.
References: ADR 0010, docs/spec.md

Proving tests:

key-lifetime-format

Lifetimes are written YYYYMMDDHHMMSSZ, in UTC, as RFC 7210’s prose describes (year, month, day, hour, minute, second), although its format line says YYYYMMDDHHSSZ. Leap seconds (second 60) and years before 1970 are refused.

RFC: RFC 7210 §2
References: ADR 0010

Proving tests:

key-text-extension

A TOML Key Table row gives its key as exactly one of key_hex or key_text. key_hex accepts upper- and lowercase digits; key_text is an extension that takes the key as its UTF-8 bytes, so a text key shared with OB-UDPST needs no conversion. RFC 7210 writes Key as a lowercase hexadecimal string.

RFC: RFC 7210 §2
References: docs/spec.md

Proving tests:

several-valid-keys-need-key-id

When several rows are valid for sending now and tie for the newest SendLifetimeStart (every row of an OB-UDPST key file, which has no lifetimes), the Client doesn’t guess: it doesn’t start unless --key-id pins one. RFC 7210 §3 selects by the newest start and leaves a tie open.

Also an OB-UDPST Difference.
RFC: RFC 7210 §3
OB-UDPST: OB-UDPST’s Client uses keyId 0 (DEF_KEY_ID) unless -y names another, or the only key when the file has one.
By default: on
Setting: --key-id
References: ADR 0010

Proving tests:

derived-keys-depend-only-on-the-row-and-time

The Derived Keys are the KDF of a Key Table row’s Shared Key and the first Setup Request’s authUnixTime, nothing else, so the Server keeps them by row and time and reuses them for the next request with both (a flood costs one KDF run). A Setup Request whose authDigest fails leaves nothing of its own: it is dropped, admits nothing, and no key from it is used for it; a later request with a valid digest would derive the same keys.

RFC: RFC 9946 §5.4.1
Requirements: RFC9946-5.4.1-7

Proving tests: