SchOS/tools/qrtest.c
Nexoniarz d8e8ae604f qr: every version, 1 to 40
PSOGM mode will put the kernel log on screen as QR codes, a page per
symbol, and version 6 held 134 bytes. Version 40 at level L holds 2,953.

What grew: blocks of two lengths and the interleave that steps around
them, the alignment-pattern grid, the version-information blocks from
version 7, the 16-bit character count from version 10. The per-version
table is down to the two columns that are genuinely transcription (EC
codewords per block, block count); total codewords come from the
geometry.

Checked against segno module for module at every version (exact-fill
payloads), and 120 symbols across all 40 versions decode with zbar.
OpenCV's detector cannot find symbols above about version 24 at all,
segno's included, so qrcheck reports those as the detector's limit.

The crash card stays a version-6 symbol (QR_CARD_BYTES), so a phone still
reads it from across the desk.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
2026-10-25 00:43:36 +02:00

357 lines
12 KiB
C

// Host harness for kernel/core/qr.c.
//
// The QR encoder cannot be unit-tested against itself in any meaningful way: a
// symbol with a mistyped table entry is still a plausible-looking grid of black
// squares. The only real test is whether a decoder reads it back, so this builds
// the same encoder on the host and writes a PNG to scan.
//
// make qrtest a sample crash report
// build/tools/qrtest out.png "text" arbitrary payload
//
// It also self-checks the parts that *can* be checked mechanically -- the
// version table's internal consistency, and the Reed-Solomon remainder against
// a known vector -- so a transcription error is caught before anyone reaches
// for a phone.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define QR_HOST
#include "../kernel/core/qr.c"
// --- Minimal PNG writer ----------------------------------------------------
//
// Uncompressed (stored) deflate blocks, so there is no zlib dependency for a
// tool whose entire job is to produce one small image. A stored block costs
// five bytes of header per 65535 bytes of data, which for a 41-module symbol is
// irrelevant.
static unsigned long crc_table[256];
static void
crc_init(void)
{
for (int n = 0; n < 256; n++) {
unsigned long c = (unsigned long)n;
for (int k = 0; k < 8; k++)
c = (c & 1) ? 0xEDB88320UL ^ (c >> 1) : c >> 1;
crc_table[n] = c;
}
}
static unsigned long
crc(const unsigned char *buf, int len)
{
unsigned long c = 0xFFFFFFFFUL;
for (int n = 0; n < len; n++)
c = crc_table[(c ^ buf[n]) & 0xFF] ^ (c >> 8);
return c ^ 0xFFFFFFFFUL;
}
static void
be32(unsigned char *p, unsigned long v)
{
p[0] = (unsigned char)(v >> 24); p[1] = (unsigned char)(v >> 16);
p[2] = (unsigned char)(v >> 8); p[3] = (unsigned char)v;
}
static void
put_chunk(FILE *f, const char *type, const unsigned char *data, int len)
{
unsigned char hdr[4];
be32(hdr, (unsigned long)len);
fwrite(hdr, 1, 4, f);
unsigned char *buf = malloc((size_t)len + 4);
memcpy(buf, type, 4);
if (len)
memcpy(buf + 4, data, (size_t)len);
fwrite(buf, 1, (size_t)len + 4, f);
be32(hdr, crc(buf, len + 4));
fwrite(hdr, 1, 4, f);
free(buf);
}
// Write the symbol as a PNG, `scale` pixels per module, with the 4-module quiet
// zone the standard requires. The quiet zone is not decoration: without it many
// decoders will not find the symbol at all.
static int
write_png(const char *path, const struct qr *q, int scale, int quiet)
{
int mods = q->size + 2 * quiet;
int w = mods * scale, h = w;
// Greyscale, 8-bit, one filter byte per row.
int stride = w + 1;
unsigned char *raw = malloc((size_t)stride * h);
for (int y = 0; y < h; y++) {
unsigned char *row = raw + (size_t)y * stride;
row[0] = 0; // filter: none
int my = y / scale - quiet;
for (int x = 0; x < w; x++) {
int mx = x / scale - quiet;
int dark = (mx >= 0 && my >= 0 && mx < q->size && my < q->size)
? q->mod[my][mx] : 0;
row[1 + x] = dark ? 0x00 : 0xFF;
}
}
int rawlen = stride * h;
// zlib stream: header, stored deflate blocks, adler32.
int cap = rawlen + (rawlen / 65535 + 1) * 5 + 6;
unsigned char *z = malloc((size_t)cap);
int zn = 0;
z[zn++] = 0x78; z[zn++] = 0x01;
int off = 0;
while (off < rawlen) {
int n = rawlen - off;
if (n > 65535) n = 65535;
z[zn++] = (off + n >= rawlen) ? 1 : 0;
z[zn++] = (unsigned char)(n & 0xFF);
z[zn++] = (unsigned char)(n >> 8);
z[zn++] = (unsigned char)(~n & 0xFF);
z[zn++] = (unsigned char)((~n >> 8) & 0xFF);
memcpy(z + zn, raw + off, (size_t)n);
zn += n;
off += n;
}
unsigned long a = 1, b = 0;
for (int i = 0; i < rawlen; i++) {
a = (a + raw[i]) % 65521;
b = (b + a) % 65521;
}
be32(z + zn, (b << 16) | a);
zn += 4;
FILE *f = fopen(path, "wb");
if (!f) { perror(path); return -1; }
fwrite("\x89PNG\r\n\x1a\n", 1, 8, f);
unsigned char ihdr[13];
be32(ihdr, (unsigned long)w);
be32(ihdr + 4, (unsigned long)h);
ihdr[8] = 8; ihdr[9] = 0; ihdr[10] = 0; ihdr[11] = 0; ihdr[12] = 0;
put_chunk(f, "IHDR", ihdr, 13);
put_chunk(f, "IDAT", z, zn);
put_chunk(f, "IEND", NULL, 0);
fclose(f);
free(raw);
free(z);
return 0;
}
// --- Self-checks -----------------------------------------------------------
static int failures;
static void
check(int ok, const char *what)
{
printf(" [%s] %s\n", ok ? "ok" : "FAIL", what);
if (!ok) failures++;
}
// The version table is the one part of qr.c that is transcription rather than
// reasoning, so check what can be checked: the data left over is positive and
// the blocks are no more than one codeword apart, and the symbol has room for
// exactly the codewords the geometry formula claims.
static void
check_tables(void)
{
printf("version table\n");
for (int v = 1; v <= QR_MAX_VERSION; v++) {
int total = total_codewords(v);
int data = data_codewords(v);
char msg[128];
snprintf(msg, sizeof msg, "v%d: %d data codewords in %d blocks",
v, data, ecc_blocks[v]);
check(data > 0 && data / ecc_blocks[v] > 0, msg);
// Count the modules a symbol of this version actually leaves for data,
// by building one and asking. If the formula's codeword count disagrees
// with the geometry, one of the two is wrong.
static struct qr q;
memset(&q, 0, sizeof q);
q.size = 17 + 4 * v;
draw_function_patterns(&q);
int free_modules = 0;
for (int y = 0; y < q.size; y++)
for (int x = 0; x < q.size; x++)
if (!q.fn[y][x]) free_modules++;
snprintf(msg, sizeof msg,
"v%d: %d free modules holds %d codewords (%d spare bits)",
v, free_modules, total, free_modules - total * 8);
check(free_modules >= total * 8 && free_modules - total * 8 < 8, msg);
}
}
// A known Reed-Solomon vector. "HELLO WORLD" as a version 1-M byte-mode symbol
// has data codewords 0x40 0xB3 0x42 ... ; rather than depend on remembering the
// whole thing, check the standard's own worked example: the generator
// polynomial for degree 10 begins with the coefficients below.
static void
check_rs(void)
{
printf("reed-solomon\n");
unsigned char g[30];
rs_divisor(10, g);
// Generator for 10 EC codewords, highest power first, leading 1 implicit.
static const unsigned char want10[10] = {
216, 194, 159, 111, 199, 94, 95, 113, 157, 193
};
check(memcmp(g, want10, 10) == 0, "degree-10 generator polynomial");
rs_divisor(7, g);
static const unsigned char want7[7] = { 127, 122, 154, 164, 11, 68, 117 };
check(memcmp(g, want7, 7) == 0, "degree-7 generator polynomial");
// Multiplication is the primitive everything else rests on.
check(gmul(0, 5) == 0 && gmul(1, 5) == 5, "gf(256) identity and zero");
check(gmul(0x80, 0x02) == 0x1D, "gf(256) reduction by 0x11D");
}
static void
check_encode(void)
{
printf("encode\n");
static struct qr q;
check(qr_encode(&q, (const unsigned char *)"HELLO", 5) == 0, "short payload encodes");
check(q.size == 21, "and picks version 1");
unsigned char big[QR_MAX_BYTES];
memset(big, 'A', sizeof big);
check(qr_encode(&q, big, QR_MAX_BYTES) == 0, "maximum payload encodes");
check(q.size == QR_MAX_SIZE, "and picks version 40");
check(qr_capacity(40) == 2953 && qr_capacity(10) == 271 && qr_capacity(9) == 230,
"capacities match the standard at v9, v10 and v40");
check(qr_encode(&q, big, QR_MAX_BYTES + 1) == -1, "one byte over is refused");
// Every function module must still be a function module after masking --
// i.e. masking must not have touched the finders.
qr_encode(&q, (const unsigned char *)"test", 4);
check(q.mod[0][0] && q.mod[6][6] && !q.mod[5][5], "finder pattern intact");
check(q.mod[q.size - 8][8] == 1, "dark module set");
// Row 6 alternates with the *even* coordinates dark, so the first timing
// module clear of the separator (x=8) is dark and x=9 is light. Asserting
// this the other way round was the one self-check that failed on the first
// run, and the test was what was wrong -- worth keeping the orientation
// written down.
check(q.mod[6][8] == 1 && q.mod[6][9] == 0, "horizontal timing pattern");
check(q.mod[8][6] == 1 && q.mod[9][6] == 0, "vertical timing pattern");
}
int
main(int argc, char **argv)
{
crc_init();
// -q suppresses the terminal rendering. It is on by default because a
// phone can read the symbol straight off the terminal without anyone
// opening a file, but it is 40 lines of escape codes and sometimes in the
// way.
int quiet = 0;
if (argc > 1 && strcmp(argv[1], "-q") == 0) {
quiet = 1;
argc--; argv++;
}
// -m dumps the raw module matrix and nothing else, for comparison against
// an independent encoder. This is the check that actually matters: a symbol
// can pass every self-check above and still be unreadable if the zigzag,
// the masking or the block interleave is subtly wrong, and none of those
// have a plausible-looking failure mode -- they all just produce noise that
// resembles a QR code. tools/qrcheck.py drives this.
// -f dumps the function-pattern map alongside the modules, so a checker can
// run the zigzag backwards and recover the codewords. Knowing *which*
// codeword two encoders disagree about is the difference between a bug you
// can see and one you can only stare at.
if (argc > 2 && strcmp(argv[1], "-f") == 0) {
static struct qr q;
int n = (int)strlen(argv[2]);
if (n > QR_MAX_BYTES || qr_encode(&q, (const unsigned char *)argv[2], n) != 0)
return 1;
for (int y = 0; y < q.size; y++) {
for (int x = 0; x < q.size; x++)
putchar(q.fn[y][x] ? '1' : '0');
putchar('\n');
}
return 0;
}
if (argc > 2 && strcmp(argv[1], "-m") == 0) {
static struct qr q;
int n = (int)strlen(argv[2]);
if (n > QR_MAX_BYTES || qr_encode(&q, (const unsigned char *)argv[2], n) != 0)
return 1;
for (int y = 0; y < q.size; y++) {
for (int x = 0; x < q.size; x++)
putchar(q.mod[y][x] ? '1' : '0');
putchar('\n');
}
return 0;
}
const char *out = argc > 1 ? argv[1] : "build/qr.png";
const char *text = argc > 2 ? argv[2] :
"SCHOS PANIC\n"
"build 8f2a91c4\n"
"trap 14 err 4 cpu 0\n"
"rip ffffffff80104e2a\n"
"cr2 0000000000000018\n"
"pid 7 schinit";
check_tables();
check_rs();
check_encode();
static struct qr q;
int len = (int)strlen(text);
if (len > QR_MAX_BYTES) {
fprintf(stderr, "qrtest: payload is %d bytes, max %d\n", len, QR_MAX_BYTES);
return 1;
}
if (qr_encode(&q, (const unsigned char *)text, len) != 0) {
fprintf(stderr, "qrtest: encode failed\n");
return 1;
}
if (write_png(out, &q, 8, 4) != 0)
return 1;
printf("\n%d bytes -> version %d (%dx%d modules) -> %s\n",
len, (q.size - 17) / 4, q.size, q.size, out);
// Also dump it to the terminal, where a phone can read it directly off the
// screen without opening the file. Two spaces per module because a terminal
// cell is about twice as tall as it is wide, and a squashed symbol will not
// scan.
if (!quiet) {
printf("\n");
for (int i = 0; i < 2; i++) {
printf(" ");
for (int x = 0; x < q.size + 8; x++) printf("\033[107m \033[0m");
printf("\n");
}
for (int y = 0; y < q.size; y++) {
printf(" \033[107m \033[0m");
for (int x = 0; x < q.size; x++)
printf("%s \033[0m", q.mod[y][x] ? "\033[40m" : "\033[107m");
printf("\033[107m \033[0m\n");
}
for (int i = 0; i < 2; i++) {
printf(" ");
for (int x = 0; x < q.size + 8; x++) printf("\033[107m \033[0m");
printf("\n");
}
}
printf("\n%s\n", failures ? "SELF-CHECKS FAILED" : "self-checks passed");
return failures ? 1 : 0;
}