首次提交

This commit is contained in:
2026-07-13 12:06:16 +08:00
commit e3c810b1a6
1690 changed files with 228324 additions and 0 deletions
+255
View File
@@ -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