Skip to main content

compio_io/ancillary/
mod.rs

1//! Ancillary data (control message) support for connected streams.
2//!
3//! Ancillary messages are used to pass out-of-band information such as file
4//! descriptors (Unix domain sockets), credentials, or kTLS record types.
5//!
6//! # Types
7//!
8//! - [`AncillaryBuf`]: A fixed-size, properly aligned stack buffer for
9//!   ancillary messages.
10//! - [`AncillaryBuilder`]: A builder for constructing ancillary messages into a
11//!   [`AncillaryBuf`].
12//! - [`AncillaryIter`]: An iterator over a buffer of ancillary messages.
13//! - [`AncillaryRef`]: A reference to a single ancillary data entry.
14//! - [`AncillaryData`]: Trait for types that can be encoded/decoded as
15//!   ancillary data payloads.
16//! - [`CodecError`]: Error type for encoding/decoding operations.
17//!
18//! # Traits
19//!
20//! - [`AsyncReadAncillary`]: read data together with ancillary data
21//! - [`AsyncWriteAncillary`]: write data together with ancillary data
22//!
23//! # Functions
24//!
25//! - [`ancillary_space`]: Helper function to calculate ancillary message size
26//!   for a type.
27//!
28//! # Modules
29//!
30//! - [`bytemuck_ext`]: Extension module for automatic [`AncillaryData`]
31//!   implementation via bytemuck (requires `bytemuck` feature).
32
33use std::{
34    mem::MaybeUninit,
35    ops::{Deref, DerefMut},
36    ptr,
37};
38
39use compio_buf::{IoBuf, IoBufMut, SetLen};
40
41mod io;
42
43pub use self::io::*;
44
45#[cfg(feature = "bytemuck")]
46pub mod bytemuck_ext;
47mod sys;
48
49/// Reference to an ancillary (control) message.
50pub struct AncillaryRef<'a>(sys::CMsgRef<'a>);
51
52impl AncillaryRef<'_> {
53    /// Returns the level of the control message.
54    pub fn level(&self) -> i32 {
55        self.0.level()
56    }
57
58    /// Returns the type of the control message.
59    pub fn ty(&self) -> i32 {
60        self.0.ty()
61    }
62
63    /// Returns the length of the control message.
64    #[allow(clippy::len_without_is_empty)]
65    pub fn len(&self) -> usize {
66        self.0.len() as _
67    }
68
69    /// Returns a copy of the data in the control message.
70    pub fn data<T: AncillaryData>(&self) -> Result<T, CodecError> {
71        self.0.decode_data()
72    }
73}
74
75/// An iterator for ancillary (control) messages.
76pub struct AncillaryIter<'a> {
77    inner: sys::CMsgIter,
78    buffer: &'a [u8],
79}
80
81impl<'a> AncillaryIter<'a> {
82    /// Create [`AncillaryIter`] with the given buffer.
83    ///
84    /// # Panics
85    ///
86    /// This function will panic if the buffer is too short or not properly
87    /// aligned.
88    ///
89    /// # Safety
90    ///
91    /// The buffer should contain valid control messages.
92    pub unsafe fn new(buffer: &'a [u8]) -> Self {
93        Self {
94            inner: sys::CMsgIter::new(buffer.as_ptr(), buffer.len()),
95            buffer,
96        }
97    }
98}
99
100impl<'a> Iterator for AncillaryIter<'a> {
101    type Item = AncillaryRef<'a>;
102
103    fn next(&mut self) -> Option<Self::Item> {
104        unsafe {
105            let cmsg = self.inner.current(self.buffer.as_ptr());
106            self.inner.next(self.buffer.as_ptr());
107            cmsg.map(AncillaryRef)
108        }
109    }
110}
111
112/// Helper to construct ancillary (control) messages.
113pub struct AncillaryBuilder<'a, B: ?Sized> {
114    inner: sys::CMsgIter,
115    buffer: &'a mut B,
116}
117
118impl<'a, B: IoBufMut + ?Sized> AncillaryBuilder<'a, B> {
119    /// Create [`AncillaryBuilder`] with the given buffer. The buffer will be
120    /// cleared on creation.
121    ///
122    /// # Panics
123    ///
124    /// This function will panic if the buffer is too short or not properly
125    /// aligned.
126    pub fn new(buffer: &'a mut B) -> Self {
127        // SAFETY: always safe to make it empty.
128        unsafe { buffer.set_len(0) };
129        let slice = buffer.ensure_init();
130        let inner = sys::CMsgIter::new(slice.as_ptr(), slice.len());
131        Self { inner, buffer }
132    }
133
134    /// Append a control message into the buffer.
135    pub fn push<T: AncillaryData>(
136        &mut self,
137        level: i32,
138        ty: i32,
139        value: &T,
140    ) -> Result<(), CodecError> {
141        if !self.inner.is_space_enough(T::SIZE) {
142            return Err(CodecError::BufferTooSmall);
143        }
144
145        // SAFETY: method `new` guarantees the buffer is zeroed and properly aligned,
146        // and we have checked the space.
147        let mut cmsg = unsafe { self.inner.current_mut(self.buffer.buf_mut_ptr().cast()) }
148            .expect("sufficient space");
149        cmsg.set_level(level);
150        cmsg.set_ty(ty);
151        unsafe {
152            self.buffer.advance(cmsg.encode_data(value)?);
153        }
154
155        unsafe { self.inner.next(self.buffer.buf_mut_ptr().cast()) };
156
157        Ok(())
158    }
159}
160
161/// A fixed-size, stack-allocated buffer for ancillary (control) messages.
162///
163/// Properly aligned for the platform's control message header type
164/// (`cmsghdr` on Unix, `CMSGHDR` on Windows), so it can be passed directly
165/// to [`AncillaryIter`] and [`AncillaryBuilder`].
166pub struct AncillaryBuf<const N: usize> {
167    inner: [u8; N],
168    len: usize,
169    _align: [sys::cmsghdr; 0],
170}
171
172impl<const N: usize> AncillaryBuf<N> {
173    /// Create a new zeroed [`AncillaryBuf`].
174    pub fn new() -> Self {
175        Self {
176            inner: [0u8; N],
177            len: 0,
178            _align: [],
179        }
180    }
181
182    /// Create [`AncillaryBuilder`] with this buffer. The buffer will be zeroed
183    /// on creation.
184    ///
185    /// # Panics
186    ///
187    /// This function will panic if this buffer is too short.
188    pub fn builder(&mut self) -> AncillaryBuilder<'_, Self> {
189        AncillaryBuilder::new(self)
190    }
191}
192
193impl<const N: usize> Default for AncillaryBuf<N> {
194    fn default() -> Self {
195        Self::new()
196    }
197}
198
199impl<const N: usize> IoBuf for AncillaryBuf<N> {
200    fn as_init(&self) -> &[u8] {
201        &self.inner[..self.len]
202    }
203}
204
205impl<const N: usize> SetLen for AncillaryBuf<N> {
206    unsafe fn set_len(&mut self, len: usize) {
207        debug_assert!(len <= N);
208        self.len = len;
209    }
210}
211
212impl<const N: usize> IoBufMut for AncillaryBuf<N> {
213    fn as_uninit(&mut self) -> &mut [MaybeUninit<u8>] {
214        self.inner.as_uninit()
215    }
216}
217
218impl<const N: usize> Deref for AncillaryBuf<N> {
219    type Target = [u8];
220
221    fn deref(&self) -> &Self::Target {
222        &self.inner[0..self.len]
223    }
224}
225
226impl<const N: usize> DerefMut for AncillaryBuf<N> {
227    fn deref_mut(&mut self) -> &mut Self::Target {
228        &mut self.inner[0..self.len]
229    }
230}
231
232/// Returns the buffer size required to hold one ancillary message carrying a
233/// value of type `T`.
234///
235/// This is the platform-appropriate equivalent of `CMSG_SPACE(T::SIZE)` on
236/// Unix or `WSA_CMSG_SPACE(T::SIZE)` on Windows, and can be used as a const
237/// generic argument for [`AncillaryBuf`].
238pub const fn ancillary_space<T: AncillaryData>() -> usize {
239    // SAFETY: CMSG_SPACE is always safe
240    #[allow(clippy::unnecessary_cast)]
241    unsafe {
242        sys::CMSG_SPACE(T::SIZE as _) as usize
243    }
244}
245
246/// Error that can occur when encoding or decoding ancillary data.
247#[derive(Debug)]
248pub enum CodecError {
249    /// The provided buffer is too small to hold the encoded data.
250    BufferTooSmall,
251    /// Another error occurred during encoding or decoding.
252    Other(Box<dyn std::error::Error + Send + Sync + 'static>),
253}
254
255impl CodecError {
256    /// Create a new [`CodecError::Other`] from any error type.
257    pub fn other(error: impl Into<Box<dyn std::error::Error + Send + Sync + 'static>>) -> Self {
258        Self::Other(error.into())
259    }
260
261    /// Attempt to downcast the error to a concrete type.
262    ///
263    /// Returns `Some(&T)` if the error is of type `T`, otherwise `None`.
264    pub fn downcast_ref<T: std::error::Error + 'static>(&self) -> Option<&T> {
265        match self {
266            Self::Other(e) => e.downcast_ref(),
267            _ => None,
268        }
269    }
270
271    /// Attempt to downcast the error to a concrete type.
272    ///
273    /// Returns `Some(&mut T)` if the error is of type `T`, otherwise `None`.
274    pub fn downcast_mut<T: std::error::Error + 'static>(&mut self) -> Option<&mut T> {
275        match self {
276            Self::Other(e) => e.downcast_mut(),
277            _ => None,
278        }
279    }
280}
281
282impl std::fmt::Display for CodecError {
283    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
284        match self {
285            Self::BufferTooSmall => write!(f, "buffer too small for encoding/decoding"),
286            Self::Other(e) => write!(f, "codec error: {}", e),
287        }
288    }
289}
290
291impl std::error::Error for CodecError {
292    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
293        match self {
294            Self::Other(e) => Some(e.as_ref()),
295            _ => None,
296        }
297    }
298}
299
300/// Trait for types that can be encoded and decoded as ancillary data payloads.
301///
302/// This trait enables a type to be used as the data payload in control messages
303/// (ancillary data). Types implementing this trait can be passed to
304/// [`AncillaryBuilder::push`] and retrieved via [`AncillaryRef::data`].
305///
306/// # Built-in Implementations
307///
308/// This trait is implemented for the following platform-specific types:
309///
310/// - Unix: `libc::in_addr`, `libc::in_pktinfo`, `libc::in6_pktinfo`
311/// - Windows: `IN_PKTINFO`, `IN6_PKTINFO`
312///
313/// When the `bytemuck` feature is enabled, this trait is also automatically
314/// implemented for types that implement [`bytemuck_ext::BitwiseAncillaryData`]:
315///
316/// - Primitive types: `()`, `u8`, `u16`, `u32`, `u64`, `u128`, `usize`, `i8`,
317///   `i16`, `i32`, `i64`, `i128`, `isize`, `f32`, `f64`
318/// - Fixed-size arrays of the above types (up to size 512)
319///
320/// For custom types with the `bytemuck` feature enabled, you can implement
321/// [`bytemuck_ext::BitwiseAncillaryData`] to automatically get
322/// [`AncillaryData`] (see [`bytemuck_ext`] for details). Otherwise, you must
323/// manually implement this trait with custom encoding/decoding logic.
324///
325/// # Example
326///
327/// ```
328/// use std::mem::MaybeUninit;
329///
330/// use compio_io::ancillary::{AncillaryData, CodecError};
331///
332/// struct MyData {
333///     value: u32,
334/// }
335///
336/// impl AncillaryData for MyData {
337///     const SIZE: usize = std::mem::size_of::<u32>();
338///
339///     fn encode(&self, buffer: &mut [MaybeUninit<u8>]) -> Result<(), CodecError> {
340///         if buffer.len() < Self::SIZE {
341///             return Err(CodecError::BufferTooSmall);
342///         }
343///         let bytes = self.value.to_ne_bytes();
344///         for (i, &byte) in bytes.iter().enumerate() {
345///             buffer[i] = MaybeUninit::new(byte);
346///         }
347///         Ok(())
348///     }
349///
350///     fn decode(buffer: &[u8]) -> Result<Self, CodecError> {
351///         if buffer.len() < Self::SIZE {
352///             return Err(CodecError::BufferTooSmall);
353///         }
354///         let mut bytes = [0u8; 4];
355///         bytes.copy_from_slice(&buffer[..4]);
356///         Ok(MyData {
357///             value: u32::from_ne_bytes(bytes),
358///         })
359///     }
360/// }
361/// ```
362pub trait AncillaryData: Sized {
363    /// The size in bytes of the encoded representation.
364    ///
365    /// This defaults to `std::mem::size_of::<Self>()` but can be overridden
366    /// for types with custom encoding.
367    const SIZE: usize = std::mem::size_of::<Self>();
368
369    /// Encode this value into the provided buffer.
370    ///
371    /// # Errors
372    ///
373    /// Returns [`CodecError::BufferTooSmall`] if the buffer is too small to
374    /// hold the encoded data, or [`CodecError::Other`] for other encoding
375    /// errors.
376    fn encode(&self, buffer: &mut [MaybeUninit<u8>]) -> Result<(), CodecError>;
377
378    /// Decode a value from the provided buffer.
379    ///
380    /// # Errors
381    ///
382    /// Returns [`CodecError::BufferTooSmall`] if the buffer is too small,
383    /// or [`CodecError::Other`] for other decoding errors.
384    fn decode(buffer: &[u8]) -> Result<Self, CodecError>;
385}
386
387unsafe fn copy_to_bytes<T: AncillaryData>(
388    src: &T,
389    dest: &mut [MaybeUninit<u8>],
390) -> Result<(), CodecError> {
391    if dest.len() < T::SIZE {
392        return Err(CodecError::BufferTooSmall);
393    }
394    unsafe {
395        ptr::copy_nonoverlapping::<u8>(src as *const T as _, dest.as_mut_ptr() as _, T::SIZE);
396    }
397    Ok(())
398}
399
400unsafe fn copy_from_bytes<T: AncillaryData>(src: &[u8]) -> Result<T, CodecError> {
401    if src.len() < T::SIZE {
402        return Err(CodecError::BufferTooSmall);
403    }
404    let src_ptr = src.as_ptr() as *const T;
405    unsafe { Ok(ptr::read_unaligned(src_ptr)) }
406}