@seriousme/opifex 1.1.0

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 (120) hide show
  1. package/.github/workflows/closeStale.yml +15 -0
  2. package/.github/workflows/codeql-analysis.yml +71 -0
  3. package/.github/workflows/deno-ci.yml +16 -0
  4. package/.github/workflows/nodejs-ci.yml +23 -0
  5. package/.github/workflows/publish.yml +16 -0
  6. package/.vscode/extensions.json +10 -0
  7. package/.vscode/settings.json +12 -0
  8. package/LICENSE.txt +21 -0
  9. package/README.md +34 -0
  10. package/bin/conformanceServer.ts +70 -0
  11. package/bin/demoServer.ts +64 -0
  12. package/bin/mqtt.ts +211 -0
  13. package/client/client.ts +181 -0
  14. package/client/context.ts +197 -0
  15. package/client/deps.ts +36 -0
  16. package/client/handlers/handleConnack.ts +21 -0
  17. package/client/handlers/handlePacket.ts +70 -0
  18. package/client/handlers/handlePuback.ts +10 -0
  19. package/client/handlers/handlePubcomp.ts +13 -0
  20. package/client/handlers/handlePublish.ts +32 -0
  21. package/client/handlers/handlePubrec.ts +24 -0
  22. package/client/handlers/handlePubrel.ts +26 -0
  23. package/client/handlers/handleSuback.ts +14 -0
  24. package/client/handlers/handleUnsuback.ts +11 -0
  25. package/client/mod.ts +11 -0
  26. package/client/store/deps.ts +22 -0
  27. package/client/store/memoryStore.ts +56 -0
  28. package/client/store/store.ts +29 -0
  29. package/deno/README.md +64 -0
  30. package/deno/client.ts +48 -0
  31. package/deno/server.test.ts +14 -0
  32. package/deno/server.ts +31 -0
  33. package/deno/wrapDenoConn.ts +12 -0
  34. package/deno.json +8 -0
  35. package/deno.lock +47 -0
  36. package/dev_utils/dummyConn.ts +95 -0
  37. package/dev_utils/mod.ts +1 -0
  38. package/mqttConn/mqttConn.test.ts +110 -0
  39. package/mqttConn/mqttConn.ts +134 -0
  40. package/mqttPacket/AuthenticationResult.ts +16 -0
  41. package/mqttPacket/BitMask.ts +10 -0
  42. package/mqttPacket/PacketType.ts +21 -0
  43. package/mqttPacket/connack.test.ts +129 -0
  44. package/mqttPacket/connack.ts +37 -0
  45. package/mqttPacket/connect.test.ts +297 -0
  46. package/mqttPacket/connect.ts +159 -0
  47. package/mqttPacket/decoder.test.ts +123 -0
  48. package/mqttPacket/decoder.ts +109 -0
  49. package/mqttPacket/disconnect.test.ts +50 -0
  50. package/mqttPacket/disconnect.ts +21 -0
  51. package/mqttPacket/encoder.test.ts +39 -0
  52. package/mqttPacket/encoder.ts +72 -0
  53. package/mqttPacket/length.test.ts +75 -0
  54. package/mqttPacket/length.ts +57 -0
  55. package/mqttPacket/mod.ts +151 -0
  56. package/mqttPacket/pingreq.test.ts +50 -0
  57. package/mqttPacket/pingreq.ts +22 -0
  58. package/mqttPacket/pingres.test.ts +50 -0
  59. package/mqttPacket/pingres.ts +21 -0
  60. package/mqttPacket/puback.test.ts +55 -0
  61. package/mqttPacket/puback.ts +28 -0
  62. package/mqttPacket/pubcomp.test.ts +55 -0
  63. package/mqttPacket/pubcomp.ts +28 -0
  64. package/mqttPacket/publish.test.ts +198 -0
  65. package/mqttPacket/publish.ts +73 -0
  66. package/mqttPacket/pubrec.test.ts +55 -0
  67. package/mqttPacket/pubrec.ts +28 -0
  68. package/mqttPacket/pubrel.test.ts +55 -0
  69. package/mqttPacket/pubrel.ts +28 -0
  70. package/mqttPacket/suback.test.ts +49 -0
  71. package/mqttPacket/suback.ts +41 -0
  72. package/mqttPacket/subscribe.test.ts +233 -0
  73. package/mqttPacket/subscribe.ts +69 -0
  74. package/mqttPacket/types.ts +27 -0
  75. package/mqttPacket/unsuback.test.ts +55 -0
  76. package/mqttPacket/unsuback.ts +28 -0
  77. package/mqttPacket/unsubscribe.test.ts +136 -0
  78. package/mqttPacket/unsubscribe.ts +48 -0
  79. package/mqttPacket/validators.ts +20 -0
  80. package/node/README.md +46 -0
  81. package/node/client.ts +58 -0
  82. package/node/server.test.ts +14 -0
  83. package/node/server.ts +34 -0
  84. package/node/wrapNodeSocket.ts +54 -0
  85. package/package.json +35 -0
  86. package/persistence/deps.ts +12 -0
  87. package/persistence/memory/memoryPersistence.test.ts +151 -0
  88. package/persistence/memory/memoryPersistence.ts +156 -0
  89. package/persistence/persistence.ts +24 -0
  90. package/persistence/store.ts +14 -0
  91. package/server/context.ts +157 -0
  92. package/server/deps.ts +37 -0
  93. package/server/handlers/handleConnect.ts +73 -0
  94. package/server/handlers/handleDisconnect.ts +6 -0
  95. package/server/handlers/handlePacket.ts +78 -0
  96. package/server/handlers/handlePingreq.ts +8 -0
  97. package/server/handlers/handlePuback.ts +12 -0
  98. package/server/handlers/handlePubcomp.ts +12 -0
  99. package/server/handlers/handlePublish.ts +47 -0
  100. package/server/handlers/handlePubrec.ts +20 -0
  101. package/server/handlers/handlePubrel.ts +24 -0
  102. package/server/handlers/handleSubscribe.ts +47 -0
  103. package/server/handlers/handleUnsubscribe.ts +17 -0
  104. package/server/mod.ts +16 -0
  105. package/server/server.ts +74 -0
  106. package/server/test/handleConnect.test.ts +126 -0
  107. package/server/test/test-handlers.ts +52 -0
  108. package/socket/socket.ts +59 -0
  109. package/timer/timer.test.ts +48 -0
  110. package/timer/timer.ts +45 -0
  111. package/trie/trie.test.ts +147 -0
  112. package/trie/trie.ts +106 -0
  113. package/utils/args.ts +86 -0
  114. package/utils/assert.ts +31 -0
  115. package/utils/asyncQueue.ts +63 -0
  116. package/utils/deferred.ts +12 -0
  117. package/utils/logger.ts +35 -0
  118. package/utils/mod.ts +7 -0
  119. package/utils/nextTick.ts +3 -0
  120. package/utils/platform.ts +4 -0
@@ -0,0 +1,21 @@
1
+ export const PacketType = {
2
+ reserved: 0,
3
+ connect: 1,
4
+ connack: 2,
5
+ publish: 3,
6
+ puback: 4,
7
+ pubrec: 5,
8
+ pubrel: 6,
9
+ pubcomp: 7,
10
+ subscribe: 8,
11
+ suback: 9,
12
+ unsubscribe: 10,
13
+ unsuback: 11,
14
+ pingreq: 12,
15
+ pingres: 13,
16
+ disconnect: 14,
17
+ } as const;
18
+
19
+ export const PacketNameByType = Object.fromEntries(
20
+ Object.entries(PacketType).map(([k, v]) => [v, k]),
21
+ );
@@ -0,0 +1,129 @@
1
+ import assert from "node:assert/strict";
2
+ import { test } from "node:test";
3
+ import { decode, encode } from "./mod.ts";
4
+ import { PacketType } from "./PacketType.ts";
5
+
6
+ test("encode Connack packet", () => {
7
+ assert.deepStrictEqual(
8
+ encode({
9
+ type: PacketType.connack,
10
+ sessionPresent: false,
11
+ returnCode: 0,
12
+ }),
13
+ Uint8Array.from([
14
+ // fixedHeader
15
+ 0x20, // packetType + flags
16
+ 2, // remainingLength
17
+ // variableHeader
18
+ 0, // connack flags
19
+ 0, // return code
20
+ ]),
21
+ );
22
+ });
23
+
24
+ test("decode Connack packet", () => {
25
+ assert.deepStrictEqual(
26
+ decode(
27
+ Uint8Array.from([
28
+ // fixedHeader
29
+ 0x20, // packetType + flags
30
+ 2, // remainingLength
31
+ // variableHeader
32
+ 0, // connack flags
33
+ 0, // return code
34
+ ]),
35
+ ),
36
+ {
37
+ type: PacketType.connack,
38
+ sessionPresent: false,
39
+ returnCode: 0,
40
+ },
41
+ );
42
+ });
43
+
44
+ test("encode Connack with session present", () => {
45
+ assert.deepStrictEqual(
46
+ encode({
47
+ type: PacketType.connack,
48
+ sessionPresent: true,
49
+ returnCode: 0,
50
+ }),
51
+ Uint8Array.from([
52
+ // fixedHeader
53
+ 0x20, // packetType + flags
54
+ 2, // remainingLength
55
+ // variableHeader
56
+ 1, // connack flags (sessionPresent)
57
+ 0, // return code
58
+ ]),
59
+ );
60
+ });
61
+
62
+ test("decode Connack with session present", () => {
63
+ assert
64
+ .deepStrictEqual(
65
+ decode(
66
+ Uint8Array.from([
67
+ // fixedHeader
68
+ 0x20, // packetType + flags
69
+ 2, // remainingLength
70
+ // variableHeader
71
+ 1, // connack flags (sessionPresent)
72
+ 0, // return code
73
+ ]),
74
+ ),
75
+ {
76
+ type: PacketType.connack,
77
+ sessionPresent: true,
78
+ returnCode: 0,
79
+ },
80
+ );
81
+ });
82
+
83
+ test("decode Connack with non-zero returnCode", () => {
84
+ assert.deepStrictEqual(
85
+ decode(
86
+ Uint8Array.from([
87
+ // fixedHeader
88
+ 0x20, // packetType + flags
89
+ 2, // remainingLength
90
+ // variableHeader
91
+ 0, // connack flags
92
+ 4, // return code (bad username or password)
93
+ ]),
94
+ ),
95
+ {
96
+ type: PacketType.connack,
97
+ sessionPresent: false,
98
+ returnCode: 4,
99
+ },
100
+ );
101
+ });
102
+
103
+ test("decode Connack with invalid returnCode", () => {
104
+ assert.throws(
105
+ () =>
106
+ decode(
107
+ Uint8Array.from([
108
+ // fixedHeader
109
+ 0x20, // packetType + flags
110
+ 2, // remainingLength
111
+ // variableHeader
112
+ 0, // connack flags
113
+ 64, // return code (reserved)
114
+ ]),
115
+ ),
116
+ Error,
117
+ "Invalid return code",
118
+ );
119
+ });
120
+
121
+ test("decode short Connack packets", () => {
122
+ assert.throws(
123
+ () => decode(Uint8Array.from([0x20])),
124
+ Error,
125
+ "decoding failed",
126
+ );
127
+ assert.throws(() => decode(Uint8Array.from([0x20, 2])), Error, "too short");
128
+ assert.throws(() => decode(Uint8Array.from([0x20, 2, 0])), Error, "too long");
129
+ });
@@ -0,0 +1,37 @@
1
+ import type { TAuthenticationResult, TPacketType } from "./types.ts";
2
+ import { PacketType } from "./PacketType.ts";
3
+ import { BitMask } from "./BitMask.ts";
4
+ import { booleanFlag, Decoder, DecoderError } from "./decoder.ts";
5
+ import { AuthenticationResultByNumber } from "./AuthenticationResult.ts";
6
+
7
+ export type ConnackPacket = {
8
+ type: TPacketType;
9
+ sessionPresent: boolean;
10
+ returnCode: TAuthenticationResult;
11
+ };
12
+
13
+ export default {
14
+ encode(packet: ConnackPacket): { flags: number; bytes: number[] } {
15
+ const flags = 0;
16
+ return {
17
+ flags,
18
+ bytes: [packet.sessionPresent ? 1 : 0, packet.returnCode || 0],
19
+ };
20
+ },
21
+
22
+ decode(buffer: Uint8Array, _flags: number): ConnackPacket | undefined {
23
+ const decoder = new Decoder(buffer);
24
+
25
+ const sessionPresent = booleanFlag(decoder.getByte(), BitMask.bit0);
26
+ const returnCode = decoder.getByte() as TAuthenticationResult;
27
+ decoder.done();
28
+ if (!AuthenticationResultByNumber[returnCode]) {
29
+ throw new DecoderError("Invalid return code");
30
+ }
31
+ return {
32
+ type: PacketType.connack,
33
+ sessionPresent,
34
+ returnCode,
35
+ };
36
+ },
37
+ };
@@ -0,0 +1,297 @@
1
+ import { PacketType } from "./PacketType.ts";
2
+ import assert from "node:assert/strict";
3
+ import { test } from "node:test";
4
+
5
+ import { type ConnectPacket, decode, encode } from "./mod.ts";
6
+
7
+ test("encode Connect with ClientId", () => {
8
+ assert.deepStrictEqual(
9
+ encode({
10
+ type: PacketType.connect,
11
+ clientId: "id",
12
+ }),
13
+ Uint8Array.from([
14
+ // fixedHeader
15
+ 16, // packetType + flags
16
+ 14, // remainingLength
17
+ // variableHeader
18
+ 0, // protocolNameLength MSB
19
+ 4, // protocolNameLength LSB
20
+ 77, // 'M'
21
+ 81, // 'Q'
22
+ 84, // 'T'
23
+ 84, // 'T'
24
+ 4, // protocolLevel
25
+ 2, // connectFlags (cleanSession)
26
+ 0, // keepAlive MSB
27
+ 0, // keepAlive LSB
28
+ // payload
29
+ // clientId
30
+ 0, // length MSB
31
+ 2, // length LSB
32
+ 105, // 'i'
33
+ 100, // 'd'
34
+ ]),
35
+ );
36
+ });
37
+
38
+ test("encode Connect with Clean false", () => {
39
+ assert.deepStrictEqual(
40
+ encode({
41
+ type: PacketType.connect,
42
+ clientId: "id",
43
+ clean: false,
44
+ }),
45
+ Uint8Array.from([
46
+ // fixedHeader
47
+ 16, // packetType + flags
48
+ 14, // remainingLength
49
+ // variableHeader
50
+ 0, // protocolNameLength MSB
51
+ 4, // protocolNameLength LSB
52
+ 77, // 'M'
53
+ 81, // 'Q'
54
+ 84, // 'T'
55
+ 84, // 'T'
56
+ 4, // protocolLevel
57
+ 0, // connectFlags
58
+ 0, // keepAlive MSB
59
+ 0, // keepAlive LSB
60
+ // payload
61
+ // clientId
62
+ 0, // length MSB
63
+ 2, // length LSB
64
+ 105, // 'i'
65
+ 100, // 'd'
66
+ ]),
67
+ );
68
+ });
69
+
70
+ test("encode Connect with KeepAlive", () => {
71
+ assert.deepStrictEqual(
72
+ encode({
73
+ type: PacketType.connect,
74
+ clientId: "id",
75
+ keepAlive: 300,
76
+ }),
77
+ Uint8Array.from([
78
+ // fixedHeader
79
+ 16, // packetType + flags
80
+ 14, // remainingLength
81
+ // variableHeader
82
+ 0, // protocolNameLength MSB
83
+ 4, // protocolNameLength LSB
84
+ 77, // 'M'
85
+ 81, // 'Q'
86
+ 84, // 'T'
87
+ 84, // 'T'
88
+ 4, // protocolLevel
89
+ 2, // connectFlags (cleanSession)
90
+ 1, // keepAlive MSB
91
+ 44, // keepAlive LSB
92
+ // payload
93
+ // clientId
94
+ 0, // length MSB
95
+ 2, // length LSB
96
+ 105, // 'i'
97
+ 100, // 'd'
98
+ ]),
99
+ );
100
+ });
101
+
102
+ const encodedConnect = [
103
+ // fixedHeader
104
+ 16, // packetType + flags
105
+ 26, // remainingLength
106
+ // variableHeader
107
+ 0, // protocolNameLength MSB
108
+ 4, // protocolNameLength LSB
109
+ 77, // 'M'
110
+ 81, // 'Q'
111
+ 84, // 'T'
112
+ 84, // 'T'
113
+ 4, // protocolLevel
114
+ 194, // connectFlags (usernameFlag, passwordFlag, cleanSession)
115
+ 0, // keepAlive MSB
116
+ 0, // keepAlive LSB
117
+ // payload
118
+ // clientId
119
+ 0, // length MSB
120
+ 2, // length LSB
121
+ 105, // 'i'
122
+ 100, // 'd'
123
+ // username
124
+ 0, // length MSB
125
+ 4, // length LSB
126
+ 117, // 'u'
127
+ 115, // 's'
128
+ 101, // 'e'
129
+ 114, // 'r'
130
+ // password
131
+ 0, // length MSB
132
+ 4, // length LSB
133
+ 112, // 'p'
134
+ 97, // 'a'
135
+ 115, // 's'
136
+ 115, // 's'
137
+ ];
138
+
139
+ const decodedConnect: ConnectPacket = {
140
+ type: PacketType.connect,
141
+ clientId: "id",
142
+ protocolName: "MQTT",
143
+ protocolLevel: 4,
144
+ username: "user",
145
+ password: Uint8Array.from([
146
+ 112, // 'p'
147
+ 97, // 'a'
148
+ 115, // 's'
149
+ 115, // 's'
150
+ ]),
151
+ will: undefined,
152
+ clean: true,
153
+ keepAlive: 0,
154
+ };
155
+
156
+ test("encode Connect with username and password", () => {
157
+ assert.deepStrictEqual(
158
+ encode({
159
+ type: PacketType.connect,
160
+ clientId: "id",
161
+ username: "user",
162
+ password: Uint8Array.from([
163
+ 112, // 'p'
164
+ 97, // 'a'
165
+ 115, // 's'
166
+ 115, // 's'
167
+ ]),
168
+ }),
169
+ Uint8Array.from(encodedConnect),
170
+ );
171
+ });
172
+
173
+ test("decode Connect with username and password", () => {
174
+ assert.deepStrictEqual(
175
+ decode(Uint8Array.from(encodedConnect)),
176
+ decodedConnect,
177
+ );
178
+ });
179
+
180
+ const decodedConnectWithWill = Object.assign({}, decodedConnect);
181
+ decodedConnectWithWill.will = {
182
+ topic: "mywill",
183
+ payload: Uint8Array.from([
184
+ 77, // 'M'
185
+ 81, // 'Q'
186
+ 84, // 'T'
187
+ 84, // 'T'
188
+ ]),
189
+ retain: false,
190
+ qos: 1,
191
+ };
192
+
193
+ const encodedConnectWithWill = [
194
+ // fixedHeader
195
+ 16, // packetType + flags
196
+ 40, // remainingLength
197
+ // variableHeader
198
+ 0, // protocolNameLength MSB
199
+ 4, // protocolNameLength LSB
200
+ 77, // 'M'
201
+ 81, // 'Q'
202
+ 84, // 'T'
203
+ 84, // 'T'
204
+ 4, // protocolLevel
205
+ 206, // connectFlags (usernameFlag, passwordFlag, cleanSession, hasWill, willretain, willQos)
206
+ 0, // keepAlive MSB
207
+ 0, // keepAlive LSB
208
+ // payload
209
+ // clientId
210
+ 0, // length MSB
211
+ 2, // length LSB
212
+ 105, // 'i'
213
+ 100, // 'd'
214
+ // will topic
215
+ 0, // length MSB
216
+ 6, // length LSB
217
+ 109, // 'm'
218
+ 121, // 'y'
219
+ 119, // 'w'
220
+ 105, // 'i'
221
+ 108, // 'l'
222
+ 108, // 'l'
223
+ // will payload (raw bytes)
224
+ 0, // length MSB
225
+ 4, // length LSB
226
+ 77, // 'M'
227
+ 81, // 'Q'
228
+ 84, // 'T'
229
+ 84, // 'T'
230
+ // username
231
+ 0, // length MSB
232
+ 4, // length LSB
233
+ 117, // 'u'
234
+ 115, // 's'
235
+ 101, // 'e'
236
+ 114, // 'r'
237
+ // password
238
+ 0, // length MSB
239
+ 4, // length LSB
240
+ 112, // 'p'
241
+ 97, // 'a'
242
+ 115, // 's'
243
+ 115, // 's'
244
+ ];
245
+
246
+ test("encode Connect with username and password and will", () => {
247
+ assert.deepStrictEqual(
248
+ encode(decodedConnectWithWill),
249
+ Uint8Array.from(encodedConnectWithWill),
250
+ );
251
+ });
252
+
253
+ test("decode invalid Connect", () => {
254
+ const longConnect = [...encodedConnect, 0];
255
+ longConnect[1]++;
256
+ const reservedBitConnect = [...encodedConnect];
257
+ reservedBitConnect[9] += 1; // set bit 0 of flags
258
+ const invalidWillQosConnect = [...encodedConnectWithWill];
259
+ invalidWillQosConnect[9] += 16; // set bit 4 of flags
260
+
261
+ assert.throws(
262
+ () => decode(Uint8Array.from([...longConnect])),
263
+ Error,
264
+ "too long",
265
+ );
266
+
267
+ assert.throws(
268
+ () => decode(Uint8Array.from(reservedBitConnect)),
269
+ Error,
270
+ "Invalid reserved bit",
271
+ );
272
+
273
+ assert.throws(
274
+ () => decode(Uint8Array.from(invalidWillQosConnect)),
275
+ Error,
276
+ "Invalid will qos",
277
+ );
278
+
279
+ assert.throws(
280
+ () =>
281
+ decode(
282
+ Uint8Array.from([
283
+ // fixedHeader
284
+ 16, // packetType + flags + reserved bit set to 1
285
+ 26, // remainingLength
286
+ // variableHeader
287
+ 0, // protocolNameLength MSB
288
+ 4, // protocolNameLength LSB
289
+ 77, // 'M'
290
+ 81, // 'Q'
291
+ 84, // 'T'
292
+ ]),
293
+ ),
294
+ Error,
295
+ "too short",
296
+ );
297
+ });
@@ -0,0 +1,159 @@
1
+ import type { ClientId, Payload, QoS, Topic, TPacketType } from "./types.ts";
2
+ import { PacketType } from "./PacketType.ts";
3
+ import { BitMask } from "./BitMask.ts";
4
+ import { Encoder } from "./encoder.ts";
5
+ import {
6
+ booleanFlag,
7
+ Decoder,
8
+ DecoderError,
9
+ hasEmptyFlags,
10
+ } from "./decoder.ts";
11
+
12
+ export type ConnectPacket = {
13
+ type: TPacketType;
14
+ protocolName?: string;
15
+ protocolLevel?: number;
16
+ clientId?: ClientId;
17
+ username?: string;
18
+ password?: Uint8Array;
19
+ will?: {
20
+ topic: Topic;
21
+ payload: Payload;
22
+ retain?: boolean;
23
+ qos?: QoS;
24
+ };
25
+ clean?: boolean;
26
+ keepAlive?: number;
27
+ };
28
+
29
+ function invalidProtocolName(version: number, name: string): boolean {
30
+ if (version === 4 && name !== "MQTT") {
31
+ return true;
32
+ }
33
+ return false;
34
+ }
35
+
36
+ export default {
37
+ encode(packet: ConnectPacket) {
38
+ const flags = 0;
39
+
40
+ const protocolLevel = 4;
41
+ const protocolName = protocolLevel === 4 ? "MQTT" : "MQIsdp";
42
+ const clientId = packet.clientId || "";
43
+ const usernameFlag = !!packet.username;
44
+ const passwordFlag = !!packet.password;
45
+ const willRetain = !!packet.will?.retain;
46
+ const willQoS = packet.will?.qos || 0;
47
+ const willFlag = !!packet.will;
48
+ const cleanSession = packet.clean !== false;
49
+ const connectFlags = (usernameFlag ? BitMask.bit7 : 0) +
50
+ (passwordFlag ? BitMask.bit6 : 0) +
51
+ (willRetain ? BitMask.bit5 : 0) +
52
+ (willQoS & 2 ? BitMask.bit4 : 0) +
53
+ (willQoS & 1 ? BitMask.bit3 : 0) +
54
+ (willFlag ? BitMask.bit2 : 0) +
55
+ (cleanSession ? BitMask.bit1 : 0);
56
+ const keepAlive = packet.keepAlive || 0;
57
+
58
+ const encoder = new Encoder();
59
+ encoder
60
+ .setUtf8String(protocolName)
61
+ .setByte(protocolLevel)
62
+ .setByte(connectFlags)
63
+ .setInt16(keepAlive)
64
+ .setUtf8String(clientId);
65
+
66
+ if (packet.will) {
67
+ encoder.setTopic(packet.will.topic).setByteArray(packet.will.payload);
68
+ }
69
+
70
+ if (packet.username) {
71
+ encoder.setUtf8String(packet.username);
72
+ }
73
+
74
+ if (packet.password) {
75
+ encoder.setByteArray(packet.password);
76
+ }
77
+ return { flags, bytes: encoder.done() };
78
+ },
79
+
80
+ decode(buffer: Uint8Array, flags: number): ConnectPacket {
81
+ const decoder = new Decoder(buffer);
82
+ const protocolName = decoder.getUtf8String();
83
+ const protocolLevel = decoder.getByte();
84
+ if (invalidProtocolName(protocolLevel, protocolName)) {
85
+ throw new DecoderError("Invalid protocol name");
86
+ }
87
+
88
+ const connectFlags = decoder.getByte();
89
+
90
+ const usernameFlag = booleanFlag(connectFlags, BitMask.bit7);
91
+ const passwordFlag = booleanFlag(connectFlags, BitMask.bit6);
92
+ const willRetain = booleanFlag(connectFlags, BitMask.bit5);
93
+ const willQoS = (connectFlags & (BitMask.bit4 + BitMask.bit3)) >> 3;
94
+ const willFlag = booleanFlag(connectFlags, BitMask.bit2);
95
+ const cleanSession = booleanFlag(connectFlags, BitMask.bit1);
96
+ const reservedBit = booleanFlag(connectFlags, BitMask.bit0);
97
+
98
+ hasEmptyFlags(flags);
99
+ // The Server MUST validate that the reserved flag in the CONNECT Control Packet
100
+ // is set to zero and disconnect the Client if it is not zero [MQTT-3.1.2-3].
101
+ if (reservedBit) {
102
+ throw new DecoderError("Invalid reserved bit");
103
+ }
104
+
105
+ if (willQoS !== 0 && willQoS !== 1 && willQoS !== 2) {
106
+ throw new DecoderError("Invalid will qos");
107
+ }
108
+
109
+ const keepAlive = decoder.getInt16();
110
+ const clientId = decoder.getUtf8String();
111
+
112
+ let willTopic;
113
+ let willPayload;
114
+ if (willFlag) {
115
+ willTopic = decoder.getTopic();
116
+ willPayload = decoder.getByteArray();
117
+ }
118
+
119
+ let username;
120
+ let password;
121
+ if (usernameFlag) {
122
+ username = decoder.getUtf8String();
123
+ }
124
+
125
+ if (passwordFlag) {
126
+ password = decoder.getByteArray();
127
+ }
128
+
129
+ decoder.done();
130
+ if (!willFlag && (willQoS !== 0 || willRetain === true)) {
131
+ throw new DecoderError(
132
+ "Will QoS must be 0 and Will retain to false when Will flag is false",
133
+ );
134
+ }
135
+
136
+ if (clientId.length === 0 && cleanSession === false) {
137
+ throw new DecoderError("Clean session must be true if clientID is empty");
138
+ }
139
+
140
+ return {
141
+ type: PacketType.connect,
142
+ protocolName: protocolName,
143
+ protocolLevel,
144
+ clientId: clientId,
145
+ username: username ? username : undefined,
146
+ password: password ? password : undefined,
147
+ will: willFlag
148
+ ? {
149
+ topic: willTopic || "",
150
+ payload: willPayload || Uint8Array.from([0]),
151
+ retain: willRetain,
152
+ qos: willQoS,
153
+ }
154
+ : undefined,
155
+ clean: cleanSession,
156
+ keepAlive,
157
+ };
158
+ },
159
+ };