@seriousme/opifex 1.2.0 → 1.3.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/CHANGELOG.md +37 -0
- package/README.md +72 -4
- package/bin/conformanceServer.ts +1 -1
- package/bin/mqtt.ts +5 -2
- package/client/client.ts +3 -3
- package/client/context.ts +2 -0
- package/client/mod.ts +1 -2
- package/deno/client.ts +4 -3
- package/deno/server.ts +3 -2
- package/deno.json +4 -2
- package/deno.lock +0 -16
- package/dev_utils/dummyConn.ts +4 -4
- package/dist/bin/conformanceServer.js +52 -0
- package/dist/bin/demoServer.js +29 -57
- package/dist/bin/mqtt.js +148 -187
- package/dist/client/ConnectionState.js +7 -0
- package/dist/client/client.js +125 -0
- package/dist/client/context.js +148 -0
- package/dist/client/deps.js +5 -0
- package/dist/client/handlers/handleConnack.js +18 -0
- package/dist/client/handlers/handlePacket.js +50 -0
- package/dist/client/handlers/handlePuback.js +6 -0
- package/dist/client/handlers/handlePubcomp.js +9 -0
- package/dist/client/handlers/handlePublish.js +28 -0
- package/dist/client/handlers/handlePubrec.js +17 -0
- package/dist/client/handlers/handlePubrel.js +19 -0
- package/dist/client/handlers/handleSuback.js +9 -0
- package/dist/client/handlers/handleUnsuback.js +7 -0
- package/dist/client/mod.js +5 -3
- package/dist/client/store/deps.js +1 -0
- package/dist/client/store/memoryStore.js +36 -0
- package/dist/client/store/store.js +1 -0
- package/dist/mqttConn/mqttConn.js +104 -0
- package/dist/mqttPacket/AuthenticationResult.js +12 -0
- package/dist/mqttPacket/BitMask.js +10 -0
- package/dist/mqttPacket/PacketType.js +18 -0
- package/dist/mqttPacket/connack.js +27 -0
- package/dist/mqttPacket/connect.js +115 -0
- package/dist/mqttPacket/decoder.js +89 -0
- package/dist/mqttPacket/disconnect.js +14 -0
- package/dist/mqttPacket/encoder.js +56 -0
- package/dist/mqttPacket/length.js +43 -0
- package/dist/mqttPacket/mod.js +71 -0
- package/dist/mqttPacket/pingreq.js +15 -0
- package/dist/mqttPacket/pingres.js +14 -0
- package/dist/mqttPacket/puback.js +20 -0
- package/dist/mqttPacket/pubcomp.js +20 -0
- package/dist/mqttPacket/publish.js +51 -0
- package/dist/mqttPacket/pubrec.js +20 -0
- package/dist/mqttPacket/pubrel.js +20 -0
- package/dist/mqttPacket/suback.js +29 -0
- package/dist/mqttPacket/subscribe.js +52 -0
- package/dist/mqttPacket/types.js +1 -0
- package/dist/mqttPacket/unsuback.js +20 -0
- package/dist/mqttPacket/unsubscribe.js +36 -0
- package/dist/mqttPacket/validators.js +15 -0
- package/dist/node/client.js +65 -0
- package/dist/node/server.js +22 -0
- package/dist/node/wrapNodeSocket.js +47 -0
- package/dist/persistence/deps.js +3 -0
- package/dist/persistence/memory/memoryPersistence.js +117 -0
- package/dist/persistence/persistence.js +1 -0
- package/dist/persistence/store.js +1 -0
- package/dist/server/context.js +100 -0
- package/dist/server/deps.js +5 -0
- package/dist/server/handlers/handleConnect.js +46 -0
- package/dist/server/handlers/handleDisconnect.js +4 -0
- package/dist/server/handlers/handlePacket.js +58 -0
- package/dist/server/handlers/handlePingreq.js +6 -0
- package/dist/server/handlers/handlePuback.js +8 -0
- package/dist/server/handlers/handlePubcomp.js +8 -0
- package/dist/server/handlers/handlePublish.js +40 -0
- package/dist/server/handlers/handlePubrec.js +15 -0
- package/dist/server/handlers/handlePubrel.js +18 -0
- package/dist/server/handlers/handleSubscribe.js +33 -0
- package/dist/server/handlers/handleUnsubscribe.js +12 -0
- package/dist/server/mod.js +7 -589
- package/dist/server/server.js +47 -0
- package/dist/socket/socket.js +36 -0
- package/dist/timer/timer.js +40 -0
- package/dist/trie/trie.js +95 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/utils/args.js +61 -0
- package/dist/utils/assert.js +19 -0
- package/dist/utils/asyncQueue.js +56 -0
- package/dist/utils/deferred.js +11 -0
- package/dist/utils/logger.js +29 -0
- package/dist/utils/mod.js +7 -0
- package/dist/utils/nextTick.js +3 -0
- package/dist/utils/platform.js +4 -0
- package/mqttConn/mqttConn.ts +4 -1
- package/node/README.md +3 -1
- package/node/client.ts +30 -6
- package/node/server.ts +2 -0
- package/node/wrapNodeSocket.ts +4 -4
- package/package.json +16 -3
- package/server/mod.ts +1 -2
- package/socket/socket.ts +3 -2
- package/timer/timer.ts +1 -1
- package/tsconfig.json +3 -0
- package/.vscode/extensions.json +0 -10
- package/.vscode/settings.json +0 -12
- package/deno/wrapDenoConn.ts +0 -12
- package/dist/client-DqLTBVC2.js +0 -541
- package/dist/timer-DDWVNsyG.js +0 -924
- package/dist/wrapNodeSocket-BAJm059T.js +0 -115
package/dist/timer-DDWVNsyG.js
DELETED
|
@@ -1,924 +0,0 @@
|
|
|
1
|
-
import "node:process";
|
|
2
|
-
|
|
3
|
-
const PacketType = {
|
|
4
|
-
reserved: 0,
|
|
5
|
-
connect: 1,
|
|
6
|
-
connack: 2,
|
|
7
|
-
publish: 3,
|
|
8
|
-
puback: 4,
|
|
9
|
-
pubrec: 5,
|
|
10
|
-
pubrel: 6,
|
|
11
|
-
pubcomp: 7,
|
|
12
|
-
subscribe: 8,
|
|
13
|
-
suback: 9,
|
|
14
|
-
unsubscribe: 10,
|
|
15
|
-
unsuback: 11,
|
|
16
|
-
pingreq: 12,
|
|
17
|
-
pingres: 13,
|
|
18
|
-
disconnect: 14,
|
|
19
|
-
};
|
|
20
|
-
const PacketNameByType = Object.fromEntries(
|
|
21
|
-
Object.entries(PacketType).map(([k, v]) => [v, k]),
|
|
22
|
-
);
|
|
23
|
-
|
|
24
|
-
const invalidTopicRegEx = new RegExp(/^$|\+|#|\x00|\uFFFD/);
|
|
25
|
-
const invalidTopicFilterRegEx = new RegExp(/^$|#.|[^\/]\+|\+[^\/]|\x00|\uFFFD/);
|
|
26
|
-
function invalidTopic(value) {
|
|
27
|
-
return invalidTopicRegEx.test(value);
|
|
28
|
-
}
|
|
29
|
-
function invalidTopicFilter(value) {
|
|
30
|
-
return invalidTopicFilterRegEx.test(value);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function encodeLength(n) {
|
|
34
|
-
const output = [];
|
|
35
|
-
let x = n;
|
|
36
|
-
do {
|
|
37
|
-
let encodedByte = x % 128;
|
|
38
|
-
x = Math.floor(x / 128);
|
|
39
|
-
if (x > 0) {
|
|
40
|
-
encodedByte = encodedByte | 128;
|
|
41
|
-
}
|
|
42
|
-
output.push(encodedByte);
|
|
43
|
-
} while (x > 0);
|
|
44
|
-
if (output.length > 4) {
|
|
45
|
-
throw Error("length encoding failed");
|
|
46
|
-
}
|
|
47
|
-
return output;
|
|
48
|
-
}
|
|
49
|
-
function getLengthDecoder() {
|
|
50
|
-
let numLengthBytes = 1;
|
|
51
|
-
let length = 0;
|
|
52
|
-
let multiplier = 1;
|
|
53
|
-
return function addLengthByte(encodedByte) {
|
|
54
|
-
length += (encodedByte & 127) * multiplier;
|
|
55
|
-
multiplier *= 128;
|
|
56
|
-
if ((encodedByte & 128) === 0) {
|
|
57
|
-
return { done: true, length, numLengthBytes };
|
|
58
|
-
}
|
|
59
|
-
if (numLengthBytes++ >= 4) {
|
|
60
|
-
throw Error("length decoding failed");
|
|
61
|
-
}
|
|
62
|
-
return { done: false, length, numLengthBytes };
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const BitMask = {
|
|
67
|
-
bit0: 2 ** 0,
|
|
68
|
-
bit1: 2 ** 1,
|
|
69
|
-
bit2: 2 ** 2,
|
|
70
|
-
bit3: 2 ** 3,
|
|
71
|
-
bit4: 2 ** 4,
|
|
72
|
-
bit5: 2 ** 5,
|
|
73
|
-
bit6: 2 ** 6,
|
|
74
|
-
bit7: 2 ** 7,
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
const utf8Encoder = new TextEncoder();
|
|
78
|
-
class EncoderError extends Error {
|
|
79
|
-
constructor(message) {
|
|
80
|
-
super(message);
|
|
81
|
-
this.name = "EncoderError";
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
class Encoder {
|
|
85
|
-
buffer;
|
|
86
|
-
constructor() {
|
|
87
|
-
this.buffer = [];
|
|
88
|
-
}
|
|
89
|
-
setByte(value) {
|
|
90
|
-
this.buffer.push(value);
|
|
91
|
-
return this;
|
|
92
|
-
}
|
|
93
|
-
setInt16(value) {
|
|
94
|
-
this.setByte(value >> 8);
|
|
95
|
-
this.setByte(value & 255);
|
|
96
|
-
return this;
|
|
97
|
-
}
|
|
98
|
-
setByteArray(value) {
|
|
99
|
-
if (value.length > 65535) {
|
|
100
|
-
throw new EncoderError("More than 0xffff bytes of data");
|
|
101
|
-
}
|
|
102
|
-
this.setInt16(value.length);
|
|
103
|
-
this.buffer.push(...value);
|
|
104
|
-
return this;
|
|
105
|
-
}
|
|
106
|
-
setUtf8String(value) {
|
|
107
|
-
this.setByteArray(utf8Encoder.encode(value));
|
|
108
|
-
return this;
|
|
109
|
-
}
|
|
110
|
-
setTopic(value) {
|
|
111
|
-
if (invalidTopic(value)) {
|
|
112
|
-
throw new EncoderError(
|
|
113
|
-
"Topic must contain valid UTF-8 and contain more than 1 byte and no wildcards",
|
|
114
|
-
);
|
|
115
|
-
}
|
|
116
|
-
this.setUtf8String(value);
|
|
117
|
-
return this;
|
|
118
|
-
}
|
|
119
|
-
setTopicFilter(value) {
|
|
120
|
-
if (invalidTopicFilter(value)) {
|
|
121
|
-
throw new EncoderError(
|
|
122
|
-
"Topicfilter must contain valid UTF-8 and contain more than 1 byte and valid wildcards",
|
|
123
|
-
);
|
|
124
|
-
}
|
|
125
|
-
this.setUtf8String(value);
|
|
126
|
-
return this;
|
|
127
|
-
}
|
|
128
|
-
setRemainder(value) {
|
|
129
|
-
this.buffer.push(...value);
|
|
130
|
-
return this;
|
|
131
|
-
}
|
|
132
|
-
done() {
|
|
133
|
-
return this.buffer;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const utf8Decoder = new TextDecoder("utf-8");
|
|
138
|
-
function booleanFlag(byte, mask) {
|
|
139
|
-
return !!(byte & mask);
|
|
140
|
-
}
|
|
141
|
-
function isEmptyBuf(buf) {
|
|
142
|
-
if (buf.length > 0) {
|
|
143
|
-
throw new DecoderError("Packet too long");
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
function hasEmptyFlags(flags) {
|
|
147
|
-
if (flags !== 0) {
|
|
148
|
-
throw new DecoderError("Invalid fixed header flags");
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
class DecoderError extends Error {
|
|
152
|
-
constructor(message) {
|
|
153
|
-
super(message);
|
|
154
|
-
this.name = "DecoderError";
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
class Decoder {
|
|
158
|
-
buf;
|
|
159
|
-
pos;
|
|
160
|
-
len;
|
|
161
|
-
constructor(buf, pos = 0) {
|
|
162
|
-
this.buf = buf;
|
|
163
|
-
this.pos = pos;
|
|
164
|
-
this.len = buf.length;
|
|
165
|
-
}
|
|
166
|
-
checkpos(pos) {
|
|
167
|
-
if (pos > this.len) {
|
|
168
|
-
throw new DecoderError("Packet too short");
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
getByte() {
|
|
172
|
-
this.checkpos(this.pos);
|
|
173
|
-
return this.buf[this.pos++];
|
|
174
|
-
}
|
|
175
|
-
getInt16() {
|
|
176
|
-
const msb = this.getByte();
|
|
177
|
-
const lsb = this.getByte();
|
|
178
|
-
return msb << 8 | lsb;
|
|
179
|
-
}
|
|
180
|
-
getByteArray() {
|
|
181
|
-
const len = this.getInt16();
|
|
182
|
-
const start = this.pos;
|
|
183
|
-
const end = this.pos + len;
|
|
184
|
-
this.pos = end;
|
|
185
|
-
this.checkpos(end);
|
|
186
|
-
return this.buf.subarray(start, end);
|
|
187
|
-
}
|
|
188
|
-
getUtf8String() {
|
|
189
|
-
const str = utf8Decoder.decode(this.getByteArray());
|
|
190
|
-
return str;
|
|
191
|
-
}
|
|
192
|
-
getTopic() {
|
|
193
|
-
const topic = this.getUtf8String();
|
|
194
|
-
if (invalidTopic(topic)) {
|
|
195
|
-
throw new DecoderError(
|
|
196
|
-
"Topic must contain valid UTF-8 and contain more than 1 byte and no wildcards",
|
|
197
|
-
);
|
|
198
|
-
}
|
|
199
|
-
return topic;
|
|
200
|
-
}
|
|
201
|
-
getTopicFilter() {
|
|
202
|
-
const topicFilter = this.getUtf8String();
|
|
203
|
-
if (invalidTopicFilter(topicFilter)) {
|
|
204
|
-
throw new DecoderError(
|
|
205
|
-
"Topicfilter must contain valid UTF-8 and contain more than 1 byte and valid wildcards",
|
|
206
|
-
);
|
|
207
|
-
}
|
|
208
|
-
return topicFilter;
|
|
209
|
-
}
|
|
210
|
-
getRemainder() {
|
|
211
|
-
const start = this.pos;
|
|
212
|
-
const end = this.len;
|
|
213
|
-
this.pos = end;
|
|
214
|
-
return this.buf.subarray(start, end);
|
|
215
|
-
}
|
|
216
|
-
atEnd() {
|
|
217
|
-
if (this.len === this.pos) {
|
|
218
|
-
return true;
|
|
219
|
-
}
|
|
220
|
-
return false;
|
|
221
|
-
}
|
|
222
|
-
done() {
|
|
223
|
-
if (this.atEnd()) {
|
|
224
|
-
return true;
|
|
225
|
-
}
|
|
226
|
-
throw new DecoderError("Packet too long");
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
function invalidProtocolName(version, name) {
|
|
231
|
-
if (version === 4 && name !== "MQTT") {
|
|
232
|
-
return true;
|
|
233
|
-
}
|
|
234
|
-
return false;
|
|
235
|
-
}
|
|
236
|
-
var connect = {
|
|
237
|
-
encode(packet) {
|
|
238
|
-
const flags = 0;
|
|
239
|
-
const protocolLevel = 4;
|
|
240
|
-
const protocolName = "MQTT";
|
|
241
|
-
const clientId = packet.clientId || "";
|
|
242
|
-
const usernameFlag = !!packet.username;
|
|
243
|
-
const passwordFlag = !!packet.password;
|
|
244
|
-
const willRetain = !!packet.will?.retain;
|
|
245
|
-
const willQoS = packet.will?.qos || 0;
|
|
246
|
-
const willFlag = !!packet.will;
|
|
247
|
-
const cleanSession = packet.clean !== false;
|
|
248
|
-
const connectFlags = (usernameFlag ? BitMask.bit7 : 0) +
|
|
249
|
-
(passwordFlag ? BitMask.bit6 : 0) + (willRetain ? BitMask.bit5 : 0) +
|
|
250
|
-
(willQoS & 2 ? BitMask.bit4 : 0) + (willQoS & 1 ? BitMask.bit3 : 0) +
|
|
251
|
-
(willFlag ? BitMask.bit2 : 0) + (cleanSession ? BitMask.bit1 : 0);
|
|
252
|
-
const keepAlive = packet.keepAlive || 0;
|
|
253
|
-
const encoder = new Encoder();
|
|
254
|
-
encoder.setUtf8String(protocolName).setByte(protocolLevel).setByte(
|
|
255
|
-
connectFlags,
|
|
256
|
-
).setInt16(keepAlive).setUtf8String(clientId);
|
|
257
|
-
if (packet.will) {
|
|
258
|
-
encoder.setTopic(packet.will.topic).setByteArray(packet.will.payload);
|
|
259
|
-
}
|
|
260
|
-
if (packet.username) {
|
|
261
|
-
encoder.setUtf8String(packet.username);
|
|
262
|
-
}
|
|
263
|
-
if (packet.password) {
|
|
264
|
-
encoder.setByteArray(packet.password);
|
|
265
|
-
}
|
|
266
|
-
return { flags, bytes: encoder.done() };
|
|
267
|
-
},
|
|
268
|
-
decode(buffer, flags) {
|
|
269
|
-
const decoder = new Decoder(buffer);
|
|
270
|
-
const protocolName = decoder.getUtf8String();
|
|
271
|
-
const protocolLevel = decoder.getByte();
|
|
272
|
-
if (invalidProtocolName(protocolLevel, protocolName)) {
|
|
273
|
-
throw new DecoderError("Invalid protocol name");
|
|
274
|
-
}
|
|
275
|
-
const connectFlags = decoder.getByte();
|
|
276
|
-
const usernameFlag = booleanFlag(connectFlags, BitMask.bit7);
|
|
277
|
-
const passwordFlag = booleanFlag(connectFlags, BitMask.bit6);
|
|
278
|
-
const willRetain = booleanFlag(connectFlags, BitMask.bit5);
|
|
279
|
-
const willQoS = (connectFlags & BitMask.bit4 + BitMask.bit3) >> 3;
|
|
280
|
-
const willFlag = booleanFlag(connectFlags, BitMask.bit2);
|
|
281
|
-
const cleanSession = booleanFlag(connectFlags, BitMask.bit1);
|
|
282
|
-
const reservedBit = booleanFlag(connectFlags, BitMask.bit0);
|
|
283
|
-
hasEmptyFlags(flags);
|
|
284
|
-
if (reservedBit) {
|
|
285
|
-
throw new DecoderError("Invalid reserved bit");
|
|
286
|
-
}
|
|
287
|
-
if (willQoS !== 0 && willQoS !== 1 && willQoS !== 2) {
|
|
288
|
-
throw new DecoderError("Invalid will qos");
|
|
289
|
-
}
|
|
290
|
-
const keepAlive = decoder.getInt16();
|
|
291
|
-
const clientId = decoder.getUtf8String();
|
|
292
|
-
let willTopic;
|
|
293
|
-
let willPayload;
|
|
294
|
-
if (willFlag) {
|
|
295
|
-
willTopic = decoder.getTopic();
|
|
296
|
-
willPayload = decoder.getByteArray();
|
|
297
|
-
}
|
|
298
|
-
let username;
|
|
299
|
-
let password;
|
|
300
|
-
if (usernameFlag) {
|
|
301
|
-
username = decoder.getUtf8String();
|
|
302
|
-
}
|
|
303
|
-
if (passwordFlag) {
|
|
304
|
-
password = decoder.getByteArray();
|
|
305
|
-
}
|
|
306
|
-
decoder.done();
|
|
307
|
-
if (!willFlag && (willQoS !== 0 || willRetain === true)) {
|
|
308
|
-
throw new DecoderError(
|
|
309
|
-
"Will QoS must be 0 and Will retain to false when Will flag is false",
|
|
310
|
-
);
|
|
311
|
-
}
|
|
312
|
-
if (clientId.length === 0 && cleanSession === false) {
|
|
313
|
-
throw new DecoderError("Clean session must be true if clientID is empty");
|
|
314
|
-
}
|
|
315
|
-
return {
|
|
316
|
-
type: PacketType.connect,
|
|
317
|
-
protocolName,
|
|
318
|
-
protocolLevel,
|
|
319
|
-
clientId,
|
|
320
|
-
username: username ? username : void 0,
|
|
321
|
-
password: password ? password : void 0,
|
|
322
|
-
will: willFlag
|
|
323
|
-
? {
|
|
324
|
-
topic: willTopic || "",
|
|
325
|
-
payload: willPayload || Uint8Array.from([0]),
|
|
326
|
-
retain: willRetain,
|
|
327
|
-
qos: willQoS,
|
|
328
|
-
}
|
|
329
|
-
: void 0,
|
|
330
|
-
clean: cleanSession,
|
|
331
|
-
keepAlive,
|
|
332
|
-
};
|
|
333
|
-
},
|
|
334
|
-
};
|
|
335
|
-
|
|
336
|
-
const AuthenticationResult = {
|
|
337
|
-
ok: 0,
|
|
338
|
-
unacceptableProtocol: 1,
|
|
339
|
-
rejectedUsername: 2,
|
|
340
|
-
serverUnavailable: 3,
|
|
341
|
-
badUsernameOrPassword: 4,
|
|
342
|
-
notAuthorized: 5,
|
|
343
|
-
};
|
|
344
|
-
const AuthenticationResultByNumber = Object.fromEntries(
|
|
345
|
-
Object.entries(AuthenticationResult).map(([k, v]) => [v, k]),
|
|
346
|
-
);
|
|
347
|
-
|
|
348
|
-
var connack = {
|
|
349
|
-
encode(packet) {
|
|
350
|
-
const flags = 0;
|
|
351
|
-
return {
|
|
352
|
-
flags,
|
|
353
|
-
bytes: [packet.sessionPresent ? 1 : 0, packet.returnCode || 0],
|
|
354
|
-
};
|
|
355
|
-
},
|
|
356
|
-
decode(buffer, _flags) {
|
|
357
|
-
const decoder = new Decoder(buffer);
|
|
358
|
-
const sessionPresent = booleanFlag(decoder.getByte(), BitMask.bit0);
|
|
359
|
-
const returnCode = decoder.getByte();
|
|
360
|
-
decoder.done();
|
|
361
|
-
if (!AuthenticationResultByNumber[returnCode]) {
|
|
362
|
-
throw new DecoderError("Invalid return code");
|
|
363
|
-
}
|
|
364
|
-
return {
|
|
365
|
-
type: PacketType.connack,
|
|
366
|
-
sessionPresent,
|
|
367
|
-
returnCode,
|
|
368
|
-
};
|
|
369
|
-
},
|
|
370
|
-
};
|
|
371
|
-
|
|
372
|
-
var publish = {
|
|
373
|
-
encode(packet) {
|
|
374
|
-
const qos = packet.qos || 0;
|
|
375
|
-
const flags = (packet.dup ? BitMask.bit3 : 0) +
|
|
376
|
-
(qos & 2 ? BitMask.bit2 : 0) + (qos & 1 ? BitMask.bit1 : 0) +
|
|
377
|
-
(packet.retain ? BitMask.bit0 : 0);
|
|
378
|
-
const encoder = new Encoder();
|
|
379
|
-
encoder.setTopic(packet.topic);
|
|
380
|
-
if (qos === 1 || qos === 2) {
|
|
381
|
-
if (typeof packet.id !== "number" || packet.id < 1) {
|
|
382
|
-
throw new EncoderError("when qos is 1 or 2, packet must have id");
|
|
383
|
-
}
|
|
384
|
-
encoder.setInt16(packet.id);
|
|
385
|
-
}
|
|
386
|
-
encoder.setRemainder(packet.payload);
|
|
387
|
-
return { flags, bytes: encoder.done() };
|
|
388
|
-
},
|
|
389
|
-
decode(buffer, flags) {
|
|
390
|
-
const dup = booleanFlag(flags, BitMask.bit3);
|
|
391
|
-
const qos = (flags & 6) >> 1;
|
|
392
|
-
const retain = booleanFlag(flags, BitMask.bit0);
|
|
393
|
-
if (qos !== 0 && qos !== 1 && qos !== 2) {
|
|
394
|
-
throw new DecoderError("Invalid qos");
|
|
395
|
-
}
|
|
396
|
-
if (dup && qos === 0) {
|
|
397
|
-
throw new DecoderError("Invalid qos for possible duplicate");
|
|
398
|
-
}
|
|
399
|
-
const decoder = new Decoder(buffer);
|
|
400
|
-
const topic = decoder.getTopic();
|
|
401
|
-
let id = 0;
|
|
402
|
-
if (qos > 0) {
|
|
403
|
-
id = decoder.getInt16();
|
|
404
|
-
}
|
|
405
|
-
const payload = decoder.getRemainder();
|
|
406
|
-
return {
|
|
407
|
-
type: PacketType.publish,
|
|
408
|
-
topic,
|
|
409
|
-
payload,
|
|
410
|
-
dup,
|
|
411
|
-
retain,
|
|
412
|
-
qos,
|
|
413
|
-
id,
|
|
414
|
-
};
|
|
415
|
-
},
|
|
416
|
-
};
|
|
417
|
-
|
|
418
|
-
var puback = {
|
|
419
|
-
encode(packet) {
|
|
420
|
-
const flags = 0;
|
|
421
|
-
const encoder = new Encoder();
|
|
422
|
-
encoder.setInt16(packet.id);
|
|
423
|
-
return { flags, bytes: encoder.done() };
|
|
424
|
-
},
|
|
425
|
-
decode(buffer) {
|
|
426
|
-
const decoder = new Decoder(buffer);
|
|
427
|
-
const id = decoder.getInt16();
|
|
428
|
-
decoder.done();
|
|
429
|
-
return {
|
|
430
|
-
type: PacketType.puback,
|
|
431
|
-
id,
|
|
432
|
-
};
|
|
433
|
-
},
|
|
434
|
-
};
|
|
435
|
-
|
|
436
|
-
var pubrec = {
|
|
437
|
-
encode(packet) {
|
|
438
|
-
const flags = 0;
|
|
439
|
-
const encoder = new Encoder();
|
|
440
|
-
encoder.setInt16(packet.id);
|
|
441
|
-
return { flags, bytes: encoder.done() };
|
|
442
|
-
},
|
|
443
|
-
decode(buffer) {
|
|
444
|
-
const decoder = new Decoder(buffer);
|
|
445
|
-
const id = decoder.getInt16();
|
|
446
|
-
decoder.done();
|
|
447
|
-
return {
|
|
448
|
-
type: PacketType.pubrec,
|
|
449
|
-
id,
|
|
450
|
-
};
|
|
451
|
-
},
|
|
452
|
-
};
|
|
453
|
-
|
|
454
|
-
var pubrel = {
|
|
455
|
-
encode(packet) {
|
|
456
|
-
const flags = 0;
|
|
457
|
-
const encoder = new Encoder();
|
|
458
|
-
encoder.setInt16(packet.id);
|
|
459
|
-
return { flags, bytes: encoder.done() };
|
|
460
|
-
},
|
|
461
|
-
decode(buffer) {
|
|
462
|
-
const decoder = new Decoder(buffer);
|
|
463
|
-
const id = decoder.getInt16();
|
|
464
|
-
decoder.done();
|
|
465
|
-
return {
|
|
466
|
-
type: PacketType.pubrel,
|
|
467
|
-
id,
|
|
468
|
-
};
|
|
469
|
-
},
|
|
470
|
-
};
|
|
471
|
-
|
|
472
|
-
var pubcomp = {
|
|
473
|
-
encode(packet) {
|
|
474
|
-
const flags = 0;
|
|
475
|
-
const encoder = new Encoder();
|
|
476
|
-
encoder.setInt16(packet.id);
|
|
477
|
-
return { flags, bytes: encoder.done() };
|
|
478
|
-
},
|
|
479
|
-
decode(buffer) {
|
|
480
|
-
const decoder = new Decoder(buffer);
|
|
481
|
-
const id = decoder.getInt16();
|
|
482
|
-
decoder.done();
|
|
483
|
-
return {
|
|
484
|
-
type: PacketType.pubcomp,
|
|
485
|
-
id,
|
|
486
|
-
};
|
|
487
|
-
},
|
|
488
|
-
};
|
|
489
|
-
|
|
490
|
-
var subscribe = {
|
|
491
|
-
encode(packet) {
|
|
492
|
-
const flags = 2;
|
|
493
|
-
const encoder = new Encoder();
|
|
494
|
-
encoder.setInt16(packet.id);
|
|
495
|
-
for (const sub of packet.subscriptions) {
|
|
496
|
-
encoder.setTopic(sub.topicFilter);
|
|
497
|
-
encoder.setByte(sub.qos);
|
|
498
|
-
}
|
|
499
|
-
return { flags, bytes: encoder.done() };
|
|
500
|
-
},
|
|
501
|
-
decode(buffer, flags) {
|
|
502
|
-
if (!booleanFlag(flags, BitMask.bit1)) {
|
|
503
|
-
throw new DecoderError("Invalid header");
|
|
504
|
-
}
|
|
505
|
-
const decoder = new Decoder(buffer);
|
|
506
|
-
const id = decoder.getInt16();
|
|
507
|
-
const subscriptions = [];
|
|
508
|
-
do {
|
|
509
|
-
const topicFilter = decoder.getTopicFilter();
|
|
510
|
-
const qos = decoder.getByte();
|
|
511
|
-
if (qos !== 0 && qos !== 1 && qos !== 2) {
|
|
512
|
-
throw new DecoderError("Invalid qos");
|
|
513
|
-
}
|
|
514
|
-
if (qos > 0 && id === 0) {
|
|
515
|
-
throw new DecoderError("Invalid packet identifier");
|
|
516
|
-
}
|
|
517
|
-
subscriptions.push({
|
|
518
|
-
topicFilter,
|
|
519
|
-
qos,
|
|
520
|
-
});
|
|
521
|
-
} while (!decoder.atEnd());
|
|
522
|
-
decoder.done();
|
|
523
|
-
return {
|
|
524
|
-
type: PacketType.subscribe,
|
|
525
|
-
id,
|
|
526
|
-
subscriptions,
|
|
527
|
-
};
|
|
528
|
-
},
|
|
529
|
-
};
|
|
530
|
-
|
|
531
|
-
var suback = {
|
|
532
|
-
encode(packet) {
|
|
533
|
-
const flags = 0;
|
|
534
|
-
const encoder = new Encoder();
|
|
535
|
-
encoder.setInt16(packet.id);
|
|
536
|
-
encoder.setRemainder(packet.returnCodes);
|
|
537
|
-
return {
|
|
538
|
-
flags,
|
|
539
|
-
bytes: encoder.done(),
|
|
540
|
-
};
|
|
541
|
-
},
|
|
542
|
-
decode(buffer) {
|
|
543
|
-
const decoder = new Decoder(buffer);
|
|
544
|
-
const id = decoder.getInt16();
|
|
545
|
-
const payload = decoder.getRemainder();
|
|
546
|
-
const returnCodes = [];
|
|
547
|
-
for (const code of payload) {
|
|
548
|
-
returnCodes.push(code);
|
|
549
|
-
}
|
|
550
|
-
return {
|
|
551
|
-
type: PacketType.suback,
|
|
552
|
-
id,
|
|
553
|
-
returnCodes,
|
|
554
|
-
};
|
|
555
|
-
},
|
|
556
|
-
};
|
|
557
|
-
|
|
558
|
-
var unsubscribe = {
|
|
559
|
-
encode(packet) {
|
|
560
|
-
const flags = 2;
|
|
561
|
-
const encoder = new Encoder();
|
|
562
|
-
encoder.setInt16(packet.id);
|
|
563
|
-
for (const topicFilter of packet.topicFilters) {
|
|
564
|
-
encoder.setTopicFilter(topicFilter);
|
|
565
|
-
}
|
|
566
|
-
return { flags, bytes: encoder.done() };
|
|
567
|
-
},
|
|
568
|
-
decode(buffer, flags) {
|
|
569
|
-
if (!booleanFlag(flags, BitMask.bit1)) {
|
|
570
|
-
throw new DecoderError("Invalid header");
|
|
571
|
-
}
|
|
572
|
-
const decoder = new Decoder(buffer);
|
|
573
|
-
const id = decoder.getInt16();
|
|
574
|
-
const topicFilters = [];
|
|
575
|
-
do {
|
|
576
|
-
const topicFilter = decoder.getTopicFilter();
|
|
577
|
-
topicFilters.push(topicFilter);
|
|
578
|
-
} while (!decoder.atEnd());
|
|
579
|
-
decoder.done();
|
|
580
|
-
return {
|
|
581
|
-
type: PacketType.unsubscribe,
|
|
582
|
-
id,
|
|
583
|
-
topicFilters,
|
|
584
|
-
};
|
|
585
|
-
},
|
|
586
|
-
};
|
|
587
|
-
|
|
588
|
-
var unsuback = {
|
|
589
|
-
encode(packet) {
|
|
590
|
-
const flags = 0;
|
|
591
|
-
const encoder = new Encoder();
|
|
592
|
-
encoder.setInt16(packet.id);
|
|
593
|
-
return { flags, bytes: encoder.done() };
|
|
594
|
-
},
|
|
595
|
-
decode(buffer) {
|
|
596
|
-
const decoder = new Decoder(buffer);
|
|
597
|
-
const id = decoder.getInt16();
|
|
598
|
-
decoder.done();
|
|
599
|
-
return {
|
|
600
|
-
type: PacketType.unsuback,
|
|
601
|
-
id,
|
|
602
|
-
};
|
|
603
|
-
},
|
|
604
|
-
};
|
|
605
|
-
|
|
606
|
-
var pingreq = {
|
|
607
|
-
encode(_packet) {
|
|
608
|
-
const flags = 0;
|
|
609
|
-
return { flags, bytes: [] };
|
|
610
|
-
},
|
|
611
|
-
decode(buffer, flags) {
|
|
612
|
-
hasEmptyFlags(flags);
|
|
613
|
-
isEmptyBuf(buffer);
|
|
614
|
-
return {
|
|
615
|
-
type: PacketType.pingreq,
|
|
616
|
-
};
|
|
617
|
-
},
|
|
618
|
-
};
|
|
619
|
-
|
|
620
|
-
var pingres = {
|
|
621
|
-
encode(_packet) {
|
|
622
|
-
const flags = 0;
|
|
623
|
-
return { flags, bytes: [] };
|
|
624
|
-
},
|
|
625
|
-
decode(buffer) {
|
|
626
|
-
isEmptyBuf(buffer);
|
|
627
|
-
return {
|
|
628
|
-
type: PacketType.pingres,
|
|
629
|
-
};
|
|
630
|
-
},
|
|
631
|
-
};
|
|
632
|
-
|
|
633
|
-
var disconnect = {
|
|
634
|
-
encode(_packet) {
|
|
635
|
-
const flags = 0;
|
|
636
|
-
return { flags, bytes: [] };
|
|
637
|
-
},
|
|
638
|
-
decode(buffer, _flags) {
|
|
639
|
-
isEmptyBuf(buffer);
|
|
640
|
-
return {
|
|
641
|
-
type: PacketType.disconnect,
|
|
642
|
-
};
|
|
643
|
-
},
|
|
644
|
-
};
|
|
645
|
-
|
|
646
|
-
const packetsByType = [
|
|
647
|
-
null,
|
|
648
|
-
connect,
|
|
649
|
-
// 1
|
|
650
|
-
connack,
|
|
651
|
-
// 2
|
|
652
|
-
publish,
|
|
653
|
-
// 3
|
|
654
|
-
puback,
|
|
655
|
-
// 4
|
|
656
|
-
pubrec,
|
|
657
|
-
// 5
|
|
658
|
-
pubrel,
|
|
659
|
-
// 6
|
|
660
|
-
pubcomp,
|
|
661
|
-
// 7
|
|
662
|
-
subscribe,
|
|
663
|
-
// 8
|
|
664
|
-
suback,
|
|
665
|
-
// 9
|
|
666
|
-
unsubscribe,
|
|
667
|
-
// 10
|
|
668
|
-
unsuback,
|
|
669
|
-
// 11
|
|
670
|
-
pingreq,
|
|
671
|
-
// 12
|
|
672
|
-
pingres,
|
|
673
|
-
// 13
|
|
674
|
-
disconnect,
|
|
675
|
-
// 14
|
|
676
|
-
];
|
|
677
|
-
function encode(packet) {
|
|
678
|
-
const packetType = packet.type;
|
|
679
|
-
const pkt = packet;
|
|
680
|
-
const encoded = packetsByType[packetType]?.encode(pkt);
|
|
681
|
-
if (!encoded) {
|
|
682
|
-
throw Error("Packet encoding failed");
|
|
683
|
-
}
|
|
684
|
-
const { flags, bytes } = encoded;
|
|
685
|
-
return Uint8Array.from([
|
|
686
|
-
packetType << 4 | flags,
|
|
687
|
-
...encodeLength(bytes.length),
|
|
688
|
-
...bytes,
|
|
689
|
-
]);
|
|
690
|
-
}
|
|
691
|
-
function decodePayload(firstByte, buffer) {
|
|
692
|
-
const packetType = firstByte >> 4;
|
|
693
|
-
const flags = firstByte & 15;
|
|
694
|
-
const packet = packetsByType[packetType]?.decode(buffer, flags);
|
|
695
|
-
if (packet !== void 0) {
|
|
696
|
-
return packet;
|
|
697
|
-
}
|
|
698
|
-
throw new Error("packet decoding failed");
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
class AssertionError extends Error {
|
|
702
|
-
/** Constructs a new instance. */
|
|
703
|
-
constructor(message) {
|
|
704
|
-
super(message);
|
|
705
|
-
this.name = "AssertionError";
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
function assert(expr, msg = "") {
|
|
709
|
-
if (!expr) {
|
|
710
|
-
throw new AssertionError(msg);
|
|
711
|
-
}
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
const LogLevel = {
|
|
715
|
-
error: 0,
|
|
716
|
-
warn: 1,
|
|
717
|
-
info: 2,
|
|
718
|
-
verbose: 3,
|
|
719
|
-
debug: 4,
|
|
720
|
-
};
|
|
721
|
-
class Logger {
|
|
722
|
-
defaultError = console.error;
|
|
723
|
-
defaultWarn = console.warn;
|
|
724
|
-
defaultInfo = console.info;
|
|
725
|
-
defaultVerbose = console.log;
|
|
726
|
-
defaultDebug = console.log;
|
|
727
|
-
// deno-lint-ignore no-explicit-any
|
|
728
|
-
noop = (..._data) => {
|
|
729
|
-
};
|
|
730
|
-
error = this.defaultError;
|
|
731
|
-
warn = this.defaultWarn;
|
|
732
|
-
info = this.defaultInfo;
|
|
733
|
-
verbose = this.noop;
|
|
734
|
-
debug = this.noop;
|
|
735
|
-
constructor() {
|
|
736
|
-
}
|
|
737
|
-
level(logLevel) {
|
|
738
|
-
this.warn = logLevel > 0 ? this.defaultWarn : this.noop;
|
|
739
|
-
this.info = logLevel > 1 ? this.defaultInfo : this.noop;
|
|
740
|
-
this.verbose = logLevel > 2 ? this.defaultVerbose : this.noop;
|
|
741
|
-
this.debug = logLevel > 3 ? this.defaultDebug : this.noop;
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
const logger = new Logger();
|
|
745
|
-
|
|
746
|
-
class Conn {
|
|
747
|
-
reader;
|
|
748
|
-
writer;
|
|
749
|
-
closed;
|
|
750
|
-
remoteAddr;
|
|
751
|
-
closer;
|
|
752
|
-
constructor(sockConn) {
|
|
753
|
-
this.closed = false;
|
|
754
|
-
this.reader = sockConn.readable.getReader({ mode: "byob" });
|
|
755
|
-
this.writer = sockConn.writable.getWriter();
|
|
756
|
-
this.closer = sockConn.closer;
|
|
757
|
-
this.remoteAddr = sockConn.remoteAddr;
|
|
758
|
-
}
|
|
759
|
-
async read(buff) {
|
|
760
|
-
const buff2 = new Uint8Array(buff.length);
|
|
761
|
-
const result = await this.reader.read(buff2);
|
|
762
|
-
if (!result.done) {
|
|
763
|
-
buff.set(result.value);
|
|
764
|
-
}
|
|
765
|
-
return result.value?.byteLength || null;
|
|
766
|
-
}
|
|
767
|
-
write(data) {
|
|
768
|
-
if (this.closed) {
|
|
769
|
-
return Promise.reject();
|
|
770
|
-
}
|
|
771
|
-
this.writer.write(data);
|
|
772
|
-
return new Promise((resolve) => resolve(data.length));
|
|
773
|
-
}
|
|
774
|
-
close() {
|
|
775
|
-
if (!this.closed) {
|
|
776
|
-
this.closed = true;
|
|
777
|
-
this.closer();
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
const MqttConnError = {
|
|
783
|
-
invalidPacket: "Invalid Packet",
|
|
784
|
-
packetTooLarge: "Packet too large",
|
|
785
|
-
UnexpectedEof: "Unexpected EOF",
|
|
786
|
-
};
|
|
787
|
-
async function readByte(conn) {
|
|
788
|
-
const buf = new Uint8Array(1);
|
|
789
|
-
const bytesRead = await conn.read(buf);
|
|
790
|
-
assert(bytesRead !== null, MqttConnError.UnexpectedEof);
|
|
791
|
-
assert(bytesRead !== 0, MqttConnError.UnexpectedEof);
|
|
792
|
-
return buf[0];
|
|
793
|
-
}
|
|
794
|
-
async function readFull(conn, buf) {
|
|
795
|
-
let bytesRead = 0;
|
|
796
|
-
while (bytesRead < buf.length) {
|
|
797
|
-
const read = await conn.read(buf.subarray(bytesRead));
|
|
798
|
-
assert(read !== null, MqttConnError.UnexpectedEof);
|
|
799
|
-
assert(read !== 0, MqttConnError.UnexpectedEof);
|
|
800
|
-
bytesRead += read;
|
|
801
|
-
}
|
|
802
|
-
}
|
|
803
|
-
async function readPacket(conn, maxPacketSize) {
|
|
804
|
-
const decodeLength = getLengthDecoder();
|
|
805
|
-
const firstByte = await readByte(conn);
|
|
806
|
-
let result;
|
|
807
|
-
do {
|
|
808
|
-
const byte = await readByte(conn);
|
|
809
|
-
result = decodeLength(byte);
|
|
810
|
-
} while (!result.done);
|
|
811
|
-
const remainingLength = result.length;
|
|
812
|
-
assert(remainingLength < maxPacketSize - 1, MqttConnError.packetTooLarge);
|
|
813
|
-
const packetBuf = new Uint8Array(remainingLength);
|
|
814
|
-
await readFull(conn, packetBuf);
|
|
815
|
-
const packet = decodePayload(firstByte, packetBuf);
|
|
816
|
-
assert(packet !== null, MqttConnError.UnexpectedEof);
|
|
817
|
-
return packet;
|
|
818
|
-
}
|
|
819
|
-
class MqttConn {
|
|
820
|
-
conn;
|
|
821
|
-
maxPacketSize;
|
|
822
|
-
_reason = void 0;
|
|
823
|
-
_isClosed = false;
|
|
824
|
-
constructor({
|
|
825
|
-
conn,
|
|
826
|
-
maxPacketSize,
|
|
827
|
-
}) {
|
|
828
|
-
this.conn = new Conn(conn);
|
|
829
|
-
this.maxPacketSize = maxPacketSize || 2 * 1024 * 1024;
|
|
830
|
-
}
|
|
831
|
-
get reason() {
|
|
832
|
-
return this._reason;
|
|
833
|
-
}
|
|
834
|
-
async *[Symbol.asyncIterator]() {
|
|
835
|
-
while (!this._isClosed) {
|
|
836
|
-
try {
|
|
837
|
-
yield await readPacket(this.conn, this.maxPacketSize);
|
|
838
|
-
} catch (err) {
|
|
839
|
-
if (err instanceof Error) {
|
|
840
|
-
if (err.name === "PartialReadError") {
|
|
841
|
-
err.message = MqttConnError.UnexpectedEof;
|
|
842
|
-
}
|
|
843
|
-
this._reason = err.message;
|
|
844
|
-
}
|
|
845
|
-
this.close();
|
|
846
|
-
break;
|
|
847
|
-
}
|
|
848
|
-
}
|
|
849
|
-
}
|
|
850
|
-
async send(data) {
|
|
851
|
-
try {
|
|
852
|
-
await this.conn.write(encode(data));
|
|
853
|
-
} catch {
|
|
854
|
-
this.close();
|
|
855
|
-
}
|
|
856
|
-
}
|
|
857
|
-
get isClosed() {
|
|
858
|
-
return this._isClosed;
|
|
859
|
-
}
|
|
860
|
-
close() {
|
|
861
|
-
if (this.isClosed) return;
|
|
862
|
-
try {
|
|
863
|
-
this.conn.close();
|
|
864
|
-
} catch (e) {
|
|
865
|
-
console.error(e);
|
|
866
|
-
} finally {
|
|
867
|
-
this._isClosed = true;
|
|
868
|
-
}
|
|
869
|
-
}
|
|
870
|
-
}
|
|
871
|
-
|
|
872
|
-
class Timer {
|
|
873
|
-
delay;
|
|
874
|
-
// delay in microseconds
|
|
875
|
-
// deno-lint-ignore ban-types
|
|
876
|
-
action;
|
|
877
|
-
// function to perform when timer expires
|
|
878
|
-
timer = 0;
|
|
879
|
-
end = 0;
|
|
880
|
-
running = false;
|
|
881
|
-
// deno-lint-ignore ban-types
|
|
882
|
-
constructor(action, delay, wait = false) {
|
|
883
|
-
this.delay = delay;
|
|
884
|
-
this.action = action;
|
|
885
|
-
if (!wait) {
|
|
886
|
-
this.reset();
|
|
887
|
-
}
|
|
888
|
-
}
|
|
889
|
-
startTimer(delay) {
|
|
890
|
-
this.running = true;
|
|
891
|
-
this.timer = setTimeout(() => this.ring(), delay);
|
|
892
|
-
}
|
|
893
|
-
ring() {
|
|
894
|
-
const timeLeft = this.end - Date.now();
|
|
895
|
-
if (timeLeft > 0) {
|
|
896
|
-
this.startTimer(timeLeft);
|
|
897
|
-
return;
|
|
898
|
-
}
|
|
899
|
-
this.running = false;
|
|
900
|
-
this.action();
|
|
901
|
-
}
|
|
902
|
-
reset() {
|
|
903
|
-
this.end = Date.now() + this.delay;
|
|
904
|
-
if (!this.running) {
|
|
905
|
-
this.startTimer(this.delay);
|
|
906
|
-
}
|
|
907
|
-
}
|
|
908
|
-
clear() {
|
|
909
|
-
clearTimeout(this.timer);
|
|
910
|
-
this.running = false;
|
|
911
|
-
}
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
export {
|
|
915
|
-
assert as a,
|
|
916
|
-
AuthenticationResult as A,
|
|
917
|
-
AuthenticationResultByNumber as b,
|
|
918
|
-
logger as l,
|
|
919
|
-
LogLevel as L,
|
|
920
|
-
MqttConn as M,
|
|
921
|
-
PacketNameByType as c,
|
|
922
|
-
PacketType as P,
|
|
923
|
-
Timer as T,
|
|
924
|
-
};
|