Skip to content

Commit 30516cd

Browse files
committed
Added ability to unpack and pack from Rust
1 parent 042c70a commit 30516cd

File tree

7 files changed

+163
-6
lines changed

7 files changed

+163
-6
lines changed

example/skel/.gitignore

Lines changed: 3 additions & 1 deletion
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

Lines changed: 13 additions & 0 deletions
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": "david.cole1340@gmail.com"
8+
}
9+
],
10+
"require": {
11+
"psy/psysh": "^0.10.8"
12+
}
13+
}

example/skel/src/lib.rs

Lines changed: 21 additions & 1 deletion
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,14 @@ 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![1u8, 2, 4, 8];
199+
retval.set_binary(v);
200+
}

example/skel/test.php

Lines changed: 6 additions & 1 deletion
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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
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: Sized {
21+
fn pack_into(vec: Vec<Self>) -> *mut ZendString; //{
22+
// unsafe { ext_php_rs_zend_string_init(ptr as *mut i8, vec.len() as _, false) }
23+
// }
24+
25+
/// Unpacks a given Zend binary string into a Rust vector. Can be used to pass data from `pack`
26+
/// in PHP to Rust without encoding into another format. Note that the data *must* be all one
27+
/// type, as this implementation only unpacks one type.
28+
///
29+
/// # Safety
30+
///
31+
/// This is an unsafe function. There is no way to tell if the data passed from the PHP
32+
/// function is indeed the correct format. Exercise caution when using the `unpack` functions.
33+
/// In fact, even when used correctly, the results can differ depending on the platform and the
34+
/// size of each type on the platform. Consult the [`pack`](https://www.php.net/manual/en/function.pack.php)
35+
/// function documentation for more details.
36+
///
37+
/// # Parameters
38+
///
39+
/// * `s` - The Zend string containing the binary data.
40+
unsafe fn unpack_into(s: &ZendString) -> Vec<Self>;
41+
}
42+
43+
/// Implements the [`Pack`] trait for a given type.
44+
/// The first argument is the type and the second argument is the factor of size difference between
45+
/// the given type and an 8-bit integer e.g. impl Unpack for i32, factor = 4 => 4 * 8 = 32
46+
#[macro_use]
47+
macro_rules! pack_impl {
48+
($t: ty, $d: expr) => {
49+
unsafe impl Pack for $t {
50+
fn pack_into(vec: Vec<Self>) -> *mut ZendString {
51+
let len = vec.len() * $d;
52+
let ptr = Box::into_raw(vec.into_boxed_slice());
53+
unsafe { ext_php_rs_zend_string_init(ptr as *mut i8, len as _, false) }
54+
}
55+
56+
unsafe fn unpack_into(s: &ZendString) -> Vec<Self> {
57+
let len = s.len / $d;
58+
let mut result = Vec::with_capacity(len as _);
59+
let ptr = s.val.as_ptr() as *const $t;
60+
61+
for i in 0..len {
62+
result.push(*ptr.offset(i as _));
63+
}
64+
65+
result
66+
}
67+
}
68+
};
69+
}
70+
71+
pack_impl!(u8, 1);
72+
pack_impl!(i8, 1);
73+
74+
pack_impl!(u16, 2);
75+
pack_impl!(i16, 2);
76+
77+
pack_impl!(u32, 4);
78+
pack_impl!(i32, 4);
79+
80+
pack_impl!(u64, 8);
81+
pack_impl!(i64, 8);
82+
83+
pack_impl!(f32, 4);
84+
pack_impl!(f64, 8);

src/php/types/zval.rs

Lines changed: 35 additions & 3 deletions
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>(&mut self, val: Vec<T>) {
292+
let ptr = T::pack_into(val);
293+
self.value.str_ = ptr;
294+
self.u1.type_info = IS_STRING_EX;
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)