| … | … | @@ -440,6 +440,114 @@ void test_ipv4_parse() { |
| 440 | 440 | } |
| 441 | 441 | } |
| 442 | 442 | |
| 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 | |
| 461 | static 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 | |
| 469 | static 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 | |
| 443 | 551 | int main() { |
| 444 | 552 | if (sodium_init() < 0) { |
| 445 | 553 | fprintf(stderr, "sodium_init failed\n"); |
| … | … | @@ -469,6 +577,8 @@ int main() { |
| 469 | 577 | test_dht_request_encoding(); |
| 470 | 578 | printf("\n"); |
| 471 | 579 | test_dht_response_encoding(); |
| 580 | printf("\n"); |
| 581 | test_truncated_decode(); |
| 472 | 582 | |
| 473 | 583 | printf("\n=== Results: %d/%d passed ===\n", tests_passed, tests_run); |
| 474 | 584 | return tests_passed == tests_run ? 0 : 1; |