homebridge-tuya-plus 3.13.0 → 3.13.1-dev.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/CODEOWNERS +5 -0
- package/.github/workflows/publish-dev.yml +91 -0
- package/Readme.MD +33 -0
- package/config.schema.json +231 -12
- package/index.js +21 -5
- package/lib/BaseAccessory.js +7 -0
- package/lib/DehumidifierAccessory.js +1 -1
- package/lib/IrrigationSystemAccessory.js +611 -0
- package/lib/OilDiffuserAccessory.js +1 -1
- package/lib/SimpleFanLightAccessory.js +54 -12
- package/lib/SimpleGarageDoorAccessory.js +178 -262
- package/lib/TuyaAccessory.js +95 -28
- package/lib/TuyaDiscovery.js +52 -19
- package/lib/WledDimmerAccessory.js +703 -0
- package/package.json +1 -1
- package/test/IrrigationSystemAccessory.test.js +446 -0
- package/test/SimpleFanLightAccessory.test.js +299 -0
- package/test/SimpleGarageDoorAccessory.test.js +258 -538
- package/test/TuyaAccessory.protocol.test.js +626 -0
- package/test/getCategory.test.js +65 -0
- package/test/support/mocks.js +30 -2
- package/wiki/Supported-Device-Types.md +167 -22
- package/wiki/User-documented-device-config.md +57 -0
package/lib/TuyaAccessory.js
CHANGED
|
@@ -23,16 +23,35 @@ class TuyaAccessory extends EventEmitter {
|
|
|
23
23
|
...props,
|
|
24
24
|
};
|
|
25
25
|
|
|
26
|
+
// The version can arrive as a string ('3.4', from broadcasts) or a number (3.4,
|
|
27
|
+
// from config.json). Keep a canonical string for protocol headers and a float for
|
|
28
|
+
// capability checks so devices reporting newer versions (e.g. a future 3.6) are
|
|
29
|
+
// handled by the newest stack instead of falling into a broken legacy path.
|
|
30
|
+
if (this.context.version === undefined || this.context.version === null || String(this.context.version).trim() === '') {
|
|
31
|
+
this.context.version = '3.5';
|
|
32
|
+
} else {
|
|
33
|
+
this.context.version = String(this.context.version).trim();
|
|
34
|
+
}
|
|
35
|
+
this._protocolVersion = parseFloat(this.context.version);
|
|
36
|
+
if (isNaN(this._protocolVersion)) {
|
|
37
|
+
if (this.log) this.log.warn(`Unrecognized protocol version "${this.context.version}" for ${this.context.name}; assuming 3.5.`);
|
|
38
|
+
this.context.version = '3.5';
|
|
39
|
+
this._protocolVersion = 3.5;
|
|
40
|
+
}
|
|
41
|
+
if (this._protocolVersion > 3.5 && this.log) {
|
|
42
|
+
this.log.info(`${this.context.name} reports protocol ${this.context.version}, newer than the newest known Tuya LAN protocol (3.5); using the 3.5 (GCM) stack with ${this.context.version} headers.`);
|
|
43
|
+
}
|
|
44
|
+
|
|
26
45
|
this.state = {};
|
|
27
46
|
this._cachedBuffer = Buffer.allocUnsafe(0);
|
|
28
47
|
|
|
29
48
|
this._msgQueue = async.queue(this[
|
|
30
|
-
this.
|
|
31
|
-
this.
|
|
32
|
-
this.
|
|
49
|
+
this._protocolVersion < 3.2 ? '_msgHandler_3_1' :
|
|
50
|
+
this._protocolVersion < 3.4 ? '_msgHandler_3_3' :
|
|
51
|
+
this._protocolVersion < 3.5 ? '_msgHandler_3_4' : '_msgHandler_3_5'
|
|
33
52
|
].bind(this), 1);
|
|
34
53
|
|
|
35
|
-
if (this.
|
|
54
|
+
if (this._protocolVersion >= 3.2) {
|
|
36
55
|
this.context.pingGap = Math.min(this.context.pingGap || 9, 9);
|
|
37
56
|
//this.log.info(`Changing ping gap for ${this.context.name} to ${this.context.pingGap}s`);
|
|
38
57
|
}
|
|
@@ -110,7 +129,7 @@ class TuyaAccessory extends EventEmitter {
|
|
|
110
129
|
};
|
|
111
130
|
|
|
112
131
|
this._socket.on('connect', () => {
|
|
113
|
-
if (this.
|
|
132
|
+
if (this._protocolVersion < 3.4) {
|
|
114
133
|
clearTimeout(this._socket._connTimeout);
|
|
115
134
|
|
|
116
135
|
this.connected = true;
|
|
@@ -130,7 +149,7 @@ class TuyaAccessory extends EventEmitter {
|
|
|
130
149
|
if (this.context.intro === false) return;
|
|
131
150
|
this.connected = true;
|
|
132
151
|
|
|
133
|
-
if (this.
|
|
152
|
+
if (this._protocolVersion >= 3.4) {
|
|
134
153
|
this._tmpLocalKey = crypto.randomBytes(16);
|
|
135
154
|
const payload = {
|
|
136
155
|
data: this._tmpLocalKey,
|
|
@@ -148,7 +167,7 @@ class TuyaAccessory extends EventEmitter {
|
|
|
148
167
|
|
|
149
168
|
do {
|
|
150
169
|
let startingIndex;
|
|
151
|
-
if (this.
|
|
170
|
+
if (this._protocolVersion >= 3.5) {
|
|
152
171
|
startingIndex = this._cachedBuffer.indexOf('00006699', 'hex');
|
|
153
172
|
} else {
|
|
154
173
|
startingIndex = this._cachedBuffer.indexOf('000055aa', 'hex');
|
|
@@ -160,7 +179,7 @@ class TuyaAccessory extends EventEmitter {
|
|
|
160
179
|
if (startingIndex !== 0) this._cachedBuffer = this._cachedBuffer.slice(startingIndex);
|
|
161
180
|
|
|
162
181
|
let endingIndex;
|
|
163
|
-
if (this.
|
|
182
|
+
if (this._protocolVersion >= 3.5) {
|
|
164
183
|
endingIndex = this._cachedBuffer.indexOf('00009966', 'hex');
|
|
165
184
|
if (endingIndex !== -1) endingIndex += 4;
|
|
166
185
|
} else {
|
|
@@ -207,9 +226,21 @@ class TuyaAccessory extends EventEmitter {
|
|
|
207
226
|
}
|
|
208
227
|
});
|
|
209
228
|
|
|
210
|
-
this._socket.on('close',
|
|
229
|
+
this._socket.on('close', () => {
|
|
211
230
|
this.connected = false;
|
|
212
231
|
this.session_key = null;
|
|
232
|
+
|
|
233
|
+
// A heartbeat timer must never outlive its socket. The pinger
|
|
234
|
+
// callbacks act on `this._socket`, so a stale timer left over from a
|
|
235
|
+
// dead socket would later fire against a freshly reconnected one and
|
|
236
|
+
// trip a spurious ERR_PING_TIMED_OUT. Clearing it here covers every
|
|
237
|
+
// teardown path (error-driven destroy as well as the graceful end
|
|
238
|
+
// below).
|
|
239
|
+
if (this._socket._pinger) {
|
|
240
|
+
clearTimeout(this._socket._pinger);
|
|
241
|
+
this._socket._pinger = null;
|
|
242
|
+
}
|
|
243
|
+
|
|
213
244
|
//this.log.info('Closed connection with', this.context.name);
|
|
214
245
|
});
|
|
215
246
|
|
|
@@ -217,6 +248,32 @@ class TuyaAccessory extends EventEmitter {
|
|
|
217
248
|
this.connected = false;
|
|
218
249
|
this.session_key = null;
|
|
219
250
|
this.log.info('Disconnected from', this.context.name);
|
|
251
|
+
|
|
252
|
+
// The device closed the connection on its own. This is routine for
|
|
253
|
+
// many Tuya devices (e.g. LED ceiling lights) that recycle their
|
|
254
|
+
// long-lived LAN sockets every few minutes. Previously nothing here
|
|
255
|
+
// tore the socket down or reconnected: the heartbeat timer kept
|
|
256
|
+
// running and, because `connected` is now false, its pings went
|
|
257
|
+
// nowhere until the full ping timeout elapsed and emitted a
|
|
258
|
+
// misleading "ERR_PING_TIMED_OUT" ~30s later - which was the only
|
|
259
|
+
// thing that ultimately triggered a reconnect. Tear down and
|
|
260
|
+
// reconnect promptly instead, so recovery is quick and the logs
|
|
261
|
+
// reflect what actually happened (a clean disconnect, not a ping
|
|
262
|
+
// failure).
|
|
263
|
+
if (this._socket._pinger) {
|
|
264
|
+
clearTimeout(this._socket._pinger);
|
|
265
|
+
this._socket._pinger = null;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Destroying first means a trailing RST can't resurface as an
|
|
269
|
+
// 'error' and schedule a second, competing reconnect.
|
|
270
|
+
this._socket.destroy();
|
|
271
|
+
|
|
272
|
+
if (!this._socket._errorReconnect) {
|
|
273
|
+
this._socket._errorReconnect = setTimeout(() => {
|
|
274
|
+
process.nextTick(this._connect.bind(this));
|
|
275
|
+
}, 5000);
|
|
276
|
+
}
|
|
220
277
|
});
|
|
221
278
|
}
|
|
222
279
|
|
|
@@ -336,7 +393,8 @@ class TuyaAccessory extends EventEmitter {
|
|
|
336
393
|
return callback();
|
|
337
394
|
}
|
|
338
395
|
|
|
339
|
-
let versionPos = task.msg.indexOf(
|
|
396
|
+
let versionPos = task.msg.indexOf(this.context.version);
|
|
397
|
+
if (versionPos === -1) versionPos = task.msg.indexOf('3.3');
|
|
340
398
|
if (versionPos === -1) versionPos = task.msg.indexOf('3.2');
|
|
341
399
|
const cleanMsg = task.msg.slice(versionPos === -1 ? len - size + ((task.msg.readUInt32BE(16) & 0xFFFFFF00) ? 0 : 4) : 15 + versionPos, len - 8);
|
|
342
400
|
|
|
@@ -410,7 +468,7 @@ class TuyaAccessory extends EventEmitter {
|
|
|
410
468
|
return callback();
|
|
411
469
|
}
|
|
412
470
|
|
|
413
|
-
let versionPos = task.msg.indexOf(
|
|
471
|
+
let versionPos = task.msg.indexOf(this.context.version);
|
|
414
472
|
const cleanMsg = task.msg.slice(versionPos === -1 ? len - size + ((task.msg.readUInt32BE(16) & 0xFFFFFF00) ? 0 : 4) : 15 + versionPos, len - 0x24);
|
|
415
473
|
|
|
416
474
|
const expectedCrc = task.msg.slice(len - 0x24, task.msg.length - 4).toString('hex');
|
|
@@ -430,7 +488,7 @@ class TuyaAccessory extends EventEmitter {
|
|
|
430
488
|
|
|
431
489
|
let parsedPayload;
|
|
432
490
|
try {
|
|
433
|
-
if (decryptedMsg
|
|
491
|
+
if (hasVersionHeader(decryptedMsg)) {
|
|
434
492
|
decryptedMsg = decryptedMsg.slice(15)
|
|
435
493
|
}
|
|
436
494
|
let res = JSON.parse(decryptedMsg)
|
|
@@ -508,7 +566,8 @@ class TuyaAccessory extends EventEmitter {
|
|
|
508
566
|
}
|
|
509
567
|
|
|
510
568
|
/*
|
|
511
|
-
* 3.5 Protocol message handler
|
|
569
|
+
* 3.5+ Protocol message handler (0x6699 frames, AES-128-GCM). Also used for any
|
|
570
|
+
* newer version a device may report (e.g. 3.6) until a format change is documented.
|
|
512
571
|
*/
|
|
513
572
|
_msgHandler_3_5(task, callback) {
|
|
514
573
|
if (!(task.msg instanceof Buffer)) return callback();
|
|
@@ -558,8 +617,8 @@ class TuyaAccessory extends EventEmitter {
|
|
|
558
617
|
}
|
|
559
618
|
}
|
|
560
619
|
|
|
561
|
-
// remove leading version header
|
|
562
|
-
if (payload
|
|
620
|
+
// remove leading version header ("3.x" + 12 bytes), whichever 3.x the device used
|
|
621
|
+
if (hasVersionHeader(payload)) payload = payload.slice(15);
|
|
563
622
|
|
|
564
623
|
// Attempt JSON parse
|
|
565
624
|
let parsedPayload;
|
|
@@ -651,7 +710,7 @@ class TuyaAccessory extends EventEmitter {
|
|
|
651
710
|
t,
|
|
652
711
|
dps
|
|
653
712
|
};
|
|
654
|
-
const data =
|
|
713
|
+
const data = this._protocolVersion >= 3.4
|
|
655
714
|
? {
|
|
656
715
|
data: {
|
|
657
716
|
...payload,
|
|
@@ -664,12 +723,12 @@ class TuyaAccessory extends EventEmitter {
|
|
|
664
723
|
: payload;
|
|
665
724
|
result = this._send({
|
|
666
725
|
data,
|
|
667
|
-
cmd:
|
|
726
|
+
cmd: this._protocolVersion >= 3.4 ? 13 : 7
|
|
668
727
|
});
|
|
669
728
|
if (result !== true) this.log.info(" Result", result);
|
|
670
729
|
if (this.context.sendEmptyUpdate) {
|
|
671
730
|
//this.log.info(" Sending", this.context.name, 'empty signature');
|
|
672
|
-
this._send({cmd:
|
|
731
|
+
this._send({cmd: this._protocolVersion >= 3.4 ? 13 : 7});
|
|
673
732
|
}
|
|
674
733
|
} else {
|
|
675
734
|
//this.log.info(`Sending first query to ${this.context.name} (${this.context.version})`);
|
|
@@ -678,7 +737,7 @@ class TuyaAccessory extends EventEmitter {
|
|
|
678
737
|
gwId: this.context.id,
|
|
679
738
|
devId: this.context.id
|
|
680
739
|
},
|
|
681
|
-
cmd:
|
|
740
|
+
cmd: this._protocolVersion >= 3.4 ? 16 : 10
|
|
682
741
|
});
|
|
683
742
|
}
|
|
684
743
|
|
|
@@ -705,9 +764,9 @@ class TuyaAccessory extends EventEmitter {
|
|
|
705
764
|
if (this.context.fake) return;
|
|
706
765
|
if (!this.connected) return false;
|
|
707
766
|
|
|
708
|
-
if (this.
|
|
709
|
-
if (this.
|
|
710
|
-
if (this.
|
|
767
|
+
if (this._protocolVersion < 3.2) return this._send_3_1(o);
|
|
768
|
+
if (this._protocolVersion < 3.4) return this._send_3_3(o);
|
|
769
|
+
if (this._protocolVersion < 3.5) return this._send_3_4(o);
|
|
711
770
|
return this._send_3_5(o);
|
|
712
771
|
}
|
|
713
772
|
|
|
@@ -759,9 +818,9 @@ class TuyaAccessory extends EventEmitter {
|
|
|
759
818
|
cmd.toString(16).padStart(8, '0'), //command
|
|
760
819
|
'00000000' //size
|
|
761
820
|
];
|
|
762
|
-
//version
|
|
821
|
+
//version ("3.x" + 12 null bytes, e.g. '332e33...' for 3.3)
|
|
763
822
|
if (cmd === 7 && !data) hex.push('00000000');
|
|
764
|
-
else if (cmd !== 9 && cmd !== 10) hex.push('
|
|
823
|
+
else if (cmd !== 9 && cmd !== 10) hex.push(Buffer.concat([Buffer.from(this.context.version), Buffer.alloc(12)]).toString('hex'));
|
|
765
824
|
//data
|
|
766
825
|
if (data) {
|
|
767
826
|
const cipher = crypto.createCipheriv('aes-128-ecb', this.context.key, '');
|
|
@@ -804,10 +863,10 @@ class TuyaAccessory extends EventEmitter {
|
|
|
804
863
|
cmd !== 3 &&
|
|
805
864
|
cmd !== 5 &&
|
|
806
865
|
cmd !== 18) {
|
|
807
|
-
// Add 3.4
|
|
866
|
+
// Add version header (the version the device reported, e.g. '3.4')
|
|
808
867
|
// check this: mqc_very_pcmcd_mcd(int a1, unsigned int a2)
|
|
809
868
|
const buffer = Buffer.alloc(data.length + 15);
|
|
810
|
-
Buffer.from(
|
|
869
|
+
Buffer.from(this.context.version).copy(buffer, 0);
|
|
811
870
|
data.copy(buffer, 15);
|
|
812
871
|
data = buffer;
|
|
813
872
|
}
|
|
@@ -843,7 +902,8 @@ class TuyaAccessory extends EventEmitter {
|
|
|
843
902
|
}
|
|
844
903
|
|
|
845
904
|
/*
|
|
846
|
-
* 3.5 Protocol send
|
|
905
|
+
* 3.5+ Protocol send (0x6699 frames, AES-128-GCM). Also used for any newer version
|
|
906
|
+
* a device may report (e.g. 3.6) until a format change is documented.
|
|
847
907
|
*/
|
|
848
908
|
_send_3_5(o) {
|
|
849
909
|
let {cmd, data} = {...o};
|
|
@@ -855,8 +915,9 @@ class TuyaAccessory extends EventEmitter {
|
|
|
855
915
|
}
|
|
856
916
|
|
|
857
917
|
if (cmd !== 10 && cmd !== 9 && cmd !== 16 && cmd !== 3 && cmd !== 5 && cmd !== 18) {
|
|
918
|
+
// Version header uses the version the device reported (3.5, or newer like 3.6)
|
|
858
919
|
const buffer = Buffer.alloc(data.length + 15);
|
|
859
|
-
Buffer.from(
|
|
920
|
+
Buffer.from(this.context.version).copy(buffer, 0);
|
|
860
921
|
data.copy(buffer, 15);
|
|
861
922
|
data = buffer;
|
|
862
923
|
}
|
|
@@ -934,6 +995,12 @@ const hmac = (data, hmacKey) => {
|
|
|
934
995
|
return crypto.createHmac('sha256', hmacKey).update(data, 'utf8').digest();
|
|
935
996
|
};
|
|
936
997
|
|
|
998
|
+
// Payloads of protocol 3.2+ may start with a 15-byte version header: "3.x" followed by
|
|
999
|
+
// 12 bytes. Match any "3.<digit>" so replies still parse when the device answers with a
|
|
1000
|
+
// different minor version than the one we connected with.
|
|
1001
|
+
const hasVersionHeader = buf =>
|
|
1002
|
+
buf.length >= 15 && buf[0] === 0x33 && buf[1] === 0x2e && buf[2] >= 0x30 && buf[2] <= 0x39;
|
|
1003
|
+
|
|
937
1004
|
const xorBuffers = (a, b) => {
|
|
938
1005
|
const len = Math.min(a.length, b.length);
|
|
939
1006
|
const out = Buffer.alloc(len);
|
package/lib/TuyaDiscovery.js
CHANGED
|
@@ -4,11 +4,12 @@ const EventEmitter = require('events');
|
|
|
4
4
|
|
|
5
5
|
const UDP_KEY = Buffer.from('6c1ec8e2bb9bb59ab50b0daf649b410a', 'hex');
|
|
6
6
|
|
|
7
|
-
const GCM_DISCOVERY_KEY = crypto.createHash('md5').update('yGAdlopoPVldABfn').digest(); // v3.5 UDP/GCM key
|
|
7
|
+
const GCM_DISCOVERY_KEY = crypto.createHash('md5').update('yGAdlopoPVldABfn').digest(); // v3.5+ UDP/GCM key
|
|
8
8
|
const PREFIX_55AA = 0x000055aa; // v3.1-3.4
|
|
9
|
-
const PREFIX_6699 = 0x00006699; // v3.5
|
|
9
|
+
const PREFIX_6699 = 0x00006699; // v3.5+
|
|
10
10
|
const SUFFIX_AA55 = 0x0000aa55;
|
|
11
11
|
const SUFFIX_9966 = 0x00009966;
|
|
12
|
+
const REQ_DEVINFO = 0x25; // v3.5+ broadcast probe command (FR_TYPE_BOARDCAST_LPV34/REQ_DEVINFO)
|
|
12
13
|
|
|
13
14
|
class TuyaDiscovery extends EventEmitter {
|
|
14
15
|
constructor() {
|
|
@@ -36,8 +37,15 @@ class TuyaDiscovery extends EventEmitter {
|
|
|
36
37
|
this._running = true;
|
|
37
38
|
this._start(6666);
|
|
38
39
|
this._start(6667);
|
|
39
|
-
this._start(7000); // v3.5 direct-reply port
|
|
40
|
-
|
|
40
|
+
this._start(7000); // v3.5+ direct-reply port
|
|
41
|
+
|
|
42
|
+
// v3.5+ devices stay silent until solicited. One probe usually suffices,
|
|
43
|
+
// but UDP is lossy and the device may have been mid-boot — repeat every
|
|
44
|
+
// 6s (matches tinytuya's BROADCASTTIME) so a single dropped packet
|
|
45
|
+
// doesn't burn the entire discovery window.
|
|
46
|
+
this._sendV35Probe();
|
|
47
|
+
if (this._v35ProbeTimer) clearInterval(this._v35ProbeTimer);
|
|
48
|
+
this._v35ProbeTimer = setInterval(() => this._sendV35Probe(), 6000);
|
|
41
49
|
|
|
42
50
|
return this;
|
|
43
51
|
}
|
|
@@ -48,6 +56,11 @@ class TuyaDiscovery extends EventEmitter {
|
|
|
48
56
|
this._stop(6667);
|
|
49
57
|
this._stop(7000);
|
|
50
58
|
|
|
59
|
+
if (this._v35ProbeTimer) {
|
|
60
|
+
clearInterval(this._v35ProbeTimer);
|
|
61
|
+
this._v35ProbeTimer = null;
|
|
62
|
+
}
|
|
63
|
+
|
|
51
64
|
return this;
|
|
52
65
|
}
|
|
53
66
|
|
|
@@ -114,7 +127,9 @@ class TuyaDiscovery extends EventEmitter {
|
|
|
114
127
|
const suffix = msg.readUInt32BE(len - 4);
|
|
115
128
|
|
|
116
129
|
/* 3.1-3.4 packets: 0x55AA … 0xAA55
|
|
117
|
-
3.5
|
|
130
|
+
3.5+ packets: 0x6699 … 0x9966
|
|
131
|
+
The 6699 payload carries the device's "version" field (3.5, or newer like
|
|
132
|
+
3.6), which flows into TuyaAccessory and selects the protocol stack. */
|
|
118
133
|
const isV34Frame = prefix === 0x000055aa && suffix === 0x0000aa55;
|
|
119
134
|
const isV35Frame = prefix === 0x00006699 && suffix === 0x00009966;
|
|
120
135
|
|
|
@@ -243,25 +258,43 @@ class TuyaDiscovery extends EventEmitter {
|
|
|
243
258
|
}
|
|
244
259
|
}
|
|
245
260
|
|
|
261
|
+
// Broadcast a v3.5+ REQ_DEVINFO probe to UDP/7000. v3.5+ devices do not
|
|
262
|
+
// emit periodic plaintext broadcasts like 3.1-3.3, so they are invisible
|
|
263
|
+
// to passive listeners until something solicits them. Each correctly-formed
|
|
264
|
+
// probe causes the device to unicast its discovery JSON back to us, which
|
|
265
|
+
// _handleV35 then decodes.
|
|
266
|
+
//
|
|
267
|
+
// Frame layout (big-endian):
|
|
268
|
+
// prefix(4) + reserved(2) + seq(4) + cmd(4) + length(4) [14B header after prefix]
|
|
269
|
+
// + iv(12) + ciphertext(payloadLen) + tag(16) + suffix(4)
|
|
270
|
+
//
|
|
271
|
+
// AAD for GCM must be the 14 header bytes after the prefix exactly as
|
|
272
|
+
// they appear on the wire. Anything else and the receiver's auth tag
|
|
273
|
+
// verification fails and the packet is silently dropped.
|
|
246
274
|
_sendV35Probe() {
|
|
247
|
-
const socket = dgram.createSocket('udp4');
|
|
248
275
|
const payload = Buffer.from('{"from":"app","ip":"255.255.255.255"}');
|
|
249
|
-
|
|
276
|
+
|
|
277
|
+
const prefix = Buffer.from('00006699', 'hex');
|
|
278
|
+
const suffix = Buffer.from('00009966', 'hex');
|
|
279
|
+
const reserved = Buffer.alloc(2); // 0x0000
|
|
280
|
+
const seq = Buffer.alloc(4); // 0
|
|
281
|
+
const cmd = Buffer.alloc(4); cmd.writeUInt32BE(REQ_DEVINFO, 0);
|
|
282
|
+
const iv = crypto.randomBytes(12);
|
|
283
|
+
const len = Buffer.alloc(4); len.writeUInt32BE(iv.length + payload.length + 16, 0);
|
|
284
|
+
const aad = Buffer.concat([reserved, seq, cmd, len]);
|
|
285
|
+
|
|
250
286
|
const cipher = crypto.createCipheriv('aes-128-gcm', GCM_DISCOVERY_KEY, iv);
|
|
251
|
-
cipher.setAAD(
|
|
287
|
+
cipher.setAAD(aad);
|
|
252
288
|
const enc = Buffer.concat([cipher.update(payload), cipher.final()]);
|
|
253
289
|
const tag = cipher.getAuthTag();
|
|
254
|
-
|
|
255
|
-
const frame = Buffer.concat([
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
Buffer.from('00009966','hex')
|
|
263
|
-
]);
|
|
264
|
-
socket.send(frame, 7000, '255.255.255.255', () => socket.close());
|
|
290
|
+
|
|
291
|
+
const frame = Buffer.concat([prefix, reserved, seq, cmd, len, iv, enc, tag, suffix]);
|
|
292
|
+
|
|
293
|
+
const socket = dgram.createSocket('udp4');
|
|
294
|
+
socket.bind(0, () => {
|
|
295
|
+
socket.setBroadcast(true);
|
|
296
|
+
socket.send(frame, 7000, '255.255.255.255', () => socket.close());
|
|
297
|
+
});
|
|
265
298
|
}
|
|
266
299
|
}
|
|
267
300
|
|