From e5da82236bfc74353cda3d0ba04b268623d0fd94 Mon Sep 17 00:00:00 2001 From: Ingo Fischer Date: Mon, 27 Jan 2025 13:29:53 +0100 Subject: [PATCH 1/9] Improve 3.5 version detection by using the suffix which is 3.5 only currently --- lib/message-parser.js | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/lib/message-parser.js b/lib/message-parser.js index 371df00..a3f54d0 100644 --- a/lib/message-parser.js +++ b/lib/message-parser.js @@ -138,6 +138,7 @@ class MessageParser { let sequenceN; let commandByte; let payloadSize; + let overwriteVersion = undefined; if (suffix === 0x0000AA55) { // Get sequence number @@ -154,6 +155,9 @@ class MessageParser { throw new TypeError(`Packet missing payload: payload has length ${payloadSize}.`); } } else if (suffix === 0x00009966) { + // When this suffix comes in we should have 3.5 version + overwriteVersion = '3.5'; + // Get sequence number sequenceN = buffer.readUInt32BE(6); @@ -167,6 +171,8 @@ class MessageParser { if (buffer.length - 8 < payloadSize) { throw new TypeError(`Packet missing payload: payload has length ${payloadSize}.`); } + } else { + throw new TypeError(`Suffix does not match: ${buffer.toString('hex')}`); // Should never happen } const packageFromDiscovery = ( @@ -183,7 +189,7 @@ class MessageParser { // Get the payload // Adjust for messages lacking a return code let payload; - if (this.version === '3.5') { + if (overwriteVersion === "3.5" || this.version === '3.5') { payload = buffer.slice(HEADER_SIZE_3_5, HEADER_SIZE_3_5 + payloadSize); sequenceN = buffer.slice(6, 10).readUInt32BE(); commandByte = buffer.slice(10, 14).readUInt32BE(); @@ -222,17 +228,18 @@ class MessageParser { } } - return {payload, leftover, commandByte, sequenceN}; + return {payload, leftover, commandByte, sequenceN, version: overwriteVersion || this.version}; } /** * Attempts to decode a given payload into * an object or string. * @param {Buffer} data to decode + * @param {String} version of protocol * @returns {Object|String} * object if payload is JSON, otherwise string */ - getPayload(data) { + getPayload(data, version) { if (data.length === 0) { return false; } @@ -249,7 +256,7 @@ class MessageParser { } // Incoming 3.5 data isn't 0 because of iv and tag so check size after - if (this.version === '3.5') { + if (version === '3.5') { if (data.length === 0) { return false; } @@ -279,7 +286,7 @@ class MessageParser { parseRecursive(buffer, packets) { const result = this.parsePacket(buffer); - result.payload = this.getPayload(result.payload); + result.payload = this.getPayload(result.payload, result.version); packets.push(result); From d5a443524c025130735f34c36665f0d4c2e39819 Mon Sep 17 00:00:00 2001 From: Ingo Fischer Date: Mon, 27 Jan 2025 13:30:37 +0100 Subject: [PATCH 2/9] Fix potential crashes on timeouts or "no responses" --- index.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/index.js b/index.js index 2c12c9b..e277eea 100644 --- a/index.js +++ b/index.js @@ -131,7 +131,7 @@ class TuyaDevice extends EventEmitter { * @example * // get all available data from device * tuya.get({schema: true}).then(data => console.log(data)) - * @returns {Promise} + * @returns {Promise} * returns boolean if single property is requested, otherwise returns object of results */ async get(options = {}) { @@ -194,7 +194,7 @@ class TuyaDevice extends EventEmitter { } // Return first property by default - return data.dps['1']; + return data.dps ? data.dps['1'] : undefined; } /** @@ -525,8 +525,10 @@ class TuyaDevice extends EventEmitter { // Send ping this.client.write(buffer); if (this.globalOptions.issueRefreshOnPing) { - this.refresh(); - this.get(); + this.refresh().then(() => this.get()).catch(error => { + debug('Error refreshing/getting on ping: ' + error); + this.emit('error', error); + }); } } From 281fae0f58b71bc2d4242cc93480a2a280c9c1ef Mon Sep 17 00:00:00 2001 From: Ingo Fischer Date: Mon, 27 Jan 2025 13:31:06 +0100 Subject: [PATCH 3/9] WHen we can not parse the packet exit --- index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/index.js b/index.js index e277eea..b493b34 100644 --- a/index.js +++ b/index.js @@ -1019,6 +1019,7 @@ class TuyaDevice extends EventEmitter { } catch (error) { debug(error); reject(error); + return; } debug('UDP data:'); From 2903c61549eedfec29748779812a473719679e0a Mon Sep 17 00:00:00 2001 From: Ingo Fischer Date: Mon, 27 Jan 2025 13:33:27 +0100 Subject: [PATCH 4/9] Make linter happy --- lib/message-parser.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/message-parser.js b/lib/message-parser.js index a3f54d0..ea79c94 100644 --- a/lib/message-parser.js +++ b/lib/message-parser.js @@ -138,7 +138,7 @@ class MessageParser { let sequenceN; let commandByte; let payloadSize; - let overwriteVersion = undefined; + let overwriteVersion; if (suffix === 0x0000AA55) { // Get sequence number @@ -172,7 +172,7 @@ class MessageParser { throw new TypeError(`Packet missing payload: payload has length ${payloadSize}.`); } } else { - throw new TypeError(`Suffix does not match: ${buffer.toString('hex')}`); // Should never happen + throw new TypeError(`Suffix does not match: ${buffer.toString('hex')}`); // Should never happen } const packageFromDiscovery = ( @@ -189,7 +189,7 @@ class MessageParser { // Get the payload // Adjust for messages lacking a return code let payload; - if (overwriteVersion === "3.5" || this.version === '3.5') { + if (overwriteVersion === '3.5' || this.version === '3.5') { payload = buffer.slice(HEADER_SIZE_3_5, HEADER_SIZE_3_5 + payloadSize); sequenceN = buffer.slice(6, 10).readUInt32BE(); commandByte = buffer.slice(10, 14).readUInt32BE(); From 42093592093af811a6edf68a0ce78f7865f9bfaf Mon Sep 17 00:00:00 2001 From: Ingo Fischer Date: Mon, 27 Jan 2025 13:33:36 +0100 Subject: [PATCH 5/9] update some deps --- package-lock.json | 4 ++-- package.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2f8de0c..91a3199 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "7.7.0", "license": "MIT", "dependencies": { - "debug": "^4.3.7", + "debug": "^4.4.0", "p-queue": "6.6.2", "p-retry": "4.6.2", "p-timeout": "3.2.0" @@ -20,7 +20,7 @@ "clone": "2.1.2", "coveralls": "3.1.1", "delay": "4.4.1", - "documentation": "^14.0.0", + "documentation": "^14.0.3", "nyc": "15.1.0", "xo": "0.25.4" } diff --git a/package.json b/package.json index e8f7312..81195a4 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ }, "homepage": "https://github.com/codetheweb/tuyapi#readme", "dependencies": { - "debug": "^4.3.7", + "debug": "^4.4.0", "p-queue": "6.6.2", "p-retry": "4.6.2", "p-timeout": "3.2.0" @@ -49,7 +49,7 @@ "clone": "2.1.2", "coveralls": "3.1.1", "delay": "4.4.1", - "documentation": "^14.0.0", + "documentation": "^14.0.3", "nyc": "15.1.0", "xo": "0.25.4" }, From a8212a85d114af2814940586fce0361d47f10429 Mon Sep 17 00:00:00 2001 From: Ingo Fischer Date: Tue, 28 Jan 2025 10:44:24 +0100 Subject: [PATCH 6/9] ignore not-decryptable payloads in find --- index.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/index.js b/index.js index b493b34..ff91ad3 100644 --- a/index.js +++ b/index.js @@ -1025,6 +1025,11 @@ class TuyaDevice extends EventEmitter { debug('UDP data:'); debug(dataRes); + if (typeof dataRes.payload === 'string') { + debug('Received string payload. Ignoring.'); + return; + } + const thisID = dataRes.payload.gwId; const thisIP = dataRes.payload.ip; From 87947311d68d01ac566e28cbdaf876e60524bd21 Mon Sep 17 00:00:00 2001 From: Ingo Fischer Date: Wed, 29 Jan 2025 11:52:24 +0100 Subject: [PATCH 7/9] use detected version when decrypting --- lib/cipher.js | 18 ++++++++++-------- lib/message-parser.js | 2 +- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/lib/cipher.js b/lib/cipher.js index ddac1d0..7a4ebbc 100644 --- a/lib/cipher.js +++ b/lib/cipher.js @@ -112,17 +112,19 @@ class TuyaCipher { } /** - * Decrypts data. - * @param {String|Buffer} data to decrypt - * @returns {Object|String} - * returns object if data is JSON, else returns string - */ - decrypt(data) { - if (this.version === '3.4') { + * Decrypts data. + * @param {String|Buffer} data to decrypt + * @param {String} [version] protocol version + * @returns {Object|String} + * returns object if data is JSON, else returns string + */ + decrypt(data, version) { + version = version || this.version; + if (version === '3.4') { return this._decrypt34(data); } - if (this.version === '3.5') { + if (version === '3.5') { return this._decrypt35(data); } diff --git a/lib/message-parser.js b/lib/message-parser.js index ea79c94..37a74cf 100644 --- a/lib/message-parser.js +++ b/lib/message-parser.js @@ -250,7 +250,7 @@ class MessageParser { throw new Error('Missing key or version in constructor.'); } - data = this.cipher.decrypt(data); + data = this.cipher.decrypt(data, version); } catch (_) { data = data.toString('utf8'); } From 379e253562bb3e4bc1d907298339e3cdcd6cc456 Mon Sep 17 00:00:00 2001 From: Ingo Fischer Date: Tue, 13 May 2025 11:56:06 +0200 Subject: [PATCH 8/9] do a second try decrypt on broadcast if key is set --- index.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/index.js b/index.js index ff91ad3..5618d73 100644 --- a/index.js +++ b/index.js @@ -1018,8 +1018,15 @@ class TuyaDevice extends EventEmitter { dataRes = parser.parse(message)[0]; } catch (error) { debug(error); - reject(error); - return; + + const devParser = new MessageParser({key: this.device.key, version: this.device.version}); + try { + dataRes = devParser.parse(message)[0]; + } catch (devError) { + debug(devError); + reject(error); + return; + } } debug('UDP data:'); @@ -1056,7 +1063,7 @@ class TuyaDevice extends EventEmitter { this.device.id = dataRes.payload.gwId; this.device.gwID = dataRes.payload.gwId; - // Change product key if neccessary + // Change product key if necessary this.device.productKey = dataRes.payload.productKey; // Change protocol version if necessary From 23fc54d34004f36911f3ef55f077bc056800eff1 Mon Sep 17 00:00:00 2001 From: Ingo Fischer Date: Tue, 13 May 2025 11:57:45 +0200 Subject: [PATCH 9/9] also test Node.js 24 --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index dda6d7e..95eb8dc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node-version: [12.x, 14.x, 16.x, 18.x, 20.x, 22.x] + node-version: [12.x, 14.x, 16.x, 18.x, 20.x, 22.x, 24.x] steps: - uses: actions/checkout@v4