/// <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);
        }
        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>
        /// Compare encoded/decoded dataset messages
        /// </summary>
        /// <param name="uadpDataSetMessage"></param>
        /// <returns></returns>
        private void CompareEncodeDecode(UadpDataSetMessage uadpDataSetMessage)
        {
            ServiceMessageContext messageContextEncode = new ServiceMessageContext();
            BinaryEncoder         encoder = new BinaryEncoder(messageContextEncode);

            uadpDataSetMessage.Encode(encoder);
            byte[] bytes = ReadBytes(encoder.BaseStream);
            encoder.Dispose();

            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);
        }
        private async Task<IServiceResponse> ReceiveResponseAsync(CancellationToken token = default(CancellationToken))
        {
            await this.receivingSemaphore.WaitAsync(token).ConfigureAwait(false);
            try
            {
                token.ThrowIfCancellationRequested();
                this.ThrowIfClosedOrNotOpening();
                uint sequenceNumber;
                uint requestId;
                int paddingHeaderSize;
                int plainHeaderSize;
                int bodySize;
                int paddingSize;

                var bodyStream = SerializableBytes.CreateWritableStream();
                var bodyDecoder = new BinaryDecoder(bodyStream, this);
                try
                {
                    // read chunks
                    int chunkCount = 0;
                    bool isFinal = false;
                    do
                    {
                        chunkCount++;
                        if (this.LocalMaxChunkCount > 0 && chunkCount > this.LocalMaxChunkCount)
                        {
                            throw new ServiceResultException(StatusCodes.BadEncodingLimitsExceeded);
                        }

                        var count = await this.ReceiveAsync(this.receiveBuffer, 0, (int)this.LocalReceiveBufferSize, token).ConfigureAwait(false);
                        if (count == 0)
                        {
                            return null;
                        }

                        var stream = new MemoryStream(this.receiveBuffer, 0, count, true, true);
                        var decoder = new BinaryDecoder(stream, this);
                        try
                        {
                            uint channelId;
                            uint messageType = decoder.ReadUInt32(null);
                            int messageLength = (int)decoder.ReadUInt32(null);
                            Debug.Assert(count == messageLength, "Bytes received not equal to encoded Message length");
                            switch (messageType)
                            {
                                case UaTcpMessageTypes.MSGF:
                                case UaTcpMessageTypes.MSGC:
                                    // header
                                    channelId = decoder.ReadUInt32(null);
                                    if (channelId != this.ChannelId)
                                    {
                                        throw new ServiceResultException(StatusCodes.BadTcpSecureChannelUnknown);
                                    }

                                    // symmetric security header
                                    var tokenId = decoder.ReadUInt32(null);

                                    // detect new token
                                    if (tokenId != this.currentServerTokenId)
                                    {
                                        this.currentServerTokenId = tokenId;

                                        // update with new keys
                                        if (this.symIsSigned)
                                        {
                                            this.symVerifier.Key = this.serverSigningKey;
                                            if (this.symIsEncrypted)
                                            {
                                                this.currentServerEncryptingKey = this.serverEncryptingKey;
                                                this.currentServerInitializationVector = this.serverInitializationVector;
                                                this.symDecryptor = this.symEncryptionAlgorithm.CreateDecryptor(this.currentServerEncryptingKey, this.currentServerInitializationVector);
                                            }
                                        }
                                    }

                                    plainHeaderSize = decoder.Position;

                                    // decrypt
                                    if (this.symIsEncrypted)
                                    {
                                        using (var symDecryptor = this.symEncryptionAlgorithm.CreateDecryptor(this.currentServerEncryptingKey, this.currentServerInitializationVector))
                                        {
                                            int inputCount = messageLength - plainHeaderSize;
                                            Debug.Assert(inputCount % symDecryptor.InputBlockSize == 0, "Input data is not an even number of encryption blocks.");
                                            symDecryptor.TransformBlock(this.receiveBuffer, plainHeaderSize, inputCount, this.receiveBuffer, plainHeaderSize);
                                            symDecryptor.TransformFinalBlock(this.receiveBuffer, messageLength, 0);
                                        }
                                    }

                                    // verify
                                    if (this.symIsSigned)
                                    {
                                        var datalen = messageLength - this.symSignatureSize;
                                        byte[] signature = this.symVerifier.ComputeHash(this.receiveBuffer, 0, datalen);
                                        if (!signature.SequenceEqual(this.receiveBuffer.AsArraySegment(datalen, this.symSignatureSize)))
                                        {
                                            throw new ServiceResultException(StatusCodes.BadSecurityChecksFailed);
                                        }
                                    }

                                    // read sequence header
                                    sequenceNumber = decoder.ReadUInt32(null);
                                    requestId = decoder.ReadUInt32(null);

                                    // body
                                    if (this.symIsEncrypted)
                                    {
                                        if (this.symEncryptionBlockSize > 256)
                                        {
                                            paddingHeaderSize = 2;
                                            paddingSize = BitConverter.ToInt16(this.receiveBuffer, messageLength - this.symSignatureSize - paddingHeaderSize);
                                        }
                                        else
                                        {
                                            paddingHeaderSize = 1;
                                            paddingSize = this.receiveBuffer[messageLength - this.symSignatureSize - paddingHeaderSize];
                                        }

                                        bodySize = messageLength - plainHeaderSize - SequenceHeaderSize - paddingSize - paddingHeaderSize - this.symSignatureSize;
                                    }
                                    else
                                    {
                                        bodySize = messageLength - plainHeaderSize - SequenceHeaderSize - this.symSignatureSize;
                                    }

                                    bodyStream.Write(this.receiveBuffer, plainHeaderSize + SequenceHeaderSize, bodySize);
                                    isFinal = messageType == UaTcpMessageTypes.MSGF;
                                    break;

                                case UaTcpMessageTypes.OPNF:
                                case UaTcpMessageTypes.OPNC:
                                    // header
                                    channelId = decoder.ReadUInt32(null);

                                    // asymmetric header
                                    var securityPolicyUri = decoder.ReadString(null);
                                    var serverCertificateByteString = decoder.ReadByteString(null);
                                    var clientThumbprint = decoder.ReadByteString(null);
                                    plainHeaderSize = decoder.Position;

                                    // decrypt
                                    if (this.asymIsEncrypted)
                                    {
                                        byte[] cipherTextBlock = new byte[this.asymLocalCipherTextBlockSize];
                                        int jj = plainHeaderSize;
                                        for (int ii = plainHeaderSize; ii < messageLength; ii += this.asymLocalCipherTextBlockSize)
                                        {
                                            Buffer.BlockCopy(this.receiveBuffer, ii, cipherTextBlock, 0, this.asymLocalCipherTextBlockSize);

                                            // decrypt with local private key.
                                            byte[] plainTextBlock = this.LocalPrivateKey.Decrypt(cipherTextBlock, this.asymEncryptionPadding);
                                            Debug.Assert(plainTextBlock.Length == this.asymLocalPlainTextBlockSize, "Decrypted block length was not as expected.");
                                            Buffer.BlockCopy(plainTextBlock, 0, this.receiveBuffer, jj, this.asymLocalPlainTextBlockSize);
                                            jj += this.asymLocalPlainTextBlockSize;
                                        }

                                        messageLength = jj;
                                        decoder.Position = plainHeaderSize;
                                    }

                                    // verify
                                    if (this.asymIsSigned)
                                    {
                                        // verify with remote public key.
                                        var datalen = messageLength - this.asymRemoteSignatureSize;
                                        if (!this.RemotePublicKey.VerifyData(this.receiveBuffer, 0, datalen, this.receiveBuffer.AsArraySegment(datalen, this.asymRemoteSignatureSize).ToArray(), this.asymSignatureHashAlgorithmName, RSASignaturePadding.Pkcs1))
                                        {
                                            throw new ServiceResultException(StatusCodes.BadSecurityChecksFailed);
                                        }
                                    }

                                    // sequence header
                                    sequenceNumber = decoder.ReadUInt32(null);
                                    requestId = decoder.ReadUInt32(null);

                                    // body
                                    if (this.asymIsEncrypted)
                                    {
                                        if (this.asymLocalCipherTextBlockSize > 256)
                                        {
                                            paddingHeaderSize = 2;
                                            paddingSize = BitConverter.ToInt16(this.receiveBuffer, messageLength - this.asymRemoteSignatureSize - paddingHeaderSize);
                                        }
                                        else
                                        {
                                            paddingHeaderSize = 1;
                                            paddingSize = this.receiveBuffer[messageLength - this.asymRemoteSignatureSize - paddingHeaderSize];
                                        }

                                        bodySize = messageLength - plainHeaderSize - SequenceHeaderSize - paddingSize - paddingHeaderSize - this.asymRemoteSignatureSize;
                                    }
                                    else
                                    {
                                        bodySize = messageLength - plainHeaderSize - SequenceHeaderSize - this.asymRemoteSignatureSize;
                                    }

                                    bodyStream.Write(this.receiveBuffer, plainHeaderSize + SequenceHeaderSize, bodySize);
                                    isFinal = messageType == UaTcpMessageTypes.OPNF;
                                    break;

                                case UaTcpMessageTypes.ERRF:
                                case UaTcpMessageTypes.MSGA:
                                case UaTcpMessageTypes.OPNA:
                                case UaTcpMessageTypes.CLOA:
                                    var code = (StatusCode)decoder.ReadUInt32(null);
                                    var message = decoder.ReadString(null);
                                    throw new ServiceResultException(code, message);

                                default:
                                    throw new ServiceResultException(StatusCodes.BadUnknownResponse);
                            }

                            if (this.LocalMaxMessageSize > 0 && bodyStream.Position > this.LocalMaxMessageSize)
                            {
                                throw new ServiceResultException(StatusCodes.BadEncodingLimitsExceeded);
                            }
                        }
                        finally
                        {
                            decoder.Dispose();
                        }
                    }
                    while (!isFinal);
                    bodyStream.Seek(0L, SeekOrigin.Begin);
                    var nodeId = bodyDecoder.ReadNodeId(null);
                    IServiceResponse response;

                    // fast path
                    if (nodeId == PublishResponseNodeId)
                    {
                        response = new PublishResponse();
                    }
                    else if (nodeId == ReadResponseNodeId)
                    {
                        response = new ReadResponse();
                    }
                    else
                    {
                        // find node in dictionary
                        Type type2;
                        if (!BinaryEncodingIdToTypeDictionary.TryGetValue(NodeId.ToExpandedNodeId(nodeId, this.NamespaceUris), out type2))
                        {
                            throw new ServiceResultException(StatusCodes.BadEncodingError, "NodeId not registered in dictionary.");
                        }

                        // create response
                        response = (IServiceResponse)Activator.CreateInstance(type2);
                    }

                    // set properties from message stream
                    response.Decode(bodyDecoder);
                    return response;
                }
                finally
                {
                    bodyDecoder.Dispose();
                }
            }
            finally
            {
                this.receivingSemaphore.Release();
            }
        }
        protected override async Task OnOpenAsync(CancellationToken token)
        {
            token.ThrowIfCancellationRequested();
            this.sendBuffer = new byte[MinBufferSize];
            this.receiveBuffer = new byte[MinBufferSize];

            this.tcpClient = new System.Net.Sockets.TcpClient { NoDelay = true };
            var uri = new UriBuilder(this.RemoteEndpoint.EndpointUrl);
            await this.tcpClient.ConnectAsync(uri.Host, uri.Port).ConfigureAwait(false);
            this.instream = this.outstream = this.tcpClient.GetStream();

            // send 'hello'.
            int count;
            var encoder = new BinaryEncoder(new MemoryStream(this.sendBuffer, 0, MinBufferSize, true, false));
            try
            {
                encoder.WriteUInt32(null, UaTcpMessageTypes.HELF);
                encoder.WriteUInt32(null, 0u);
                encoder.WriteUInt32(null, ProtocolVersion);
                encoder.WriteUInt32(null, this.LocalReceiveBufferSize);
                encoder.WriteUInt32(null, this.LocalSendBufferSize);
                encoder.WriteUInt32(null, this.LocalMaxMessageSize);
                encoder.WriteUInt32(null, this.LocalMaxChunkCount);
                encoder.WriteString(null, uri.ToString());
                count = encoder.Position;
                encoder.Position = 4;
                encoder.WriteUInt32(null, (uint)count);
                encoder.Position = count;

                await this.SendAsync(this.sendBuffer, 0, count, token).ConfigureAwait(false);
            }
            finally
            {
                encoder.Dispose();
            }

            // receive response
            count = await this.ReceiveAsync(this.receiveBuffer, 0, MinBufferSize, token).ConfigureAwait(false);
            if (count == 0)
            {
                throw new ObjectDisposedException("socket");
            }

            // decode 'ack' or 'err'.
            var decoder = new BinaryDecoder(new MemoryStream(this.receiveBuffer, 0, count, false, false));
            try
            {
                var type = decoder.ReadUInt32(null);
                var len = decoder.ReadUInt32(null);
                if (type == UaTcpMessageTypes.ACKF)
                {
                    var remoteProtocolVersion = decoder.ReadUInt32(null);
                    if (remoteProtocolVersion < ProtocolVersion)
                    {
                        throw new ServiceResultException(StatusCodes.BadProtocolVersionUnsupported);
                    }

                    this.RemoteSendBufferSize = decoder.ReadUInt32(null);
                    this.RemoteReceiveBufferSize = decoder.ReadUInt32(null);
                    this.RemoteMaxMessageSize = decoder.ReadUInt32(null);
                    this.RemoteMaxChunkCount = decoder.ReadUInt32(null);
                    return;
                }
                else if (type == UaTcpMessageTypes.ERRF)
                {
                    var statusCode = decoder.ReadUInt32(null);
                    var message = decoder.ReadString(null);
                    throw new ServiceResultException(statusCode, message);
                }

                throw new InvalidOperationException("UaTcpTransportChannel.OnOpenAsync received unexpected message type.");
            }
            finally
            {
                decoder.Dispose();
            }
        }