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 — Empaqueta información a una cadena binaria
Empaqueta los argumentos dados a una cadena binaria según el formato dado por
format
.
La idea de esta función fue tomada de Perl y todos los códigos de formato funcionan igual que en Perl. Sin embargo, hay algunos códigos de formato que no están, como el código de formato "u" de Perl.
Observe que la distinción entre valores con signo y sin signo sólo afecta a la función unpack(), la función pack() da los mismos resultados para códigos de formato con y sin signo.
format
La cadena format
consiste en códigos de formato
seguidos de un argumento opcional de repetición. El argumento de repetición puede
ser un valor entero o *
para repetir hasta
el final de los datos de entrada. Para a, A, h, H las repeticiones especifican
cuántos caracteres se toman de un argumento de datos, para @ es la posición
absoluta donde colocar el dato siguiente, para todo lo demás las
repeticiones especifican cuántos argumentos de datos se consumen y empaquetan
en la cadena binaria resultante.
Los formatos implementados actualmente son:
Código | Descripción |
---|---|
a | cadena rellena de NUL |
A | cadena rellena de SPACE |
h | cadena hexadecimal, nibble bajo primero |
H | cadena hexadecimal, nibble alto primero |
c | signed char |
C | carácter sin signo |
s | short con signo (siempre 16 bits, orden de byte de la máquina) |
S | short sin signo (siempre 16 bits, orden de byte de la máquina) |
n | short sin signo (siempre 16 bits, orden de byte big endian) |
v | short sin signo (siempre 16 bits, orden de byte little endian) |
i | integer con signo (tamaño y orden de byte dependientes de la máquina) |
I | integer sin signo (tamaño y orden de byte dependientes de la máquina) |
l | long con signo (siempre 32 bits, orden de byte de la máquina) |
L | long sin signo (siempre 32 bits, orden de byte de la máquina) |
N | long sin signo (siempre 32 bits, orden de byte big endian) |
V | long sin signo (siempre 32 bits, orden de byte little endian) |
q | long largo con signo (siempre 64 bit, orden de byte big endian) |
Q | long largo sin signo (siempre 64 bit, orden de byte big endian) |
J | long largo sin signo (siempre 64 bit, orden de byte big endian) |
P | long largo sin signo (siempre 64 bit, orden de byte little endian) |
f | float (tamaño y representación dependientes de la máquina) |
d | double (tamaño y representación dependientes de la máquina) |
x | byte NUL |
X | copia de seguridad de un byte |
Z | Cadena rellena de NUL (nuevo en PHP 5.5) |
@ | relleno de NUL hasta la posición absoluta |
args
Devuelve una cadena binaria que contiene información.
Versión | Descripción |
---|---|
5.6.3 | Se añadieron los códigos "q", "Q", "J" y "P" para habilitar el trabajo con número de 64 bit. |
5.5.0 | Se añadió el código "Z" con funcionalidad equivalente a "a" por compatibilidad con Perl. |
Ejemplo #1 Ejemplo de pack()
<?php
$datosbinarios = pack("nvc*", 0x1234, 0x5678, 65, 66);
?>
La cadena binaria resultante será de 6 bytes de longitud y contendrá la secuencia de bytes 0x12, 0x34, 0x78, 0x56, 0x41, 0x42.
Observe que PHP almacena internamente los valores de tipo integer como
valores con signo de tamaño dependiente de la máquina (de tipo long
de C).
Los literales de tipo integer y las operaciones que producen números fuera de los límites del
tipo integer serán almacenados como float.
Cuando se empaquetan estos floats como integers, primero son convertidos al tipo integer.
Esto puede o no puede resultar en el patrón de bytes deseado.
El caso más relevante es cuando se empaquetan números sin signo que serían
representables con el tipo integer si fueran sin signo.
En sistemas donde el tipo integer tiene un tamaño de 32 bits, la conversión
normalmente resultará en el mismo patrón de bytes que si el tipo integer fuera
sin signo (aunque esto depende de las conversiones de con signo a sin signo definidas en la
implementación, conforme al estándar de C). En sistemas donde el
tipo integer tiene un tamaño de 64 bits, el tipo float casi
con toda seguridad no tendrá una mantisa suficientemente grande para mantener el valor sin
pérdida de precisión. Si esos sistemas también tienen un tipo
int
de C de 64 bits nativo (al contrario que la mayoría de sistemas basados en UNIX),
la única forma de usar el formato de empaquetación I
en el rango superior es crear
valores integer negativos con la misma representación de bytes
que el valor sin signo deseado.
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];
}
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.
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.
You will get the same effect with
<?php
function _readInt($fp)
{
return unpack('V', fread($fp, 4));
}
?>
or unpack('N', ...) for big-endianness.
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);
?>