OB-UDPST Differences
An OB-UDPST Difference is behaviour in which udpstp and OB-UDPST differ while udpstp conforms to the RFC. 31 records are OB-UDPST Differences only; with the Interpretations OB-UDPST doesn’t share, 97 records describe a difference from OB-UDPST.
Load Rate Adjustment Algorithms (1)
delay-var-is-the-maximum-by-default
With useOwDelVar True, a Trial Interval’s DelayVar is its maximum one-way delay variation (delayVarMax), the “delay range” RFC 9946 §5.1(6) and RFC 9097 §8.1 judge. A Server setting makes it the average, delayVarSum / delayVarCnt rounded half up, as OB-UDPST uses. With useOwDelVar False it is the Status PDU’s rttVarSample, as in OB-UDPST.
RFC: RFC 9946 §5.1; RFC 9097 §8.1
OB-UDPST: Classifies by the rounded average one-way delay variation, which is lower than the maximum, so an OB-UDPST Server sees fewer Errored Intervals and can search higher on a jittery path.
By default: on
Setting: --delay-var-statistic, delay_var_statistic in TOML
Proving tests:
crates/udpstp-core/tests/interval_classification.rs::the_maximum_is_the_default_and_the_average_a_settingcrates/udpstp-core/tests/interval_classification.rs::the_average_rounds_and_clean_is_strictly_below_the_low_thresholdcrates/udpstp-core/tests/interval_classification.rs::without_one_way_delay_the_latest_rtt_sample_is_delay_var
The Client (9)
auth-mode-2-default
The Client authenticates in mode 2 by default, which also authenticates every Status PDU. Mode 1 is used only when chosen, for all Servers or per Server, never as a fallback.
RFC: RFC 9946 §5.3
OB-UDPST: OB-UDPST supports mode 1 only: its Client sends mode 1 with a key, and 0 without one.
By default: on
Setting: --auth-mode, --mode-1-server
References: docs/spec.md
Proving tests:
crates/udpstp-core/tests/auth_mode_default.rs::a_server_without_mode_2_is_reported_and_the_client_does_not_fall_backcrates/udpstp-core/tests/auth_mode_default.rs::mode_1_is_used_only_with_the_server_it_was_chosen_for
exit-zero-with-warnings
A Test that completed with only warnings exits 0, so scripts treat it as a completed measurement. The JSON ErrorStatus still carries the warning.
OB-UDPST: OB-UDPST exits with the warning’s ErrorStatus.
References: docs/spec.md story 31
Proving tests:
exit-own-error-statuses
udpstp adds ErrorStatus values OB-UDPST lacks, in numbers it leaves unused: 49, the last warning code, for a completed Test with no valid Maximum IP-Layer Capacity (JSON only); 201 for a coercion the Client was told to refuse; 202 for a parameter the Client can’t run with; 203 for a Multi-Connection Test whose Servers set different parameters; 204 for a Test the Client user interrupted (record client-interrupt). These ErrorStatus values sit in OB-UDPST’s connection-error range (200 plus an offset), away from the ranges that add an IANA-registered cmdResponse.
OB-UDPST: OB-UDPST has none of these: it adds no warning for a missing maximum, and uses only 200 in its connection-error range.
References: docs/spec.md
Proving tests:
crates/udpstp/src/test_connection_end.rs::every_exit_statuscrates/udpstp/src/exit.rs::a_tests_status_from_its_test_connections
client-interrupt
On Ctrl-C (SIGINT), SIGTERM or SIGHUP (each unless the Client started with it ignored), or the Windows console’s Ctrl-C, Ctrl-Break, close and shutdown events, while its Test runs, the Client stops the Test: each Test Connection in its Data Phase sends its STOP2 (record client-initiated-stop), so the Server ends its own at once, within 1 s, and the results so far are printed with ErrorStatus 204. On Unix the Client then dies of the same signal, so a shell sees 128 plus its number, even when every Test Connection had already ended (the JSON ErrorStatus then reflects the Test); on Windows it exits 204. A signal before the Test starts or after it ends, or a second one, ends the Client at once. A failed write to standard output is noticed when it fails: it stops a running Test the same way, and the Client exits 204, as when its final results can’t be written.
OB-UDPST: OB-UDPST on SIGINT or SIGTERM stops sending without a STOP, so the Server ends the Test by its Watchdog, and exits 255 (udpst.c signal_exit).
Proving tests:
crates/udpstp-core/tests/multi_connection_failures.rs::an_interrupted_test_stops_every_test_connection_with_the_stop_exchangecrates/udpstp-core/tests/multi_connection_failures.rs::an_interrupt_after_the_test_ended_is_no_abortcrates/udpstp/src/exit.rs::a_tests_status_from_its_test_connectionscrates/udpstp/tests/loopback.rs::an_interrupted_client_stops_the_test_and_reports_it(runs only with cfg(unix))crates/udpstp/tests/loopback.rs::a_client_whose_stdout_closes_stops_cleanlycrates/udpstp/tests/loopback.rs::a_client_whose_stdout_closes_after_activation_stops_cleanlycrates/udpstp/tests/loopback.rs::a_client_started_with_sigint_ignored_keeps_running(runs only with cfg(unix))
exit-most-severe-warning
A completed Test reports its most severe warning, in this order: 49 no valid maximum, 4 traffic stopped for 1 s, 5 the peer stopped receiving, 3 the peer missed Status PDUs, 2 Status PDUs never arrived, 6 invalid PDUs. A warning never replaces an error. A Multi-Connection Test reports success with the most severe warning of all once any Test Connection completed; otherwise the first Test Connection’s error by mcIndex, or 200 when none had one of its own.
OB-UDPST: OB-UDPST keeps the last status written: a later warning replaces an earlier, more severe one, and replaces an error too; 200 overwrites only success or a warning, and a Test Connection’s normal stop turns any error into 0.
References: docs/spec.md
Proving tests:
crates/udpstp/src/test_connection_end.rs::every_exit_statuscrates/udpstp/src/exit.rs::a_tests_status_from_its_test_connections
json-at-max-is-the-maximum
In the JSON results, AtMax and ModalResult describe the Maximum IP-Layer Capacity, the highest Sub-Interval that meets the Performance Criterion, as RFC 9097 defines the metric. A Capacity Mode with no such Sub-Interval has no AtMax (or ModalResult entry) and ErrorStatus 49. The Highest IP-Layer Capacity, with no loss criterion, is in the udpstp extension object.
RFC: RFC 9097 §6.3
Requirements: RFC9097-6.3-L1
OB-UDPST: OB-UDPST’s AtMax is the Highest IP-Layer Capacity: the Sub-Interval with the greatest rate, whatever its loss.
Proving tests:
json-summary-delays-from-samples
In the JSON results, the delay fields of a Sub-Interval without delay or RTT samples are 0, as OB-UDPST writes them, and the Sub-Interval is listed under NoSamples in the extension. Summary and ModalSummary are computed from real samples only.
OB-UDPST: OB-UDPST’s Summary.PDVMin and RTTMin take the minimum over Sub-Intervals including those zeros, and PDVAvg averages them in.
No proving test.
json-missing-sub-intervals
In the JSON results, a Sub-Interval never measured is left out of IncrementalResult and listed under MissingSubIntervals in the extension, and Interval is each Sub-Interval’s subIntSeqNo, so a gap shows.
OB-UDPST: OB-UDPST’s Interval counts the Sub-Intervals processed, so it has no notion of a missing one: the numbering runs on.
No proving test.
json-incomplete-test-results-in-extension
When a Test activated but didn’t complete (ErrorStatus 200 and up), Output has only its header and trailer, as OB-UDPST writes it. The extension’s SubIntervals keeps every measured Sub-Interval with the same fields and units as Output.IncrementalResult, including loss and delay figures, or an empty array if none was measured. Its MinRTT is the smallest measured minimum RTT in seconds, or null if unavailable. Both are omitted with --json-extension=false.
OB-UDPST: OB-UDPST drops every Sub-Interval result of a Test that ends with ErrorStatus 200.
Proving tests:
crates/udpstp/src/test_results.rs::an_interrupted_tests_json_keeps_its_measured_sub_intervalscrates/udpstp/src/test_results.rs::an_incomplete_tests_json_keeps_missing_measurements_explicitcrates/udpstp/tests/loopback.rs::an_interrupted_client_stops_the_test_and_reports_it(runs only with cfg(unix))
The Control Phase and Admission (7)
silent-authentication-failures
Every Setup Request that fails authentication is dropped silently (bad digest, unknown keyId, key outside its lifetime, time outside the window, unsupported or missing authMode, no keys configured), as the RFC’s default; only Troubleshooting Mode answers.
OB-UDPST: OB-UDPST always answers these failures with a coded Setup Response (AUTHFAIL, AUTHTIME, AUTHNC, AUTHREQ, AUTHINV) and has no silent mode.
Proving tests:
crates/udpstp-core/tests/troubleshooting_mode.rs::by_default_authentication_failures_are_silentcrates/udpstp-core/tests/setup_rejections.rs::a_request_whose_digest_fails_gets_no_response
legacy-protocol-versions-get-badver
Any protocolVer other than 20 is rejected with BADVER, legacy versions 11 to 19 included; the response carries protocolVer 20.
OB-UDPST: OB-UDPST also serves protocolVer 11 to 19.
Proving tests:
crates/udpstp-core/tests/setup_rejections.rs::a_bad_protocol_version_is_rejected_with_the_expected_versioncrates/udpstp-core/tests/setup_rejections.rs::an_unauthenticated_request_gets_unsigned_rejections
setup-check-order
The Server checks a Setup Request in the RFC’s order: size, checksum, authentication, then the replay cache, the Source’s budget and the Key Table row’s budget, then the other fields: BADVER, BADJS, BADTMTU. Then Admission’s limits: a copy of an admitted request is ignored; the Multi-Connection fields (MCINVPAR); for a new Test, the Tests per Source, then the Quota (CONNFAIL); a missing maxBandwidth (NOMAXBW); the per-Test cap, then the global cap (CONNFAIL); the Admitted Rate Cap and one Test’s share of it (CAPEXC).
OB-UDPST: OB-UDPST judges the fields before authMode, and checks MCINVPAR right after BADVER, before BADJS and BADTMTU; so an unauthenticated request learns the field results before being told authentication is required.
Proving tests:
crates/udpstp-core/tests/setup_rejections.rs::a_disallowed_unauthenticated_request_is_silent_whatever_its_optionscrates/udpstp-core/tests/setup_rejections.rs::the_options_are_judged_before_the_multi_connection_fieldscrates/udpstp-core/src/admission.rs::a_setup_is_refused_by_the_first_check_it_fails
unknown-rate-adj-algo-refused
An Activation Request whose rateAdjAlgo is neither B nor C is refused with BADPARAM: running another algorithm would measure by a method the Client didn’t ask for.
OB-UDPST: OB-UDPST coerces any other value to B. Its Client only sends B or C, so interop isn’t affected.
Proving tests:
dual-phase-reserved-field-ignored
The Activation Request’s reserved fields are ignored, as the RFC requires, including the one OB-UDPST uses for srAdjSuppCount (dual-phase testing), so a Consumer can’t get a fixed rate that way.
OB-UDPST: OB-UDPST reads srAdjSuppCount from a reserved field and runs row 0 or the requested row as a fixed rate for the suppressed Sub-Intervals.
Proving tests:
out-of-range-parameters-clamped
An out-of-range Activation parameter is clamped to the nearest limit of our range, and the response reports it. Our ranges are narrower than OB-UDPST’s. An OB-UDPST Client adopts the coerced values without complaint.
OB-UDPST: OB-UDPST resets an out-of-range value to its default (useOwDelVar to False, ignoreOooDup to True). Its ranges are wider: thresholds 1 to 10000, highSpeedDelta and slowAdjThresh from 1, seqErrThresh to 65535, testIntTime to 3600.
Proving tests:
row-zero-fixed-size
Row 0 sends one 625-byte IP packet every 50 ms on the second transmitter: 0.1 Mbps, always the same size. The RFC gives row 0 no meaning beyond the first row.
OB-UDPST: OB-UDPST’s row 0 sends one add-on every 50 ms of random size, up to 1222 bytes of payload (1472 with -T), with the random-size bit set in udpAddon2: about 0.11 Mbps.
Proving tests:
The Data Phase (10)
peer-tears-down-a-second-later
An end that stopped sending also stops refreshing its peer’s Watchdog. After a break in one direction, an OB-UDPST peer ends its Test Connection about 1 s after udpstp does, and reports traffic stopped in both directions.
OB-UDPST: OB-UDPST keeps sending for 3 s, so its peer’s Watchdog was last reset about 1 s later than ours.
References: ADR 0002
No proving test.
stop-pdus-reset-the-watchdog
Every valid Load PDU or Status PDU resets the Watchdog, STOP ones included, as the RFC describes the reset.
OB-UDPST: OB-UDPST resets its watchdog only while both its own and the received testAction are TEST_ACT_TEST.
No proving test.
transmitter-switched-on-starts-at-once
A Transmitter that a new Sending Rate Structure switches back on starts at once.
OB-UDPST: OB-UDPST arms it only when the other Transmitter fires, so once both are zeroed the Client never sends again.
Proving tests:
stop-confirmed-without-a-transmitter
An Upstream Client whose Sending Rate Structure has no Transmitter still confirms a STOP, with a header-only Load PDU.
OB-UDPST: An OB-UDPST Client confirms at its next send timer, so with no Transmitter armed it sends nothing to confirm with.
Proving tests:
server-polices-upstream-rate
The Server polices an Upstream Client: three Trial Intervals in a row above 1.5 times the highest rate it assigned within the last second start the STOP exchange early and end the Test Connection with EndReason::OverRate (ADR 0008, amended by ADR 0011 so a search through a deep buffer isn’t stopped).
OB-UDPST: OB-UDPST doesn’t police the Client’s rate.
References: ADR 0008, ADR 0011
Proving tests:
crates/udpstp-core/tests/upstream.rs::the_server_stops_a_client_sending_over_its_ratecrates/udpstp-core/tests/algorithm_b.rs::an_upstream_search_through_a_deep_buffer_is_not_stopped_over_rate
missed-bursts-catch-up-or-are-skipped
The Load Sender keeps an absolute schedule. After a late wake-up it still sends each missed Burst less than the catch-up budget (1 ms; 2 ms on Windows, whose timers wake later) late, and the most recent one always; older ones are Skipped Bursts, counted, reported (DataEvent::BurstsSkipped) and deducted from the IP-Layer Sender Bit Rate. ADR 0008 and ADR 0012 hold the reasoning.
OB-UDPST: OB-UDPST re-arms each send timer from “now”, so missed intervals are lost with no catch-up and no record.
References: ADR 0008, ADR 0012
Proving tests:
crates/udpstp-core/tests/downstream_load.rs::a_stalled_engine_skips_missed_bursts_and_reports_themcrates/udpstp-core/tests/downstream_load.rs::bursts_missed_by_less_than_the_catch_up_budget_are_all_sentcrates/udpstp-core/tests/downstream_load.rs::bursts_missed_by_more_than_the_catch_up_budget_are_skippedcrates/udpstp-core/tests/downstream_load.rs::a_burst_exactly_the_catch_up_budget_late_is_skipped
status-pdu-sums-saturate
tiRxBytes, delayVarSum and the RTT variation sums saturate at their field’s maximum. Only extreme byte counts or lpduTimes reach this.
OB-UDPST: OB-UDPST’s wrap.
No proving test.
lpdu-seq-no-jumps-held
An lpduSeqNo jump of more than 1024 (for a first Load PDU, more than 1024 from 1 either way) counts only once the next Load PDU continues from it within 1024; otherwise it is ignored (DataEvent::IgnoredLoadPdus). 32 out-of-order Load PDUs in a consecutive run more than 1024 behind resync counting to them, and open intervals then take back their own loss. The held Load PDU gives no delay sample and counts in the interval of the Load PDU that confirms it. Load PDUs carry no authentication, so this keeps a forged one from hiding loss.
OB-UDPST: In OB-UDPST one forged Load PDU up to 2^31 ahead leaves every later one out of order, hiding all loss for the rest of the Test.
Proving tests:
crates/udpstp-core/tests/sequence_errors.rs::a_jump_over_1024_waits_for_the_next_load_pducrates/udpstp-core/tests/sequence_errors.rs::a_forged_first_load_pdu_is_ignoredcrates/udpstp-core/tests/sequence_errors.rs::counting_follows_a_run_of_32_out_of_order_load_pduscrates/udpstp-core/tests/sequence_errors.rs::a_resync_lets_the_interval_take_back_its_own_loss
negative-rtt-gives-no-sample
A negative raw RTT, from an echoed spduTime in the future, which only a forged or corrupted Load PDU carries, gives no sample.
OB-UDPST: OB-UDPST casts it to unsigned and reports a huge rttVarSample.
Proving tests:
checksum-verified-only-when-configured
A populated checkSum is verified only when checksum use is configured, as §5.6 says.
OB-UDPST: OB-UDPST verifies every populated checkSum, whatever its build flag.
Proving tests:
crates/udpstp-core/tests/control_checksum.rs::only_a_configured_receiver_verifies_and_only_a_populated_fieldcrates/udpstp-core/tests/downstream_load.rs::the_client_verifies_a_populated_checksum_when_configured
Keys and authentication (4)
obudpst-key-file-reading
An OB-UDPST udpst.keys file is read as OB-UDPST reads it (comma, space and tab separators; a line ends at #, \r or a NUL byte; keyIds as C’s strtol reads them; keys of up to 64 bytes), with two exceptions. Lines aren’t split at 1023 bytes. A keyId with a - sign, or one above 255 before narrowing, is refused.
OB-UDPST: OB-UDPST reads lines with fgets into a 1024-byte buffer, so a longer line continues as a new one, and narrows strtol’s result to int, so a signed or oversized keyId can wrap into 0-255.
Proving tests:
crates/udpstp/tests/key_files.rs::the_server_loads_an_obudpst_key_filecrates/udpstp/tests/key_files.rs::an_invalid_key_file_exits_with_51_and_says_wherecrates/udpstp/tests/loopback.rs::a_mode1_test_runs_with_a_toml_key_table_and_an_obudpst_key_filecrates/udpstp/tests/loopback.rs::an_obudpst_key_need_not_be_utf8
key-file-private
On Unix unsafe key-file permissions are refused. Windows assesses and reads the same read-shared file under ADR 0015’s supported local-NTFS policy, trusting only the running account, SYSTEM and built-in Administrators. Only a supported policy match proceeds to content reading; mismatches and unverified permissions are distinct key file errors (51), with accurate reasons and remediation, without claiming effective exposure. Server errors reach stderr and any configured startup log; Client failures leave stdout empty. The native unsafe boundary, descriptor bounds, read-only sharing and content wiping remain unchanged. The settings-only preview loads no keys; ordinary print-config still validates them. The manual reconstruction removes explicit outsider grants. The installer validates keys through a real virtual-account trial and contains failures with disabled startup, stopped service and cleared recovery. Protected containing storage remains a deployment assumption. RFC 9946 prescribes no storage algorithm.
OB-UDPST: OB-UDPST reads a key file whatever its permissions.
References: docs/spec.md, ADR 0015
Proving tests:
crates/udpstp/tests/key_files.rs::a_key_file_others_can_read_is_refused(runs only with cfg(unix))crates/udpstp/tests/key_files.rs::a_key_file_must_be_a_regular_file(runs only with cfg(unix))crates/udpstp/tests/key_files.rs::a_private_key_file_loads_on_windows(runs only with cfg(windows))crates/udpstp/tests/windows_key_permissions.rs::native_acl_matrix_uses_the_production_assessment(runs only with cfg(windows))crates/udpstp/tests/windows_key_permissions.rs::both_roles_refuse_mismatch_and_unverified_without_key_material(runs only with cfg(windows))crates/udpstp/tests/windows_key_permissions.rs::native_inspection_failure_and_read_sharing(runs only with cfg(windows))crates/udpstp/tests/windows_key_permissions.rs::a_private_file_supports_a_successful_authenticated_test(runs only with cfg(windows))crates/udpstp/tests/windows_key_permissions.rs::manual_setup_removes_pre_existing_outsider_grants(runs only with cfg(windows))crates/udpstp/tests/windows_key_permissions.rs::refused_server_keys_reach_stderr_and_the_daily_log(runs only with cfg(windows))crates/udpstp/tests/cli.rs::settings_only_prints_settings_without_loading_keys_or_opening_logscrates/udpstp/tests/cli.rs::settings_only_requires_printing_and_cannot_run_as_a_service
key-file-owner
On Unix a key file must be owned by the effective user running udpstp or by root; another owner could replace the keys. Running udpstp setuid isn’t supported.
OB-UDPST: OB-UDPST doesn’t check who owns a key file.
Proving tests:
crates/udpstp/tests/key_files.rs::a_key_file_owned_by_another_user_is_refused(runs only with cfg(unix))
key-file-size-limit
A key file larger than 1 MiB is refused, so it can be read into one buffer that is wiped after parsing.
OB-UDPST: OB-UDPST reads a key file of any length, line by line.
Proving tests: