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 bool quoted = false;
173
174 if (ch == '"') {
175 // If we encounter a quote while parsing an integer, assume that
176 // the quoted string will contain an integer. Get the next
177 // character and continue parsing as if we expect an integer.
178 quoted = true;
179 ch = (char)TRY(ujson_getc(uj));
180 }
181
182 if (ch == '-') {
183 neg = true;
184 ch = (char)TRY(ujson_getc(uj));
185 }
186 uint64_t value = 0;
187
188 if (!(ch >= '0' && ch <= '9')) {
189 return NOT_FOUND();
190 }
191 status_t s;
192 while (ch >= '0' && ch <= '9') {
193 if (value > UINT64_MAX / 10) {
194 return OUT_OF_RANGE();
195 }
196 value *= 10;
197 if (value > UINT64_MAX - (ch - '0')) {
198 return OUT_OF_RANGE();
199 }
200 value += ch - '0';
201 s = ujson_getc(uj);
202 if (status_err(s))
203 break;
204 ch = (char)s.value;
205 }
206
207 if (status_ok(s)) {
208 if (quoted) {
209 if (ch != '"') {
210 return INVALID_ARGUMENT();
211 }
212 // Close quote on an integer in quoted string.
213 // Don't have to unget the quote because we
214 // want to consume it.
215 } else {
216 TRY(ujson_ungetc(uj, ch));
217 }
218 }
219
220 if (neg) {
221 if (value > (uint64_t)INT64_MAX + 1) {
222 return OUT_OF_RANGE();
223 }
224
225 int64_t neg_value = -1 * (int64_t)value;
226 memcpy(result, &neg_value, rsz);
227 return OK_STATUS();
228 }
229
230 memcpy(result, &value, rsz);
231 return OK_STATUS();
232}
233
234status_t ujson_deserialize_bool(ujson_t *uj, bool *value) {
235 char got = (char)TRY(consume_whitespace(uj));
236 if (got == 't') {
237 TRY(ujson_consume(uj, 'r'));
238 TRY(ujson_consume(uj, 'u'));
239 TRY(ujson_consume(uj, 'e'));
240 *value = true;
241 } else if (got == 'f') {
242 TRY(ujson_consume(uj, 'a'));
243 TRY(ujson_consume(uj, 'l'));
244 TRY(ujson_consume(uj, 's'));
245 TRY(ujson_consume(uj, 'e'));
246 *value = false;
247 } else {
248 ujson_ungetc(uj, got);
249 return NOT_FOUND();
250 }
251 return OK_STATUS();
252}
253
254status_t ujson_deserialize_uint64_t(ujson_t *uj, uint64_t *value) {
255 return ujson_parse_integer(uj, (void *)value, sizeof(*value));
256}
257status_t ujson_deserialize_uint32_t(ujson_t *uj, uint32_t *value) {
258 return ujson_parse_integer(uj, (void *)value, sizeof(*value));
259}
260status_t ujson_deserialize_uint16_t(ujson_t *uj, uint16_t *value) {
261 return ujson_parse_integer(uj, (void *)value, sizeof(*value));
262}
263status_t ujson_deserialize_uint8_t(ujson_t *uj, uint8_t *value) {
264 return ujson_parse_integer(uj, (void *)value, sizeof(*value));
265}
266status_t ujson_deserialize_size_t(ujson_t *uj, size_t *value) {
267 return ujson_parse_integer(uj, (void *)value, sizeof(*value));
268}
269status_t ujson_deserialize_int64_t(ujson_t *uj, int64_t *value) {
270 return ujson_parse_integer(uj, (void *)value, sizeof(*value));
271}
272status_t ujson_deserialize_int32_t(ujson_t *uj, int32_t *value) {
273 return ujson_parse_integer(uj, (void *)value, sizeof(*value));
274}
275status_t ujson_deserialize_int16_t(ujson_t *uj, int16_t *value) {
276 return ujson_parse_integer(uj, (void *)value, sizeof(*value));
277}
278status_t ujson_deserialize_int8_t(ujson_t *uj, int8_t *value) {
279 return ujson_parse_integer(uj, (void *)value, sizeof(*value));
280}
281
282static const char hex[] = "0123456789abcdef";
283
284status_t ujson_serialize_string(ujson_t *uj, const char *buf) {
285 uint8_t ch;
286 TRY(ujson_putbuf(uj, "\"", 1));
287 while ((ch = (uint8_t)*buf) != '\0') {
288 if (ch < 0x20 || ch == '"' || ch == '\\' || ch >= 0x7f) {
289 switch (ch) {
290 case '"':
291 TRY(ujson_putbuf(uj, "\\\"", 2));
292 break;
293 case '\\':
294 TRY(ujson_putbuf(uj, "\\\\", 2));
295 break;
296 case '\b':
297 TRY(ujson_putbuf(uj, "\\b", 2));
298 break;
299 case '\f':
300 TRY(ujson_putbuf(uj, "\\f", 2));
301 break;
302 case '\n':
303 TRY(ujson_putbuf(uj, "\\n", 2));
304 break;
305 case '\r':
306 TRY(ujson_putbuf(uj, "\\r", 2));
307 break;
308 case '\t':
309 TRY(ujson_putbuf(uj, "\\t", 2));
310 break;
311 default: {
312 char esc[] = {'\\', 'u', '0', '0', hex[ch >> 4], hex[ch & 0xF]};
313 TRY(ujson_putbuf(uj, esc, sizeof(esc)));
314 }
315 }
316 } else {
317 TRY(ujson_putbuf(uj, buf, 1));
318 }
319 ++buf;
320 }
321 TRY(ujson_putbuf(uj, "\"", 1));
322 return OK_STATUS();
323}
324
325static status_t ujson_serialize_integer64(ujson_t *uj, uint64_t value,
326 bool neg) {
327 char buf[24];
328 char *end = buf + sizeof(buf);
329 size_t len = 0;
330 *--end = '\0';
331
332 // If negative, two's complement for the absolute value.
333 if (neg)
334 value = ~value + 1;
335 do {
336 // We've banned __udivdi3; do division with the replacement function.
337 uint64_t remainder;
338 value = udiv64_slow(value, 10, &remainder);
339 *--end = '0' + (char)remainder;
340 ++len;
341 } while (value);
342 if (neg) {
343 *--end = '-';
344 ++len;
345 }
346 TRY(ujson_putbuf(uj, end, len));
347 return OK_STATUS();
348}
349
350static status_t ujson_serialize_integer32(ujson_t *uj, uint32_t value,
351 bool neg) {
352 char buf[24];
353 char *end = buf + sizeof(buf);
354 size_t len = 0;
355 *--end = '\0';
356
357 // If negative, two's complement for the absolute value.
358 if (neg)
359 value = ~value + 1;
360 do {
361 *--end = '0' + value % 10;
362 value /= 10;
363 ++len;
364 } while (value);
365 if (neg) {
366 *--end = '-';
367 ++len;
368 }
369 TRY(ujson_putbuf(uj, end, len));
370 return OK_STATUS();
371}
372
373status_t ujson_serialize_bool(ujson_t *uj, const bool *value) {
374 if (*value) {
375 TRY(ujson_putbuf(uj, "true", 4));
376 } else {
377 TRY(ujson_putbuf(uj, "false", 5));
378 }
379 return OK_STATUS();
380}
381
382status_t ujson_serialize_uint64_t(ujson_t *uj, const uint64_t *value) {
383 return ujson_serialize_integer64(uj, *value, false);
384}
385status_t ujson_serialize_uint32_t(ujson_t *uj, const uint32_t *value) {
386 return ujson_serialize_integer32(uj, *value, false);
387}
388
389status_t ujson_serialize_uint16_t(ujson_t *uj, const uint16_t *value) {
390 return ujson_serialize_integer32(uj, *value, false);
391}
392
393status_t ujson_serialize_uint8_t(ujson_t *uj, const uint8_t *value) {
394 return ujson_serialize_integer32(uj, *value, false);
395}
396
397status_t ujson_serialize_size_t(ujson_t *uj, const size_t *value) {
398 if (sizeof(size_t) == sizeof(uint64_t)) {
399 return ujson_serialize_integer64(uj, *value, false);
400 } else {
401 return ujson_serialize_integer32(uj, *value, false);
402 }
403}
404
405status_t ujson_serialize_int64_t(ujson_t *uj, const int64_t *value) {
406 return ujson_serialize_integer64(uj, (uint64_t)*value, *value < 0);
407}
408
409status_t ujson_serialize_int32_t(ujson_t *uj, const int32_t *value) {
410 return ujson_serialize_integer32(uj, (uint32_t)*value, *value < 0);
411}
412
413status_t ujson_serialize_int16_t(ujson_t *uj, const int16_t *value) {
414 return ujson_serialize_integer32(uj, (uint32_t)*value, *value < 0);
415}
416
417status_t ujson_serialize_int8_t(ujson_t *uj, const int8_t *value) {
418 return ujson_serialize_integer32(uj, (uint32_t)*value, *value < 0);
419}
420
421status_t ujson_deserialize_status_t(ujson_t *uj, status_t *value) {
422 private_status_t code;
423 uint32_t module_id = 0;
424 uint32_t arg = 0;
425 TRY(ujson_consume(uj, '{'));
426 TRY(ujson_deserialize_private_status_t(uj, &code));
427 TRY(ujson_consume(uj, ':'));
428 if (TRY(ujson_consume_maybe(uj, '['))) {
429 char module[4];
430 TRY(ujson_parse_qs(uj, module, sizeof(module)));
431 module_id = MAKE_MODULE_ID(module[0], module[1], module[2]);
432 TRY(ujson_consume(uj, ','));
433 TRY(ujson_deserialize_uint32_t(uj, &arg));
434 TRY(ujson_consume(uj, ']'));
435 } else {
436 TRY(ujson_deserialize_uint32_t(uj, &arg));
437 }
438 TRY(ujson_consume(uj, '}'));
439 *value =
440 status_create((absl_status_t)code, module_id, __FILE__, (int32_t)arg);
441 return OK_STATUS();
442}
443
444status_t ujson_serialize_status_t(ujson_t *uj, const status_t *value) {
445 buffer_sink_t out = {
446 .data = uj,
447 .sink = (sink_func_ptr)ujson_putbuf_sink,
448 };
449 base_fprintf(out, "%!r", *value);
450 return OK_STATUS();
451}