Skip to main content

virtio_accel_transport/
regions.rs

1//! Transport-neutral flattened descriptor metadata.
2
3/// Direction of one flattened descriptor-backed byte region.
4#[derive(Clone, Copy, Debug, PartialEq, Eq)]
5pub enum RegionDirection {
6    /// Bytes are readable by the device and contain request data.
7    DeviceReadable,
8    /// Bytes are writable by the device and contain response data.
9    DeviceWritable,
10}
11
12/// Direction and length of one flattened chain region, without an address or transport identity.
13#[derive(Clone, Copy, Debug, PartialEq, Eq)]
14pub struct ChainRegion {
15    /// Direction in which the device may access this region.
16    pub direction: RegionDirection,
17    /// Nonzero region length in bytes.
18    pub bytes: u64,
19}
20
21impl ChainRegion {
22    /// Construct a device-readable region.
23    pub const fn readable(bytes: u64) -> Self {
24        Self {
25            direction: RegionDirection::DeviceReadable,
26            bytes,
27        }
28    }
29
30    /// Construct a device-writable region.
31    pub const fn writable(bytes: u64) -> Self {
32        Self {
33            direction: RegionDirection::DeviceWritable,
34            bytes,
35        }
36    }
37}
38
39/// Failure to validate the device-specific flattened chain layout.
40#[derive(Clone, Copy, Debug, PartialEq, Eq)]
41pub enum ChainLayoutError {
42    /// The chain contains fewer than two or more than the configured maximum descriptors.
43    DescriptorCount,
44    /// A descriptor has zero length.
45    ZeroLength,
46    /// A readable descriptor follows a writable descriptor, or one direction is missing.
47    Direction,
48    /// A readable or writable byte total overflowed `u64`.
49    LengthOverflow,
50    /// The mapped byte ports do not exactly match the flattened descriptor totals.
51    PortLengthMismatch,
52}
53
54/// Validated counts and byte totals for one flattened descriptor chain.
55#[derive(Clone, Copy, Debug, PartialEq, Eq)]
56pub struct ChainLayout {
57    descriptor_count: u16,
58    readable_descriptors: u16,
59    writable_descriptors: u16,
60    readable_bytes: u64,
61    writable_bytes: u64,
62}
63
64impl ChainLayout {
65    /// Total flattened descriptor count.
66    pub const fn descriptor_count(self) -> u16 {
67        self.descriptor_count
68    }
69
70    /// Number of device-readable descriptors.
71    pub const fn readable_descriptors(self) -> u16 {
72        self.readable_descriptors
73    }
74
75    /// Number of device-writable descriptors.
76    pub const fn writable_descriptors(self) -> u16 {
77        self.writable_descriptors
78    }
79
80    /// Total device-readable bytes.
81    pub const fn readable_bytes(self) -> u64 {
82        self.readable_bytes
83    }
84
85    /// Total device-writable bytes.
86    pub const fn writable_bytes(self) -> u64 {
87        self.writable_bytes
88    }
89
90    /// Validate mapped byte-port lengths against this layout.
91    pub const fn validate_port_lengths(
92        self,
93        request_bytes: u64,
94        response_bytes: u64,
95    ) -> Result<(), ChainLayoutError> {
96        if request_bytes != self.readable_bytes || response_bytes != self.writable_bytes {
97            return Err(ChainLayoutError::PortLengthMismatch);
98        }
99        Ok(())
100    }
101}
102
103/// Validate transport-neutral descriptor metadata before frame bytes are decoded.
104///
105/// This operation is nonblocking, performs no allocation, and visits each region once. Guest
106/// addresses, descriptor indices, and mapping details remain owned by the transport adapter.
107pub fn validate_chain_layout(
108    regions: &[ChainRegion],
109    max_descriptors: u16,
110) -> Result<ChainLayout, ChainLayoutError> {
111    if regions.len() < 2 || regions.len() > usize::from(max_descriptors) {
112        return Err(ChainLayoutError::DescriptorCount);
113    }
114
115    let mut readable_descriptors = 0_u16;
116    let mut writable_descriptors = 0_u16;
117    let mut readable_bytes = 0_u64;
118    let mut writable_bytes = 0_u64;
119    let mut saw_writable = false;
120
121    for region in regions {
122        if region.bytes == 0 {
123            return Err(ChainLayoutError::ZeroLength);
124        }
125        match region.direction {
126            RegionDirection::DeviceReadable => {
127                if saw_writable {
128                    return Err(ChainLayoutError::Direction);
129                }
130                readable_descriptors += 1;
131                readable_bytes = readable_bytes
132                    .checked_add(region.bytes)
133                    .ok_or(ChainLayoutError::LengthOverflow)?;
134            }
135            RegionDirection::DeviceWritable => {
136                saw_writable = true;
137                writable_descriptors += 1;
138                writable_bytes = writable_bytes
139                    .checked_add(region.bytes)
140                    .ok_or(ChainLayoutError::LengthOverflow)?;
141            }
142        }
143    }
144
145    if readable_descriptors == 0 || writable_descriptors == 0 {
146        return Err(ChainLayoutError::Direction);
147    }
148
149    Ok(ChainLayout {
150        descriptor_count: regions.len() as u16,
151        readable_descriptors,
152        writable_descriptors,
153        readable_bytes,
154        writable_bytes,
155    })
156}
157
158#[cfg(test)]
159mod tests {
160    use super::*;
161
162    #[test]
163    fn chain_layout_rejects_direction_and_length_errors() {
164        assert_eq!(
165            validate_chain_layout(&[ChainRegion::readable(16)], 8),
166            Err(ChainLayoutError::DescriptorCount)
167        );
168        assert_eq!(
169            validate_chain_layout(
170                &[
171                    ChainRegion::readable(16),
172                    ChainRegion::writable(16),
173                    ChainRegion::readable(1),
174                ],
175                8,
176            ),
177            Err(ChainLayoutError::Direction)
178        );
179        assert_eq!(
180            validate_chain_layout(&[ChainRegion::readable(0), ChainRegion::writable(16)], 8),
181            Err(ChainLayoutError::ZeroLength)
182        );
183        assert_eq!(
184            validate_chain_layout(
185                &[
186                    ChainRegion::readable(u64::MAX),
187                    ChainRegion::readable(1),
188                    ChainRegion::writable(16),
189                ],
190                8,
191            ),
192            Err(ChainLayoutError::LengthOverflow)
193        );
194
195        let layout =
196            validate_chain_layout(&[ChainRegion::readable(16), ChainRegion::writable(16)], 8)
197                .unwrap();
198        assert_eq!(
199            layout.validate_port_lengths(15, 16),
200            Err(ChainLayoutError::PortLengthMismatch)
201        );
202    }
203}