use std::fs::File;
use std::io::Write;
use std::path::Path;
use crate::error::Result;
pub struct Recorder {
file: File,
failed: Option<std::io::Error>,
}
impl Recorder {
pub fn create(path: &Path, device: Option<&str>) -> Result<Self> {
let mut file = File::create(path)?;
writeln!(
file,
"# nord-usb replay script, recorded from hardware.\n\
# Format: '<O|I> <hex>' -- O = host->device, I = device->host.\n\
# source: nord"
)?;
if let Some(device) = device {
writeln!(file, "# device: {device}")?;
}
Ok(Self { file, failed: None })
}
pub fn intent(&mut self, intent: &str) {
if self.failed.is_some() {
return;
}
if let Err(e) = writeln!(self.file, "\n# intent: {intent}") {
self.failed = Some(e);
}
}
pub fn out(&mut self, bytes: &[u8]) {
self.line('O', bytes);
}
pub fn r#in(&mut self, bytes: &[u8]) {
self.line('I', bytes);
}
pub fn expect(&mut self, e: &crate::error::Error) {
if self.failed.is_some() {
return;
}
if let Err(io) = writeln!(self.file, "# expect: err {}", e.expect_kind()) {
self.failed = Some(io);
}
}
pub fn check(&mut self) -> Result<()> {
match self.failed.take() {
Some(e) => Err(e.into()),
None => Ok(()),
}
}
fn line(&mut self, tag: char, bytes: &[u8]) {
if self.failed.is_some() {
return;
}
let mut hex = String::with_capacity(bytes.len() * 2);
for b in bytes {
hex.push_str(&format!("{b:02x}"));
}
if let Err(e) = writeln!(self.file, "{tag} {hex}") {
self.failed = Some(e);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_frame_that_could_not_be_written_is_reported_by_the_check() {
let path = std::env::temp_dir().join(format!("nord-record-{}.script", std::process::id()));
File::create(&path).expect("the script path is writable");
let unwritable = File::open(&path).expect("reopening it read-only");
let mut recorder = Recorder {
file: unwritable,
failed: None,
};
recorder.out(&[0x00, 0x11]);
let err = recorder
.check()
.expect_err("the frame never reached the script");
assert!(matches!(err, crate::error::Error::Io(_)), "{err}");
assert!(
recorder.check().is_ok(),
"a reported failure is not reported twice"
);
std::fs::remove_file(&path).ok();
}
}