Software APIs
ujson.c
1// Copyright lowRISC contributors (OpenTitan project).
2// Licensed under the Apache License, Version 2.0, see LICENSE for details.
3// SPDX-License-Identifier: Apache-2.0
4
5#include "sw/device/lib/ujson/ujson.h"
6
7#include <stdbool.h>
8#include <stdint.h>
9#include <string.h>
10
11#include "sw/device/lib/base/crc32.h"
13#include "sw/device/lib/base/status.h"
15#include "sw/device/lib/ujson/private_status.h"
16
17static bool is_space(int c) { return c == ' ' || (c >= '\t' && c < '\t' + 5); }
18
19ujson_t ujson_init(void *context, status_t (*getc)(void *),
20 status_t (*putbuf)(void *, const char *, size_t),
21 status_t (*flushbuf)(void *)) {
22 ujson_t u = UJSON_INIT(context, getc, putbuf, flushbuf);
23 return u;
24}
25
26void ujson_crc32_reset(ujson_t *uj) { crc32_init(&uj->crc32); }
27
28uint32_t ujson_crc32_finish(ujson_t *uj) { return crc32_finish(&uj->crc32); }
29
30status_t ujson_putbuf(ujson_t *uj, const char *buf, size_t len) {
31 crc32_add(&uj->crc32, buf, len);
32 return uj->putbuf(uj->io_context, buf, len);
33}
34
35static size_t ujson_putbuf_sink(ujson_t *uj, const char *buf, size_t len) {
36 status_t result = ujson_putbuf(uj, buf, len);
37 if (!status_ok(result)) {
38 return 0;
39 }
40 return (size_t)result.value;
41}
42
43status_t ujson_flushbuf(ujson_t *uj) { return uj->flushbuf(uj->io_context); }
44
45status_t ujson_getc(ujson_t *uj) {
46 int16_t buffer = uj->buffer;
47 if (buffer >= 0) {
48 uj->buffer = -1;
49 return OK_STATUS(buffer);
50 } else {
51 status_t s = uj->getc(uj->io_context);
52 if (!status_err(s)) {
53 crc32_add8(&uj->crc32, (uint8_t)s.value);
54 }
55 return s;
56 }
57}
58
59status_t ujson_ungetc(ujson_t *uj, char ch) {
60 if (uj->buffer >= 0) {
61 return FAILED_PRECONDITION();
62 }
63 uj->buffer = ch;
64 return OK_STATUS();
65}
66
67bool ujson_streq(const char *a, const char *b) {
68 while (*a && *b && *a == *b) {
69 ++a;
70 ++b;
71 }
72 return *a == *b;
73}
74
75// Consumes whitespace returning first non-whitepsace character found.
76static status_t consume_whitespace(ujson_t *uj) {
77 int ch;
78 do {
79 ch = TRY(ujson_getc(uj));
80 } while (is_space(ch));
81 return OK_STATUS(ch);
82}
83
84static status_t consume_hexdigit(ujson_t *uj) {
85 int ch = TRY(ujson_getc(uj));
86 if (ch >= '0' && ch <= '9') {
87 return OK_STATUS(ch - '0');
88 } else if (ch >= 'A' && ch <= 'F') {
89 return OK_STATUS(ch - 'A' + 10);
90 } else if (ch >= 'a' && ch <= 'f') {
91 return OK_STATUS(ch - 'a' + 10);
92 } else {
93 return OUT_OF_RANGE();
94 }
95}
96
97static status_t consume_hex(ujson_t *uj) {
98 int a = TRY(consume_hexdigit(uj));
99 int b = TRY(consume_hexdigit(uj));
100 int c = TRY(consume_hexdigit(uj));
101 int d = TRY(consume_hexdigit(uj));
102 return OK_STATUS((a << 12) | (b << 8) | (c << 4) | d);
103}
104
105status_t ujson_consume(ujson_t *uj, char ch) {
106 if (ch != TRY(consume_whitespace(uj))) {
107 return NOT_FOUND();
108 }
109 return OK_STATUS();
110}
111
112status_t ujson_consume_maybe(ujson_t *uj, char ch) {
113 char got = (char)TRY(consume_whitespace(uj));
114 if (ch != got) {
115 ujson_ungetc(uj, got);
116 return OK_STATUS(0);
117 }
118 return OK_STATUS(1);
119}
120
121status_t ujson_parse_qs(ujson_t *uj, char *str, size_t len) {
122 char ch;
123 int n = 0;
124 len--; // One char for the nul terminator.
125 TRY(ujson_consume(uj, '"'));
126 while (true) {
127 ch = (char)TRY(ujson_getc(uj));
128 if (ch == '\"')
129 break;
130 if (ch == '\\') {
131 ch = (char)TRY(ujson_getc(uj));
132 switch (ch) {
133 case '"':
134 case '\\':
135 case '/':
136 break;
137 case 'b':
138 ch = '\b';
139 break;
140 case 'f':
141 ch = '\f';
142 break;
143 case 'n':
144 ch = '\n';
145 break;
146 case 'r':
147 ch = '\r';
148 break;
149 case 't':
150 ch = '\t';
151 break;
152 case 'u':
153 ch = (char)TRY(consume_hex(uj));
154 break;
155 default:
156 return OUT_OF_RANGE();
157 }
158 }
159 if (len > 0) {
160 *str++ = ch;
161 --len;
162 n++;
163 }
164 }
165 *str = '\0';
166 return OK_STATUS(n);
167}
168
169status_t ujson_parse_integer(ujson_t *uj, void *result, size_t rsz) {
170 char ch = (char)TRY(consume_whitespace(uj));
171 bool neg = false;
172
173 if (ch == '-') {
174 neg = true;
175 ch = (char)TRY(ujson_getc(uj));
176 }
177 uint64_t value = 0;
178
179 if (!(ch >= '0' && ch <= '9')) {
180 return NOT_FOUND();
181 }
182 status_t s;
183 while (ch >= '0' && ch <= '9') {
184 if (value > UINT64_MAX / 10) {
185 return OUT_OF_RANGE();
186 }
187 value *= 10;
188 if (value > UINT64_MAX - (ch - '0')) {
189 return OUT_OF_RANGE();
190 }
191 value += ch - '0';
192 s = ujson_getc(uj);
193 if (status_err(s))
194 break;
195 ch = (char)s.value;
196 }
197 if (status_ok(s))
198 TRY(ujson_ungetc(uj, ch));
199
200 if (neg) {
201 if (value > (uint64_t)INT64_MAX + 1) {
202 return OUT_OF_RANGE();
203 }
204
205 int64_t neg_value = -1 * (int64_t)value;
206 memcpy(result, &neg_value, rsz);
207 return OK_STATUS();
208 }
209
210 memcpy(result, &value, rsz);
211 return OK_STATUS();
212}
213
214status_t ujson_deserialize_bool(ujson_t *uj, bool *value) {
215 char got = (char)TRY(consume_whitespace(uj));
216 if (got == 't') {
217 TRY(ujson_consume(uj, 'r'));
218 TRY(ujson_consume(uj, 'u'));
219 TRY(ujson_consume(uj, 'e'));
220 *value = true;
221 } else if (got == 'f') {
222 TRY(ujson_consume(uj, 'a'));
223 TRY(ujson_consume(uj, 'l'));
224 TRY(ujson_consume(uj, 's'));
225 TRY(ujson_consume(uj, 'e'));
226 *value = false;
227 } else {
228 ujson_ungetc(uj, got);
229 return NOT_FOUND();
230 }
231 return OK_STATUS();
232}
233
234status_t ujson_deserialize_uint64_t(ujson_t *uj, uint64_t *value) {
235 return ujson_parse_integer(uj, (void *)value, sizeof(*value));
236}
237status_t ujson_deserialize_uint32_t(ujson_t *uj, uint32_t *value) {
238 return ujson_parse_integer(uj, (void *)value, sizeof(*value));
239}
240status_t ujson_deserialize_uint16_t(ujson_t *uj, uint16_t *value) {
241 return ujson_parse_integer(uj, (void *)value, sizeof(*value));
242}
243status_t ujson_deserialize_uint8_t(ujson_t *uj, uint8_t *value) {
244 return ujson_parse_integer(uj, (void *)value, sizeof(*value));
245}
246status_t ujson_deserialize_size_t(ujson_t *uj, size_t *value) {
247 return ujson_parse_integer(uj, (void *)value, sizeof(*value));
248}
249status_t ujson_deserialize_int64_t(ujson_t *uj, int64_t *value) {
250 return ujson_parse_integer(uj, (void *)value, sizeof(*value));
251}
252status_t ujson_deserialize_int32_t(ujson_t *uj, int32_t *value) {
253 return ujson_parse_integer(uj, (void *)value, sizeof(*value));
254}
255status_t ujson_deserialize_int16_t(ujson_t *uj, int16_t *value) {
256 return ujson_parse_integer(uj, (void *)value, sizeof(*value));
257}
258status_t ujson_deserialize_int8_t(ujson_t *uj, int8_t *value) {
259 return ujson_parse_integer(uj, (void *)value, sizeof(*value));
260}
261
262static const char hex[] = "0123456789abcdef";
263
264status_t ujson_serialize_string(ujson_t *uj, const char *buf) {
265 uint8_t ch;
266 TRY(ujson_putbuf(uj, "\"", 1));
267 while ((ch = (uint8_t)*buf) != '\0') {
268 if (ch < 0x20 || ch == '"' || ch == '\\' || ch >= 0x7f) {
269 switch (ch) {
270 case '"':
271 TRY(ujson_putbuf(uj, "\\\"", 2));
272 break;
273 case '\\':
274 TRY(ujson_putbuf(uj, "\\\\", 2));
275 break;
276 case '\b':
277 TRY(ujson_putbuf(uj, "\\b", 2));
278 break;
279 case '\f':
280 TRY(ujson_putbuf(uj, "\\f", 2));
281 break;
282 case '\n':
283 TRY(ujson_putbuf(uj, "\\n", 2));
284 break;
285 case '\r':
286 TRY(ujson_putbuf(uj, "\\r", 2));
287 break;
288 case '\t':
289 TRY(ujson_putbuf(uj, "\\t", 2));
290 break;
291 default: {
292 char esc[] = {'\\', 'u', '0', '0', hex[ch >> 4], hex[ch & 0xF]};
293 TRY(ujson_putbuf(uj, esc, sizeof(esc)));
294 }
295 }
296 } else {
297 TRY(ujson_putbuf(uj, buf, 1));
298 }
299 ++buf;
300 }
301 TRY(ujson_putbuf(uj, "\"", 1));
302 return OK_STATUS();
303}
304
305static status_t ujson_serialize_integer64(ujson_t *uj, uint64_t value,
306 bool neg) {
307 char buf[24];
308 char *end = buf + sizeof(buf);
309 size_t len = 0;
310 *--end = '\0';
311
312 // If negative, two's complement for the absolute value.
313 if (neg)
314 value = ~value + 1;
315 do {
316 // We've banned __udivdi3; do division with the replacement function.
317 uint64_t remainder;
318 value = udiv64_slow(value, 10, &remainder);
319 *--end = '0' + (char)remainder;
320 ++len;
321 } while (value);
322 if (neg) {
323 *--end = '-';
324 ++len;
325 }
326 TRY(ujson_putbuf(uj, end, len));
327 return OK_STATUS();
328}
329
330static status_t ujson_serialize_integer32(ujson_t *uj, uint32_t value,
331 bool neg) {
332 char buf[24];
333 char *end = buf + sizeof(buf);
334 size_t len = 0;
335 *--end = '\0';
336
337 // If negative, two's complement for the absolute value.
338 if (neg)
339 value = ~value + 1;
340 do {
341 *--end = '0' + value % 10;
342 value /= 10;
343 ++len;
344 } while (value);
345 if (neg) {
346 *--end = '-';
347 ++len;
348 }
349 TRY(ujson_putbuf(uj, end, len));
350 return OK_STATUS();
351}
352
353status_t ujson_serialize_bool(ujson_t *uj, const bool *value) {
354 if (*value) {
355 TRY(ujson_putbuf(uj, "true", 4));
356 } else {
357 TRY(ujson_putbuf(uj, "false", 5));
358 }
359 return OK_STATUS();
360}
361
362status_t ujson_serialize_uint64_t(ujson_t *uj, const uint64_t *value) {
363 return ujson_serialize_integer64(uj, *value, false);
364}
365status_t ujson_serialize_uint32_t(ujson_t *uj, const uint32_t *value) {
366 return ujson_serialize_integer32(uj, *value, false);
367}
368
369status_t ujson_serialize_uint16_t(ujson_t *uj, const uint16_t *value) {
370 return ujson_serialize_integer32(uj, *value, false);
371}
372
373status_t ujson_serialize_uint8_t(ujson_t *uj, const uint8_t *value) {
374 return ujson_serialize_integer32(uj, *value, false);
375}
376
377status_t ujson_serialize_size_t(ujson_t *uj, const size_t *value) {
378 if (sizeof(size_t) == sizeof(uint64_t)) {
379 return ujson_serialize_integer64(uj, *value, false);
380 } else {
381 return ujson_serialize_integer32(uj, *value, false);
382 }
383}
384
385status_t ujson_serialize_int64_t(ujson_t *uj, const int64_t *value) {
386 return ujson_serialize_integer64(uj, (uint64_t)*value, *value < 0);
387}
388
389status_t ujson_serialize_int32_t(ujson_t *uj, const int32_t *value) {
390 return ujson_serialize_integer32(uj, (uint32_t)*value, *value < 0);
391}
392
393status_t ujson_serialize_int16_t(ujson_t *uj, const int16_t *value) {
394 return ujson_serialize_integer32(uj, (uint32_t)*value, *value < 0);
395}
396
397status_t ujson_serialize_int8_t(ujson_t *uj, const int8_t *value) {
398 return ujson_serialize_integer32(uj, (uint32_t)*value, *value < 0);
399}
400
401status_t ujson_deserialize_status_t(ujson_t *uj, status_t *value) {
402 private_status_t code;
403 uint32_t module_id = 0;
404 uint32_t arg = 0;
405 TRY(ujson_consume(uj, '{'));
406 TRY(ujson_deserialize_private_status_t(uj, &code));
407 TRY(ujson_consume(uj, ':'));
408 if (TRY(ujson_consume_maybe(uj, '['))) {
409 char module[4];
410 TRY(ujson_parse_qs(uj, module, sizeof(module)));
411 module_id = MAKE_MODULE_ID(module[0], module[1], module[2]);
412 TRY(ujson_consume(uj, ','));
413 TRY(ujson_deserialize_uint32_t(uj, &arg));
414 TRY(ujson_consume(uj, ']'));
415 } else {
416 TRY(ujson_deserialize_uint32_t(uj, &arg));
417 }
418 TRY(ujson_consume(uj, '}'));
419 *value =
420 status_create((absl_status_t)code, module_id, __FILE__, (int32_t)arg);
421 return OK_STATUS();
422}
423
424status_t ujson_serialize_status_t(ujson_t *uj, const status_t *value) {
425 buffer_sink_t out = {
426 .data = uj,
427 .sink = (sink_func_ptr)ujson_putbuf_sink,
428 };
429 base_fprintf(out, "%!r", *value);
430 return OK_STATUS();
431}