use std::collections::VecDeque;
use std::io::{self, BufRead, Read, Write};
use crate::engine::RawFrame;
use crate::engine::format::actisense_ascii::write_line as write_actisense;
use crate::engine::format::ebl::encode_frame as encode_ebl;
use crate::engine::format::ngt1::{EblHeader, Ngt1Decoder, NgtEvent};
use crate::engine::format::plain::write_line as write_plain;
use crate::engine::format::ydwg02::write_line as write_ydwg02;
use crate::engine::format::{
InputFormat, detect, header_implies_coalesced, parse_format_header, parse_with,
};
pub use crate::engine::FrameSource as FrameReader;
pub trait FrameWriter {
fn write_frame(&mut self, frame: &RawFrame) -> io::Result<()>;
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
pub fn copy<R: FrameReader + ?Sized, W: FrameWriter + ?Sized>(
reader: &mut R,
writer: &mut W,
) -> io::Result<u64> {
let mut n = 0;
while let Some(frame) = reader.read_frame()? {
writer.write_frame(&frame)?;
n += 1;
}
writer.flush()?;
Ok(n)
}
pub struct LineFrameReader<R: BufRead> {
lines: crate::io::LineReader<R>,
active: Option<InputFormat>,
header_coalesced: bool,
}
impl<R: BufRead> LineFrameReader<R> {
pub fn new(reader: R) -> Self {
Self {
lines: crate::io::LineReader::new(reader),
active: None,
header_coalesced: false,
}
}
pub fn with_format(reader: R, format: InputFormat) -> Self {
Self {
lines: crate::io::LineReader::new(reader),
active: Some(format),
header_coalesced: false,
}
}
pub fn active_format(&self) -> Option<InputFormat> {
self.active
}
pub fn header_coalesced(&self) -> bool {
self.header_coalesced
}
}
impl<R: BufRead> FrameReader for LineFrameReader<R> {
fn read_frame(&mut self) -> io::Result<Option<RawFrame>> {
loop {
let Some(line) = self.lines.next_line()? else {
return Ok(None);
};
if line.is_empty() {
continue;
}
if line.starts_with('#') {
if let Some(fmt) = parse_format_header(line) {
if self.active.is_none() {
self.active = Some(fmt);
log::info!("input format set by header: {fmt:?}");
}
if header_implies_coalesced(line) {
self.header_coalesced = true;
log::debug!("header declares coalesced format; skipping reassembly");
}
}
continue;
}
if self.active.is_none() {
self.active = detect(line).or(Some(InputFormat::Plain));
log::debug!("input format: {:?}", self.active);
}
let format = self.active.expect("active format set above");
match parse_with(format, line) {
Ok(Some(frame)) => return Ok(Some(frame)),
Ok(None) => continue,
Err(crate::engine::format::PlainError::Empty) => continue,
Err(e) => {
log::warn!("skipping malformed input line: {e}");
continue;
}
}
}
}
}
pub struct PlainWriter<W: Write> {
inner: W,
buf: String,
}
impl<W: Write> PlainWriter<W> {
pub fn new(inner: W) -> Self {
Self {
inner,
buf: String::with_capacity(256),
}
}
}
impl<W: Write> FrameWriter for PlainWriter<W> {
fn write_frame(&mut self, frame: &RawFrame) -> io::Result<()> {
self.buf.clear();
write_plain(&mut self.buf, frame).expect("PLAIN write to String");
self.buf.push('\n');
self.inner.write_all(self.buf.as_bytes())
}
fn flush(&mut self) -> io::Result<()> {
self.inner.flush()
}
}
pub struct TextLineWriter<W: Write> {
inner: W,
buf: String,
encode: fn(&mut String, &RawFrame) -> std::fmt::Result,
}
impl<W: Write> TextLineWriter<W> {
pub fn ydwg02(inner: W) -> Self {
Self::new(inner, write_ydwg02)
}
pub fn actisense(inner: W) -> Self {
Self::new(inner, write_actisense)
}
fn new(inner: W, encode: fn(&mut String, &RawFrame) -> std::fmt::Result) -> Self {
Self {
inner,
buf: String::with_capacity(256),
encode,
}
}
}
impl<W: Write> FrameWriter for TextLineWriter<W> {
fn write_frame(&mut self, frame: &RawFrame) -> io::Result<()> {
self.buf.clear();
(self.encode)(&mut self.buf, frame).expect("text write to String");
self.buf.push('\n');
self.inner.write_all(self.buf.as_bytes())
}
fn flush(&mut self) -> io::Result<()> {
self.inner.flush()
}
}
pub struct EblWriter<W: Write> {
inner: W,
buf: Vec<u8>,
}
impl<W: Write> EblWriter<W> {
pub fn new(inner: W) -> Self {
Self {
inner,
buf: Vec::with_capacity(256),
}
}
}
impl<W: Write> FrameWriter for EblWriter<W> {
fn write_frame(&mut self, frame: &RawFrame) -> io::Result<()> {
self.buf.clear();
encode_ebl(frame, &mut self.buf);
self.inner.write_all(&self.buf)
}
fn flush(&mut self) -> io::Result<()> {
self.inner.flush()
}
}
pub struct EblReader<R: Read> {
inner: R,
decoder: Ngt1Decoder,
queue: VecDeque<RawFrame>,
pending_ts: Option<u64>,
buf: Box<[u8; 8192]>,
eof: bool,
}
impl<R: Read> EblReader<R> {
pub fn new(inner: R) -> Self {
Self {
inner,
decoder: Ngt1Decoder::with_ebl(),
queue: VecDeque::new(),
pending_ts: None,
buf: Box::new([0u8; 8192]),
eof: false,
}
}
}
impl<R: Read> FrameReader for EblReader<R> {
fn read_frame(&mut self) -> io::Result<Option<RawFrame>> {
loop {
if let Some(frame) = self.queue.pop_front() {
return Ok(Some(frame));
}
if self.eof {
return Ok(None);
}
let n = self.inner.read(&mut self.buf[..])?;
if n == 0 {
self.eof = true;
continue;
}
for ev in self.decoder.push_bytes(&self.buf[..n]) {
match ev {
NgtEvent::Header(EblHeader::Timestamp(ms)) => self.pending_ts = Some(ms),
NgtEvent::Message(m) => {
if let Some(mut frame) = m.to_raw_frame() {
if let Some(ms) = self.pending_ts.take() {
frame.timestamp = Some(crate::engine::format_iso_ms(ms));
}
self.queue.push_back(frame);
}
}
NgtEvent::Header(_) | NgtEvent::Error(_) => {}
}
}
}
}
}
impl FrameWriter for crate::io::device::FrameSender {
fn write_frame(&mut self, frame: &RawFrame) -> io::Result<()> {
self.send_frame(frame.clone())
.map_err(|e| io::Error::new(io::ErrorKind::BrokenPipe, e))
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
#[test]
fn line_reader_yields_frames_and_skips_noise() {
let input = "\
# format=PLAIN
2022-09-10T12:10:16.614Z,6,60928,5,255,8,fb,9b,70,22,00,9b,50,c0
2022-09-10T12:10:16.700Z,6,60928,6,255,8,01,02,03,04,05,06,07,08
";
let mut r = LineFrameReader::new(Cursor::new(input));
let f1 = r.read_frame().unwrap().expect("first frame");
assert_eq!(f1.pgn, 60928);
assert_eq!(f1.src, 5);
let f2 = r.read_frame().unwrap().expect("second frame");
assert_eq!(f2.src, 6);
assert!(r.read_frame().unwrap().is_none());
assert_eq!(r.active_format(), Some(InputFormat::Plain));
}
#[test]
fn plain_round_trips_through_copy() {
let input = "2022-09-10T12:10:16.614Z,6,60928,5,255,8,fb,9b,70,22,00,9b,50,c0\n";
let mut reader = LineFrameReader::new(Cursor::new(input));
let mut out: Vec<u8> = Vec::new();
let n = copy(&mut reader, &mut PlainWriter::new(&mut out)).unwrap();
assert_eq!(n, 1);
let mut back = LineFrameReader::new(Cursor::new(out));
let f = back.read_frame().unwrap().unwrap();
assert_eq!(f.pgn, 60928);
assert_eq!(f.src, 5);
assert_eq!(f.dst, 255);
assert_eq!(
&f.data[..],
&[0xfb, 0x9b, 0x70, 0x22, 0x00, 0x9b, 0x50, 0xc0]
);
}
#[test]
fn header_coalesced_is_reported() {
let input = "# format=FAST\n";
let mut r = LineFrameReader::new(Cursor::new(input));
assert!(r.read_frame().unwrap().is_none());
assert!(r.header_coalesced());
}
}