diff --git a/lib/webfuse/impl/json/writer.c b/lib/webfuse/impl/json/writer.c new file mode 100644 index 0000000..40834da --- /dev/null +++ b/lib/webfuse/impl/json/writer.c @@ -0,0 +1,516 @@ +#include "webfuse/impl/json/writer.h" +#include "webfuse/impl/util/base64.h" + +#include +#include +#include + +#define WF_JSON_WRITER_INITIAL_MAX_LEVEL 7 + +#define WF_JSON_WRITER_SEPARATOR_SIZE ( 1) +#define WF_JSON_WRITER_INT_SIZE (31 + WF_JSON_WRITER_SEPARATOR_SIZE) +#define WF_JSON_WRITER_NULL_SIZE ( 4 + WF_JSON_WRITER_SEPARATOR_SIZE) +#define WF_JSON_WRITER_BOOL_SIZE ( 5 + WF_JSON_WRITER_SEPARATOR_SIZE) +#define WF_JSON_WRITER_ADDITIONAL_STRING_SIZE ( 2 + WF_JSON_WRITER_SEPARATOR_SIZE) +#define WF_JSON_WRITER_BEGIN_ARRAY_SIZE ( 1 + WF_JSON_WRITER_SEPARATOR_SIZE) +#define WF_JSON_WRITER_END_ARRAY_SIZE ( 1) +#define WF_JSON_WRITER_BEGIN_OBJECT_SIZE ( 1 + WF_JSON_WRITER_SEPARATOR_SIZE) +#define WF_JSON_WRITER_END_OBJECT_SIZE ( 1) +#define WF_JSON_WRITER_ADDITIONAL_OBJECT_KEY_SIZE ( 4) + +enum wf_json_writer_state +{ + WF_JSON_WRITER_STATE_INIT, + WF_JSON_WRITER_STATE_ARRAY_FIRST, + WF_JSON_WRITER_STATE_ARRAY_NEXT, + WF_JSON_WRITER_STATE_OBJECT_FIRST, + WF_JSON_WRITER_STATE_OBJECT_NEXT +}; + +struct wf_json_writer +{ + enum wf_json_writer_state * state; + size_t max_level; + size_t level; + size_t capacity; + size_t pre; + size_t offset; + char * data; + char * raw_data; +}; + +// -- + +static char +wf_impl_json_writer_get_esc( + char c); + +static void +wf_impl_json_write_raw_char( + struct wf_json_writer * writer, + char value); + +static void +wf_impl_json_write_raw( + struct wf_json_writer * writer, + char const * value, + size_t length); + +static void +wf_impl_json_reserve( + struct wf_json_writer * writer, + size_t needed); + +static void +wf_impl_json_begin_value( + struct wf_json_writer * writer); + +static void +wf_impl_json_end_value( + struct wf_json_writer * writer); + +static void +wf_impl_json_push_state( + struct wf_json_writer * writer, + enum wf_json_writer_state state); + +static void +wf_impl_json_pop_state( + struct wf_json_writer * writer); + +// -- + +struct wf_json_writer * +wf_impl_json_writer_create( + size_t initial_capacity, + size_t pre) +{ + struct wf_json_writer * writer = malloc(sizeof(struct wf_json_writer)); + writer->level = 0; + writer->max_level = WF_JSON_WRITER_INITIAL_MAX_LEVEL; + writer->state = malloc((1 + writer->max_level) * sizeof(enum wf_json_writer_state)); + writer->state[writer->level] = WF_JSON_WRITER_STATE_INIT; + writer->pre = pre; + writer->offset = 0; + writer->capacity = initial_capacity; + writer->raw_data = malloc(writer->pre + writer->capacity); + writer->data = &(writer->raw_data[pre]); + + return writer; +} + +void +wf_impl_json_writer_dispose( + struct wf_json_writer * writer) +{ + free(writer->raw_data); + free(writer->state); + free(writer); +} + +void +wf_impl_json_writer_reset( + struct wf_json_writer * writer) +{ + writer->level = 0; + writer->state[writer->level] = WF_JSON_WRITER_STATE_INIT; + writer->offset = 0; +} + +char * +wf_impl_json_writer_take( + struct wf_json_writer * writer, + size_t * size) +{ + wf_impl_json_reserve(writer, 1); + writer->data[writer->offset] = '\0'; + + writer->raw_data = NULL; + + if (NULL != size) + { + *size = writer->offset; + } + + return writer->data; +} + +void +wf_impl_json_write_null( + struct wf_json_writer * writer) +{ + wf_impl_json_reserve(writer, WF_JSON_WRITER_NULL_SIZE); + wf_impl_json_begin_value(writer); + wf_impl_json_write_raw(writer, "null", 4); + wf_impl_json_end_value(writer); +} + +void +wf_impl_json_write_bool( + struct wf_json_writer * writer, + bool value) +{ + wf_impl_json_reserve(writer, WF_JSON_WRITER_BOOL_SIZE); + wf_impl_json_begin_value(writer); + + if (value) + { + wf_impl_json_write_raw(writer, "true", 4); + } + else + { + wf_impl_json_write_raw(writer, "false", 5); + } + + wf_impl_json_end_value(writer); +} + +void +wf_impl_json_write_int( + struct wf_json_writer * writer, + int value) +{ + wf_impl_json_reserve(writer, WF_JSON_WRITER_INT_SIZE); + wf_impl_json_begin_value(writer); + + bool const is_signed = (0 > value); + char buffer[WF_JSON_WRITER_INT_SIZE]; + size_t offset = WF_JSON_WRITER_INT_SIZE; + buffer[--offset] = '\0'; + if (is_signed) + { + if (INT_MIN == value) + { + char const actual = (char) abs(value % 10); + buffer[--offset] = (char) ('0' + actual); + value /= 10; + } + + value =- value; + } + + do + { + char const actual = (char) (value % 10); + buffer[--offset] = (char) ('0' + actual); + value /= 10; + } while (0 != value); + + if (is_signed) + { + buffer[--offset] = '-'; + } + + size_t const length = (WF_JSON_WRITER_INT_SIZE - offset - 1); + wf_impl_json_write_raw(writer, &(buffer[offset]), length); + + wf_impl_json_end_value(writer); +} + +void +wf_impl_json_write_string( + struct wf_json_writer * writer, + char const * value) +{ + size_t length = strlen(value); + wf_impl_json_reserve(writer, length + WF_JSON_WRITER_ADDITIONAL_STRING_SIZE); + wf_impl_json_begin_value(writer); + + wf_impl_json_write_raw_char(writer, '\"'); + for (size_t i = 0; i < length; i++) + { + char const c = value[i]; + if ((' ' <= c) && ('\\' != c) && ('\"' != c)) + { + wf_impl_json_write_raw_char(writer, c); + } + else + { + char esc = wf_impl_json_writer_get_esc(c); + + wf_impl_json_reserve(writer, (length - i) + 2); + wf_impl_json_write_raw_char(writer, '\\'); + wf_impl_json_write_raw_char(writer, esc); + } + + } + wf_impl_json_write_raw_char(writer, '\"'); + + wf_impl_json_end_value(writer); +} + +void +wf_impl_json_write_string_nocheck( + struct wf_json_writer * writer, + char const * value) +{ + size_t length = strlen(value); + wf_impl_json_reserve(writer, length + WF_JSON_WRITER_ADDITIONAL_STRING_SIZE); + wf_impl_json_begin_value(writer); + + wf_impl_json_write_raw_char(writer, '\"'); + wf_impl_json_write_raw(writer, value, length); + wf_impl_json_write_raw_char(writer, '\"'); + + wf_impl_json_end_value(writer); + +} + +void +wf_impl_json_write_bytes( + struct wf_json_writer * writer, + char const * data, + size_t length) +{ + size_t encoded_length = wf_impl_base64_encoded_size(length); + wf_impl_json_reserve(writer, length + WF_JSON_WRITER_ADDITIONAL_STRING_SIZE); + wf_impl_json_begin_value(writer); + + wf_impl_json_write_raw_char(writer, '\"'); + wf_impl_base64_encode((uint8_t const *) data, length, &(writer->data[writer->offset]), encoded_length); + writer->offset = encoded_length; + wf_impl_json_write_raw_char(writer, '\"'); + + wf_impl_json_end_value(writer); +} + +void +wf_impl_json_write_array_begin( + struct wf_json_writer * writer) +{ + wf_impl_json_reserve(writer, WF_JSON_WRITER_BEGIN_ARRAY_SIZE); + wf_impl_json_begin_value(writer); + wf_impl_json_push_state(writer, WF_JSON_WRITER_STATE_ARRAY_FIRST); + wf_impl_json_write_raw_char(writer, '['); +} + +void +wf_impl_json_write_array_end( + struct wf_json_writer * writer) +{ + wf_impl_json_reserve(writer, WF_JSON_WRITER_END_ARRAY_SIZE); + wf_impl_json_write_raw_char(writer, ']'); + wf_impl_json_pop_state(writer); + wf_impl_json_end_value(writer); +} + +void +wf_impl_json_write_object_begin( + struct wf_json_writer * writer) +{ + wf_impl_json_reserve(writer, WF_JSON_WRITER_BEGIN_OBJECT_SIZE); + wf_impl_json_begin_value(writer); + wf_impl_json_push_state(writer, WF_JSON_WRITER_STATE_OBJECT_FIRST); + wf_impl_json_write_raw_char(writer, '{'); +} + +void +wf_impl_json_write_object_end( + struct wf_json_writer * writer) +{ + wf_impl_json_reserve(writer, WF_JSON_WRITER_END_OBJECT_SIZE); + wf_impl_json_write_raw_char(writer, '}'); + wf_impl_json_pop_state(writer); + wf_impl_json_end_value(writer); +} + +void +wf_impl_json_write_object_key( + struct wf_json_writer * writer, + char const * key) +{ + size_t length = strlen(key); + wf_impl_json_reserve(writer, length + WF_JSON_WRITER_ADDITIONAL_OBJECT_KEY_SIZE); + + if (WF_JSON_WRITER_STATE_OBJECT_NEXT == writer->state[writer->level]) + { + wf_impl_json_write_raw_char(writer, ','); + } + else + { + writer->state[writer->level] = WF_JSON_WRITER_STATE_OBJECT_NEXT; + } + + wf_impl_json_write_raw_char(writer, '\"'); + wf_impl_json_write_raw(writer, key, length); + wf_impl_json_write_raw_char(writer, '\"'); + wf_impl_json_write_raw_char(writer, ':'); +} + +void +wf_impl_json_write_object_null( + struct wf_json_writer * writer, + char const * key) +{ + wf_impl_json_write_object_key(writer, key); + wf_impl_json_write_null(writer); +} + +void +wf_impl_json_write_object_bool( + struct wf_json_writer * writer, + char const * key, + bool value) +{ + wf_impl_json_write_object_key(writer, key); + wf_impl_json_write_bool(writer, value); +} + +void +wf_impl_json_write_object_int( + struct wf_json_writer * writer, + char const * key, + int value) +{ + wf_impl_json_write_object_key(writer, key); + wf_impl_json_write_int(writer, value); +} + +void +wf_impl_json_write_object_string( + struct wf_json_writer * writer, + char const * key, + char const * value) +{ + wf_impl_json_write_object_key(writer, key); + wf_impl_json_write_string(writer, value); +} + +void +wf_impl_json_write_object_string_nocheck( + struct wf_json_writer * writer, + char const * key, + char const * value) +{ + wf_impl_json_write_object_key(writer, key); + wf_impl_json_write_string_nocheck(writer, value); +} + +void +wf_impl_json_write_object_bytes( + struct wf_json_writer * writer, + char const * key, + char const * data, + size_t length) +{ + wf_impl_json_write_object_key(writer, key); + wf_impl_json_write_bytes(writer, data, length); +} + +void +wf_impl_json_write_object_begin_array( + struct wf_json_writer * writer, + char const * key) +{ + wf_impl_json_write_object_key(writer, key); + wf_impl_json_write_array_begin(writer); +} + +void +wf_impl_json_write_object_begin_object( + struct wf_json_writer * writer, + char const * key) +{ + wf_impl_json_write_object_key(writer, key); + wf_impl_json_write_array_end(writer); +} + +// -- + +static char +wf_impl_json_writer_get_esc( + char c) +{ + switch (c) + { + case '\b': return 'b'; + case '\f': return 'f'; + case '\n': return 'n'; + case '\r': return 'r'; + case '\t': return 't'; + default: + return c; + } +} + +static void +wf_impl_json_write_raw_char( + struct wf_json_writer * writer, + char value) +{ + writer->data[writer->offset++] = value; +} + +static void +wf_impl_json_write_raw( + struct wf_json_writer * writer, + char const * value, + size_t length) +{ + memcpy(&(writer->data[writer->offset]), value, length); + writer->offset += length; +} + +static void +wf_impl_json_reserve( + struct wf_json_writer * writer, + size_t needed) +{ + if ((writer->capacity - writer->offset) < needed) + { + size_t new_capacity = 2 * writer->capacity; + while ((new_capacity - writer->offset) < needed) + { + new_capacity *= 2; + } + + writer->raw_data = realloc(writer->raw_data, writer->pre + new_capacity); + writer->data = &(writer->raw_data[writer->pre]); + writer->capacity = new_capacity; + } +} + +static void +wf_impl_json_begin_value( + struct wf_json_writer * writer) +{ + if (WF_JSON_WRITER_STATE_ARRAY_NEXT == writer->state[writer->level]) + { + wf_impl_json_write_raw_char(writer, ','); + } +} + +static void +wf_impl_json_end_value( + struct wf_json_writer * writer) +{ + if (WF_JSON_WRITER_STATE_ARRAY_FIRST == writer->state[writer->level]) + { + writer->state[writer->level] = WF_JSON_WRITER_STATE_ARRAY_NEXT; + } +} + +static void +wf_impl_json_push_state( + struct wf_json_writer * writer, + enum wf_json_writer_state state) +{ + if (writer->level >= writer->max_level) + { + writer->max_level *= 2; + writer->state = realloc(writer->state, (1 + writer->max_level) * sizeof(enum wf_json_writer_state)); + } + + writer->level++; + writer->state[writer->level] = state; +} + +static void +wf_impl_json_pop_state( + struct wf_json_writer * writer) +{ + if (writer->level > 0) + { + writer->level--; + } +} diff --git a/lib/webfuse/impl/json/writer.h b/lib/webfuse/impl/json/writer.h new file mode 100644 index 0000000..e7d937c --- /dev/null +++ b/lib/webfuse/impl/json/writer.h @@ -0,0 +1,137 @@ +#ifndef WF_IMPL_JSON_WRITER_H +#define WF_IMPL_JSON_WRITER_H + +#ifndef __cplusplus +#include +#include +#else +#include +#endif + +#ifdef __cplusplus +extern "C" +{ +#endif + +struct wf_json_writer; + +extern struct wf_json_writer * +wf_impl_json_writer_create( + size_t initial_capacity, + size_t pre); + +extern void +wf_impl_json_writer_dispose( + struct wf_json_writer * writer); + +extern void +wf_impl_json_writer_reset( + struct wf_json_writer * writer); + +extern char * +wf_impl_json_writer_take( + struct wf_json_writer * writer, + size_t * size); + +extern void +wf_impl_json_write_null( + struct wf_json_writer * writer); + +extern void +wf_impl_json_write_bool( + struct wf_json_writer * writer, + bool value); + +extern void +wf_impl_json_write_int( + struct wf_json_writer * writer, + int value); + +extern void +wf_impl_json_write_string( + struct wf_json_writer * writer, + char const * value); + +extern void +wf_impl_json_write_string_nocheck( + struct wf_json_writer * writer, + char const * value); + +extern void +wf_impl_json_write_bytes( + struct wf_json_writer * writer, + char const * data, + size_t length); + +extern void +wf_impl_json_write_array_begin( + struct wf_json_writer * writer); + +extern void +wf_impl_json_write_array_end( + struct wf_json_writer * writer); + +extern void +wf_impl_json_write_object_begin( + struct wf_json_writer * writer); + +extern void +wf_impl_json_write_object_end( + struct wf_json_writer * writer); + +extern void +wf_impl_json_write_object_key( + struct wf_json_writer * writer, + char const * key); + +extern void +wf_impl_json_write_object_null( + struct wf_json_writer * writer, + char const * key); + +extern void +wf_impl_json_write_object_bool( + struct wf_json_writer * writer, + char const * key, + bool value); + +extern void +wf_impl_json_write_object_int( + struct wf_json_writer * writer, + char const * key, + int value); + +extern void +wf_impl_json_write_object_string( + struct wf_json_writer * writer, + char const * key, + char const * value); + +extern void +wf_impl_json_write_object_string_nocheck( + struct wf_json_writer * writer, + char const * key, + char const * value); + +extern void +wf_impl_json_write_object_bytes( + struct wf_json_writer * writer, + char const * key, + char const * data, + size_t length); + +extern void +wf_impl_json_write_object_begin_array( + struct wf_json_writer * writer, + char const * key); + +extern void +wf_impl_json_write_object_begin_object( + struct wf_json_writer * writer, + char const * key); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/meson.build b/meson.build index 604d268..1a04770 100644 --- a/meson.build +++ b/meson.build @@ -28,6 +28,7 @@ webfuse_static = static_library('webfuse', 'lib/webfuse/impl/timer/manager.c', 'lib/webfuse/impl/timer/timepoint.c', 'lib/webfuse/impl/timer/timer.c', + 'lib/webfuse/impl/json/writer.c', 'lib/webfuse/impl/jsonrpc/proxy.c', 'lib/webfuse/impl/jsonrpc/proxy_request_manager.c', 'lib/webfuse/impl/jsonrpc/proxy_variadic.c', @@ -115,6 +116,7 @@ test_certs_dep = declare_dependency( sources: [test_server_certs, test_client_certs]) alltests = executable('alltests', + 'test/webfuse/json/test_writer.cc', 'test/webfuse/jsonrpc/mock_timer_callback.cc', 'test/webfuse/jsonrpc/mock_timer.cc', 'test/webfuse/jsonrpc/test_is_request.cc', diff --git a/test/webfuse/json/test_writer.cc b/test/webfuse/json/test_writer.cc new file mode 100644 index 0000000..421cdfd --- /dev/null +++ b/test/webfuse/json/test_writer.cc @@ -0,0 +1,196 @@ +#include "webfuse/impl/json/writer.h" +#include +#include +#include + +namespace +{ + +class writer +{ +public: + writer(size_t initial_capacity = 128) + : writer_(wf_impl_json_writer_create(initial_capacity, 0)) + { + } + + ~writer() + { + wf_impl_json_writer_dispose(writer_); + } + + operator wf_json_writer * () + { + return writer_; + } + + std::string take() + { + char * text = wf_impl_json_writer_take(writer_, nullptr); + std::string result = text; + free(text); + + return result; + } + +private: + wf_json_writer * writer_; +}; + +} + +TEST(json_writer, write_null) +{ + writer writer; + + wf_impl_json_write_null(writer); + ASSERT_STREQ("null", writer.take().c_str()); +} + +TEST(json_writer, write_true) +{ + writer writer; + + wf_impl_json_write_bool(writer, true); + ASSERT_STREQ("true", writer.take().c_str()); +} + +TEST(json_writer, write_false) +{ + writer writer; + + wf_impl_json_write_bool(writer, false); + ASSERT_STREQ("false", writer.take().c_str()); +} + +TEST(json_writer, positive_int) +{ + writer writer; + + wf_impl_json_write_int(writer, 42); + ASSERT_STREQ("42", writer.take().c_str()); +} + +TEST(json_writer, int_max) +{ + writer writer; + std::string int_max = std::to_string(INT_MAX); + + wf_impl_json_write_int(writer, INT_MAX); + ASSERT_EQ(int_max, writer.take()); +} + +TEST(json_writer, negative_int) +{ + writer writer; + + wf_impl_json_write_int(writer, -12345); + ASSERT_STREQ("-12345", writer.take().c_str()); +} + +TEST(json_writer, int_min) +{ + writer writer; + std::string int_min = std::to_string(INT_MIN); + + wf_impl_json_write_int(writer, INT_MIN); + ASSERT_EQ(int_min, writer.take()); +} + +TEST(json_writer, write_string) +{ + writer writer; + + wf_impl_json_write_string(writer, "brummni"); + ASSERT_STREQ("\"brummni\"", writer.take().c_str()); +} + +TEST(json_writer, write_string_escape) +{ + writer writer; + + wf_impl_json_write_string(writer, "_\"_\\_\r_\n_\t_\b_\f_"); + ASSERT_STREQ("\"_\\\"_\\\\_\\r_\\n_\\t_\\b_\\f_\"", writer.take().c_str()); +} + +TEST(json_writer, write_string_nocheck) +{ + writer writer; + + wf_impl_json_write_string_nocheck(writer, "_\"_\\_\r_\n_\t_\b_\f_"); + ASSERT_STREQ("\"_\"_\\_\r_\n_\t_\b_\f_\"", writer.take().c_str()); +} + +TEST(json_writer, write_empty_array) +{ + writer writer; + + wf_impl_json_write_array_begin(writer); + wf_impl_json_write_array_end(writer); + ASSERT_STREQ("[]", writer.take().c_str()); +} + +TEST(json_writer, write_complex_array) +{ + writer writer; + + wf_impl_json_write_array_begin(writer); + wf_impl_json_write_null(writer); + wf_impl_json_write_bool(writer, true); + + wf_impl_json_write_array_begin(writer); + wf_impl_json_write_int(writer, 0); + wf_impl_json_write_string(writer, "foo"); + wf_impl_json_write_array_begin(writer); + + wf_impl_json_write_array_end(writer); + wf_impl_json_write_array_end(writer); + wf_impl_json_write_array_end(writer); + + ASSERT_STREQ("[null,true,[0,\"foo\",[]]]", writer.take().c_str()); +} + +TEST(json_writer, write_empty_object) +{ + writer writer; + + wf_impl_json_write_object_begin(writer); + wf_impl_json_write_object_end(writer); + ASSERT_STREQ("{}", writer.take().c_str()); +} + +TEST(json_writer, write_comple_object) +{ + writer writer; + + wf_impl_json_write_object_begin(writer); + wf_impl_json_write_object_string(writer, "method", "add"); + wf_impl_json_write_object_begin_array(writer, "params"); + wf_impl_json_write_int(writer, 1); + wf_impl_json_write_int(writer, 2); + wf_impl_json_write_array_end(writer); + wf_impl_json_write_object_int(writer, "id", 42); + wf_impl_json_write_object_end(writer); + ASSERT_STREQ("{\"method\":\"add\",\"params\":[1,2],\"id\":42}", writer.take().c_str()); +} + +TEST(json_writer, write_deep_nested_array) +{ + writer writer; + size_t count = 10; + std::string expected = ""; + + for (size_t i = 0; i < count; i++) + { + wf_impl_json_write_array_begin(writer); + expected += "["; + } + + for (size_t i = 0; i < count; i++) + { + wf_impl_json_write_array_end(writer); + expected += "]"; + } + + ASSERT_EQ(expected, writer.take()); +}