|
@@ -0,0 +1,535 @@
|
|
|
+/* 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 (c) 2019 Fraunhofer IOSB (Author: Lukas Meling)
|
|
|
+ */
|
|
|
+
|
|
|
+#include "ua_types.h"
|
|
|
+#include "ua_types_generated_handling.h"
|
|
|
+#include "ua_types_encoding_json.h"
|
|
|
+#include "ua_pubsub_networkmessage.h"
|
|
|
+
|
|
|
+/* Json keys for dsm */
|
|
|
+const char * UA_DECODEKEY_MESSAGES = ("Messages");
|
|
|
+const char * UA_DECODEKEY_MESSAGETYPE = ("MessageType");
|
|
|
+const char * UA_DECODEKEY_MESSAGEID = ("MessageId");
|
|
|
+const char * UA_DECODEKEY_PUBLISHERID = ("PublisherId");
|
|
|
+const char * UA_DECODEKEY_DATASETCLASSID = ("DataSetClassId");
|
|
|
+
|
|
|
+/* Json keys for dsm */
|
|
|
+const char * UA_DECODEKEY_DATASETWRITERID = ("DataSetWriterId");
|
|
|
+const char * UA_DECODEKEY_SEQUENCENUMBER = ("SequenceNumber");
|
|
|
+const char * UA_DECODEKEY_METADATAVERSION = ("MetaDataVersion");
|
|
|
+const char * UA_DECODEKEY_TIMESTAMP = ("Timestamp");
|
|
|
+const char * UA_DECODEKEY_DSM_STATUS = ("Status");
|
|
|
+const char * UA_DECODEKEY_PAYLOAD = ("Payload");
|
|
|
+const char * UA_DECODEKEY_DS_TYPE = ("Type");
|
|
|
+
|
|
|
+/* -- json encoding/decoding -- */
|
|
|
+static UA_StatusCode writeJsonKey_UA_String(CtxJson *ctx, UA_String *in){
|
|
|
+ UA_STACKARRAY(char, out, in->length + 1);
|
|
|
+ memcpy(out, in->data, in->length);
|
|
|
+ out[in->length] = 0;
|
|
|
+ return writeJsonKey(ctx, out);
|
|
|
+}
|
|
|
+
|
|
|
+static UA_StatusCode
|
|
|
+UA_DataSetMessage_encodeJson_internal(const UA_DataSetMessage* src, UA_UInt16 dataSetWriterId, CtxJson *ctx){
|
|
|
+ status rv = writeJsonObjStart(ctx);
|
|
|
+
|
|
|
+ /* DataSetWriterId */
|
|
|
+ rv |= writeJsonKey(ctx, UA_DECODEKEY_DATASETWRITERID);
|
|
|
+ rv |= encodeJsonInternal(&dataSetWriterId, &UA_TYPES[UA_TYPES_UINT16], ctx);
|
|
|
+ if(rv != UA_STATUSCODE_GOOD)
|
|
|
+ return rv;
|
|
|
+
|
|
|
+ /* DataSetMessageSequenceNr */
|
|
|
+ if(src->header.dataSetMessageSequenceNrEnabled) {
|
|
|
+ rv |= writeJsonKey(ctx, UA_DECODEKEY_SEQUENCENUMBER);
|
|
|
+ rv |= encodeJsonInternal(&(src->header.dataSetMessageSequenceNr), &UA_TYPES[UA_TYPES_UINT16], ctx);
|
|
|
+ if(rv != UA_STATUSCODE_GOOD)
|
|
|
+ return rv;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* MetaDataVersion */
|
|
|
+ if(src->header.configVersionMajorVersionEnabled || src->header.configVersionMinorVersionEnabled) {
|
|
|
+ rv |= writeJsonKey(ctx, UA_DECODEKEY_METADATAVERSION);
|
|
|
+ UA_ConfigurationVersionDataType cvd;
|
|
|
+ cvd.majorVersion = src->header.configVersionMajorVersion;
|
|
|
+ cvd.minorVersion = src->header.configVersionMinorVersion;
|
|
|
+ rv |= encodeJsonInternal(&cvd, &UA_TYPES[UA_TYPES_CONFIGURATIONVERSIONDATATYPE], ctx);
|
|
|
+ if(rv != UA_STATUSCODE_GOOD)
|
|
|
+ return rv;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Timestamp */
|
|
|
+ if(src->header.timestampEnabled) {
|
|
|
+ rv |= writeJsonKey(ctx, UA_DECODEKEY_TIMESTAMP);
|
|
|
+ rv |= encodeJsonInternal(&(src->header.timestamp), &UA_TYPES[UA_TYPES_DATETIME], ctx);
|
|
|
+ if(rv != UA_STATUSCODE_GOOD)
|
|
|
+ return rv;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Status */
|
|
|
+ if(src->header.statusEnabled) {
|
|
|
+ rv |= writeJsonKey(ctx, UA_DECODEKEY_DSM_STATUS);
|
|
|
+ rv |= encodeJsonInternal(&(src->header.status), &UA_TYPES[UA_TYPES_STATUSCODE], ctx);
|
|
|
+ if(rv != UA_STATUSCODE_GOOD)
|
|
|
+ return rv;
|
|
|
+ }
|
|
|
+
|
|
|
+ rv |= writeJsonKey(ctx, UA_DECODEKEY_PAYLOAD);
|
|
|
+ rv |= writeJsonObjStart(ctx);
|
|
|
+
|
|
|
+ /* TODO: currently no difference between delta and key frames. Own dataSetMessageType for json?*/
|
|
|
+ if(src->header.dataSetMessageType == UA_DATASETMESSAGE_DATAKEYFRAME) {
|
|
|
+ if(src->data.keyFrameData.fieldNames == NULL){
|
|
|
+ return UA_STATUSCODE_BADENCODINGERROR;
|
|
|
+ }
|
|
|
+
|
|
|
+ if(src->header.fieldEncoding == UA_FIELDENCODING_VARIANT) {
|
|
|
+ /* KEYFRAME VARIANT */
|
|
|
+ for (UA_UInt16 i = 0; i < src->data.keyFrameData.fieldCount; i++) {
|
|
|
+ rv |= writeJsonKey_UA_String(ctx, &src->data.keyFrameData.fieldNames[i]);
|
|
|
+ rv |= encodeJsonInternal(&(src->data.keyFrameData.dataSetFields[i].value),
|
|
|
+ &UA_TYPES[UA_TYPES_VARIANT], ctx);
|
|
|
+ if(rv != UA_STATUSCODE_GOOD)
|
|
|
+ return rv;
|
|
|
+ }
|
|
|
+ } else if(src->header.fieldEncoding == UA_FIELDENCODING_RAWDATA) {
|
|
|
+ return UA_STATUSCODE_BADNOTIMPLEMENTED;
|
|
|
+ } else if(src->header.fieldEncoding == UA_FIELDENCODING_DATAVALUE) {
|
|
|
+ /* KEYFRAME DATAVALUE */
|
|
|
+ for (UA_UInt16 i = 0; i < src->data.keyFrameData.fieldCount; i++) {
|
|
|
+ rv |= writeJsonKey_UA_String(ctx, &src->data.keyFrameData.fieldNames[i]);
|
|
|
+ rv |= encodeJsonInternal(&(src->data.keyFrameData.dataSetFields[i]), &UA_TYPES[UA_TYPES_DATAVALUE], ctx);
|
|
|
+ if(rv != UA_STATUSCODE_GOOD)
|
|
|
+ return rv;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }else if(src->header.dataSetMessageType == UA_DATASETMESSAGE_DATADELTAFRAME){
|
|
|
+ return UA_STATUSCODE_BADNOTSUPPORTED;
|
|
|
+ }
|
|
|
+ rv |= writeJsonObjEnd(ctx); /* Payload */
|
|
|
+ rv |= writeJsonObjEnd(ctx); /* DataSetMessage */
|
|
|
+ return rv;
|
|
|
+}
|
|
|
+
|
|
|
+static UA_StatusCode
|
|
|
+UA_NetworkMessage_encodeJson_internal(const UA_NetworkMessage* src, CtxJson *ctx) {
|
|
|
+ status rv = UA_STATUSCODE_GOOD;
|
|
|
+ /* currently only ua-data is supported */
|
|
|
+ if(src->networkMessageType == UA_NETWORKMESSAGE_DATASET) {
|
|
|
+ writeJsonObjStart(ctx);
|
|
|
+
|
|
|
+ /* Table 91 – JSON NetworkMessage Definition
|
|
|
+ MessageId | String | A globally unique identifier for the message.
|
|
|
+ This value is mandatory. */
|
|
|
+ rv |= writeJsonKey(ctx, UA_DECODEKEY_MESSAGEID);
|
|
|
+ /* if a messageId is given use it, otherwise generate a GUID(-string). MessageId is always decoded as a string. */
|
|
|
+ if(src->messageIdEnabled){
|
|
|
+ rv |= encodeJsonInternal(&src->messageId, &UA_TYPES[UA_TYPES_STRING], ctx);
|
|
|
+ }else{
|
|
|
+ UA_Guid guid = UA_Guid_random();
|
|
|
+ rv |= encodeJsonInternal(&guid, &UA_TYPES[UA_TYPES_GUID], ctx);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* MessageType */
|
|
|
+ rv |= writeJsonKey(ctx, UA_DECODEKEY_MESSAGETYPE);
|
|
|
+ UA_String s = UA_STRING("ua-data");
|
|
|
+ rv |= encodeJsonInternal(&s, &UA_TYPES[UA_TYPES_STRING], ctx);
|
|
|
+
|
|
|
+ /* PublisherId */
|
|
|
+ if(src->publisherIdEnabled) {
|
|
|
+ rv = writeJsonKey(ctx, UA_DECODEKEY_PUBLISHERID);
|
|
|
+ switch (src->publisherIdType) {
|
|
|
+ case UA_PUBLISHERDATATYPE_BYTE:
|
|
|
+ rv |= encodeJsonInternal(&src->publisherId.publisherIdByte, &UA_TYPES[UA_TYPES_BYTE], ctx);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case UA_PUBLISHERDATATYPE_UINT16:
|
|
|
+ rv |= encodeJsonInternal(&src->publisherId.publisherIdUInt16, &UA_TYPES[UA_TYPES_UINT16], ctx);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case UA_PUBLISHERDATATYPE_UINT32:
|
|
|
+ rv |= encodeJsonInternal(&src->publisherId.publisherIdUInt32, &UA_TYPES[UA_TYPES_UINT32], ctx);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case UA_PUBLISHERDATATYPE_UINT64:
|
|
|
+ rv |= encodeJsonInternal(&src->publisherId.publisherIdUInt64, &UA_TYPES[UA_TYPES_UINT64], ctx);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case UA_PUBLISHERDATATYPE_STRING:
|
|
|
+ rv |= encodeJsonInternal(&src->publisherId.publisherIdString, &UA_TYPES[UA_TYPES_STRING], ctx);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if(rv != UA_STATUSCODE_GOOD)
|
|
|
+ return rv;
|
|
|
+
|
|
|
+ /* DataSetClassId */
|
|
|
+ if(src->dataSetClassIdEnabled) {
|
|
|
+ rv |= writeJsonKey(ctx, UA_DECODEKEY_DATASETCLASSID);
|
|
|
+ rv |= encodeJsonInternal(&src->dataSetClassId, &UA_TYPES[UA_TYPES_GUID], ctx);
|
|
|
+ if(rv != UA_STATUSCODE_GOOD)
|
|
|
+ return rv;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Payload: DataSetMessages */
|
|
|
+ UA_Byte count = src->payloadHeader.dataSetPayloadHeader.count;
|
|
|
+ if(count > 0){
|
|
|
+ UA_UInt16 *dataSetWriterIds = src->payloadHeader.dataSetPayloadHeader.dataSetWriterIds;
|
|
|
+ if(!dataSetWriterIds){
|
|
|
+ return UA_STATUSCODE_BADENCODINGERROR;
|
|
|
+ }
|
|
|
+
|
|
|
+ rv |= writeJsonKey(ctx, UA_DECODEKEY_MESSAGES);
|
|
|
+ rv |= writeJsonArrStart(ctx); /* start array */
|
|
|
+
|
|
|
+ for (UA_UInt16 i = 0; i < count; i++) {
|
|
|
+ writeJsonCommaIfNeeded(ctx);
|
|
|
+ rv |= UA_DataSetMessage_encodeJson_internal(&(src->payload.dataSetPayload.dataSetMessages[i]), dataSetWriterIds[i], ctx);
|
|
|
+ if(rv != UA_STATUSCODE_GOOD)
|
|
|
+ return rv;
|
|
|
+ /* comma is needed if more dsm are present */
|
|
|
+ ctx->commaNeeded[ctx->depth] = true;
|
|
|
+ }
|
|
|
+ rv |= writeJsonArrEnd(ctx); /* end array */
|
|
|
+ }
|
|
|
+ rv |= writeJsonObjEnd(ctx);
|
|
|
+ } else {
|
|
|
+ /* no discovery message implemented */
|
|
|
+ rv = UA_STATUSCODE_BADNOTIMPLEMENTED;
|
|
|
+ }
|
|
|
+ return rv;
|
|
|
+}
|
|
|
+
|
|
|
+UA_StatusCode
|
|
|
+UA_NetworkMessage_encodeJson(const UA_NetworkMessage *src,
|
|
|
+ UA_Byte **bufPos, const UA_Byte **bufEnd, UA_String *namespaces,
|
|
|
+ size_t namespaceSize, UA_String *serverUris,
|
|
|
+ size_t serverUriSize, UA_Boolean useReversible) {
|
|
|
+ /* 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;
|
|
|
+
|
|
|
+ status ret = UA_NetworkMessage_encodeJson_internal(src, &ctx);
|
|
|
+
|
|
|
+ *bufPos = ctx.pos;
|
|
|
+ *bufEnd = ctx.end;
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+size_t
|
|
|
+UA_NetworkMessage_calcSizeJson(const UA_NetworkMessage *src,
|
|
|
+ UA_String *namespaces, size_t namespaceSize,
|
|
|
+ UA_String *serverUris, size_t serverUriSize,
|
|
|
+ UA_Boolean useReversible){
|
|
|
+
|
|
|
+ /* 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;
|
|
|
+
|
|
|
+ status ret = UA_NetworkMessage_encodeJson_internal(src, &ctx);
|
|
|
+ if(ret != UA_STATUSCODE_GOOD)
|
|
|
+ return 0;
|
|
|
+ return (size_t)ctx.pos;
|
|
|
+}
|
|
|
+
|
|
|
+/* decode json */
|
|
|
+static status MetaDataVersion_decodeJsonInternal(void* cvd, const UA_DataType *type, CtxJson *ctx, ParseCtx *parseCtx, UA_Boolean moveToken){
|
|
|
+ return decodeJsonInternal(cvd, &UA_TYPES[UA_TYPES_CONFIGURATIONVERSIONDATATYPE], ctx, parseCtx, UA_TRUE);
|
|
|
+}
|
|
|
+
|
|
|
+static status DataSetPayload_decodeJsonInternal(void* dsmP, const UA_DataType *type, CtxJson *ctx, ParseCtx *parseCtx, UA_Boolean moveToken){
|
|
|
+ UA_DataSetMessage* dsm = (UA_DataSetMessage*)dsmP;
|
|
|
+ dsm->header.dataSetMessageValid = UA_TRUE;
|
|
|
+ if(isJsonNull(ctx, parseCtx)){
|
|
|
+ parseCtx->index++;
|
|
|
+ return UA_STATUSCODE_GOOD;
|
|
|
+ }
|
|
|
+
|
|
|
+ size_t length = (size_t)parseCtx->tokenArray[parseCtx->index].size;
|
|
|
+ UA_String *fieldNames = (UA_String*)UA_calloc(length, sizeof(UA_String));
|
|
|
+ dsm->data.keyFrameData.fieldNames = fieldNames;
|
|
|
+ dsm->data.keyFrameData.fieldCount = (UA_UInt16)length;
|
|
|
+ dsm->data.keyFrameData.dataSetFields =
|
|
|
+ (UA_DataValue *)UA_Array_new(dsm->data.keyFrameData.fieldCount, &UA_TYPES[UA_TYPES_DATAVALUE]);
|
|
|
+
|
|
|
+ status ret = UA_STATUSCODE_GOOD;
|
|
|
+ parseCtx->index++; // We go to first Object key!
|
|
|
+
|
|
|
+ /* iterate over the key/value pairs in the object. Keys are stored in fieldnames. */
|
|
|
+ for(size_t i = 0; i < length; ++i) {
|
|
|
+ ret = getDecodeSignature(UA_TYPES_STRING)(&fieldNames[i], type, ctx, parseCtx, UA_TRUE);
|
|
|
+ if(ret != UA_STATUSCODE_GOOD){
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+
|
|
|
+ //TODO: Is field value a variant or datavalue? Current check if type and body present.
|
|
|
+ UA_Boolean isVariant = UA_TRUE;
|
|
|
+ size_t searchResult = 0;
|
|
|
+ status foundType = lookAheadForKey("Type", ctx, parseCtx, &searchResult);
|
|
|
+ status foundBody = lookAheadForKey("Body", ctx, parseCtx, &searchResult);
|
|
|
+ if(foundType == UA_STATUSCODE_GOOD && foundBody == UA_STATUSCODE_GOOD){
|
|
|
+ dsm->header.fieldEncoding = UA_FIELDENCODING_VARIANT;
|
|
|
+ }else{
|
|
|
+ isVariant = UA_FALSE;
|
|
|
+ dsm->header.fieldEncoding = UA_FIELDENCODING_DATAVALUE;
|
|
|
+ }
|
|
|
+
|
|
|
+ UA_DataValue_init(&dsm->data.keyFrameData.dataSetFields[i]);
|
|
|
+
|
|
|
+ if(isVariant){
|
|
|
+ ret = getDecodeSignature(UA_TYPES_VARIANT)(&dsm->data.keyFrameData.dataSetFields[i].value, type, ctx, parseCtx, UA_TRUE);
|
|
|
+ dsm->data.keyFrameData.dataSetFields[i].hasValue = UA_TRUE;
|
|
|
+ }else{
|
|
|
+ ret = getDecodeSignature(UA_TYPES_DATAVALUE)(&dsm->data.keyFrameData.dataSetFields[i], type, ctx, parseCtx, UA_TRUE);
|
|
|
+ dsm->data.keyFrameData.dataSetFields[i].hasValue = UA_TRUE;
|
|
|
+ }
|
|
|
+
|
|
|
+ if(ret != UA_STATUSCODE_GOOD){
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+static status
|
|
|
+DatasetMessage_Payload_decodeJsonInternal(UA_DataSetMessage* dsm, const UA_DataType *type, CtxJson *ctx, ParseCtx *parseCtx, UA_Boolean moveToken) {
|
|
|
+ UA_ConfigurationVersionDataType cvd;
|
|
|
+ UA_UInt16 dataSetWriterId; /* the id is currently not processed */
|
|
|
+
|
|
|
+ dsm->header.fieldEncoding = UA_FIELDENCODING_DATAVALUE;
|
|
|
+
|
|
|
+ DecodeEntry entries[6] = {
|
|
|
+ {UA_DECODEKEY_DATASETWRITERID, &dataSetWriterId, getDecodeSignature(UA_TYPES_UINT16), false},
|
|
|
+ {UA_DECODEKEY_SEQUENCENUMBER, &dsm->header.dataSetMessageSequenceNr, getDecodeSignature(UA_TYPES_UINT16), false},
|
|
|
+ {UA_DECODEKEY_METADATAVERSION, &cvd, &MetaDataVersion_decodeJsonInternal, false},
|
|
|
+ {UA_DECODEKEY_TIMESTAMP, &dsm->header.timestamp, getDecodeSignature(UA_TYPES_DATETIME), false},
|
|
|
+ {UA_DECODEKEY_DSM_STATUS, &dsm->header.status, getDecodeSignature(UA_TYPES_UINT16), false},
|
|
|
+ {UA_DECODEKEY_PAYLOAD, dsm, &DataSetPayload_decodeJsonInternal, false}
|
|
|
+ };
|
|
|
+
|
|
|
+ status ret = decodeFields(ctx, parseCtx, entries, 6, NULL);
|
|
|
+ if(ret != UA_STATUSCODE_GOOD || !entries[0].found){
|
|
|
+ /* no dataSetwriterid. Is mandatory. Abort. */
|
|
|
+ return UA_STATUSCODE_BADDECODINGERROR;
|
|
|
+ }else{
|
|
|
+ if(parseCtx->custom != NULL){
|
|
|
+ UA_UInt16* dataSetWriterIdsArray = (UA_UInt16*)parseCtx->custom;
|
|
|
+
|
|
|
+ if(*parseCtx->currentCustomIndex < parseCtx->numCustom){
|
|
|
+ dataSetWriterIdsArray[*parseCtx->currentCustomIndex] = dataSetWriterId;
|
|
|
+ (*parseCtx->currentCustomIndex)++;
|
|
|
+ }else{
|
|
|
+ return UA_STATUSCODE_BADDECODINGERROR;
|
|
|
+ }
|
|
|
+ }else{
|
|
|
+ return UA_STATUSCODE_BADDECODINGERROR;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ dsm->header.dataSetMessageSequenceNrEnabled = entries[1].found;
|
|
|
+ dsm->header.configVersionMajorVersion = cvd.majorVersion;
|
|
|
+ dsm->header.configVersionMinorVersion = cvd.minorVersion;
|
|
|
+ dsm->header.configVersionMajorVersionEnabled = entries[2].found;
|
|
|
+ dsm->header.configVersionMinorVersionEnabled = entries[2].found;
|
|
|
+ dsm->header.timestampEnabled = entries[3].found;
|
|
|
+ dsm->header.statusEnabled = entries[4].found;
|
|
|
+ if(!entries[5].found){
|
|
|
+ /* No payload found */
|
|
|
+ return UA_STATUSCODE_BADDECODINGERROR;
|
|
|
+ }
|
|
|
+
|
|
|
+ dsm->header.dataSetMessageType = UA_DATASETMESSAGE_DATAKEYFRAME;
|
|
|
+ dsm->header.picoSecondsIncluded = UA_FALSE;
|
|
|
+ dsm->header.dataSetMessageValid = UA_TRUE;
|
|
|
+ dsm->header.fieldEncoding = UA_FIELDENCODING_VARIANT;
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+static status
|
|
|
+DatasetMessage_Array_decodeJsonInternal(void *UA_RESTRICT dst, const UA_DataType *type, CtxJson *ctx, ParseCtx *parseCtx, UA_Boolean moveToken) {
|
|
|
+ /* Array! */
|
|
|
+ if(getJsmnType(parseCtx) != JSMN_ARRAY){
|
|
|
+ return UA_STATUSCODE_BADDECODINGERROR;
|
|
|
+ }
|
|
|
+ size_t length = (size_t)parseCtx->tokenArray[parseCtx->index].size;
|
|
|
+
|
|
|
+ /* Return early for empty arrays */
|
|
|
+ if(length == 0) {
|
|
|
+ return UA_STATUSCODE_GOOD;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Allocate memory */
|
|
|
+ UA_DataSetMessage *dsm = (UA_DataSetMessage*)UA_calloc(length, sizeof(UA_DataSetMessage));
|
|
|
+ if(dsm == NULL)
|
|
|
+ return UA_STATUSCODE_BADOUTOFMEMORY;
|
|
|
+
|
|
|
+ /* Copy new Pointer do dest */
|
|
|
+ memcpy(dst, &dsm, sizeof(void*));
|
|
|
+
|
|
|
+ /* We go to first Array member! */
|
|
|
+ parseCtx->index++;
|
|
|
+
|
|
|
+ status ret = UA_STATUSCODE_BADDECODINGERROR;
|
|
|
+ /* Decode array members */
|
|
|
+ for(size_t i = 0; i < length; ++i) {
|
|
|
+ ret = DatasetMessage_Payload_decodeJsonInternal(&dsm[i], NULL, ctx, parseCtx, UA_TRUE);
|
|
|
+ if(ret != UA_STATUSCODE_GOOD){
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+static status NetworkMessage_decodeJsonInternal(UA_NetworkMessage *dst, CtxJson *ctx, ParseCtx *parseCtx){
|
|
|
+ memset(dst, 0, sizeof(UA_NetworkMessage));
|
|
|
+ dst->chunkMessage = UA_FALSE;
|
|
|
+ dst->groupHeaderEnabled = UA_FALSE;
|
|
|
+ dst->payloadHeaderEnabled = UA_FALSE;
|
|
|
+ dst->picosecondsEnabled = UA_FALSE;
|
|
|
+ dst->promotedFieldsEnabled = UA_FALSE;
|
|
|
+
|
|
|
+ /* Look forward for publisheId, if present check if type if primitve (Number) or String. */
|
|
|
+ u8 publishIdTypeIndex = UA_TYPES_STRING;
|
|
|
+ {
|
|
|
+ size_t searchResultPublishIdType = 0;
|
|
|
+ status found = lookAheadForKey(UA_DECODEKEY_PUBLISHERID, ctx, parseCtx, &searchResultPublishIdType);
|
|
|
+ if(found == UA_STATUSCODE_GOOD){
|
|
|
+ jsmntok_t publishIdToken = parseCtx->tokenArray[searchResultPublishIdType];
|
|
|
+ if(publishIdToken.type == JSMN_PRIMITIVE){
|
|
|
+ publishIdTypeIndex = UA_TYPES_UINT64;
|
|
|
+ dst->publisherIdType = UA_PUBLISHERDATATYPE_UINT64; //store in biggest possible
|
|
|
+ }else if(publishIdToken.type == JSMN_STRING){
|
|
|
+ publishIdTypeIndex = UA_TYPES_STRING;
|
|
|
+ dst->publisherIdType = UA_PUBLISHERDATATYPE_STRING;
|
|
|
+ }else{
|
|
|
+ return UA_STATUSCODE_BADDECODINGERROR;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ size_t messageCount = 0;
|
|
|
+ {
|
|
|
+ //Is Messages an Array? How big?
|
|
|
+ size_t searchResultMessages = 0;
|
|
|
+ status found = lookAheadForKey(UA_DECODEKEY_MESSAGES, ctx, parseCtx, &searchResultMessages);
|
|
|
+ if(found == UA_STATUSCODE_GOOD){
|
|
|
+ jsmntok_t bodyToken = parseCtx->tokenArray[searchResultMessages];
|
|
|
+ if(bodyToken.type == JSMN_ARRAY){
|
|
|
+ messageCount = (size_t)parseCtx->tokenArray[searchResultMessages].size;
|
|
|
+ }else{
|
|
|
+ return UA_STATUSCODE_BADNOTIMPLEMENTED;
|
|
|
+ }
|
|
|
+ }else{
|
|
|
+ return UA_STATUSCODE_BADNOTIMPLEMENTED;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ //Set up custom context for the dataSetwriterId
|
|
|
+ size_t currentCustomIndex = 0;
|
|
|
+ parseCtx->custom = (void*)UA_calloc(messageCount, sizeof(UA_UInt16));
|
|
|
+ parseCtx->currentCustomIndex = ¤tCustomIndex;
|
|
|
+ parseCtx->numCustom = messageCount;
|
|
|
+
|
|
|
+ /* MessageType */
|
|
|
+ UA_Boolean isUaData = UA_TRUE;
|
|
|
+ size_t searchResultMessageType = 0;
|
|
|
+ status found = lookAheadForKey(UA_DECODEKEY_MESSAGETYPE, ctx, parseCtx, &searchResultMessageType);
|
|
|
+ if(found == UA_STATUSCODE_GOOD){
|
|
|
+ size_t size = (size_t)(parseCtx->tokenArray[searchResultMessageType].end - parseCtx->tokenArray[searchResultMessageType].start);
|
|
|
+ char* msgType = (char*)(ctx->pos + parseCtx->tokenArray[searchResultMessageType].start);
|
|
|
+ if(size == 7){ //ua-data
|
|
|
+ if(strncmp(msgType, "ua-data", size) != 0){
|
|
|
+ return UA_STATUSCODE_BADDECODINGERROR;
|
|
|
+ }else{
|
|
|
+ isUaData = UA_TRUE;
|
|
|
+ }
|
|
|
+ }else if(size == 11){ //ua-metadata
|
|
|
+ if(strncmp(msgType, "ua-metadata", size) != 0){
|
|
|
+ return UA_STATUSCODE_BADDECODINGERROR;
|
|
|
+ }else{
|
|
|
+ isUaData = UA_FALSE;
|
|
|
+ }
|
|
|
+ }else{
|
|
|
+ return UA_STATUSCODE_BADDECODINGERROR;
|
|
|
+ }
|
|
|
+ }else{
|
|
|
+ return UA_STATUSCODE_BADDECODINGERROR;
|
|
|
+ }
|
|
|
+
|
|
|
+ if(isUaData){
|
|
|
+ /* Network Message */
|
|
|
+ UA_String messageType;
|
|
|
+ DecodeEntry entries[5] = {
|
|
|
+ {UA_DECODEKEY_MESSAGEID, &dst->messageId, getDecodeSignature(UA_TYPES_STRING), false},
|
|
|
+ {UA_DECODEKEY_MESSAGETYPE, &messageType, NULL, false},
|
|
|
+ {UA_DECODEKEY_PUBLISHERID, &dst->publisherId.publisherIdString, getDecodeSignature(publishIdTypeIndex), false},
|
|
|
+ {UA_DECODEKEY_DATASETCLASSID, &dst->dataSetClassId, getDecodeSignature(UA_TYPES_GUID), false},
|
|
|
+ {UA_DECODEKEY_MESSAGES, &dst->payload.dataSetPayload.dataSetMessages, &DatasetMessage_Array_decodeJsonInternal, false}
|
|
|
+ };
|
|
|
+
|
|
|
+ //Store publisherId in correct union
|
|
|
+ if(publishIdTypeIndex == UA_TYPES_UINT64)
|
|
|
+ entries[2].fieldPointer = &dst->publisherId.publisherIdUInt64;
|
|
|
+
|
|
|
+ status ret = decodeFields(ctx, parseCtx, entries, 5, NULL);
|
|
|
+ if(ret != UA_STATUSCODE_GOOD){
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+
|
|
|
+ dst->messageIdEnabled = entries[0].found;
|
|
|
+ dst->publisherIdEnabled = entries[2].found;
|
|
|
+ if(dst->publisherIdEnabled){
|
|
|
+ dst->publisherIdType = UA_PUBLISHERDATATYPE_STRING;
|
|
|
+ }
|
|
|
+ dst->dataSetClassIdEnabled = entries[3].found;
|
|
|
+ dst->payloadHeaderEnabled = UA_TRUE;
|
|
|
+ dst->payloadHeader.dataSetPayloadHeader.count = (UA_Byte)messageCount;
|
|
|
+
|
|
|
+ //Set the dataSetWriterIds. They are filled in the dataSet decoding.
|
|
|
+ dst->payloadHeader.dataSetPayloadHeader.dataSetWriterIds = (UA_UInt16*)parseCtx->custom;
|
|
|
+ return ret;
|
|
|
+ }else{
|
|
|
+ //TODO: MetaData
|
|
|
+ return UA_STATUSCODE_BADNOTIMPLEMENTED;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+status UA_NetworkMessage_decodeJson(UA_NetworkMessage *dst, UA_ByteString *src){
|
|
|
+ /* Set up the context */
|
|
|
+ CtxJson ctx;
|
|
|
+ memset(&ctx, 0, sizeof(CtxJson));
|
|
|
+ ParseCtx parseCtx;
|
|
|
+ memset(&parseCtx, 0, sizeof(ParseCtx));
|
|
|
+ parseCtx.tokenArray = (jsmntok_t*)malloc(sizeof(jsmntok_t) * TOKENCOUNT);
|
|
|
+ memset(parseCtx.tokenArray, 0, sizeof(jsmntok_t) * TOKENCOUNT);
|
|
|
+ status ret = tokenize(&parseCtx, &ctx, src);
|
|
|
+ if(ret != UA_STATUSCODE_GOOD){
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+ ret = NetworkMessage_decodeJsonInternal(dst, &ctx, &parseCtx);
|
|
|
+ free(parseCtx.tokenArray);
|
|
|
+ return ret;
|
|
|
+}
|