Skip to main content

phantom_protocol/
errors.rs

1// Phase 3.6: `CoreError` is part of the embedded-friendly subset (the
2// `SessionTransport` trait and `EmbeddedLeg` both surface it). Under a
3// bare-metal `--no-default-features --features embedded,no-std` build the
4// module compiles without `std`; `String` comes from `alloc`, and the std-only
5// `From<std::io::Error>` / `From<anyhow::Error>` converters plus the
6// `uniffi::Error` / `thiserror::Error` derives are cfg-gated off. A hand-rolled
7// `Display` impl steps in for the no-std path.
8#[cfg(not(feature = "std"))]
9use alloc::string::String;
10
11#[cfg(feature = "std")]
12use thiserror::Error;
13
14/// Universal Core Error Enum compatible with FFI exports
15#[cfg_attr(feature = "std", derive(Error))]
16#[cfg_attr(feature = "bindings", derive(uniffi::Error))]
17#[derive(Debug)]
18// Adding a variant must not be a SemVer-major break for downstream `match`es
19// (FIPS / migration / flow-control errors are expected to land post-1.0).
20#[non_exhaustive]
21pub enum CoreError {
22    #[cfg_attr(feature = "std", error("Network I/O Error: {0}"))]
23    NetworkError(String),
24
25    #[cfg_attr(feature = "std", error("Serialization Error: {0}"))]
26    SerializationError(String),
27
28    #[cfg_attr(feature = "std", error("System Busy"))]
29    Busy,
30
31    #[cfg_attr(feature = "std", error("Invalid Configuration: {0}"))]
32    ConfigError(String),
33
34    #[cfg_attr(feature = "std", error("Cryptography Error: {0}"))]
35    CryptoError(String),
36
37    #[cfg_attr(feature = "std", error("Validation Error: {0}"))]
38    ValidationError(String),
39
40    #[cfg_attr(feature = "std", error("Runtime initialization failed: {0}"))]
41    RuntimeError(String),
42
43    #[cfg_attr(feature = "std", error("Key derivation failed"))]
44    KeyDerivationError,
45
46    #[cfg_attr(feature = "std", error("Random number generation failed: {0}"))]
47    RngError(String),
48
49    #[cfg_attr(feature = "std", error("Internal concurrency error: {0}"))]
50    InternalError(String),
51
52    #[cfg_attr(feature = "std", error("Handshake failed: {0}"))]
53    HandshakeError(String),
54
55    #[cfg_attr(feature = "std", error("Stream error: {0}"))]
56    StreamError(String),
57
58    #[cfg_attr(feature = "std", error("Session not found: {0}"))]
59    SessionNotFound(String),
60
61    #[cfg_attr(feature = "std", error("Connection closed"))]
62    ConnectionClosed,
63
64    #[cfg_attr(feature = "std", error("Timeout"))]
65    Timeout,
66
67    /// Sliding-window replay protection rejected a packet. The AEAD layer
68    /// already cryptographically prevents replay (strict-counter nonces), but
69    /// the explicit window catches duplicates earlier and gives operators a
70    /// metric signal (`replay_rejected_total`).
71    #[cfg_attr(feature = "std", error("replay protection rejected packet: {0}"))]
72    ReplayDetected(String),
73
74    /// A requested cipher suite is not available in the current build.
75    /// Emitted under `--features fips` when a caller asks for a
76    /// non-FIPS-approved primitive (today: `ChaCha20-Poly1305`). The
77    /// variant is always compiled so error matching stays stable across
78    /// feature configurations.
79    #[cfg_attr(feature = "std", error("cipher suite unavailable: {0}"))]
80    CipherSuiteUnavailable(String),
81
82    /// FIPS 140-3 §7.7 power-on self-test failed at process start.
83    /// Surfaced by [`crate::api::PhantomListener::bind`] /
84    /// [`crate::api::PhantomSession::connect_with_transport`] under
85    /// `--features fips` when
86    /// [`crate::crypto::self_tests::ensure_post_passed`] returns an
87    /// error — refusing to stand up a session / listener over broken
88    /// primitives.
89    ///
90    /// Gated on `fips`. The payload is a `String` (not the typed
91    /// `SelfTestError`) so the variant stays UniFFI-exportable — the
92    /// `Debug` rendering of `SelfTestError` is sufficient diagnostic
93    /// signal for a fatal POST failure.
94    #[cfg(feature = "fips")]
95    #[error("FIPS POST self-test failed: {0}")]
96    FipsSelfTestFailure(String),
97}
98
99// --- Converters for internal errors ---
100
101// `std::io::Error`, `anyhow::Error`, and `getrandom::Error` are all only in
102// the dep graph when the `std` feature is on; gate the converters accordingly.
103#[cfg(feature = "std")]
104impl core::convert::From<std::io::Error> for CoreError {
105    fn from(e: std::io::Error) -> Self {
106        CoreError::NetworkError(e.to_string())
107    }
108}
109
110#[cfg(feature = "std")]
111impl From<getrandom::Error> for CoreError {
112    fn from(e: getrandom::Error) -> Self {
113        CoreError::RngError(e.to_string())
114    }
115}
116
117#[cfg(feature = "std")]
118impl From<anyhow::Error> for CoreError {
119    fn from(e: anyhow::Error) -> Self {
120        CoreError::InternalError(e.to_string())
121    }
122}
123
124// Hand-rolled Display impl for the no-std path — `thiserror` 1.x is std-bound
125// so its derive is gated off above. The format strings mirror the
126// `#[error("…")]` attributes exactly.
127#[cfg(not(feature = "std"))]
128impl core::fmt::Display for CoreError {
129    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
130        match self {
131            Self::NetworkError(s) => write!(f, "Network I/O Error: {s}"),
132            Self::SerializationError(s) => write!(f, "Serialization Error: {s}"),
133            Self::Busy => write!(f, "System Busy"),
134            Self::ConfigError(s) => write!(f, "Invalid Configuration: {s}"),
135            Self::CryptoError(s) => write!(f, "Cryptography Error: {s}"),
136            Self::ValidationError(s) => write!(f, "Validation Error: {s}"),
137            Self::RuntimeError(s) => write!(f, "Runtime initialization failed: {s}"),
138            Self::KeyDerivationError => write!(f, "Key derivation failed"),
139            Self::RngError(s) => write!(f, "Random number generation failed: {s}"),
140            Self::InternalError(s) => write!(f, "Internal concurrency error: {s}"),
141            Self::HandshakeError(s) => write!(f, "Handshake failed: {s}"),
142            Self::StreamError(s) => write!(f, "Stream error: {s}"),
143            Self::SessionNotFound(s) => write!(f, "Session not found: {s}"),
144            Self::ConnectionClosed => write!(f, "Connection closed"),
145            Self::Timeout => write!(f, "Timeout"),
146            Self::ReplayDetected(s) => write!(f, "replay protection rejected packet: {s}"),
147            Self::CipherSuiteUnavailable(s) => write!(f, "cipher suite unavailable: {s}"),
148        }
149    }
150}
151
152// `core::error::Error` requires Rust 1.81; MSRV is 1.75. Gate the impl on the
153// `error_in_core` feature being available (compile-time check via cfg of the
154// rust version is not possible here, so simply omit — `Display` + `Debug` are
155// sufficient for the embedded subset's error-propagation needs).