Development¶
How Linkside is built, and how it is tested.
Architecture¶
[Pi 5 + 2× Netgear A9000] ── Ethernet (IPv6 link-local), SSH ──> [Mac]
Raspberry Pi OS Lite tshark (Wireshark 4.6) decodes
monitor mode, channel/width via iw Linkside app (SwiftUI + LinksideKit): metrics, events,
linkside-cap: address filter + truncation per frame type profile, MLO mapping, Mist, hints, export
- The Pi captures and tunes, nothing more.
linkside-radiosets the monitor interfaces and their channel and width withiw; linkside-cap (C, libpcap) captures with an address filter and truncation per frame type and writes pcap to stdout. The Mac controls everything over SSH: no daemon on the Pi, no wire format of its own. - The Mac starts per radio a capture stream over SSH and tshark processes that decode it. The engine in LinksideKit (Swift) does everything after that: the client's connection, Block Ack success, the TXOPs, events, the client profile, the MLO link mapping, the EAP timeline, Mist, the rules and the export. How the engine counts has its definitions.
- Hardware-dependent parts (SSH to the Pi, the Mist API) sit behind a protocol with a replay or fake implementation, so the engine and the rules run on recorded sessions in tests. So does the Mac's clock of a live session (
MacClock), which most of its tests move by hand (the LinksideKit README).
The repository¶
| Path | Contents |
|---|---|
sensor/ |
linkside-cap, the scripts for the radios and the system setup, the config fragments for the Pi |
app/ |
The Xcode project (a thin SwiftUI shell) and the package LinksideKit: the library LinksideKit (sensor link, decoding, engine, sessions, Mist, hints, export), LinksideUI (the screens) and linkside-cli |
testdata/ |
Synthetic captures and recorded sessions from own devices, anonymized, with their golden files (test data) |
tools/anonymize/ |
The anonymizer for test data (anonymize) |
docs/ |
This site; the measurements, the testlab runbook and the analysis are part of it |
Each part has a README with its layout, its build and its tests.
Build and test¶
make -C sensor test # linkside-cap and the script tests, also on the Mac
make -C tools/anonymize test
make -C app/LinksideKit test # the engine, the sessions, the screens' store
make -C app/LinksideKit test-golden # every recording against its golden files
make docs # this site, strict: a broken link fails
The golden tests compare each recording's analysis with its golden.json and verdict.json, and its expected.json holds the right answer, which only a person changes: golden files. The screens render to PNG without running the app (RenderTests, in the LinksideKit README).
The icon and the logo¶
One symbol serves as the app's icon, the site's logo and its favicon: a link between two ends, and beside it the sensor, listening. Navy #13263F, white for the link, orange #EB6834 for the sensor.
| File | What |
|---|---|
app/Linkside/AppIcon.icon |
The app's icon, an Icon Composer document: the link and the sensor as two layers of glass on the navy fill (ASSETCATALOG_COMPILER_APPICON_NAME). Xcode compiles the looks of macOS 26 (default, dark, tinted, clear) and flat renderings up to 1024 px for macOS 15 |
docs/assets/logo.svg |
The site's logo: the icon flat, on its navy tile, so it reads on the light and the dark header |
docs/assets/favicon.svg |
The favicon, drawn for 16 and 32 px: thicker lines, one arc instead of two |
docs/assets/favicon.png |
The favicon as the site serves it, rendered from favicon.svg |
A change to the symbol goes into the two layers of the .icon, logo.svg and favicon.svg together. Icon Composer opens the .icon; its ictool renders each look without it, and rsvg-convert (Homebrew's librsvg) the favicon:
"/Applications/Xcode.app/Contents/Applications/Icon Composer.app/Contents/Executables/ictool" \
app/Linkside/AppIcon.icon --export-image --output-file icon.png \
--platform macOS --rendition Default --width 1024 --height 1024 --scale 1 # also Dark, TintedLight, TintedDark, ClearLight, ClearDark
rsvg-convert -w 32 -h 32 docs/assets/favicon.svg -o docs/assets/favicon.png
A Mac that ran a build before it had the icon keeps the generic icon in the icon cache of macOS, per path of the app: on 2026-10-25 the Release build in DerivedData showed none in the Dock in dark mode (at 64 and 128 px, the Dock's sizes), while a copy of the same build elsewhere showed it. lsregister -f and restarting the Dock left it; emptying the cache brought it back:
sudo rm -rf /Library/Caches/com.apple.iconservices.store
sudo killall iconservicesd
killall iconservicesagent Dock
The evidence¶
- Test data: every recording, what it shows and its expected outcome, and the scorecard of the rules.
- Testlab runbook: the problems caused on purpose at home, with their setup and the expected verdict.
- Measurements: what was measured on the hardware, the APs, Mist and the decoder, one document per subject.
- Feasibility: what the sensor can detect and attribute per problem class.