blob: 269ebd19d3a1acc03c0d2bbc70ff7c957951f512 [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-2017 (c) Fraunhofer IOSB (Author: Julius Pfrommer)
* Copyright 2014, 2016-2017 (c) Florian Palm
* Copyright 2015-2016 (c) Sten GrĂ¼ner
* Copyright 2015 (c) Oleksiy Vasylyev
* Copyright 2016-2017 (c) Stefan Profanter, fortiss GmbH
* Copyright 2017 (c) Mark Giraud, Fraunhofer IOSB
* Copyright 2019 (c) Kalycito Infotech Private Limited
*/
#include <open62541/transport_generated_encoding_binary.h>
#include <open62541/types_generated_encoding_binary.h>
#include <open62541/types_generated_handling.h>
#include "ua_connection_internal.h"
#include "ua_securechannel.h"
#include "ua_types_encoding_binary.h"
#include "ua_util_internal.h"
void UA_Connection_deleteMembers(UA_Connection *connection) {
UA_ByteString_deleteMembers(&connection->incompleteChunk);
}
UA_StatusCode
UA_Connection_processHELACK(UA_Connection *connection,
const UA_ConnectionConfig *localConfig,
const UA_ConnectionConfig *remoteConfig) {
connection->config = *remoteConfig;
/* The lowest common version is used by both sides */
if(connection->config.protocolVersion > localConfig->protocolVersion)
connection->config.protocolVersion = localConfig->protocolVersion;
/* Can we receive the max send size? */
if(connection->config.sendBufferSize > localConfig->recvBufferSize)
connection->config.sendBufferSize = localConfig->recvBufferSize;
/* Can we send the max receive size? */
if(connection->config.recvBufferSize > localConfig->sendBufferSize)
connection->config.recvBufferSize = localConfig->sendBufferSize;
/* Chunks of at least 8192 bytes must be permissible.
* See Part 6, Clause 6.7.1 */
if(connection->config.recvBufferSize < 8192 ||
connection->config.sendBufferSize < 8192 ||
(connection->config.maxMessageSize != 0 &&
connection->config.maxMessageSize < 8192))
return UA_STATUSCODE_BADINTERNALERROR;
connection->state = UA_CONNECTION_ESTABLISHED;
return UA_STATUSCODE_GOOD;
}
/* Hides some errors before sending them to a client according to the
* standard. */
static void
hideErrors(UA_TcpErrorMessage *const error) {
switch(error->error) {
case UA_STATUSCODE_BADCERTIFICATEUNTRUSTED:
case UA_STATUSCODE_BADCERTIFICATEREVOKED:
error->error = UA_STATUSCODE_BADSECURITYCHECKSFAILED;
error->reason = UA_STRING_NULL;
break;
// TODO: Check if these are all cases that need to be covered.
default:
break;
}
}
void
UA_Connection_sendError(UA_Connection *connection, UA_TcpErrorMessage *error) {
hideErrors(error);
UA_TcpMessageHeader header;
header.messageTypeAndChunkType = UA_MESSAGETYPE_ERR + UA_CHUNKTYPE_FINAL;
// Header + ErrorMessage (error + reasonLength_field + length)
header.messageSize = 8 + (4 + 4 + (UA_UInt32)error->reason.length);
/* Get the send buffer from the network layer */
UA_ByteString msg = UA_BYTESTRING_NULL;
UA_StatusCode retval = connection->getSendBuffer(connection, header.messageSize, &msg);
if(retval != UA_STATUSCODE_GOOD)
return;
/* Encode and send the response */
UA_Byte *bufPos = msg.data;
const UA_Byte *bufEnd = &msg.data[msg.length];
UA_TcpMessageHeader_encodeBinary(&header, &bufPos, bufEnd);
UA_TcpErrorMessage_encodeBinary(error, &bufPos, bufEnd);
msg.length = header.messageSize;
connection->send(connection, &msg);
}
static UA_StatusCode
bufferIncompleteChunk(UA_Connection *connection, const UA_Byte *pos,
const UA_Byte *end) {
UA_assert(connection->incompleteChunk.length == 0);
UA_assert(pos < end);
size_t length = (uintptr_t)end - (uintptr_t)pos;
UA_StatusCode retval = UA_ByteString_allocBuffer(&connection->incompleteChunk, length);
if(retval != UA_STATUSCODE_GOOD)
return retval;
memcpy(connection->incompleteChunk.data, pos, length);
return UA_STATUSCODE_GOOD;
}
static UA_StatusCode
processChunk(UA_Connection *connection, void *application,
UA_Connection_processChunk processCallback,
const UA_Byte **posp, const UA_Byte *end, UA_Boolean *done) {
const UA_Byte *pos = *posp;
const size_t remaining = (uintptr_t)end - (uintptr_t)pos;
/* At least 8 byte needed for the header. Wait for the next chunk. */
if(remaining < 8) {
*done = true;
return UA_STATUSCODE_GOOD;
}
/* Check the message type */
UA_MessageType msgtype = (UA_MessageType)
((UA_UInt32)pos[0] + ((UA_UInt32)pos[1] << 8) + ((UA_UInt32)pos[2] << 16));
if(msgtype != UA_MESSAGETYPE_MSG && msgtype != UA_MESSAGETYPE_ERR &&
msgtype != UA_MESSAGETYPE_OPN && msgtype != UA_MESSAGETYPE_HEL &&
msgtype != UA_MESSAGETYPE_ACK && msgtype != UA_MESSAGETYPE_CLO) {
/* The message type is not recognized */
return UA_STATUSCODE_BADTCPMESSAGETYPEINVALID;
}
UA_Byte isFinal = pos[3];
if(isFinal != 'C' && isFinal != 'F' && isFinal != 'A') {
/* The message type is not recognized */
return UA_STATUSCODE_BADTCPMESSAGETYPEINVALID;
}
UA_UInt32 chunk_length = 0;
UA_ByteString temp = { 8, (UA_Byte*)(uintptr_t)pos }; /* At least 8 byte left */
size_t temp_offset = 4;
/* Decoding the UInt32 cannot fail */
UA_UInt32_decodeBinary(&temp, &temp_offset, &chunk_length);
/* The message size is not allowed */
if(chunk_length < 16 || chunk_length > connection->config.recvBufferSize)
return UA_STATUSCODE_BADTCPMESSAGETOOLARGE;
/* Have an the complete chunk */
if(chunk_length > remaining) {
*done = true;
return UA_STATUSCODE_GOOD;
}
/* Process the chunk; forward the position pointer */
temp.length = chunk_length;
*posp += chunk_length;
*done = false;
return processCallback(application, connection, &temp);
}
UA_StatusCode
UA_Connection_processChunks(UA_Connection *connection, void *application,
UA_Connection_processChunk processCallback,
const UA_ByteString *packet) {
const UA_Byte *pos = packet->data;
const UA_Byte *end = &packet->data[packet->length];
UA_ByteString appended = connection->incompleteChunk;
/* Prepend the incomplete last chunk. This is usually done in the
* networklayer. But we test for a buffered incomplete chunk here again to
* work around "lazy" network layers. */
if(appended.length > 0) {
connection->incompleteChunk = UA_BYTESTRING_NULL;
UA_Byte *t = (UA_Byte*)UA_realloc(appended.data, appended.length + packet->length);
if(!t) {
UA_ByteString_deleteMembers(&appended);
return UA_STATUSCODE_BADOUTOFMEMORY;
}
memcpy(&t[appended.length], pos, packet->length);
appended.data = t;
appended.length += packet->length;
pos = t;
end = &t[appended.length];
}
UA_assert(connection->incompleteChunk.length == 0);
/* Loop over the received chunks. pos is increased with each chunk. */
UA_Boolean done = false;
UA_StatusCode retval = UA_STATUSCODE_GOOD;
while(!done) {
retval = processChunk(connection, application, processCallback, &pos, end, &done);
/* If an irrecoverable error happens: do not buffer incomplete chunk */
if(retval != UA_STATUSCODE_GOOD)
goto cleanup;
}
if(end > pos)
retval = bufferIncompleteChunk(connection, pos, end);
cleanup:
UA_ByteString_deleteMembers(&appended);
return retval;
}
/* In order to know whether a chunk was processed, we insert an redirection into
* the callback. */
struct completeChunkTrampolineData {
UA_Boolean called;
void *application;
UA_Connection_processChunk processCallback;
};
static UA_StatusCode
completeChunkTrampoline(void *application, UA_Connection *connection,
UA_ByteString *chunk) {
struct completeChunkTrampolineData *data =
(struct completeChunkTrampolineData*)application;
data->called = true;
return data->processCallback(data->application, connection, chunk);
}
UA_StatusCode
UA_Connection_receiveChunksBlocking(UA_Connection *connection, void *application,
UA_Connection_processChunk processCallback,
UA_UInt32 timeout) {
UA_DateTime now = UA_DateTime_nowMonotonic();
UA_DateTime maxDate = now + (timeout * UA_DATETIME_MSEC);
struct completeChunkTrampolineData data;
data.called = false;
data.application = application;
data.processCallback = processCallback;
UA_StatusCode retval = UA_STATUSCODE_GOOD;
while(true) {
/* Listen for messages to arrive */
UA_ByteString packet = UA_BYTESTRING_NULL;
retval = connection->recv(connection, &packet, timeout);
if(retval != UA_STATUSCODE_GOOD)
break;
/* Try to process one complete chunk */
retval = UA_Connection_processChunks(connection, &data,
completeChunkTrampoline, &packet);
connection->releaseRecvBuffer(connection, &packet);
if(data.called)
break;
/* We received a message. But the chunk is incomplete. Compute the
* remaining timeout. */
now = UA_DateTime_nowMonotonic();
/* >= avoid timeout to be set to 0 */
if(now >= maxDate)
return UA_STATUSCODE_GOODNONCRITICALTIMEOUT;
/* round always to upper value to avoid timeout to be set to 0
* if(maxDate - now) < (UA_DATETIME_MSEC/2) */
timeout = (UA_UInt32)(((maxDate - now) + (UA_DATETIME_MSEC - 1)) / UA_DATETIME_MSEC);
}
return retval;
}
UA_StatusCode
UA_Connection_receiveChunksNonBlocking(UA_Connection *connection, void *application,
UA_Connection_processChunk processCallback) {
struct completeChunkTrampolineData data;
data.called = false;
data.application = application;
data.processCallback = processCallback;
/* Listen for messages to arrive */
UA_ByteString packet = UA_BYTESTRING_NULL;
UA_StatusCode retval = connection->recv(connection, &packet, 1);
if((retval != UA_STATUSCODE_GOOD) && (retval != UA_STATUSCODE_GOODNONCRITICALTIMEOUT))
return retval;
/* Try to process one complete chunk */
retval = UA_Connection_processChunks(connection, &data, completeChunkTrampoline, &packet);
connection->releaseRecvBuffer(connection, &packet);
return retval;
}
void UA_Connection_detachSecureChannel(UA_Connection *connection) {
UA_SecureChannel *channel = connection->channel;
if(channel)
/* only replace when the channel points to this connection */
UA_atomic_cmpxchg((void**)&channel->connection, connection, NULL);
UA_atomic_xchg((void**)&connection->channel, NULL);
}
// TODO: Return an error code
void
UA_Connection_attachSecureChannel(UA_Connection *connection, UA_SecureChannel *channel) {
if(UA_atomic_cmpxchg((void**)&channel->connection, NULL, connection) == NULL)
UA_atomic_xchg((void**)&connection->channel, (void*)channel);
}