1use std::collections::{HashMap, HashSet};
2use std::pin::Pin;
3use std::sync::atomic::{AtomicBool, Ordering};
4use std::sync::{Arc, Mutex, PoisonError, Weak};
5
6use hidpp::channel::HidppChannel;
7use openlogi_fixture::{HidCassette, ReportSupport, RequestMatch};
8use tokio::sync::mpsc;
9
10use crate::backend::{BackendError, HidBackend, HotplugEvent, HotplugStream, NodeId, NodeInfo};
11
12use super::ReplayError;
13use super::barrier::{ReplayResponseBarrier, ResponseGates};
14use super::channel::{
15 CassetteState, ReplayCompletion, ReplayRawHidChannel, ReplayRawWriter, ReplayRawWriterHandle,
16};
17
18#[derive(Clone, Copy, Debug, PartialEq, Eq)]
20pub enum NodePresence {
21 Present,
23 Absent,
25}
26
27#[derive(Clone, Copy, Debug, PartialEq, Eq)]
29pub enum OpenOutcome {
30 Hidpp,
32 NotHidpp,
34 Denied,
36 Disconnected,
38}
39
40#[derive(Clone, Copy, Debug, PartialEq, Eq)]
42pub enum ChannelConnection {
43 Connected,
45 Disconnected,
47}
48
49#[derive(Clone, Copy, Debug, PartialEq, Eq)]
51pub enum RawWriterAvailability {
52 Capture,
54 Unavailable,
56}
57
58#[derive(Clone, Copy, Debug, PartialEq, Eq)]
60pub enum ReceiverLinkState {
61 Online,
63 Offline,
65}
66
67#[derive(Clone, Copy, Debug, PartialEq, Eq)]
69pub enum ReceiverSlotState {
70 Empty,
72 Paired(ReceiverLinkState),
74}
75
76#[derive(Clone, Copy, Debug, PartialEq, Eq)]
78pub struct ReceiverSlot {
79 pub slot: u8,
81 pub state: ReceiverSlotState,
83}
84
85#[derive(Clone, Debug)]
87pub struct ReplayChannel {
88 pub id: String,
90 pub connection: ChannelConnection,
92 pub report_support: ReportSupport,
94}
95
96#[derive(Clone, Debug)]
98pub struct ReplayNode {
99 pub info: NodeInfo,
101 pub presence: NodePresence,
103 pub open_outcome: OpenOutcome,
105 pub channel: Option<String>,
107 pub raw_writer: RawWriterAvailability,
109 pub receiver_slots: Vec<ReceiverSlot>,
114}
115
116#[derive(Clone, Debug)]
118pub struct ReplayTopology {
119 pub nodes: Vec<ReplayNode>,
121 pub channels: Vec<ReplayChannel>,
123}
124
125struct NodeRuntime {
126 node: ReplayNode,
127 open_count: usize,
128 raw_written: Arc<Mutex<Vec<Vec<u8>>>>,
129 raw_connected: Arc<AtomicBool>,
130}
131
132struct ChannelRuntime {
133 report_support: ReportSupport,
134 connection: ChannelConnection,
135 cassette: Arc<CassetteState>,
136 response_gates: Arc<ResponseGates>,
137 written: Arc<Mutex<Vec<Vec<u8>>>>,
138 lifetimes: Vec<ChannelLifetime>,
139 open_count: usize,
140}
141
142struct ChannelLifetime {
143 connected: Weak<AtomicBool>,
144 incoming: mpsc::UnboundedSender<Vec<u8>>,
145}
146
147struct BackendState {
148 nodes: Vec<NodeRuntime>,
149 channels: HashMap<String, ChannelRuntime>,
150}
151
152pub struct ReplayBackend {
157 state: Mutex<BackendState>,
158 watchers: Mutex<Vec<mpsc::UnboundedSender<HotplugEvent>>>,
159}
160
161impl ReplayBackend {
162 pub fn new(topology: ReplayTopology, cassettes: Vec<HidCassette>) -> Result<Self, ReplayError> {
164 let channels = build_channels(topology.channels, cassettes)?;
165 let nodes = build_nodes(topology.nodes, &channels)?;
166
167 Ok(Self {
168 state: Mutex::new(BackendState { nodes, channels }),
169 watchers: Mutex::new(Vec::new()),
170 })
171 }
172
173 pub fn set_node_presence(
175 &self,
176 node: &NodeId,
177 presence: NodePresence,
178 ) -> Result<(), ReplayError> {
179 let mut state = self.state.lock().unwrap_or_else(PoisonError::into_inner);
180 let runtime = find_node_mut(&mut state.nodes, node)?;
181 runtime.node.presence = presence;
182 Ok(())
183 }
184
185 pub fn set_open_outcome(&self, node: &NodeId, outcome: OpenOutcome) -> Result<(), ReplayError> {
187 let mut state = self.state.lock().unwrap_or_else(PoisonError::into_inner);
188 let runtime = find_node_mut(&mut state.nodes, node)?;
189 if outcome == OpenOutcome::Hidpp && runtime.node.channel.is_none() {
190 return Err(ReplayError::invalid(
191 "replay topology",
192 format!("node {node} has no logical channel"),
193 ));
194 }
195 runtime.node.open_outcome = outcome;
196 Ok(())
197 }
198
199 pub fn set_channel_connection(
204 &self,
205 channel: &str,
206 connection: ChannelConnection,
207 ) -> Result<(), ReplayError> {
208 let mut state = self.state.lock().unwrap_or_else(PoisonError::into_inner);
209 let runtime = state
210 .channels
211 .get_mut(channel)
212 .ok_or_else(|| ReplayError::UnknownChannel(channel.to_string()))?;
213 runtime.connection = connection;
214 runtime.lifetimes.retain(|lifetime| {
215 let Some(connection_flag) = lifetime.connected.upgrade() else {
216 return false;
217 };
218 if connection == ChannelConnection::Disconnected {
219 connection_flag.store(false, Ordering::SeqCst);
220 }
221 true
222 });
223 Ok(())
224 }
225
226 pub fn hold_next_response(
231 &self,
232 channel: &str,
233 request_match: RequestMatch,
234 request: &[u8],
235 ) -> Result<ReplayResponseBarrier, ReplayError> {
236 let state = self.state.lock().unwrap_or_else(PoisonError::into_inner);
237 let runtime = state
238 .channels
239 .get(channel)
240 .ok_or_else(|| ReplayError::UnknownChannel(channel.to_string()))?;
241 runtime
242 .report_support
243 .validate_report(request)
244 .map_err(|error| ReplayError::invalid("replay response barrier", error.to_string()))?;
245 Ok(runtime.response_gates.hold(request_match, request))
246 }
247
248 pub fn emit_channel_report(&self, channel: &str, report: &[u8]) -> Result<usize, ReplayError> {
252 let mut state = self.state.lock().unwrap_or_else(PoisonError::into_inner);
253 let runtime = state
254 .channels
255 .get_mut(channel)
256 .ok_or_else(|| ReplayError::UnknownChannel(channel.to_string()))?;
257 runtime
258 .report_support
259 .validate_report(report)
260 .map_err(|error| ReplayError::invalid("replay channel report", error.to_string()))?;
261 runtime.cassette.validate_report(report)?;
262 let mut delivered = 0;
263 runtime.lifetimes.retain(|lifetime| {
264 let Some(connected) = lifetime.connected.upgrade() else {
265 return false;
266 };
267 if connected.load(Ordering::SeqCst) && lifetime.incoming.send(report.to_vec()).is_ok() {
268 delivered += 1;
269 }
270 true
271 });
272 Ok(delivered)
273 }
274
275 pub fn channel_lifetime_count(&self, channel: &str) -> Result<usize, ReplayError> {
277 let mut state = self.state.lock().unwrap_or_else(PoisonError::into_inner);
278 let runtime = state
279 .channels
280 .get_mut(channel)
281 .ok_or_else(|| ReplayError::UnknownChannel(channel.to_string()))?;
282 runtime
283 .lifetimes
284 .retain(|lifetime| lifetime.connected.upgrade().is_some());
285 Ok(runtime.lifetimes.len())
286 }
287
288 pub fn set_receiver_slot_state(
296 &self,
297 node: &NodeId,
298 slot: u8,
299 slot_state: ReceiverSlotState,
300 ) -> Result<(), ReplayError> {
301 let state = self.state.lock().unwrap_or_else(PoisonError::into_inner);
302 receiver_cassette(&state, node)?.set_slot(slot, slot_state)
303 }
304
305 pub fn receiver_slot_state(
307 &self,
308 node: &NodeId,
309 slot: u8,
310 ) -> Result<ReceiverSlotState, ReplayError> {
311 let state = self.state.lock().unwrap_or_else(PoisonError::into_inner);
312 receiver_cassette(&state, node)?.slot(slot)
313 }
314
315 pub fn emit_hotplug(&self, event: HotplugEvent) {
317 self.watchers
318 .lock()
319 .unwrap_or_else(PoisonError::into_inner)
320 .retain(|watcher| watcher.send(event).is_ok());
321 }
322
323 pub fn open_count(&self, node: &NodeId) -> Result<usize, ReplayError> {
325 let state = self.state.lock().unwrap_or_else(PoisonError::into_inner);
326 state
327 .nodes
328 .iter()
329 .find(|runtime| runtime.node.info.id == *node)
330 .map(|runtime| runtime.open_count)
331 .ok_or_else(|| ReplayError::UnknownNode(node.to_string()))
332 }
333
334 pub fn channel_completion(&self, channel: &str) -> Result<ReplayCompletion, ReplayError> {
336 let state = self.state.lock().unwrap_or_else(PoisonError::into_inner);
337 let runtime = state
338 .channels
339 .get(channel)
340 .ok_or_else(|| ReplayError::UnknownChannel(channel.to_string()))?;
341 let written = runtime
342 .written
343 .lock()
344 .unwrap_or_else(PoisonError::into_inner)
345 .clone();
346 let mut completion = runtime.cassette.completion(written);
347 completion.channel_open_count = runtime.open_count;
348 Ok(completion)
349 }
350
351 pub fn require_complete(&self) -> Result<(), ReplayError> {
353 let state = self.state.lock().unwrap_or_else(PoisonError::into_inner);
354 for runtime in state.channels.values() {
355 let written = runtime
356 .written
357 .lock()
358 .unwrap_or_else(PoisonError::into_inner)
359 .clone();
360 runtime.cassette.completion(written).require_complete()?;
361 }
362 Ok(())
363 }
364
365 pub fn raw_writer_handle(&self, node: &NodeId) -> Result<ReplayRawWriterHandle, ReplayError> {
367 let state = self.state.lock().unwrap_or_else(PoisonError::into_inner);
368 let runtime = state
369 .nodes
370 .iter()
371 .find(|runtime| runtime.node.info.id == *node)
372 .ok_or_else(|| ReplayError::UnknownNode(node.to_string()))?;
373 Ok(ReplayRawWriterHandle::from_parts(
374 Arc::clone(&runtime.raw_written),
375 Arc::clone(&runtime.raw_connected),
376 ))
377 }
378}
379
380fn build_channels(
381 channels: Vec<ReplayChannel>,
382 cassettes: Vec<HidCassette>,
383) -> Result<HashMap<String, ChannelRuntime>, ReplayError> {
384 let mut cassette_by_channel = HashMap::new();
385 for cassette in cassettes {
386 cassette.validate()?;
387 let channel = cassette.channel.clone();
388 if cassette_by_channel
389 .insert(channel.clone(), cassette)
390 .is_some()
391 {
392 return Err(ReplayError::invalid(
393 "replay topology",
394 format!("multiple cassettes name channel {channel}"),
395 ));
396 }
397 }
398
399 let mut runtimes = HashMap::new();
400 for channel in channels {
401 if channel.id.trim().is_empty() {
402 return Err(ReplayError::invalid(
403 "replay topology",
404 "channel id must not be empty",
405 ));
406 }
407 let Some(cassette) = cassette_by_channel.remove(&channel.id) else {
408 return Err(ReplayError::invalid(
409 "replay topology",
410 format!("channel {} has no cassette", channel.id),
411 ));
412 };
413 if cassette.report_support != channel.report_support {
414 return Err(ReplayError::invalid(
415 "replay topology",
416 format!(
417 "channel {} and its cassette disagree on report support",
418 channel.id
419 ),
420 ));
421 }
422 if runtimes
423 .insert(
424 channel.id.clone(),
425 ChannelRuntime {
426 report_support: channel.report_support,
427 connection: channel.connection,
428 cassette: CassetteState::new(cassette)?,
429 response_gates: Arc::new(ResponseGates::default()),
430 written: Arc::new(Mutex::new(Vec::new())),
431 lifetimes: Vec::new(),
432 open_count: 0,
433 },
434 )
435 .is_some()
436 {
437 return Err(ReplayError::invalid(
438 "replay topology",
439 format!("duplicate channel {}", channel.id),
440 ));
441 }
442 }
443 if let Some(extra) = cassette_by_channel.keys().next() {
444 return Err(ReplayError::invalid(
445 "replay topology",
446 format!("cassette references unknown channel {extra}"),
447 ));
448 }
449 Ok(runtimes)
450}
451
452fn build_nodes(
453 nodes: Vec<ReplayNode>,
454 channels: &HashMap<String, ChannelRuntime>,
455) -> Result<Vec<NodeRuntime>, ReplayError> {
456 let mut node_ids = HashSet::new();
457 let mut runtimes = Vec::new();
458 for mut node in nodes {
459 if !node_ids.insert(node.info.id.clone()) {
460 return Err(ReplayError::invalid(
461 "replay topology",
462 format!("duplicate node {}", node.info.id),
463 ));
464 }
465 if node.open_outcome == OpenOutcome::Hidpp && node.channel.is_none() {
466 return Err(ReplayError::invalid(
467 "replay topology",
468 format!("HID++ node {} has no logical channel", node.info.id),
469 ));
470 }
471 if let Some(channel) = &node.channel
472 && !channels.contains_key(channel)
473 {
474 return Err(ReplayError::invalid(
475 "replay topology",
476 format!("node {} references unknown channel {channel}", node.info.id),
477 ));
478 }
479 validate_receiver_slots(&node)?;
480 if !node.receiver_slots.is_empty() {
481 let channel = node
482 .channel
483 .as_ref()
484 .and_then(|id| channels.get(id))
485 .ok_or_else(|| {
486 ReplayError::invalid(
487 "replay topology",
488 format!(
489 "node {} declares receiver slots without a channel",
490 node.info.id
491 ),
492 )
493 })?;
494 channel
497 .cassette
498 .declare_slots(std::mem::take(&mut node.receiver_slots))?;
499 }
500 runtimes.push(NodeRuntime {
501 node,
502 open_count: 0,
503 raw_written: Arc::new(Mutex::new(Vec::new())),
504 raw_connected: Arc::new(AtomicBool::new(true)),
505 });
506 }
507 Ok(runtimes)
508}
509
510fn validate_receiver_slots(node: &ReplayNode) -> Result<(), ReplayError> {
511 let mut slots = HashSet::new();
512 for slot in &node.receiver_slots {
513 if !(1..=6).contains(&slot.slot) {
514 return Err(ReplayError::invalid(
515 "replay topology",
516 format!(
517 "node {} has invalid receiver slot {}",
518 node.info.id, slot.slot
519 ),
520 ));
521 }
522 if !slots.insert(slot.slot) {
523 return Err(ReplayError::invalid(
524 "replay topology",
525 format!("node {} repeats receiver slot {}", node.info.id, slot.slot),
526 ));
527 }
528 }
529 Ok(())
530}
531
532fn receiver_cassette<'a>(
533 state: &'a BackendState,
534 node: &NodeId,
535) -> Result<&'a CassetteState, ReplayError> {
536 let runtime = state
537 .nodes
538 .iter()
539 .find(|runtime| runtime.node.info.id == *node)
540 .ok_or_else(|| ReplayError::UnknownNode(node.to_string()))?;
541 runtime
542 .node
543 .channel
544 .as_ref()
545 .and_then(|id| state.channels.get(id))
546 .map(|channel| channel.cassette.as_ref())
547 .ok_or_else(|| {
548 ReplayError::invalid(
549 "replay topology",
550 format!("node {node} has no receiver channel"),
551 )
552 })
553}
554
555fn find_node_mut<'a>(
556 nodes: &'a mut [NodeRuntime],
557 node: &NodeId,
558) -> Result<&'a mut NodeRuntime, ReplayError> {
559 nodes
560 .iter_mut()
561 .find(|runtime| runtime.node.info.id == *node)
562 .ok_or_else(|| ReplayError::UnknownNode(node.to_string()))
563}
564
565#[hidpp::async_trait]
566impl HidBackend for ReplayBackend {
567 async fn enumerate(&self) -> Result<Vec<NodeInfo>, BackendError> {
568 let state = self.state.lock().unwrap_or_else(PoisonError::into_inner);
569 Ok(state
570 .nodes
571 .iter()
572 .filter(|runtime| runtime.node.presence == NodePresence::Present)
573 .map(|runtime| runtime.node.info.clone())
574 .collect())
575 }
576
577 async fn enumerate_hidpp(&self) -> Result<Vec<NodeInfo>, BackendError> {
578 self.enumerate().await
579 }
580
581 async fn open_hidpp(&self, node: &NodeInfo) -> Result<Option<Arc<HidppChannel>>, BackendError> {
582 let raw = {
583 let mut state = self.state.lock().unwrap_or_else(PoisonError::into_inner);
584 let Some(index) = state
585 .nodes
586 .iter()
587 .position(|runtime| runtime.node.info.id == node.id)
588 else {
589 return Err(BackendError::Disconnected);
590 };
591 if state.nodes[index].node.presence == NodePresence::Absent {
592 return Err(BackendError::Disconnected);
593 }
594 state.nodes[index].open_count += 1;
595 match state.nodes[index].node.open_outcome {
596 OpenOutcome::NotHidpp => return Ok(None),
597 OpenOutcome::Denied => {
598 return Err(BackendError::Backend("replay HID open denied".to_string()));
599 }
600 OpenOutcome::Disconnected => return Err(BackendError::Disconnected),
601 OpenOutcome::Hidpp => {}
602 }
603 let channel_id =
604 state.nodes[index].node.channel.clone().ok_or_else(|| {
605 BackendError::Backend("replay node has no channel".to_string())
606 })?;
607 let vendor_id = state.nodes[index].node.info.vendor_id;
608 let product_id = state.nodes[index].node.info.product_id;
609 let channel = state.channels.get_mut(&channel_id).ok_or_else(|| {
610 BackendError::Backend(format!("replay channel {channel_id} is missing"))
611 })?;
612 channel.open_count += 1;
613 let connected = Arc::new(AtomicBool::new(
614 channel.connection == ChannelConnection::Connected,
615 ));
616 let raw = ReplayRawHidChannel::from_parts(
617 vendor_id,
618 product_id,
619 channel.report_support,
620 Arc::clone(&channel.cassette),
621 Arc::clone(&channel.written),
622 Arc::clone(&connected),
623 Arc::clone(&channel.response_gates),
624 );
625 channel.lifetimes.push(ChannelLifetime {
626 connected: Arc::downgrade(&connected),
627 incoming: raw.incoming_sender(),
628 });
629 raw
630 };
631 HidppChannel::from_raw_channel(raw)
632 .await
633 .map(Arc::new)
634 .map(Some)
635 .map_err(|error| BackendError::Backend(error.to_string()))
636 }
637
638 async fn open_raw_writer(
639 &self,
640 node: &NodeInfo,
641 ) -> Result<Box<dyn crate::backend::RawWriter>, BackendError> {
642 let state = self.state.lock().unwrap_or_else(PoisonError::into_inner);
643 let runtime = state
644 .nodes
645 .iter()
646 .find(|runtime| runtime.node.info.id == node.id)
647 .ok_or(BackendError::Disconnected)?;
648 if runtime.node.presence == NodePresence::Absent {
649 return Err(BackendError::Disconnected);
650 }
651 if runtime.node.raw_writer == RawWriterAvailability::Unavailable {
652 return Err(BackendError::Backend(
653 "replay node has no raw writer".to_string(),
654 ));
655 }
656 Ok(Box::new(ReplayRawWriter::from_parts(
657 Arc::clone(&runtime.raw_written),
658 Arc::clone(&runtime.raw_connected),
659 )))
660 }
661
662 fn watch(&self) -> Result<HotplugStream, BackendError> {
663 let (sender, mut receiver) = mpsc::unbounded_channel();
664 self.watchers
665 .lock()
666 .unwrap_or_else(PoisonError::into_inner)
667 .push(sender);
668 Ok(Box::new(futures_lite::stream::poll_fn(move |context| {
669 Pin::new(&mut receiver).poll_recv(context)
670 })))
671 }
672}