vox-shm 0.3.1

Shared-memory transport for vox — lock-free rings for zero-copy IPC
Documentation
//! High-level SHM bootstrap helpers over control connections.
//!
//! This module layers host orchestration (`HostHub`) on top of the shared
//! bootstrap wire primitives from `shm_primitives::bootstrap`.

use std::io;

use shm_primitives::PeerId;
pub use shm_primitives::bootstrap::{
    BOOTSTRAP_REQUEST_HEADER_LEN, BOOTSTRAP_REQUEST_MAGIC, BOOTSTRAP_RESPONSE_HEADER_LEN,
    BOOTSTRAP_RESPONSE_MAGIC, BootstrapError, BootstrapRequestRef, BootstrapResponseOwned,
    BootstrapResponseRef, BootstrapStatus, decode_request, decode_response, encode_request,
    encode_response,
};
pub use shm_primitives::bootstrap::{
    BootstrapSuccessNames, ReceivedBootstrapResponseStream, recv_response_stream,
    send_response_stream,
};

use crate::host::{GuestSpawnTicket, HostHub, HostPeer};
use crate::segment::Segment;

pub use crate::host::PreparedPeer;
#[cfg(windows)]
pub use crate::host::guest_link_from_names;
#[cfg(unix)]
pub use crate::host::{guest_link_from_raw, guest_link_from_ticket};

/// Result of preparing one bootstrap success response for a guest.
pub struct PreparedBootstrapPeer {
    pub host_peer: HostPeer,
    pub guest_ticket: GuestSpawnTicket,
    pub response_frame: Vec<u8>,
}

impl PreparedBootstrapPeer {
    pub fn peer_id(&self) -> PeerId {
        self.guest_ticket.peer_id
    }
}

impl HostHub {
    /// Reserve a peer and prepare a success response frame for bootstrap.
    ///
    /// `payload` is application-defined response data (typically the segment path).
    pub fn prepare_bootstrap_success(&self, payload: &[u8]) -> io::Result<PreparedBootstrapPeer> {
        let prepared = self.prepare_peer()?;
        let (host_peer, guest_ticket) = prepared.into_parts();
        let response_frame = encode_response(
            BootstrapStatus::Success,
            guest_ticket.peer_id.get() as u32,
            payload,
        )
        .map_err(|err| io::Error::other(format!("failed to encode bootstrap response: {err}")))?;

        Ok(PreparedBootstrapPeer {
            host_peer,
            guest_ticket,
            response_frame,
        })
    }

    /// Build an error response frame for bootstrap request rejection.
    pub fn bootstrap_error_response(&self, message: &[u8]) -> io::Result<Vec<u8>> {
        encode_response(BootstrapStatus::Error, 0, message)
            .map_err(|err| io::Error::other(format!("failed to encode bootstrap error: {err}")))
    }
}

#[cfg(unix)]
impl PreparedBootstrapPeer {
    /// Send the prepared success response and bootstrap fds on Unix.
    ///
    /// r[impl shm.bootstrap.success]
    /// r[impl shm.bootstrap.unix]
    pub fn send_success_unix(
        &self,
        control_fd: std::os::fd::RawFd,
        segment: &Segment,
    ) -> Result<(), BootstrapError> {
        let fds = shm_primitives::bootstrap::BootstrapSuccessFds {
            doorbell_fd: self.guest_ticket.doorbell.as_raw_fd(),
            segment_fd: segment.as_raw_fd(),
            mmap_control_fd: self.guest_ticket.mmap_rx.as_raw_fd(),
        };

        shm_primitives::bootstrap::send_response_unix(
            control_fd,
            BootstrapStatus::Success,
            self.guest_ticket.peer_id.get() as u32,
            // This was generated by prepare_bootstrap_success; decode+reuse keeps one source.
            decode_response(&self.response_frame)?.payload,
            Some(&fds),
        )
    }
}

impl PreparedBootstrapPeer {
    /// Send the prepared success response over a byte stream (no fd-passing).
    ///
    /// The payload encodes `{segment_path}\0{doorbell_pipe}\0{mmap_ctrl_pipe}`
    /// so the guest can connect by name.
    ///
    /// r[impl shm.bootstrap.success]
    pub fn send_success_stream(
        &self,
        stream: &mut impl io::Write,
        segment: &Segment,
    ) -> Result<(), BootstrapError> {
        let names = BootstrapSuccessNames {
            segment_path: segment
                .path()
                .to_str()
                .ok_or_else(|| {
                    BootstrapError::Io(io::Error::new(
                        io::ErrorKind::InvalidData,
                        "segment path is not valid UTF-8",
                    ))
                })?
                .to_string(),
            doorbell_name: self.guest_ticket.doorbell_arg(),
            mmap_ctrl_name: self.guest_ticket.mmap_rx_arg(),
        };
        let payload = names.encode();

        send_response_stream(
            stream,
            BootstrapStatus::Success,
            self.guest_ticket.peer_id.get() as u32,
            &payload,
        )
    }
}