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.
45use alloc::collections::VecDeque;
6use core::ops::Range;
78use derivative::Derivative;
9use netstack3_base::SeqNum;
1011/// A generic data structure that keeps track of ordered sequence number ranges.
12///
13/// Each kept range has associated metadata of type `M`.
14#[derive(Debug, Derivative)]
15#[derivative(Default(bound = ""))]
16#[cfg_attr(test, derive(PartialEq, Eq))]
17pub(crate) struct SeqRanges<M> {
18 blocks: VecDeque<SeqRange<M>>,
19}
2021impl<M> SeqRanges<M> {
22pub(crate) fn is_empty(&self) -> bool {
23self.blocks.is_empty()
24 }
2526pub(crate) fn pop_front_if<F: FnOnce(&SeqRange<M>) -> bool>(
27&mut self,
28 f: F,
29 ) -> Option<SeqRange<M>> {
30let front = self.blocks.front()?;
31if f(front) {
32self.blocks.pop_front()
33 } else {
34None
35}
36 }
3738/// Inserts `range` into this tracking structure.
39 ///
40 /// No-op if the range is empty.
41 ///
42 /// `meta` is attached to the newly created range and all the ranges it
43 /// touches, including if `range` is a subset of a currently tracked range.
44pub(crate) fn insert(&mut self, range: Range<SeqNum>, meta: M)
45where
46M: Clone,
47 {
48let Range { mut start, mut end } = range;
49let Self { blocks } = self;
5051if start == end {
52return;
53 }
5455if blocks.is_empty() {
56 blocks.push_back(SeqRange { range: Range { start, end }, meta });
57return;
58 }
5960// Search for the first segment whose `start` is greater.
61let first_after = match blocks.binary_search_by(|block| {
62if block.range.start == start {
63return core::cmp::Ordering::Equal;
64 }
65if block.range.start.before(start) {
66 core::cmp::Ordering::Less
67 } else {
68 core::cmp::Ordering::Greater
69 }
70 }) {
71Ok(r) => {
72// We found the exact same start point, so the first segment
73 // whose start is greater must be the next one.
74r + 1
75}
76Err(e) => {
77// When binary search doesn't find the exact place it returns
78 // the index where this block should be in, which should be the
79 // next greater range.
80e
81 }
82 };
8384let mut merge_right = 0;
85for block in blocks.range(first_after..blocks.len()) {
86if end.before(block.range.start) {
87break;
88 }
89 merge_right += 1;
90if end.before(block.range.end) {
91 end = block.range.end;
92break;
93 }
94 }
9596let mut merge_left = 0;
97for block in blocks.range(0..first_after).rev() {
98if start.after(block.range.end) {
99break;
100 }
101// There is no guarantee that `end.after(range.end)`, not doing
102 // the following may shrink existing coverage. For example:
103 // range.start = 0, range.end = 10, start = 0, end = 1, will result
104 // in only 0..1 being tracked in the resulting assembler. We didn't
105 // do the symmetrical thing above when merging to the right because
106 // the search guarantees that `start.before(range.start)`, thus the
107 // problem doesn't exist there. The asymmetry rose from the fact
108 // that we used `start` to perform the search.
109if end.before(block.range.end) {
110 end = block.range.end;
111 }
112 merge_left += 1;
113if start.after(block.range.start) {
114 start = block.range.start;
115break;
116 }
117 }
118119if merge_left == 0 && merge_right == 0 {
120// If the new segment cannot merge with any of its neighbors, we
121 // add a new entry for it.
122blocks.insert(first_after, SeqRange { range: Range { start, end }, meta });
123 } else {
124// Otherwise, we put the new segment at the left edge of the merge
125 // window and remove all other existing segments.
126let left_edge = first_after - merge_left;
127let right_edge = first_after + merge_right;
128 blocks[left_edge] = SeqRange { range: Range { start, end }, meta };
129for i in right_edge..blocks.len() {
130 blocks[i - merge_left - merge_right + 1] = blocks[i].clone();
131 }
132 blocks.truncate(blocks.len() - merge_left - merge_right + 1);
133 }
134 }
135136pub(crate) fn iter(&self) -> impl Iterator<Item = &SeqRange<M>> + '_ {
137self.blocks.iter()
138 }
139}
140141impl<M: Clone> FromIterator<SeqRange<M>> for SeqRanges<M> {
142fn from_iter<T: IntoIterator<Item = SeqRange<M>>>(iter: T) -> Self {
143let mut ranges = SeqRanges::default();
144for SeqRange { range, meta } in iter {
145 ranges.insert(range, meta)
146 }
147 ranges
148 }
149}
150151/// A range kept in [`SeqRanges`].
152#[derive(Debug, Clone)]
153#[cfg_attr(test, derive(PartialEq, Eq))]
154pub(crate) struct SeqRange<M> {
155pub(crate) range: Range<SeqNum>,
156pub(crate) meta: M,
157}
158159#[cfg(test)]
160mod test {
161use super::*;
162163use alloc::format;
164165use netstack3_base::WindowSize;
166use proptest::strategy::{Just, Strategy};
167use proptest::test_runner::Config;
168use proptest::{prop_assert, prop_assert_eq, proptest};
169use proptest_support::failed_seeds_no_std;
170171proptest! {
172#![proptest_config(Config {
173// Add all failed seeds here.
174failure_persistence: failed_seeds_no_std!(
175"cc f621ca7d3a2b108e0dc41f7169ad028f4329b79e90e73d5f68042519a9f63999",
176"cc c449aebed201b4ec4f137f3c224f20325f4cfee0b7fd596d9285176b6d811aa9"
177),
178 ..Config::default()
179 })]
180181 #[test]
182fn seq_ranges_insert(insertions in proptest::collection::vec(insertions(), 200)) {
183let mut seq_ranges = SeqRanges::<()>::default();
184let mut num_insertions_performed = 0;
185let mut min_seq = SeqNum::new(WindowSize::MAX.into());
186let mut max_seq = SeqNum::new(0);
187for Range { start, end } in insertions {
188if min_seq.after(start) {
189 min_seq = start;
190 }
191if max_seq.before(end) {
192 max_seq = end;
193 }
194// assert that it's impossible to have more entries than the
195 // number of insertions performed.
196prop_assert!(seq_ranges.blocks.len() <= num_insertions_performed);
197 seq_ranges.insert(start..end, ());
198 num_insertions_performed += 1;
199200// assert that the ranges are sorted and don't overlap with
201 // each other.
202for i in 1..seq_ranges.blocks.len() {
203prop_assert!(
204 seq_ranges.blocks[i-1].range.end.before(seq_ranges.blocks[i].range.start)
205 );
206 }
207 }
208prop_assert_eq!(seq_ranges.blocks.front().unwrap().range.start, min_seq);
209prop_assert_eq!(seq_ranges.blocks.back().unwrap().range.end, max_seq);
210 }
211 }
212213fn insertions() -> impl Strategy<Value = Range<SeqNum>> {
214 (0..u32::from(WindowSize::MAX)).prop_flat_map(|start| {
215 (start + 1..=u32::from(WindowSize::MAX)).prop_flat_map(move |end| {
216 Just(Range { start: SeqNum::new(start), end: SeqNum::new(end) })
217 })
218 })
219 }
220}