Skip to main content

virtio_accel_device/
frame.rs

1//! Atomic preflight for one transport-neutral command frame.
2//!
3//! This is the only byte-oriented entry point needed by the command engine. It validates the
4//! flattened chain shape, confirms that the byte ports match the advertised totals, decodes the
5//! complete request, and writes recoverable protocol errors before returning. Only
6//! [`FramePreflight::Ready`] contains a request that semantic dispatch may act on.
7
8use virtio_accel_core::{ByteSink, ByteSource};
9use virtio_accel_proto::StatusCode;
10
11use crate::{
12    ChainLayoutError, ChainRegion, DecodedRequest, FrameDecodeError, FrameDecoder,
13    ResponseWriteError, ResponseWriter, UnrecoverableDecodeError, validate_chain_layout,
14};
15
16#[derive(Clone, Copy, Debug, PartialEq, Eq)]
17pub enum UnusableFrame {
18    ChainLayout(ChainLayoutError),
19    Request(UnrecoverableDecodeError),
20    InsufficientResponse {
21        request_id: u64,
22        required: u64,
23        available: u64,
24    },
25}
26
27#[derive(Debug)]
28pub enum FramePreflight<'a> {
29    /// The complete frame is validated and may proceed to semantic dispatch.
30    Ready(DecodedRequest<'a>),
31    /// A recoverable protocol error was written to the response port.
32    Rejected {
33        request_id: u64,
34        status: StatusCode,
35        used: u32,
36    },
37    /// No response bytes were written and the chain must complete with used length zero.
38    Unusable(UnusableFrame),
39}
40
41#[derive(Clone, Copy, Debug, PartialEq, Eq)]
42pub enum FramePreflightError {
43    ResponseWrite(ResponseWriteError),
44}
45
46/// Validate one complete chain before semantic state or a backend can be reached.
47///
48/// The function is allocation-free except for the bounded `SUBMIT` binding allocation performed
49/// by [`FrameDecoder`]. `Ready` leaves the response untouched. `Rejected` initializes exactly one
50/// 16-byte error header. `Unusable` leaves the response untouched.
51pub fn preflight_command_frame<'a>(
52    decoder: &FrameDecoder,
53    regions: &[ChainRegion],
54    request: &'a dyn ByteSource,
55    response: &mut dyn ByteSink,
56) -> Result<FramePreflight<'a>, FramePreflightError> {
57    let layout = match validate_chain_layout(regions, decoder.limits().max_chain_descriptors()) {
58        Ok(layout) => layout,
59        Err(error) => {
60            return Ok(FramePreflight::Unusable(UnusableFrame::ChainLayout(error)));
61        }
62    };
63    if let Err(error) = layout.validate_port_lengths(request.len(), response.len()) {
64        return Ok(FramePreflight::Unusable(UnusableFrame::ChainLayout(error)));
65    }
66
67    match decoder.decode(request, response.len()) {
68        Ok(request) => Ok(FramePreflight::Ready(request)),
69        Err(FrameDecodeError::Protocol { request_id, status }) => {
70            let used = ResponseWriter::new(response, decoder.limits().max_response_bytes())
71                .write_empty(status, request_id)
72                .map_err(FramePreflightError::ResponseWrite)?;
73            Ok(FramePreflight::Rejected {
74                request_id,
75                status,
76                used,
77            })
78        }
79        Err(FrameDecodeError::Unrecoverable(error)) => {
80            Ok(FramePreflight::Unusable(UnusableFrame::Request(error)))
81        }
82        Err(FrameDecodeError::InsufficientResponse {
83            request_id,
84            required,
85            available,
86        }) => Ok(FramePreflight::Unusable(
87            UnusableFrame::InsufficientResponse {
88                request_id,
89                required,
90                available,
91            },
92        )),
93    }
94}