@seriousme/opifex 1.11.2 → 2.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.txt +1 -1
- package/README.md +102 -43
- package/bin/conformanceServer.ts +45 -13
- package/bin/demoServer.ts +38 -13
- package/bin/mqtt.ts +52 -30
- package/client/ConnectionState.ts +2 -0
- package/client/client.ts +57 -47
- package/client/context.ts +75 -21
- package/client/deps.ts +5 -0
- package/client/handlers/handleAuth.test.ts +193 -0
- package/client/handlers/handleAuth.ts +89 -0
- package/client/handlers/handleConnack.test.ts +173 -0
- package/client/handlers/handleConnack.ts +16 -6
- package/client/handlers/handleDisconnect.test.ts +22 -0
- package/client/handlers/handleDisconnect.ts +17 -0
- package/client/handlers/handlePacket.test.ts +242 -0
- package/client/handlers/handlePacket.ts +19 -5
- package/client/handlers/handlePuback.test.ts +99 -0
- package/client/handlers/handlePuback.ts +6 -0
- package/client/handlers/handlePubcomp.test.ts +112 -0
- package/client/handlers/handlePubcomp.ts +5 -0
- package/client/handlers/handlePublish.test.ts +188 -0
- package/client/handlers/handlePubrec.test.ts +119 -0
- package/client/handlers/handlePubrec.ts +5 -1
- package/client/handlers/handlePubrel.test.ts +136 -0
- package/client/handlers/handleSuback.test.ts +122 -0
- package/client/handlers/handleSuback.ts +3 -0
- package/client/handlers/handleUnsuback.test.ts +101 -0
- package/client/mod.ts +2 -2
- package/client/store/memoryStore.ts +2 -2
- package/client/store/store.ts +3 -1
- package/deno/README.md +1 -2
- package/deno/tcpClient.test.ts +75 -0
- package/deno/tcpClient.ts +16 -11
- package/deno/tcpServer.test.ts +22 -21
- package/deno/tcpServer.ts +5 -0
- package/deno/tlsServer.test.ts +97 -0
- package/deno/tlsServer.ts +42 -0
- package/deno/wsServer.test.ts +93 -0
- package/deno/wsServer.ts +73 -0
- package/dev_utils/generateCert.ts +95 -0
- package/dev_utils/mockConn.ts +36 -0
- package/dev_utils/mockServer.ts +28 -0
- package/dev_utils/mod.ts +30 -2
- package/dev_utils/packetHelpers.ts +404 -0
- package/dev_utils/test-handlers.ts +98 -0
- package/dev_utils/timers.ts +14 -0
- package/dev_utils/webStreamPair.test.ts +171 -0
- package/dev_utils/webStreamPair.ts +143 -0
- package/dist/bin/conformanceServer.js +17 -12
- package/dist/bin/conformanceServer.js.map +1 -1
- package/dist/bin/demoServer.js +16 -12
- package/dist/bin/demoServer.js.map +1 -1
- package/dist/bin/mqtt.js +19 -16
- package/dist/bin/mqtt.js.map +1 -1
- package/dist/client/ConnectionState.d.ts +2 -0
- package/dist/client/ConnectionState.d.ts.map +1 -1
- package/dist/client/ConnectionState.js +2 -0
- package/dist/client/ConnectionState.js.map +1 -1
- package/dist/client/client.d.ts +19 -22
- package/dist/client/client.d.ts.map +1 -1
- package/dist/client/client.js +34 -20
- package/dist/client/client.js.map +1 -1
- package/dist/client/context.d.ts +11 -3
- package/dist/client/context.d.ts.map +1 -1
- package/dist/client/context.js +44 -12
- package/dist/client/context.js.map +1 -1
- package/dist/client/deps.d.ts +3 -2
- package/dist/client/deps.d.ts.map +1 -1
- package/dist/client/deps.js +1 -1
- package/dist/client/deps.js.map +1 -1
- package/dist/client/handlers/handleAuth.d.ts +9 -0
- package/dist/client/handlers/handleAuth.d.ts.map +1 -0
- package/dist/client/handlers/handleAuth.js +66 -0
- package/dist/client/handlers/handleAuth.js.map +1 -0
- package/dist/client/handlers/handleConnack.d.ts +2 -2
- package/dist/client/handlers/handleConnack.d.ts.map +1 -1
- package/dist/client/handlers/handleConnack.js +16 -6
- package/dist/client/handlers/handleConnack.js.map +1 -1
- package/dist/client/handlers/handleDisconnect.d.ts +11 -0
- package/dist/client/handlers/handleDisconnect.d.ts.map +1 -0
- package/dist/client/handlers/handleDisconnect.js +15 -0
- package/dist/client/handlers/handleDisconnect.js.map +1 -0
- package/dist/client/handlers/handlePacket.d.ts.map +1 -1
- package/dist/client/handlers/handlePacket.js +14 -3
- package/dist/client/handlers/handlePacket.js.map +1 -1
- package/dist/client/handlers/handlePuback.d.ts.map +1 -1
- package/dist/client/handlers/handlePuback.js +5 -0
- package/dist/client/handlers/handlePuback.js.map +1 -1
- package/dist/client/handlers/handlePubcomp.d.ts.map +1 -1
- package/dist/client/handlers/handlePubcomp.js +5 -0
- package/dist/client/handlers/handlePubcomp.js.map +1 -1
- package/dist/client/handlers/handlePubrec.d.ts.map +1 -1
- package/dist/client/handlers/handlePubrec.js +5 -1
- package/dist/client/handlers/handlePubrec.js.map +1 -1
- package/dist/client/handlers/handleSuback.d.ts +1 -0
- package/dist/client/handlers/handleSuback.d.ts.map +1 -1
- package/dist/client/handlers/handleSuback.js +3 -0
- package/dist/client/handlers/handleSuback.js.map +1 -1
- package/dist/client/mod.d.ts +2 -2
- package/dist/client/mod.d.ts.map +1 -1
- package/dist/client/mod.js +1 -1
- package/dist/client/mod.js.map +1 -1
- package/dist/client/store/memoryStore.d.ts.map +1 -1
- package/dist/client/store/memoryStore.js +2 -2
- package/dist/client/store/memoryStore.js.map +1 -1
- package/dist/client/store/store.d.ts +2 -1
- package/dist/client/store/store.d.ts.map +1 -1
- package/dist/client/store/store.js +1 -1
- package/dist/client/store/store.js.map +1 -1
- package/dist/dev_utils/generateCert.d.ts +11 -0
- package/dist/dev_utils/generateCert.d.ts.map +1 -0
- package/dist/dev_utils/generateCert.js +76 -0
- package/dist/dev_utils/generateCert.js.map +1 -0
- package/dist/examples/authHandler.test.d.ts +2 -0
- package/dist/examples/authHandler.test.d.ts.map +1 -0
- package/dist/examples/authHandler.test.js +123 -0
- package/dist/examples/authHandler.test.js.map +1 -0
- package/dist/examples/mqtt.test.d.ts +2 -0
- package/dist/examples/mqtt.test.d.ts.map +1 -0
- package/dist/examples/mqtt.test.js +70 -0
- package/dist/examples/mqtt.test.js.map +1 -0
- package/dist/examples/mqtts.test.d.ts +2 -0
- package/dist/examples/mqtts.test.d.ts.map +1 -0
- package/dist/examples/mqtts.test.js +81 -0
- package/dist/examples/mqtts.test.js.map +1 -0
- package/dist/mqttConn/mqttConn.d.ts +25 -16
- package/dist/mqttConn/mqttConn.d.ts.map +1 -1
- package/dist/mqttConn/mqttConn.js +69 -40
- package/dist/mqttConn/mqttConn.js.map +1 -1
- package/dist/mqttPacket/AuthenticationResult.d.ts.map +1 -1
- package/dist/mqttPacket/BitMask.d.ts +12 -11
- package/dist/mqttPacket/BitMask.d.ts.map +1 -1
- package/dist/mqttPacket/BitMask.js +10 -18
- package/dist/mqttPacket/BitMask.js.map +1 -1
- package/dist/mqttPacket/PacketType.d.ts.map +1 -1
- package/dist/mqttPacket/Properties.d.ts +2 -2
- package/dist/mqttPacket/Properties.d.ts.map +1 -1
- package/dist/mqttPacket/Properties.js.map +1 -1
- package/dist/mqttPacket/ReasonCode.d.ts +1 -0
- package/dist/mqttPacket/ReasonCode.d.ts.map +1 -1
- package/dist/mqttPacket/ReasonCode.js +1 -0
- package/dist/mqttPacket/ReasonCode.js.map +1 -1
- package/dist/mqttPacket/RetainHandling.d.ts.map +1 -1
- package/dist/mqttPacket/auth.d.ts +3 -5
- package/dist/mqttPacket/auth.d.ts.map +1 -1
- package/dist/mqttPacket/auth.js +31 -30
- package/dist/mqttPacket/auth.js.map +1 -1
- package/dist/mqttPacket/codec.d.ts +48 -0
- package/dist/mqttPacket/codec.d.ts.map +1 -0
- package/dist/mqttPacket/codec.js +93 -0
- package/dist/mqttPacket/codec.js.map +1 -0
- package/dist/mqttPacket/connack.d.ts +4 -5
- package/dist/mqttPacket/connack.d.ts.map +1 -1
- package/dist/mqttPacket/connack.js +43 -45
- package/dist/mqttPacket/connack.js.map +1 -1
- package/dist/mqttPacket/connect.d.ts +26 -27
- package/dist/mqttPacket/connect.d.ts.map +1 -1
- package/dist/mqttPacket/connect.js +156 -157
- package/dist/mqttPacket/connect.js.map +1 -1
- package/dist/mqttPacket/decoder.d.ts +1 -13
- package/dist/mqttPacket/decoder.d.ts.map +1 -1
- package/dist/mqttPacket/decoder.js +7 -28
- package/dist/mqttPacket/decoder.js.map +1 -1
- package/dist/mqttPacket/disconnect.d.ts +4 -5
- package/dist/mqttPacket/disconnect.d.ts.map +1 -1
- package/dist/mqttPacket/disconnect.js +41 -43
- package/dist/mqttPacket/disconnect.js.map +1 -1
- package/dist/mqttPacket/encoder.d.ts +1 -15
- package/dist/mqttPacket/encoder.d.ts.map +1 -1
- package/dist/mqttPacket/encoder.js +4 -27
- package/dist/mqttPacket/encoder.js.map +1 -1
- package/dist/mqttPacket/length.js +1 -1
- package/dist/mqttPacket/length.js.map +1 -1
- package/dist/mqttPacket/mod.d.ts +21 -103
- package/dist/mqttPacket/mod.d.ts.map +1 -1
- package/dist/mqttPacket/mod.js +5 -94
- package/dist/mqttPacket/mod.js.map +1 -1
- package/dist/mqttPacket/pingreq.d.ts +4 -5
- package/dist/mqttPacket/pingreq.d.ts.map +1 -1
- package/dist/mqttPacket/pingreq.js +11 -13
- package/dist/mqttPacket/pingreq.js.map +1 -1
- package/dist/mqttPacket/pingres.d.ts +4 -5
- package/dist/mqttPacket/pingres.d.ts.map +1 -1
- package/dist/mqttPacket/pingres.js +11 -13
- package/dist/mqttPacket/pingres.js.map +1 -1
- package/dist/mqttPacket/protocolLevels.d.ts +11 -6
- package/dist/mqttPacket/protocolLevels.d.ts.map +1 -1
- package/dist/mqttPacket/protocolLevels.js +5 -1
- package/dist/mqttPacket/protocolLevels.js.map +1 -1
- package/dist/mqttPacket/pubblishAcks.d.ts +9 -10
- package/dist/mqttPacket/pubblishAcks.d.ts.map +1 -1
- package/dist/mqttPacket/pubblishAcks.js +43 -45
- package/dist/mqttPacket/pubblishAcks.js.map +1 -1
- package/dist/mqttPacket/publish.d.ts +14 -14
- package/dist/mqttPacket/publish.d.ts.map +1 -1
- package/dist/mqttPacket/publish.js +53 -55
- package/dist/mqttPacket/publish.js.map +1 -1
- package/dist/mqttPacket/suback.d.ts +4 -5
- package/dist/mqttPacket/suback.d.ts.map +1 -1
- package/dist/mqttPacket/suback.js +38 -40
- package/dist/mqttPacket/suback.js.map +1 -1
- package/dist/mqttPacket/subscribe.d.ts +36 -7
- package/dist/mqttPacket/subscribe.d.ts.map +1 -1
- package/dist/mqttPacket/subscribe.js +90 -89
- package/dist/mqttPacket/subscribe.js.map +1 -1
- package/dist/mqttPacket/types.d.ts +1 -8
- package/dist/mqttPacket/types.d.ts.map +1 -1
- package/dist/mqttPacket/unsuback.d.ts +4 -5
- package/dist/mqttPacket/unsuback.d.ts.map +1 -1
- package/dist/mqttPacket/unsuback.js +32 -34
- package/dist/mqttPacket/unsuback.js.map +1 -1
- package/dist/mqttPacket/unsubscribe.d.ts +23 -6
- package/dist/mqttPacket/unsubscribe.d.ts.map +1 -1
- package/dist/mqttPacket/unsubscribe.js +45 -44
- package/dist/mqttPacket/unsubscribe.js.map +1 -1
- package/dist/mqttPacket/validators.d.ts +4 -16
- package/dist/mqttPacket/validators.d.ts.map +1 -1
- package/dist/mqttPacket/validators.js +5 -26
- package/dist/mqttPacket/validators.js.map +1 -1
- package/dist/node/tcpClient.d.ts +1 -1
- package/dist/node/tcpClient.d.ts.map +1 -1
- package/dist/node/tcpClient.js +12 -7
- package/dist/node/tcpClient.js.map +1 -1
- package/dist/node/tcpServer.d.ts.map +1 -1
- package/dist/node/tcpServer.js +4 -0
- package/dist/node/tcpServer.js.map +1 -1
- package/dist/node/tlsServer.d.ts +19 -0
- package/dist/node/tlsServer.d.ts.map +1 -0
- package/dist/node/tlsServer.js +58 -0
- package/dist/node/tlsServer.js.map +1 -0
- package/dist/node/wrapNodeSocket.d.ts.map +1 -1
- package/dist/node/wrapNodeSocket.js +6 -28
- package/dist/node/wrapNodeSocket.js.map +1 -1
- package/dist/persistence/basicPersistence.d.ts +36 -0
- package/dist/persistence/basicPersistence.d.ts.map +1 -0
- package/dist/persistence/basicPersistence.js +306 -0
- package/dist/persistence/basicPersistence.js.map +1 -0
- package/dist/persistence/deps.d.ts +3 -2
- package/dist/persistence/deps.d.ts.map +1 -1
- package/dist/persistence/deps.js +2 -1
- package/dist/persistence/deps.js.map +1 -1
- package/dist/persistence/memory/memoryPersistence.d.ts +6 -23
- package/dist/persistence/memory/memoryPersistence.d.ts.map +1 -1
- package/dist/persistence/memory/memoryPersistence.js +12 -115
- package/dist/persistence/memory/memoryPersistence.js.map +1 -1
- package/dist/persistence/memory/memoryStorage.d.ts +45 -0
- package/dist/persistence/memory/memoryStorage.d.ts.map +1 -0
- package/dist/persistence/memory/memoryStorage.js +143 -0
- package/dist/persistence/memory/memoryStorage.js.map +1 -0
- package/dist/persistence/mod.d.ts +3 -3
- package/dist/persistence/mod.d.ts.map +1 -1
- package/dist/persistence/mod.js +6 -1
- package/dist/persistence/mod.js.map +1 -1
- package/dist/persistence/persistence.d.ts +44 -69
- package/dist/persistence/persistence.d.ts.map +1 -1
- package/dist/persistence/persistence.js +1 -2
- package/dist/persistence/persistence.js.map +1 -1
- package/dist/persistence/sqlite/sqliteDatabase.d.ts +46 -0
- package/dist/persistence/sqlite/sqliteDatabase.d.ts.map +1 -0
- package/dist/persistence/sqlite/sqliteDatabase.js +215 -0
- package/dist/persistence/sqlite/sqliteDatabase.js.map +1 -0
- package/dist/persistence/sqlite/sqlitePersistence.d.ts +16 -0
- package/dist/persistence/sqlite/sqlitePersistence.d.ts.map +1 -0
- package/dist/persistence/sqlite/sqlitePersistence.js +29 -0
- package/dist/persistence/sqlite/sqlitePersistence.js.map +1 -0
- package/dist/persistence/sqlite/sqliteStorage.d.ts +36 -0
- package/dist/persistence/sqlite/sqliteStorage.d.ts.map +1 -0
- package/dist/persistence/sqlite/sqliteStorage.js +194 -0
- package/dist/persistence/sqlite/sqliteStorage.js.map +1 -0
- package/dist/persistence/storage.d.ts +38 -0
- package/dist/persistence/storage.d.ts.map +1 -0
- package/dist/persistence/storage.js +5 -0
- package/dist/persistence/storage.js.map +1 -0
- package/dist/server/config.d.ts +47 -0
- package/dist/server/config.d.ts.map +1 -0
- package/dist/server/config.js +41 -0
- package/dist/server/config.js.map +1 -0
- package/dist/server/context.d.ts +198 -24
- package/dist/server/context.d.ts.map +1 -1
- package/dist/server/context.js +432 -53
- package/dist/server/context.js.map +1 -1
- package/dist/server/deps.d.ts +5 -6
- package/dist/server/deps.d.ts.map +1 -1
- package/dist/server/deps.js +2 -2
- package/dist/server/deps.js.map +1 -1
- package/dist/server/handlers/completeConnect.d.ts +8 -0
- package/dist/server/handlers/completeConnect.d.ts.map +1 -0
- package/dist/server/handlers/completeConnect.js +74 -0
- package/dist/server/handlers/completeConnect.js.map +1 -0
- package/dist/server/handlers/handleAuth.d.ts +9 -0
- package/dist/server/handlers/handleAuth.d.ts.map +1 -0
- package/dist/server/handlers/handleAuth.js +69 -0
- package/dist/server/handlers/handleAuth.js.map +1 -0
- package/dist/server/handlers/handleConnect.d.ts +2 -4
- package/dist/server/handlers/handleConnect.d.ts.map +1 -1
- package/dist/server/handlers/handleConnect.js +140 -67
- package/dist/server/handlers/handleConnect.js.map +1 -1
- package/dist/server/handlers/handleDisconnect.d.ts +1 -1
- package/dist/server/handlers/handleDisconnect.d.ts.map +1 -1
- package/dist/server/handlers/handleDisconnect.js +3 -3
- package/dist/server/handlers/handleDisconnect.js.map +1 -1
- package/dist/server/handlers/handlePacket.d.ts.map +1 -1
- package/dist/server/handlers/handlePacket.js +19 -6
- package/dist/server/handlers/handlePacket.js.map +1 -1
- package/dist/server/handlers/handlePuback.d.ts +1 -3
- package/dist/server/handlers/handlePuback.d.ts.map +1 -1
- package/dist/server/handlers/handlePuback.js +2 -8
- package/dist/server/handlers/handlePuback.js.map +1 -1
- package/dist/server/handlers/handlePubcomp.d.ts +1 -1
- package/dist/server/handlers/handlePubcomp.d.ts.map +1 -1
- package/dist/server/handlers/handlePubcomp.js +2 -4
- package/dist/server/handlers/handlePubcomp.js.map +1 -1
- package/dist/server/handlers/handlePublish.d.ts +1 -1
- package/dist/server/handlers/handlePublish.d.ts.map +1 -1
- package/dist/server/handlers/handlePublish.js +149 -45
- package/dist/server/handlers/handlePublish.js.map +1 -1
- package/dist/server/handlers/handlePubrec.d.ts.map +1 -1
- package/dist/server/handlers/handlePubrec.js +2 -3
- package/dist/server/handlers/handlePubrec.js.map +1 -1
- package/dist/server/handlers/handlePubrel.d.ts.map +1 -1
- package/dist/server/handlers/handlePubrel.js +6 -2
- package/dist/server/handlers/handlePubrel.js.map +1 -1
- package/dist/server/handlers/handleSubscribe.d.ts +1 -6
- package/dist/server/handlers/handleSubscribe.d.ts.map +1 -1
- package/dist/server/handlers/handleSubscribe.js +99 -38
- package/dist/server/handlers/handleSubscribe.js.map +1 -1
- package/dist/server/handlers/handleUnsubscribe.d.ts.map +1 -1
- package/dist/server/handlers/handleUnsubscribe.js +19 -4
- package/dist/server/handlers/handleUnsubscribe.js.map +1 -1
- package/dist/server/mod.d.ts +3 -3
- package/dist/server/mod.d.ts.map +1 -1
- package/dist/server/mod.js +1 -1
- package/dist/server/mod.js.map +1 -1
- package/dist/server/server.d.ts +29 -6
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +84 -15
- package/dist/server/server.js.map +1 -1
- package/dist/socket/socket.d.ts +11 -9
- package/dist/socket/socket.d.ts.map +1 -1
- package/dist/socket/socket.js +67 -15
- package/dist/socket/socket.js.map +1 -1
- package/dist/timer/timer.d.ts +32 -0
- package/dist/timer/timer.d.ts.map +1 -1
- package/dist/timer/timer.js +32 -0
- package/dist/timer/timer.js.map +1 -1
- package/dist/trie/trie.d.ts +10 -5
- package/dist/trie/trie.d.ts.map +1 -1
- package/dist/trie/trie.js +47 -17
- package/dist/trie/trie.js.map +1 -1
- package/dist/utils/assert.d.ts.map +1 -1
- package/dist/utils/{BufferedAsyncIterable.d.ts → bufferedAsyncIterable.d.ts} +1 -1
- package/dist/utils/bufferedAsyncIterable.d.ts.map +1 -0
- package/dist/utils/{BufferedAsyncIterable.js → bufferedAsyncIterable.js} +1 -1
- package/dist/utils/bufferedAsyncIterable.js.map +1 -0
- package/dist/utils/deferred.d.ts.map +1 -1
- package/dist/utils/deferred.js.map +1 -1
- package/dist/utils/getOrInsertPolyFill.d.ts +2 -0
- package/dist/utils/getOrInsertPolyFill.d.ts.map +1 -0
- package/dist/utils/getOrInsertPolyFill.js +12 -0
- package/dist/utils/getOrInsertPolyFill.js.map +1 -0
- package/dist/utils/logger.d.ts +40 -3
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +53 -7
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/mod.d.ts +6 -1
- package/dist/utils/mod.d.ts.map +1 -1
- package/dist/utils/mod.js +5 -1
- package/dist/utils/mod.js.map +1 -1
- package/dist/utils/outboundTopicAliasManager.d.ts +52 -0
- package/dist/utils/outboundTopicAliasManager.d.ts.map +1 -0
- package/dist/utils/outboundTopicAliasManager.js +74 -0
- package/dist/utils/outboundTopicAliasManager.js.map +1 -0
- package/dist/utils/queue.d.ts.map +1 -1
- package/dist/utils/topicFilter.d.ts +32 -0
- package/dist/utils/topicFilter.d.ts.map +1 -0
- package/dist/utils/topicFilter.js +52 -0
- package/dist/utils/topicFilter.js.map +1 -0
- package/dist/utils/utils.d.ts +2 -0
- package/dist/utils/utils.d.ts.map +1 -1
- package/dist/utils/utils.js +2 -0
- package/dist/utils/utils.js.map +1 -1
- package/dist/utils/validators.d.ts +34 -0
- package/dist/utils/validators.d.ts.map +1 -0
- package/dist/utils/validators.js +61 -0
- package/dist/utils/validators.js.map +1 -0
- package/dist/web/webSocketStream.d.ts +29 -0
- package/dist/web/webSocketStream.d.ts.map +1 -0
- package/dist/web/webSocketStream.js +69 -0
- package/dist/web/webSocketStream.js.map +1 -0
- package/dist/web/wrapWebSocket.d.ts +12 -0
- package/dist/web/wrapWebSocket.d.ts.map +1 -0
- package/dist/web/wrapWebSocket.js +41 -0
- package/dist/web/wrapWebSocket.js.map +1 -0
- package/dist/web/wsClient.d.ts +7 -0
- package/dist/web/wsClient.d.ts.map +1 -0
- package/dist/web/wsClient.js +22 -0
- package/dist/web/wsClient.js.map +1 -0
- package/dist/web/wsClient.test.d.ts +2 -0
- package/dist/web/wsClient.test.d.ts.map +1 -0
- package/dist/web/wsClient.test.js +20 -0
- package/dist/web/wsClient.test.js.map +1 -0
- package/examples/authHandler.test.ts +164 -0
- package/examples/mqtt.test.ts +89 -0
- package/examples/mqtts.test.ts +100 -0
- package/examples/webSocket.test.ts +94 -0
- package/mqttConn/mqttConn.test.ts +9 -9
- package/mqttConn/mqttConn.ts +91 -49
- package/mqttPacket/BitMask.ts +11 -19
- package/mqttPacket/Properties.ts +6 -3
- package/mqttPacket/ReasonCode.ts +2 -1
- package/mqttPacket/auth.test.ts +1 -1
- package/mqttPacket/auth.ts +41 -48
- package/mqttPacket/codec.ts +146 -0
- package/mqttPacket/connack.ts +57 -68
- package/mqttPacket/connect.test.ts +2 -2
- package/mqttPacket/connect.ts +197 -204
- package/mqttPacket/decoder.test.ts +0 -39
- package/mqttPacket/decoder.ts +9 -36
- package/mqttPacket/disconnect.ts +57 -68
- package/mqttPacket/encoder.ts +5 -40
- package/mqttPacket/length.ts +1 -1
- package/mqttPacket/mod.ts +48 -200
- package/mqttPacket/pingreq.ts +21 -27
- package/mqttPacket/pingres.ts +21 -27
- package/mqttPacket/protocolLevels.ts +12 -7
- package/mqttPacket/pubblishAcks.ts +68 -77
- package/mqttPacket/publish.ts +76 -83
- package/mqttPacket/suback.ts +48 -58
- package/mqttPacket/subscribe.ts +141 -117
- package/mqttPacket/types.ts +1 -9
- package/mqttPacket/unsuback.ts +46 -58
- package/mqttPacket/unsubscribe.ts +74 -63
- package/mqttPacket/validators.ts +5 -31
- package/node/socket-node-stream.test.ts +322 -0
- package/node/socket-node-stream.ts +174 -0
- package/node/tcpClient.test.ts +70 -0
- package/node/tcpClient.ts +13 -12
- package/node/tcpServer.test.ts +24 -20
- package/node/tcpServer.ts +6 -0
- package/node/tlsServer.test.ts +94 -0
- package/node/tlsServer.ts +81 -0
- package/node/wrapNodeSocket.ts +7 -30
- package/package.json +73 -45
- package/persistence/basicPersistence.ts +495 -0
- package/persistence/deps.ts +11 -1
- package/persistence/memory/memoryPersistence.ts +12 -152
- package/persistence/memory/memoryStorage.ts +232 -0
- package/persistence/mod.ts +4 -4
- package/persistence/persistence.test.ts +24 -0
- package/persistence/persistence.testing.ts +1324 -0
- package/persistence/persistence.ts +111 -67
- package/persistence/sqlite/sqliteDatabase.ts +294 -0
- package/persistence/sqlite/sqlitePersistence.test.ts +48 -0
- package/persistence/sqlite/sqlitePersistence.ts +36 -0
- package/persistence/sqlite/sqliteStorage.ts +324 -0
- package/persistence/storage.ts +84 -0
- package/server/config.ts +65 -0
- package/server/context.ts +658 -74
- package/server/deps.ts +25 -5
- package/server/handlers/completeConnect.ts +93 -0
- package/server/handlers/duplicate.test.ts +196 -0
- package/server/handlers/handleAuth.test.ts +225 -0
- package/server/handlers/handleAuth.ts +88 -0
- package/server/handlers/handleConnect.test.ts +589 -0
- package/server/handlers/handleConnect.ts +201 -86
- package/server/handlers/handleConnect.version.test.ts +108 -0
- package/server/handlers/handleDisconnect.test.ts +56 -0
- package/server/handlers/handleDisconnect.ts +3 -3
- package/server/handlers/handlePacket.test.ts +24 -0
- package/server/handlers/handlePacket.ts +30 -8
- package/server/handlers/handlePingreq.test.ts +29 -0
- package/server/handlers/handlePuback.test.ts +28 -0
- package/server/handlers/handlePuback.ts +5 -8
- package/server/handlers/handlePubcomp.test.ts +28 -0
- package/server/handlers/handlePubcomp.ts +5 -4
- package/server/handlers/handlePublish.test.ts +471 -0
- package/server/handlers/handlePublish.ts +189 -35
- package/server/handlers/handlePubrec.test.ts +28 -0
- package/server/handlers/handlePubrec.ts +2 -3
- package/server/handlers/handlePubrel.test.ts +74 -0
- package/server/handlers/handlePubrel.ts +12 -2
- package/server/handlers/handleSubscribe.test.ts +971 -0
- package/server/handlers/handleSubscribe.ts +158 -39
- package/server/handlers/handleUnsubscribe.test.ts +79 -0
- package/server/handlers/handleUnsubscribe.ts +29 -5
- package/server/handlers/keepalive.test.ts +132 -0
- package/server/handlers/session.persistence.test.ts +194 -0
- package/server/handlers/will.delivery.test.ts +221 -0
- package/server/mod.ts +4 -3
- package/server/preconnect.test.ts +151 -0
- package/server/server.ts +115 -17
- package/socket/socket.test.ts +91 -0
- package/socket/socket.ts +81 -31
- package/timer/timer.test.ts +4 -7
- package/timer/timer.ts +36 -0
- package/tools/benchmarks/receiver.ts +51 -0
- package/tools/benchmarks/sender.ts +48 -0
- package/tools/benchmarks/utils.ts +31 -0
- package/tools/mqtt-compat/README.md +130 -0
- package/tools/mqtt-compat/broker.ts +52 -0
- package/tools/mqtt-compat/paho-mqtt4.test.ts +379 -0
- package/tools/mqtt-compat/paho-mqtt5.tests.ts +887 -0
- package/trie/trie.test.ts +9 -1
- package/trie/trie.ts +57 -22
- package/utils/bufferedAsyncIterable.test.ts +92 -0
- package/utils/{BufferedAsyncIterable.ts → bufferedAsyncIterable.ts} +1 -1
- package/utils/deferred.test.ts +36 -0
- package/utils/deferred.ts +0 -1
- package/utils/getOrInsertPolyFill.ts +12 -0
- package/utils/logger.test.ts +189 -0
- package/utils/logger.ts +62 -7
- package/utils/mod.ts +15 -1
- package/utils/outboundTopicAliasManager.ts +100 -0
- package/utils/queue.test.ts +61 -0
- package/utils/topicFilter.test.ts +63 -0
- package/utils/topicFilter.ts +61 -0
- package/utils/utils.ts +3 -0
- package/utils/validators.ts +67 -0
- package/web/webSocketStream.ts +97 -0
- package/web/wrapWebSocket.ts +52 -0
- package/web/wsClient.test.ts +26 -0
- package/web/wsClient.ts +25 -0
- package/benchmarks/receiver.ts +0 -35
- package/benchmarks/sender.ts +0 -32
- package/benchmarks/utils.ts +0 -9
- package/dev_utils/dummyConn.ts +0 -110
- package/dist/persistence/store.d.ts +0 -13
- package/dist/persistence/store.d.ts.map +0 -1
- package/dist/persistence/store.js +0 -2
- package/dist/persistence/store.js.map +0 -1
- package/dist/utils/BufferedAsyncIterable.d.ts.map +0 -1
- package/dist/utils/BufferedAsyncIterable.js.map +0 -1
- package/examples/tcpServer.test.ts +0 -148
- package/persistence/memory/memoryPersistence.test.ts +0 -156
- package/persistence/store.ts +0 -15
- package/server/test/handleConnect.test.ts +0 -130
- package/server/test/test-handlers.ts +0 -53
|
@@ -0,0 +1,1324 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared test suite for IPersistence implementations.
|
|
3
|
+
* Ensures behavioral parity between MemoryPersistence and SqlitePersistence.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, test } from "node:test";
|
|
7
|
+
import assert from "node:assert/strict";
|
|
8
|
+
import { MQTTLevel, PacketType } from "./deps.ts";
|
|
9
|
+
import type { QoS } from "./deps.ts";
|
|
10
|
+
import type {
|
|
11
|
+
ClientSubscription,
|
|
12
|
+
ExtPublishPacket,
|
|
13
|
+
IPersistence,
|
|
14
|
+
} from "./persistence.ts";
|
|
15
|
+
import type { PublishPacketV5 } from "../mqttPacket/publish.ts";
|
|
16
|
+
|
|
17
|
+
const utf8Encoder = new TextEncoder();
|
|
18
|
+
const utf8Decoder = new TextDecoder();
|
|
19
|
+
|
|
20
|
+
function createPacket(
|
|
21
|
+
topic: string,
|
|
22
|
+
payload: string,
|
|
23
|
+
options: Partial<ExtPublishPacket> = {},
|
|
24
|
+
): ExtPublishPacket {
|
|
25
|
+
return {
|
|
26
|
+
type: PacketType.publish,
|
|
27
|
+
protocolLevel: MQTTLevel.v5,
|
|
28
|
+
topic,
|
|
29
|
+
payload: utf8Encoder.encode(payload),
|
|
30
|
+
qos: 0,
|
|
31
|
+
...options,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function createReceiver(
|
|
36
|
+
persistence: IPersistence,
|
|
37
|
+
clientId: string,
|
|
38
|
+
clean = false,
|
|
39
|
+
): Promise<{ received: ExtPublishPacket[] }> {
|
|
40
|
+
if (clean) {
|
|
41
|
+
await persistence.deregisterClient(clientId);
|
|
42
|
+
}
|
|
43
|
+
const received: ExtPublishPacket[] = [];
|
|
44
|
+
await persistence.registerClient(
|
|
45
|
+
clientId,
|
|
46
|
+
(pkt) => {
|
|
47
|
+
received.push(pkt);
|
|
48
|
+
return Promise.resolve();
|
|
49
|
+
},
|
|
50
|
+
);
|
|
51
|
+
return { received };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export type PersistenceFactoryOptions = {
|
|
55
|
+
name: string;
|
|
56
|
+
factory: () => {
|
|
57
|
+
persistence: IPersistence;
|
|
58
|
+
cleanup: () => void | Promise<void>;
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export function runPersistenceTestSuite(options: PersistenceFactoryOptions) {
|
|
63
|
+
const { name, factory } = options;
|
|
64
|
+
describe(`${name} - Shared Behavioral Tests`, () => {
|
|
65
|
+
// === Basic Operations ===
|
|
66
|
+
|
|
67
|
+
test("registerClient creates client", async () => {
|
|
68
|
+
const { persistence, cleanup } = factory();
|
|
69
|
+
const { existingSession } = await persistence.registerClient(
|
|
70
|
+
"client1",
|
|
71
|
+
() => Promise.resolve(),
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
assert.strictEqual(existingSession, false);
|
|
75
|
+
cleanup();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// === Subscription Tests ===
|
|
79
|
+
|
|
80
|
+
test("subscribe adds topic to subscriptions", async () => {
|
|
81
|
+
const { persistence, cleanup } = factory();
|
|
82
|
+
await persistence.registerClient("client1", () => Promise.resolve());
|
|
83
|
+
|
|
84
|
+
await persistence.subscribe("client1", {
|
|
85
|
+
topicFilter: "test/topic",
|
|
86
|
+
shareName: "",
|
|
87
|
+
qos: 1,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const subs = await Array.fromAsync(
|
|
91
|
+
persistence.listSubscriptions("client1"),
|
|
92
|
+
);
|
|
93
|
+
const match = subs.find((s) => s.topicFilter === "test/topic");
|
|
94
|
+
|
|
95
|
+
assert(match !== undefined);
|
|
96
|
+
assert.strictEqual(match.qos, 1);
|
|
97
|
+
cleanup();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("subscribe V5 adds subscription parameters to subscriptions", async () => {
|
|
101
|
+
const { persistence, cleanup } = factory();
|
|
102
|
+
await persistence.registerClient("client1", () => Promise.resolve());
|
|
103
|
+
|
|
104
|
+
const subscriptionData: ClientSubscription = {
|
|
105
|
+
topicFilter: "test/topic",
|
|
106
|
+
shareName: "",
|
|
107
|
+
qos: 1,
|
|
108
|
+
noLocal: true,
|
|
109
|
+
retainAsPublished: true,
|
|
110
|
+
retainHandling: 2,
|
|
111
|
+
subscriptionIdentifier: 5,
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
await persistence.subscribe(
|
|
115
|
+
"client1",
|
|
116
|
+
subscriptionData,
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
const subs = await Array.fromAsync(
|
|
120
|
+
persistence.listSubscriptions("client1"),
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
assert.deepEqual(subs[0], subscriptionData);
|
|
124
|
+
cleanup();
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test("unsubscribe removes topic from subscriptions", async () => {
|
|
128
|
+
const { persistence, cleanup } = factory();
|
|
129
|
+
await persistence.registerClient("client1", () => Promise.resolve());
|
|
130
|
+
|
|
131
|
+
await persistence.subscribe("client1", {
|
|
132
|
+
topicFilter: "test/topic",
|
|
133
|
+
shareName: "",
|
|
134
|
+
qos: 1,
|
|
135
|
+
});
|
|
136
|
+
await persistence.unsubscribe("client1", "test/topic", "");
|
|
137
|
+
|
|
138
|
+
const subs = await Array.fromAsync(
|
|
139
|
+
persistence.listSubscriptions("client1"),
|
|
140
|
+
);
|
|
141
|
+
const match = subs.find((s) => s.topicFilter === "test/topic");
|
|
142
|
+
assert.equal(match, undefined);
|
|
143
|
+
cleanup();
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("unsubscribe on non-existent topic is a no-op", async () => {
|
|
147
|
+
const { persistence, cleanup } = factory();
|
|
148
|
+
await persistence.registerClient("client1", () => Promise.resolve());
|
|
149
|
+
|
|
150
|
+
// Should not throw
|
|
151
|
+
await persistence.unsubscribe("client1", "nonexistent/topic", "");
|
|
152
|
+
const subs = await Array.fromAsync(
|
|
153
|
+
persistence.listSubscriptions("client1"),
|
|
154
|
+
);
|
|
155
|
+
assert.strictEqual(subs.length, 0);
|
|
156
|
+
cleanup();
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// === Publish/Subscribe Routing ===
|
|
160
|
+
|
|
161
|
+
test("publish routes to exact topic subscriber", async () => {
|
|
162
|
+
const { persistence, cleanup } = factory();
|
|
163
|
+
const { received } = await createReceiver(persistence, "client1");
|
|
164
|
+
|
|
165
|
+
await persistence.subscribe("client1", {
|
|
166
|
+
topicFilter: "test/topic",
|
|
167
|
+
shareName: "",
|
|
168
|
+
qos: 0,
|
|
169
|
+
});
|
|
170
|
+
await persistence.publish(
|
|
171
|
+
"client1",
|
|
172
|
+
createPacket("test/topic", "hello"),
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
assert.strictEqual(received.length, 1);
|
|
176
|
+
assert.strictEqual(received[0].topic, "test/topic");
|
|
177
|
+
cleanup();
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test("publish does not route to unsubscribed topics", async () => {
|
|
181
|
+
const { persistence, cleanup } = factory();
|
|
182
|
+
const { received } = await createReceiver(persistence, "client1");
|
|
183
|
+
|
|
184
|
+
await persistence.publish(
|
|
185
|
+
"client1",
|
|
186
|
+
createPacket("test/topic", "hello"),
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
assert.strictEqual(received.length, 0);
|
|
190
|
+
cleanup();
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test("publish routes to + wildcard subscriber", async () => {
|
|
194
|
+
const { persistence, cleanup } = factory();
|
|
195
|
+
const { received } = await createReceiver(persistence, "client1");
|
|
196
|
+
|
|
197
|
+
await persistence.subscribe("client1", {
|
|
198
|
+
topicFilter: "sensors/+/temp",
|
|
199
|
+
shareName: "",
|
|
200
|
+
qos: 0,
|
|
201
|
+
});
|
|
202
|
+
await persistence.publish(
|
|
203
|
+
"client1",
|
|
204
|
+
createPacket("sensors/room1/temp", "22"),
|
|
205
|
+
);
|
|
206
|
+
await persistence.publish(
|
|
207
|
+
"client1",
|
|
208
|
+
createPacket("sensors/room2/temp", "24"),
|
|
209
|
+
);
|
|
210
|
+
await persistence.publish(
|
|
211
|
+
"client1",
|
|
212
|
+
createPacket("sensors/room1/humidity", "50"),
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
assert.strictEqual(received.length, 2);
|
|
216
|
+
cleanup();
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
test("publish routes to # wildcard subscriber", async () => {
|
|
220
|
+
const { persistence, cleanup } = factory();
|
|
221
|
+
const { received } = await createReceiver(persistence, "client1");
|
|
222
|
+
|
|
223
|
+
await persistence.subscribe("client1", {
|
|
224
|
+
topicFilter: "sensors/#",
|
|
225
|
+
shareName: "",
|
|
226
|
+
qos: 0,
|
|
227
|
+
});
|
|
228
|
+
await persistence.publish(
|
|
229
|
+
"client1",
|
|
230
|
+
createPacket("sensors/temp", "22"),
|
|
231
|
+
);
|
|
232
|
+
await persistence.publish(
|
|
233
|
+
"client1",
|
|
234
|
+
createPacket("sensors/room1/temp", "24"),
|
|
235
|
+
);
|
|
236
|
+
await persistence.publish(
|
|
237
|
+
"client1",
|
|
238
|
+
createPacket("other/topic", "data"),
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
assert.strictEqual(received.length, 2);
|
|
242
|
+
cleanup();
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
test("publish deduplicates when client matches multiple subscriptions", async () => {
|
|
246
|
+
const { persistence, cleanup } = factory();
|
|
247
|
+
const { received } = await createReceiver(persistence, "client1");
|
|
248
|
+
|
|
249
|
+
await persistence.subscribe("client1", {
|
|
250
|
+
topicFilter: "test/+",
|
|
251
|
+
qos: 1,
|
|
252
|
+
shareName: "",
|
|
253
|
+
});
|
|
254
|
+
await persistence.subscribe("client1", {
|
|
255
|
+
topicFilter: "test/#",
|
|
256
|
+
qos: 2,
|
|
257
|
+
shareName: "",
|
|
258
|
+
});
|
|
259
|
+
await persistence.subscribe("client1", {
|
|
260
|
+
topicFilter: "test/topic",
|
|
261
|
+
shareName: "",
|
|
262
|
+
qos: 0,
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
await persistence.publish(
|
|
266
|
+
"client1",
|
|
267
|
+
createPacket("test/topic", "hello", { qos: 2 }),
|
|
268
|
+
);
|
|
269
|
+
|
|
270
|
+
// Should receive only once with highest QoS
|
|
271
|
+
assert.strictEqual(received.length, 1);
|
|
272
|
+
assert.strictEqual(received[0].qos, 2);
|
|
273
|
+
cleanup();
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
test("publish routes to multiple clients", async () => {
|
|
277
|
+
const { persistence, cleanup } = factory();
|
|
278
|
+
const { received: received1 } = await createReceiver(
|
|
279
|
+
persistence,
|
|
280
|
+
"client1",
|
|
281
|
+
);
|
|
282
|
+
const { received: received2 } = await createReceiver(
|
|
283
|
+
persistence,
|
|
284
|
+
"client2",
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
await persistence.subscribe("client1", {
|
|
288
|
+
topicFilter: "test/topic",
|
|
289
|
+
shareName: "",
|
|
290
|
+
qos: 0,
|
|
291
|
+
});
|
|
292
|
+
await persistence.subscribe("client2", {
|
|
293
|
+
topicFilter: "test/topic",
|
|
294
|
+
shareName: "",
|
|
295
|
+
qos: 1,
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
await persistence.publish(
|
|
299
|
+
"some-publisher",
|
|
300
|
+
createPacket("test/topic", "hello", { qos: 1 }),
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
assert.strictEqual(received1.length, 1);
|
|
304
|
+
assert.strictEqual(received2.length, 1);
|
|
305
|
+
assert.strictEqual(received1[0].qos, 0);
|
|
306
|
+
assert.strictEqual(received2[0].qos, 1);
|
|
307
|
+
cleanup();
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
// === Retained Messages ===
|
|
311
|
+
|
|
312
|
+
test("publish with retain=true and empty payload clears retained", async () => {
|
|
313
|
+
const { persistence, cleanup } = factory();
|
|
314
|
+
|
|
315
|
+
await persistence.publish(
|
|
316
|
+
"publisher",
|
|
317
|
+
createPacket("test/topic", "retained", { retain: true }),
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
const { received: received1 } = await createReceiver(
|
|
321
|
+
persistence,
|
|
322
|
+
"client1",
|
|
323
|
+
);
|
|
324
|
+
// Register subscription in Trie first
|
|
325
|
+
await persistence.subscribe("client1", {
|
|
326
|
+
topicFilter: "test/topic",
|
|
327
|
+
shareName: "",
|
|
328
|
+
qos: 0,
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
await persistence.handleRetained("client1", [{
|
|
332
|
+
topicFilter: "test/topic",
|
|
333
|
+
shareName: "",
|
|
334
|
+
qos: 0,
|
|
335
|
+
}]);
|
|
336
|
+
assert.strictEqual(received1.length, 1);
|
|
337
|
+
|
|
338
|
+
await persistence.publish("publisher", {
|
|
339
|
+
type: PacketType.publish,
|
|
340
|
+
protocolLevel: MQTTLevel.v5,
|
|
341
|
+
topic: "test/topic",
|
|
342
|
+
payload: new Uint8Array(0),
|
|
343
|
+
retain: true,
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
const { received: received2 } = await createReceiver(
|
|
347
|
+
persistence,
|
|
348
|
+
"client2",
|
|
349
|
+
);
|
|
350
|
+
// Register subscription in Trie first
|
|
351
|
+
await persistence.subscribe("client2", {
|
|
352
|
+
topicFilter: "test/topic",
|
|
353
|
+
shareName: "",
|
|
354
|
+
qos: 0,
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
await persistence.handleRetained("client2", [{
|
|
358
|
+
topicFilter: "test/topic",
|
|
359
|
+
shareName: "",
|
|
360
|
+
qos: 0,
|
|
361
|
+
}]);
|
|
362
|
+
assert.strictEqual(received2.length, 0);
|
|
363
|
+
cleanup();
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
test("handleRetained sends matching retained messages", async () => {
|
|
367
|
+
const { persistence, cleanup } = factory();
|
|
368
|
+
|
|
369
|
+
await persistence.publish(
|
|
370
|
+
"publisher",
|
|
371
|
+
createPacket("sensor/temp", "22", { retain: true }),
|
|
372
|
+
);
|
|
373
|
+
await persistence.publish(
|
|
374
|
+
"publisher",
|
|
375
|
+
createPacket("sensor/humidity", "50", { retain: true }),
|
|
376
|
+
);
|
|
377
|
+
await persistence.publish(
|
|
378
|
+
"publisher",
|
|
379
|
+
createPacket("other/topic", "data", { retain: true }),
|
|
380
|
+
);
|
|
381
|
+
|
|
382
|
+
const { received } = await createReceiver(persistence, "client1");
|
|
383
|
+
// Register subscription in Trie first
|
|
384
|
+
await persistence.subscribe("client1", {
|
|
385
|
+
topicFilter: "sensor/+",
|
|
386
|
+
shareName: "",
|
|
387
|
+
qos: 0,
|
|
388
|
+
});
|
|
389
|
+
await persistence.handleRetained("client1", [{
|
|
390
|
+
topicFilter: "sensor/+",
|
|
391
|
+
shareName: "",
|
|
392
|
+
qos: 0,
|
|
393
|
+
}]);
|
|
394
|
+
|
|
395
|
+
assert.strictEqual(received.length, 2);
|
|
396
|
+
cleanup();
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
// === Session Persistence ===
|
|
400
|
+
|
|
401
|
+
test("reconnect without deregistration retains client state", async () => {
|
|
402
|
+
const { persistence, cleanup } = factory();
|
|
403
|
+
|
|
404
|
+
const client = "reconnect client";
|
|
405
|
+
const qos0opts = { qos: 0 as QoS, retain: false };
|
|
406
|
+
|
|
407
|
+
// QoS 0 packets must not be retained
|
|
408
|
+
const incomingQoS0pkt = createPacket("incoming/data", "data", qos0opts);
|
|
409
|
+
const outgoingQoS0pkt = createPacket("outgoing/data", "data", qos0opts);
|
|
410
|
+
// Qos > 0 must be retained
|
|
411
|
+
const incomingQoS1pkt = createPacket("incoming/data", "data", {
|
|
412
|
+
qos: 1,
|
|
413
|
+
id: 12,
|
|
414
|
+
retain: false,
|
|
415
|
+
});
|
|
416
|
+
const incomingQoS2pkt = createPacket("incoming/data", "data", {
|
|
417
|
+
qos: 2,
|
|
418
|
+
id: 13,
|
|
419
|
+
retain: false,
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
const outgoingQoS1pkt = createPacket("outgoing/data", "data", {
|
|
423
|
+
qos: 1,
|
|
424
|
+
id: 14,
|
|
425
|
+
retain: false,
|
|
426
|
+
});
|
|
427
|
+
const outgoingQoS2pkt = createPacket("outgoing/data", "data", {
|
|
428
|
+
qos: 2,
|
|
429
|
+
id: 15,
|
|
430
|
+
retain: false,
|
|
431
|
+
});
|
|
432
|
+
const outgoingAckId = 33;
|
|
433
|
+
|
|
434
|
+
await persistence.registerClient(client, () => Promise.resolve());
|
|
435
|
+
await persistence.subscribe(client, {
|
|
436
|
+
topicFilter: "test/topic",
|
|
437
|
+
shareName: "",
|
|
438
|
+
qos: 1,
|
|
439
|
+
});
|
|
440
|
+
await persistence.addPendingIncomingPacket(client, incomingQoS0pkt);
|
|
441
|
+
await persistence.addPendingIncomingPacket(client, incomingQoS1pkt);
|
|
442
|
+
await persistence.addPendingIncomingPacket(client, incomingQoS2pkt);
|
|
443
|
+
|
|
444
|
+
await persistence.addPendingOutgoingPacket(client, outgoingQoS0pkt);
|
|
445
|
+
await persistence.addPendingOutgoingPacket(client, outgoingQoS1pkt);
|
|
446
|
+
await persistence.addPendingOutgoingPacket(client, outgoingQoS2pkt);
|
|
447
|
+
await persistence.addPendingAck(client, outgoingAckId);
|
|
448
|
+
|
|
449
|
+
const { existingSession } = await persistence.registerClient(
|
|
450
|
+
client,
|
|
451
|
+
() => Promise.resolve(),
|
|
452
|
+
);
|
|
453
|
+
|
|
454
|
+
assert.strictEqual(existingSession, true);
|
|
455
|
+
const subs = await Array.fromAsync(
|
|
456
|
+
persistence.listSubscriptions(client),
|
|
457
|
+
);
|
|
458
|
+
assert.strictEqual(subs.length, 1);
|
|
459
|
+
assert.strictEqual(subs[0].topicFilter, "test/topic");
|
|
460
|
+
assert.strictEqual(subs[0].qos, 1);
|
|
461
|
+
const inPkts = await Array.fromAsync(
|
|
462
|
+
persistence.listPendingIncomingPackets(client),
|
|
463
|
+
);
|
|
464
|
+
assert.strictEqual(inPkts.length, 2);
|
|
465
|
+
assert.strictEqual(inPkts[0].topic, incomingQoS1pkt.topic);
|
|
466
|
+
assert.strictEqual(inPkts[0].qos, incomingQoS1pkt.qos);
|
|
467
|
+
const inPkt1 = await persistence.getPendingIncomingPacket(client, 12);
|
|
468
|
+
const inPkt2 = await persistence.getPendingIncomingPacket(client, 13);
|
|
469
|
+
assert.deepEqual(inPkt1, inPkts[0]);
|
|
470
|
+
assert.deepEqual(inPkt2, inPkts[1]);
|
|
471
|
+
|
|
472
|
+
// Register subscription in Trie first
|
|
473
|
+
await persistence.subscribe(client, {
|
|
474
|
+
topicFilter: "outgoing/+",
|
|
475
|
+
shareName: "",
|
|
476
|
+
qos: 1,
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
const outPkts = await Array.fromAsync(
|
|
480
|
+
persistence.listPendingOutgoingPackets(client),
|
|
481
|
+
);
|
|
482
|
+
assert.strictEqual(outPkts.length, 2);
|
|
483
|
+
assert.strictEqual(outPkts[0].topic, outgoingQoS2pkt.topic);
|
|
484
|
+
const acks = await Array.fromAsync(
|
|
485
|
+
persistence.listPendingAcks(client),
|
|
486
|
+
);
|
|
487
|
+
assert.strictEqual(acks.length, 1);
|
|
488
|
+
assert.strictEqual(acks[0], outgoingAckId);
|
|
489
|
+
cleanup();
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
test("client deregistration discards previous subscriptions ", async () => {
|
|
493
|
+
const { persistence, cleanup } = factory();
|
|
494
|
+
const incomingPacket = createPacket("incoming/data", "data", {
|
|
495
|
+
qos: 1,
|
|
496
|
+
id: 12,
|
|
497
|
+
retain: false,
|
|
498
|
+
});
|
|
499
|
+
const outgoingPacket = createPacket("outgoing/data", "data", {
|
|
500
|
+
qos: 2,
|
|
501
|
+
id: 13,
|
|
502
|
+
retain: false,
|
|
503
|
+
});
|
|
504
|
+
const outgoingAckId = 33;
|
|
505
|
+
await persistence.registerClient("client1", () => Promise.resolve());
|
|
506
|
+
await persistence.subscribe("client1", {
|
|
507
|
+
topicFilter: "test/topic",
|
|
508
|
+
shareName: "",
|
|
509
|
+
qos: 1,
|
|
510
|
+
});
|
|
511
|
+
await persistence.addPendingIncomingPacket("client1", incomingPacket);
|
|
512
|
+
await persistence.addPendingOutgoingPacket("client1", outgoingPacket);
|
|
513
|
+
await persistence.addPendingAck("client1", outgoingAckId);
|
|
514
|
+
await persistence.deregisterClient("client1");
|
|
515
|
+
|
|
516
|
+
const { existingSession } = await persistence.registerClient(
|
|
517
|
+
"client1",
|
|
518
|
+
() => Promise.resolve(),
|
|
519
|
+
);
|
|
520
|
+
|
|
521
|
+
assert.strictEqual(existingSession, false);
|
|
522
|
+
const subs = await Array.fromAsync(
|
|
523
|
+
persistence.listSubscriptions("client1"),
|
|
524
|
+
);
|
|
525
|
+
assert.strictEqual(subs.length, 0);
|
|
526
|
+
const inPkts = await Array.fromAsync(
|
|
527
|
+
persistence.listPendingIncomingPackets("client1"),
|
|
528
|
+
);
|
|
529
|
+
assert.strictEqual(inPkts.length, 0);
|
|
530
|
+
const outPkts = await Array.fromAsync(
|
|
531
|
+
persistence.listPendingOutgoingPackets("client1"),
|
|
532
|
+
);
|
|
533
|
+
assert.strictEqual(outPkts.length, 0);
|
|
534
|
+
const acks = await Array.fromAsync(
|
|
535
|
+
persistence.listPendingAcks("client1"),
|
|
536
|
+
);
|
|
537
|
+
assert.strictEqual(acks.length, 0);
|
|
538
|
+
cleanup();
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
// === Edge Cases ===
|
|
542
|
+
|
|
543
|
+
test("empty topic filter subscription", async () => {
|
|
544
|
+
const { persistence, cleanup } = factory();
|
|
545
|
+
await persistence.registerClient("client1", () => Promise.resolve());
|
|
546
|
+
|
|
547
|
+
await persistence.subscribe("client1", {
|
|
548
|
+
topicFilter: "",
|
|
549
|
+
qos: 0,
|
|
550
|
+
shareName: "",
|
|
551
|
+
});
|
|
552
|
+
const subs = await Array.fromAsync(
|
|
553
|
+
persistence.listSubscriptions("client1"),
|
|
554
|
+
);
|
|
555
|
+
const match = subs.find((s) => s.topicFilter === "");
|
|
556
|
+
assert(match !== undefined);
|
|
557
|
+
cleanup();
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
test("special characters in topic", async () => {
|
|
561
|
+
const { persistence, cleanup } = factory();
|
|
562
|
+
const { received } = await createReceiver(persistence, "client1");
|
|
563
|
+
|
|
564
|
+
const specialTopic = "test/日本語/émoji/🔥";
|
|
565
|
+
await persistence.subscribe("client1", {
|
|
566
|
+
topicFilter: specialTopic,
|
|
567
|
+
shareName: "",
|
|
568
|
+
qos: 0,
|
|
569
|
+
});
|
|
570
|
+
await persistence.publish(
|
|
571
|
+
"client1",
|
|
572
|
+
createPacket(specialTopic, "data"),
|
|
573
|
+
);
|
|
574
|
+
|
|
575
|
+
assert.strictEqual(received.length, 1);
|
|
576
|
+
cleanup();
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
test("very long topic name", async () => {
|
|
580
|
+
const { persistence, cleanup } = factory();
|
|
581
|
+
const { received } = await createReceiver(persistence, "client1");
|
|
582
|
+
|
|
583
|
+
const longTopic = "a/".repeat(100) + "end";
|
|
584
|
+
await persistence.subscribe("client1", {
|
|
585
|
+
topicFilter: longTopic,
|
|
586
|
+
shareName: "",
|
|
587
|
+
qos: 0,
|
|
588
|
+
});
|
|
589
|
+
await persistence.publish(
|
|
590
|
+
"client1",
|
|
591
|
+
createPacket(longTopic, "data"),
|
|
592
|
+
);
|
|
593
|
+
|
|
594
|
+
assert.strictEqual(received.length, 1);
|
|
595
|
+
cleanup();
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
test("many subscriptions per client", async () => {
|
|
599
|
+
const { persistence, cleanup } = factory();
|
|
600
|
+
await persistence.registerClient("client1", () => Promise.resolve());
|
|
601
|
+
|
|
602
|
+
for (let i = 0; i < 100; i++) {
|
|
603
|
+
await persistence.subscribe(
|
|
604
|
+
"client1",
|
|
605
|
+
{
|
|
606
|
+
topicFilter: `topic/${i}`,
|
|
607
|
+
shareName: "",
|
|
608
|
+
qos: i % 3 as 0 | 1 | 2,
|
|
609
|
+
},
|
|
610
|
+
);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
const subs = await Array.fromAsync(
|
|
614
|
+
persistence.listSubscriptions("client1"),
|
|
615
|
+
);
|
|
616
|
+
assert.strictEqual(subs.length, 100);
|
|
617
|
+
cleanup();
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
test("rapid subscribe/unsubscribe cycles", async () => {
|
|
621
|
+
const { persistence, cleanup } = factory();
|
|
622
|
+
await persistence.registerClient("client1", () => Promise.resolve());
|
|
623
|
+
|
|
624
|
+
for (let i = 0; i < 50; i++) {
|
|
625
|
+
await persistence.subscribe("client1", {
|
|
626
|
+
topicFilter: "test/topic",
|
|
627
|
+
shareName: "",
|
|
628
|
+
qos: 1,
|
|
629
|
+
});
|
|
630
|
+
await persistence.unsubscribe("client1", "test/topic", "");
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
const subs = await Array.fromAsync(
|
|
634
|
+
persistence.listSubscriptions("client1"),
|
|
635
|
+
);
|
|
636
|
+
assert.strictEqual(subs.length, 0);
|
|
637
|
+
cleanup();
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
// === Low-Level Packet & Ack Stores ===
|
|
641
|
+
|
|
642
|
+
test("nextPacketId returns incrementing IDs", async () => {
|
|
643
|
+
const { persistence, cleanup } = factory();
|
|
644
|
+
await persistence.registerClient("client1", () => Promise.resolve());
|
|
645
|
+
|
|
646
|
+
const id1 = await persistence.nextPacketId("client1");
|
|
647
|
+
const id2 = await persistence.nextPacketId("client1");
|
|
648
|
+
const id3 = await persistence.nextPacketId("client1");
|
|
649
|
+
|
|
650
|
+
assert(id1 >= 1 && id1 <= 65535);
|
|
651
|
+
assert(id2 >= 1 && id2 <= 65535);
|
|
652
|
+
assert(id3 >= 1 && id3 <= 65535);
|
|
653
|
+
assert(id1 !== id2 && id2 !== id3);
|
|
654
|
+
cleanup();
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
test("pendingOutgoing operations", async () => {
|
|
658
|
+
const { persistence, cleanup } = factory();
|
|
659
|
+
await persistence.registerClient("client1", () => Promise.resolve());
|
|
660
|
+
|
|
661
|
+
const packet = createPacket("test", "data", { id: 1, qos: 1 });
|
|
662
|
+
await persistence.addPendingOutgoingPacket("client1", packet);
|
|
663
|
+
// Register subscription in Trie first
|
|
664
|
+
await persistence.subscribe("client1", {
|
|
665
|
+
topicFilter: "test",
|
|
666
|
+
shareName: "",
|
|
667
|
+
qos: 1,
|
|
668
|
+
});
|
|
669
|
+
const packets = await Array.fromAsync(
|
|
670
|
+
persistence.listPendingOutgoingPackets("client1"),
|
|
671
|
+
);
|
|
672
|
+
assert.strictEqual(packets.length, 1);
|
|
673
|
+
assert.deepStrictEqual(packets[0].topic, packet.topic);
|
|
674
|
+
|
|
675
|
+
const deleted = await persistence.deletePendingOutgoingPacket(
|
|
676
|
+
"client1",
|
|
677
|
+
1,
|
|
678
|
+
);
|
|
679
|
+
assert(deleted);
|
|
680
|
+
|
|
681
|
+
const packetsPostDelete = await Array.fromAsync(
|
|
682
|
+
persistence.listPendingOutgoingPackets("client1"),
|
|
683
|
+
);
|
|
684
|
+
assert.strictEqual(packetsPostDelete.length, 0);
|
|
685
|
+
cleanup();
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
test("pendingIncoming operations", async () => {
|
|
689
|
+
const { persistence, cleanup } = factory();
|
|
690
|
+
await persistence.registerClient("client1", () => Promise.resolve());
|
|
691
|
+
|
|
692
|
+
const packet = createPacket("test", "data", { id: 1, qos: 1 });
|
|
693
|
+
await persistence.addPendingIncomingPacket("client1", packet);
|
|
694
|
+
|
|
695
|
+
const packets = await Array.fromAsync(
|
|
696
|
+
persistence.listPendingIncomingPackets("client1"),
|
|
697
|
+
);
|
|
698
|
+
assert.strictEqual(packets.length, 1);
|
|
699
|
+
assert.deepStrictEqual(packets[0].topic, packet.topic);
|
|
700
|
+
|
|
701
|
+
const deleted = await persistence.deletePendingIncomingPacket(
|
|
702
|
+
"client1",
|
|
703
|
+
1,
|
|
704
|
+
);
|
|
705
|
+
assert(deleted);
|
|
706
|
+
|
|
707
|
+
const packetsPostDelete = await Array.fromAsync(
|
|
708
|
+
persistence.listPendingIncomingPackets("client1"),
|
|
709
|
+
);
|
|
710
|
+
assert.strictEqual(packetsPostDelete.length, 0);
|
|
711
|
+
cleanup();
|
|
712
|
+
});
|
|
713
|
+
|
|
714
|
+
test("pendingAckOutgoing operations", async () => {
|
|
715
|
+
const { persistence, cleanup } = factory();
|
|
716
|
+
await persistence.registerClient("client1", () => Promise.resolve());
|
|
717
|
+
|
|
718
|
+
await persistence.addPendingAck("client1", 99);
|
|
719
|
+
assert(await persistence.hasPendingAck("client1", 99));
|
|
720
|
+
|
|
721
|
+
const acks = await Array.fromAsync(
|
|
722
|
+
persistence.listPendingAcks("client1"),
|
|
723
|
+
);
|
|
724
|
+
assert.deepStrictEqual(acks, [99]);
|
|
725
|
+
|
|
726
|
+
const deleted = await persistence.deletePendingAck("client1", 99);
|
|
727
|
+
assert(deleted);
|
|
728
|
+
assert(!(await persistence.hasPendingAck("client1", 99)));
|
|
729
|
+
cleanup();
|
|
730
|
+
});
|
|
731
|
+
});
|
|
732
|
+
|
|
733
|
+
// === Concurrency Tests ===
|
|
734
|
+
|
|
735
|
+
describe("Concurrency Tests", () => {
|
|
736
|
+
test(`${name} - multiple clients publishing simultaneously`, async () => {
|
|
737
|
+
const { persistence, cleanup } = factory();
|
|
738
|
+
const allReceived: ExtPublishPacket[][] = [];
|
|
739
|
+
|
|
740
|
+
// Create 10 clients
|
|
741
|
+
for (let i = 0; i < 10; i++) {
|
|
742
|
+
const { received } = await createReceiver(
|
|
743
|
+
persistence,
|
|
744
|
+
`client${i}`,
|
|
745
|
+
);
|
|
746
|
+
allReceived.push(received);
|
|
747
|
+
await persistence.subscribe(`client${i}`, {
|
|
748
|
+
topicFilter: "broadcast",
|
|
749
|
+
shareName: "",
|
|
750
|
+
qos: 0,
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
// Publish 10 messages
|
|
755
|
+
const publishPromises: Promise<void>[] = [];
|
|
756
|
+
for (let i = 0; i < 10; i++) {
|
|
757
|
+
publishPromises.push(
|
|
758
|
+
persistence.publish(
|
|
759
|
+
"sender",
|
|
760
|
+
createPacket("broadcast", `msg-${i}`),
|
|
761
|
+
),
|
|
762
|
+
);
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
await Promise.all(publishPromises);
|
|
766
|
+
|
|
767
|
+
// Each client should receive all 10 messages
|
|
768
|
+
for (const received of allReceived) {
|
|
769
|
+
assert.strictEqual(received.length, 10);
|
|
770
|
+
}
|
|
771
|
+
cleanup();
|
|
772
|
+
});
|
|
773
|
+
|
|
774
|
+
test(`${name} - concurrent subscribe and publish`, async () => {
|
|
775
|
+
const { persistence, cleanup } = factory();
|
|
776
|
+
await persistence.registerClient("client1", () => Promise.resolve());
|
|
777
|
+
|
|
778
|
+
const operations: Promise<void>[] = [];
|
|
779
|
+
|
|
780
|
+
// Interleave subscribes and publishes
|
|
781
|
+
for (let i = 0; i < 20; i++) {
|
|
782
|
+
operations.push(
|
|
783
|
+
persistence.subscribe("client1", {
|
|
784
|
+
topicFilter: `topic/${i}`,
|
|
785
|
+
shareName: "",
|
|
786
|
+
qos: 0,
|
|
787
|
+
}),
|
|
788
|
+
);
|
|
789
|
+
operations.push(
|
|
790
|
+
persistence.publish(
|
|
791
|
+
"client1",
|
|
792
|
+
createPacket(`topic/${i}`, `data${i}`),
|
|
793
|
+
),
|
|
794
|
+
);
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
await Promise.all(operations);
|
|
798
|
+
|
|
799
|
+
const subs = await Array.fromAsync(
|
|
800
|
+
persistence.listSubscriptions("client1"),
|
|
801
|
+
);
|
|
802
|
+
assert(subs.length <= 20);
|
|
803
|
+
cleanup();
|
|
804
|
+
});
|
|
805
|
+
|
|
806
|
+
test(`${name} - client register/deregister under load`, async () => {
|
|
807
|
+
const { persistence, cleanup } = factory();
|
|
808
|
+
|
|
809
|
+
const operations: Promise<void>[] = [];
|
|
810
|
+
for (let cycle = 0; cycle < 5; cycle++) {
|
|
811
|
+
for (let i = 0; i < 10; i++) {
|
|
812
|
+
operations.push(
|
|
813
|
+
(async () => {
|
|
814
|
+
await persistence.registerClient(
|
|
815
|
+
`client-${cycle}-${i}`,
|
|
816
|
+
() => Promise.resolve(),
|
|
817
|
+
);
|
|
818
|
+
await persistence.subscribe(`client-${cycle}-${i}`, {
|
|
819
|
+
topicFilter: "test/#",
|
|
820
|
+
shareName: "",
|
|
821
|
+
qos: 0,
|
|
822
|
+
});
|
|
823
|
+
await persistence.deregisterClient(`client-${cycle}-${i}`);
|
|
824
|
+
})(),
|
|
825
|
+
);
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
await Promise.all(operations);
|
|
830
|
+
cleanup();
|
|
831
|
+
});
|
|
832
|
+
});
|
|
833
|
+
|
|
834
|
+
// === Packet ID Edge Cases ===
|
|
835
|
+
|
|
836
|
+
describe("Packet ID Edge Cases", () => {
|
|
837
|
+
test(`${name} - nextPacketId returns IDs in valid range`, async () => {
|
|
838
|
+
const { persistence, cleanup } = factory();
|
|
839
|
+
await persistence.registerClient("client1", () => Promise.resolve());
|
|
840
|
+
|
|
841
|
+
// Generate several IDs and verify they're all in valid range
|
|
842
|
+
for (let i = 0; i < 10; i++) {
|
|
843
|
+
const id = await persistence.nextPacketId("client1");
|
|
844
|
+
assert(id >= 1 && id <= 65535, `ID ${id} should be in range 1-65535`);
|
|
845
|
+
}
|
|
846
|
+
cleanup();
|
|
847
|
+
});
|
|
848
|
+
|
|
849
|
+
test(`${name} - nextPacketId returns unique IDs`, async () => {
|
|
850
|
+
const { persistence, cleanup } = factory();
|
|
851
|
+
await persistence.registerClient("client1", () => Promise.resolve());
|
|
852
|
+
|
|
853
|
+
const ids = new Set<number>();
|
|
854
|
+
for (let i = 0; i < 100; i++) {
|
|
855
|
+
const id = await persistence.nextPacketId("client1");
|
|
856
|
+
assert(!ids.has(id), `ID ${id} should be unique`);
|
|
857
|
+
ids.add(id);
|
|
858
|
+
}
|
|
859
|
+
cleanup();
|
|
860
|
+
});
|
|
861
|
+
|
|
862
|
+
test(`${name} - nextPacketId skips IDs in pendingAckOutgoing`, async () => {
|
|
863
|
+
const { persistence, cleanup } = factory();
|
|
864
|
+
await persistence.registerClient("client1", () => Promise.resolve());
|
|
865
|
+
|
|
866
|
+
// get 2 ids to figure out what the next one will be
|
|
867
|
+
await persistence.nextPacketId("client1");
|
|
868
|
+
const targetSkippedId = await persistence.nextPacketId("client1");
|
|
869
|
+
|
|
870
|
+
// add the ack
|
|
871
|
+
await persistence.addPendingAck("client1", targetSkippedId);
|
|
872
|
+
|
|
873
|
+
// simulate session reset, the ack should still be there
|
|
874
|
+
await persistence.registerClient("client1", () => Promise.resolve());
|
|
875
|
+
|
|
876
|
+
// generate a bunch of idś the target should be skipped
|
|
877
|
+
const generatedIds = new Set<number>();
|
|
878
|
+
for (let i = 0; i < 5; i++) {
|
|
879
|
+
const id = await persistence.nextPacketId("client1");
|
|
880
|
+
generatedIds.add(id);
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
assert.strictEqual(
|
|
884
|
+
generatedIds.has(targetSkippedId),
|
|
885
|
+
false,
|
|
886
|
+
`Packet-ID ${targetSkippedId} had overgeslagen moeten worden omdat deze in pendingAckOutgoing staat`,
|
|
887
|
+
);
|
|
888
|
+
cleanup();
|
|
889
|
+
});
|
|
890
|
+
|
|
891
|
+
test(`${name} - nextPacketId skips IDs in pendingOutgoingPackets`, async () => {
|
|
892
|
+
const { persistence, cleanup } = factory();
|
|
893
|
+
await persistence.registerClient("client1", () => Promise.resolve());
|
|
894
|
+
|
|
895
|
+
// generate an id
|
|
896
|
+
const nextId = await persistence.nextPacketId("client1");
|
|
897
|
+
|
|
898
|
+
// put a packet wih this id in the pendingOutgoingPackets queue
|
|
899
|
+
const blockedPacket = createPacket("test/topic", "payload", {
|
|
900
|
+
id: nextId,
|
|
901
|
+
qos: 1,
|
|
902
|
+
});
|
|
903
|
+
await persistence.addPendingOutgoingPacket("client1", blockedPacket);
|
|
904
|
+
|
|
905
|
+
// Simulate a client-reconnect which might incorrectly reset the id counter
|
|
906
|
+
await persistence.registerClient("client1", () => Promise.resolve());
|
|
907
|
+
|
|
908
|
+
// requiest a new set of id's it should not reuse previous id's
|
|
909
|
+
const generatedIds = new Set<number>();
|
|
910
|
+
for (let i = 0; i < 5; i++) {
|
|
911
|
+
const id = await persistence.nextPacketId("client1");
|
|
912
|
+
generatedIds.add(id);
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
assert.strictEqual(
|
|
916
|
+
generatedIds.has(nextId),
|
|
917
|
+
false,
|
|
918
|
+
`Packet-ID ${nextId} should have been sikpped because it is still in pendingOutgoingPackets`,
|
|
919
|
+
);
|
|
920
|
+
cleanup();
|
|
921
|
+
});
|
|
922
|
+
});
|
|
923
|
+
|
|
924
|
+
// === Large Payload Tests ===
|
|
925
|
+
|
|
926
|
+
describe("Large Payload Tests", () => {
|
|
927
|
+
test(`${name} - handles large payloads`, async () => {
|
|
928
|
+
const { persistence, cleanup } = factory();
|
|
929
|
+
const { received } = await createReceiver(persistence, "client1");
|
|
930
|
+
await persistence.subscribe("client1", {
|
|
931
|
+
topicFilter: "large",
|
|
932
|
+
shareName: "",
|
|
933
|
+
qos: 0,
|
|
934
|
+
});
|
|
935
|
+
|
|
936
|
+
// 1MB payload
|
|
937
|
+
const largePayload = new Uint8Array(1024 * 1024);
|
|
938
|
+
for (let i = 0; i < largePayload.length; i++) {
|
|
939
|
+
largePayload[i] = i % 256;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
const packet: ExtPublishPacket = {
|
|
943
|
+
type: PacketType.publish,
|
|
944
|
+
protocolLevel: MQTTLevel.v5,
|
|
945
|
+
topic: "large",
|
|
946
|
+
payload: largePayload,
|
|
947
|
+
qos: 0,
|
|
948
|
+
};
|
|
949
|
+
|
|
950
|
+
await persistence.publish("sender", packet);
|
|
951
|
+
|
|
952
|
+
assert.strictEqual(received.length, 1);
|
|
953
|
+
assert.strictEqual(received[0].payload?.length, 1024 * 1024);
|
|
954
|
+
cleanup();
|
|
955
|
+
});
|
|
956
|
+
|
|
957
|
+
test(`${name} - retained message with large payload`, async () => {
|
|
958
|
+
const { persistence, cleanup } = factory();
|
|
959
|
+
|
|
960
|
+
const largePayload = new Uint8Array(100 * 1024); // 100KB
|
|
961
|
+
const packet: ExtPublishPacket = {
|
|
962
|
+
type: PacketType.publish,
|
|
963
|
+
protocolLevel: MQTTLevel.v5,
|
|
964
|
+
topic: "large/retained",
|
|
965
|
+
payload: largePayload,
|
|
966
|
+
qos: 0,
|
|
967
|
+
retain: true,
|
|
968
|
+
};
|
|
969
|
+
|
|
970
|
+
await persistence.publish("sender", packet);
|
|
971
|
+
|
|
972
|
+
const { received } = await createReceiver(
|
|
973
|
+
persistence,
|
|
974
|
+
"client1",
|
|
975
|
+
);
|
|
976
|
+
// Register subscription in Trie first
|
|
977
|
+
await persistence.subscribe("client1", {
|
|
978
|
+
topicFilter: "large/retained",
|
|
979
|
+
shareName: "",
|
|
980
|
+
qos: 0,
|
|
981
|
+
});
|
|
982
|
+
await persistence.handleRetained("client1", [{
|
|
983
|
+
topicFilter: "large/retained",
|
|
984
|
+
shareName: "",
|
|
985
|
+
qos: 0,
|
|
986
|
+
}]);
|
|
987
|
+
assert.strictEqual(received.length, 1);
|
|
988
|
+
|
|
989
|
+
assert.strictEqual(received[0]?.payload?.length, 100 * 1024);
|
|
990
|
+
cleanup();
|
|
991
|
+
});
|
|
992
|
+
});
|
|
993
|
+
|
|
994
|
+
// === Wildcard Edge Cases ===
|
|
995
|
+
|
|
996
|
+
describe("Wildcard Edge Cases", () => {
|
|
997
|
+
test(`${name} - # at root matches all topics`, async () => {
|
|
998
|
+
const { persistence, cleanup } = factory();
|
|
999
|
+
const { received } = await createReceiver(persistence, "client1");
|
|
1000
|
+
|
|
1001
|
+
await persistence.subscribe("client1", {
|
|
1002
|
+
topicFilter: "#",
|
|
1003
|
+
shareName: "",
|
|
1004
|
+
qos: 0,
|
|
1005
|
+
});
|
|
1006
|
+
|
|
1007
|
+
await persistence.publish("sender", createPacket("a", "1"));
|
|
1008
|
+
await persistence.publish("sender", createPacket("a/b", "2"));
|
|
1009
|
+
await persistence.publish("sender", createPacket("a/b/c", "3"));
|
|
1010
|
+
|
|
1011
|
+
assert.strictEqual(received.length, 3);
|
|
1012
|
+
cleanup();
|
|
1013
|
+
});
|
|
1014
|
+
|
|
1015
|
+
test(
|
|
1016
|
+
`${name} - # at root does not match $`,
|
|
1017
|
+
async () => {
|
|
1018
|
+
const { persistence, cleanup } = factory();
|
|
1019
|
+
const { received } = await createReceiver(
|
|
1020
|
+
persistence,
|
|
1021
|
+
"client1",
|
|
1022
|
+
);
|
|
1023
|
+
|
|
1024
|
+
await persistence.subscribe("client1", {
|
|
1025
|
+
topicFilter: "+/+",
|
|
1026
|
+
shareName: "",
|
|
1027
|
+
qos: 0,
|
|
1028
|
+
});
|
|
1029
|
+
await persistence.subscribe("client1", {
|
|
1030
|
+
topicFilter: "#",
|
|
1031
|
+
shareName: "",
|
|
1032
|
+
qos: 0,
|
|
1033
|
+
});
|
|
1034
|
+
|
|
1035
|
+
await persistence.publish("sender", createPacket("$topic", "1"));
|
|
1036
|
+
assert.strictEqual(received.length, 0);
|
|
1037
|
+
cleanup();
|
|
1038
|
+
},
|
|
1039
|
+
);
|
|
1040
|
+
|
|
1041
|
+
test(`${name} - + matches single level including empty`, async () => {
|
|
1042
|
+
const { persistence, cleanup } = factory();
|
|
1043
|
+
const { received } = await createReceiver(persistence, "client1");
|
|
1044
|
+
|
|
1045
|
+
await persistence.subscribe("client1", {
|
|
1046
|
+
topicFilter: "a/+/c",
|
|
1047
|
+
shareName: "",
|
|
1048
|
+
qos: 0,
|
|
1049
|
+
});
|
|
1050
|
+
|
|
1051
|
+
await persistence.publish("sender", createPacket("a/b/c", "1"));
|
|
1052
|
+
await persistence.publish("sender", createPacket("a//c", "2")); // Empty middle level - still matches
|
|
1053
|
+
|
|
1054
|
+
// Both should match: + matches any single level (including empty string)
|
|
1055
|
+
assert.strictEqual(received.length, 2);
|
|
1056
|
+
cleanup();
|
|
1057
|
+
});
|
|
1058
|
+
|
|
1059
|
+
test(`${name} - multiple # subscriptions from same client`, async () => {
|
|
1060
|
+
const { persistence, cleanup } = factory();
|
|
1061
|
+
const { received } = await createReceiver(persistence, "client1");
|
|
1062
|
+
|
|
1063
|
+
await persistence.subscribe("client1", {
|
|
1064
|
+
topicFilter: "a/#",
|
|
1065
|
+
shareName: "",
|
|
1066
|
+
qos: 0,
|
|
1067
|
+
});
|
|
1068
|
+
await persistence.subscribe("client1", {
|
|
1069
|
+
topicFilter: "a/b/#",
|
|
1070
|
+
shareName: "",
|
|
1071
|
+
qos: 1,
|
|
1072
|
+
});
|
|
1073
|
+
|
|
1074
|
+
await persistence.publish(
|
|
1075
|
+
"sender",
|
|
1076
|
+
createPacket("a/b/c", "data", { qos: 1 }),
|
|
1077
|
+
);
|
|
1078
|
+
|
|
1079
|
+
// Should deduplicate and use highest QoS
|
|
1080
|
+
assert.strictEqual(received.length, 1);
|
|
1081
|
+
assert.strictEqual(received[0].qos, 1);
|
|
1082
|
+
cleanup();
|
|
1083
|
+
});
|
|
1084
|
+
});
|
|
1085
|
+
// === NoLocal tests , V5 specific feature ===
|
|
1086
|
+
|
|
1087
|
+
describe("NoLocal Cases (V5 feature)", () => {
|
|
1088
|
+
test(`${name} - MQTT v5 - noLocal prevents forwarding own publications`, async () => {
|
|
1089
|
+
const { persistence, cleanup } = factory();
|
|
1090
|
+
const { received } = await createReceiver(persistence, "client1");
|
|
1091
|
+
|
|
1092
|
+
// Subscribe with noLocal = true
|
|
1093
|
+
await persistence.subscribe(
|
|
1094
|
+
"client1",
|
|
1095
|
+
{
|
|
1096
|
+
topicFilter: "chat/room1",
|
|
1097
|
+
shareName: "",
|
|
1098
|
+
qos: 0,
|
|
1099
|
+
noLocal: true,
|
|
1100
|
+
},
|
|
1101
|
+
);
|
|
1102
|
+
|
|
1103
|
+
// Client 1 publishes a message to the channel
|
|
1104
|
+
await persistence.publish(
|
|
1105
|
+
"client1", // publisherClientId
|
|
1106
|
+
createPacket("chat/room1", "hello from myself", {
|
|
1107
|
+
protocolLevel: MQTTLevel.v5,
|
|
1108
|
+
}),
|
|
1109
|
+
);
|
|
1110
|
+
|
|
1111
|
+
// Client 1 should NOT receive its own message
|
|
1112
|
+
assert.strictEqual(received.length, 0);
|
|
1113
|
+
cleanup();
|
|
1114
|
+
});
|
|
1115
|
+
|
|
1116
|
+
test(`${name} - MQTT v5 - noLocal still allows forwarding other clients' publications`, async () => {
|
|
1117
|
+
const { persistence, cleanup } = factory();
|
|
1118
|
+
const { received } = await createReceiver(persistence, "client1");
|
|
1119
|
+
|
|
1120
|
+
await persistence.subscribe("client1", {
|
|
1121
|
+
topicFilter: "chat/room1",
|
|
1122
|
+
shareName: "",
|
|
1123
|
+
qos: 0,
|
|
1124
|
+
noLocal: true,
|
|
1125
|
+
});
|
|
1126
|
+
|
|
1127
|
+
// Client 2 publishes a message
|
|
1128
|
+
await persistence.publish(
|
|
1129
|
+
"client2", // different publisher
|
|
1130
|
+
createPacket("chat/room1", "hello from client2", {
|
|
1131
|
+
protocolLevel: MQTTLevel.v5,
|
|
1132
|
+
}),
|
|
1133
|
+
);
|
|
1134
|
+
|
|
1135
|
+
// Client 1 SHOULD receive this message
|
|
1136
|
+
assert.strictEqual(received.length, 1);
|
|
1137
|
+
assert.strictEqual(
|
|
1138
|
+
received[0].payload ? utf8Decoder.decode(received[0].payload) : "",
|
|
1139
|
+
"hello from client2",
|
|
1140
|
+
);
|
|
1141
|
+
cleanup();
|
|
1142
|
+
});
|
|
1143
|
+
});
|
|
1144
|
+
|
|
1145
|
+
// === Retain as Published , V5 specific feature ===
|
|
1146
|
+
describe("Retain As Published (V5 feature)", () => {
|
|
1147
|
+
test(`${name} - MQTT v5 retainAsPublished=false (default) clears the retain flag on delivery`, async () => {
|
|
1148
|
+
const { persistence, cleanup } = factory();
|
|
1149
|
+
const { received } = await createReceiver(persistence, "client1");
|
|
1150
|
+
|
|
1151
|
+
// Subscribe with retainAsPublished = false (default behavior)
|
|
1152
|
+
await persistence.subscribe("client1", {
|
|
1153
|
+
topicFilter: "status/update",
|
|
1154
|
+
shareName: "",
|
|
1155
|
+
qos: 0,
|
|
1156
|
+
noLocal: false,
|
|
1157
|
+
retainAsPublished: false,
|
|
1158
|
+
});
|
|
1159
|
+
|
|
1160
|
+
await persistence.publish(
|
|
1161
|
+
"publisher",
|
|
1162
|
+
createPacket("status/update", "online", {
|
|
1163
|
+
retain: true,
|
|
1164
|
+
protocolLevel: MQTTLevel.v5,
|
|
1165
|
+
}),
|
|
1166
|
+
);
|
|
1167
|
+
|
|
1168
|
+
assert.strictEqual(received.length, 1);
|
|
1169
|
+
// The forwarded packet MUST have retain set to false
|
|
1170
|
+
assert.strictEqual(received[0].retain, false);
|
|
1171
|
+
cleanup();
|
|
1172
|
+
});
|
|
1173
|
+
|
|
1174
|
+
test(`${name} - MQTT v5 - retainAsPublished=true preserves the retain flag on delivery`, async () => {
|
|
1175
|
+
const { persistence, cleanup } = factory();
|
|
1176
|
+
const { received } = await createReceiver(persistence, "client1");
|
|
1177
|
+
|
|
1178
|
+
// Subscribe with retainAsPublished = true
|
|
1179
|
+
await persistence.subscribe("client1", {
|
|
1180
|
+
topicFilter: "status/update",
|
|
1181
|
+
shareName: "",
|
|
1182
|
+
qos: 0,
|
|
1183
|
+
noLocal: false,
|
|
1184
|
+
retainAsPublished: true,
|
|
1185
|
+
});
|
|
1186
|
+
|
|
1187
|
+
await persistence.publish(
|
|
1188
|
+
"publisher",
|
|
1189
|
+
createPacket("status/update", "online", {
|
|
1190
|
+
retain: true,
|
|
1191
|
+
protocolLevel: MQTTLevel.v5,
|
|
1192
|
+
}),
|
|
1193
|
+
);
|
|
1194
|
+
|
|
1195
|
+
assert.strictEqual(received.length, 1);
|
|
1196
|
+
// The forwarded packet MUST retain its original retain flag
|
|
1197
|
+
assert.strictEqual(received[0].retain, true);
|
|
1198
|
+
cleanup();
|
|
1199
|
+
});
|
|
1200
|
+
});
|
|
1201
|
+
|
|
1202
|
+
// === Subscription Identifier, V5 specific feature ===
|
|
1203
|
+
describe("Subscription Identifier (V5 feature)", () => {
|
|
1204
|
+
test(`${name} - MQTT v5 - subscriptionIdentifier is attached to outgoing packets on MQTT v5`, async () => {
|
|
1205
|
+
const { persistence, cleanup } = factory();
|
|
1206
|
+
const { received } = await createReceiver(persistence, "client1");
|
|
1207
|
+
|
|
1208
|
+
// Subscribe with subscription identifier = 42
|
|
1209
|
+
const subscriptionData: ClientSubscription = {
|
|
1210
|
+
topicFilter: "sensor/temp",
|
|
1211
|
+
shareName: "",
|
|
1212
|
+
qos: 0,
|
|
1213
|
+
noLocal: false,
|
|
1214
|
+
retainAsPublished: false,
|
|
1215
|
+
retainHandling: 0,
|
|
1216
|
+
subscriptionIdentifier: 42,
|
|
1217
|
+
};
|
|
1218
|
+
await persistence.subscribe(
|
|
1219
|
+
"client1",
|
|
1220
|
+
subscriptionData,
|
|
1221
|
+
);
|
|
1222
|
+
|
|
1223
|
+
await persistence.publish(
|
|
1224
|
+
"publisher",
|
|
1225
|
+
createPacket("sensor/temp", "24", { protocolLevel: MQTTLevel.v5 }),
|
|
1226
|
+
);
|
|
1227
|
+
|
|
1228
|
+
assert.strictEqual(received.length, 1);
|
|
1229
|
+
const packet = received[0] as PublishPacketV5;
|
|
1230
|
+
assert.deepStrictEqual(
|
|
1231
|
+
packet.properties?.subscriptionIdentifiers,
|
|
1232
|
+
[42],
|
|
1233
|
+
);
|
|
1234
|
+
cleanup();
|
|
1235
|
+
});
|
|
1236
|
+
});
|
|
1237
|
+
|
|
1238
|
+
test("Message expiry removes expired packets when listing pending packets", async () => {
|
|
1239
|
+
const { persistence, cleanup } = factory();
|
|
1240
|
+
await persistence.registerClient("client1", () => Promise.resolve());
|
|
1241
|
+
await persistence.subscribe("client1", {
|
|
1242
|
+
topicFilter: "test/topic",
|
|
1243
|
+
shareName: "",
|
|
1244
|
+
qos: 1,
|
|
1245
|
+
});
|
|
1246
|
+
|
|
1247
|
+
// Non-expired packet (expires far in the future)
|
|
1248
|
+
const validPacket = createPacket("test/topic", "valid payload", {
|
|
1249
|
+
id: 1,
|
|
1250
|
+
qos: 1,
|
|
1251
|
+
protocolLevel: MQTTLevel.v5,
|
|
1252
|
+
properties: { messageExpiryInterval: 60 },
|
|
1253
|
+
expiresAtMs: Date.now() + 60 * 1000,
|
|
1254
|
+
});
|
|
1255
|
+
|
|
1256
|
+
// Packet configured to expire almost immediately (1 second)
|
|
1257
|
+
const expiredPacket = createPacket("test/topic", "expired payload", {
|
|
1258
|
+
id: 2,
|
|
1259
|
+
qos: 1,
|
|
1260
|
+
protocolLevel: MQTTLevel.v5,
|
|
1261
|
+
properties: { messageExpiryInterval: 1 },
|
|
1262
|
+
expiresAtMs: Date.now() + 1000,
|
|
1263
|
+
});
|
|
1264
|
+
|
|
1265
|
+
// Add packets to outgoing and incoming pending queues
|
|
1266
|
+
await persistence.addPendingOutgoingPacket("client1", validPacket);
|
|
1267
|
+
await persistence.addPendingOutgoingPacket("client1", expiredPacket);
|
|
1268
|
+
|
|
1269
|
+
await persistence.addPendingIncomingPacket("client1", validPacket);
|
|
1270
|
+
await persistence.addPendingIncomingPacket("client1", expiredPacket);
|
|
1271
|
+
|
|
1272
|
+
// Wait for the short expiry interval to pass
|
|
1273
|
+
await new Promise((resolve) => setTimeout(resolve, 1100));
|
|
1274
|
+
|
|
1275
|
+
// Verify outgoing pending list filtered out expired message
|
|
1276
|
+
const outgoing = await Array.fromAsync(
|
|
1277
|
+
persistence.listPendingOutgoingPackets("client1"),
|
|
1278
|
+
);
|
|
1279
|
+
assert.strictEqual(outgoing.length, 1);
|
|
1280
|
+
assert.strictEqual(outgoing[0].id, 1);
|
|
1281
|
+
|
|
1282
|
+
// Verify incoming pending list filtered out expired message
|
|
1283
|
+
const incoming = await Array.fromAsync(
|
|
1284
|
+
persistence.listPendingIncomingPackets("client1"),
|
|
1285
|
+
);
|
|
1286
|
+
assert.strictEqual(incoming.length, 1);
|
|
1287
|
+
assert.strictEqual(incoming[0].id, 1);
|
|
1288
|
+
|
|
1289
|
+
cleanup();
|
|
1290
|
+
});
|
|
1291
|
+
|
|
1292
|
+
test("Outgoing packet dup update works", async () => {
|
|
1293
|
+
const { persistence, cleanup } = factory();
|
|
1294
|
+
const client = "client1";
|
|
1295
|
+
const id = 127;
|
|
1296
|
+
await persistence.registerClient(client, () => Promise.resolve());
|
|
1297
|
+
// Non-expired packet (expires far in the future)
|
|
1298
|
+
const dupPacket = createPacket("test/topic", "valid payload", {
|
|
1299
|
+
id,
|
|
1300
|
+
qos: 1,
|
|
1301
|
+
protocolLevel: MQTTLevel.v5,
|
|
1302
|
+
properties: { messageExpiryInterval: 60 },
|
|
1303
|
+
expiresAtMs: Date.now() + 60 * 1000,
|
|
1304
|
+
});
|
|
1305
|
+
|
|
1306
|
+
// Add packet to outgoing pending queue
|
|
1307
|
+
await persistence.addPendingOutgoingPacket(client, dupPacket);
|
|
1308
|
+
const packetNoDup = await persistence.getPendingOutgoingPacket(client, id);
|
|
1309
|
+
assert.strictEqual(packetNoDup?.dup ?? false, false);
|
|
1310
|
+
// Update the dup
|
|
1311
|
+
const result = await persistence.updatePendingOutgoingPacket(
|
|
1312
|
+
client,
|
|
1313
|
+
id,
|
|
1314
|
+
true,
|
|
1315
|
+
);
|
|
1316
|
+
assert.strictEqual(result, true);
|
|
1317
|
+
const packetWithDup = await persistence.getPendingOutgoingPacket(
|
|
1318
|
+
client,
|
|
1319
|
+
id,
|
|
1320
|
+
);
|
|
1321
|
+
assert.strictEqual(packetWithDup?.dup, true);
|
|
1322
|
+
cleanup();
|
|
1323
|
+
});
|
|
1324
|
+
}
|