feat: the mock can drop and invert packets, and carries a noisy channel
--drop-every/--drop-burst withhold datagrams the counter and stream clock have already advanced over, and --reorder-every inverts a pair on the wire; both are deterministic for the reason --glitch-at is, since a probe asserts the spacing of what it finds and a random count is not a figure. A `noise` signal gives the dense-trace and intensity-floor paths a input that is not piecewise smooth. verification.md records the measured readings, including the one that is a finding rather than a baseline: a burst past kGlitchPackets (256) is read as a producer clock glitch, each bridge advances the anchor by one packet span against a timeline 300 ahead, and four consecutive failures then throw the stream onto the arrival clock. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
cd9657663a
commit
82180f45f7
3 files changed
+157
-20
No files matched your search
@@ -201,8 +201,8 @@ as distinct from the fault injection below, which exercises ingest:
|
||||
- `--quiet-at S` freezes `sine` at its held value from stream second `S` onwards, so it stops
|
||||
crossing zero: the dropout trigger's input.
|
||||
|
||||
The mock also carries one **fault injection** that no probe schedules, because what it exercises is
|
||||
the ingest rather than the UI: `--time-glitch-at <stream seconds> --time-glitch-by <seconds>` makes
|
||||
The mock also carries three **fault injections** that no probe schedules, because what they exercise
|
||||
is the ingest rather than the UI. The first: `--time-glitch-at <stream seconds> --time-glitch-by <seconds>` makes
|
||||
**one** packet's time signal jump ahead and then carries on from where the true timeline was, which
|
||||
is what a device whose clock glitches puts on the wire. Run it with the trigger armed, e.g.
|
||||
|
||||
@@ -228,6 +228,46 @@ jumping 5.040 → 16.040, and two latches **1.2 s** apart. Note the window here
|
||||
session for that port; the figures above are the default 2 s window (50 % pre), so move
|
||||
`~/.config/udpscope/<host>_<port>.toml` aside to reproduce them.
|
||||
|
||||
The other two are **packet loss** (`--drop-every N`, `--drop-burst K`) and **wire inversion**
|
||||
(`--reorder-every N`). Both withhold a datagram the counter and the stream clock have already
|
||||
advanced over, which is what makes them loss and reordering rather than a slower producer: the
|
||||
client orders the stream by the header's counter alone (`udps_client.c:504`), counting a forward
|
||||
jump as `counter_gaps` and rejecting anything that does not advance it as `stale_packets`. There is
|
||||
**no resequencing and no interpolation** anywhere below the ring — an inverted packet's samples are
|
||||
discarded and the hole they leave is a jump in x, which is the behaviour these options are for.
|
||||
|
||||
All of the readings below are off `UDPSCOPE_EXIT_DUMP`'s `[udpscope] frames= packets= gaps=` line,
|
||||
over an 8 s run against `--rate 10000 --batch 100 --freq 5` (100 packets a second) except where the
|
||||
burst needs the faster stream. **`gaps` is the exact reading** and repeats to the packet; `frames`
|
||||
and `packets` move by one or two with the run's own timing. Run each of these on its own — four of
|
||||
them back to back in one script starved the mock past the 2 s silence timeout and every case,
|
||||
including the clean one, then read a reconnect instead of what it was testing:
|
||||
|
||||
- **clean**, as the reference: `frames=1029 packets=1030 gaps=0`, no warning.
|
||||
- `--drop-every 10`: `frames=925 packets=926 gaps=102`. Every tenth packet never reaches the wire,
|
||||
so `packets` falls with `frames` and `gaps` is the count of them. No warning: a one-packet hole is
|
||||
a one-packet jump in the stamps, far inside the glitch guard.
|
||||
- `--reorder-every 10`: `frames=925 packets=1028 gaps=102`. This is the signature to look for —
|
||||
`packets` is the clean figure (every datagram arrived) while `frames` is the dropped one, because
|
||||
the overtaken packet is decoded far enough to be read as stale and then thrown away. One inversion
|
||||
costs one `gaps` (for the packet that overtook it) **and** one `stale_packets`, which udpscope does
|
||||
not currently display; the ~104 missing frames are the only place it shows.
|
||||
- `--drop-every 1000 --drop-burst 200` at `--rate 100000 --batch 100` (1000 packets a second):
|
||||
`frames=7000 packets=7001 gaps=1600`, eight bursts of 200, and still no warning.
|
||||
- `--drop-every 1000 --drop-burst 300`, same stream: `frames=6203 packets=6204 gaps=2400` **and the
|
||||
guard fires** — `warning: ... jumped implausibly far ahead of the previous packet (1.299..1.29999
|
||||
against a previous 0.99899)`, then `timing samples by arrival instead`. 300 packet spans is past
|
||||
`kGlitchPackets` (256), so real loss is read as a clock glitch; and since each bridge advances the
|
||||
anchor by one packet span while the true timeline is 300 ahead, the next four packets fail too and
|
||||
`kFailureLimit` throws the stream onto the arrival clock. A burst between 256 packets and the 2 s
|
||||
`silence_timeout_s` is the only window in which this is reachable, and it is a finding, not a
|
||||
baseline.
|
||||
|
||||
Keep a burst under 2 s of wire time whatever the rate: past that the client reconnects, the mock
|
||||
resets its counter and clock on the new CONNECT, and the run reads `gaps=0` with a *backwards* stamp
|
||||
warning (`does not advance across packets ... 0..0.0099 against a previous 0.3899`) — the reconnect
|
||||
path, not the loss path.
|
||||
|
||||
**Run one self-check at a time.** Two concurrent `xvfb-run -a` instances end up on the
|
||||
same display and the second gets no GL context — it dumps `layout over 0 frames` and
|
||||
`valid=-1`, which looks like a pipeline failure and is not one. `tools/mock_udpstreamer.py`
|
||||
|
||||
Reference in new issue
Block a user