OverviewFilesCommitsIssuesDocsReleasesPeers

hyperswarm-peer commits / 5f3f980

test: pin what a decoder owes its caller on a TRUNCATED frame
src/compact_encoding.h is about to delegate its byte work to libcompact. No JS vector can describe the behaviour that matters for that swap, because it is about what happens when the wire is WRONG, so it has to be written down first or the refactor has nothing to be measured against. Three obligations, 24 assertions: 1. a decoder never reads past `end` 2. a truncated decode leaves start == end, so a `while (start < end)` loop over a malformed frame terminates instead of spinning 3. it yields 0 or null — never a half-read value the caller cannot tell from a real one ⛔ 23 hold today. ONE DOES NOT, and this commit is red on it: uint64_decode is two uint32 decodes, so a buffer with four bytes left returns the low word. Nothing depends on the value of a truncated decode — but "0 or null" is the contract worth having, and the swap is what will deliver it. 75/76. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
5f3f980ff7890512235f3c1e727a3f2010e640a5
by plankatron <fleeky@gmail.com> · 2026-09-10T18:53:23.000Z

Changed files (2) +110 -0

test/test_compact_encoding +0 -0
(no content changes — binary or rename)
test/test_compact_encoding.cpp +110 -0
……@@ -440,6 +440,114 @@ void test_ipv4_parse() {
440440 }
441441}
442442
443// ---- Truncated input: the contract a decoder owes its caller ----------------
444//
445// These are not wire-compatibility tests — no JS vector can describe them,
446// because they are about what happens when the wire is WRONG. They exist to
447// pin the contract down before src/compact_encoding.h delegates its byte work
448// to libcompact:
449//
450// 1. a decoder never reads past `end`
451// 2. a truncated decode leaves start == end, so a `while (start < end)` loop
452// over a malformed frame terminates instead of spinning
453// 3. it yields a zero / null, never a half-read value the caller cannot
454// distinguish from a real one
455//
456// (3) is the one the hand-written codec does not fully honour: uint64_decode is
457// two uint32 decodes, so four remaining bytes yield the low word rather than 0.
458// Nothing depends on the value of a truncated decode, and "0 or null" is the
459// contract worth having.
460
461static bool check_true(const char *name, bool ok) {
462 tests_run++;
463 if (!ok) { printf("FAIL %s\n", name); return false; }
464 tests_passed++;
465 printf("PASS %s\n", name);
466 return true;
467}
468
469static void test_truncated_decode() {
470 printf("--- Truncated input ---\n");
471 uint8_t buf[64];
472 memset(buf, 0, sizeof(buf));
473
474 {
475 EncodeState s = { 0, 0, buf };
476 check_true("uint_decode of nothing yields 0", cenc::uint_decode(s) == 0);
477 check_true(" and does not move past the end", s.start == s.end);
478 }
479 {
480 buf[0] = 0xfd; buf[1] = 0x01;
481 EncodeState s = { 0, 2, buf };
482 check_true("uint_decode of a truncated 0xfd yields 0", cenc::uint_decode(s) == 0);
483 check_true(" and clamps start to end", s.start == s.end);
484 }
485 {
486 buf[0] = 0xfe;
487 EncodeState s = { 0, 4, buf };
488 check_true("uint_decode of a truncated 0xfe yields 0", cenc::uint_decode(s) == 0);
489 check_true(" and clamps start to end", s.start == s.end);
490 }
491 {
492 buf[0] = 0xff;
493 EncodeState s = { 0, 5, buf };
494 check_true("uint_decode of a truncated 0xff yields 0", cenc::uint_decode(s) == 0);
495 check_true(" and clamps start to end", s.start == s.end);
496 }
497 {
498 EncodeState s = { 0, 1, buf };
499 check_true("uint16_decode with one byte left yields 0", cenc::uint16_decode(s) == 0);
500 check_true(" and clamps start to end", s.start == s.end);
501 }
502 {
503 EncodeState s = { 0, 3, buf };
504 check_true("uint32_decode with three bytes left yields 0", cenc::uint32_decode(s) == 0);
505 check_true(" and clamps start to end", s.start == s.end);
506 }
507 {
508 buf[0] = 1; buf[1] = 2; buf[2] = 3; buf[3] = 4;
509 EncodeState s = { 0, 4, buf };
510 const uint64_t v = cenc::uint64_decode(s);
511 check_true("uint64_decode with half a word left yields 0, not the half", v == 0);
512 check_true(" and clamps start to end", s.start == s.end);
513 memset(buf, 0, sizeof(buf));
514 }
515 {
516 EncodeState s = { 0, 31, buf };
517 check_true("fixed32_decode short yields null", cenc::fixed32_decode(s) == nullptr);
518 check_true(" and clamps start to end", s.start == s.end);
519 }
520 {
521 EncodeState s = { 0, 63, buf };
522 check_true("fixed64_decode short yields null", cenc::fixed64_decode(s) == nullptr);
523 check_true(" and clamps start to end", s.start == s.end);
524 }
525 {
526 buf[0] = 10; // says ten bytes follow; three do
527 EncodeState s = { 0, 4, buf };
528 BufView v = cenc::buffer_decode(s);
529 check_true("buffer_decode of a lying length yields nothing",
530 v.data == nullptr && v.len == 0);
531 check_true(" and clamps start to end", s.start == s.end);
532 }
533 {
534 buf[0] = 0;
535 EncodeState s = { 0, 1, buf };
536 BufView v = cenc::buffer_decode(s);
537 check_true("an empty buffer decodes as null, not as a zero-length view",
538 v.data == nullptr && v.len == 0);
539 }
540 {
541 buf[0] = 3; buf[1] = 0xaa; buf[2] = 0xbb; buf[3] = 0xcc;
542 EncodeState s = { 0, 4, buf };
543 BufView v = cenc::buffer_decode(s);
544 check_true("a good buffer decodes ZERO-COPY, into the input",
545 v.len == 3 && v.data == buf + 1);
546 check_true(" and consumes exactly its bytes", s.start == 4);
547 memset(buf, 0, sizeof(buf));
548 }
549}
550
443551int main() {
444552 if (sodium_init() < 0) {
445553 fprintf(stderr, "sodium_init failed\n");
……@@ -469,6 +577,8 @@ int main() {
469577 test_dht_request_encoding();
470578 printf("\n");
471579 test_dht_response_encoding();
580 printf("\n");
581 test_truncated_decode();
472582
473583 printf("\n=== Results: %d/%d passed ===\n", tests_passed, tests_run);
474584 return tests_passed == tests_run ? 0 : 1;