Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ pnpm build

| Tool | Description |
|------|-------------|
| `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. |
| `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. |

## Architecture

Expand Down
4 changes: 2 additions & 2 deletions packages/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,8 @@ osc` defaults to 8000, BEYOND's factory port) and the zone has to be under live
control.

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

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

Expand Down
80 changes: 78 additions & 2 deletions tools/traffic/PROTOCOL.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ the part you use while something is broken.
- [What is still missing, and how to capture it](#what-is-still-missing-and-how-to-capture-it)
- [Diagnostic playbook](#diagnostic-playbook)
- [Ways in that are not this protocol](#ways-in-that-are-not-this-protocol)
- [Guided experiments, and the one tool that transmits](#guided-experiments-and-the-one-tool-that-transmits)

## The installation, as the network sees it

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

### No frame body is ever sent twice

The question that decides whether a captured frame could simply be replayed:
does BEYOND ever emit the same body twice? Over 1,161 frames of *static* content
across two captures — an idle slice and the amber capture, i.e. the best case for
repetition, since the picture is not changing at all:

| | result |
| --- | --- |
| distinct bodies | 1,161 of 1,161 — zero repeats |
| body bytes constant across every frame | 0 of 2,360 |
| bytes two consecutive bodies share | 1–21 of 2,360 (chance alone ≈ 9) |
| bodies shared between two devices getting the same scene | 0 |

Reproduce with `./bin/decode <capture> --repeats`.

A static scene re-encrypted into a completely different 2,360 bytes every 16 ms
means each frame carries a nonce, a counter, or a stream-cipher position. Two
consequences worth stating plainly, because they close off the two obvious
shortcuts:

- **Replaying a captured frame is not a route in.** A copy is either rejected as
stale or, at best, decrypts to a single stale picture — and there is no
repetition anywhere to build a mapping from.
- **A known-plaintext attack has nothing to bite on.** Byte-identical input
(blackout, held amber) produces unrelated ciphertext, so we cannot line up
"this look" against "these bytes", which is the technique that would otherwise
work on a home-grown scheme.

So: **the frame path cannot be decoded from captures alone**, and this is where
passive analysis ends. Getting further would need something a capture cannot
provide — key material, instrumented software, or vendor documentation. Per the
Expand Down Expand Up @@ -358,6 +388,22 @@ tag, and the tooling is already built for it:
Worth doing for the handful that matter (colour balance, scan rate, blanking
delay) rather than all 223.

**5. Nobody has tried sending the 16062 lines.** Those lines are plaintext, and
the format is fully understood — so the cheapest remaining question is whether
anything on the network *acts* on them, or whether the broadcast is only BEYOND
narrating itself. This is a test in the room, not a capture:

```
close BEYOND completely (tray included)
./bin/session replay --host <FB4_IP>[,<FB4_IP>…]
```

Evidence for the pessimistic answer: 16062 has only ever been seen host →
network, never toward BEYOND, and the FB4s take their orders on 3348. So the
expectation is that nothing moves. It is still worth ten minutes, because the
result is unambiguous either way and it is the only cheap experiment left that
could end in direct control.

Also worth having, cheaply, while someone is at the machine: BEYOND's projector
list screenshot (to explain `45.4`), the FB4 firmware versions, and a listing of
what content is on each SD card — the last one decides whether the ArtNet route
Expand Down Expand Up @@ -400,6 +446,34 @@ also stops being available to BEYOND. Untested here, and it would be the first
thing in this project that transmits toward hardware; noted so the option isn't
rediscovered from scratch.

## Guided experiments, and the one tool that transmits

`./bin/session --list` runs the experiments above end to end: it starts the
capture, walks the operator through what to do at the machine, stops the capture,
decodes it, and says what the result means. Three of them:

| experiment | answers | transmits |
| --- | --- | --- |
| `./bin/session handshake` | is there a key exchange on connect? (gap 1) | no |
| `./bin/session osc-rgba` | do our OSC values land, and on which zone? (gaps 2, 3) | no |
| `./bin/session replay --host <ip>` | does anything act on the 16062 lines? (gap 5) | yes |

`./bin/replay` is the only tool here that puts packets on the wire, and only when
given `--transmit` **and** `--host`. Without them it prints the exact datagrams
it would send and exits, which is also the fastest way to check the format
against this document:

```
./bin/replay --zone all --colour amber --sweep
```

It sends nothing but BEYOND's own plaintext live-control lines — the same bytes
BEYOND broadcasts, at the same rate a human moving a slider would — never
fabricated frame-stream traffic, which is not constructible anyway. Rules for
running it: BEYOND closed (otherwise a change in the room proves nothing about
what caused it), somebody watching the heads, E-stop in reach, and never as an
unattended loop.

## What this toolkit is good for

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

Everything in `tools/traffic` reads files and sockets. It never transmits toward
the hardware.
Everything in `tools/traffic` reads files and sockets, with one deliberate
exception: `./bin/replay --transmit`, which exists to answer the one question
capture analysis cannot, and which sends only the plaintext live-control lines
BEYOND itself broadcasts.
49 changes: 43 additions & 6 deletions tools/traffic/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ A small CLI toolkit for capturing and reverse-engineering the network traffic
between Pangolin BEYOND and Pangolin laser hardware (an FB4), built on
Wireshark's command-line tools.

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

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

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

This is the only confirmation OSC can give you: BEYOND broadcasts what its live
control holds (while its RGBA panel is open), so send a message and watch the
value move. Silence means nothing is arriving. Receive-only, like everything here.
value move. Silence means nothing is arriving. This listener only receives.

## Guided experiments

```
./bin/session --list # what each experiment answers
./bin/session handshake # BEYOND's connection setup, incl. the TCP SYN
./bin/session osc-rgba # our OSC and BEYOND's echo, in one file
./bin/session replay --host 169.254.53.5 # does anything act on the 16062 lines? (transmits)
```

Each one starts the capture, tells you what to do at the machine, stops the
capture, decodes it, and says what the result means — so the answer doesn't
depend on remembering the right `tshark` filter at 2am. These are the open
questions from [PROTOCOL.md](PROTOCOL.md), one command each.

## Sending live-control lines

```
./bin/replay --zone all --colour amber --sweep # dry run: print the bytes
./bin/replay --zone 1 --sweep --transmit --host 169.254.53.5
```

The format BEYOND broadcasts is understood; what nobody has tested is whether
anything *listens*. The captures suggest not (16062 has only ever been seen
going host → network), but it is a ten-minute experiment with an unambiguous
result. Run it with BEYOND closed — otherwise a change in the room proves nothing
about what caused it — someone watching the heads, and the E-stop in reach.

`./bin/decode <capture> --repeats` answers the companion question, from captures
we already have: BEYOND never sends the same frame body twice, not even for a
static scene, so a captured frame cannot be replayed.

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

The decoder has tests, run from the repo root:
The decoder and the replay builder have tests, run from the repo root:

```
python3 -m unittest discover -s tools/traffic/lib -p '*_test.py'
Expand Down
21 changes: 21 additions & 0 deletions tools/traffic/bin/replay
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/usr/bin/env bash
#
# replay — send BEYOND's plaintext live-control lines yourself, to find out
# whether anything on the network acts on them.
#
# Usage:
# ./bin/replay # dry run: print the bytes
# ./bin/replay --zone all --colour amber
# ./bin/replay --sweep --zone 1 --transmit --host 169.254.53.5
#
# This is the only tool here that transmits, and only with --transmit. Every
# other tool in tools/traffic stays receive-only.
#
# Run it with BEYOND closed — otherwise a change in the room proves nothing
# about who caused it. Have someone watching the heads and the E-stop in reach.

source "$(dirname "${BASH_SOURCE[0]}")/../lib/common.sh"

command -v python3 >/dev/null || { echo 'error: python3 is required for replay' >&2; exit 127; }

exec python3 "$TRAFFIC_ROOT/lib/replay.py" "$@"
Loading
Loading