From d360f8ed78adf08e2a99df0be141b0a823cf8be9 Mon Sep 17 00:00:00 2001 From: cirakg Date: Tue, 20 Oct 2026 11:24:58 +0200 Subject: docs: record M6c (Data Matrix, PDF417) results Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 15 +++++++++++++++ README.md | 10 +++++++++- docs/DEVELOPER_GUIDE.md | 2 ++ docs/TESTING.md | 19 +++++++++++++++++++ 4 files changed, 45 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e1d8912..31b6ce8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,21 @@ Milestone M6 in progress (released as 0.6.0 when all four parts are done). - Drawn in the largest centred square at 2 px per module or more. - Checked against segno (`tools/gen_qr_tables.py`, `make codes`), decoded with zxing-cpp, and decoded from a screenshot of a version 6 card. +- M6c, Data Matrix (`DataMatrix.mc`): + - ECC 200 square sizes 10x10 to 144x144, ASCII with digit pairs, and + Base256 or Upper Shift for UTF-8. + - Encoded as a job; identical to ppf.datamatrix in ASCII mode. +- M6c, PDF417 (`Pdf417.mc`): + - Text, byte and numeric compaction as pdf417gen does it, with automatic + (ISO) or fixed error-correction levels. + - Columns and module size chosen for the screen. + - The pattern tables are generated JSON resources, loaded only while + encoding (`tools/gen_pdf417_tables.py`). + - Identical to pdf417gen. +- 2D codes now share a matrix type with a row height, a fit rule for round + screens, deferred cache saving and 8-at-a-time packing. +- Dev image: pdf417gen, ppf-datamatrix and aztec-code-generator as reference + encoders (pure Python, MIT). ## [0.5.0] - 2026-10-20 diff --git a/README.md b/README.md index 9655d4f..b9a119b 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,13 @@ A Garmin Connect IQ watch app that shows one-time passwords (TOTP, HOTP, and optional Steam Guard codes) and stores barcodes and QR codes (loyalty cards, tickets) to show at a scanner. Everything lives encrypted on the watch. There is no cloud, no backend and no analytics, and the app requests no permissions, so it cannot use the network. -> **Status: M6a (linear barcodes) and M6b (QR) done; M6c-M6d (Data Matrix, PDF417, Aztec) next; M1 (settings spike) ready for your phone test.** The plan below was approved on 2026-10-18. M0 built the Docker toolchain and pipeline, M2 the crypto and OTP core, M3 the encrypted vault ([docs/SECURITY.md](docs/SECURITY.md)), M4 the screens and M5 import (`otpauth://` and Aegis JSON via the phone settings). M6a added the code screen and the eight linear symbologies. M6b added QR Code: versions 1 to 40, levels L to H, optimal mode segmentation and all eight masks, encoded on the watch as a resumable job and cached encrypted. QR is checked against segno module for module and decoded with zxing-cpp, and screenshots of both kinds of code decode on seven simulated watches. The settings spike (`make spike`, [docs/SETTINGS_SPIKE.md](docs/SETTINGS_SPIKE.md)) waits for your test. The Phase 0 research, with sources, is in [docs/FEASIBILITY.md](docs/FEASIBILITY.md); the daily workflow is in [docs/DEVELOPER_GUIDE.md](docs/DEVELOPER_GUIDE.md). +> **Status: M6a-M6c (linear barcodes, QR, Data Matrix, PDF417) done; M6d (Aztec) next; M1 (settings spike) ready for your phone test.** The plan below was approved on 2026-10-18. M0 built the Docker toolchain and pipeline, M2 the crypto and OTP core, M3 the encrypted vault ([docs/SECURITY.md](docs/SECURITY.md)), M4 the screens and M5 import (`otpauth://` and Aegis JSON via the phone settings). M6 added the code screen and, so far, 11 of the 12 symbologies, encoded on the watch: +> +> - **Linear codes** are encoded directly. +> - **2D codes** are encoded as resumable jobs and cached encrypted. +> - **Checks:** each is compared with a reference encoder (zxing-cpp, segno, ppf.datamatrix, pdf417gen) and decoded with zxing-cpp, and screenshots of four kinds of code decode on seven simulated watches. +> +> The settings spike (`make spike`, [docs/SETTINGS_SPIKE.md](docs/SETTINGS_SPIKE.md)) waits for your test. The Phase 0 research, with sources, is in [docs/FEASIBILITY.md](docs/FEASIBILITY.md); the daily workflow is in [docs/DEVELOPER_GUIDE.md](docs/DEVELOPER_GUIDE.md). "OTP Wallet" is a working title. @@ -590,6 +596,8 @@ Linear: EAN-13 needs 113 modules, which means 1 px per module on a 218 px screen 1. **Reference comparison**: Python reference encoders in Docker (zxing-cpp's writer, segno for QR with forced version and mask, and others) generate expected bar patterns and matrices for fixed parameters. The watch encoder must match them exactly wherever the output is deterministic: the EAN/UPC family, Code 39, ITF, Codabar, QR with forced version and mask, and Data Matrix in ASCII mode. Code 128 is the exception, because it has several equally short encodings. zxing-cpp 3 writes through zint, which breaks those ties differently from the zxing rules the watch uses. For Code 128 the module count must match (it did for 5,000 random strings), and the code must decode (check 2). For QR there are two deliberate differences from segno: - **Padding.** segno adds a whole zero byte when the bit stream already ends on a codeword boundary (always the case in byte mode). ISO/IEC 18004 7.4.10 adds no padding there, and the watch follows the standard. The exact references therefore use payloads that segno pads correctly. - **Segmentation.** segno encodes one mode per symbol, while the watch segments optimally. The exact references use single-mode payloads, and mixed content is covered by decoding. + + Data Matrix is compared with ppf.datamatrix in ASCII mode. ppf turns a pad codeword of exactly 254 into 0 (`(R + 130) % 254`), while ISO/IEC 16022 5.2.3 keeps 254, so the references avoid payloads whose padding hits that value. PDF417 is compared with pdf417gen at fixed columns and level. The watch mirrors pdf417gen's compaction rules, so its rows match exactly. 2. **Decoding**: the simulator tests dump every encoder output. It's rendered at each device's module size and decoded with zxing-cpp, which reads all 12 symbologies. The decoded text must equal the input, for hundreds of seeded random payloads per symbology. 3. **Screenshots**: simulator screenshots of the code screen on Forerunner 255S, Venu Sq 2 and Forerunner 965 must decode with zxing-cpp. 4. **Real world** (manual checklist): your phone's camera, plus any real shop scanner you can try, indoors and outdoors, on MIP and AMOLED. diff --git a/docs/DEVELOPER_GUIDE.md b/docs/DEVELOPER_GUIDE.md index 14c4e05..2646e1b 100644 --- a/docs/DEVELOPER_GUIDE.md +++ b/docs/DEVELOPER_GUIDE.md @@ -78,6 +78,8 @@ The simulator is a GTK program. The scripts start it on a virtual display (Xvfb - Assigning a value above 255 to a ByteArray element throws; mask with `& 0xFF`. - Native HMAC supports only SHA-256; HMAC-SHA1 and HMAC-SHA512 go through `Hmac.generic`. - Function calls are expensive in the VM; hot loops inline small helpers (SHA-512 went 37 % faster that way). +- **Long `&&` chains with `instanceof`** can make the compiler's type checker run out of memory: one test condition needed more than 6 GB until it was split into separate `if` statements. +- **JSON resources over 8 KB** produce a compiler warning, so large tables are split; the PDF417 patterns are three resources, one per cluster. - **The watchdog** ("Code Executed Too Long") is not enforced in unit-test runs, but it is in normal simulator runs, with the device's `watchdogCount` per event: 120,000 bytecodes on the Forerunner 255/255S, 240,000 on the others. M4 measured what it means. 200 byte-wise PBKDF2 iterations in one event tripped it on the 255S, and so did two SHA-512 codes in one draw. So: - Anything long runs as a `Jobs.Job` with a bounded step (`Kdf.ITERATIONS_PER_STEP` = 100). - Screens compute at most one SHA-512 code per event, in a timer event separate from drawing. diff --git a/docs/TESTING.md b/docs/TESTING.md index ca72af3..cf9bee0 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -26,6 +26,24 @@ Test devices (`TEST_DEVICES`): Forerunner 255S (weakest: 218 px round MIP, butto ## Results +### M6c, 2026-10-20 (Data Matrix and PDF417, in 0.6.0) + +Monkey C unit tests: 117 per device. + +1. **Reference comparison** (`make codes`): + - **Data Matrix:** 27 symbols identical to ppf.datamatrix in ASCII mode, from 10 x 10 to 144 x 144, including multi-region and 10-block interleaved sizes. For 1,100 digits at 88 x 88, the watch, ppf and zint (through zxing-cpp) agree bit for bit. + - **PDF417:** 27 symbols identical to pdf417gen at fixed columns and levels. They cover all four text submodes, digit runs below and above 13, UTF-8 bytes, exact 6-byte groups, levels 0 to 6 and 1 to 12 columns. +2. **Decoding with zxing-cpp:** + - 10 Data Matrix codes, including Base256 UTF-8, Upper Shift, 88 x 88 and 144 x 144. + - 5 PDF417 codes laid out for the Forerunner 255S, Forerunner 70 and Venu Sq 2 screens. +3. **Screenshots.** The UI flow opens a Data Matrix card and a PDF417 card after the QR card. All decode on all seven watches, the PDF417 at 1 px per module on the 255S. + +Found and fixed along the way: + +- **ppf padding bug:** see README 12.4. +- **Compiler out of memory:** one long `&&` chain of `instanceof` tests in a test made the Monkey C compiler run out of memory (more than 6 GB), and splitting the chain fixed it. +- **Watchdog on the 255S:** packing an 86 x 87 PDF417 matrix for the cache in the same event as the encoder's last step tripped the watchdog. PDF417 rows are now stored once with a row height of 3, packing does 8 modules per loop, and the cache is saved in its own timer event. + ### M6b, 2026-10-20 (QR, in 0.6.0) Monkey C unit tests: 111 per device. @@ -167,3 +185,4 @@ Things the simulator can't verify, done by you on the Forerunner 70. The checkli - [ ] M5: in Garmin Connect, paste a few `otpauth://` links of test accounts (not your real ones) into the Import fields, turn "Import now" on and save. Check the result on the watch and in the Result field. Also check that the text fields are emptied afterwards: this answers part of the M1 question. - [ ] M6a: with "Debug: demo entries", open "Gym card" (an EAN-13). Scan it with your phone's camera, or a shop scanner if you can, indoors and outdoors. Tell me whether it reads. - [ ] M6b: open "Member QR" (a demo entry) and scan it with your phone's camera. The first opening encodes it; note roughly how long that takes. The second opening should be instant (cache). +- [ ] M6c: scan the "Parcel" (Data Matrix) and "Ticket" (PDF417) demo cards with a phone app that reads them, for example the zxing-based "Barcode Scanner". -- cgit v1.2.3