// 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 #include #include #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; }