// Copyright 2020-2026 The NATS Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Adapted from server/mqtt.go in the NATS server Go source. namespace ZB.MOM.NatsNet.Server.Mqtt; // ============================================================================ // Per-client MQTT state // ============================================================================ /// /// Per-client MQTT state attached to every connection established via the MQTT /// listener or WebSocket upgrade. /// Mirrors Go mqtt struct in server/mqtt.go. /// internal sealed class MqttHandler { private readonly Lock _mu = new(); // ------------------------------------------------------------------ // Identity // ------------------------------------------------------------------ /// MQTT client identifier presented in the CONNECT packet. public string ClientId { get; set; } = string.Empty; /// Whether this is a clean session. public bool CleanSession { get; set; } // ------------------------------------------------------------------ // Session / Will // ------------------------------------------------------------------ /// Session associated with this connection after a successful CONNECT. public MqttSession? Session { get; set; } /// /// Quick reference to the account session manager. /// Immutable after processConnect() completes. /// public MqttAccountSessionManager? AccountSessionManager { get; set; } /// Will message to publish when this connection closes unexpectedly. public MqttWill? Will { get; set; } // ------------------------------------------------------------------ // Keep-alive // ------------------------------------------------------------------ /// Keep-alive interval in seconds (0 = disabled). public ushort KeepAlive { get; set; } // ------------------------------------------------------------------ // QoS pending / packet identifiers // ------------------------------------------------------------------ /// Next packet identifier to use for QoS >0 outbound messages. public ushort NextPi { get; set; } /// /// Pending ack map: packet identifier → pending state. /// Used for tracking in-flight QoS 1/2 PUBLISH packets. /// public Dictionary Pending { get; } = new(); // ------------------------------------------------------------------ // Protocol flags // ------------------------------------------------------------------ /// /// When true, the server rejects QoS-2 PUBLISH from this client /// and terminates the connection on receipt of such a packet. /// Mirrors Go mqtt.rejectQoS2Pub. /// public bool RejectQoS2Pub { get; set; } /// /// When true, QoS-2 SUBSCRIBE requests are silently downgraded to QoS-1. /// Mirrors Go mqtt.downgradeQoS2Sub. /// public bool DowngradeQoS2Sub { get; set; } // ------------------------------------------------------------------ // Parse state (used by the read-loop MQTT byte-stream parser) // ------------------------------------------------------------------ /// Current state of the fixed-header / remaining-length state machine. public byte ParseState { get; set; } /// Control packet type byte extracted from the current fixed header. public byte PktType { get; set; } /// Remaining length of the current control packet (bytes still to read). public int RemLen { get; set; } /// Buffer accumulating the current packet's variable-header and payload. public byte[]? Buf { get; set; } /// Multiplier accumulator used during multi-byte remaining-length decoding. public int RemLenMult { get; set; } // ------------------------------------------------------------------ // Thread safety // ------------------------------------------------------------------ /// Lock protecting mutable fields on this instance. public Lock Mu => _mu; } // ============================================================================ // Server-side MQTT extension methods (stubs) // ============================================================================ /// /// Stub extension methods on for MQTT server operations. /// Mirrors the server-receiver MQTT functions in server/mqtt.go. /// All methods throw until session 22 is complete. /// internal static class MqttServerExtensions { /// /// Start listening for MQTT client connections. /// Mirrors Go (*Server).startMQTT(). /// public static void StartMqtt(this NatsServer server) => throw new NotImplementedException("TODO: session 22"); /// /// Configure MQTT authentication overrides from the MQTT options block. /// Mirrors Go (*Server).mqttConfigAuth(). /// public static void MqttConfigAuth(this NatsServer server, object mqttOpts) => throw new NotImplementedException("TODO: session 22"); /// /// Handle cleanup when an MQTT client connection closes. /// Mirrors Go (*Server).mqttHandleClosedClient(). /// public static void MqttHandleClosedClient(this NatsServer server, object client) => throw new NotImplementedException("TODO: session 22"); /// /// Propagate a change to the maximum ack-pending limit to all MQTT sessions. /// Mirrors Go (*Server).mqttUpdateMaxAckPending(). /// public static void MqttUpdateMaxAckPending(this NatsServer server, ushort maxp) => throw new NotImplementedException("TODO: session 22"); /// /// Retrieve or lazily-create the JSA for the named account. /// Mirrors Go (*Server).mqttGetJSAForAccount(). /// public static MqttJsa MqttGetJsaForAccount(this NatsServer server, string account) => throw new NotImplementedException("TODO: session 22"); /// /// Store a QoS message for an account on a (possibly new) NATS subject. /// Mirrors Go (*Server).mqttStoreQoSMsgForAccountOnNewSubject(). /// public static void MqttStoreQosMsgForAccountOnNewSubject( this NatsServer server, int hdr, byte[] msg, string account, string subject) => throw new NotImplementedException("TODO: session 22"); /// /// Get or create the for the client's account. /// Mirrors Go (*Server).getOrCreateMQTTAccountSessionManager(). /// public static MqttAccountSessionManager GetOrCreateMqttAccountSessionManager( this NatsServer server, object client) => throw new NotImplementedException("TODO: session 22"); /// /// Create a new for the given account. /// Mirrors Go (*Server).mqttCreateAccountSessionManager(). /// public static MqttAccountSessionManager MqttCreateAccountSessionManager( this NatsServer server, object account, System.Threading.CancellationToken cancel) => throw new NotImplementedException("TODO: session 22"); /// /// Determine how many JetStream replicas to use for MQTT streams. /// Mirrors Go (*Server).mqttDetermineReplicas(). /// public static int MqttDetermineReplicas(this NatsServer server) => throw new NotImplementedException("TODO: session 22"); /// /// Process an MQTT CONNECT packet after parsing. /// Mirrors Go (*Server).mqttProcessConnect(). /// public static void MqttProcessConnect( this NatsServer server, object client, MqttConnectProto cp, bool trace) => throw new NotImplementedException("TODO: session 22"); /// /// Send the Will message for a client that disconnected unexpectedly. /// Mirrors Go (*Server).mqttHandleWill(). /// public static void MqttHandleWill(this NatsServer server, object client) => throw new NotImplementedException("TODO: session 22"); /// /// Process an inbound MQTT PUBLISH packet. /// Mirrors Go (*Server).mqttProcessPub(). /// public static void MqttProcessPub( this NatsServer server, object client, MqttPublishInfo pp, bool trace) => throw new NotImplementedException("TODO: session 22"); /// /// Initiate delivery of a PUBLISH message via JetStream. /// Mirrors Go (*Server).mqttInitiateMsgDelivery(). /// public static void MqttInitiateMsgDelivery( this NatsServer server, object client, MqttPublishInfo pp) => throw new NotImplementedException("TODO: session 22"); /// /// Store a QoS-2 PUBLISH exactly once (idempotent). /// Mirrors Go (*Server).mqttStoreQoS2MsgOnce(). /// public static void MqttStoreQoS2MsgOnce( this NatsServer server, object client, MqttPublishInfo pp) => throw new NotImplementedException("TODO: session 22"); /// /// Process an inbound MQTT PUBREL packet. /// Mirrors Go (*Server).mqttProcessPubRel(). /// public static void MqttProcessPubRel( this NatsServer server, object client, ushort pi, bool trace) => throw new NotImplementedException("TODO: session 22"); /// /// Audit retained-message permissions after a configuration reload. /// Mirrors Go (*Server).mqttCheckPubRetainedPerms(). /// public static void MqttCheckPubRetainedPerms(this NatsServer server) => throw new NotImplementedException("TODO: session 22"); }