Browse Source

Chunking callback (#534)

* changed signature of encoding functions -> new callback interface, **dst to allow buffer changing in callback

* changed signature of callback: removed not needed parameters

* added first implementation for send-chunk-callback function. Some fields need to be filled (messagetype etc)

* added UA_SecureChannel_sendChunk, added enumeration for Message type, added request type

* added function call to encode function

* some clean up, reworked enumerations

* some trivial fixes @FlorianPalm

* changed string encoding function to split up long srings

* merging...

* added missing file

* added variable for chunking.

* added observing used chunks

* little fix in string_encodeBinary

* changed size of chunk count in connection config and increased array size (2000) to test chunking on arrays (stolen from @Stasik0 chunking_longjmp branch)

* changes in connection parameters

* added demo (server_chunking) for chunking

* removed demo configuration

* changed connection send buffer allocation (remoteConf.recvBufferSize to localConf.sendBufferSize)

* changed allocation size of send/recv buffer

* renaming

* added proposal for chunk abort

* fixing array encoding, using array encoding for string encoding, further minor fixes

* removed accidently added file

* fixing typo in demo

* fixing server_chunking (config structure)

* new callback

* simplify chunking

* remove unnecessary changes against master

* fixed compile warning

* fixed errors to get chunking to work again. started unit test for chunking

* chunking should now also work with normal arrays in addition to long strings
(still, there is a mem leak in the unit test)

* added chunked array example

* fixed array encoding problem (don't increase current source buffer pointer if encoding has failed)

* some unit tests for chunking, checking encoding of an array, tnx @FlorianPalm

* added unit test for chunked string

* changed data types to prevent warnings

* removed mem leak in string test

* re-inserted assert for clang

* cosmetics on server_chunking

* corrected cosmetics

* added abort message (chunking)

* removed memleak (still mem problem)

* fixed message size (array encoding works again)

* string encoding works again
test for chunk count and message size (local config is used)

* added evaluation of retvals to prevent encoding on NULL buffer

* removed debug printfs
testing: localConf -> remoteConf

* fixed stateless extension

* split out a separate chunking unit test

* remove the chunking example; as long as the unit test works, end users won't need to see it

* remove some cosmetic changes

* some cosmetic changes

* reverted connection settings

* changed connection settings within the server_chunking example

* cosmetic changes to the chunked encoding

* fix amalgamation build

* cleanup of encoding with chunking

* revert a a change to calloc in ua_types.c

* allow chunking in the standard config

* fix subscriptions with chunking

* use normal array encoding in variants when possible

* fix handling of old_pos pointers for chunking
Julius Pfrommer 8 years ago
parent
commit
c317396f4c

+ 3 - 3
plugins/ua_config_standard.c

@@ -43,12 +43,12 @@ const UA_ServerConfig UA_ServerConfig_standard = {
     .enableUsernamePasswordLogin = true,
     .usernamePasswordLogins = usernamePasswords,
     .usernamePasswordLoginsSize = 2,
-    
-	.publishingIntervalLimits = { .min = 100.0, .max = 3600.0 * 1000.0 },
+
+    .publishingIntervalLimits = { .min = 100.0, .max = 3600.0 * 1000.0 },
     .lifeTimeCountLimits = { .max = 15000, .min = 3 },
     .keepAliveCountLimits = { .max = 100, .min = 1 },
     .maxNotificationsPerPublish = 1000,
-	.samplingIntervalLimits = { .min = 50.0, .max = 24.0 * 3600.0 * 1000.0 },
+    .samplingIntervalLimits = { .min = 50.0, .max = 24.0 * 3600.0 * 1000.0 },
     .queueSizeLimits = { .max = 100, .min = 1 }
 };
 

+ 1 - 1
src/client/ua_client.c

@@ -511,7 +511,7 @@ static UA_StatusCode CloseSecureChannel(UA_Client *client) {
     retval |= UA_SymmetricAlgorithmSecurityHeader_encodeBinary(&symHeader, &message, &offset);
     retval |= UA_SequenceHeader_encodeBinary(&seqHeader, &message, &offset);
     retval |= UA_NodeId_encodeBinary(&typeId, &message, &offset);
-    retval |= UA_encodeBinary(&request, &UA_TYPES[UA_TYPES_CLOSESECURECHANNELREQUEST], &message, &offset);
+    retval |= UA_encodeBinary(&request, &UA_TYPES[UA_TYPES_CLOSESECURECHANNELREQUEST],NULL,NULL, &message, &offset);
 
     msgHeader.messageHeader.messageSize = (UA_UInt32)offset;
     offset = 0;

+ 26 - 23
src/server/ua_services_attribute.c

@@ -393,29 +393,32 @@ void Service_Read(UA_Server *server, UA_Session *session, const UA_ReadRequest *
 
 #ifdef UA_ENABLE_NONSTANDARD_STATELESS
     /* Add an expiry header for caching */
-    if(session->sessionId.namespaceIndex == 0 && session->sessionId.identifierType == UA_NODEIDTYPE_NUMERIC && session->sessionId.identifier.numeric == 0){
-		UA_ExtensionObject additionalHeader;
-		UA_ExtensionObject_init(&additionalHeader);
-		additionalHeader.encoding = UA_EXTENSIONOBJECT_ENCODED_BYTESTRING;
-		additionalHeader.content.encoded.typeId =UA_TYPES[UA_TYPES_VARIANT].typeId;
-
-		UA_Variant variant;
-		UA_Variant_init(&variant);
-
-		UA_DateTime* expireArray = NULL;
-		expireArray = UA_Array_new(request->nodesToReadSize, &UA_TYPES[UA_TYPES_DATETIME]);
-		variant.data = expireArray;
-
-		/*expires in 20 seconds*/
-		for(UA_UInt32 i = 0;i < response->resultsSize;i++) {
-			expireArray[i] = UA_DateTime_now() + 20 * 100 * 1000 * 1000;
-		}
-		UA_Variant_setArray(&variant, expireArray, request->nodesToReadSize, &UA_TYPES[UA_TYPES_DATETIME]);
-
-		size_t offset = 0;
-		UA_ByteString str;
-        UA_ByteString_allocBuffer(&str, 65536);
-        UA_StatusCode retval = UA_encodeBinary(&variant, &UA_TYPES[UA_TYPES_VARIANT], &str, &offset);
+    if(session->sessionId.namespaceIndex == 0 &&
+       session->sessionId.identifierType == UA_NODEIDTYPE_NUMERIC &&
+       session->sessionId.identifier.numeric == 0){
+        UA_ExtensionObject additionalHeader;
+        UA_ExtensionObject_init(&additionalHeader);
+        additionalHeader.encoding = UA_EXTENSIONOBJECT_ENCODED_BYTESTRING;
+        additionalHeader.content.encoded.typeId =UA_TYPES[UA_TYPES_VARIANT].typeId;
+
+        UA_Variant variant;
+        UA_Variant_init(&variant);
+
+        UA_DateTime* expireArray = NULL;
+        expireArray = UA_Array_new(request->nodesToReadSize, &UA_TYPES[UA_TYPES_DATETIME]);
+        variant.data = expireArray;
+
+        /* expires in 20 seconds */
+        for(UA_UInt32 i = 0;i < response->resultsSize;i++) {
+            expireArray[i] = UA_DateTime_now() + 20 * 100 * 1000 * 1000;
+        }
+        UA_Variant_setArray(&variant, expireArray, request->nodesToReadSize, &UA_TYPES[UA_TYPES_DATETIME]);
+
+        size_t offset = 0;
+        UA_ByteString str;
+        UA_ByteString_allocBuffer(&str, UA_calcSizeBinary(&variant, &UA_TYPES[UA_TYPES_VARIANT]));
+        /* No chunking callback for the encoding */
+        UA_StatusCode retval = UA_encodeBinary(&variant, &UA_TYPES[UA_TYPES_VARIANT], NULL, NULL, &str, &offset);
         UA_Array_delete(expireArray, request->nodesToReadSize, &UA_TYPES[UA_TYPES_DATETIME]);
         if(retval == UA_STATUSCODE_GOOD){
             additionalHeader.content.encoded.body.data = str.data;

+ 1 - 1
src/server/ua_subscription.c

@@ -80,7 +80,7 @@ static void SampleCallback(UA_Server *server, UA_MonitoredItem *monitoredItem) {
     }
     size_t encodingOffset = 0;
     retval = UA_encodeBinary(&newvalue->value.value, &UA_TYPES[UA_TYPES_VARIANT],
-                             &newValueAsByteString, &encodingOffset);
+                             NULL, NULL, &newValueAsByteString, &encodingOffset);
 
     /* error or the content has not changed */
     if(retval != UA_STATUSCODE_GOOD ||

+ 1 - 1
src/ua_connection.c

@@ -7,7 +7,7 @@
 // max message size is 64k
 const UA_ConnectionConfig UA_ConnectionConfig_standard =
     {.protocolVersion = 0, .sendBufferSize = 65536, .recvBufferSize = 65536,
-     .maxMessageSize = 65536, .maxChunkCount = 1};
+     .maxMessageSize = 1048576, .maxChunkCount = 16};
 
 void UA_Connection_init(UA_Connection *connection) {
     connection->state = UA_CONNECTION_CLOSED;

+ 106 - 33
src/ua_securechannel.c

@@ -5,6 +5,8 @@
 #include "ua_types_generated_encoding_binary.h"
 #include "ua_transport_generated_encoding_binary.h"
 
+#define UA_SECURE_MESSAGE_HEADER_LENGTH 24
+
 void UA_SecureChannel_init(UA_SecureChannel *channel) {
     UA_MessageSecurityMode_init(&channel->securityMode);
     UA_ChannelSecurityToken_init(&channel->securityToken);
@@ -113,66 +115,137 @@ UA_Session * UA_SecureChannel_getSession(UA_SecureChannel *channel, UA_NodeId *t
     return se->session;
 }
 
-void UA_SecureChannel_revolveTokens(UA_SecureChannel *channel){
-    if(channel->nextSecurityToken.tokenId==0) //no security token issued
+void UA_SecureChannel_revolveTokens(UA_SecureChannel *channel) {
+    if(channel->nextSecurityToken.tokenId == 0) //no security token issued
         return;
 
     //FIXME: not thread-safe
-    //swap tokens
     memcpy(&channel->securityToken, &channel->nextSecurityToken, sizeof(UA_ChannelSecurityToken));
     UA_ChannelSecurityToken_init(&channel->nextSecurityToken);
 }
 
-UA_StatusCode UA_SecureChannel_sendBinaryMessage(UA_SecureChannel *channel, UA_UInt32 requestId,
-                                                  const void *content, const UA_DataType *contentType) {
+static UA_StatusCode
+UA_SecureChannel_sendChunk(UA_ChunkInfo *ci, UA_ByteString *dst, size_t offset) {
+    UA_SecureChannel *channel = ci->channel;
     UA_Connection *connection = channel->connection;
     if(!connection)
-        return UA_STATUSCODE_BADINTERNALERROR;
+       return UA_STATUSCODE_BADINTERNALERROR;
 
-    UA_NodeId typeId = contentType->typeId;
-    if(typeId.identifierType != UA_NODEIDTYPE_NUMERIC)
-        return UA_STATUSCODE_BADINTERNALERROR;
-    typeId.identifier.numeric += UA_ENCODINGOFFSET_BINARY;
+    /* adjust the buffer where the header was hidden */
+    dst->data = &dst->data[-UA_SECURE_MESSAGE_HEADER_LENGTH];
+    dst->length += UA_SECURE_MESSAGE_HEADER_LENGTH;
+    offset += UA_SECURE_MESSAGE_HEADER_LENGTH;
+    ci->messageSizeSoFar += offset;
 
+    UA_Boolean chunkedMsg = (ci->chunksSoFar > 0 || ci->final == false);
+    UA_Boolean abortMsg = ((++ci->chunksSoFar >= connection->remoteConf.maxChunkCount ||
+                            ci->messageSizeSoFar > connection->remoteConf.maxMessageSize)) && chunkedMsg;
+
+    /* Prepare the chunk headers */
     UA_SecureConversationMessageHeader respHeader;
-    respHeader.messageHeader.messageTypeAndChunkType = UA_MESSAGETYPE_MSG + UA_CHUNKTYPE_FINAL;
-    respHeader.messageHeader.messageSize = 0;
     respHeader.secureChannelId = channel->securityToken.channelId;
+    respHeader.messageHeader.messageTypeAndChunkType = ci->messageType;
+    if(!abortMsg) {
+        if(ci->final)
+            respHeader.messageHeader.messageTypeAndChunkType += UA_CHUNKTYPE_FINAL;
+        else
+            respHeader.messageHeader.messageTypeAndChunkType += UA_CHUNKTYPE_INTERMEDIATE;
+    } else {
+        respHeader.messageHeader.messageTypeAndChunkType += UA_CHUNKTYPE_ABORT;
+        ci->abort = true;
+        UA_StatusCode retval = UA_STATUSCODE_BADTCPMESSAGETOOLARGE;
+        UA_String errorMsg = UA_STRING("Encoded message too long");
+        offset = UA_SECURE_MESSAGE_HEADER_LENGTH;
+        UA_UInt32_encodeBinary(&retval,dst,&offset);
+        UA_String_encodeBinary(&errorMsg,dst,&offset);
+    }
+    respHeader.messageHeader.messageSize = (UA_UInt32)offset;
 
     UA_SymmetricAlgorithmSecurityHeader symSecHeader;
     symSecHeader.tokenId = channel->securityToken.tokenId;
 
     UA_SequenceHeader seqHeader;
-    seqHeader.requestId = requestId;
+    seqHeader.requestId = ci->requestId;
+#ifndef UA_ENABLE_MULTITHREADING
+    seqHeader.sequenceNumber = ++channel->sequenceNumber;
+#else
+    seqHeader.sequenceNumber = uatomic_add_return(&channel->sequenceNumber, 1);
+#endif
+
+    /* Encode the header at the beginning of the buffer */
+    size_t offset_header = 0;
+    UA_SecureConversationMessageHeader_encodeBinary(&respHeader, dst, &offset_header);
+    UA_SymmetricAlgorithmSecurityHeader_encodeBinary(&symSecHeader, dst, &offset_header);
+    UA_SequenceHeader_encodeBinary(&seqHeader, dst, &offset_header);
+
+    /* Send the chunk, the buffer is freed in the network layer */
+    dst->length = offset; /* set the buffer length to the content length */
+    connection->send(channel->connection, dst);
+
+    /* Replace with the buffer for the next chunk */
+    if(!ci->final && !ci->abort) {
+        UA_StatusCode retval = connection->getSendBuffer(connection, connection->localConf.sendBufferSize, dst);
+        if(retval != UA_STATUSCODE_GOOD)
+            return retval;
+        /* Hide the header of the buffer, so that the ensuing encoding does not overwrite anything */
+        dst->data = &dst->data[UA_SECURE_MESSAGE_HEADER_LENGTH];
+        dst->length = connection->localConf.sendBufferSize - UA_SECURE_MESSAGE_HEADER_LENGTH;
+    }
+    return UA_STATUSCODE_GOOD;
+}
 
+UA_StatusCode
+UA_SecureChannel_sendBinaryMessage(UA_SecureChannel *channel, UA_UInt32 requestId, const void *content,
+                                   const UA_DataType *contentType) {
+    UA_Connection *connection = channel->connection;
+    if(!connection)
+        return UA_STATUSCODE_BADINTERNALERROR;
+
+    /* Allocate the message buffer */
     UA_ByteString message;
-    UA_StatusCode retval = connection->getSendBuffer(connection, connection->remoteConf.recvBufferSize, &message);
+    UA_StatusCode retval = connection->getSendBuffer(connection, connection->localConf.sendBufferSize, &message);
     if(retval != UA_STATUSCODE_GOOD)
         return retval;
 
-    size_t messagePos = 24; // after the headers
-    retval |= UA_NodeId_encodeBinary(&typeId, &message, &messagePos);
-    retval |= UA_encodeBinary(content, contentType, &message, &messagePos);
+    /* Hide the message beginning where the header will be encoded */
+    message.data = &message.data[UA_SECURE_MESSAGE_HEADER_LENGTH];
+    message.length -= UA_SECURE_MESSAGE_HEADER_LENGTH;
+
+    /* Encode the message type */
+    size_t messagePos = 0;
+    UA_NodeId typeId = contentType->typeId; /* always numeric */
+    typeId.identifier.numeric += UA_ENCODINGOFFSET_BINARY;
+    UA_NodeId_encodeBinary(&typeId, &message, &messagePos);
+
+    /* Encode with the chunking callback */
+    UA_ChunkInfo ci;
+    ci.channel = channel;
+    ci.requestId = requestId;
+    ci.chunksSoFar = 0;
+    ci.messageSizeSoFar = 0;
+    ci.final = false;
+    ci.messageType = UA_MESSAGETYPE_MSG;
+    ci.abort = false;
+    if(typeId.identifier.numeric == 446 || typeId.identifier.numeric == 449)
+        ci.messageType = UA_MESSAGETYPE_OPN;
+    else if(typeId.identifier.numeric == 452 || typeId.identifier.numeric == 455)
+        ci.messageType = UA_MESSAGETYPE_CLO;
+    retval = UA_encodeBinary(content, contentType, (UA_exchangeEncodeBuffer)UA_SecureChannel_sendChunk,
+                             &ci, &message, &messagePos);
+
+    /* Abort message was sent, the buffer is already freed */
+    if(ci.abort)
+        return retval;
 
+    /* Encoding failed, release the message */
     if(retval != UA_STATUSCODE_GOOD) {
+        /* Unhide the beginning of the buffer (header) */
+        message.data = &message.data[-UA_SECURE_MESSAGE_HEADER_LENGTH];
         connection->releaseSendBuffer(connection, &message);
         return retval;
     }
 
-    /* now write the header with the size */
-    respHeader.messageHeader.messageSize = (UA_UInt32)messagePos;
-#ifndef UA_ENABLE_MULTITHREADING
-    seqHeader.sequenceNumber = ++channel->sequenceNumber;
-#else
-    seqHeader.sequenceNumber = uatomic_add_return(&channel->sequenceNumber, 1);
-#endif
-
-    messagePos = 0;
-    UA_SecureConversationMessageHeader_encodeBinary(&respHeader, &message, &messagePos);
-    UA_SymmetricAlgorithmSecurityHeader_encodeBinary(&symSecHeader, &message, &messagePos);
-    UA_SequenceHeader_encodeBinary(&seqHeader, &message, &messagePos);
-    message.length = respHeader.messageHeader.messageSize;
-
-    retval = connection->send(connection, &message);
-    return retval;
+    /* Encoding finished, send the final chunk */
+    ci.final = UA_TRUE;
+    return UA_SecureChannel_sendChunk(&ci, &message, messagePos);
 }

+ 12 - 0
src/ua_securechannel.h

@@ -14,6 +14,7 @@ struct SessionEntry {
     UA_Session *session; // Just a pointer. The session is held in the session manager or the client
 };
 
+/* For chunked requests */
 struct ChunkEntry {
     LIST_ENTRY(ChunkEntry) pointers;
     UA_UInt32 requestId;
@@ -21,6 +22,17 @@ struct ChunkEntry {
     UA_ByteString bytes;
 };
 
+/* For chunked responses */
+typedef struct {
+    UA_SecureChannel *channel;
+    UA_UInt32 requestId;
+    UA_UInt32 messageType;
+    UA_UInt16 chunksSoFar;
+    size_t messageSizeSoFar;
+    UA_Boolean final;
+    UA_Boolean abort;
+} UA_ChunkInfo;
+
 struct UA_SecureChannel {
     UA_MessageSecurityMode  securityMode;
     UA_ChannelSecurityToken securityToken; // the channelId is contained in the securityToken

+ 126 - 101
src/ua_types_encoding_binary.c

@@ -1,11 +1,11 @@
 #include "ua_util.h"
-#include "ua_types_generated.h"
 #include "ua_types_encoding_binary.h"
+#include "ua_types_generated.h"
 
 /* We give pointers to the current position and the last position in the buffer
    instead of a string with an offset. */
 typedef UA_Byte * UA_RESTRICT * const bufpos;
-typedef const UA_Byte * const bufend;
+typedef UA_Byte const * bufend;
 
 /* Only structured types need a type-pointer for decoding. We pass the pointer
    in a thread-local variable to minimize the signature of the de-/encoding
@@ -22,6 +22,22 @@ static const UA_decodeBinarySignature decodeBinaryJumpTable[UA_BUILTIN_TYPES_COU
 typedef size_t (*UA_calcSizeBinarySignature)(const void *UA_RESTRICT p, const UA_DataType *contenttype);
 static const UA_calcSizeBinarySignature calcSizeBinaryJumpTable[UA_BUILTIN_TYPES_COUNT + 1];
 
+/* Thread-local buffers used for exchanging the buffer for chunking */
+UA_THREAD_LOCAL UA_ByteString *encodeBuf; /* the original buffer */
+UA_THREAD_LOCAL UA_exchangeEncodeBuffer exchangeBufferCallback;
+UA_THREAD_LOCAL void *exchangeBufferCallbackHandle;
+
+static UA_StatusCode exchangeBuffer(bufpos pos, bufend *end) {
+    if(!exchangeBufferCallback)
+        return UA_STATUSCODE_BADENCODINGERROR;
+    size_t offset = ((uintptr_t)*pos - (uintptr_t)encodeBuf->data) / sizeof(UA_Byte);
+    UA_StatusCode retval = exchangeBufferCallback(exchangeBufferCallbackHandle, encodeBuf, offset);
+    /* set pos and end in order to continue encoding */
+    *pos = encodeBuf->data;
+    *end = &encodeBuf->data[encodeBuf->length];
+    return retval;
+}
+
 /*****************/
 /* Integer Types */
 /*****************/
@@ -354,19 +370,38 @@ Array_encodeBinary(const void *src, size_t length, const UA_DataType *contenttyp
         return retval;
 
     if(contenttype->overlayable) {
-        if(end < *pos + (contenttype->memSize * length))
-            return UA_STATUSCODE_BADENCODINGERROR;
-        memcpy(*pos, src, contenttype->memSize * length);
-        (*pos) += contenttype->memSize * length;
-        return retval;
+        size_t i = 0; /* the number of already encoded elements */
+        while(end < *pos + (contenttype->memSize * (length-i))) {
+            /* not enough space, need to exchange the buffer */
+            size_t elements = ((uintptr_t)end - (uintptr_t)*pos) / (sizeof(UA_Byte) * contenttype->memSize);
+            memcpy(*pos, src, contenttype->memSize * elements);
+            *pos += contenttype->memSize * elements;
+            i += elements;
+            retval = exchangeBuffer(pos, &end);
+            if(retval != UA_STATUSCODE_GOOD)
+                return retval;
+        }
+        /* encode the remaining elements */
+        memcpy(*pos, src, contenttype->memSize * (length-i));
+        *pos += contenttype->memSize * (length-i);
+        return UA_STATUSCODE_GOOD;
     }
 
     uintptr_t ptr = (uintptr_t)src;
     size_t encode_index = contenttype->builtin ? contenttype->typeIndex : UA_BUILTIN_TYPES_COUNT;
     for(size_t i = 0; i < length && retval == UA_STATUSCODE_GOOD; i++) {
         pass_type = contenttype;
+        UA_Byte *oldpos = *pos;
         retval = encodeBinaryJumpTable[encode_index]((const void*)ptr, pos, end);
         ptr += contenttype->memSize;
+        if(retval == UA_STATUSCODE_BADENCODINGERROR) {
+            /* exchange the buffer and try to encode the same element once more */
+            *pos = oldpos;
+            retval = exchangeBuffer(pos, &end);
+            /* Repeat encoding of the same element */
+            ptr -= contenttype->memSize;
+            i--;
+        }
     }
     return retval;
 }
@@ -382,7 +417,7 @@ Array_decodeBinary(bufpos pos, bufend end, UA_Int32 signed_length, void *UA_REST
         return UA_STATUSCODE_GOOD;
     }
     size_t length = (size_t)signed_length;
-        
+
     /* filter out arrays that can obviously not be parsed, because the message
        is too small */
     if(*pos + ((contenttype->memSize * length) / 32) > end)
@@ -421,25 +456,9 @@ Array_decodeBinary(bufpos pos, bufend end, UA_Int32 signed_length, void *UA_REST
 /* Builtin Types */
 /*****************/
 
-static UA_StatusCode
+static UA_INLINE UA_StatusCode
 String_encodeBinary(UA_String const *src, bufpos pos, bufend end) {
-    if(*pos + sizeof(UA_Int32) + src->length > end)
-        return UA_STATUSCODE_BADENCODINGERROR;
-    if(src->length > UA_INT32_MAX)
-        return UA_STATUSCODE_BADINTERNALERROR;
-    UA_StatusCode retval;
-    if((void*)src->data <= UA_EMPTY_ARRAY_SENTINEL) {
-        UA_Int32 signed_length = -1;
-        if(src->data == UA_EMPTY_ARRAY_SENTINEL)
-            signed_length = 0;
-        retval = Int32_encodeBinary(&signed_length, pos, end);
-    } else {
-        UA_Int32 signed_length = (UA_Int32)src->length;
-        retval = Int32_encodeBinary(&signed_length, pos, end);
-        memcpy(*pos, src->data, src->length);
-        *pos += src->length;
-    }
-    return retval;
+    return Array_encodeBinary(src->data, src->length, &UA_TYPES[UA_TYPES_BYTE], pos, end);
 }
 
 static UA_INLINE UA_StatusCode
@@ -778,8 +797,6 @@ ExtensionObject_decodeBinary(bufpos pos, bufend end, UA_ExtensionObject *dst) {
 }
 
 /* Variant */
-/* Types that are not builtin get wrapped in an ExtensionObject */
-
 enum UA_VARIANT_ENCODINGMASKTYPE {
     UA_VARIANT_ENCODINGMASKTYPE_TYPEID_MASK = 0x3F,        // bits 0:5
     UA_VARIANT_ENCODINGMASKTYPE_DIMENSIONS  = (0x01 << 6), // bit 6
@@ -793,67 +810,61 @@ Variant_encodeBinary(UA_Variant const *src, bufpos pos, bufend end) {
     const UA_Boolean isArray = src->arrayLength > 0 || src->data <= UA_EMPTY_ARRAY_SENTINEL;
     const UA_Boolean hasDimensions = isArray && src->arrayDimensionsSize > 0;
     const UA_Boolean isBuiltin = src->type->builtin;
+
+    /* Encode the encodingbyte */
     UA_Byte encodingByte = 0;
     if(isArray) {
         encodingByte |= UA_VARIANT_ENCODINGMASKTYPE_ARRAY;
         if(hasDimensions)
             encodingByte |= UA_VARIANT_ENCODINGMASKTYPE_DIMENSIONS;
     }
-
-    UA_NodeId typeId;
-    UA_NodeId_init(&typeId);
-    size_t encode_index = src->type->typeIndex;
-    if(isBuiltin) {
-        /* Do an extra lookup. Enums are encoded as UA_UInt32. */
-        encodingByte |= UA_VARIANT_ENCODINGMASKTYPE_TYPEID_MASK &
-            (UA_Byte) (src->type->typeIndex + 1);
-    } else {
-        encode_index = UA_BUILTIN_TYPES_COUNT;
-        /* wrap the datatype in an extensionobject */
-        encodingByte |= UA_VARIANT_ENCODINGMASKTYPE_TYPEID_MASK & (UA_Byte) 22;
-        typeId = src->type->typeId;
-        if(typeId.identifierType != UA_NODEIDTYPE_NUMERIC)
-            return UA_STATUSCODE_BADINTERNALERROR;
-        typeId.identifier.numeric += UA_ENCODINGOFFSET_BINARY;
-    }
+    if(isBuiltin)
+        encodingByte |= UA_VARIANT_ENCODINGMASKTYPE_TYPEID_MASK & (UA_Byte) (src->type->typeIndex + 1);
+    else
+        encodingByte |= UA_VARIANT_ENCODINGMASKTYPE_TYPEID_MASK & (UA_Byte) 22; /* ExtensionObject */
     UA_StatusCode retval = Byte_encodeBinary(&encodingByte, pos, end);
 
-    size_t length = src->arrayLength;
-    if(!isArray) {
-        length = 1;
+    /* Encode the content */
+    if(isBuiltin) {
+        if(!isArray) {
+            size_t encode_index = src->type->typeIndex;
+            pass_type = src->type;
+            retval |= encodeBinaryJumpTable[encode_index](src->data, pos, end);
+        } else
+            retval |= Array_encodeBinary(src->data, src->arrayLength, src->type, pos, end);
     } else {
+        /* Wrap not-builtin elements into an extensionobject */
         if(src->arrayDimensionsSize > UA_INT32_MAX)
             return UA_STATUSCODE_BADINTERNALERROR;
-        UA_Int32 encodeLength = -1;
-        if(src->arrayLength > 0)
-            encodeLength = (UA_Int32)src->arrayLength;
-        else if(src->data == UA_EMPTY_ARRAY_SENTINEL)
-            encodeLength = 0;
-        retval |= Int32_encodeBinary(&encodeLength, pos, end);
-    }
-
-    uintptr_t ptr = (uintptr_t)src->data;
-    const UA_UInt16 memSize = src->type->memSize;
-    for(size_t i = 0; i < length; i++) {
-        UA_Byte *old_pos; // before encoding the actual content
-        if(!isBuiltin) {
-            /* The type is wrapped inside an extensionobject */
-            retval |= NodeId_encodeBinary(&typeId, pos, end);
-            UA_Byte eoEncoding = UA_EXTENSIONOBJECT_ENCODED_BYTESTRING;
-            retval |= Byte_encodeBinary(&eoEncoding, pos, end);
-            (*pos) += 4;
-            old_pos = *pos;
+        size_t length = 1;
+        if(isArray) {
+            length = src->arrayLength;
+            UA_Int32 encodedLength = (UA_Int32)src->arrayLength;
+            retval |= Int32_encodeBinary(&encodedLength, pos, end);
         }
-        pass_type = src->type;
-        retval |= encodeBinaryJumpTable[encode_index]((const void*)ptr, pos, end);
-        if(!isBuiltin) {
-            /* Jump back and print the length of the extension object */
-            UA_Int32 encodingLength = (UA_Int32)(((uintptr_t)*pos - (uintptr_t)old_pos) / sizeof(UA_Byte));
-            old_pos -= 4;
-            retval |= Int32_encodeBinary(&encodingLength, &old_pos, end);
+        UA_ExtensionObject eo;
+        UA_ExtensionObject_init(&eo);
+        eo.encoding = UA_EXTENSIONOBJECT_DECODED;
+        eo.content.decoded.type = src->type;
+        const UA_UInt16 memSize = src->type->memSize;
+        uintptr_t ptr = (uintptr_t)src->data;
+        for(size_t i = 0; i < length && retval == UA_STATUSCODE_GOOD; i++) {
+            UA_Byte *oldpos = *pos;
+            eo.content.decoded.data = (void*)ptr;
+            retval |= ExtensionObject_encodeBinary(&eo, pos, end);
+            ptr += memSize;
+            if(retval == UA_STATUSCODE_BADENCODINGERROR) {
+                /* exchange/send with the current buffer with chunking */
+                *pos = oldpos;
+                retval = exchangeBuffer(pos, &end);
+                /* encode the same element in the next iteration */
+                i--;
+                ptr -= memSize;
+            }
         }
-        ptr += memSize;
     }
+
+    /* Encode the dimensions */
     if(hasDimensions)
         retval |= Array_encodeBinary(src->arrayDimensions, src->arrayDimensionsSize,
                                      &UA_TYPES[UA_TYPES_INT32], pos, end);
@@ -1080,15 +1091,24 @@ UA_encodeBinaryInternal(const void *src, bufpos pos, bufend end) {
     UA_Byte membersSize = pass_type->membersSize;
     const UA_DataType *localtype = pass_type;
     const UA_DataType *typelists[2] = { UA_TYPES, &localtype[-localtype->typeIndex] };
-    for(size_t i = 0; i < membersSize; i++) {
+    for(size_t i = 0; i < membersSize && retval == UA_STATUSCODE_GOOD; i++) {
         const UA_DataTypeMember *member = &localtype->members[i];
         pass_type = &typelists[!member->namespaceZero][member->memberTypeIndex];
         if(!member->isArray) {
             ptr += member->padding;
             size_t encode_index = pass_type->builtin ? pass_type->typeIndex : UA_BUILTIN_TYPES_COUNT;
             size_t memSize = pass_type->memSize;
+            UA_Byte *oldpos = *pos;
             retval |= encodeBinaryJumpTable[encode_index]((const void*)ptr, pos, end);
             ptr += memSize;
+            if(retval == UA_STATUSCODE_BADENCODINGERROR) {
+                /* exchange/send the buffer and try to encode the same type once more */
+                *pos = oldpos;
+                retval = exchangeBuffer(pos, &end);
+                /* re-encode the same member on the new buffer */
+                ptr -= member->padding + memSize;
+                i--;
+            }
         } else {
             ptr += member->padding;
             const size_t length = *((const size_t*)ptr);
@@ -1101,20 +1121,20 @@ UA_encodeBinaryInternal(const void *src, bufpos pos, bufend end) {
 }
 
 static const UA_encodeBinarySignature encodeBinaryJumpTable[UA_BUILTIN_TYPES_COUNT + 1] = {
-    (UA_encodeBinarySignature)Boolean_encodeBinary, 
+    (UA_encodeBinarySignature)Boolean_encodeBinary,
     (UA_encodeBinarySignature)Byte_encodeBinary, // SByte
-    (UA_encodeBinarySignature)Byte_encodeBinary, 
+    (UA_encodeBinarySignature)Byte_encodeBinary,
     (UA_encodeBinarySignature)UInt16_encodeBinary, // Int16
-    (UA_encodeBinarySignature)UInt16_encodeBinary, 
-    (UA_encodeBinarySignature)UInt32_encodeBinary, // Int32 
-    (UA_encodeBinarySignature)UInt32_encodeBinary, 
+    (UA_encodeBinarySignature)UInt16_encodeBinary,
+    (UA_encodeBinarySignature)UInt32_encodeBinary, // Int32
+    (UA_encodeBinarySignature)UInt32_encodeBinary,
     (UA_encodeBinarySignature)UInt64_encodeBinary, // Int64
-    (UA_encodeBinarySignature)UInt64_encodeBinary, 
-    (UA_encodeBinarySignature)Float_encodeBinary, 
-    (UA_encodeBinarySignature)Double_encodeBinary, 
+    (UA_encodeBinarySignature)UInt64_encodeBinary,
+    (UA_encodeBinarySignature)Float_encodeBinary,
+    (UA_encodeBinarySignature)Double_encodeBinary,
     (UA_encodeBinarySignature)String_encodeBinary,
-    (UA_encodeBinarySignature)UInt64_encodeBinary, // DateTime 
-    (UA_encodeBinarySignature)Guid_encodeBinary, 
+    (UA_encodeBinarySignature)UInt64_encodeBinary, // DateTime
+    (UA_encodeBinarySignature)Guid_encodeBinary,
     (UA_encodeBinarySignature)String_encodeBinary, // ByteString
     (UA_encodeBinarySignature)String_encodeBinary, // XmlElement
     (UA_encodeBinarySignature)NodeId_encodeBinary,
@@ -1129,11 +1149,16 @@ static const UA_encodeBinarySignature encodeBinaryJumpTable[UA_BUILTIN_TYPES_COU
     (UA_encodeBinarySignature)UA_encodeBinaryInternal,
 };
 
-UA_StatusCode UA_encodeBinary(const void *src, const UA_DataType *localtype,
-                              UA_ByteString *dst, size_t *offset) {
+UA_StatusCode
+UA_encodeBinary(const void *src, const UA_DataType *localtype,
+                UA_exchangeEncodeBuffer callback, void *handle,
+                UA_ByteString *dst, size_t *offset) {
     UA_Byte *pos = &dst->data[*offset];
     UA_Byte *end = &dst->data[dst->length];
     pass_type = localtype;
+    encodeBuf = dst;
+    exchangeBufferCallback = callback;
+    exchangeBufferCallbackHandle = handle;
     UA_StatusCode retval = UA_encodeBinaryInternal(src, &pos, end);
     *offset = (size_t)(pos - dst->data) / sizeof(UA_Byte);
     return retval;
@@ -1161,7 +1186,8 @@ UA_decodeBinaryInternal(bufpos pos, bufend end, void *dst) {
             ptr += sizeof(size_t);
             UA_Int32 slength = -1;
             retval |= Int32_decodeBinary(pos, end, &slength);
-            retval |= Array_decodeBinary(pos, end, slength, (void *UA_RESTRICT *UA_RESTRICT)ptr, length, pass_type);
+            retval |= Array_decodeBinary(pos, end, slength, (void *UA_RESTRICT *UA_RESTRICT)ptr,
+                                         length, pass_type);
             ptr += sizeof(void*);
         }
     }
@@ -1171,20 +1197,20 @@ UA_decodeBinaryInternal(bufpos pos, bufend end, void *dst) {
 }
 
 static const UA_decodeBinarySignature decodeBinaryJumpTable[UA_BUILTIN_TYPES_COUNT + 1] = {
-    (UA_decodeBinarySignature)Boolean_decodeBinary, 
+    (UA_decodeBinarySignature)Boolean_decodeBinary,
     (UA_decodeBinarySignature)Byte_decodeBinary, // SByte
-    (UA_decodeBinarySignature)Byte_decodeBinary, 
+    (UA_decodeBinarySignature)Byte_decodeBinary,
     (UA_decodeBinarySignature)UInt16_decodeBinary, // Int16
-    (UA_decodeBinarySignature)UInt16_decodeBinary, 
-    (UA_decodeBinarySignature)UInt32_decodeBinary, // Int32 
-    (UA_decodeBinarySignature)UInt32_decodeBinary, 
+    (UA_decodeBinarySignature)UInt16_decodeBinary,
+    (UA_decodeBinarySignature)UInt32_decodeBinary, // Int32
+    (UA_decodeBinarySignature)UInt32_decodeBinary,
     (UA_decodeBinarySignature)UInt64_decodeBinary, // Int64
-    (UA_decodeBinarySignature)UInt64_decodeBinary, 
-    (UA_decodeBinarySignature)Float_decodeBinary, 
-    (UA_decodeBinarySignature)Double_decodeBinary, 
+    (UA_decodeBinarySignature)UInt64_decodeBinary,
+    (UA_decodeBinarySignature)Float_decodeBinary,
+    (UA_decodeBinarySignature)Double_decodeBinary,
     (UA_decodeBinarySignature)String_decodeBinary,
-    (UA_decodeBinarySignature)UInt64_decodeBinary, // DateTime 
-    (UA_decodeBinarySignature)Guid_decodeBinary, 
+    (UA_decodeBinarySignature)UInt64_decodeBinary, // DateTime
+    (UA_decodeBinarySignature)Guid_decodeBinary,
     (UA_decodeBinarySignature)String_decodeBinary, // ByteString
     (UA_decodeBinarySignature)String_decodeBinary, // XmlElement
     (UA_decodeBinarySignature)NodeId_decodeBinary,
@@ -1299,8 +1325,7 @@ ExtensionObject_calcSizeBinary(const UA_ExtensionObject *src, UA_DataType *_) {
             return 0;
         s += NodeId_calcSizeBinary(&src->content.decoded.type->typeId, NULL);
         s += 4; // length
-        size_t encode_index = src->content.decoded.type->builtin ?
-            src->content.decoded.type->typeIndex : UA_BUILTIN_TYPES_COUNT;
+        size_t encode_index = pass_type->builtin ? pass_type->typeIndex : UA_BUILTIN_TYPES_COUNT;
         s += calcSizeBinaryJumpTable[encode_index](src->content.decoded.data, src->content.decoded.type);
     } else {
         s += NodeId_calcSizeBinary(&src->content.encoded.typeId, NULL);

+ 9 - 4
src/ua_types_encoding_binary.h

@@ -3,11 +3,16 @@
 
 #include "ua_types.h"
 
-UA_StatusCode UA_encodeBinary(const void *src, const UA_DataType *type, UA_ByteString *dst,
-                              size_t *offset) UA_FUNC_ATTR_WARN_UNUSED_RESULT;
+typedef UA_StatusCode (*UA_exchangeEncodeBuffer)(void *handle, UA_ByteString *buf, size_t offset);
 
-UA_StatusCode UA_decodeBinary(const UA_ByteString *src, size_t *offset, void *dst,
-                              const UA_DataType *type) UA_FUNC_ATTR_WARN_UNUSED_RESULT;
+UA_StatusCode
+UA_encodeBinary(const void *src, const UA_DataType *type,
+                UA_exchangeEncodeBuffer exchangeBufferCallback, void *exchangeBufferCallbackHandle,
+                UA_ByteString *dst, size_t *offset) UA_FUNC_ATTR_WARN_UNUSED_RESULT;
+
+UA_StatusCode
+UA_decodeBinary(const UA_ByteString *src, size_t *offset, void *dst,
+                const UA_DataType *type) UA_FUNC_ATTR_WARN_UNUSED_RESULT;
 
 size_t UA_calcSizeBinary(void *p, const UA_DataType *type);
 

+ 4 - 0
tests/CMakeLists.txt

@@ -33,6 +33,10 @@ add_executable(check_memory check_memory.c $<TARGET_OBJECTS:open62541-object>)
 target_link_libraries(check_memory ${LIBS})
 add_test(memory ${CMAKE_CURRENT_BINARY_DIR}/check_memory)
 
+add_executable(check_chunking check_chunking.c $<TARGET_OBJECTS:open62541-object>)
+target_link_libraries(check_chunking ${LIBS})
+add_test(chunking ${CMAKE_CURRENT_BINARY_DIR}/check_chunking)
+
 # add_executable(check_stack check_stack.c)
 # target_link_libraries(check_stack ${LIBS})
 # add_test(stack ${CMAKE_CURRENT_BINARY_DIR}/check_stack)

+ 130 - 0
tests/check_chunking.c

@@ -0,0 +1,130 @@
+#include "ua_types.h"
+#include "ua_types_encoding_binary.h"
+#include "ua_types_generated.h"
+#include "ua_types_generated_encoding_binary.h"
+#include "ua_securechannel.h"
+#include "ua_util.h"
+#include "check.h"
+
+UA_ByteString *buffers;
+size_t bufIndex;
+size_t counter;
+size_t dataCount;
+
+static UA_StatusCode sendChunkMockUp(UA_ChunkInfo *ci, UA_ByteString *dst, size_t offset) {
+    bufIndex++;
+    dst->data = buffers[bufIndex].data;
+    dst->length = buffers[bufIndex].length;
+    counter++;
+    dataCount += offset;
+    return UA_STATUSCODE_GOOD;
+}
+START_TEST(encodeArrayIntoFiveChunksShallWork) {
+
+    size_t arraySize = 30; //number of elements within the array which should be encoded
+    size_t offset = 0; // encoding offset
+    size_t chunkCount = 6; // maximum chunk count
+    size_t chunkSize = 30; //size in bytes of each chunk
+    UA_ChunkInfo ci;
+    bufIndex = 0;
+    counter = 0;
+    dataCount = 0;
+    UA_Int32 *ar = UA_Array_new(arraySize,&UA_TYPES[UA_TYPES_INT32]);
+    buffers = UA_Array_new(chunkCount, &UA_TYPES[UA_TYPES_BYTESTRING]);
+    for(size_t i=0;i<chunkCount;i++){
+        UA_ByteString_allocBuffer(&buffers[i],chunkSize);
+    }
+
+    UA_ByteString workingBuffer=buffers[0];
+
+    for(size_t i=0;i<arraySize;i++){
+        ar[i]=(UA_Int32)i;
+    }
+    UA_Variant v;
+    UA_Variant_setArrayCopy(&v,ar,arraySize,&UA_TYPES[UA_TYPES_INT32]);
+
+    UA_StatusCode retval = UA_encodeBinary(&v,&UA_TYPES[UA_TYPES_VARIANT],(UA_exchangeEncodeBuffer)sendChunkMockUp,&ci,&workingBuffer,&offset);
+
+    ck_assert_uint_eq(retval,UA_STATUSCODE_GOOD);
+    ck_assert_int_eq(counter,4); //5 chunks allocated - callback called 4 times
+
+    dataCount += offset; //last piece of data - no callback was called
+    ck_assert_int_eq(UA_calcSizeBinary(&v,&UA_TYPES[UA_TYPES_VARIANT]), dataCount);
+
+    UA_Variant_deleteMembers(&v);
+    UA_Array_delete(buffers, chunkCount, &UA_TYPES[UA_TYPES_BYTESTRING]);
+    UA_Array_delete(ar, arraySize, &UA_TYPES[UA_TYPES_INT32]);
+
+}
+END_TEST
+
+START_TEST(encodeStringIntoFiveChunksShallWork) {
+
+    size_t stringLength = 120; //number of elements within the array which should be encoded
+    size_t offset = 0; // encoding offset
+    size_t chunkCount = 6; // maximum chunk count
+    size_t chunkSize = 30; //size in bytes of each chunk
+
+    UA_String string;
+    UA_ChunkInfo ci;
+    bufIndex = 0;
+    counter = 0;
+    dataCount = 0;
+    UA_String_init(&string);
+    string.data = malloc(stringLength);
+    string.length = stringLength;
+    char tmpString[9] = {'o','p','e','n','6','2','5','4','1'};
+    //char tmpString[9] = {'1','4','5','2','6','n','e','p','o'};
+    buffers = UA_Array_new(chunkCount, &UA_TYPES[UA_TYPES_BYTESTRING]);
+    for(size_t i=0;i<chunkCount;i++){
+        UA_ByteString_allocBuffer(&buffers[i],chunkSize);
+    }
+
+    UA_ByteString workingBuffer=buffers[0];
+
+    for(size_t i=0;i<stringLength;i++){
+        size_t tmp = i % 9;
+        string.data[i] =  tmpString[tmp];
+    }
+    UA_Variant v;
+    UA_Variant_setScalarCopy(&v,&string,&UA_TYPES[UA_TYPES_STRING]);
+
+    UA_StatusCode retval = UA_encodeBinary(&v,&UA_TYPES[UA_TYPES_VARIANT],(UA_exchangeEncodeBuffer)sendChunkMockUp,&ci,&workingBuffer,&offset);
+
+    ck_assert_uint_eq(retval,UA_STATUSCODE_GOOD);
+    ck_assert_int_eq(counter,4); //5 chunks allocated - callback called 4 times
+
+    dataCount += offset; //last piece of data - no callback was called
+    ck_assert_int_eq(UA_calcSizeBinary(&v,&UA_TYPES[UA_TYPES_VARIANT]), dataCount);
+
+    UA_Variant_deleteMembers(&v);
+    UA_Array_delete(buffers, chunkCount, &UA_TYPES[UA_TYPES_BYTESTRING]);
+    UA_String_deleteMembers(&string);
+}
+END_TEST
+
+
+static Suite *testSuite_builtin(void) {
+    Suite *s = suite_create("Chunked encoding");
+    TCase *tc_message = tcase_create("encode chunking");
+    tcase_add_test(tc_message,encodeArrayIntoFiveChunksShallWork);
+    tcase_add_test(tc_message,encodeStringIntoFiveChunksShallWork);
+    suite_add_tcase(s, tc_message);
+    return s;
+}
+
+
+int main(void) {
+    int      number_failed = 0;
+    Suite   *s;
+    SRunner *sr;
+
+    s  = testSuite_builtin();
+    sr = srunner_create(s);
+    srunner_set_fork_status(sr, CK_NOFORK);
+    srunner_run_all(sr, CK_NORMAL);
+    number_failed += srunner_ntests_failed(sr);
+    srunner_free(sr);
+
+    return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}

+ 4 - 4
tests/check_memory.c

@@ -61,7 +61,7 @@ START_TEST(encodeShallYieldDecode) {
 	void *obj1 = UA_new(&UA_TYPES[_i]);
     UA_StatusCode retval = UA_ByteString_allocBuffer(&msg1, 65000); // fixed buf size
 	ck_assert_int_eq(retval, UA_STATUSCODE_GOOD);
-    retval = UA_encodeBinary(obj1, &UA_TYPES[_i], &msg1, &pos);
+    retval = UA_encodeBinary(obj1, &UA_TYPES[_i], NULL, NULL, &msg1, &pos);
     msg1.length = pos;
 	if(retval != UA_STATUSCODE_GOOD) {
 		UA_delete(obj1, &UA_TYPES[_i]);
@@ -77,7 +77,7 @@ START_TEST(encodeShallYieldDecode) {
     ck_assert(!memcmp(obj1, obj2, UA_TYPES[_i].memSize)); // bit identical decoding
 	retval = UA_ByteString_allocBuffer(&msg2, 65000);
 	ck_assert_int_eq(retval, UA_STATUSCODE_GOOD);
-	pos = 0; retval = UA_encodeBinary(obj2, &UA_TYPES[_i], &msg2, &pos);
+	pos = 0; retval = UA_encodeBinary(obj2, &UA_TYPES[_i], NULL, NULL, &msg2, &pos);
     msg2.length = pos;
 	ck_assert_int_eq(retval, UA_STATUSCODE_GOOD);
 
@@ -99,7 +99,7 @@ START_TEST(decodeShallFailWithTruncatedBufferButSurvive) {
 	void *obj1 = UA_new(&UA_TYPES[_i]);
 	size_t pos = 0;
     UA_StatusCode retval = UA_ByteString_allocBuffer(&msg1, 65000); // fixed buf size
-    retval |= UA_encodeBinary(obj1, &UA_TYPES[_i], &msg1, &pos);
+    retval |= UA_encodeBinary(obj1, &UA_TYPES[_i], NULL, NULL, &msg1, &pos);
 	UA_delete(obj1, &UA_TYPES[_i]);
     if(retval != UA_STATUSCODE_GOOD) {
         UA_ByteString_deleteMembers(&msg1);
@@ -202,7 +202,7 @@ START_TEST(calcSizeBinaryShallBeCorrect) {
     UA_StatusCode retval = UA_ByteString_allocBuffer(&msg, predicted_size);
 	ck_assert_int_eq(retval, UA_STATUSCODE_GOOD);
     size_t offset = 0;
-    retval = UA_encodeBinary(obj, &UA_TYPES[_i], &msg, &offset);
+retval = UA_encodeBinary(obj, &UA_TYPES[_i], NULL, NULL, &msg, &offset);
     if(retval)
         printf("%i\n",_i);
 	ck_assert_int_eq(retval, UA_STATUSCODE_GOOD);

+ 1 - 1
tools/generate_datatypes.py

@@ -131,7 +131,7 @@ class Type(object):
         return funcs
 
     def encoding_h(self):
-        enc = "static UA_INLINE UA_StatusCode UA_%s_encodeBinary(const UA_%s *src, UA_ByteString *dst, size_t *offset) { return UA_encodeBinary(src, %s, dst, offset); }\n"
+        enc = "static UA_INLINE UA_StatusCode UA_%s_encodeBinary(const UA_%s *src, UA_ByteString *dst, size_t *offset) { return UA_encodeBinary(src, %s, NULL, NULL, dst, offset); }\n"
         enc += "static UA_INLINE UA_StatusCode UA_%s_decodeBinary(const UA_ByteString *src, size_t *offset, UA_%s *dst) { return UA_decodeBinary(src, offset, dst, %s); }"
         return enc % tuple(list(itertools.chain(*itertools.repeat([self.name, self.name, self.datatype_ptr()], 2))))