/* Copyright 2011-2021 David Robillard Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "node.h" #include "namespaces.h" #include "string_utils.h" #include "system.h" #include "exess/exess.h" #include "serd/serd.h" #include #include #include #include #include #ifndef NDEBUG # define MUST_SUCCEED(status) assert(!(status)) #else # define MUST_SUCCEED(status) ((void)(status)) #endif static const SerdNodeFlags meta_mask = (SERD_HAS_DATATYPE | SERD_HAS_LANGUAGE); // Round size up to an even multiple of the node alignment static size_t serd_node_pad_size(const size_t size) { const size_t n_trailing = size % serd_node_align; const size_t n_pad = n_trailing ? (serd_node_align - n_trailing) : 0u; return size + n_pad; } static void serd_node_check_padding(const SerdNode* node) { (void)node; #ifndef NDEBUG if (node) { const size_t padded_length = serd_node_pad_length(node->length); for (size_t i = 0; i < padded_length - node->length; ++i) { assert(serd_node_buffer_c(node)[node->length + i] == '\0'); } serd_node_check_padding(serd_node_datatype(node)); serd_node_check_padding(serd_node_language(node)); } #endif } size_t serd_node_total_size(const SerdNode* const node) { const size_t real_length = serd_node_pad_length(node->length); const size_t base_size = sizeof(SerdNode) + real_length; if (!(node->flags & meta_mask)) { return base_size; } const SerdNode* const meta = serd_node_meta_c(node); const size_t meta_real_length = serd_node_pad_length(meta->length); return base_size + sizeof(SerdNode) + meta_real_length; } SerdNode* serd_node_malloc(const size_t size) { SerdNode* const node = (SerdNode*)serd_calloc_aligned(serd_node_align, serd_node_pad_size(size)); assert((uintptr_t)node % serd_node_align == 0); return node; } SerdNode* serd_node_try_malloc(const SerdWriteResult r) { return (r.status && r.status != SERD_ERR_OVERFLOW) ? NULL : serd_node_malloc(r.count); } void serd_node_set(SerdNode** const dst, const SerdNode* const src) { if (!src) { serd_free_aligned(*dst); *dst = NULL; return; } const size_t size = serd_node_total_size(src); if (!*dst || serd_node_total_size(*dst) < size) { serd_free_aligned(*dst); *dst = (SerdNode*)serd_calloc_aligned(serd_node_align, size); } assert(*dst); memcpy(*dst, src, size); } /** Zero node padding. This is used for nodes which live in re-used stack memory during reading, which must be normalized before being passed to a sink so comparison will work correctly. */ void serd_node_zero_pad(SerdNode* node) { char* buf = serd_node_buffer(node); const size_t padded_length = serd_node_pad_length(node->length); memset(buf + node->length, 0, padded_length - node->length); if (node->flags & (SERD_HAS_DATATYPE | SERD_HAS_LANGUAGE)) { serd_node_zero_pad(serd_node_meta(node)); } } static SerdWriteResult result(const SerdStatus status, const size_t count) { const SerdWriteResult result = {status, count}; return result; } static SerdWriteResult serd_node_construct_simple(const size_t buf_size, void* const buf, const SerdNodeType type, const SerdNodeFlags flags, const SerdStringView string) { const size_t total_size = sizeof(SerdNode) + serd_node_pad_length(string.len); if (!buf || total_size > buf_size) { return result(SERD_ERR_OVERFLOW, total_size); } SerdNode* const node = (SerdNode*)buf; node->length = string.len; node->flags = flags; node->type = type; if (string.buf) { memcpy(serd_node_buffer(node), string.buf, string.len); } serd_node_zero_pad(node); return result(SERD_SUCCESS, total_size); } SerdWriteResult serd_node_construct_token(const size_t buf_size, void* const buf, const SerdNodeType type, const SerdStringView string) { return serd_node_construct_simple(buf_size, buf, type, 0u, string); } bool is_langtag(const SerdStringView string) { // First character must be a letter size_t i = 0; if (!string.len || !is_alpha(string.buf[i])) { return false; } // First component must be all letters while (++i < string.len && string.buf[i] && string.buf[i] != '-') { if (!is_alpha(string.buf[i])) { return false; } } // Following components can have letters and digits while (i < string.len && string.buf[i] == '-') { while (++i < string.len && string.buf[i] && string.buf[i] != '-') { const char c = string.buf[i]; if (!is_alpha(c) && !is_digit(c)) { return false; } } } return true; } SerdWriteResult serd_node_construct_literal(const size_t buf_size, void* const buf, const SerdStringView string, const SerdNodeFlags flags, const SerdStringView meta) { if (!(flags & (SERD_HAS_DATATYPE | SERD_HAS_LANGUAGE))) { return serd_node_construct_simple( buf_size, buf, SERD_LITERAL, flags, string); } if ((flags & SERD_HAS_DATATYPE) && (flags & SERD_HAS_LANGUAGE)) { return result(SERD_ERR_BAD_ARG, 0); } if (!meta.len) { return result(SERD_ERR_BAD_ARG, 0); } if (((flags & SERD_HAS_DATATYPE) && (!serd_uri_string_has_scheme(meta.buf) || !strcmp(meta.buf, NS_RDF "langString"))) || ((flags & SERD_HAS_LANGUAGE) && !is_langtag(meta))) { return result(SERD_ERR_BAD_ARG, 0); } const size_t padded_length = serd_node_pad_length(string.len); const size_t meta_size = sizeof(SerdNode) + serd_node_pad_length(meta.len); const size_t total_size = sizeof(SerdNode) + padded_length + meta_size; if (!buf || total_size > buf_size) { return result(SERD_ERR_OVERFLOW, total_size); } SerdNode* const node = (SerdNode*)buf; node->length = string.len; node->flags = flags; node->type = SERD_LITERAL; memcpy(serd_node_buffer(node), string.buf, string.len); SerdNode* meta_node = node + 1 + (padded_length / sizeof(SerdNode)); meta_node->type = (flags & SERD_HAS_DATATYPE) ? SERD_URI : SERD_LITERAL; meta_node->length = meta.len; memcpy(serd_node_buffer(meta_node), meta.buf, meta.len); serd_node_zero_pad(node); return result(SERD_SUCCESS, total_size); } SerdWriteResult serd_node_construct(const size_t buf_size, void* const buf, const SerdNodeType type, const SerdStringView string, const SerdNodeFlags flags, const SerdStringView meta) { return ((type == SERD_LITERAL) ? serd_node_construct_literal(buf_size, buf, string, flags, meta) : (meta.len > 0u) ? result(SERD_ERR_BAD_ARG, 0) : serd_node_construct_token(buf_size, buf, type, string)); } SerdWriteResult serd_node_construct_boolean(const size_t buf_size, void* const buf, const bool value) { char temp[EXESS_MAX_BOOLEAN_LENGTH + 1] = {0}; const ExessResult r = exess_write_boolean(value, sizeof(temp), temp); MUST_SUCCEED(r.status); // The only error is buffer overrun return serd_node_construct_literal(buf_size, buf, SERD_SUBSTRING(temp, r.count), SERD_HAS_DATATYPE, SERD_STRING(EXESS_XSD_URI "boolean")); } SerdWriteResult serd_node_construct_decimal(const size_t buf_size, void* const buf, const double value) { char temp[EXESS_MAX_DECIMAL_LENGTH + 1] = {0}; const ExessResult r = exess_write_decimal(value, sizeof(temp), temp); MUST_SUCCEED(r.status); // The only error is buffer overrun return serd_node_construct_literal(buf_size, buf, SERD_SUBSTRING(temp, r.count), SERD_HAS_DATATYPE, SERD_STRING(EXESS_XSD_URI "decimal")); } SerdWriteResult serd_node_construct_double(const size_t buf_size, void* const buf, const double value) { char temp[EXESS_MAX_DOUBLE_LENGTH + 1] = {0}; const ExessResult r = exess_write_double(value, sizeof(temp), temp); MUST_SUCCEED(r.status); // The only error is buffer overrun return serd_node_construct_literal(buf_size, buf, SERD_SUBSTRING(temp, r.count), SERD_HAS_DATATYPE, SERD_STRING(EXESS_XSD_URI "double")); } SerdWriteResult serd_node_construct_float(const size_t buf_size, void* const buf, const float value) { char temp[EXESS_MAX_FLOAT_LENGTH + 1] = {0}; const ExessResult r = exess_write_float(value, sizeof(temp), temp); MUST_SUCCEED(r.status); // The only error is buffer overrun return serd_node_construct_literal(buf_size, buf, SERD_SUBSTRING(temp, r.count), SERD_HAS_DATATYPE, SERD_STRING(EXESS_XSD_URI "float")); } SerdWriteResult serd_node_construct_integer(const size_t buf_size, void* const buf, const int64_t value, const SerdStringView datatype) { if (datatype.len && !serd_uri_string_has_scheme(datatype.buf)) { return result(SERD_ERR_BAD_ARG, 0); } char temp[24] = {0}; const ExessResult r = exess_write_long(value, sizeof(temp), temp); MUST_SUCCEED(r.status); // The only error is buffer overrun return serd_node_construct_literal( buf_size, buf, SERD_SUBSTRING(temp, r.count), SERD_HAS_DATATYPE, datatype.len ? datatype : SERD_STRING(NS_XSD "integer")); } SerdWriteResult serd_node_construct_base64(const size_t buf_size, void* const buf, const size_t value_size, const void* const value, const SerdStringView datatype) { static const SerdStringView xsd_base64Binary = SERD_STRING(NS_XSD "base64Binary"); // Verify argument sanity if (!value || !value_size || (datatype.len && !serd_uri_string_has_scheme(datatype.buf))) { return result(SERD_ERR_BAD_ARG, 0); } // Determine the type to use (default to xsd:base64Binary) const SerdStringView type = datatype.len ? datatype : xsd_base64Binary; const size_t type_length = serd_node_pad_length(type.len); const size_t type_size = sizeof(SerdNode) + type_length; // Find the length of the encoded string (just an O(1) arithmetic expression) ExessResult r = exess_write_base64(value_size, value, 0, NULL); // Check that the provided buffer is large enough const size_t padded_length = serd_node_pad_length(r.count); const size_t total_size = sizeof(SerdNode) + padded_length + type_size; if (!buf || total_size > buf_size) { return result(SERD_ERR_OVERFLOW, total_size); } SerdNode* const node = (SerdNode*)buf; node->length = r.count; node->flags = SERD_HAS_DATATYPE; node->type = SERD_LITERAL; // Write the encoded base64 into the node body r = exess_write_base64( value_size, value, total_size - sizeof(SerdNode), serd_node_buffer(node)); MUST_SUCCEED(r.status); (void)r; // Append datatype SerdNode* meta_node = node + 1 + (padded_length / sizeof(SerdNode)); meta_node->length = type.len; meta_node->flags = 0u; meta_node->type = SERD_URI; memcpy(serd_node_buffer(meta_node), type.buf, type.len); return result(SERD_SUCCESS, total_size); } static size_t string_sink(const void* const buf, const size_t size, const size_t nmemb, void* const stream) { char** ptr = (char**)stream; memcpy(*ptr, buf, size * nmemb); *ptr += size * nmemb; return nmemb; } SerdWriteResult serd_node_construct_uri(const size_t buf_size, void* const buf, const SerdURIView uri) { const size_t length = serd_uri_string_length(uri); const size_t required_size = sizeof(SerdNode) + serd_node_pad_length(length); if (!buf || buf_size < required_size) { return result(SERD_ERR_OVERFLOW, required_size); } // Write node header SerdNode* const node = (SerdNode*)buf; node->length = length; node->flags = 0u; node->type = SERD_URI; // Serialise URI to node body char* ptr = serd_node_buffer(node); const size_t actual_length = serd_write_uri(uri, string_sink, &ptr); assert(actual_length == length); serd_node_buffer(node)[actual_length] = '\0'; serd_node_check_padding(node); return result(SERD_SUCCESS, required_size); } SerdNode* serd_node_new(const SerdNodeType type, const SerdStringView string, const SerdNodeFlags flags, const SerdStringView meta) { SerdWriteResult r = serd_node_construct(0, NULL, type, string, flags, meta); if (r.status != SERD_ERR_OVERFLOW) { return NULL; } assert(r.count % sizeof(SerdNode) == 0); SerdNode* const node = serd_node_malloc(sizeof(SerdNode) + r.count + 1); if (node) { r = serd_node_construct(r.count, node, type, string, flags, meta); MUST_SUCCEED(r.status); // Any error should have been reported above } return node; } SerdNode* serd_new_token(const SerdNodeType type, const SerdStringView string) { return serd_node_new(type, string, 0u, SERD_EMPTY_STRING()); } SerdNode* serd_new_string(const SerdStringView str) { return serd_node_new(SERD_LITERAL, str, 0u, SERD_EMPTY_STRING()); } SerdNode* serd_new_literal(const SerdStringView str, const SerdNodeFlags flags, const SerdStringView meta) { return serd_node_new(SERD_LITERAL, str, flags, meta); } ExessResult serd_node_get_value_as(const SerdNode* const node, const ExessDatatype value_type, const size_t value_size, void* const value) { const SerdNode* const datatype_node = serd_node_datatype(node); const ExessDatatype node_type = datatype_node ? exess_datatype_from_uri(serd_node_string(datatype_node)) : EXESS_NOTHING; if (node_type == EXESS_NOTHING || (node_type == EXESS_HEX && value_type == EXESS_BASE64) || (node_type == EXESS_BASE64 && value_type == EXESS_HEX)) { // Try to read the large or untyped node string directly into the result const ExessVariableResult vr = exess_read_value(value_type, value_size, value, serd_node_string(node)); const ExessResult r = {vr.status, vr.write_count}; return r; } // Read the (smallish) value from the node ExessValue node_value = {false}; const ExessVariableResult vr = exess_read_value( node_type, sizeof(node_value), &node_value, serd_node_string(node)); if (vr.status) { const ExessResult r = {vr.status, 0u}; return r; } // Coerce value to the desired type if possible return exess_value_coerce(EXESS_REDUCE_PRECISION, node_type, vr.write_count, &node_value, value_type, value_size, value); } bool serd_get_boolean(const SerdNode* const node) { assert(node); bool value = false; serd_node_get_value_as(node, EXESS_BOOLEAN, sizeof(value), &value); return value; } double serd_get_double(const SerdNode* const node) { assert(node); double value = (double)NAN; // NOLINT(google-readability-casting) serd_node_get_value_as(node, EXESS_DOUBLE, sizeof(value), &value); return value; } float serd_get_float(const SerdNode* const node) { assert(node); float value = (float)NAN; // NOLINT(google-readability-casting) serd_node_get_value_as(node, EXESS_FLOAT, sizeof(value), &value); return value; } int64_t serd_get_integer(const SerdNode* const node) { assert(node); int64_t value = 0; serd_node_get_value_as(node, EXESS_LONG, sizeof(value), &value); return value; } size_t serd_get_base64_size(const SerdNode* const node) { return exess_base64_decoded_size(serd_node_length(node)); } SerdWriteResult serd_get_base64(const SerdNode* const node, const size_t buf_size, void* const buf) { const size_t max_size = serd_get_base64_size(node); const ExessVariableResult r = exess_read_base64(buf_size, buf, serd_node_string(node)); return r.status == EXESS_NO_SPACE ? result(SERD_ERR_OVERFLOW, max_size) : r.status ? result(SERD_ERR_BAD_SYNTAX, 0u) : result(SERD_SUCCESS, r.write_count); } SerdNode* serd_node_copy(const SerdNode* node) { if (!node) { return NULL; } const size_t size = serd_node_total_size(node); SerdNode* copy = (SerdNode*)serd_calloc_aligned(serd_node_align, size); memcpy(copy, node, size); return copy; } bool serd_node_equals(const SerdNode* const a, const SerdNode* const b) { if (a == b) { return true; } if (!a || !b || a->length != b->length || a->flags != b->flags || a->type != b->type) { return false; } const size_t length = a->length; if (!!memcmp(serd_node_string(a), serd_node_string(b), length)) { return false; } const SerdNodeFlags flags = a->flags; if (flags & meta_mask) { const SerdNode* const am = serd_node_meta_c(a); const SerdNode* const bm = serd_node_meta_c(b); return am->length == bm->length && am->type == bm->type && !memcmp(serd_node_string(am), serd_node_string(bm), am->length); } return true; } int serd_node_compare(const SerdNode* const a, const SerdNode* const b) { assert(a); assert(b); int cmp = 0; if ((cmp = ((int)a->type - (int)b->type)) || (cmp = strcmp(serd_node_string_i(a), serd_node_string_i(b))) || (cmp = ((int)a->flags - (int)b->flags)) || !(a->flags & (SERD_HAS_LANGUAGE | SERD_HAS_DATATYPE))) { return cmp; } assert(a->flags == b->flags); assert(a->flags & (SERD_HAS_LANGUAGE | SERD_HAS_DATATYPE)); assert(b->flags & (SERD_HAS_LANGUAGE | SERD_HAS_DATATYPE)); const SerdNode* const ma = serd_node_meta_c(a); const SerdNode* const mb = serd_node_meta_c(b); assert(ma->type == mb->type); assert(ma->flags == mb->flags); return strcmp(serd_node_string_i(ma), serd_node_string_i(mb)); } SerdNode* serd_new_uri(const SerdStringView string) { return serd_new_token(SERD_URI, string); } SerdNode* serd_new_parsed_uri(const SerdURIView uri) { SerdWriteResult r = serd_node_construct_uri(0u, NULL, uri); SerdNode* const node = serd_node_try_malloc(r); if (node) { r = serd_node_construct_uri(r.count, node, uri); MUST_SUCCEED(r.status); } return node; } typedef struct { char* buf; size_t len; size_t offset; } ConstructWriteHead; static size_t construct_write(const void* const buf, const size_t size, const size_t nmemb, void* const stream) { const size_t n_bytes = size * nmemb; ConstructWriteHead* const head = (ConstructWriteHead*)stream; if (head->buf && head->offset + n_bytes <= head->len) { memcpy(head->buf + head->offset, buf, n_bytes); } head->offset += n_bytes; return n_bytes; } SerdWriteResult serd_node_construct_file_uri(const size_t buf_size, void* const buf, const SerdStringView path, const SerdStringView hostname) { SerdNode* const node = (SerdNode*)buf; ConstructWriteHead head = {(char*)buf, buf_size, 0u}; size_t count = 0u; // Write node header SerdNode header = {0u, 0u, SERD_URI}; count += construct_write(&header, 1, sizeof(header), &head); // Write URI string node body const size_t length = serd_write_file_uri(path, hostname, construct_write, &head); // Terminate string count += length; count += construct_write("", 1, 1, &head); // Write any additional null bytes needed for padding const size_t padded_length = serd_node_pad_length(length); for (size_t p = 0u; p < padded_length - length; ++p) { count += construct_write("", 1, 1, &head); } if (!buf || count > buf_size) { return result(SERD_ERR_OVERFLOW, count); } node->length = length; assert(node->length == strlen(serd_node_string(node))); return result(SERD_SUCCESS, count); } SerdNode* serd_new_file_uri(const SerdStringView path, const SerdStringView hostname) { SerdWriteResult r = serd_node_construct_file_uri(0, NULL, path, hostname); SerdNode* const node = serd_node_try_malloc(r); if (node) { r = serd_node_construct_file_uri(r.count, node, path, hostname); MUST_SUCCEED(r.status); assert(serd_node_length(node) == strlen(serd_node_string(node))); } serd_node_check_padding(node); return node; } SerdNode* serd_new_double(const double d) { SerdWriteResult r = serd_node_construct_double(0, NULL, d); SerdNode* const node = serd_node_try_malloc(r); if (node) { r = serd_node_construct_double(r.count, node, d); MUST_SUCCEED(r.status); assert(serd_node_length(node) == strlen(serd_node_string(node))); } serd_node_check_padding(node); return node; } SerdNode* serd_new_float(const float f) { SerdWriteResult r = serd_node_construct_float(0, NULL, f); SerdNode* const node = serd_node_try_malloc(r); if (node) { r = serd_node_construct_float(r.count, node, f); MUST_SUCCEED(r.status); assert(serd_node_length(node) == strlen(serd_node_string(node))); } serd_node_check_padding(node); return node; } SerdNode* serd_new_boolean(bool b) { SerdWriteResult r = serd_node_construct_boolean(0, NULL, b); SerdNode* const node = serd_node_try_malloc(r); if (node) { r = serd_node_construct_boolean(r.count, node, b); MUST_SUCCEED(r.status); assert(serd_node_length(node) == strlen(serd_node_string(node))); } serd_node_check_padding(node); return node; } SerdNode* serd_new_decimal(const double d) { SerdWriteResult r = serd_node_construct_decimal(0, NULL, d); SerdNode* const node = serd_node_try_malloc(r); if (node) { r = serd_node_construct_decimal(r.count, node, d); MUST_SUCCEED(r.status); assert(serd_node_length(node) == strlen(serd_node_string(node))); } serd_node_check_padding(node); return node; } SerdNode* serd_new_integer(const int64_t i, const SerdStringView datatype) { SerdWriteResult r = serd_node_construct_integer(0, NULL, i, datatype); SerdNode* const node = serd_node_try_malloc(r); if (node) { r = serd_node_construct_integer(r.count, node, i, datatype); MUST_SUCCEED(r.status); assert(serd_node_length(node) == strlen(serd_node_string(node))); } serd_node_check_padding(node); return node; } SerdNode* serd_new_base64(const void* buf, size_t size, const SerdStringView datatype) { SerdWriteResult r = serd_node_construct_base64(0, NULL, size, buf, datatype); SerdNode* const node = serd_node_try_malloc(r); if (node) { r = serd_node_construct_base64(r.count, node, size, buf, datatype); MUST_SUCCEED(r.status); assert(serd_node_length(node) == strlen(serd_node_string(node))); } serd_node_check_padding(node); return node; } SerdNodeType serd_node_type(const SerdNode* const node) { assert(node); return node->type; } const char* serd_node_string(const SerdNode* const node) { assert(node); return (const char*)(node + 1); } size_t serd_node_length(const SerdNode* const node) { assert(node); return node->length; } SerdStringView serd_node_string_view(const SerdNode* const node) { assert(node); const SerdStringView r = {(const char*)(node + 1), node->length}; return r; } SerdURIView serd_node_uri_view(const SerdNode* const node) { assert(node); return (node->type == SERD_URI) ? serd_parse_uri(serd_node_string(node)) : SERD_URI_NULL; } const SerdNode* serd_node_datatype(const SerdNode* const node) { assert(node); if (!(node->flags & SERD_HAS_DATATYPE)) { return NULL; } const SerdNode* const datatype = serd_node_meta_c(node); assert(datatype->type == SERD_URI); return datatype; } const SerdNode* serd_node_language(const SerdNode* const node) { assert(node); if (!(node->flags & SERD_HAS_LANGUAGE)) { return NULL; } const SerdNode* const lang = serd_node_meta_c(node); assert(lang->type == SERD_LITERAL); return lang; } SerdNodeFlags serd_node_flags(const SerdNode* const node) { return node->flags; } void serd_node_free(SerdNode* const node) { serd_free_aligned(node); } #undef MUST_SUCCEED