Skip to content

Commit

Permalink
Simplify using esp-println's framing
Browse files Browse the repository at this point in the history
  • Loading branch information
bugadani committed Aug 26, 2023
1 parent 865f4a4 commit 0fc516b
Showing 1 changed file with 34 additions and 112 deletions.
146 changes: 34 additions & 112 deletions espflash/src/cli/monitor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use crossterm::{
terminal::{disable_raw_mode, enable_raw_mode},
QueueableCommand,
};
use defmt_decoder::{DecodeError, Frame, Table};
use defmt_decoder::{Frame, StreamDecoder, Table};
use lazy_static::lazy_static;
use log::error;
use miette::{IntoDiagnostic, Result};
Expand Down Expand Up @@ -78,127 +78,47 @@ enum FrameKind<'a> {
Raw(&'a [u8]),
}

struct FrameDelimiter {
struct FrameDelimiter<'a> {
buffer: Vec<u8>,
table: Option<Table>,
started: bool,
decoder: Option<Box<dyn StreamDecoder + 'a>>,
in_frame: bool,
}

impl FrameDelimiter {
const FRAME_START: &[u8] = &[0xFF, 0x00];
const FRAME_END: &[u8] = &[0xFC, 0x00];

fn search(haystack: &[u8], look_for_end: bool) -> Option<(&[u8], usize)> {
let needle = if look_for_end { FRAME_END } else { FRAME_START };
haystack
.windows(needle.len())
.position(|window| window == FRAME_START || window == FRAME_END)
.map(|pos| (&haystack[pos..][..2], pos))
}

impl FrameDelimiter<'_> {
pub fn feed(&mut self, buffer: &[u8], mut process: impl FnMut(FrameKind<'_>)) {
let Some(table) = self.table.as_mut() else {
let Some(table) = self.decoder.as_mut() else {
process(FrameKind::Raw(buffer));
return;
};

let mut buffer = buffer;
let mut retry = false;
while !buffer.is_empty() || retry {
retry = false;
// try to break buffer into 0-bounded frames
let end_of_frame = buffer.iter().position(|&x| x == 0);
let Some(pos) = end_of_frame else {
// we either have a frame open already, or we are in serial output mode
// either case, we consume everything so we can break out of the loop

self.buffer.extend_from_slice(buffer);
return;
};

let consumed = pos + 1; // also take the trailing 0 byte
let (frame_bytes, rest) = buffer.split_at(consumed);
buffer = rest;

// Print bootloader output as is
if !self.started {
self.started = true;
self.buffer.extend_from_slice(frame_bytes);
process(FrameKind::Raw(&self.buffer));
self.buffer.clear();
continue;
}
self.buffer.extend_from_slice(buffer);

let frame_bytes = if !self.buffer.is_empty() {
self.buffer.extend_from_slice(frame_bytes);
self.buffer.as_slice()
while let Some((delimiter, pos)) = search(&self.buffer, self.in_frame) {
let frame = &self.buffer[..pos];
if delimiter == FRAME_START {
process(FrameKind::Raw(frame));
} else {
frame_bytes
};

// we have a complete frame, try to decode it
let mut decoder = table.new_stream_decoder();
decoder.received(frame_bytes);

match decoder.decode() {
Ok(frame) => process(FrameKind::Defmt(frame)),

Err(DecodeError::UnexpectedEof) => unreachable!(),
Err(DecodeError::Malformed) => try_parse_mixed(&mut process, table, frame_bytes),
}

// We ended on a 0 byte, and we have processed everything one way or another.
self.buffer.clear();
}
}
}

fn try_parse_mixed(process: &mut impl FnMut(FrameKind<'_>), table: &Table, frame_bytes: &[u8]) {
// We have a frame that mixes regular serial output and a defmt frame in the end.
// We walk backwards and try to decode the frame that starts at our moving index.
// If deconding is successful, we move backwards some more.
// We stop when we don't find more starting points.

// This is a weird and probably not very fast heuristic, that assumes ASCII
// strings will rarely contain valid defmt frames and that a defmt frame will rarely be valid
// ASCII.

let mut frame_start = frame_bytes.len();
let mut candidate_frame_start = frame_start - 2;

let is_valid_frame = |frame_bytes| {
let mut decoder = table.new_stream_decoder();
decoder.received(frame_bytes);
match decoder.decode() {
Ok(frame) => frame.display_message().to_string().len() > frame_bytes.len(),
Err(_) => false,
}
};

let assume_text = |frame_bytes: &[u8]| {
frame_bytes
.iter()
.any(|b| *b > 0x20 || [b'\n', b'\r', b'\t'].contains(b))
};

let mut first_valid = true;

// We're checking as far back as there can be a valid Rzcobs run, which is
// 134 data bytes and a 0xFF.
while candidate_frame_start >= frame_start.saturating_sub(135) {
if is_valid_frame(&frame_bytes[candidate_frame_start..]) {
// Assume a frame can't be valid ASCII.
if first_valid || !assume_text(&frame_bytes[candidate_frame_start..frame_start]) {
frame_start = candidate_frame_start;
first_valid = false;
table.received(frame);
if let Ok(frame) = table.decode() {
process(FrameKind::Defmt(frame));
} else {
log::warn!("Failed to decode defmt frame");
log::debug!("Frame contents: {:02X?}", frame);
}
}
self.buffer.drain(..pos + delimiter.len());
}

if candidate_frame_start == 0 {
break;
}

candidate_frame_start -= 1;
}

let (raw, frame) = frame_bytes.split_at(frame_start);
if !raw.is_empty() {
process(FrameKind::Raw(raw));
}

let mut decoder = table.new_stream_decoder();
decoder.received(frame);
if let Ok(frame) = decoder.decode() {
process(FrameKind::Defmt(frame));
}
}

Expand Down Expand Up @@ -258,8 +178,10 @@ pub fn monitor(

let mut delimiter = FrameDelimiter {
buffer: Vec::new(),
table: defmt_encoding.map(|(table, _)| table),
started: false,
decoder: defmt_encoding
.as_ref()
.map(|(table, _)| table.new_stream_decoder()),
in_frame: false,
};

loop {
Expand Down

0 comments on commit 0fc516b

Please sign in to comment.