import { AsyncWrap, providerType } from "./async_wrap.ts";import { GetAddrInfoReqWrap } from "./cares_wrap.ts";import { HandleWrap } from "./handle_wrap.ts";import { ownerSymbol } from "./symbols.ts";import { codeMap, errorMap } from "./uv.ts";import { notImplemented } from "../_utils.ts";import { Buffer } from "../buffer.ts";import type { ErrnoException } from "../internal/errors.ts";import { isIP } from "../internal/net.ts";
import { isLinux, isWindows } from "../../_util/os.ts";
const DenoListenDatagram = Deno[Deno.internal]?.nodeUnstable?.listenDatagram || Deno.listenDatagram;
type MessageType = string | Uint8Array | Buffer | DataView;
const AF_INET = 2;const AF_INET6 = 10;
const UDP_DGRAM_MAXSIZE = 64 * 1024;
export class SendWrap extends AsyncWrap { list!: MessageType[]; address!: string; port!: number;
callback!: (error: ErrnoException | null, bytes?: number) => void; oncomplete!: (err: number | null, sent?: number) => void;
constructor() { super(providerType.UDPSENDWRAP); }}
export class UDP extends HandleWrap { [ownerSymbol]: unknown = null;
#address?: string; #family?: string; #port?: number;
#remoteAddress?: string; #remoteFamily?: string; #remotePort?: number;
#listener?: Deno.DatagramConn; #receiving = false;
#recvBufferSize = UDP_DGRAM_MAXSIZE; #sendBufferSize = UDP_DGRAM_MAXSIZE;
onmessage!: ( nread: number, handle: UDP, buf?: Buffer, rinfo?: { address: string; family: "IPv4" | "IPv6"; port: number; size?: number; }, ) => void;
lookup!: ( address: string, callback: ( err: ErrnoException | null, address: string, family: number, ) => void, ) => GetAddrInfoReqWrap | Record<string, never>;
constructor() { super(providerType.UDPWRAP); }
addMembership(_multicastAddress: string, _interfaceAddress?: string): number { notImplemented("udp.UDP.prototype.addMembership"); }
addSourceSpecificMembership( _sourceAddress: string, _groupAddress: string, _interfaceAddress?: string, ): number { notImplemented("udp.UDP.prototype.addSourceSpecificMembership"); }
bind(ip: string, port: number, flags: number): number { return this.#doBind(ip, port, flags, AF_INET); }
bind6(ip: string, port: number, flags: number): number { return this.#doBind(ip, port, flags, AF_INET6); }
bufferSize( size: number, buffer: boolean, ctx: Record<string, string | number>, ): number | undefined { let err: string | undefined;
if (size > UDP_DGRAM_MAXSIZE) { err = "EINVAL"; } else if (!this.#address) { err = isWindows ? "ENOTSOCK" : "EBADF"; }
if (err) { ctx.errno = codeMap.get(err)!; ctx.code = err; ctx.message = errorMap.get(ctx.errno)![1]; ctx.syscall = buffer ? "uv_recv_buffer_size" : "uv_send_buffer_size";
return; }
if (size !== 0) { size = isLinux ? size * 2 : size;
if (buffer) { return (this.#recvBufferSize = size); }
return (this.#sendBufferSize = size); }
return buffer ? this.#recvBufferSize : this.#sendBufferSize; }
connect(ip: string, port: number): number { return this.#doConnect(ip, port, AF_INET); }
connect6(ip: string, port: number): number { return this.#doConnect(ip, port, AF_INET6); }
disconnect(): number { this.#remoteAddress = undefined; this.#remotePort = undefined; this.#remoteFamily = undefined;
return 0; }
dropMembership( _multicastAddress: string, _interfaceAddress?: string, ): number { notImplemented("udp.UDP.prototype.dropMembership"); }
dropSourceSpecificMembership( _sourceAddress: string, _groupAddress: string, _interfaceAddress?: string, ): number { notImplemented("udp.UDP.prototype.dropSourceSpecificMembership"); }
getpeername(peername: Record<string, string | number>): number { if (this.#remoteAddress === undefined) { return codeMap.get("EBADF")!; }
peername.address = this.#remoteAddress; peername.port = this.#remotePort!; peername.family = this.#remoteFamily!;
return 0; }
getsockname(sockname: Record<string, string | number>): number { if (this.#address === undefined) { return codeMap.get("EBADF")!; }
sockname.address = this.#address; sockname.port = this.#port!; sockname.family = this.#family!;
return 0; }
open(_fd: number): number { notImplemented("udp.UDP.prototype.open"); }
recvStart(): number { if (!this.#receiving) { this.#receiving = true; this.#receive(); }
return 0; }
recvStop(): number { this.#receiving = false;
return 0; }
override ref() { notImplemented("udp.UDP.prototype.ref"); }
send( req: SendWrap, bufs: MessageType[], count: number, ...args: [number, string, boolean] | [boolean] ): number { return this.#doSend(req, bufs, count, args, AF_INET); }
send6( req: SendWrap, bufs: MessageType[], count: number, ...args: [number, string, boolean] | [boolean] ): number { return this.#doSend(req, bufs, count, args, AF_INET6); }
setBroadcast(_bool: 0 | 1): number { notImplemented("udp.UDP.prototype.setBroadcast"); }
setMulticastInterface(_interfaceAddress: string): number { notImplemented("udp.UDP.prototype.setMulticastInterface"); }
setMulticastLoopback(_bool: 0 | 1): number { notImplemented("udp.UDP.prototype.setMulticastLoopback"); }
setMulticastTTL(_ttl: number): number { notImplemented("udp.UDP.prototype.setMulticastTTL"); }
setTTL(_ttl: number): number { notImplemented("udp.UDP.prototype.setTTL"); }
override unref() { notImplemented("udp.UDP.prototype.unref"); }
#doBind(ip: string, port: number, _flags: number, family: number): number { const listenOptions = { port, hostname: ip, transport: "udp" as const, };
let listener;
try { listener = DenoListenDatagram(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.#family = family === AF_INET6 ? ("IPv6" as const) : ("IPv4" as const); this.#listener = listener;
return 0; }
#doConnect(ip: string, port: number, family: number): number { this.#remoteAddress = ip; this.#remotePort = port; this.#remoteFamily = family === AF_INET6 ? ("IPv6" as const) : ("IPv4" as const);
return 0; }
#doSend( req: SendWrap, bufs: MessageType[], _count: number, args: [number, string, boolean] | [boolean], _family: number, ): number { let hasCallback: boolean;
if (args.length === 3) { this.#remotePort = args[0] as number; this.#remoteAddress = args[1] as string; hasCallback = args[2] as boolean; } else { hasCallback = args[0] as boolean; }
const addr: Deno.NetAddr = { hostname: this.#remoteAddress!, port: this.#remotePort!, transport: "udp", };
const payload = new Uint8Array( Buffer.concat( bufs.map((buf) => { if (typeof buf === "string") { return Buffer.from(buf); }
return Buffer.from(buf.buffer, buf.byteOffset, buf.byteLength); }), ), );
(async () => { let sent: number; let err: number | null = null;
try { sent = await this.#listener!.send(payload, addr); } catch (e) { if (e instanceof Deno.errors.BadResource) { err = codeMap.get("EBADF")!; } else if ( e instanceof Error && e.message.match(/os error (40|90|10040)/) ) { err = codeMap.get("EMSGSIZE")!; } else { err = codeMap.get("UNKNOWN")!; }
sent = 0; }
if (hasCallback) { try { req.oncomplete(err, sent); } catch { } } })();
return 0; }
async #receive() { if (!this.#receiving) { return; }
const p = new Uint8Array(this.#recvBufferSize);
let buf: Uint8Array; let remoteAddr: Deno.NetAddr | null; let nread: number | null;
try { [buf, remoteAddr] = (await this.#listener!.receive(p)) as [ Uint8Array, Deno.NetAddr, ];
nread = buf.length; } catch (e) { if ( e instanceof Deno.errors.Interrupted || e instanceof Deno.errors.BadResource ) { nread = 0; } else { nread = codeMap.get("UNKNOWN")!; }
buf = new Uint8Array(0); remoteAddr = null; }
nread ??= 0;
const rinfo = remoteAddr ? { address: remoteAddr.hostname, port: remoteAddr.port, family: isIP(remoteAddr.hostname) === 6 ? ("IPv6" as const) : ("IPv4" as const), } : undefined;
try { this.onmessage(nread, this, Buffer.from(buf), rinfo); } catch { }
this.#receive(); }
override _onClose(): number { this.#receiving = false;
this.#address = undefined; this.#port = undefined; this.#family = undefined;
try { this.#listener!.close(); } catch { }
this.#listener = undefined;
return 0; }}