/// <summary> /// Compare encoded/decoded dataset messages /// </summary> /// <param name="uadpDataSetMessage"></param> /// <returns></returns> private void CompareEncodeDecode(UadpDataSetMessage uadpDataSetMessage) { IServiceMessageContext messageContextEncode = new ServiceMessageContext(); byte[] bytes; var memoryStream = new MemoryStream(); using (BinaryEncoder encoder = new BinaryEncoder(memoryStream, messageContextEncode, true)) { uadpDataSetMessage.Encode(encoder); _ = encoder.Close(); bytes = ReadBytes(memoryStream); } UadpDataSetMessage uaDataSetMessageDecoded = new UadpDataSetMessage(); BinaryDecoder decoder = new BinaryDecoder(bytes, messageContextEncode); // workaround uaDataSetMessageDecoded.DataSetWriterId = TestDataSetWriterId; uaDataSetMessageDecoded.DecodePossibleDataSetReader(decoder, m_firstDataSetReaderType); decoder.Dispose(); // compare uadpDataSetMessage with uaDataSetMessageDecoded CompareUadpDataSetMessages(uadpDataSetMessage, uaDataSetMessageDecoded); }
/// <summary> /// Sends an error message over the socket. /// </summary> protected void SendErrorMessage(ServiceResult error) { Utils.Trace("Channel {0}: SendErrorMessage()", ChannelId); byte[] buffer = BufferManager.TakeBuffer(SendBufferSize, "SendErrorMessage"); try { BinaryEncoder encoder = new BinaryEncoder(buffer, 0, SendBufferSize, Quotas.MessageContext); encoder.WriteUInt32(null, TcpMessageType.Error); encoder.WriteUInt32(null, 0); WriteErrorMessageBody(encoder, error); int size = encoder.Close(); UpdateMessageSize(buffer, 0, size); BeginWriteMessage(new ArraySegment <byte>(buffer, 0, size), null); buffer = null; } finally { if (buffer != null) { BufferManager.ReturnBuffer(buffer, "SendErrorMessage"); } } }
public void ValidateMajorVersionEqMinorVersionEq( [Values(DataSetFieldContentMask.None, DataSetFieldContentMask.RawData, // list here all possible DataSetFieldContentMask DataSetFieldContentMask.ServerPicoSeconds, DataSetFieldContentMask.ServerTimestamp, DataSetFieldContentMask.SourcePicoSeconds, DataSetFieldContentMask.SourceTimestamp, DataSetFieldContentMask.StatusCode, DataSetFieldContentMask.ServerPicoSeconds | DataSetFieldContentMask.ServerTimestamp, DataSetFieldContentMask.ServerPicoSeconds | DataSetFieldContentMask.SourcePicoSeconds, DataSetFieldContentMask.ServerPicoSeconds | DataSetFieldContentMask.SourceTimestamp, DataSetFieldContentMask.ServerPicoSeconds | DataSetFieldContentMask.StatusCode, DataSetFieldContentMask.ServerPicoSeconds | DataSetFieldContentMask.ServerTimestamp | DataSetFieldContentMask.SourcePicoSeconds, DataSetFieldContentMask.ServerPicoSeconds | DataSetFieldContentMask.ServerTimestamp | DataSetFieldContentMask.SourceTimestamp, DataSetFieldContentMask.ServerPicoSeconds | DataSetFieldContentMask.ServerTimestamp | DataSetFieldContentMask.StatusCode, DataSetFieldContentMask.ServerPicoSeconds | DataSetFieldContentMask.ServerTimestamp | DataSetFieldContentMask.SourcePicoSeconds | DataSetFieldContentMask.SourceTimestamp, DataSetFieldContentMask.ServerPicoSeconds | DataSetFieldContentMask.ServerTimestamp | DataSetFieldContentMask.SourcePicoSeconds | DataSetFieldContentMask.StatusCode, DataSetFieldContentMask.ServerPicoSeconds | DataSetFieldContentMask.ServerTimestamp | DataSetFieldContentMask.SourcePicoSeconds | DataSetFieldContentMask.SourceTimestamp | DataSetFieldContentMask.StatusCode )] DataSetFieldContentMask dataSetFieldContentMask) { const int VersionValue = 2; // Arrange UadpDataSetMessage uadpDataSetMessage = GetFirstDataSetMessage(dataSetFieldContentMask); // Act uadpDataSetMessage.SetMessageContentMask(UadpDataSetMessageContentMask.MajorVersion | UadpDataSetMessageContentMask.MinorVersion); uadpDataSetMessage.MetaDataVersion.MajorVersion = VersionValue; uadpDataSetMessage.MetaDataVersion.MinorVersion = VersionValue * 10; IServiceMessageContext messageContextEncode = new ServiceMessageContext(); byte[] bytes; var memoryStream = new MemoryStream(); using (BinaryEncoder encoder = new BinaryEncoder(memoryStream, messageContextEncode, true)) { uadpDataSetMessage.Encode(encoder); _ = encoder.Close(); bytes = ReadBytes(memoryStream); } UadpDataSetMessage uaDataSetMessageDecoded = new UadpDataSetMessage(); BinaryDecoder decoder = new BinaryDecoder(bytes, messageContextEncode); // Make sure the reader MajorVersion and MinorVersion are the same with the ones on the dataset message DataSetReaderDataType reader = (DataSetReaderDataType)m_firstDataSetReaderType.MemberwiseClone(); reader.DataSetMetaData.ConfigurationVersion.MajorVersion = VersionValue; reader.DataSetMetaData.ConfigurationVersion.MinorVersion = VersionValue * 10; // workaround uaDataSetMessageDecoded.DataSetWriterId = TestDataSetWriterId; uaDataSetMessageDecoded.DecodePossibleDataSetReader(decoder, reader); decoder.Dispose(); // Assert Assert.AreEqual(DataSetDecodeErrorReason.NoError, uaDataSetMessageDecoded.DecodeErrorReason); Assert.AreEqual(false, uaDataSetMessageDecoded.IsMetadataMajorVersionChange); Assert.AreNotEqual(null, uaDataSetMessageDecoded.DataSet); // compare uadpDataSetMessage with uaDataSetMessageDecoded CompareUadpDataSetMessages(uadpDataSetMessage, uaDataSetMessageDecoded); }
/// <summary> /// Reverse client is connected, send reverse hello message. /// </summary> private void OnReverseConnectComplete(object sender, IMessageSocketAsyncEventArgs result) { var ar = (ReverseConnectAsyncResult)result.UserToken; if (ar == null || m_pendingReverseHello != null) { return; } if (result.IsSocketError) { ar.Exception = new ServiceResultException(StatusCodes.BadNotConnected, result.SocketErrorString); ar.OperationCompleted(); return; } byte[] buffer = BufferManager.TakeBuffer(SendBufferSize, "OnReverseConnectConnectComplete"); try { // start reading messages. ar.Socket.ReadNextMessage(); // send reverse hello message. BinaryEncoder encoder = new BinaryEncoder(buffer, 0, SendBufferSize, Quotas.MessageContext); encoder.WriteUInt32(null, TcpMessageType.ReverseHello); encoder.WriteUInt32(null, 0); encoder.WriteString(null, EndpointDescription.Server.ApplicationUri); encoder.WriteString(null, EndpointDescription.EndpointUrl); int size = encoder.Close(); UpdateMessageSize(buffer, 0, size); // set state to waiting for hello. State = TcpChannelState.Connecting; m_pendingReverseHello = ar; BeginWriteMessage(new ArraySegment <byte>(buffer, 0, size), null); buffer = null; } catch (Exception e) { ar.Exception = e; ar.OperationCompleted(); } finally { if (buffer != null) { BufferManager.ReturnBuffer(buffer, "OnReverseConnectComplete"); } } }
/// <summary> /// Writes the collection to a binary stream. The stream is closed by this method. /// </summary> public void SaveAsBinary(ISystemContext context, Stream ostrm) { ServiceMessageContext messageContext = new ServiceMessageContext(); messageContext.NamespaceUris = context.NamespaceUris; messageContext.ServerUris = context.ServerUris; messageContext.Factory = context.EncodeableFactory; BinaryEncoder encoder = new BinaryEncoder(ostrm, messageContext); encoder.SaveStringTable(context.NamespaceUris); encoder.SaveStringTable(context.ServerUris); encoder.WriteInt32(null, this.Count); for (int ii = 0; ii < this.Count; ii++) { NodeState state = this[ii]; state.SaveAsBinary(context, encoder); } encoder.Close(); }
/// <summary> /// Secures the message using the security token. /// </summary> protected BufferCollection WriteSymmetricMessage( uint messageType, uint requestId, ChannelToken token, object messageBody, bool isRequest, out bool limitsExceeded) { limitsExceeded = false; bool success = false; BufferCollection chunksToProcess = null; try { // calculate chunk sizes. int maxCipherTextSize = SendBufferSize - TcpMessageLimits.SymmetricHeaderSize; int maxCipherBlocks = maxCipherTextSize / EncryptionBlockSize; int maxPlainTextSize = maxCipherBlocks * EncryptionBlockSize; int maxPayloadSize = maxPlainTextSize - SymmetricSignatureSize - 1 - TcpMessageLimits.SequenceHeaderSize; int headerSize = TcpMessageLimits.SymmetricHeaderSize + TcpMessageLimits.SequenceHeaderSize; // write the body to stream. ArraySegmentStream ostrm = new ArraySegmentStream( BufferManager, SendBufferSize, headerSize, maxPayloadSize); // check for encodeable body. IEncodeable encodeable = messageBody as IEncodeable; if (encodeable != null) { // debug code used to verify that message aborts are handled correctly. // int maxMessageSize = Quotas.MessageContext.MaxMessageSize; // Quotas.MessageContext.MaxMessageSize = Int32.MaxValue; BinaryEncoder.EncodeMessage(encodeable, ostrm, Quotas.MessageContext); // Quotas.MessageContext.MaxMessageSize = maxMessageSize; } // check for raw bytes. ArraySegment <byte>?rawBytes = messageBody as ArraySegment <byte>?; if (rawBytes != null) { BinaryEncoder encoder = new BinaryEncoder(ostrm, Quotas.MessageContext); encoder.WriteRawBytes(rawBytes.Value.Array, rawBytes.Value.Offset, rawBytes.Value.Count); encoder.Close(); } chunksToProcess = ostrm.GetBuffers("WriteSymmetricMessage"); // ensure there is at least one chunk. if (chunksToProcess.Count == 0) { byte[] buffer = BufferManager.TakeBuffer(SendBufferSize, "WriteSymmetricMessage"); chunksToProcess.Add(new ArraySegment <byte>(buffer, 0, 0)); } BufferCollection chunksToSend = new BufferCollection(chunksToProcess.Capacity); int messageSize = 0; for (int ii = 0; ii < chunksToProcess.Count; ii++) { ArraySegment <byte> chunkToProcess = chunksToProcess[ii]; // nothing more to do if limits exceeded. if (limitsExceeded) { BufferManager.ReturnBuffer(chunkToProcess.Array, "WriteSymmetricMessage"); continue; } MemoryStream strm = new MemoryStream(chunkToProcess.Array, 0, SendBufferSize); BinaryEncoder encoder = new BinaryEncoder(strm, Quotas.MessageContext); // check if the message needs to be aborted. if (MessageLimitsExceeded(isRequest, messageSize + chunkToProcess.Count - headerSize, ii + 1)) { encoder.WriteUInt32(null, messageType | TcpMessageType.Abort); // replace the body in the chunk with an error message. BinaryEncoder errorEncoder = new BinaryEncoder( chunkToProcess.Array, chunkToProcess.Offset, chunkToProcess.Count, Quotas.MessageContext); WriteErrorMessageBody(errorEncoder, (isRequest) ? StatusCodes.BadRequestTooLarge : StatusCodes.BadResponseTooLarge); int size = errorEncoder.Close(); chunkToProcess = new ArraySegment <byte>(chunkToProcess.Array, chunkToProcess.Offset, size); limitsExceeded = true; } // check if the message is complete. else if (ii == chunksToProcess.Count - 1) { encoder.WriteUInt32(null, messageType | TcpMessageType.Final); } // more chunks to follow. else { encoder.WriteUInt32(null, messageType | TcpMessageType.Intermediate); } int count = 0; count += TcpMessageLimits.SequenceHeaderSize; count += chunkToProcess.Count; count += SymmetricSignatureSize; // calculate the padding. int padding = 0; if (SecurityMode == MessageSecurityMode.SignAndEncrypt) { // reserve one byte for the padding size. count++; if (count % EncryptionBlockSize != 0) { padding = EncryptionBlockSize - (count % EncryptionBlockSize); } count += padding; } count += TcpMessageLimits.SymmetricHeaderSize; encoder.WriteUInt32(null, (uint)count); encoder.WriteUInt32(null, ChannelId); encoder.WriteUInt32(null, token.TokenId); uint sequenceNumber = GetNewSequenceNumber(); encoder.WriteUInt32(null, sequenceNumber); encoder.WriteUInt32(null, requestId); // skip body. strm.Seek(chunkToProcess.Count, SeekOrigin.Current); // update message size count. messageSize += chunkToProcess.Count; // write padding. if (SecurityMode == MessageSecurityMode.SignAndEncrypt) { for (int jj = 0; jj <= padding; jj++) { encoder.WriteByte(null, (byte)padding); } } if (SecurityMode != MessageSecurityMode.None) { // calculate and write signature. byte[] signature = Sign(token, new ArraySegment <byte>(chunkToProcess.Array, 0, encoder.Position), isRequest); if (signature != null) { encoder.WriteRawBytes(signature, 0, signature.Length); } } if (SecurityMode == MessageSecurityMode.SignAndEncrypt) { // encrypt the data. ArraySegment <byte> dataToEncrypt = new ArraySegment <byte>(chunkToProcess.Array, TcpMessageLimits.SymmetricHeaderSize, encoder.Position - TcpMessageLimits.SymmetricHeaderSize); Encrypt(token, dataToEncrypt, isRequest); } // add the header into chunk. chunksToSend.Add(new ArraySegment <byte>(chunkToProcess.Array, 0, encoder.Position)); } // ensure the buffers don't get cleaned up on exit. success = true; return(chunksToSend); } finally { if (!success) { if (chunksToProcess != null) { chunksToProcess.Release(BufferManager, "WriteSymmetricMessage"); } } } }
/// <summary> /// Sends a OpenSecureChannel request. /// </summary> protected BufferCollection WriteAsymmetricMessage( uint messageType, uint requestId, X509Certificate2 senderCertificate, X509Certificate2Collection senderCertificateChain, X509Certificate2 receiverCertificate, ArraySegment <byte> messageBody) { bool success = false; BufferCollection chunksToSend = new BufferCollection(); byte[] buffer = BufferManager.TakeBuffer(SendBufferSize, "WriteAsymmetricMessage"); try { BinaryEncoder encoder = new BinaryEncoder(buffer, 0, SendBufferSize, Quotas.MessageContext); int headerSize = 0; if (senderCertificateChain != null && senderCertificateChain.Count > 0) { int senderCertificateSize = 0; WriteAsymmetricMessageHeader( encoder, messageType | TcpMessageType.Intermediate, ChannelId, SecurityPolicyUri, senderCertificate, senderCertificateChain, receiverCertificate, out senderCertificateSize); headerSize = GetAsymmetricHeaderSize(SecurityPolicyUri, senderCertificate, senderCertificateSize); } else { WriteAsymmetricMessageHeader( encoder, messageType | TcpMessageType.Intermediate, ChannelId, SecurityPolicyUri, senderCertificate, receiverCertificate); headerSize = GetAsymmetricHeaderSize(SecurityPolicyUri, senderCertificate); } int signatureSize = GetAsymmetricSignatureSize(senderCertificate); // save the header. ArraySegment <byte> header = new ArraySegment <byte>(buffer, 0, headerSize); // calculate the space available. int plainTextBlockSize = GetPlainTextBlockSize(receiverCertificate); int cipherTextBlockSize = GetCipherTextBlockSize(receiverCertificate); int maxCipherTextSize = SendBufferSize - headerSize; int maxCipherBlocks = maxCipherTextSize / cipherTextBlockSize; int maxPlainTextSize = maxCipherBlocks * plainTextBlockSize; int maxPayloadSize = maxPlainTextSize - signatureSize - 1 - TcpMessageLimits.SequenceHeaderSize; int bytesToWrite = messageBody.Count; int startOfBytes = messageBody.Offset; while (bytesToWrite > 0) { encoder.WriteUInt32(null, GetNewSequenceNumber()); encoder.WriteUInt32(null, requestId); int payloadSize = bytesToWrite; if (payloadSize > maxPayloadSize) { payloadSize = maxPayloadSize; } else { UpdateMessageType(buffer, 0, messageType | TcpMessageType.Final); } // write the message body. encoder.WriteRawBytes(messageBody.Array, messageBody.Offset + startOfBytes, payloadSize); // calculate the amount of plain text to encrypt. int plainTextSize = encoder.Position - headerSize + signatureSize; // calculate the padding. int padding = 0; if (SecurityMode != MessageSecurityMode.None) { if (CertificateFactory.GetRSAPublicKeySize(receiverCertificate) <= TcpMessageLimits.KeySizeExtraPadding) { // need to reserve one byte for the padding. plainTextSize++; if (plainTextSize % plainTextBlockSize != 0) { padding = plainTextBlockSize - (plainTextSize % plainTextBlockSize); } encoder.WriteByte(null, (byte)padding); for (int ii = 0; ii < padding; ii++) { encoder.WriteByte(null, (byte)padding); } } else { // need to reserve one byte for the padding. plainTextSize++; // need to reserve one byte for the extrapadding. plainTextSize++; if (plainTextSize % plainTextBlockSize != 0) { padding = plainTextBlockSize - (plainTextSize % plainTextBlockSize); } byte paddingSize = (byte)(padding & 0xff); byte extraPaddingByte = (byte)((padding >> 8) & 0xff); encoder.WriteByte(null, paddingSize); for (int ii = 0; ii < padding; ii++) { encoder.WriteByte(null, (byte)paddingSize); } encoder.WriteByte(null, extraPaddingByte); } // update the plaintext size with the padding size. plainTextSize += padding; } // calculate the number of block to encrypt. int encryptedBlocks = plainTextSize / plainTextBlockSize; // calculate the size of the encrypted data. int cipherTextSize = encryptedBlocks * cipherTextBlockSize; // put the message size after encryption into the header. UpdateMessageSize(buffer, 0, cipherTextSize + headerSize); // write the signature. byte[] signature = Sign(new ArraySegment <byte>(buffer, 0, encoder.Position), senderCertificate); if (signature != null) { encoder.WriteRawBytes(signature, 0, signature.Length); } int messageSize = encoder.Close(); // encrypt the data. ArraySegment <byte> encryptedBuffer = Encrypt( new ArraySegment <byte>(buffer, headerSize, messageSize - headerSize), header, receiverCertificate); // check for math errors due to code bugs. if (encryptedBuffer.Count != cipherTextSize + headerSize) { throw new InvalidDataException("Actual message size is not the same as the predicted message size."); } // save chunk. chunksToSend.Add(encryptedBuffer); bytesToWrite -= payloadSize; startOfBytes += payloadSize; // reset the encoder to write the plaintext for the next chunk into the same buffer. if (bytesToWrite > 0) { MemoryStream ostrm = new MemoryStream(buffer, 0, SendBufferSize); ostrm.Seek(header.Count, SeekOrigin.Current); encoder = new BinaryEncoder(ostrm, Quotas.MessageContext); } } // ensure the buffers don't get clean up on exit. success = true; return(chunksToSend); } catch (Exception ex) { throw new Exception("Could not write async message", ex); } finally { BufferManager.ReturnBuffer(buffer, "WriteAsymmetricMessage"); if (!success) { chunksToSend.Release(BufferManager, "WriteAsymmetricMessage"); } } }
private bool ProcessHelloMessage(uint messageType, ArraySegment<byte> messageChunk) { // validate the channel state. if (State != TcpChannelState.Connecting) { ForceChannelFault(StatusCodes.BadTcpMessageTypeInvalid, "Client sent an unexpected Hello message."); return false; } try { MemoryStream istrm = new MemoryStream(messageChunk.Array, messageChunk.Offset, messageChunk.Count, false); BinaryDecoder decoder = new BinaryDecoder(istrm, Quotas.MessageContext); istrm.Seek(TcpMessageLimits.MessageTypeAndSize, SeekOrigin.Current); // read requested buffer sizes. uint protocolVersion = decoder.ReadUInt32(null); uint receiveBufferSize = decoder.ReadUInt32(null); uint sendBufferSize = decoder.ReadUInt32(null); uint maxMessageSize = decoder.ReadUInt32(null); uint maxChunkCount = decoder.ReadUInt32(null); // read the endpoint url. int length = decoder.ReadInt32(null); if (length > 0) { if (length > TcpMessageLimits.MaxEndpointUrlLength) { ForceChannelFault(StatusCodes.BadTcpEndpointUrlInvalid); return false; } byte[] endpointUrl = new byte[length]; for (int ii = 0; ii < endpointUrl.Length; ii++) { endpointUrl[ii] = decoder.ReadByte(null); } if (!SetEndpointUrl(new UTF8Encoding().GetString(endpointUrl))) { ForceChannelFault(StatusCodes.BadTcpEndpointUrlInvalid); return false; } } decoder.Close(); // update receive buffer size. if (receiveBufferSize < ReceiveBufferSize) { ReceiveBufferSize = (int)receiveBufferSize; } if (ReceiveBufferSize < TcpMessageLimits.MinBufferSize) { ReceiveBufferSize = TcpMessageLimits.MinBufferSize; } // update send buffer size. if (sendBufferSize < SendBufferSize) { SendBufferSize = (int)sendBufferSize; } if (SendBufferSize < TcpMessageLimits.MinBufferSize) { SendBufferSize = TcpMessageLimits.MinBufferSize; } // update the max message size. if (maxMessageSize > 0 && maxMessageSize < MaxResponseMessageSize) { MaxResponseMessageSize = (int)maxMessageSize; } if (MaxResponseMessageSize < SendBufferSize) { MaxResponseMessageSize = SendBufferSize; } // update the max chunk count. if (maxChunkCount > 0 && maxChunkCount < MaxResponseChunkCount) { MaxResponseChunkCount = (int)maxChunkCount; } // send acknowledge. byte[] buffer = BufferManager.TakeBuffer(SendBufferSize, "ProcessHelloMessage"); try { MemoryStream ostrm = new MemoryStream(buffer, 0, SendBufferSize); BinaryEncoder encoder = new BinaryEncoder(ostrm, Quotas.MessageContext); encoder.WriteUInt32(null, TcpMessageType.Acknowledge); encoder.WriteUInt32(null, 0); encoder.WriteUInt32(null, 0); // ProtocolVersion encoder.WriteUInt32(null, (uint)ReceiveBufferSize); encoder.WriteUInt32(null, (uint)SendBufferSize); encoder.WriteUInt32(null, (uint)MaxRequestMessageSize); encoder.WriteUInt32(null, (uint)MaxRequestChunkCount); int size = encoder.Close(); UpdateMessageSize(buffer, 0, size); // now ready for the open or bind request. State = TcpChannelState.Opening; BeginWriteMessage(new ArraySegment<byte>(buffer, 0, size), Int32.MaxValue, null); buffer = null; } finally { if (buffer != null) { BufferManager.ReturnBuffer(buffer, "ProcessHelloMessage"); } } } catch (Exception e) { ForceChannelFault(e, StatusCodes.BadTcpInternalError, "Unexpected error while processing a Hello message."); } return false; }
/// <summary> /// Sends an error message over the socket. /// </summary> protected void SendErrorMessage(ServiceResult error) { // Utils.Trace("Channel {0}: SendErrorMessage()", ChannelId); byte[] buffer = BufferManager.TakeBuffer(SendBufferSize, "SendErrorMessage"); try { BinaryEncoder encoder = new BinaryEncoder(buffer, 0, SendBufferSize, Quotas.MessageContext); encoder.WriteUInt32(null, TcpMessageType.Error); encoder.WriteUInt32(null, 0); WriteErrorMessageBody(encoder, error); int size = encoder.Close(); UpdateMessageSize(buffer, 0, size); BeginWriteMessage(new ArraySegment<byte>(buffer, 0, size), Int32.MaxValue, null); buffer = null; } finally { if (buffer != null) { BufferManager.ReturnBuffer(buffer, "SendErrorMessage"); } } }
private bool ProcessHelloMessage(ArraySegment <byte> messageChunk) { // validate the channel state. if (State != TcpChannelState.Connecting) { ForceChannelFault(StatusCodes.BadTcpMessageTypeInvalid, "Client sent an unexpected Hello message."); return(false); } try { MemoryStream istrm = new MemoryStream(messageChunk.Array, messageChunk.Offset, messageChunk.Count, false); BinaryDecoder decoder = new BinaryDecoder(istrm, Quotas.MessageContext); istrm.Seek(TcpMessageLimits.MessageTypeAndSize, SeekOrigin.Current); // read requested buffer sizes. uint protocolVersion = decoder.ReadUInt32(null); uint receiveBufferSize = decoder.ReadUInt32(null); uint sendBufferSize = decoder.ReadUInt32(null); uint maxMessageSize = decoder.ReadUInt32(null); uint maxChunkCount = decoder.ReadUInt32(null); // read the endpoint url. int length = decoder.ReadInt32(null); if (length > 0) { if (length > TcpMessageLimits.MaxEndpointUrlLength) { ForceChannelFault(StatusCodes.BadTcpEndpointUrlInvalid); return(false); } byte[] endpointUrl = new byte[length]; for (int ii = 0; ii < endpointUrl.Length; ii++) { endpointUrl[ii] = decoder.ReadByte(null); } if (!SetEndpointUrl(new UTF8Encoding().GetString(endpointUrl, 0, endpointUrl.Length))) { ForceChannelFault(StatusCodes.BadTcpEndpointUrlInvalid); return(false); } } decoder.Close(); // update receive buffer size. if (receiveBufferSize < ReceiveBufferSize) { ReceiveBufferSize = (int)receiveBufferSize; } if (ReceiveBufferSize < TcpMessageLimits.MinBufferSize) { ReceiveBufferSize = TcpMessageLimits.MinBufferSize; } // update send buffer size. if (sendBufferSize < SendBufferSize) { SendBufferSize = (int)sendBufferSize; } if (SendBufferSize < TcpMessageLimits.MinBufferSize) { SendBufferSize = TcpMessageLimits.MinBufferSize; } // update the max message size. if (maxMessageSize > 0 && maxMessageSize < MaxResponseMessageSize) { MaxResponseMessageSize = (int)maxMessageSize; } if (MaxResponseMessageSize < SendBufferSize) { MaxResponseMessageSize = SendBufferSize; } // update the max chunk count. if (maxChunkCount > 0 && maxChunkCount < MaxResponseChunkCount) { MaxResponseChunkCount = (int)maxChunkCount; } // send acknowledge. byte[] buffer = BufferManager.TakeBuffer(SendBufferSize, "ProcessHelloMessage"); try { MemoryStream ostrm = new MemoryStream(buffer, 0, SendBufferSize); BinaryEncoder encoder = new BinaryEncoder(ostrm, Quotas.MessageContext); encoder.WriteUInt32(null, TcpMessageType.Acknowledge); encoder.WriteUInt32(null, 0); encoder.WriteUInt32(null, 0); // ProtocolVersion encoder.WriteUInt32(null, (uint)ReceiveBufferSize); encoder.WriteUInt32(null, (uint)SendBufferSize); encoder.WriteUInt32(null, (uint)MaxRequestMessageSize); encoder.WriteUInt32(null, (uint)MaxRequestChunkCount); int size = encoder.Close(); UpdateMessageSize(buffer, 0, size); // now ready for the open or bind request. State = TcpChannelState.Opening; BeginWriteMessage(new ArraySegment <byte>(buffer, 0, size), null); buffer = null; } finally { if (buffer != null) { BufferManager.ReturnBuffer(buffer, "ProcessHelloMessage"); } } } catch (Exception e) { ForceChannelFault(e, StatusCodes.BadTcpInternalError, "Unexpected error while processing a Hello message."); } return(false); }
/// <summary> /// Secures the message using the security token. /// </summary> protected BufferCollection WriteSymmetricMessage( uint messageType, uint requestId, TcpChannelToken token, object messageBody, bool isRequest, out bool limitsExceeded) { limitsExceeded = false; bool success = false; BufferCollection chunksToProcess = null; try { // calculate chunk sizes. int maxCipherTextSize = SendBufferSize - TcpMessageLimits.SymmetricHeaderSize; int maxCipherBlocks = maxCipherTextSize/EncryptionBlockSize; int maxPlainTextSize = maxCipherBlocks*EncryptionBlockSize; int maxPayloadSize = maxPlainTextSize - SymmetricSignatureSize - 1 - TcpMessageLimits.SequenceHeaderSize; int headerSize = TcpMessageLimits.SymmetricHeaderSize + TcpMessageLimits.SequenceHeaderSize; // write the body to stream. ArraySegmentStream ostrm = new ArraySegmentStream( BufferManager, SendBufferSize, headerSize, maxPayloadSize); // check for encodeable body. IEncodeable encodeable = messageBody as IEncodeable; if (encodeable != null) { // debug code used to verify that message aborts are handled correctly. // int maxMessageSize = Quotas.MessageContext.MaxMessageSize; // Quotas.MessageContext.MaxMessageSize = Int32.MaxValue; BinaryEncoder.EncodeMessage(encodeable, ostrm, Quotas.MessageContext); // Quotas.MessageContext.MaxMessageSize = maxMessageSize; } // check for raw bytes. ArraySegment<byte>? rawBytes = messageBody as ArraySegment<byte>?; if (rawBytes != null) { BinaryEncoder encoder = new BinaryEncoder(ostrm, Quotas.MessageContext); encoder.WriteRawBytes(rawBytes.Value.Array, rawBytes.Value.Offset, rawBytes.Value.Count); encoder.Close(); } chunksToProcess = ostrm.GetBuffers("WriteSymmetricMessage"); // ensure there is at least one chunk. if (chunksToProcess.Count == 0) { byte[] buffer = BufferManager.TakeBuffer(SendBufferSize, "WriteSymmetricMessage"); chunksToProcess.Add(new ArraySegment<byte>(buffer, 0, 0)); } BufferCollection chunksToSend = new BufferCollection(chunksToProcess.Capacity); int messageSize = 0; for (int ii = 0; ii < chunksToProcess.Count; ii++) { ArraySegment<byte> chunkToProcess = chunksToProcess[ii]; // nothing more to do if limits exceeded. if (limitsExceeded) { BufferManager.ReturnBuffer(chunkToProcess.Array, "WriteSymmetricMessage"); continue; } MemoryStream strm = new MemoryStream(chunkToProcess.Array, 0, SendBufferSize); BinaryEncoder encoder = new BinaryEncoder(strm, Quotas.MessageContext); // check if the message needs to be aborted. if (MessageLimitsExceeded(isRequest, messageSize + chunkToProcess.Count - headerSize, ii+1)) { encoder.WriteUInt32(null, messageType | TcpMessageType.Abort); // replace the body in the chunk with an error message. BinaryEncoder errorEncoder = new BinaryEncoder( chunkToProcess.Array, chunkToProcess.Offset, chunkToProcess.Count, Quotas.MessageContext); WriteErrorMessageBody(errorEncoder, (isRequest)?StatusCodes.BadRequestTooLarge:StatusCodes.BadResponseTooLarge); int size = errorEncoder.Close(); chunkToProcess = new ArraySegment<byte>(chunkToProcess.Array, chunkToProcess.Offset, size); limitsExceeded = true; } // check if the message is complete. else if (ii == chunksToProcess.Count-1) { encoder.WriteUInt32(null, messageType | TcpMessageType.Final); } // more chunks to follow. else { encoder.WriteUInt32(null, messageType | TcpMessageType.Intermediate); } int count = 0; count += TcpMessageLimits.SequenceHeaderSize; count += chunkToProcess.Count; count += SymmetricSignatureSize; // calculate the padding. int padding = 0; if (SecurityMode == MessageSecurityMode.SignAndEncrypt) { // reserve one byte for the padding size. count++; if (count%EncryptionBlockSize != 0) { padding = EncryptionBlockSize - (count%EncryptionBlockSize); } count += padding; } count += TcpMessageLimits.SymmetricHeaderSize; encoder.WriteUInt32(null, (uint)count); encoder.WriteUInt32(null, ChannelId); encoder.WriteUInt32(null, token.TokenId); uint sequenceNumber = GetNewSequenceNumber(); encoder.WriteUInt32(null, sequenceNumber); encoder.WriteUInt32(null, requestId); // skip body. strm.Seek(chunkToProcess.Count, SeekOrigin.Current); // update message size count. messageSize += chunkToProcess.Count; // write padding. if (SecurityMode == MessageSecurityMode.SignAndEncrypt) { for (int jj = 0; jj <= padding; jj++) { encoder.WriteByte(null, (byte)padding); } } if (SecurityMode != MessageSecurityMode.None) { // calculate and write signature. byte[] signature = Sign(token, new ArraySegment<byte>(chunkToProcess.Array, 0, encoder.Position), isRequest); if (signature != null) { encoder.WriteRawBytes(signature, 0, signature.Length); } } if (SecurityMode == MessageSecurityMode.SignAndEncrypt) { // encrypt the data. ArraySegment<byte> dataToEncrypt = new ArraySegment<byte>(chunkToProcess.Array, TcpMessageLimits.SymmetricHeaderSize, encoder.Position-TcpMessageLimits.SymmetricHeaderSize); Encrypt(token, dataToEncrypt, isRequest); } // add the header into chunk. chunksToSend.Add(new ArraySegment<byte>(chunkToProcess.Array, 0, encoder.Position)); } // ensure the buffers don't get cleaned up on exit. success = true; return chunksToSend; } finally { if (!success) { if (chunksToProcess != null) { chunksToProcess.Release(BufferManager, "WriteSymmetricMessage"); } } } }
/// <summary> /// Sends a Hello message. /// </summary> private void SendHelloMessage(WriteOperation operation) { // Utils.Trace("Channel {0}: SendHelloMessage()", ChannelId); byte[] buffer = BufferManager.TakeBuffer(SendBufferSize, "SendHelloMessage"); try { MemoryStream ostrm = new MemoryStream(buffer, 0, SendBufferSize); BinaryEncoder encoder = new BinaryEncoder(ostrm, Quotas.MessageContext); encoder.WriteUInt32(null, TcpMessageType.Hello); encoder.WriteUInt32(null, 0); encoder.WriteUInt32(null, 0); // ProtocolVersion encoder.WriteUInt32(null, (uint)ReceiveBufferSize); encoder.WriteUInt32(null, (uint)SendBufferSize); encoder.WriteUInt32(null, (uint)MaxResponseMessageSize); encoder.WriteUInt32(null, (uint)MaxResponseChunkCount); byte[] endpointUrl = new UTF8Encoding().GetBytes(m_url.ToString()); if (endpointUrl.Length > TcpMessageLimits.MaxEndpointUrlLength) { byte[] truncatedUrl = new byte[TcpMessageLimits.MaxEndpointUrlLength]; Array.Copy(endpointUrl, truncatedUrl, TcpMessageLimits.MaxEndpointUrlLength); endpointUrl = truncatedUrl; } encoder.WriteByteString(null, endpointUrl); int size = encoder.Close(); UpdateMessageSize(buffer, 0, size); BeginWriteMessage(new ArraySegment<byte>(buffer, 0, size), Int32.MaxValue, operation); buffer = null; } finally { if (buffer != null) { BufferManager.ReturnBuffer(buffer, "SendHelloMessage"); } } }
/// <summary> /// Sends a OpenSecureChannel response. /// </summary> protected BufferCollection WriteAsymmetricMessage( uint messageType, uint requestId, X509Certificate2 senderCertificate, X509Certificate2 receiverCertificate, ArraySegment<byte> messageBody) { bool success = false; BufferCollection chunksToSend = new BufferCollection(); byte[] buffer = BufferManager.TakeBuffer(SendBufferSize, "WriteAsymmetricMessage"); try { int headerSize = GetAsymmetricHeaderSize(SecurityPolicyUri, senderCertificate); int signatureSize = GetAsymmetricSignatureSize(senderCertificate); BinaryEncoder encoder = new BinaryEncoder(buffer, 0, SendBufferSize, Quotas.MessageContext); WriteAsymmetricMessageHeader( encoder, messageType | TcpMessageType.Intermediate, ChannelId, SecurityPolicyUri, senderCertificate, receiverCertificate); // save the header. ArraySegment<byte> header = new ArraySegment<byte>(buffer, 0, headerSize); // calculate the space available. int plainTextBlockSize = GetPlainTextBlockSize(receiverCertificate); int cipherTextBlockSize = GetCipherTextBlockSize(receiverCertificate); int maxCipherTextSize = SendBufferSize - headerSize; int maxCipherBlocks = maxCipherTextSize/cipherTextBlockSize; int maxPlainTextSize = maxCipherBlocks*plainTextBlockSize; int maxPayloadSize = maxPlainTextSize - signatureSize - 1 - TcpMessageLimits.SequenceHeaderSize; int bytesToWrite = messageBody.Count; int startOfBytes = messageBody.Offset; while (bytesToWrite > 0) { encoder.WriteUInt32(null, GetNewSequenceNumber()); encoder.WriteUInt32(null, requestId); int payloadSize = bytesToWrite; if (payloadSize > maxPayloadSize) { payloadSize = maxPayloadSize; } else { UpdateMessageType(buffer, 0, messageType | TcpMessageType.Final); } // write the message body. encoder.WriteRawBytes(messageBody.Array, messageBody.Offset+startOfBytes, payloadSize); // calculate the amount of plain text to encrypt. int plainTextSize = encoder.Position - headerSize + signatureSize; // calculate the padding. int padding = 0; if (SecurityMode != MessageSecurityMode.None) { if (receiverCertificate.PublicKey.Key.KeySize <= TcpMessageLimits.KeySizeExtraPadding) { // need to reserve one byte for the padding. plainTextSize++; if (plainTextSize % plainTextBlockSize != 0) { padding = plainTextBlockSize - (plainTextSize % plainTextBlockSize); } encoder.WriteByte(null, (byte)padding); for (int ii = 0; ii < padding; ii++) { encoder.WriteByte(null, (byte)padding); } } else { // need to reserve one byte for the padding. plainTextSize++; // need to reserve one byte for the extrapadding. plainTextSize++; if (plainTextSize % plainTextBlockSize != 0) { padding = plainTextBlockSize - (plainTextSize % plainTextBlockSize); } byte paddingSize = (byte)(padding & 0xff); byte extraPaddingByte = (byte)((padding >> 8) & 0xff); encoder.WriteByte(null, paddingSize); for (int ii = 0; ii < padding; ii++) { encoder.WriteByte(null, (byte)paddingSize); } encoder.WriteByte(null, extraPaddingByte); } // update the plaintext size with the padding size. plainTextSize += padding; } // calculate the number of block to encrypt. int encryptedBlocks = plainTextSize/plainTextBlockSize; // calculate the size of the encrypted data. int cipherTextSize = encryptedBlocks*cipherTextBlockSize; // put the message size after encryption into the header. UpdateMessageSize(buffer, 0, cipherTextSize + headerSize); // write the signature. byte[] signature = Sign(new ArraySegment<byte>(buffer, 0, encoder.Position), senderCertificate); if (signature != null) { encoder.WriteRawBytes(signature, 0, signature.Length); } int messageSize = encoder.Close(); // encrypt the data. ArraySegment<byte> encryptedBuffer = Encrypt( new ArraySegment<byte>(buffer, headerSize, messageSize-headerSize), header, receiverCertificate); // check for math errors due to code bugs. if (encryptedBuffer.Count != cipherTextSize + headerSize) { throw new InvalidDataException("Actual message size is not the same as the predicted message size."); } // save chunk. chunksToSend.Add(encryptedBuffer); bytesToWrite -= payloadSize; startOfBytes += payloadSize; // reset the encoder to write the plaintext for the next chunk into the same buffer. if (bytesToWrite > 0) { MemoryStream ostrm = new MemoryStream(buffer, 0, SendBufferSize); ostrm.Seek(header.Count, SeekOrigin.Current); encoder = new BinaryEncoder(ostrm, Quotas.MessageContext); } } // ensure the buffers don't get clean up on exit. success = true; return chunksToSend; } finally { BufferManager.ReturnBuffer(buffer, "WriteAsymmetricMessage"); if (!success) { chunksToSend.Release(BufferManager, "WriteAsymmetricMessage"); } } }
/// <summary> /// Called whenever socket operation completes /// </summary> /// <param name="arg"></param> /// <param name="ok"></param> /// <param name="index"></param> /// <param name="timeout"></param> /// <returns>true if completed, false to be called again</returns> public bool CompleteAsync(int index, SocketAsyncEventArgs arg, out bool ok, out int timeout) { ok = false; timeout = _timeout; if (arg.SocketError != SocketError.Success) { _logger.Debug("Probe {index} : {remoteEp} found no opc server. {error}", index, _socket?.RemoteEndPoint, arg.SocketError); _state = State.BeginProbe; return(true); } while (true) { switch (_state) { case State.BeginProbe: if (arg.ConnectSocket == null) { _logger.Error("Probe {index} : Called without connected socket!", index); return(true); } _socket = arg.ConnectSocket; var ep = _socket.RemoteEndPoint.TryResolve(); using (var ostrm = new MemoryStream(_buffer, 0, _buffer.Length)) using (var encoder = new BinaryEncoder(ostrm, ServiceMessageContext.GlobalContext)) { encoder.WriteUInt32(null, TcpMessageType.Hello); encoder.WriteUInt32(null, 0); encoder.WriteUInt32(null, 0); // ProtocolVersion encoder.WriteUInt32(null, TcpMessageLimits.DefaultMaxMessageSize); encoder.WriteUInt32(null, TcpMessageLimits.DefaultMaxMessageSize); encoder.WriteUInt32(null, TcpMessageLimits.DefaultMaxMessageSize); encoder.WriteUInt32(null, TcpMessageLimits.DefaultMaxMessageSize); encoder.WriteByteString(null, Encoding.UTF8.GetBytes("opc.tcp://" + ep)); _size = encoder.Close(); } _buffer[4] = (byte)(_size & 0x000000FF); _buffer[5] = (byte)((_size & 0x0000FF00) >> 8); _buffer[6] = (byte)((_size & 0x00FF0000) >> 16); _buffer[7] = (byte)((_size & 0xFF000000) >> 24); arg.SetBuffer(_buffer, 0, _size); _len = 0; _logger.Debug("Probe {index} : {ep} ({remoteEp})...", index, "opc.tcp://" + ep, _socket.RemoteEndPoint); _state = State.SendHello; if (!_socket.SendAsync(arg)) { break; } return(false); case State.SendHello: _len += arg.Count; if (_len >= _size) { _len = 0; _size = TcpMessageLimits.MessageTypeAndSize; _state = State.ReceiveSize; arg.SetBuffer(0, _size); // Start read size if (!_socket.ReceiveAsync(arg)) { break; } return(false); } // Continue to send reset arg.SetBuffer(_len, _size - _len); if (!_socket.SendAsync(arg)) { break; } return(false); case State.ReceiveSize: _len += arg.Count; if (_len >= _size) { var type = BitConverter.ToUInt32(_buffer, 0); if (type != TcpMessageType.Acknowledge) { if (TcpMessageType.IsValid(type)) { _logger.Debug("Probe {index} : {remoteEp} " + "returned message type {type} != Ack.", index, _socket.RemoteEndPoint, type); } else { _logger.Verbose("Probe {index} : {remoteEp} " + "returned invalid message type {type}.", index, _socket.RemoteEndPoint, type); } _state = State.BeginProbe; return(true); } _size = (int)BitConverter.ToUInt32(_buffer, 4); if (_size > _buffer.Length) { _logger.Debug("Probe {index} : {remoteEp} " + "returned invalid message length {size}.", index, _socket.RemoteEndPoint, _size); _state = State.BeginProbe; return(true); } _len = 0; // Start receive message _state = State.ReceiveAck; } // Continue to read rest of type and size arg.SetBuffer(_len, _size - _len); if (!_socket.ReceiveAsync(arg)) { break; } return(false); case State.ReceiveAck: _len += arg.Count; if (_len >= _size) { _state = State.BeginProbe; // Validate message using (var istrm = new MemoryStream(_buffer, 0, _size)) using (var decoder = new BinaryDecoder(istrm, ServiceMessageContext.GlobalContext)) { var protocolVersion = decoder.ReadUInt32(null); var sendBufferSize = (int)decoder.ReadUInt32(null); var receiveBufferSize = (int)decoder.ReadUInt32(null); var maxMessageSize = (int)decoder.ReadUInt32(null); var maxChunkCount = (int)decoder.ReadUInt32(null); _logger.Information("Probe {index} : found OPC UA " + "server at {remoteEp} (protocol:{protocolVersion}) ...", index, _socket.RemoteEndPoint, protocolVersion); if (sendBufferSize < TcpMessageLimits.MinBufferSize || receiveBufferSize < TcpMessageLimits.MinBufferSize) { _logger.Warning("Probe {index} : Bad size value read " + "{sendBufferSize} or {receiveBufferSize} from opc " + "server at {_socket.RemoteEndPoint}.", index, sendBufferSize, receiveBufferSize, _socket.RemoteEndPoint); } } ok = true; return(true); } // Continue to read rest arg.SetBuffer(_len, _size - _len); if (!_socket.ReceiveAsync(arg)) { break; } return(false); default: throw new SystemException("Bad state"); } } }