#include "dtob.h"
#include "json.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#define PASS(name) printf(" PASS: %s\n", name)
static DtobValue *roundtrip(const DtobValue *val, const DtobTypesHeader *th,
DtobTypesHeader *out_th)
{
DtobValue *arr = dtob_array();
DtobValue *copy = dtob_deep_copy((DtobValue *)val);
dtob_array_push(arr, copy);
size_t len;
uint8_t *buf = dtob_encode_with_types(arr, th, 1, &len);
dtob_free(arr);
assert(buf);
dtob_types_init(out_th);
DtobValue *parsed_arr = dtob_decode_with_types(buf, len, out_th);
free(buf);
assert(parsed_arr);
assert(parsed_arr->type == DTOB_ARRAY);
assert(parsed_arr->num_elements == 1);
DtobValue *result = parsed_arr->elements[0];
parsed_arr->elements[0] = NULL;
parsed_arr->num_elements = 0;
dtob_free(parsed_arr);
return result;
}
static void test_one_type_int32(void)
{
DtobTypesHeader th;
dtob_types_init(&th);
uint16_t ops[] = { DTOB_CODE_INT32 };
dtob_types_add(&th, 23, "temperature", ops, 1);
uint8_t data[4] = { 0x2A, 0x00, 0x00, 0x00 };
DtobValue *v = dtob_custom(23, data, 4);
DtobTypesHeader out_th;
DtobValue *parsed = roundtrip(v, &th, &out_th);
assert(parsed);
assert(parsed->type == DTOB_CUSTOM);
assert(parsed->custom_code == 23);
assert(parsed->inner_code == DTOB_CODE_INT32);
assert(parsed->data_len == 4);
assert(memcmp(parsed->data, data, 4) == 0);
assert(out_th.count == 1);
assert(strcmp(out_th.entries[0].name, "temperature") == 0);
assert(out_th.entries[0].n_opcodes == 1);
assert(out_th.entries[0].opcodes[0] == DTOB_CODE_INT32);
dtob_free(v);
dtob_free(parsed);
free(th.entries[0].name);
free(out_th.entries[0].name);
PASS("one-type enum (int32)");
}
static void test_nullable(void)
{
DtobTypesHeader th;
dtob_types_init(&th);
dtob_types_add(&th, 23, "nullable", NULL, 0);
DtobValue *v = dtob_custom(23, NULL, 0);
DtobTypesHeader out_th;
DtobValue *parsed = roundtrip(v, &th, &out_th);
assert(parsed);
assert(parsed->type == DTOB_CUSTOM);
assert(parsed->custom_code == 23);
assert(parsed->inner_code == 0);
assert(parsed->data_len == 0);
assert(out_th.count == 1);
assert(strcmp(out_th.entries[0].name, "nullable") == 0);
assert(out_th.entries[0].n_opcodes == 0);
dtob_free(v);
dtob_free(parsed);
free(th.entries[0].name);
free(out_th.entries[0].name);
PASS("nullable type (n_opcodes=0)");
}
static void test_one_type_raw_string(void)
{
DtobTypesHeader th;
dtob_types_init(&th);
uint16_t ops[] = { DTOB_CODE_RAW };
dtob_types_add(&th, 23, "string", ops, 1);
const char *str = "hello";
DtobValue *v = dtob_custom(23, (const uint8_t *)str, 5);
DtobTypesHeader out_th;
DtobValue *parsed = roundtrip(v, &th, &out_th);
assert(parsed);
assert(parsed->type == DTOB_CUSTOM);
assert(parsed->inner_code == DTOB_CODE_RAW);
assert(parsed->data_len == 5);
assert(memcmp(parsed->data, "hello", 5) == 0);
dtob_free(v);
dtob_free(parsed);
free(th.entries[0].name);
free(out_th.entries[0].name);
PASS("one-type enum (raw as string, variable)");
}
static void test_multi_type_enum(void)
{
DtobTypesHeader th;
dtob_types_init(&th);
uint16_t ops[] = { DTOB_CODE_INT32, DTOB_CODE_RAW };
dtob_types_add(&th, 23, "value", ops, 2);
uint8_t data[4] = { 0x07, 0x00, 0x00, 0x00 };
DtobValue *v = dtob_custom(23, data, 4);
v->inner_code = DTOB_CODE_INT32;
DtobTypesHeader out_th;
DtobValue *parsed = roundtrip(v, &th, &out_th);
assert(parsed);
assert(parsed->type == DTOB_CUSTOM);
assert(parsed->inner_code == DTOB_CODE_INT32);
assert(parsed->data_len == 4);
assert(memcmp(parsed->data, data, 4) == 0);
dtob_free(v);
dtob_free(parsed);
free(out_th.entries[0].name);
v = dtob_custom(23, (const uint8_t *)"abc", 3);
v->inner_code = DTOB_CODE_RAW;
DtobTypesHeader out_th2;
parsed = roundtrip(v, &th, &out_th2);
assert(parsed);
assert(parsed->inner_code == DTOB_CODE_RAW);
assert(parsed->data_len == 3);
assert(memcmp(parsed->data, "abc", 3) == 0);
dtob_free(v);
dtob_free(parsed);
free(th.entries[0].name);
free(out_th2.entries[0].name);
PASS("multi-type enum (int32 | raw)");
}
static void test_multiple_types(void)
{
DtobTypesHeader th;
dtob_types_init(&th);
uint16_t ops1[] = { DTOB_CODE_INT32 };
dtob_types_add(&th, 23, "age", ops1, 1);
uint16_t ops2[] = { DTOB_CODE_RAW };
dtob_types_add(&th, 24, "name", ops2, 1);
dtob_types_add(&th, 25, "nullable", NULL, 0);
DtobValue *arr = dtob_array();
uint8_t age_data[4] = { 0x1E, 0x00, 0x00, 0x00 };
dtob_array_push(arr, dtob_custom(23, age_data, 4));
dtob_array_push(arr, dtob_custom(24, (const uint8_t *)"Alice", 5));
dtob_array_push(arr, dtob_custom(25, NULL, 0));
size_t len;
uint8_t *buf = dtob_encode_with_types(arr, &th, 1, &len);
dtob_free(arr);
assert(buf);
DtobTypesHeader out_th;
dtob_types_init(&out_th);
DtobValue *parsed = dtob_decode_with_types(buf, len, &out_th);
free(buf);
assert(parsed);
assert(parsed->type == DTOB_ARRAY);
assert(parsed->num_elements == 3);
assert(parsed->elements[0]->custom_code == 23);
assert(parsed->elements[0]->inner_code == DTOB_CODE_INT32);
assert(parsed->elements[0]->data_len == 4);
assert(parsed->elements[1]->custom_code == 24);
assert(parsed->elements[1]->inner_code == DTOB_CODE_RAW);
assert(parsed->elements[1]->data_len == 5);
assert(parsed->elements[2]->custom_code == 25);
assert(parsed->elements[2]->inner_code == 0);
assert(parsed->elements[2]->data_len == 0);
assert(out_th.count == 3);
dtob_free(parsed);
for (size_t i = 0; i < th.count; i++) free(th.entries[i].name);
for (size_t i = 0; i < out_th.count; i++) free(out_th.entries[i].name);
PASS("multiple custom types in header");
}
static void test_one_type_float64(void)
{
DtobTypesHeader th;
dtob_types_init(&th);
uint16_t ops[] = { DTOB_CODE_DOUBLE };
dtob_types_add(&th, 23, "coord", ops, 1);
double val = 3.14159;
DtobValue *v = dtob_custom(23, (const uint8_t *)&val, 8);
DtobTypesHeader out_th;
DtobValue *parsed = roundtrip(v, &th, &out_th);
assert(parsed);
assert(parsed->inner_code == DTOB_CODE_DOUBLE);
assert(parsed->data_len == 8);
double got;
memcpy(&got, parsed->data, 8);
assert(got == val);
dtob_free(v);
dtob_free(parsed);
free(th.entries[0].name);
free(out_th.entries[0].name);
PASS("one-type enum (float64)");
}
static void test_custom_in_kvset(void)
{
DtobTypesHeader th;
dtob_types_init(&th);
uint16_t ops[] = { DTOB_CODE_UINT8 };
dtob_types_add(&th, 23, "level", ops, 1);
DtobValue *kvs = dtob_kvset();
uint8_t lvl = 5;
dtob_kvset_put(kvs, "difficulty", dtob_custom(23, &lvl, 1));
dtob_kvset_put(kvs, "name", json_string("test", 4));
size_t len;
uint8_t *buf = dtob_encode_with_types(kvs, &th, 1, &len);
dtob_free(kvs);
assert(buf);
DtobTypesHeader out_th;
dtob_types_init(&out_th);
DtobValue *parsed = dtob_decode_with_types(buf, len, &out_th);
free(buf);
assert(parsed);
assert(parsed->type == DTOB_KV_SET);
int found = 0;
for (size_t i = 0; i < parsed->num_pairs; i++) {
if (parsed->pairs[i].key_len == 10 &&
memcmp(parsed->pairs[i].key, "difficulty", 10) == 0) {
DtobValue *dv = parsed->pairs[i].value;
assert(dv->type == DTOB_CUSTOM);
assert(dv->custom_code == 23);
assert(dv->inner_code == DTOB_CODE_UINT8);
assert(dv->data_len == 1);
assert(dv->data[0] == 5);
found = 1;
}
}
assert(found);
dtob_free(parsed);
free(th.entries[0].name);
free(out_th.entries[0].name);
PASS("custom value inside kv_set");
}
static void test_one_type_raw(void)
{
DtobTypesHeader th;
dtob_types_init(&th);
uint16_t ops[] = { DTOB_CODE_RAW };
dtob_types_add(&th, 23, "blob", ops, 1);
uint8_t raw_data[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x01 };
DtobValue *v = dtob_custom(23, raw_data, 6);
DtobTypesHeader out_th;
DtobValue *parsed = roundtrip(v, &th, &out_th);
assert(parsed);
assert(parsed->inner_code == DTOB_CODE_RAW);
assert(parsed->data_len == 6);
assert(memcmp(parsed->data, raw_data, 6) == 0);
dtob_free(v);
dtob_free(parsed);
free(th.entries[0].name);
free(out_th.entries[0].name);
PASS("one-type enum (raw, variable)");
}
static void test_nullable_with_data_types(void)
{
DtobTypesHeader th;
dtob_types_init(&th);
uint16_t ops[] = { DTOB_CODE_INT32, DTOB_CODE_UINT64 };
dtob_types_add(&th, 22, "number", ops, 2);
dtob_types_add(&th, 23, "nullable", NULL, 0);
DtobValue *arr = dtob_array();
uint8_t data[4] = { 0x0A, 0x00, 0x00, 0x00 };
DtobValue *num = dtob_custom(22, data, 4);
num->inner_code = DTOB_CODE_INT32;
dtob_array_push(arr, num);
dtob_array_push(arr, dtob_custom(23, NULL, 0));
size_t len;
uint8_t *buf = dtob_encode_with_types(arr, &th, 1, &len);
dtob_free(arr);
assert(buf);
DtobTypesHeader out_th;
dtob_types_init(&out_th);
DtobValue *parsed = dtob_decode_with_types(buf, len, &out_th);
free(buf);
assert(parsed);
assert(parsed->num_elements == 2);
assert(parsed->elements[0]->custom_code == 22);
assert(parsed->elements[0]->inner_code == DTOB_CODE_INT32);
assert(parsed->elements[1]->custom_code == 23);
assert(parsed->elements[1]->inner_code == 0);
dtob_free(parsed);
for (size_t i = 0; i < th.count; i++) free(th.entries[i].name);
for (size_t i = 0; i < out_th.count; i++) free(out_th.entries[i].name);
PASS("nullable with data types in same header");
}
static void test_types_first_enforcement(void)
{
DtobTypesHeader th;
dtob_types_init(&th);
uint16_t ops[] = { DTOB_CODE_INT32 };
dtob_types_add(&th, 22, "num", ops, 1);
DtobValue *arr = dtob_array();
uint8_t data[4] = { 0x01, 0x00, 0x00, 0x00 };
dtob_array_push(arr, dtob_custom(22, data, 4));
size_t len;
uint8_t *buf = dtob_encode_with_types(arr, &th, 1, &len);
dtob_free(arr);
assert(buf);
DtobTypesHeader out_th;
dtob_types_init(&out_th);
DtobValue *parsed = dtob_decode_with_types(buf, len, &out_th);
free(buf);
assert(parsed);
dtob_free(parsed);
free(th.entries[0].name);
free(out_th.entries[0].name);
PASS("types block first element enforcement");
}
int main(void)
{
printf("=== typed enum tests ===\n");
test_one_type_int32();
test_nullable();
test_one_type_raw_string();
test_multi_type_enum();
test_multiple_types();
test_one_type_float64();
test_custom_in_kvset();
test_one_type_raw();
test_nullable_with_data_types();
test_types_first_enforcement();
printf("ALL %d TESTS PASSED\n", 10);
return 0;
}