public ReliableSamHelper(
            ISamHelper samHelper,
            ReliableSamHelperSettings settings
            )
        {
            if (samHelper == null)
            {
                throw new ArgumentNullException(
                          MyNameof.GetLocalVarName(() => samHelper));
            }
            if (settings == null)
            {
                throw new ArgumentNullException(
                          MyNameof.GetLocalVarName(() => settings));
            }
            _samHelper = samHelper;
            _settings  = settings;
            var rng = new Random(DateTime.UtcNow.Millisecond);

            _nextOutMessageId = (uint)rng.Next(int.MaxValue);
            RawDatagramReceived.ObserveOn(TaskPoolScheduler.Default).Subscribe(args => _log.Trace(
                                                                                   "{5} recv raw datagram of {0} bytes " +
                                                                                   "from {1} id={2}, rid={3}, kind={6}, ansiS='{4}'",
                                                                                   args.Data.Length,
                                                                                   args.Destination.Substring(0, 20),
                                                                                   args.MessageId,
                                                                                   args.ReplyToMessageId,
                                                                                   Encoding.ASCII.GetString(args.Data),
                                                                                   _reliableSamHelperGuid.ToString().Substring(0, 5),
                                                                                   args.MessageKind
                                                                                   ));
            ReliableMessageReceived.ObserveOn(TaskPoolScheduler.Default).Subscribe(args =>
                                                                                   _log.Trace(
                                                                                       "{4} recv reliable message of {0} bytes " +
                                                                                       "from {1} id={2}, rid={3}, kind={5}",
                                                                                       args.Data.Length,
                                                                                       args.Destination.Substring(0, 20),
                                                                                       args.MessageId,
                                                                                       args.ReplyToMessageId,
                                                                                       _reliableSamHelperGuid.ToString().Substring(0, 5),
                                                                                       args.MessageKind
                                                                                       )
                                                                                   );
            _subscriptions.AddRange(
                new[]
            {
                _samHelper.DatagramDataReceived.ObserveOn(TaskPoolScheduler.Default).Subscribe(
                    SamHelperOnDatagramDataReceived
                    ),
                GetProtocolVersionReceived.ObserveOn(TaskPoolScheduler.Default).Subscribe(
                    OnGetProtocolVersionReceived
                    ),
                HandshakeStartReceived.ObserveOn(TaskPoolScheduler.Default).Subscribe(
                    OnHandshakeStartReceived
                    ),
                GetMessageStatusReceived.ObserveOn(TaskPoolScheduler.Default).Subscribe(
                    OnGetMessageStatusReceived
                    ),
                BlockSendReceived.ObserveOn(TaskPoolScheduler.Default).Subscribe(
                    OnBlockSendReceived
                    ),
                Observable.Interval(TimeSpan.FromSeconds(20.0d)).ObserveOn(TaskPoolScheduler.Default).Subscribe(
                    IntervalCleanupAction
                    )
            }
                );
            _stateHelper.SetInitializedState();
        }
        private async void SamHelperOnDatagramDataReceived(
            DatagramDataReceivedArgs datagramDataReceivedArgs
            )
        {
            try
            {
                using (_stateHelper.GetFuncWrapper())
                {
                    if (
                        datagramDataReceivedArgs.Data.Length
                        > (_settings.MaxBlockSize + 1000)
                        )
                    {
                        return;
                    }
                    if (
                        _dropDestinations.Contains(
                            datagramDataReceivedArgs.Destination
                            )
                        )
                    {
                        return;
                    }

                    string fromDestination = datagramDataReceivedArgs.Destination;
                    using (
                        var reader = new EndianBinaryReader(
                            _littleEndianConverter,
                            new MemoryStream(datagramDataReceivedArgs.Data)
                            )
                        )
                    {
                        var blockType = (ReliableDatagramCodes)reader.ReadByte();
                        switch (blockType)
                        {
                        case ReliableDatagramCodes.RawDatagram:
                        {
                            uint messageId        = reader.ReadUInt32();
                            uint replyToMessageId = reader.ReadUInt32();
                            byte messageKind      = reader.ReadByte();
                            var  messageHash      = reader.ReadBytesOrThrow(32);
                            var  rawData          = ReadBytesToEnd(reader);
                            if (
                                messageHash.SequenceEqual(
                                    GetMessageHash(
                                        messageId,
                                        replyToMessageId,
                                        messageKind,
                                        rawData
                                        )
                                    )
                                )
                            {
                                RawDatagramReceived.OnNext(
                                    new ReliableMessageReceivedArgs()
                                    {
                                        Data             = rawData,
                                        Destination      = fromDestination,
                                        MessageId        = messageId,
                                        ReplyToMessageId = replyToMessageId,
                                        MessageKind      = messageKind
                                    }
                                    );
                            }
                            return;
                        }

                        case ReliableDatagramCodes.HandshakeStart:
                        {
                            uint messageId        = reader.ReadUInt32();
                            uint replyToMessageId = reader.ReadUInt32();
                            byte messageKind      = reader.ReadByte();
                            uint totalMessageSize = reader.ReadUInt32();
                            if (totalMessageSize > _settings.MaxMesageLength)
                            {
                                return;
                            }
                            uint blockSize = reader.ReadUInt32();
                            if (blockSize > _settings.MaxBlockSize)
                            {
                                return;
                            }
                            var  messageHash    = reader.ReadBytesOrThrow(32);
                            uint firstBlockSize = reader.ReadUInt32();
                            if (
                                firstBlockSize == 0
                                ||
                                firstBlockSize > _settings.MaxBlockSize
                                )
                            {
                                return;
                            }
                            byte[] firstBlockData
                                = reader.ReadBytesOrThrow((int)firstBlockSize);
                            HandshakeStartReceived.OnNext(
                                new HandshakeStartArgs
                                {
                                    Destination      = fromDestination,
                                    BlockSize        = blockSize,
                                    MessageId        = messageId,
                                    ReplyToMessageId = replyToMessageId,
                                    MessageSize      = totalMessageSize,
                                    MessageKind      = messageKind,
                                    MessageHash      = messageHash,
                                    FirstBlockData   = firstBlockData
                                }
                                );
                            return;
                        }

                        case ReliableDatagramCodes.MessageStatus:
                        {
                            uint messageId = reader.ReadUInt32();
                            if (!await CheckExistOutMessage(fromDestination, messageId).ConfigureAwait(false))
                            {
                                return;
                            }
                            var errorCode
                                = (MessageStatusArgs.MessageStatusCode)
                                  reader.ReadByte();
                            uint blocksReceived = reader.ReadUInt32();
                            MessageStatusReceived.OnNext(
                                new MessageStatusArgs()
                                {
                                    Destination
                                        = datagramDataReceivedArgs
                                          .Destination,
                                    MessageId      = messageId,
                                    StatusCode     = errorCode,
                                    BlocksReceived = blocksReceived
                                }
                                );
                            return;
                        }

                        case ReliableDatagramCodes.BlockSend:
                        {
                            uint messageId = reader.ReadUInt32();
                            uint blockId   = reader.ReadUInt32();
                            var  blockData = ReadBytesToEnd(reader);
                            if (
                                blockData.Length == 0 ||
                                blockData.Length > _settings.MaxBlockSize
                                )
                            {
                                return;
                            }
                            BlockSendReceived.OnNext(new BlockSendArgs()
                                {
                                    Destination = fromDestination,
                                    MessageId   = messageId,
                                    BlockId     = blockId,
                                    BlockData   = blockData
                                });
                            return;
                        }

                        case ReliableDatagramCodes.BlockConfirmation:
                        {
                            uint messageId = reader.ReadUInt32();
                            if (!await CheckExistOutMessage(fromDestination, messageId).ConfigureAwait(false))
                            {
                                return;
                            }
                            uint blockId = reader.ReadUInt32();
                            BlockConfirmationReceived.OnNext(
                                new BlockConfirmationArgs()
                                {
                                    Destination = fromDestination,
                                    MessageId   = messageId,
                                    BlockId     = blockId
                                }
                                );
                            return;
                        }

                        case ReliableDatagramCodes.GetMessageStatus:
                        {
                            uint messageId = reader.ReadUInt32();
                            GetMessageStatusReceived.OnNext(
                                new GetMessageStatusArgs()
                                {
                                    Destination = fromDestination,
                                    MessageId   = messageId
                                }
                                );
                            return;
                        }

                        case ReliableDatagramCodes.GetProtocolVersion:
                        {
                            GetProtocolVersionReceived.OnNext(
                                new GetProtocolVersionArgs()
                                {
                                    Destination = fromDestination
                                }
                                );
                            return;
                        }

                        case ReliableDatagramCodes.ProtocolVersion:
                        {
                            uint protocolVersion = reader.ReadUInt32();
                            ProtocolVersionReceived.OnNext(
                                new ProtocolVersionArgs()
                                {
                                    Destination     = fromDestination,
                                    ProtocolVersion = protocolVersion
                                }
                                );
                            return;
                        }
                        }
                    }
                }
            }
            catch (EndOfStreamException exc)
            {
                _log.Error(
                    "SamHelperOnDatagramDataReceived" +
                    " EndOfStreamException '{0}'",
                    exc.ToString()
                    );
            }
            catch (OperationCanceledException)
            {
            }
            catch (WrongDisposableObjectStateException)
            {
            }
            catch (Exception exc)
            {
                _log.Error(
                    "SamHelperOnDatagramDataReceived" +
                    " unexpected error '{0}'",
                    exc.ToString()
                    );
            }
        }