Skip to content

Commit 62adcbc

Browse files
committed
perf: ⚡️ inline connect parsing
1 parent eb9eace commit 62adcbc

File tree

7 files changed

+69
-87
lines changed

7 files changed

+69
-87
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
- MQTT packet encoder/decoder for Node.js.
44
- `mqtt-codec` is 4-10x faster than `mqtt-packet`, see benchmarks below and uses less memory.
5+
- Zero dependencies.
56

67
## Benchmarks
78

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@
4141
"@semantic-release/changelog": "^5.0.1",
4242
"@semantic-release/git": "^9.0.0",
4343
"@semantic-release/npm": "^7.0.6",
44-
"@types/bl": "^2.1.0",
4544
"@types/jest": "^26.0.14",
4645
"benchmark": "^2.1.4",
4746
"husky": "^4.3.0",

src/MqttDecoder.ts

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import {BufferList} from './BufferList';
22
import {ERROR, PACKET_TYPE} from './enums';
33
import {PacketConnack, parseConnack} from './packets/connack';
4-
import {PacketConnect, parseConnect} from './packets/connect';
4+
import {PacketConnect} from './packets/connect';
55
import {PacketPublish} from './packets/publish';
66
import {PacketPuback, parsePuback} from './packets/puback';
77
import {PacketPubrec, parsePubrec} from './packets/pubrec';
@@ -153,9 +153,52 @@ export class MqttDecoder {
153153
return new PacketPublish(b, l, t, i, p, d);
154154
}
155155
case PACKET_TYPE.CONNECT: {
156-
const packet = parseConnect(b, l, list, offset);
157-
this.version = packet.v;
156+
const buf = list.slice(offset, offset + l);
158157
list.consume(packetEndOffset);
158+
offset = 2 + buf.readUInt16BE(0); // Skip "MQTT" or "MQIsdp" protocol name.
159+
const v = buf.readUInt8(offset++);
160+
const f = buf.readUInt8(offset++);
161+
const k = buf.readUInt16BE(offset);
162+
offset += 2;
163+
// const ui32 = buf.readUInt32BE(offset);
164+
// const v = (ui32 & 0xFF000000) >> 24;
165+
// const f = (ui32 & 0xFF0000) >> 16;
166+
// const k = (ui32 & 0xFFFF);
167+
// offset += 4;
168+
let p: Properties = {};
169+
if (v === 5) {
170+
const [props, propsSize] = parseProps(buf, offset);
171+
p = props;
172+
offset += propsSize;
173+
}
174+
const clientId = parseBinary(buf, offset);
175+
const id = clientId.toString('utf8');
176+
offset += 2 + clientId.byteLength;
177+
const packet = new PacketConnect(b, l, v, f, k, p, id);
178+
if (packet.willFlag()) {
179+
if (v === 5) {
180+
const [props, propsSize] = parseProps(buf, offset);
181+
packet.wp = props;
182+
offset += propsSize;
183+
} else packet.wp = {};
184+
const willTopic = parseBinary(buf, offset);
185+
packet.wt = willTopic.toString('utf8');
186+
offset += 2 + willTopic.byteLength;
187+
const willPayload = parseBinary(buf, offset);
188+
packet.w = willPayload;
189+
offset += 2 + willPayload.byteLength;
190+
}
191+
if (packet.userNameFlag()) {
192+
const userName = parseBinary(buf, offset);
193+
packet.usr = userName.toString('utf8');
194+
offset += 2 + userName.byteLength;
195+
}
196+
if (packet.passwordFlag()) {
197+
const password = parseBinary(buf, offset);
198+
packet.pwd = password;
199+
offset += 2 + password.byteLength;
200+
}
201+
this.version = packet.v;
159202
return packet;
160203
}
161204
case PACKET_TYPE.CONNACK: {

src/packets/connect.ts

Lines changed: 0 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -69,45 +69,3 @@ export class PacketConnect extends Packet implements PacketConnectData {
6969
return !!(this.f & 0b00000010);
7070
}
7171
}
72-
73-
export const parseConnect = (b: number, l: number, data: BufferList, offset: number): PacketConnect => {
74-
offset += 2 + data.readUInt16BE(offset); // Skip "MQTT" or "MQIsdp" protocol name.
75-
const v = data.readUInt8(offset++);
76-
const f = data.readUInt8(offset++);
77-
const k = data.readUInt16BE(offset);
78-
offset += 2;
79-
let p: Properties = {};
80-
if (v === 5) {
81-
const [props, propsSize] = parseProps(data, offset);
82-
p = props;
83-
offset += propsSize;
84-
}
85-
const clientId = parseBinary(data, offset);
86-
const id = clientId.toString('utf8');
87-
offset += 2 + clientId.byteLength;
88-
const packet = new PacketConnect(b, l, v, f, k, p, id);
89-
if (packet.willFlag()) {
90-
if (v === 5) {
91-
const [props, propsSize] = parseProps(data, offset);
92-
packet.wp = props;
93-
offset += propsSize;
94-
} else packet.wp = {};
95-
const willTopic = parseBinary(data, offset);
96-
packet.wt = willTopic.toString('utf8');
97-
offset += 2 + willTopic.byteLength;
98-
const willPayload = parseBinary(data, offset);
99-
packet.w = willPayload;
100-
offset += 2 + willPayload.byteLength;
101-
}
102-
if (packet.userNameFlag()) {
103-
const userName = parseBinary(data, offset);
104-
packet.usr = userName.toString('utf8');
105-
offset += 2 + userName.byteLength;
106-
}
107-
if (packet.passwordFlag()) {
108-
const password = parseBinary(data, offset);
109-
packet.pwd = password;
110-
offset += 2 + password.byteLength;
111-
}
112-
return packet;
113-
};

src/types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,10 @@ export interface Properties {
3131
[PROPERTY.SubscriptionIdentifierAvailable]?: number;
3232
[PROPERTY.SharedSubscriptionAvailable]?: number;
3333
}
34+
35+
export interface BufferLike {
36+
readUInt8(offset: number): number;
37+
readUInt16BE(offset: number): number;
38+
readUInt32BE(offset: number): number;
39+
slice(start: number, end: number): Buffer;
40+
}

src/util/parse.ts

Lines changed: 15 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
import {BufferList} from '../BufferList';
21
import {ERROR, PROPERTY} from '../enums';
3-
import {Properties} from '../types';
2+
import {BufferLike, Properties} from '../types';
43

5-
export const parseVarInt = (data: BufferList, offset: number): [int: number, size: number] => {
4+
export const parseVarInt = (data: BufferLike, offset: number): [int: number, size: number] => {
65
const b1 = data.readUInt8(offset);
76
if (!(b1 & 0b10000000)) return [b1 & 0b01111111, 1];
87
const b2 = data.readUInt8(offset + 1);
@@ -13,37 +12,19 @@ export const parseVarInt = (data: BufferList, offset: number): [int: number, siz
1312
return [((b4 & 0b01111111) << 21) + ((b3 & 0b01111111) << 14) + ((b2 & 0b01111111) << 7) + (b1 & 0b01111111), 4];
1413
};
1514

16-
export const parseVarIntBuf = (buf: Buffer, offset: number): [int: number, size: number] => {
17-
const b1 = buf.readUInt8(offset);
18-
if (!(b1 & 0b10000000)) return [b1 & 0b01111111, 1];
19-
const b2 = buf.readUInt8(offset + 1);
20-
if (!(b2 & 0b10000000)) return [((b2 & 0b01111111) << 7) + (b1 & 0b01111111), 2];
21-
const b3 = buf.readUInt8(offset + 2);
22-
if (!(b3 & 0b10000000)) return [((b3 & 0b01111111) << 14) + ((b2 & 0b01111111) << 7) + (b1 & 0b01111111), 3];
23-
const b4 = buf.readUInt8(offset + 3);
24-
return [((b4 & 0b01111111) << 21) + ((b3 & 0b01111111) << 14) + ((b2 & 0b01111111) << 7) + (b1 & 0b01111111), 4];
25-
};
26-
27-
export const parseBinary = (list: BufferList, offset: number): Buffer => {
15+
export const parseBinary = (list: BufferLike, offset: number): Buffer => {
2816
const stringOffset = offset + 2;
2917
const dataLength = list.readUInt16BE(offset);
3018
return list.slice(stringOffset, stringOffset + dataLength);
3119
};
3220

33-
export const parseBinaryBuf = (buf: Buffer, offset: number): Buffer => {
34-
const stringOffset = offset + 2;
35-
const dataLength = buf.readUInt16BE(offset);
36-
return buf.slice(stringOffset, stringOffset + dataLength);
37-
};
38-
39-
export const parseProps = (data: BufferList, offset: number): [props: Properties, size: number] => {
21+
export const parseProps = (data: BufferLike, offset: number): [props: Properties, size: number] => {
4022
const [int, size] = parseVarInt(data, offset);
4123
offset += size;
42-
const buf = data.slice(offset, offset + int);
24+
const end = offset + int;
4325
const props: Properties = {};
44-
offset = 0;
45-
while (offset < int) {
46-
const byte = buf.readUInt8(offset++);
26+
while (offset < end) {
27+
const byte = data.readUInt8(offset++);
4728
switch (byte) {
4829
case PROPERTY.PayloadFormatIndicator:
4930
case PROPERTY.RequestProblemInformation:
@@ -53,35 +34,35 @@ export const parseProps = (data: BufferList, offset: number): [props: Properties
5334
case PROPERTY.WildcardSubscriptionAvailable:
5435
case PROPERTY.SubscriptionIdentifierAvailable:
5536
case PROPERTY.SharedSubscriptionAvailable: {
56-
props[byte] = buf.readUInt8(offset);
37+
props[byte] = data.readUInt8(offset);
5738
offset += 1;
5839
break;
5940
}
6041
case PROPERTY.ServerKeepAlive:
6142
case PROPERTY.ReceiveMaximum:
6243
case PROPERTY.TopicAliasMaximum:
6344
case PROPERTY.TopicAlias: {
64-
props[byte] = buf.readUInt16BE(offset);
45+
props[byte] = data.readUInt16BE(offset);
6546
offset += 2;
6647
break;
6748
}
6849
case PROPERTY.MessageExpiryInterval:
6950
case PROPERTY.WillDelayInterval:
7051
case PROPERTY.SessionExpiryInterval:
7152
case PROPERTY.MaximumPacketSize: {
72-
props[byte] = buf.readUInt32BE(offset);
53+
props[byte] = data.readUInt32BE(offset);
7354
offset += 4;
7455
break;
7556
}
7657
case PROPERTY.SubscriptionIdentifier: {
77-
const [value, size] = parseVarIntBuf(buf, offset);
58+
const [value, size] = parseVarInt(data, offset);
7859
props[byte] = value;
7960
offset += size;
8061
break;
8162
}
8263
case PROPERTY.CorrelationData:
8364
case PROPERTY.AuthenticationData: {
84-
const {byteLength} = (props[byte] = parseBinaryBuf(buf, offset));
65+
const {byteLength} = (props[byte] = parseBinary(data, offset));
8566
offset += 2 + byteLength;
8667
break;
8768
}
@@ -92,16 +73,16 @@ export const parseProps = (data: BufferList, offset: number): [props: Properties
9273
case PROPERTY.ResponseInformation:
9374
case PROPERTY.ServerReference:
9475
case PROPERTY.ReasonString: {
95-
const binary = parseBinaryBuf(buf, offset);
76+
const binary = parseBinary(data, offset);
9677
props[byte] = binary.toString('utf8');
9778
offset += 2 + binary.byteLength;
9879
break;
9980
}
10081
case PROPERTY.UserProperty: {
10182
if (!props[PROPERTY.UserProperty]) props[PROPERTY.UserProperty] = [];
102-
const key = parseBinaryBuf(buf, offset);
83+
const key = parseBinary(data, offset);
10384
offset += 2 + key.byteLength;
104-
const value = parseBinaryBuf(buf, offset);
85+
const value = parseBinary(data, offset);
10586
offset += 2 + value.byteLength;
10687
props[PROPERTY.UserProperty]!.push([key.toString('utf8'), value.toString('utf8')]);
10788
break;

yarn.lock

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -753,13 +753,6 @@
753753
dependencies:
754754
"@babel/types" "^7.3.0"
755755

756-
"@types/bl@^2.1.0":
757-
version "2.1.0"
758-
resolved "https://registry.yarnpkg.com/@types/bl/-/bl-2.1.0.tgz#45c881c97feae1223d63bbc5b83166153fcb2a15"
759-
integrity sha512-1TdA9IXOy4sdqn8vgieQ6GZAiHiPNrOiO1s2GJjuYPw4QVY7gYoVjkW049avj33Ez7IcIvu43hQsMsoUFbCn2g==
760-
dependencies:
761-
"@types/node" "*"
762-
763756
"@types/color-name@^1.1.1":
764757
version "1.1.1"
765758
resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0"

0 commit comments

Comments
 (0)