Skip to main content

netstack3_base/tcp/
timestamp.rs

1// Copyright 2025 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! TCP Timestamp Option as defined in RFC 7323.
6
7use core::cmp::{Ord, Ordering, PartialOrd};
8use core::marker::PhantomData;
9use core::ops::Add;
10use core::time::Duration;
11use derivative::Derivative;
12
13/// A timestamp to be used in the TCP Timestamp Option.
14///
15/// Per RFC 7323 Section 5.2:
16///   the "timestamps" are 32-bit unsigned integers in a modular 32-bit space.
17///
18/// Note: timestamps have no inherent units. The sender and receiver are each
19/// free to decide upon the units relevant to the timestamps they generate.
20/// However, per RFC 7323 Section 5.4:
21///   (a)  The timestamp clock must not be "too slow". [...]
22///   (b)  The timestamp clock must not be "too fast". [...]
23///   Based upon these considerations, we choose a timestamp clock frequency in
24///   the range 1 ms to 1 sec per tick.
25#[derive(Debug, Derivative, Clone, Copy)]
26#[derivative(Eq(bound = ""), PartialEq(bound = ""))]
27pub struct Timestamp<U> {
28    timestamp: u32,
29    unit: PhantomData<U>,
30}
31
32/// A marker type indicating that the backing units of a [`Timestamp`] are
33/// unknown.
34#[derive(Debug, Clone, Copy)]
35pub enum Unitless {}
36
37/// A marker type indicating that the backing units of a [`Timestamp`] are
38/// milliseconds.
39///
40/// Note: Based on the considerations from RFC 7323 section 5.4, milliseconds
41/// are the most appropriate units to use for timestamps generated by ourselves.
42/// This choice supports bandwidths up to 8 Tbps, and idle connections up to 24
43/// days.
44#[derive(Debug, Clone, Copy)]
45pub enum Milliseconds {}
46
47impl<U> Timestamp<U> {
48    /// Constructs a new [`Timestamp`] from a raw [`u32`].
49    pub const fn new(timestamp: u32) -> Self {
50        Timestamp { timestamp, unit: PhantomData::<U> }
51    }
52
53    /// Retrieve the raw 32-bit timestamp value.
54    pub const fn get(&self) -> u32 {
55        let Timestamp { timestamp, unit: _ } = self;
56        *timestamp
57    }
58}
59
60impl Timestamp<Milliseconds> {
61    /// Computes `self` - `other`, returning `None` if `self` is before `other`.
62    pub fn duration_since(&self, other: &Timestamp<Milliseconds>) -> Option<Duration> {
63        if self < other {
64            return None;
65        }
66        // Per RFC 7323 Section 5.2:
67        //   the "timestamps" are 32-bit unsigned integers in a modular 32-bit
68        //   space.
69        // As such, the diff is computed with wrapping subtraction.
70        let diff = self.timestamp.wrapping_sub(other.timestamp);
71        Some(Duration::from_millis(diff.into()))
72    }
73}
74
75impl<U> Ord for Timestamp<U> {
76    fn cmp(&self, rhs: &Timestamp<U>) -> Ordering {
77        let Timestamp { timestamp: lhs, unit: _ } = self;
78        let Timestamp { timestamp: rhs, unit: _ } = rhs;
79        // Per RFC 7323 Section 5.2:
80        //   Thus, "less than" is defined the same way it is for TCP sequence
81        //   numbers, and the same implementation techniques apply.  If s and t
82        //   are timestamp values,
83        //       s < t  if 0 < (t - s) < 2^31,
84        //   computed in unsigned 32-bit arithmetic.
85        let delta = rhs.wrapping_sub(*lhs);
86        if delta == 0 {
87            Ordering::Equal
88        } else if delta > 0 && delta < (1 << 31) {
89            Ordering::Less
90        } else {
91            Ordering::Greater
92        }
93    }
94}
95
96impl<U> PartialOrd for Timestamp<U> {
97    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
98        Some(self.cmp(other))
99    }
100}
101
102impl Add<Duration> for Timestamp<Milliseconds> {
103    type Output = Self;
104
105    fn add(self, rhs: Duration) -> Self::Output {
106        let Timestamp { timestamp: lhs, unit } = self;
107        let rhs: u128 = rhs.as_millis();
108        // The following `as` coercion is sound because `Timestamp` exists in
109        // modular 32-bit space. This drops the higher order bits from the
110        // original value, which is representative of the `Timestamp` wrapping
111        // around.
112        let rhs = rhs as u32;
113        Timestamp { timestamp: lhs.wrapping_add(rhs), unit }
114    }
115}
116
117/// The TCP timestamp option as defined in RFC 7323, Section 3.
118#[derive(Debug, PartialEq, Eq, Clone, Copy)]
119pub struct TimestampOption {
120    /// The timestamp value.
121    pub(super) ts_val: Timestamp<Unitless>,
122    /// The timestamp echo reply.
123    pub(super) ts_echo_reply: Timestamp<Unitless>,
124}
125
126/// Like `TimestampOption` but with units appropriate for a timestamp option
127/// sent by us.
128#[derive(Debug, PartialEq, Eq, Clone, Copy)]
129pub struct TxTimestampOption {
130    /// The timestamp value.
131    ///
132    /// The timestamps sent by us use `Milliseconds`.
133    pub ts_val: Timestamp<Milliseconds>,
134    /// The timestamp echo reply.
135    pub ts_echo_reply: Timestamp<Unitless>,
136}
137
138/// Like `TimestampOption` but with units appropriate for a timestamp option
139/// sent by the peer.
140#[derive(Debug, PartialEq, Eq, Clone, Copy)]
141pub struct RxTimestampOption {
142    /// The timestamp value.
143    pub ts_val: Timestamp<Unitless>,
144    /// The timestamp echo reply.
145    ///
146    /// The timestamps sent by us use `Milliseconds` so any "echoed" timestamps
147    /// we receive from the peer should also be `Milliseconds`.
148    pub ts_echo_reply: Timestamp<Milliseconds>,
149}
150
151impl From<TxTimestampOption> for TimestampOption {
152    fn from(timestamp: TxTimestampOption) -> TimestampOption {
153        let TxTimestampOption { ts_val, ts_echo_reply } = timestamp;
154        // NB: Drop the units from `ts_val`.
155        let ts_val = Timestamp::<Unitless>::new(ts_val.timestamp);
156        TimestampOption { ts_val, ts_echo_reply }
157    }
158}
159
160impl From<TimestampOption> for RxTimestampOption {
161    fn from(timestamp: TimestampOption) -> RxTimestampOption {
162        let TimestampOption { ts_val, ts_echo_reply } = timestamp;
163        // NB: Assume `Millisecond` units for `ts_echo_reply`.
164        let ts_echo_reply = Timestamp::<Milliseconds>::new(ts_echo_reply.timestamp);
165        RxTimestampOption { ts_val, ts_echo_reply }
166    }
167}
168
169impl From<&packet_formats::tcp::options::TimestampOption> for TimestampOption {
170    fn from(timestamp: &packet_formats::tcp::options::TimestampOption) -> TimestampOption {
171        Self {
172            ts_val: Timestamp::new(timestamp.ts_val()),
173            ts_echo_reply: Timestamp::new(timestamp.ts_echo_reply()),
174        }
175    }
176}
177
178impl From<&TimestampOption> for packet_formats::tcp::options::TimestampOption {
179    fn from(timestamp: &TimestampOption) -> packet_formats::tcp::options::TimestampOption {
180        let TimestampOption { ts_val, ts_echo_reply } = timestamp;
181        packet_formats::tcp::options::TimestampOption::new(ts_val.get(), ts_echo_reply.get())
182    }
183}
184
185#[cfg(any(test, feature = "testutils"))]
186mod testutils {
187    use super::*;
188
189    impl TimestampOption {
190        /// Construct a new `TimestampOption` for use in tests.
191        pub const fn new(ts_val: Timestamp<Unitless>, ts_echo_reply: Timestamp<Unitless>) -> Self {
192            TimestampOption { ts_val, ts_echo_reply }
193        }
194    }
195
196    impl<U> Timestamp<U> {
197        /// Convert the timestamp into one without units.
198        pub const fn discard_unit(self) -> Timestamp<Unitless> {
199            let Timestamp { timestamp, unit: _ } = self;
200            Timestamp { timestamp, unit: PhantomData }
201        }
202    }
203}
204
205#[cfg(test)]
206mod tests {
207    use super::*;
208
209    use test_case::test_case;
210
211    #[test_case(1, 2 => Ordering::Less; "less_than")]
212    #[test_case(1, 1 => Ordering::Equal; "equal_to")]
213    #[test_case(2, 1 => Ordering::Greater; "greater_than")]
214    #[test_case(u32::MAX, 0 => Ordering::Less; "wrapped")]
215    #[test_case(0, (1<<31) - 1 => Ordering::Less; "last_in_bounds")]
216    #[test_case(0, 1<<31 => Ordering::Greater; "first_out_of_bounds")]
217    fn timestamp_ordering(lhs: u32, rhs: u32) -> Ordering {
218        let lhs = Timestamp::<Unitless>::new(lhs);
219        let rhs = Timestamp::<Unitless>::new(rhs);
220        lhs.cmp(&rhs)
221    }
222
223    #[test_case(1, 1 => 2; "add")]
224    #[test_case(u32::MAX, 1 => 0; "wrap_in_add")]
225    #[test_case(1, (u32::MAX as u64) + 1 => 1; "wrap_in_duration")]
226    fn timestamp_add_millis(lhs: u32, rhs: u64) -> u32 {
227        let lhs = Timestamp::<Milliseconds>::new(lhs);
228        let rhs = Duration::from_millis(rhs);
229        let result = lhs + rhs;
230        result.get()
231    }
232
233    #[test_case(1, 1, Some(0); "same_time")]
234    #[test_case(1, 2, None; "after")]
235    #[test_case(2, 1, Some(1); "before")]
236    #[test_case(0, 1 << 31, Some(1 << 31); "before_boundary")]
237    #[test_case(0, (1 << 31) - 1, None; "after_boundary")]
238    fn timestamp_duration_since_millis(lhs: u32, rhs: u32, expected_duration: Option<u32>) {
239        let lhs = Timestamp::<Milliseconds>::new(lhs);
240        let rhs = Timestamp::<Milliseconds>::new(rhs);
241        let expected_duration = expected_duration.map(|d| Duration::from_millis(d.into()));
242        assert_eq!(lhs.duration_since(&rhs), expected_duration)
243    }
244}