matterbridge-roborock-vacuum-plugin
Version:
Matterbridge Roborock Vacuum Plugin
80 lines (79 loc) • 3.42 kB
JavaScript
import crypto from 'node:crypto';
import CRC32 from 'crc-32';
import { Parser } from 'binary-parser';
import { ResponseMessage } from '../broadcast/model/responseMessage.js';
import * as CryptoUtils from './cryptoHelper.js';
import { Protocol } from '../broadcast/model/protocol.js';
export class MessageDeserializer {
context;
messageParser;
logger;
constructor(context, logger) {
this.context = context;
this.logger = logger;
this.messageParser = new Parser()
.endianess('big')
.string('version', {
length: 3,
})
.uint32('seq')
.uint32('random')
.uint32('timestamp')
.uint16('protocol')
.uint16('payloadLen')
.buffer('payload', {
length: 'payloadLen',
})
.uint32('crc32');
}
deserialize(duid, message) {
const version = message.toString('latin1', 0, 3);
if (version !== '1.0' && version !== 'A01') {
throw new Error('unknown protocol version ' + version);
}
const crc32 = CRC32.buf(message.subarray(0, message.length - 4)) >>> 0;
const expectedCrc32 = message.readUInt32BE(message.length - 4);
if (crc32 != expectedCrc32) {
throw new Error(`Wrong CRC32 ${crc32}, expected ${expectedCrc32}`);
}
const localKey = this.context.getLocalKey(duid);
if (!localKey) {
this.logger.notice(`Unable to retrieve local key for ${duid}, it should be from other vacuums`);
return new ResponseMessage(duid, { dps: { id: 0, result: null } });
}
const data = this.messageParser.parse(message);
if (version == '1.0') {
const aesKey = CryptoUtils.md5bin(CryptoUtils.encodeTimestamp(data.timestamp) + localKey + CryptoUtils.SALT);
const decipher = crypto.createDecipheriv('aes-128-ecb', aesKey, null);
data.payload = Buffer.concat([decipher.update(data.payload), decipher.final()]);
}
else if (version == 'A01') {
const iv = CryptoUtils.md5hex(data.random.toString(16).padStart(8, '0') + '726f626f726f636b2d67a6d6da').substring(8, 24);
const decipher = crypto.createDecipheriv('aes-128-cbc', localKey, iv);
data.payload = Buffer.concat([decipher.update(data.payload), decipher.final()]);
}
if (data.protocol == Protocol.map_response) {
return new ResponseMessage(duid, { dps: { id: 0, result: null } });
}
if (data.protocol == Protocol.rpc_response || data.protocol == Protocol.general_request) {
return this.deserializeProtocolRpcResponse(duid, data);
}
else {
this.logger.error('unknown protocol: ' + data.protocol);
return new ResponseMessage(duid, { dps: { id: 0, result: null } });
}
}
deserializeProtocolRpcResponse(duid, data) {
const payload = JSON.parse(data.payload.toString());
const dps = payload.dps;
this.parseJsonInDps(dps, Protocol.general_request);
this.parseJsonInDps(dps, Protocol.rpc_response);
return new ResponseMessage(duid, dps);
}
parseJsonInDps(dps, index) {
const indexString = index.toString();
if (dps[indexString] !== undefined) {
dps[indexString] = JSON.parse(dps[indexString]);
}
}
}