Skip to content

Instantly share code, notes, and snippets.

@masakielastic
Last active July 30, 2024 15:02
Show Gist options
  • Save masakielastic/9777eaf02907b18585974ba00764aab6 to your computer and use it in GitHub Desktop.
Save masakielastic/9777eaf02907b18585974ba00764aab6 to your computer and use it in GitHub Desktop.
<?php
class FrameHeader {
private $bytes;
public function __construct(string $bytes)
{
$this->bytes = $bytes;
}
public static function from(int $length, int $type, int $streamId, array $flags = [])
{
$bytes = static::lengthString($length).chr($type).
static::flagString($flags).static::idString($streamId);
return new static($bytes);
}
private static function lengthString(int $length): string
{
return match(true) {
0x100 > $length => "\x00\x00",
0x10000 > $length => "\x00",
default => ""
}.chr($length);
}
private static function flagString(array $flags): string
{
$flag = 0x0;
if (count($flags)) {
foreach ($flags as $value) {
$flag += $value;
}
}
return pack("c", $flag);
}
private static function idString(int $id): string
{
return match(true) {
0x100 > $id => "\x00\x00\x00",
0x10000 > $id => "\x00\x00",
0x1000000 > $id => "\x00",
default => ""
}.chr($id);
}
public function getBytes(): string
{
return $this->bytes;
}
public function getLength()
{
return hexdec(bin2hex(substr($this->bytes, 0, 3)));
}
public function getType()
{
return ord(substr($this->bytes, 3, 1));
}
public function getFlag()
{
return ord(substr($this->bytes, 4, 1));
}
}
class Frame {
private $bytes;
public function __construct(string $bytes)
{
$this->bytes = $bytes;
}
public function getBytes(): string
{
return $this->bytes;
}
}
class SettingsFrame extends Frame {
public static function from()
{
$bytes = FrameHeader::from(0x0, 0x4, 0x0)->getBytes();
return new static($bytes);
}
}
class Ack extends Frame {
public static function from()
{
$bytes = FrameHeader::from(0x0, 0x4, 0x0, [0x01])->getBytes();
return new static($bytes);
}
}
class HeadersFrame extends Frame {
public static function from(array $headers, array $flags = [], int $streamId)
{
$payload = '';
foreach ($headers as $header) {
$name = pack("c", strlen($header[0])).$header[0];
$value = pack("c", strlen($header[1])).$header[1];
$payload .= chr(0x0).$name.$value;
}
$header = FrameHeader::from(strlen($payload), 0x1, $streamId, $flags)->getBytes();
return new static($header.$payload);
}
}
class DataFrame extends Frame {
public static function from(string $data, int $streamId, array $flags = [])
{
$header = FrameHeader::from(strlen($data), 0x0, $streamId, $flags)->getBytes();
return new static($header.$data);
}
}
<?php
class FrameParser implements \IteratorAggregate {
public function __construct(private string $bytes)
{
}
public function getIterator(): \Generator
{
$bytesSize = strlen($this->bytes);
$index = 0;
$current = 0;
$next = 0;
while (true) {
if ($next >= $bytesSize) {
break;
}
$size = hexdec(bin2hex(substr($this->bytes, $next, 3))) + 9;
$next += $size;
yield $index => substr($this->bytes, $current, $size);
$current = $next;
++$index;
}
}
}
<?php
require 'Frame.php';
require 'FrameParser.php';
function request($key = null): string
{
$headers = [
[':method', 'GET'],
[':path', '/'],
[':scheme', 'http'],
[':authority', 'localhost']
];
$ret = [
'settings' => SettingsFrame::from()->getBytes(),
'ack' => Ack::from()->getBytes(),
'headers' => HeadersFrame::from($headers, 0x1, [0x1, 0x4])->getBytes()
];
return empty($key) ? implode($ret) : $ret[$key];
}
$bytes = request();
$iter = new FrameParser($bytes);
$ret = [];
foreach ($iter as $index => $value) {
$ret[] = $value;
}
var_dump(
request('settings') === $ret[0],
request('ack') === $ret[1],
request('headers') === $ret[2]
);
<?php
require 'Frame.php';
$settings = SettingsFrame::from()->getBytes();
$ack = Ack::from()->getBytes();
$headers = [
[':method', 'GET'],
[':path', '/'],
[':scheme', 'http'],
[':authority', 'localhost']
];
$header = HeadersFrame::from($headers, 0x1, [0x1, 0x4])->getBytes();
$data = DataFrame::from("Hello\n", 0x1, [0x1])->getBytes();
$ret = "\x00\x00\x3a".
"\x01".
"\x05".
"\x00\x00\x00\x01".
"\x00".
"\x07:method". // 7 :method
"\x03\x47\x45\x54". // 3 GET
"\x00".
"\x05\x3a\x70\x61\x74\x68". // 5 :path
"\x01\x2f". // 1 /
"\x00".
"\x07\x3a\x73\x63\x68\x65\x6d\x65". // 7 :scheme
"\x04http". // 4 http
"\x00".
"\x0a\x3a\x61\x75\x74\x68\x6f\x72\x69\x74\x79". // 10 :authority
"\x09localhost"; // 9 localhost
var_dump(
"\x00\x00\x00\x04\x00\x00\x00\x00\x00" === $settings,
"\x00\x00\x00\x04\x01\x00\x00\x00\x00" === $ack,
$ret === $header,
"\x00\x00\x06".
"\x00".
"\x01".
"\x00\x00\x00\x01".
"\x48\x65\x6c\x6c\x6f\x0a" === $data
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment