1use std::sync::Arc;
17
18use axum::{extract::State, response::IntoResponse, routing::post};
19use axum_connect_rpc::extractor::ConnectRpc;
20use endhost_api_models::{SegmentsDiscovery, SegmentsError, UnderlayDiscovery};
21use endhost_api_protobuf::v1::{
22 ListSegmentsRequest, ListSegmentsResponse, ListUnderlaysRequest, ListUnderlaysResponse,
23};
24use sciparse::identifier::isd_asn::IsdAsn;
25
26pub const ENDHOST_API_V1: &str = "scion.endhost.v1";
28
29pub const UNDERLAY_SERVICE: &str = "UnderlayService";
31pub const SEGMENTS_SERVICE: &str = "SegmentsService";
33
34pub const LIST_UNDERLAYS: &str = "/ListUnderlays";
36pub const LIST_SEGMENTS: &str = "/ListSegments";
38
39pub fn nest_endhost_api(
41 base_router: axum::Router,
42 underlay_service: Arc<dyn UnderlayDiscovery>,
43 path_service: Arc<dyn SegmentsDiscovery>,
44) -> axum::Router {
45 let underlay_router = axum::Router::new()
46 .route(LIST_UNDERLAYS, post(list_underlays_handler))
47 .with_state(underlay_service);
48 let base_router = base_router.nest(
49 &service_path(ENDHOST_API_V1, UNDERLAY_SERVICE),
50 underlay_router,
51 );
52
53 let segment_router = axum::Router::new()
54 .route(LIST_SEGMENTS, post(list_segments_handler))
55 .with_state(path_service);
56 base_router.nest(
57 &service_path(ENDHOST_API_V1, SEGMENTS_SERVICE),
58 segment_router,
59 )
60}
61
62async fn list_underlays_handler(
63 State(underlay_service): State<Arc<dyn UnderlayDiscovery>>,
64 ConnectRpc(request): ConnectRpc<ListUnderlaysRequest>,
65) -> ConnectRpc<ListUnderlaysResponse> {
66 tracing::info!(request = ?request, "list_underlays request");
67 let response: ListUnderlaysResponse = underlay_service
68 .list_underlays(request.isd_as.map(IsdAsn::from).unwrap_or(IsdAsn::WILDCARD))
69 .into();
70 tracing::info!(response = ?response, "list_underlays response");
71 ConnectRpc(response)
72}
73
74async fn list_segments_handler(
75 State(path_service): State<Arc<dyn SegmentsDiscovery>>,
76 ConnectRpc(request): ConnectRpc<ListSegmentsRequest>,
77) -> Result<ConnectRpc<ListSegmentsResponse>, axum::response::Response> {
78 let (src, dst) = (
79 IsdAsn::from(request.src_isd_as),
80 IsdAsn::from(request.dst_isd_as),
81 );
82 tracing::debug!(?src, ?dst, page_size=?request.page_size, page_token=?request.page_token, "list_segments request");
83 match path_service
84 .list_segments(src, dst, request.page_size, request.page_token)
85 .await
86 {
87 Ok(segments) => {
88 let response: ListSegmentsResponse = segments.into();
89 tracing::info!(
90 num_core = response.core_segments.len(),
91 num_up = response.up_segments.len(),
92 num_down = response.down_segments.len(),
93 "list_segments response"
94 );
95 Ok(ConnectRpc(response))
96 }
97 Err(SegmentsError::InvalidArgument(msg)) => {
98 tracing::error!(src = %src, dst = %dst, error = %msg, "list_segments invalid argument");
99 Err((axum::http::StatusCode::BAD_REQUEST, msg).into_response())
100 }
101 Err(SegmentsError::InternalError(msg)) => {
102 tracing::error!(src = %src, dst = %dst, error = %msg, "list_segments internal error");
103 Err((axum::http::StatusCode::INTERNAL_SERVER_ERROR, msg).into_response())
104 }
105 }
106}
107
108fn service_path(api: &str, service: &str) -> String {
109 format!("/{api}.{service}")
110}