|  | // Copyright lowRISC contributors. | 
|  | // Licensed under the Apache License, Version 2.0, see LICENSE for details. | 
|  | // SPDX-License-Identifier: Apache-2.0 | 
|  |  | 
|  | #include "sw/device/lib/ujson/ujson.h" | 
|  |  | 
|  | #include <stdbool.h> | 
|  | #include <stdint.h> | 
|  | #include <string.h> | 
|  |  | 
|  | #include "sw/device/lib/base/math.h" | 
|  | #include "sw/device/lib/base/status.h" | 
|  | #include "sw/device/lib/runtime/print.h" | 
|  | #include "sw/device/lib/ujson/private_status.h" | 
|  |  | 
|  | static bool is_space(int c) { return c == ' ' || (unsigned)c - '\t' < 5; } | 
|  |  | 
|  | ujson_t ujson_init(void *context, status_t (*getc)(void *), | 
|  | status_t (*putbuf)(void *, const char *, size_t)) { | 
|  | ujson_t u = UJSON_INIT(context, getc, putbuf); | 
|  | return u; | 
|  | } | 
|  |  | 
|  | status_t ujson_putbuf(ujson_t *uj, const char *buf, size_t len) { | 
|  | return uj->putbuf(uj->io_context, buf, len); | 
|  | } | 
|  |  | 
|  | status_t ujson_getc(ujson_t *uj) { | 
|  | int16_t buffer = uj->buffer; | 
|  | if (buffer >= 0) { | 
|  | uj->buffer = -1; | 
|  | return OK_STATUS(buffer); | 
|  | } else { | 
|  | return uj->getc(uj->io_context); | 
|  | } | 
|  | } | 
|  |  | 
|  | status_t ujson_ungetc(ujson_t *uj, char ch) { | 
|  | if (uj->buffer >= 0) { | 
|  | return FAILED_PRECONDITION(); | 
|  | } | 
|  | uj->buffer = ch; | 
|  | return OK_STATUS(); | 
|  | } | 
|  |  | 
|  | bool ujson_streq(const char *a, const char *b) { | 
|  | while (*a && *b && *a == *b) { | 
|  | ++a; | 
|  | ++b; | 
|  | } | 
|  | return *a == *b; | 
|  | } | 
|  |  | 
|  | // Consumes whitespace returning first non-whitepsace character found. | 
|  | static status_t consume_whitespace(ujson_t *uj) { | 
|  | int ch; | 
|  | do { | 
|  | ch = TRY(ujson_getc(uj)); | 
|  | } while (is_space(ch)); | 
|  | return OK_STATUS(ch); | 
|  | } | 
|  |  | 
|  | static status_t consume_hexdigit(ujson_t *uj) { | 
|  | int ch = TRY(ujson_getc(uj)); | 
|  | if (ch >= '0' && ch <= '9') { | 
|  | return OK_STATUS(ch - '0'); | 
|  | } else if (ch >= 'A' && ch <= 'F') { | 
|  | return OK_STATUS(ch - 'A' + 10); | 
|  | } else if (ch >= 'a' && ch <= 'f') { | 
|  | return OK_STATUS(ch - 'a' + 10); | 
|  | } else { | 
|  | return OUT_OF_RANGE(); | 
|  | } | 
|  | } | 
|  |  | 
|  | static status_t consume_hex(ujson_t *uj) { | 
|  | int a = TRY(consume_hexdigit(uj)); | 
|  | int b = TRY(consume_hexdigit(uj)); | 
|  | int c = TRY(consume_hexdigit(uj)); | 
|  | int d = TRY(consume_hexdigit(uj)); | 
|  | return OK_STATUS((a << 12) | (b << 8) | (c << 4) | d); | 
|  | } | 
|  |  | 
|  | status_t ujson_consume(ujson_t *uj, char ch) { | 
|  | if (ch != TRY(consume_whitespace(uj))) { | 
|  | return NOT_FOUND(); | 
|  | } | 
|  | return OK_STATUS(); | 
|  | } | 
|  |  | 
|  | status_t ujson_consume_maybe(ujson_t *uj, char ch) { | 
|  | char got = TRY(consume_whitespace(uj)); | 
|  | if (ch != got) { | 
|  | ujson_ungetc(uj, got); | 
|  | return OK_STATUS(0); | 
|  | } | 
|  | return OK_STATUS(1); | 
|  | } | 
|  |  | 
|  | status_t ujson_parse_qs(ujson_t *uj, char *str, size_t len) { | 
|  | int ch; | 
|  | int n = 0; | 
|  | len--;  // One char for the nul terminator. | 
|  | TRY(ujson_consume(uj, '"')); | 
|  | while (true) { | 
|  | ch = TRY(ujson_getc(uj)); | 
|  | if (ch == '\"') | 
|  | break; | 
|  | if (ch == '\\') { | 
|  | ch = TRY(ujson_getc(uj)); | 
|  | switch (ch) { | 
|  | case '"': | 
|  | case '\\': | 
|  | case '/': | 
|  | break; | 
|  | case 'b': | 
|  | ch = '\b'; | 
|  | break; | 
|  | case 'f': | 
|  | ch = '\f'; | 
|  | break; | 
|  | case 'n': | 
|  | ch = '\n'; | 
|  | break; | 
|  | case 'r': | 
|  | ch = '\r'; | 
|  | break; | 
|  | case 't': | 
|  | ch = '\t'; | 
|  | break; | 
|  | case 'u': | 
|  | ch = TRY(consume_hex(uj)); | 
|  | break; | 
|  | default: | 
|  | return OUT_OF_RANGE(); | 
|  | } | 
|  | } | 
|  | if (len > 0) { | 
|  | *str++ = ch; | 
|  | --len; | 
|  | n++; | 
|  | } | 
|  | } | 
|  | *str = '\0'; | 
|  | return OK_STATUS(n); | 
|  | } | 
|  |  | 
|  | status_t ujson_parse_integer(ujson_t *uj, void *result, size_t rsz) { | 
|  | char ch = TRY(consume_whitespace(uj)); | 
|  | bool neg = false; | 
|  |  | 
|  | if (ch == '-') { | 
|  | neg = true; | 
|  | ch = TRY(ujson_getc(uj)); | 
|  | } | 
|  | int64_t value = 0; | 
|  |  | 
|  | if (!(ch >= '0' && ch <= '9')) { | 
|  | return NOT_FOUND(); | 
|  | } | 
|  | status_t s; | 
|  | while (ch >= '0' && ch <= '9') { | 
|  | value *= 10; | 
|  | value += ch - '0'; | 
|  | s = ujson_getc(uj); | 
|  | if (status_err(s)) | 
|  | break; | 
|  | ch = (char)s.value; | 
|  | } | 
|  | if (status_ok(s)) | 
|  | TRY(ujson_ungetc(uj, ch)); | 
|  | if (neg) | 
|  | value = -value; | 
|  | memcpy(result, &value, rsz); | 
|  | return OK_STATUS(); | 
|  | } | 
|  |  | 
|  | status_t ujson_deserialize_bool(ujson_t *uj, bool *value) { | 
|  | char got = TRY(consume_whitespace(uj)); | 
|  | if (got == 't') { | 
|  | TRY(ujson_consume(uj, 'r')); | 
|  | TRY(ujson_consume(uj, 'u')); | 
|  | TRY(ujson_consume(uj, 'e')); | 
|  | *value = true; | 
|  | } else if (got == 'f') { | 
|  | TRY(ujson_consume(uj, 'a')); | 
|  | TRY(ujson_consume(uj, 'l')); | 
|  | TRY(ujson_consume(uj, 's')); | 
|  | TRY(ujson_consume(uj, 'e')); | 
|  | *value = false; | 
|  | } else { | 
|  | ujson_ungetc(uj, got); | 
|  | return NOT_FOUND(); | 
|  | } | 
|  | return OK_STATUS(); | 
|  | } | 
|  |  | 
|  | status_t ujson_deserialize_uint64_t(ujson_t *uj, uint64_t *value) { | 
|  | return ujson_parse_integer(uj, (void *)value, sizeof(*value)); | 
|  | } | 
|  | status_t ujson_deserialize_uint32_t(ujson_t *uj, uint32_t *value) { | 
|  | return ujson_parse_integer(uj, (void *)value, sizeof(*value)); | 
|  | } | 
|  | status_t ujson_deserialize_uint16_t(ujson_t *uj, uint16_t *value) { | 
|  | return ujson_parse_integer(uj, (void *)value, sizeof(*value)); | 
|  | } | 
|  | status_t ujson_deserialize_uint8_t(ujson_t *uj, uint8_t *value) { | 
|  | return ujson_parse_integer(uj, (void *)value, sizeof(*value)); | 
|  | } | 
|  | status_t ujson_deserialize_size_t(ujson_t *uj, size_t *value) { | 
|  | return ujson_parse_integer(uj, (void *)value, sizeof(*value)); | 
|  | } | 
|  | status_t ujson_deserialize_int64_t(ujson_t *uj, int64_t *value) { | 
|  | return ujson_parse_integer(uj, (void *)value, sizeof(*value)); | 
|  | } | 
|  | status_t ujson_deserialize_int32_t(ujson_t *uj, int32_t *value) { | 
|  | return ujson_parse_integer(uj, (void *)value, sizeof(*value)); | 
|  | } | 
|  | status_t ujson_deserialize_int16_t(ujson_t *uj, int16_t *value) { | 
|  | return ujson_parse_integer(uj, (void *)value, sizeof(*value)); | 
|  | } | 
|  | status_t ujson_deserialize_int8_t(ujson_t *uj, int8_t *value) { | 
|  | return ujson_parse_integer(uj, (void *)value, sizeof(*value)); | 
|  | } | 
|  |  | 
|  | static const char hex[] = "0123456789abcdef"; | 
|  |  | 
|  | status_t ujson_serialize_string(ujson_t *uj, const char *buf) { | 
|  | uint8_t ch; | 
|  | TRY(ujson_putbuf(uj, "\"", 1)); | 
|  | while ((ch = (uint8_t)*buf) != '\0') { | 
|  | if (ch < 0x20 || ch == '"' || ch == '\\' || ch >= 0x7f) { | 
|  | switch (ch) { | 
|  | case '"': | 
|  | TRY(ujson_putbuf(uj, "\\\"", 2)); | 
|  | break; | 
|  | case '\\': | 
|  | TRY(ujson_putbuf(uj, "\\\\", 2)); | 
|  | break; | 
|  | case '\b': | 
|  | TRY(ujson_putbuf(uj, "\\b", 2)); | 
|  | break; | 
|  | case '\f': | 
|  | TRY(ujson_putbuf(uj, "\\f", 2)); | 
|  | break; | 
|  | case '\n': | 
|  | TRY(ujson_putbuf(uj, "\\n", 2)); | 
|  | break; | 
|  | case '\r': | 
|  | TRY(ujson_putbuf(uj, "\\r", 2)); | 
|  | break; | 
|  | case '\t': | 
|  | TRY(ujson_putbuf(uj, "\\t", 2)); | 
|  | break; | 
|  | default: { | 
|  | char esc[] = {'\\', 'u', '0', '0', hex[ch >> 4], hex[ch & 0xF]}; | 
|  | TRY(ujson_putbuf(uj, esc, sizeof(esc))); | 
|  | } | 
|  | } | 
|  | } else { | 
|  | TRY(ujson_putbuf(uj, buf, 1)); | 
|  | } | 
|  | ++buf; | 
|  | } | 
|  | TRY(ujson_putbuf(uj, "\"", 1)); | 
|  | return OK_STATUS(); | 
|  | } | 
|  |  | 
|  | static status_t ujson_serialize_integer64(ujson_t *uj, uint64_t value, | 
|  | bool neg) { | 
|  | char buf[24]; | 
|  | char *end = buf + sizeof(buf); | 
|  | size_t len = 0; | 
|  | *--end = '\0'; | 
|  |  | 
|  | // If negative, two's complement for the absolute value. | 
|  | if (neg) | 
|  | value = ~value + 1; | 
|  | do { | 
|  | // We've banned __udivdi3; do division with the replacement function. | 
|  | uint64_t remainder; | 
|  | value = udiv64_slow(value, 10, &remainder); | 
|  | *--end = '0' + remainder; | 
|  | ++len; | 
|  | } while (value); | 
|  | if (neg) { | 
|  | *--end = '-'; | 
|  | ++len; | 
|  | } | 
|  | TRY(ujson_putbuf(uj, end, len)); | 
|  | return OK_STATUS(); | 
|  | } | 
|  |  | 
|  | static status_t ujson_serialize_integer32(ujson_t *uj, uint32_t value, | 
|  | bool neg) { | 
|  | char buf[24]; | 
|  | char *end = buf + sizeof(buf); | 
|  | size_t len = 0; | 
|  | *--end = '\0'; | 
|  |  | 
|  | // If negative, two's complement for the absolute value. | 
|  | if (neg) | 
|  | value = ~value + 1; | 
|  | do { | 
|  | *--end = '0' + value % 10; | 
|  | value /= 10; | 
|  | ++len; | 
|  | } while (value); | 
|  | if (neg) { | 
|  | *--end = '-'; | 
|  | ++len; | 
|  | } | 
|  | TRY(ujson_putbuf(uj, end, len)); | 
|  | return OK_STATUS(); | 
|  | } | 
|  |  | 
|  | status_t ujson_serialize_bool(ujson_t *uj, const bool *value) { | 
|  | if (*value) { | 
|  | TRY(ujson_putbuf(uj, "true", 4)); | 
|  | } else { | 
|  | TRY(ujson_putbuf(uj, "false", 5)); | 
|  | } | 
|  | return OK_STATUS(); | 
|  | } | 
|  |  | 
|  | status_t ujson_serialize_uint64_t(ujson_t *uj, const uint64_t *value) { | 
|  | return ujson_serialize_integer64(uj, *value, false); | 
|  | } | 
|  | status_t ujson_serialize_uint32_t(ujson_t *uj, const uint32_t *value) { | 
|  | return ujson_serialize_integer32(uj, *value, false); | 
|  | } | 
|  |  | 
|  | status_t ujson_serialize_uint16_t(ujson_t *uj, const uint16_t *value) { | 
|  | return ujson_serialize_integer32(uj, *value, false); | 
|  | } | 
|  |  | 
|  | status_t ujson_serialize_uint8_t(ujson_t *uj, const uint8_t *value) { | 
|  | return ujson_serialize_integer32(uj, *value, false); | 
|  | } | 
|  |  | 
|  | status_t ujson_serialize_size_t(ujson_t *uj, const size_t *value) { | 
|  | if (sizeof(size_t) == sizeof(uint64_t)) { | 
|  | return ujson_serialize_integer64(uj, *value, false); | 
|  | } else { | 
|  | return ujson_serialize_integer32(uj, *value, false); | 
|  | } | 
|  | } | 
|  |  | 
|  | status_t ujson_serialize_int64_t(ujson_t *uj, const int64_t *value) { | 
|  | return ujson_serialize_integer64(uj, (uint64_t)*value, *value < 0); | 
|  | } | 
|  |  | 
|  | status_t ujson_serialize_int32_t(ujson_t *uj, const int32_t *value) { | 
|  | return ujson_serialize_integer32(uj, (uint32_t)*value, *value < 0); | 
|  | } | 
|  |  | 
|  | status_t ujson_serialize_int16_t(ujson_t *uj, const int16_t *value) { | 
|  | return ujson_serialize_integer32(uj, (uint32_t)*value, *value < 0); | 
|  | } | 
|  |  | 
|  | status_t ujson_serialize_int8_t(ujson_t *uj, const int8_t *value) { | 
|  | return ujson_serialize_integer32(uj, (uint32_t)*value, *value < 0); | 
|  | } | 
|  |  | 
|  | status_t ujson_deserialize_status_t(ujson_t *uj, status_t *value) { | 
|  | private_status_t code; | 
|  | uint32_t module_id = 0; | 
|  | uint32_t arg = 0; | 
|  | TRY(ujson_consume(uj, '{')); | 
|  | TRY(ujson_deserialize_private_status_t(uj, &code)); | 
|  | TRY(ujson_consume(uj, ':')); | 
|  | if (TRY(ujson_consume_maybe(uj, '['))) { | 
|  | char module[4]; | 
|  | TRY(ujson_parse_qs(uj, module, sizeof(module))); | 
|  | module_id = MAKE_MODULE_ID(module[0], module[1], module[2]); | 
|  | TRY(ujson_consume(uj, ',')); | 
|  | TRY(ujson_deserialize_uint32_t(uj, &arg)); | 
|  | TRY(ujson_consume(uj, ']')); | 
|  | } else { | 
|  | TRY(ujson_deserialize_uint32_t(uj, &arg)); | 
|  | } | 
|  | TRY(ujson_consume(uj, '}')); | 
|  | *value = status_create((absl_status_t)code, module_id, __FILE__, arg); | 
|  | return OK_STATUS(); | 
|  | } | 
|  |  | 
|  | status_t ujson_serialize_status_t(ujson_t *uj, const status_t *value) { | 
|  | buffer_sink_t out = { | 
|  | .data = uj->io_context, | 
|  | .sink = (size_t(*)(void *, const char *, size_t))uj->putbuf, | 
|  | }; | 
|  | base_fprintf(out, "%!r", *value); | 
|  | return OK_STATUS(); | 
|  | } |