1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
use std::time::{Duration, SystemTime, UNIX_EPOCH};
/// NTP timestamp representation (64 bits)
/// As defined in RFC 3550
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct NtpTimestamp {
/// Seconds since January 1, 1900
pub seconds: u32,
/// Fraction of a second
pub fraction: u32,
}
impl NtpTimestamp {
/// Create a new NTP timestamp from the current system time
pub fn now() -> Self {
// Get current time since UNIX epoch
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_else(|_| Duration::from_secs(0));
// Convert to NTP timestamp (seconds since January 1, 1900)
// NTP epoch starts 70 years before UNIX epoch (2208988800 seconds)
let ntp_seconds = now.as_secs() + 2208988800;
// Convert nanoseconds to NTP fraction (2^32 / 10^9)
let nanos = now.subsec_nanos();
let ntp_fraction = (nanos as u64 * 0x100000000u64 / 1_000_000_000) as u32;
Self {
seconds: ntp_seconds as u32,
fraction: ntp_fraction,
}
}
/// Convert to a 64-bit representation
pub fn to_u64(&self) -> u64 {
(self.seconds as u64) << 32 | (self.fraction as u64)
}
/// Convert to a 32-bit representation for RTCP reports
///
/// Returns the middle 32 bits of the NTP timestamp, which is used in RTCP
/// report blocks (last_sr field) for RTT calculations.
/// This is defined in RFC 3550 Section 6.4.1.
pub fn to_u32(&self) -> u32 {
// Take the middle 16 bits of seconds and the most significant 16 bits of fraction
((self.seconds & 0x0000FFFF) << 16) | ((self.fraction & 0xFFFF0000) >> 16)
}
/// Convert from a 64-bit representation
pub fn from_u64(value: u64) -> Self {
Self {
seconds: (value >> 32) as u32,
fraction: value as u32,
}
}
/// Convert to a Duration since UNIX epoch
pub fn to_duration_since_unix_epoch(&self) -> Duration {
// NTP epoch to UNIX epoch offset (70 years in seconds)
const NTP_TO_UNIX_OFFSET: u64 = 2208988800;
// Calculate seconds, handling underflow if the timestamp is before UNIX epoch
let seconds = if self.seconds as u64 > NTP_TO_UNIX_OFFSET {
self.seconds as u64 - NTP_TO_UNIX_OFFSET
} else {
0 // If timestamp predates UNIX epoch, return 0
};
// Convert fraction to nanoseconds (fraction * 10^9 / 2^32)
let nanos = ((self.fraction as u64) * 1_000_000_000) >> 32;
Duration::new(seconds, nanos as u32)
}
/// Create a new NTP timestamp from a Duration since UNIX epoch
pub fn from_duration_since_unix_epoch(duration: Duration) -> Self {
// NTP epoch to UNIX epoch offset (70 years in seconds)
const UNIX_TO_NTP_OFFSET: u64 = 2208988800;
// Convert seconds
let seconds = duration.as_secs() + UNIX_TO_NTP_OFFSET;
// Convert nanoseconds to fraction
let nanos = duration.subsec_nanos();
let fraction = ((nanos as u64 * 0x100000000u64) / 1_000_000_000) as u32;
Self {
seconds: seconds as u32,
fraction,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ntp_timestamp_creation() {
let timestamp = NtpTimestamp::now();
// The timestamp should be roughly the current time
// We just check it's in a reasonable range (after 2020)
assert!(timestamp.seconds > 3786825600); // Jan 1, 2020 in NTP time
}
#[test]
fn test_ntp_timestamp_conversion() {
// Create a test timestamp
let timestamp = NtpTimestamp {
seconds: 3786825600, // Jan 1, 2020 in NTP time
fraction: 0x80000000, // 0.5 seconds
};
// Convert to u64 and back
let u64_value = timestamp.to_u64();
let converted = NtpTimestamp::from_u64(u64_value);
assert_eq!(converted.seconds, timestamp.seconds);
assert_eq!(converted.fraction, timestamp.fraction);
}
#[test]
fn test_ntp_timestamp_to_duration() {
// Create a test timestamp for Jan 1, 2020, 00:00:00.5
let timestamp = NtpTimestamp {
seconds: 3786825600, // Jan 1, 2020 in NTP time
fraction: 0x80000000, // 0.5 seconds
};
let duration = timestamp.to_duration_since_unix_epoch();
// Expected: Jan 1, 2020 minus NTP epoch offset plus 0.5 seconds
assert_eq!(duration.as_secs(), 1577836800); // Jan 1, 2020 in UNIX time
assert!(duration.subsec_nanos() > 499000000 && duration.subsec_nanos() < 501000000);
}
#[test]
fn test_ntp_timestamp_from_duration() {
// Create a duration for Jan 1, 2020, 00:00:00.5
let duration = Duration::new(1577836800, 500000000);
let timestamp = NtpTimestamp::from_duration_since_unix_epoch(duration);
// Expected: Jan 1, 2020 in NTP time plus 0.5 seconds
assert_eq!(timestamp.seconds, 3786825600);
// Fraction should be close to 0.5 (0x80000000)
let expected = 0x80000000u32;
let tolerance = 100; // Allow small rounding errors
assert!(
timestamp.fraction >= expected.saturating_sub(tolerance)
&& timestamp.fraction <= expected.saturating_add(tolerance)
);
}
}