<?hh
/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @package thrift.protocol.binary
 */

/**
 * Utility class for serializing and deserializing
 * a thrift object using TBinaryProtocolAccelerated.
 */
class TBinarySerializer extends TProtocolSerializer {

  // NOTE(rmarin): Because thrift_protocol_write_binary
  // adds a begin message prefix, you cannot specify
  // a transport in which to serialize an object. It has to
  // be a string. Otherwise we will break the compatibility with
  // normal deserialization.
  public static function serialize(
    IThriftStruct $object,
    bool $disable_hphp_extension = false,
  ): string {
    $transport = new TMemoryBuffer();
    $protocol = new TBinaryProtocolAccelerated($transport);
    if (function_exists('thrift_protocol_write_binary') &&
        !$disable_hphp_extension) {
      thrift_protocol_write_binary(
        $protocol,
        $object->getName(),
        TMessageType::REPLY,
        $object,
        0,
        $protocol->isStrictWrite(),
      );

      $unused_name = null;
      $unused_type = null;
      $unused_seqid = null;
      $protocol->readMessageBegin(
        inout $unused_name,
        inout $unused_type,
        inout $unused_seqid,
      );
    } else {
      $object->write($protocol);
    }
    return $transport->getBuffer();
  }

  public static function deserialize<T as IThriftStruct>(
    string $str,
    T $object,
    bool $disable_hphp_extension = false,
  ): T {
    $transport = new TMemoryBuffer();
    $protocol = new TBinaryProtocolAccelerated($transport);
    if (function_exists('thrift_protocol_read_binary') &&
        !$disable_hphp_extension) {
      $protocol->writeMessageBegin('', TMessageType::REPLY, 0);
      $transport->write($str);
      $object = thrift_protocol_read_binary(
        $protocol,
        get_class($object),
        $protocol->isStrictRead(),
      );
    } else {
      $transport->write($str);
      $object->read($protocol);
    }
    return $object;
  }
}


<?hh
/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @package thrift.protocol.compact
 */

/**
 * Do not use this class.
 *
 * This class exists for backwards compatibility, use
 * TCompactProtocolAccelerated or TCompactProtocolUnaccelerated
 * @deprecated
 */
class TCompactProtocol extends TCompactProtocolAccelerated {
  public function __construct($trans) {
    parent::__construct($trans);
  }
}


<?hh
/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @package thrift.protocol.compact
 */

/**
 * Accelerated compact protocol: used in conjunction with a Thrift HPHP
 * extension for faster serialization and deserialization. The generated Thrift
 * code uses instanceof to look for this class and call into the extension.
 */
class TCompactProtocolAccelerated extends TCompactProtocolBase {
  // The generated Thrift code calls this as a final check. If it returns true,
  // the HPHP extension will be used; if it returns false, the above PHP code is
  // used as a fallback.
  public static function checkVersion($v) {
    return $v == 1;
  }

  public function __construct($trans) {
    // If the transport doesn't implement putBack, wrap it in a
    // TBufferedTransport (which does)
    if (!($trans instanceof IThriftBufferedTransport)) {
      $trans = new TBufferedTransport($trans);
    }

    parent::__construct($trans);
  }
}


<?hh
/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Compact implementation of the Thrift protocol.
 *
 */
abstract class TCompactProtocolBase extends TProtocol {

  const COMPACT_STOP = 0x00;
  const COMPACT_TRUE = 0x01;
  const COMPACT_FALSE = 0x02;
  const COMPACT_BYTE = 0x03;
  const COMPACT_I16 = 0x04;
  const COMPACT_I32 = 0x05;
  const COMPACT_I64 = 0x06;
  const COMPACT_DOUBLE = 0x07;
  const COMPACT_BINARY = 0x08;
  const COMPACT_LIST = 0x09;
  const COMPACT_SET = 0x0A;
  const COMPACT_MAP = 0x0B;
  const COMPACT_STRUCT = 0x0C;
  const COMPACT_FLOAT = 0x0D;

  const STATE_CLEAR = 0;
  const STATE_FIELD_WRITE = 1;
  const STATE_VALUE_WRITE = 2;
  const STATE_CONTAINER_WRITE = 3;
  const STATE_BOOL_WRITE = 4;
  const STATE_FIELD_READ = 5;
  const STATE_CONTAINER_READ = 6;
  const STATE_VALUE_READ = 7;
  const STATE_BOOL_READ = 8;

  const VERSION_MASK = 0x1f;
  const VERSION = 2;
  const VERSION_LOW = 1;
  const VERSION_DOUBLE_BE = 2;
  const PROTOCOL_ID = 0x82;
  const TYPE_MASK = 0xe0;
  const TYPE_SHIFT_AMOUNT = 5;

  const I16_MIN = -(1 << 15);
  const I16_MAX = (1 << 15) - 1;
  const I32_MIN = -(1 << 31);
  const I32_MAX = (1 << 31) - 1;

  protected static
    $ctypes = darray[
      TType::STOP => self::COMPACT_STOP,
      TType::BOOL => self::COMPACT_TRUE, // used for collection
      TType::BYTE => self::COMPACT_BYTE,
      TType::I16 => self::COMPACT_I16,
      TType::I32 => self::COMPACT_I32,
      TType::I64 => self::COMPACT_I64,
      TType::DOUBLE => self::COMPACT_DOUBLE,
      TType::FLOAT => self::COMPACT_FLOAT,
      TType::STRING => self::COMPACT_BINARY,
      TType::STRUCT => self::COMPACT_STRUCT,
      TType::LST => self::COMPACT_LIST,
      TType::SET => self::COMPACT_SET,
      TType::MAP => self::COMPACT_MAP,
    ];

  protected static
    $ttypes = darray[
      self::COMPACT_STOP => TType::STOP,
      self::COMPACT_TRUE => TType::BOOL, // used for collection
      self::COMPACT_FALSE => TType::BOOL,
      self::COMPACT_BYTE => TType::BYTE,
      self::COMPACT_I16 => TType::I16,
      self::COMPACT_I32 => TType::I32,
      self::COMPACT_I64 => TType::I64,
      self::COMPACT_DOUBLE => TType::DOUBLE,
      self::COMPACT_FLOAT => TType::FLOAT,
      self::COMPACT_BINARY => TType::STRING,
      self::COMPACT_STRUCT => TType::STRUCT,
      self::COMPACT_LIST => TType::LST,
      self::COMPACT_SET => TType::SET,
      self::COMPACT_MAP => TType::MAP,
    ];

  protected $state = self::STATE_CLEAR;
  protected $lastFid = 0;
  protected $boolFid = null;
  protected $boolValue = null;
  protected $structs = varray[];
  protected $containers = varray[];
  protected $version = self::VERSION;

  public function setWriteVersion($ver) {
    $this->version = $ver;
  }

  public function checkRange($value, $min, $max) {
    if ($value < $min || $value > $max) {
      throw new TProtocolException("Value is out of range");
    }
  }

  // Some varint / zigzag helper methods
  public function toZigZag($n, $bits) {
    return ($n << 1) ^ ($n >> ($bits - 1));
  }

  public function fromZigZag($n) {
    return ($n >> 1) ^ -($n & 1);
  }

  public function getVarint($data) {
    $out = "";
    while (true) {
      if (($data & ~0x7f) === 0) {
        $out .= chr($data);
        break;
      } else {
        $out .= chr(($data & 0xff) | 0x80);
        $data = $data >> 7;
      }
    }
    return $out;
  }

  public function writeVarint($data) {
    $out = $this->getVarint($data);
    $this->trans_->write($out);
    return strlen($out);
  }

  public function readVarint(&$result) {
    $idx = 0;
    $shift = 0;
    $result = 0;
    while (true) {
      $x = $this->trans_->readAll(1);
      $byte = ord($x);
      $idx += 1;
      $result |= ($byte & 0x7f) << $shift;
      if (($byte >> 7) === 0) {
        return $idx;
      }
      $shift += 7;
    }

    return $idx;
  }

  public function __construct($trans) {
    parent::__construct($trans);
  }

  <<__Override>>
  public function writeMessageBegin($name, $type, $seqid) {
    $written =
      $this->writeUByte(self::PROTOCOL_ID) +
      $this->writeUByte(
        $this->version | ($type << self::TYPE_SHIFT_AMOUNT),
      ) +
      $this->writeVarint($seqid) +
      $this->writeString($name);
    $this->state = self::STATE_VALUE_WRITE;
    return $written;
  }

  <<__Override>>
  public function writeMessageEnd() {
    $this->state = self::STATE_CLEAR;
    return 0;
  }

  <<__Override>>
  public function writeStructBegin($name) {
    $this->structs[] = varray[$this->state, $this->lastFid];
    $this->state = self::STATE_FIELD_WRITE;
    $this->lastFid = 0;
    return 0;
  }

  <<__Override>>
  public function writeStructEnd() {
    $old_values = array_pop(&$this->structs);
    $this->state = $old_values[0];
    $this->lastFid = $old_values[1];
    return 0;
  }

  <<__Override>>
  public function writeFieldStop() {
    return $this->writeByte(0);
  }

  public function writeFieldHeader($type, $fid) {
    $written = 0;
    $delta = $fid - $this->lastFid;
    if (0 < $delta && $delta <= 15) {
      $written = $this->writeUByte(($delta << 4) | $type);
    } else {
      $written = $this->writeByte($type) + $this->writeI16($fid);
    }
    $this->lastFid = $fid;
    return $written;
  }

  <<__Override>>
  public function writeFieldBegin($field_name, $field_type, $field_id) {
    if ($field_type == TType::BOOL) {
      $this->state = self::STATE_BOOL_WRITE;
      $this->boolFid = $field_id;
      return 0;
    } else {
      $this->state = self::STATE_VALUE_WRITE;
      return $this->writeFieldHeader(self::$ctypes[$field_type], $field_id);
    }
  }

  <<__Override>>
  public function writeFieldEnd() {
    $this->state = self::STATE_FIELD_WRITE;
    return 0;
  }

  public function writeCollectionBegin($etype, $size) {
    $written = 0;
    if ($size <= 14) {
      $written = $this->writeUByte($size << 4 | self::$ctypes[$etype]);
    } else {
      $written =
        $this->writeUByte(0xf0 | self::$ctypes[$etype]) +
        $this->writeVarint($size);
    }
    $this->containers[] = $this->state;
    $this->state = self::STATE_CONTAINER_WRITE;

    return $written;
  }

  <<__Override>>
  public function writeMapBegin($key_type, $val_type, $size) {
    $written = 0;
    if ($size == 0) {
      $written = $this->writeByte(0);
    } else {
      $written =
        $this->writeVarint($size) +
        $this->writeUByte(
          self::$ctypes[$key_type] << 4 | self::$ctypes[$val_type],
        );
    }
    $this->containers[] = $this->state;
    $this->state = self::STATE_CONTAINER_WRITE;
    return $written;
  }

  public function writeCollectionEnd() {
    $this->state = array_pop(&$this->containers);
    return 0;
  }

  <<__Override>>
  public function writeMapEnd() {
    return $this->writeCollectionEnd();
  }

  <<__Override>>
  public function writeListBegin($elem_type, $size) {
    return $this->writeCollectionBegin($elem_type, $size);
  }

  <<__Override>>
  public function writeListEnd() {
    return $this->writeCollectionEnd();
  }

  <<__Override>>
  public function writeSetBegin($elem_type, $size) {
    return $this->writeCollectionBegin($elem_type, $size);
  }

  <<__Override>>
  public function writeSetEnd() {
    return $this->writeCollectionEnd();
  }

  <<__Override>>
  public function writeBool($value) {
    if ($this->state == self::STATE_BOOL_WRITE) {
      $ctype = self::COMPACT_FALSE;
      if ($value) {
        $ctype = self::COMPACT_TRUE;
      }
      return $this->writeFieldHeader($ctype, $this->boolFid);
    } else if ($this->state == self::STATE_CONTAINER_WRITE) {
      return $this->writeByte(
        $value
          ? self::COMPACT_TRUE
          : self::COMPACT_FALSE,
      );
    } else {
      throw new TProtocolException('Invalid state in compact protocol');
    }
  }

  <<__Override>>
  public function writeByte($value) {
    $this->trans_->write(chr($value));
    return 1;
  }

  public function writeUByte($byte) {
    $this->trans_->write(chr($byte));
    return 1;
  }

  <<__Override>>
  public function writeI16($value) {
    $this->checkRange($value, self::I16_MIN, self::I16_MAX);
    $thing = $this->toZigZag($value, 16);
    return $this->writeVarint($thing);
  }

  <<__Override>>
  public function writeI32($value) {
    $this->checkRange($value, self::I32_MIN, self::I32_MAX);
    $thing = $this->toZigZag($value, 32);
    return $this->writeVarint($thing);
  }

  <<__Override>>
  public function writeDouble($value) {
    $data = pack('d', $value);
    if ($this->version >= self::VERSION_DOUBLE_BE) {
      $data = strrev($data);
    }
    $this->trans_->write($data);
    return 8;
  }

  <<__Override>>
  public function writeFloat($value) {
    $data = pack('f', $value);
    $data = strrev($data);
    $this->trans_->write($data);
    return 4;
  }

  <<__Override>>
  public function writeString($value) {
    $value = (string) $value;
    $len = strlen($value);
    $result = $this->writeVarint($len);
    if ($len) {
      $this->trans_->write($value);
    }
    return $result + $len;
  }

  <<__Override>>
  public function readFieldBegin(
    inout $name,
    inout $field_type,
    inout $field_id,
  ) {
    $result = $this->readUByte(&$field_type);
    $delta = $field_type >> 4;
    $field_type = $field_type & 0x0f;

    if ($field_type == self::COMPACT_STOP) {
      $field_id = 0;
      $field_type = $this->getTType($field_type);
      return $result;
    }

    if ($delta == 0) {
      $result += $this->readI16(&$field_id);
    } else {
      $field_id = $this->lastFid + $delta;
    }
    $this->lastFid = $field_id;

    if ($field_type == self::COMPACT_TRUE) {
      $this->state = self::STATE_BOOL_READ;
      $this->boolValue = true;
    } else if ($field_type == self::COMPACT_FALSE) {
      $this->state = self::STATE_BOOL_READ;
      $this->boolValue = false;
    } else {
      $this->state = self::STATE_VALUE_READ;
    }

    $field_type = $this->getTType($field_type);
    return $result;
  }

  <<__Override>>
  public function readFieldEnd() {
    $this->state = self::STATE_FIELD_READ;
    return 0;
  }

  public function readUByte(&$value) {
    $data = $this->trans_->readAll(1);
    $value = ord($data);
    return 1;
  }

  <<__Override>>
  public function readByte(&$value) {
    $data = $this->trans_->readAll(1);
    $value = ord($data);
    if ($value > 0x7f) {
      $value = 0 - (($value - 1) ^ 0xff);
    }
    return 1;
  }

  public function readZigZag(&$value) {
    $result = $this->readVarint(&$value);
    $value = $this->fromZigZag($value);
    return $result;
  }

  <<__Override>>
  public function readMessageBegin(inout $name, inout $type, inout $seqid) {
    $protoId = 0;
    $result = $this->readUByte(&$protoId);
    if ($protoId != self::PROTOCOL_ID) {
      throw new TProtocolException('Bad protocol id in TCompact message');
    }
    $verType = 0;
    $result += $this->readUByte(&$verType);
    $type =
      ($verType & self::TYPE_MASK) >>
      self::TYPE_SHIFT_AMOUNT;
    $this->version = $verType & self::VERSION_MASK;
    if (!($this->version <= self::VERSION &&
          $this->version >= self::VERSION_LOW)) {
      throw new TProtocolException('Bad version in TCompact message');
    }
    $result += $this->readVarint(&$seqid);
    $result += $this->readString(&$name);

    return $result;
  }

  <<__Override>>
  public function readMessageEnd() {
    return 0;
  }

  <<__Override>>
  public function readStructBegin(inout $name) {
    $name = ''; // unused
    $this->structs[] = varray[$this->state, $this->lastFid];
    $this->state = self::STATE_FIELD_READ;
    $this->lastFid = 0;
    return 0;
  }

  <<__Override>>
  public function readStructEnd() {
    $last = array_pop(&$this->structs);
    $this->state = $last[0];
    $this->lastFid = $last[1];
    return 0;
  }

  public function readCollectionBegin(&$type, &$size) {
    $sizeType = 0;
    $result = $this->readUByte(&$sizeType);
    $size = $sizeType >> 4;
    $type = $this->getTType($sizeType);
    if ($size == 15) {
      $result += $this->readVarint(&$size);
    }
    $this->containers[] = $this->state;
    $this->state = self::STATE_CONTAINER_READ;

    return $result;
  }

  <<__Override>>
  public function readMapBegin(inout $key_type, inout $val_type, inout $size) {
    $result = $this->readVarint(&$size);
    $types = 0;
    if ($size > 0) {
      $result += $this->readUByte(&$types);
    }
    $val_type = $this->getTType($types);
    $key_type = $this->getTType($types >> 4);
    $this->containers[] = $this->state;
    $this->state = self::STATE_CONTAINER_READ;

    return $result;
  }

  public function readCollectionEnd() {
    $this->state = array_pop(&$this->containers);
    return 0;
  }

  <<__Override>>
  public function readMapEnd() {
    return $this->readCollectionEnd();
  }

  <<__Override>>
  public function readListBegin(&$elem_type, &$size) {
    return $this->readCollectionBegin(&$elem_type, &$size);
  }

  <<__Override>>
  public function readListEnd() {
    return $this->readCollectionEnd();
  }

  <<__Override>>
  public function readSetBegin(&$elem_type, &$size) {
    return $this->readCollectionBegin(&$elem_type, &$size);
  }

  <<__Override>>
  public function readSetEnd() {
    return $this->readCollectionEnd();
  }

  <<__Override>>
  public function readBool(&$value) {
    if ($this->state == self::STATE_BOOL_READ) {
      $value = $this->boolValue;
      return 0;
    } else if ($this->state == self::STATE_CONTAINER_READ) {
      $result = $this->readByte(&$value);
      $value = $value == self::COMPACT_TRUE;
      return $result;
    } else {
      throw new TProtocolException('Invalid state in compact protocol');
    }
  }

  <<__Override>>
  public function readI16(&$value) {
    return $this->readZigZag(&$value);
  }

  <<__Override>>
  public function readI32(&$value) {
    return $this->readZigZag(&$value);
  }

  <<__Override>>
  public function readDouble(&$value) {
    $data = $this->trans_->readAll(8);
    if ($this->version >= self::VERSION_DOUBLE_BE) {
      $data = strrev($data);
    }
    $arr = unpack('d', $data);
    $value = $arr[1];
    return 8;
  }

  <<__Override>>
  public function readFloat(&$value) {
    $data = $this->trans_->readAll(4);
    $data = strrev($data);
    $arr = unpack('f', $data);
    $value = $arr[1];
    return 4;
  }

  <<__Override>>
  public function readString(&$value) {
    $len = 0;
    $result = $this->readVarint(&$len);
    if ($len) {
      $value = $this->trans_->readAll($len);
    } else {
      $value = '';
    }
    return $result + $len;
  }

  public function getTType($byte) {
    return self::$ttypes[$byte & 0x0f];
  }

  // If we are on a 32bit architecture we have to explicitly deal with
  // 64-bit twos-complement arithmetic since PHP wants to treat all ints
  // as signed and any int over 2^31 - 1 as a float

  // Read and write I64 as two 32 bit numbers $hi and $lo

  <<__Override>>
  public function readI64(&$value) {
    // Read varint from wire
    $hi = 0;
    $lo = 0;

    $idx = 0;
    $shift = 0;
    $arr = darray[];

    while (true) {
      $x = $this->trans_->readAll(1);
      $byte = ord($x);
      $idx += 1;
      if ($shift < 32) {
        $lo |= (($byte & 0x7f) << $shift) & 0x00000000ffffffff;
      }
      // Shift hi and lo together.
      if ($shift >= 32) {
        $hi |= (($byte & 0x7f) << ($shift - 32));
      } else if ($shift > 24) {
        $hi |= (($byte & 0x7f) >> ($shift - 24));
      }
      if (($byte >> 7) === 0) {
        break;
      }
      $shift += 7;
    }

    // Now, unzig it.
    $xorer = 0;
    if ($lo & 1) {
      $xorer = 0xffffffff;
    }
    $lo = ($lo >> 1) & 0x7fffffff;
    $lo = $lo | (($hi & 1) << 31);
    $hi = ($hi >> 1) ^ $xorer;
    $lo = $lo ^ $xorer;

    // Now put $hi and $lo back together
    if (true) {
      $isNeg = $hi < 0;

      // Check for a negative
      if ($isNeg) {
        $hi = ~$hi & (int) 0xffffffff;
        $lo = ~$lo & (int) 0xffffffff;

        if ($lo == (int) 0xffffffff) {
          $hi++;
          $lo = 0;
        } else {
          $lo++;
        }
      }

      // Force 32bit words in excess of 2G to be positive - we deal with sign
      // explicitly below

      if ($hi & (int) 0x80000000) {
        $hi &= (int) 0x7fffffff;
        $hi += 0x80000000;
      }

      if ($lo & (int) 0x80000000) {
        $lo &= (int) 0x7fffffff;
        $lo += 0x80000000;
      }

      $value = $hi * 4294967296 + $lo;

      if ($isNeg) {
        $value = 0 - $value;
      }
    } else {

      // Upcast negatives in LSB bit
      if ($arr[2] & 0x80000000) {
        $arr[2] = $arr[2] & 0xffffffff;
      }

      // Check for a negative
      if ($arr[1] & 0x80000000) {
        $arr[1] = $arr[1] & 0xffffffff;
        $arr[1] = $arr[1] ^ 0xffffffff;
        $arr[2] = $arr[2] ^ 0xffffffff;
        $value = 0 - $arr[1] * 4294967296 - $arr[2] - 1;
      } else {
        $value = $arr[1] * 4294967296 + $arr[2];
      }
    }

    return $idx;
  }

  <<__Override>>
  public function writeI64($value) {
    // If we are in an I32 range, use the easy method below.
    if (($value > 4294967296) || ($value < -4294967296)) {
      // Convert $value to $hi and $lo
      $neg = $value < 0;

      if ($neg) {
        $value *= -1;
      }

      $hi = (int) $value >> 32;
      $lo = (int) $value & 0xffffffff;

      if ($neg) {
        $hi = ~$hi;
        $lo = ~$lo;
        if (($lo & (int) 0xffffffff) == (int) 0xffffffff) {
          $lo = 0;
          $hi++;
        } else {
          $lo++;
        }
      }

      // Now do the zigging and zagging.
      $xorer = 0;
      if ($neg) {
        $xorer = 0xffffffff;
      }
      $lowbit = ($lo >> 31) & 1;
      $hi = ($hi << 1) | $lowbit;
      $lo = ($lo << 1);
      $lo = ($lo ^ $xorer) & 0xffffffff;
      $hi = ($hi ^ $xorer) & 0xffffffff;

      // now write out the varint, ensuring we shift both hi and lo
      $out = "";
      while (true) {
        if (($lo & ~0x7f) === 0 && $hi === 0) {
          $out .= chr($lo);
          break;
        } else {
          $out .= chr(($lo & 0xff) | 0x80);
          $lo = $lo >> 7;
          $lo = $lo | ($hi << 25);
          $hi = $hi >> 7;
          // Right shift carries sign, but we don't want it to.
          $hi = $hi & (127 << 25);
        }
      }

      $this->trans_->write($out);

      return strlen($out);
    } else {
      return $this->writeVarint($this->toZigZag($value, 64));
    }
  }
}


<?hh
/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @package thrift.protocol.compact
 */

/**
 * Compact Protocol Factory
 */
class TCompactProtocolFactory implements TProtocolFactory {

  public function __construct() {}

  public function getProtocol(TTransport $trans): TProtocol {
    return new TCompactProtocolAccelerated($trans);
  }
}


<?hh
/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @package thrift.protocol.compact
 */

/**
 * Old slow unaccelerated protocol.
 */
class TCompactProtocolUnaccelerated extends TCompactProtocolBase {
  public function __construct($trans) {
    parent::__construct($trans);
  }
}


<?hh
/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @package thrift.protocol.compact
 */

/**
 * Utility class for serializing and deserializing
 * a thrift object using TCompactProtocol.
 */
class TCompactSerializer extends TProtocolSerializer {

  public static function serialize(
    IThriftStruct $object,
    ?int $override_version = null,
    bool $disable_hphp_extension = false,
  ): string {
    $transport = new TMemoryBuffer();
    $protocol = new TCompactProtocolAccelerated($transport);

    $use_hphp_extension =
      function_exists('thrift_protocol_write_compact') &&
      !$disable_hphp_extension;

    $last_version = null;
    if ($override_version !== null) {
      $protocol->setWriteVersion($override_version);
      if (function_exists('thrift_protocol_set_compact_version')) {
        $last_version =
          thrift_protocol_set_compact_version($override_version);
      } else {
        $use_hphp_extension = false;
      }
    }

    if ($use_hphp_extension) {
      thrift_protocol_write_compact(
        $protocol,
        $object->getName(),
        TMessageType::REPLY,
        $object,
        0,
      );
      if ($last_version !== null) {
        thrift_protocol_set_compact_version($last_version);
      }
      $unused_name = null;
      $unused_type = null;
      $unused_seqid = null;
      $protocol->readMessageBegin(
        inout $unused_name,
        inout $unused_type,
        inout $unused_seqid,
      );
    } else {
      $object->write($protocol);
    }
    return $transport->getBuffer();
  }

  public static function deserialize<T as IThriftStruct>(
    string $str,
    T $object,
    ?int $override_version = null,
    bool $disable_hphp_extension = false,
  ) {
    $transport = new TMemoryBuffer();
    $protocol = new TCompactProtocolAccelerated($transport);

    $use_hphp_extension =
      function_exists('thrift_protocol_read_compact') &&
      !$disable_hphp_extension;

    if ($override_version !== null) {
      $protocol->setWriteVersion($override_version);
      if (!function_exists('thrift_protocol_set_compact_version')) {
        $use_hphp_extension = false;
      }
    }

    if ($use_hphp_extension) {
      $protocol->writeMessageBegin('', TMessageType::REPLY, 0);
      $transport->write($str);
      $object = thrift_protocol_read_compact($protocol, get_class($object));
    } else {
      $transport->write($str);
      /* HH_FIXME[2060] Trust me, I know what I'm doing */
      $object->read($protocol);
    }
    return $object;
  }
}


<?hh
/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @package thrift.protocol.simplejson
 */

/**
 * Protocol for encoding/decoding simple json
 */
class TSimpleJSONProtocol extends TProtocol {
  const VERSION_1 = 0x80010000;

  private IThriftBufferedTransport $bufTrans;
  private Vector<TSimpleJSONProtocolContext> $contexts;

  public function __construct(TTransport $trans) {
    $this->contexts = Vector {};
    if (!($trans instanceof IThriftBufferedTransport)) {
      $trans = new TBufferedTransport($trans);
    }
    $this->bufTrans = $trans;
    parent::__construct($trans);
    $this->contexts
      ->add(new TSimpleJSONProtocolContext($this->trans_, $this->bufTrans));
  }

  private function pushListWriteContext(): int {
    return $this->pushWriteContext(
      new TSimpleJSONProtocolListContext($this->trans_, $this->bufTrans),
    );
  }

  private function pushMapWriteContext(): int {
    return $this->pushWriteContext(
      new TSimpleJSONProtocolMapContext($this->trans_, $this->bufTrans),
    );
  }

  private function pushListReadContext(): void {
    $this->pushReadContext(
      new TSimpleJSONProtocolListContext($this->trans_, $this->bufTrans),
    );
  }

  private function pushMapReadContext(): void {
    $this->pushReadContext(
      new TSimpleJSONProtocolMapContext($this->trans_, $this->bufTrans),
    );
  }

  private function pushWriteContext(TSimpleJSONProtocolContext $ctx): int {
    $this->contexts->add($ctx);
    return $ctx->writeStart();
  }

  private function popWriteContext(): int {
    $ctx = $this->contexts->pop();
    return $ctx->writeEnd();
  }

  private function pushReadContext(TSimpleJSONProtocolContext $ctx): void {
    $this->contexts->add($ctx);
    $ctx->readStart();
  }

  private function popReadContext(): void {
    $ctx = $this->contexts->pop();
    $ctx->readEnd();
  }

  private function getContext(): TSimpleJSONProtocolContext {
    return $this->contexts->at($this->contexts->count() - 1);
  }

  public function writeMessageBegin($name, $type, $seqid) {
    return
      $this->getContext()->writeSeparator() +
      $this->pushListWriteContext() +
      $this->writeI32(self::VERSION_1) +
      $this->writeString($name) +
      $this->writeI32($type) +
      $this->writeI32($seqid);
  }

  public function writeMessageEnd() {
    return $this->popWriteContext();
  }

  public function writeStructBegin($name) {
    return
      $this->getContext()->writeSeparator() + $this->pushMapWriteContext();
  }

  public function writeStructEnd() {
    return $this->popWriteContext();
  }

  public function writeFieldBegin($fieldName, $fieldType, $fieldId) {
    return $this->writeString($fieldName);
  }

  public function writeFieldEnd() {
    return 0;
  }

  public function writeFieldStop() {
    return 0;
  }

  public function writeMapBegin($keyType, $valType, $size) {
    return
      $this->getContext()->writeSeparator() + $this->pushMapWriteContext();
  }

  public function writeMapEnd() {
    return $this->popWriteContext();
  }

  public function writeListBegin($elemType, $size) {
    return
      $this->getContext()->writeSeparator() + $this->pushListWriteContext();
  }

  public function writeListEnd() {
    return $this->popWriteContext();
  }

  public function writeSetBegin($elemType, $size) {
    return
      $this->getContext()->writeSeparator() + $this->pushListWriteContext();
  }

  public function writeSetEnd() {
    return $this->popWriteContext();
  }

  public function writeBool($value) {
    $x = $this->getContext()->writeSeparator();
    if ($value) {
      $this->trans_->write('true');
      $x += 4;
    } else {
      $this->trans_->write('false');
      $x += 5;
    }
    return $x;
  }

  public function writeByte($value) {
    return $this->writeNum((int) $value);
  }

  public function writeI16($value) {
    return $this->writeNum((int) $value);
  }

  public function writeI32($value) {
    return $this->writeNum((int) $value);
  }

  public function writeI64($value) {
    return $this->writeNum((int) $value);
  }

  public function writeDouble($value) {
    return $this->writeNum((float) $value);
  }

  public function writeFloat($value) {
    return $this->writeNum((float) $value);
  }

  private function writeNum($value) {
    $ctx = $this->getContext();
    $ret = $ctx->writeSeparator();
    if ($ctx->escapeNum()) {
      $value = (string) $value;
    }

    $enc = json_encode($value);
    $this->trans_->write($enc);

    return $ret + strlen($enc);
  }

  public function writeString($value) {
    $ctx = $this->getContext();
    $ret = $ctx->writeSeparator();
    $value = (string) $value;
    $sb = '';
    $sb .= '"';
    $len = strlen($value);
    for ($i = 0; $i < $len; $i++) {
      $c = $value[$i];
      $ord = ord($c);
      switch ($ord) {
        case 8:
          $sb .= '\b';
          break;
        case 9:
          $sb .= '\t';
          break;
        case 10:
          $sb .= '\n';
          break;
        case 12:
          $sb .= '\f';
          break;
        case 13:
          $sb .= '\r';
          break;
        case 34:
          // "
        case 92:
          // \
          $sb .= '\\';
          $sb .= $c;
          break;
        default:
          if ($ord < 32 || $ord > 126) {
            $sb .= '\\u00';
            $sb .= bin2hex($c);
          } else {
            $sb .= $c;
          }
          break;
      }
    }
    $sb .= '"';
    $enc = $sb;
    $this->trans_->write($enc);

    return $ret + strlen($enc);
  }

  public function readMessageBegin(inout $name, inout $type, inout $seqid) {
    throw new TProtocolException(
      'Reading with TSimpleJSONProtocol is not supported. '.
      'Use readFromJSON() on your struct',
    );
  }

  public function readMessageEnd() {
    throw new TProtocolException(
      'Reading with TSimpleJSONProtocol is not supported. '.
      'Use readFromJSON() on your struct',
    );
  }

  public function readStructBegin(inout $_name) {
    $this->getContext()->readSeparator();
    $this->skipWhitespace();
    $this->pushMapReadContext();
  }

  public function readStructEnd() {
    $this->popReadContext();
  }

  public function readFieldBegin(
    inout $name,
    inout $fieldType,
    inout $fieldId,
  ) {
    $fieldId = null;
    $ctx = $this->getContext();
    $name = null;
    while ($name === null) {
      if ($ctx->readContextOver()) {
        $fieldType = TType::STOP;
        break;
      } else {
        $ctx->readSeparator();
        $this->skipWhitespace();
        $name = $this->readJSONString()[0];
        // We need to guess the type of the value, in case the name is bogus or we are in a skip method up the stack
        $offset = $this->skipWhitespace(true);
        $this->expectChar(':', true, $offset);
        $offset += 1 + $this->skipWhitespace(true, $offset + 1);
        $c = $this->bufTrans->peek(1, $offset);
        if ($c === 'n' && $this->bufTrans->peek(4, $offset) === 'null') {
          // We actually want to skip this field, but there isn't an appropriate
          // TType to send back. So instead, we will silently skip
          $ctx->readSeparator();
          $this->skipWhitespace();
          $this->trans_->readAll(4);
          $name = null;
          continue;
        } else {
          $fieldType = $this->guessFieldTypeBasedOnByte($c);
        }
      }
    }
  }

  public function readFieldEnd() {
    // Do nothing
  }

  public function readMapBegin(inout $keyType, inout $valType, inout $size) {
    $size = null;
    $this->getContext()->readSeparator();
    $this->skipWhitespace();
    $this->pushMapReadContext();
    if ($this->readMapHasNext()) {
      // We need to guess the type of the keys/values, in case we are in a skip method up the stack
      $keyType = TType::STRING;
      $this->skipWhitespace(); // This is not a peek, since we can do this safely again
      $offset = $this->readJSONString(true)[1];
      $offset += $this->skipWhitespace(true, $offset);
      $this->expectChar(':', true, $offset);
      $offset += 1 + $this->skipWhitespace(true, $offset + 1);
      $c = $this->bufTrans->peek(1, $offset);
      $valType = $this->guessFieldTypeBasedOnByte($c);
    }
  }

  public function readMapHasNext(): bool {
    return !$this->getContext()->readContextOver();
  }

  public function readMapEnd() {
    $this->popReadContext();
  }

  public function readListBegin(&$elemType, &$size) {
    $size = null;
    $this->getContext()->readSeparator();
    $this->skipWhitespace();
    $this->pushListReadContext();
    if ($this->readListHasNext()) {
      // We need to guess the type of the values, in case we are in a skip method up the stack
      $this->skipWhitespace(); // This is not a peek, since we can do this safely again
      $c = $this->bufTrans->peek(1);
      $elemType = $this->guessFieldTypeBasedOnByte($c);
    }
  }

  public function readListHasNext(): bool {
    return !$this->getContext()->readContextOver();
  }

  public function readListEnd() {
    $this->popReadContext();
  }

  public function readSetBegin(&$elemType, &$size) {
    $size = null;
    $this->getContext()->readSeparator();
    $this->skipWhitespace();
    $this->pushListReadContext();
    if ($this->readSetHasNext()) {
      // We need to guess the type of the values, in case we are in a skip method up the stack
      $this->skipWhitespace(); // This is not a peek, since we can do this safely again
      $c = $this->bufTrans->peek(1);
      $elemType = $this->guessFieldTypeBasedOnByte($c);
    }
  }

  public function readSetHasNext(): bool {
    return !$this->getContext()->readContextOver();
  }

  public function readSetEnd() {
    $this->popReadContext();
  }

  public function readBool(&$value) {
    $this->getContext()->readSeparator();
    $this->skipWhitespace();
    $c = $this->trans_->readAll(1);
    $target = null;
    switch ($c) {
      case 't':
        $value = true;
        $target = 'rue';
        break;
      case 'f':
        $value = false;
        $target = 'alse';
        break;
      default:
        throw new TProtocolException(
          'TSimpleJSONProtocol: Expected t or f, encountered 0x'.
          bin2hex($c),
        );
    }

    for ($i = 0; $i < strlen($target); $i++) {
      $this->expectChar($target[$i]);
    }
  }

  private function readInteger(?int $min, ?int $max): int {
    $val = intval($this->readNumStr());
    if (($min !== null && $max !== null) && ($val < $min || $val > $max)) {
      throw new TProtocolException(
        'TProtocolException: value '.
        $val.
        ' is outside the expected bounds',
      );
    }
    return $val;
  }

  private function readNumStr(): string {
    $ctx = $this->getContext();
    if ($ctx->escapeNum()) {
      $this->expectChar('"');
    }
    $count = 0;
    $reading = true;
    while ($reading) {
      $c = $this->bufTrans->peek(1, $count);
      switch ($c) {
        case '+':
        case '-':
        case '.':
        case '0':
        case '1':
        case '2':
        case '3':
        case '4':
        case '5':
        case '6':
        case '7':
        case '8':
        case '9':
        case 'E':
        case 'e':
          $count++;
          break;
        default:
          $reading = false;
          break;
      }
    }
    $str = $this->trans_->readAll($count);
    if (!preg_match(
          '/^[+-]?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$/',
          $str,
        )) {
      throw new TProtocolException(
        'TSimpleJSONProtocol: Invalid json number '.$str,
      );
    }
    if ($ctx->escapeNum()) {
      $this->expectChar('"');
    }

    return $str;
  }

  public function readByte(&$value) {
    $this->getContext()->readSeparator();
    $this->skipWhitespace();
    $value = $this->readInteger(-0x80, 0x7f);
  }

  public function readI16(&$value) {
    $this->getContext()->readSeparator();
    $this->skipWhitespace();
    $value = $this->readInteger(-0x8000, 0x7fff);
  }

  public function readI32(&$value) {
    $this->getContext()->readSeparator();
    $this->skipWhitespace();
    $value = $this->readInteger(-0x80000000, 0x7fffffff);
  }

  public function readI64(&$value) {
    $this->getContext()->readSeparator();
    $this->skipWhitespace();
    $value = $this->readInteger(null, null);
  }

  public function readDouble(&$value) {
    $this->getContext()->readSeparator();
    $this->skipWhitespace();
    $value = (float)$this->readNumStr();
  }

  public function readFloat(&$value) {
    $this->getContext()->readSeparator();
    $this->skipWhitespace();
    $value = (float)$this->readNumStr();
  }

  public function readString(&$value) {
    $this->getContext()->readSeparator();
    $this->skipWhitespace();
    $value = $this->readJSONString()[0];
  }

  private function readJSONString(
    bool $peek = false,
    int $start = 0,
  ): Pair<string, int> {
    if (!$peek) {
      $start = 0;
    }
    $this->expectChar('"', $peek, $start);
    $count = $peek ? 1 : 0;
    $sb = '';
    $reading = true;
    while ($reading) {
      $c = $this->bufTrans->peek(1, $start + $count);
      switch ($c) {
        case '"':
          $reading = false;
          break;
        case '\\':
          $count++;
          $c = $this->bufTrans->peek(1, $start + $count);
          switch ($c) {
            case '\\':
              $count++;
              $sb .= '\\';
              break;
            case '"':
              $count++;
              $sb .= '"';
              break;
            case 'b':
              $count++;
              $sb .= chr(0x08);
              break;
            case '/':
              $count++;
              $sb .= '/';
              break;
            case 'f':
              $count++;
              $sb .= "\f";
              break;
            case 'n':
              $count++;
              $sb .= "\n";
              break;
            case 'r':
              $count++;
              $sb .= "\r";
              break;
            case 't':
              $count++;
              $sb .= "\t";
              break;
            case 'u':
              $count++;
              $this->expectChar('0', true, $start + $count);
              $this->expectChar('0', true, $start + $count + 1);
              $count += 2;
              $sb .= hex2bin($this->bufTrans->peek(2, $start + $count));
              $count += 2;
              break;
            default:
              throw new TProtocolException(
                'TSimpleJSONProtocol: Expected Control Character, found 0x'.
                bin2hex($c),
              );
          }
          break;
        case '':
          // end of buffer, this string is unclosed
          $reading = false;
          break;
        default:
          $count++;
          $sb .= $c;
          break;
      }
    }

    if (!$peek) {
      $this->trans_->readAll($count);
    }

    $this->expectChar('"', $peek, $start + $count);
    return Pair {$sb, $count + 1};
  }

  private function skipWhitespace(bool $peek = false, int $start = 0): int {
    if (!$peek) {
      $start = 0;
    }
    $count = 0;
    $reading = true;
    while ($reading) {
      $byte = $this->bufTrans->peek(1, $count + $start);
      switch ($byte) {
        case ' ':
        case "\t":
        case "\n":
        case "\r":
          $count++;
          break;
        default:
          $reading = false;
          break;
      }
    }
    if (!$peek) {
      $this->trans_->readAll($count);
    }

    return $count;
  }

  private function expectChar(
    string $char,
    bool $peek = false,
    int $start = 0,
  ): void {
    if (!$peek) {
      $start = 0;
    }
    $c = null;
    if ($peek) {
      $c = $this->bufTrans->peek(1, $start);
    } else {
      $c = $this->trans_->readAll(1);
    }

    if ($c !== $char) {
      throw new TProtocolException(
        'TSimpleJSONProtocol: Expected '.
        $char.
        ', but encountered 0x'.
        bin2hex($c),
      );
    }
  }

  private function guessFieldTypeBasedOnByte(string $byte): ?int {
    switch ($byte) {
      case '{':
        return TType::STRUCT;
      case '[':
        return TType::LST;
      case 't':
      case 'f':
        return TType::BOOL;
      case '-':
      case '0':
      case '1':
      case '2':
      case '3':
      case '4':
      case '5':
      case '6':
      case '7':
      case '8':
      case '9':
        // These technically aren't allowed to start JSON floats, but are here for backwards compatibility
      case '+':
      case '.':
        return TType::DOUBLE;
      case '"':
        return TType::STRING;
      case ']':
      case '}':
        // We can get here with empty lists/maps, returning a dummy value
        return TType::STOP;
    }

    throw new TProtocolException(
      'TSimpleJSONProtocol: Unable to guess TType for character 0x'.
      bin2hex($byte),
    );
  }
}


<?hh
/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @package thrift.protocol.simplejson
 */

class TSimpleJSONProtocolContext {
  final public function __construct(
    protected TTransport $trans,
    protected IThriftBufferedTransport $bufTrans,
  ) {}

  public function writeStart(): int {
    return 0;
  }

  public function writeSeparator(): int {
    return 0;
  }

  public function writeEnd(): int {
    return 0;
  }

  public function readStart(): void {
    // Do nothing
  }

  public function readSeparator(): void {
    // Do nothing
  }

  public function readContextOver(): bool {
    return true;
  }

  public function readEnd(): void {
    // Do nothing
  }

  public function escapeNum(): bool {
    return false;
  }

  protected function skipWhitespace(bool $skip = true): int {
    $count = 0;
    $reading = true;
    while ($reading) {
      $byte = $this->bufTrans->peek(1, $count);
      switch ($byte) {
        case ' ':
        case "\t":
        case "\n":
        case "\r":
          $count++;
          break;
        default:
          $reading = false;
          break;
      }
    }
    if ($skip) {
      $this->trans->readAll($count);
    }
    return $count;
  }
}


<?hh
/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @package thrift.protocol.simplejson
 */

class TSimpleJSONProtocolListContext extends TSimpleJSONProtocolContext {
  private bool $first = true;

  public function writeStart(): int {
    $this->trans->write('[');
    return 1;
  }

  public function writeSeparator(): int {
    if ($this->first) {
      $this->first = false;
      return 0;
    }

    $this->trans->write(',');
    return 1;
  }

  public function writeEnd(): int {
    $this->trans->write(']');
    return 1;
  }

  public function readStart(): void {
    $this->skipWhitespace();
    $c = $this->trans->readAll(1);
    if ($c !== '[') {
      throw new TProtocolException(
        'TSimpleJSONProtocol: Expected "[", encountered 0x'.bin2hex($c),
      );
    }
  }

  public function readSeparator(): void {
    if ($this->first) {
      $this->first = false;
      return;
    }

    $this->skipWhitespace();
    $c = $this->trans->readAll(1);
    if ($c !== ',') {
      throw new TProtocolException(
        'TSimpleJSONProtocol: Expected ",", encountered 0x'.bin2hex($c),
      );
    }
  }

  public function readContextOver(): bool {
    $pos = $this->skipWhitespace(false);
    $c = $this->bufTrans->peek(1, $pos);
    if (!$this->first && $c !== ',' && $c !== ']') {
      throw new TProtocolException(
        'TSimpleJSONProtocol: Expected "," or "]", encountered 0x'.
        bin2hex($c),
      );
    }

    return ($c === ']');
  }

  public function readEnd(): void {
    $this->skipWhitespace();
    $c = $this->trans->readAll(1);
    if ($c !== ']') {
      throw new TProtocolException(
        'TSimpleJSONProtocol: Expected "]", encountered 0x'.bin2hex($c),
      );
    }
  }

  public function escapeNum(): bool {
    return false;
  }
}


<?hh
/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @package thrift.protocol.simplejson
 */

class TSimpleJSONProtocolMapContext extends TSimpleJSONProtocolContext {
  private bool $first = true;
  private bool $colon = true;

  public function writeStart(): int {
    $this->trans->write('{');
    return 1;
  }

  public function writeSeparator(): int {
    if ($this->first) {
      $this->first = false;
      return 0;
    }

    if ($this->colon) {
      $this->trans->write(':');
    } else {
      $this->trans->write(',');
    }

    $this->colon = !$this->colon;
    return 1;
  }

  public function writeEnd(): int {
    $this->trans->write('}');
    return 1;
  }

  public function readStart(): void {
    $this->skipWhitespace();
    $c = $this->trans->readAll(1);
    if ($c !== '{') {
      throw new TProtocolException(
        'TSimpleJSONProtocol: Expected "{", encountered 0x'.bin2hex($c),
      );
    }
  }

  public function readSeparator(): void {
    if ($this->first) {
      $this->first = false;
      return;
    }

    $this->skipWhitespace();
    $c = $this->trans->readAll(1);
    $target = $this->colon ? ':' : ',';

    if ($c !== $target) {
      throw new TProtocolException(
        'TSimpleJSONProtocol: Expected "'.
        $target.
        '", encountered 0x'.
        bin2hex($c),
      );
    }

    $this->colon = !$this->colon;
  }

  public function readContextOver(): bool {
    $pos = $this->skipWhitespace(false);
    $c = $this->bufTrans->peek(1, $pos);
    if (!$this->first && $c !== ',' && $c !== '}') {
      throw new TProtocolException(
        'TSimpleJSONProtocol: Expected "," or "}", encountered 0x'.
        bin2hex($c),
      );
    }

    return ($c === '}');
  }

  public function readEnd(): void {
    $this->skipWhitespace();
    $c = $this->trans->readAll(1);
    if ($c !== '}') {
      throw new TProtocolException(
        'TSimpleJSONProtocol: Expected "}", encountered 0x'.bin2hex($c),
      );
    }
  }

  public function escapeNum(): bool {
    return $this->colon;
  }
}


<?hh
/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @package thrift.protocol.simplejson
 */

/**
 * Utility class for serializing
 * a thrift object using TSimpleJSONProtocol
 */
class TSimpleJSONSerializer extends TProtocolSerializer {
  public static function serialize(IThriftStruct $object): string {
    $transport = new TMemoryBuffer();
    $protocol = new TSimpleJSONProtocol($transport);
    $object->write($protocol);
    return $transport->getBuffer();
  }

  public static function deserialize<T as IThriftStruct>(
    string $str,
    T $object,
  ): T {
    $transport = new TMemoryBuffer($str);
    $protocol = new TSimpleJSONProtocol($transport);
    $object->read($protocol);
    return $object;
  }
}


<?hh
/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @package thrift.protocol.simplephpobject
 */

class TSimplePHPObjectProtocol extends TProtocol {

  private Iterator<mixed> $top;
  private Vector<Iterator<mixed>> $stack;

  public function __construct(mixed $val) {
    $this->top = new ArrayIterator(array($val));
    $this->stack = Vector {$this->top};
    parent::__construct(new TNullTransport());
  }

  public function readMessageBegin(inout $name, inout $type, inout $seqid) {
    throw new TProtocolException('Not Supported');
  }

  public function readMessageEnd() {
    throw new TProtocolException('Not Supported');
  }

  public function readStructBegin(inout $name) {
    $name = null;
    $val = $this->top->current();
    $this->top->next();
    $itr = null;
    if ($val instanceof Set || $val instanceof ImmSet) {
      throw new TProtocolException(
        'Unsupported data structure for struct: '.gettype($val),
      );
    } else if ($val instanceof Vector) {
      if ($val->isEmpty()) {
        // Empty maps can be (de)serialized incorrectly as vectors
        $size = 0;
        $itr = $val->getIterator();
      } else {
        throw new TProtocolException(
          'Unsupported data structure for struct: '.gettype($val),
        );
      }
    } else if ($val instanceof ImmVector) {
      if ($val->isEmpty()) {
        // Empty maps can be (de)serialized incorrectly as vectors
        $size = 0;
        $itr = $val->getIterator();
      } else {
        throw new TProtocolException(
          'Unsupported data structure for struct: '.gettype($val),
        );
      }
    } else if ($val instanceof Map) {
      $itr = $val->getIterator();
    } else if ($val instanceof ImmMap) {
      $itr = $val->getIterator();
    } else if (is_array($val)) {
      $itr = new ArrayIterator($val);
    } else {
      $val = (array) $val;
      $itr = new ArrayIterator($val);
    }
    $itr = new TSimplePHPObjectProtocolKeyedIteratorWrapper($itr);
    $this->stack->add($itr);
    $this->top = $itr;
  }

  public function readStructEnd() {
    $this->stack->pop();
    $val = $this->stack->lastValue();
    invariant($val !== null, "Stack is empty, this shouldn't happen!");
    $this->top = $val;
  }

  public function readFieldBegin(
    inout $name,
    inout $fieldType,
    inout $fieldId,
  ) {
    $val = null;
    while ($val === null) {
      if (!$this->top->valid()) {
        $fieldType = TType::STOP;
        return;
      }

      $fieldId = null;
      $name = $this->top->current();
      $this->top->next();
      $val = $this->top->current();
      if ($val === null) {
        $this->top->next();
        continue;
      }

      $fieldType = $this->guessTypeForValue($val);
    }
  }

  public function readFieldEnd() {}

  public function readMapBegin(inout $keyType, inout $valType, inout $size) {
    $val = $this->top->current();
    $this->top->next();
    $itr = null;
    if ($val instanceof Set || $val instanceof ImmSet) {
      throw new TProtocolException(
        'Unsupported data structure for map: '.gettype($val),
      );
    } else if ($val instanceof Vector) {
      if ($val->isEmpty()) {
        // Empty maps can be (de)serialized incorrectly as vectors
        $size = 0;
        $itr = $val->getIterator();
      } else {
        throw new TProtocolException(
          'Unsupported data structure for map: '.gettype($val),
        );
      }
    } else if ($val instanceof ImmVector) {
      if ($val->isEmpty()) {
        // Empty maps can be (de)serialized incorrectly as vectors
        $size = 0;
        $itr = $val->getIterator();
      } else {
        throw new TProtocolException(
          'Unsupported data structure for map: '.gettype($val),
        );
      }
    } else if ($val instanceof Map) {
      $itr = $val->getIterator();
      $size = $val->count();
    } else if ($val instanceof ImmMap) {
      $itr = $val->getIterator();
      $size = $val->count();
    } else if (is_array($val)) {
      $itr = new ArrayIterator($val);
      $size = count($val);
    } else {
      $val = (array) $val;
      $itr = new ArrayIterator($val);
      $size = count($val);
    }
    $itr = new TSimplePHPObjectProtocolKeyedIteratorWrapper($itr);
    $this->stack->add($itr);
    $this->top = $itr;

    if ($itr->valid()) {
      $key_sample = $itr->current();
      $itr->next();
      $val_sample = $itr->current();
      $itr->rewind();

      $keyType = $this->guessTypeForValue($key_sample);
      $valType = $this->guessTypeForValue($val_sample);
    }
  }

  public function readMapEnd() {
    $this->stack->pop();
    $val = $this->stack->lastValue();
    invariant($val !== null, "Stack is empty, this shouldn't happen!");
    $this->top = $val;
  }

  public function readListBegin(&$elemType, &$size) {
    $val = $this->top->current();
    $this->top->next();
    $itr = null;
    if ($val instanceof Set || $val instanceof ImmSet) {
      throw new TProtocolException(
        'Unsupported data structure for list: '.gettype($val),
      );
    } else if ($val instanceof Map) {
      $itr = $val->getIterator();
      $size = $val->count();
    } else if ($val instanceof ImmMap) {
      $itr = $val->getIterator();
      $size = $val->count();
    } else if ($val instanceof Vector) {
      $itr = $val->getIterator();
      $size = $val->count();
    } else if ($val instanceof ImmVector) {
      $itr = $val->getIterator();
      $size = $val->count();
    } else if (is_array($val)) {
      $itr = new ArrayIterator($val);
      $size = count($val);
    } else {
      $val = (array) $val;
      $itr = new ArrayIterator($val);
      $size = count($val);
    }
    $this->stack->add($itr);
    $this->top = $itr;

    if ($itr->valid()) {
      $val_sample = $itr->current();
      $elemType = $this->guessTypeForValue($val_sample);
    }
  }

  public function readListEnd() {
    $this->stack->pop();
    $val = $this->stack->lastValue();
    invariant($val !== null, "Stack is empty, this shouldn't happen!");
    $this->top = $val;
  }

  public function readSetBegin(&$elemType, &$size) {
    $val = $this->top->current();
    $this->top->next();
    $itr = null;
    if ($val instanceof Map) {
      $itr = $val->getIterator();
      $size = $val->count();
    } else if ($val instanceof ImmMap) {
      $itr = $val->getIterator();
      $size = $val->count();
    } else if ($val instanceof Vector) {
      $itr = $val->getIterator();
      $size = $val->count();
    } else if ($val instanceof ImmVector) {
      $itr = $val->getIterator();
      $size = $val->count();
    } else if ($val instanceof Set) {
      $itr = $val->getIterator();
      $size = $val->count();
    } else if ($val instanceof ImmSet) {
      $itr = $val->getIterator();
      $size = $val->count();
    } else if (is_array($val)) {
      $itr = new ArrayIterator($val);
      $size = count($val);
    } else {
      $val = array_keys((array) $val);
      $itr = new ArrayIterator($val);
      $size = count($val);
    }
    $this->stack->add($itr);
    $this->top = $itr;

    if ($itr->valid()) {
      $val_sample = $itr->current();
      $elemType = $this->guessTypeForValue($val_sample);
    }
  }

  public function readSetEnd() {
    $this->stack->pop();
    $val = $this->stack->lastValue();
    invariant($val !== null, "Stack is empty, this shouldn't happen!");
    $this->top = $val;
  }

  public function readBool(&$value) {
    $value = (bool) $this->top->current();
    $this->top->next();
  }

  public function readByte(&$value) {
    $value = (int) $this->top->current();
    $this->top->next();
    if ($value < -0x80 || $value > 0x7F) {
      throw new TProtocolException('Value is outside of valid range');
    }
  }

  public function readI16(&$value) {
    $value = (int) $this->top->current();
    $this->top->next();
    if ($value < -0x8000 || $value > 0x7FFF) {
      throw new TProtocolException('Value is outside of valid range');
    }
  }

  public function readI32(&$value) {
    $value = (int) $this->top->current();
    $this->top->next();
    if ($value < -0x80000000 || $value > 0x7FFFFFFF) {
      throw new TProtocolException('Value is outside of valid range');
    }
  }

  public function readI64(&$value) {
    $value = (int) $this->top->current();
    $this->top->next();
    if ($value < (-0x80000000 << 32) ||
        $value > (0x7FFFFFFF << 32 | 0xFFFFFFFF)) {
      throw new TProtocolException('Value is outside of valid range');
    }
  }

  public function readDouble(&$value) {
    $value = (float) $this->top->current();
    $this->top->next();
  }

  public function readFloat(&$value) {
    $value = (float) $this->top->current();
    $this->top->next();
  }

  public function readString(&$value) {
    $value = (string) $this->top->current();
    $this->top->next();
  }

  public function writeMessageBegin($name, $type, $seqid) {
    throw new TProtocolException('Not Implemented');
  }

  public function writeMessageEnd() {
    throw new TProtocolException('Not Implemented');
  }

  public function writeStructBegin($name) {
    throw new TProtocolException('Not Implemented');
  }

  public function writeStructEnd() {
    throw new TProtocolException('Not Implemented');
  }

  public function writeFieldBegin($fieldName, $fieldType, $fieldId) {
    throw new TProtocolException('Not Implemented');
  }

  public function writeFieldEnd() {
    throw new TProtocolException('Not Implemented');
  }

  public function writeFieldStop() {
    throw new TProtocolException('Not Implemented');
  }

  public function writeMapBegin($keyType, $valType, $size) {
    throw new TProtocolException('Not Implemented');
  }

  public function writeMapEnd() {
    throw new TProtocolException('Not Implemented');
  }

  public function writeListBegin($elemType, $size) {
    throw new TProtocolException('Not Implemented');
  }

  public function writeListEnd() {
    throw new TProtocolException('Not Implemented');
  }

  public function writeSetBegin($elemType, $size) {
    throw new TProtocolException('Not Implemented');
  }

  public function writeSetEnd() {
    throw new TProtocolException('Not Implemented');
  }

  public function writeBool($value) {
    throw new TProtocolException('Not Implemented');
  }

  public function writeByte($value) {
    throw new TProtocolException('Not Implemented');
  }

  public function writeI16($value) {
    throw new TProtocolException('Not Implemented');
  }

  public function writeI32($value) {
    throw new TProtocolException('Not Implemented');
  }

  public function writeI64($value) {
    throw new TProtocolException('Not Implemented');
  }

  public function writeDouble($value) {
    throw new TProtocolException('Not Implemented');
  }

  public function writeFloat($value) {
    throw new TProtocolException('Not Implemented');
  }

  public function writeString($value) {
    throw new TProtocolException('Not Implemented');
  }

  private function guessTypeForValue(mixed $val): int {
    if (is_bool($val)) {
      return TType::BOOL;
    } else if (is_int($val)) {
      return TType::I64;
    } else if (is_float($val)) {
      return TType::DOUBLE;
    } else if (is_string($val)) {
      return TType::STRING;
    } else if (is_object($val) || is_array($val) || $val instanceof Map) {
      return TType::STRUCT;
    } else if ($val instanceof Iterable) {
      return TType::LST;
    }

    throw new TProtocolException(
      'Unable to guess thrift type for '.gettype($val),
    );
  }
}


<?hh
/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @package thrift.protocol.simplephpobject
 */

class TSimplePHPObjectProtocolKeyedIteratorWrapper
  implements Iterator<mixed> {
  private bool $key = true;

  public function __construct(private Iterator<mixed> $itr) {}

  public function key(): string {
    invariant_violation('Cannot Access Key');
  }

  public function current(): mixed {
    if ($this->key) {
      // UNSAFE_BLOCK
      return $this->itr->key();
    } else {
      return $this->itr->current();
    }
  }

  public function next(): void {
    $this->key = !$this->key;
    if ($this->key) {
      $this->itr->next();
    }
  }

  public function rewind(): void {
    $this->key = !$this->key;
    if (!$this->key) {
      $this->itr->rewind();
    }
  }

  public function valid(): bool {
    return $this->itr->valid();
  }
}


<?hh
/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @package thrift.server
 */

/**
 * Server that can run in non-blocking mode
 */
class TNonBlockingServer extends TServer {
  protected int $clientIdx = 0;
  protected array<int, TBufferedTransport> $clients = array();

  public function __construct(
    IThriftProcessor $processor,
    TServerSocket $serverTransport,
    TTransportFactory $transportFactory,
    TProtocolFactory $protocolFactory,
  ) {
    parent::__construct(
      $processor,
      $serverTransport,
      $transportFactory,
      $protocolFactory,
    );
  }

  /**
   * Because our server is non-blocking, don't close this socket
   * until we need to.
   *
   * @return bool true if we should keep the client alive
   */
  protected function handle(TBufferedTransport $client): bool {
    $trans = $this->transportFactory->getTransport($client);
    $prot = $this->protocolFactory->getProtocol($trans);

    $this->_clientBegin($prot);
    try {
      // First check the transport is readable to avoid
      // blocking on read
      if (!($trans instanceof TTransportStatus) || $trans->isReadable()) {
        $this->processor->process($prot, $prot);
      }
    } catch (Exception $x) {
      $md = $client->getMetaData();
      if ($md['timed_out']) {
        // keep waiting for the client to send more requests
      } else if ($md['eof']) {
        invariant($trans instanceof TTransport, 'Need to make Hack happy');
        $trans->close();
        return false;
      } else {
        echo 'Handle caught transport exception: '.$x->getMessage()."\n";
      }
    }
    return true;
  }

  protected function processExistingClients(): void {
    foreach ($this->clients as $i => $client) {
      if (!$this->handle($client)) {
        // remove the client from our list of open clients if
        // our handler reports that the client is no longer alive
        unset($this->clients[$i]);
      }
    }
  }

  /*
   * This method should be called repeately on idle to listen and
   * process an request. If there is no pending request, it will
   * return;
   */
  public function serve(): void {
    $this->serverTransport->listen();
    $this->process();
  }

  public function process(): void {
    // 0 timeout is non-blocking
    $client = $this->serverTransport->accept(0);
    if ($client) {
      $this->clients[$this->clientIdx++] = $client;
    }

    $this->processExistingClients();
  }
}


<?hh
/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @package thrift.server
 */

/**
 * Base interface for a server.
 *
 *     The attribute serverEventHandler (default: null) receives
 *     callbacks for various events in the server lifecycle.  It should
 *     be set to an instance of TServerEventHandler.
 */
abstract class TServer {
  protected IThriftProcessor $processor;
  protected TServerSocket $serverTransport;
  protected TTransportFactory $transportFactory;
  protected TProtocolFactory $protocolFactory;
  protected ?TServerEventHandler $serverEventHandler;

  public function __construct(
    IThriftProcessor $processor,
    TServerSocket $serverTransport,
    TTransportFactory $transportFactory,
    TProtocolFactory $protocolFactory,
  ) {
    $this->processor = $processor;
    $this->serverTransport = $serverTransport;
    $this->transportFactory = $transportFactory;
    $this->protocolFactory = $protocolFactory;

    $this->serverEventHandler = null;
  }

  protected function _clientBegin(TProtocol $prot): void {
    if ($this->serverEventHandler) {
      $this->serverEventHandler->clientBegin($prot);
    }
  }

  protected function handle(TBufferedTransport $client): bool {
    $trans = $this->transportFactory->getTransport($client);
    $prot = $this->protocolFactory->getProtocol($trans);

    $this->_clientBegin($prot);
    try {
      $this->processor->process($prot, $prot);
    } catch (TTransportException $tx) {
      // ignore
      return false;
    } catch (Exception $x) {
      echo 'Handle caught transport exception: '.$x->getMessage()."\n";
      return false;
    }

    $trans->close();
    return true;
  }

  public abstract function serve(): void;
}


<?hh
/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @package thrift.server
 */

/**
 * Event handler base class.  Override selected methods on this class
 * to implement custom event handling
 */
abstract class TServerEventHandler {

  /**
   * Called before a new client request is handled
   */
  public abstract function clientBegin(TProtocol $prot): void;
}


<?hh
/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @package thrift.transport
 */

interface InstrumentedTTransport {
  public function getBytesWritten(): int;
  public function getBytesRead(): int;
  public function resetBytesWritten(): void;
  public function resetBytesRead(): void;
}


<?hh
/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @package thrift.transport
 */

/**
 * TTransport subclasses that implement InstrumentedTTransport and use
 * InstrumentedTTransportTrait have a simple set of counters available.
 */
trait InstrumentedTTransportTrait {
  private int $bytesWritten = 0;
  private int $bytesRead = 0;

  public function getBytesWritten(): int {
    return $this->bytesWritten;
  }

  public function getBytesRead(): int {
    return $this->bytesRead;
  }

  public function resetBytesWritten(): void {
    $this->bytesWritten = 0;
  }

  public function resetBytesRead(): void {
    $this->bytesRead = 0;
  }

  protected function onWrite(int $bytes_written): void {
    $this->bytesWritten += $bytes_written;
  }

  protected function onRead(int $bytes_read): void {
    $this->bytesRead += $bytes_read;
  }
}


<?hh
/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @package thrift.transport
 */

interface IThriftBufferedTransport {
  public function peek(int $len, int $start = 0);
  public function putBack(string $data): void;
  public function minBytesAvailable(): int;
}


<?hh
/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @package thrift.transport
 */

interface IThriftRemoteConn extends TTransportStatus {
  public function open(): void;
  public function close(): void;
  public function flush(): void;
  public function isOpen(): bool;

  public function read(int $len): string;
  public function readAll(int $len): string;
  public function write(string $buf): void;

  public function getRecvTimeout(): int;
  public function setRecvTimeout(int $timeout): void;

  public function getHost(): string;
  public function getPort(): int;
}


<?hh
/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @package thrift.transport
 */

/**
 * Buffered transport. Stores data to an internal buffer that it doesn't
 * actually write out until flush is called. For reading, we do a greedy
 * read and then serve data out of the internal buffer.
 *
 * @package thrift.transport
 */
class TBufferedTransport extends TTransport
  implements TTransportStatus, IThriftBufferedTransport {

  /**
   * Constructor. Creates a buffered transport around an underlying transport
   */
  public function __construct(
    ?TTransport $transport = null,
    int $rBufSize = 512,
    int $wBufSize = 512,
  ) {
    $this->transport_ = $transport ?: new TNullTransport();
    $this->rBufSize_ = $rBufSize;
    $this->wBufSize_ = $wBufSize;
  }

  /**
   * The underlying transport
   *
   * @var TTransport
   */
  protected TTransport $transport_;

  /**
   * The receive buffer size
   *
   * @var int
   */
  protected int $rBufSize_ = 512;

  /**
   * The write buffer size
   *
   * @var int
   */
  protected int $wBufSize_ = 512;

  /**
   * The write buffer.
   *
   * @var string
   */
  protected string $wBuf_ = '';

  /**
   * The read buffer.
   *
   * @var string
   */
  protected string $rBuf_ = '';

  public function isOpen(): bool {
    return $this->transport_->isOpen();
  }

  public function open(): void {
    $this->transport_->open();
  }

  public function close(): void {
    $this->transport_->close();
  }

  public function putBack(string $data): void {
    if (strlen($this->rBuf_) === 0) {
      $this->rBuf_ = $data;
    } else {
      $this->rBuf_ = ($data.$this->rBuf_);
    }
  }

  public function getMetaData(): array<string, mixed> {
    if ($this->transport_ instanceof TSocket) {
      return $this->transport_->getMetaData();
    }

    return array();
  }

  public function isReadable(): bool {
    if (strlen($this->rBuf_) > 0) {
      return true;
    }

    if ($this->transport_ instanceof TTransportStatus) {
      return $this->transport_->isReadable();
    }

    return true;
  }

  public function isWritable(): bool {
    if ($this->transport_ instanceof TTransportStatus) {
      return $this->transport_->isWritable();
    }

    return true;
  }

  public function minBytesAvailable(): int {
    return strlen($this->rBuf_);
  }

  /**
   * The reason that we customize readAll here is that the majority of PHP
   * streams are already internally buffered by PHP. The socket stream, for
   * example, buffers internally and blocks if you call read with $len greater
   * than the amount of data available, unlike recv() in C.
   *
   * Therefore, use the readAll method of the wrapped transport inside
   * the buffered readAll.
   */
  public function readAll(int $len): string {
    $have = strlen($this->rBuf_);
    $data = '';
    if ($have == 0) {
      $data = $this->transport_->readAll($len);
    } else if ($have < $len) {
      $data = $this->rBuf_;
      $this->rBuf_ = '';
      $data .= $this->transport_->readAll($len - $have);
    } else if ($have == $len) {
      $data = $this->rBuf_;
      $this->rBuf_ = '';
    } else if ($have > $len) {
      $data = substr($this->rBuf_, 0, $len);
      $this->rBuf_ = substr($this->rBuf_, $len);
    }
    return $data;
  }

  public function read(int $len): string {
    if (strlen($this->rBuf_) === 0) {
      $this->rBuf_ = $this->transport_->read($this->rBufSize_);
    }

    if (strlen($this->rBuf_) <= $len) {
      $ret = $this->rBuf_;
      $this->rBuf_ = '';
      return $ret;
    }

    $ret = substr($this->rBuf_, 0, $len);
    $this->rBuf_ = substr($this->rBuf_, $len);
    return $ret;
  }

  /**
   * Peek some bytes in the buffer without removing the bytes from it
   *
   * @param int $len   length to peek
   * @param int $start the start position of the returned string
   *
   * @return null on peek failure
   */
  public function peek(int $len, int $start = 0): string {
    $bytes_needed = $len + $start;
    // read until either timeout OR get enough bytes
    if (strlen($this->rBuf_) == 0) {
      $this->rBuf_ = $this->transport_->readAll($bytes_needed);
    } else if ($bytes_needed > strlen($this->rBuf_)) {
      $this->rBuf_ .=
        $this->transport_->readAll($bytes_needed - strlen($this->rBuf_));
    }

    $ret = substr($this->rBuf_, $start, $len);
    return $ret;
  }

  public function write(string $buf): void {
    $this->wBuf_ .= $buf;
    if (strlen($this->wBuf_) >= $this->wBufSize_) {
      $out = $this->wBuf_;

      // Note that we clear the internal wBuf_ prior to the underlying write
      // to ensure we're in a sane state (i.e. internal buffer cleaned)
      // if the underlying write throws up an exception
      $this->wBuf_ = '';
      $this->transport_->write($out);
    }
  }

  public function flush(): void {
    if (strlen($this->wBuf_) > 0) {
      $this->transport_->write($this->wBuf_);
      $this->wBuf_ = '';
    }
    $this->transport_->flush();
  }

}


<?hh
/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @package thrift.transport
 */

/**
 * Framed transport. Writes and reads data in chunks that are stamped with
 * their length.
 *
 * @package thrift.transport
 */
class TFramedTransport extends TTransport
  implements TTransportStatus, IThriftBufferedTransport {

  /**
   * Underlying transport object.
   *
   * @var TTransport
   */
  protected TTransport $transport_;

  /**
   * Buffer for read data.
   *
   * @var string
   */
  protected string $rBuf_ = '';

  /**
   * Position in rBuf_ to read the next char
   *
   * @var int
   */
  protected int $rIndex_ = 0;

  /**
   * Buffer for queued output data
   *
   * @var string
   */
  protected string $wBuf_ = '';

  /**
   * Whether to frame reads
   *
   * @var bool
   */
  private bool $read_;

  /**
   * Whether to frame writes
   *
   * @var bool
   */
  private bool $write_;

  /**
   * Constructor.
   *
   * @param TTransport $transport Underlying transport
   */
  public function __construct(
    ?TTransport $transport = null,
    bool $read = true,
    bool $write = true,
  ) {
    $this->transport_ = $transport ?: new TNullTransport();
    $this->read_ = $read;
    $this->write_ = $write;
  }

  public function isOpen(): bool {
    return $this->transport_->isOpen();
  }

  public function open(): void {
    $this->transport_->open();
  }

  public function close(): void {
    $this->transport_->close();
  }

  public function isReadable(): bool {
    if (strlen($this->rBuf_) > 0) {
      return true;
    }

    if ($this->transport_ instanceof TTransportStatus) {
      return $this->transport_->isReadable();
    }

    return true;
  }

  public function isWritable(): bool {
    if ($this->transport_ instanceof TTransportStatus) {
      return $this->transport_->isWritable();
    }

    return true;
  }

  public function minBytesAvailable(): int {
    return strlen($this->rBuf_) - $this->rIndex_;
  }

  /**
   * Reads from the buffer. When more data is required reads another entire
   * chunk and serves future reads out of that.
   *
   * @param int $len How much data
   */
  public function read(int $len): string {
    if (!$this->read_) {
      return $this->transport_->read($len);
    }

    if (strlen($this->rBuf_) === 0) {
      $this->readFrame();
    }

    // Return substr
    $out = substr($this->rBuf_, $this->rIndex_, $len);
    $this->rIndex_ += $len;

    if (strlen($this->rBuf_) <= $this->rIndex_) {
      $this->rBuf_ = '';
      $this->rIndex_ = 0;
    }
    return $out;
  }

  /**
   * Peek some bytes in the frame without removing the bytes from the buffer
   *
   * @param int $len   length to peek
   * @param int $start the start position of the returned string
   */
  public function peek(int $len, int $start = 0): string {
    if (!$this->read_) {
      return '';
    }

    if (strlen($this->rBuf_) === 0) {
      $this->readFrame();
    }

    // Return substr
    $out = substr($this->rBuf_, $this->rIndex_ + $start, $len);

    return $out;
  }

  /**
   * Put previously read data back into the buffer
   *
   * @param string $data data to return
   */
  public function putBack(string $data): void {
    if (strlen($this->rBuf_) === 0) {
      $this->rBuf_ = $data;
    } else {
      $this->rBuf_ = ($data.(string) substr($this->rBuf_, $this->rIndex_));
    }
    $this->rIndex_ = 0;
  }

  /**
   * Reads a chunk of data into the internal read buffer.
   */
  private function readFrame(): void {
    $buf = $this->transport_->readAll(4);
    $val = unpack('N', $buf);
    $sz = $val[1];

    $this->rBuf_ = $this->transport_->readAll($sz);
    $this->rIndex_ = 0;
  }

  /**
   * Writes some data to the pending output buffer.
   *
   * @param string $buf The data
   * @param int    $len Limit of bytes to write
   */
  public function write(string $buf, ?int $len = null): void {
    if (!$this->write_) {
      $this->transport_->write($buf);
      return;
    }

    if ($len !== null && $len < strlen($buf)) {
      $buf = substr($buf, 0, $len);
    }
    $this->wBuf_ .= $buf;
  }

  /**
   * Writes the output buffer to the stream in the format of a 4-byte length
   * followed by the actual data.
   */
  public function flush(): void {
    if (!$this->write_ || strlen($this->wBuf_) == 0) {
      $this->transport_->flush();
      return;
    }

    $out = (string) pack('N', strlen($this->wBuf_));
    $out .= $this->wBuf_;

    // Note that we clear the internal wBuf_ prior to the underlying write
    // to ensure we're in a sane state (i.e. internal buffer cleaned)
    // if the underlying write throws up an exception
    $this->wBuf_ = '';
    $this->transport_->write($out);
    $this->transport_->flush();
  }

}