Skip to content

Commit

Permalink
collect changes...
Browse files Browse the repository at this point in the history
  • Loading branch information
jpfr committed Nov 12, 2024
1 parent 790039e commit 1f0485c
Show file tree
Hide file tree
Showing 7 changed files with 161 additions and 106 deletions.
5 changes: 5 additions & 0 deletions include/open62541/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,9 @@ UA_String_fromChars(const char *src) UA_FUNC_ATTR_WARN_UNUSED_RESULT;
UA_Boolean UA_EXPORT
UA_String_isEmpty(const UA_String *s);

UA_StatusCode
UA_String_append(UA_String *s, const UA_String s2);

UA_EXPORT extern const UA_String UA_STRING_NULL;

/**
Expand Down Expand Up @@ -1321,6 +1324,8 @@ typedef struct {

UA_Boolean unquotedKeys; /* Don't print quotes around object element keys */
UA_Boolean stringNodeIds; /* String encoding for NodeIds, like "ns=1;i=42" */
UA_Boolean stringQualifiedNames; /* String QualifiedNames, like "0:name" */
UA_Boolean stringLocalizedText; /* String LocalizedText, like "en:text" */
} UA_EncodeJsonOptions;

/* Returns the number of bytes the value src takes in json encoding. Returns
Expand Down
13 changes: 11 additions & 2 deletions include/open62541/util.h
Original file line number Diff line number Diff line change
Expand Up @@ -260,8 +260,17 @@ UA_readNumberWithBase(const UA_Byte *buf, size_t buflen,
* extended escaping are ``,()[] \t\n\v\f\r``.
*
* This documentation always states whether "and-escaping" or the
* "extended-and-escaping is used.
*
* "extended-and-escaping is used. */

UA_StatusCode
UA_String_escape(const UA_String str, UA_String *out,
UA_Boolean extended);

/* In-situ, modifies (and maybe frees) the string */
void
UA_String_unescape(UA_String *str);

/**
* .. _relativepath:
*
* RelativePath Expressions
Expand Down
225 changes: 126 additions & 99 deletions src/ua_types_encoding_json.c
Original file line number Diff line number Diff line change
Expand Up @@ -749,7 +749,7 @@ ENCODE_JSON(NodeId) {
if(ctx->stringNodeIds) {
UA_String out = UA_STRING_NULL;
ret |= UA_NodeId_print(src, &out);
ret |= encodeJsonJumpTable[UA_DATATYPEKIND_STRING](ctx, &out, NULL);
ret |= ENCODE_DIRECT_JSON(&out, String);
UA_String_clear(&out);
return ret;
}
Expand All @@ -766,18 +766,17 @@ ENCODE_JSON(NodeId) {
/* 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. */
ret |= writeJsonKey(ctx, UA_JSONKEY_NAMESPACE);
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;
/* If not found, print the identifier */
ret |= ENCODE_DIRECT_JSON(&src->namespaceIndex, UInt16);
}
}
}
Expand All @@ -793,7 +792,7 @@ ENCODE_JSON(ExpandedNodeId) {
if(ctx->stringNodeIds) {
UA_String out = UA_STRING_NULL;
ret |= UA_ExpandedNodeId_print(src, &out);
ret |= encodeJsonJumpTable[UA_DATATYPEKIND_STRING](ctx, &out, NULL);
ret |= ENCODE_DIRECT_JSON(&out, String);
UA_String_clear(&out);
return ret;
}
Expand All @@ -807,16 +806,15 @@ ENCODE_JSON(ExpandedNodeId) {
if(ctx->useReversible) {
/* Reversible Case */

ret |= writeJsonKey(ctx, UA_JSONKEY_NAMESPACE);
if(src->namespaceUri.data) {
/* 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(src->nodeId.namespaceIndex > 0) {
/* If the NamespaceUri is not specified, the NamespaceIndex is
* encoded. Encoded as a JSON number for the reversible encoding.
* Omitted if the NamespaceIndex equals 0. */
ret |= writeJsonKey(ctx, UA_JSONKEY_NAMESPACE);
ret |= ENCODE_DIRECT_JSON(&src->nodeId.namespaceIndex, UInt16);
}

Expand All @@ -834,20 +832,20 @@ ENCODE_JSON(ExpandedNodeId) {
* NamespaceUri associated with the NamespaceIndex encoded as a JSON
* string. A NamespaceIndex of 1 is always encoded as a JSON number. */

ret |= writeJsonKey(ctx, UA_JSONKEY_NAMESPACE);
if(src->namespaceUri.data) {
ret |= writeJsonKey(ctx, UA_JSONKEY_NAMESPACE);
ret |= ENCODE_DIRECT_JSON(&src->namespaceUri, String);
} else {
if(src->nodeId.namespaceIndex == 1) {
ret |= writeJsonKey(ctx, UA_JSONKEY_NAMESPACE);
ret |= ENCODE_DIRECT_JSON(&src->nodeId.namespaceIndex, UInt16);
} else {
/* Check if Namespace given and in range */
if(src->nodeId.namespaceIndex >= ctx->namespacesSize || !ctx->namespaces)
return UA_STATUSCODE_BADNOTFOUND;
UA_String namespaceEntry = ctx->namespaces[src->nodeId.namespaceIndex];
ret |= writeJsonKey(ctx, UA_JSONKEY_NAMESPACE);
ret |= ENCODE_DIRECT_JSON(&namespaceEntry, String);
if(src->nodeId.namespaceIndex < ctx->namespacesSize && ctx->namespaces != NULL) {
UA_String namespaceEntry = ctx->namespaces[src->nodeId.namespaceIndex];
ret |= ENCODE_DIRECT_JSON(&namespaceEntry, String);
} else {
ret |= ENCODE_DIRECT_JSON(&src->nodeId.namespaceIndex, UInt16);
}
}
}

Expand All @@ -869,8 +867,26 @@ ENCODE_JSON(ExpandedNodeId) {

/* LocalizedText */
ENCODE_JSON(LocalizedText) {
status ret = UA_STATUSCODE_GOOD;

if(ctx->stringLocalizedText) {
UA_String out = UA_STRING_NULL;
ret |= writeChar(ctx, '"');
if(src->locale.length > 0) {
ret |= UA_String_escape(src->locale, &out, false);
ret |= writeChars(ctx, (char*)out.data, out.length);
UA_String_clear(&out);
ret |= writeChar(ctx, ':');
}
ret |= UA_String_escape(src->text, &out, false);
ret |= writeChars(ctx, (char*)out.data, out.length);
UA_String_clear(&out);
ret |= writeChar(ctx, '"');
return ret;
}

if(ctx->useReversible) {
status ret = writeJsonObjStart(ctx);
ret = writeJsonObjStart(ctx);
ret |= writeJsonKey(ctx, UA_JSONKEY_LOCALE);
ret |= ENCODE_DIRECT_JSON(&src->locale, String);
ret |= writeJsonKey(ctx, UA_JSONKEY_TEXT);
Expand All @@ -884,7 +900,22 @@ ENCODE_JSON(LocalizedText) {
}

ENCODE_JSON(QualifiedName) {
status ret = writeJsonObjStart(ctx);
status ret = UA_STATUSCODE_GOOD;
if(ctx->stringQualifiedNames) {
ret |= writeChar(ctx, '"');
if(src->namespaceIndex > 0) {
ret |= ENCODE_DIRECT_JSON(&src->namespaceIndex, UInt16);
ret |= writeChar(ctx, ':');
}
UA_String out = UA_STRING_NULL;
ret |= UA_String_escape(src->name, &out, false);
ret |= writeChars(ctx, (char*)out.data, out.length);
UA_String_clear(&out);
ret |= writeChar(ctx, '"');
return ret;
}

ret = writeJsonObjStart(ctx);
ret |= writeJsonKey(ctx, UA_JSONKEY_NAME);
ret |= ENCODE_DIRECT_JSON(&src->name, String);

Expand All @@ -898,12 +929,10 @@ ENCODE_JSON(QualifiedName) {
* 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. */
ret |= writeJsonKey(ctx, UA_JSONKEY_URI);
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];
Expand Down Expand Up @@ -1318,6 +1347,8 @@ UA_encodeJson(const void *src, const UA_DataType *type, UA_ByteString *outBuf,
ctx.prettyPrint = options->prettyPrint;
ctx.unquotedKeys = options->unquotedKeys;
ctx.stringNodeIds = options->stringNodeIds;
ctx.stringQualifiedNames = options->stringQualifiedNames;
ctx.stringLocalizedText = options->stringLocalizedText;
}

/* Encode */
Expand Down Expand Up @@ -2145,13 +2176,6 @@ DECODE_JSON(StatusCode) {
return UInt32_decodeJson(ctx, dst, NULL);
}

static status
VariantDimension_decodeJson(ParseCtx *ctx, void *dst, const UA_DataType *type) {
(void) type;
const UA_DataType *dimType = &UA_TYPES[UA_TYPES_UINT32];
return Array_decodeJson(ctx, (void**)dst, dimType);
}

/* Get type type encoded by the ExtensionObject at ctx->index.
* Returns NULL if that fails (type unknown or otherwise). */
static const UA_DataType *
Expand Down Expand Up @@ -2314,6 +2338,73 @@ Array_decodeJsonUnwrapExtensionObject(ParseCtx *ctx, void **dst, const UA_DataTy
return UA_STATUSCODE_GOOD;
}

static status
decodeVariantBodyWithType(ParseCtx *ctx, UA_Variant *dst, size_t bodyIndex,
size_t *dimIndex, const UA_DataType *type) {
/* Value is an array? */
UA_Boolean isArray = (ctx->tokens[bodyIndex].type == CJ5_TOKEN_ARRAY);

/* TODO: Handling of null-arrays (length -1) needs to be clarified
*
* if(tokenIsNull(ctx, bodyIndex)) {
* isArray = true;
* dst->arrayLength = 0;
* } */

/* No array but has dimension -> error */
if(!isArray && dimIndex)
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(type->typeKind > UA_DATATYPEKIND_DIAGNOSTICINFO)
return UA_STATUSCODE_BADDECODINGERROR;

/* A variant cannot contain a variant. But it can contain an array of
* variants */
if(type->typeKind == UA_DATATYPEKIND_VARIANT && !isArray)
return UA_STATUSCODE_BADDECODINGERROR;

ctx->depth++;
ctx->index = bodyIndex;

/* Decode an array */
status res = UA_STATUSCODE_GOOD;
if(isArray) {
/* Try to unwrap ExtensionObjects in the array.
* The members must all have the same type. */
const UA_DataType *unwrapType = NULL;
if(type == &UA_TYPES[UA_TYPES_EXTENSIONOBJECT] &&
(unwrapType = getArrayUnwrapType(ctx, bodyIndex))) {
res = Array_decodeJsonUnwrapExtensionObject(ctx, &dst->data, unwrapType);
} else {
dst->type = type;
res = Array_decodeJson(ctx, &dst->data, type);
}

/* Decode array dimensions */
if(dimIndex) {
ctx->index = *dimIndex;
res |= Array_decodeJson(ctx, (void**)&dst->arrayDimensions, &UA_TYPES[UA_TYPES_UINT32]);
}
ctx->depth--;
return res;
}

/* Decode a value wrapped in an ExtensionObject */
if(type->typeKind == UA_DATATYPEKIND_EXTENSIONOBJECT)
return Variant_decodeJsonUnwrapExtensionObject(ctx, dst, NULL);

/* Allocate Memory for Body */
dst->data = UA_new(type);
if(!dst->data)
return UA_STATUSCODE_BADOUTOFMEMORY;

/* Decode the body */
dst->type = type;
return decodeJsonJumpTable[type->typeKind](ctx, dst, type);
}

DECODE_JSON(Variant) {
CHECK_NULL_SKIP; /* Treat null as an empty variant */
CHECK_OBJECT;
Expand Down Expand Up @@ -2343,8 +2434,8 @@ DECODE_JSON(Variant) {

/* Set the type */
UA_NodeId typeNodeId = UA_NODEID_NUMERIC(0, (UA_UInt32)idType);
dst->type = UA_findDataTypeWithCustom(&typeNodeId, ctx->customTypes);
if(!dst->type)
type = UA_findDataTypeWithCustom(&typeNodeId, ctx->customTypes);
if(!type)
return UA_STATUSCODE_BADDECODINGERROR;

/* Search for body */
Expand All @@ -2353,79 +2444,15 @@ DECODE_JSON(Variant) {
if(ret != UA_STATUSCODE_GOOD)
return UA_STATUSCODE_BADDECODINGERROR;

/* Value is an array? */
UA_Boolean isArray = (ctx->tokens[bodyIndex].type == CJ5_TOKEN_ARRAY);

/* TODO: Handling of null-arrays (length -1) needs to be clarified
*
* if(tokenIsNull(ctx, bodyIndex)) {
* isArray = true;
* dst->arrayLength = 0;
* } */

/* Has the variant dimension? */
UA_Boolean hasDimension = false;
/* Search for the dimensions */
size_t *dimPtr = NULL;
size_t dimIndex = 0;
ret = lookAheadForKey(ctx, UA_JSONKEY_DIMENSION, &dimIndex);
if(ret == UA_STATUSCODE_GOOD)
hasDimension = (ctx->tokens[dimIndex].size > 0);

/* 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_DATATYPEKIND_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, dst->type},
{UA_JSONKEY_DIMENSION, &dst->arrayDimensions, VariantDimension_decodeJson, false, NULL}
};

/* Try to unwrap ExtensionObjects in the array.
* The members must all have the same type. */
if(dst->type == &UA_TYPES[UA_TYPES_EXTENSIONOBJECT]) {
const UA_DataType *unwrapType = getArrayUnwrapType(ctx, bodyIndex);
if(unwrapType) {
dst->type = unwrapType;
entries[1].type = unwrapType;
entries[1].function = (decodeJsonSignature)
Array_decodeJsonUnwrapExtensionObject;
}
}

return decodeFields(ctx, entries, (hasDimension) ? 3 : 2);
}

/* 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, Variant_decodeJsonUnwrapExtensionObject, false, NULL}
};
return decodeFields(ctx, entries, 2);
}
if(ret == UA_STATUSCODE_GOOD && ctx->tokens[dimIndex].size > 0)
dimPtr = &dimIndex;

/* 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, NULL, false, dst->type}
};
return decodeFields(ctx, entries, 2);
/* Decode the body */
return decodeVariantBodyWithType(ctx, dst, bodyIndex, dimPtr, type);
}

DECODE_JSON(DataValue) {
Expand Down
2 changes: 2 additions & 0 deletions src/ua_types_encoding_json.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ typedef struct {
UA_Boolean prettyPrint;
UA_Boolean unquotedKeys;
UA_Boolean stringNodeIds;
UA_Boolean stringQualifiedNames;
UA_Boolean stringLocalizedText;
} CtxJson;

UA_StatusCode writeJsonObjStart(CtxJson *ctx);
Expand Down
Loading

0 comments on commit 1f0485c

Please sign in to comment.