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
This commit is contained in:
parent
5ea03c4cf5
commit
d8e8ae604f
5 changed files with 147 additions and 83 deletions
|
|
@ -8,25 +8,26 @@
|
|||
// no second machine on the desk. A phone camera is a working out-of-band
|
||||
// channel that needs no drivers on our side at all. See docs/architecture/graphics.md.
|
||||
//
|
||||
// Deliberately small, in three ways:
|
||||
// Deliberately small, in two ways:
|
||||
//
|
||||
// - Byte mode only. The report is arbitrary bytes; alphanumeric mode would
|
||||
// pack tighter but only for a restricted character set.
|
||||
// - Error correction level L only. The screen is a clean, evenly lit,
|
||||
// high-contrast source -- the failure mode QR's stronger levels protect
|
||||
// against (dirt, print damage, curved packaging) does not apply.
|
||||
// - Versions 1 to 6 only. Version 7 is where QR starts requiring an extra
|
||||
// 18-bit version-information block in two more places on the symbol, and
|
||||
// version 10 is where the character count field grows and the alignment
|
||||
// pattern grid becomes 2-dimensional. Stopping at 6 avoids both, and
|
||||
// 134 bytes is a large crash report.
|
||||
//
|
||||
// If a payload ever needs more than 134 bytes, the honest fix is to shorten the
|
||||
// report -- not to reach for version 10 and its two extra tables.
|
||||
// Every version, 1 to 40. It stopped at 6 -- 134 bytes, enough for the crash
|
||||
// card -- until PSOGM mode wanted the log itself off the machine, a page of it
|
||||
// per symbol: version 40 at level L carries 2,953 bytes. A struct qr is
|
||||
// therefore about 62 KB; keep one static, never on a stack.
|
||||
|
||||
#define QR_MAX_VERSION 6
|
||||
#define QR_MAX_SIZE (17 + 4 * QR_MAX_VERSION) // 41 modules per side
|
||||
#define QR_MAX_BYTES 134
|
||||
#define QR_MAX_VERSION 40
|
||||
#define QR_MAX_SIZE (17 + 4 * QR_MAX_VERSION) // 177 modules per side
|
||||
#define QR_MAX_BYTES 2953
|
||||
|
||||
// The crash card on the PSOGM stays small, so a phone reads it from across a
|
||||
// desk: a version-6 symbol at most.
|
||||
#define QR_CARD_BYTES 134
|
||||
|
||||
struct qr {
|
||||
int size; // modules per side; 0 if unencoded
|
||||
|
|
|
|||
|
|
@ -326,7 +326,7 @@ text_at(int x, int y, const char *s, uint32 color)
|
|||
static int
|
||||
draw_qr_panel(int sw, int sh, int ch, char *reason, addr_t *pcs, int npcs)
|
||||
{
|
||||
char payload[QR_MAX_BYTES];
|
||||
char payload[QR_CARD_BYTES];
|
||||
int n = build_qr_payload(payload, sizeof(payload), reason, pcs, npcs);
|
||||
|
||||
if (n <= 0 || qr_encode(&panic_qr, (unsigned char *)payload, n) != 0)
|
||||
|
|
|
|||
158
kernel/core/qr.c
158
kernel/core/qr.c
|
|
@ -2,8 +2,8 @@
|
|||
//
|
||||
// Freestanding on purpose: no allocation, no floating point, no libc beyond
|
||||
// memset/memmove. It has to run in panic context, where the heap may be exactly
|
||||
// what is broken. Every buffer here is either on the caller's struct or a
|
||||
// bounded local.
|
||||
// what is broken. Every buffer here is on the caller's struct, a bounded
|
||||
// local, or static.
|
||||
//
|
||||
// The structure follows ISO/IEC 18004 in the order the standard describes it,
|
||||
// which is also the only order in which any of it can be checked:
|
||||
|
|
@ -40,42 +40,66 @@
|
|||
// every row below, because levels 1..6 at L happen to have equally sized
|
||||
// blocks. qr_encode() asserts exactly that at runtime, which is what turns a
|
||||
// mistyped number here into a visible failure instead of an unscannable symbol.
|
||||
static const struct {
|
||||
short total; // total codewords in the symbol
|
||||
unsigned char ecc; // EC codewords per block
|
||||
unsigned char blocks; // number of RS blocks
|
||||
} vinfo[QR_MAX_VERSION + 1] = {
|
||||
{ 0, 0, 0 }, // version 0 does not exist; keeps indexing natural
|
||||
{ 26, 7, 1 },
|
||||
{ 44, 10, 1 },
|
||||
{ 70, 15, 1 },
|
||||
{ 100, 20, 1 },
|
||||
{ 134, 26, 1 },
|
||||
{ 172, 18, 2 },
|
||||
// Error correction level L, every version. Two numbers per version are
|
||||
// transcribed from ISO/IEC 18004 table 9 -- EC codewords per block, and how
|
||||
// many blocks -- and everything else is computed: the symbol's total codeword
|
||||
// count from its geometry, and the block lengths from the total. Above
|
||||
// version 5 the blocks are not all one length; the first ones are a codeword
|
||||
// shorter, and interleaving has to step around the gap.
|
||||
static const unsigned char ecc_per_block[QR_MAX_VERSION + 1] = {
|
||||
0, 7, 10, 15, 20, 26, 18, 20, 24, 30, 18, 20, 24, 26, 30, 22, 24, 28, 30, 28,
|
||||
28, 28, 28, 30, 30, 26, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30,
|
||||
30,
|
||||
};
|
||||
static const unsigned char ecc_blocks[QR_MAX_VERSION + 1] = {
|
||||
0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7,
|
||||
8, 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24,
|
||||
25,
|
||||
};
|
||||
|
||||
// Modules left for data once every function pattern is drawn: the whole
|
||||
// square, less finders and separators (192), timing (2 * (n - 16)), format
|
||||
// (31), alignment patterns and, from version 7, the two version blocks (36).
|
||||
static int
|
||||
raw_modules(int v)
|
||||
{
|
||||
int r = (16 * v + 128) * v + 64;
|
||||
if (v >= 2) {
|
||||
int na = v / 7 + 2;
|
||||
r -= (25 * na - 10) * na - 55;
|
||||
if (v >= 7)
|
||||
r -= 36;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
static int
|
||||
total_codewords(int v)
|
||||
{
|
||||
return raw_modules(v) / 8;
|
||||
}
|
||||
|
||||
static int
|
||||
data_codewords(int v)
|
||||
{
|
||||
return total_codewords(v) - ecc_per_block[v] * ecc_blocks[v];
|
||||
}
|
||||
|
||||
// Byte mode's character count is 8 bits up to version 9 and 16 from 10.
|
||||
static int
|
||||
count_bits(int v)
|
||||
{
|
||||
return v <= 9 ? 8 : 16;
|
||||
}
|
||||
|
||||
int
|
||||
qr_capacity(int version)
|
||||
{
|
||||
if (version < 1 || version > QR_MAX_VERSION)
|
||||
return 0;
|
||||
// Data codewords, less the two bytes of framing: a 4-bit mode indicator, an
|
||||
// 8-bit character count (versions 1-9), and a 4-bit terminator come to
|
||||
// exactly two codewords' worth of overhead.
|
||||
int data = vinfo[version].total - vinfo[version].ecc * vinfo[version].blocks;
|
||||
return data - 2;
|
||||
return (data_codewords(version) * 8 - 4 - count_bits(version)) / 8;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// GF(256) and Reed-Solomon
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Multiply in GF(256) modulo x^8 + x^4 + x^3 + x^2 + 1 (0x11D).
|
||||
//
|
||||
// Done by shift-and-add rather than log/exp tables. Two 256-byte tables plus
|
||||
// their initialisation is more code than this loop, and this needs no init step
|
||||
// at all -- which matters when the caller is a panic handler that cannot assume
|
||||
// any particular earlier function ran.
|
||||
static unsigned char
|
||||
gmul(unsigned char x, unsigned char y)
|
||||
{
|
||||
|
|
@ -231,11 +255,37 @@ draw_function_patterns(struct qr *q)
|
|||
draw_finder(q, n - 4, 3);
|
||||
draw_finder(q, 3, n - 4);
|
||||
|
||||
// For versions 2-6 the alignment centres are {6, n-7}, and three of those
|
||||
// four combinations collide with a finder. Only the bottom-right survives,
|
||||
// which is why there is no position table here. Version 7 would need one.
|
||||
if (q->size > 21)
|
||||
draw_alignment(q, n - 7, n - 7);
|
||||
// Alignment patterns on a grid: the same list of centres on both axes,
|
||||
// every combination drawn except the three that would sit on a finder. The
|
||||
// list starts at 6, ends at n-7, and steps evenly (in even numbers) between,
|
||||
// except version 32, which the standard spaces by hand.
|
||||
int v = (n - 17) / 4;
|
||||
if (v >= 2) {
|
||||
int na = v / 7 + 2, pos[7];
|
||||
int step = v == 32 ? 26 : (v * 4 + na * 2 + 1) / (na * 2 - 2) * 2;
|
||||
pos[0] = 6;
|
||||
for (int i = na - 1, p = n - 7; i >= 1; i--, p -= step)
|
||||
pos[i] = p;
|
||||
for (int i = 0; i < na; i++)
|
||||
for (int j = 0; j < na; j++)
|
||||
if (!((i == 0 && j == 0) || (i == 0 && j == na - 1) ||
|
||||
(i == na - 1 && j == 0)))
|
||||
draw_alignment(q, pos[i], pos[j]);
|
||||
}
|
||||
|
||||
// From version 7 the version number is written out too, 6 bits and a
|
||||
// BCH(18,6) check, in a 3x6 block beside each of the two far finders.
|
||||
if (v >= 7) {
|
||||
int rem = v;
|
||||
for (int i = 0; i < 12; i++)
|
||||
rem = (rem << 1) ^ ((rem >> 11) * 0x1F25);
|
||||
long bits = ((long)v << 12) | rem;
|
||||
for (int i = 0; i < 18; i++) {
|
||||
int bit = (bits >> i) & 1, a = n - 11 + i % 3, b = i / 3;
|
||||
set_fn(q, a, b, bit);
|
||||
set_fn(q, b, a, bit);
|
||||
}
|
||||
}
|
||||
|
||||
draw_format(q, 0); // reserve; overwritten once a mask is chosen
|
||||
}
|
||||
|
|
@ -357,25 +407,24 @@ qr_encode(struct qr *q, const unsigned char *data, int len)
|
|||
if (v > QR_MAX_VERSION)
|
||||
return -1;
|
||||
|
||||
int total = vinfo[v].total;
|
||||
int ecclen = vinfo[v].ecc;
|
||||
int blocks = vinfo[v].blocks;
|
||||
int datacw = total - ecclen * blocks;
|
||||
|
||||
// The table claims equal-sized blocks for every version this file supports.
|
||||
// If that is ever untrue the interleave below is silently wrong, so check it
|
||||
// rather than trust it.
|
||||
if (datacw % blocks != 0)
|
||||
return -1;
|
||||
int blocklen = datacw / blocks;
|
||||
int blocks = ecc_blocks[v];
|
||||
int ecclen = ecc_per_block[v];
|
||||
int total = total_codewords(v);
|
||||
int datacw = data_codewords(v);
|
||||
int nshort = blocks - total % blocks; // blocks one data codeword short
|
||||
int shortlen = total / blocks - ecclen; // their data length
|
||||
|
||||
// --- 1. payload -> data codewords ---------------------------------------
|
||||
unsigned char cw[172]; // >= max total codewords
|
||||
//
|
||||
// Static, not on the stack: version 40 is 3,706 codewords, and this runs on
|
||||
// whatever stack a panic left. Not reentrant, which nothing here needs.
|
||||
static unsigned char cw[3706], out[3706];
|
||||
static unsigned char ec[25][30];
|
||||
memset(cw, 0, sizeof(cw));
|
||||
|
||||
int pos = 0;
|
||||
put_bits(cw, &pos, 0x4, 4); // byte mode
|
||||
put_bits(cw, &pos, (unsigned)len, 8);
|
||||
put_bits(cw, &pos, (unsigned)len, count_bits(v));
|
||||
for (int i = 0; i < len; i++)
|
||||
put_bits(cw, &pos, data[i], 8);
|
||||
|
||||
|
|
@ -393,20 +442,25 @@ qr_encode(struct qr *q, const unsigned char *data, int len)
|
|||
cw[i] = (i - pos / 8) % 2 == 0 ? 0xEC : 0x11;
|
||||
|
||||
// --- 2 and 3. error correction, then interleave -------------------------
|
||||
//
|
||||
// Block b starts at start(b): the short blocks first, then the long ones.
|
||||
unsigned char gen[30];
|
||||
unsigned char ec[4][30];
|
||||
rs_divisor(ecclen, gen);
|
||||
#define BSTART(b) ((b) * shortlen + ((b) > nshort ? (b) - nshort : 0))
|
||||
#define BLEN(b) (shortlen + ((b) >= nshort))
|
||||
for (int b = 0; b < blocks; b++)
|
||||
rs_remainder(&cw[b * blocklen], blocklen, gen, ecclen, ec[b]);
|
||||
rs_remainder(&cw[BSTART(b)], BLEN(b), gen, ecclen, ec[b]);
|
||||
|
||||
unsigned char out[172];
|
||||
int o = 0;
|
||||
for (int i = 0; i < blocklen; i++)
|
||||
for (int i = 0; i <= shortlen; i++)
|
||||
for (int b = 0; b < blocks; b++)
|
||||
out[o++] = cw[b * blocklen + i];
|
||||
if (i < BLEN(b))
|
||||
out[o++] = cw[BSTART(b) + i];
|
||||
for (int i = 0; i < ecclen; i++)
|
||||
for (int b = 0; b < blocks; b++)
|
||||
out[o++] = ec[b][i];
|
||||
#undef BSTART
|
||||
#undef BLEN
|
||||
|
||||
// --- 4. function patterns ------------------------------------------------
|
||||
memset(q, 0, sizeof(*q));
|
||||
|
|
|
|||
|
|
@ -117,10 +117,15 @@ def decode(m):
|
|||
return ""
|
||||
|
||||
|
||||
# Byte capacity per version: total - ecc*blocks - 2. Mirrors the table in qr.c;
|
||||
# the exact-fill cases below are built from it, and a disagreement would show up
|
||||
# Byte-mode capacity per version at level L, from ISO/IEC 18004 table 7 --
|
||||
# written out rather than computed, so it checks qr.c instead of agreeing with
|
||||
# it. The exact-fill cases below are built from it, and a disagreement shows up
|
||||
# as a version mismatch rather than passing quietly.
|
||||
CAPACITY = {1: 17, 2: 32, 3: 53, 4: 78, 5: 106, 6: 134}
|
||||
CAPACITY = dict(enumerate([
|
||||
17, 32, 53, 78, 106, 134, 154, 192, 230, 271, 321, 367, 425, 458, 520,
|
||||
586, 644, 718, 792, 858, 929, 1003, 1091, 1171, 1273, 1367, 1465, 1528,
|
||||
1628, 1732, 1840, 1952, 2068, 2188, 2303, 2431, 2563, 2699, 2809, 2953,
|
||||
], start=1))
|
||||
|
||||
alpha = lambda n: "".join(chr(65 + i % 26) for i in range(n))
|
||||
|
||||
|
|
@ -133,8 +138,9 @@ CASES = [
|
|||
bytes(range(1, 40)).decode("latin-1"), # non-printable payload
|
||||
]
|
||||
# One payload that exactly fills each version, and one just under, so both the
|
||||
# padded and unpadded paths are covered at every version -- including 6, which
|
||||
# is the only one with more than one Reed-Solomon block.
|
||||
# padded and unpadded paths are covered at every version: several Reed-Solomon
|
||||
# blocks from 6, blocks of two lengths from 6 too, the version information
|
||||
# from 7, the 16-bit character count from 10.
|
||||
for v, cap in CAPACITY.items():
|
||||
CASES.append(alpha(cap))
|
||||
CASES.append(alpha(cap - 1))
|
||||
|
|
|
|||
|
|
@ -155,25 +155,26 @@ check(int ok, const char *what)
|
|||
}
|
||||
|
||||
// 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.
|
||||
// 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 = vinfo[v].total;
|
||||
int data = total - vinfo[v].ecc * vinfo[v].blocks;
|
||||
int total = total_codewords(v);
|
||||
int data = data_codewords(v);
|
||||
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);
|
||||
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 table's codeword count disagrees
|
||||
// by building one and asking. If the formula's codeword count disagrees
|
||||
// with the geometry, one of the two is wrong.
|
||||
struct qr q;
|
||||
static struct qr q;
|
||||
memset(&q, 0, sizeof q);
|
||||
q.size = 17 + 4 * v;
|
||||
draw_function_patterns(&q);
|
||||
|
|
@ -219,7 +220,7 @@ static void
|
|||
check_encode(void)
|
||||
{
|
||||
printf("encode\n");
|
||||
struct qr q;
|
||||
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");
|
||||
|
|
@ -227,7 +228,9 @@ check_encode(void)
|
|||
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(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 --
|
||||
|
|
@ -270,7 +273,7 @@ main(int argc, char **argv)
|
|||
// 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;
|
||||
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;
|
||||
|
|
@ -283,7 +286,7 @@ main(int argc, char **argv)
|
|||
}
|
||||
|
||||
if (argc > 2 && strcmp(argv[1], "-m") == 0) {
|
||||
struct qr q;
|
||||
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;
|
||||
|
|
@ -308,7 +311,7 @@ main(int argc, char **argv)
|
|||
check_rs();
|
||||
check_encode();
|
||||
|
||||
struct qr q;
|
||||
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);
|
||||
|
|
|
|||
Loading…
Reference in a new issue