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