Beispiel #1
0
        public async Task SendAckAsync(string clientId, IFlowPacket ack, IMqttChannel <IPacket> channel, PendingMessageStatus status = PendingMessageStatus.PendingToSend)
        {
            if ((ack.Type == MqttPacketType.PublishReceived || ack.Type == MqttPacketType.PublishRelease) &&
                status == PendingMessageStatus.PendingToSend)
            {
                SavePendingAcknowledgement(ack, clientId);
            }

            if (!channel.IsConnected)
            {
                return;
            }

            await channel.SendAsync(ack)
            .ConfigureAwait(continueOnCapturedContext: false);

            if (ack.Type == MqttPacketType.PublishReceived)
            {
                await MonitorAckAsync <PublishRelease> (ack, clientId, channel)
                .ConfigureAwait(continueOnCapturedContext: false);
            }
            else if (ack.Type == MqttPacketType.PublishRelease)
            {
                await MonitorAckAsync <PublishComplete> (ack, clientId, channel)
                .ConfigureAwait(continueOnCapturedContext: false);
            }
        }
        public async Task ExecuteAsync(string clientId, IPacket input, IMqttChannel <IPacket> channel)
        {
            if (input.Type != MqttPacketType.Disconnect)
            {
                return;
            }

            await Task.Run(() =>
            {
                Disconnect disconnect = input as Disconnect;

                _tracer.Info(ServerProperties.DisconnectFlow_Disconnecting(clientId));

                _willRepository.Delete(clientId);

                ClientSession session = _sessionRepository.Read(clientId);

                if (session == null)
                {
                    throw new MqttException(ServerProperties.SessionRepository_ClientSessionNotFound(clientId));
                }

                if (session.Clean)
                {
                    _sessionRepository.Delete(session.Id);

                    _tracer.Info(ServerProperties.Server_DeletedSessionOnDisconnect(clientId));
                }

                _connectionProvider.RemoveConnection(clientId);
            });
        }
Beispiel #3
0
        public IMqttChannel <IPacket> Create(IMqttChannel <byte[]> binaryChannel)
        {
            IEnumerable <IFormatter> formatters    = GetFormatters();
            PacketManager            packetManager = new PacketManager(formatters);

            return(new PacketChannel(binaryChannel, packetManager, _configuration));
        }
        void ProcessChannel(IMqttChannel <byte[]> binaryChannel)
        {
            _tracer.Verbose(ServerProperties.Server_NewSocketAccepted);

            IMqttChannel <IPacket> packetChannel  = _channelFactory.Create(binaryChannel);
            ServerPacketListener   packetListener = new ServerPacketListener(packetChannel, _connectionProvider, _flowProvider, _configuration);

            packetListener.Listen();
            packetListener
            .PacketStream
            .Subscribe(_ => { }, ex =>
            {
                _tracer.Error(ex, ServerProperties.Server_PacketsObservableError);
                packetChannel.Dispose();
                packetListener.Dispose();
            }, () =>
            {
                _tracer.Warn(ServerProperties.Server_PacketsObservableCompleted);
                packetChannel.Dispose();
                packetListener.Dispose();
            }
                       );

            _channels.Add(packetChannel);
        }
Beispiel #5
0
        public async Task ExecuteAsync(string clientId, IPacket input, IMqttChannel <IPacket> channel)
        {
            if (input.Type != MqttPacketType.Unsubscribe)
            {
                return;
            }

            Unsubscribe   unsubscribe = input as Unsubscribe;
            ClientSession session     = _sessionRepository.Read(clientId);

            if (session == null)
            {
                throw new MqttException(ServerProperties.SessionRepository_ClientSessionNotFound(clientId));
            }

            foreach (string topic in unsubscribe.Topics)
            {
                ClientSubscription subscription = session.GetSubscriptions().FirstOrDefault(s => s.TopicFilter == topic);

                if (subscription != null)
                {
                    session.RemoveSubscription(subscription);
                }
            }

            _sessionRepository.Update(session);

            await channel.SendAsync(new UnsubscribeAck( unsubscribe.PacketId ));
        }
        public async Task ExecuteAsync(string clientId, IPacket input, IMqttChannel <IPacket> channel)
        {
            if (input.Type != MqttPacketType.ConnectAck)
            {
                return;
            }

            var ack = input as ConnectAck;

            if (ack.Status != MqttConnectionStatus.Accepted)
            {
                return;
            }

            var session = sessionRepository.Read(clientId);

            if (session == null)
            {
                throw new MqttException(string.Format(Properties.Resources.SessionRepository_ClientSessionNotFound, clientId));
            }

            await SendPendingMessagesAsync(session, channel)
            .ConfigureAwait(continueOnCapturedContext: false);
            await SendPendingAcknowledgementsAsync(session, channel)
            .ConfigureAwait(continueOnCapturedContext: false);
        }
Beispiel #7
0
        private static int ReadBodyLength(IMqttChannel channel, byte initialEncodedByte, byte[] singleByteBuffer, CancellationToken cancellationToken)
        {
            // Alorithm taken from https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/errata01/os/mqtt-v3.1.1-errata01-os-complete.html.
            var multiplier  = 128;
            var value       = initialEncodedByte & 127;
            int encodedByte = initialEncodedByte;

            while ((encodedByte & 128) != 0)
            {
                cancellationToken.ThrowIfCancellationRequested();

                // Here the async/await pattern is not used becuase the overhead of context switches
                // is too big for reading 1 byte in a row. We expect that the remaining data was sent
                // directly after the initial bytes. If the client disconnects just in this moment we
                // will get an exception anyway.
                encodedByte = ReadByte(channel, singleByteBuffer, cancellationToken);

                value += (byte)(encodedByte & 127) * multiplier;
                if (multiplier > 128 * 128 * 128)
                {
                    throw new MqttProtocolViolationException("Remaining length is invalid.");
                }

                multiplier *= 128;
            }

            return(value);
        }
Beispiel #8
0
        public PacketChannel(IMqttChannel <byte[]> innerChannel,
                             IPacketManager manager,
                             MqttConfiguration configuration)
        {
            _innerChannel = innerChannel;
            _manager      = manager;

            _receiver     = new ReplaySubject <IPacket>(window: TimeSpan.FromSeconds(configuration.WaitTimeoutSecs));
            _sender       = new ReplaySubject <IPacket>(window: TimeSpan.FromSeconds(configuration.WaitTimeoutSecs));
            _subscription = innerChannel
                            .ReceiverStream
                            .Subscribe(async bytes =>
            {
                try
                {
                    IPacket packet = await _manager.GetPacketAsync(bytes);

                    _receiver.OnNext(packet);
                }
                catch (MqttException ex)
                {
                    _receiver.OnError(ex);
                }
            }, onError: ex => _receiver.OnError(ex), onCompleted: () => _receiver.OnCompleted());
        }
        public async Task ExecuteAsync(string clientId, IPacket input, IMqttChannel <IPacket> channel)
        {
            if (input.Type != MqttPacketType.Disconnect)
            {
                return;
            }

            await Task.Run(() => {
                var disconnect = input as Disconnect;

                tracer.Info(Server.Properties.Resources.DisconnectFlow_Disconnecting, clientId);

                willRepository.Delete(clientId);

                var session = sessionRepository.Read(clientId);

                if (session == null)
                {
                    throw new MqttException(string.Format(Properties.Resources.SessionRepository_ClientSessionNotFound, clientId));
                }

                if (session.Clean)
                {
                    sessionRepository.Delete(session.Id);

                    tracer.Info(Server.Properties.Resources.Server_DeletedSessionOnDisconnect, clientId);
                }

                connectionProvider.RemoveConnection(clientId);
            });
        }
Beispiel #10
0
        public static async Task <MqttFixedHeader> ReadFixedHeaderAsync(IMqttChannel channel, byte[] fixedHeaderBuffer, byte[] singleByteBuffer, CancellationToken cancellationToken)
        {
            // The MQTT fixed header contains 1 byte of flags and at least 1 byte for the remaining data length.
            // So in all cases at least 2 bytes must be read for a complete MQTT packet.
            // async/await is used here because the next packet is received in a couple of minutes so the performance
            // impact is acceptable according to a useless waiting thread.
            var buffer         = fixedHeaderBuffer;
            var totalBytesRead = 0;

            while (totalBytesRead < buffer.Length)
            {
                var bytesRead = await channel.ReadAsync(buffer, totalBytesRead, buffer.Length - totalBytesRead, cancellationToken).ConfigureAwait(false);

                if (bytesRead <= 0)
                {
                    cancellationToken.ThrowIfCancellationRequested();
                    ExceptionHelper.ThrowGracefulSocketClose();
                }

                totalBytesRead += bytesRead;
            }

            var hasRemainingLength = buffer[1] != 0;

            if (!hasRemainingLength)
            {
                return(new MqttFixedHeader(buffer[0], 0));
            }

            var bodyLength = ReadBodyLength(channel, buffer[1], singleByteBuffer, cancellationToken);

            return(new MqttFixedHeader(buffer[0], bodyLength));
        }
        public IMqttChannel <IPacket> Create(IMqttChannel <byte[]> binaryChannel)
        {
            var formatters    = GetFormatters();
            var packetManager = new PacketManager(formatters);

            return(new PacketChannel(binaryChannel, packetManager, configuration));
        }
Beispiel #12
0
        public async Task SendPublishAsync(string clientId, Publish message, IMqttChannel <IPacket> channel, PendingMessageStatus status = PendingMessageStatus.PendingToSend)
        {
            if (channel == null || !channel.IsConnected)
            {
                SaveMessage(message, clientId, PendingMessageStatus.PendingToSend);
                return;
            }

            var qos = configuration.GetSupportedQos(message.QualityOfService);

            if (qos != MqttQualityOfService.AtMostOnce && status == PendingMessageStatus.PendingToSend)
            {
                SaveMessage(message, clientId, PendingMessageStatus.PendingToAcknowledge);
            }

            await channel.SendAsync(message)
            .ConfigureAwait(continueOnCapturedContext: false);

            if (qos == MqttQualityOfService.AtLeastOnce)
            {
                await MonitorAckAsync <PublishAck> (message, clientId, channel)
                .ConfigureAwait(continueOnCapturedContext: false);
            }
            else if (qos == MqttQualityOfService.ExactlyOnce)
            {
                await MonitorAckAsync <PublishReceived> (message, clientId, channel).ConfigureAwait(continueOnCapturedContext: false);

                await channel
                .ReceiverStream
                .ObserveOn(NewThreadScheduler.Default)
                .OfType <PublishComplete> ()
                .FirstOrDefaultAsync(x => x.PacketId == message.PacketId.Value);
            }
        }
        public void Setup()
        {
            var factory   = new MqttFactory();
            var tcpServer = new MqttTcpServerAdapter(new MqttNetEventLogger());

            tcpServer.ClientHandler += args =>
            {
                _serverChannel =
                    (IMqttChannel)args.GetType().GetField("_channel",
                                                          System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)
                    .GetValue(args);

                return(Task.CompletedTask);
            };

            _mqttServer = factory.CreateMqttServer(new[] { tcpServer }, new MqttNetEventLogger());

            var serverOptions = new MqttServerOptionsBuilder().Build();

            _mqttServer.StartAsync(serverOptions).GetAwaiter().GetResult();

            var clientOptions = new MqttClientOptionsBuilder()
                                .WithTcpServer("localhost").Build();

            var tcpOptions = (MqttClientTcpOptions)clientOptions.ChannelOptions;

            _clientChannel = new MqttTcpChannel(new MqttClientOptions {
                ChannelOptions = tcpOptions
            });

            _clientChannel.ConnectAsync(CancellationToken.None).GetAwaiter().GetResult();
        }
Beispiel #14
0
        async Task SendPendingMessagesAsync(ClientSession session, IMqttChannel <IPacket> channel)
        {
            foreach (var pendingMessage in session.GetPendingMessages())
            {
                var publish = new Publish(pendingMessage.Topic, pendingMessage.QualityOfService,
                                          pendingMessage.Retain, pendingMessage.Duplicated, pendingMessage.PacketId)
                {
                    Payload = pendingMessage.Payload
                };

                if (pendingMessage.Status == PendingMessageStatus.PendingToSend)
                {
                    session.RemovePendingMessage(pendingMessage);
                    sessionRepository.Update(session);

                    await senderFlow.SendPublishAsync(session.Id, publish, channel)
                    .ConfigureAwait(continueOnCapturedContext: false);
                }
                else
                {
                    await senderFlow.SendPublishAsync(session.Id, publish, channel, PendingMessageStatus.PendingToAcknowledge)
                    .ConfigureAwait(continueOnCapturedContext: false);
                }
            }
        }
Beispiel #15
0
        public async Task ExecuteAsync(string clientId, IPacket input, IMqttChannel <IPacket> channel)
        {
            if (input.Type != MqttPacketType.Unsubscribe)
            {
                return;
            }

            var unsubscribe = input as Unsubscribe;
            var session     = sessionRepository.Read(clientId);

            if (session == null)
            {
                throw new MqttException(string.Format(Properties.Resources.SessionRepository_ClientSessionNotFound, clientId));
            }

            foreach (var topic in unsubscribe.Topics)
            {
                var subscription = session.GetSubscriptions().FirstOrDefault(s => s.TopicFilter == topic);

                if (subscription != null)
                {
                    session.RemoveSubscription(subscription);
                }
            }

            sessionRepository.Update(session);

            await channel.SendAsync(new UnsubscribeAck (unsubscribe.PacketId))
            .ConfigureAwait(continueOnCapturedContext: false);
        }
        async Task HandlePublishAsync(string clientId, Publish publish, IMqttChannel <IPacket> channel)
        {
            Validate(publish, clientId);

            var qos     = configuration.GetSupportedQos(publish.QualityOfService);
            var session = sessionRepository.Read(clientId);

            if (session == null)
            {
                throw new MqttException(string.Format(Properties.Resources.SessionRepository_ClientSessionNotFound, clientId));
            }

            if (qos == MqttQualityOfService.ExactlyOnce && session.GetPendingAcknowledgements().Any(ack => ack.Type == MqttPacketType.PublishReceived && ack.PacketId == publish.PacketId.Value))
            {
                await SendQosAck(clientId, qos, publish, channel)
                .ConfigureAwait(continueOnCapturedContext: false);

                return;
            }

            await SendQosAck(clientId, qos, publish, channel)
            .ConfigureAwait(continueOnCapturedContext: false);
            await ProcessPublishAsync(publish, clientId)
            .ConfigureAwait(continueOnCapturedContext: false);
        }
Beispiel #17
0
        private static async Task <int> ReadBodyLengthAsync(IMqttChannel channel, byte initialEncodedByte, byte[] singleByteBuffer, CancellationToken cancellationToken)
        {
            var offset      = 0;
            var multiplier  = 128;
            var value       = initialEncodedByte & 127;
            int encodedByte = initialEncodedByte;

            while ((encodedByte & 128) != 0)
            {
                offset++;
                if (offset > 3)
                {
                    throw new MqttProtocolViolationException("Remaining length is invalid.");
                }

                cancellationToken.ThrowIfCancellationRequested();

                encodedByte = await ReadByteAsync(channel, singleByteBuffer, cancellationToken).ConfigureAwait(false);

                value      += (byte)(encodedByte & 127) * multiplier;
                multiplier *= 128;
            }

            return(value);
        }
Beispiel #18
0
 internal MqttConnectedClient(IMqttChannel <IPacket> packetChannel,
                              IProtocolFlowProvider flowProvider,
                              IRepositoryProvider repositoryProvider,
                              IPacketIdProvider packetIdProvider,
                              MqttConfiguration configuration)
     : base(packetChannel, flowProvider, repositoryProvider, packetIdProvider, configuration)
 {
 }
Beispiel #19
0
        public async Task ExecuteAsync(string clientId, IPacket input, IMqttChannel <IPacket> channel)
        {
            if (input.Type != MqttPacketType.Connect)
            {
                return;
            }

            var connect = input as Connect;

            if (!authenticationProvider.Authenticate(clientId, connect.UserName, connect.Password))
            {
                throw new MqttConnectionException(MqttConnectionStatus.BadUserNameOrPassword);
            }

            var session        = sessionRepository.Read(clientId);
            var sessionPresent = connect.CleanSession ? false : session != null;

            if (connect.CleanSession && session != null)
            {
                sessionRepository.Delete(session.Id);
                session = null;

                tracer.Info(Server.Properties.Resources.Server_CleanedOldSession, clientId);
            }

            var sendPendingMessages = false;

            if (session == null)
            {
                session = new ClientSession(clientId, connect.CleanSession);

                sessionRepository.Create(session);

                tracer.Info(Server.Properties.Resources.Server_CreatedSession, clientId);
            }
            else
            {
                sendPendingMessages = true;
            }

            if (connect.Will != null)
            {
                var connectionWill = new ConnectionWill(clientId, connect.Will);

                willRepository.Create(connectionWill);
            }

            await channel.SendAsync(new ConnectAck (MqttConnectionStatus.Accepted, sessionPresent))
            .ConfigureAwait(continueOnCapturedContext: false);

            if (sendPendingMessages)
            {
                await SendPendingMessagesAsync(session, channel)
                .ConfigureAwait(continueOnCapturedContext: false);
                await SendPendingAcknowledgementsAsync(session, channel)
                .ConfigureAwait(continueOnCapturedContext: false);
            }
        }
        public async Task ExecuteAsync(string clientId, IPacket input, IMqttChannel <IPacket> channel)
        {
            if (input.Type != MqttPacketType.Connect)
            {
                return;
            }

            Connect connect = input as Connect;

            if (!_authenticationProvider.Authenticate(clientId, connect.UserName, connect.Password))
            {
                throw new MqttConnectionException(MqttConnectionStatus.BadUserNameOrPassword);
            }

            ClientSession session        = _sessionRepository.Read(clientId);
            bool          sessionPresent = connect.CleanSession ? false : session != null;

            _tracer.Info($"Client connecting with protocol level {connect.ProtocolLelvel}.");

            if (connect.CleanSession && session != null)
            {
                _sessionRepository.Delete(session.Id);
                session = null;

                _tracer.Info(ServerProperties.Server_CleanedOldSession(clientId));
            }

            bool sendPendingMessages = false;

            if (session == null)
            {
                session = new ClientSession(clientId, connect.CleanSession);

                _sessionRepository.Create(session);

                _tracer.Info(ServerProperties.Server_CreatedSession(clientId));
            }
            else
            {
                sendPendingMessages = true;
            }

            if (connect.Will != null)
            {
                ConnectionWill connectionWill = new ConnectionWill(clientId, connect.Will);

                _willRepository.Create(connectionWill);
            }

            await channel.SendAsync(new ConnectAck( MqttConnectionStatus.Accepted, sessionPresent ));

            if (sendPendingMessages)
            {
                await SendPendingMessagesAsync(session, channel);
                await SendPendingAcknowledgementsAsync(session, channel);
            }
        }
        public async Task ExecuteAsync(string clientId, IPacket input, IMqttChannel <IPacket> channel)
        {
            if (input.Type != MqttPacketType.PingRequest)
            {
                return;
            }

            await channel.SendAsync(new PingResponse());
        }
Beispiel #22
0
        async Task InitializeChannelAsync()
        {
            Channel = await channelFactory
                      .CreateAsync()
                      .ConfigureAwait(continueOnCapturedContext: false);

            packetListener = new ClientPacketListener(Channel, flowProvider, configuration);
            packetListener.Listen();
            ObservePackets();
        }
Beispiel #23
0
        public async Task ExecuteAsync(string clientId, IPacket input, IMqttChannel <IPacket> channel)
        {
            if (input.Type != MqttPacketType.PingRequest)
            {
                return;
            }

            await channel.SendAsync(new PingResponse())
            .ConfigureAwait(continueOnCapturedContext: false);
        }
Beispiel #24
0
 public ClientPacketListener(IMqttChannel <IPacket> channel,
                             IProtocolFlowProvider flowProvider,
                             MqttConfiguration configuration)
 {
     this.channel       = channel;
     this.flowProvider  = flowProvider;
     this.configuration = configuration;
     packets            = new ReplaySubject <IPacket> (window: TimeSpan.FromSeconds(configuration.WaitTimeoutSecs));
     flowRunner         = TaskRunner.Get();
 }
Beispiel #25
0
        public void when_getting_connection_from_client_then_succeeds()
        {
            ConnectionProvider provider = new ConnectionProvider();
            string             clientId = Guid.NewGuid().ToString();

            provider.AddConnection(clientId, Mock.Of <IMqttChannel <IPacket> >(c => c.IsConnected == true));

            IMqttChannel <IPacket> connection = provider.GetConnection(clientId);

            Assert.NotNull(connection);
        }
Beispiel #26
0
        public void when_getting_connection_from_disconnected_client_then_no_connection_is_returned()
        {
            ConnectionProvider provider = new ConnectionProvider();
            string             clientId = Guid.NewGuid().ToString();

            provider.AddConnection(clientId, Mock.Of <IMqttChannel <IPacket> >(c => c.IsConnected == false));

            IMqttChannel <IPacket> connection = provider.GetConnection(clientId);

            Assert.Null(connection);
        }
Beispiel #27
0
        public void AddConnection(string clientId, IMqttChannel <IPacket> connection)
        {
            if (_connections.TryGetValue(clientId, out _))
            {
                _tracer.Warn(ServerProperties.ConnectionProvider_ClientIdExists(clientId));

                RemoveConnection(clientId);
            }

            _connections.TryAdd(clientId, connection);
        }
        async Task SendPendingMessagesAsync(ClientSession session, IMqttChannel <IPacket> channel)
        {
            foreach (PendingMessage pendingMessage in session.GetPendingMessages())
            {
                Publish publish = new Publish(pendingMessage.Topic, pendingMessage.QualityOfService,
                                              pendingMessage.Retain, pendingMessage.Duplicated, pendingMessage.PacketId);

                await _senderFlow
                .SendPublishAsync(session.Id, publish, channel, PendingMessageStatus.PendingToAcknowledge);
            }
        }
Beispiel #29
0
        public MqttChannelAdapter(IMqttChannel channel, IMqttPacketSerializer serializer, IMqttNetChildLogger logger)
        {
            if (logger == null)
            {
                throw new ArgumentNullException(nameof(logger));
            }

            _channel         = channel ?? throw new ArgumentNullException(nameof(channel));
            PacketSerializer = serializer ?? throw new ArgumentNullException(nameof(serializer));

            _logger = logger.CreateChildLogger(nameof(MqttChannelAdapter));
        }
        public void when_keep_alive_enabled_and_no_packet_received_then_times_out()
        {
            Mock <IConnectionProvider>        connectionProvider        = new Mock <IConnectionProvider>();
            Mock <IServerPublishReceiverFlow> serverPublishReceiverFlow = new Mock <IServerPublishReceiverFlow>();
            Mock <IProtocolFlowProvider>      flowProvider = new Mock <IProtocolFlowProvider>();

            serverPublishReceiverFlow.Setup(f => f.SendWillAsync(It.IsAny <string>()))
            .Returns(Task.FromResult(true));
            flowProvider.Setup(p => p.GetFlow <IServerPublishReceiverFlow>())
            .Returns(serverPublishReceiverFlow.Object);
            flowProvider.Setup(p => p.GetFlow(It.IsAny <MqttPacketType>()))
            .Returns(Mock.Of <IProtocolFlow>());

            MqttConfiguration configuration = new MqttConfiguration {
                WaitTimeoutSecs = 10
            };
            Subject <IPacket> receiver = new Subject <IPacket>();
            Subject <IPacket> sender   = new Subject <IPacket>();
            Mock <IMqttChannel <IPacket> > packetChannelMock = new Mock <IMqttChannel <IPacket> >();

            packetChannelMock.Setup(c => c.ReceiverStream).Returns(receiver);
            packetChannelMock.Setup(c => c.SenderStream).Returns(sender);

            IMqttChannel <IPacket> packetChannel = packetChannelMock.Object;

            ServerPacketListener listener = new ServerPacketListener(packetChannel, connectionProvider.Object, flowProvider.Object, configuration);

            listener.Listen();

            ManualResetEventSlim timeoutSignal = new ManualResetEventSlim(initialState: false);

            listener.PacketStream.Subscribe(_ => { }, ex =>
            {
                timeoutSignal.Set();
            });

            string  clientId  = Guid.NewGuid().ToString();
            ushort  keepAlive = 1;
            Connect connect   = new Connect(clientId, cleanSession: true, MqttProtocol.SupportedLevel)
            {
                KeepAlive = keepAlive
            };

            receiver.OnNext(connect);

            ConnectAck connectAck = new ConnectAck(MqttConnectionStatus.Accepted, existingSession: false);

            sender.OnNext(connectAck);

            bool timeoutOccurred = timeoutSignal.Wait(((int)((keepAlive + 1) * 1.5) * 1000));

            Assert.True(timeoutOccurred);
        }