use crate::{protocol, Ctx, Progress};
use anyhow::{Context, Result};
use rawlink::Link;
use std::fmt::Write as _;
use std::time::{Duration, Instant};
const RECV_TIMEOUT: Duration = Duration::from_millis(500);
pub fn is_sender_frame(d: &[u8]) -> bool {
d.len() >= 14 && d[0..6] == protocol::CARD_MAC
}
pub fn is_card_frame(d: &[u8]) -> bool {
d.len() >= 14 && d[6..12] == protocol::CARD_MAC
}
pub fn is_our_frame(d: &[u8]) -> bool {
d.len() >= 12 && d[6..12] == protocol::SENDER_MAC
}
pub fn open(ctx: &Ctx) -> Result<Link> {
Link::open(&ctx.iface, RECV_TIMEOUT)
}
pub fn await_reply<T>(
dev: &mut Link,
wait: Duration,
mut pick: impl FnMut(&[u8]) -> Option<T>,
) -> Result<Option<T>> {
await_any_frame(dev, wait, |f| if is_card_frame(f) { pick(f) } else { None })
}
pub fn await_any_frame<T>(
dev: &mut Link,
wait: Duration,
mut pick: impl FnMut(&[u8]) -> Option<T>,
) -> Result<Option<T>> {
let deadline = Instant::now() + wait;
while Instant::now() < deadline {
for f in dev.recv()? {
if let Some(v) = pick(f) {
return Ok(Some(v));
}
}
}
Ok(None)
}
const LATTICE: &[u8] = b"Lattice Semiconductor";
pub fn has_lattice_header(img: &[u8]) -> bool {
img.windows(LATTICE.len()).take(256).any(|w| w == LATTICE)
}
pub fn contains_lattice_header(d: &[u8]) -> bool {
d.windows(LATTICE.len()).any(|w| w == LATTICE)
}
pub fn parse_color(parts: &[String]) -> Result<[u8; 3]> {
match parts {
[hex] => {
let hex = hex.trim_start_matches('#');
anyhow::ensure!(hex.len() == 6, "expected RRGGBB hex or three 0-255 values");
let v = u32::from_str_radix(hex, 16).context("bad hex color")?;
Ok([(v >> 16) as u8, (v >> 8) as u8, v as u8])
}
[r, g, b] => Ok([r.parse()?, g.parse()?, b.parse()?]),
_ => anyhow::bail!("expected RRGGBB hex or three 0-255 values"),
}
}
pub fn hex(bytes: &[u8], sep: &str) -> String {
let mut s = String::with_capacity(bytes.len() * (2 + sep.len()));
for (i, b) in bytes.iter().enumerate() {
if i > 0 {
s.push_str(sep);
}
let _ = write!(s, "{b:02x}");
}
s
}
pub fn warn(p: &mut dyn Progress, msg: impl std::fmt::Display) {
p.err(&format!("rxp: warning: {msg}"));
}
pub fn hexdump(p: &mut dyn Progress, data: &[u8]) {
for (i, chunk) in data.chunks(16).enumerate() {
p.out(&format!(" {:04x}: {}", i * 16, hex(chunk, " ")));
}
}
pub fn mac(b: &[u8]) -> String {
hex(b, ":")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn hex_matches_the_per_byte_format_and_join() {
let bytes: Vec<u8> = (0..=255).collect();
let joined = |sep: &str| {
bytes
.iter()
.map(|b| format!("{b:02x}"))
.collect::<Vec<_>>()
.join(sep)
};
assert_eq!(hex(&bytes, " "), joined(" "));
assert_eq!(hex(&bytes, ":"), joined(":"));
assert_eq!(hex(&[], " "), "");
assert_eq!(
mac(&[0x11, 0x22, 0x33, 0x44, 0x55, 0x66]),
"11:22:33:44:55:66"
);
}
#[test]
fn a_lattice_header_is_only_found_near_the_start() {
let mut img = vec![0u8; 600];
img[100..121].copy_from_slice(LATTICE);
assert!(has_lattice_header(&img));
assert!(contains_lattice_header(&img));
let mut late = vec![0u8; 600];
late[300..321].copy_from_slice(LATTICE);
assert!(!has_lattice_header(&late));
assert!(contains_lattice_header(&late));
}
}