Skip to main content

rightkit_send/
error.rs

1//! Typed errors. `Display` gives a short English diagnostic for logs. A host that
2//! shows text to people matches on the variant (or on the serialized `code`) and
3//! words it in its own language.
4
5use serde::Serialize;
6use std::fmt;
7
8#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
9#[serde(tag = "code", rename_all = "snake_case")]
10#[non_exhaustive]
11pub enum Error {
12    /// The stored certificate and key do not form a usable identity.
13    Identity { detail: String },
14    /// The listen port is held by another program.
15    PortInUse { port: u16 },
16    /// The listen port could not be bound for another reason.
17    Listen { port: u16, detail: String },
18    /// The discovery socket could not be opened; devices that find this one
19    /// first still work.
20    DiscoveryUnavailable { detail: String },
21    /// The device list could not be written to the trust store path.
22    TrustStore { detail: String },
23    /// A device key was not a 64-character SHA-256 in hex.
24    InvalidFingerprint,
25    /// A device record was asked for that has no record.
26    NotListed,
27    /// The default state was set to Allow; only single devices may be allowed.
28    AllowNotDefault,
29    /// Forgetting a record is refused while that device is on the network.
30    DeviceNearby,
31    /// The send target is not on the network.
32    DeviceNotNearby,
33    /// The send target is set to Deny.
34    DeviceBlocked,
35    /// The device announced no usable address.
36    NoUsableAddress,
37    /// The device's identity is not a valid fingerprint.
38    InvalidIdentity { peer: String },
39    /// The device answered over plain HTTP. Sends are refused without TLS.
40    PeerPlaintext { peer: String },
41    /// The certificate the device presented is not the one its fingerprint names.
42    /// Distinct from an unreachable device: the address answered with another key.
43    PeerIdentityMismatch { peer: String },
44    /// The device could not be connected to.
45    PeerUnreachable { peer: String, detail: String },
46    /// The connection to the device failed after it was made.
47    PeerLost { peer: String, detail: String },
48    /// The device answered with a status the protocol does not define here.
49    PeerAnswer { peer: String, status: u16 },
50    /// The device's answer could not be parsed.
51    PeerReply { peer: String },
52    /// The device asks for a PIN, which is not supported.
53    PeerNeedsPin { peer: String },
54    /// The device is busy with another transfer.
55    PeerBusy { peer: String },
56    /// The device refused the request as too large for an unproven sender.
57    PeerTooLarge { peer: String },
58    /// The device is rate limiting requests.
59    PeerRateLimited { peer: String },
60    /// The device refused one file.
61    FileRefused { peer: String, file: String },
62    /// The device reported that one file arrived damaged.
63    FileDamaged { peer: String, file: String },
64    /// Sending one file stopped.
65    FileStopped { file: String, detail: String },
66    /// More entries than the send limit.
67    TooManyFiles { max: usize },
68    /// Nothing to send after expanding the selection.
69    NothingToSend,
70    /// A chosen path is neither a file nor a folder.
71    NotFileOrFolder { path: String },
72    /// A received file's checksum did not match the one the sender named.
73    Damaged { file: String },
74    /// An internal failure that should not happen (serializing our own message).
75    Internal { detail: String },
76    /// A chosen path could not be read.
77    Unreadable { path: String, detail: String },
78    /// The sender stopped responding while a transfer was open.
79    SenderStopped,
80    /// A request from an unproven sender named too much (receive side).
81    Unverified { max_files: usize, max_bytes: u64 },
82    /// A received file could not be saved.
83    SaveFailed { file: String, detail: String },
84    /// A received file was cut off mid-transfer.
85    Interrupted { file: String, detail: String },
86    /// A received file could not be marked as coming from outside the computer.
87    /// An unproven sender's file is then not kept.
88    Mark { file: String, detail: String },
89}
90
91impl fmt::Display for Error {
92    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93        match self {
94            Error::Identity { detail } => write!(f, "identity unusable: {detail}"),
95            Error::PortInUse { port } => write!(f, "port {port} is already in use"),
96            Error::Listen { port, detail } => write!(f, "cannot listen on port {port}: {detail}"),
97            Error::DiscoveryUnavailable { detail } => write!(f, "discovery unavailable: {detail}"),
98            Error::TrustStore { detail } => write!(f, "cannot save the device list: {detail}"),
99            Error::InvalidFingerprint => write!(f, "fingerprint is not 64 hex characters"),
100            Error::NotListed => write!(f, "device is not in the list"),
101            Error::AllowNotDefault => write!(f, "the default state cannot be allow"),
102            Error::DeviceNearby => write!(f, "device is on the network now"),
103            Error::DeviceNotNearby => write!(f, "device is not on the network"),
104            Error::DeviceBlocked => write!(f, "device is set to deny"),
105            Error::NoUsableAddress => write!(f, "device has no usable address"),
106            Error::InvalidIdentity { peer } => write!(f, "{peer} presented no valid identity"),
107            Error::PeerPlaintext { peer } => write!(f, "{peer} has TLS turned off"),
108            Error::PeerIdentityMismatch { peer } => {
109                write!(f, "{peer}: certificate does not match its fingerprint")
110            }
111            Error::PeerUnreachable { peer, detail } => write!(f, "cannot reach {peer}: {detail}"),
112            Error::PeerLost { peer, detail } => {
113                write!(f, "lost the connection to {peer}: {detail}")
114            }
115            Error::PeerAnswer { peer, status } => write!(f, "{peer} answered {status}"),
116            Error::PeerReply { peer } => write!(f, "{peer} sent an answer that cannot be read"),
117            Error::PeerNeedsPin { peer } => write!(f, "{peer} requires a PIN"),
118            Error::PeerBusy { peer } => write!(f, "{peer} is busy with another transfer"),
119            Error::PeerTooLarge { peer } => {
120                write!(f, "{peer} refused: the request is over the unproven limit")
121            }
122            Error::PeerRateLimited { peer } => write!(f, "{peer} is rate limiting requests"),
123            Error::FileRefused { peer, file } => write!(f, "{peer} refused {file}"),
124            Error::FileDamaged { peer, file } => write!(f, "{peer} reports {file} arrived damaged"),
125            Error::FileStopped { file, detail } => write!(f, "sending {file} stopped: {detail}"),
126            Error::TooManyFiles { max } => write!(f, "more than {max} files"),
127            Error::NothingToSend => write!(f, "nothing to send"),
128            Error::NotFileOrFolder { path } => write!(f, "{path} is not a file or folder"),
129            Error::Unreadable { path, detail } => write!(f, "cannot read {path}: {detail}"),
130            Error::SenderStopped => write!(f, "the sender stopped responding"),
131            Error::Unverified {
132                max_files,
133                max_bytes,
134            } => write!(
135                f,
136                "a sender that cannot prove its identity may send at most {max_files} files and {max_bytes} bytes at once"
137            ),
138            Error::SaveFailed { file, detail } => write!(f, "cannot save {file}: {detail}"),
139            Error::Interrupted { file, detail } => {
140                write!(f, "transfer of {file} interrupted: {detail}")
141            }
142            Error::Mark { file, detail } => write!(f, "cannot mark {file} as downloaded: {detail}"),
143            Error::Damaged { file } => write!(f, "{file} arrived damaged"),
144            Error::Internal { detail } => write!(f, "internal error: {detail}"),
145        }
146    }
147}
148
149/// A failed connection to a peer. A certificate that is not the one the
150/// fingerprint names is `PeerIdentityMismatch`; anything else is `otherwise`.
151pub(crate) fn peer_io(
152    peer: &str,
153    error: &std::io::Error,
154    otherwise: impl FnOnce(String, String) -> Error,
155) -> Error {
156    if crate::net::is_identity_mismatch(error) {
157        Error::PeerIdentityMismatch {
158            peer: peer.to_string(),
159        }
160    } else {
161        otherwise(peer.to_string(), error.to_string())
162    }
163}
164
165impl std::error::Error for Error {}
166
167#[cfg(test)]
168mod tests {
169    use super::*;
170
171    #[test]
172    fn errors_carry_stable_codes() {
173        let error = Error::PeerIdentityMismatch {
174            peer: "Phone".into(),
175        };
176        let value = serde_json::to_value(&error).unwrap();
177        assert_eq!(value["code"], "peer_identity_mismatch");
178        assert_eq!(value["peer"], "Phone");
179        let unit = serde_json::to_value(&Error::InvalidFingerprint).unwrap();
180        assert_eq!(unit["code"], "invalid_fingerprint");
181        assert_eq!(
182            Error::PortInUse { port: 53317 }.to_string(),
183            "port 53317 is already in use"
184        );
185    }
186
187    #[test]
188    fn a_mismatch_is_distinct_from_other_connection_failures() {
189        let refused = std::io::Error::new(std::io::ErrorKind::ConnectionRefused, "refused");
190        let classified = peer_io("Phone", &refused, |peer, detail| Error::PeerUnreachable {
191            peer,
192            detail,
193        });
194        assert!(matches!(classified, Error::PeerUnreachable { .. }));
195    }
196}