Skip to main content

ext_php_rs/
binary_slice.rs

1//! Provides implementations for converting from Zend binary strings as slices,
2//! commonly returned from functions such as [`pack`] and [`unpack`].
3//!
4//! [`pack`]: https://www.php.net/manual/en/function.pack.php
5//! [`unpack`]: https://www.php.net/manual/en/function.unpack.php
6
7use crate::ffi::zend_string;
8
9use std::{ops::Deref, slice::from_raw_parts};
10
11use crate::{convert::FromZval, flags::DataType, types::Zval};
12
13/// Acts as a wrapper around `&[T]` where `T` implements [`PackSlice`].
14/// Primarily used for passing read-only binary data into Rust functions.
15#[derive(Debug)]
16pub struct BinarySlice<'a, T>(&'a [T])
17where
18    T: PackSlice;
19
20impl<'a, T> BinarySlice<'a, T>
21where
22    T: PackSlice,
23{
24    /// Creates a new binary slice wrapper from a slice of data.
25    ///
26    /// # Parameters
27    ///
28    /// * `data` - Slice to store inside the binary wrapper.
29    pub fn new(data: &'a [T]) -> Self {
30        Self(data)
31    }
32}
33
34impl<'a, T> Deref for BinarySlice<'a, T>
35where
36    T: PackSlice,
37{
38    type Target = &'a [T];
39
40    fn deref(&self) -> &Self::Target {
41        &self.0
42    }
43}
44
45impl<'a, T> FromZval<'a> for BinarySlice<'a, T>
46where
47    T: PackSlice,
48{
49    const TYPE: DataType = DataType::String;
50
51    fn from_zval(zval: &'a Zval) -> Option<Self> {
52        zval.binary_slice().map(BinarySlice)
53    }
54}
55
56impl<'a, T> From<BinarySlice<'a, T>> for &'a [T]
57where
58    T: PackSlice,
59{
60    fn from(value: BinarySlice<'a, T>) -> Self {
61        value.0
62    }
63}
64
65impl<'a, T> From<&'a [T]> for BinarySlice<'a, T>
66where
67    T: PackSlice,
68{
69    fn from(value: &'a [T]) -> Self {
70        Self::new(value)
71    }
72}
73
74/// Used to expose a Zend binary string as a slice. Useful in conjunction with
75/// the [`pack`] and [`unpack`] functions built-in to PHP.
76///
77/// # Safety
78///
79/// The types cannot be ensured between PHP and Rust, as the data is represented
80/// as a string when crossing the language boundary. Exercise caution when using
81/// these functions.
82///
83/// [`pack`]: https://www.php.net/manual/en/function.pack.php
84/// [`unpack`]: https://www.php.net/manual/en/function.unpack.php
85pub unsafe trait PackSlice: Clone {
86    /// Creates a Rust slice from a given Zend binary string. Can be used to
87    /// pass data from `pack` in PHP to Rust without encoding into another
88    /// format. Note that the data *must* be all one type, as this
89    /// implementation only unpacks one type.
90    ///
91    /// There is no way to tell if the data stored in the string is actually of
92    /// the given type. The results of this function can also differ from
93    /// platform-to-platform due to the different representation of some
94    /// types on different platforms. Consult the [`pack`] function
95    /// documentation for more details.
96    ///
97    /// # Safety
98    ///
99    /// The returned slice aliases the string's bytes, so the caller must
100    /// guarantee:
101    ///
102    /// * `s.len` is a whole multiple of `size_of::<Self>()`.
103    /// * `s.val` is aligned to `align_of::<Self>()`. Zend allocates strings on
104    ///   `ZEND_MM_ALIGNMENT` (8 bytes) boundaries and `val` sits at an 8-byte
105    ///   offset, but the `zend_string` type itself only promises 1-byte
106    ///   alignment for `val`.
107    ///
108    /// [`Zval::binary_slice`](crate::types::Zval::binary_slice) performs both
109    /// checks and is the safe entry point.
110    ///
111    /// # Parameters
112    ///
113    /// * `s` - The Zend string containing the binary data.
114    ///
115    /// [`pack`]: https://www.php.net/manual/en/function.pack.php
116    unsafe fn unpack_into(s: &zend_string) -> &[Self];
117}
118
119/// Implements the [`PackSlice`] trait for a given type.
120macro_rules! pack_slice_impl {
121    ($t: ty) => {
122        pack_slice_impl!($t, <$t>::BITS);
123    };
124
125    ($t: ty, $d: expr) => {
126        unsafe impl PackSlice for $t {
127            unsafe fn unpack_into(s: &zend_string) -> &[Self] {
128                let len = s.len / ($d as usize / 8);
129                // The caller guarantees `val` is aligned for `Self`, see `PackSlice`.
130                #[allow(clippy::cast_ptr_alignment)]
131                let ptr = s.val.as_ptr().cast::<$t>();
132                // SAFETY: the caller guarantees alignment and that `len` whole elements
133                // fit in the string's `s.len` readable bytes.
134                unsafe { from_raw_parts(ptr, len) }
135            }
136        }
137    };
138}
139
140pack_slice_impl!(u8);
141pack_slice_impl!(i8);
142
143pack_slice_impl!(u16);
144pack_slice_impl!(i16);
145
146pack_slice_impl!(u32);
147pack_slice_impl!(i32);
148
149pack_slice_impl!(u64);
150pack_slice_impl!(i64);
151
152pack_slice_impl!(isize);
153pack_slice_impl!(usize);
154
155pack_slice_impl!(f32, 32);
156pack_slice_impl!(f64, 64);