// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// Adversarial / pentest harness (env: native_pentest) - run SEPARATELY from the
// per-commit unit tests. It throws malformed, oversized, partial (slowloris-style),
// binary, and deterministically-random input at the untrusted-input parsers and
// asserts the invariants that keep the device safe:
//   * fixed footprint - every fixed buffer index stays within its bound (no
//     overflow), whatever the input;
//   * fail-closed - bad input lands in a defined error/too-large/too-long state,
//     never an out-of-range one;
//   * liveness - the parser always terminates (no hang / no over-read).
// A crash, hang, or sanitizer trip here is a finding. See docs/PENTEST.md.
//
// Targets (every codec that consumes untrusted bytes from the wire or the user):
//   HTTP request parser, multipart/form-data, CoAP datagram, SNMP agent + BER,
//   Modbus slave ADU + master response, CBOR reader, MessagePack reader, JSON
//   reader, base64, base32.
// Build it with -fsanitize=address,undefined for the strongest assurance.

#include "network_drivers/presentation/base64/base64.h"
#include "network_drivers/presentation/cbor/cbor.h"
#include "network_drivers/presentation/http_parser/http_parser.h"
#include "network_drivers/presentation/json/json.h"
#include "network_drivers/presentation/msgpack/msgpack.h"
#include "network_drivers/presentation/multipart/multipart.h"
#include "services/coap/coap.h"
#include "services/modbus/modbus.h"
#include "services/modbus/modbus_master.h"
#include "services/snmp/snmp_agent.h"
#include "services/snmp/snmp_ber.h"
#include "services/totp/totp.h"
#include <string.h>
#include <unity.h>

// Deterministic PRNG (xorshift32) so any finding reproduces from the seed.
static uint32_t g_rng = 0x1234567u;
static uint32_t xs32()
{
    g_rng ^= g_rng << 13;
    g_rng ^= g_rng >> 17;
    g_rng ^= g_rng << 5;
    return g_rng;
}

void setUp()
{
    g_rng = 0x1234567u;
}
void tearDown()
{
}

// ---------------------------------------------------------------------------
// HTTP request parser - the primary attack surface
// ---------------------------------------------------------------------------

static HttpReq g_req;

// Every fixed buffer index must stay within its compile-time bound, and the state
// must be a defined enum value - regardless of the input.
static void assert_http_bounded(const HttpReq *r)
{
    TEST_ASSERT_TRUE(r->path_idx <= MAX_PATH_LEN);
    TEST_ASSERT_TRUE(r->query_idx <= MAX_QUERY_LEN);
    TEST_ASSERT_TRUE(r->header_count <= MAX_HEADERS);
    TEST_ASSERT_TRUE(r->body_len <= BODY_BUF_SIZE);
    TEST_ASSERT_TRUE((int)r->parse_state >= 0 && (int)r->parse_state <= (int)PARSE_URI_TOO_LONG);
}

static void feed(HttpReq *r, const uint8_t *data, size_t n)
{
    http_parser_reset(r);
    for (size_t i = 0; i < n; i++)
        http_parser_feed(r, data[i]);
}

static void feed_str(HttpReq *r, const char *s)
{
    feed(r, (const uint8_t *)s, strlen(s));
}

void test_http_oversized_path()
{
    static uint8_t buf[MAX_PATH_LEN * 4 + 64];
    size_t n = 0;
    const char *pre = "GET /";
    memcpy(buf, pre, strlen(pre));
    n = strlen(pre);
    for (size_t i = 0; i < MAX_PATH_LEN * 3; i++)
        buf[n++] = 'a';
    const char *post = " HTTP/1.1\r\nHost: x\r\n\r\n";
    memcpy(buf + n, post, strlen(post));
    n += strlen(post);
    feed(&g_req, buf, n);
    assert_http_bounded(&g_req);
    TEST_ASSERT_EQUAL_INT(PARSE_URI_TOO_LONG, g_req.parse_state); // fails closed -> 414
}

void test_http_content_length_too_large()
{
    const char *req = "POST / HTTP/1.1\r\nHost: x\r\nContent-Length: 99999999\r\n\r\n";
    feed(&g_req, (const uint8_t *)req, strlen(req));
    assert_http_bounded(&g_req);
    TEST_ASSERT_EQUAL_INT(PARSE_ENTITY_TOO_LARGE, g_req.parse_state); // -> 413
}

void test_http_header_flood()
{
    static uint8_t buf[8192];
    size_t n = 0;
    const char *line = "GET / HTTP/1.1\r\n";
    memcpy(buf, line, strlen(line));
    n = strlen(line);
    for (int i = 0; i < 200; i++) // far more than MAX_HEADERS
    {
        const char *h = "X-Pad: 1\r\n";
        memcpy(buf + n, h, strlen(h));
        n += strlen(h);
    }
    buf[n++] = '\r';
    buf[n++] = '\n';
    feed(&g_req, buf, n);
    assert_http_bounded(&g_req); // header_count clamped to MAX_HEADERS, no overflow
}

void test_http_malformed_lines()
{
    const char *vectors[] = {
        "",
        "\r\n\r\n",
        "GET",
        "GET ",
        "GET /\r\n\r\n",          // no HTTP version
        "GET / HTTP/9.9\r\n\r\n", // bad version
        "\x00\x01\x02\x03 / HTTP/1.1\r\n\r\n",
        "GET / HTTP/1.1\nHost:x\n\n",                   // bare LF
        "GET /\xff\xfe HTTP/1.1\r\n\r\n",               // high bytes in path
        "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n",             // HTTP/2 preface (confusion)
        "GET / HTTP/1.1\r\nContent-Length: abc\r\n\r\n" // non-numeric CL
    };
    for (size_t i = 0; i < sizeof(vectors) / sizeof(vectors[0]); i++)
    {
        feed(&g_req, (const uint8_t *)vectors[i], strlen(vectors[i]));
        assert_http_bounded(&g_req); // never crashes / overflows; lands in a defined state
    }
}

// Request-smuggling / desync vectors: ambiguous framing must never let the parser
// land out of bounds. (Whether each is rejected is policy; bounded + defined is
// the safety invariant.)
void test_http_smuggling_vectors()
{
    const char *vectors[] = {
        // Duplicate Content-Length.
        "POST / HTTP/1.1\r\nHost: x\r\nContent-Length: 5\r\nContent-Length: 6\r\n\r\nHELLO",
        // Content-Length + Transfer-Encoding (CL.TE).
        "POST / HTTP/1.1\r\nHost: x\r\nContent-Length: 4\r\nTransfer-Encoding: chunked\r\n\r\n0\r\n\r\n",
        // Transfer-Encoding + Content-Length (TE.CL).
        "POST / HTTP/1.1\r\nHost: x\r\nTransfer-Encoding: chunked\r\nContent-Length: 4\r\n\r\n5\r\nHELLO\r\n0\r\n\r\n",
        // Obfuscated Transfer-Encoding header.
        "POST / HTTP/1.1\r\nHost: x\r\nTransfer-Encoding:\tchunked\r\n\r\n0\r\n\r\n",
        "POST / HTTP/1.1\r\nHost: x\r\nTransfer-Encoding : chunked\r\n\r\n0\r\n\r\n",
        "POST / HTTP/1.1\r\nHost: x\r\nTransfer-Encoding: chunked, identity\r\n\r\n0\r\n\r\n",
        // Negative / hex / signed / overflowing Content-Length.
        "POST / HTTP/1.1\r\nHost: x\r\nContent-Length: -1\r\n\r\n",
        "POST / HTTP/1.1\r\nHost: x\r\nContent-Length: 0x10\r\n\r\n",
        "POST / HTTP/1.1\r\nHost: x\r\nContent-Length: +5\r\n\r\nHELLO",
        "POST / HTTP/1.1\r\nHost: x\r\nContent-Length: 18446744073709551616\r\n\r\n",
        // Chunked with a giant / non-hex / negative chunk size.
        "POST / HTTP/1.1\r\nHost: x\r\nTransfer-Encoding: chunked\r\n\r\nffffffff\r\nA",
        "POST / HTTP/1.1\r\nHost: x\r\nTransfer-Encoding: chunked\r\n\r\n-1\r\nA",
        "POST / HTTP/1.1\r\nHost: x\r\nTransfer-Encoding: chunked\r\n\r\nzz\r\nA",
    };
    for (size_t i = 0; i < sizeof(vectors) / sizeof(vectors[0]); i++)
    {
        feed_str(&g_req, vectors[i]);
        assert_http_bounded(&g_req);
    }
}

// Header / request-line injection: control bytes and exotic request-target forms
// must stay bounded and defined.
void test_http_header_injection()
{
    const char *vectors[] = {
        "GET /\r\nX-Injected: 1 HTTP/1.1\r\n\r\n",           // CRLF in the request target
        "GET / HTTP/1.1\r\nX-Foo: a\rb\r\n\r\n",             // bare CR in a header value
        "GET / HTTP/1.1\r\nX-Foo: a\nb\r\n\r\n",             // bare LF in a header value
        "GET / HTTP/1.1\r\nX-Foo: a\x00 b\r\n\r\n",          // embedded NUL
        "GET /%2e%2e/%2e%2e/etc/passwd HTTP/1.1\r\n\r\n",    // percent-encoded traversal
        "GET /%00 HTTP/1.1\r\n\r\n",                         // percent-encoded NUL
        "GET http://evil/ HTTP/1.1\r\nHost: x\r\n\r\n",      // absolute-form target
        "GET //evil/path HTTP/1.1\r\nHost: x\r\n\r\n",       // network-path reference
        "CONNECT evil:443 HTTP/1.1\r\n\r\n",                 // CONNECT authority-form
        "GET / HTTP/1.1\r\n: novalue\r\n\r\n",               // empty header name
        "GET / HTTP/1.1\r\nNo-Colon-Header\r\n\r\n",         // header without colon
        "GET / HTTP/1.1\r\nX-Foo:" /* no CRLF, truncated */, // truncated header
    };
    for (size_t i = 0; i < sizeof(vectors) / sizeof(vectors[0]); i++)
    {
        feed_str(&g_req, vectors[i]);
        assert_http_bounded(&g_req);
    }
}

void test_http_binary_and_tls()
{
    // A TLS ClientHello record header fed to the cleartext HTTP parser.
    const uint8_t tls[] = {0x16, 0x03, 0x01, 0x02, 0x00, 0x01, 0x00, 0x01, 0xfc, 0x03, 0x03};
    feed(&g_req, tls, sizeof(tls));
    assert_http_bounded(&g_req);
}

void test_http_partial_slowloris()
{
    // A long request fed nothing-but-headers, never terminated: must stay bounded
    // and in progress (the idle-timeout sweep reaps it in the live server).
    http_parser_reset(&g_req);
    const char *line = "GET / HTTP/1.1\r\n";
    for (const char *p = line; *p; p++)
        http_parser_feed(&g_req, (uint8_t)*p);
    for (int i = 0; i < 5000; i++)
    {
        http_parser_feed(&g_req, 'X'); // an endless header name, byte by byte
        if ((i & 0x3F) == 0)
            assert_http_bounded(&g_req);
    }
    assert_http_bounded(&g_req);
}

void test_http_random_fuzz()
{
    // Many deterministically-random streams of random length: after each, the
    // parser must be bounded and in a defined state (no crash, no overflow).
    for (int iter = 0; iter < 20000; iter++)
    {
        uint8_t buf[300];
        size_t n = xs32() % sizeof(buf);
        for (size_t i = 0; i < n; i++)
            buf[i] = (uint8_t)(xs32() & 0xFF);
        feed(&g_req, buf, n);
        assert_http_bounded(&g_req);
    }
}

// Structured fuzz: a well-formed request skeleton with random bytes spliced into
// the path, a header value, and the body - finds bugs the pure-random stream
// rarely reaches because it stays in the parser's deeper states.
void test_http_structured_fuzz()
{
    for (int iter = 0; iter < 20000; iter++)
    {
        char buf[600];
        size_t n = 0;
        const char *m = "POST /";
        memcpy(buf + n, m, strlen(m));
        n += strlen(m);
        size_t plen = xs32() % 40;
        for (size_t i = 0; i < plen; i++)
            buf[n++] = (char)(xs32() & 0xFF);
        const char *mid = " HTTP/1.1\r\nHost: x\r\nX-Fuzz: ";
        memcpy(buf + n, mid, strlen(mid));
        n += strlen(mid);
        size_t hlen = xs32() % 40;
        for (size_t i = 0; i < hlen; i++)
            buf[n++] = (char)(xs32() & 0xFF);
        const char *clh = "\r\nContent-Length: ";
        memcpy(buf + n, clh, strlen(clh));
        n += strlen(clh);
        size_t blen = xs32() % 64;
        n += (size_t)snprintf(buf + n, sizeof(buf) - n, "%u\r\n\r\n", (unsigned)blen);
        for (size_t i = 0; i < blen && n < sizeof(buf); i++)
            buf[n++] = (char)(xs32() & 0xFF);
        feed(&g_req, (const uint8_t *)buf, n);
        assert_http_bounded(&g_req);
    }
}

// ---------------------------------------------------------------------------
// multipart/form-data - parsed in-place over HttpReq::body
// ---------------------------------------------------------------------------

// Build a complete multipart POST with a random body, feed it through the HTTP
// parser, then parse it: part_count must stay within MAX_MULTIPART_PARTS and every
// part must point inside the body buffer.
static void assert_multipart_bounded(const HttpReq *r, const Multipart *mp)
{
    TEST_ASSERT_TRUE(mp->part_count >= 0 && mp->part_count <= MAX_MULTIPART_PARTS);
    const char *base = (const char *)r->body;
    for (int i = 0; i < mp->part_count; i++)
    {
        if (mp->parts[i].data)
        {
            TEST_ASSERT_TRUE(mp->parts[i].data >= base);
            TEST_ASSERT_TRUE(mp->parts[i].data <= base + r->body_len);
            TEST_ASSERT_TRUE(mp->parts[i].data_len <= r->body_len);
        }
    }
}

void test_multipart_random()
{
    for (int iter = 0; iter < 10000; iter++)
    {
        char buf[800];
        size_t n = 0;
        const char *head = "POST /u HTTP/1.1\r\nHost: x\r\n"
                           "Content-Type: multipart/form-data; boundary=BND\r\nContent-Length: ";
        memcpy(buf + n, head, strlen(head));
        n += strlen(head);
        // Random body, occasionally seeded with real delimiters so the parser
        // walks deeper into the part-splitting logic.
        char body[400];
        size_t blen = xs32() % sizeof(body);
        for (size_t i = 0; i < blen; i++)
        {
            uint32_t r = xs32();
            if ((r & 0x1F) == 0 && i + 6 < blen)
            {
                memcpy(body + i, "\r\n--BND", 6);
                i += 5;
            }
            else
                body[i] = (char)(r & 0xFF);
        }
        n += (size_t)snprintf(buf + n, sizeof(buf) - n, "%u\r\n\r\n", (unsigned)blen);
        size_t copy = blen;
        if (n + copy > sizeof(buf))
            copy = sizeof(buf) - n;
        memcpy(buf + n, body, copy);
        n += copy;

        feed(&g_req, (const uint8_t *)buf, n);
        assert_http_bounded(&g_req);
        Multipart mp;
        multipart_parse(&g_req, &mp); // bool result irrelevant; invariants are
        assert_multipart_bounded(&g_req, &mp);
    }
}

// ---------------------------------------------------------------------------
// CoAP datagram processor (RFC 7252) - untrusted UDP input
// ---------------------------------------------------------------------------

static void coap_noop(const CoapRequest *, CoapResponse *)
{
}

void test_coap_random_datagram()
{
    coap_server_init();
    coap_server_add_resource("/r", COAP_ALLOW_GET | COAP_ALLOW_POST | COAP_ALLOW_PUT, coap_noop);
    for (int iter = 0; iter < 12000; iter++)
    {
        uint8_t req[320];
        size_t n = xs32() % sizeof(req);
        for (size_t i = 0; i < n; i++)
            req[i] = (uint8_t)(xs32() & 0xFF);
        uint8_t resp[256];
        size_t rn = coap_server_process(req, n, resp, sizeof(resp));
        TEST_ASSERT_TRUE(rn <= sizeof(resp)); // never writes past the response buffer
    }
}

// Structured CoAP: a valid 4-byte header + random options/payload, so the option
// parser (delta/length nibbles, extended encodings) is exercised in depth.
void test_coap_structured_fuzz()
{
    coap_server_init();
    coap_server_add_resource("/r", COAP_ALLOW_GET, coap_noop);
    for (int iter = 0; iter < 12000; iter++)
    {
        uint8_t req[320];
        size_t n = 0;
        uint8_t tkl = xs32() % 9;            // token length nibble (8 is illegal -> must fail closed)
        req[n++] = (uint8_t)(0x40 | tkl);    // ver=1, type=CON, TKL
        req[n++] = (uint8_t)(xs32() & 0x7F); // code
        req[n++] = (uint8_t)(xs32() & 0xFF); // message id hi
        req[n++] = (uint8_t)(xs32() & 0xFF); // message id lo
        size_t rest = xs32() % (sizeof(req) - 4);
        for (size_t i = 0; i < rest; i++)
            req[n++] = (uint8_t)(xs32() & 0xFF);
        uint8_t resp[256];
        size_t rn = coap_server_process(req, n, resp, sizeof(resp));
        TEST_ASSERT_TRUE(rn <= sizeof(resp));
    }
}

// ---------------------------------------------------------------------------
// SNMP agent + ASN.1/BER codec - untrusted UDP input (classic fuzz target)
// ---------------------------------------------------------------------------

void test_snmp_random_packet()
{
    snmp_agent_init("public");
    snmp_agent_set_rw_community("private");
    for (int iter = 0; iter < 12000; iter++)
    {
        uint8_t req[320];
        size_t n = xs32() % sizeof(req);
        for (size_t i = 0; i < n; i++)
            req[i] = (uint8_t)(xs32() & 0xFF);
        uint8_t resp[512];
        size_t rn = snmp_agent_process(req, n, resp, sizeof(resp));
        TEST_ASSERT_TRUE(rn <= sizeof(resp)); // never writes past the response buffer
    }
}

// Structured SNMP: a valid SEQUENCE wrapper around random contents, so the BER
// length/tag walker is driven past the outer frame.
void test_snmp_structured_fuzz()
{
    snmp_agent_init("public");
    for (int iter = 0; iter < 12000; iter++)
    {
        uint8_t req[260];
        size_t inner = xs32() % 200;
        size_t n = 0;
        req[n++] = 0x30; // SEQUENCE
        req[n++] = (uint8_t)inner;
        for (size_t i = 0; i < inner; i++)
            req[n++] = (uint8_t)(xs32() & 0xFF);
        uint8_t resp[512];
        size_t rn = snmp_agent_process(req, n, resp, sizeof(resp));
        TEST_ASSERT_TRUE(rn <= sizeof(resp));
    }
}

void test_ber_decoder_random()
{
    for (int iter = 0; iter < 20000; iter++)
    {
        uint8_t buf[80];
        size_t n = xs32() % sizeof(buf);
        for (size_t i = 0; i < n; i++)
            buf[i] = (uint8_t)(xs32() & 0xFF);
        BerDec d;
        ber_dec_init(&d, buf, n);
        // Walk the buffer with a mix of readers; the cursor must never pass len.
        for (int step = 0; step < 16 && d.ok; step++)
        {
            TEST_ASSERT_TRUE(d.pos <= d.len);
            uint8_t tag = 0;
            size_t len = 0;
            if (!ber_read_header(&d, &tag, &len))
                break;
            TEST_ASSERT_TRUE(d.pos <= d.len);
            if (tag == 0x02) // INTEGER
            {
                long v = 0;
                ber_dec_init(&d, buf, n); // re-read from the top as an integer
                long dummy = 0;
                (void)dummy;
                (void)v;
                ber_read_integer(&d, &v);
                TEST_ASSERT_TRUE(d.pos <= d.len);
                break;
            }
            else if (tag == 0x06) // OID
            {
                uint32_t arcs[32];
                size_t cnt = 0;
                ber_read_oid(&d, arcs, 32, &cnt);
                TEST_ASSERT_TRUE(cnt <= 32);
                TEST_ASSERT_TRUE(d.pos <= d.len);
                break;
            }
            else
            {
                ber_skip(&d, len);
                TEST_ASSERT_TRUE(d.pos <= d.len);
            }
        }
        TEST_ASSERT_TRUE(d.pos <= d.len);
    }
}

// ---------------------------------------------------------------------------
// Modbus - slave ADU codec + master response parser
// ---------------------------------------------------------------------------

void test_modbus_random_adu()
{
    modbus_server_init();
    for (int iter = 0; iter < 20000; iter++)
    {
        uint8_t req[280];
        size_t n = xs32() % sizeof(req);
        for (size_t i = 0; i < n; i++)
            req[i] = (uint8_t)(xs32() & 0xFF);
        uint8_t resp[MODBUS_ADU_MAX];
        size_t rn = modbus_process_adu(req, n, resp, sizeof(resp));
        TEST_ASSERT_TRUE(rn <= sizeof(resp)); // never writes past the response buffer
    }
}

void test_modbus_master_response_random()
{
    for (int iter = 0; iter < 20000; iter++)
    {
        uint8_t adu[280];
        size_t n = xs32() % sizeof(adu);
        for (size_t i = 0; i < n; i++)
            adu[i] = (uint8_t)(xs32() & 0xFF);
        uint16_t regs[64];
        uint8_t exception = 0;
        int got = modbus_parse_response(adu, n, regs, 64, &exception);
        TEST_ASSERT_TRUE(got <= 64); // never reports more registers than fit
    }
}

// ---------------------------------------------------------------------------
// CBOR reader - untrusted binary (RFC 8949)
// ---------------------------------------------------------------------------

void test_cbor_reader_random()
{
    for (int iter = 0; iter < 20000; iter++)
    {
        uint8_t buf[80];
        size_t n = xs32() % sizeof(buf);
        for (size_t i = 0; i < n; i++)
            buf[i] = (uint8_t)(xs32() & 0xFF);
        CborReader r;
        cbor_reader_init(&r, buf, n);
        // Try each typed reader against the head item; whatever happens, the
        // cursor stays within bounds and the error flag is sticky/defined.
        for (int step = 0; step < 12 && !r.err; step++)
        {
            TEST_ASSERT_TRUE(r.pos <= r.len);
            uint64_t u = 0;
            int64_t s = 0;
            bool b = false;
            float f = 0;
            const char *t = nullptr;
            const uint8_t *by = nullptr;
            size_t l = 0, cnt = 0;
            switch (xs32() % 8)
            {
            case 0:
                cbor_read_uint(&r, &u);
                break;
            case 1:
                cbor_read_int(&r, &s);
                break;
            case 2:
                cbor_read_bool(&r, &b);
                break;
            case 3:
                cbor_read_null(&r);
                break;
            case 4:
                cbor_read_float(&r, &f);
                break;
            case 5:
                if (cbor_read_text(&r, &t, &l))
                    TEST_ASSERT_TRUE(l <= r.len);
                break;
            case 6:
                if (cbor_read_bytes(&r, &by, &l))
                    TEST_ASSERT_TRUE(l <= r.len);
                break;
            default:
                cbor_read_array(&r, &cnt);
                break;
            }
            TEST_ASSERT_TRUE(r.pos <= r.len);
        }
        TEST_ASSERT_TRUE(r.pos <= r.len);
    }
}

// ---------------------------------------------------------------------------
// MessagePack reader - untrusted binary
// ---------------------------------------------------------------------------

void test_msgpack_reader_random()
{
    for (int iter = 0; iter < 20000; iter++)
    {
        uint8_t buf[80];
        size_t n = xs32() % sizeof(buf);
        for (size_t i = 0; i < n; i++)
            buf[i] = (uint8_t)(xs32() & 0xFF);
        MsgpackReader r;
        msgpack_reader_init(&r, buf, n);
        // Try each typed reader against the head object; whatever happens, the
        // cursor stays within bounds and the error flag is sticky/defined.
        for (int step = 0; step < 12 && !r.err; step++)
        {
            TEST_ASSERT_TRUE(r.pos <= r.len);
            (void)msgpack_peek(&r);
            uint64_t u = 0;
            int64_t s = 0;
            bool b = false;
            float f = 0;
            const char *t = nullptr;
            const uint8_t *by = nullptr;
            size_t l = 0, cnt = 0;
            switch (xs32() % 9)
            {
            case 0:
                msgpack_read_uint(&r, &u);
                break;
            case 1:
                msgpack_read_int(&r, &s);
                break;
            case 2:
                msgpack_read_bool(&r, &b);
                break;
            case 3:
                msgpack_read_nil(&r);
                break;
            case 4:
                msgpack_read_float(&r, &f);
                break;
            case 5:
                if (msgpack_read_str(&r, &t, &l))
                    TEST_ASSERT_TRUE(l <= r.len);
                break;
            case 6:
                if (msgpack_read_bytes(&r, &by, &l))
                    TEST_ASSERT_TRUE(l <= r.len);
                break;
            case 7:
                msgpack_read_array(&r, &cnt);
                break;
            default:
                msgpack_read_map(&r, &cnt);
                break;
            }
            TEST_ASSERT_TRUE(r.pos <= r.len);
        }
        TEST_ASSERT_TRUE(r.pos <= r.len);
    }
}

// ---------------------------------------------------------------------------
// JSON reader - untrusted text
// ---------------------------------------------------------------------------

void test_json_reader_random()
{
    for (int iter = 0; iter < 20000; iter++)
    {
        char in[200];
        size_t n = xs32() % (sizeof(in) - 1);
        for (size_t i = 0; i < n; i++)
            in[i] = (char)(xs32() & 0xFF);
        in[n] = '\0';
        char sval[64];
        long ival = 0;
        bool bval = false;
        // No assert beyond "does not crash / over-read"; the readers return bool
        // and must never write past their caller buffers.
        json_get_str(in, "key", sval, sizeof(sval));
        TEST_ASSERT_TRUE(sval[sizeof(sval) - 1] == sval[sizeof(sval) - 1]); // touch (sanitizer guard)
        json_get_int(in, "key", &ival);
        json_get_bool(in, "key", &bval);
    }
}

void test_json_reader_malformed()
{
    const char *vectors[] = {
        "",
        "{",
        "}",
        "{\"key\":",
        "{\"key\":\"",
        "{\"key\":\"\\",                        // dangling escape
        "{\"key\":\"\\u",                       // truncated unicode escape
        "{\"key\":\"\\uZZZZ\"}",                // bad unicode escape
        "{\"key\":12345678901234567890}",       // integer overflow
        "[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]", // deep nesting
        "{\"key\":{\"key\":{\"key\":1}}}",      // nested key collision
        "{\"key\" : true , }",                  // trailing comma
    };
    char sval[64];
    long ival = 0;
    bool bval = false;
    for (size_t i = 0; i < sizeof(vectors) / sizeof(vectors[0]); i++)
    {
        json_get_str(vectors[i], "key", sval, sizeof(sval));
        json_get_int(vectors[i], "key", &ival);
        json_get_bool(vectors[i], "key", &bval);
    }
    TEST_ASSERT_TRUE(true); // reached here = no crash / over-read
}

// ---------------------------------------------------------------------------
// base64 / base32 decoders - user-supplied text
// ---------------------------------------------------------------------------

void test_base64_random()
{
    for (int iter = 0; iter < 12000; iter++)
    {
        char in[88];
        size_t n = xs32() % (sizeof(in) - 1);
        for (size_t i = 0; i < n; i++)
            in[i] = (char)(xs32() & 0xFF);
        in[n] = '\0';
        uint8_t out[64];
        size_t r = base64_decode(in, out, sizeof(out));
        TEST_ASSERT_TRUE(r <= sizeof(out)); // never writes past the output cap
    }
}

void test_base32_random()
{
    for (int iter = 0; iter < 12000; iter++)
    {
        char in[64];
        size_t n = xs32() % (sizeof(in) - 1);
        for (size_t i = 0; i < n; i++)
            in[i] = (char)(xs32() & 0xFF);
        in[n] = '\0';
        uint8_t out[40];
        int r = detws_base32_decode(in, out, sizeof(out));
        TEST_ASSERT_TRUE(r <= (int)sizeof(out)); // -1 on bad input, else within cap
    }
}

int main()
{
    UNITY_BEGIN();
    // HTTP
    RUN_TEST(test_http_oversized_path);
    RUN_TEST(test_http_content_length_too_large);
    RUN_TEST(test_http_header_flood);
    RUN_TEST(test_http_malformed_lines);
    RUN_TEST(test_http_smuggling_vectors);
    RUN_TEST(test_http_header_injection);
    RUN_TEST(test_http_binary_and_tls);
    RUN_TEST(test_http_partial_slowloris);
    RUN_TEST(test_http_random_fuzz);
    RUN_TEST(test_http_structured_fuzz);
    // multipart
    RUN_TEST(test_multipart_random);
    // CoAP
    RUN_TEST(test_coap_random_datagram);
    RUN_TEST(test_coap_structured_fuzz);
    // SNMP / BER
    RUN_TEST(test_snmp_random_packet);
    RUN_TEST(test_snmp_structured_fuzz);
    RUN_TEST(test_ber_decoder_random);
    // Modbus
    RUN_TEST(test_modbus_random_adu);
    RUN_TEST(test_modbus_master_response_random);
    // CBOR / JSON
    RUN_TEST(test_cbor_reader_random);
    RUN_TEST(test_msgpack_reader_random);
    RUN_TEST(test_json_reader_random);
    RUN_TEST(test_json_reader_malformed);
    // base64 / base32
    RUN_TEST(test_base64_random);
    RUN_TEST(test_base32_random);
    return UNITY_END();
}
