Skip to main content

arcbox_virtio_vsock/
device.rs

1//! `VirtioVsock` device — TX/RX queue handling, custom-VMM hot path, `VirtioDevice` impl.
2
3use std::collections::HashMap;
4use std::sync::{Arc, Mutex, RwLock};
5
6use arcbox_virtio_core::error::{Result, VirtioError};
7use arcbox_virtio_core::queue::VirtQueue;
8use arcbox_virtio_core::{DeviceCtx, QueueConfig, VirtioDevice, VirtioDeviceId, virtio_bindings};
9
10use crate::addr::{HOST_CID, RESERVED_CID, VsockAddr, VsockHostConnections};
11use crate::backend::{LoopbackBackend, VsockBackend};
12use crate::connection::{ConnectionState, VsockConnection};
13use crate::manager::VsockConnectionManager;
14use crate::protocol::{VsockHeader, VsockOp};
15
16/// Forwards `buf` to `fd` with partial-write + `EAGAIN` handling.
17///
18/// A single `libc::write` on a non-blocking socketpair can return short
19/// (SO_SNDBUF full) or `EAGAIN` (buffer completely full). The previous
20/// implementation dropped the tail in both cases, silently truncating
21/// responses larger than the socket buffer (macOS default ~8 KiB). This
22/// helper loops until all bytes are written, the peer closes the fd, or
23/// the deadline expires. Returns the total number of bytes successfully
24/// delivered.
25///
26/// Runs on the vCPU thread via the BSP's TX handler, so we cap the total
27/// poll wait at a few milliseconds per call — enough to let the client
28/// drain typical RPC responses, short enough that a slow consumer does
29/// not stall the guest indefinitely. If the cap is hit we return a short
30/// count; `advance_fwd_cnt` then reflects only what was delivered, and
31/// the guest's credit accounting backs off naturally. See ABX-365.
32fn write_all_with_backoff(fd: i32, buf: &[u8]) -> usize {
33    const MAX_POLL_RETRIES: u32 = 16;
34    const POLL_TIMEOUT_MS: libc::c_int = 2; // total worst case: 32 ms
35
36    let mut offset = 0usize;
37    let mut eagain_retries = 0u32;
38
39    while offset < buf.len() {
40        // SAFETY: fd is a valid connected socket from the manager;
41        // `buf[offset..]` is a live slice for the remaining bytes.
42        let ret = unsafe {
43            libc::write(
44                fd,
45                buf[offset..].as_ptr().cast::<libc::c_void>(),
46                buf.len() - offset,
47            )
48        };
49
50        use std::cmp::Ordering;
51        match ret.cmp(&0) {
52            Ordering::Greater => {
53                offset += ret as usize;
54                eagain_retries = 0;
55            }
56            Ordering::Equal => {
57                // Peer closed. Nothing more we can do.
58                break;
59            }
60            Ordering::Less => {
61                let err = std::io::Error::last_os_error();
62                match err.raw_os_error() {
63                    Some(e) if e == libc::EAGAIN || e == libc::EWOULDBLOCK => {
64                        if eagain_retries >= MAX_POLL_RETRIES {
65                            tracing::warn!(
66                                "Vsock: giving up after {MAX_POLL_RETRIES} EAGAIN retries at offset {offset}/{} on fd {fd}",
67                                buf.len(),
68                            );
69                            break;
70                        }
71                        eagain_retries += 1;
72                        // Wait for POLLOUT so the next write has a chance.
73                        let mut pfd = libc::pollfd {
74                            fd,
75                            events: libc::POLLOUT,
76                            revents: 0,
77                        };
78                        // SAFETY: single pollfd on the stack, count=1.
79                        let _ = unsafe { libc::poll(&mut pfd, 1, POLL_TIMEOUT_MS) };
80                    }
81                    Some(libc::EINTR) => {}
82                    _ => {
83                        tracing::warn!("Vsock: write to fd {fd} failed at offset {offset}: {err}");
84                        break;
85                    }
86                }
87            }
88        }
89    }
90
91    offset
92}
93
94/// Vsock device configuration.
95#[derive(Debug, Clone)]
96pub struct VsockConfig {
97    /// Guest CID (Context Identifier).
98    pub guest_cid: u64,
99}
100
101impl Default for VsockConfig {
102    fn default() -> Self {
103        Self {
104            guest_cid: 3, // First available guest CID
105        }
106    }
107}
108
109/// `VirtIO` vsock device.
110///
111/// Enables socket communication between host (CID 2) and guest using
112/// virtio transport.
113pub struct VirtioVsock {
114    config: VsockConfig,
115    features: u64,
116    acked_features: u64,
117    /// Backend for host-side socket handling.
118    backend: Option<Arc<Mutex<dyn VsockBackend>>>,
119    /// Active connections.
120    connections: RwLock<HashMap<(u32, u32), VsockConnection>>,
121    /// Queue 0: RX (host -> guest).
122    rx_queue: Option<VirtQueue>,
123    /// Queue 1: TX (guest -> host).
124    tx_queue: Option<VirtQueue>,
125    /// Queue 2: Event (control events).
126    event_queue: Option<VirtQueue>,
127    /// Host-side connection fds keyed by guest port.
128    /// Used by the guest-memory `process_queue` path to forward data
129    /// between host sockets and guest vsock queues.
130    host_connections: HashMap<u32, std::os::unix::io::RawFd>,
131    /// Last processed avail index for TX queue (guest-memory path).
132    last_avail_idx_tx: usize,
133    /// Last processed avail index for RX queue (guest-memory path).
134    last_avail_idx_rx: usize,
135    /// Guest memory + IRQ context. Bound at registration time on the
136    /// HV backend; remains `None` on the VZ backend (which does not use
137    /// the custom-VMM `poll_rx_injection` path).
138    ctx: Option<DeviceCtx>,
139    /// Trait-object view of the host-side connection manager. Used by
140    /// `process_queue` (TX path) so tests can supply a mock implementing
141    /// `VsockHostConnections` without dragging in the concrete manager.
142    conns: Option<Arc<Mutex<dyn VsockHostConnections>>>,
143    /// Concrete view of the host-side connection manager. Required by
144    /// `poll_rx_injection`, which calls non-trait methods (`backend_rxq`,
145    /// `connections_with_pending_rx`, `get`/`get_mut`/`remove`,
146    /// `enqueue_rw`/`enqueue_reset`, `peek`/`dequeue`/`pending` on
147    /// `RxOps`, etc.). Always set alongside `conns` in production via
148    /// `bind_connection_manager`; left `None` in unit-test contexts.
149    conn_mgr: Option<Arc<Mutex<VsockConnectionManager>>>,
150}
151
152impl VirtioVsock {
153    /// Feature: Stream socket.
154    pub const FEATURE_STREAM: u64 = 1 << 0;
155    /// Feature: Seqpacket socket.
156    pub const FEATURE_SEQPACKET: u64 = 1 << 1;
157    /// VirtIO version 1 compliance (required for modern MMIO transport).
158    pub const FEATURE_VERSION_1: u64 = 1 << virtio_bindings::virtio_config::VIRTIO_F_VERSION_1;
159
160    /// Well-known CID for host.
161    pub const HOST_CID: u64 = HOST_CID;
162    /// Reserved CID.
163    pub const RESERVED_CID: u64 = RESERVED_CID;
164
165    /// Creates a new vsock device.
166    #[must_use]
167    pub fn new(config: VsockConfig) -> Self {
168        Self {
169            config,
170            features: Self::FEATURE_STREAM
171                | Self::FEATURE_VERSION_1
172                | arcbox_virtio_core::queue::VIRTIO_F_EVENT_IDX,
173            acked_features: 0,
174            backend: None,
175            connections: RwLock::new(HashMap::new()),
176            rx_queue: None,
177            tx_queue: None,
178            event_queue: None,
179            host_connections: HashMap::new(),
180            last_avail_idx_tx: 0,
181            last_avail_idx_rx: 0,
182            ctx: None,
183            conns: None,
184            conn_mgr: None,
185        }
186    }
187
188    /// Creates a vsock device with a backend.
189    #[must_use]
190    pub fn with_backend<B: VsockBackend + 'static>(config: VsockConfig, backend: B) -> Self {
191        Self {
192            config,
193            features: Self::FEATURE_STREAM
194                | Self::FEATURE_VERSION_1
195                | arcbox_virtio_core::queue::VIRTIO_F_EVENT_IDX,
196            acked_features: 0,
197            backend: Some(Arc::new(Mutex::new(backend))),
198            connections: RwLock::new(HashMap::new()),
199            rx_queue: None,
200            tx_queue: None,
201            event_queue: None,
202            host_connections: HashMap::new(),
203            last_avail_idx_tx: 0,
204            last_avail_idx_rx: 0,
205            ctx: None,
206            conns: None,
207            conn_mgr: None,
208        }
209    }
210
211    /// Sets the backend.
212    pub fn set_backend<B: VsockBackend + 'static>(&mut self, backend: B) {
213        self.backend = Some(Arc::new(Mutex::new(backend)));
214    }
215
216    /// Binds the device's `DeviceCtx` (guest memory + IRQ trigger).
217    /// Required by the custom-VMM `poll_rx_injection` hot path.
218    pub fn bind_ctx(&mut self, ctx: DeviceCtx) {
219        self.ctx = Some(ctx);
220    }
221
222    /// Binds a trait-object view of the host-side connection manager.
223    /// Required by `process_queue(1, ...)` (TX path). Tests set this
224    /// directly with a mock; production callers use
225    /// `bind_connection_manager` which also sets the concrete view.
226    pub fn bind_connections(&mut self, conns: Arc<Mutex<dyn VsockHostConnections>>) {
227        self.conns = Some(conns);
228    }
229
230    /// Binds the concrete `VsockConnectionManager`. Required by
231    /// `poll_rx_injection`, which uses non-trait methods. Stores both
232    /// the trait-object view (for `process_queue`) and the concrete
233    /// view (for `poll_rx_injection`) — same `Arc`, two lenses.
234    pub fn bind_connection_manager(&mut self, mgr: Arc<Mutex<VsockConnectionManager>>) {
235        self.conns = Some(mgr.clone());
236        self.conn_mgr = Some(mgr);
237    }
238
239    /// Returns a clone of the trait-object connection manager Arc.
240    pub fn connections(&self) -> Option<Arc<Mutex<dyn VsockHostConnections>>> {
241        self.conns.clone()
242    }
243
244    /// Returns the guest CID.
245    #[must_use]
246    pub const fn guest_cid(&self) -> u64 {
247        self.config.guest_cid
248    }
249
250    /// Handles a connection request from guest.
251    pub fn handle_connect(&self, src_port: u32, dst_port: u32) -> Result<()> {
252        let local = VsockAddr::new(self.config.guest_cid, src_port);
253        let remote = VsockAddr::new(Self::HOST_CID, dst_port);
254
255        let mut conn = VsockConnection::new(local, remote);
256        conn.state = ConnectionState::Connecting;
257
258        if let Some(ref backend) = self.backend {
259            backend.lock().unwrap().on_connect(local)?;
260            conn.state = ConnectionState::Connected;
261        }
262
263        self.connections
264            .write()
265            .unwrap()
266            .insert((src_port, dst_port), conn);
267        tracing::debug!(
268            "Vsock connect: {}:{} -> {}:{}",
269            self.config.guest_cid,
270            src_port,
271            Self::HOST_CID,
272            dst_port
273        );
274
275        Ok(())
276    }
277
278    /// Handles data from guest.
279    pub fn handle_send(&self, src_port: u32, dst_port: u32, data: &[u8]) -> Result<usize> {
280        let local = VsockAddr::new(self.config.guest_cid, src_port);
281
282        if let Some(ref backend) = self.backend {
283            backend.lock().unwrap().on_send(local, data)
284        } else {
285            let mut conns = self.connections.write().unwrap();
286            if let Some(conn) = conns.get_mut(&(src_port, dst_port)) {
287                conn.enqueue_tx(data);
288                Ok(data.len())
289            } else {
290                Err(VirtioError::InvalidOperation("Connection not found".into()))
291            }
292        }
293    }
294
295    /// Handles receive request from guest.
296    pub fn handle_recv(&self, src_port: u32, dst_port: u32, buf: &mut [u8]) -> Result<usize> {
297        let local = VsockAddr::new(self.config.guest_cid, src_port);
298
299        if let Some(ref backend) = self.backend {
300            backend.lock().unwrap().on_recv(local, buf)
301        } else {
302            let mut conns = self.connections.write().unwrap();
303            if let Some(conn) = conns.get_mut(&(src_port, dst_port)) {
304                let data = conn.dequeue_rx(buf.len());
305                buf[..data.len()].copy_from_slice(&data);
306                Ok(data.len())
307            } else {
308                Err(VirtioError::InvalidOperation("Connection not found".into()))
309            }
310        }
311    }
312
313    /// Handles connection close from guest.
314    pub fn handle_close(&self, src_port: u32, dst_port: u32) -> Result<()> {
315        let local = VsockAddr::new(self.config.guest_cid, src_port);
316
317        if let Some(ref backend) = self.backend {
318            backend.lock().unwrap().on_close(local)?;
319        }
320
321        self.connections
322            .write()
323            .unwrap()
324            .remove(&(src_port, dst_port));
325        tracing::debug!("Vsock close: {}:{}", self.config.guest_cid, src_port);
326
327        Ok(())
328    }
329
330    /// Returns the number of active connections.
331    #[must_use]
332    pub fn connection_count(&self) -> usize {
333        self.connections.read().unwrap().len()
334    }
335
336    /// Returns a mutable reference to the TX queue.
337    pub fn tx_queue_mut(&mut self) -> Option<&mut VirtQueue> {
338        self.tx_queue.as_mut()
339    }
340
341    /// Returns a mutable reference to the RX queue.
342    pub fn rx_queue_mut(&mut self) -> Option<&mut VirtQueue> {
343        self.rx_queue.as_mut()
344    }
345
346    /// Handles a TX packet from the guest, forwarding data to host fds.
347    fn handle_tx_packet_with_fds(
348        &self,
349        hdr: &VsockHeader,
350        payload: &[u8],
351        connections: Option<&mut dyn VsockHostConnections>,
352    ) {
353        // Copy packed fields to locals to avoid unaligned reference UB.
354        let src_cid = { hdr.src_cid };
355        let dst_cid = { hdr.dst_cid };
356        let src_port = { hdr.src_port };
357        let dst_port = { hdr.dst_port };
358        let buf_alloc = { hdr.buf_alloc };
359        let fwd_cnt = { hdr.fwd_cnt };
360        let flags = { hdr.flags };
361
362        match hdr.operation() {
363            Some(VsockOp::Request) => {
364                tracing::debug!(
365                    "Vsock TX: OP_REQUEST src={}:{} dst={}:{}",
366                    src_cid,
367                    src_port,
368                    dst_cid,
369                    dst_port,
370                );
371            }
372            Some(VsockOp::Response) => {
373                // Guest accepted a host-initiated connection.
374                // src_port = guest port, dst_port = host ephemeral port.
375                tracing::info!(
376                    "Vsock TX: OP_RESPONSE — connection established (guest_port={}, host_port={})",
377                    src_port,
378                    dst_port,
379                );
380                if let Some(conns) = connections {
381                    conns.update_peer_credit(src_port, dst_port, buf_alloc, fwd_cnt);
382                    conns.mark_connected(src_port, dst_port);
383                }
384            }
385            Some(VsockOp::Rw) => {
386                // Guest sends data. src_port = guest port, dst_port = host port.
387                if let Some(conns) = connections {
388                    conns.update_peer_credit(src_port, dst_port, buf_alloc, fwd_cnt);
389                    if let Some(fd) = conns.fd_for(src_port, dst_port) {
390                        if !payload.is_empty() {
391                            let total = payload.len();
392                            let forwarded = write_all_with_backoff(fd, payload);
393                            if forwarded > 0 {
394                                tracing::debug!(
395                                    "Vsock TX: OP_RW guest_port={} host_port={} -> fd {fd}, {}/{} bytes",
396                                    src_port,
397                                    dst_port,
398                                    forwarded,
399                                    total,
400                                );
401                                // Advance fwd_cnt by the byte count we actually
402                                // delivered to the host socket. If the write
403                                // loop gave up due to sustained EAGAIN or a
404                                // hard error, `forwarded` will be < total and
405                                // guest credit accounting will reflect that
406                                // (fewer acks → guest backs off).
407                                #[allow(clippy::cast_possible_truncation)]
408                                {
409                                    conns.advance_fwd_cnt(src_port, dst_port, forwarded as u32);
410                                }
411                            }
412                            if forwarded < total {
413                                tracing::warn!(
414                                    "Vsock TX: truncated write guest_port={} host_port={}: only {}/{} bytes forwarded (ABX-365)",
415                                    src_port,
416                                    dst_port,
417                                    forwarded,
418                                    total,
419                                );
420                            }
421                        }
422                    } else {
423                        tracing::warn!(
424                            "Vsock TX: OP_RW no host fd for guest_port={} host_port={}",
425                            src_port,
426                            dst_port,
427                        );
428                    }
429                }
430            }
431            Some(VsockOp::Shutdown) => {
432                tracing::debug!(
433                    "Vsock TX: OP_SHUTDOWN guest_port={} host_port={} flags=0x{:x}",
434                    src_port,
435                    dst_port,
436                    flags,
437                );
438                if let Some(conns) = connections {
439                    // Dispatch on the shutdown flags — a half-close (only
440                    // F_RECEIVE or only F_SEND) should preserve the fd so
441                    // either side can still drain in-flight data.
442                    conns.handle_shutdown(src_port, dst_port, flags);
443                }
444            }
445            Some(VsockOp::Rst) => {
446                tracing::debug!(
447                    "Vsock TX: OP_RST guest_port={} host_port={}",
448                    src_port,
449                    dst_port,
450                );
451                if let Some(conns) = connections {
452                    conns.remove_connection(src_port, dst_port);
453                }
454            }
455            Some(VsockOp::CreditUpdate) => {
456                tracing::trace!(
457                    "Vsock TX: OP_CREDIT_UPDATE guest_port={} host_port={} buf_alloc={} fwd_cnt={}",
458                    src_port,
459                    dst_port,
460                    buf_alloc,
461                    fwd_cnt,
462                );
463                if let Some(conns) = connections {
464                    conns.update_peer_credit(src_port, dst_port, buf_alloc, fwd_cnt);
465                }
466            }
467            Some(VsockOp::CreditRequest) => {
468                tracing::trace!(
469                    "Vsock TX: OP_CREDIT_REQUEST guest_port={} host_port={}",
470                    src_port,
471                    dst_port,
472                );
473                if let Some(conns) = connections {
474                    conns.update_peer_credit(src_port, dst_port, buf_alloc, fwd_cnt);
475                    conns.enqueue_credit_update(src_port, dst_port);
476                }
477            }
478            _ => {}
479        }
480    }
481
482    /// Registers a host-side fd for a guest vsock port.
483    /// When the guest sends data to this port, it will be written to the fd.
484    /// When the fd has data, it will be injected into the guest RX queue.
485    pub fn add_host_connection(&mut self, guest_port: u32, fd: std::os::unix::io::RawFd) {
486        tracing::info!("Vsock: host connection for guest port {guest_port} -> fd {fd}");
487        self.host_connections.insert(guest_port, fd);
488    }
489
490    /// Process pending TX queue packets from guest.
491    ///
492    /// Pops available descriptors from the TX virtqueue, parses vsock headers,
493    /// and dispatches each packet based on its operation code. Returns a list
494    /// of completed descriptor heads and their written lengths, suitable for
495    /// `push_used_batch()`.
496    ///
497    /// # Errors
498    ///
499    /// Returns an error if the TX queue is not ready or packet processing fails.
500    pub fn process_tx_queue(&mut self, memory: &mut [u8]) -> Result<Vec<(u16, u32)>> {
501        // Phase 1: Collect raw descriptor data from the TX queue.
502        let mut raw_packets: Vec<(u16, Vec<u8>)> = Vec::new();
503
504        {
505            let queue = self
506                .tx_queue
507                .as_mut()
508                .ok_or_else(|| VirtioError::NotReady("TX queue not ready".into()))?;
509
510            while let Some((head_idx, chain)) = queue.pop_avail() {
511                let mut data = Vec::new();
512
513                for desc in chain {
514                    if !desc.is_write_only() {
515                        // Read-only buffers contain the guest-produced packet.
516                        let start = desc.addr as usize;
517                        let end = start + desc.len as usize;
518                        if end <= memory.len() {
519                            data.extend_from_slice(&memory[start..end]);
520                        }
521                    }
522                }
523
524                raw_packets.push((head_idx, data));
525            }
526        }
527
528        // Phase 2: Parse and dispatch each packet.
529        let mut completions = Vec::new();
530        // Collect RX packets to inject after releasing the connections lock.
531        let mut rx_inject: Vec<(VsockHeader, Vec<u8>)> = Vec::new();
532
533        for (head_idx, data) in &raw_packets {
534            if data.len() < VsockHeader::SIZE {
535                tracing::warn!(
536                    "Vsock TX: descriptor {} too short ({} bytes), skipping",
537                    head_idx,
538                    data.len()
539                );
540                completions.push((*head_idx, 0u32));
541                continue;
542            }
543
544            let header = match VsockHeader::from_bytes(&data[..VsockHeader::SIZE]) {
545                Some(h) => h,
546                None => {
547                    tracing::warn!(
548                        "Vsock TX: failed to parse header for descriptor {}",
549                        head_idx
550                    );
551                    completions.push((*head_idx, 0u32));
552                    continue;
553                }
554            };
555
556            let payload_len = { header.len } as usize;
557            let payload = if payload_len > 0 && data.len() > VsockHeader::SIZE {
558                let avail = data.len() - VsockHeader::SIZE;
559                &data[VsockHeader::SIZE..VsockHeader::SIZE + payload_len.min(avail)]
560            } else {
561                &[] as &[u8]
562            };
563
564            let src_port = { header.src_port };
565            let dst_port = { header.dst_port };
566
567            match header.operation() {
568                Some(VsockOp::Request) => {
569                    tracing::debug!(
570                        "Vsock TX: OP_REQUEST from port {} to port {}",
571                        src_port,
572                        dst_port
573                    );
574                    match self.handle_connect(src_port, dst_port) {
575                        Ok(()) => {
576                            // Build a RESPONSE header to inject into the RX queue.
577                            let resp = VsockHeader::new(
578                                VsockAddr::new(Self::HOST_CID, dst_port),
579                                VsockAddr::new(self.config.guest_cid, src_port),
580                                VsockOp::Response,
581                            );
582                            rx_inject.push((resp, Vec::new()));
583                        }
584                        Err(e) => {
585                            tracing::warn!("Vsock TX: connect failed: {}", e);
586                            // Send RST back to the guest.
587                            let rst = VsockHeader::new(
588                                VsockAddr::new(Self::HOST_CID, dst_port),
589                                VsockAddr::new(self.config.guest_cid, src_port),
590                                VsockOp::Rst,
591                            );
592                            rx_inject.push((rst, Vec::new()));
593                        }
594                    }
595                }
596                Some(VsockOp::Response) => {
597                    // Guest acknowledging a host-initiated connection.
598                    tracing::debug!(
599                        "Vsock TX: OP_RESPONSE from port {} to port {}",
600                        src_port,
601                        dst_port
602                    );
603                    let mut conns = self.connections.write().unwrap();
604                    if let Some(conn) = conns.get_mut(&(src_port, dst_port)) {
605                        conn.state = ConnectionState::Connected;
606                    }
607                }
608                Some(VsockOp::Rw) => {
609                    tracing::trace!(
610                        "Vsock TX: OP_RW {} bytes from port {} to port {}",
611                        payload.len(),
612                        src_port,
613                        dst_port
614                    );
615                    if let Err(e) = self.handle_send(src_port, dst_port, payload) {
616                        tracing::warn!("Vsock TX: send failed: {}", e);
617                    }
618                }
619                Some(VsockOp::Shutdown) => {
620                    tracing::debug!(
621                        "Vsock TX: OP_SHUTDOWN from port {} to port {}",
622                        src_port,
623                        dst_port
624                    );
625                    if let Err(e) = self.handle_close(src_port, dst_port) {
626                        tracing::warn!("Vsock TX: close failed: {}", e);
627                    }
628                    // Confirm with RST.
629                    let rst = VsockHeader::new(
630                        VsockAddr::new(Self::HOST_CID, dst_port),
631                        VsockAddr::new(self.config.guest_cid, src_port),
632                        VsockOp::Rst,
633                    );
634                    rx_inject.push((rst, Vec::new()));
635                }
636                Some(VsockOp::Rst) => {
637                    tracing::debug!(
638                        "Vsock TX: OP_RST from port {} to port {}",
639                        src_port,
640                        dst_port
641                    );
642                    let _ = self.handle_close(src_port, dst_port);
643                }
644                Some(VsockOp::CreditUpdate) => {
645                    let buf_alloc = { header.buf_alloc };
646                    let fwd_cnt = { header.fwd_cnt };
647                    tracing::trace!(
648                        "Vsock TX: OP_CREDIT_UPDATE port {} buf_alloc={} fwd_cnt={}",
649                        src_port,
650                        buf_alloc,
651                        fwd_cnt
652                    );
653                    let mut conns = self.connections.write().unwrap();
654                    if let Some(conn) = conns.get_mut(&(src_port, dst_port)) {
655                        conn.update_peer_credit(buf_alloc, fwd_cnt);
656                    }
657                }
658                Some(VsockOp::CreditRequest) => {
659                    tracing::trace!(
660                        "Vsock TX: OP_CREDIT_REQUEST from port {} to port {}",
661                        src_port,
662                        dst_port
663                    );
664                    // Respond with our credit state.
665                    let conns = self.connections.read().unwrap();
666                    if let Some(conn) = conns.get(&(src_port, dst_port)) {
667                        let mut update = VsockHeader::new(
668                            VsockAddr::new(Self::HOST_CID, dst_port),
669                            VsockAddr::new(self.config.guest_cid, src_port),
670                            VsockOp::CreditUpdate,
671                        );
672                        update.buf_alloc = conn.buf_alloc;
673                        update.fwd_cnt = conn.fwd_cnt;
674                        rx_inject.push((update, Vec::new()));
675                    }
676                }
677                Some(VsockOp::Invalid) | None => {
678                    let raw_op = { header.op };
679                    tracing::warn!(
680                        "Vsock TX: unknown/invalid op {} from port {}",
681                        raw_op,
682                        src_port
683                    );
684                }
685            }
686
687            completions.push((*head_idx, data.len() as u32));
688        }
689
690        // Phase 3: Inject any pending RX response packets.
691        for (hdr, payload) in rx_inject {
692            if let Err(e) = self.inject_rx_packet(&hdr, &payload, memory) {
693                tracing::warn!("Vsock: failed to inject RX packet: {}", e);
694            }
695        }
696
697        Ok(completions)
698    }
699
700    /// Process a specific virtqueue by index.
701    ///
702    /// Queue indices follow the VirtIO vsock specification:
703    /// - 0: RX (host -> guest) — processed externally via `inject_rx_packet`
704    /// - 1: TX (guest -> host) — dispatched here
705    /// - 2: Event queue       — not yet implemented
706    ///
707    /// # Errors
708    ///
709    /// Returns an error if processing fails.
710    pub fn process_queue(&mut self, queue_idx: u16, memory: &mut [u8]) -> Result<Vec<(u16, u32)>> {
711        match queue_idx {
712            1 => self.process_tx_queue(memory),
713            _ => Ok(Vec::new()),
714        }
715    }
716
717    /// Injects a response packet into the guest RX queue.
718    ///
719    /// Pops an available descriptor from the RX queue, writes the vsock header
720    /// and optional payload into guest memory via the descriptor chain, then
721    /// marks it as used. The MMIO/interrupt handler is responsible for
722    /// signalling the guest after this call.
723    ///
724    /// # Errors
725    ///
726    /// Returns an error if the RX queue is not ready or no descriptors are
727    /// available.
728    pub fn inject_rx_packet(
729        &mut self,
730        header: &VsockHeader,
731        data: &[u8],
732        memory: &mut [u8],
733    ) -> Result<()> {
734        let queue = self
735            .rx_queue
736            .as_mut()
737            .ok_or_else(|| VirtioError::NotReady("RX queue not ready".into()))?;
738
739        let (head_idx, chain) = queue
740            .pop_avail()
741            .ok_or_else(|| VirtioError::InvalidQueue("No available RX descriptors".into()))?;
742
743        let header_bytes = header.to_bytes();
744        let total_len = header_bytes.len() + data.len();
745        let mut frame = Vec::with_capacity(total_len);
746        frame.extend_from_slice(&header_bytes);
747        frame.extend_from_slice(data);
748
749        let mut written = 0usize;
750        for desc in chain {
751            if !desc.is_write_only() {
752                continue;
753            }
754            let start = desc.addr as usize;
755            let remaining = frame.len().saturating_sub(written);
756            let to_write = remaining.min(desc.len as usize);
757            if to_write == 0 {
758                continue;
759            }
760            let end = start + to_write;
761            if end > memory.len() {
762                return Err(VirtioError::MemoryError(
763                    "RX descriptor points outside guest memory".into(),
764                ));
765            }
766            memory[start..end].copy_from_slice(&frame[written..written + to_write]);
767            written += to_write;
768        }
769
770        queue.push_used(head_idx, written as u32);
771        Ok(())
772    }
773
774    // =====================================================================
775    // Custom-VMM RX-injection hot path
776    // =====================================================================
777    //
778    // `poll_rx_injection` was previously `DeviceManager::poll_vsock_rx`.
779    // It is the device side of the vsock RX loop the BSP vCPU drives
780    // each iteration: peek host fds, drain the backend RX queue into
781    // guest descriptors, and opportunistically process the TX queue.
782    // Requires `bind_ctx` and `bind_connections` to have been called.
783
784    /// Drives one round of vsock RX/TX maintenance:
785    /// 1. Peek every connected host fd; on data → enqueue RW; on EOF →
786    ///    enqueue RST.
787    /// 2. Pop entries from the backend RX queue, build vsock packets
788    ///    (REQUEST/RESPONSE/RW/SHUTDOWN/CREDIT_*), and write them into
789    ///    available guest RX descriptors via `write_to_rx_descriptor`.
790    /// 3. If `tx_qcfg` is supplied, drain the TX virtqueue via
791    ///    `process_queue(1, ...)` so guest→host responses are picked up
792    ///    on the same poll cycle.
793    ///
794    /// Returns `true` when anything was injected (caller fires
795    /// INT_VRING). Returns `false` if the device isn't fully bound or
796    /// nothing was pending.
797    #[allow(clippy::too_many_lines)]
798    pub fn poll_rx_injection(
799        &mut self,
800        rx_qcfg: &QueueConfig,
801        tx_qcfg: Option<&QueueConfig>,
802    ) -> bool {
803        use std::os::fd::AsRawFd;
804
805        use crate::manager::{RxOps, TX_BUFFER_SIZE};
806
807        let Some(ctx) = self.ctx.clone() else {
808            return false;
809        };
810        let Some(conns) = self.conn_mgr.clone() else {
811            return false;
812        };
813        let mem_arc = ctx.mem.clone();
814        let gpa_base_usize = mem_arc.gpa_base();
815        let mem_len = mem_arc.len();
816
817        let mut injected = false;
818
819        // ------------------------------------------------------------------
820        // Phase 1: peek every connected fd → enqueue RW or RST
821        // ------------------------------------------------------------------
822        {
823            let connected_fds = conns
824                .lock()
825                .map(|mgr| mgr.connected_fds())
826                .unwrap_or_default();
827
828            // Log at INFO once per unique count change to avoid spam.
829            static LAST_COUNT: std::sync::atomic::AtomicUsize =
830                std::sync::atomic::AtomicUsize::new(0);
831            let count = connected_fds.len();
832            if count != LAST_COUNT.swap(count, std::sync::atomic::Ordering::Relaxed) {
833                tracing::info!("vsock Phase 1: {} connected fds", count);
834            }
835
836            for (conn_id, fd) in &connected_fds {
837                let mut peek_buf = [0u8; 1];
838                // SAFETY: `*fd` is owned by the connection manager and
839                // stays live for the duration of this peek. `peek_buf` is
840                // a valid mutable slice. MSG_DONTWAIT keeps it non-blocking.
841                let n = unsafe {
842                    libc::recv(
843                        *fd,
844                        peek_buf.as_mut_ptr().cast::<libc::c_void>(),
845                        1,
846                        libc::MSG_PEEK | libc::MSG_DONTWAIT,
847                    )
848                };
849                if n > 0 {
850                    tracing::trace!(
851                        "vsock Phase 1: data on fd {} for {:?} — enqueue RW",
852                        fd,
853                        conn_id,
854                    );
855                    if let Ok(mut mgr) = conns.lock() {
856                        mgr.enqueue_rw(*conn_id);
857                    }
858                } else if n == 0 {
859                    tracing::debug!(
860                        "vsock Phase 1: EOF on fd {} for {:?} — enqueue RST",
861                        fd,
862                        conn_id,
863                    );
864                    if let Ok(mut mgr) = conns.lock() {
865                        mgr.enqueue_reset(*conn_id);
866                    }
867                }
868                // n < 0 with EAGAIN/EWOULDBLOCK = no data, skip.
869            }
870        }
871
872        // ------------------------------------------------------------------
873        // Phase 2: drain backend_rxq → fill RX descriptors
874        // ------------------------------------------------------------------
875        if !rx_qcfg.ready || rx_qcfg.size == 0 {
876            return injected;
877        }
878        let Some(rx_desc) = (rx_qcfg.desc_addr as usize).checked_sub(gpa_base_usize) else {
879            return injected;
880        };
881        let Some(rx_avail) = (rx_qcfg.avail_addr as usize).checked_sub(gpa_base_usize) else {
882            return injected;
883        };
884        let Some(rx_used) = (rx_qcfg.used_addr as usize).checked_sub(gpa_base_usize) else {
885            return injected;
886        };
887        let q_size = rx_qcfg.size as usize;
888
889        // SAFETY: `mem_arc` was constructed from the VM-lifetime guest RAM
890        // mmap. The slice we derive is short-lived (dropped before phase 3
891        // re-derives its own slice) and used only by code that follows the
892        // VirtIO descriptor-ownership discipline.
893        let Some(guest_mem) = (unsafe { mem_arc.slice_mut(gpa_base_usize, mem_len) }) else {
894            return injected;
895        };
896
897        if rx_avail + 4 > guest_mem.len() {
898            return injected;
899        }
900
901        // Process backend_rxq: pop connections, fill RX descriptors. If we
902        // run out of guest descriptors while backend_rxq still has entries,
903        // we set `injected = true` so the caller raises INT_VRING — that
904        // wakes the guest's rx_work, which refills descriptors, and the
905        // next poll cycle drains the stalled entries.
906        let mut rxq_starved = false;
907        loop {
908            let avail_idx =
909                u16::from_le_bytes([guest_mem[rx_avail + 2], guest_mem[rx_avail + 3]]) as usize;
910            let used_idx_off = rx_used + 2;
911            let used_idx =
912                u16::from_le_bytes([guest_mem[used_idx_off], guest_mem[used_idx_off + 1]]) as usize;
913
914            if avail_idx == used_idx {
915                if let Ok(mgr) = conns.lock() {
916                    if !mgr.backend_rxq.is_empty() {
917                        rxq_starved = true;
918                    }
919                }
920                break;
921            }
922
923            let conn_id = {
924                let Ok(mut mgr) = conns.lock() else {
925                    break;
926                };
927                mgr.backend_rxq.pop_front()
928            };
929            let Some(conn_id) = conn_id else {
930                break; // No pending connections.
931            };
932
933            // Build the packet for this connection's highest-priority op.
934            let packet = {
935                let Ok(mut mgr) = conns.lock() else {
936                    break;
937                };
938                let Some(conn) = mgr.get_mut(&conn_id) else {
939                    continue; // Connection removed while queued.
940                };
941
942                if conn.rx_queue.peek() == RxOps::RESET {
943                    conn.rx_queue.dequeue();
944                    let hdr = VsockHeader::new(
945                        VsockAddr::host(conn_id.host_port),
946                        VsockAddr::new(conn.guest_cid, conn_id.guest_port),
947                        VsockOp::Rst,
948                    );
949                    let pkt = hdr.to_bytes().to_vec();
950                    mgr.remove(&conn_id);
951                    pkt
952                } else {
953                    let op = conn.rx_queue.dequeue();
954                    if op == 0 {
955                        continue; // Spurious entry — no pending ops.
956                    }
957
958                    match op {
959                        RxOps::REQUEST => {
960                            let hdr = VsockHeader::new(
961                                VsockAddr::host(conn_id.host_port),
962                                VsockAddr::new(conn.guest_cid, conn_id.guest_port),
963                                VsockOp::Request,
964                            );
965                            tracing::debug!(
966                                "Vsock RX: OP_REQUEST guest_port={} host_port={}",
967                                conn_id.guest_port,
968                                conn_id.host_port,
969                            );
970                            hdr.to_bytes().to_vec()
971                        }
972                        RxOps::RESPONSE => {
973                            conn.connect = true;
974                            let hdr = VsockHeader::new(
975                                VsockAddr::host(conn_id.host_port),
976                                VsockAddr::new(conn.guest_cid, conn_id.guest_port),
977                                VsockOp::Response,
978                            );
979                            tracing::debug!(
980                                "Vsock RX: OP_RESPONSE guest_port={} host_port={}",
981                                conn_id.guest_port,
982                                conn_id.host_port,
983                            );
984                            hdr.to_bytes().to_vec()
985                        }
986                        RxOps::RW => {
987                            if conn.peer_no_recv() {
988                                // Peer half-closed its receive side. Drop the
989                                // RW silently; the fd stays open so the peer's
990                                // own sends still drain via the TX path.
991                                tracing::trace!(
992                                    "Vsock RX: skipping RW for half-closed conn guest_port={} host_port={}",
993                                    conn_id.guest_port,
994                                    conn_id.host_port,
995                                );
996                                continue;
997                            }
998                            if !conn.connect {
999                                let hdr = VsockHeader::new(
1000                                    VsockAddr::host(conn_id.host_port),
1001                                    VsockAddr::new(conn.guest_cid, conn_id.guest_port),
1002                                    VsockOp::Rst,
1003                                );
1004                                mgr.remove(&conn_id);
1005                                hdr.to_bytes().to_vec()
1006                            } else {
1007                                let credit = conn.peer_avail_credit();
1008                                if credit == 0 {
1009                                    let mut hdr = VsockHeader::new(
1010                                        VsockAddr::host(conn_id.host_port),
1011                                        VsockAddr::new(conn.guest_cid, conn_id.guest_port),
1012                                        VsockOp::CreditRequest,
1013                                    );
1014                                    hdr.buf_alloc = TX_BUFFER_SIZE;
1015                                    hdr.fwd_cnt = conn.fwd_cnt.0;
1016                                    // Re-queue the RW so we retry once the peer
1017                                    // refreshes our view; mark the request as
1018                                    // pending so maybe_request_credit below
1019                                    // doesn't also enqueue a duplicate.
1020                                    conn.rx_queue.enqueue(RxOps::RW);
1021                                    conn.note_credit_request_sent();
1022                                    hdr.to_bytes().to_vec()
1023                                } else {
1024                                    let fd = conn.internal_fd.as_raw_fd();
1025                                    let max_read = credit.min(4096);
1026                                    let mut buf = vec![0u8; max_read];
1027                                    // SAFETY: `fd` is borrowed from
1028                                    // `conn.internal_fd`, live for the call.
1029                                    // `buf` is a valid mutable allocation.
1030                                    let n = unsafe {
1031                                        libc::read(
1032                                            fd,
1033                                            buf.as_mut_ptr().cast::<libc::c_void>(),
1034                                            max_read,
1035                                        )
1036                                    };
1037                                    if n <= 0 {
1038                                        if n == 0 {
1039                                            let mut hdr = VsockHeader::new(
1040                                                VsockAddr::host(conn_id.host_port),
1041                                                VsockAddr::new(conn.guest_cid, conn_id.guest_port),
1042                                                VsockOp::Shutdown,
1043                                            );
1044                                            hdr.flags = 3; // RCV | SEND
1045                                            hdr.buf_alloc = TX_BUFFER_SIZE;
1046                                            hdr.fwd_cnt = conn.fwd_cnt.0;
1047                                            hdr.to_bytes().to_vec()
1048                                        } else {
1049                                            continue; // EAGAIN
1050                                        }
1051                                    } else {
1052                                        let data = &buf[..n as usize];
1053                                        let mut hdr = VsockHeader::new(
1054                                            VsockAddr::host(conn_id.host_port),
1055                                            VsockAddr::new(conn.guest_cid, conn_id.guest_port),
1056                                            VsockOp::Rw,
1057                                        );
1058                                        hdr.len = data.len() as u32;
1059                                        hdr.buf_alloc = TX_BUFFER_SIZE;
1060                                        hdr.fwd_cnt = conn.fwd_cnt.0;
1061
1062                                        conn.record_rx(data.len() as u32);
1063                                        // After sending, our view of the
1064                                        // peer's free buffer has shrunk.
1065                                        // Ask for a refresh if we've crossed
1066                                        // the half-window mark.
1067                                        conn.maybe_request_credit();
1068
1069                                        let hdr_bytes = hdr.to_bytes();
1070                                        let mut pkt =
1071                                            Vec::with_capacity(VsockHeader::SIZE + data.len());
1072                                        pkt.extend_from_slice(&hdr_bytes[..VsockHeader::SIZE]);
1073                                        pkt.extend_from_slice(data);
1074
1075                                        tracing::debug!(
1076                                            "Vsock RX: OP_RW {} bytes guest_port={} host_port={} fwd_cnt={}",
1077                                            data.len(),
1078                                            conn_id.guest_port,
1079                                            conn_id.host_port,
1080                                            conn.fwd_cnt.0,
1081                                        );
1082                                        pkt
1083                                    }
1084                                }
1085                            }
1086                        }
1087                        RxOps::CREDIT_UPDATE => {
1088                            let mut hdr = VsockHeader::new(
1089                                VsockAddr::host(conn_id.host_port),
1090                                VsockAddr::new(conn.guest_cid, conn_id.guest_port),
1091                                VsockOp::CreditUpdate,
1092                            );
1093                            hdr.buf_alloc = TX_BUFFER_SIZE;
1094                            hdr.fwd_cnt = conn.fwd_cnt.0;
1095                            conn.mark_credit_sent();
1096                            hdr.to_bytes().to_vec()
1097                        }
1098                        RxOps::CREDIT_REQUEST => {
1099                            // Ask the peer for their current fwd_cnt. The
1100                            // pending flag is already set — it stays set
1101                            // until the peer answers with CREDIT_UPDATE,
1102                            // which clears it via update_peer_credit.
1103                            let mut hdr = VsockHeader::new(
1104                                VsockAddr::host(conn_id.host_port),
1105                                VsockAddr::new(conn.guest_cid, conn_id.guest_port),
1106                                VsockOp::CreditRequest,
1107                            );
1108                            hdr.buf_alloc = TX_BUFFER_SIZE;
1109                            hdr.fwd_cnt = conn.fwd_cnt.0;
1110                            tracing::debug!(
1111                                "Vsock RX: OP_CREDIT_REQUEST guest_port={} host_port={}",
1112                                conn_id.guest_port,
1113                                conn_id.host_port,
1114                            );
1115                            hdr.to_bytes().to_vec()
1116                        }
1117                        _ => continue,
1118                    }
1119                }
1120            };
1121
1122            // Write the packet into an available RX descriptor.
1123            let written = Self::write_to_rx_descriptor(
1124                guest_mem,
1125                rx_desc,
1126                rx_avail,
1127                rx_used,
1128                q_size,
1129                gpa_base_usize,
1130                &packet,
1131            );
1132
1133            if written > 0 {
1134                injected = true;
1135
1136                // Fire injected_notify for REQUEST ops — unblocks any
1137                // daemon-side connect waiting in `connect_vsock_hv`.
1138                if let Ok(mut mgr) = conns.lock() {
1139                    if let Some(conn) = mgr.get_mut(&conn_id) {
1140                        if let Some(tx) = conn.injected_notify.take() {
1141                            let _ = tx.send(());
1142                        }
1143                    }
1144                }
1145            }
1146
1147            // If the connection still has pending ops, re-push it.
1148            if let Ok(mut mgr) = conns.lock() {
1149                if let Some(conn) = mgr.get(&conn_id) {
1150                    if conn.rx_queue.pending() {
1151                        mgr.backend_rxq.push_back(conn_id);
1152                    }
1153                }
1154            }
1155        }
1156
1157        if rxq_starved {
1158            injected = true;
1159        }
1160
1161        // Drop the phase-2 slice borrow before phase 3 re-derives one
1162        // (and before we hand a fresh `&mut [u8]` to `process_queue`,
1163        // which takes `&mut self`). `let _ = ...` for clippy.
1164        let _ = guest_mem;
1165
1166        // ------------------------------------------------------------------
1167        // Phase 3: TX poll — drain TX queue for guest→host responses
1168        // ------------------------------------------------------------------
1169        if let Some(tx_qcfg) = tx_qcfg {
1170            // SAFETY: same as above — short-lived slice, descriptor-scoped
1171            // access discipline holds.
1172            let Some(tx_mem) = (unsafe { mem_arc.slice_mut(gpa_base_usize, mem_len) }) else {
1173                return injected;
1174            };
1175            // Use `VirtioDevice::process_queue` directly on `&mut self`.
1176            // `tx_mem` borrows `mem_arc` (a clone), not `self`, so the
1177            // borrows are disjoint.
1178            match <Self as VirtioDevice>::process_queue(self, 1, tx_mem, tx_qcfg) {
1179                Ok(completions) if !completions.is_empty() => {
1180                    tracing::trace!("Vsock TX poll: {} completions", completions.len());
1181                    injected = true;
1182
1183                    // After TX processing, re-queue any connections whose
1184                    // RX state advanced (e.g. CreditUpdate after OP_RW).
1185                    if let Ok(mut mgr) = conns.lock() {
1186                        let ids: Vec<_> = mgr.connections_with_pending_rx();
1187                        for id in ids {
1188                            mgr.backend_rxq.push_back(id);
1189                        }
1190                    }
1191                }
1192                Err(e) => {
1193                    tracing::warn!("Vsock TX poll error: {e}");
1194                }
1195                _ => {}
1196            }
1197        }
1198
1199        injected
1200    }
1201
1202    /// Writes `packet` into the next available RX descriptor chain.
1203    ///
1204    /// `desc_addr`, `avail_addr`, `used_addr` are slice offsets (already
1205    /// translated from GPA by subtracting `gpa_base`). Returns the number
1206    /// of bytes written, or 0 if no RX descriptor was available or the
1207    /// descriptor chain ran out of writable buffer space.
1208    #[allow(clippy::too_many_arguments)]
1209    fn write_to_rx_descriptor(
1210        guest_mem: &mut [u8],
1211        desc_addr: usize,
1212        avail_addr: usize,
1213        used_addr: usize,
1214        q_size: usize,
1215        gpa_base: usize,
1216        packet: &[u8],
1217    ) -> usize {
1218        let avail_idx =
1219            u16::from_le_bytes([guest_mem[avail_addr + 2], guest_mem[avail_addr + 3]]) as usize;
1220        let used_idx_off = used_addr + 2;
1221        let used_idx =
1222            u16::from_le_bytes([guest_mem[used_idx_off], guest_mem[used_idx_off + 1]]) as usize;
1223
1224        if avail_idx == used_idx {
1225            return 0; // No available descriptors.
1226        }
1227
1228        let ring_off = avail_addr + 4 + 2 * (used_idx % q_size);
1229        if ring_off + 2 > guest_mem.len() {
1230            return 0;
1231        }
1232        let head_idx = u16::from_le_bytes([guest_mem[ring_off], guest_mem[ring_off + 1]]) as usize;
1233
1234        // Walk descriptor chain, writing packet data to WRITE-flagged
1235        // descriptors.
1236        let mut written = 0;
1237        let mut idx = head_idx;
1238        for _ in 0..q_size {
1239            let d_off = desc_addr + idx * 16;
1240            if d_off + 16 > guest_mem.len() {
1241                break;
1242            }
1243            let addr_gpa =
1244                u64::from_le_bytes(guest_mem[d_off..d_off + 8].try_into().unwrap()) as usize;
1245            let len =
1246                u32::from_le_bytes(guest_mem[d_off + 8..d_off + 12].try_into().unwrap()) as usize;
1247            let flags = u16::from_le_bytes(guest_mem[d_off + 12..d_off + 14].try_into().unwrap());
1248            let next = u16::from_le_bytes(guest_mem[d_off + 14..d_off + 16].try_into().unwrap());
1249            let Some(addr) = addr_gpa.checked_sub(gpa_base) else {
1250                continue;
1251            };
1252
1253            if flags & 2 != 0 && addr + len <= guest_mem.len() {
1254                let remaining = packet.len().saturating_sub(written);
1255                let to_write = remaining.min(len);
1256                if to_write > 0 {
1257                    guest_mem[addr..addr + to_write]
1258                        .copy_from_slice(&packet[written..written + to_write]);
1259                    written += to_write;
1260                }
1261            }
1262
1263            if flags & 1 == 0 || written >= packet.len() {
1264                break;
1265            }
1266            idx = next as usize;
1267        }
1268
1269        if written == 0 {
1270            return 0;
1271        }
1272
1273        // Update used ring entry.
1274        let used_entry = used_addr + 4 + (used_idx % q_size) * 8;
1275        if used_entry + 8 <= guest_mem.len() {
1276            guest_mem[used_entry..used_entry + 4].copy_from_slice(&(head_idx as u32).to_le_bytes());
1277            guest_mem[used_entry + 4..used_entry + 8]
1278                .copy_from_slice(&(written as u32).to_le_bytes());
1279            std::sync::atomic::fence(std::sync::atomic::Ordering::Release);
1280            let new_used = (used_idx + 1) as u16;
1281            guest_mem[used_idx_off..used_idx_off + 2].copy_from_slice(&new_used.to_le_bytes());
1282        }
1283
1284        written
1285    }
1286}
1287
1288impl VirtioDevice for VirtioVsock {
1289    fn device_id(&self) -> VirtioDeviceId {
1290        VirtioDeviceId::Vsock
1291    }
1292
1293    fn features(&self) -> u64 {
1294        self.features
1295    }
1296
1297    fn ack_features(&mut self, features: u64) {
1298        self.acked_features = self.features & features;
1299    }
1300
1301    fn read_config(&self, offset: u64, data: &mut [u8]) {
1302        // Configuration space layout:
1303        // offset 0: guest_cid (u64)
1304        let config_data = self.config.guest_cid.to_le_bytes();
1305
1306        let offset = offset as usize;
1307        let len = data.len().min(config_data.len().saturating_sub(offset));
1308        if len > 0 {
1309            data[..len].copy_from_slice(&config_data[offset..offset + len]);
1310        }
1311    }
1312
1313    fn write_config(&mut self, _offset: u64, _data: &[u8]) {
1314        // Vsock config is read-only
1315    }
1316
1317    fn activate(&mut self) -> Result<()> {
1318        // Create virtqueues: RX (0), TX (1), Event (2).
1319        self.rx_queue = Some(VirtQueue::new(256)?);
1320        self.tx_queue = Some(VirtQueue::new(256)?);
1321        self.event_queue = Some(VirtQueue::new(64)?);
1322
1323        // If no backend is set, use loopback for testing.
1324        if self.backend.is_none() {
1325            tracing::info!("Vsock: using loopback backend (no backend configured)");
1326            self.backend = Some(Arc::new(Mutex::new(LoopbackBackend::new())));
1327        }
1328        tracing::info!(
1329            "Vsock device activated, guest CID: {}",
1330            self.config.guest_cid
1331        );
1332        Ok(())
1333    }
1334
1335    fn reset(&mut self) {
1336        self.acked_features = 0;
1337        self.connections.write().unwrap().clear();
1338        self.backend = None;
1339        self.rx_queue = None;
1340        self.tx_queue = None;
1341        self.event_queue = None;
1342        self.last_avail_idx_tx = 0;
1343        self.last_avail_idx_rx = 0;
1344    }
1345
1346    fn process_queue(
1347        &mut self,
1348        queue_idx: u16,
1349        memory: &mut [u8],
1350        queue_config: &QueueConfig,
1351    ) -> Result<Vec<(u16, u32)>> {
1352        // Queue 0 = RX (host→guest), Queue 1 = TX (guest→host), Queue 2 = Event.
1353        // We handle TX here: extract vsock packets, forward data to host fds.
1354        // We also try to inject pending RX data from host fds.
1355        if queue_idx != 1 || !queue_config.ready || queue_config.size == 0 {
1356            return Ok(Vec::new());
1357        }
1358
1359        // Translate GPAs to slice offsets by subtracting gpa_base (checked to
1360        // guard against a malicious guest providing a GPA below the RAM base).
1361        let gpa_base = queue_config.gpa_base as usize;
1362        let desc_addr = (queue_config.desc_addr as usize)
1363            .checked_sub(gpa_base)
1364            .ok_or_else(|| {
1365                tracing::warn!(
1366                    "invalid desc GPA {:#x} below ram base {:#x}",
1367                    queue_config.desc_addr,
1368                    gpa_base
1369                );
1370                VirtioError::InvalidQueue("desc GPA below ram base".into())
1371            })?;
1372        let avail_addr = (queue_config.avail_addr as usize)
1373            .checked_sub(gpa_base)
1374            .ok_or_else(|| {
1375                tracing::warn!(
1376                    "invalid avail GPA {:#x} below ram base {:#x}",
1377                    queue_config.avail_addr,
1378                    gpa_base
1379                );
1380                VirtioError::InvalidQueue("avail GPA below ram base".into())
1381            })?;
1382        let used_addr = (queue_config.used_addr as usize)
1383            .checked_sub(gpa_base)
1384            .ok_or_else(|| {
1385                tracing::warn!(
1386                    "invalid used GPA {:#x} below ram base {:#x}",
1387                    queue_config.used_addr,
1388                    gpa_base
1389                );
1390                VirtioError::InvalidQueue("used GPA below ram base".into())
1391            })?;
1392        let q_size = queue_config.size as usize;
1393
1394        if avail_addr + 4 > memory.len() {
1395            return Ok(Vec::new());
1396        }
1397        let avail_idx =
1398            u16::from_le_bytes([memory[avail_addr + 2], memory[avail_addr + 3]]) as usize;
1399
1400        let mut current_avail = self.last_avail_idx_tx;
1401        let mut completions = Vec::new();
1402
1403        while current_avail != avail_idx {
1404            let ring_off = avail_addr + 4 + 2 * (current_avail % q_size);
1405            if ring_off + 2 > memory.len() {
1406                break;
1407            }
1408            let head_idx = u16::from_le_bytes([memory[ring_off], memory[ring_off + 1]]) as usize;
1409
1410            // Walk descriptor chain to extract vsock packet.
1411            let mut packet_data = Vec::new();
1412            let mut idx = head_idx;
1413            for _ in 0..q_size {
1414                let d_off = desc_addr + idx * 16;
1415                if d_off + 16 > memory.len() {
1416                    break;
1417                }
1418                let addr = match (u64::from_le_bytes(memory[d_off..d_off + 8].try_into().unwrap())
1419                    as usize)
1420                    .checked_sub(gpa_base)
1421                {
1422                    Some(a) => a,
1423                    None => continue,
1424                };
1425                let len =
1426                    u32::from_le_bytes(memory[d_off + 8..d_off + 12].try_into().unwrap()) as usize;
1427                let flags = u16::from_le_bytes(memory[d_off + 12..d_off + 14].try_into().unwrap());
1428                let next = u16::from_le_bytes(memory[d_off + 14..d_off + 16].try_into().unwrap());
1429
1430                // TX descriptors are read-only (guest→host data).
1431                if flags & arcbox_virtio_core::queue::flags::WRITE == 0
1432                    && addr + len <= memory.len()
1433                {
1434                    packet_data.extend_from_slice(&memory[addr..addr + len]);
1435                }
1436
1437                if flags & arcbox_virtio_core::queue::flags::NEXT == 0 {
1438                    break;
1439                }
1440                idx = next as usize;
1441            }
1442
1443            // Parse vsock header (44 bytes) and forward via host fds.
1444            if packet_data.len() >= VsockHeader::SIZE {
1445                if let Some(hdr) = VsockHeader::from_bytes(&packet_data[..VsockHeader::SIZE]) {
1446                    let op_val = { hdr.op };
1447                    let src_cid = { hdr.src_cid };
1448                    let dst_cid = { hdr.dst_cid };
1449                    let src_port = { hdr.src_port };
1450                    let dst_port = { hdr.dst_port };
1451                    tracing::info!(
1452                        "Vsock TX: op={} src={}:{} dst={}:{} len={} (packet_data={} bytes)",
1453                        op_val,
1454                        src_cid,
1455                        src_port,
1456                        dst_cid,
1457                        dst_port,
1458                        { hdr.len },
1459                        packet_data.len(),
1460                    );
1461
1462                    let payload = &packet_data[VsockHeader::SIZE..];
1463                    if let Some(conns_arc) = self.conns.clone() {
1464                        if let Ok(mut conns) = conns_arc.lock() {
1465                            self.handle_tx_packet_with_fds(&hdr, payload, Some(&mut *conns));
1466                        }
1467                    } else {
1468                        self.handle_tx_packet_with_fds(&hdr, payload, None);
1469                    }
1470                }
1471            } else {
1472                tracing::warn!(
1473                    "Vsock TX: packet too short ({} bytes < {} header), skipping",
1474                    packet_data.len(),
1475                    VsockHeader::SIZE,
1476                );
1477            }
1478
1479            // Update used ring.
1480            let used_idx_off = used_addr + 2;
1481            let used_idx = u16::from_le_bytes([memory[used_idx_off], memory[used_idx_off + 1]]);
1482            let used_entry = used_addr + 4 + ((used_idx as usize) % q_size) * 8;
1483            if used_entry + 8 <= memory.len() {
1484                memory[used_entry..used_entry + 4]
1485                    .copy_from_slice(&(head_idx as u32).to_le_bytes());
1486                memory[used_entry + 4..used_entry + 8]
1487                    .copy_from_slice(&(packet_data.len() as u32).to_le_bytes());
1488                std::sync::atomic::fence(std::sync::atomic::Ordering::Release);
1489                let new_used = used_idx.wrapping_add(1);
1490                memory[used_idx_off..used_idx_off + 2].copy_from_slice(&new_used.to_le_bytes());
1491            }
1492
1493            // Update avail_event.
1494            let avail_event_off = used_addr + 4 + 8 * q_size;
1495            if avail_event_off + 2 <= memory.len() {
1496                let ae = ((current_avail + 1) as u16).to_le_bytes();
1497                memory[avail_event_off] = ae[0];
1498                memory[avail_event_off + 1] = ae[1];
1499            }
1500
1501            completions.push((head_idx as u16, packet_data.len() as u32));
1502            current_avail += 1;
1503        }
1504
1505        self.last_avail_idx_tx = current_avail;
1506        Ok(completions)
1507    }
1508}
1509
1510#[cfg(test)]
1511mod tests {
1512    use super::*;
1513
1514    #[test]
1515    fn test_vsock_config_default() {
1516        let config = VsockConfig::default();
1517        assert_eq!(config.guest_cid, 3);
1518    }
1519
1520    #[test]
1521    fn test_vsock_config_custom() {
1522        let config = VsockConfig { guest_cid: 100 };
1523        assert_eq!(config.guest_cid, 100);
1524    }
1525
1526    #[test]
1527    fn test_vsock_config_clone() {
1528        let config = VsockConfig { guest_cid: 42 };
1529        let cloned = config.clone();
1530        assert_eq!(cloned.guest_cid, 42);
1531    }
1532
1533    #[test]
1534    fn test_vsock_new() {
1535        let vsock = VirtioVsock::new(VsockConfig::default());
1536        assert_eq!(vsock.guest_cid(), 3);
1537    }
1538
1539    #[test]
1540    fn test_vsock_device_id() {
1541        let vsock = VirtioVsock::new(VsockConfig::default());
1542        assert_eq!(vsock.device_id(), VirtioDeviceId::Vsock);
1543    }
1544
1545    #[test]
1546    fn test_vsock_features() {
1547        let vsock = VirtioVsock::new(VsockConfig::default());
1548        let features = vsock.features();
1549        assert!(features & VirtioVsock::FEATURE_STREAM != 0);
1550    }
1551
1552    #[test]
1553    fn test_vsock_ack_features() {
1554        let mut vsock = VirtioVsock::new(VsockConfig::default());
1555
1556        vsock.ack_features(VirtioVsock::FEATURE_STREAM);
1557        assert_eq!(vsock.acked_features, VirtioVsock::FEATURE_STREAM);
1558    }
1559
1560    #[test]
1561    fn test_vsock_ack_unsupported_feature() {
1562        let mut vsock = VirtioVsock::new(VsockConfig::default());
1563
1564        // SEQPACKET is not supported by default
1565        vsock.ack_features(VirtioVsock::FEATURE_SEQPACKET);
1566        assert_eq!(vsock.acked_features, 0);
1567    }
1568
1569    #[test]
1570    fn test_vsock_read_config() {
1571        let config = VsockConfig {
1572            guest_cid: 0x12345678,
1573        };
1574        let vsock = VirtioVsock::new(config);
1575
1576        let mut data = [0u8; 8];
1577        vsock.read_config(0, &mut data);
1578
1579        let cid = u64::from_le_bytes(data);
1580        assert_eq!(cid, 0x12345678);
1581    }
1582
1583    #[test]
1584    fn test_vsock_read_config_partial() {
1585        let config = VsockConfig {
1586            guest_cid: 0xDEADBEEF,
1587        };
1588        let vsock = VirtioVsock::new(config);
1589
1590        let mut data = [0u8; 4];
1591        vsock.read_config(0, &mut data);
1592
1593        let low_bytes = u32::from_le_bytes(data);
1594        assert_eq!(low_bytes, 0xDEADBEEF);
1595    }
1596
1597    #[test]
1598    fn test_vsock_read_config_offset() {
1599        let config = VsockConfig {
1600            guest_cid: 0xAABBCCDD_11223344,
1601        };
1602        let vsock = VirtioVsock::new(config);
1603
1604        let mut data = [0u8; 4];
1605        vsock.read_config(4, &mut data);
1606
1607        let high_bytes = u32::from_le_bytes(data);
1608        assert_eq!(high_bytes, 0xAABBCCDD);
1609    }
1610
1611    #[test]
1612    fn test_vsock_read_config_beyond() {
1613        let vsock = VirtioVsock::new(VsockConfig::default());
1614
1615        let mut data = [0xFFu8; 4];
1616        vsock.read_config(100, &mut data);
1617    }
1618
1619    #[test]
1620    fn test_vsock_write_config_noop() {
1621        let mut vsock = VirtioVsock::new(VsockConfig { guest_cid: 42 });
1622
1623        vsock.write_config(0, &[0xFF; 8]);
1624
1625        assert_eq!(vsock.guest_cid(), 42);
1626    }
1627
1628    #[test]
1629    fn test_vsock_activate() {
1630        let mut vsock = VirtioVsock::new(VsockConfig::default());
1631        assert!(vsock.activate().is_ok());
1632    }
1633
1634    #[test]
1635    fn test_vsock_reset() {
1636        let mut vsock = VirtioVsock::new(VsockConfig::default());
1637        vsock.ack_features(VirtioVsock::FEATURE_STREAM);
1638        assert_ne!(vsock.acked_features, 0);
1639
1640        vsock.reset();
1641        assert_eq!(vsock.acked_features, 0);
1642    }
1643
1644    #[test]
1645    fn test_vsock_constants() {
1646        assert_eq!(VirtioVsock::HOST_CID, 2);
1647        assert_eq!(VirtioVsock::RESERVED_CID, 1);
1648        assert_eq!(VirtioVsock::FEATURE_STREAM, 1 << 0);
1649        assert_eq!(VirtioVsock::FEATURE_SEQPACKET, 1 << 1);
1650    }
1651
1652    #[test]
1653    fn test_vsock_with_loopback_backend() {
1654        let vsock = VirtioVsock::with_backend(VsockConfig::default(), LoopbackBackend::new());
1655        assert_eq!(vsock.guest_cid(), 3);
1656        assert_eq!(vsock.connection_count(), 0);
1657    }
1658
1659    #[test]
1660    fn test_vsock_connect_send_recv() {
1661        let vsock = VirtioVsock::with_backend(VsockConfig::default(), LoopbackBackend::new());
1662
1663        vsock.handle_connect(1000, 80).unwrap();
1664        assert_eq!(vsock.connection_count(), 1);
1665
1666        let data = b"GET / HTTP/1.1";
1667        let sent = vsock.handle_send(1000, 80, data).unwrap();
1668        assert_eq!(sent, data.len());
1669
1670        let mut buf = [0u8; 64];
1671        let received = vsock.handle_recv(1000, 80, &mut buf).unwrap();
1672        assert_eq!(received, data.len());
1673        assert_eq!(&buf[..received], data);
1674
1675        vsock.handle_close(1000, 80).unwrap();
1676        assert_eq!(vsock.connection_count(), 0);
1677    }
1678
1679    #[test]
1680    fn test_vsock_activate_creates_queues() {
1681        let mut vsock = VirtioVsock::new(VsockConfig::default());
1682        assert!(vsock.rx_queue.is_none());
1683        assert!(vsock.tx_queue.is_none());
1684        assert!(vsock.event_queue.is_none());
1685
1686        vsock.activate().unwrap();
1687
1688        assert!(vsock.rx_queue.is_some());
1689        assert!(vsock.tx_queue.is_some());
1690        assert!(vsock.event_queue.is_some());
1691    }
1692
1693    #[test]
1694    fn test_vsock_reset_clears_queues() {
1695        let mut vsock = VirtioVsock::new(VsockConfig::default());
1696        vsock.activate().unwrap();
1697        assert!(vsock.rx_queue.is_some());
1698
1699        vsock.reset();
1700        assert!(vsock.rx_queue.is_none());
1701        assert!(vsock.tx_queue.is_none());
1702        assert!(vsock.event_queue.is_none());
1703    }
1704
1705    /// Helper: Build a simulated guest memory region with a vsock packet
1706    /// placed at a given address, and configure the TX queue with matching
1707    /// descriptors.
1708    fn setup_tx_packet(
1709        vsock: &mut VirtioVsock,
1710        guest_addr: usize,
1711        header: &VsockHeader,
1712        payload: &[u8],
1713        memory: &mut Vec<u8>,
1714    ) {
1715        let header_bytes = header.to_bytes();
1716        let total = header_bytes.len() + payload.len();
1717
1718        if memory.len() < guest_addr + total {
1719            memory.resize(guest_addr + total, 0);
1720        }
1721
1722        memory[guest_addr..guest_addr + header_bytes.len()].copy_from_slice(&header_bytes);
1723        if !payload.is_empty() {
1724            memory[guest_addr + header_bytes.len()..guest_addr + total].copy_from_slice(payload);
1725        }
1726
1727        let queue = vsock.tx_queue.as_mut().unwrap();
1728        let desc = arcbox_virtio_core::queue::Descriptor {
1729            addr: guest_addr as u64,
1730            len: total as u32,
1731            flags: 0, // Read-only for device
1732            next: 0,
1733        };
1734        queue.set_descriptor(0, desc).unwrap();
1735        queue.add_avail(0).unwrap();
1736    }
1737
1738    #[test]
1739    fn test_process_tx_queue_not_ready() {
1740        let mut vsock = VirtioVsock::new(VsockConfig::default());
1741        let mut memory = vec![0u8; 1024];
1742        let result = vsock.process_tx_queue(&mut memory);
1743        assert!(result.is_err());
1744    }
1745
1746    #[test]
1747    fn test_process_tx_queue_empty() {
1748        let mut vsock = VirtioVsock::new(VsockConfig::default());
1749        vsock.activate().unwrap();
1750
1751        let mut memory = vec![0u8; 4096];
1752        let completions = vsock.process_tx_queue(&mut memory).unwrap();
1753        assert!(completions.is_empty());
1754    }
1755
1756    #[test]
1757    fn test_process_tx_queue_connect_request() {
1758        let mut vsock = VirtioVsock::with_backend(VsockConfig::default(), LoopbackBackend::new());
1759        vsock.activate().unwrap();
1760
1761        let mut memory = vec![0u8; 4096];
1762
1763        // Guest sends OP_REQUEST from port 1000 to host port 80.
1764        let header = VsockHeader::new(
1765            VsockAddr::new(3, 1000),
1766            VsockAddr::new(VirtioVsock::HOST_CID, 80),
1767            VsockOp::Request,
1768        );
1769        setup_tx_packet(&mut vsock, 0x100, &header, &[], &mut memory);
1770
1771        // Also prepare RX queue with a write-only descriptor for the response.
1772        {
1773            let rx_queue = vsock.rx_queue.as_mut().unwrap();
1774            let rx_desc = arcbox_virtio_core::queue::Descriptor {
1775                addr: 0x800,
1776                len: 256,
1777                flags: arcbox_virtio_core::queue::flags::WRITE,
1778                next: 0,
1779            };
1780            rx_queue.set_descriptor(0, rx_desc).unwrap();
1781            rx_queue.add_avail(0).unwrap();
1782        }
1783
1784        let completions = vsock.process_tx_queue(&mut memory).unwrap();
1785        assert_eq!(completions.len(), 1);
1786        assert_eq!(completions[0].0, 0); // descriptor head index
1787
1788        assert_eq!(vsock.connection_count(), 1);
1789
1790        let resp_header = VsockHeader::from_bytes(&memory[0x800..0x800 + VsockHeader::SIZE]);
1791        assert!(resp_header.is_some());
1792        let resp = resp_header.unwrap();
1793        assert_eq!(resp.operation(), Some(VsockOp::Response));
1794        let resp_src_cid = resp.src_cid;
1795        let resp_dst_cid = resp.dst_cid;
1796        assert_eq!(resp_src_cid, VirtioVsock::HOST_CID);
1797        assert_eq!(resp_dst_cid, 3);
1798    }
1799
1800    #[test]
1801    fn test_process_tx_queue_data_rw() {
1802        let mut vsock = VirtioVsock::with_backend(VsockConfig::default(), LoopbackBackend::new());
1803        vsock.activate().unwrap();
1804
1805        vsock.handle_connect(1000, 80).unwrap();
1806
1807        let mut memory = vec![0u8; 4096];
1808
1809        let payload = b"hello world";
1810        let mut header = VsockHeader::new(
1811            VsockAddr::new(3, 1000),
1812            VsockAddr::new(VirtioVsock::HOST_CID, 80),
1813            VsockOp::Rw,
1814        );
1815        header.len = payload.len() as u32;
1816        setup_tx_packet(&mut vsock, 0x100, &header, payload, &mut memory);
1817
1818        let completions = vsock.process_tx_queue(&mut memory).unwrap();
1819        assert_eq!(completions.len(), 1);
1820
1821        let backend = vsock.backend.as_ref().unwrap();
1822        let mut backend = backend.lock().unwrap();
1823        let addr = VsockAddr::new(3, 1000);
1824        assert!(backend.has_pending_data(addr));
1825
1826        let mut buf = [0u8; 64];
1827        let n = backend.on_recv(addr, &mut buf).unwrap();
1828        assert_eq!(&buf[..n], payload);
1829    }
1830
1831    #[test]
1832    fn test_process_tx_queue_shutdown() {
1833        let mut vsock = VirtioVsock::with_backend(VsockConfig::default(), LoopbackBackend::new());
1834        vsock.activate().unwrap();
1835
1836        vsock.handle_connect(2000, 443).unwrap();
1837        assert_eq!(vsock.connection_count(), 1);
1838
1839        let mut memory = vec![0u8; 4096];
1840
1841        let header = VsockHeader::new(
1842            VsockAddr::new(3, 2000),
1843            VsockAddr::new(VirtioVsock::HOST_CID, 443),
1844            VsockOp::Shutdown,
1845        );
1846        setup_tx_packet(&mut vsock, 0x100, &header, &[], &mut memory);
1847
1848        // Provide an RX descriptor for the RST response.
1849        {
1850            let rx_queue = vsock.rx_queue.as_mut().unwrap();
1851            let rx_desc = arcbox_virtio_core::queue::Descriptor {
1852                addr: 0x800,
1853                len: 256,
1854                flags: arcbox_virtio_core::queue::flags::WRITE,
1855                next: 0,
1856            };
1857            rx_queue.set_descriptor(0, rx_desc).unwrap();
1858            rx_queue.add_avail(0).unwrap();
1859        }
1860
1861        let completions = vsock.process_tx_queue(&mut memory).unwrap();
1862        assert_eq!(completions.len(), 1);
1863
1864        assert_eq!(vsock.connection_count(), 0);
1865
1866        let rst_header = VsockHeader::from_bytes(&memory[0x800..0x800 + VsockHeader::SIZE]);
1867        assert!(rst_header.is_some());
1868        assert_eq!(rst_header.unwrap().operation(), Some(VsockOp::Rst));
1869    }
1870
1871    #[test]
1872    fn test_process_tx_queue_credit_update() {
1873        let mut vsock = VirtioVsock::with_backend(VsockConfig::default(), LoopbackBackend::new());
1874        vsock.activate().unwrap();
1875
1876        vsock.handle_connect(3000, 22).unwrap();
1877
1878        let mut memory = vec![0u8; 4096];
1879
1880        let mut header = VsockHeader::new(
1881            VsockAddr::new(3, 3000),
1882            VsockAddr::new(VirtioVsock::HOST_CID, 22),
1883            VsockOp::CreditUpdate,
1884        );
1885        header.buf_alloc = 131_072;
1886        header.fwd_cnt = 500;
1887        setup_tx_packet(&mut vsock, 0x100, &header, &[], &mut memory);
1888
1889        let completions = vsock.process_tx_queue(&mut memory).unwrap();
1890        assert_eq!(completions.len(), 1);
1891
1892        let conns = vsock.connections.read().unwrap();
1893        let conn = conns.get(&(3000, 22)).unwrap();
1894        assert_eq!(conn.peer_buf_alloc, 131_072);
1895        assert_eq!(conn.peer_fwd_cnt, 500);
1896    }
1897
1898    #[test]
1899    fn test_process_queue_dispatches_tx() {
1900        let mut vsock = VirtioVsock::with_backend(VsockConfig::default(), LoopbackBackend::new());
1901        vsock.activate().unwrap();
1902
1903        let mut memory = vec![0u8; 4096];
1904
1905        let completions = vsock.process_queue(1, &mut memory).unwrap();
1906        assert!(completions.is_empty());
1907    }
1908
1909    #[test]
1910    fn test_process_queue_unknown_index() {
1911        let mut vsock = VirtioVsock::new(VsockConfig::default());
1912        vsock.activate().unwrap();
1913
1914        let mut memory = vec![0u8; 1024];
1915        let completions = vsock.process_queue(0, &mut memory).unwrap();
1916        assert!(completions.is_empty());
1917        let completions = vsock.process_queue(2, &mut memory).unwrap();
1918        assert!(completions.is_empty());
1919        let completions = vsock.process_queue(99, &mut memory).unwrap();
1920        assert!(completions.is_empty());
1921    }
1922
1923    #[test]
1924    fn test_inject_rx_packet_not_ready() {
1925        let mut vsock = VirtioVsock::new(VsockConfig::default());
1926        let header = VsockHeader::new(
1927            VsockAddr::host(80),
1928            VsockAddr::new(3, 1000),
1929            VsockOp::Response,
1930        );
1931        let mut memory = vec![0u8; 1024];
1932        let result = vsock.inject_rx_packet(&header, &[], &mut memory);
1933        assert!(result.is_err());
1934    }
1935
1936    #[test]
1937    fn test_inject_rx_packet_no_descriptors() {
1938        let mut vsock = VirtioVsock::new(VsockConfig::default());
1939        vsock.activate().unwrap();
1940
1941        let header = VsockHeader::new(
1942            VsockAddr::host(80),
1943            VsockAddr::new(3, 1000),
1944            VsockOp::Response,
1945        );
1946        let mut memory = vec![0u8; 1024];
1947        let result = vsock.inject_rx_packet(&header, &[], &mut memory);
1948        assert!(result.is_err());
1949    }
1950
1951    #[test]
1952    fn test_inject_rx_packet_with_data() {
1953        let mut vsock = VirtioVsock::new(VsockConfig::default());
1954        vsock.activate().unwrap();
1955
1956        let mut memory = vec![0u8; 4096];
1957
1958        {
1959            let rx_queue = vsock.rx_queue.as_mut().unwrap();
1960            let desc = arcbox_virtio_core::queue::Descriptor {
1961                addr: 0x200,
1962                len: 512,
1963                flags: arcbox_virtio_core::queue::flags::WRITE,
1964                next: 0,
1965            };
1966            rx_queue.set_descriptor(0, desc).unwrap();
1967            rx_queue.add_avail(0).unwrap();
1968        }
1969
1970        let payload = b"response data";
1971        let mut header =
1972            VsockHeader::new(VsockAddr::host(80), VsockAddr::new(3, 1000), VsockOp::Rw);
1973        header.len = payload.len() as u32;
1974
1975        vsock
1976            .inject_rx_packet(&header, payload, &mut memory)
1977            .unwrap();
1978
1979        let written_hdr =
1980            VsockHeader::from_bytes(&memory[0x200..0x200 + VsockHeader::SIZE]).unwrap();
1981        assert_eq!(written_hdr.operation(), Some(VsockOp::Rw));
1982        let wh_src_cid = written_hdr.src_cid;
1983        assert_eq!(wh_src_cid, VirtioVsock::HOST_CID);
1984
1985        let payload_start = 0x200 + VsockHeader::SIZE;
1986        assert_eq!(
1987            &memory[payload_start..payload_start + payload.len()],
1988            payload
1989        );
1990    }
1991
1992    /// Builds a simulated split virtqueue layout in a flat memory buffer.
1993    /// Returns (`desc_addr`, `avail_addr`, `used_addr`).
1994    fn setup_virtqueue_layout(
1995        memory: &mut Vec<u8>,
1996        base: usize,
1997        q_size: usize,
1998    ) -> (usize, usize, usize) {
1999        let desc_addr = base;
2000        let avail_addr = desc_addr + q_size * 16;
2001        let avail_addr = (avail_addr + 15) & !15;
2002        let avail_size = 4 + 2 * q_size + 2;
2003        let used_addr = avail_addr + avail_size;
2004        let used_addr = (used_addr + 15) & !15;
2005        let used_size = 4 + 8 * q_size + 2;
2006        let total = used_addr + used_size;
2007        if memory.len() < total {
2008            memory.resize(total, 0);
2009        }
2010        (desc_addr, avail_addr, used_addr)
2011    }
2012
2013    fn write_descriptor(
2014        memory: &mut [u8],
2015        desc_addr: usize,
2016        idx: usize,
2017        addr: u64,
2018        len: u32,
2019        flags: u16,
2020        next: u16,
2021    ) {
2022        let off = desc_addr + idx * 16;
2023        memory[off..off + 8].copy_from_slice(&addr.to_le_bytes());
2024        memory[off + 8..off + 12].copy_from_slice(&len.to_le_bytes());
2025        memory[off + 12..off + 14].copy_from_slice(&flags.to_le_bytes());
2026        memory[off + 14..off + 16].copy_from_slice(&next.to_le_bytes());
2027    }
2028
2029    fn avail_ring_push(memory: &mut [u8], avail_addr: usize, q_size: usize, head_idx: u16) {
2030        let avail_idx =
2031            u16::from_le_bytes([memory[avail_addr + 2], memory[avail_addr + 3]]) as usize;
2032        let ring_off = avail_addr + 4 + 2 * (avail_idx % q_size);
2033        memory[ring_off..ring_off + 2].copy_from_slice(&head_idx.to_le_bytes());
2034        let new_idx = (avail_idx + 1) as u16;
2035        memory[avail_addr + 2..avail_addr + 4].copy_from_slice(&new_idx.to_le_bytes());
2036    }
2037
2038    /// Verifies that the guest-memory-based `process_queue` correctly parses
2039    /// a 44-byte OP_RESPONSE packet from the TX virtqueue.
2040    #[test]
2041    fn test_process_queue_guest_memory_op_response() {
2042        let mut vsock = VirtioVsock::new(VsockConfig::default());
2043        vsock.activate().unwrap();
2044
2045        let q_size = 16usize;
2046        let mut memory = vec![0u8; 0x10000];
2047
2048        let (desc_addr, avail_addr, used_addr) =
2049            setup_virtqueue_layout(&mut memory, 0x4000, q_size);
2050
2051        let pkt_addr = 0x8000usize;
2052        let hdr = VsockHeader::new(
2053            VsockAddr::new(3, 1024),
2054            VsockAddr::host(50000),
2055            VsockOp::Response,
2056        );
2057        let hdr_bytes = hdr.to_bytes();
2058        assert_eq!(
2059            hdr_bytes.len(),
2060            44,
2061            "VsockHeader must serialize to 44 bytes"
2062        );
2063        memory[pkt_addr..pkt_addr + 44].copy_from_slice(&hdr_bytes[..44]);
2064
2065        write_descriptor(&mut memory, desc_addr, 0, pkt_addr as u64, 44, 0, 0);
2066        avail_ring_push(&mut memory, avail_addr, q_size, 0);
2067
2068        struct MockConns {
2069            connected: Vec<(u32, u32)>,
2070            credit_updates: Vec<(u32, u32, u32, u32)>,
2071        }
2072        impl VsockHostConnections for MockConns {
2073            fn fd_for(&self, _gp: u32, _hp: u32) -> Option<std::os::unix::io::RawFd> {
2074                None
2075            }
2076            fn mark_connected(&mut self, gp: u32, hp: u32) {
2077                self.connected.push((gp, hp));
2078            }
2079            fn remove_connection(&mut self, _gp: u32, _hp: u32) {}
2080            fn update_peer_credit(&mut self, gp: u32, hp: u32, ba: u32, fc: u32) {
2081                self.credit_updates.push((gp, hp, ba, fc));
2082            }
2083        }
2084
2085        let mock = Arc::new(Mutex::new(MockConns {
2086            connected: Vec::new(),
2087            credit_updates: Vec::new(),
2088        }));
2089
2090        let qcfg = QueueConfig {
2091            desc_addr: desc_addr as u64,
2092            avail_addr: avail_addr as u64,
2093            used_addr: used_addr as u64,
2094            size: q_size as u16,
2095            ready: true,
2096            gpa_base: 0,
2097        };
2098        vsock.bind_connections(mock.clone());
2099
2100        let completions =
2101            <VirtioVsock as VirtioDevice>::process_queue(&mut vsock, 1, &mut memory, &qcfg)
2102                .unwrap();
2103
2104        assert_eq!(
2105            completions.len(),
2106            1,
2107            "Expected 1 completion for OP_RESPONSE"
2108        );
2109        assert_eq!(completions[0].0, 0, "head_idx should be 0");
2110        assert_eq!(completions[0].1, 44, "written bytes should be 44");
2111
2112        let mock_guard = mock.lock().unwrap();
2113        assert_eq!(
2114            mock_guard.connected.len(),
2115            1,
2116            "mark_connected should be called once for OP_RESPONSE"
2117        );
2118        assert_eq!(mock_guard.connected[0], (1024, 50000));
2119
2120        assert_eq!(mock_guard.credit_updates.len(), 1);
2121        assert_eq!(
2122            mock_guard.credit_updates[0],
2123            (1024, 50000, 64 * 1024, 0),
2124            "peer credit should be synced from OP_RESPONSE header"
2125        );
2126    }
2127
2128    /// Verifies that a 44-byte OP_RST from guest is correctly parsed via
2129    /// the guest-memory `process_queue` path.
2130    #[test]
2131    fn test_process_queue_guest_memory_op_rst() {
2132        let mut vsock = VirtioVsock::new(VsockConfig::default());
2133        vsock.activate().unwrap();
2134
2135        let q_size = 16usize;
2136        let mut memory = vec![0u8; 0x10000];
2137
2138        let (desc_addr, avail_addr, used_addr) =
2139            setup_virtqueue_layout(&mut memory, 0x4000, q_size);
2140
2141        let pkt_addr = 0x8000usize;
2142        let hdr = VsockHeader::new(
2143            VsockAddr::new(3, 1024),
2144            VsockAddr::host(50000),
2145            VsockOp::Rst,
2146        );
2147        memory[pkt_addr..pkt_addr + 44].copy_from_slice(&hdr.to_bytes()[..44]);
2148
2149        write_descriptor(&mut memory, desc_addr, 0, pkt_addr as u64, 44, 0, 0);
2150        avail_ring_push(&mut memory, avail_addr, q_size, 0);
2151
2152        struct MockConns {
2153            removed: Vec<(u32, u32)>,
2154        }
2155        impl VsockHostConnections for MockConns {
2156            fn fd_for(&self, _: u32, _: u32) -> Option<std::os::unix::io::RawFd> {
2157                None
2158            }
2159            fn mark_connected(&mut self, _: u32, _: u32) {}
2160            fn remove_connection(&mut self, gp: u32, hp: u32) {
2161                self.removed.push((gp, hp));
2162            }
2163        }
2164        let mock = Arc::new(Mutex::new(MockConns {
2165            removed: Vec::new(),
2166        }));
2167
2168        let qcfg = QueueConfig {
2169            desc_addr: desc_addr as u64,
2170            avail_addr: avail_addr as u64,
2171            used_addr: used_addr as u64,
2172            size: q_size as u16,
2173            ready: true,
2174            gpa_base: 0,
2175        };
2176        vsock.bind_connections(mock.clone());
2177
2178        let completions =
2179            <VirtioVsock as VirtioDevice>::process_queue(&mut vsock, 1, &mut memory, &qcfg)
2180                .unwrap();
2181        assert_eq!(completions.len(), 1);
2182
2183        let mock_guard = mock.lock().unwrap();
2184        assert_eq!(mock_guard.removed.len(), 1);
2185        assert_eq!(mock_guard.removed[0], (1024, 50000));
2186    }
2187}