Skip to content

Commit 50b532f

Browse files
authored
Merge pull request #118 from constructive-io/feat/traffic-replay-experiment
feat(traffic): answer whether frames repeat, and make the remaining experiments one command each
2 parents e2d7955 + 39b8f00 commit 50b532f

10 files changed

Lines changed: 750 additions & 11 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ pnpm build
6464

6565
| Tool | Description |
6666
|------|-------------|
67-
| `tools/traffic` | Passive capture and byte-level analysis of Pangolin BEYOND ⇄ FB4 traffic, on Wireshark's CLI. Driven from Advanced → Traffic in the desktop app, or straight from a terminal — see [tools/traffic/README.md](tools/traffic/README.md). Observation only; it never transmits to laser hardware. |
67+
| `tools/traffic` | Passive capture and byte-level analysis of Pangolin BEYOND ⇄ FB4 traffic, on Wireshark's CLI. Driven from Advanced → Traffic in the desktop app, or straight from a terminal — see [tools/traffic/README.md](tools/traffic/README.md). Observation, plus one hand-run experiment (`bin/replay`) that sends BEYOND's own plaintext live-control lines. |
6868

6969
## Architecture
7070

packages/cli/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,8 @@ osc` defaults to 8000, BEYOND's factory port) and the zone has to be under live
121121
control.
122122

123123
This is aimed output on a configured target, unrelated to
124-
[`tools/traffic`](../../tools/traffic), which stays passive — it observes
125-
BEYOND ⇄ FB4 traffic and never transmits.
124+
[`tools/traffic`](../../tools/traffic), which observes BEYOND ⇄ FB4 traffic and
125+
transmits nothing except its `bin/replay` experiment, run by hand.
126126

127127
### `wavegrid config` (or `wavegrid --print-config`)
128128

tools/traffic/PROTOCOL.md

Lines changed: 78 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ the part you use while something is broken.
2222
- [What is still missing, and how to capture it](#what-is-still-missing-and-how-to-capture-it)
2323
- [Diagnostic playbook](#diagnostic-playbook)
2424
- [Ways in that are not this protocol](#ways-in-that-are-not-this-protocol)
25+
- [Guided experiments, and the one tool that transmits](#guided-experiments-and-the-one-tool-that-transmits)
2526

2627
## The installation, as the network sees it
2728

@@ -248,6 +249,35 @@ readable either. The 32-byte `0x00028010` body is all zeroes — the one body in
248249
the protocol that is plainly not encrypted, which is itself a small hint that the
249250
opacity is applied per message type rather than to the whole connection.
250251

252+
### No frame body is ever sent twice
253+
254+
The question that decides whether a captured frame could simply be replayed:
255+
does BEYOND ever emit the same body twice? Over 1,161 frames of *static* content
256+
across two captures — an idle slice and the amber capture, i.e. the best case for
257+
repetition, since the picture is not changing at all:
258+
259+
| | result |
260+
| --- | --- |
261+
| distinct bodies | 1,161 of 1,161 — zero repeats |
262+
| body bytes constant across every frame | 0 of 2,360 |
263+
| bytes two consecutive bodies share | 1–21 of 2,360 (chance alone ≈ 9) |
264+
| bodies shared between two devices getting the same scene | 0 |
265+
266+
Reproduce with `./bin/decode <capture> --repeats`.
267+
268+
A static scene re-encrypted into a completely different 2,360 bytes every 16 ms
269+
means each frame carries a nonce, a counter, or a stream-cipher position. Two
270+
consequences worth stating plainly, because they close off the two obvious
271+
shortcuts:
272+
273+
- **Replaying a captured frame is not a route in.** A copy is either rejected as
274+
stale or, at best, decrypts to a single stale picture — and there is no
275+
repetition anywhere to build a mapping from.
276+
- **A known-plaintext attack has nothing to bite on.** Byte-identical input
277+
(blackout, held amber) produces unrelated ciphertext, so we cannot line up
278+
"this look" against "these bytes", which is the technique that would otherwise
279+
work on a home-grown scheme.
280+
251281
So: **the frame path cannot be decoded from captures alone**, and this is where
252282
passive analysis ends. Getting further would need something a capture cannot
253283
provide — key material, instrumented software, or vendor documentation. Per the
@@ -358,6 +388,22 @@ tag, and the tooling is already built for it:
358388
Worth doing for the handful that matter (colour balance, scan rate, blanking
359389
delay) rather than all 223.
360390

391+
**5. Nobody has tried sending the 16062 lines.** Those lines are plaintext, and
392+
the format is fully understood — so the cheapest remaining question is whether
393+
anything on the network *acts* on them, or whether the broadcast is only BEYOND
394+
narrating itself. This is a test in the room, not a capture:
395+
396+
```
397+
close BEYOND completely (tray included)
398+
./bin/session replay --host <FB4_IP>[,<FB4_IP>…]
399+
```
400+
401+
Evidence for the pessimistic answer: 16062 has only ever been seen host →
402+
network, never toward BEYOND, and the FB4s take their orders on 3348. So the
403+
expectation is that nothing moves. It is still worth ten minutes, because the
404+
result is unambiguous either way and it is the only cheap experiment left that
405+
could end in direct control.
406+
361407
Also worth having, cheaply, while someone is at the machine: BEYOND's projector
362408
list screenshot (to explain `45.4`), the FB4 firmware versions, and a listing of
363409
what content is on each SD card — the last one decides whether the ArtNet route
@@ -400,6 +446,34 @@ also stops being available to BEYOND. Untested here, and it would be the first
400446
thing in this project that transmits toward hardware; noted so the option isn't
401447
rediscovered from scratch.
402448

449+
## Guided experiments, and the one tool that transmits
450+
451+
`./bin/session --list` runs the experiments above end to end: it starts the
452+
capture, walks the operator through what to do at the machine, stops the capture,
453+
decodes it, and says what the result means. Three of them:
454+
455+
| experiment | answers | transmits |
456+
| --- | --- | --- |
457+
| `./bin/session handshake` | is there a key exchange on connect? (gap 1) | no |
458+
| `./bin/session osc-rgba` | do our OSC values land, and on which zone? (gaps 2, 3) | no |
459+
| `./bin/session replay --host <ip>` | does anything act on the 16062 lines? (gap 5) | yes |
460+
461+
`./bin/replay` is the only tool here that puts packets on the wire, and only when
462+
given `--transmit` **and** `--host`. Without them it prints the exact datagrams
463+
it would send and exits, which is also the fastest way to check the format
464+
against this document:
465+
466+
```
467+
./bin/replay --zone all --colour amber --sweep
468+
```
469+
470+
It sends nothing but BEYOND's own plaintext live-control lines — the same bytes
471+
BEYOND broadcasts, at the same rate a human moving a slider would — never
472+
fabricated frame-stream traffic, which is not constructible anyway. Rules for
473+
running it: BEYOND closed (otherwise a change in the room proves nothing about
474+
what caused it), somebody watching the heads, E-stop in reach, and never as an
475+
unattended loop.
476+
403477
## What this toolkit is good for
404478

405479
- Confirming an OSC message reached BEYOND, and what value it set, per zone
@@ -411,5 +485,7 @@ rediscovered from scratch.
411485
told to draw nothing".
412486
- Naming settings tags by controlled experiment, when someone has the machine.
413487

414-
Everything in `tools/traffic` reads files and sockets. It never transmits toward
415-
the hardware.
488+
Everything in `tools/traffic` reads files and sockets, with one deliberate
489+
exception: `./bin/replay --transmit`, which exists to answer the one question
490+
capture analysis cannot, and which sends only the plaintext live-control lines
491+
BEYOND itself broadcasts.

tools/traffic/README.md

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ A small CLI toolkit for capturing and reverse-engineering the network traffic
44
between Pangolin BEYOND and Pangolin laser hardware (an FB4), built on
55
Wireshark's command-line tools.
66

7-
**Passive only.** Nothing here transmits, replays, or spoofs anything: it lists
8-
interfaces, reads the neighbour table, captures, and analyses files on disk. No
9-
packet is ever sent toward the laser. Understanding the protocol comes first.
7+
**Passive, with one deliberate exception.** Everything here lists interfaces,
8+
reads the neighbour table, captures and analyses files on disk. The exception is
9+
`bin/replay`, which sends BEYOND's own plaintext live-control lines — and only
10+
when given `--transmit` and a `--host`; without them it prints the bytes it would
11+
send and exits. Nothing here ever fabricates frame-stream traffic.
1012

1113
Nothing in the Wavegrid app depends on these tools being installed — the app
1214
only looks for them when you open the Traffic tab (Advanced → Traffic), which
@@ -17,7 +19,8 @@ is also where you choose the directory captures are written to.
1719
- `tshark`, `dumpcap`, `capinfos`, `editcap`, `mergecap` — all ship with
1820
Wireshark. On macOS they live inside `Wireshark.app`, and the scripts look
1921
there, so a plain drag-to-Applications install works without touching `PATH`.
20-
- `python3` (macOS and Linux both have it) for `compare`, `decode` and `rgba`.
22+
- `python3` (macOS and Linux both have it) for `compare`, `decode`, `rgba` and
23+
`replay`.
2124
- Permission to capture. `./bin/doctor` says whether you have it and prints the
2225
exact privileged command if you do not — it never runs it for you.
2326

@@ -97,7 +100,38 @@ For watching that live instead of after the fact:
97100

98101
This is the only confirmation OSC can give you: BEYOND broadcasts what its live
99102
control holds (while its RGBA panel is open), so send a message and watch the
100-
value move. Silence means nothing is arriving. Receive-only, like everything here.
103+
value move. Silence means nothing is arriving. This listener only receives.
104+
105+
## Guided experiments
106+
107+
```
108+
./bin/session --list # what each experiment answers
109+
./bin/session handshake # BEYOND's connection setup, incl. the TCP SYN
110+
./bin/session osc-rgba # our OSC and BEYOND's echo, in one file
111+
./bin/session replay --host 169.254.53.5 # does anything act on the 16062 lines? (transmits)
112+
```
113+
114+
Each one starts the capture, tells you what to do at the machine, stops the
115+
capture, decodes it, and says what the result means — so the answer doesn't
116+
depend on remembering the right `tshark` filter at 2am. These are the open
117+
questions from [PROTOCOL.md](PROTOCOL.md), one command each.
118+
119+
## Sending live-control lines
120+
121+
```
122+
./bin/replay --zone all --colour amber --sweep # dry run: print the bytes
123+
./bin/replay --zone 1 --sweep --transmit --host 169.254.53.5
124+
```
125+
126+
The format BEYOND broadcasts is understood; what nobody has tested is whether
127+
anything *listens*. The captures suggest not (16062 has only ever been seen
128+
going host → network), but it is a ten-minute experiment with an unambiguous
129+
result. Run it with BEYOND closed — otherwise a change in the room proves nothing
130+
about what caused it — someone watching the heads, and the E-stop in reach.
131+
132+
`./bin/decode <capture> --repeats` answers the companion question, from captures
133+
we already have: BEYOND never sends the same frame body twice, not even for a
134+
static scene, so a captured frame cannot be replayed.
101135

102136
[PROTOCOL.md](PROTOCOL.md) is the full report on what the captures so far
103137
actually say: every port and message type, the header layout byte by byte, the
@@ -160,13 +194,16 @@ bin/experiment guided one-state-per-file capture run
160194
bin/compare byte-level diff of two captures
161195
bin/decode read a capture as Pangolin protocols
162196
bin/rgba live view of BEYOND's live-control values
197+
bin/session run one open question end to end: capture, guide, decode
198+
bin/replay send BEYOND's live-control lines (the only tool that transmits)
163199
lib/common.sh tool discovery, capture directory, JSON helpers
164200
lib/compare.py the diff itself
165201
lib/pangolin.py the protocol decoder
166202
lib/rgba_listen.py the live listener
203+
lib/replay.py the datagrams, and the sending
167204
```
168205

169-
The decoder has tests, run from the repo root:
206+
The decoder and the replay builder have tests, run from the repo root:
170207

171208
```
172209
python3 -m unittest discover -s tools/traffic/lib -p '*_test.py'

tools/traffic/bin/replay

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#!/usr/bin/env bash
2+
#
3+
# replay — send BEYOND's plaintext live-control lines yourself, to find out
4+
# whether anything on the network acts on them.
5+
#
6+
# Usage:
7+
# ./bin/replay # dry run: print the bytes
8+
# ./bin/replay --zone all --colour amber
9+
# ./bin/replay --sweep --zone 1 --transmit --host 169.254.53.5
10+
#
11+
# This is the only tool here that transmits, and only with --transmit. Every
12+
# other tool in tools/traffic stays receive-only.
13+
#
14+
# Run it with BEYOND closed — otherwise a change in the room proves nothing
15+
# about who caused it. Have someone watching the heads and the E-stop in reach.
16+
17+
source "$(dirname "${BASH_SOURCE[0]}")/../lib/common.sh"
18+
19+
command -v python3 >/dev/null || { echo 'error: python3 is required for replay' >&2; exit 127; }
20+
21+
exec python3 "$TRAFFIC_ROOT/lib/replay.py" "$@"

0 commit comments

Comments
 (0)