/* 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 #include #include #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); }