Mercurial > crates > nonstick
comparison libpam-sys/libpam-sys-helpers/src/lib.rs @ 148:4b3a5095f68c
Move libpam-sys helpers into their own library.
- Renames libpam-sys-helpers to libpam-sys-consts.
- Moves libpam-sys-helpers::helpers into libpam-sys-helpers,
which moves them completely out of libpam-sys's dependency chain.
- Moves the aliases from libpam-sys into libpam-sys::aliases.
| author | Paul Fisher <paul@pfish.zone> |
|---|---|
| date | Mon, 07 Jul 2025 12:11:43 -0400 |
| parents | libpam-sys/libpam-sys-helpers/src/memory.rs@ebb71a412b58 |
| children | 14708d9061dc |
comparison
equal
deleted
inserted
replaced
| 147:4d7333337569 | 148:4b3a5095f68c |
|---|---|
| 1 #![doc = include_str!("../README.md")] | 1 //! This package contains helpers to deal with memory management and |
| 2 //! | 2 //! annoying type stuff in `libpam-sys` (and LibPAM in general). |
| 3 //! ## Current implementation | 3 |
| 4 //! | 4 use std::error::Error; |
| 5 //! This documentation was built based on the | 5 use std::marker::{PhantomData, PhantomPinned}; |
| 6 #![doc = concat!("**", env!("LIBPAMSYS_IMPL"), "** implementation.")] | 6 use std::mem::ManuallyDrop; |
| 7 | 7 use std::ptr::NonNull; |
| 8 pub mod constants; | 8 use std::{any, fmt, mem, ptr, slice}; |
| 9 pub mod memory; | 9 // Type aliases: |
| 10 | 10 |
| 11 /// Information about the PAM implementation you're using right now. | 11 // Memory management |
| 12 /// | 12 |
| 13 /// This module contains constants and values that can be used at build-script, | 13 /// A pointer-to-pointer-to-message container for PAM's conversation callback. |
| 14 /// compile, and run time to determine what PAM implementation you're using. | 14 /// |
| 15 /// | 15 /// The PAM conversation callback requires a pointer to a pointer of |
| 16 /// ## Always available | 16 /// `pam_message`s. Linux-PAM handles this differently than all other |
| 17 /// | 17 /// PAM implementations (including the X/SSO PAM standard). |
| 18 /// [`PamImpl::CURRENT`] will tell you what version of PAM you're using. | 18 /// |
| 19 /// It can be imported in any Rust code, from build scripts to runtime. | 19 /// X/SSO appears to specify a pointer-to-pointer-to-array: |
| 20 /// | 20 /// |
| 21 /// ## Compile time | 21 /// ```text |
| 22 /// | 22 /// points to ┌────────────┐ ╔═ Message[] ═╗ |
| 23 /// Use [`enable_pam_impl_cfg`] in your `build.rs` to generate custom `#[cfg]`s | 23 /// messages ┄┄┄┄┄┄┄┄┄┄> │ *messages ┄┼┄┄┄┄┄> ║ style ║ |
| 24 /// for conditional compilation based on PAM implementation. | 24 /// └────────────┘ ║ data ┄┄┄┄┄┄┄╫┄┄> ... |
| 25 /// | 25 /// ╟─────────────╢ |
| 26 /// This will set the current `pam_impl` as well as registering all known | 26 /// ║ style ║ |
| 27 /// PAM implementations with `rustc-check-cfg` to get cfg-checking. | 27 /// ║ data ┄┄┄┄┄┄┄╫┄┄> ... |
| 28 /// | 28 /// ╟─────────────╢ |
| 29 /// The names that appear in the `cfg` variables are the same as the values | 29 /// ║ ... ║ |
| 30 /// in the [`PamImpl`] enum. | |
| 31 /// | |
| 32 /// ``` | 30 /// ``` |
| 33 /// #[cfg(pam_impl = "OpenPam")] | 31 /// |
| 34 /// fn openpam_specific_func(handle: *const libpam_sys::pam_handle) { | 32 /// whereas Linux-PAM uses an `**argv`-style pointer-to-array-of-pointers: |
| 35 /// let environ = libpam_sys::pam_getenvlist(handle); | 33 /// |
| 36 /// // ... | 34 /// ```text |
| 37 /// libpam_sys::openpam_free_envlist() | 35 /// points to ┌──────────────┐ ╔═ Message ═╗ |
| 38 /// } | 36 /// messages ┄┄┄┄┄┄┄┄┄┄> │ messages[0] ┄┼┄┄┄┄> ║ style ║ |
| 39 /// | 37 /// │ messages[1] ┄┼┄┄┄╮ ║ data ┄┄┄┄┄╫┄┄> ... |
| 40 /// // This will give you a warning since "UnknownImpl" is not in the cfg. | 38 /// │ ... │ ┆ ╚═══════════╝ |
| 41 /// #[cfg(not(pam_impl = "UnknownImpl"))] | 39 /// ┆ |
| 42 /// fn do_something() { | 40 /// ┆ ╔═ Message ═╗ |
| 43 /// // ... | 41 /// ╰┄┄> ║ style ║ |
| 44 /// } | 42 /// ║ data ┄┄┄┄┄╫┄┄> ... |
| 43 /// ╚═══════════╝ | |
| 45 /// ``` | 44 /// ``` |
| 46 /// | 45 /// |
| 47 /// The [`pam_impl_name!`] macro will expand to this same value, currently | 46 /// Because the `messages` remain owned by the application which calls into PAM, |
| 48 #[doc = concat!("`", env!("LIBPAMSYS_IMPL"), "`.")] | 47 /// we can solve this with One Simple Trick: make the intermediate list point |
| 49 pub mod pam_impl; | 48 /// into the same array: |
| 50 #[doc(inline)] | 49 /// |
| 51 pub use pam_impl::*; | 50 /// ```text |
| 51 /// points to ┌──────────────┐ ╔═ Message[] ═╗ | |
| 52 /// messages ┄┄┄┄┄┄┄┄┄┄> │ messages[0] ┄┼┄┄┄┄> ║ style ║ | |
| 53 /// │ messages[1] ┄┼┄┄╮ ║ data ┄┄┄┄┄┄┄╫┄┄> ... | |
| 54 /// │ ... │ ┆ ╟─────────────╢ | |
| 55 /// ╰┄> ║ style ║ | |
| 56 /// ║ data ┄┄┄┄┄┄┄╫┄┄> ... | |
| 57 /// ╟─────────────╢ | |
| 58 /// ║ ... ║ | |
| 59 /// | |
| 60 /// ``` | |
| 61 #[derive(Debug)] | |
| 62 pub struct PtrPtrVec<T> { | |
| 63 data: Vec<T>, | |
| 64 pointers: Vec<*const T>, | |
| 65 } | |
| 66 | |
| 67 // Since this is a wrapper around a Vec with no dangerous functionality*, | |
| 68 // this can be Send and Sync provided the original Vec is. | |
| 69 // | |
| 70 // * It will only become unsafe when the user dereferences a pointer or sends it | |
| 71 // to an unsafe function. | |
| 72 unsafe impl<T> Send for PtrPtrVec<T> where Vec<T>: Send {} | |
| 73 unsafe impl<T> Sync for PtrPtrVec<T> where Vec<T>: Sync {} | |
| 74 | |
| 75 impl<T> PtrPtrVec<T> { | |
| 76 /// Takes ownership of the given Vec and creates a vec of pointers to it. | |
| 77 pub fn new(data: Vec<T>) -> Self { | |
| 78 let start = data.as_ptr(); | |
| 79 // We do this slightly tricky little dance to satisfy Miri: | |
| 80 // | |
| 81 // A pointer extracted from a reference can only legally access | |
| 82 // that reference's memory. This means that if we say: | |
| 83 // pointers[0] = &data[0] as *const T; | |
| 84 // we can't traverse through pointers[0] to reach data[1], | |
| 85 // we can only use pointers[1]. | |
| 86 // | |
| 87 // However, if we use the start-of-vec pointer from the `data` vector, | |
| 88 // its "provenance"* is valid for the entire array (even if the address | |
| 89 // of the pointer is the same). This avoids some behavior which is | |
| 90 // technically undefined. While the CPU sees no difference between | |
| 91 // those two pointers, the compiler is allowed to make optimizations | |
| 92 // based on that provenance (even if, in this case, it isn't likely | |
| 93 // to do so). | |
| 94 // | |
| 95 // data.as_ptr() points here, and is valid for the whole Vec. | |
| 96 // ┃ | |
| 97 // ┠─────────────────╮ | |
| 98 // ┌─────┬─────┬─────┐ | |
| 99 // data │ [0] │ [1] │ [2] │ | |
| 100 // └─────┴─────┴─────┘ | |
| 101 // ┠─────╯ ┊ | |
| 102 // ┃ ┊ ┊ | |
| 103 // (&data[0] as *const T) points to the same place, but is valid | |
| 104 // only for that 0th element. | |
| 105 // ┊ ┊ | |
| 106 // ┠─────╯ | |
| 107 // ┃ | |
| 108 // (&data[1] as *const T) points here, and is only valid | |
| 109 // for that element. | |
| 110 // | |
| 111 // We only have to do this for pointers[0] because only that pointer | |
| 112 // is used for accessing elements other than data[0] (in XSSO). | |
| 113 // | |
| 114 // * "provenance" is kind of like if every pointer in your program | |
| 115 // remembered where it came from and, based on that, it had an implied | |
| 116 // memory range it was valid for, separate from its address. | |
| 117 // https://doc.rust-lang.org/std/ptr/#provenance | |
| 118 // (It took a long time for me to understand this.) | |
| 119 let mut pointers = Vec::with_capacity(data.len()); | |
| 120 // Ensure the 0th pointer has provenance from the entire vec | |
| 121 // (even though it's numerically identical to &data[0] as *const T). | |
| 122 pointers.push(start); | |
| 123 // The 1st and everything thereafter only need to have the provenance | |
| 124 // of their own memory. | |
| 125 pointers.extend(data[1..].iter().map(|r| r as *const T)); | |
| 126 Self { data, pointers } | |
| 127 } | |
| 128 | |
| 129 /// Gives you back your Vec. | |
| 130 pub fn into_inner(self) -> Vec<T> { | |
| 131 self.data | |
| 132 } | |
| 133 | |
| 134 /// Gets a pointer-to-pointer suitable for passing into the Conversation. | |
| 135 pub fn as_ptr<Dest>(&self) -> *const *const Dest { | |
| 136 Self::assert_size::<Dest>(); | |
| 137 self.pointers.as_ptr().cast::<*const Dest>() | |
| 138 } | |
| 139 | |
| 140 /// Iterates over a Linux-PAM–style pointer-to-array-of-pointers. | |
| 141 /// | |
| 142 /// # Safety | |
| 143 /// | |
| 144 /// `ptr_ptr` must be a valid pointer to an array of pointers, | |
| 145 /// there must be at least `count` valid pointers in the array, | |
| 146 /// and each pointer in that array must point to a valid `T`. | |
| 147 #[deprecated = "use [`Self::iter_over`] instead, unless you really need this specific version"] | |
| 148 #[allow(dead_code)] | |
| 149 pub unsafe fn iter_over_linux<'a, Src>( | |
| 150 ptr_ptr: *const *const Src, | |
| 151 count: usize, | |
| 152 ) -> impl Iterator<Item = &'a T> | |
| 153 where | |
| 154 T: 'a, | |
| 155 { | |
| 156 Self::assert_size::<Src>(); | |
| 157 slice::from_raw_parts(ptr_ptr.cast::<&T>(), count) | |
| 158 .iter() | |
| 159 .copied() | |
| 160 } | |
| 161 | |
| 162 /// Iterates over an X/SSO–style pointer-to-pointer-to-array. | |
| 163 /// | |
| 164 /// # Safety | |
| 165 /// | |
| 166 /// You must pass a valid pointer to a valid pointer to an array, | |
| 167 /// there must be at least `count` elements in the array, | |
| 168 /// and each value in that array must be a valid `T`. | |
| 169 #[deprecated = "use [`Self::iter_over`] instead, unless you really need this specific version"] | |
| 170 #[allow(dead_code)] | |
| 171 pub unsafe fn iter_over_xsso<'a, Src>( | |
| 172 ptr_ptr: *const *const Src, | |
| 173 count: usize, | |
| 174 ) -> impl Iterator<Item = &'a T> | |
| 175 where | |
| 176 T: 'a, | |
| 177 { | |
| 178 Self::assert_size::<Src>(); | |
| 179 slice::from_raw_parts(*ptr_ptr.cast(), count).iter() | |
| 180 } | |
| 181 | |
| 182 /// Iterates over a PAM message list appropriate to your system's impl. | |
| 183 /// | |
| 184 /// This selects the correct pointer/array structure to use for a message | |
| 185 /// that was given to you by your system. | |
| 186 /// | |
| 187 /// # Safety | |
| 188 /// | |
| 189 /// `ptr_ptr` must point to a valid message list, there must be at least | |
| 190 /// `count` messages in the list, and all messages must be a valid `Src`. | |
| 191 #[allow(deprecated)] | |
| 192 pub unsafe fn iter_over<'a, Src>( | |
| 193 ptr_ptr: *const *const Src, | |
| 194 count: usize, | |
| 195 ) -> impl Iterator<Item = &'a T> | |
| 196 where | |
| 197 T: 'a, | |
| 198 { | |
| 199 #[cfg(pam_impl = "LinuxPam")] | |
| 200 return Self::iter_over_linux(ptr_ptr, count); | |
| 201 #[cfg(not(pam_impl = "LinuxPam"))] | |
| 202 return Self::iter_over_xsso(ptr_ptr, count); | |
| 203 } | |
| 204 | |
| 205 fn assert_size<That>() { | |
| 206 assert_eq!( | |
| 207 mem::size_of::<T>(), | |
| 208 mem::size_of::<That>(), | |
| 209 "type {t} is not the size of {that}", | |
| 210 t = any::type_name::<T>(), | |
| 211 that = any::type_name::<That>(), | |
| 212 ); | |
| 213 } | |
| 214 } | |
| 215 | |
| 216 /// Error returned when attempting to allocate a buffer that is too big. | |
| 217 /// | |
| 218 /// This is specifically used in [`OwnedBinaryPayload`] when you try to allocate | |
| 219 /// a message larger than 2<sup>32</sup> bytes. | |
| 220 #[derive(Debug, PartialEq)] | |
| 221 pub struct TooBigError { | |
| 222 pub size: usize, | |
| 223 pub max: usize, | |
| 224 } | |
| 225 | |
| 226 impl Error for TooBigError {} | |
| 227 | |
| 228 impl fmt::Display for TooBigError { | |
| 229 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
| 230 write!( | |
| 231 f, | |
| 232 "can't allocate a message of {size} bytes (max {max})", | |
| 233 size = self.size, | |
| 234 max = self.max | |
| 235 ) | |
| 236 } | |
| 237 } | |
| 238 | |
| 239 /// A trait wrapping memory management. | |
| 240 /// | |
| 241 /// This is intended to allow you to bring your own allocator for | |
| 242 /// [`OwnedBinaryPayload`]s. | |
| 243 /// | |
| 244 /// For an implementation example, see the implementation of this trait | |
| 245 /// for [`Vec`]. | |
| 246 #[allow(clippy::wrong_self_convention)] | |
| 247 pub trait Buffer { | |
| 248 /// Allocates a buffer of `len` elements, filled with the default. | |
| 249 fn allocate(len: usize) -> Self; | |
| 250 | |
| 251 fn as_ptr(this: &Self) -> *const u8; | |
| 252 | |
| 253 /// Returns a slice view of `size` elements of the given memory. | |
| 254 /// | |
| 255 /// # Safety | |
| 256 /// | |
| 257 /// The caller must not request more elements than are allocated. | |
| 258 unsafe fn as_mut_slice(this: &mut Self, len: usize) -> &mut [u8]; | |
| 259 | |
| 260 /// Consumes this ownership and returns a pointer to the start of the arena. | |
| 261 fn into_ptr(this: Self) -> NonNull<u8>; | |
| 262 | |
| 263 /// "Adopts" the memory at the given pointer, taking it under management. | |
| 264 /// | |
| 265 /// Running the operation: | |
| 266 /// | |
| 267 /// ``` | |
| 268 /// # use libpam_sys_helpers::Buffer; | |
| 269 /// # fn test<T: Default, OwnerType: Buffer>(bytes: usize) { | |
| 270 /// let owner = OwnerType::allocate(bytes); | |
| 271 /// let ptr = OwnerType::into_ptr(owner); | |
| 272 /// let owner = unsafe { OwnerType::from_ptr(ptr, bytes) }; | |
| 273 /// # } | |
| 274 /// ``` | |
| 275 /// | |
| 276 /// must be a no-op. | |
| 277 /// | |
| 278 /// # Safety | |
| 279 /// | |
| 280 /// The pointer must be valid, and the caller must provide the exact size | |
| 281 /// of the given arena. | |
| 282 unsafe fn from_ptr(ptr: NonNull<u8>, bytes: usize) -> Self; | |
| 283 } | |
| 284 | |
| 285 impl Buffer for Vec<u8> { | |
| 286 fn allocate(bytes: usize) -> Self { | |
| 287 vec![0; bytes] | |
| 288 } | |
| 289 | |
| 290 fn as_ptr(this: &Self) -> *const u8 { | |
| 291 Vec::as_ptr(this) | |
| 292 } | |
| 293 | |
| 294 unsafe fn as_mut_slice(this: &mut Self, bytes: usize) -> &mut [u8] { | |
| 295 &mut this[..bytes] | |
| 296 } | |
| 297 | |
| 298 fn into_ptr(this: Self) -> NonNull<u8> { | |
| 299 let mut me = ManuallyDrop::new(this); | |
| 300 // SAFETY: a Vec is guaranteed to have a nonzero pointer. | |
| 301 unsafe { NonNull::new_unchecked(me.as_mut_ptr()) } | |
| 302 } | |
| 303 | |
| 304 unsafe fn from_ptr(ptr: NonNull<u8>, bytes: usize) -> Self { | |
| 305 Vec::from_raw_parts(ptr.as_ptr(), bytes, bytes) | |
| 306 } | |
| 307 } | |
| 308 | |
| 309 /// The structure of the "binary message" payload for the `PAM_BINARY_PROMPT` | |
| 310 /// extension from Linux-PAM. | |
| 311 pub struct BinaryPayload { | |
| 312 /// The total byte size of the message, including this header, | |
| 313 /// as u32 in network byte order (big endian). | |
| 314 pub total_bytes_u32be: [u8; 4], | |
| 315 /// A tag used to provide some kind of hint as to what the data is. | |
| 316 /// Its meaning is undefined. | |
| 317 pub data_type: u8, | |
| 318 /// Where the data itself would start, used as a marker to make this | |
| 319 /// not [`Unpin`] (since it is effectively an intrusive data structure | |
| 320 /// pointing to immediately after itself). | |
| 321 pub _marker: PhantomData<PhantomPinned>, | |
| 322 } | |
| 323 | |
| 324 impl BinaryPayload { | |
| 325 /// The most data it's possible to put into a [`BinaryPayload`]. | |
| 326 pub const MAX_SIZE: usize = (u32::MAX - 5) as usize; | |
| 327 | |
| 328 /// Fills in the provided buffer with the given data. | |
| 329 /// | |
| 330 /// This uses [`copy_from_slice`](slice::copy_from_slice) internally, | |
| 331 /// so `buf` must be exactly 5 bytes longer than `data`, or this function | |
| 332 /// will panic. | |
| 333 pub fn fill(buf: &mut [u8], data: &[u8], data_type: u8) { | |
| 334 let ptr: *mut Self = buf.as_mut_ptr().cast(); | |
| 335 // SAFETY: We're given a slice, which always has a nonzero pointer. | |
| 336 let me = unsafe { ptr.as_mut().unwrap_unchecked() }; | |
| 337 me.total_bytes_u32be = u32::to_be_bytes(buf.len() as u32); | |
| 338 me.data_type = data_type; | |
| 339 buf[5..].copy_from_slice(data) | |
| 340 } | |
| 341 | |
| 342 /// The total storage needed for the message, including header. | |
| 343 pub unsafe fn total_bytes(this: *const Self) -> usize { | |
| 344 let header = this.as_ref().unwrap_unchecked(); | |
| 345 u32::from_be_bytes(header.total_bytes_u32be) as usize | |
| 346 } | |
| 347 | |
| 348 /// Gets the total byte buffer of the BinaryMessage stored at the pointer. | |
| 349 /// | |
| 350 /// The returned data slice is borrowed from where the pointer points to. | |
| 351 /// | |
| 352 /// # Safety | |
| 353 /// | |
| 354 /// - The pointer must point to a valid `BinaryPayload`. | |
| 355 /// - The borrowed data must not outlive the pointer's validity. | |
| 356 pub unsafe fn buffer_of<'a>(ptr: *const Self) -> &'a [u8] { | |
| 357 slice::from_raw_parts(ptr.cast(), Self::total_bytes(ptr).max(5)) | |
| 358 } | |
| 359 | |
| 360 /// Gets the contents of the BinaryMessage stored at the given pointer. | |
| 361 /// | |
| 362 /// The returned data slice is borrowed from where the pointer points to. | |
| 363 /// This is a cheap operation and doesn't do *any* copying. | |
| 364 /// | |
| 365 /// We don't take a `&self` reference here because accessing beyond | |
| 366 /// the range of the `Self` data (i.e., beyond the 5 bytes of `self`) | |
| 367 /// is undefined behavior. Instead, you have to pass a raw pointer | |
| 368 /// directly to the data. | |
| 369 /// | |
| 370 /// # Safety | |
| 371 /// | |
| 372 /// - The pointer must point to a valid `BinaryPayload`. | |
| 373 /// - The borrowed data must not outlive the pointer's validity. | |
| 374 pub unsafe fn contents<'a>(ptr: *const Self) -> (&'a [u8], u8) { | |
| 375 let header: &Self = ptr.as_ref().unwrap_unchecked(); | |
| 376 (&Self::buffer_of(ptr)[5..], header.data_type) | |
| 377 } | |
| 378 | |
| 379 /// Zeroes out the data of this payload. | |
| 380 /// | |
| 381 /// # Safety | |
| 382 /// | |
| 383 /// - The pointer must point to a valid `BinaryPayload`. | |
| 384 /// - The binary payload must not be used in the future, | |
| 385 /// since its length metadata is gone and so its buffer is unknown. | |
| 386 pub unsafe fn zero(ptr: *mut Self) { | |
| 387 let size = Self::total_bytes(ptr); | |
| 388 let ptr: *mut u8 = ptr.cast(); | |
| 389 for x in 0..size { | |
| 390 ptr::write_volatile(ptr.byte_add(x), mem::zeroed()) | |
| 391 } | |
| 392 } | |
| 393 } | |
| 394 | |
| 395 /// A binary message owned by some storage. | |
| 396 /// | |
| 397 /// This is an owned, memory-managed version of [`BinaryPayload`]. | |
| 398 /// The `O` type manages the memory where the payload lives. | |
| 399 /// [`Vec<u8>`] is one such manager and can be used when ownership | |
| 400 /// of the data does not need to transit through PAM. | |
| 401 #[derive(Debug)] | |
| 402 pub struct OwnedBinaryPayload<Owner: Buffer>(Owner); | |
| 403 | |
| 404 impl<O: Buffer> OwnedBinaryPayload<O> { | |
| 405 /// Allocates a new OwnedBinaryPayload. | |
| 406 /// | |
| 407 /// This will return a [`TooBigError`] if you try to allocate too much | |
| 408 /// (more than [`BinaryPayload::MAX_SIZE`]). | |
| 409 pub fn new(data: &[u8], type_: u8) -> Result<Self, TooBigError> { | |
| 410 let total_len: u32 = (data.len() + 5).try_into().map_err(|_| TooBigError { | |
| 411 size: data.len(), | |
| 412 max: BinaryPayload::MAX_SIZE, | |
| 413 })?; | |
| 414 let total_len = total_len as usize; | |
| 415 let mut buf = O::allocate(total_len); | |
| 416 // SAFETY: We just allocated this exact size. | |
| 417 BinaryPayload::fill( | |
| 418 unsafe { Buffer::as_mut_slice(&mut buf, total_len) }, | |
| 419 data, | |
| 420 type_, | |
| 421 ); | |
| 422 Ok(Self(buf)) | |
| 423 } | |
| 424 | |
| 425 /// The contents of the buffer. | |
| 426 pub fn contents(&self) -> (&[u8], u8) { | |
| 427 unsafe { BinaryPayload::contents(self.as_ptr()) } | |
| 428 } | |
| 429 | |
| 430 /// The total bytes needed to store this, including the header. | |
| 431 pub fn total_bytes(&self) -> usize { | |
| 432 unsafe { BinaryPayload::buffer_of(Buffer::as_ptr(&self.0).cast()).len() } | |
| 433 } | |
| 434 | |
| 435 /// Unwraps this into the raw storage backing it. | |
| 436 pub fn into_inner(self) -> O { | |
| 437 self.0 | |
| 438 } | |
| 439 | |
| 440 /// Gets a const pointer to the start of the message's buffer. | |
| 441 pub fn as_ptr(&self) -> *const BinaryPayload { | |
| 442 Buffer::as_ptr(&self.0).cast() | |
| 443 } | |
| 444 | |
| 445 /// Consumes ownership of this message and converts it to a raw pointer | |
| 446 /// to the start of the message. | |
| 447 /// | |
| 448 /// To clean this up, you should eventually pass it into [`Self::from_ptr`] | |
| 449 /// with the same `O` ownership type. | |
| 450 pub fn into_ptr(self) -> NonNull<BinaryPayload> { | |
| 451 Buffer::into_ptr(self.0).cast() | |
| 452 } | |
| 453 | |
| 454 /// Takes ownership of the given pointer. | |
| 455 /// | |
| 456 /// # Safety | |
| 457 /// | |
| 458 /// You must provide a valid pointer, allocated by (or equivalent to one | |
| 459 /// allocated by) [`Self::new`]. For instance, passing a pointer allocated | |
| 460 /// by `malloc` to `OwnedBinaryPayload::<Vec<u8>>::from_ptr` is not allowed. | |
| 461 pub unsafe fn from_ptr(ptr: NonNull<BinaryPayload>) -> Self { | |
| 462 Self(O::from_ptr( | |
| 463 ptr.cast(), | |
| 464 BinaryPayload::total_bytes(ptr.as_ptr()), | |
| 465 )) | |
| 466 } | |
| 467 } | |
| 468 | |
| 469 #[cfg(test)] | |
| 470 mod tests { | |
| 471 use super::*; | |
| 472 use std::ptr; | |
| 473 | |
| 474 type VecPayload = OwnedBinaryPayload<Vec<u8>>; | |
| 475 | |
| 476 #[test] | |
| 477 fn test_binary_payload() { | |
| 478 let simple_message = &[0u8, 0, 0, 16, 0xff, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; | |
| 479 let empty = &[0u8; 5]; | |
| 480 | |
| 481 assert_eq!((&[0u8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10][..], 0xff), unsafe { | |
| 482 BinaryPayload::contents(simple_message.as_ptr().cast()) | |
| 483 }); | |
| 484 assert_eq!((&[][..], 0x00), unsafe { | |
| 485 BinaryPayload::contents(empty.as_ptr().cast()) | |
| 486 }); | |
| 487 } | |
| 488 | |
| 489 #[test] | |
| 490 fn test_owned_binary_payload() { | |
| 491 let (data, typ) = ( | |
| 492 &[0, 1, 1, 8, 9, 9, 9, 8, 8, 1, 9, 9, 9, 1, 1, 9, 7, 2, 5, 3][..], | |
| 493 112, | |
| 494 ); | |
| 495 let payload = VecPayload::new(data, typ).unwrap(); | |
| 496 assert_eq!((data, typ), payload.contents()); | |
| 497 let ptr = payload.into_ptr(); | |
| 498 let payload = unsafe { VecPayload::from_ptr(ptr) }; | |
| 499 assert_eq!((data, typ), payload.contents()); | |
| 500 } | |
| 501 | |
| 502 #[test] | |
| 503 #[ignore] | |
| 504 fn test_owned_too_big() { | |
| 505 let data = vec![0xFFu8; 0x1_0000_0001]; | |
| 506 assert_eq!( | |
| 507 TooBigError { | |
| 508 max: 0xffff_fffa, | |
| 509 size: 0x1_0000_0001 | |
| 510 }, | |
| 511 VecPayload::new(&data, 5).unwrap_err() | |
| 512 ) | |
| 513 } | |
| 514 | |
| 515 #[cfg(debug_assertions)] | |
| 516 #[test] | |
| 517 #[should_panic] | |
| 518 fn test_new_wrong_size() { | |
| 519 let bad_vec = vec![0; 19]; | |
| 520 let msg = PtrPtrVec::new(bad_vec); | |
| 521 let _ = msg.as_ptr::<u64>(); | |
| 522 } | |
| 523 | |
| 524 #[allow(deprecated)] | |
| 525 #[test] | |
| 526 #[should_panic] | |
| 527 fn test_iter_xsso_wrong_size() { | |
| 528 unsafe { | |
| 529 let _ = PtrPtrVec::<u8>::iter_over_xsso::<f64>(ptr::null(), 1); | |
| 530 } | |
| 531 } | |
| 532 | |
| 533 #[allow(deprecated)] | |
| 534 #[test] | |
| 535 #[should_panic] | |
| 536 fn test_iter_linux_wrong_size() { | |
| 537 unsafe { | |
| 538 let _ = PtrPtrVec::<u128>::iter_over_linux::<()>(ptr::null(), 1); | |
| 539 } | |
| 540 } | |
| 541 | |
| 542 #[allow(deprecated)] | |
| 543 #[test] | |
| 544 fn test_right_size() { | |
| 545 let good_vec = vec![(1u64, 2u64), (3, 4), (5, 6)]; | |
| 546 let ptr = good_vec.as_ptr(); | |
| 547 let msg = PtrPtrVec::new(good_vec); | |
| 548 let msg_ref: *const *const (i64, i64) = msg.as_ptr(); | |
| 549 assert_eq!(unsafe { *msg_ref }, ptr.cast()); | |
| 550 | |
| 551 let linux_result: Vec<(i64, i64)> = unsafe { PtrPtrVec::iter_over_linux(msg_ref, 3) } | |
| 552 .cloned() | |
| 553 .collect(); | |
| 554 let xsso_result: Vec<(i64, i64)> = unsafe { PtrPtrVec::iter_over_xsso(msg_ref, 3) } | |
| 555 .cloned() | |
| 556 .collect(); | |
| 557 assert_eq!(vec![(1, 2), (3, 4), (5, 6)], linux_result); | |
| 558 assert_eq!(vec![(1, 2), (3, 4), (5, 6)], xsso_result); | |
| 559 drop(msg) | |
| 560 } | |
| 561 | |
| 562 #[allow(deprecated)] | |
| 563 #[test] | |
| 564 fn test_iter_ptr_ptr() { | |
| 565 // These boxes are larger than a single pointer because we want to | |
| 566 // make sure they're not accidentally allocated adjacently | |
| 567 // in such a way that it's compatible with X/SSO. | |
| 568 // | |
| 569 // a pointer to (&str, i32) can be treated as a pointer to (&str). | |
| 570 #[repr(C)] | |
| 571 struct pair(&'static str, i32); | |
| 572 let boxes = vec![ | |
| 573 Box::new(pair("a", 1)), | |
| 574 Box::new(pair("b", 2)), | |
| 575 Box::new(pair("c", 3)), | |
| 576 Box::new(pair("D", 4)), | |
| 577 ]; | |
| 578 let ptr: *const *const &str = boxes.as_ptr().cast(); | |
| 579 let got: Vec<&str> = unsafe { PtrPtrVec::iter_over_linux(ptr, 4) } | |
| 580 .cloned() | |
| 581 .collect(); | |
| 582 assert_eq!(vec!["a", "b", "c", "D"], got); | |
| 583 | |
| 584 // On the other hand, we explicitly want these to be adjacent. | |
| 585 let nums = [-1i8, 2, 3]; | |
| 586 let ptr = nums.as_ptr(); | |
| 587 let got: Vec<u8> = unsafe { PtrPtrVec::iter_over_xsso(&ptr, 3) } | |
| 588 .cloned() | |
| 589 .collect(); | |
| 590 assert_eq!(vec![255, 2, 3], got); | |
| 591 } | |
| 592 } |
