char/avril

(wip) why'd you have to go and make things so complicaaaaated?

git clone https://git.t4t.associates/char/avril

Charlotte Somgenerate std typedefs from typescript impl38d7f6c

main
20.9 KiB710 linesraw
1import {
2  ArrayIsArray,
3  getter,
4  NumberIsFinite,
5  ObjectCreate,
6  ObjectDefineProperty as define,
7  ObjectFreeze as freeze,
8  RangeError,
9  TypedArrayPrototypeGetBuffer as bufferOf,
10  TypedArrayPrototypeGetByteLength as length,
11  TypedArrayPrototypeGetByteOffset as offsetOf,
12  TypedArrayPrototypeGetTag as tag,
13  TypeError,
14  Uint8Array,
15  Uint8ArrayPrototypeSet as setBytes,
16  uncurryThis,
17  WeakMap,
18  WeakMapPrototypeSet as set,
19} from "ext:core/primordials";
20import {
21  promiseResolve,
22  promiseThen as then,
23  safePromise,
24} from "ext:core/promises";
25import {
26  op_net_accept,
27  op_net_close,
28  op_net_connect_start,
29  op_net_connected,
30  op_net_datagram_broadcast,
31  op_net_datagram_listen,
32  op_net_datagram_receive,
33  op_net_datagram_send,
34  op_net_listen,
35  op_net_read,
36  op_net_shutdown,
37  op_net_write,
38  op_net_writev,
39} from "ext:core/ops";
40import type {
41  NetAddress,
42  NetConnection,
43  NetListener,
44  NetListenerId,
45  NetUdpAddress,
46} from "ext:core/ops";
47import { array, dictionary, state, usv } from "ext:web/idl.js";
48import { addListener, removeListener, signals } from "ext:web/events.js";
49import { registerUncloneable } from "ext:web/serialization.js";
50import {
51  byobRequest,
52  enqueue,
53  errorStream,
54  finish,
55  ReadableStream,
56  ReadableStreamBYOBRequest,
57  WritableStream,
58} from "ext:webstreams/streams.js";
59import type {
60  ReadableByteStreamController,
61  WritableStreamDefaultController,
62} from "ext:webstreams/streams.js";
63import { failure, result } from "ext:file/errors.js";
64
65interface SocketData {
66  id: number | null;
67  owner: object;
68  reader?: ReadableByteStreamController | null;
69  writer?: WritableStreamDefaultController | null;
70}
71interface ConnectionData<A extends NetAddress = NetAddress> extends SocketData {
72  writing: Promise<void>;
73  local: A;
74  remote: A;
75  readable: ReadableStream<Uint8Array<ArrayBuffer>> | null;
76  writable: WritableStream<Uint8Array<ArrayBufferLike>> | null;
77  reader: ReadableByteStreamController | null;
78  writer: WritableStreamDefaultController | null;
79}
80interface ListenerData<A extends NetAddress = NetAddress> extends SocketData {
81  id: NetListenerId<A> | null;
82  local: A;
83}
84interface DatagramData extends SocketData {
85  writing: Promise<void>;
86  local: Readonly<NetUdpAddress>;
87}
88type Datagram = [
89  bytes: Uint8Array<ArrayBufferLike>,
90  remote: Readonly<NetUdpAddress>,
91];
92const connections = new WeakMap<object, ConnectionData>(),
93  listeners = new WeakMap<object, ListenerData>(),
94  datagrams = new WeakMap<object, DatagramData>();
95registerUncloneable(connections);
96registerUncloneable(listeners);
97registerUncloneable(datagrams);
98const FinalizationRegistry = globalThis.FinalizationRegistry;
99const register = uncurryThis(
100  (FinalizationRegistry.prototype as FinalizationRegistry<number>).register,
101);
102const unregister = uncurryThis(FinalizationRegistry.prototype.unregister);
103const registry = new FinalizationRegistry((id: number) => op_net_close(id));
104const getView = getter(ReadableStreamBYOBRequest.prototype, "view");
105const respond = uncurryThis(ReadableStreamBYOBRequest.prototype.respond);
106const writableError = uncurryThis(
107  WritableStreamDefaultController.prototype.error,
108);
109const dispose: typeof Symbol.dispose = Symbol.dispose,
110  asyncIterator: typeof Symbol.asyncIterator = Symbol.asyncIterator,
111  empty = new Uint8Array();
112const safeInteger = Number.isSafeInteger;
113
114function args<T extends unknown[]>(...items: T): T {
115  const value = array();
116  for (let i = 0; i < items.length; i++) value[i] = items[i];
117  return value as T;
118}
119function address<A extends NetAddress | NetUdpAddress>(value: A): A {
120  const copy: (NetAddress | NetUdpAddress) & { __proto__?: null } =
121    value.transport === "unix"
122      ? { __proto__: null, transport: "unix", path: value.path }
123      : {
124        __proto__: null,
125        transport: value.transport,
126        hostname: value.hostname,
127        port: value.port,
128      };
129  return freeze(copy) as A;
130}
131function options(
132  value: unknown,
133  connecting = false,
134): NetAddress & { __proto__?: null } {
135  const options = dictionary(value);
136  const transport = options.transport,
137    host = options.hostname,
138    inputPort = options.port,
139    path = options.path;
140  if (transport === "unix") {
141    if (host !== undefined || inputPort !== undefined) {
142      throw new TypeError("Unix sockets cannot specify hostname or port");
143    }
144    if (path === undefined) throw new TypeError("Unix sockets require a path");
145    const label = usv(path);
146    if (!label) throw new TypeError("Unix sockets require a path");
147    return { __proto__: null, transport: "unix", path: label };
148  }
149  if (path !== undefined) {
150    throw new TypeError("Socket paths require Unix transport");
151  }
152  const port = +(inputPort as number);
153  if (transport !== undefined && transport !== "tcp") {
154    throw new TypeError("Expected TCP transport");
155  }
156  if (
157    !NumberIsFinite(port) || port < (connecting ? 1 : 0) || port > 65535 ||
158    port % 1 !== 0
159  ) throw new RangeError("Invalid socket port");
160  return {
161    __proto__: null,
162    transport: "tcp" as const,
163    hostname: host === undefined ? "127.0.0.1" : usv(host),
164    port,
165  };
166}
167function id<I extends number>(data: { id: I | null }): I {
168  if (data.id === null) throw failure("BadResource", "Socket is closed");
169  return data.id;
170}
171function bytes(value: unknown): Uint8Array<ArrayBufferLike> {
172  if (tag(value) !== "Uint8Array") {
173    throw new TypeError("Socket I/O requires a Uint8Array");
174  }
175  setBytes(value as Uint8Array<ArrayBufferLike>, empty);
176  return value as Uint8Array<ArrayBufferLike>;
177}
178function segment<B extends ArrayBufferLike>(
179  value: Uint8Array<B>,
180  offset = 0,
181): Uint8Array<B> {
182  const count = length(value) - offset;
183  return new Uint8Array(
184    bufferOf(value),
185    offsetOf(value) + offset,
186    count < 65536 ? count : 65536,
187  );
188}
189function vectors(buffers: unknown): Uint8Array<ArrayBuffer>[] {
190  if (!ArrayIsArray(buffers)) {
191    throw new TypeError("Socket I/O requires an array of Uint8Array");
192  }
193  // Snapshot before queueing, without rereading caller-controlled elements.
194  const count = buffers.length, pieces: Uint8Array<ArrayBuffer>[] = args();
195  if (!safeInteger(count) || count < 0 || count > 4294967295) {
196    throw new RangeError("Invalid vector length");
197  }
198  let total = 0;
199  for (let i = 0; i < count; i++) {
200    const view: unknown = buffers[i];
201    if (tag(view) !== "Uint8Array") {
202      throw new TypeError("Socket I/O requires an array of Uint8Array");
203    }
204    setBytes(view as Uint8Array<ArrayBufferLike>, empty);
205    const size = length(view);
206    if (size === 0) continue;
207    if (!safeInteger(total + size)) {
208      throw new RangeError("Vector exceeds safe integer range");
209    }
210    total += size;
211    // Fixed snapshot for the native call, which needs unshared storage.
212    pieces[pieces.length] = new Uint8Array(
213      new Uint8Array(
214        bufferOf(view),
215        offsetOf(view),
216        size,
217      ),
218    );
219  }
220  return pieces;
221}
222function close(
223  data: SocketData,
224  quiet = false,
225  source: "read" | "write" | null = null,
226): void {
227  if (data.id === null) {
228    if (!quiet) id(data);
229    return;
230  }
231  const current = data.id;
232  data.id = null;
233  unregister(registry, data.owner);
234  const error = failure("BadResource", "Socket is closed");
235  if (data.reader && source !== "read") errorStream(data.reader, error);
236  if (data.writer && source !== "write") writableError(data.writer, error);
237  const reply = op_net_close(current);
238  if (!quiet) result(reply);
239}
240function queue<T>(
241  data: { writing: Promise<void> },
242  operation: () => T | PromiseLike<T>,
243): Promise<T> {
244  const promise = then(data.writing, operation, operation);
245  data.writing = then(promise, () => {}, () => {});
246  return promise;
247}
248const socketKey = Symbol();
249type TcpAddr = Extract<NetAddress, { transport: "tcp" }>;
250type UnixAddr = Extract<NetAddress, { transport: "unix" }>;
251type TcpOptions = {
252  transport?: "tcp";
253  hostname?: string;
254  port: number;
255  signal?: AbortSignal;
256};
257type UnixOptions = {
258  transport: "unix";
259  path: string;
260  hostname?: never;
261  port?: never;
262  signal?: AbortSignal;
263};
264
265class Conn<A extends NetAddress = NetAddress> {
266  declare static readonly [Symbol.hasInstance]: (value: unknown) => value is Conn;
267  #data: ConnectionData<A>;
268
269  constructor(...args: [typeof socketKey, NetConnection<A>]) {
270    const key = args[0], row = args[1];
271    if (key !== socketKey) throw new TypeError("Illegal constructor");
272    this.#data = {
273      id: row[0],
274      owner: this,
275      local: address(row[1]),
276      remote: address(row[2]),
277      readable: null,
278      writable: null,
279      reader: null,
280      writer: null,
281      writing: promiseResolve(undefined),
282    };
283    set(connections, this, this.#data);
284    register(registry, this, row[0], this);
285  }
286  get localAddr(): A {
287    state(connections, this);
288    return this.#data.local;
289  }
290  get remoteAddr(): A {
291    state(connections, this);
292    return this.#data.remote;
293  }
294  async read(buffer: Uint8Array<ArrayBufferLike>): Promise<number | null> {
295    const data = state(connections, this), current = id(data);
296    bytes(buffer);
297    const chunk = result(
298      await safePromise(op_net_read(args(current, length(buffer)))),
299    );
300    setBytes(buffer, empty);
301    if (chunk === null) return null;
302    setBytes(buffer, chunk);
303    return length(chunk);
304  }
305  write(buffer: Uint8Array<ArrayBufferLike>): Promise<number> {
306    const data = state(connections, this);
307    id(data);
308    const snapshot = new Uint8Array(segment(bytes(buffer)));
309    return queue(
310      data,
311      () => then(op_net_write(args(id(data), snapshot)), result),
312    );
313  }
314  writev(buffers: unknown): Promise<number> {
315    const data = state(connections, this);
316    id(data);
317    const snapshots = vectors(buffers);
318    return queue(data, async () => {
319      if (snapshots.length === 0) {
320        return result(
321          await safePromise(op_net_writev(args(id(data), snapshots))),
322        );
323      }
324      let total = 0, elem = 0, inner = 0;
325      while (elem < snapshots.length) {
326        const batch: Uint8Array<ArrayBuffer>[] = args();
327        let budget = 65536, scan = elem, scanInner = inner;
328        while (scan < snapshots.length && batch.length < 16 && budget > 0) {
329          const piece = snapshots[scan], size = length(piece) - scanInner;
330          if (size === 0) {
331            scan++;
332            scanInner = 0;
333            continue;
334          }
335          const take = size < budget ? size : budget;
336          batch[batch.length] = new Uint8Array(
337            bufferOf(piece),
338            offsetOf(piece) + scanInner,
339            take,
340          );
341          budget -= take;
342          scanInner += take;
343          if (scanInner >= length(piece)) {
344            scan++;
345            scanInner = 0;
346          }
347        }
348        const count = result(
349          await safePromise(op_net_writev(args(id(data), batch))),
350        );
351        if (!safeInteger(count) || count <= 0 || count > 65536 - budget) {
352          throw failure("IoError", "Invalid partial socket write");
353        }
354        total += count;
355        let skip = count;
356        while (skip > 0) {
357          const remaining = length(snapshots[elem]) - inner;
358          if (remaining === 0) {
359            elem++;
360            inner = 0;
361            continue;
362          }
363          if (skip < remaining) {
364            inner += skip;
365            break;
366          }
367          skip -= remaining;
368          elem++;
369          inner = 0;
370        }
371      }
372      return total;
373    });
374  }
375  closeWrite(): Promise<void> {
376    const data = state(connections, this);
377    id(data);
378    return queue(data, () =>
379      then(op_net_shutdown(id(data)), (reply) => {
380        result(reply);
381      }));
382  }
383  close() {
384    close(state(connections, this));
385  }
386  [dispose]() {
387    close(state(connections, this), true);
388  }
389  get readable() {
390    const data = state(connections, this);
391    if (data.readable) return data.readable;
392    id(data);
393    data.readable = new ReadableStream({
394      type: "bytes",
395      start(controller) {
396        data.reader = controller;
397      },
398      async pull(controller) {
399        try {
400          const request = byobRequest(controller),
401            view = request && getView(request);
402          const chunk = result(
403            await safePromise(
404              op_net_read(args(id(data), view ? length(view) : 65536)),
405            ),
406          );
407          if (data.id === null) return;
408          if (chunk === null) finish(controller);
409          else if (request) {
410            setBytes(view!, chunk);
411            respond(request, length(chunk));
412          } else enqueue(controller, chunk);
413        } catch (error) {
414          errorStream(controller, error);
415          close(data, true, "read");
416        }
417      },
418      cancel() {
419        close(data, true, "read");
420      },
421    }, { highWaterMark: 0 });
422    return data.readable;
423  }
424  get writable() {
425    const data = state(connections, this);
426    if (data.writable) return data.writable;
427    id(data);
428    data.writable = new WritableStream({
429      start(controller) {
430        data.writer = controller;
431      },
432      async write(value) {
433        try {
434          const snapshot = new Uint8Array(bytes(value));
435          await safePromise(queue(data, async () => {
436            let offset = 0;
437            while (offset < length(snapshot)) {
438              const count = result(
439                await safePromise(
440                  op_net_write(
441                    args(
442                      id(data),
443                      segment(snapshot, offset),
444                    ),
445                  ),
446                ),
447              );
448              if (count <= 0 || count > length(snapshot) - offset) {
449                throw failure("IoError", "Invalid partial socket write");
450              }
451              offset += count;
452            }
453          }));
454        } catch (error) {
455          close(data, true, "write");
456          throw error;
457        }
458      },
459      close() {
460        return queue(data, () => then(op_net_shutdown(id(data)), result));
461      },
462      abort() {
463        close(data, true, "write");
464      },
465    }, {
466      highWaterMark: 65536,
467      size(value) {
468        return tag(value) === "Uint8Array" ? length(value) : 1;
469      },
470    });
471    return data.writable;
472  }
473}
474function connection<A extends NetAddress>(row: NetConnection<A>): Conn<A> {
475  return new Conn(socketKey, row);
476}
477class Listener<A extends NetAddress = NetAddress> {
478  declare static readonly [Symbol.hasInstance]: (value: unknown) => value is Listener;
479  #data: ListenerData<A>;
480
481  constructor(...args: [typeof socketKey, NetListener<A>]) {
482    const key = args[0], row = args[1];
483    if (key !== socketKey) throw new TypeError("Illegal constructor");
484    this.#data = { id: row[0], owner: this, local: address(row[1]) };
485    set(listeners, this, this.#data);
486    register(registry, this, row[0], this);
487  }
488  get addr(): A {
489    state(listeners, this);
490    return this.#data.local;
491  }
492  async accept(): Promise<Conn<A>> {
493    state(listeners, this);
494    return connection(
495      result(await safePromise(op_net_accept(id(this.#data)))),
496    );
497  }
498  close() {
499    close(state(listeners, this));
500  }
501  [dispose]() {
502    close(state(listeners, this), true);
503  }
504  [asyncIterator](): AsyncIterableIterator<Conn<A>, undefined> {
505    state(listeners, this);
506    const data = this.#data;
507    return {
508      async next() {
509        if (data.id === null) return { done: true, value: undefined };
510        try {
511          return {
512            done: false,
513            value: connection(
514              result(await safePromise(op_net_accept(id(data)))),
515            ),
516          };
517        } catch (error) {
518          if (data.id === null) return { done: true, value: undefined };
519          throw error;
520        }
521      },
522      return() {
523        close(data, true);
524        return promiseResolve({ done: true, value: undefined });
525      },
526      [asyncIterator]() {
527        return this;
528      },
529    };
530  }
531}
532function listen(value: TcpOptions): Listener<TcpAddr>;
533function listen(value: UnixOptions): Listener<UnixAddr>;
534function listen(value: unknown): Listener {
535  const row = result(op_net_listen(options(value)));
536  return new Listener(socketKey, row);
537}
538function connect(value: TcpOptions): Promise<Conn<TcpAddr>>;
539function connect(value: UnixOptions): Promise<Conn<UnixAddr>>;
540async function connect(value: unknown): Promise<Conn> {
541  const settings = dictionary(value);
542  const target = options(settings, true), signal = settings.signal;
543  if (signal !== undefined && state(signals, signal).aborted) {
544    throw state(signals, signal).reason;
545  }
546  const current = result(op_net_connect_start(target));
547  const cancel = () => op_net_close(current);
548  if (signal !== undefined) {
549    addListener(signal, "abort", cancel, { once: true });
550  }
551  try {
552    const row = result(await safePromise(op_net_connected(current)));
553    if (signal !== undefined && state(signals, signal).aborted) {
554      cancel();
555      throw state(signals, signal).reason;
556    }
557    return connection(row);
558  } catch (error) {
559    cancel();
560    if (signal !== undefined && state(signals, signal).aborted) {
561      throw state(signals, signal).reason;
562    }
563    throw error;
564  } finally {
565    if (signal !== undefined) removeListener(signal, "abort", cancel);
566  }
567}
568function datagramOptions(
569  value: unknown,
570  sending = false,
571): NetUdpAddress & { __proto__?: null } {
572  const options = dictionary(value);
573  const transport = options.transport,
574    inputHost = options.hostname,
575    inputPort = options.port,
576    path = options.path;
577  if (transport !== undefined && transport !== "udp") {
578    throw new TypeError("Expected UDP transport");
579  }
580  if (path !== undefined) {
581    throw new TypeError("UDP sockets cannot specify a path");
582  }
583  const port = +(inputPort as number);
584  if (
585    !NumberIsFinite(port) || port < (sending ? 1 : 0) || port > 65535 ||
586    port % 1 !== 0
587  ) throw new RangeError("Invalid UDP port");
588  return {
589    __proto__: null,
590    transport: "udp" as const,
591    hostname: inputHost === undefined ? "127.0.0.1" : usv(inputHost),
592    port,
593  };
594}
595async function receiveDatagram(
596  data: DatagramData,
597  buffer: Uint8Array<ArrayBufferLike> | undefined,
598): Promise<Datagram> {
599  const current = id(data);
600  if (buffer !== undefined) bytes(buffer);
601  const row = result(
602    await safePromise(
603      op_net_datagram_receive(
604        args(current, buffer === undefined ? 65535 : length(buffer)),
605      ),
606    ),
607  );
608  let message: Uint8Array<ArrayBufferLike> = row[0];
609  if (buffer !== undefined) {
610    setBytes(buffer, empty);
611    setBytes(buffer, message);
612    message = new Uint8Array(
613      bufferOf(buffer),
614      offsetOf(buffer),
615      length(message),
616    );
617  }
618  return [message, address(row[1])];
619}
620class DatagramConn {
621  declare static readonly [Symbol.hasInstance]: (value: unknown) => value is DatagramConn;
622  private constructor() {
623    throw new TypeError("Illegal constructor");
624  }
625  get addr() {
626    return state(datagrams, this).local;
627  }
628  receive(
629    buffer: Uint8Array<ArrayBufferLike> | undefined = undefined,
630  ): Promise<Datagram> {
631    return receiveDatagram(state(datagrams, this), buffer);
632  }
633  send(buffer: Uint8Array<ArrayBufferLike>, target: unknown): Promise<number> {
634    const data = state(datagrams, this);
635    id(data);
636    bytes(buffer);
637    if (length(buffer) > 65507) {
638      throw new RangeError("Datagram exceeds 65507 bytes");
639    }
640    const snapshot: Uint8Array<ArrayBuffer> = new Uint8Array(buffer),
641      destination = datagramOptions(target, true);
642    return queue(
643      data,
644      () =>
645        then(
646          op_net_datagram_send(args(id(data), snapshot, destination)),
647          result,
648        ),
649    );
650  }
651  setBroadcast(enabled = true): void {
652    result(
653      op_net_datagram_broadcast(args(id(state(datagrams, this)), !!enabled)),
654    );
655  }
656  close() {
657    close(state(datagrams, this));
658  }
659  [dispose]() {
660    close(state(datagrams, this), true);
661  }
662  [asyncIterator](): AsyncIterableIterator<Datagram, undefined> {
663    const data = state(datagrams, this);
664    return {
665      async next() {
666        if (data.id === null) return { done: true, value: undefined };
667        try {
668          return {
669            done: false,
670            value: await safePromise(receiveDatagram(data, undefined)),
671          };
672        } catch (error) {
673          if (data.id === null) return { done: true, value: undefined };
674          throw error;
675        }
676      },
677      return() {
678        close(data, true);
679        return promiseResolve({ done: true, value: undefined });
680      },
681      [asyncIterator]() {
682        return this;
683      },
684    };
685  }
686}
687function listenDatagram(value: unknown): DatagramConn {
688  const row = result(op_net_datagram_listen(datagramOptions(value)));
689  const socket = ObjectCreate(DatagramConn.prototype) as DatagramConn;
690  set(datagrams, socket, {
691    id: row[0],
692    owner: socket,
693    local: address(row[1]),
694    writing: promiseResolve(undefined),
695  });
696  register(registry, socket, row[0], socket);
697  return socket;
698}
699for (
700  const pair of [[Conn, "Conn"], [Listener, "Listener"], [
701    DatagramConn,
702    "DatagramConn",
703  ]] as const
704) {
705  define(pair[0].prototype, Symbol.toStringTag, {
706    value: pair[1],
707    configurable: true,
708  });
709}
710export { Conn, connect, DatagramConn, listen, listenDatagram, Listener };