@oazmi/kitchensink
Version:
a collection of personal utility functions
182 lines • 10.8 kB
TypeScript
/** this submodule contains a common description of what a network connection (abbr. `conn`) must implement.
* once you wrap your network primitives over the {@link NetConn} and {@link NetAddr} interfaces,
* you'll be able to utilize a good chunk of this network library to easily create tcp and udp agnostic logic for your server-application.
*
* @module
*/
import { AwaitableQueue } from "../promiseman.js";
import type { MaybePromise, Require } from "../typedefs.js";
/** this enum contains some common default buffer sizes used across the network library.
* in most cases, you can either "bring your own" buffer if theses sizes do not suffice you,
* or sometimes a custom buffer is not even needed, as
* the underlying implementation takes care of using a sufficiently sized buffer
* (such as in the case of `Deno.DatagramConn`, which, I think, always returns a fully loaded buffer when reading).
*/
export declare const enum SIZE {
/** the buffer bytesize of common non-packet based connections (such as tcp). */
BufferBytes = 4096,
/** while the MTU on most routers is set to `1500` bytes, and while practically speaking,
* only ethernet jumboframes would increase this limit to about 9000 bytes,
* if there's a udp packet coming from localhost, this size limit is greatly increased to 64kb on linux,
* 16kb on mac-os, and unlimited bytes on windows.
*
* since the datagram is discarded after a single read (even if you couldn't entirely fit it into your buffer),
* it presents a challenge for supporting udp packets from localhost applications if they do not segment the packets themselves.
* thus, if you experience udp packet data corruption on localhost when receiving packets via this library,
* you can either increase the buffer size set here to something much greater,
* or modify your os-settings to set a 16kb limit on the MTU of udp datagrams.
* - for windows, I found this [gist](https://gist.github.com/odyssey4me/c2f7542f985a953bb1e4).
* although, I haven't tried it or looked at it carefully myself.
*/
DatagramMtu = 16384
}
/** a net address is an ip-address and port-number pair,
* that specifies where a packet is being sent or received from.
*/
export interface NetAddr {
/** either the ip, or domain-name of the network-address.
*
* ### examples
* - `"192.168.100.1"` (ipv4)
* - `"[::ffff:192.168.100.1]"` (ipv4 in [ipv6 representation](https://stackoverflow.com/a/186848))
* - `"example.com"` (a hostname)
* - `"0.0.0.0"` (an address that portrays "all self-host nicknames".
* this is only acceptable for a server/listening. it is not for a client to connect to.)
*
* ### invalid examples
* - `"192.168.100.1/24"` (an ipv4 [CIDR](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing))
* - `"192.168.100.1:8000"` (an ipv4 with a port number)
* - `"[::ffff:192.168.100.1]:8000"` (an ipv6 with a port number)
* - `"http://example.com"` (an http url. a host name is not a url! but the url _does_ contain the host name)
* - `"localhost"` (it is only defined on windows, and may only work in browsers.
* use `"0.0.0.0"`, or `"127.0.0.1"` instead for local loopback)
*/
hostname: string;
/** a 2-byte number (1-65534) that dictates the destination or source port on a specific {@link hostname | host}.
*
* use the special `0` empherial port to let your os assign any general purpose temporary port to you,
* that is guaranteed to be available.
*
* also, I think the `0xFFFF` (65535) port is reserved.
*
* @defaultValue `0`
*/
port: number;
/** specifies whether this address is an ipv4 or an ipv6 address.
* if an ip is in essence an ipv4, but dressed up like an ipv6, such as `"::ffff:192.168.100.1"`,
* then the ip-family should be set to `4` rather than `6`.
*
* @defaultValue `4`
*/
family: 4 | 6;
}
/** the return value of a {@link NetConn | connection} when it is read. */
export type NetConnReadValue = [buffer: Uint8Array<ArrayBuffer>, Addr: NetAddr];
/** our abstract definition of a network connection is simply a _socket_ that can perform asynchronous read and send operations. */
export interface NetConn {
/** the buffer size of the underlying `Uint8Array` of this connection.
*
* when you receive a {@link NetConnReadValue | buffer} of this {@link size} from the {@link read} method,
* it might be an indication that there are already _more remaining_ bytes available that could to be read.
*/
readonly size: number;
/** read the incoming data from the connection into a `Uint8Array` buffer,
* and get the {@link NetAddr | address} from which the message came from.
*
* if a readable data is already available, returned value will be of the {@link NetConnReadValue} kind,
* but if it isn't immediately available, a `Promise` to {@link NetConnReadValue} will be returned.
* this way, you can operate in both, immediate synchronous mode (for tasks, such as polling),
* or in asynchronous mode (for situations where you are anticipating a message).
*
* the awaitable {@link NetConnReadValue} value is a 2-tuple consisting of the `Uint8Array` buffer that's read,
* and the {@link NetAddr} from which the message originates from.
*
* it is possible for a read to successfully return with a buffer of zero bytesize,
* because it would indicate that a zero sized packet was received,
* which is different from _not receiving_ any packets.
*
* TODO: what about partial packet? should the `NetConn` interface demand that they be joined before being presented by this interface?
* or should we allow the implementations to return data as it comes in?
*
* TODO: actually, I think it would be better to define this in a way that _is_ intentionally blocking when awaited,
* rather than saying that it _may_ return `undefined` when it hasn't received any messages.
*
* TODO: I just figured out that tcp, unlike udp, is stream-based.
* meaning that if a client sends us 2 tcp messages, they will be concatenated back to back.
* and if our internal read buffer is large enough, it will consume/contain both messages, with no segmentationg.
*/
read(): MaybePromise<NetConnReadValue>;
/** send the contents of the array buffer to a host with the `addr` network-address, over your connection.
*
* resolves to the number of bytes written. though it is kind of pointless given the TODO conundrum below:
*
* TODO: what should we do about input buffers that are not entirely sent in a single packet?
* should the implementation loop until all of it has been sent? or should it be up to the user of this interface to do that on their own?
*
* for now, I will enforce the rule that the `send` method **must** completely send the buffer before resolving its promise.
* it really makes things simpler for the end user,
* and it only adds one line of code for underlying implementations that do not necessarily send their buffer all in one go.
*/
send(buffer: Uint8Array, addr: NetAddr): Promise<number>;
/** closes the connection on your local device to free up resources. */
close(): void;
}
/** a dictionary with "hostname" (ip) as its keys, and an an array queue of {@link NetConnReadValue} as its value.
*
* uncaught (or untrapped) hostnames are not stored in here. they go to a different queue.
*/
type NetConnSink_traps = Record<string, // hostname
AwaitableQueue<NetConnReadValue>>;
/** a net-connection sink traps certain messages received from specific `hostname`s (ip-addresses),
* while queuing the rest of messages elsewhere.
*
* you can think of it as network-connection with a builtin hostname filter system,
* allowing you prioritize the reading of incoming messages from a certain hostname,
* and later take care of the remaining un-organized/untrapped set of messages,
* when nothing of high-priority is taking place.
*
* the way it works is that you must first set a `hostname` "trap" via the the {@link trapAddr} method,
* and then, to read incoming messages coming from `hostname`,
* you will use the {@link readAddr} method to receive the messages, one at a time.
*/
export declare class NetConnSink<BASE extends NetConn = NetConn> implements NetConn {
#private;
protected readonly base: BASE;
protected readonly trapped: NetConnSink_traps;
protected readonly untrapped: AwaitableQueue<NetConnReadValue>;
readonly size: number;
readonly abortController: AbortController;
constructor(base_conn: BASE, abort_controller?: AbortController);
/** specify a hostname/ip-address to trap its future packets under a separate collection,
* that can be read back via {@link readAddr}.
*/
trapAddr(addr: Require<Partial<NetAddr>, "hostname">): void;
/** remove an address "trap", so that it will no longer be filtered.
* the returned value will contain all unread messages that had been trapped for the given address.
*/
untrapAddr(addr: Require<Partial<NetAddr>, "hostname">): Array<NetConnReadValue>;
/** read incoming messages from a certain "trapped" address.
*
* > [!note]
* > remember, if a message from a certain address, `addr`,
* > made its way through _before_ you add that address to the list of trapped addresses (via {@link trapAddr}),
* > then that message will end up in the "untrapped" category, and you will not receive it through this method.
*/
readAddr(addr: Require<Partial<NetAddr>, "hostname">): MaybePromise<NetConnReadValue>;
/** read incoming "untrapped" messages, that do not fit into any of the existing address traps (added via {@link trapAddr}). */
read(): MaybePromise<NetConnReadValue>;
/** returns the number of remaining untrapped unread messages.
* the value may be negative, indicating that one or more things have already requested to snatch the message as soon as it comes.
*/
remainingUnread(): number;
/** returns the number of remaining unread messages for the specified trapped address.
* the value may be negative, indicating that one or more things have already requested to snatch the message as soon as it comes.
*/
remainingUnreadAddr(addr: Require<Partial<NetAddr>, "hostname">): number;
send(buffer: Uint8Array, addr: NetAddr): Promise<number>;
close(): void;
/** this infinite loop reads all messages as they come in, and then organizes them as needed. */
protected initLoop(): Promise<void>;
}
export {};
//# sourceMappingURL=conn.d.ts.map