Skip to main content

darkbio_wire/protocol/
error.rs

1// wire-rs: encrypted protocol between Ark and host
2// Copyright 2026 Dark Bio AG. All rights reserved.
3
4//! Errors returned by protocol methods and promises.
5
6use super::{RemoteError, ReservedErrors};
7use crate::transport;
8use std::convert::Infallible;
9use std::sync::Arc;
10
11/// Failure of a protocol operation. A remote application's error is carried by
12/// [`Error::Remote`]; it does not by itself end the session.
13#[derive(Clone, Debug, thiserror::Error)]
14pub enum Error {
15    /// The session or server was closed locally, including by dropping its owner.
16    #[error("wire protocol closed")]
17    Closed,
18
19    /// The operation's absolute deadline expired. Remote work may still run.
20    #[error("wire operation timed out")]
21    Timeout,
22
23    /// The underlying transport failed or the peer reset the session.
24    #[error("wire transport failed: {0}")]
25    Transport(#[from] Arc<transport::Error>),
26
27    /// The peer returned an application error for this request.
28    #[error("wire peer failed the request, code {}: {}", .0.code, .0.msg)]
29    Remote(RemoteError),
30
31    /// The response's `Message` variant does not match the type requested by
32    /// `Promise::wait()`. This does not end the session.
33    #[error("wire response type mismatch: expected {expected}, received {received}")]
34    UnexpectedResponse {
35        /// Expected protobuf message type.
36        expected: &'static str,
37        /// Received protobuf message type.
38        received: &'static str,
39    },
40
41    /// The submitted message cannot be sent from this session's side. Reported
42    /// through the request or reply promise; this error does not end the session.
43    #[error("wire message cannot be sent in this direction: {0}")]
44    WrongDirection(&'static str),
45
46    /// The encoded message exceeds the transport's sending limit.
47    #[error("wire message too large: {0} bytes")]
48    TooLarge(usize),
49
50    /// The peer sent an invalid envelope or payload. The session closes when this
51    /// is detected. Nested payloads are checked only when `recv()` or `wait()`
52    /// reads them.
53    #[error("wire peer sent a malformed message")]
54    Malformed,
55
56    /// A peer request would exceed the session's request limit. Also returned if
57    /// the limit is lowered below usage. Carries the configured request limit.
58    /// This closes the session.
59    #[error("wire inbound request limit exceeded: {0}")]
60    InboundRequestLimitExceeded(usize),
61
62    /// Buffering an incoming envelope would exceed the session's byte limit.
63    /// Also returned if the limit is lowered below usage. Carries the configured
64    /// byte limit. This closes the session.
65    #[error("wire inbound byte limit exceeded: {0}")]
66    InboundByteLimitExceeded(usize),
67}
68
69impl From<transport::Error> for Error {
70    /// Wraps a transport error in an `Arc` so pending promises can share it.
71    fn from(error: transport::Error) -> Self {
72        Self::Transport(Arc::new(error))
73    }
74}
75
76impl From<RemoteError> for Error {
77    /// Wraps the peer's error code and message in `Error::Remote`.
78    fn from(error: RemoteError) -> Self {
79        Self::Remote(error)
80    }
81}
82
83impl From<Infallible> for Error {
84    /// Allows `Promise::wait()` to return `Message` without extracting a variant.
85    fn from(error: Infallible) -> Self {
86        match error {}
87    }
88}
89
90impl Error {
91    /// Whether a session or server ending with this error did so in an orderly
92    /// way, through a local close, a peer reset or the stream ending.
93    pub(super) fn orderly(&self) -> bool {
94        match self {
95            Self::Closed => true,
96            Self::Transport(error) => matches!(
97                **error,
98                transport::Error::SessionReset | transport::Error::Terminated
99            ),
100            _ => false,
101        }
102    }
103
104    /// The error as a log reason, a transport failure named by the transport's
105    /// own error rather than by the wrapping one.
106    pub(super) fn reason(&self) -> &dyn std::fmt::Display {
107        match self {
108            Self::Transport(error) => error.as_ref(),
109            other => other,
110        }
111    }
112}
113
114impl RemoteError {
115    /// Builds an error with a numeric code and a human-readable message.
116    /// Codes from 0x100 are request-specific. Use [`Self::reserved`] for named
117    /// protocol errors.
118    pub fn new(code: u64, msg: impl Into<String>) -> Self {
119        Self {
120            code,
121            msg: msg.into(),
122        }
123    }
124
125    /// Builds an error from a reserved protocol code and a human-readable message.
126    pub fn reserved(code: ReservedErrors, msg: impl Into<String>) -> Self {
127        Self::new(code as u64, msg)
128    }
129}