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