/* 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 #include #include "ua_types_encoding_binary.h" #include #include #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) /* If parseCtx->index points to the beginning of an object, move the index to * the next token after this object. Attention! The index can be moved after the * last parsed token. So the array length has to be checked afterwards. */ static void skipObject(ParseCtx *parseCtx) { int end = parseCtx->tokenArray[parseCtx->index].end; do { parseCtx->index++; } while(parseCtx->index < parseCtx->tokenCount && parseCtx->tokenArray[parseCtx->index].start < end); } 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; } 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!*/ if (parseCtx->index >= parseCtx->tokenCount) /* We got invalid json. See https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=14620 */ return UA_STATUSCODE_BADOUTOFRANGE; *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) { skipObject(parseCtx); return UA_STATUSCODE_GOOD; } size_t size = ((size_t)parseCtx->tokenArray[searchResultType].end - (size_t)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); if(typeDecodeStatus != UA_STATUSCODE_GOOD) return typeDecodeStatus; /* A NULL Variant */ if(idTypeDecoded == 0) { skipObject(parseCtx); return UA_STATUSCODE_GOOD; } /* Set the type */ UA_NodeId typeNodeId = UA_NODEID_NUMERIC(0, (UA_UInt32)idTypeDecoded); dst->type = UA_findDataType(&typeNodeId); if(!dst->type) return UA_STATUSCODE_BADDECODINGERROR; /* Search for body */ size_t searchResultBody = 0; ret = lookAheadForKey(UA_JSONKEY_BODY, ctx, parseCtx, &searchResultBody); if(ret != UA_STATUSCODE_GOOD) { /*TODO: no body? set value NULL?*/ return UA_STATUSCODE_BADDECODINGERROR; } /* value is an array? */ UA_Boolean isArray = false; if(parseCtx->tokenArray[searchResultBody].type == JSMN_ARRAY) { isArray = true; dst->arrayLength = (size_t)parseCtx->tokenArray[searchResultBody].size; } /* Has the variant dimension? */ UA_Boolean hasDimension = false; size_t searchResultDim = 0; ret = lookAheadForKey(UA_JSONKEY_DIMENSION, ctx, parseCtx, &searchResultDim); if(ret == UA_STATUSCODE_GOOD) { hasDimension = true; dst->arrayDimensionsSize = (size_t)parseCtx->tokenArray[searchResultDim].size; } /* 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(dst->type->typeKind > UA_TYPES_DIAGNOSTICINFO) return UA_STATUSCODE_BADDECODINGERROR; /* A variant cannot contain a variant. But it can contain an array of * variants */ if(dst->type->typeKind == UA_DATATYPEKIND_VARIANT && !isArray) return UA_STATUSCODE_BADDECODINGERROR; /* Decode an array */ 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, dst->type); /*use first 2 fields*/ } else { ret = decodeFields(ctx, parseCtx, entries, 3, dst->type); /*use all fields*/ } return ret; } /* Decode a value wrapped in an ExtensionObject */ if(dst->type->typeKind == UA_DATATYPEKIND_EXTENSIONOBJECT) { DecodeEntry entries[2] = {{UA_JSONKEY_TYPE, NULL, NULL, false, NULL}, {UA_JSONKEY_BODY, dst, (decodeJsonSignature)Variant_decodeJsonUnwrapExtensionObject, false, NULL}}; return decodeFields(ctx, parseCtx, entries, 2, dst->type); } /* Allocate Memory for Body */ dst->data = UA_new(dst->type); 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}}; return decodeFields(ctx, parseCtx, entries, 2, dst->type); } 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 */ if (typeOfBody == NULL) return UA_STATUSCODE_BADDECODINGERROR; 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_TYPENAMES 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; }