Skip to content

Commit a75fcb5

Browse files
Added ability to unpack and pack from Rust (#32)
* Added ability to `unpack` and `pack` from Rust * Updated `Pack::pack_into` documentation * Replaced constant with new flags, fixed example * Accept slices/arrays when using `Zval::set_binary`
1 parent fcdd933 commit a75fcb5

File tree

7 files changed

+175
-6
lines changed

7 files changed

+175
-6
lines changed

example/skel/.gitignore

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
/target
22
Cargo.lock
33
/.vscode
4-
expand.rs
4+
expand.rs
5+
vendor
6+
composer.lock

example/skel/composer.json

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"name": "rust/skel",
3+
"license": "MIT OR Apache-2.0",
4+
"authors": [
5+
{
6+
"name": "David Cole",
7+
"email": "[email protected]"
8+
}
9+
],
10+
"require": {
11+
"psy/psysh": "^0.10.8"
12+
}
13+
}

example/skel/src/lib.rs

+22-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ use ext_php_rs::{
1212
flags::MethodFlags,
1313
function::FunctionBuilder,
1414
module::{ModuleBuilder, ModuleEntry},
15-
types::{array::ZendHashTable, long::ZendLong, object::ZendClassObject, zval::Zval},
15+
types::{
16+
array::ZendHashTable, long::ZendLong, object::ZendClassObject, string::ZendString,
17+
zval::Zval,
18+
},
1619
},
1720
ZendObjectHandler,
1821
};
@@ -133,12 +136,18 @@ pub extern "C" fn get_module() -> *mut ext_php_rs::php::module::ModuleEntry {
133136
.returns(DataType::Array, false, false)
134137
.build();
135138

139+
let iter = FunctionBuilder::new("skel_unpack", skel_unpack)
140+
.arg(Arg::new("arr", DataType::String))
141+
.returns(DataType::String, false, false)
142+
.build();
143+
136144
ModuleBuilder::new("ext-skel", "0.1.0")
137145
.info_function(php_module_info)
138146
.startup_function(module_init)
139147
.function(funct)
140148
.function(array)
141149
.function(t)
150+
.function(iter)
142151
.build()
143152
.into_raw()
144153
}
@@ -178,3 +187,15 @@ pub extern "C" fn skeleton_array(execute_data: &mut ExecutionData, _retval: &mut
178187
pub extern "C" fn test_array(_execute_data: &mut ExecutionData, retval: &mut Zval) {
179188
retval.set_array(vec![1, 2, 3, 4]);
180189
}
190+
191+
pub extern "C" fn skel_unpack(execute_data: &mut ExecutionData, retval: &mut Zval) {
192+
let mut packed = Arg::new("arr", DataType::String);
193+
parse_args!(execute_data, packed);
194+
195+
let zv = packed.zval().unwrap();
196+
let val = unsafe { zv.binary::<f32>() };
197+
dbg!(val);
198+
//let v = vec![1i32, 2, 4, 8];
199+
let v = [1i32, 2, 4, 8];
200+
retval.set_binary(v);
201+
}

example/skel/test.php

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,7 @@
11
<?php
2-
var_dump(test_array());
2+
3+
include __DIR__.'/vendor/autoload.php';
4+
5+
$x = pack('f*', 1234, 5678, 9012);
6+
var_dump(unpack('l*', skel_unpack($x)));
7+
dd($x);

src/php/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@ pub mod execution_data;
99
pub mod flags;
1010
pub mod function;
1111
pub mod module;
12+
pub mod pack;
1213
pub mod types;

src/php/pack.rs

+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
//! Provides implementations for converting to and from Zend binary strings, commonly returned
2+
//! 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+
7+
use super::types::string::ZendString;
8+
use crate::bindings::ext_php_rs_zend_string_init;
9+
10+
/// Used to convert between Zend binary strings and vectors. Useful in conjunction with the
11+
/// [`pack`] and [`unpack`] functions built-in to PHP.
12+
///
13+
/// # Safety
14+
///
15+
/// The types cannot be ensured between PHP and Rust, as the data is represented as a string when
16+
/// crossing the language boundary. Exercise caution when using these functions.
17+
///
18+
/// [`pack`]: https://www.php.net/manual/en/function.pack.php
19+
/// [`unpack`]: https://www.php.net/manual/en/function.unpack.php
20+
pub unsafe trait Pack: Clone {
21+
/// Packs a given vector into a Zend binary string. Can be passed to PHP and then unpacked
22+
/// using the [`unpack`] function. Note you should probably use the [`set_binary`] method on the
23+
/// [`Zval`] struct instead of this function directly, as there is currently no way to set a
24+
/// [`ZendString`] on a [`Zval`] directly.
25+
///
26+
/// # Parameters
27+
///
28+
/// * `vec` - The vector to pack into a binary string.
29+
///
30+
/// [`unpack`]: https://www.php.net/manual/en/function.unpack.php
31+
/// [`Zval`]: crate::php::types::zval::Zval
32+
/// [`ZendString`]: crate::php::types::string::ZendString
33+
/// [`set_binary`]: crate::php::types::zval::Zval#method.set_binary
34+
fn pack_into(vec: Vec<Self>) -> *mut ZendString;
35+
36+
/// Unpacks a given Zend binary string into a Rust vector. Can be used to pass data from `pack`
37+
/// in PHP to Rust without encoding into another format. Note that the data *must* be all one
38+
/// type, as this implementation only unpacks one type.
39+
///
40+
/// # Safety
41+
///
42+
/// This is an unsafe function. There is no way to tell if the data passed from the PHP
43+
/// function is indeed the correct format. Exercise caution when using the `unpack` functions.
44+
/// In fact, even when used correctly, the results can differ depending on the platform and the
45+
/// size of each type on the platform. Consult the [`pack`](https://www.php.net/manual/en/function.pack.php)
46+
/// function documentation for more details.
47+
///
48+
/// # Parameters
49+
///
50+
/// * `s` - The Zend string containing the binary data.
51+
unsafe fn unpack_into(s: &ZendString) -> Vec<Self>;
52+
}
53+
54+
/// Implements the [`Pack`] trait for a given type.
55+
/// The first argument is the type and the second argument is the factor of size difference between
56+
/// the given type and an 8-bit integer e.g. impl Unpack for i32, factor = 4 => 4 * 8 = 32
57+
#[macro_use]
58+
macro_rules! pack_impl {
59+
($t: ty, $d: expr) => {
60+
unsafe impl Pack for $t {
61+
fn pack_into(vec: Vec<Self>) -> *mut ZendString {
62+
let len = vec.len() * $d;
63+
let ptr = Box::into_raw(vec.into_boxed_slice());
64+
unsafe { ext_php_rs_zend_string_init(ptr as *mut i8, len as _, false) }
65+
}
66+
67+
unsafe fn unpack_into(s: &ZendString) -> Vec<Self> {
68+
let len = s.len / $d;
69+
let mut result = Vec::with_capacity(len as _);
70+
let ptr = s.val.as_ptr() as *const $t;
71+
72+
for i in 0..len {
73+
result.push(*ptr.offset(i as _));
74+
}
75+
76+
result
77+
}
78+
}
79+
};
80+
}
81+
82+
pack_impl!(u8, 1);
83+
pack_impl!(i8, 1);
84+
85+
pack_impl!(u16, 2);
86+
pack_impl!(i16, 2);
87+
88+
pack_impl!(u32, 4);
89+
pack_impl!(i32, 4);
90+
91+
pack_impl!(u64, 8);
92+
pack_impl!(i64, 8);
93+
94+
pack_impl!(f32, 4);
95+
pack_impl!(f64, 8);

src/php/types/zval.rs

+35-3
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use crate::{
1111
zval,
1212
},
1313
errors::{Error, Result},
14+
php::pack::Pack,
1415
};
1516

1617
use crate::php::{
@@ -76,9 +77,11 @@ impl<'a> Zval {
7677
// We can safely cast our *const c_char into a *const u8 as both
7778
// only occupy one byte.
7879
unsafe {
79-
let len = (*self.value.str_).len;
80-
let ptr = (*self.value.str_).val.as_ptr() as *const u8;
81-
let _str = std::str::from_utf8(slice::from_raw_parts(ptr, len as usize)).ok()?;
80+
let _str = std::str::from_utf8(slice::from_raw_parts(
81+
(*self.value.str_).val.as_ptr() as *const u8,
82+
(*self.value.str_).len as usize,
83+
))
84+
.ok()?;
8285

8386
Some(_str.to_string())
8487
}
@@ -87,6 +90,24 @@ impl<'a> Zval {
8790
}
8891
}
8992

93+
/// Returns the value of the zval if it is a string and can be unpacked into a vector of a
94+
/// given type. Similar to the [`unpack`](https://www.php.net/manual/en/function.unpack.php)
95+
/// in PHP, except you can only unpack one type.
96+
///
97+
/// # Safety
98+
///
99+
/// There is no way to tell if the data stored in the string is actually of the given type.
100+
/// The results of this function can also differ from platform-to-platform due to the different
101+
/// representation of some types on different platforms. Consult the [`pack`](https://www.php.net/manual/en/function.pack.php)
102+
/// function documentation for more details.
103+
pub unsafe fn binary<T: Pack>(&self) -> Option<Vec<T>> {
104+
if self.is_string() {
105+
Some(T::unpack_into(self.value.str_.as_ref()?))
106+
} else {
107+
None
108+
}
109+
}
110+
90111
/// Returns the value of the zval if it is a resource.
91112
pub fn resource(&self) -> Option<*mut zend_resource> {
92113
// TODO: Can we improve this function? I haven't done much research into
@@ -262,6 +283,17 @@ impl<'a> Zval {
262283
self.u1.type_info = ZvalTypeFlags::StringEx.bits();
263284
}
264285

286+
/// Sets the value of the zval as a binary string.
287+
///
288+
/// # Parameters
289+
///
290+
/// * `val` - The value to set the zval as.
291+
pub fn set_binary<T: Pack, U: AsRef<[T]>>(&mut self, val: U) {
292+
let ptr = T::pack_into(val.as_ref().to_vec());
293+
self.value.str_ = ptr;
294+
self.u1.type_info = ZvalTypeFlags::StringEx.bits();
295+
}
296+
265297
/// Sets the value of the zval as a persistent string.
266298
/// This means that the zend string will persist between
267299
/// request lifetime.

0 commit comments

Comments
 (0)