snap_control/crpc_api/
api_service.rs1use std::sync::Arc;
17
18use axum::{Extension, Router, extract::State, http::StatusCode};
19use scion_sdk_axum_connect_rpc::{error::CrpcError, extractor::ConnectRpc};
20use snap_tokens::snap_token::SnapTokenClaims;
21
22use crate::{
23 crpc_api::api_service::model::SessionManager,
24 protobuf::anapaya::snap::v1::api_service::{
25 GetSnapDataPlaneSessionGrantRequest, GetSnapDataPlaneSessionGrantResponse,
26 RenewSnapDataPlaneSessionGrantRequest, RenewSnapDataPlaneSessionGrantResponse,
27 },
28};
29
30pub mod model {
32 use std::net::SocketAddr;
33
34 use axum::http::StatusCode;
35 use snap_tokens::snap_token::SnapTokenClaims;
36
37 pub trait SessionManager: Send + Sync {
39 fn create_session(
41 &self,
42 snap_token: SnapTokenClaims,
43 ) -> Result<Vec<SessionGrant>, (StatusCode, anyhow::Error)>;
44 fn renew_session(
46 &self,
47 address: SocketAddr,
48 snap_token: SnapTokenClaims,
49 ) -> Result<SessionGrant, (StatusCode, anyhow::Error)>;
50 }
51
52 pub struct SessionGrant {
54 pub address: SocketAddr,
56 pub token: String,
58 }
59}
60
61pub(crate) mod convert {
62 use std::net::AddrParseError;
63
64 use thiserror::Error;
65
66 use crate::{
67 crpc_api::api_service::model::SessionGrant, protobuf::anapaya::snap::v1::api_service as rpc,
68 };
69
70 impl From<SessionGrant> for rpc::SnapDataPlaneSessionGrant {
72 fn from(value: SessionGrant) -> Self {
73 rpc::SnapDataPlaneSessionGrant {
74 address: value.address.to_string(),
75 token: value.token,
76 }
77 }
78 }
79
80 #[derive(Debug, Error, PartialEq, Eq)]
82 pub enum SessionGrantError {
83 #[error("session grant field is required")]
86 MissingGrant,
87 #[error("invalid address: {0}")]
89 InvalidAddress(#[from] AddrParseError),
90 }
91
92 impl TryFrom<rpc::SnapDataPlaneSessionGrant> for SessionGrant {
94 type Error = SessionGrantError;
95 fn try_from(value: rpc::SnapDataPlaneSessionGrant) -> Result<Self, Self::Error> {
96 Ok(SessionGrant {
97 address: value.address.parse()?,
98 token: value.token,
99 })
100 }
101 }
102
103 impl TryFrom<rpc::GetSnapDataPlaneSessionGrantResponse> for Vec<SessionGrant> {
104 type Error = SessionGrantError;
105 fn try_from(value: rpc::GetSnapDataPlaneSessionGrantResponse) -> Result<Self, Self::Error> {
106 value
107 .grants
108 .into_iter()
109 .map(SessionGrant::try_from)
110 .collect()
111 }
112 }
113
114 impl TryFrom<rpc::RenewSnapDataPlaneSessionGrantResponse> for SessionGrant {
115 type Error = SessionGrantError;
116 fn try_from(
117 value: rpc::RenewSnapDataPlaneSessionGrantResponse,
118 ) -> Result<Self, Self::Error> {
119 match value.grant {
120 Some(grant) => SessionGrant::try_from(grant),
121 None => Err(SessionGrantError::MissingGrant),
122 }
123 }
124 }
125}
126
127pub(crate) const SERVICE_PATH: &str = "/anapaya.snap.v1.SnapControl";
128pub(crate) const GET_SNAP_DATA_PLANE_SESSION_GRANT: &str = "/GetSnapDataPlaneSessionGrant";
129pub(crate) const RENEW_SNAP_DATA_PLANE_SESSION_GRANT: &str = "/RenewSnapDataPlaneSessionGrant";
130
131pub fn nest_snap_control_api(
133 router: axum::Router,
134 session_service: Arc<dyn SessionManager>,
135) -> axum::Router {
136 router.nest(
137 SERVICE_PATH,
138 Router::new()
139 .route(
140 GET_SNAP_DATA_PLANE_SESSION_GRANT,
141 axum::routing::post(add_snap_data_plane_session_handler),
142 )
143 .route(
144 RENEW_SNAP_DATA_PLANE_SESSION_GRANT,
145 axum::routing::post(renew_snap_data_plane_session_handler),
146 )
147 .with_state(session_service),
148 )
149}
150
151#[axum_macros::debug_handler]
152async fn add_snap_data_plane_session_handler(
153 State(session_manager): State<Arc<dyn SessionManager>>,
154 snap_token: Extension<SnapTokenClaims>,
155 ConnectRpc(_request): ConnectRpc<GetSnapDataPlaneSessionGrantRequest>,
156) -> Result<ConnectRpc<GetSnapDataPlaneSessionGrantResponse>, CrpcError> {
157 let grants = session_manager.create_session(snap_token.0.clone())?;
158 Ok(ConnectRpc(GetSnapDataPlaneSessionGrantResponse {
159 grants: grants.into_iter().map(Into::into).collect(),
160 }))
161}
162
163async fn renew_snap_data_plane_session_handler(
164 State(session_manager): State<Arc<dyn SessionManager>>,
165 snap_token: Extension<SnapTokenClaims>,
166 ConnectRpc(request): ConnectRpc<RenewSnapDataPlaneSessionGrantRequest>,
167) -> Result<ConnectRpc<RenewSnapDataPlaneSessionGrantResponse>, CrpcError> {
168 let address = request.address.parse().map_err(|e| {
169 CrpcError::new(
170 StatusCode::BAD_REQUEST.into(),
171 format!("invalid data plane address: {e}"),
172 )
173 })?;
174
175 let grant = session_manager.renew_session(address, snap_token.0.clone())?;
176 Ok(ConnectRpc(RenewSnapDataPlaneSessionGrantResponse {
177 grant: Some(grant.into()),
178 }))
179}