Skip to main content

ntp/
error.rs

1// Copyright 2026 U.S. Federal Government (in countries where recognized)
2// SPDX-License-Identifier: Apache-2.0
3
4//! Custom error types for buffer-based NTP packet parsing and serialization.
5//!
6//! [`ParseError`] is designed to be `no_std`-compatible, using no heap allocation.
7//! When the `std` feature is enabled, it also implements [`std::error::Error`] and
8//! can be converted to [`std::io::Error`].
9
10use core::fmt;
11
12/// Errors that can occur during buffer-based NTP packet parsing or serialization.
13#[derive(Clone, Debug, Eq, PartialEq)]
14pub enum ParseError {
15    /// The buffer is too short for the expected data.
16    BufferTooShort {
17        /// Number of bytes needed.
18        needed: usize,
19        /// Number of bytes available.
20        available: usize,
21    },
22    /// An invalid or unrecognized field value was encountered.
23    InvalidField {
24        /// Name of the field that was invalid.
25        field: &'static str,
26        /// The invalid value.
27        value: u32,
28    },
29    /// Extension field has an invalid length (less than 4 bytes).
30    InvalidExtensionLength {
31        /// The declared length that was invalid.
32        declared: u16,
33    },
34    /// Extension field data extends beyond the buffer.
35    ExtensionOverflow,
36}
37
38impl fmt::Display for ParseError {
39    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40        match self {
41            ParseError::BufferTooShort { needed, available } => {
42                write!(
43                    f,
44                    "buffer too short: needed {} bytes, got {}",
45                    needed, available
46                )
47            }
48            ParseError::InvalidField { field, value } => {
49                write!(f, "invalid {} value: {}", field, value)
50            }
51            ParseError::InvalidExtensionLength { declared } => {
52                write!(f, "extension field length less than 4: {}", declared)
53            }
54            ParseError::ExtensionOverflow => {
55                write!(f, "extension field value extends beyond packet")
56            }
57        }
58    }
59}
60
61#[cfg(feature = "std")]
62impl From<ParseError> for std::io::Error {
63    fn from(err: ParseError) -> std::io::Error {
64        let kind = match &err {
65            ParseError::BufferTooShort { .. } => std::io::ErrorKind::UnexpectedEof,
66            ParseError::InvalidField { .. } => std::io::ErrorKind::InvalidData,
67            ParseError::InvalidExtensionLength { .. } => std::io::ErrorKind::InvalidData,
68            ParseError::ExtensionOverflow => std::io::ErrorKind::InvalidData,
69        };
70        std::io::Error::new(kind, err)
71    }
72}
73
74#[cfg(feature = "std")]
75impl std::error::Error for ParseError {}
76
77#[cfg(all(test, feature = "std"))]
78mod tests {
79    use super::*;
80
81    #[test]
82    fn test_display_buffer_too_short() {
83        let err = ParseError::BufferTooShort {
84            needed: 48,
85            available: 10,
86        };
87        assert_eq!(err.to_string(), "buffer too short: needed 48 bytes, got 10");
88    }
89
90    #[test]
91    fn test_display_invalid_field() {
92        let err = ParseError::InvalidField {
93            field: "leap indicator",
94            value: 5,
95        };
96        assert_eq!(err.to_string(), "invalid leap indicator value: 5");
97    }
98
99    #[test]
100    fn test_display_invalid_extension_length() {
101        let err = ParseError::InvalidExtensionLength { declared: 2 };
102        assert_eq!(err.to_string(), "extension field length less than 4: 2");
103    }
104
105    #[test]
106    fn test_display_extension_overflow() {
107        let err = ParseError::ExtensionOverflow;
108        assert_eq!(
109            err.to_string(),
110            "extension field value extends beyond packet"
111        );
112    }
113
114    #[test]
115    fn test_into_io_error() {
116        let parse_err = ParseError::BufferTooShort {
117            needed: 48,
118            available: 0,
119        };
120        let io_err: std::io::Error = parse_err.into();
121        assert_eq!(io_err.kind(), std::io::ErrorKind::UnexpectedEof);
122    }
123
124    #[test]
125    fn test_parse_error_is_std_error() {
126        let err: Box<dyn std::error::Error> = Box::new(ParseError::ExtensionOverflow);
127        assert_eq!(
128            err.to_string(),
129            "extension field value extends beyond packet"
130        );
131    }
132}