Skip to content
File

Blob: src/node/internal/internal_net.ts

typescript2321 lines
1// Copyright (c) 2017-2022 Cloudflare, Inc.
2// Licensed under the Apache 2.0 license found in the LICENSE file or at:
3// https://opensource.org/licenses/Apache-2.0
4//
5// Copyright Joyent, Inc. and other Node contributors.
6//
7// Permission is hereby granted, free of charge, to any person obtaining a
8// copy of this software and associated documentation files (the
9// "Software"), to deal in the Software without restriction, including
10// without limitation the rights to use, copy, modify, merge, publish,
11// distribute, sublicense, and/or sell copies of the Software, and to permit
12// persons to whom the Software is furnished to do so, subject to the
13// following conditions:
14//
15// The above copyright notice and this permission notice shall be included
16// in all copies or substantial portions of the Software.
17//
18// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
19// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
21// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
22// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
23// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
24// USE OR OTHER DEALINGS IN THE SOFTWARE.
25 
26/* eslint-disable @typescript-eslint/no-empty-object-type */
27 
28import inner from 'cloudflare-internal:sockets';
29 
30import {
31 AbortError,
32 ERR_INVALID_ARG_VALUE,
33 ERR_INVALID_ARG_TYPE,
34 ERR_MISSING_ARGS,
35 ERR_OUT_OF_RANGE,
36 ERR_OPTION_NOT_IMPLEMENTED,
37 ERR_SOCKET_CLOSED,
38 ERR_SOCKET_CLOSED_BEFORE_CONNECTION,
39 ERR_SOCKET_CONNECTING,
40 ERR_INVALID_IP_ADDRESS,
41 ERR_INVALID_ADDRESS,
42 EPIPE,
43} from 'node-internal:internal_errors';
44 
45import {
46 validateAbortSignal,
47 validateArray,
48 validateFunction,
49 validateInt32,
50 validateNumber,
51 validatePort,
52 validateObject,
53 validateOneOf,
54 validateBoolean,
55 validateString,
56 validateUint32,
57} from 'node-internal:validators';
58 
59import { isUint8Array, isArrayBufferView } from 'node-internal:internal_types';
60import { Duplex } from 'node-internal:streams_duplex';
61import { Buffer } from 'node-internal:internal_buffer';
62import {
63 kDestroyed,
64 kIsReadable,
65 kIsWritable,
66} from 'node-internal:streams_util';
67import type {
68 IpcSocketConnectOpts,
69 SocketConnectOpts,
70 TcpSocketConnectOpts,
71 AddressInfo,
72 Socket as _Socket,
73 SocketAddress as _SocketAddress,
74 SocketAddressInitOptions,
75 IPVersion,
76 OnReadOpts,
77} from 'node:net';
78import type { InspectOptions } from 'node:util';
79import type { Writable } from 'node:stream';
80import { JSStreamSocket } from 'node-internal:internal_tls_jsstream';
81 
82import { inspect } from 'node-internal:internal_inspect';
83import type { FixedLengthArray } from 'node-internal:internal_utils';
84 
85const kInspect = inspect.custom;
86 
87const kLastWriteQueueSize = Symbol('kLastWriteQueueSize');
88const kTimeout = Symbol('kTimeout');
89const kBuffer = Symbol('kBuffer');
90const kBufferCb = Symbol('kBufferCb');
91const kBufferGen = Symbol('kBufferGen');
92const kBytesRead = Symbol('kBytesRead');
93const kBytesWritten = Symbol('kBytesWritten');
94const kUpdateTimer = Symbol('kUpdateTimer');
95export const normalizedArgsSymbol = Symbol('normalizedArgs');
96export const kReinitializeHandle = Symbol('kReinitializeHandle');
97 
98// Once the socket has been opened, the socket info provided by the
99// socket.opened promise will be stored here.
100const kSocketInfo = Symbol('kSocketInfo');
101 
102// IPv4 Segment
103const v4Seg = '(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])';
104const v4Str = `(?:${v4Seg}\\.){3}${v4Seg}`;
105const IPv4Reg = new RegExp(`^${v4Str}$`);
106 
107// IPv6 Segment
108const v6Seg = '(?:[0-9a-fA-F]{1,4})';
109const IPv6Reg = new RegExp(
110 '^(?:' +
111 `(?:${v6Seg}:){7}(?:${v6Seg}|:)|` +
112 `(?:${v6Seg}:){6}(?:${v4Str}|:${v6Seg}|:)|` +
113 `(?:${v6Seg}:){5}(?::${v4Str}|(?::${v6Seg}){1,2}|:)|` +
114 `(?:${v6Seg}:){4}(?:(?::${v6Seg}){0,1}:${v4Str}|(?::${v6Seg}){1,3}|:)|` +
115 `(?:${v6Seg}:){3}(?:(?::${v6Seg}){0,2}:${v4Str}|(?::${v6Seg}){1,4}|:)|` +
116 `(?:${v6Seg}:){2}(?:(?::${v6Seg}){0,3}:${v4Str}|(?::${v6Seg}){1,5}|:)|` +
117 `(?:${v6Seg}:){1}(?:(?::${v6Seg}){0,4}:${v4Str}|(?::${v6Seg}){1,6}|:)|` +
118 `(?::(?:(?::${v6Seg}){0,5}:${v4Str}|(?::${v6Seg}){1,7}|:))` +
119 ')(?:%[0-9a-zA-Z-.:]{1,})?$'
120);
121 
122const TIMEOUT_MAX = 2 ** 31 - 1;
123 
124// ======================================================================================
125 
126export type SocketOptions = {
127 timeout?: number;
128 writable?: boolean;
129 readable?: boolean;
130 decodeStrings?: boolean;
131 autoDestroy?: boolean;
132 objectMode?: boolean;
133 readableObjectMode?: boolean;
134 writableObjectMode?: boolean;
135 keepAliveInitialDelay?: number;
136 fd?: number;
137 handle?: Socket['_handle'];
138 noDelay?: boolean;
139 keepAlive?: boolean;
140 allowHalfOpen?: boolean;
141 emitClose?: boolean;
142 signal?: AbortSignal;
143 onread?:
144 | ({ callback?: () => Uint8Array; buffer?: Uint8Array } & OnReadOpts)
145 | null;
146};
147 
148export function Server(): void {
149 throw new Error('Server is not implemented');
150}
151 
152export type SocketWriteData = Array<{
153 chunk: any; // eslint-disable-line @typescript-eslint/no-explicit-any
154 encoding: BufferEncoding;
155}>;
156 
157// @ts-expect-error TS2323 Redeclare error.
158export declare class Socket extends _Socket {
159 timeout: number;
160 connecting: boolean;
161 _aborted: boolean;
162 _hadError: boolean;
163 _parent: null | Socket['_handle'];
164 _parentWrap: null | Socket | JSStreamSocket;
165 _host: null | string;
166 _peername: null | string;
167 _getsockname(): {
168 address?: string;
169 port?: number;
170 family?: string;
171 };
172 [kLastWriteQueueSize]: number | null | undefined;
173 [kTimeout]: Socket | null | undefined;
174 [kBuffer]: null | boolean | Uint8Array;
175 [kBufferCb]:
176 | null
177 | undefined
178 | ((len?: number, buf?: Buffer) => boolean | Uint8Array);
179 [kBufferGen]: null | (() => undefined | Uint8Array);
180 [kSocketInfo]: null | {
181 address?: string;
182 port?: number;
183 family?: number | string;
184 remoteAddress?: Record<string, unknown>;
185 };
186 [kBytesRead]: number;
187 [kBytesWritten]: number;
188 [kReinitializeHandle](handle: Socket['_handle']): void;
189 _closeAfterHandlingError: boolean;
190 _handle: null | {
191 writeQueueSize?: number;
192 lastWriteQueueSize?: number;
193 reading: boolean | undefined;
194 bytesRead: number;
195 bytesWritten: number;
196 socket: ReturnType<typeof inner.connect>;
197 reader: ReadableStreamBYOBReader;
198 writer: WritableStreamDefaultWriter<unknown>;
199 options: {
200 host: string;
201 port: number;
202 addressType: number;
203 };
204 };
205 _sockname?: null | AddressInfo;
206 _onTimeout(): void;
207 _unrefTimer(): void;
208 _writeGeneric(
209 writev: boolean,
210 data: SocketWriteData,
211 encoding: string,
212 cb: (err?: Error) => void
213 ): void;
214 _final(cb: (err?: Error) => void): void;
215 _read(n: number): void;
216 _reset(): void;
217 _getpeername(): Record<string, unknown>;
218 _readableState: undefined;
219 _closed: boolean;
220 writableErrored: boolean;
221 readableErrored: boolean;
222 [kIsReadable]: boolean;
223 [kIsWritable]: boolean;
224 [kDestroyed]: boolean;
225 _writableState: undefined;
226 _bytesDispatched: number;
227 _pendingData: SocketWriteData | null;
228 _pendingEncoding: string;
229 _undestroy(): void;
230 // This should have existed in _Socket type but it's not...
231 writableBuffer?: Writable & {
232 allBuffers: boolean;
233 length: number;
234 };
235 
236 // Defined by TLSSocket
237 encrypted?: boolean;
238 _finishInit(): void;
239 
240 constructor(options?: SocketOptions);
241 prototype: Socket;
242 resetAndClosing?: boolean;
243}
244 
245// @ts-expect-error TS2323 Redeclare error.
246export function Socket(this: Socket, options?: SocketOptions): Socket {
247 if (!(this instanceof Socket)) {
248 return new Socket(options);
249 }
250 
251 if (options?.objectMode) {
252 throw new ERR_INVALID_ARG_VALUE(
253 'options.objectMode',
254 options.objectMode,
255 'is not supported'
256 );
257 } else if (options?.readableObjectMode || options?.writableObjectMode) {
258 throw new ERR_INVALID_ARG_VALUE(
259 `options.${
260 options.readableObjectMode ? 'readableObjectMode' : 'writableObjectMode'
261 }`,
262 options.readableObjectMode || options.writableObjectMode,
263 'is not supported'
264 );
265 }
266 if (options?.keepAliveInitialDelay !== undefined) {
267 validateNumber(
268 options.keepAliveInitialDelay,
269 'options.keepAliveInitialDelay'
270 );
271 
272 if (options.keepAliveInitialDelay < 0) {
273 options.keepAliveInitialDelay = 0;
274 }
275 }
276 
277 if (typeof options === 'number') {
278 options = { fd: options as number };
279 } else {
280 options = { ...options };
281 }
282 
283 if (options.fd !== undefined) {
284 // We are not supporting the options.fd option for now. This is the option
285 // that allows users to pass in a file descriptor to an existing socket.
286 // Workers doesn't have file descriptors and does not use them in any way.
287 throw new ERR_OPTION_NOT_IMPLEMENTED('options.fd');
288 }
289 
290 // We do not support the noDelay and keepAlive options at this
291 // time and will just ignore them if they are passed.
292 //
293 // We shouldn't throw an error for the following validations,
294 // because it breaks packages such as redis.
295 //
296 // if (options.noDelay) {
297 // throw new ERR_OPTION_NOT_IMPLEMENTED('truthy options.noDelay');
298 // }
299 //
300 // if (options.keepAlive) {
301 // throw new ERR_OPTION_NOT_IMPLEMENTED('truthy options.keepAlive');
302 // }
303 
304 options.allowHalfOpen = Boolean(options.allowHalfOpen);
305 // TODO(now): Match behavior with Node.js
306 // In Node.js, emitClose and autoDestroy are false by default so that
307 // the socket must handle those itself, including emitting the close
308 // event with the hadError argument. We should match that behavior.
309 options.emitClose = false;
310 options.autoDestroy = true;
311 options.decodeStrings = false;
312 
313 // In Node.js, these are meaningful when the options.fd is used.
314 // We do not support options.fd so we just ignore whatever value
315 // is given and always pass true.
316 options.readable = true;
317 options.writable = true;
318 
319 this._handle = null;
320 this.connecting = false;
321 this._hadError = false;
322 this._parent = null;
323 this._parentWrap = null;
324 this._host = null;
325 this[kLastWriteQueueSize] = 0;
326 this[kTimeout] = null;
327 this[kBuffer] = null;
328 this[kBufferCb] = null;
329 this[kBufferGen] = null;
330 this[kSocketInfo] = null;
331 this[kBytesRead] = 0;
332 this[kBytesWritten] = 0;
333 this._closeAfterHandlingError = false;
334 // @ts-expect-error TS2540 Required due to types
335 this.autoSelectFamilyAttemptedAddresses = [];
336 
337 this._undestroy();
338 this._sockname = null;
339 this._pendingData = null;
340 this._pendingEncoding = '';
341 
342 // Call Duplex constructor before setting up the abort signal
343 // This ensures the stream methods are properly set up before
344 // any abort handling that might call destroy()
345 Duplex.call(this, options);
346 
347 if (options.handle) {
348 validateObject(options.handle, 'options.handle');
349 this._handle = options.handle;
350 }
351 
352 // We explicitly listen for all 'end' events, not only for once
353 // because Socket class supports reconnection through s.connect();
354 this.on('end', onReadableStreamEnd);
355 
356 const onread = options.onread;
357 if (
358 onread != null &&
359 typeof onread === 'object' &&
360 // The onread.buffer can either be a Uint8Array or a function that returns
361 // a Uint8Array.
362 (isUint8Array(onread.buffer) || typeof onread.buffer === 'function') &&
363 // The onread.callback is the function used to deliver the read buffer to
364 // the application.
365 typeof onread.callback === 'function'
366 ) {
367 if (typeof onread.buffer === 'function') {
368 this[kBuffer] = true;
369 this[kBufferGen] = onread.buffer;
370 } else {
371 this[kBuffer] = onread.buffer;
372 this[kBufferGen] = (): Uint8Array | undefined => onread.buffer;
373 }
374 this[kBufferCb] = onread.callback;
375 } else {
376 this[kBuffer] = true;
377 this[kBufferGen] = (): Uint8Array => new Uint8Array(4096);
378 this[kBufferCb] = undefined;
379 }
380 
381 // Now set up abort signal handling after the Duplex constructor
382 if (options.signal) {
383 addClientAbortSignalOption(this, options.signal);
384 }
385 
386 this[kBytesRead] = 0;
387 this[kBytesWritten] = 0;
388 
389 // TODO(soon): Enable this once blockList is implemented.
390 // if (options.blockList) {
391 // if (!BlockList.isBlockList(options.blockList)) {
392 // throw new ERR_INVALID_ARG_TYPE('options.blockList', 'net.BlockList', options.blockList);
393 // }
394 // this.blockList = options.blockList;
395 // }
396 
397 return this;
398}
399 
400Object.setPrototypeOf(Socket.prototype, Duplex.prototype);
401Object.setPrototypeOf(Socket, Duplex);
402 
403Socket.prototype._unrefTimer = function _unrefTimer(this: Socket): void {
404 // eslint-disable-next-line @typescript-eslint/no-this-alias
405 for (let s: Socket | null = this; s != null; s = s._parentWrap) {
406 if (s[kTimeout] != null) {
407 clearTimeout(s[kTimeout] as unknown as number);
408 s[kTimeout] = this.setTimeout(s.timeout, (): void => {
409 s._onTimeout();
410 });
411 }
412 }
413};
414 
415Socket.prototype.setTimeout = function (
416 this: Socket,
417 msecs: number,
418 callback?: () => void
419): Socket {
420 if (this.destroyed) return this;
421 
422 this.timeout = msecs;
423 
424 // Type checking identical to timers.enroll()
425 msecs = getTimerDuration(msecs, 'msecs');
426 
427 // Attempt to clear an existing timer in both cases -
428 // even if it will be rescheduled we don't want to leak an existing timer.
429 clearTimeout(this[kTimeout] as unknown as number);
430 
431 if (msecs === 0) {
432 if (callback !== undefined) {
433 validateFunction(callback, 'callback');
434 this.removeListener('timeout', callback);
435 }
436 } else {
437 // @ts-expect-error TS2740 Required to not overcomplicate types
438 this[kTimeout] = setTimeout((): void => {
439 this._onTimeout();
440 }, msecs);
441 if (callback !== undefined) {
442 validateFunction(callback, 'callback');
443 this.once('timeout', callback);
444 }
445 }
446 return this;
447};
448 
449Socket.prototype._onTimeout = function (this: Socket): void {
450 const handle = this._handle;
451 const lastWriteQueueSize = this[kLastWriteQueueSize] as number;
452 if (lastWriteQueueSize > 0 && handle) {
453 // `lastWriteQueueSize !== writeQueueSize` means there is
454 // an active write in progress, so we suppress the timeout.
455 const { writeQueueSize } = handle;
456 if (lastWriteQueueSize !== writeQueueSize) {
457 this[kLastWriteQueueSize] = writeQueueSize;
458 this._unrefTimer();
459 return;
460 }
461 }
462 this.emit('timeout');
463};
464 
465Socket.prototype._getpeername = function (
466 this: Socket
467): Record<string, unknown> {
468 if (this._handle == null || this[kSocketInfo] == null) {
469 return {};
470 }
471 
472 return { ...this[kSocketInfo].remoteAddress };
473};
474 
475Socket.prototype._getsockname = function (this: Socket): AddressInfo | {} {
476 if (this._handle == null) {
477 return {};
478 }
479 this._sockname ??= {
480 address: '0.0.0.0',
481 port: 0,
482 family: 'IPv4',
483 };
484 return this._sockname;
485};
486 
487Socket.prototype.address = function (this: Socket): {} | AddressInfo {
488 return this._getsockname();
489};
490 
491// ======================================================================================
492// Writable side ...
493 
494Socket.prototype._writeGeneric = function (
495 this: Socket,
496 writev: boolean,
497 data: SocketWriteData,
498 encoding: string,
499 cb: (err?: Error) => void
500 // eslint-disable-next-line @typescript-eslint/no-invalid-void-type
501): false | void {
502 // If we are still connecting, buffer this for later.
503 // The writable logic will buffer up any more writes while
504 // waiting for this one to be done.
505 try {
506 if (this.connecting) {
507 this._pendingData = data;
508 this._pendingEncoding = encoding;
509 function onClose(): void {
510 cb(new ERR_SOCKET_CLOSED_BEFORE_CONNECTION());
511 }
512 this.once('connect', () => {
513 this.off('close', onClose);
514 this._writeGeneric(writev, data, encoding, cb);
515 });
516 this.once('close', onClose);
517 return;
518 }
519 
520 this._pendingData = null;
521 this._pendingEncoding = '';
522 
523 if (this._handle?.writer === undefined) {
524 cb(new ERR_SOCKET_CLOSED());
525 return false;
526 }
527 
528 this._unrefTimer();
529 
530 let lastWriteSize = 0;
531 if (writev) {
532 // data is an array of strings or ArrayBufferViews. We're going to concat
533 // them all together into a single buffer so we can write all at once. This
534 // trades higher memory use by copying the input buffers for fewer round trips
535 // through the write loop in the stream. Since the write loops involve bouncing
536 // back and forth across the kj event loop boundary and requires reacquiring the
537 // isolate lock after each write, this should be more efficient in the long run.
538 const buffers = [];
539 for (const d of data) {
540 if (typeof d.chunk === 'string') {
541 const buf = Buffer.from(d.chunk, d.encoding);
542 buffers.push(buf);
543 lastWriteSize += buf.byteLength;
544 } else if (isArrayBufferView(d.chunk)) {
545 buffers.push(
546 new Uint8Array(
547 d.chunk.buffer,
548 d.chunk.byteOffset,
549 d.chunk.byteLength
550 )
551 );
552 lastWriteSize += d.chunk.byteLength;
553 } else {
554 throw new ERR_INVALID_ARG_TYPE(
555 'chunk',
556 ['string', 'ArrayBufferView'],
557 d.chunk
558 );
559 }
560 }
561 this._unrefTimer();
562 
563 this._handle.writer.write(Buffer.concat(buffers)).then(
564 () => {
565 if (this._handle != null) {
566 this._handle.bytesWritten += this[kLastWriteQueueSize] ?? 0;
567 } else {
568 this[kBytesWritten] += this[kLastWriteQueueSize] ?? 0;
569 }
570 this[kLastWriteQueueSize] = 0;
571 this._unrefTimer();
572 cb();
573 },
574 (err: unknown): void => {
575 this[kLastWriteQueueSize] = 0;
576 this._unrefTimer();
577 cb(err as Error);
578 }
579 );
580 } else {
581 let bufferData: Buffer;
582 if (typeof data === 'string') {
583 bufferData = Buffer.from(data, encoding);
584 } else {
585 bufferData = data as unknown as Buffer;
586 }
587 this._handle.writer.write(bufferData).then(
588 () => {
589 if (this._handle != null) {
590 this._handle.bytesWritten += this[kLastWriteQueueSize] ?? 0;
591 } else {
592 this[kBytesWritten] += this[kLastWriteQueueSize] ?? 0;
593 }
594 this[kLastWriteQueueSize] = 0;
595 this._unrefTimer();
596 cb();
597 },
598 (err: unknown): void => {
599 this[kLastWriteQueueSize] = 0;
600 this._unrefTimer();
601 
602 // Think of the following code:
603 //
604 // const socket = net.connect(env.SERVER_THAT_DIES_PORT);
605 // socket.on('end', () => {
606 // strictEqual(socket.writable, true);
607 // socket.write('hello world');
608 // resolve();
609 // });
610 //
611 // If we don't omit the error message for CLOSED, socket.write()
612 // will throw an error. This is not compliant with Node.js behavior.
613 if (
614 (err as Error).message !== 'This WritableStream has been closed.'
615 ) {
616 cb(err as Error);
617 } else {
618 cb();
619 }
620 }
621 );
622 lastWriteSize = (data as unknown as Buffer).byteLength;
623 }
624 this[kLastWriteQueueSize] = lastWriteSize;
625 } catch (err) {
626 this.destroy(err as Error);
627 }
628};
629 
630Socket.prototype._writev = function (
631 this: Socket,
632 chunks: SocketWriteData,
633 cb: () => void
634): void {
635 this._writeGeneric(true, chunks, '', cb);
636};
637 
638Socket.prototype._write = function (
639 this: Socket,
640 data: SocketWriteData,
641 encoding: string,
642 cb: (err?: Error) => void
643): void {
644 this._writeGeneric(false, data, encoding, cb);
645};
646 
647Socket.prototype._final = function (
648 this: Socket,
649 cb: (err?: Error) => void
650): void {
651 if (this.connecting) {
652 this.once('connect', () => {
653 this._final(cb);
654 });
655 return;
656 }
657 
658 // If there is no writer, then there's really nothing left to do here.
659 if (this._handle == null) {
660 cb();
661 return;
662 }
663 
664 this._handle.writer.close().then(
665 (): void => {
666 cb();
667 },
668 (err: unknown): void => {
669 cb(err as Error);
670 }
671 );
672};
673 
674Socket.prototype.end = function (
675 this: Socket,
676 // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
677 data?: string | Uint8Array | NodeJS.BufferEncoding | VoidFunction,
678 encoding?: NodeJS.BufferEncoding | VoidFunction,
679 cb?: VoidFunction
680): Socket {
681 // @ts-expect-error this fails after upgrading to @types/node@22.14
682 Duplex.prototype.end.call(this, data, encoding, cb);
683 return this;
684};
685 
686// ======================================================================================
687// Readable side
688 
689Socket.prototype.pause = function (this: Socket): Socket {
690 if (this[kBuffer] && !this.connecting && this._handle?.reading) {
691 // If the read loop is already running, setting reading to false
692 // will interrupt it after the current read completes (if any)
693 if (!this.destroyed) {
694 this._handle.reading = false;
695 }
696 }
697 return Duplex.prototype.pause.call(this) as unknown as Socket;
698};
699 
700Socket.prototype.resume = function (this: Socket): Socket {
701 if (
702 this[kBuffer] &&
703 !this.connecting &&
704 this._handle &&
705 !this._handle.reading
706 ) {
707 tryReadStart(this);
708 }
709 return Duplex.prototype.resume.call(this) as unknown as Socket;
710};
711 
712Socket.prototype.read = function (
713 this: Socket,
714 n: number
715): ReturnType<typeof Duplex.prototype.read> {
716 if (
717 this[kBuffer] &&
718 !this.connecting &&
719 this._handle &&
720 !this._handle.reading
721 ) {
722 tryReadStart(this);
723 }
724 
725 return Duplex.prototype.read.call(this, n);
726};
727 
728Socket.prototype._read = function (this: Socket, n: number): void {
729 if (this.connecting || !this._handle) {
730 this.once('connect', () => {
731 this._read(n);
732 });
733 } else if (!this._handle.reading) {
734 tryReadStart(this);
735 }
736};
737 
738// ======================================================================================
739// Destroy and reset
740 
741Socket.prototype._reset = function (this: Socket): Socket {
742 this.resetAndClosing = true;
743 return this.destroy();
744};
745 
746Socket.prototype.resetAndDestroy = function (this: Socket): Socket {
747 // In Node.js, the resetAndDestroy method is used to "[close] the TCP connection by
748 // sending an RST packet and destroy the stream. If this TCP socket is in connecting
749 // status, it will send an RST packet and destroy this TCP socket once it is connected.
750 // Otherwise, it will call socket.destroy with an ERR_SOCKET_CLOSED Error. If this is
751 // not a TCP socket (for example, a pipe), calling this method will immediately throw
752 // an ERR_INVALID_HANDLE_TYPE Error." In our implementation we really don't have a way
753 // of ensuring whether or not an RST packet is actually sent so this is largely an
754 // alias for the existing destroy. If the socket is still connecting, it will be
755 // destroyed immediately after the connection is established.
756 if (this._handle) {
757 if (this.connecting) {
758 this.once('connect', () => {
759 this._reset();
760 });
761 } else {
762 this._reset();
763 }
764 } else {
765 this.destroy(new ERR_SOCKET_CLOSED());
766 }
767 return this;
768};
769 
770Socket.prototype.destroySoon = function (this: Socket): void {
771 if (this.writable) {
772 this.end();
773 }
774 
775 if (this.writableFinished) {
776 this.destroy();
777 } else {
778 this.once('finish', () => {
779 // Do not call this.destroy.bind(this) since user can override it.
780 this.destroy();
781 });
782 }
783};
784 
785Socket.prototype._destroy = function (
786 this: Socket,
787 exception: Error,
788 cb: (err?: Error) => void
789): void {
790 this.connecting = false;
791 
792 // eslint-disable-next-line @typescript-eslint/no-this-alias
793 for (let s: Socket | null = this; s !== null; s = s._parentWrap) {
794 clearTimeout(s[kTimeout] as unknown as number);
795 }
796 
797 if (this._handle != null) {
798 this._handle.socket.close().then(
799 () => {
800 cleanupAfterDestroy(this, cb, exception);
801 },
802 (err: unknown) => {
803 cleanupAfterDestroy(this, cb, (err || exception) as Error);
804 }
805 );
806 } else {
807 cleanupAfterDestroy(this, cb, exception);
808 }
809};
810 
811// ======================================================================================
812// Connection
813 
814// @ts-expect-error TS2322 Type inconsistencies between types/node
815Socket.prototype.connect = function (
816 this: Socket,
817 ...args: unknown[]
818): Socket | undefined {
819 let normalized;
820 // @ts-expect-error TS7015 Required not to overcomplicate types
821 if (Array.isArray(args[0]) && args[0][normalizedArgsSymbol]) {
822 normalized = args[0];
823 } else {
824 normalized = _normalizeArgs(args);
825 }
826 const options = normalized[0] as TcpSocketConnectOpts & IpcSocketConnectOpts;
827 const cb = normalized[1] as ((err: Error | null) => void) | null;
828 
829 if (this.connecting) {
830 throw new ERR_SOCKET_CONNECTING();
831 }
832 if (this._aborted) {
833 if (cb) {
834 cb(new AbortError());
835 } else {
836 throw new AbortError();
837 }
838 return undefined;
839 }
840 
841 if (cb !== null) {
842 this.once('connect', cb);
843 }
844 
845 if (this._parentWrap?.connecting) {
846 return this;
847 }
848 
849 // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
850 if (options.port === undefined && options.path == null) {
851 throw new ERR_MISSING_ARGS(['options', 'port', 'path']);
852 }
853 
854 if (this.write !== Socket.prototype.write) {
855 // eslint-disable-next-line @typescript-eslint/unbound-method
856 this.write = Socket.prototype.write;
857 }
858 
859 // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
860 if (options.path != null) {
861 throw new ERR_INVALID_ARG_VALUE('path', options.path, 'is not supported');
862 }
863 
864 this[kBytesRead] = 0;
865 this[kBytesWritten] = 0;
866 
867 // This is required for "reconnection".
868 // Previous connected handle needs to emit "close" event.
869 // This ensures that the previous handle is closed before initializing a new one.
870 if (this._handle) {
871 this._handle.socket.close().then(
872 () => {
873 initializeConnection(this, options);
874 },
875 () => {
876 initializeConnection(this, options);
877 }
878 );
879 } else {
880 initializeConnection(this, options);
881 }
882 
883 return this;
884};
885 
886Socket.prototype[kReinitializeHandle] = function reinitializeHandle(
887 handle: Socket['_handle']
888): void {
889 this._handle?.socket.close().then(
890 () => {
891 cleanupAfterDestroy(this);
892 },
893 (err: unknown) => {
894 cleanupAfterDestroy(this, null, err as Error);
895 }
896 );
897 
898 this._handle = handle;
899 this._undestroy();
900 this._sockname = null;
901};
902 
903// ======================================================================================
904// Socket methods that are not no-ops or nominal impls
905 
906Socket.prototype.setNoDelay = function (
907 this: Socket,
908 _enable?: boolean
909): Socket {
910 // Ignore this for now.
911 // Cloudflare connect() does not support this.
912 return this;
913};
914 
915Socket.prototype.setKeepAlive = function (
916 this: Socket,
917 _enable?: boolean,
918 _initialDelay?: number
919): Socket {
920 // Ignore this for now.
921 // This is used by services like mySQL.
922 // TODO(soon): Investigate supporting this.
923 return this;
924};
925 
926// @ts-expect-error TS2322 Intentionally no-op
927Socket.prototype.ref = function (this: Socket): void {
928 // Intentional no-op
929};
930 
931// @ts-expect-error TS2322 Intentionally no-op
932Socket.prototype.unref = function (this: Socket): void {
933 // Intentional no-op
934};
935 
936Object.defineProperties(Socket.prototype, {
937 _connecting: {
938 // @ts-expect-error TS2353 Required for __proto__
939 __proto__: null,
940 get(this: Socket): boolean {
941 return this.connecting;
942 },
943 },
944 pending: {
945 // @ts-expect-error TS2353 Required for __proto__
946 __proto__: null,
947 get(this: Socket): boolean {
948 return !this._handle || this.connecting;
949 },
950 configurable: true,
951 },
952 readyState: {
953 // @ts-expect-error TS2353 Required for __proto__
954 __proto__: null,
955 get(this: Socket): string {
956 if (this.connecting) {
957 return 'opening';
958 } else if (this.readable && this.writable) {
959 return 'open';
960 } else if (this.readable && !this.writable) {
961 return 'readOnly';
962 } else if (!this.readable && this.writable) {
963 return 'writeOnly';
964 }
965 return 'closed';
966 },
967 },
968 bufferSize: {
969 // @ts-expect-error TS2353 Required for __proto__
970 __proto__: null,
971 get(this: Socket): number | undefined {
972 if (this._handle) {
973 return this.writableLength;
974 }
975 return undefined;
976 },
977 },
978 [kUpdateTimer]: {
979 // @ts-expect-error TS2353 Required for __proto__
980 __proto__: null,
981 get(this: Socket): VoidFunction {
982 // eslint-disable-next-line @typescript-eslint/unbound-method
983 return this._unrefTimer;
984 },
985 },
986 bytesRead: {
987 // @ts-expect-error TS2353 Required for __proto__
988 __proto__: null,
989 configurable: false,
990 enumerable: true,
991 get(this: Socket): number {
992 return this._handle ? this._handle.bytesRead : this[kBytesRead];
993 },
994 },
995 _bytesDispatched: {
996 // @ts-expect-error TS2353 Required for __proto__
997 __proto__: null,
998 configurable: false,
999 enumerable: true,
1000 get(this: Socket): number {
1001 return this._handle ? this._handle.bytesWritten : this[kBytesWritten];
1002 },
1003 },
1004 bytesWritten: {
1005 // @ts-expect-error TS2353 Required for __proto__
1006 __proto__: null,
1007 configurable: false,
1008 enumerable: true,
1009 get(this: Socket): number | undefined {
1010 let bytes = this._bytesDispatched;
1011 const data = this._pendingData as unknown as Buffer | string | null;
1012 const encoding = this._pendingEncoding;
1013 const writableBuffer = this.writableBuffer;
1014 
1015 if (!writableBuffer) return undefined;
1016 
1017 if (Array.isArray(data)) {
1018 // Was a writev, iterate over chunks to get total length
1019 for (let i = 0; i < data.length; i++) {
1020 const chunk = data[i] as Buffer | null;
1021 
1022 if (chunk == null) {
1023 continue;
1024 }
1025 
1026 // @ts-expect-error TS2339 allBuffers doesn't exist on type.
1027 if (chunk instanceof Buffer || data.allBuffers) bytes += chunk.length;
1028 else {
1029 bytes += Buffer.byteLength(
1030 // @ts-expect-error TS2339 TODO(soon): Use correct type here.
1031 chunk.chunk as Buffer,
1032 // @ts-expect-error TS2339 TODO(soon): Use correct type here.
1033 chunk.encoding as string
1034 );
1035 }
1036 }
1037 } else if (data) {
1038 // Writes are either a string or a Buffer.
1039 if (typeof data !== 'string') {
1040 bytes += data.length;
1041 } else {
1042 bytes += Buffer.byteLength(data, encoding);
1043 }
1044 } else {
1045 const flushed =
1046 this._handle != null
1047 ? this._handle.bytesWritten
1048 : this[kBytesWritten];
1049 return this.writableLength + flushed;
1050 }
1051 
1052 return bytes;
1053 },
1054 },
1055 remoteAddress: {
1056 // @ts-expect-error TS2353 Required for __proto__
1057 __proto__: null,
1058 configurable: false,
1059 enumerable: true,
1060 get(this: Socket): unknown {
1061 return this._getpeername().address;
1062 },
1063 },
1064 remoteFamily: {
1065 // @ts-expect-error TS2353 Required for __proto__
1066 __proto__: null,
1067 configurable: false,
1068 enumerable: true,
1069 get(this: Socket): unknown {
1070 return this._getpeername().family;
1071 },
1072 },
1073 remotePort: {
1074 // @ts-expect-error TS2353 Required for __proto__
1075 __proto__: null,
1076 configurable: false,
1077 enumerable: true,
1078 get(this: Socket): unknown {
1079 return this._getpeername().port;
1080 },
1081 },
1082 localAddress: {
1083 // @ts-expect-error TS2353 Required for __proto__
1084 __proto__: null,
1085 configurable: false,
1086 enumerable: true,
1087 get(this: Socket): string | undefined {
1088 return this._getsockname().address;
1089 },
1090 },
1091 localPort: {
1092 // @ts-expect-error TS2353 Required for __proto__
1093 __proto__: null,
1094 configurable: false,
1095 enumerable: true,
1096 get(this: Socket): number | undefined {
1097 return this._getsockname().port;
1098 },
1099 },
1100 localFamily: {
1101 // @ts-expect-error TS2353 Required for __proto__
1102 __proto__: null,
1103 configurable: false,
1104 enumerable: true,
1105 get(this: Socket): string | undefined {
1106 return this._getsockname().family;
1107 },
1108 },
1109});
1110 
1111// ======================================================================================
1112// Helper/utility methods
1113 
1114function cleanupAfterDestroy(
1115 socket: Socket,
1116 cb?: ((err?: Error) => void) | null,
1117 error?: Error
1118): void {
1119 const isException = error != null;
1120 if (socket._handle != null) {
1121 socket[kBytesRead] = socket._handle.bytesRead;
1122 socket[kBytesWritten] = socket._handle.bytesWritten;
1123 socket.resetAndClosing = false;
1124 }
1125 socket[kLastWriteQueueSize] = 0;
1126 socket[kSocketInfo] = null;
1127 
1128 // If there's an error, emit it before the close event
1129 if (error != null) {
1130 socket.emit('error', error);
1131 }
1132 
1133 cb?.(error);
1134 socket.emit('close', isException);
1135}
1136 
1137function initializeConnection(
1138 socket: Socket,
1139 options: TcpSocketConnectOpts
1140): void {
1141 const {
1142 host = 'localhost',
1143 family,
1144 hints,
1145 autoSelectFamily,
1146 autoSelectFamilyAttemptTimeout,
1147 lookup,
1148 localAddress,
1149 localPort,
1150 } = options;
1151 let { port } = options;
1152 if (localAddress && !isIP(localAddress)) {
1153 throw new ERR_INVALID_IP_ADDRESS(localAddress);
1154 }
1155 
1156 if (localPort) {
1157 validateNumber(localPort, 'options.localPort');
1158 }
1159 
1160 if (typeof port !== 'number' && typeof port !== 'string') {
1161 throw new ERR_INVALID_ARG_TYPE('options.port', ['number', 'string'], port);
1162 }
1163 validatePort(port);
1164 port |= 0;
1165 
1166 if (autoSelectFamily != null) {
1167 // We don't support this option.
1168 // We shouldn't throw this because services like mongodb depends on it.
1169 // TODO(soon): Investigate supporting this.
1170 validateBoolean(autoSelectFamily, 'options.autoSelectFamily');
1171 }
1172 
1173 if (autoSelectFamilyAttemptTimeout != null) {
1174 // We don't support this option.
1175 // We shouldn't throw this because services like mongodb depends on it.
1176 // TODO(soon): Investigate supporting this.
1177 validateInt32(
1178 autoSelectFamilyAttemptTimeout,
1179 'options.autoSelectFamilyAttemptTimeout',
1180 1
1181 );
1182 }
1183 
1184 socket._unrefTimer();
1185 socket.connecting = true;
1186 
1187 const continueConnection = (
1188 host: unknown,
1189 port: number,
1190 family: number | string
1191 ): void => {
1192 socket[kSocketInfo] = {
1193 remoteAddress: {
1194 address: host,
1195 port,
1196 family: family === 4 ? 'IPv4' : family === 6 ? 'IPv6' : undefined,
1197 },
1198 };
1199 
1200 if (family === 6) {
1201 // The host is an IPv6 address. We need to wrap it in square brackets.
1202 host = `[${host}]`;
1203 }
1204 
1205 // @ts-expect-error TS2540 Unnecessary error due to using @types/node
1206 socket.autoSelectFamilyAttemptedAddresses = [`${host}:${port}`];
1207 
1208 socket.emit('connectionAttempt', host, port, addressType);
1209 
1210 socket._host = `${host}`;
1211 
1212 try {
1213 const handle = inner.connect(`${host}:${port}`, {
1214 allowHalfOpen: socket.allowHalfOpen,
1215 // A Node.js socket is always capable of being upgraded to the TLS socket.
1216 secureTransport: socket.encrypted ? 'on' : 'starttls',
1217 // We are not going to pass the high water-mark here. The outer Node.js
1218 // stream will implement the appropriate backpressure for us.
1219 });
1220 
1221 // Our version of the socket._handle is necessarily different from Node.js'.
1222 // It serves the same purpose but any code that may exist that is depending
1223 // on `_handle` being a particular type (which it shouldn't be) will fail.
1224 socket._handle = {
1225 socket: handle,
1226 writer: handle.writable.getWriter(),
1227 reader: handle.readable.getReader({ mode: 'byob' }),
1228 bytesRead: 0,
1229 bytesWritten: 0,
1230 reading: false,
1231 options: {
1232 host: socket._host,
1233 port,
1234 addressType,
1235 },
1236 };
1237 
1238 // We need to undestroy the stream to connect to it.
1239 socket._undestroy();
1240 socket._sockname = null;
1241 
1242 handle.opened.then(onConnectionOpened.bind(socket), (err: unknown) => {
1243 socket.emit('connectionAttemptFailed', host, port, addressType, err);
1244 socket.destroy(err as Error);
1245 });
1246 
1247 handle.closed.then(
1248 onConnectionClosed.bind(socket),
1249 (error: unknown): void => {
1250 // Do not call socket.destroy.bind(socket) since user can override it.
1251 socket.destroy(error as Error);
1252 }
1253 );
1254 } catch (err) {
1255 socket.destroy(err as Error);
1256 }
1257 };
1258 
1259 if (lookup != null) {
1260 validateFunction(lookup, 'options.lookup');
1261 }
1262 
1263 const addressType = isIP(host);
1264 
1265 // The host is not an IP address. That's allowed in our implementation, but let's
1266 // see if the user provided a lookup function. If not, we'll skip.
1267 if (addressType === 0 && lookup != null) {
1268 // Looks like we have a lookup function! Let's call it. The expectation is that
1269 // the lookup function will produce a good IP address from the non-IP address
1270 // that is given. How that is done is left entirely up to the application code.
1271 // The connection attempt will continue once the lookup function invokes the
1272 // given callback.
1273 const lookupOptions = { family: family || addressType, hints };
1274 lookup(
1275 host,
1276 lookupOptions,
1277 (err: Error | null, address: string, family: number | string): void => {
1278 socket.emit('lookup', err, address, family, host);
1279 if (err) {
1280 socket.destroy(err);
1281 return;
1282 }
1283 if (isIP(address) === 0) {
1284 throw new ERR_INVALID_IP_ADDRESS(address);
1285 }
1286 if (
1287 family !== 4 &&
1288 family !== 6 &&
1289 family !== 'IPv4' &&
1290 family !== 'IPv6'
1291 ) {
1292 throw new ERR_INVALID_ARG_VALUE('family', family, 'must be 4 or 6');
1293 }
1294 continueConnection(address, port, family);
1295 }
1296 );
1297 } else {
1298 continueConnection(host, port, addressType);
1299 }
1300}
1301 
1302export function onConnectionOpened(this: Socket): void {
1303 // Callback may come after call to destroy
1304 if (this.destroyed) {
1305 return;
1306 }
1307 
1308 this.connecting = false;
1309 this._sockname = null;
1310 this._unrefTimer();
1311 this.emit('connect');
1312 this.emit('ready');
1313 if (!this.isPaused()) {
1314 tryReadStart(this);
1315 }
1316}
1317 
1318export function onConnectionClosed(this: Socket): void {
1319 if (this._handle?.socket.upgraded) {
1320 // The socket is being upgraded from insecure to TLS.
1321 // No need to handle this particular close event.
1322 return;
1323 }
1324 // eslint-disable-next-line @typescript-eslint/no-this-alias
1325 for (let s: Socket | null = this; s !== null; s = s._parentWrap) {
1326 clearTimeout(s[kTimeout] as unknown as number);
1327 }
1328 
1329 if (!this.destroyed) {
1330 // We have to manually trigger an 'end' event because we are using
1331 // BYOB buffers with Socket class.
1332 this.emit('end');
1333 }
1334}
1335 
1336async function startRead(socket: Socket): Promise<void> {
1337 if (!socket._handle) return;
1338 const reader = socket._handle.reader;
1339 try {
1340 while (socket._handle.reading === true) {
1341 const generatedBuffer = socket[kBufferGen]?.();
1342 
1343 // Let's be extra cautious here and handle nullish values.
1344 if (generatedBuffer == null || generatedBuffer.length === 0) {
1345 // When reading a static buffer with fixed length, it's highly likely to
1346 // read the whole buffer in a single take, which will make the second
1347 // operation to read an empty buffer.
1348 //
1349 // Workerd throws the following exception when reading empty buffers
1350 // TypeError: You must call read() on a "byob" reader with a positive-sized TypedArray object.
1351 // Therefore, let's skip calling read operation and stop reading here.
1352 break;
1353 }
1354 
1355 // The [kBufferGen] function should always be a function that returns
1356 // a Uint8Array we can read into.
1357 const { value, done } = await reader.read(
1358 generatedBuffer as Uint8Array<ArrayBuffer>
1359 );
1360 
1361 // Make sure the socket was not destroyed while we were waiting.
1362 // If it was, we're going to throw away the chunk of data we just
1363 // read.
1364 if (socket.destroyed) {
1365 // Doh! Well, this is awkward. Let's just stop reading and return.
1366 // There's really nothing else we should try to do here.
1367 break;
1368 }
1369 
1370 // Reset the timeout timer since we received data.
1371 socket._unrefTimer();
1372 
1373 if (done) {
1374 // All done! If allowHalfOpen is true, then this will just end the
1375 // readable side of the socket. If allowHalfOpen is false, then this
1376 // should allow the current write queue to drain but not allow any
1377 // further writes to be queued.
1378 socket.push(null);
1379 break;
1380 }
1381 
1382 // If the byteLength is zero, skip the push.
1383 if (value.byteLength === 0) {
1384 continue;
1385 }
1386 socket._handle.bytesRead += value.byteLength;
1387 
1388 // The socket API is expected to produce Buffer instances, not Uint8Arrays
1389 const buffer = Buffer.from(
1390 value.buffer,
1391 value.byteOffset,
1392 value.byteLength
1393 );
1394 
1395 if (typeof socket[kBufferCb] === 'function') {
1396 if (socket[kBufferCb](buffer.byteLength, buffer) === false) {
1397 // If the callback returns explicitly false (not falsy) then
1398 // we're being asked to stop reading for now.
1399 break;
1400 }
1401 continue;
1402 }
1403 
1404 // Because we're pushing the buffer onto the stream we can't use the shared
1405 // buffer here or the next read will overwrite it! We need to copy. For the
1406 // more efficient version, use onread.
1407 if (!socket.push(Buffer.from(buffer))) {
1408 // If push returns false, we've hit the high water mark and should stop
1409 // reading until the stream requests to start reading again.
1410 break;
1411 }
1412 }
1413 } catch (_err) {
1414 // Ignore error, and don't log them.
1415 // This is mostly triggered for invalid sockets with following errors:
1416 // - "This ReadableStream belongs to an object that is closing."
1417 } finally {
1418 // Disable eslint to match Node.js behavior
1419 // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
1420 if (socket._handle != null) {
1421 socket._handle.reading = false;
1422 }
1423 }
1424}
1425 
1426export function tryReadStart(socket: Socket): void {
1427 if (socket._handle != null) {
1428 socket._handle.reading = true;
1429 }
1430 startRead(socket).catch((err: unknown) => socket.destroy(err as Error));
1431}
1432 
1433function writeAfterFIN(
1434 this: Socket,
1435 chunk: Uint8Array | string,
1436 encoding?: NodeJS.BufferEncoding | null | ((err?: Error) => void),
1437 cb?: (err?: Error) => void
1438): boolean {
1439 if (!this.writableEnded) {
1440 // @ts-expect-error TS2554 Required due to @types/node
1441 return Duplex.prototype.write.call(this, chunk, encoding, cb);
1442 }
1443 
1444 if (typeof encoding === 'function') {
1445 cb = encoding;
1446 encoding = null;
1447 }
1448 
1449 const er = new EPIPE();
1450 
1451 if (cb != null && typeof cb === 'function') {
1452 queueMicrotask(() => {
1453 cb(er);
1454 });
1455 }
1456 this.destroy(er);
1457 
1458 return false;
1459}
1460 
1461function onReadableStreamEnd(this: Socket): void {
1462 if (!this.allowHalfOpen) {
1463 this.write = writeAfterFIN;
1464 }
1465}
1466 
1467export function getTimerDuration(msecs: unknown, name: string): number {
1468 validateNumber(msecs, name);
1469 if (msecs < 0 || !Number.isFinite(msecs)) {
1470 throw new ERR_OUT_OF_RANGE(name, 'a non-negative finite number', msecs);
1471 }
1472 
1473 // Ensure that msecs fits into signed int32
1474 if (msecs > TIMEOUT_MAX) {
1475 return TIMEOUT_MAX;
1476 }
1477 
1478 return msecs;
1479}
1480 
1481export function toNumber(x: unknown): number | false {
1482 return (x = Number(x)) >= 0 ? (x as number) : false;
1483}
1484 
1485export function isPipeName(s: unknown): boolean {
1486 return typeof s === 'string' && toNumber(s) === false;
1487}
1488 
1489export type NormalizedArgs = [
1490 {
1491 path?: string;
1492 port?: number;
1493 host?: string;
1494 },
1495 ((...args: unknown[]) => void) | null,
1496];
1497 
1498export function _normalizeArgs(args: unknown[]): NormalizedArgs {
1499 let arr: NormalizedArgs;
1500 
1501 if (args.length === 0) {
1502 arr = [{}, null];
1503 // @ts-expect-error TS2554 Required due to @types/node
1504 arr[normalizedArgsSymbol] = true;
1505 return arr;
1506 }
1507 
1508 const arg0 = args[0];
1509 let options: {
1510 path?: string;
1511 port?: number;
1512 host?: string;
1513 } = {};
1514 if (typeof arg0 === 'object' && arg0 !== null) {
1515 // (options[...][, cb])
1516 options = arg0;
1517 } else if (isPipeName(arg0)) {
1518 // (path[...][, cb])
1519 options.path = arg0 as string;
1520 } else {
1521 // ([port][, host][...][, cb])
1522 options.port = arg0 as number;
1523 if (args.length > 1 && typeof args[1] === 'string') {
1524 // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
1525 options.host = args[1] as string;
1526 }
1527 }
1528 
1529 const cb = args[args.length - 1];
1530 if (typeof cb !== 'function') arr = [options, null];
1531 else arr = [options, cb as (...args: unknown[]) => unknown];
1532 
1533 // @ts-expect-error TS2554 Required due to @types/node
1534 arr[normalizedArgsSymbol] = true;
1535 return arr;
1536}
1537 
1538function addClientAbortSignalOption(self: Socket, signal: AbortSignal): void {
1539 validateAbortSignal(signal, 'options.signal');
1540 let disposable: Disposable | undefined;
1541 
1542 function onAbort(): void {
1543 disposable?.[Symbol.dispose]();
1544 self._aborted = true;
1545 }
1546 
1547 if (signal.aborted) {
1548 queueMicrotask(onAbort);
1549 } else {
1550 queueMicrotask(() => {
1551 disposable = addAbortListener(signal, onAbort);
1552 });
1553 }
1554}
1555 
1556function addAbortListener(
1557 signal: AbortSignal | undefined,
1558 listener: VoidFunction
1559): Disposable {
1560 if (signal === undefined) {
1561 throw new ERR_INVALID_ARG_TYPE('signal', 'AbortSignal', signal);
1562 }
1563 validateAbortSignal(signal, 'signal');
1564 validateFunction(listener, 'listener');
1565 
1566 let removeEventListener: undefined | (() => void);
1567 if (signal.aborted) {
1568 queueMicrotask(() => {
1569 listener();
1570 });
1571 } else {
1572 signal.addEventListener('abort', listener, { once: true });
1573 removeEventListener = (): void => {
1574 signal.removeEventListener('abort', listener);
1575 };
1576 }
1577 return {
1578 // @ts-expect-error TS2353 Required for __proto__
1579 __proto__: null,
1580 [Symbol.dispose](): void {
1581 removeEventListener?.();
1582 },
1583 };
1584}
1585 
1586// ======================================================================================
1587// The rest of the exports
1588 
1589export function connect(...args: unknown[]): Socket {
1590 const normalized = _normalizeArgs(args);
1591 const options = normalized[0] as SocketOptions;
1592 const socket: Socket = new Socket(options);
1593 if (options.timeout) {
1594 socket.setTimeout(options.timeout);
1595 }
1596 if (socket.destroyed) {
1597 return socket;
1598 }
1599 return socket.connect(normalized as unknown as SocketConnectOpts);
1600}
1601 
1602export const createConnection = connect;
1603 
1604export function createServer(): void {
1605 throw new Error('createServer() is not implemented');
1606}
1607 
1608export function getDefaultAutoSelectFamily(): boolean {
1609 // This is the only value we support.
1610 return false;
1611}
1612 
1613export function setDefaultAutoSelectFamily(val: unknown): void {
1614 if (!val) return;
1615 throw new ERR_INVALID_ARG_VALUE('val', val);
1616}
1617 
1618// We don't actually make use of this. It's here only for compatibility.
1619// The value is not used anywhere.
1620let autoSelectFamilyAttemptTimeout: number = 10;
1621 
1622export function getDefaultAutoSelectFamilyAttemptTimeout(): number {
1623 return autoSelectFamilyAttemptTimeout;
1624}
1625 
1626export function setDefaultAutoSelectFamilyAttemptTimeout(val: unknown): void {
1627 validateInt32(val, 'val', 1);
1628 if (val < 10) val = 10;
1629 autoSelectFamilyAttemptTimeout = val as number;
1630}
1631 
1632export function isIP(input: unknown): number {
1633 if (isIPv4(input)) return 4;
1634 if (isIPv6(input)) return 6;
1635 return 0;
1636}
1637 
1638export function isIPv4(input: unknown): boolean {
1639 input = typeof input !== 'string' ? `${input}` : input;
1640 return IPv4Reg.test(input as string);
1641}
1642 
1643export function isIPv6(input: unknown): boolean {
1644 input = typeof input !== 'string' ? `${input}` : input;
1645 return IPv6Reg.test(input as string);
1646}
1647 
1648// ======================================================================================
1649 
1650export class SocketAddress implements _SocketAddress {
1651 #address: string;
1652 #port: number;
1653 #family: IPVersion;
1654 #flowlabel: number;
1655 
1656 static isSocketAddress(value: unknown): value is SocketAddress {
1657 return value instanceof SocketAddress;
1658 }
1659 
1660 constructor(options: SocketAddressInitOptions = {}) {
1661 validateObject(options, 'options');
1662 this.#family = options.family || 'ipv4';
1663 // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
1664 if (typeof this.#family?.toLowerCase === 'function')
1665 this.#family = this.#family.toLowerCase() as IPVersion;
1666 validateOneOf(this.#family, 'options.family', ['ipv4', 'ipv6']);
1667 
1668 const {
1669 address = this.#family === 'ipv4' ? '127.0.0.1' : '::',
1670 port = 0,
1671 flowlabel = 0,
1672 } = options;
1673 
1674 validateString(address, 'options.address');
1675 const _port = validatePort(port, 'options.port');
1676 validateUint32(flowlabel, 'options.flowlabel', false);
1677 
1678 switch (this.#family) {
1679 case 'ipv4':
1680 if (!isIPv4(address)) {
1681 throw new ERR_INVALID_ADDRESS();
1682 }
1683 break;
1684 case 'ipv6':
1685 if (!isIPv6(address)) {
1686 throw new ERR_INVALID_ADDRESS();
1687 }
1688 break;
1689 }
1690 
1691 // Node.js' implementation is a bit more complicated since it is backed
1692 // by a C++ class wrapping an actual socket address structure. We don't
1693 // need that here, so we keep things simple.
1694 
1695 this.#address = address;
1696 this.#port = _port;
1697 this.#flowlabel = flowlabel;
1698 }
1699 
1700 get address(): string {
1701 return this.#address;
1702 }
1703 
1704 get port(): number {
1705 return this.#port;
1706 }
1707 
1708 get family(): IPVersion {
1709 return this.#family;
1710 }
1711 
1712 get flowlabel(): number {
1713 return this.#flowlabel;
1714 }
1715 
1716 [kInspect](depth: number, options: InspectOptions): string | this {
1717 if (depth < 0) return this;
1718 
1719 const opts: InspectOptions = {
1720 ...options,
1721 depth: options.depth == null ? null : options.depth - 1,
1722 };
1723 
1724 // @ts-expect-error TS2769 not all the overloads are compatible
1725 return `SocketAddress ${inspect(this.toJSON(), opts)}`;
1726 }
1727 
1728 toJSON(): {
1729 address: string;
1730 port: number;
1731 family: IPVersion;
1732 flowlabel: number;
1733 } {
1734 return {
1735 address: this.address,
1736 port: this.port,
1737 family: this.family,
1738 flowlabel: this.flowlabel,
1739 };
1740 }
1741 
1742 static parse(input: string): SocketAddress | undefined {
1743 validateString(input, 'input');
1744 try {
1745 const parsed = URL.parse(`http://${input}`);
1746 if (parsed == null) {
1747 return undefined;
1748 }
1749 const { hostname: address, port } = parsed;
1750 if (address.startsWith('[') && address.endsWith(']')) {
1751 return new SocketAddress({
1752 address: address.slice(1, -1),
1753 // @ts-expect-error TS2362 port will be a string, this converts it
1754 port: port | 0,
1755 family: 'ipv6',
1756 });
1757 }
1758 // @ts-expect-error TS2362 port will be a string, this converts it
1759 return new SocketAddress({ address, port: port | 0 });
1760 } catch {
1761 // Ignore errors here. Return undefined if the input cannot
1762 // be successfully parsed or is not a proper socket address.
1763 }
1764 return undefined;
1765 }
1766}
1767 
1768// ======================================================================================
1769 
1770// Note: the bulk of the following BlockList related code was authored by claude...
1771// The implementation in Node.js is split between javascript and C++.
1772// Here we do the entire implementation as TypeScript simply because
1773// we don't need the C++ parts at all.
1774 
1775const kIpv6Regex = /^::ffff:(\d+\.\d+\.\d+\.\d+)$/i;
1776const kIpv6RangeRegex =
1777 /Range: IPv6 ([0-9a-fA-F:]{1,39})-([0-9a-fA-F:]{1,39})/i;
1778const kIpv6AddressRegex = /Address: IPv6 ([0-9a-fA-F:]{1,39})/i;
1779const kIpv6SubnetRegex = /Subnet: IPv6 ([0-9a-fA-F:]{1,39})\/(\d{1,3})/i;
1780const kIpv4RangeRegex =
1781 /Range: IPv4 (\d{1,3}(?:\.\d{1,3}){3})-(\d{1,3}(?:\.\d{1,3}){3})/;
1782const kIpv4AddressRegex = /Address: IPv4 (\d{1,3}(?:\.\d{1,3}){3})/;
1783const kIpv4SubnetRegex = /Subnet: IPv4 (\d{1,3}(?:\.\d{1,3}){3})\/(\d{1,2})/;
1784 
1785// IPv4/IPv6 CIDR network utilities
1786function parseIPv4(ip: string): FixedLengthArray<number, 4> | undefined {
1787 // When the input is not a valid IPv4 address, return an empty array.
1788 const parts = ip.split('.');
1789 if (parts.length !== 4) return undefined;
1790 
1791 const nums = [0, 0, 0, 0] as FixedLengthArray<number, 4>;
1792 
1793 for (let n = 0; n < parts.length; n++) {
1794 const part = parts[n];
1795 if (part === undefined) return undefined;
1796 const num = parseInt(part, 10);
1797 if (isNaN(num) || num < 0 || num > 255) return undefined;
1798 nums[n] = num;
1799 }
1800 
1801 return nums;
1802}
1803 
1804function parseIPv6(ip: string): FixedLengthArray<number, 8> | undefined {
1805 // Handle IPv4-mapped IPv6 addresses
1806 if (ip.includes('.')) {
1807 const match = ip.match(kIpv6Regex);
1808 if (match != null && match[1]) {
1809 const ipv4Parts = parseIPv4(match[1]);
1810 if (ipv4Parts !== undefined) {
1811 return [
1812 0,
1813 0,
1814 0,
1815 0,
1816 0,
1817 0xffff,
1818 (ipv4Parts[0] << 8) | ipv4Parts[1],
1819 (ipv4Parts[2] << 8) | ipv4Parts[3],
1820 ];
1821 }
1822 }
1823 }
1824 
1825 // Expand :: notation
1826 let expanded = ip;
1827 if (ip.includes('::')) {
1828 const parts = ip.split('::');
1829 if (parts.length === 2) {
1830 const left = parts[0]?.split(':') ?? [];
1831 const right = parts[1]?.split(':') ?? [];
1832 const missing = 8 - left.length - right.length;
1833 const middle = Array.from({ length: missing }).fill('0');
1834 expanded = [...left, ...middle, ...right].join(':');
1835 }
1836 }
1837 
1838 const parts = expanded.split(':');
1839 if (parts.length !== 8) return undefined;
1840 
1841 const nums = parts.map((p) => {
1842 const num = parseInt(p || '0', 16);
1843 return num >= 0 && num <= 0xffff ? num : -1;
1844 }) as FixedLengthArray<number, 8>;
1845 
1846 return nums.includes(-1) ? undefined : nums;
1847}
1848 
1849function ipv4ToNumber(ip: string): number {
1850 const parts = parseIPv4(ip);
1851 if (parts === undefined)
1852 throw new ERR_INVALID_IP_ADDRESS('Invalid IPv4 address');
1853 return (parts[0] << 24) | (parts[1] << 16) | (parts[2] << 8) | parts[3];
1854}
1855 
1856function ipv6ToBigInt(ip: string): bigint {
1857 const parts = parseIPv6(ip);
1858 if (parts === undefined)
1859 throw new ERR_INVALID_IP_ADDRESS('Invalid IPv6 address');
1860 
1861 let result = 0n;
1862 for (let i = 0; i < 8; i++) {
1863 const part = parts[i];
1864 result = (result << 16n) | BigInt(part as number);
1865 }
1866 return result;
1867}
1868 
1869function isInIPv4Subnet(ip: string, network: string, prefix: number): boolean {
1870 if (prefix < 0 || prefix > 32) return false;
1871 if (prefix === 0) return true;
1872 
1873 const ipNum = ipv4ToNumber(ip);
1874 const netNum = ipv4ToNumber(network);
1875 // Create a subnet mask by shifting all 1s left by (32 - prefix) bits
1876 // This creates a mask with 'prefix' number of leading 1s followed by 0s
1877 // For example, prefix=24 creates mask 0xffffff00 (255.255.255.0)
1878 const mask = (0xffffffff << (32 - prefix)) >>> 0;
1879 
1880 return (ipNum & mask) === (netNum & mask);
1881}
1882 
1883function isInIPv6Subnet(ip: string, network: string, prefix: number): boolean {
1884 if (prefix < 0 || prefix > 128) return false;
1885 if (prefix === 0) return true;
1886 
1887 const ipBig = ipv6ToBigInt(ip);
1888 const netBig = ipv6ToBigInt(network);
1889 
1890 // Create a 128-bit subnet mask for IPv6 by shifting all 1s left by (128 - prefix) bits
1891 // This creates a mask with 'prefix' number of leading 1s followed by 0s
1892 // The AND with 0xfff...f ensures we stay within 128 bits
1893 // For example, prefix=64 creates a mask with 64 leading 1s and 64 trailing 0s
1894 const mask =
1895 (0xffffffffffffffffffffffffffffffffn << BigInt(128 - prefix)) &
1896 0xffffffffffffffffffffffffffffffffn;
1897 
1898 return (ipBig & mask) === (netBig & mask);
1899}
1900 
1901function compareIPv4(a: string, b: string): number {
1902 const aNum = ipv4ToNumber(a);
1903 const bNum = ipv4ToNumber(b);
1904 return aNum - bNum;
1905}
1906 
1907function compareIPv6(a: string, b: string): number {
1908 const aBig = ipv6ToBigInt(a);
1909 const bBig = ipv6ToBigInt(b);
1910 return aBig < bBig ? -1 : aBig > bBig ? 1 : 0;
1911}
1912 
1913function formatIPFamily(family: 'ipv4' | 'ipv6'): 'IPv4' | 'IPv6' {
1914 return family === 'ipv4' ? 'IPv4' : 'IPv6';
1915}
1916 
1917interface BlockListRule {
1918 type: 'address' | 'range' | 'subnet';
1919 family: 'ipv4' | 'ipv6';
1920 toString(): string;
1921 check(address: string, family: 'ipv4' | 'ipv6'): boolean;
1922}
1923 
1924class AddressRule implements BlockListRule {
1925 type = 'address' as const;
1926 address: string;
1927 family: 'ipv4' | 'ipv6';
1928 
1929 constructor(address: string, family: 'ipv4' | 'ipv6') {
1930 this.address = address;
1931 this.family = family;
1932 }
1933 
1934 toString(): string {
1935 return `Address: ${formatIPFamily(this.family)} ${this.address}`;
1936 }
1937 
1938 check(address: string, family: 'ipv4' | 'ipv6'): boolean {
1939 if (this.family !== family) {
1940 // Handle IPv4-mapped IPv6 addresses
1941 if (
1942 this.family === 'ipv4' &&
1943 family === 'ipv6' &&
1944 address.startsWith('::ffff:')
1945 ) {
1946 const ipv4Part = address.substring(7);
1947 return this.address === ipv4Part;
1948 }
1949 return false;
1950 }
1951 return this.address === address;
1952 }
1953}
1954 
1955class RangeRule implements BlockListRule {
1956 type = 'range' as const;
1957 start: string;
1958 end: string;
1959 family: 'ipv4' | 'ipv6';
1960 
1961 constructor(start: string, end: string, family: 'ipv4' | 'ipv6') {
1962 this.start = start;
1963 this.end = end;
1964 this.family = family;
1965 }
1966 
1967 toString(): string {
1968 return `Range: ${formatIPFamily(this.family)} ${this.start}-${this.end}`;
1969 }
1970 
1971 check(address: string, family: 'ipv4' | 'ipv6'): boolean {
1972 if (this.family !== family) {
1973 // Handle IPv4-mapped IPv6 for IPv4 ranges
1974 if (
1975 this.family === 'ipv4' &&
1976 family === 'ipv6' &&
1977 address.startsWith('::ffff:')
1978 ) {
1979 const ipv4Part = address.substring(7);
1980 const cmpStart = compareIPv4(ipv4Part, this.start);
1981 const cmpEnd = compareIPv4(ipv4Part, this.end);
1982 return cmpStart >= 0 && cmpEnd <= 0;
1983 }
1984 return false;
1985 }
1986 
1987 if (family === 'ipv4') {
1988 const cmpStart = compareIPv4(address, this.start);
1989 const cmpEnd = compareIPv4(address, this.end);
1990 return cmpStart >= 0 && cmpEnd <= 0;
1991 } else {
1992 const cmpStart = compareIPv6(address, this.start);
1993 const cmpEnd = compareIPv6(address, this.end);
1994 return cmpStart >= 0 && cmpEnd <= 0;
1995 }
1996 }
1997}
1998 
1999class SubnetRule implements BlockListRule {
2000 type = 'subnet' as const;
2001 network: string;
2002 prefix: number;
2003 family: 'ipv4' | 'ipv6';
2004 
2005 constructor(network: string, prefix: number, family: 'ipv4' | 'ipv6') {
2006 this.network = network;
2007 this.prefix = prefix;
2008 this.family = family;
2009 }
2010 
2011 toString(): string {
2012 return `Subnet: ${formatIPFamily(this.family)} ${this.network}/${this.prefix}`;
2013 }
2014 
2015 check(address: string, family: 'ipv4' | 'ipv6'): boolean {
2016 if (this.family !== family) {
2017 // Handle IPv4-mapped IPv6 for IPv4 subnets
2018 if (
2019 this.family === 'ipv4' &&
2020 family === 'ipv6' &&
2021 address.startsWith('::ffff:')
2022 ) {
2023 const ipv4Part = address.substring(7);
2024 return isInIPv4Subnet(ipv4Part, this.network, this.prefix);
2025 }
2026 return false;
2027 }
2028 
2029 if (family === 'ipv4') {
2030 return isInIPv4Subnet(address, this.network, this.prefix);
2031 } else {
2032 return isInIPv6Subnet(address, this.network, this.prefix);
2033 }
2034 }
2035}
2036 
2037export class BlockList {
2038 #rules: BlockListRule[] = [];
2039 
2040 static isBlockList(value: unknown): value is BlockList {
2041 return value instanceof BlockList;
2042 }
2043 
2044 addAddress(
2045 address: string | SocketAddress,
2046 family: 'ipv4' | 'ipv6' = 'ipv4'
2047 ): void {
2048 if (SocketAddress.isSocketAddress(address)) {
2049 this.#rules.push(new AddressRule(address.address, address.family));
2050 } else {
2051 validateString(address, 'address');
2052 validateOneOf(family, 'family', ['ipv4', 'ipv6']);
2053 
2054 // Validate the address format
2055 if (family === 'ipv4' && !isIPv4(address)) {
2056 throw new ERR_INVALID_IP_ADDRESS('Invalid IPv4 address');
2057 }
2058 if (family === 'ipv6' && !isIPv6(address)) {
2059 throw new ERR_INVALID_IP_ADDRESS('Invalid IPv6 address');
2060 }
2061 
2062 this.#rules.push(new AddressRule(address, family));
2063 }
2064 }
2065 
2066 addRange(
2067 start: string | SocketAddress,
2068 end: string | SocketAddress,
2069 family: 'ipv4' | 'ipv6' = 'ipv4'
2070 ): void {
2071 let startAddr: string;
2072 let endAddr: string;
2073 let addrFamily: 'ipv4' | 'ipv6';
2074 
2075 if (SocketAddress.isSocketAddress(start)) {
2076 startAddr = start.address;
2077 addrFamily = start.family;
2078 } else {
2079 validateString(start, 'start');
2080 validateOneOf(family, 'family', ['ipv4', 'ipv6']);
2081 startAddr = start;
2082 addrFamily = family;
2083 }
2084 
2085 if (SocketAddress.isSocketAddress(end)) {
2086 endAddr = end.address;
2087 if (SocketAddress.isSocketAddress(start) && end.family !== addrFamily) {
2088 throw new ERR_INVALID_ARG_VALUE(
2089 'end',
2090 end,
2091 'must be same family as start'
2092 );
2093 }
2094 } else {
2095 validateString(end, 'end');
2096 endAddr = end;
2097 }
2098 
2099 // Validate addresses
2100 if (addrFamily === 'ipv4') {
2101 if (!isIPv4(startAddr) || !isIPv4(endAddr)) {
2102 throw new ERR_INVALID_IP_ADDRESS('Invalid IPv4 address');
2103 }
2104 if (compareIPv4(startAddr, endAddr) > 0) {
2105 throw new ERR_INVALID_ARG_VALUE(
2106 'start',
2107 startAddr,
2108 'must come before end'
2109 );
2110 }
2111 } else {
2112 if (!isIPv6(startAddr) || !isIPv6(endAddr)) {
2113 throw new ERR_INVALID_IP_ADDRESS('Invalid IPv6 address');
2114 }
2115 if (compareIPv6(startAddr, endAddr) > 0) {
2116 throw new ERR_INVALID_ARG_VALUE(
2117 'start',
2118 startAddr,
2119 'must come before end'
2120 );
2121 }
2122 }
2123 
2124 this.#rules.push(new RangeRule(startAddr, endAddr, addrFamily));
2125 }
2126 
2127 addSubnet(
2128 network: string | SocketAddress,
2129 prefix: number,
2130 family: 'ipv4' | 'ipv6' = 'ipv4'
2131 ): void {
2132 let networkAddr: string;
2133 let addrFamily: 'ipv4' | 'ipv6';
2134 
2135 if (SocketAddress.isSocketAddress(network)) {
2136 networkAddr = network.address;
2137 addrFamily = network.family;
2138 } else {
2139 validateString(network, 'network');
2140 validateOneOf(family, 'family', ['ipv4', 'ipv6']);
2141 networkAddr = network;
2142 addrFamily = family;
2143 }
2144 
2145 validateInt32(prefix, 'prefix');
2146 
2147 // Validate prefix range
2148 if (addrFamily === 'ipv4') {
2149 if (prefix < 0 || prefix > 32) {
2150 throw new ERR_OUT_OF_RANGE('prefix', 'between 0 and 32', prefix);
2151 }
2152 if (!isIPv4(networkAddr)) {
2153 throw new ERR_INVALID_IP_ADDRESS('Invalid IPv4 address');
2154 }
2155 } else {
2156 if (prefix < 0 || prefix > 128) {
2157 throw new ERR_OUT_OF_RANGE('prefix', 'between 0 and 128', prefix);
2158 }
2159 if (!isIPv6(networkAddr)) {
2160 throw new ERR_INVALID_IP_ADDRESS('Invalid IPv6 address');
2161 }
2162 }
2163 
2164 this.#rules.push(new SubnetRule(networkAddr, prefix, addrFamily));
2165 }
2166 
2167 check(
2168 address: string | SocketAddress,
2169 family: 'ipv4' | 'ipv6' = 'ipv4'
2170 ): boolean {
2171 let checkAddr: string;
2172 let checkFamily: 'ipv4' | 'ipv6';
2173 
2174 if (SocketAddress.isSocketAddress(address)) {
2175 checkAddr = address.address;
2176 checkFamily = address.family;
2177 } else {
2178 validateString(address, 'address');
2179 validateOneOf(family, 'family', ['ipv4', 'ipv6']);
2180 checkAddr = address;
2181 checkFamily = family.toLowerCase() as 'ipv4' | 'ipv6';
2182 }
2183 
2184 // Validate address format
2185 try {
2186 if (checkFamily === 'ipv4' && !isIPv4(checkAddr)) {
2187 return false;
2188 }
2189 if (checkFamily === 'ipv6' && !isIPv6(checkAddr)) {
2190 return false;
2191 }
2192 } catch {
2193 return false;
2194 }
2195 
2196 // Check against all rules
2197 for (const rule of this.#rules) {
2198 if (rule.check(checkAddr, checkFamily)) {
2199 return true;
2200 }
2201 }
2202 
2203 return false;
2204 }
2205 
2206 get rules(): string[] {
2207 return this.#rules.map((rule) => rule.toString());
2208 }
2209 
2210 [kInspect](depth: number, options: InspectOptions): string {
2211 if (depth < 0) return '[BlockList]';
2212 
2213 const opts: InspectOptions = {
2214 ...options,
2215 depth: options.depth == null ? null : options.depth - 1,
2216 };
2217 
2218 // @ts-expect-error TS2769 not all the overloads are compatible
2219 return `BlockList {\n rules: ${inspect(this.rules, opts)}\n}`;
2220 }
2221 
2222 toJSON(): string[] {
2223 return this.rules;
2224 }
2225 
2226 fromJSON(data: string | string[]): void {
2227 let rules: string[];
2228 
2229 if (typeof data === 'string') {
2230 try {
2231 rules = JSON.parse(data) as string[];
2232 } catch {
2233 throw new ERR_INVALID_ARG_VALUE('data', data, 'must be valid JSON');
2234 }
2235 } else {
2236 rules = data;
2237 }
2238 
2239 validateArray(rules, 'data');
2240 
2241 for (const item of data) {
2242 if (item.includes('IPv4')) {
2243 // IPv4 subnet pattern
2244 const subnetMatch = item.match(kIpv4SubnetRegex);
2245 if (subnetMatch) {
2246 const [, network, prefix] = subnetMatch;
2247 if (network === undefined || prefix === undefined) {
2248 // Skip the rule if parsing failed.
2249 continue;
2250 }
2251 this.addSubnet(network, parseInt(prefix, 10));
2252 continue;
2253 }
2254 
2255 // IPv4 address pattern
2256 const addressMatch = item.match(kIpv4AddressRegex);
2257 
2258 if (addressMatch) {
2259 const [, address] = addressMatch;
2260 if (address === undefined) {
2261 // Skip the rule if parsing failed.
2262 continue;
2263 }
2264 this.addAddress(address);
2265 continue;
2266 }
2267 
2268 // IPv4 range pattern
2269 const rangeMatch = item.match(kIpv4RangeRegex);
2270 if (rangeMatch) {
2271 const [, start, end] = rangeMatch;
2272 if (start === undefined || end === undefined) {
2273 // Skip the rule if parsing failed.
2274 continue;
2275 }
2276 this.addRange(start, end);
2277 continue;
2278 }
2279 }
2280 
2281 if (item.includes('IPv6')) {
2282 // IPv6 subnet pattern
2283 const ipv6SubnetMatch = item.match(kIpv6SubnetRegex);
2284 if (ipv6SubnetMatch) {
2285 const [, network, prefix] = ipv6SubnetMatch;
2286 if (network === undefined || prefix === undefined) {
2287 // Skip the rule if parsing failed.
2288 continue;
2289 }
2290 this.addSubnet(network, parseInt(prefix, 10), 'ipv6');
2291 continue;
2292 }
2293 
2294 // IPv6 address pattern
2295 const ipv6AddressMatch = item.match(kIpv6AddressRegex);
2296 if (ipv6AddressMatch) {
2297 const [, address] = ipv6AddressMatch;
2298 if (address === undefined) {
2299 // Skip the rule if parsing failed.
2300 continue;
2301 }
2302 this.addAddress(address, 'ipv6');
2303 continue;
2304 }
2305 
2306 // IPv6 range pattern
2307 const ipv6RangeMatch = item.match(kIpv6RangeRegex);
2308 if (ipv6RangeMatch) {
2309 const [, start, end] = ipv6RangeMatch;
2310 if (start === undefined || end === undefined) {
2311 // Skip the rule if parsing failed.
2312 continue;
2313 }
2314 this.addRange(start, end, 'ipv6');
2315 continue;
2316 }
2317 }
2318 }
2319 }
2320}