UNPKG

matterbridge-roborock-vacuum-plugin

Version:
130 lines 5.17 kB
import crypto from 'node:crypto'; import * as dgram from 'node:dgram'; import { Parser } from 'binary-parser/dist/binary_parser.js'; import CRC32 from 'crc-32'; import { ProtocolVersion } from '../enums/index.js'; export class LocalNetworkUDPClient { logger; PORT = 58866; server = undefined; listeners = []; V10Parser; L01Parser; constructor(logger) { this.logger = logger; this.V10Parser = new Parser() .endianness('big') .string('version', { length: 3 }) .uint32('seq') .uint16('protocol') .uint16('payloadLen') .buffer('payload', { length: 'payloadLen' }) .uint32('crc32'); this.L01Parser = new Parser() .endianness('big') .string('version', { length: 3 }) .string('field1', { length: 4 }) .string('field2', { length: 2 }) .uint16('payloadLen') .buffer('payload', { length: 'payloadLen' }) .uint32('crc32'); } registerListener(listener) { this.listeners.push(listener); } connect() { try { this.server = dgram.createSocket('udp4'); this.server.bind(this.PORT); this.server.on('message', this.onMessage.bind(this)); this.server.on('error', this.onError.bind(this)); } catch (err) { this.logger.error(`[LocalNetworkUDPClient] Failed to create UDP server: ${err}`); this.server = undefined; } } disconnect() { if (this.server) { return new Promise((resolve) => { this.server?.close(() => { this.server = undefined; resolve(); }); }); } return Promise.resolve(); } async onError(result) { this.logger.error(`[LocalNetworkUDPClient] UDP socket error: ${result}`); if (this.server) { this.server.close(); this.server = undefined; } return Promise.resolve(); } async onMessage(buffer) { const message = await this.deserializeMessage(buffer); this.logger.debug(`[LocalNetworkUDPClient] Received message: ${JSON.stringify(message)}`); for (const listener of this.listeners) { await listener.onMessage(message.duid, message.ip); } } async deserializeMessage(buffer) { const version = buffer.toString('latin1', 0, 3); let data; switch (version) { case ProtocolVersion.V1: data = await this.deserializeV10Message(buffer); return JSON.parse(data); case ProtocolVersion.L01: data = await this.deserializeL01Message(buffer); return JSON.parse(data); default: throw new Error(`[LocalNetworkUDPClient] Unsupported protocol version: ${version}`); } } async deserializeV10Message(buffer) { const data = this.V10Parser.parse(buffer); const crc32 = CRC32.buf(buffer.subarray(0, buffer.length - 4)) >>> 0; const expectedCrc32 = data.crc32; if (crc32 != expectedCrc32) { throw new Error(`[LocalNetworkUDPClient] wrong CRC32: ${crc32}, expect: ${expectedCrc32}`); } const decipher = crypto.createDecipheriv('aes-128-ecb', Buffer.from('qWKYcdQWrbm9hPqe', 'utf8'), null); decipher.setAutoPadding(false); let decrypted = decipher.update(data.payload, 'binary', 'utf8'); decrypted += decipher.final('utf8'); const paddingLength = decrypted.charCodeAt(decrypted.length - 1); return decrypted.slice(0, -paddingLength); } async deserializeL01Message(buffer) { const data = this.L01Parser.parse(buffer); const crc32 = CRC32.buf(buffer.subarray(0, buffer.length - 4)) >>> 0; const expectedCrc32 = data.crc32; if (crc32 != expectedCrc32) { throw new Error(`[LocalNetworkUDPClient] wrong CRC32: ${crc32}, expect: ${expectedCrc32}`); } const payload = data.payload; const key = crypto.createHash('sha256').update(Buffer.from('qWKYcdQWrbm9hPqe', 'utf8')).digest(); const digestInput = buffer.subarray(0, 9); const digest = crypto.createHash('sha256').update(digestInput).digest(); const iv = digest.subarray(0, 12); const tag = payload.subarray(payload.length - 16); const ciphertext = payload.subarray(0, payload.length - 16); const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv); decipher.setAuthTag(tag); try { const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]); return decrypted.toString('utf8'); } catch (e) { const exception = e; throw new Error(`[LocalNetworkUDPClient] failed to decrypt: ${exception.message} iv: ${iv.toString('hex')} tag: ${tag.toString('hex')} encrypted: ${ciphertext.toString('hex')}`, { cause: e }); } } } //# sourceMappingURL=udpClient.js.map