diff options
author | Hugo Landau <hlandau@openssl.org> | 2023-09-08 11:14:09 +0100 |
---|---|---|
committer | Hugo Landau <hlandau@openssl.org> | 2024-02-02 11:49:34 +0000 |
commit | 9c89b9fe1b157f8d10529a07d30c333b799c12e1 (patch) | |
tree | d0e987d50e3d1e91b1ddec1778ba8573e3878d20 /ssl/quic/json_enc.c | |
parent | c0af01f349a2178e03fe5eb2365640da7d865439 (diff) | |
download | openssl-9c89b9fe1b157f8d10529a07d30c333b799c12e1.tar.gz |
QLOG: JSON Encoder: Implementation
Reviewed-by: Matt Caswell <matt@openssl.org>
Reviewed-by: Neil Horman <nhorman@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/22037)
Diffstat (limited to 'ssl/quic/json_enc.c')
-rw-r--r-- | ssl/quic/json_enc.c | 722 |
1 files changed, 722 insertions, 0 deletions
diff --git a/ssl/quic/json_enc.c b/ssl/quic/json_enc.c new file mode 100644 index 0000000000..80dfdb0265 --- /dev/null +++ b/ssl/quic/json_enc.c @@ -0,0 +1,722 @@ +/* + * Copyright 2023 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the Apache License 2.0 (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#include "internal/json_enc.h" +#include "internal/nelem.h" +#include <string.h> +#include <math.h> + +/* + * wbuf + * ==== + */ +static int wbuf_flush(struct json_write_buf *wbuf); + +static int wbuf_init(struct json_write_buf *wbuf, BIO *bio, size_t alloc) +{ + wbuf->buf = OPENSSL_malloc(alloc); + if (wbuf->buf == NULL) + return 0; + + wbuf->cur = 0; + wbuf->alloc = alloc; + wbuf->bio = bio; + return 1; +} + +static void wbuf_cleanup(struct json_write_buf *wbuf) +{ + OPENSSL_free(wbuf->buf); + wbuf->buf = NULL; + wbuf->alloc = 0; +} + +static void wbuf_set_bio(struct json_write_buf *wbuf, BIO *bio) +{ + wbuf->bio = bio; +} + +/* Empty write buffer. */ +static ossl_inline void wbuf_clean(struct json_write_buf *wbuf) +{ + wbuf->cur = 0; +} + +/* Available data remaining in buffer. */ +static ossl_inline size_t wbuf_avail(struct json_write_buf *wbuf) +{ + return wbuf->alloc - wbuf->cur; +} + +/* Add character to write buffer, returning 0 on flush failure. */ +static ossl_inline int wbuf_write_char(struct json_write_buf *wbuf, char c) +{ + if (wbuf_avail(wbuf) == 0) { + if (!wbuf_flush(wbuf)) + return 0; + } + + wbuf->buf[wbuf->cur++] = c; + return 1; +} + +/* + * Write zero-terminated string to write buffer, returning 0 on flush failure. + */ +static int wbuf_write_str(struct json_write_buf *wbuf, const char *s) +{ + char c; + + while ((c = *s++) != 0) + if (!wbuf_write_char(wbuf, c)) + return 0; + + return 1; +} + +/* Flush write buffer, returning 0 on I/O failure. */ +static int wbuf_flush(struct json_write_buf *wbuf) +{ + size_t written = 0, total_written = 0; + + while (total_written < wbuf->cur) { + if (!BIO_write_ex(wbuf->bio, + wbuf->buf + total_written, + wbuf->cur - total_written, + &written)) { + memmove(wbuf->buf, + wbuf->buf + total_written, + wbuf->cur - total_written); + wbuf->cur = 0; + return 0; + } + + total_written += written; + } + + wbuf->cur = 0; + return 1; +} + +/* + * JSON_ENC: Stack Management + * ========================== + */ + +static int json_ensure_stack_size(JSON_ENC *json, size_t num_bytes) +{ + unsigned char *stack; + + if (json->stack_bytes >= num_bytes) + return 1; + + if (num_bytes <= OSSL_NELEM(json->stack_small)) { + stack = json->stack_small; + } else { + if (json->stack == json->stack_small) + json->stack = NULL; + + stack = OPENSSL_realloc(json->stack, num_bytes); + if (stack == NULL) + return 0; + } + + json->stack = stack; + json->stack_bytes = num_bytes; + return 1; +} + +/* Push one bit onto the stack. Returns 0 on allocation failure. */ +static int json_push(JSON_ENC *json, unsigned int v) +{ + if (v > 1) + return 0; + + if (json->stack_end_byte >= json->stack_bytes) { + size_t new_size + = (json->stack_bytes == 0) + ? OSSL_NELEM(json->stack_small) + : (json->stack_bytes * 2); + + if (!json_ensure_stack_size(json, new_size)) + return 0; + + json->stack_bytes = new_size; + } + + if (v > 0) + json->stack[json->stack_end_byte] |= (v << json->stack_end_bit); + else + json->stack[json->stack_end_byte] &= ~(1U << json->stack_end_bit); + + json->stack_end_bit = (json->stack_end_bit + 1) % CHAR_BIT; + if (json->stack_end_bit == 0) + ++json->stack_end_byte; + + return 1; +} + +/* + * Pop a bit from the stack. Returns 0 if stack is empty. Use json_peek() to get + * the value before calling this. + */ +static int json_pop(JSON_ENC *json) +{ + if (json->stack_end_byte == 0 && json->stack_end_bit == 0) + return 0; + + if (json->stack_end_bit == 0) { + --json->stack_end_byte; + json->stack_end_bit = CHAR_BIT - 1; + } else { + --json->stack_end_bit; + } + + return 1; +} + +/* + * Returns the bit on the top of the stack, or -1 if the stack is empty. + */ +static int json_peek(JSON_ENC *json) +{ + size_t obyte, obit; + + obyte = json->stack_end_byte; + obit = json->stack_end_bit; + if (obit == 0) { + if (obyte == 0) + return -1; + + --obyte; + obit = CHAR_BIT - 1; + } else { + --obit; + } + + return (json->stack[obyte] & (1U << obit)) != 0; +} + +/* + * JSON_ENC: Initialisation + * ======================== + */ + +enum { + STATE_PRE_KEY, + STATE_PRE_ITEM, + STATE_PRE_COMMA +}; + +static ossl_inline int in_ijson(const JSON_ENC *json) +{ + return (json->flags & JSON_FLAG_IJSON) != 0; +} + +static ossl_inline int in_seq(const JSON_ENC *json) +{ + return (json->flags & JSON_FLAG_SEQ) != 0; +} + +static ossl_inline int in_pretty(const JSON_ENC *json) +{ + return (json->flags & JSON_FLAG_PRETTY) != 0; +} + +int ossl_json_init(JSON_ENC *json, BIO *bio, uint32_t flags) +{ + memset(json, 0, sizeof(*json)); + json->flags = flags; + json->error = 1; + if (!wbuf_init(&json->wbuf, bio, 4096)) + return 0; + + json->state = STATE_PRE_COMMA; + return ossl_json_reset(json); +} + +void ossl_json_cleanup(JSON_ENC *json) +{ + wbuf_cleanup(&json->wbuf); + + if (json->stack != json->stack_small) + OPENSSL_free(json->stack); + + json->stack = NULL; +} + +int ossl_json_flush_cleanup(JSON_ENC *json) +{ + int ok = ossl_json_flush(json); + + ossl_json_cleanup(json); + return ok; +} + +int ossl_json_reset(JSON_ENC *json) +{ + wbuf_clean(&json->wbuf); + json->stack_end_byte = 0; + json->stack_end_bit = 0; + json->error = 0; + return 1; +} + +int ossl_json_flush(JSON_ENC *json) +{ + return wbuf_flush(&json->wbuf); +} + +int ossl_json_set_sink(JSON_ENC *json, BIO *bio) +{ + wbuf_set_bio(&json->wbuf, bio); + return 1; +} + +int ossl_json_in_error(JSON_ENC *json) +{ + return json->error; +} + +/* + * JSON Builder Calls + * ================== + */ + +static void json_write_qstring(JSON_ENC *json, const char *str); +static void json_indent(JSON_ENC *json); + +static ossl_inline int json_in_error(const JSON_ENC *json) +{ + return json->error; +} + +static void json_raise_error(JSON_ENC *json) +{ + json->error = 1; +} + +static void json_undefer(JSON_ENC *json) +{ + if (!json->defer_indent) + return; + + json_indent(json); +} + +static void json_write_char(JSON_ENC *json, char ch) +{ + if (json_in_error(json)) + return; + + json_undefer(json); + if (!wbuf_write_char(&json->wbuf, ch)) + json_raise_error(json); +} + +static void json_write_str(JSON_ENC *json, const char *s) +{ + if (json_in_error(json)) + return; + + json_undefer(json); + if (!wbuf_write_str(&json->wbuf, s)) + json_raise_error(json); +} + +static void json_indent(JSON_ENC *json) +{ + size_t i, depth; + + json->defer_indent = 0; + + if (!in_pretty(json)) + return; + + json_write_char(json, '\n'); + + depth = json->stack_end_byte * 8 + json->stack_end_bit; + for (i = 0; i < depth * 4; ++i) + json_write_str(json, " "); +} + +static int json_pre_item(JSON_ENC *json) +{ + int s; + + if (json_in_error(json)) + return 0; + + switch (json->state) { + case STATE_PRE_COMMA: + s = json_peek(json); + + if (s == 0) { + json_raise_error(json); + return 0; + } + + if (s == 1) { + json_write_char(json, ','); + if (json_in_error(json)) + return 0; + + json_indent(json); + } + + if (s < 0 && in_seq(json)) + json_write_char(json, '\x1E'); + + json->state = STATE_PRE_ITEM; + break; + + case STATE_PRE_ITEM: + break; + + case STATE_PRE_KEY: + default: + json_raise_error(json); + return 0; + } + + return 1; +} + +static void json_post_item(JSON_ENC *json) +{ + int s = json_peek(json); + + json->state = STATE_PRE_COMMA; + + if (s < 0 && in_seq(json)) + json_write_char(json, '\n'); +} + +/* + * Begin a composite structure (object or array). + * + * type: 0=object, 1=array. + */ +static void composite_begin(JSON_ENC *json, int type, char ch) +{ + if (!json_pre_item(json) + || !json_push(json, type)) + json_raise_error(json); + + json_write_char(json, ch); + json->defer_indent = 1; +} + +/* + * End a composite structure (object or array). + * + * type: 0=object, 1=array. Errors on mismatch. + */ +static void composite_end(JSON_ENC *json, int type, char ch) +{ + int was_defer = json->defer_indent; + + if (json_in_error(json)) + return; + + json->defer_indent = 0; + + if (json_peek(json) != type) + return; + + if (type == 0 && json->state == STATE_PRE_ITEM) { + json_raise_error(json); + return; + } + + if (!json_pop(json)) + return; + + if (!was_defer) + json_indent(json); + + json_write_char(json, ch); + json_post_item(json); +} + +/* Begin a new JSON object. */ +void ossl_json_object_begin(JSON_ENC *json) +{ + composite_begin(json, 0, '{'); + json->state = STATE_PRE_KEY; +} + +/* End a JSON obejct. Must be matched with a call to ossl_json_object_begin(). */ +void ossl_json_object_end(JSON_ENC *json) +{ + composite_end(json, 0, '}'); +} + +/* Begin a new JSON array. */ +void ossl_json_array_begin(JSON_ENC *json) +{ + composite_begin(json, 1, '['); + json->state = STATE_PRE_ITEM; +} + +/* End a JSON array. Must be matched with a call to ossl_json_array_end(). */ +void ossl_json_array_end(JSON_ENC *json) +{ + composite_end(json, 1, ']'); +} + +/* + * Encode a JSON key within an object. Pass a zero-terminated string, which can + * be freed immediately following the call to this function. + */ +void ossl_json_key(JSON_ENC *json, const char *key) +{ + if (json_in_error(json)) + return; + + if (json_peek(json) != 0) { + /* Not in object */ + json_raise_error(json); + return; + } + + if (json->state == STATE_PRE_COMMA) { + json_write_char(json, ','); + json->state = STATE_PRE_KEY; + } + + json_indent(json); + if (json->state != STATE_PRE_KEY) { + json_raise_error(json); + return; + } + + json_write_qstring(json, key); + if (json_in_error(json)) + return; + + json_write_char(json, ':'); + if (in_pretty(json)) + json_write_char(json, ' '); + + json->state = STATE_PRE_ITEM; +} + +/* Encode a JSON 'null' value. */ +void ossl_json_null(JSON_ENC *json) +{ + if (!json_pre_item(json)) + return; + + json_write_str(json, "null"); + json_post_item(json); +} + +void ossl_json_bool(JSON_ENC *json, int v) +{ + if (!json_pre_item(json)) + return; + + json_write_str(json, v > 0 ? "true" : "false"); + json_post_item(json); +} + +#define POW_53 (((int64_t)1) << 53) + +/* Encode a JSON integer from a uint64_t. */ +static void json_u64(JSON_ENC *json, uint64_t v, int noquote) +{ + char buf[22], *p = buf + sizeof(buf) - 1; + int quote = !noquote && in_ijson(json) && v > (uint64_t)(POW_53 - 1); + + if (!json_pre_item(json)) + return; + + if (quote) + json_write_char(json, '"'); + + if (v == 0) + p = "0"; + else + for (*p = '\0'; v > 0; v /= 10) + *--p = '0' + v % 10; + + json_write_str(json, p); + + if (quote) + json_write_char(json, '"'); + + json_post_item(json); +} + +void ossl_json_u64(JSON_ENC *json, uint64_t v) +{ + json_u64(json, v, 0); +} + +/* Encode a JSON integer from an int64_t. */ +void ossl_json_i64(JSON_ENC *json, int64_t value) +{ + uint64_t uv; + int quote; + + if (value >= 0) { + ossl_json_u64(json, (uint64_t)value); + return; + } + + if (!json_pre_item(json)) + return; + + quote = in_ijson(json) + && (value > POW_53 - 1 || value < -POW_53 + 1); + + if (quote) + json_write_char(json, '"'); + + json_write_char(json, '-'); + + uv = (value == INT64_MIN) + ? ((uint64_t)-(INT64_MIN + 1)) + 1 + : (uint64_t)-value; + json_u64(json, uv, /*noquote=*/1); + + if (quote && !json_in_error(json)) + json_write_char(json, '"'); +} + +/* Encode a JSON number from a 64-bit floating point value. */ +void ossl_json_f64(JSON_ENC *json, double value) +{ + char buf[32]; + + if (!json_pre_item(json)) + return; + + if (isnan(value) || isinf(value)) { + json_raise_error(json); + return; + } + + snprintf(buf, sizeof(buf), "%1.17g", value); + json_write_str(json, buf); + json_post_item(json); +} + +/* + * Encode a JSON UTF-8 string from a zero-terminated string. The string passed + * can be freed immediately following the call to this function. + */ +static ossl_inline int hex_digit(int v) +{ + return v >= 10 ? 'a' + (v - 10) : '0' + v; +} + +static ossl_inline void +json_write_qstring_inner(JSON_ENC *json, const char *str, size_t str_len, + int nul_term) +{ + char c, *o, obuf[7]; + int i; + size_t j; + + if (json_in_error(json)) + return; + + json_write_char(json, '"'); + + for (j = 0; (nul_term && *str != '\0') + || (!nul_term && j < str_len); ++str, ++j) { + c = *str; + switch (c) { + case '\n': o = "\\n"; break; + case '\r': o = "\\r"; break; + case '\t': o = "\\t"; break; + case '\b': o = "\\b"; break; + case '\f': o = "\\f"; break; + case '"': o = "\\\""; break; + case '\\': o = "\\\\"; break; + default: + if ((unsigned char)c >= 0x80) { + json_raise_error(json); + return; + } + if ((unsigned char)c < 0x20 || (unsigned char)c >= 0x7f) { + obuf[0] = '\\'; + obuf[1] = 'u'; + for (i = 0; i < 4; ++i) + obuf[2 + i] = hex_digit((c >> ((3 - i) * 4)) & 0xF); + obuf[6] = '\0'; + o = obuf; + } else { + json_write_char(json, c); + continue; + } + break; + } + + json_write_str(json, o); + } + + json_write_char(json, '"'); +} + +static void +json_write_qstring(JSON_ENC *json, const char *str) +{ + json_write_qstring_inner(json, str, 0, 1); +} + +static void +json_write_qstring_len(JSON_ENC *json, const char *str, size_t str_len) +{ + json_write_qstring_inner(json, str, str_len, 0); +} + +void ossl_json_str(JSON_ENC *json, const char *str) +{ + if (!json_pre_item(json)) + return; + + json_write_qstring(json, str); + json_post_item(json); +} + +void ossl_json_str_len(JSON_ENC *json, const char *str, size_t str_len) +{ + if (!json_pre_item(json)) + return; + + json_write_qstring_len(json, str, str_len); + json_post_item(json); +} + +/* + * Encode binary data as a lowercase hex string. data_len is the data length in + * bytes. + */ +void ossl_json_str_hex(JSON_ENC *json, const void *data, size_t data_len) +{ + const unsigned char *b = data, *end = b + data_len; + unsigned char c; + + if (!json_pre_item(json)) + return; + + json_write_char(json, '"'); + + for (; b < end; ++b) { + c = *b; + json_write_char(json, hex_digit(c >> 4)); + json_write_char(json, hex_digit(c & 0x0F)); + } + + json_write_char(json, '"'); + json_post_item(json); +} |