///|
/// Property names use XML spelling; absent properties differ from present empty/zero values.
pub(all) struct BasicHeader {
body_size : UInt64
properties : Array[(String, Argument)]
} derive(Debug, Eq)
///|
pub fn basic_property_spec() -> Array[(String, ArgumentKind)] {
[
("content-type", ShortStringKind),
("content-encoding", ShortStringKind),
("headers", TableKind),
("delivery-mode", OctetKind),
("priority", OctetKind),
("correlation-id", ShortStringKind),
("reply-to", ShortStringKind),
("expiration", ShortStringKind),
("message-id", ShortStringKind),
("timestamp", LongLongKind),
("type", ShortStringKind),
("user-id", ShortStringKind),
("app-id", ShortStringKind),
("cluster-id", ShortStringKind),
]
}
///|
pub fn BasicHeader::encode(
self : BasicHeader,
channel : Int,
max_size? : Int = 131072,
) -> Frame raise FrameError {
if channel < 1 || channel > 65535 || max_size < 8 || max_size > 16777216 {
raise Invalid("invalid content header channel or frame limit")
}
let spec = basic_property_spec()
if self.properties.length() > spec.length() {
raise Invalid("too many Basic properties")
}
let values : Map[String, Argument] = Map([])
for (key, value) in self.properties {
if values.contains(key) || !spec.any(item => item.0 == key) {
raise Invalid("unknown or duplicate Basic property")
}
values[key] = value
}
let mut flags = 0UL
for i, (key, _) in spec {
if values.contains(key) {
flags = flags | (1UL << (15 - i))
}
}
let w = WireWriter::new()
w.uint(60UL, 2)
w.uint(0UL, 2)
w.uint(self.body_size, 8)
w.uint(flags, 2)
for (key, kind) in spec {
if values.get(key) is Some(value) {
write_argument(w, kind, value)
}
}
let frame : Frame = { kind: 2, channel, payload: Bytes::from_array(w.bytes), }
validate(frame, max_size)
frame
}
///|
pub fn BasicHeader::decode(frame : Frame) -> BasicHeader raise FrameError {
validate(frame, 16777216)
if frame.kind != 2 {
raise Invalid("expected content header frame")
}
let r = WireReader::new(frame.payload)
if r.uint(2) != 60UL || r.uint(2) != 0UL {
raise Invalid("expected Basic class header with weight zero")
}
let body_size = r.uint(8)
let flags = r.uint(2)
if (flags & 3UL) != 0UL {
raise Invalid("unsupported Basic property flag or continuation")
}
let properties = []
for i, (key, kind) in basic_property_spec() {
if (flags & (1UL << (15 - i))) != 0UL {
let value = match kind {
ShortStringKind => ShortString(r.shortstr())
OctetKind => Octet(r.uint(1).to_int())
LongLongKind => LongLong(r.uint(8))
TableKind => Table(read_table(r, 0))
_ => raise Invalid("unsupported Basic property kind")
}
properties.push((key, value))
}
}
r.finish()
{ body_size, properties, }
}
///|
/// Build method/header/body frames together, with the negotiated frame envelope limit.
/// Bodies use the existing bounded body_frames helper (up to 1 MiB).
pub fn content_frames(
command : Method,
channel : Int,
properties : Array[(String, Argument)],
body : Bytes,
max_frame_size? : Int = 131072,
) -> Array[Frame] raise FrameError {
let spec = match method_spec(command.class_id, command.method_id) {
Some(spec) => spec
None => raise Invalid("unknown content method")
}
if !spec.carries_content {
raise Invalid("method does not carry content")
}
let first = command.encode(channel, max_size=max_frame_size)
let header : BasicHeader = {
body_size: body.length().to_uint64(),
properties,
}
let frames = [first, header.encode(channel, max_size=max_frame_size)]
for frame in body_frames(channel, body, max_frame_size) {
frames.push(frame)
}
frames
}
///|
pub fn Content::basic_header(self : Content) -> BasicHeader raise FrameError {
BasicHeader::decode({ kind: 2, channel: self.channel, payload: self.header, })
}