data FEATURE generic JSON format parser
diff --git a/CMakeLists.txt b/CMakeLists.txt
index d93921d..fba0fdb 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -155,6 +155,7 @@
src/path.c
src/diff.c
src/context.c
+ src/json.c
src/tree_data.c
src/tree_data_free.c
src/tree_data_helpers.c
diff --git a/src/common.c b/src/common.c
index b1ccd65..7a8d83a 100644
--- a/src/common.c
+++ b/src/common.c
@@ -265,6 +265,59 @@
return LY_SUCCESS;
}
+LY_ERR
+ly_pututf8(char *dst, uint32_t value, size_t *bytes_written)
+{
+ if (value < 0x80) {
+ /* one byte character */
+ if (value < 0x20 &&
+ value != 0x09 &&
+ value != 0x0a &&
+ value != 0x0d) {
+ return LY_EINVAL;
+ }
+
+ dst[0] = value;
+ (*bytes_written) = 1;
+ } else if (value < 0x800) {
+ /* two bytes character */
+ dst[0] = 0xc0 | (value >> 6);
+ dst[1] = 0x80 | (value & 0x3f);
+ (*bytes_written) = 2;
+ } else if (value < 0xfffe) {
+ /* three bytes character */
+ if (((value & 0xf800) == 0xd800) ||
+ (value >= 0xfdd0 && value <= 0xfdef)) {
+ /* exclude surrogate blocks %xD800-DFFF */
+ /* exclude noncharacters %xFDD0-FDEF */
+ return LY_EINVAL;
+ }
+
+ dst[0] = 0xe0 | (value >> 12);
+ dst[1] = 0x80 | ((value >> 6) & 0x3f);
+ dst[2] = 0x80 | (value & 0x3f);
+
+ (*bytes_written) = 3;
+ } else if (value < 0x10fffe) {
+ if ((value & 0xffe) == 0xffe) {
+ /* exclude noncharacters %xFFFE-FFFF, %x1FFFE-1FFFF, %x2FFFE-2FFFF, %x3FFFE-3FFFF, %x4FFFE-4FFFF,
+ * %x5FFFE-5FFFF, %x6FFFE-6FFFF, %x7FFFE-7FFFF, %x8FFFE-8FFFF, %x9FFFE-9FFFF, %xAFFFE-AFFFF,
+ * %xBFFFE-BFFFF, %xCFFFE-CFFFF, %xDFFFE-DFFFF, %xEFFFE-EFFFF, %xFFFFE-FFFFF, %x10FFFE-10FFFF */
+ return LY_EINVAL;
+ }
+ /* four bytes character */
+ dst[0] = 0xf0 | (value >> 18);
+ dst[1] = 0x80 | ((value >> 12) & 0x3f);
+ dst[2] = 0x80 | ((value >> 6) & 0x3f);
+ dst[3] = 0x80 | (value & 0x3f);
+
+ (*bytes_written) = 4;
+ } else {
+ return LY_EINVAL;
+ }
+ return LY_SUCCESS;
+}
+
/**
* @brief Static table of the UTF8 characters lengths according to their first byte.
*/
diff --git a/src/common.h b/src/common.h
index 4ff5220..20c95e1 100644
--- a/src/common.h
+++ b/src/common.h
@@ -342,6 +342,25 @@
LY_ERR ly_getutf8(const char **input, uint32_t *utf8_char, size_t *bytes_read);
/**
+ * Store UTF-8 character specified as 4byte integer into the dst buffer.
+ *
+ * UTF-8 mapping:
+ * 00000000 -- 0000007F: 0xxxxxxx
+ * 00000080 -- 000007FF: 110xxxxx 10xxxxxx
+ * 00000800 -- 0000FFFF: 1110xxxx 10xxxxxx 10xxxxxx
+ * 00010000 -- 001FFFFF: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
+ *
+ * Includes checking for valid characters (following RFC 7950, sec 9.4)
+ *
+ * @param[in, out] dst Destination buffer to store the UTF-8 character, must provide enough space (up to 4 bytes) for storing the UTF-8 character.
+ * @param[in] value 32b value of the UTF-8 character to store.
+ * @param[out] bytes_written Number of bytes written into @p dst (size of the written UTF-8 character).
+ * @return LY_SUCCESS on success
+ * @return LY_EINVAL in case of invalid UTF-8 @p value to store.
+ */
+LY_ERR ly_pututf8(char *dst, uint32_t value, size_t *bytes_written);
+
+/**
* @brief Get number of characters in the @p str, taking multibyte characters into account.
* @param[in] str String to examine.
* @param[in] bytes Number of valid bytes that are supposed to be taken into account in @p str.
diff --git a/src/json.c b/src/json.c
new file mode 100644
index 0000000..3a449e6
--- /dev/null
+++ b/src/json.c
@@ -0,0 +1,788 @@
+/**
+ * @file json.c
+ * @author Radek Krejci <rkrejci@cesnet.cz>
+ * @brief Generic JSON format parser for libyang
+ *
+ * Copyright (c) 2020 CESNET, z.s.p.o.
+ *
+ * This source code is licensed under BSD 3-Clause License (the "License").
+ * You may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://opensource.org/licenses/BSD-3-Clause
+ */
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+
+#include "common.h"
+#include "json.h"
+#include "parser_internal.h"
+
+#define JSON_PUSH_STATUS_RET(CTX, STATUS) \
+ LY_CHECK_ERR_RET(ly_set_add(&CTX->status, (void*)STATUS, LY_SET_OPT_USEASLIST) == -1, LOGMEM(CTX->ctx), LY_EMEM)
+
+#define JSON_POP_STATUS_RET(CTX) \
+ assert(CTX->status.count); CTX->status.count--;
+
+const char*
+lyjson_token2str(enum LYJSON_PARSER_STATUS status)
+{
+ switch (status) {
+ case LYJSON_ERROR:
+ return "error";
+ case LYJSON_ROOT:
+ return "document root";
+ case LYJSON_FALSE:
+ return "false";
+ case LYJSON_TRUE:
+ return "true";
+ case LYJSON_NULL:
+ return "null";
+ case LYJSON_OBJECT:
+ return "object";
+ case LYJSON_OBJECT_CLOSED:
+ return "object closed";
+ case LYJSON_OBJECT_EMPTY:
+ return "empty object";
+ case LYJSON_ARRAY:
+ return "array";
+ case LYJSON_ARRAY_CLOSED:
+ return "array closed";
+ case LYJSON_ARRAY_EMPTY:
+ return "empty array";
+ case LYJSON_NUMBER:
+ return "number";
+ case LYJSON_STRING:
+ return "string";
+ case LYJSON_END:
+ return "end of input";
+ }
+
+ return "";
+}
+
+static LY_ERR
+skip_ws(struct lyjson_ctx *jsonctx)
+{
+ /* skip leading whitespaces */
+ while (*jsonctx->in->current != '\0' && is_jsonws(*jsonctx->in->current)) {
+ if (*jsonctx->in->current == 0x0a) { /* new line */
+ jsonctx->line++;
+ }
+ ly_in_skip(jsonctx->in, 1);
+ }
+ if (*jsonctx->in->current == '\0') {
+ JSON_PUSH_STATUS_RET(jsonctx, LYJSON_END);
+ }
+
+ return LY_SUCCESS;
+}
+
+/*
+ * @brief Set value corresponding to the current context's status
+ */
+static void
+lyjson_ctx_set_value(struct lyjson_ctx *jsonctx, const char *value, size_t value_len, int dynamic)
+{
+ assert(jsonctx);
+
+ if (dynamic) {
+ free((char*)jsonctx->value);
+ }
+ jsonctx->value = value;
+ jsonctx->value_len = value_len;
+ jsonctx->dynamic = dynamic;
+}
+
+static LY_ERR
+lyjson_check_next(struct lyjson_ctx *jsonctx)
+{
+ if (jsonctx->status.count == 1) {
+ /* top level value (JSON-text), ws expected */
+ if (*jsonctx->in->current == '\0' || is_jsonws(*jsonctx->in->current)) {
+ return LY_SUCCESS;
+ }
+ } else if (lyjson_ctx_status(jsonctx, 1) == LYJSON_OBJECT) {
+ LY_CHECK_RET(skip_ws(jsonctx));
+ if (*jsonctx->in->current == ',' || *jsonctx->in->current == '}') {
+ return LY_SUCCESS;
+ }
+ } else if (lyjson_ctx_status(jsonctx, 1) == LYJSON_ARRAY) {
+ LY_CHECK_RET(skip_ws(jsonctx));
+ if (*jsonctx->in->current == ',' || *jsonctx->in->current == ']') {
+ return LY_SUCCESS;
+ }
+ } else {
+ LOGVAL(jsonctx->ctx, LY_VLOG_LINE, &jsonctx->line, LYVE_SYNTAX,
+ "Unexpected character \"%c\" after JSON %s.", *jsonctx->in->current, lyjson_token2str(lyjson_ctx_status(jsonctx, 0)));
+ }
+
+ return LY_EVALID;
+}
+
+/**
+ * Input is expected to start after the opening quotation-mark.
+ * When succeeds, input is moved after the closing quotation-mark.
+ */
+static LY_ERR
+lyjson_string_(struct lyjson_ctx *jsonctx)
+{
+#define BUFSIZE 24
+#define BUFSIZE_STEP 128
+
+ const char *in = jsonctx->in->current, *start;
+ char *buf = NULL;
+ size_t offset; /* read offset in input buffer */
+ size_t len; /* length of the output string (write offset in output buffer) */
+ size_t size = 0; /* size of the output buffer */
+ size_t u;
+ uint64_t start_line;
+
+ assert(jsonctx);
+
+ /* init */
+ start = in;
+ start_line = jsonctx->line;
+ offset = len = 0;
+
+ /* parse */
+ while (in[offset]) {
+ if (in[offset] == '\\') {
+ /* escape sequence */
+ size_t slash = offset;
+ uint32_t value;
+ uint8_t i = 1;
+
+ if (!buf) {
+ /* prepare output buffer */
+ buf = malloc(BUFSIZE);
+ LY_CHECK_ERR_RET(!buf, LOGMEM(jsonctx->ctx), LY_EMEM);
+ size = BUFSIZE;
+ }
+
+ /* allocate enough for the offset and next character,
+ * we will need 4 bytes at most since we support only the predefined
+ * (one-char) entities and character references */
+ if (len + offset + 4 >= size) {
+ buf = ly_realloc(buf, size + BUFSIZE_STEP);
+ LY_CHECK_ERR_RET(!buf, LOGMEM(jsonctx->ctx), LY_EMEM);
+ size += BUFSIZE_STEP;
+ }
+
+ if (offset) {
+ /* store what we have so far */
+ memcpy(&buf[len], in, offset);
+ len += offset;
+ in += offset;
+ offset = 0;
+ }
+
+ switch (in[++offset]) {
+ case '"':
+ /* quotation mark */
+ value = 0x22;
+ break;
+ case '\\':
+ /* reverse solidus */
+ value = 0x5c;
+ break;
+ case '/':
+ /* solidus */
+ value = 0x2f;
+ break;
+ case 'b':
+ /* backspace */
+ value = 0x08;
+ break;
+ case 'f':
+ /* form feed */
+ value = 0x0c;
+ break;
+ case 'n':
+ /* line feed */
+ value = 0x0a;
+ break;
+ case 'r':
+ /* carriage return */
+ value = 0x0d;
+ break;
+ case 't':
+ /* tab */
+ value = 0x09;
+ break;
+ case 'u':
+ /* Basic Multilingual Plane character \uXXXX */
+ offset++;
+ for (value = i = 0; i < 4; i++) {
+ if (isdigit(in[offset + i])) {
+ u = (in[offset + i] - '0');
+ } else if (in[offset + i] > 'F') {
+ u = 10 + (in[offset + i] - 'a');
+ } else {
+ u = 10 + (in[offset + i] - 'A');
+ }
+ value = (16 * value) + u;
+ }
+ break;
+ default:
+ /* invalid escape sequence */
+ LOGVAL(jsonctx->ctx, LY_VLOG_LINE, &jsonctx->line, LYVE_SYNTAX,
+ "Invalid character escape sequence \\%c.", in[offset]);
+ goto error;
+
+ }
+
+ offset += i; /* add read escaped characters */
+ LY_CHECK_ERR_GOTO(ly_pututf8(&buf[len], value, &u),
+ LOGVAL(jsonctx->ctx, LY_VLOG_LINE, &jsonctx->line, LYVE_SYNTAX,
+ "Invalid character reference \"%.*s\" (0x%08x).", offset - slash, &in[slash], value),
+ error);
+ len += u; /* update number of bytes in buffer */
+ in += offset; /* move the input by the processed bytes stored in the buffer ... */
+ offset = 0; /* ... and reset the offset index for future moving data into buffer */
+
+ } else if (in[offset] == '"') {
+ /* end of string */
+ if (buf) {
+ /* realloc exact size string */
+ buf = ly_realloc(buf, len + offset + 1);
+ LY_CHECK_ERR_RET(!buf, LOGMEM(jsonctx->ctx), LY_EMEM);
+ size = len + offset + 1;
+ memcpy(&buf[len], in, offset);
+
+ /* set terminating NULL byte */
+ buf[len + offset] = '\0';
+ }
+ len += offset;
+ ++offset;
+ in += offset;
+ goto success;
+ } else {
+ /* get it as UTF-8 character for check */
+ const char *c = &in[offset];
+ uint32_t code = 0;
+ size_t code_len = 0;
+
+ LY_CHECK_ERR_GOTO(ly_getutf8(&c, &code, &code_len),
+ LOGVAL(jsonctx->ctx, LY_VLOG_LINE, &jsonctx->line, LY_VCODE_INCHAR, in[offset]), error);
+
+ LY_CHECK_ERR_GOTO(!is_jsonstrchar(code),
+ LOGVAL(jsonctx->ctx, LY_VLOG_LINE, &jsonctx->line, LYVE_SYNTAX,
+ "Invalid character in JSON string \"%.*s\" (0x%08x).", &in[offset] - start + code_len, start, code),
+ error);
+
+ /* character is ok, continue */
+ offset += code_len;
+ }
+ }
+
+ /* EOF reached before endchar */
+ LOGVAL(jsonctx->ctx, LY_VLOG_LINE, &jsonctx->line, LY_VCODE_EOF);
+ LOGVAL(jsonctx->ctx, LY_VLOG_LINE, &start_line, LYVE_SYNTAX, "Missing quotation-mark at the end of a JSON string.");
+
+error:
+ free(buf);
+ return LY_EVALID;
+
+success:
+ ly_in_skip(jsonctx->in, in - jsonctx->in->current);
+ if (buf) {
+ lyjson_ctx_set_value(jsonctx, buf, len, 1);
+ } else {
+ lyjson_ctx_set_value(jsonctx, start, len, 0);
+ }
+
+ return LY_SUCCESS;
+
+#undef BUFSIZE
+#undef BUFSIZE_STEP
+}
+
+/*
+ *
+ * Wrapper around lyjson_string_() adding LYJSON_STRING status into context to allow using lyjson_string_() for parsing object's name.
+ */
+static LY_ERR
+lyjson_string(struct lyjson_ctx *jsonctx)
+{
+ LY_CHECK_RET(lyjson_string_(jsonctx));
+
+ JSON_PUSH_STATUS_RET(jsonctx, LYJSON_STRING);
+ LY_CHECK_RET(lyjson_check_next(jsonctx));
+
+ return LY_SUCCESS;
+}
+
+static LY_ERR
+lyjson_number(struct lyjson_ctx *jsonctx)
+{
+ size_t offset = 0, exponent = 0;
+ const char *in = jsonctx->in->current;
+ int minus = 0;
+
+ if (in[offset] == '-') {
+ ++offset;
+ minus = 1;
+ }
+
+ if (in[offset] == '0') {
+ ++offset;
+ } else if (isdigit(in[offset])) {
+ ++offset;
+ while (isdigit(in[offset])) {
+ ++offset;
+ }
+ } else {
+invalid_character:
+ if (in[offset]) {
+ LOGVAL(jsonctx->ctx, LY_VLOG_LINE, &jsonctx->line, LYVE_SYNTAX, "Invalid character in JSON Number value (\"%c\").", in[offset]);
+ } else {
+ LOGVAL(jsonctx->ctx, LY_VLOG_LINE, &jsonctx->line, LY_VCODE_EOF);
+ }
+ return LY_EVALID;
+ }
+
+ if (in[offset] == '.') {
+ ++offset;
+ if (!isdigit(in[offset])) {
+ goto invalid_character;
+ }
+ while (isdigit(in[offset])) {
+ ++offset;
+ }
+ }
+
+ if ((in[offset] == 'e') || (in[offset] == 'E')) {
+ exponent = offset++;
+ if ((in[offset] == '+') || (in[offset] == '-')) {
+ ++offset;
+ }
+ if (!isdigit(in[offset])) {
+ goto invalid_character;
+ }
+ while (isdigit(in[offset])) {
+ ++offset;
+ }
+ }
+
+ if (exponent) {
+ /* convert JSON number with exponent into the representation used by YANG */
+ long int e_val;
+ char *ptr, *dec_point, *num;
+ const char *e_ptr = &in[exponent + 1];
+ size_t num_len, i;
+ long int dp_position; /* final position of the deciaml point */
+
+ errno = 0;
+ e_val = strtol(e_ptr, &ptr, 10);
+ if (errno) {
+ LOGVAL(jsonctx->ctx, LY_VLOG_LINE, &jsonctx->line, LYVE_SEMANTICS,
+ "Exponent out-of-bounds in a JSON Number value (%.*s).", offset - minus - (e_ptr - in), e_ptr);
+ return LY_EVALID;
+ }
+
+
+ dec_point = ly_strnchr(in, '.', exponent);
+ if (!dec_point) {
+ /* value is integer, we are just ... */
+ if (e_val >= 0) {
+ /* adding zeros at the end */
+ num_len = exponent + e_val;
+ dp_position = num_len; /* decimal point is behind the actual value */
+ } else if ((size_t)labs(e_val) < exponent) {
+ /* adding decimal point between the integer's digits */
+ num_len = exponent + 1;
+ dp_position = exponent + e_val;
+ } else {
+ /* adding decimal point before the integer with adding leading zero(s) */
+ num_len = labs(e_val) + 2;
+ dp_position = exponent + e_val;
+ }
+ dp_position -= minus;
+ } else {
+ /* value is decimal, we are moving the decimal point */
+ dp_position = dec_point - in + e_val - minus;
+ if (dp_position > (ssize_t)exponent) {
+ /* moving decimal point after the decimal value make the integer result */
+ num_len = dp_position;
+ } else if (dp_position < 0) {
+ /* moving decimal point before the decimal value requires additional zero(s)
+ * (decimal point is already count in exponent value) */
+ num_len = exponent + labs(dp_position) + 1;
+ } else {
+ /* moving decimal point just inside the decimal value does not make any change in length */
+ num_len = exponent;
+ }
+ }
+
+ /* allocate buffer for the result (add terminating NULL-byte */
+ num = malloc(num_len + 1);
+ LY_CHECK_ERR_RET(!num, LOGMEM(jsonctx->ctx), LY_EMEM);
+
+ /* compose the resulting vlaue */
+ i = 0;
+ if (minus) {
+ num[i++] = '-';
+ }
+ /* add leading zeros */
+ if (dp_position <= 0) {
+ num[i++] = '0';
+ num[i++] = '.';
+ for (; dp_position; dp_position++) {
+ num[i++] = '0';
+ }
+ }
+ /* copy the value */
+ for (unsigned int dp_placed = dp_position ? 0 : 1, j = minus; j < exponent; j++) {
+ if (in[j] == '.') {
+ continue;
+ }
+ if (!dp_placed) {
+ if (!dp_position) {
+ num[i++] = '.';
+ dp_placed = 1;
+ } else {
+ dp_position--;
+ if (in[j] == '0') {
+ num_len--;
+ continue;
+ }
+ }
+ }
+
+ num[i++] = in[j];
+ }
+ /* trailing zeros */
+ while (dp_position--) {
+ num[i++] = '0';
+ }
+ /* terminating NULL byte */
+ num[i] = '\0';
+
+ /* store the modified number */
+ lyjson_ctx_set_value(jsonctx, num, num_len, 1);
+ } else {
+ /* store the number */
+ lyjson_ctx_set_value(jsonctx, jsonctx->in->current, offset, 0);
+ }
+ ly_in_skip(jsonctx->in, offset);
+
+ JSON_PUSH_STATUS_RET(jsonctx, LYJSON_NUMBER);
+ LY_CHECK_RET(lyjson_check_next(jsonctx));
+
+ return LY_SUCCESS;
+}
+
+static LY_ERR
+lyjson_object_name(struct lyjson_ctx *jsonctx)
+{
+ if (*jsonctx->in->current != '"') {
+ LOGVAL(jsonctx->ctx, LY_VLOG_LINE, &jsonctx->line, LY_VCODE_INSTREXP, LY_VCODE_INSTREXP_len(jsonctx->in->current),
+ jsonctx->in->current, "a JSON object's member");
+ return LY_EVALID;
+ }
+ ly_in_skip(jsonctx->in, 1);
+
+ LY_CHECK_RET(lyjson_string_(jsonctx));
+ LY_CHECK_RET(skip_ws(jsonctx));
+ LY_CHECK_ERR_RET(
+ *jsonctx->in->current != ':',
+ LOGVAL(jsonctx->ctx, LY_VLOG_LINE, &jsonctx->line, LY_VCODE_INSTREXP,
+ LY_VCODE_INSTREXP_len(jsonctx->in->current), jsonctx->in->current, "a JSON object's name-separator ':'"),
+ LY_EVALID);
+ ly_in_skip(jsonctx->in, 1);
+ LY_CHECK_RET(skip_ws(jsonctx));
+
+ return LY_SUCCESS;
+}
+
+static LY_ERR
+lyjson_object(struct lyjson_ctx *jsonctx)
+{
+ LY_CHECK_RET(skip_ws(jsonctx));
+
+ if (*jsonctx->in->current == '}') {
+ /* empty object */
+ ly_in_skip(jsonctx->in, 1);
+ lyjson_ctx_set_value(jsonctx, NULL, 0, 0);
+ JSON_PUSH_STATUS_RET(jsonctx, LYJSON_OBJECT_EMPTY);
+ return LY_SUCCESS;
+ }
+
+ LY_CHECK_RET(lyjson_object_name(jsonctx));
+
+ /* output data are set by lyjson_string_() */
+ JSON_PUSH_STATUS_RET(jsonctx, LYJSON_OBJECT);
+
+ return LY_SUCCESS;
+}
+
+/*
+ * @brief Process JSON array envelope
+ *
+ *
+ *
+ * @param[in] jsonctx JSON parser context
+ * @return LY_SUCCESS or LY_EMEM
+ */
+static LY_ERR
+lyjson_array(struct lyjson_ctx *jsonctx)
+{
+ LY_CHECK_RET(skip_ws(jsonctx));
+
+ if (*jsonctx->in->current == ']') {
+ /* empty array */
+ ly_in_skip(jsonctx->in, 1);
+ JSON_PUSH_STATUS_RET(jsonctx, LYJSON_ARRAY_EMPTY);
+ } else {
+ JSON_PUSH_STATUS_RET(jsonctx, LYJSON_ARRAY);
+ }
+
+ /* erase previous values, array has no value on its own */
+ lyjson_ctx_set_value(jsonctx, NULL, 0, 0);
+
+ return LY_SUCCESS;
+}
+
+static LY_ERR
+lyjson_value(struct lyjson_ctx *jsonctx)
+{
+ if (jsonctx->status.count && lyjson_ctx_status(jsonctx, 0) == LYJSON_END) {
+ return LY_SUCCESS;
+ }
+
+ if (*jsonctx->in->current == 'f' && !strncmp(jsonctx->in->current, "false", 5)) {
+ /* false */
+ lyjson_ctx_set_value(jsonctx, jsonctx->in->current, 5, 0);
+ ly_in_skip(jsonctx->in, 5);
+ JSON_PUSH_STATUS_RET(jsonctx, LYJSON_FALSE);
+ LY_CHECK_RET(lyjson_check_next(jsonctx));
+
+ } else if (*jsonctx->in->current == 't' && !strncmp(jsonctx->in->current, "true", 4)) {
+ /* true */
+ lyjson_ctx_set_value(jsonctx, jsonctx->in->current, 4, 0);
+ ly_in_skip(jsonctx->in, 4);
+ JSON_PUSH_STATUS_RET(jsonctx, LYJSON_TRUE);
+ LY_CHECK_RET(lyjson_check_next(jsonctx));
+
+ } else if (*jsonctx->in->current == 'n' && !strncmp(jsonctx->in->current, "null", 4)) {
+ /* none */
+ lyjson_ctx_set_value(jsonctx, jsonctx->in->current, 0, 0);
+ ly_in_skip(jsonctx->in, 4);
+ JSON_PUSH_STATUS_RET(jsonctx, LYJSON_NULL);
+ LY_CHECK_RET(lyjson_check_next(jsonctx));
+
+ } else if (*jsonctx->in->current == '"') {
+ /* string */
+ ly_in_skip(jsonctx->in, 1);
+ LY_CHECK_RET(lyjson_string(jsonctx));
+
+ } else if (*jsonctx->in->current == '[') {
+ /* array */
+ ly_in_skip(jsonctx->in, 1);
+ LY_CHECK_RET(lyjson_array(jsonctx));
+
+ } else if (*jsonctx->in->current == '{') {
+ /* object */
+ ly_in_skip(jsonctx->in, 1);
+ LY_CHECK_RET(lyjson_object(jsonctx));
+
+ } else if (*jsonctx->in->current == '-' || (*jsonctx->in->current >= '0' && *jsonctx->in->current <= '9')) {
+ /* number */
+ LY_CHECK_RET(lyjson_number(jsonctx));
+
+ } else {
+ /* unexpected value */
+ LOGVAL(jsonctx->ctx, LY_VLOG_LINE, &jsonctx->line, LY_VCODE_INSTREXP, LY_VCODE_INSTREXP_len(jsonctx->in->current),
+ jsonctx->in->current, "a JSON value");
+ return LY_EVALID;
+ }
+
+ return LY_SUCCESS;
+}
+
+LY_ERR
+lyjson_ctx_new(const struct ly_ctx *ctx, struct ly_in *in, struct lyjson_ctx **jsonctx_p)
+{
+ LY_ERR ret = LY_SUCCESS;
+ struct lyjson_ctx *jsonctx;
+
+ assert(ctx);
+ assert(in);
+ assert(jsonctx_p);
+
+ /* new context */
+ jsonctx = calloc(1, sizeof *jsonctx);
+ LY_CHECK_ERR_RET(!jsonctx, LOGMEM(ctx), LY_EMEM);
+ jsonctx->ctx = ctx;
+ jsonctx->line = 1;
+ jsonctx->in = in;
+
+ /* parse JSON value, if any */
+ LY_CHECK_GOTO(ret = skip_ws(jsonctx), cleanup);
+ if (lyjson_ctx_status(jsonctx, 0) == LYJSON_END) {
+ /* empty data input */
+ goto cleanup;
+ }
+
+ ret = lyjson_value(jsonctx);
+
+ if (jsonctx->status.count > 1 && lyjson_ctx_status(jsonctx, 0) == LYJSON_END) {
+ LOGVAL(jsonctx->ctx, LY_VLOG_LINE, &jsonctx->line, LY_VCODE_EOF);
+ ret = LY_EVALID;
+ }
+
+cleanup:
+ if (ret) {
+ lyjson_ctx_free(jsonctx);
+ } else {
+ *jsonctx_p = jsonctx;
+ }
+ return ret;
+}
+
+void
+lyjson_ctx_backup(struct lyjson_ctx *jsonctx)
+{
+ if (jsonctx->backup.dynamic) {
+ free((char *)jsonctx->backup.value);
+ }
+ jsonctx->backup.status = lyjson_ctx_status(jsonctx, 0);
+ jsonctx->backup.status_count = jsonctx->status.count;
+ jsonctx->backup.value = jsonctx->value;
+ jsonctx->backup.value_len = jsonctx->value_len;
+ jsonctx->backup.input = jsonctx->in->current;
+ jsonctx->backup.dynamic = jsonctx->dynamic;
+ jsonctx->dynamic = 0;
+}
+
+void
+lyjson_ctx_restore(struct lyjson_ctx *jsonctx)
+{
+ if (jsonctx->dynamic) {
+ free((char *)jsonctx->value);
+ }
+ jsonctx->status.count = jsonctx->backup.status_count;
+ jsonctx->status.objs[jsonctx->backup.status_count - 1] = (void*)jsonctx->backup.status;
+ jsonctx->value = jsonctx->backup.value;
+ jsonctx->value_len = jsonctx->backup.value_len;
+ jsonctx->in->current = jsonctx->backup.input;
+ jsonctx->dynamic = jsonctx->backup.dynamic;
+ jsonctx->backup.dynamic = 0;
+}
+
+LY_ERR
+lyjson_ctx_next(struct lyjson_ctx *jsonctx, enum LYJSON_PARSER_STATUS *status)
+{
+ LY_ERR ret = LY_SUCCESS;
+ int toplevel = 0;
+ enum LYJSON_PARSER_STATUS prev;
+
+ assert(jsonctx);
+
+ prev = lyjson_ctx_status(jsonctx, 0);
+
+ if (prev == LYJSON_OBJECT || prev == LYJSON_ARRAY) {
+ /* get value for the object's member OR the first value in the array */
+ ret = lyjson_value(jsonctx);
+ goto result;
+ } else {
+ /* the previous token is closed and should be completely processed */
+ JSON_POP_STATUS_RET(jsonctx);
+ prev = lyjson_ctx_status(jsonctx, 0);
+ }
+
+ if (!jsonctx->status.count) {
+ /* we are done with the top level value */
+ toplevel = 1;
+ }
+ LY_CHECK_RET(skip_ws(jsonctx));
+ if (toplevel && !jsonctx->status.count) {
+ /* EOF expected, but there are some data after the top level token */
+ LOGVAL(jsonctx->ctx, LY_VLOG_LINE, &jsonctx->line, LYVE_SYNTAX,
+ "Expecting end-of-input, but some data follows the top level JSON value.");
+ return LY_EVALID;
+ }
+
+ if (toplevel) {
+ /* we are done */
+ return LY_SUCCESS;
+ }
+
+ /* continue with the next token */
+ assert(prev == LYJSON_OBJECT || prev == LYJSON_ARRAY);
+
+ if (*jsonctx->in->current == ',') {
+ /* sibling item in the ... */
+ ly_in_skip(jsonctx->in, 1);
+ LY_CHECK_RET(skip_ws(jsonctx));
+
+ if (prev == LYJSON_OBJECT) {
+ /* ... object - get another object's member */
+ ret = lyjson_object_name(jsonctx);
+ } else { /* LYJSON_ARRAY */
+ /* ... array - get another complete value */
+ ret = lyjson_value(jsonctx);
+ }
+ } else if ((prev == LYJSON_OBJECT && *jsonctx->in->current == '}') || (prev == LYJSON_ARRAY && *jsonctx->in->current == ']')) {
+ ly_in_skip(jsonctx->in, 1);
+ JSON_POP_STATUS_RET(jsonctx);
+ JSON_PUSH_STATUS_RET(jsonctx, prev + 1);
+ } else {
+ /* unexpected value */
+ LOGVAL(jsonctx->ctx, LY_VLOG_LINE, &jsonctx->line, LY_VCODE_INSTREXP, LY_VCODE_INSTREXP_len(jsonctx->in->current),
+ jsonctx->in->current, prev == LYJSON_ARRAY ? "another JSON value in array" : "another JSON object's member");
+ return LY_EVALID;
+ }
+
+result:
+ if (ret == LY_SUCCESS && jsonctx->status.count > 1 && lyjson_ctx_status(jsonctx, 0) == LYJSON_END) {
+ LOGVAL(jsonctx->ctx, LY_VLOG_LINE, &jsonctx->line, LY_VCODE_EOF);
+ ret = LY_EVALID;
+ }
+
+ if (ret == LY_SUCCESS && status) {
+ *status = lyjson_ctx_status(jsonctx, 0);
+ }
+
+ return ret;
+}
+
+enum LYJSON_PARSER_STATUS
+lyjson_ctx_status(struct lyjson_ctx *jsonctx, uint32_t index)
+{
+ assert(jsonctx);
+
+ if (jsonctx->status.count < index) {
+ return LYJSON_ERROR;
+ } else if (jsonctx->status.count == index) {
+ return LYJSON_ROOT;
+ } else {
+ return (enum LYJSON_PARSER_STATUS)jsonctx->status.objs[jsonctx->status.count - (index + 1)];
+ }
+}
+
+void
+lyjson_ctx_free(struct lyjson_ctx *jsonctx)
+{
+ if (!jsonctx) {
+ return;
+ }
+
+ if (jsonctx->dynamic) {
+ free((char*)jsonctx->value);
+ }
+ if (jsonctx->backup.dynamic) {
+ free((char *)jsonctx->backup.value);
+ }
+
+ ly_set_erase(&jsonctx->status, NULL);
+
+ free(jsonctx);
+}
+
diff --git a/src/json.h b/src/json.h
new file mode 100644
index 0000000..8373284
--- /dev/null
+++ b/src/json.h
@@ -0,0 +1,127 @@
+/**
+ * @file json.h
+ * @author Radek Krejci <rkrejci@cesnet.cz>
+ * @brief Generic JSON format parser routines.
+ *
+ * Copyright (c) 2020 CESNET, z.s.p.o.
+ *
+ * This source code is licensed under BSD 3-Clause License (the "License").
+ * You may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://opensource.org/licenses/BSD-3-Clause
+ */
+
+#ifndef LY_JSON_H_
+#define LY_JSON_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "log.h"
+#include "set.h"
+
+struct ly_ctx;
+struct ly_out;
+struct ly_prefix;
+
+/* Macro to test if character is whitespace */
+#define is_jsonws(c) (c == 0x20 || c == 0x9 || c == 0xa || c == 0xd)
+
+/* Macro to test if character is valid string character */
+#define is_jsonstrchar(c) (c == 0x20 || c == 0x21 || (c >= 0x23 && c <= 0x5b) || (c >= 0x5d && c <= 0x10ffff))
+
+/**
+ * @brief Status of the parser providing information what is expected next (which function is supposed to be called).
+ */
+enum LYJSON_PARSER_STATUS {
+ LYJSON_ERROR, /* JSON parser error - value is used as an error return code */
+ LYJSON_ROOT, /* JSON document root, used internally */
+ LYJSON_OBJECT, /* JSON object */
+ LYJSON_OBJECT_CLOSED, /* JSON object closed */
+ LYJSON_OBJECT_EMPTY, /* empty JSON object { }*/
+ LYJSON_ARRAY, /* JSON array */
+ LYJSON_ARRAY_CLOSED, /* JSON array closed */
+ LYJSON_ARRAY_EMPTY, /* empty JSON array */
+ LYJSON_NUMBER, /* JSON number value */
+ LYJSON_STRING, /* JSON string value */
+ LYJSON_FALSE, /* JSON false value */
+ LYJSON_TRUE, /* JSON true value */
+ LYJSON_NULL, /* JSON null value */
+ LYJSON_END /* end of input data */
+};
+
+struct lyjson_ctx {
+
+ struct ly_set status; /* stack of LYJSON_PARSER_STATUS values corresponding to the JSON items being processed */
+
+ const char *value; /* LYJSON_STRING, LYJSON_NUMBER, LYJSON_OBJECT */
+ size_t value_len; /* LYJSON_STRING, LYJSON_NUMBER, LYJSON_OBJECT */
+ int dynamic; /* LYJSON_STRING, LYJSON_NUMBER, LYJSON_OBJECT */
+
+ struct {
+ enum LYJSON_PARSER_STATUS status;
+ uint32_t status_count;
+ const char *value;
+ size_t value_len;
+ int dynamic;
+ const char *input;
+ } backup;
+};
+
+/**
+ * @brief Create a new JSON parser context and start parsing.
+ *
+ * @param[in] ctx libyang context.
+ * @param[in] in JSON string data to parse.
+ * @param[out] jsonctx New JSON context with status ::LYJSON_VALUE.
+ * @return LY_ERR value.
+ */
+LY_ERR lyjson_ctx_new(const struct ly_ctx *ctx, struct ly_in *in, struct lyjson_ctx **jsonctx);
+
+/**
+ * @brief Get status of the parser as the last/previous parsed token
+ *
+ * @param[in] jsonctx JSON context to check.
+ * @param[in] index Index of the token, starting by 0 for the last token
+ * @return LYJSON_ERROR in case of invalid index, other LYJSON_PARSER_STATUS corresponding to the token.
+ */
+enum LYJSON_PARSER_STATUS lyjson_ctx_status(struct lyjson_ctx *jsonctx, uint32_t index);
+
+/**
+ * @brief Get string representation of the JSON context status (token).
+ *
+ * @param[in] status Context status (aka JSON token)
+ * @return String representation of the @p status.
+ */
+const char* lyjson_token2str(enum LYJSON_PARSER_STATUS status);
+
+/**
+ * @brief Move to the next JSON artefact and update parser status.
+ *
+ * @param[in] jsonctx XML context to move.
+ * @param[out] status Optional parameter to provide new parser status
+ * @return LY_ERR value.
+ */
+LY_ERR lyjson_ctx_next(struct lyjson_ctx *jsonctx, enum LYJSON_PARSER_STATUS *status);
+
+/**
+ * @brief Backup the JSON parser context's state To restore the backup, use lyjson_ctx_restore().
+ * @param[in] jsonctx JSON parser context to backup.
+ */
+void lyjson_ctx_backup(struct lyjson_ctx *jsonctx);
+
+/**
+ * @brief REstore the JSON parser context's state from the backup created by lyjson_ctx_backup().
+ * @param[in] jsonctx JSON parser context to restore.
+ */
+void lyjson_ctx_restore(struct lyjson_ctx *jsonctx);
+
+/**
+ * @brief Remove the allocated working memory of the context.
+ *
+ * @param[in] jsonctx JSON context to clear.
+ */
+void lyjson_ctx_free(struct lyjson_ctx *jsonctx);
+
+#endif /* LY_JSON_H_ */
diff --git a/src/xml.c b/src/xml.c
index 67819ee..d1ba184 100644
--- a/src/xml.c
+++ b/src/xml.c
@@ -325,71 +325,6 @@
}
/**
- * Store UTF-8 character specified as 4byte integer into the dst buffer.
- * Returns number of written bytes (4 max), expects that dst has enough space.
- *
- * UTF-8 mapping:
- * 00000000 -- 0000007F: 0xxxxxxx
- * 00000080 -- 000007FF: 110xxxxx 10xxxxxx
- * 00000800 -- 0000FFFF: 1110xxxx 10xxxxxx 10xxxxxx
- * 00010000 -- 001FFFFF: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
- *
- * Includes checking for valid characters (following RFC 7950, sec 9.4)
- */
-static LY_ERR
-lyxml_pututf8(char *dst, uint32_t value, size_t *bytes_written)
-{
- if (value < 0x80) {
- /* one byte character */
- if (value < 0x20 &&
- value != 0x09 &&
- value != 0x0a &&
- value != 0x0d) {
- return LY_EINVAL;
- }
-
- dst[0] = value;
- (*bytes_written) = 1;
- } else if (value < 0x800) {
- /* two bytes character */
- dst[0] = 0xc0 | (value >> 6);
- dst[1] = 0x80 | (value & 0x3f);
- (*bytes_written) = 2;
- } else if (value < 0xfffe) {
- /* three bytes character */
- if (((value & 0xf800) == 0xd800) ||
- (value >= 0xfdd0 && value <= 0xfdef)) {
- /* exclude surrogate blocks %xD800-DFFF */
- /* exclude noncharacters %xFDD0-FDEF */
- return LY_EINVAL;
- }
-
- dst[0] = 0xe0 | (value >> 12);
- dst[1] = 0x80 | ((value >> 6) & 0x3f);
- dst[2] = 0x80 | (value & 0x3f);
-
- (*bytes_written) = 3;
- } else if (value < 0x10fffe) {
- if ((value & 0xffe) == 0xffe) {
- /* exclude noncharacters %xFFFE-FFFF, %x1FFFE-1FFFF, %x2FFFE-2FFFF, %x3FFFE-3FFFF, %x4FFFE-4FFFF,
- * %x5FFFE-5FFFF, %x6FFFE-6FFFF, %x7FFFE-7FFFF, %x8FFFE-8FFFF, %x9FFFE-9FFFF, %xAFFFE-AFFFF,
- * %xBFFFE-BFFFF, %xCFFFE-CFFFF, %xDFFFE-DFFFF, %xEFFFE-EFFFF, %xFFFFE-FFFFF, %x10FFFE-10FFFF */
- return LY_EINVAL;
- }
- /* four bytes character */
- dst[0] = 0xf0 | (value >> 18);
- dst[1] = 0x80 | ((value >> 12) & 0x3f);
- dst[2] = 0x80 | ((value >> 6) & 0x3f);
- dst[3] = 0x80 | (value & 0x3f);
-
- (*bytes_written) = 4;
- } else {
- return LY_EINVAL;
- }
- return LY_SUCCESS;
-}
-
-/**
* @brief Parse XML text content (value).
*
* @param[in] xmlctx XML context to use.
@@ -507,7 +442,7 @@
LY_VCODE_INSTREXP_len(&in[offset]), &in[offset], ";"),
error);
++offset;
- LY_CHECK_ERR_GOTO(lyxml_pututf8(&buf[len], n, &u),
+ LY_CHECK_ERR_GOTO(ly_pututf8(&buf[len], n, &u),
LOGVAL(ctx, LY_VLOG_LINE, &xmlctx->line, LYVE_SYNTAX,
"Invalid character reference \"%.*s\" (0x%08x).", 12, p, n),
error);
diff --git a/tests/utests/CMakeLists.txt b/tests/utests/CMakeLists.txt
index 624cfdc..5180113 100644
--- a/tests/utests/CMakeLists.txt
+++ b/tests/utests/CMakeLists.txt
@@ -4,6 +4,7 @@
ly_add_utest(NAME inout SOURCES test_inout.c)
ly_add_utest(NAME context SOURCES test_context.c)
ly_add_utest(NAME xml SOURCES test_xml.c)
+ly_add_utest(NAME json SOURCES test_json.c)
ly_add_utest(NAME xpath SOURCES test_xpath.c)
ly_add_utest(NAME yanglib SOURCES test_yanglib.c)
ly_add_utest(NAME schema SOURCES schema/test_schema.c schema/test_schema_common.c schema/test_schema_stmts.c)
diff --git a/tests/utests/test_json.c b/tests/utests/test_json.c
new file mode 100644
index 0000000..0482fd8
--- /dev/null
+++ b/tests/utests/test_json.c
@@ -0,0 +1,579 @@
+/*
+ * @file json.c
+ * @author: Radek Krejci <rkrejci@cesnet.cz>
+ * @brief unit tests for a generic JSON parser
+ *
+ * Copyright (c) 2020 CESNET, z.s.p.o.
+ *
+ * This source code is licensed under BSD 3-Clause License (the "License").
+ * You may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://opensource.org/licenses/BSD-3-Clause
+ */
+
+#define _DEFAULT_SOURCE
+#define _GNU_SOURCE
+
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <cmocka.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "json.h"
+#include "context.h"
+#include "parser_internal.h"
+
+void *testfunc = NULL;
+
+static int
+setup(void **state)
+{
+ if (ly_ctx_new(NULL, 0, (struct ly_ctx **)state)) {
+ return 1;
+ }
+
+ return 0;
+}
+
+static int
+teardown(void **state)
+{
+ ly_ctx_destroy(*state, NULL);
+ return 0;
+}
+
+#define BUFSIZE 1024
+char logbuf[BUFSIZE] = {0};
+
+/* set to 0 to printing error messages to stderr instead of checking them in code */
+#define ENABLE_LOGGER_CHECKING 1
+
+static void
+logger(LY_LOG_LEVEL level, const char *msg, const char *path)
+{
+ (void) level; /* unused */
+
+ if (path) {
+ snprintf(logbuf, BUFSIZE - 1, "%s %s", msg, path);
+ } else {
+ strncpy(logbuf, msg, BUFSIZE - 1);
+ }
+}
+
+static int
+logger_setup(void **state)
+{
+ (void) state; /* unused */
+#if ENABLE_LOGGER_CHECKING
+ ly_set_log_clb(logger, 1);
+#endif
+ return 0;
+}
+
+static int
+logger_teardown(void **state)
+{
+ (void) state; /* unused */
+#if ENABLE_LOGGER_CHECKING
+ if (testfunc) {
+ fprintf(stderr, "%s\n", logbuf);
+ }
+#endif
+ return 0;
+}
+
+void
+logbuf_clean(void)
+{
+ logbuf[0] = '\0';
+}
+
+#if ENABLE_LOGGER_CHECKING
+# define logbuf_assert(str) assert_string_equal(logbuf, str)
+#else
+# define logbuf_assert(str)
+#endif
+
+
+
+static void
+test_general(void **state)
+{
+ struct lyjson_ctx *jsonctx;
+ struct ly_in *in;
+ const char *str;
+
+ testfunc = test_general;
+
+ /* empty */
+ str = "";
+ assert_int_equal(LY_SUCCESS, ly_in_new_memory(str, &in));
+ assert_int_equal(LY_SUCCESS, lyjson_ctx_new(*state, in, &jsonctx));
+ assert_int_equal(LYJSON_END, lyjson_ctx_status(jsonctx, 0));
+ lyjson_ctx_free(jsonctx);
+
+ str = " \n\t \n";
+ assert_non_null(ly_in_memory(in, str));
+ assert_int_equal(LY_SUCCESS, lyjson_ctx_new(*state, in, &jsonctx));
+ assert_int_equal(LYJSON_END, lyjson_ctx_status(jsonctx, 0));
+ lyjson_ctx_free(jsonctx);
+
+ /* constant values */
+ str = "true";
+ assert_non_null(ly_in_memory(in, str));
+ assert_int_equal(LY_SUCCESS, lyjson_ctx_new(*state, in, &jsonctx));
+ assert_int_equal(LYJSON_TRUE, lyjson_ctx_status(jsonctx, 0));
+ assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL));
+ assert_int_equal(LYJSON_END, lyjson_ctx_status(jsonctx, 0));
+ lyjson_ctx_free(jsonctx);
+
+ str = "false";
+ assert_non_null(ly_in_memory(in, str));
+ assert_int_equal(LY_SUCCESS, lyjson_ctx_new(*state, in, &jsonctx));
+ assert_int_equal(LYJSON_FALSE, lyjson_ctx_status(jsonctx, 0));
+ assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL));
+ assert_int_equal(LYJSON_END, lyjson_ctx_status(jsonctx, 0));
+ lyjson_ctx_free(jsonctx);
+
+ str = "null";
+ assert_non_null(ly_in_memory(in, str));
+ assert_int_equal(LY_SUCCESS, lyjson_ctx_new(*state, in, &jsonctx));
+ assert_int_equal(LYJSON_NULL, lyjson_ctx_status(jsonctx, 0));
+ assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL));
+ assert_int_equal(LYJSON_END, lyjson_ctx_status(jsonctx, 0));
+ lyjson_ctx_free(jsonctx);
+
+ ly_in_free(in, 0);
+ testfunc = NULL;
+}
+
+static void
+test_number(void **state)
+{
+ struct lyjson_ctx *jsonctx;
+ struct ly_in *in;
+ const char *str;
+
+ testfunc = test_number;
+
+ /* simple value */
+ str = "11";
+ assert_int_equal(LY_SUCCESS, ly_in_new_memory(str, &in));
+ assert_int_equal(LY_SUCCESS, lyjson_ctx_new(*state, in, &jsonctx));
+ assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx, 0));
+ assert_string_equal("11", jsonctx->value);
+ assert_int_equal(2, jsonctx->value_len);
+ assert_int_equal(0, jsonctx->dynamic);
+ lyjson_ctx_free(jsonctx);
+
+ /* fraction number */
+ str = "37.7668";
+ assert_non_null(ly_in_memory(in, str));
+ assert_int_equal(LY_SUCCESS, lyjson_ctx_new(*state, in, &jsonctx));
+ assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx, 0));
+ assert_string_equal("37.7668", jsonctx->value);
+ assert_int_equal(7, jsonctx->value_len);
+ assert_int_equal(0, jsonctx->dynamic);
+ lyjson_ctx_free(jsonctx);
+
+ /* negative number */
+ str = "-122.3959";
+ assert_non_null(ly_in_memory(in, str));
+ assert_int_equal(LY_SUCCESS, lyjson_ctx_new(*state, in, &jsonctx));
+ assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx, 0));
+ assert_string_equal("-122.3959", jsonctx->value);
+ assert_int_equal(9, jsonctx->value_len);
+ assert_int_equal(0, jsonctx->dynamic);
+ lyjson_ctx_free(jsonctx);
+
+ /* exp number */
+ str = "1E10";
+ assert_non_null(ly_in_memory(in, str));
+ assert_int_equal(LY_SUCCESS, lyjson_ctx_new(*state, in, &jsonctx));
+ assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx, 0));
+ assert_string_equal("10000000000", jsonctx->value);
+ assert_int_equal(11, jsonctx->value_len);
+ assert_int_equal(1, jsonctx->dynamic);
+ lyjson_ctx_free(jsonctx);
+
+ str = "15E-1";
+ assert_non_null(ly_in_memory(in, str));
+ assert_int_equal(LY_SUCCESS, lyjson_ctx_new(*state, in, &jsonctx));
+ assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx, 0));
+ assert_string_equal("1.5", jsonctx->value);
+ assert_int_equal(3, jsonctx->value_len);
+ assert_int_equal(1, jsonctx->dynamic);
+ lyjson_ctx_free(jsonctx);
+
+ str = "15E-3";
+ assert_non_null(ly_in_memory(in, str));
+ assert_int_equal(LY_SUCCESS, lyjson_ctx_new(*state, in, &jsonctx));
+ assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx, 0));
+ assert_string_equal("0.015", jsonctx->value);
+ assert_int_equal(5, jsonctx->value_len);
+ assert_int_equal(1, jsonctx->dynamic);
+ lyjson_ctx_free(jsonctx);
+
+ /* exp fraction number */
+ str = "1.1e3";
+ assert_non_null(ly_in_memory(in, str));
+ assert_int_equal(LY_SUCCESS, lyjson_ctx_new(*state, in, &jsonctx));
+ assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx, 0));
+ assert_string_equal("1100", jsonctx->value);
+ assert_int_equal(4, jsonctx->value_len);
+ assert_int_equal(1, jsonctx->dynamic);
+ lyjson_ctx_free(jsonctx);
+
+ /* negative exp fraction number */
+ str = "1.1e-3";
+ assert_non_null(ly_in_memory(in, str));
+ assert_int_equal(LY_SUCCESS, lyjson_ctx_new(*state, in, &jsonctx));
+ assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx, 0));
+ assert_string_equal("0.0011", jsonctx->value);
+ assert_int_equal(6, jsonctx->value_len);
+ assert_int_equal(1, jsonctx->dynamic);
+ lyjson_ctx_free(jsonctx);
+
+ /* exp negative fraction number */
+ str = "-0.11e3";
+ assert_non_null(ly_in_memory(in, str));
+ assert_int_equal(LY_SUCCESS, lyjson_ctx_new(*state, in, &jsonctx));
+ assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx, 0));
+ assert_string_equal("-110", jsonctx->value);
+ assert_int_equal(4, jsonctx->value_len);
+ assert_int_equal(1, jsonctx->dynamic);
+ lyjson_ctx_free(jsonctx);
+
+ /* negative exp negative fraction number */
+ str = "-3.14e-3";
+ assert_non_null(ly_in_memory(in, str));
+ assert_int_equal(LY_SUCCESS, lyjson_ctx_new(*state, in, &jsonctx));
+ assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx, 0));
+ assert_string_equal("-0.00314", jsonctx->value);
+ assert_int_equal(8, jsonctx->value_len);
+ assert_int_equal(1, jsonctx->dynamic);
+ lyjson_ctx_free(jsonctx);
+
+ /* various invalid inputs */
+ str = "-x";
+ assert_non_null(ly_in_memory(in, str));
+ assert_int_equal(LY_EVALID, lyjson_ctx_new(*state, in, &jsonctx));
+ logbuf_assert("Invalid character in JSON Number value (\"x\"). Line number 1.");
+
+ str = " -";
+ assert_non_null(ly_in_memory(in, str));
+ assert_int_equal(LY_EVALID, lyjson_ctx_new(*state, in, &jsonctx));
+ logbuf_assert("Unexpected end-of-input. Line number 1.");
+
+ str = "--1";
+ assert_non_null(ly_in_memory(in, str));
+ assert_int_equal(LY_EVALID, lyjson_ctx_new(*state, in, &jsonctx));
+ logbuf_assert("Invalid character in JSON Number value (\"-\"). Line number 1.");
+
+ str = "+1";
+ assert_non_null(ly_in_memory(in, str));
+ assert_int_equal(LY_EVALID, lyjson_ctx_new(*state, in, &jsonctx));
+ logbuf_assert("Invalid character sequence \"+1\", expected a JSON value. Line number 1.");
+
+ str = " 1.x ";
+ assert_non_null(ly_in_memory(in, str));
+ assert_int_equal(LY_EVALID, lyjson_ctx_new(*state, in, &jsonctx));
+ logbuf_assert("Invalid character in JSON Number value (\"x\"). Line number 1.");
+
+ str = "1.";
+ assert_non_null(ly_in_memory(in, str));
+ assert_int_equal(LY_EVALID, lyjson_ctx_new(*state, in, &jsonctx));
+ logbuf_assert("Unexpected end-of-input. Line number 1.");
+
+ str = " 1eo ";
+ assert_non_null(ly_in_memory(in, str));
+ assert_int_equal(LY_EVALID, lyjson_ctx_new(*state, in, &jsonctx));
+ logbuf_assert("Invalid character in JSON Number value (\"o\"). Line number 1.");
+
+ str = "1e";
+ assert_non_null(ly_in_memory(in, str));
+ assert_int_equal(LY_EVALID, lyjson_ctx_new(*state, in, &jsonctx));
+ logbuf_assert("Unexpected end-of-input. Line number 1.");
+
+ ly_in_free(in, 0);
+ testfunc = NULL;
+}
+
+static void
+test_string(void **state)
+{
+ struct lyjson_ctx *jsonctx;
+ struct ly_in *in;
+ const char *str;
+
+ testfunc = test_string;
+
+ /* simple string */
+ str = "\"hello\"";
+ assert_int_equal(LY_SUCCESS, ly_in_new_memory(str, &in));
+ assert_int_equal(LY_SUCCESS, lyjson_ctx_new(*state, in, &jsonctx));
+ assert_int_equal(LYJSON_STRING, lyjson_ctx_status(jsonctx, 0));
+ assert_ptr_equal(&str[1], jsonctx->value);
+ assert_int_equal(5, jsonctx->value_len);
+ assert_int_equal(0, jsonctx->dynamic);
+ lyjson_ctx_free(jsonctx);
+
+ /* 4-byte utf8 character */
+ str = "\"\\t𠜎\"";
+ assert_non_null(ly_in_memory(in, str));
+ assert_int_equal(LY_SUCCESS, lyjson_ctx_new(*state, in, &jsonctx));
+ assert_int_equal(LYJSON_STRING, lyjson_ctx_status(jsonctx, 0));
+ assert_string_equal("\t𠜎", jsonctx->value);
+ assert_int_equal(5, jsonctx->value_len);
+ assert_int_equal(1, jsonctx->dynamic);
+ lyjson_ctx_free(jsonctx);
+
+ /* valid escape sequences - note that here it mixes valid JSON string characters (RFC 7159, sec. 7) and
+ * valid characters in YANG string type (RFC 7950, sec. 9.4). Since the latter is a subset of JSON string,
+ * the YANG string type's restrictions apply to the JSON escape sequences */
+ str = "\"\\\" \\\\ \\r \\/ \\n \\t \\u20ac\"";
+ assert_non_null(ly_in_memory(in, str));
+ assert_int_equal(LY_SUCCESS, lyjson_ctx_new(*state, in, &jsonctx));
+ assert_int_equal(LYJSON_STRING, lyjson_ctx_status(jsonctx, 0));
+ assert_string_equal("\" \\ \r / \n \t €", jsonctx->value);
+ assert_int_equal(15, jsonctx->value_len);
+ assert_int_equal(1, jsonctx->dynamic);
+ lyjson_ctx_free(jsonctx);
+
+ /* backspace and form feed are valid JSON escape sequences, but the control characters they represents are not allowed values for YANG string type */
+ str = "\"\\b\"";
+ assert_non_null(ly_in_memory(in, str));
+ assert_int_equal(LY_EVALID, lyjson_ctx_new(*state, in, &jsonctx));
+ logbuf_assert("Invalid character reference \"\\b\" (0x00000008). Line number 1.");
+
+ str = "\"\\f\"";
+ assert_non_null(ly_in_memory(in, str));
+ assert_int_equal(LY_EVALID, lyjson_ctx_new(*state, in, &jsonctx));
+ logbuf_assert("Invalid character reference \"\\f\" (0x0000000c). Line number 1.");
+
+ /* unterminated string */
+ str = "\"unterminated string";
+ assert_non_null(ly_in_memory(in, str));
+ assert_int_equal(LY_EVALID, lyjson_ctx_new(*state, in, &jsonctx));
+ logbuf_assert("Missing quotation-mark at the end of a JSON string. Line number 1.");
+
+ /* invalid escape sequence */
+ str = "\"char \\x \"";
+ assert_non_null(ly_in_memory(in, str));
+ assert_int_equal(LY_EVALID, lyjson_ctx_new(*state, in, &jsonctx));
+ logbuf_assert("Invalid character escape sequence \\x. Line number 1.");
+
+ /* new line is allowed only as escaped character in JSON */
+ str = "\"\n\"";
+ assert_non_null(ly_in_memory(in, str));
+ assert_int_equal(LY_EVALID, lyjson_ctx_new(*state, in, &jsonctx));
+ logbuf_assert("Invalid character in JSON string \"\n\" (0x0000000a). Line number 1.");
+
+ ly_in_free(in, 0);
+ testfunc = NULL;
+}
+
+static void
+test_object(void **state)
+{
+ struct lyjson_ctx *jsonctx;
+ struct ly_in *in;
+ const char *str;
+
+ testfunc = test_object;
+
+ /* empty */
+ str = " { } ";
+ assert_int_equal(LY_SUCCESS, ly_in_new_memory(str, &in));
+ assert_int_equal(LY_SUCCESS, lyjson_ctx_new(*state, in, &jsonctx));
+ assert_int_equal(LYJSON_OBJECT_EMPTY, lyjson_ctx_status(jsonctx, 0));
+ assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL));
+ assert_int_equal(LYJSON_END, lyjson_ctx_status(jsonctx, 0));
+ lyjson_ctx_free(jsonctx);
+
+ /* simple value */
+ str = "{\"name\" : \"Radek\"}";
+ assert_non_null(ly_in_memory(in, str));
+ assert_int_equal(LY_SUCCESS, lyjson_ctx_new(*state, in, &jsonctx));
+ assert_int_equal(LYJSON_OBJECT, lyjson_ctx_status(jsonctx, 0));
+ assert_ptr_equal(&str[2], jsonctx->value);
+ assert_int_equal(4, jsonctx->value_len);
+ assert_int_equal(0, jsonctx->dynamic);
+ assert_string_equal("\"Radek\"}", jsonctx->in->current);
+ assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL));
+ assert_int_equal(LYJSON_STRING, lyjson_ctx_status(jsonctx, 0));
+ assert_string_equal("Radek\"}", jsonctx->value);
+ assert_int_equal(5, jsonctx->value_len);
+ assert_int_equal(0, jsonctx->dynamic);
+ assert_string_equal("}", jsonctx->in->current);
+ assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL));
+ assert_int_equal(LYJSON_OBJECT_CLOSED, lyjson_ctx_status(jsonctx, 0));
+ assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL));
+ assert_int_equal(LYJSON_END, lyjson_ctx_status(jsonctx, 0));
+ lyjson_ctx_free(jsonctx);
+
+ /* two values */
+ str = "{\"smart\" : true,\"handsom\":false}";
+ assert_non_null(ly_in_memory(in, str));
+ assert_int_equal(LY_SUCCESS, lyjson_ctx_new(*state, in, &jsonctx));
+ assert_int_equal(LYJSON_OBJECT, lyjson_ctx_status(jsonctx, 0));
+ assert_string_equal("smart\" : true,\"handsom\":false}", jsonctx->value);
+ assert_int_equal(5, jsonctx->value_len);
+ assert_int_equal(0, jsonctx->dynamic);
+ assert_string_equal("true,\"handsom\":false}", jsonctx->in->current);
+ assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL));
+ assert_int_equal(LYJSON_TRUE, lyjson_ctx_status(jsonctx, 0));
+ assert_string_equal(",\"handsom\":false}", jsonctx->in->current);
+ assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL));
+ assert_int_equal(LYJSON_OBJECT, lyjson_ctx_status(jsonctx, 0));
+ assert_string_equal("handsom\":false}", jsonctx->value);
+ assert_int_equal(7, jsonctx->value_len);
+ assert_int_equal(0, jsonctx->dynamic);
+ assert_string_equal("false}", jsonctx->in->current);
+ assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL));
+ assert_int_equal(LYJSON_FALSE, lyjson_ctx_status(jsonctx, 0));
+ assert_string_equal("}", jsonctx->in->current);
+ assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL));
+ assert_int_equal(LYJSON_OBJECT_CLOSED, lyjson_ctx_status(jsonctx, 0));
+ assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL));
+ assert_int_equal(LYJSON_END, lyjson_ctx_status(jsonctx, 0));
+ lyjson_ctx_free(jsonctx);
+
+ /* inherited objects */
+ str = "{\"person\" : {\"name\":\"Radek\"}}";
+ assert_non_null(ly_in_memory(in, str));
+ assert_int_equal(LY_SUCCESS, lyjson_ctx_new(*state, in, &jsonctx));
+ assert_int_equal(LYJSON_OBJECT, lyjson_ctx_status(jsonctx, 0));
+ assert_string_equal("person\" : {\"name\":\"Radek\"}}", jsonctx->value);
+ assert_int_equal(6, jsonctx->value_len);
+ assert_int_equal(0, jsonctx->dynamic);
+ assert_string_equal("{\"name\":\"Radek\"}}", jsonctx->in->current);
+ assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL));
+ assert_int_equal(LYJSON_OBJECT, lyjson_ctx_status(jsonctx, 0));
+ assert_string_equal("name\":\"Radek\"}}", jsonctx->value);
+ assert_int_equal(4, jsonctx->value_len);
+ assert_int_equal(0, jsonctx->dynamic);
+ assert_string_equal("\"Radek\"}}", jsonctx->in->current);
+ assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL));
+ assert_int_equal(LYJSON_STRING, lyjson_ctx_status(jsonctx, 0));
+ assert_string_equal("Radek\"}}", jsonctx->value);
+ assert_int_equal(5, jsonctx->value_len);
+ assert_int_equal(0, jsonctx->dynamic);
+ assert_string_equal("}}", jsonctx->in->current);
+ assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL));
+ assert_int_equal(LYJSON_OBJECT_CLOSED, lyjson_ctx_status(jsonctx, 0));
+ assert_string_equal("}", jsonctx->in->current);
+ assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL));
+ assert_int_equal(LYJSON_OBJECT_CLOSED, lyjson_ctx_status(jsonctx, 0));
+ assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL));
+ assert_int_equal(LYJSON_END, lyjson_ctx_status(jsonctx, 0));
+ lyjson_ctx_free(jsonctx);
+
+ /* new line is allowed only as escaped character in JSON */
+ str = "{ unquoted : \"data\"}";
+ assert_non_null(ly_in_memory(in, str));
+ assert_int_equal(LY_EVALID, lyjson_ctx_new(*state, in, &jsonctx));
+ logbuf_assert("Invalid character sequence \"unquoted : \"data\"}\", expected a JSON object's member. Line number 1.");
+
+ ly_in_free(in, 0);
+ testfunc = NULL;
+}
+
+static void
+test_array(void **state)
+{
+ struct lyjson_ctx *jsonctx;
+ struct ly_in *in;
+ const char *str;
+
+ testfunc = test_array;
+
+ /* empty */
+ str = " [ ] ";
+ assert_int_equal(LY_SUCCESS, ly_in_new_memory(str, &in));
+ assert_int_equal(LY_SUCCESS, lyjson_ctx_new(*state, in, &jsonctx));
+ assert_int_equal(LYJSON_ARRAY_EMPTY, lyjson_ctx_status(jsonctx, 0));
+ assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL));
+ assert_int_equal(LYJSON_END, lyjson_ctx_status(jsonctx, 0));
+ lyjson_ctx_free(jsonctx);
+
+ /* simple value */
+ str = "[ null]";
+ assert_non_null(ly_in_memory(in, str));
+ assert_int_equal(LY_SUCCESS, lyjson_ctx_new(*state, in, &jsonctx));
+ assert_int_equal(LYJSON_ARRAY, lyjson_ctx_status(jsonctx, 0));
+ assert_null(jsonctx->value);
+ assert_int_equal(0, jsonctx->value_len);
+ assert_int_equal(0, jsonctx->dynamic);
+ assert_string_equal("null]", jsonctx->in->current);
+ assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL));
+ assert_int_equal(LYJSON_NULL, lyjson_ctx_status(jsonctx, 0));
+ assert_string_equal("]", jsonctx->in->current);
+ assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL));
+ assert_int_equal(LYJSON_ARRAY_CLOSED, lyjson_ctx_status(jsonctx, 0));
+ assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL));
+ assert_int_equal(LYJSON_END, lyjson_ctx_status(jsonctx, 0));
+ lyjson_ctx_free(jsonctx);
+
+ /* two values */
+ str = "[{\"a\":null},\"x\"]";
+ assert_non_null(ly_in_memory(in, str));
+ assert_int_equal(LY_SUCCESS, lyjson_ctx_new(*state, in, &jsonctx));
+ assert_int_equal(LYJSON_ARRAY, lyjson_ctx_status(jsonctx, 0));
+ assert_null(jsonctx->value);
+ assert_int_equal(0, jsonctx->value_len);
+ assert_int_equal(0, jsonctx->dynamic);
+ assert_string_equal("{\"a\":null},\"x\"]", jsonctx->in->current);
+ assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL));
+ assert_int_equal(LYJSON_OBJECT, lyjson_ctx_status(jsonctx, 0));
+ assert_string_equal("a\":null},\"x\"]", jsonctx->value);
+ assert_int_equal(1, jsonctx->value_len);
+ assert_int_equal(0, jsonctx->dynamic);
+ assert_string_equal("null},\"x\"]", jsonctx->in->current);
+ assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL));
+ assert_int_equal(LYJSON_NULL, lyjson_ctx_status(jsonctx, 0));
+ assert_string_equal("},\"x\"]", jsonctx->in->current);
+ assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL));
+ assert_int_equal(LYJSON_OBJECT_CLOSED, lyjson_ctx_status(jsonctx, 0));
+ assert_string_equal(",\"x\"]", jsonctx->in->current);
+ assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL));
+ assert_int_equal(LYJSON_STRING, lyjson_ctx_status(jsonctx, 0));
+ assert_string_equal("x\"]", jsonctx->value);
+ assert_int_equal(1, jsonctx->value_len);
+ assert_int_equal(0, jsonctx->dynamic);
+ assert_string_equal("]", jsonctx->in->current);
+ assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL));
+ assert_int_equal(LYJSON_ARRAY_CLOSED, lyjson_ctx_status(jsonctx, 0));
+ assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL));
+ assert_int_equal(LYJSON_END, lyjson_ctx_status(jsonctx, 0));
+ lyjson_ctx_free(jsonctx);
+
+ /* new line is allowed only as escaped character in JSON */
+ str = "[ , null]";
+ assert_non_null(ly_in_memory(in, str));
+ assert_int_equal(LY_SUCCESS, lyjson_ctx_new(*state, in, &jsonctx));
+ assert_int_equal(LY_EVALID, lyjson_ctx_next(jsonctx, NULL));
+ logbuf_assert("Invalid character sequence \", null]\", expected a JSON value. Line number 1.");
+ lyjson_ctx_free(jsonctx);
+
+ ly_in_free(in, 0);
+ testfunc = NULL;
+}
+
+int main(void)
+{
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(test_general, logger_setup, logger_teardown),
+ cmocka_unit_test_setup_teardown(test_number, logger_setup, logger_teardown),
+ cmocka_unit_test_setup_teardown(test_string, logger_setup, logger_teardown),
+ cmocka_unit_test_setup_teardown(test_object, logger_setup, logger_teardown),
+ cmocka_unit_test_setup_teardown(test_array, logger_setup, logger_teardown),
+ };
+
+ return cmocka_run_group_tests(tests, setup, teardown);
+}