Two pieces required before any real-device release ships:
(1) cmd/sh1e-decode — a Pi-side CLI that validates and pretty-prints
an SH1E envelope. Reads from stdin, a file, or a hex string
(`-hex "53 48 31 45 ..."`, spaces/colons/pipes stripped so you
can paste straight from the composer's hex dump pane).
Output: ACCEPT line with byte count + fingerprint, then per-block
summaries (plate type, every text block's font/size/anchor/align/
rotation/content, every SVG path's anchor/scale/rotation/d-string).
On rejection: REJECT <TAG>: <error> where TAG is a stable label
matching the sentinel error name — easy to grep in CI fuzz runs.
Run it like:
sh1e-decode some-design.sh1e
sh1e-decode < piped-stdin
sh1e-decode -hex "53 48 31 45 01 4f 00 ..."
(2) engrave/wire/sh1e/fuzz_test.go — native Go 1.18+ fuzz harness for
Decode and a faster envelope-only variant.
Property: any input must either return a valid Design that
round-trips through Encode to byte-identical output (canonical
encoding) OR return a non-nil error. A panic, hang, or
out-of-memory is a test failure — the Pi controller runs this
parser on untrusted QR-scanned bytes and a crash there is a
real-world fault.
Seed corpus: valid envelopes from the existing tests +
deliberately corrupted variants (length-field flipped, payload
byte flipped) + obvious shape attacks (empty, wrong magic, wrong
version, mostly-FF spam).
Smoke-tested for 5 seconds → 0 crashes, 933k execs/sec, 60 new
interesting inputs discovered, 72 corpus entries. Spec calls for
1 CPU-week before any release ships; CI runs a shorter window
per merge.
Run with:
go test -fuzz=FuzzDecode -fuzztime=10m ./engrave/wire/sh1e
go test -fuzz=FuzzDecodeEnvelopeOnly -fuzztime=10m ./engrave/wire/sh1e
Plus a CRC-stdlib drift smoke test guarding against a future
dependency swap quietly changing the CRC table.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The new code we're adding ourselves — the envelope format that ships
plate designs from the browser-side composer to a Pi controller.
Full spec at docs/architecture/sh1e-spec.md.
Implementation:
engrave/wire/sh1e/sh1e.go (~325 lines)
- Envelope: 4-byte magic "SH1E" + 1-byte version + 2-byte LE
payload_len + 4-byte LE CRC32-IEEE + N-byte CBOR payload.
11-byte overhead. uint16 length cap = 65535-byte payload.
- Public types: Design, TextBlock, SvgPath, PlateType (Small/
Square/Large), FontID (Comfortaa/Poppins/Constant), Alignment
(Left/Center/Right).
- Encode(d Design) -> []byte: validates, computes the SHA-256
design fingerprint over fields 1-3, canonical-CBOR encodes
the whole Design (with fingerprint as field 4), wraps in
envelope. Same Design always produces byte-identical output.
- Decode([]byte) -> Design: header check, length check, CRC
check, CBOR unmarshal, canonical-encoding round-trip check
(re-encodes the decoded value and compares bytes — catches
non-canonical input), structural validation, fingerprint
recomputation + match.
- Sentinel errors for every rejection mode (errors.Is-friendly):
ErrBadMagic, ErrUnsupportedVersion, ErrLengthMismatch,
ErrCRCMismatch, ErrBadCBOR, ErrNotCanonical, ErrTruncated,
ErrFingerprintMismatch, ErrPayloadTooLarge, ErrTooManyBlocks,
ErrTooManyPaths, ErrTextTooLong, ErrPathTooLong, ErrNonASCII,
ErrInvalidEnum, ErrOutOfRange, ErrInvalidRotation.
engrave/wire/sh1e/sh1e_test.go (~270 lines)
- Round-trip: minimal design + design-with-logo.
- Stability: same input -> byte-identical output across encodes.
- Fingerprint independence: encoder overwrites caller-supplied
Fingerprint with the recomputed one.
- Envelope rejections: truncated, bad magic, wrong version,
length-mismatch, corrupted CRC, fingerprint mismatch (forged
payload with valid CRC).
- Validation rejections: bad plate type, bad font ID, font size
out of range, text too long, non-ASCII text, too many blocks,
too many paths, path too long, bad alignment, bad rotation,
bad scale percent.
All 17 test packages green. Strict-subset SVG path validator (M/L/
H/V/Q/C/Z only, no arcs / S / T) deliberately deferred to live
alongside the path renderer in a later commit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>