Skip to main content

endhost_api/
routes.rs

1// Copyright 2025 Anapaya Systems
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//   http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14//! Endhost API endpoint definitions and endpoint handlers.
15
16use std::sync::Arc;
17
18use axum::{extract::State, response::IntoResponse, routing::post};
19use endhost_api_models::{SegmentsDiscovery, SegmentsError, UnderlayDiscovery};
20use endhost_api_protobuf::v1::{
21    ListSegmentsRequest, ListSegmentsResponse, ListUnderlaysRequest, ListUnderlaysResponse,
22};
23use scion_sdk_axum_connect_rpc::extractor::ConnectRpc;
24use sciparse::identifier::isd_asn::IsdAsn;
25
26/// Endhost API base path.
27pub const ENDHOST_API_V1: &str = "scion.endhost.v1";
28
29/// Underlay service.
30pub const UNDERLAY_SERVICE: &str = "UnderlayService";
31/// Segments service.
32pub const SEGMENTS_SERVICE: &str = "SegmentsService";
33
34/// Path service.
35#[deprecated(note = "Use SEGMENTS_SERVICE instead")]
36pub const PATH_SERVICE: &str = "PathService";
37
38/// List underlays endpoint.
39pub const LIST_UNDERLAYS: &str = "/ListUnderlays";
40/// List segments endpoint.
41pub const LIST_SEGMENTS: &str = "/ListSegments";
42
43/// List paths endpoint.
44#[deprecated(note = "Use LIST_SEGMENTS instead")]
45pub const LIST_PATHS: &str = "/ListPaths";
46
47/// Nests the endhost API routes into the provided `base_router`.
48pub fn nest_endhost_api(
49    base_router: axum::Router,
50    underlay_service: Arc<dyn UnderlayDiscovery>,
51    path_service: Arc<dyn SegmentsDiscovery>,
52) -> axum::Router {
53    let underlay_router = axum::Router::new()
54        .route(LIST_UNDERLAYS, post(list_underlays_handler))
55        .with_state(underlay_service);
56    let base_router = base_router.nest(
57        &service_path(ENDHOST_API_V1, UNDERLAY_SERVICE),
58        underlay_router,
59    );
60
61    // XXX(bunert): deprecated path service
62    #[allow(deprecated)]
63    let path_router = axum::Router::new()
64        .route(LIST_PATHS, post(list_segments_handler))
65        .with_state(path_service.clone());
66    #[allow(deprecated)]
67    let base_router = base_router.nest(&service_path(ENDHOST_API_V1, PATH_SERVICE), path_router);
68
69    let segment_router = axum::Router::new()
70        .route(LIST_SEGMENTS, post(list_segments_handler))
71        .with_state(path_service);
72    base_router.nest(
73        &service_path(ENDHOST_API_V1, SEGMENTS_SERVICE),
74        segment_router,
75    )
76}
77
78async fn list_underlays_handler(
79    State(underlay_service): State<Arc<dyn UnderlayDiscovery>>,
80    ConnectRpc(request): ConnectRpc<ListUnderlaysRequest>,
81) -> ConnectRpc<ListUnderlaysResponse> {
82    tracing::info!(request = ?request, "list_underlays request");
83    let response: ListUnderlaysResponse = underlay_service
84        .list_underlays(request.isd_as.map(IsdAsn::from).unwrap_or(IsdAsn::WILDCARD))
85        .into();
86    tracing::info!(response = ?response, "list_underlays response");
87    ConnectRpc(response)
88}
89
90async fn list_segments_handler(
91    State(path_service): State<Arc<dyn SegmentsDiscovery>>,
92    ConnectRpc(request): ConnectRpc<ListSegmentsRequest>,
93) -> Result<ConnectRpc<ListSegmentsResponse>, axum::response::Response> {
94    let (src, dst) = (
95        IsdAsn::from(request.src_isd_as),
96        IsdAsn::from(request.dst_isd_as),
97    );
98    tracing::debug!(?src, ?dst, page_size=?request.page_size, page_token=?request.page_token, "list_segments request");
99    match path_service
100        .list_segments(src, dst, request.page_size, request.page_token)
101        .await
102    {
103        Ok(segments) => {
104            let response: ListSegmentsResponse = segments.into();
105            tracing::info!(
106                num_core = response.core_segments.len(),
107                num_up = response.up_segments.len(),
108                num_down = response.down_segments.len(),
109                "list_segments response"
110            );
111            Ok(ConnectRpc(response))
112        }
113        Err(SegmentsError::InvalidArgument(msg)) => {
114            tracing::error!(src = %src, dst = %dst, error = %msg, "list_segments invalid argument");
115            Err((axum::http::StatusCode::BAD_REQUEST, msg).into_response())
116        }
117        Err(SegmentsError::InternalError(msg)) => {
118            tracing::error!(src = %src, dst = %dst, error = %msg, "list_segments internal error");
119            Err((axum::http::StatusCode::INTERNAL_SERVER_ERROR, msg).into_response())
120        }
121    }
122}
123
124fn service_path(api: &str, service: &str) -> String {
125    format!("/{api}.{service}")
126}