If you'd like to understand pack/unpack. There is a tutorial here in perl, that works equally well in understanding it for php:
http://perldoc.perl.org/perlpacktut.html
(PHP 4, PHP 5, PHP 7, PHP 8)
pack — Compacta datos en una cadena binaria
Compacta los argumentos args
en una cadena binaria, siguiendo el formato format
.
El concepto proviene de Perl y todo el formato funciona
de la misma forma que en Perl, pero algunos formatos aún faltan
(como "u
").
Tenga en cuenta que la distinción entre signado y no signado solo afecta a la función unpack(), mientras que la función pack() proporcionará el mismo resultado para ambos formatos.
format
La chaîne de caractères format
consiste en códigos
de formato seguidos por un argumento repetidor opcional. El repetidor
puede ser un valor entero o *
para una repetición hasta el final de los datos de entrada. Para a, A, h, H,
el repetidor especifica cuántos caracteres de un dato se toman, para
@, es la posición absoluta donde se insertan los próximos datos, para
todo lo demás, el repetidor especifica cuántos argumentos de datos son
consumidos y compactados en la cadena binaria resultante.
Los formatos actualmente aceptados son:
Código | Descripción |
---|---|
a | NUL - Una cadena completada con null |
A | SPACE - Una cadena completada con un espacio |
h | Cadena hexadecimal h, bit de menor peso en primer lugar |
H | Cadena hexadecimal H, bit de mayor peso en primer lugar |
c | Carácter signado |
C | Carácter no signado |
s | entero corto signado (siempre 16 bits, orden de bytes dependiente de la máquina) |
S | entero corto no signado (siempre 16 bits, orden de bytes dependiente de la máquina) |
n | entero corto no signado (siempre 16 bits, orden de bytes big endian) |
v | entero corto no signado (siempre 16 bits, orden de bytes little endian) |
i | entero signado (tamaño y orden de bytes dependientes de la máquina) |
I | entero no signado (tamaño y orden de bytes dependientes de la máquina) |
l | entero largo signado (siempre 32 bits, orden de bytes dependiente de la máquina) |
L | entero largo no signado (siempre 32 bits, orden de bytes dependiente de la máquina) |
N | entero largo no signado (siempre 32 bits, orden de bytes big endian) |
V | entero largo no signado (siempre 32 bits, orden de bytes little endian) |
q | entero doblemente largo signado (siempre 64 bits, orden de bytes dependiente de la máquina) |
Q | entero doblemente largo no signado (siempre 64 bits, orden de bytes dependiente de la máquina) |
J | entero doblemente largo no signado (siempre 64 bits, orden de bytes big endian) |
P | entero doblemente largo no signado (siempre 64 bits, orden de bytes little endian) |
f | número de coma flotante (tamaño y representación dependientes de la máquina) |
g | número de coma flotante (tamaño dependiente de la máquina, orden de bytes little endian) |
G | número de coma flotante (tamaño dependiente de la máquina, orden de bytes big endian) |
d | número de coma flotante doble (tamaño y representación dependientes de la máquina) |
e | número de coma flotante doble (tamaño dependiente de la máquina, orden de bytes little endian) |
E | número de coma flotante doble (tamaño dependiente de la máquina, orden de bytes big endian) |
x | carácter NUL |
X | Retrocede un carácter |
Z | La cadena terminada por NUL (ASCIIZ) será completada con el valor NULL |
@ | Rellena con NUL hasta la posición absoluta |
values
Devuelve una chaîne de caractères binaria que contiene los datos.
Versión | Descripción |
---|---|
8.0.0 |
Esta función ya no devuelve false en caso de error.
|
7.2.0 | Los tipos float y double admiten Big Endian y Little Endian. |
7.0.15, 7.1.1 | Se han añadido los códigos "e", "E", "g" y "G" para activar la compatibilidad con el orden de bytes para los números de coma flotante de simple y doble precisión. |
Ejemplo #1 Ejemplo con pack()
<?php
$binarydata = pack("nvc*", 0x1234, 0x5678, 65, 66);
?>
La cadena binaria resultante tendrá 6 bytes de longitud, y contendrá la secuencia 0x12, 0x34, 0x78, 0x56, 0x41, 0x42.
Los códigos de formato q
, Q
, J
y P
no están disponibles
en las versiones de PHP de 32 bits.
Tenga en cuenta que PHP almacena internamente los valores int como valores signados dependientes de la máquina. Las operaciones sobre enteros que llevan a números fuera del espacio de definición del int serán almacenados como float. Al empaquetar estos flotantes en enteros, se convierten al tipo entero. Esto puede llevar potencialmente a una representación inesperada de los bytes.
El caso más clásico es el empaquetado de números no signados que serían
representables en el tipo int si este fuera no signado.
En los sistemas con un int de 32 bits, la conversión
resulta en un byte idéntico al que si el tipo int fuera no signado
(aunque esto sigue dependiendo de la implementación no signada a signada, del estándar C).
En los sistemas con un int de 64 bits, el
float no suele tener una mantisa lo suficientemente amplia para contener el valor
sin pérdida de precisión. Si estos sistemas también poseen un tipo C nativo
int
de 64 bits (la mayoría de los *NIX no lo tienen), la única forma
de usar el formato de empaquetado I
en valores altos es crear
valores int negativos con la misma representación de bytes que la
valor no signado deseado correspondiente.
If you'd like to understand pack/unpack. There is a tutorial here in perl, that works equally well in understanding it for php:
http://perldoc.perl.org/perlpacktut.html
A helper class to convert integer to binary strings and vice versa. Useful for writing and reading integers to / from files or sockets.
<?php
class int_helper
{
public static function int8($i) {
return is_int($i) ? pack("c", $i) : unpack("c", $i)[1];
}
public static function uInt8($i) {
return is_int($i) ? pack("C", $i) : unpack("C", $i)[1];
}
public static function int16($i) {
return is_int($i) ? pack("s", $i) : unpack("s", $i)[1];
}
public static function uInt16($i, $endianness=false) {
$f = is_int($i) ? "pack" : "unpack";
if ($endianness === true) { // big-endian
$i = $f("n", $i);
}
else if ($endianness === false) { // little-endian
$i = $f("v", $i);
}
else if ($endianness === null) { // machine byte order
$i = $f("S", $i);
}
return is_array($i) ? $i[1] : $i;
}
public static function int32($i) {
return is_int($i) ? pack("l", $i) : unpack("l", $i)[1];
}
public static function uInt32($i, $endianness=false) {
$f = is_int($i) ? "pack" : "unpack";
if ($endianness === true) { // big-endian
$i = $f("N", $i);
}
else if ($endianness === false) { // little-endian
$i = $f("V", $i);
}
else if ($endianness === null) { // machine byte order
$i = $f("L", $i);
}
return is_array($i) ? $i[1] : $i;
}
public static function int64($i) {
return is_int($i) ? pack("q", $i) : unpack("q", $i)[1];
}
public static function uInt64($i, $endianness=false) {
$f = is_int($i) ? "pack" : "unpack";
if ($endianness === true) { // big-endian
$i = $f("J", $i);
}
else if ($endianness === false) { // little-endian
$i = $f("P", $i);
}
else if ($endianness === null) { // machine byte order
$i = $f("Q", $i);
}
return is_array($i) ? $i[1] : $i;
}
}
?>
Usage example:
<?php
Header("Content-Type: text/plain");
include("int_helper.php");
echo int_helper::uInt8(0x6b) . PHP_EOL; // k
echo int_helper::uInt8(107) . PHP_EOL; // k
echo int_helper::uInt8("\x6b") . PHP_EOL . PHP_EOL; // 107
echo int_helper::uInt16(4101) . PHP_EOL; // \x05\x10
echo int_helper::uInt16("\x05\x10") . PHP_EOL; // 4101
echo int_helper::uInt16("\x05\x10", true) . PHP_EOL . PHP_EOL; // 1296
echo int_helper::uInt32(2147483647) . PHP_EOL; // \xff\xff\xff\x7f
echo int_helper::uInt32("\xff\xff\xff\x7f") . PHP_EOL . PHP_EOL; // 2147483647
// Note: Test this with 64-bit build of PHP
echo int_helper::uInt64(9223372036854775807) . PHP_EOL; // \xff\xff\xff\xff\xff\xff\xff\x7f
echo int_helper::uInt64("\xff\xff\xff\xff\xff\xff\xff\x7f") . PHP_EOL . PHP_EOL; // 9223372036854775807
?>
Note that the the upper command in perl looks like this:
$binarydata = pack ("n v c*", 0x1234, 0x5678, 65, 66);
In PHP it seems that no whitespaces are allowed in the first parameter. So if you want to convert your pack command from perl -> PHP, don't forget to remove the whitespaces!
If you need to unpack a signed short from big-endian or little-endian specifically, instead of machine-byte-order, you need only unpack it as the unsigned form, and then if the result is >= 2^15, subtract 2^16 from it.
And example would be:
<?php
$foo = unpack("n", $signedbigendianshort);
$foo = $foo[1];
if($foo >= pow(2, 15)) $foo -= pow(2, 16);
?>
/* Convert float from HostOrder to Network Order */
function FToN( $val )
{
$a = unpack("I",pack( "f",$val ));
return pack("N",$a[1] );
}
/* Convert float from Network Order to HostOrder */
function NToF($val )
{
$a = unpack("N",$val);
$b = unpack("f",pack( "I",$a[1]));
return $b[1];
}
You will get the same effect with
<?php
function _readInt($fp)
{
return unpack('V', fread($fp, 4));
}
?>
or unpack('N', ...) for big-endianness.
Even though in a 64-bit architecure intval(6123456789) = 6123456789, and sprintf('%b', 5000000000) = 100101010000001011111001000000000
pack will not treat anything passed to it as 64-bit. If you want to pack a 64-bit integer:
<?php
$big = 5000000000;
$left = 0xffffffff00000000;
$right = 0x00000000ffffffff;
$l = ($big & $left) >>32;
$r = $big & $right;
$good = pack('NN', $l, $r);
$urlsafe = str_replace(array('+','/'), array('-','_'), base64_encode($good));
//done!
//rebuild:
$unurl = str_replace(array('-','_'), array('+','/'), $urlsafe);
$binary = base64_decode($unurl);
$set = unpack('N2', $tmp);
print_r($set);
$original = $set[1] << 32 | $set[2];
echo $original, "\\r\\n";
?>
results in:
Array
(
[1] => 1
[2] => 705032704
)
5000000000
but ONLY on a 64-bit enabled machine and PHP distro.
Be aware of format code H always padding the 0 for byte-alignment to the right (for odd count of nibbles).
So pack("H", "7") results in 0x70 (ASCII character 'p') and not in 0x07 (BELL character)
as well as pack("H*", "347") results in 0x34 ('4') and 0x70 ('p') and not 0x03 and 0x47.
pack()
h Hex string, low nibble first (not same hex2bin())
H Hex string, high nibble first (same hex2bin())
Using pack to write Arabic char(s) to a file.
<?php
$text = "㔆㘆㘆";
$text = mb_convert_encoding($text, "UCS-2BE", "HTML-ENTITIES");
$len = mb_strlen($text);
$bom = mb_convert_encoding("", "unicode", "HTML-ENTITIES");
$fp = fopen('text.txt', 'w');
fwrite($fp, pack('a2', $bom));
fwrite($fp, pack("a{$len}", $text));
fwrite($fp, pack('a2', $bom));
fwrite($fp, pack('a2', "\n"));
fclose($fp);
?>