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);