Skip to content

Commit

Permalink
Merge pull request #656 from nospam2k/patch-3
Browse files Browse the repository at this point in the history
Update message-parser.js
  • Loading branch information
Apollon77 authored Aug 15, 2024
2 parents 32fa7f6 + f526710 commit 59883ed
Showing 1 changed file with 134 additions and 35 deletions.
169 changes: 134 additions & 35 deletions lib/message-parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const Cipher = require('./cipher');
const crc = require('./crc');

const HEADER_SIZE = 16;
const HEADER_SIZE_3_5 = 4;

/**
* Human-readable definitions
Expand Down Expand Up @@ -109,14 +110,17 @@ class MessageParser {
// Check for prefix
const prefix = buffer.readUInt32BE(0);

if (prefix !== 0x000055AA) {
// Only for 3.4 and 3.5 packets
if (prefix !== 0x000055AA && prefix !== 0x00006699) {
throw new TypeError(`Prefix does not match: ${buffer.toString('hex')}`);
}

// Check for extra data
let leftover = false;

const suffixLocation = buffer.indexOf('0000AA55', 0, 'hex');
let suffixLocation = buffer.indexOf('0000AA55', 0, 'hex');
if (suffixLocation === -1) // Couldn't find 0000AA55 during parse
suffixLocation = buffer.indexOf('00009966', 0, 'hex');

if (suffixLocation !== buffer.length - 4) {
leftover = buffer.slice(suffixLocation + 4);
Expand All @@ -126,22 +130,45 @@ class MessageParser {
// Check for suffix
const suffix = buffer.readUInt32BE(buffer.length - 4);

if (suffix !== 0x0000AA55) {
if (suffix !== 0x0000AA55 && suffix !== 0x00009966) {
throw new TypeError(`Suffix does not match: ${buffer.toString('hex')}`);
}

// Get sequence number
const sequenceN = buffer.readUInt32BE(4);
let sequenceN;
let commandByte;
let payloadSize;

// Get command byte
const commandByte = buffer.readUInt32BE(8);
if (suffix === 0x0000AA55)
{
// Get sequence number
sequenceN = buffer.readUInt32BE(4);

// Get payload size
const payloadSize = buffer.readUInt32BE(12);
// Get command byte
commandByte = buffer.readUInt32BE(8);

// Check for payload
if (buffer.length - 8 < payloadSize) {
throw new TypeError(`Packet missing payload: payload has length ${payloadSize}.`);
// Get payload size
payloadSize = buffer.readUInt32BE(12);

// Check for payload
if (buffer.length - 8 < payloadSize) {
throw new TypeError(`Packet missing payload: payload has length ${payloadSize}.`);
}
}
else if (suffix === 0x00009966)
{
// Get sequence number
sequenceN = buffer.readUInt32BE(6);

// Get command byte
commandByte = buffer.readUInt32BE(10);

// Get payload size
payloadSize = buffer.readUInt32BE(14) + 14; // Add additional bytes for extras

// Check for payload
if (buffer.length - 8 < payloadSize) {
throw new TypeError(`Packet missing payload: payload has length ${payloadSize}.`);
}
}

const packageFromDiscovery = (
Expand All @@ -158,34 +185,48 @@ class MessageParser {
// Get the payload
// Adjust for messages lacking a return code
let payload;
if (returnCode & 0xFFFFFF00) {
if (this.version === '3.4' && !packageFromDiscovery) {
payload = buffer.slice(HEADER_SIZE, HEADER_SIZE + payloadSize - 0x24);
if(this.version !== '3.5')
{
if (returnCode & 0xFFFFFF00) {
if (this.version === '3.4' && !packageFromDiscovery) {
payload = buffer.slice(HEADER_SIZE, HEADER_SIZE + payloadSize - 0x24);
}
else if (this.version === '3.5' && !packageFromDiscovery) {
payload = buffer.slice(HEADER_SIZE, HEADER_SIZE + payloadSize - 0x24);
} else {
payload = buffer.slice(HEADER_SIZE, HEADER_SIZE + payloadSize - 8);
}
} else if (this.version === '3.4' && !packageFromDiscovery) {
payload = buffer.slice(HEADER_SIZE + 4, HEADER_SIZE + payloadSize - 0x24);
} else if (this.version === '3.5' && !packageFromDiscovery) {
payload = buffer.slice(HEADER_SIZE + 4, HEADER_SIZE + payloadSize - 0x24);
} else {
payload = buffer.slice(HEADER_SIZE, HEADER_SIZE + payloadSize - 8);
}
} else if (this.version === '3.4' && !packageFromDiscovery) {
payload = buffer.slice(HEADER_SIZE + 4, HEADER_SIZE + payloadSize - 0x24);
} else {
payload = buffer.slice(HEADER_SIZE + 4, HEADER_SIZE + payloadSize - 8);
}

// Check CRC
if (this.version === '3.4' && !packageFromDiscovery) {
const expectedCrc = buffer.slice(HEADER_SIZE + payloadSize - 0x24, buffer.length - 4).toString('hex');
const computedCrc = this.cipher.hmac(buffer.slice(0, HEADER_SIZE + payloadSize - 0x24)).toString('hex');

if (expectedCrc !== computedCrc) {
throw new Error(`HMAC mismatch: expected ${expectedCrc}, was ${computedCrc}. ${buffer.toString('hex')}`);
payload = buffer.slice(HEADER_SIZE + 4, HEADER_SIZE + payloadSize - 8);
}
} else {
const expectedCrc = buffer.readInt32BE(HEADER_SIZE + payloadSize - 8);
const computedCrc = crc(buffer.slice(0, payloadSize + 8));

if (expectedCrc !== computedCrc) {
throw new Error(`CRC mismatch: expected ${expectedCrc}, was ${computedCrc}. ${buffer.toString('hex')}`);
// Check CRC
if (this.version === '3.4' && !packageFromDiscovery) {
const expectedCrc = buffer.slice(HEADER_SIZE + payloadSize - 0x24, buffer.length - 4).toString('hex');
const computedCrc = this.cipher.hmac(buffer.slice(0, HEADER_SIZE + payloadSize - 0x24)).toString('hex');

if (expectedCrc !== computedCrc) {
throw new Error(`HMAC mismatch: expected ${expectedCrc}, was ${computedCrc}. ${buffer.toString('hex')}`);
}
} else if (this.version !== '3.5') {
const expectedCrc = buffer.readInt32BE(HEADER_SIZE + payloadSize - 8);
const computedCrc = crc(buffer.slice(0, payloadSize + 8));

if (expectedCrc !== computedCrc) {
throw new Error(`CRC mismatch: expected ${expectedCrc}, was ${computedCrc}. ${buffer.toString('hex')}`);
}
}
}
else
{
payload = buffer.slice(HEADER_SIZE_3_5, HEADER_SIZE_3_5 + payloadSize);
sequenceN = buffer.slice(6, 10).readUInt32BE();
commandByte = buffer.slice(10, 14).readUInt32BE();
}

return {payload, leftover, commandByte, sequenceN};
}
Expand Down Expand Up @@ -213,6 +254,13 @@ class MessageParser {
data = data.toString('utf8');
}

// Incoming 3.5 data isn't 0 because of iv and tag so check size after
if(this.version === '3.5')
{
if(data.length === 0)
return false;
}

// Try to parse data as JSON.
// If error, return as string.
if (typeof data === 'string') {
Expand Down Expand Up @@ -288,6 +336,10 @@ class MessageParser {
return this._encode34(options);
}

else if (this.version === '3.5') {
return this._encode35(options);
}

return this._encodePre34(options);
}

Expand Down Expand Up @@ -421,6 +473,53 @@ class MessageParser {
buffer.writeUInt32BE(0x0000AA55, payload.length + 48);
return buffer;
}

/**
* Encodes a payload into a Tuya-protocol-complient packet for protocol version 3.5
* @param {Object} options Options for encoding
* @param {Buffer|String|Object} options.data data to encode
* @param {Boolean} options.encrypted whether or not to encrypt the data
* @param {Number} options.commandByte
* command byte of packet (use CommandType definitions)
* @param {Number} [options.sequenceN] optional, sequence number
* @returns {Buffer} Encoded Buffer
*/
_encode35(options) {
let payload = options.data;

if (options.commandByte !== CommandType.DP_QUERY &&
options.commandByte !== CommandType.HEART_BEAT &&
options.commandByte !== CommandType.DP_QUERY_NEW &&
options.commandByte !== CommandType.SESS_KEY_NEG_START &&
options.commandByte !== CommandType.SESS_KEY_NEG_FINISH &&
options.commandByte !== CommandType.DP_REFRESH) {
// Add 3.5 header
const buffer = Buffer.alloc(payload.length + 15);
Buffer.from('3.5').copy(buffer, 0);
payload.copy(buffer, 15);
payload = buffer;
options.data = '3.5\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + options.data;
}

// Allocate buffer for prefix, unknown, sequence, command, length
let buffer = Buffer.alloc(18);

// Add prefix, command, and length
buffer.writeUInt32BE(0x00006699, 0); // Prefix
buffer.writeUInt16BE(0x0, 4); // Unknown
buffer.writeUInt32BE(options.sequenceN, 6); // Sequence
buffer.writeUInt32BE(options.commandByte, 10); // Command
buffer.writeUInt32BE(payload.length + 0x1c, 14); // Length

const encrypted = this.cipher.encrypt({
data: payload,
aad: buffer.slice(4, 18)
});

buffer = Buffer.concat([buffer, encrypted]);

return buffer;
}
}

module.exports = {MessageParser, CommandType};

0 comments on commit 59883ed

Please sign in to comment.