<?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
 */

/**
 * NOTE(mcslee): This currently contains a ton of duplicated code from TBase
 * because we need to save CPU cycles and this is not yet in an extension.
 * Ideally we'd multiply-inherit TException from both Exception and Base, but
 * that's not possible in PHP and there are no modules either, so for now we
 * apologetically take a trip to HackTown.
 *
 * Can be called with standard Exception constructor (message, code) or with
 * Thrift Base object constructor (spec, vals).
 *
 * @param mixed $p1 Message (string) or type-spec (array)
 * @param mixed $p2 Code (integer) or values (array)
 */
class TException extends Exception {
  public function __construct($p1 = null, $p2 = 0) {
    if (is_array($p1) && is_array($p2)) {
      $spec = $p1;
      $vals = $p2;
      foreach ($spec as $fid => $fspec) {
        $var = $fspec['var'];
        if (isset($vals[$var])) {
          $this->$var = $vals[$var];
        }
      }
      parent::__construct();
    } else {
      parent::__construct((string) $p1, $p2);
    }
  }

  static
    $tmethod = array(
      TType::BOOL => 'Bool',
      TType::BYTE => 'Byte',
      TType::I16 => 'I16',
      TType::I32 => 'I32',
      TType::I64 => 'I64',
      TType::DOUBLE => 'Double',
      TType::STRING => 'String',
      TType::FLOAT => 'Float',
    );

  private function _readMap(&$var, $spec, $input) {
    $xfer = 0;
    $ktype = $spec['ktype'];
    $vtype = $spec['vtype'];
    $kread = $vread = null;
    $kspec = array();
    $vspec = array();
    if (isset(TBase::$tmethod[$ktype])) {
      $kread = 'read'.TBase::$tmethod[$ktype];
    } else {
      $kspec = $spec['key'];
    }
    if (isset(TBase::$tmethod[$vtype])) {
      $vread = 'read'.TBase::$tmethod[$vtype];
    } else {
      $vspec = $spec['val'];
    }
    $var = array();
    $_ktype = 0;
    $_vtype = 0;
    $size = 0;
    $xfer += $input->readMapBegin(inout $_ktype, inout $_vtype, inout $size);
    for ($i = 0; $i < $size; ++$i) {
      $key = $val = null;
      if ($kread !== null) {
        $xfer += $input->$kread($key);
      } else {
        switch ($ktype) {
          case TType::STRUCT:
            $class = $kspec['class'];
            $key = new $class();
            $xfer += $key->read($input);
            break;
          case TType::MAP:
            $xfer += $this->_readMap($key, $kspec, $input);
            break;
          case TType::LST:
            $xfer += $this->_readList($key, $kspec, $input, false);
            break;
          case TType::SET:
            $xfer += $this->_readList($key, $kspec, $input, true);
            break;
        }
      }
      if ($vread !== null) {
        $xfer += $input->$vread($val);
      } else {
        switch ($vtype) {
          case TType::STRUCT:
            $class = $vspec['class'];
            $val = new $class();
            $xfer += $val->read($input);
            break;
          case TType::MAP:
            $xfer += $this->_readMap($val, $vspec, $input);
            break;
          case TType::LST:
            $xfer += $this->_readList($val, $vspec, $input, false);
            break;
          case TType::SET:
            $xfer += $this->_readList($val, $vspec, $input, true);
            break;
        }
      }
      $var[$key] = $val;
    }
    $xfer += $input->readMapEnd();
    return $xfer;
  }

  private function _readList(&$var, $spec, $input, $set = false) {
    $xfer = 0;
    $etype = $spec['etype'];
    $eread = $vread = null;
    if (isset(TBase::$tmethod[$etype])) {
      $eread = 'read'.TBase::$tmethod[$etype];
    } else {
      $espec = $spec['elem'];
    }
    /* UNSAFE_EXPR - this array is used both as vector and a map */
    $var = array();
    $_etype = $size = 0;
    if ($set) {
      $xfer += $input->readSetBegin($_etype, $size);
    } else {
      $xfer += $input->readListBegin($_etype, $size);
    }
    for ($i = 0; $i < $size; ++$i) {
      $elem = null;
      if ($eread !== null) {
        $xfer += $input->$eread($elem);
      } else {
        $espec = $spec['elem'];
        switch ($etype) {
          case TType::STRUCT:
            $class = $espec['class'];
            $elem = new $class();
            $xfer += $elem->read($input);
            break;
          case TType::MAP:
            $xfer += $this->_readMap($elem, $espec, $input);
            break;
          case TType::LST:
            $xfer += $this->_readList($elem, $espec, $input, false);
            break;
          case TType::SET:
            $xfer += $this->_readList($elem, $espec, $input, true);
            break;
        }
      }
      if ($set) {
        $var[$elem] = true;
      } else {
        $var[] = $elem;
      }
    }
    if ($set) {
      $xfer += $input->readSetEnd();
    } else {
      $xfer += $input->readListEnd();
    }
    return $xfer;
  }

  protected function _read($class, $spec, $input) {
    $xfer = 0;
    $fname = null;
    $ftype = 0;
    $fid = 0;
    $xfer += $input->readStructBegin(inout $fname);
    while (true) {
      $xfer += $input->readFieldBegin(inout $fname, inout $ftype, inout $fid);
      if ($ftype == TType::STOP) {
        break;
      }
      if (isset($spec[$fid])) {
        $fspec = $spec[$fid];
        $var = $fspec['var'];
        if ($ftype == $fspec['type']) {
          $xfer = 0;
          if (isset(TBase::$tmethod[$ftype])) {
            $func = 'read'.TBase::$tmethod[$ftype];
            $xfer += $input->$func($this->$var);
          } else {
            switch ($ftype) {
              case TType::STRUCT:
                $class = $fspec['class'];
                $this->$var = new $class();
                $xfer += $this->$var->read($input);
                break;
              case TType::MAP:
                $xfer += $this->_readMap($this->$var, $fspec, $input);
                break;
              case TType::LST:
                $xfer +=
                  $this->_readList($this->$var, $fspec, $input, false);
                break;
              case TType::SET:
                $xfer +=
                  $this->_readList($this->$var, $fspec, $input, true);
                break;
            }
          }
        } else {
          $xfer += $input->skip($ftype);
        }
      } else {
        $xfer += $input->skip($ftype);
      }
      $xfer += $input->readFieldEnd();
    }
    $xfer += $input->readStructEnd();
    return $xfer;
  }

  private function _writeMap($var, $spec, $output) {
    $xfer = 0;
    $ktype = $spec['ktype'];
    $vtype = $spec['vtype'];
    $kwrite = $vwrite = null;
    $kspec = null;
    $vspec = null;
    if (isset(TBase::$tmethod[$ktype])) {
      $kwrite = 'write'.TBase::$tmethod[$ktype];
    } else {
      $kspec = $spec['key'];
    }
    if (isset(TBase::$tmethod[$vtype])) {
      $vwrite = 'write'.TBase::$tmethod[$vtype];
    } else {
      $vspec = $spec['val'];
    }
    $xfer += $output->writeMapBegin($ktype, $vtype, count($var));
    foreach ($var as $key => $val) {
      if (isset($kwrite)) {
        $xfer += $output->$kwrite($key);
      } else {
        switch ($ktype) {
          case TType::STRUCT:
            $xfer += $key->write($output);
            break;
          case TType::MAP:
            $xfer += $this->_writeMap($key, $kspec, $output);
            break;
          case TType::LST:
            $xfer += $this->_writeList($key, $kspec, $output, false);
            break;
          case TType::SET:
            $xfer += $this->_writeList($key, $kspec, $output, true);
            break;
        }
      }
      if (isset($vwrite)) {
        $xfer += $output->$vwrite($val);
      } else {
        switch ($vtype) {
          case TType::STRUCT:
            $xfer += $val->write($output);
            break;
          case TType::MAP:
            $xfer += $this->_writeMap($val, $vspec, $output);
            break;
          case TType::LST:
            $xfer += $this->_writeList($val, $vspec, $output, false);
            break;
          case TType::SET:
            $xfer += $this->_writeList($val, $vspec, $output, true);
            break;
        }
      }
    }
    $xfer += $output->writeMapEnd();
    return $xfer;
  }

  private function _writeList($var, $spec, $output, $set = false) {
    $xfer = 0;
    $etype = $spec['etype'];
    $ewrite = null;
    $espec = null;
    if (isset(TBase::$tmethod[$etype])) {
      $ewrite = 'write'.TBase::$tmethod[$etype];
    } else {
      $espec = $spec['elem'];
    }
    if ($set) {
      $xfer += $output->writeSetBegin($etype, count($var));
    } else {
      $xfer += $output->writeListBegin($etype, count($var));
    }
    foreach ($var as $key => $val) {
      $elem = $set ? $key : $val;
      if (isset($ewrite)) {
        $xfer += $output->$ewrite($elem);
      } else {
        switch ($etype) {
          case TType::STRUCT:
            $xfer += $elem->write($output);
            break;
          case TType::MAP:
            $xfer += $this->_writeMap($elem, $espec, $output);
            break;
          case TType::LST:
            $xfer += $this->_writeList($elem, $espec, $output, false);
            break;
          case TType::SET:
            $xfer += $this->_writeList($elem, $espec, $output, true);
            break;
        }
      }
    }
    if ($set) {
      $xfer += $output->writeSetEnd();
    } else {
      $xfer += $output->writeListEnd();
    }
    return $xfer;
  }

  protected function _write($class, $spec, $output) {
    $xfer = 0;
    $xfer += $output->writeStructBegin($class);
    foreach ($spec as $fid => $fspec) {
      $var = $fspec['var'];
      if ($this->$var !== null) {
        $ftype = $fspec['type'];
        $xfer += $output->writeFieldBegin($var, $ftype, $fid);
        if (isset(TBase::$tmethod[$ftype])) {
          $func = 'write'.TBase::$tmethod[$ftype];
          $xfer += $output->$func($this->$var);
        } else {
          switch ($ftype) {
            case TType::STRUCT:
              $xfer += $this->$var->write($output);
              break;
            case TType::MAP:
              $xfer += $this->_writeMap($this->$var, $fspec, $output);
              break;
            case TType::LST:
              $xfer +=
                $this->_writeList($this->$var, $fspec, $output, false);
              break;
            case TType::SET:
              $xfer +=
                $this->_writeList($this->$var, $fspec, $output, true);
              break;
          }
        }
        $xfer += $output->writeFieldEnd();
      }
    }
    $xfer += $output->writeFieldStop();
    $xfer += $output->writeStructEnd();
    return $xfer;
  }

}


<?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
 */

class THandlerShortCircuitException extends Exception {
  const int R_SUCCESS = 0;
  const int R_EXPECTED_EX = 1;
  const int R_UNEXPECTED_EX = 2;

  public function __construct(public $result, public int $resultType) {
    parent::__construct();
  }
}


<?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
 */

abstract class ThriftAsyncProcessor extends ThriftProcessorBase
  implements IThriftAsyncProcessor {

  abstract const type TThriftIf as IThriftAsyncIf;

  final public async function processAsync(
    TProtocol $input,
    TProtocol $output,
  ): Awaitable<bool> {
    $rseqid = 0;
    $fname = '';
    $mtype = 0;

    $input->readMessageBegin(inout $fname, inout $mtype, inout $rseqid);
    $methodname = 'process_'.$fname;
    if (!method_exists($this, $methodname)) {
      $handler_ctx = $this->eventHandler_->getHandlerContext($fname);
      $this->eventHandler_->preRead($handler_ctx, $fname, array());
      $input->skip(TType::STRUCT);
      $input->readMessageEnd();
      $this->eventHandler_->postRead($handler_ctx, $fname, array());
      $x = new TApplicationException(
        'Function '.$fname.' not implemented.',
        TApplicationException::UNKNOWN_METHOD,
      );
      $this->eventHandler_->handlerError($handler_ctx, $fname, $x);
      $output->writeMessageBegin($fname, TMessageType::EXCEPTION, $rseqid);
      $x->write($output);
      $output->writeMessageEnd();
      $output->getTransport()->flush();
      return true;
    }
    /* UNSAFE_EXPR[2011]: This is safe */
    await $this->$methodname($rseqid, $input, $output);
    return true;
  }

  final public function process(TProtocol $input, TProtocol $output): bool {
    return HH\Asio\join($this->processAsync($input, $output));
  }
}


<?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
 */

<<__ConsistentConstruct>>
abstract class ThriftClientBase implements IThriftClient {
  protected TProtocol $input_;
  protected TProtocol $output_;
  protected ?IThriftAsyncChannel $channel_;
  protected TClientAsyncHandler $asyncHandler_;
  protected TClientEventHandler $eventHandler_;

  protected int $seqid_ = 0;

  final public static function factory(
  ): (string, (function(TProtocol, ?TProtocol, ?IThriftAsyncChannel): this)) {
    return tuple(
      get_called_class(),
      function(
        TProtocol $input,
        ?TProtocol $output,
        ?IThriftAsyncChannel $channel,
      ) {
        return new static($input, $output, $channel);
      },
    );
  }

  public function __construct(
    TProtocol $input,
    ?TProtocol $output = null,
    ?IThriftAsyncChannel $channel = null,
  ) {
    $this->input_ = $input;
    $this->output_ = $output ?: $input;
    $this->channel_ = $channel;
    $this->asyncHandler_ = new TClientAsyncHandler();
    $this->eventHandler_ = new TClientEventHandler();
  }

  public function setAsyncHandler(TClientAsyncHandler $async_handler): this {
    $this->asyncHandler_ = $async_handler;
    return $this;
  }

  public function getAsyncHandler(): TClientAsyncHandler {
    return $this->asyncHandler_;
  }

  public function setEventHandler(TClientEventHandler $event_handler): this {
    $this->eventHandler_ = $event_handler;
    return $this;
  }

  public function getEventHandler(): TClientEventHandler {
    return $this->eventHandler_;
  }

  protected function getNextSequenceID(): int {
    $currentseqid = $this->seqid_;
    if ($this->seqid_ >= 0x7fffffff) {
      $this->seqid_ = 0;
    } else {
      $this->seqid_++;
    }
    return $currentseqid;
  }

  <<__Memoize>>
  protected static function defaultOptions()[read_globals]: RpcOptions {
    return new RpcOptions();
  }

  /**
   * For use when you want to set specific options on THE NEXT RPC invocation
   * utilizing this client.
   *
   * You should call this IMMEDIATELY prior to your rpc call, as in
   * await $client->onceWithOptions($options)->myApiInvocation();
   *
   * IMPORTANT: as part of the next RPC call, these options will be reset, so
   * if you want to apply these options to multiple RPCs calls, you will need
   * to utilize this function repeatedly
   */
  public function onceWithOptions(RpcOptions $rpc_options)[write_props]: this {
    invariant(
      $this->options_ is null,
      'trying to set options twice on the same client at the same time',
    );
    $this->options_ = $rpc_options;
    return $this;
  }

  protected function getAndResetOptions()[write_props]: ?RpcOptions {
    $options = $this->options_;
    $this->options_ = null;
    return $options;
  }

  protected function sendImplHelper(
    IThriftStruct $args,
    string $function_name,
    bool $is_one_way,
  ): int {
    $currentseqid = $this->getNextSequenceID();
    try {
      $this->eventHandler_->preSend($function_name, $args, $currentseqid);
      if ($this->output_ is \TBinaryProtocolAccelerated) {
        thrift_protocol_write_binary(
          $this->output_,
          $function_name,
          TMessageType::CALL,
          $args,
          $currentseqid,
          $this->output_->isStrictWrite(),
          $is_one_way,
        );
      } else if ($this->output_ is \TCompactProtocolAccelerated) {
        thrift_protocol_write_compact(
          $this->output_,
          $function_name,
          TMessageType::CALL,
          $args,
          $currentseqid,
          $is_one_way,
        );
      } else {
        $this->output_->writeMessageBegin(
          $function_name,
          TMessageType::CALL,
          $currentseqid,
        );
        $args->write($this->output_);
        $this->output_->writeMessageEnd();
        if ($is_one_way) {
          $this->output_->getTransport()->onewayFlush();
        } else {
          $this->output_->getTransport()->flush();
        }
      }
    } catch (\THandlerShortCircuitException $ex) {
      switch ($ex->resultType) {
        case THandlerShortCircuitException::R_EXPECTED_EX:
        case THandlerShortCircuitException::R_UNEXPECTED_EX:
          $this->eventHandler_
            ->sendError($function_name, $args, $currentseqid, $ex->result);
          throw $ex->result;
        case THandlerShortCircuitException::R_SUCCESS:
        default:
          $this->eventHandler_->postSend($function_name, $args, $currentseqid);
          return $currentseqid;
      }
    } catch (\Exception $ex) {
      $this->eventHandler_
        ->sendError($function_name, $args, $currentseqid, $ex);
      throw $ex;
    }
    $this->eventHandler_->postSend($function_name, $args, $currentseqid);
    return $currentseqid;
  }

  protected function recvImplHelper<TResulttype as IResultThriftStruct, TRet>(
    classname<TResulttype> $result,
    string $name,
    bool $is_return_void,
    ?int $expectedsequenceid,
    shape(?'read_options' => int) $options = shape(),
  ): TRet where TRet = TResulttype::TResult {
    try {
      $this->eventHandler_->preRecv($name, $expectedsequenceid);
      if ($this->input_ is \TBinaryProtocolAccelerated) {
        $result = thrift_protocol_read_binary(
          $this->input_,
          $result,
          $this->input_->isStrictRead(),
          Shapes::idx($options, 'read_options', 0),
        );
      } else if ($this->input_ is \TCompactProtocolAccelerated) {
        $result = thrift_protocol_read_compact(
          $this->input_,
          $result,
          Shapes::idx($options, 'read_options', 0),
        );
      } else {
        $rseqid = 0;
        $fname = '';
        $mtype = 0;

        $this->input_
          ->readMessageBegin(inout $fname, inout $mtype, inout $rseqid);
        if ($mtype === TMessageType::EXCEPTION) {
          $x = new \TApplicationException();
          $x->read($this->input_);
          $this->input_->readMessageEnd();
          throw $x;
        }
        $result = $result::withDefaultValues();
        $result->read($this->input_);
        $this->input_->readMessageEnd();
        if ($expectedsequenceid !== null && ($rseqid !== $expectedsequenceid)) {
          throw new \TProtocolException(
            $name." failed: sequence id is out of order",
          );
        }
      }
    } catch (THandlerShortCircuitException $ex) {
      switch ($ex->resultType) {
        case THandlerShortCircuitException::R_EXPECTED_EX:
          $this->eventHandler_
            ->recvException($name, $expectedsequenceid, $ex->result);
          throw $ex->result;
        case THandlerShortCircuitException::R_UNEXPECTED_EX:
          $this->eventHandler_
            ->recvError($name, $expectedsequenceid, $ex->result);
          throw $ex->result;
        case THandlerShortCircuitException::R_SUCCESS:
        default:
          $this->eventHandler_
            ->postRecv($name, $expectedsequenceid, $ex->result);
          // this should just always be null in the ThriftSyncStructWithoutResult case
          return $ex->result;
      }
    } catch (\Exception $ex) {
      $this->eventHandler_->recvError($name, $expectedsequenceid, $ex);
      throw $ex;
    }

    if (!$is_return_void) {
      if ($result is ThriftSyncStructWithResult) {
        $successful_result = $result->success;
      } else if ($result is ThriftAsyncStructWithResult) {
        $successful_result = $result->success;
      } else {
        $successful_result = null;
      }
      if ($successful_result is nonnull) {
        $this->eventHandler_
          ->postRecv($name, $expectedsequenceid, $successful_result);
        return $successful_result;
      }
    }

    $exception = $result->checkForException();
    if ($exception is nonnull) {
      $this->eventHandler_
        ->recvException($name, $expectedsequenceid, $exception);
      throw $exception;
    }

    if ($is_return_void) {
      $this->eventHandler_
        ->postRecv($name, $expectedsequenceid, null);
      return
        HH\FIXME\UNSAFE_CAST<null, TRet>(null, 'FIXME[4110] TRet is void here');
    } else {
      $x = new \TApplicationException(
        $name." failed: unknown result",
        TApplicationException::MISSING_RESULT,
      );
      $this->eventHandler_
        ->recvError($name, $expectedsequenceid, $x);
      throw $x;
    }
  }

  protected async function genAwaitResponse<
    TResulttype as IResultThriftStruct,
    TRet,
  >(
    classname<TResulttype> $result,
    string $name,
    bool $is_return_void,
    int $expectedsequenceid,
    RpcOptions $rpc_options,
    shape(?'read_options' => int) $options = shape(),
  ): Awaitable<TRet> where TRet = TResulttype::TResult {
    $channel = $this->channel_;
    $out_transport = $this->output_->getTransport();
    $in_transport = $this->input_->getTransport();
    if (
      $channel !== null &&
      $out_transport is \TMemoryBuffer &&
      $in_transport is \TMemoryBuffer
    ) {
      $msg = $out_transport->getBuffer();
      $out_transport->resetBuffer();
      list($result_msg, $_read_headers) =
        await $channel->genSendRequestResponse($rpc_options, $msg);
      $in_transport->resetBuffer();
      $in_transport->write($result_msg);
    } else {
      await $this->asyncHandler_->genWait($expectedsequenceid);
    }
    $response = $this->recvImplHelper(
      $result,
      $name,
      $is_return_void,
      $expectedsequenceid,
      $options,
    );
    await $this->asyncHandler_->genAfter();
    return $response;
  }

  protected async function genAwaitNoResponse(
    RpcOptions $rpc_options,
  ): Awaitable<void> {
    $channel = $this->channel_;
    $out_transport = $this->output_->getTransport();
    if ($channel !== null && $out_transport is \TMemoryBuffer) {
      $msg = $out_transport->getBuffer();
      $out_transport->resetBuffer();
      await $channel->genSendRequestNoResponse($rpc_options, $msg);
    }
  }

  protected function recvImplStreamDecodeHelper<
    TStreamResponseType as IResultThriftStruct,
    TStreamType,
  >(
    classname<TStreamResponseType> $stream_response_type,
    string $name,
    shape(?'read_options' => int) $_options = shape(),
  ): (function(?string, ?\Exception): TStreamType)
  where
    TStreamType = TStreamResponseType::TResult {
    $protocol = $this->input_;
    return (?string $stream_payload, ?\Exception $ex) ==> {
      try {
        if ($ex !== null) {
          throw $ex;
        }
        $transport = $protocol->getTransport();
        invariant(
          $transport is \TMemoryBuffer,
          "Stream methods require TMemoryBuffer transport",
        );

        $transport->resetBuffer();
        $transport->write($stream_payload as nonnull);
        $result = $stream_response_type::withDefaultValues();
        $result->read($protocol);
        $protocol->readMessageEnd();
      } catch (\THandlerShortCircuitException $ex) {
        throw $ex->result;
      }
      if ($result is ThriftSyncStructWithResult) {
        $successful_result = $result->success;
      } else if ($result is ThriftAsyncStructWithResult) {
        $successful_result = $result->success;
      } else {
        $successful_result = null;
      }
      if ($successful_result !== null) {
        return $successful_result;
      }
      throw new \TApplicationException(
        $name." failed: unknown result",
        TApplicationException::MISSING_RESULT,
      );
    };
  }

  protected async function genAwaitStreamResponse<
    TFirstResponseType as IResultThriftStruct,
    TFirstType,
    TStreamResponseType as IResultThriftStruct,
    TStreamType,
  >(
    classname<TFirstResponseType> $first_response_type,
    classname<TStreamResponseType> $stream_response_type,
    string $name,
    bool $is_first_response_null,
    int $expectedsequenceid,
    RpcOptions $rpc_options,
    shape(?'read_options' => int) $options = shape(),
  ): Awaitable<\ResponseAndClientStream<TFirstType, TStreamType>>
  where
    TFirstType = TFirstResponseType::TResult,
    TStreamType = TStreamResponseType::TResult {

    $channel = $this->channel_;
    $out_transport = $this->output_->getTransport();
    $in_transport = $this->input_->getTransport();
    invariant(
      $channel !== null &&
        $out_transport is \TMemoryBuffer &&
        $in_transport is \TMemoryBuffer,
      "Stream methods require nonnull channel and TMemoryBuffer transport",
    );
    $msg = $out_transport->getBuffer();
    $out_transport->resetBuffer();
    list($result_msg, $_read_headers, $stream) =
      await $channel->genSendRequestStreamResponse($rpc_options, $msg);

    $stream_gen = $stream->gen<TStreamType>(
      $this->recvImplStreamDecodeHelper($stream_response_type, $name, $options),
    );
    $in_transport->resetBuffer();
    $in_transport->write($result_msg);
    $first_response = $this->recvImplHelper(
      $first_response_type,
      $name,
      $is_first_response_null,
      $expectedsequenceid,
      $options,
    );
    await $this->asyncHandler_->genAfter();
    return new \ResponseAndClientStream<TFirstType, TStreamType>(
      $first_response,
      $stream_gen,
    );
  }

  protected function sendImplSinkEncodeHelper<
    TSinkPayloadType as IResultThriftStruct,
    TSinkType,
  >(classname<TSinkPayloadType> $sink_payload_type): (function(
    ?TSinkType,
    ?\Exception,
  ): (string, bool)) where TSinkType = TSinkPayloadType::TResult {
    $protocol = $this->output_;
    return (?TSinkType $sink_payload, ?\Exception $ex) ==> {
      $transport = $protocol->getTransport();
      invariant(
        $transport is \TMemoryBuffer,
        "Sink methods require TMemoryBuffer transport",
      );

      $is_application_ex = false;
      if ($ex !== null) {
        if ($ex is \TApplicationException) {
          $is_application_ex = true;
          $result = $ex;
        } else {
          $result = new \TApplicationException(
            $ex->getMessage()."\n".$ex->getTraceAsString(),
          );
        }
      } else {
        $result = $sink_payload_type::withDefaultValues();
        if ($result is ThriftSyncStructWithResult) {
          $result->success = $sink_payload;
        } else if ($result is ThriftAsyncStructWithResult) {
          $result->success = $sink_payload;
        }
      }

      $result->write($protocol);
      $protocol->writeMessageEnd();
      $transport->flush();
      $msg = $transport->getBuffer();
      $transport->resetBuffer();
      return tuple($msg, $is_application_ex);
    };
  }

  protected function recvImplFinalResponseHelper<
    TSinkFinalResponseType as IResultThriftStruct,
    TSinkFinalType,
  >(
    classname<TSinkFinalResponseType> $sink_final_response_type,
    string $name,
  ): (function(?string, ?\Exception): TSinkFinalType)
  where
    TSinkFinalType = TSinkFinalResponseType::TResult {
    $protocol = $this->input_;
    return (?string $sink_final_response, ?\Exception $ex) ==> {
      try {
        if ($ex !== null) {
          throw $ex;
        }
        $transport = $protocol->getTransport();
        invariant(
          $transport is \TMemoryBuffer,
          "Stream methods require TMemoryBuffer transport",
        );

        $transport->resetBuffer();
        $transport->write($sink_final_response as nonnull);
        $result = $sink_final_response_type::withDefaultValues();
        $result->read($protocol);
        $protocol->readMessageEnd();
      } catch (\THandlerShortCircuitException $ex) {
        throw $ex->result;
      }
      if ($result is ThriftSyncStructWithResult) {
        if ($result->success !== null) {
          return $result->success;
        }
      } else if ($result is ThriftAsyncStructWithResult) {
        if ($result->success !== null) {
          return $result->success;
        }
      }
      throw new \TApplicationException(
        $name." failed: unknown result",
        TApplicationException::MISSING_RESULT,
      );
    };
  }

  protected async function genAwaitSinkResponse<
    TSinkFirstResponseType as IResultThriftStruct,
    TSinkFirstType,
    TSinkPayloadType as IResultThriftStruct,
    TSinkType,
    TSinkFinalResponseType as IResultThriftStruct,
    TSinkFinalType,
  >(
    classname<TSinkFirstResponseType> $first_response_type,
    classname<TSinkPayloadType> $sink_payload_type,
    classname<TSinkFinalResponseType> $final_response_type,
    string $name,
    bool $is_first_response_null,
    int $expectedsequenceid,
    RpcOptions $rpc_options,
    shape(?'read_options' => int) $options = shape(),
  ): Awaitable<
    \ResponseAndClientSink<TSinkFirstType, TSinkType, TSinkFinalType>,
  >
  where
    TSinkFirstType = TSinkFirstResponseType::TResult,
    TSinkType = TSinkPayloadType::TResult,
    TSinkFinalType = TSinkFinalResponseType::TResult {

    $channel = $this->channel_;
    $out_transport = $this->output_->getTransport();
    $in_transport = $this->input_->getTransport();
    invariant(
      $channel !== null &&
        $out_transport is \TMemoryBuffer &&
        $in_transport is \TMemoryBuffer,
      "Sink methods require nonnull channel and TMemoryBuffer transport",
    );

    $msg = $out_transport->getBuffer();
    $out_transport->resetBuffer();
    list($result_msg, $_read_headers, $sink) =
      await $channel->genSendRequestSink($rpc_options, $msg);

    $payload_serializer = $this->sendImplSinkEncodeHelper($sink_payload_type);
    $final_response_deserializer =
      $this->recvImplFinalResponseHelper($final_response_type, $name);
    $client_sink_func = async function(
      AsyncGenerator<null, TSinkType, void> $pld_generator,
    ) use ($sink, $payload_serializer, $final_response_deserializer) {
      return await $sink->genSink<TSinkType, TSinkFinalType>(
        $pld_generator,
        $payload_serializer,
        $final_response_deserializer,
      );
    };

    $in_transport->resetBuffer();
    $in_transport->write($result_msg);
    $first_response = $this->recvImplHelper(
      $first_response_type,
      $name,
      $is_first_response_null,
      $expectedsequenceid,
      $options,
    );

    await $this->asyncHandler_->genAfter();
    return new \ResponseAndClientSink<
      TSinkFirstType,
      TSinkType,
      TSinkFinalType,
    >($first_response, $client_sink_func);
  }
}


<?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
 */

abstract class ThriftProcessorBase implements IThriftProcessor {
  abstract const type TThriftIf as IThriftIf;
  protected TProcessorEventHandler $eventHandler_;

  // This exists so subclasses still using php can still access the handler
  // Once the migration to hack is complete, this field can be removed safely
  protected mixed $handler_;

  public function __construct(
    protected this::TThriftIf $handler,
  ) {
    $this->eventHandler_ = new TProcessorEventHandler();
    $this->handler_ = $handler;
  }

  public function getHandler(): this::TThriftIf {
    return $this->handler;
  }

  public function setEventHandler(TProcessorEventHandler $event_handler): this {
    $this->eventHandler_ = $event_handler;
    return $this;
  }

  public function getEventHandler(): TProcessorEventHandler {
    return $this->eventHandler_;
  }
}


<?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.
 */

/**
 * Generic Protocol Reader and Writer
 */
class ThriftSerializationHelper {
  public static function readStruct(
    TProtocol $protocol,
    $object,
  ): int {
    $field_name = '';
    $field_type = 0;
    $field_id = 0;

    $tspec = $object::$_TSPEC;
    $xfer = $protocol->readStructBegin(inout $field_name);
    while (true) {
      $xfer += $protocol->readFieldBegin(
        inout $field_name,
        inout $field_type,
        inout $field_id,
      );

      // Break once we reach the end of the struct.
      if ($field_type === TType::STOP) {
        break;
      }

      if ($field_id !== null) {
        // Compact, Binary and JSON:
        // These protocols only encode the field id.

        // Versioning:
        // Skip type if there is a mismatch between the tspec and the
        // type we obtain from the buffer.
        if (!isset($tspec[$field_id]) ||
            $field_type !== $tspec[$field_id]['type']) {
          $xfer += $protocol->skip($field_type);
          $xfer += $protocol->readFieldEnd();
          continue;
        }

        // This uses the TSPEC to find the field name.
        $field_name = $tspec[$field_id]['var'];
      } else {
        // SimpleJSON:
        // This protocol only encodes the field name.

        // Versioning:
        // Skip type if there is a mismatch between the tspec and the
        // type that we obtain from the buffer.
        if (!array_key_exists($field_name, $object::$_TFIELDMAP)) {
          $xfer += $protocol->skip($field_type);
          $xfer += $protocol->readFieldEnd();
          continue;
        }

        // This uses the TFIELDMAP to find the field id.
        $field_id = $object::$_TFIELDMAP[$field_name];
      }

      $xfer += self::readStructHelper(
        $protocol,
        $tspec[$field_id]['type'],
        &$object->$field_name,
        $tspec[$field_id]
      );

      $xfer += $protocol->readFieldEnd();
    }
    $xfer += $protocol->readStructEnd();
    return $xfer;
  }

  public static function readUnion(
    TProtocol $protocol,
    $object,
    &$union_enum,
  ): int {
    $field_name = '';
    $field_type = 0;
    $field_id = 0;

    $tspec = $object::$_TSPEC;
    $union_enum_name = get_class($object) . "Enum";
    $union_enum = $union_enum_name::_EMPTY_;
    $xfer = $protocol->readStructBegin(inout $field_name);
    while (true) {
      $xfer += $protocol->readFieldBegin(
        inout $field_name,
        inout $field_type,
        inout $field_id,
      );

      // Break once we reach the end of the struct.
      if ($field_type === TType::STOP) {
        break;
      }

      if ($field_id !== null) {
        // Compact, Binary and JSON:
        // These protocols only encode the field id.

        // Versioning:
        // Skip type if there is a mismatch between the tspec and the
        // type we obtain from the buffer.
        if (!isset($tspec[$field_id]) ||
            $field_type !== $tspec[$field_id]['type']) {
          $xfer += $protocol->skip($field_type);
          $xfer += $protocol->readFieldEnd();
          continue;
        }

        // This uses the TSPEC to find the field name.
        $field_name = $tspec[$field_id]['var'];
      } else {
        // SimpleJSON:
        // This protocol only encodes the field name.

        // Versioning:
        // Skip type if there is a mismatch between the tspec and the
        // type that we obtain from the buffer.
        // Since SimpleJSON doesn't have a field id, we use reflection
        // to inspect the object for the element.
        if (!array_key_exists($field_name, $object::$_TFIELDMAP)) {
          $xfer += $protocol->skip($field_type);
          $xfer += $protocol->readFieldEnd();
          continue;
        }

        // This uses the TFIELDMAP to find the field id.
        $field_id = $object::$_TFIELDMAP[$field_name];
      }

      $field_name_tmp = $object->$field_name;
      $xfer += self::readStructHelper(
        $protocol,
        $tspec[$field_id]['type'],
        &$field_name_tmp,
        $tspec[$field_id]
      );
      $object->$field_name = $field_name_tmp;
      $union_enum = $union_enum_name::coerce($object::$_TFIELDMAP[$field_name]);
      $xfer += $protocol->readFieldEnd();
    }
    $xfer += $protocol->readStructEnd();
    return $xfer;
  }

  private static function readStructHelper(
    TProtocol $protocol,
    $field_type,
    &$object,
    $tspec,
  ): int {
    $xfer = 0;
    switch ($field_type) {
      case TType::BOOL:
        $xfer += $protocol->readBool(&$object);
        break;
      case TType::BYTE:
        $xfer += $protocol->readByte(&$object);
        break;
      case TType::I16:
        $xfer += $protocol->readI16(&$object);
        break;
      case TType::I32:
        // Enums:
        // In Hack, enums are encoded as I32s.
        // This looks into the tspec to distinguish the two of them.
        // Optimization opportunity: Add a TType of enum and encode that to
        // the tspec to avoid this if statement.
        if (isset($tspec['enum'])) {
          $val = null;
          $xfer += $protocol->readI32(&$val);
          $enum_class = $tspec['enum'];
          $object = $enum_class::coerce($val);
        } else {
          $xfer += $protocol->readI32(&$object);
        }
        break;
      case TType::I64:
        $xfer += $protocol->readI64(&$object);
        break;
      case TType::DOUBLE:
        $xfer += $protocol->readDouble(&$object);
        break;
      case TType::FLOAT:
        $xfer += $protocol->readFloat(&$object);
        break;
      case TType::STRING:
        $xfer += $protocol->readString(&$object);
        break;
      case TType::LST:
        $size = 0;
        $element_type = 0;
        $xfer += $protocol->readListBegin(&$element_type, &$size);

        // Use the correct collection.
        $list = null;
        if ($tspec['format'] === 'harray') {
          $list = vec[];
        } else if ($tspec['format'] === 'collection') {
          $list = Vector {};
        } else { // format === 'array'
          $list = varray[];
        }

        for ($i = 0; $size === null || $i < $size; ++$i) {
          if ($size === null && !$protocol->readListHasNext()) {
            break;
          }

          $list_element = null;
          $xfer += self::readStructHelper(
            $protocol,
            $tspec['etype'],
            &$list_element,
            $tspec['elem']
          );

          // If the element type is enum and the enum
          // does not exist, it will return a null.
          if ($list_element === null) {
            continue;
          }

          /* HH_IGNORE_ERROR[4006] type can be Vector, vec, or array */
          $list[] = $list_element;
        }
        $object = $list;
        $xfer += $protocol->readListEnd();
        break;
      case TType::SET:
        $size = 0;
        $element_type = 0;
        $xfer += $protocol->readSetBegin(&$element_type, &$size);

        // Use the correct collection.
        if ($tspec['format'] === 'harray') {
          $set = keyset[];
        } else if ($tspec['format'] === 'collection') {
          $set = Set {};
        } else { // format === 'array'
          $set = array();
        }

        for ($i = 0; $size === null || $i < $size; ++$i) {
          if ($size === null && !$protocol->readSetHasNext()) {
            break;
          }

          $set_element = null;
          $xfer += self::readStructHelper(
            $protocol,
            $tspec['etype'],
            &$set_element,
            $tspec['elem']
          );

          // If the element type is enum and the enum
          // does not exist, it will return a null.
          if ($set_element === null) {
            continue;
          }

          // When using a set array(), we can't append in the normal way.
          // Therefore, we need to distinguish between the two types
          // before we add the element to the set.
          if ($tspec['format'] === 'array') {
            invariant(is_array($set), 'for hack');
            $set[$set_element] = true;
          } else {
            /* HH_IGNORE_ERROR[4006] type will be Set or keyset */
            $set[] = $set_element;
          }
        }
        $object = $set;
        $xfer += $protocol->readSetEnd();
        break;
      case TType::MAP:
        $size = 0;
        $key_type = 0;
        $value_type = 0;
        $xfer += $protocol->readMapBegin(
          inout $key_type,
          inout $value_type,
          inout $size,
        );

        // Use the correct collection.
        $map = null;
        if ($tspec['format'] === 'harray') {
          $map = dict[];
        } else if ($tspec['format'] === 'collection') {
          $map = Map {};
        } else { // format === 'array'
          $map = array();
        }

        for ($i = 0; $size === null || $i < $size; ++$i) {
          if ($size === null && !$protocol->readMapHasNext()) {
            break;
          }

          $key = null;
          $val = null;
          $xfer += self::readStructHelper(
            $protocol,
            $tspec['ktype'],
            &$key,
            $tspec['key']
          );
          $xfer += self::readStructHelper(
            $protocol,
            $tspec['vtype'],
            &$val,
            $tspec['val']
          );

          // If the element type is enum and the enum
          // does not exist, it will return a null.
          if ($key === null || $val === null) {
            continue;
          }

          $map[$key] = $val;
        }
        $object = $map;
        $xfer += $protocol->readMapEnd();
        break;
      case TType::STRUCT:
        $object = new $tspec['class']();
        $xfer += $object->read($protocol);
        break;
      default:
        $xfer += $protocol->skip($field_type);
    }
    return $xfer;
  }

  public static function writeStruct(
    TProtocol $protocol,
    $object,
  ): int {
    $xfer = $protocol->writeStructBegin($object->getName());
    foreach ($object::$_TSPEC as $field_id => $field) {
      $field_name = $field['var'];

      // Optionals:
      // When a field is marked as optional and it's not set then
      // ignore the field. Otherwise, include default value.
      // Note: If an optional field has a default value,
      // it will still be added to the buffer.
      if (!isset($object->$field_name)) {
        continue;
      }

      $field_type = $field['type'];
      $xfer += $protocol->writeFieldBegin($field_name, $field_type, $field_id);
      $xfer += self::writeStructHelper(
        $protocol,
        $field_type,
        $object->$field_name,
        $field,
      );
      $xfer += $protocol->writeFieldEnd();
    }
    $xfer += $protocol->writeFieldStop();
    $xfer += $protocol->writeStructEnd();
    return $xfer;
  }

  private static function writeStructHelper(
    TProtocol $protocol,
    $field_type,
    $object,
    $tspec,
  ): int {
    $xfer = 0;
    switch ($field_type) {
      case TType::BOOL:
        $xfer += $protocol->writeBool($object);
        break;
      case TType::BYTE:
        $xfer += $protocol->writeByte($object);
        break;
      case TType::I16:
        $xfer += $protocol->writeI16($object);
        break;
      case TType::I32:
        $xfer += $protocol->writeI32($object);
        break;
      case TType::I64:
        $xfer += $protocol->writeI64($object);
        break;
      case TType::DOUBLE:
        $xfer += $protocol->writeDouble($object);
        break;
      case TType::FLOAT:
        $xfer += $protocol->writeFloat($object);
        break;
      case TType::STRING:
        $xfer += $protocol->writeString($object);
        break;
      case TType::LST:
        $xfer += $protocol->writeListBegin($tspec['etype'], count($object));
        if ($object !== null) {
          foreach ($object ?? vec[] as $iter) {
            $xfer += self::writeStructHelper(
              $protocol,
              $tspec['etype'],
              $iter,
              $tspec['elem'],
            );
          }
        }
        $xfer += $protocol->writeListEnd();
        break;
      case TType::SET:
        $xfer += $protocol->writeSetBegin($tspec['etype'], count($object));
        if ($object !== null) {
          foreach ($object as $kiter => $iter) {
            $xfer += self::writeStructHelper(
              $protocol,
              $tspec['etype'],
              $tspec['format'] === 'array' ? $kiter : $iter,
              $tspec['elem'],
            );
          }
        }
        $xfer += $protocol->writeSetEnd();
        break;
      case TType::MAP:
        $xfer += $protocol->writeMapBegin(
          $tspec['ktype'],
          $tspec['vtype'],
          count($object),
        );
        if ($object !== null) {
          foreach ($object ?? dict[] as $kiter => $viter) {
            $xfer += self::writeStructHelper(
              $protocol,
              $tspec['ktype'],
              $kiter,
              $tspec['key'],
            );
            $xfer += self::writeStructHelper(
              $protocol,
              $tspec['vtype'],
              $viter,
              $tspec['val'],
            );
          }
        }
        $xfer += $protocol->writeMapEnd();
        break;
      case TType::STRUCT:
        if ($object !== null) {
          $xfer += $object->write($protocol);
        } else {
          $xfer += $protocol->writeStructBegin($tspec['class']);
          $xfer += $protocol->writeStructEnd();
        }
        break;
      default:
        $xfer += $protocol->skip($field_type);
    }
    return $xfer;
  }
}


<?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.
 */

/**
 * Trait for Thrift Structs to call into the Serialization Helper
 */
trait ThriftSerializationTrait implements \IThriftStruct {
  public function read(\TProtocol $protocol) : int {
    return \ThriftSerializationHelper::readStruct($protocol, $this);
  }

  public function write(\TProtocol $protocol) : int {
    return \ThriftSerializationHelper::writeStruct($protocol, $this);
  }
}


<?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
 */

abstract final class ThriftStructTypes {
  const type TFieldSpec = shape(
    'var' => string,
    'type' => \TType,
    ?'union' => bool,
    ?'etype' => \TType,
    ?'elem' => self::TElemSpec,
    ?'ktype' => \TType,
    ?'vtype' => \TType,
    ?'key' => self::TElemSpec,
    ?'val' => self::TElemSpec,
    ?'format' => string,
    ?'class' => string,
    ?'enum' => string,
    ?'type_annotations' => varray<\IThriftStruct>,
    ?'field_annotations' => varray<\IThriftStruct>,
  );
  const type TElemSpec = shape(
    'type' => \TType,
    ?'etype' => \TType,
    ?'elem' => mixed, // self::TElemSpec once hack supports recursive types
    ?'ktype' => \TType,
    ?'vtype' => \TType,
    ?'key' => mixed, // self::TElemSpec once hack supports recursive types
    ?'val' => mixed, // self::TElemSpec once hack supports recursive types
    ?'format' => string,
    ?'class' => string,
    ?'enum' => string,
  );
  const type TGenericSpec = shape(
    'type' => \TType,
    ?'var' => string,
    ?'union' => bool,
    ?'etype' => \TType,
    ?'elem' => mixed, // self::TElemSpec once hack supports recursive types
    ?'ktype' => \TType,
    ?'vtype' => \TType,
    ?'key' => mixed, // self::TElemSpec once hack supports recursive types
    ?'val' => mixed, // self::TElemSpec once hack supports recursive types
    ?'format' => string,
    ?'class' => classname<\IThriftStruct>,
    ?'enum' => string, // enumname<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
 */

abstract class ThriftSyncProcessor
  extends ThriftProcessorBase {

  abstract const type TThriftIf as IThriftSyncIf;

  public function process(TProtocol $input, TProtocol $output): bool {
    $rseqid = 0;
    $fname = '';
    $mtype = 0;

    $input->readMessageBegin(inout $fname, inout $mtype, inout $rseqid);
    $methodname = 'process_'.$fname;
    if (!method_exists($this, $methodname)) {
      $handler_ctx = $this->eventHandler_->getHandlerContext($fname);
      $this->eventHandler_->preRead($handler_ctx, $fname, array());
      $input->skip(TType::STRUCT);
      $input->readMessageEnd();
      $this->eventHandler_->postRead($handler_ctx, $fname, array());
      $x = new TApplicationException(
        'Function '.$fname.' not implemented.',
        TApplicationException::UNKNOWN_METHOD,
      );
      $this->eventHandler_->handlerError($handler_ctx, $fname, $x);
      $output->writeMessageBegin($fname, TMessageType::EXCEPTION, $rseqid);
      $x->write($output);
      $output->writeMessageEnd();
      $output->getTransport()->flush();
      return true;
    }
    /* UNSAFE_EXPR[2011]: This is safe */
    $this->$methodname($rseqid, $input, $output);
    return true;
  }
}


<?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.
 */

/**
 * Trait for Thrift Unions to call into the Serialization Helper
 */
trait ThriftUnionSerializationTrait implements \IThriftStruct {

  public function read(\TProtocol $protocol): int {
    /* HH_IGNORE_ERROR[4053] every thrift union has a _type field */
    $type = $this->_type;
    $ret = \ThriftSerializationHelper::readUnion($protocol, $this, inout $type);
    /* HH_IGNORE_ERROR[4053] every thrift union has a _type field */
    $this->_type = $type;
    return $ret;
  }

  public function write(\TProtocol $protocol): int {
    // Writing Structs and Unions is the same procedure.
    return \ThriftSerializationHelper::writeStruct($protocol, $this);
  }
}


<?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
 */

abstract final class ThriftUtil {

  <<__Rx, __AtMostRxAsArgs>>
  public static function mapDict<Tk as arraykey, Tv1, Tv2>(
    <<__OnlyRxIfImpl(Rx\KeyedTraversable::class)>>
    KeyedTraversable<Tk, Tv1> $traversable,
    <<__AtMostRxAsFunc>> (function(Tv1): Tv2) $value_func,
  ): dict<Tk, Tv2> {
    $result = dict[];
    foreach ($traversable as $key => $value) {
      $result[$key] = $value_func($value);
    }
    return $result;
  }

  <<__Rx, __AtMostRxAsArgs>>
  public static function mapVec<Tv1, Tv2>(
    <<__OnlyRxIfImpl(Rx\Traversable::class)>> Traversable<Tv1> $traversable,
    <<__AtMostRxAsFunc>> (function(Tv1): Tv2) $value_func,
  ): vec<Tv2> {
    $result = vec[];
    foreach ($traversable as $value) {
      $result[] = $value_func($value);
    }
    return $result;
  }

  <<__Rx, __AtMostRxAsArgs>>
  public static function mapKeyset<Tv1, Tv2 as arraykey>(
    <<__OnlyRxIfImpl(Rx\Traversable::class)>> Traversable<Tv1> $traversable,
    <<__AtMostRxAsFunc>> (function(Tv1): Tv2) $value_func,
  ): keyset<Tv2> {
    $result = keyset[];
    foreach ($traversable as $value) {
      $result[] = $value_func($value);
    }
    return $result;
  }

  <<__Rx, __AtMostRxAsArgs>>
  public static function toDArray<Tk as arraykey, Tv>(
    <<__OnlyRxIfImpl(Rx\KeyedTraversable::class)>>
    KeyedTraversable<Tk, Tv> $traversable,
  ): darray<Tk, Tv> {
    $result = darray[];
    foreach ($traversable as $key => $value) {
      $result[HH\array_key_cast($key)] = $value;
    }
    /* HH_IGNORE_ERROR[4110] maintain lie */
    return $result;
  }
}


<?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
 */

/**
 * Message types for RPC
 */
abstract class TMessageType {
  const CALL = 1;
  const REPLY = 2;
  const EXCEPTION = 3;
}


<?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
 */

/**
 * Event handler for thrift processors
 */
class TProcessorEventHandler {

  // Called at the start of processing a handler method
  public function getHandlerContext(string $fn_name): mixed {
    return null;
  }

  // Called before the handler method's argument are read
  public function preRead(
    mixed $handler_context,
    string $fn_name,
    mixed $args,
  ): void {}

  // Called after the handler method's argument are read
  public function postRead(
    mixed $handler_context,
    string $fn_name,
    mixed $args,
  ): void {}

  // Called right before the handler is executed.
  // Exceptions thrown here are caught by handlerException and handlerError.
  public function preExec(
    mixed $handler_context,
    string $service_name,
    string $fn_name,
    mixed $args,
  ): void {}

  // Called right after the handler is executed.
  // Exceptions thrown here are caught by handlerException and handlerError.
  public function postExec(
    mixed $handler_context,
    string $fn_name,
    mixed $result,
  ): void {}

  // Called before the handler method's $results are written
  public function preWrite(
    mixed $handler_context,
    string $fn_name,
    mixed $result,
  ): void {}

  // Called after the handler method's $results are written
  public function postWrite(
    mixed $handler_context,
    string $fn_name,
    mixed $result,
  ): void {}

  // Called if (and only if) the handler threw an expected $exception.
  public function handlerException(
    mixed $handler_context,
    string $fn_name,
    Exception $exception,
  ): void {}

  /**
   * Called if (and only if) the handler threw an unexpected $exception.
   *
   * Note that this method is NOT called if the handler threw an
   * exception that is declared in the thrift service specification
   */
  public function handlerError(
    mixed $handler_context,
    string $fn_name,
    Exception $exception,
  ): 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
 */

final class TProcessorMultiEventHandler extends TProcessorEventHandler {
  private Map<string, TProcessorEventHandler> $handlers;

  public function __construct() {
    $this->handlers = Map {};
  }

  public function addHandler(
    string $key,
    TProcessorEventHandler $handler,
  ): this {
    $this->handlers[$key] = $handler;
    return $this;
  }

  public function getHandler(string $key): TProcessorEventHandler {
    return $this->handlers[$key];
  }

  public function removeHandler(string $key): TProcessorEventHandler {
    $handler = $this->getHandler($key);
    $this->handlers->remove($key);
    return $handler;
  }

  // Called at the start of processing a handler method
  public function getHandlerContext(string $fn_name): mixed {
    $context = Map {};
    foreach ($this->handlers as $key => $handler) {
      $context[$key] = $handler->getHandlerContext($fn_name);
    }

    return $context;
  }

  // Called before the handler method's argument are read
  public function preRead(
    mixed $handler_context,
    string $fn_name,
    mixed $args,
  ): void {
    invariant($handler_context instanceof Map, 'Context is not a Map');
    foreach ($this->handlers as $key => $handler) {
      $handler->preRead($handler_context->at($key), $fn_name, $args);
    }
  }

  // Called after the handler method's argument are read
  public function postRead(
    mixed $handler_context,
    string $fn_name,
    mixed $args,
  ): void {
    invariant($handler_context instanceof Map, 'Context is not a Map');
    foreach ($this->handlers as $key => $handler) {
      $handler->postRead($handler_context->at($key), $fn_name, $args);
    }
  }

  // Called right before the handler is executed.
  // Exceptions thrown here are caught by handlerException and handlerError.
  public function preExec(
    mixed $handler_context,
    string $service_name,
    string $fn_name,
    mixed $args,
  ): void {
    invariant($handler_context instanceof Map, 'Context is not a Map');
    foreach ($this->handlers as $key => $handler) {
      $handler->preExec(
        $handler_context->at($key),
        $service_name,
        $fn_name,
        $args
      );
    }
  }

  // Called right after the handler is executed.
  // Exceptions thrown here are caught by handlerException and handlerError.
  public function postExec(
    mixed $handler_context,
    string $fn_name,
    mixed $result,
  ): void {
    invariant($handler_context instanceof Map, 'Context is not a Map');
    foreach ($this->handlers as $key => $handler) {
      $handler->postExec($handler_context->at($key), $fn_name, $result);
    }
  }

  // Called before the handler method's $results are written
  public function preWrite(
    mixed $handler_context,
    string $fn_name,
    mixed $result,
  ): void {
    invariant($handler_context instanceof Map, 'Context is not a Map');
    foreach ($this->handlers as $key => $handler) {
      $handler->preWrite($handler_context->at($key), $fn_name, $result);
    }
  }

  // Called after the handler method's $results are written
  public function postWrite(
    mixed $handler_context,
    string $fn_name,
    mixed $result,
  ): void {
    invariant($handler_context instanceof Map, 'Context is not a Map');
    foreach ($this->handlers as $key => $handler) {
      $handler->postWrite($handler_context->at($key), $fn_name, $result);
    }
  }

  // Called if (and only if) the handler threw an expected $exception.
  public function handlerException(
    mixed $handler_context,
    string $fn_name,
    Exception $ex,
  ): void {
    invariant($handler_context instanceof Map, 'Context is not a Map');
    foreach ($this->handlers as $key => $handler) {
      $handler->handlerException($handler_context->at($key), $fn_name, $ex);
    }
  }

  /**
   * Called if (and only if) the handler threw an unexpected $exception.
   *
   * Note that this method is NOT called if the handler threw an
   * exception that is declared in the thrift service specification
   */
  public function handlerError(
    mixed $handler_context,
    string $fn_name,
    Exception $ex,
  ): void {
    invariant($handler_context instanceof Map, 'Context is not a Map');
    foreach ($this->handlers as $key => $handler) {
      $handler->handlerError($handler_context->at($key), $fn_name, $ex);
    }
  }
}


<?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
 */

/**
 * Data types that can be sent via Thrift
 */
enum TType: int as int {
  STOP = 0;
  VOID = 1;
  BOOL = 2;
  BYTE = 3;
  DOUBLE = 4;
  I16 = 6;
  I32 = 8;
  I64 = 10;
  STRING = 11;
  STRUCT = 12;
  MAP = 13;
  SET = 14;
  LST = 15; // N.B. cannot use LIST keyword in PHP!
  UTF8 = 16;
  UTF16 = 17;
  FLOAT = 19;
}


<?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
 */

// Result message and read headers
type TAsyncChannelResponse = (string, darray<string, string>);

<<Oncalls('thrift')>>
interface IThriftAsyncChannel {
  public function genSendRequestResponse(
    \RpcOptions $options,
    string $msg,
  ): Awaitable<TAsyncChannelResponse>;

  public function genSendRequestNoResponse(
    \RpcOptions $options,
    string $msg,
  ): Awaitable<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.protocol
 */

/**
 * Protocol base class module.
 */
abstract class TProtocol {
  // The below may seem silly, but it is to get around the problem that the
  // "instanceof" operator can only take in a T_VARIABLE and not a T_STRING
  // or T_CONSTANT_ENCAPSED_STRING. Using "is_a()" instead of "instanceof" is
  // a workaround but is deprecated in PHP5. This is used in the generated
  // deserialization code.
  public static $TBINARYPROTOCOLACCELERATED = 'TBinaryProtocolAccelerated';
  public static $TCOMPACTPROTOCOLACCELERATED = 'TCompactProtocolAccelerated';
  public static
    $TBINARYPROTOCOLUNACCELERATED = 'TBinaryProtocolUnaccelerated';
  public static
    $TCOMPACTPROTOCOLUNACCELERATED = 'TCompactProtocolUnaccelerated';

  /**
   * Underlying transport
   *
   * @var TTransport
   */
  protected TTransport $trans_;

  /**
   * Constructor
   */
  protected function __construct($trans) {
    $this->trans_ = $trans;
  }

  /**
   * Accessor for transport
   *
   * @return TTransport
   */
  public function getTransport() {
    return $this->trans_;
  }

  // NOTE: These are deprecated
  public function getOutputTransport() {
    return $this->trans_;
  }
  public function getInputTransport() {
    return $this->trans_;
  }

  /**
   * Writes the message header
   *
   * @param string $name Function name
   * @param int $type message type TMessageType::CALL or TMessageType::REPLY
   * @param int $seqid The sequence id of this message
   */
  public abstract function writeMessageBegin($name, $type, $seqid);

  /**
   * Close the message
   */
  public abstract function writeMessageEnd();

  /**
   * Writes a struct header.
   *
   * @param string     $name Struct name
   * @throws TException on write error
   * @return int How many bytes written
   */
  public abstract function writeStructBegin($name);

  /**
   * Close a struct.
   *
   * @throws TException on write error
   * @return int How many bytes written
   */
  public abstract function writeStructEnd();

  /*
   * Starts a field.
   *
   * @param string     $name Field name
   * @param int        $type Field type
   * @param int        $fid  Field id
   * @throws TException on write error
   * @return int How many bytes written
   */
  public abstract function writeFieldBegin($fieldName, $fieldType, $fieldId);

  public abstract function writeFieldEnd();

  public abstract function writeFieldStop();

  public abstract function writeMapBegin($keyType, $valType, $size);

  public abstract function writeMapEnd();

  public abstract function writeListBegin($elemType, $size);

  public abstract function writeListEnd();

  public abstract function writeSetBegin($elemType, $size);

  public abstract function writeSetEnd();

  public abstract function writeBool($bool);

  public abstract function writeByte($byte);

  public abstract function writeI16($i16);

  public abstract function writeI32($i32);

  public abstract function writeI64($i64);

  public abstract function writeDouble($dub);

  public abstract function writeFloat($flt);

  public abstract function writeString($str);

  /**
   * Reads the message header
   *
   * @param string $name Function name
   * @param int $type message type TMessageType::CALL or TMessageType::REPLY
   * @param int $seqid The sequence id of this message
   */
  public abstract function readMessageBegin(
    inout $name,
    inout $type,
    inout $seqid,
  );

  /**
   * Read the close of message
   */
  public abstract function readMessageEnd();

  public abstract function readStructBegin(inout $name);

  public abstract function readStructEnd();

  public abstract function readFieldBegin(
    inout $name,
    inout $fieldType,
    inout $fieldId,
  );

  public abstract function readFieldEnd();

  public abstract function readMapBegin(
    inout $keyType,
    inout $valType,
    inout $size,
  );

  public function readMapHasNext(): bool {
    throw new TProtocolException(
      get_called_class().' does not support unknown map sizes',
    );
  }

  public abstract function readMapEnd();

  public abstract function readListBegin(&$elemType, &$size);

  public function readListHasNext(): bool {
    throw new TProtocolException(
      get_called_class().' does not support unknown list sizes',
    );
  }

  public abstract function readListEnd();

  public abstract function readSetBegin(&$elemType, &$size);

  public function readSetHasNext(): bool {
    throw new TProtocolException(
      get_called_class().' does not support unknown set sizes',
    );
  }

  public abstract function readSetEnd();

  public abstract function readBool(&$bool);

  public abstract function readByte(&$byte);

  public abstract function readI16(&$i16);

  public abstract function readI32(&$i32);

  public abstract function readI64(&$i64);

  public abstract function readDouble(&$dub);

  public abstract function readFloat(&$flt);

  public abstract function readString(&$str);

  /**
   * The skip function is a utility to parse over unrecognized date without
   * causing corruption.
   *
   * @param TType $type What type is it
   */
  public function skip($type) {
    $_ref = null;
    switch ($type) {
      case TType::BOOL:
        return $this->readBool($_ref);
      case TType::BYTE:
        return $this->readByte($_ref);
      case TType::I16:
        return $this->readI16($_ref);
      case TType::I32:
        return $this->readI32($_ref);
      case TType::I64:
        return $this->readI64($_ref);
      case TType::DOUBLE:
        return $this->readDouble($_ref);
      case TType::FLOAT:
        return $this->readFloat($_ref);
      case TType::STRING:
        return $this->readString($_ref);
      case TType::STRUCT: {
          $result = $this->readStructBegin(inout $_ref);
          while (true) {
            $ftype = null;
            $result += $this->readFieldBegin(
              inout $_ref,
              inout $ftype,
              inout $_ref,
            );
            if ($ftype == TType::STOP) {
              break;
            }
            $result += $this->skip($ftype);
            $result += $this->readFieldEnd();
          }
          $result += $this->readStructEnd();
          return $result;
        }
      case TType::MAP: {
          $keyType = null;
          $valType = null;
          $size = 0;
          $result = $this->readMapBegin(
            inout $keyType,
            inout $valType,
            inout $size,
          );
          for ($i = 0; $size === null || $i < $size; $i++) {
            if ($size === null && !$this->readMapHasNext()) {
              break;
            }
            $result += $this->skip($keyType);
            $result += $this->skip($valType);
          }
          $result += $this->readMapEnd();
          return $result;
        }
      case TType::SET: {
          $elemType = null;
          $size = 0;
          $result = $this->readSetBegin($elemType, $size);
          for ($i = 0; $size === null || $i < $size; $i++) {
            if ($size === null && !$this->readSetHasNext()) {
              break;
            }
            $result += $this->skip($elemType);
          }
          $result += $this->readSetEnd();
          return $result;
        }
      case TType::LST: {
          $elemType = null;
          $size = 0;
          $result = $this->readListBegin($elemType, $size);
          for ($i = 0; $size === null || $i < $size; $i++) {
            if ($size === null && !$this->readSetHasNext()) {
              break;
            }
            $result += $this->skip($elemType);
          }
          $result += $this->readListEnd();
          return $result;
        }
      default:
        throw new TProtocolException(
          'Unknown field type: '.$type,
          TProtocolException::INVALID_DATA,
        );
    }
  }

  /**
   * Utility for skipping binary data
   *
   * @param TTransport $itrans TTransport object
   * @param int        $type   Field type
   */
  public static function skipBinary($itrans, $type) {
    switch ($type) {
      case TType::BOOL:
        return $itrans->readAll(1);
      case TType::BYTE:
        return $itrans->readAll(1);
      case TType::I16:
        return $itrans->readAll(2);
      case TType::I32:
        return $itrans->readAll(4);
      case TType::I64:
        return $itrans->readAll(8);
      case TType::DOUBLE:
        return $itrans->readAll(8);
      case TType::FLOAT:
        return $itrans->readAll(4);
      case TType::STRING:
        $len = unpack('N', $itrans->readAll(4));
        $len = $len[1];
        if ($len > 0x7fffffff) {
          $len = 0 - (($len - 1) ^ 0xffffffff);
        }
        return 4 + $itrans->readAll($len);
      case TType::STRUCT: {
          $result = 0;
          while (true) {
            $ftype = 0;
            $fid = 0;
            $data = $itrans->readAll(1);
            $arr = unpack('c', $data);
            $ftype = $arr[1];
            if ($ftype == TType::STOP) {
              break;
            }
            // I16 field id
            $result += $itrans->readAll(2);
            $result += self::skipBinary($itrans, $ftype);
          }
          return $result;
        }
      case TType::MAP: {
          // Ktype
          $data = $itrans->readAll(1);
          $arr = unpack('c', $data);
          $ktype = $arr[1];
          // Vtype
          $data = $itrans->readAll(1);
          $arr = unpack('c', $data);
          $vtype = $arr[1];
          // Size
          $data = $itrans->readAll(4);
          $arr = unpack('N', $data);
          $size = $arr[1];
          if ($size > 0x7fffffff) {
            $size = 0 - (($size - 1) ^ 0xffffffff);
          }
          $result = 6;
          for ($i = 0; $i < $size; $i++) {
            $result += self::skipBinary($itrans, $ktype);
            $result += self::skipBinary($itrans, $vtype);
          }
          return $result;
        }
      case TType::SET:
      case TType::LST: {
          // Vtype
          $data = $itrans->readAll(1);
          $arr = unpack('c', $data);
          $vtype = $arr[1];
          // Size
          $data = $itrans->readAll(4);
          $arr = unpack('N', $data);
          $size = $arr[1];
          if ($size > 0x7fffffff) {
            $size = 0 - (($size - 1) ^ 0xffffffff);
          }
          $result = 5;
          for ($i = 0; $i < $size; $i++) {
            $result += self::skipBinary($itrans, $vtype);
          }
          return $result;
        }
      default:
        throw new TProtocolException(
          'Unknown field type: '.$type,
          TProtocolException::INVALID_DATA,
        );
    }
  }
}


<?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
 */

/**
 * Protocol module. Contains all the types and definitions needed to implement
 * a protocol encoder/decoder.
 *
 * @package thrift.protocol
 */

/**
 * Protocol exceptions
 */
class TProtocolException extends TException {
  const UNKNOWN = 0;
  const INVALID_DATA = 1;
  const NEGATIVE_SIZE = 2;
  const SIZE_LIMIT = 3;
  const BAD_VERSION = 4;
  const NOT_IMPLEMENTED = 5;
  const MISSING_REQUIRED_FIELD = 6;

  public function __construct(?string $message = null, int $code = 0) {
    parent::__construct($message, $code);
  }
}


<?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
 */

/**
 * Protocol factory creates protocol objects from transports
 */
interface TProtocolFactory {
  /**
   * Build a protocol from the base transport
   *
   * @return TProtocol protocol
   */
  public function getProtocol(TTransport $trans): TProtocol;
}


<?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
 */

/**
 * Base class for serializing thrift structs using a TProtocol
 */
<<__ConsistentConstruct>>
abstract class TProtocolSerializer {
  abstract public static function serialize(IThriftStruct $object): string;

  abstract public static function deserialize<T as IThriftStruct>(
    string $str,
    T $object,
  ): T;
}


<?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
 */

/**
 * Do not use this class.
 *
 * This class exists for backwards compatibility, use TBinaryProtocolAccelerated
 * or TBinaryProtocolUnaccelerated
 * @deprecated
 */
class TBinaryProtocol extends TBinaryProtocolAccelerated {
  public function __construct(
    $trans,
    $strict_read = false,
    $strict_write = true,
  ) {
    parent::__construct($trans, $strict_read, $strict_write);
  }
}


<?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
 */

/**
 * Accelerated binary protocol: used in conjunction with the thrift_protocol
 * extension for faster deserialization
 */
class TBinaryProtocolAccelerated extends TBinaryProtocolBase {
  public function __construct(
    $trans,
    $strict_read = false,
    $strict_write = true,
  ) {
    // 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, $strict_read, $strict_write);
  }
  public function isStrictRead() {
    return $this->strictRead_;
  }
  public function isStrictWrite() {
    return $this->strictWrite_;
  }
}


<?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
 */

/**
 * Binary implementation of the Thrift protocol.
 */
abstract class TBinaryProtocolBase extends TProtocol {

  const VERSION_MASK = 0xffff0000;
  const VERSION_1 = 0x80010000;

  protected $strictRead_ = false;
  protected $strictWrite_ = true;
  protected $littleendian_ = false;
  protected $memory_limit = 128000000; //128M, the default
  protected $sequenceID = null;

  public function __construct(
    $trans,
    $strictRead = false,
    $strictWrite = true,
  ) {
    parent::__construct($trans);
    $this->strictRead_ = $strictRead;
    $this->strictWrite_ = $strictWrite;
    if (pack('S', 1) == "\x01\x00") {
      $this->littleendian_ = true;
    }
    $this->memory_limit = self::getBytes(ini_get('memory_limit'));
  }

  // helper function to get integer from potential php short notation
  public static function getBytes($notation) {
    $val = trim($notation);
    $last = strtolower($val[strlen($val) - 1]);
    switch ($last) {
      // The 'G' modifier is available since PHP 5.1.0
      case 'g':
        $val *= 1024;
        // FALLTHROUGH
      case 'm':
        $val *= 1024;
        // FALLTHROUGH
      case 'k':
        $val *= 1024;
    }

    return $val;
  }

  public function writeMessageBegin($name, $type, $seqid) {
    if ($this->strictWrite_) {
      $version = self::VERSION_1 | $type;
      return
        $this->writeI32($version) +
        $this->writeString($name) +
        $this->writeI32($seqid);
    } else {
      return
        $this->writeString($name) +
        $this->writeByte($type) +
        $this->writeI32($seqid);
    }
  }

  public function writeMessageEnd() {
    return 0;
  }

  public function writeStructBegin($name) {
    return 0;
  }

  public function writeStructEnd() {
    return 0;
  }

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

  public function writeFieldEnd() {
    return 0;
  }

  public function writeFieldStop() {
    return $this->writeByte(TType::STOP);
  }

  public function writeMapBegin($keyType, $valType, $size) {
    return
      $this->writeByte($keyType) +
      $this->writeByte($valType) +
      $this->writeI32($size);
  }

  public function writeMapEnd() {
    return 0;
  }

  public function writeListBegin($elemType, $size) {
    return $this->writeByte($elemType) + $this->writeI32($size);
  }

  public function writeListEnd() {
    return 0;
  }

  public function writeSetBegin($elemType, $size) {
    return $this->writeByte($elemType) + $this->writeI32($size);
  }

  public function writeSetEnd() {
    return 0;
  }

  public function writeBool($value) {
    $data = pack('c', $value ? 1 : 0);
    $this->trans_->write($data);
    return 1;
  }

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

  public function writeI16($value) {
    $data = chr($value).chr($value >> 8);
    if ($this->littleendian_) {
      $data = strrev($data);
    }
    $this->trans_->write($data);
    return 2;
  }

  public function writeI32($value) {
    $data = chr($value).chr($value >> 8).chr($value >> 16).chr($value >> 24);
    if ($this->littleendian_) {
      $data = strrev($data);
    }
    $this->trans_->write($data);
    return 4;
  }

  public function writeI64($value) {
    $data = '';
    // 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
    if (PHP_INT_SIZE == 4) {
      $neg = $value < 0;

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

      $hi = (int) ($value / 4294967296);
      $lo = (int) $value;

      if ($neg) {
        $hi = ~$hi;
        $lo = ~$lo;
        if (($lo & (int) 0xffffffff) == (int) 0xffffffff) {
          $lo = 0;
          $hi++;
        } else {
          $lo++;
        }
      }
      $data = pack('N2', $hi, $lo);

    } else {
      $data =
        chr($value).
        chr($value >> 8).
        chr($value >> 16).
        chr($value >> 24).
        chr($value >> 32).
        chr($value >> 40).
        chr($value >> 48).
        chr($value >> 56);
      if ($this->littleendian_) {
        $data = strrev($data);
      }
    }

    $this->trans_->write($data);
    return 8;
  }

  public function writeDouble($value) {
    $data = pack('d', $value);
    if ($this->littleendian_) {
      $data = strrev($data);
    }
    $this->trans_->write($data);
    return 8;
  }

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

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

  private function unpackI32($data) {
    $value =
      ord($data[3]) |
      (ord($data[2]) << 8) |
      (ord($data[1]) << 16) |
      (ord($data[0]) << 24);
    if ($value > 0x7fffffff) {
      $value = 0 - (($value - 1) ^ 0xffffffff);
    }
    return $value;
  }

  /**
   * Returns the sequence ID of the next message; only valid when called
   * before readMessageBegin()
   */
  public function peekSequenceID() {
    $trans = $this->trans_;
    if (!($trans instanceof IThriftBufferedTransport)) {
      throw new TProtocolException(
        get_class($this->trans_).' does not support peek',
        TProtocolException::BAD_VERSION,
      );
    }
    if ($this->sequenceID !== null) {
      throw new TProtocolException(
        'peekSequenceID can only be called '.'before readMessageBegin',
        TProtocolException::INVALID_DATA,
      );
    }

    $data = $trans->peek(4);
    $sz = $this->unpackI32($data);
    $start = 4;

    if ($sz < 0) {
      $version = $sz & self::VERSION_MASK;
      if ($version != self::VERSION_1) {
        throw new TProtocolException(
          'Bad version identifier: '.$sz,
          TProtocolException::BAD_VERSION,
        );
      }
      // skip name string
      $data = $trans->peek(4, $start);
      $name_len = $this->unpackI32($data);
      $start += 4 + $name_len;
      // peek seqId
      $data = $trans->peek(4, $start);
      $seqid = $this->unpackI32($data);
    } else {
      if ($this->strictRead_) {
        throw new TProtocolException(
          'No version identifier, old protocol client?',
          TProtocolException::BAD_VERSION,
        );
      } else {
        // need to guard the length from mis-configured other type of TCP server
        // for example, if mis-configure sshd, will read 'SSH-' as the length
        // if memory limit is -1, means no limit.
        if ($this->memory_limit > 0 && $sz > $this->memory_limit) {
          throw new TProtocolException(
            'Length overflow: '.$sz,
            TProtocolException::SIZE_LIMIT,
          );
        }
        // Handle pre-versioned input
        $start += $sz;
        // skip type byte
        $start += 1;
        // peek seqId
        $data = $trans->peek(4, $start);
        $seqid = $this->unpackI32($data);
      }
    }
    return $seqid;
  }

  public function readMessageBegin(inout $name, inout $type, inout $seqid) {
    $sz = 0;
    $result = $this->readI32($sz);
    if ($sz < 0) {
      $version = $sz & self::VERSION_MASK;
      if ($version != self::VERSION_1) {
        throw new TProtocolException(
          'Bad version identifier: '.$sz,
          TProtocolException::BAD_VERSION,
        );
      }
      $type = $sz & 0x000000ff;
      $result += $this->readString($name) + $this->readI32($seqid);
    } else {
      if ($this->strictRead_) {
        throw new TProtocolException(
          'No version identifier, old protocol client?',
          TProtocolException::BAD_VERSION,
        );
      } else {
        // need to guard the length from mis-configured other type of TCP server
        // for example, if mis-configure sshd, will read 'SSH-' as the length
        // if memory limit is -1, means no limit.
        if ($this->memory_limit > 0 && $sz > $this->memory_limit) {
          throw new TProtocolException(
            'Length overflow: '.$sz,
            TProtocolException::SIZE_LIMIT,
          );
        }
        // Handle pre-versioned input
        $name = $this->trans_->readAll($sz);
        $result += $sz + $this->readByte($type) + $this->readI32($seqid);
      }
    }
    $this->sequenceID = $seqid;
    return $result;
  }

  public function readMessageEnd() {
    $this->sequenceID = null;
    return 0;
  }

  public function readStructBegin(inout $name) {
    $name = '';
    return 0;
  }

  public function readStructEnd() {
    return 0;
  }

  public function readFieldBegin(
    inout $name,
    inout $fieldType,
    inout $fieldId,
  ) {
    $result = $this->readByte($fieldType);
    if ($fieldType == TType::STOP) {
      $fieldId = 0;
      return $result;
    }
    $result += $this->readI16($fieldId);
    return $result;
  }

  public function readFieldEnd() {
    return 0;
  }

  public function readMapBegin(inout $keyType, inout $valType, inout $size) {
    return
      $this->readByte($keyType) +
      $this->readByte($valType) +
      $this->readI32($size);
  }

  public function readMapEnd() {
    return 0;
  }

  public function readListBegin(&$elemType, &$size) {
    return $this->readByte($elemType) + $this->readI32($size);
  }

  public function readListEnd() {
    return 0;
  }

  public function readSetBegin(&$elemType, &$size) {
    return $this->readByte($elemType) + $this->readI32($size);
  }

  public function readSetEnd() {
    return 0;
  }

  public function readBool(&$value) {
    $data = $this->trans_->readAll(1);
    $arr = unpack('c', $data);
    $value = $arr[1] == 1;
    return 1;
  }

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

  public function readI16(&$value) {
    $data = $this->trans_->readAll(2);
    $value = ord($data[1]) | (ord($data[0]) << 8);
    if ($value > 0x7fff) {
      $value = 0 - (($value - 1) ^ 0xffff);
    }
    return 2;
  }

  public function readI32(&$value) {
    $data = $this->trans_->readAll(4);
    $value = $this->unpackI32($data);
    return 4;
  }

  public function readI64(&$value) {
    $data = $this->trans_->readAll(8);

    $arr = unpack('N2', $data);

    // 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
    if (PHP_INT_SIZE == 4) {

      $hi = $arr[1];
      $lo = $arr[2];
      $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 pe positive - we deal wigh 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 8;
  }

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

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

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


<?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
 */

/**
 * Binary Protocol Factory
 */
class TBinaryProtocolFactory implements TProtocolFactory {
  protected bool $strictRead = false;
  protected bool $strictWrite = true;

  public function __construct(
    bool $strict_read = false,
    bool $strict_write = true,
  ) {
    $this->strictRead = $strict_read;
    $this->strictWrite = $strict_write;
  }

  public function getProtocol(TTransport $trans): TProtocol {
    return new TBinaryProtocolAccelerated(
      $trans,
      $this->strictRead,
      $this->strictWrite,
    );
  }
}


<?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
 */

/**
 * Old slow unaccelerated protocol.
 */
class TBinaryProtocolUnaccelerated extends TBinaryProtocolBase {
  public function __construct(
    $trans,
    $strict_read = false,
    $strict_write = true,
  ) {
    parent::__construct($trans, $strict_read, $strict_write);
  }
}