vpp_plugin/vlibapi/
mod.rs

1//! VPP API library
2//!
3//! Traits, types and helpers for working with API messages and client registrations.
4
5use std::{
6    marker::PhantomData,
7    mem::{self, MaybeUninit},
8    ops::{Deref, DerefMut},
9    ptr::{self, NonNull},
10};
11
12use crate::{
13    bindings::{
14        vl_api_helper_client_index_to_registration, vl_api_helper_send_msg, vl_api_registration_t,
15        vl_msg_api_alloc, vl_msg_api_free,
16    },
17    vlib::BarrierHeldMainRef,
18};
19
20/// An owned VPP message buffer containing a `T`.
21///
22/// The message can be sent to a client using [`Registration::send_message`].
23///
24/// Important invariant:
25///
26/// - `T` must have an alignment of 1 (e.g. by `#[repr(packed)]`)
27pub struct Message<T> {
28    pointer: NonNull<T>,
29}
30
31impl<T> Message<T> {
32    /// Allocate a VPP message and initialise it by copying `value` into the
33    /// newly-allocated buffer.
34    ///
35    /// # Panics
36    ///
37    /// Panics if `align_of::<T>() != 1` because the VPP API message allocator does not provide
38    /// alignment guarantees; generated message structs are expected to be packed.
39    pub fn new(value: T) -> Self {
40        if std::mem::align_of::<T>() != 1 {
41            // It's unclear what alignment guarantees vpp gives. Possibly the memory is aligned
42            // to align_of(msghdr_t), but play it safe and required packed types -
43            // vpp-plugin-api-gen generated types always have this anyway.
44            panic!("Messages must only contain #[repr(packed)] types");
45        }
46
47        // SAFETY: `vl_msg_api_alloc` returns a pointer to at least `size_of::<T>()` bytes (or
48        // null on allocation failure). We have asserted `align_of::<T>() == 1` so the cast to
49        // `*mut T` is valid. It is safe to use `NonNull::new_unchecked` because the VPP
50        // allocation cannot fail and instead aborts on allocation failures.
51        unsafe {
52            let mut me = Self {
53                pointer: NonNull::new_unchecked(
54                    vl_msg_api_alloc(std::mem::size_of::<T>() as i32) as *mut T
55                ),
56            };
57            ptr::copy_nonoverlapping(&value, me.pointer.as_mut(), 1);
58            me
59        }
60    }
61
62    /// Allocate an uninitialised VPP message buffer for `T`.
63    ///
64    /// This returns a `Message<MaybeUninit<T>>`. Use [`Self::write`] or [`Self::assume_init`]
65    /// after manually initialising the contents.
66    ///
67    ///
68    /// # Panics
69    ///
70    /// Panics if `align_of::<T>() != 1` for the same reason as `new`.
71    pub fn new_uninit() -> Message<MaybeUninit<T>> {
72        if std::mem::align_of::<T>() != 1 {
73            // It's unclear what alignment guarantees vpp gives. Possibly the memory is aligned
74            // to align_of(msghdr_t), but play it safe and required packed types -
75            // vpp-plugin-api-gen generated types always have this anyway.
76            panic!("Messages must only contain #[repr(packed)] types");
77        }
78
79        // SAFETY: `vl_msg_api_alloc` returns a pointer to at least `size_of::<MaybeUninit<T>>()`
80        // bytes. Casting that pointer to `*mut MaybeUninit<T>` is valid as the buffer is
81        // uninitialised but suitably sized. It is safe to use `NonNull::new_unchecked` because
82        // the VPP allocation cannot fail and instead aborts on allocation failures.
83        unsafe {
84            Message {
85                pointer: NonNull::new_unchecked(vl_msg_api_alloc(
86                    std::mem::size_of::<MaybeUninit<T>>() as i32,
87                ) as *mut MaybeUninit<T>),
88            }
89        }
90    }
91
92    /// Consume the `Message` and return the raw pointer to the underlying buffer
93    ///
94    /// The returned pointer becomes the caller's responsibility. The `Message` destructor will
95    /// not run for `m` and the underlying buffer will not be freed by Rust; callers must ensure
96    /// the buffer is eventually freed (for example by passing it to VPP or calling
97    /// `vl_msg_api_free`).
98    ///
99    /// Not a method on `Message` to avoid clashing with application methods of the same name on
100    /// the underlying type.
101    pub fn into_raw(m: Self) -> *mut T {
102        let m = mem::ManuallyDrop::new(m);
103        m.pointer.as_ptr()
104    }
105}
106
107impl<T> Message<MaybeUninit<T>> {
108    /// Convert a `Message<MaybeUninit<T>>` into a `Message<T>` without performing any
109    /// initialisation checks
110    ///
111    /// # Safety
112    ///
113    /// The caller must ensure that the underlying buffer is fully initialised for `T`. If the
114    /// memory is not properly initialised, using the resulting `Message<T>` is undefined
115    /// behaviour.
116    pub unsafe fn assume_init(self) -> Message<T> {
117        let pointer = Message::into_raw(self);
118        Message {
119            pointer: NonNull::new_unchecked(pointer as *mut T),
120        }
121    }
122
123    /// Initialise the previously-uninitialised buffer with `value` and return the initialised
124    /// `Message<T>`
125    pub fn write(mut self, value: T) -> Message<T> {
126        // SAFETY: We have exclusive ownership of the allocated buffer for
127        // `self`. Writing `value` into the `MaybeUninit<T>` buffer
128        // initialises it, after which `assume_init` converts the message to
129        // `Message<T>`.
130        unsafe {
131            (*self).write(value);
132            self.assume_init()
133        }
134    }
135}
136
137impl<T> Deref for Message<T> {
138    type Target = T;
139
140    fn deref(&self) -> &Self::Target {
141        // SAFETY: `self.pointer` was allocated by `vl_msg_api_alloc` and points to a valid,
142        // initialised `T` for the lifetime of `&self`.
143        unsafe { self.pointer.as_ref() }
144    }
145}
146
147impl<T> DerefMut for Message<T> {
148    fn deref_mut(&mut self) -> &mut Self::Target {
149        // SAFETY: `self.pointer` was allocated by `vl_msg_api_alloc` and we hold exclusive access
150        // via `&mut self`, so returning a mutable reference to the inner `T` is valid.
151        unsafe { self.pointer.as_mut() }
152    }
153}
154
155impl<T: Default> Default for Message<T> {
156    fn default() -> Self {
157        Self::new_uninit().write(Default::default())
158    }
159}
160
161impl<T> Drop for Message<T> {
162    fn drop(&mut self) {
163        // SAFETY: We own the underlying buffer and the memory is considered initialised for `T`
164        // at time of drop. It's therefore safe to drop the contained `T` and free the buffer
165        // with the VPP message API free function.
166        unsafe {
167            ptr::drop_in_place(self.pointer.as_ptr());
168            vl_msg_api_free(self.pointer.as_ptr().cast());
169        }
170    }
171}
172
173impl<T> From<T> for Message<T> {
174    fn from(value: T) -> Self {
175        Self::new(value)
176    }
177}
178
179/// Trait used by generated message types that require endian conversions.
180///
181/// Implementations should swap fields between host and network byte order.
182pub trait EndianSwap {
183    /// Swap the endianness of the message in-place.
184    ///
185    /// `to_net == true` indicates conversion from host to network order.
186    fn endian_swap(&mut self, to_net: bool);
187}
188
189/// Registration state for the VPP side of an API client
190///
191/// A `&mut Registration` corresponds to a C `vl_api_registration *`.
192///
193/// Use [`RegistrationScope::from_client_index`] to obtain a mutable reference.
194#[repr(transparent)]
195pub struct Registration(foreign_types::Opaque);
196
197impl Registration {
198    /// Construct a `&mut Registration` from a raw `vl_api_registration_t` pointer.
199    ///
200    /// # Safety
201    ///
202    /// - `ptr` must be a valid, non-null pointer to a `vl_api_registration_t`.
203    /// - The caller must ensure exclusive mutable access for the returned lifetime `'a` (no other
204    ///   references or concurrent uses may alias the same underlying registration for the
205    ///   duration of the returned borrow).
206    /// - The pointer must remain valid for the returned lifetime and must not be freed or
207    ///   invalidated while the borrow is active.
208    pub unsafe fn from_ptr_mut<'a>(ptr: *mut vl_api_registration_t) -> &'a mut Self {
209        &mut *(ptr as *mut _)
210    }
211
212    /// Return the raw `vl_api_registration_t` pointer for this `Registration`.
213    pub fn as_ptr(&self) -> *mut vl_api_registration_t {
214        self as *const _ as *mut _
215    }
216
217    /// Send a message to the registration.
218    ///
219    /// This consumes `message` and transfers ownership of the underlying buffer to VPP.
220    pub fn send_message<T>(&mut self, message: Message<T>) {
221        // SAFETY: `self.as_ptr()` returns a raw `vl_api_registration_t` pointer that is valid
222        // for the duration of this call. `Message::into_raw` transfers ownership of the message
223        // buffer and yields a pointer that is safe to pass to the C API; the C API takes
224        // ownership of the buffer. `vl_api_helper_send_msg` is called with valid pointers.
225        unsafe {
226            vl_api_helper_send_msg(self.as_ptr(), Message::into_raw(message).cast());
227        }
228    }
229}
230
231/// Scope helper used to obtain short-lived `&mut Registration` borrows.
232///
233/// This enforces that `Registration` references obtained cannot be retained beyond the
234/// `registration_scope` function call
235pub struct RegistrationScope<'scope>(PhantomData<&'scope ()>);
236
237impl<'scope> RegistrationScope<'scope> {
238    /// Look up a `Registration` by VPP client index.
239    ///
240    /// Returns `Some(&mut Registration)` when the client index corresponds to a current
241    /// registration, or `None` if no registration exists for that index.
242    // FIXME: change &mut to & since we cannot prevent aliases here
243    pub fn from_client_index(
244        &self,
245        _vm: &BarrierHeldMainRef,
246        client_index: u32,
247    ) -> Option<&'scope mut Registration> {
248        // SAFETY: `vl_api_helper_client_index_to_registration` returns either a null pointer or
249        // a valid pointer to a `vl_api_registration_t` that lives as long as the corresponding
250        // client registration in VPP. The lifetime of the returned reference ensures the caller
251        // cannot retain that reference beyond the intended scope.
252        unsafe {
253            let ptr = vl_api_helper_client_index_to_registration(client_index.to_be());
254            if ptr.is_null() {
255                None
256            } else {
257                Some(Registration::from_ptr_mut(ptr))
258            }
259        }
260    }
261}
262
263/// Execute a closure with a temporary `RegistrationScope`.
264///
265/// Used to ensure any `&mut Registration` borrows that are obtained are tied to the lifetime of
266/// the closure and cannot accidentally escape.
267pub fn registration_scope<F, T>(f: F) -> T
268where
269    F: for<'scope> FnOnce(&'scope RegistrationScope<'scope>) -> T,
270{
271    let scope = RegistrationScope(PhantomData);
272    f(&scope)
273}