lox-airplay-sender 0.1.0

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.
Files changed (75) hide show
  1. package/README.md +85 -0
  2. package/dist/core/ap2_test.d.ts +1 -0
  3. package/dist/core/ap2_test.js +8 -0
  4. package/dist/core/atv.d.ts +16 -0
  5. package/dist/core/atv.js +215 -0
  6. package/dist/core/atvAuthenticator.d.ts +30 -0
  7. package/dist/core/atvAuthenticator.js +134 -0
  8. package/dist/core/audioOut.d.ts +30 -0
  9. package/dist/core/audioOut.js +80 -0
  10. package/dist/core/deviceAirtunes.d.ts +72 -0
  11. package/dist/core/deviceAirtunes.js +501 -0
  12. package/dist/core/devices.d.ts +50 -0
  13. package/dist/core/devices.js +209 -0
  14. package/dist/core/index.d.ts +47 -0
  15. package/dist/core/index.js +97 -0
  16. package/dist/core/rtsp.d.ts +12 -0
  17. package/dist/core/rtsp.js +1590 -0
  18. package/dist/core/srp.d.ts +14 -0
  19. package/dist/core/srp.js +128 -0
  20. package/dist/core/udpServers.d.ts +26 -0
  21. package/dist/core/udpServers.js +149 -0
  22. package/dist/esm/core/ap2_test.js +8 -0
  23. package/dist/esm/core/atv.js +215 -0
  24. package/dist/esm/core/atvAuthenticator.js +134 -0
  25. package/dist/esm/core/audioOut.js +80 -0
  26. package/dist/esm/core/deviceAirtunes.js +501 -0
  27. package/dist/esm/core/devices.js +209 -0
  28. package/dist/esm/core/index.js +97 -0
  29. package/dist/esm/core/rtsp.js +1590 -0
  30. package/dist/esm/core/srp.js +128 -0
  31. package/dist/esm/core/udpServers.js +149 -0
  32. package/dist/esm/homekit/credentials.js +100 -0
  33. package/dist/esm/homekit/encryption.js +82 -0
  34. package/dist/esm/homekit/number.js +47 -0
  35. package/dist/esm/homekit/tlv.js +97 -0
  36. package/dist/esm/index.js +265 -0
  37. package/dist/esm/package.json +1 -0
  38. package/dist/esm/utils/alac.js +62 -0
  39. package/dist/esm/utils/alacEncoder.js +34 -0
  40. package/dist/esm/utils/circularBuffer.js +124 -0
  41. package/dist/esm/utils/config.js +28 -0
  42. package/dist/esm/utils/http.js +148 -0
  43. package/dist/esm/utils/ntp.js +27 -0
  44. package/dist/esm/utils/numUtil.js +17 -0
  45. package/dist/esm/utils/packetPool.js +52 -0
  46. package/dist/esm/utils/util.js +9 -0
  47. package/dist/homekit/credentials.d.ts +30 -0
  48. package/dist/homekit/credentials.js +100 -0
  49. package/dist/homekit/encryption.d.ts +12 -0
  50. package/dist/homekit/encryption.js +82 -0
  51. package/dist/homekit/number.d.ts +7 -0
  52. package/dist/homekit/number.js +47 -0
  53. package/dist/homekit/tlv.d.ts +25 -0
  54. package/dist/homekit/tlv.js +97 -0
  55. package/dist/index.d.ts +109 -0
  56. package/dist/index.js +265 -0
  57. package/dist/utils/alac.d.ts +9 -0
  58. package/dist/utils/alac.js +62 -0
  59. package/dist/utils/alacEncoder.d.ts +14 -0
  60. package/dist/utils/alacEncoder.js +34 -0
  61. package/dist/utils/circularBuffer.d.ts +31 -0
  62. package/dist/utils/circularBuffer.js +124 -0
  63. package/dist/utils/config.d.ts +25 -0
  64. package/dist/utils/config.js +28 -0
  65. package/dist/utils/http.d.ts +19 -0
  66. package/dist/utils/http.js +148 -0
  67. package/dist/utils/ntp.d.ts +7 -0
  68. package/dist/utils/ntp.js +27 -0
  69. package/dist/utils/numUtil.d.ts +5 -0
  70. package/dist/utils/numUtil.js +17 -0
  71. package/dist/utils/packetPool.d.ts +25 -0
  72. package/dist/utils/packetPool.js +52 -0
  73. package/dist/utils/util.d.ts +2 -0
  74. package/dist/utils/util.js +9 -0
  75. package/package.json +62 -0
@@ -0,0 +1,1590 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Client = Client;
4
+ var net = require('net'), nodeCrypto = require('crypto'), events = require('events'), util = require('util'), fs = require('fs'), dgram = require('dgram');
5
+ const ntp = require('../utils/ntp').default ?? require('../utils/ntp');
6
+ const config = require('../utils/config').default ?? require('../utils/config');
7
+ const nu = require('../utils/numUtil');
8
+ const bplistCreator = require('bplist-creator');
9
+ const bplistParser = require('bplist-parser');
10
+ const LegacySRP = require('./srp').default ?? require('./srp');
11
+ const { SRP, SRPClient, SrpClient } = require('fast-srp-hap');
12
+ const ATVAuthenticator = require('./atvAuthenticator').default ?? require('./atvAuthenticator');
13
+ const tlv = require('../homekit/tlv').default;
14
+ const enc = require('../homekit/encryption').default;
15
+ const Credentials = require('../homekit/credentials').Credentials;
16
+ const { method, attempt } = require('lodash');
17
+ const { hexString2ArrayBuffer } = require('../utils/util');
18
+ const ed25519_js = require('@noble/ed25519');
19
+ const curve25519_js = require('curve25519-js');
20
+ const varint = require('varint');
21
+ const struct = require('python-struct');
22
+ const { default: number } = require('../homekit/number');
23
+ var INFO = -1, OPTIONS = 0, ANNOUNCE = 1, SETUP = 2, RECORD = 3, SETVOLUME = 4, PLAYING = 5, TEARDOWN = 6, CLOSED = 7, SETDAAP = 8, SETART = 9, PAIR_VERIFY_1 = 10, PAIR_VERIFY_2 = 11, OPTIONS2 = 12, AUTH_SETUP = 13, PAIR_PIN_START = 14, PAIR_PIN_SETUP_1 = 15, PAIR_PIN_SETUP_2 = 16, PAIR_PIN_SETUP_3 = 17, PAIR_SETUP_1 = 18, PAIR_SETUP_2 = 19, PAIR_SETUP_3 = 20, PAIR_VERIFY_HAP_1 = 21, PAIR_VERIFY_HAP_2 = 22, SETUP_AP2_1 = 23, SETUP_AP2_2 = 24, SETPEERS = 25, FLUSH = 26, GETVOLUME = 27, SETPROGRESS = 28, OPTIONS3 = 29;
24
+ var rtsp_methods = ["INFO",
25
+ "OPTIONS",
26
+ "ANNOUNCE",
27
+ "SETUP",
28
+ "RECORD",
29
+ "SETVOLUME",
30
+ "PLAYING",
31
+ "TEARDOWN",
32
+ "CLOSED",
33
+ "SETDAAP",
34
+ "SETART",
35
+ "PAIR_VERIFY_1",
36
+ "PAIR_VERIFY_2",
37
+ "OPTIONS2",
38
+ "AUTH_SETUP",
39
+ "PAIR_PIN_START",
40
+ "PAIR_PIN_SETUP_1",
41
+ "PAIR_PIN_SETUP_2",
42
+ "PAIR_PIN_SETUP_3",
43
+ "PAIR_SETUP_1",
44
+ "PAIR_SETUP_2",
45
+ "PAIR_SETUP_3",
46
+ "PAIR_VERIFY_HAP_1",
47
+ "PAIR_VERIFY_HAP_2",
48
+ "SETUP_AP2_1",
49
+ "SETUP_AP2_2",
50
+ "SETPEERS",
51
+ "FLUSH",
52
+ "GETVOLUME",
53
+ "SETPROGRESS",
54
+ "OPTIONS3"
55
+ ];
56
+ function formatLogArg(value) {
57
+ if (Buffer.isBuffer(value)) {
58
+ return `<buffer len=${value.length}>`;
59
+ }
60
+ if (value instanceof Uint8Array) {
61
+ return `<uint8 len=${value.length}>`;
62
+ }
63
+ if (typeof value === 'string') {
64
+ return value;
65
+ }
66
+ try {
67
+ return JSON.stringify(value);
68
+ }
69
+ catch {
70
+ return String(value);
71
+ }
72
+ }
73
+ function logLine(ctx, ...args) {
74
+ if (!args.length) {
75
+ return;
76
+ }
77
+ if (ctx?.log) {
78
+ const formatted = args.map(formatLogArg).join(' ');
79
+ ctx.log('debug', formatted);
80
+ return;
81
+ }
82
+ // eslint-disable-next-line no-console
83
+ console.log(...args);
84
+ }
85
+ function Client(volume, password, audioOut, options) {
86
+ events.EventEmitter.call(this);
87
+ this.audioOut = audioOut;
88
+ this.status = PAIR_VERIFY_1;
89
+ this.socket = null;
90
+ this.cseq = 0;
91
+ this.announceId = null;
92
+ this.activeRemote = nu.randomInt(9).toString().toUpperCase();
93
+ this.dacpId = "04F8191D99BEC6E9";
94
+ this.session = null;
95
+ this.timeout = null;
96
+ this.volume = volume;
97
+ this.progress = 0;
98
+ this.duration = 0;
99
+ this.starttime = 0;
100
+ this.password = password;
101
+ this.passwordTried = false;
102
+ this.requireEncryption = false;
103
+ this.trackInfo = null;
104
+ this.artwork = null;
105
+ this.artworkContentType = null;
106
+ this.callback = null;
107
+ this.controlPort = null;
108
+ this.timingPort = null;
109
+ this.sentFakeProgess = false;
110
+ this.timingDestPort = null;
111
+ this.eventPort = null;
112
+ this.heartBeat = null;
113
+ this.pair_verify_1_verifier = null;
114
+ this.pair_verify_1_signature = null;
115
+ this.code_digest = null;
116
+ this.authSecret = null;
117
+ this.mode = options?.mode ?? 0;
118
+ this.dnstxt = options?.txt ?? [];
119
+ this.alacEncoding = options?.alacEncoding ?? true;
120
+ this.needPassword = options?.needPassword ?? false;
121
+ this.airplay2 = options?.airplay2 ?? false;
122
+ this.needPin = options?.needPin ?? false;
123
+ this.debug = options?.debug ?? false;
124
+ this.transient = options?.transient ?? false;
125
+ this.borkedshp = options?.borkedshp ?? false;
126
+ this.log = options?.log;
127
+ this.logLine = (...args) => logLine(this, ...args);
128
+ this.privateKey = null;
129
+ this.srp = new SRP(2048);
130
+ this.I = '366B4165DD64AD3A';
131
+ this.P = null;
132
+ this.s = null;
133
+ this.B = null;
134
+ this.a = null;
135
+ this.A = null;
136
+ this.M1 = null;
137
+ this.epk = null;
138
+ this.authTag = null;
139
+ this._atv_salt = null;
140
+ this._atv_pub_key = null;
141
+ this._hap_genkey = null;
142
+ this._hap_encrypteddata = null;
143
+ this.pairingId = null;
144
+ this.seed = null;
145
+ this.credentials = null;
146
+ this.event_credentials = null;
147
+ this.verifier_hap_1 = null;
148
+ this.encryptionKey = null;
149
+ this.encryptedChannel = false;
150
+ this.hostip = null;
151
+ this.homekitver = this.transient ? "4" : "3";
152
+ this.metadataReady = false;
153
+ }
154
+ util.inherits(Client, events.EventEmitter);
155
+ exports.default = { Client };
156
+ Client.prototype.startHandshake = function (udpServers, host, port) {
157
+ var self = this;
158
+ this.startTimeout();
159
+ this.hostip = host;
160
+ this.controlPort = udpServers.control.port;
161
+ this.timingPort = udpServers.timing.port;
162
+ this.socket = net.connect(port, host, async function () {
163
+ self.clearTimeout();
164
+ if (self.needPassword || self.needPin) {
165
+ self.status = PAIR_PIN_START;
166
+ self.sendNextRequest();
167
+ self.startHeartBeat();
168
+ }
169
+ else {
170
+ if (self.mode != 2) {
171
+ if (self.debug)
172
+ self.logLine?.("AUTH_SETUP", "nah");
173
+ self.status = OPTIONS;
174
+ self.sendNextRequest();
175
+ self.startHeartBeat();
176
+ }
177
+ else {
178
+ self.status = AUTH_SETUP;
179
+ if (self.debug)
180
+ self.logLine?.("AUTH_SETUP", "yah");
181
+ self.sendNextRequest();
182
+ self.startHeartBeat();
183
+ }
184
+ }
185
+ });
186
+ var blob = '';
187
+ this.socket.on('data', function (data) {
188
+ if (self.encryptedChannel && self.credentials) {
189
+ // if (self.debug != false) self.logLine?.("incoming", data)
190
+ data = self.credentials.decrypt(data);
191
+ }
192
+ self.clearTimeout();
193
+ /*
194
+ * I wish I could use node's HTTP parser for this...
195
+ * I assume that all responses have empty bodies.
196
+ */
197
+ var rawData = data;
198
+ const dataStr = data.toString();
199
+ blob += dataStr;
200
+ var endIndex = blob.indexOf('\r\n\r\n');
201
+ if (endIndex < 0) {
202
+ return;
203
+ }
204
+ endIndex += 4;
205
+ blob = blob.substring(0, endIndex);
206
+ self.processData(blob, rawData);
207
+ blob = dataStr.substring(endIndex);
208
+ });
209
+ this.socket.on('error', function (err) {
210
+ self.socket = null;
211
+ if (self.debug)
212
+ self.logLine?.(err.code);
213
+ if (err.code === 'ECONNREFUSED') {
214
+ if (self.debug)
215
+ self.logLine?.('block');
216
+ self.cleanup('connection_refused');
217
+ }
218
+ else
219
+ self.cleanup('rtsp_socket', err.code);
220
+ });
221
+ this.socket.on('end', function () {
222
+ if (self.debug)
223
+ self.logLine?.('block2');
224
+ self.cleanup('disconnected');
225
+ });
226
+ };
227
+ Client.prototype.startTimeout = function () {
228
+ var self = this;
229
+ this.timeout = setTimeout(function () {
230
+ if (self.debug)
231
+ self.logLine?.('timeout');
232
+ self.cleanup('timeout');
233
+ }, config.rtsp_timeout);
234
+ };
235
+ Client.prototype.clearTimeout = function () {
236
+ if (this.timeout !== null) {
237
+ clearTimeout(this.timeout);
238
+ this.timeout = null;
239
+ }
240
+ };
241
+ Client.prototype.teardown = function () {
242
+ if (this.status === CLOSED) {
243
+ this.emit('end', 'stopped');
244
+ return;
245
+ }
246
+ this.status = TEARDOWN;
247
+ this.sendNextRequest();
248
+ };
249
+ Client.prototype.setVolume = function (volume, callback) {
250
+ if (this.status !== PLAYING)
251
+ return;
252
+ this.volume = volume;
253
+ this.callback = callback;
254
+ this.status = SETVOLUME;
255
+ this.sendNextRequest();
256
+ };
257
+ Client.prototype.setProgress = function (progress, duration, callback) {
258
+ if (this.status !== PLAYING)
259
+ return;
260
+ let normProgress = progress;
261
+ let normDuration = duration;
262
+ if (normDuration > 1000) {
263
+ if (normProgress > 1000) {
264
+ normProgress = Math.round(normProgress / 1000);
265
+ }
266
+ normDuration = Math.round(normDuration / 1000);
267
+ }
268
+ if (normDuration > 0) {
269
+ normProgress = Math.min(Math.max(0, normProgress), normDuration);
270
+ }
271
+ this.progress = normProgress;
272
+ this.duration = normDuration;
273
+ this.callback = callback;
274
+ this.status = SETPROGRESS;
275
+ this.sendNextRequest();
276
+ };
277
+ Client.prototype.setPasscode = async function (passcode) {
278
+ this.password = passcode;
279
+ this.status = this.airplay2 ? PAIR_SETUP_1 : PAIR_PIN_SETUP_1;
280
+ this.sendNextRequest();
281
+ };
282
+ Client.prototype.startHeartBeat = function () {
283
+ var self = this;
284
+ if (config.rtsp_heartbeat > 0) {
285
+ this.heartBeat = setInterval(function () {
286
+ self.sendHeartBeat(function () {
287
+ //this.logLine?.('HeartBeat sent!');
288
+ });
289
+ }, config.rtsp_heartbeat);
290
+ }
291
+ };
292
+ Client.prototype.sendHeartBeat = function (callback) {
293
+ if (this.status !== PLAYING)
294
+ return;
295
+ this.status = OPTIONS;
296
+ this.callback = callback;
297
+ this.sendNextRequest();
298
+ };
299
+ Client.prototype.setTrackInfo = function (name, artist, album, callback) {
300
+ if (this.status !== PLAYING)
301
+ return;
302
+ if (name != this.trackInfo?.name || artist != this.trackInfo?.artist || album != this.trackInfo?.album) {
303
+ this.starttime = this.audioOut.lastSeq * config.frames_per_packet + 2 * config.sampling_rate;
304
+ }
305
+ this.trackInfo = {
306
+ name: name,
307
+ artist: artist,
308
+ album: album
309
+ };
310
+ this.status = SETDAAP;
311
+ this.callback = callback;
312
+ this.sendNextRequest();
313
+ };
314
+ Client.prototype.setArtwork = function (art, contentType, callback) {
315
+ if (this.status !== PLAYING)
316
+ return;
317
+ if (typeof contentType == 'function') {
318
+ callback = contentType;
319
+ contentType = null;
320
+ }
321
+ if (typeof art == 'string') {
322
+ var self = this;
323
+ if (contentType === null) {
324
+ var ext = art.slice(-4);
325
+ if (ext == ".jpg" || ext == "jpeg") {
326
+ contentType = "image/jpeg";
327
+ }
328
+ else if (ext == ".png") {
329
+ contentType = "image/png";
330
+ }
331
+ else if (ext == ".gif") {
332
+ contentType = "image/gif";
333
+ }
334
+ else {
335
+ return self.cleanup('unknown_art_file_ext');
336
+ }
337
+ }
338
+ return fs.readFile(art, function (err, data) {
339
+ if (err !== null) {
340
+ return self.cleanup('invalid_art_file');
341
+ }
342
+ self.setArtwork(data, contentType, callback);
343
+ });
344
+ }
345
+ if (contentType === null)
346
+ return this.cleanup('no_art_content_type');
347
+ this.artworkContentType = contentType;
348
+ this.artwork = art;
349
+ this.status = SETART;
350
+ this.callback = callback;
351
+ this.sendNextRequest();
352
+ };
353
+ Client.prototype.nextCSeq = function () {
354
+ this.cseq += 1;
355
+ return this.cseq;
356
+ };
357
+ Client.prototype.cleanup = function (type, msg) {
358
+ this.emit('end', type, msg);
359
+ this.status = CLOSED;
360
+ this.trackInfo = null;
361
+ this.artwork = null;
362
+ this.artworkContentType = null;
363
+ this.callback = null;
364
+ this.srp = null;
365
+ this.P = null;
366
+ this.s = null;
367
+ this.B = null;
368
+ this.a = null;
369
+ this.A = null;
370
+ this.M1 = null;
371
+ this.epk = null;
372
+ this.authTag = null;
373
+ this._hap_genkey = null;
374
+ this._hap_encrypteddata = null;
375
+ this.seed = null;
376
+ this.credentials = null;
377
+ // this.password = null;
378
+ this.removeAllListeners();
379
+ if (this.timeout) {
380
+ clearTimeout(this.timeout);
381
+ this.timeout = null;
382
+ }
383
+ if (this.heartBeat) {
384
+ clearInterval(this.heartBeat);
385
+ this.heartBeat = null;
386
+ }
387
+ if (this.socket) {
388
+ this.socket.destroy();
389
+ this.socket = null;
390
+ }
391
+ };
392
+ function parseResponse(blob) {
393
+ var response = {}, lines = blob.split('\r\n');
394
+ if (lines[0].match(/^Audio-Latency/)) {
395
+ let tmp = lines[0];
396
+ lines[0] = lines[1];
397
+ lines[1] = tmp;
398
+ }
399
+ var codeRes = /(\w+)\/(\S+) (\d+) (.*)/.exec(lines[0]);
400
+ if (!codeRes) {
401
+ response.code = 599;
402
+ response.status = 'UNEXPECTED ' + lines[0];
403
+ return response;
404
+ }
405
+ response.code = parseInt(codeRes[3], 10);
406
+ response.status = codeRes[4];
407
+ var headers = {};
408
+ lines.slice(1).forEach(function (line) {
409
+ var res = /([^:]+):\s*(.*)/.exec(line);
410
+ if (!res)
411
+ return;
412
+ headers[res[1]] = res[2];
413
+ });
414
+ response.headers = headers;
415
+ //this.logLine?.(response);
416
+ return response;
417
+ }
418
+ function parseResponse2(blob, self) {
419
+ var response = {}, lines = blob.split('\r\n');
420
+ // if (self.debug) self.logLine?.(lines);
421
+ if (lines[0].match(/^Audio-Latency/)) {
422
+ let tmp = lines[0];
423
+ lines[0] = lines[1];
424
+ lines[1] = tmp;
425
+ }
426
+ var codeRes = /(\w+)\/(\S+) (\d+) (.*)/.exec(lines[0]);
427
+ if (!codeRes) {
428
+ response.code = 599;
429
+ response.status = 'UNEXPECTED ' + lines[0];
430
+ return response;
431
+ }
432
+ response.code = parseInt(codeRes[3], 10);
433
+ response.status = codeRes[4];
434
+ var headers = {};
435
+ lines.slice(1).forEach(function (line) {
436
+ var res = /([^:]+):\s*(.*)/.exec(line);
437
+ if (!res)
438
+ return;
439
+ headers[res[1]] = res[2];
440
+ });
441
+ response.headers = headers;
442
+ // if (this.debug) this.logLine?.('res: ', response);
443
+ return response;
444
+ }
445
+ function md5(str) {
446
+ var md5sum = nodeCrypto.createHash('md5');
447
+ md5sum.update(str);
448
+ return md5sum.digest('hex').toUpperCase();
449
+ }
450
+ function md5norm(str) {
451
+ var md5sum = nodeCrypto.createHash('md5');
452
+ md5sum.update(str);
453
+ return md5sum.digest('hex');
454
+ }
455
+ Client.prototype.makeHead = function (method, uri, di, clear = false, dimode = null) {
456
+ var head = method + ' ' + uri + ' RTSP/1.0' + '\r\n';
457
+ if (!clear) {
458
+ head += 'CSeq: ' + this.nextCSeq() + '\r\n' +
459
+ 'User-Agent: ' + (this.airplay2 ? "AirPlay/409.16" : config.user_agent) + '\r\n' +
460
+ 'DACP-ID: ' + this.dacpId + '\r\n' +
461
+ (this.session ? 'Session: ' + this.session + '\r\n' : '') +
462
+ 'Active-Remote: ' + this.activeRemote + '\r\n';
463
+ head += 'Client-Instance: ' + this.dacpId + '\r\n';
464
+ }
465
+ ;
466
+ if (di) {
467
+ if (dimode == 'airplay2') {
468
+ var ha1 = md5norm(di.username + ':' + di.realm + ':' + di.password);
469
+ var ha2 = md5norm(method + ':' + uri);
470
+ var diResponse = md5(ha1 + ':' + di.nonce + ':' + ha2);
471
+ }
472
+ else {
473
+ var ha1 = md5(di.username + ':' + di.realm + ':' + di.password);
474
+ var ha2 = md5(method + ':' + uri);
475
+ var diResponse = md5(ha1 + ':' + di.nonce + ':' + ha2);
476
+ }
477
+ head += 'Authorization: Digest ' +
478
+ 'username="' + di.username + '", ' +
479
+ 'realm="' + di.realm + '", ' +
480
+ 'nonce="' + di.nonce + '", ' +
481
+ 'uri="' + uri + '", ' +
482
+ 'response="' + diResponse + '"\r\n';
483
+ }
484
+ return head;
485
+ };
486
+ Client.prototype.makeHeadWithURL = function (method, digestInfo, dimode) {
487
+ return this.makeHead(method, 'rtsp://' + this.socket.address().address + '/' + this.announceId, digestInfo, false, dimode);
488
+ };
489
+ Client.prototype.makeRtpInfo = function () {
490
+ var nextSeq = this.audioOut.lastSeq + 1;
491
+ var rtpSyncTime = nextSeq * config.frames_per_packet + 2 * config.sampling_rate;
492
+ return 'RTP-Info: seq=' + nextSeq + ';rtptime=' + rtpSyncTime + '\r\n';
493
+ };
494
+ Client.prototype.sendNextRequest = async function (di) {
495
+ var request = '';
496
+ var body = '';
497
+ if (this.debug)
498
+ this.logLine?.('Sending request:', rtsp_methods[this.status + 1]);
499
+ switch (this.status) {
500
+ case PAIR_PIN_START:
501
+ this.I = '366B4165DD64AD3A';
502
+ this.P = null;
503
+ this.s = null;
504
+ this.B = null;
505
+ this.a = null;
506
+ this.A = null;
507
+ this.M1 = null;
508
+ this.epk = null;
509
+ this.authTag = null;
510
+ this._atv_salt = null;
511
+ this._atv_pub_key = null;
512
+ this._hap_encrypteddata = null;
513
+ this.seed = null;
514
+ this.pairingId = nodeCrypto.randomUUID();
515
+ this.credentials = null;
516
+ this.verifier_hap_1 = null;
517
+ this.encryptionKey = null;
518
+ request = '';
519
+ if (this.transient && (this.needPin != true) && (this.needPassword != true)) {
520
+ (this.status = PAIR_SETUP_1);
521
+ this.sendNextRequest();
522
+ }
523
+ else if (this.needPin) {
524
+ request += this.makeHead("POST", "/pair-pin-start", "", true);
525
+ if (this.airplay2) {
526
+ request += 'User-Agent: AirPlay/409.16\r\n';
527
+ request += 'Connection: keep-alive\r\n';
528
+ request += 'CSeq: ' + 0 + '\r\n';
529
+ }
530
+ request += 'Content-Length:' + 0 + '\r\n\r\n';
531
+ this.socket.write(Buffer.from(request, 'utf-8'));
532
+ }
533
+ else {
534
+ this.logLine?.("pass", this.password);
535
+ if (this.password) {
536
+ this.status = this.airplay2 ? PAIR_SETUP_1 : PAIR_PIN_SETUP_1;
537
+ this.logLine?.("pass2", this.password);
538
+ this.sendNextRequest();
539
+ }
540
+ else {
541
+ if (!this.needPassword) {
542
+ this.status = this.airplay2 ? INFO : PAIR_PIN_SETUP_1;
543
+ this.sendNextRequest();
544
+ }
545
+ else {
546
+ this.emit("need_password");
547
+ }
548
+ }
549
+ }
550
+ request = '';
551
+ //}
552
+ break;
553
+ case PAIR_PIN_SETUP_1:
554
+ request = '';
555
+ request += this.makeHead("POST", "/pair-setup-pin", "", true);
556
+ request += 'Content-Type: application/x-apple-binary-plist\r\n';
557
+ let u = bplistCreator({
558
+ user: '366B4165DD64AD3A',
559
+ method: 'pin'
560
+ });
561
+ request += 'Content-Length:' + Buffer.byteLength(u) + '\r\n\r\n';
562
+ this.socket.write(Buffer.concat([Buffer.from(request, 'utf-8'), u]));
563
+ request = '';
564
+ break;
565
+ case PAIR_PIN_SETUP_2:
566
+ request = '';
567
+ request += this.makeHead("POST", "/pair-setup-pin", "", true);
568
+ request += 'Content-Type: application/x-apple-binary-plist\r\n';
569
+ let u1 = bplistCreator({
570
+ pk: Buffer.from(this.A, 'hex'),
571
+ proof: Buffer.from(this.M1, 'hex')
572
+ });
573
+ request += 'Content-Length:' + Buffer.byteLength(u1) + '\r\n\r\n';
574
+ this.socket.write(Buffer.concat([Buffer.from(request, 'utf-8'), u1]));
575
+ request = '';
576
+ break;
577
+ case PAIR_PIN_SETUP_3:
578
+ request = '';
579
+ request += this.makeHead("POST", "/pair-setup-pin", "", true);
580
+ request += 'Content-Type: application/x-apple-binary-plist\r\n';
581
+ let u2 = bplistCreator({
582
+ epk: Buffer.from(this.epk, 'hex'),
583
+ authTag: Buffer.from(this.authTag, 'hex')
584
+ });
585
+ request += 'Content-Length:' + Buffer.byteLength(u2) + '\r\n\r\n';
586
+ this.socket.write(Buffer.concat([Buffer.from(request, 'utf-8'), u2]));
587
+ request = '';
588
+ break;
589
+ case PAIR_VERIFY_1:
590
+ request = '';
591
+ request += this.makeHead("POST", "/pair-verify", "", true);
592
+ request += 'Content-Type: application/octet-stream\r\n';
593
+ this.pair_verify_1_verifier = ATVAuthenticator.verifier(this.authSecret);
594
+ if (this.debug)
595
+ this.logLine?.(this.authSecret);
596
+ request += 'Content-Length:' + Buffer.byteLength(this.pair_verify_1_verifier.verifierBody) + '\r\n\r\n';
597
+ this.socket.write(Buffer.concat([Buffer.from(request, 'utf-8'), this.pair_verify_1_verifier.verifierBody]));
598
+ request = '';
599
+ break;
600
+ case PAIR_VERIFY_2:
601
+ request = '';
602
+ request += this.makeHead("POST", "/pair-verify", "", true);
603
+ request += 'Content-Type: application/octet-stream\r\n';
604
+ request += 'Content-Length:' + Buffer.byteLength(this.pair_verify_1_signature) + '\r\n\r\n';
605
+ this.socket.write(Buffer.concat([Buffer.from(request, 'utf-8'), this.pair_verify_1_signature]));
606
+ request = '';
607
+ // const verifier = ATVAuthenticator.verifier('3c0591f41d1236c9ce5078sscd6fcd42f71f374b8b6dff33fea825366f1c34f828');
608
+ // request += 'Content-Length:' + Buffer.byteLength(verifier.verifierBody) + '\r\n\r\n';
609
+ // this.socket.write(Buffer.concat([Buffer.from(request, 'utf-8'),verifier.verifierBody]))
610
+ // request = ''
611
+ break;
612
+ case PAIR_SETUP_1:
613
+ if (this.debug)
614
+ this.logLine?.('loh');
615
+ request = '';
616
+ request += this.makeHead("POST", "/pair-setup", "", true);
617
+ request += 'User-Agent: AirPlay/409.16\r\n';
618
+ request += 'CSeq: ' + this.nextCSeq() + '\r\n';
619
+ request += 'Connection: keep-alive\r\n';
620
+ request += 'X-Apple-HKP: ' + this.homekitver + '\r\n';
621
+ this.logLine?.('rtsp.transient', this.transient);
622
+ if (this.transient == true) {
623
+ this.logLine?.('rtsp.transient', 'uas');
624
+ let ps1 = tlv.encode(tlv.Tag.Sequence, 0x01, tlv.Tag.PairingMethod, 0x00, tlv.Tag.Flags, 0x00000010);
625
+ request += 'Content-Length: ' + Buffer.byteLength(ps1) + '\r\n';
626
+ request += 'Content-Type: application/octet-stream' + '\r\n\r\n';
627
+ this.socket.write(Buffer.concat([Buffer.from(request, 'utf-8'), ps1]));
628
+ }
629
+ else {
630
+ let ps1 = tlv.encode(tlv.Tag.PairingMethod, 0x00, tlv.Tag.Sequence, 0x01);
631
+ request += 'Content-Length: ' + 6 + '\r\n';
632
+ request += 'Content-Type: application/octet-stream' + '\r\n\r\n';
633
+ this.socket.write(Buffer.concat([Buffer.from(request, 'utf-8'), ps1]));
634
+ }
635
+ request = '';
636
+ break;
637
+ case PAIR_SETUP_2:
638
+ if (this.debug)
639
+ this.logLine?.('loh2');
640
+ request = '';
641
+ request += this.makeHead("POST", "/pair-setup", "", true);
642
+ request += 'User-Agent: AirPlay/409.16\r\n';
643
+ request += 'CSeq: ' + this.nextCSeq() + '\r\n';
644
+ request += 'Connection: keep-alive\r\n';
645
+ request += 'X-Apple-HKP: ' + this.homekitver + '\r\n';
646
+ request += 'Content-Type: application/octet-stream\r\n';
647
+ let ps2 = tlv.encode(tlv.Tag.Sequence, 0x03, tlv.Tag.PublicKey, this.A, tlv.Tag.Proof, this.M1);
648
+ request += 'Content-Length: ' + Buffer.byteLength(ps2) + '\r\n\r\n';
649
+ this.socket.write(Buffer.concat([Buffer.from(request, 'utf-8'), ps2]));
650
+ request = '';
651
+ break;
652
+ case PAIR_SETUP_3:
653
+ if (this.debug)
654
+ this.logLine?.('loh3');
655
+ request = '';
656
+ request += this.makeHead("POST", "/pair-setup", "", true);
657
+ request += 'User-Agent: AirPlay/409.16\r\n';
658
+ request += 'CSeq: ' + this.nextCSeq() + '\r\n';
659
+ request += 'Connection: keep-alive\r\n';
660
+ request += 'X-Apple-HKP: ' + this.homekitver + '\r\n';
661
+ request += 'Content-Type: application/octet-stream\r\n';
662
+ this.K = this.srp.computeK();
663
+ this.seed = nodeCrypto.randomBytes(32);
664
+ // let keyPair = ed25519.MakeKeypair(this.seed);
665
+ this.privateKey = ed25519_js.utils.randomPrivateKey();
666
+ let publicKey = await ed25519_js.getPublicKey(this.privateKey);
667
+ // let keyPair = nacl.sign.keyPair.fromSeed(this.seed)
668
+ // let privateKey = keyPair.secretKey;
669
+ // let publicKey = keyPair.publicKey;
670
+ let deviceHash = enc.HKDF("sha512", Buffer.from("Pair-Setup-Controller-Sign-Salt"), this.K, Buffer.from("Pair-Setup-Controller-Sign-Info"), 32);
671
+ let deviceInfo = Buffer.concat([deviceHash, Buffer.from(this.pairingId), publicKey]);
672
+ let deviceSignature = await ed25519_js.sign(deviceInfo, this.privateKey);
673
+ // let deviceSignature = nacl.sign(deviceInfo, privateKey)
674
+ this.encryptionKey = enc.HKDF("sha512", Buffer.from("Pair-Setup-Encrypt-Salt"), this.K, Buffer.from("Pair-Setup-Encrypt-Info"), 32);
675
+ let tlvData = tlv.encode(tlv.Tag.Username, Buffer.from(this.pairingId), tlv.Tag.PublicKey, publicKey, tlv.Tag.Signature, deviceSignature);
676
+ let encryptedTLV = Buffer.concat(enc.encryptAndSeal(tlvData, null, Buffer.from('PS-Msg05'), this.encryptionKey));
677
+ // this.logLine?.("DEBUG: Encrypted Data=" + encryptedTLV.toString('hex'));
678
+ let outerTLV = tlv.encode(tlv.Tag.Sequence, 0x05, tlv.Tag.EncryptedData, encryptedTLV);
679
+ request += 'Content-Length: ' + Buffer.byteLength(outerTLV) + '\r\n\r\n';
680
+ this.socket.write(Buffer.concat([Buffer.from(request, 'utf-8'), outerTLV]));
681
+ request = '';
682
+ break;
683
+ case PAIR_VERIFY_HAP_1:
684
+ request = '';
685
+ request += this.makeHead("POST", "/pair-verify", "", true);
686
+ request += 'User-Agent: AirPlay/409.16\r\n';
687
+ request += 'CSeq: ' + this.nextCSeq() + '\r\n';
688
+ request += 'Connection: keep-alive\r\n';
689
+ request += 'X-Apple-HKP: ' + this.homekitver + '\r\n';
690
+ request += 'Content-Type: application/octet-stream\r\n';
691
+ let hap1kp = curve25519_js.generateKeyPair(Buffer.alloc(32));
692
+ this.verifyPrivate = Buffer.from(hap1kp.private);
693
+ this.verifyPublic = Buffer.from(hap1kp.public);
694
+ // this.verifyPrivate = Buffer.alloc(32);
695
+ // curve25519.makeSecretKey(this.verifyPrivate);
696
+ // this.verifyPublic = curve25519.derivePublicKey(this.verifyPrivate);
697
+ let encodedData = tlv.encode(tlv.Tag.Sequence, 0x01, tlv.Tag.PublicKey, this.verifyPublic);
698
+ request += 'Content-Length: ' + Buffer.byteLength(encodedData) + '\r\n\r\n';
699
+ this.socket.write(Buffer.concat([Buffer.from(request, 'utf-8'), encodedData]));
700
+ request = '';
701
+ break;
702
+ case PAIR_VERIFY_HAP_2:
703
+ request = '';
704
+ request += this.makeHead("POST", "/pair-verify", "", true);
705
+ request += 'User-Agent: AirPlay/409.16\r\n';
706
+ request += 'CSeq: ' + this.nextCSeq() + '\r\n';
707
+ request += 'Connection: keep-alive\r\n';
708
+ request += 'X-Apple-HKP: ' + this.homekitver + '\r\n';
709
+ request += 'Content-Type: application/octet-stream\r\n';
710
+ let identifier = tlv.decode(this.verifier_hap_1.pairingData)[tlv.Tag.Username];
711
+ let signature = tlv.decode(this.verifier_hap_1.pairingData)[tlv.Tag.Signature];
712
+ let material = Buffer.concat([this.verifyPublic, Buffer.from(this.credentials.pairingId), this.verifier_hap_1.sessionPublicKey]);
713
+ // let keyPair1 = ed25519.MakeKeypair(this.credentials.encryptionKey);
714
+ // let signed = ed25519.Sign(material, keyPair1);
715
+ // let keyPair1 = ed25519.MakeKeypair(this.credentials.encryptionKey);
716
+ let signed = await ed25519_js.sign(material, this.privateKey);
717
+ this.logLine?.("lengths", this.credentials.encryptionKey.length);
718
+ // let keyPair1 = nacl.sign.keyPair.fromSeed(this.credentials.encryptionKey)
719
+ // let signed = nacl.sign(material, keyPair1.secretKey);
720
+ let plainTLV = tlv.encode(tlv.Tag.Username, Buffer.from(this.credentials.pairingId), tlv.Tag.Signature, signed);
721
+ let encryptedTLV1 = Buffer.concat(enc.encryptAndSeal(plainTLV, null, Buffer.from('PV-Msg03'), this.verifier_hap_1.encryptionKey));
722
+ let pv2 = tlv.encode(tlv.Tag.Sequence, 0x03, tlv.Tag.EncryptedData, encryptedTLV1);
723
+ request += 'Content-Length: ' + Buffer.byteLength(pv2) + '\r\n\r\n';
724
+ this.socket.write(Buffer.concat([Buffer.from(request, 'utf-8'), pv2]));
725
+ request = '';
726
+ break;
727
+ case AUTH_SETUP:
728
+ request = '';
729
+ request += this.makeHead("POST", "/auth-setup", di);
730
+ request += 'Content-Length: ' + 33 + '\r\n\r\n';
731
+ let finalbuffer = Buffer.concat([Buffer.from(request, 'utf-8'),
732
+ Buffer.from([0x01, // unencrypted
733
+ 0x4e, 0xea, 0xd0, 0x4e, 0xa9, 0x2e, 0x47, 0x69,
734
+ 0xd2, 0xe1, 0xfb, 0xd0, 0x96, 0x81, 0xd5, 0x94,
735
+ 0xa8, 0xef, 0x18, 0x45, 0x4a, 0x24, 0xae, 0xaf,
736
+ 0xb3, 0x14, 0x97, 0x0d, 0xa0, 0xb5, 0xa3, 0x49])
737
+ ]);
738
+ if (this.airplay2 != true && this.credentials != null) {
739
+ try {
740
+ this.socket.write(this.credentials.encrypt(finalbuffer));
741
+ }
742
+ catch (e) {
743
+ }
744
+ }
745
+ else {
746
+ this.socket.write(finalbuffer);
747
+ }
748
+ request = '';
749
+ // this.status = OPTIONS;
750
+ // this.sendNextRequest()
751
+ break;
752
+ case OPTIONS:
753
+ request += this.makeHead('OPTIONS', '*', di);
754
+ if (this.airplay2) {
755
+ request += 'User-Agent: AirPlay/409.16\r\n';
756
+ request += 'Connection: keep-alive\r\n';
757
+ }
758
+ request += 'Apple-Challenge: SdX9kFJVxgKVMFof/Znj4Q\r\n\r\n';
759
+ break;
760
+ case OPTIONS2:
761
+ request = '';
762
+ request += this.makeHead('OPTIONS', '*');
763
+ request += this.code_digest;
764
+ this.logLine?.(request);
765
+ this.socket.write(Buffer.from(request, 'utf-8'));
766
+ request = '';
767
+ break;
768
+ case OPTIONS3:
769
+ request = '';
770
+ request += this.makeHead('OPTIONS', '*', di);
771
+ this.logLine?.(request);
772
+ this.socket.write(Buffer.from(request, 'utf-8'));
773
+ request = '';
774
+ break;
775
+ case ANNOUNCE:
776
+ if (this.announceId == null) {
777
+ this.announceId = nu.randomInt(10);
778
+ }
779
+ body =
780
+ 'v=0\r\n' +
781
+ 'o=iTunes ' + this.announceId + ' 0 IN IP4 ' + this.socket.address().address + '\r\n' +
782
+ 's=iTunes\r\n' +
783
+ 'c=IN IP4 ' + this.socket.address().address + '\r\n' +
784
+ 't=0 0\r\n' +
785
+ 'm=audio 0 RTP/AVP 96\r\n';
786
+ if (!this.alacEncoding) {
787
+ body = body + 'a=rtpmap:96 L16/44100/2\r\n' +
788
+ 'a=fmtp:96 352 0 16 40 10 14 2 255 0 0 44100\r\n';
789
+ }
790
+ else {
791
+ body = body + 'a=rtpmap:96 AppleLossless\r\n' +
792
+ 'a=fmtp:96 352 0 16 40 10 14 2 255 0 0 44100\r\n';
793
+ }
794
+ ;
795
+ if (this.requireEncryption) {
796
+ body +=
797
+ 'a=rsaaeskey:' + config.rsa_aeskey_base64 + '\r\n' +
798
+ 'a=aesiv:' + config.iv_base64 + '\r\n';
799
+ }
800
+ request += this.makeHeadWithURL('ANNOUNCE', di);
801
+ request +=
802
+ 'Content-Type: application/sdp\r\n' +
803
+ 'Content-Length: ' + body.length + '\r\n\r\n';
804
+ request += body;
805
+ //this.logLine?.(request);
806
+ break;
807
+ case SETUP:
808
+ request += this.makeHeadWithURL('SETUP', di);
809
+ request +=
810
+ 'Transport: RTP/AVP/UDP;unicast;interleaved=0-1;mode=record;' +
811
+ 'control_port=' + this.controlPort + ';' +
812
+ 'timing_port=' + this.timingPort + '\r\n\r\n';
813
+ //this.logLine?.(request);
814
+ break;
815
+ case INFO:
816
+ request += this.makeHead('GET', '/info', di, true);
817
+ request += 'User-Agent: AirPlay/409.16\r\n';
818
+ request += 'Connection: keep-alive\r\n';
819
+ request += 'CSeq: ' + this.nextCSeq() + '\r\n\r\n';
820
+ if (this.credentials) {
821
+ let enct1x = this.credentials.encrypt(Buffer.concat([Buffer.from(request, 'utf-8')]));
822
+ this.socket.write(enct1x);
823
+ request = '';
824
+ }
825
+ //this.logLine?.(request);
826
+ break;
827
+ case SETUP_AP2_1:
828
+ if (this.announceId == null) {
829
+ this.announceId = nu.randomInt(10);
830
+ }
831
+ request += this.makeHeadWithURL('SETUP', di, "airplay2");
832
+ request += 'Content-Type: application/x-apple-binary-plist\r\n';
833
+ // request += 'CSeq: ' + this.nextCSeq() + '\r\n' ;
834
+ // this.timingPort = 32325;
835
+ this.logLine?.('starting ports', this.timingPort, this.controlPort);
836
+ let setap1 = bplistCreator({ deviceID: '2C:61:F3:B6:64:C1',
837
+ sessionUUID: '8EB266BA-B741-40C5-8213-4B7A38DF8773',
838
+ timingPort: this.timingPort,
839
+ timingProtocol: 'NTP'
840
+ // ekey: config.rsa_aeskey_base64,
841
+ // eiv: config.iv_base64
842
+ });
843
+ try {
844
+ this.timingsocket.close();
845
+ }
846
+ catch (e) { }
847
+ try {
848
+ this.timingsocket = dgram.createSocket({ type: 'udp4', reuseAddr: true });
849
+ var self = this;
850
+ this.timingsocket.on('message', function (msg, rinfo) {
851
+ // only listen and respond on own hosts
852
+ // if (this.hosts.indexOf(rinfo.address) < 0) return;
853
+ var ts1 = msg.readUInt32BE(24);
854
+ var ts2 = msg.readUInt32BE(28);
855
+ var reply = Buffer.alloc(32);
856
+ reply.writeUInt16BE(0x80d3, 0);
857
+ reply.writeUInt16BE(0x0007, 2);
858
+ reply.writeUInt32BE(0x00000000, 4);
859
+ reply.writeUInt32BE(ts1, 8);
860
+ reply.writeUInt32BE(ts2, 12);
861
+ var ntpTime = ntp.timestamp();
862
+ ntpTime.copy(reply, 16);
863
+ ntpTime.copy(reply, 24);
864
+ self.timingsocket.send(reply, 0, reply.length, rinfo.port, rinfo.address);
865
+ self.logLine?.('timing socket pinged', rinfo.port, rinfo.address);
866
+ });
867
+ this.timingsocket.bind(this.timingPort, this.socket.address().address);
868
+ }
869
+ catch (e) { }
870
+ request += 'Content-Length: ' + Buffer.byteLength(setap1) + '\r\n\r\n';
871
+ this.logLine?.(request);
872
+ let s1ct = this.credentials.encrypt(Buffer.concat([Buffer.from(request, 'utf-8'), setap1]));
873
+ this.socket.write(s1ct);
874
+ request = '';
875
+ break;
876
+ case SETPEERS:
877
+ request += this.makeHeadWithURL('SETPEERS', di);
878
+ request += 'Content-Type: /peer-list-changed\r\n';
879
+ let speers = bplistCreator([
880
+ this.hostip, this.socket.address().address
881
+ ]);
882
+ this.logLine?.([
883
+ this.hostip, this.socket.address().address
884
+ ]);
885
+ request += 'Content-Length: ' + Buffer.byteLength(speers) + '\r\n\r\n';
886
+ let spct = this.credentials.encrypt(Buffer.concat([Buffer.from(request, 'utf-8'), speers]));
887
+ this.socket.write(spct);
888
+ request = '';
889
+ break;
890
+ case FLUSH:
891
+ request += this.makeHeadWithURL('FLUSH', di);
892
+ request += this.makeRtpInfo() + '\r\n';
893
+ let fct = this.credentials.encrypt(Buffer.concat([Buffer.from(request, 'utf-8')]));
894
+ this.socket.write(fct);
895
+ request = '';
896
+ break;
897
+ case SETUP_AP2_2:
898
+ if (this.announceId == null) {
899
+ this.announceId = nu.randomInt(10);
900
+ }
901
+ request += this.makeHeadWithURL('SETUP', di);
902
+ request += 'Content-Type: application/x-apple-binary-plist\r\n';
903
+ let setap2 = bplistCreator({ streams: [{ audioFormat: 262144, // PCM/44100/16/2
904
+ audioMode: 'default',
905
+ controlPort: this.controlPort,
906
+ ct: 2,
907
+ isMedia: true,
908
+ latencyMax: 88200,
909
+ latencyMin: 11025,
910
+ shk: Buffer.from(this.credentials.writeKey),
911
+ spf: 352,
912
+ sr: 44100,
913
+ type: 0x60,
914
+ supportsDynamicStreamID: false,
915
+ streamConnectionID: this.announceId
916
+ }] });
917
+ request += 'Content-Length: ' + Buffer.byteLength(setap2) + '\r\n\r\n';
918
+ this.controlsocket = dgram.createSocket({ type: 'udp4', reuseAddr: true });
919
+ var self = this;
920
+ this.controlsocket.on('message', function (msg, rinfo) {
921
+ self.logLine?.('controlsocket.data', msg);
922
+ });
923
+ this.controlsocket.bind(this.controlPort, this.socket.address().address);
924
+ let s2ct = this.credentials.encrypt(Buffer.concat([Buffer.from(request, 'utf-8'), setap2]));
925
+ this.socket.write(s2ct);
926
+ request = '';
927
+ break;
928
+ case RECORD:
929
+ //this.logLine?.(request);
930
+ if (this.airplay2) {
931
+ this.eventsocket = net.connect(this.eventPort, this.hostip, async function () {
932
+ });
933
+ this.eventsocket.on('data', function (data) {
934
+ self.logLine?.('eventsocket.data', data);
935
+ self.logLine?.('eventsocket.data2', self.event_credentials.decrypt(data).toString());
936
+ });
937
+ this.eventsocket.on('error', function (err) {
938
+ self.logLine?.('eventsocket.error', err);
939
+ });
940
+ this.event_credentials = new Credentials("sdsds", "", "", "", this.seed);
941
+ this.event_credentials.writeKey = enc.HKDF("sha512", Buffer.from("Events-Salt"), this.srp.computeK(), Buffer.from("Events-Read-Encryption-Key"), 32);
942
+ this.event_credentials.readKey = enc.HKDF("sha512", Buffer.from("Events-Salt"), this.srp.computeK(), Buffer.from("Events-Write-Encryption-Key"), 32);
943
+ }
944
+ if (this.airplay2 != null && this.credentials != null) {
945
+ // this.controlsocket.close();
946
+ var nextSeq = this.audioOut.lastSeq + 10;
947
+ var rtpSyncTime = nextSeq * config.frames_per_packet + 2 * config.sampling_rate;
948
+ request += this.makeHead('RECORD', 'rtsp://' + this.socket.address().address + '/' + this.announceId, di, true);
949
+ request += 'CSeq: ' + ++this.cseq + '\r\n';
950
+ request += 'User-Agent: AirPlay/409.16' + '\r\n';
951
+ request += 'Client-Instance: ' + this.dacpId + '\r\n';
952
+ request += 'DACP-ID: ' + this.dacpId + '\r\n';
953
+ request += 'Active-Remote: ' + this.activeRemote + '\r\n';
954
+ request += 'X-Apple-ProtocolVersion: 1\r\n';
955
+ request += 'Range: npt=0-\r\n';
956
+ request += this.makeRtpInfo() + '\r\n';
957
+ // request += '\r\n';
958
+ this.logLine?.('ssdas3', request);
959
+ let rct = this.credentials.encrypt(Buffer.from(request, 'utf-8'));
960
+ this.socket.write(rct);
961
+ request = "";
962
+ }
963
+ else {
964
+ request += this.makeHeadWithURL('RECORD', di);
965
+ request += 'Range: npt=0-\r\n';
966
+ request += this.makeRtpInfo() + '\r\n';
967
+ }
968
+ break;
969
+ case GETVOLUME:
970
+ body = "volume\r\n";
971
+ request += this.makeHeadWithURL('GET_PARAMETER', di);
972
+ request +=
973
+ 'Content-Type: text/parameters\r\n' +
974
+ 'Content-Length: ' + body.length + '\r\n\r\n';
975
+ if (this.airplay2) {
976
+ let rct2 = this.credentials.encrypt(Buffer.concat([Buffer.from(request + body, 'utf-8')]));
977
+ this.socket.write(rct2);
978
+ request = "";
979
+ }
980
+ else {
981
+ }
982
+ break;
983
+ case SETVOLUME:
984
+ var attenuation = this.volume === 0.0 ?
985
+ -144.0 :
986
+ (-30.0) * (100 - this.volume) / 100.0;
987
+ body = 'volume: ' + attenuation + '\r\n';
988
+ request += this.makeHeadWithURL('SET_PARAMETER', di);
989
+ request +=
990
+ 'Content-Type: text/parameters\r\n' +
991
+ 'Content-Length: ' + body.length + '\r\n\r\n';
992
+ request += body;
993
+ //this.logLine?.(request);
994
+ break;
995
+ case SETPROGRESS:
996
+ function hms(seconds) {
997
+ const h = Math.floor(seconds / 3600);
998
+ const m = Math.floor((seconds % 3600) / 60);
999
+ const s = Math.floor(seconds % 60);
1000
+ return [h, m, s].map((a) => a.toString().padStart(2, '0')).join(':');
1001
+ }
1002
+ let position = this.starttime + (this.progress) * Math.floor((2 * config.sampling_rate) / (config.frames_per_packet / 125) / 0.71);
1003
+ let duration = this.starttime + (this.duration) * Math.floor((2 * config.sampling_rate) / (config.frames_per_packet / 125) / 0.71);
1004
+ if (this.debug) {
1005
+ this.logLine?.('start', this.starttime, 'position', position, 'duration', duration, 'position1', hms(this.progress), 'duration1', hms(this.duration));
1006
+ }
1007
+ body = "progress: " + this.starttime + "/" + position + "/" + duration + '\r\n';
1008
+ request += this.makeHeadWithURL('SET_PARAMETER', di);
1009
+ request +=
1010
+ 'Content-Type: text/parameters\r\n' +
1011
+ 'Content-Length: ' + body.length + '\r\n\r\n';
1012
+ request += body;
1013
+ //this.logLine?.(request);
1014
+ break;
1015
+ case SETDAAP:
1016
+ let daapenc = true;
1017
+ //daapenc = true
1018
+ var name = this.daapEncode('minm', this.trackInfo.name, daapenc);
1019
+ var artist = this.daapEncode('asar', this.trackInfo.artist, daapenc);
1020
+ var album = this.daapEncode('asal', this.trackInfo.album, daapenc);
1021
+ var daapInfo = this.daapEncodeList('mlit', daapenc, name, artist, album);
1022
+ var head = this.makeHeadWithURL('SET_PARAMETER', di);
1023
+ head += this.makeRtpInfo();
1024
+ head +=
1025
+ 'Content-Type: application/x-dmap-tagged\r\n' +
1026
+ 'Content-Length: ' + daapInfo.length + '\r\n\r\n';
1027
+ var buf = Buffer.alloc(head.length);
1028
+ buf.write(head, 0, head.length, 'utf-8');
1029
+ request = Buffer.concat([buf, daapInfo]);
1030
+ //this.logLine?.(request);
1031
+ break;
1032
+ case SETART:
1033
+ var head = this.makeHeadWithURL('SET_PARAMETER', di);
1034
+ head += this.makeRtpInfo();
1035
+ head +=
1036
+ 'Content-Type: ' + this.artworkContentType + '\r\n' +
1037
+ 'Content-Length: ' + this.artwork.length + '\r\n\r\n';
1038
+ var buf = Buffer.alloc(head.length);
1039
+ buf.write(head, 0, head.length, 'utf-8');
1040
+ request = Buffer.concat([buf, this.artwork]);
1041
+ //this.logLine?.(request);
1042
+ if (this.encryptedChannel && this.credentials) {
1043
+ this.socket.write(this.credentials.encrypt(Buffer.concat([request])));
1044
+ }
1045
+ else {
1046
+ this.socket.write(request);
1047
+ }
1048
+ request = '';
1049
+ break;
1050
+ case TEARDOWN:
1051
+ try {
1052
+ this.socket.end(this.makeHead('TEARDOWN', '', di) + '\r\n');
1053
+ }
1054
+ catch (_) { }
1055
+ if (this.debug)
1056
+ this.logLine?.('teardown');
1057
+ this.cleanup('stopped');
1058
+ // return here since the socket is closed
1059
+ return;
1060
+ default:
1061
+ return;
1062
+ }
1063
+ this.startTimeout();
1064
+ if (this.encryptedChannel && this.credentials) {
1065
+ this.socket.write(this.credentials.encrypt(Buffer.concat([Buffer.from(request, 'utf-8')])));
1066
+ }
1067
+ else {
1068
+ this.socket.write(request);
1069
+ }
1070
+ };
1071
+ Client.prototype.daapEncodeList = function (field, enc, ...args) {
1072
+ var values = Array.prototype.slice.call(args);
1073
+ var value = Buffer.concat(values);
1074
+ var buf = Buffer.alloc(field.length + 4);
1075
+ buf.write(field, 0, field.length, enc ? 'utf-8' : "ascii");
1076
+ buf.writeUInt32BE(value.byteLength, field.length);
1077
+ return Buffer.concat([buf, value]);
1078
+ };
1079
+ Client.prototype.daapEncode = function (field, value, enc) {
1080
+ var valuebuf = Buffer.from(value, 'utf-8');
1081
+ var buf = Buffer.alloc(field.length + valuebuf.byteLength + 4);
1082
+ buf.write(field, 0, field.length, enc ? 'utf-8' : "ascii");
1083
+ buf.writeUInt32BE(valuebuf.byteLength, field.length);
1084
+ buf.write(value, field.length + 4, valuebuf.byteLength, enc ? 'utf-8' : "ascii");
1085
+ return buf;
1086
+ };
1087
+ Client.prototype.parsePorts = function (headers) {
1088
+ function parsePort(name, transport) {
1089
+ var re = new RegExp(name + '=(\\d+)');
1090
+ var res = re.exec(transport);
1091
+ return res ? parseInt(res[1]) : null;
1092
+ }
1093
+ var transport = String(headers['Transport'] ?? ''), rtspConfig = {
1094
+ audioLatency: parseInt(String(headers['Audio-Latency'] ?? '0'), 10),
1095
+ requireEncryption: this.requireEncryption
1096
+ }, names = ['server_port', 'control_port', 'timing_port'];
1097
+ for (var i = 0; i < names.length; i++) {
1098
+ var name = names[i];
1099
+ var port = parsePort(name, transport);
1100
+ if (port === null) {
1101
+ if (this.debug)
1102
+ this.logLine?.('parseport');
1103
+ // this.cleanup('parse_ports', transport);
1104
+ // return false;
1105
+ rtspConfig[name] = 4533;
1106
+ }
1107
+ else
1108
+ rtspConfig[name] = port;
1109
+ }
1110
+ this.emit('config', rtspConfig);
1111
+ return true;
1112
+ };
1113
+ function parseAuthenticate(auth, field) {
1114
+ var re = new RegExp(field + '="([^"]+)"'), res = re.exec(auth);
1115
+ return res ? res[1] : null;
1116
+ }
1117
+ Client.prototype.processData = function (blob, rawData) {
1118
+ this.logLine?.('Receiving request:', this.hostip, rtsp_methods[this.status + 1]);
1119
+ var response = parseResponse2(blob, this), headers = response.headers || {};
1120
+ if (this.debug != false) {
1121
+ if ((rawData.toString()).includes("bplist00")) {
1122
+ try {
1123
+ let buf = Buffer.from(rawData).slice(rawData.length - parseInt(headers['Content-Length']), rawData.length);
1124
+ let bplist = bplistParser.parseBuffer(buf);
1125
+ this.logLine?.("incoming-res: \r\n", JSON.stringify(bplist));
1126
+ }
1127
+ catch (_) {
1128
+ this.logLine?.("incoming-res: \r\n", rawData.toString());
1129
+ }
1130
+ }
1131
+ else {
1132
+ this.logLine?.("incoming-res: \r\n", rawData.toString());
1133
+ }
1134
+ }
1135
+ if (this.status != OPTIONS && this.status != OPTIONS2 && this.mode == 0) {
1136
+ if (response.code === 401) {
1137
+ if (!this.password) {
1138
+ if (this.debug)
1139
+ this.logLine?.('nopass');
1140
+ if (this.status == OPTIONS3) {
1141
+ this.emit('pair_failed');
1142
+ this.cleanup('no_password');
1143
+ }
1144
+ return;
1145
+ }
1146
+ if (response.code === 455) {
1147
+ return;
1148
+ }
1149
+ if (this.passwordTried) {
1150
+ if (this.debug)
1151
+ this.logLine?.('badpass');
1152
+ this.emit('pair_failed');
1153
+ this.cleanup('bad_password');
1154
+ return;
1155
+ }
1156
+ else
1157
+ this.passwordTried = true;
1158
+ var auth = headers['WWW-Authenticate'];
1159
+ var di = {
1160
+ realm: parseAuthenticate(auth, 'realm'),
1161
+ nonce: parseAuthenticate(auth, 'nonce'),
1162
+ username: 'iTunes',
1163
+ password: this.password
1164
+ };
1165
+ if (this.debug)
1166
+ this.logLine?.();
1167
+ this.sendNextRequest(di);
1168
+ return;
1169
+ }
1170
+ if (response.code === 453) {
1171
+ if (this.debug)
1172
+ this.logLine?.('busy');
1173
+ this.cleanup('busy');
1174
+ return;
1175
+ }
1176
+ if (response.code === 403 && this.status == ANNOUNCE && this.mode == 2) {
1177
+ this.status = AUTH_SETUP;
1178
+ this.sendNextRequest();
1179
+ return;
1180
+ }
1181
+ if (response.code !== 200) {
1182
+ if (this.status != SETVOLUME && (this.status != ANNOUNCE && this.mode == 2) && this.status != SETPEERS && this.status != FLUSH && this.status != RECORD && this.status != GETVOLUME && this.status != SETPROGRESS && this.status != SETDAAP && this.status != SETART) {
1183
+ if ([PAIR_VERIFY_1,
1184
+ PAIR_VERIFY_2,
1185
+ AUTH_SETUP,
1186
+ PAIR_PIN_START,
1187
+ PAIR_PIN_SETUP_1,
1188
+ PAIR_PIN_SETUP_2,
1189
+ PAIR_PIN_SETUP_3].includes(this.status)) {
1190
+ this.emit('pair_failed');
1191
+ }
1192
+ this.cleanup(response.status);
1193
+ return;
1194
+ }
1195
+ }
1196
+ }
1197
+ else if (this.mode == 1) {
1198
+ if (response.code === 401) {
1199
+ if (!this.password) {
1200
+ if (this.debug)
1201
+ this.logLine?.('nopass');
1202
+ this.emit('pair_failed');
1203
+ this.cleanup('no_password');
1204
+ return;
1205
+ }
1206
+ if (this.passwordTried) {
1207
+ if (this.debug)
1208
+ this.logLine?.('badpass');
1209
+ this.emit('pair_failed');
1210
+ this.cleanup('bad_password');
1211
+ return;
1212
+ }
1213
+ else
1214
+ this.passwordTried = true;
1215
+ var auth = headers['WWW-Authenticate'];
1216
+ var di = {
1217
+ realm: parseAuthenticate(auth, 'realm'),
1218
+ nonce: parseAuthenticate(auth, 'nonce'),
1219
+ username: 'iTunes',
1220
+ password: this.password
1221
+ };
1222
+ if (this.debug)
1223
+ this.logLine?.(di);
1224
+ this.sendNextRequest(di);
1225
+ return;
1226
+ }
1227
+ if (response.code === 453) {
1228
+ if (this.debug)
1229
+ this.logLine?.('busy');
1230
+ this.cleanup('busy');
1231
+ return;
1232
+ }
1233
+ if (response.code === 403 && this.status == ANNOUNCE && this.mode == 2) {
1234
+ this.status = AUTH_SETUP;
1235
+ this.sendNextRequest();
1236
+ return;
1237
+ }
1238
+ if (response.code !== 200) {
1239
+ if (this.debug)
1240
+ this.logLine?.(response.status);
1241
+ if (this.status != SETVOLUME && (this.status != ANNOUNCE && this.mode == 2) && this.status != SETPEERS && this.status != FLUSH && this.status != RECORD && this.status != GETVOLUME && this.status != SETPROGRESS && this.status != SETDAAP && this.status != SETART) {
1242
+ if ([PAIR_VERIFY_1,
1243
+ PAIR_VERIFY_2,
1244
+ AUTH_SETUP,
1245
+ PAIR_PIN_START,
1246
+ PAIR_PIN_SETUP_1,
1247
+ PAIR_PIN_SETUP_2,
1248
+ PAIR_PIN_SETUP_3].includes(this.status)) {
1249
+ this.emit('pair_failed');
1250
+ }
1251
+ this.cleanup(response.status);
1252
+ return;
1253
+ }
1254
+ }
1255
+ }
1256
+ // password was accepted (or not needed)
1257
+ this.passwordTried = false;
1258
+ switch (this.status) {
1259
+ case PAIR_PIN_START:
1260
+ if (!this.transient) {
1261
+ this.emit('need_password');
1262
+ }
1263
+ this.status = this.airplay2 ? PAIR_SETUP_1 : PAIR_PIN_SETUP_1;
1264
+ if (!this.transient) {
1265
+ return;
1266
+ }
1267
+ break;
1268
+ case PAIR_PIN_SETUP_1:
1269
+ this.srp = new LegacySRP(2048);
1270
+ this.P = this.password;
1271
+ let bufa = Buffer.from(rawData).slice(rawData.length - parseInt(headers['Content-Length']), rawData.length);
1272
+ const { pk, salt } = bplistParser.parseBuffer(bufa)[0];
1273
+ this.s = salt.toString('hex');
1274
+ this.B = pk.toString('hex');
1275
+ // SRP: Generate random auth_secret, 'a'; if pairing is successful, it'll be utilized in
1276
+ // subsequent session authentication(s).
1277
+ this.a = nodeCrypto.randomBytes(32).toString('hex');
1278
+ // SRP: Compute A and M1.
1279
+ this.A = this.srp.A(this.a);
1280
+ this.M1 = this.srp.M1(this.I, this.P, this.s, this.a, this.B);
1281
+ this.status = PAIR_PIN_SETUP_2;
1282
+ break;
1283
+ case PAIR_PIN_SETUP_2:
1284
+ const { epk, authTag } = ATVAuthenticator.confirm(this.a, this.srp.K(this.I, this.P, this.s, this.a, this.B));
1285
+ this.epk = epk;
1286
+ this.authTag = authTag;
1287
+ this.status = PAIR_PIN_SETUP_3;
1288
+ break;
1289
+ case PAIR_PIN_SETUP_3:
1290
+ this.status = PAIR_VERIFY_1;
1291
+ this.authSecret = this.a;
1292
+ break;
1293
+ case PAIR_VERIFY_1:
1294
+ let buf1 = Buffer.from(rawData).slice(rawData.length - parseInt(headers['Content-Length']), rawData.length);
1295
+ this.logLine?.('verify2', Buffer.byteLength(buf1));
1296
+ if (Buffer.byteLength(buf1) != 0) {
1297
+ const atv_pub = buf1.slice(0, 32).toString('hex');
1298
+ const atv_data = buf1.slice(32).toString('hex');
1299
+ const shared = ATVAuthenticator.shared(this.pair_verify_1_verifier.v_pri, atv_pub);
1300
+ const signed = ATVAuthenticator.signed(this.authSecret, this.pair_verify_1_verifier.v_pub, atv_pub);
1301
+ this.pair_verify_1_signature = Buffer.from(Buffer.from([0x00, 0x00, 0x00, 0x00]).toString('hex') +
1302
+ ATVAuthenticator.signature(shared, atv_data, signed), 'hex');
1303
+ if (this.debug)
1304
+ this.logLine?.('verify2', Buffer.byteLength(this.pair_verify_1_signature));
1305
+ this.status = PAIR_VERIFY_2;
1306
+ }
1307
+ else {
1308
+ this.emit('pair_failed');
1309
+ this.cleanup('pair_failed');
1310
+ return;
1311
+ }
1312
+ break;
1313
+ case PAIR_VERIFY_2:
1314
+ this.status = OPTIONS;
1315
+ break;
1316
+ case PAIR_SETUP_1:
1317
+ let buf2 = Buffer.from(rawData).slice(rawData.length - parseInt(headers['Content-Length']), rawData.length);
1318
+ let databuf1 = tlv.decode(buf2);
1319
+ if (this.debug)
1320
+ this.logLine?.(databuf1);
1321
+ if (databuf1[tlv.Tag.BackOff]) {
1322
+ let backOff = databuf1[tlv.Tag.BackOff];
1323
+ this.logLine?.(backOff);
1324
+ let seconds = backOff.length >= 2 ? Buffer.from(backOff).readInt16LE(0) : 0;
1325
+ this.logLine?.("You've attempt to pair too recently. Try again in " + (seconds) + " seconds.");
1326
+ }
1327
+ if (databuf1[tlv.Tag.ErrorCode]) {
1328
+ let buffer = databuf1[tlv.Tag.ErrorCode];
1329
+ this.logLine?.("Device responded with error code " + Buffer.from(buffer).readIntLE(0, buffer.byteLength) + ". Try rebooting your Apple TV.");
1330
+ }
1331
+ if (databuf1[tlv.Tag.PublicKey]) {
1332
+ this._atv_pub_key = databuf1[tlv.Tag.PublicKey];
1333
+ this._atv_salt = databuf1[tlv.Tag.Salt];
1334
+ this._hap_genkey = nodeCrypto.randomBytes(32);
1335
+ if (this.password == null) {
1336
+ this.password = 3939; // transient
1337
+ }
1338
+ this.srp = new SrpClient(SRP.params.hap, Buffer.from(this._atv_salt), Buffer.from("Pair-Setup"), Buffer.from(this.password.toString()), Buffer.from(this._hap_genkey), true);
1339
+ this.srp.setB(this._atv_pub_key);
1340
+ this.A = this.srp.computeA();
1341
+ this.M1 = this.srp.computeM1();
1342
+ this.status = PAIR_SETUP_2;
1343
+ }
1344
+ else {
1345
+ this.emit('pair_failed');
1346
+ this.cleanup('pair_failed');
1347
+ return;
1348
+ }
1349
+ break;
1350
+ case PAIR_SETUP_2:
1351
+ let buf3 = Buffer.from(rawData).slice(rawData.length - parseInt(headers['Content-Length']), rawData.length);
1352
+ let databuf2 = tlv.decode(buf3);
1353
+ this.deviceProof = databuf2[tlv.Tag.Proof];
1354
+ if (!this.deviceProof) {
1355
+ this.emit('pair_failed');
1356
+ this.cleanup('pair_failed');
1357
+ return;
1358
+ }
1359
+ // this.logLine?.("DEBUG: Device Proof=" + this.deviceProof.toString('hex'));
1360
+ this.srp.checkM2(this.deviceProof);
1361
+ if (this.transient == true) {
1362
+ this.credentials = new Credentials("sdsds", "", "", "", this.seed);
1363
+ this.credentials.writeKey = enc.HKDF("sha512", Buffer.from("Control-Salt"), this.srp.computeK(), Buffer.from("Control-Write-Encryption-Key"), 32);
1364
+ this.credentials.readKey = enc.HKDF("sha512", Buffer.from("Control-Salt"), this.srp.computeK(), Buffer.from("Control-Read-Encryption-Key"), 32);
1365
+ this.encryptedChannel = true;
1366
+ this.logLine?.(this.srp.computeK());
1367
+ this.status = SETUP_AP2_1;
1368
+ }
1369
+ else {
1370
+ this.status = PAIR_SETUP_3;
1371
+ }
1372
+ break;
1373
+ case PAIR_SETUP_3:
1374
+ let buf4 = Buffer.from(rawData).slice(rawData.length - parseInt(headers['Content-Length']), rawData.length);
1375
+ let encryptedData = tlv.decode(buf4)[tlv.Tag.EncryptedData];
1376
+ let cipherText = encryptedData.slice(0, -16);
1377
+ let hmac = encryptedData.slice(-16);
1378
+ let decrpytedData = enc.verifyAndDecrypt(cipherText, hmac, null, Buffer.from('PS-Msg06'), this.encryptionKey);
1379
+ let tlvData = tlv.decode(decrpytedData);
1380
+ this.credentials = new Credentials("sdsds", tlvData[tlv.Tag.Username], this.pairingId, tlvData[tlv.Tag.PublicKey], this.seed);
1381
+ this.status = PAIR_VERIFY_HAP_1;
1382
+ break;
1383
+ case PAIR_VERIFY_HAP_1:
1384
+ let buf5 = Buffer.from(rawData).slice(rawData.length - parseInt(headers['Content-Length']), rawData.length);
1385
+ let decodedData = tlv.decode(buf5);
1386
+ let sessionPublicKey = decodedData[tlv.Tag.PublicKey];
1387
+ let encryptedData1 = decodedData[tlv.Tag.EncryptedData];
1388
+ if (sessionPublicKey.length != 32) {
1389
+ throw new Error(`sessionPublicKey must be 32 bytes (but was ${sessionPublicKey.length})`);
1390
+ }
1391
+ let cipherText1 = encryptedData1.slice(0, -16);
1392
+ let hmac1 = encryptedData1.slice(-16);
1393
+ // let sharedSecret = curve25519.deriveSharedSecret(this.verifyPrivate, sessionPublicKey);
1394
+ let sharedSecret = curve25519_js.sharedKey(this.verifyPrivate, sessionPublicKey);
1395
+ let encryptionKey = enc.HKDF("sha512", Buffer.from("Pair-Verify-Encrypt-Salt"), sharedSecret, Buffer.from("Pair-Verify-Encrypt-Info"), 32);
1396
+ let decryptedData = enc.verifyAndDecrypt(cipherText1, hmac1, null, Buffer.from('PV-Msg02'), encryptionKey);
1397
+ this.verifier_hap_1 = {
1398
+ sessionPublicKey: sessionPublicKey,
1399
+ sharedSecret: sharedSecret,
1400
+ encryptionKey: encryptionKey,
1401
+ pairingData: decryptedData
1402
+ };
1403
+ this.status = PAIR_VERIFY_HAP_2;
1404
+ this.sharedSecret = sharedSecret;
1405
+ break;
1406
+ case PAIR_VERIFY_HAP_2:
1407
+ let buf6 = Buffer.from(rawData).slice(rawData.length - parseInt(headers['Content-Length']), rawData.length);
1408
+ this.credentials.readKey = enc.HKDF("sha512", Buffer.from("Control-Salt"), this.verifier_hap_1.sharedSecret, Buffer.from("Control-Read-Encryption-Key"), 32);
1409
+ this.credentials.writeKey = enc.HKDF("sha512", Buffer.from("Control-Salt"), this.verifier_hap_1.sharedSecret, Buffer.from("Control-Write-Encryption-Key"), 32);
1410
+ if (this.debug) {
1411
+ this.logLine?.('write', this.credentials.writeKey);
1412
+ }
1413
+ if (this.debug) {
1414
+ this.logLine?.('buf6', buf6);
1415
+ }
1416
+ this.encryptedChannel = true;
1417
+ this.status = SETUP_AP2_1;
1418
+ break;
1419
+ case SETUP_AP2_1:
1420
+ this.logLine?.('timing port parsing');
1421
+ let buf7 = Buffer.from(rawData).slice(rawData.length - parseInt(headers['Content-Length']), rawData.length);
1422
+ let sa1_bplist = bplistParser.parseBuffer(buf7);
1423
+ this.eventPort = sa1_bplist[0]['eventPort'];
1424
+ if (sa1_bplist[0]['timingPort'])
1425
+ this.timingDestPort = sa1_bplist[0]['timingPort'];
1426
+ this.logLine?.('timing port ok', sa1_bplist[0]['timingPort']);
1427
+ // let rtspConfig1 = {
1428
+ // audioLatency: 50,
1429
+ // requireEncryption: false,
1430
+ // server_port : 22223,
1431
+ // control_port : this.controlPort,
1432
+ // timing_port : this.timingPort,
1433
+ // event_port: this.eventPort,
1434
+ // credentials : this.credentials
1435
+ // }
1436
+ // this.emit('config', rtspConfig1);
1437
+ // this.eventsocket.bind(3003, this.socket.address().address);
1438
+ this.status = SETPEERS;
1439
+ break;
1440
+ case SETUP_AP2_2:
1441
+ let buf8 = Buffer.from(rawData).slice(rawData.length - parseInt(headers['Content-Length']), rawData.length);
1442
+ let sa2_bplist = bplistParser.parseBuffer(buf8);
1443
+ let rtspConfig = {
1444
+ audioLatency: 50,
1445
+ requireEncryption: false,
1446
+ server_port: sa2_bplist[0]["streams"][0]["dataPort"],
1447
+ control_port: sa2_bplist[0]["streams"][0]["controlPort"],
1448
+ timing_port: this.timingDestPort ? this.timingDestPort : this.timingPort,
1449
+ credentials: this.credentials
1450
+ };
1451
+ this.timingsocket.close();
1452
+ this.controlsocket.close();
1453
+ this.emit('config', rtspConfig);
1454
+ this.logLine?.("goto info");
1455
+ // this.session = 1;
1456
+ this.status = RECORD;
1457
+ // this.emit('ready');
1458
+ break;
1459
+ case SETPEERS:
1460
+ this.status = SETUP_AP2_2;
1461
+ break;
1462
+ case FLUSH:
1463
+ this.status = PLAYING;
1464
+ this.metadataReady = true;
1465
+ this.emit('pair_success');
1466
+ this.session = "1";
1467
+ this.logLine?.("flush");
1468
+ this.emit('ready');
1469
+ // this.logLine?.(sa2_bplist[0]["streams"][0]["controlPort"], sa2_bplist[0]["streams"][0]["dataPort"] )
1470
+ break;
1471
+ case INFO:
1472
+ let buf9 = Buffer.from(rawData).slice(rawData.length - parseInt(headers['Content-Length']), rawData.length);
1473
+ this.status = (this.credentials) ? RECORD : PAIR_SETUP_1;
1474
+ break;
1475
+ case GETVOLUME:
1476
+ this.status = RECORD;
1477
+ break;
1478
+ case AUTH_SETUP:
1479
+ this.status = OPTIONS;
1480
+ break;
1481
+ case OPTIONS:
1482
+ /*
1483
+ * Devices like Apple TV and Zeppelin Air do not support encryption.
1484
+ * Only way of checking that: they do not reply to Apple-Challenge
1485
+ */
1486
+ if (headers['Apple-Response'])
1487
+ this.requireEncryption = true;
1488
+ // this.logLine?.("yeah22332",headers['WWW-Authenticate'],response.code)
1489
+ if (headers['WWW-Authenticate'] != null && response.code === 401) {
1490
+ let auth = headers['WWW-Authenticate'];
1491
+ let realm = parseAuthenticate(auth, 'realm');
1492
+ let nonce = parseAuthenticate(auth, 'nonce');
1493
+ let uri = "*";
1494
+ let user = "iTunes";
1495
+ let methodx = "OPTIONS";
1496
+ let pwd = this.password;
1497
+ let ha1 = md5norm(`${user}:${realm}:${pwd}`);
1498
+ let ha2 = md5norm(`${methodx}:${uri}`);
1499
+ let di_response = md5(`${ha1}:${nonce}:${ha2}`);
1500
+ this.code_digest = `Authorization: Digest username="${user}", realm="${realm}", nonce="${nonce}", uri="${uri}", response="${di_response}" \r\n\r\n`;
1501
+ this.status = OPTIONS2;
1502
+ }
1503
+ else {
1504
+ this.status = this.session ? PLAYING : (this.airplay2 ? PAIR_PIN_START : ANNOUNCE);
1505
+ // if (this.status == ANNOUNCE && response.code === 200){this.emit('pair_success')};
1506
+ }
1507
+ break;
1508
+ case OPTIONS2:
1509
+ /*
1510
+ * Devices like Apple TV and Zeppelin Air do not support encryption.
1511
+ * Only way of checking that: they do not reply to Apple-Challenge
1512
+ */
1513
+ // if(headers['Apple-Response'])
1514
+ // this.requireEncryption = true;
1515
+ if (headers['WWW-Authenticate'] != null && response.code === 401) {
1516
+ let auth = headers['WWW-Authenticate'];
1517
+ let realm = parseAuthenticate(auth, 'realm');
1518
+ let nonce = parseAuthenticate(auth, 'nonce');
1519
+ let uri = "*";
1520
+ let user = "iTunes";
1521
+ let methodx = "OPTIONS";
1522
+ let pwd = this.password;
1523
+ let ha1 = md5(`${user}:${realm}:${pwd}`);
1524
+ let ha2 = md5(`${methodx}:${uri}`);
1525
+ let di_response = md5(`${ha1}:${nonce}:${ha2}`);
1526
+ this.code_digest = `Authorization: Digest username="${user}", realm="${realm}", nonce="${nonce}", uri="${uri}", response="${di_response}" \r\n\r\n`;
1527
+ this.status = OPTIONS3;
1528
+ }
1529
+ else {
1530
+ this.status = this.session ? PLAYING : (this.airplay2 ? SETUP_AP2_1 : ANNOUNCE);
1531
+ // if (this.status == ANNOUNCE && response.code === 200){this.emit('pair_success')}
1532
+ }
1533
+ ;
1534
+ break;
1535
+ case OPTIONS3:
1536
+ this.status = this.session ? PLAYING : (this.airplay2 ? SETUP_AP2_1 : ANNOUNCE);
1537
+ // if (this.status == ANNOUNCE && response.code === 200){this.emit('pair_success')}
1538
+ break;
1539
+ case ANNOUNCE:
1540
+ this.status = (this.airplay2 == true && this.mode == 2) ? PAIR_PIN_START : SETUP;
1541
+ break;
1542
+ case SETUP:
1543
+ this.status = RECORD;
1544
+ this.session = headers['Session'];
1545
+ this.parsePorts(headers);
1546
+ break;
1547
+ case RECORD:
1548
+ this.metadataReady = true;
1549
+ this.emit('pair_success');
1550
+ if (!this.airplay2) {
1551
+ this.session = this.session ?? "1";
1552
+ this.emit('ready');
1553
+ }
1554
+ ;
1555
+ this.status = SETVOLUME;
1556
+ break;
1557
+ case SETVOLUME:
1558
+ if (!this.sentFakeProgess) {
1559
+ this.progress = 10;
1560
+ this.duration = 2000000;
1561
+ this.sentFakeProgess = true;
1562
+ this.status = SETPROGRESS;
1563
+ }
1564
+ else {
1565
+ this.status = PLAYING;
1566
+ }
1567
+ ;
1568
+ break;
1569
+ case SETPROGRESS:
1570
+ this.status = this.airplay2 ? FLUSH : PLAYING;
1571
+ break;
1572
+ case SETDAAP:
1573
+ this.status = PLAYING;
1574
+ break;
1575
+ case SETART:
1576
+ this.status = PLAYING;
1577
+ break;
1578
+ }
1579
+ try {
1580
+ if (this.callback != null) {
1581
+ this.callback();
1582
+ }
1583
+ }
1584
+ catch (e) { }
1585
+ this.sendNextRequest();
1586
+ };
1587
+ Client.prototype.parseObject = function (plist) {
1588
+ if (this.debug)
1589
+ this.logLine?.('plist', plist);
1590
+ };