text: <?php
  
  namespace PHPFastCGI\Test\FastCGIDaemon\Helper\Client;
  
  use PHPFastCGI\FastCGIDaemon\DaemonInterface;
  use PHPUnit\Framework\TestCase;
  
  /**
   * Helper class that wraps a stream resource and provides helper methods for
   * writing and reading FastCGI records.
   */
  class ConnectionWrapper
  {
      /**
       * @var resource
       */
      private $stream;
  
      /**
       * Constructor.
       *
       * @param resource $stream
       */
      public function __construct($stream)
      {
          $this->stream = $stream;
      }
  
      /**
       * Read a request from the connection.
       *
       * @param TestCase $testCase
       * @param string                      $requestId
       *
       * @return string
       */
      public function readResponse(TestCase $testCase, $requestId)
      {
          $response = '';
  
          do {
              $record = $this->readRecord($testCase);
  
              $testCase->assertEquals($requestId, $record['requestId']);
  
              if (DaemonInterface::FCGI_STDOUT === $record['type']) {
                  $response .= $record['contentData'];
              }
          } while (DaemonInterface::FCGI_END_REQUEST !== $record['type']);
  
          return $response;
      }
  
      /**
       * Read a record from the connection.
       *
       * @param TestCase $testCase
       *
       * @return array
       */
      public function readRecord(TestCase $testCase)
      {
          $headerData = fread($this->stream, 8);
          $headerFormat = 'Cversion/Ctype/nrequestId/ncontentLength/CpaddingLength/x';
  
          $testCase->assertEquals(8, strlen($headerData));
  
          $record = unpack($headerFormat, $headerData);
  
          if ($record['contentLength'] > 0) {
              $record['contentData'] = '';
  
              do {
                  $block = fread($this->stream, $record['contentLength'] - strlen($record['contentData']));
  
                  $record['contentData'] .= $block;
              } while (strlen($block) > 0 && strlen($record['contentData']) !== $record['contentLength']);
  
              $testCase->assertEquals($record['contentLength'], strlen($record['contentData']));
          } else {
              $record['contentData'] = '';
          }
  
          if ($record['paddingLength'] > 0) {
              fread($this->stream, $record['paddingLength']);
          }
  
          return $record;
      }
  
      /**
       * Write a request to the stream.
       *
       * @param string $requestId FastCGI request ID
       * @param array  $params    FastCGI environment variables
       * @param string $body      FastCGI stdin resource stream
       */
      public function writeRequest($requestId, array $params, $body)
      {
          $this->writeBeginRequestRecord($requestId, DaemonInterface::FCGI_RESPONDER, 0);
          $this->writeParamsRecord($requestId, $params);
  
          $this->writeParamsRecord($requestId);
  
          $chunks = str_split($body, 65535);
  
          foreach ($chunks as $chunk) {
              $this->writeStdinRecord($requestId, $chunk);
          }
  
          $this->writeStdinRecord($requestId);
      }
  
      /**
       * Write a record to the stream.
       *
       * @param string $type
       * @param string $requestId
       * @param string $content
       * @param int    $paddingLength
       */
      public function writeRecord($type, $requestId, $content = '', $paddingLength = 0)
      {
          $header  = pack('CCnnCx', DaemonInterface::FCGI_VERSION_1, $type, $requestId, strlen($content), $paddingLength);
          $padding = pack('x'.$paddingLength);
  
          fwrite($this->stream, $header);
          fwrite($this->stream, $content);
          fwrite($this->stream, $padding);
      }
  
      /**
       * Write a begin request record.
       *
       * @param int $requestId
       * @param int $role
       * @param int $flags
       */
      public function writeBeginRequestRecord($requestId, $role, $flags)
      {
          $content = pack('nCx5', $role, $flags);
          $this->writeRecord(DaemonInterface::FCGI_BEGIN_REQUEST, $requestId, $content);
      }
  
      /**
       * Write a params record.
       *
       * @param int   $requestId
       * @param array $params
       */
      public function writeParamsRecord($requestId, array $params = null)
      {
          if (null === $params) {
              $this->writeRecord(DaemonInterface::FCGI_PARAMS, $requestId);
  
              return;
          }
  
          $content = '';
  
          $addLength = function ($parameter) use (&$content) {
              $parameterLength = strlen($parameter);
  
              if ($parameterLength > 0x7F) {
                  $content .= pack('N', $parameterLength | 0x80000000);
              } else {
                  $content .= pack('C', $parameterLength);
              }
          };
  
          foreach ($params as $name => $value) {
              $addLength($name);
              $addLength($value);
  
              $content .= $name;
              $content .= $value;
          }
  
          $contentLength = strlen($content);
          $paddingLength = ((int) ceil(((float) $contentLength) / 8.0) * 8) - $contentLength;
  
          $this->writeRecord(DaemonInterface::FCGI_PARAMS, $requestId, $content, $paddingLength);
      }
  
      /**
       * Write an abort request record.
       *
       * @param int $requestId
       */
      public function writeAbortRequestRecord($requestId)
      {
          $this->writeRecord(DaemonInterface::FCGI_ABORT_REQUEST, $requestId);
      }
  
      /**
       * Write a stdin record.
       *
       * @param int    $requestId
       * @param string $content
       */
      public function writeStdinRecord($requestId, $content = '')
      {
          $this->writeRecord(DaemonInterface::FCGI_STDIN, $requestId, $content);
      }
  }
  