@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,123 @@
1
+ import assert from "node:assert/strict";
2
+ import { test } from "node:test";
3
+
4
+ import { Decoder } from "./decoder.ts";
5
+
6
+ const utf8encoder = new TextEncoder();
7
+
8
+ test("decode byte", () => {
9
+ const byte = 127;
10
+ const decoder = new Decoder(Uint8Array.from([byte]));
11
+ assert.deepStrictEqual(decoder.getByte(), byte);
12
+ assert.deepStrictEqual(decoder.done(), true);
13
+ });
14
+
15
+ test("decode Int16", () => {
16
+ const value = 0xf0f2;
17
+ const msb = 0xf0;
18
+ const lsb = 0xf2;
19
+ const decoder = new Decoder(Uint8Array.from([msb, lsb]));
20
+ assert.deepStrictEqual(decoder.getInt16(), value);
21
+ assert.deepStrictEqual(decoder.done(), true);
22
+ });
23
+
24
+ test("decode Int16 with remainder", () => {
25
+ const value = 0xf0f2;
26
+ const msb = 0xf0;
27
+ const lsb = 0xf2;
28
+ const decoder = new Decoder(Uint8Array.from([msb, lsb, 0xff]));
29
+ assert.deepStrictEqual(decoder.getInt16(), value, "value is correct");
30
+ assert.deepStrictEqual(decoder.atEnd(), false);
31
+ });
32
+
33
+ test("decode byte array", () => {
34
+ const byteArray = new Array(300);
35
+ byteArray.fill(127);
36
+ const len = byteArray.length;
37
+ const decoder = new Decoder(
38
+ Uint8Array.from([len >> 8, len & 0xff, ...byteArray]),
39
+ );
40
+ assert.deepStrictEqual(decoder.getByteArray(), Uint8Array.from(byteArray));
41
+ assert.deepStrictEqual(decoder.done(), true);
42
+ });
43
+
44
+ test("decode byte array as remainder", () => {
45
+ const str = "hello world";
46
+ const byteArray = utf8encoder.encode(str);
47
+ const decoder = new Decoder(byteArray);
48
+ assert.deepStrictEqual(decoder.getRemainder(), byteArray);
49
+ assert.deepStrictEqual(decoder.done(), true);
50
+ });
51
+
52
+ test("decode byte array as empty remainder", () => {
53
+ const str = "hello world";
54
+ const emptyArray = Uint8Array.from([]);
55
+ const byteArray = utf8encoder.encode(str);
56
+ const len = byteArray.length;
57
+ const decoder = new Decoder(Uint8Array.from([0x00, len, ...byteArray]));
58
+ assert.deepStrictEqual(decoder.getUtf8String(), str);
59
+ assert.deepStrictEqual(decoder.getRemainder(), emptyArray);
60
+ assert.deepStrictEqual(decoder.done(), true);
61
+ });
62
+
63
+ test("decode string", () => {
64
+ const str = "hello world";
65
+ const byteArray = utf8encoder.encode(str);
66
+ const len = byteArray.length;
67
+ const decoder = new Decoder(Uint8Array.from([0x00, len, ...byteArray]));
68
+ assert.deepStrictEqual(decoder.getUtf8String(), str);
69
+ assert.deepStrictEqual(decoder.done(), true);
70
+ });
71
+
72
+ test("decode topic", () => {
73
+ const str = "hello world";
74
+ const byteArray = utf8encoder.encode(str);
75
+ const len = byteArray.length;
76
+ const decoder = new Decoder(Uint8Array.from([0x00, len, ...byteArray]));
77
+ assert.deepStrictEqual(decoder.getTopic(), str);
78
+ assert.deepStrictEqual(decoder.done(), true);
79
+ });
80
+
81
+ test("Topic too short", () => {
82
+ const decoder = new Decoder(Uint8Array.from([0x00, 0]));
83
+ assert.throws(
84
+ () => decoder.getTopic(),
85
+ Error,
86
+ "Topic must contain valid UTF-8 and contain more than 1 byte and no wildcards",
87
+ );
88
+ });
89
+
90
+ test("Invalid topic", () => {
91
+ const decoder = new Decoder(Uint8Array.from([0x00, 0x01, 0x00]));
92
+ assert.throws(
93
+ () => decoder.getTopic(),
94
+ Error,
95
+ "Topic must contain valid UTF-8 and contain more than 1 byte and no wildcards",
96
+ );
97
+ });
98
+
99
+ test("Invalid topicFilter", () => {
100
+ const decoder = new Decoder(Uint8Array.from([0x00, 0x01, 0x00]));
101
+ assert.throws(
102
+ () => decoder.getTopic(),
103
+ Error,
104
+ "Topic must contain valid UTF-8 and contain more than 1 byte and no wildcards",
105
+ );
106
+ });
107
+
108
+ test("Buffer too short", () => {
109
+ const str = "hello world";
110
+ const byteArray = utf8encoder.encode(str);
111
+ const len = byteArray.length;
112
+ const decoder = new Decoder(Uint8Array.from([0x00, len + 1, ...byteArray]));
113
+ assert.throws(() => decoder.getUtf8String(), Error, "too short");
114
+ });
115
+
116
+ test("Buffer too long", () => {
117
+ const str = "hello world";
118
+ const byteArray = utf8encoder.encode(str);
119
+ const len = byteArray.length;
120
+ const decoder = new Decoder(Uint8Array.from([0x00, len, ...byteArray, 0]));
121
+ assert.deepStrictEqual(decoder.getUtf8String(), str);
122
+ assert.throws(() => decoder.done(), Error, "too long");
123
+ });
@@ -0,0 +1,109 @@
1
+ import type { TBitMask, Topic, TopicFilter } from "./types.ts";
2
+ import { invalidTopic, invalidTopicFilter } from "./validators.ts";
3
+
4
+ const utf8Decoder = new TextDecoder("utf-8");
5
+
6
+ export function booleanFlag(byte: number, mask: TBitMask): boolean {
7
+ return !!(byte & mask);
8
+ }
9
+
10
+ export function isEmptyBuf(buf: Uint8Array): void {
11
+ if (buf.length > 0) {
12
+ throw new DecoderError("Packet too long");
13
+ }
14
+ }
15
+
16
+ export function hasEmptyFlags(flags: number): void {
17
+ if (flags !== 0) {
18
+ throw new DecoderError("Invalid fixed header flags");
19
+ }
20
+ }
21
+
22
+ export class DecoderError extends Error {
23
+ constructor(message: string) {
24
+ super(message);
25
+ this.name = "DecoderError";
26
+ }
27
+ }
28
+
29
+ export class Decoder {
30
+ private buf: Uint8Array;
31
+ private pos: number;
32
+ private len: number;
33
+
34
+ constructor(buf: Uint8Array, pos: number = 0) {
35
+ this.buf = buf;
36
+ this.pos = pos;
37
+ this.len = buf.length;
38
+ }
39
+ checkpos(pos: number): void {
40
+ if (pos > this.len) {
41
+ throw new DecoderError("Packet too short");
42
+ }
43
+ }
44
+
45
+ getByte(): number {
46
+ this.checkpos(this.pos);
47
+ return this.buf[this.pos++];
48
+ }
49
+
50
+ getInt16(): number {
51
+ const msb = this.getByte();
52
+ const lsb = this.getByte();
53
+ return (msb << 8) | lsb;
54
+ }
55
+
56
+ getByteArray(): Uint8Array {
57
+ const len = this.getInt16();
58
+ const start = this.pos;
59
+ const end = this.pos + len;
60
+ this.pos = end;
61
+ this.checkpos(end);
62
+ return this.buf.subarray(start, end);
63
+ }
64
+
65
+ getUtf8String(): string {
66
+ const str = utf8Decoder.decode(this.getByteArray());
67
+ return str;
68
+ }
69
+
70
+ getTopic(): Topic {
71
+ const topic = this.getUtf8String();
72
+ if (invalidTopic(topic)) {
73
+ throw new DecoderError(
74
+ "Topic must contain valid UTF-8 and contain more than 1 byte and no wildcards",
75
+ );
76
+ }
77
+ return topic;
78
+ }
79
+
80
+ getTopicFilter(): TopicFilter {
81
+ const topicFilter = this.getUtf8String();
82
+ if (invalidTopicFilter(topicFilter)) {
83
+ throw new DecoderError(
84
+ "Topicfilter must contain valid UTF-8 and contain more than 1 byte and valid wildcards",
85
+ );
86
+ }
87
+ return topicFilter;
88
+ }
89
+
90
+ getRemainder() {
91
+ const start = this.pos;
92
+ const end = this.len;
93
+ this.pos = end;
94
+ return this.buf.subarray(start, end);
95
+ }
96
+
97
+ atEnd(): boolean {
98
+ if (this.len === this.pos) {
99
+ return true;
100
+ }
101
+ return false;
102
+ }
103
+ done(): boolean {
104
+ if (this.atEnd()) {
105
+ return true;
106
+ }
107
+ throw new DecoderError("Packet too long");
108
+ }
109
+ }
@@ -0,0 +1,50 @@
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 Disconnect", () => {
8
+ assert.deepStrictEqual(
9
+ encode({
10
+ type: PacketType.disconnect,
11
+ }),
12
+ Uint8Array.from([
13
+ // fixedHeader
14
+ 224, // packetType + flags
15
+ 0, // remainingLength
16
+ ]),
17
+ );
18
+ });
19
+
20
+ test("decode Disconnect", () => {
21
+ assert.deepStrictEqual(
22
+ decode(
23
+ Uint8Array.from([
24
+ // fixedHeader
25
+ 224, // packetType + flags
26
+ 0, // remainingLength
27
+ ]),
28
+ ),
29
+ {
30
+ type: PacketType.disconnect,
31
+ },
32
+ );
33
+ });
34
+
35
+ test("decode invalid Disconnect", () => {
36
+ assert.throws(
37
+ () =>
38
+ decode(
39
+ Uint8Array.from([
40
+ // fixedHeader
41
+ 224, // packetType + flags
42
+ 2, // remainingLength
43
+ 0,
44
+ 0,
45
+ ]),
46
+ ),
47
+ Error,
48
+ "too long",
49
+ );
50
+ });
@@ -0,0 +1,21 @@
1
+ import { PacketType } from "./PacketType.ts";
2
+ import { isEmptyBuf } from "./decoder.ts";
3
+ import type { TPacketType } from "./types.ts";
4
+
5
+ export type DisconnectPacket = {
6
+ type: TPacketType;
7
+ };
8
+
9
+ export default {
10
+ encode(_packet: DisconnectPacket) {
11
+ const flags = 0;
12
+ return { flags, bytes: [] };
13
+ },
14
+
15
+ decode(buffer: Uint8Array, _flags: number): DisconnectPacket {
16
+ isEmptyBuf(buffer);
17
+ return {
18
+ type: PacketType.disconnect,
19
+ };
20
+ },
21
+ };
@@ -0,0 +1,39 @@
1
+ import assert from "node:assert/strict";
2
+ import { test } from "node:test";
3
+
4
+ import { Encoder } from "./encoder.ts";
5
+
6
+ const utf8Encoder = new TextEncoder();
7
+
8
+ test("encode byte", () => {
9
+ const byte = 127;
10
+ const encoder = new Encoder();
11
+ encoder.setByte(byte);
12
+ assert.deepStrictEqual(encoder.done(), [byte]);
13
+ });
14
+
15
+ test("encode byte array", () => {
16
+ const byteArray = new Array(300);
17
+ byteArray.fill(127);
18
+ const len = byteArray.length;
19
+ const encoder = new Encoder();
20
+ encoder.setByteArray(Uint8Array.from(byteArray));
21
+ assert.deepStrictEqual(encoder.done(), [len >> 8, len & 0xff, ...byteArray]);
22
+ });
23
+
24
+ test("encode byte array as remainder", () => {
25
+ const str = "hello world";
26
+ const byteArray = utf8Encoder.encode(str);
27
+ const encoder = new Encoder();
28
+ encoder.setRemainder(byteArray);
29
+ assert.deepStrictEqual(encoder.done(), [...byteArray]);
30
+ });
31
+
32
+ test("encode string", () => {
33
+ const str = "hello world";
34
+ const byteArray = utf8Encoder.encode(str);
35
+ const len = byteArray.length;
36
+ const encoder = new Encoder();
37
+ encoder.setUtf8String(str);
38
+ assert.deepStrictEqual(encoder.done(), [0x00, len, ...byteArray]);
39
+ });
@@ -0,0 +1,72 @@
1
+ const utf8Encoder = new TextEncoder();
2
+ import type { Topic, TopicFilter } from "./types.ts";
3
+ import { invalidTopic, invalidTopicFilter } from "./validators.ts";
4
+
5
+ export class EncoderError extends Error {
6
+ constructor(message: string) {
7
+ super(message);
8
+ this.name = "EncoderError";
9
+ }
10
+ }
11
+
12
+ export class Encoder {
13
+ private buffer: number[];
14
+
15
+ constructor() {
16
+ this.buffer = [];
17
+ }
18
+
19
+ setByte(value: number): this {
20
+ this.buffer.push(value);
21
+ return this;
22
+ }
23
+
24
+ setInt16(value: number): this {
25
+ this.setByte(value >> 8);
26
+ this.setByte(value & 0xff);
27
+ return this;
28
+ }
29
+
30
+ setByteArray(value: Uint8Array): this {
31
+ if (value.length > 0xffff) {
32
+ throw new EncoderError("More than 0xffff bytes of data");
33
+ }
34
+ this.setInt16(value.length);
35
+ this.buffer.push(...value);
36
+ return this;
37
+ }
38
+
39
+ setUtf8String(value: string): this {
40
+ this.setByteArray(utf8Encoder.encode(value));
41
+ return this;
42
+ }
43
+
44
+ setTopic(value: Topic): this {
45
+ if (invalidTopic(value)) {
46
+ throw new EncoderError(
47
+ "Topic must contain valid UTF-8 and contain more than 1 byte and no wildcards",
48
+ );
49
+ }
50
+ this.setUtf8String(value);
51
+ return this;
52
+ }
53
+
54
+ setTopicFilter(value: TopicFilter): this {
55
+ if (invalidTopicFilter(value)) {
56
+ throw new EncoderError(
57
+ "Topicfilter must contain valid UTF-8 and contain more than 1 byte and valid wildcards",
58
+ );
59
+ }
60
+ this.setUtf8String(value);
61
+ return this;
62
+ }
63
+
64
+ setRemainder(value: Uint8Array | number[]): this {
65
+ this.buffer.push(...value);
66
+ return this;
67
+ }
68
+
69
+ done(): number[] {
70
+ return this.buffer;
71
+ }
72
+ }
@@ -0,0 +1,75 @@
1
+ import assert from "node:assert/strict";
2
+ import { test } from "node:test";
3
+
4
+ import { decodeLength as decode, encodeLength as encode } from "./length.ts";
5
+
6
+ test("encodeLength", function encodeLength() {
7
+ assert.deepStrictEqual(encode(0), [0x00]);
8
+ assert.deepStrictEqual(encode(127), [0x7f]);
9
+ assert.deepStrictEqual(encode(128), [0x80, 0x01]);
10
+ assert.deepStrictEqual(encode(16_383), [0xff, 0x7f]);
11
+ assert.deepStrictEqual(encode(16_384), [0x80, 0x80, 0x01]);
12
+ assert.deepStrictEqual(encode(2_097_151), [0xff, 0xff, 0x7f]);
13
+ assert.deepStrictEqual(encode(2_097_152), [0x80, 0x80, 0x80, 0x01]);
14
+ assert.deepStrictEqual(encode(268_435_455), [0xff, 0xff, 0xff, 0x7f]);
15
+ assert.throws(() => encode(268_435_456), Error, "length encoding failed");
16
+ });
17
+
18
+ test("decodeLength", function decodeLength() {
19
+ assert.deepStrictEqual(decode(Uint8Array.from([0x00]), 0), {
20
+ length: 0,
21
+ numLengthBytes: 1,
22
+ });
23
+ assert.deepStrictEqual(decode(Uint8Array.from([0x7f]), 0), {
24
+ length: 127,
25
+ numLengthBytes: 1,
26
+ });
27
+ assert.deepStrictEqual(decode(Uint8Array.from([0x80, 0x01]), 0), {
28
+ length: 128,
29
+ numLengthBytes: 2,
30
+ });
31
+ assert.deepStrictEqual(decode(Uint8Array.from([0xff, 0x7f]), 0), {
32
+ length: 16_383,
33
+ numLengthBytes: 2,
34
+ });
35
+ assert.deepStrictEqual(decode(Uint8Array.from([0xff, 0x7f, 0xff]), 0), {
36
+ length: 16_383,
37
+ numLengthBytes: 2,
38
+ });
39
+ assert.deepStrictEqual(decode(Uint8Array.from([0x80, 0x80, 0x01]), 0), {
40
+ length: 16_384,
41
+ numLengthBytes: 3,
42
+ });
43
+ assert.deepStrictEqual(decode(Uint8Array.from([0xff, 0xff, 0x7f]), 0), {
44
+ length: 2_097_151,
45
+ numLengthBytes: 3,
46
+ });
47
+ assert.deepStrictEqual(decode(Uint8Array.from([0x80, 0x80, 0x80, 0x01]), 0), {
48
+ length: 2_097_152,
49
+ numLengthBytes: 4,
50
+ });
51
+ assert.deepStrictEqual(decode(Uint8Array.from([0xff, 0xff, 0xff, 0x7f]), 0), {
52
+ length: 268_435_455,
53
+ numLengthBytes: 4,
54
+ });
55
+ assert.throws(
56
+ () => decode(Uint8Array.from([0xff, 0xff, 0xff, 0xff]), 0),
57
+ Error,
58
+ "length decoding failed",
59
+ );
60
+ assert.throws(
61
+ () => decode(Uint8Array.from([0xff, 0xff, 0xff, 0xff, 0x7f]), 0),
62
+ Error,
63
+ "length decoding failed",
64
+ );
65
+ assert.throws(
66
+ () => decode(Uint8Array.from([0xff]), 0),
67
+ Error,
68
+ "length decoding failed",
69
+ );
70
+ assert.throws(
71
+ () => decode(Uint8Array.from([]), 0),
72
+ Error,
73
+ "length decoding failed",
74
+ );
75
+ });
@@ -0,0 +1,57 @@
1
+ export function encodeLength(n: number): number[] {
2
+ const output = [];
3
+ let x = n;
4
+ do {
5
+ let encodedByte = x % 0x80;
6
+ x = Math.floor(x / 0x80);
7
+ if (x > 0) {
8
+ encodedByte = encodedByte | 0x80;
9
+ }
10
+ output.push(encodedByte);
11
+ } while (x > 0);
12
+ if (output.length > 4) {
13
+ throw Error("length encoding failed");
14
+ }
15
+ return output;
16
+ }
17
+
18
+ export function decodeLength(
19
+ buf: Uint8Array,
20
+ start: number,
21
+ ): { length: number; numLengthBytes: number } {
22
+ const decode = getLengthDecoder();
23
+ const lenBuf = buf.subarray(start);
24
+ for (const byte of lenBuf) {
25
+ const { done, length, numLengthBytes } = decode(byte);
26
+ if (done) {
27
+ return { length, numLengthBytes };
28
+ }
29
+ }
30
+ throw Error("length decoding failed");
31
+ }
32
+
33
+ export type LengthDecoderResult = {
34
+ done: boolean;
35
+ length: number;
36
+ numLengthBytes: number;
37
+ };
38
+
39
+ export function getLengthDecoder(): (
40
+ encodedByte: number,
41
+ ) => LengthDecoderResult {
42
+ let numLengthBytes = 1;
43
+ let length = 0;
44
+ let multiplier = 1;
45
+
46
+ return function addLengthByte(encodedByte: number) {
47
+ length += (encodedByte & 0x7f) * multiplier;
48
+ multiplier *= 0x80;
49
+ if ((encodedByte & 0x80) === 0) {
50
+ return { done: true, length, numLengthBytes };
51
+ }
52
+ if (numLengthBytes++ >= 4) {
53
+ throw Error("length decoding failed");
54
+ }
55
+ return { done: false, length, numLengthBytes };
56
+ };
57
+ }
@@ -0,0 +1,151 @@
1
+ import type {
2
+ ClientId,
3
+ Dup,
4
+ PacketId,
5
+ Payload,
6
+ QoS,
7
+ ReturnCodes,
8
+ TAuthenticationResult,
9
+ Topic,
10
+ TopicFilter,
11
+ TPacketType,
12
+ } from "./types.ts";
13
+ import { PacketNameByType, PacketType } from "./PacketType.ts";
14
+ import { invalidTopic, invalidTopicFilter, invalidUTF8 } from "./validators.ts";
15
+ import { decodeLength, encodeLength } from "./length.ts";
16
+ import connect, { type ConnectPacket } from "./connect.ts";
17
+ import connack, { type ConnackPacket } from "./connack.ts";
18
+ import {
19
+ AuthenticationResult,
20
+ AuthenticationResultByNumber,
21
+ } from "./AuthenticationResult.ts";
22
+ import publish, { type PublishPacket } from "./publish.ts";
23
+ import puback, { type PubackPacket } from "./puback.ts";
24
+ import pubrec, { type PubrecPacket } from "./pubrec.ts";
25
+ import pubrel, { type PubrelPacket } from "./pubrel.ts";
26
+ import pubcomp, { type PubcompPacket } from "./pubcomp.ts";
27
+ import subscribe, { type SubscribePacket } from "./subscribe.ts";
28
+ import suback, { type SubackPacket } from "./suback.ts";
29
+ import unsubscribe, { type UnsubscribePacket } from "./unsubscribe.ts";
30
+ import unsuback, { type UnsubackPacket } from "./unsuback.ts";
31
+ import pingreq, { type PingreqPacket } from "./pingreq.ts";
32
+ import pingres, { type PingresPacket } from "./pingres.ts";
33
+ import disconnect, { type DisconnectPacket } from "./disconnect.ts";
34
+ import { DecoderError } from "./decoder.ts";
35
+
36
+ export type AnyPacket =
37
+ | ConnectPacket
38
+ | ConnackPacket
39
+ | PublishPacket
40
+ | PubackPacket
41
+ | PubrecPacket
42
+ | PubrelPacket
43
+ | PubcompPacket
44
+ | SubscribePacket
45
+ | SubackPacket
46
+ | UnsubscribePacket
47
+ | UnsubackPacket
48
+ | PingreqPacket
49
+ | PingresPacket
50
+ | DisconnectPacket;
51
+
52
+ export type {
53
+ ClientId,
54
+ ConnackPacket,
55
+ ConnectPacket,
56
+ DisconnectPacket,
57
+ Dup,
58
+ PacketId,
59
+ Payload,
60
+ PingreqPacket,
61
+ PingresPacket,
62
+ PubackPacket,
63
+ PubcompPacket,
64
+ PublishPacket,
65
+ PubrecPacket,
66
+ PubrelPacket,
67
+ QoS,
68
+ ReturnCodes,
69
+ SubackPacket,
70
+ SubscribePacket,
71
+ TAuthenticationResult,
72
+ Topic,
73
+ TopicFilter,
74
+ TPacketType,
75
+ UnsubackPacket,
76
+ UnsubscribePacket,
77
+ };
78
+
79
+ export type { Subscription } from "./subscribe.ts";
80
+
81
+ export {
82
+ AuthenticationResult,
83
+ AuthenticationResultByNumber,
84
+ decodeLength,
85
+ encodeLength,
86
+ invalidTopic,
87
+ invalidTopicFilter,
88
+ invalidUTF8,
89
+ PacketNameByType,
90
+ PacketType,
91
+ };
92
+
93
+ export const packetsByType = [
94
+ null,
95
+ connect, // 1
96
+ connack, // 2
97
+ publish, // 3
98
+ puback, // 4
99
+ pubrec, // 5
100
+ pubrel, // 6
101
+ pubcomp, // 7
102
+ subscribe, // 8
103
+ suback, // 9
104
+ unsubscribe, // 10
105
+ unsuback, // 11
106
+ pingreq, // 12
107
+ pingres, // 13
108
+ disconnect, // 14
109
+ ] as const;
110
+
111
+ export function encode(packet: AnyPacket) {
112
+ const packetType: number = packet.type;
113
+ // deno-lint-ignore no-explicit-any
114
+ const pkt: any = packet;
115
+ const encoded = packetsByType[packetType]?.encode(pkt);
116
+ if (!encoded) {
117
+ throw Error("Packet encoding failed");
118
+ }
119
+ const { flags, bytes } = encoded;
120
+ return Uint8Array.from([
121
+ (packetType << 4) | flags,
122
+ ...encodeLength(bytes.length),
123
+ ...bytes,
124
+ ]);
125
+ }
126
+
127
+ export function decodePayload(
128
+ firstByte: number,
129
+ buffer: Uint8Array,
130
+ ): AnyPacket {
131
+ const packetType = firstByte >> 4;
132
+ const flags = firstByte & 0x0f;
133
+ const packet = packetsByType[packetType]?.decode(buffer, flags);
134
+ if (packet !== undefined) {
135
+ return packet;
136
+ }
137
+ throw new Error("packet decoding failed");
138
+ }
139
+
140
+ export function decode(buffer: Uint8Array): AnyPacket {
141
+ if (buffer.length < 2) {
142
+ throw new DecoderError("Packet decoding failed");
143
+ }
144
+ const { length, numLengthBytes } = decodeLength(buffer, 1);
145
+ const start = numLengthBytes + 1;
146
+ const end = start + length;
147
+ return decodePayload(buffer[0], buffer.subarray(start, end));
148
+ }
149
+
150
+ export { getLengthDecoder } from "../mqttPacket/length.ts";
151
+ export type { LengthDecoderResult } from "../mqttPacket/length.ts";