Coverage Report

Created: 2026-11-12 06:59

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/ntp-parser-0.6.0/src/ntp.rs
Line
Count
Source
1
use nom::bytes::streaming::take;
2
use nom::combinator::{complete, map, map_parser, opt};
3
use nom::error::{make_error, ErrorKind};
4
use nom::multi::many1;
5
use nom::number::streaming::be_u8;
6
pub use nom::{Err, IResult};
7
use nom_derive::*;
8
9
#[derive(Debug, PartialEq)]
10
pub enum NtpPacket<'a> {
11
    V3(NtpV3Packet<'a>),
12
    V4(NtpV4Packet<'a>),
13
}
14
15
0
#[derive(Clone, Copy, Debug, Eq, PartialEq, NomBE)]
Unexecuted instantiation: <ntp_parser::ntp::NtpMode as nom_derive::traits::Parse<&[u8]>>::parse_be
Unexecuted instantiation: <ntp_parser::ntp::NtpMode as nom_derive::traits::Parse<&[u8]>>::parse_le
Unexecuted instantiation: <ntp_parser::ntp::NtpMode as nom_derive::traits::Parse<&[u8]>>::parse_be
Unexecuted instantiation: <ntp_parser::ntp::NtpMode as nom_derive::traits::Parse<&[u8]>>::parse_le
16
pub struct NtpMode(pub u8);
17
18
#[allow(non_upper_case_globals)]
19
impl NtpMode {
20
    pub const Reserved: NtpMode = NtpMode(0);
21
    pub const SymmetricActive: NtpMode = NtpMode(1);
22
    pub const SymmetricPassive: NtpMode = NtpMode(2);
23
    pub const Client: NtpMode = NtpMode(3);
24
    pub const Server: NtpMode = NtpMode(4);
25
    pub const Broadcast: NtpMode = NtpMode(5);
26
    pub const NtpControlMessage: NtpMode = NtpMode(6);
27
    pub const Private: NtpMode = NtpMode(7);
28
}
29
30
/// An NTP version 3 packet
31
#[derive(Debug, PartialEq, NomBE)]
32
pub struct NtpV3Packet<'a> {
33
    #[nom(PreExec = "let (i, b0) = be_u8(i)?;")]
34
    #[nom(Value(b0 >> 6))]
35
    pub li: u8,
36
    #[nom(Value((b0 >> 3) & 0b111))]
37
    pub version: u8,
38
    #[nom(Value(NtpMode(b0 & 0b111)))]
39
    pub mode: NtpMode,
40
    pub stratum: u8,
41
    pub poll: i8,
42
    pub precision: i8,
43
    pub root_delay: u32,
44
    pub root_dispersion: u32,
45
    pub ref_id: u32,
46
    pub ts_ref: u64,
47
    pub ts_orig: u64,
48
    pub ts_recv: u64,
49
    pub ts_xmit: u64,
50
51
    #[nom(Parse = "opt(complete(take(12usize)))")]
52
    pub authenticator: Option<&'a [u8]>,
53
}
54
55
/// An NTP version 4 packet
56
#[derive(Debug, PartialEq, NomBE)]
57
pub struct NtpV4Packet<'a> {
58
    #[nom(PreExec = "let (i, b0) = be_u8(i)?;")]
59
    #[nom(Value(b0 >> 6))]
60
    pub li: u8,
61
    #[nom(Value((b0 >> 3) & 0b111))]
62
    pub version: u8,
63
    #[nom(Value(NtpMode(b0 & 0b111)))]
64
    pub mode: NtpMode,
65
    pub stratum: u8,
66
    pub poll: i8,
67
    pub precision: i8,
68
    pub root_delay: u32,
69
    pub root_dispersion: u32,
70
    pub ref_id: u32,
71
    pub ts_ref: u64,
72
    pub ts_orig: u64,
73
    pub ts_recv: u64,
74
    pub ts_xmit: u64,
75
76
    #[nom(Parse = "try_parse_extensions")]
77
    pub extensions: Vec<NtpExtension<'a>>,
78
    #[nom(Cond(!i.is_empty()))]
79
    pub auth: Option<NtpMac<'a>>,
80
}
81
82
impl<'a> NtpV4Packet<'a> {
83
0
    pub fn get_precision(&self) -> f32 {
84
0
        2.0_f32.powf(self.precision as f32)
85
0
    }
Unexecuted instantiation: <ntp_parser::ntp::NtpV4Packet>::get_precision
Unexecuted instantiation: <ntp_parser::ntp::NtpV4Packet>::get_precision
86
}
87
88
2.65k
#[derive(Debug, PartialEq, NomBE)]
<ntp_parser::ntp::NtpExtension as nom_derive::traits::Parse<&[u8]>>::parse_be
Line
Count
Source
88
680
#[derive(Debug, PartialEq, NomBE)]
Unexecuted instantiation: <ntp_parser::ntp::NtpExtension as nom_derive::traits::Parse<&[u8]>>::parse_le
<ntp_parser::ntp::NtpExtension as nom_derive::traits::Parse<&[u8]>>::parse_be
Line
Count
Source
88
1.97k
#[derive(Debug, PartialEq, NomBE)]
Unexecuted instantiation: <ntp_parser::ntp::NtpExtension as nom_derive::traits::Parse<&[u8]>>::parse_le
89
pub struct NtpExtension<'a> {
90
    pub field_type: u16,
91
    pub length: u16,
92
    #[nom(Parse = "take(length)")]
93
    pub value: &'a [u8],
94
    /*padding*/
95
}
96
97
0
#[derive(Debug, PartialEq, NomBE)]
Unexecuted instantiation: <ntp_parser::ntp::NtpMac as nom_derive::traits::Parse<&[u8]>>::parse_be
Unexecuted instantiation: <ntp_parser::ntp::NtpMac as nom_derive::traits::Parse<&[u8]>>::parse_le
Unexecuted instantiation: <ntp_parser::ntp::NtpMac as nom_derive::traits::Parse<&[u8]>>::parse_be
Unexecuted instantiation: <ntp_parser::ntp::NtpMac as nom_derive::traits::Parse<&[u8]>>::parse_le
98
pub struct NtpMac<'a> {
99
    pub key_id: u32,
100
    #[nom(Parse = "take(16usize)")]
101
    pub mac: &'a [u8],
102
}
103
104
#[inline]
105
1.71M
pub fn parse_ntp_extension(i: &[u8]) -> IResult<&[u8], NtpExtension> {
106
1.71M
    NtpExtension::parse(i)
107
1.71M
}
ntp_parser::ntp::parse_ntp_extension
Line
Count
Source
105
359k
pub fn parse_ntp_extension(i: &[u8]) -> IResult<&[u8], NtpExtension> {
106
359k
    NtpExtension::parse(i)
107
359k
}
ntp_parser::ntp::parse_ntp_extension
Line
Count
Source
105
1.35M
pub fn parse_ntp_extension(i: &[u8]) -> IResult<&[u8], NtpExtension> {
106
1.35M
    NtpExtension::parse(i)
107
1.35M
}
108
109
// Attempt to parse extensions.
110
//
111
// See section 7.5 of [RFC5905] and [RFC7822]:
112
// In NTPv4, one or more extension fields can be inserted after the
113
//    header and before the MAC, which is always present when an extension
114
//    field is present.
115
//
116
// So:
117
//  if == 20, only MAC
118
//  if >  20, ext + MAC
119
//  if ==  0, nothing
120
//  else      error
121
5.47k
fn try_parse_extensions(i: &[u8]) -> IResult<&[u8], Vec<NtpExtension>> {
122
5.47k
    if i.is_empty() || i.len() == 20 {
123
        // if empty, or if remaining length is exactly the MAC length (20), assume we do not have
124
        // extensions
125
1.50k
        return Ok((i, Vec::new()));
126
3.97k
    }
127
3.97k
    if i.len() < 20 {
128
28
        return Err(Err::Error(make_error(i, ErrorKind::Eof)));
129
3.94k
    }
130
3.94k
    map_parser(take(i.len() - 20), many1(complete(parse_ntp_extension)))(i)
131
5.47k
}
ntp_parser::ntp::try_parse_extensions
Line
Count
Source
121
1.05k
fn try_parse_extensions(i: &[u8]) -> IResult<&[u8], Vec<NtpExtension>> {
122
1.05k
    if i.is_empty() || i.len() == 20 {
123
        // if empty, or if remaining length is exactly the MAC length (20), assume we do not have
124
        // extensions
125
123
        return Ok((i, Vec::new()));
126
936
    }
127
936
    if i.len() < 20 {
128
18
        return Err(Err::Error(make_error(i, ErrorKind::Eof)));
129
918
    }
130
918
    map_parser(take(i.len() - 20), many1(complete(parse_ntp_extension)))(i)
131
1.05k
}
ntp_parser::ntp::try_parse_extensions
Line
Count
Source
121
4.41k
fn try_parse_extensions(i: &[u8]) -> IResult<&[u8], Vec<NtpExtension>> {
122
4.41k
    if i.is_empty() || i.len() == 20 {
123
        // if empty, or if remaining length is exactly the MAC length (20), assume we do not have
124
        // extensions
125
1.38k
        return Ok((i, Vec::new()));
126
3.03k
    }
127
3.03k
    if i.len() < 20 {
128
10
        return Err(Err::Error(make_error(i, ErrorKind::Eof)));
129
3.02k
    }
130
3.02k
    map_parser(take(i.len() - 20), many1(complete(parse_ntp_extension)))(i)
131
4.41k
}
132
133
/// Parse an NTP version 3 packet (RFC 1305)
134
#[inline]
135
0
pub fn parse_ntpv3(i: &[u8]) -> IResult<&[u8], NtpV3Packet> {
136
0
    NtpV3Packet::parse(i)
137
0
}
Unexecuted instantiation: ntp_parser::ntp::parse_ntpv3
Unexecuted instantiation: ntp_parser::ntp::parse_ntpv3
138
139
/// Parse an NTP version 4 packet (RFC 1305)
140
#[inline]
141
0
pub fn parse_ntpv4(i: &[u8]) -> IResult<&[u8], NtpV4Packet> {
142
0
    NtpV4Packet::parse(i)
143
0
}
Unexecuted instantiation: ntp_parser::ntp::parse_ntpv4
Unexecuted instantiation: ntp_parser::ntp::parse_ntpv4
144
145
/// Parse an NTP packet, version 3 or 4
146
#[inline]
147
9.41k
pub fn parse_ntp(i: &[u8]) -> IResult<&[u8], NtpPacket> {
148
9.41k
    let (_, b0) = be_u8(i)?;
149
9.40k
    match (b0 >> 3) & 0b111 {
150
3.79k
        3 => map(NtpV3Packet::parse, NtpPacket::V3)(i),
151
5.58k
        4 => map(NtpV4Packet::parse, NtpPacket::V4)(i),
152
30
        _ => Err(Err::Error(make_error(i, ErrorKind::Tag))),
153
    }
154
9.41k
}
ntp_parser::ntp::parse_ntp
Line
Count
Source
147
1.56k
pub fn parse_ntp(i: &[u8]) -> IResult<&[u8], NtpPacket> {
148
1.56k
    let (_, b0) = be_u8(i)?;
149
1.55k
    match (b0 >> 3) & 0b111 {
150
410
        3 => map(NtpV3Packet::parse, NtpPacket::V3)(i),
151
1.12k
        4 => map(NtpV4Packet::parse, NtpPacket::V4)(i),
152
19
        _ => Err(Err::Error(make_error(i, ErrorKind::Tag))),
153
    }
154
1.56k
}
ntp_parser::ntp::parse_ntp
Line
Count
Source
147
7.85k
pub fn parse_ntp(i: &[u8]) -> IResult<&[u8], NtpPacket> {
148
7.85k
    let (_, b0) = be_u8(i)?;
149
7.85k
    match (b0 >> 3) & 0b111 {
150
3.38k
        3 => map(NtpV3Packet::parse, NtpPacket::V3)(i),
151
4.45k
        4 => map(NtpV4Packet::parse, NtpPacket::V4)(i),
152
11
        _ => Err(Err::Error(make_error(i, ErrorKind::Tag))),
153
    }
154
7.85k
}
155
156
#[cfg(test)]
157
mod tests {
158
    use crate::ntp::*;
159
160
    static NTP_REQ1: &[u8] = &[
161
        0xd9, 0x00, 0x0a, 0xfa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x90, 0x00, 0x00, 0x00,
162
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
163
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc5, 0x02, 0x04, 0xec, 0xec,
164
        0x42, 0xee, 0x92,
165
    ];
166
167
    #[test]
168
    fn test_ntp_packet_simple() {
169
        let empty = &b""[..];
170
        let bytes = NTP_REQ1;
171
        let expected = NtpV4Packet {
172
            li: 3,
173
            version: 3,
174
            mode: NtpMode::SymmetricActive,
175
            stratum: 0,
176
            poll: 10,
177
            precision: -6,
178
            root_delay: 0,
179
            root_dispersion: 0x010290,
180
            ref_id: 0,
181
            ts_ref: 0,
182
            ts_orig: 0,
183
            ts_recv: 0,
184
            ts_xmit: 14195914391047827090u64,
185
            extensions: Vec::new(),
186
            auth: None,
187
        };
188
        let res = parse_ntpv4(&bytes);
189
        assert_eq!(res, Ok((empty, expected)));
190
    }
191
192
    static NTP_REQ2: &[u8] = &[
193
        0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
194
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
195
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcc, 0x25, 0xcc, 0x13, 0x2b,
196
        0x02, 0x10, 0x00, 0x00, 0x00, 0x00, 0x01, 0x52, 0x80, 0x0c, 0x2b, 0x59, 0x00, 0x64, 0x66,
197
        0x84, 0xf4, 0x4c, 0xa4, 0xee, 0xce, 0x12, 0xb8,
198
    ];
199
200
    #[test]
201
    fn test_ntp_packet_mac() {
202
        let empty = &b""[..];
203
        let bytes = NTP_REQ2;
204
        let expected = NtpV4Packet {
205
            li: 0,
206
            version: 4,
207
            mode: NtpMode::Client,
208
            stratum: 0,
209
            poll: 0,
210
            precision: 0,
211
            root_delay: 12,
212
            root_dispersion: 0,
213
            ref_id: 0,
214
            ts_ref: 0,
215
            ts_orig: 0,
216
            ts_recv: 0,
217
            ts_xmit: 14710388140573593600,
218
            extensions: Vec::new(),
219
            auth: Some(NtpMac {
220
                key_id: 1,
221
                mac: &bytes[52..],
222
            }),
223
        };
224
        let res = parse_ntpv4(&bytes);
225
        assert_eq!(res, Ok((empty, expected)));
226
    }
227
228
    static NTP_REQ2B: &[u8] = &[
229
        0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
230
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
231
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcc, 0x25, 0xcc, 0x13, 0x2b,
232
        0x02, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x52, 0x80, 0x0c, 0x2b,
233
        0x59, 0x00, 0x64, 0x66, 0x84, 0xf4, 0x4c, 0xa4, 0xee, 0xce, 0x12, 0xb8,
234
    ];
235
236
    #[test]
237
    fn test_ntp_packet_extension() {
238
        let empty = &b""[..];
239
        let bytes = NTP_REQ2B;
240
        let expected = NtpV4Packet {
241
            li: 0,
242
            version: 4,
243
            mode: NtpMode::Client,
244
            stratum: 0,
245
            poll: 0,
246
            precision: 0,
247
            root_delay: 12,
248
            root_dispersion: 0,
249
            ref_id: 0,
250
            ts_ref: 0,
251
            ts_orig: 0,
252
            ts_recv: 0,
253
            ts_xmit: 14710388140573593600,
254
            extensions: vec![NtpExtension {
255
                field_type: 0,
256
                length: 0,
257
                value: empty,
258
            }],
259
            auth: Some(NtpMac {
260
                key_id: 1,
261
                mac: &bytes[56..],
262
            }),
263
        };
264
        let res = parse_ntpv4(&bytes);
265
        assert_eq!(res, Ok((empty, expected)));
266
    }
267
268
    // from wireshark test captures 'ntp.pcap'
269
    static NTPV3_REQ: &[u8] = &[
270
        0x1b, 0x04, 0x06, 0xf5, 0x00, 0x00, 0x10, 0x0d, 0x00, 0x00, 0x05, 0x57, 0x82, 0xdc, 0x18,
271
        0x18, 0xba, 0x29, 0x66, 0x36, 0x7d, 0xd0, 0x00, 0x00, 0xba, 0x29, 0x66, 0x36, 0x7d, 0x58,
272
        0x40, 0x00, 0xba, 0x29, 0x66, 0x36, 0x7d, 0xd0, 0x00, 0x00, 0xba, 0x29, 0x66, 0x76, 0x7d,
273
        0x50, 0x50, 0x00,
274
    ];
275
276
    #[test]
277
    fn test_ntp_packet_v3() {
278
        let empty = &b""[..];
279
        let bytes = NTPV3_REQ;
280
        let expected = NtpV3Packet {
281
            li: 0,
282
            version: 3,
283
            mode: NtpMode::Client,
284
            stratum: 4,
285
            poll: 6,
286
            precision: -11,
287
            root_delay: 4109,
288
            root_dispersion: 0x0557,
289
            ref_id: 0x82dc1818,
290
            ts_ref: 0xba296636_7dd00000,
291
            ts_orig: 0xba296636_7d584000,
292
            ts_recv: 0xba296636_7dd00000,
293
            ts_xmit: 0xba296676_7d505000,
294
            authenticator: None,
295
        };
296
        let res = NtpV3Packet::parse(&bytes);
297
        assert_eq!(res, Ok((empty, expected)));
298
    }
299
}