Files
2026-07-13 12:06:16 +08:00

273 lines
6.8 KiB
JavaScript

'use strict'
const retimer = require('retimer')
const { pipeline } = require('stream')
const write = require('../write')
const QoSPacket = require('../qos-packet')
const { through } = require('../utils')
const handleSubscribe = require('./subscribe')
const uniqueId = require('hyperid')()
function Connack (arg) {
this.cmd = 'connack'
this.returnCode = arg.returnCode
this.sessionPresent = arg.sessionPresent
}
function ClientPacketStatus (client, packet) {
this.client = client
this.packet = packet
}
const connectActions = [
authenticate,
setKeepAlive,
fetchSubs,
restoreSubs,
storeWill,
registerClient,
doConnack,
emptyQueue
]
const errorMessages = [
'',
'unacceptable protocol version',
'identifier rejected',
'Server unavailable',
'bad user name or password',
'not authorized',
'keep alive limit exceeded'
]
function handleConnect (client, packet, done) {
clearTimeout(client._connectTimer)
client._connectTimer = null
client.connecting = true
client.broker.preConnect(client, packet, negate)
function negate (err, successful) {
if (!err && successful === true) {
setImmediate(init, client, packet, done)
} else {
client.connecting = false
done(err)
}
}
}
function init (client, packet, done) {
const clientId = packet.clientId
let returnCode = 0
// [MQTT-3.1.2-2]
if (packet.protocolVersion < 3 || packet.protocolVersion > 4) {
returnCode = 1
}
// MQTT 3.1.0 allows <= 23 client id length
if (packet.protocolVersion === 3 && clientId.length > client.broker.maxClientsIdLength) {
returnCode = 2
}
// check if the client keepalive is compatible with broker settings
if (client.broker.keepaliveLimit && (!packet.keepalive || packet.keepalive > client.broker.keepaliveLimit)) {
returnCode = 6
}
if (returnCode > 0) {
const error = new Error(errorMessages[returnCode])
error.errorCode = returnCode
doConnack(
{ client, returnCode, sessionPresent: false },
done.bind(this, error))
return
}
client.id = clientId || 'aedes_' + uniqueId()
client.clean = packet.clean
client.version = packet.protocolVersion
client._will = packet.will
client.broker._series(
new ClientPacketStatus(client, packet),
connectActions,
{ returnCode: 0, sessionPresent: false }, // [MQTT-3.1.4-4], [MQTT-3.2.2-4]
function (err) {
this.client.connecting = false
if (!err) {
this.client.connected = true
this.client.broker.emit('clientReady', client)
this.client.emit('connected')
}
done(err)
})
}
function authenticate (arg, done) {
const client = this.client
client.pause()
client.broker.authenticate(
client,
this.packet.username,
this.packet.password,
negate)
function negate (err, successful) {
if (client.closed || client.broker.closed) {
// a hack, sometimes client.close() or broker.close() happened
// before authenticate() comes back
// we stop here for not to register it and deregister it in write()
return
}
if (!err && successful) {
client._authorized = true
return done()
}
if (err) {
const errCode = err.returnCode
if (errCode && (errCode >= 2 && errCode <= 5)) {
arg.returnCode = errCode
} else {
arg.returnCode = 5
}
if (!err.message) {
err.message = errorMessages[arg.returnCode]
}
} else {
arg.returnCode = 5
err = new Error(errorMessages[arg.returnCode])
}
err.errorCode = arg.returnCode
arg.client = client
doConnack(arg,
// [MQTT-3.2.2-5]
client.close.bind(client, done.bind(this, err)))
}
}
function setKeepAlive (arg, done) {
if (this.packet.keepalive > 0) {
const client = this.client
// [MQTT-3.1.2-24]
client._keepaliveInterval = (this.packet.keepalive * 1500) + 1
client._keepaliveTimer = retimer(function keepaliveTimeout () {
client.broker.emit('keepaliveTimeout', client)
client.emit('error', new Error('keep alive timeout'))
}, client._keepaliveInterval)
}
done()
}
function fetchSubs (arg, done) {
const client = this.client
if (!this.packet.clean) {
client.broker.persistence.subscriptionsByClient({
id: client.id,
done,
arg
}, gotSubs)
return
}
arg.sessionPresent = false // [MQTT-3.2.2-1]
client.broker.persistence.cleanSubscriptions(
client,
done)
}
function gotSubs (err, subs, client) {
if (err) {
return client.done(err)
}
client.arg.subs = subs
client.done()
}
function restoreSubs (arg, done) {
if (arg.subs) {
handleSubscribe(this.client, { subscriptions: arg.subs }, true, done)
arg.sessionPresent = !!arg.subs // cast to boolean, [MQTT-3.2.2-2]
return
}
arg.sessionPresent = false // [MQTT-3.2.2-1], [MQTT-3.2.2-3]
done()
}
function storeWill (arg, done) {
const client = this.client
client.will = client._will
// delete any existing will messages from persistence
client.broker.persistence.delWill(client, function () {
if (client.will) {
client.broker.persistence.putWill(
client,
client.will,
done)
} else {
done()
}
})
}
function registerClient (arg, done) {
const client = this.client
client.broker.registerClient(client)
done()
}
function doConnack (arg, done) {
const client = arg.client || this.client
const connack = new Connack(arg)
write(client, connack, function (err) {
if (!err) {
client.broker.emit('connackSent', connack, client)
client.connackSent = true
}
done(err)
})
}
// push any queued messages (included retained messages) at the disconnected time
// when QoS > 0 and session is true
function emptyQueue (arg, done) {
const client = this.client
const persistence = client.broker.persistence
const outgoing = persistence.outgoingStream(client)
client.resume()
pipeline(
outgoing,
through(function clearQueue (data, enc, next) {
const packet = new QoSPacket(data, client)
// Here we are deliberatly passing only the error
// This is because there is no destination stream so the "client"
// Object filled the buffer up to the highWaterMark preventing stored messages
// being sent
packet.writeCallback = (error, _client) => next(error)
persistence.outgoingUpdate(client, packet, emptyQueueFilter)
}),
done
)
}
function emptyQueueFilter (err, client, packet) {
const next = packet.writeCallback
if (err) {
client.emit('error', err)
return next()
}
const authorized = (packet.cmd === 'publish')
? client.broker.authorizeForward(client, packet)
: true
const persistence = client.broker.persistence
if (client.clean || !authorized) {
persistence.outgoingClearMessageId(client, packet, next)
} else {
write(client, packet, next)
}
}
module.exports = handleConnect