Skip to content

Commit

Permalink
Start writing tests
Browse files Browse the repository at this point in the history
  • Loading branch information
bwoebi committed Feb 5, 2024
1 parent 1182f56 commit d5da228
Show file tree
Hide file tree
Showing 7 changed files with 204 additions and 35 deletions.
2 changes: 2 additions & 0 deletions psalm.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,7 @@
<file name="src/Driver/Http2Driver.php"/>
</errorLevel>
</PropertyNotSetInConstructor>

<RiskyTruthyFalsyComparison errorLevel="suppress" />
</issueHandlers>
</psalm>
4 changes: 3 additions & 1 deletion src/Driver/Http3Driver.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ class Http3Driver extends ConnectionHttpDriver
private DeferredCancellation $closeCancellation;
/** @var array<int, int> */
private array $settings = [];
/** @var DeferredFuture<array<int, int>> */
private DeferredFuture $parsedSettings;
/** @var array<int, \Closure(string $buf, QuicSocket $stream): void */
private array $streamHandlers;
Expand All @@ -75,7 +76,8 @@ public function __construct(
$this->parsedSettings = new DeferredFuture;
}

public function getSettings(): \SplObjectStorage
/** @return array<int, int> */
public function getSettings(): \array
{
return $this->parsedSettings->getFuture()->await();
}
Expand Down
27 changes: 15 additions & 12 deletions src/Driver/Internal/Http3/Http3Parser.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public static function decodeVarint(string $string, int &$off): int
}
return ($int << 24) + (\ord($string[$off++]) << 16) + (\ord($string[$off++]) << 8) + \ord($string[$off++]);
default:
if (\strlen($string) < --$off + 7) {
if (\strlen($string) < $off-- + 7) {
return -1;
}
$int = \unpack("J", $string, $off)[1] & 0x3FFFFFFFFFFFFFFF;
Expand Down Expand Up @@ -136,18 +136,17 @@ public static function readFrameWithoutType(QuicSocket $stream, string &$buf, in
return \substr($buf, 0, $length);
}

private function parseSettings(string $contents)
private function parseSettings(string $contents): void
{
$off = 0;
$settings = new \SplObjectStorage;
$settings = [];
while ((-1 !== $key = self::decodeVarint($contents, $off)) && (-1 !== $value = self::decodeVarint($contents, $off))) {
if ($key = Http3Settings::tryFrom($key)) {
$settings[$key] = $value;
}
$settings[$key] = $value;
}
$this->queue->push([Http3Frame::SETTINGS, $settings]);
}

// Function to be used by a client
public function awaitHttpResponse(QuicSocket $stream): \Generator
{
$off = 0;
Expand Down Expand Up @@ -202,8 +201,10 @@ private function readHttpMessage(QuicSocket $stream, string &$buf, int &$off): \
yield Http3Frame::DATA => \substr($buf, $off, $length);
$off += $length;
} else {
yield \substr($buf, $off);
$length -= \strlen($buf);
if (\strlen($buf) > $off) {
yield Http3Frame::DATA => \substr($buf, $off);
}
$length -= \strlen($buf) - $off;
$buf = "";
$off = 0;
while (true) {
Expand All @@ -219,13 +220,14 @@ private function readHttpMessage(QuicSocket $stream, string &$buf, int &$off): \
yield Http3Frame::DATA => $buf;
$length -= \strlen($buf);
} else {
yield Http3Frame::DATA => \substr($buf, $length);
yield Http3Frame::DATA => \substr($buf, 0, $length);
$off = $length;
break;
}
}
}
// no break
break;

case Http3Frame::PUSH_PROMISE:
if (!$headers = self::readFrameWithoutType($stream, $buf, $off, $this->headerSizeLimit)) {
return;
Expand Down Expand Up @@ -279,6 +281,7 @@ private static function processHeaders(array $decoded): ?array
return [$headers, $pseudo];
}

/** @return ConcurrentIterator<list{Http3Frame::HEADERS, QuicSocket, \Generator}|list{Http3Frame::GOAWAY|Http3Frame::MAX_PUSH_ID|Http3Frame::CANCEL_PUSH, int}|list{Http3Frame::PRIORITY_UPDATE_Push|Http3Frame::PRIORITY_UPDATE_Request, int, string}|list{Http3Frame::PUSH_PROMISE, int, callable(): \Generator}|list{int, string, QuicSocket}> */
public function process(): ConcurrentIterator
{
EventLoop::queue(function () {
Expand Down Expand Up @@ -415,15 +418,15 @@ public static function parsePriority(array|string $headers): ?array
return [$urgency, $incremental];
}

public function abort(Http3ConnectionException $exception)
public function abort(Http3ConnectionException $exception): void
{
if (!$this->queue->isComplete()) {
$this->connection->close($exception->getCode(), $exception->getMessage());
$this->queue->error($exception);
}
}

private function datagramReceiver()
private function datagramReceiver(): void
{
$this->datagramReceiveEmpty = new DeferredCancellation;
$cancellation = $this->datagramReceiveEmpty->getCancellation();
Expand Down
44 changes: 28 additions & 16 deletions src/Driver/Internal/Http3/Http3Writer.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,53 +25,65 @@ public static function encodeVarint(int $int): string
if ($int <= 0x3FFFFFFF) {
return \pack("N", 0x80000000 | $int);
}
return \pack("J", 0xC000000000000000 | $int);
return \pack("J", -0x4000000000000000 | $int);
}

private static function sendFrame(QuicSocket $stream, Http3Frame $type, string $payload)
public static function sendFrame(QuicSocket $stream, int $type, string $payload): void
{
$stream->write(self::encodeVarint($type->value) . self::encodeVarint(\strlen($payload)) . $payload);
$stream->write(self::encodeVarint($type) . self::encodeVarint(\strlen($payload)) . $payload);
}

public function sendHeaderFrame(QuicSocket $stream, string $payload)
private static function sendKnownFrame(QuicSocket $stream, Http3Frame $type, string $payload): void
{
self::sendFrame($stream, Http3Frame::HEADERS, $payload);
self::sendFrame($stream, $type->value, $payload);
}

public function sendData(QuicSocket $stream, string $payload)
public function sendHeaderFrame(QuicSocket $stream, string $payload): void
{
self::sendFrame($stream, Http3Frame::DATA, $payload);
self::sendKnownFrame($stream, Http3Frame::HEADERS, $payload);
}

public function sendGoaway(int $highestStreamId)
public function sendData(QuicSocket $stream, string $payload): void
{
self::sendFrame($this->controlStream, Http3Frame::GOAWAY, self::encodeVarint($highestStreamId));
self::sendKnownFrame($stream, Http3Frame::DATA, $payload);
}

private function startControlStream()
public function sendGoaway(int $highestStreamId): void
{
$this->controlStream = $this->connection->openStream();
$this->controlStream->endReceiving(); // unidirectional please
self::sendKnownFrame($this->controlStream, Http3Frame::GOAWAY, self::encodeVarint($highestStreamId));
}

public function initiateUnidirectionalStream(int $streamType): QuicSocket
{
$stream = $this->connection->openStream();
$stream->endReceiving(); // unidirectional please
$stream->write(self::encodeVarint($streamType));
return $stream;
}

private function startControlStream(): void
{
$this->controlStream = $this->initiateUnidirectionalStream(Http3StreamType::Control->value);

$ints = [];
foreach ($this->settings as $setting => $value) {
$ints[] = self::encodeVarint($setting);
$ints[] = self::encodeVarint($value);
}
self::sendFrame($this->controlStream, Http3Frame::SETTINGS, \implode($ints));
self::sendKnownFrame($this->controlStream, Http3Frame::SETTINGS, \implode($ints));
}

public function maxDatagramSize()
public function maxDatagramSize(): int
{
return $this->connection->maxDatagramSize() - 8; // to include the longest quarter stream id varint
}

public function writeDatagram(QuicSocket $stream, string $buf)
public function writeDatagram(QuicSocket $stream, string $buf): void
{
$this->connection->send(self::encodeVarint($stream->getId() >> 2) . $buf);
}

public function close()
public function close(): void
{
$this->connection->close(Http3Error::H3_NO_ERROR->value);
}
Expand Down
7 changes: 4 additions & 3 deletions src/Driver/Internal/Http3/QPack.php
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ class QPack
["x-frame-options", "sameorigin"],
];

/** @return positive-int */
private static function decodeDynamicInteger(string $input, int $maxBits, int &$off): int
{
if (!isset($input[$off])) {
Expand Down Expand Up @@ -143,7 +144,7 @@ private static function decodeDynamicInteger(string $input, int $maxBits, int &$
return $int;
}

public static function decodeDynamicField(string $input, int $startBits, int &$off)
public static function decodeDynamicField(string $input, int $startBits, int &$off): string
{
$startOff = $off;
$length = self::decodeDynamicInteger($input, $startBits, $off);
Expand All @@ -161,7 +162,7 @@ public static function decodeDynamicField(string $input, int $startBits, int &$o
return $string;
}

public function decode(string $input, int &$off) /* : array */
public function decode(string $input, int &$off): array
{
// @TODO implementation is deliberately primitive...: we just enforce dynamic table size 0
$headers = [];
Expand Down Expand Up @@ -234,7 +235,7 @@ private static function encodeDynamicInteger(int $maxStartValue, int $int): stri
return $out . \chr($int >> $i);
}

private static function encodeDynamicField(int $startBits, string $input)
private static function encodeDynamicField(int $startBits, string $input): string
{
return self::encodeDynamicInteger($startBits, \strlen($input)) . $input;
}
Expand Down
12 changes: 9 additions & 3 deletions src/SocketHttpServer.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
use Amp\Quic\QuicServerConfig;
use Amp\Quic\QuicServerSocket;
use Amp\Socket\BindContext;
use Amp\Socket\InternetAddress;
use Amp\Socket\ResourceServerSocketFactory;
use Amp\Socket\ServerSocket;
use Amp\Socket\ServerSocketFactory;
Expand All @@ -43,7 +44,7 @@ final class SocketHttpServer implements HttpServer

private readonly HttpDriverFactory $httpDriverFactory;

/** @var array<string, array{SocketAddress, BindContext|null}> */
/** @var array<string, array{SocketAddress, QuicServerConfig|BindContext|null}> */
private array $addresses = [];

/** @var list<ServerSocket> */
Expand Down Expand Up @@ -283,6 +284,7 @@ public function start(RequestHandler $requestHandler, ErrorHandler $errorHandler
throw new CompositeException($exceptions);
}

/** @var \SplObjectStorage<QuicServerConfig, non-empty-list<InternetAddress>> $quicConnections */
$quicConnections = new \SplObjectStorage;
/** @var ServerSocket[] $tcpServers */
$tcpServers = [];
Expand All @@ -295,8 +297,12 @@ public function start(RequestHandler $requestHandler, ErrorHandler $errorHandler
*/
foreach ($this->addresses as [$address, $bindContext]) {
if ($bindContext instanceof QuicServerConfig) {
$quicConnections[$bindContext] ??= [];
$quicConnections[$bindContext] = \array_merge($quicConnections[$bindContext], [$address]);
\assert($address instanceof InternetAddress);
if (isset($quicConnections[$bindContext])) {
$quicConnections[$bindContext] = \array_merge($quicConnections[$bindContext], [$address]);
} else {
$quicConnections[$bindContext] = [$address];
}
} else {
$tlsContext = $bindContext?->getTlsContext()?->withApplicationLayerProtocols(
$this->httpDriverFactory->getApplicationLayerProtocols(),
Expand Down
Loading

0 comments on commit d5da228

Please sign in to comment.