import { notImplemented } from "../_utils.ts";import { unreachable } from "../../testing/asserts.ts";import { ConnectionWrap } from "./connection_wrap.ts";import { AsyncWrap, providerType } from "./async_wrap.ts";import { LibuvStreamWrap } from "./stream_wrap.ts";import { ownerSymbol } from "./symbols.ts";import { codeMap } from "./uv.ts";import { delay } from "../../async/mod.ts";import { kStreamBaseField } from "./stream_wrap.ts";import { isIP } from "../internal/net.ts";
enum socketType { SOCKET, SERVER,}
interface AddressInfo { address: string; family?: string; port: number;}
const INITIAL_ACCEPT_BACKOFF_DELAY = 5;
const MAX_ACCEPT_BACKOFF_DELAY = 1000;
function _ceilPowOf2(n: number) { const roundPowOf2 = 1 << 31 - Math.clz32(n);
return roundPowOf2 < n ? roundPowOf2 * 2 : roundPowOf2;}
export class TCPConnectWrap extends AsyncWrap { oncomplete!: ( status: number, handle: ConnectionWrap, req: TCPConnectWrap, readable: boolean, writeable: boolean, ) => void; address!: string; port!: number; localAddress!: string; localPort!: number;
constructor() { super(providerType.TCPCONNECTWRAP); }}
export enum constants { SOCKET = socketType.SOCKET, SERVER = socketType.SERVER, UV_TCP_IPV6ONLY,}
export class TCP extends ConnectionWrap { [ownerSymbol]: unknown = null; reading = false;
#address?: string; #port?: number;
#remoteAddress?: string; #remoteFamily?: string; #remotePort?: number;
#backlog?: number; #listener!: Deno.Listener; #connections = 0;
#closed = false; #acceptBackoffDelay?: number;
constructor(type: number, conn?: Deno.Conn) { let provider: providerType;
switch (type) { case socketType.SOCKET: { provider = providerType.TCPWRAP;
break; } case socketType.SERVER: { provider = providerType.TCPSERVERWRAP;
break; } default: { unreachable(); } }
super(provider, conn);
if (conn && provider === providerType.TCPWRAP) { const localAddr = conn.localAddr as Deno.NetAddr; this.#address = localAddr.hostname; this.#port = localAddr.port;
const remoteAddr = conn.remoteAddr as Deno.NetAddr; this.#remoteAddress = remoteAddr.hostname; this.#remotePort = remoteAddr.port; this.#remoteFamily = isIP(remoteAddr.hostname) === 6 ? "IPv6" : "IPv4"; } }
open(_fd: number): number { notImplemented(); }
bind(address: string, port: number): number { return this.#bind(address, port, 0); }
bind6(address: string, port: number, flags: number): number { return this.#bind(address, port, flags); }
connect(req: TCPConnectWrap, address: string, port: number): number { return this.#connect(req, address, port); }
connect6(req: TCPConnectWrap, address: string, port: number): number { return this.#connect(req, address, port); }
listen(backlog: number): number { this.#backlog = _ceilPowOf2(backlog + 1);
const listenOptions = { hostname: this.#address!, port: this.#port!, transport: "tcp" as const, };
let listener;
try { listener = Deno.listen(listenOptions); } catch (e) { if (e instanceof Deno.errors.AddrInUse) { return codeMap.get("EADDRINUSE")!; } else if (e instanceof Deno.errors.AddrNotAvailable) { return codeMap.get("EADDRNOTAVAIL")!; }
return codeMap.get("UNKNOWN")!; }
const address = listener.addr as Deno.NetAddr; this.#address = address.hostname; this.#port = address.port;
this.#listener = listener; this.#accept();
return 0; }
getsockname(sockname: Record<string, never> | AddressInfo): number { if ( typeof this.#address === "undefined" || typeof this.#port === "undefined" ) { return codeMap.get("EADDRNOTAVAIL")!; }
sockname.address = this.#address; sockname.port = this.#port; sockname.family = isIP(this.#address) === 6 ? "IPv6" : "IPv4";
return 0; }
getpeername(peername: Record<string, never> | AddressInfo): number { if ( typeof this.#remoteAddress === "undefined" || typeof this.#remotePort === "undefined" ) { return codeMap.get("EADDRNOTAVAIL")!; }
peername.address = this.#remoteAddress; peername.port = this.#remotePort; peername.family = this.#remoteFamily;
return 0; }
setNoDelay(_noDelay: boolean): number { notImplemented(); }
setKeepAlive(_enable: boolean, _initialDelay: number): number { notImplemented(); }
setSimultaneousAccepts(_enable: boolean) { notImplemented(); }
#bind(address: string, port: number, _flags: number): number { this.#address = address; this.#port = port;
return 0; }
#connect(req: TCPConnectWrap, address: string, port: number): number { this.#remoteAddress = address; this.#remotePort = port; this.#remoteFamily = isIP(address) === 6 ? "IPv6" : "IPv4";
const connectOptions: Deno.ConnectOptions = { hostname: address, port, transport: "tcp", };
Deno.connect(connectOptions).then((conn: Deno.Conn) => { const localAddr = conn.localAddr as Deno.NetAddr; this.#address = req.localAddress = localAddr.hostname; this.#port = req.localPort = localAddr.port; this[kStreamBaseField] = conn;
try { this.afterConnect(req, 0); } catch { } }, () => { try { this.afterConnect(req, codeMap.get("ECONNREFUSED")!); } catch { } });
return 0; }
async #acceptBackoff(): Promise<void> { if (!this.#acceptBackoffDelay) { this.#acceptBackoffDelay = INITIAL_ACCEPT_BACKOFF_DELAY; } else { this.#acceptBackoffDelay *= 2; }
if (this.#acceptBackoffDelay >= MAX_ACCEPT_BACKOFF_DELAY) { this.#acceptBackoffDelay = MAX_ACCEPT_BACKOFF_DELAY; }
await delay(this.#acceptBackoffDelay);
this.#accept(); }
async #accept(): Promise<void> { if (this.#closed) { return; }
if (this.#connections > this.#backlog!) { this.#acceptBackoff();
return; }
let connection: Deno.Conn;
try { connection = await this.#listener.accept(); } catch (e) { if (e instanceof Deno.errors.BadResource && this.#closed) { return; }
try { this.onconnection!(codeMap.get("UNKNOWN")!, undefined); } catch { }
this.#acceptBackoff();
return; }
this.#acceptBackoffDelay = undefined;
const connectionHandle = new TCP(socketType.SOCKET, connection); this.#connections++;
try { this.onconnection!(0, connectionHandle); } catch { }
return this.#accept(); }
async _onClose(): Promise<number> { this.#closed = true; this.reading = false;
this.#address = undefined; this.#port = undefined;
this.#remoteAddress = undefined; this.#remoteFamily = undefined; this.#remotePort = undefined;
this.#backlog = undefined; this.#connections = 0; this.#acceptBackoffDelay = undefined;
if (this.provider === providerType.TCPSERVERWRAP) { try { this.#listener.close(); } catch { } }
return await LibuvStreamWrap.prototype._onClose.call(this); }}