#include "naut/bencode.h" #include "test.h" #include #include static naut_bc_doc *ok(const char *s) { naut_bc_doc *d = NULL; naut_err e = naut_bc_parse((const uint8_t *)s, strlen(s), &d); if (e != NAUT_OK) { fprintf(stderr, " unexpected reject: \"%s\" (%s)\n", s, naut_strerror(e)); } return e == NAUT_OK ? d : NULL; } static int rejects(const char *s, size_t len) { naut_bc_doc *d = NULL; naut_err e = naut_bc_parse((const uint8_t *)s, len, &d); if (e == NAUT_OK) { naut_bc_free(d); return 0; } return 1; } #define REJECT(s) CHECK(rejects(s, sizeof(s) - 1)) int main(void) { /* integers */ naut_bc_doc *d; int64_t iv; d = ok("i42e"); CHECK(d && naut_bc_get_int(naut_bc_root(d), &iv) && iv == 42); naut_bc_free(d); d = ok("i0e"); CHECK(d && naut_bc_get_int(naut_bc_root(d), &iv) && iv == 0); naut_bc_free(d); d = ok("i-7e"); CHECK(d && naut_bc_get_int(naut_bc_root(d), &iv) && iv == -7); naut_bc_free(d); REJECT("ie"); REJECT("i03e"); REJECT("i-0e"); REJECT("i-e"); REJECT("i1 e"); REJECT("i42"); /* strings (zero-copy + binary-safe) */ const uint8_t *sp; size_t sn; d = ok("4:spam"); CHECK(d && naut_bc_get_str(naut_bc_root(d), &sp, &sn) && sn == 4 && !memcmp(sp,"spam",4)); /* slice points into the original buffer, not a copy */ naut_bc_free(d); REJECT("4:spa"); REJECT("01:a"); /* leading zero length */ /* binary string with embedded NUL */ { const char buf[] = "5:a\0b\0c"; /* len prefix "5:" then a \0 b \0 c */ naut_bc_doc *bd = NULL; CHECK(naut_bc_parse((const uint8_t *)buf, 7, &bd) == NAUT_OK); CHECK(bd && naut_bc_get_str(naut_bc_root(bd), &sp, &sn) && sn == 5 && sp[1] == 0 && sp[3] == 0); naut_bc_free(bd); } /* list */ d = ok("l4:spami42ee"); CHECK(d); const naut_bc *l = naut_bc_root(d); CHECK(l && l->type == NAUT_BC_LIST && l->v.list.count == 2); CHECK(naut_bc_str_eq(naut_bc_list_at(l, 0), "spam")); CHECK(naut_bc_get_int(naut_bc_list_at(l, 1), &iv) && iv == 42); naut_bc_free(d); /* dict + raw span preservation (crucial for info-hash) */ d = ok("d3:bar4:spam3:fooi42ee"); const naut_bc *root = naut_bc_root(d); CHECK(naut_bc_str_eq(naut_bc_dict_get(root, "bar"), "spam")); CHECK(naut_bc_get_int(naut_bc_dict_get(root, "foo"), &iv) && iv == 42); const naut_bc *bar = naut_bc_dict_get(root, "bar"); CHECK(bar->raw_len == 6 && memcmp(bar->raw, "4:spam", 6) == 0); /* exact encoding */ CHECK(naut_bc_dict_get(root, "missing") == NULL); naut_bc_free(d); /* structural rejects */ REJECT("i1ex"); /* trailing garbage */ REJECT("d3:bar4:spam"); /* unterminated dict */ REJECT("l1:a"); /* unterminated list */ REJECT("di1e1:ae"); /* non-string dict key */ REJECT(""); /* empty */ /* depth bomb: 200 nested lists must be rejected, not overflow the stack */ { char bomb[512]; memset(bomb, 'l', sizeof bomb); CHECK(rejects(bomb, sizeof bomb)); } /* encoder round-trips back to an equal parse */ { naut_bc_writer w; naut_bc_w_init(&w); naut_bc_w_dict_begin(&w); naut_bc_w_cstr(&w, "foo"); naut_bc_w_int(&w, 7); naut_bc_w_cstr(&w, "list"); naut_bc_w_list_begin(&w); naut_bc_w_cstr(&w, "x"); naut_bc_w_int(&w, -1); naut_bc_w_end(&w); naut_bc_w_end(&w); CHECK(w.err == NAUT_OK); naut_bc_doc *rd = NULL; CHECK(naut_bc_parse(w.buf, w.len, &rd) == NAUT_OK); const naut_bc *r = naut_bc_root(rd); CHECK(naut_bc_get_int(naut_bc_dict_get(r, "foo"), &iv) && iv == 7); const naut_bc *lst = naut_bc_dict_get(r, "list"); CHECK(lst && lst->type == NAUT_BC_LIST && lst->v.list.count == 2); naut_bc_free(rd); naut_bc_w_free(&w); } TEST_MAIN_END(); }