///|
/// A bit field in a decoded packet or bus payload.
pub struct PacketField {
name : String
lsb : Int
width : Int
signed : Bool
display : String
} derive(Debug)
///|
/// A packet layout used by protocol and register analysis.
pub struct PacketLayout {
name : String
total_bits : Int
fields : Array[PacketField]
byte_order : String
} derive(Debug)
///|
/// A decoded packet field value.
pub struct PacketFieldValue {
name : String
raw : Int64?
text : String
valid : Bool
} derive(Debug)
///|
/// One packet captured at a protocol timestamp.
pub struct PacketRecord {
index : Int
time : Int64
payload : Value?
fields : Array[PacketFieldValue]
valid : Bool
error : String?
} derive(Debug)
///|
/// List supported packet byte orders.
pub fn packet_byte_order_catalog() -> Array[String] {
["msb0", "lsb0", "little-endian", "big-endian"]
}
///|
/// Create an empty packet layout.
pub fn packet_layout(name : String, total_bits : Int) -> PacketLayout {
{ name, total_bits: Int::max(1, total_bits), fields: [], byte_order: "msb0" }
}
///|
/// Set byte order.
pub fn PacketLayout::with_byte_order(
self : PacketLayout,
byte_order : String,
) -> PacketLayout {
{ ..self, byte_order, }
}
///|
/// Add a field, clamped to the layout width.
pub fn PacketLayout::with_field(
self : PacketLayout,
name : String,
lsb : Int,
width : Int,
signed : Bool,
) -> PacketLayout {
let safe_lsb = Int::max(0, lsb)
let safe_width = Int::max(1, Int::min(width, self.total_bits - safe_lsb))
let fields = self.fields.copy()
fields.push({
name,
lsb: safe_lsb,
width: safe_width,
signed,
display: "decimal",
})
{ ..self, fields, }
}
///|
/// Decode one payload value according to a packet layout.
pub fn decode_packet(
layout : PacketLayout,
time : Int64,
payload : Value?,
) -> PacketRecord {
let fields : Array[PacketFieldValue] = []
for field in layout.fields {
let raw = match payload {
Some(value) =>
packet_extract_bits(value, field.lsb, field.width, layout.byte_order)
None => None
}
fields.push({
name: field.name,
raw,
text: packet_optional_int(raw),
valid: raw is Some(_),
})
}
let valid = payload is Some(_) && fields.all(fn(item) { item.valid })
{
index: 0,
time,
payload,
fields,
valid,
error: if valid {
None
} else {
Some("payload is absent or has unknown bits")
},
}
}
///|
/// Decode a sequence of packet payloads.
pub fn decode_packets(
layout : PacketLayout,
payloads : Array[(Int64, Value?)],
) -> Array[PacketRecord] {
let result : Array[PacketRecord] = []
for index, item in payloads {
let record = decode_packet(layout, item.0, item.1)
result.push({ ..record, index, })
}
result
}
///|
/// Return the first field with a named identifier.
pub fn PacketRecord::field(
self : PacketRecord,
name : String,
) -> PacketFieldValue? {
for item in self.fields {
if item.name == name {
return Some(item)
}
}
None
}
///|
/// Return all valid records.
pub fn valid_packets(records : Array[PacketRecord]) -> Array[PacketRecord] {
records.filter(fn(item) { item.valid })
}
///|
/// Export packet records as CSV.
pub fn packet_records_csv(records : Array[PacketRecord]) -> String {
let out = StringBuilder()
out.write_string("index,time,valid,payload,fields,error\n")
for record in records {
out.write_string(
record.index.to_string() +
"," +
record.time.to_string() +
"," +
record.valid.to_string() +
",",
)
out.write_string(
match record.payload {
Some(value) => escape_text(value.display())
None => ""
},
)
out.write_char(',')
out.write_string(
record.fields.map(fn(item) { item.name + "=" + item.text }).join(";"),
)
out.write_char(',')
out.write_string(
match record.error {
Some(item) => escape_text(item)
None => ""
},
)
out.write_char('\n')
}
out.to_string()
}
///|
/// Render a layout as JSON.
pub fn PacketLayout::to_json(self : PacketLayout) -> String {
let fields = self.fields.map(packet_field_json).join(",")
"{\"name\":\"" +
escape_text(self.name) +
"\",\"total_bits\":" +
self.total_bits.to_string() +
",\"byte_order\":\"" +
self.byte_order +
"\",\"fields\":[" +
fields +
"]}"
}
///|
/// Render a packet record as JSON.
pub fn PacketRecord::to_json(self : PacketRecord) -> String {
let fields = self.fields.map(packet_field_value_json).join(",")
"{\"index\":" +
self.index.to_string() +
",\"time\":" +
self.time.to_string() +
",\"valid\":" +
self.valid.to_string() +
",\"payload\":" +
packet_value_json(self.payload) +
",\"fields\":[" +
fields +
"]}"
}
///|
/// Extract a bounded unsigned field from scalar or vector data.
fn packet_extract_bits(
value : Value,
lsb : Int,
width : Int,
byte_order : String,
) -> Int64? {
let bits = value.bits()
if bits.length() == 0 {
return None
}
let mut result = 0L
for offset in 0..= bits.length() {
return None
}
match bits[index] {
One => result += 1L << offset
Zero => ()
_ => return None
}
}
Some(result)
}
///|
/// Format an optional packet field value.
fn packet_optional_int(value : Int64?) -> String {
match value {
Some(item) => item.to_string()
None => "?"
}
}
///|
/// Encode an optional packet payload.
fn packet_value_json(value : Value?) -> String {
match value {
Some(item) => value_json(item)
None => "null"
}
}
///|
/// Encode one layout field.
fn packet_field_json(item : PacketField) -> String {
"{\"name\":\"" +
escape_text(item.name) +
"\",\"lsb\":" +
item.lsb.to_string() +
",\"width\":" +
item.width.to_string() +
",\"signed\":" +
item.signed.to_string() +
"}"
}
///|
/// Encode one decoded field value.
fn packet_field_value_json(item : PacketFieldValue) -> String {
let raw = match item.raw {
Some(value) => value.to_string()
None => "null"
}
"{\"name\":\"" +
escape_text(item.name) +
"\",\"raw\":" +
raw +
",\"text\":\"" +
escape_text(item.text) +
"\",\"valid\":" +
item.valid.to_string() +
"}"
}