# Raw networking `connect` and `listen` from `avril:std` provide plaintext TCP and Unix streams. TLS is not yet implemented. ```ts import { listen } from "avril:std"; const listener = listen({ hostname: "127.0.0.1", port: 9000 }); for await (const connection of listener) { // Handle connections concurrently if one must not block accepting the next. connection.readable.pipeTo(connection.writable).finally(() => connection.close()); } ``` ```ts import { connect } from "avril:std"; const connection = await connect({ hostname: "127.0.0.1", port: 9000 }); const writer = connection.writable.getWriter(); await writer.write(new TextEncoder().encode("hello")); await writer.close(); // Send FIN; receiving remains possible. writer.releaseLock(); const reader = connection.readable.getReader(); while (true) { const { value, done } = await reader.read(); if (done) break; console.log(value); } reader.releaseLock(); connection.close(); ``` ## Addresses and permissions Both functions default to hostname `127.0.0.1`; port is required. `listen` permits port `0`, while `connect` requires a nonzero port. Addresses are immutable `{ transport: "tcp", hostname, port }` objects. A listener exposes `addr`; a connection exposes `localAddr` and `remoteAddr`. TCP requires `net` authority for the requested hostname/port. Port-zero listeners check both port `0` and the selected port; use a hostname-wide grant. Accepting and using accepted connections check listener authority, not an outbound grant for the peer. DNS lookup and connection use the requested hostname's authority. Every accept, read, write, and write shutdown rechecks authority. Connect, accept, and read also recheck after awaiting I/O, before releasing results. `pledge` and worker restrictions apply to existing resources. Closing never requires authority. The CLI HTTP listener exemption does not apply to raw sockets. ## Unix streams ```ts const listener = listen({ transport: "unix", path: "/tmp/avril.sock" }); const connection = await connect({ transport: "unix", path: "/tmp/avril.sock" }); ``` Unix streams use the same `Conn`, half-close, byte-stream, cancellation, and iterator APIs as TCP. Specify `transport: "unix"` and a nonempty path; hostname and port are not accepted. Relative paths resolve against the runtime's working directory. Addresses are `{ transport: "unix", path }`; unnamed local/peer addresses have `path: null`. Both connecting and listening require filesystem **write** authority at the socket path, not `net` or filesystem read authority. Connecting resolves the final symlink and checks authority at its target. Accepts and subsequent connection I/O recheck that same write authority. Closing and unlinking do not need a fresh write grant. Listeners never overwrite existing filesystem entries. Cleanup checks device/inode identity and preserves replacements. Linux listeners pin their parent directory and inode for binding/cleanup. Other Unix systems use the documented path-based, race-prone fallback shared with HTTP serving. Unix connects perform path-based lookups on every platform and can also race with filesystem changes after the authority check. These APIs are not supported on non-Unix platforms. ## UDP datagrams ```ts import { listenDatagram } from "avril:std"; const socket = listenDatagram({ hostname: "127.0.0.1", port: 9001 }); for await (const [packet, peer] of socket) { await socket.send(packet, peer); } ``` `listenDatagram({ transport?: "udp", hostname?, port })` returns a `DatagramConn` with immutable `addr: { transport: "udp", hostname, port }`. The default hostname is `127.0.0.1`; port `0` selects an available port. `send(bytes, { hostname?, port, transport?: "udp" })` also defaults to loopback. Binding and receiving require `net` authority for the bound hostname/port. Every send also requires authority for its **destination** hostname/port, checked before DNS and again before sending. Hostname/port grants cover both TCP and UDP. DNS chooses an address matching the socket's IPv4/IPv6 family. The CLI HTTP exemption does not apply. `receive(buffer?)` returns `[Uint8Array, UdpAddr]`. With a supplied buffer, the returned view shares that buffer; an oversized incoming packet is truncated and the remainder discarded, not carried into the next receive. An empty buffer still consumes a packet. Empty datagrams return an empty array with a peer address: they are not EOF. Without a buffer, receives allow up to 65,535 bytes. `send` snapshots its payload, preserves message boundaries, and returns the number of bytes sent. Sends are ordered in invocation order; UDP itself does not guarantee delivery or ordering. Payloads over the portable 65,507-byte limit are rejected rather than split. IP fragmentation may still happen below that limit. `setBroadcast(enabled = true)` controls IPv4 broadcast permission on the socket; each broadcast destination still needs its own network grant. Multicast membership and Unix datagrams are not implemented. Closing cancels pending receives and sends; disposal is idempotent and handles cannot be structured-cloned. The async iterator ends on close and closes the socket when its loop is broken. Worker termination releases the socket. Merely binding does not keep the event loop alive; a pending receive does. ## Stream I/O and lifetime `Conn.read(Uint8Array)` returns a byte count or `null` for EOF. An empty buffer returns `0`. `Conn.write(Uint8Array)` snapshots its input and may write fewer bytes than requested, with a maximum of 64 KiB per call. Loop over partial writes, or use `writable`, which writes each chunk completely with backpressure. Writes and `closeWrite()` are ordered in invocation order. `Conn.writev(readonly Uint8Array[])` writes one ordered vector as a single queued operation: the byte sequence equals the concatenation of its views, empty views are skipped, and unlike `write` the call completes the entire vector before resolving with the total bytes written. Later writes and `closeWrite()` cannot interleave between its batches, and an error may leave a prefix written without rolling it back. The whole vector is validated and snapshotted at invocation, so mutating the array or its views afterwards cannot affect any submitted bytes. An empty or all-empty vector returns 0, still performing the usual closed and authority checks. Snapshots retain the entire payload until completion; await writes rather than queueing unbounded batches. `readable` is a byte stream supporting BYOB readers. Its EOF does not close the write half. `closeWrite()` and closing `writable` send FIN without closing the read half; repeated write shutdown is harmless. Reading and writing can proceed simultaneously. Do not mix direct reads with a stream reader. `close()` closes both halves and cancels pending I/O. Canceling `readable` or aborting `writable` also closes the connection. Stream errors close the connection. Sockets and listeners support idempotent `[Symbol.dispose]()` and cannot be structured-cloned. Close resources explicitly; garbage collection is only a fallback. Worker termination closes the worker's sockets and pending operations. `Listener.accept()` returns a connection. Its async iterator ends when the listener closes; breaking the loop closes the listener, but not connections already accepted. A pending accept keeps the event loop alive; merely binding a listener does not. `connect({ ..., signal })` supports aborting an in-progress connect, including DNS lookup (the OS resolver itself may continue after cancellation). An already-aborted signal prevents networking. The signal does not own the connection once `connect` returns; use `connection.close()` to cancel later I/O.