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
13use packet_formats::tcp::options::TcpOption;
14
15/// A timestamp to be used in the TCP Timestamp Option.
16///
17/// Per RFC 7323 Section 5.2:
18///   the "timestamps" are 32-bit unsigned integers in a modular 32-bit space.
19///
20/// Note: timestamps have no inherent units. The sender and receiver are each
21/// free to decide upon the units relevant to the timestamps they generate.
22/// However, per RFC 7323 Section 5.4:
23///   (a)  The timestamp clock must not be "too slow". [...]
24///   (b)  The timestamp clock must not be "too fast". [...]
25///   Based upon these considerations, we choose a timestamp clock frequency in
26///   the range 1 ms to 1 sec per tick.
27#[derive(Debug, Derivative, Clone, Copy)]
28#[derivative(Eq(bound = ""), PartialEq(bound = ""))]
29pub struct Timestamp<U> {
30    timestamp: u32,
31    unit: PhantomData<U>,
32}
33
34/// A marker type indicating that the backing units of a [`Timestamp`] are
35/// unknown.
36#[derive(Debug, Clone, Copy)]
37pub enum Unitless {}
38
39/// A marker type indicating that the backing units of a [`Timestamp`] are
40/// milliseconds.
41///
42/// Note: Based on the considerations from RFC 7323 section 5.4, milliseconds
43/// are the most appropriate units to use for timestamps generated by ourselves.
44/// This choice supports bandwidths up to 8 Tbps, and idle connections up to 24
45/// days.
46#[derive(Debug, Clone, Copy)]
47pub enum Milliseconds {}
48
49impl<U> Timestamp<U> {
50    /// Constructs a new [`Timestamp`] from a raw [`u32`].
51    pub const fn new(timestamp: u32) -> Self {
52        Timestamp { timestamp, unit: PhantomData::<U> }
53    }
54
55    /// Retrieve the raw 32-bit timestamp value.
56    pub const fn get(&self) -> u32 {
57        let Timestamp { timestamp, unit: _ } = self;
58        *timestamp
59    }
60}
61
62impl<U> Ord for Timestamp<U> {
63    fn cmp(&self, rhs: &Timestamp<U>) -> Ordering {
64        let Timestamp { timestamp: lhs, unit: _ } = self;
65        let Timestamp { timestamp: rhs, unit: _ } = rhs;
66        // Per RFC 7323 Section 5.2:
67        //   Thus, "less than" is defined the same way it is for TCP sequence
68        //   numbers, and the same implementation techniques apply.  If s and t
69        //   are timestamp values,
70        //       s < t  if 0 < (t - s) < 2^31,
71        //   computed in unsigned 32-bit arithmetic.
72        let delta = rhs.wrapping_sub(*lhs);
73        if delta == 0 {
74            Ordering::Equal
75        } else if delta > 0 && delta < (1 << 31) {
76            Ordering::Less
77        } else {
78            Ordering::Greater
79        }
80    }
81}
82
83impl<U> PartialOrd for Timestamp<U> {
84    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
85        Some(self.cmp(other))
86    }
87}
88
89impl Add<Duration> for Timestamp<Milliseconds> {
90    type Output = Self;
91
92    fn add(self, rhs: Duration) -> Self::Output {
93        let Timestamp { timestamp: lhs, unit } = self;
94        let rhs: u128 = rhs.as_millis();
95        // The following `as` coercion is sound because `Timestamp` exists in
96        // modular 32-bit space. This drops the higher order bits from the
97        // original value, which is representative of the `Timestamp` wrapping
98        // around.
99        let rhs = rhs as u32;
100        Timestamp { timestamp: lhs.wrapping_add(rhs), unit }
101    }
102}
103
104/// The TCP timestamp option as defined in RFC 7323, Section 3.
105#[derive(Debug, PartialEq, Eq, Clone, Copy)]
106pub struct TimestampOption {
107    /// The timestamp value.
108    pub(super) ts_val: Timestamp<Unitless>,
109    /// The timestamp echo reply.
110    pub(super) ts_echo_reply: Timestamp<Unitless>,
111}
112
113/// Like `TimestampOption` but with units appropriate for a timestamp option
114/// sent by us.
115#[derive(Debug, PartialEq, Eq, Clone, Copy)]
116pub struct TxTimestampOption {
117    /// The timestamp value.
118    ///
119    /// The timestamps sent by us use `Milliseconds`.
120    pub ts_val: Timestamp<Milliseconds>,
121    /// The timestamp echo reply.
122    pub ts_echo_reply: Timestamp<Unitless>,
123}
124
125/// Like `TimestampOption` but with units appropriate for a timestamp option
126/// sent by the peer.
127#[derive(Debug, PartialEq, Eq, Clone, Copy)]
128pub struct RxTimestampOption {
129    /// The timestamp value.
130    pub ts_val: Timestamp<Unitless>,
131    /// The timestamp echo reply.
132    ///
133    /// The timestamps sent by us use `Milliseconds` so any "echoed" timestamps
134    /// we receive from the peer should also be `Milliseconds`.
135    pub ts_echo_reply: Timestamp<Milliseconds>,
136}
137
138impl From<TxTimestampOption> for TimestampOption {
139    fn from(timestamp: TxTimestampOption) -> TimestampOption {
140        let TxTimestampOption { ts_val, ts_echo_reply } = timestamp;
141        // NB: Drop the units from `ts_val`.
142        let ts_val = Timestamp::<Unitless>::new(ts_val.timestamp);
143        TimestampOption { ts_val, ts_echo_reply }
144    }
145}
146
147impl From<TimestampOption> for RxTimestampOption {
148    fn from(timestamp: TimestampOption) -> RxTimestampOption {
149        let TimestampOption { ts_val, ts_echo_reply } = timestamp;
150        // NB: Assume `Millisecond` units for `ts_echo_reply`.
151        let ts_echo_reply = Timestamp::<Milliseconds>::new(ts_echo_reply.timestamp);
152        RxTimestampOption { ts_val, ts_echo_reply }
153    }
154}
155
156impl<'a> Into<TcpOption<'a>> for TimestampOption {
157    fn into(self) -> TcpOption<'a> {
158        let TimestampOption { ts_val, ts_echo_reply } = self;
159        TcpOption::Timestamp { ts_val: ts_val.get(), ts_echo_reply: ts_echo_reply.get() }
160    }
161}
162
163#[cfg(test)]
164mod tests {
165    use super::*;
166
167    use test_case::test_case;
168
169    #[test_case(1, 2 => Ordering::Less; "less_than")]
170    #[test_case(1, 1 => Ordering::Equal; "equal_to")]
171    #[test_case(2, 1 => Ordering::Greater; "greater_than")]
172    #[test_case(u32::MAX, 0 => Ordering::Less; "wrapped")]
173    #[test_case(0, (1<<31) - 1 => Ordering::Less; "last_in_bounds")]
174    #[test_case(0, 1<<31 => Ordering::Greater; "first_out_of_bounds")]
175    fn timestamp_ordering(lhs: u32, rhs: u32) -> Ordering {
176        let lhs = Timestamp::<Unitless>::new(lhs);
177        let rhs = Timestamp::<Unitless>::new(rhs);
178        lhs.cmp(&rhs)
179    }
180
181    #[test_case(1, 1 => 2; "add")]
182    #[test_case(u32::MAX, 1 => 0; "wrap_in_add")]
183    #[test_case(1, (u32::MAX as u64) + 1 => 1; "wrap_in_duration")]
184    fn timestamp_add_millis(lhs: u32, rhs: u64) -> u32 {
185        let lhs = Timestamp::<Milliseconds>::new(lhs);
186        let rhs = Duration::from_millis(rhs);
187        let result = lhs + rhs;
188        result.get()
189    }
190}