blob: 6ecbd09f23ff234cd29171c587e5e1f8717be634 [file] [log] [blame]
Bryan Duxbury11dfc8b2011-07-13 18:15:41 +00001<?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
23include_once $GLOBALS['THRIFT_ROOT'].'/transport/TBufferedTransport.php';
24
25/**
26 * Compact implementation of the Thrift protocol.
27 *
28 */
29class TCompactProtocol extends TProtocol {
30
31 const COMPACT_STOP = 0x00;
32 const COMPACT_TRUE = 0x01;
33 const COMPACT_FALSE = 0x02;
34 const COMPACT_BYTE = 0x03;
35 const COMPACT_I16 = 0x04;
36 const COMPACT_I32 = 0x05;
37 const COMPACT_I64 = 0x06;
38 const COMPACT_DOUBLE = 0x07;
39 const COMPACT_BINARY = 0x08;
40 const COMPACT_LIST = 0x09;
41 const COMPACT_SET = 0x0A;
42 const COMPACT_MAP = 0x0B;
43 const COMPACT_STRUCT = 0x0C;
44
45 const STATE_CLEAR = 0;
46 const STATE_FIELD_WRITE = 1;
47 const STATE_VALUE_WRITE = 2;
48 const STATE_CONTAINER_WRITE = 3;
49 const STATE_BOOL_WRITE = 4;
50 const STATE_FIELD_READ = 5;
51 const STATE_CONTAINER_READ = 6;
52 const STATE_VALUE_READ = 7;
53 const STATE_BOOL_READ = 8;
54
55 const VERSION_MASK = 0x1f;
56 const VERSION = 1;
57 const PROTOCOL_ID = 0x82;
58 const TYPE_MASK = 0xe0;
59 const TYPE_SHIFT_AMOUNT = 5;
60
61 protected static $ctypes = array(
62 TType::STOP => TCompactProtocol::COMPACT_STOP,
63 TType::BOOL => TCompactProtocol::COMPACT_TRUE, // used for collection
64 TType::BYTE => TCompactProtocol::COMPACT_BYTE,
65 TType::I16 => TCompactProtocol::COMPACT_I16,
66 TType::I32 => TCompactProtocol::COMPACT_I32,
67 TType::I64 => TCompactProtocol::COMPACT_I64,
68 TType::DOUBLE => TCompactProtocol::COMPACT_DOUBLE,
69 TType::STRING => TCompactProtocol::COMPACT_BINARY,
70 TType::STRUCT => TCompactProtocol::COMPACT_STRUCT,
71 TType::LST => TCompactProtocol::COMPACT_LIST,
72 TType::SET => TCompactProtocol::COMPACT_SET,
73 TType::MAP => TCompactProtocol::COMPACT_MAP,
74 );
75
76 protected static $ttypes = array(
77 TCompactProtocol::COMPACT_STOP => TType::STOP ,
78 TCompactProtocol::COMPACT_TRUE => TType::BOOL, // used for collection
79 TCompactProtocol::COMPACT_FALSE => TType::BOOL,
80 TCompactProtocol::COMPACT_BYTE => TType::BYTE,
81 TCompactProtocol::COMPACT_I16 => TType::I16,
82 TCompactProtocol::COMPACT_I32 => TType::I32,
83 TCompactProtocol::COMPACT_I64 => TType::I64,
84 TCompactProtocol::COMPACT_DOUBLE => TType::DOUBLE,
85 TCompactProtocol::COMPACT_BINARY => TType::STRING,
86 TCompactProtocol::COMPACT_STRUCT => TType::STRUCT,
87 TCompactProtocol::COMPACT_LIST => TType::LST,
88 TCompactProtocol::COMPACT_SET => TType::SET,
89 TCompactProtocol::COMPACT_MAP => TType::MAP,
90 );
91
92 protected $state = TCompactProtocol::STATE_CLEAR;
93 protected $lastFid = 0;
94 protected $boolFid = null;
95 protected $boolValue = null;
96 protected $structs = array();
97 protected $containers = array();
98
99 // Some varint / zigzag helper methods
100 public function toZigZag($n, $bits) {
101 return ($n << 1) ^ ($n >> ($bits - 1));
102 }
103
104 public function fromZigZag($n) {
105 return ($n >> 1) ^ -($n & 1);
106 }
107
108 public function getVarint($data) {
109 $out = "";
110 while (true) {
111 if (($data & ~0x7f) === 0) {
112 $out .= chr($data);
113 break;
114 } else {
115 $out .= chr(($data & 0xff) | 0x80);
116 $data = $data >> 7;
117 }
118 }
119 return $out;
120 }
121
122 public function writeVarint($data) {
123 $out = $this->getVarint($data);
124 $result = strlen($out);
125 $this->trans_->write($out, $result);
126 return $result;
127 }
128
129 public function readVarint(&$result) {
130 $idx = 0;
131 $shift = 0;
132 $result = 0;
133 while (true) {
134 $x = $this->trans_->readAll(1);
135 $arr = unpack('C', $x);
136 $byte = $arr[1];
137 $idx += 1;
138 $result |= ($byte & 0x7f) << $shift;
139 if (($byte >> 7) === 0) {
140 return $idx;
141 }
142 $shift += 7;
143 }
144
145 return $idx;
146 }
147
148 public function __construct($trans) {
149 parent::__construct($trans);
150 }
151
152 public function writeMessageBegin($name, $type, $seqid) {
153 $written =
154 $this->writeUByte(TCompactProtocol::PROTOCOL_ID) +
155 $this->writeUByte(TCompactProtocol::VERSION |
156 ($type << TCompactProtocol::TYPE_SHIFT_AMOUNT)) +
157 $this->writeVarint($seqid) +
158 $this->writeString($name);
159 $this->state = TCompactProtocol::STATE_VALUE_WRITE;
160 return $written;
161 }
162
163 public function writeMessageEnd() {
164 $this->state = TCompactProtocol::STATE_CLEAR;
165 return 0;
166 }
167
168 public function writeStructBegin($name) {
169 $this->structs[] = array($this->state, $this->lastFid);
170 $this->state = TCompactProtocol::STATE_FIELD_WRITE;
171 $this->lastFid = 0;
172 return 0;
173 }
174
175 public function writeStructEnd() {
176 $old_values = array_pop($this->structs);
177 $this->state = $old_values[0];
178 $this->lastFid = $old_values[1];
179 return 0;
180 }
181
182 public function writeFieldStop() {
183 return $this->writeByte(0);
184 }
185
186 public function writeFieldHeader($type, $fid) {
187 $written = 0;
188 $delta = $fid - $this->lastFid;
189 if (0 < $delta && $delta <= 15) {
190 $written = $this->writeUByte(($delta << 4) | $type);
191 } else {
192 $written = $this->writeByte($type) +
193 $this->writeI16($fid);
194 }
195 $this->lastFid = $fid;
196 return $written;
197 }
198
199 public function writeFieldBegin($field_name, $field_type, $field_id) {
200 if ($field_type == TTYPE::BOOL) {
201 $this->state = TCompactProtocol::STATE_BOOL_WRITE;
202 $this->boolFid = $field_id;
203 return 0;
204 } else {
205 $this->state = TCompactProtocol::STATE_VALUE_WRITE;
206 return $this->writeFieldHeader(self::$ctypes[$field_type], $field_id);
207 }
208 }
209
210 public function writeFieldEnd() {
211 $this->state = TCompactProtocol::STATE_FIELD_WRITE;
212 return 0;
213 }
214
215 public function writeCollectionBegin($etype, $size) {
216 $written = 0;
217 if ($size <= 14) {
218 $written = $this->writeUByte($size << 4 |
219 self::$ctypes[$etype]);
220 } else {
221 $written = $this->writeUByte(0xf0 |
222 self::$ctypes[$etype]) +
223 $this->writeVarint($size);
224 }
225 $this->containers[] = $this->state;
226 $this->state = TCompactProtocol::STATE_CONTAINER_WRITE;
227
228 return $written;
229 }
230
231 public function writeMapBegin($key_type, $val_type, $size) {
232 $written = 0;
233 if ($size == 0) {
234 $written = $this->writeByte(0);
235 } else {
236 $written = $this->writeVarint($size) +
237 $this->writeUByte(self::$ctypes[$key_type] << 4 |
238 self::$ctypes[$val_type]);
239 }
240 $this->containers[] = $this->state;
241 return $written;
242 }
243
244 public function writeCollectionEnd() {
245 $this->state = array_pop($this->containers);
246 return 0;
247 }
248
249 public function writeMapEnd() {
250 return $this->writeCollectionEnd();
251 }
252
253 public function writeListBegin($elem_type, $size) {
254 return $this->writeCollectionBegin($elem_type, $size);
255 }
256
257 public function writeListEnd() {
258 return $this->writeCollectionEnd();
259 }
260
261 public function writeSetBegin($elem_type, $size) {
262 return $this->writeCollectionBegin($elem_type, $size);
263 }
264
265 public function writeSetEnd() {
266 return $this->writeCollectionEnd();
267 }
268
269 public function writeBool($value) {
270 if ($this->state == TCompactProtocol::STATE_BOOL_WRITE) {
271 $ctype = TCompactProtocol::COMPACT_FALSE;
272 if ($value) {
273 $ctype = TCompactProtocol::COMPACT_TRUE;
274 }
275 return $this->writeFieldHeader($ctype, $this->boolFid);
276 } else if ($this->state == TCompactProtocol::STATE_CONTAINER_WRITE) {
277 return $this->writeByte($value ? 1 : 0);
278 } else {
279 throw new TProtocolException('Invalid state in compact protocol');
280 }
281 }
282
283 public function writeByte($value) {
284 $data = pack('c', $value);
285 $this->trans_->write($data, 1);
286 return 1;
287 }
288
289 public function writeUByte($byte) {
290 $this->trans_->write(pack('C', $byte), 1);
291 return 1;
292 }
293
294 public function writeI16($value) {
295 $thing = $this->toZigZag($value, 16);
296 return $this->writeVarint($thing);
297 }
298
299 public function writeI32($value) {
300 $thing = $this->toZigZag($value, 32);
301 return $this->writeVarint($thing);
302 }
303
304 public function writeDouble($value) {
305 $data = pack('d', $value);
306 $this->trans_->write(strrev($data), 8);
307 return 8;
308 }
309
310 public function writeString($value) {
311 $len = strlen($value);
312 $result = $this->writeVarint($len);
313 if ($len) {
314 $this->trans_->write($value, $len);
315 }
316 return $result + $len;
317 }
318
319 public function readFieldBegin(&$name, &$field_type, &$field_id) {
320 $result = $this->readUByte($field_type);
321
322 if (($field_type & 0x0f) == TType::STOP) {
323 $field_id = 0;
324 return $result;
325 }
326 $delta = $field_type >> 4;
327 if ($delta == 0) {
328 $result += $this->readI16($field_id);
329 } else {
330 $field_id = $this->lastFid + $delta;
331 }
332 $this->lastFid = $field_id;
333 $field_type = $this->getTType($field_type & 0x0f);
334 if ($field_type == TCompactProtocol::COMPACT_TRUE) {
335 $this->state = TCompactProtocol::STATE_BOOL_READ;
336 $this->boolValue = true;
337 } else if ($field_type == TCompactProtocol::COMPACT_FALSE) {
338 $this->state = TCompactProtocol::STATE_BOOL_READ;
339 $this->boolValue = false;
340 } else {
341 $this->state = TCompactProtocol::STATE_VALUE_READ;
342 }
343 return $result;
344 }
345
346 public function readFieldEnd() {
347 $this->state = TCompactProtocol::STATE_FIELD_READ;
348 return 0;
349 }
350
351 public function readUByte(&$value) {
352 $data = $this->trans_->readAll(1);
353 $arr = unpack('C', $data);
354 $value = $arr[1];
355 return 1;
356 }
357
358 public function readByte(&$value) {
359 $data = $this->trans_->readAll(1);
360 $arr = unpack('c', $data);
361 $value = $arr[1];
362 return 1;
363 }
364
365 public function readZigZag(&$value) {
366 $result = $this->readVarint($value);
367 $value = $this->fromZigZag($value);
368 return $result;
369 }
370
371 public function readMessageBegin(&$name, &$type, &$seqid) {
372 $protoId = 0;
373 $result = $this->readUByte($protoId);
374 if ($protoId != TCompactProtocol::PROTOCOL_ID) {
375 throw new TProtocolException('Bad protocol id in TCompact message');
376 }
377 $verType = 0;
378 $result += $this->readUByte($verType);
379 $type = ($verType & TCompactProtocol::TYPE_MASK) >>
380 TCompactProtocol::TYPE_SHIFT_AMOUNT;
381 $version = $verType & TCompactProtocol::VERSION_MASK;
382 if ($version != TCompactProtocol::VERSION) {
383 throw new TProtocolException('Bad version in TCompact message');
384 }
385 $result += $this->readVarint($seqId);
386 $name += $this->readString($name);
387
388 return $result;
389 }
390
391 public function readMessageEnd() {
392 return 0;
393 }
394
395 public function readStructBegin(&$name) {
396 $name = ''; // unused
397 $this->structs[] = array($this->state, $this->lastFid);
398 $this->state = TCompactProtocol::STATE_FIELD_READ;
399 $this->lastFid = 0;
400 return 0;
401 }
402
403 public function readStructEnd() {
404 $last = array_pop($this->structs);
405 $this->state = $last[0];
406 $this->lastFid = $last[1];
407 return 0;
408 }
409
410 public function readCollectionBegin(&$type, &$size) {
411 $sizeType = 0;
412 $result = $this->readUByte($sizeType);
413 $size = $sizeType >> 4;
414 $type = $this->getTType($sizeType);
415 if ($size == 15) {
416 $result += $this->readVarint($size);
417 }
418 $this->containers[] = $this->state;
419 $this->state = TCompactProtocol::STATE_CONTAINER_READ;
420
421 return $result;
422 }
423
424 public function readMapBegin(&$key_type, &$val_type, &$size) {
425 $result = $this->readVarint($size);
426 $types = 0;
427 if ($size > 0) {
428 $result += $this->readUByte($types);
429 }
430 $val_type = $this->getTType($types);
431 $key_type = $this->getTType($types >> 4);
432 $this->containers[] = $this->state;
433 $this->state = TCompactProtocol::STATE_CONTAINER_READ;
434
435 return $result;
436 }
437
438 public function readCollectionEnd() {
439 $this->state = array_pop($this->containers);
440 return 0;
441 }
442
443 public function readMapEnd() {
444 return $this->readCollectionEnd();
445 }
446
447 public function readListBegin(&$elem_type, &$size) {
448 return $this->readCollectionBegin($elem_type, $size);
449 }
450
451 public function readListEnd() {
452 return $this->readCollectionEnd();
453 }
454
455 public function readSetBegin(&$elem_type, &$size) {
456 return $this->readCollectionBegin($elem_type, $size);
457 }
458
459 public function readSetEnd() {
460 return $this->readCollectionEnd();
461 }
462
463 public function readBool(&$value) {
464 if ($this->state == TCompactProtocol::STATE_BOOL_READ) {
465 $value = $this->boolValue;
466 return 0;
467 } else if ($this->state == TCompactProtocol::STATE_CONTAINER_READ) {
468 return $this->readByte($value);
469 } else {
470 throw new TProtocolException('Invalid state in compact protocol');
471 }
472 }
473
474 public function readI16(&$value) {
475 return $this->readZigZag($value);
476 }
477
478 public function readI32(&$value) {
479 return $this->readZigZag($value);
480 }
481
482 public function readDouble(&$value) {
483 $data = strrev($this->trans_->readAll(8));
484 $arr = unpack('d', $data);
485 $value = $arr[1];
486 return 8;
487 }
488
489 public function readString(&$value) {
490 $result = $this->readVarint($len);
491 if ($len) {
492 $value = $this->trans_->readAll($len);
493 } else {
494 $value = '';
495 }
496 return $result + $len;
497 }
498
499 public function getTType($byte) {
500 return self::$ttypes[$byte & 0x0f];
501 }
502
503 // If we are on a 32bit architecture we have to explicitly deal with
504 // 64-bit twos-complement arithmetic since PHP wants to treat all ints
505 // as signed and any int over 2^31 - 1 as a float
506
507 // Read and write I64 as two 32 bit numbers $hi and $lo
508
509 public function readI64(&$value) {
510 // Read varint from wire
511 $hi = 0;
512 $lo = 0;
513
514 $idx = 0;
515 $shift = 0;
516
517 while (true) {
518 $x = $this->trans_->readAll(1);
519 $arr = unpack('C', $x);
520 $byte = $arr[1];
521 $idx += 1;
522 if ($shift < 32) {
523 $lo |= (($byte & 0x7f) << $shift) &
524 0x00000000ffffffff;
525 }
526 // Shift hi and lo together.
527 if ($shift >= 32) {
528 $hi |= (($byte & 0x7f) << ($shift - 32));
529 } else if ($shift > 25) {
530 $hi |= (($byte & 0x7f) >> ($shift - 25));
531 }
532 if (($byte >> 7) === 0) {
533 break;
534 }
535 $shift += 7;
536 }
537
538 // Now, unzig it.
539 $xorer = 0;
540 if ($lo & 1) {
541 $xorer = 0xffffffff;
542 }
543 $lo = ($lo >> 1) & 0x7fffffff;
544 $lo = $lo | (($hi & 1) << 31);
545 $hi = ($hi >> 1) ^ $xorer;
546 $lo = $lo ^ $xorer;
547
548 // Now put $hi and $lo back together
549 if (true) {
550 $isNeg = $hi < 0;
551
552 // Check for a negative
553 if ($isNeg) {
554 $hi = ~$hi & (int)0xffffffff;
555 $lo = ~$lo & (int)0xffffffff;
556
557 if ($lo == (int)0xffffffff) {
558 $hi++;
559 $lo = 0;
560 } else {
561 $lo++;
562 }
563 }
564
565 // Force 32bit words in excess of 2G to be positive - we deal with sign
566 // explicitly below
567
568 if ($hi & (int)0x80000000) {
569 $hi &= (int)0x7fffffff;
570 $hi += 0x80000000;
571 }
572
573 if ($lo & (int)0x80000000) {
574 $lo &= (int)0x7fffffff;
575 $lo += 0x80000000;
576 }
577
578 $value = $hi * 4294967296 + $lo;
579
580 if ($isNeg) {
581 $value = 0 - $value;
582 }
583 } else {
584
585 // Upcast negatives in LSB bit
586 if ($arr[2] & 0x80000000) {
587 $arr[2] = $arr[2] & 0xffffffff;
588 }
589
590 // Check for a negative
591 if ($arr[1] & 0x80000000) {
592 $arr[1] = $arr[1] & 0xffffffff;
593 $arr[1] = $arr[1] ^ 0xffffffff;
594 $arr[2] = $arr[2] ^ 0xffffffff;
595 $value = 0 - $arr[1] * 4294967296 - $arr[2] - 1;
596 } else {
597 $value = $arr[1] * 4294967296 + $arr[2];
598 }
599 }
600
601 return $idx;
602 }
603
604 public function writeI64($value) {
605 // If we are in an I32 range, use the easy method below.
606 if (($value > 4294967296) || ($value < -4294967296)) {
607 // Convert $value to $hi and $lo
608 $neg = $value < 0;
609
610 if ($neg) {
611 $value *= -1;
612 }
613
614 $hi = (int)$value >> 32;
615 $lo = (int)$value & 0xffffffff;
616
617 if ($neg) {
618 $hi = ~$hi;
619 $lo = ~$lo;
620 if (($lo & (int)0xffffffff) == (int)0xffffffff) {
621 $lo = 0;
622 $hi++;
623 } else {
624 $lo++;
625 }
626 }
627
628 // Now do the zigging and zagging.
629 $xorer = 0;
630 if ($neg) {
631 $xorer = 0xffffffff;
632 }
633 $lowbit = ($lo >> 31) & 1;
634 $hi = ($hi << 1) | $lowbit;
635 $lo = ($lo << 1);
636 $lo = ($lo ^ $xorer) & 0xffffffff;
637 $hi = ($hi ^ $xorer) & 0xffffffff;
638
639 // now write out the varint, ensuring we shift both hi and lo
640 $out = "";
641 while (true) {
642 if (($lo & ~0x7f) === 0 &&
643 $hi === 0) {
644 $out .= chr($lo);
645 break;
646 } else {
647 $out .= chr(($lo & 0xff) | 0x80);
648 $lo = $lo >> 7;
649 $lo = $lo | ($hi << 25);
650 $hi = $hi >> 7;
651 // Right shift carries sign, but we don't want it to.
652 $hi = $hi & (127 << 25);
653 }
654 }
655
656 $ret = strlen($out);
657 $this->trans_->write($out, $ret);
658
659 return $ret;
660 } else {
661 return $this->writeVarint($this->toZigZag($value, 64));
662 }
663 }
664}
665
666/**
667 * Compact Protocol Factory
668 */
669class TCompcatProtocolFactory implements TProtocolFactory {
670
671 public function __construct() {
672 }
673
674 public function getProtocol($trans) {
675 return new TCompactProtocol($trans);
676 }
677}
678