///|
pub suberror ProtocolError {
Invalid(String)
Limit(String)
Truncated(String)
} derive(Debug)
///|
pub extend ProtocolError with @debug.Debug::{to_repr}
///|
/// A protocol record; content is immutable and independent of decoder storage.
pub(all) struct Record {
kind : Int32
id : Int32
content : Bytes
} derive(Eq, Debug)
///|
pub extend Record with Eq::{equal, not_equal}
///|
pub extend Record with @debug.Debug::{to_repr}
///|
/// Incremental decoder. A failure poisons the decoder; create a new one.
pub struct Decoder {
priv storage : FixedArray[Byte]
priv mut used : Int32
priv mut expected : Int32
priv mut failed : Bool
}
///|
pub fn Decoder::new() -> Decoder {
{
storage: FixedArray::make(65798, b'\x00'),
used: 0,
expected: 8,
failed: false,
}
}
///|
/// Accept at most 64 KiB per call. Partial records remain buffered.
pub fn Decoder::feed(
self : Decoder,
bytes : Bytes,
) -> Array[Record] raise ProtocolError {
if self.failed {
raise Invalid("decoder already failed")
}
if bytes.length() > 65536 {
self.failed = true
raise Limit("feed chunk exceeds 65536 bytes")
}
let records = []
for byte in bytes {
self.storage[self.used] = byte
self.used += 1
if self.used == 8 {
if self.storage[0] != b'\x01' {
self.failed = true
raise Invalid("unsupported FastCGI version")
}
self.expected = 8 +
self.storage[4].to_int() * 256 +
self.storage[5].to_int() +
self.storage[6].to_int()
}
if self.used == self.expected {
let n = self.storage[4].to_int() * 256 + self.storage[5].to_int()
records.push({
kind: self.storage[1].to_int(),
id: self.storage[2].to_int() * 256 + self.storage[3].to_int(),
content: Bytes::makei(n, i => self.storage[8 + i]),
})
self.used = 0
self.expected = 8
}
}
records
}
///|
/// Call only at transport EOF. EOF cannot terminate a partial record.
pub fn Decoder::finish(self : Decoder) -> Unit raise ProtocolError {
if self.failed {
raise Invalid("decoder already failed")
}
if self.used != 0 {
self.failed = true
raise Truncated("record at EOF")
}
}
///|
/// Encode one record with 8-byte alignment padding.
pub fn encode_record(record : Record) -> Bytes raise ProtocolError {
if record.kind < 0 || record.kind > 255 || record.id < 0 || record.id > 65535 {
raise Invalid("record kind or request id out of range")
}
let n = record.content.length()
if n > 65535 {
raise Limit("record content exceeds 65535 bytes")
}
let padding = (8 - n % 8) % 8
let out = @buf.Buffer()
for
value in [
1,
record.kind,
record.id / 256,
record.id % 256,
n / 256,
n % 256,
padding,
0,
] {
out.write_byte(value.to_byte())
}
out.write_bytes(record.content)
for _ in 0.. Array[Record] {
let out = []
let mut offset = 0
while offset < content.length() {
let n = Int32::min(65535, content.length() - offset)
out.push({ kind, id, content: content[offset:offset + n].to_owned(), })
offset += n
}
out.push({ kind, id, content: b"", })
out
}
///|
pub fn end_record(
id : Int32,
app_status~ : UInt32 = 0U,
protocol_status~ : Int32 = 0,
) -> Record {
let out = @buf.Buffer()
out.write_uint_be(app_status)
out.write_byte(protocol_status.to_byte())
for _ in 0..<3 {
out.write_byte(b'\x00')
}
{ kind: 3, id, content: out.to_bytes(), }
}