@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,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
|
+
};
|