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
             );
         }
     }
 }