get.pme.sh/pnats@v0.0.0-20240304004023-26bb5a137ed0/server/README-MQTT.md (about)

     1  **MQTT Implementation Overview**
     2  
     3  Revision 1.1
     4  
     5  Authors: Ivan Kozlovic, Lev Brouk
     6  
     7  NATS Server currently supports most of MQTT 3.1.1. This document describes how
     8  it is implementated.
     9  
    10  It is strongly recommended to review the [MQTT v3.1.1
    11  specifications](https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html)
    12  and get a detailed understanding before proceeding with this document.
    13  
    14  # Contents
    15  
    16  1. [Concepts](#1-concepts)
    17     - [Server, client](#server-client)
    18     - [Connection, client ID, session](#connection-client-id-session)
    19     - [Packets, messages, and subscriptions](#packets-messages-and-subscriptions)
    20     - [Quality of Service (QoS), publish identifier (PI)](#quality-of-service-qos-publish-identifier-pi)
    21     - [Retained message](#retained-message)
    22     - [Will message](#will-message)
    23  2. [Use of JetStream](#2-use-of-jetstream)
    24     - [JetStream API](#jetstream-api)
    25     - [Streams](#streams)
    26     - [Consumers and Internal NATS Subscriptions](#consumers-and-internal-nats-subscriptions)
    27  3. [Lifecycles](#3-lifecycles)
    28     - [Connection, Session](#connection-session)
    29     - [Subscription](#subscription)
    30     - [Message](#message)
    31     - [Retained messages](#retained-messages)
    32  4. [Implementation Notes](#4-implementation-notes)
    33     - [Hooking into NATS I/O](#hooking-into-nats-io)
    34     - [Session Management](#session-management)
    35     - [Processing QoS acks: PUBACK, PUBREC, PUBCOMP](#processing-qos-acks-puback-pubrec-pubcomp)
    36     - [Subject Wildcards](#subject-wildcards)
    37  5. [Known issues](#5-known-issues)
    38  
    39  # 1. Concepts
    40  
    41  ## Server, client
    42  
    43  In the MQTT specification there are concepts of **Client** and **Server**, used
    44  somewhat interchangeably with those of **Sender** and **Receiver**. A **Server**
    45  acts as a **Receiver** when it gets `PUBLISH` messages from a **Sender**
    46  **Client**, and acts as a **Sender** when it delivers them to subscribed
    47  **Clients**.
    48  
    49  In the NATS server implementation there are also concepts (types) `server` and
    50  `client`. `client` is an internal representation of a (connected) client and
    51  runs its own read and write loops. Both of these have an `mqtt` field that if
    52  set makes them behave as MQTT-compliant.
    53  
    54  The code and comments may sometimes be confusing as they refer to `server` and
    55  `client` sometimes ambiguously between MQTT and NATS.
    56  
    57  ## Connection, client ID, session
    58  
    59  When an MQTT client connects to a server, it must send a `CONNECT` packet to
    60  create an **MQTT Connection**. The packet must include a **Client Identifier**.
    61  The server will then create or load a previously saved **Session** for the (hash
    62  of) the client ID.
    63  
    64  ## Packets, messages, and subscriptions
    65  
    66  The low level unit of transmission in MQTT is a **Packet**. Examples of packets
    67  are: `CONNECT`, `SUBSCRIBE`, `SUBACK`, `PUBLISH`, `PUBCOMP`, etc.
    68  
    69  An **MQTT Message** starts with a `PUBLISH` packet that a client sends to the
    70  server. It is then matched against the current **MQTT Subscriptions** and is
    71  delivered to them as appropriate. During the message delivery the server acts as
    72  an MQTT client, and the receiver acts as an MQTT server.
    73  
    74  Internally we use **NATS Messages** and **NATS Subscriptions** to facilitate
    75  message delivery. This may be somewhat confusing as the code refers to `msg` and
    76  `sub`. What may be even more confusing is that some MQTT packets (specifically,
    77  `PUBREL`) are represented as NATS messages, and that the original MQTT packet
    78  "metadata" may be encoded as NATS message headers.
    79  
    80  ## Quality of Service (QoS), publish identifier (PI)
    81  
    82  MQTT specifies 3 levels of quality of service (**QoS**):
    83  
    84  - `0` for at most once. A single delivery attempt.
    85  - `1` for at least once. Will try to redeliver until acknowledged by the
    86    receiver.
    87  - `2` for exactly once. See the [SPEC REF] for the acknowledgement flow.
    88  
    89  QoS 1 and 2 messages need to be identified with publish identifiers (**PI**s). A
    90  PI is a 16-bit integer that must uniquely identify a message for the duration of
    91  the required exchange of acknowledgment packets.
    92  
    93  Note that the QoS applies separately to the transmission of a message from a
    94  sender client to the server, and from the server to the receiving client. There
    95  is no protocol-level acknowledgements between the receiver and the original
    96  sender. The sender passes the ownership of messages to the server, and the
    97  server then delivers them at maximum possible QoS to the receivers
    98  (subscribers). The PIs for in-flight outgoing messages are issued and stored per
    99  session.
   100  
   101  ## Retained message
   102  
   103  A **Retained Message** is not part of any MQTT session and is not removed when the
   104  session that produced it goes away. Instead, the server needs to persist a
   105  _single_ retained message per topic. When a subscription is started, the server
   106  needs to send the “matching” retained messages, that is, messages that would
   107  have been delivered to the new subscription should that subscription had been
   108  running prior to the publication of this message.
   109  
   110  Retained messages are removed when the server receives a retained message with
   111  an empty body. Still, this retained message that serves as a “delete” of a
   112  retained message will be processed as a normal published message.
   113  
   114  Retained messages can have QoS.
   115  
   116  ## Will message
   117  
   118  The `CONNECT` packet can contain information about a **Will Message** that needs to
   119  be sent to any client subscribing on the Will topic/subject in the event that
   120  the client is disconnected implicitly, that is, not as a result as the client
   121  sending the `DISCONNECT` packet.
   122  
   123  Will messages can have the retain flag and QoS.
   124  
   125  # 2. Use of JetStream
   126  
   127  The MQTT implementation relies heavily on JetStream. We use it to:
   128  
   129  - Persist (and restore) the [Session](#connection-client-id-session) state.
   130  - Store and retrieve [Retained messages](#retained-message).
   131  - Persist incoming [QoS 1 and
   132    2](#quality-of-service-qos-publish-identifier-pi) messages, and
   133    re-deliver if needed.
   134  - Store and de-duplicate incoming [QoS
   135    2](#quality-of-service-qos-publish-identifier-pi) messages.
   136  - Persist and re-deliver outgoing [QoS
   137    2](#quality-of-service-qos-publish-identifier-pi) `PUBREL` packets.
   138  
   139  Here is the overview of how we set up and use JetStream **streams**,
   140  **consumers**, and **internal NATS subscriptions**.
   141  
   142  ## JetStream API
   143  
   144  All interactions with JetStream are performed via `mqttJSA` that sends NATS
   145  requests to JetStream. Most are processed synchronously and await a response,
   146  some (e.g. `jsa.sendAck()`) are sent asynchronously. JetStream API is usually
   147  referred to as `jsa` in the code. No special locking is required to use `jsa`,
   148  however the asynchronous use of JetStream may create race conditions with
   149  delivery callbacks.
   150  
   151  ## Streams
   152  
   153  We create the following streams unless they already exist. Failing to ensure the
   154  streams would prevent the client from connecting.
   155  
   156  Each stream is created with a replica value that is determined by the size of
   157  the cluster but limited to 3. It can also be overwritten by the stream_replicas
   158  option in the MQTT configuration block.
   159  
   160  The streams are created the first time an Account Session Manager is initialized
   161  and are used by all sessions in it. Note that to avoid race conditions, some
   162  subscriptions are created first. The streams are never deleted. See
   163  `mqttCreateAccountSessionManager()` for details.
   164  
   165  1. `$MQTT_sess` stores persisted **Session** records. It filters on
   166     `"$MQTT.sess.>` subject and has a “limits” policy with `MaxMsgsPer` setting
   167     of 1.
   168  2. `$MQTT_msgs` is used for **QoS 1 and 2 message delivery**.
   169     It filters on `$MQTT.msgs.>` subject and has an “interest” policy.
   170  3. `$MQTT_rmsgs` stores **Retained Messages**. They are all
   171     stored (and filtered) on a single subject `$MQTT.rmsg`. This stream has a
   172     limits policy.
   173  4. `$MQTT_qos2in` stores and deduplicates **Incoming QoS 2 Messages**. It
   174     filters on `$MQTT.qos2.in.>` and has a "limits" policy with `MaxMsgsPer` of
   175     1.
   176  5. `$MQTT_out` stores **Outgoing QoS 2** `PUBREL` packets. It filters on
   177     `$MQTT.out.>` and has a "interest" retention policy.
   178  
   179  ## Consumers and Internal NATS Subscriptions
   180  
   181  ### Account Scope
   182  
   183  - A durable consumer for [Retained Messages](#retained-message) -
   184    `$MQTT_rmsgs_<server name hash>`
   185  - A subscription to handle all [jsa](#jetstream-api) replies for the account.
   186  - A subscription to replies to "session persist" requests, so that we can detect
   187    the use of a session with the same client ID anywhere in the cluster.
   188  - 2 subscriptions to support [retained messages](#retained-message):
   189    `$MQTT.sub.<nuid>` for the messages themselves, and one to receive replies to
   190    "delete retained message" JS API (on the JS reply subject var).
   191  
   192  ### Session Scope
   193  
   194  When a new QoS 2 MQTT subscription is detected in a session, we ensure that
   195  there is a durable consumer for [QoS
   196  2](#quality-of-service-qos-publish-identifier-pi) `PUBREL`s out for delivery -
   197  `$MQTT_PUBREL_<session id hash>`
   198  
   199  ### Subscription Scope
   200  
   201  For all MQTT subscriptions, regardless of their QoS, we create internal NATS subscriptions to
   202  
   203  - `subject` (directly encoded from `topic`). This subscription is used to
   204    deliver QoS 0 messages, and messages originating from NATS.
   205  - if needed, `subject fwc` complements `subject` for topics like `topic.#` to
   206    include `topic` itself, see [top-level wildcards](#subject-wildcards)
   207  
   208  For QoS 1 or 2 MQTT subscriptions we ensure:
   209  
   210  - A durable consumer for messages out for delivery - `<session ID hash>_<nuid>`
   211  - An internal subscription to `$MQTT.sub.<nuid>` to deliver the messages to the
   212    receiving client.
   213  
   214  ### (Old) Notes
   215  
   216  As indicated before, for a QoS1 or QoS2 subscription, the server will create a
   217  JetStream consumer with the appropriate subject filter. If the subscription
   218  already existed, then only the NATS subscription is created for the JetStream
   219  consumer’s delivery subject.
   220  
   221  Note that JS consumers can be created with an “Replicas” override, which from
   222  recent discussion is problematic with “Interest” policy streams, which
   223  “$MQTT_msgs” is.
   224  
   225  We do handle situations where a subscription on the same subject filter is sent
   226  with a different QoS as per MQTT specifications. If the existing was on QoS 1 or
   227  2, and the “new” is for QoS 0, then we delete the existing JS consumer.
   228  
   229  Subscriptions that are QoS 0 have a NATS subscription with the callback function
   230  being `mqttDeliverMsgCbQos0()`; while QoS 1 and 2 have a NATS subscription with
   231  callback `mqttDeliverMsgCbQos12()`. Both those functions have comments that
   232  describe the reason for their existence and what they are doing. For instance
   233  the `mqttDeliverMsgCbQos0()` callback will reject any producing client that is
   234  of type JETSTREAM, so that it handles only non JetStream (QoS 1 and 2) messages.
   235  
   236  Both these functions end-up calling mqttDeliver() which will first enqueue the
   237  possible retained messages buffer before delivering any new message. The message
   238  itself being delivered is serialized in MQTT format and enqueued to the client’s
   239  outbound buffer and call to addToPCD is made so that it is flushed out of the
   240  readloop.
   241  
   242  # 3. Lifecycles
   243  
   244  ## Connection, Session
   245  
   246  An MQTT connection is created when a listening MQTT server receives a `CONNECT`
   247  packet. See `mqttProcessConnect()`. A connection is associated with a session.
   248  Steps:
   249  
   250  1. Ensure that we have an `AccountSessionManager` so we can have an
   251     `mqttSession`. Lazily initialize JetStream streams, and internal consumers
   252     and subscriptions. See `getOrCreateMQTTAccountSessionManager()`.
   253  2. Find and disconnect any previous session/client for the same ID. See
   254     `mqttProcessConnect()`.
   255  3. Ensure we have an `mqttSession` - create a new or load a previously persisted
   256     one. If the clean flag is set in `CONNECT`, clean the session. see
   257     `mqttSession.clear()`
   258  4. Initialize session's subscriptions, if any.
   259  5. Always send back a `CONNACK` packet. If there were errors in previous steps,
   260     include the error.
   261  
   262  An MQTT connection can be closed for a number of reasons, including receiving a
   263  `DISCONNECT` from the client, explicit internal errors processing MQTT packets,
   264  or the server receiving another `CONNECT` packet with the same client ID. See
   265  `mqttHandleClosedClient()` and `mqttHandleWill()`. Steps:
   266  
   267  1. Send out the Will Message if applicable (if not caused by a `DISCONNECT` packet)
   268  2. Delete the the JetStream consumers for to QoS 1 and 2 packet delivery through
   269     JS API calls (if "clean" session flag is set)
   270  3. Delete the session record from the “$MQTT_sess” stream, based on recorded
   271     stream sequence. (if "clean" session flag is set)
   272  4. Close the client connection.
   273  
   274  On an explicit disconnect, that is, the client sends the DISCONNECT packet, the
   275  server will NOT send the Will, as per specifications.
   276  
   277  For sessions that had the “clean” flag, the JS consumers corresponding to QoS 1
   278  subscriptions are deleted through JS API calls, the session record is then
   279  deleted (based on recorded stream sequence) from the “$MQTT_sess” stream.
   280  
   281  Finally, the client connection is closed
   282  
   283  Sessions are persisted on disconnect, and on subscriptions changes.
   284  
   285  ## Subscription
   286  
   287  Receiving an MQTT `SUBSCRIBE` packet creates new subscriptions, or updates
   288  existing subscriptions in a session. Each `SUBSCRIBE` packet may contain several
   289  specific subscriptions (`topic` + QoS in each). We always respond with a
   290  `SUBACK`, which may indicate which subscriptions errored out.
   291  
   292  For each subscription in the packet, we:
   293  
   294  1. Ignore it if `topic` starts with `$MQTT.sub.`.
   295  2. Set up QoS 0 message delivery - an internal NATS subscription on `topic`.
   296  3. Replay any retained messages for `topic`, once as QoS 0.
   297  4. If we already have a subscription on `topic`, update its QoS
   298  5. If this is a QoS 2 subscription in the session, ensure we have the [PUBREL
   299     consumer](#session-scope) for the session.
   300  6. If this is a QoS 1 or 2 subscription, ensure we have the [Message
   301     consumer](#subscription-scope) for this subscription (or delete one if it
   302     exists and this is now a QoS 0 sub).
   303  7. Add an extra subscription for the [top-level wildcard](#subject-wildcards) case.
   304  8. Update the session, persist it if changed.
   305  
   306  When a session is restored (no clean flag), we go through the same steps to
   307  re-subscribe to its stored subscription, except step #8 which would have been
   308  redundant.
   309  
   310  When we get an `UNSUBSCRIBE` packet, it can contain multiple subscriptions to
   311  unsubscribe. The parsing will generate a slice of mqttFilter objects that
   312  contain the “filter” (the topic with possibly wildcard of the subscription) and
   313  the QoS value. The server goes through the list and deletes the JS consumer (if
   314  QoS 1 or 2) and unsubscribes the NATS subscription for the delivery subject (if
   315  it was a QoS 1 or 2) or on the actual topic/subject. In case of the “#”
   316  wildcard, the server will handle the “level up” subscriptions that NATS had to
   317  create.
   318  
   319  Again, we update the session and persist it as needed in the `$MQTT_sess`
   320  stream.
   321  
   322  ## Message
   323  
   324  1. Detect an incoming PUBLISH packet, parse and check the message QoS. Fill out
   325     the session's `mqttPublish` struct that contains information about the
   326     published message. (see `mqttParse()`, `mqttParsePub()`)
   327  2. Process the message according to its QoS (see `mqttProcessPub()`)
   328  
   329     - QoS 0:
   330       - Initiate message delivery
   331     - QoS 1:
   332       - Initiate message delivery
   333       - Send back a `PUBACK`
   334     - QoS 2:
   335       - Store the message in `$MQTT_qos2in` stream, using a PI-specific subject.
   336         Since `MaxMsgsPer` is set to 1, we will ignore duplicates on the PI.
   337       - Send back a `PUBREC`
   338       - "Wait" for a `PUBREL`, then initiate message delivery
   339       - Remove the previously stored QoS2 message
   340       - Send back a `PUBCOMP`
   341  
   342  3. Initiate message delivery (see `mqttInitiateMsgDelivery()`)
   343  
   344     - Convert the MQTT `topic` into a NATS `subject` using
   345       `mqttTopicToNATSPubSubject()` function. If there is a known subject
   346       mapping, then we select the new subject using `selectMappedSubject()`
   347       function and then convert back this subject into an MQTT topic using
   348       `natsSubjectToMQTTTopic()` function.
   349     - Re-serialize the `PUBLISH` packet received as a NATS message. Use NATS
   350       headers for the metadata, and the deliverable MQTT `PUBLISH` packet as the
   351       contents.
   352     - Publish the messages as `subject` (and `subject fwc` if applicable, see
   353       [subject wildcards](#subject-wildcards)). Use the "standard" NATS
   354       `c.processInboundClientMsg()` to do that. `processInboundClientMsg()` will
   355       distribute the message to any NATS subscriptions (including routes,
   356       gateways, leafnodes) and the relevant MQTT subscriptions.
   357     - Check for retained messages, process as needed. See
   358       `c.processInboundClientMsg()` calling `c.mqttHandlePubRetain()` For MQTT
   359       clients.
   360     - If the message QoS is 1 or 2, store it in `$MQTT_msgs` stream as
   361       `$MQTT.msgs.<subject>` for "at least once" delivery with retries.
   362  
   363  4. Let NATS and JetStream deliver to the internal subscriptions, and to the
   364     receiving clients. See `mqttDeliverMsgCb...()`
   365  
   366     - The NATS message posted to `subject` (and `subject fwc`) will be delivered
   367       to each relevant internal subscription by calling `mqttDeliverMsgCbQoS0()`.
   368       The function has access to both the publishing and the receiving clients.
   369  
   370       - Ignore all irrelevant invocations. Specifically, do nothing if the
   371         message needs to be delivered with a higher QoS - that will be handled by
   372         the other, `...QoS12` callback. Note that if the original message was
   373         publuished with a QoS 1 or 2, but the subscription has its maximum QoS
   374         set to 0, the message will be delivered by this callback.
   375       - Ignore "reserved" subscriptions, as per MQTT spec.
   376       - Decode delivery `topic` from the NATS `subject`.
   377       - Write (enqueue) outgoing `PUBLISH` packet.
   378       - **DONE for QoS 0**
   379  
   380     - The NATS message posted to JetStream as `$MQTT.msgs.subject` will be
   381       consumed by subscription-specific consumers. Note that MQTT subscriptions
   382       with max QoS 0 do not have JetStream consumers. They are handled by the
   383       QoS0 callback.
   384  
   385       The consumers will deliver it to the `$MQTT.sub.<nuid>`
   386       subject for their respective NATS subscriptions by calling
   387       `mqttDeliverMsgCbQoS12()`. This callback too has access to both the
   388       publishing and the receiving clients.
   389  
   390       - Ignore "reserved" subscriptions, as per MQTT spec.
   391       - See if this is a re-delivery from JetStream by checking `sess.cpending`
   392         for the JS reply subject. If so, use the existing PI and treat this as a
   393         duplicate redelivery.
   394       - Otherwise, assign the message a new PI (see `trackPublish()` and
   395         `bumpPI()`) and store it in `sess.cpending` and `sess.pendingPublish`,
   396         along with the JS reply subject that can be used to remove this pending
   397         message from the consumer once it's delivered to the receipient.
   398       - Decode delivery `topic` from the NATS `subject`.
   399       - Write (enqueue) outgoing `PUBLISH` packet.
   400  
   401  5. QoS 1: "Wait" for a `PUBACK`. See `mqttProcessPubAck()`.
   402  
   403     - When received, remove the PI from the tracking maps, send an ACK to
   404       consumer to remove the message.
   405     - **DONE for QoS 1**
   406  
   407  6. QoS 2: "Wait" for a `PUBREC`. When received, we need to do all the same
   408     things as in the QoS 1 `PUBACK` case, but we need to send out a `PUBREL`, and
   409     continue using the same PI until the delivery flow is complete and we get
   410     back a `PUBCOMP`. For that, we add the PI to `sess.pendingPubRel`, and to
   411     `sess.cpending` with the PubRel consumer durable name.
   412  
   413     We also compose and store a headers-only NATS message signifying a `PUBREL`
   414     out for delivery, and store it in the `$MQTT_qos2out` stream, as
   415     `$MQTT.qos2.out.<session-id>`.
   416  
   417  7. QoS 2: Deliver `PUBREL`. The PubRel session-specific consumer will publish to
   418     internal subscription on `$MQTT.qos2.delivery`, calling
   419     `mqttDeliverPubRelCb()`. We store the ACK reply subject in `cpending` to
   420     remove the JS message on `PUBCOMP`, compose and send out a `PUBREL` packet.
   421  
   422  8. QoS 2: "Wait" for a `PUBCOMP`. See `mqttProcessPubComp()`.
   423     - When received, remove the PI from the tracking maps, send an ACK to
   424       consumer to remove the `PUBREL` message.
   425     - **DONE for QoS 2**
   426  
   427  ## Retained messages
   428  
   429  When we process an inbound `PUBLISH` and submit it to
   430  `processInboundClientMsg()` function, for MQTT clients it will invoke
   431  `mqttHandlePubRetain()` which checks if the published message is “retained” or
   432  not.
   433  
   434  If it is, then we construct a record representing the retained message and store
   435  it in the `$MQTT_rmsg` stream, under the single `$MQTT.rmsg` subject. The stored
   436  record (in JSON) contains information about the subject, topic, MQTT flags, user
   437  that produced this message and the message content itself. It is stored and the
   438  stream sequence is remembered in the memory structure that contains retained
   439  messages.
   440  
   441  Note that when creating an account session manager, the retained messages stream
   442  is read from scratch to load all the messages through the use of a JS consumer.
   443  The associated subscription will process the recovered retained messages or any
   444  new that comes from the network.
   445  
   446  A retained message is added to a map and a subscription is created and inserted
   447  into a sublist that will be used to perform a ReverseMatch() when a subscription
   448  is started and we want to find all retained messages that the subscription would
   449  have received if it had been running prior to the message being published.
   450  
   451  If a retained message on topic “foo” already exists, then the server has to
   452  delete the old message at the stream sequence we saved when storing it.
   453  
   454  This could have been done with having retained messages stored under
   455  `$MQTT.rmsg.<subject>` as opposed to all under a single subject, and make use of
   456  the `MaxMsgsPer` field set to 1. The `MaxMsgsPer` option was introduced well into
   457  the availability of MQTT and changes to the sessions was made in [PR
   458  #2501](https://github.com/nats-io/nats-server/pull/2501), with a conversion of
   459  existing streams such as `$MQTT*sess*<sess ID>` into a single stream with unique
   460  subjects, but the changes were not made to the retained messages stream.
   461  
   462  There are also subscriptions for the handling of retained messages which are
   463  messages that are asked by the publisher to be retained by the MQTT server to be
   464  delivered to matching subscriptions when they start. There is a single message
   465  per topic. Retained messages are deleted when the user sends a retained message
   466  (there is a flag in the PUBLISH protocol) on a given topic with an empty body.
   467  The difficulty with retained messages is to handle them in a cluster since all
   468  servers need to be aware of their presence so that they can deliver them to
   469  subscriptions that those servers may become the leader for.
   470  
   471  - `$MQTT_rmsgs` which has a “limits” policy and holds retained messages, all
   472    under `$MQTT.rmsg` single subject. Not sure why I did not use MaxMsgsPer for
   473    this stream and not filter `$MQTT.rmsg.>`.
   474  
   475  The first step when processing a new subscription is to gather the retained
   476  messages that would be a match for this subscription. To do so, the server will
   477  serialize into a buffer all messages for the account session manager’s sublist’s
   478  ReverseMatch result. We use the returned subscriptions’ subject to find from a
   479  map appropriate retained message (see `serializeRetainedMsgsForSub()` for
   480  details).
   481  
   482  # 4. Implementation Notes
   483  
   484  ## Hooking into NATS I/O
   485  
   486  ### Starting the accept loop
   487  
   488  The MQTT accept loop is started when the server detects that an MQTT port has
   489  been defined in the configuration file. It works similarly to all other accept
   490  loops. Note that for MQTT over websocket, the websocket port has to be defined
   491  and MQTT clients will connect to that port instead of the MQTT port and need to
   492  provide `/mqtt` as part of the URL to redirect the creation of the client to an
   493  MQTT client (with websocket support) instead of a regular NATS with websocket.
   494  See the branching done in `startWebsocketServer()`. See `startMQTT()`.
   495  
   496  ### Starting the read/write loops
   497  
   498  When a TCP connection is accepted, the internal go routine will invoke
   499  `createMQTTClient()`. This function will set a `c.mqtt` object that will make it
   500  become an MQTT client (through the `isMqtt()` helper function). The `readLoop()`
   501  and `writeLoop()` are started similarly to other clients. However, the read loop
   502  will branch out to `mqttParse()` instead when detecting that this is an MQTT
   503  client.
   504  
   505  ## Session Management
   506  
   507  ### Account Session Manager
   508  
   509  `mqttAccountSessionManager` is an object that holds the state of all sessions in
   510  an account. It also manages the lifecycle of JetStream streams and internal
   511  subscriptions for processing JS API replies, session updates, etc. See
   512  `mqttCreateAccountSessionManager()`. It is lazily initialized upon the first
   513  MQTT `CONNECT` packet received. Account session manager is referred to as `asm`
   514  in the code.
   515  
   516  Note that creating the account session manager (and attempting to create the
   517  streams) is done only once per account on a given server, since once created the
   518  account session manager for a given account would be found in the sessions map
   519  of the mqttSessionManager object.
   520  
   521  ### Find and disconnect previous session/client
   522  
   523  Once all that is done, we now go to the creation of the session object itself.
   524  For that, we first need to make sure that it does not already exist, meaning
   525  that it is registered on the server - or anywhere in the cluster. Note that MQTT
   526  dictates that if a session with the same ID connects, the OLD session needs to
   527  be closed, not the new one being created. NATS Server complies with this
   528  requirement.
   529  
   530  Once a session is detected to already exists, the old one (as described above)
   531  is closed and the new one accepted, however, the session ID is maintained in a
   532  flappers map so that we detect situations where sessions with the same ID are
   533  started multiple times causing the previous one to be closed. When that
   534  detection occurs, the newly created session is put in “jail” for a second to
   535  avoid a very rapid succession of connect/disconnect. This has already been seen
   536  by users since there was some issue there where we would schedule the connection
   537  closed instead of waiting in place which was causing a panic.
   538  
   539  We also protect from multiple clients on a given server trying to connect with
   540  the same ID at the “same time” while the processing of a CONNECT of a session is
   541  not yet finished. This is done with the use of a sessLocked map, keyed by the
   542  session ID.
   543  
   544  ### Create or restore the session
   545  
   546  If everything is good up to that point, the server will either create or restore
   547  a session from the stream. This is done in the `createOrRestoreSession()`
   548  function. The client/session ID is hashed and added to the session’s stream
   549  subject along with the JS domain to prevent clients connecting from different
   550  domains to “pollute” the session stream of a given domain.
   551  
   552  Since each session constitutes a subject and the stream has a maximum of 1
   553  message per subject, we attempt to load the last message on the formed subject.
   554  If we don’t find it, then the session object is created “empty”, while if we
   555  find a record, we create the session object based on the record persisted on the
   556  stream.
   557  
   558  If the session was restored from the JS stream, we keep track of the stream
   559  sequence where the record was located. When we save the session (even if it
   560  already exists) we will use this sequence number to set the
   561  `JSExpectedLastSubjSeq` header so that we handle possibly different servers in a
   562  (super)cluster to detect the race of clients trying to use the same session ID,
   563  since only one of the write should succeed. On success, the session’s new
   564  sequence is remembered by the server that did the write.
   565  
   566  When created or restored, the CONNACK can now be sent back to the client, and if
   567  there were any recovered subscriptions, they are now processed.
   568  
   569  ## Processing QoS acks: PUBACK, PUBREC, PUBCOMP
   570  
   571  When the server delivers a message with QoS 1 or 2 (also a `PUBREL` for QoS 2) to a subscribed client, the client will send back an acknowledgement. See `mqttProcessPubAck()`, `mqttProcessPubRec()`, and `mqttProcessPubComp()`
   572  
   573  While the specific logic for each packet differs, these handlers all update the
   574  session's PI mappings (`cpending`, `pendingPublish`, `pendingPubRel`), and if
   575  needed send an ACK to JetStream to remove the message from its consumer and stop
   576  the re-delivery attempts.
   577  
   578  ## Subject Wildcards
   579  
   580  Note that MQTT subscriptions have wildcards too, the `“+”` wildcard is equivalent
   581  to NATS’s `“*”` wildcard, however, MQTT’s wildcard `“#”` is similar to `“>”`, except
   582  that it also includes the level above. That is, a subscription on `“foo/#”` would
   583  receive messages on `“foo/bar/baz”`, but also on `“foo”`.
   584  
   585  So, for MQTT subscriptions enging with a `'#'` we are forced to create 2
   586  internal NATS subscriptions, one on `“foo”` and one on `“foo.>”`.
   587  
   588  # 5. Known issues
   589  - "active" redelivery for QoS from JetStream (compliant, just a note)
   590  - JetStream QoS redelivery happens out of (original) order
   591  - finish delivery of in-flight messages after UNSUB
   592  - finish delivery of in-flight messages after a reconnect
   593  - consider replacing `$MQTT_msgs` with `$MQTT_out`.
   594  - consider using unique `$MQTT.rmsg.>` and `MaxMsgsPer` for retained messages.
   595  - add a cli command to list/clean old sessions