SchOS/tools/qrtest.c
Nexoniarz e04ca28812 Rename Schunix to SchOS; the kernel becomes SchIFT
Removes the "-unix" element and with it any trademark exposure to The
Open Group.

The split follows the ecosystem layout: SchOS is the system, and the
kernel has its own name. Kernel-identity strings are the only places
that take SchIFT -- uname(2) sysname, /proc/version, and the two licence
sentences that say "kernel" outright. That is what every Unix does here:
`uname -s` on Linux reports the kernel, not the distribution. Everything
a user reads as the product -- panic screen, boot menu, error prefixes,
docs, LICENSE, NOTICE -- is SchOS.

schunix.img is schos.img, the logo assets and the musl TLS patch are
renamed, and __SCHUNIX__ is __SCHOS__ (the define and the patch that
consumes it moved together).

Two things the rename broke, both found by the bootcheck suite:

  - shmtest wrote "SCHUNIX-MMAP-OK" with a hardcoded length of 15 and
    compared it back by hand-counted offsets (m[3]=='U', m[14]=='K').
    The shorter marker left the write running two bytes past the literal
    and the compare reading past the data, failing a file-backed mmap
    that was working correctly. Now written and compared through one
    constant with sizeof.
  - bootcheck asserted a 31-byte file after appending to
    "hello-from-schunix"; the shorter string makes it 29.

Both are the same mistake: a string literal whose length is written out
somewhere else. Full suite green -- base, Tty, Ps, Sock, Fb, Usb, Evdev.
2026-08-09 14:09:59 +02:00

354 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: block sizes must divide evenly, and
// the symbol must have room for exactly the codewords claimed.
static void
check_tables(void)
{
printf("version table\n");
for (int v = 1; v <= QR_MAX_VERSION; v++) {
int total = vinfo[v].total;
int data = total - vinfo[v].ecc * vinfo[v].blocks;
char msg[128];
snprintf(msg, sizeof msg, "v%d: %d data codewords divide into %d blocks",
v, data, vinfo[v].blocks);
check(data > 0 && data % vinfo[v].blocks == 0, msg);
// Count the modules a symbol of this version actually leaves for data,
// by building one and asking. If the table's codeword count disagrees
// with the geometry, one of the two is wrong.
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");
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 == 41, "and picks version 6");
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) {
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) {
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();
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;
}