Skip to main content

godspeed/
net.rs

1// SPDX-License-Identifier: Apache-2.0
2//! The network, without the packing.
3//!
4//! # What this wraps, and what it deliberately does not
5//!
6//! `net-stack` answers two distinct kinds of request, and only the first is here:
7//!
8//! 1. **Plain requests to the service**, addressed by a leading opcode byte - resolve a name, ping a
9//!    host, run one TCP transaction, ask what our address is. That is this module.
10//!
11//! 2. **Invocations of a delegated resource capability** (§7.10) - a UDP socket or a TCP listener is
12//!    a real kernel capability that `net-stack` mints and grants, and using one means invoking it
13//!    with an operation and a right. **That is here now** - `docs/stdlib-design.md` §8 argued for
14//!    leaving it out, and the implementation has since crossed that boundary.
15//!
16//! So: this module gets you a name resolved, a host pinged, a TCP request answered - AND a listening
17//! socket. [`Net::socket`], [`Net::listen`], [`Listener::accept`] and [`Conn`] are all here. This
18//! header said "It does not get you a listening socket. That boundary is deliberate", which was true
19//! when written and is the opposite of the module below it now.
20//!
21//! # A caution the rest of this library does not need
22//!
23//! `net-stack`'s request protocol has moved 34 times in three releases. `fs`'s has not. Every opcode
24//! below therefore names the `services/net-stack` dispatch arm or constant it mirrors (line numbers
25//! are not cited: they rot), and the surface is kept to the operations with a demonstrated caller - because the cost of this module being wrong is not a
26//! compile error, it is a machine that quietly talks to the wrong port.
27
28use godspeed_sdk::capability::{CapHandle, RIGHT_READ, RIGHT_WRITE};
29use godspeed_sdk::ipc::Message;
30use godspeed_sdk::service_context::ServiceContext;
31
32use crate::call;
33use crate::error::Error;
34use crate::resource::{self, Held};
35pub use crate::addr::Ipv4;
36
37// The opcodes, as `services/net-stack` dispatches them (its `pl.first() == Some(&N)` arms).
38const OP_RESOLVE: u8 = 1;
39const OP_PING: u8 = 3;
40const OP_ARP: u8 = 6;
41const OP_RENEW: u8 = 8;
42const OP_TCP: u8 = 21;
43/// Any opcode `net-stack` does not recognise is answered with the status blob. Using an explicitly
44/// reserved value rather than a real operation keeps that from being an accident. It is not a
45/// guess: `net-stack` lists 0 alongside the real read-only ops in its gateway-retry trigger, so the
46/// service already treats it as a request a client legitimately makes.
47const OP_STATUS: u8 = 0;
48
49/// The first byte of every request, echoed at byte 0 of the reply so a late answer to an earlier
50/// question is recognised rather than read as this one's. A SECOND counter from the filesystem's:
51/// the two channels are independent and a tag only has to be unique against its own.
52const TAG_BASE: u8 = 0x80;
53
54/// Open a UDP socket, as `net-stack` numbers its operations.
55const OP_OPEN_SOCKET: u8 = 2;
56/// Listen on a TCP port: `[22, port_hi, port_lo]`.
57const OP_LISTEN: u8 = 22;
58/// Open an outbound TCP connection held as a capability.
59const OP_CONNECT: u8 = 23;
60
61// Operations on a LISTENER capability.
62const LOP_ACCEPT: u8 = 0;
63const LOP_CLOSE: u8 = 1;
64
65// Operations on a CONNECTION capability.
66const COP_RECV: u8 = 0;
67const COP_SEND: u8 = 1;
68const COP_CLOSE: u8 = 2;
69
70/// Everything after the echoed tag.
71fn body(m: &Message) -> &[u8] {
72    let b = m.payload_bytes();
73    if b.len() > 1 { &b[1..] } else { &[] }
74}
75
76/// A resolve or a TCP transaction crosses the network, so the five-second default is too tight.
77/// `net-stack` bounds a DNS resolve by the CLIENT's patience rather than by an attempt count (its
78/// own words, and a fix on this branch), which means the number here IS the policy.
79pub const NET_SECS: i64 = 10;
80
81/// How long to wait for a datagram sent through a [`Socket`].
82///
83/// **Longer than `net-stack`'s own worst case for the operation, and that is the whole point.** It
84/// retries a datagram `DANCE_TRIES` (6) times at `DANCE_SECS` (2) apiece before answering - so a
85/// client waiting ten gives up while the service is still working and reports
86/// [`Error::OutcomeUnknown`] about a request that was about to succeed.
87///
88/// **Why 30 and not 12.** The retry budget reads as 6 x 2 = 12 seconds. It is not: each of those
89/// waits bounds itself with
90///
91/// ```ignore
92/// let t0 = self.epoch_secs_monotonic();
93/// if self.epoch_secs_monotonic() - t0 >= max_secs { return Timeout }
94/// ```
95///
96/// and `epoch_secs_monotonic` returns WHOLE SECONDS. A "2 second" deadline therefore elapses when
97/// the second COUNTER advances by two - anywhere between just over 1s and just under 3s of real
98/// time, depending where in the second `t0` fell. The worst case per try is `max_secs + 1`:
99///
100/// ```text
101/// 6 tries x (2 + 1) = up to 18 seconds
102/// ```
103///
104/// which is why 10 and 15 both produced intermittent [`Error::OutcomeUnknown`] and 30 is stable.
105///
106/// **The general rule, which matters more than this constant: a deadline built from whole-second
107/// differences carries up to +1s of slop, so N chained waits of S seconds bound at N x (S + 1).**
108/// A budget computed as N x S is short, and being short turns a slow SUCCESS into an unknown
109/// outcome - the one error that forbids the retry which would have fixed it.
110///
111/// That is the worst available false report: `OutcomeUnknown` tells a caller the operation MAY have
112/// happened and must not be retried. A deadline shorter than the service's own bound does not bound
113/// anything useful - it converts a slow success into an unknown outcome, which is strictly worse
114/// than waiting.
115pub const SOCKET_SECS: i64 = 30;
116
117/// How long to wait for a TCP transaction through [`Net::tcp`].
118///
119/// A connect, a request and a response to a host that may be far away and may be slow. `services/
120/// shell` used twenty seconds for exactly this before it moved onto this library, and the move to
121/// [`NET_SECS`] halved it by accident - which nothing in QEMU is slow enough to notice, and a real
122/// site on real hardware certainly is. Restored, and named so it cannot drift again.
123pub const TCP_SECS: i64 = 20;
124
125/// What the machine currently believes about the network.
126///
127/// A snapshot, not a subscription: `net-stack` re-reads the link when asked, so a cable pulled since
128/// the last call shows up on the next one rather than being cached into a comfortable lie.
129#[derive(Copy, Clone, PartialEq, Eq, Debug)]
130pub struct Status {
131    /// Our address. All zeroes means unconfigured.
132    pub ip: Ipv4,
133    /// The default gateway.
134    pub gateway: Ipv4,
135    /// The gateway's MAC, valid only when [`gateway_resolved`](Status::gateway_resolved).
136    pub gateway_mac: [u8; 6],
137    /// The DNS server, from the lease.
138    pub dns: Ipv4,
139    /// ARP has resolved the gateway, so traffic can leave.
140    pub gateway_resolved: bool,
141    /// A ping has succeeded at least once.
142    pub ping_ok: bool,
143    /// **DHCP granted this address**, as opposed to it being a fallback guess. This is the
144    /// difference between configured and merely reachable, and it is the bit worth asserting on: a
145    /// receive path that has stopped working shows up here as a stack that never got a lease.
146    pub leased: bool,
147}
148
149impl Status {
150    /// The short answer to "is the network usable": we have a leased address and a resolved gateway.
151    pub fn usable(&self) -> bool {
152        self.leased && self.gateway_resolved
153    }
154}
155
156/// A handle to the network service.
157///
158/// # Authority
159///
160/// Carries none of its own. Every call rides the caller's existing send capability to `net-stack`,
161/// granted by its spawn request (a contract declares it as `ipc_send = ["net-stack"]`, CLAUDE.md
162/// 13.6); a task granted none gets [`Error::Unreachable`], exactly as for any peer it
163/// cannot reach. **There is no ambient network** (§3.1, and `docs/networking.md` says the same).
164pub struct Net<'a> {
165    ctx: &'a ServiceContext,
166    tag: u8,
167    /// Fired once when a request lingers. See [`Net::with_notice`].
168    notice: Option<&'a dyn Fn()>,
169    /// Where the keys a noticing wait reads (and does not act on) go - see [`Net::keeping`].
170    keep: Option<&'a dyn Fn(u8)>,
171    /// The command's watch (`call::Watch`); see `Fs::watching`.
172    watch: Option<&'a dyn call::Watch>,
173}
174
175impl<'a> Net<'a> {
176    /// Open a UDP socket and receive a CAPABILITY to it (7.10).
177    ///
178    /// The returned [`Socket`] is the authority to send through one source port. It is minted by
179    /// `net-stack`, validated by the kernel on every use, and revoked when `net-stack` restarts -
180    /// at which point operations return [`Error::Revoked`] and you open another.
181    ///
182    /// **Blocks** up to [`NET_SECS`]. **Authority:** the caller's existing `net-stack` capability;
183    /// opening a socket grants nothing the spawn request did not already grant.
184    ///
185    /// # Errors
186    /// - [`Error::Unavailable`] - `net-stack` would not open one. Usually no usable NIC.
187    /// - [`Error::Failed`] - it answered without a capability.
188    /// - Opening is idempotent from the caller's side, so a no-answer error is safe to retry -
189    ///   though it may leave a socket behind in `net-stack`.
190    pub fn socket<'n>(&'n mut self) -> Result<Socket<'n, 'a>, Error> {
191        let ctx = self.ctx;
192        let r = self.call(&[OP_OPEN_SOCKET], NET_SECS)?;
193        if body(&r).first() != Some(&1) {
194            return Err(Error::Unavailable);
195        }
196        // The capability rode the reply as an EMBEDDED cap; the kernel placed it in our table on
197        // receipt and it is ours to claim or leak.
198        let cap = ctx.take_pending_cap().ok_or(Error::Failed)?;
199        Ok(Socket { net: self, ctx, cap, held: Held::new() })
200    }
201
202    /// The next tag, for the socket capability - which speaks to the SAME service on the SAME
203    /// endpoint and must therefore draw from THIS counter rather than start a second one.
204    pub(crate) fn next_tag_pub(&mut self) -> u8 {
205        self.tag = TAG_BASE.wrapping_add(self.tag.wrapping_sub(TAG_BASE).wrapping_add(1) & 0x3F);
206        self.tag
207    }
208
209    /// Listen on a TCP port and receive a CAPABILITY to the listener.
210    ///
211    /// The returned [`Listener`] is the authority to accept connections on one port, and nothing
212    /// else. `net-stack` refuses a port already listened on, so two programs cannot silently share
213    /// one.
214    ///
215    /// **Blocks** up to [`NET_SECS`]. **Authority:** the caller's existing `net-stack` capability.
216    ///
217    /// # Errors
218    /// - [`Error::Unavailable`] - `net-stack` would not listen: the port is taken, the listener
219    ///   table is full, or there is no usable NIC. Its log says which.
220    /// - [`Error::Failed`] - it agreed and sent no capability.
221    pub fn listen<'n>(&'n mut self, port: u16) -> Result<Listener<'n, 'a>, Error> {
222        let ctx = self.ctx;
223        let r = self.call(&[OP_LISTEN, (port >> 8) as u8, port as u8], NET_SECS)?;
224        if body(&r).first() != Some(&1) {
225            return Err(Error::Unavailable);
226        }
227        let cap = ctx.take_pending_cap().ok_or(Error::Failed)?;
228        Ok(Listener { net: self, ctx, cap, held: Held::new(), closed: false })
229    }
230
231    /// Open a TCP connection to `ip:port` and receive a CAPABILITY to it.
232    ///
233    /// Unlike [`tcp`](Net::tcp), which is one request and one reply, the returned [`Stream`] is a
234    /// conversation: send, read, send again, for as long as both ends keep it open. That is what a
235    /// protocol with a handshake needs - TLS above all.
236    ///
237    /// **Blocks** until the connection is established or refused, up to [`TCP_SECS`]. **Authority:**
238    /// the caller's existing `net-stack` capability; the connection's capability grants only that
239    /// one connection.
240    ///
241    /// # Errors
242    /// - [`Error::Unavailable`] - `net-stack` could not connect: no answer, refused, no usable NIC,
243    ///   or its connection table is full. Its log says which.
244    /// - [`Error::Failed`] - it agreed and sent no capability.
245    pub fn connect<'n>(&'n mut self, ip: Ipv4, port: u16) -> Result<Stream<'n, 'a>, Error> {
246        let ctx = self.ctx;
247        let req = [OP_CONNECT, ip.0[0], ip.0[1], ip.0[2], ip.0[3], (port >> 8) as u8, port as u8];
248        let r = self.call(&req, TCP_SECS)?;
249        if body(&r).first() != Some(&1) {
250            return Err(Error::Unavailable);
251        }
252        let cap = ctx.take_pending_cap().ok_or(Error::Failed)?;
253        Ok(Stream { net: self, ctx, cap, held: Held::new(), closed: false })
254    }
255
256    /// Take a network handle. Cheap, allocates nothing, grants nothing.
257    pub fn new(ctx: &'a ServiceContext) -> Net<'a> {
258        Net { ctx, tag: TAG_BASE, notice: None, keep: None, watch: None }
259    }
260
261    /// A network handle that tells you when a call is taking a while.
262    ///
263    /// `notice` is fired once, after a couple of seconds, if a request has not been answered. It
264    /// takes nothing and returns nothing: it means only "this is lasting". An interactive program
265    /// prints `[q] quit` and starts watching the keyboard; a daemon might log it; most programs do
266    /// not need it at all.
267    ///
268    /// This exists because `services/shell` could not otherwise move onto this library without
269    /// dropping that affordance on exactly the calls where a person needs it - a `tcp` to an
270    /// unreachable host waits the full deadline in silence. Losing that would have been a
271    /// regression dressed up as a migration.
272    pub fn with_notice(ctx: &'a ServiceContext, notice: &'a dyn Fn()) -> Net<'a> {
273        Net { ctx, tag: TAG_BASE, notice: Some(notice), keep: None, watch: None }
274    }
275
276    /// Hand every key a noticing wait reads, other than a quit key, to `keep`, in order, instead of
277    /// discarding it - as [`Fs::keeping`](crate::fs::Fs::keeping) does (`backlog/fixed/80` H16).
278    pub fn keeping(mut self, keep: &'a dyn Fn(u8)) -> Net<'a> {
279        self.keep = Some(keep);
280        self
281    }
282
283    /// Lend this handle the command's watch ([`call::Watch`]): every request then shows the command's
284    /// `[q] quit` once the COMMAND has run 2 s, and stops at the next request after a quit. Takes
285    /// precedence over a notice; a watch that is not active leaves the handle's own notice, if any, else the
286    /// plain wait.
287    pub fn watching(mut self, watch: &'a dyn call::Watch) -> Net<'a> {
288        self.watch = Some(watch);
289        self
290    }
291
292    /// Send `[tag, patience, body..]` and return the reply with the tag checked and stripped.
293    ///
294    /// **The two header bytes are not optional.** `net-stack` strips them from any request of two
295    /// bytes or more and echoes the tag back at byte 0, so a request built without them has its
296    /// OPCODE eaten as a tag and is dispatched on whatever byte follows. The first draft of this
297    /// module got that wrong, and only `status()` worked - because a one-byte request falls below
298    /// the strip threshold and reaches the default arm, which answers status anyway.
299    ///
300    /// The patience byte is this call's deadline, told to the service so it can stop holding a
301    /// request its client has already given up on. It is the client's number, which is why it
302    /// travels with the request rather than living in the service.
303    fn call(&mut self, body: &[u8], secs: i64) -> Result<Message, Error> {
304        let mut req = [0u8; 512];
305        if 2 + body.len() > req.len() {
306            return Err(Error::InvalidInput);
307        }
308        self.tag = TAG_BASE.wrapping_add(self.tag.wrapping_sub(TAG_BASE).wrapping_add(1) & 0x3F);
309        let tag = self.tag;
310        req[0] = tag;
311        req[1] = secs.clamp(0, 255) as u8;
312        req[2..2 + body.len()].copy_from_slice(body);
313        let msg = Message::from_bytes(&req[..2 + body.len()]);
314        let r = match self.watch {
315            // Only the reply carrying THIS request's tag (see `call::request_watched_sifted`).
316            Some(w) if w.active() => call::request_watched_sifted(self.ctx, "net-stack", &msg, secs, w,
317                &mut |m| m.payload_bytes().first() == Some(&tag))?,
318            _ => call::request_within_notice(self.ctx, "net-stack", &msg, secs, self.notice, self.keep)?,
319        };
320        // A reply whose tag does not match is the answer to a request we already gave up on. Reading
321        // it as this one's is how a channel goes "out of step" and every later exchange answers the
322        // question before.
323        if r.payload_bytes().first() != Some(&tag) {
324            return Err(Error::Malformed);
325        }
326        Ok(r)
327    }
328
329    /// What the machine currently believes about the network.
330    ///
331    /// **Blocks** briefly. **Read-only**, so every no-answer failure is safe to retry.
332    pub fn status(&mut self) -> Result<Status, Error> {
333        let r = self.call(&[OP_STATUS], call::DEFAULT_SECS)?;
334        let b = body(&r);
335        if b.len() < 19 {
336            return Err(Error::Malformed);
337        }
338        Ok(Status {
339            ip: Ipv4([b[0], b[1], b[2], b[3]]),
340            gateway: Ipv4([b[4], b[5], b[6], b[7]]),
341            gateway_mac: [b[8], b[9], b[10], b[11], b[12], b[13]],
342            gateway_resolved: b[14] & 1 != 0,
343            ping_ok: b[14] & 2 != 0,
344            leased: b[14] & 4 != 0,
345            dns: Ipv4([b[15], b[16], b[17], b[18]]),
346        })
347    }
348
349    /// Resolve a hostname to an address. A host that is already an address, in dotted-quad
350    /// (`192.168.4.40`), is that address, and no name server is asked: a resolver answers "no such
351    /// name" for it, which made a link to a machine on the local network fail before it connected.
352    ///
353    /// **Blocks** up to [`NET_SECS`]: this leaves the machine and a name server may be slow or
354    /// absent. **Read-only**, so a retry is always safe.
355    ///
356    /// # Errors
357    /// [`Error::NotFound`] if the name does not resolve. [`Error::OutcomeUnknown`] if no answer
358    /// arrives in time - harmless here, since resolving twice costs only time.
359    pub fn resolve(&mut self, host: &str) -> Result<Ipv4, Error> {
360        if let Some(ip) = Ipv4::parse(host) {
361            return Ok(ip);
362        }
363        let mut req = [0u8; 256];
364        let n = host.len().min(req.len() - 1);
365        if n != host.len() {
366            return Err(Error::InvalidInput);
367        }
368        req[0] = OP_RESOLVE;
369        req[1..1 + n].copy_from_slice(host.as_bytes());
370        let r = self.call(&req[..1 + n], NET_SECS)?;
371        // `[status, ip(4), frames, udp, timeouts]` (net-stack's op 1): status 1 with the address, 2 when a
372        // server answered with no A record, 0 when no server answered. This read the STATUS as the first
373        // octet until 2026-10-16, so every address it returned was shifted by a byte - `1.172.67.222` for
374        // `172.67.222.x` - and nothing noticed, because nothing called it until the package manager did.
375        let b = body(&r);
376        match b {
377            [1, a, b2, c, d, ..] if [*a, *b2, *c, *d] != [0, 0, 0, 0] => Ok(Ipv4([*a, *b2, *c, *d])),
378            [2, ..] => Err(Error::NotFound),
379            [0, ..] => Err(Error::Unavailable),
380            _ => Err(Error::Malformed),
381        }
382    }
383
384    /// Send one ICMP echo and wait for the reply.
385    ///
386    /// **Blocks** up to [`NET_SECS`]. Returns `true` if the host answered.
387    ///
388    /// A host that does not answer is `Ok(false)`, not an error: silence is a legitimate result from
389    /// a reachable network, and conflating it with "the request failed" is how a diagnostic tool
390    /// starts lying about which half is broken.
391    pub fn ping(&mut self, ip: Ipv4) -> Result<bool, Error> {
392        let req = [OP_PING, ip.0[0], ip.0[1], ip.0[2], ip.0[3]];
393        let r = self.call(&req, NET_SECS)?;
394        Ok(body(&r).first() == Some(&1))
395    }
396
397    /// Resolve one host's MAC address on the local link.
398    ///
399    /// **Blocks.** `Ok(None)` means nothing answered, which on a local link usually means the host
400    /// is not there - again a result rather than a failure.
401    pub fn arp(&mut self, ip: Ipv4) -> Result<Option<[u8; 6]>, Error> {
402        let req = [OP_ARP, ip.0[0], ip.0[1], ip.0[2], ip.0[3]];
403        let r = self.call(&req, NET_SECS)?;
404        let b = body(&r);
405        if b.len() < 7 || b[0] != 1 {
406            return Ok(None);
407        }
408        Ok(Some([b[1], b[2], b[3], b[4], b[5], b[6]]))
409    }
410
411    /// One whole TCP transaction: connect, send `request`, read the reply, close.
412    ///
413    /// Writes the reply into `buf` and returns how many bytes landed there.
414    ///
415    /// **Blocks** up to [`TCP_SECS`]. **This one changes state on the far side**: it is a request to
416    /// somebody else's server, and that server may act on it. On [`Error::OutcomeUnknown`] the
417    /// request may have been delivered and acted upon, and re-sending it is a SECOND transaction -
418    /// which for anything that is not a plain fetch is exactly the double-submit problem.
419    /// [`Error::retry_is_safe`] answers `false` there for that reason.
420    ///
421    /// # Errors
422    /// [`Error::Failed`] if the connection could not be made or was closed with nothing returned.
423    pub fn tcp(&mut self, ip: Ipv4, port: u16, request: &[u8], buf: &mut [u8]) -> Result<usize, Error> {
424        let mut req = [0u8; 500];
425        let head = 7;
426        if head + request.len() > req.len() {
427            return Err(Error::InvalidInput);
428        }
429        req[0] = OP_TCP;
430        req[1..5].copy_from_slice(&ip.0);
431        req[5] = (port >> 8) as u8;
432        req[6] = port as u8;
433        req[head..head + request.len()].copy_from_slice(request);
434        let r = self.call(&req[..head + request.len()], TCP_SECS)?;
435        // `[status, bytes...]`: 0 and what came back, or 1 alone for a transaction that failed (its
436        // reason is in `net-stack`'s log). A failure was an EMPTY reply until 2026-10-10, which three
437        // ports refuse to deliver at all (`backlog/fixed/80` D5, `backlog/fixed/66`).
438        let b = match body(&r) {
439            [0, rest @ ..] => rest,
440            [1, ..] => return Err(Error::Failed),
441            _ => return Err(Error::Malformed),
442        };
443        // Checked BEFORE copying, so `BufferTooSmall` leaves the buffer untouched - the same
444        // contract `fs::read_into` gives. A partially-filled buffer plus an error is the worst of
445        // both: the caller cannot tell how much of it is real.
446        if b.len() > buf.len() {
447            return Err(Error::BufferTooSmall);
448        }
449        buf[..b.len()].copy_from_slice(b);
450        Ok(b.len())
451    }
452
453    /// Re-run the address dance in place, for a link that came up after boot.
454    ///
455    /// **Blocks. Changes state**: it may replace the machine's address. On
456    /// [`Error::OutcomeUnknown`] call [`status`](Net::status) to find out what happened rather than
457    /// renewing again.
458    pub fn renew(&mut self) -> Result<(), Error> {
459        self.call(&[OP_RENEW], NET_SECS)?;
460        Ok(())
461    }
462}
463
464/// A UDP socket, held as a capability.
465///
466/// Minted by `net-stack` and validated by the kernel on every use, exactly as a file capability is
467/// (7.10). Holding one is the authority to send through it and nothing else: it names one source
468/// port, it cannot be widened, and it stops working the moment `net-stack` revokes it or dies.
469///
470/// # Why it borrows the [`Net`] handle
471///
472/// The same reason [`File`](crate::file::File) borrows its `Fs`: both speak to one service over one
473/// endpoint and must share ONE correlation-tag counter. Two counters can mint the same tag for two
474/// exchanges in flight, and the result is not a loud rejection but a stale reply silently accepted
475/// as the current answer. The borrow makes that unspellable.
476pub struct Socket<'n, 'a: 'n> {
477    net: &'n mut Net<'a>,
478    ctx: &'a ServiceContext,
479    cap: CapHandle,
480    held: Held,
481}
482
483impl<'n, 'a: 'n> Socket<'n, 'a> {
484    /// Send a datagram and return what came back, in `buf`.
485    ///
486    /// Returns how many bytes of response landed in `buf`. **Zero means nothing answered** - which
487    /// is an ordinary outcome for UDP, not an error, and is why this is `Ok(0)` rather than an
488    /// `Err`. Nothing is retried on your behalf.
489    ///
490    /// **Blocks** up to [`SOCKET_SECS`], because the round trip happens inside `net-stack` - it
491    /// sends, retries, and waits for the response before replying to us at all.
492    ///
493    /// **Authority:** this capability's `WRITE` right, checked by the KERNEL before `net-stack` is
494    /// reached.
495    ///
496    /// # The ambiguity, stated
497    ///
498    /// `net-stack` answers "nothing came back" - and also "not sent at all", when it has no gateway
499    /// yet - with a single zero byte, which is byte-identical to a
500    /// genuine one-byte response of `0x00`. This reports both as `Ok(0)`. The protocol makes them the
501    /// same bytes, so no reading of it can tell them apart; recording that is better than choosing
502    /// one and being quietly wrong for the other. (`services/shell` reports the sentinel as one byte
503    /// of DATA, which is how `sock` says "received 1 bytes back" about a query nobody answered.)
504    ///
505    /// # Errors
506    /// - [`Error::PermissionDenied`] - this capability does not carry `WRITE`.
507    /// - [`Error::Revoked`] - `net-stack` revoked it, or died and was replaced. Open a new one.
508    /// - [`Error::BufferTooSmall`] - the response does not fit. **Nothing is retried**; the datagram
509    ///   was already sent, so call again only if the peer tolerates a repeat.
510    /// - **A send MUTATES the world.** On [`Error::OutcomeUnknown`] the datagram may have left; see
511    ///   [`Error::retry_is_safe`], which says no.
512    pub fn send_to(&mut self, ip: Ipv4, port: u16, data: &[u8], buf: &mut [u8]) -> Result<usize, Error> {
513        let mut body = [0u8; 6 + 1024];
514        if 6 + data.len() > body.len() {
515            return Err(Error::InvalidInput);
516        }
517        body[..4].copy_from_slice(&ip.0);
518        body[4] = (port >> 8) as u8;
519        body[5] = port as u8;
520        body[6..6 + data.len()].copy_from_slice(data);
521
522        let tag = self.net.next_tag_pub();
523        // The patience byte is THIS call's deadline, so the two cannot disagree - see `Net::call`,
524        // which says the same thing about a named request.
525        let m = resource::invoke(self.ctx, self.cap, RIGHT_WRITE, tag, Some(SOCKET_SECS as u8),
526                                 &body[..6 + data.len()], SOCKET_SECS, &mut self.held)?;
527        // `[tag, response..]` - the tag is verified and left in place, so the body starts at 1.
528        let b = m.payload_bytes();
529        let resp = if b.len() > 1 { &b[1..] } else { &[][..] };
530        // The sentinel. See the ambiguity note above.
531        if resp.len() <= 1 && resp.first().copied().unwrap_or(0) == 0 {
532            return Ok(0);
533        }
534        if resp.len() > buf.len() {
535            return Err(Error::BufferTooSmall);
536        }
537        buf[..resp.len()].copy_from_slice(resp);
538        Ok(resp.len())
539    }
540
541    /// Take one message that arrived during an operation and was NOT the reply.
542    ///
543    /// Drain this in a loop after every operation if your task serves clients - they are real
544    /// requests, held rather than dropped, and only you can answer them. A program that serves
545    /// nobody always gets `None`.
546    pub fn take_held(&mut self) -> Option<Message> {
547        self.held.take()
548    }
549}
550
551impl<'n, 'a: 'n> Drop for Socket<'n, 'a> {
552    /// Drops our handle to the capability.
553    ///
554    /// **There is no close operation for a UDP socket** - `net-stack` exposes send and nothing else
555    /// on this capability - so this releases the local cap-table slot and cannot tell the service
556    /// anything. The socket is reclaimed when `net-stack` revokes it or restarts. Recorded rather
557    /// than hidden: a reader comparing this with [`File`](crate::file::File), which does close, would
558    /// otherwise assume an omission.
559    fn drop(&mut self) {
560        self.ctx.remove_cap(self.cap);
561    }
562}
563
564/// A TCP listener, held as a capability.
565///
566/// Accepting hands over authority - each accepted connection is its own capability - so accepting
567/// requires the listener's WRITE right. A read-only listener capability can be held and inspected
568/// and cannot take connections.
569pub struct Listener<'n, 'a: 'n> {
570    net: &'n mut Net<'a>,
571    ctx: &'a ServiceContext,
572    cap: CapHandle,
573    held: Held,
574    closed: bool,
575}
576
577impl<'n, 'a: 'n> Listener<'n, 'a> {
578    /// One invocation on a capability this listener owns, drawing from the one tag counter.
579    ///
580    /// The patience byte is this call's deadline. `accept` is the reason it has to be there: a server
581    /// polling for a caller is a LONG wait on a held capability, and `net-stack` used to put such a
582    /// request aside for a fixed 1500 ms and then throw it away while the client waited twenty
583    /// seconds for an answer that no longer existed.
584    fn call(&mut self, cap: CapHandle, right: u8, body_bytes: &[u8]) -> Result<Message, Error> {
585        let tag = self.net.next_tag_pub();
586        resource::invoke(self.ctx, cap, right, tag, Some(NET_SECS as u8), body_bytes, NET_SECS,
587                         &mut self.held)
588    }
589
590    /// Take the next connection, if one is waiting.
591    ///
592    /// **`Ok(None)` means nobody has connected yet.** That is an ordinary poll result, not an error,
593    /// so a server loops on it - and loops rather than blocking so it can still notice its own
594    /// deadline, its operator, or a request on its endpoint.
595    ///
596    /// **Blocks** up to [`NET_SECS`] per call. **Authority:** this listener's `WRITE` right, because
597    /// accepting hands over authority.
598    ///
599    /// # Errors
600    /// [`Error::PermissionDenied`] if the capability lacks `WRITE`; [`Error::Revoked`] if
601    /// `net-stack` restarted, in which case listen again - the port went with it.
602    pub fn accept(&mut self) -> Result<Option<Conn<'_, 'n, 'a>>, Error> {
603        let cap = self.cap;
604        let r = self.call(cap, RIGHT_WRITE, &[LOP_ACCEPT])?;
605        // `[tag, 1]` and an embedded capability, or `[tag, 0]` for nobody yet.
606        if body(&r).first() != Some(&1) {
607            return Ok(None);
608        }
609        let conn = self.ctx.take_pending_cap().ok_or(Error::Failed)?;
610        Ok(Some(Conn { lis: self, cap: conn, closed: false }))
611    }
612
613    /// Stop listening and release the port.
614    ///
615    /// Consumes the listener. Dropping one also releases it, but a `Drop` cannot report a failure -
616    /// and a port that was not released refuses the next `listen` on it, which is a confusing way to
617    /// find out. Close explicitly where it matters.
618    pub fn close(mut self) -> Result<(), Error> {
619        self.close_inner()
620    }
621
622    fn close_inner(&mut self) -> Result<(), Error> {
623        if self.closed {
624            return Ok(());
625        }
626        self.closed = true;
627        let cap = self.cap;
628        let r = self.call(cap, RIGHT_WRITE, &[LOP_CLOSE]).map(|_| ());
629        self.ctx.remove_cap(cap);
630        r
631    }
632
633    /// Take one message that arrived during an operation and was NOT the reply. See
634    /// [`Socket::take_held`].
635    pub fn take_held(&mut self) -> Option<Message> {
636        self.held.take()
637    }
638}
639
640impl<'n, 'a: 'n> Drop for Listener<'n, 'a> {
641    fn drop(&mut self) {
642        let _ = self.close_inner();
643    }
644}
645
646/// One accepted TCP connection, held as a capability.
647///
648/// Borrows its [`Listener`], so one connection is open at a time and the tag counter has a single
649/// owner from `Net` down. That is `serve`'s real model - accept one, answer it, close, accept the
650/// next - now enforced rather than remembered.
651pub struct Conn<'c, 'n: 'c, 'a: 'n> {
652    lis: &'c mut Listener<'n, 'a>,
653    cap: CapHandle,
654    closed: bool,
655}
656
657impl<'c, 'n: 'c, 'a: 'n> Conn<'c, 'n, 'a> {
658    /// Read whatever has arrived, into `buf`. Returns how many bytes.
659    ///
660    /// **Zero means nothing has arrived YET**, not end of stream - TCP delivers when it delivers, so
661    /// a caller that wants more loops. Nothing here tells "nothing yet" from "the peer has gone":
662    /// [`is_closed`](Conn::is_closed) reports only whether THIS handle was closed.
663    ///
664    /// Any buffer size works: the request tells `net-stack` how much `buf` holds, and it takes no
665    /// more than that off the connection, so nothing is read and then lost. One call moves at most
666    /// 4095 bytes, one message. (Until 2026-10-10 it asked for everything and dropped what did not fit -
667    /// `backlog/fixed/80` G3.)
668    ///
669    /// **Authority:** this connection's `READ` right.
670    pub fn recv(&mut self, buf: &mut [u8]) -> Result<usize, Error> {
671        let cap = self.cap;
672        let max = buf.len().min(u16::MAX as usize) as u16;
673        let [lo, hi] = max.to_le_bytes();
674        let r = self.lis.call(cap, RIGHT_READ, &[COP_RECV, lo, hi])?;
675        let b = r.payload_bytes();
676        let data = if b.len() > 1 { &b[1..] } else { &[][..] };
677        // More than was asked for is a peer that did not keep its side - said, not trimmed.
678        if data.len() > buf.len() {
679            return Err(Error::Malformed);
680        }
681        buf[..data.len()].copy_from_slice(data);
682        Ok(data.len())
683    }
684
685    /// Send bytes. Returns how many were ACCEPTED, which may be fewer than offered.
686    ///
687    /// **A short send is reported, not hidden.** `net-stack`'s send arena is fixed, so a caller
688    /// offering more than fits must know how much was taken or it loses the tail without being told
689    /// (26.7). Send the remainder on a later call.
690    ///
691    /// **Authority:** this connection's `WRITE` right.
692    pub fn send(&mut self, data: &[u8]) -> Result<usize, Error> {
693        let mut req = [0u8; 1 + 1024];
694        if 1 + data.len() > req.len() {
695            return Err(Error::InvalidInput);
696        }
697        req[0] = COP_SEND;
698        req[1..1 + data.len()].copy_from_slice(data);
699        let cap = self.cap;
700        let r = self.lis.call(cap, RIGHT_WRITE, &req[..1 + data.len()])?;
701        let b = body(&r);
702        if b.len() < 2 {
703            return Err(Error::Malformed);
704        }
705        Ok(((b[1] as usize) << 8 | b[0] as usize).min(data.len()))
706    }
707
708    /// Whether [`close`](Conn::close) has already been called on this handle.
709    pub fn is_closed(&self) -> bool {
710        self.closed
711    }
712
713    /// Close the connection.
714    ///
715    /// Consumes the handle. Dropping also closes, and cannot report the outcome.
716    pub fn close(mut self) -> Result<(), Error> {
717        self.close_inner()
718    }
719
720    fn close_inner(&mut self) -> Result<(), Error> {
721        if self.closed {
722            return Ok(());
723        }
724        self.closed = true;
725        let cap = self.cap;
726        let r = self.lis.call(cap, RIGHT_WRITE, &[COP_CLOSE]).map(|_| ());
727        self.lis.ctx.remove_cap(cap);
728        r
729    }
730}
731
732impl<'c, 'n: 'c, 'a: 'n> Drop for Conn<'c, 'n, 'a> {
733    fn drop(&mut self) {
734        let _ = self.close_inner();
735    }
736}
737
738/// One outbound TCP connection, held as a capability. See [`Net::connect`].
739///
740/// Borrows its [`Net`], so the tag counter has one owner while the connection is open.
741pub struct Stream<'n, 'a: 'n> {
742    net: &'n mut Net<'a>,
743    ctx: &'a ServiceContext,
744    cap: CapHandle,
745    held: Held,
746    closed: bool,
747}
748
749impl<'n, 'a: 'n> Stream<'n, 'a> {
750    fn call(&mut self, right: u8, body_bytes: &[u8]) -> Result<Message, Error> {
751        let tag = self.net.next_tag_pub();
752        resource::invoke(self.ctx, self.cap, right, tag, Some(NET_SECS as u8), body_bytes, NET_SECS, &mut self.held)
753    }
754
755    /// Read whatever has arrived, into `buf`, and return how many bytes.
756    ///
757    /// **Zero means nothing has arrived yet**, exactly as for [`Conn::recv`]: TCP delivers when it
758    /// delivers, and a caller that needs more waits a little and asks again. A connection the peer
759    /// closed and that has been read to the end is released by `net-stack`, after which this returns
760    /// [`Error::Revoked`] - that is the end of the stream.
761    ///
762    /// One call moves at most 4095 bytes, one message, so a buffer of 4 KiB is what a download wants.
763    /// **Authority:** this connection's `READ` right.
764    pub fn recv(&mut self, buf: &mut [u8]) -> Result<usize, Error> {
765        let max = buf.len().min(u16::MAX as usize) as u16;
766        let [lo, hi] = max.to_le_bytes();
767        let r = self.call(RIGHT_READ, &[COP_RECV, lo, hi])?;
768        let b = r.payload_bytes();
769        let data = if b.len() > 1 { &b[1..] } else { &[][..] };
770        if data.len() > buf.len() {
771            return Err(Error::Malformed);
772        }
773        buf[..data.len()].copy_from_slice(data);
774        Ok(data.len())
775    }
776
777    /// Send up to 1024 bytes and return how many were ACCEPTED, which may be fewer, or none while
778    /// `net-stack`'s send buffer is full. Send the rest on a later call; nothing is dropped silently.
779    ///
780    /// **Authority:** this connection's `WRITE` right.
781    pub fn send(&mut self, data: &[u8]) -> Result<usize, Error> {
782        let mut req = [0u8; 1 + 1024];
783        let n = data.len().min(1024);
784        req[0] = COP_SEND;
785        req[1..1 + n].copy_from_slice(&data[..n]);
786        let r = self.call(RIGHT_WRITE, &req[..1 + n])?;
787        let b = body(&r);
788        if b.len() < 2 {
789            return Err(Error::Malformed);
790        }
791        Ok(((b[1] as usize) << 8 | b[0] as usize).min(n))
792    }
793
794    /// Close the connection: `net-stack` sends what is still queued, then its FIN.
795    ///
796    /// Consumes the handle. Dropping also closes, and cannot report the outcome.
797    pub fn close(mut self) -> Result<(), Error> {
798        self.close_inner()
799    }
800
801    fn close_inner(&mut self) -> Result<(), Error> {
802        if self.closed {
803            return Ok(());
804        }
805        self.closed = true;
806        let r = self.call(RIGHT_WRITE, &[COP_CLOSE]).map(|_| ());
807        self.ctx.remove_cap(self.cap);
808        r
809    }
810
811    /// Take one message that arrived during an operation and was NOT the reply. See
812    /// [`Socket::take_held`].
813    pub fn take_held(&mut self) -> Option<Message> {
814        self.held.take()
815    }
816}
817
818impl<'n, 'a: 'n> Drop for Stream<'n, 'a> {
819    fn drop(&mut self) {
820        let _ = self.close_inner();
821    }
822}