#include "cli.h"
static int opcode_from_name(const char *name, size_t len)
{
static const struct { const char *n; uint8_t c; } map[] = {
{"int8", DTOB_CODE_INT8}, {"int16", DTOB_CODE_INT16},
{"int32", DTOB_CODE_INT32}, {"int64", DTOB_CODE_INT64},
{"uint8", DTOB_CODE_UINT8}, {"uint16", DTOB_CODE_UINT16},
{"uint32", DTOB_CODE_UINT32}, {"uint64", DTOB_CODE_UINT64},
{"float", DTOB_CODE_FLOAT}, {"double", DTOB_CODE_DOUBLE},
{"raw", DTOB_CODE_RAW},
};
for (size_t i = 0; i < sizeof(map)/sizeof(map[0]); i++) {
if (strlen(map[i].n) == len && memcmp(map[i].n, name, len) == 0)
return map[i].c;
}
return -1;
}
typedef struct {
char *key;
char *subkey;
uint16_t code;
} PairEntry;
typedef struct {
PairEntry *entries;
size_t count;
size_t cap;
} PairLookup;
static void pl_init(PairLookup *pl)
{
pl->cap = 16;
pl->entries = malloc(pl->cap * sizeof(PairEntry));
pl->count = 0;
}
static void pl_add(PairLookup *pl, const char *key,
const char *subkey, uint16_t code)
{
if (pl->count >= pl->cap) {
pl->cap *= 2;
pl->entries = realloc(pl->entries, pl->cap * sizeof(PairEntry));
}
pl->entries[pl->count].key = strdup(key);
pl->entries[pl->count].subkey = strdup(subkey);
pl->entries[pl->count].code = code;
pl->count++;
}
static int pl_match(const PairLookup *pl, const char *key, size_t key_len,
const char *subkey, size_t subkey_len)
{
for (size_t i = 0; i < pl->count; i++) {
if (strlen(pl->entries[i].key) == key_len &&
memcmp(pl->entries[i].key, key, key_len) == 0 &&
strlen(pl->entries[i].subkey) == subkey_len &&
memcmp(pl->entries[i].subkey, subkey, subkey_len) == 0)
return pl->entries[i].code;
}
return -1;
}
static int pl_has_key(const PairLookup *pl, const char *key, size_t key_len)
{
for (size_t i = 0; i < pl->count; i++) {
if (strlen(pl->entries[i].key) == key_len &&
memcmp(pl->entries[i].key, key, key_len) == 0)
return 1;
}
return 0;
}
static int pl_type_code(const PairLookup *pl, const DtobTypesHeader *th,
const char *key, size_t key_len)
{
for (size_t i = 0; i < pl->count; i++) {
if (strlen(pl->entries[i].key) == key_len &&
memcmp(pl->entries[i].key, key, key_len) == 0) {
for (size_t j = 0; j < th->count; j++) {
if (th->entries[j].is_struct &&
strcmp(th->entries[j].name, pl->entries[i].key) == 0)
return th->entries[j].code;
}
}
}
return -1;
}
static void pl_free(PairLookup *pl)
{
for (size_t i = 0; i < pl->count; i++) {
free(pl->entries[i].key);
free(pl->entries[i].subkey);
}
free(pl->entries);
}
typedef struct {
char *key;
uint16_t code;
} EnumKey;
typedef struct {
EnumKey *entries;
size_t count;
size_t cap;
} EnumLookup;
static void el_init(EnumLookup *el)
{
el->cap = 16;
el->entries = malloc(el->cap * sizeof(EnumKey));
el->count = 0;
}
static void el_add(EnumLookup *el, const char *key, uint16_t code)
{
if (el->count >= el->cap) {
el->cap *= 2;
el->entries = realloc(el->entries, el->cap * sizeof(EnumKey));
}
el->entries[el->count].key = strdup(key);
el->entries[el->count].code = code;
el->count++;
}
static int el_match(const EnumLookup *el, const char *key, size_t key_len)
{
for (size_t i = 0; i < el->count; i++) {
if (strlen(el->entries[i].key) == key_len &&
memcmp(el->entries[i].key, key, key_len) == 0)
return el->entries[i].code;
}
return -1;
}
static void el_free(EnumLookup *el)
{
for (size_t i = 0; i < el->count; i++)
free(el->entries[i].key);
free(el->entries);
}
typedef struct {
const PairLookup *variants;
const EnumLookup *enums;
const PairLookup *structs;
const DtobTypesHeader *types;
} EncCtx;
static int round_up_width(size_t len)
{
if (len <= 1) return 1;
if (len <= 2) return 2;
if (len <= 4) return 4;
return 8;
}
static uint16_t match_value_opcode(const DtobValue *val, const DtobCustomType *ct)
{
switch ((int)val->type) {
case JSON_STRING:
case DTOB_RAW:
for (size_t i = 0; i < ct->n_opcodes; i++)
if (ct->opcodes[i] == DTOB_CODE_RAW) return DTOB_CODE_RAW;
for (size_t i = 0; i < ct->n_opcodes; i++)
if (ct->opcodes[i] == JSON_CODE_STRING) return JSON_CODE_STRING;
return 0;
case JSON_TRUE:
for (size_t i = 0; i < ct->n_opcodes; i++)
if (ct->opcodes[i] == JSON_CODE_TRUE) return JSON_CODE_TRUE;
return 0;
case JSON_FALSE:
for (size_t i = 0; i < ct->n_opcodes; i++)
if (ct->opcodes[i] == JSON_CODE_FALSE) return JSON_CODE_FALSE;
return 0;
case JSON_NULL:
for (size_t i = 0; i < ct->n_opcodes; i++)
if (ct->opcodes[i] == JSON_CODE_NULL) return JSON_CODE_NULL;
return 0;
case DTOB_FLOAT:
for (size_t i = 0; i < ct->n_opcodes; i++)
if (ct->opcodes[i] == DTOB_CODE_DOUBLE) return DTOB_CODE_DOUBLE;
for (size_t i = 0; i < ct->n_opcodes; i++)
if (ct->opcodes[i] == DTOB_CODE_FLOAT) return DTOB_CODE_FLOAT;
return 0;
case DTOB_INT: {
int is_neg = (val->data_len > 0 && (val->data[0] & 0x80));
int width = round_up_width(val->data_len);
if (is_neg) {
uint16_t signed_ops[] = { DTOB_CODE_INT8, DTOB_CODE_INT16,
DTOB_CODE_INT32, DTOB_CODE_INT64 };
int widths[] = { 1, 2, 4, 8 };
for (int w = 0; w < 4; w++) {
if (widths[w] < width) continue;
for (size_t i = 0; i < ct->n_opcodes; i++)
if (ct->opcodes[i] == signed_ops[w]) return signed_ops[w];
}
} else {
uint16_t unsigned_ops[] = { DTOB_CODE_UINT8, DTOB_CODE_UINT16,
DTOB_CODE_UINT32, DTOB_CODE_UINT64 };
uint16_t signed_ops[] = { DTOB_CODE_INT8, DTOB_CODE_INT16,
DTOB_CODE_INT32, DTOB_CODE_INT64 };
int widths[] = { 1, 2, 4, 8 };
for (int w = 0; w < 4; w++) {
if (widths[w] < width) continue;
for (size_t i = 0; i < ct->n_opcodes; i++)
if (ct->opcodes[i] == unsigned_ops[w]) return unsigned_ops[w];
for (size_t i = 0; i < ct->n_opcodes; i++)
if (ct->opcodes[i] == signed_ops[w]) return signed_ops[w];
}
}
return 0;
}
default: return 0;
}
}
static void write_opcode_data(ByteWriter *w, const DtobValue *val, uint16_t opcode)
{
int expected = dtob_opcode_data_size(opcode);
if (expected == 0) return;
if (opcode >= DTOB_CODE_INT8 && opcode <= DTOB_CODE_UINT64) {
int width = expected;
int is_neg = (val->data_len > 0 && (val->data[0] & 0x80));
uint8_t buf[8] = {0};
uint8_t fill = is_neg ? 0xFF : 0x00;
int pad = width - (int)val->data_len;
if (pad > 0) {
memset(buf, fill, pad);
memcpy(buf + pad, val->data, val->data_len);
} else {
memcpy(buf, val->data + val->data_len - width, width);
}
bw_write_data(w, buf, width);
return;
}
if (opcode == DTOB_CODE_FLOAT && val->data_len == 8) {
double d;
memcpy(&d, val->data, 8);
float f = (float)d;
bw_write_data(w, (const uint8_t *)&f, 4);
return;
}
if (val->data && val->data_len > 0)
bw_write_data(w, val->data, val->data_len);
}
static int encode_enum_value(ByteWriter *w, const DtobValue *val,
const DtobCustomType *ct)
{
uint16_t opcode = match_value_opcode(val, ct);
if (opcode == 0) {
fprintf(stderr, "error: value type not allowed in enum \"%s\"\n",
ct->name);
return 1;
}
bw_write_ctrl(w, ct->code);
if (ct->n_opcodes == 1) {
write_opcode_data(w, val, opcode);
} else {
bw_write_ctrl(w, opcode);
write_opcode_data(w, val, opcode);
}
return 0;
}
static int encode_struct_value(ByteWriter *w, const char *key, size_t key_len,
const DtobValue *val, const PairLookup *structs,
const DtobTypesHeader *th)
{
int scode = pl_type_code(structs, th, key, key_len);
if (scode < 0) return 1;
if (val->type != DTOB_KV_SET) {
fprintf(stderr, "error: struct \"%.*s\" value must be an object\n",
(int)key_len, key);
return 1;
}
DtobCustomType *sct = dtob_types_get(th, (uint16_t)scode);
bw_write_ctrl(w, (uint16_t)scode);
bw_write_ctrl(w, DTOB_CODE_OPEN);
char sname[256];
size_t slen = key_len < sizeof(sname) - 1 ? key_len : sizeof(sname) - 1;
memcpy(sname, key, slen);
sname[slen] = '\0';
for (size_t mi = 0; mi < val->num_pairs; mi++) {
const char *mkey = (const char *)val->pairs[mi].key;
size_t mkey_len = val->pairs[mi].key_len;
int mop = pl_match(structs, sname, slen, mkey, mkey_len);
if (mop < 0) {
fprintf(stderr, "error: unknown member \"%.*s\" in struct \"%s\"\n",
(int)mkey_len, mkey, sname);
return 1;
}
if (sct) {
bw_write_ctrl(w, (uint16_t)mop);
write_opcode_data(w, val->pairs[mi].value, (uint16_t)mop);
}
}
bw_write_ctrl(w, DTOB_CODE_ARR_CLOSE);
return 0;
}
static const struct { uint16_t code; const char *name; uint16_t inner; } conv_types[] = {
{ JSON_CODE_STRING, "string", DTOB_CODE_RAW },
{ JSON_CODE_TRUE, "true", 0 },
{ JSON_CODE_FALSE, "false", 0 },
{ JSON_CODE_NULL, "null", 0 },
};
static void bw_write_magic(ByteWriter *w)
{
for (int i = 0; i < DTOB_MAGIC_LEN; i++)
bw_write_byte(w, (uint8_t)DTOB_MAGIC[i]);
}
static void bw_emit_type_entry(ByteWriter *w, uint16_t code, const char *name,
const uint16_t *inner, size_t n_inner, int is_struct)
{
bw_write_ctrl(w, DTOB_CODE_OPEN);
bw_write_ctrl(w, code);
bw_write_data(w, (const uint8_t *)name, strlen(name));
for (size_t i = 0; i < n_inner; i++)
bw_write_ctrl(w, inner[i]);
bw_write_ctrl(w, is_struct ? DTOB_CODE_ARR_CLOSE : DTOB_CODE_KV_CLOSE);
}
static void bw_emit_conv_types(ByteWriter *w, const int has[4])
{
for (int i = 0; i < 4; i++) {
if (!has[i]) continue;
bw_emit_type_entry(w, conv_types[i].code, conv_types[i].name,
conv_types[i].inner ? &conv_types[i].inner : NULL,
conv_types[i].inner ? 1 : 0, 0);
}
}
static int encode_value(ByteWriter *w, const DtobValue *v, const EncCtx *ctx)
{
if (!v) return 0;
switch ((int)v->type) {
case JSON_STRING:
bw_write_ctrl(w, JSON_CODE_STRING);
bw_write_data(w, v->data, v->data_len);
break;
case DTOB_INT: {
int is_negative = (v->data_len > 0 && (v->data[0] & 0x80));
int is_unsigned = !is_negative;
int width = round_up_width(v->data_len);
int width_index = 0;
switch (width) { case 2: width_index = 1; break;
case 4: width_index = 2; break;
case 8: width_index = 3; break; }
uint16_t code = 8 + (is_unsigned ? 4 : 0) + width_index;
bw_write_ctrl(w, code);
uint8_t buf[8] = {0};
uint8_t fill = is_negative ? 0xFF : 0x00;
int pad = width - (int)v->data_len;
if (pad > 0) {
memset(buf, fill, pad);
memcpy(buf + pad, v->data, v->data_len);
} else {
memcpy(buf, v->data + v->data_len - width, width);
}
bw_write_data(w, buf, width);
break;
}
case DTOB_FLOAT:
bw_write_ctrl(w, DTOB_CODE_DOUBLE);
bw_write_data(w, v->data, v->data_len);
break;
case DTOB_RAW:
bw_write_ctrl(w, DTOB_CODE_RAW);
bw_write_data(w, v->data, v->data_len);
break;
case JSON_TRUE: bw_write_ctrl(w, JSON_CODE_TRUE); break;
case JSON_FALSE: bw_write_ctrl(w, JSON_CODE_FALSE); break;
case JSON_NULL: bw_write_ctrl(w, JSON_CODE_NULL); break;
case DTOB_CUSTOM:
bw_write_ctrl(w, v->custom_code);
if (v->data && v->data_len > 0)
bw_write_data(w, v->data, v->data_len);
break;
case DTOB_ARRAY:
bw_write_ctrl(w, DTOB_CODE_OPEN);
for (size_t i = 0; i < v->num_elements; i++) {
if (encode_value(w, v->elements[i], ctx) != 0) return 1;
}
bw_write_ctrl(w, DTOB_CODE_ARR_CLOSE);
break;
case DTOB_KV_SET: {
bw_write_ctrl(w, DTOB_CODE_OPEN);
for (size_t i = 0; i < v->num_pairs; i++) {
const char *key = (const char *)v->pairs[i].key;
size_t key_len = v->pairs[i].key_len;
DtobValue *val = v->pairs[i].value;
if (ctx->structs && pl_has_key(ctx->structs, key, key_len)) {
if (encode_struct_value(w, key, key_len, val,
ctx->structs, ctx->types) != 0)
return 1;
continue;
}
int ucode = ctx->enums ? el_match(ctx->enums, key, key_len) : -1;
if (ucode >= 0) {
DtobCustomType *ct = dtob_types_get(ctx->types, (uint16_t)ucode);
if (encode_enum_value(w, val, ct) != 0) return 1;
continue;
}
if (ctx->variants && val->type == JSON_STRING &&
pl_has_key(ctx->variants, key, key_len)) {
int code = pl_match(ctx->variants, key, key_len,
(const char *)val->data, val->data_len);
if (code < 0) {
fprintf(stderr, "error: value \"%.*s\" is not a valid "
"variant of type \"%.*s\"\n",
(int)val->data_len, val->data,
(int)key_len, key);
return 1;
}
bw_write_ctrl(w, (uint16_t)code);
continue;
}
bw_write_ctrl(w, DTOB_CODE_RAW);
bw_write_data(w, v->pairs[i].key, v->pairs[i].key_len);
if (encode_value(w, val, ctx) != 0) return 1;
}
bw_write_ctrl(w, DTOB_CODE_KV_CLOSE);
break;
}
}
return 0;
}
static int build_types_from_json(const DtobValue *types_val,
DtobTypesHeader *th, PairLookup *variants,
EnumLookup *el, PairLookup *structs)
{
if (types_val->type != DTOB_KV_SET) {
fprintf(stderr, "error: \"types\" must be an object\n");
return 1;
}
uint16_t next_code = DTOB_CUSTOM_MIN;
for (size_t i = 0; i < types_val->num_pairs; i++) {
const char *type_name = (const char *)types_val->pairs[i].key;
size_t type_name_len = types_val->pairs[i].key_len;
DtobValue *def = types_val->pairs[i].value;
char type_z[256];
if (type_name_len >= sizeof(type_z)) {
fprintf(stderr, "error: type name too long\n");
return 1;
}
memcpy(type_z, type_name, type_name_len);
type_z[type_name_len] = '\0';
if (next_code > DTOB_CUSTOM_MAX) {
fprintf(stderr, "error: too many custom types (max %d)\n",
DTOB_CUSTOM_MAX);
return 1;
}
if (def->type == DTOB_KV_SET) {
if (def->num_pairs == 0) {
fprintf(stderr, "error: struct \"%s\" has no members\n", type_z);
return 1;
}
uint16_t opcodes[15];
size_t n_opcodes = 0;
for (size_t j = 0; j < def->num_pairs; j++) {
const char *mname = (const char *)def->pairs[j].key;
size_t mname_len = def->pairs[j].key_len;
DtobValue *mval = def->pairs[j].value;
if (mval->type != JSON_STRING) {
fprintf(stderr, "error: struct member opcode must be a string\n");
return 1;
}
int op = opcode_from_name((const char *)mval->data, mval->data_len);
if (op < 0) {
fprintf(stderr, "error: unknown opcode \"%.*s\" in struct \"%s\"\n",
(int)mval->data_len, mval->data, type_z);
return 1;
}
if (n_opcodes >= 15) {
fprintf(stderr, "error: too many members in struct \"%s\"\n", type_z);
return 1;
}
opcodes[n_opcodes++] = (uint16_t)op;
char mname_z[256];
if (mname_len >= sizeof(mname_z)) mname_len = sizeof(mname_z) - 1;
memcpy(mname_z, mname, mname_len);
mname_z[mname_len] = '\0';
pl_add(structs, type_z, mname_z, (uint16_t)op);
}
dtob_types_add(th, next_code, type_z, opcodes, n_opcodes);
th->entries[th->count - 1].is_struct = 1;
next_code++;
continue;
}
if (def->type != DTOB_ARRAY) {
fprintf(stderr, "error: type \"%s\" must map to an array or object\n", type_z);
return 1;
}
DtobValue *arr = def;
if (arr->num_elements == 0) {
fprintf(stderr, "error: type \"%s\" has no members\n", type_z);
return 1;
}
int is_enum = 1;
uint16_t opcodes[15];
size_t n_opcodes = 0;
for (size_t j = 0; j < arr->num_elements; j++) {
DtobValue *elem = arr->elements[j];
if (elem->type != JSON_STRING) { is_enum = 0; break; }
int op = opcode_from_name((const char *)elem->data, elem->data_len);
if (op < 0) { is_enum = 0; break; }
if (n_opcodes >= 15) { is_enum = 0; break; }
opcodes[n_opcodes++] = (uint16_t)op;
}
if (is_enum && n_opcodes > 0) {
dtob_types_add(th, next_code, type_z, opcodes, n_opcodes);
el_add(el, type_z, next_code);
next_code++;
} else {
for (size_t j = 0; j < arr->num_elements; j++) {
DtobValue *elem = arr->elements[j];
if (elem->type != JSON_STRING) {
fprintf(stderr, "error: variant values must be strings\n");
return 1;
}
if (next_code > DTOB_CUSTOM_MAX) {
fprintf(stderr, "error: too many custom types (max %d)\n",
DTOB_CUSTOM_MAX);
return 1;
}
char var_z[256];
if (elem->data_len >= sizeof(var_z)) {
fprintf(stderr, "error: variant name too long\n");
return 1;
}
memcpy(var_z, elem->data, elem->data_len);
var_z[elem->data_len] = '\0';
dtob_types_add(th, next_code, var_z, NULL, 0);
pl_add(variants, type_z, var_z, next_code);
next_code++;
}
}
}
return 0;
}
static void scan_conventions(const DtobValue *v,
int *has_string, int *has_true,
int *has_false, int *has_null)
{
if (!v) return;
switch ((int)v->type) {
case JSON_STRING: *has_string = 1; break;
case JSON_TRUE: *has_true = 1; break;
case JSON_FALSE: *has_false = 1; break;
case JSON_NULL: *has_null = 1; break;
case DTOB_ARRAY:
for (size_t i = 0; i < v->num_elements; i++)
scan_conventions(v->elements[i], has_string, has_true, has_false, has_null);
break;
case DTOB_KV_SET:
for (size_t i = 0; i < v->num_pairs; i++)
scan_conventions(v->pairs[i].value, has_string, has_true, has_false, has_null);
for (size_t i = 0; i < v->num_elements; i++)
scan_conventions(v->elements[i], has_string, has_true, has_false, has_null);
break;
default: break;
}
}
int typed_encode(const DtobValue *root, uint8_t **out, size_t *out_len)
{
if (root->type != DTOB_KV_SET || root->num_pairs != 2) {
fprintf(stderr, "error: --types requires root object with exactly "
"\"types\" and \"data\" keys\n");
return 1;
}
DtobValue *types_val = NULL;
DtobValue *data_val = NULL;
for (size_t i = 0; i < root->num_pairs; i++) {
if (root->pairs[i].key_len == 5 &&
memcmp(root->pairs[i].key, "types", 5) == 0)
types_val = root->pairs[i].value;
else if (root->pairs[i].key_len == 4 &&
memcmp(root->pairs[i].key, "data", 4) == 0)
data_val = root->pairs[i].value;
}
if (!types_val || !data_val) {
fprintf(stderr, "error: --types requires \"types\" and \"data\" keys\n");
return 1;
}
DtobTypesHeader th;
dtob_types_init(&th);
PairLookup variants;
pl_init(&variants);
EnumLookup el;
el_init(&el);
PairLookup structs;
pl_init(&structs);
if (build_types_from_json(types_val, &th, &variants, &el, &structs) != 0) {
pl_free(&variants);
el_free(&el);
pl_free(&structs);
return 1;
}
ByteWriter w;
bw_init(&w, 0);
bw_write_magic(&w);
bw_write_ctrl(&w, DTOB_CODE_OPEN);
bw_write_ctrl(&w, DTOB_CODE_OPEN);
for (size_t i = 0; i < th.count; i++)
bw_emit_type_entry(&w, th.entries[i].code, th.entries[i].name,
th.entries[i].opcodes, th.entries[i].n_opcodes,
th.entries[i].is_struct);
bw_write_ctrl(&w, DTOB_CODE_TYPES_CLOSE);
EncCtx ctx = { &variants, &el, &structs, &th };
int err = 0;
if (data_val->type == DTOB_ARRAY) {
for (size_t i = 0; i < data_val->num_elements; i++) {
if (encode_value(&w, data_val->elements[i], &ctx) != 0) { err = 1; break; }
}
if (!err) bw_write_ctrl(&w, DTOB_CODE_ARR_CLOSE);
} else if (data_val->type == DTOB_KV_SET) {
for (size_t i = 0; i < data_val->num_pairs; i++) {
const char *key = (const char *)data_val->pairs[i].key;
size_t key_len = data_val->pairs[i].key_len;
DtobValue *val = data_val->pairs[i].value;
if (pl_has_key(&structs, key, key_len)) {
if (encode_struct_value(&w, key, key_len, val, &structs, &th) != 0)
{ err = 1; break; }
continue;
}
int ucode = el_match(&el, key, key_len);
if (ucode >= 0) {
DtobCustomType *ct = dtob_types_get(&th, (uint16_t)ucode);
if (ct && ct->n_opcodes == 0) {
bw_write_ctrl(&w, ct->code);
} else if (ct) {
if (encode_enum_value(&w, val, ct) != 0) { err = 1; break; }
}
continue;
}
if (pl_has_key(&variants, key, key_len)) {
if (val->type == JSON_STRING) {
int code = pl_match(&variants, key, key_len,
(const char *)val->data, val->data_len);
if (code < 0) {
fprintf(stderr, "error: value \"%.*s\" is not a valid "
"variant of type \"%.*s\"\n",
(int)val->data_len, val->data,
(int)key_len, key);
err = 1; break;
}
bw_write_ctrl(&w, (uint16_t)code);
continue;
}
}
bw_write_ctrl(&w, DTOB_CODE_RAW);
bw_write_data(&w, data_val->pairs[i].key, data_val->pairs[i].key_len);
if (encode_value(&w, val, &ctx) != 0) { err = 1; break; }
}
if (!err) bw_write_ctrl(&w, DTOB_CODE_KV_CLOSE);
} else {
err = encode_value(&w, data_val, &ctx);
bw_write_ctrl(&w, DTOB_CODE_ARR_CLOSE);
}
for (size_t i = 0; i < th.count; i++)
free(th.entries[i].name);
pl_free(&variants);
el_free(&el);
pl_free(&structs);
if (err) {
free(w.buf);
return 1;
}
*out = w.buf;
*out_len = w.pos;
return 0;
}
typedef struct {
char **keys;
uint16_t *prim_opcodes;
uint16_t *field_codes;
size_t n_keys;
uint16_t struct_code;
char *name;
int count;
} InferredStruct;
typedef struct {
InferredStruct *entries;
size_t count;
size_t cap;
} InferStructTable;
static void ist_init(InferStructTable *t)
{
t->cap = 8;
t->entries = malloc(t->cap * sizeof(InferredStruct));
t->count = 0;
}
static void ist_free(InferStructTable *t)
{
for (size_t i = 0; i < t->count; i++) {
for (size_t j = 0; j < t->entries[i].n_keys; j++)
free(t->entries[i].keys[j]);
free(t->entries[i].keys);
free(t->entries[i].prim_opcodes);
free(t->entries[i].field_codes);
free(t->entries[i].name);
}
free(t->entries);
}
static int cmp_str_ptr(const void *a, const void *b)
{
return strcmp(*(const char **)a, *(const char **)b);
}
static char **extract_sorted_keys(const DtobValue *kv, size_t *out_n)
{
*out_n = kv->num_pairs;
if (*out_n == 0) return NULL;
char **keys = malloc(*out_n * sizeof(char *));
for (size_t i = 0; i < *out_n; i++) {
size_t len = kv->pairs[i].key_len;
keys[i] = malloc(len + 1);
memcpy(keys[i], kv->pairs[i].key, len);
keys[i][len] = '\0';
}
qsort(keys, *out_n, sizeof(char *), cmp_str_ptr);
return keys;
}
static int key_sets_equal(char **a, size_t na, char **b, size_t nb)
{
if (na != nb) return 0;
for (size_t i = 0; i < na; i++)
if (strcmp(a[i], b[i]) != 0) return 0;
return 1;
}
static uint16_t infer_field_opcode(DtobValue **elems, size_t n_elems,
const char *key, size_t key_len)
{
int all_str = 1, all_uint = 1, all_int = 1, all_float = 1;
int max_width = 1;
for (size_t i = 0; i < n_elems; i++) {
DtobValue *val = NULL;
for (size_t p = 0; p < elems[i]->num_pairs; p++) {
if (elems[i]->pairs[p].key_len == key_len &&
memcmp(elems[i]->pairs[p].key, key, key_len) == 0) {
val = elems[i]->pairs[p].value;
break;
}
}
if (!val) return 0;
int vt = (int)val->type;
if (vt == (int)DTOB_ARRAY || vt == (int)DTOB_KV_SET) return 0;
if (vt != JSON_STRING && vt != (int)DTOB_RAW) all_str = 0;
if (vt != (int)DTOB_INT) { all_int = 0; all_uint = 0; }
else {
if (val->data_len > 0 && (val->data[0] & 0x80)) all_uint = 0;
int w = round_up_width(val->data_len);
if (w > max_width) max_width = w;
}
if (vt != (int)DTOB_FLOAT) all_float = 0;
}
if (all_str) return DTOB_CODE_RAW;
if (all_float) return DTOB_CODE_DOUBLE;
if (all_uint) {
switch (max_width) {
case 1: return DTOB_CODE_UINT8;
case 2: return DTOB_CODE_UINT16;
case 4: return DTOB_CODE_UINT32;
default: return DTOB_CODE_UINT64;
}
}
if (all_int) {
switch (max_width) {
case 1: return DTOB_CODE_INT8;
case 2: return DTOB_CODE_INT16;
case 4: return DTOB_CODE_INT32;
default: return DTOB_CODE_INT64;
}
}
return 0;
}
static void ist_observe(InferStructTable *t, DtobValue **elems, size_t n_elems,
const char *parent_key)
{
for (size_t i = 0; i < n_elems; i++)
if (!elems[i] || elems[i]->type != DTOB_KV_SET) return;
size_t n_keys;
char **keys = extract_sorted_keys(elems[0], &n_keys);
if (!keys || n_keys == 0) { free(keys); return; }
for (size_t i = 1; i < n_elems; i++) {
size_t nk2;
char **k2 = extract_sorted_keys(elems[i], &nk2);
int eq = key_sets_equal(keys, n_keys, k2, nk2);
for (size_t j = 0; j < nk2; j++) free(k2[j]);
free(k2);
if (!eq) {
for (size_t j = 0; j < n_keys; j++) free(keys[j]);
free(keys);
return;
}
}
for (size_t i = 0; i < t->count; i++) {
if (key_sets_equal(t->entries[i].keys, t->entries[i].n_keys, keys, n_keys)) {
t->entries[i].count += (int)n_elems;
for (size_t j = 0; j < n_keys; j++) free(keys[j]);
free(keys);
return;
}
}
if (t->count >= t->cap) {
t->cap *= 2;
t->entries = realloc(t->entries, t->cap * sizeof(InferredStruct));
}
InferredStruct *s = &t->entries[t->count++];
s->keys = keys;
s->n_keys = n_keys;
s->count = (int)n_elems;
s->struct_code = 0;
s->name = strdup(parent_key ? parent_key : "record");
s->prim_opcodes = malloc(n_keys * sizeof(uint16_t));
s->field_codes = calloc(n_keys, sizeof(uint16_t));
for (size_t ki = 0; ki < n_keys; ki++) {
s->prim_opcodes[ki] = infer_field_opcode(elems, n_elems,
s->keys[ki], strlen(s->keys[ki]));
if (s->prim_opcodes[ki] == 0) {
for (size_t j = 0; j < n_keys; j++) free(s->keys[j]);
free(s->keys);
free(s->prim_opcodes);
free(s->field_codes);
free(s->name);
t->count--;
return;
}
}
}
static void scan_for_structs(const DtobValue *v, const char *parent_key,
InferStructTable *t)
{
if (!v) return;
if (v->type == DTOB_ARRAY) {
if (v->num_elements >= 3) {
int all_kv = 1;
for (size_t i = 0; i < v->num_elements; i++)
if (!v->elements[i] || v->elements[i]->type != DTOB_KV_SET)
{ all_kv = 0; break; }
if (all_kv)
ist_observe(t, v->elements, v->num_elements, parent_key);
}
for (size_t i = 0; i < v->num_elements; i++)
scan_for_structs(v->elements[i], NULL, t);
} else if (v->type == DTOB_KV_SET) {
for (size_t i = 0; i < v->num_pairs; i++) {
char key_z[256];
size_t klen = v->pairs[i].key_len < 255 ? v->pairs[i].key_len : 255;
memcpy(key_z, v->pairs[i].key, klen);
key_z[klen] = '\0';
scan_for_structs(v->pairs[i].value, key_z, t);
}
for (size_t i = 0; i < v->num_elements; i++)
scan_for_structs(v->elements[i], NULL, t);
}
}
static InferredStruct *ist_match_kv(const InferStructTable *t, const DtobValue *kv)
{
if (!kv || kv->type != DTOB_KV_SET || kv->num_pairs == 0) return NULL;
size_t n_keys;
char **keys = extract_sorted_keys(kv, &n_keys);
if (!keys) return NULL;
InferredStruct *result = NULL;
for (size_t i = 0; i < t->count; i++) {
if (t->entries[i].struct_code != 0 &&
key_sets_equal(t->entries[i].keys, t->entries[i].n_keys, keys, n_keys)) {
result = &t->entries[i];
break;
}
}
for (size_t i = 0; i < n_keys; i++) free(keys[i]);
free(keys);
return result;
}
static void encode_with_inferred(ByteWriter *w, const DtobValue *v,
const InferStructTable *t);
static void encode_struct_instance(ByteWriter *w, const DtobValue *kv,
const InferredStruct *s)
{
bw_write_ctrl(w, s->struct_code);
bw_write_ctrl(w, DTOB_CODE_OPEN);
for (size_t ki = 0; ki < s->n_keys; ki++) {
size_t klen = strlen(s->keys[ki]);
for (size_t p = 0; p < kv->num_pairs; p++) {
if (kv->pairs[p].key_len == klen &&
memcmp(kv->pairs[p].key, s->keys[ki], klen) == 0) {
bw_write_ctrl(w, s->field_codes[ki]);
write_opcode_data(w, kv->pairs[p].value, s->prim_opcodes[ki]);
break;
}
}
}
bw_write_ctrl(w, DTOB_CODE_ARR_CLOSE);
}
static void encode_with_inferred(ByteWriter *w, const DtobValue *v,
const InferStructTable *t)
{
if (!v) return;
if (v->type == DTOB_KV_SET) {
InferredStruct *s = ist_match_kv(t, v);
if (s) { encode_struct_instance(w, v, s); return; }
bw_write_ctrl(w, DTOB_CODE_OPEN);
for (size_t i = 0; i < v->num_pairs; i++) {
bw_write_ctrl(w, DTOB_CODE_RAW);
bw_write_data(w, v->pairs[i].key, v->pairs[i].key_len);
encode_with_inferred(w, v->pairs[i].value, t);
}
bw_write_ctrl(w, DTOB_CODE_KV_CLOSE);
} else if (v->type == DTOB_ARRAY) {
bw_write_ctrl(w, DTOB_CODE_OPEN);
for (size_t i = 0; i < v->num_elements; i++)
encode_with_inferred(w, v->elements[i], t);
bw_write_ctrl(w, DTOB_CODE_ARR_CLOSE);
} else {
dtob_writer_value((DtobWriter *)w, v);
}
}
int infer_typed_encode(const DtobValue *root, uint8_t **out, size_t *out_len)
{
InferStructTable t;
ist_init(&t);
scan_for_structs(root, NULL, &t);
int has_string = 0, has_true = 0, has_false = 0, has_null = 0;
scan_conventions(root, &has_string, &has_true, &has_false, &has_null);
uint16_t next_code = DTOB_CUSTOM_MIN;
for (size_t i = 0; i < t.count; i++) {
InferredStruct *s = &t.entries[i];
if (s->count < 3) continue;
if (next_code + s->n_keys > DTOB_CUSTOM_MAX) {
fprintf(stderr, "warning: too many inferred types, some skipped\n");
break;
}
for (size_t ki = 0; ki < s->n_keys; ki++)
s->field_codes[ki] = next_code++;
s->struct_code = next_code++;
}
int has_structs = (next_code > DTOB_CUSTOM_MIN);
if (!has_string && !has_true && !has_false && !has_null && !has_structs) {
*out = dtob_encode(root, out_len);
ist_free(&t);
return 0;
}
ByteWriter w;
bw_init(&w, 0);
bw_write_magic(&w);
bw_write_ctrl(&w, DTOB_CODE_OPEN);
int has[] = { has_string, has_true, has_false, has_null };
bw_write_ctrl(&w, DTOB_CODE_OPEN);
bw_emit_conv_types(&w, has);
for (size_t i = 0; i < t.count; i++) {
InferredStruct *s = &t.entries[i];
if (s->struct_code == 0) continue;
for (size_t ki = 0; ki < s->n_keys; ki++)
bw_emit_type_entry(&w, s->field_codes[ki], s->keys[ki],
&s->prim_opcodes[ki], 1, 0);
bw_emit_type_entry(&w, s->struct_code, s->name,
s->field_codes, s->n_keys, 1);
}
bw_write_ctrl(&w, DTOB_CODE_TYPES_CLOSE);
if (root->type == DTOB_ARRAY) {
for (size_t i = 0; i < root->num_elements; i++)
encode_with_inferred(&w, root->elements[i], &t);
bw_write_ctrl(&w, DTOB_CODE_ARR_CLOSE);
} else if (root->type == DTOB_KV_SET) {
for (size_t i = 0; i < root->num_pairs; i++) {
bw_write_ctrl(&w, DTOB_CODE_RAW);
bw_write_data(&w, root->pairs[i].key, root->pairs[i].key_len);
encode_with_inferred(&w, root->pairs[i].value, &t);
}
for (size_t i = 0; i < root->num_elements; i++)
encode_with_inferred(&w, root->elements[i], &t);
bw_write_ctrl(&w, DTOB_CODE_KV_CLOSE);
} else {
encode_with_inferred(&w, root, &t);
bw_write_ctrl(&w, DTOB_CODE_ARR_CLOSE);
}
ist_free(&t);
*out = w.buf;
*out_len = w.pos;
return 0;
}
static const struct { const char *tag; uint16_t code; } html_tag_table[] = {
{"html",HTML_CODE_HTML},{"head",HTML_CODE_HEAD},{"body",HTML_CODE_BODY},
{"title",HTML_CODE_TITLE},{"base",HTML_CODE_BASE},{"isindex",HTML_CODE_ISINDEX},
{"link",HTML_CODE_LINK},{"nextid",HTML_CODE_NEXTID},
{"h1",HTML_CODE_H1},{"h2",HTML_CODE_H2},{"h3",HTML_CODE_H3},
{"h4",HTML_CODE_H4},{"h5",HTML_CODE_H5},{"h6",HTML_CODE_H6},
{"p",HTML_CODE_P},{"address",HTML_CODE_ADDRESS},
{"blockquote",HTML_CODE_BLOCKQUOTE},{"pre",HTML_CODE_PRE},
{"ul",HTML_CODE_UL},{"ol",HTML_CODE_OL},{"li",HTML_CODE_LI},
{"dl",HTML_CODE_DL},{"dt",HTML_CODE_DT},{"dd",HTML_CODE_DD},
{"dir",HTML_CODE_DIR},{"menu",HTML_CODE_MENU},
{"a",HTML_CODE_A},{"img",HTML_CODE_IMG},
{"em",HTML_CODE_EM},{"strong",HTML_CODE_STRONG},{"code",HTML_CODE_CODE},
{"samp",HTML_CODE_SAMP},{"kbd",HTML_CODE_KBD},{"var",HTML_CODE_VAR},
{"dfn",HTML_CODE_DFN},{"cite",HTML_CODE_CITE},
{"tt",HTML_CODE_TT},{"b",HTML_CODE_B},{"i",HTML_CODE_I},{"u",HTML_CODE_U},
{"plaintext",HTML_CODE_PLAINTEXT},{"xmp",HTML_CODE_XMP},
{"listing",HTML_CODE_LISTING},
{"meta",HTML_CODE_META},{"br",HTML_CODE_BR},{"hr",HTML_CODE_HR},
{"form",HTML_CODE_FORM},{"input",HTML_CODE_INPUT},{"select",HTML_CODE_SELECT},
{"option",HTML_CODE_OPTION},{"textarea",HTML_CODE_TEXTAREA},
{"script",HTML_CODE_SCRIPT},{"style",HTML_CODE_STYLE},
{"div",HTML_CODE_DIV},{"center",HTML_CODE_CENTER},
{"table",HTML_CODE_TABLE},{"caption",HTML_CODE_CAPTION},
{"tr",HTML_CODE_TR},{"th",HTML_CODE_TH},{"td",HTML_CODE_TD},
{"applet",HTML_CODE_APPLET},{"param",HTML_CODE_PARAM},
{"map",HTML_CODE_MAP},{"area",HTML_CODE_AREA},
{"font",HTML_CODE_FONT},{"basefont",HTML_CODE_BASEFONT},
{"big",HTML_CODE_BIG},{"small",HTML_CODE_SMALL},
{"strike",HTML_CODE_STRIKE},{"sub",HTML_CODE_SUB},{"sup",HTML_CODE_SUP},
{"abbr",HTML_CODE_ABBR},{"acronym",HTML_CODE_ACRONYM},
{"bdo",HTML_CODE_BDO},{"q",HTML_CODE_Q},{"s",HTML_CODE_S},
{"span",HTML_CODE_SPAN},{"button",HTML_CODE_BUTTON},
{"fieldset",HTML_CODE_FIELDSET},{"label",HTML_CODE_LABEL},
{"legend",HTML_CODE_LEGEND},{"optgroup",HTML_CODE_OPTGROUP},
{"col",HTML_CODE_COL},{"colgroup",HTML_CODE_COLGROUP},
{"tbody",HTML_CODE_TBODY},{"tfoot",HTML_CODE_TFOOT},{"thead",HTML_CODE_THEAD},
{"object",HTML_CODE_OBJECT},{"iframe",HTML_CODE_IFRAME},
{"frame",HTML_CODE_FRAME},{"frameset",HTML_CODE_FRAMESET},
{"noframes",HTML_CODE_NOFRAMES},{"del",HTML_CODE_DEL},{"ins",HTML_CODE_INS},
{"noscript",HTML_CODE_NOSCRIPT},
{"article",HTML_CODE_ARTICLE},{"aside",HTML_CODE_ASIDE},
{"footer",HTML_CODE_FOOTER},{"header",HTML_CODE_HEADER},
{"hgroup",HTML_CODE_HGROUP},{"main",HTML_CODE_MAIN},
{"nav",HTML_CODE_NAV},{"section",HTML_CODE_SECTION},
{"search",HTML_CODE_SEARCH},
{"bdi",HTML_CODE_BDI},{"data",HTML_CODE_DATA},{"mark",HTML_CODE_MARK},
{"rp",HTML_CODE_RP},{"rt",HTML_CODE_RT},{"ruby",HTML_CODE_RUBY},
{"time",HTML_CODE_TIME},{"wbr",HTML_CODE_WBR},
{"audio",HTML_CODE_AUDIO},{"video",HTML_CODE_VIDEO},
{"source",HTML_CODE_SOURCE},{"track",HTML_CODE_TRACK},
{"embed",HTML_CODE_EMBED},{"picture",HTML_CODE_PICTURE},
{"details",HTML_CODE_DETAILS},{"dialog",HTML_CODE_DIALOG},
{"summary",HTML_CODE_SUMMARY},
{"datalist",HTML_CODE_DATALIST},{"meter",HTML_CODE_METER},
{"output",HTML_CODE_OUTPUT},{"progress",HTML_CODE_PROGRESS},
{"figcaption",HTML_CODE_FIGCAPTION},{"figure",HTML_CODE_FIGURE},
{"canvas",HTML_CODE_CANVAS},{"slot",HTML_CODE_SLOT},
{"template",HTML_CODE_TEMPLATE},
{"selectedcontent",HTML_CODE_SELECTEDCONTENT},
};
#define HTML_TAG_COUNT (sizeof(html_tag_table) / sizeof(html_tag_table[0]))
static int html_tag_code(const char *tag, size_t len)
{
for (size_t i = 0; i < HTML_TAG_COUNT; i++) {
if (strlen(html_tag_table[i].tag) == len &&
strncasecmp(html_tag_table[i].tag, tag, len) == 0)
return (int)html_tag_table[i].code;
}
return -1;
}
static const char *html_code_name(uint16_t code)
{
for (size_t i = 0; i < HTML_TAG_COUNT; i++)
if (html_tag_table[i].code == code)
return html_tag_table[i].tag;
return NULL;
}
static const DtobValue *kvset_get(const DtobValue *kv, const char *key)
{
if (!kv) return NULL;
size_t klen = strlen(key);
for (size_t i = 0; i < kv->num_pairs; i++)
if (kv->pairs[i].key_len == klen &&
memcmp(kv->pairs[i].key, key, klen) == 0)
return kv->pairs[i].value;
return NULL;
}
static void scan_html_tags(const DtobValue *v, int *used, int *has_text)
{
if (!v) return;
if (v->type == DTOB_KV_SET) {
const DtobValue *tag = kvset_get(v, "t");
if (tag && (tag->type == JSON_STRING || tag->type == DTOB_RAW) &&
tag->data && tag->data_len > 0) {
int code = html_tag_code((const char *)tag->data, tag->data_len);
if (code >= 0) used[code] = 1;
}
const DtobValue *attrs = kvset_get(v, "a");
if (attrs) scan_html_tags(attrs, used, has_text);
const DtobValue *children = kvset_get(v, "c");
if (children) scan_html_tags(children, used, has_text);
for (size_t i = 0; i < v->num_pairs; i++)
scan_html_tags(v->pairs[i].value, used, has_text);
} else if (v->type == DTOB_ARRAY) {
for (size_t i = 0; i < v->num_elements; i++)
scan_html_tags(v->elements[i], used, has_text);
} else if (v->type == JSON_STRING || v->type == DTOB_RAW) {
*has_text = 1;
}
}
static void html_encode_node(ByteWriter *w, const DtobValue *v);
static void html_encode_element(ByteWriter *w, const DtobValue *elem)
{
const DtobValue *tag_val = kvset_get(elem, "t");
const DtobValue *attrs = kvset_get(elem, "a");
const DtobValue *children = kvset_get(elem, "c");
bw_write_ctrl(w, DTOB_CODE_OPEN);
int code = -1;
if (tag_val && tag_val->data && tag_val->data_len > 0)
code = html_tag_code((const char *)tag_val->data, tag_val->data_len);
if (code >= 0) {
bw_write_ctrl(w, (uint16_t)code);
} else if (tag_val) {
bw_write_ctrl(w, DTOB_CODE_RAW);
bw_write_data(w, (const uint8_t *)"t", 1);
bw_write_ctrl(w, HTML_CODE_STRING);
bw_write_data(w, tag_val->data, tag_val->data_len);
}
if (attrs && attrs->type == DTOB_KV_SET && attrs->num_pairs > 0) {
bw_write_ctrl(w, DTOB_CODE_RAW);
bw_write_data(w, (const uint8_t *)"a", 1);
bw_write_ctrl(w, DTOB_CODE_OPEN);
for (size_t i = 0; i < attrs->num_pairs; i++) {
const DtobValue *val = attrs->pairs[i].value;
bw_write_ctrl(w, DTOB_CODE_RAW);
bw_write_data(w, attrs->pairs[i].key, attrs->pairs[i].key_len);
if (val->type == DTOB_RAW) {
bw_write_ctrl(w, DTOB_CODE_RAW);
bw_write_data(w, val->data, val->data_len);
} else if (val->type == DTOB_ARRAY) {
bw_write_ctrl(w, DTOB_CODE_OPEN);
for (size_t j = 0; j < val->num_elements; j++) {
const DtobValue *entry = val->elements[j];
if (!entry || entry->type != DTOB_KV_SET) continue;
bw_write_ctrl(w, DTOB_CODE_OPEN);
for (size_t k = 0; k < entry->num_pairs; k++) {
const DtobValue *ev = entry->pairs[k].value;
bw_write_ctrl(w, DTOB_CODE_RAW);
bw_write_data(w, entry->pairs[k].key, entry->pairs[k].key_len);
if (ev->type == DTOB_RAW) {
bw_write_ctrl(w, DTOB_CODE_RAW);
bw_write_data(w, ev->data, ev->data_len);
} else {
bw_write_ctrl(w, HTML_CODE_STRING);
bw_write_data(w, ev->data, ev->data_len);
}
}
bw_write_ctrl(w, DTOB_CODE_KV_CLOSE);
}
bw_write_ctrl(w, DTOB_CODE_ARR_CLOSE);
} else {
bw_write_ctrl(w, HTML_CODE_STRING);
bw_write_data(w, val->data, val->data_len);
}
}
bw_write_ctrl(w, DTOB_CODE_KV_CLOSE);
}
if (children && children->type == DTOB_ARRAY && children->num_elements > 0) {
bw_write_ctrl(w, DTOB_CODE_RAW);
bw_write_data(w, (const uint8_t *)"c", 1);
bw_write_ctrl(w, DTOB_CODE_OPEN);
for (size_t i = 0; i < children->num_elements; i++)
html_encode_node(w, children->elements[i]);
bw_write_ctrl(w, DTOB_CODE_ARR_CLOSE);
}
bw_write_ctrl(w, DTOB_CODE_KV_CLOSE);
}
static void html_encode_node(ByteWriter *w, const DtobValue *v)
{
if (!v) return;
if (v->type == DTOB_KV_SET && kvset_get(v, "t")) {
html_encode_element(w, v);
} else if (v->type == JSON_STRING || v->type == DTOB_RAW) {
bw_write_ctrl(w, HTML_CODE_STRING);
bw_write_data(w, v->data, v->data_len);
} else {
dtob_writer_value((DtobWriter *)w, v);
}
}
int html_typed_encode(const DtobValue *root, uint8_t **out, size_t *out_len)
{
int used[HTML_CODE_SELECTEDCONTENT + 1];
memset(used, 0, sizeof(used));
int has_text = 0;
scan_html_tags(root, used, &has_text);
ByteWriter w;
bw_init(&w, 0);
bw_write_magic(&w);
bw_write_ctrl(&w, DTOB_CODE_OPEN);
bw_write_ctrl(&w, DTOB_CODE_OPEN);
if (has_text) {
uint16_t raw_op = DTOB_CODE_RAW;
bw_emit_type_entry(&w, HTML_CODE_STRING, "string", &raw_op, 1, 0);
}
for (int c = DTOB_CUSTOM_MIN; c <= HTML_CODE_SELECTEDCONTENT; c++) {
if (!used[c]) continue;
const char *name = html_code_name((uint16_t)c);
if (!name) continue;
bw_emit_type_entry(&w, (uint16_t)c, name, NULL, 0, 0);
}
bw_write_ctrl(&w, DTOB_CODE_TYPES_CLOSE);
html_encode_node(&w, root);
bw_write_ctrl(&w, DTOB_CODE_ARR_CLOSE);
*out = w.buf;
*out_len = w.pos;
return 0;
}
int html_raw_encode(const char *html, size_t html_len,
const ResourceList *rl,
uint8_t **out, size_t *out_len)
{
ByteWriter w;
bw_init(&w, 0);
bw_write_magic(&w);
bw_write_ctrl(&w, DTOB_CODE_OPEN);
bw_write_ctrl(&w, DTOB_CODE_RAW);
bw_write_data(&w, (const uint8_t *)html, html_len);
if (rl) {
for (size_t i = 0; i < rl->count; i++) {
bw_write_ctrl(&w, DTOB_CODE_RAW);
bw_write_data(&w, rl->entries[i].data, rl->entries[i].len);
}
}
bw_write_ctrl(&w, DTOB_CODE_ARR_CLOSE);
*out = w.buf;
*out_len = w.pos;
return 0;
}