use std::collections::{HashMap, HashSet, VecDeque};
use tinyvec::TinyVec;
use crate::{
core::{
encode::{EncodeError, WireEncode},
view::View,
},
dataplane_path::standard::{
model::{InfoField, Segment, StandardPath},
types::InfoFieldFlags,
view::StandardPathView,
},
identifier::isd_asn::IsdAsn,
path::{
ScionPath,
metadata::{InterfaceMetadata, PathMetadata, path_interface::PathInterface},
},
segment::{Entry, PathSegment, SegmentID},
};
pub struct MultiGraph<'a, WeightFn, EntryType: Entry>
where
WeightFn: Fn(&InputSegment<'a, EntryType>, u64, bool) -> u64,
{
adjacencies: HashMap<Vertex, VertexInfo<'a, EntryType>>,
weight_fn: WeightFn,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Edge {
pub weight: u64,
pub shortcut_idx: usize,
pub peer: Option<usize>,
}
type EdgeMap<'a, EntryType> = HashMap<&'a InputSegment<'a, EntryType>, Edge>;
type VertexInfo<'a, EntryType> = HashMap<Vertex, EdgeMap<'a, EntryType>>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Vertex {
AS(IsdAsn),
Peering {
local_ia: IsdAsn,
local_ifid: u16,
peer_ia: IsdAsn,
peer_ifid: u16,
},
}
impl std::fmt::Display for Vertex {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Vertex::AS(ia) => write!(f, "IA({ia})"),
Vertex::Peering {
local_ia,
local_ifid: local_ifif,
peer_ia,
peer_ifid,
} => {
write!(
f,
"Peering({local_ia}#{local_ifif} -> {peer_ia}#{peer_ifid})"
)
}
}
}
}
impl Vertex {
#[allow(dead_code)]
#[inline]
fn mermaid_id(&self) -> String {
match self {
Vertex::AS(ia) => ia.to_u64().to_string(),
Vertex::Peering {
local_ia,
local_ifid: local_ifif,
peer_ia,
peer_ifid,
} => {
format!(
"{}-{}-{}-{}",
local_ia.to_u64(),
local_ifif,
peer_ia.to_u64(),
peer_ifid
)
}
}
}
#[allow(dead_code)]
#[inline]
pub fn mermaid_node(&self) -> String {
format!("{}[\"{}\"]", self.mermaid_id(), self)
}
#[inline]
const fn ia(&self) -> Option<IsdAsn> {
match self {
Vertex::AS(ia) => Some(*ia),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum InputSegment<'a, EntryType: Entry> {
Core(&'a PathSegment<EntryType>, SegmentID),
NonCore(&'a PathSegment<EntryType>, SegmentID),
}
impl<'a, EntryType: Entry> InputSegment<'a, EntryType> {
#[inline]
pub fn new_core(path_segment: &'a PathSegment<EntryType>) -> Self {
let id = path_segment.id();
Self::Core(path_segment, id)
}
#[inline]
pub fn new_non_core(path_segment: &'a PathSegment<EntryType>) -> Self {
let id = path_segment.id();
Self::NonCore(path_segment, id)
}
#[inline]
const fn is_non_core(&self) -> bool {
matches!(self, Self::NonCore(_, _))
}
#[inline]
const fn is_core(&self) -> bool {
matches!(self, Self::Core(_, _))
}
#[inline]
const fn id(&self) -> &SegmentID {
match self {
Self::Core(_, id) => id,
Self::NonCore(_, id) => id,
}
}
#[inline]
const fn path_segment(&self) -> &PathSegment<EntryType> {
match self {
Self::Core(path_segment, _) => path_segment,
Self::NonCore(path_segment, _) => path_segment,
}
}
}
#[inline]
pub(crate) fn number_of_hops<EntryType: Entry>(
segment: &InputSegment<EntryType>,
shortcut_idx: u64,
towards_peer: bool,
) -> u64 {
let weight = segment.path_segment().len() as u64 - 1 - shortcut_idx;
if !towards_peer {
weight
} else {
weight + 1
}
}
impl<'a, EntryType: Entry, F> MultiGraph<'a, F, EntryType>
where
F: Fn(&InputSegment<EntryType>, u64, bool) -> u64,
{
#[inline]
pub fn new(weight_fn: F) -> Self {
Self {
adjacencies: HashMap::new(),
weight_fn,
}
}
#[inline]
pub fn add_segments(&mut self, segments: &'a [InputSegment<EntryType>]) -> usize {
let mut added = 0;
for segment in segments {
if self.add_segment(segment).is_ok() {
added += 1;
}
}
added
}
#[inline]
fn add_segment(&mut self, segment: &'a InputSegment<EntryType>) -> Result<(), &'static str> {
match segment {
InputSegment::Core(..) => {
self.add_core_segment(segment)?;
}
InputSegment::NonCore(..) => {
self.add_non_core_segment(segment)?;
}
}
Ok(())
}
#[inline]
fn add_core_segment(
&mut self,
segment: &'a InputSegment<EntryType>,
) -> Result<(), &'static str> {
let first_ia = segment.path_segment().first_ia();
let last_ia = segment.path_segment().last_ia();
let (Some(first_ia), Some(last_ia)) = (first_ia, last_ia) else {
return Err("Segment does not contain any hops");
};
self.add_edge(
Vertex::AS(first_ia),
Vertex::AS(last_ia),
segment,
Edge {
weight: (self.weight_fn)(segment, 0, false),
shortcut_idx: 0,
peer: None,
},
);
Ok(())
}
fn add_non_core_segment(
&mut self,
segment: &'a InputSegment<EntryType>,
) -> Result<(), &'static str> {
let Some(leaf) = segment.path_segment().last_ia() else {
return Err("Segment does not contain any hops");
};
for (idx, entry) in segment.path_segment().iter().enumerate().rev() {
if idx != segment.path_segment().len() - 1 {
self.add_edge(
Vertex::AS(leaf),
Vertex::AS(entry.local),
segment,
Edge {
weight: (self.weight_fn)(segment, idx as u64, false),
shortcut_idx: idx,
peer: None,
},
);
}
for (peer_idx, peer) in entry.peer_entries.iter().enumerate() {
self.add_directed_edge(
Vertex::AS(leaf),
Vertex::Peering {
local_ia: entry.local,
local_ifid: peer.hop_field.cons_ingress,
peer_ia: peer.peer,
peer_ifid: peer.peer_interface,
},
segment,
Edge {
weight: (self.weight_fn)(segment, idx as u64, true),
shortcut_idx: idx,
peer: Some(peer_idx),
},
);
self.add_directed_edge(
Vertex::Peering {
local_ia: peer.peer,
local_ifid: peer.peer_interface,
peer_ia: entry.local,
peer_ifid: peer.hop_field.cons_ingress,
},
Vertex::AS(leaf),
segment,
Edge {
weight: (self.weight_fn)(segment, idx as u64, false),
shortcut_idx: idx,
peer: Some(peer_idx),
},
)
}
}
Ok(())
}
#[inline]
fn add_edge(
&mut self,
src: Vertex,
dst: Vertex,
segment: &'a InputSegment<EntryType>,
edge: Edge,
) {
self.add_directed_edge(src, dst, segment, edge);
self.add_directed_edge(dst, src, segment, edge);
}
#[inline]
fn add_directed_edge(
&mut self,
src: Vertex,
dst: Vertex,
segment: &'a InputSegment<EntryType>,
edge: Edge,
) {
self.adjacencies
.entry(src)
.or_default()
.entry(dst)
.or_default()
.insert(segment, edge);
}
pub fn get_paths(&self, src: IsdAsn, dst: IsdAsn) -> Vec<PathSolution<'_, EntryType>> {
let mut solutions = Vec::new();
let mut queue = VecDeque::from([PathSolution::new(Vertex::AS(src))]);
while let Some(current_solution) = queue.pop_front() {
if let Some(next_vertex) = self.adjacencies.get(¤t_solution.current_vertex) {
for (next_vertex, edges) in next_vertex {
for (segment, edge) in edges {
let new_solution = match current_solution.try_add_edge(SolutionEdge {
edge: *edge,
src: current_solution.current_vertex,
dst: *next_vertex,
segment,
}) {
Some(s) => s,
None => continue,
};
if *next_vertex == Vertex::AS(dst) {
solutions.push(new_solution);
} else {
queue.push_back(new_solution);
}
}
}
}
}
solutions.sort_by(|a, b| {
let d = a.cost.cmp(&b.cost).then(a.edges.len().cmp(&b.edges.len()));
if d.is_ne() {
return d;
}
for (edge_a, edge_b) in a.edges.iter().zip(b.edges.iter()) {
let d = edge_a.edge.peer.cmp(&edge_b.edge.peer);
if d.is_ne() {
return d;
}
let d = edge_a
.edge
.shortcut_idx
.cmp(&edge_b.edge.shortcut_idx)
.reverse();
if d.is_ne() {
return d;
}
let d = edge_a.segment.id().cmp(edge_b.segment.id());
if d.is_ne() {
return d;
}
}
std::cmp::Ordering::Equal
});
solutions
}
#[allow(dead_code)]
pub fn mermaid_flowchart(&self) -> String {
let mut flowchart = String::new();
flowchart.push_str("flowchart TD;\n");
let mut seen = HashSet::new();
for (src, dsts) in &self.adjacencies {
for (dst, edges) in dsts {
for (segment, edge) in edges {
if let (Vertex::AS(_), Vertex::AS(_)) = (src, dst) {
if seen.contains(&(dst, src, segment.id())) {
continue;
} else {
flowchart.push_str(&format!(
"{} ---|Seg: {} {} Weight: {} Shortcut: {}{}| {}\n",
src.mermaid_node(),
segment.path_segment().info().segment_id,
if segment.is_core() {
"Core"
} else {
"Non-Core"
},
edge.weight,
edge.shortcut_idx,
if let Some(peer) = edge.peer {
format!(" Peer: {peer}")
} else {
"".to_string()
},
dst.mermaid_node()
));
seen.insert((src, dst, segment.id()));
}
} else {
flowchart.push_str(&format!(
"{} -->|Seg: {} {} Weight: {} Shortcut: {}{}| {}\n",
src.mermaid_node(),
segment.path_segment().info().segment_id,
if segment.is_core() {
"Core"
} else {
"Non-Core"
},
edge.weight,
edge.shortcut_idx,
if let Some(peer) = edge.peer {
format!(" Peer: {peer}")
} else {
"".to_string()
},
dst.mermaid_node()
));
}
}
}
}
flowchart
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct SolutionEdge<'a, EntryType: Entry> {
pub edge: Edge,
pub src: Vertex,
pub dst: Vertex,
pub segment: &'a InputSegment<'a, EntryType>,
}
impl<'a, EntryType: Entry> SolutionEdge<'a, EntryType> {
fn initialize_segment_id(&self) -> u16 {
let in_construction_order = self.dst.ia().is_some_and(|dst| {
dst == self
.segment
.path_segment()
.last_ia()
.expect("Segments are checked to have at least one hop")
});
let mut stop_at = if in_construction_order {
self.edge.shortcut_idx
} else {
self.segment.path_segment().len() - 1
};
if self.edge.peer.is_some() && self.edge.shortcut_idx == stop_at {
stop_at += 1;
}
self.segment.path_segment().as_entries[..stop_at]
.iter()
.fold(
self.segment.path_segment().info().segment_id,
|beta, entry| {
beta ^ u16::from_be_bytes([
entry.get().hop_entry.hop_field.mac[0],
entry.get().hop_entry.hop_field.mac[1],
])
},
)
}
}
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct PathSolution<'a, EntryType: Entry> {
edges: Vec<SolutionEdge<'a, EntryType>>,
current_vertex: Vertex,
cost: u64,
}
impl<'a, EntryType: Entry> std::fmt::Debug for PathSolution<'a, EntryType> {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"PathSolution({})",
self.edges
.iter()
.map(|e| format!("{}->{}", e.src, e.dst))
.collect::<Vec<_>>()
.join(", ")
)
}
}
impl<'a, EntryType: Entry> PathSolution<'a, EntryType> {
#[inline]
pub const fn new(current_vertex: Vertex) -> Self {
Self {
edges: Vec::new(),
current_vertex,
cost: 0,
}
}
#[inline]
pub fn try_add_edge(&self, e: SolutionEdge<'a, EntryType>) -> Option<Self> {
if !self.valid_next_seg(e.segment) {
return None;
}
let cost = self.cost + e.edge.weight;
let current_vertex = e.dst;
let mut new_edges = self.edges.clone();
new_edges.push(e);
Some(Self {
edges: new_edges,
current_vertex,
cost,
})
}
#[inline]
fn valid_next_seg(&self, segment: &InputSegment<EntryType>) -> bool {
match self.edges.as_slice() {
[] => true,
[last] => {
last.segment.is_non_core() || segment.is_non_core()
}
[first, second] => {
first.segment.is_non_core() && second.segment.is_core() && segment.is_non_core()
}
_ => {
false
}
}
}
pub fn path(&self) -> Result<Option<ScionPath>, EncodeError> {
if self.edges.is_empty() {
return Ok(None);
}
let mut mtu = u16::MAX;
let mut path = StandardPath::new_empty();
let mut interfaces = Vec::new();
for solution_edge in self.edges.iter() {
let mut segment_interfaces = Vec::new();
let mut hops = TinyVec::with_capacity(
solution_edge.segment.path_segment().len() - solution_edge.edge.shortcut_idx,
);
for (idx, as_entry) in solution_edge
.segment
.path_segment()
.as_entries
.iter()
.enumerate()
.skip(solution_edge.edge.shortcut_idx)
.rev()
{
let as_entry = as_entry.get();
let hopfield = match solution_edge.edge.peer {
Some(peer_idx) if idx == solution_edge.edge.shortcut_idx => {
let peer = as_entry.peer_entries.get(peer_idx).expect(
"Peer index is checked to be valid when adding edges to the graph",
);
let hopfield = peer.hop_field.to_dp_hopfield();
mtu = std::cmp::min(mtu, peer.peer_mtu);
hopfield
}
_ => {
let hopfield = as_entry.hop_entry.hop_field.to_dp_hopfield();
let is_shortcut = idx == solution_edge.edge.shortcut_idx && idx != 0;
if as_entry.hop_entry.ingress_mtu != 0 && !is_shortcut {
mtu = std::cmp::min(mtu, as_entry.hop_entry.ingress_mtu);
}
hopfield
}
};
if hopfield.cons_egress != 0 {
segment_interfaces.push(InterfaceMetadata::new_without_metadata(
PathInterface {
isd_asn: as_entry.local,
id: hopfield.cons_egress,
},
));
}
let is_shortcut = idx == solution_edge.edge.shortcut_idx && idx != 0;
let is_peer =
idx == solution_edge.edge.shortcut_idx && solution_edge.edge.peer.is_some();
if hopfield.cons_ingress != 0 && (!is_shortcut || is_peer) {
segment_interfaces.push(InterfaceMetadata::new_without_metadata(
PathInterface {
isd_asn: as_entry.local,
id: hopfield.cons_ingress,
},
));
}
hops.push(hopfield);
mtu = std::cmp::min(mtu, as_entry.mtu as u16);
}
let cons_dir = solution_edge.dst.ia().is_some_and(|dst| {
dst == solution_edge
.segment
.path_segment()
.last_ia()
.expect("Segments are checked to have at least one hop")
});
if cons_dir {
hops.reverse();
segment_interfaces.reverse();
}
interfaces.extend(segment_interfaces);
let mut flags = InfoFieldFlags::empty();
flags.set(InfoFieldFlags::CONS_DIR, cons_dir);
flags.set(InfoFieldFlags::PEERING, solution_edge.edge.peer.is_some());
if path
.segments
.try_push(Segment {
info_field: InfoField {
flags,
segment_id: solution_edge.initialize_segment_id(),
timestamp: solution_edge.segment.path_segment().info().timestamp,
},
hop_fields: hops,
})
.is_some()
{
panic!("valid path segment should always fit in the path")
}
}
let expiration = path.expiration();
let encoded = path.try_encode_to_vec()?.into_boxed_slice();
let encoded = crate::dataplane_path::view::ScionDpPathView::Standard(
StandardPathView::try_from_boxed(encoded)
.expect("valid path encoding should always produce a valid view"),
);
let start_ia = interfaces
.first()
.expect("edges are checked to be not empty")
.interface
.isd_asn;
let end_ia = interfaces
.last()
.expect("edges are checked to be not empty")
.interface
.isd_asn;
let metadata = PathMetadata {
expiration: expiration.into(),
mtu,
interfaces: Some(interfaces),
epic_auth: None,
notes: None,
};
let path = ScionPath::new(start_ia, end_ia, encoded, Some(metadata), None);
Ok(Some(path))
}
}