scion_stack/path/
fetcher.rs1use std::sync::Arc;
21
22use endhost_api_client::client::EndhostApiClient;
23use sciparse::{identifier::isd_asn::IsdAsn, path::ScionPath};
24
25use crate::path::fetcher::traits::{
26 PathFetchError, PathFetcher, SegmentFetchError, SegmentFetcher, Segments,
27};
28
29pub mod traits {
31 use std::{borrow::Cow, future::Future};
32
33 use sciparse::{identifier::isd_asn::IsdAsn, path::ScionPath, segment::SignedPathSegment};
34
35 pub trait PathFetcher: Send + Sync + 'static {
37 fn fetch_paths(
39 &self,
40 src: IsdAsn,
41 dst: IsdAsn,
42 ) -> impl Future<Output = Result<Vec<ScionPath>, PathFetchError>> + Send + '_;
43 }
44
45 #[derive(Debug, thiserror::Error)]
47 #[non_exhaustive]
48 pub enum PathFetchError {
49 #[error("failed to fetch segments: {0}")]
51 FetchSegments(#[from] SegmentFetchError),
52
53 #[error("no paths found")]
55 NoPathsFound,
56
57 #[error("internal error: {0}")]
59 InternalError(Cow<'static, str>),
60 }
61
62 #[async_trait::async_trait]
64 pub trait SegmentFetcher: Send + Sync + 'static {
65 async fn fetch_segments(
67 &self,
68 src: IsdAsn,
69 dst: IsdAsn,
70 ) -> Result<Segments, SegmentFetchError>;
71 }
72
73 pub type SegmentFetchError = Box<dyn std::error::Error + Send + Sync>;
80
81 #[derive(Debug)]
83 #[non_exhaustive]
84 pub struct Segments {
85 pub core_segments: Vec<SignedPathSegment>,
87 pub non_core_segments: Vec<SignedPathSegment>,
89 }
90
91 impl Segments {
92 #[must_use]
94 pub fn new(
95 core_segments: Vec<SignedPathSegment>,
96 non_core_segments: Vec<SignedPathSegment>,
97 ) -> Self {
98 Self {
99 core_segments,
100 non_core_segments,
101 }
102 }
103 }
104}
105
106pub struct PathFetcherImpl {
108 segment_fetchers: Vec<(String, Arc<dyn SegmentFetcher>)>,
109 timeout: std::time::Duration,
112}
113
114impl PathFetcherImpl {
115 pub fn new(
117 segment_fetchers: Vec<(String, Arc<dyn SegmentFetcher>)>,
118 timeout: std::time::Duration,
119 ) -> Self {
120 Self {
121 segment_fetchers,
122 timeout,
123 }
124 }
125}
126
127impl PathFetcher for PathFetcherImpl {
128 async fn fetch_paths(
129 &self,
130 src: IsdAsn,
131 dst: IsdAsn,
132 ) -> Result<Vec<ScionPath>, PathFetchError> {
133 let mut all_core_segments = Vec::new();
134 let mut all_non_core_segments = Vec::new();
135
136 let fetch_tasks: Vec<_> = self
138 .segment_fetchers
139 .iter()
140 .map(|(_, fetcher)| {
141 tokio::time::timeout(self.timeout, fetcher.fetch_segments(src, dst))
142 })
143 .collect();
144
145 let results = futures::future::join_all(fetch_tasks).await;
146
147 let mut errors = Vec::new();
149
150 for (i, result) in results.into_iter().enumerate() {
151 let fetcher_name = &self.segment_fetchers[i].0;
152 match result {
153 Ok(res) => {
154 match res {
155 Ok(segments) => {
156 tracing::info!(
157 name = %fetcher_name,
158 n_core_segments = segments.core_segments.len(),
159 n_non_core_segments = segments.non_core_segments.len(),
160 %src,
161 %dst,
162 "Segment fetcher succeeded"
163 );
164 all_core_segments.extend(segments.core_segments);
165 all_non_core_segments.extend(segments.non_core_segments);
166 }
167 Err(e) => {
168 errors.push((fetcher_name.clone(), e));
169 }
170 }
171 }
172 Err(e) => {
173 errors.push((
174 fetcher_name.clone(),
175 Box::new(e) as Box<dyn std::error::Error + Send + Sync>,
176 ));
177 }
178 }
179 }
180
181 let paths =
182 sciparse::path::combinator::combine(src, dst, all_core_segments, all_non_core_segments);
183
184 for (fetcher_name, error) in &errors {
185 tracing::warn!(
186 name = %fetcher_name,
187 %error,
188 %src,
189 %dst,
190 "Segment fetcher failed"
191 );
192 }
193
194 if let Some((_name, err)) = errors.into_iter().next()
197 && paths.is_empty()
198 {
199 return Err(PathFetchError::FetchSegments(err));
200 }
201
202 Ok(paths)
203 }
204}
205
206pub struct EndhostApiSegmentFetcher {
208 client: Arc<dyn EndhostApiClient>,
209}
210
211impl EndhostApiSegmentFetcher {
212 pub fn new(client: Arc<dyn EndhostApiClient>) -> Self {
214 Self { client }
215 }
216}
217
218#[async_trait::async_trait]
219impl SegmentFetcher for EndhostApiSegmentFetcher {
220 async fn fetch_segments(
221 &self,
222 src: IsdAsn,
223 dst: IsdAsn,
224 ) -> Result<Segments, SegmentFetchError> {
225 let resp = self
226 .client
227 .list_segments(src, dst, 128, String::new())
228 .await?;
229
230 tracing::trace!(
231 n_core=resp.segments.core_segments.len(),
232 n_up=resp.segments.up_segments.len(),
233 n_down=resp.segments.down_segments.len(),
234 src = %src,
235 dst = %dst,
236 "Received segments from endhost API"
237 );
238
239 let (core_segments, non_core_segments) = resp.segments.split_parts();
240 Ok(Segments {
241 core_segments: core_segments.into_iter().collect(),
242 non_core_segments: non_core_segments.into_iter().collect(),
243 })
244 }
245}