@seriousme/opifex
Version:
MQTT client & server for Deno & NodeJS
151 lines • 4.55 kB
JavaScript
/**
* @module mqttConn
*/
import { decodePayload, encode, getLengthDecoder, } from "../mqttPacket/mod.js";
import { assert } from "../utils/mod.js";
import { Conn } from "../socket/socket.js";
/**
* Common MQTT connection error messages
*/
export const MqttConnError = {
invalidPacket: "Invalid Packet",
packetTooLarge: "Packet too large",
UnexpectedEof: "Unexpected EOF",
};
/**
* Read a single byte from the connection
* @param conn Connection to read from
* @returns Single byte as number
* @throws Error if EOF reached unexpectedly
*/
async function readByte(conn) {
const buf = new Uint8Array(1);
const bytesRead = await conn.read(buf);
assert(bytesRead !== null, MqttConnError.UnexpectedEof);
assert(bytesRead !== 0, MqttConnError.UnexpectedEof);
return buf[0];
}
/**
* Read exact number of bytes into buffer
* @param conn Connection to read from
* @param buf Buffer to read into
* @throws Error if EOF reached unexpectedly
*/
async function readFull(conn, buf) {
let bytesRead = 0;
while (bytesRead < buf.length) {
const read = await conn.read(buf.subarray(bytesRead));
assert(read !== null, MqttConnError.UnexpectedEof);
assert(read !== 0, MqttConnError.UnexpectedEof);
bytesRead += read;
}
}
/**
* Read a complete MQTT packet from the connection
* @param conn Connection to read from
* @param maxPacketSize Maximum allowed packet size
* @returns Decoded MQTT packet
* @throws Error if packet is invalid or too large
*/
export async function readPacket(conn, maxPacketSize) {
// fixed header is 1 byte of type + flags
// + a maximum of 4 bytes to encode the remaining length
const decodeLength = getLengthDecoder();
const firstByte = await readByte(conn);
let result;
do {
const byte = await readByte(conn);
result = decodeLength(byte);
} while (!result.done);
const remainingLength = result.length;
assert(remainingLength < maxPacketSize - 1, MqttConnError.packetTooLarge);
const packetBuf = new Uint8Array(remainingLength);
// read the rest of the packet
await readFull(conn, packetBuf);
const packet = decodePayload(firstByte, packetBuf);
assert(packet !== null, MqttConnError.UnexpectedEof);
return packet;
}
/**
* MQTT Connection class implementing IMqttConn interface
*/
export class MqttConn {
/** Underlying connection */
conn;
/** Maximum allowed packet size */
maxPacketSize;
/** Reason for connection closure if any */
_reason = undefined;
/** Whether connection is closed */
_isClosed = false;
/**
* Create new MQTT connection
* @param options Connection options
* @param options.conn Underlying socket connection
* @param options.maxPacketSize Maximum allowed packet size (default 2MB)
*/
constructor({ conn, maxPacketSize, }) {
this.conn = new Conn(conn);
this.maxPacketSize = maxPacketSize || 2 * 1024 * 1024;
}
/** Get reason for connection closure */
get reason() {
return this._reason;
}
/**
* Async iterator for receiving packets
* @yields MQTT packets
*/
async *[Symbol.asyncIterator]() {
while (!this._isClosed) {
try {
yield await readPacket(this.conn, this.maxPacketSize);
}
catch (err) {
if (err instanceof Error) {
if (err.name === "PartialReadError") {
err.message = MqttConnError.UnexpectedEof;
}
this._reason = err.message;
}
// packet too large, malformed packet or connection closed
this.close();
break;
}
}
}
/**
* Send an MQTT packet
* @param data Packet to send
*/
async send(data) {
try {
await this.conn.write(encode(data));
}
catch (err) {
if (err instanceof Error) {
this._reason = err.message;
}
this.close();
}
}
/** Whether connection is closed */
get isClosed() {
return this._isClosed;
}
/** Close the connection */
close() {
if (this.isClosed)
return;
try {
this.conn.close();
}
catch (e) {
console.error(e);
}
finally {
this._isClosed = true;
}
}
}
//# sourceMappingURL=mqttConn.js.map