1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
//! 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 crate::bindings::{ext_php_rs_zend_string_init, zend_string};

/// 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 zend_string;

    /// 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
    ///
    /// 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`] function
    /// documentation for more details.
    ///
    /// # Parameters
    ///
    /// * `s` - The Zend string containing the binary data.
    ///
    /// [`pack`]: https://www.php.net/manual/en/function.pack.php
    fn unpack_into(s: &zend_string) -> Vec<Self>;
}

/// Implements the [`Pack`] trait for a given type.
macro_rules! pack_impl {
    ($t: ty) => {
        pack_impl!($t, <$t>::BITS);
    };

    ($t: ty, $d: expr) => {
        unsafe impl Pack for $t {
            fn pack_into(vec: Vec<Self>) -> *mut zend_string {
                let len = vec.len() * ($d as usize / 8);
                let ptr = Box::into_raw(vec.into_boxed_slice());
                unsafe { ext_php_rs_zend_string_init(ptr as *mut i8, len as _, false) }
            }

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

                // SAFETY: We calculate the length of memory that we can legally read based on the
                // side of the type, therefore we never read outside the memory we should.
                for i in 0..len {
                    result.push(unsafe { *ptr.offset(i as _) });
                }

                result
            }
        }
    };
}

pack_impl!(u8);
pack_impl!(i8);

pack_impl!(u16);
pack_impl!(i16);

pack_impl!(u32);
pack_impl!(i32);

pack_impl!(u64);
pack_impl!(i64);

pack_impl!(isize);
pack_impl!(usize);

pack_impl!(f32, 32);
pack_impl!(f64, 64);