github.com/nats-io/nats-server/v2@v2.11.0-preview.2/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 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