1use 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 Identity { detail: String },
14 PortInUse { port: u16 },
16 Listen { port: u16, detail: String },
18 DiscoveryUnavailable { detail: String },
21 TrustStore { detail: String },
23 InvalidFingerprint,
25 NotListed,
27 AllowNotDefault,
29 DeviceNearby,
31 DeviceNotNearby,
33 DeviceBlocked,
35 NoUsableAddress,
37 InvalidIdentity { peer: String },
39 PeerPlaintext { peer: String },
41 PeerIdentityMismatch { peer: String },
44 PeerUnreachable { peer: String, detail: String },
46 PeerLost { peer: String, detail: String },
48 PeerAnswer { peer: String, status: u16 },
50 PeerReply { peer: String },
52 PeerNeedsPin { peer: String },
54 PeerBusy { peer: String },
56 PeerTooLarge { peer: String },
58 PeerRateLimited { peer: String },
60 FileRefused { peer: String, file: String },
62 FileDamaged { peer: String, file: String },
64 FileStopped { file: String, detail: String },
66 TooManyFiles { max: usize },
68 NothingToSend,
70 NotFileOrFolder { path: String },
72 Damaged { file: String },
74 Internal { detail: String },
76 Unreadable { path: String, detail: String },
78 SenderStopped,
80 Unverified { max_files: usize, max_bytes: u64 },
82 SaveFailed { file: String, detail: String },
84 Interrupted { file: String, detail: String },
86 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
149pub(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}