| /* This Source Code Form is subject to the terms of the Mozilla Public |
| * License, v. 2.0. If a copy of the MPL was not distributed with this |
| * file, You can obtain one at http://mozilla.org/MPL/2.0/. |
| * |
| * Copyright 2014-2018 (c) Fraunhofer IOSB (Author: Julius Pfrommer) |
| * Copyright 2018 (c) Fraunhofer IOSB (Author: Lukas Meling) |
| */ |
| |
| #include "ua_types_encoding_json.h" |
| |
| #include <open62541/types_generated.h> |
| #include <open62541/types_generated_handling.h> |
| |
| #include "ua_types_encoding_binary.h" |
| |
| #include <float.h> |
| #include <math.h> |
| |
| #ifdef UA_ENABLE_CUSTOM_LIBC |
| #include "../deps/musl/floatscan.h" |
| #include "../deps/musl/vfprintf.h" |
| #endif |
| |
| #include "../deps/itoa.h" |
| #include "../deps/atoi.h" |
| #include "../deps/string_escape.h" |
| #include "../deps/base64.h" |
| |
| #include "../deps/libc_time.h" |
| |
| #if defined(_MSC_VER) |
| # define strtoll _strtoi64 |
| # define strtoull _strtoui64 |
| #endif |
| |
| /* vs2008 does not have INFINITY and NAN defined */ |
| #ifndef INFINITY |
| # define INFINITY ((UA_Double)(DBL_MAX+DBL_MAX)) |
| #endif |
| #ifndef NAN |
| # define NAN ((UA_Double)(INFINITY-INFINITY)) |
| #endif |
| |
| #if defined(_MSC_VER) |
| # pragma warning(disable: 4756) |
| # pragma warning(disable: 4056) |
| #endif |
| |
| #define UA_NODEIDTYPE_NUMERIC_TWOBYTE 0 |
| #define UA_NODEIDTYPE_NUMERIC_FOURBYTE 1 |
| #define UA_NODEIDTYPE_NUMERIC_COMPLETE 2 |
| |
| #define UA_EXPANDEDNODEID_SERVERINDEX_FLAG 0x40 |
| #define UA_EXPANDEDNODEID_NAMESPACEURI_FLAG 0x80 |
| |
| #define UA_JSON_DATETIME_LENGTH 30 |
| |
| /* Max length of numbers for the allocation of temp buffers. Don't forget that |
| * printf adds an additional \0 at the end! |
| * |
| * Sources: |
| * https://www.exploringbinary.com/maximum-number-of-decimal-digits-in-binary-floating-point-numbers/ |
| * |
| * UInt16: 3 + 1 |
| * SByte: 3 + 1 |
| * UInt32: |
| * Int32: |
| * UInt64: |
| * Int64: |
| * Float: 149 + 1 |
| * Double: 767 + 1 |
| */ |
| |
| /************/ |
| /* Encoding */ |
| /************/ |
| |
| #define ENCODE_JSON(TYPE) static status \ |
| TYPE##_encodeJson(const UA_##TYPE *src, const UA_DataType *type, CtxJson *ctx) |
| |
| #define ENCODE_DIRECT_JSON(SRC, TYPE) \ |
| TYPE##_encodeJson((const UA_##TYPE*)SRC, NULL, ctx) |
| |
| extern const encodeJsonSignature encodeJsonJumpTable[UA_DATATYPEKINDS]; |
| extern const decodeJsonSignature decodeJsonJumpTable[UA_DATATYPEKINDS]; |
| |
| /* Forward declarations */ |
| UA_String UA_DateTime_toJSON(UA_DateTime t); |
| ENCODE_JSON(ByteString); |
| |
| static status UA_FUNC_ATTR_WARN_UNUSED_RESULT |
| writeChar(CtxJson *ctx, char c) { |
| if(ctx->pos >= ctx->end) |
| return UA_STATUSCODE_BADENCODINGLIMITSEXCEEDED; |
| if(!ctx->calcOnly) |
| *ctx->pos = (UA_Byte)c; |
| ctx->pos++; |
| return UA_STATUSCODE_GOOD; |
| } |
| |
| #define WRITE_JSON_ELEMENT(ELEM) \ |
| UA_FUNC_ATTR_WARN_UNUSED_RESULT status \ |
| writeJson##ELEM(CtxJson *ctx) |
| |
| static WRITE_JSON_ELEMENT(Quote) { |
| return writeChar(ctx, '\"'); |
| } |
| |
| WRITE_JSON_ELEMENT(ObjStart) { |
| /* increase depth, save: before first key-value no comma needed. */ |
| ctx->depth++; |
| ctx->commaNeeded[ctx->depth] = false; |
| return writeChar(ctx, '{'); |
| } |
| |
| WRITE_JSON_ELEMENT(ObjEnd) { |
| ctx->depth--; //decrease depth |
| ctx->commaNeeded[ctx->depth] = true; |
| return writeChar(ctx, '}'); |
| } |
| |
| WRITE_JSON_ELEMENT(ArrStart) { |
| /* increase depth, save: before first array entry no comma needed. */ |
| ctx->commaNeeded[++ctx->depth] = false; |
| return writeChar(ctx, '['); |
| } |
| |
| WRITE_JSON_ELEMENT(ArrEnd) { |
| ctx->depth--; //decrease depth |
| ctx->commaNeeded[ctx->depth] = true; |
| return writeChar(ctx, ']'); |
| } |
| |
| WRITE_JSON_ELEMENT(CommaIfNeeded) { |
| if(ctx->commaNeeded[ctx->depth]) |
| return writeChar(ctx, ','); |
| return UA_STATUSCODE_GOOD; |
| } |
| |
| status |
| writeJsonArrElm(CtxJson *ctx, const void *value, |
| const UA_DataType *type) { |
| status ret = writeJsonCommaIfNeeded(ctx); |
| ctx->commaNeeded[ctx->depth] = true; |
| ret |= encodeJsonInternal(value, type, ctx); |
| return ret; |
| } |
| |
| status writeJsonObjElm(CtxJson *ctx, const char *key, |
| const void *value, const UA_DataType *type){ |
| return writeJsonKey(ctx, key) | encodeJsonInternal(value, type, ctx); |
| } |
| |
| status writeJsonNull(CtxJson *ctx) { |
| if(ctx->pos + 4 > ctx->end) |
| return UA_STATUSCODE_BADENCODINGLIMITSEXCEEDED; |
| if(ctx->calcOnly) { |
| ctx->pos += 4; |
| } else { |
| *(ctx->pos++) = 'n'; |
| *(ctx->pos++) = 'u'; |
| *(ctx->pos++) = 'l'; |
| *(ctx->pos++) = 'l'; |
| } |
| return UA_STATUSCODE_GOOD; |
| } |
| |
| /* Keys for JSON */ |
| |
| /* LocalizedText */ |
| static const char* UA_JSONKEY_LOCALE = "Locale"; |
| static const char* UA_JSONKEY_TEXT = "Text"; |
| |
| /* QualifiedName */ |
| static const char* UA_JSONKEY_NAME = "Name"; |
| static const char* UA_JSONKEY_URI = "Uri"; |
| |
| /* NodeId */ |
| static const char* UA_JSONKEY_ID = "Id"; |
| static const char* UA_JSONKEY_IDTYPE = "IdType"; |
| static const char* UA_JSONKEY_NAMESPACE = "Namespace"; |
| |
| /* ExpandedNodeId */ |
| static const char* UA_JSONKEY_SERVERURI = "ServerUri"; |
| |
| /* Variant */ |
| static const char* UA_JSONKEY_TYPE = "Type"; |
| static const char* UA_JSONKEY_BODY = "Body"; |
| static const char* UA_JSONKEY_DIMENSION = "Dimension"; |
| |
| /* DataValue */ |
| static const char* UA_JSONKEY_VALUE = "Value"; |
| static const char* UA_JSONKEY_STATUS = "Status"; |
| static const char* UA_JSONKEY_SOURCETIMESTAMP = "SourceTimestamp"; |
| static const char* UA_JSONKEY_SOURCEPICOSECONDS = "SourcePicoseconds"; |
| static const char* UA_JSONKEY_SERVERTIMESTAMP = "ServerTimestamp"; |
| static const char* UA_JSONKEY_SERVERPICOSECONDS = "ServerPicoseconds"; |
| |
| /* ExtensionObject */ |
| static const char* UA_JSONKEY_ENCODING = "Encoding"; |
| static const char* UA_JSONKEY_TYPEID = "TypeId"; |
| |
| /* StatusCode */ |
| static const char* UA_JSONKEY_CODE = "Code"; |
| static const char* UA_JSONKEY_SYMBOL = "Symbol"; |
| |
| /* DiagnosticInfo */ |
| static const char* UA_JSONKEY_SYMBOLICID = "SymbolicId"; |
| static const char* UA_JSONKEY_NAMESPACEURI = "NamespaceUri"; |
| static const char* UA_JSONKEY_LOCALIZEDTEXT = "LocalizedText"; |
| static const char* UA_JSONKEY_ADDITIONALINFO = "AdditionalInfo"; |
| static const char* UA_JSONKEY_INNERSTATUSCODE = "InnerStatusCode"; |
| static const char* UA_JSONKEY_INNERDIAGNOSTICINFO = "InnerDiagnosticInfo"; |
| |
| /* Writes null terminated string to output buffer (current ctx->pos). Writes |
| * comma in front of key if needed. Encapsulates key in quotes. */ |
| status UA_FUNC_ATTR_WARN_UNUSED_RESULT |
| writeJsonKey(CtxJson *ctx, const char* key) { |
| size_t size = strlen(key); |
| if(ctx->pos + size + 4 > ctx->end) /* +4 because of " " : and , */ |
| return UA_STATUSCODE_BADENCODINGLIMITSEXCEEDED; |
| status ret = writeJsonCommaIfNeeded(ctx); |
| ctx->commaNeeded[ctx->depth] = true; |
| if(ctx->calcOnly) { |
| ctx->commaNeeded[ctx->depth] = true; |
| ctx->pos += 3; |
| ctx->pos += size; |
| return ret; |
| } |
| |
| ret |= writeChar(ctx, '\"'); |
| for(size_t i = 0; i < size; i++) { |
| *(ctx->pos++) = (u8)key[i]; |
| } |
| ret |= writeChar(ctx, '\"'); |
| ret |= writeChar(ctx, ':'); |
| return ret; |
| } |
| |
| /* Boolean */ |
| ENCODE_JSON(Boolean) { |
| size_t sizeOfJSONBool; |
| if(*src == true) { |
| sizeOfJSONBool = 4; /*"true"*/ |
| } else { |
| sizeOfJSONBool = 5; /*"false"*/ |
| } |
| |
| if(ctx->calcOnly) { |
| ctx->pos += sizeOfJSONBool; |
| return UA_STATUSCODE_GOOD; |
| } |
| |
| if(ctx->pos + sizeOfJSONBool > ctx->end) |
| return UA_STATUSCODE_BADENCODINGLIMITSEXCEEDED; |
| |
| if(*src) { |
| *(ctx->pos++) = 't'; |
| *(ctx->pos++) = 'r'; |
| *(ctx->pos++) = 'u'; |
| *(ctx->pos++) = 'e'; |
| } else { |
| *(ctx->pos++) = 'f'; |
| *(ctx->pos++) = 'a'; |
| *(ctx->pos++) = 'l'; |
| *(ctx->pos++) = 's'; |
| *(ctx->pos++) = 'e'; |
| } |
| return UA_STATUSCODE_GOOD; |
| } |
| |
| /*****************/ |
| /* Integer Types */ |
| /*****************/ |
| |
| /* Byte */ |
| ENCODE_JSON(Byte) { |
| char buf[4]; |
| UA_UInt16 digits = itoaUnsigned(*src, buf, 10); |
| |
| /* Ensure destination can hold the data- */ |
| if(ctx->pos + digits > ctx->end) |
| return UA_STATUSCODE_BADENCODINGLIMITSEXCEEDED; |
| |
| /* Copy digits to the output string/buffer. */ |
| if(!ctx->calcOnly) |
| memcpy(ctx->pos, buf, digits); |
| ctx->pos += digits; |
| return UA_STATUSCODE_GOOD; |
| } |
| |
| /* signed Byte */ |
| ENCODE_JSON(SByte) { |
| char buf[5]; |
| UA_UInt16 digits = itoaSigned(*src, buf); |
| if(ctx->pos + digits > ctx->end) |
| return UA_STATUSCODE_BADENCODINGLIMITSEXCEEDED; |
| if(!ctx->calcOnly) |
| memcpy(ctx->pos, buf, digits); |
| ctx->pos += digits; |
| return UA_STATUSCODE_GOOD; |
| } |
| |
| /* UInt16 */ |
| ENCODE_JSON(UInt16) { |
| char buf[6]; |
| UA_UInt16 digits = itoaUnsigned(*src, buf, 10); |
| |
| if(ctx->pos + digits > ctx->end) |
| return UA_STATUSCODE_BADENCODINGLIMITSEXCEEDED; |
| |
| if(!ctx->calcOnly) |
| memcpy(ctx->pos, buf, digits); |
| ctx->pos += digits; |
| return UA_STATUSCODE_GOOD; |
| } |
| |
| /* Int16 */ |
| ENCODE_JSON(Int16) { |
| char buf[7]; |
| UA_UInt16 digits = itoaSigned(*src, buf); |
| |
| if(ctx->pos + digits > ctx->end) |
| return UA_STATUSCODE_BADENCODINGLIMITSEXCEEDED; |
| |
| if(!ctx->calcOnly) |
| memcpy(ctx->pos, buf, digits); |
| ctx->pos += digits; |
| return UA_STATUSCODE_GOOD; |
| } |
| |
| /* UInt32 */ |
| ENCODE_JSON(UInt32) { |
| char buf[11]; |
| UA_UInt16 digits = itoaUnsigned(*src, buf, 10); |
| |
| if(ctx->pos + digits > ctx->end) |
| return UA_STATUSCODE_BADENCODINGLIMITSEXCEEDED; |
| |
| if(!ctx->calcOnly) |
| memcpy(ctx->pos, buf, digits); |
| ctx->pos += digits; |
| return UA_STATUSCODE_GOOD; |
| } |
| |
| /* Int32 */ |
| ENCODE_JSON(Int32) { |
| char buf[12]; |
| UA_UInt16 digits = itoaSigned(*src, buf); |
| |
| if(ctx->pos + digits > ctx->end) |
| return UA_STATUSCODE_BADENCODINGLIMITSEXCEEDED; |
| |
| if(!ctx->calcOnly) |
| memcpy(ctx->pos, buf, digits); |
| ctx->pos += digits; |
| return UA_STATUSCODE_GOOD; |
| } |
| |
| /* UInt64 */ |
| ENCODE_JSON(UInt64) { |
| char buf[23]; |
| buf[0] = '\"'; |
| UA_UInt16 digits = itoaUnsigned(*src, buf + 1, 10); |
| buf[digits + 1] = '\"'; |
| UA_UInt16 length = (UA_UInt16)(digits + 2); |
| |
| if(ctx->pos + length > ctx->end) |
| return UA_STATUSCODE_BADENCODINGLIMITSEXCEEDED; |
| |
| if(!ctx->calcOnly) |
| memcpy(ctx->pos, buf, length); |
| |
| ctx->pos += length; |
| return UA_STATUSCODE_GOOD; |
| } |
| |
| /* Int64 */ |
| ENCODE_JSON(Int64) { |
| char buf[23]; |
| buf[0] = '\"'; |
| UA_UInt16 digits = itoaSigned(*src, buf + 1); |
| buf[digits + 1] = '\"'; |
| UA_UInt16 length = (UA_UInt16)(digits + 2); |
| |
| if(ctx->pos + length > ctx->end) |
| return UA_STATUSCODE_BADENCODINGLIMITSEXCEEDED; |
| |
| if(!ctx->calcOnly) |
| memcpy(ctx->pos, buf, length); |
| ctx->pos += length; |
| return UA_STATUSCODE_GOOD; |
| } |
| |
| /************************/ |
| /* Floating Point Types */ |
| /************************/ |
| |
| /* Convert special numbers to string |
| * - fmt_fp gives NAN, nan,-NAN, -nan, inf, INF, -inf, -INF |
| * - Special floating-point numbers such as positive infinity (INF), negative |
| * infinity (-INF) and not-a-number (NaN) shall be represented by the values |
| * “Infinity”, “-Infinity” and “NaN” encoded as a JSON string. */ |
| static status |
| checkAndEncodeSpecialFloatingPoint(char *buffer, size_t *len) { |
| /*nan and NaN*/ |
| if(*len == 3 && |
| (buffer[0] == 'n' || buffer[0] == 'N') && |
| (buffer[1] == 'a' || buffer[1] == 'A') && |
| (buffer[2] == 'n' || buffer[2] == 'N')) { |
| *len = 5; |
| memcpy(buffer, "\"NaN\"", *len); |
| return UA_STATUSCODE_GOOD; |
| } |
| |
| /*-nan and -NaN*/ |
| if(*len == 4 && buffer[0] == '-' && |
| (buffer[1] == 'n' || buffer[1] == 'N') && |
| (buffer[2] == 'a' || buffer[2] == 'A') && |
| (buffer[3] == 'n' || buffer[3] == 'N')) { |
| *len = 6; |
| memcpy(buffer, "\"-NaN\"", *len); |
| return UA_STATUSCODE_GOOD; |
| } |
| |
| /*inf*/ |
| if(*len == 3 && |
| (buffer[0] == 'i' || buffer[0] == 'I') && |
| (buffer[1] == 'n' || buffer[1] == 'N') && |
| (buffer[2] == 'f' || buffer[2] == 'F')) { |
| *len = 10; |
| memcpy(buffer, "\"Infinity\"", *len); |
| return UA_STATUSCODE_GOOD; |
| } |
| |
| /*-inf*/ |
| if(*len == 4 && buffer[0] == '-' && |
| (buffer[1] == 'i' || buffer[1] == 'I') && |
| (buffer[2] == 'n' || buffer[2] == 'N') && |
| (buffer[3] == 'f' || buffer[3] == 'F')) { |
| *len = 11; |
| memcpy(buffer, "\"-Infinity\"", *len); |
| return UA_STATUSCODE_GOOD; |
| } |
| return UA_STATUSCODE_GOOD; |
| } |
| |
| ENCODE_JSON(Float) { |
| char buffer[200]; |
| if(*src == *src) { |
| #ifdef UA_ENABLE_CUSTOM_LIBC |
| fmt_fp(buffer, *src, 0, -1, 0, 'g'); |
| #else |
| UA_snprintf(buffer, 200, "%.149g", (UA_Double)*src); |
| #endif |
| } else { |
| strcpy(buffer, "NaN"); |
| } |
| |
| size_t len = strlen(buffer); |
| if(len == 0) |
| return UA_STATUSCODE_BADENCODINGERROR; |
| |
| checkAndEncodeSpecialFloatingPoint(buffer, &len); |
| |
| if(ctx->pos + len > ctx->end) |
| return UA_STATUSCODE_BADENCODINGLIMITSEXCEEDED; |
| |
| if(!ctx->calcOnly) |
| memcpy(ctx->pos, buffer, len); |
| |
| ctx->pos += len; |
| return UA_STATUSCODE_GOOD; |
| } |
| |
| ENCODE_JSON(Double) { |
| char buffer[2000]; |
| if(*src == *src) { |
| #ifdef UA_ENABLE_CUSTOM_LIBC |
| fmt_fp(buffer, *src, 0, 17, 0, 'g'); |
| #else |
| UA_snprintf(buffer, 2000, "%.1074g", *src); |
| #endif |
| } else { |
| strcpy(buffer, "NaN"); |
| } |
| |
| size_t len = strlen(buffer); |
| checkAndEncodeSpecialFloatingPoint(buffer, &len); |
| |
| if(ctx->pos + len > ctx->end) |
| return UA_STATUSCODE_BADENCODINGLIMITSEXCEEDED; |
| |
| if(!ctx->calcOnly) |
| memcpy(ctx->pos, buffer, len); |
| |
| ctx->pos += len; |
| return UA_STATUSCODE_GOOD; |
| } |
| |
| static status |
| encodeJsonArray(CtxJson *ctx, const void *ptr, size_t length, |
| const UA_DataType *type) { |
| encodeJsonSignature encodeType = encodeJsonJumpTable[type->typeKind]; |
| status ret = writeJsonArrStart(ctx); |
| uintptr_t uptr = (uintptr_t)ptr; |
| for(size_t i = 0; i < length && ret == UA_STATUSCODE_GOOD; ++i) { |
| ret |= writeJsonCommaIfNeeded(ctx); |
| ret |= encodeType((const void*)uptr, type, ctx); |
| ctx->commaNeeded[ctx->depth] = true; |
| uptr += type->memSize; |
| } |
| ret |= writeJsonArrEnd(ctx); |
| return ret; |
| } |
| |
| /*****************/ |
| /* Builtin Types */ |
| /*****************/ |
| |
| static const u8 hexmapLower[16] = |
| {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; |
| static const u8 hexmapUpper[16] = |
| {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; |
| |
| ENCODE_JSON(String) { |
| if(!src->data) |
| return writeJsonNull(ctx); |
| |
| if(src->length == 0) { |
| status retval = writeJsonQuote(ctx); |
| retval |= writeJsonQuote(ctx); |
| return retval; |
| } |
| |
| UA_StatusCode ret = writeJsonQuote(ctx); |
| |
| /* Escaping adapted from https://github.com/akheron/jansson dump.c */ |
| |
| const char *str = (char*)src->data; |
| const char *pos = str; |
| const char *end = str; |
| const char *lim = str + src->length; |
| UA_UInt32 codepoint = 0; |
| while(1) { |
| const char *text; |
| u8 seq[13]; |
| size_t length; |
| |
| while(end < lim) { |
| end = utf8_iterate(pos, (size_t)(lim - pos), (int32_t *)&codepoint); |
| if(!end) |
| return UA_STATUSCODE_BADENCODINGERROR; |
| |
| /* mandatory escape or control char */ |
| if(codepoint == '\\' || codepoint == '"' || codepoint < 0x20) |
| break; |
| |
| /* TODO: Why is this commented? */ |
| /* slash |
| if((flags & JSON_ESCAPE_SLASH) && codepoint == '/') |
| break;*/ |
| |
| /* non-ASCII |
| if((flags & JSON_ENSURE_ASCII) && codepoint > 0x7F) |
| break;*/ |
| |
| pos = end; |
| } |
| |
| if(pos != str) { |
| if(ctx->pos + (pos - str) > ctx->end) |
| return UA_STATUSCODE_BADENCODINGLIMITSEXCEEDED; |
| if(!ctx->calcOnly) |
| memcpy(ctx->pos, str, (size_t)(pos - str)); |
| ctx->pos += pos - str; |
| } |
| |
| if(end == pos) |
| break; |
| |
| /* handle \, /, ", and control codes */ |
| length = 2; |
| switch(codepoint) { |
| case '\\': text = "\\\\"; break; |
| case '\"': text = "\\\""; break; |
| case '\b': text = "\\b"; break; |
| case '\f': text = "\\f"; break; |
| case '\n': text = "\\n"; break; |
| case '\r': text = "\\r"; break; |
| case '\t': text = "\\t"; break; |
| case '/': text = "\\/"; break; |
| default: |
| if(codepoint < 0x10000) { |
| /* codepoint is in BMP */ |
| seq[0] = '\\'; |
| seq[1] = 'u'; |
| UA_Byte b1 = (UA_Byte)(codepoint >> 8u); |
| UA_Byte b2 = (UA_Byte)(codepoint >> 0u); |
| seq[2] = hexmapLower[(b1 & 0xF0u) >> 4u]; |
| seq[3] = hexmapLower[b1 & 0x0Fu]; |
| seq[4] = hexmapLower[(b2 & 0xF0u) >> 4u]; |
| seq[5] = hexmapLower[b2 & 0x0Fu]; |
| length = 6; |
| } else { |
| /* not in BMP -> construct a UTF-16 surrogate pair */ |
| codepoint -= 0x10000; |
| UA_UInt32 first = 0xD800u | ((codepoint & 0xffc00u) >> 10u); |
| UA_UInt32 last = 0xDC00u | (codepoint & 0x003ffu); |
| |
| UA_Byte fb1 = (UA_Byte)(first >> 8u); |
| UA_Byte fb2 = (UA_Byte)(first >> 0u); |
| |
| UA_Byte lb1 = (UA_Byte)(last >> 8u); |
| UA_Byte lb2 = (UA_Byte)(last >> 0u); |
| |
| seq[0] = '\\'; |
| seq[1] = 'u'; |
| seq[2] = hexmapLower[(fb1 & 0xF0u) >> 4u]; |
| seq[3] = hexmapLower[fb1 & 0x0Fu]; |
| seq[4] = hexmapLower[(fb2 & 0xF0u) >> 4u]; |
| seq[5] = hexmapLower[fb2 & 0x0Fu]; |
| |
| seq[6] = '\\'; |
| seq[7] = 'u'; |
| seq[8] = hexmapLower[(lb1 & 0xF0u) >> 4u]; |
| seq[9] = hexmapLower[lb1 & 0x0Fu]; |
| seq[10] = hexmapLower[(lb2 & 0xF0u) >> 4u]; |
| seq[11] = hexmapLower[lb2 & 0x0Fu]; |
| length = 12; |
| } |
| text = (char*)seq; |
| break; |
| } |
| |
| if(ctx->pos + length > ctx->end) |
| return UA_STATUSCODE_BADENCODINGLIMITSEXCEEDED; |
| if(!ctx->calcOnly) |
| memcpy(ctx->pos, text, length); |
| ctx->pos += length; |
| str = pos = end; |
| } |
| |
| ret |= writeJsonQuote(ctx); |
| return ret; |
| } |
| |
| ENCODE_JSON(ByteString) { |
| if(!src->data) |
| return writeJsonNull(ctx); |
| |
| if(src->length == 0) { |
| status retval = writeJsonQuote(ctx); |
| retval |= writeJsonQuote(ctx); |
| return retval; |
| } |
| |
| status ret = writeJsonQuote(ctx); |
| size_t flen = 0; |
| unsigned char *ba64 = UA_base64(src->data, src->length, &flen); |
| |
| /* Not converted, no mem */ |
| if(!ba64) |
| return UA_STATUSCODE_BADENCODINGERROR; |
| |
| if(ctx->pos + flen > ctx->end) { |
| UA_free(ba64); |
| return UA_STATUSCODE_BADENCODINGLIMITSEXCEEDED; |
| } |
| |
| /* Copy flen bytes to output stream. */ |
| if(!ctx->calcOnly) |
| memcpy(ctx->pos, ba64, flen); |
| ctx->pos += flen; |
| |
| /* Base64 result no longer needed */ |
| UA_free(ba64); |
| |
| ret |= writeJsonQuote(ctx); |
| return ret; |
| } |
| |
| /* Converts Guid to a hexadecimal represenation */ |
| static void UA_Guid_to_hex(const UA_Guid *guid, u8* out) { |
| /* |
| 16 byte |
| +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ |
| | data1 |data2|data3| data4 | |
| +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ |
| |aa aa aa aa-bb bb-cc cc-dd dd-ee ee ee ee ee ee| |
| +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ |
| 36 character |
| */ |
| |
| #ifdef hexCharlowerCase |
| const u8 *hexmap = hexmapLower; |
| #else |
| const u8 *hexmap = hexmapUpper; |
| #endif |
| size_t i = 0, j = 28; |
| for(; i<8;i++,j-=4) /* pos 0-7, 4byte, (a) */ |
| out[i] = hexmap[(guid->data1 >> j) & 0x0Fu]; |
| out[i++] = '-'; /* pos 8 */ |
| for(j=12; i<13;i++,j-=4) /* pos 9-12, 2byte, (b) */ |
| out[i] = hexmap[(uint16_t)(guid->data2 >> j) & 0x0Fu]; |
| out[i++] = '-'; /* pos 13 */ |
| for(j=12; i<18;i++,j-=4) /* pos 14-17, 2byte (c) */ |
| out[i] = hexmap[(uint16_t)(guid->data3 >> j) & 0x0Fu]; |
| out[i++] = '-'; /* pos 18 */ |
| for(j=0;i<23;i+=2,j++) { /* pos 19-22, 2byte (d) */ |
| out[i] = hexmap[(guid->data4[j] & 0xF0u) >> 4u]; |
| out[i+1] = hexmap[guid->data4[j] & 0x0Fu]; |
| } |
| out[i++] = '-'; /* pos 23 */ |
| for(j=2; i<36;i+=2,j++) { /* pos 24-35, 6byte (e) */ |
| out[i] = hexmap[(guid->data4[j] & 0xF0u) >> 4u]; |
| out[i+1] = hexmap[guid->data4[j] & 0x0Fu]; |
| } |
| } |
| |
| /* Guid */ |
| ENCODE_JSON(Guid) { |
| if(ctx->pos + 38 > ctx->end) /* 36 + 2 (") */ |
| return UA_STATUSCODE_BADENCODINGLIMITSEXCEEDED; |
| status ret = writeJsonQuote(ctx); |
| u8 *buf = ctx->pos; |
| if(!ctx->calcOnly) |
| UA_Guid_to_hex(src, buf); |
| ctx->pos += 36; |
| ret |= writeJsonQuote(ctx); |
| return ret; |
| } |
| |
| static void |
| printNumber(u16 n, u8 *pos, size_t digits) { |
| for(size_t i = digits; i > 0; --i) { |
| pos[i - 1] = (u8) ((n % 10) + '0'); |
| n = n / 10; |
| } |
| } |
| |
| ENCODE_JSON(DateTime) { |
| UA_DateTimeStruct tSt = UA_DateTime_toStruct(*src); |
| |
| /* Format: yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSS'Z' is used. max 30 bytes.*/ |
| UA_Byte buffer[UA_JSON_DATETIME_LENGTH]; |
| |
| printNumber(tSt.year, &buffer[0], 4); |
| buffer[4] = '-'; |
| printNumber(tSt.month, &buffer[5], 2); |
| buffer[7] = '-'; |
| printNumber(tSt.day, &buffer[8], 2); |
| buffer[10] = 'T'; |
| printNumber(tSt.hour, &buffer[11], 2); |
| buffer[13] = ':'; |
| printNumber(tSt.min, &buffer[14], 2); |
| buffer[16] = ':'; |
| printNumber(tSt.sec, &buffer[17], 2); |
| buffer[19] = '.'; |
| printNumber(tSt.milliSec, &buffer[20], 3); |
| printNumber(tSt.microSec, &buffer[23], 3); |
| printNumber(tSt.nanoSec, &buffer[26], 3); |
| |
| size_t length = 28; |
| while (buffer[length] == '0') |
| length--; |
| if (length != 19) |
| length++; |
| |
| buffer[length] = 'Z'; |
| UA_String str = {length + 1, buffer}; |
| return ENCODE_DIRECT_JSON(&str, String); |
| } |
| |
| /* NodeId */ |
| static status |
| NodeId_encodeJsonInternal(UA_NodeId const *src, CtxJson *ctx) { |
| status ret = UA_STATUSCODE_GOOD; |
| switch (src->identifierType) { |
| case UA_NODEIDTYPE_NUMERIC: |
| ret |= writeJsonKey(ctx, UA_JSONKEY_ID); |
| ret |= ENCODE_DIRECT_JSON(&src->identifier.numeric, UInt32); |
| break; |
| case UA_NODEIDTYPE_STRING: |
| ret |= writeJsonKey(ctx, UA_JSONKEY_IDTYPE); |
| ret |= writeChar(ctx, '1'); |
| ret |= writeJsonKey(ctx, UA_JSONKEY_ID); |
| ret |= ENCODE_DIRECT_JSON(&src->identifier.string, String); |
| break; |
| case UA_NODEIDTYPE_GUID: |
| ret |= writeJsonKey(ctx, UA_JSONKEY_IDTYPE); |
| ret |= writeChar(ctx, '2'); |
| ret |= writeJsonKey(ctx, UA_JSONKEY_ID); /* Id */ |
| ret |= ENCODE_DIRECT_JSON(&src->identifier.guid, Guid); |
| break; |
| case UA_NODEIDTYPE_BYTESTRING: |
| ret |= writeJsonKey(ctx, UA_JSONKEY_IDTYPE); |
| ret |= writeChar(ctx, '3'); |
| ret |= writeJsonKey(ctx, UA_JSONKEY_ID); /* Id */ |
| ret |= ENCODE_DIRECT_JSON(&src->identifier.byteString, ByteString); |
| break; |
| default: |
| return UA_STATUSCODE_BADINTERNALERROR; |
| } |
| return ret; |
| } |
| |
| ENCODE_JSON(NodeId) { |
| UA_StatusCode ret = writeJsonObjStart(ctx); |
| ret |= NodeId_encodeJsonInternal(src, ctx); |
| if(ctx->useReversible) { |
| if(src->namespaceIndex > 0) { |
| ret |= writeJsonKey(ctx, UA_JSONKEY_NAMESPACE); |
| ret |= ENCODE_DIRECT_JSON(&src->namespaceIndex, UInt16); |
| } |
| } else { |
| /* For the non-reversible encoding, the field is the NamespaceUri |
| * associated with the NamespaceIndex, encoded as a JSON string. |
| * A NamespaceIndex of 1 is always encoded as a JSON number. */ |
| if(src->namespaceIndex == 1) { |
| ret |= writeJsonKey(ctx, UA_JSONKEY_NAMESPACE); |
| ret |= ENCODE_DIRECT_JSON(&src->namespaceIndex, UInt16); |
| } else { |
| ret |= writeJsonKey(ctx, UA_JSONKEY_NAMESPACE); |
| |
| /* Check if Namespace given and in range */ |
| if(src->namespaceIndex < ctx->namespacesSize && ctx->namespaces != NULL) { |
| UA_String namespaceEntry = ctx->namespaces[src->namespaceIndex]; |
| ret |= ENCODE_DIRECT_JSON(&namespaceEntry, String); |
| } else { |
| return UA_STATUSCODE_BADNOTFOUND; |
| } |
| } |
| } |
| |
| ret |= writeJsonObjEnd(ctx); |
| return ret; |
| } |
| |
| /* ExpandedNodeId */ |
| ENCODE_JSON(ExpandedNodeId) { |
| status ret = writeJsonObjStart(ctx); |
| /* Encode the NodeId */ |
| ret |= NodeId_encodeJsonInternal(&src->nodeId, ctx); |
| |
| if(ctx->useReversible) { |
| if(src->namespaceUri.data != NULL && src->namespaceUri.length != 0 && |
| (void*) src->namespaceUri.data > UA_EMPTY_ARRAY_SENTINEL) { |
| /* If the NamespaceUri is specified it is encoded as a JSON string in this field. */ |
| ret |= writeJsonKey(ctx, UA_JSONKEY_NAMESPACE); |
| ret |= ENCODE_DIRECT_JSON(&src->namespaceUri, String); |
| } else { |
| /* If the NamespaceUri is not specified, the NamespaceIndex is encoded with these rules: |
| * The field is encoded as a JSON number for the reversible encoding. |
| * The field is omitted if the NamespaceIndex equals 0. */ |
| if(src->nodeId.namespaceIndex > 0) { |
| ret |= writeJsonKey(ctx, UA_JSONKEY_NAMESPACE); |
| ret |= ENCODE_DIRECT_JSON(&src->nodeId.namespaceIndex, UInt16); |
| } |
| } |
| |
| /* Encode the serverIndex/Url |
| * This field is encoded as a JSON number for the reversible encoding. |
| * This field is omitted if the ServerIndex equals 0. */ |
| if(src->serverIndex > 0) { |
| ret |= writeJsonKey(ctx, UA_JSONKEY_SERVERURI); |
| ret |= ENCODE_DIRECT_JSON(&src->serverIndex, UInt32); |
| } |
| |
| ret |= writeJsonObjEnd(ctx); |
| return ret; |
| } |
| |
| |
| /* NON-Reversible Case */ |
| |
| /* If the NamespaceUri is not specified, the NamespaceIndex is encoded with these rules: |
| * For the non-reversible encoding the field is the NamespaceUri associated with the |
| * NamespaceIndex encoded as a JSON string. |
| * A NamespaceIndex of 1 is always encoded as a JSON number. */ |
| |
| if(src->namespaceUri.data != NULL && src->namespaceUri.length != 0) { |
| ret |= writeJsonKey(ctx, UA_JSONKEY_NAMESPACE); |
| ret |= ENCODE_DIRECT_JSON(&src->namespaceUri, String); |
| if(ret != UA_STATUSCODE_GOOD) |
| return ret; |
| } else { |
| if(src->nodeId.namespaceIndex == 1) { |
| ret |= writeJsonKey(ctx, UA_JSONKEY_NAMESPACE); |
| ret |= ENCODE_DIRECT_JSON(&src->nodeId.namespaceIndex, UInt16); |
| if(ret != UA_STATUSCODE_GOOD) |
| return ret; |
| } else { |
| ret |= writeJsonKey(ctx, UA_JSONKEY_NAMESPACE); |
| |
| /* Check if Namespace given and in range */ |
| if(src->nodeId.namespaceIndex < ctx->namespacesSize |
| && ctx->namespaces != NULL) { |
| |
| UA_String namespaceEntry = ctx->namespaces[src->nodeId.namespaceIndex]; |
| ret |= ENCODE_DIRECT_JSON(&namespaceEntry, String); |
| if(ret != UA_STATUSCODE_GOOD) |
| return ret; |
| } else { |
| return UA_STATUSCODE_BADNOTFOUND; |
| } |
| } |
| } |
| |
| /* For the non-reversible encoding, this field is the ServerUri associated |
| * with the ServerIndex portion of the ExpandedNodeId, encoded as a JSON |
| * string. */ |
| |
| /* Check if Namespace given and in range */ |
| if(src->serverIndex < ctx->serverUrisSize && ctx->serverUris != NULL) { |
| UA_String serverUriEntry = ctx->serverUris[src->serverIndex]; |
| ret |= writeJsonKey(ctx, UA_JSONKEY_SERVERURI); |
| ret |= ENCODE_DIRECT_JSON(&serverUriEntry, String); |
| } else { |
| return UA_STATUSCODE_BADNOTFOUND; |
| } |
| ret |= writeJsonObjEnd(ctx); |
| return ret; |
| } |
| |
| /* LocalizedText */ |
| ENCODE_JSON(LocalizedText) { |
| if(ctx->useReversible) { |
| status ret = writeJsonObjStart(ctx); |
| ret |= writeJsonKey(ctx, UA_JSONKEY_LOCALE); |
| ret |= ENCODE_DIRECT_JSON(&src->locale, String); |
| ret |= writeJsonKey(ctx, UA_JSONKEY_TEXT); |
| ret |= ENCODE_DIRECT_JSON(&src->text, String); |
| ret |= writeJsonObjEnd(ctx); |
| return ret; |
| } |
| |
| /* For the non-reversible form, LocalizedText value shall be encoded as a |
| * JSON string containing the Text component.*/ |
| return ENCODE_DIRECT_JSON(&src->text, String); |
| } |
| |
| ENCODE_JSON(QualifiedName) { |
| status ret = writeJsonObjStart(ctx); |
| ret |= writeJsonKey(ctx, UA_JSONKEY_NAME); |
| ret |= ENCODE_DIRECT_JSON(&src->name, String); |
| |
| if(ctx->useReversible) { |
| if(src->namespaceIndex != 0) { |
| ret |= writeJsonKey(ctx, UA_JSONKEY_URI); |
| ret |= ENCODE_DIRECT_JSON(&src->namespaceIndex, UInt16); |
| } |
| } else { |
| /* For the non-reversible form, the NamespaceUri associated with the |
| * NamespaceIndex portion of the QualifiedName is encoded as JSON string |
| * unless the NamespaceIndex is 1 or if NamespaceUri is unknown. In |
| * these cases, the NamespaceIndex is encoded as a JSON number. */ |
| if(src->namespaceIndex == 1) { |
| ret |= writeJsonKey(ctx, UA_JSONKEY_URI); |
| ret |= ENCODE_DIRECT_JSON(&src->namespaceIndex, UInt16); |
| } else { |
| ret |= writeJsonKey(ctx, UA_JSONKEY_URI); |
| |
| /* Check if Namespace given and in range */ |
| if(src->namespaceIndex < ctx->namespacesSize && ctx->namespaces != NULL) { |
| UA_String namespaceEntry = ctx->namespaces[src->namespaceIndex]; |
| ret |= ENCODE_DIRECT_JSON(&namespaceEntry, String); |
| } else { |
| /* If not encode as number */ |
| ret |= ENCODE_DIRECT_JSON(&src->namespaceIndex, UInt16); |
| } |
| } |
| } |
| |
| return ret | writeJsonObjEnd(ctx); |
| } |
| |
| ENCODE_JSON(StatusCode) { |
| if(!src) |
| return writeJsonNull(ctx); |
| |
| if(ctx->useReversible) |
| return ENCODE_DIRECT_JSON(src, UInt32); |
| |
| if(*src == UA_STATUSCODE_GOOD) |
| return writeJsonNull(ctx); |
| |
| status ret = UA_STATUSCODE_GOOD; |
| ret |= writeJsonObjStart(ctx); |
| ret |= writeJsonKey(ctx, UA_JSONKEY_CODE); |
| ret |= ENCODE_DIRECT_JSON(src, UInt32); |
| ret |= writeJsonKey(ctx, UA_JSONKEY_SYMBOL); |
| const char *codename = UA_StatusCode_name(*src); |
| UA_String statusDescription = UA_STRING((char*)(uintptr_t)codename); |
| ret |= ENCODE_DIRECT_JSON(&statusDescription, String); |
| ret |= writeJsonObjEnd(ctx); |
| return ret; |
| } |
| |
| /* ExtensionObject */ |
| ENCODE_JSON(ExtensionObject) { |
| u8 encoding = (u8) src->encoding; |
| if(encoding == UA_EXTENSIONOBJECT_ENCODED_NOBODY) |
| return writeJsonNull(ctx); |
| |
| status ret = UA_STATUSCODE_GOOD; |
| /* already encoded content.*/ |
| if(encoding <= UA_EXTENSIONOBJECT_ENCODED_XML) { |
| ret |= writeJsonObjStart(ctx); |
| if(ctx->useReversible) { |
| ret |= writeJsonKey(ctx, UA_JSONKEY_TYPEID); |
| ret |= ENCODE_DIRECT_JSON(&src->content.encoded.typeId, NodeId); |
| if(ret != UA_STATUSCODE_GOOD) |
| return ret; |
| } |
| |
| switch (src->encoding) { |
| case UA_EXTENSIONOBJECT_ENCODED_BYTESTRING: |
| { |
| if(ctx->useReversible) { |
| ret |= writeJsonKey(ctx, UA_JSONKEY_ENCODING); |
| ret |= writeChar(ctx, '1'); |
| } |
| ret |= writeJsonKey(ctx, UA_JSONKEY_BODY); |
| ret |= ENCODE_DIRECT_JSON(&src->content.encoded.body, String); |
| break; |
| } |
| case UA_EXTENSIONOBJECT_ENCODED_XML: |
| { |
| if(ctx->useReversible) { |
| ret |= writeJsonKey(ctx, UA_JSONKEY_ENCODING); |
| ret |= writeChar(ctx, '2'); |
| } |
| ret |= writeJsonKey(ctx, UA_JSONKEY_BODY); |
| ret |= ENCODE_DIRECT_JSON(&src->content.encoded.body, String); |
| break; |
| } |
| default: |
| ret = UA_STATUSCODE_BADINTERNALERROR; |
| } |
| |
| ret |= writeJsonObjEnd(ctx); |
| return ret; |
| } /* encoding <= UA_EXTENSIONOBJECT_ENCODED_XML */ |
| |
| /* Cannot encode with no type description */ |
| if(!src->content.decoded.type) |
| return UA_STATUSCODE_BADENCODINGERROR; |
| |
| if(!src->content.decoded.data) |
| return writeJsonNull(ctx); |
| |
| UA_NodeId typeId = src->content.decoded.type->typeId; |
| if(typeId.identifierType != UA_NODEIDTYPE_NUMERIC) |
| return UA_STATUSCODE_BADENCODINGERROR; |
| |
| ret |= writeJsonObjStart(ctx); |
| const UA_DataType *contentType = src->content.decoded.type; |
| if(ctx->useReversible) { |
| /* REVERSIBLE */ |
| ret |= writeJsonKey(ctx, UA_JSONKEY_TYPEID); |
| ret |= ENCODE_DIRECT_JSON(&typeId, NodeId); |
| |
| /* Encode the content */ |
| ret |= writeJsonKey(ctx, UA_JSONKEY_BODY); |
| ret |= encodeJsonInternal(src->content.decoded.data, contentType, ctx); |
| } else { |
| /* NON-REVERSIBLE |
| * For the non-reversible form, ExtensionObject values |
| * shall be encoded as a JSON object containing only the |
| * value of the Body field. The TypeId and Encoding fields are dropped. |
| * |
| * TODO: UA_JSONKEY_BODY key in the ExtensionObject? |
| */ |
| ret |= writeJsonKey(ctx, UA_JSONKEY_BODY); |
| ret |= encodeJsonInternal(src->content.decoded.data, contentType, ctx); |
| } |
| |
| ret |= writeJsonObjEnd(ctx); |
| return ret; |
| } |
| |
| static status |
| Variant_encodeJsonWrapExtensionObject(const UA_Variant *src, const bool isArray, CtxJson *ctx) { |
| size_t length = 1; |
| |
| status ret = UA_STATUSCODE_GOOD; |
| if(isArray) { |
| if(src->arrayLength > UA_INT32_MAX) |
| return UA_STATUSCODE_BADENCODINGERROR; |
| |
| length = src->arrayLength; |
| } |
| |
| /* Set up the ExtensionObject */ |
| UA_ExtensionObject eo; |
| UA_ExtensionObject_init(&eo); |
| eo.encoding = UA_EXTENSIONOBJECT_DECODED; |
| eo.content.decoded.type = src->type; |
| const u16 memSize = src->type->memSize; |
| uintptr_t ptr = (uintptr_t) src->data; |
| |
| if(isArray) { |
| ret |= writeJsonArrStart(ctx); |
| ctx->commaNeeded[ctx->depth] = false; |
| |
| /* Iterate over the array */ |
| for(size_t i = 0; i < length && ret == UA_STATUSCODE_GOOD; ++i) { |
| eo.content.decoded.data = (void*) ptr; |
| ret |= writeJsonArrElm(ctx, &eo, &UA_TYPES[UA_TYPES_EXTENSIONOBJECT]); |
| ptr += memSize; |
| } |
| |
| ret |= writeJsonArrEnd(ctx); |
| return ret; |
| } |
| |
| eo.content.decoded.data = (void*) ptr; |
| return encodeJsonInternal(&eo, &UA_TYPES[UA_TYPES_EXTENSIONOBJECT], ctx); |
| } |
| |
| static status |
| addMultiArrayContentJSON(CtxJson *ctx, void* array, const UA_DataType *type, |
| size_t *index, UA_UInt32 *arrayDimensions, size_t dimensionIndex, |
| size_t dimensionSize) { |
| /* Check the recursion limit */ |
| if(ctx->depth > UA_JSON_ENCODING_MAX_RECURSION) |
| return UA_STATUSCODE_BADENCODINGERROR; |
| |
| /* Stop recursion: The inner Arrays are written */ |
| status ret; |
| if(dimensionIndex == (dimensionSize - 1)) { |
| ret = encodeJsonArray(ctx, ((u8*)array) + (type->memSize * *index), |
| arrayDimensions[dimensionIndex], type); |
| (*index) += arrayDimensions[dimensionIndex]; |
| return ret; |
| } |
| |
| /* Recurse to the next dimension */ |
| ret = writeJsonArrStart(ctx); |
| for(size_t i = 0; i < arrayDimensions[dimensionIndex]; i++) { |
| ret |= writeJsonCommaIfNeeded(ctx); |
| ret |= addMultiArrayContentJSON(ctx, array, type, index, arrayDimensions, |
| dimensionIndex + 1, dimensionSize); |
| ctx->commaNeeded[ctx->depth] = true; |
| if(ret != UA_STATUSCODE_GOOD) |
| return ret; |
| } |
| ret |= writeJsonArrEnd(ctx); |
| return ret; |
| } |
| |
| ENCODE_JSON(Variant) { |
| /* If type is 0 (NULL) the Variant contains a NULL value and the containing |
| * JSON object shall be omitted or replaced by the JSON literal ‘null’ (when |
| * an element of a JSON array). */ |
| if(!src->type) { |
| return writeJsonNull(ctx); |
| } |
| |
| /* Set the content type in the encoding mask */ |
| const UA_Boolean isBuiltin = (src->type->typeKind <= UA_DATATYPEKIND_DIAGNOSTICINFO); |
| const UA_Boolean isEnum = (src->type->typeKind == UA_DATATYPEKIND_ENUM); |
| |
| /* Set the array type in the encoding mask */ |
| const bool isArray = src->arrayLength > 0 || src->data <= UA_EMPTY_ARRAY_SENTINEL; |
| const bool hasDimensions = isArray && src->arrayDimensionsSize > 0; |
| status ret = UA_STATUSCODE_GOOD; |
| |
| if(ctx->useReversible) { |
| ret |= writeJsonObjStart(ctx); |
| if(ret != UA_STATUSCODE_GOOD) |
| return ret; |
| |
| /* Encode the content */ |
| if(!isBuiltin && !isEnum) { |
| /* REVERSIBLE: NOT BUILTIN, can it be encoded? Wrap in extension object.*/ |
| ret |= writeJsonKey(ctx, UA_JSONKEY_TYPE); |
| ret |= ENCODE_DIRECT_JSON(&UA_TYPES[UA_TYPES_EXTENSIONOBJECT].typeId.identifier.numeric, UInt32); |
| ret |= writeJsonKey(ctx, UA_JSONKEY_BODY); |
| ret |= Variant_encodeJsonWrapExtensionObject(src, isArray, ctx); |
| } else if(!isArray) { |
| /*REVERSIBLE: BUILTIN, single value.*/ |
| ret |= writeJsonKey(ctx, UA_JSONKEY_TYPE); |
| ret |= ENCODE_DIRECT_JSON(&src->type->typeId.identifier.numeric, UInt32); |
| ret |= writeJsonKey(ctx, UA_JSONKEY_BODY); |
| ret |= encodeJsonInternal(src->data, src->type, ctx); |
| } else { |
| /*REVERSIBLE: BUILTIN, array.*/ |
| ret |= writeJsonKey(ctx, UA_JSONKEY_TYPE); |
| ret |= ENCODE_DIRECT_JSON(&src->type->typeId.identifier.numeric, UInt32); |
| ret |= writeJsonKey(ctx, UA_JSONKEY_BODY); |
| ret |= encodeJsonArray(ctx, src->data, src->arrayLength, src->type); |
| } |
| |
| if(ret != UA_STATUSCODE_GOOD) |
| return ret; |
| |
| /* REVERSIBLE: Encode the array dimensions */ |
| if(hasDimensions && ret == UA_STATUSCODE_GOOD) { |
| ret |= writeJsonKey(ctx, UA_JSONKEY_DIMENSION); |
| ret |= encodeJsonArray(ctx, src->arrayDimensions, src->arrayDimensionsSize, |
| &UA_TYPES[UA_TYPES_INT32]); |
| if(ret != UA_STATUSCODE_GOOD) |
| return ret; |
| } |
| |
| ret |= writeJsonObjEnd(ctx); |
| return ret; |
| } /* reversible */ |
| |
| |
| /* NON-REVERSIBLE |
| * For the non-reversible form, Variant values shall be encoded as a JSON object containing only |
| * the value of the Body field. The Type and Dimensions fields are dropped. Multi-dimensional |
| * arrays are encoded as a multi dimensional JSON array as described in 5.4.5. |
| */ |
| |
| ret |= writeJsonObjStart(ctx); |
| if(!isBuiltin && !isEnum) { |
| /*NON REVERSIBLE: NOT BUILTIN, can it be encoded? Wrap in extension object.*/ |
| if(src->arrayDimensionsSize > 1) { |
| return UA_STATUSCODE_BADNOTIMPLEMENTED; |
| } |
| |
| ret |= writeJsonKey(ctx, UA_JSONKEY_BODY); |
| ret |= Variant_encodeJsonWrapExtensionObject(src, isArray, ctx); |
| } else if(!isArray) { |
| /*NON REVERSIBLE: BUILTIN, single value.*/ |
| ret |= writeJsonKey(ctx, UA_JSONKEY_BODY); |
| ret |= encodeJsonInternal(src->data, src->type, ctx); |
| } else { |
| /*NON REVERSIBLE: BUILTIN, array.*/ |
| ret |= writeJsonKey(ctx, UA_JSONKEY_BODY); |
| |
| size_t dimensionSize = src->arrayDimensionsSize; |
| if(dimensionSize > 1) { |
| /*nonreversible multidimensional array*/ |
| size_t index = 0; size_t dimensionIndex = 0; |
| void *ptr = src->data; |
| const UA_DataType *arraytype = src->type; |
| ret |= addMultiArrayContentJSON(ctx, ptr, arraytype, &index, |
| src->arrayDimensions, dimensionIndex, dimensionSize); |
| } else { |
| /*nonreversible simple array*/ |
| ret |= encodeJsonArray(ctx, src->data, src->arrayLength, src->type); |
| } |
| } |
| |
| ret |= writeJsonObjEnd(ctx); |
| return ret; |
| } |
| |
| /* DataValue */ |
| ENCODE_JSON(DataValue) { |
| UA_Boolean hasValue = src->hasValue && src->value.type != NULL; |
| UA_Boolean hasStatus = src->hasStatus && src->status; |
| UA_Boolean hasSourceTimestamp = src->hasSourceTimestamp && src->sourceTimestamp; |
| UA_Boolean hasSourcePicoseconds = src->hasSourcePicoseconds && src->sourcePicoseconds; |
| UA_Boolean hasServerTimestamp = src->hasServerTimestamp && src->serverTimestamp; |
| UA_Boolean hasServerPicoseconds = src->hasServerPicoseconds && src->serverPicoseconds; |
| |
| if(!hasValue && !hasStatus && !hasSourceTimestamp && !hasSourcePicoseconds && |
| !hasServerTimestamp && !hasServerPicoseconds) { |
| return writeJsonNull(ctx); /*no element, encode as null*/ |
| } |
| |
| status ret = UA_STATUSCODE_GOOD; |
| ret |= writeJsonObjStart(ctx); |
| |
| if(hasValue) { |
| ret |= writeJsonKey(ctx, UA_JSONKEY_VALUE); |
| ret |= ENCODE_DIRECT_JSON(&src->value, Variant); |
| if(ret != UA_STATUSCODE_GOOD) |
| return ret; |
| } |
| |
| if(hasStatus) { |
| ret |= writeJsonKey(ctx, UA_JSONKEY_STATUS); |
| ret |= ENCODE_DIRECT_JSON(&src->status, StatusCode); |
| if(ret != UA_STATUSCODE_GOOD) |
| return ret; |
| } |
| |
| if(hasSourceTimestamp) { |
| ret |= writeJsonKey(ctx, UA_JSONKEY_SOURCETIMESTAMP); |
| ret |= ENCODE_DIRECT_JSON(&src->sourceTimestamp, DateTime); |
| if(ret != UA_STATUSCODE_GOOD) |
| return ret; |
| } |
| |
| if(hasSourcePicoseconds) { |
| ret |= writeJsonKey(ctx, UA_JSONKEY_SOURCEPICOSECONDS); |
| ret |= ENCODE_DIRECT_JSON(&src->sourcePicoseconds, UInt16); |
| if(ret != UA_STATUSCODE_GOOD) |
| return ret; |
| } |
| |
| if(hasServerTimestamp) { |
| ret |= writeJsonKey(ctx, UA_JSONKEY_SERVERTIMESTAMP); |
| ret |= ENCODE_DIRECT_JSON(&src->serverTimestamp, DateTime); |
| if(ret != UA_STATUSCODE_GOOD) |
| return ret; |
| } |
| |
| if(hasServerPicoseconds) { |
| ret |= writeJsonKey(ctx, UA_JSONKEY_SERVERPICOSECONDS); |
| ret |= ENCODE_DIRECT_JSON(&src->serverPicoseconds, UInt16); |
| if(ret != UA_STATUSCODE_GOOD) |
| return ret; |
| } |
| |
| ret |= writeJsonObjEnd(ctx); |
| return ret; |
| } |
| |
| /* DiagnosticInfo */ |
| ENCODE_JSON(DiagnosticInfo) { |
| status ret = UA_STATUSCODE_GOOD; |
| if(!src->hasSymbolicId && !src->hasNamespaceUri && !src->hasLocalizedText && |
| !src->hasLocale && !src->hasAdditionalInfo && !src->hasInnerDiagnosticInfo && |
| !src->hasInnerStatusCode) { |
| return writeJsonNull(ctx); /*no element present, encode as null.*/ |
| } |
| |
| ret |= writeJsonObjStart(ctx); |
| |
| if(src->hasSymbolicId) { |
| ret |= writeJsonKey(ctx, UA_JSONKEY_SYMBOLICID); |
| ret |= ENCODE_DIRECT_JSON(&src->symbolicId, UInt32); |
| if(ret != UA_STATUSCODE_GOOD) |
| return ret; |
| } |
| |
| if(src->hasNamespaceUri) { |
| ret |= writeJsonKey(ctx, UA_JSONKEY_NAMESPACEURI); |
| ret |= ENCODE_DIRECT_JSON(&src->namespaceUri, UInt32); |
| if(ret != UA_STATUSCODE_GOOD) |
| return ret; |
| } |
| |
| if(src->hasLocalizedText) { |
| ret |= writeJsonKey(ctx, UA_JSONKEY_LOCALIZEDTEXT); |
| ret |= ENCODE_DIRECT_JSON(&src->localizedText, UInt32); |
| if(ret != UA_STATUSCODE_GOOD) |
| return ret; |
| } |
| |
| if(src->hasLocale) { |
| ret |= writeJsonKey(ctx, UA_JSONKEY_LOCALE); |
| ret |= ENCODE_DIRECT_JSON(&src->locale, UInt32); |
| if(ret != UA_STATUSCODE_GOOD) |
| return ret; |
| } |
| |
| if(src->hasAdditionalInfo) { |
| ret |= writeJsonKey(ctx, UA_JSONKEY_ADDITIONALINFO); |
| ret |= ENCODE_DIRECT_JSON(&src->additionalInfo, String); |
| if(ret != UA_STATUSCODE_GOOD) |
| return ret; |
| } |
| |
| if(src->hasInnerStatusCode) { |
| ret |= writeJsonKey(ctx, UA_JSONKEY_INNERSTATUSCODE); |
| ret |= ENCODE_DIRECT_JSON(&src->innerStatusCode, StatusCode); |
| if(ret != UA_STATUSCODE_GOOD) |
| return ret; |
| } |
| |
| if(src->hasInnerDiagnosticInfo && src->innerDiagnosticInfo) { |
| ret |= writeJsonKey(ctx, UA_JSONKEY_INNERDIAGNOSTICINFO); |
| /* Check recursion depth in encodeJsonInternal */ |
| ret |= encodeJsonInternal(src->innerDiagnosticInfo, &UA_TYPES[UA_TYPES_DIAGNOSTICINFO], ctx); |
| if(ret != UA_STATUSCODE_GOOD) |
| return ret; |
| } |
| |
| ret |= writeJsonObjEnd(ctx); |
| return ret; |
| } |
| |
| static status |
| encodeJsonStructure(const void *src, const UA_DataType *type, CtxJson *ctx) { |
| /* Check the recursion limit */ |
| if(ctx->depth > UA_JSON_ENCODING_MAX_RECURSION) |
| return UA_STATUSCODE_BADENCODINGERROR; |
| ctx->depth++; |
| |
| status ret = writeJsonObjStart(ctx); |
| |
| uintptr_t ptr = (uintptr_t) src; |
| u8 membersSize = type->membersSize; |
| const UA_DataType * typelists[2] = {UA_TYPES, &type[-type->typeIndex]}; |
| for(size_t i = 0; i < membersSize && ret == UA_STATUSCODE_GOOD; ++i) { |
| const UA_DataTypeMember *m = &type->members[i]; |
| const UA_DataType *mt = &typelists[!m->namespaceZero][m->memberTypeIndex]; |
| |
| if(m->memberName != NULL && *m->memberName != 0) |
| ret |= writeJsonKey(ctx, m->memberName); |
| |
| if(!m->isArray) { |
| ptr += m->padding; |
| size_t memSize = mt->memSize; |
| ret |= encodeJsonJumpTable[mt->typeKind]((const void*) ptr, mt, ctx); |
| ptr += memSize; |
| } else { |
| ptr += m->padding; |
| const size_t length = *((const size_t*) ptr); |
| ptr += sizeof (size_t); |
| ret |= encodeJsonArray(ctx, *(void * const *)ptr, length, mt); |
| ptr += sizeof (void*); |
| } |
| } |
| |
| ret |= writeJsonObjEnd(ctx); |
| |
| ctx->depth--; |
| return ret; |
| } |
| |
| static status |
| encodeJsonNotImplemented(const void *src, const UA_DataType *type, CtxJson *ctx) { |
| (void) src, (void) type, (void)ctx; |
| return UA_STATUSCODE_BADNOTIMPLEMENTED; |
| } |
| |
| const encodeJsonSignature encodeJsonJumpTable[UA_DATATYPEKINDS] = { |
| (encodeJsonSignature)Boolean_encodeJson, |
| (encodeJsonSignature)SByte_encodeJson, /* SByte */ |
| (encodeJsonSignature)Byte_encodeJson, |
| (encodeJsonSignature)Int16_encodeJson, /* Int16 */ |
| (encodeJsonSignature)UInt16_encodeJson, |
| (encodeJsonSignature)Int32_encodeJson, /* Int32 */ |
| (encodeJsonSignature)UInt32_encodeJson, |
| (encodeJsonSignature)Int64_encodeJson, /* Int64 */ |
| (encodeJsonSignature)UInt64_encodeJson, |
| (encodeJsonSignature)Float_encodeJson, |
| (encodeJsonSignature)Double_encodeJson, |
| (encodeJsonSignature)String_encodeJson, |
| (encodeJsonSignature)DateTime_encodeJson, /* DateTime */ |
| (encodeJsonSignature)Guid_encodeJson, |
| (encodeJsonSignature)ByteString_encodeJson, /* ByteString */ |
| (encodeJsonSignature)String_encodeJson, /* XmlElement */ |
| (encodeJsonSignature)NodeId_encodeJson, |
| (encodeJsonSignature)ExpandedNodeId_encodeJson, |
| (encodeJsonSignature)StatusCode_encodeJson, /* StatusCode */ |
| (encodeJsonSignature)QualifiedName_encodeJson, /* QualifiedName */ |
| (encodeJsonSignature)LocalizedText_encodeJson, |
| (encodeJsonSignature)ExtensionObject_encodeJson, |
| (encodeJsonSignature)DataValue_encodeJson, |
| (encodeJsonSignature)Variant_encodeJson, |
| (encodeJsonSignature)DiagnosticInfo_encodeJson, |
| (encodeJsonSignature)encodeJsonNotImplemented, /* Decimal */ |
| (encodeJsonSignature)Int32_encodeJson, /* Enum */ |
| (encodeJsonSignature)encodeJsonStructure, |
| (encodeJsonSignature)encodeJsonNotImplemented, /* Structure with optional fields */ |
| (encodeJsonSignature)encodeJsonNotImplemented, /* Union */ |
| (encodeJsonSignature)encodeJsonNotImplemented /* BitfieldCluster */ |
| }; |
| |
| status |
| encodeJsonInternal(const void *src, const UA_DataType *type, CtxJson *ctx) { |
| return encodeJsonJumpTable[type->typeKind](src, type, ctx); |
| } |
| |
| status UA_FUNC_ATTR_WARN_UNUSED_RESULT |
| UA_encodeJson(const void *src, const UA_DataType *type, |
| u8 **bufPos, const u8 **bufEnd, UA_String *namespaces, |
| size_t namespaceSize, UA_String *serverUris, |
| size_t serverUriSize, UA_Boolean useReversible) { |
| if(!src || !type) |
| return UA_STATUSCODE_BADINTERNALERROR; |
| |
| /* Set up the context */ |
| CtxJson ctx; |
| memset(&ctx, 0, sizeof(ctx)); |
| ctx.pos = *bufPos; |
| ctx.end = *bufEnd; |
| ctx.depth = 0; |
| ctx.namespaces = namespaces; |
| ctx.namespacesSize = namespaceSize; |
| ctx.serverUris = serverUris; |
| ctx.serverUrisSize = serverUriSize; |
| ctx.useReversible = useReversible; |
| ctx.calcOnly = false; |
| |
| /* Encode */ |
| status ret = encodeJsonJumpTable[type->typeKind](src, type, &ctx); |
| |
| *bufPos = ctx.pos; |
| *bufEnd = ctx.end; |
| return ret; |
| } |
| |
| /************/ |
| /* CalcSize */ |
| /************/ |
| size_t |
| UA_calcSizeJson(const void *src, const UA_DataType *type, |
| UA_String *namespaces, size_t namespaceSize, |
| UA_String *serverUris, size_t serverUriSize, |
| UA_Boolean useReversible) { |
| if(!src || !type) |
| return UA_STATUSCODE_BADINTERNALERROR; |
| |
| /* Set up the context */ |
| CtxJson ctx; |
| memset(&ctx, 0, sizeof(ctx)); |
| ctx.pos = 0; |
| ctx.end = (const UA_Byte*)(uintptr_t)SIZE_MAX; |
| ctx.depth = 0; |
| ctx.namespaces = namespaces; |
| ctx.namespacesSize = namespaceSize; |
| ctx.serverUris = serverUris; |
| ctx.serverUrisSize = serverUriSize; |
| ctx.useReversible = useReversible; |
| ctx.calcOnly = true; |
| |
| /* Encode */ |
| status ret = encodeJsonJumpTable[type->typeKind](src, type, &ctx); |
| if(ret != UA_STATUSCODE_GOOD) |
| return 0; |
| return (size_t)ctx.pos; |
| } |
| |
| /**********/ |
| /* Decode */ |
| /**********/ |
| |
| /* Macro which gets current size and char pointer of current Token. Needs |
| * ParseCtx (parseCtx) and CtxJson (ctx). Does NOT increment index of Token. */ |
| #define GET_TOKEN(data, size) do { \ |
| (size) = (size_t)(parseCtx->tokenArray[parseCtx->index].end - parseCtx->tokenArray[parseCtx->index].start); \ |
| (data) = (char*)(ctx->pos + parseCtx->tokenArray[parseCtx->index].start); } while(0) |
| |
| #define ALLOW_NULL do { \ |
| if(isJsonNull(ctx, parseCtx)) { \ |
| parseCtx->index++; \ |
| return UA_STATUSCODE_GOOD; \ |
| }} while(0) |
| |
| #define CHECK_TOKEN_BOUNDS do { \ |
| if(parseCtx->index >= parseCtx->tokenCount) \ |
| return UA_STATUSCODE_BADDECODINGERROR; \ |
| } while(0) |
| |
| #define CHECK_PRIMITIVE do { \ |
| if(getJsmnType(parseCtx) != JSMN_PRIMITIVE) { \ |
| return UA_STATUSCODE_BADDECODINGERROR; \ |
| }} while(0) |
| |
| #define CHECK_STRING do { \ |
| if(getJsmnType(parseCtx) != JSMN_STRING) { \ |
| return UA_STATUSCODE_BADDECODINGERROR; \ |
| }} while(0) |
| |
| #define CHECK_OBJECT do { \ |
| if(getJsmnType(parseCtx) != JSMN_OBJECT) { \ |
| return UA_STATUSCODE_BADDECODINGERROR; \ |
| }} while(0) |
| |
| /* Forward declarations*/ |
| #define DECODE_JSON(TYPE) static status \ |
| TYPE##_decodeJson(UA_##TYPE *dst, const UA_DataType *type, \ |
| CtxJson *ctx, ParseCtx *parseCtx, UA_Boolean moveToken) |
| |
| /* decode without moving the token index */ |
| #define DECODE_DIRECT_JSON(DST, TYPE) TYPE##_decodeJson((UA_##TYPE*)DST, NULL, ctx, parseCtx, false) |
| |
| static status |
| Array_decodeJson(void *dst, const UA_DataType *type, CtxJson *ctx, |
| ParseCtx *parseCtx, UA_Boolean moveToken); |
| |
| static status |
| Array_decodeJson_internal(void **dst, const UA_DataType *type, |
| CtxJson *ctx, ParseCtx *parseCtx, UA_Boolean moveToken); |
| |
| static status |
| Variant_decodeJsonUnwrapExtensionObject(UA_Variant *dst, const UA_DataType *type, |
| CtxJson *ctx, ParseCtx *parseCtx, UA_Boolean moveToken); |
| |
| /* Json decode Helper */ |
| jsmntype_t |
| getJsmnType(const ParseCtx *parseCtx) { |
| if(parseCtx->index >= parseCtx->tokenCount) |
| return JSMN_UNDEFINED; |
| return parseCtx->tokenArray[parseCtx->index].type; |
| } |
| |
| static UA_Boolean |
| isJsonTokenNull(const CtxJson *ctx, jsmntok_t *token) { |
| if(token->type != JSMN_PRIMITIVE) |
| return false; |
| char* elem = (char*)(ctx->pos + token->start); |
| return (elem[0] == 'n' && elem[1] == 'u' && elem[2] == 'l' && elem[3] == 'l'); |
| } |
| |
| UA_Boolean |
| isJsonNull(const CtxJson *ctx, const ParseCtx *parseCtx) { |
| if(parseCtx->index >= parseCtx->tokenCount) |
| return false; |
| |
| if(parseCtx->tokenArray[parseCtx->index].type != JSMN_PRIMITIVE) { |
| return false; |
| } |
| char* elem = (char*)(ctx->pos + parseCtx->tokenArray[parseCtx->index].start); |
| return (elem[0] == 'n' && elem[1] == 'u' && elem[2] == 'l' && elem[3] == 'l'); |
| } |
| |
| static UA_SByte jsoneq(const char *json, jsmntok_t *tok, const char *searchKey) { |
| /* TODO: necessary? |
| if(json == NULL |
| || tok == NULL |
| || searchKey == NULL) { |
| return -1; |
| } */ |
| |
| if(tok->type == JSMN_STRING) { |
| if(strlen(searchKey) == (size_t)(tok->end - tok->start) ) { |
| if(strncmp(json + tok->start, |
| (const char*)searchKey, (size_t)(tok->end - tok->start)) == 0) { |
| return 0; |
| } |
| } |
| } |
| return -1; |
| } |
| |
| DECODE_JSON(Boolean) { |
| CHECK_PRIMITIVE; |
| CHECK_TOKEN_BOUNDS; |
| size_t tokenSize; |
| char* tokenData; |
| GET_TOKEN(tokenData, tokenSize); |
| |
| if(tokenSize == 4 && |
| tokenData[0] == 't' && tokenData[1] == 'r' && |
| tokenData[2] == 'u' && tokenData[3] == 'e') { |
| *dst = true; |
| } else if(tokenSize == 5 && |
| tokenData[0] == 'f' && tokenData[1] == 'a' && |
| tokenData[2] == 'l' && tokenData[3] == 's' && |
| tokenData[4] == 'e') { |
| *dst = false; |
| } else { |
| return UA_STATUSCODE_BADDECODINGERROR; |
| } |
| |
| if(moveToken) |
| parseCtx->index++; |
| return UA_STATUSCODE_GOOD; |
| } |
| |
| #ifdef UA_ENABLE_CUSTOM_LIBC |
| static UA_StatusCode |
| parseUnsignedInteger(char* inputBuffer, size_t sizeOfBuffer, |
| UA_UInt64 *destinationOfNumber) { |
| UA_UInt64 d = 0; |
| atoiUnsigned(inputBuffer, sizeOfBuffer, &d); |
| if(!destinationOfNumber) |
| return UA_STATUSCODE_BADDECODINGERROR; |
| *destinationOfNumber = d; |
| return UA_STATUSCODE_GOOD; |
| } |
| |
| static UA_StatusCode |
| parseSignedInteger(char* inputBuffer, size_t sizeOfBuffer, |
| UA_Int64 *destinationOfNumber) { |
| UA_Int64 d = 0; |
| atoiSigned(inputBuffer, sizeOfBuffer, &d); |
| if(!destinationOfNumber) |
| return UA_STATUSCODE_BADDECODINGERROR; |
| *destinationOfNumber = d; |
| return UA_STATUSCODE_GOOD; |
| } |
| #else |
| /* Safe strtol variant of unsigned string conversion. |
| * Returns UA_STATUSCODE_BADDECODINGERROR in case of overflows. |
| * Buffer limit is 20 digits. */ |
| static UA_StatusCode |
| parseUnsignedInteger(char* inputBuffer, size_t sizeOfBuffer, |
| UA_UInt64 *destinationOfNumber) { |
| /* Check size to avoid huge malicious stack allocation. |
| * No UInt64 can have more digits than 20. */ |
| if(sizeOfBuffer > 20) { |
| return UA_STATUSCODE_BADDECODINGERROR; |
| } |
| |
| /* convert to null terminated string */ |
| UA_STACKARRAY(char, string, sizeOfBuffer+1); |
| memcpy(string, inputBuffer, sizeOfBuffer); |
| string[sizeOfBuffer] = 0; |
| |
| /* Conversion */ |
| char *endptr, *str; |
| str = string; |
| errno = 0; /* To distinguish success/failure after call */ |
| UA_UInt64 val = strtoull(str, &endptr, 10); |
| |
| /* Check for various possible errors */ |
| if((errno == ERANGE && (val == LLONG_MAX || val == 0)) |
| || (errno != 0 )) { |
| return UA_STATUSCODE_BADDECODINGERROR; |
| } |
| |
| /* Check if no digits were found */ |
| if(endptr == str) |
| return UA_STATUSCODE_BADDECODINGERROR; |
| |
| /* copy to destination */ |
| *destinationOfNumber = val; |
| return UA_STATUSCODE_GOOD; |
| } |
| |
| /* Safe strtol variant of unsigned string conversion. |
| * Returns UA_STATUSCODE_BADDECODINGERROR in case of overflows. |
| * Buffer limit is 20 digits. */ |
| static UA_StatusCode |
| parseSignedInteger(char* inputBuffer, size_t sizeOfBuffer, |
| UA_Int64 *destinationOfNumber) { |
| /* Check size to avoid huge malicious stack allocation. |
| * No UInt64 can have more digits than 20. */ |
| if(sizeOfBuffer > 20) |
| return UA_STATUSCODE_BADDECODINGERROR; |
| |
| /* convert to null terminated string */ |
| UA_STACKARRAY(char, string, sizeOfBuffer + 1); |
| memcpy(string, inputBuffer, sizeOfBuffer); |
| string[sizeOfBuffer] = 0; |
| |
| /* Conversion */ |
| char *endptr, *str; |
| str = string; |
| errno = 0; /* To distinguish success/failure after call */ |
| UA_Int64 val = strtoll(str, &endptr, 10); |
| |
| /* Check for various possible errors */ |
| if((errno == ERANGE && (val == LONG_MAX || val == LONG_MIN)) |
| || (errno != 0 )) { |
| return UA_STATUSCODE_BADDECODINGERROR; |
| } |
| |
| /* Check if no digits were found */ |
| if(endptr == str) |
| return UA_STATUSCODE_BADDECODINGERROR; |
| |
| /* copy to destination */ |
| *destinationOfNumber = val; |
| return UA_STATUSCODE_GOOD; |
| } |
| #endif |
| |
| DECODE_JSON(Byte) { |
| CHECK_TOKEN_BOUNDS; |
| CHECK_PRIMITIVE; |
| size_t tokenSize; |
| char* tokenData; |
| GET_TOKEN(tokenData, tokenSize); |
| |
| UA_UInt64 out = 0; |
| UA_StatusCode s = parseUnsignedInteger(tokenData, tokenSize, &out); |
| *dst = (UA_Byte)out; |
| |
| if(moveToken) |
| parseCtx->index++; |
| return s; |
| } |
| |
| DECODE_JSON(UInt16) { |
| CHECK_TOKEN_BOUNDS; |
| CHECK_PRIMITIVE; |
| size_t tokenSize; |
| char* tokenData; |
| GET_TOKEN(tokenData, tokenSize); |
| |
| UA_UInt64 out = 0; |
| UA_StatusCode s = parseUnsignedInteger(tokenData, tokenSize, &out); |
| *dst = (UA_UInt16)out; |
| |
| if(moveToken) |
| parseCtx->index++; |
| return s; |
| } |
| |
| DECODE_JSON(UInt32) { |
| CHECK_TOKEN_BOUNDS; |
| CHECK_PRIMITIVE; |
| size_t tokenSize; |
| char* tokenData; |
| GET_TOKEN(tokenData, tokenSize); |
| |
| UA_UInt64 out = 0; |
| UA_StatusCode s = parseUnsignedInteger(tokenData, tokenSize, &out); |
| *dst = (UA_UInt32)out; |
| |
| if(moveToken) |
| parseCtx->index++; |
| return s; |
| } |
| |
| DECODE_JSON(UInt64) { |
| CHECK_TOKEN_BOUNDS; |
| CHECK_STRING; |
| size_t tokenSize; |
| char* tokenData; |
| GET_TOKEN(tokenData, tokenSize); |
| |
| UA_UInt64 out = 0; |
| UA_StatusCode s = parseUnsignedInteger(tokenData, tokenSize, &out); |
| *dst = (UA_UInt64)out; |
| |
| if(moveToken) |
| parseCtx->index++; |
| return s; |
| } |
| |
| DECODE_JSON(SByte) { |
| CHECK_TOKEN_BOUNDS; |
| CHECK_PRIMITIVE; |
| size_t tokenSize; |
| char* tokenData; |
| GET_TOKEN(tokenData, tokenSize); |
| |
| UA_Int64 out = 0; |
| UA_StatusCode s = parseSignedInteger(tokenData, tokenSize, &out); |
| *dst = (UA_SByte)out; |
| |
| if(moveToken) |
| parseCtx->index++; |
| return s; |
| } |
| |
| DECODE_JSON(Int16) { |
| CHECK_TOKEN_BOUNDS; |
| CHECK_PRIMITIVE; |
| size_t tokenSize; |
| char* tokenData; |
| GET_TOKEN(tokenData, tokenSize); |
| |
| UA_Int64 out = 0; |
| UA_StatusCode s = parseSignedInteger(tokenData, tokenSize, &out); |
| *dst = (UA_Int16)out; |
| |
| if(moveToken) |
| parseCtx->index++; |
| return s; |
| } |
| |
| DECODE_JSON(Int32) { |
| CHECK_TOKEN_BOUNDS; |
| CHECK_PRIMITIVE; |
| size_t tokenSize; |
| char* tokenData; |
| GET_TOKEN(tokenData, tokenSize); |
| |
| UA_Int64 out = 0; |
| UA_StatusCode s = parseSignedInteger(tokenData, tokenSize, &out); |
| *dst = (UA_Int32)out; |
| |
| if(moveToken) |
| parseCtx->index++; |
| return s; |
| } |
| |
| DECODE_JSON(Int64) { |
| CHECK_TOKEN_BOUNDS; |
| CHECK_STRING; |
| size_t tokenSize; |
| char* tokenData; |
| GET_TOKEN(tokenData, tokenSize); |
| |
| UA_Int64 out = 0; |
| UA_StatusCode s = parseSignedInteger(tokenData, tokenSize, &out); |
| *dst = (UA_Int64)out; |
| |
| if(moveToken) |
| parseCtx->index++; |
| return s; |
| } |
| |
| static UA_UInt32 hex2int(char ch) { |
| if(ch >= '0' && ch <= '9') |
| return (UA_UInt32)(ch - '0'); |
| if(ch >= 'A' && ch <= 'F') |
| return (UA_UInt32)(ch - 'A' + 10); |
| if(ch >= 'a' && ch <= 'f') |
| return (UA_UInt32)(ch - 'a' + 10); |
| return 0; |
| } |
| |
| /* Float |
| * Either a JSMN_STRING or JSMN_PRIMITIVE |
| */ |
| DECODE_JSON(Float) { |
| CHECK_TOKEN_BOUNDS; |
| size_t tokenSize; |
| char* tokenData; |
| GET_TOKEN(tokenData, tokenSize); |
| |
| /* https://www.exploringbinary.com/maximum-number-of-decimal-digits-in-binary-floating-point-numbers/ |
| * Maximum digit counts for select IEEE floating-point formats: 149 |
| * Sanity check. */ |
| if(tokenSize > 150) |
| return UA_STATUSCODE_BADDECODINGERROR; |
| |
| jsmntype_t tokenType = getJsmnType(parseCtx); |
| if(tokenType == JSMN_STRING) { |
| /*It could be a String with Nan, Infinity*/ |
| if(tokenSize == 8 && memcmp(tokenData, "Infinity", 8) == 0) { |
| *dst = (UA_Float)INFINITY; |
| return UA_STATUSCODE_GOOD; |
| } |
| |
| if(tokenSize == 9 && memcmp(tokenData, "-Infinity", 9) == 0) { |
| /* workaround an MSVC 2013 issue */ |
| *dst = (UA_Float)-INFINITY; |
| return UA_STATUSCODE_GOOD; |
| } |
| |
| if(tokenSize == 3 && memcmp(tokenData, "NaN", 3) == 0) { |
| *dst = (UA_Float)NAN; |
| return UA_STATUSCODE_GOOD; |
| } |
| |
| if(tokenSize == 4 && memcmp(tokenData, "-NaN", 4) == 0) { |
| *dst = (UA_Float)NAN; |
| return UA_STATUSCODE_GOOD; |
| } |
| return UA_STATUSCODE_BADDECODINGERROR; |
| } |
| |
| if(tokenType != JSMN_PRIMITIVE) |
| return UA_STATUSCODE_BADDECODINGERROR; |
| |
| /* Null-Terminate for sscanf. */ |
| UA_STACKARRAY(char, string, tokenSize+1); |
| memcpy(string, tokenData, tokenSize); |
| string[tokenSize] = 0; |
| |
| UA_Float d = 0; |
| #ifdef UA_ENABLE_CUSTOM_LIBC |
| d = (UA_Float)__floatscan(string, 1, 0); |
| #else |
| char c = 0; |
| /* On success, the function returns the number of variables filled. |
| * In the case of an input failure before any data could be successfully read, EOF is returned. */ |
| int ret = sscanf(string, "%f%c", &d, &c); |
| |
| /* Exactly one var must be filled. %c acts as a guard for wrong input which is accepted by sscanf. |
| E.g. 1.23.45 is not accepted. */ |
| if(ret == EOF || (ret != 1)) |
| return UA_STATUSCODE_BADDECODINGERROR; |
| #endif |
| |
| *dst = d; |
| |
| parseCtx->index++; |
| return UA_STATUSCODE_GOOD; |
| } |
| |
| /* Either a JSMN_STRING or JSMN_PRIMITIVE */ |
| DECODE_JSON(Double) { |
| CHECK_TOKEN_BOUNDS; |
| size_t tokenSize; |
| char* tokenData; |
| GET_TOKEN(tokenData, tokenSize); |
| |
| /* https://www.exploringbinary.com/maximum-number-of-decimal-digits-in-binary-floating-point-numbers/ |
| * Maximum digit counts for select IEEE floating-point formats: 1074 |
| * Sanity check. |
| */ |
| if(tokenSize > 1075) |
| return UA_STATUSCODE_BADDECODINGERROR; |
| |
| jsmntype_t tokenType = getJsmnType(parseCtx); |
| if(tokenType == JSMN_STRING) { |
| /*It could be a String with Nan, Infinity*/ |
| if(tokenSize == 8 && memcmp(tokenData, "Infinity", 8) == 0) { |
| *dst = INFINITY; |
| return UA_STATUSCODE_GOOD; |
| } |
| |
| if(tokenSize == 9 && memcmp(tokenData, "-Infinity", 9) == 0) { |
| /* workaround an MSVC 2013 issue */ |
| *dst = -INFINITY; |
| return UA_STATUSCODE_GOOD; |
| } |
| |
| if(tokenSize == 3 && memcmp(tokenData, "NaN", 3) == 0) { |
| *dst = NAN; |
| return UA_STATUSCODE_GOOD; |
| } |
| |
| if(tokenSize == 4 && memcmp(tokenData, "-NaN", 4) == 0) { |
| *dst = NAN; |
| return UA_STATUSCODE_GOOD; |
| } |
| return UA_STATUSCODE_BADDECODINGERROR; |
| } |
| |
| if(tokenType != JSMN_PRIMITIVE) |
| return UA_STATUSCODE_BADDECODINGERROR; |
| |
| /* Null-Terminate for sscanf. Should this better be handled on heap? Max |
| * 1075 input chars allowed. Not using heap. */ |
| UA_STACKARRAY(char, string, tokenSize+1); |
| memcpy(string, tokenData, tokenSize); |
| string[tokenSize] = 0; |
| |
| UA_Double d = 0; |
| #ifdef UA_ENABLE_CUSTOM_LIBC |
| d = (UA_Double)__floatscan(string, 2, 0); |
| #else |
| char c = 0; |
| /* On success, the function returns the number of variables filled. |
| * In the case of an input failure before any data could be successfully read, EOF is returned. */ |
| int ret = sscanf(string, "%lf%c", &d, &c); |
| |
| /* Exactly one var must be filled. %c acts as a guard for wrong input which is accepted by sscanf. |
| E.g. 1.23.45 is not accepted. */ |
| if(ret == EOF || (ret != 1)) |
| return UA_STATUSCODE_BADDECODINGERROR; |
| #endif |
| |
| *dst = d; |
| |
| parseCtx->index++; |
| return UA_STATUSCODE_GOOD; |
| } |
| |
| /* |
| Expects 36 chars in format 00000003-0009-000A-0807-060504030201 |
| | data1| |d2| |d3| |d4| | data4 | |
| */ |
| static UA_Guid UA_Guid_fromChars(const char* chars) { |
| UA_Guid dst; |
| UA_Guid_init(&dst); |
| for(size_t i = 0; i < 8; i++) |
| dst.data1 |= (UA_UInt32)(hex2int(chars[i]) << (28 - (i*4))); |
| for(size_t i = 0; i < 4; i++) { |
| dst.data2 |= (UA_UInt16)(hex2int(chars[9+i]) << (12 - (i*4))); |
| dst.data3 |= (UA_UInt16)(hex2int(chars[14+i]) << (12 - (i*4))); |
| } |
| dst.data4[0] |= (UA_Byte)(hex2int(chars[19]) << 4u); |
| dst.data4[0] |= (UA_Byte)(hex2int(chars[20]) << 0u); |
| dst.data4[1] |= (UA_Byte)(hex2int(chars[21]) << 4u); |
| dst.data4[1] |= (UA_Byte)(hex2int(chars[22]) << 0u); |
| for(size_t i = 0; i < 6; i++) { |
| dst.data4[2+i] |= (UA_Byte)(hex2int(chars[24 + i*2]) << 4u); |
| dst.data4[2+i] |= (UA_Byte)(hex2int(chars[25 + i*2]) << 0u); |
| } |
| return dst; |
| } |
| |
| DECODE_JSON(Guid) { |
| CHECK_STRING; |
| CHECK_TOKEN_BOUNDS; |
| size_t tokenSize; |
| char* tokenData; |
| GET_TOKEN(tokenData, tokenSize); |
| |
| if(tokenSize != 36) |
| return UA_STATUSCODE_BADDECODINGERROR; |
| |
| /* check if incorrect chars are present */ |
| for(size_t i = 0; i < tokenSize; i++) { |
| if(!(tokenData[i] == '-' |
| || (tokenData[i] >= '0' && tokenData[i] <= '9') |
| || (tokenData[i] >= 'A' && tokenData[i] <= 'F') |
| || (tokenData[i] >= 'a' && tokenData[i] <= 'f'))) { |
| return UA_STATUSCODE_BADDECODINGERROR; |
| } |
| } |
| |
| *dst = UA_Guid_fromChars(tokenData); |
| |
| if(moveToken) |
| parseCtx->index++; |
| return UA_STATUSCODE_GOOD; |
| } |
| |
| DECODE_JSON(String) { |
| ALLOW_NULL; |
| CHECK_STRING; |
| CHECK_TOKEN_BOUNDS; |
| size_t tokenSize; |
| char* tokenData; |
| GET_TOKEN(tokenData, tokenSize); |
| |
| /* Empty string? */ |
| if(tokenSize == 0) { |
| dst->data = (UA_Byte*)UA_EMPTY_ARRAY_SENTINEL; |
| dst->length = 0; |
| if(moveToken) |
| parseCtx->index++; |
| return UA_STATUSCODE_GOOD; |
| } |
| |
| /* The actual value is at most of the same length as the source string: |
| * - Shortcut escapes (e.g. "\t") (length 2) are converted to 1 byte |
| * - A single \uXXXX escape (length 6) is converted to at most 3 bytes |
| * - Two \uXXXX escapes (length 12) forming an UTF-16 surrogate pair are |
| * converted to 4 bytes */ |
| char *outputBuffer = (char*)UA_malloc(tokenSize); |
| if(!outputBuffer) |
| return UA_STATUSCODE_BADOUTOFMEMORY; |
| |
| const char *p = (char*)tokenData; |
| const char *end = (char*)&tokenData[tokenSize]; |
| char *pos = outputBuffer; |
| while(p < end) { |
| /* No escaping */ |
| if(*p != '\\') { |
| *(pos++) = *(p++); |
| continue; |
| } |
| |
| /* Escape character */ |
| p++; |
| if(p == end) |
| goto cleanup; |
| |
| if(*p != 'u') { |
| switch(*p) { |
| case '"': case '\\': case '/': *pos = *p; break; |
| case 'b': *pos = '\b'; break; |
| case 'f': *pos = '\f'; break; |
| case 'n': *pos = '\n'; break; |
| case 'r': *pos = '\r'; break; |
| case 't': *pos = '\t'; break; |
| default: goto cleanup; |
| } |
| pos++; |
| p++; |
| continue; |
| } |
| |
| /* Unicode */ |
| if(p + 4 >= end) |
| goto cleanup; |
| int32_t value_signed = decode_unicode_escape(p); |
| if(value_signed < 0) |
| goto cleanup; |
| uint32_t value = (uint32_t)value_signed; |
| p += 5; |
| |
| if(0xD800 <= value && value <= 0xDBFF) { |
| /* Surrogate pair */ |
| if(p + 5 >= end) |
| goto cleanup; |
| if(*p != '\\' || *(p + 1) != 'u') |
| goto cleanup; |
| int32_t value2 = decode_unicode_escape(p + 1); |
| if(value2 < 0xDC00 || value2 > 0xDFFF) |
| goto cleanup; |
| value = ((value - 0xD800u) << 10u) + (uint32_t)((value2 - 0xDC00) + 0x10000); |
| p += 6; |
| } else if(0xDC00 <= value && value <= 0xDFFF) { |
| /* Invalid Unicode '\\u%04X' */ |
| goto cleanup; |
| } |
| |
| size_t length; |
| if(utf8_encode((int32_t)value, pos, &length)) |
| goto cleanup; |
| |
| pos += length; |
| } |
| |
| dst->length = (size_t)(pos - outputBuffer); |
| if(dst->length > 0) { |
| dst->data = (UA_Byte*)outputBuffer; |
| } else { |
| dst->data = (UA_Byte*)UA_EMPTY_ARRAY_SENTINEL; |
| UA_free(outputBuffer); |
| } |
| |
| if(moveToken) |
| parseCtx->index++; |
| return UA_STATUSCODE_GOOD; |
| |
| cleanup: |
| UA_free(outputBuffer); |
| return UA_STATUSCODE_BADDECODINGERROR; |
| } |
| |
| DECODE_JSON(ByteString) { |
| ALLOW_NULL; |
| CHECK_STRING; |
| CHECK_TOKEN_BOUNDS; |
| size_t tokenSize; |
| char* tokenData; |
| GET_TOKEN(tokenData, tokenSize); |
| |
| /* Empty bytestring? */ |
| if(tokenSize == 0) { |
| dst->data = (UA_Byte*)UA_EMPTY_ARRAY_SENTINEL; |
| dst->length = 0; |
| return UA_STATUSCODE_GOOD; |
| } |
| |
| size_t flen = 0; |
| unsigned char* unB64 = UA_unbase64((unsigned char*)tokenData, tokenSize, &flen); |
| if(unB64 == 0) |
| return UA_STATUSCODE_BADDECODINGERROR; |
| |
| dst->data = (u8*)unB64; |
| dst->length = flen; |
| |
| if(moveToken) |
| parseCtx->index++; |
| return UA_STATUSCODE_GOOD; |
| } |
| |
| DECODE_JSON(LocalizedText) { |
| ALLOW_NULL; |
| CHECK_OBJECT; |
| |
| DecodeEntry entries[2] = { |
| {UA_JSONKEY_LOCALE, &dst->locale, (decodeJsonSignature) String_decodeJson, false, NULL}, |
| {UA_JSONKEY_TEXT, &dst->text, (decodeJsonSignature) String_decodeJson, false, NULL} |
| }; |
| |
| return decodeFields(ctx, parseCtx, entries, 2, type); |
| } |
| |
| DECODE_JSON(QualifiedName) { |
| ALLOW_NULL; |
| CHECK_OBJECT; |
| |
| DecodeEntry entries[2] = { |
| {UA_JSONKEY_NAME, &dst->name, (decodeJsonSignature) String_decodeJson, false, NULL}, |
| {UA_JSONKEY_URI, &dst->namespaceIndex, (decodeJsonSignature) UInt16_decodeJson, false, NULL} |
| }; |
| |
| return decodeFields(ctx, parseCtx, entries, 2, type); |
| } |
| |
| /* Function for searching ahead of the current token. Used for retrieving the |
| * OPC UA type of a token */ |
| static status |
| searchObjectForKeyRec(const char *searchKey, CtxJson *ctx, |
| ParseCtx *parseCtx, size_t *resultIndex, UA_UInt16 depth) { |
| UA_StatusCode ret = UA_STATUSCODE_BADNOTFOUND; |
| |
| CHECK_TOKEN_BOUNDS; |
| |
| if(parseCtx->tokenArray[parseCtx->index].type == JSMN_OBJECT) { |
| size_t objectCount = (size_t)parseCtx->tokenArray[parseCtx->index].size; |
| parseCtx->index++; /*Object to first Key*/ |
| |
| for(size_t i = 0; i < objectCount; i++) { |
| CHECK_TOKEN_BOUNDS; |
| if(depth == 0) { /* we search only on first layer */ |
| if(jsoneq((char*)ctx->pos, &parseCtx->tokenArray[parseCtx->index], searchKey) == 0) { |
| /*found*/ |
| parseCtx->index++; /*We give back a pointer to the value of the searched key!*/ |
| *resultIndex = parseCtx->index; |
| return UA_STATUSCODE_GOOD; |
| } |
| } |
| |
| parseCtx->index++; /* value */ |
| CHECK_TOKEN_BOUNDS; |
| |
| if(parseCtx->tokenArray[parseCtx->index].type == JSMN_OBJECT) { |
| ret = searchObjectForKeyRec(searchKey, ctx, parseCtx, resultIndex, |
| (UA_UInt16)(depth + 1)); |
| } else if(parseCtx->tokenArray[parseCtx->index].type == JSMN_ARRAY) { |
| ret = searchObjectForKeyRec(searchKey, ctx, parseCtx, resultIndex, |
| (UA_UInt16)(depth + 1)); |
| } else { |
| /* Only Primitive or string */ |
| parseCtx->index++; |
| } |
| } |
| } else if(parseCtx->tokenArray[parseCtx->index].type == JSMN_ARRAY) { |
| size_t arraySize = (size_t)parseCtx->tokenArray[parseCtx->index].size; |
| parseCtx->index++; /*Object to first element*/ |
| |
| for(size_t i = 0; i < arraySize; i++) { |
| CHECK_TOKEN_BOUNDS; |
| if(parseCtx->tokenArray[parseCtx->index].type == JSMN_OBJECT) { |
| ret = searchObjectForKeyRec(searchKey, ctx, parseCtx, resultIndex, |
| (UA_UInt16)(depth + 1)); |
| } else if(parseCtx->tokenArray[parseCtx->index].type == JSMN_ARRAY) { |
| ret = searchObjectForKeyRec(searchKey, ctx, parseCtx, resultIndex, |
| (UA_UInt16)(depth + 1)); |
| } else { |
| /* Only Primitive or string */ |
| parseCtx->index++; |
| } |
| } |
| } |
| return ret; |
| } |
| |
| UA_FUNC_ATTR_WARN_UNUSED_RESULT status |
| lookAheadForKey(const char* search, CtxJson *ctx, |
| ParseCtx *parseCtx, size_t *resultIndex) { |
| UA_UInt16 oldIndex = parseCtx->index; /* Save index for later restore */ |
| |
| UA_UInt16 depth = 0; |
| UA_StatusCode ret = searchObjectForKeyRec(search, ctx, parseCtx, resultIndex, depth); |
| |
| parseCtx->index = oldIndex; /* Restore index */ |
| return ret; |
| } |
| |
| /* Function used to jump over an object which cannot be parsed */ |
| static status |
| jumpOverRec(CtxJson *ctx, ParseCtx *parseCtx, |
| size_t *resultIndex, UA_UInt16 depth) { |
| UA_StatusCode ret = UA_STATUSCODE_BADDECODINGERROR; |
| CHECK_TOKEN_BOUNDS; |
| |
| if(parseCtx->tokenArray[parseCtx->index].type == JSMN_OBJECT) { |
| size_t objectCount = (size_t)(parseCtx->tokenArray[parseCtx->index].size); |
| |
| parseCtx->index++; /*Object to first Key*/ |
| CHECK_TOKEN_BOUNDS; |
| |
| size_t i; |
| for(i = 0; i < objectCount; i++) { |
| CHECK_TOKEN_BOUNDS; |
| |
| parseCtx->index++; /*value*/ |
| CHECK_TOKEN_BOUNDS; |
| |
| if(parseCtx->tokenArray[parseCtx->index].type == JSMN_OBJECT) { |
| jumpOverRec(ctx, parseCtx, resultIndex, (UA_UInt16)(depth + 1)); |
| } else if(parseCtx->tokenArray[parseCtx->index].type == JSMN_ARRAY) { |
| jumpOverRec(ctx, parseCtx, resultIndex, (UA_UInt16)(depth + 1)); |
| } else { |
| /*Only Primitive or string*/ |
| parseCtx->index++; |
| } |
| } |
| } else if(parseCtx->tokenArray[parseCtx->index].type == JSMN_ARRAY) { |
| size_t arraySize = (size_t)(parseCtx->tokenArray[parseCtx->index].size); |
| |
| parseCtx->index++; /*Object to first element*/ |
| CHECK_TOKEN_BOUNDS; |
| |
| size_t i; |
| for(i = 0; i < arraySize; i++) { |
| if(parseCtx->tokenArray[parseCtx->index].type == JSMN_OBJECT) { |
| jumpOverRec(ctx, parseCtx, resultIndex, (UA_UInt16)(depth + 1)); |
| } else if(parseCtx->tokenArray[parseCtx->index].type == JSMN_ARRAY) { |
| jumpOverRec(ctx, parseCtx, resultIndex, (UA_UInt16)(depth + 1)); |
| } else { |
| /*Only Primitive or string*/ |
| parseCtx->index++; |
| } |
| } |
| } |
| return ret; |
| } |
| |
| static status |
| jumpOverObject(CtxJson *ctx, ParseCtx *parseCtx, size_t *resultIndex) { |
| UA_UInt16 oldIndex = parseCtx->index; /* Save index for later restore */ |
| UA_UInt16 depth = 0; |
| jumpOverRec(ctx, parseCtx, resultIndex, depth); |
| *resultIndex = parseCtx->index; |
| parseCtx->index = oldIndex; /* Restore index */ |
| return UA_STATUSCODE_GOOD; |
| } |
| |
| static status |
| prepareDecodeNodeIdJson(UA_NodeId *dst, CtxJson *ctx, ParseCtx *parseCtx, |
| u8 *fieldCount, DecodeEntry *entries) { |
| /* possible keys: Id, IdType*/ |
| /* Id must always be present */ |
| entries[*fieldCount].fieldName = UA_JSONKEY_ID; |
| entries[*fieldCount].found = false; |
| entries[*fieldCount].type = NULL; |
| |
| /* IdType */ |
| UA_Boolean hasIdType = false; |
| size_t searchResult = 0; |
| status ret = lookAheadForKey(UA_JSONKEY_IDTYPE, ctx, parseCtx, &searchResult); |
| if(ret == UA_STATUSCODE_GOOD) { /*found*/ |
| hasIdType = true; |
| } |
| |
| if(hasIdType) { |
| size_t size = (size_t)(parseCtx->tokenArray[searchResult].end - |
| parseCtx->tokenArray[searchResult].start); |
| if(size < 1) { |
| return UA_STATUSCODE_BADDECODINGERROR; |
| } |
| |
| char *idType = (char*)(ctx->pos + parseCtx->tokenArray[searchResult].start); |
| |
| if(idType[0] == '2') { |
| dst->identifierType = UA_NODEIDTYPE_GUID; |
| entries[*fieldCount].fieldPointer = &dst->identifier.guid; |
| entries[*fieldCount].function = (decodeJsonSignature) Guid_decodeJson; |
| } else if(idType[0] == '1') { |
| dst->identifierType = UA_NODEIDTYPE_STRING; |
| entries[*fieldCount].fieldPointer = &dst->identifier.string; |
| entries[*fieldCount].function = (decodeJsonSignature) String_decodeJson; |
| } else if(idType[0] == '3') { |
| dst->identifierType = UA_NODEIDTYPE_BYTESTRING; |
| entries[*fieldCount].fieldPointer = &dst->identifier.byteString; |
| entries[*fieldCount].function = (decodeJsonSignature) ByteString_decodeJson; |
| } else { |
| return UA_STATUSCODE_BADDECODINGERROR; |
| } |
| |
| /* Id always present */ |
| (*fieldCount)++; |
| |
| entries[*fieldCount].fieldName = UA_JSONKEY_IDTYPE; |
| entries[*fieldCount].fieldPointer = NULL; |
| entries[*fieldCount].function = NULL; |
| entries[*fieldCount].found = false; |
| entries[*fieldCount].type = NULL; |
| |
| /* IdType */ |
| (*fieldCount)++; |
| } else { |
| dst->identifierType = UA_NODEIDTYPE_NUMERIC; |
| entries[*fieldCount].fieldPointer = &dst->identifier.numeric; |
| entries[*fieldCount].function = (decodeJsonSignature) UInt32_decodeJson; |
| entries[*fieldCount].type = NULL; |
| (*fieldCount)++; |
| } |
| |
| return UA_STATUSCODE_GOOD; |
| } |
| |
| DECODE_JSON(NodeId) { |
| ALLOW_NULL; |
| CHECK_OBJECT; |
| |
| /* NameSpace */ |
| UA_Boolean hasNamespace = false; |
| size_t searchResultNamespace = 0; |
| status ret = lookAheadForKey(UA_JSONKEY_NAMESPACE, ctx, parseCtx, &searchResultNamespace); |
| if(ret != UA_STATUSCODE_GOOD) { |
| dst->namespaceIndex = 0; |
| } else { |
| hasNamespace = true; |
| } |
| |
| /* Keep track over number of keys present, incremented if key found */ |
| u8 fieldCount = 0; |
| DecodeEntry entries[3]; |
| ret = prepareDecodeNodeIdJson(dst, ctx, parseCtx, &fieldCount, entries); |
| if(ret != UA_STATUSCODE_GOOD) |
| return ret; |
| |
| if(hasNamespace) { |
| entries[fieldCount].fieldName = UA_JSONKEY_NAMESPACE; |
| entries[fieldCount].fieldPointer = &dst->namespaceIndex; |
| entries[fieldCount].function = (decodeJsonSignature) UInt16_decodeJson; |
| entries[fieldCount].found = false; |
| entries[fieldCount].type = NULL; |
| fieldCount++; |
| } else { |
| dst->namespaceIndex = 0; |
| } |
| ret = decodeFields(ctx, parseCtx, entries, fieldCount, type); |
| return ret; |
| } |
| |
| DECODE_JSON(ExpandedNodeId) { |
| ALLOW_NULL; |
| CHECK_OBJECT; |
| |
| /* Keep track over number of keys present, incremented if key found */ |
| u8 fieldCount = 0; |
| |
| /* ServerUri */ |
| UA_Boolean hasServerUri = false; |
| size_t searchResultServerUri = 0; |
| status ret = lookAheadForKey(UA_JSONKEY_SERVERURI, ctx, parseCtx, &searchResultServerUri); |
| if(ret != UA_STATUSCODE_GOOD) { |
| dst->serverIndex = 0; |
| } else { |
| hasServerUri = true; |
| } |
| |
| /* NameSpace */ |
| UA_Boolean hasNamespace = false; |
| UA_Boolean isNamespaceString = false; |
| size_t searchResultNamespace = 0; |
| ret = lookAheadForKey(UA_JSONKEY_NAMESPACE, ctx, parseCtx, &searchResultNamespace); |
| if(ret != UA_STATUSCODE_GOOD) { |
| dst->namespaceUri = UA_STRING_NULL; |
| } else { |
| hasNamespace = true; |
| jsmntok_t nsToken = parseCtx->tokenArray[searchResultNamespace]; |
| if(nsToken.type == JSMN_STRING) |
| isNamespaceString = true; |
| } |
| |
| DecodeEntry entries[4]; |
| ret = prepareDecodeNodeIdJson(&dst->nodeId, ctx, parseCtx, &fieldCount, entries); |
| if(ret != UA_STATUSCODE_GOOD) |
| return ret; |
| |
| if(hasNamespace) { |
| entries[fieldCount].fieldName = UA_JSONKEY_NAMESPACE; |
| if(isNamespaceString) { |
| entries[fieldCount].fieldPointer = &dst->namespaceUri; |
| entries[fieldCount].function = (decodeJsonSignature) String_decodeJson; |
| } else { |
| entries[fieldCount].fieldPointer = &dst->nodeId.namespaceIndex; |
| entries[fieldCount].function = (decodeJsonSignature) UInt16_decodeJson; |
| } |
| entries[fieldCount].found = false; |
| entries[fieldCount].type = NULL; |
| fieldCount++; |
| } |
| |
| if(hasServerUri) { |
| entries[fieldCount].fieldName = UA_JSONKEY_SERVERURI; |
| entries[fieldCount].fieldPointer = &dst->serverIndex; |
| entries[fieldCount].function = (decodeJsonSignature) UInt32_decodeJson; |
| entries[fieldCount].found = false; |
| entries[fieldCount].type = NULL; |
| fieldCount++; |
| } else { |
| dst->serverIndex = 0; |
| } |
| |
| return decodeFields(ctx, parseCtx, entries, fieldCount, type); |
| } |
| |
| DECODE_JSON(DateTime) { |
| CHECK_STRING; |
| CHECK_TOKEN_BOUNDS; |
| size_t tokenSize; |
| char* tokenData; |
| GET_TOKEN(tokenData, tokenSize); |
| |
| /* TODO: proper ISO 8601:2004 parsing, musl strptime!*/ |
| /* DateTime ISO 8601:2004 without milli is 20 Characters, with millis 24 */ |
| if(tokenSize != 20 && tokenSize != 24) { |
| return UA_STATUSCODE_BADDECODINGERROR; |
| } |
| |
| /* sanity check */ |
| if(tokenData[4] != '-' || tokenData[7] != '-' || tokenData[10] != 'T' || |
| tokenData[13] != ':' || tokenData[16] != ':' || |
| !(tokenData[19] == 'Z' || tokenData[19] == '.')) { |
| return UA_STATUSCODE_BADDECODINGERROR; |
| } |
| |
| struct mytm dts; |
| memset(&dts, 0, sizeof(dts)); |
| |
| UA_UInt64 year = 0; |
| atoiUnsigned(&tokenData[0], 4, &year); |
| dts.tm_year = (UA_UInt16)year - 1900; |
| UA_UInt64 month = 0; |
| atoiUnsigned(&tokenData[5], 2, &month); |
| dts.tm_mon = (UA_UInt16)month - 1; |
| UA_UInt64 day = 0; |
| atoiUnsigned(&tokenData[8], 2, &day); |
| dts.tm_mday = (UA_UInt16)day; |
| UA_UInt64 hour = 0; |
| atoiUnsigned(&tokenData[11], 2, &hour); |
| dts.tm_hour = (UA_UInt16)hour; |
| UA_UInt64 min = 0; |
| atoiUnsigned(&tokenData[14], 2, &min); |
| dts.tm_min = (UA_UInt16)min; |
| UA_UInt64 sec = 0; |
| atoiUnsigned(&tokenData[17], 2, &sec); |
| dts.tm_sec = (UA_UInt16)sec; |
| |
| UA_UInt64 msec = 0; |
| if(tokenSize == 24) { |
| atoiUnsigned(&tokenData[20], 3, &msec); |
| } |
| |
| long long sinceunix = __tm_to_secs(&dts); |
| UA_DateTime dt = (UA_DateTime)((UA_UInt64)(sinceunix*UA_DATETIME_SEC + |
| UA_DATETIME_UNIX_EPOCH) + |
| (UA_UInt64)(UA_DATETIME_MSEC * msec)); |
| *dst = dt; |
| |
| if(moveToken) |
| parseCtx->index++; |
| return UA_STATUSCODE_GOOD; |
| } |
| |
| DECODE_JSON(StatusCode) { |
| status ret = DECODE_DIRECT_JSON(dst, UInt32); |
| if(ret != UA_STATUSCODE_GOOD) |
| return ret; |
| |
| if(moveToken) |
| parseCtx->index++; |
| return UA_STATUSCODE_GOOD; |
| } |
| |
| static status |
| VariantDimension_decodeJson(void * dst, const UA_DataType *type, |
| CtxJson *ctx, ParseCtx *parseCtx, UA_Boolean moveToken) { |
| (void) type; |
| const UA_DataType *dimType = &UA_TYPES[UA_TYPES_UINT32]; |
| return Array_decodeJson_internal((void**)dst, dimType, ctx, parseCtx, moveToken); |
| } |
| |
| DECODE_JSON(Variant) { |
| ALLOW_NULL; |
| CHECK_OBJECT; |
| |
| /* First search for the variant type in the json object. */ |
| size_t searchResultType = 0; |
| status ret = lookAheadForKey(UA_JSONKEY_TYPE, ctx, parseCtx, &searchResultType); |
| if(ret != UA_STATUSCODE_GOOD) |
| return UA_STATUSCODE_BADDECODINGERROR; |
| |
| size_t size = (size_t)(parseCtx->tokenArray[searchResultType].end - parseCtx->tokenArray[searchResultType].start); |
| |
| /* check if size is zero or the type is not a number */ |
| if(size < 1 || parseCtx->tokenArray[searchResultType].type != JSMN_PRIMITIVE) { |
| return UA_STATUSCODE_BADDECODINGERROR; |
| } |
| |
| /*Parse the type*/ |
| UA_UInt64 idTypeDecoded = 0; |
| char *idTypeEncoded = (char*)(ctx->pos + parseCtx->tokenArray[searchResultType].start); |
| status typeDecodeStatus = atoiUnsigned(idTypeEncoded, size, &idTypeDecoded); |
| |
| /* value is not a valid number */ |
| if(typeDecodeStatus != UA_STATUSCODE_GOOD) { |
| return typeDecodeStatus; |
| } |
| |
| /*Set the type, Get the Type by nodeID!*/ |
| UA_NodeId typeNodeId = UA_NODEID_NUMERIC(0, (UA_UInt32)idTypeDecoded); |
| const UA_DataType *bodyType = UA_findDataType(&typeNodeId); |
| if(bodyType == NULL) { |
| return UA_STATUSCODE_BADDECODINGERROR; |
| } |
| |
| /*Set the type*/ |
| dst->type = bodyType; |
| |
| /* LookAhead BODY */ |
| /* Does the variant contain an array? */ |
| UA_Boolean isArray = false; |
| UA_Boolean isBodyNull = false; |
| |
| /* Search for body */ |
| size_t searchResultBody = 0; |
| ret = lookAheadForKey(UA_JSONKEY_BODY, ctx, parseCtx, &searchResultBody); |
| if(ret == UA_STATUSCODE_GOOD) { /* body found */ |
| /* get body token */ |
| jsmntok_t bodyToken = parseCtx->tokenArray[searchResultBody]; |
| |
| /*BODY is null?*/ |
| if(isJsonTokenNull(ctx, &bodyToken)) { |
| dst->data = NULL; |
| isBodyNull = true; |
| } |
| |
| if(bodyToken.type == JSMN_ARRAY) { |
| isArray = true; |
| |
| size_t arraySize = 0; |
| arraySize = (size_t)parseCtx->tokenArray[searchResultBody].size; |
| dst->arrayLength = arraySize; |
| } |
| } else { |
| /*TODO: no body? set value NULL?*/ |
| return UA_STATUSCODE_BADDECODINGERROR; |
| } |
| |
| /* LookAhead DIMENSION */ |
| UA_Boolean hasDimension = false; |
| |
| /* Has the variant dimension? */ |
| size_t searchResultDim = 0; |
| ret = lookAheadForKey(UA_JSONKEY_DIMENSION, ctx, parseCtx, &searchResultDim); |
| if(ret == UA_STATUSCODE_GOOD) { |
| hasDimension = true; |
| size_t dimensionSize = 0; |
| dimensionSize = (size_t)parseCtx->tokenArray[searchResultDim].size; |
| dst->arrayDimensionsSize = dimensionSize; |
| } |
| |
| /* no array but has dimension. error? */ |
| if(!isArray && hasDimension) |
| return UA_STATUSCODE_BADDECODINGERROR; |
| |
| /* Get the datatype of the content. The type must be a builtin data type. |
| * All not-builtin types are wrapped in an ExtensionObject. */ |
| if(bodyType->typeKind > UA_TYPES_DIAGNOSTICINFO) |
| return UA_STATUSCODE_BADDECODINGERROR; |
| |
| /* A variant cannot contain a variant. But it can contain an array of |
| * variants */ |
| if(bodyType->typeKind == UA_DATATYPEKIND_VARIANT && !isArray) |
| return UA_STATUSCODE_BADDECODINGERROR; |
| |
| if(isArray) { |
| DecodeEntry entries[3] = { |
| {UA_JSONKEY_TYPE, NULL, NULL, false, NULL}, |
| {UA_JSONKEY_BODY, &dst->data, (decodeJsonSignature) Array_decodeJson, false, NULL}, |
| {UA_JSONKEY_DIMENSION, &dst->arrayDimensions, |
| (decodeJsonSignature) VariantDimension_decodeJson, false, NULL}}; |
| |
| if(!hasDimension) { |
| ret = decodeFields(ctx, parseCtx, entries, 2, bodyType); /*use first 2 fields*/ |
| } else { |
| ret = decodeFields(ctx, parseCtx, entries, 3, bodyType); /*use all fields*/ |
| } |
| } else if(bodyType->typeKind != UA_DATATYPEKIND_EXTENSIONOBJECT) { |
| /* Allocate Memory for Body */ |
| if(!isBodyNull) { |
| dst->data = UA_new(bodyType); |
| if(!dst->data) |
| return UA_STATUSCODE_BADOUTOFMEMORY; |
| } |
| |
| DecodeEntry entries[2] = {{UA_JSONKEY_TYPE, NULL, NULL, false, NULL}, |
| {UA_JSONKEY_BODY, dst->data, (decodeJsonSignature) decodeJsonInternal, false, NULL}}; |
| ret = decodeFields(ctx, parseCtx, entries, 2, bodyType); |
| } else { /* extensionObject */ |
| DecodeEntry entries[2] = {{UA_JSONKEY_TYPE, NULL, NULL, false, NULL}, |
| {UA_JSONKEY_BODY, dst, |
| (decodeJsonSignature) Variant_decodeJsonUnwrapExtensionObject, false, NULL}}; |
| ret = decodeFields(ctx, parseCtx, entries, 2, bodyType); |
| } |
| return ret; |
| } |
| |
| DECODE_JSON(DataValue) { |
| ALLOW_NULL; |
| CHECK_OBJECT; |
| |
| DecodeEntry entries[6] = { |
| {UA_JSONKEY_VALUE, &dst->value, (decodeJsonSignature) Variant_decodeJson, false, NULL}, |
| {UA_JSONKEY_STATUS, &dst->status, (decodeJsonSignature) StatusCode_decodeJson, false, NULL}, |
| {UA_JSONKEY_SOURCETIMESTAMP, &dst->sourceTimestamp, (decodeJsonSignature) DateTime_decodeJson, false, NULL}, |
| {UA_JSONKEY_SOURCEPICOSECONDS, &dst->sourcePicoseconds, (decodeJsonSignature) UInt16_decodeJson, false, NULL}, |
| {UA_JSONKEY_SERVERTIMESTAMP, &dst->serverTimestamp, (decodeJsonSignature) DateTime_decodeJson, false, NULL}, |
| {UA_JSONKEY_SERVERPICOSECONDS, &dst->serverPicoseconds, (decodeJsonSignature) UInt16_decodeJson, false, NULL}}; |
| |
| status ret = decodeFields(ctx, parseCtx, entries, 6, type); |
| dst->hasValue = entries[0].found; dst->hasStatus = entries[1].found; |
| dst->hasSourceTimestamp = entries[2].found; dst->hasSourcePicoseconds = entries[3].found; |
| dst->hasServerTimestamp = entries[4].found; dst->hasServerPicoseconds = entries[5].found; |
| return ret; |
| } |
| |
| DECODE_JSON(ExtensionObject) { |
| ALLOW_NULL; |
| CHECK_OBJECT; |
| |
| /* Search for Encoding */ |
| size_t searchEncodingResult = 0; |
| status ret = lookAheadForKey(UA_JSONKEY_ENCODING, ctx, parseCtx, &searchEncodingResult); |
| |
| /* If no encoding found it is structure encoding */ |
| if(ret != UA_STATUSCODE_GOOD) { |
| UA_NodeId typeId; |
| UA_NodeId_init(&typeId); |
| |
| size_t searchTypeIdResult = 0; |
| ret = lookAheadForKey(UA_JSONKEY_TYPEID, ctx, parseCtx, &searchTypeIdResult); |
| if(ret != UA_STATUSCODE_GOOD) { |
| /* TYPEID not found, abort */ |
| return UA_STATUSCODE_BADENCODINGERROR; |
| } |
| |
| /* parse the nodeid */ |
| /*for restore*/ |
| UA_UInt16 index = parseCtx->index; |
| parseCtx->index = (UA_UInt16)searchTypeIdResult; |
| ret = NodeId_decodeJson(&typeId, &UA_TYPES[UA_TYPES_NODEID], ctx, parseCtx, true); |
| if(ret != UA_STATUSCODE_GOOD) |
| return ret; |
| |
| /*restore*/ |
| parseCtx->index = index; |
| const UA_DataType *typeOfBody = UA_findDataType(&typeId); |
| if(!typeOfBody) { |
| /*dont decode body: 1. save as bytestring, 2. jump over*/ |
| dst->encoding = UA_EXTENSIONOBJECT_ENCODED_BYTESTRING; |
| UA_NodeId_copy(&typeId, &dst->content.encoded.typeId); |
| |
| /*Check if Object in Extentionobject*/ |
| if(getJsmnType(parseCtx) != JSMN_OBJECT) { |
| UA_NodeId_deleteMembers(&typeId); |
| return UA_STATUSCODE_BADDECODINGERROR; |
| } |
| |
| /*Search for Body to save*/ |
| size_t searchBodyResult = 0; |
| ret = lookAheadForKey(UA_JSONKEY_BODY, ctx, parseCtx, &searchBodyResult); |
| if(ret != UA_STATUSCODE_GOOD) { |
| /*No Body*/ |
| UA_NodeId_deleteMembers(&typeId); |
| return UA_STATUSCODE_BADDECODINGERROR; |
| } |
| |
| if(searchBodyResult >= (size_t)parseCtx->tokenCount) { |
| /*index not in Tokenarray*/ |
| UA_NodeId_deleteMembers(&typeId); |
| return UA_STATUSCODE_BADDECODINGERROR; |
| } |
| |
| /* Get the size of the Object as a string, not the Object key count! */ |
| UA_Int64 sizeOfJsonString =(parseCtx->tokenArray[searchBodyResult].end - |
| parseCtx->tokenArray[searchBodyResult].start); |
| |
| char* bodyJsonString = (char*)(ctx->pos + parseCtx->tokenArray[searchBodyResult].start); |
| |
| if(sizeOfJsonString <= 0) { |
| UA_NodeId_deleteMembers(&typeId); |
| return UA_STATUSCODE_BADDECODINGERROR; |
| } |
| |
| /* Save encoded as bytestring. */ |
| ret = UA_ByteString_allocBuffer(&dst->content.encoded.body, (size_t)sizeOfJsonString); |
| if(ret != UA_STATUSCODE_GOOD) { |
| UA_NodeId_deleteMembers(&typeId); |
| return ret; |
| } |
| |
| memcpy(dst->content.encoded.body.data, bodyJsonString, (size_t)sizeOfJsonString); |
| |
| size_t tokenAfteExtensionObject = 0; |
| jumpOverObject(ctx, parseCtx, &tokenAfteExtensionObject); |
| |
| if(tokenAfteExtensionObject == 0) { |
| /*next object token not found*/ |
| UA_NodeId_deleteMembers(&typeId); |
| UA_ByteString_deleteMembers(&dst->content.encoded.body); |
| return UA_STATUSCODE_BADDECODINGERROR; |
| } |
| |
| parseCtx->index = (UA_UInt16)tokenAfteExtensionObject; |
| |
| return UA_STATUSCODE_GOOD; |
| } |
| |
| /*Type id not used anymore, typeOfBody has type*/ |
| UA_NodeId_deleteMembers(&typeId); |
| |
| /*Set Found Type*/ |
| dst->content.decoded.type = typeOfBody; |
| dst->encoding = UA_EXTENSIONOBJECT_DECODED; |
| |
| if(searchTypeIdResult != 0) { |
| dst->content.decoded.data = UA_new(typeOfBody); |
| if(!dst->content.decoded.data) |
| return UA_STATUSCODE_BADOUTOFMEMORY; |
| |
| UA_NodeId typeId_dummy; |
| DecodeEntry entries[2] = { |
| {UA_JSONKEY_TYPEID, &typeId_dummy, (decodeJsonSignature) NodeId_decodeJson, false, NULL}, |
| {UA_JSONKEY_BODY, dst->content.decoded.data, |
| (decodeJsonSignature) decodeJsonJumpTable[typeOfBody->typeKind], false, NULL} |
| }; |
| |
| return decodeFields(ctx, parseCtx, entries, 2, typeOfBody); |
| } else { |
| return UA_STATUSCODE_BADDECODINGERROR; |
| } |
| } else { /* UA_JSONKEY_ENCODING found */ |
| /*Parse the encoding*/ |
| UA_UInt64 encoding = 0; |
| char *extObjEncoding = (char*)(ctx->pos + parseCtx->tokenArray[searchEncodingResult].start); |
| size_t size = (size_t)(parseCtx->tokenArray[searchEncodingResult].end - parseCtx->tokenArray[searchEncodingResult].start); |
| atoiUnsigned(extObjEncoding, size, &encoding); |
| |
| if(encoding == 1) { |
| /* BYTESTRING in Json Body */ |
| dst->encoding = UA_EXTENSIONOBJECT_ENCODED_BYTESTRING; |
| UA_UInt16 encodingTypeJson; |
| DecodeEntry entries[3] = { |
| {UA_JSONKEY_ENCODING, &encodingTypeJson, (decodeJsonSignature) UInt16_decodeJson, false, NULL}, |
| {UA_JSONKEY_BODY, &dst->content.encoded.body, (decodeJsonSignature) String_decodeJson, false, NULL}, |
| {UA_JSONKEY_TYPEID, &dst->content.encoded.typeId, (decodeJsonSignature) NodeId_decodeJson, false, NULL} |
| }; |
| |
| return decodeFields(ctx, parseCtx, entries, 3, type); |
| } else if(encoding == 2) { |
| /* XmlElement in Json Body */ |
| dst->encoding = UA_EXTENSIONOBJECT_ENCODED_XML; |
| UA_UInt16 encodingTypeJson; |
| DecodeEntry entries[3] = { |
| {UA_JSONKEY_ENCODING, &encodingTypeJson, (decodeJsonSignature) UInt16_decodeJson, false, NULL}, |
| {UA_JSONKEY_BODY, &dst->content.encoded.body, (decodeJsonSignature) String_decodeJson, false, NULL}, |
| {UA_JSONKEY_TYPEID, &dst->content.encoded.typeId, (decodeJsonSignature) NodeId_decodeJson, false, NULL} |
| }; |
| return decodeFields(ctx, parseCtx, entries, 3, type); |
| } else { |
| return UA_STATUSCODE_BADDECODINGERROR; |
| } |
| } |
| return UA_STATUSCODE_BADNOTIMPLEMENTED; |
| } |
| |
| static status |
| Variant_decodeJsonUnwrapExtensionObject(UA_Variant *dst, const UA_DataType *type, |
| CtxJson *ctx, ParseCtx *parseCtx, UA_Boolean moveToken) { |
| (void) type, (void) moveToken; |
| /*EXTENSIONOBJECT POSITION!*/ |
| UA_UInt16 old_index = parseCtx->index; |
| UA_Boolean typeIdFound; |
| |
| /* Decode the DataType */ |
| UA_NodeId typeId; |
| UA_NodeId_init(&typeId); |
| |
| size_t searchTypeIdResult = 0; |
| status ret = lookAheadForKey(UA_JSONKEY_TYPEID, ctx, parseCtx, &searchTypeIdResult); |
| |
| if(ret != UA_STATUSCODE_GOOD) { |
| /*No Typeid found*/ |
| typeIdFound = false; |
| /*return UA_STATUSCODE_BADDECODINGERROR;*/ |
| } else { |
| typeIdFound = true; |
| /* parse the nodeid */ |
| parseCtx->index = (UA_UInt16)searchTypeIdResult; |
| ret = NodeId_decodeJson(&typeId, &UA_TYPES[UA_TYPES_NODEID], ctx, parseCtx, true); |
| if(ret != UA_STATUSCODE_GOOD) { |
| UA_NodeId_deleteMembers(&typeId); |
| return ret; |
| } |
| |
| /*restore index, ExtensionObject position*/ |
| parseCtx->index = old_index; |
| } |
| |
| /* ---Decode the EncodingByte--- */ |
| if(!typeIdFound) |
| return UA_STATUSCODE_BADDECODINGERROR; |
| |
| UA_Boolean encodingFound = false; |
| /*Search for Encoding*/ |
| size_t searchEncodingResult = 0; |
| ret = lookAheadForKey(UA_JSONKEY_ENCODING, ctx, parseCtx, &searchEncodingResult); |
| |
| UA_UInt64 encoding = 0; |
| /*If no encoding found it is Structure encoding*/ |
| if(ret == UA_STATUSCODE_GOOD) { /*FOUND*/ |
| encodingFound = true; |
| char *extObjEncoding = (char*)(ctx->pos + parseCtx->tokenArray[searchEncodingResult].start); |
| size_t size = (size_t)(parseCtx->tokenArray[searchEncodingResult].end |
| - parseCtx->tokenArray[searchEncodingResult].start); |
| atoiUnsigned(extObjEncoding, size, &encoding); |
| } |
| |
| const UA_DataType *typeOfBody = UA_findDataType(&typeId); |
| |
| if(encoding == 0 || typeOfBody != NULL) { |
| /*This value is 0 if the body is Structure encoded as a JSON object (see 5.4.6).*/ |
| /* Found a valid type and it is structure encoded so it can be unwrapped */ |
| dst->type = typeOfBody; |
| |
| /* Allocate memory for type*/ |
| dst->data = UA_new(dst->type); |
| if(!dst->data) { |
| UA_NodeId_deleteMembers(&typeId); |
| return UA_STATUSCODE_BADOUTOFMEMORY; |
| } |
| |
| /* Decode the content */ |
| UA_NodeId nodeIddummy; |
| DecodeEntry entries[3] = |
| { |
| {UA_JSONKEY_TYPEID, &nodeIddummy, (decodeJsonSignature) NodeId_decodeJson, false, NULL}, |
| {UA_JSONKEY_BODY, dst->data, |
| (decodeJsonSignature) decodeJsonJumpTable[dst->type->typeKind], false, NULL}, |
| {UA_JSONKEY_ENCODING, NULL, NULL, false, NULL}}; |
| |
| ret = decodeFields(ctx, parseCtx, entries, encodingFound ? 3:2, typeOfBody); |
| if(ret != UA_STATUSCODE_GOOD) { |
| UA_free(dst->data); |
| } |
| } else if(encoding == 1 || encoding == 2 || typeOfBody == NULL) { |
| UA_NodeId_deleteMembers(&typeId); |
| |
| /* decode as ExtensionObject */ |
| dst->type = &UA_TYPES[UA_TYPES_EXTENSIONOBJECT]; |
| |
| /* Allocate memory for extensionobject*/ |
| dst->data = UA_new(dst->type); |
| if(!dst->data) |
| return UA_STATUSCODE_BADOUTOFMEMORY; |
| |
| /* decode: Does not move tokenindex. */ |
| ret = DECODE_DIRECT_JSON(dst->data, ExtensionObject); |
| if(ret != UA_STATUSCODE_GOOD) |
| UA_free(dst->data); |
| } else { |
| /*no recognized encoding type*/ |
| return UA_STATUSCODE_BADDECODINGERROR; |
| } |
| return ret; |
| } |
| status DiagnosticInfoInner_decodeJson(void* dst, const UA_DataType* type, |
| CtxJson* ctx, ParseCtx* parseCtx, UA_Boolean moveToken); |
| |
| DECODE_JSON(DiagnosticInfo) { |
| ALLOW_NULL; |
| CHECK_OBJECT; |
| |
| DecodeEntry entries[7] = { |
| {UA_JSONKEY_SYMBOLICID, &dst->symbolicId, (decodeJsonSignature) Int32_decodeJson, false, NULL}, |
| {UA_JSONKEY_NAMESPACEURI, &dst->namespaceUri, (decodeJsonSignature) Int32_decodeJson, false, NULL}, |
| {UA_JSONKEY_LOCALIZEDTEXT, &dst->localizedText, (decodeJsonSignature) Int32_decodeJson, false, NULL}, |
| {UA_JSONKEY_LOCALE, &dst->locale, (decodeJsonSignature) Int32_decodeJson, false, NULL}, |
| {UA_JSONKEY_ADDITIONALINFO, &dst->additionalInfo, (decodeJsonSignature) String_decodeJson, false, NULL}, |
| {UA_JSONKEY_INNERSTATUSCODE, &dst->innerStatusCode, (decodeJsonSignature) StatusCode_decodeJson, false, NULL}, |
| {UA_JSONKEY_INNERDIAGNOSTICINFO, &dst->innerDiagnosticInfo, (decodeJsonSignature) DiagnosticInfoInner_decodeJson, false, NULL}}; |
| status ret = decodeFields(ctx, parseCtx, entries, 7, type); |
| |
| dst->hasSymbolicId = entries[0].found; dst->hasNamespaceUri = entries[1].found; |
| dst->hasLocalizedText = entries[2].found; dst->hasLocale = entries[3].found; |
| dst->hasAdditionalInfo = entries[4].found; dst->hasInnerStatusCode = entries[5].found; |
| dst->hasInnerDiagnosticInfo = entries[6].found; |
| return ret; |
| } |
| |
| status |
| DiagnosticInfoInner_decodeJson(void* dst, const UA_DataType* type, |
| CtxJson* ctx, ParseCtx* parseCtx, UA_Boolean moveToken) { |
| UA_DiagnosticInfo *inner = (UA_DiagnosticInfo*)UA_calloc(1, sizeof(UA_DiagnosticInfo)); |
| if(inner == NULL) { |
| return UA_STATUSCODE_BADOUTOFMEMORY; |
| } |
| memcpy(dst, &inner, sizeof(UA_DiagnosticInfo*)); /* Copy new Pointer do dest */ |
| return DiagnosticInfo_decodeJson(inner, type, ctx, parseCtx, moveToken); |
| } |
| |
| status |
| decodeFields(CtxJson *ctx, ParseCtx *parseCtx, DecodeEntry *entries, |
| size_t entryCount, const UA_DataType *type) { |
| CHECK_TOKEN_BOUNDS; |
| size_t objectCount = (size_t)(parseCtx->tokenArray[parseCtx->index].size); |
| status ret = UA_STATUSCODE_GOOD; |
| |
| if(entryCount == 1) { |
| if(*(entries[0].fieldName) == 0) { /*No MemberName*/ |
| return entries[0].function(entries[0].fieldPointer, type, |
| ctx, parseCtx, true); /*ENCODE DIRECT*/ |
| } |
| } else if(entryCount == 0) { |
| return UA_STATUSCODE_BADDECODINGERROR; |
| } |
| |
| parseCtx->index++; /*go to first key*/ |
| CHECK_TOKEN_BOUNDS; |
| |
| for (size_t currentObjectCount = 0; currentObjectCount < objectCount && |
| parseCtx->index < parseCtx->tokenCount; currentObjectCount++) { |
| |
| /* start searching at the index of currentObjectCount */ |
| for (size_t i = currentObjectCount; i < entryCount + currentObjectCount; i++) { |
| /* Search for KEY, if found outer loop will be one less. Best case |
| * is objectCount if in order! */ |
| size_t index = i % entryCount; |
| |
| CHECK_TOKEN_BOUNDS; |
| if(jsoneq((char*) ctx->pos, &parseCtx->tokenArray[parseCtx->index], |
| entries[index].fieldName) != 0) |
| continue; |
| |
| if(entries[index].found) { |
| /*Duplicate Key found, abort.*/ |
| return UA_STATUSCODE_BADDECODINGERROR; |
| } |
| |
| entries[index].found = true; |
| |
| parseCtx->index++; /*goto value*/ |
| CHECK_TOKEN_BOUNDS; |
| |
| /* Find the data type. |
| * TODO: get rid of parameter type. Only forward via DecodeEntry. |
| */ |
| const UA_DataType *membertype = type; |
| if(entries[index].type) |
| membertype = entries[index].type; |
| |
| if(entries[index].function != NULL) { |
| ret = entries[index].function(entries[index].fieldPointer, |
| membertype, ctx, parseCtx, true); /*Move Token True*/ |
| if(ret != UA_STATUSCODE_GOOD) |
| return ret; |
| } else { |
| /*overstep single value, this will not work if object or array |
| Only used not to double parse pre looked up type, but it has to be overstepped*/ |
| parseCtx->index++; |
| } |
| break; |
| } |
| } |
| return ret; |
| } |
| |
| static status |
| Array_decodeJson_internal(void **dst, const UA_DataType *type, |
| CtxJson *ctx, ParseCtx *parseCtx, UA_Boolean moveToken) { |
| (void) moveToken; |
| status ret; |
| |
| if(parseCtx->tokenArray[parseCtx->index].type != JSMN_ARRAY) |
| return UA_STATUSCODE_BADDECODINGERROR; |
| |
| size_t length = (size_t)parseCtx->tokenArray[parseCtx->index].size; |
| |
| /* Save the length of the array */ |
| size_t *p = (size_t*) dst - 1; |
| *p = length; |
| |
| /* Return early for empty arrays */ |
| if(length == 0) { |
| *dst = UA_EMPTY_ARRAY_SENTINEL; |
| return UA_STATUSCODE_GOOD; |
| } |
| |
| /* Allocate memory */ |
| *dst = UA_calloc(length, type->memSize); |
| if(*dst == NULL) |
| return UA_STATUSCODE_BADOUTOFMEMORY; |
| |
| parseCtx->index++; /* We go to first Array member!*/ |
| |
| /* Decode array members */ |
| uintptr_t ptr = (uintptr_t)*dst; |
| for(size_t i = 0; i < length; ++i) { |
| ret = decodeJsonJumpTable[type->typeKind]((void*)ptr, type, ctx, parseCtx, true); |
| if(ret != UA_STATUSCODE_GOOD) { |
| UA_Array_delete(*dst, i+1, type); |
| *dst = NULL; |
| return ret; |
| } |
| ptr += type->memSize; |
| } |
| return UA_STATUSCODE_GOOD; |
| } |
| |
| /*Wrapper for array with valid decodingStructure.*/ |
| static status |
| Array_decodeJson(void * dst, const UA_DataType *type, CtxJson *ctx, |
| ParseCtx *parseCtx, UA_Boolean moveToken) { |
| return Array_decodeJson_internal((void **)dst, type, ctx, parseCtx, moveToken); |
| } |
| |
| static status |
| decodeJsonStructure(void *dst, const UA_DataType *type, CtxJson *ctx, |
| ParseCtx *parseCtx, UA_Boolean moveToken) { |
| (void) moveToken; |
| /* Check the recursion limit */ |
| if(ctx->depth > UA_JSON_ENCODING_MAX_RECURSION) |
| return UA_STATUSCODE_BADENCODINGERROR; |
| ctx->depth++; |
| |
| uintptr_t ptr = (uintptr_t)dst; |
| status ret = UA_STATUSCODE_GOOD; |
| u8 membersSize = type->membersSize; |
| const UA_DataType *typelists[2] = { UA_TYPES, &type[-type->typeIndex] }; |
| |
| UA_STACKARRAY(DecodeEntry, entries, membersSize); |
| |
| for(size_t i = 0; i < membersSize && ret == UA_STATUSCODE_GOOD; ++i) { |
| const UA_DataTypeMember *m = &type->members[i]; |
| const UA_DataType *mt = &typelists[!m->namespaceZero][m->memberTypeIndex]; |
| |
| entries[i].type = mt; |
| if(!m->isArray) { |
| ptr += m->padding; |
| entries[i].fieldName = m->memberName; |
| entries[i].fieldPointer = (void*)ptr; |
| entries[i].function = decodeJsonJumpTable[mt->typeKind]; |
| entries[i].found = false; |
| ptr += mt->memSize; |
| } else { |
| ptr += m->padding; |
| ptr += sizeof(size_t); |
| entries[i].fieldName = m->memberName; |
| entries[i].fieldPointer = (void*)ptr; |
| entries[i].function = (decodeJsonSignature)Array_decodeJson; |
| entries[i].found = false; |
| ptr += sizeof(void*); |
| } |
| } |
| |
| ret = decodeFields(ctx, parseCtx, entries, membersSize, type); |
| |
| ctx->depth--; |
| return ret; |
| } |
| |
| static status |
| decodeJsonNotImplemented(void *dst, const UA_DataType *type, CtxJson *ctx, |
| ParseCtx *parseCtx, UA_Boolean moveToken) { |
| (void)dst, (void)type, (void)ctx, (void)parseCtx, (void)moveToken; |
| return UA_STATUSCODE_BADNOTIMPLEMENTED; |
| } |
| |
| const decodeJsonSignature decodeJsonJumpTable[UA_DATATYPEKINDS] = { |
| (decodeJsonSignature)Boolean_decodeJson, |
| (decodeJsonSignature)SByte_decodeJson, /* SByte */ |
| (decodeJsonSignature)Byte_decodeJson, |
| (decodeJsonSignature)Int16_decodeJson, /* Int16 */ |
| (decodeJsonSignature)UInt16_decodeJson, |
| (decodeJsonSignature)Int32_decodeJson, /* Int32 */ |
| (decodeJsonSignature)UInt32_decodeJson, |
| (decodeJsonSignature)Int64_decodeJson, /* Int64 */ |
| (decodeJsonSignature)UInt64_decodeJson, |
| (decodeJsonSignature)Float_decodeJson, |
| (decodeJsonSignature)Double_decodeJson, |
| (decodeJsonSignature)String_decodeJson, |
| (decodeJsonSignature)DateTime_decodeJson, /* DateTime */ |
| (decodeJsonSignature)Guid_decodeJson, |
| (decodeJsonSignature)ByteString_decodeJson, /* ByteString */ |
| (decodeJsonSignature)String_decodeJson, /* XmlElement */ |
| (decodeJsonSignature)NodeId_decodeJson, |
| (decodeJsonSignature)ExpandedNodeId_decodeJson, |
| (decodeJsonSignature)StatusCode_decodeJson, /* StatusCode */ |
| (decodeJsonSignature)QualifiedName_decodeJson, /* QualifiedName */ |
| (decodeJsonSignature)LocalizedText_decodeJson, |
| (decodeJsonSignature)ExtensionObject_decodeJson, |
| (decodeJsonSignature)DataValue_decodeJson, |
| (decodeJsonSignature)Variant_decodeJson, |
| (decodeJsonSignature)DiagnosticInfo_decodeJson, |
| (decodeJsonSignature)decodeJsonNotImplemented, /* Decimal */ |
| (decodeJsonSignature)Int32_decodeJson, /* Enum */ |
| (decodeJsonSignature)decodeJsonStructure, |
| (decodeJsonSignature)decodeJsonNotImplemented, /* Structure with optional fields */ |
| (decodeJsonSignature)decodeJsonNotImplemented, /* Union */ |
| (decodeJsonSignature)decodeJsonNotImplemented /* BitfieldCluster */ |
| }; |
| |
| decodeJsonSignature getDecodeSignature(u8 index) { |
| return decodeJsonJumpTable[index]; |
| } |
| |
| status |
| tokenize(ParseCtx *parseCtx, CtxJson *ctx, const UA_ByteString *src) { |
| /* Set up the context */ |
| ctx->pos = &src->data[0]; |
| ctx->end = &src->data[src->length]; |
| ctx->depth = 0; |
| parseCtx->tokenCount = 0; |
| parseCtx->index = 0; |
| |
| /*Set up tokenizer jsmn*/ |
| jsmn_parser p; |
| jsmn_init(&p); |
| parseCtx->tokenCount = (UA_Int32) |
| jsmn_parse(&p, (char*)src->data, src->length, |
| parseCtx->tokenArray, UA_JSON_MAXTOKENCOUNT); |
| |
| if(parseCtx->tokenCount < 0) { |
| if(parseCtx->tokenCount == JSMN_ERROR_NOMEM) |
| return UA_STATUSCODE_BADOUTOFMEMORY; |
| return UA_STATUSCODE_BADDECODINGERROR; |
| } |
| |
| return UA_STATUSCODE_GOOD; |
| } |
| |
| UA_StatusCode |
| decodeJsonInternal(void *dst, const UA_DataType *type, |
| CtxJson *ctx, ParseCtx *parseCtx, UA_Boolean moveToken) { |
| |
| return decodeJsonJumpTable[type->typeKind](dst, type, ctx, parseCtx, moveToken); |
| } |
| |
| status UA_FUNC_ATTR_WARN_UNUSED_RESULT |
| UA_decodeJson(const UA_ByteString *src, void *dst, const UA_DataType *type) { |
| |
| #ifndef UA_ENABLE_TYPEDESCRIPTION |
| return UA_STATUSCODE_BADNOTSUPPORTED; |
| #endif |
| |
| if(dst == NULL || src == NULL || type == NULL) { |
| return UA_STATUSCODE_BADARGUMENTSMISSING; |
| } |
| |
| /* Set up the context */ |
| CtxJson ctx; |
| ParseCtx parseCtx; |
| parseCtx.tokenArray = (jsmntok_t*)UA_malloc(sizeof(jsmntok_t) * UA_JSON_MAXTOKENCOUNT); |
| if(!parseCtx.tokenArray) |
| return UA_STATUSCODE_BADOUTOFMEMORY; |
| |
| status ret = tokenize(&parseCtx, &ctx, src); |
| if(ret != UA_STATUSCODE_GOOD) |
| goto cleanup; |
| |
| /* Assume the top-level element is an object */ |
| if(parseCtx.tokenCount < 1 || parseCtx.tokenArray[0].type != JSMN_OBJECT) { |
| if(parseCtx.tokenCount == 1) { |
| if(parseCtx.tokenArray[0].type == JSMN_PRIMITIVE || |
| parseCtx.tokenArray[0].type == JSMN_STRING) { |
| /* Only a primitive to parse. Do it directly. */ |
| memset(dst, 0, type->memSize); /* Initialize the value */ |
| ret = decodeJsonJumpTable[type->typeKind](dst, type, &ctx, &parseCtx, true); |
| goto cleanup; |
| } |
| } |
| ret = UA_STATUSCODE_BADDECODINGERROR; |
| goto cleanup; |
| } |
| |
| /* Decode */ |
| memset(dst, 0, type->memSize); /* Initialize the value */ |
| ret = decodeJsonJumpTable[type->typeKind](dst, type, &ctx, &parseCtx, true); |
| |
| cleanup: |
| UA_free(parseCtx.tokenArray); |
| |
| /* sanity check if all Tokens were processed */ |
| if(!(parseCtx.index == parseCtx.tokenCount || |
| parseCtx.index == parseCtx.tokenCount-1)) { |
| ret = UA_STATUSCODE_BADDECODINGERROR; |
| } |
| |
| if(ret != UA_STATUSCODE_GOOD) |
| UA_deleteMembers(dst, type); /* Clean up */ |
| return ret; |
| } |