1use crate::util::{
4 await_any_frame, await_reply, hexdump, is_card_frame, is_our_frame, is_sender_frame, mac, open,
5};
6use crate::{protocol, Ctx, Progress};
7use anyhow::{Context, Result};
8use rawlink::{read_pcap, PcapPacket};
9use std::time::{Duration, Instant};
10
11pub fn parse_hex(s: &str) -> Result<Vec<u8>> {
12 let clean: String = s
13 .chars()
14 .filter(|c| !c.is_whitespace() && *c != ',')
15 .collect();
16 anyhow::ensure!(
17 clean.len().is_multiple_of(2),
18 "hex string must have an even length"
19 );
20 (0..clean.len())
21 .step_by(2)
22 .map(|i| u8::from_str_radix(&clean[i..i + 2], 16).context("bad hex"))
23 .collect()
24}
25
26pub fn raw_send(
28 ctx: &Ctx,
29 ty: &str,
30 payload: &str,
31 pad: usize,
32 wait: u64,
33 show: usize,
34 p: &mut dyn Progress,
35) -> Result<()> {
36 let t = parse_hex(ty)?;
37 anyhow::ensure!(t.len() == 2, "--type must be exactly two hex bytes");
38 let mut pl = parse_hex(payload)?;
39 if pl.len() < pad {
40 pl.resize(pad, 0);
41 }
42
43 let mut frame = Vec::with_capacity(14 + pl.len());
44 frame.extend_from_slice(&protocol::CARD_MAC);
45 frame.extend_from_slice(&protocol::SENDER_MAC);
46 frame.extend_from_slice(&t);
47 frame.extend_from_slice(&pl);
48
49 let mut dev = open(ctx)?;
50 dev.send(&frame)?;
51
52 let mut seen = 0;
53 await_reply(&mut dev, Duration::from_secs(wait), |f| {
54 seen += 1;
55 p.out(&format!(
56 "reply {seen}: type {:02x}{:02x}, {} bytes",
57 f[12],
58 f[13],
59 f.len()
60 ));
61 hexdump(p, &f[14..f.len().min(14 + show)]);
62 (seen >= 2).then_some(())
63 })?;
64 if seen == 0 {
65 p.out(&format!("no reply within {wait}s"));
66 }
67 Ok(())
68}
69
70fn ts(p: &PcapPacket) -> f64 {
72 f64::from(p.ts_sec) + f64::from(p.ts_usec) / 1e6
73}
74
75pub fn pcap_summary(path: &str, dump: bool, p: &mut dyn Progress) -> Result<()> {
76 let pcap = read_pcap(path)?;
77 let pkts: Vec<_> = pcap.packets().collect();
78 p.out(&format!("{} packets", pkts.len()));
79 let t0 = pkts.first().map_or(0.0, ts);
80 let mut counts: std::collections::BTreeMap<(bool, u8), (usize, usize)> =
81 std::collections::BTreeMap::default();
82 for pk in &pkts {
83 let d = pk.data;
84 if d.len() < 14 {
85 continue;
86 }
87 let (dir_tx, ty) = if is_sender_frame(d) {
88 (true, d[12])
89 } else if is_card_frame(d) {
90 (false, d[12])
91 } else {
92 continue;
93 };
94 let e = counts.entry((dir_tx, ty)).or_default();
95 e.0 += 1;
96 e.1 += d.len();
97 if dump && ty != 0x55 && ty != 0x01 && ty != 0x0a {
98 p.out(&format!(
99 "\n[{:9.4}s] {} type 0x{:02x} len {}",
100 ts(pk) - t0,
101 if dir_tx { "PC->card" } else { "card->PC" },
102 ty,
103 d.len()
104 ));
105 hexdump(p, &d[..d.len().min(160)]);
106 }
107 }
108 p.out(&format!(
109 "\n{:<10} {:>6} {:>10} type",
110 "direction", "count", "bytes"
111 ));
112 for ((tx, ty), (n, bytes)) in counts {
113 p.out(&format!(
114 "{:<10} {:>6} {:>10} 0x{ty:02x}",
115 if tx { "PC->card" } else { "card->PC" },
116 n,
117 bytes
118 ));
119 }
120 Ok(())
121}
122
123pub fn replay(
124 ctx: &Ctx,
125 path: &str,
126 types: Option<&str>,
127 gap_us: u64,
128 all: bool,
129 p: &mut dyn Progress,
130) -> Result<()> {
131 let filter: Option<Vec<u8>> = match types {
132 Some(t) => Some(
133 t.split(',')
134 .map(|s| u8::from_str_radix(s.trim(), 16))
135 .collect::<Result<_, _>>()
136 .context("bad --types list")?,
137 ),
138 None => None,
139 };
140 let pcap = read_pcap(path)?;
141 let mut dev = open(ctx)?;
142 let mut sent = 0usize;
143 for pk in pcap.packets() {
144 let d = pk.data;
145 if !is_sender_frame(d) {
146 continue;
147 }
148 let ty = d[12];
149 let selected = match &filter {
150 Some(f) => f.contains(&ty),
151 None => all || !matches!(ty, 0x55 | 0x01 | 0x0a | 0x07),
152 };
153 if !selected {
154 continue;
155 }
156 dev.send(d)?;
157 sent += 1;
158 std::thread::sleep(Duration::from_micros(gap_us));
159 }
160 p.out(&format!("replayed {sent} frames from {path}"));
161 Ok(())
162}
163
164pub fn discover_one(ctx: &Ctx, wait: u64) -> Result<Option<protocol::DiscoveryInfo>> {
166 let mut dev = open(ctx)?;
167 dev.send(&protocol::discovery())?;
168 await_any_frame(
169 &mut dev,
170 Duration::from_secs(wait),
171 protocol::parse_discovery_response,
172 )
173}
174
175#[must_use]
178pub fn describe(info: &protocol::DiscoveryInfo) -> String {
179 let model = receivers::by_id(info.card_id).map_or("unknown", |m| m.name.as_str());
180 format!(
181 "receiver card #{}: id=0x{:02x} model={model} firmware={}.{:02} detected size {}x{}",
182 info.controller, info.card_id, info.ver_major, info.ver_minor, info.cols, info.rows
183 )
184}
185
186pub fn discover(
189 ctx: &Ctx,
190 wait: u64,
191 p: &mut dyn Progress,
192) -> Result<Vec<protocol::DiscoveryInfo>> {
193 discover_all(ctx, wait, |info| p.out(&describe(info)))
194}
195
196pub fn discover_all(
199 ctx: &Ctx,
200 wait: u64,
201 mut each: impl FnMut(&protocol::DiscoveryInfo),
202) -> Result<Vec<protocol::DiscoveryInfo>> {
203 let mut dev = open(ctx)?;
204 dev.send(&protocol::discovery())?;
205 let deadline = Instant::now() + Duration::from_secs(wait);
206 let mut found = Vec::new();
207 while Instant::now() < deadline {
208 for f in dev.recv()? {
209 if is_our_frame(f) {
211 continue;
212 }
213 if let Some(info) = protocol::parse_discovery_response(f) {
214 each(&info);
215 found.push(info);
216 }
217 }
218 }
219 Ok(found)
220}
221
222pub fn listen(ctx: &Ctx, wait: u64, include_ours: bool, p: &mut dyn Progress) -> Result<()> {
223 let mut dev = open(ctx)?;
224 let deadline = Instant::now() + Duration::from_secs(wait);
225 while Instant::now() < deadline {
226 for f in dev.recv()? {
227 if f.len() < 14 || (!include_ours && is_our_frame(f)) {
230 continue;
231 }
232 p.out(&format!(
233 "frame: dst {} src {} type {:02x}{:02x} len {}",
234 mac(&f[0..6]),
235 mac(&f[6..12]),
236 f[12],
237 f[13],
238 f.len()
239 ));
240 hexdump(p, &f[14..f.len().min(14 + 96)]);
241 }
242 }
243 Ok(())
244}