| // 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(); | |
| } |