@seriousme/opifex 1.6.2 → 1.6.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.
Files changed (77) hide show
  1. package/.github/dependabot.yml +7 -0
  2. package/.github/workflows/closeStale.yml +1 -1
  3. package/.github/workflows/codeql-analysis.yml +4 -4
  4. package/CHANGELOG.md +6 -0
  5. package/README.md +6 -7
  6. package/bin/conformanceServer.ts +67 -16
  7. package/bin/demoServer.ts +69 -14
  8. package/bin/mqtt.ts +30 -0
  9. package/client/ConnectionState.ts +12 -0
  10. package/client/client.ts +56 -2
  11. package/client/context.ts +5 -0
  12. package/client/handlers/handleConnack.ts +6 -0
  13. package/client/handlers/handlePacket.ts +7 -0
  14. package/client/handlers/handlePuback.ts +8 -2
  15. package/client/handlers/handlePubcomp.ts +10 -4
  16. package/client/handlers/handlePublish.ts +6 -2
  17. package/client/handlers/handlePubrec.ts +13 -6
  18. package/client/handlers/handlePubrel.ts +16 -6
  19. package/client/handlers/handleSuback.ts +9 -6
  20. package/client/handlers/handleUnsuback.ts +10 -3
  21. package/client/store/memoryStore.ts +25 -0
  22. package/client/store/store.ts +39 -0
  23. package/deno/tcpServer.test.ts +137 -2
  24. package/deno.json +2 -1
  25. package/dist/bin/conformanceServer.js +61 -15
  26. package/dist/bin/demoServer.js +63 -13
  27. package/dist/bin/mqtt.js +29 -0
  28. package/dist/client/ConnectionState.js +9 -0
  29. package/dist/client/client.js +56 -1
  30. package/dist/client/context.js +4 -0
  31. package/dist/client/handlers/handleConnack.js +6 -0
  32. package/dist/client/handlers/handlePacket.js +7 -0
  33. package/dist/client/handlers/handlePuback.js +8 -1
  34. package/dist/client/handlers/handlePubcomp.js +10 -3
  35. package/dist/client/handlers/handlePublish.js +6 -1
  36. package/dist/client/handlers/handlePubrec.js +13 -4
  37. package/dist/client/handlers/handlePubrel.js +16 -4
  38. package/dist/client/handlers/handleSuback.js +9 -4
  39. package/dist/client/handlers/handleUnsuback.js +10 -2
  40. package/dist/client/store/memoryStore.js +22 -0
  41. package/dist/client/store/store.js +3 -0
  42. package/dist/node/tcpServer.js +9 -2
  43. package/dist/node/wrapNodeSocket.js +9 -0
  44. package/dist/persistence/memory/memoryPersistence.js +1 -0
  45. package/dist/server/handlers/handleConnect.js +39 -5
  46. package/dist/server/handlers/handleDisconnect.js +5 -0
  47. package/dist/server/handlers/handlePacket.js +7 -0
  48. package/dist/server/handlers/handlePingreq.js +5 -0
  49. package/dist/server/handlers/handlePuback.js +9 -1
  50. package/dist/server/handlers/handlePubcomp.js +8 -2
  51. package/dist/server/handlers/handlePublish.js +13 -0
  52. package/dist/server/handlers/handlePubrec.js +11 -3
  53. package/dist/server/handlers/handlePubrel.js +12 -3
  54. package/dist/server/handlers/handleSubscribe.js +27 -4
  55. package/dist/server/handlers/handleUnsubscribe.js +6 -0
  56. package/dist/utils/asyncQueue.js +2 -0
  57. package/dist/utils/nextTick.js +13 -1
  58. package/node/tcpServer.test.ts +61 -6
  59. package/node/tcpServer.ts +9 -2
  60. package/node/wrapNodeSocket.ts +13 -2
  61. package/package.json +2 -2
  62. package/persistence/memory/memoryPersistence.test.ts +2 -1
  63. package/persistence/memory/memoryPersistence.ts +1 -0
  64. package/persistence/store.ts +1 -0
  65. package/server/handlers/handleConnect.ts +49 -6
  66. package/server/handlers/handleDisconnect.ts +5 -0
  67. package/server/handlers/handlePacket.ts +7 -0
  68. package/server/handlers/handlePingreq.ts +6 -0
  69. package/server/handlers/handlePuback.ts +9 -2
  70. package/server/handlers/handlePubcomp.ts +8 -3
  71. package/server/handlers/handlePublish.ts +13 -0
  72. package/server/handlers/handlePubrec.ts +11 -3
  73. package/server/handlers/handlePubrel.ts +12 -4
  74. package/server/handlers/handleSubscribe.ts +28 -5
  75. package/server/handlers/handleUnsubscribe.ts +6 -0
  76. package/utils/asyncQueue.ts +2 -1
  77. package/utils/nextTick.ts +13 -1
@@ -1,12 +1,22 @@
1
1
  import type { Context } from "../context.ts";
2
2
  import { PacketType, type PubrelPacket } from "../deps.ts";
3
3
 
4
- // A PUBREL Packet is the response to a PUBREC Packet.
5
- // It is the third packet of the QoS 2 protocol exchange.
6
-
7
- // Method A, Initiate onward delivery of the Application Message then discard message
8
- // Send PUBCOMP <Packet Identifier>
9
-
4
+ /**
5
+ * Handles a PUBREL packet in the MQTT QoS 2 flow
6
+ * It is the third packet of the QoS 2 protocol exchange
7
+ * and a response to the PUBREC packet
8
+ *
9
+ * It uses Method A from the MQTT specification
10
+ * @param ctx - The MQTT client context
11
+ * @param packet - The PUBREL packet received
12
+ * @description
13
+ * When a PUBREL packet is received:
14
+ * 1. If there is a matching pending incoming message:
15
+ * - Delivers the stored message to the application
16
+ * - Removes the message from pending incoming store
17
+ * - Sends PUBCOMP packet as acknowledgement
18
+ * 2. If no matching message found, silently ignores
19
+ */
10
20
  export async function handlePubrel(
11
21
  ctx: Context,
12
22
  packet: PubrelPacket,
@@ -1,12 +1,15 @@
1
1
  import type { Context } from "../context.ts";
2
2
  import type { SubackPacket } from "../deps.ts";
3
3
 
4
- // A SUBACK Packet is sent by the Server to the Client to confirm receipt
5
- // and processing of a SUBSCRIBE Packet.
6
-
7
- // A SUBACK Packet contains a list of return codes, that specify the maximum QoS level
8
- // that was granted in each Subscription that was requested by the SUBSCRIBE.
9
-
4
+ /**
5
+ * Handles SUBACK packets received from the server
6
+ * @param ctx - The MQTT client context
7
+ * @param packet - The SUBACK packet containing subscription confirmations
8
+ * @description
9
+ * - Processes SUBACK packets sent by server to confirm SUBSCRIBE requests
10
+ * - Removes the packet ID from pending outgoing messages
11
+ * - Notifies the client about granted QoS levels for each subscription
12
+ */
10
13
  export function handleSuback(ctx: Context, packet: SubackPacket): void {
11
14
  const id = packet.id;
12
15
  ctx.store.pendingOutgoing.delete(id);
@@ -1,9 +1,16 @@
1
1
  import type { Context } from "../context.ts";
2
2
  import type { UnsubackPacket } from "../deps.ts";
3
3
 
4
- // The UNSUBACK Packet is sent by the Server to the Client to confirm receipt
5
- // of an UNSUBSCRIBE Packet.
6
-
4
+ /**
5
+ * Handles the UNSUBACK packet received from the server
6
+ * @param ctx - The MQTT client context
7
+ * @param packet - The UNSUBACK packet received from the server
8
+ * @description
9
+ * The UNSUBACK Packet is sent by the Server to the Client to confirm receipt of
10
+ * an UNSUBSCRIBE Packet.
11
+ * When received, it removes the pending outgoing request and completes the corresponding
12
+ * unsubscribe operation.
13
+ */
7
14
  export function handleUnsuback(ctx: Context, packet: UnsubackPacket): void {
8
15
  const id = packet.id;
9
16
  ctx.store.pendingOutgoing.delete(id);
@@ -1,3 +1,7 @@
1
+ /**
2
+ * @module memoryStore
3
+ */
4
+
1
5
  import { assert, type PacketId } from "./deps.ts";
2
6
 
3
7
  import {
@@ -10,13 +14,25 @@ import {
10
14
  type PendingOutgoingPackets,
11
15
  } from "./store.ts";
12
16
 
17
+ /**
18
+ * In-memory implementation of the IStore interface for managing MQTT packet storage
19
+ */
13
20
  export class MemoryStore implements IStore {
21
+ /** Current packet ID counter */
14
22
  private packetId: PacketId;
15
23
 
24
+ /** Map of pending incoming packets */
16
25
  pendingIncoming: PacketStore<pendingIncoming>;
26
+
27
+ /** Map of pending outgoing packets */
17
28
  pendingOutgoing: PacketStore<PendingOutgoing>;
29
+
30
+ /** Map of pending acknowledgment outgoing packets */
18
31
  pendingAckOutgoing: PacketStore<PendingAckOutgoing>;
19
32
 
33
+ /**
34
+ * Creates a new MemoryStore instance
35
+ */
20
36
  constructor() {
21
37
  this.packetId = 0;
22
38
  this.pendingIncoming = new Map();
@@ -24,6 +40,11 @@ export class MemoryStore implements IStore {
24
40
  this.pendingAckOutgoing = new Map();
25
41
  }
26
42
 
43
+ /**
44
+ * Generates the next available packet ID
45
+ * @returns {PacketId} The next available packet ID
46
+ * @throws {Error} If no unused packet ID is available
47
+ */
27
48
  nextId(): PacketId {
28
49
  const currentId = this.packetId;
29
50
  do {
@@ -41,6 +62,10 @@ export class MemoryStore implements IStore {
41
62
  return this.packetId;
42
63
  }
43
64
 
65
+ /**
66
+ * Asynchronously yields all pending outgoing packets
67
+ * @yields {PendingOutgoingPackets} Pending outgoing packets
68
+ */
44
69
  async *pendingOutgoingPackets(): AsyncGenerator<
45
70
  PendingOutgoingPackets,
46
71
  void,
@@ -1,3 +1,6 @@
1
+ /**
2
+ * @module MQTT Store Types
3
+ */
1
4
  import type {
2
5
  PacketId,
3
6
  PublishPacket,
@@ -5,22 +8,58 @@ import type {
5
8
  SubscribePacket,
6
9
  UnsubscribePacket,
7
10
  } from "./deps.ts";
11
+
12
+ /**
13
+ * Maximum value for MQTT packet IDs (65535)
14
+ */
8
15
  export const maxPacketId = 0xffff;
9
16
 
17
+ /**
18
+ * Generic type for storing MQTT packets by their packet ID
19
+ * @template T The packet type to store
20
+ */
10
21
  export type PacketStore<T> = Map<PacketId, T>;
11
22
 
23
+ /**
24
+ * Type for incoming PUBLISH packets that are pending processing
25
+ */
12
26
  export type pendingIncoming = PublishPacket;
27
+
28
+ /**
29
+ * Type for outgoing packets (PUBLISH, SUBSCRIBE, UNSUBSCRIBE) that are pending sending
30
+ */
13
31
  export type PendingOutgoing =
14
32
  | PublishPacket
15
33
  | SubscribePacket
16
34
  | UnsubscribePacket;
35
+
36
+ /**
37
+ * Type for outgoing PUBREL packets that are pending acknowledgement
38
+ */
17
39
  export type PendingAckOutgoing = PubrelPacket;
40
+
41
+ /**
42
+ * Union type of all pending outgoing packet types
43
+ */
18
44
  export type PendingOutgoingPackets = PendingAckOutgoing | PendingOutgoing;
19
45
 
46
+ /**
47
+ * Interface defining the storage requirements for MQTT packet state management
48
+ */
20
49
  export interface IStore {
50
+ /** Store for incoming packets pending processing */
21
51
  pendingIncoming: PacketStore<pendingIncoming>;
52
+
53
+ /** Store for outgoing packets pending sending */
22
54
  pendingOutgoing: PacketStore<PendingOutgoing>;
55
+
56
+ /** Store for outgoing packets pending acknowledgement */
23
57
  pendingAckOutgoing: PacketStore<PendingAckOutgoing>;
58
+
59
+ /**
60
+ * Generator that yields pending outgoing packets
61
+ * @returns AsyncGenerator that yields PendingOutgoing or PubrelPacket packets
62
+ */
24
63
  pendingOutgoingPackets(): AsyncGenerator<
25
64
  PendingOutgoing | PubrelPacket,
26
65
  void,
@@ -1,14 +1,149 @@
1
- import assert from "node:assert/strict";
2
1
  import { test } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { TcpClient } from "./tcpClient.ts";
3
4
  import { TcpServer } from "./tcpServer.ts";
5
+ import { logger, LogLevel } from "../utils/mod.ts";
6
+ import type { PublishPacket, QoS } from "../mqttPacket/mod.ts";
4
7
 
5
- test("Deno testServer", () => {
8
+ logger.level(LogLevel.info);
9
+ export function sleep(ms: number): Promise<unknown> {
10
+ return new Promise((r) => setTimeout(r, ms));
11
+ }
12
+
13
+ test("Deno Test pubSub using client and server", async function () {
6
14
  const server = new TcpServer({ port: 0 }, {});
7
15
  server.start();
16
+
8
17
  assert.deepStrictEqual(
9
18
  server.port !== undefined,
10
19
  true,
11
20
  "server runs a a random port",
12
21
  );
22
+ logger.verbose({ port: server.port, address: server.address });
23
+
24
+ const params = {
25
+ url: new URL(`mqtt://${server.address}:${server.port}`),
26
+ numberOfRetries: 0,
27
+ };
28
+
29
+ const client = new TcpClient();
30
+
31
+ await client.connect(params);
32
+ assert(true, "Client connected to server");
33
+
34
+ const publishSet: { topic: string; qos: QoS }[] = [
35
+ { topic: "t0@q0", qos: 0 },
36
+ { topic: "t1@q0", qos: 0 },
37
+ { topic: "t2@q0", qos: 0 },
38
+ { topic: "t0@q1", qos: 1 },
39
+ { topic: "t1@q1", qos: 1 },
40
+ { topic: "t2@q1", qos: 1 },
41
+ { topic: "t0@q2", qos: 2 },
42
+ { topic: "t1@q2", qos: 2 },
43
+ { topic: "t2@q2", qos: 2 },
44
+ ];
45
+
46
+ const subscriptions = publishSet.map((item) => ({
47
+ topicFilter: item.topic,
48
+ qos: item.qos,
49
+ }));
50
+ await client.subscribe({
51
+ subscriptions,
52
+ });
53
+
54
+ // the IIFE ensures message reception runs in parallel
55
+ logger.verbose(`Start receiving`);
56
+ const received: PublishPacket[] = [];
57
+ (async function () {
58
+ for await (const item of client.messages()) {
59
+ logger.verbose(`Receiving: ${item.topic} -- ${item.qos}`);
60
+ received.push(item);
61
+ }
62
+ })();
63
+ // end of IIFE
64
+ for (const item of publishSet) {
65
+ logger.verbose(`Publishing: ${item.topic} -- ${item.qos}`);
66
+ await client.publish({
67
+ topic: item.topic,
68
+ qos: item.qos,
69
+ payload: new Uint8Array([0x01]),
70
+ });
71
+ }
72
+
73
+ await sleep(100);
74
+ logger.verbose(`Disconnect client`);
75
+ //client.closeMessages();
76
+ await client.disconnect();
77
+
78
+ logger.verbose(`Check completeness`);
79
+ for (const item of publishSet) {
80
+ const found = received.find((f) =>
81
+ f.topic == item.topic && f.qos === item.qos
82
+ );
83
+ logger.verbose(`Found: ${item.topic} -- ${item.qos}`);
84
+ assert(found, `${item.topic} -- ${item.qos}`);
85
+ }
86
+
87
+ logger.verbose(`Stop server`);
88
+ server.stop();
89
+ });
90
+
91
+ test("Deno Test subscription persistence after reconnect", async function () {
92
+ // Start server
93
+ const server = new TcpServer({ port: 0 }, {});
94
+ server.start();
95
+
96
+ const params = {
97
+ url: new URL(`mqtt://${server.address}:${server.port}`),
98
+ numberOfRetries: 0,
99
+ };
100
+
101
+ const client = new TcpClient();
102
+ const testTopic = "test/topic";
103
+ const received: PublishPacket[] = [];
104
+
105
+ // First connection and subscription
106
+ await client.connect(params);
107
+ await client.subscribe({
108
+ subscriptions: [{
109
+ topicFilter: testTopic,
110
+ qos: 0,
111
+ }],
112
+ });
113
+
114
+ // Start receiving messages
115
+ (async function () {
116
+ for await (const item of client.messages()) {
117
+ received.push(item);
118
+ }
119
+ })();
120
+
121
+ // Disconnect client
122
+ await client.disconnect();
123
+ await sleep(100);
124
+
125
+ // Reconnect client
126
+ await client.connect(params);
127
+ await sleep(100);
128
+
129
+ // Publish test message
130
+ await client.publish({
131
+ topic: testTopic,
132
+ qos: 0,
133
+ payload: new Uint8Array([0x01]),
134
+ });
135
+
136
+ await sleep(100);
137
+ logger.verbose(`Disconnect client`);
138
+ await client.disconnect();
139
+
140
+ // Verify message was received
141
+ assert.equal(received.length, 1, "Should receive one message");
142
+ assert.equal(
143
+ received[0].topic,
144
+ testTopic,
145
+ "Should receive message on subscribed topic",
146
+ );
147
+
13
148
  server.stop();
14
149
  });
package/deno.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@seriousme/opifex",
3
- "version": "1.6.2",
3
+ "version": "1.6.3",
4
+ "license": "MIT",
4
5
  "exports": {
5
6
  "./server": "./server/mod.ts",
6
7
  "./client": "./client/mod.ts",
@@ -1,45 +1,91 @@
1
1
  #!/usr/bin/env -S node --experimental-strip-types
2
2
  // this server is intended to be used with https://github.com/eclipse/iottestware
3
+ /**
4
+ * MQTT Server implementation with authentication and authorization
5
+ * @module mqttServer
6
+ */
7
+ import { TcpServer } from "../node/tcpServer.js";
3
8
  import { AuthenticationResult } from "../server/mod.js";
4
9
  import { getArgs, logger, LogLevel, parseArgs } from "../utils/mod.js";
5
- import { TcpServer } from "../node/tcpServer.js";
10
+ /**
11
+ * UTF-8 decoder for parsing passwords
12
+ */
6
13
  const utf8Decoder = new TextDecoder();
14
+ /**
15
+ * Map storing valid username/password combinations
16
+ */
7
17
  const userTable = new Map();
8
18
  userTable.set("IoTester_1", "strong_password");
9
19
  userTable.set("IoTester_2", "strong_password");
20
+ /**
21
+ * Regular expression for validating usernames
22
+ * Allows alphanumeric characters up to 23 chars long
23
+ */
10
24
  const strictUsername = new RegExp(/^[a-zA-Z0-9]{0,23}$/);
11
- const notAuthorizedTable = new Set();
12
- notAuthorizedTable.add(["123-456-789", "eclipse/iot/tesware/0data"]);
25
+ /**
26
+ * Flag to enable/disable username checking
27
+ * If false, all users are allowed access
28
+ * If true, only users in userTable are allowed access
29
+ */
30
+ const checkUsername = true;
31
+ /**
32
+ * Authenticates MQTT clients
33
+ * @param {Context} _ctx - Connection context
34
+ * @param {string} clientId - Client identifier
35
+ * @param {string} username - Username to authenticate
36
+ * @param {Uint8Array} password - Password as byte array
37
+ * @returns {TAuthenticationResult} Authentication result
38
+ */
13
39
  function isAuthenticated(_ctx, clientId, username, password) {
14
40
  const pwd = utf8Decoder.decode(password);
15
- logger.debug(`Verifying authentication of client '${clientId}' with username '${username}' and password '${pwd}'`);
16
- if (!userTable.has(username)) {
17
- if (!strictUsername.test(username)) {
18
- return AuthenticationResult.badUsernameOrPassword;
19
- }
20
- }
21
- const pass = userTable.get(username);
22
- if (pwd === pass) {
41
+ logger.info(`Verifying authentication of client '${clientId}' with username '${username}' and password '${pwd}'`);
42
+ if (!checkUsername) {
43
+ // allow all users access
23
44
  return AuthenticationResult.ok;
24
45
  }
46
+ // is the username valid according to MQTT specification
47
+ if (!strictUsername.test(username)) {
48
+ return AuthenticationResult.badUsernameOrPassword;
49
+ }
50
+ // Does user and password match an entry in userTable
51
+ if (userTable.has(username)) {
52
+ const pass = userTable.get(username);
53
+ if (pwd === pass) {
54
+ return AuthenticationResult.ok;
55
+ }
56
+ }
25
57
  return AuthenticationResult.notAuthorized;
26
58
  }
59
+ /**
60
+ * Checks if client is authorized to publish to a topic
61
+ * @param {Context} ctx - Connection context
62
+ * @param {Topic} topic - Topic to publish to
63
+ * @returns {boolean} True if authorized
64
+ */
27
65
  function isAuthorizedToPublish(ctx, topic) {
28
66
  logger.debug(`Checking authorization of client '${ctx.store?.clientId}' to publish on topic '${topic}'`);
29
- // if (notAuthorizedTable.has([ctx.client,topic])){
30
- // return false;
31
- // }
32
67
  return true;
33
68
  }
69
+ /**
70
+ * Checks if client is authorized to subscribe to a topic
71
+ * @param {Context} ctx - Connection context
72
+ * @param {Topic} topic - Topic to subscribe to
73
+ * @returns {boolean} True if authorized
74
+ */
34
75
  function isAuthorizedToSubscribe(ctx, topic) {
35
76
  logger.debug(`Checking authorization of client '${ctx.store?.clientId}' to subscribe to topic '${topic}'`);
36
77
  return true;
37
78
  }
38
- /** start the server **/
79
+ /**
80
+ * Parse command line arguments and start server
81
+ */
39
82
  const { _: [portNum] } = parseArgs(getArgs());
40
83
  const port = Number(portNum ?? 1883);
41
84
  const hostname = "::";
42
85
  logger.level(LogLevel.info);
86
+ /**
87
+ * TCP Server instance with authentication and authorization handlers
88
+ */
43
89
  const tcpServer = new TcpServer({ port, hostname }, {
44
90
  handlers: {
45
91
  isAuthenticated,
@@ -1,40 +1,90 @@
1
1
  #!/usr/bin/env -S node --experimental-strip-types
2
+ /**
3
+ * MQTT Server implementation with authentication and authorization
4
+ * @module mqttServer
5
+ */
2
6
  import { TcpServer } from "../node/tcpServer.js";
3
7
  import { AuthenticationResult } from "../server/mod.js";
4
8
  import { getArgs, logger, LogLevel, parseArgs } from "../utils/mod.js";
9
+ /**
10
+ * UTF-8 decoder for parsing passwords
11
+ */
5
12
  const utf8Decoder = new TextDecoder();
13
+ /**
14
+ * Map storing valid username/password combinations
15
+ */
6
16
  const userTable = new Map();
7
17
  userTable.set("IoTester_1", "strong_password");
8
18
  userTable.set("IoTester_2", "strong_password");
9
- // const strictUsername = new RegExp(/^[a-zA-Z0-9]{0,23}$/);
19
+ /**
20
+ * Regular expression for validating usernames
21
+ * Allows alphanumeric characters up to 23 chars long
22
+ */
23
+ const strictUsername = new RegExp(/^[a-zA-Z0-9]{0,23}$/);
24
+ /**
25
+ * Flag to enable/disable username checking
26
+ * If false, all users are allowed access
27
+ * If true, only users in userTable are allowed access
28
+ */
29
+ const checkUsername = false;
30
+ /**
31
+ * Authenticates MQTT clients
32
+ * @param {Context} _ctx - Connection context
33
+ * @param {string} clientId - Client identifier
34
+ * @param {string} username - Username to authenticate
35
+ * @param {Uint8Array} password - Password as byte array
36
+ * @returns {TAuthenticationResult} Authentication result
37
+ */
10
38
  function isAuthenticated(_ctx, clientId, username, password) {
11
39
  const pwd = utf8Decoder.decode(password);
12
40
  logger.info(`Verifying authentication of client '${clientId}' with username '${username}' and password '${pwd}'`);
13
- return AuthenticationResult.ok;
14
- // if (!userTable.has(username)) {
15
- // if (!strictUsername.test(username)) {
16
- // return AuthenticationResult.badUsernameOrPassword;
17
- // }
18
- // }
19
- // const pass = userTable.get(username);
20
- // if (pwd === pass) {
21
- // return AuthenticationResult.ok;
22
- // }
23
- // return AuthenticationResult.badUsernameOrPassword;
41
+ if (!checkUsername) {
42
+ // allow all users access
43
+ return AuthenticationResult.ok;
44
+ }
45
+ // is the username valid according to MQTT specification
46
+ if (!strictUsername.test(username)) {
47
+ return AuthenticationResult.badUsernameOrPassword;
48
+ }
49
+ // Does user and password match an entry in userTable
50
+ if (userTable.has(username)) {
51
+ const pass = userTable.get(username);
52
+ if (pwd === pass) {
53
+ return AuthenticationResult.ok;
54
+ }
55
+ }
56
+ return AuthenticationResult.notAuthorized;
24
57
  }
58
+ /**
59
+ * Checks if client is authorized to publish to a topic
60
+ * @param {Context} ctx - Connection context
61
+ * @param {Topic} topic - Topic to publish to
62
+ * @returns {boolean} True if authorized
63
+ */
25
64
  function isAuthorizedToPublish(ctx, topic) {
26
65
  logger.debug(`Checking authorization of client '${ctx.store?.clientId}' to publish on topic '${topic}'`);
27
66
  return true;
28
67
  }
68
+ /**
69
+ * Checks if client is authorized to subscribe to a topic
70
+ * @param {Context} ctx - Connection context
71
+ * @param {Topic} topic - Topic to subscribe to
72
+ * @returns {boolean} True if authorized
73
+ */
29
74
  function isAuthorizedToSubscribe(ctx, topic) {
30
75
  logger.debug(`Checking authorization of client '${ctx.store?.clientId}' to subscribe to topic '${topic}'`);
31
76
  return true;
32
77
  }
33
- /** start the server **/
78
+ /**
79
+ * Parse command line arguments and start server
80
+ */
34
81
  const { _: [portNum] } = parseArgs(getArgs());
35
82
  const port = Number(portNum ?? 1883);
36
83
  const hostname = "::";
37
84
  logger.level(LogLevel.info);
85
+ /**
86
+ * TCP Server instance with authentication and authorization handlers
87
+ */
38
88
  const tcpServer = new TcpServer({ port, hostname }, {
39
89
  handlers: {
40
90
  isAuthenticated,
package/dist/bin/mqtt.js CHANGED
@@ -1,4 +1,33 @@
1
1
  #!/usr/bin/env -S node --experimental-strip-types
2
+ /**
3
+ * @fileoverview MQTT command line interface that provides publish and subscribe functionality
4
+ * @module mqtt-cli
5
+ *
6
+ * @description
7
+ * This module implements a command line interface for MQTT operations:
8
+ * - Publish messages to an MQTT broker
9
+ * - Subscribe to topics on an MQTT broker
10
+ *
11
+ * Features:
12
+ * - TLS support with custom certificates
13
+ * - Authentication with username/password
14
+ * - Configurable QoS levels (0-2)
15
+ * - Message retention
16
+ * - Session persistence
17
+ * - Custom client IDs
18
+ *
19
+ * @example
20
+ * # Publish a message
21
+ * mqtt publish -t topic -m message
22
+ *
23
+ * # Subscribe to a topic
24
+ * mqtt subscribe -t topic
25
+ *
26
+ * @requires node
27
+ * @requires ../client/mod.ts
28
+ * @requires ../node/tcpClient.ts
29
+ * @requires ../utils/mod.ts
30
+ */
2
31
  import { DEFAULT_URL } from "../client/mod.js";
3
32
  import { getFileData, TcpClient } from "../node/tcpClient.js";
4
33
  import { getArgs, logger, LogLevel, parseArgs } from "../utils/mod.js";
@@ -1,7 +1,16 @@
1
+ /**
2
+ * @description Object representing possible connection states
3
+ * @readonly
4
+ */
1
5
  export const ConnectionState = {
6
+ /** System is offline and not attempting connection */
2
7
  offline: "offline",
8
+ /** System is in the process of establishing a connection */
3
9
  connecting: "connecting",
10
+ /** System has an active connection */
4
11
  connected: "connected",
12
+ /** System is in the process of disconnecting */
5
13
  disconnecting: "disconnecting",
14
+ /** System has been disconnected */
6
15
  disconnected: "disconnected",
7
16
  };