Skip to main content

ext_php_rs/
binary.rs

1//! Provides implementations for converting to and from Zend binary strings,
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::{ext_php_rs_zend_string_init, zend_string};
8
9use std::{
10    convert::TryFrom,
11    iter::FromIterator,
12    ops::{Deref, DerefMut},
13};
14
15use crate::{
16    convert::{FromZval, IntoZval},
17    error::{Error, Result},
18    flags::DataType,
19    types::Zval,
20};
21
22/// Acts as a wrapper around [`Vec<T>`] where `T` implements [`Pack`]. Primarily
23/// used for passing binary data into Rust functions. Can be treated as a
24/// [`Vec`] in most situations, or can be 'unwrapped' into a [`Vec`] through the
25/// [`From`] implementation on [`Vec`].
26#[derive(Debug)]
27pub struct Binary<T: Pack>(Vec<T>);
28
29impl<T: Pack> Binary<T> {
30    /// Creates a new binary wrapper from a set of data which can be converted
31    /// into a vector.
32    ///
33    /// # Parameters
34    ///
35    /// * `data` - Data to store inside the binary wrapper.
36    pub fn new(data: impl Into<Vec<T>>) -> Self {
37        Self(data.into())
38    }
39}
40
41impl<T: Pack> Deref for Binary<T> {
42    type Target = Vec<T>;
43
44    fn deref(&self) -> &Self::Target {
45        &self.0
46    }
47}
48
49impl<T: Pack> DerefMut for Binary<T> {
50    fn deref_mut(&mut self) -> &mut Self::Target {
51        &mut self.0
52    }
53}
54
55impl<T: Pack> FromZval<'_> for Binary<T> {
56    const TYPE: DataType = DataType::String;
57
58    fn from_zval(zval: &Zval) -> Option<Self> {
59        zval.binary().map(Binary)
60    }
61}
62
63impl<T: Pack> TryFrom<Zval> for Binary<T> {
64    type Error = Error;
65
66    fn try_from(value: Zval) -> Result<Self> {
67        Self::from_zval(&value).ok_or_else(|| Error::ZvalConversion(value.get_type()))
68    }
69}
70
71impl<T: Pack> IntoZval for Binary<T> {
72    const TYPE: DataType = DataType::String;
73    const NULLABLE: bool = false;
74
75    fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> {
76        zv.set_binary(self.0);
77        Ok(())
78    }
79}
80
81impl<T: Pack> From<Binary<T>> for Vec<T> {
82    fn from(value: Binary<T>) -> Self {
83        value.0
84    }
85}
86
87impl<T: Pack> From<Vec<T>> for Binary<T> {
88    fn from(value: Vec<T>) -> Self {
89        Self::new(value)
90    }
91}
92
93impl<T: Pack> FromIterator<T> for Binary<T> {
94    fn from_iter<U: IntoIterator<Item = T>>(iter: U) -> Self {
95        Self(iter.into_iter().collect::<Vec<_>>())
96    }
97}
98
99/// Used to convert between Zend binary strings and vectors. Useful in
100/// conjunction with the [`pack`] and [`unpack`] functions built-in to PHP.
101///
102/// # Safety
103///
104/// The types cannot be ensured between PHP and Rust, as the data is represented
105/// as a string when crossing the language boundary. Exercise caution when using
106/// these functions.
107///
108/// [`pack`]: https://www.php.net/manual/en/function.pack.php
109/// [`unpack`]: https://www.php.net/manual/en/function.unpack.php
110pub unsafe trait Pack: Clone {
111    /// Packs a given vector into a Zend binary string. Can be passed to PHP and
112    /// then unpacked using the [`unpack`] function.
113    ///
114    /// # Parameters
115    ///
116    /// * `vec` - The vector to pack into a binary string.
117    ///
118    /// [`unpack`]: https://www.php.net/manual/en/function.unpack.php
119    fn pack_into(vec: Vec<Self>) -> *mut zend_string;
120
121    /// Unpacks a given Zend binary string into a Rust vector. Can be used to
122    /// pass data from `pack` in PHP to Rust without encoding into another
123    /// format. Note that the data *must* be all one type, as this
124    /// implementation only unpacks one type.
125    ///
126    /// Trailing bytes that do not fill a whole `Self` are ignored. The bytes
127    /// are read unaligned, so any valid `zend_string` is accepted.
128    ///
129    /// There is no way to tell if the data stored in the string is actually of
130    /// the given type. The results of this function can also differ from
131    /// platform-to-platform due to the different representation of some
132    /// types on different platforms. Consult the [`pack`] function
133    /// documentation for more details.
134    ///
135    /// # Parameters
136    ///
137    /// * `s` - The Zend string containing the binary data.
138    ///
139    /// [`pack`]: https://www.php.net/manual/en/function.pack.php
140    fn unpack_into(s: &zend_string) -> Vec<Self>;
141}
142
143/// Implements the [`Pack`] trait for a given type.
144macro_rules! pack_impl {
145    ($t: ty) => {
146        pack_impl!($t, <$t>::BITS);
147    };
148
149    ($t: ty, $d: expr) => {
150        unsafe impl Pack for $t {
151            fn pack_into(vec: Vec<Self>) -> *mut zend_string {
152                let len = vec.len() * ($d as usize / 8);
153                let ptr = vec.as_ptr();
154                unsafe { ext_php_rs_zend_string_init(ptr.cast(), len as _, false) }
155            }
156
157            fn unpack_into(s: &zend_string) -> Vec<Self> {
158                let len = s.len / ($d as usize / 8);
159                // Only `read_unaligned` dereferences this pointer, so the alignment
160                // the cast would normally promise is never relied on.
161                #[allow(clippy::cast_ptr_alignment)]
162                let ptr = s.val.as_ptr().cast::<$t>();
163
164                // SAFETY: a valid `zend_string` has `len` readable bytes at `val`, and
165                // `len * size_of::<Self>() <= s.len`. `read_unaligned` has no alignment
166                // requirement, so a 1-byte-aligned `val` is fine.
167                (0..len)
168                    .map(|i| unsafe { ptr.add(i).read_unaligned() })
169                    .collect()
170            }
171        }
172    };
173}
174
175pack_impl!(u8);
176pack_impl!(i8);
177
178pack_impl!(u16);
179pack_impl!(i16);
180
181pack_impl!(u32);
182pack_impl!(i32);
183
184pack_impl!(u64);
185pack_impl!(i64);
186
187pack_impl!(isize);
188pack_impl!(usize);
189
190pack_impl!(f32, 32);
191pack_impl!(f64, 64);