This reference describes the implemented native runtime. Historical paths and internal version labels are retained where relevant. Later extension chapters supersede older limitations.
This stage implements only the foundation milestone in NETWORK-SUITE-PLAN.md. Old TCP/DNS/TLS commands retain their syntax, budgets, and fatal-resource behavior. New commands use runtime-owned nonblocking CLOEXEC descriptors. No public network tests.
The finite design was written before implementation. The contract below includes the tested full-duplex refinement: read/WANT_READ preserves its retry arguments without preventing compatible writes. Specification and quality review are pending.
Implemented syntax and contract
socket wait $events $interests timeout_ms
socket tryaccept $result $connection $listener handshake_ms
socket tryrecv $result $data $connection maxbytes
socket trysend $result $connection data
socket handshake $result $connection
socket tlslisten $listener address port backlog
udp bind $handle address port
udp sendto $result $handle address port data
udp recvfrom $result $data $handle maxbytes
udp endpoint $address $port $handle
udp close $handle
Declare destinations first. Metadata destinations become JSON; raw data and handle outputs remain scalar (binary safe). Distinct output variables required. All inputs, output types/storage reservations, handles and limits are checked before I/O. Metadata destinations can already hold JSON; raw destinations cannot. Handles alias resources, not ownership; copied handles become stale together on close/failure.
wait takes a JSON array of 1–32 unique objects with exactly handle (string) and
events (r, w, rw). UDP and stream handles share this wait. Listener events
must be r. It returns an array in interest order, omitting idle entries, each with
handle (string), and read, write, eof, error, handshake, expired booleans. Empty means
timeout. HUP/error is readiness, not proof of EOF: only a read establishes EOF.
Known EOF and shared plaintext or SSL_pending bytes are immediately readable.
For pending TLS operations, logical requested direction is mapped to OpenSSL WANT
(direction can be opposite); readiness means retry, not guaranteed progress.
Wait never performs application I/O or handshake work. Timeout is 0–30000 ms (zero
is a probe); positive reservations share the old cumulative 60000 ms budget.
tryaccept makes one accept attempt. result.status is wouldblock (connection
output empty), progress (plain established), or handshake (TLS accepted but not
verified/usable yet). TLS handshake deadline is absolute from acceptance, 1–30000
ms, reserved against the same cumulative time budget. No implicit handshake wait.
handshake makes one SSL_accept attempt; result status wouldblock or progress,
want is r, w, or empty. Wait with r on a handshaking handle maps to WANT.
An expired handshake is invalidated on handshake/use or a wait selecting it; wait
returns expired/error for that handle. Unselected handles cannot advance after
deadline and retain at most one of the 32 bounded ownership slots until close/use
or runtime destruction. A wait is capped by selected handshake deadlines.
tryrecv makes at most one transport read (up to 8192 of maxbytes 1–65536), first
consuming the existing shared buffer. Metadata: status progress, wouldblock,
or eof; count, eof, want. EOF is separate from no bytes yet. TLS read retry
arguments are runtime-owned and frozen across WANT; repeat tryrecv with the same
maxbytes. Legacy reads and changed read sizes are rejected while this retry is
pending. A pending read/WANT_READ allows trysend while retaining the original
owned read buffer and size: otherwise TLS tickets or fragmented records could
deadlock full-duplex application messages. A read/WANT_WRITE is conservatively
exclusive; only retry the read (or close). While a TLS write remains pending, reads
are rejected, and only an empty-data write resume (or close) is permitted.
Before an idle TLS read, a zero-time readiness probe avoids needlessly entering a
pending SSL_read. TLS protocol processing can still yield WANT after readiness.
trysend owns one bounded (0–65536 bytes) payload per handle. Metadata: status
progress or wouldblock, accepted (new bytes copied this call), count (bytes
newly completed by send/SSL_write this call), pending, want. Initial acceptance
is NOT completion or peer delivery. Resume a pending write with empty data only;
new nonempty data, legacy sends/reads and TLS tryrecv while pending are rejected.
Each invocation does at most one write. A partial success removes only its
completed prefix; WANT preserves the exact pointer/length until retry. Empty data
without pending work succeeds with zero counts. Account total accepted payload
bytes once, not on retries. Close discards pending data; transport failure closes
the handle (plain or TLS) and does not imply zero bytes reached the peer. No blind
full-payload retries. Plaintext tryrecv is allowed while a plaintext write is pending;
legacy I/O is not. When pending TLS write exists, wait interest must be w. A pending
read/WANT_WRITE requires logical r; read/WANT_READ allows r, w, or rw.
TLS servers require both networking gates plus explicit operator
--tls-cert-file PATH --tls-key-file PATH. Both options must appear together and
require both gates. PEM certificate chain and unencrypted private key pair are
loaded/checked before creating/binding listener FD; encrypted keys fail without
prompting. TLS >=1.2, no renegotiation/compression/session cache. No client-cert
authentication claim. Listener context is reused by accepted SSL objects, whose
refcounts keep it alive if listener closes. Legacy accept on TLS listeners is
rejected: use staged tryaccept/handshake. Existing tlsconnect handles support the
new operations without changing original verification.
UDP uses numeric IPv4/IPv6, localhost/empty bind selecting 127.0.0.1; wildcard
only when explicit. Bind requires both gates. No raw sockets, broadcast option,
multicast joins, DNS, or hidden outbound auto-bind. sendto is one atomic datagram
attempt (0–65507 bytes), status progress/wouldblock, count; retry wouldblock at
application discretion. recvfrom consumes one packet (maxbytes 1–65507), raw data
plus metadata status progress/wouldblock/truncated, count, size (original size),
address and port. Truncation is explicit and the remainder is discarded, never
reported as success. Zero-size datagrams are progress, not EOF. MSG_TRUNC obtains
original size; reserve 65535 bytes before receive so even discarded bytes are
accounted. Peer metadata is numeric. No delivery/order/reliability guarantee.
Shared limits / integration
32 live handles total (UDP included), 4096 socket+UDP calls, 16 MiB attempted sends/actual received bytes, 60000 ms timeout reservations. New pending buffers share 1 MiB aggregate conservative capacity reservation with old stream buffers; reserve copy headroom before mutation. All fatal limits remain uncatchable. Try calls do not poll-block/retry EINTR; EINTR is wouldblock. wait retries EINTR under one deadline. FD exhaustion releases owned descriptors before diagnostics. No per-handle background threads, implicit retries, or hidden blocking readline. Kernel/library memory, local cert reads and crypto are not hard-real-time bounded; use external process limits for untrusted workloads. New families remain modules.
Next stages must preserve accepted-versus-completed accounting, preserve unsent application buffers, use wait instead of blocking line reads, and close pending TLS operations rather than switching retry arguments. Protocol queues must have independent finite budgets. No WebSocket/MQTT/etc implementation is part of stage 1.
Primary references inspected
OpenSSL 3.0 SSL_write (identical retry arguments, partial write completion),
SSL_read, SSL_accept, SSL_CTX_use_certificate, SSL_get_error; Linux man-pages poll(2), recv(2),
udp(7), retrieved from docs.openssl.org and man7.org. Extraction backend and
browser access failed; direct HTTPS retrieval succeeded. Raw selected excerpts
and retrieved page lengths are recorded in build/foundations-primary-docs.json.
OpenSSL SSL_get_error explicitly forbids other I/O during write/WANT_WRITE and
describes opposite-direction retries. Full-duplex read retry ownership is exercised
with real encrypted records rather than mocked SSL responses.
Reproduce and run actual examples
From /root/vanilla1:
python3 tests/verify_foundations.py
python3 tests/test_foundations.py ./build/vanilla1
ASAN_OPTIONS=detect_leaks=1:halt_on_error=1 UBSAN_OPTIONS=halt_on_error=1 python3 tests/test_foundations.py ./build/vanilla1-asan
python3 tests/run_foundations_demos.py ./build/vanilla1
ASAN_OPTIONS=detect_leaks=1:halt_on_error=1 UBSAN_OPTIONS=halt_on_error=1 python3 tests/run_foundations_demos.py ./build/vanilla1-asan
make test and make test-sanitize also include the new cases and demos. The full
verifier additionally runs all six baseline cache differentials and old TCP/DNS/TLS
suites on pre-network-suite. The wrapper prints JSON including exact application
stdout. Actual checked stdout, grouped by application:
alice received: bob: hello
bob received: alice: hello
chat: slow reader blocked; incomplete line isolated; both healthy peers served
TLS: verified Python client exchanged exact messages
UDP: binary-NUL and zero-length datagrams roundtripped
The chat uses examples/chat_server.v and two concurrent actual
examples/chat_client.v interpreters. Two further Python sockets are a deliberately
non-reading peer and an incomplete-line sender. Before replying to either healthy
client, the server verifies that its slow-reader send actually returns wouldblock.
It does not call blocking accept/readline/sendall in its event loop. The finite
fixture exchanges alice: hello\n / bob: hello\n, checks both replies, and reaps
all three interpreter processes. This is a bounded demonstration, not a long-lived
chat service or a general application queue library.
examples/tls_server.v is run with disposable operator-supplied CA-issued PEMs.
Python verifies its chain and localhost identity, sends verified hello in two
fragments, receives exactly verified reply, and observes close_notify. The main
test suite separately checks invalid/mismatched/missing/encrypted credentials,
pre-bind pair validation, wrong identity rejection and a healthy TLS handshake
beside a stalled peer. No certificate or private key is added as a shipped example.
examples/udp_roundtrip.v sends two packets to a Python loopback socket: exactly
61 00 62 (hex) and an empty packet. The peer checks both original boundaries and
echoes each separately. Vanilla checks peer address/port and exact bytes. Additional
tests exercise IPv6, truncation, zero-probe readiness and mixed UDP/TCP wait lists.
Manual TLS launch syntax (replace paths with operator-owned unencrypted PEMs and a readiness filename that does not yet exist):
./build/vanilla1 --allow-network --allow-listen --allow-files --tls-cert-file /path/server-chain.pem --tls-key-file /path/server-key.pem run examples/tls_server.v -- build/new-tls-port.txt
The wrapper is the fully automated route; it generates temporary credentials, discovers ephemeral ports, bounds every wait, and removes the temporary files.
Verified evidence and limits of coverage
Final full verifier: 2764/2764 checks per artifact on release and ASan/UBSan; no compiler warnings or sanitizer diagnostics in passing runs. Breakdown:
| Corpus | Checks per artifact |
|---|---|
| Core | 183 |
| Utilities / permissions / worklog | 173 / 16 / 130 |
| Prior TCP / DNS-TLS | 73 / 70 |
| Three prior demos | 3 |
| Foundations / three new demos | 79 / 3 |
| Cache differential against six baselines | 339 each, 2034 total |
The cache corpus is repeated against pre-optimization, pre-utilities, pre-worklog, pre-sockets, pre-dns-tls and pre-network-suite; these totals are not counts of unique programs. Pre-network-suite additionally passes 73 old TCP and 70 old DNS/TLS cases outside per-candidate totals. All 17 original/baseline files have unchanged SHA-256 hashes, including the parent-preserved pre-network-suite artifacts.
Evidence: docs/foundations-verification.json (commands, counts, durations, binary
hashes, log paths), docs/foundations-preservation.json, docs/foundations-tests*.json,
docs/foundations-demos*.json, and docs/foundations-cleanup.json. Raw full-suite
logs are build/foundations-*.log. build/foundations-red.log records the initial
expected rejection of the then-unimplemented API. Early fixture failures are kept
in build/foundations-initial-fixture-failures.log: a TLS 1.3 ticket legitimately
made an idle read pending, and a fixed sleep failed to force write backpressure
under the slower sanitizer build. The final retry test synchronizes draining on
an observed WANT; it checks 6,553,600 exact nonuniform bytes with distinct block
tags, accepted/completed totals and no duplication. No sanitizer was suppressed.
SPEC reset regression correction
A real loopback SO_LINGER reset reproduced a plaintext tryrecv transport error
that left the copied handle and owned read buffer live. The read catch now erases
the handle for both transports; local shared ownership releases the FD and buffers
on unwinding. Preflight validation remains outside that catch. trysend and staged
handshake transport catches already erase unconditionally; their behavior was not
changed. Legacy blocking plaintext recv/send implementations were left untouched.
The existing contract above has not been weakened.
Five added cases check real reset receive/send failures with exact unchanged JSON and raw output sentinels, invalidation of original and copied handles, 40 reset cycles under RLIMIT_NOFILE=20 without explicit successful closes, old blocking plaintext recv retaining its alias, and recv/send/handshake preflight preserving outputs and a usable handle. The repeated run exceeds the live handle/FD limits if ownership leaks; ASan leak detection is enabled. Fixtures join peers and use bounded waits, with no reset synchronization sleeps.
Before the fix, both artifacts passed 2/4 reset cases and failed the two receive
cases; afterward both pass 4/4 reset cases and 1/1 preflight case. Logs are
build/foundations-reset-red-{release,asan}.log,
build/foundations-reset-green-{release,asan}.log, and
build/foundations-reset-preflight-{release,asan}.log. The reviewer's unchanged
reproducer prints alias invalidated afterward (its bug-expecting final assertion
then intentionally exits 1); before/after output is preserved in
build/foundations-reset-reviewer-{before,after}-{release,asan}.log.
The initial repeated fixture exceeded cumulative timeout reservations; its failed
log is retained as build/foundations-reset-initial-budget-fixture-failure.log.
Reservations were reduced to 300 ms per operation, not removed. The first full
verification hit an existing HTTP self-signed fixture timeout (172/173 utilities);
its log and partial JSON are preserved in
build/foundations-reset-initial-{full,utilities}-failure.log and
build/foundations-reset-initial-full-failure.json. Utilities passed on retry
without source changes, then the complete verifier passed, including both sets of
demos. Final transcript: build/foundations-reset-full-verifier.log.
Targeted reruns (from the repository root):
make release sanitize
python3 tests/test_foundations.py ./build/vanilla1 'TCP real reset'
ASAN_OPTIONS=detect_leaks=1:halt_on_error=1 UBSAN_OPTIONS=halt_on_error=1 python3 tests/test_foundations.py ./build/vanilla1-asan 'TCP real reset'
python3 tests/test_foundations.py ./build/vanilla1 'TCP preflight'
ASAN_OPTIONS=detect_leaks=1:halt_on_error=1 UBSAN_OPTIONS=halt_on_error=1 python3 tests/test_foundations.py ./build/vanilla1-asan 'TCP preflight'
python3 tests/verify_foundations.py
The real MemoryBIO fixture sends only a TLS record header plus one byte, requires Vanilla to write a response while its read is pending, then releases the remaining record. Vanilla preserves the original read arguments, rejects a changed read limit and conflicting legacy I/O, and receives the exact application bytes. This guards against a full-duplex architecture that silently deadlocks on read/WANT_READ.
Reused installed dependencies: GCC 13.3.0, OpenSSL 3.0.13, c-ares 1.27.0; no packages were installed for this stage. No public endpoint, broadcast packet, raw socket, remote deployment or production change was used. No surviving fixture interpreter was found after the full run; fixture contexts also join threads and close sockets.
Limitations: no deterministic key-update-induced read/WANT_WRITE or write/WANT_READ fixture, forced allocator failure, OS EINTR injection, UDP send-queue wouldblock injection, large fuzz campaign, or whole-process RSS guarantee. Installed dependency libraries are not sanitizer-instrumented. Handshake crypto and filesystem certificate reads are cooperative, not preemptible deadlines. No mutual TLS/client identity verification, graceful drain guarantee, asynchronous DNS/connect API, or optional blocking TLS-server convenience accept is supplied. Existing bounded connect and legacy operations remain available, but must not be placed in a latency-sensitive event loop. Stages after these foundations remain pending parent dispatch/review.