// The self-built HTTP/2 frame layer (RFC 7540 §4, §6). Byte-level, pure, and
// exhaustively round-trippable: every frame type encodes to and decodes from the
// exact wire bytes, so the transport can be layered on top without touching the
// codec. No sockets here — that is the multiplexer's job, sequenced after.
///|
/// HTTP/2 frame type codes (RFC 7540 §6).
pub let frame_data : Int = 0x0
///|
pub let frame_headers : Int = 0x1
///|
pub let frame_priority : Int = 0x2
///|
pub let frame_rst_stream : Int = 0x3
///|
pub let frame_settings : Int = 0x4
///|
pub let frame_push_promise : Int = 0x5
///|
pub let frame_ping : Int = 0x6
///|
pub let frame_goaway : Int = 0x7
///|
pub let frame_window_update : Int = 0x8
///|
pub let frame_continuation : Int = 0x9
///|
/// HTTP/2 frame flags (RFC 7540 §6). Flags are type-specific; the same bit
/// carries different meaning per frame type, hence the shared numeric values.
pub let flag_end_stream : Int = 0x1
///|
/// `ACK` on SETTINGS and PING shares bit 0x1 with `END_STREAM`.
pub let flag_ack : Int = 0x1
///|
pub let flag_end_headers : Int = 0x4
///|
pub let flag_padded : Int = 0x8
///|
pub let flag_priority : Int = 0x20
///|
/// SETTINGS parameter identifiers (RFC 7540 §6.5.2).
pub let settings_header_table_size : Int = 0x1
///|
pub let settings_enable_push : Int = 0x2
///|
pub let settings_max_concurrent_streams : Int = 0x3
///|
pub let settings_initial_window_size : Int = 0x4
///|
pub let settings_max_frame_size : Int = 0x5
///|
pub let settings_max_header_list_size : Int = 0x6
///|
/// HTTP/2 error codes (RFC 7540 §7), carried by RST_STREAM and GOAWAY.
pub let error_no_error : Int = 0x0
///|
pub let error_protocol_error : Int = 0x1
///|
pub let error_internal_error : Int = 0x2
///|
pub let error_flow_control_error : Int = 0x3
///|
pub let error_settings_timeout : Int = 0x4
///|
pub let error_stream_closed : Int = 0x5
///|
pub let error_frame_size_error : Int = 0x6
///|
pub let error_refused_stream : Int = 0x7
///|
pub let error_cancel : Int = 0x8
///|
pub let error_compression_error : Int = 0x9
///|
pub let error_connect_error : Int = 0xA
///|
pub let error_enhance_your_calm : Int = 0xB
///|
pub let error_inadequate_security : Int = 0xC
///|
pub let error_http_1_1_required : Int = 0xD
///|
/// The stream-priority block shared by PRIORITY frames and the optional priority
/// section of HEADERS (RFC 7540 §5.3.2). `weight` is the raw wire byte (`0..255`);
/// the effective priority weight is `weight + 1` (§6.3).
pub(all) struct Priority {
exclusive : Bool
stream_dependency : Int
weight : Int
} derive(Eq)
///|
/// A raised decode failure. `Incomplete` is the normal "need more bytes" signal a
/// streaming reader catches to wait for the rest; the others are protocol errors.
pub suberror FrameError {
Incomplete
FrameSizeError(String)
ProtocolError(String)
} derive(Eq)
///|
pub impl Show for FrameError with fn output(self, logger) {
match self {
Incomplete => logger.write_string("Incomplete")
FrameSizeError(m) => logger.write_string("FrameSizeError(" + m + ")")
ProtocolError(m) => logger.write_string("ProtocolError(" + m + ")")
}
}
///|
/// A decoded HTTP/2 frame (RFC 7540 §6). Each variant carries the semantic
/// payload with padding already stripped; `padding` is the number of padding
/// bytes to (re)emit. `Unknown` preserves extension/unrecognised frames verbatim
/// so a reader can forward or ignore them (RFC 7540 §4.1).
pub(all) enum Frame {
Data(stream_id~ : Int, data~ : Bytes, end_stream~ : Bool, padding~ : Int)
Headers(
stream_id~ : Int,
fragment~ : Bytes,
end_stream~ : Bool,
end_headers~ : Bool,
priority~ : Priority?,
padding~ : Int
)
Priority(stream_id~ : Int, priority~ : Priority)
RstStream(stream_id~ : Int, error_code~ : Int)
Settings(params~ : Array[(Int, Int)], ack~ : Bool)
PushPromise(
stream_id~ : Int,
promised_id~ : Int,
fragment~ : Bytes,
end_headers~ : Bool,
padding~ : Int
)
Ping(payload~ : Bytes, ack~ : Bool)
GoAway(last_stream_id~ : Int, error_code~ : Int, debug~ : Bytes)
WindowUpdate(stream_id~ : Int, increment~ : Int)
Continuation(stream_id~ : Int, fragment~ : Bytes, end_headers~ : Bool)
Unknown(ftype~ : Int, flags~ : Int, stream_id~ : Int, payload~ : Bytes)
} derive(Eq)
// -- big-endian primitives --------------------------------------------------
///|
fn be_write_u16(buf : Buffer, n : Int) -> Unit {
buf.write_byte((n >> 8).to_byte())
buf.write_byte(n.to_byte())
}
///|
fn be_write_u24(buf : Buffer, n : Int) -> Unit {
buf.write_byte((n >> 16).to_byte())
buf.write_byte((n >> 8).to_byte())
buf.write_byte(n.to_byte())
}
///|
fn be_write_u32(buf : Buffer, n : Int) -> Unit {
buf.write_byte((n >> 24).to_byte())
buf.write_byte((n >> 16).to_byte())
buf.write_byte((n >> 8).to_byte())
buf.write_byte(n.to_byte())
}
///|
fn be_read_u16(data : Bytes, off : Int) -> Int {
(data[off].to_int() << 8) | data[off + 1].to_int()
}
///|
fn be_read_u24(data : Bytes, off : Int) -> Int {
(data[off].to_int() << 16) |
(data[off + 1].to_int() << 8) |
data[off + 2].to_int()
}
///|
fn be_read_u32(data : Bytes, off : Int) -> Int {
(data[off].to_int() << 24) |
(data[off + 1].to_int() << 16) |
(data[off + 2].to_int() << 8) |
data[off + 3].to_int()
}
///|
/// Write a 31-bit stream identifier with the reserved top bit optionally set (the
/// `E` exclusive bit in a priority block; always clear elsewhere per §4.1).
fn be_write_stream_id(buf : Buffer, id : Int, top_bit : Bool) -> Unit {
let hi = (id >> 24) & 0x7F
buf.write_byte((if top_bit { hi | 0x80 } else { hi }).to_byte())
buf.write_byte((id >> 16).to_byte())
buf.write_byte((id >> 8).to_byte())
buf.write_byte(id.to_byte())
}
///|
fn be_write_priority(buf : Buffer, p : Priority) -> Unit {
be_write_stream_id(buf, p.stream_dependency, p.exclusive)
buf.write_byte(p.weight.to_byte())
}
///|
fn parse_priority(payload : Bytes, off : Int) -> Priority {
let raw = be_read_u32(payload, off)
{
exclusive: (payload[off].to_int() & 0x80) != 0,
stream_dependency: raw & 0x7FFFFFFF,
weight: payload[off + 4].to_int(),
}
}
// -- frame header -----------------------------------------------------------
///|
/// The fixed 9-octet frame header (RFC 7540 §4.1): a 24-bit payload length, an
/// 8-bit type, 8-bit flags, a reserved bit, and a 31-bit stream identifier.
pub(all) struct FrameHeader {
length : Int
ftype : Int
flags : Int
stream_id : Int
} derive(Eq)
///|
/// Encode a `FrameHeader` to its 9 wire octets.
pub fn FrameHeader::encode(self : FrameHeader) -> Bytes {
let buf = Buffer()
be_write_u24(buf, self.length)
buf.write_byte(self.ftype.to_byte())
buf.write_byte(self.flags.to_byte())
be_write_stream_id(buf, self.stream_id, false)
buf.to_bytes()
}
///|
/// Decode the 9-octet frame header at `offset`. Raises `Incomplete` when fewer
/// than 9 octets are available. The reserved bit is masked off the stream id.
pub fn decode_frame_header(
data : Bytes,
offset? : Int = 0,
) -> FrameHeader raise FrameError {
if offset + 9 > data.length() {
raise Incomplete
}
{
length: be_read_u24(data, offset),
ftype: data[offset + 3].to_int(),
flags: data[offset + 4].to_int(),
stream_id: be_read_u32(data, offset + 5) & 0x7FFFFFFF,
}
}
// -- frame encoding ---------------------------------------------------------
///|
/// The numeric frame-type code of this frame (RFC 7540 §6).
pub fn Frame::frame_type(self : Frame) -> Int {
match self {
Data(..) => frame_data
Headers(..) => frame_headers
Priority(..) => frame_priority
RstStream(..) => frame_rst_stream
Settings(..) => frame_settings
PushPromise(..) => frame_push_promise
Ping(..) => frame_ping
GoAway(..) => frame_goaway
WindowUpdate(..) => frame_window_update
Continuation(..) => frame_continuation
Unknown(ftype~, ..) => ftype
}
}
///|
/// Assemble a full frame from its header fields and an already-built payload.
fn emit(ftype : Int, flags : Int, stream_id : Int, payload : Bytes) -> Bytes {
let buf = Buffer()
be_write_u24(buf, payload.length())
buf.write_byte(ftype.to_byte())
buf.write_byte(flags.to_byte())
be_write_stream_id(buf, stream_id, false)
buf.write_bytes(payload)
buf.to_bytes()
}
///|
/// Write `n` zero padding octets.
fn write_padding(buf : Buffer, n : Int) -> Unit {
for i = 0; i < n; i = i + 1 {
buf.write_byte(b'\x00')
}
}
///|
/// Encode this frame to its complete wire representation (9-octet header +
/// payload), the exact inverse of `decode_frame`.
pub fn Frame::encode(self : Frame) -> Bytes {
match self {
Data(stream_id~, data~, end_stream~, padding~) => {
let mut flags = 0
if end_stream {
flags = flags | flag_end_stream
}
let body = Buffer()
if padding > 0 {
flags = flags | flag_padded
body.write_byte(padding.to_byte())
}
body.write_bytes(data)
write_padding(body, padding)
emit(frame_data, flags, stream_id, body.to_bytes())
}
Headers(
stream_id~,
fragment~,
end_stream~,
end_headers~,
priority~,
padding~
) => {
let mut flags = 0
if end_stream {
flags = flags | flag_end_stream
}
if end_headers {
flags = flags | flag_end_headers
}
let body = Buffer()
if padding > 0 {
flags = flags | flag_padded
body.write_byte(padding.to_byte())
}
match priority {
Some(p) => {
flags = flags | flag_priority
be_write_priority(body, p)
}
None => ()
}
body.write_bytes(fragment)
write_padding(body, padding)
emit(frame_headers, flags, stream_id, body.to_bytes())
}
Priority(stream_id~, priority~) => {
let body = Buffer()
be_write_priority(body, priority)
emit(frame_priority, 0, stream_id, body.to_bytes())
}
RstStream(stream_id~, error_code~) => {
let body = Buffer()
be_write_u32(body, error_code)
emit(frame_rst_stream, 0, stream_id, body.to_bytes())
}
Settings(params~, ack~) => {
let body = Buffer()
if !ack {
for pair in params {
be_write_u16(body, pair.0)
be_write_u32(body, pair.1)
}
}
emit(frame_settings, if ack { flag_ack } else { 0 }, 0, body.to_bytes())
}
PushPromise(stream_id~, promised_id~, fragment~, end_headers~, padding~) => {
let mut flags = 0
if end_headers {
flags = flags | flag_end_headers
}
let body = Buffer()
if padding > 0 {
flags = flags | flag_padded
body.write_byte(padding.to_byte())
}
be_write_stream_id(body, promised_id, false)
body.write_bytes(fragment)
write_padding(body, padding)
emit(frame_push_promise, flags, stream_id, body.to_bytes())
}
Ping(payload~, ack~) =>
emit(frame_ping, if ack { flag_ack } else { 0 }, 0, payload)
GoAway(last_stream_id~, error_code~, debug~) => {
let body = Buffer()
be_write_stream_id(body, last_stream_id, false)
be_write_u32(body, error_code)
body.write_bytes(debug)
emit(frame_goaway, 0, 0, body.to_bytes())
}
WindowUpdate(stream_id~, increment~) => {
let body = Buffer()
be_write_stream_id(body, increment, false)
emit(frame_window_update, 0, stream_id, body.to_bytes())
}
Continuation(stream_id~, fragment~, end_headers~) =>
emit(
frame_continuation,
if end_headers {
flag_end_headers
} else {
0
},
stream_id,
fragment,
)
Unknown(ftype~, flags~, stream_id~, payload~) =>
emit(ftype, flags, stream_id, payload)
}
}
// -- frame decoding ---------------------------------------------------------
///|
/// Strip the padding of a PADDED frame body, returning `(content, pad_len)` where
/// `content` excludes the leading pad-length octet and the trailing pad octets.
fn strip_padding(payload : Bytes) -> (Bytes, Int) raise FrameError {
if payload.length() < 1 {
raise ProtocolError("padded frame missing pad-length octet")
}
let pad_len = payload[0].to_int()
if 1 + pad_len > payload.length() {
raise ProtocolError("pad length exceeds payload")
}
(payload[1:payload.length() - pad_len].to_owned(), pad_len)
}
///|
/// Decode exactly one frame at `offset`, returning `(frame, bytes_consumed)`
/// where `bytes_consumed` is `9 + payload_length`. Raises `Incomplete` when the
/// buffer does not yet hold the whole frame, or a protocol error when the payload
/// is malformed for its type. The inverse of `Frame::encode`.
pub fn decode_frame(
data : Bytes,
offset? : Int = 0,
) -> (Frame, Int) raise FrameError {
let hdr = decode_frame_header(data, offset~)
let start = offset + 9
if start + hdr.length > data.length() {
raise Incomplete
}
let payload = data[start:start + hdr.length].to_owned()
let padded = (hdr.flags & flag_padded) != 0
let frame = if hdr.ftype == frame_data {
let (body, pad) = if padded { strip_padding(payload) } else { (payload, 0) }
Frame::Data(
stream_id=hdr.stream_id,
data=body,
end_stream=(hdr.flags & flag_end_stream) != 0,
padding=pad,
)
} else if hdr.ftype == frame_headers {
let (region, pad) = if padded {
strip_padding(payload)
} else {
(payload, 0)
}
let has_priority = (hdr.flags & flag_priority) != 0
let priority = if has_priority {
if region.length() < 5 {
raise ProtocolError("HEADERS priority block truncated")
}
Some(parse_priority(region, 0))
} else {
None
}
let frag_start = if has_priority { 5 } else { 0 }
Frame::Headers(
stream_id=hdr.stream_id,
fragment=region[frag_start:].to_owned(),
end_stream=(hdr.flags & flag_end_stream) != 0,
end_headers=(hdr.flags & flag_end_headers) != 0,
priority~,
padding=pad,
)
} else if hdr.ftype == frame_priority {
if payload.length() != 5 {
raise FrameSizeError("PRIORITY frame must be 5 octets")
}
Priority(stream_id=hdr.stream_id, priority=parse_priority(payload, 0))
} else if hdr.ftype == frame_rst_stream {
if payload.length() != 4 {
raise FrameSizeError("RST_STREAM frame must be 4 octets")
}
Frame::RstStream(
stream_id=hdr.stream_id,
error_code=be_read_u32(payload, 0),
)
} else if hdr.ftype == frame_settings {
let ack = (hdr.flags & flag_ack) != 0
if ack && payload.length() != 0 {
raise FrameSizeError("SETTINGS ACK must carry no payload")
}
if payload.length() % 6 != 0 {
raise FrameSizeError("SETTINGS payload must be a multiple of 6 octets")
}
let params = []
for i = 0; i < payload.length(); i = i + 6 {
params.push((be_read_u16(payload, i), be_read_u32(payload, i + 2)))
}
Settings(params~, ack~)
} else if hdr.ftype == frame_push_promise {
let (region, pad) = if padded {
strip_padding(payload)
} else {
(payload, 0)
}
if region.length() < 4 {
raise ProtocolError("PUSH_PROMISE missing promised stream id")
}
PushPromise(
stream_id=hdr.stream_id,
promised_id=be_read_u32(region, 0) & 0x7FFFFFFF,
fragment=region[4:].to_owned(),
end_headers=(hdr.flags & flag_end_headers) != 0,
padding=pad,
)
} else if hdr.ftype == frame_ping {
if payload.length() != 8 {
raise FrameSizeError("PING frame must be 8 octets")
}
Ping(payload~, ack=(hdr.flags & flag_ack) != 0)
} else if hdr.ftype == frame_goaway {
if payload.length() < 8 {
raise FrameSizeError("GOAWAY frame must be at least 8 octets")
}
GoAway(
last_stream_id=be_read_u32(payload, 0) & 0x7FFFFFFF,
error_code=be_read_u32(payload, 4),
debug=payload[8:].to_owned(),
)
} else if hdr.ftype == frame_window_update {
if payload.length() != 4 {
raise FrameSizeError("WINDOW_UPDATE frame must be 4 octets")
}
WindowUpdate(
stream_id=hdr.stream_id,
increment=be_read_u32(payload, 0) & 0x7FFFFFFF,
)
} else if hdr.ftype == frame_continuation {
Continuation(
stream_id=hdr.stream_id,
fragment=payload,
end_headers=(hdr.flags & flag_end_headers) != 0,
)
} else {
Unknown(ftype=hdr.ftype, flags=hdr.flags, stream_id=hdr.stream_id, payload~)
}
(frame, 9 + hdr.length)
}