///|
pub(all) enum ArgumentKind {
BitKind
OctetKind
ShortKind
LongKind
LongLongKind
ShortStringKind
LongStringKind
TableKind
} derive(Debug, Eq)
///|
pub(all) enum Argument {
Bit(Bool)
Octet(Int)
Short(Int)
Long(UInt)
LongLong(UInt64)
ShortString(String)
LongString(Bytes)
Table(Array[(String, FieldValue)])
} derive(Debug, Eq)
///|
pub struct MethodSpec {
class_id : Int
method_id : Int
name : String
fields : Array[(String, ArgumentKind)]
carries_content : Bool
} derive(Debug, Eq)
///|
pub(all) struct Method {
class_id : Int
method_id : Int
arguments : Array[Argument]
} derive(Debug, Eq)
///|
fn method_channel(class_id : Int, channel : Int) -> Unit raise FrameError {
if channel < 0 || channel > 65535 || (class_id == 10) != (channel == 0) {
raise Invalid(
"connection methods require channel zero; other methods require a nonzero channel",
)
}
}
///|
/// Names use the specification spelling, for example "queue.declare" or "basic.get-ok".
pub fn Method::new(
name : String,
arguments : Array[Argument],
) -> Method raise FrameError {
let spec = match method_spec_by_name(name) {
Some(spec) => spec
None => raise Invalid("unknown method name")
}
{ class_id: spec.class_id, method_id: spec.method_id, arguments, }
}
///|
fn write_argument(
w : WireWriter,
kind : ArgumentKind,
value : Argument,
) -> Unit raise FrameError {
match (kind, value) {
(OctetKind, Octet(n)) => w.uint(checked_int(n, 0, 255), 1)
(ShortKind, Short(n)) => w.uint(checked_int(n, 0, 65535), 2)
(LongKind, Long(n)) => w.uint(n.to_uint64(), 4)
(LongLongKind, LongLong(n)) => w.uint(n, 8)
(ShortStringKind, ShortString(s)) => w.shortstr(s)
(LongStringKind, LongString(s)) => w.longstr(s)
(TableKind, Table(t)) => write_table(w, t, 0)
_ => raise Invalid("method argument type mismatch")
}
}
///|
pub fn Method::encode(
self : Method,
channel : Int,
max_size? : Int = 131072,
) -> Frame raise FrameError {
method_channel(self.class_id, channel)
let spec = match method_spec(self.class_id, self.method_id) {
Some(spec) => spec
None => raise Invalid("unknown class or method id")
}
if self.arguments.length() != spec.fields.length() {
raise Invalid("method argument count mismatch")
}
let w = WireWriter::new()
w.uint(self.class_id.to_uint64(), 2)
w.uint(self.method_id.to_uint64(), 2)
let mut bits = 0UL
let mut used = 0
for i, (_, kind) in spec.fields {
if kind == BitKind {
let bit = match self.arguments[i] {
Bit(value) => value
_ => raise Invalid("method argument type mismatch")
}
if bit {
bits = bits | (1UL << used)
}
used += 1
if used == 8 {
w.uint(bits, 1)
bits = 0UL
used = 0
}
} else {
if used > 0 {
w.uint(bits, 1)
bits = 0UL
used = 0
}
write_argument(w, kind, self.arguments[i])
}
}
if used > 0 {
w.uint(bits, 1)
}
let frame : Frame = { kind: 1, channel, payload: Bytes::from_array(w.bytes), }
if max_size < 8 || max_size > 16777216 {
raise Invalid("invalid frame limit")
}
validate(frame, max_size)
frame
}
///|
/// Decode exactly one known method, rejecting truncated and extra arguments.
/// Unused packed bit positions are ignored on input and zeroed on encoding.
pub fn Method::decode(frame : Frame) -> Method raise FrameError {
validate(frame, 16777216)
if frame.kind != 1 {
raise Invalid("expected method frame")
}
let r = WireReader::new(frame.payload)
let class_id = r.uint(2).to_int()
let method_id = r.uint(2).to_int()
method_channel(class_id, frame.channel)
let spec = match method_spec(class_id, method_id) {
Some(spec) => spec
None => raise Invalid("unknown class or method id")
}
let arguments = []
let mut bits = 0UL
let mut used = 8
for (_, kind) in spec.fields {
if kind == BitKind {
if used == 8 {
bits = r.uint(1)
used = 0
}
arguments.push(Bit((bits & (1UL << used)) != 0UL))
used += 1
} else {
used = 8
arguments.push(
match kind {
OctetKind => Octet(r.uint(1).to_int())
ShortKind => Short(r.uint(2).to_int())
LongKind => Long(r.uint(4).to_uint())
LongLongKind => LongLong(r.uint(8))
ShortStringKind => ShortString(r.shortstr())
LongStringKind => LongString(r.longstr())
TableKind => Table(read_table(r, 0))
BitKind => raise Invalid("invalid bit field state")
},
)
}
}
r.finish()
{ class_id, method_id, arguments, }
}