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}