Skip to main content

prefix_trie/trieview/
difference.rs

1//! Difference set-operation view: L minus R.
2//!
3//! [`DifferenceView`] yields every prefix present in the left view but **not** in the right.
4//!
5//! `right` is `Option<R>` internally:
6//! - `None`: no right-side filter; all of left's entries are in the difference.
7//! - `Some`: right is at the same depth as left (maintained by `get_child`).
8//!
9//! At the same depth:
10//! - `data_bitmap  = left & !right`: entries in left but not right
11//! - `child_bitmap = left`: must visit all left children; some subtrees may be fully absent from
12//!   right
13//! - `get_data     = left.get_data(bit)`: `T = L::T`, same type as left
14//! - `get_child`   = descend both; if right lacks the child, pass None for right
15//!
16//! If right starts deeper than left (e.g. user-supplied view_at), it is carried
17//! along until left's traversal descends to right's depth, at which point they align.
18
19use std::marker::PhantomData;
20
21use crate::AsView;
22use crate::{node::child_bit as node_child_bit, prefix::mask_from_prefix_len, Prefix};
23
24use super::iter::ViewIter;
25use super::TrieView;
26
27/// An immutable view over the difference of two [`TrieView`]s.
28///
29/// Returned by [`TrieView::difference`]. Iterates over every prefix present
30/// in the left view but **not** in the right view, yielding values from the left side.
31///
32/// The right side is `Option<R>` internally: once the traversal enters a subtree
33/// that the right view does not cover, the right side becomes `None` and all
34/// remaining left entries in that subtree are yielded unconditionally.
35#[derive(Clone)]
36pub struct DifferenceView<'a, L, R> {
37    left: L,
38    right: Option<R>,
39    _phantom: PhantomData<&'a ()>,
40}
41
42impl<'a, L, R> DifferenceView<'a, L, R>
43where
44    L: TrieView<'a>,
45    R: TrieView<'a, P = L::P>,
46{
47    /// Construct a `DifferenceView`, aligning the right side to the left's position.
48    ///
49    /// - If right is shallower, it is navigated toward left's position.  If the path
50    ///   diverges or a required child is absent, `right` becomes `None` (all left
51    ///   entries are kept for that subtree).
52    /// - If right is deeper, it is stored as-is and will be aligned during traversal
53    ///   as `get_child` descends toward right's depth.
54    pub(crate) fn new(left: L, right: R) -> Self {
55        let right = align_right(&left, right);
56        Self {
57            left,
58            right,
59            _phantom: PhantomData,
60        }
61    }
62}
63
64/// Navigate `right` to match `left`'s position as closely as possible.
65///
66/// Returns `None` if keys diverge (the right subtrie has no overlap with left).
67fn align_right<'a, L, R>(left: &L, right: R) -> Option<R>
68where
69    L: TrieView<'a>,
70    R: TrieView<'a, P = L::P>,
71{
72    // Check key agreement at the shallower prefix_len.
73    let min_prefix_len = left.prefix_len().min(right.prefix_len());
74    let mask = mask_from_prefix_len(min_prefix_len as u8);
75    if left.key() & mask != right.key() & mask {
76        return None; // diverging keys -> R has no overlap with L's subtrie
77    }
78
79    match right.depth().cmp(&left.depth()) {
80        std::cmp::Ordering::Less => {
81            // R is shallower: navigate toward L's depth/key.
82            right.navigate_to(left.key(), left.prefix_len())
83        }
84        std::cmp::Ordering::Equal => {
85            // Same depth
86            Some(right)
87        }
88        std::cmp::Ordering::Greater => {
89            // R is deeper: keep as-is; handled during traversal via get_child.
90            Some(right)
91        }
92    }
93}
94
95impl<'a, L, R> TrieView<'a> for DifferenceView<'a, L, R>
96where
97    L: TrieView<'a>,
98    R: TrieView<'a, P = L::P>,
99{
100    type P = L::P;
101    type T = L::T;
102
103    #[inline]
104    fn depth(&self) -> u32 {
105        self.left.depth()
106    }
107
108    #[inline]
109    fn key(&self) -> <L::P as Prefix>::R {
110        self.left.key()
111    }
112
113    #[inline]
114    fn prefix_len(&self) -> u32 {
115        self.left.prefix_len()
116    }
117
118    #[inline]
119    fn data_bitmap(&self) -> u32 {
120        match &self.right {
121            None => self.left.data_bitmap(),
122            Some(r) => {
123                if r.depth() == self.left.depth() {
124                    // Right is at the same level -> mask out entries present in right.
125                    self.left.data_bitmap() & !r.data_bitmap()
126                } else {
127                    // Right is deeper -> it doesn't have data at left's current depth.
128                    self.left.data_bitmap()
129                }
130            }
131        }
132    }
133
134    #[inline]
135    fn child_bitmap(&self) -> u32 {
136        // Always use left's child bitmap: we must visit every left child because
137        // some subtrees may be absent from right, making all their entries eligible.
138        self.left.child_bitmap()
139    }
140
141    #[inline]
142    unsafe fn get_data(&mut self, data_bit: u32) -> L::T {
143        self.left.get_data(data_bit)
144    }
145
146    unsafe fn get_child(&mut self, child_bit: u32) -> Self {
147        let l_child = self.left.get_child(child_bit);
148        let r_child = match &mut self.right {
149            None => None,
150            Some(r) => {
151                if r.depth() == self.left.depth() {
152                    // Both at the same depth; descend both together.
153                    if (r.child_bitmap() >> child_bit) & 1 == 1 {
154                        Some(r.get_child(child_bit))
155                    } else {
156                        None // right has no child here -> all left entries kept
157                    }
158                } else {
159                    // Right is deeper (right.depth > left.depth).
160                    // Only carry right along if this child leads toward right's subtrie.
161                    let toward_r = node_child_bit(self.left.depth(), r.key());
162                    if child_bit == toward_r {
163                        Some(self.right.take().unwrap())
164                    } else {
165                        None
166                    }
167                }
168            }
169        };
170        DifferenceView {
171            left: l_child,
172            right: r_child,
173            _phantom: PhantomData,
174        }
175    }
176
177    unsafe fn reposition(&mut self, key: <L::P as Prefix>::R, prefix_len: u32) {
178        self.left.reposition(key, prefix_len);
179        // right does not need repositioning. It is anyways just used as a filter.
180    }
181}
182
183impl<'a, L, R> IntoIterator for DifferenceView<'a, L, R>
184where
185    L: TrieView<'a>,
186    R: TrieView<'a, P = L::P>,
187{
188    type Item = (L::P, L::T);
189    type IntoIter = ViewIter<'a, DifferenceView<'a, L, R>>;
190
191    fn into_iter(self) -> Self::IntoIter {
192        self.iter()
193    }
194}
195
196impl<'a, L, R> AsView<'a> for DifferenceView<'a, L, R>
197where
198    L: TrieView<'a>,
199    R: TrieView<'a, P = L::P>,
200{
201    type P = L::P;
202    type View = Self;
203
204    fn view(self) -> Self {
205        self
206    }
207}
208
209#[cfg(test)]
210mod tests {
211    use crate::{
212        Prefix,
213        {
214            trieview::{AsView, TrieView},
215            PrefixMap,
216        },
217    };
218
219    type P = (u32, u8);
220
221    fn p(repr: u32, len: u8) -> P {
222        P::from_repr_len(repr, len)
223    }
224
225    fn map_from(entries: &[(u32, u8, i32)]) -> PrefixMap<P, i32> {
226        let mut m = PrefixMap::new();
227        for &(repr, len, val) in entries {
228            m.insert(p(repr, len), val);
229        }
230        m
231    }
232
233    fn collect_diff<'a>(iter: impl Iterator<Item = (P, &'a i32)>) -> Vec<(P, i32)> {
234        iter.map(|(p, v)| (p, *v)).collect()
235    }
236
237    // -- Same-depth cases ------------------------------------------------------
238
239    #[test]
240    fn diff_basic() {
241        // a \ b removes only shared prefixes; unshared left entries are kept.
242        let a = map_from(&[
243            (0x0a000000, 8, 1),
244            (0x0a010000, 16, 2),
245            (0x0a020000, 16, 3),
246            (0x0b000000, 8, 4),
247        ]);
248        let b = map_from(&[
249            (0x0a000000, 8, 10),  // shared with a -> removed from result
250            (0x0a020000, 16, 30), // shared with a -> removed from result
251            (0x0c000000, 8, 99),  // not in a -> irrelevant
252        ]);
253        let got = collect_diff(a.view().difference(b.view()).into_iter());
254        assert_eq!(
255            got,
256            vec![
257                (p(0x0a010000, 16), 2), // kept: not in b
258                (p(0x0b000000, 8), 4),  // kept: not in b
259            ]
260        );
261    }
262
263    #[test]
264    fn diff_no_overlap() {
265        // b has completely different prefixes -> all of a is kept.
266        let a = map_from(&[(0x0a000000, 8, 1), (0x0a010000, 16, 2)]);
267        let b = map_from(&[(0x0b000000, 8, 10), (0x0c000000, 8, 20)]);
268        let got = collect_diff(a.view().difference(b.view()).into_iter());
269        assert_eq!(got, vec![(p(0x0a000000, 8), 1), (p(0x0a010000, 16), 2),]);
270    }
271
272    #[test]
273    fn diff_b_superset() {
274        // b contains every prefix in a -> result is empty.
275        let a = map_from(&[(0x0a000000, 8, 1), (0x0a010000, 16, 2)]);
276        let b = map_from(&[
277            (0x0a000000, 8, 10),
278            (0x0a010000, 16, 20),
279            (0x0b000000, 8, 99),
280        ]);
281        let got = collect_diff(a.view().difference(b.view()).into_iter());
282        assert!(got.is_empty());
283    }
284
285    #[test]
286    fn diff_b_empty() {
287        // Empty b -> all of a is returned.
288        let a = map_from(&[(0x0a000000, 8, 1), (0x0a010000, 16, 2)]);
289        let b: PrefixMap<P, i32> = PrefixMap::new();
290        let got = collect_diff(a.view().difference(b.view()).into_iter());
291        assert_eq!(got, vec![(p(0x0a000000, 8), 1), (p(0x0a010000, 16), 2),]);
292    }
293
294    #[test]
295    fn diff_a_empty() {
296        let a: PrefixMap<P, i32> = PrefixMap::new();
297        let b = map_from(&[(0x0a000000, 8, 10)]);
298        assert!(a.view().difference(b.view()).into_iter().next().is_none());
299    }
300
301    /// Large same-depth test covering multiple subtries and levels.
302    #[test]
303    fn diff_large_same_depth() {
304        let a = map_from(&[
305            (0x01000000, 8, 1),
306            (0x0a000000, 8, 10),
307            (0x0a010000, 16, 11),
308            (0x0a010100, 24, 12),
309            (0x0a020000, 16, 13),
310            (0x0b000000, 8, 20),
311            (0x0b010000, 16, 21),
312            (0x64000000, 8, 100),
313            (0xc0a80000, 16, 200),
314        ]);
315        let b = map_from(&[
316            (0x0a000000, 8, 99),  // removes 10/8
317            (0x0a010100, 24, 99), // removes 10.1.1/24
318            (0x0b000000, 8, 99),  // removes 11/8
319            (0x64000000, 8, 99),  // removes 100/8
320            (0xc0a80100, 24, 99), // not in a -> no effect
321        ]);
322        let got = collect_diff(a.view().difference(b.view()).into_iter());
323        assert_eq!(
324            got,
325            vec![
326                (p(0x01000000, 8), 1),    // kept: not in b
327                (p(0x0a010000, 16), 11),  // kept: 10.1/16 is not in b (only /24 is)
328                (p(0x0a020000, 16), 13),  // kept: not in b
329                (p(0x0b010000, 16), 21),  // kept: 11.1/16 not in b (only /8 is)
330                (p(0xc0a80000, 16), 200), // kept: 192.168/16 not in b
331            ]
332        );
333    }
334
335    // -- find / find_lpm --------------------------------------------------------
336
337    #[test]
338    fn diff_find_then_iter() {
339        let a = map_from(&[
340            (0x0a000000, 8, 1),
341            (0x0a010000, 16, 2),
342            (0x0a010100, 24, 3),
343            (0x0b000000, 8, 4),
344        ]);
345        let b = map_from(&[
346            (0x0a000000, 8, 10),
347            (0x0a010000, 16, 20), // removes 10.1/16 from difference
348        ]);
349        // diff: {10.1.1/24, 11/8}; find 10.1/16 subtrie in diff -> only 10.1.1/24
350        let got = collect_diff(
351            a.view()
352                .difference(b.view())
353                .find(&p(0x0a010000, 16))
354                .unwrap()
355                .into_iter(),
356        );
357        assert_eq!(got, vec![(p(0x0a010100, 24), 3)]);
358    }
359
360    #[test]
361    fn diff_mut_find_lpm_value_does_not_require_clone() {
362        let mut a = map_from(&[(0x0a000000, 8, 1), (0x0a010000, 16, 2), (0x0a010100, 24, 3)]);
363        let b = map_from(&[(0x0a010100, 24, 30)]);
364
365        let got = (&mut a)
366            .view()
367            .difference(b.view())
368            .find_lpm_value(&p(0x0a010180, 25))
369            .map(|(prefix, value)| {
370                *value += 10;
371                (prefix, *value)
372            });
373
374        assert_eq!(got, Some((p(0x0a010000, 16), 12)));
375        assert_eq!(a.get(&p(0x0a010000, 16)), Some(&12));
376    }
377
378    // -- Depth-difference cases ------------------------------------------------
379    //
380    // With K=5, `view_at(&p(addr, len))` lands at depth = floor(len/K)*K.
381    //   len=8  -> depth 5    len=16 -> depth 15
382
383    /// Right is shallower: right is navigated to left's depth at construction.
384    /// Entries in left's subtrie that are also in right's navigated position are removed.
385    #[test]
386    fn diff_right_shallower_navigated_to_left() {
387        // a_sub = 10.x subtrie (depth 5); b_root = root (depth 0).
388        // align_right navigates b_root to depth 5 via the 10.x child.
389        // At depth 5, b has {10/8, 10.1/16}; a has {10/8, 10.1/16, 10.2/16}.
390        // Difference: {10.2/16}.
391        let a = map_from(&[(0x0a000000, 8, 1), (0x0a010000, 16, 2), (0x0a020000, 16, 3)]);
392        let b = map_from(&[
393            (0x0a000000, 8, 10),
394            (0x0a010000, 16, 20),
395            (0x0b000000, 8, 30), // irrelevant -> outside a_sub
396        ]);
397        let a_sub = a.view_at(&p(0x0a000000, 8)).unwrap(); // depth 5
398        let b_root = b.view(); // depth 0
399
400        let got = collect_diff(a_sub.difference(b_root).into_iter());
401        assert_eq!(got, vec![(p(0x0a020000, 16), 3)]);
402    }
403
404    /// Right navigated to left, but right diverges (no path to left's key).
405    /// align_right returns None -> all left entries are kept.
406    #[test]
407    fn diff_right_shallower_diverges() {
408        // a_sub = 10.x subtrie (depth 5); b has no entries in 10.x at all.
409        // Right navigates toward 10.x but finds no child -> right = None -> all kept.
410        let a = map_from(&[(0x0a000000, 8, 1), (0x0a010000, 16, 2)]);
411        let b = map_from(&[
412            (0x0b000000, 8, 10), // 11.x -> completely different subtrie
413            (0x0c000000, 8, 20),
414        ]);
415        let a_sub = a.view_at(&p(0x0a000000, 8)).unwrap(); // depth 5
416        let b_root = b.view(); // depth 0
417
418        let got = collect_diff(a_sub.difference(b_root).into_iter());
419        // No path from b_root to 10.x -> right = None -> all of a_sub kept
420        assert_eq!(got, vec![(p(0x0a000000, 8), 1), (p(0x0a010000, 16), 2),]);
421    }
422
423    /// Left is shallower than right (right is positioned deeper in the trie).
424    /// Left leads; right is carried toward its depth via get_child.
425    /// Left's entries in subtries NOT leading toward right are kept unconditionally.
426    #[test]
427    fn diff_left_shallower_right_deeper() {
428        // a_root (depth 0) minus b_sub at 10.x (depth 5).
429        // a has: 9/8, 10/8, 10.1/16, 11/8.
430        // b_sub covers: 10/8, 10.1/16, 10.2/16 (within 10.x).
431        //
432        // Expected difference:
433        //   9/8  -> kept (not in b_sub's subtrie)
434        //   10/8 -> removed (b_sub has it)
435        //   10.1/16 -> removed
436        //   11/8 -> kept (not in b_sub's subtrie)
437        let a = map_from(&[
438            (0x09000000, 8, 1),
439            (0x0a000000, 8, 2),
440            (0x0a010000, 16, 3),
441            (0x0b000000, 8, 4),
442        ]);
443        let b = map_from(&[
444            (0x0a000000, 8, 10),
445            (0x0a010000, 16, 20),
446            (0x0a020000, 16, 30),
447        ]);
448        let a_root = a.view();
449        let b_sub = b.view_at(&p(0x0a000000, 8)).unwrap(); // depth 5
450
451        let got = collect_diff(a_root.difference(b_sub).into_iter());
452        assert_eq!(
453            got,
454            vec![
455                (p(0x09000000, 8), 1), // kept: 9/8 not toward b_sub
456                (p(0x0b000000, 8), 4), // kept: 11/8 not toward b_sub
457            ]
458        );
459    }
460
461    /// Left has entries in many subtries; right (deeper) only covers one of them.
462    /// Entries in subtries NOT leading toward right are kept entirely.
463    #[test]
464    fn diff_left_multiple_subtries_right_covers_one() {
465        let a = map_from(&[
466            (0x0a000000, 8, 1),
467            (0x0a010000, 16, 2),
468            (0x0b000000, 8, 3),
469            (0x0b010000, 16, 4),
470            (0x0c000000, 8, 5),
471        ]);
472        let b = map_from(&[
473            (0x0a000000, 8, 10),  // covers 10/8
474            (0x0a010000, 16, 20), // covers 10.1/16
475        ]);
476        let a_root = a.view();
477        let b_sub = b.view_at(&p(0x0a000000, 8)).unwrap(); // depth 5
478
479        let got = collect_diff(a_root.difference(b_sub).into_iter());
480        assert_eq!(
481            got,
482            vec![
483                // 10.x entries: 10/8 and 10.1/16 both in b_sub -> removed
484                (p(0x0b000000, 8), 3), // not toward b_sub -> kept entirely
485                (p(0x0b010000, 16), 4),
486                (p(0x0c000000, 8), 5),
487            ]
488        );
489    }
490
491    // -- Composition -----------------------------------------------------------
492
493    #[test]
494    fn diff_composed_with_intersection() {
495        // (a \ b) ∩ c -> DifferenceView implements TrieView so it composes.
496        let a = map_from(&[(0x0a000000, 8, 1), (0x0a010000, 16, 2), (0x0b000000, 8, 3)]);
497        let b = map_from(&[(0x0a000000, 8, 99)]); // removes 10/8 from a
498        let c = map_from(&[(0x0a010000, 16, 100), (0x0b000000, 8, 200)]);
499        // a \ b = {10.1/16, 11/8}; ∩ c = {10.1/16, 11/8} (both in c)
500        let got: Vec<_> = a
501            .view()
502            .difference(b.view())
503            .intersection(c.view())
504            .unwrap()
505            .into_iter()
506            .map(|(p, (l, r))| (p, *l, *r))
507            .collect();
508        assert_eq!(
509            got,
510            vec![(p(0x0a010000, 16), 2, 100), (p(0x0b000000, 8), 3, 200),]
511        );
512    }
513
514    #[test]
515    fn diff_composed_difference_of_differences() {
516        // (a \ b) \ c
517        let a = map_from(&[
518            (0x0a000000, 8, 1),
519            (0x0a010000, 16, 2),
520            (0x0b000000, 8, 3),
521            (0x0c000000, 8, 4),
522        ]);
523        let b = map_from(&[(0x0a000000, 8, 99)]); // removes 10/8
524        let c = map_from(&[(0x0b000000, 8, 99)]); // removes 11/8
525
526        // a \ b = {10.1/16, 11/8, 12/8}; \ c = {10.1/16, 12/8}
527        let got = collect_diff(
528            a.view()
529                .difference(b.view())
530                .difference(c.view())
531                .into_iter(),
532        );
533        assert_eq!(got, vec![(p(0x0a010000, 16), 2), (p(0x0c000000, 8), 4),]);
534    }
535
536    #[test]
537    fn view_into_right_child() {
538        let a = map_from(&[(0x00000000, 0, 0), (0x00000000, 1, 1), (0x00000000, 2, 2)]);
539        let b = map_from(&[(0x00000000, 0, 0), (0x00000000, 2, 2)]);
540        let b_view = b.view_at(&p(0x00000000, 1)).unwrap();
541        let got = a.view().difference(b_view).iter().collect::<Vec<_>>();
542        let want = vec![(p(0x00000000, 0), &0), (p(0x00000000, 1), &1)];
543        assert_eq!(got, want);
544    }
545
546    #[test]
547    fn view_into_left_child() {
548        let a = map_from(&[(0x00000000, 0, 0), (0x00000000, 1, 1), (0x00000000, 2, 2)]);
549        let b = map_from(&[(0x00000000, 0, 0), (0x00000000, 2, 2)]);
550        let a_view = a.view_at(&p(0x00000000, 1)).unwrap();
551        let got = a_view.difference(b.view()).iter().collect::<Vec<_>>();
552        let want = vec![(p(0x00000000, 1), &1)];
553        assert_eq!(got, want);
554    }
555
556    #[test]
557    fn view_into_right_child_deep() {
558        let a = map_from(&[(0x00000000, 0, 0), (0x00000000, 5, 5), (0x00000000, 6, 6)]);
559        let b = map_from(&[(0x00000000, 0, 0), (0x00000000, 6, 6)]);
560        let b_view = b.view_at(&p(0x00000000, 5)).unwrap();
561        let got = a.view().difference(b_view).iter().collect::<Vec<_>>();
562        let want = vec![(p(0x00000000, 0), &0), (p(0x00000000, 5), &5)];
563        assert_eq!(got, want);
564    }
565
566    #[test]
567    fn view_into_left_child_deep() {
568        let a = map_from(&[(0x00000000, 0, 0), (0x00000000, 5, 5), (0x00000000, 6, 6)]);
569        let b = map_from(&[(0x00000000, 0, 0), (0x00000000, 6, 6)]);
570        let a_view = a.view_at(&p(0x00000000, 5)).unwrap();
571        let got = a_view.difference(b.view()).iter().collect::<Vec<_>>();
572        let want = vec![(p(0x00000000, 5), &5)];
573        assert_eq!(got, want);
574    }
575
576    // -- iter_from on difference views ------------------------------------------
577
578    #[test]
579    fn diff_iter_from_inclusive() {
580        let a = map_from(&[
581            (0x0a000000, 8, 1),
582            (0x0a010000, 16, 2),
583            (0x0a020000, 16, 3),
584            (0x0a030000, 16, 4),
585        ]);
586        let b = map_from(&[(0x0a010000, 16, 20)]);
587
588        // diff: 10/8, 10.2/16, 10.3/16
589        let from = collect_diff(
590            a.view()
591                .difference(b.view())
592                .iter_from(&p(0x0a020000, 16), true),
593        );
594        assert_eq!(from, vec![(p(0x0a020000, 16), 3), (p(0x0a030000, 16), 4)]);
595    }
596
597    #[test]
598    fn diff_iter_from_exclusive() {
599        let a = map_from(&[(0x0a000000, 8, 1), (0x0a010000, 16, 2), (0x0a020000, 16, 3)]);
600        let b = map_from(&[(0x0a010000, 16, 20)]);
601
602        // diff: 10/8, 10.2/16
603        let from = collect_diff(
604            a.view()
605                .difference(b.view())
606                .iter_from(&p(0x0a000000, 8), false),
607        );
608        assert_eq!(from, vec![(p(0x0a020000, 16), 3)]);
609    }
610
611    #[test]
612    fn diff_iter_from_subview() {
613        let a = map_from(&[
614            (0x0a000000, 8, 1), // excluded by sub-view
615            (0x0a020000, 16, 2),
616            (0x0a030000, 16, 3),
617            (0x0b000000, 8, 4), // excluded by sub-view
618        ]);
619        let b = map_from(&[(0x0a020000, 16, 20)]);
620
621        // Sub-view at 10.2.0.0/15 covers 10.2–10.3, excludes 10/8, 11/8
622        // a sub-view entries: 10.2/16, 10.3/16
623        // diff removes 10.2/16 → only 10.3/16 remains
624        let diff = a.view_at(&p(0x0a020000, 15)).unwrap().difference(b.view());
625        let all = collect_diff(diff.clone().iter());
626        assert_eq!(all, vec![(p(0x0a030000, 16), 3)]);
627
628        // iter_from inclusive at 10.3/16 → same single result
629        let from = collect_diff(diff.iter_from(&p(0x0a030000, 16), true));
630        assert_eq!(from, vec![(p(0x0a030000, 16), 3)]);
631    }
632}