summaryrefslogtreecommitdiff
path: root/ext/node/polyfills/internal_binding/tcp_wrap.ts
diff options
context:
space:
mode:
authorBartek IwaƄczuk <biwanczuk@gmail.com>2023-02-14 17:38:45 +0100
committerGitHub <noreply@github.com>2023-02-14 17:38:45 +0100
commitd47147fb6ad229b1c039aff9d0959b6e281f4df5 (patch)
tree6e9e790f2b9bc71b5f0c9c7e64b95cae31579d58 /ext/node/polyfills/internal_binding/tcp_wrap.ts
parent1d00bbe47e2ca14e2d2151518e02b2324461a065 (diff)
feat(ext/node): embed std/node into the snapshot (#17724)
This commit moves "deno_std/node" in "ext/node" crate. The code is transpiled and snapshotted during the build process. During the first pass a minimal amount of work was done to create the snapshot, a lot of code in "ext/node" depends on presence of "Deno" global. This code will be gradually fixed in the follow up PRs to migrate it to import relevant APIs from "internal:" modules. Currently the code from snapshot is not used in any way, and all Node/npm compatibility still uses code from "https://deno.land/std/node" (or from the location specified by "DENO_NODE_COMPAT_URL"). This will also be handled in a follow up PRs. --------- Co-authored-by: crowlkats <crowlkats@toaxl.com> Co-authored-by: Divy Srivastava <dj.srivastava23@gmail.com> Co-authored-by: Yoshiya Hinosawa <stibium121@gmail.com>
Diffstat (limited to 'ext/node/polyfills/internal_binding/tcp_wrap.ts')
-rw-r--r--ext/node/polyfills/internal_binding/tcp_wrap.ts493
1 files changed, 493 insertions, 0 deletions
diff --git a/ext/node/polyfills/internal_binding/tcp_wrap.ts b/ext/node/polyfills/internal_binding/tcp_wrap.ts
new file mode 100644
index 000000000..d0da3e10c
--- /dev/null
+++ b/ext/node/polyfills/internal_binding/tcp_wrap.ts
@@ -0,0 +1,493 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// This module ports:
+// - https://github.com/nodejs/node/blob/master/src/tcp_wrap.cc
+// - https://github.com/nodejs/node/blob/master/src/tcp_wrap.h
+
+import { notImplemented } from "internal:deno_node/polyfills/_utils.ts";
+import { unreachable } from "internal:deno_node/polyfills/_util/asserts.ts";
+import { ConnectionWrap } from "internal:deno_node/polyfills/internal_binding/connection_wrap.ts";
+import {
+ AsyncWrap,
+ providerType,
+} from "internal:deno_node/polyfills/internal_binding/async_wrap.ts";
+import { LibuvStreamWrap } from "internal:deno_node/polyfills/internal_binding/stream_wrap.ts";
+import { ownerSymbol } from "internal:deno_node/polyfills/internal_binding/symbols.ts";
+import { codeMap } from "internal:deno_node/polyfills/internal_binding/uv.ts";
+import { delay } from "internal:deno_node/polyfills/_util/async.ts";
+import { kStreamBaseField } from "internal:deno_node/polyfills/internal_binding/stream_wrap.ts";
+import { isIP } from "internal:deno_node/polyfills/internal/net.ts";
+import {
+ ceilPowOf2,
+ INITIAL_ACCEPT_BACKOFF_DELAY,
+ MAX_ACCEPT_BACKOFF_DELAY,
+} from "internal:deno_node/polyfills/internal_binding/_listen.ts";
+
+/** The type of TCP socket. */
+enum socketType {
+ SOCKET,
+ SERVER,
+}
+
+interface AddressInfo {
+ address: string;
+ family?: number;
+ port: number;
+}
+
+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;
+ override reading = false;
+
+ #address?: string;
+ #port?: number;
+
+ #remoteAddress?: string;
+ #remoteFamily?: number;
+ #remotePort?: number;
+
+ #backlog?: number;
+ #listener!: Deno.Listener;
+ #connections = 0;
+
+ #closed = false;
+ #acceptBackoffDelay?: number;
+
+ /**
+ * Creates a new TCP class instance.
+ * @param type The socket type.
+ * @param conn Optional connection object to wrap.
+ */
+ 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);
+
+ // TODO(cmorten): the handling of new connections and construction feels
+ // a little off. Suspect duplicating in some fashion.
+ 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);
+ }
+ }
+
+ /**
+ * Opens a file descriptor.
+ * @param fd The file descriptor to open.
+ * @return An error status code.
+ */
+ open(_fd: number): number {
+ // REF: https://github.com/denoland/deno/issues/6529
+ notImplemented("TCP.prototype.open");
+ }
+
+ /**
+ * Bind to an IPv4 address.
+ * @param address The hostname to bind to.
+ * @param port The port to bind to
+ * @return An error status code.
+ */
+ bind(address: string, port: number): number {
+ return this.#bind(address, port, 0);
+ }
+
+ /**
+ * Bind to an IPv6 address.
+ * @param address The hostname to bind to.
+ * @param port The port to bind to
+ * @return An error status code.
+ */
+ bind6(address: string, port: number, flags: number): number {
+ return this.#bind(address, port, flags);
+ }
+
+ /**
+ * Connect to an IPv4 address.
+ * @param req A TCPConnectWrap instance.
+ * @param address The hostname to connect to.
+ * @param port The port to connect to.
+ * @return An error status code.
+ */
+ connect(req: TCPConnectWrap, address: string, port: number): number {
+ return this.#connect(req, address, port);
+ }
+
+ /**
+ * Connect to an IPv6 address.
+ * @param req A TCPConnectWrap instance.
+ * @param address The hostname to connect to.
+ * @param port The port to connect to.
+ * @return An error status code.
+ */
+ connect6(req: TCPConnectWrap, address: string, port: number): number {
+ return this.#connect(req, address, port);
+ }
+
+ /**
+ * Listen for new connections.
+ * @param backlog The maximum length of the queue of pending connections.
+ * @return An error status code.
+ */
+ 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")!;
+ } else if (e instanceof Deno.errors.PermissionDenied) {
+ throw e;
+ }
+
+ // TODO(cmorten): map errors to appropriate error codes.
+ 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;
+ }
+
+ override ref() {
+ if (this.#listener) {
+ this.#listener.ref();
+ }
+
+ if (this[kStreamBaseField]) {
+ this[kStreamBaseField].ref();
+ }
+ }
+
+ override unref() {
+ if (this.#listener) {
+ this.#listener.unref();
+ }
+
+ if (this[kStreamBaseField]) {
+ this[kStreamBaseField].unref();
+ }
+ }
+
+ /**
+ * Populates the provided object with local address entries.
+ * @param sockname An object to add the local address entries to.
+ * @return An error status code.
+ */
+ 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);
+
+ return 0;
+ }
+
+ /**
+ * Populates the provided object with remote address entries.
+ * @param peername An object to add the remote address entries to.
+ * @return An error status code.
+ */
+ 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;
+ }
+
+ /**
+ * @param noDelay
+ * @return An error status code.
+ */
+ setNoDelay(_noDelay: boolean): number {
+ // TODO(bnoordhuis) https://github.com/denoland/deno/pull/13103
+ return 0;
+ }
+
+ /**
+ * @param enable
+ * @param initialDelay
+ * @return An error status code.
+ */
+ setKeepAlive(_enable: boolean, _initialDelay: number): number {
+ // TODO(bnoordhuis) https://github.com/denoland/deno/pull/13103
+ return 0;
+ }
+
+ /**
+ * Windows only.
+ *
+ * Deprecated by Node.
+ * REF: https://github.com/nodejs/node/blob/master/lib/net.js#L1731
+ *
+ * @param enable
+ * @return An error status code.
+ * @deprecated
+ */
+ setSimultaneousAccepts(_enable: boolean) {
+ // Low priority to implement owing to it being deprecated in Node.
+ notImplemented("TCP.prototype.setSimultaneousAccepts");
+ }
+
+ /**
+ * Bind to an IPv4 or IPv6 address.
+ * @param address The hostname to bind to.
+ * @param port The port to bind to
+ * @param _flags
+ * @return An error status code.
+ */
+ #bind(address: string, port: number, _flags: number): number {
+ // Deno doesn't currently separate bind from connect etc.
+ // REF:
+ // - https://doc.deno.land/deno/stable/~/Deno.connect
+ // - https://doc.deno.land/deno/stable/~/Deno.listen
+ //
+ // This also means we won't be connecting from the specified local address
+ // and port as providing these is not an option in Deno.
+ // REF:
+ // - https://doc.deno.land/deno/stable/~/Deno.ConnectOptions
+ // - https://doc.deno.land/deno/stable/~/Deno.ListenOptions
+
+ this.#address = address;
+ this.#port = port;
+
+ return 0;
+ }
+
+ /**
+ * Connect to an IPv4 or IPv6 address.
+ * @param req A TCPConnectWrap instance.
+ * @param address The hostname to connect to.
+ * @param port The port to connect to.
+ * @return An error status code.
+ */
+ #connect(req: TCPConnectWrap, address: string, port: number): number {
+ this.#remoteAddress = address;
+ this.#remotePort = port;
+ this.#remoteFamily = isIP(address);
+
+ const connectOptions: Deno.ConnectOptions = {
+ hostname: address,
+ port,
+ transport: "tcp",
+ };
+
+ Deno.connect(connectOptions).then(
+ (conn: Deno.Conn) => {
+ // Incorrect / backwards, but correcting the local address and port with
+ // what was actually used given we can't actually specify these in Deno.
+ 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 {
+ // swallow callback errors.
+ }
+ },
+ () => {
+ try {
+ // TODO(cmorten): correct mapping of connection error to status code.
+ this.afterConnect(req, codeMap.get("ECONNREFUSED")!);
+ } catch {
+ // swallow callback errors.
+ }
+ },
+ );
+
+ return 0;
+ }
+
+ /** Handle backoff delays following an unsuccessful accept. */
+ async #acceptBackoff() {
+ // Backoff after transient errors to allow time for the system to
+ // recover, and avoid blocking up the event loop with a continuously
+ // running loop.
+ 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();
+ }
+
+ /** Accept new connections. */
+ 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) {
+ // Listener and server has closed.
+ return;
+ }
+
+ try {
+ // TODO(cmorten): map errors to appropriate error codes.
+ this.onconnection!(codeMap.get("UNKNOWN")!, undefined);
+ } catch {
+ // swallow callback errors.
+ }
+
+ this.#acceptBackoff();
+
+ return;
+ }
+
+ // Reset the backoff delay upon successful accept.
+ this.#acceptBackoffDelay = undefined;
+
+ const connectionHandle = new TCP(socketType.SOCKET, connection);
+ this.#connections++;
+
+ try {
+ this.onconnection!(0, connectionHandle);
+ } catch {
+ // swallow callback errors.
+ }
+
+ return this.#accept();
+ }
+
+ /** Handle server closure. */
+ override _onClose(): 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 {
+ // listener already closed
+ }
+ }
+
+ return LibuvStreamWrap.prototype._onClose.call(this);
+ }
+}