Skip to main content

rtc_shared/replay_detector/
mod.rs

1mod fixed_big_int;
2#[cfg(test)]
3mod replay_detector_test;
4
5use fixed_big_int::*;
6
7// ReplayDetector is the interface of sequence replay detector.
8/// Tracks which sequence numbers have already been seen, so replayed packets can be dropped.
9///
10/// Both DTLS and SRTP require this: an attacker who captures a packet must not be able to
11/// have it accepted a second time.
12pub trait ReplayDetector: Send + Sync {
13    /// Returns `true` if `seq` has not been seen before and is inside the window.
14    ///
15    /// This only tests; call [`Self::accept`] afterwards to record the packet as received.
16    fn check(&mut self, seq: u64) -> bool;
17    /// Commits the sequence number from the preceding [`Self::check`] call as received.
18    ///
19    /// Split from `check` so a caller can validate a packet's authenticity first and only then
20    /// record it — a forged packet must not advance the window.
21    fn accept(&mut self);
22}
23
24/// A replay detector over a monotonically increasing sequence number that never wraps.
25///
26/// Handles the full 64-bit range, which is what DTLS needs. See
27/// [`WrappedSlidingWindowDetector`] for sequence numbers that do wrap.
28pub struct SlidingWindowDetector {
29    accepted: bool,
30    seq: u64,
31    latest_seq: u64,
32    max_seq: u64,
33    window_size: usize,
34    mask: FixedBigInt,
35}
36
37impl SlidingWindowDetector {
38    /// Creates a detector with a `window_size`-wide window over sequence numbers up to
39    /// `max_seq`.
40    ///
41    /// Does not allow wrapping: it handles monotonically increasing sequence numbers across
42    /// the full 64-bit range, which is what DTLS replay protection needs.
43    pub fn new(window_size: usize, max_seq: u64) -> Self {
44        SlidingWindowDetector {
45            accepted: false,
46            seq: 0,
47            latest_seq: 0,
48            max_seq,
49            window_size,
50            mask: FixedBigInt::new(window_size),
51        }
52    }
53}
54
55impl ReplayDetector for SlidingWindowDetector {
56    fn check(&mut self, seq: u64) -> bool {
57        self.accepted = false;
58
59        if seq > self.max_seq {
60            // Exceeded upper limit.
61            return false;
62        }
63
64        if seq <= self.latest_seq {
65            if self.latest_seq >= self.window_size as u64 + seq {
66                return false;
67            }
68            if self.mask.bit((self.latest_seq - seq) as usize) != 0 {
69                // The sequence number is duplicated.
70                return false;
71            }
72        }
73
74        self.accepted = true;
75        self.seq = seq;
76        true
77    }
78
79    fn accept(&mut self) {
80        if !self.accepted {
81            return;
82        }
83
84        if self.seq > self.latest_seq {
85            // Update the head of the window.
86            self.mask.lsh((self.seq - self.latest_seq) as usize);
87            self.latest_seq = self.seq;
88        }
89        let diff = (self.latest_seq - self.seq) % self.max_seq;
90        self.mask.set_bit(diff as usize);
91    }
92}
93
94/// A replay detector for a sequence number that wraps at a known maximum.
95///
96/// SRTP's 16-bit sequence numbers wrap, so the window has to interpret a large backwards
97/// jump as a rollover rather than a replay.
98pub struct WrappedSlidingWindowDetector {
99    accepted: bool,
100    seq: u64,
101    latest_seq: u64,
102    max_seq: u64,
103    window_size: usize,
104    mask: FixedBigInt,
105    init: bool,
106}
107
108impl WrappedSlidingWindowDetector {
109    /// Creates a detector with a `window_size`-wide window that allows the sequence number to
110    /// wrap at `max_seq`.
111    ///
112    /// Suitable for the short counters used by SRTP and SRTCP.
113    pub fn new(window_size: usize, max_seq: u64) -> Self {
114        WrappedSlidingWindowDetector {
115            accepted: false,
116            seq: 0,
117            latest_seq: 0,
118            max_seq,
119            window_size,
120            mask: FixedBigInt::new(window_size),
121            init: false,
122        }
123    }
124}
125
126impl ReplayDetector for WrappedSlidingWindowDetector {
127    fn check(&mut self, seq: u64) -> bool {
128        self.accepted = false;
129
130        if seq > self.max_seq {
131            // Exceeded upper limit.
132            return false;
133        }
134        if !self.init {
135            if seq != 0 {
136                self.latest_seq = seq - 1;
137            } else {
138                self.latest_seq = self.max_seq;
139            }
140            self.init = true;
141        }
142
143        let mut diff = self.latest_seq as i64 - seq as i64;
144        // Wrap the number.
145        if diff > self.max_seq as i64 / 2 {
146            diff -= (self.max_seq + 1) as i64;
147        } else if diff <= -(self.max_seq as i64 / 2) {
148            diff += (self.max_seq + 1) as i64;
149        }
150
151        if diff >= self.window_size as i64 {
152            // Too old.
153            return false;
154        }
155        if diff >= 0 && self.mask.bit(diff as usize) != 0 {
156            // The sequence number is duplicated.
157            return false;
158        }
159
160        self.accepted = true;
161        self.seq = seq;
162        true
163    }
164
165    fn accept(&mut self) {
166        if !self.accepted {
167            return;
168        }
169
170        let mut diff = self.latest_seq as i64 - self.seq as i64;
171        // Wrap the number.
172        if diff > self.max_seq as i64 / 2 {
173            diff -= (self.max_seq + 1) as i64;
174        } else if diff <= -(self.max_seq as i64 / 2) {
175            diff += (self.max_seq + 1) as i64;
176        }
177
178        assert!(diff < self.window_size as i64);
179
180        if diff < 0 {
181            // Update the head of the window.
182            self.mask.lsh((-diff) as usize);
183            self.latest_seq = self.seq;
184            self.mask.set_bit(0);
185        } else {
186            self.mask.set_bit(diff as usize);
187        }
188    }
189}
190
191#[derive(Default)]
192/// A detector that accepts everything.
193///
194/// For contexts where replay protection is disabled or handled elsewhere.
195pub struct NoOpReplayDetector;
196
197impl ReplayDetector for NoOpReplayDetector {
198    fn check(&mut self, _: u64) -> bool {
199        true
200    }
201    fn accept(&mut self) {}
202}