///|
/// RabbitMQ field-table dialect. Floating point values preserve their raw IEEE bits.
/// LongString retains arbitrary bytes; table order and duplicate keys are preserved.
pub(all) enum FieldValue {
Boolean(Bool)
Signed8(Int)
Unsigned8(Int)
Signed16(Int)
Unsigned16(Int)
Signed32(Int)
Unsigned32(UInt)
Signed64(Int64)
Float32Bits(UInt)
Float64Bits(UInt64)
Decimal(Int, Int)
LongString(Bytes)
ByteArray(Bytes)
Timestamp(UInt64)
ArrayValue(Array[FieldValue])
TableValue(Array[(String, FieldValue)])
Void
} derive(Debug, Eq)
///|
fn checked_int(value : Int, low : Int, high : Int) -> UInt64 raise FrameError {
if value < low || value > high {
raise Invalid("integer field out of range")
}
value.to_int64().reinterpret_as_uint64()
}
///|
fn write_table(
w : WireWriter,
entries : Array[(String, FieldValue)],
depth : Int,
) -> Unit raise FrameError {
w.node(depth)
let start = w.bytes.length()
w.uint(0UL, 4)
for (key, value) in entries {
w.shortstr(key)
write_field(w, value, depth + 1)
}
w.patch_size(start)
}
///|
fn write_field(
w : WireWriter,
value : FieldValue,
depth : Int,
) -> Unit raise FrameError {
w.node(depth)
match value {
Boolean(v) => {
w.uint(116UL, 1)
w.uint(if v { 1UL } else { 0UL }, 1)
}
Signed8(v) => {
w.uint(98UL, 1)
w.uint(checked_int(v, -128, 127), 1)
}
Unsigned8(v) => {
w.uint(66UL, 1)
w.uint(checked_int(v, 0, 255), 1)
}
Signed16(v) => {
w.uint(115UL, 1)
w.uint(checked_int(v, -32768, 32767), 2)
}
Unsigned16(v) => {
w.uint(117UL, 1)
w.uint(checked_int(v, 0, 65535), 2)
}
Signed32(v) => {
w.uint(73UL, 1)
w.uint(v.to_int64().reinterpret_as_uint64(), 4)
}
Unsigned32(v) => {
w.uint(105UL, 1)
w.uint(v.to_uint64(), 4)
}
Signed64(v) => {
w.uint(108UL, 1)
w.uint(v.reinterpret_as_uint64(), 8)
}
Float32Bits(v) => {
w.uint(102UL, 1)
w.uint(v.to_uint64(), 4)
}
Float64Bits(v) => {
w.uint(100UL, 1)
w.uint(v, 8)
}
Decimal(scale, coefficient) => {
w.uint(68UL, 1)
w.uint(checked_int(scale, 0, 255), 1)
w.uint(coefficient.to_int64().reinterpret_as_uint64(), 4)
}
LongString(v) => {
w.uint(83UL, 1)
w.longstr(v)
}
ByteArray(v) => {
w.uint(120UL, 1)
w.longstr(v)
}
Timestamp(v) => {
w.uint(84UL, 1)
w.uint(v, 8)
}
Void => w.uint(86UL, 1)
TableValue(entries) => {
w.uint(70UL, 1)
write_table(w, entries, depth)
}
ArrayValue(values) => {
w.uint(65UL, 1)
let start = w.bytes.length()
w.uint(0UL, 4)
for item in values {
write_field(w, item, depth + 1)
}
w.patch_size(start)
}
}
}
///|
fn read_table(
r : WireReader,
depth : Int,
) -> Array[(String, FieldValue)] raise FrameError {
r.node(depth)
let end = r.container_end()
let outer = r.end
r.end = end
let entries = []
while r.pos < end {
let key = r.shortstr()
entries.push((key, read_field(r, depth + 1)))
}
r.end = outer
entries
}
///|
fn read_field(r : WireReader, depth : Int) -> FieldValue raise FrameError {
r.node(depth)
match r.uint(1).to_int() {
116 => Boolean(r.uint(1) != 0UL)
98 => {
let n = r.uint(1).to_int()
Signed8(if n >= 128 { n - 256 } else { n })
}
66 => Unsigned8(r.uint(1).to_int())
115 => {
let n = r.uint(2).to_int()
Signed16(if n >= 32768 { n - 65536 } else { n })
}
117 => Unsigned16(r.uint(2).to_int())
73 => Signed32(r.uint(4).to_int())
105 => Unsigned32(r.uint(4).to_uint())
108 => Signed64(r.uint(8).reinterpret_as_int64())
102 => Float32Bits(r.uint(4).to_uint())
100 => Float64Bits(r.uint(8))
68 => {
let scale = r.uint(1).to_int()
Decimal(scale, r.uint(4).to_int())
}
83 => LongString(r.longstr())
120 => ByteArray(r.longstr())
84 => Timestamp(r.uint(8))
86 => Void
70 => TableValue(read_table(r, depth))
65 => {
let end = r.container_end()
let outer = r.end
r.end = end
let values = []
while r.pos < end {
values.push(read_field(r, depth + 1))
}
r.end = outer
ArrayValue(values)
}
_ => raise Invalid("unknown field-table type tag")
}
}
///|
/// Encode a length-prefixed AMQP table; maximum depth 32, 65536 visited nodes.
pub fn encode_table(
entries : Array[(String, FieldValue)],
) -> Bytes raise FrameError {
let w = WireWriter::new()
write_table(w, entries, 0)
Bytes::from_array(w.bytes)
}
///|
pub fn decode_table(
bytes : Bytes,
) -> Array[(String, FieldValue)] raise FrameError {
let r = WireReader::new(bytes)
let result = read_table(r, 0)
r.finish()
result
}