| /* 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 <open62541/types.h> |
| #include <open62541/types_generated_handling.h> |
| |
| #include "ua_pubsub_networkmessage.h" |
| #include "ua_types_encoding_json.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 |= writeJsonObjElm(ctx, UA_DECODEKEY_DATASETWRITERID, |
| &dataSetWriterId, &UA_TYPES[UA_TYPES_UINT16]); |
| if(rv != UA_STATUSCODE_GOOD) |
| return rv; |
| |
| /* DataSetMessageSequenceNr */ |
| if(src->header.dataSetMessageSequenceNrEnabled) { |
| rv |= writeJsonObjElm(ctx, UA_DECODEKEY_SEQUENCENUMBER, |
| &src->header.dataSetMessageSequenceNr, |
| &UA_TYPES[UA_TYPES_UINT16]); |
| if(rv != UA_STATUSCODE_GOOD) |
| return rv; |
| } |
| |
| /* MetaDataVersion */ |
| if(src->header.configVersionMajorVersionEnabled || src->header.configVersionMinorVersionEnabled) { |
| UA_ConfigurationVersionDataType cvd; |
| cvd.majorVersion = src->header.configVersionMajorVersion; |
| cvd.minorVersion = src->header.configVersionMinorVersion; |
| rv |= writeJsonObjElm(ctx, UA_DECODEKEY_METADATAVERSION, &cvd, |
| &UA_TYPES[UA_TYPES_CONFIGURATIONVERSIONDATATYPE]); |
| if(rv != UA_STATUSCODE_GOOD) |
| return rv; |
| } |
| |
| /* Timestamp */ |
| if(src->header.timestampEnabled) { |
| rv |= writeJsonObjElm(ctx, UA_DECODEKEY_TIMESTAMP, &src->header.timestamp, |
| &UA_TYPES[UA_TYPES_DATETIME]); |
| if(rv != UA_STATUSCODE_GOOD) |
| return rv; |
| } |
| |
| /* Status */ |
| if(src->header.statusEnabled) { |
| rv |= writeJsonObjElm(ctx, UA_DECODEKEY_DSM_STATUS, |
| &src->header.status, &UA_TYPES[UA_TYPES_STATUSCODE]); |
| 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 the field names are not defined, write |
| * out empty field names. */ |
| if(src->header.dataSetMessageType == UA_DATASETMESSAGE_DATAKEYFRAME) { |
| |
| if(src->header.fieldEncoding == UA_FIELDENCODING_VARIANT) { |
| /* KEYFRAME VARIANT */ |
| for (UA_UInt16 i = 0; i < src->data.keyFrameData.fieldCount; i++) { |
| if(src->data.keyFrameData.fieldNames) |
| rv |= writeJsonKey_UA_String(ctx, &src->data.keyFrameData.fieldNames[i]); |
| else |
| rv |= writeJsonKey(ctx, ""); |
| 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_DATAVALUE) { |
| /* KEYFRAME DATAVALUE */ |
| for (UA_UInt16 i = 0; i < src->data.keyFrameData.fieldCount; i++) { |
| if(src->data.keyFrameData.fieldNames) |
| rv |= writeJsonKey_UA_String(ctx, &src->data.keyFrameData.fieldNames[i]); |
| else |
| rv |= writeJsonKey(ctx, ""); |
| rv |= encodeJsonInternal(&src->data.keyFrameData.dataSetFields[i], |
| &UA_TYPES[UA_TYPES_DATAVALUE], ctx); |
| if(rv != UA_STATUSCODE_GOOD) |
| return rv; |
| } |
| } else { |
| /* RawData */ |
| return UA_STATUSCODE_BADNOTIMPLEMENTED; |
| } |
| } else { |
| /* DeltaFrame */ |
| 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, no discovery message implemented */ |
| if(src->networkMessageType != UA_NETWORKMESSAGE_DATASET) |
| return UA_STATUSCODE_BADNOTIMPLEMENTED; |
| |
| writeJsonObjStart(ctx); |
| |
| /* Table 91 – JSON NetworkMessage Definition |
| * MessageId | String | A globally unique identifier for the message. |
| * This value is mandatory. But we don't check uniqueness in the |
| * encoding layer. */ |
| rv |= writeJsonObjElm(ctx, UA_DECODEKEY_MESSAGEID, |
| &src->messageId, &UA_TYPES[UA_TYPES_STRING]); |
| |
| /* MessageType */ |
| UA_String s = UA_STRING("ua-data"); |
| rv |= writeJsonObjElm(ctx, UA_DECODEKEY_MESSAGETYPE, |
| &s, &UA_TYPES[UA_TYPES_STRING]); |
| |
| /* 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 |= writeJsonObjElm(ctx, UA_DECODEKEY_DATASETCLASSID, |
| &src->dataSetClassId, &UA_TYPES[UA_TYPES_GUID]); |
| 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); |
| 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. |
| 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; |
| 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 { |
| dsm->header.fieldEncoding = UA_FIELDENCODING_DATAVALUE; |
| 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, NULL}, |
| {UA_DECODEKEY_SEQUENCENUMBER, &dsm->header.dataSetMessageSequenceNr, |
| getDecodeSignature(UA_TYPES_UINT16), false, NULL}, |
| {UA_DECODEKEY_METADATAVERSION, &cvd, |
| &MetaDataVersion_decodeJsonInternal, false, NULL}, |
| {UA_DECODEKEY_TIMESTAMP, &dsm->header.timestamp, |
| getDecodeSignature(UA_TYPES_DATETIME), false, NULL}, |
| {UA_DECODEKEY_DSM_STATUS, &dsm->header.status, |
| getDecodeSignature(UA_TYPES_UINT16), false, NULL}, |
| {UA_DECODEKEY_PAYLOAD, dsm, |
| &DataSetPayload_decodeJsonInternal, false, NULL} |
| }; |
| |
| 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; |
| } |
| } |
| |
| /* Is Messages an Array? How big? */ |
| size_t messageCount = 0; |
| size_t searchResultMessages = 0; |
| found = lookAheadForKey(UA_DECODEKEY_MESSAGES, ctx, parseCtx, &searchResultMessages); |
| if(found != UA_STATUSCODE_GOOD) |
| return UA_STATUSCODE_BADNOTIMPLEMENTED; |
| jsmntok_t bodyToken = parseCtx->tokenArray[searchResultMessages]; |
| if(bodyToken.type != JSMN_ARRAY) |
| return UA_STATUSCODE_BADNOTIMPLEMENTED; |
| messageCount = (size_t)parseCtx->tokenArray[searchResultMessages].size; |
| |
| /* 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; |
| found = lookAheadForKey(UA_DECODEKEY_MESSAGETYPE, ctx, parseCtx, &searchResultMessageType); |
| if(found != UA_STATUSCODE_GOOD) |
| return UA_STATUSCODE_BADDECODINGERROR; |
| 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; |
| isUaData = UA_TRUE; |
| } else if(size == 11) { //ua-metadata |
| if(strncmp(msgType, "ua-metadata", size) != 0) |
| return UA_STATUSCODE_BADDECODINGERROR; |
| isUaData = UA_FALSE; |
| } else { |
| return UA_STATUSCODE_BADDECODINGERROR; |
| } |
| |
| //TODO: MetaData |
| if(!isUaData) |
| return UA_STATUSCODE_BADNOTIMPLEMENTED; |
| |
| /* Network Message */ |
| UA_String messageType; |
| DecodeEntry entries[5] = { |
| {UA_DECODEKEY_MESSAGEID, &dst->messageId, getDecodeSignature(UA_TYPES_STRING), false, NULL}, |
| {UA_DECODEKEY_MESSAGETYPE, &messageType, NULL, false, NULL}, |
| {UA_DECODEKEY_PUBLISHERID, &dst->publisherId.publisherIdString, getDecodeSignature(publishIdTypeIndex), false, NULL}, |
| {UA_DECODEKEY_DATASETCLASSID, &dst->dataSetClassId, getDecodeSignature(UA_TYPES_GUID), false, NULL}, |
| {UA_DECODEKEY_MESSAGES, &dst->payload.dataSetPayload.dataSetMessages, &DatasetMessage_Array_decodeJsonInternal, false, NULL} |
| }; |
| |
| //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; |
| } |
| |
| status UA_NetworkMessage_decodeJson(UA_NetworkMessage *dst, const 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*)UA_malloc(sizeof(jsmntok_t) * UA_JSON_MAXTOKENCOUNT); |
| memset(parseCtx.tokenArray, 0, sizeof(jsmntok_t) * UA_JSON_MAXTOKENCOUNT); |
| status ret = tokenize(&parseCtx, &ctx, src); |
| if(ret != UA_STATUSCODE_GOOD){ |
| return ret; |
| } |
| ret = NetworkMessage_decodeJsonInternal(dst, &ctx, &parseCtx); |
| UA_free(parseCtx.tokenArray); |
| return ret; |
| } |