use std::collections::HashSet;
use scion_proto::address::IsdAsn;
use crate::network::scion::topology::{FastTopologyLookup, ScionAs, ScionLink};
pub trait TopologyLinkVisitor: Clone {
type Output;
fn visit(&mut self, used_link: Option<&ScionLink>, current_as: &ScionAs);
fn finish(self, final_link: bool) -> Option<Self::Output>;
#[expect(unused_variables)]
fn should_follow_link(&self, current_as: &ScionAs, next_link: &ScionLink) -> bool {
true
}
}
pub fn walk_all_links<'topo, Visitor: TopologyLinkVisitor>(
visitor: Visitor,
start_as: IsdAsn,
topo_lookup: &FastTopologyLookup<'topo>,
) -> Vec<Visitor::Output> {
let Some(start_as) = topo_lookup.topology.as_map.get(&start_as) else {
return vec![];
};
let mut visited: HashSet<IsdAsn> = HashSet::new();
return visit_recurse(start_as, None, visitor, &mut visited, topo_lookup);
fn visit_recurse<'topo, VisitorRec: TopologyLinkVisitor>(
current_as: &'topo ScionAs,
used_link: Option<&ScionLink>,
mut visitor: VisitorRec,
visited: &mut HashSet<IsdAsn>,
topo_lookup: &FastTopologyLookup<'topo>,
) -> Vec<VisitorRec::Output> {
if !visited.insert(current_as.isd_as) {
return vec![]; }
visitor.visit(used_link, current_as);
let empty_vec = Vec::new();
let links = topo_lookup
.as_to_link_map
.get(¤t_as.isd_as)
.unwrap_or(&empty_vec);
let mut result = Vec::new();
for next_link in links {
if Some(*next_link) == used_link {
continue;
}
if visitor.should_follow_link(current_as, next_link) {
let Some(next_interface) = next_link.get_peer(¤t_as.isd_as) else {
debug_assert!(false, "Link {next_link} has no peer for AS {current_as:?}");
continue; };
let Some(next_as) = topo_lookup.topology.as_map.get(&next_interface.isd_as) else {
debug_assert!(false, "Missing as in topology: {next_interface:?}");
continue; };
let results = visit_recurse(
next_as,
Some(next_link),
visitor.clone(),
visited,
topo_lookup,
);
result.extend(results.into_iter());
}
}
visited.remove(¤t_as.isd_as);
let final_link = result.is_empty();
result.extend(visitor.finish(final_link));
result
}
}