1pub use ntp_proto::error::ParseError;
36
37use std::fmt;
38use std::io;
39
40#[derive(Debug)]
42pub enum NtpServerError {
43 Protocol(ProtocolError),
45 Nts(NtsError),
47 Config(ConfigError),
49 Io(io::Error),
51}
52
53#[derive(Clone, Debug)]
58pub enum ProtocolError {
59 RequestTooShort {
61 received: usize,
63 },
64 UnexpectedMode {
66 mode: u8,
68 },
69 UnsupportedVersion {
71 version: u8,
73 },
74 ZeroTransmitTimestamp,
76 NoExtensionFields,
78 Other(String),
80}
81
82#[derive(Clone, Debug)]
88pub enum NtsError {
89 MissingExtension {
91 field: &'static str,
93 },
94 CookieDecryptionFailed,
96 AuthenticatorParseFailed,
98 AuthenticationFailed,
100 KeyExportFailed {
102 detail: String,
104 },
105 KeyStorePoisoned,
107 UnrecognizedCriticalRecord {
109 record_type: u16,
111 },
112 MissingNextProtocol,
114 Other(String),
116}
117
118#[derive(Clone, Debug)]
120pub enum ConfigError {
121 InvalidListenAddress {
123 address: String,
125 detail: String,
127 },
128 InvalidTlsCredentials {
130 detail: String,
132 },
133 Other(String),
135}
136
137impl fmt::Display for NtpServerError {
140 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
141 match self {
142 NtpServerError::Protocol(e) => write!(f, "NTP server protocol error: {e}"),
143 NtpServerError::Nts(e) => write!(f, "NTS server error: {e}"),
144 NtpServerError::Config(e) => write!(f, "NTP server config error: {e}"),
145 NtpServerError::Io(e) => write!(f, "{e}"),
146 }
147 }
148}
149
150impl fmt::Display for ProtocolError {
151 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
152 match self {
153 ProtocolError::RequestTooShort { received } => {
154 write!(f, "NTP request too short ({received} bytes)")
155 }
156 ProtocolError::UnexpectedMode { mode } => {
157 write!(f, "unexpected request mode: {mode}")
158 }
159 ProtocolError::UnsupportedVersion { version } => {
160 write!(f, "unsupported NTP version: {version}")
161 }
162 ProtocolError::ZeroTransmitTimestamp => {
163 write!(f, "client transmit timestamp is zero")
164 }
165 ProtocolError::NoExtensionFields => {
166 write!(f, "request has no extension fields")
167 }
168 ProtocolError::Other(msg) => write!(f, "{msg}"),
169 }
170 }
171}
172
173impl fmt::Display for NtsError {
174 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
175 match self {
176 NtsError::MissingExtension { field } => {
177 write!(f, "missing NTS extension field: {field}")
178 }
179 NtsError::CookieDecryptionFailed => {
180 write!(f, "failed to decrypt NTS cookie (expired or invalid)")
181 }
182 NtsError::AuthenticatorParseFailed => {
183 write!(f, "failed to parse NTS Authenticator")
184 }
185 NtsError::AuthenticationFailed => {
186 write!(f, "NTS AEAD authentication failed")
187 }
188 NtsError::KeyExportFailed { detail } => {
189 write!(f, "TLS key export failed: {detail}")
190 }
191 NtsError::KeyStorePoisoned => {
192 write!(f, "master key store lock poisoned")
193 }
194 NtsError::UnrecognizedCriticalRecord { record_type } => {
195 write!(f, "unrecognized critical NTS-KE record type: {record_type}")
196 }
197 NtsError::MissingNextProtocol => {
198 write!(f, "client did not send a supported Next Protocol")
199 }
200 NtsError::Other(msg) => write!(f, "{msg}"),
201 }
202 }
203}
204
205impl fmt::Display for ConfigError {
206 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
207 match self {
208 ConfigError::InvalidListenAddress { address, detail } => {
209 write!(f, "invalid listen address '{address}': {detail}")
210 }
211 ConfigError::InvalidTlsCredentials { detail } => {
212 write!(f, "invalid TLS credentials: {detail}")
213 }
214 ConfigError::Other(msg) => write!(f, "{msg}"),
215 }
216 }
217}
218
219impl std::error::Error for NtpServerError {
222 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
223 match self {
224 NtpServerError::Io(e) => Some(e),
225 _ => None,
226 }
227 }
228}
229
230impl std::error::Error for ProtocolError {}
231impl std::error::Error for NtsError {}
232impl std::error::Error for ConfigError {}
233
234impl From<NtpServerError> for io::Error {
237 fn from(err: NtpServerError) -> io::Error {
238 let kind = match &err {
239 NtpServerError::Protocol(_) => io::ErrorKind::InvalidData,
240 NtpServerError::Nts(NtsError::KeyExportFailed { .. }) => io::ErrorKind::Other,
241 NtpServerError::Nts(NtsError::KeyStorePoisoned) => io::ErrorKind::Other,
242 NtpServerError::Nts(_) => io::ErrorKind::InvalidData,
243 NtpServerError::Config(_) => io::ErrorKind::InvalidInput,
244 NtpServerError::Io(e) => e.kind(),
245 };
246 if let NtpServerError::Io(e) = err {
248 return e;
249 }
250 io::Error::new(kind, err)
251 }
252}
253
254impl From<io::Error> for NtpServerError {
255 fn from(err: io::Error) -> NtpServerError {
256 NtpServerError::Io(err)
257 }
258}
259
260impl From<ProtocolError> for NtpServerError {
261 fn from(err: ProtocolError) -> NtpServerError {
262 NtpServerError::Protocol(err)
263 }
264}
265
266impl From<NtsError> for NtpServerError {
267 fn from(err: NtsError) -> NtpServerError {
268 NtpServerError::Nts(err)
269 }
270}
271
272impl From<ConfigError> for NtpServerError {
273 fn from(err: ConfigError) -> NtpServerError {
274 NtpServerError::Config(err)
275 }
276}
277
278#[cfg(test)]
281mod tests {
282 use super::*;
283
284 #[test]
285 fn test_protocol_error_display() {
286 let e = ProtocolError::RequestTooShort { received: 10 };
287 assert_eq!(e.to_string(), "NTP request too short (10 bytes)");
288 }
289
290 #[test]
291 fn test_protocol_error_unexpected_mode() {
292 let e = ProtocolError::UnexpectedMode { mode: 5 };
293 assert_eq!(e.to_string(), "unexpected request mode: 5");
294 }
295
296 #[test]
297 fn test_protocol_error_unsupported_version() {
298 let e = ProtocolError::UnsupportedVersion { version: 2 };
299 assert_eq!(e.to_string(), "unsupported NTP version: 2");
300 }
301
302 #[test]
303 fn test_protocol_error_zero_transmit() {
304 let e = ProtocolError::ZeroTransmitTimestamp;
305 assert_eq!(e.to_string(), "client transmit timestamp is zero");
306 }
307
308 #[test]
309 fn test_protocol_error_no_extensions() {
310 let e = ProtocolError::NoExtensionFields;
311 assert_eq!(e.to_string(), "request has no extension fields");
312 }
313
314 #[test]
315 fn test_nts_error_display() {
316 let e = NtsError::MissingExtension {
317 field: "Unique Identifier",
318 };
319 assert_eq!(
320 e.to_string(),
321 "missing NTS extension field: Unique Identifier"
322 );
323 }
324
325 #[test]
326 fn test_nts_error_cookie_decryption() {
327 let e = NtsError::CookieDecryptionFailed;
328 assert_eq!(
329 e.to_string(),
330 "failed to decrypt NTS cookie (expired or invalid)"
331 );
332 }
333
334 #[test]
335 fn test_nts_error_authentication() {
336 let e = NtsError::AuthenticationFailed;
337 assert_eq!(e.to_string(), "NTS AEAD authentication failed");
338 }
339
340 #[test]
341 fn test_nts_error_key_export() {
342 let e = NtsError::KeyExportFailed {
343 detail: "no session".to_string(),
344 };
345 assert_eq!(e.to_string(), "TLS key export failed: no session");
346 }
347
348 #[test]
349 fn test_nts_error_key_store_poisoned() {
350 let e = NtsError::KeyStorePoisoned;
351 assert_eq!(e.to_string(), "master key store lock poisoned");
352 }
353
354 #[test]
355 fn test_config_error_display() {
356 let e = ConfigError::InvalidListenAddress {
357 address: "bad:addr".to_string(),
358 detail: "not a valid socket address".to_string(),
359 };
360 assert_eq!(
361 e.to_string(),
362 "invalid listen address 'bad:addr': not a valid socket address"
363 );
364 }
365
366 #[test]
367 fn test_config_error_tls() {
368 let e = ConfigError::InvalidTlsCredentials {
369 detail: "bad PEM".to_string(),
370 };
371 assert_eq!(e.to_string(), "invalid TLS credentials: bad PEM");
372 }
373
374 #[test]
375 fn test_server_error_to_io_error_kind() {
376 let cases: Vec<(NtpServerError, io::ErrorKind)> = vec![
377 (
378 NtpServerError::Protocol(ProtocolError::ZeroTransmitTimestamp),
379 io::ErrorKind::InvalidData,
380 ),
381 (
382 NtpServerError::Nts(NtsError::AuthenticationFailed),
383 io::ErrorKind::InvalidData,
384 ),
385 (
386 NtpServerError::Nts(NtsError::KeyStorePoisoned),
387 io::ErrorKind::Other,
388 ),
389 (
390 NtpServerError::Config(ConfigError::Other("test".to_string())),
391 io::ErrorKind::InvalidInput,
392 ),
393 ];
394 for (srv_err, expected_kind) in cases {
395 let io_err: io::Error = srv_err.into();
396 assert_eq!(io_err.kind(), expected_kind);
397 }
398 }
399
400 #[test]
401 fn test_server_error_downcast_roundtrip() {
402 let err = NtpServerError::Protocol(ProtocolError::RequestTooShort { received: 10 });
403 let io_err: io::Error = err.into();
404 assert_eq!(io_err.kind(), io::ErrorKind::InvalidData);
405
406 let inner = io_err
407 .get_ref()
408 .unwrap()
409 .downcast_ref::<NtpServerError>()
410 .unwrap();
411 assert!(matches!(
412 inner,
413 NtpServerError::Protocol(ProtocolError::RequestTooShort { received: 10 })
414 ));
415 }
416
417 #[test]
418 fn test_io_error_passthrough() {
419 let orig = io::Error::new(io::ErrorKind::ConnectionReset, "reset");
420 let kind = orig.kind();
421 let srv_err = NtpServerError::Io(orig);
422 let io_err: io::Error = srv_err.into();
423 assert_eq!(io_err.kind(), kind);
424 assert_eq!(io_err.to_string(), "reset");
425 }
426
427 #[test]
428 fn test_from_io_error() {
429 let orig = io::Error::new(io::ErrorKind::BrokenPipe, "broken");
430 let srv_err: NtpServerError = orig.into();
431 assert!(matches!(srv_err, NtpServerError::Io(_)));
432 }
433
434 #[test]
435 fn test_from_protocol_error() {
436 let proto_err = ProtocolError::ZeroTransmitTimestamp;
437 let srv_err: NtpServerError = proto_err.into();
438 assert!(matches!(srv_err, NtpServerError::Protocol(_)));
439 }
440
441 #[test]
442 fn test_from_nts_error() {
443 let nts_err = NtsError::AuthenticationFailed;
444 let srv_err: NtpServerError = nts_err.into();
445 assert!(matches!(srv_err, NtpServerError::Nts(_)));
446 }
447
448 #[test]
449 fn test_from_config_error() {
450 let cfg_err = ConfigError::Other("test".to_string());
451 let srv_err: NtpServerError = cfg_err.into();
452 assert!(matches!(srv_err, NtpServerError::Config(_)));
453 }
454
455 #[test]
456 fn test_ntp_server_error_source() {
457 let io_err = io::Error::new(io::ErrorKind::BrokenPipe, "broken");
458 let srv_err = NtpServerError::Io(io_err);
459 assert!(std::error::Error::source(&srv_err).is_some());
460
461 let proto_err = NtpServerError::Protocol(ProtocolError::ZeroTransmitTimestamp);
462 assert!(std::error::Error::source(&proto_err).is_none());
463 }
464}