blob: f652f5c99986a64c79f23b6ed91b7f85eaafa5ff [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 2014-2018 (c) Fraunhofer IOSB (Author: Julius Pfrommer)
* Copyright 2014-2017 (c) Florian Palm
* Copyright 2014-2016 (c) Sten Grüner
* Copyright 2014 (c) Leon Urbas
* Copyright 2015 (c) LEvertz
* Copyright 2015 (c) Chris Iatrou
* Copyright 2015-2016 (c) Oleksiy Vasylyev
* Copyright 2016-2017 (c) Stefan Profanter, fortiss GmbH
* Copyright 2016 (c) Lorenz Haas
* Copyright 2017 (c) Mark Giraud, Fraunhofer IOSB
* Copyright 2017 (c) Henrik Norrman
*/
#include "ua_types_encoding_binary.h"
#include <open62541/types_generated.h>
#include <open62541/types_generated_handling.h>
#include "ua_util_internal.h"
/**
* Type Encoding and Decoding
* --------------------------
* The following methods contain encoding and decoding functions for the builtin
* data types and generic functions that operate on all types and arrays. This
* requires the type description from a UA_DataType structure.
*
* Encoding Context
* ^^^^^^^^^^^^^^^^
* If possible, the encoding context is stored in a thread-local variable to
* speed up encoding. If thread-local variables are not supported, the context
* is "looped through" every method call. The ``_``-macro accesses either the
* thread-local or the "looped through" context . */
/* Part 6 §5.1.5: Decoders shall support at least 100 nesting levels */
#define UA_ENCODING_MAX_RECURSION 100
typedef struct {
/* Pointers to the current position and the last position in the buffer */
u8 *pos;
const u8 *end;
u8 **oldpos; /* Sentinel for a lower stacktrace exchanging the buffer */
u16 depth; /* How often did we en-/decoding recurse? */
const UA_DataTypeArray *customTypes;
UA_exchangeEncodeBuffer exchangeBufferCallback;
void *exchangeBufferCallbackHandle;
} Ctx;
typedef status
(*encodeBinarySignature)(const void *UA_RESTRICT src, const UA_DataType *type,
Ctx *UA_RESTRICT ctx);
typedef status
(*decodeBinarySignature)(void *UA_RESTRICT dst, const UA_DataType *type,
Ctx *UA_RESTRICT ctx);
typedef size_t
(*calcSizeBinarySignature)(const void *UA_RESTRICT p, const UA_DataType *contenttype);
#define ENCODE_BINARY(TYPE) static status \
TYPE##_encodeBinary(const UA_##TYPE *UA_RESTRICT src, \
const UA_DataType *type, Ctx *UA_RESTRICT ctx)
#define DECODE_BINARY(TYPE) static status \
TYPE##_decodeBinary(UA_##TYPE *UA_RESTRICT dst, \
const UA_DataType *type, Ctx *UA_RESTRICT ctx)
#define CALCSIZE_BINARY(TYPE) static size_t \
TYPE##_calcSizeBinary(const UA_##TYPE *UA_RESTRICT src, \
const UA_DataType *_)
#define ENCODE_DIRECT(SRC, TYPE) TYPE##_encodeBinary((const UA_##TYPE*)SRC, NULL, ctx)
#define DECODE_DIRECT(DST, TYPE) TYPE##_decodeBinary((UA_##TYPE*)DST, NULL, ctx)
/* Jumptables for de-/encoding and computing the buffer length. The methods in
* the decoding jumptable do not all clean up their allocated memory when an
* error occurs. So a final _clear needs to be called before returning to the
* user. */
extern const encodeBinarySignature encodeBinaryJumpTable[UA_DATATYPEKINDS];
extern const decodeBinarySignature decodeBinaryJumpTable[UA_DATATYPEKINDS];
extern const calcSizeBinarySignature calcSizeBinaryJumpTable[UA_DATATYPEKINDS];
/* Breaking a message up into chunks is integrated with the encoding. When the
* end of a buffer is reached, a callback is executed that sends the current
* buffer as a chunk and exchanges the encoding buffer "underneath" the ongoing
* encoding. This reduces the RAM requirements and unnecessary copying. */
/* Send the current chunk and replace the buffer */
static status exchangeBuffer(Ctx *ctx) {
if(!ctx->exchangeBufferCallback)
return UA_STATUSCODE_BADENCODINGERROR;
return ctx->exchangeBufferCallback(ctx->exchangeBufferCallbackHandle,
&ctx->pos, &ctx->end);
}
/* If encoding fails, exchange the buffer and try again. */
static status
encodeWithExchangeBuffer(const void *ptr, const UA_DataType *type, Ctx *ctx) {
u8 *oldpos = ctx->pos; /* Last known good position */
ctx->oldpos = &oldpos;
status ret = encodeBinaryJumpTable[type->typeKind](ptr, type, ctx);
if(ret == UA_STATUSCODE_BADENCODINGLIMITSEXCEEDED && ctx->oldpos == &oldpos) {
ctx->pos = oldpos; /* Send the position to the last known good position
* and switch */
ret = exchangeBuffer(ctx);
if(ret != UA_STATUSCODE_GOOD)
return ret;
ret = encodeBinaryJumpTable[type->typeKind](ptr, type, ctx);
}
return ret;
}
#define ENCODE_WITHEXCHANGE(VAR, TYPE) \
encodeWithExchangeBuffer((const void*)VAR, &UA_TYPES[TYPE], ctx)
/*****************/
/* Integer Types */
/*****************/
#if !UA_BINARY_OVERLAYABLE_INTEGER
#pragma message "Integer endianness could not be detected to be little endian. Use slow generic encoding."
/* These en/decoding functions are only used when the architecture isn't little-endian. */
static void
UA_encode16(const u16 v, u8 buf[2]) {
buf[0] = (u8)v;
buf[1] = (u8)(v >> 8);
}
static void
UA_decode16(const u8 buf[2], u16 *v) {
*v = (u16)((u16)buf[0] + (((u16)buf[1]) << 8));
}
static void
UA_encode32(const u32 v, u8 buf[4]) {
buf[0] = (u8)v;
buf[1] = (u8)(v >> 8);
buf[2] = (u8)(v >> 16);
buf[3] = (u8)(v >> 24);
}
static void
UA_decode32(const u8 buf[4], u32 *v) {
*v = (u32)((u32)buf[0] + (((u32)buf[1]) << 8) +
(((u32)buf[2]) << 16) + (((u32)buf[3]) << 24));
}
static void
UA_encode64(const u64 v, u8 buf[8]) {
buf[0] = (u8)v;
buf[1] = (u8)(v >> 8);
buf[2] = (u8)(v >> 16);
buf[3] = (u8)(v >> 24);
buf[4] = (u8)(v >> 32);
buf[5] = (u8)(v >> 40);
buf[6] = (u8)(v >> 48);
buf[7] = (u8)(v >> 56);
}
static void
UA_decode64(const u8 buf[8], u64 *v) {
*v = (u64)((u64)buf[0] + (((u64)buf[1]) << 8) +
(((u64)buf[2]) << 16) + (((u64)buf[3]) << 24) +
(((u64)buf[4]) << 32) + (((u64)buf[5]) << 40) +
(((u64)buf[6]) << 48) + (((u64)buf[7]) << 56));
}
#endif /* !UA_BINARY_OVERLAYABLE_INTEGER */
/* Boolean */
/* Note that sizeof(bool) != 1 on some platforms. Overlayable integer encoding
* is disabled in those cases. */
ENCODE_BINARY(Boolean) {
if(ctx->pos + 1 > ctx->end)
return UA_STATUSCODE_BADENCODINGLIMITSEXCEEDED;
*ctx->pos = *(const u8*)src;
++ctx->pos;
return UA_STATUSCODE_GOOD;
}
DECODE_BINARY(Boolean) {
if(ctx->pos + 1 > ctx->end)
return UA_STATUSCODE_BADDECODINGERROR;
*dst = (*ctx->pos > 0) ? true : false;
++ctx->pos;
return UA_STATUSCODE_GOOD;
}
/* Byte */
ENCODE_BINARY(Byte) {
if(ctx->pos + sizeof(u8) > ctx->end)
return UA_STATUSCODE_BADENCODINGLIMITSEXCEEDED;
*ctx->pos = *(const u8*)src;
++ctx->pos;
return UA_STATUSCODE_GOOD;
}
DECODE_BINARY(Byte) {
if(ctx->pos + sizeof(u8) > ctx->end)
return UA_STATUSCODE_BADDECODINGERROR;
*dst = *ctx->pos;
++ctx->pos;
return UA_STATUSCODE_GOOD;
}
/* UInt16 */
ENCODE_BINARY(UInt16) {
if(ctx->pos + sizeof(u16) > ctx->end)
return UA_STATUSCODE_BADENCODINGLIMITSEXCEEDED;
#if UA_BINARY_OVERLAYABLE_INTEGER
memcpy(ctx->pos, src, sizeof(u16));
#else
UA_encode16(*src, ctx->pos);
#endif
ctx->pos += 2;
return UA_STATUSCODE_GOOD;
}
DECODE_BINARY(UInt16) {
if(ctx->pos + sizeof(u16) > ctx->end)
return UA_STATUSCODE_BADDECODINGERROR;
#if UA_BINARY_OVERLAYABLE_INTEGER
memcpy(dst, ctx->pos, sizeof(u16));
#else
UA_decode16(ctx->pos, dst);
#endif
ctx->pos += 2;
return UA_STATUSCODE_GOOD;
}
/* UInt32 */
ENCODE_BINARY(UInt32) {
if(ctx->pos + sizeof(u32) > ctx->end)
return UA_STATUSCODE_BADENCODINGLIMITSEXCEEDED;
#if UA_BINARY_OVERLAYABLE_INTEGER
memcpy(ctx->pos, src, sizeof(u32));
#else
UA_encode32(*src, ctx->pos);
#endif
ctx->pos += 4;
return UA_STATUSCODE_GOOD;
}
DECODE_BINARY(UInt32) {
if(ctx->pos + sizeof(u32) > ctx->end)
return UA_STATUSCODE_BADDECODINGERROR;
#if UA_BINARY_OVERLAYABLE_INTEGER
memcpy(dst, ctx->pos, sizeof(u32));
#else
UA_decode32(ctx->pos, dst);
#endif
ctx->pos += 4;
return UA_STATUSCODE_GOOD;
}
/* UInt64 */
ENCODE_BINARY(UInt64) {
if(ctx->pos + sizeof(u64) > ctx->end)
return UA_STATUSCODE_BADENCODINGLIMITSEXCEEDED;
#if UA_BINARY_OVERLAYABLE_INTEGER
memcpy(ctx->pos, src, sizeof(u64));
#else
UA_encode64(*src, ctx->pos);
#endif
ctx->pos += 8;
return UA_STATUSCODE_GOOD;
}
DECODE_BINARY(UInt64) {
if(ctx->pos + sizeof(u64) > ctx->end)
return UA_STATUSCODE_BADDECODINGERROR;
#if UA_BINARY_OVERLAYABLE_INTEGER
memcpy(dst, ctx->pos, sizeof(u64));
#else
UA_decode64(ctx->pos, dst);
#endif
ctx->pos += 8;
return UA_STATUSCODE_GOOD;
}
/************************/
/* Floating Point Types */
/************************/
/* Can we reuse the integer encoding mechanism by casting floating point
* values? */
#if (UA_FLOAT_IEEE754 == 1) && (UA_LITTLE_ENDIAN == UA_FLOAT_LITTLE_ENDIAN)
# define Float_encodeBinary UInt32_encodeBinary
# define Float_decodeBinary UInt32_decodeBinary
# define Double_encodeBinary UInt64_encodeBinary
# define Double_decodeBinary UInt64_decodeBinary
#else
#include <math.h>
#pragma message "No native IEEE 754 format detected. Use slow generic encoding."
/* Handling of IEEE754 floating point values was taken from Beej's Guide to
* Network Programming (http://beej.us/guide/bgnet/) and enhanced to cover the
* edge cases +/-0, +/-inf and nan. */
static uint64_t
pack754(long double f, unsigned bits, unsigned expbits) {
unsigned significandbits = bits - expbits - 1;
long double fnorm;
long long sign;
if(f < 0) { sign = 1; fnorm = -f; }
else { sign = 0; fnorm = f; }
int shift = 0;
while(fnorm >= 2.0) { fnorm /= 2.0; ++shift; }
while(fnorm < 1.0) { fnorm *= 2.0; --shift; }
fnorm = fnorm - 1.0;
long long significand = (long long)(fnorm * ((float)(1LL<<significandbits) + 0.5f));
long long exponent = shift + ((1<<(expbits-1)) - 1);
return (uint64_t)((sign<<(bits-1)) | (exponent<<(bits-expbits-1)) | significand);
}
static long double
unpack754(uint64_t i, unsigned bits, unsigned expbits) {
unsigned significandbits = bits - expbits - 1;
long double result = (long double)(i&(uint64_t)((1LL<<significandbits)-1));
result /= (1LL<<significandbits);
result += 1.0f;
unsigned bias = (unsigned)(1<<(expbits-1)) - 1;
long long shift = (long long)((i>>significandbits) & (uint64_t)((1LL<<expbits)-1)) - bias;
while(shift > 0) { result *= 2.0; --shift; }
while(shift < 0) { result /= 2.0; ++shift; }
result *= ((i>>(bits-1))&1)? -1.0: 1.0;
return result;
}
/* Float */
#define FLOAT_NAN 0xffc00000
#define FLOAT_INF 0x7f800000
#define FLOAT_NEG_INF 0xff800000
#define FLOAT_NEG_ZERO 0x80000000
ENCODE_BINARY(Float) {
UA_Float f = *src;
u32 encoded;
/* cppcheck-suppress duplicateExpression */
if(f != f) encoded = FLOAT_NAN;
else if(f == 0.0f) encoded = signbit(f) ? FLOAT_NEG_ZERO : 0;
else if(f/f != f/f) encoded = f > 0 ? FLOAT_INF : FLOAT_NEG_INF;
else encoded = (u32)pack754(f, 32, 8);
return ENCODE_DIRECT(&encoded, UInt32);
}
DECODE_BINARY(Float) {
u32 decoded;
status ret = DECODE_DIRECT(&decoded, UInt32);
if(ret != UA_STATUSCODE_GOOD)
return ret;
if(decoded == 0) *dst = 0.0f;
else if(decoded == FLOAT_NEG_ZERO) *dst = -0.0f;
else if(decoded == FLOAT_INF) *dst = INFINITY;
else if(decoded == FLOAT_NEG_INF) *dst = -INFINITY;
else if((decoded >= 0x7f800001 && decoded <= 0x7fffffff) ||
(decoded >= 0xff800001)) *dst = NAN;
else *dst = (UA_Float)unpack754(decoded, 32, 8);
return UA_STATUSCODE_GOOD;
}
/* Double */
#define DOUBLE_NAN 0xfff8000000000000L
#define DOUBLE_INF 0x7ff0000000000000L
#define DOUBLE_NEG_INF 0xfff0000000000000L
#define DOUBLE_NEG_ZERO 0x8000000000000000L
ENCODE_BINARY(Double) {
UA_Double d = *src;
u64 encoded;
/* cppcheck-suppress duplicateExpression */
if(d != d) encoded = DOUBLE_NAN;
else if(d == 0.0) encoded = signbit(d) ? DOUBLE_NEG_ZERO : 0;
else if(d/d != d/d) encoded = d > 0 ? DOUBLE_INF : DOUBLE_NEG_INF;
else encoded = pack754(d, 64, 11);
return ENCODE_DIRECT(&encoded, UInt64);
}
DECODE_BINARY(Double) {
u64 decoded;
status ret = DECODE_DIRECT(&decoded, UInt64);
if(ret != UA_STATUSCODE_GOOD)
return ret;
if(decoded == 0) *dst = 0.0;
else if(decoded == DOUBLE_NEG_ZERO) *dst = -0.0;
else if(decoded == DOUBLE_INF) *dst = INFINITY;
else if(decoded == DOUBLE_NEG_INF) *dst = -INFINITY;
else if((decoded >= 0x7ff0000000000001L && decoded <= 0x7fffffffffffffffL) ||
(decoded >= 0xfff0000000000001L)) *dst = NAN;
else *dst = (UA_Double)unpack754(decoded, 64, 11);
return UA_STATUSCODE_GOOD;
}
#endif
/******************/
/* Array Handling */
/******************/
static status
Array_encodeBinaryOverlayable(uintptr_t ptr, size_t length,
size_t elementMemSize, Ctx *ctx) {
/* Store the number of already encoded elements */
size_t finished = 0;
/* Loop as long as more elements remain than fit into the chunk */
while(ctx->end < ctx->pos + (elementMemSize * (length-finished))) {
size_t possible = ((uintptr_t)ctx->end - (uintptr_t)ctx->pos) / (sizeof(u8) * elementMemSize);
size_t possibleMem = possible * elementMemSize;
memcpy(ctx->pos, (void*)ptr, possibleMem);
ctx->pos += possibleMem;
ptr += possibleMem;
finished += possible;
status ret = exchangeBuffer(ctx);
ctx->oldpos = NULL; /* Set the sentinel so that no upper stack frame
* with a saved pos attempts to exchange from an
* invalid position in the old buffer. */
if(ret != UA_STATUSCODE_GOOD)
return ret;
}
/* Encode the remaining elements */
memcpy(ctx->pos, (void*)ptr, elementMemSize * (length-finished));
ctx->pos += elementMemSize * (length-finished);
return UA_STATUSCODE_GOOD;
}
static status
Array_encodeBinaryComplex(uintptr_t ptr, size_t length,
const UA_DataType *type, Ctx *ctx) {
/* Encode every element */
for(size_t i = 0; i < length; ++i) {
status ret = encodeWithExchangeBuffer((const void*)ptr, type, ctx);
ptr += type->memSize;
if(ret != UA_STATUSCODE_GOOD)
return ret; /* Unrecoverable fail */
}
return UA_STATUSCODE_GOOD;
}
static status
Array_encodeBinary(const void *src, size_t length,
const UA_DataType *type, Ctx *ctx) {
/* Check and convert the array length to int32 */
i32 signed_length = -1;
if(length > UA_INT32_MAX)
return UA_STATUSCODE_BADINTERNALERROR;
if(length > 0)
signed_length = (i32)length;
else if(src == UA_EMPTY_ARRAY_SENTINEL)
signed_length = 0;
/* Encode the array length */
status ret = ENCODE_WITHEXCHANGE(&signed_length, UA_TYPES_INT32);
/* Quit early? */
if(ret != UA_STATUSCODE_GOOD || length == 0)
return ret;
/* Encode the content */
if(!type->overlayable)
return Array_encodeBinaryComplex((uintptr_t)src, length, type, ctx);
return Array_encodeBinaryOverlayable((uintptr_t)src, length, type->memSize, ctx);
}
static status
Array_decodeBinary(void *UA_RESTRICT *UA_RESTRICT dst, size_t *out_length,
const UA_DataType *type, Ctx *ctx) {
/* Decode the length */
i32 signed_length;
status ret = DECODE_DIRECT(&signed_length, UInt32); /* Int32 */
if(ret != UA_STATUSCODE_GOOD)
return ret;
/* Return early for empty arrays */
if(signed_length <= 0) {
*out_length = 0;
if(signed_length < 0)
*dst = NULL;
else
*dst = UA_EMPTY_ARRAY_SENTINEL;
return UA_STATUSCODE_GOOD;
}
/* Filter out arrays that can obviously not be decoded, because the message
* is too small for the array length. This prevents the allocation of very
* long arrays for bogus messages.*/
size_t length = (size_t)signed_length;
if(ctx->pos + ((type->memSize * length) / 32) > ctx->end)
return UA_STATUSCODE_BADDECODINGERROR;
/* Allocate memory */
*dst = UA_calloc(length, type->memSize);
if(!*dst)
return UA_STATUSCODE_BADOUTOFMEMORY;
if(type->overlayable) {
/* memcpy overlayable array */
if(ctx->end < ctx->pos + (type->memSize * length)) {
UA_free(*dst);
*dst = NULL;
return UA_STATUSCODE_BADDECODINGERROR;
}
memcpy(*dst, ctx->pos, type->memSize * length);
ctx->pos += type->memSize * length;
} else {
/* Decode array members */
uintptr_t ptr = (uintptr_t)*dst;
for(size_t i = 0; i < length; ++i) {
ret = decodeBinaryJumpTable[type->typeKind]((void*)ptr, type, ctx);
if(ret != UA_STATUSCODE_GOOD) {
/* +1 because last element is also already initialized */
UA_Array_delete(*dst, i+1, type);
*dst = NULL;
return ret;
}
ptr += type->memSize;
}
}
*out_length = length;
return UA_STATUSCODE_GOOD;
}
/*****************/
/* Builtin Types */
/*****************/
ENCODE_BINARY(String) {
return Array_encodeBinary(src->data, src->length, &UA_TYPES[UA_TYPES_BYTE], ctx);
}
DECODE_BINARY(String) {
return Array_decodeBinary((void**)&dst->data, &dst->length, &UA_TYPES[UA_TYPES_BYTE], ctx);
}
/* Guid */
ENCODE_BINARY(Guid) {
status ret = UA_STATUSCODE_GOOD;
ret |= ENCODE_DIRECT(&src->data1, UInt32);
ret |= ENCODE_DIRECT(&src->data2, UInt16);
ret |= ENCODE_DIRECT(&src->data3, UInt16);
if(ctx->pos + (8*sizeof(u8)) > ctx->end)
return UA_STATUSCODE_BADENCODINGLIMITSEXCEEDED;
memcpy(ctx->pos, src->data4, 8*sizeof(u8));
ctx->pos += 8;
return ret;
}
DECODE_BINARY(Guid) {
status ret = UA_STATUSCODE_GOOD;
ret |= DECODE_DIRECT(&dst->data1, UInt32);
ret |= DECODE_DIRECT(&dst->data2, UInt16);
ret |= DECODE_DIRECT(&dst->data3, UInt16);
if(ctx->pos + (8*sizeof(u8)) > ctx->end)
return UA_STATUSCODE_BADDECODINGERROR;
memcpy(dst->data4, ctx->pos, 8*sizeof(u8));
ctx->pos += 8;
return ret;
}
/* NodeId */
#define UA_NODEIDTYPE_NUMERIC_TWOBYTE 0u
#define UA_NODEIDTYPE_NUMERIC_FOURBYTE 1u
#define UA_NODEIDTYPE_NUMERIC_COMPLETE 2u
#define UA_EXPANDEDNODEID_SERVERINDEX_FLAG 0x40u
#define UA_EXPANDEDNODEID_NAMESPACEURI_FLAG 0x80u
/* For ExpandedNodeId, we prefill the encoding mask. */
static status
NodeId_encodeBinaryWithEncodingMask(UA_NodeId const *src, u8 encoding, Ctx *ctx) {
status ret = UA_STATUSCODE_GOOD;
switch(src->identifierType) {
case UA_NODEIDTYPE_NUMERIC:
if(src->identifier.numeric > UA_UINT16_MAX || src->namespaceIndex > UA_BYTE_MAX) {
encoding |= UA_NODEIDTYPE_NUMERIC_COMPLETE;
ret |= ENCODE_DIRECT(&encoding, Byte);
ret |= ENCODE_DIRECT(&src->namespaceIndex, UInt16);
ret |= ENCODE_DIRECT(&src->identifier.numeric, UInt32);
} else if(src->identifier.numeric > UA_BYTE_MAX || src->namespaceIndex > 0) {
encoding |= UA_NODEIDTYPE_NUMERIC_FOURBYTE;
ret |= ENCODE_DIRECT(&encoding, Byte);
u8 nsindex = (u8)src->namespaceIndex;
ret |= ENCODE_DIRECT(&nsindex, Byte);
u16 identifier16 = (u16)src->identifier.numeric;
ret |= ENCODE_DIRECT(&identifier16, UInt16);
} else {
encoding |= UA_NODEIDTYPE_NUMERIC_TWOBYTE;
ret |= ENCODE_DIRECT(&encoding, Byte);
u8 identifier8 = (u8)src->identifier.numeric;
ret |= ENCODE_DIRECT(&identifier8, Byte);
}
break;
case UA_NODEIDTYPE_STRING:encoding |= (u8)UA_NODEIDTYPE_STRING;
ret |= ENCODE_DIRECT(&encoding, Byte);
ret |= ENCODE_DIRECT(&src->namespaceIndex, UInt16);
if(ret != UA_STATUSCODE_GOOD)
return ret;
ret = ENCODE_DIRECT(&src->identifier.string, String);
break;
case UA_NODEIDTYPE_GUID:encoding |= (u8)UA_NODEIDTYPE_GUID;
ret |= ENCODE_DIRECT(&encoding, Byte);
ret |= ENCODE_DIRECT(&src->namespaceIndex, UInt16);
ret |= ENCODE_DIRECT(&src->identifier.guid, Guid);
break;
case UA_NODEIDTYPE_BYTESTRING:encoding |= (u8)UA_NODEIDTYPE_BYTESTRING;
ret |= ENCODE_DIRECT(&encoding, Byte);
ret |= ENCODE_DIRECT(&src->namespaceIndex, UInt16);
if(ret != UA_STATUSCODE_GOOD)
return ret;
ret = ENCODE_DIRECT(&src->identifier.byteString, String); /* ByteString */
break;
default:
return UA_STATUSCODE_BADINTERNALERROR;
}
return ret;
}
ENCODE_BINARY(NodeId) {
return NodeId_encodeBinaryWithEncodingMask(src, 0, ctx);
}
DECODE_BINARY(NodeId) {
u8 dstByte = 0, encodingByte = 0;
u16 dstUInt16 = 0;
/* Decode the encoding bitfield */
status ret = DECODE_DIRECT(&encodingByte, Byte);
if(ret != UA_STATUSCODE_GOOD)
return ret;
/* Filter out the bits used only for ExpandedNodeIds */
encodingByte &= (u8)~(u8)(UA_EXPANDEDNODEID_SERVERINDEX_FLAG |
UA_EXPANDEDNODEID_NAMESPACEURI_FLAG);
/* Decode the namespace and identifier */
switch(encodingByte) {
case UA_NODEIDTYPE_NUMERIC_TWOBYTE:
dst->identifierType = UA_NODEIDTYPE_NUMERIC;
ret = DECODE_DIRECT(&dstByte, Byte);
dst->identifier.numeric = dstByte;
dst->namespaceIndex = 0;
break;
case UA_NODEIDTYPE_NUMERIC_FOURBYTE:
dst->identifierType = UA_NODEIDTYPE_NUMERIC;
ret |= DECODE_DIRECT(&dstByte, Byte);
dst->namespaceIndex = dstByte;
ret |= DECODE_DIRECT(&dstUInt16, UInt16);
dst->identifier.numeric = dstUInt16;
break;
case UA_NODEIDTYPE_NUMERIC_COMPLETE:
dst->identifierType = UA_NODEIDTYPE_NUMERIC;
ret |= DECODE_DIRECT(&dst->namespaceIndex, UInt16);
ret |= DECODE_DIRECT(&dst->identifier.numeric, UInt32);
break;
case UA_NODEIDTYPE_STRING:
dst->identifierType = UA_NODEIDTYPE_STRING;
ret |= DECODE_DIRECT(&dst->namespaceIndex, UInt16);
ret |= DECODE_DIRECT(&dst->identifier.string, String);
break;
case UA_NODEIDTYPE_GUID:
dst->identifierType = UA_NODEIDTYPE_GUID;
ret |= DECODE_DIRECT(&dst->namespaceIndex, UInt16);
ret |= DECODE_DIRECT(&dst->identifier.guid, Guid);
break;
case UA_NODEIDTYPE_BYTESTRING:
dst->identifierType = UA_NODEIDTYPE_BYTESTRING;
ret |= DECODE_DIRECT(&dst->namespaceIndex, UInt16);
ret |= DECODE_DIRECT(&dst->identifier.byteString, String); /* ByteString */
break;
default:
ret |= UA_STATUSCODE_BADINTERNALERROR;
break;
}
return ret;
}
/* ExpandedNodeId */
ENCODE_BINARY(ExpandedNodeId) {
/* Set up the encoding mask */
u8 encoding = 0;
if((void*)src->namespaceUri.data > UA_EMPTY_ARRAY_SENTINEL)
encoding |= UA_EXPANDEDNODEID_NAMESPACEURI_FLAG;
if(src->serverIndex > 0)
encoding |= UA_EXPANDEDNODEID_SERVERINDEX_FLAG;
/* Encode the NodeId */
status ret = NodeId_encodeBinaryWithEncodingMask(&src->nodeId, encoding, ctx);
if(ret != UA_STATUSCODE_GOOD)
return ret;
/* Encode the namespace. */
if((void*)src->namespaceUri.data > UA_EMPTY_ARRAY_SENTINEL) {
ret = ENCODE_DIRECT(&src->namespaceUri, String);
if(ret != UA_STATUSCODE_GOOD)
return ret;
}
/* Encode the serverIndex */
if(src->serverIndex > 0)
ret = ENCODE_WITHEXCHANGE(&src->serverIndex, UA_TYPES_UINT32);
return ret;
}
DECODE_BINARY(ExpandedNodeId) {
/* Decode the encoding mask */
if(ctx->pos >= ctx->end)
return UA_STATUSCODE_BADDECODINGERROR;
u8 encoding = *ctx->pos;
/* Decode the NodeId */
status ret = DECODE_DIRECT(&dst->nodeId, NodeId);
/* Decode the NamespaceUri */
if(encoding & UA_EXPANDEDNODEID_NAMESPACEURI_FLAG) {
dst->nodeId.namespaceIndex = 0;
ret |= DECODE_DIRECT(&dst->namespaceUri, String);
}
/* Decode the ServerIndex */
if(encoding & UA_EXPANDEDNODEID_SERVERINDEX_FLAG)
ret |= DECODE_DIRECT(&dst->serverIndex, UInt32);
return ret;
}
/* QualifiedName */
ENCODE_BINARY(QualifiedName) {
status ret = ENCODE_DIRECT(&src->namespaceIndex, UInt16);
ret |= ENCODE_DIRECT(&src->name, String);
return ret;
}
DECODE_BINARY(QualifiedName) {
status ret = DECODE_DIRECT(&dst->namespaceIndex, UInt16);
ret |= DECODE_DIRECT(&dst->name, String);
return ret;
}
/* LocalizedText */
#define UA_LOCALIZEDTEXT_ENCODINGMASKTYPE_LOCALE 0x01u
#define UA_LOCALIZEDTEXT_ENCODINGMASKTYPE_TEXT 0x02u
ENCODE_BINARY(LocalizedText) {
/* Set up the encoding mask */
u8 encoding = 0;
if(src->locale.data)
encoding |= UA_LOCALIZEDTEXT_ENCODINGMASKTYPE_LOCALE;
if(src->text.data)
encoding |= UA_LOCALIZEDTEXT_ENCODINGMASKTYPE_TEXT;
/* Encode the encoding byte */
status ret = ENCODE_DIRECT(&encoding, Byte);
if(ret != UA_STATUSCODE_GOOD)
return ret;
/* Encode the strings */
if(encoding & UA_LOCALIZEDTEXT_ENCODINGMASKTYPE_LOCALE)
ret |= ENCODE_DIRECT(&src->locale, String);
if(encoding & UA_LOCALIZEDTEXT_ENCODINGMASKTYPE_TEXT)
ret |= ENCODE_DIRECT(&src->text, String);
return ret;
}
DECODE_BINARY(LocalizedText) {
/* Decode the encoding mask */
u8 encoding = 0;
status ret = DECODE_DIRECT(&encoding, Byte);
/* Decode the content */
if(encoding & UA_LOCALIZEDTEXT_ENCODINGMASKTYPE_LOCALE)
ret |= DECODE_DIRECT(&dst->locale, String);
if(encoding & UA_LOCALIZEDTEXT_ENCODINGMASKTYPE_TEXT)
ret |= DECODE_DIRECT(&dst->text, String);
return ret;
}
/* The binary encoding has a different nodeid from the data type. So it is not
* possible to reuse UA_findDataType */
static const UA_DataType *
UA_findDataTypeByBinaryInternal(const UA_NodeId *typeId, Ctx *ctx) {
/* We only store a numeric identifier for the encoding nodeid of data types */
if(typeId->identifierType != UA_NODEIDTYPE_NUMERIC)
return NULL;
/* Always look in built-in types first
* (may contain data types from all namespaces) */
for(size_t i = 0; i < UA_TYPES_COUNT; ++i) {
if(UA_TYPES[i].binaryEncodingId == typeId->identifier.numeric &&
UA_TYPES[i].typeId.namespaceIndex == typeId->namespaceIndex)
return &UA_TYPES[i];
}
const UA_DataTypeArray *customTypes = ctx->customTypes;
while(customTypes) {
for(size_t i = 0; i < customTypes->typesSize; ++i) {
if(customTypes->types[i].binaryEncodingId == typeId->identifier.numeric &&
customTypes->types[i].typeId.namespaceIndex == typeId->namespaceIndex)
return &customTypes->types[i];
}
customTypes = customTypes->next;
}
return NULL;
}
const UA_DataType *
UA_findDataTypeByBinary(const UA_NodeId *typeId) {
Ctx ctx;
ctx.customTypes = NULL;
return UA_findDataTypeByBinaryInternal(typeId, &ctx);
}
/* ExtensionObject */
ENCODE_BINARY(ExtensionObject) {
u8 encoding = (u8)src->encoding;
/* No content or already encoded content. */
if(encoding <= UA_EXTENSIONOBJECT_ENCODED_XML) {
status ret = ENCODE_DIRECT(&src->content.encoded.typeId, NodeId);
if(ret != UA_STATUSCODE_GOOD)
return ret;
ret = ENCODE_WITHEXCHANGE(&encoding, UA_TYPES_BYTE);
if(ret != UA_STATUSCODE_GOOD)
return ret;
switch(src->encoding) {
case UA_EXTENSIONOBJECT_ENCODED_NOBODY:
break;
case UA_EXTENSIONOBJECT_ENCODED_BYTESTRING:
case UA_EXTENSIONOBJECT_ENCODED_XML:
ret = ENCODE_DIRECT(&src->content.encoded.body, String); /* ByteString */
break;
default:
ret = UA_STATUSCODE_BADINTERNALERROR;
}
return ret;
}
/* Cannot encode with no data or no type description */
if(!src->content.decoded.type || !src->content.decoded.data)
return UA_STATUSCODE_BADENCODINGERROR;
/* Write the NodeId for the binary encoded type. The NodeId is always
* numeric, so no buffer replacement is taking place. */
UA_NodeId typeId = src->content.decoded.type->typeId;
if(typeId.identifierType != UA_NODEIDTYPE_NUMERIC)
return UA_STATUSCODE_BADENCODINGERROR;
typeId.identifier.numeric = src->content.decoded.type->binaryEncodingId;
status ret = ENCODE_DIRECT(&typeId, NodeId);
/* Write the encoding byte */
encoding = UA_EXTENSIONOBJECT_ENCODED_BYTESTRING;
ret |= ENCODE_DIRECT(&encoding, Byte);
/* Compute the content length */
const UA_DataType *contentType = src->content.decoded.type;
size_t len = UA_calcSizeBinary(src->content.decoded.data, contentType);
/* Encode the content length */
if(len > UA_INT32_MAX)
return UA_STATUSCODE_BADENCODINGERROR;
i32 signed_len = (i32)len;
ret |= ENCODE_DIRECT(&signed_len, UInt32); /* Int32 */
/* Return early upon failures (no buffer exchange until here) */
if(ret != UA_STATUSCODE_GOOD)
return ret;
/* Encode the content */
return encodeWithExchangeBuffer(src->content.decoded.data, contentType, ctx);
}
static status
ExtensionObject_decodeBinaryContent(UA_ExtensionObject *dst, const UA_NodeId *typeId, Ctx *ctx) {
/* Lookup the datatype */
const UA_DataType *type = UA_findDataTypeByBinaryInternal(typeId, ctx);
/* Unknown type, just take the binary content */
if(!type) {
dst->encoding = UA_EXTENSIONOBJECT_ENCODED_BYTESTRING;
UA_NodeId_copy(typeId, &dst->content.encoded.typeId);
return DECODE_DIRECT(&dst->content.encoded.body, String); /* ByteString */
}
/* Allocate memory */
dst->content.decoded.data = UA_new(type);
if(!dst->content.decoded.data)
return UA_STATUSCODE_BADOUTOFMEMORY;
/* Jump over the length field (TODO: check if the decoded length matches) */
ctx->pos += 4;
/* Decode */
dst->encoding = UA_EXTENSIONOBJECT_DECODED;
dst->content.decoded.type = type;
return decodeBinaryJumpTable[type->typeKind](dst->content.decoded.data, type, ctx);
}
DECODE_BINARY(ExtensionObject) {
u8 encoding = 0;
UA_NodeId binTypeId; /* Can contain a string nodeid. But no corresponding
* type is then found in open62541. We only store
* numerical nodeids of the binary encoding identifier.
* The extenionobject will be decoded to contain a
* binary blob. */
UA_NodeId_init(&binTypeId);
status ret = UA_STATUSCODE_GOOD;
ret |= DECODE_DIRECT(&binTypeId, NodeId);
ret |= DECODE_DIRECT(&encoding, Byte);
if(ret != UA_STATUSCODE_GOOD) {
UA_NodeId_clear(&binTypeId);
return ret;
}
switch(encoding) {
case UA_EXTENSIONOBJECT_ENCODED_BYTESTRING:
ret = ExtensionObject_decodeBinaryContent(dst, &binTypeId, ctx);
UA_NodeId_deleteMembers(&binTypeId);
break;
case UA_EXTENSIONOBJECT_ENCODED_NOBODY:
dst->encoding = (UA_ExtensionObjectEncoding)encoding;
dst->content.encoded.typeId = binTypeId; /* move to dst */
dst->content.encoded.body = UA_BYTESTRING_NULL;
break;
case UA_EXTENSIONOBJECT_ENCODED_XML:
dst->encoding = (UA_ExtensionObjectEncoding)encoding;
dst->content.encoded.typeId = binTypeId; /* move to dst */
ret = DECODE_DIRECT(&dst->content.encoded.body, String); /* ByteString */
if(ret != UA_STATUSCODE_GOOD)
UA_NodeId_clear(&dst->content.encoded.typeId);
break;
default:
UA_NodeId_clear(&binTypeId);
ret = UA_STATUSCODE_BADDECODINGERROR;
break;
}
return ret;
}
/* Variant */
static status
Variant_encodeBinaryWrapExtensionObject(const UA_Variant *src,
const UA_Boolean isArray, Ctx *ctx) {
/* Default to 1 for a scalar. */
size_t length = 1;
/* Encode the array length if required */
status ret = UA_STATUSCODE_GOOD;
if(isArray) {
if(src->arrayLength > UA_INT32_MAX)
return UA_STATUSCODE_BADENCODINGERROR;
length = src->arrayLength;
i32 encodedLength = (i32)src->arrayLength;
ret = ENCODE_DIRECT(&encodedLength, UInt32); /* Int32 */
if(ret != UA_STATUSCODE_GOOD)
return ret;
}
/* Set up the ExtensionObject */
UA_ExtensionObject eo;
UA_ExtensionObject_init(&eo);
eo.encoding = UA_EXTENSIONOBJECT_DECODED;
eo.content.decoded.type = src->type;
const u16 memSize = src->type->memSize;
uintptr_t ptr = (uintptr_t)src->data;
/* Iterate over the array */
for(size_t i = 0; i < length && ret == UA_STATUSCODE_GOOD; ++i) {
eo.content.decoded.data = (void*)ptr;
ret = encodeWithExchangeBuffer(&eo, &UA_TYPES[UA_TYPES_EXTENSIONOBJECT], ctx);
ptr += memSize;
}
return ret;
}
enum UA_VARIANT_ENCODINGMASKTYPE {
UA_VARIANT_ENCODINGMASKTYPE_TYPEID_MASK = 0x3Fu, /* bits 0:5 */
UA_VARIANT_ENCODINGMASKTYPE_DIMENSIONS = (u8)(0x01u << 6u), /* bit 6 */
UA_VARIANT_ENCODINGMASKTYPE_ARRAY = (u8)(0x01u << 7u) /* bit 7 */
};
ENCODE_BINARY(Variant) {
/* Quit early for the empty variant */
u8 encoding = 0;
if(!src->type)
return ENCODE_DIRECT(&encoding, Byte);
/* Set the content type in the encoding mask */
const UA_Boolean isBuiltin = (src->type->typeKind <= UA_DATATYPEKIND_DIAGNOSTICINFO);
const UA_Boolean isEnum = (src->type->typeKind == UA_DATATYPEKIND_ENUM);
if(isBuiltin)
encoding = (u8)(encoding | (u8)((u8)UA_VARIANT_ENCODINGMASKTYPE_TYPEID_MASK & (u8)(src->type->typeKind + 1u)));
else if(isEnum)
encoding = (u8)(encoding | (u8)((u8)UA_VARIANT_ENCODINGMASKTYPE_TYPEID_MASK & (u8)(UA_TYPES_INT32 + 1u)));
else
encoding = (u8)(encoding | (u8)((u8)UA_VARIANT_ENCODINGMASKTYPE_TYPEID_MASK & (u8)(UA_TYPES_EXTENSIONOBJECT + 1u)));
/* Set the array type in the encoding mask */
const UA_Boolean isArray = src->arrayLength > 0 || src->data <= UA_EMPTY_ARRAY_SENTINEL;
const UA_Boolean hasDimensions = isArray && src->arrayDimensionsSize > 0;
if(isArray) {
encoding |= (u8)UA_VARIANT_ENCODINGMASKTYPE_ARRAY;
if(hasDimensions)
encoding |= (u8)UA_VARIANT_ENCODINGMASKTYPE_DIMENSIONS;
}
/* Encode the encoding byte */
status ret = ENCODE_DIRECT(&encoding, Byte);
if(ret != UA_STATUSCODE_GOOD)
return ret;
/* Encode the content */
if(!isBuiltin && !isEnum)
ret = Variant_encodeBinaryWrapExtensionObject(src, isArray, ctx);
else if(!isArray)
ret = encodeWithExchangeBuffer(src->data, src->type, ctx);
else
ret = Array_encodeBinary(src->data, src->arrayLength, src->type, ctx);
/* Encode the array dimensions */
if(hasDimensions && ret == UA_STATUSCODE_GOOD)
ret = Array_encodeBinary(src->arrayDimensions, src->arrayDimensionsSize,
&UA_TYPES[UA_TYPES_INT32], ctx);
return ret;
}
static status
Variant_decodeBinaryUnwrapExtensionObject(UA_Variant *dst, Ctx *ctx) {
/* Save the position in the ByteString. If unwrapping is not possible, start
* from here to decode a normal ExtensionObject. */
u8 *old_pos = ctx->pos;
/* Decode the DataType */
UA_NodeId typeId;
UA_NodeId_init(&typeId);
status ret = DECODE_DIRECT(&typeId, NodeId);
if(ret != UA_STATUSCODE_GOOD)
return ret;
/* Decode the EncodingByte */
u8 encoding;
ret = DECODE_DIRECT(&encoding, Byte);
if(ret != UA_STATUSCODE_GOOD) {
UA_NodeId_clear(&typeId);
return ret;
}
/* Search for the datatype. Default to ExtensionObject. */
if(encoding == UA_EXTENSIONOBJECT_ENCODED_BYTESTRING &&
(dst->type = UA_findDataTypeByBinaryInternal(&typeId, ctx)) != NULL) {
/* Jump over the length field (TODO: check if length matches) */
ctx->pos += 4;
} else {
/* Reset and decode as ExtensionObject */
dst->type = &UA_TYPES[UA_TYPES_EXTENSIONOBJECT];
ctx->pos = old_pos;
UA_NodeId_clear(&typeId);
}
/* Allocate memory */
dst->data = UA_new(dst->type);
if(!dst->data)
return UA_STATUSCODE_BADOUTOFMEMORY;
/* Decode the content */
return decodeBinaryJumpTable[dst->type->typeKind](dst->data, dst->type, ctx);
}
/* The resulting variant always has the storagetype UA_VARIANT_DATA. */
DECODE_BINARY(Variant) {
/* Decode the encoding byte */
u8 encodingByte;
status ret = DECODE_DIRECT(&encodingByte, Byte);
if(ret != UA_STATUSCODE_GOOD)
return ret;
/* Return early for an empty variant (was already _inited) */
if(encodingByte == 0)
return UA_STATUSCODE_GOOD;
/* Does the variant contain an array? */
const UA_Boolean isArray = (encodingByte & (u8)UA_VARIANT_ENCODINGMASKTYPE_ARRAY) > 0;
/* Get the datatype of the content. The type must be a builtin data type.
* All not-builtin types are wrapped in an ExtensionObject. The "type kind"
* for types up to DiagnsticInfo equals to the index in the encoding
* byte. */
size_t typeKind = (size_t)((encodingByte & (u8)UA_VARIANT_ENCODINGMASKTYPE_TYPEID_MASK) - 1);
if(typeKind > UA_DATATYPEKIND_DIAGNOSTICINFO)
return UA_STATUSCODE_BADDECODINGERROR;
/* A variant cannot contain a variant. But it can contain an array of
* variants */
if(typeKind == UA_DATATYPEKIND_VARIANT && !isArray)
return UA_STATUSCODE_BADDECODINGERROR;
/* Check the recursion limit */
if(ctx->depth > UA_ENCODING_MAX_RECURSION)
return UA_STATUSCODE_BADENCODINGERROR;
ctx->depth++;
/* Decode the content */
dst->type = &UA_TYPES[typeKind];
if(isArray) {
ret = Array_decodeBinary(&dst->data, &dst->arrayLength, dst->type, ctx);
} else if(typeKind != UA_DATATYPEKIND_EXTENSIONOBJECT) {
dst->data = UA_new(dst->type);
if(!dst->data)
return UA_STATUSCODE_BADOUTOFMEMORY;
ret = decodeBinaryJumpTable[typeKind](dst->data, dst->type, ctx);
} else {
ret = Variant_decodeBinaryUnwrapExtensionObject(dst, ctx);
}
/* Decode array dimensions */
if(isArray && (encodingByte & (u8)UA_VARIANT_ENCODINGMASKTYPE_DIMENSIONS) > 0)
ret |= Array_decodeBinary((void**)&dst->arrayDimensions, &dst->arrayDimensionsSize,
&UA_TYPES[UA_TYPES_INT32], ctx);
ctx->depth--;
return ret;
}
/* DataValue */
ENCODE_BINARY(DataValue) {
/* Set up the encoding mask */
u8 encodingMask = src->hasValue;
encodingMask |= (u8)(src->hasStatus << 1u);
encodingMask |= (u8)(src->hasSourceTimestamp << 2u);
encodingMask |= (u8)(src->hasServerTimestamp << 3u);
encodingMask |= (u8)(src->hasSourcePicoseconds << 4u);
encodingMask |= (u8)(src->hasServerPicoseconds << 5u);
/* Encode the encoding byte */
status ret = ENCODE_DIRECT(&encodingMask, Byte);
if(ret != UA_STATUSCODE_GOOD)
return ret;
/* Encode the variant. */
if(src->hasValue) {
ret = ENCODE_DIRECT(&src->value, Variant);
if(ret != UA_STATUSCODE_GOOD)
return ret;
}
if(src->hasStatus)
ret |= ENCODE_WITHEXCHANGE(&src->status, UA_TYPES_STATUSCODE);
if(src->hasSourceTimestamp)
ret |= ENCODE_WITHEXCHANGE(&src->sourceTimestamp, UA_TYPES_DATETIME);
if(src->hasSourcePicoseconds)
ret |= ENCODE_WITHEXCHANGE(&src->sourcePicoseconds, UA_TYPES_UINT16);
if(src->hasServerTimestamp)
ret |= ENCODE_WITHEXCHANGE(&src->serverTimestamp, UA_TYPES_DATETIME);
if(src->hasServerPicoseconds)
ret |= ENCODE_WITHEXCHANGE(&src->serverPicoseconds, UA_TYPES_UINT16);
return ret;
}
#define MAX_PICO_SECONDS 9999
DECODE_BINARY(DataValue) {
/* Decode the encoding mask */
u8 encodingMask;
status ret = DECODE_DIRECT(&encodingMask, Byte);
if(ret != UA_STATUSCODE_GOOD)
return ret;
/* Check the recursion limit */
if(ctx->depth > UA_ENCODING_MAX_RECURSION)
return UA_STATUSCODE_BADENCODINGERROR;
ctx->depth++;
/* Decode the content */
if(encodingMask & 0x01u) {
dst->hasValue = true;
ret |= DECODE_DIRECT(&dst->value, Variant);
}
if(encodingMask & 0x02u) {
dst->hasStatus = true;
ret |= DECODE_DIRECT(&dst->status, UInt32); /* StatusCode */
}
if(encodingMask & 0x04u) {
dst->hasSourceTimestamp = true;
ret |= DECODE_DIRECT(&dst->sourceTimestamp, UInt64); /* DateTime */
}
if(encodingMask & 0x10u) {
dst->hasSourcePicoseconds = true;
ret |= DECODE_DIRECT(&dst->sourcePicoseconds, UInt16);
if(dst->sourcePicoseconds > MAX_PICO_SECONDS)
dst->sourcePicoseconds = MAX_PICO_SECONDS;
}
if(encodingMask & 0x08u) {
dst->hasServerTimestamp = true;
ret |= DECODE_DIRECT(&dst->serverTimestamp, UInt64); /* DateTime */
}
if(encodingMask & 0x20u) {
dst->hasServerPicoseconds = true;
ret |= DECODE_DIRECT(&dst->serverPicoseconds, UInt16);
if(dst->serverPicoseconds > MAX_PICO_SECONDS)
dst->serverPicoseconds = MAX_PICO_SECONDS;
}
ctx->depth--;
return ret;
}
/* DiagnosticInfo */
ENCODE_BINARY(DiagnosticInfo) {
/* Set up the encoding mask */
u8 encodingMask = src->hasSymbolicId;
encodingMask |= (u8)(src->hasNamespaceUri << 1u);
encodingMask |= (u8)(src->hasLocalizedText << 2u);
encodingMask |= (u8)(src->hasLocale << 3u);
encodingMask |= (u8)(src->hasAdditionalInfo << 4u);
encodingMask |= (u8)(src->hasInnerDiagnosticInfo << 5u);
/* Encode the numeric content */
status ret = ENCODE_DIRECT(&encodingMask, Byte);
if(src->hasSymbolicId)
ret |= ENCODE_DIRECT(&src->symbolicId, UInt32); /* Int32 */
if(src->hasNamespaceUri)
ret |= ENCODE_DIRECT(&src->namespaceUri, UInt32); /* Int32 */
if(src->hasLocalizedText)
ret |= ENCODE_DIRECT(&src->localizedText, UInt32); /* Int32 */
if(src->hasLocale)
ret |= ENCODE_DIRECT(&src->locale, UInt32); /* Int32 */
if(ret != UA_STATUSCODE_GOOD)
return ret;
/* Encode the additional info */
if(src->hasAdditionalInfo) {
ret = ENCODE_DIRECT(&src->additionalInfo, String);
if(ret != UA_STATUSCODE_GOOD)
return ret;
}
/* Encode the inner status code */
if(src->hasInnerStatusCode) {
ret = ENCODE_WITHEXCHANGE(&src->innerStatusCode, UA_TYPES_UINT32);
if(ret != UA_STATUSCODE_GOOD)
return ret;
}
/* Encode the inner diagnostic info */
if(src->hasInnerDiagnosticInfo)
// innerDiagnosticInfo is already a pointer, so don't use the & reference here
ret = ENCODE_WITHEXCHANGE(src->innerDiagnosticInfo,
UA_TYPES_DIAGNOSTICINFO);
return ret;
}
DECODE_BINARY(DiagnosticInfo) {
/* Decode the encoding mask */
u8 encodingMask;
status ret = DECODE_DIRECT(&encodingMask, Byte);
if(ret != UA_STATUSCODE_GOOD)
return ret;
/* Decode the content */
if(encodingMask & 0x01u) {
dst->hasSymbolicId = true;
ret |= DECODE_DIRECT(&dst->symbolicId, UInt32); /* Int32 */
}
if(encodingMask & 0x02u) {
dst->hasNamespaceUri = true;
ret |= DECODE_DIRECT(&dst->namespaceUri, UInt32); /* Int32 */
}
if(encodingMask & 0x04u) {
dst->hasLocalizedText = true;
ret |= DECODE_DIRECT(&dst->localizedText, UInt32); /* Int32 */
}
if(encodingMask & 0x08u) {
dst->hasLocale = true;
ret |= DECODE_DIRECT(&dst->locale, UInt32); /* Int32 */
}
if(encodingMask & 0x10u) {
dst->hasAdditionalInfo = true;
ret |= DECODE_DIRECT(&dst->additionalInfo, String);
}
if(encodingMask & 0x20u) {
dst->hasInnerStatusCode = true;
ret |= DECODE_DIRECT(&dst->innerStatusCode, UInt32); /* StatusCode */
}
if(encodingMask & 0x40u) {
/* innerDiagnosticInfo is allocated on the heap */
dst->innerDiagnosticInfo = (UA_DiagnosticInfo*)
UA_calloc(1, sizeof(UA_DiagnosticInfo));
if(!dst->innerDiagnosticInfo)
return UA_STATUSCODE_BADOUTOFMEMORY;
dst->hasInnerDiagnosticInfo = true;
/* Check the recursion limit */
if(ctx->depth > UA_ENCODING_MAX_RECURSION)
return UA_STATUSCODE_BADENCODINGERROR;
ctx->depth++;
ret |= DECODE_DIRECT(dst->innerDiagnosticInfo, DiagnosticInfo);
ctx->depth--;
}
return ret;
}
static status
encodeBinaryStruct(const void *src, const UA_DataType *type, Ctx *ctx) {
/* Check the recursion limit */
if(ctx->depth > UA_ENCODING_MAX_RECURSION)
return UA_STATUSCODE_BADENCODINGERROR;
ctx->depth++;
uintptr_t ptr = (uintptr_t)src;
status ret = UA_STATUSCODE_GOOD;
u8 membersSize = type->membersSize;
const UA_DataType *typelists[2] = { UA_TYPES, &type[-type->typeIndex] };
/* Loop over members */
for(size_t i = 0; i < membersSize; ++i) {
const UA_DataTypeMember *m = &type->members[i];
const UA_DataType *mt = &typelists[!m->namespaceZero][m->memberTypeIndex];
ptr += m->padding;
/* Array. Buffer-exchange is done inside Array_encodeBinary if required. */
if(m->isArray) {
const size_t length = *((const size_t*)ptr);
ptr += sizeof(size_t);
ret = Array_encodeBinary(*(void *UA_RESTRICT const *)ptr, length, mt, ctx);
ptr += sizeof(void*);
continue;
}
/* Scalar */
ret = encodeWithExchangeBuffer((const void*)ptr, mt, ctx);
ptr += mt->memSize;
}
ctx->depth--;
return ret;
}
static status
encodeBinaryNotImplemented(const void *src, const UA_DataType *type, Ctx *ctx) {
(void)src, (void)type, (void)ctx;
return UA_STATUSCODE_BADNOTIMPLEMENTED;
}
/********************/
/* Structured Types */
/********************/
const encodeBinarySignature encodeBinaryJumpTable[UA_DATATYPEKINDS] = {
(encodeBinarySignature)Boolean_encodeBinary,
(encodeBinarySignature)Byte_encodeBinary, /* SByte */
(encodeBinarySignature)Byte_encodeBinary,
(encodeBinarySignature)UInt16_encodeBinary, /* Int16 */
(encodeBinarySignature)UInt16_encodeBinary,
(encodeBinarySignature)UInt32_encodeBinary, /* Int32 */
(encodeBinarySignature)UInt32_encodeBinary,
(encodeBinarySignature)UInt64_encodeBinary, /* Int64 */
(encodeBinarySignature)UInt64_encodeBinary,
(encodeBinarySignature)Float_encodeBinary,
(encodeBinarySignature)Double_encodeBinary,
(encodeBinarySignature)String_encodeBinary,
(encodeBinarySignature)UInt64_encodeBinary, /* DateTime */
(encodeBinarySignature)Guid_encodeBinary,
(encodeBinarySignature)String_encodeBinary, /* ByteString */
(encodeBinarySignature)String_encodeBinary, /* XmlElement */
(encodeBinarySignature)NodeId_encodeBinary,
(encodeBinarySignature)ExpandedNodeId_encodeBinary,
(encodeBinarySignature)UInt32_encodeBinary, /* StatusCode */
(encodeBinarySignature)QualifiedName_encodeBinary,
(encodeBinarySignature)LocalizedText_encodeBinary,
(encodeBinarySignature)ExtensionObject_encodeBinary,
(encodeBinarySignature)DataValue_encodeBinary,
(encodeBinarySignature)Variant_encodeBinary,
(encodeBinarySignature)DiagnosticInfo_encodeBinary,
(encodeBinarySignature)encodeBinaryNotImplemented, /* Decimal */
(encodeBinarySignature)UInt32_encodeBinary, /* Enumeration */
(encodeBinarySignature)encodeBinaryStruct,
(encodeBinarySignature)encodeBinaryNotImplemented, /* Structure with Optional Fields */
(encodeBinarySignature)encodeBinaryStruct, /* Union */
(encodeBinarySignature)encodeBinaryStruct /* BitfieldCluster */
};
status
UA_encodeBinary(const void *src, const UA_DataType *type,
u8 **bufPos, const u8 **bufEnd,
UA_exchangeEncodeBuffer exchangeCallback, void *exchangeHandle) {
/* Set up the context */
Ctx ctx;
ctx.pos = *bufPos;
ctx.end = *bufEnd;
ctx.depth = 0;
ctx.exchangeBufferCallback = exchangeCallback;
ctx.exchangeBufferCallbackHandle = exchangeHandle;
if(!ctx.pos)
return UA_STATUSCODE_BADINVALIDARGUMENT;
/* Encode */
status ret = encodeWithExchangeBuffer(src, type, &ctx);
/* Set the new buffer position for the output. Beware that the buffer might
* have been exchanged internally. */
*bufPos = ctx.pos;
*bufEnd = ctx.end;
return ret;
}
static status
decodeBinaryNotImplemented(void *dst, const UA_DataType *type, Ctx *ctx) {
(void)dst, (void)type, (void)ctx;
return UA_STATUSCODE_BADNOTIMPLEMENTED;
}
static status
decodeBinaryStructure(void *dst, const UA_DataType *type, Ctx *ctx) {
/* Check the recursion limit */
if(ctx->depth > UA_ENCODING_MAX_RECURSION)
return UA_STATUSCODE_BADENCODINGERROR;
ctx->depth++;
uintptr_t ptr = (uintptr_t)dst;
status ret = UA_STATUSCODE_GOOD;
u8 membersSize = type->membersSize;
const UA_DataType *typelists[2] = { UA_TYPES, &type[-type->typeIndex] };
/* Loop over members */
for(size_t i = 0; i < membersSize && ret == UA_STATUSCODE_GOOD; ++i) {
const UA_DataTypeMember *m = &type->members[i];
const UA_DataType *mt = &typelists[!m->namespaceZero][m->memberTypeIndex];
ptr += m->padding;
/* Array */
if(m->isArray) {
size_t *length = (size_t*)ptr;
ptr += sizeof(size_t);
ret = Array_decodeBinary((void *UA_RESTRICT *UA_RESTRICT)ptr, length, mt , ctx);
ptr += sizeof(void*);
continue;
}
/* Scalar */
ret = decodeBinaryJumpTable[mt->typeKind]((void *UA_RESTRICT)ptr, mt, ctx);
ptr += mt->memSize;
}
ctx->depth--;
return ret;
}
const decodeBinarySignature decodeBinaryJumpTable[UA_DATATYPEKINDS] = {
(decodeBinarySignature)Boolean_decodeBinary,
(decodeBinarySignature)Byte_decodeBinary, /* SByte */
(decodeBinarySignature)Byte_decodeBinary,
(decodeBinarySignature)UInt16_decodeBinary, /* Int16 */
(decodeBinarySignature)UInt16_decodeBinary,
(decodeBinarySignature)UInt32_decodeBinary, /* Int32 */
(decodeBinarySignature)UInt32_decodeBinary,
(decodeBinarySignature)UInt64_decodeBinary, /* Int64 */
(decodeBinarySignature)UInt64_decodeBinary,
(decodeBinarySignature)Float_decodeBinary,
(decodeBinarySignature)Double_decodeBinary,
(decodeBinarySignature)String_decodeBinary,
(decodeBinarySignature)UInt64_decodeBinary, /* DateTime */
(decodeBinarySignature)Guid_decodeBinary,
(decodeBinarySignature)String_decodeBinary, /* ByteString */
(decodeBinarySignature)String_decodeBinary, /* XmlElement */
(decodeBinarySignature)NodeId_decodeBinary,
(decodeBinarySignature)ExpandedNodeId_decodeBinary,
(decodeBinarySignature)UInt32_decodeBinary, /* StatusCode */
(decodeBinarySignature)QualifiedName_decodeBinary,
(decodeBinarySignature)LocalizedText_decodeBinary,
(decodeBinarySignature)ExtensionObject_decodeBinary,
(decodeBinarySignature)DataValue_decodeBinary,
(decodeBinarySignature)Variant_decodeBinary,
(decodeBinarySignature)DiagnosticInfo_decodeBinary,
(decodeBinarySignature)decodeBinaryNotImplemented, /* Decimal */
(decodeBinarySignature)UInt32_decodeBinary, /* Enumeration */
(decodeBinarySignature)decodeBinaryStructure,
(decodeBinarySignature)decodeBinaryNotImplemented, /* Structure with optional fields */
(decodeBinarySignature)decodeBinaryNotImplemented, /* Union */
(decodeBinarySignature)decodeBinaryNotImplemented /* BitfieldCluster */
};
status
UA_decodeBinary(const UA_ByteString *src, size_t *offset, void *dst,
const UA_DataType *type, const UA_DataTypeArray *customTypes) {
/* Set up the context */
Ctx ctx;
ctx.pos = &src->data[*offset];
ctx.end = &src->data[src->length];
ctx.depth = 0;
ctx.customTypes = customTypes;
/* Decode */
memset(dst, 0, type->memSize); /* Initialize the value */
status ret = decodeBinaryJumpTable[type->typeKind](dst, type, &ctx);
if(ret == UA_STATUSCODE_GOOD) {
/* Set the new offset */
*offset = (size_t)(ctx.pos - src->data) / sizeof(u8);
} else {
/* Clean up */
UA_clear(dst, type);
memset(dst, 0, type->memSize);
}
return ret;
}
/**
* Compute the Message Size
* ------------------------
* The following methods are used to compute the length of a datum in binary
* encoding. */
static size_t
Array_calcSizeBinary(const void *src, size_t length, const UA_DataType *type) {
size_t s = 4; /* length */
if(type->overlayable) {
s += type->memSize * length;
return s;
}
uintptr_t ptr = (uintptr_t)src;
for(size_t i = 0; i < length; ++i) {
s += calcSizeBinaryJumpTable[type->typeKind]((const void*)ptr, type);
ptr += type->memSize;
}
return s;
}
static size_t calcSizeBinary1(const void *_, const UA_DataType *__) { (void)_, (void)__; return 1; }
static size_t calcSizeBinary2(const void *_, const UA_DataType *__) { (void)_, (void)__; return 2; }
static size_t calcSizeBinary4(const void *_, const UA_DataType *__) { (void)_, (void)__; return 4; }
static size_t calcSizeBinary8(const void *_, const UA_DataType *__) { (void)_, (void)__; return 8; }
CALCSIZE_BINARY(String) { return 4 + src->length; }
CALCSIZE_BINARY(Guid) { return 16; }
CALCSIZE_BINARY(NodeId) {
size_t s = 1; /* Encoding byte */
switch(src->identifierType) {
case UA_NODEIDTYPE_NUMERIC:
if(src->identifier.numeric > UA_UINT16_MAX || src->namespaceIndex > UA_BYTE_MAX) {
s += 6;
} else if(src->identifier.numeric > UA_BYTE_MAX || src->namespaceIndex > 0) {
s += 3;
} else {
s += 1;
}
break;
case UA_NODEIDTYPE_BYTESTRING:
case UA_NODEIDTYPE_STRING:
s += 2;
s += String_calcSizeBinary(&src->identifier.string, NULL);
break;
case UA_NODEIDTYPE_GUID:
s += 18;
break;
default:
return 0;
}
return s;
}
CALCSIZE_BINARY(ExpandedNodeId) {
size_t s = NodeId_calcSizeBinary(&src->nodeId, NULL);
if(src->namespaceUri.length > 0)
s += String_calcSizeBinary(&src->namespaceUri, NULL);
if(src->serverIndex > 0)
s += 4;
return s;
}
CALCSIZE_BINARY(QualifiedName) {
return 2 + String_calcSizeBinary(&src->name, NULL);
}
CALCSIZE_BINARY(LocalizedText) {
size_t s = 1; /* Encoding byte */
if(src->locale.data)
s += String_calcSizeBinary(&src->locale, NULL);
if(src->text.data)
s += String_calcSizeBinary(&src->text, NULL);
return s;
}
CALCSIZE_BINARY(ExtensionObject) {
size_t s = 1; /* Encoding byte */
/* Encoded content */
if(src->encoding <= UA_EXTENSIONOBJECT_ENCODED_XML) {
s += NodeId_calcSizeBinary(&src->content.encoded.typeId, NULL);
switch(src->encoding) {
case UA_EXTENSIONOBJECT_ENCODED_NOBODY:
break;
case UA_EXTENSIONOBJECT_ENCODED_BYTESTRING:
case UA_EXTENSIONOBJECT_ENCODED_XML:
s += String_calcSizeBinary(&src->content.encoded.body, NULL);
break;
default:
return 0;
}
return s;
}
/* Decoded content */
if(!src->content.decoded.type || !src->content.decoded.data)
return 0;
if(src->content.decoded.type->typeId.identifierType != UA_NODEIDTYPE_NUMERIC)
return 0;
s += NodeId_calcSizeBinary(&src->content.decoded.type->typeId, NULL); /* Type encoding length */
s += 4; /* Encoding length field */
const UA_DataType *type = src->content.decoded.type;
s += calcSizeBinaryJumpTable[type->typeKind](src->content.decoded.data, type); /* Encoding length */
return s;
}
CALCSIZE_BINARY(Variant) {
size_t s = 1; /* Encoding byte */
if(!src->type)
return s;
const UA_Boolean isArray = src->arrayLength > 0 || src->data <= UA_EMPTY_ARRAY_SENTINEL;
if(isArray)
s += Array_calcSizeBinary(src->data, src->arrayLength, src->type);
else
s += calcSizeBinaryJumpTable[src->type->typeKind](src->data, src->type);
const UA_Boolean isBuiltin = (src->type->typeKind <= UA_DATATYPEKIND_DIAGNOSTICINFO);
const UA_Boolean isEnum = (src->type->typeKind == UA_DATATYPEKIND_ENUM);
if(!isBuiltin && !isEnum) {
/* The type is wrapped inside an extensionobject */
/* (NodeId + encoding byte + extension object length) * array length */
size_t length = isArray ? src->arrayLength : 1;
s += (NodeId_calcSizeBinary(&src->type->typeId, NULL) + 1 + 4) * length;
}
const UA_Boolean hasDimensions = isArray && src->arrayDimensionsSize > 0;
if(hasDimensions)
s += Array_calcSizeBinary(src->arrayDimensions, src->arrayDimensionsSize,
&UA_TYPES[UA_TYPES_INT32]);
return s;
}
CALCSIZE_BINARY(DataValue) {
size_t s = 1; /* Encoding byte */
if(src->hasValue)
s += Variant_calcSizeBinary(&src->value, NULL);
if(src->hasStatus)
s += 4;
if(src->hasSourceTimestamp)
s += 8;
if(src->hasSourcePicoseconds)
s += 2;
if(src->hasServerTimestamp)
s += 8;
if(src->hasServerPicoseconds)
s += 2;
return s;
}
CALCSIZE_BINARY(DiagnosticInfo) {
size_t s = 1; /* Encoding byte */
if(src->hasSymbolicId)
s += 4;
if(src->hasNamespaceUri)
s += 4;
if(src->hasLocalizedText)
s += 4;
if(src->hasLocale)
s += 4;
if(src->hasAdditionalInfo)
s += String_calcSizeBinary(&src->additionalInfo, NULL);
if(src->hasInnerStatusCode)
s += 4;
if(src->hasInnerDiagnosticInfo)
s += DiagnosticInfo_calcSizeBinary(src->innerDiagnosticInfo, NULL);
return s;
}
static size_t
calcSizeBinaryStructure(const void *p, const UA_DataType *type) {
size_t s = 0;
uintptr_t ptr = (uintptr_t)p;
u8 membersSize = type->membersSize;
const UA_DataType *typelists[2] = { UA_TYPES, &type[-type->typeIndex] };
/* Loop over members */
for(size_t i = 0; i < membersSize; ++i) {
const UA_DataTypeMember *member = &type->members[i];
const UA_DataType *membertype = &typelists[!member->namespaceZero][member->memberTypeIndex];
ptr += member->padding;
/* Array */
if(member->isArray) {
const size_t length = *((const size_t*)ptr);
ptr += sizeof(size_t);
s += Array_calcSizeBinary(*(void *UA_RESTRICT const *)ptr, length, membertype);
ptr += sizeof(void*);
continue;
}
/* Scalar */
s += calcSizeBinaryJumpTable[membertype->typeKind]((const void*)ptr, membertype);
ptr += membertype->memSize;
}
return s;
}
static size_t
calcSizeBinaryNotImplemented(const void *p, const UA_DataType *type) {
(void)p, (void)type;
return 0;
}
const calcSizeBinarySignature calcSizeBinaryJumpTable[UA_DATATYPEKINDS] = {
(calcSizeBinarySignature)calcSizeBinary1, /* Boolean */
(calcSizeBinarySignature)calcSizeBinary1, /* SByte */
(calcSizeBinarySignature)calcSizeBinary1, /* Byte */
(calcSizeBinarySignature)calcSizeBinary2, /* Int16 */
(calcSizeBinarySignature)calcSizeBinary2, /* UInt16 */
(calcSizeBinarySignature)calcSizeBinary4, /* Int32 */
(calcSizeBinarySignature)calcSizeBinary4, /* UInt32 */
(calcSizeBinarySignature)calcSizeBinary8, /* Int64 */
(calcSizeBinarySignature)calcSizeBinary8, /* UInt64 */
(calcSizeBinarySignature)calcSizeBinary4, /* Float */
(calcSizeBinarySignature)calcSizeBinary8, /* Double */
(calcSizeBinarySignature)String_calcSizeBinary,
(calcSizeBinarySignature)calcSizeBinary8, /* DateTime */
(calcSizeBinarySignature)Guid_calcSizeBinary,
(calcSizeBinarySignature)String_calcSizeBinary, /* ByteString */
(calcSizeBinarySignature)String_calcSizeBinary, /* XmlElement */
(calcSizeBinarySignature)NodeId_calcSizeBinary,
(calcSizeBinarySignature)ExpandedNodeId_calcSizeBinary,
(calcSizeBinarySignature)calcSizeBinary4, /* StatusCode */
(calcSizeBinarySignature)QualifiedName_calcSizeBinary,
(calcSizeBinarySignature)LocalizedText_calcSizeBinary,
(calcSizeBinarySignature)ExtensionObject_calcSizeBinary,
(calcSizeBinarySignature)DataValue_calcSizeBinary,
(calcSizeBinarySignature)Variant_calcSizeBinary,
(calcSizeBinarySignature)DiagnosticInfo_calcSizeBinary,
(calcSizeBinarySignature)calcSizeBinaryNotImplemented, /* Decimal */
(calcSizeBinarySignature)calcSizeBinary4, /* Enumeration */
(calcSizeBinarySignature)calcSizeBinaryStructure,
(calcSizeBinarySignature)calcSizeBinaryNotImplemented, /* Structure with Optional Fields */
(calcSizeBinarySignature)calcSizeBinaryNotImplemented, /* Union */
(calcSizeBinarySignature)calcSizeBinaryNotImplemented /* BitfieldCluster */
};
size_t
UA_calcSizeBinary(const void *p, const UA_DataType *type) {
return calcSizeBinaryJumpTable[type->typeKind](p, type);
}