diff --git a/.gitignore b/.gitignore index a14702c..103c27a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -# dependencies (bun install) +# dependencies node_modules # output diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index b839785..52edc3b 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -1,370 +1,79 @@ -# QR Stream - Architecture +# RaptorQR Architecture -This document describes the internal design, wire format, and algorithms used by QR Stream. For user-facing installation and usage instructions, see [README.md](README.md). +RaptorQR is a pnpm monorepo for camera-based QR transfer. The project is split so the protocol and codec code can become a reusable low-level library, while the CLI and web app remain consumers of that library. ---- +## Monorepo Layout -## Table of Contents +```text +packages/raptorqr-core + src/protocol fixed header, CRC32C, transfer profiles + src/sender packetizers and frame scheduling + src/fec RaptorQ facade, deprecated JS RLNC, outer RS helpers + src/qr QR capacity, encode/decode facades, raster helpers + src/gif GIF parse/render helpers + src/reconstruct payload assembly -1. [High-Level Architecture](#high-level-architecture) -2. [Project Structure](#project-structure) -3. [Dependencies](#dependencies) -4. [The Protocol](#the-protocol) -5. [Algorithms](#algorithms) -6. [Data Flow](#data-flow) -7. [Design Decisions](#design-decisions) -8. [Common Pitfalls](#common-pitfalls) +packages/raptorqr-wasm + src/fast_qr fast_qr wasm-bindgen artifacts and Colab script + src/raptorq cberner/raptorq wasm-bindgen artifacts and Colab script ---- +packages/raptorqr-cli + src/raptorqr.ts CLI entrypoint + src/terminal_raster.ts terminal QR renderer + src/static_server.ts built web app preview server -## High-Level Architecture - -``` -┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ -│ Sender UI │──────▶│ Encode Worker │──────▶│ GIF Worker │ -│ (Preact hooks) │ │ (Web Worker) │ │ (Web Worker) │ -└─────────────────┘ └─────────────────┘ └─────────────────┘ - │ - ▼ - ┌────────────┐ - │ .gif file │ - │ (animated) │ - └────────────┘ - │ -┌─────────────────┐ ┌─────────────────┐ │ -│ Receiver UI │◀──────│ Decode Worker │◀──────├──────── camera / file -│ (Preact hooks) │ │ (Web Worker) │ │ -└─────────────────┘ └─────────────────┘ └──────────────────────────┘ +apps/web + src/app Preact routes and UI + src/workers encode, decode, GIF, and QR render workers + src/lib app-local worker orchestration + src/tests browser/WebAssembly integration tests ``` -Everything heavy (compression, RLNC encoding, GIF encoding, QR decoding) runs in dedicated Web Workers so the UI stays responsive. +## Package Boundaries ---- +`@raptorqr/core` owns protocol behavior and public transfer APIs. It exports environment-neutral modules from `@raptorqr/core`, browser wrappers from `@raptorqr/core/browser`, and Node/CLI wrappers from `@raptorqr/core/node`. -## Project Structure +`@raptorqr/wasm` owns generated WASM artifacts. Core imports fast_qr through `@raptorqr/wasm/fast-qr` and RaptorQ through `@raptorqr/wasm/raptorq`. -``` -src/ -├── app/ -│ ├── app.tsx # App shell with hash-based tab routing -│ └── routes/ -│ ├── sender.tsx # Text/file input, GIF preview, download -│ └── receiver.tsx # Camera scanner, GIF upload, results -├── core/ -│ ├── fec/ # Forward Error Correction (RLNC) -│ │ ├── gf256.ts # GF(256) arithmetic (log/antilog tables) -│ │ ├── rlnc_encoder.ts # Systematic RLNC encoder -│ │ ├── rlnc_decoder.ts # Incremental Gaussian-elimination decoder -│ │ └── xoshiro.ts # xoshiro128** PRNG for deterministic coeffs -│ ├── gif/ -│ │ ├── gif_parser.ts # GIF decoding (LZW, frame extraction) -│ │ └── gif_render.ts # GIF encoding (2-colour palette, gifenc) -│ ├── preprocess/ -│ │ └── compress.ts # (legacy) deflate helpers -│ ├── protocol/ -│ │ ├── constants.ts # Protocol constants: K/R, sizes, flags -│ │ ├── crc32c.ts # CRC32-C (Castagnoli) with lookup table -│ │ └── packet.ts # 8-byte fixed header + payload + CRC -│ ├── qr/ -│ │ ├── qr_encode.ts # QR matrix generation (qrcode-generator) -│ │ ├── qr_decode.ts # QR decoding wrapper (jsQR) -│ │ └── frame_raster.ts # Matrix → RGBA raster (scale, quiet zone) -│ ├── reconstruct/ -│ │ └── assemble.ts # Concatenate generations, trim padding -│ └── sender/ -│ ├── packetizer.ts # Data → packets: compress, split, wrap metadata -│ └── scheduler.ts # Interleave systematic/coded symbols into frames -├── workers/ -│ ├── encode.worker.ts # Orchestrates packetizer + scheduler -│ ├── decode.worker.ts # Feeds frames to RLNC decoder, reassembles -│ └── gif.worker.ts # Rasters packets → RGBA → GIF via gifenc -├── tests/ -│ ├── complete.test.ts # Unit tests for all core modules -│ ├── frame_decode.test.ts # QR encode→decode roundtrip per frame -│ ├── gif_roundtrip.test.ts# Full GIF encode→parse→decode roundtrip -│ ├── prod_roundtrip.test.ts# Deterministic frame-loss recovery test -│ ├── test_qr_modules.test.ts# QR capacity, raster, GIF render tests -│ └── setup.ts # happy-dom test environment setup -├── types/ -│ └── gifenc.d.ts # Type declarations for gifenc -├── cli/ -│ ├── qr-stream.ts # CLI entry point (terminal QR display) -│ │ # stdin → isText=true (text mode) -│ │ # → isText=false, embeds filename+MIME -│ ├── terminal_raster.ts # Half-block Unicode renderer -│ └── static_server.ts # Built-in preview server (--serve) -├── index.html # Single-page app entry -└── main.tsx # Renders into #root +`@raptorqr/cli` depends on core and wasm. It bundles to `packages/raptorqr-cli/dist/raptorqr.js` and copies required WASM sidecars next to the bundle. + +`@raptorqr/web` is private. It owns Vite, Preact UI, workers, and app-local worker pools. App-local `@/*` imports must not leak into packages. + +## Protocol + +The transport packet keeps the existing 8-byte header plus payload plus CRC32C trailer. The protocol does not add QR profile negotiation to the header; the receiver infers QR version from decoded symbols and packet payload size. + +FEC codec detection uses the existing symbol index field: + +* `symbolIndex = 0..23`: deprecated JS RLNC compatible packets +* `symbolIndex = 31`: primary RaptorQ WASM packets + +`wasm-raptorq` is the default FEC codec. `js-rlnc` remains explicit and test-covered, but it is deprecated and is never used as an automatic fallback if RaptorQ WASM is unavailable. + +## QR Encoding And Decoding + +QR generation and FEC are separate layers: + +* FEC codec: `wasm-raptorq` or deprecated `js-rlnc` +* QR encoder: `fast-qr-wasm` or `zxing-wasm` + +fast_qr WASM exposes both RGBA rendering and raw matrix output. The web app uses RGBA output for live/GIF rendering; the CLI uses matrix output for terminal rendering. + +ZXing WASM is used for decoding and remains available as a QR writer option in the browser. + +## Build And Test + +Root commands orchestrate package-level commands: + +```bash +pnpm build +pnpm test +pnpm dev:web ``` ---- +The test split follows ownership: -## Dependencies - -### Runtime - -- **preact** - UI framework (React-compatible, ~10 KB) -- **qrcode-generator** - QR matrix generation (versions 1–40, all ECC levels) -- **jsqr** - QR decoding from `ImageData` (grayscale + adaptive thresholding internally) -- **gifenc** - Animated GIF encoder (2-colour palette, LZW compression) -- **fflate** - Fast deflate/inflate (compression for large payloads) - -### Dev / Build - -- **vite** - Build tool, dev server, worker bundling -- **@preact/preset-vite** - Preact JSX transform for Vite -- **vitest** - Test runner -- **happy-dom** - DOM environment for headless tests -- **typescript** - Type checking -- **esbuild** - CLI bundle (via `build:cli` script) - ---- - -## The Protocol - -There is **one hardcoded profile** - no negotiation, no manifest, no session IDs. - -### Profile Constants - -- **QR Version:** V10 (57×57 modules) -- **ECC Level:** M (~15% correction) -- **Source symbols per generation (K):** 16 -- **Repair symbols per generation (R):** 8 -- **Symbol payload:** 201 bytes -- **Max packet size:** 213 bytes (fits exactly in V10-M) -- **Frame delay:** 200 ms (5 fps) -- **Max file size:** ~8 MB - -### Packet Format (fixed 8-byte header) - -All multi-byte fields are **little-endian**. - -``` -Offset Size Field -───────────────────────────────────────────────────────────────────────────────── - 0 1 Magic: 0x51 ('Q') - 1 4 Packed word (32 bits): - ├─ bits 0–11 : generation index (0–4095) - ├─ bits 12–23 : total generations (0–4095) - ├─ bits 24–28 : symbol index (0–31) - ├─ bit 29 : isText flag (1 = text, 0 = file) - ├─ bit 30 : isLastGeneration flag - └─ bit 31 : compressed flag - 5 3 Data length (preprocessed size, 24-bit, 0–16,777,215) - 8 201 Payload (zero-padded to 201 B) -209 4 CRC32-C over bytes 0–208 -``` - -Total: **213 bytes** → fits in a V10-M QR code. - -**Symbol index convention:** -- `0–15` = systematic symbol (`sourceIndex = symbolIndex`) -- `16–23` = coded repair symbol (`codedSymbolIndex = symbolIndex − 16`) -- `24–31` = reserved - -### File Metadata Wrapping - -For file transfers (not text), the raw file bytes are prefixed with a tiny metadata envelope before compression: - -``` -[1 byte: filename length (0–255)] -[N bytes: filename UTF-8] -[1 byte: MIME type length (0–255)] -[M bytes: MIME type UTF-8] -[rest: actual file data] -``` - -This lets the receiver restore the original filename and MIME type on download. - ---- - -## Algorithms - -### 1. RLNC over GF(256) - -We use **Random Linear Network Coding** with a systematic encoding. - -**Encoder (`rlnc_encoder.ts`):** -- Given `K` source symbols, output `K` systematic + `R` coded symbols. -- Systematic symbols are the original data (identity coefficient vector). -- Each coded symbol is a random linear combination: `C_j = Σ coeff[i] · S_i` (multiplication and addition in GF(256)). -- Coefficients are deterministically derived from `(generationIndex, codedSymbolIndex)` via a xoshiro128** PRNG. - -**Decoder (`rlnc_decoder.ts`):** -- Maintains an augmented coefficient matrix in **reduced row-echelon form (RREF)**. -- Each incoming symbol is forward-eliminated against existing pivots, then if it has a new pivot: - 1. Scale the row so pivot = 1 - 2. Eliminate the new pivot from all existing rows - 3. Insert maintaining pivot-column order -- When `rank == K`, the matrix is identity and the RHS data is the reconstructed source symbols. - -### 2. GF(256) Arithmetic (`gf256.ts`) - -- Irreducible polynomial: `x^8 + x^4 + x^3 + x^2 + 1` (0x11d, same as AES). -- Pre-computed **log/antilog tables** at module load time for O(1) multiply/divide/inverse. -- Addition/subtraction = XOR (same operation in characteristic-2 fields). - -### 3. QR Code Generation (`qr_encode.ts`, `frame_raster.ts`) - -- Uses `qrcode-generator` library to produce boolean module matrices. -- Capacity is computed from an embedded RS block table (versions 1–40, all ECC levels). -- `rasterizeQR()` scales each module to `scale × scale` pixels and adds a 4-module white quiet zone. -- Output is pure black/white RGBA `ImageData`. - -### 4. QR Decoding (`qr_decode.ts`) - -- Thin wrapper around `jsQR`. -- `jsQR` internally converts RGBA → grayscale and applies adaptive thresholding (8×8 regions with 5×5 averaging). No external preprocessing is needed. -- For camera scanning we pass `inversionAttempts: 'attemptBoth'` (handles glare/reflections). For GIF file mode we use `'dontInvert'` (our QRs are black-on-white, giving ~50% speedup). - -### 5. GIF Encoding (`gif_render.ts`) - -- Uses `gifenc` with a **2-colour global palette** (white, black). -- Each frame is converted from RGBA to indexed (threshold at 50% brightness). -- The NETSCAPE 2.0 extension sets loop count to infinity. -- Default delay: 200 ms per frame (5 fps). - -### 6. Frame Scheduling (`scheduler.ts`) - -- Systematic symbols are interleaved across generations first, then coded symbols. -- Generation order is deterministically shuffled using `totalGenerations` as a seed. -- This spreads redundancy evenly: if you watch any prefix of the GIF, you see some symbols from every generation. - ---- - -## Data Flow - -### Sender - -``` -Text or File - │ - ▼ -[Wrap metadata if file] - │ - ▼ -[Optional deflate compression (fflate)] - │ - ▼ -Split into 201-byte symbols - │ - ▼ -Group into generations of K=16 - │ - ▼ -RLNC encode each generation → 16 systematic + 8 coded symbols - │ - ▼ -Build packets (8-byte header + payload + CRC32C) - │ - ▼ -Schedule frames (interleave systematic, then coded, shuffle generations) - │ - ▼ -Rasterize each packet to QR code (V10-M, scale=3, 4-module quiet zone) - │ - ▼ -Encode frames into animated GIF (2-colour palette, 200 ms delay) - │ - ▼ -Blob URL → preview + download -``` - -### Receiver - -``` -Camera frames or GIF file - │ - ▼ -[If camera: software crop center 50% (2× zoom), optional camera zoom API] - │ - ▼ -Decode QR with jsQR → raw bytes - │ - ▼ -Parse packet (verify magic, verify CRC32C) - │ - ▼ -Deduplicate by (generationIndex, symbolIndex) - │ - ▼ -Feed to RLNC decoder (systematic or coded based on symbolIndex) - │ - ▼ -When rank == K for a generation → mark solved - │ - ▼ -When all generations solved: - │ - ├── Text mode → decompress → TextDecoder → show in