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
|
|
@ -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));
|
||||
|
|
|
|||
Loading…
Reference in a new issue