public async Task <uint> SendReliableMessage(
            string destination,
            byte[] data,
            IProgress <OutMessageProgressInfo> progress = null,
            uint messageId          = 0,
            uint replyToMessageId   = 0,
            byte messageKind        = 0,
            CancellationToken token = default(CancellationToken)
            )
        {
            using (_stateHelper.GetFuncWrapper())
            {
                try
                {
                    Assert.True(SamHelper.IsDestinationStringValid(destination));
                    Assert.NotNull(data);
                    Assert.InRange(data.Length, 1, _settings.MaxMesageLength);
                    if (messageId == 0)
                    {
                        messageId = await GetNextOutMessageId().ConfigureAwait(false);
                    }
                    DestinationInfo destinationInfo;
                    using (await _destinationDbLockSem.GetDisposable(token: token).ConfigureAwait(false))
                    {
                        if (!_destinationDb.ContainsKey(destination))
                        {
                            _destinationDb.Add(destination, new DestinationInfo());
                        }
                        destinationInfo = _destinationDb[destination];
                    }
                    OutMessageInfo outMessageInfo;
                    using (await destinationInfo.OutMessagesDbLockSem.GetDisposable(token: token).ConfigureAwait(false))
                    {
                        if (
                            destinationInfo.OutMessagesDb.ContainsKey(messageId)
                            )
                        {
                            throw new EnumException <SendReliableMessageExcs>(
                                      SendReliableMessageExcs.ExistMessageId
                                      );
                        }
                        outMessageInfo = new OutMessageInfo(
                            data,
                            replyToMessageId,
                            _settings.MaxBlockSize,
                            progress
                            );
                        destinationInfo.OutMessagesDb.Add(messageId, outMessageInfo);
                    }
                    try
                    {
                        uint blocksCount = outMessageInfo.BlockCount;

                        /*
                         * Handshake
                         */
                        using (
                            var compositeTokenSource = CancellationTokenSource.CreateLinkedTokenSource(
                                _cts.Token,
                                token
                                )
                            )
                        {
                            try
                            {
                                var messageStatusTask =
                                    MessageStatusReceived
                                    .Where(
                                        x =>
                                        x.Destination == destination &&
                                        x.MessageId == messageId
                                        )
                                    .FirstAsync()
                                    .ToTask(compositeTokenSource.Token);
                                byte[] messageHash = GetMessageHash(
                                    messageId,
                                    replyToMessageId,
                                    messageKind,
                                    data
                                    );
                                for (
                                    outMessageInfo.HandshakeAttemptNo = 0;
                                    outMessageInfo.HandshakeAttemptNo
                                    <= _settings.HandshakeAttemptCount;
                                    outMessageInfo.HandshakeAttemptNo++
                                    )
                                {
                                    if (
                                        outMessageInfo.HandshakeAttemptNo
                                        == _settings.HandshakeAttemptCount
                                        )
                                    {
                                        throw new EnumException <SendReliableMessageExcs>(
                                                  SendReliableMessageExcs.HandshakeTimeout
                                                  );
                                    }
                                    await SendHandshakeStart(
                                        destination,
                                        messageId,
                                        replyToMessageId,
                                        (uint)data.Length,
                                        outMessageInfo.BlockSize,
                                        messageHash,
                                        outMessageInfo.Data[0],
                                        messageKind
                                        ).ConfigureAwait(false);

                                    var waitTasks = await Task.WhenAny(
                                        messageStatusTask,
                                        Task.Delay(
                                            TimeSpan.FromSeconds(
                                                _settings.HandshakeTimeoutSeconds
                                                ),
                                            compositeTokenSource.Token
                                            )
                                        ).ConfigureAwait(false);

                                    if (waitTasks == messageStatusTask)
                                    {
                                        var messageStatus = await messageStatusTask.ConfigureAwait(false);

                                        if (
                                            !(
                                                messageStatus.StatusCode ==
                                                MessageStatusArgs.MessageStatusCode.HandshakeOk
                                                ||
                                                (
                                                    messageStatus.StatusCode ==
                                                    MessageStatusArgs.MessageStatusCode.AllBlocksReceived &&
                                                    outMessageInfo.BlockCount == 1
                                                )
                                                )
                                            )
                                        {
                                            throw new EnumException <SendReliableMessageExcs>(
                                                      SendReliableMessageExcs.HandshakeError,
                                                      messageStatus.WriteObjectToJson()
                                                      );
                                        }
                                        break;
                                    }
                                }
                                outMessageInfo.Status            = OutMessageStatus.HandshakeOk;
                                outMessageInfo.BlockConfirmed[0] = true;
                                if (outMessageInfo.BlockCount > 1)
                                {
                                    // Handshake established, start sending blocks
                                    DateTime lastBlockConfirmationTime = DateTime.UtcNow;
                                    var      blocksInProgress          = new SortedSet <uint>();
                                    var      blocksInProgressLockSem   = new SemaphoreSlim(1);
                                    var      blockSentTimes            = new DateTime[blocksCount];
                                    using (
                                        BlockConfirmationReceived
                                        .Where(_ =>
                                               _.Destination == destination &&
                                               _.MessageId == messageId
                                               )
                                        .ObserveOn(TaskPoolScheduler.Default)
                                        .Subscribe(async x =>
                                    {
                                        if (
                                            x.BlockId < blocksCount &&
                                            !outMessageInfo.BlockConfirmed[x.BlockId]
                                            )
                                        {
                                            await outMessageInfo.ConfirmBlock(x.BlockId).ConfigureAwait(false);
                                            lastBlockConfirmationTime = DateTime.UtcNow;
                                            using (await blocksInProgressLockSem.GetDisposable().ConfigureAwait(false))
                                            {
                                                blocksInProgress.Remove(x.BlockId);
                                            }
                                        }
                                    }
                                                   )
                                        )
                                    {
                                        while (true)
                                        {
                                            compositeTokenSource.Token.ThrowIfCancellationRequested();
                                            IEnumerable <int> blocksToSend;
                                            using (
                                                await
                                                blocksInProgressLockSem.GetDisposable(token: compositeTokenSource.Token)
                                                .ConfigureAwait(false))
                                            {
                                                if (
                                                    blocksInProgress.Count == 0 &&
                                                    outMessageInfo.BlockConfirmed.All(x => x)
                                                    )
                                                {
                                                    break;
                                                }
                                                blocksInProgress.RemoveWhere(
                                                    i =>
                                                    DateTime.UtcNow >
                                                    (
                                                        blockSentTimes[i]
                                                        + TimeSpan.FromSeconds(
                                                            _settings
                                                            .ConfirmationOneBlockTimeoutSeconds
                                                            )
                                                    )
                                                    );
                                                blocksToSend = Enumerable.Range(0, (int)blocksCount)
                                                               .Where(
                                                    i =>
                                                    !blocksInProgress.Contains((uint)i) &&
                                                    !outMessageInfo.BlockConfirmed[i]
                                                    )
                                                               .Take(
                                                    Math.Max(
                                                        0,
                                                        _settings.WindowSize
                                                        - blocksInProgress.Count
                                                        )
                                                    );
                                            }
                                            foreach (int blockId in blocksToSend)
                                            {
                                                await SendBlock(
                                                    destination,
                                                    messageId,
                                                    (uint)blockId
                                                    ).ConfigureAwait(false);

                                                await outMessageInfo.SendBlock(
                                                    (uint)blockId
                                                    ).ConfigureAwait(false);

                                                using (
                                                    await
                                                    blocksInProgressLockSem.GetDisposable(token: compositeTokenSource.Token)
                                                    .ConfigureAwait(false))
                                                {
                                                    blocksInProgress.Add((uint)blockId);
                                                }
                                                blockSentTimes[blockId] = DateTime.UtcNow;
                                            }
                                            if (
                                                DateTime.UtcNow >
                                                lastBlockConfirmationTime +
                                                TimeSpan.FromSeconds(
                                                    _settings.ConfirmationTimeoutSeconds
                                                    )
                                                )
                                            {
                                                throw new EnumException <SendReliableMessageExcs>(
                                                          SendReliableMessageExcs.SendBlocksTimeout
                                                          );
                                            }
                                            await Task.Delay(50, compositeTokenSource.Token).ConfigureAwait(false);
                                        }
                                    }
                                }
                                outMessageInfo.Status = OutMessageStatus.AllBlocksSent;
                                // all blocks sent and confirmed
                                _log.Trace(
                                    "{4}, send reliable message of {0} bytes " +
                                    "to {1} id={2},rid={3},kind={5}",
                                    data.Length,
                                    destination.Substring(0, 20),
                                    messageId,
                                    replyToMessageId,
                                    _reliableSamHelperGuid.ToString().Substring(0, 5),
                                    messageKind
                                    );
                                return(messageId);
                            }
                            finally
                            {
                                compositeTokenSource.Cancel();
                            }
                        }
                    }
                    finally
                    {
                        TryRemoveOutMessageInfo(destinationInfo, messageId);
                    }
                }
                catch (EnumException <SendReliableMessageExcs> )
                {
                    throw;
                }
                catch (OperationCanceledException)
                {
                    throw;
                }
                catch (TimeoutException)
                {
                    throw;
                }
                catch (Exception exc)
                {
                    throw new EnumException <SendReliableMessageExcs>(
                              SendReliableMessageExcs.UnexpectedError,
                              innerException: exc
                              );
                }
            }
        }
 public async Task<uint> SendReliableMessage(
     string destination,
     byte[] data,
     IProgress<OutMessageProgressInfo> progress = null,
     uint messageId = 0,
     uint replyToMessageId = 0,
     byte messageKind = 0,
     CancellationToken token = default(CancellationToken)
 )
 {
     using (_stateHelper.GetFuncWrapper())
     {
         try
         {
             Assert.True(SamHelper.IsDestinationStringValid(destination));
             Assert.NotNull(data);
             Assert.InRange(data.Length, 1, _settings.MaxMesageLength);
             if (messageId == 0)
                 messageId = await GetNextOutMessageId().ConfigureAwait(false);
             DestinationInfo destinationInfo;
             using (await _destinationDbLockSem.GetDisposable(token: token).ConfigureAwait(false))
             {
                 if (!_destinationDb.ContainsKey(destination))
                     _destinationDb.Add(destination, new DestinationInfo());
                 destinationInfo = _destinationDb[destination];
             }
             OutMessageInfo outMessageInfo;
             using (await destinationInfo.OutMessagesDbLockSem.GetDisposable(token: token).ConfigureAwait(false))
             {
                 if (
                     destinationInfo.OutMessagesDb.ContainsKey(messageId)
                 )
                     throw new EnumException<SendReliableMessageExcs>(
                         SendReliableMessageExcs.ExistMessageId
                     );
                 outMessageInfo = new OutMessageInfo(
                     data,
                     replyToMessageId,
                     _settings.MaxBlockSize,
                     progress
                 );
                 destinationInfo.OutMessagesDb.Add(messageId, outMessageInfo);
             }
             try
             {
                 uint blocksCount = outMessageInfo.BlockCount;
                 /*
                  * Handshake
                  */
                 using (
                     var compositeTokenSource = CancellationTokenSource.CreateLinkedTokenSource(
                         _cts.Token,
                         token
                     )
                 )
                 {
                     try
                     {
                         var messageStatusTask =
                             MessageStatusReceived
                                 .Where(
                                     x =>
                                         x.Destination == destination
                                         && x.MessageId == messageId
                                 )
                                 .FirstAsync()
                                 .ToTask(compositeTokenSource.Token);
                         byte[] messageHash = GetMessageHash(
                             messageId,
                             replyToMessageId,
                             messageKind,
                             data
                         );
                         for (
                             outMessageInfo.HandshakeAttemptNo = 0;
                             outMessageInfo.HandshakeAttemptNo
                                 <= _settings.HandshakeAttemptCount;
                             outMessageInfo.HandshakeAttemptNo++
                         )
                         {
                             if (
                                 outMessageInfo.HandshakeAttemptNo
                                 == _settings.HandshakeAttemptCount
                             )
                                 throw new EnumException<SendReliableMessageExcs>(
                                     SendReliableMessageExcs.HandshakeTimeout
                                 );
                             await SendHandshakeStart(
                                 destination,
                                 messageId,
                                 replyToMessageId,
                                 (uint)data.Length,
                                 outMessageInfo.BlockSize,
                                 messageHash,
                                 outMessageInfo.Data[0],
                                 messageKind
                             ).ConfigureAwait(false);
                             var waitTasks = await Task.WhenAny(
                                 messageStatusTask,
                                 Task.Delay(
                                     TimeSpan.FromSeconds(
                                         _settings.HandshakeTimeoutSeconds
                                     ),
                                     compositeTokenSource.Token
                                 )
                             ).ConfigureAwait(false);
                             if (waitTasks == messageStatusTask)
                             {
                                 var messageStatus = await messageStatusTask.ConfigureAwait(false);
                                 if (
                                     !(
                                         messageStatus.StatusCode ==
                                         MessageStatusArgs.MessageStatusCode.HandshakeOk
                                         ||
                                         (
                                             messageStatus.StatusCode ==
                                             MessageStatusArgs.MessageStatusCode.AllBlocksReceived
                                             && outMessageInfo.BlockCount == 1
                                             )
                                         )
                                     )
                                 {
                                     throw new EnumException<SendReliableMessageExcs>(
                                         SendReliableMessageExcs.HandshakeError,
                                         messageStatus.WriteObjectToJson()
                                     );
                                 }
                                 break;
                             }
                         }
                         outMessageInfo.Status = OutMessageStatus.HandshakeOk;
                         outMessageInfo.BlockConfirmed[0] = true;
                         if (outMessageInfo.BlockCount > 1)
                         {
                             // Handshake established, start sending blocks
                             DateTime lastBlockConfirmationTime = DateTime.UtcNow;
                             var blocksInProgress = new SortedSet<uint>();
                             var blocksInProgressLockSem = new SemaphoreSlim(1);
                             var blockSentTimes = new DateTime[blocksCount];
                             using (
                                 BlockConfirmationReceived
                                     .Where( _ =>
                                         _.Destination == destination
                                         && _.MessageId == messageId
                                     )
                                     .ObserveOn(TaskPoolScheduler.Default)
                                     .Subscribe(async x =>
                                         {
                                             if (
                                                 x.BlockId < blocksCount
                                                 && !outMessageInfo.BlockConfirmed[x.BlockId]
                                                 )
                                             {
                                                 await outMessageInfo.ConfirmBlock(x.BlockId).ConfigureAwait(false);
                                                 lastBlockConfirmationTime = DateTime.UtcNow;
                                                 using (await blocksInProgressLockSem.GetDisposable().ConfigureAwait(false))
                                                 {
                                                     blocksInProgress.Remove(x.BlockId);
                                                 }
                                             }
                                         }
                                     )
                             )
                             {
                                 while (true)
                                 {
                                     compositeTokenSource.Token.ThrowIfCancellationRequested();
                                     IEnumerable<int> blocksToSend;
                                     using (
                                         await
                                             blocksInProgressLockSem.GetDisposable(token: compositeTokenSource.Token)
                                                 .ConfigureAwait(false))
                                     {
                                         if (
                                             blocksInProgress.Count == 0
                                             && outMessageInfo.BlockConfirmed.All(x => x)
                                             )
                                             break;
                                         blocksInProgress.RemoveWhere(
                                             i =>
                                                 DateTime.UtcNow >
                                                 (
                                                     blockSentTimes[i]
                                                     + TimeSpan.FromSeconds(
                                                         _settings
                                                             .ConfirmationOneBlockTimeoutSeconds
                                                         )
                                                     )
                                             );
                                         blocksToSend = Enumerable.Range(0, (int)blocksCount)
                                             .Where(
                                                 i =>
                                                     !blocksInProgress.Contains((uint)i)
                                                     && !outMessageInfo.BlockConfirmed[i]
                                             )
                                             .Take(
                                                 Math.Max(
                                                     0,
                                                     _settings.WindowSize
                                                     - blocksInProgress.Count
                                                     )
                                             );
                                     }
                                     foreach (int blockId in blocksToSend)
                                     {
                                         await SendBlock(
                                             destination,
                                             messageId,
                                             (uint)blockId
                                         ).ConfigureAwait(false);
                                         await outMessageInfo.SendBlock(
                                             (uint)blockId
                                         ).ConfigureAwait(false);
                                         using (
                                             await
                                                 blocksInProgressLockSem.GetDisposable(token: compositeTokenSource.Token)
                                                     .ConfigureAwait(false))
                                         {
                                             blocksInProgress.Add((uint)blockId);
                                         }
                                         blockSentTimes[blockId] = DateTime.UtcNow;
                                     }
                                     if (
                                         DateTime.UtcNow >
                                         lastBlockConfirmationTime +
                                         TimeSpan.FromSeconds(
                                             _settings.ConfirmationTimeoutSeconds
                                         )
                                     )
                                         throw new EnumException<SendReliableMessageExcs>(
                                             SendReliableMessageExcs.SendBlocksTimeout
                                             );
                                     await Task.Delay(50, compositeTokenSource.Token).ConfigureAwait(false);
                                 }
                             }
                         }
                         outMessageInfo.Status = OutMessageStatus.AllBlocksSent;
                         // all blocks sent and confirmed
                         _log.Trace(
                             "{4}, send reliable message of {0} bytes " +
                             "to {1} id={2},rid={3},kind={5}",
                             data.Length,
                             destination.Substring(0, 20),
                             messageId,
                             replyToMessageId,
                             _reliableSamHelperGuid.ToString().Substring(0, 5),
                             messageKind
                             );
                         return messageId;
                     }
                     finally
                     {
                         compositeTokenSource.Cancel();
                     }
                 }
             }
             finally
             {
                 TryRemoveOutMessageInfo(destinationInfo, messageId);
             }
         }
         catch (EnumException<SendReliableMessageExcs>)
         {
             throw;
         }
         catch (OperationCanceledException)
         {
             throw;
         }
         catch (TimeoutException)
         {
             throw;
         }
         catch (Exception exc)
         {
             throw new EnumException<SendReliableMessageExcs>(
                 SendReliableMessageExcs.UnexpectedError,
                 innerException: exc
             );
         }
     }
 }