Skip to content

Commit d29e035

Browse files
committed
feat: 🎸 add encoding to connect packet and username setting
1 parent eca912f commit d29e035

File tree

5 files changed

+199
-4
lines changed

5 files changed

+199
-4
lines changed

‎src/packet.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ export class Packet implements PacketHeaderData {
1212
/** Packet first byte. */
1313
public b: number;
1414

15-
/** Variable length. */
15+
/**
16+
* Remaining Length - packet size less first byte and Remaining Length
17+
* variable integer size.
18+
*/
1619
public l: number;
1720

1821
constructor(b: number, l: number) {
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import {PACKET_TYPE} from '../../enums';
2+
import {PacketConnect} from '../connect';
3+
4+
test('can create a packet', () => {
5+
const packet = PacketConnect.create(5, 0, 30, {}, 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx');
6+
expect(packet.b >> 4).toBe(PACKET_TYPE.CONNECT);
7+
expect(packet.id).toBe('xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx');
8+
expect(packet.v).toBe(5);
9+
expect(packet.f).toBe(0);
10+
expect(packet.k).toBe(30);
11+
expect(packet.p).toEqual({});
12+
});
13+
14+
test('can set username', () => {
15+
const packet = PacketConnect.create(5, 0, 30, {}, 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx');
16+
expect(packet.userNameFlag()).toBe(false);
17+
expect(packet.usr).toBe(undefined);
18+
packet.setUserName('streamich');
19+
expect(packet.userNameFlag()).toBe(true);
20+
expect(packet.usr).toBe('streamich');
21+
packet.setUserName('lol');
22+
expect(packet.userNameFlag()).toBe(true);
23+
expect(packet.usr).toBe('lol');
24+
packet.setUserName('');
25+
expect(packet.userNameFlag()).toBe(true);
26+
expect(packet.usr).toBe('');
27+
});
28+
29+
test('can remove username', () => {
30+
const packet = PacketConnect.create(5, 0, 30, {}, 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx');
31+
expect(packet.userNameFlag()).toBe(false);
32+
expect(packet.usr).toBe(undefined);
33+
packet.removeUserName();
34+
expect(packet.userNameFlag()).toBe(false);
35+
expect(packet.usr).toBe(undefined);
36+
packet.setUserName('streamich');
37+
expect(packet.userNameFlag()).toBe(true);
38+
expect(packet.usr).toBe('streamich');
39+
packet.removeUserName();
40+
expect(packet.userNameFlag()).toBe(false);
41+
expect(packet.usr).toBe(undefined);
42+
});

‎src/packets/connect.ts

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import {BufferList} from '../BufferList';
1+
import { PACKET_TYPE } from '../enums';
22
import {Packet, PacketHeaderData} from '../packet';
33
import {Properties, QoS} from '../types';
4-
import {parseProps, parseBinary} from '../util/parse';
4+
import {encodeConnect} from './connect/encodeConnect';
55

66
export interface PacketConnectData extends PacketHeaderData {
77
/** Protocol version. */
@@ -27,6 +27,23 @@ export interface PacketConnectData extends PacketHeaderData {
2727
}
2828

2929
export class PacketConnect extends Packet implements PacketConnectData {
30+
/**
31+
* @param v Version, i.e. 5 or 4
32+
* @param f Connect flags
33+
* @param k Keep-alive
34+
* @param p Properties object in case of MQTT 5.0, or empty `{}` object otherwise.
35+
* @param id Connection ID
36+
*/
37+
static create(
38+
v: number,
39+
f: number,
40+
k: number,
41+
p: Properties,
42+
id: string,
43+
): PacketConnect {
44+
return new PacketConnect(PACKET_TYPE.CONNECT << 4, 0, v, f, k, p, id);
45+
}
46+
3047
public wp?: Properties;
3148
public wt?: string;
3249
public w?: Buffer;
@@ -49,6 +66,16 @@ export class PacketConnect extends Packet implements PacketConnectData {
4966
return !!(this.f & 0b10000000);
5067
}
5168

69+
public setUserName(usr: string) {
70+
this.usr = usr;
71+
this.f |= 0b10000000;
72+
}
73+
74+
public removeUserName() {
75+
this.usr = undefined;
76+
this.f &= ~0b10000000;
77+
}
78+
5279
public passwordFlag(): boolean {
5380
return !!(this.f & 0b01000000);
5481
}
@@ -68,4 +95,8 @@ export class PacketConnect extends Packet implements PacketConnectData {
6895
public cleanStart(): boolean {
6996
return !!(this.f & 0b00000010);
7097
}
98+
99+
public toBuffer(): Buffer {
100+
return encodeConnect(this);
101+
}
71102
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import {genProps} from '../../util/genProps/v7';
2+
import {PacketConnect} from '../connect';
3+
4+
// "MQTT" string with 2 byte length prefix
5+
const bufferMQTT = Buffer.from([0, 2, 0x4d, 0x51, 0x54, 0x54]);
6+
7+
export const encodeConnect = (packet: PacketConnect): Buffer => {
8+
const {b, f, id, v, k, p, w, wt, wp, usr, pwd} = packet;
9+
const hasWill = packet.willFlag();
10+
const hasUserName = packet.userNameFlag();
11+
const hasPassword = packet.passwordFlag();
12+
13+
const isV5 = v === 5;
14+
const props = isV5 ? genProps(p) : null;
15+
const propsLength = props ? props.length : 0;
16+
const willProps = isV5 && !!wp ? genProps(wp) : null;
17+
const willPropsLength = willProps ? willProps.length : 0;
18+
const willTopicLength: number = hasWill ? Buffer.byteLength(wt!) : 0;
19+
const willPayloadLength: number = hasWill ? w!.length : 0;
20+
const lenClientId = Buffer.byteLength(id);
21+
const userNameLength = hasUserName ? Buffer.byteLength(usr!) : 0;
22+
const passwordLength = hasPassword ? Buffer.byteLength(pwd!) : 0;
23+
24+
const remainingLength: number =
25+
2 + 4 + // "MQTT" string and string length prefix
26+
1 + // Protocol Version
27+
1 + // Connection flags
28+
2 + // Keep-alive
29+
(isV5 ? propsLength : 0) + // Properties
30+
2 + lenClientId + // Client ID
31+
(hasWill ? (
32+
willPropsLength + // Will props
33+
2 + willTopicLength + // Will topic
34+
2 + willPayloadLength // Will payload
35+
) : 0) +
36+
(hasUserName ? 2 + userNameLength : 0) + // Username
37+
(hasPassword ? 2 + passwordLength : 0); // Password
38+
39+
packet.l = remainingLength;
40+
const remainingLengthSize = remainingLength < 128 ? 1 : remainingLength < 16_384 ? 2 : remainingLength < 2_097_152 ? 3 : 4;
41+
const bufferLength = 1 + remainingLengthSize + remainingLength;
42+
const buf = Buffer.allocUnsafe(bufferLength);
43+
44+
buf.writeUInt8(b, 0);
45+
46+
let offset = 1;
47+
48+
switch (remainingLengthSize) {
49+
case 1:
50+
buf.writeUInt8(remainingLength, 1);
51+
offset = 2;
52+
break;
53+
case 2:
54+
buf.writeUInt16LE(((remainingLength & 0b011111110000000) << 1) | (0b10000000 | (remainingLength & 0b01111111)), 1);
55+
offset = 3;
56+
break;
57+
case 3:
58+
buf.writeUInt16LE(((0b100000000000000 | (remainingLength & 0b011111110000000)) << 1) | (0b10000000 | (remainingLength & 0b01111111)), 1);
59+
buf.writeUInt8((remainingLength >> 14) & 0b01111111, 3);
60+
offset = 4;
61+
break;
62+
case 4:
63+
buf.writeUInt32LE((((((remainingLength >> 21) & 0b01111111) << 8) | (0b10000000 | ((remainingLength >> 14) & 0b01111111))) << 16) |
64+
((0b100000000000000 | (remainingLength & 0b011111110000000)) << 1) | (0b10000000 | (remainingLength & 0b01111111)), 1);
65+
offset = 5;
66+
break;
67+
}
68+
69+
bufferMQTT.copy(buf, offset);
70+
offset += 6;
71+
72+
buf.writeUInt8(v, offset);
73+
offset += 1;
74+
75+
buf.writeUInt8(f, offset);
76+
offset += 1;
77+
78+
buf.writeUInt16BE(k, offset);
79+
offset += 2;
80+
81+
if (isV5) {
82+
props!.copy(buf, offset);
83+
offset+= propsLength;
84+
}
85+
86+
buf.write(id, offset);
87+
offset += lenClientId;
88+
89+
if (hasWill) {
90+
if (isV5) {
91+
willProps!.copy(buf, offset);
92+
offset += willPropsLength;
93+
}
94+
buf.writeUInt16BE(willTopicLength, offset);
95+
offset += 2;
96+
buf.write(wt!, offset);
97+
offset += willTopicLength;
98+
buf.writeUInt16BE(willPayloadLength, offset);
99+
offset += 2;
100+
w!.copy(buf, offset);
101+
offset += willPayloadLength;
102+
}
103+
104+
if (hasUserName) {
105+
buf.writeUInt16BE(userNameLength, offset);
106+
offset += 2;
107+
buf.write(usr!, offset);
108+
offset += userNameLength;
109+
}
110+
111+
if (hasPassword) {
112+
buf.writeUInt16BE(passwordLength, offset);
113+
offset += 2;
114+
pwd!.copy(buf, offset);
115+
offset += passwordLength;
116+
}
117+
118+
return buf;
119+
}

‎src/packets/publish/encodePublish/v1.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export const encodePublish = (packet: PacketPublish, version: number): Buffer =>
1818
const buf = Buffer.allocUnsafe(bufferLength);
1919
packet.l = remainingLength;
2020

21-
buf.writeUInt8(packet.b);
21+
buf.writeUInt8(packet.b, 0);
2222

2323
let offset = 1;
2424

0 commit comments

Comments
 (0)