Aerys\HPack::table_resize PHP Method

table_resize() public method

removal of old entries as per 4.4
public table_resize ( $maxSize = null )
    public function table_resize($maxSize = null)
    {
        if (isset($maxSize)) {
            $this->maxSize = $maxSize;
        }
        while ($this->size > $this->maxSize) {
            list($name, $value) = \array_pop($this->headers);
            $this->size -= 32 + \strlen($name) + \strlen($value);
        }
    }

Usage Example

Example #1
0
 public function parser(Client $client, $settings = "") : \Generator
 {
     $maxHeaderSize = $client->options->maxHeaderSize;
     $maxBodySize = $client->options->maxBodySize;
     $maxStreams = $client->options->maxConcurrentStreams;
     // $bodyEmitSize = $client->options->ioGranularity; // redundant because data frames, which is 16 KB
     assert(!\defined("Aerys\\DEBUG_HTTP2") || (print "INIT\n"));
     $this->writeFrame($client, pack("nNnN", self::INITIAL_WINDOW_SIZE, $maxBodySize + 256, self::MAX_CONCURRENT_STREAMS, $maxStreams), self::SETTINGS, self::NOFLAG);
     $this->writeFrame($client, "þÿÿ", self::WINDOW_UPDATE, self::NOFLAG);
     // effectively disabling global flow control...
     $headers = [];
     $bodyLens = [];
     $table = new HPack();
     $setSetting = function ($buffer) use($client, $table) {
         $unpacked = \unpack("nsetting/Nvalue", $buffer);
         // $unpacked["value"] >= 0
         assert(!defined("Aerys\\DEBUG_HTTP2") || (print "SETTINGS({$unpacked["setting"]}): {$unpacked["value"]}\n"));
         switch ($unpacked["setting"]) {
             case self::MAX_HEADER_LIST_SIZE:
                 if ($unpacked["value"] >= 4096) {
                     return self::PROTOCOL_ERROR;
                     // @TODO correct error??
                 }
                 $table->table_resize($unpacked["value"]);
                 break;
             case self::INITIAL_WINDOW_SIZE:
                 if ($unpacked["value"] >= 1 << 31) {
                     return self::FLOW_CONTROL_ERROR;
                 }
                 $client->initialWindowSize = $unpacked["value"];
                 break;
             case self::ENABLE_PUSH:
                 if ($unpacked["value"] & ~1) {
                     return self::PROTOCOL_ERROR;
                 }
                 $client->allowsPush = (bool) $unpacked["value"];
                 break;
         }
     };
     if ($settings != "") {
         if (\strlen($settings) % 6 != 0) {
             $error = self::FRAME_SIZE_ERROR;
             goto connection_error;
         }
         do {
             if ($error = $setSetting($settings)) {
                 goto connection_error;
             }
             $settings = substr($settings, 6);
         } while ($settings != "");
     }
     $buffer = yield;
     $preface = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n";
     while (\strlen($buffer) < \strlen($preface)) {
         $buffer .= yield;
     }
     if (\strncmp($buffer, $preface, \strlen($preface)) !== 0) {
         $start = \strpos($buffer, "HTTP/") + 5;
         if ($start < \strlen($buffer)) {
             ($this->emit)([HttpDriver::ERROR, ["protocol" => \substr($buffer, $start, \strpos($buffer, "\r\n", $start) - $start)], HttpDriver::BAD_VERSION], $client);
         }
         while (1) {
             yield;
         }
     }
     $buffer = \substr($buffer, \strlen($preface));
     $client->remainingKeepAlives = $maxStreams;
     while (1) {
         while (\strlen($buffer) < 9) {
             $buffer .= yield;
         }
         $length = \unpack("N", "{$buffer}")[1];
         // @TODO SETTINGS: MAX_FRAME_SIZE
         $type = $buffer[3];
         $flags = $buffer[4];
         $id = \unpack("N", substr($buffer, 5, 4))[1];
         // the highest bit must be zero... but RFC does not specify what should happen when it is set to 1?
         /*if ($id < 0) {
               $id = ~$id;
           }*/
         assert(!\defined("Aerys\\DEBUG_HTTP2") || (print "Flag: " . bin2hex($flags) . "; Type: " . bin2hex($type) . "; Stream: {$id}; Length: {$length}\n"));
         $buffer = \substr($buffer, 9);
         switch ($type) {
             case self::DATA:
                 if ($id === 0) {
                     $error = self::PROTOCOL_ERROR;
                     goto connection_error;
                 }
                 if (!isset($client->bodyPromisors[$id])) {
                     if (isset($headers[$id])) {
                         $error = self::PROTOCOL_ERROR;
                         goto connection_error;
                     } else {
                         $error = self::STREAM_CLOSED;
                         while (\strlen($buffer) < $length) {
                             $buffer .= yield;
                         }
                         $buffer = \substr($buffer, $length);
                         goto stream_error;
                     }
                 }
                 if (($flags & self::PADDED) !== "") {
                     if ($buffer === "") {
                         $buffer = yield;
                     }
                     $padding = \ord($buffer);
                     $buffer = \substr($buffer, 1);
                     $length--;
                     if ($padding > $length) {
                         $error = self::PROTOCOL_ERROR;
                         goto connection_error;
                     }
                 } else {
                     $padding = 0;
                 }
                 if ($length > 1 << 14) {
                     $error = self::PROTOCOL_ERROR;
                     goto connection_error;
                 }
                 if (($bodyLens[$id] ?? 0) + $length > $maxBodySize) {
                     $error = self::ENHANCE_YOUR_CALM;
                     while ($length) {
                         $buffer = yield;
                         $length -= \strlen($buffer);
                     }
                     $buffer = substr($buffer, \strlen($buffer) + $length);
                     goto stream_error;
                 }
                 while (\strlen($buffer) < $length) {
                     $buffer .= yield;
                 }
                 if (($flags & self::END_STREAM) !== "") {
                     $bodyLens[$id] = $length;
                     $type = HttpDriver::ENTITY_RESULT;
                 } else {
                     unset($bodyLens[$id]);
                     $type = HttpDriver::ENTITY_PART;
                 }
                 $body = \substr($buffer, 0, $length - $padding);
                 assert(!\defined("Aerys\\DEBUG_HTTP2") || (print "DATA({$length}): {$body}\n"));
                 if ($body != "") {
                     ($this->emit)([$type, ["id" => $id, "protocol" => "2.0", "body" => $body], null], $client);
                 }
                 $buffer = \substr($buffer, $length);
                 continue 2;
             case self::HEADERS:
                 if (isset($headers[$id])) {
                     $error = self::PROTOCOL_ERROR;
                     goto connection_error;
                 }
                 if ($client->remainingKeepAlives-- == 0) {
                     $error = self::PROTOCOL_ERROR;
                     goto connection_error;
                 }
                 if (($flags & self::PADDED) !== "") {
                     if ($buffer == "") {
                         $buffer = yield;
                     }
                     $padding = \ord($buffer);
                     $buffer = \substr($buffer, 1);
                     $length--;
                 } else {
                     $padding = 0;
                 }
                 if (($flags & self::PRIORITY_FLAG) !== "") {
                     while (\strlen($buffer) < 5) {
                         $buffer .= yield;
                     }
                     /* Not yet needed?!
                        $dependency = unpack("N", $buffer)[1];
                        if ($dependency < 0) {
                            $dependency = ~$dependency;
                            $exclusive = true;
                        } else {
                            $exclusive = false;
                        }
                        $weight = $buffer[4];
                        */
                     $buffer = \substr($buffer, 5);
                     $length -= 5;
                 }
                 if ($padding >= $length) {
                     $error = self::PROTOCOL_ERROR;
                     goto connection_error;
                 }
                 if ($length > $maxHeaderSize) {
                     $error = self::ENHANCE_YOUR_CALM;
                     while ($length) {
                         $buffer = yield;
                         $length -= \strlen($buffer);
                     }
                     $buffer = substr($buffer, \strlen($buffer) + $length);
                     goto stream_error;
                 }
                 while (\strlen($buffer) < $length) {
                     $buffer .= yield;
                 }
                 $packed = \substr($buffer, 0, $length - $padding);
                 $buffer = \substr($buffer, $length);
                 $streamEnd = ($flags & self::END_STREAM) !== "";
                 if (($flags & self::END_HEADERS) != "") {
                     goto parse_headers;
                 } else {
                     $headers[$id] = $packed;
                 }
                 continue 2;
             case self::PRIORITY:
                 if ($length != 5) {
                     $error = self::FRAME_SIZE_ERROR;
                     while (\strlen($buffer) < $length) {
                         $buffer .= yield;
                     }
                     $buffer = substr($buffer, $length);
                     goto stream_error;
                 }
                 if ($id === 0) {
                     $error = self::PROTOCOL_ERROR;
                     goto connection_error;
                 }
                 while (\strlen($buffer) < 5) {
                     $buffer .= yield;
                 }
                 /* @TODO PRIORITY frames not yet handled?!
                    $dependency = unpack("N", $buffer);
                    if ($dependency < 0) {
                        $dependency = ~$dependency;
                        $exclusive = true;
                    } else {
                        $exclusive = false;
                    }
                    if ($dependency == 0) {
                        $error = self::PROTOCOL_ERROR;
                        goto connection_error;
                    }
                    $weight = $buffer[4];
                    */
                 $buffer = \substr($buffer, 5);
                 assert(!defined("Aerys\\DEBUG_HTTP2") || (print "PRIORITY: - \n"));
                 continue 2;
             case self::RST_STREAM:
                 if ($length != 4) {
                     $error = self::FRAME_SIZE_ERROR;
                     goto connection_error;
                 }
                 if ($id === 0) {
                     $error = self::PROTOCOL_ERROR;
                     goto connection_error;
                 }
                 while (\strlen($buffer) < 4) {
                     $buffer .= yield;
                 }
                 $error = \unpack("N", $buffer)[1];
                 assert(!defined("Aerys\\DEBUG_HTTP2") || (print "RST_STREAM: {$error}\n"));
                 if (isset($client->bodyPromisors[$id])) {
                     $client->bodyPromisors[$id]->fail(new ClientException());
                     unset($client->bodyPromisors[$id]);
                 }
                 unset($headers[$id], $bodyLens[$id], $client->streamWindow[$id], $client->streamEnd[$id], $client->streamWindowBuffer[$id]);
                 $buffer = \substr($buffer, 4);
                 continue 2;
             case self::SETTINGS:
                 if ($id !== 0) {
                     $error = self::PROTOCOL_ERROR;
                     goto connection_error;
                 }
                 if (($flags & self::ACK) !== "") {
                     if ($length) {
                         $error = self::PROTOCOL_ERROR;
                         goto connection_error;
                     }
                     assert(!defined("Aerys\\DEBUG_HTTP2") || (print "SETTINGS: ACK\n"));
                     // got ACK
                     continue 2;
                 } elseif ($length % 6 != 0) {
                     $error = self::FRAME_SIZE_ERROR;
                     goto connection_error;
                 }
                 while ($length > 0) {
                     while (\strlen($buffer) < 6) {
                         $buffer .= yield;
                     }
                     if ($error = $setSetting($buffer)) {
                         goto connection_error;
                     }
                     $buffer = \substr($buffer, 6);
                     $length -= 6;
                 }
                 $this->writeFrame($client, "", self::SETTINGS, self::ACK);
                 continue 2;
                 // PUSH_PROMISE sent by client is a PROTOCOL_ERROR (just like undefined frame types)
             // PUSH_PROMISE sent by client is a PROTOCOL_ERROR (just like undefined frame types)
             case self::PING:
                 if ($length != 8) {
                     $error = self::FRAME_SIZE_ERROR;
                     goto connection_error;
                 }
                 if ($id !== 0) {
                     $error = self::PROTOCOL_ERROR;
                     goto connection_error;
                 }
                 while (\strlen($buffer) < 8) {
                     $buffer .= yield;
                 }
                 $data = \substr($buffer, 0, 8);
                 if (($flags & self::ACK) !== "") {
                     // do not resolve ping - unneeded because of keepAliveTimeout
                 } else {
                     $this->writeFrame($client, $data, self::PING, self::ACK);
                 }
                 $buffer = \substr($buffer, 8);
                 continue 2;
             case self::GOAWAY:
                 if ($id !== 0) {
                     $error = self::PROTOCOL_ERROR;
                     goto connection_error;
                 }
                 $lastId = \unpack("N", $buffer)[1];
                 // the highest bit must be zero... but RFC does not specify what should happen when it is set to 1?
                 if ($lastId < 0) {
                     $lastId = ~$lastId;
                 }
                 $error = \unpack("N", substr($buffer, 4, 4))[1];
                 $buffer = \substr($buffer, 8);
                 $length -= 8;
                 while (\strlen($buffer) < $length) {
                     $buffer .= yield;
                 }
                 if ($error !== 0) {
                     // ($this->emit)([HttpDriver::ERROR, ["body" => substr($buffer, 0, $length)], $error], $client);
                 }
                 assert(!defined("Aerys\\DEBUG_HTTP2") || (print "GOAWAY({$error}): " . substr($buffer, 0, $length) . "\n"));
                 $client->shouldClose = true;
                 while (1) {
                     yield;
                 }
                 // keepAliveTimeout will force a close when necessary
             // keepAliveTimeout will force a close when necessary
             case self::WINDOW_UPDATE:
                 while (\strlen($buffer) < 4) {
                     $buffer .= yield;
                 }
                 if ($buffer === "") {
                     $error = self::PROTOCOL_ERROR;
                     if ($id) {
                         goto stream_error;
                     } else {
                         goto connection_error;
                     }
                 }
                 $windowSize = \unpack("N", $buffer)[1];
                 if ($id) {
                     if (!isset($client->streamWindow[$id])) {
                         $client->streamWindow[$id] = $client->initialWindowSize + $windowSize;
                     } else {
                         $client->streamWindow[$id] += $windowSize;
                     }
                     if (isset($client->streamWindowBuffer[$id])) {
                         $this->tryDataSend($client, $id);
                     }
                 } else {
                     $client->window += $windowSize;
                     foreach ($client->streamWindowBuffer as $stream => $data) {
                         $this->tryDataSend($client, $stream);
                         if ($client->window == 0) {
                             break;
                         }
                     }
                 }
                 $buffer = \substr($buffer, 4);
                 continue 2;
             case self::CONTINUATION:
                 if (!isset($headers[$id])) {
                     $error = self::PROTOCOL_ERROR;
                     goto connection_error;
                 }
                 while (\strlen($buffer) < $length) {
                     $buffer .= yield;
                 }
                 $headers[$id] .= \substr($buffer, 0, $length);
                 $buffer = \substr($buffer, $length);
                 if (($flags & self::END_HEADERS) !== "") {
                     $packed = $headers[$id];
                     unset($headers[$id]);
                     goto parse_headers;
                 }
                 continue 2;
             default:
                 assert(!defined("Aerys\\DEBUG_HTTP2") || (print "BAD TYPE: " . ord($type) . "\n"));
                 $error = self::PROTOCOL_ERROR;
                 goto connection_error;
         }
         parse_headers:
         $headerList = $table->decode($packed);
         if ($headerList === null) {
             $error = self::COMPRESSION_ERROR;
             break;
         }
         assert(!defined("Aerys\\DEBUG_HTTP2") || (print "HEADER(" . (\strlen($packed) - $padding) . "): " . implode(" | ", array_map(function ($x) {
             return implode(": ", $x);
         }, $headerList)) . "\n"));
         $headerArray = [];
         foreach ($headerList as list($name, $value)) {
             $headerArray[$name][] = $value;
         }
         $parseResult = ["id" => $id, "trace" => $headerList, "protocol" => "2.0", "method" => $headerArray[":method"][0], "uri" => !empty($headerArray[":authority"][0]) ? "{$headerArray[":scheme"][0]}://{$headerArray[":authority"][0]}{$headerArray[":path"][0]}" : $headerArray[":path"][0], "headers" => $headerArray, "body" => ""];
         if (!isset($client->streamWindow[$id])) {
             $client->streamWindow[$id] = $client->initialWindowSize;
         }
         $client->streamWindowBuffer[$id] = "";
         if ($streamEnd) {
             ($this->emit)([HttpDriver::RESULT, $parseResult, null], $client);
         } else {
             ($this->emit)([HttpDriver::ENTITY_HEADERS, $parseResult, null], $client);
         }
         continue;
         stream_error:
         $this->writeFrame($client, pack("N", $error), self::RST_STREAM, self::NOFLAG, $id);
         assert(!defined("Aerys\\DEBUG_HTTP2") || (print "Stream ERROR: {$error}\n"));
         unset($headers[$id], $bodyLens[$id]);
         $client->remainingKeepAlives++;
         continue;
     }
     connection_error:
     $client->shouldClose = true;
     $this->writeFrame($client, pack("NN", 0, $error), self::GOAWAY, self::NOFLAG);
     assert(!defined("Aerys\\DEBUG_HTTP2") || (print "Connection ERROR: {$error}\n"));
     while (1) {
         yield;
     }
 }