///|
const MAGIC_FIRST : Byte = 0x4d
///|
const MAGIC_SECOND : Byte = 0x57
///|
const HEADER_SIZE : Int = 8
///|
const MAX_PAYLOAD : Int = 16777215
///|
/// A decoded application frame.
pub struct Frame {
version : Int
kind : Int
payload : Bytes
}
///|
pub fn Frame::new(
version : Int,
kind : Int,
payload : Bytes,
) -> Result[Frame, String] {
if version < 0 || version > 255 {
Err("frame.version.range")
} else if kind < 0 || kind > 255 {
Err("frame.kind.range")
} else if payload.length() > MAX_PAYLOAD {
Err("frame.payload.too_large")
} else {
Ok({ version, kind, payload })
}
}
///|
pub fn Frame::version(self : Frame) -> Int {
self.version
}
///|
pub fn Frame::kind(self : Frame) -> Int {
self.kind
}
///|
pub fn Frame::payload(self : Frame) -> Bytes {
self.payload
}
///|
/// Encode as magic(2), version(1), kind(1), length(3), payload.
pub fn encode(frame : Frame) -> Bytes {
let n = frame.payload.length()
let out = Buffer()
out.write_byte(MAGIC_FIRST)
out.write_byte(MAGIC_SECOND)
out.write_byte(frame.version.to_byte())
out.write_byte(frame.kind.to_byte())
out.write_byte((n / 65536).to_byte())
out.write_byte((n / 256 % 256).to_byte())
out.write_byte((n % 256).to_byte())
out.write_byte(checksum(frame.version, frame.kind, frame.payload).to_byte())
out.write_bytes(frame.payload)
out.to_bytes()
}
///|
fn checksum(version : Int, kind : Int, payload : Bytes) -> Int {
let mut x = version ^ kind
for b in payload {
x = (x + b.to_int()) % 256
}
x
}
///|
pub fn decode(data : Bytes) -> Result[Frame, String] {
if data.length() < HEADER_SIZE {
return Err("frame.truncated.header")
}
if data[0] != MAGIC_FIRST || data[1] != MAGIC_SECOND {
return Err("frame.magic")
}
let n = data[4].to_int() * 65536 + data[5].to_int() * 256 + data[6].to_int()
if data.length() != HEADER_SIZE + n {
return Err("frame.truncated.payload")
}
let payload = data[8:].to_owned()
let version = data[2].to_int()
let kind = data[3].to_int()
if data[7].to_int() != checksum(version, kind, payload) {
return Err("frame.checksum")
}
Ok({ version, kind, payload })
}
///|
/// Return a stable human-readable error category.
pub fn explain(error : String) -> String {
match error {
"frame.magic" => "invalid MoonWire magic"
"frame.checksum" => "payload checksum mismatch"
"frame.truncated.header" => "frame header is incomplete"
"frame.truncated.payload" =>
"frame payload is incomplete or has trailing bytes"
_ => "invalid frame"
}
}
///|
/// Return the encoded frame size for a payload.
pub fn encoded_size(payload_length : Int) -> Result[Int, String] {
if payload_length < 0 || payload_length > 16777215 {
Err("frame.payload.range")
} else {
Ok(HEADER_SIZE + payload_length)
}
}
///|
/// Return the payload length encoded in a valid frame header.
pub fn payload_length(data : Bytes) -> Result[Int, String] {
match parse_header(data) {
Err(error) => Err(error)
Ok(header) => Ok(header.payload_length())
}
}
///|
/// Check whether bytes contain exactly one valid frame.
pub fn is_valid(data : Bytes) -> Bool {
decode(data) is Ok(_)
}
///|
/// Compute the checksum used by the frame format.
pub fn frame_checksum(
version : Int,
kind : Int,
payload : Bytes,
) -> Result[Int, String] {
if version < 0 || version > 255 || kind < 0 || kind > 255 {
Err("frame.invalid")
} else if payload.length() > MAX_PAYLOAD {
Err("frame.invalid")
} else {
Ok(checksum(version, kind, payload))
}
}
///|
/// Return the version from a valid header.
pub fn header_version(data : Bytes) -> Result[Int, String] {
match parse_header(data) {
Err(error) => Err(error)
Ok(header) => Ok(header.version())
}
}
///|
/// Return the kind from a valid header.
pub fn header_kind(data : Bytes) -> Result[Int, String] {
match parse_header(data) {
Err(error) => Err(error)
Ok(header) => Ok(header.kind())
}
}
///|
/// Return the encoded size of this frame.
pub fn Frame::encoded_size(self : Frame) -> Int {
HEADER_SIZE + self.payload.length()
}
///|
/// Metadata read from a frame header.
pub struct Header {
version : Int
kind : Int
payload_length : Int
checksum : Int
}
///|
/// Parse and validate the fixed-size header without decoding the payload.
pub fn parse_header(data : Bytes) -> Result[Header, String] {
if data.length() < HEADER_SIZE {
Err("frame.truncated.header")
} else if data[0] != MAGIC_FIRST || data[1] != MAGIC_SECOND {
Err("frame.magic")
} else {
Ok({
version: data[2].to_int(),
kind: data[3].to_int(),
payload_length: data[4].to_int() * 65536 +
data[5].to_int() * 256 +
data[6].to_int(),
checksum: data[7].to_int(),
})
}
}
///|
/// Get the version from parsed header metadata.
pub fn Header::version(self : Header) -> Int {
self.version
}
///|
/// Get the kind from parsed header metadata.
pub fn Header::kind(self : Header) -> Int {
self.kind
}
///|
/// Get the payload length from parsed header metadata.
pub fn Header::payload_length(self : Header) -> Int {
self.payload_length
}
///|
/// Get the stored checksum from parsed header metadata.
pub fn Header::checksum(self : Header) -> Int {
self.checksum
}
///|
/// Decode the first frame in data and return trailing bytes separately.
pub fn decode_prefix(data : Bytes) -> Result[(Frame, Bytes), String] {
match parse_header(data) {
Err(error) => Err(error)
Ok(header) => {
let total = HEADER_SIZE + header.payload_length()
if data.length() < total {
Err("frame.truncated.payload")
} else {
match decode(data[:total].to_owned()) {
Err(error) => Err(error)
Ok(frame) => Ok((frame, data[total:].to_owned()))
}
}
}
}
}
///|
/// Validate a frame without allocating a decoded payload for callers.
pub fn validate(data : Bytes) -> Result[Unit, String] {
match decode(data) {
Err(error) => Err(error)
Ok(_) => Ok(())
}
}
///|
/// Return the maximum payload size representable by the wire format.
pub fn max_payload_length() -> Int {
MAX_PAYLOAD
}
///|
/// Return the fixed header size in bytes.
pub fn header_size() -> Int {
HEADER_SIZE
}
///|
/// Compare two frames by all wire-visible fields.
pub fn Frame::same(self : Frame, other : Frame) -> Bool {
self.version == other.version &&
self.kind == other.kind &&
self.payload == other.payload
}
///|
/// Encode a frame from primitive fields after applying constructor checks.
pub fn encode_checked(
version : Int,
kind : Int,
payload : Bytes,
) -> Result[Bytes, String] {
match Frame::new(version, kind, payload) {
Err(error) => Err(error)
Ok(frame) => Ok(encode(frame))
}
}
///|
/// Check the stored checksum in a complete frame without returning its payload.
pub fn checksum_matches(data : Bytes) -> Result[Bool, String] {
match parse_header(data) {
Err(error) => Err(error)
Ok(header) => {
let total = HEADER_SIZE + header.payload_length()
if data.length() != total {
Err("frame.truncated.payload")
} else {
let payload = data[HEADER_SIZE:].to_owned()
Ok(
header.checksum() ==
checksum(header.version(), header.kind(), payload),
)
}
}
}
}
///|
/// Return the total number of bytes required by a parsed header.
pub fn Header::total_size(self : Header) -> Int {
HEADER_SIZE + self.payload_length
}