blob: 4281a80ca98be09b1d768efcf598293c0470ec1a [file] [log] [blame]
/* 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 = &currentCustomIndex;
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;
}