714 lines
17 KiB
JavaScript
714 lines
17 KiB
JavaScript
'use strict'
|
|
|
|
const { test } = require('tap')
|
|
const { through } = require('../lib/utils')
|
|
const Faketimers = require('@sinonjs/fake-timers')
|
|
const { setup, connect, subscribe, noError } = require('./helper')
|
|
const aedes = require('../')
|
|
|
|
// [MQTT-3.3.1-9]
|
|
test('live retain packets', function (t) {
|
|
t.plan(5)
|
|
const expected = {
|
|
cmd: 'publish',
|
|
topic: 'hello',
|
|
payload: Buffer.from('world'),
|
|
retain: false,
|
|
dup: false,
|
|
length: 12,
|
|
qos: 0
|
|
}
|
|
|
|
const s = noError(connect(setup()), t)
|
|
t.teardown(s.broker.close.bind(s.broker))
|
|
|
|
subscribe(t, s, 'hello', 0, function () {
|
|
s.outStream.on('data', function (packet) {
|
|
t.same(packet, expected)
|
|
})
|
|
|
|
s.broker.publish({
|
|
cmd: 'publish',
|
|
topic: 'hello',
|
|
payload: Buffer.from('world'),
|
|
retain: true,
|
|
dup: false,
|
|
length: 12,
|
|
qos: 0
|
|
}, function () {
|
|
t.pass('publish finished')
|
|
})
|
|
})
|
|
})
|
|
|
|
test('retain messages', function (t) {
|
|
t.plan(4)
|
|
|
|
const broker = aedes()
|
|
t.teardown(broker.close.bind(broker))
|
|
|
|
const publisher = connect(setup(broker))
|
|
const subscriber = connect(setup(broker))
|
|
const expected = {
|
|
cmd: 'publish',
|
|
topic: 'hello',
|
|
payload: Buffer.from('world'),
|
|
qos: 0,
|
|
dup: false,
|
|
length: 12,
|
|
retain: true
|
|
}
|
|
|
|
broker.subscribe('hello', function (packet, cb) {
|
|
cb()
|
|
|
|
// defer this or it will receive the message which
|
|
// is being published
|
|
setImmediate(function () {
|
|
subscribe(t, subscriber, 'hello', 0, function () {
|
|
subscriber.outStream.once('data', function (packet) {
|
|
t.same(packet, expected, 'packet must match')
|
|
})
|
|
})
|
|
})
|
|
})
|
|
|
|
publisher.inStream.write(expected)
|
|
})
|
|
|
|
test('retain messages propagates through broker subscriptions', function (t) {
|
|
t.plan(1)
|
|
|
|
const broker = aedes()
|
|
t.teardown(broker.close.bind(broker))
|
|
|
|
const expected = {
|
|
cmd: 'publish',
|
|
topic: 'hello',
|
|
payload: Buffer.from('world'),
|
|
qos: 0,
|
|
dup: false,
|
|
retain: true
|
|
}
|
|
|
|
const subscriberFunc = function (packet, cb) {
|
|
packet = Object.assign({}, packet)
|
|
delete packet.brokerId
|
|
delete packet.brokerCounter
|
|
cb()
|
|
setImmediate(function () {
|
|
t.same(packet, expected, 'packet must not have been modified')
|
|
})
|
|
}
|
|
|
|
broker.subscribe('hello', subscriberFunc, function () {
|
|
broker.publish(expected)
|
|
})
|
|
})
|
|
|
|
test('avoid wrong deduping of retain messages', function (t) {
|
|
t.plan(7)
|
|
|
|
const broker = aedes()
|
|
t.teardown(broker.close.bind(broker))
|
|
|
|
const publisher = connect(setup(broker))
|
|
const subscriber = connect(setup(broker))
|
|
const expected = {
|
|
cmd: 'publish',
|
|
topic: 'hello',
|
|
payload: Buffer.from('world'),
|
|
qos: 0,
|
|
dup: false,
|
|
length: 12,
|
|
retain: true
|
|
}
|
|
|
|
broker.subscribe('hello', function (packet, cb) {
|
|
cb()
|
|
// subscribe and publish another topic
|
|
subscribe(t, subscriber, 'hello2', 0, function () {
|
|
cb()
|
|
|
|
publisher.inStream.write({
|
|
cmd: 'publish',
|
|
topic: 'hello2',
|
|
payload: Buffer.from('world'),
|
|
qos: 0,
|
|
dup: false
|
|
})
|
|
|
|
subscriber.outStream.once('data', function (packet) {
|
|
subscribe(t, subscriber, 'hello', 0, function () {
|
|
subscriber.outStream.once('data', function (packet) {
|
|
t.same(packet, expected, 'packet must match')
|
|
})
|
|
})
|
|
})
|
|
})
|
|
})
|
|
|
|
publisher.inStream.write(expected)
|
|
})
|
|
|
|
test('reconnected subscriber will not receive retained messages when QoS 0 and clean', function (t) {
|
|
t.plan(4)
|
|
|
|
const broker = aedes()
|
|
t.teardown(broker.close.bind(broker))
|
|
|
|
const publisher = connect(setup(broker), { clean: true })
|
|
let subscriber = connect(setup(broker), { clean: true })
|
|
const expected = {
|
|
cmd: 'publish',
|
|
topic: 'hello',
|
|
payload: Buffer.from('world'),
|
|
qos: 0,
|
|
retain: false,
|
|
dup: false,
|
|
length: 12
|
|
}
|
|
subscribe(t, subscriber, 'hello', 0, function () {
|
|
publisher.inStream.write({
|
|
cmd: 'publish',
|
|
topic: 'hello',
|
|
payload: 'world',
|
|
qos: 0,
|
|
retain: false
|
|
})
|
|
subscriber.outStream.once('data', function (packet) {
|
|
t.same(packet, expected, 'packet must match')
|
|
subscriber.inStream.end()
|
|
publisher.inStream.write({
|
|
cmd: 'publish',
|
|
topic: 'hello',
|
|
payload: 'foo',
|
|
qos: 0,
|
|
retain: true
|
|
})
|
|
subscriber = connect(setup(broker), { clean: true })
|
|
subscriber.outStream.on('data', function (packet) {
|
|
t.fail('should not received retain message')
|
|
})
|
|
})
|
|
})
|
|
})
|
|
|
|
test('subscriber will not receive retained messages when QoS is 128', function (t) {
|
|
t.plan(3)
|
|
|
|
const broker = aedes()
|
|
t.teardown(broker.close.bind(broker))
|
|
|
|
const pubPacket = {
|
|
cmd: 'publish',
|
|
topic: 'hello',
|
|
payload: 'world',
|
|
qos: 1,
|
|
retain: true,
|
|
messageId: 42
|
|
}
|
|
|
|
broker.authorizeSubscribe = function (client, sub, callback) {
|
|
if (sub.topic === pubPacket.topic) {
|
|
callback(null, null)
|
|
} else {
|
|
callback(null, sub)
|
|
}
|
|
}
|
|
|
|
const publisher = connect(setup(broker), { clean: true })
|
|
|
|
publisher.inStream.write(pubPacket)
|
|
|
|
publisher.outStream.on('data', function (packet) {
|
|
const subscriber = connect(setup(broker), { clean: true })
|
|
subscribe(t, subscriber, pubPacket.topic, 128, function () {
|
|
subscriber.outStream.on('data', function (packet) {
|
|
t.fail('should not received retain message')
|
|
})
|
|
})
|
|
})
|
|
})
|
|
|
|
// [MQTT-3.3.1-6]
|
|
test('new QoS 0 subscribers receive QoS 0 retained messages when clean', function (t) {
|
|
t.plan(9)
|
|
|
|
const clock = Faketimers.createClock()
|
|
const broker = aedes()
|
|
t.teardown(function () {
|
|
clock.reset()
|
|
broker.close()
|
|
})
|
|
|
|
const publisher = connect(setup(broker), { clean: true })
|
|
const expected = {
|
|
cmd: 'publish',
|
|
topic: 'hello/world',
|
|
payload: Buffer.from('big big world'),
|
|
qos: 0,
|
|
retain: true,
|
|
dup: false,
|
|
length: 26
|
|
}
|
|
publisher.inStream.write({
|
|
cmd: 'publish',
|
|
topic: 'hello/world',
|
|
payload: 'big big world',
|
|
qos: 0,
|
|
retain: true
|
|
})
|
|
const subscriber1 = connect(setup(broker), { clean: true })
|
|
subscribe(t, subscriber1, 'hello/world', 0, function () {
|
|
subscriber1.outStream.on('data', function (packet) {
|
|
t.same(packet, expected, 'packet must match')
|
|
clock.tick(100)
|
|
})
|
|
})
|
|
const subscriber2 = connect(setup(broker), { clean: true })
|
|
subscribe(t, subscriber2, 'hello/+', 0, function () {
|
|
subscriber2.outStream.on('data', function (packet) {
|
|
t.same(packet, expected, 'packet must match')
|
|
clock.tick(100)
|
|
})
|
|
})
|
|
|
|
clock.setTimeout(() => {
|
|
t.equal(broker.counter, 9)
|
|
}, 200)
|
|
})
|
|
|
|
// [MQTT-3.3.1-5]
|
|
test('new QoS 0 subscribers receive downgraded QoS 1 retained messages when clean', function (t) {
|
|
t.plan(6)
|
|
|
|
const broker = aedes()
|
|
|
|
const publisher = connect(setup(broker), { clean: true })
|
|
const expected = {
|
|
cmd: 'publish',
|
|
topic: 'hello',
|
|
payload: Buffer.from('world'),
|
|
qos: 0,
|
|
retain: true,
|
|
dup: false,
|
|
length: 12
|
|
}
|
|
publisher.inStream.write({
|
|
cmd: 'publish',
|
|
topic: 'hello',
|
|
payload: 'world',
|
|
qos: 1,
|
|
retain: true,
|
|
messageId: 42
|
|
})
|
|
publisher.outStream.on('data', function (packet) {
|
|
const subscriber = connect(setup(broker), { clean: true })
|
|
subscribe(t, subscriber, 'hello', 0, function () {
|
|
subscriber.outStream.on('data', function (packet) {
|
|
t.not(packet.messageId, 42, 'messageId should not be the same')
|
|
delete packet.messageId
|
|
t.same(packet, expected, 'packet must match')
|
|
broker.close()
|
|
})
|
|
})
|
|
})
|
|
broker.on('closed', function () {
|
|
t.equal(broker.counter, 8)
|
|
})
|
|
})
|
|
|
|
// [MQTT-3.3.1-10]
|
|
test('clean retained messages', function (t) {
|
|
t.plan(3)
|
|
|
|
const broker = aedes()
|
|
t.teardown(broker.close.bind(broker))
|
|
|
|
const publisher = connect(setup(broker), { clean: true })
|
|
publisher.inStream.write({
|
|
cmd: 'publish',
|
|
topic: 'hello',
|
|
payload: 'world',
|
|
qos: 0,
|
|
retain: true
|
|
})
|
|
publisher.inStream.write({
|
|
cmd: 'publish',
|
|
topic: 'hello',
|
|
payload: '',
|
|
qos: 0,
|
|
retain: true
|
|
})
|
|
const subscriber = connect(setup(broker), { clean: true })
|
|
subscribe(t, subscriber, 'hello', 0, function () {
|
|
subscriber.outStream.once('data', function (packet) {
|
|
t.fail('should not received retain message')
|
|
})
|
|
})
|
|
})
|
|
|
|
// [MQTT-3.3.1-11]
|
|
test('broker not store zero-byte retained messages', function (t) {
|
|
t.plan(0)
|
|
|
|
const broker = aedes()
|
|
t.teardown(broker.close.bind(broker))
|
|
|
|
const s = connect(setup(broker))
|
|
|
|
s.inStream.write({
|
|
cmd: 'publish',
|
|
topic: 'hello',
|
|
payload: '',
|
|
retain: true
|
|
})
|
|
s.broker.on('publish', function (packet, client) {
|
|
if (packet.topic.startsWith('$SYS/')) {
|
|
return
|
|
}
|
|
const stream = s.broker.persistence.createRetainedStream(packet.topic)
|
|
stream.pipe(through(function sendRetained (packet, enc, cb) {
|
|
t.fail('not store zero-byte retained messages')
|
|
}))
|
|
})
|
|
})
|
|
|
|
test('fail to clean retained messages without retain flag', function (t) {
|
|
t.plan(4)
|
|
|
|
const broker = aedes()
|
|
t.teardown(broker.close.bind(broker))
|
|
|
|
const publisher = connect(setup(broker), { clean: true })
|
|
const expected = {
|
|
cmd: 'publish',
|
|
topic: 'hello',
|
|
payload: Buffer.from('world'),
|
|
qos: 0,
|
|
retain: true,
|
|
dup: false,
|
|
length: 12
|
|
}
|
|
publisher.inStream.write({
|
|
cmd: 'publish',
|
|
topic: 'hello',
|
|
payload: 'world',
|
|
qos: 0,
|
|
retain: true
|
|
})
|
|
publisher.inStream.write({
|
|
cmd: 'publish',
|
|
topic: 'hello',
|
|
payload: '',
|
|
qos: 0,
|
|
retain: false
|
|
})
|
|
const subscriber = connect(setup(broker), { clean: true })
|
|
subscribe(t, subscriber, 'hello', 0, function () {
|
|
subscriber.outStream.on('data', function (packet) {
|
|
t.same(packet, expected, 'packet must match')
|
|
})
|
|
})
|
|
})
|
|
|
|
test('only get the last retained messages in same topic', function (t) {
|
|
t.plan(4)
|
|
|
|
const broker = aedes()
|
|
t.teardown(broker.close.bind(broker))
|
|
|
|
const publisher = connect(setup(broker), { clean: true })
|
|
const expected = {
|
|
cmd: 'publish',
|
|
topic: 'hello',
|
|
payload: Buffer.from('foo'),
|
|
qos: 0,
|
|
retain: true,
|
|
dup: false,
|
|
length: 10
|
|
}
|
|
publisher.inStream.write({
|
|
cmd: 'publish',
|
|
topic: 'hello',
|
|
payload: 'world',
|
|
qos: 0,
|
|
retain: true
|
|
})
|
|
publisher.inStream.write({
|
|
cmd: 'publish',
|
|
topic: 'hello',
|
|
payload: 'foo',
|
|
qos: 0,
|
|
retain: true
|
|
})
|
|
const subscriber = connect(setup(broker), { clean: true })
|
|
subscribe(t, subscriber, 'hello', 0, function () {
|
|
subscriber.outStream.on('data', function (packet) {
|
|
t.same(packet, expected, 'packet must match')
|
|
})
|
|
})
|
|
})
|
|
|
|
test('deliver QoS 1 retained messages to new subscriptions', function (t) {
|
|
t.plan(4)
|
|
|
|
const broker = aedes()
|
|
t.teardown(broker.close.bind(broker))
|
|
|
|
const publisher = connect(setup(broker))
|
|
const subscriber = connect(setup(broker))
|
|
const expected = {
|
|
cmd: 'publish',
|
|
topic: 'hello',
|
|
payload: Buffer.from('world'),
|
|
qos: 1,
|
|
dup: false,
|
|
length: 14,
|
|
retain: true
|
|
}
|
|
|
|
publisher.inStream.write({
|
|
cmd: 'publish',
|
|
topic: 'hello',
|
|
payload: 'world',
|
|
qos: 1,
|
|
messageId: 42,
|
|
retain: true
|
|
})
|
|
|
|
publisher.outStream.on('data', function (packet) {
|
|
subscribe(t, subscriber, 'hello', 1, function () {
|
|
subscriber.outStream.once('data', function (packet) {
|
|
delete packet.messageId
|
|
t.same(packet, expected, 'packet must match')
|
|
})
|
|
})
|
|
})
|
|
})
|
|
|
|
test('deliver QoS 1 retained messages to established subscriptions', function (t) {
|
|
t.plan(4)
|
|
|
|
const broker = aedes()
|
|
t.teardown(broker.close.bind(broker))
|
|
|
|
const publisher = connect(setup(broker))
|
|
const subscriber = connect(setup(broker))
|
|
const expected = {
|
|
cmd: 'publish',
|
|
topic: 'hello',
|
|
payload: Buffer.from('world'),
|
|
qos: 1,
|
|
dup: false,
|
|
length: 14,
|
|
retain: false
|
|
}
|
|
|
|
subscribe(t, subscriber, 'hello', 1, function () {
|
|
subscriber.outStream.once('data', function (packet) {
|
|
delete packet.messageId
|
|
t.same(packet, expected, 'packet must match')
|
|
})
|
|
publisher.inStream.write({
|
|
cmd: 'publish',
|
|
topic: 'hello',
|
|
payload: 'world',
|
|
qos: 1,
|
|
messageId: 42,
|
|
retain: true
|
|
})
|
|
})
|
|
})
|
|
|
|
test('deliver QoS 0 retained message with QoS 1 subscription', function (t) {
|
|
t.plan(4)
|
|
|
|
const broker = aedes()
|
|
t.teardown(broker.close.bind(broker))
|
|
|
|
const publisher = connect(setup(broker))
|
|
const subscriber = connect(setup(broker))
|
|
const expected = {
|
|
cmd: 'publish',
|
|
topic: 'hello',
|
|
payload: Buffer.from('world'),
|
|
qos: 0,
|
|
dup: false,
|
|
length: 12,
|
|
retain: true
|
|
}
|
|
|
|
broker.mq.on('hello', function (msg, cb) {
|
|
cb()
|
|
|
|
// defer this or it will receive the message which
|
|
// is being published
|
|
setImmediate(function () {
|
|
subscribe(t, subscriber, 'hello', 1, function () {
|
|
subscriber.outStream.once('data', function (packet) {
|
|
t.same(packet, expected, 'packet must match')
|
|
})
|
|
})
|
|
})
|
|
})
|
|
|
|
publisher.inStream.write({
|
|
cmd: 'publish',
|
|
topic: 'hello',
|
|
payload: Buffer.from('world'),
|
|
qos: 0,
|
|
messageId: 42,
|
|
retain: true
|
|
})
|
|
})
|
|
|
|
test('disconnect and retain messages with QoS 1 [clean=false]', function (t) {
|
|
t.plan(7)
|
|
|
|
const broker = aedes()
|
|
t.teardown(broker.close.bind(broker))
|
|
|
|
let subscriber = noError(connect(setup(broker), { clean: false, clientId: 'abcde' }), t)
|
|
const expected = {
|
|
cmd: 'publish',
|
|
topic: 'hello',
|
|
payload: Buffer.from('world'),
|
|
qos: 1,
|
|
dup: false,
|
|
length: 14,
|
|
retain: true
|
|
}
|
|
|
|
subscribe(t, subscriber, 'hello', 1, function () {
|
|
subscriber.inStream.write({
|
|
cmd: 'disconnect'
|
|
})
|
|
|
|
subscriber.outStream.on('data', function (packet) {
|
|
console.log('original', packet)
|
|
})
|
|
|
|
const publisher = noError(connect(setup(broker)), t)
|
|
|
|
publisher.inStream.write({
|
|
cmd: 'publish',
|
|
topic: 'hello',
|
|
payload: 'world',
|
|
qos: 1,
|
|
messageId: 42,
|
|
retain: true
|
|
})
|
|
|
|
publisher.outStream.once('data', function (packet) {
|
|
t.equal(packet.cmd, 'puback')
|
|
|
|
subscriber = connect(setup(broker), { clean: false, clientId: 'abcde' }, function (connect) {
|
|
t.equal(connect.sessionPresent, true, 'session present is set to true')
|
|
})
|
|
|
|
subscriber.outStream.once('data', function (packet) {
|
|
// receive any queued messages (no matter they are retained messages) at the disconnected time
|
|
t.not(packet.messageId, 42, 'messageId must differ')
|
|
delete packet.messageId
|
|
packet.length = 14
|
|
t.same(packet, expected, 'packet must match')
|
|
|
|
// there should be no messages come from restored subscriptions
|
|
subscriber.outStream.once('data', function (packet) {
|
|
t.fail('should not receive any more messages')
|
|
})
|
|
})
|
|
})
|
|
})
|
|
})
|
|
|
|
test('disconnect and two retain messages with QoS 1 [clean=false]', function (t) {
|
|
t.plan(15)
|
|
|
|
const broker = aedes()
|
|
t.teardown(broker.close.bind(broker))
|
|
|
|
let subscriber = noError(connect(setup(broker), { clean: false, clientId: 'abcde' }), t)
|
|
const expected = {
|
|
cmd: 'publish',
|
|
topic: 'hello',
|
|
qos: 1,
|
|
dup: false,
|
|
length: 14,
|
|
retain: true
|
|
}
|
|
|
|
subscribe(t, subscriber, 'hello', 1, function () {
|
|
subscriber.inStream.write({
|
|
cmd: 'disconnect'
|
|
})
|
|
|
|
subscriber.outStream.on('data', function (packet) {
|
|
console.log('original', packet)
|
|
})
|
|
|
|
const publisher = noError(connect(setup(broker)), t)
|
|
|
|
publisher.inStream.write({
|
|
cmd: 'publish',
|
|
topic: 'hello',
|
|
payload: 'world',
|
|
qos: 1,
|
|
messageId: 41,
|
|
retain: true
|
|
})
|
|
|
|
publisher.outStream.once('data', function (packet) {
|
|
t.equal(packet.cmd, 'puback')
|
|
|
|
publisher.inStream.write({
|
|
cmd: 'publish',
|
|
topic: 'hello',
|
|
payload: 'world2',
|
|
qos: 1,
|
|
messageId: 42,
|
|
retain: true
|
|
})
|
|
|
|
publisher.outStream.once('data', function (packet) {
|
|
t.equal(packet.cmd, 'puback')
|
|
|
|
subscriber = connect(setup(broker), { clean: false, clientId: 'abcde' }, function (connect) {
|
|
t.equal(connect.sessionPresent, true, 'session present is set to true')
|
|
})
|
|
|
|
subscriber.outStream.once('data', function (packet) {
|
|
// receive any queued messages (included retained messages) at the disconnected time
|
|
t.not(packet.messageId, 41, 'messageId must differ')
|
|
delete packet.messageId
|
|
packet.length = 14
|
|
expected.payload = Buffer.from('world')
|
|
t.same(packet, expected, 'packet must match')
|
|
|
|
// receive any queued messages (included retained messages) at the disconnected time
|
|
subscriber.outStream.once('data', function (packet) {
|
|
t.not(packet.messageId, 42, 'messageId must differ')
|
|
delete packet.messageId
|
|
packet.length = 14
|
|
expected.payload = Buffer.from('world2')
|
|
t.same(packet, expected, 'packet must match')
|
|
|
|
// should get the last retained message when we do a subscribe
|
|
subscribe(t, subscriber, 'hello', 1, function () {
|
|
subscriber.outStream.on('data', function (packet) {
|
|
t.not(packet.messageId, 42, 'messageId must differ')
|
|
delete packet.messageId
|
|
packet.length = 14
|
|
expected.payload = Buffer.from('world2')
|
|
t.same(packet, expected, 'packet must match')
|
|
})
|
|
})
|
|
})
|
|
})
|
|
})
|
|
})
|
|
})
|
|
})
|