charm/arraybuffer

Typed arrays for structs powered by FFI. Gives you the equivalent of UInt8Array, UInt16Array, UInt32Array, Float64Array and ArrayBuffer in JavaScript.

1.0.4 2022-05-24 10:16 UTC

This package is auto-updated.

Last update: 2024-04-24 14:28:56 UTC


README

A powerful class modelled after the javascript ArrayBuffer. The library performs much faster than using pack() and unpack(), thanks to PHP 7.4 FFI.

When you allocate an ArrayBuffer, you can read and write the individual values of the memory region directly as various strong types:

  • ArrayBuffer::getUInt8View() will give you an array of unsigned 8 bit integers. Corresponding methods exists for 16, 32 and 64 bit unsigned integers.
  • ArrayBuffer::getFloat32View() will give you an array of 32 bit float numbers, and similarly you can use the 64 bit method.

Basic Usage

$someString = 'Hello World!';
$buffer = Charm\ArrayBuffer::fromString($someString);

// get an Uint8View
$uint8array = $buffer->uint8;

// access as an unsigned 8 bit integer
echo chr($buffer->uint8[2])."\n"; // 108' - the ascii value of the third character in the string

// access as single characters
echo $buffer->char[2]."\n"; // 'l'

// access as a 16 bit signed integer
echo $buffer->int16[0]."\n";

// update the previous integer by modifying byte 0
$buffer->int8[1] = 0;
echo $buffer->int16[0]."\n";

Little and Big Endian

Little endian means that the "little end" of the integer or float value is stored first. This means that the least significant byte is stored first.

When you're accessing data from a buffer, the data will be read and written with the CPU default endianness.

If the computer is using big endian, and the file or network stream you're working with is in little endian format - you can convert the endianness of integers and floats after you've fetched them.

// putting 0xFF00 in the memory slots
$buffer->uint[0] = 255;
$buffer->uint[1] = 0;

// reading a 2 byte integer

if ($buffer->uint16[0] === 0xFF00) {
    echo "Big endian computer\n";
} else {
    echo "Little endian computer\n";
}

If your computer has the wrong endianness compared to the buffer you're working with, you will have to convert the endianness before you write and after you read.

$value = $buffer->uint16->flip($buffer->uint16[0]);

More complex data

Complex binary data parsing is most easily done by declaring a struct complex type, like this:

/**
 * We've received some binary data, so we create an ArrayBuffer
 */
$buffer = Charm\ArrayBuffer::fromString($someBinaryData);

/**
 * We would like to work with the data more efficiently, so we declare a normal `FFI\CType` object.
 * You may want to make these types using a more traditional C-style header file (.h) and invoke
 * {@see FFI::type()} yourself.
 */
$headerType = $buffer->createStruct([
    'marker' => 'char[5]',              // 5 bytes are reserved for a header string
    'packetType' => 'uint8_t',          // 1 byte for the packet type
    'reserved0' => 'char[2]',           // 2 bytes skipped
    'sender_ipv4' => 'uint8_t[4]',      // 4 bytes for the IPv4 address of the sender
    'receiver_ipv4' => 'uint8_t[4]',    // 4 bytes for the IPv4 address of the receiver
    'reserved1' => 'char[48]',          // 48 bytes skipped to make the header packet 128 bytes total
    'timestamp' => 'uint64_t',          // a 64 bit integer for the unix timestamp
]);

/**
 * Use the struct to get a view from the ArrayBuffer at offset 0.
 */
$header = $buffer->getStructView($headerType, 0);

/**
 * Read properties. The properties map directly to the underlying ArrayBuffer, so
 * if the buffer is modified through other views, it will be reflected here
 * instantly.
 */
if ($header->marker != 'PACKT') {
    throw \Exception("Packet is not the right type");
}

/**
 * Write properties. As mentioned, this will update the underlying ArrayBuffer
 * and all other views regardless of format.
 */
$header->packetType = 42;
$header->sender_ipv4[0] = 127;
$header->sender_ipv4[1] = 0;
$header->sender_ipv4[2] = 0;
$header->sender_ipv4[3] = 1;
$header->receiver_ipv4[0] = 127;
$header->receiver_ipv4[1] = 0;
$header->receiver_ipv4[2] = 0;
$header->receiver_ipv4[3] = 1;
$header->timestamp = time();

/**
 * To demonstrate the memory mapping, we can access packetType directly if we wish.
 */
var_dump($buffer->uint8[5]); // 42