Skip to main content

openlogi_device/
replay.rs

1//! Strict cassette replay over the production device-layer contracts.
2//!
3//! This module owns mutable virtual topology, replay channels, response
4//! barriers, and diagnostics. Persisted schemas and privacy verification live
5//! in `openlogi-fixture`.
6
7mod backend;
8mod barrier;
9mod channel;
10mod fixture;
11mod slots;
12
13use openlogi_fixture::FixtureError;
14use thiserror::Error;
15
16pub use backend::{
17    ChannelConnection, NodePresence, OpenOutcome, RawWriterAvailability, ReceiverLinkState,
18    ReceiverSlot, ReceiverSlotState, ReplayBackend, ReplayChannel, ReplayNode, ReplayTopology,
19};
20pub use barrier::ReplayResponseBarrier;
21pub use channel::{
22    ReplayChannelHandle, ReplayCompletion, ReplayMismatch, ReplayRawHidChannel, ReplayRawWriter,
23    ReplayRawWriterHandle,
24};
25
26/// A replay topology, matching, or completion failure.
27#[derive(Clone, Debug, Error, PartialEq, Eq)]
28pub enum ReplayError {
29    /// A persisted fixture contract is invalid.
30    #[error(transparent)]
31    Fixture(#[from] FixtureError),
32    /// Mutable replay state violates a runtime invariant.
33    #[error("invalid {asset}: {message}")]
34    InvalidState {
35        /// Human-readable replay asset kind.
36        asset: &'static str,
37        /// Specific failed invariant.
38        message: String,
39    },
40    /// No pending cassette exchange matched an outgoing report.
41    #[error("unmatched HID request: actual={actual}, hidpp20_normalized={normalized}")]
42    UnmatchedRequest {
43        /// Exact outgoing bytes as lowercase hex.
44        actual: String,
45        /// The same bytes with only the HID++ 2.0 software-ID nibble cleared.
46        normalized: String,
47    },
48    /// One or more required cassette exchanges were not consumed.
49    #[error("required cassette exchanges were not consumed: {requests:?}")]
50    UnconsumedExchanges {
51        /// Normalized request keys that remained pending.
52        requests: Vec<String>,
53    },
54    /// A topology operation named a node that does not exist.
55    #[error("unknown replay node {0}")]
56    UnknownNode(String),
57    /// A topology operation named a logical channel that does not exist.
58    #[error("unknown replay channel {0}")]
59    UnknownChannel(String),
60}
61
62impl ReplayError {
63    fn invalid(asset: &'static str, message: impl Into<String>) -> Self {
64        Self::InvalidState {
65            asset,
66            message: message.into(),
67        }
68    }
69}
70
71#[cfg(test)]
72mod session_replay_tests;
73#[cfg(test)]
74mod tests;