예제 #1
0
 public PublishReflex(MqttClientConfiguration mqttConfiguration, IIncomingPacketStore store, Func <IActivityMonitor, string, PipeReader, int, QualityOfService, bool, CancellationToken, ValueTask> messageHandler, OutputPump output)
 {
     _mqttConfiguration = mqttConfiguration;
     _store             = store;
     _messageHandler    = messageHandler;
     _output            = output;
 }
예제 #2
0
 protected override async ValueTask OnClosingAsync(DisconnectedReason reason)
 {
     if (reason == DisconnectedReason.UserDisconnected)
     {
         await State !.OutputPump.SendMessageAsync(null, OutgoingDisconnect.Instance);  // TODO: We need a logger here.
     }
 }
예제 #3
0
        public override async ValueTask <bool> SendPackets(IOutputLogger?m, CancellationToken cancellationToken)
        {
            if (IsPingReqTimeout)  // Because we are in a loop, this will be called immediately after a return. Keep this in mind.
            {
                await OutputPump.DisconnectAsync(DisconnectedReason.PingReqTimeout);

                return(true); // true so that the loop exit immediatly without calling the next method.
            }
            return(await base.SendPackets(m, cancellationToken));
        }
예제 #4
0
        public override async Task WaitPacketAvailableToSendAsync(IOutputLogger?m, CancellationToken cancellationToken)
        {
            if (IsPingReqTimeout)  // Because we are in a loop, this will be called immediately after a return. Keep this in mind.
            {
                await OutputPump.DisconnectAsync(DisconnectedReason.PingReqTimeout);

                return;
            }
            Task packetAvailable = base.WaitPacketAvailableToSendAsync(m, cancellationToken);
            Task keepAlive       = _config.DelayHandler.Delay(_config.KeepAliveSeconds * 1000, cancellationToken);

            _ = await Task.WhenAny(packetAvailable, keepAlive);

            if (packetAvailable.IsCompleted)
            {
                return;
            }
            using (m?.MainLoopSendingKeepAlive())
            {
                await ProcessOutgoingPacket(m, OutgoingPingReq.Instance, cancellationToken);
            }
            _stopwatch.Restart();
            WaitingPingResp = true;
        }
예제 #5
0
 public StateHolder(InputPump input, OutputPump output) => (Input, OutputPump) = (input, output);
예제 #6
0
        public static ValueTask <Task <T?> > SendPacket <T>(IActivityMonitor?m, IOutgoingPacketStore store, OutputPump output, IOutgoingPacket packet)
            where T : class
        {
            IDisposableGroup?group = m?.OpenTrace($"Sending a packet '{packet}'in QoS {packet.Qos}");

            return(packet.Qos switch
            {
                QualityOfService.AtMostOnce => PublishQoS0 <T>(m, group, output, packet),
                QualityOfService.AtLeastOnce => StoreAndSend <T>(m, group, output, store, packet, packet.Qos),
                QualityOfService.ExactlyOnce => StoreAndSend <T>(m, group, output, store, packet, packet.Qos),
                _ => throw new ArgumentException("Invalid QoS."),
            });
예제 #7
0
 public PublishLifecycleReflex(IIncomingPacketStore packetIdStore, IOutgoingPacketStore store, OutputPump output)
 => (_packetIdStore, _store, _output) = (packetIdStore, store, output);
예제 #8
0
 public OutputProcessorWithKeepAlive(MqttClientConfiguration config, OutputPump outputPump, PipeWriter pipeWriter, IOutgoingPacketStore store)
     : base(outputPump, pipeWriter, store)
 {
     _config    = config;
     _stopwatch = config.StopwatchFactory.Create();
 }
예제 #9
0
        /// <inheritdoc/>
        public async Task <ConnectResult> ConnectAsync(IActivityMonitor?m, MqttClientCredentials?credentials = null, OutgoingLastWill?lastWill = null)
        {
            if (IsConnected)
            {
                throw new InvalidOperationException("This client is already connected.");
            }
            using (m?.OpenTrace("Connecting..."))
            {
                try
                {
                    (IOutgoingPacketStore store, IIncomingPacketStore packetIdStore) = await _config.StoreFactory.CreateAsync(m, _pConfig, _config, _config.ConnectionString, credentials?.CleanSession ?? true);

                    IMqttChannel channel = await _config.ChannelFactory.CreateAsync(m, _config.ConnectionString);

                    ConnectAckReflex     connectAckReflex = new();
                    Task <ConnectResult> connectedTask    = connectAckReflex.Task;
                    var             output = new OutputPump(this, _pConfig);
                    OutputProcessor outputProcessor;
                    var             input = new InputPump(this, channel.DuplexPipe.Input, connectAckReflex.ProcessIncomingPacket);
                    OpenPumps(m, new ClientState(input, output, channel, packetIdStore, store));
                    ReflexMiddlewareBuilder builder = new ReflexMiddlewareBuilder()
                                                      .UseMiddleware(new PublishReflex(_config, packetIdStore, OnMessage, output))
                                                      .UseMiddleware(new PublishLifecycleReflex(packetIdStore, store, output))
                                                      .UseMiddleware(new SubackReflex(store))
                                                      .UseMiddleware(new UnsubackReflex(store));
                    if (_config.KeepAliveSeconds == 0)
                    {
                        outputProcessor = new OutputProcessor(output, channel.DuplexPipe.Output, store);
                    }
                    else
                    {
                        OutputProcessorWithKeepAlive withKeepAlive = new(_config, output, channel.DuplexPipe.Output, store);;
                        outputProcessor = withKeepAlive;
                        _ = builder.UseMiddleware(withKeepAlive);
                    }
                    output.StartPumping(outputProcessor);
                    connectAckReflex.Reflex = builder.Build(InvalidPacket);
                    OutgoingConnect             outgoingConnect    = new(_pConfig, _config, credentials, lastWill);
                    CancellationTokenSource     cts                = new(_config.WaitTimeoutMilliseconds);
                    IOutgoingPacket.WriteResult writeConnectResult = await outgoingConnect.WriteAsync(_pConfig.ProtocolLevel, channel.DuplexPipe.Output, cts.Token);

                    if (writeConnectResult != IOutgoingPacket.WriteResult.Written)
                    {
                        _ = await CloseAsync(DisconnectedReason.None);

                        return(new ConnectResult(ConnectError.Timeout));
                    }
                    Task timeout = _config.DelayHandler.Delay(_config.WaitTimeoutMilliseconds, CloseToken);
                    _ = await Task.WhenAny(connectedTask, timeout);

                    // This following code wouldn't be better with a sort of ... switch/pattern matching ?
                    if (connectedTask.Exception is not null)
                    {
                        m?.Fatal(connectedTask.Exception);
                        _ = await CloseAsync(DisconnectedReason.None);

                        return(new ConnectResult(ConnectError.InternalException));
                    }
                    if (CloseToken.IsCancellationRequested)
                    {
                        _ = await CloseAsync(DisconnectedReason.None);

                        return(new ConnectResult(ConnectError.RemoteDisconnected));
                    }
                    if (!connectedTask.IsCompleted)
                    {
                        _ = await CloseAsync(DisconnectedReason.None);

                        return(new ConnectResult(ConnectError.Timeout));
                    }
                    ConnectResult res = await connectedTask;
                    if (res.ConnectError != ConnectError.Ok)
                    {
                        _ = await CloseAsync(DisconnectedReason.None);

                        return(new ConnectResult(res.ConnectError));
                    }
                    bool askedCleanSession = credentials?.CleanSession ?? true;
                    if (askedCleanSession && res.SessionState != SessionState.CleanSession)
                    {
                        _ = await CloseAsync(DisconnectedReason.None);

                        return(new ConnectResult(ConnectError.ProtocolError_SessionNotFlushed));
                    }
                    if (res.SessionState == SessionState.CleanSession)
                    {
                        ValueTask task = packetIdStore.ResetAsync();
                        await store.ResetAsync();

                        await task;
                    }
                    else
                    {
                        throw new NotImplementedException();
                    }
                    return(res);
                }
                catch (Exception e)
                {
                    m?.Error("Error while connecting, closing client.", e);
                    _ = await CloseAsync(DisconnectedReason.None);

                    return(new ConnectResult(ConnectError.InternalException));
                }
            }
        }
예제 #10
0
 public ClientState(InputPump input, OutputPump output, IMqttChannel channel, IIncomingPacketStore packetIdStore, IOutgoingPacketStore store) : base(input, output)
 {
     Channel       = channel;
     PacketIdStore = packetIdStore;
     Store         = store;
 }