@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.
- package/.github/workflows/closeStale.yml +15 -0
- package/.github/workflows/codeql-analysis.yml +71 -0
- package/.github/workflows/deno-ci.yml +16 -0
- package/.github/workflows/nodejs-ci.yml +23 -0
- package/.github/workflows/publish.yml +16 -0
- package/.vscode/extensions.json +10 -0
- package/.vscode/settings.json +12 -0
- package/LICENSE.txt +21 -0
- package/README.md +34 -0
- package/bin/conformanceServer.ts +70 -0
- package/bin/demoServer.ts +64 -0
- package/bin/mqtt.ts +211 -0
- package/client/client.ts +181 -0
- package/client/context.ts +197 -0
- package/client/deps.ts +36 -0
- package/client/handlers/handleConnack.ts +21 -0
- package/client/handlers/handlePacket.ts +70 -0
- package/client/handlers/handlePuback.ts +10 -0
- package/client/handlers/handlePubcomp.ts +13 -0
- package/client/handlers/handlePublish.ts +32 -0
- package/client/handlers/handlePubrec.ts +24 -0
- package/client/handlers/handlePubrel.ts +26 -0
- package/client/handlers/handleSuback.ts +14 -0
- package/client/handlers/handleUnsuback.ts +11 -0
- package/client/mod.ts +11 -0
- package/client/store/deps.ts +22 -0
- package/client/store/memoryStore.ts +56 -0
- package/client/store/store.ts +29 -0
- package/deno/README.md +64 -0
- package/deno/client.ts +48 -0
- package/deno/server.test.ts +14 -0
- package/deno/server.ts +31 -0
- package/deno/wrapDenoConn.ts +12 -0
- package/deno.json +8 -0
- package/deno.lock +47 -0
- package/dev_utils/dummyConn.ts +95 -0
- package/dev_utils/mod.ts +1 -0
- package/mqttConn/mqttConn.test.ts +110 -0
- package/mqttConn/mqttConn.ts +134 -0
- package/mqttPacket/AuthenticationResult.ts +16 -0
- package/mqttPacket/BitMask.ts +10 -0
- package/mqttPacket/PacketType.ts +21 -0
- package/mqttPacket/connack.test.ts +129 -0
- package/mqttPacket/connack.ts +37 -0
- package/mqttPacket/connect.test.ts +297 -0
- package/mqttPacket/connect.ts +159 -0
- package/mqttPacket/decoder.test.ts +123 -0
- package/mqttPacket/decoder.ts +109 -0
- package/mqttPacket/disconnect.test.ts +50 -0
- package/mqttPacket/disconnect.ts +21 -0
- package/mqttPacket/encoder.test.ts +39 -0
- package/mqttPacket/encoder.ts +72 -0
- package/mqttPacket/length.test.ts +75 -0
- package/mqttPacket/length.ts +57 -0
- package/mqttPacket/mod.ts +151 -0
- package/mqttPacket/pingreq.test.ts +50 -0
- package/mqttPacket/pingreq.ts +22 -0
- package/mqttPacket/pingres.test.ts +50 -0
- package/mqttPacket/pingres.ts +21 -0
- package/mqttPacket/puback.test.ts +55 -0
- package/mqttPacket/puback.ts +28 -0
- package/mqttPacket/pubcomp.test.ts +55 -0
- package/mqttPacket/pubcomp.ts +28 -0
- package/mqttPacket/publish.test.ts +198 -0
- package/mqttPacket/publish.ts +73 -0
- package/mqttPacket/pubrec.test.ts +55 -0
- package/mqttPacket/pubrec.ts +28 -0
- package/mqttPacket/pubrel.test.ts +55 -0
- package/mqttPacket/pubrel.ts +28 -0
- package/mqttPacket/suback.test.ts +49 -0
- package/mqttPacket/suback.ts +41 -0
- package/mqttPacket/subscribe.test.ts +233 -0
- package/mqttPacket/subscribe.ts +69 -0
- package/mqttPacket/types.ts +27 -0
- package/mqttPacket/unsuback.test.ts +55 -0
- package/mqttPacket/unsuback.ts +28 -0
- package/mqttPacket/unsubscribe.test.ts +136 -0
- package/mqttPacket/unsubscribe.ts +48 -0
- package/mqttPacket/validators.ts +20 -0
- package/node/README.md +46 -0
- package/node/client.ts +58 -0
- package/node/server.test.ts +14 -0
- package/node/server.ts +34 -0
- package/node/wrapNodeSocket.ts +54 -0
- package/package.json +35 -0
- package/persistence/deps.ts +12 -0
- package/persistence/memory/memoryPersistence.test.ts +151 -0
- package/persistence/memory/memoryPersistence.ts +156 -0
- package/persistence/persistence.ts +24 -0
- package/persistence/store.ts +14 -0
- package/server/context.ts +157 -0
- package/server/deps.ts +37 -0
- package/server/handlers/handleConnect.ts +73 -0
- package/server/handlers/handleDisconnect.ts +6 -0
- package/server/handlers/handlePacket.ts +78 -0
- package/server/handlers/handlePingreq.ts +8 -0
- package/server/handlers/handlePuback.ts +12 -0
- package/server/handlers/handlePubcomp.ts +12 -0
- package/server/handlers/handlePublish.ts +47 -0
- package/server/handlers/handlePubrec.ts +20 -0
- package/server/handlers/handlePubrel.ts +24 -0
- package/server/handlers/handleSubscribe.ts +47 -0
- package/server/handlers/handleUnsubscribe.ts +17 -0
- package/server/mod.ts +16 -0
- package/server/server.ts +74 -0
- package/server/test/handleConnect.test.ts +126 -0
- package/server/test/test-handlers.ts +52 -0
- package/socket/socket.ts +59 -0
- package/timer/timer.test.ts +48 -0
- package/timer/timer.ts +45 -0
- package/trie/trie.test.ts +147 -0
- package/trie/trie.ts +106 -0
- package/utils/args.ts +86 -0
- package/utils/assert.ts +31 -0
- package/utils/asyncQueue.ts +63 -0
- package/utils/deferred.ts +12 -0
- package/utils/logger.ts +35 -0
- package/utils/mod.ts +7 -0
- package/utils/nextTick.ts +3 -0
- package/utils/platform.ts +4 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { PacketType } from "./PacketType.ts";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { test } from "node:test";
|
|
4
|
+
|
|
5
|
+
import { decode, encode } from "./mod.ts";
|
|
6
|
+
|
|
7
|
+
test("encode Pubrel", () => {
|
|
8
|
+
assert.deepStrictEqual(
|
|
9
|
+
encode({
|
|
10
|
+
type: PacketType.pubrel,
|
|
11
|
+
id: 1337,
|
|
12
|
+
}),
|
|
13
|
+
Uint8Array.from([
|
|
14
|
+
// fixedHeader
|
|
15
|
+
0x60, // packetType + flags
|
|
16
|
+
2, // remainingLength
|
|
17
|
+
// variableHeader
|
|
18
|
+
5, // id MSB
|
|
19
|
+
57, // id LSB
|
|
20
|
+
]),
|
|
21
|
+
);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("decode Pubrel ", () => {
|
|
25
|
+
assert.deepStrictEqual(
|
|
26
|
+
decode(
|
|
27
|
+
Uint8Array.from([
|
|
28
|
+
// fixedHeader
|
|
29
|
+
0x60, // packetType + flags
|
|
30
|
+
2, // remainingLength
|
|
31
|
+
// variableHeader
|
|
32
|
+
5, // id MSB
|
|
33
|
+
57, // id LSB
|
|
34
|
+
]),
|
|
35
|
+
),
|
|
36
|
+
{
|
|
37
|
+
type: PacketType.pubrel,
|
|
38
|
+
id: 1337,
|
|
39
|
+
},
|
|
40
|
+
);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("decodeShortPubrelPackets", () => {
|
|
44
|
+
assert.throws(
|
|
45
|
+
() => decode(Uint8Array.from([0x60])),
|
|
46
|
+
Error,
|
|
47
|
+
"decoding failed",
|
|
48
|
+
);
|
|
49
|
+
assert.throws(() => decode(Uint8Array.from([0x60, 2])), Error, "too short");
|
|
50
|
+
assert.throws(
|
|
51
|
+
() => decode(Uint8Array.from([0x60, 3, 0, 0, 0])),
|
|
52
|
+
Error,
|
|
53
|
+
"too long",
|
|
54
|
+
);
|
|
55
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { PacketId, TPacketType } from "./types.ts";
|
|
2
|
+
import { PacketType } from "./PacketType.ts";
|
|
3
|
+
import { Decoder } from "./decoder.ts";
|
|
4
|
+
import { Encoder } from "./encoder.ts";
|
|
5
|
+
|
|
6
|
+
export type PubrelPacket = {
|
|
7
|
+
type: TPacketType;
|
|
8
|
+
id: PacketId;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export default {
|
|
12
|
+
encode(packet: PubrelPacket) {
|
|
13
|
+
const flags = 0;
|
|
14
|
+
const encoder = new Encoder();
|
|
15
|
+
encoder.setInt16(packet.id);
|
|
16
|
+
return { flags, bytes: encoder.done() };
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
decode(buffer: Uint8Array): PubrelPacket {
|
|
20
|
+
const decoder = new Decoder(buffer);
|
|
21
|
+
const id = decoder.getInt16();
|
|
22
|
+
decoder.done();
|
|
23
|
+
return {
|
|
24
|
+
type: PacketType.pubrel,
|
|
25
|
+
id,
|
|
26
|
+
};
|
|
27
|
+
},
|
|
28
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { PacketType } from "./PacketType.ts";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { test } from "node:test";
|
|
4
|
+
|
|
5
|
+
import { decode, encode } from "./mod.ts";
|
|
6
|
+
|
|
7
|
+
test("encode Suback", () => {
|
|
8
|
+
assert.deepStrictEqual(
|
|
9
|
+
encode({
|
|
10
|
+
type: PacketType.suback,
|
|
11
|
+
id: 1,
|
|
12
|
+
returnCodes: [0, 1],
|
|
13
|
+
}),
|
|
14
|
+
Uint8Array.from([
|
|
15
|
+
// fixedHeader
|
|
16
|
+
0x90, // packetType + flags
|
|
17
|
+
4, // remainingLength
|
|
18
|
+
// variableHeader
|
|
19
|
+
0, // id MSB
|
|
20
|
+
1, // id LSB
|
|
21
|
+
// payload
|
|
22
|
+
0,
|
|
23
|
+
1,
|
|
24
|
+
]),
|
|
25
|
+
);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("decode Suback", () => {
|
|
29
|
+
assert.deepStrictEqual(
|
|
30
|
+
decode(
|
|
31
|
+
Uint8Array.from([
|
|
32
|
+
// fixedHeader
|
|
33
|
+
0x90, // packetType + flags
|
|
34
|
+
4, // remainingLength
|
|
35
|
+
// variableHeader
|
|
36
|
+
0, // id MSB
|
|
37
|
+
1, // id LSB
|
|
38
|
+
// payload
|
|
39
|
+
0,
|
|
40
|
+
1,
|
|
41
|
+
]),
|
|
42
|
+
),
|
|
43
|
+
{
|
|
44
|
+
type: PacketType.suback,
|
|
45
|
+
id: 1,
|
|
46
|
+
returnCodes: [0, 1],
|
|
47
|
+
},
|
|
48
|
+
);
|
|
49
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { PacketId, ReturnCodes, TPacketType } from "./types.ts";
|
|
2
|
+
import { PacketType } from "./PacketType.ts";
|
|
3
|
+
import { Encoder } from "./encoder.ts";
|
|
4
|
+
import { Decoder } from "./decoder.ts";
|
|
5
|
+
|
|
6
|
+
export type SubackPacket = {
|
|
7
|
+
type: TPacketType;
|
|
8
|
+
id: PacketId;
|
|
9
|
+
returnCodes: ReturnCodes;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export default {
|
|
13
|
+
encode(packet: SubackPacket) {
|
|
14
|
+
const flags = 0;
|
|
15
|
+
const encoder = new Encoder();
|
|
16
|
+
encoder.setInt16(packet.id);
|
|
17
|
+
encoder.setRemainder(packet.returnCodes);
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
flags,
|
|
21
|
+
bytes: encoder.done(),
|
|
22
|
+
};
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
decode(buffer: Uint8Array): SubackPacket {
|
|
26
|
+
const decoder = new Decoder(buffer);
|
|
27
|
+
const id = decoder.getInt16();
|
|
28
|
+
const payload = decoder.getRemainder();
|
|
29
|
+
const returnCodes = [];
|
|
30
|
+
|
|
31
|
+
for (const code of payload) {
|
|
32
|
+
returnCodes.push(code);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
type: PacketType.suback,
|
|
37
|
+
id,
|
|
38
|
+
returnCodes,
|
|
39
|
+
};
|
|
40
|
+
},
|
|
41
|
+
};
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { PacketType } from "./PacketType.ts";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { test } from "node:test";
|
|
4
|
+
|
|
5
|
+
import { decode, encode } from "./mod.ts";
|
|
6
|
+
|
|
7
|
+
test("encode Subscribe", () => {
|
|
8
|
+
assert.deepStrictEqual(
|
|
9
|
+
encode({
|
|
10
|
+
type: PacketType.subscribe,
|
|
11
|
+
id: 1,
|
|
12
|
+
subscriptions: [
|
|
13
|
+
{ topicFilter: "a/b", qos: 0 },
|
|
14
|
+
{ topicFilter: "c/d", qos: 1 },
|
|
15
|
+
],
|
|
16
|
+
}),
|
|
17
|
+
Uint8Array.from([
|
|
18
|
+
// fixedHeader
|
|
19
|
+
0x82, // packetType + flags
|
|
20
|
+
14, // remainingLength
|
|
21
|
+
// variableHeader
|
|
22
|
+
0, // id MSB
|
|
23
|
+
1, // id LSB
|
|
24
|
+
// payload
|
|
25
|
+
0, // topic filter length MSB
|
|
26
|
+
3, // topic filter length LSB
|
|
27
|
+
97, // 'a'
|
|
28
|
+
47, // '/'
|
|
29
|
+
98, // 'b'
|
|
30
|
+
0, // qos
|
|
31
|
+
0, // topic filter length MSB
|
|
32
|
+
3, // topic filter length LSB
|
|
33
|
+
99, // 'c'
|
|
34
|
+
47, // '/'
|
|
35
|
+
100, // 'd'
|
|
36
|
+
1, // qos
|
|
37
|
+
]),
|
|
38
|
+
);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("decode Subscribe", () => {
|
|
42
|
+
assert.deepStrictEqual(
|
|
43
|
+
decode(
|
|
44
|
+
Uint8Array.from([
|
|
45
|
+
// fixedHeader
|
|
46
|
+
0x82, // packetType + flags
|
|
47
|
+
14, // remainingLength
|
|
48
|
+
// variableHeader
|
|
49
|
+
0, // id MSB
|
|
50
|
+
1, // id LSB
|
|
51
|
+
// payload
|
|
52
|
+
0, // topic filter length MSB
|
|
53
|
+
3, // topic filter length LSB
|
|
54
|
+
97, // 'a'
|
|
55
|
+
47, // '/'
|
|
56
|
+
98, // 'b'
|
|
57
|
+
0, // qos
|
|
58
|
+
0, // topic filter length MSB
|
|
59
|
+
3, // topic filter length LSB
|
|
60
|
+
99, // 'c'
|
|
61
|
+
47, // '/'
|
|
62
|
+
100, // 'd'
|
|
63
|
+
1, // qos
|
|
64
|
+
]),
|
|
65
|
+
),
|
|
66
|
+
{
|
|
67
|
+
type: PacketType.subscribe,
|
|
68
|
+
id: 1,
|
|
69
|
+
subscriptions: [
|
|
70
|
+
{ topicFilter: "a/b", qos: 0 },
|
|
71
|
+
{ topicFilter: "c/d", qos: 1 },
|
|
72
|
+
],
|
|
73
|
+
},
|
|
74
|
+
);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("decode Subscribe missing bit 1 flag", () => {
|
|
78
|
+
assert.throws(
|
|
79
|
+
() =>
|
|
80
|
+
decode(
|
|
81
|
+
Uint8Array.from([
|
|
82
|
+
// fixedHeader
|
|
83
|
+
0x80, // packetType + flags
|
|
84
|
+
14, // remainingLength
|
|
85
|
+
// variableHeader
|
|
86
|
+
0, // id MSB
|
|
87
|
+
1, // id LSB
|
|
88
|
+
// payload
|
|
89
|
+
0, // topic filter length MSB
|
|
90
|
+
3, // topic filter length LSB
|
|
91
|
+
97, // 'a'
|
|
92
|
+
47, // '/'
|
|
93
|
+
98, // 'b'
|
|
94
|
+
0, // qos
|
|
95
|
+
0, // topic filter length MSB
|
|
96
|
+
3, // topic filter length LSB
|
|
97
|
+
99, // 'c'
|
|
98
|
+
47, // '/'
|
|
99
|
+
100, // 'd'
|
|
100
|
+
1, // qos
|
|
101
|
+
]),
|
|
102
|
+
),
|
|
103
|
+
Error,
|
|
104
|
+
"Invalid header",
|
|
105
|
+
);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("decode Subscribe packet too short", () => {
|
|
109
|
+
assert.throws(
|
|
110
|
+
() =>
|
|
111
|
+
decode(
|
|
112
|
+
Uint8Array.from([
|
|
113
|
+
// fixedHeader
|
|
114
|
+
0x82, // packetType + flags
|
|
115
|
+
14, // remainingLength
|
|
116
|
+
// variableHeader
|
|
117
|
+
0, // id MSB
|
|
118
|
+
1, // id LSB
|
|
119
|
+
// payload
|
|
120
|
+
0, // topic filter length MSB
|
|
121
|
+
3, // topic filter length LSB
|
|
122
|
+
97, // 'a'
|
|
123
|
+
47, // '/'
|
|
124
|
+
98, // 'b'
|
|
125
|
+
0, // qos
|
|
126
|
+
0, // topic filter length MSB
|
|
127
|
+
3, // topic filter length LSB
|
|
128
|
+
99, // 'c'
|
|
129
|
+
47, // '/'
|
|
130
|
+
1, // qos
|
|
131
|
+
]),
|
|
132
|
+
),
|
|
133
|
+
Error,
|
|
134
|
+
"Invalid qos",
|
|
135
|
+
);
|
|
136
|
+
assert.throws(
|
|
137
|
+
() =>
|
|
138
|
+
decode(
|
|
139
|
+
Uint8Array.from([
|
|
140
|
+
// fixedHeader
|
|
141
|
+
0x82, // packetType + flags
|
|
142
|
+
14, // remainingLength
|
|
143
|
+
// variableHeader
|
|
144
|
+
0, // id MSB
|
|
145
|
+
1, // id LSB
|
|
146
|
+
// payload
|
|
147
|
+
0, // topic filter length MSB
|
|
148
|
+
3, // topic filter length LSB
|
|
149
|
+
97, // 'a'
|
|
150
|
+
47, // '/'
|
|
151
|
+
98, // 'b'
|
|
152
|
+
0, // qos
|
|
153
|
+
0, // topic filter length MSB
|
|
154
|
+
3, // topic filter length LSB
|
|
155
|
+
99, // 'c'
|
|
156
|
+
]),
|
|
157
|
+
),
|
|
158
|
+
Error,
|
|
159
|
+
"too short",
|
|
160
|
+
);
|
|
161
|
+
assert.throws(
|
|
162
|
+
() =>
|
|
163
|
+
decode(
|
|
164
|
+
Uint8Array.from([
|
|
165
|
+
// fixedHeader
|
|
166
|
+
0x82, // packetType + flags
|
|
167
|
+
14, // remainingLength
|
|
168
|
+
// variableHeader
|
|
169
|
+
0, // id MSB
|
|
170
|
+
1, // id LSB
|
|
171
|
+
]),
|
|
172
|
+
),
|
|
173
|
+
Error,
|
|
174
|
+
"too short",
|
|
175
|
+
);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test("decode Subscribe packet invalid packet id with QoS > 0", () => {
|
|
179
|
+
assert.throws(
|
|
180
|
+
() =>
|
|
181
|
+
decode(
|
|
182
|
+
Uint8Array.from([
|
|
183
|
+
// fixedHeader
|
|
184
|
+
0x82, // packetType + flags
|
|
185
|
+
13, // remainingLength
|
|
186
|
+
// variableHeader
|
|
187
|
+
0, // id MSB
|
|
188
|
+
0, // id LSB
|
|
189
|
+
// payload
|
|
190
|
+
0, // topic filter length MSB
|
|
191
|
+
3, // topic filter length LSB
|
|
192
|
+
97, // 'a'
|
|
193
|
+
47, // '/'
|
|
194
|
+
98, // 'b'
|
|
195
|
+
0, // qos
|
|
196
|
+
0, // topic filter length MSB
|
|
197
|
+
2, // topic filter length LSB
|
|
198
|
+
99, // 'c'
|
|
199
|
+
47, // '/'
|
|
200
|
+
1, // qos
|
|
201
|
+
]),
|
|
202
|
+
),
|
|
203
|
+
Error,
|
|
204
|
+
"Invalid packet identifier",
|
|
205
|
+
);
|
|
206
|
+
assert.throws(
|
|
207
|
+
() =>
|
|
208
|
+
decode(
|
|
209
|
+
Uint8Array.from([
|
|
210
|
+
// fixedHeader
|
|
211
|
+
0x82, // packetType + flags
|
|
212
|
+
13, // remainingLength
|
|
213
|
+
// variableHeader
|
|
214
|
+
0, // id MSB
|
|
215
|
+
0, // id LSB
|
|
216
|
+
// payload
|
|
217
|
+
0, // topic filter length MSB
|
|
218
|
+
3, // topic filter length LSB
|
|
219
|
+
97, // 'a'
|
|
220
|
+
47, // '/'
|
|
221
|
+
98, // 'b'
|
|
222
|
+
0, // qos
|
|
223
|
+
0, // topic filter length MSB
|
|
224
|
+
2, // topic filter length LSB
|
|
225
|
+
99, // 'c'
|
|
226
|
+
47, // '/'
|
|
227
|
+
2, // qos
|
|
228
|
+
]),
|
|
229
|
+
),
|
|
230
|
+
Error,
|
|
231
|
+
"Invalid packet identifier",
|
|
232
|
+
);
|
|
233
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { PacketId, QoS, TopicFilter, TPacketType } from "./types.ts";
|
|
2
|
+
import { PacketType } from "./PacketType.ts";
|
|
3
|
+
import { BitMask } from "./BitMask.ts";
|
|
4
|
+
import { Encoder } from "./encoder.ts";
|
|
5
|
+
import { booleanFlag, Decoder, DecoderError } from "./decoder.ts";
|
|
6
|
+
|
|
7
|
+
export type SubscribePacket = {
|
|
8
|
+
type: TPacketType;
|
|
9
|
+
id: PacketId;
|
|
10
|
+
subscriptions: Subscription[];
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type Subscription = {
|
|
14
|
+
topicFilter: TopicFilter;
|
|
15
|
+
qos: QoS;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export default {
|
|
19
|
+
encode(packet: SubscribePacket) {
|
|
20
|
+
// Bits 3,2,1 and 0 of the fixed header of the SUBSCRIBE Control Packet are reserved and
|
|
21
|
+
// MUST be set to 0,0,1 and 0 respectively. The Server MUST treat any other value as
|
|
22
|
+
// malformed and close the Network Connection [MQTT-3.8.1-1].
|
|
23
|
+
const flags = 0b0010;
|
|
24
|
+
|
|
25
|
+
const encoder = new Encoder();
|
|
26
|
+
encoder.setInt16(packet.id);
|
|
27
|
+
for (const sub of packet.subscriptions) {
|
|
28
|
+
encoder.setTopic(sub.topicFilter);
|
|
29
|
+
encoder.setByte(sub.qos);
|
|
30
|
+
}
|
|
31
|
+
return { flags, bytes: encoder.done() };
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
decode(buffer: Uint8Array, flags: number): SubscribePacket {
|
|
35
|
+
// Bits 3,2,1 and 0 of the fixed header of the SUBSCRIBE Control Packet are reserved and
|
|
36
|
+
// MUST be set to 0,0,1 and 0 respectively. The Server MUST treat any other value as
|
|
37
|
+
// malformed and close the Network Connection [MQTT-3.8.1-1].
|
|
38
|
+
if (!booleanFlag(flags, BitMask.bit1)) {
|
|
39
|
+
throw new DecoderError("Invalid header");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const decoder = new Decoder(buffer);
|
|
43
|
+
const id = decoder.getInt16();
|
|
44
|
+
|
|
45
|
+
const subscriptions: Subscription[] = [];
|
|
46
|
+
// The payload of a SUBSCRIBE packet MUST contain at least one Topic Filter / QoS pair.
|
|
47
|
+
// A SUBSCRIBE packet with no payload is a protocol violation [MQTT-3.8.3-3].
|
|
48
|
+
do {
|
|
49
|
+
const topicFilter = decoder.getTopicFilter();
|
|
50
|
+
const qos = decoder.getByte();
|
|
51
|
+
if (qos !== 0 && qos !== 1 && qos !== 2) {
|
|
52
|
+
throw new DecoderError("Invalid qos");
|
|
53
|
+
}
|
|
54
|
+
if (qos > 0 && id === 0) {
|
|
55
|
+
throw new DecoderError("Invalid packet identifier");
|
|
56
|
+
}
|
|
57
|
+
subscriptions.push({
|
|
58
|
+
topicFilter: topicFilter,
|
|
59
|
+
qos,
|
|
60
|
+
});
|
|
61
|
+
} while (!decoder.atEnd());
|
|
62
|
+
decoder.done();
|
|
63
|
+
return {
|
|
64
|
+
type: PacketType.subscribe,
|
|
65
|
+
id,
|
|
66
|
+
subscriptions,
|
|
67
|
+
};
|
|
68
|
+
},
|
|
69
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { BitMask } from "./BitMask.ts";
|
|
2
|
+
import type { PacketType } from "./PacketType.ts";
|
|
3
|
+
import type { AuthenticationResult } from "./AuthenticationResult.ts";
|
|
4
|
+
|
|
5
|
+
export type TBitMask = typeof BitMask[keyof typeof BitMask];
|
|
6
|
+
export type TPacketType = typeof PacketType[keyof typeof PacketType];
|
|
7
|
+
export type TAuthenticationResult =
|
|
8
|
+
typeof AuthenticationResult[keyof typeof AuthenticationResult];
|
|
9
|
+
|
|
10
|
+
export type QoS = 0 | 1 | 2;
|
|
11
|
+
|
|
12
|
+
export type Payload = Uint8Array;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The MQTT topic to publish or subscribe to
|
|
16
|
+
*/
|
|
17
|
+
export type Topic = string;
|
|
18
|
+
|
|
19
|
+
export type TopicFilter = string;
|
|
20
|
+
|
|
21
|
+
export type Dup = boolean;
|
|
22
|
+
|
|
23
|
+
export type PacketId = number;
|
|
24
|
+
|
|
25
|
+
export type ReturnCodes = number[];
|
|
26
|
+
|
|
27
|
+
export type ClientId = string;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { PacketType } from "./PacketType.ts";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { test } from "node:test";
|
|
4
|
+
|
|
5
|
+
import { decode, encode } from "./mod.ts";
|
|
6
|
+
|
|
7
|
+
test("encode Puback", () => {
|
|
8
|
+
assert.deepStrictEqual(
|
|
9
|
+
encode({
|
|
10
|
+
type: PacketType.unsuback,
|
|
11
|
+
id: 1337,
|
|
12
|
+
}),
|
|
13
|
+
Uint8Array.from([
|
|
14
|
+
// fixedHeader
|
|
15
|
+
0xb0, // packetType + flags
|
|
16
|
+
2, // remainingLength
|
|
17
|
+
// variableHeader
|
|
18
|
+
5, // id MSB
|
|
19
|
+
57, // id LSB
|
|
20
|
+
]),
|
|
21
|
+
);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("decode Puback ", () => {
|
|
25
|
+
assert.deepStrictEqual(
|
|
26
|
+
decode(
|
|
27
|
+
Uint8Array.from([
|
|
28
|
+
// fixedHeader
|
|
29
|
+
0xb0, // packetType + flags
|
|
30
|
+
2, // remainingLength
|
|
31
|
+
// variableHeader
|
|
32
|
+
5, // id MSB
|
|
33
|
+
57, // id LSB
|
|
34
|
+
]),
|
|
35
|
+
),
|
|
36
|
+
{
|
|
37
|
+
type: PacketType.unsuback,
|
|
38
|
+
id: 1337,
|
|
39
|
+
},
|
|
40
|
+
);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("decodeShortPubackPackets", () => {
|
|
44
|
+
assert.throws(
|
|
45
|
+
() => decode(Uint8Array.from([0xb0])),
|
|
46
|
+
Error,
|
|
47
|
+
"decoding failed",
|
|
48
|
+
);
|
|
49
|
+
assert.throws(() => decode(Uint8Array.from([0xb0, 2])), Error, "too short");
|
|
50
|
+
assert.throws(
|
|
51
|
+
() => decode(Uint8Array.from([0xb0, 3, 0, 0, 0])),
|
|
52
|
+
Error,
|
|
53
|
+
"too long",
|
|
54
|
+
);
|
|
55
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { PacketId, TPacketType } from "./types.ts";
|
|
2
|
+
import { PacketType } from "./PacketType.ts";
|
|
3
|
+
import { Decoder } from "./decoder.ts";
|
|
4
|
+
import { Encoder } from "./encoder.ts";
|
|
5
|
+
|
|
6
|
+
export type UnsubackPacket = {
|
|
7
|
+
type: TPacketType;
|
|
8
|
+
id: PacketId;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export default {
|
|
12
|
+
encode(packet: UnsubackPacket) {
|
|
13
|
+
const flags = 0;
|
|
14
|
+
const encoder = new Encoder();
|
|
15
|
+
encoder.setInt16(packet.id);
|
|
16
|
+
return { flags, bytes: encoder.done() };
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
decode(buffer: Uint8Array): UnsubackPacket {
|
|
20
|
+
const decoder = new Decoder(buffer);
|
|
21
|
+
const id = decoder.getInt16();
|
|
22
|
+
decoder.done();
|
|
23
|
+
return {
|
|
24
|
+
type: PacketType.unsuback,
|
|
25
|
+
id,
|
|
26
|
+
};
|
|
27
|
+
},
|
|
28
|
+
};
|