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; }
protected override async ValueTask OnClosingAsync(DisconnectedReason reason) { if (reason == DisconnectedReason.UserDisconnected) { await State !.OutputPump.SendMessageAsync(null, OutgoingDisconnect.Instance); // TODO: We need a logger here. } }
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)); }
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; }
public StateHolder(InputPump input, OutputPump output) => (Input, OutputPump) = (input, output);
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."), });
public PublishLifecycleReflex(IIncomingPacketStore packetIdStore, IOutgoingPacketStore store, OutputPump output) => (_packetIdStore, _store, _output) = (packetIdStore, store, output);
public OutputProcessorWithKeepAlive(MqttClientConfiguration config, OutputPump outputPump, PipeWriter pipeWriter, IOutgoingPacketStore store) : base(outputPump, pipeWriter, store) { _config = config; _stopwatch = config.StopwatchFactory.Create(); }
/// <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)); } } }
public ClientState(InputPump input, OutputPump output, IMqttChannel channel, IIncomingPacketStore packetIdStore, IOutgoingPacketStore store) : base(input, output) { Channel = channel; PacketIdStore = packetIdStore; Store = store; }