bser_write.c (410 lines of code) (raw):

#include <string.h> #include <stdarg.h> #include <assert.h> #include "bser.h" #include "bser_write.h" #define SIZE_U8 sizeof(uint8_t) #define SIZE_S8 sizeof(int8_t) #define SIZE_S16 sizeof(int16_t) #define SIZE_S32 sizeof(int32_t) #define SIZE_S64 sizeof(int64_t) #define SIZE_DBL sizeof(double) #define SIZE_MAGIC sizeof(char[2]) /* Interface for writing to a stream using a file or char[], or null */ typedef struct stream { /* Returns the number bytes were successfully written to the stream */ size_t (*write)(struct stream*, const void* buffer, size_t bytes); } stream_t; static size_t write_json(json_t* json, stream_t* stream); static size_t write_integer(json_t* json, stream_t* stream) { uint8_t tag; size_t bytes = 0; assert(json_is_integer(json)); json_int_t v = json_integer_value(json); int8_t i8 = (int8_t)v; if (i8 == v) { tag = BSER_TAG_INT8; if (stream->write(stream, &tag, SIZE_U8) == SIZE_U8 && stream->write(stream, &i8, SIZE_S8) == SIZE_S8) { bytes = SIZE_U8 + SIZE_S8; } } else { int16_t i16 = (int16_t)v; if (i16 == v) { tag = BSER_TAG_INT16; if (stream->write(stream, &tag, SIZE_U8) == SIZE_U8 && stream->write(stream, &i16, SIZE_S16) == SIZE_S16) { bytes = SIZE_U8 + SIZE_S16; } } else { int32_t i32 = (int32_t)v; if (i32 == v) { tag = BSER_TAG_INT32; if (stream->write(stream, &tag, SIZE_U8) == SIZE_U8 && stream->write(stream, &i32, SIZE_S32) == SIZE_S32) { bytes = SIZE_U8 + SIZE_S32; } } else { tag = BSER_TAG_INT64; if (stream->write(stream, &tag, SIZE_U8) == SIZE_U8 && stream->write(stream, &v, SIZE_S64) == SIZE_S64) { bytes = SIZE_U8 + SIZE_S64; } } } } return bytes; } static size_t write_string(json_t* json, stream_t* stream) { size_t bytes = 0; assert(json_is_string(json)); const char* chars = json_string_value(json); size_t len = strlen(chars); uint8_t tag = BSER_TAG_STRING; if (stream->write(stream, &tag, SIZE_U8) == SIZE_U8) { json_t* length_node = json_integer(len); size_t len_bytes = write_integer(length_node, stream); json_decref(length_node); if (len_bytes > 0 && stream->write(stream, chars, len) == len) { bytes = SIZE_U8 + len_bytes + len; } } return bytes; } static size_t write_object(json_t* json, stream_t* stream) { uint8_t tag = BSER_TAG_OBJECT; size_t bytes = 0; assert(json_is_object(json)); if (stream->write(stream, &tag, SIZE_U8) == SIZE_U8) { size_t total_bytes = SIZE_U8; json_t* length_node = json_integer(json_object_size(json)); size_t integer_length = write_integer(length_node, stream); total_bytes += integer_length; json_decref(length_node); if (integer_length > 0) { size_t val_bytes = 1; void *iter = json_object_iter(json); while (iter != NULL && val_bytes > 0) { val_bytes = 0; json_t* key_node = json_string(json_object_iter_key(iter)); size_t key_bytes = write_string(key_node, stream); total_bytes += key_bytes; json_decref(key_node); if (key_bytes > 0) { json_t* value = json_object_iter_value(iter); val_bytes = write_json(value, stream); total_bytes += val_bytes; } iter = json_object_iter_next(json, iter); } if (iter == NULL) { bytes = total_bytes; } } } return bytes; } static int can_be_compact_array(json_t* json) { assert(json_is_array(json)); size_t length = json_array_size(json); int is_all_objects = 1; for (int i = 0; i < length && is_all_objects; ++i) { json_t* elem = json_array_get(json, i); is_all_objects &= json_is_object(elem); } return length > 1 && is_all_objects; } static int string_array_contains(json_t* array, const char* string) { assert(json_is_array(array)); int is_present = 0; for (int i = 0; i < json_array_size(array) && is_present == 0; ++i) { json_t* str = json_array_get(array, i); assert(json_is_string(str)); is_present = !strcmp(json_string_value(str), string); } return is_present; } static json_t* build_compact_array_header(json_t* json) { assert(json_is_array(json)); size_t length = json_array_size(json); json_t* compact_header = json_array(); for (int i = 0; i < length; ++i) { json_t* obj = json_array_get(json, i); assert(json_is_object(obj)); void* iter = json_object_iter(obj); while (iter) { const char* key = json_object_iter_key(iter); if (!string_array_contains(compact_header, key)) { json_array_append_new(compact_header, json_string(key)); } iter = json_object_iter_next(obj, iter); } } return compact_header; } static size_t write_array(json_t* json, stream_t* stream); static size_t write_compact_object(json_t* object, json_t* header, stream_t* stream) { size_t bytes = 0; size_t field_bytes = 1; size_t fields_bytes = 0; int i; assert(json_is_array(header)); size_t header_length = json_array_size(header); for (i = 0; i < header_length && field_bytes > 0; ++i) { const char* key = json_string_value(json_array_get(header, i)); json_t* value = json_object_get(object, key); if (value == NULL) { uint8_t no_field_tag = BSER_TAG_NO_FIELD; field_bytes = stream->write(stream, &no_field_tag, SIZE_U8); } else { field_bytes = write_json(value, stream); } fields_bytes += field_bytes; } if (i == header_length) { bytes = fields_bytes; } return bytes; } static size_t write_compact_objects(json_t* objects, json_t* header, stream_t* stream) { size_t bytes = 0; size_t obj_bytes = 1; size_t objs_bytes = 0; int i; assert(json_is_array(objects)); size_t array_length = json_array_size(objects); for (i = 0; i < array_length && obj_bytes > 0; ++i) { json_t* obj = json_array_get(objects, i); obj_bytes = write_compact_object(obj, header, stream); objs_bytes += obj_bytes; } if (i == array_length) { bytes = objs_bytes; } return bytes; } static size_t write_compact_array(json_t* array, stream_t* stream) { size_t bytes = 0; assert(json_is_array(array)); json_t* header = build_compact_array_header(array); uint8_t tag = BSER_TAG_COMPACT_ARRAY; if (stream->write(stream, &tag, SIZE_U8) == SIZE_U8) { size_t header_bytes = write_array(header, stream); if (header_bytes > 0) { size_t array_length = json_array_size(array); json_t* array_length_node = json_integer(array_length); size_t integer_size = write_integer(array_length_node, stream); if (integer_size > 0) { size_t written = write_compact_objects(array, header, stream); if (written > 0) { bytes = SIZE_U8 + header_bytes + integer_size + written; } } json_decref(array_length_node); } } json_decref(header); return bytes; } static size_t write_array(json_t* json, stream_t* stream) { size_t bytes = 0; if (can_be_compact_array(json)) { bytes = write_compact_array(json, stream); } else { uint8_t tag = BSER_TAG_ARRAY; if (stream->write(stream, &tag, SIZE_U8) == SIZE_U8) { size_t int_bytes; size_t length = json_array_size(json); json_t* length_node = json_integer(length); int_bytes = write_integer(length_node, stream); json_decref(length_node); if (int_bytes > 0) { int i; size_t elem_bytes = 1; size_t elems_bytes = 0; for (i = 0; i < length && elem_bytes > 0; ++i) { elem_bytes = write_json(json_array_get(json, i), stream); elems_bytes += elem_bytes; } if (i == length) { bytes = SIZE_U8 + int_bytes + elems_bytes; } } } } return bytes; } static size_t write_real(json_t* json, stream_t* stream) { uint8_t op = BSER_TAG_REAL; assert(json_is_real(json)); double val = json_real_value(json); size_t bytes = 0; if (stream->write(stream, &op, SIZE_U8) == SIZE_U8) { if (stream->write(stream, &val, SIZE_DBL) == SIZE_DBL) { bytes = SIZE_U8 + SIZE_DBL; } } return bytes; } static size_t write_true(json_t* json, stream_t* stream) { uint8_t op = BSER_TAG_TRUE; return stream->write(stream, &op, SIZE_U8); } static size_t write_false(json_t* json, stream_t* stream) { uint8_t op = BSER_TAG_FALSE; return stream->write(stream, &op, SIZE_U8); } static size_t write_null(json_t* json, stream_t* stream) { uint8_t op = BSER_TAG_NULL; return stream->write(stream, &op, SIZE_U8); } static size_t write_json(json_t* json, stream_t* stream) { switch (json_typeof(json)) { case JSON_OBJECT: return write_object(json, stream); case JSON_ARRAY: return write_array(json, stream); case JSON_STRING: return write_string(json, stream); case JSON_INTEGER: return write_integer(json, stream); case JSON_REAL: return write_real(json, stream); case JSON_TRUE: return write_true(json, stream); case JSON_FALSE: return write_false(json, stream); case JSON_NULL: return write_null(json, stream); default: return 0; } } /* Null stream is used to count the number of bytes that would be written. */ struct null_stream { stream_t stream; }; static size_t null_stream_write(stream_t* s, const void* data, size_t nb) { return nb; } /* For writing to a memory buffer stream */ struct buffer_stream { stream_t stream; bser_buffer_t buffer; }; static size_t buffer_stream_write(stream_t* s, const void* data, size_t nb) { size_t result = 0; struct buffer_stream* stream = (struct buffer_stream*)s; if (stream->buffer.cursor + nb <= stream->buffer.datalen) { memcpy(stream->buffer.data + stream->buffer.cursor, data, nb); stream->buffer.cursor += nb; result = nb; } return result; } /* For writing a FILE stream */ struct file_stream { stream_t stream; FILE* file; size_t position; }; static size_t file_stream_write(stream_t* s, const void* data, size_t nb) { struct file_stream* stream = (struct file_stream*)s; size_t count = fwrite(data, SIZE_U8, nb, stream->file); if (count == nb) { stream->position += nb; return nb; } else { return 0; } } size_t bser_encoding_size(json_t* node) { struct null_stream stream; stream.stream.write = null_stream_write; return write_json(node, &stream.stream); } size_t bser_header_size(size_t content_size) { json_t* node = json_integer(content_size); size_t sz = SIZE_MAGIC + bser_encoding_size(node); json_decref(node); return sz; } static size_t write_header(size_t content_size, stream_t* stream) { const uint8_t magic[] = { 0x00, 0x01 }; size_t node_bytes; json_t* content_size_node = json_integer(content_size); if (stream->write(stream, magic, SIZE_MAGIC) == SIZE_MAGIC && (node_bytes = write_json(content_size_node, stream)) > 0) { return SIZE_MAGIC + node_bytes; } else { return 0; } } static size_t write_pdu(json_t* root, size_t content_size, stream_t* stream) { size_t hdr_bytes; size_t content_bytes; if ((hdr_bytes = write_header(content_size, stream)) > 0 && (content_bytes = write_json(root, stream)) == content_size) { return hdr_bytes + content_bytes; } else { return 0; } } size_t bser_write_to_buffer( json_t* root, size_t content_size, void* buffer, size_t buflen) { struct buffer_stream stream; assert(buffer != NULL); stream.stream.write = buffer_stream_write; stream.buffer.data = buffer; stream.buffer.datalen = buflen; stream.buffer.cursor = 0; return write_pdu(root, content_size, &stream.stream); } size_t bser_write_to_file(json_t* root, FILE* file) { size_t content_size; size_t bytes; struct file_stream stream; assert(file != NULL); stream.stream.write = file_stream_write; stream.file = file; stream.position = 0; content_size = bser_encoding_size(root); bytes = write_pdu(root, content_size, &stream.stream); fflush(file); return bytes; }