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 — Упаковать данные в бинарную строку
Упаковывает заданные аргументы в бинарную строку в соответствии с форматом в параметре
format
.
Идея этой функции была заимствована из Perl и все коды форматирования работают так же. Однако, есть некоторые отсутствующие коды форматирования, такие как код формата Perl "u".
Необходимо иметь в виду, что отличие между знаковыми и беззнаковыми значениями влияет только на функцию unpack(), тогда как функция pack() даёт одинаковый результат для знаковых и беззнаковых кодов формата.
format
Параметр format
задаётся в виде строки и состоит
из кодов формата и опционального аргумента повторения. Аргумент может быть
целочисленным, либо *
для повторения до конца введённых
данных. Для a, A, h, H число повторений определяет то, сколько символов
взято от одного аргумента данных, для @ - это абсолютная позиция для размещения
следующих данных, для всего остального число повторений определяет как много
аргументов данных было обработано и упаковано в результирующую бинарную строку.
Работающие на данный момент форматы:
Код | Описание |
---|---|
a | Строка (string) с NUL-заполнением |
A | Строка (string) со SPACE-заполнением |
h | Hex-строка (Hex string), с нижнего разряда |
H | Hex-строка (Hex string), с верхнего разряда |
c | знаковый символ (char) |
C | беззнаковый символ (char) |
s | знаковый short (всегда 16 бит, машинный байтовый порядок) |
S | беззнаковый short (всегда 16 бит, машинный байтовый порядок) |
n | беззнаковый short (всегда 16 бит, порядок big endian) |
v | беззнаковый short (всегда 16 бит, порядок little endian) |
i | знаковый integer (машинно-зависимый размер и порядок) |
I | беззнаковый integer (машинно-зависимый размер и порядок) |
l | знаковый long (всегда 32 бит, машинный порядок) |
L | беззнаковый long (всегда 32 бит, машинный порядок) |
N | беззнаковый long (всегда 32 бит, порядок big endian) |
V | беззнаковый long (всегда 32 бит, порядок little endian) |
q | signed long long (всегда 64 bit, машинный порядок) |
Q | беззнаковый long long (всегда 64 bit, машинный порядок) |
J | беззнаковый long long (всегда 64 bit, порядок big endian) |
P | беззнаковый long long (всегда 64 bit, порядок little endian) |
f | float (машинно-зависимые размер и представление) |
g | float (машинно-зависимые размер, порядок байт little endian) |
G | float (машинно-зависимые размер, порядок байт big endian) |
d | double (машинно-зависимые размер и представление) |
e | double (машинно-зависимые размер, порядок байт little endian) |
E | double (машинно-зависимые размер, порядок байт big endian) |
x | байт NUL |
X | Резервирование одного байта |
Z | Строка (string) с NUL-заполнением |
@ | NUL-заполнение до абсолютной позиции |
values
Возвращает бинарную строку, содержащую данные.
Версия | Описание |
---|---|
8.0.0 |
Функция больше не возвращает false в случае возникновения ошибки.
|
7.2.0 | Типы float и double поддерживают как обратный, так и прямой порядок передачи байтов. |
7.0.15,7.1.1 | Добавлены коды "e", "E", "g" и "G" для поддержки принудительного указания порядка байт для float и double. |
Пример #1 Пример использования pack()
<?php
$binarydata = pack("nvc*", 0x1234, 0x5678, 65, 66);
?>
Полученная бинарная строка длиной 6 байт будет содержать последовательность байтов 0x12, 0x34, 0x78, 0x56, 0x41, 0x42.
Необходимо отметить, что PHP сохраняет значения типа int как
знаковые с машинно-зависимым размером (C тип long
).
Все числа, переданные как int, но выходящие за границы этого типа будут сохранены с типом float.
При упаковке этих float как int, они будут переданы с типом int. Это может как совпадать, так и нет с заданным шаблоном.
Наиболее частым является случай, когда упаковываются беззнаковые числа типа int. В системах, где int имеет размер в 32 бита, число обычно передаётся по тому же шаблону, как если бы int был беззнаковым (хотя, это зависит от определённых преобразований из знаковое в беззнаковое, согласно стандарту С). В системах, где тип int имеет размер в 64 бита, тип float не имеет мантиссы, достаточной по размеру для хранения значения без потери точности. Если эти системы также имеют простой 64-битный С тип int
(большинство UNIX систем не имеют), то единственным путём для использования формата I
в верхнем разряде является создание отрицательных int значений с тем же представлением как и в определённом беззнаковом значении.
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);
?>