1<?php
2/*
3 * Licensed to the Apache Software Foundation (ASF) under one
4 * or more contributor license agreements. See the NOTICE file
5 * distributed with this work for additional information
6 * regarding copyright ownership. The ASF licenses this file
7 * to you under the Apache License, Version 2.0 (the
8 * "License"); you may not use this file except in compliance
9 * with the License. You may obtain a copy of the License at
10 *
11 *   http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing,
14 * software distributed under the License is distributed on an
15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 * KIND, either express or implied. See the License for the
17 * specific language governing permissions and limitations
18 * under the License.
19 *
20 * @package thrift.protocol
21 */
22
23namespace Thrift\Protocol;
24
25use Thrift\Type\TType;
26use Thrift\Exception\TProtocolException;
27use Thrift\Factory\TStringFuncFactory;
28
29/**
30 * Binary implementation of the Thrift protocol.
31 *
32 */
33class TBinaryProtocol extends TProtocol
34{
35    const VERSION_MASK = 0xffff0000;
36    const VERSION_1 = 0x80010000;
37
38    protected $strictRead_ = false;
39    protected $strictWrite_ = true;
40
41    public function __construct($trans, $strictRead = false, $strictWrite = true)
42    {
43        parent::__construct($trans);
44        $this->strictRead_ = $strictRead;
45        $this->strictWrite_ = $strictWrite;
46    }
47
48    public function writeMessageBegin($name, $type, $seqid)
49    {
50        if ($this->strictWrite_) {
51            $version = self::VERSION_1 | $type;
52
53            return
54                $this->writeI32($version) +
55                $this->writeString($name) +
56                $this->writeI32($seqid);
57        } else {
58            return
59                $this->writeString($name) +
60                $this->writeByte($type) +
61                $this->writeI32($seqid);
62        }
63    }
64
65    public function writeMessageEnd()
66    {
67        return 0;
68    }
69
70    public function writeStructBegin($name)
71    {
72        return 0;
73    }
74
75    public function writeStructEnd()
76    {
77        return 0;
78    }
79
80    public function writeFieldBegin($fieldName, $fieldType, $fieldId)
81    {
82        return
83            $this->writeByte($fieldType) +
84            $this->writeI16($fieldId);
85    }
86
87    public function writeFieldEnd()
88    {
89        return 0;
90    }
91
92    public function writeFieldStop()
93    {
94        return
95            $this->writeByte(TType::STOP);
96    }
97
98    public function writeMapBegin($keyType, $valType, $size)
99    {
100        return
101            $this->writeByte($keyType) +
102            $this->writeByte($valType) +
103            $this->writeI32($size);
104    }
105
106    public function writeMapEnd()
107    {
108        return 0;
109    }
110
111    public function writeListBegin($elemType, $size)
112    {
113        return
114            $this->writeByte($elemType) +
115            $this->writeI32($size);
116    }
117
118    public function writeListEnd()
119    {
120        return 0;
121    }
122
123    public function writeSetBegin($elemType, $size)
124    {
125        return
126            $this->writeByte($elemType) +
127            $this->writeI32($size);
128    }
129
130    public function writeSetEnd()
131    {
132        return 0;
133    }
134
135    public function writeBool($value)
136    {
137        $data = pack('c', $value ? 1 : 0);
138        $this->trans_->write($data, 1);
139
140        return 1;
141    }
142
143    public function writeByte($value)
144    {
145        $data = pack('c', $value);
146        $this->trans_->write($data, 1);
147
148        return 1;
149    }
150
151    public function writeI16($value)
152    {
153        $data = pack('n', $value);
154        $this->trans_->write($data, 2);
155
156        return 2;
157    }
158
159    public function writeI32($value)
160    {
161        $data = pack('N', $value);
162        $this->trans_->write($data, 4);
163
164        return 4;
165    }
166
167    public function writeI64($value)
168    {
169        // If we are on a 32bit architecture we have to explicitly deal with
170        // 64-bit twos-complement arithmetic since PHP wants to treat all ints
171        // as signed and any int over 2^31 - 1 as a float
172        if (PHP_INT_SIZE == 4) {
173            $neg = $value < 0;
174
175            if ($neg) {
176                $value *= -1;
177            }
178
179            $hi = (int)($value / 4294967296);
180            $lo = (int)$value;
181
182            if ($neg) {
183                $hi = ~$hi;
184                $lo = ~$lo;
185                if (($lo & (int)0xffffffff) == (int)0xffffffff) {
186                    $lo = 0;
187                    $hi++;
188                } else {
189                    $lo++;
190                }
191            }
192            $data = pack('N2', $hi, $lo);
193        } else {
194            $hi = $value >> 32;
195            $lo = $value & 0xFFFFFFFF;
196            $data = pack('N2', $hi, $lo);
197        }
198
199        $this->trans_->write($data, 8);
200
201        return 8;
202    }
203
204    public function writeDouble($value)
205    {
206        $data = pack('d', $value);
207        $this->trans_->write(strrev($data), 8);
208
209        return 8;
210    }
211
212    public function writeString($value)
213    {
214        $len = TStringFuncFactory::create()->strlen($value);
215        $result = $this->writeI32($len);
216        if ($len) {
217            $this->trans_->write($value, $len);
218        }
219
220        return $result + $len;
221    }
222
223    public function readMessageBegin(&$name, &$type, &$seqid)
224    {
225        $result = $this->readI32($sz);
226        if ($sz < 0) {
227            $version = (int)($sz & self::VERSION_MASK);
228            if ($version != (int)self::VERSION_1) {
229                throw new TProtocolException('Bad version identifier: ' . $sz, TProtocolException::BAD_VERSION);
230            }
231            $type = $sz & 0x000000ff;
232            $result +=
233                $this->readString($name) +
234                $this->readI32($seqid);
235        } else {
236            if ($this->strictRead_) {
237                throw new TProtocolException(
238                    'No version identifier, old protocol client?',
239                    TProtocolException::BAD_VERSION
240                );
241            } else {
242                // Handle pre-versioned input
243                $name = $this->trans_->readAll($sz);
244                $result +=
245                    $sz +
246                    $this->readByte($type) +
247                    $this->readI32($seqid);
248            }
249        }
250
251        return $result;
252    }
253
254    public function readMessageEnd()
255    {
256        return 0;
257    }
258
259    public function readStructBegin(&$name)
260    {
261        $name = '';
262
263        return 0;
264    }
265
266    public function readStructEnd()
267    {
268        return 0;
269    }
270
271    public function readFieldBegin(&$name, &$fieldType, &$fieldId)
272    {
273        $result = $this->readByte($fieldType);
274        if ($fieldType == TType::STOP) {
275            $fieldId = 0;
276
277            return $result;
278        }
279        $result += $this->readI16($fieldId);
280
281        return $result;
282    }
283
284    public function readFieldEnd()
285    {
286        return 0;
287    }
288
289    public function readMapBegin(&$keyType, &$valType, &$size)
290    {
291        return
292            $this->readByte($keyType) +
293            $this->readByte($valType) +
294            $this->readI32($size);
295    }
296
297    public function readMapEnd()
298    {
299        return 0;
300    }
301
302    public function readListBegin(&$elemType, &$size)
303    {
304        return
305            $this->readByte($elemType) +
306            $this->readI32($size);
307    }
308
309    public function readListEnd()
310    {
311        return 0;
312    }
313
314    public function readSetBegin(&$elemType, &$size)
315    {
316        return
317            $this->readByte($elemType) +
318            $this->readI32($size);
319    }
320
321    public function readSetEnd()
322    {
323        return 0;
324    }
325
326    public function readBool(&$value)
327    {
328        $data = $this->trans_->readAll(1);
329        $arr = unpack('c', $data);
330        $value = $arr[1] == 1;
331
332        return 1;
333    }
334
335    public function readByte(&$value)
336    {
337        $data = $this->trans_->readAll(1);
338        $arr = unpack('c', $data);
339        $value = $arr[1];
340
341        return 1;
342    }
343
344    public function readI16(&$value)
345    {
346        $data = $this->trans_->readAll(2);
347        $arr = unpack('n', $data);
348        $value = $arr[1];
349        if ($value > 0x7fff) {
350            $value = 0 - (($value - 1) ^ 0xffff);
351        }
352
353        return 2;
354    }
355
356    public function readI32(&$value)
357    {
358        $data = $this->trans_->readAll(4);
359        $arr = unpack('N', $data);
360        $value = $arr[1];
361        if ($value > 0x7fffffff) {
362            $value = 0 - (($value - 1) ^ 0xffffffff);
363        }
364
365        return 4;
366    }
367
368    public function readI64(&$value)
369    {
370        $data = $this->trans_->readAll(8);
371
372        $arr = unpack('N2', $data);
373
374        // If we are on a 32bit architecture we have to explicitly deal with
375        // 64-bit twos-complement arithmetic since PHP wants to treat all ints
376        // as signed and any int over 2^31 - 1 as a float
377        if (PHP_INT_SIZE == 4) {
378            $hi = $arr[1];
379            $lo = $arr[2];
380            $isNeg = $hi < 0;
381
382            // Check for a negative
383            if ($isNeg) {
384                $hi = ~$hi & (int)0xffffffff;
385                $lo = ~$lo & (int)0xffffffff;
386
387                if ($lo == (int)0xffffffff) {
388                    $hi++;
389                    $lo = 0;
390                } else {
391                    $lo++;
392                }
393            }
394
395            // Force 32bit words in excess of 2G to pe positive - we deal wigh sign
396            // explicitly below
397
398            if ($hi & (int)0x80000000) {
399                $hi &= (int)0x7fffffff;
400                $hi += 0x80000000;
401            }
402
403            if ($lo & (int)0x80000000) {
404                $lo &= (int)0x7fffffff;
405                $lo += 0x80000000;
406            }
407
408            $value = $hi * 4294967296 + $lo;
409
410            if ($isNeg) {
411                $value = 0 - $value;
412            }
413        } else {
414            // Upcast negatives in LSB bit
415            if ($arr[2] & 0x80000000) {
416                $arr[2] = $arr[2] & 0xffffffff;
417            }
418
419            // Check for a negative
420            if ($arr[1] & 0x80000000) {
421                $arr[1] = $arr[1] & 0xffffffff;
422                $arr[1] = $arr[1] ^ 0xffffffff;
423                $arr[2] = $arr[2] ^ 0xffffffff;
424                $value = 0 - $arr[1] * 4294967296 - $arr[2] - 1;
425            } else {
426                $value = $arr[1] * 4294967296 + $arr[2];
427            }
428        }
429
430        return 8;
431    }
432
433    public function readDouble(&$value)
434    {
435        $data = strrev($this->trans_->readAll(8));
436        $arr = unpack('d', $data);
437        $value = $arr[1];
438
439        return 8;
440    }
441
442    public function readString(&$value)
443    {
444        $result = $this->readI32($len);
445        if ($len) {
446            $value = $this->trans_->readAll($len);
447        } else {
448            $value = '';
449        }
450
451        return $result + $len;
452    }
453}
454