Skip to content

Commit 259e0b5

Browse files
authored
Merge pull request #690 from codetheweb/optimizations2701
Optimize 3.5 detection and prevent crashes
2 parents ffa64c3 + 23fc54d commit 259e0b5

File tree

6 files changed

+49
-25
lines changed

6 files changed

+49
-25
lines changed

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ jobs:
1111
runs-on: ubuntu-latest
1212
strategy:
1313
matrix:
14-
node-version: [12.x, 14.x, 16.x, 18.x, 20.x, 22.x]
14+
node-version: [12.x, 14.x, 16.x, 18.x, 20.x, 22.x, 24.x]
1515

1616
steps:
1717
- uses: actions/checkout@v4

index.js

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ class TuyaDevice extends EventEmitter {
131131
* @example
132132
* // get all available data from device
133133
* tuya.get({schema: true}).then(data => console.log(data))
134-
* @returns {Promise<Boolean|Object>}
134+
* @returns {Promise<Boolean|undefined|Object>}
135135
* returns boolean if single property is requested, otherwise returns object of results
136136
*/
137137
async get(options = {}) {
@@ -194,7 +194,7 @@ class TuyaDevice extends EventEmitter {
194194
}
195195

196196
// Return first property by default
197-
return data.dps['1'];
197+
return data.dps ? data.dps['1'] : undefined;
198198
}
199199

200200
/**
@@ -525,8 +525,10 @@ class TuyaDevice extends EventEmitter {
525525
// Send ping
526526
this.client.write(buffer);
527527
if (this.globalOptions.issueRefreshOnPing) {
528-
this.refresh();
529-
this.get();
528+
this.refresh().then(() => this.get()).catch(error => {
529+
debug('Error refreshing/getting on ping: ' + error);
530+
this.emit('error', error);
531+
});
530532
}
531533
}
532534

@@ -1016,12 +1018,25 @@ class TuyaDevice extends EventEmitter {
10161018
dataRes = parser.parse(message)[0];
10171019
} catch (error) {
10181020
debug(error);
1019-
reject(error);
1021+
1022+
const devParser = new MessageParser({key: this.device.key, version: this.device.version});
1023+
try {
1024+
dataRes = devParser.parse(message)[0];
1025+
} catch (devError) {
1026+
debug(devError);
1027+
reject(error);
1028+
return;
1029+
}
10201030
}
10211031

10221032
debug('UDP data:');
10231033
debug(dataRes);
10241034

1035+
if (typeof dataRes.payload === 'string') {
1036+
debug('Received string payload. Ignoring.');
1037+
return;
1038+
}
1039+
10251040
const thisID = dataRes.payload.gwId;
10261041
const thisIP = dataRes.payload.ip;
10271042

@@ -1048,7 +1063,7 @@ class TuyaDevice extends EventEmitter {
10481063
this.device.id = dataRes.payload.gwId;
10491064
this.device.gwID = dataRes.payload.gwId;
10501065

1051-
// Change product key if neccessary
1066+
// Change product key if necessary
10521067
this.device.productKey = dataRes.payload.productKey;
10531068

10541069
// Change protocol version if necessary

lib/cipher.js

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -112,17 +112,19 @@ class TuyaCipher {
112112
}
113113

114114
/**
115-
* Decrypts data.
116-
* @param {String|Buffer} data to decrypt
117-
* @returns {Object|String}
118-
* returns object if data is JSON, else returns string
119-
*/
120-
decrypt(data) {
121-
if (this.version === '3.4') {
115+
* Decrypts data.
116+
* @param {String|Buffer} data to decrypt
117+
* @param {String} [version] protocol version
118+
* @returns {Object|String}
119+
* returns object if data is JSON, else returns string
120+
*/
121+
decrypt(data, version) {
122+
version = version || this.version;
123+
if (version === '3.4') {
122124
return this._decrypt34(data);
123125
}
124126

125-
if (this.version === '3.5') {
127+
if (version === '3.5') {
126128
return this._decrypt35(data);
127129
}
128130

lib/message-parser.js

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ class MessageParser {
138138
let sequenceN;
139139
let commandByte;
140140
let payloadSize;
141+
let overwriteVersion;
141142

142143
if (suffix === 0x0000AA55) {
143144
// Get sequence number
@@ -154,6 +155,9 @@ class MessageParser {
154155
throw new TypeError(`Packet missing payload: payload has length ${payloadSize}.`);
155156
}
156157
} else if (suffix === 0x00009966) {
158+
// When this suffix comes in we should have 3.5 version
159+
overwriteVersion = '3.5';
160+
157161
// Get sequence number
158162
sequenceN = buffer.readUInt32BE(6);
159163

@@ -167,6 +171,8 @@ class MessageParser {
167171
if (buffer.length - 8 < payloadSize) {
168172
throw new TypeError(`Packet missing payload: payload has length ${payloadSize}.`);
169173
}
174+
} else {
175+
throw new TypeError(`Suffix does not match: ${buffer.toString('hex')}`); // Should never happen
170176
}
171177

172178
const packageFromDiscovery = (
@@ -183,7 +189,7 @@ class MessageParser {
183189
// Get the payload
184190
// Adjust for messages lacking a return code
185191
let payload;
186-
if (this.version === '3.5') {
192+
if (overwriteVersion === '3.5' || this.version === '3.5') {
187193
payload = buffer.slice(HEADER_SIZE_3_5, HEADER_SIZE_3_5 + payloadSize);
188194
sequenceN = buffer.slice(6, 10).readUInt32BE();
189195
commandByte = buffer.slice(10, 14).readUInt32BE();
@@ -222,17 +228,18 @@ class MessageParser {
222228
}
223229
}
224230

225-
return {payload, leftover, commandByte, sequenceN};
231+
return {payload, leftover, commandByte, sequenceN, version: overwriteVersion || this.version};
226232
}
227233

228234
/**
229235
* Attempts to decode a given payload into
230236
* an object or string.
231237
* @param {Buffer} data to decode
238+
* @param {String} version of protocol
232239
* @returns {Object|String}
233240
* object if payload is JSON, otherwise string
234241
*/
235-
getPayload(data) {
242+
getPayload(data, version) {
236243
if (data.length === 0) {
237244
return false;
238245
}
@@ -243,13 +250,13 @@ class MessageParser {
243250
throw new Error('Missing key or version in constructor.');
244251
}
245252

246-
data = this.cipher.decrypt(data);
253+
data = this.cipher.decrypt(data, version);
247254
} catch (_) {
248255
data = data.toString('utf8');
249256
}
250257

251258
// Incoming 3.5 data isn't 0 because of iv and tag so check size after
252-
if (this.version === '3.5') {
259+
if (version === '3.5') {
253260
if (data.length === 0) {
254261
return false;
255262
}
@@ -279,7 +286,7 @@ class MessageParser {
279286
parseRecursive(buffer, packets) {
280287
const result = this.parsePacket(buffer);
281288

282-
result.payload = this.getPayload(result.payload);
289+
result.payload = this.getPayload(result.payload, result.version);
283290

284291
packets.push(result);
285292

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
},
3939
"homepage": "https://github.com/codetheweb/tuyapi#readme",
4040
"dependencies": {
41-
"debug": "^4.3.7",
41+
"debug": "^4.4.0",
4242
"p-queue": "6.6.2",
4343
"p-retry": "4.6.2",
4444
"p-timeout": "3.2.0"
@@ -49,7 +49,7 @@
4949
"clone": "2.1.2",
5050
"coveralls": "3.1.1",
5151
"delay": "4.4.1",
52-
"documentation": "^14.0.0",
52+
"documentation": "^14.0.3",
5353
"nyc": "15.1.0",
5454
"xo": "0.25.4"
5555
},

0 commit comments

Comments
 (0)