Przeglądaj źródła

introduce MessageContext to work with partially sent messages

Julius Pfrommer 6 lat temu
rodzic
commit
05fc3600ea
2 zmienionych plików z 193 dodań i 134 usunięć
  1. 144 131
      src/ua_securechannel.c
  2. 49 3
      src/ua_securechannel.h

+ 144 - 131
src/ua_securechannel.c

@@ -14,7 +14,6 @@
 
 #define UA_BITMASK_MESSAGETYPE 0x00ffffff
 #define UA_BITMASK_CHUNKTYPE 0xff000000
-#define UA_SECURE_MESSAGE_HEADER_LENGTH 24
 #define UA_ASYMMETRIC_ALG_SECURITY_HEADER_FIXED_LENGTH 12
 #define UA_SYMMETRIC_ALG_SECURITY_HEADER_LENGTH 4
 #define UA_SEQUENCE_HEADER_LENGTH 8
@@ -31,27 +30,13 @@ UA_THREAD_LOCAL UA_StatusCode sendAsym_sendFailure;
 UA_THREAD_LOCAL UA_StatusCode processSym_seqNumberFailure;
 #endif
 
-/* Callback data for sending responses in multiple chunks */
-typedef struct {
-    UA_SecureChannel *channel;
-    UA_UInt32 requestId;
-    UA_UInt32 messageType;
-
-    UA_UInt16 chunksSoFar;
-    size_t messageSizeSoFar;
-
-    UA_ByteString messageBuffer;
-    UA_Boolean final;
-} UA_ChunkInfo;
-
 UA_StatusCode
 UA_SecureChannel_init(UA_SecureChannel *channel,
                       const UA_SecurityPolicy *securityPolicy,
                       const UA_ByteString *remoteCertificate) {
 
-    if(channel == NULL || securityPolicy == NULL || remoteCertificate == NULL) {
+    if(channel == NULL || securityPolicy == NULL || remoteCertificate == NULL)
         return UA_STATUSCODE_BADINTERNALERROR;
-    }
 
     memset(channel, 0, sizeof(UA_SecureChannel));
     channel->state = UA_SECURECHANNELSTATE_FRESH;
@@ -252,8 +237,8 @@ calculatePaddingAsym(const UA_SecurityPolicy *securityPolicy, const void *channe
     size_t signatureSize = securityPolicy->asymmetricModule.cryptoModule.
         getLocalSignatureSize(securityPolicy, channelContext);
     size_t paddingBytes = 1;
-    if(securityPolicy->asymmetricModule.cryptoModule.getRemoteEncryptionKeyLength(securityPolicy,
-                                                                                  channelContext) > 2048)
+    if(securityPolicy->asymmetricModule.cryptoModule.
+       getRemoteEncryptionKeyLength(securityPolicy, channelContext) > 2048)
         ++paddingBytes;
     size_t padding = (plainTextBlockSize - ((bytesToWrite + signatureSize + paddingBytes) % plainTextBlockSize));
     *paddingSize = (UA_Byte) (padding & 0xff);
@@ -292,9 +277,9 @@ hideBytesAsym(UA_SecureChannel *const channel, UA_Byte **const buf_start,
         *buf_end -= 2; // padding byte and extraPadding byte
 
         /* Add some overhead length due to RSA implementations adding a signature themselves */
-        *buf_end -= securityPolicy->channelModule
-                                  .getRemoteAsymEncryptionBufferLengthOverhead(channel->channelContext,
-                                                                               potentialEncryptionMaxSize);
+        *buf_end -= securityPolicy->channelModule.
+            getRemoteAsymEncryptionBufferLengthOverhead(channel->channelContext,
+                                                        potentialEncryptionMaxSize);
     }
 }
 
@@ -358,9 +343,8 @@ UA_SecureChannel_sendAsymmetricOPNMessage(UA_SecureChannel *channel, UA_UInt32 r
             *buf_pos = paddingSize;
             ++buf_pos;
         }
-        if(securityPolicy->asymmetricModule.cryptoModule.getRemoteEncryptionKeyLength(securityPolicy,
-                                                                                      channel->channelContext)
-           > 2048) {
+        if(securityPolicy->asymmetricModule.cryptoModule.
+           getRemoteEncryptionKeyLength(securityPolicy, channel->channelContext) > 2048) {
             *buf_pos = extraPaddingSize;
             ++buf_pos;
         }
@@ -465,35 +449,49 @@ calculatePaddingSym(const UA_SecurityPolicy *securityPolicy, const void *channel
     return padding;
 }
 
-/* Sends a message using symmetric encryption if defined
- *
- * @param ci the chunk information that is used to send the chunk.
- * @param buf_pos the position in the send buffer after the body was encoded.
- *                Should be less than or equal to buf_end.
- * @param buf_end the maximum position of the body. */
+static void
+setBufPos(UA_MessageContext *mc) {
+    const UA_SecureChannel *channel= mc->channel;
+    const UA_SecurityPolicy *securityPolicy = channel->securityPolicy;
+
+    /* Forward the data pointer so that the payload is encoded after the
+     * message header */
+    mc->buf_pos = &mc->messageBuffer.data[UA_SECURE_MESSAGE_HEADER_LENGTH];
+    mc->buf_end = &mc->messageBuffer.data[mc->messageBuffer.length];
+
+    if(channel->securityMode == UA_MESSAGESECURITYMODE_SIGN ||
+       channel->securityMode == UA_MESSAGESECURITYMODE_SIGNANDENCRYPT)
+        mc->buf_end -= securityPolicy->symmetricModule.cryptoModule.
+            getLocalSignatureSize(securityPolicy, channel->channelContext);
+
+    /* Hide a byte needed for padding */
+    if(channel->securityMode == UA_MESSAGESECURITYMODE_SIGNANDENCRYPT)
+        mc->buf_end -= 2;
+}
+
 static UA_StatusCode
-sendChunkSymmetric(UA_ChunkInfo *ci, UA_Byte **buf_pos, const UA_Byte **buf_end) {
+sendSymmetricChunk(UA_MessageContext *mc) {
     UA_StatusCode res = UA_STATUSCODE_GOOD;
-    UA_SecureChannel *const channel = ci->channel;
+    UA_SecureChannel *const channel = mc->channel;
     const UA_SecurityPolicy *securityPolicy = channel->securityPolicy;
     UA_Connection *const connection = channel->connection;
     if(!connection)
         return UA_STATUSCODE_BADINTERNALERROR;
 
     /* Will this chunk surpass the capacity of the SecureChannel for the message? */
-    UA_Byte *buf_body_start = ci->messageBuffer.data + UA_SECURE_MESSAGE_HEADER_LENGTH;
-    UA_Byte *buf_body_end = *buf_pos;
+    UA_Byte *buf_body_start = mc->messageBuffer.data + UA_SECURE_MESSAGE_HEADER_LENGTH;
+    const UA_Byte *buf_body_end = mc->buf_pos;
     size_t bodyLength = (uintptr_t) buf_body_end - (uintptr_t) buf_body_start;
-    ci->messageSizeSoFar += bodyLength;
-    ci->chunksSoFar++;
-    if(ci->messageSizeSoFar > connection->remoteConf.maxMessageSize &&
+    mc->messageSizeSoFar += bodyLength;
+    mc->chunksSoFar++;
+    if(mc->messageSizeSoFar > connection->remoteConf.maxMessageSize &&
        connection->remoteConf.maxMessageSize != 0)
         res = UA_STATUSCODE_BADRESPONSETOOLARGE;
-    if(ci->chunksSoFar > connection->remoteConf.maxChunkCount &&
+    if(mc->chunksSoFar > connection->remoteConf.maxChunkCount &&
        connection->remoteConf.maxChunkCount != 0)
         res = UA_STATUSCODE_BADRESPONSETOOLARGE;
     if(res != UA_STATUSCODE_GOOD) {
-        connection->releaseSendBuffer(channel->connection, &ci->messageBuffer);
+        connection->releaseSendBuffer(channel->connection, &mc->messageBuffer);
         return res;
     }
 
@@ -509,58 +507,59 @@ sendChunkSymmetric(UA_ChunkInfo *ci, UA_Byte **buf_pos, const UA_Byte **buf_end)
 
         // This is <= because the paddingSize byte also has to be written.
         for(UA_UInt16 i = 0; i <= totalPaddingSize; ++i) {
-            **buf_pos = paddingSize;
-            ++(*buf_pos);
+            *mc->buf_pos = paddingSize;
+            ++(mc->buf_pos);
         }
         if(extraPaddingSize > 0) {
-            **buf_pos = extraPaddingSize;
-            ++(*buf_pos);
+            *mc->buf_pos = extraPaddingSize;
+            ++(mc->buf_pos);
         }
     }
 
     /* The total message length */
-    size_t pre_sig_length = (uintptr_t) (*buf_pos) - (uintptr_t) ci->messageBuffer.data;
+    size_t pre_sig_length = (uintptr_t) (mc->buf_pos) - (uintptr_t) mc->messageBuffer.data;
     size_t total_length = pre_sig_length;
     if(channel->securityMode == UA_MESSAGESECURITYMODE_SIGN ||
        channel->securityMode == UA_MESSAGESECURITYMODE_SIGNANDENCRYPT)
         total_length += securityPolicy->symmetricModule.cryptoModule.
             getLocalSignatureSize(securityPolicy, channel->channelContext);
+    mc->messageBuffer.length = total_length; /* For giving the buffer to the network layer */
 
     /* Encode the chunk headers at the beginning of the buffer */
     UA_assert(res == UA_STATUSCODE_GOOD);
-    UA_Byte *header_pos = ci->messageBuffer.data;
+    UA_Byte *header_pos = mc->messageBuffer.data;
     UA_SecureConversationMessageHeader respHeader;
     respHeader.secureChannelId = channel->securityToken.channelId;
-    respHeader.messageHeader.messageTypeAndChunkType = ci->messageType;
-    respHeader.messageHeader.messageSize = (UA_UInt32) total_length;
-    if(ci->final)
+    respHeader.messageHeader.messageTypeAndChunkType = mc->messageType;
+    respHeader.messageHeader.messageSize = (UA_UInt32)total_length;
+    if(mc->final)
         respHeader.messageHeader.messageTypeAndChunkType += UA_CHUNKTYPE_FINAL;
     else
         respHeader.messageHeader.messageTypeAndChunkType += UA_CHUNKTYPE_INTERMEDIATE;
     res = UA_encodeBinary(&respHeader, &UA_TRANSPORT[UA_TRANSPORT_SECURECONVERSATIONMESSAGEHEADER],
-                          &header_pos, buf_end, NULL, NULL);
+                          &header_pos, &mc->buf_end, NULL, NULL);
 
     UA_SymmetricAlgorithmSecurityHeader symSecHeader;
     symSecHeader.tokenId = channel->securityToken.tokenId;
     res |= UA_encodeBinary(&symSecHeader.tokenId,
                            &UA_TRANSPORT[UA_TRANSPORT_SYMMETRICALGORITHMSECURITYHEADER],
-                           &header_pos, buf_end, NULL, NULL);
+                           &header_pos, &mc->buf_end, NULL, NULL);
 
     UA_SequenceHeader seqHeader;
-    seqHeader.requestId = ci->requestId;
+    seqHeader.requestId = mc->requestId;
     seqHeader.sequenceNumber = UA_atomic_add(&channel->sendSequenceNumber, 1);
     res |= UA_encodeBinary(&seqHeader, &UA_TRANSPORT[UA_TRANSPORT_SEQUENCEHEADER],
-                           &header_pos, buf_end, NULL, NULL);
+                           &header_pos, &mc->buf_end, NULL, NULL);
 
     /* Sign message */
     if(channel->securityMode == UA_MESSAGESECURITYMODE_SIGN ||
        channel->securityMode == UA_MESSAGESECURITYMODE_SIGNANDENCRYPT) {
-        UA_ByteString dataToSign = ci->messageBuffer;
+        UA_ByteString dataToSign = mc->messageBuffer;
         dataToSign.length = pre_sig_length;
         UA_ByteString signature;
         signature.length = securityPolicy->symmetricModule.cryptoModule.
             getLocalSignatureSize(securityPolicy, channel->channelContext);
-        signature.data = *buf_pos;
+        signature.data = mc->buf_pos;
         res |= securityPolicy->symmetricModule.cryptoModule.
             sign(securityPolicy, channel->channelContext, &dataToSign, &signature);
     }
@@ -568,115 +567,130 @@ sendChunkSymmetric(UA_ChunkInfo *ci, UA_Byte **buf_pos, const UA_Byte **buf_end)
     /* Encrypt message */
     if(channel->securityMode == UA_MESSAGESECURITYMODE_SIGNANDENCRYPT) {
         UA_ByteString dataToEncrypt;
-        dataToEncrypt.data = ci->messageBuffer.data + UA_SECUREMH_AND_SYMALGH_LENGTH;
+        dataToEncrypt.data = mc->messageBuffer.data + UA_SECUREMH_AND_SYMALGH_LENGTH;
         dataToEncrypt.length = total_length - UA_SECUREMH_AND_SYMALGH_LENGTH;
         res |= securityPolicy->symmetricModule.cryptoModule.
             encrypt(securityPolicy, channel->channelContext, &dataToEncrypt);
     }
 
     if(res != UA_STATUSCODE_GOOD) {
-        connection->releaseSendBuffer(channel->connection, &ci->messageBuffer);
+        connection->releaseSendBuffer(channel->connection, &mc->messageBuffer);
         return res;
     }
 
     /* Send the chunk, the buffer is freed in the network layer */
-    ci->messageBuffer.length = respHeader.messageHeader.messageSize;
-    res = connection->send(channel->connection, &ci->messageBuffer);
-    if(res != UA_STATUSCODE_GOOD)
-        return res;
+    return connection->send(channel->connection, &mc->messageBuffer);
+}
 
-    /* Replace with the buffer for the next chunk */
-    if(!ci->final) {
-        res = connection->getSendBuffer(connection, connection->localConf.sendBufferSize,
-                                        &ci->messageBuffer);
-        if(res != UA_STATUSCODE_GOOD)
-            return res;
-
-        /* Forward the data pointer so that the payload is encoded after the
-         * message header */
-        *buf_pos = &ci->messageBuffer.data[UA_SECURE_MESSAGE_HEADER_LENGTH];
-        *buf_end = &ci->messageBuffer.data[ci->messageBuffer.length];
-
-        if(channel->securityMode == UA_MESSAGESECURITYMODE_SIGN ||
-           channel->securityMode == UA_MESSAGESECURITYMODE_SIGNANDENCRYPT)
-            *buf_end -= securityPolicy->symmetricModule.cryptoModule.
-                getLocalSignatureSize(securityPolicy, channel->channelContext);
-
-        /* Hide a byte needed for padding */
-        if(channel->securityMode == UA_MESSAGESECURITYMODE_SIGNANDENCRYPT)
-            *buf_end -= 2;
-    }
-    return res;
+/* Callback from the encoding layer. Send the chunk and replace the buffer. */
+static UA_StatusCode
+sendSymmetricEncodingCallback(void *data, UA_Byte **buf_pos, const UA_Byte **buf_end) {
+    /* Set buf values from encoding in the messagecontext */
+    UA_MessageContext *mc = (UA_MessageContext*)data;
+    mc->buf_pos = *buf_pos;
+    mc->buf_end = *buf_end;
+
+    /* Send out */
+    UA_StatusCode retval = sendSymmetricChunk(mc);
+    if(retval != UA_STATUSCODE_GOOD)
+        return retval;
+
+    /* Set a new buffer for the next chunk */
+    UA_Connection *connection = mc->channel->connection;
+    retval = connection->getSendBuffer(connection, connection->localConf.sendBufferSize,
+                                       &mc->messageBuffer);
+    if(retval != UA_STATUSCODE_GOOD)
+        return retval;
+
+    /* Hide bytes for header, padding and signature */
+    setBufPos(mc);
+    *buf_pos = mc->buf_pos;
+    *buf_end = mc->buf_end;
+    return UA_STATUSCODE_GOOD;
 }
 
 UA_StatusCode
-UA_SecureChannel_sendSymmetricMessage(UA_SecureChannel *channel, UA_UInt32 requestId,
-                                      UA_MessageType messageType, const void *content,
-                                      const UA_DataType *contentType) {
-    const UA_SecurityPolicy *const securityPolicy = channel->securityPolicy;
+UA_MessageContext_begin(UA_MessageContext *mc, UA_SecureChannel *channel,
+                        UA_UInt32 requestId, UA_MessageType messageType) {
     UA_Connection *connection = channel->connection;
     if(!connection)
         return UA_STATUSCODE_BADINTERNALERROR;
 
+    /* Create the chunking info structure */
+    mc->channel = channel;
+    mc->requestId = requestId;
+    mc->chunksSoFar = 0;
+    mc->messageSizeSoFar = 0;
+    mc->final = false;
+    mc->messageBuffer = UA_BYTESTRING_NULL;
+    mc->messageType = messageType;
+
     /* Minimum required size */
     if(connection->localConf.sendBufferSize <= UA_SECURE_MESSAGE_HEADER_LENGTH)
         return UA_STATUSCODE_BADRESPONSETOOLARGE;
 
-    /* Create the chunking info structure */
-    UA_ChunkInfo ci;
-    ci.channel = channel;
-    ci.requestId = requestId;
-    ci.chunksSoFar = 0;
-    ci.messageSizeSoFar = 0;
-    ci.final = false;
-    ci.messageBuffer = UA_BYTESTRING_NULL;
-    ci.messageType = messageType;
-
     /* Allocate the message buffer */
     UA_StatusCode retval =
         connection->getSendBuffer(connection, connection->localConf.sendBufferSize,
-                                  &ci.messageBuffer);
+                                  &mc->messageBuffer);
     if(retval != UA_STATUSCODE_GOOD)
         return retval;
 
-    /* Hide the message beginning where the header will be encoded */
-    UA_Byte *buf_start = &ci.messageBuffer.data[UA_SECURE_MESSAGE_HEADER_LENGTH];
-    const UA_Byte *buf_end = &ci.messageBuffer.data[ci.messageBuffer.length];
+    /* Hide bytes for header, padding and signature */
+    setBufPos(mc);
+    return UA_STATUSCODE_GOOD;
+}
 
-    /* Hide bytes for signature */
-    if(channel->securityMode == UA_MESSAGESECURITYMODE_SIGN ||
-       channel->securityMode == UA_MESSAGESECURITYMODE_SIGNANDENCRYPT)
-        buf_end -= securityPolicy->symmetricModule.cryptoModule.
-            getLocalSignatureSize(securityPolicy, channel->channelContext);
+UA_StatusCode
+UA_MessageContext_encode(UA_MessageContext *mc, const void *content,
+                         const UA_DataType *contentType) {
+    UA_StatusCode retval = UA_encodeBinary(content, contentType, &mc->buf_pos, &mc->buf_end,
+                                           sendSymmetricEncodingCallback, mc);
+    if(retval != UA_STATUSCODE_GOOD) {
+        /* TODO: Send the abort message */
+        if(mc->messageBuffer.length > 0) {
+            UA_Connection *connection = mc->channel->connection;
+            connection->releaseSendBuffer(connection, &mc->messageBuffer);
+        }
+    }
+    return retval;
+}
 
-    /* Hide one byte for padding */
-    if(channel->securityMode == UA_MESSAGESECURITYMODE_SIGNANDENCRYPT)
-        buf_end -= 2;
+UA_StatusCode
+UA_MessageContext_finish(UA_MessageContext *mc) {
+    mc->final = true;
+    return sendSymmetricChunk(mc);
+}
 
-    /* Encode the message type */
-    UA_NodeId typeId = UA_NODEID_NUMERIC(0, contentType->binaryEncodingId);
-    retval = UA_encodeBinary(&typeId, &UA_TYPES[UA_TYPES_NODEID],
-                             &buf_start, &buf_end, NULL, NULL);
+void
+UA_MessageContext_abort(UA_MessageContext *mc) {
+    UA_ByteString_deleteMembers(&mc->messageBuffer);
+}
 
-    /* Encode with the chunking callback */
-    retval |= UA_encodeBinary(content, contentType, &buf_start, &buf_end,
-                              (UA_exchangeEncodeBuffer) sendChunkSymmetric, &ci);
+UA_StatusCode
+UA_SecureChannel_sendSymmetricMessage(UA_SecureChannel *channel, UA_UInt32 requestId,
+                                      UA_MessageType messageType, void *payload,
+                                      const UA_DataType *payloadType) {
+    UA_MessageContext mc;
+    UA_StatusCode retval;
+    UA_NodeId typeId = UA_NODEID_NUMERIC(0, payloadType->binaryEncodingId);
+    retval = UA_MessageContext_begin(&mc, channel, requestId, UA_MESSAGETYPE_MSG);
+    if(retval != UA_STATUSCODE_GOOD)
+        return retval;
 
-    /* TODO: Error handling. Send out an abort chunk if this is not the first chunk.
-     * If this is the first chunk of the message:
-     * - Client: Do nothing, maybe revert the sequence number?
-     * - Server: Send a ServiceFault response? Depending on which error codes? */
-    if(retval != UA_STATUSCODE_GOOD) {
-        /* the abort message was not sent */
-        if(!ci.final)
-            sendChunkSymmetric(&ci, &buf_start, &buf_end);
-        connection->releaseSendBuffer(connection, &ci.messageBuffer);
+    /* Assert's required for clang-analyzer */
+    UA_assert(mc.buf_pos == &mc.messageBuffer.data[UA_SECURE_MESSAGE_HEADER_LENGTH]);
+    UA_assert(mc.buf_end == &mc.messageBuffer.data[mc.messageBuffer.length]);
+
+    retval |= UA_MessageContext_encode(&mc, &typeId, &UA_TYPES[UA_TYPES_NODEID]);
+    if(retval != UA_STATUSCODE_GOOD)
+        return retval;
+
+    retval |= UA_MessageContext_encode(&mc, payload, payloadType);
+    if(retval != UA_STATUSCODE_GOOD)
         return retval;
-    }
 
-    /* Encoding finished, send the final chunk */
-    ci.final = UA_TRUE;
-    return sendChunkSymmetric(&ci, &buf_start, &buf_end);
+    return UA_MessageContext_finish(&mc);
 }
 
 /*****************************/
@@ -794,8 +808,7 @@ decryptChunk(UA_SecureChannel *channel, const UA_SecurityPolicyCryptoModule *cry
        channel->securityMode == UA_MESSAGESECURITYMODE_SIGNANDENCRYPT ||
        messageType == UA_MESSAGETYPE_OPN) {
         /* Compute the padding size */
-        sigsize = cryptoModule->
-                                  getRemoteSignatureSize(securityPolicy, channel->channelContext);
+        sigsize = cryptoModule-> getRemoteSignatureSize(securityPolicy, channel->channelContext);
 
         if(channel->securityMode == UA_MESSAGESECURITYMODE_SIGNANDENCRYPT ||
            (messageType == UA_MESSAGETYPE_OPN &&
@@ -803,7 +816,7 @@ decryptChunk(UA_SecureChannel *channel, const UA_SecurityPolicyCryptoModule *cry
             paddingSize = chunk->data[chunkSizeAfterDecryption - sigsize - 1];
 
             size_t keyLength = cryptoModule->
-                                               getRemoteEncryptionKeyLength(securityPolicy, channel->channelContext);
+                getRemoteEncryptionKeyLength(securityPolicy, channel->channelContext);
             if(keyLength > 2048) {
                 paddingSize <<= 8; /* Extra padding size */
                 paddingSize += chunk->data[chunkSizeAfterDecryption - sigsize - 2];

+ 49 - 3
src/ua_securechannel.h

@@ -18,6 +18,7 @@ extern "C" {
 #include "ua_util.h"
 
 #define UA_SECURE_CONVERSATION_MESSAGE_HEADER_LENGTH 12
+#define UA_SECURE_MESSAGE_HEADER_LENGTH 24
 
 #ifdef UA_ENABLE_UNIT_TEST_FAILURE_HOOKS
 extern UA_THREAD_LOCAL UA_StatusCode decrypt_verifySignatureFailure;
@@ -97,18 +98,63 @@ UA_StatusCode UA_SecureChannel_generateNonce(const UA_SecureChannel *const chann
 void UA_SecureChannel_attachSession(UA_SecureChannel *channel, UA_Session *session);
 void UA_SecureChannel_detachSession(UA_SecureChannel *channel, UA_Session *session);
 UA_Session * UA_SecureChannel_getSession(UA_SecureChannel *channel, UA_NodeId *token);
-
 UA_StatusCode UA_SecureChannel_revolveTokens(UA_SecureChannel *channel);
 
+/**
+ * Sending Messages
+ * ---------------- */
+
 UA_StatusCode
 UA_SecureChannel_sendSymmetricMessage(UA_SecureChannel *channel, UA_UInt32 requestId,
-                                      UA_MessageType messageType, const void *content,
-                                      const UA_DataType *contentType);
+                                      UA_MessageType messageType, void *payload,
+                                      const UA_DataType *payloadType);
+
+typedef struct {
+    UA_SecureChannel *channel;
+    UA_UInt32 requestId;
+    UA_UInt32 messageType;
+
+    UA_UInt16 chunksSoFar;
+    size_t messageSizeSoFar;
+
+    UA_ByteString messageBuffer;
+    UA_Byte *buf_pos;
+    const UA_Byte *buf_end;
+
+    UA_Boolean final;
+} UA_MessageContext;
+
+/* Start the context of a new symmetric message. */
+UA_StatusCode
+UA_MessageContext_begin(UA_MessageContext *mc, UA_SecureChannel *channel,
+                        UA_UInt32 requestId, UA_MessageType messageType);
+
+/* Encode the content and send out full chunks. If the return code is good, then
+ * the ChunkInfo contains encoded content that has not been sent. If the return
+ * code is bad, then the ChunkInfo has been cleaned up internally. */
+UA_StatusCode
+UA_MessageContext_encode(UA_MessageContext *mc, const void *content,
+                         const UA_DataType *contentType);
+
+/* Sends a symmetric message already encoded in the context. The context is
+ * cleaned up, also in case of errors. */
+UA_StatusCode
+UA_MessageContext_finish(UA_MessageContext *mc);
+
+/* To be used when a failure occures when a MessageContext is open. Note that
+ * the _encode and _finish methods will clean up internally. _abort can be run
+ * on a MessageContext that has already been cleaned up before. */
+void
+UA_MessageContext_abort(UA_MessageContext *mc);
 
 UA_StatusCode
 UA_SecureChannel_sendAsymmetricOPNMessage(UA_SecureChannel *channel, UA_UInt32 requestId,
                                           const void *content, const UA_DataType *contentType);
 
+/**
+ * Process Received Chunks
+ * ----------------------- */
+
 typedef UA_StatusCode
 (UA_ProcessMessageCallback)(void *application, UA_SecureChannel *channel,
                             UA_MessageType messageType, UA_UInt32 requestId,