diff options
author | hazæ41 <hazae41@gmail.com> | 2020-02-21 17:26:54 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-02-21 11:26:54 -0500 |
commit | 08686cbc3ae63008837ee45b2c4f41d6674c57dd (patch) | |
tree | 19367caaba8e2f9abca60a06a40561c86c18cd59 /cli/js/net.ts | |
parent | dd8a10948195f231a6a9eb652e3f208813904ad6 (diff) |
feat: support UDP sockets (#3946)
Diffstat (limited to 'cli/js/net.ts')
-rw-r--r-- | cli/js/net.ts | 119 |
1 files changed, 103 insertions, 16 deletions
diff --git a/cli/js/net.ts b/cli/js/net.ts index a89468f02..9d82a3a3f 100644 --- a/cli/js/net.ts +++ b/cli/js/net.ts @@ -4,7 +4,7 @@ import { read, write, close } from "./files.ts"; import * as dispatch from "./dispatch.ts"; import { sendSync, sendAsync } from "./dispatch_json.ts"; -export type Transport = "tcp"; +export type Transport = "tcp" | "udp"; // TODO support other types: // export type Transport = "tcp" | "tcp4" | "tcp6" | "unix" | "unixpacket"; @@ -14,6 +14,31 @@ export interface Addr { port: number; } +export interface UDPAddr { + transport?: Transport; + hostname?: string; + port: number; +} + +/** A socket is a generic transport listener for message-oriented protocols */ +export interface UDPConn extends AsyncIterator<[Uint8Array, Addr]> { + /** Waits for and resolves to the next message to the `Socket`. */ + receive(p?: Uint8Array): Promise<[Uint8Array, Addr]>; + + /** Sends a message to the target. */ + send(p: Uint8Array, addr: UDPAddr): Promise<void>; + + /** Close closes the socket. Any pending message promises will be rejected + * with errors. + */ + close(): void; + + /** Return the address of the `Socket`. */ + addr: Addr; + + [Symbol.asyncIterator](): AsyncIterator<[Uint8Array, Addr]>; +} + /** A Listener is a generic transport listener for stream-oriented protocols. */ export interface Listener extends AsyncIterator<Conn> { /** Waits for and resolves to the next connection to the `Listener`. */ @@ -87,7 +112,7 @@ export class ConnImpl implements Conn { export class ListenerImpl implements Listener { constructor( readonly rid: number, - public addr: Addr, + readonly addr: Addr, private closing: boolean = false ) {} @@ -123,6 +148,63 @@ export class ListenerImpl implements Listener { } } +export async function recvfrom( + rid: number, + p: Uint8Array +): Promise<[number, Addr]> { + const { size, remoteAddr } = await sendAsync(dispatch.OP_RECEIVE, { rid }, p); + return [size, remoteAddr]; +} + +export class UDPConnImpl implements UDPConn { + constructor( + readonly rid: number, + readonly addr: Addr, + public bufSize: number = 1024, + private closing: boolean = false + ) {} + + async receive(p?: Uint8Array): Promise<[Uint8Array, Addr]> { + const buf = p || new Uint8Array(this.bufSize); + const [size, remoteAddr] = await recvfrom(this.rid, buf); + const sub = buf.subarray(0, size); + return [sub, remoteAddr]; + } + + async send(p: Uint8Array, addr: UDPAddr): Promise<void> { + const remote = { hostname: "127.0.0.1", transport: "udp", ...addr }; + if (remote.transport !== "udp") throw Error("Remote transport must be UDP"); + const args = { ...remote, rid: this.rid }; + await sendAsync(dispatch.OP_SEND, args, p); + } + + close(): void { + this.closing = true; + close(this.rid); + } + + async next(): Promise<IteratorResult<[Uint8Array, Addr]>> { + if (this.closing) { + return { value: undefined, done: true }; + } + return await this.receive() + .then(value => ({ value, done: false })) + .catch(e => { + // It wouldn't be correct to simply check this.closing here. + // TODO: Get a proper error kind for this case, don't check the message. + // The current error kind is Other. + if (e.message == "Socket has been closed") { + return { value: undefined, done: true }; + } + throw e; + }); + } + + [Symbol.asyncIterator](): AsyncIterator<[Uint8Array, Addr]> { + return this; + } +} + export interface Conn extends Reader, Writer, Closer { /** The local address of the connection. */ localAddr: Addr; @@ -146,14 +228,16 @@ export interface ListenOptions { transport?: Transport; } +const listenDefaults = { hostname: "0.0.0.0", transport: "tcp" }; + /** Listen announces on the local transport address. * * @param options * @param options.port The port to connect to. (Required.) * @param options.hostname A literal IP address or host name that can be * resolved to an IP address. If not specified, defaults to 0.0.0.0 - * @param options.transport Defaults to "tcp". Later we plan to add "tcp4", - * "tcp6", "udp", "udp4", "udp6", "ip", "ip4", "ip6", "unix", "unixgram" and + * @param options.transport Must be "tcp" or "udp". Defaults to "tcp". Later we plan to add "tcp4", + * "tcp6", "udp4", "udp6", "ip", "ip4", "ip6", "unix", "unixgram" and * "unixpacket". * * Examples: @@ -163,16 +247,19 @@ export interface ListenOptions { * listen({ hostname: "[2001:db8::1]", port: 80 }); * listen({ hostname: "golang.org", port: 80, transport: "tcp" }) */ -export function listen(options: ListenOptions): Listener { - const hostname = options.hostname || "0.0.0.0"; - const transport = options.transport || "tcp"; - - const res = sendSync(dispatch.OP_LISTEN, { - hostname, - port: options.port, - transport - }); - return new ListenerImpl(res.rid, res.localAddr); +export function listen( + options: ListenOptions & { transport?: "tcp" } +): Listener; +export function listen(options: ListenOptions & { transport: "udp" }): UDPConn; +export function listen(options: ListenOptions): Listener | UDPConn { + const args = { ...listenDefaults, ...options }; + const res = sendSync(dispatch.OP_LISTEN, args); + + if (args.transport === "tcp") { + return new ListenerImpl(res.rid, res.localAddr); + } else { + return new UDPConnImpl(res.rid, res.localAddr); + } } export interface ConnectOptions { @@ -189,8 +276,8 @@ const connectDefaults = { hostname: "127.0.0.1", transport: "tcp" }; * @param options.port The port to connect to. (Required.) * @param options.hostname A literal IP address or host name that can be * resolved to an IP address. If not specified, defaults to 127.0.0.1 - * @param options.transport Defaults to "tcp". Later we plan to add "tcp4", - * "tcp6", "udp", "udp4", "udp6", "ip", "ip4", "ip6", "unix", "unixgram" and + * @param options.transport Must be "tcp" or "udp". Defaults to "tcp". Later we plan to add "tcp4", + * "tcp6", "udp4", "udp6", "ip", "ip4", "ip6", "unix", "unixgram" and * "unixpacket". * * Examples: |