Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added ability to unpack and pack from Rust #32

Merged
merged 4 commits into from
May 5, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion example/skel/.gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
/target
Cargo.lock
/.vscode
expand.rs
expand.rs
vendor
composer.lock
13 changes: 13 additions & 0 deletions example/skel/composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"name": "rust/skel",
"license": "MIT OR Apache-2.0",
"authors": [
{
"name": "David Cole",
"email": "[email protected]"
}
],
"require": {
"psy/psysh": "^0.10.8"
}
}
23 changes: 22 additions & 1 deletion example/skel/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ use ext_php_rs::{
flags::MethodFlags,
function::FunctionBuilder,
module::{ModuleBuilder, ModuleEntry},
types::{array::ZendHashTable, long::ZendLong, object::ZendClassObject, zval::Zval},
types::{
array::ZendHashTable, long::ZendLong, object::ZendClassObject, string::ZendString,
zval::Zval,
},
},
ZendObjectHandler,
};
Expand Down Expand Up @@ -133,12 +136,18 @@ pub extern "C" fn get_module() -> *mut ext_php_rs::php::module::ModuleEntry {
.returns(DataType::Array, false, false)
.build();

let iter = FunctionBuilder::new("skel_unpack", skel_unpack)
.arg(Arg::new("arr", DataType::String))
.returns(DataType::String, false, false)
.build();

ModuleBuilder::new("ext-skel", "0.1.0")
.info_function(php_module_info)
.startup_function(module_init)
.function(funct)
.function(array)
.function(t)
.function(iter)
.build()
.into_raw()
}
Expand Down Expand Up @@ -178,3 +187,15 @@ pub extern "C" fn skeleton_array(execute_data: &mut ExecutionData, _retval: &mut
pub extern "C" fn test_array(_execute_data: &mut ExecutionData, retval: &mut Zval) {
retval.set_array(vec![1, 2, 3, 4]);
}

pub extern "C" fn skel_unpack(execute_data: &mut ExecutionData, retval: &mut Zval) {
let mut packed = Arg::new("arr", DataType::String);
parse_args!(execute_data, packed);

let zv = packed.zval().unwrap();
let val = unsafe { zv.binary::<f32>() };
dbg!(val);
//let v = vec![1i32, 2, 4, 8];
let v = [1i32, 2, 4, 8];
retval.set_binary(v);
}
7 changes: 6 additions & 1 deletion example/skel/test.php
Original file line number Diff line number Diff line change
@@ -1,2 +1,7 @@
<?php
var_dump(test_array());

include __DIR__.'/vendor/autoload.php';

$x = pack('f*', 1234, 5678, 9012);
var_dump(unpack('l*', skel_unpack($x)));
dd($x);
1 change: 1 addition & 0 deletions src/php/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ pub mod execution_data;
pub mod flags;
pub mod function;
pub mod module;
pub mod pack;
pub mod types;
95 changes: 95 additions & 0 deletions src/php/pack.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
//! Provides implementations for converting to and from Zend binary strings, commonly returned
//! from functions such as [`pack`] and [`unpack`].
//!
//! [`pack`]: https://www.php.net/manual/en/function.pack.php
//! [`unpack`]: https://www.php.net/manual/en/function.unpack.php

use super::types::string::ZendString;
use crate::bindings::ext_php_rs_zend_string_init;

/// Used to convert between Zend binary strings and vectors. Useful in conjunction with the
/// [`pack`] and [`unpack`] functions built-in to PHP.
///
/// # Safety
///
/// The types cannot be ensured between PHP and Rust, as the data is represented as a string when
/// crossing the language boundary. Exercise caution when using these functions.
///
/// [`pack`]: https://www.php.net/manual/en/function.pack.php
/// [`unpack`]: https://www.php.net/manual/en/function.unpack.php
pub unsafe trait Pack: Clone {
/// Packs a given vector into a Zend binary string. Can be passed to PHP and then unpacked
/// using the [`unpack`] function. Note you should probably use the [`set_binary`] method on the
/// [`Zval`] struct instead of this function directly, as there is currently no way to set a
/// [`ZendString`] on a [`Zval`] directly.
///
/// # Parameters
///
/// * `vec` - The vector to pack into a binary string.
///
/// [`unpack`]: https://www.php.net/manual/en/function.unpack.php
/// [`Zval`]: crate::php::types::zval::Zval
/// [`ZendString`]: crate::php::types::string::ZendString
/// [`set_binary`]: crate::php::types::zval::Zval#method.set_binary
fn pack_into(vec: Vec<Self>) -> *mut ZendString;

/// Unpacks a given Zend binary string into a Rust vector. Can be used to pass data from `pack`
/// in PHP to Rust without encoding into another format. Note that the data *must* be all one
/// type, as this implementation only unpacks one type.
///
/// # Safety
///
/// This is an unsafe function. There is no way to tell if the data passed from the PHP
/// function is indeed the correct format. Exercise caution when using the `unpack` functions.
/// In fact, even when used correctly, the results can differ depending on the platform and the
/// size of each type on the platform. Consult the [`pack`](https://www.php.net/manual/en/function.pack.php)
/// function documentation for more details.
///
/// # Parameters
///
/// * `s` - The Zend string containing the binary data.
unsafe fn unpack_into(s: &ZendString) -> Vec<Self>;
}

/// Implements the [`Pack`] trait for a given type.
/// The first argument is the type and the second argument is the factor of size difference between
/// the given type and an 8-bit integer e.g. impl Unpack for i32, factor = 4 => 4 * 8 = 32
#[macro_use]
macro_rules! pack_impl {
($t: ty, $d: expr) => {
unsafe impl Pack for $t {
fn pack_into(vec: Vec<Self>) -> *mut ZendString {
let len = vec.len() * $d;
let ptr = Box::into_raw(vec.into_boxed_slice());
unsafe { ext_php_rs_zend_string_init(ptr as *mut i8, len as _, false) }
}

unsafe fn unpack_into(s: &ZendString) -> Vec<Self> {
let len = s.len / $d;
let mut result = Vec::with_capacity(len as _);
let ptr = s.val.as_ptr() as *const $t;

for i in 0..len {
result.push(*ptr.offset(i as _));
}

result
}
}
};
}

pack_impl!(u8, 1);
pack_impl!(i8, 1);

pack_impl!(u16, 2);
pack_impl!(i16, 2);

pack_impl!(u32, 4);
pack_impl!(i32, 4);

pack_impl!(u64, 8);
pack_impl!(i64, 8);

pack_impl!(f32, 4);
pack_impl!(f64, 8);
38 changes: 35 additions & 3 deletions src/php/types/zval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use crate::{
zval,
},
errors::{Error, Result},
php::pack::Pack,
};

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

Some(_str.to_string())
}
Expand All @@ -87,6 +90,24 @@ impl<'a> Zval {
}
}

/// Returns the value of the zval if it is a string and can be unpacked into a vector of a
/// given type. Similar to the [`unpack`](https://www.php.net/manual/en/function.unpack.php)
/// in PHP, except you can only unpack one type.
///
/// # Safety
///
/// There is no way to tell if the data stored in the string is actually of the given type.
/// The results of this function can also differ from platform-to-platform due to the different
/// representation of some types on different platforms. Consult the [`pack`](https://www.php.net/manual/en/function.pack.php)
/// function documentation for more details.
pub unsafe fn binary<T: Pack>(&self) -> Option<Vec<T>> {
if self.is_string() {
Some(T::unpack_into(self.value.str_.as_ref()?))
} else {
None
}
}

/// Returns the value of the zval if it is a resource.
pub fn resource(&self) -> Option<*mut zend_resource> {
// TODO: Can we improve this function? I haven't done much research into
Expand Down Expand Up @@ -262,6 +283,17 @@ impl<'a> Zval {
self.u1.type_info = ZvalTypeFlags::StringEx.bits();
}

/// Sets the value of the zval as a binary string.
///
/// # Parameters
///
/// * `val` - The value to set the zval as.
pub fn set_binary<T: Pack, U: AsRef<[T]>>(&mut self, val: U) {
let ptr = T::pack_into(val.as_ref().to_vec());
self.value.str_ = ptr;
self.u1.type_info = ZvalTypeFlags::StringEx.bits();
}

/// Sets the value of the zval as a persistent string.
/// This means that the zend string will persist between
/// request lifetime.
Expand Down