Skip to content

Commit 38a7ca8

Browse files
committed
Merge branch 'release-0.5.0-rc1' into bincode
2 parents 334843d + 909ba27 commit 38a7ca8

File tree

5 files changed

+167
-101
lines changed

5 files changed

+167
-101
lines changed

Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "routecore"
3-
version = "0.5.0-rc1-dev"
3+
version = "0.5.0-rc1"
44
authors = ["NLnet Labs <[email protected]>"]
55
categories = ["network-programming"]
66
description = "A Library with Building Blocks for BGP Routing"

Changelog.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
## 0.5.0-rc0
1+
## 0.5.0-rc1
22

3-
Released 2024-05-29.
3+
Released 2024-06-10.
44

55
This release features a lot of changes, big and small. The list below is not
66
exhaustive but tries to highlight and describe the bigger (and perhaps, more

fuzz/Cargo.lock

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

fuzz/fuzz_targets/path_selection.rs

+34-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use libfuzzer_sys::fuzz_target;
66
use routecore::bgp::{
77
path_attributes::PaMap,
88
path_selection::{OrdStrat, OrdRoute, Rfc4271, SkipMed, TiebreakerInfo,
9-
best_backup, best
9+
best_backup, best_backup_generic, best
1010
}
1111
};
1212

@@ -73,6 +73,38 @@ fn verify_path_selection<'a, T, OS, I>(candidates: I)
7373
(best2.borrow().tiebreakers(), best2.borrow().pa_map()),
7474
(backup2.borrow().tiebreakers(), backup2.borrow().pa_map()),
7575
);
76+
77+
let (best_gen, backup_gen) = verify_path_selection_generic(candidates);
78+
let (best_gen, _backup_gen) = (best_gen.unwrap(), backup_gen.unwrap());
79+
80+
assert_eq!(best2, best_gen);
81+
82+
// Because best_backup_generic can't deal with duplicates, this won't
83+
// always hold:
84+
//assert_eq!(backup2, backup_gen);
85+
86+
}
87+
88+
fn verify_path_selection_generic<I, T>(candidates: I) -> (Option<T>, Option<T>)
89+
where
90+
I: Iterator<Item = T>,
91+
T: Debug + Ord
92+
{
93+
94+
let (best, backup) = best_backup_generic(
95+
// create tuples of the OrdRoute and a unique 'id'
96+
candidates.enumerate().map(|(idx, c)| (c, idx))
97+
);
98+
// because the returned values are tuples as well, we can now compare
99+
// those (instead of comparing the OrdRoute). With the unique IDs
100+
// attached, we can check whether `best` and `backup` are not equal in
101+
// terms of content.
102+
assert!(best.is_some());
103+
assert!(backup.is_some());
104+
assert_ne!(best, backup);
105+
106+
107+
(best.map(|t| t.0), backup.map(|t| t.0))
76108
}
77109

78110
// XXX while doing fuzz_target!(|data: &[u8]| ... and then creating an
@@ -104,9 +136,9 @@ fuzz_target!(|data: (
104136
Err(_) => return,
105137
};
106138

107-
//dbg!("skipmed");
108139
verify_ord(&a, &b, &c);
109140
verify_path_selection([&a, &b, &c, &b, &c, &a].into_iter());
141+
//verify_path_selection_generic([&a, &b, &c].into_iter());
110142

111143
//dbg!("rfc4271");
112144
/*

src/bgp/path_selection.rs

+129-95
Original file line numberDiff line numberDiff line change
@@ -492,61 +492,55 @@ impl fmt::Display for DecisionErrorType {
492492

493493
//------------ Helpers -------------------------------------------------------
494494

495-
pub fn preferred<'a, OS: OrdStrat>(
496-
a: OrdRoute<'a, OS>, b: OrdRoute<'a, OS>
497-
) -> OrdRoute<'a, OS> {
495+
496+
/// Returns the preferred route.
497+
///
498+
/// Note this method works on anything `T: Ord` and is thus not limited to
499+
/// `OrdRoute`. Hence one can pass in a tuple of `(OrdRoute, L)` as long as
500+
/// `L` implements `Ord`, to include and get back any local value like an ID.
501+
/// This is consistent with [`fn best`] and [`fn best_backup_generic`].
502+
pub fn preferred<T: Ord>(a: T, b: T) -> T {
498503
cmp::min(a,b)
499504
}
500505

501506
/// Selects the preferred ('best') route.
502-
pub fn best<'a, T, I, OS>(it: I) -> Option<T>
507+
///
508+
/// Note this method works on an iterator yielding anything `T: Ord`, so not
509+
/// limited to `OrdRoute`. It is in that sense consistent with [`fn
510+
/// best_backup_generic`], i.e. one can pass in tuples of `OrdRoute` and
511+
/// something else that implements `Ord`.
512+
pub fn best<T, I>(it: I) -> Option<T>
503513
where
504514
T: Ord,
505515
I: Iterator<Item = T>,
506-
T: core::borrow::Borrow<OrdRoute<'a, OS>>
507516
{
508517
it.min()
509518
}
510519

511-
/*
512-
pub fn best_with_strat<'a, OS, AltOS, I, T1>(it: I)
513-
-> Option<OrdRoute<'a, AltOS>>
514-
where
515-
OS: 'a + OrdStrat,
516-
AltOS: 'a + OrdStrat,
517-
I: Iterator<Item = T1>,
518-
T1: core::borrow::Borrow<OrdRoute<'a, OS>>
519-
{
520-
it.map(|r| r.into_strat()).min()
521-
}
522-
*/
523-
524-
525-
// XXX would this be convenient?
526-
pub fn best_alt<'a, OS: OrdStrat>(
527-
_pamaps: impl Iterator<Item = &'a PaMap>,
528-
_tiebreakers: impl Iterator<Item = TiebreakerInfo>
529-
) -> Option<OrdRoute<'a, OS>> {
530-
unimplemented!()
531-
}
532-
533-
/// Returns the 'best' and second-best path.
520+
/// Alternative, generic version of `fn best_backup`.
534521
///
535-
/// If the iterator passed in contains no paths, `(None, None)` is returned.
536-
/// If the iterator yields only a single item, that will be the 'best' path,
537-
/// and the 'backup' will be None, i.e. `(Some(best), None)`.
522+
/// This method takes any iterator yielding items implementing Ord. As such,
523+
/// it has little to do with routes or path selection per se.
538524
///
539-
/// In all other cases (an iterator yielding two or more non-identical
540-
/// values), both members of the tuple should be `Some(..)`.
541-
///
542-
/// The returned 'best' path is the same as the path returned by [`fn best`].
543-
pub fn best_backup<'a, OS, T>(
544-
it: impl Iterator<Item = T>
545-
) -> (Option<T>, Option<T>)
546-
where
547-
OS: OrdStrat,
548-
T: Ord,
549-
T: core::borrow::Borrow<OrdRoute<'a, OS>>
525+
/// Note that because of this genericness, we have no access to methods or
526+
/// members of `T`, and thus are unable to compare actual contents such as a
527+
/// `PaMap` or the `TiebreakerInfo` when comparing `OrdRoute`s. This means the
528+
/// method can not check for any duplicate route information between `T`s, and
529+
/// really only order them. The caller therefore has to make sure to pass in
530+
/// an iterator that does not yield any duplicate routes.
531+
///
532+
/// This method enables the caller to attach additional information, as long
533+
/// as it implements `Ord`. For example, one can pass in an iterator over
534+
/// tuples of `OrdRoute` and something else. As long as the `OrdRoute` is the
535+
/// first member of that tuple and no duplicates are yielded from the
536+
/// iterator, the additional information is not used in the ordering process
537+
/// but is returned together with the 'best' and 'backup' tuples. This can be
538+
/// useful when the caller needs to relate routes to local IDs or something
539+
/// alike.
540+
pub fn best_backup_generic<I, T>(it: I) -> (Option<T>, Option<T>)
541+
where
542+
I: Iterator<Item = T>,
543+
T: Ord
550544
{
551545
let mut best = None;
552546
let mut backup = None;
@@ -564,6 +558,57 @@ pub fn best_backup<'a, OS, T>(
564558
// put it back in
565559
best = Some(cur_best);
566560

561+
// c is not better than best, now check backup
562+
match backup.take() {
563+
None => { backup = Some(c); }
564+
Some(cur_backup) => {
565+
if c < cur_backup {
566+
// c is preferred over current backup
567+
backup = Some(c);
568+
} else {
569+
// put it back in
570+
backup = Some(cur_backup);
571+
}
572+
573+
}
574+
}
575+
}
576+
}
577+
}
578+
579+
(best, backup)
580+
}
581+
582+
583+
// Attaches an index to a (generic) T, only used internally in _best_backup.
584+
type RouteWithIndex<T> = (usize, T);
585+
586+
// Internal version doing the heavy lifting for both best_backup and
587+
// best_backup_position. Returns both the routes themselves (T) and their
588+
// index (usize).
589+
fn _best_backup<'a, I, T, OS>(it: I)
590+
-> (Option<RouteWithIndex<T>>, Option<RouteWithIndex<T>>)
591+
where
592+
OS: OrdStrat,
593+
I: Iterator<Item = T>,
594+
T: Ord + core::borrow::Borrow<OrdRoute<'a, OS>>
595+
{
596+
let mut best: Option<RouteWithIndex<T>> = None;
597+
let mut backup: Option<RouteWithIndex<T>> = None;
598+
599+
for (idx, c) in it.enumerate() {
600+
match best.take() {
601+
None => { best = Some((idx, c)); continue }
602+
Some((idx_best, cur_best)) => {
603+
if c < cur_best {
604+
// c is preferred over current best
605+
best = Some((idx, c));
606+
backup = Some((idx_best, cur_best));
607+
continue;
608+
}
609+
// put it back in
610+
best = Some((idx_best, cur_best));
611+
567612
// c is not better than best, now check backup
568613
match backup.take() {
569614
None => {
@@ -580,24 +625,26 @@ pub fn best_backup<'a, OS, T>(
580625
// though the actual path attributes differ.
581626
//
582627

583-
// Best is always Some at this point, unwrap is safe.
584-
if best.as_ref().unwrap().borrow().inner() != c.borrow().inner() {
585-
backup = Some(c);
586-
} }
587-
Some(cur_backup) => {
628+
// `best` is always Some(..) at this point, but we do
629+
// an `if let` instead of an `unwrap` anyway.
630+
if let Some((_, cur_best)) = best.as_ref() {
631+
if cur_best.borrow().inner() != c.borrow().inner()
632+
{
633+
backup = Some((idx, c));
634+
}
635+
}
636+
}
637+
Some((idx_backup, cur_backup)) => {
588638
if c < cur_backup {
589639
// c is preferred over current backup
590640
// check if it is not the same route as 'best'
591-
if best.as_ref() != Some(&c) {
592-
backup = Some(c);
593-
} else {
594-
backup = Some(cur_backup);
641+
if best.as_ref().map(|t| &t.1) != Some(&c) {
642+
backup = Some((idx, c));
643+
continue;
595644
}
596-
} else {
597-
// put it back in
598-
backup = Some(cur_backup);
599645
}
600-
646+
// put it back in
647+
backup = Some((idx_backup, cur_backup));
601648
}
602649
}
603650
}
@@ -607,50 +654,37 @@ pub fn best_backup<'a, OS, T>(
607654
(best, backup)
608655
}
609656

610-
/*
611-
pub fn best_multistrat<'a, OS1: 'a + OrdStrat, OS2: 'a + OrdStrat, I>(it: I)
612-
-> Option<(OrdRoute<'a, OS1>, OrdRoute<'a, OS2>)>
613-
where
614-
I: Clone + Iterator<Item = OrdRoute<'a, OS1>>,
657+
/// Returns the 'best' and second-best path.
658+
///
659+
/// If the iterator passed in contains no paths, `(None, None)` is returned.
660+
/// If the iterator yields only a single item, that will be the 'best' path,
661+
/// and the 'backup' will be None, i.e. `(Some(best), None)`.
662+
///
663+
/// In all other cases (an iterator yielding two or more non-identical
664+
/// values), both members of the tuple should be `Some(..)`.
665+
///
666+
/// The returned 'best' path is the same as the path returned by [`fn best`].
667+
pub fn best_backup<'a, I, T, OS>(it: I) -> (Option<T>, Option<T>)
668+
where
669+
OS: OrdStrat,
670+
I: Iterator<Item = T>,
671+
T: Ord + core::borrow::Borrow<OrdRoute<'a, OS>>
615672
{
616-
let res1 = best(it.clone());
617-
let res1 = match res1 {
618-
Some(r) => r,
619-
None => return None
620-
};
621-
622-
// Given that res1 is not None, `it` is non-empty.
623-
// For a non-empty collection of OrdRoutes, there is always a best, so we
624-
// can unwrap().
625-
let res2 = best_with_strat::<'_, _, OS2, _>(it).unwrap();
626-
627-
Some((res1, res2))
673+
let (best, backup) = _best_backup(it);
674+
(best.map(|b| b.1), backup.map(|b| b.1))
628675
}
629-
*/
630-
631-
/*
632-
pub fn best_multistrat<'a, I, T1, T2, OS1, OS2>(it: I) -> Option<(T1, T2)>
633-
where
634-
T1: Ord + core::borrow::Borrow<OrdRoute<'a, OS1>>,
635-
T2: Ord,
636-
OS1: OrdStrat,
637-
OS2: OrdStrat,
638-
I: Clone + Iterator<Item = T1>,
676+
677+
/// Returns the index of the best and backup paths in the passed iterator.
678+
pub fn best_backup_position<'a, I, T, OS>(it: I)
679+
-> (Option<usize>, Option<usize>)
680+
where
681+
OS: OrdStrat,
682+
I: Iterator<Item = T>,
683+
T: Ord + core::borrow::Borrow<OrdRoute<'a, OS>>
639684
{
640-
let res1 = best(it.clone());
641-
let res1 = match res1 {
642-
Some(r) => r,
643-
None => return None
644-
};
645-
646-
// Given that res1 is not None, `it` is non-empty.
647-
// For a non-empty collection of OrdRoutes, there is always a best, so we
648-
// can unwrap().
649-
let res2 = best_with_strat::<'_, OS1, OS2, _>(it).unwrap();
650-
651-
Some((res1, res2))
685+
let (best, backup) = _best_backup(it);
686+
(best.map(|b| b.0), backup.map(|b| b.0))
652687
}
653-
*/
654688

655689

656690

0 commit comments

Comments
 (0)