use std::{
io::{stdout, ErrorKind, Write},
time::Duration,
};
use crossterm::{
event::{poll, read, Event, KeyCode, KeyEvent, KeyModifiers},
style::{Color, Print, PrintStyledContent, Stylize},
terminal::{disable_raw_mode, enable_raw_mode},
QueueableCommand,
};
use lazy_static::lazy_static;
use log::error;
use miette::{IntoDiagnostic, Result};
use regex::Regex;
use self::{line_endings::normalized, symbols::Symbols};
use crate::{connection::reset_after_flash, interface::Interface};
mod line_endings;
mod symbols;
lazy_static! {
static ref RE_FN_ADDR: Regex = Regex::new(r"0x[[:xdigit:]]{8}").unwrap();
}
#[derive(Default)]
struct SerialContext<'ctx> {
symbols: Option<Symbols<'ctx>>,
previous_frag: Option<String>,
previous_line: Option<String>,
}
impl<'ctx> SerialContext<'ctx> {
fn new(symbols: Option<Symbols<'ctx>>) -> Self {
Self {
symbols,
..Self::default()
}
}
}
struct RawModeGuard;
impl RawModeGuard {
pub fn new() -> Result<Self> {
enable_raw_mode().into_diagnostic()?;
Ok(RawModeGuard)
}
}
impl Drop for RawModeGuard {
fn drop(&mut self) {
if let Err(e) = disable_raw_mode() {
error!("{:#}", e)
}
}
}
pub fn monitor(
mut serial: Interface,
elf: Option<&[u8]>,
pid: u16,
baud: u32,
) -> serialport::Result<()> {
println!("Commands:");
println!(" CTRL+R Reset chip");
println!(" CTRL+C Exit");
println!();
serial.serial_port_mut().set_baud_rate(baud)?;
serial
.serial_port_mut()
.set_timeout(Duration::from_millis(5))?;
let symbols = if let Some(bytes) = elf {
Symbols::try_from(bytes).ok()
} else {
None
};
let mut ctx = SerialContext::new(symbols);
let _raw_mode = RawModeGuard::new();
let stdout = stdout();
let mut stdout = stdout.lock();
let mut buff = [0; 1024];
loop {
let read_count = match serial.serial_port_mut().read(&mut buff) {
Ok(count) => Ok(count),
Err(e) if e.kind() == ErrorKind::TimedOut => Ok(0),
Err(e) if e.kind() == ErrorKind::Interrupted => continue,
err => err,
}?;
if read_count > 0 {
handle_serial(&mut ctx, &buff[0..read_count], &mut stdout);
}
if poll(Duration::from_secs(0))? {
if let Event::Key(key) = read()? {
if key.modifiers.contains(KeyModifiers::CONTROL) {
match key.code {
KeyCode::Char('c') => break,
KeyCode::Char('r') => {
reset_after_flash(&mut serial, pid)?;
continue;
}
_ => {}
}
}
if let Some(bytes) = handle_key_event(key) {
serial.serial_port_mut().write_all(&bytes)?;
serial.serial_port_mut().flush()?;
}
}
}
}
Ok(())
}
fn handle_serial(ctx: &mut SerialContext, buff: &[u8], out: &mut dyn Write) {
let text: Vec<u8> = normalized(buff.iter().copied()).collect();
let text = String::from_utf8_lossy(&text).to_string();
let mut lines = text.lines().collect::<Vec<_>>();
let incomplete = if text.ends_with('\n') {
None
} else {
lines.pop()
};
for line in lines {
out.queue(Print(line)).ok();
ctx.previous_line = if let Some(frag) = &ctx.previous_frag {
Some(format!("{frag}{line}"))
} else {
Some(line.to_string())
};
ctx.previous_frag = None;
if let Some(symbols) = &ctx.symbols {
if let Some(line) = &ctx.previous_line {
for matched in RE_FN_ADDR.find_iter(line).map(|m| m.as_str()) {
let addr = parse_int::parse::<u64>(matched).unwrap();
let name = symbols.get_name(addr).unwrap_or_else(|| "??".into());
let (file, line_num) =
if let Some((file, line_num)) = symbols.get_location(addr) {
(file, line_num.to_string())
} else {
("??".into(), "??".into())
};
out.queue(PrintStyledContent(
format!("\r\n{matched} - {name}\r\n at {file}:{line_num}")
.with(Color::Yellow),
))
.unwrap();
}
}
}
out.write_all(b"\r\n").ok();
}
if let Some(line) = incomplete {
out.queue(Print(line)).ok();
if let Some(frag) = &ctx.previous_frag {
ctx.previous_frag = Some(format!("{frag}{line}"));
} else {
ctx.previous_frag = Some(line.to_string());
}
}
out.flush().ok();
}
fn handle_key_event(key_event: KeyEvent) -> Option<Vec<u8>> {
let mut buf = [0; 4];
let key_str: Option<&[u8]> = match key_event.code {
KeyCode::Backspace => Some(b"\x08"),
KeyCode::Enter => Some(b"\r"),
KeyCode::Left => Some(b"\x1b[D"),
KeyCode::Right => Some(b"\x1b[C"),
KeyCode::Home => Some(b"\x1b[H"),
KeyCode::End => Some(b"\x1b[F"),
KeyCode::Up => Some(b"\x1b[A"),
KeyCode::Down => Some(b"\x1b[B"),
KeyCode::Tab => Some(b"\x09"),
KeyCode::Delete => Some(b"\x1b[3~"),
KeyCode::Insert => Some(b"\x1b[2~"),
KeyCode::Esc => Some(b"\x1b"),
KeyCode::Char(ch) => {
if key_event.modifiers & KeyModifiers::CONTROL == KeyModifiers::CONTROL {
buf[0] = ch as u8;
if ('a'..='z').contains(&ch) || (ch == ' ') {
buf[0] &= 0x1f;
Some(&buf[0..1])
} else if ('4'..='7').contains(&ch) {
buf[0] = (buf[0] + 8) & 0x1f;
Some(&buf[0..1])
} else {
Some(ch.encode_utf8(&mut buf).as_bytes())
}
} else {
Some(ch.encode_utf8(&mut buf).as_bytes())
}
}
_ => None,
};
key_str.map(|slice| slice.into())
}