Skip to main content

agentos_kernel/
socket_table.rs

1use crate::fd_table::TransferredFd;
2use crate::poll::{PollEvents, POLLERR, POLLHUP, POLLIN, POLLOUT};
3use crate::vfs::normalize_path;
4#[cfg(not(target_arch = "wasm32"))]
5use agentos_runtime::accounting::{Reservation, ResourceClass, ResourceLedger};
6use std::any::Any;
7use std::collections::{BTreeMap, BTreeSet, VecDeque};
8use std::error::Error;
9use std::fmt;
10use std::net::{Ipv4Addr, Ipv6Addr};
11use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
12use std::sync::{Arc, Mutex, MutexGuard};
13use std::time::Instant;
14
15pub type SocketId = u64;
16pub type SocketResult<T> = Result<T, SocketTableError>;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum SocketReadinessKind {
20    Data,
21    Accept,
22}
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub struct SocketReadiness {
26    pub socket_id: SocketId,
27    pub kind: SocketReadinessKind,
28}
29
30type SocketReadinessSink = Arc<dyn Fn(SocketReadiness) + Send + Sync + 'static>;
31
32#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
33pub struct SocketReadTraceSnapshot {
34    pub socket_record_clone_calls: u64,
35    pub socket_record_clone_us: u64,
36    pub read_recv_calls: u64,
37    pub read_recv_bytes: u64,
38    pub read_recv_chunks: u64,
39    pub read_recv_copy_us: u64,
40}
41
42struct SocketReadTraceCounters {
43    socket_record_clone_calls: AtomicU64,
44    socket_record_clone_us: AtomicU64,
45    read_recv_calls: AtomicU64,
46    read_recv_bytes: AtomicU64,
47    read_recv_chunks: AtomicU64,
48    read_recv_copy_us: AtomicU64,
49}
50
51impl SocketReadTraceCounters {
52    const fn new() -> Self {
53        Self {
54            socket_record_clone_calls: AtomicU64::new(0),
55            socket_record_clone_us: AtomicU64::new(0),
56            read_recv_calls: AtomicU64::new(0),
57            read_recv_bytes: AtomicU64::new(0),
58            read_recv_chunks: AtomicU64::new(0),
59            read_recv_copy_us: AtomicU64::new(0),
60        }
61    }
62}
63
64static SOCKET_READ_TRACE_ENABLED: AtomicBool = AtomicBool::new(false);
65static SOCKET_READ_TRACE_COUNTERS: SocketReadTraceCounters = SocketReadTraceCounters::new();
66
67pub fn set_socket_read_trace_enabled(enabled: bool) {
68    SOCKET_READ_TRACE_ENABLED.store(enabled, Ordering::Relaxed);
69}
70
71pub fn reset_socket_read_trace() {
72    for counter in [
73        &SOCKET_READ_TRACE_COUNTERS.socket_record_clone_calls,
74        &SOCKET_READ_TRACE_COUNTERS.socket_record_clone_us,
75        &SOCKET_READ_TRACE_COUNTERS.read_recv_calls,
76        &SOCKET_READ_TRACE_COUNTERS.read_recv_bytes,
77        &SOCKET_READ_TRACE_COUNTERS.read_recv_chunks,
78        &SOCKET_READ_TRACE_COUNTERS.read_recv_copy_us,
79    ] {
80        counter.store(0, Ordering::Relaxed);
81    }
82}
83
84pub fn socket_read_trace_snapshot() -> SocketReadTraceSnapshot {
85    SocketReadTraceSnapshot {
86        socket_record_clone_calls: SOCKET_READ_TRACE_COUNTERS
87            .socket_record_clone_calls
88            .load(Ordering::Relaxed),
89        socket_record_clone_us: SOCKET_READ_TRACE_COUNTERS
90            .socket_record_clone_us
91            .load(Ordering::Relaxed),
92        read_recv_calls: SOCKET_READ_TRACE_COUNTERS
93            .read_recv_calls
94            .load(Ordering::Relaxed),
95        read_recv_bytes: SOCKET_READ_TRACE_COUNTERS
96            .read_recv_bytes
97            .load(Ordering::Relaxed),
98        read_recv_chunks: SOCKET_READ_TRACE_COUNTERS
99            .read_recv_chunks
100            .load(Ordering::Relaxed),
101        read_recv_copy_us: SOCKET_READ_TRACE_COUNTERS
102            .read_recv_copy_us
103            .load(Ordering::Relaxed),
104    }
105}
106
107#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
108pub struct InetSocketAddress {
109    host: String,
110    port: u16,
111}
112
113impl InetSocketAddress {
114    pub fn new(host: impl Into<String>, port: u16) -> Self {
115        Self {
116            host: host.into(),
117            port,
118        }
119    }
120
121    pub fn host(&self) -> &str {
122        &self.host
123    }
124
125    pub const fn port(&self) -> u16 {
126        self.port
127    }
128}
129
130#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
131pub enum SocketDomain {
132    Inet,
133    Inet6,
134    Unix,
135}
136
137#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
138pub enum SocketType {
139    Stream,
140    Datagram,
141    SeqPacket,
142}
143
144#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
145pub enum SocketState {
146    Created,
147    Bound,
148    Listening,
149    Connected,
150}
151
152impl SocketState {
153    pub const fn counts_as_listener(self) -> bool {
154        matches!(self, Self::Listening)
155    }
156
157    pub const fn counts_as_connection(self) -> bool {
158        matches!(self, Self::Connected)
159    }
160}
161
162#[derive(Debug, Clone, Copy, PartialEq, Eq)]
163pub enum SocketShutdown {
164    Read,
165    Write,
166    Both,
167}
168
169#[derive(Debug, Clone, Copy, PartialEq, Eq)]
170pub enum DatagramSocketOption {
171    ReuseAddr,
172    ReusePort,
173    Broadcast,
174}
175
176#[derive(Debug, Clone, Copy, PartialEq, Eq)]
177pub struct SocketSpec {
178    pub domain: SocketDomain,
179    pub socket_type: SocketType,
180}
181
182impl SocketSpec {
183    pub const fn new(domain: SocketDomain, socket_type: SocketType) -> Self {
184        Self {
185            domain,
186            socket_type,
187        }
188    }
189
190    pub const fn tcp() -> Self {
191        Self::new(SocketDomain::Inet, SocketType::Stream)
192    }
193
194    pub const fn udp() -> Self {
195        Self::new(SocketDomain::Inet, SocketType::Datagram)
196    }
197
198    pub const fn unix_stream() -> Self {
199        Self::new(SocketDomain::Unix, SocketType::Stream)
200    }
201
202    pub const fn unix_datagram() -> Self {
203        Self::new(SocketDomain::Unix, SocketType::Datagram)
204    }
205
206    pub const fn unix_seqpacket() -> Self {
207        Self::new(SocketDomain::Unix, SocketType::SeqPacket)
208    }
209}
210
211#[derive(Debug, Clone, PartialEq, Eq)]
212pub struct SocketRecord {
213    id: SocketId,
214    owner_pid: u32,
215    spec: SocketSpec,
216    state: SocketState,
217    local_address: Option<InetSocketAddress>,
218    peer_address: Option<InetSocketAddress>,
219    local_unix_path: Option<String>,
220    peer_unix_path: Option<String>,
221    listener_state: Option<ListenerState>,
222    connection_state: Option<ConnectionState>,
223    datagram_state: Option<DatagramState>,
224}
225
226impl SocketRecord {
227    pub const fn id(&self) -> SocketId {
228        self.id
229    }
230
231    pub const fn owner_pid(&self) -> u32 {
232        self.owner_pid
233    }
234
235    pub const fn spec(&self) -> SocketSpec {
236        self.spec
237    }
238
239    pub const fn state(&self) -> SocketState {
240        self.state
241    }
242
243    pub fn local_address(&self) -> Option<&InetSocketAddress> {
244        self.local_address.as_ref()
245    }
246
247    pub fn peer_address(&self) -> Option<&InetSocketAddress> {
248        self.peer_address.as_ref()
249    }
250
251    pub fn local_unix_path(&self) -> Option<&str> {
252        self.local_unix_path.as_deref()
253    }
254
255    pub fn peer_unix_path(&self) -> Option<&str> {
256        self.peer_unix_path.as_deref()
257    }
258
259    pub fn listen_backlog(&self) -> Option<usize> {
260        self.listener_state.as_ref().map(|state| state.backlog)
261    }
262
263    pub fn pending_accept_count(&self) -> usize {
264        self.listener_state
265            .as_ref()
266            .map(|state| state.pending_accepts.len())
267            .unwrap_or(0)
268    }
269
270    pub fn peer_socket_id(&self) -> Option<SocketId> {
271        self.connection_state
272            .as_ref()
273            .and_then(|state| state.peer_socket_id)
274    }
275
276    pub fn buffered_read_bytes(&self) -> usize {
277        self.connection_state
278            .as_ref()
279            .map(ConnectionState::buffered_len)
280            .unwrap_or(0)
281    }
282
283    pub fn read_shutdown(&self) -> bool {
284        self.connection_state
285            .as_ref()
286            .map(|state| state.read_shutdown)
287            .unwrap_or(false)
288    }
289
290    pub fn write_shutdown(&self) -> bool {
291        self.connection_state
292            .as_ref()
293            .map(|state| state.write_shutdown)
294            .unwrap_or(false)
295    }
296
297    pub fn peer_write_shutdown(&self) -> bool {
298        self.connection_state
299            .as_ref()
300            .map(|state| state.peer_write_shutdown)
301            .unwrap_or(false)
302    }
303
304    pub fn queued_datagrams(&self) -> usize {
305        self.datagram_state
306            .as_ref()
307            .map(|state| state.recv_queue.len())
308            .unwrap_or_else(|| {
309                self.connection_state
310                    .as_ref()
311                    .filter(|_| self.spec.socket_type != SocketType::Stream)
312                    .map(|state| state.recv_buffer.len())
313                    .unwrap_or(0)
314            })
315    }
316
317    pub fn queued_datagram_bytes(&self) -> usize {
318        self.datagram_state
319            .as_ref()
320            .map(|state| datagram_queue_bytes(&state.recv_queue))
321            .unwrap_or(0)
322    }
323
324    pub fn reuse_address(&self) -> bool {
325        self.datagram_state
326            .as_ref()
327            .map(|state| state.reuse_addr)
328            .unwrap_or(false)
329    }
330
331    pub fn reuse_port(&self) -> bool {
332        self.datagram_state
333            .as_ref()
334            .map(|state| state.reuse_port)
335            .unwrap_or(false)
336    }
337
338    pub fn broadcast_enabled(&self) -> bool {
339        self.datagram_state
340            .as_ref()
341            .map(|state| state.broadcast)
342            .unwrap_or(false)
343    }
344
345    pub fn multicast_membership_count(&self) -> usize {
346        self.datagram_state
347            .as_ref()
348            .map(|state| state.multicast_memberships.len())
349            .unwrap_or(0)
350    }
351
352    pub fn has_multicast_membership(&self, membership: &SocketMulticastMembership) -> bool {
353        self.datagram_state
354            .as_ref()
355            .map(|state| state.multicast_memberships.contains(membership))
356            .unwrap_or(false)
357    }
358}
359
360#[derive(Debug, Clone, PartialEq, Eq)]
361pub struct ReceivedDatagram {
362    source_address: Option<InetSocketAddress>,
363    payload: Vec<u8>,
364}
365
366pub type OpaqueTransferredRight = Arc<dyn Any + Send + Sync + 'static>;
367
368#[derive(Clone)]
369pub enum TransferredSocketRight {
370    Fd(TransferredFd),
371    Opaque(OpaqueTransferredRight),
372}
373
374impl fmt::Debug for TransferredSocketRight {
375    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
376        match self {
377            Self::Fd(fd) => f.debug_tuple("Fd").field(&fd.description_id()).finish(),
378            Self::Opaque(resource) => f
379                .debug_tuple("Opaque")
380                .field(&(Arc::as_ptr(resource) as *const ()))
381                .finish(),
382        }
383    }
384}
385
386impl PartialEq for TransferredSocketRight {
387    fn eq(&self, other: &Self) -> bool {
388        match (self, other) {
389            (Self::Fd(left), Self::Fd(right)) => left == right,
390            (Self::Opaque(left), Self::Opaque(right)) => Arc::ptr_eq(left, right),
391            _ => false,
392        }
393    }
394}
395
396impl Eq for TransferredSocketRight {}
397
398#[derive(Debug)]
399pub struct ReceivedSocketMessage {
400    pub payload: Vec<u8>,
401    pub rights: Vec<TransferredSocketRight>,
402    pub truncated: bool,
403    pub full_length: usize,
404}
405
406impl ReceivedDatagram {
407    pub fn source_address(&self) -> Option<&InetSocketAddress> {
408        self.source_address.as_ref()
409    }
410
411    pub fn payload(&self) -> &[u8] {
412        &self.payload
413    }
414
415    pub fn into_parts(self) -> (Option<InetSocketAddress>, Vec<u8>) {
416        (self.source_address, self.payload)
417    }
418}
419
420/// Native sidecar handoff that transfers the kernel queue's exact resource
421/// ownership with the datagram. Standalone callers may keep using
422/// `recv_datagram`, whose boundary releases queue ownership immediately.
423#[cfg(not(target_arch = "wasm32"))]
424pub type DatagramReservations = (Reservation, Reservation, Reservation, Reservation);
425
426#[cfg(not(target_arch = "wasm32"))]
427#[derive(Debug)]
428pub struct ChargedReceivedDatagram {
429    datagram: ReceivedDatagram,
430    reservations: Option<DatagramReservations>,
431}
432
433#[cfg(not(target_arch = "wasm32"))]
434impl ChargedReceivedDatagram {
435    pub fn into_parts(
436        self,
437    ) -> (
438        Option<InetSocketAddress>,
439        Vec<u8>,
440        Option<DatagramReservations>,
441    ) {
442        let (source_address, payload) = self.datagram.into_parts();
443        (source_address, payload, self.reservations)
444    }
445}
446
447#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
448pub struct SocketTableSnapshot {
449    pub sockets: usize,
450    pub listeners: usize,
451    pub connections: usize,
452    pub buffered_bytes: usize,
453    pub datagram_queue_len: usize,
454}
455
456#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
457pub struct SocketMulticastMembership {
458    group_address: String,
459    interface_address: Option<String>,
460}
461
462impl SocketMulticastMembership {
463    pub fn new(group_address: impl Into<String>, interface_address: Option<String>) -> Self {
464        Self {
465            group_address: group_address.into(),
466            interface_address,
467        }
468    }
469
470    pub fn group_address(&self) -> &str {
471        &self.group_address
472    }
473
474    pub fn interface_address(&self) -> Option<&str> {
475        self.interface_address.as_deref()
476    }
477}
478
479#[derive(Debug, Clone, PartialEq, Eq)]
480pub struct SocketTableError {
481    code: &'static str,
482    message: String,
483}
484
485impl SocketTableError {
486    pub fn code(&self) -> &'static str {
487        self.code
488    }
489
490    fn not_found(socket_id: SocketId) -> Self {
491        Self {
492            code: "ENOENT",
493            message: format!("no such socket {socket_id}"),
494        }
495    }
496
497    fn invalid_argument(message: impl Into<String>) -> Self {
498        Self {
499            code: "EINVAL",
500            message: message.into(),
501        }
502    }
503
504    fn address_in_use(message: impl Into<String>) -> Self {
505        Self {
506            code: "EADDRINUSE",
507            message: message.into(),
508        }
509    }
510
511    fn address_not_available(message: impl Into<String>) -> Self {
512        Self {
513            code: "EADDRNOTAVAIL",
514            message: message.into(),
515        }
516    }
517
518    fn not_found_address(message: impl Into<String>) -> Self {
519        Self {
520            code: "ECONNREFUSED",
521            message: message.into(),
522        }
523    }
524
525    fn would_block(message: impl Into<String>) -> Self {
526        Self {
527            code: "EAGAIN",
528            message: message.into(),
529        }
530    }
531
532    fn not_connected(message: impl Into<String>) -> Self {
533        Self {
534            code: "ENOTCONN",
535            message: message.into(),
536        }
537    }
538
539    fn broken_pipe(message: impl Into<String>) -> Self {
540        Self {
541            code: "EPIPE",
542            message: message.into(),
543        }
544    }
545
546    #[cfg(not(target_arch = "wasm32"))]
547    fn resource_limit(error: agentos_runtime::accounting::LimitError) -> Self {
548        Self {
549            code: "EAGAIN",
550            message: error.to_string(),
551        }
552    }
553
554    #[cfg(not(target_arch = "wasm32"))]
555    fn accounting_invariant(message: impl Into<String>) -> Self {
556        Self {
557            code: "EIO",
558            message: format!(
559                "ERR_AGENTOS_RESOURCE_ACCOUNTING_INVARIANT: {}",
560                message.into()
561            ),
562        }
563    }
564
565    fn id_exhausted() -> Self {
566        Self {
567            code: "EMFILE",
568            message: String::from(
569                "ERR_AGENTOS_SOCKET_ID_EXHAUSTED: VM kernel socket id space exhausted",
570            ),
571        }
572    }
573}
574
575impl fmt::Display for SocketTableError {
576    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
577        write!(f, "{}: {}", self.code, self.message)
578    }
579}
580
581impl Error for SocketTableError {}
582
583#[derive(Debug, Default)]
584struct SocketTableState {
585    sockets: BTreeMap<SocketId, SocketRecord>,
586    by_owner: BTreeMap<u32, BTreeSet<SocketId>>,
587    bound_inet_streams: BTreeMap<InetSocketAddress, SocketId>,
588    bound_inet_datagrams: BTreeMap<InetSocketAddress, BTreeSet<SocketId>>,
589    bound_unix_streams: BTreeMap<String, SocketId>,
590    multicast_groups: BTreeMap<SocketMulticastMembership, BTreeSet<SocketId>>,
591    next_socket_id: SocketId,
592    #[cfg(not(target_arch = "wasm32"))]
593    retained_resources: BTreeMap<SocketId, RetainedSocketResources>,
594}
595
596/// Exact ownership for bytes and datagrams retained by one kernel socket.
597///
598/// Socket and connection count reservations intentionally live in the shared
599/// capability registry. Keeping only queue storage here avoids charging the
600/// same kernel-backed capability twice while still making queue admission and
601/// mutation one operation.
602#[cfg(not(target_arch = "wasm32"))]
603#[derive(Debug, Default)]
604struct RetainedSocketResources {
605    buffered_bytes: VecDeque<Reservation>,
606    datagrams: VecDeque<Reservation>,
607    udp_bytes: VecDeque<Reservation>,
608    udp_datagrams: VecDeque<Reservation>,
609}
610
611#[derive(Debug, Clone, PartialEq, Eq)]
612struct ListenerState {
613    backlog: usize,
614    pending_accepts: VecDeque<PendingConnection>,
615}
616
617#[derive(Debug, Clone, PartialEq, Eq, Default)]
618struct ConnectionState {
619    peer_socket_id: Option<SocketId>,
620    recv_buffer: VecDeque<RecvChunk>,
621    recv_buffer_len: usize,
622    read_shutdown: bool,
623    write_shutdown: bool,
624    peer_write_shutdown: bool,
625}
626
627#[derive(Debug, Clone, PartialEq, Eq)]
628struct RecvChunk {
629    data: Vec<u8>,
630    rights: Vec<TransferredSocketRight>,
631}
632
633impl ConnectionState {
634    fn buffered_len(&self) -> usize {
635        self.recv_buffer_len
636    }
637
638    fn has_buffered_data(&self) -> bool {
639        !self.recv_buffer.is_empty()
640    }
641
642    fn push_recv(&mut self, data: &[u8], rights: Vec<TransferredSocketRight>) {
643        if data.is_empty() && rights.is_empty() {
644            return;
645        }
646        self.recv_buffer.push_back(RecvChunk {
647            data: data.to_vec(),
648            rights,
649        });
650        self.recv_buffer_len = self.recv_buffer_len.saturating_add(data.len());
651    }
652
653    fn read_recv(&mut self, max_bytes: usize, message_oriented: bool) -> Option<Vec<u8>> {
654        self.read_recv_message(max_bytes, message_oriented)
655            .map(|message| message.payload)
656    }
657
658    fn read_recv_message(
659        &mut self,
660        max_bytes: usize,
661        message_oriented: bool,
662    ) -> Option<ReceivedSocketMessage> {
663        if self.recv_buffer.is_empty() {
664            return None;
665        }
666
667        if message_oriented {
668            let chunk = self.recv_buffer.pop_front()?;
669            self.recv_buffer_len = self.recv_buffer_len.saturating_sub(chunk.data.len());
670            let truncated = chunk.data.len() > max_bytes;
671            let full_length = chunk.data.len();
672            return Some(ReceivedSocketMessage {
673                payload: chunk.data[..chunk.data.len().min(max_bytes)].to_vec(),
674                rights: chunk.rights,
675                truncated,
676                full_length,
677            });
678        }
679
680        if max_bytes == 0 {
681            let chunk = self.recv_buffer.front_mut()?;
682            return Some(ReceivedSocketMessage {
683                payload: Vec::new(),
684                rights: std::mem::take(&mut chunk.rights),
685                truncated: false,
686                full_length: 0,
687            });
688        }
689
690        let read_len = self.recv_buffer_len.min(max_bytes);
691        self.recv_buffer_len -= read_len;
692
693        let mut remaining = read_len;
694        let mut chunks = 0usize;
695        let trace_started = SOCKET_READ_TRACE_ENABLED
696            .load(Ordering::Relaxed)
697            .then(Instant::now);
698        let mut out = Vec::with_capacity(read_len);
699        let mut rights = Vec::new();
700        while remaining > 0 {
701            let mut chunk = self.recv_buffer.pop_front()?;
702            chunks += 1;
703            rights.append(&mut chunk.rights);
704            if chunk.data.len() <= remaining {
705                remaining -= chunk.data.len();
706                out.extend_from_slice(&chunk.data);
707                continue;
708            }
709
710            let tail = chunk.data.split_off(remaining);
711            out.extend_from_slice(&chunk.data);
712            self.recv_buffer.push_front(RecvChunk {
713                data: tail,
714                rights: Vec::new(),
715            });
716            remaining = 0;
717        }
718        if let Some(started) = trace_started {
719            SOCKET_READ_TRACE_COUNTERS
720                .read_recv_calls
721                .fetch_add(1, Ordering::Relaxed);
722            SOCKET_READ_TRACE_COUNTERS.read_recv_bytes.fetch_add(
723                u64::try_from(read_len).unwrap_or(u64::MAX),
724                Ordering::Relaxed,
725            );
726            SOCKET_READ_TRACE_COUNTERS
727                .read_recv_chunks
728                .fetch_add(u64::try_from(chunks).unwrap_or(u64::MAX), Ordering::Relaxed);
729            SOCKET_READ_TRACE_COUNTERS.read_recv_copy_us.fetch_add(
730                u64::try_from(started.elapsed().as_micros()).unwrap_or(u64::MAX),
731                Ordering::Relaxed,
732            );
733        }
734        Some(ReceivedSocketMessage {
735            payload: out,
736            rights,
737            truncated: false,
738            full_length: read_len,
739        })
740    }
741
742    fn peek_recv_message(
743        &self,
744        max_bytes: usize,
745        message_oriented: bool,
746    ) -> Option<ReceivedSocketMessage> {
747        let first = self.recv_buffer.front()?;
748        if message_oriented {
749            return Some(ReceivedSocketMessage {
750                payload: first.data[..first.data.len().min(max_bytes)].to_vec(),
751                rights: first.rights.clone(),
752                truncated: first.data.len() > max_bytes,
753                full_length: first.data.len(),
754            });
755        }
756
757        let read_len = self.recv_buffer_len.min(max_bytes);
758        let mut payload = Vec::with_capacity(read_len);
759        let mut rights = Vec::new();
760        let mut remaining = read_len;
761        for (index, chunk) in self.recv_buffer.iter().enumerate() {
762            if index > 0 && remaining == 0 {
763                break;
764            }
765            rights.extend(chunk.rights.iter().cloned());
766            let take = remaining.min(chunk.data.len());
767            payload.extend_from_slice(&chunk.data[..take]);
768            remaining -= take;
769        }
770        Some(ReceivedSocketMessage {
771            payload,
772            rights,
773            truncated: false,
774            full_length: read_len,
775        })
776    }
777
778    fn clear_recv(&mut self) {
779        self.recv_buffer.clear();
780        self.recv_buffer_len = 0;
781    }
782}
783
784#[derive(Debug, Clone, PartialEq, Eq)]
785struct PendingConnection {
786    peer_address: Option<InetSocketAddress>,
787    peer_unix_path: Option<String>,
788    accepted_socket_id: Option<SocketId>,
789}
790
791#[derive(Debug, Clone, PartialEq, Eq, Default)]
792struct DatagramState {
793    recv_queue: VecDeque<QueuedDatagram>,
794    reuse_addr: bool,
795    reuse_port: bool,
796    broadcast: bool,
797    multicast_memberships: BTreeSet<SocketMulticastMembership>,
798}
799
800#[derive(Debug, Clone, PartialEq, Eq)]
801struct QueuedDatagram {
802    source_address: Option<InetSocketAddress>,
803    payload: Vec<u8>,
804}
805
806struct SocketTableInner {
807    state: Mutex<SocketTableState>,
808    readiness_sink: Mutex<Option<SocketReadinessSink>>,
809    #[cfg(not(target_arch = "wasm32"))]
810    resource_ledger: Mutex<Option<Arc<ResourceLedger>>>,
811}
812
813impl Default for SocketTableInner {
814    fn default() -> Self {
815        Self {
816            state: Mutex::new(SocketTableState::default()),
817            readiness_sink: Mutex::new(None),
818            #[cfg(not(target_arch = "wasm32"))]
819            resource_ledger: Mutex::new(None),
820        }
821    }
822}
823
824impl fmt::Debug for SocketTableInner {
825    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
826        f.debug_struct("SocketTableInner")
827            .field("state", &self.state)
828            .finish_non_exhaustive()
829    }
830}
831
832#[derive(Debug, Clone, Default)]
833pub struct SocketTable {
834    inner: Arc<SocketTableInner>,
835}
836
837impl SocketTable {
838    pub fn new() -> Self {
839        Self::default()
840    }
841
842    /// Install the VM ledger before any socket is created. The table owns
843    /// reservations only for data physically retained in kernel queues.
844    #[cfg(not(target_arch = "wasm32"))]
845    pub fn set_resource_ledger(&self, ledger: Arc<ResourceLedger>) -> SocketResult<()> {
846        let table = lock_or_recover(&self.inner.state);
847        if !table.sockets.is_empty() {
848            return Err(SocketTableError::invalid_argument(
849                "socket resource ledger must be installed before socket creation",
850            ));
851        }
852        let mut target = lock_or_recover(&self.inner.resource_ledger);
853        if let Some(current) = target.as_ref() {
854            if Arc::ptr_eq(current, &ledger) {
855                return Ok(());
856            }
857            return Err(SocketTableError::invalid_argument(
858                "socket resource ledger is already installed",
859            ));
860        }
861        *target = Some(ledger);
862        Ok(())
863    }
864
865    #[cfg(not(target_arch = "wasm32"))]
866    pub fn has_resource_ledger(&self) -> bool {
867        lock_or_recover(&self.inner.resource_ledger).is_some()
868    }
869
870    #[cfg(target_arch = "wasm32")]
871    pub const fn has_resource_ledger(&self) -> bool {
872        false
873    }
874
875    #[cfg(not(target_arch = "wasm32"))]
876    fn resource_ledger(&self) -> Option<Arc<ResourceLedger>> {
877        lock_or_recover(&self.inner.resource_ledger).clone()
878    }
879
880    #[cfg(not(target_arch = "wasm32"))]
881    pub fn buffered_byte_capacity_available(&self) -> bool {
882        self.resource_ledger()
883            .is_none_or(|ledger| ledger.capacity_available(ResourceClass::BufferedBytes, 1))
884    }
885
886    #[cfg(not(target_arch = "wasm32"))]
887    pub fn datagram_capacity_available(&self) -> bool {
888        self.resource_ledger().is_none_or(|ledger| {
889            ledger.capacity_available(ResourceClass::Datagrams, 1)
890                && ledger.capacity_available(ResourceClass::UdpDatagrams, 1)
891        })
892    }
893
894    #[cfg(not(target_arch = "wasm32"))]
895    fn reserve_buffered_bytes(&self, amount: usize) -> SocketResult<Option<Reservation>> {
896        if amount == 0 {
897            return Ok(None);
898        }
899        self.resource_ledger()
900            .map(|ledger| {
901                ledger
902                    .reserve(ResourceClass::BufferedBytes, amount)
903                    .map_err(SocketTableError::resource_limit)
904            })
905            .transpose()
906    }
907
908    #[cfg(not(target_arch = "wasm32"))]
909    fn reserve_datagram(
910        &self,
911        amount: usize,
912    ) -> SocketResult<Option<(Reservation, Reservation, Reservation, Reservation)>> {
913        let Some(ledger) = self.resource_ledger() else {
914            return Ok(None);
915        };
916        let bytes = ledger
917            .reserve(ResourceClass::BufferedBytes, amount)
918            .map_err(SocketTableError::resource_limit)?;
919        let datagram = ledger
920            .reserve(ResourceClass::Datagrams, 1)
921            .map_err(SocketTableError::resource_limit)?;
922        let udp_bytes = ledger
923            .reserve(ResourceClass::UdpBytes, amount)
924            .map_err(SocketTableError::resource_limit)?;
925        let udp_datagram = ledger
926            .reserve(ResourceClass::UdpDatagrams, 1)
927            .map_err(SocketTableError::resource_limit)?;
928        Ok(Some((bytes, datagram, udp_bytes, udp_datagram)))
929    }
930
931    pub fn set_readiness_sink<F>(&self, sink: Option<F>)
932    where
933        F: Fn(SocketReadiness) + Send + Sync + 'static,
934    {
935        let mut target = lock_or_recover(&self.inner.readiness_sink);
936        *target = sink.map(|sink| Arc::new(sink) as SocketReadinessSink);
937    }
938
939    fn emit_readiness(&self, readiness: Option<SocketReadiness>) {
940        let Some(readiness) = readiness else {
941            return;
942        };
943        let sink = lock_or_recover(&self.inner.readiness_sink).clone();
944        if let Some(sink) = sink {
945            sink(readiness);
946        }
947    }
948
949    pub fn allocate(&self, owner_pid: u32, spec: SocketSpec) -> SocketResult<SocketRecord> {
950        self.allocate_with_state(owner_pid, spec, SocketState::Created)
951    }
952
953    pub fn allocate_with_state(
954        &self,
955        owner_pid: u32,
956        spec: SocketSpec,
957        state: SocketState,
958    ) -> SocketResult<SocketRecord> {
959        let mut table = lock_or_recover(&self.inner.state);
960        let socket_id = next_socket_id(&mut table)?;
961        let record = SocketRecord {
962            id: socket_id,
963            owner_pid,
964            spec,
965            state,
966            local_address: None,
967            peer_address: None,
968            local_unix_path: None,
969            peer_unix_path: None,
970            listener_state: None,
971            connection_state: default_connection_state(spec, state),
972            datagram_state: default_datagram_state(spec),
973        };
974        table.sockets.insert(socket_id, record.clone());
975        table
976            .by_owner
977            .entry(owner_pid)
978            .or_default()
979            .insert(socket_id);
980        Ok(record)
981    }
982
983    pub fn get(&self, socket_id: SocketId) -> Option<SocketRecord> {
984        lock_or_recover(&self.inner.state)
985            .sockets
986            .get(&socket_id)
987            .cloned()
988    }
989
990    pub fn reassign_owner(&self, socket_id: SocketId, owner_pid: u32) -> SocketResult<()> {
991        let mut table = lock_or_recover(&self.inner.state);
992        let previous_owner = table
993            .sockets
994            .get(&socket_id)
995            .ok_or_else(|| SocketTableError::not_found(socket_id))?
996            .owner_pid;
997        if previous_owner == owner_pid {
998            return Ok(());
999        }
1000        if let Some(ids) = table.by_owner.get_mut(&previous_owner) {
1001            ids.remove(&socket_id);
1002            if ids.is_empty() {
1003                table.by_owner.remove(&previous_owner);
1004            }
1005        }
1006        table
1007            .by_owner
1008            .entry(owner_pid)
1009            .or_default()
1010            .insert(socket_id);
1011        table
1012            .sockets
1013            .get_mut(&socket_id)
1014            .expect("socket checked before owner reassignment")
1015            .owner_pid = owner_pid;
1016        Ok(())
1017    }
1018
1019    pub fn records_for_owner(&self, owner_pid: u32) -> Vec<SocketRecord> {
1020        let table = lock_or_recover(&self.inner.state);
1021        let Some(socket_ids) = table.by_owner.get(&owner_pid) else {
1022            return Vec::new();
1023        };
1024        socket_ids
1025            .iter()
1026            .filter_map(|socket_id| table.sockets.get(socket_id).cloned())
1027            .collect()
1028    }
1029
1030    pub fn update_state(
1031        &self,
1032        socket_id: SocketId,
1033        new_state: SocketState,
1034    ) -> SocketResult<SocketRecord> {
1035        let mut table = lock_or_recover(&self.inner.state);
1036        let record = table
1037            .sockets
1038            .get_mut(&socket_id)
1039            .ok_or_else(|| SocketTableError::not_found(socket_id))?;
1040        validate_state_transition(record.state, new_state)?;
1041        record.state = new_state;
1042        if new_state != SocketState::Listening {
1043            record.listener_state = None;
1044        }
1045        if new_state == SocketState::Connected && supports_connection_lifecycle(record.spec) {
1046            record
1047                .connection_state
1048                .get_or_insert_with(ConnectionState::default);
1049        } else if new_state != SocketState::Connected {
1050            record.connection_state = None;
1051        }
1052        Ok(record.clone())
1053    }
1054
1055    pub fn bind_inet(
1056        &self,
1057        socket_id: SocketId,
1058        address: InetSocketAddress,
1059    ) -> SocketResult<SocketRecord> {
1060        let mut address = normalize_inet_address(address);
1061        let mut table = lock_or_recover(&self.inner.state);
1062        let existing = table
1063            .sockets
1064            .get(&socket_id)
1065            .cloned()
1066            .ok_or_else(|| SocketTableError::not_found(socket_id))?;
1067        if !supports_inet_bind(existing.spec) {
1068            return Err(SocketTableError::invalid_argument(format!(
1069                "socket {socket_id} is not an INET socket"
1070            )));
1071        }
1072        // POSIX bind(port=0) assigns a free ephemeral port (kernel-owned, so the
1073        // converged browser path does not need a host port allocator).
1074        if address.port() == 0 {
1075            let port = assign_ephemeral_inet_port(&table, existing.spec, address.host())?;
1076            address = normalize_inet_address(InetSocketAddress::new(address.host(), port));
1077        }
1078        let conflicting_ids =
1079            lookup_conflicting_bound_inet_socket_ids(&table, existing.spec, &address);
1080        if has_incompatible_inet_bind_conflict(&table, &existing, &conflicting_ids) {
1081            return Err(SocketTableError::address_in_use(format!(
1082                "address {}:{} is already bound",
1083                address.host(),
1084                address.port()
1085            )));
1086        }
1087        let cloned = {
1088            let record = table
1089                .sockets
1090                .get_mut(&socket_id)
1091                .ok_or_else(|| SocketTableError::not_found(socket_id))?;
1092
1093            match record.state {
1094                SocketState::Created => {}
1095                SocketState::Bound if record.local_address.as_ref() == Some(&address) => {
1096                    return Ok(record.clone());
1097                }
1098                SocketState::Bound | SocketState::Listening | SocketState::Connected => {
1099                    return Err(SocketTableError::invalid_argument(format!(
1100                        "socket {socket_id} cannot bind in state {:?}",
1101                        record.state
1102                    )));
1103                }
1104            }
1105
1106            record.local_address = Some(address.clone());
1107            record.peer_address = None;
1108            record.local_unix_path = None;
1109            record.peer_unix_path = None;
1110            record.listener_state = None;
1111            record.connection_state = None;
1112            record.state = SocketState::Bound;
1113            record.clone()
1114        };
1115        register_bound_inet_socket(&mut table, cloned.spec, address, socket_id);
1116        Ok(cloned)
1117    }
1118
1119    pub fn set_datagram_socket_option(
1120        &self,
1121        socket_id: SocketId,
1122        option: DatagramSocketOption,
1123        enabled: bool,
1124    ) -> SocketResult<SocketRecord> {
1125        let mut table = lock_or_recover(&self.inner.state);
1126        let record = table
1127            .sockets
1128            .get_mut(&socket_id)
1129            .ok_or_else(|| SocketTableError::not_found(socket_id))?;
1130        let datagram_state = datagram_state_mut(record)?;
1131
1132        match option {
1133            DatagramSocketOption::ReuseAddr => datagram_state.reuse_addr = enabled,
1134            DatagramSocketOption::ReusePort => datagram_state.reuse_port = enabled,
1135            DatagramSocketOption::Broadcast => datagram_state.broadcast = enabled,
1136        }
1137
1138        Ok(record.clone())
1139    }
1140
1141    pub fn add_multicast_membership(
1142        &self,
1143        socket_id: SocketId,
1144        membership: SocketMulticastMembership,
1145    ) -> SocketResult<SocketRecord> {
1146        let mut table = lock_or_recover(&self.inner.state);
1147        let normalized_membership = {
1148            let record = table
1149                .sockets
1150                .get(&socket_id)
1151                .ok_or_else(|| SocketTableError::not_found(socket_id))?;
1152            validate_multicast_socket(record)?;
1153            normalize_multicast_membership(record.spec, membership)?
1154        };
1155
1156        let cloned = {
1157            let record = table
1158                .sockets
1159                .get_mut(&socket_id)
1160                .ok_or_else(|| SocketTableError::not_found(socket_id))?;
1161            let datagram_state = datagram_state_mut(record)?;
1162            datagram_state
1163                .multicast_memberships
1164                .insert(normalized_membership.clone());
1165            record.clone()
1166        };
1167
1168        table
1169            .multicast_groups
1170            .entry(normalized_membership)
1171            .or_default()
1172            .insert(socket_id);
1173        Ok(cloned)
1174    }
1175
1176    pub fn drop_multicast_membership(
1177        &self,
1178        socket_id: SocketId,
1179        membership: SocketMulticastMembership,
1180    ) -> SocketResult<SocketRecord> {
1181        let mut table = lock_or_recover(&self.inner.state);
1182        let normalized_membership = {
1183            let record = table
1184                .sockets
1185                .get(&socket_id)
1186                .ok_or_else(|| SocketTableError::not_found(socket_id))?;
1187            validate_multicast_socket(record)?;
1188            normalize_multicast_membership(record.spec, membership)?
1189        };
1190
1191        let cloned = {
1192            let record = table
1193                .sockets
1194                .get_mut(&socket_id)
1195                .ok_or_else(|| SocketTableError::not_found(socket_id))?;
1196            let datagram_state = datagram_state_mut(record)?;
1197            if !datagram_state
1198                .multicast_memberships
1199                .remove(&normalized_membership)
1200            {
1201                return Err(SocketTableError::address_not_available(format!(
1202                    "socket {socket_id} has not joined multicast group {}",
1203                    normalized_membership.group_address()
1204                )));
1205            }
1206            record.clone()
1207        };
1208
1209        if let Some(members) = table.multicast_groups.get_mut(&normalized_membership) {
1210            members.remove(&socket_id);
1211            if members.is_empty() {
1212                table.multicast_groups.remove(&normalized_membership);
1213            }
1214        }
1215
1216        Ok(cloned)
1217    }
1218
1219    pub fn bind_unix(
1220        &self,
1221        socket_id: SocketId,
1222        path: impl Into<String>,
1223    ) -> SocketResult<SocketRecord> {
1224        let path = normalize_unix_socket_path(path.into())?;
1225        let mut table = lock_or_recover(&self.inner.state);
1226        let existing = table
1227            .sockets
1228            .get(&socket_id)
1229            .cloned()
1230            .ok_or_else(|| SocketTableError::not_found(socket_id))?;
1231        if !supports_unix_stream_lifecycle(existing.spec) {
1232            return Err(SocketTableError::invalid_argument(format!(
1233                "socket {socket_id} is not a Unix stream socket"
1234            )));
1235        }
1236        let existing_id = table.bound_unix_streams.get(&path).copied();
1237        let cloned = {
1238            let record = table
1239                .sockets
1240                .get_mut(&socket_id)
1241                .ok_or_else(|| SocketTableError::not_found(socket_id))?;
1242
1243            if let Some(bound_socket_id) = existing_id {
1244                if bound_socket_id != socket_id {
1245                    return Err(SocketTableError::address_in_use(format!(
1246                        "path {path} is already bound"
1247                    )));
1248                }
1249            }
1250
1251            match record.state {
1252                SocketState::Created => {}
1253                SocketState::Bound if record.local_unix_path.as_deref() == Some(path.as_str()) => {
1254                    return Ok(record.clone());
1255                }
1256                SocketState::Bound | SocketState::Listening | SocketState::Connected => {
1257                    return Err(SocketTableError::invalid_argument(format!(
1258                        "socket {socket_id} cannot bind in state {:?}",
1259                        record.state
1260                    )));
1261                }
1262            }
1263
1264            record.local_address = None;
1265            record.peer_address = None;
1266            record.local_unix_path = Some(path.clone());
1267            record.peer_unix_path = None;
1268            record.listener_state = None;
1269            record.connection_state = None;
1270            record.state = SocketState::Bound;
1271            record.clone()
1272        };
1273        table.bound_unix_streams.insert(path, socket_id);
1274        Ok(cloned)
1275    }
1276
1277    pub fn listen(&self, socket_id: SocketId, backlog: usize) -> SocketResult<SocketRecord> {
1278        if backlog == 0 {
1279            return Err(SocketTableError::invalid_argument(
1280                "listener backlog must be greater than zero",
1281            ));
1282        }
1283
1284        let mut table = lock_or_recover(&self.inner.state);
1285        let record = table
1286            .sockets
1287            .get_mut(&socket_id)
1288            .ok_or_else(|| SocketTableError::not_found(socket_id))?;
1289
1290        if !supports_listener_lifecycle(record.spec) {
1291            return Err(SocketTableError::invalid_argument(format!(
1292                "socket {socket_id} is not a stream socket"
1293            )));
1294        }
1295        if record.state != SocketState::Bound || !has_bound_endpoint(record) {
1296            return Err(SocketTableError::invalid_argument(format!(
1297                "socket {socket_id} must be bound before listen"
1298            )));
1299        }
1300
1301        record.state = SocketState::Listening;
1302        record.listener_state = Some(ListenerState {
1303            backlog,
1304            pending_accepts: VecDeque::new(),
1305        });
1306        Ok(record.clone())
1307    }
1308
1309    pub fn enqueue_incoming_tcp_connection(
1310        &self,
1311        listener_socket_id: SocketId,
1312        peer_address: InetSocketAddress,
1313    ) -> SocketResult<()> {
1314        let readiness = {
1315            let mut table = lock_or_recover(&self.inner.state);
1316            let record = table
1317                .sockets
1318                .get_mut(&listener_socket_id)
1319                .ok_or_else(|| SocketTableError::not_found(listener_socket_id))?;
1320
1321            if record.state != SocketState::Listening {
1322                return Err(SocketTableError::invalid_argument(format!(
1323                    "socket {listener_socket_id} is not listening"
1324                )));
1325            }
1326
1327            let listener_state = record.listener_state.as_mut().ok_or_else(|| {
1328                SocketTableError::invalid_argument(format!(
1329                    "socket {listener_socket_id} has no listener state"
1330                ))
1331            })?;
1332
1333            if listener_state.pending_accepts.len() >= listener_state.backlog {
1334                return Err(SocketTableError::would_block(format!(
1335                    "listener {listener_socket_id} backlog is full"
1336                )));
1337            }
1338
1339            let was_empty = listener_state.pending_accepts.is_empty();
1340            listener_state.pending_accepts.push_back(PendingConnection {
1341                peer_address: Some(peer_address),
1342                peer_unix_path: None,
1343                accepted_socket_id: None,
1344            });
1345            was_empty.then_some(SocketReadiness {
1346                socket_id: listener_socket_id,
1347                kind: SocketReadinessKind::Accept,
1348            })
1349        };
1350        self.emit_readiness(readiness);
1351        Ok(())
1352    }
1353
1354    pub fn accept(&self, listener_socket_id: SocketId) -> SocketResult<SocketRecord> {
1355        let mut table = lock_or_recover(&self.inner.state);
1356        let (owner_pid, spec, local_address, local_unix_path, needs_socket_id) = {
1357            let record = table
1358                .sockets
1359                .get(&listener_socket_id)
1360                .ok_or_else(|| SocketTableError::not_found(listener_socket_id))?;
1361
1362            if record.state != SocketState::Listening {
1363                return Err(SocketTableError::invalid_argument(format!(
1364                    "socket {listener_socket_id} is not listening"
1365                )));
1366            }
1367
1368            let listener_state = record.listener_state.as_ref().ok_or_else(|| {
1369                SocketTableError::invalid_argument(format!(
1370                    "socket {listener_socket_id} has no listener state"
1371                ))
1372            })?;
1373            let pending = listener_state.pending_accepts.front().ok_or_else(|| {
1374                SocketTableError::would_block(format!(
1375                    "listener {listener_socket_id} has no pending connections"
1376                ))
1377            })?;
1378
1379            (
1380                record.owner_pid,
1381                record.spec,
1382                record.local_address.clone(),
1383                record.local_unix_path.clone(),
1384                pending.accepted_socket_id.is_none(),
1385            )
1386        };
1387        // External pending connections need a new kernel identity. Reserve it
1388        // before removing the backlog entry so exhaustion is a retryable,
1389        // non-destructive accept failure.
1390        let new_socket_id = needs_socket_id
1391            .then(|| next_socket_id(&mut table))
1392            .transpose()?;
1393        let pending = table
1394            .sockets
1395            .get_mut(&listener_socket_id)
1396            .and_then(|record| record.listener_state.as_mut())
1397            .and_then(|listener| listener.pending_accepts.pop_front())
1398            .ok_or_else(|| {
1399                SocketTableError::accounting_invariant(format!(
1400                    "listener {listener_socket_id} lost a pending accept during allocation"
1401                ))
1402            })?;
1403
1404        if let Some(accepted_socket_id) = pending.accepted_socket_id {
1405            return table
1406                .sockets
1407                .get(&accepted_socket_id)
1408                .cloned()
1409                .ok_or_else(|| SocketTableError::not_found(accepted_socket_id));
1410        }
1411
1412        let socket_id = new_socket_id.ok_or_else(|| {
1413            SocketTableError::accounting_invariant(format!(
1414                "listener {listener_socket_id} accepted an external connection without an id"
1415            ))
1416        })?;
1417        let record = SocketRecord {
1418            id: socket_id,
1419            owner_pid,
1420            spec,
1421            state: SocketState::Connected,
1422            local_address,
1423            peer_address: pending.peer_address,
1424            local_unix_path,
1425            peer_unix_path: pending.peer_unix_path,
1426            listener_state: None,
1427            connection_state: default_connection_state(spec, SocketState::Connected),
1428            datagram_state: default_datagram_state(spec),
1429        };
1430        table.sockets.insert(socket_id, record.clone());
1431        table
1432            .by_owner
1433            .entry(owner_pid)
1434            .or_default()
1435            .insert(socket_id);
1436        Ok(record)
1437    }
1438
1439    pub fn connect_pair(
1440        &self,
1441        socket_id: SocketId,
1442        peer_socket_id: SocketId,
1443    ) -> SocketResult<(SocketRecord, SocketRecord)> {
1444        if socket_id == peer_socket_id {
1445            return Err(SocketTableError::invalid_argument(
1446                "socket cannot connect to itself",
1447            ));
1448        }
1449
1450        let mut table = lock_or_recover(&self.inner.state);
1451        let mut socket = table
1452            .sockets
1453            .remove(&socket_id)
1454            .ok_or_else(|| SocketTableError::not_found(socket_id))?;
1455        let Some(mut peer) = table.sockets.remove(&peer_socket_id) else {
1456            table.sockets.insert(socket_id, socket);
1457            return Err(SocketTableError::not_found(peer_socket_id));
1458        };
1459
1460        if let Err(error) = validate_connect_pair(&socket, &peer) {
1461            table.sockets.insert(socket_id, socket);
1462            table.sockets.insert(peer_socket_id, peer);
1463            return Err(error);
1464        }
1465
1466        socket.state = SocketState::Connected;
1467        socket.peer_address = peer.local_address.clone();
1468        socket.peer_unix_path = peer.local_unix_path.clone();
1469        socket.listener_state = None;
1470        socket.connection_state = Some(ConnectionState {
1471            peer_socket_id: Some(peer_socket_id),
1472            ..ConnectionState::default()
1473        });
1474
1475        peer.state = SocketState::Connected;
1476        peer.peer_address = socket.local_address.clone();
1477        peer.peer_unix_path = socket.local_unix_path.clone();
1478        peer.listener_state = None;
1479        peer.connection_state = Some(ConnectionState {
1480            peer_socket_id: Some(socket_id),
1481            ..ConnectionState::default()
1482        });
1483
1484        let socket_clone = socket.clone();
1485        let peer_clone = peer.clone();
1486        table.sockets.insert(socket_id, socket);
1487        table.sockets.insert(peer_socket_id, peer);
1488        Ok((socket_clone, peer_clone))
1489    }
1490
1491    pub fn find_bound_inet_socket(
1492        &self,
1493        spec: SocketSpec,
1494        address: &InetSocketAddress,
1495    ) -> Option<SocketRecord> {
1496        let address = normalize_inet_address(address.clone());
1497        let table = lock_or_recover(&self.inner.state);
1498        let socket_id = lookup_bound_inet_socket(&table, spec, &address)?;
1499        table.sockets.get(&socket_id).cloned()
1500    }
1501
1502    pub fn connect_to_bound_inet_stream(
1503        &self,
1504        socket_id: SocketId,
1505        target_address: InetSocketAddress,
1506    ) -> SocketResult<()> {
1507        let target_address = normalize_inet_address(target_address);
1508        let (result, readiness) = {
1509            let mut table = lock_or_recover(&self.inner.state);
1510            let listener_socket_id =
1511                lookup_bound_inet_socket_in_table(&table.bound_inet_streams, &target_address)
1512                    .ok_or_else(|| {
1513                        SocketTableError::not_found_address(format!(
1514                            "no listening socket bound at {}:{}",
1515                            target_address.host(),
1516                            target_address.port()
1517                        ))
1518                    })?;
1519
1520            if socket_id == listener_socket_id {
1521                return Err(SocketTableError::invalid_argument(
1522                    "socket cannot connect to its own listening endpoint",
1523                ));
1524            }
1525
1526            let mut client = table
1527                .sockets
1528                .remove(&socket_id)
1529                .ok_or_else(|| SocketTableError::not_found(socket_id))?;
1530            let mut accept_was_empty = false;
1531            let result = (|| {
1532                // Validate the listener and confirm backlog capacity BEFORE consuming a
1533                // socket id. The id counter is monotonic and never
1534                // reclaims, so allocating an id before this check leaks one on every
1535                // rejected connect (for example when the backlog is full).
1536                {
1537                    let listener = table
1538                        .sockets
1539                        .get(&listener_socket_id)
1540                        .ok_or_else(|| SocketTableError::not_found(listener_socket_id))?;
1541                    validate_connect_to_listener(&client, listener)?;
1542
1543                    let listener_state = listener.listener_state.as_ref().ok_or_else(|| {
1544                        SocketTableError::invalid_argument(format!(
1545                            "socket {listener_socket_id} has no listener state"
1546                        ))
1547                    })?;
1548                    if listener_state.pending_accepts.len() >= listener_state.backlog {
1549                        return Err(SocketTableError::would_block(format!(
1550                            "listener {listener_socket_id} backlog is full"
1551                        )));
1552                    }
1553                }
1554
1555                // Capacity confirmed: only now is it safe to consume a socket id.
1556                let accepted_socket_id = next_socket_id(&mut table)?;
1557                let listener = table
1558                    .sockets
1559                    .get_mut(&listener_socket_id)
1560                    .ok_or_else(|| SocketTableError::not_found(listener_socket_id))?;
1561                let listener_state = listener.listener_state.as_mut().ok_or_else(|| {
1562                    SocketTableError::invalid_argument(format!(
1563                        "socket {listener_socket_id} has no listener state"
1564                    ))
1565                })?;
1566
1567                let accepted = SocketRecord {
1568                    id: accepted_socket_id,
1569                    owner_pid: listener.owner_pid,
1570                    spec: listener.spec,
1571                    state: SocketState::Connected,
1572                    local_address: listener.local_address.clone(),
1573                    peer_address: client.local_address.clone(),
1574                    local_unix_path: None,
1575                    peer_unix_path: None,
1576                    listener_state: None,
1577                    connection_state: Some(ConnectionState {
1578                        peer_socket_id: Some(socket_id),
1579                        ..ConnectionState::default()
1580                    }),
1581                    datagram_state: default_datagram_state(listener.spec),
1582                };
1583
1584                accept_was_empty = listener_state.pending_accepts.is_empty();
1585                listener_state.pending_accepts.push_back(PendingConnection {
1586                    peer_address: client.local_address.clone(),
1587                    peer_unix_path: None,
1588                    accepted_socket_id: Some(accepted_socket_id),
1589                });
1590
1591                client.state = SocketState::Connected;
1592                client.peer_address = listener.local_address.clone();
1593                client.peer_unix_path = None;
1594                client.listener_state = None;
1595                client.connection_state = Some(ConnectionState {
1596                    peer_socket_id: Some(accepted_socket_id),
1597                    ..ConnectionState::default()
1598                });
1599
1600                Ok(accepted)
1601            })();
1602
1603            let result = match result {
1604                Ok(accepted) => {
1605                    let accepted_socket_id = accepted.id;
1606                    table.sockets.insert(socket_id, client);
1607                    table.sockets.insert(accepted_socket_id, accepted.clone());
1608                    table
1609                        .by_owner
1610                        .entry(accepted.owner_pid)
1611                        .or_default()
1612                        .insert(accepted_socket_id);
1613                    Ok(())
1614                }
1615                Err(error) => {
1616                    table.sockets.insert(socket_id, client);
1617                    Err(error)
1618                }
1619            };
1620            let readiness = result.is_ok().then_some(()).and_then(|()| {
1621                accept_was_empty.then_some(SocketReadiness {
1622                    socket_id: listener_socket_id,
1623                    kind: SocketReadinessKind::Accept,
1624                })
1625            });
1626            (result, readiness)
1627        };
1628        self.emit_readiness(readiness);
1629        result
1630    }
1631
1632    pub fn find_bound_unix_socket(&self, path: &str) -> Option<SocketRecord> {
1633        let path = normalize_unix_socket_path(path).ok()?;
1634        let table = lock_or_recover(&self.inner.state);
1635        let socket_id = table.bound_unix_streams.get(&path).copied()?;
1636        table.sockets.get(&socket_id).cloned()
1637    }
1638
1639    pub fn connect_to_bound_unix_stream(
1640        &self,
1641        socket_id: SocketId,
1642        target_path: impl Into<String>,
1643    ) -> SocketResult<()> {
1644        let target_path = normalize_unix_socket_path(target_path.into())?;
1645        let (result, readiness) = {
1646            let mut table = lock_or_recover(&self.inner.state);
1647            let listener_socket_id = table
1648                .bound_unix_streams
1649                .get(&target_path)
1650                .copied()
1651                .ok_or_else(|| {
1652                    SocketTableError::not_found_address(format!(
1653                        "no listening socket bound at path {target_path}"
1654                    ))
1655                })?;
1656
1657            if socket_id == listener_socket_id {
1658                return Err(SocketTableError::invalid_argument(
1659                    "socket cannot connect to its own listening endpoint",
1660                ));
1661            }
1662
1663            let mut client = table
1664                .sockets
1665                .remove(&socket_id)
1666                .ok_or_else(|| SocketTableError::not_found(socket_id))?;
1667            let mut accept_was_empty = false;
1668            let result = (|| {
1669                // Validate the listener and confirm backlog capacity BEFORE consuming a
1670                // socket id. The id counter is monotonic and never
1671                // reclaims, so allocating an id before this check leaks one on every
1672                // rejected connect (for example when the backlog is full).
1673                {
1674                    let listener = table
1675                        .sockets
1676                        .get(&listener_socket_id)
1677                        .ok_or_else(|| SocketTableError::not_found(listener_socket_id))?;
1678                    validate_connect_to_listener(&client, listener)?;
1679
1680                    let listener_state = listener.listener_state.as_ref().ok_or_else(|| {
1681                        SocketTableError::invalid_argument(format!(
1682                            "socket {listener_socket_id} has no listener state"
1683                        ))
1684                    })?;
1685                    if listener_state.pending_accepts.len() >= listener_state.backlog {
1686                        return Err(SocketTableError::would_block(format!(
1687                            "listener {listener_socket_id} backlog is full"
1688                        )));
1689                    }
1690                }
1691
1692                // Capacity confirmed: only now is it safe to consume a socket id.
1693                let accepted_socket_id = next_socket_id(&mut table)?;
1694                let listener = table
1695                    .sockets
1696                    .get_mut(&listener_socket_id)
1697                    .ok_or_else(|| SocketTableError::not_found(listener_socket_id))?;
1698                let listener_state = listener.listener_state.as_mut().ok_or_else(|| {
1699                    SocketTableError::invalid_argument(format!(
1700                        "socket {listener_socket_id} has no listener state"
1701                    ))
1702                })?;
1703
1704                let accepted = SocketRecord {
1705                    id: accepted_socket_id,
1706                    owner_pid: listener.owner_pid,
1707                    spec: listener.spec,
1708                    state: SocketState::Connected,
1709                    local_address: None,
1710                    peer_address: None,
1711                    local_unix_path: listener.local_unix_path.clone(),
1712                    peer_unix_path: client.local_unix_path.clone(),
1713                    listener_state: None,
1714                    connection_state: Some(ConnectionState {
1715                        peer_socket_id: Some(socket_id),
1716                        ..ConnectionState::default()
1717                    }),
1718                    datagram_state: default_datagram_state(listener.spec),
1719                };
1720
1721                accept_was_empty = listener_state.pending_accepts.is_empty();
1722                listener_state.pending_accepts.push_back(PendingConnection {
1723                    peer_address: None,
1724                    peer_unix_path: client.local_unix_path.clone(),
1725                    accepted_socket_id: Some(accepted_socket_id),
1726                });
1727
1728                client.state = SocketState::Connected;
1729                client.peer_address = None;
1730                client.peer_unix_path = listener.local_unix_path.clone();
1731                client.listener_state = None;
1732                client.connection_state = Some(ConnectionState {
1733                    peer_socket_id: Some(accepted_socket_id),
1734                    ..ConnectionState::default()
1735                });
1736
1737                Ok(accepted)
1738            })();
1739
1740            let result = match result {
1741                Ok(accepted) => {
1742                    let accepted_socket_id = accepted.id;
1743                    table.sockets.insert(socket_id, client);
1744                    table.sockets.insert(accepted_socket_id, accepted.clone());
1745                    table
1746                        .by_owner
1747                        .entry(accepted.owner_pid)
1748                        .or_default()
1749                        .insert(accepted_socket_id);
1750                    Ok(())
1751                }
1752                Err(error) => {
1753                    table.sockets.insert(socket_id, client);
1754                    Err(error)
1755                }
1756            };
1757            let readiness = result.is_ok().then_some(()).and_then(|()| {
1758                accept_was_empty.then_some(SocketReadiness {
1759                    socket_id: listener_socket_id,
1760                    kind: SocketReadinessKind::Accept,
1761                })
1762            });
1763            (result, readiness)
1764        };
1765        self.emit_readiness(readiness);
1766        result
1767    }
1768
1769    pub fn send_to_bound_udp_socket(
1770        &self,
1771        socket_id: SocketId,
1772        target_address: InetSocketAddress,
1773        data: &[u8],
1774    ) -> SocketResult<usize> {
1775        let target_address = normalize_inet_address(target_address);
1776        let readiness = {
1777            let mut table = lock_or_recover(&self.inner.state);
1778            let sender = table
1779                .sockets
1780                .get(&socket_id)
1781                .cloned()
1782                .ok_or_else(|| SocketTableError::not_found(socket_id))?;
1783            validate_bound_udp_sender(&sender)?;
1784            let source_address = sender.local_address.as_ref().map(|source| {
1785                if source.host() == "0.0.0.0" || source.host() == "::" {
1786                    InetSocketAddress::new(target_address.host(), source.port())
1787                } else {
1788                    source.clone()
1789                }
1790            });
1791
1792            let receiver_socket_id = lookup_bound_inet_datagram_socket_in_table(
1793                &table.bound_inet_datagrams,
1794                &target_address,
1795            )
1796            .ok_or_else(|| {
1797                SocketTableError::not_found_address(format!(
1798                    "no UDP socket bound at {}:{}",
1799                    target_address.host(),
1800                    target_address.port()
1801                ))
1802            })?;
1803            let receiver = table
1804                .sockets
1805                .get_mut(&receiver_socket_id)
1806                .ok_or_else(|| SocketTableError::not_found(receiver_socket_id))?;
1807            validate_bound_udp_receiver(receiver)?;
1808
1809            // A connected UDP socket only admits datagrams from its selected
1810            // peer. The sender still observes a successful datagram send, as
1811            // it would when the network drops a packet before delivery.
1812            if receiver.peer_address.is_some()
1813                && receiver.peer_address.as_ref() != source_address.as_ref()
1814            {
1815                return Ok(data.len());
1816            }
1817
1818            let datagram_state = receiver.datagram_state.as_mut().ok_or_else(|| {
1819                SocketTableError::invalid_argument(format!(
1820                    "socket {receiver_socket_id} does not support datagrams"
1821                ))
1822            })?;
1823            #[cfg(not(target_arch = "wasm32"))]
1824            let retained = self.reserve_datagram(data.len())?;
1825            let was_empty = datagram_state.recv_queue.is_empty();
1826            datagram_state.recv_queue.push_back(QueuedDatagram {
1827                source_address,
1828                payload: data.to_vec(),
1829            });
1830            #[cfg(not(target_arch = "wasm32"))]
1831            if let Some((bytes, datagram, udp_bytes, udp_datagram)) = retained {
1832                let resources = table
1833                    .retained_resources
1834                    .entry(receiver_socket_id)
1835                    .or_default();
1836                resources.buffered_bytes.push_back(bytes);
1837                resources.datagrams.push_back(datagram);
1838                resources.udp_bytes.push_back(udp_bytes);
1839                resources.udp_datagrams.push_back(udp_datagram);
1840            }
1841            was_empty.then_some(SocketReadiness {
1842                socket_id: receiver_socket_id,
1843                kind: SocketReadinessKind::Data,
1844            })
1845        };
1846        self.emit_readiness(readiness);
1847        Ok(data.len())
1848    }
1849
1850    pub fn connect_bound_udp_socket(
1851        &self,
1852        socket_id: SocketId,
1853        peer_address: InetSocketAddress,
1854    ) -> SocketResult<()> {
1855        let mut table = lock_or_recover(&self.inner.state);
1856        let socket = table
1857            .sockets
1858            .get_mut(&socket_id)
1859            .ok_or_else(|| SocketTableError::not_found(socket_id))?;
1860        validate_bound_udp_sender(socket)?;
1861        if socket.peer_address.is_some() {
1862            return Err(SocketTableError::invalid_argument(format!(
1863                "UDP socket {socket_id} is already connected"
1864            )));
1865        }
1866        socket.peer_address = Some(normalize_inet_address(peer_address));
1867        Ok(())
1868    }
1869
1870    pub fn disconnect_bound_udp_socket(&self, socket_id: SocketId) -> SocketResult<()> {
1871        let mut table = lock_or_recover(&self.inner.state);
1872        let socket = table
1873            .sockets
1874            .get_mut(&socket_id)
1875            .ok_or_else(|| SocketTableError::not_found(socket_id))?;
1876        validate_bound_udp_sender(socket)?;
1877        if socket.peer_address.take().is_none() {
1878            return Err(SocketTableError::not_connected(format!(
1879                "UDP socket {socket_id} is not connected"
1880            )));
1881        }
1882        Ok(())
1883    }
1884
1885    pub fn send_connected_udp_socket(
1886        &self,
1887        socket_id: SocketId,
1888        data: &[u8],
1889    ) -> SocketResult<usize> {
1890        let peer_address = {
1891            let table = lock_or_recover(&self.inner.state);
1892            let socket = table
1893                .sockets
1894                .get(&socket_id)
1895                .ok_or_else(|| SocketTableError::not_found(socket_id))?;
1896            validate_bound_udp_sender(socket)?;
1897            socket.peer_address.clone().ok_or_else(|| {
1898                SocketTableError::not_connected(format!("UDP socket {socket_id} is not connected"))
1899            })?
1900        };
1901        self.send_to_bound_udp_socket(socket_id, peer_address, data)
1902    }
1903
1904    pub fn check_send_to_bound_udp_socket(
1905        &self,
1906        socket_id: SocketId,
1907        target_address: InetSocketAddress,
1908    ) -> SocketResult<()> {
1909        let target_address = normalize_inet_address(target_address);
1910        let table = lock_or_recover(&self.inner.state);
1911        let sender = table
1912            .sockets
1913            .get(&socket_id)
1914            .ok_or_else(|| SocketTableError::not_found(socket_id))?;
1915        validate_bound_udp_sender(sender)?;
1916
1917        let receiver_socket_id = lookup_bound_inet_datagram_socket_in_table(
1918            &table.bound_inet_datagrams,
1919            &target_address,
1920        )
1921        .ok_or_else(|| {
1922            SocketTableError::not_found_address(format!(
1923                "no UDP socket bound at {}:{}",
1924                target_address.host(),
1925                target_address.port()
1926            ))
1927        })?;
1928        let receiver = table
1929            .sockets
1930            .get(&receiver_socket_id)
1931            .ok_or_else(|| SocketTableError::not_found(receiver_socket_id))?;
1932        validate_bound_udp_receiver(receiver)?;
1933        Ok(())
1934    }
1935
1936    pub fn recv_datagram(
1937        &self,
1938        socket_id: SocketId,
1939        max_bytes: usize,
1940    ) -> SocketResult<Option<ReceivedDatagram>> {
1941        let (datagram, reservations) = self.recv_datagram_inner(socket_id, max_bytes)?;
1942        drop(reservations);
1943        Ok(datagram)
1944    }
1945
1946    #[cfg(not(target_arch = "wasm32"))]
1947    pub fn recv_datagram_charged(
1948        &self,
1949        socket_id: SocketId,
1950        max_bytes: usize,
1951    ) -> SocketResult<Option<ChargedReceivedDatagram>> {
1952        let (datagram, reservations) = self.recv_datagram_inner(socket_id, max_bytes)?;
1953        Ok(datagram.map(|datagram| ChargedReceivedDatagram {
1954            datagram,
1955            reservations,
1956        }))
1957    }
1958
1959    #[cfg(not(target_arch = "wasm32"))]
1960    fn recv_datagram_inner(
1961        &self,
1962        socket_id: SocketId,
1963        max_bytes: usize,
1964    ) -> SocketResult<(Option<ReceivedDatagram>, Option<DatagramReservations>)> {
1965        let mut table = lock_or_recover(&self.inner.state);
1966        let record = table
1967            .sockets
1968            .get(&socket_id)
1969            .ok_or_else(|| SocketTableError::not_found(socket_id))?;
1970        validate_bound_udp_receiver(record)?;
1971        let datagram_state = record.datagram_state.as_ref().ok_or_else(|| {
1972            SocketTableError::invalid_argument(format!(
1973                "socket {socket_id} does not support datagrams"
1974            ))
1975        })?;
1976        if datagram_state.recv_queue.is_empty() {
1977            return Err(SocketTableError::would_block(format!(
1978                "socket {socket_id} has no queued datagrams"
1979            )));
1980        }
1981        // Validate and transfer accounting ownership before removing the
1982        // payload. An internal ownership mismatch must not silently discard a
1983        // guest datagram and leave the queue/accountant out of sync.
1984        let reservations = if self.has_resource_ledger() {
1985            Some(take_retained_datagram(&mut table, socket_id)?)
1986        } else {
1987            None
1988        };
1989        let datagram = table
1990            .sockets
1991            .get_mut(&socket_id)
1992            .and_then(|record| record.datagram_state.as_mut())
1993            .and_then(|state| state.recv_queue.pop_front())
1994            .ok_or_else(|| {
1995                SocketTableError::accounting_invariant(format!(
1996                    "socket {socket_id} lost a datagram during charged transfer"
1997                ))
1998            })?;
1999        let mut payload = datagram.payload;
2000        // Truncate in place so the original backing allocation and its full
2001        // byte reservation move together. Copying a prefix here would require
2002        // a second reservation until the source allocation was released.
2003        payload.truncate(max_bytes);
2004        Ok((
2005            Some(ReceivedDatagram {
2006                source_address: datagram.source_address,
2007                payload,
2008            }),
2009            reservations,
2010        ))
2011    }
2012
2013    #[cfg(target_arch = "wasm32")]
2014    fn recv_datagram_inner(
2015        &self,
2016        socket_id: SocketId,
2017        max_bytes: usize,
2018    ) -> SocketResult<(Option<ReceivedDatagram>, Option<()>)> {
2019        let mut table = lock_or_recover(&self.inner.state);
2020        let record = table
2021            .sockets
2022            .get_mut(&socket_id)
2023            .ok_or_else(|| SocketTableError::not_found(socket_id))?;
2024        validate_bound_udp_receiver(record)?;
2025        let datagram_state = record.datagram_state.as_mut().ok_or_else(|| {
2026            SocketTableError::invalid_argument(format!(
2027                "socket {socket_id} does not support datagrams"
2028            ))
2029        })?;
2030        let Some(datagram) = datagram_state.recv_queue.pop_front() else {
2031            return Err(SocketTableError::would_block(format!(
2032                "socket {socket_id} has no queued datagrams"
2033            )));
2034        };
2035        let mut payload = datagram.payload;
2036        payload.truncate(max_bytes);
2037        Ok((
2038            Some(ReceivedDatagram {
2039                source_address: datagram.source_address,
2040                payload,
2041            }),
2042            None,
2043        ))
2044    }
2045
2046    pub fn poll(&self, socket_id: SocketId, requested: PollEvents) -> SocketResult<PollEvents> {
2047        let table = lock_or_recover(&self.inner.state);
2048        let record = table
2049            .sockets
2050            .get(&socket_id)
2051            .ok_or_else(|| SocketTableError::not_found(socket_id))?;
2052
2053        let mut events = PollEvents::empty();
2054        match record.state {
2055            SocketState::Listening => {
2056                if requested.intersects(POLLIN) && record.pending_accept_count() > 0 {
2057                    events |= POLLIN;
2058                }
2059            }
2060            SocketState::Connected => {
2061                let connection = record.connection_state.as_ref().ok_or_else(|| {
2062                    SocketTableError::not_connected(format!("socket {socket_id} is not connected"))
2063                })?;
2064                let peer = connection
2065                    .peer_socket_id
2066                    .and_then(|peer_socket_id| table.sockets.get(&peer_socket_id));
2067
2068                if requested.intersects(POLLIN) && connection.has_buffered_data() {
2069                    events |= POLLIN;
2070                }
2071                if connection.peer_write_shutdown || peer.is_none() {
2072                    events |= POLLHUP;
2073                }
2074
2075                if requested.intersects(POLLOUT) && !connection.write_shutdown {
2076                    if peer
2077                        .and_then(|peer| peer.connection_state.as_ref())
2078                        .map(|peer_connection| peer_connection.read_shutdown)
2079                        .unwrap_or(true)
2080                    {
2081                        events |= POLLERR;
2082                    } else {
2083                        events |= POLLOUT;
2084                    }
2085                }
2086            }
2087            SocketState::Bound if supports_inet_datagram_lifecycle(record.spec) => {
2088                let datagram_state = record.datagram_state.as_ref().ok_or_else(|| {
2089                    SocketTableError::invalid_argument(format!(
2090                        "socket {socket_id} does not support datagrams"
2091                    ))
2092                })?;
2093                if requested.intersects(POLLIN) && !datagram_state.recv_queue.is_empty() {
2094                    events |= POLLIN;
2095                }
2096                if requested.intersects(POLLOUT) {
2097                    events |= POLLOUT;
2098                }
2099            }
2100            SocketState::Created | SocketState::Bound => {}
2101        }
2102
2103        Ok(events)
2104    }
2105
2106    pub fn write(&self, socket_id: SocketId, data: &[u8]) -> SocketResult<usize> {
2107        let readiness = {
2108            let mut table = lock_or_recover(&self.inner.state);
2109            let record = table
2110                .sockets
2111                .get(&socket_id)
2112                .cloned()
2113                .ok_or_else(|| SocketTableError::not_found(socket_id))?;
2114            let connection = record.connection_state.as_ref().ok_or_else(|| {
2115                SocketTableError::not_connected(format!("socket {socket_id} is not connected"))
2116            })?;
2117            if record.state != SocketState::Connected {
2118                return Err(SocketTableError::not_connected(format!(
2119                    "socket {socket_id} is not connected"
2120                )));
2121            }
2122            if connection.write_shutdown {
2123                return Err(SocketTableError::broken_pipe(format!(
2124                    "socket {socket_id} write side is shut down"
2125                )));
2126            }
2127
2128            let peer_socket_id = connection.peer_socket_id.ok_or_else(|| {
2129                SocketTableError::broken_pipe(format!("socket {socket_id} peer is closed"))
2130            })?;
2131            let peer = table.sockets.get_mut(&peer_socket_id).ok_or_else(|| {
2132                SocketTableError::broken_pipe(format!("socket {socket_id} peer is closed"))
2133            })?;
2134            let peer_connection = peer.connection_state.as_mut().ok_or_else(|| {
2135                SocketTableError::broken_pipe(format!("socket {socket_id} peer is closed"))
2136            })?;
2137            if peer_connection.read_shutdown {
2138                return Err(SocketTableError::broken_pipe(format!(
2139                    "socket {peer_socket_id} read side is shut down"
2140                )));
2141            }
2142
2143            #[cfg(not(target_arch = "wasm32"))]
2144            let retained = self.reserve_buffered_bytes(data.len())?;
2145            let was_empty = !peer_connection.has_buffered_data();
2146            peer_connection.push_recv(data, Vec::new());
2147            #[cfg(not(target_arch = "wasm32"))]
2148            if let Some(retained) = retained {
2149                table
2150                    .retained_resources
2151                    .entry(peer_socket_id)
2152                    .or_default()
2153                    .buffered_bytes
2154                    .push_back(retained);
2155            }
2156            (was_empty && !data.is_empty()).then_some(SocketReadiness {
2157                socket_id: peer_socket_id,
2158                kind: SocketReadinessKind::Data,
2159            })
2160        };
2161        self.emit_readiness(readiness);
2162        Ok(data.len())
2163    }
2164
2165    pub fn send_message(
2166        &self,
2167        socket_id: SocketId,
2168        data: &[u8],
2169        rights: Vec<TransferredSocketRight>,
2170    ) -> SocketResult<usize> {
2171        let readiness = {
2172            let mut table = lock_or_recover(&self.inner.state);
2173            let record = table
2174                .sockets
2175                .get(&socket_id)
2176                .cloned()
2177                .ok_or_else(|| SocketTableError::not_found(socket_id))?;
2178            let connection = record.connection_state.as_ref().ok_or_else(|| {
2179                SocketTableError::not_connected(format!("socket {socket_id} is not connected"))
2180            })?;
2181            if record.state != SocketState::Connected {
2182                return Err(SocketTableError::not_connected(format!(
2183                    "socket {socket_id} is not connected"
2184                )));
2185            }
2186            if connection.write_shutdown {
2187                return Err(SocketTableError::broken_pipe(format!(
2188                    "socket {socket_id} write side is shut down"
2189                )));
2190            }
2191            if data.is_empty() && record.spec.socket_type == SocketType::Stream {
2192                return Ok(0);
2193            }
2194
2195            let peer_socket_id = connection.peer_socket_id.ok_or_else(|| {
2196                SocketTableError::broken_pipe(format!("socket {socket_id} peer is closed"))
2197            })?;
2198            let peer = table.sockets.get_mut(&peer_socket_id).ok_or_else(|| {
2199                SocketTableError::broken_pipe(format!("socket {socket_id} peer is closed"))
2200            })?;
2201            let peer_connection = peer.connection_state.as_mut().ok_or_else(|| {
2202                SocketTableError::broken_pipe(format!("socket {socket_id} peer is closed"))
2203            })?;
2204            if peer_connection.read_shutdown {
2205                return Err(SocketTableError::broken_pipe(format!(
2206                    "socket {peer_socket_id} read side is shut down"
2207                )));
2208            }
2209            let was_empty = !peer_connection.has_buffered_data();
2210            peer_connection.push_recv(data, rights);
2211            was_empty.then_some(SocketReadiness {
2212                socket_id: peer_socket_id,
2213                kind: SocketReadinessKind::Data,
2214            })
2215        };
2216        self.emit_readiness(readiness);
2217        Ok(data.len())
2218    }
2219
2220    pub fn check_write(&self, socket_id: SocketId) -> SocketResult<()> {
2221        let table = lock_or_recover(&self.inner.state);
2222        let record = table
2223            .sockets
2224            .get(&socket_id)
2225            .ok_or_else(|| SocketTableError::not_found(socket_id))?;
2226        let connection = record.connection_state.as_ref().ok_or_else(|| {
2227            SocketTableError::not_connected(format!("socket {socket_id} is not connected"))
2228        })?;
2229        if record.state != SocketState::Connected {
2230            return Err(SocketTableError::not_connected(format!(
2231                "socket {socket_id} is not connected"
2232            )));
2233        }
2234        if connection.write_shutdown {
2235            return Err(SocketTableError::broken_pipe(format!(
2236                "socket {socket_id} write side is shut down"
2237            )));
2238        }
2239
2240        let peer_socket_id = connection.peer_socket_id.ok_or_else(|| {
2241            SocketTableError::broken_pipe(format!("socket {socket_id} peer is closed"))
2242        })?;
2243        let peer = table.sockets.get(&peer_socket_id).ok_or_else(|| {
2244            SocketTableError::broken_pipe(format!("socket {socket_id} peer is closed"))
2245        })?;
2246        let peer_connection = peer.connection_state.as_ref().ok_or_else(|| {
2247            SocketTableError::broken_pipe(format!("socket {socket_id} peer is closed"))
2248        })?;
2249        if peer_connection.read_shutdown {
2250            return Err(SocketTableError::broken_pipe(format!(
2251                "socket {peer_socket_id} read side is shut down"
2252            )));
2253        }
2254
2255        Ok(())
2256    }
2257
2258    pub fn read(&self, socket_id: SocketId, max_bytes: usize) -> SocketResult<Option<Vec<u8>>> {
2259        if max_bytes == 0 {
2260            return Ok(Some(Vec::new()));
2261        }
2262
2263        let mut table = lock_or_recover(&self.inner.state);
2264        let record_ref = table
2265            .sockets
2266            .get(&socket_id)
2267            .ok_or_else(|| SocketTableError::not_found(socket_id))?;
2268        let clone_started = SOCKET_READ_TRACE_ENABLED
2269            .load(Ordering::Relaxed)
2270            .then(Instant::now);
2271        let record = record_ref.clone();
2272        if let Some(started) = clone_started {
2273            SOCKET_READ_TRACE_COUNTERS
2274                .socket_record_clone_calls
2275                .fetch_add(1, Ordering::Relaxed);
2276            SOCKET_READ_TRACE_COUNTERS.socket_record_clone_us.fetch_add(
2277                u64::try_from(started.elapsed().as_micros()).unwrap_or(u64::MAX),
2278                Ordering::Relaxed,
2279            );
2280        }
2281        if record.state != SocketState::Connected {
2282            return Err(SocketTableError::not_connected(format!(
2283                "socket {socket_id} is not connected"
2284            )));
2285        }
2286
2287        let connection = record.connection_state.as_ref().ok_or_else(|| {
2288            SocketTableError::not_connected(format!("socket {socket_id} is not connected"))
2289        })?;
2290        if connection.read_shutdown {
2291            return Ok(None);
2292        }
2293        if connection.has_buffered_data() {
2294            let record = table
2295                .sockets
2296                .get_mut(&socket_id)
2297                .ok_or_else(|| SocketTableError::not_found(socket_id))?;
2298            let connection = record.connection_state.as_mut().ok_or_else(|| {
2299                SocketTableError::not_connected(format!("socket {socket_id} is not connected"))
2300            })?;
2301            let result =
2302                connection.read_recv(max_bytes, record.spec.socket_type != SocketType::Stream);
2303            #[cfg(not(target_arch = "wasm32"))]
2304            if self.has_resource_ledger() {
2305                if let Some(read) = result.as_ref() {
2306                    release_retained_bytes(&mut table, socket_id, read.len())?;
2307                }
2308            }
2309            return Ok(result);
2310        }
2311
2312        let peer_open = connection
2313            .peer_socket_id
2314            .map(|peer_socket_id| table.sockets.contains_key(&peer_socket_id))
2315            .unwrap_or(false);
2316        if connection.peer_write_shutdown || !peer_open {
2317            return Ok(None);
2318        }
2319
2320        Err(SocketTableError::would_block(format!(
2321            "socket {socket_id} has no readable data"
2322        )))
2323    }
2324
2325    pub fn recv_message(
2326        &self,
2327        socket_id: SocketId,
2328        max_bytes: usize,
2329        peek: bool,
2330    ) -> SocketResult<Option<ReceivedSocketMessage>> {
2331        let mut table = lock_or_recover(&self.inner.state);
2332        let record = table
2333            .sockets
2334            .get_mut(&socket_id)
2335            .ok_or_else(|| SocketTableError::not_found(socket_id))?;
2336        if record.state != SocketState::Connected {
2337            return Err(SocketTableError::not_connected(format!(
2338                "socket {socket_id} is not connected"
2339            )));
2340        }
2341        let message_oriented = record.spec.socket_type != SocketType::Stream;
2342        let connection = record.connection_state.as_mut().ok_or_else(|| {
2343            SocketTableError::not_connected(format!("socket {socket_id} is not connected"))
2344        })?;
2345        if connection.read_shutdown {
2346            return Ok(None);
2347        }
2348        if connection.has_buffered_data() {
2349            return Ok(if peek {
2350                connection.peek_recv_message(max_bytes, message_oriented)
2351            } else {
2352                connection.read_recv_message(max_bytes, message_oriented)
2353            });
2354        }
2355        if connection.peer_write_shutdown {
2356            return Ok(None);
2357        }
2358        Err(SocketTableError::would_block(format!(
2359            "socket {socket_id} has no readable data"
2360        )))
2361    }
2362
2363    pub fn next_message_rights_count(&self, socket_id: SocketId) -> SocketResult<usize> {
2364        let table = lock_or_recover(&self.inner.state);
2365        let record = table
2366            .sockets
2367            .get(&socket_id)
2368            .ok_or_else(|| SocketTableError::not_found(socket_id))?;
2369        let connection = record.connection_state.as_ref().ok_or_else(|| {
2370            SocketTableError::not_connected(format!("socket {socket_id} is not connected"))
2371        })?;
2372        Ok(connection
2373            .recv_buffer
2374            .iter()
2375            .take(1)
2376            .map(|chunk| chunk.rights.len())
2377            .sum())
2378    }
2379
2380    pub fn shutdown(&self, socket_id: SocketId, how: SocketShutdown) -> SocketResult<SocketRecord> {
2381        let mut table = lock_or_recover(&self.inner.state);
2382        let record = table
2383            .sockets
2384            .remove(&socket_id)
2385            .ok_or_else(|| SocketTableError::not_found(socket_id))?;
2386
2387        if record.state != SocketState::Connected {
2388            table.sockets.insert(socket_id, record);
2389            return Err(SocketTableError::not_connected(format!(
2390                "socket {socket_id} is not connected"
2391            )));
2392        }
2393
2394        let Some(mut connection) = record.connection_state.clone() else {
2395            table.sockets.insert(socket_id, record);
2396            return Err(SocketTableError::not_connected(format!(
2397                "socket {socket_id} is not connected"
2398            )));
2399        };
2400
2401        if matches!(how, SocketShutdown::Read | SocketShutdown::Both) {
2402            connection.clear_recv();
2403            #[cfg(not(target_arch = "wasm32"))]
2404            release_all_retained_bytes(&mut table, socket_id);
2405            connection.read_shutdown = true;
2406        }
2407        if matches!(how, SocketShutdown::Write | SocketShutdown::Both) {
2408            connection.write_shutdown = true;
2409            if let Some(peer_socket_id) = connection.peer_socket_id {
2410                if let Some(peer) = table.sockets.get_mut(&peer_socket_id) {
2411                    if let Some(peer_connection) = peer.connection_state.as_mut() {
2412                        peer_connection.peer_write_shutdown = true;
2413                    }
2414                }
2415            }
2416        }
2417
2418        let mut record = record;
2419        record.connection_state = Some(connection);
2420        let cloned = record.clone();
2421        table.sockets.insert(socket_id, record);
2422        Ok(cloned)
2423    }
2424
2425    pub fn remove(&self, socket_id: SocketId) -> SocketResult<SocketRecord> {
2426        let mut table = lock_or_recover(&self.inner.state);
2427        remove_socket(&mut table, socket_id).ok_or_else(|| SocketTableError::not_found(socket_id))
2428    }
2429
2430    pub fn remove_all_for_pid(&self, owner_pid: u32) -> Vec<SocketRecord> {
2431        let mut table = lock_or_recover(&self.inner.state);
2432        let Some(socket_ids) = table.by_owner.remove(&owner_pid) else {
2433            return Vec::new();
2434        };
2435
2436        socket_ids
2437            .into_iter()
2438            .filter_map(|socket_id| remove_socket(&mut table, socket_id))
2439            .collect()
2440    }
2441
2442    pub fn snapshot(&self) -> SocketTableSnapshot {
2443        let table = lock_or_recover(&self.inner.state);
2444        let mut snapshot = SocketTableSnapshot {
2445            sockets: table.sockets.len(),
2446            ..SocketTableSnapshot::default()
2447        };
2448        for record in table.sockets.values() {
2449            if record.state.counts_as_listener() {
2450                snapshot.listeners += 1;
2451            }
2452            if record.state.counts_as_connection() {
2453                snapshot.connections += 1;
2454            }
2455            if let Some(connection) = &record.connection_state {
2456                snapshot.buffered_bytes = snapshot
2457                    .buffered_bytes
2458                    .saturating_add(connection.buffered_len());
2459                if record.spec.socket_type != SocketType::Stream {
2460                    snapshot.datagram_queue_len = snapshot
2461                        .datagram_queue_len
2462                        .saturating_add(connection.recv_buffer.len());
2463                }
2464            }
2465            if let Some(datagram_state) = &record.datagram_state {
2466                snapshot.datagram_queue_len = snapshot
2467                    .datagram_queue_len
2468                    .saturating_add(datagram_state.recv_queue.len());
2469                snapshot.buffered_bytes = snapshot
2470                    .buffered_bytes
2471                    .saturating_add(datagram_queue_bytes(&datagram_state.recv_queue));
2472            }
2473        }
2474        snapshot
2475    }
2476}
2477
2478#[cfg(not(target_arch = "wasm32"))]
2479fn release_retained_bytes(
2480    table: &mut SocketTableState,
2481    socket_id: SocketId,
2482    mut amount: usize,
2483) -> SocketResult<()> {
2484    if amount == 0 {
2485        return Ok(());
2486    }
2487    let resources = table
2488        .retained_resources
2489        .get_mut(&socket_id)
2490        .ok_or_else(|| {
2491            SocketTableError::accounting_invariant(format!(
2492                "socket {socket_id} released {amount} unowned buffered bytes"
2493            ))
2494        })?;
2495    let owned = resources
2496        .buffered_bytes
2497        .iter()
2498        .try_fold(0usize, |total, reservation| {
2499            total.checked_add(reservation.amount())
2500        })
2501        .ok_or_else(|| {
2502            SocketTableError::accounting_invariant(format!(
2503                "socket {socket_id} buffered-byte ownership overflowed"
2504            ))
2505        })?;
2506    if owned < amount {
2507        return Err(SocketTableError::accounting_invariant(format!(
2508            "socket {socket_id} released {amount} buffered bytes but owns only {owned}"
2509        )));
2510    }
2511    while amount > 0 {
2512        let reservation = resources.buffered_bytes.front_mut().ok_or_else(|| {
2513            SocketTableError::accounting_invariant(format!(
2514                "socket {socket_id} released more buffered bytes than it owns"
2515            ))
2516        })?;
2517        if reservation.amount() <= amount {
2518            amount -= reservation.amount();
2519            resources.buffered_bytes.pop_front();
2520        } else {
2521            let released = reservation.split(amount).ok_or_else(|| {
2522                SocketTableError::accounting_invariant(format!(
2523                    "socket {socket_id} could not split its buffered-byte reservation"
2524                ))
2525            })?;
2526            drop(released);
2527            amount = 0;
2528        }
2529    }
2530    if resources.buffered_bytes.is_empty()
2531        && resources.datagrams.is_empty()
2532        && resources.udp_bytes.is_empty()
2533        && resources.udp_datagrams.is_empty()
2534    {
2535        table.retained_resources.remove(&socket_id);
2536    }
2537    Ok(())
2538}
2539
2540#[cfg(not(target_arch = "wasm32"))]
2541fn take_retained_datagram(
2542    table: &mut SocketTableState,
2543    socket_id: SocketId,
2544) -> SocketResult<(Reservation, Reservation, Reservation, Reservation)> {
2545    let resources = table
2546        .retained_resources
2547        .get_mut(&socket_id)
2548        .ok_or_else(|| {
2549            SocketTableError::accounting_invariant(format!(
2550                "socket {socket_id} transferred an unowned datagram"
2551            ))
2552        })?;
2553    if resources.buffered_bytes.is_empty()
2554        || resources.datagrams.is_empty()
2555        || resources.udp_bytes.is_empty()
2556        || resources.udp_datagrams.is_empty()
2557    {
2558        return Err(SocketTableError::accounting_invariant(format!(
2559            "socket {socket_id} has incomplete datagram ownership"
2560        )));
2561    }
2562    let bytes = resources.buffered_bytes.pop_front().ok_or_else(|| {
2563        SocketTableError::accounting_invariant(format!(
2564            "socket {socket_id} transferred a datagram without buffered-byte ownership"
2565        ))
2566    })?;
2567    let datagram = resources.datagrams.pop_front().ok_or_else(|| {
2568        SocketTableError::accounting_invariant(format!(
2569            "socket {socket_id} transferred more datagrams than it owns"
2570        ))
2571    })?;
2572    let udp_bytes = resources.udp_bytes.pop_front().ok_or_else(|| {
2573        SocketTableError::accounting_invariant(format!(
2574            "socket {socket_id} transferred a datagram without UDP-byte ownership"
2575        ))
2576    })?;
2577    let udp_datagram = resources.udp_datagrams.pop_front().ok_or_else(|| {
2578        SocketTableError::accounting_invariant(format!(
2579            "socket {socket_id} transferred more UDP datagrams than it owns"
2580        ))
2581    })?;
2582    if resources.buffered_bytes.is_empty()
2583        && resources.datagrams.is_empty()
2584        && resources.udp_bytes.is_empty()
2585        && resources.udp_datagrams.is_empty()
2586    {
2587        table.retained_resources.remove(&socket_id);
2588    }
2589    Ok((bytes, datagram, udp_bytes, udp_datagram))
2590}
2591
2592#[cfg(not(target_arch = "wasm32"))]
2593fn release_all_retained_bytes(table: &mut SocketTableState, socket_id: SocketId) {
2594    let remove_entry = if let Some(resources) = table.retained_resources.get_mut(&socket_id) {
2595        resources.buffered_bytes.clear();
2596        resources.datagrams.is_empty()
2597            && resources.udp_bytes.is_empty()
2598            && resources.udp_datagrams.is_empty()
2599    } else {
2600        false
2601    };
2602    if remove_entry {
2603        table.retained_resources.remove(&socket_id);
2604    }
2605}
2606
2607fn datagram_queue_bytes(queue: &VecDeque<QueuedDatagram>) -> usize {
2608    queue
2609        .iter()
2610        .map(|datagram| datagram.payload.len())
2611        .sum::<usize>()
2612}
2613
2614fn next_socket_id(table: &mut SocketTableState) -> SocketResult<SocketId> {
2615    if table.next_socket_id == 0 {
2616        table.next_socket_id = 1;
2617    }
2618    let socket_id = table.next_socket_id;
2619    table.next_socket_id = table
2620        .next_socket_id
2621        .checked_add(1)
2622        .ok_or_else(SocketTableError::id_exhausted)?;
2623    Ok(socket_id)
2624}
2625
2626fn validate_state_transition(current: SocketState, next: SocketState) -> SocketResult<()> {
2627    if current == SocketState::Connected && next != SocketState::Connected {
2628        return Err(SocketTableError::invalid_argument(format!(
2629            "invalid socket state transition from {current:?} to {next:?}"
2630        )));
2631    }
2632    Ok(())
2633}
2634
2635fn validate_connect_pair(socket: &SocketRecord, peer: &SocketRecord) -> SocketResult<()> {
2636    if socket.spec != peer.spec {
2637        return Err(SocketTableError::invalid_argument(format!(
2638            "socket {} and peer {} have incompatible types",
2639            socket.id, peer.id
2640        )));
2641    }
2642    if !supports_connection_lifecycle(socket.spec) {
2643        return Err(SocketTableError::invalid_argument(format!(
2644            "socket {} does not support stream connections",
2645            socket.id
2646        )));
2647    }
2648    if !supports_connection_lifecycle(peer.spec) {
2649        return Err(SocketTableError::invalid_argument(format!(
2650            "socket {} does not support stream connections",
2651            peer.id
2652        )));
2653    }
2654    if !matches!(socket.state, SocketState::Created | SocketState::Bound) {
2655        return Err(SocketTableError::invalid_argument(format!(
2656            "socket {} cannot connect in state {:?}",
2657            socket.id, socket.state
2658        )));
2659    }
2660    if !matches!(peer.state, SocketState::Created | SocketState::Bound) {
2661        return Err(SocketTableError::invalid_argument(format!(
2662            "socket {} cannot connect in state {:?}",
2663            peer.id, peer.state
2664        )));
2665    }
2666    Ok(())
2667}
2668
2669fn default_connection_state(spec: SocketSpec, state: SocketState) -> Option<ConnectionState> {
2670    if state == SocketState::Connected && supports_connection_lifecycle(spec) {
2671        Some(ConnectionState::default())
2672    } else {
2673        None
2674    }
2675}
2676
2677fn default_datagram_state(spec: SocketSpec) -> Option<DatagramState> {
2678    if supports_inet_datagram_lifecycle(spec) {
2679        Some(DatagramState::default())
2680    } else {
2681        None
2682    }
2683}
2684
2685fn supports_connection_lifecycle(spec: SocketSpec) -> bool {
2686    matches!(spec.socket_type, SocketType::Stream)
2687        || (spec.domain == SocketDomain::Unix
2688            && matches!(
2689                spec.socket_type,
2690                SocketType::Datagram | SocketType::SeqPacket
2691            ))
2692}
2693
2694fn supports_listener_lifecycle(spec: SocketSpec) -> bool {
2695    matches!(spec.socket_type, SocketType::Stream)
2696        && matches!(
2697            spec.domain,
2698            SocketDomain::Inet | SocketDomain::Inet6 | SocketDomain::Unix
2699        )
2700}
2701
2702fn supports_inet_bind(spec: SocketSpec) -> bool {
2703    matches!(spec.domain, SocketDomain::Inet | SocketDomain::Inet6)
2704        && matches!(spec.socket_type, SocketType::Stream | SocketType::Datagram)
2705}
2706
2707fn supports_unix_stream_lifecycle(spec: SocketSpec) -> bool {
2708    matches!(spec.socket_type, SocketType::Stream) && matches!(spec.domain, SocketDomain::Unix)
2709}
2710
2711fn supports_inet_stream_lifecycle(spec: SocketSpec) -> bool {
2712    matches!(spec.socket_type, SocketType::Stream)
2713        && matches!(spec.domain, SocketDomain::Inet | SocketDomain::Inet6)
2714}
2715
2716fn supports_inet_datagram_lifecycle(spec: SocketSpec) -> bool {
2717    matches!(spec.socket_type, SocketType::Datagram)
2718        && matches!(spec.domain, SocketDomain::Inet | SocketDomain::Inet6)
2719}
2720
2721/// Pick a free ephemeral port for `bind(port=0)`, scanning the IANA dynamic
2722/// range and skipping any already in conflict for `spec`/`host`.
2723fn assign_ephemeral_inet_port(
2724    table: &SocketTableState,
2725    spec: SocketSpec,
2726    host: &str,
2727) -> SocketResult<u16> {
2728    const EPHEMERAL_START: u16 = 49152;
2729    const EPHEMERAL_END: u16 = 65535;
2730    for port in EPHEMERAL_START..=EPHEMERAL_END {
2731        let candidate = normalize_inet_address(InetSocketAddress::new(host, port));
2732        if lookup_conflicting_bound_inet_socket_ids(table, spec, &candidate).is_empty() {
2733            return Ok(port);
2734        }
2735    }
2736    Err(SocketTableError::address_in_use(
2737        "no free ephemeral port available",
2738    ))
2739}
2740
2741fn lookup_conflicting_bound_inet_socket_ids(
2742    table: &SocketTableState,
2743    spec: SocketSpec,
2744    address: &InetSocketAddress,
2745) -> Vec<SocketId> {
2746    if supports_inet_stream_lifecycle(spec) {
2747        table
2748            .bound_inet_streams
2749            .iter()
2750            .find_map(|(bound_address, socket_id)| {
2751                inet_stream_bind_addresses_overlap(bound_address, address).then_some(*socket_id)
2752            })
2753            .into_iter()
2754            .collect()
2755    } else if supports_inet_datagram_lifecycle(spec) {
2756        table
2757            .bound_inet_datagrams
2758            .iter()
2759            .filter(|(bound_address, _)| inet_stream_bind_addresses_overlap(bound_address, address))
2760            .flat_map(|(_, socket_ids)| socket_ids.iter().copied())
2761            .collect()
2762    } else {
2763        Vec::new()
2764    }
2765}
2766
2767fn lookup_bound_inet_socket(
2768    table: &SocketTableState,
2769    spec: SocketSpec,
2770    address: &InetSocketAddress,
2771) -> Option<SocketId> {
2772    if supports_inet_stream_lifecycle(spec) {
2773        lookup_bound_inet_socket_in_table(&table.bound_inet_streams, address)
2774    } else if supports_inet_datagram_lifecycle(spec) {
2775        lookup_bound_inet_datagram_socket_in_table(&table.bound_inet_datagrams, address)
2776    } else {
2777        None
2778    }
2779}
2780
2781fn inet_stream_bind_addresses_overlap(
2782    existing: &InetSocketAddress,
2783    requested: &InetSocketAddress,
2784) -> bool {
2785    if existing == requested {
2786        return true;
2787    }
2788
2789    wildcard_inet_address(existing).as_ref() == Some(requested)
2790        || wildcard_inet_address(requested).as_ref() == Some(existing)
2791}
2792
2793fn lookup_bound_inet_socket_in_table(
2794    sockets: &BTreeMap<InetSocketAddress, SocketId>,
2795    address: &InetSocketAddress,
2796) -> Option<SocketId> {
2797    sockets.get(address).copied().or_else(|| {
2798        wildcard_inet_address(address).and_then(|wildcard| sockets.get(&wildcard).copied())
2799    })
2800}
2801
2802fn lookup_bound_inet_datagram_socket_in_table(
2803    sockets: &BTreeMap<InetSocketAddress, BTreeSet<SocketId>>,
2804    address: &InetSocketAddress,
2805) -> Option<SocketId> {
2806    sockets
2807        .get(address)
2808        .and_then(|socket_ids| socket_ids.first().copied())
2809        .or_else(|| {
2810            wildcard_inet_address(address).and_then(|wildcard| {
2811                sockets
2812                    .get(&wildcard)
2813                    .and_then(|socket_ids| socket_ids.first().copied())
2814            })
2815        })
2816}
2817
2818fn register_bound_inet_socket(
2819    table: &mut SocketTableState,
2820    spec: SocketSpec,
2821    address: InetSocketAddress,
2822    socket_id: SocketId,
2823) {
2824    if supports_inet_stream_lifecycle(spec) {
2825        table.bound_inet_streams.insert(address, socket_id);
2826    } else if supports_inet_datagram_lifecycle(spec) {
2827        table
2828            .bound_inet_datagrams
2829            .entry(address)
2830            .or_default()
2831            .insert(socket_id);
2832    }
2833}
2834
2835fn validate_connect_to_listener(
2836    client: &SocketRecord,
2837    listener: &SocketRecord,
2838) -> SocketResult<()> {
2839    if !supports_connection_lifecycle(client.spec) {
2840        return Err(SocketTableError::invalid_argument(format!(
2841            "socket {} does not support stream connections",
2842            client.id
2843        )));
2844    }
2845    if !supports_listener_lifecycle(listener.spec) {
2846        return Err(SocketTableError::invalid_argument(format!(
2847            "socket {} is not a stream listener",
2848            listener.id
2849        )));
2850    }
2851    if !matches!(client.state, SocketState::Created | SocketState::Bound) {
2852        return Err(SocketTableError::invalid_argument(format!(
2853            "socket {} cannot connect in state {:?}",
2854            client.id, client.state
2855        )));
2856    }
2857    if listener.state != SocketState::Listening {
2858        return Err(SocketTableError::invalid_argument(format!(
2859            "socket {} is not listening",
2860            listener.id
2861        )));
2862    }
2863    Ok(())
2864}
2865
2866fn has_bound_endpoint(record: &SocketRecord) -> bool {
2867    record.local_address.is_some() || record.local_unix_path.is_some()
2868}
2869
2870fn validate_bound_udp_sender(sender: &SocketRecord) -> SocketResult<()> {
2871    if !supports_inet_datagram_lifecycle(sender.spec) {
2872        return Err(SocketTableError::invalid_argument(format!(
2873            "socket {} is not an INET datagram socket",
2874            sender.id
2875        )));
2876    }
2877    if sender.state != SocketState::Bound || sender.local_address.is_none() {
2878        return Err(SocketTableError::invalid_argument(format!(
2879            "socket {} must be bound before sending datagrams",
2880            sender.id
2881        )));
2882    }
2883    Ok(())
2884}
2885
2886fn validate_bound_udp_receiver(receiver: &SocketRecord) -> SocketResult<()> {
2887    if !supports_inet_datagram_lifecycle(receiver.spec) {
2888        return Err(SocketTableError::invalid_argument(format!(
2889            "socket {} is not an INET datagram socket",
2890            receiver.id
2891        )));
2892    }
2893    if receiver.state != SocketState::Bound || receiver.local_address.is_none() {
2894        return Err(SocketTableError::invalid_argument(format!(
2895            "socket {} must be bound to receive datagrams",
2896            receiver.id
2897        )));
2898    }
2899    Ok(())
2900}
2901
2902fn datagram_state_mut(record: &mut SocketRecord) -> SocketResult<&mut DatagramState> {
2903    if !supports_inet_datagram_lifecycle(record.spec) {
2904        return Err(SocketTableError::invalid_argument(format!(
2905            "socket {} is not an INET datagram socket",
2906            record.id
2907        )));
2908    }
2909    record.datagram_state.as_mut().ok_or_else(|| {
2910        SocketTableError::invalid_argument(format!(
2911            "socket {} does not support datagrams",
2912            record.id
2913        ))
2914    })
2915}
2916
2917fn validate_multicast_socket(record: &SocketRecord) -> SocketResult<()> {
2918    validate_bound_udp_receiver(record)?;
2919    if record.spec.domain != SocketDomain::Inet {
2920        return Err(SocketTableError::invalid_argument(format!(
2921            "socket {} multicast membership is only implemented for IPv4 datagrams",
2922            record.id
2923        )));
2924    }
2925    Ok(())
2926}
2927
2928fn normalize_multicast_membership(
2929    spec: SocketSpec,
2930    membership: SocketMulticastMembership,
2931) -> SocketResult<SocketMulticastMembership> {
2932    let group_address = membership.group_address.trim().to_ascii_lowercase();
2933    let interface_address = membership
2934        .interface_address
2935        .map(|value| value.trim().to_ascii_lowercase())
2936        .filter(|value| !value.is_empty());
2937
2938    match spec.domain {
2939        SocketDomain::Inet => {
2940            let parsed = group_address.parse::<Ipv4Addr>().map_err(|_| {
2941                SocketTableError::invalid_argument(format!(
2942                    "invalid IPv4 multicast address {group_address}"
2943                ))
2944            })?;
2945            if !parsed.is_multicast() {
2946                return Err(SocketTableError::invalid_argument(format!(
2947                    "address {group_address} is not an IPv4 multicast group"
2948                )));
2949            }
2950        }
2951        SocketDomain::Inet6 => {
2952            let parsed = group_address.parse::<Ipv6Addr>().map_err(|_| {
2953                SocketTableError::invalid_argument(format!(
2954                    "invalid IPv6 multicast address {group_address}"
2955                ))
2956            })?;
2957            if !parsed.is_multicast() {
2958                return Err(SocketTableError::invalid_argument(format!(
2959                    "address {group_address} is not an IPv6 multicast group"
2960                )));
2961            }
2962        }
2963        SocketDomain::Unix => {
2964            return Err(SocketTableError::invalid_argument(
2965                "unix sockets do not support multicast membership",
2966            ));
2967        }
2968    }
2969
2970    Ok(SocketMulticastMembership::new(
2971        group_address,
2972        interface_address,
2973    ))
2974}
2975
2976fn has_incompatible_inet_bind_conflict(
2977    table: &SocketTableState,
2978    record: &SocketRecord,
2979    conflicting_ids: &[SocketId],
2980) -> bool {
2981    conflicting_ids.iter().any(|conflicting_id| {
2982        if *conflicting_id == record.id {
2983            return false;
2984        }
2985
2986        let Some(existing) = table.sockets.get(conflicting_id) else {
2987            return false;
2988        };
2989
2990        if supports_inet_datagram_lifecycle(record.spec) {
2991            !inet_datagram_bind_shares_port(record, existing)
2992        } else {
2993            true
2994        }
2995    })
2996}
2997
2998fn inet_datagram_bind_shares_port(requested: &SocketRecord, existing: &SocketRecord) -> bool {
2999    (requested.reuse_port() && existing.reuse_port())
3000        || (requested.reuse_address() && existing.reuse_address())
3001}
3002
3003fn remove_socket(table: &mut SocketTableState, socket_id: SocketId) -> Option<SocketRecord> {
3004    let record = table.sockets.remove(&socket_id)?;
3005    #[cfg(not(target_arch = "wasm32"))]
3006    table.retained_resources.remove(&socket_id);
3007    unregister_bound_socket(table, &record);
3008    unregister_multicast_memberships(table, &record);
3009    if let Some(listener_state) = record.listener_state.as_ref() {
3010        let pending_socket_ids = listener_state
3011            .pending_accepts
3012            .iter()
3013            .filter_map(|pending| pending.accepted_socket_id)
3014            .collect::<Vec<_>>();
3015        for pending_socket_id in pending_socket_ids {
3016            let _ = remove_socket(table, pending_socket_id);
3017        }
3018    }
3019    if let Some(connection) = record.connection_state.as_ref() {
3020        if let Some(peer_socket_id) = connection.peer_socket_id {
3021            if let Some(peer) = table.sockets.get_mut(&peer_socket_id) {
3022                if let Some(peer_connection) = peer.connection_state.as_mut() {
3023                    if peer_connection.peer_socket_id == Some(socket_id) {
3024                        peer_connection.peer_socket_id = None;
3025                    }
3026                    peer_connection.peer_write_shutdown = true;
3027                }
3028            }
3029        }
3030    }
3031    if let Some(owner_sockets) = table.by_owner.get_mut(&record.owner_pid) {
3032        owner_sockets.remove(&socket_id);
3033        if owner_sockets.is_empty() {
3034            table.by_owner.remove(&record.owner_pid);
3035        }
3036    }
3037    Some(record)
3038}
3039
3040fn unregister_bound_socket(table: &mut SocketTableState, record: &SocketRecord) {
3041    let Some(address) = record.local_address.as_ref() else {
3042        if supports_unix_stream_lifecycle(record.spec) {
3043            if let Some(path) = record.local_unix_path.as_ref() {
3044                if table.bound_unix_streams.get(path).copied() == Some(record.id) {
3045                    table.bound_unix_streams.remove(path);
3046                }
3047            }
3048        }
3049        return;
3050    };
3051    if supports_inet_stream_lifecycle(record.spec)
3052        && table.bound_inet_streams.get(address).copied() == Some(record.id)
3053    {
3054        table.bound_inet_streams.remove(address);
3055    }
3056    if supports_inet_datagram_lifecycle(record.spec) {
3057        if let Some(socket_ids) = table.bound_inet_datagrams.get_mut(address) {
3058            socket_ids.remove(&record.id);
3059            if socket_ids.is_empty() {
3060                table.bound_inet_datagrams.remove(address);
3061            }
3062        }
3063    }
3064}
3065
3066fn unregister_multicast_memberships(table: &mut SocketTableState, record: &SocketRecord) {
3067    let Some(datagram_state) = record.datagram_state.as_ref() else {
3068        return;
3069    };
3070
3071    for membership in &datagram_state.multicast_memberships {
3072        if let Some(socket_ids) = table.multicast_groups.get_mut(membership) {
3073            socket_ids.remove(&record.id);
3074            if socket_ids.is_empty() {
3075                table.multicast_groups.remove(membership);
3076            }
3077        }
3078    }
3079}
3080
3081fn normalize_inet_address(address: InetSocketAddress) -> InetSocketAddress {
3082    match address.host().to_ascii_lowercase().as_str() {
3083        "localhost" => InetSocketAddress::new("127.0.0.1", address.port()),
3084        _ => address,
3085    }
3086}
3087
3088fn wildcard_inet_address(address: &InetSocketAddress) -> Option<InetSocketAddress> {
3089    match address.host() {
3090        "127.0.0.1" => Some(InetSocketAddress::new("0.0.0.0", address.port())),
3091        "::1" => Some(InetSocketAddress::new("::", address.port())),
3092        _ => None,
3093    }
3094}
3095
3096fn normalize_unix_socket_path(path: impl AsRef<str>) -> SocketResult<String> {
3097    let normalized = normalize_path(path.as_ref());
3098    if normalized == "/" {
3099        return Err(SocketTableError::invalid_argument(
3100            "unix socket path must not be empty or root",
3101        ));
3102    }
3103    Ok(normalized)
3104}
3105
3106fn lock_or_recover<'a, T>(mutex: &'a Mutex<T>) -> MutexGuard<'a, T> {
3107    match mutex.lock() {
3108        Ok(guard) => guard,
3109        Err(poisoned) => poisoned.into_inner(),
3110    }
3111}
3112
3113#[cfg(test)]
3114mod tests {
3115    use super::*;
3116    #[cfg(not(target_arch = "wasm32"))]
3117    use agentos_runtime::accounting::{ResourceLimit, ResourceUsage};
3118
3119    /// Reads the monotonic socket-id counter without advancing it, so a test can
3120    /// observe whether a code path consumed an id.
3121    fn peek_next_socket_id(table: &SocketTable) -> SocketId {
3122        lock_or_recover(&table.inner.state).next_socket_id
3123    }
3124
3125    #[test]
3126    fn exhausted_socket_ids_fail_without_reusing_a_live_identity() {
3127        let table = SocketTable::new();
3128        lock_or_recover(&table.inner.state).next_socket_id = u64::MAX;
3129
3130        let error = table
3131            .allocate(1, SocketSpec::tcp())
3132            .expect_err("exhausted id space must reject allocation");
3133
3134        assert_eq!(error.code(), "EMFILE");
3135        assert!(error
3136            .to_string()
3137            .contains("ERR_AGENTOS_SOCKET_ID_EXHAUSTED"));
3138        assert_eq!(table.snapshot().sockets, 0);
3139    }
3140
3141    #[test]
3142    fn exhausted_accept_preserves_the_pending_connection() {
3143        let table = SocketTable::new();
3144        let listener = table
3145            .allocate(1, SocketSpec::tcp())
3146            .expect("allocate listener");
3147        let address = InetSocketAddress::new("127.0.0.1", 43001);
3148        table
3149            .bind_inet(listener.id(), address)
3150            .expect("bind listener");
3151        table.listen(listener.id(), 1).expect("listen");
3152        table
3153            .enqueue_incoming_tcp_connection(
3154                listener.id(),
3155                InetSocketAddress::new("127.0.0.1", 43002),
3156            )
3157            .expect("queue incoming connection");
3158        lock_or_recover(&table.inner.state).next_socket_id = u64::MAX;
3159
3160        let error = table
3161            .accept(listener.id())
3162            .expect_err("exhausted id space must reject accept");
3163
3164        assert_eq!(error.code(), "EMFILE");
3165        assert_eq!(
3166            table
3167                .get(listener.id())
3168                .expect("listener remains live")
3169                .pending_accept_count(),
3170            1
3171        );
3172    }
3173
3174    #[test]
3175    fn full_backlog_unix_connect_does_not_consume_socket_id() {
3176        let table = SocketTable::new();
3177        let path = "/tmp/leak-test/server.sock";
3178
3179        let listener = table
3180            .allocate(1, SocketSpec::unix_stream())
3181            .expect("allocate listener");
3182        table
3183            .bind_unix(listener.id, path)
3184            .expect("bind unix listener");
3185        table.listen(listener.id, 1).expect("listen with backlog 1");
3186
3187        // Fill the only backlog slot with one pending connection.
3188        let first = table
3189            .allocate(2, SocketSpec::unix_stream())
3190            .expect("allocate first client");
3191        table
3192            .connect_to_bound_unix_stream(first.id, path)
3193            .expect("first connect fills the backlog");
3194
3195        // A second connect must be rejected because the backlog is full, and it
3196        // must NOT consume a socket id (the counter is monotonic and never reclaims).
3197        let second = table
3198            .allocate(2, SocketSpec::unix_stream())
3199            .expect("allocate second client");
3200        let before = peek_next_socket_id(&table);
3201        let error = table
3202            .connect_to_bound_unix_stream(second.id, path)
3203            .expect_err("full-backlog connect must fail");
3204        assert_eq!(error.code(), "EAGAIN");
3205        let after = peek_next_socket_id(&table);
3206
3207        assert_eq!(
3208            before, after,
3209            "full-backlog unix connect leaked a socket id (counter advanced from {before} to {after})"
3210        );
3211    }
3212
3213    #[test]
3214    fn full_backlog_inet_connect_does_not_consume_socket_id() {
3215        let table = SocketTable::new();
3216        let target = InetSocketAddress::new("127.0.0.1", 49222);
3217
3218        let listener = table
3219            .allocate(1, SocketSpec::tcp())
3220            .expect("allocate listener");
3221        table
3222            .bind_inet(listener.id, target.clone())
3223            .expect("bind inet listener");
3224        table.listen(listener.id, 1).expect("listen with backlog 1");
3225
3226        // Fill the only backlog slot with one pending connection.
3227        let first = table
3228            .allocate(2, SocketSpec::tcp())
3229            .expect("allocate first client");
3230        table
3231            .connect_to_bound_inet_stream(first.id, target.clone())
3232            .expect("first connect fills the backlog");
3233
3234        // A second connect must be rejected because the backlog is full, and it
3235        // must NOT consume a socket id (the counter is monotonic and never reclaims).
3236        let second = table
3237            .allocate(2, SocketSpec::tcp())
3238            .expect("allocate second client");
3239        let before = peek_next_socket_id(&table);
3240        let error = table
3241            .connect_to_bound_inet_stream(second.id, target)
3242            .expect_err("full-backlog connect must fail");
3243        assert_eq!(error.code(), "EAGAIN");
3244        let after = peek_next_socket_id(&table);
3245
3246        assert_eq!(
3247            before, after,
3248            "full-backlog inet connect leaked a socket id (counter advanced from {before} to {after})"
3249        );
3250    }
3251
3252    #[cfg(not(target_arch = "wasm32"))]
3253    fn test_resource_ledger(buffered_bytes: usize, datagrams: usize) -> Arc<ResourceLedger> {
3254        Arc::new(ResourceLedger::root(
3255            "test-vm",
3256            [
3257                (
3258                    ResourceClass::BufferedBytes,
3259                    ResourceLimit::new(buffered_bytes, "test.maxBufferedBytes"),
3260                ),
3261                (
3262                    ResourceClass::Datagrams,
3263                    ResourceLimit::new(datagrams, "test.maxDatagrams"),
3264                ),
3265                (
3266                    ResourceClass::UdpBytes,
3267                    ResourceLimit::new(buffered_bytes, "limits.udp.maxBufferedBytes"),
3268                ),
3269                (
3270                    ResourceClass::UdpDatagrams,
3271                    ResourceLimit::new(datagrams, "limits.udp.maxBufferedDatagrams"),
3272                ),
3273            ],
3274        ))
3275    }
3276
3277    #[cfg(not(target_arch = "wasm32"))]
3278    fn usage(ledger: &ResourceLedger, class: ResourceClass) -> ResourceUsage {
3279        ledger.usage(class)
3280    }
3281
3282    #[cfg(not(target_arch = "wasm32"))]
3283    #[test]
3284    fn stream_queue_reservations_follow_write_read_shutdown_and_close() {
3285        let table = SocketTable::new();
3286        let ledger = test_resource_ledger(5, 1);
3287        table
3288            .set_resource_ledger(Arc::clone(&ledger))
3289            .expect("install resource ledger");
3290
3291        let writer = table
3292            .allocate(1, SocketSpec::tcp())
3293            .expect("allocate writer");
3294        let reader = table
3295            .allocate(2, SocketSpec::tcp())
3296            .expect("allocate reader");
3297        table
3298            .connect_pair(writer.id(), reader.id())
3299            .expect("connect stream pair");
3300
3301        // Socket and connection counts belong to CapabilityRegistry, not the
3302        // kernel queue owner.
3303        assert_eq!(usage(&ledger, ResourceClass::Sockets).used, 0);
3304        assert_eq!(usage(&ledger, ResourceClass::Connections).used, 0);
3305
3306        table.write(writer.id(), b"12345").expect("fill queue");
3307        assert_eq!(usage(&ledger, ResourceClass::BufferedBytes).used, 5);
3308        assert!(!table.buffered_byte_capacity_available());
3309        let error = table
3310            .write(writer.id(), b"6")
3311            .expect_err("write beyond retained-byte limit must fail");
3312        assert_eq!(error.code(), "EAGAIN");
3313        assert_eq!(table.snapshot().buffered_bytes, 5);
3314        assert_eq!(usage(&ledger, ResourceClass::BufferedBytes).used, 5);
3315
3316        assert_eq!(
3317            table.read(reader.id(), 2).expect("partial read"),
3318            Some(b"12".to_vec())
3319        );
3320        assert_eq!(usage(&ledger, ResourceClass::BufferedBytes).used, 3);
3321        assert!(table.buffered_byte_capacity_available());
3322
3323        table
3324            .shutdown(reader.id(), SocketShutdown::Read)
3325            .expect("discard unread queue");
3326        assert_eq!(usage(&ledger, ResourceClass::BufferedBytes).used, 0);
3327
3328        table.remove(writer.id()).expect("close writer");
3329        table.remove(reader.id()).expect("close reader");
3330        assert!(ledger.is_zero());
3331    }
3332
3333    #[cfg(not(target_arch = "wasm32"))]
3334    #[test]
3335    fn retained_byte_mismatch_fails_before_releasing_owned_capacity() {
3336        let table = SocketTable::new();
3337        let ledger = test_resource_ledger(8, 1);
3338        table
3339            .set_resource_ledger(Arc::clone(&ledger))
3340            .expect("install resource ledger");
3341        let writer = table
3342            .allocate(1, SocketSpec::tcp())
3343            .expect("allocate writer");
3344        let reader = table
3345            .allocate(2, SocketSpec::tcp())
3346            .expect("allocate reader");
3347        table
3348            .connect_pair(writer.id(), reader.id())
3349            .expect("connect stream pair");
3350        table.write(writer.id(), b"abc").expect("queue bytes");
3351
3352        let mut state = lock_or_recover(&table.inner.state);
3353        let error = release_retained_bytes(&mut state, reader.id(), 4)
3354            .expect_err("over-release must fail atomically");
3355        assert_eq!(error.code(), "EIO");
3356        assert_eq!(
3357            state
3358                .retained_resources
3359                .get(&reader.id())
3360                .expect("retained ownership")
3361                .buffered_bytes
3362                .front()
3363                .expect("byte reservation")
3364                .amount(),
3365            3
3366        );
3367        drop(state);
3368        assert_eq!(usage(&ledger, ResourceClass::BufferedBytes).used, 3);
3369        assert_eq!(table.snapshot().buffered_bytes, 3);
3370    }
3371
3372    #[cfg(not(target_arch = "wasm32"))]
3373    #[test]
3374    fn datagram_reservations_are_atomic_and_release_full_truncated_payload() {
3375        let table = SocketTable::new();
3376        let ledger = test_resource_ledger(8, 1);
3377        table
3378            .set_resource_ledger(Arc::clone(&ledger))
3379            .expect("install resource ledger");
3380
3381        let sender = table
3382            .allocate(1, SocketSpec::udp())
3383            .expect("allocate sender");
3384        let receiver = table
3385            .allocate(2, SocketSpec::udp())
3386            .expect("allocate receiver");
3387        let sender_address = InetSocketAddress::new("127.0.0.1", 41001);
3388        let receiver_address = InetSocketAddress::new("127.0.0.1", 41002);
3389        table
3390            .bind_inet(sender.id(), sender_address)
3391            .expect("bind sender");
3392        table
3393            .bind_inet(receiver.id(), receiver_address.clone())
3394            .expect("bind receiver");
3395
3396        table
3397            .send_to_bound_udp_socket(sender.id(), receiver_address.clone(), b"abc")
3398            .expect("enqueue datagram");
3399        assert_eq!(usage(&ledger, ResourceClass::BufferedBytes).used, 3);
3400        assert_eq!(usage(&ledger, ResourceClass::Datagrams).used, 1);
3401        assert_eq!(usage(&ledger, ResourceClass::UdpBytes).used, 3);
3402        assert_eq!(usage(&ledger, ResourceClass::UdpDatagrams).used, 1);
3403        assert!(!table.datagram_capacity_available());
3404
3405        let error = table
3406            .send_to_bound_udp_socket(sender.id(), receiver_address.clone(), b"def")
3407            .expect_err("second datagram must fail atomically");
3408        assert_eq!(error.code(), "EAGAIN");
3409        assert_eq!(table.snapshot().datagram_queue_len, 1);
3410        assert_eq!(usage(&ledger, ResourceClass::BufferedBytes).used, 3);
3411        assert_eq!(usage(&ledger, ResourceClass::Datagrams).used, 1);
3412        assert_eq!(usage(&ledger, ResourceClass::UdpBytes).used, 3);
3413        assert_eq!(usage(&ledger, ResourceClass::UdpDatagrams).used, 1);
3414
3415        let received = table
3416            .recv_datagram(receiver.id(), 1)
3417            .expect("receive datagram")
3418            .expect("queued datagram");
3419        assert_eq!(received.payload(), b"a");
3420        assert_eq!(usage(&ledger, ResourceClass::BufferedBytes).used, 0);
3421        assert_eq!(usage(&ledger, ResourceClass::Datagrams).used, 0);
3422        assert_eq!(usage(&ledger, ResourceClass::UdpBytes).used, 0);
3423        assert_eq!(usage(&ledger, ResourceClass::UdpDatagrams).used, 0);
3424
3425        table
3426            .send_to_bound_udp_socket(sender.id(), receiver_address.clone(), b"charged")
3427            .expect("enqueue charged handoff");
3428        let charged = table
3429            .recv_datagram_charged(receiver.id(), 1)
3430            .expect("charged receive")
3431            .expect("charged datagram");
3432        assert_eq!(usage(&ledger, ResourceClass::BufferedBytes).used, 7);
3433        assert_eq!(usage(&ledger, ResourceClass::Datagrams).used, 1);
3434        assert_eq!(usage(&ledger, ResourceClass::UdpBytes).used, 7);
3435        assert_eq!(usage(&ledger, ResourceClass::UdpDatagrams).used, 1);
3436        let error = table
3437            .send_to_bound_udp_socket(sender.id(), receiver_address.clone(), b"blocked")
3438            .expect_err("guest-bound datagram ownership must retain the count permit");
3439        assert_eq!(error.code(), "EAGAIN");
3440        let (_, payload, reservations) = charged.into_parts();
3441        assert_eq!(payload, b"c");
3442        let (bytes, datagram, udp_bytes, udp_datagram) = reservations.expect("charged ownership");
3443        assert_eq!(bytes.amount(), 7);
3444        assert_eq!(datagram.amount(), 1);
3445        assert_eq!(udp_bytes.amount(), 7);
3446        assert_eq!(udp_datagram.amount(), 1);
3447        drop((bytes, datagram, udp_bytes, udp_datagram));
3448        assert_eq!(usage(&ledger, ResourceClass::BufferedBytes).used, 0);
3449        assert_eq!(usage(&ledger, ResourceClass::Datagrams).used, 0);
3450        assert_eq!(usage(&ledger, ResourceClass::UdpBytes).used, 0);
3451        assert_eq!(usage(&ledger, ResourceClass::UdpDatagrams).used, 0);
3452
3453        table
3454            .send_to_bound_udp_socket(sender.id(), receiver_address, b"queued")
3455            .expect("enqueue replacement datagram");
3456        table.remove(receiver.id()).expect("close queued receiver");
3457        assert_eq!(usage(&ledger, ResourceClass::BufferedBytes).used, 0);
3458        assert_eq!(usage(&ledger, ResourceClass::Datagrams).used, 0);
3459        assert_eq!(usage(&ledger, ResourceClass::UdpBytes).used, 0);
3460        assert_eq!(usage(&ledger, ResourceClass::UdpDatagrams).used, 0);
3461        table.remove(sender.id()).expect("close sender");
3462        assert!(ledger.is_zero());
3463    }
3464
3465    #[test]
3466    fn connected_udp_filters_non_peer_datagrams_and_disconnect_restores_delivery() {
3467        let table = SocketTable::new();
3468        let allowed_sender = table
3469            .allocate(1, SocketSpec::udp())
3470            .expect("allocate allowed sender");
3471        let other_sender = table
3472            .allocate(2, SocketSpec::udp())
3473            .expect("allocate other sender");
3474        let receiver = table
3475            .allocate(3, SocketSpec::udp())
3476            .expect("allocate receiver");
3477        let allowed_address = InetSocketAddress::new("127.0.0.1", 41101);
3478        let other_address = InetSocketAddress::new("127.0.0.1", 41102);
3479        let receiver_address = InetSocketAddress::new("127.0.0.1", 41103);
3480        table
3481            .bind_inet(allowed_sender.id(), allowed_address.clone())
3482            .expect("bind allowed sender");
3483        table
3484            .bind_inet(other_sender.id(), other_address)
3485            .expect("bind other sender");
3486        table
3487            .bind_inet(receiver.id(), receiver_address.clone())
3488            .expect("bind receiver");
3489
3490        table
3491            .connect_bound_udp_socket(receiver.id(), allowed_address.clone())
3492            .expect("connect receiver to allowed peer");
3493        assert_eq!(
3494            table
3495                .get(receiver.id())
3496                .expect("connected receiver")
3497                .peer_address(),
3498            Some(&allowed_address)
3499        );
3500
3501        assert_eq!(
3502            table
3503                .send_to_bound_udp_socket(other_sender.id(), receiver_address.clone(), b"drop")
3504                .expect("non-peer send still succeeds"),
3505            4
3506        );
3507        assert_eq!(
3508            table
3509                .recv_datagram(receiver.id(), usize::MAX)
3510                .expect_err("connected receiver must filter another peer")
3511                .code(),
3512            "EAGAIN"
3513        );
3514
3515        table
3516            .send_to_bound_udp_socket(allowed_sender.id(), receiver_address.clone(), b"allowed")
3517            .expect("send from connected peer");
3518        let received = table
3519            .recv_datagram(receiver.id(), usize::MAX)
3520            .expect("receive from connected peer")
3521            .expect("connected datagram");
3522        assert_eq!(received.payload(), b"allowed");
3523        assert_eq!(received.source_address(), Some(&allowed_address));
3524
3525        table
3526            .send_connected_udp_socket(receiver.id(), b"reply")
3527            .expect("send through connected peer state");
3528        let reply = table
3529            .recv_datagram(allowed_sender.id(), usize::MAX)
3530            .expect("receive connected reply")
3531            .expect("connected reply datagram");
3532        assert_eq!(reply.payload(), b"reply");
3533        assert_eq!(reply.source_address(), Some(&receiver_address));
3534
3535        table
3536            .disconnect_bound_udp_socket(receiver.id())
3537            .expect("disconnect receiver");
3538        assert!(table
3539            .get(receiver.id())
3540            .expect("disconnected receiver")
3541            .peer_address()
3542            .is_none());
3543        table
3544            .send_to_bound_udp_socket(other_sender.id(), receiver_address, b"accepted")
3545            .expect("send after disconnect");
3546        assert_eq!(
3547            table
3548                .recv_datagram(receiver.id(), usize::MAX)
3549                .expect("receive after disconnect")
3550                .expect("disconnected datagram")
3551                .payload(),
3552            b"accepted"
3553        );
3554    }
3555
3556    #[cfg(not(target_arch = "wasm32"))]
3557    #[test]
3558    fn incomplete_datagram_ownership_does_not_discard_payload() {
3559        let table = SocketTable::new();
3560        let ledger = test_resource_ledger(8, 1);
3561        table
3562            .set_resource_ledger(Arc::clone(&ledger))
3563            .expect("install resource ledger");
3564        let sender = table
3565            .allocate(1, SocketSpec::udp())
3566            .expect("allocate sender");
3567        let receiver = table
3568            .allocate(2, SocketSpec::udp())
3569            .expect("allocate receiver");
3570        let sender_address = InetSocketAddress::new("127.0.0.1", 42001);
3571        let receiver_address = InetSocketAddress::new("127.0.0.1", 42002);
3572        table
3573            .bind_inet(sender.id(), sender_address)
3574            .expect("bind sender");
3575        table
3576            .bind_inet(receiver.id(), receiver_address.clone())
3577            .expect("bind receiver");
3578        table
3579            .send_to_bound_udp_socket(sender.id(), receiver_address, b"abc")
3580            .expect("queue datagram");
3581
3582        // Simulate an internal ownership defect. Receiving must report it
3583        // without losing the guest-visible payload.
3584        let missing = lock_or_recover(&table.inner.state)
3585            .retained_resources
3586            .get_mut(&receiver.id())
3587            .expect("retained ownership")
3588            .udp_datagrams
3589            .pop_front()
3590            .expect("UDP datagram reservation");
3591        drop(missing);
3592        let error = table
3593            .recv_datagram(receiver.id(), usize::MAX)
3594            .expect_err("incomplete ownership must fail");
3595        assert_eq!(error.code(), "EIO");
3596        assert_eq!(table.snapshot().datagram_queue_len, 1);
3597        assert_eq!(table.snapshot().buffered_bytes, 3);
3598    }
3599
3600    #[cfg(not(target_arch = "wasm32"))]
3601    #[test]
3602    fn retained_queue_admission_charges_the_process_parent_atomically() {
3603        let process = Arc::new(ResourceLedger::root(
3604            "process",
3605            [(
3606                ResourceClass::BufferedBytes,
3607                ResourceLimit::new(3, "runtime.resources.maxBufferedBytes"),
3608            )],
3609        ));
3610        let vm = Arc::new(ResourceLedger::child(
3611            "vm",
3612            [(
3613                ResourceClass::BufferedBytes,
3614                ResourceLimit::new(5, "limits.resources.maxSocketBufferedBytes"),
3615            )],
3616            Arc::clone(&process),
3617        ));
3618        let table = SocketTable::new();
3619        table
3620            .set_resource_ledger(Arc::clone(&vm))
3621            .expect("install child ledger");
3622        let writer = table
3623            .allocate(1, SocketSpec::tcp())
3624            .expect("allocate writer");
3625        let reader = table
3626            .allocate(2, SocketSpec::tcp())
3627            .expect("allocate reader");
3628        table
3629            .connect_pair(writer.id(), reader.id())
3630            .expect("connect stream pair");
3631
3632        let error = table
3633            .write(writer.id(), b"1234")
3634            .expect_err("process aggregate must reject before queue mutation");
3635        assert_eq!(error.code(), "EAGAIN");
3636        assert_eq!(table.snapshot().buffered_bytes, 0);
3637        assert_eq!(usage(&process, ResourceClass::BufferedBytes).used, 0);
3638        assert_eq!(usage(&vm, ResourceClass::BufferedBytes).used, 0);
3639
3640        table
3641            .write(writer.id(), b"123")
3642            .expect("write within aggregate limit");
3643        assert_eq!(usage(&process, ResourceClass::BufferedBytes).used, 3);
3644        assert_eq!(usage(&vm, ResourceClass::BufferedBytes).used, 3);
3645        table.read(reader.id(), 3).expect("drain retained bytes");
3646        assert_eq!(usage(&process, ResourceClass::BufferedBytes).used, 0);
3647        assert_eq!(usage(&vm, ResourceClass::BufferedBytes).used, 0);
3648    }
3649}