1use crate::{protocol, Ctx, Progress};
4use anyhow::{Context, Result};
5use rawlink::Link;
6use std::fmt::Write as _;
7use std::time::{Duration, Instant};
8
9const RECV_TIMEOUT: Duration = Duration::from_millis(500);
11
12pub fn is_sender_frame(d: &[u8]) -> bool {
14 d.len() >= 14 && d[0..6] == protocol::CARD_MAC
15}
16
17pub fn is_card_frame(d: &[u8]) -> bool {
19 d.len() >= 14 && d[6..12] == protocol::CARD_MAC
20}
21
22pub fn is_our_frame(d: &[u8]) -> bool {
24 d.len() >= 12 && d[6..12] == protocol::SENDER_MAC
25}
26
27pub fn open(ctx: &Ctx) -> Result<Link> {
32 Link::open(&ctx.iface, RECV_TIMEOUT)
33}
34
35pub fn await_reply<T>(
37 dev: &mut Link,
38 wait: Duration,
39 mut pick: impl FnMut(&[u8]) -> Option<T>,
40) -> Result<Option<T>> {
41 await_any_frame(dev, wait, |f| if is_card_frame(f) { pick(f) } else { None })
42}
43
44pub fn await_any_frame<T>(
46 dev: &mut Link,
47 wait: Duration,
48 mut pick: impl FnMut(&[u8]) -> Option<T>,
49) -> Result<Option<T>> {
50 let deadline = Instant::now() + wait;
51 while Instant::now() < deadline {
52 for f in dev.recv()? {
53 if let Some(v) = pick(f) {
54 return Ok(Some(v));
55 }
56 }
57 }
58 Ok(None)
59}
60
61const LATTICE: &[u8] = b"Lattice Semiconductor";
62
63pub fn has_lattice_header(img: &[u8]) -> bool {
65 img.windows(LATTICE.len()).take(256).any(|w| w == LATTICE)
66}
67
68pub fn contains_lattice_header(d: &[u8]) -> bool {
70 d.windows(LATTICE.len()).any(|w| w == LATTICE)
71}
72
73pub fn parse_color(parts: &[String]) -> Result<[u8; 3]> {
75 match parts {
76 [hex] => {
77 let hex = hex.trim_start_matches('#');
78 anyhow::ensure!(hex.len() == 6, "expected RRGGBB hex or three 0-255 values");
79 let v = u32::from_str_radix(hex, 16).context("bad hex color")?;
80 Ok([(v >> 16) as u8, (v >> 8) as u8, v as u8])
81 }
82 [r, g, b] => Ok([r.parse()?, g.parse()?, b.parse()?]),
83 _ => anyhow::bail!("expected RRGGBB hex or three 0-255 values"),
84 }
85}
86
87pub fn hex(bytes: &[u8], sep: &str) -> String {
89 let mut s = String::with_capacity(bytes.len() * (2 + sep.len()));
90 for (i, b) in bytes.iter().enumerate() {
91 if i > 0 {
92 s.push_str(sep);
93 }
94 let _ = write!(s, "{b:02x}");
95 }
96 s
97}
98
99pub fn warn(p: &mut dyn Progress, msg: impl std::fmt::Display) {
101 p.err(&format!("rxp: warning: {msg}"));
102}
103
104pub fn hexdump(p: &mut dyn Progress, data: &[u8]) {
105 for (i, chunk) in data.chunks(16).enumerate() {
106 p.out(&format!(" {:04x}: {}", i * 16, hex(chunk, " ")));
107 }
108}
109
110pub fn mac(b: &[u8]) -> String {
112 hex(b, ":")
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118
119 #[test]
120 fn hex_matches_the_per_byte_format_and_join() {
121 let bytes: Vec<u8> = (0..=255).collect();
122 let joined = |sep: &str| {
123 bytes
124 .iter()
125 .map(|b| format!("{b:02x}"))
126 .collect::<Vec<_>>()
127 .join(sep)
128 };
129 assert_eq!(hex(&bytes, " "), joined(" "));
130 assert_eq!(hex(&bytes, ":"), joined(":"));
131 assert_eq!(hex(&[], " "), "");
132 assert_eq!(
133 mac(&[0x11, 0x22, 0x33, 0x44, 0x55, 0x66]),
134 "11:22:33:44:55:66"
135 );
136 }
137
138 #[test]
139 fn a_lattice_header_is_only_found_near_the_start() {
140 let mut img = vec![0u8; 600];
141 img[100..121].copy_from_slice(LATTICE);
142 assert!(has_lattice_header(&img));
143 assert!(contains_lattice_header(&img));
144 let mut late = vec![0u8; 600];
145 late[300..321].copy_from_slice(LATTICE);
146 assert!(!has_lattice_header(&late));
147 assert!(contains_lattice_header(&late));
148 }
149}