use anyhow::{Context, anyhow};
use defmt_decoder::{
DecodeError, StreamDecoder,
log::format::{Formatter, FormatterConfig, FormatterFormat},
};
use probe_rs::rtt::Error;
use time::{OffsetDateTime, UtcOffset, macros::format_description};
use std::{
fmt::{self, Write},
sync::Arc,
};
use crate::util::rtt::DataFormat;
pub enum RttDecoder {
String {
timestamp_offset: Option<UtcOffset>,
last_line_done: bool,
show_timestamps: bool,
},
BinaryLE,
Defmt {
processor: DefmtProcessor,
},
}
impl From<&RttDecoder> for DataFormat {
fn from(config: &RttDecoder) -> Self {
match config {
RttDecoder::String { .. } => DataFormat::String,
RttDecoder::BinaryLE => DataFormat::BinaryLE,
RttDecoder::Defmt { .. } => DataFormat::Defmt,
}
}
}
impl fmt::Debug for RttDecoder {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RttDecoder::String {
timestamp_offset,
last_line_done,
show_timestamps,
} => f
.debug_struct("String")
.field("timestamp_offset", timestamp_offset)
.field("last_line_done", last_line_done)
.field("show_timestamps", show_timestamps)
.finish(),
RttDecoder::BinaryLE => f.debug_struct("BinaryLE").finish(),
RttDecoder::Defmt { .. } => f.debug_struct("Defmt").finish_non_exhaustive(),
}
}
}
impl RttDecoder {
pub fn is_binary(&self) -> bool {
matches!(self, RttDecoder::BinaryLE)
}
pub async fn process(
&mut self,
buffer: &[u8],
collector: &mut impl RttDataHandler,
) -> Result<(), Error> {
if buffer.is_empty() {
return Ok(());
}
match self {
RttDecoder::BinaryLE => collector.on_binary_data(buffer).await,
RttDecoder::String {
timestamp_offset,
last_line_done,
show_timestamps,
} => {
let string = Self::process_string(
buffer,
*timestamp_offset,
last_line_done,
*show_timestamps,
)?;
collector.on_string_data(string).await
}
RttDecoder::Defmt { processor } => {
let string = processor.process(buffer)?;
collector.on_string_data(string).await
}
}
}
fn process_string(
buffer: &[u8],
offset: Option<UtcOffset>,
last_line_done: &mut bool,
show_timestamps: bool,
) -> Result<String, Error> {
let incoming = String::from_utf8_lossy(buffer);
if !show_timestamps {
return Ok(incoming.to_string());
}
let Some(offset) = offset else {
return Ok(incoming.to_string());
};
let timestamp = OffsetDateTime::now_utc()
.to_offset(offset)
.format(format_description!(
"[hour repr:24]:[minute]:[second].[subsecond digits:3]"
))
.expect("Incorrect format string. This shouldn't happen.");
let mut formatted_data = String::new();
for line in incoming.split_inclusive('\n') {
if *last_line_done {
write!(formatted_data, "{timestamp}: ").expect("Writing to String cannot fail");
}
write!(formatted_data, "{line}").expect("Writing to String cannot fail");
*last_line_done = line.ends_with('\n');
}
Ok(formatted_data)
}
}
pub trait RttDataHandler {
async fn on_binary_data(&mut self, data: &[u8]) -> Result<(), Error> {
let mut formatted_data = String::with_capacity(data.len() * 4);
for element in data {
write!(&mut formatted_data, "{element:#04x}").expect("Writing to String cannot fail");
}
self.on_string_data(formatted_data).await
}
async fn on_string_data(&mut self, data: String) -> Result<(), Error>;
}
pub struct DefmtStateInner {
pub table: defmt_decoder::Table,
pub locs: Option<defmt_decoder::Locations>,
}
impl DefmtStateInner {
pub fn try_from_bytes(buffer: &[u8]) -> Result<Option<Self>, Error> {
let Some(table) =
defmt_decoder::Table::parse(buffer).with_context(|| "Failed to parse defmt data")?
else {
return Ok(None);
};
let locs = table
.get_locations(buffer)
.with_context(|| "Failed to parse defmt data")?;
let locs = if !table.is_empty() && locs.is_empty() {
tracing::warn!(
"Insufficient DWARF info; compile your program with `debug = 2` to enable location info."
);
None
} else if table.indices().all(|idx| locs.contains_key(&(idx as u64))) {
Some(locs)
} else {
tracing::warn!("Location info is incomplete; it will be omitted from the output.");
None
};
Ok(Some(DefmtStateInner { table, locs }))
}
}
#[derive(Clone)]
pub struct DefmtState {
inner: Arc<DefmtStateInner>,
}
impl DefmtState {
pub fn try_from_bytes(buffer: &[u8]) -> Result<Option<Self>, Error> {
Ok(
DefmtStateInner::try_from_bytes(buffer)?.map(|inner| DefmtState {
inner: Arc::new(inner),
}),
)
}
fn as_ref(&self) -> &DefmtStateInner {
&self.inner
}
pub fn table(&self) -> &defmt_decoder::Table {
&self.inner.table
}
}
impl fmt::Debug for DefmtState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("DefmtState").finish_non_exhaustive()
}
}
pub struct DefmtProcessor {
formatter: Formatter,
decoder: Box<dyn StreamDecoder>,
defmt_data: DefmtState,
}
impl DefmtProcessor {
pub fn new(
defmt_data: DefmtState,
show_timestamps: bool,
show_location: bool,
log_format: Option<&str>,
) -> Self {
let has_timestamp = defmt_data.table().has_timestamp();
let format = match log_format {
None | Some("oneline") => FormatterFormat::OneLine {
with_location: show_location,
},
Some("full") => FormatterFormat::Default {
with_location: show_location,
},
Some(format) => FormatterFormat::Custom(format),
};
Self {
formatter: Formatter::new(FormatterConfig {
format,
is_timestamp_available: has_timestamp && show_timestamps,
}),
decoder: unsafe {
std::mem::transmute::<Box<dyn StreamDecoder>, Box<dyn StreamDecoder + 'static>>(
defmt_data.as_ref().table.new_stream_decoder(),
)
},
defmt_data: defmt_data.clone(),
}
}
fn process(&mut self, buffer: &[u8]) -> Result<String, Error> {
let DefmtStateInner { table, locs } = self.defmt_data.as_ref();
self.decoder.received(buffer);
let mut formatted_data = String::new();
loop {
match self.decoder.decode() {
Ok(frame) => {
let loc = locs.as_ref().and_then(|locs| locs.get(&frame.index()));
let (file, line, module) = if let Some(loc) = loc {
(
loc.file.display().to_string(),
Some(loc.line.try_into().unwrap()),
Some(loc.module.as_str()),
)
} else {
(
format!(
"└─ <invalid location: defmt frame-index: {}>",
frame.index()
),
None,
None,
)
};
let s = self
.formatter
.format_frame(frame, Some(&file), line, module);
writeln!(formatted_data, "{s}").expect("Writing to String cannot fail");
}
Err(DecodeError::UnexpectedEof) => break,
Err(DecodeError::Malformed) if table.encoding().can_recover() => {
}
Err(DecodeError::Malformed) => {
return Err(Error::Other(anyhow!(
"Unrecoverable error while decoding Defmt \
data. Some data may have been lost: {}",
DecodeError::Malformed
)));
}
}
}
Ok(formatted_data)
}
}