Skip to content

FrameDecoder / FrameEncoder โ€‹

Applies to: Hub (phlix-hub)

Low-level binary framing for all Hub relay messages. Implements a streaming decoder and static helper encoder using a simple, fixed-format wire protocol.

Wire Format โ€‹

[4-byte sequence][1-byte type][2-byte length][payload]
FieldSizeDescription
sequence4 bytesBig-endian unsigned integer. Monotonically increasing per connection.
type1 byteFrame type identifier (see below).
length2 bytesBig-endian unsigned short. Payload byte length (0โ€“65535).
payloadlength bytesFrame-specific data.

Total overhead per frame: 7 bytes + payload.

Frame Types โ€‹

ByteNameDirectionDescription
0x01HELLOServer โ†’ HubInitial handshake. Payload: {version: string, server_id: string}.
0x02HELLO_ACKHub โ†’ ServerAcceptance. Payload: {relay_token: string, heartbeat_interval: int}.
0x03CLIENT_CONNECTServer โ†” HubA client connection is being routed through the relay. Payload: {client_id: string, server_id: string}.
0x04CLIENT_DISCONNECTServer โ†” HubClient disconnected. Payload: {client_id: string, reason?: string}.
0x05DATAServer โ†” HubRaw tunneled payload forwarded between client and server. Payload: raw bytes (no framing).
0x06HEARTBEATServer โ†” HubKeep-alive ping. No payload (length = 0).
0x07DISCONNECTEDServer โ†’ HubServer is shutting down. Payload: {reason?: string}.
0x08ERRORHub โ†’ ServerProtocol error. Payload: {code: int, message: string}.

FrameDecoder โ€‹

Phlix\Hub\Relay\FrameDecoder

Streams bytes through a ring buffer and emits complete frames incrementally. Call append() as data arrives, then repeatedly call decode() until it returns null.

php
use Phlix\Hub\Relay\FrameDecoder;

$decoder = new FrameDecoder();

$decoder->append($someBytes);  // e.g. from a TCP socket

while (($frame = $decoder->decode()) !== null) {
    // $frame is a Frame object: {sequence: int, type: int, payload: string}
    process($frame);
}

Reset on new connection โ€‹

php
$decoder->reset();

Call reset() when the underlying transport is closed or a new connection begins.

Invalid type handling โ€‹

Per RFC 6455 ยง7.4.1, invalid frame types (any byte not defined in the table above) throw InvalidFrameTypeException with code 1011. The decoder is in an undefined state after this exception โ€” discard it and create a new instance.

FrameEncoder โ€‹

Phlix\Hub\Relay\FrameEncoder

Static factory methods produce correctly-formatted binary frames.

php
use Phlix\Hub\Relay\FrameEncoder;

$seq = 1;

// Client CONNECT frame
$bytes = FrameEncoder::clientConnect($seq, 'client-abc', 'server-xyz');
// โ†’ 7 bytes header + payload

// DATA frame
$bytes = FrameEncoder::data($seq, "\x00\x01\x02\x03");

// HEARTBEAT (no payload)
$bytes = FrameEncoder::heartbeat($seq);

Available factories โ€‹

MethodType bytePayload
FrameEncoder::hello(int $seq, string $version, string $serverId)0x01JSON {version, server_id}
FrameEncoder::helloAck(int $seq, string $relayToken, int $heartbeatInterval)0x02JSON {relay_token, heartbeat_interval}
FrameEncoder::clientConnect(int $seq, string $clientId, string $serverId)0x03JSON {client_id, server_id}
FrameEncoder::clientDisconnect(int $seq, string $clientId, ?string $reason)0x04JSON {client_id, reason}
FrameEncoder::data(int $seq, string $payload)0x05Raw bytes
FrameEncoder::heartbeat(int $seq)0x06Empty
FrameEncoder::disconnected(int $seq, ?string $reason)0x07JSON {reason}
FrameEncoder::error(int $seq, int $code, string $message)0x08JSON {code, message}

All methods accept $seq as the first argument. Sequence numbers are caller-managed โ€” pass a monotonically increasing integer per connection.

Complete example โ€‹

php
use Phlix\Hub\Relay\FrameDecoder;
use Phlix\Hub\Relay\FrameEncoder;

$seq = 0;
$decoder = new FrameDecoder();

// Receive bytes from socket...
$decoder->append($bytes);

// Decode frames
while (($frame = $decoder->decode()) !== null) {
    match ($frame->type) {
        FrameEncoder::TYPE_HELLO => handleHello($frame),
        FrameEncoder::TYPE_DATA => handleData($frame),
        FrameEncoder::TYPE_HEARTBEAT => send(FrameEncoder::heartbeat($seq++)),
        default => send(FrameEncoder::error($seq++, 1011, 'Unknown frame type')),
    };
}

// Send a DATA frame
$out = FrameEncoder::data($seq++, "\x00\x01\x02\x03");

BSD-3-Clause