aedes 0.51.3 → 1.0.1
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/.claude/settings.local.json +12 -0
- package/.github/actions/sticky-pr-comment/action.yml +55 -0
- package/.github/workflows/benchmark-compare-serial.yml +60 -0
- package/.github/workflows/ci.yml +12 -17
- package/.release-it.json +18 -0
- package/.taprc +15 -6
- package/README.md +6 -4
- package/aedes.d.ts +0 -6
- package/aedes.js +270 -242
- package/benchmarks/README.md +33 -0
- package/benchmarks/pingpong.js +94 -25
- package/benchmarks/receiver.js +77 -0
- package/benchmarks/report.js +150 -0
- package/benchmarks/runBenchmarks.js +118 -0
- package/benchmarks/sender.js +86 -0
- package/benchmarks/server.js +19 -18
- package/checkVersion.js +20 -0
- package/docs/Aedes.md +66 -8
- package/docs/Client.md +3 -4
- package/docs/Examples.md +39 -22
- package/docs/MIGRATION.md +50 -0
- package/eslint.config.js +8 -0
- package/example.js +51 -40
- package/examples/clusters/index.js +28 -23
- package/examples/clusters/package.json +10 -6
- package/lib/client.js +405 -306
- package/lib/handlers/connect.js +42 -38
- package/lib/handlers/index.js +9 -11
- package/lib/handlers/ping.js +2 -3
- package/lib/handlers/puback.js +5 -5
- package/lib/handlers/publish.js +29 -14
- package/lib/handlers/pubrec.js +9 -17
- package/lib/handlers/pubrel.js +34 -25
- package/lib/handlers/subscribe.js +47 -43
- package/lib/handlers/unsubscribe.js +16 -19
- package/lib/qos-packet.js +14 -17
- package/lib/utils.js +5 -12
- package/lib/write.js +4 -5
- package/package.json +139 -136
- package/test/auth.js +468 -804
- package/test/basic.js +613 -575
- package/test/bridge.js +44 -40
- package/test/client-pub-sub.js +531 -504
- package/test/close_socket_by_other_party.js +137 -102
- package/test/connect.js +487 -484
- package/test/drain-timeout.js +593 -0
- package/test/drain-toxiproxy.js +620 -0
- package/test/events.js +173 -145
- package/test/helper.js +351 -73
- package/test/keep-alive.js +40 -67
- package/test/meta.js +257 -210
- package/test/not-blocking.js +93 -197
- package/test/qos1.js +464 -554
- package/test/qos2.js +308 -393
- package/test/regr-21.js +39 -21
- package/test/require.cjs +22 -0
- package/test/retain.js +349 -398
- package/test/topics.js +176 -183
- package/test/types/aedes.test-d.ts +4 -8
- package/test/will.js +310 -428
- package/types/instance.d.ts +40 -35
- package/types/packet.d.ts +10 -10
- package/.coveralls.yml +0 -1
- package/benchmarks/bombing.js +0 -34
- package/benchmarks/bombingQoS1.js +0 -36
- package/benchmarks/throughputCounter.js +0 -23
- package/benchmarks/throughputCounterQoS1.js +0 -33
- package/types/.eslintrc.json +0 -47
package/test/helper.js
CHANGED
|
@@ -1,119 +1,397 @@
|
|
|
1
|
-
|
|
1
|
+
import { setTimeout as delay } from 'node:timers/promises'
|
|
2
|
+
import { duplexPair, Transform } from 'node:stream'
|
|
3
|
+
import { platform } from 'node:os'
|
|
4
|
+
import mqtt from 'mqtt-packet'
|
|
5
|
+
import { Aedes } from '../aedes.js'
|
|
2
6
|
|
|
3
|
-
const duplexify = require('duplexify')
|
|
4
|
-
const mqtt = require('mqtt-connection')
|
|
5
|
-
const { through } = require('../lib/utils')
|
|
6
|
-
const util = require('util')
|
|
7
|
-
const aedes = require('../')
|
|
8
|
-
|
|
9
|
-
const parseStream = mqtt.parseStream
|
|
10
|
-
const generateStream = mqtt.generateStream
|
|
11
7
|
let clients = 0
|
|
12
8
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Check if tests should be skipped on Windows and macOS platforms
|
|
11
|
+
* These platforms often lack proper support for certain network features
|
|
12
|
+
* like socket.readStop() or have issues with Docker/Testcontainers
|
|
13
|
+
* @returns {boolean} true if tests should be skipped on this platform
|
|
14
|
+
*/
|
|
15
|
+
export function shouldSkipOnWindowsAndMac () {
|
|
16
|
+
const os = platform()
|
|
17
|
+
return os === 'win32' || os === 'darwin'
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function setup (broker) {
|
|
21
|
+
const [client, server] = duplexPair()
|
|
22
|
+
const inStream = new Transform(
|
|
23
|
+
{
|
|
24
|
+
objectMode: true,
|
|
25
|
+
transform (chunk, enc, callback) {
|
|
26
|
+
this.push(mqtt.generate(chunk))
|
|
27
|
+
callback()
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
inStream.pipe(client)
|
|
31
|
+
const outStream = packetGenerator(mqtt.parser(), client)
|
|
32
|
+
|
|
33
|
+
const serverClosed = () => {
|
|
34
|
+
client.destroy()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const clientClosed = () => {
|
|
38
|
+
server.destroy()
|
|
39
|
+
}
|
|
17
40
|
|
|
18
|
-
|
|
41
|
+
server.on('close', serverClosed)
|
|
42
|
+
server.on('end', serverClosed)
|
|
43
|
+
client.on('close', clientClosed)
|
|
44
|
+
client.on('end', clientClosed)
|
|
19
45
|
|
|
20
46
|
return {
|
|
21
|
-
client: broker.handle(
|
|
22
|
-
conn,
|
|
23
|
-
inStream,
|
|
47
|
+
client: broker.handle(server),
|
|
48
|
+
conn: client,
|
|
24
49
|
outStream,
|
|
50
|
+
inStream,
|
|
25
51
|
broker
|
|
26
52
|
}
|
|
27
53
|
}
|
|
28
54
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
55
|
+
/**
|
|
56
|
+
* Establishes an MQTT connection with the specified options
|
|
57
|
+
* @param {Object} s - The connection state object
|
|
58
|
+
* @param {Object} opts - MQTT connection options
|
|
59
|
+
* @returns {Object} the connack packet
|
|
60
|
+
*/
|
|
32
61
|
|
|
33
|
-
|
|
62
|
+
export async function connect (s, opts = {}) {
|
|
63
|
+
const connect = opts.connect || {}
|
|
64
|
+
connect.cmd = 'connect'
|
|
65
|
+
connect.protocolId = connect.protocolId || 'MQTT'
|
|
66
|
+
connect.protocolVersion = connect.protocolVersion || 4
|
|
67
|
+
connect.clean = connect.clean !== false
|
|
68
|
+
connect.clientId = connect.clientId || 'my-client'
|
|
69
|
+
connect.username = connect.username || 'my username'
|
|
70
|
+
connect.password = connect.password || 'my pass'
|
|
71
|
+
connect.keepalive = connect.keepalive || 0
|
|
72
|
+
const expectedReturnCode = opts.expectedReturnCode || 0
|
|
73
|
+
const verifyIsConnack = opts.verifyIsConnack !== false
|
|
74
|
+
const verifyReturnedOk = verifyIsConnack ? opts.verifyReturnedOk !== false : false
|
|
75
|
+
const noWait = opts.noWait
|
|
34
76
|
|
|
35
|
-
opts.
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
opts.
|
|
39
|
-
|
|
40
|
-
|
|
77
|
+
if (opts.autoClientId) {
|
|
78
|
+
connect.clientId = 'my-client-' + clients++
|
|
79
|
+
}
|
|
80
|
+
if (opts.noCredentials) {
|
|
81
|
+
connect.username = undefined
|
|
82
|
+
connect.password = undefined
|
|
83
|
+
}
|
|
41
84
|
|
|
42
|
-
|
|
85
|
+
if (opts.noClientId) {
|
|
86
|
+
connect.clientId = undefined
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
s.inStream.write(connect)
|
|
90
|
+
if (noWait) {
|
|
91
|
+
return
|
|
92
|
+
}
|
|
93
|
+
const { value: connack } = await s.outStream.next()
|
|
94
|
+
if (verifyIsConnack && connack?.cmd !== 'connack') {
|
|
95
|
+
throw new Error('Expected connack')
|
|
96
|
+
}
|
|
97
|
+
if (verifyReturnedOk && (connack.returnCode !== expectedReturnCode)) {
|
|
98
|
+
throw new Error(`Expected connack return code ${expectedReturnCode} but received ${connack.returnCode}`)
|
|
99
|
+
}
|
|
100
|
+
return connack
|
|
101
|
+
}
|
|
43
102
|
|
|
103
|
+
/**
|
|
104
|
+
* Creates a new MQTT connection and establishes it
|
|
105
|
+
* @param {Object} t - Test assertion object
|
|
106
|
+
* @returns {Object} Connection state
|
|
107
|
+
*/
|
|
108
|
+
export async function createAndConnect (t, opts = {}) {
|
|
109
|
+
const broker = await Aedes.createBroker(opts.broker)
|
|
110
|
+
t.after(() => broker.close())
|
|
111
|
+
const s = setup(broker)
|
|
112
|
+
await connect(s, opts)
|
|
44
113
|
return s
|
|
114
|
+
}
|
|
45
115
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
116
|
+
/**
|
|
117
|
+
* Creates a new MQTT connection and establishes it for publisher and subscriber
|
|
118
|
+
* @param {Object} t - Test assertion object
|
|
119
|
+
* @returns {Object} Connection state { broker, publisher, subscriber }
|
|
120
|
+
*/
|
|
121
|
+
export async function createPubSub (t, opts = {}) {
|
|
122
|
+
const publisherOpts = { clientId: 'publisher', ...opts.publisher }
|
|
123
|
+
const subscriberOpts = { clientId: 'subscriber', ...opts.subscriber }
|
|
51
124
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
125
|
+
const broker = await Aedes.createBroker()
|
|
126
|
+
t.after(() => broker.close())
|
|
127
|
+
|
|
128
|
+
const publisher = setup(broker)
|
|
129
|
+
const subscriber = setup(broker)
|
|
130
|
+
await connect(publisher, { connect: publisherOpts })
|
|
131
|
+
await connect(subscriber, { connect: subscriberOpts })
|
|
132
|
+
return { broker, publisher, subscriber }
|
|
61
133
|
}
|
|
62
134
|
|
|
63
|
-
|
|
64
|
-
|
|
135
|
+
/**
|
|
136
|
+
* Sets up error handling for the broker connection
|
|
137
|
+
* @param {Object} s - The connection state object
|
|
138
|
+
* @param {Object} t - Test assertion object
|
|
139
|
+
* @returns {Object} Connection state with error handling
|
|
140
|
+
*/
|
|
141
|
+
export function noError (s, t) {
|
|
142
|
+
s.broker.on('clientError', (client, err) => {
|
|
65
143
|
if (err) throw err
|
|
66
|
-
t.
|
|
144
|
+
t.assert.equal(err, undefined, 'must not error')
|
|
67
145
|
})
|
|
68
|
-
|
|
69
146
|
return s
|
|
70
147
|
}
|
|
71
148
|
|
|
72
|
-
|
|
149
|
+
/**
|
|
150
|
+
* @param {Object} s - The connection state object
|
|
151
|
+
* @param {Object} packet - The packet to publish
|
|
152
|
+
* @returns {Promise} - Promise that resolves when the packet is published
|
|
153
|
+
*/
|
|
154
|
+
export async function brokerPublish (s, packet) {
|
|
155
|
+
return new Promise((resolve) => {
|
|
156
|
+
s.broker.publish(packet, () => {
|
|
157
|
+
setImmediate(resolve)
|
|
158
|
+
})
|
|
159
|
+
})
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* publish a packet to the broker
|
|
164
|
+
* @param {Object} t - Test assertion object
|
|
165
|
+
* @param {Object} s - The connection state object
|
|
166
|
+
* @param {Object} packet - The packet to publish
|
|
167
|
+
* @returns {Promise} - Promise that resolves when the packet is published
|
|
168
|
+
*/
|
|
169
|
+
export async function publish (t, s, packet) {
|
|
170
|
+
s.inStream.write(packet)
|
|
171
|
+
const msgId = packet.messageId
|
|
172
|
+
if (packet.qos === 1) {
|
|
173
|
+
const { value: puback } = await s.outStream.next()
|
|
174
|
+
t.assert.equal(puback.cmd, 'puback')
|
|
175
|
+
return puback
|
|
176
|
+
}
|
|
177
|
+
if (packet.qos === 2) {
|
|
178
|
+
const { value: pubrec } = await s.outStream.next()
|
|
179
|
+
t.assert.deepStrictEqual(structuredClone(pubrec), {
|
|
180
|
+
cmd: 'pubrec',
|
|
181
|
+
messageId: msgId,
|
|
182
|
+
length: 2,
|
|
183
|
+
dup: false,
|
|
184
|
+
retain: false,
|
|
185
|
+
qos: 0,
|
|
186
|
+
payload: null,
|
|
187
|
+
topic: null
|
|
188
|
+
}, 'pubrec must match')
|
|
189
|
+
s.inStream.write({
|
|
190
|
+
cmd: 'pubrel',
|
|
191
|
+
messageId: pubrec.messageId
|
|
192
|
+
})
|
|
193
|
+
const { value: pubcomp } = await s.outStream.next()
|
|
194
|
+
t.assert.deepEqual(structuredClone(pubcomp), {
|
|
195
|
+
cmd: 'pubcomp',
|
|
196
|
+
messageId: msgId,
|
|
197
|
+
length: 2,
|
|
198
|
+
dup: false,
|
|
199
|
+
retain: false,
|
|
200
|
+
qos: 0,
|
|
201
|
+
payload: null,
|
|
202
|
+
topic: null
|
|
203
|
+
}, 'pubcomp must match')
|
|
204
|
+
return pubcomp
|
|
205
|
+
}
|
|
206
|
+
return null
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Subscribes to a single MQTT topic
|
|
210
|
+
* @param {Object} t - Test assertion object
|
|
211
|
+
* @param {Object} subscriber - The subscriber client
|
|
212
|
+
* @param {string} topic - Topic to subscribe to
|
|
213
|
+
* @param {number} qos - Quality of Service level
|
|
214
|
+
* @param {number} messageId - Message ID for the subscription
|
|
215
|
+
* @returns {Object} The subscription packet
|
|
216
|
+
*/
|
|
217
|
+
export async function subscribe (t, subscriber, topic, qos, messageId = 24) {
|
|
73
218
|
subscriber.inStream.write({
|
|
74
219
|
cmd: 'subscribe',
|
|
75
|
-
messageId
|
|
220
|
+
messageId,
|
|
76
221
|
subscriptions: [{
|
|
77
222
|
topic,
|
|
78
223
|
qos
|
|
79
224
|
}]
|
|
80
225
|
})
|
|
81
226
|
|
|
82
|
-
subscriber.outStream.
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
if (done) {
|
|
88
|
-
done(null, packet)
|
|
89
|
-
}
|
|
90
|
-
})
|
|
227
|
+
const { value: packet } = await subscriber.outStream.next()
|
|
228
|
+
t.assert.equal(packet.cmd, 'suback')
|
|
229
|
+
t.assert.equal(packet.granted[0], qos)
|
|
230
|
+
t.assert.equal(packet.messageId, messageId)
|
|
231
|
+
return packet
|
|
91
232
|
}
|
|
92
233
|
|
|
93
|
-
|
|
94
|
-
|
|
234
|
+
/**
|
|
235
|
+
* Subscribes to multiple MQTT topics
|
|
236
|
+
* @param {Object} t - Test assertion object
|
|
237
|
+
* @param {Object} subscriber - The subscriber client
|
|
238
|
+
* @param {Array<Object>} subs - Array of subscription objects with topic and qos
|
|
239
|
+
* @param {Array<number>} expectedGranted - Expected QoS levels granted
|
|
240
|
+
*/
|
|
241
|
+
export async function subscribeMultiple (t, subscriber, subs, expectedGranted) {
|
|
95
242
|
subscriber.inStream.write({
|
|
96
243
|
cmd: 'subscribe',
|
|
97
244
|
messageId: 24,
|
|
98
245
|
subscriptions: subs
|
|
99
246
|
})
|
|
100
247
|
|
|
101
|
-
subscriber.outStream.
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
248
|
+
const { value: packet } = await subscriber.outStream.next()
|
|
249
|
+
t.assert.equal(packet.cmd, 'suback')
|
|
250
|
+
t.assert.deepEqual(packet.granted, expectedGranted)
|
|
251
|
+
t.assert.equal(packet.messageId, 24)
|
|
252
|
+
return packet
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Wraps a promise with a timeout
|
|
257
|
+
* @param {Promise} promise - Promise to wrap with timeout
|
|
258
|
+
* @param {number} timeoutMs - Timeout in milliseconds
|
|
259
|
+
* @param {Error} err - Error to throw on timeout
|
|
260
|
+
* @returns {Promise} Promise that rejects if timeout occurs
|
|
261
|
+
*/
|
|
262
|
+
export async function withTimeout (promise, timeoutMs, timeoutResult) {
|
|
263
|
+
const timeoutPromise = delay(timeoutMs, timeoutResult)
|
|
264
|
+
return Promise.race([promise, timeoutPromise])
|
|
265
|
+
}
|
|
105
266
|
|
|
106
|
-
|
|
107
|
-
|
|
267
|
+
/**
|
|
268
|
+
* Asynchronously yields MQTT packets parsed from a source stream, with backpressure management.
|
|
269
|
+
*
|
|
270
|
+
* Backpressure is controlled using a queue and the `highWaterMark`/`lowWaterMark` parameters:
|
|
271
|
+
* - When the number of buffered packets in the queue reaches `highWaterMark`, the source stream is paused to prevent overload.
|
|
272
|
+
* - When the queue size drops to or below `lowWaterMark`, the source stream is resumed to allow more data to flow.
|
|
273
|
+
*
|
|
274
|
+
* @param {Object} parser - The emitter object that parses MQTT packets and emits 'packet' events.
|
|
275
|
+
* @param {Readable} sourceStream - The source stream to read MQTT packets from.
|
|
276
|
+
* @param {Object} [opts] - Options for backpressure control.
|
|
277
|
+
* @param {number} [opts.highWaterMark=2] - Maximum number of packets to buffer before pausing the source stream.
|
|
278
|
+
* @param {number} [opts.lowWaterMark=0] - Minimum number of packets in the buffer to resume the source stream.
|
|
279
|
+
* @returns {AsyncGenerator<Object|null, void, unknown>} An async generator that yields MQTT packets, or null when done.
|
|
280
|
+
*/
|
|
281
|
+
async function * packetGenerator (parser, sourceStream, opts = {
|
|
282
|
+
highWaterMark: 2,
|
|
283
|
+
lowWaterMark: 0
|
|
284
|
+
}) {
|
|
285
|
+
const { highWaterMark, lowWaterMark } = opts
|
|
286
|
+
if (!Number.isInteger(highWaterMark) || highWaterMark < 1) {
|
|
287
|
+
throw new Error('highWaterMark must be a positive integer')
|
|
288
|
+
}
|
|
289
|
+
if (!Number.isInteger(lowWaterMark) || lowWaterMark < 0) {
|
|
290
|
+
throw new Error('lowWaterMark must be a non-negative integer')
|
|
291
|
+
}
|
|
292
|
+
if (lowWaterMark >= highWaterMark) {
|
|
293
|
+
throw new Error('lowWaterMark must be less than highWaterMark')
|
|
294
|
+
}
|
|
295
|
+
const queue = []
|
|
296
|
+
let waiting = null
|
|
297
|
+
let done = false
|
|
298
|
+
|
|
299
|
+
const onPacket = packet => {
|
|
300
|
+
if (waiting) {
|
|
301
|
+
waiting(packet)
|
|
302
|
+
waiting = null
|
|
303
|
+
} else {
|
|
304
|
+
queue.push(packet)
|
|
108
305
|
}
|
|
109
|
-
|
|
306
|
+
// Pause source if queue is full
|
|
307
|
+
if (queue.length >= highWaterMark) {
|
|
308
|
+
sourceStream.pause()
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
parser.on('packet', onPacket)
|
|
313
|
+
sourceStream.on('data', parser.parse.bind(parser))
|
|
314
|
+
const endGeneration = (value) => {
|
|
315
|
+
if (waiting) {
|
|
316
|
+
waiting(value)
|
|
317
|
+
waiting = null
|
|
318
|
+
}
|
|
319
|
+
if (value) {
|
|
320
|
+
queue.push(value)
|
|
321
|
+
}
|
|
322
|
+
done = true
|
|
323
|
+
}
|
|
324
|
+
sourceStream.on('error', (err) => endGeneration(err))
|
|
325
|
+
sourceStream.on('end', () => endGeneration(null))
|
|
326
|
+
sourceStream.on('close', () => endGeneration(null))
|
|
327
|
+
|
|
328
|
+
try {
|
|
329
|
+
while (true) {
|
|
330
|
+
if (done) {
|
|
331
|
+
yield null
|
|
332
|
+
} else {
|
|
333
|
+
if (queue.length === 0) {
|
|
334
|
+
// Wait for next packet
|
|
335
|
+
const packet = await new Promise(resolve => (waiting = resolve))
|
|
336
|
+
yield packet
|
|
337
|
+
} else {
|
|
338
|
+
// There is buffered data ready
|
|
339
|
+
// If we're emptying the queue, resume source
|
|
340
|
+
if (queue.length <= lowWaterMark) {
|
|
341
|
+
sourceStream.resume()
|
|
342
|
+
}
|
|
343
|
+
yield queue.shift()
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
} finally {
|
|
348
|
+
sourceStream.pause()
|
|
349
|
+
parser.off('packet', onPacket)
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Reads next packet from subscriber
|
|
355
|
+
* @param {Object} s - The connection state object
|
|
356
|
+
* @returns
|
|
357
|
+
*/
|
|
358
|
+
export async function nextPacket (s) {
|
|
359
|
+
const { value: packet } = await s.outStream.next()
|
|
360
|
+
return packet
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Reads next packet from subscriber with timeout
|
|
365
|
+
* @param {Object} s - The connection state object
|
|
366
|
+
* @param {number} timeoutMs - Timeout in milliseconds
|
|
367
|
+
* @returns
|
|
368
|
+
*/
|
|
369
|
+
export async function nextPacketWithTimeOut (s, timeoutMs) {
|
|
370
|
+
return withTimeout(nextPacket(s), timeoutMs, null)
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Checks if there is no packet received within the specified timeout
|
|
375
|
+
* @param {Object} t - Test assertion object
|
|
376
|
+
* @param {Object} s - s The connection state object
|
|
377
|
+
* @param {number} timeoutMs - Timeout in milliseconds
|
|
378
|
+
* @returns
|
|
379
|
+
*/
|
|
380
|
+
export async function checkNoPacket (t, s, timeoutMs = 10) {
|
|
381
|
+
const result = await nextPacketWithTimeOut(s, timeoutMs)
|
|
382
|
+
t.assert.equal(result, null, 'no packet received')
|
|
110
383
|
}
|
|
111
384
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
385
|
+
/**
|
|
386
|
+
*
|
|
387
|
+
* @param {Object} s - s The connection state object
|
|
388
|
+
* @param {string} rawPacket - space separated string of hex values
|
|
389
|
+
* @example rawWrite(s, "10 0C 00 04 4D 51 54 54 04 00 00 00 00 00")
|
|
390
|
+
*/
|
|
391
|
+
export function rawWrite (s, rawPacket) {
|
|
392
|
+
s.conn.write(Buffer.from(rawPacket.replace(/ /g, ''), 'hex'))
|
|
119
393
|
}
|
|
394
|
+
/**
|
|
395
|
+
* sleep
|
|
396
|
+
*/
|
|
397
|
+
export { delay }
|
package/test/keep-alive.js
CHANGED
|
@@ -1,92 +1,65 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
import { test } from 'node:test'
|
|
2
|
+
import { once } from 'node:events'
|
|
3
|
+
import { Aedes } from '../aedes.js'
|
|
4
|
+
import {
|
|
5
|
+
createAndConnect,
|
|
6
|
+
delay,
|
|
7
|
+
nextPacket,
|
|
8
|
+
setup,
|
|
9
|
+
} from './helper.js'
|
|
10
|
+
|
|
11
|
+
test('supports pingreq/pingresp', async (t) => {
|
|
10
12
|
t.plan(1)
|
|
11
13
|
|
|
12
|
-
const s =
|
|
13
|
-
t.teardown(s.broker.close.bind(s.broker))
|
|
14
|
+
const s = await createAndConnect(t)
|
|
14
15
|
|
|
15
|
-
s.broker.on('keepaliveTimeout',
|
|
16
|
-
t.fail('keep alive should not timeout')
|
|
16
|
+
s.broker.on('keepaliveTimeout', client => {
|
|
17
|
+
t.assert.fail('keep alive should not timeout')
|
|
17
18
|
})
|
|
18
19
|
|
|
19
20
|
s.inStream.write({
|
|
20
21
|
cmd: 'pingreq'
|
|
21
22
|
})
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
})
|
|
24
|
+
const packet = await nextPacket(s)
|
|
25
|
+
t.assert.equal(packet.cmd, 'pingresp', 'the response is a pingresp')
|
|
26
26
|
})
|
|
27
27
|
|
|
28
|
-
test('supports keep alive disconnections',
|
|
28
|
+
test('supports keep alive disconnections', async (t) => {
|
|
29
29
|
t.plan(2)
|
|
30
30
|
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
t.
|
|
34
|
-
|
|
35
|
-
s.broker.on('keepaliveTimeout', function (client) {
|
|
36
|
-
t.pass('keep alive timeout')
|
|
37
|
-
})
|
|
38
|
-
eos(s.conn, function () {
|
|
39
|
-
t.pass('waits 1 and a half the keepalive timeout')
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
setTimeout(() => {
|
|
43
|
-
clock.uninstall()
|
|
44
|
-
}, 1.5)
|
|
45
|
-
clock.tick(1.5)
|
|
31
|
+
const s = await createAndConnect(t, { connect: { keepalive: 1 } })
|
|
32
|
+
await once(s.broker, 'keepaliveTimeout')
|
|
33
|
+
t.assert.ok(true, 'keep alive timeout', 'timeout was triggered')
|
|
34
|
+
t.assert.ok(s.client.conn.closed, 'waits 1 and a half the keepalive timeout')
|
|
46
35
|
})
|
|
47
36
|
|
|
48
|
-
test('supports keep alive disconnections after a pingreq',
|
|
37
|
+
test('supports keep alive disconnections after a pingreq', async (t) => {
|
|
49
38
|
t.plan(3)
|
|
50
39
|
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
eos(s.conn, function () {
|
|
56
|
-
t.pass('waits 1 and a half the keepalive timeout')
|
|
57
|
-
})
|
|
58
|
-
s.broker.on('keepaliveTimeout', function (client) {
|
|
59
|
-
t.pass('keep alive timeout')
|
|
60
|
-
})
|
|
61
|
-
s.outStream.on('data', function (packet) {
|
|
62
|
-
t.equal(packet.cmd, 'pingresp', 'the response is a pingresp')
|
|
40
|
+
const s = await createAndConnect(t, { connect: { keepalive: 1 } })
|
|
41
|
+
await delay(1)
|
|
42
|
+
s.inStream.write({
|
|
43
|
+
cmd: 'pingreq'
|
|
63
44
|
})
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}, 1)
|
|
70
|
-
clock.tick(3)
|
|
45
|
+
const packet = await nextPacket(s)
|
|
46
|
+
t.assert.equal(packet.cmd, 'pingresp', 'the response is a pingresp')
|
|
47
|
+
await once(s.broker, 'keepaliveTimeout')
|
|
48
|
+
t.assert.ok(true, 'keep alive timeout', 'timeout was triggered')
|
|
49
|
+
t.assert.ok(s.client.conn.closed, 'waits 1 and a half the keepalive timeout')
|
|
71
50
|
})
|
|
72
51
|
|
|
73
|
-
test('disconnect if a connect does not arrive in time',
|
|
52
|
+
test('disconnect if a connect does not arrive in time', async (t) => {
|
|
74
53
|
t.plan(2)
|
|
75
54
|
|
|
76
|
-
const
|
|
77
|
-
const s = setup(aedes({
|
|
55
|
+
const broker = await Aedes.createBroker({
|
|
78
56
|
connectTimeout: 500
|
|
79
|
-
}))
|
|
80
|
-
t.teardown(s.broker.close.bind(s.broker))
|
|
81
|
-
|
|
82
|
-
s.client.on('error', function (err) {
|
|
83
|
-
t.equal(err.message, 'connect did not arrive in time')
|
|
84
|
-
})
|
|
85
|
-
eos(s.conn, function () {
|
|
86
|
-
t.pass('waits waitConnectTimeout before ending')
|
|
87
57
|
})
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
58
|
+
|
|
59
|
+
const s = setup(broker)
|
|
60
|
+
t.after(() => broker.close())
|
|
61
|
+
|
|
62
|
+
const [err] = await once(s.client, 'error')
|
|
63
|
+
t.assert.equal(err.message, 'connect did not arrive in time')
|
|
64
|
+
t.assert.ok(s.client.conn.closed, 'waits waitConnectTimeout before ending')
|
|
92
65
|
})
|