1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | exports.HAPConnection = exports.HAPConnectionEvent = exports.HAPConnectionState = exports.EventedHTTPServer = exports.EventedHTTPServerEvent = exports.HAPEncryption = void 0;
|
4 | const tslib_1 = require("tslib");
|
5 | const domain_formatter_1 = require("@homebridge/ciao/lib/util/domain-formatter");
|
6 | const assert_1 = tslib_1.__importDefault(require("assert"));
|
7 | const debug_1 = tslib_1.__importDefault(require("debug"));
|
8 | const events_1 = require("events");
|
9 | const http_1 = tslib_1.__importDefault(require("http"));
|
10 | const net_1 = tslib_1.__importDefault(require("net"));
|
11 | const os_1 = tslib_1.__importDefault(require("os"));
|
12 | const hapCrypto = tslib_1.__importStar(require("./hapCrypto"));
|
13 | const net_utils_1 = require("./net-utils");
|
14 | const uuid = tslib_1.__importStar(require("./uuid"));
|
15 | const debug = (0, debug_1.default)("HAP-NodeJS:EventedHTTPServer");
|
16 | const debugCon = (0, debug_1.default)("HAP-NodeJS:EventedHTTPServer:Connection");
|
17 | const debugEvents = (0, debug_1.default)("HAP-NodeJS:EventEmitter");
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 | class HAPEncryption {
|
24 | clientPublicKey;
|
25 | secretKey;
|
26 | publicKey;
|
27 | sharedSecret;
|
28 | hkdfPairEncryptionKey;
|
29 | accessoryToControllerCount = 0;
|
30 | controllerToAccessoryCount = 0;
|
31 | accessoryToControllerKey;
|
32 | controllerToAccessoryKey;
|
33 | incompleteFrame;
|
34 | constructor(clientPublicKey, secretKey, publicKey, sharedSecret, hkdfPairEncryptionKey) {
|
35 | this.clientPublicKey = clientPublicKey;
|
36 | this.secretKey = secretKey;
|
37 | this.publicKey = publicKey;
|
38 | this.sharedSecret = sharedSecret;
|
39 | this.hkdfPairEncryptionKey = hkdfPairEncryptionKey;
|
40 | this.accessoryToControllerKey = Buffer.alloc(0);
|
41 | this.controllerToAccessoryKey = Buffer.alloc(0);
|
42 | }
|
43 | }
|
44 | exports.HAPEncryption = HAPEncryption;
|
45 |
|
46 |
|
47 |
|
48 | var EventedHTTPServerEvent;
|
49 | (function (EventedHTTPServerEvent) {
|
50 | EventedHTTPServerEvent["LISTENING"] = "listening";
|
51 | EventedHTTPServerEvent["CONNECTION_OPENED"] = "connection-opened";
|
52 | EventedHTTPServerEvent["REQUEST"] = "request";
|
53 | EventedHTTPServerEvent["CONNECTION_CLOSED"] = "connection-closed";
|
54 | })(EventedHTTPServerEvent || (exports.EventedHTTPServerEvent = EventedHTTPServerEvent = {}));
|
55 |
|
56 |
|
57 |
|
58 |
|
59 |
|
60 |
|
61 |
|
62 |
|
63 |
|
64 |
|
65 |
|
66 |
|
67 |
|
68 |
|
69 |
|
70 |
|
71 |
|
72 |
|
73 |
|
74 |
|
75 | class EventedHTTPServer extends events_1.EventEmitter {
|
76 | static CONNECTION_TIMEOUT_LIMIT = 16;
|
77 | static MAX_CONNECTION_IDLE_TIME = 60 * 60 * 1000;
|
78 | tcpServer;
|
79 | |
80 |
|
81 |
|
82 | connections = new Set();
|
83 | |
84 |
|
85 |
|
86 |
|
87 | connectionsByUsername = new Map();
|
88 | connectionIdleTimeout;
|
89 | connectionLoggingInterval;
|
90 | constructor() {
|
91 | super();
|
92 | this.tcpServer = net_1.default.createServer();
|
93 | }
|
94 | scheduleNextConnectionIdleTimeout() {
|
95 | this.connectionIdleTimeout = undefined;
|
96 | if (!this.tcpServer.listening) {
|
97 | return;
|
98 | }
|
99 | debug("Running idle timeout timer...");
|
100 | const currentTime = new Date().getTime();
|
101 | let nextTimeout = -1;
|
102 | for (const connection of this.connections) {
|
103 | const timeDelta = currentTime - connection.lastSocketOperation;
|
104 | if (timeDelta >= EventedHTTPServer.MAX_CONNECTION_IDLE_TIME) {
|
105 | debug("[%s] Closing connection as it was inactive for " + timeDelta + "ms");
|
106 | connection.close();
|
107 | }
|
108 | else {
|
109 | nextTimeout = Math.max(nextTimeout, EventedHTTPServer.MAX_CONNECTION_IDLE_TIME - timeDelta);
|
110 | }
|
111 | }
|
112 | if (this.connections.size >= EventedHTTPServer.CONNECTION_TIMEOUT_LIMIT) {
|
113 | this.connectionIdleTimeout = setTimeout(this.scheduleNextConnectionIdleTimeout.bind(this), nextTimeout);
|
114 | }
|
115 | }
|
116 | address() {
|
117 | return this.tcpServer.address();
|
118 | }
|
119 | listen(targetPort, hostname) {
|
120 | this.tcpServer.listen(targetPort, hostname, () => {
|
121 | const address = this.tcpServer.address();
|
122 | debug("Server listening on %s:%s", address.family === "IPv6" ? `[${address.address}]` : address.address, address.port);
|
123 | this.connectionLoggingInterval = setInterval(() => {
|
124 | const connectionInformation = [...this.connections]
|
125 | .map(connection => `${connection.remoteAddress}:${connection.remotePort}`)
|
126 | .join(", ");
|
127 | debug("Currently %d hap connections open: %s", this.connections.size, connectionInformation);
|
128 | }, 60_000);
|
129 | this.connectionLoggingInterval.unref();
|
130 | this.emit("listening" , address.port, address.address);
|
131 | });
|
132 | this.tcpServer.on("connection", this.onConnection.bind(this));
|
133 | }
|
134 | stop() {
|
135 | if (this.connectionLoggingInterval != null) {
|
136 | clearInterval(this.connectionLoggingInterval);
|
137 | this.connectionLoggingInterval = undefined;
|
138 | }
|
139 | if (this.connectionIdleTimeout != null) {
|
140 | clearTimeout(this.connectionIdleTimeout);
|
141 | this.connectionIdleTimeout = undefined;
|
142 | }
|
143 | this.tcpServer.close();
|
144 | for (const connection of this.connections) {
|
145 | connection.close();
|
146 | }
|
147 | }
|
148 | destroy() {
|
149 | this.stop();
|
150 | this.removeAllListeners();
|
151 | }
|
152 | |
153 |
|
154 |
|
155 |
|
156 |
|
157 |
|
158 |
|
159 |
|
160 |
|
161 |
|
162 |
|
163 | broadcastEvent(aid, iid, value, originator, immediateDelivery) {
|
164 | for (const connection of this.connections) {
|
165 | if (connection === originator) {
|
166 | debug("[%s] Muting event '%s' notification for this connection since it originated here.", connection.remoteAddress, aid + "." + iid);
|
167 | continue;
|
168 | }
|
169 | connection.sendEvent(aid, iid, value, immediateDelivery);
|
170 | }
|
171 | }
|
172 | onConnection(socket) {
|
173 |
|
174 | const connection = new HAPConnection(this, socket);
|
175 | connection.on("request" , (request, response) => {
|
176 | this.emit("request" , connection, request, response);
|
177 | });
|
178 | connection.on("authenticated" , this.handleConnectionAuthenticated.bind(this, connection));
|
179 | connection.on("closed" , this.handleConnectionClose.bind(this, connection));
|
180 | this.connections.add(connection);
|
181 | debug("[%s] New connection from client on interface %s (%s)", connection.remoteAddress, connection.networkInterface, connection.localAddress);
|
182 | this.emit("connection-opened" , connection);
|
183 | if (this.connections.size >= EventedHTTPServer.CONNECTION_TIMEOUT_LIMIT && !this.connectionIdleTimeout) {
|
184 | this.scheduleNextConnectionIdleTimeout();
|
185 | }
|
186 | }
|
187 | handleConnectionAuthenticated(connection, username) {
|
188 | const connections = this.connectionsByUsername.get(username);
|
189 | if (!connections) {
|
190 | this.connectionsByUsername.set(username, [connection]);
|
191 | }
|
192 | else if (!connections.includes(connection)) {
|
193 | connections.push(connection);
|
194 | }
|
195 | }
|
196 | handleConnectionClose(connection) {
|
197 | this.emit("connection-closed" , connection);
|
198 | this.connections.delete(connection);
|
199 | if (connection.username) {
|
200 | const connections = this.connectionsByUsername.get(connection.username);
|
201 | if (connections) {
|
202 | const index = connections.indexOf(connection);
|
203 | if (index !== -1) {
|
204 | connections.splice(index, 1);
|
205 | }
|
206 | if (connections.length === 0) {
|
207 | this.connectionsByUsername.delete(connection.username);
|
208 | }
|
209 | }
|
210 | }
|
211 | }
|
212 | |
213 |
|
214 |
|
215 |
|
216 |
|
217 |
|
218 |
|
219 |
|
220 |
|
221 |
|
222 | static destroyExistingConnectionsAfterUnpair(initiator, username) {
|
223 | const connections = initiator.server.connectionsByUsername.get(username);
|
224 | if (connections) {
|
225 | for (const connection of connections) {
|
226 | connection.closeConnectionAsOfUnpair(initiator);
|
227 | }
|
228 | }
|
229 | }
|
230 | }
|
231 | exports.EventedHTTPServer = EventedHTTPServer;
|
232 |
|
233 |
|
234 |
|
235 |
|
236 | var HAPConnectionState;
|
237 | (function (HAPConnectionState) {
|
238 | HAPConnectionState[HAPConnectionState["CONNECTING"] = 0] = "CONNECTING";
|
239 | HAPConnectionState[HAPConnectionState["FULLY_SET_UP"] = 1] = "FULLY_SET_UP";
|
240 | HAPConnectionState[HAPConnectionState["AUTHENTICATED"] = 2] = "AUTHENTICATED";
|
241 |
|
242 |
|
243 | HAPConnectionState[HAPConnectionState["TO_BE_TEARED_DOWN"] = 3] = "TO_BE_TEARED_DOWN";
|
244 | HAPConnectionState[HAPConnectionState["CLOSING"] = 4] = "CLOSING";
|
245 | HAPConnectionState[HAPConnectionState["CLOSED"] = 5] = "CLOSED";
|
246 | })(HAPConnectionState || (exports.HAPConnectionState = HAPConnectionState = {}));
|
247 |
|
248 |
|
249 |
|
250 | var HAPConnectionEvent;
|
251 | (function (HAPConnectionEvent) {
|
252 | HAPConnectionEvent["REQUEST"] = "request";
|
253 | HAPConnectionEvent["AUTHENTICATED"] = "authenticated";
|
254 | HAPConnectionEvent["CLOSED"] = "closed";
|
255 | })(HAPConnectionEvent || (exports.HAPConnectionEvent = HAPConnectionEvent = {}));
|
256 |
|
257 |
|
258 |
|
259 |
|
260 |
|
261 | class HAPConnection extends events_1.EventEmitter {
|
262 | |
263 |
|
264 |
|
265 | server;
|
266 | sessionID;
|
267 | state = 0 ;
|
268 | localAddress;
|
269 | remoteAddress;
|
270 | remotePort;
|
271 | networkInterface;
|
272 | tcpSocket;
|
273 | internalHttpServer;
|
274 | httpSocket;
|
275 | internalHttpServerPort;
|
276 | internalHttpServerAddress;
|
277 | lastSocketOperation = new Date().getTime();
|
278 | pendingClientSocketData = Buffer.alloc(0);
|
279 | handlingRequest = false;
|
280 | username;
|
281 | encryption;
|
282 | srpServer;
|
283 | _pairSetupState;
|
284 | _pairVerifyState;
|
285 | registeredEvents = new Set();
|
286 | eventsTimer;
|
287 | queuedEvents = [];
|
288 | |
289 |
|
290 |
|
291 | eventsQueuedForImmediateDelivery = false;
|
292 | timedWritePid;
|
293 | timedWriteTimeout;
|
294 | constructor(server, clientSocket) {
|
295 | super();
|
296 | this.server = server;
|
297 | this.sessionID = uuid.generate(clientSocket.remoteAddress + ":" + clientSocket.remotePort);
|
298 | this.localAddress = clientSocket.localAddress;
|
299 | this.remoteAddress = clientSocket.remoteAddress;
|
300 | this.remotePort = clientSocket.remotePort;
|
301 | this.networkInterface = HAPConnection.getLocalNetworkInterface(clientSocket);
|
302 |
|
303 | this.tcpSocket = clientSocket;
|
304 | this.tcpSocket.on("data", this.onTCPSocketData.bind(this));
|
305 | this.tcpSocket.on("close", this.onTCPSocketClose.bind(this));
|
306 |
|
307 | this.tcpSocket.on("error", this.onTCPSocketError.bind(this));
|
308 | this.tcpSocket.setNoDelay(true);
|
309 |
|
310 |
|
311 |
|
312 | this.internalHttpServer = http_1.default.createServer();
|
313 | this.internalHttpServer.timeout = 0;
|
314 | this.internalHttpServer.keepAliveTimeout = 0;
|
315 | this.internalHttpServer.on("listening", this.onHttpServerListening.bind(this));
|
316 | this.internalHttpServer.on("request", this.handleHttpServerRequest.bind(this));
|
317 | this.internalHttpServer.on("error", this.onHttpServerError.bind(this));
|
318 |
|
319 | this.internalHttpServer.listen(0, this.internalHttpServerAddress = (0, net_utils_1.getOSLoopbackAddressIfAvailable)());
|
320 | }
|
321 | debugListenerRegistration(event, registration = true, beforeCount = -1) {
|
322 | const stackTrace = new Error().stack.split("\n")[3];
|
323 | const eventCount = this.listeners(event).length;
|
324 | const tabs1 = event === "authenticated" ? "\t" : "\t\t";
|
325 | const tabs2 = !registration ? "\t" : "\t\t";
|
326 |
|
327 | debugEvents(`[${this.remoteAddress}] ${registration ? "Registered" : "Unregistered"} event '${String(event).toUpperCase()}' ${tabs1}(total: ${eventCount}${!registration ? " Before: " + beforeCount : ""}) ${tabs2}${stackTrace}`);
|
328 | }
|
329 |
|
330 | on(event, listener) {
|
331 | const result = super.on(event, listener);
|
332 | this.debugListenerRegistration(event);
|
333 | return result;
|
334 | }
|
335 |
|
336 | addListener(event, listener) {
|
337 | const result = super.addListener(event, listener);
|
338 | this.debugListenerRegistration(event);
|
339 | return result;
|
340 | }
|
341 |
|
342 | removeListener(event, listener) {
|
343 | const beforeCount = this.listeners(event).length;
|
344 | const result = super.removeListener(event, listener);
|
345 | this.debugListenerRegistration(event, false, beforeCount);
|
346 | return result;
|
347 | }
|
348 |
|
349 | off(event, listener) {
|
350 | const result = super.off(event, listener);
|
351 | const beforeCount = this.listeners(event).length;
|
352 | this.debugListenerRegistration(event, false, beforeCount);
|
353 | return result;
|
354 | }
|
355 | |
356 |
|
357 |
|
358 |
|
359 |
|
360 |
|
361 |
|
362 | connectionAuthenticated(username) {
|
363 | this.state = 2 ;
|
364 | this.username = username;
|
365 | this.emit("authenticated" , username);
|
366 | }
|
367 | isAuthenticated() {
|
368 | return this.state === 2 ;
|
369 | }
|
370 | close() {
|
371 | if (this.state >= 4 ) {
|
372 | return;
|
373 | }
|
374 | this.state = 4 ;
|
375 | this.tcpSocket.destroy();
|
376 | }
|
377 | closeConnectionAsOfUnpair(initiator) {
|
378 | if (this === initiator) {
|
379 |
|
380 |
|
381 | this.state = 3 ;
|
382 | }
|
383 | else {
|
384 |
|
385 | this.close();
|
386 | }
|
387 | }
|
388 | sendEvent(aid, iid, value, immediateDelivery) {
|
389 | (0, assert_1.default)(aid != null, "HAPConnection.sendEvent: aid must be defined!");
|
390 | (0, assert_1.default)(iid != null, "HAPConnection.sendEvent: iid must be defined!");
|
391 | const eventName = aid + "." + iid;
|
392 | if (!this.registeredEvents.has(eventName)) {
|
393 |
|
394 | return;
|
395 | }
|
396 | const event = {
|
397 | aid: aid,
|
398 | iid: iid,
|
399 | value: value,
|
400 | };
|
401 | if (immediateDelivery) {
|
402 |
|
403 |
|
404 | this.queuedEvents.push(event);
|
405 | this.eventsQueuedForImmediateDelivery = true;
|
406 | if (this.eventsTimer) {
|
407 | clearTimeout(this.eventsTimer);
|
408 | this.eventsTimer = undefined;
|
409 | }
|
410 | this.handleEventsTimeout();
|
411 | return;
|
412 | }
|
413 |
|
414 |
|
415 |
|
416 | for (let i = this.queuedEvents.length - 1; i >= 0; i--) {
|
417 | const queuedEvent = this.queuedEvents[i];
|
418 | if (queuedEvent.aid === aid && queuedEvent.iid === iid) {
|
419 | if (queuedEvent.value === value) {
|
420 | return;
|
421 | }
|
422 | break;
|
423 | }
|
424 | }
|
425 | this.queuedEvents.push(event);
|
426 |
|
427 | if (!this.eventsTimer) {
|
428 | this.eventsTimer = setTimeout(this.handleEventsTimeout.bind(this), 250);
|
429 | this.eventsTimer.unref();
|
430 | }
|
431 | }
|
432 | handleEventsTimeout() {
|
433 | this.eventsTimer = undefined;
|
434 | if (this.state > 2 ) {
|
435 |
|
436 | return;
|
437 | }
|
438 | this.writeQueuedEventNotifications();
|
439 | }
|
440 | writeQueuedEventNotifications() {
|
441 | if (this.queuedEvents.length === 0 || this.handlingRequest) {
|
442 | return;
|
443 | }
|
444 | if (this.eventsTimer) {
|
445 |
|
446 | clearTimeout(this.eventsTimer);
|
447 | this.eventsTimer = undefined;
|
448 | }
|
449 | const eventData = {
|
450 | characteristics: [],
|
451 | };
|
452 | for (const queuedEvent of this.queuedEvents) {
|
453 | if (!this.registeredEvents.has(queuedEvent.aid + "." + queuedEvent.iid)) {
|
454 | continue;
|
455 | }
|
456 | eventData.characteristics.push(queuedEvent);
|
457 | }
|
458 | this.queuedEvents.splice(0, this.queuedEvents.length);
|
459 | this.eventsQueuedForImmediateDelivery = false;
|
460 | this.writeEventNotification(eventData);
|
461 | }
|
462 | |
463 |
|
464 |
|
465 |
|
466 |
|
467 |
|
468 |
|
469 | writeEventNotification(notification) {
|
470 | debugCon("[%s] Sending HAP event notifications %o", this.remoteAddress, notification.characteristics);
|
471 | (0, assert_1.default)(!this.handlingRequest, "Can't write event notifications while handling a request!");
|
472 |
|
473 |
|
474 | notification.characteristics.reverse();
|
475 | const dataBuffer = Buffer.from(JSON.stringify(notification), "utf8");
|
476 | const header = Buffer.from("EVENT/1.0 200 OK\r\n" +
|
477 | "Content-Type: application/hap+json\r\n" +
|
478 | "Content-Length: " + dataBuffer.length + "\r\n" +
|
479 | "\r\n", "utf8");
|
480 | const buffer = Buffer.concat([header, dataBuffer]);
|
481 | this.tcpSocket.write(this.encrypt(buffer), this.handleTCPSocketWriteFulfilled.bind(this));
|
482 | }
|
483 | enableEventNotifications(aid, iid) {
|
484 | this.registeredEvents.add(aid + "." + iid);
|
485 | }
|
486 | disableEventNotifications(aid, iid) {
|
487 | this.registeredEvents.delete(aid + "." + iid);
|
488 | }
|
489 | hasEventNotifications(aid, iid) {
|
490 | return this.registeredEvents.has(aid + "." + iid);
|
491 | }
|
492 | getRegisteredEvents() {
|
493 | return this.registeredEvents;
|
494 | }
|
495 | clearRegisteredEvents() {
|
496 | this.registeredEvents.clear();
|
497 | }
|
498 | encrypt(data) {
|
499 |
|
500 |
|
501 |
|
502 |
|
503 | if (this.encryption && this.encryption.accessoryToControllerKey.length > 0 && this.encryption.controllerToAccessoryCount > 0) {
|
504 | return hapCrypto.layerEncrypt(data, this.encryption);
|
505 | }
|
506 | return data;
|
507 | }
|
508 | decrypt(data) {
|
509 | if (this.encryption && this.encryption.controllerToAccessoryKey.length > 0) {
|
510 |
|
511 | return hapCrypto.layerDecrypt(data, this.encryption);
|
512 | }
|
513 | return data;
|
514 | }
|
515 | onHttpServerListening() {
|
516 | const addressInfo = this.internalHttpServer.address();
|
517 | const addressString = addressInfo.family === "IPv6" ? `[${addressInfo.address}]` : addressInfo.address;
|
518 | this.internalHttpServerPort = addressInfo.port;
|
519 | debugCon("[%s] Internal HTTP server listening on %s:%s", this.remoteAddress, addressString, addressInfo.port);
|
520 | this.internalHttpServer.on("close", this.onHttpServerClose.bind(this));
|
521 |
|
522 | this.httpSocket = net_1.default.createConnection(this.internalHttpServerPort, this.internalHttpServerAddress);
|
523 | this.httpSocket.setNoDelay(true);
|
524 | this.httpSocket.on("data", this.handleHttpServerResponse.bind(this));
|
525 |
|
526 | this.httpSocket.on("error", this.onHttpSocketError.bind(this));
|
527 | this.httpSocket.on("close", this.onHttpSocketClose.bind(this));
|
528 | this.httpSocket.on("connect", () => {
|
529 |
|
530 |
|
531 |
|
532 |
|
533 | this.state = 1 ;
|
534 | debugCon("[%s] Internal HTTP socket connected. HAPConnection now fully set up!", this.remoteAddress);
|
535 |
|
536 | if (this.pendingClientSocketData && this.pendingClientSocketData.length > 0) {
|
537 | this.httpSocket.write(this.pendingClientSocketData);
|
538 | }
|
539 | this.pendingClientSocketData = undefined;
|
540 | });
|
541 | }
|
542 | |
543 |
|
544 |
|
545 |
|
546 | onTCPSocketData(data) {
|
547 | if (this.state > 2 ) {
|
548 |
|
549 | return;
|
550 | }
|
551 | this.handlingRequest = true;
|
552 | this.lastSocketOperation = new Date().getTime();
|
553 | try {
|
554 | data = this.decrypt(data);
|
555 | }
|
556 | catch (error) {
|
557 | debugCon("[%s] Error occurred trying to decrypt incoming packet: %s", this.remoteAddress, error.message);
|
558 | this.close();
|
559 | return;
|
560 | }
|
561 | if (this.state < 1 ) {
|
562 | this.pendingClientSocketData = Buffer.concat([this.pendingClientSocketData, data]);
|
563 | }
|
564 | else {
|
565 | this.httpSocket.write(data);
|
566 | }
|
567 | }
|
568 | |
569 |
|
570 |
|
571 |
|
572 |
|
573 | handleHttpServerRequest(request, response) {
|
574 | if (this.state > 2 ) {
|
575 |
|
576 | return;
|
577 | }
|
578 | debugCon("[%s] HTTP request: %s", this.remoteAddress, request.url);
|
579 | request.socket.setNoDelay(true);
|
580 | this.emit("request" , request, response);
|
581 | }
|
582 | |
583 |
|
584 |
|
585 |
|
586 |
|
587 | handleHttpServerResponse(data) {
|
588 | data = this.encrypt(data);
|
589 | this.tcpSocket.write(data, this.handleTCPSocketWriteFulfilled.bind(this));
|
590 | debugCon("[%s] HTTP Response is finished", this.remoteAddress);
|
591 | this.handlingRequest = false;
|
592 | if (this.state === 3 ) {
|
593 | setTimeout(() => this.close(), 10);
|
594 | }
|
595 | else if (this.state < 3 ) {
|
596 | if (!this.eventsTimer || this.eventsQueuedForImmediateDelivery) {
|
597 |
|
598 |
|
599 | this.writeQueuedEventNotifications();
|
600 | }
|
601 | }
|
602 | }
|
603 | handleTCPSocketWriteFulfilled() {
|
604 | this.lastSocketOperation = new Date().getTime();
|
605 | }
|
606 | onTCPSocketError(err) {
|
607 | debugCon("[%s] Client connection error: %s", this.remoteAddress, err.message);
|
608 |
|
609 | }
|
610 | onTCPSocketClose() {
|
611 | this.state = 5 ;
|
612 | debugCon("[%s] Client connection closed", this.remoteAddress);
|
613 | if (this.httpSocket) {
|
614 | this.httpSocket.destroy();
|
615 | }
|
616 | this.internalHttpServer.close();
|
617 | this.emit("closed" );
|
618 | this.removeAllListeners();
|
619 | }
|
620 | onHttpServerError(err) {
|
621 | debugCon("[%s] HTTP server error: %s", this.remoteAddress, err.message);
|
622 | if (err.code === "EADDRINUSE") {
|
623 | this.internalHttpServerPort = undefined;
|
624 | this.internalHttpServer.close();
|
625 | this.internalHttpServer.listen(0, this.internalHttpServerAddress = (0, net_utils_1.getOSLoopbackAddressIfAvailable)());
|
626 | }
|
627 | }
|
628 | onHttpServerClose() {
|
629 | debugCon("[%s] HTTP server was closed", this.remoteAddress);
|
630 |
|
631 | this.close();
|
632 | }
|
633 | onHttpSocketError(err) {
|
634 | debugCon("[%s] HTTP connection error: ", this.remoteAddress, err.message);
|
635 |
|
636 | }
|
637 | onHttpSocketClose() {
|
638 | debugCon("[%s] HTTP connection was closed", this.remoteAddress);
|
639 |
|
640 |
|
641 | this.internalHttpServer.close();
|
642 | }
|
643 | getLocalAddress(ipVersion) {
|
644 | const interfaceDetails = os_1.default.networkInterfaces()[this.networkInterface];
|
645 | if (!interfaceDetails) {
|
646 | throw new Error("Could not find " + ipVersion + " address for interface " + this.networkInterface);
|
647 | }
|
648 |
|
649 | if (ipVersion === "ipv4") {
|
650 | const ipv4Info = interfaceDetails.find(info => info.family === "IPv4");
|
651 | if (ipv4Info) {
|
652 | return ipv4Info.address;
|
653 | }
|
654 | throw new Error("Could not find " + ipVersion + " address for interface " + this.networkInterface + ".");
|
655 | }
|
656 | let localUniqueAddress;
|
657 | for (const v6entry of interfaceDetails.filter(entry => entry.family === "IPv6")) {
|
658 | if (!v6entry.scopeid) {
|
659 | return v6entry.address;
|
660 | }
|
661 | localUniqueAddress ??= v6entry.address;
|
662 | }
|
663 | if (localUniqueAddress) {
|
664 | return localUniqueAddress;
|
665 | }
|
666 | throw new Error("Could not find " + ipVersion + " address for interface " + this.networkInterface);
|
667 | }
|
668 | static getLocalNetworkInterface(socket) {
|
669 | let localAddress = socket.localAddress;
|
670 |
|
671 | const interfaces = os_1.default.networkInterfaces();
|
672 |
|
673 | const defaultInterface = () => Object.entries(interfaces).find(([, addresses]) => addresses?.some(address => !address.internal))?.[0] ?? "unknown";
|
674 |
|
675 | if (!localAddress) {
|
676 | return defaultInterface();
|
677 | }
|
678 |
|
679 | localAddress = localAddress.replace(/^::ffff:/i, "");
|
680 |
|
681 | if (/^::(?:\d{1,3}\.){3}\d{1,3}$/.test(localAddress)) {
|
682 | localAddress = localAddress.replace(/^::/, "");
|
683 | }
|
684 |
|
685 | localAddress = localAddress.split("%")[0];
|
686 |
|
687 | for (const [name, addresses] of Object.entries(interfaces)) {
|
688 | if (addresses?.some(({ address }) => address === localAddress)) {
|
689 | return name;
|
690 | }
|
691 | }
|
692 |
|
693 | const family = net_1.default.isIPv4(localAddress) ? "IPv4" : "IPv6";
|
694 |
|
695 | for (const [name, addresses] of Object.entries(interfaces)) {
|
696 | if (addresses?.some(entry => entry.family === family && (0, domain_formatter_1.getNetAddress)(localAddress, entry.netmask) === (0, domain_formatter_1.getNetAddress)(entry.address, entry.netmask))) {
|
697 | return name;
|
698 | }
|
699 | }
|
700 | console.log("WARNING: unable to determine which interface to use for socket coming from " + socket.remoteAddress + ":" + socket.remotePort + " to " +
|
701 | socket.localAddress + ".");
|
702 | return defaultInterface();
|
703 | }
|
704 | }
|
705 | exports.HAPConnection = HAPConnection;
|
706 |
|
\ | No newline at end of file |