openssl/ssl/quic/json_enc.c
Hugo Landau 9c89b9fe1b 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)
2024-02-02 11:49:34 +00:00

723 lines
15 KiB
C

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