#![cfg_attr(docsrs, feature(doc_cfg))]
#![doc(html_logo_url = "https://knurling.ferrous-systems.com/knurling_logo_light_text.svg")]
pub const DEFMT_VERSIONS: &[&str] = &["3", "4"];
#[deprecated = "Please use DEFMT_VERSIONS instead"]
pub const DEFMT_VERSION: &str = DEFMT_VERSIONS[1];
mod decoder;
mod elf2table;
mod frame;
pub mod log;
mod stream;
use std::{
collections::{BTreeMap, HashMap},
error::Error,
fmt, io,
str::FromStr,
};
use byteorder::{ReadBytesExt, LE};
use defmt_parser::Level;
use serde::{Deserialize, Serialize};
use crate::{decoder::Decoder, elf2table::parse_impl};
pub use crate::{
elf2table::{Location, Locations},
frame::Frame,
stream::StreamDecoder,
};
#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)]
pub enum Tag {
Prim,
Derived,
Bitflags,
Write,
Str,
Timestamp,
BitflagsValue,
Println,
Trace,
Debug,
Info,
Warn,
Error,
}
impl Tag {
fn to_level(&self) -> Option<Level> {
match self {
Tag::Trace => Some(Level::Trace),
Tag::Debug => Some(Level::Debug),
Tag::Info => Some(Level::Info),
Tag::Warn => Some(Level::Warn),
Tag::Error => Some(Level::Error),
_ => None,
}
}
}
#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
pub struct TableEntry {
string: StringEntry,
raw_symbol: String,
}
impl TableEntry {
pub fn new(string: StringEntry, raw_symbol: String) -> Self {
Self { string, raw_symbol }
}
#[cfg(test)]
fn new_without_symbol(tag: Tag, string: String) -> Self {
Self {
string: StringEntry::new(tag, string),
raw_symbol: "<unknown>".to_string(),
}
}
}
#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
pub struct StringEntry {
tag: Tag,
string: String,
}
impl StringEntry {
pub fn new(tag: Tag, string: String) -> Self {
Self { tag, string }
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
struct BitflagsKey {
ident: String,
package: String,
disambig: String,
crate_name: Option<String>,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub enum Encoding {
Raw,
Rzcobs,
}
impl FromStr for Encoding {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"raw" => Ok(Encoding::Raw),
"rzcobs" => Ok(Encoding::Rzcobs),
_ => anyhow::bail!("Unknown defmt encoding '{}' specified. This is a bug.", s),
}
}
}
impl Encoding {
pub const fn can_recover(&self) -> bool {
match self {
Encoding::Raw => false,
Encoding::Rzcobs => true,
}
}
}
#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
pub struct Table {
timestamp: Option<TableEntry>,
entries: BTreeMap<usize, TableEntry>,
bitflags: HashMap<BitflagsKey, Vec<(String, u128)>>,
encoding: Encoding,
}
impl Table {
pub fn parse(elf: &[u8]) -> Result<Option<Table>, anyhow::Error> {
parse_impl(elf, true)
}
pub fn parse_ignore_version(elf: &[u8]) -> Result<Option<Table>, anyhow::Error> {
parse_impl(elf, false)
}
pub fn set_timestamp_entry(&mut self, timestamp: TableEntry) {
self.timestamp = Some(timestamp);
}
fn _get(&self, index: usize) -> Result<(Option<Level>, &str), ()> {
let entry = self.entries.get(&index).ok_or(())?;
Ok((entry.string.tag.to_level(), &entry.string.string))
}
fn get_with_level(&self, index: usize) -> Result<(Option<Level>, &str), ()> {
self._get(index)
}
fn get_without_level(&self, index: usize) -> Result<&str, ()> {
let (lvl, format) = self._get(index)?;
if lvl.is_none() {
Ok(format)
} else {
Err(())
}
}
pub fn indices(&self) -> impl Iterator<Item = usize> + '_ {
self.entries.iter().filter_map(move |(idx, entry)| {
if entry.string.tag.to_level().is_some() || entry.string.tag == Tag::Println {
Some(*idx)
} else {
None
}
})
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn raw_symbols(&self) -> impl Iterator<Item = &str> + '_ {
self.entries.values().map(|s| &*s.raw_symbol)
}
pub fn get_locations(&self, elf: &[u8]) -> Result<Locations, anyhow::Error> {
elf2table::get_locations(elf, self)
}
pub fn decode<'t>(
&'t self,
mut bytes: &[u8],
) -> Result<(Frame<'t>, /* consumed: */ usize), DecodeError> {
let len = bytes.len();
let index = bytes.read_u16::<LE>()? as u64;
let mut decoder = Decoder::new(self, bytes);
let mut timestamp_format = None;
let mut timestamp_args = Vec::new();
if let Some(entry) = self.timestamp.as_ref() {
let format = &entry.string.string;
timestamp_format = Some(&**format);
timestamp_args = decoder.decode_format(format)?;
}
let (level, format) = self
.get_with_level(index as usize)
.map_err(|_| DecodeError::Malformed)?;
let args = decoder.decode_format(format)?;
let frame = Frame::new(
self,
level,
index,
timestamp_format,
timestamp_args,
format,
args,
);
let consumed = len - decoder.bytes.len();
Ok((frame, consumed))
}
pub fn new_stream_decoder(&self) -> Box<dyn StreamDecoder + Send + Sync + '_> {
match self.encoding {
Encoding::Raw => Box::new(stream::Raw::new(self)),
Encoding::Rzcobs => Box::new(stream::Rzcobs::new(self)),
}
}
pub fn encoding(&self) -> Encoding {
self.encoding
}
pub fn has_timestamp(&self) -> bool {
self.timestamp.is_some()
}
}
#[derive(Debug, Clone, PartialEq)]
enum Arg<'t> {
Bool(bool),
F32(f32),
F64(f64),
Uxx(u128),
Ixx(i128),
Str(String),
IStr(&'t str),
Format {
format: &'t str,
args: Vec<Arg<'t>>,
},
FormatSlice {
elements: Vec<FormatSliceElement<'t>>,
},
FormatSequence {
args: Vec<Arg<'t>>,
},
Slice(Vec<u8>),
Char(char),
Preformatted(String),
}
#[derive(Debug, Clone, PartialEq)]
struct FormatSliceElement<'t> {
format: &'t str,
args: Vec<Arg<'t>>,
}
#[derive(Debug, Eq, PartialEq)]
pub enum DecodeError {
UnexpectedEof,
Malformed,
}
impl From<io::Error> for DecodeError {
fn from(e: io::Error) -> Self {
if e.kind() == io::ErrorKind::UnexpectedEof {
DecodeError::UnexpectedEof
} else {
DecodeError::Malformed
}
}
}
impl fmt::Display for DecodeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
DecodeError::UnexpectedEof => f.write_str("unexpected end of stream"),
DecodeError::Malformed => f.write_str("malformed data"),
}
}
}
impl Error for DecodeError {}
#[cfg(test)]
mod tests {
use super::*;
fn test_table(entries: impl IntoIterator<Item = TableEntry>) -> Table {
Table {
timestamp: None,
entries: entries.into_iter().enumerate().collect(),
bitflags: Default::default(),
encoding: Encoding::Raw,
}
}
fn test_table_with_timestamp(
entries: impl IntoIterator<Item = TableEntry>,
timestamp: &str,
) -> Table {
Table {
timestamp: Some(TableEntry::new_without_symbol(
Tag::Timestamp,
timestamp.into(),
)),
entries: entries.into_iter().enumerate().collect(),
bitflags: Default::default(),
encoding: Encoding::Raw,
}
}
fn decode_and_expect(format: &str, bytes: &[u8], expectation: &str) {
let mut entries = BTreeMap::new();
entries.insert(
bytes[0] as usize,
TableEntry::new_without_symbol(Tag::Info, format.to_string()),
);
let table = Table {
entries,
timestamp: Some(TableEntry::new_without_symbol(
Tag::Timestamp,
"{=u8:us}".to_owned(),
)),
bitflags: Default::default(),
encoding: Encoding::Raw,
};
let frame = table.decode(bytes).unwrap().0;
assert_eq!(frame.display(false).to_string(), expectation.to_owned());
}
#[test]
fn decode() {
let entries = vec![
TableEntry::new_without_symbol(Tag::Info, "Hello, world!".to_owned()),
TableEntry::new_without_symbol(Tag::Debug, "The answer is {=u8}!".to_owned()),
];
let table = test_table(entries);
let bytes = [0, 0];
assert_eq!(
table.decode(&bytes),
Ok((
Frame::new(
&table,
Some(Level::Info),
0,
None,
vec![],
"Hello, world!",
vec![],
),
bytes.len(),
))
);
let bytes = [
1, 0, 42, ];
assert_eq!(
table.decode(&bytes),
Ok((
Frame::new(
&table,
Some(Level::Debug),
1,
None,
vec![],
"The answer is {=u8}!",
vec![Arg::Uxx(42)],
),
bytes.len(),
))
);
}
#[test]
fn all_integers() {
const FMT: &str =
"Hello, {=u8} {=u16} {=u32} {=u64} {=u128} {=i8} {=i16} {=i32} {=i64} {=i128}!";
let entries = vec![TableEntry::new_without_symbol(Tag::Info, FMT.to_owned())];
let table = test_table(entries);
let bytes = [
0, 0, 42, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, ];
assert_eq!(
table.decode(&bytes),
Ok((
Frame::new(
&table,
Some(Level::Info),
0,
None,
vec![],
FMT,
vec![
Arg::Uxx(42), Arg::Uxx(u16::MAX.into()), Arg::Uxx(u32::MAX.into()), Arg::Uxx(u64::MAX.into()), Arg::Uxx(u128::MAX), Arg::Ixx(-1), Arg::Ixx(-1), Arg::Ixx(-1), Arg::Ixx(-1), Arg::Ixx(-1), ],
),
bytes.len(),
))
);
}
#[test]
fn indices() {
let entries = vec![
TableEntry::new_without_symbol(Tag::Info, "The answer is {0=u8} {0=u8}!".to_owned()),
TableEntry::new_without_symbol(
Tag::Info,
"The answer is {1=u16} {0=u8} {1=u16}!".to_owned(),
),
];
let table = test_table(entries);
let bytes = [
0, 0, 42, ];
assert_eq!(
table.decode(&bytes),
Ok((
Frame::new(
&table,
Some(Level::Info),
0,
None,
vec![],
"The answer is {0=u8} {0=u8}!",
vec![Arg::Uxx(42)],
),
bytes.len(),
))
);
let bytes = [
1, 0, 42, 0xff, 0xff, ];
assert_eq!(
table.decode(&bytes),
Ok((
Frame::new(
&table,
Some(Level::Info),
1,
None,
vec![],
"The answer is {1=u16} {0=u8} {1=u16}!",
vec![Arg::Uxx(42), Arg::Uxx(0xffff)],
),
bytes.len(),
))
);
}
#[test]
fn format() {
let entries = vec![
TableEntry::new_without_symbol(Tag::Info, "x={=?}".to_owned()),
TableEntry::new_without_symbol(Tag::Derived, "Foo {{ x: {=u8} }}".to_owned()),
];
let table = test_table(entries);
let bytes = [
0, 0, 1, 0, 42, ];
assert_eq!(
table.decode(&bytes),
Ok((
Frame::new(
&table,
Some(Level::Info),
0,
None,
vec![],
"x={=?}",
vec![Arg::Format {
format: "Foo {{ x: {=u8} }}",
args: vec![Arg::Uxx(42)]
}],
),
bytes.len(),
))
);
}
#[test]
fn format_sequence() {
let entries = vec![
TableEntry::new_without_symbol(Tag::Info, "{=__internal_FormatSequence}".to_owned()),
TableEntry::new_without_symbol(Tag::Derived, "Foo".to_owned()),
TableEntry::new_without_symbol(Tag::Derived, "Bar({=u8})".to_owned()),
TableEntry::new_without_symbol(Tag::Derived, "State {=u8}|".to_owned()),
];
let table = test_table(entries);
let bytes = [
0, 0, 1, 0, 2, 0, 42, 3, 0, 23, 0, 0, ];
assert_eq!(
table.decode(&bytes),
Ok((
Frame::new(
&table,
Some(Level::Info),
0,
None,
vec![],
"{=__internal_FormatSequence}",
vec![Arg::FormatSequence {
args: vec![
Arg::Format {
format: "Foo",
args: vec![]
},
Arg::Format {
format: "Bar({=u8})",
args: vec![Arg::Uxx(42)]
},
Arg::Format {
format: "State {=u8}|",
args: vec![Arg::Uxx(23)]
}
]
}],
),
bytes.len(),
))
);
}
#[test]
fn display() {
let entries = vec![
TableEntry::new_without_symbol(Tag::Info, "x={=?}".to_owned()),
TableEntry::new_without_symbol(Tag::Derived, "Foo {{ x: {=u8} }}".to_owned()),
];
let table = test_table_with_timestamp(entries, "{=u8:us}");
let bytes = [
0, 0, 2, 1, 0, 42, ];
let frame = table.decode(&bytes).unwrap().0;
assert_eq!(
frame.display(false).to_string(),
"0.000002 INFO x=Foo { x: 42 }"
);
}
#[test]
fn display_message() {
let entries = vec![
TableEntry::new_without_symbol(Tag::Info, "x={=?}".to_owned()),
TableEntry::new_without_symbol(Tag::Derived, "Foo {{ x: {=u8} }}".to_owned()),
];
let table = test_table(entries);
let bytes = [
0, 0, 1, 0, 42, ];
let frame = table.decode(&bytes).unwrap().0;
assert_eq!(frame.display_message().to_string(), "x=Foo { x: 42 }");
}
#[test]
fn display_fragments() {
let entries = vec![
TableEntry::new_without_symbol(Tag::Info, "x={=?}".to_owned()),
TableEntry::new_without_symbol(Tag::Derived, "Foo {{ x: {=u8} }}".to_owned()),
];
let table = test_table(entries);
let bytes = [
0, 0, 1, 0, 42, ];
let frame = table.decode(&bytes).unwrap().0;
assert_eq!(
frame.display_fragments().collect::<Vec<String>>(),
["x=", "Foo { x: 42 }"],
);
}
#[test]
fn display_i16_with_hex_hint() {
let bytes = [
0,
0, 2, 0b1111_1111, 0b1111_1111,
];
decode_and_expect(
"i16 as hex {=i16:#x}",
&bytes,
"0.000002 INFO i16 as hex 0xffff",
);
}
#[test]
fn display_use_inner_type_hint() {
let entries = vec![
TableEntry::new_without_symbol(Tag::Info, "x={:b}".to_owned()),
TableEntry::new_without_symbol(Tag::Derived, "S {{ x: {=u8:x} }}".to_owned()),
];
let table = test_table_with_timestamp(entries, "{=u8:us}");
let bytes = [
0, 0, 2, 1, 0, 42, ];
let frame = table.decode(&bytes).unwrap().0;
assert_eq!(
frame.display(false).to_string(),
"0.000002 INFO x=S { x: 2a }",
);
assert_eq!(
frame.display_fragments().collect::<Vec<String>>(),
["x=", "S { x: 2a }"],
);
}
#[test]
fn display_use_outer_type_hint() {
let entries = vec![
TableEntry::new_without_symbol(Tag::Info, "x={:b}".to_owned()),
TableEntry::new_without_symbol(Tag::Derived, "S {{ x: {=u8:?} }}".to_owned()),
];
let table = test_table_with_timestamp(entries, "{=u8:us}");
let bytes = [
0, 0, 2, 1, 0, 42, ];
let frame = table.decode(&bytes).unwrap().0;
assert_eq!(
frame.display(false).to_string(),
"0.000002 INFO x=S { x: 101010 }",
);
assert_eq!(
frame.display_fragments().collect::<Vec<String>>(),
["x=", "S { x: 101010 }"],
);
}
#[test]
fn display_inner_str_in_struct() {
let entries = vec![
TableEntry::new_without_symbol(Tag::Info, "{}".to_owned()),
TableEntry::new_without_symbol(Tag::Derived, "S {{ x: {=str:?} }}".to_owned()),
];
let table = test_table_with_timestamp(entries, "{=u8:us}");
let bytes = [
0, 0, 2, 1, 0, 5, 0, 0, 0, b'H', b'e', b'l', b'l', b'o', ];
let frame = table.decode(&bytes).unwrap().0;
assert_eq!(
frame.display(false).to_string(),
"0.000002 INFO S { x: \"Hello\" }",
);
assert_eq!(
frame.display_fragments().collect::<Vec<String>>(),
["S { x: \"Hello\" }"],
);
}
#[test]
fn display_u8_vec() {
let entries = vec![
TableEntry::new_without_symbol(Tag::Prim, "{=u8}".to_owned()),
TableEntry::new_without_symbol(Tag::Prim, "{=[?]}".to_owned()),
TableEntry::new_without_symbol(Tag::Derived, "Data {{ name: {=?:?} }}".to_owned()),
TableEntry::new_without_symbol(Tag::Info, "{=[?]:a}".to_owned()),
];
let table = test_table_with_timestamp(entries, "{=u8:us}");
let bytes = [
3, 0, 2, 1, 0, 0, 0, 2, 0, 1, 0, 2, 0, 0, 0, 0, 0, 72, 105, ];
let frame = table.decode(&bytes).unwrap().0;
assert_eq!(
frame.display(false).to_string(),
"0.000002 INFO [Data { name: b\"Hi\" }]",
);
assert_eq!(
frame.display_fragments().collect::<Vec<String>>(),
["[Data { name: b\"Hi\" }]"],
);
}
#[test]
fn display_iso8601_timestamp() {
let bytes = [
0, 0, 2, 36, 188, 151, 238, 120, 1, 0, 0, ];
decode_and_expect(
"{=u64:iso8601ms}",
&bytes,
"0.000002 INFO 2021-04-20T09:23:44.804Z",
);
}
#[test]
fn bools_simple() {
let bytes = [
0, 0, 2, true as u8, ];
decode_and_expect("my bool={=bool}", &bytes, "0.000002 INFO my bool=true");
}
#[test]
fn bitfields() {
let bytes = [
0,
0, 2, 0b1110_0101, ];
decode_and_expect(
"x: {0=0..4:b}, y: {0=3..8:#b}",
&bytes,
"0.000002 INFO x: 101, y: 0b11100",
);
}
#[test]
fn bitfields_reverse_order() {
let bytes = [
0,
0, 2, 0b1101_0010, ];
decode_and_expect(
"x: {0=0..7:b}, y: {0=3..5:b}",
&bytes,
"0.000002 INFO x: 1010010, y: 10",
);
}
#[test]
fn bitfields_different_indices() {
let bytes = [
0,
0, 2, 0b1111_0000, 0b1110_0101, ];
decode_and_expect(
"#0: {0=0..5:b}, #1: {1=3..8:b}",
&bytes,
"0.000002 INFO #0: 10000, #1: 11100",
);
}
#[test]
fn bitfields_u16() {
let bytes = [
0,
0, 2, 0b1111_0000,
0b1110_0101, ];
decode_and_expect("x: {0=7..12:b}", &bytes, "0.000002 INFO x: 1011");
}
#[test]
fn bitfields_mixed_types() {
let bytes = [
0,
0, 2, 0b1111_0000,
0b1110_0101, 0b1111_0001, ];
decode_and_expect(
"#0: {0=7..12:b}, #1: {1=0..5:b}",
&bytes,
"0.000002 INFO #0: 1011, #1: 10001",
);
}
#[test]
fn bitfields_mixed() {
let bytes = [
0,
0, 2, 0b1111_0000,
0b1110_0101, 42, 0b1111_0001, ];
decode_and_expect(
"#0: {0=7..12:b}, #1: {1=u8}, #2: {2=0..5:b}",
&bytes,
"0.000002 INFO #0: 1011, #1: 42, #2: 10001",
);
}
#[test]
fn bitfields_across_boundaries() {
let bytes = [
0,
0, 2, 0b1101_0010,
0b0110_0011, ];
decode_and_expect(
"bitfields {0=0..7:b} {0=9..14:b}",
&bytes,
"0.000002 INFO bitfields 1010010 10001",
);
}
#[test]
fn bitfields_across_boundaries_diff_indices() {
let bytes = [
0,
0, 2, 0b1101_0010,
0b0110_0011, 0b1111_1111, ];
decode_and_expect(
"bitfields {0=0..7:b} {0=9..14:b} {1=8..10:b}",
&bytes,
"0.000002 INFO bitfields 1010010 10001 11",
);
}
#[test]
fn bitfields_truncated_front() {
let bytes = [
0,
0, 2, 0b0110_0011, ];
decode_and_expect(
"bitfields {0=9..14:b}",
&bytes,
"0.000002 INFO bitfields 10001",
);
}
#[test]
fn bitfields_non_truncated_u32() {
let bytes = [
0,
0, 2, 0b0110_0011, 0b0000_1111, 0b0101_1010, 0b1100_0011, ];
decode_and_expect(
"bitfields {0=0..2:b} {0=28..31:b}",
&bytes,
"0.000002 INFO bitfields 11 100",
);
}
#[test]
fn bitfields_u128() {
let bytes = [
0,
0, 2, 0b1110_0101, 0b1110_0101, 0b0000_0000, 0b0000_0000, 0b0000_0000, 0b0000_0000, 0b0000_0000, 0b0000_0000, 0b0000_0000, 0b0000_0000, 0b0000_0000, 0b0000_0000, 0b0000_0000, 0b0000_0000, 0b0000_0000, 0b0000_0000, ];
decode_and_expect("x: {0=119..124:b}", &bytes, "0.000002 INFO x: 1011");
}
#[test]
fn slice() {
let bytes = [
0, 0, 2, 2, 0, 0, 0, 23, 42, ];
decode_and_expect("x={=[u8]}", &bytes, "0.000002 INFO x=[23, 42]");
}
#[test]
fn slice_with_trailing_args() {
let bytes = [
0, 0, 2, 2, 0, 0, 0, 23, 42, 1, ];
decode_and_expect(
"x={=[u8]} trailing arg={=u8}",
&bytes,
"0.000002 INFO x=[23, 42] trailing arg=1",
);
}
#[test]
fn string_hello_world() {
let bytes = [
0, 0, 2, 5, 0, 0, 0, b'W', b'o', b'r', b'l', b'd',
];
decode_and_expect("Hello {=str}", &bytes, "0.000002 INFO Hello World");
}
#[test]
fn string_with_trailing_data() {
let bytes = [
0, 0, 2, 5, 0, 0, 0, b'W', b'o', b'r', b'l', b'd', 125, ];
decode_and_expect(
"Hello {=str} {=u8}",
&bytes,
"0.000002 INFO Hello World 125",
);
}
#[test]
fn char_data() {
let bytes = [
0, 0, 2, 0x61, 0x00, 0x00, 0x00, 0x9C, 0xF4, 0x01, 0x00, ];
decode_and_expect(
"Supports ASCII {=char} and Unicode {=char}",
&bytes,
"0.000002 INFO Supports ASCII a and Unicode 💜",
);
}
#[test]
fn option() {
let mut entries = BTreeMap::new();
entries.insert(
4,
TableEntry::new_without_symbol(Tag::Info, "x={=?}".to_owned()),
);
entries.insert(
3,
TableEntry::new_without_symbol(Tag::Derived, "None|Some({=?})".to_owned()),
);
entries.insert(
2,
TableEntry::new_without_symbol(Tag::Derived, "{=u8}".to_owned()),
);
let table = Table {
entries,
timestamp: Some(TableEntry::new_without_symbol(
Tag::Timestamp,
"{=u8:us}".to_owned(),
)),
bitflags: Default::default(),
encoding: Encoding::Raw,
};
let bytes = [
4, 0, 0, 3, 0, 1, 2, 0, 42, ];
let frame = table.decode(&bytes).unwrap().0;
assert_eq!(frame.display(false).to_string(), "0.000000 INFO x=Some(42)");
assert_eq!(frame.display_message().to_string(), "x=Some(42)");
assert_eq!(
frame.display_fragments().collect::<Vec<String>>(),
["x=", "Some(42)"],
);
let bytes = [
4, 0, 1, 3, 0, 0, ];
let frame = table.decode(&bytes).unwrap().0;
assert_eq!(frame.display(false).to_string(), "0.000001 INFO x=None");
assert_eq!(frame.display_message().to_string(), "x=None");
assert_eq!(
frame.display_fragments().collect::<Vec<String>>(),
["x=", "None"],
);
}
}