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