ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
sqlite_format.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file pc_sqlite_format.cpp
6 * @brief SQLite3 on-disk file-format parsers (see pc_sqlite_format.h).
7 */
8
10
11#if PC_ENABLE_SQLITE
12
13#include <string.h>
14
15namespace
16{
17uint16_t be16(const uint8_t *p)
18{
19 return (uint16_t)(((uint16_t)p[0] << 8) | p[1]);
20}
21uint32_t be32(const uint8_t *p)
22{
23 return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) | ((uint32_t)p[2] << 8) | (uint32_t)p[3];
24}
25// The 16-byte file magic, including the trailing NUL.
26const char SQLITE_MAGIC[16] = {'S', 'Q', 'L', 'i', 't', 'e', ' ', 'f', 'o', 'r', 'm', 'a', 't', ' ', '3', '\0'};
27
28bool is_pow2(uint32_t v)
29{
30 return v && (v & (v - 1)) == 0; // GCOVR_EXCL_BR_LINE the sole caller guards page_size>=512, so v==0 never reached
31}
32} // namespace
33
34size_t pc_sqlite_varint_decode(const uint8_t *buf, size_t len, uint64_t *out)
35{
36 uint64_t v = 0;
37 for (size_t i = 0; i < 8; i++)
38 {
39 if (i >= len)
40 {
41 return 0; // incomplete varint
42 }
43 v = (v << 7) | (uint8_t)(buf[i] & 0x7f);
44 if ((buf[i] & 0x80) == 0)
45 {
46 *out = v;
47 return i + 1;
48 }
49 }
50 if (len < 9)
51 {
52 return 0;
53 }
54 v = (v << 8) | buf[8]; // 9th byte carries all 8 bits
55 *out = v;
56 return 9;
57}
58
59uint64_t pc_sqlite_serial_type_size(uint64_t t)
60{
61 switch (t)
62 {
63 case 0:
64 return 0; // NULL
65 case 1:
66 return 1;
67 case 2:
68 return 2;
69 case 3:
70 return 3;
71 case 4:
72 return 4;
73 case 5:
74 return 6;
75 case 6:
76 return 8;
77 case 7:
78 return 8; // IEEE 754 float64
79 case 8:
80 return 0; // integer 0
81 case 9:
82 return 0; // integer 1
83 case 10:
84 case 11:
85 return 0; // reserved for internal use
86 default:
87 // >=12 even -> BLOB of (t-12)/2; >=13 odd -> TEXT of (t-13)/2. Floor division covers both.
88 return t >= 12 ? (t - 12) / 2 : 0; // GCOVR_EXCL_BR_LINE t<12 here means serial type 10/11, SQLite-reserved and
89 // absent from any valid record
90 }
91}
92
93bool pc_sqlite_parse_db_header(const uint8_t *buf, size_t len, SqliteDbHeader *out)
94{
95 if (len < 100 || memcmp(buf, SQLITE_MAGIC, 16) != 0)
96 {
97 return false;
98 }
99
100 uint16_t raw_ps = be16(buf + 16);
101 uint32_t page_size = (raw_ps == 1) ? 65536u : raw_ps;
102 // A valid page size is a power of two in [512, 65536].
103 // GCOVR_EXCL_BR_LINE below: page_size <= 65536 always here (raw_ps is uint16, with 1 mapping to 65536),
104 // so the > 65536 arm is a dead upper bound; the < 512 and is_pow2 arms are exercised.
105 if (page_size < 512 || page_size > 65536 || !is_pow2(page_size)) // GCOVR_EXCL_BR_LINE
106 {
107 return false;
108 }
109
110 out->page_size = page_size;
111 out->write_version = buf[18];
112 out->read_version = buf[19];
113 out->reserved_per_page = buf[20];
114 out->file_change_counter = be32(buf + 24);
115 out->page_count = be32(buf + 28);
116 out->freelist_first = be32(buf + 32);
117 out->freelist_count = be32(buf + 36);
118 out->schema_cookie = be32(buf + 40);
119 out->schema_format = be32(buf + 44);
120 out->text_encoding = be32(buf + 56);
121 out->user_version = be32(buf + 60);
122 out->application_id = be32(buf + 68);
123 out->pc_sqlite_version = be32(buf + 96);
124 return true;
125}
126
127bool pc_sqlite_parse_btree_header(const uint8_t *page, size_t page_len, size_t offset, SqliteBtreeHeader *out)
128{
129 if (offset + 8 > page_len)
130 {
131 return false;
132 }
133 const uint8_t *p = page + offset;
134 uint8_t type = p[0];
135 bool interior =
136 (type == SqliteBtree::SQLITE_BTREE_INTERIOR_INDEX || type == SqliteBtree::SQLITE_BTREE_INTERIOR_TABLE);
137 bool leaf = (type == SqliteBtree::SQLITE_BTREE_LEAF_INDEX || type == SqliteBtree::SQLITE_BTREE_LEAF_TABLE);
138 if (!interior && !leaf)
139 {
140 return false;
141 }
142 uint8_t hdr = interior ? 12 : 8;
143 if (offset + hdr > page_len)
144 {
145 return false;
146 }
147
148 out->type = type;
149 out->first_freeblock = be16(p + 1);
150 out->cell_count = be16(p + 3);
151 uint16_t ccs = be16(p + 5);
152 out->cell_content_start = (ccs == 0) ? 65536u : ccs;
153 out->frag_free_bytes = p[7];
154 out->right_most_page = interior ? be32(p + 8) : 0;
155 out->header_size = hdr;
156 return true;
157}
158
159uint32_t pc_sqlite_cell_pointer(const uint8_t *page, size_t page_len, const SqliteBtreeHeader *bh, size_t page_offset,
160 uint16_t i)
161{
162 if (i >= bh->cell_count)
163 {
164 return 0;
165 }
166 size_t at = page_offset + bh->header_size + (size_t)i * 2;
167 if (at + 2 > page_len)
168 {
169 return 0;
170 }
171 return be16(page + at); // cell pointers are offsets from the start of the page
172}
173
174bool pc_sqlite_parse_table_leaf_cell(const uint8_t *page, size_t page_len, uint32_t page_size, uint8_t reserved,
175 uint32_t cell_off, SqliteTableLeafCell *out)
176{
177 if (cell_off >= page_len)
178 {
179 return false;
180 }
181 uint64_t payload_len = 0;
182 uint64_t rowid = 0;
183 size_t n1 = pc_sqlite_varint_decode(page + cell_off, page_len - cell_off, &payload_len);
184 if (n1 == 0)
185 {
186 return false;
187 }
188 size_t n2 = pc_sqlite_varint_decode(page + cell_off + n1, page_len - cell_off - n1, &rowid);
189 if (n2 == 0)
190 {
191 return false;
192 }
193
194 // SQLite overflow threshold for a table b-tree leaf (fileformat2.html section 1.6).
195 uint32_t usable = page_size - reserved;
196 uint32_t max_local = usable - 35;
197 uint32_t min_local = ((usable - 12) * 32) / 255 - 23;
198 uint32_t local;
199 if (payload_len <= max_local)
200 {
201 local = (uint32_t)payload_len;
202 }
203 else
204 {
205 uint32_t k = min_local + (uint32_t)((payload_len - min_local) % (usable - 4));
206 local = (k <= max_local) ? k : min_local;
207 }
208
209 out->rowid = rowid;
210 out->payload_len = (uint32_t)payload_len;
211 out->local_off = cell_off + (uint32_t)(n1 + n2);
212 out->local_len = local;
213 out->has_overflow = local < payload_len;
214 if (out->local_off + local > page_len)
215 {
216 return false; // the claimed local payload does not fit the page
217 }
218 return true;
219}
220
221bool pc_sqlite_read_payload(SqlitePageReader read, void *ctx, uint32_t page_size, uint8_t reserved,
222 const uint8_t *leaf_page, const SqliteTableLeafCell *cell, uint8_t *out, uint32_t out_cap,
223 uint8_t *work_page)
224{
225 if (cell->payload_len > out_cap)
226 {
227 return false; // fail closed rather than overrun the caller buffer
228 }
229 if ((size_t)cell->local_off + cell->local_len > page_size)
230 {
231 return false;
232 }
233
234 memcpy(out, leaf_page + cell->local_off, cell->local_len);
235 uint32_t got = cell->local_len;
236 if (!cell->has_overflow)
237 {
238 return got == cell->payload_len; // wholly in-page: nothing to follow
239 }
240
241 // The 4-byte first-overflow-page number sits immediately after the local prefix.
242 size_t ptr_off = (size_t)cell->local_off + cell->local_len;
243 if (ptr_off + 4 > page_size)
244 {
245 return false;
246 }
247 uint32_t next = be32(leaf_page + ptr_off);
248
249 uint32_t usable = page_size - reserved;
250 uint32_t content = usable - 4; // per overflow page: 4-byte next pointer + content
251 if (content == 0)
252 {
253 return false;
254 }
255 // A chain cannot legitimately be longer than this; the bound turns a corrupt/looping
256 // next-pointer into a clean failure instead of an unbounded read.
257 uint32_t max_pages = cell->payload_len / content + 2;
258
259 for (uint32_t pages = 0; next != 0 && got < cell->payload_len; pages++)
260 {
261 // Unreachable belt-and-suspenders: `got` grows by `content` every iteration, so it reaches
262 // payload_len in fewer than max_pages steps - kept only as a guard against future logic changes.
263 if (pages >= max_pages) // GCOVR_EXCL_BR_LINE broken / looping chain (provably not hit; see above)
264 {
265 return false; // GCOVR_EXCL_LINE broken / looping chain (provably not hit; see above)
266 }
267 if (!read(ctx, next, work_page, page_size))
268 {
269 return false;
270 }
271 uint32_t nnext = be32(work_page);
272 uint32_t chunk = cell->payload_len - got;
273 if (chunk > content)
274 {
275 chunk = content;
276 }
277 // Also unreachable: got < payload_len <= out_cap and chunk <= payload_len - got, so got + chunk
278 // never exceeds out_cap - the entry check at the top already bounded the write.
279 if (got + chunk > out_cap) // GCOVR_EXCL_BR_LINE bounded by the entry check (see above)
280 {
281 return false; // GCOVR_EXCL_LINE
282 }
283 memcpy(out + got, work_page + 4, chunk);
284 got += chunk;
285 next = nnext;
286 }
287 return got == cell->payload_len;
288}
289
290bool pc_sqlite_record_begin(SqliteRecordCursor *c, const uint8_t *rec, uint32_t rec_len)
291{
292 uint64_t hdr_len = 0;
293 size_t n = pc_sqlite_varint_decode(rec, rec_len, &hdr_len);
294 if (n == 0 || hdr_len > rec_len || hdr_len < n)
295 {
296 return false;
297 }
298 c->rec = rec;
299 c->rec_len = rec_len;
300 c->hdr_pos = (uint32_t)n;
301 c->hdr_end = (uint32_t)hdr_len;
302 c->val_pos = (uint32_t)hdr_len;
303 return true;
304}
305
306bool pc_sqlite_record_next(SqliteRecordCursor *c, uint64_t *serial_type, const uint8_t **val, uint32_t *val_len)
307{
308 if (c->hdr_pos >= c->hdr_end)
309 {
310 return false;
311 }
312 uint64_t st = 0;
313 size_t n = pc_sqlite_varint_decode(c->rec + c->hdr_pos, c->hdr_end - c->hdr_pos, &st);
314 if (n == 0)
315 {
316 return false;
317 }
318 uint32_t sz = (uint32_t)pc_sqlite_serial_type_size(st);
319 if (c->val_pos + sz > c->rec_len)
320 {
321 return false; // value runs past the record (a truncated / overflowing row)
322 }
323 *serial_type = st;
324 *val = c->rec + c->val_pos;
325 *val_len = sz;
326 c->hdr_pos += (uint32_t)n;
327 c->val_pos += sz;
328 return true;
329}
330
331int64_t pc_sqlite_column_int(uint64_t serial_type, const uint8_t *val, uint32_t val_len)
332{
333 if (serial_type == 8)
334 {
335 return 0;
336 }
337 if (serial_type == 9)
338 {
339 return 1;
340 }
341 if (serial_type < 1 || serial_type > 6)
342 {
343 return 0;
344 }
345 size_t nbytes = (size_t)pc_sqlite_serial_type_size(serial_type);
346 if (val_len < nbytes)
347 {
348 return 0;
349 }
350 uint64_t u = 0;
351 for (size_t i = 0; i < nbytes; i++)
352 {
353 u = (u << 8) | val[i];
354 }
355 size_t bits = nbytes * 8;
356 if (bits < 64 && (u & (1ULL << (bits - 1))))
357 {
358 u |= ~((1ULL << bits) - 1); // sign-extend from the stored width
359 }
360 return (int64_t)u;
361}
362
363double pc_sqlite_column_float(const uint8_t *val, uint32_t val_len)
364{
365 if (val_len < 8)
366 {
367 return 0.0;
368 }
369 uint64_t u = 0;
370 for (int i = 0; i < 8; i++)
371 {
372 u = (u << 8) | val[i];
373 }
374 double d = 0.0;
375 memcpy(&d, &u, 8); // u holds the big-endian-read IEEE-754 bit pattern as a native u64
376 return d;
377}
378
379namespace
380{
381// The page byte offset of an interior page's b-tree header (100 only for page 1, the schema root).
382size_t page_hdr_off(uint32_t pgno)
383{
384 return pgno == 1 ? 100 : 0;
385}
386
387// Child page pointer to descend for child index `i` of an interior table page: the left-child of cell i,
388// or the right-most pointer when i == cell_count. 0 on a bad pointer.
389uint32_t interior_child(const uint8_t *page, size_t page_len, const SqliteBtreeHeader *h, size_t off, uint16_t i)
390{
391 if (i >= h->cell_count)
392 {
393 return h->right_most_page;
394 }
395 uint32_t cp = pc_sqlite_cell_pointer(page, page_len, h, off, i);
396 if (cp == 0 || (size_t)cp + 4 > page_len)
397 {
398 return 0;
399 }
400 return be32(page + cp); // an interior-table cell starts with the u32 left-child page number
401}
402
403// Descend leftmost from `pgno`, pushing interior frames, until a leaf table page is loaded into c->leaf.
404bool cursor_descend(SqliteTableCursor *c, uint32_t pgno)
405{
406 for (;;)
407 {
408 if (!c->read(c->ctx, pgno, c->work, c->page_size))
409 {
410 return false;
411 }
412 size_t off = page_hdr_off(pgno);
413 SqliteBtreeHeader h;
414 if (!pc_sqlite_parse_btree_header(c->work, c->page_size, off, &h))
415 {
416 return false;
417 }
418 if (h.type == SqliteBtree::SQLITE_BTREE_LEAF_TABLE)
419 {
420 memcpy(c->leaf, c->work, c->page_size);
421 c->leaf_hdr = h;
422 c->leaf_off = (uint32_t)off;
423 c->leaf_pgno = pgno;
424 c->leaf_count = h.cell_count;
425 c->leaf_cell = 0;
426 return true;
427 }
428 if (h.type != SqliteBtree::SQLITE_BTREE_INTERIOR_TABLE)
429 {
430 return false; // an index b-tree or garbage - not a table scan
431 }
432 if (c->depth >= SQLITE_BTREE_MAX_DEPTH)
433 {
434 return false;
435 }
436 uint32_t child = interior_child(c->work, c->page_size, &h, off, 0);
437 if (child == 0)
438 {
439 return false;
440 }
441 c->stack_pg[c->depth] = pgno;
442 c->stack_idx[c->depth] = 1; // child 0 taken; next is 1
443 c->depth++;
444 pgno = child;
445 }
446}
447} // namespace
448
449bool pc_sqlite_table_cursor_begin(SqliteTableCursor *c, SqlitePageReader read, void *ctx, uint32_t page_size,
450 uint8_t reserved, uint32_t rootpage, uint8_t *leaf_buf, uint8_t *work_buf)
451{
452 c->read = read;
453 c->ctx = ctx;
454 c->page_size = page_size;
455 c->reserved = reserved;
456 c->leaf = leaf_buf;
457 c->work = work_buf;
458 c->ovf_buf = nullptr;
459 c->ovf_cap = 0;
460 c->depth = 0;
461 c->leaf_count = 0;
462 c->leaf_cell = 0;
463 return cursor_descend(c, rootpage);
464}
465
466void pc_sqlite_table_cursor_set_overflow_buf(SqliteTableCursor *c, uint8_t *buf, uint32_t cap)
467{
468 c->ovf_buf = buf;
469 c->ovf_cap = cap;
470}
471
472// Begin reading a row record from one leaf cell: straight from the leaf page, or (for an overflowing
473// cell) reassembled into the caller's overflow buffer first. Extracted to keep the cursor loop flat.
474static bool pc_sqlite_cursor_begin_row(SqliteTableCursor *c, const SqliteTableLeafCell *cell, SqliteRecordCursor *row)
475{
476 if (!cell->has_overflow || !c->ovf_buf)
477 {
478 return pc_sqlite_record_begin(row, c->leaf + cell->local_off, cell->local_len);
479 }
480 // c->work is free here (we are at a leaf, not descending), so it serves as the scratch page.
481 if (!pc_sqlite_read_payload(c->read, c->ctx, c->page_size, c->reserved, c->leaf, cell, c->ovf_buf, c->ovf_cap,
482 c->work))
483 {
484 return false;
485 }
486 return pc_sqlite_record_begin(row, c->ovf_buf, cell->payload_len);
487}
488
489bool pc_sqlite_table_cursor_next(SqliteTableCursor *c, uint64_t *rowid, SqliteRecordCursor *row)
490{
491 for (;;)
492 {
493 if (c->leaf_cell < c->leaf_count)
494 {
495 uint32_t cp = pc_sqlite_cell_pointer(c->leaf, c->page_size, &c->leaf_hdr, c->leaf_off, c->leaf_cell);
496 c->leaf_cell++;
497 if (cp == 0)
498 {
499 continue;
500 }
501 SqliteTableLeafCell cell;
502 if (!pc_sqlite_parse_table_leaf_cell(c->leaf, c->page_size, c->page_size, c->reserved, cp, &cell))
503 {
504 continue;
505 }
506 if (!pc_sqlite_cursor_begin_row(c, &cell, row))
507 {
508 continue;
509 }
510 *rowid = cell.rowid;
511 return true;
512 }
513 // Current leaf is exhausted: advance the descent stack to the next subtree.
514 if (c->depth == 0)
515 {
516 return false; // whole table consumed
517 }
518 int top = c->depth - 1;
519 if (!c->read(c->ctx, c->stack_pg[top], c->work, c->page_size))
520 {
521 return false;
522 }
523 size_t off = page_hdr_off(c->stack_pg[top]);
524 SqliteBtreeHeader h;
525 if (!pc_sqlite_parse_btree_header(c->work, c->page_size, off, &h))
526 {
527 return false;
528 }
529 if (c->stack_idx[top] <= h.cell_count)
530 {
531 uint32_t child = interior_child(c->work, c->page_size, &h, off, c->stack_idx[top]);
532 c->stack_idx[top]++;
533 if (child == 0 || !cursor_descend(c, child))
534 {
535 return false;
536 }
537 }
538 else
539 {
540 c->depth--; // this interior frame's children are all visited; pop to its parent
541 }
542 }
543}
544
545// ---------------------------------------------------------------------------
546// Writer (bounded): build a fresh single-table database image.
547// ---------------------------------------------------------------------------
548
549namespace
550{
551void wr_be16(uint8_t *p, uint16_t v)
552{
553 p[0] = (uint8_t)(v >> 8);
554 p[1] = (uint8_t)v;
555}
556void wr_be32(uint8_t *p, uint32_t v)
557{
558 p[0] = (uint8_t)(v >> 24);
559 p[1] = (uint8_t)(v >> 16);
560 p[2] = (uint8_t)(v >> 8);
561 p[3] = (uint8_t)v;
562}
563
564// Number of bytes a varint of value v occupies (mirror of pc_sqlite_varint_encode's length).
565size_t varint_len(uint64_t v)
566{
567 if (v <= 0x7fULL)
568 {
569 return 1;
570 }
571 if (v <= 0x3fffULL)
572 {
573 return 2;
574 }
575 if (v <= 0x1fffffULL)
576 {
577 return 3;
578 }
579 if (v <= 0xfffffffULL)
580 {
581 return 4;
582 }
583 if (v <= 0x7ffffffffULL)
584 {
585 return 5;
586 }
587 if (v <= 0x3ffffffffffULL)
588 {
589 return 6;
590 }
591 if (v <= 0x1ffffffffffffULL)
592 {
593 return 7;
594 }
595 if (v <= 0xffffffffffffffULL)
596 {
597 return 8;
598 }
599 return 9;
600}
601
602// The serial type + value byte length for a column value. Integers get the minimal encoding.
603void value_serial(const SqliteValue *v, uint64_t *st, uint32_t *vlen)
604{
605 switch (v->type)
606 {
607 case SqliteColType::SQLITE_COL_NULL:
608 *st = 0;
609 *vlen = 0;
610 return;
611 case SqliteColType::SQLITE_COL_FLOAT:
612 *st = 7;
613 *vlen = 8;
614 return;
615 case SqliteColType::SQLITE_COL_TEXT:
616 *st = 13 + (uint64_t)2 * v->len;
617 *vlen = v->len;
618 return;
619 case SqliteColType::SQLITE_COL_BLOB:
620 *st = 12 + (uint64_t)2 * v->len;
621 *vlen = v->len;
622 return;
623 case SqliteColType::SQLITE_COL_INT:
624 default:
625 break;
626 }
627 int64_t x = v->i;
628 if (x == 0)
629 {
630 *st = 8;
631 *vlen = 0;
632 }
633 else if (x == 1)
634 {
635 *st = 9;
636 *vlen = 0;
637 }
638 else if (x >= -128 && x <= 127)
639 {
640 *st = 1;
641 *vlen = 1;
642 }
643 else if (x >= -32768 && x <= 32767)
644 {
645 *st = 2;
646 *vlen = 2;
647 }
648 else if (x >= -8388608 && x <= 8388607)
649 {
650 *st = 3;
651 *vlen = 3;
652 }
653 else if (x >= -2147483648LL && x <= 2147483647LL)
654 {
655 *st = 4;
656 *vlen = 4;
657 }
658 else if (x >= -140737488355328LL && x <= 140737488355327LL)
659 {
660 *st = 5;
661 *vlen = 6;
662 }
663 else
664 {
665 *st = 6;
666 *vlen = 8;
667 }
668}
669
670// Write a column's value bytes (big-endian for ints/floats) into out; returns the count.
671uint32_t write_value(const SqliteValue *v, uint64_t st, uint32_t vlen, uint8_t *out)
672{
673 if (v->type == SqliteColType::SQLITE_COL_TEXT || v->type == SqliteColType::SQLITE_COL_BLOB)
674 {
675 if (vlen)
676 {
677 memcpy(out, v->data, vlen);
678 }
679 return vlen;
680 }
681 if (v->type == SqliteColType::SQLITE_COL_FLOAT)
682 {
683 uint64_t u = 0;
684 memcpy(&u, &v->f, 8); // native bit pattern -> emit big-endian
685 for (int i = 7; i >= 0; i--)
686 {
687 *out++ = (uint8_t)(u >> (i * 8));
688 }
689 return 8;
690 }
691 if (st >= 1 && st <= 6) // signed big-endian integer of `vlen` bytes
692 {
693 uint64_t u = (uint64_t)v->i;
694 for (int i = (int)vlen - 1; i >= 0; i--)
695 {
696 out[(int)vlen - 1 - i] = (uint8_t)(u >> (i * 8));
697 }
698 return vlen;
699 }
700 return 0; // NULL / 0 / 1 constants carry no bytes
701}
702
703// Total encoded length of a record (header + values), or 0 on invalid input.
704uint32_t record_len(const SqliteValue *cols, uint32_t n)
705{
706 uint32_t st_len = 0;
707 uint32_t val_len = 0;
708 for (uint32_t c = 0; c < n; c++)
709 {
710 uint64_t st = 0;
711 uint32_t vl = 0;
712 value_serial(&cols[c], &st, &vl);
713 st_len += (uint32_t)varint_len(st);
714 val_len += vl;
715 }
716 // header_size counts its own length varint, which can grow the total - resolve the fixed point.
717 size_t hs_varlen = 1;
718 for (;;)
719 {
720 uint64_t hs = hs_varlen + st_len;
721 if (varint_len(hs) == hs_varlen)
722 {
723 break;
724 }
725 hs_varlen = varint_len(hs);
726 }
727 return (uint32_t)hs_varlen + st_len + val_len;
728}
729
730// Write a leaf-table page (b-tree header at hdr_off, then the rows as cells) into `page`. hdr_off is 100 for
731// page 1, else 0. Fails closed if a row would overflow the page or the cells + pointer array do not fit.
732bool write_leaf_page(uint8_t *page, uint32_t page_size, uint32_t hdr_off, const SqliteRow *rows, uint32_t nrows)
733{
734 const uint32_t usable = page_size; // reserved = 0 for images we build
735 const uint32_t max_local = usable - 35;
736
737 // Measure each cell up front so we can lay them out and fail closed before writing.
738 uint32_t total = 0;
739 for (uint32_t r = 0; r < nrows; r++)
740 {
741 uint32_t rl = record_len(rows[r].cols, rows[r].ncols);
742 // record_len() >= 2 whenever ncols != 0 (st_len >= ncols >= 1, plus the >=1 header varint), so
743 // rl == 0 implies ncols == 0; the guard is defense against future drift, not a reachable state.
744 if (rl == 0 && rows[r].ncols != 0) // GCOVR_EXCL_BR_LINE unreachable: rl==0 implies ncols==0 (see above)
745 {
746 return false; // GCOVR_EXCL_LINE unreachable (see above)
747 }
748 if (rl > max_local)
749 {
750 return false; // would need an overflow page - out of scope for the bounded writer
751 }
752 total += (uint32_t)varint_len(rl) + (uint32_t)varint_len(rows[r].rowid) + rl;
753 }
754 uint32_t header_end = hdr_off + 8 + 2 * nrows; // 8-byte leaf header + 2-byte cell pointer each
755 if ((uint64_t)header_end + total > page_size)
756 {
757 return false; // does not fit one leaf page
758 }
759
760 uint32_t content_start = page_size - total;
761
762 // b-tree leaf-table header.
763 uint8_t *h = page + hdr_off;
764 h[0] = (uint8_t)SqliteBtree::SQLITE_BTREE_LEAF_TABLE;
765 wr_be16(h + 1, 0); // first freeblock
766 wr_be16(h + 3, (uint16_t)nrows); // cell count
767 wr_be16(h + 5, (uint16_t)(content_start == 65536 ? 0 : content_start));
768 h[7] = 0; // fragmented free bytes
769
770 // Pack cells from the top of the content area downward; pointer array (rowid order) follows the header.
771 uint32_t off = page_size;
772 for (uint32_t r = 0; r < nrows; r++)
773 {
774 uint32_t rl = record_len(rows[r].cols, rows[r].ncols);
775 uint32_t cell_len = (uint32_t)varint_len(rl) + (uint32_t)varint_len(rows[r].rowid) + rl;
776 off -= cell_len;
777 uint8_t *cp = page + off;
778 size_t k = pc_sqlite_varint_encode(rl, cp, cell_len);
779 k += pc_sqlite_varint_encode(rows[r].rowid, cp + k, cell_len - k);
780 uint32_t w = pc_sqlite_encode_record(rows[r].cols, rows[r].ncols, cp + k, cell_len - (uint32_t)k);
781 // Unreachable: cp+k was given exactly `rl` bytes of capacity (cell_len - k, where cell_len was
782 // sized from this same `rl`). Inside pc_sqlite_encode_record(), the header-size varint, the
783 // per-column serial-type varints, and the per-column value bytes are each derived from the
784 // identical deterministic value_serial() calls (same cols/n, no mutation in between) that
785 // record_len() used to compute `rl`, so their lengths sum to exactly `rl` - the same budget the
786 // caller handed it. No internal capacity check can ever trip short for these args, so
787 // pos == rl == w always. Kept only as a guard against future logic drift between the two.
788 if (w != rl) // GCOVR_EXCL_BR_LINE internal invariant: capacity budget is exact (see above)
789 {
790 return false; // GCOVR_EXCL_LINE internal invariant: capacity budget is exact (see above)
791 }
792 wr_be16(page + hdr_off + 8 + 2 * r, (uint16_t)off); // cell pointer for row r
793 }
794 return true;
795}
796} // namespace
797
798size_t pc_sqlite_varint_encode(uint64_t v, uint8_t *out, size_t cap)
799{
800 size_t n = varint_len(v);
801 if (n > cap)
802 {
803 return 0;
804 }
805 if (n == 9)
806 {
807 // Bytes 0-7 carry bits 63..8 (7 bits each, high bit = continuation); byte 8 carries the low 8 bits.
808 for (int i = 0; i < 8; i++)
809 {
810 out[i] = (uint8_t)(0x80 | (uint8_t)((v >> (57 - 7 * i)) & 0x7f));
811 }
812 out[8] = (uint8_t)v;
813 return 9;
814 }
815 // The low 7 bits of each of the n bytes; high bit set on all but the last.
816 for (size_t i = 0; i < n; i++)
817 {
818 uint8_t bits = (uint8_t)((v >> (7 * (n - 1 - i))) & 0x7f);
819 out[i] = (i + 1 < n) ? (uint8_t)(bits | 0x80) : bits;
820 }
821 return n;
822}
823
824uint32_t pc_sqlite_encode_record(const SqliteValue *cols, uint32_t n, uint8_t *out, uint32_t out_cap)
825{
826 uint32_t total = record_len(cols, n);
827 // record_len() >= 2 whenever n != 0 (see pc_sqlite_encode_page), so total == 0 implies n == 0.
828 if (total == 0 && n != 0) // GCOVR_EXCL_BR_LINE unreachable: total==0 implies n==0 (see above)
829 {
830 return 0; // GCOVR_EXCL_LINE unreachable (see above)
831 }
832 if (total > out_cap)
833 {
834 return 0;
835 }
836
837 // Compute the header size (length varint + serial-type varints), same fixed point as record_len.
838 uint32_t st_len = 0;
839 for (uint32_t c = 0; c < n; c++)
840 {
841 uint64_t st = 0;
842 uint32_t vl = 0;
843 value_serial(&cols[c], &st, &vl);
844 st_len += (uint32_t)varint_len(st);
845 }
846 size_t hs_varlen = 1;
847 for (;;)
848 {
849 uint64_t hs = hs_varlen + st_len;
850 if (varint_len(hs) == hs_varlen)
851 {
852 break;
853 }
854 hs_varlen = varint_len(hs);
855 }
856 uint32_t header_size = (uint32_t)hs_varlen + st_len;
857
858 uint32_t pos = (uint32_t)pc_sqlite_varint_encode(header_size, out, out_cap);
859 // Serial-type varints (the header body).
860 for (uint32_t c = 0; c < n; c++)
861 {
862 uint64_t st = 0;
863 uint32_t vl = 0;
864 value_serial(&cols[c], &st, &vl);
865 pos += (uint32_t)pc_sqlite_varint_encode(st, out + pos, out_cap - pos);
866 }
867 // Value bytes, in column order.
868 for (uint32_t c = 0; c < n; c++)
869 {
870 uint64_t st = 0;
871 uint32_t vl = 0;
872 value_serial(&cols[c], &st, &vl);
873 pos += write_value(&cols[c], st, vl, out + pos);
874 }
875 return pos;
876}
877
878uint32_t pc_sqlite_build_table_db(uint32_t page_size, const char *table_name, const char *create_sql,
879 const SqliteRow *rows, uint32_t nrows, uint8_t *out, uint32_t out_cap)
880{
881 if (page_size < 512 || page_size > 65536 || !is_pow2(page_size))
882 {
883 return 0;
884 }
885 if ((uint64_t)page_size * 2 > out_cap || !table_name || !create_sql)
886 {
887 return 0;
888 }
889
890 memset(out, 0, (size_t)page_size * 2);
891
892 // --- Page 1: the 100-byte database header ---
893 memcpy(out, SQLITE_MAGIC, 16);
894 wr_be16(out + 16, (uint16_t)(page_size == 65536 ? 1 : page_size));
895 out[18] = 1; // write version (legacy)
896 out[19] = 1; // read version (legacy)
897 out[20] = 0; // reserved bytes per page
898 out[21] = 64; // max embedded payload fraction
899 out[22] = 32; // min embedded payload fraction
900 out[23] = 32; // leaf payload fraction
901 wr_be32(out + 24, 1); // file change counter
902 wr_be32(out + 28, 2); // page count
903 wr_be32(out + 40, 1); // schema cookie
904 wr_be32(out + 44, 4); // schema format number
905 wr_be32(out + 56, 1); // text encoding = UTF-8
906 wr_be32(out + 92, 1); // version-valid-for (== file change counter)
907 wr_be32(out + 96, 3046001); // SQLITE_VERSION_NUMBER that wrote the file
908
909 // --- Page 1: the pc_sqlite_schema row for our table (type,name,tbl_name,rootpage,sql) ---
910 uint32_t name_len = (uint32_t)strnlen(table_name, out_cap);
911 uint32_t sql_len = (uint32_t)strnlen(create_sql, out_cap);
912 SqliteValue master[5];
913 master[0] = {SqliteColType::SQLITE_COL_TEXT, 0, 0, (const uint8_t *)"table", 5};
914 master[1] = {SqliteColType::SQLITE_COL_TEXT, 0, 0, (const uint8_t *)table_name, name_len};
915 master[2] = {SqliteColType::SQLITE_COL_TEXT, 0, 0, (const uint8_t *)table_name, name_len};
916 master[3] = {SqliteColType::SQLITE_COL_INT, 2, 0, nullptr, 0}; // rootpage = 2
917 master[4] = {SqliteColType::SQLITE_COL_TEXT, 0, 0, (const uint8_t *)create_sql, sql_len};
918 SqliteRow master_row = {1, master, 5};
919 if (!write_leaf_page(out, page_size, 100, &master_row, 1))
920 {
921 return 0;
922 }
923
924 // --- Page 2: the table's leaf b-tree with the caller's rows ---
925 if (!write_leaf_page(out + page_size, page_size, 0, rows, nrows))
926 {
927 return 0;
928 }
929
930 return page_size * 2;
931}
932
933#endif // PC_ENABLE_SQLITE