首次提交
This commit is contained in:
+255
@@ -0,0 +1,255 @@
|
||||
'use strict'
|
||||
|
||||
const fastfall = require('fastfall')
|
||||
const Packet = require('aedes-packet')
|
||||
const { through } = require('../utils')
|
||||
const { validateTopic, $SYS_PREFIX } = require('../utils')
|
||||
const write = require('../write')
|
||||
|
||||
const subscribeTopicActions = fastfall([
|
||||
authorize,
|
||||
storeSubscriptions,
|
||||
addSubs
|
||||
])
|
||||
const restoreTopicActions = fastfall([
|
||||
authorize,
|
||||
addSubs
|
||||
])
|
||||
|
||||
function SubAck (packet, granted) {
|
||||
this.cmd = 'suback'
|
||||
this.messageId = packet.messageId
|
||||
// the qos granted
|
||||
this.granted = granted
|
||||
}
|
||||
|
||||
function Subscription (qos, func, rh, rap, nl) {
|
||||
this.qos = qos
|
||||
this.func = func
|
||||
|
||||
// retain-handling indicates how retained messages should be
|
||||
// handled when a new subscription is created
|
||||
// (see [MQTT-3.3.1-9] through [MQTT-3.3.1-11])
|
||||
this.rh = rh
|
||||
|
||||
// retain-as-published indicates whether to leave the retain flag as-is (true)
|
||||
// or to clear it before sending to subscriptions (false) default false
|
||||
// (see [MQTT-3.3.1-12] through [MQTT-3.3.1-13])
|
||||
this.rap = rap
|
||||
|
||||
// no-local indicates that a client should not receive its own
|
||||
// messages (see [MQTT-3.8.3-3])
|
||||
this.nl = nl
|
||||
}
|
||||
|
||||
function SubscribeState (client, packet, restore, finish) {
|
||||
this.client = client
|
||||
this.packet = packet
|
||||
this.actions = restore ? restoreTopicActions : subscribeTopicActions
|
||||
this.finish = finish
|
||||
this.subState = []
|
||||
}
|
||||
|
||||
function SubState (client, packet, granted, rh, rap, nl) {
|
||||
this.client = client
|
||||
this.packet = packet
|
||||
this.granted = granted
|
||||
this.rh = rh
|
||||
this.rap = rap
|
||||
this.nl = nl
|
||||
}
|
||||
|
||||
// if same subscribed topic in subs array, we pick up the last one
|
||||
function _dedupe (subs) {
|
||||
const dedupeSubs = {}
|
||||
for (let i = 0; i < subs.length; i++) {
|
||||
const sub = subs[i]
|
||||
dedupeSubs[sub.topic] = sub
|
||||
}
|
||||
const ret = []
|
||||
for (const key in dedupeSubs) {
|
||||
ret.push(dedupeSubs[key])
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
function handleSubscribe (client, packet, restore, done) {
|
||||
packet.subscriptions = packet.subscriptions.length === 1 ? packet.subscriptions : _dedupe(packet.subscriptions)
|
||||
client.broker._parallel(
|
||||
new SubscribeState(client, packet, restore, done), // what will be this in the functions
|
||||
doSubscribe, // function to call
|
||||
packet.subscriptions, // first argument of the function
|
||||
restore ? done : completeSubscribe // the function to be called when the parallel ends
|
||||
)
|
||||
}
|
||||
|
||||
function doSubscribe (sub, done) {
|
||||
const s = new SubState(this.client, this.packet, sub.qos, sub.rh, sub.rap, sub.nl)
|
||||
this.subState.push(s)
|
||||
this.actions.call(s, sub, done)
|
||||
}
|
||||
|
||||
function authorize (sub, done) {
|
||||
const err = validateTopic(sub.topic, 'SUBSCRIBE')
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
const client = this.client
|
||||
client.broker.authorizeSubscribe(client, sub, done)
|
||||
}
|
||||
|
||||
function blockDollarSignTopics (func) {
|
||||
return function deliverSharp (packet, cb) {
|
||||
// '$' is 36
|
||||
if (packet.topic.charCodeAt(0) === 36) {
|
||||
cb()
|
||||
} else {
|
||||
func(packet, cb)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function storeSubscriptions (sub, done) {
|
||||
if (!sub || typeof sub !== 'object') {
|
||||
// means failure: MQTT 3.1.1 specs > 3.9.3 Payload
|
||||
this.granted = 128
|
||||
return done(null, null)
|
||||
}
|
||||
|
||||
const packet = this.packet
|
||||
const client = this.client
|
||||
|
||||
if (client.clean) {
|
||||
return done(null, sub)
|
||||
}
|
||||
|
||||
client.broker.persistence.addSubscriptions(client, packet.subscriptions, function addSub (err) {
|
||||
done(err, sub)
|
||||
})
|
||||
}
|
||||
|
||||
function addSubs (sub, done) {
|
||||
if (!sub || typeof sub !== 'object') {
|
||||
return done()
|
||||
}
|
||||
|
||||
const client = this.client
|
||||
const broker = client.broker
|
||||
const topic = sub.topic
|
||||
const qos = sub.qos
|
||||
const rh = this.rh
|
||||
const rap = this.rap
|
||||
const nl = this.nl
|
||||
let func = qos > 0 ? client.deliverQoS : client.deliver0
|
||||
|
||||
const deliverFunc = func
|
||||
func = function handlePacketSubscription (_packet, cb) {
|
||||
_packet = new Packet(_packet, broker)
|
||||
_packet.nl = nl
|
||||
if (!rap) _packet.retain = false
|
||||
deliverFunc(_packet, cb)
|
||||
}
|
||||
|
||||
// [MQTT-4.7.2-1]
|
||||
if (isStartsWithWildcard(topic)) {
|
||||
func = blockDollarSignTopics(func)
|
||||
}
|
||||
|
||||
if (client.closed || client.broker.closed) {
|
||||
// a hack, sometimes client.close() or broker.close() happened
|
||||
// before authenticate() comes back
|
||||
// we don't continue subscription here
|
||||
return
|
||||
}
|
||||
|
||||
if (!client.subscriptions[topic]) {
|
||||
client.subscriptions[topic] = new Subscription(qos, func, rh, rap, nl)
|
||||
broker.subscribe(topic, func, done)
|
||||
} else if (client.subscriptions[topic].qos !== qos || client.subscriptions[topic].rh !== rh || client.subscriptions[topic].rap !== rap || client.subscriptions[topic].nl !== nl) {
|
||||
broker.unsubscribe(topic, client.subscriptions[topic].func)
|
||||
client.subscriptions[topic] = new Subscription(qos, func, rh, rap, nl)
|
||||
broker.subscribe(topic, func, done)
|
||||
} else {
|
||||
done()
|
||||
}
|
||||
}
|
||||
|
||||
// + is 43
|
||||
// # is 35
|
||||
function isStartsWithWildcard (topic) {
|
||||
const code = topic.charCodeAt(0)
|
||||
return code === 43 || code === 35
|
||||
}
|
||||
|
||||
function completeSubscribe (err) {
|
||||
const done = this.finish
|
||||
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
|
||||
const packet = this.packet
|
||||
const client = this.client
|
||||
|
||||
if (packet.messageId !== undefined) {
|
||||
// [MQTT-3.9.3-1]
|
||||
write(client,
|
||||
new SubAck(packet, this.subState.map(obj => obj.granted)),
|
||||
done)
|
||||
} else {
|
||||
done()
|
||||
}
|
||||
|
||||
const broker = client.broker
|
||||
|
||||
// subscriptions array to return as result in 'subscribe' event and $SYS
|
||||
const subs = packet.subscriptions
|
||||
|
||||
// topics we need to retrieve retain values from
|
||||
const topics = []
|
||||
|
||||
for (let i = 0; i < subs.length; i++) {
|
||||
// skip topics that are not allowed
|
||||
if (this.subState[i].granted !== 128) {
|
||||
topics.push(subs[i].topic)
|
||||
}
|
||||
// set granted qos to subscriptions
|
||||
subs[i].qos = this.subState[i].granted
|
||||
}
|
||||
|
||||
this.subState = []
|
||||
|
||||
broker.emit('subscribe', subs, client)
|
||||
broker.publish({
|
||||
topic: $SYS_PREFIX + broker.id + '/new/subscribes',
|
||||
payload: Buffer.from(JSON.stringify({
|
||||
clientId: client.id,
|
||||
subs
|
||||
}), 'utf8')
|
||||
}, noop)
|
||||
|
||||
// Conform to MQTT 3.1.1 section 3.1.2.4
|
||||
// Restored sessions should not contain any retained message.
|
||||
// Retained message should be only fetched from SUBSCRIBE.
|
||||
|
||||
if (topics.length > 0) {
|
||||
const persistence = broker.persistence
|
||||
const stream = persistence.createRetainedStreamCombi(topics)
|
||||
stream.pipe(through(function sendRetained (packet, enc, cb) {
|
||||
packet = new Packet({
|
||||
cmd: packet.cmd,
|
||||
qos: packet.qos,
|
||||
topic: packet.topic,
|
||||
payload: packet.payload,
|
||||
retain: true
|
||||
}, broker)
|
||||
// this should not be deduped
|
||||
packet.brokerId = null
|
||||
client.deliverQoS(packet, cb)
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
function noop () { }
|
||||
|
||||
module.exports = handleSubscribe
|
||||
Reference in New Issue
Block a user