1use std::collections::{HashMap, VecDeque};
2use std::error::Error;
3use std::io;
4use std::sync::atomic::{AtomicBool, Ordering};
5use std::sync::{Arc, Mutex, PoisonError};
6
7use hidpp::channel::RawHidChannel;
8use openlogi_fixture::{HidCassette, ReportSupport, RequestMatch, format_hex};
9use tokio::sync::mpsc;
10
11use crate::backend::{BackendError, RawWriter};
12
13use super::barrier::{RequestKey, ResponseGates};
14use super::slots::ReceiverSlots;
15use super::{ReceiverSlot, ReceiverSlotState, ReplayError};
16
17type Responder = Arc<dyn Fn(&[u8]) -> Option<Vec<u8>> + Send + Sync>;
18
19impl RequestKey {
20 fn description(&self) -> String {
21 match self {
22 Self::Exact(bytes) => format!("exact:{}", format_hex(bytes)),
23 Self::Hidpp20(bytes) => format!("hidpp20:{}", format_hex(bytes)),
24 }
25 }
26}
27
28struct PendingExchange {
29 key: RequestKey,
30 response: Option<Vec<u8>>,
31 required: bool,
32 consumed: bool,
33}
34
35struct CassetteRuntime {
36 exchanges: Vec<PendingExchange>,
37 queues: HashMap<RequestKey, VecDeque<usize>>,
38 unmatched: Vec<ReplayMismatch>,
39 receiver_slots: ReceiverSlots,
40 slot_state_errors: Vec<ReplayError>,
41}
42
43pub(super) struct CassetteState {
44 runtime: Mutex<CassetteRuntime>,
45}
46
47impl CassetteState {
48 pub(super) fn new(cassette: HidCassette) -> Result<Arc<Self>, ReplayError> {
49 cassette.validate()?;
50 let mut queues: HashMap<_, VecDeque<_>> = HashMap::new();
51 let exchanges = cassette
52 .exchanges
53 .into_iter()
54 .enumerate()
55 .map(|(index, exchange)| {
56 let key = RequestKey::from_exchange(exchange.request_match, &exchange.request);
57 queues.entry(key.clone()).or_default().push_back(index);
58 PendingExchange {
59 key,
60 response: exchange.response,
61 required: exchange.required,
62 consumed: false,
63 }
64 })
65 .collect();
66 Ok(Arc::new(Self {
67 runtime: Mutex::new(CassetteRuntime {
68 exchanges,
69 queues,
70 unmatched: Vec::new(),
71 receiver_slots: ReceiverSlots::default(),
72 slot_state_errors: Vec::new(),
73 }),
74 }))
75 }
76
77 pub(super) fn declare_slots(&self, slots: Vec<ReceiverSlot>) -> Result<(), ReplayError> {
78 self.runtime
79 .lock()
80 .unwrap_or_else(PoisonError::into_inner)
81 .receiver_slots
82 .declare(slots)
83 }
84
85 pub(super) fn set_slot(&self, slot: u8, state: ReceiverSlotState) -> Result<(), ReplayError> {
86 self.runtime
87 .lock()
88 .unwrap_or_else(PoisonError::into_inner)
89 .receiver_slots
90 .set(slot, state)
91 }
92
93 pub(super) fn slot(&self, slot: u8) -> Result<ReceiverSlotState, ReplayError> {
94 self.runtime
95 .lock()
96 .unwrap_or_else(PoisonError::into_inner)
97 .receiver_slots
98 .get(slot)
99 }
100
101 pub(super) fn validate_report(&self, report: &[u8]) -> Result<(), ReplayError> {
102 let mut runtime = self.runtime.lock().unwrap_or_else(PoisonError::into_inner);
103 runtime.validate_slot_state(None, report)
104 }
105
106 fn respond(&self, actual: &[u8]) -> Result<Option<Vec<u8>>, ReplayError> {
107 let normalized = RequestMatch::Hidpp20.request_key(actual);
108 let exact = RequestKey::Exact(actual.to_vec());
109 let hidpp20 = RequestKey::Hidpp20(normalized.clone());
110 let mut runtime = self.runtime.lock().unwrap_or_else(PoisonError::into_inner);
111 let matched = runtime
112 .queues
113 .get(&exact)
114 .and_then(VecDeque::front)
115 .map(|&index| (index, RequestMatch::Exact))
116 .or_else(|| {
117 runtime
118 .queues
119 .get(&hidpp20)
120 .and_then(VecDeque::front)
121 .map(|&index| (index, RequestMatch::Hidpp20))
122 });
123 let Some((index, request_match)) = matched else {
124 let mismatch = ReplayMismatch {
125 actual: format_hex(actual),
126 normalized: format_hex(&normalized),
127 };
128 runtime.unmatched.push(mismatch.clone());
129 return Err(ReplayError::UnmatchedRequest {
130 actual: mismatch.actual,
131 normalized: mismatch.normalized,
132 });
133 };
134 let exchange = &runtime.exchanges[index];
135 let mut response = exchange.response.clone();
136 if request_match == RequestMatch::Hidpp20
137 && let Some(response) = response.as_mut()
138 {
139 rebind_software_id(response, actual[3] & 0x0f);
140 }
141 if let Some(response) = &response {
142 runtime.validate_slot_state(Some(actual), response)?;
143 }
144 runtime
147 .queues
148 .get_mut(&RequestKey::from_exchange(request_match, actual))
149 .and_then(VecDeque::pop_front);
150 runtime.exchanges[index].consumed = true;
151 Ok(response)
152 }
153
154 pub(super) fn completion(&self, written_reports: Vec<Vec<u8>>) -> ReplayCompletion {
155 let runtime = self.runtime.lock().unwrap_or_else(PoisonError::into_inner);
156 let unconsumed_required = runtime
157 .exchanges
158 .iter()
159 .filter(|exchange| exchange.required && !exchange.consumed)
160 .map(|exchange| exchange.key.description())
161 .collect();
162 let consumed_optional = runtime
163 .exchanges
164 .iter()
165 .filter(|exchange| !exchange.required && exchange.consumed)
166 .count();
167 let unused_optional = runtime
168 .exchanges
169 .iter()
170 .filter(|exchange| !exchange.required && !exchange.consumed)
171 .count();
172 ReplayCompletion {
173 written_reports,
174 unmatched_requests: runtime.unmatched.clone(),
175 slot_state_errors: runtime.slot_state_errors.clone(),
176 unconsumed_required,
177 consumed_optional,
178 unused_optional,
179 channel_open_count: 0,
180 }
181 }
182}
183
184impl CassetteRuntime {
185 fn validate_slot_state(
186 &mut self,
187 request: Option<&[u8]>,
188 report: &[u8],
189 ) -> Result<(), ReplayError> {
190 self.receiver_slots
191 .validate(request, report)
192 .inspect_err(|error| {
193 self.slot_state_errors.push(error.clone());
194 })
195 }
196}
197
198fn rebind_software_id(response: &mut [u8], software_id: u8) {
199 if response[2] == 0xff {
200 response[4] = response[4] & 0xf0 | software_id;
201 } else {
202 response[3] = response[3] & 0xf0 | software_id;
203 }
204}
205
206#[derive(Clone, Debug, PartialEq, Eq)]
208pub struct ReplayMismatch {
209 pub actual: String,
211 pub normalized: String,
213}
214
215#[derive(Clone, Debug, PartialEq, Eq)]
217pub struct ReplayCompletion {
218 pub written_reports: Vec<Vec<u8>>,
220 pub unmatched_requests: Vec<ReplayMismatch>,
222 pub slot_state_errors: Vec<ReplayError>,
226 pub unconsumed_required: Vec<String>,
228 pub consumed_optional: usize,
230 pub unused_optional: usize,
232 pub channel_open_count: usize,
237}
238
239impl ReplayCompletion {
240 #[must_use]
242 pub fn is_complete(&self) -> bool {
243 self.unmatched_requests.is_empty()
244 && self.slot_state_errors.is_empty()
245 && self.unconsumed_required.is_empty()
246 }
247
248 pub fn require_complete(&self) -> Result<(), ReplayError> {
250 if let Some(error) = self.slot_state_errors.first() {
251 return Err(error.clone());
252 }
253 if let Some(mismatch) = self.unmatched_requests.first() {
254 return Err(ReplayError::UnmatchedRequest {
255 actual: mismatch.actual.clone(),
256 normalized: mismatch.normalized.clone(),
257 });
258 }
259 if self.unconsumed_required.is_empty() {
260 Ok(())
261 } else {
262 Err(ReplayError::UnconsumedExchanges {
263 requests: self.unconsumed_required.clone(),
264 })
265 }
266 }
267}
268
269#[derive(Clone)]
271pub struct ReplayChannelHandle {
272 cassette: Option<Arc<CassetteState>>,
273 written: Arc<Mutex<Vec<Vec<u8>>>>,
274 connected: Arc<AtomicBool>,
275}
276
277impl ReplayChannelHandle {
278 pub(super) fn from_parts(
279 cassette: Arc<CassetteState>,
280 written: Arc<Mutex<Vec<Vec<u8>>>>,
281 connected: Arc<AtomicBool>,
282 ) -> Self {
283 Self {
284 cassette: Some(cassette),
285 written,
286 connected,
287 }
288 }
289
290 #[must_use]
292 pub fn written_reports(&self) -> Vec<Vec<u8>> {
293 self.written
294 .lock()
295 .unwrap_or_else(PoisonError::into_inner)
296 .clone()
297 }
298
299 #[must_use]
301 pub fn completion(&self) -> ReplayCompletion {
302 self.cassette.as_ref().map_or_else(
303 || ReplayCompletion {
304 written_reports: self.written_reports(),
305 unmatched_requests: Vec::new(),
306 slot_state_errors: Vec::new(),
307 unconsumed_required: Vec::new(),
308 consumed_optional: 0,
309 unused_optional: 0,
310 channel_open_count: 0,
311 },
312 |cassette| cassette.completion(self.written_reports()),
313 )
314 }
315
316 pub fn require_complete(&self) -> Result<(), ReplayError> {
318 self.completion().require_complete()
319 }
320
321 pub fn set_connection(&self, connection: super::ChannelConnection) {
323 self.connected.store(
324 connection == super::ChannelConnection::Connected,
325 Ordering::SeqCst,
326 );
327 }
328}
329
330pub struct ReplayRawHidChannel {
332 vendor_id: u16,
333 product_id: u16,
334 report_support: ReportSupport,
335 incoming_tx: mpsc::UnboundedSender<Vec<u8>>,
336 incoming_rx: tokio::sync::Mutex<mpsc::UnboundedReceiver<Vec<u8>>>,
337 written: Arc<Mutex<Vec<Vec<u8>>>>,
338 cassette: Option<Arc<CassetteState>>,
339 response_gates: Arc<ResponseGates>,
340 responder: Option<Responder>,
341 connected: Arc<AtomicBool>,
342 fails: Option<fn(&[u8]) -> bool>,
343}
344
345impl ReplayRawHidChannel {
346 pub fn new(
348 cassette: HidCassette,
349 vendor_id: u16,
350 product_id: u16,
351 ) -> Result<(Self, ReplayChannelHandle), ReplayError> {
352 let report_support = cassette.report_support;
353 let cassette = CassetteState::new(cassette)?;
354 let written = Arc::new(Mutex::new(Vec::new()));
355 let connected = Arc::new(AtomicBool::new(true));
356 let response_gates = Arc::new(ResponseGates::default());
357 let channel = Self::from_parts(
358 vendor_id,
359 product_id,
360 report_support,
361 Arc::clone(&cassette),
362 Arc::clone(&written),
363 Arc::clone(&connected),
364 response_gates,
365 );
366 let handle = ReplayChannelHandle::from_parts(cassette, written, connected);
367 Ok((channel, handle))
368 }
369
370 pub(super) fn from_parts(
371 vendor_id: u16,
372 product_id: u16,
373 report_support: ReportSupport,
374 cassette: Arc<CassetteState>,
375 written: Arc<Mutex<Vec<Vec<u8>>>>,
376 connected: Arc<AtomicBool>,
377 response_gates: Arc<ResponseGates>,
378 ) -> Self {
379 let (incoming_tx, incoming_rx) = mpsc::unbounded_channel();
380 Self {
381 vendor_id,
382 product_id,
383 report_support,
384 incoming_tx,
385 incoming_rx: tokio::sync::Mutex::new(incoming_rx),
386 written,
387 cassette: Some(cassette),
388 response_gates,
389 responder: None,
390 connected,
391 fails: None,
392 }
393 }
394
395 pub(super) fn incoming_sender(&self) -> mpsc::UnboundedSender<Vec<u8>> {
396 self.incoming_tx.clone()
397 }
398
399 #[cfg(test)]
400 pub(crate) fn with_responder(
401 responder: fn(&[u8]) -> Option<Vec<u8>>,
402 ) -> (Self, ReplayChannelHandle) {
403 Self::build_scripted(responder, None)
404 }
405
406 #[cfg(test)]
407 pub(crate) fn with_dynamic_responder(
408 responder: impl Fn(&[u8]) -> Option<Vec<u8>> + Send + Sync + 'static,
409 ) -> (Self, ReplayChannelHandle) {
410 Self::build_scripted(responder, None)
411 }
412
413 #[cfg(test)]
414 pub(crate) fn with_failing_writes(
415 responder: fn(&[u8]) -> Option<Vec<u8>>,
416 fails: fn(&[u8]) -> bool,
417 ) -> (Self, ReplayChannelHandle) {
418 Self::build_scripted(responder, Some(fails))
419 }
420
421 #[cfg(test)]
423 pub(crate) fn presenting_as(mut self, product_id: u16) -> Self {
424 self.product_id = product_id;
425 self
426 }
427
428 #[cfg(test)]
429 fn build_scripted(
430 responder: impl Fn(&[u8]) -> Option<Vec<u8>> + Send + Sync + 'static,
431 fails: Option<fn(&[u8]) -> bool>,
432 ) -> (Self, ReplayChannelHandle) {
433 let (incoming_tx, incoming_rx) = mpsc::unbounded_channel();
434 let written = Arc::new(Mutex::new(Vec::new()));
435 let connected = Arc::new(AtomicBool::new(true));
436 (
437 Self {
438 vendor_id: 0x046d,
439 product_id: 0xb35b,
440 report_support: ReportSupport::ShortAndLong,
441 incoming_tx,
442 incoming_rx: tokio::sync::Mutex::new(incoming_rx),
443 written: Arc::clone(&written),
444 cassette: None,
445 response_gates: Arc::new(ResponseGates::default()),
446 responder: Some(Arc::new(responder)),
447 connected: Arc::clone(&connected),
448 fails,
449 },
450 ReplayChannelHandle {
451 cassette: None,
452 written,
453 connected,
454 },
455 )
456 }
457}
458
459#[hidpp::async_trait]
460impl RawHidChannel for ReplayRawHidChannel {
461 fn vendor_id(&self) -> u16 {
462 self.vendor_id
463 }
464
465 fn product_id(&self) -> u16 {
466 self.product_id
467 }
468
469 async fn write_report(&self, src: &[u8]) -> Result<usize, Box<dyn Error + Send + Sync>> {
470 self.written
471 .lock()
472 .unwrap_or_else(PoisonError::into_inner)
473 .push(src.to_vec());
474 if !self.connected.load(Ordering::SeqCst) {
475 return Err(disconnected_error());
476 }
477 if self.fails.is_some_and(|fails| fails(src)) {
478 return Err(disconnected_error());
479 }
480 let response = if let Some(cassette) = &self.cassette {
481 cassette.respond(src)?
482 } else {
483 self.responder.as_ref().and_then(|responder| responder(src))
484 };
485 if let Some(gate) = self.response_gates.take(src) {
486 gate.request_written(self.incoming_tx.clone(), response);
487 return Ok(src.len());
488 }
489 if let Some(response) = response {
490 self.incoming_tx
491 .send(response)
492 .map_err(|_| disconnected_error())?;
493 }
494 Ok(src.len())
495 }
496
497 async fn read_report(&self, buf: &mut [u8]) -> Result<usize, Box<dyn Error + Send + Sync>> {
498 let Some(report) = self.incoming_rx.lock().await.recv().await else {
499 return Err(disconnected_error());
500 };
501 let len = report.len().min(buf.len());
502 buf[..len].copy_from_slice(&report[..len]);
503 Ok(len)
504 }
505
506 fn is_connected(&self) -> bool {
507 self.connected.load(Ordering::SeqCst)
508 }
509
510 fn supports_short_long_hidpp(&self) -> Option<(bool, bool)> {
511 Some((
512 self.report_support.supports_short_reports(),
513 self.report_support.supports_long_reports(),
514 ))
515 }
516
517 async fn get_report_descriptor(
518 &self,
519 _buf: &mut [u8],
520 ) -> Result<usize, Box<dyn Error + Send + Sync>> {
521 unreachable!("replay channel declares HID++ report support")
522 }
523}
524
525fn disconnected_error() -> Box<dyn Error + Send + Sync> {
526 Box::new(io::Error::new(
527 io::ErrorKind::BrokenPipe,
528 "replay HID channel is disconnected",
529 ))
530}
531
532pub struct ReplayRawWriter {
534 written: Arc<Mutex<Vec<Vec<u8>>>>,
535 connected: Arc<AtomicBool>,
536}
537
538impl ReplayRawWriter {
539 #[must_use]
541 pub fn new() -> (Self, ReplayRawWriterHandle) {
542 let written = Arc::new(Mutex::new(Vec::new()));
543 let connected = Arc::new(AtomicBool::new(true));
544 (
545 Self {
546 written: Arc::clone(&written),
547 connected: Arc::clone(&connected),
548 },
549 ReplayRawWriterHandle { written, connected },
550 )
551 }
552
553 pub(super) fn from_parts(
554 written: Arc<Mutex<Vec<Vec<u8>>>>,
555 connected: Arc<AtomicBool>,
556 ) -> Self {
557 Self { written, connected }
558 }
559}
560
561#[hidpp::async_trait]
562impl RawWriter for ReplayRawWriter {
563 async fn write_output_report(&mut self, report: &[u8]) -> Result<(), BackendError> {
564 if !self.connected.load(Ordering::SeqCst) {
565 return Err(BackendError::Disconnected);
566 }
567 self.written
568 .lock()
569 .unwrap_or_else(PoisonError::into_inner)
570 .push(report.to_vec());
571 Ok(())
572 }
573}
574
575#[derive(Clone)]
577pub struct ReplayRawWriterHandle {
578 written: Arc<Mutex<Vec<Vec<u8>>>>,
579 connected: Arc<AtomicBool>,
580}
581
582impl ReplayRawWriterHandle {
583 pub(super) fn from_parts(
584 written: Arc<Mutex<Vec<Vec<u8>>>>,
585 connected: Arc<AtomicBool>,
586 ) -> Self {
587 Self { written, connected }
588 }
589
590 #[must_use]
592 pub fn written_reports(&self) -> Vec<Vec<u8>> {
593 self.written
594 .lock()
595 .unwrap_or_else(PoisonError::into_inner)
596 .clone()
597 }
598
599 pub fn set_connection(&self, connection: super::ChannelConnection) {
601 self.connected.store(
602 connection == super::ChannelConnection::Connected,
603 Ordering::SeqCst,
604 );
605 }
606}