/* 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 #include #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; }