Skip to main content

ops/
util.rs

1//! Small helpers shared across the command modules.
2
3use crate::{protocol, Ctx, Progress};
4use anyhow::{Context, Result};
5use rawlink::Link;
6use std::fmt::Write as _;
7use std::time::{Duration, Instant};
8
9/// How long one `recv` poll blocks before returning empty-handed.
10const RECV_TIMEOUT: Duration = Duration::from_millis(500);
11
12/// True for frames sent by the sender/PC to the card.
13pub fn is_sender_frame(d: &[u8]) -> bool {
14    d.len() >= 14 && d[0..6] == protocol::CARD_MAC
15}
16
17/// True for frames sent by the card back to the PC.
18pub fn is_card_frame(d: &[u8]) -> bool {
19    d.len() >= 14 && d[6..12] == protocol::CARD_MAC
20}
21
22/// True for our own transmissions, which the kernel loops back to us.
23pub fn is_our_frame(d: &[u8]) -> bool {
24    d.len() >= 12 && d[6..12] == protocol::SENDER_MAC
25}
26
27/// Open the raw link on the context's interface.
28///
29/// # Errors
30/// Fails if the interface cannot be opened.
31pub fn open(ctx: &Ctx) -> Result<Link> {
32    Link::open(&ctx.iface, RECV_TIMEOUT)
33}
34
35/// Poll `dev` until `wait` runs out or `pick` accepts a frame from the card.
36pub 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
44/// Like [`await_reply`] but offers every frame, not only the card's.
45pub 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
63/// True when a bitstream's text header sits within its first 256 bytes.
64pub fn has_lattice_header(img: &[u8]) -> bool {
65    img.windows(LATTICE.len()).take(256).any(|w| w == LATTICE)
66}
67
68/// True when a bitstream header appears anywhere in `d`.
69pub fn contains_lattice_header(d: &[u8]) -> bool {
70    d.windows(LATTICE.len()).any(|w| w == LATTICE)
71}
72
73/// `RRGGBB` (optionally `#`-prefixed) or three decimal components.
74pub 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
87/// Two lowercase hex digits per byte, `sep` between them.
88pub 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
99/// A non-fatal problem, on stderr in the same shape as an error.
100pub 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
110/// Format a MAC address for display.
111pub 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}