Merge "Update plural rules to CLDR 24"
[lhc/web/wiklou.git] / includes / libs / MWMessagePack.php
1 <?php
2 /**
3 * MessagePack serializer
4 *
5 * MessagePack is a space-efficient binary data interchange format. This
6 * class provides a pack() method that encodes native PHP values as MessagePack
7 * binary strings. The implementation is derived from msgpack-php.
8 *
9 * Copyright (c) 2013 Ori Livneh <ori@wikimedia.org>
10 * Copyright (c) 2011 OnlineCity <https://github.com/onlinecity/msgpack-php>.
11 *
12 * Permission is hereby granted, free of charge, to any person obtaining a copy
13 * of this software and associated documentation files (the "Software"), to
14 * deal in the Software without restriction, including without limitation the
15 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
16 * sell copies of the Software, and to permit persons to whom the Software is
17 * furnished to do so, subject to the following conditions:
18 *
19 * The above copyright notice and this permission notice shall be included in
20 * all copies or substantial portions of the Software.
21 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
27 * IN THE SOFTWARE.
28 *
29 * @see <http://msgpack.org/>
30 * @see <http://wiki.msgpack.org/display/MSGPACK/Format+specification>
31 *
32 * @since 1.23
33 * @file
34 */
35 class MWMessagePack {
36
37 /** @var boolean|null Whether current system is bigendian. **/
38 public static $bigendian;
39
40 /**
41 * Encode a value using MessagePack
42 *
43 * This method supports null, boolean, integer, float, string and array
44 * (both indexed and associative) types. Object serialization is not
45 * supported.
46 *
47 * @param mixed $value
48 * @return string
49 */
50 public static function pack( $value ) {
51 if ( self::$bigendian === null ) {
52 self::$bigendian = pack( 'S', 1 ) === pack( 'n', 1 );
53 }
54
55 switch ( gettype( $value ) ) {
56 case 'NULL':
57 return "\xC0";
58
59 case 'boolean':
60 return $value ? "\xC3" : "\xC2";
61
62 case 'double':
63 case 'float':
64 return self::$bigendian
65 ? "\xCB" . pack( 'd', $value )
66 : "\xCB" . strrev( pack( 'd', $value ) );
67
68 case 'string':
69 $length = strlen( $value );
70 if ( $length < 32 ) {
71 return pack( 'Ca*', 0xA0 | $length, $value );
72 } elseif ( $length <= 0xFFFF ) {
73 return pack( 'Cna*', 0xDA, $length, $value );
74 } elseif ( $length <= 0xFFFFFFFF ) {
75 return pack( 'CNa*', 0xDB, $length, $value );
76 }
77 throw new LengthException( "String too long: $length (max: 4294967295)." );
78
79 case 'integer':
80 if ( $value >= 0 ) {
81 if ( $value <= 0x7F ) {
82 // positive fixnum
83 return chr( $value );
84 }
85 if ( $value <= 0xFF ) {
86 // uint8
87 return pack( 'CC', 0xCC, $value );
88 }
89 if ( $value <= 0xFFFF ) {
90 // uint16
91 return pack( 'Cn', 0xCD, $value );
92 }
93 if ( $value <= 0xFFFFFFFF ) {
94 // uint32
95 return pack( 'CN', 0xCE, $value );
96 }
97 if ( $value <= 0xFFFFFFFFFFFFFFFF ) {
98 // uint64
99 $hi = ( $value & 0xFFFFFFFF00000000 ) >> 32;
100 $lo = $value & 0xFFFFFFFF;
101 return self::$bigendian
102 ? pack( 'CNN', 0xCF, $lo, $hi )
103 : pack( 'CNN', 0xCF, $hi, $lo );
104 }
105 } else {
106 if ( $value >= -32 ) {
107 // negative fixnum
108 return pack( 'c', $value );
109 }
110 if ( $value >= -0x80 ) {
111 // int8
112 return pack( 'Cc', 0xD0, $value );
113 }
114 if ( $value >= -0x8000 ) {
115 // int16
116 $p = pack('s',$value);
117 return self::$bigendian
118 ? pack( 'Ca2', 0xD1, $p )
119 : pack( 'Ca2', 0xD1, strrev( $p ) );
120 }
121 if ( $value >= -0x80000000 ) {
122 // int32
123 $p = pack( 'l', $value );
124 return self::$bigendian
125 ? pack( 'Ca4', 0xD2, $p )
126 : pack( 'Ca4', 0xD2, strrev( $p ) );
127 }
128 if ( $value >= -0x8000000000000000 ) {
129 // int64
130 // pack() does not support 64-bit ints either so pack into two 32-bits
131 $p1 = pack( 'l', $value & 0xFFFFFFFF );
132 $p2 = pack( 'l', ( $value >> 32 ) & 0xFFFFFFFF );
133 return self::$bigendian
134 ? pack( 'Ca4a4', 0xD3, $p1, $p2 )
135 : pack( 'Ca4a4', 0xD3, strrev( $p2 ), strrev( $p1 ) );
136 }
137 }
138 throw new LengthException( 'Invalid integer: ' . $value );
139
140 case 'array':
141 $associative = array_values( $value ) !== $value;
142 $length = count( $value );
143 $buffer = '';
144
145 if ( $length > 0xFFFFFFFF ) {
146 throw new LengthException( "Array too long: $length (max: 4294967295)." );
147 }
148
149 if ( $associative ) {
150 if ( $length < 16 ) {
151 $buffer .= pack( 'C', 0x80 | $length );
152 } elseif ( $length <= 0xFFFF ) {
153 $buffer .= pack( 'Cn', 0xDE, $length );
154 } else {
155 $buffer .= pack( 'CN', 0xDF, $length );
156 }
157 foreach ( $value as $k => $v ) {
158 $buffer .= self::pack( $k );
159 $buffer .= self::pack( $v );
160 }
161 } else {
162 if ( $length < 16 ) {
163 $buffer .= pack( 'C', 0x90 | $length );
164 } elseif ( $length <= 0xFFFF ) {
165 $buffer .= pack( 'Cn', 0xDC, $length );
166 } else {
167 $buffer .= pack( 'CN', 0xDD, $length );
168 }
169 foreach ( $value as $v ) {
170 $buffer .= self::pack( $v );
171 }
172 }
173 return $buffer;
174
175 default:
176 throw new LengthException( 'Unsupported type: ' . gettype( $value ) );
177 }
178 }
179 }