2014-01-03 01:07:10 +00:00
|
|
|
<?php
|
|
|
|
|
/**
|
|
|
|
|
* MessagePack serializer
|
|
|
|
|
*
|
|
|
|
|
* MessagePack is a space-efficient binary data interchange format. This
|
|
|
|
|
* class provides a pack() method that encodes native PHP values as MessagePack
|
|
|
|
|
* binary strings. The implementation is derived from msgpack-php.
|
|
|
|
|
*
|
|
|
|
|
* Copyright (c) 2013 Ori Livneh <ori@wikimedia.org>
|
|
|
|
|
* Copyright (c) 2011 OnlineCity <https://github.com/onlinecity/msgpack-php>.
|
|
|
|
|
*
|
|
|
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
|
|
* of this software and associated documentation files (the "Software"), to
|
|
|
|
|
* deal in the Software without restriction, including without limitation the
|
|
|
|
|
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
|
|
|
* sell copies of the Software, and to permit persons to whom the Software is
|
|
|
|
|
* furnished to do so, subject to the following conditions:
|
|
|
|
|
*
|
|
|
|
|
* The above copyright notice and this permission notice shall be included in
|
|
|
|
|
* all copies or substantial portions of the Software.
|
|
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
|
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
|
|
|
* IN THE SOFTWARE.
|
|
|
|
|
*
|
|
|
|
|
* @see <http://msgpack.org/>
|
|
|
|
|
* @see <http://wiki.msgpack.org/display/MSGPACK/Format+specification>
|
|
|
|
|
*
|
|
|
|
|
* @since 1.23
|
|
|
|
|
* @file
|
|
|
|
|
*/
|
|
|
|
|
class MWMessagePack {
|
2016-03-24 08:44:09 +00:00
|
|
|
/** @var bool|null Whether current system is bigendian. **/
|
2014-01-03 05:57:23 +00:00
|
|
|
public static $bigendian = null;
|
2014-01-03 01:07:10 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Encode a value using MessagePack
|
|
|
|
|
*
|
|
|
|
|
* This method supports null, boolean, integer, float, string and array
|
|
|
|
|
* (both indexed and associative) types. Object serialization is not
|
|
|
|
|
* supported.
|
|
|
|
|
*
|
|
|
|
|
* @param mixed $value
|
|
|
|
|
* @return string
|
2014-01-03 05:57:23 +00:00
|
|
|
* @throws InvalidArgumentException if $value is an unsupported type or too long a string
|
2014-01-03 01:07:10 +00:00
|
|
|
*/
|
|
|
|
|
public static function pack( $value ) {
|
|
|
|
|
if ( self::$bigendian === null ) {
|
|
|
|
|
self::$bigendian = pack( 'S', 1 ) === pack( 'n', 1 );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch ( gettype( $value ) ) {
|
2017-12-11 03:07:50 +00:00
|
|
|
case 'NULL':
|
|
|
|
|
return "\xC0";
|
2014-01-03 01:07:10 +00:00
|
|
|
|
2017-12-11 03:07:50 +00:00
|
|
|
case 'boolean':
|
|
|
|
|
return $value ? "\xC3" : "\xC2";
|
2014-01-03 01:07:10 +00:00
|
|
|
|
2017-12-11 03:07:50 +00:00
|
|
|
case 'double':
|
|
|
|
|
case 'float':
|
|
|
|
|
return self::$bigendian
|
|
|
|
|
? "\xCB" . pack( 'd', $value )
|
|
|
|
|
: "\xCB" . strrev( pack( 'd', $value ) );
|
2014-01-03 01:07:10 +00:00
|
|
|
|
2017-12-11 03:07:50 +00:00
|
|
|
case 'string':
|
|
|
|
|
$length = strlen( $value );
|
|
|
|
|
if ( $length < 32 ) {
|
|
|
|
|
return pack( 'Ca*', 0xA0 | $length, $value );
|
|
|
|
|
} elseif ( $length <= 0xFFFF ) {
|
|
|
|
|
return pack( 'Cna*', 0xDA, $length, $value );
|
|
|
|
|
} elseif ( $length <= 0xFFFFFFFF ) {
|
|
|
|
|
return pack( 'CNa*', 0xDB, $length, $value );
|
2014-01-03 01:07:10 +00:00
|
|
|
}
|
2014-04-24 19:48:10 +00:00
|
|
|
throw new InvalidArgumentException( __METHOD__
|
2017-12-11 03:07:50 +00:00
|
|
|
. ": string too long (length: $length; max: 4294967295)" );
|
2014-01-03 05:57:23 +00:00
|
|
|
|
2017-12-11 03:07:50 +00:00
|
|
|
case 'integer':
|
|
|
|
|
if ( $value >= 0 ) {
|
|
|
|
|
if ( $value <= 0x7F ) {
|
|
|
|
|
// positive fixnum
|
|
|
|
|
return chr( $value );
|
|
|
|
|
}
|
|
|
|
|
if ( $value <= 0xFF ) {
|
|
|
|
|
// uint8
|
|
|
|
|
return pack( 'CC', 0xCC, $value );
|
|
|
|
|
}
|
|
|
|
|
if ( $value <= 0xFFFF ) {
|
|
|
|
|
// uint16
|
|
|
|
|
return pack( 'Cn', 0xCD, $value );
|
|
|
|
|
}
|
|
|
|
|
if ( $value <= 0xFFFFFFFF ) {
|
|
|
|
|
// uint32
|
|
|
|
|
return pack( 'CN', 0xCE, $value );
|
|
|
|
|
}
|
|
|
|
|
if ( $value <= 0xFFFFFFFFFFFFFFFF ) {
|
|
|
|
|
// uint64
|
|
|
|
|
$hi = ( $value & 0xFFFFFFFF00000000 ) >> 32;
|
|
|
|
|
$lo = $value & 0xFFFFFFFF;
|
|
|
|
|
return self::$bigendian
|
|
|
|
|
? pack( 'CNN', 0xCF, $lo, $hi )
|
|
|
|
|
: pack( 'CNN', 0xCF, $hi, $lo );
|
|
|
|
|
}
|
2014-01-03 05:57:23 +00:00
|
|
|
} else {
|
2017-12-11 03:07:50 +00:00
|
|
|
if ( $value >= -32 ) {
|
|
|
|
|
// negative fixnum
|
|
|
|
|
return pack( 'c', $value );
|
|
|
|
|
}
|
|
|
|
|
if ( $value >= -0x80 ) {
|
|
|
|
|
// int8
|
|
|
|
|
return pack( 'Cc', 0xD0, $value );
|
|
|
|
|
}
|
|
|
|
|
if ( $value >= -0x8000 ) {
|
|
|
|
|
// int16
|
|
|
|
|
$p = pack( 's', $value );
|
|
|
|
|
return self::$bigendian
|
|
|
|
|
? pack( 'Ca2', 0xD1, $p )
|
|
|
|
|
: pack( 'Ca2', 0xD1, strrev( $p ) );
|
|
|
|
|
}
|
|
|
|
|
if ( $value >= -0x80000000 ) {
|
|
|
|
|
// int32
|
|
|
|
|
$p = pack( 'l', $value );
|
|
|
|
|
return self::$bigendian
|
|
|
|
|
? pack( 'Ca4', 0xD2, $p )
|
|
|
|
|
: pack( 'Ca4', 0xD2, strrev( $p ) );
|
|
|
|
|
}
|
|
|
|
|
if ( $value >= -0x8000000000000000 ) {
|
|
|
|
|
// int64
|
|
|
|
|
// pack() does not support 64-bit ints either so pack into two 32-bits
|
|
|
|
|
$p1 = pack( 'l', $value & 0xFFFFFFFF );
|
|
|
|
|
$p2 = pack( 'l', ( $value >> 32 ) & 0xFFFFFFFF );
|
|
|
|
|
return self::$bigendian
|
|
|
|
|
? pack( 'Ca4a4', 0xD3, $p1, $p2 )
|
|
|
|
|
: pack( 'Ca4a4', 0xD3, strrev( $p2 ), strrev( $p1 ) );
|
|
|
|
|
}
|
2014-01-03 05:57:23 +00:00
|
|
|
}
|
2017-12-11 03:07:50 +00:00
|
|
|
throw new InvalidArgumentException( __METHOD__ . ": invalid integer '$value'" );
|
2014-01-03 01:07:10 +00:00
|
|
|
|
2017-12-11 03:07:50 +00:00
|
|
|
case 'array':
|
|
|
|
|
$buffer = '';
|
|
|
|
|
$length = count( $value );
|
|
|
|
|
if ( $length > 0xFFFFFFFF ) {
|
|
|
|
|
throw new InvalidArgumentException( __METHOD__
|
|
|
|
|
. ": array too long (length: $length, max: 4294967295)" );
|
2014-01-03 01:07:10 +00:00
|
|
|
}
|
2017-12-11 03:07:50 +00:00
|
|
|
|
|
|
|
|
$index = 0;
|
2014-01-03 01:07:10 +00:00
|
|
|
foreach ( $value as $k => $v ) {
|
2017-12-11 03:07:50 +00:00
|
|
|
if ( $index !== $k || $index === $length ) {
|
|
|
|
|
break;
|
|
|
|
|
} else {
|
|
|
|
|
$index++;
|
|
|
|
|
}
|
2014-01-03 01:07:10 +00:00
|
|
|
}
|
2017-12-11 03:07:50 +00:00
|
|
|
$associative = $index !== $length;
|
|
|
|
|
|
|
|
|
|
if ( $associative ) {
|
|
|
|
|
if ( $length < 16 ) {
|
|
|
|
|
$buffer .= pack( 'C', 0x80 | $length );
|
|
|
|
|
} elseif ( $length <= 0xFFFF ) {
|
|
|
|
|
$buffer .= pack( 'Cn', 0xDE, $length );
|
|
|
|
|
} else {
|
|
|
|
|
$buffer .= pack( 'CN', 0xDF, $length );
|
|
|
|
|
}
|
|
|
|
|
foreach ( $value as $k => $v ) {
|
|
|
|
|
$buffer .= self::pack( $k );
|
|
|
|
|
$buffer .= self::pack( $v );
|
|
|
|
|
}
|
2014-01-03 01:07:10 +00:00
|
|
|
} else {
|
2017-12-11 03:07:50 +00:00
|
|
|
if ( $length < 16 ) {
|
|
|
|
|
$buffer .= pack( 'C', 0x90 | $length );
|
|
|
|
|
} elseif ( $length <= 0xFFFF ) {
|
|
|
|
|
$buffer .= pack( 'Cn', 0xDC, $length );
|
|
|
|
|
} else {
|
|
|
|
|
$buffer .= pack( 'CN', 0xDD, $length );
|
|
|
|
|
}
|
|
|
|
|
foreach ( $value as $v ) {
|
|
|
|
|
$buffer .= self::pack( $v );
|
|
|
|
|
}
|
2014-01-03 01:07:10 +00:00
|
|
|
}
|
2017-12-11 03:07:50 +00:00
|
|
|
return $buffer;
|
2014-01-03 01:07:10 +00:00
|
|
|
|
2017-12-11 03:07:50 +00:00
|
|
|
default:
|
|
|
|
|
throw new InvalidArgumentException( __METHOD__ . ': unsupported type ' . gettype( $value ) );
|
2014-01-03 01:07:10 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|