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
74 fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> {
75 zv.set_binary(self.0);
76 Ok(())
77 }
78}
79
80impl<T: Pack> From<Binary<T>> for Vec<T> {
81 fn from(value: Binary<T>) -> Self {
82 value.0
83 }
84}
85
86impl<T: Pack> From<Vec<T>> for Binary<T> {
87 fn from(value: Vec<T>) -> Self {
88 Self::new(value)
89 }
90}
91
92impl<T: Pack> FromIterator<T> for Binary<T> {
93 fn from_iter<U: IntoIterator<Item = T>>(iter: U) -> Self {
94 Self(iter.into_iter().collect::<Vec<_>>())
95 }
96}
97
98/// Used to convert between Zend binary strings and vectors. Useful in
99/// conjunction with the [`pack`] and [`unpack`] functions built-in to PHP.
100///
101/// # Safety
102///
103/// The types cannot be ensured between PHP and Rust, as the data is represented
104/// as a string when crossing the language boundary. Exercise caution when using
105/// these functions.
106///
107/// [`pack`]: https://www.php.net/manual/en/function.pack.php
108/// [`unpack`]: https://www.php.net/manual/en/function.unpack.php
109pub unsafe trait Pack: Clone {
110 /// Packs a given vector into a Zend binary string. Can be passed to PHP and
111 /// then unpacked using the [`unpack`] function.
112 ///
113 /// # Parameters
114 ///
115 /// * `vec` - The vector to pack into a binary string.
116 ///
117 /// [`unpack`]: https://www.php.net/manual/en/function.unpack.php
118 fn pack_into(vec: Vec<Self>) -> *mut zend_string;
119
120 /// Unpacks a given Zend binary string into a Rust vector. Can be used to
121 /// pass data from `pack` in PHP to Rust without encoding into another
122 /// format. Note that the data *must* be all one type, as this
123 /// implementation only unpacks one type.
124 ///
125 /// # Safety
126 ///
127 /// There is no way to tell if the data stored in the string is actually of
128 /// the given type. The results of this function can also differ from
129 /// platform-to-platform due to the different representation of some
130 /// types on different platforms. Consult the [`pack`] function
131 /// documentation for more details.
132 ///
133 /// # Parameters
134 ///
135 /// * `s` - The Zend string containing the binary data.
136 ///
137 /// [`pack`]: https://www.php.net/manual/en/function.pack.php
138 fn unpack_into(s: &zend_string) -> Vec<Self>;
139}
140
141/// Implements the [`Pack`] trait for a given type.
142macro_rules! pack_impl {
143 ($t: ty) => {
144 pack_impl!($t, <$t>::BITS);
145 };
146
147 ($t: ty, $d: expr) => {
148 unsafe impl Pack for $t {
149 fn pack_into(vec: Vec<Self>) -> *mut zend_string {
150 let len = vec.len() * ($d as usize / 8);
151 let ptr = Box::into_raw(vec.into_boxed_slice());
152 unsafe { ext_php_rs_zend_string_init(ptr.cast(), len as _, false) }
153 }
154
155 fn unpack_into(s: &zend_string) -> Vec<Self> {
156 let bytes = ($d / 8) as u64;
157 let len = (s.len as u64) / bytes;
158 let mut result = Vec::with_capacity(len as _);
159 let ptr = s.val.as_ptr() as *const $t;
160
161 // SAFETY: We calculate the length of memory that we can legally read based on
162 // the side of the type, therefore we never read outside the memory we
163 // should.
164 for i in 0..len {
165 result.push(unsafe { *ptr.offset(i as _) });
166 }
167
168 result
169 }
170 }
171 };
172}
173
174pack_impl!(u8);
175pack_impl!(i8);
176
177pack_impl!(u16);
178pack_impl!(i16);
179
180pack_impl!(u32);
181pack_impl!(i32);
182
183pack_impl!(u64);
184pack_impl!(i64);
185
186pack_impl!(isize);
187pack_impl!(usize);
188
189pack_impl!(f32, 32);
190pack_impl!(f64, 64);