Branch data Line data Source code
1 : : /**
2 : : * @file json_encode.c
3 : : * @brief Encode a JSON string into a compressed .trp buffer.
4 : : *
5 : : * Minimal recursive-descent parser that flattens JSON into dot-path keys.
6 : : * Objects: {"a":{"b":1}} -> key "a.b", value int(1)
7 : : * Arrays: {"x":[10,20]} -> keys "x[0]", "x[1]"
8 : : *
9 : : * Copyright (c) 2026 M. A. Chatterjee <deftio at deftio dot com>
10 : : * BSD-2-Clause — see LICENSE.txt
11 : : */
12 : :
13 : : #include "json_internal.h"
14 : :
15 : : #include <ctype.h>
16 : : #include <math.h>
17 : : #include <stdio.h>
18 : : #include <stdlib.h>
19 : : #include <string.h>
20 : :
21 : : /* ── Parser state ────────────────────────────────────────────────────── */
22 : :
23 : : typedef struct {
24 : : const char *src;
25 : : size_t len;
26 : : size_t pos;
27 : : int depth;
28 : : tp_encoder *enc;
29 : : char path[4096]; /* current dot-path buffer */
30 : : size_t path_len;
31 : : } json_parser;
32 : :
33 : : /* ── Whitespace / peek helpers ───────────────────────────────────────── */
34 : :
35 : 2037 : static void skip_ws(json_parser *p)
36 : : {
37 [ + + ]: 2051 : while (p->pos < p->len) {
38 : 2042 : char c = p->src[p->pos];
39 [ + + + - : 2042 : if (c == ' ' || c == '\t' || c == '\n' || c == '\r')
+ - - + ]
40 : 14 : p->pos++;
41 : : else
42 : : break;
43 : : }
44 : 2037 : }
45 : :
46 : 420 : static char peek(json_parser *p)
47 : : {
48 : 420 : skip_ws(p);
49 [ + + ]: 420 : if (p->pos >= p->len)
50 : 2 : return '\0';
51 : 418 : return p->src[p->pos];
52 : : }
53 : :
54 : 784 : static bool expect(json_parser *p, char c)
55 : : {
56 : 784 : skip_ws(p);
57 [ + + + + ]: 784 : if (p->pos < p->len && p->src[p->pos] == c) {
58 : 780 : p->pos++;
59 : 780 : return true;
60 : : }
61 : 4 : return false;
62 : : }
63 : :
64 : : /* ── Path manipulation ───────────────────────────────────────────────── */
65 : :
66 : 206 : static void path_push_key(json_parser *p, const char *key, size_t key_len, size_t *saved_len)
67 : : {
68 : 206 : *saved_len = p->path_len;
69 [ + + ]: 206 : if (p->path_len > 0) {
70 : 72 : p->path[p->path_len++] = '.';
71 : : }
72 [ + - ]: 206 : if (p->path_len + key_len < sizeof(p->path)) {
73 : 206 : memcpy(p->path + p->path_len, key, key_len);
74 : 206 : p->path_len += key_len;
75 : : }
76 : 206 : p->path[p->path_len] = '\0';
77 : 206 : }
78 : :
79 : 100 : static void path_push_index(json_parser *p, uint32_t idx, size_t *saved_len)
80 : : {
81 : 100 : *saved_len = p->path_len;
82 : : char tmp[16];
83 : 100 : int n = snprintf(tmp, sizeof(tmp), "[%u]", (unsigned)idx);
84 [ + - + - ]: 100 : if (n > 0 && p->path_len + (size_t)n < sizeof(p->path)) {
85 : 100 : memcpy(p->path + p->path_len, tmp, (size_t)n);
86 : 100 : p->path_len += (size_t)n;
87 : : }
88 : 100 : p->path[p->path_len] = '\0';
89 : 100 : }
90 : :
91 : 227 : static void path_restore(json_parser *p, size_t saved_len)
92 : : {
93 : 227 : p->path_len = saved_len;
94 : 227 : p->path[p->path_len] = '\0';
95 : 227 : }
96 : :
97 : : /* ── Forward declaration ─────────────────────────────────────────────── */
98 : :
99 : : static tp_result parse_value(json_parser *p);
100 : :
101 : : /* ── String parsing ──────────────────────────────────────────────────── */
102 : :
103 : 261 : static tp_result parse_string_into(json_parser *p, char *out, size_t out_cap, size_t *out_len)
104 : : {
105 [ + + ]: 261 : if (!expect(p, '"'))
106 : 1 : return TP_ERR_JSON_SYNTAX;
107 : :
108 : 260 : size_t w = 0;
109 [ + + ]: 1193 : while (p->pos < p->len) {
110 : 1192 : char c = p->src[p->pos++];
111 [ + + ]: 1192 : if (c == '"') {
112 [ + - + - ]: 251 : if (out && w < out_cap)
113 : 251 : out[w] = '\0';
114 : 251 : *out_len = w;
115 : 251 : return TP_OK;
116 : : }
117 [ + + ]: 941 : if (c == '\\') {
118 [ + + ]: 29 : if (p->pos >= p->len)
119 : 1 : return TP_ERR_JSON_SYNTAX;
120 : 28 : char esc = p->src[p->pos++];
121 [ + + + + : 28 : switch (esc) {
+ + + + ]
122 : 4 : case '"':
123 : : case '\\':
124 : : case '/':
125 : 4 : c = esc;
126 : 4 : break;
127 : 2 : case 'b':
128 : 2 : c = '\b';
129 : 2 : break;
130 : 2 : case 'f':
131 : 2 : c = '\f';
132 : 2 : break;
133 : 2 : case 'n':
134 : 2 : c = '\n';
135 : 2 : break;
136 : 2 : case 'r':
137 : 2 : c = '\r';
138 : 2 : break;
139 : 2 : case 't':
140 : 2 : c = '\t';
141 : 2 : break;
142 : 20 : case 'u': {
143 : : /* Parse 4 hex digits -> UTF-8 */
144 [ + + ]: 13 : if (p->pos + 4 > p->len)
145 : 1 : return TP_ERR_JSON_SYNTAX;
146 : 12 : uint32_t cp = 0;
147 [ + + ]: 56 : for (int i = 0; i < 4; i++) {
148 : 46 : char h = p->src[p->pos++];
149 : 46 : cp <<= 4;
150 [ + + + + ]: 46 : if (h >= '0' && h <= '9')
151 : 32 : cp |= (uint32_t)(h - '0');
152 [ + + + + ]: 14 : else if (h >= 'a' && h <= 'f')
153 : 4 : cp |= (uint32_t)(h - 'a' + 10);
154 [ + + + + ]: 10 : else if (h >= 'A' && h <= 'F')
155 : 8 : cp |= (uint32_t)(h - 'A' + 10);
156 : : else
157 : 2 : return TP_ERR_JSON_SYNTAX;
158 : : }
159 : : /* Handle surrogate pairs */
160 [ + + + - ]: 10 : if (cp >= 0xD800 && cp <= 0xDBFF) {
161 [ + + + - : 5 : if (p->pos + 6 > p->len || p->src[p->pos] != '\\' || p->src[p->pos + 1] != 'u')
- + ]
162 : 1 : return TP_ERR_JSON_SYNTAX;
163 : 4 : p->pos += 2;
164 : 4 : uint32_t lo = 0;
165 [ + + ]: 17 : for (int i = 0; i < 4; i++) {
166 : 14 : char h = p->src[p->pos++];
167 : 14 : lo <<= 4;
168 [ + - + + ]: 14 : if (h >= '0' && h <= '9')
169 : 8 : lo |= (uint32_t)(h - '0');
170 [ + + + + ]: 6 : else if (h >= 'a' && h <= 'f')
171 : 2 : lo |= (uint32_t)(h - 'a' + 10);
172 [ + - + + ]: 4 : else if (h >= 'A' && h <= 'F')
173 : 3 : lo |= (uint32_t)(h - 'A' + 10);
174 : : else
175 : 1 : return TP_ERR_JSON_SYNTAX;
176 : : }
177 [ + + - + ]: 3 : if (lo < 0xDC00 || lo > 0xDFFF)
178 : 1 : return TP_ERR_JSON_SYNTAX;
179 : 2 : cp = 0x10000 + ((cp - 0xD800) << 10) + (lo - 0xDC00);
180 : : }
181 : : /* Encode codepoint as UTF-8 */
182 [ + + ]: 7 : if (cp < 0x80) {
183 [ + - + - ]: 1 : if (out && w < out_cap)
184 : 1 : out[w] = (char)cp;
185 : 1 : w++;
186 [ + + ]: 6 : } else if (cp < 0x800) {
187 [ + - + - ]: 3 : if (out && w + 1 < out_cap) {
188 : 3 : out[w] = (char)(0xC0 | (cp >> 6));
189 : 3 : out[w + 1] = (char)(0x80 | (cp & 0x3F));
190 : : }
191 : 3 : w += 2;
192 [ + + ]: 3 : } else if (cp < 0x10000) {
193 [ + - + - ]: 1 : if (out && w + 2 < out_cap) {
194 : 1 : out[w] = (char)(0xE0 | (cp >> 12));
195 : 1 : out[w + 1] = (char)(0x80 | ((cp >> 6) & 0x3F));
196 : 1 : out[w + 2] = (char)(0x80 | (cp & 0x3F));
197 : : }
198 : 1 : w += 3;
199 [ + - ]: 2 : } else if (cp < 0x110000) {
200 [ + - + - ]: 2 : if (out && w + 3 < out_cap) {
201 : 2 : out[w] = (char)(0xF0 | (cp >> 18));
202 : 2 : out[w + 1] = (char)(0x80 | ((cp >> 12) & 0x3F));
203 : 2 : out[w + 2] = (char)(0x80 | ((cp >> 6) & 0x3F));
204 : 2 : out[w + 3] = (char)(0x80 | (cp & 0x3F));
205 : : }
206 : 2 : w += 4;
207 : : }
208 : 7 : continue; /* already handled, skip the default append */
209 : : }
210 : 1 : default:
211 : 1 : return TP_ERR_JSON_SYNTAX;
212 : : }
213 : : }
214 [ + - + - ]: 926 : if (out && w < out_cap)
215 : 926 : out[w] = c;
216 : 926 : w++;
217 : : }
218 : 1 : return TP_ERR_JSON_SYNTAX; /* unterminated string */
219 : : }
220 : :
221 : : /* ── Number parsing ──────────────────────────────────────────────────── */
222 : :
223 : 124 : static tp_result parse_number(json_parser *p, tp_value *val)
224 : : {
225 : 124 : size_t start = p->pos;
226 : 124 : bool is_float = false;
227 : 124 : bool is_neg = false;
228 : :
229 [ + - + + ]: 124 : if (p->pos < p->len && p->src[p->pos] == '-') {
230 : 4 : is_neg = true;
231 : 4 : p->pos++;
232 : : }
233 : :
234 : : /* Integer part */
235 [ + - + + ]: 124 : if (p->pos >= p->len || !isdigit((unsigned char)p->src[p->pos]))
236 : 1 : return TP_ERR_JSON_SYNTAX;
237 [ + + + + ]: 411 : while (p->pos < p->len && isdigit((unsigned char)p->src[p->pos]))
238 : 288 : p->pos++;
239 : :
240 : : /* Fractional part */
241 [ + + + + ]: 123 : if (p->pos < p->len && p->src[p->pos] == '.') {
242 : 12 : is_float = true;
243 : 12 : p->pos++;
244 [ + - + + ]: 12 : if (p->pos >= p->len || !isdigit((unsigned char)p->src[p->pos]))
245 : 1 : return TP_ERR_JSON_SYNTAX;
246 [ + - + + ]: 28 : while (p->pos < p->len && isdigit((unsigned char)p->src[p->pos]))
247 : 17 : p->pos++;
248 : : }
249 : :
250 : : /* Exponent part */
251 [ + + + + : 122 : if (p->pos < p->len && (p->src[p->pos] == 'e' || p->src[p->pos] == 'E')) {
+ + ]
252 : 5 : is_float = true;
253 : 5 : p->pos++;
254 [ + - + + : 5 : if (p->pos < p->len && (p->src[p->pos] == '+' || p->src[p->pos] == '-'))
+ + ]
255 : 3 : p->pos++;
256 [ + - + + ]: 5 : if (p->pos >= p->len || !isdigit((unsigned char)p->src[p->pos]))
257 : 2 : return TP_ERR_JSON_SYNTAX;
258 [ + - + + ]: 7 : while (p->pos < p->len && isdigit((unsigned char)p->src[p->pos]))
259 : 4 : p->pos++;
260 : : }
261 : :
262 : : /* Convert */
263 : 120 : size_t numlen = p->pos - start;
264 : : char tmp[64];
265 [ + + ]: 120 : if (numlen >= sizeof(tmp))
266 : 1 : numlen = sizeof(tmp) - 1;
267 : 120 : memcpy(tmp, p->src + start, numlen);
268 : 120 : tmp[numlen] = '\0';
269 : :
270 [ + + ]: 120 : if (is_float) {
271 : 13 : double d = strtod(tmp, NULL);
272 : 13 : *val = tp_value_float64(d);
273 [ + + ]: 107 : } else if (is_neg) {
274 : 3 : long long ll = strtoll(tmp, NULL, 10);
275 : 3 : *val = tp_value_int((int64_t)ll);
276 : : } else {
277 : 104 : unsigned long long ull = strtoull(tmp, NULL, 10);
278 [ + + ]: 104 : if (ull > (unsigned long long)INT64_MAX) {
279 : 2 : *val = tp_value_uint((uint64_t)ull);
280 : : } else {
281 : 102 : *val = tp_value_int((int64_t)ull);
282 : : }
283 : : }
284 : 120 : return TP_OK;
285 : : }
286 : :
287 : : /* ── Value parsing (recursive) ───────────────────────────────────────── */
288 : :
289 : 134 : static tp_result parse_object(json_parser *p)
290 : : {
291 : 134 : p->depth++;
292 [ + + ]: 134 : if (p->depth > TP_MAX_NESTING_DEPTH)
293 : 1 : return TP_ERR_JSON_DEPTH;
294 : :
295 [ - + ]: 133 : if (!expect(p, '{'))
296 : : return TP_ERR_JSON_SYNTAX; /* LCOV_EXCL_LINE */
297 : :
298 [ + + ]: 133 : if (peek(p) == '}') {
299 : 3 : p->pos++;
300 : 3 : p->depth--;
301 : 3 : return TP_OK;
302 : : }
303 : :
304 : 78 : while (true) {
305 : : /* Parse key */
306 : : char key[1024];
307 : 208 : size_t key_len = 0;
308 : 208 : tp_result rc = parse_string_into(p, key, sizeof(key) - 1, &key_len);
309 [ + + ]: 208 : if (rc != TP_OK)
310 : 50 : return rc;
311 : 207 : key[key_len] = '\0';
312 : :
313 : 207 : skip_ws(p);
314 [ + + ]: 207 : if (!expect(p, ':'))
315 : 1 : return TP_ERR_JSON_SYNTAX;
316 : :
317 : : /* Push key onto path */
318 : : size_t saved;
319 : 206 : path_push_key(p, key, key_len, &saved);
320 : :
321 : : /* Parse value */
322 : 206 : rc = parse_value(p);
323 [ + + ]: 206 : if (rc != TP_OK)
324 : 47 : return rc;
325 : :
326 : 159 : path_restore(p, saved);
327 : :
328 : 159 : skip_ws(p);
329 [ + + ]: 159 : if (peek(p) == '}') {
330 : 80 : p->pos++;
331 : 80 : break;
332 : : }
333 [ + + ]: 79 : if (!expect(p, ','))
334 : 1 : return TP_ERR_JSON_SYNTAX;
335 : : }
336 : :
337 : 80 : p->depth--;
338 : 80 : return TP_OK;
339 : : }
340 : :
341 : 61 : static tp_result parse_array(json_parser *p)
342 : : {
343 : 61 : p->depth++;
344 [ + + ]: 61 : if (p->depth > TP_MAX_NESTING_DEPTH)
345 : 1 : return TP_ERR_JSON_DEPTH;
346 : :
347 [ - + ]: 60 : if (!expect(p, '['))
348 : : return TP_ERR_JSON_SYNTAX; /* LCOV_EXCL_LINE */
349 : :
350 [ + + ]: 60 : if (peek(p) == ']') {
351 : 3 : p->pos++;
352 : 3 : p->depth--;
353 : 3 : return TP_OK;
354 : : }
355 : :
356 : 57 : uint32_t idx = 0;
357 : 43 : while (true) {
358 : : size_t saved;
359 : 100 : path_push_index(p, idx, &saved);
360 : :
361 : 100 : tp_result rc = parse_value(p);
362 [ + + ]: 100 : if (rc != TP_OK)
363 : 33 : return rc;
364 : :
365 : 68 : path_restore(p, saved);
366 : 68 : idx++;
367 : :
368 : 68 : skip_ws(p);
369 [ + + ]: 68 : if (peek(p) == ']') {
370 : 24 : p->pos++;
371 : 24 : break;
372 : : }
373 [ + + ]: 44 : if (!expect(p, ','))
374 : 1 : return TP_ERR_JSON_SYNTAX;
375 : : }
376 : :
377 : 24 : p->depth--;
378 : 24 : return TP_OK;
379 : : }
380 : :
381 : 306 : static tp_result parse_value(json_parser *p)
382 : : {
383 : 306 : skip_ws(p);
384 [ + + ]: 306 : if (p->pos >= p->len)
385 : 1 : return TP_ERR_JSON_SYNTAX;
386 : :
387 : 305 : char c = p->src[p->pos];
388 : :
389 [ + + ]: 305 : if (c == '{')
390 : 52 : return parse_object(p);
391 : :
392 [ + + ]: 253 : if (c == '[')
393 : 53 : return parse_array(p);
394 : :
395 [ + + ]: 200 : if (c == '"') {
396 : : /* String value — parse then store at current path */
397 : : char str[4096];
398 : 53 : size_t slen = 0;
399 : 53 : tp_result rc = parse_string_into(p, str, sizeof(str) - 1, &slen);
400 [ + + ]: 53 : if (rc != TP_OK)
401 : 9 : return rc;
402 : 44 : str[slen] = '\0';
403 : 44 : tp_value val = tp_value_string_n(str, slen);
404 : 44 : return tp_encoder_add_n(p->enc, p->path, p->path_len, &val);
405 : : }
406 : :
407 [ + + + + ]: 147 : if (c == '-' || isdigit((unsigned char)c)) {
408 : : tp_value val;
409 : 124 : tp_result rc = parse_number(p, &val);
410 [ + + ]: 124 : if (rc != TP_OK)
411 : 4 : return rc;
412 : 120 : return tp_encoder_add_n(p->enc, p->path, p->path_len, &val);
413 : : }
414 : :
415 [ + + + + ]: 23 : if (p->pos + 4 <= p->len && memcmp(p->src + p->pos, "true", 4) == 0) {
416 : 10 : p->pos += 4;
417 : 10 : tp_value val = tp_value_bool(true);
418 : 10 : return tp_encoder_add_n(p->enc, p->path, p->path_len, &val);
419 : : }
420 : :
421 [ + + + + ]: 13 : if (p->pos + 5 <= p->len && memcmp(p->src + p->pos, "false", 5) == 0) {
422 : 6 : p->pos += 5;
423 : 6 : tp_value val = tp_value_bool(false);
424 : 6 : return tp_encoder_add_n(p->enc, p->path, p->path_len, &val);
425 : : }
426 : :
427 [ + + + - ]: 7 : if (p->pos + 4 <= p->len && memcmp(p->src + p->pos, "null", 4) == 0) {
428 : 6 : p->pos += 4;
429 : 6 : tp_value val = tp_value_null();
430 : 6 : return tp_encoder_add_n(p->enc, p->path, p->path_len, &val);
431 : : }
432 : :
433 : 1 : return TP_ERR_JSON_SYNTAX;
434 : : }
435 : :
436 : : /* ── One-shot encode ─────────────────────────────────────────────────── */
437 : :
438 : 96 : tp_result tp_json_encode(const char *json_str, size_t json_len, uint8_t **buf, size_t *buf_len)
439 : : {
440 [ + + + + : 96 : if (!json_str || !buf || !buf_len)
+ + ]
441 : 3 : return TP_ERR_INVALID_PARAM;
442 : :
443 : : /* Allocation failure paths are excluded from coverage (LCOV_EXCL). */
444 : 93 : tp_encoder *enc = NULL;
445 : 93 : tp_result rc = tp_encoder_create(&enc);
446 [ - + ]: 93 : if (rc != TP_OK)
447 : : return rc; /* LCOV_EXCL_LINE */
448 : :
449 : : json_parser parser;
450 : 93 : memset(&parser, 0, sizeof(parser));
451 : 93 : parser.src = json_str;
452 : 93 : parser.len = json_len;
453 : 93 : parser.pos = 0;
454 : 93 : parser.depth = 0;
455 : 93 : parser.enc = enc;
456 : 93 : parser.path_len = 0;
457 : 93 : parser.path[0] = '\0';
458 : :
459 : : /* Determine root type and parse */
460 : 93 : skip_ws(&parser);
461 [ + + ]: 93 : if (parser.pos >= parser.len) {
462 : 2 : tp_encoder_destroy(&enc);
463 : 2 : return TP_ERR_JSON_SYNTAX;
464 : : }
465 : :
466 : : uint32_t root_type;
467 : 91 : char root_c = parser.src[parser.pos];
468 [ + + ]: 91 : if (root_c == '{') {
469 : 82 : root_type = TP_JSON_ROOT_OBJECT;
470 : 82 : rc = parse_object(&parser);
471 [ + + ]: 9 : } else if (root_c == '[') {
472 : 8 : root_type = TP_JSON_ROOT_ARRAY;
473 : 8 : rc = parse_array(&parser);
474 : : } else {
475 : 1 : tp_encoder_destroy(&enc);
476 : 1 : return TP_ERR_JSON_SYNTAX;
477 : : }
478 : :
479 [ + + ]: 90 : if (rc != TP_OK) {
480 : : /* LCOV_EXCL_START */
481 : : tp_encoder_destroy(&enc);
482 : : return rc;
483 : : /* LCOV_EXCL_STOP */
484 : : }
485 : :
486 : : /* Store root type metadata */
487 : 69 : tp_value root_val = tp_value_uint(root_type);
488 : 69 : rc = tp_encoder_add(enc, TP_JSON_META_ROOT, &root_val);
489 [ - + ]: 69 : if (rc != TP_OK) {
490 : : /* LCOV_EXCL_START */
491 : : tp_encoder_destroy(&enc);
492 : : return rc;
493 : : /* LCOV_EXCL_STOP */
494 : : }
495 : :
496 : 69 : rc = tp_encoder_build(enc, buf, buf_len);
497 : 69 : tp_encoder_destroy(&enc);
498 : 69 : return rc;
499 : : }
|