snap_control/
client.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//! Connect RPC client for the SNAP control plane API.
15
16use std::{net::SocketAddr, ops::Deref, sync::Arc};
17
18use async_trait::async_trait;
19use endhost_api_client::client::CrpcEndhostApiClient;
20use scion_sdk_reqwest_connect_rpc::{client::CrpcClientError, token_source::TokenSource};
21use url::Url;
22
23use crate::{
24    crpc_api::api_service::{
25        GET_SNAP_DATA_PLANE_SESSION_GRANT, RENEW_SNAP_DATA_PLANE_SESSION_GRANT, SERVICE_PATH,
26        convert::SessionGrantError, model::SessionGrant,
27    },
28    protobuf::anapaya::snap::v1::api_service::{
29        GetSnapDataPlaneSessionGrantRequest, GetSnapDataPlaneSessionGrantResponse,
30        RenewSnapDataPlaneSessionGrantRequest, RenewSnapDataPlaneSessionGrantResponse,
31    },
32};
33
34/// Re-export the endhost API client and the reqwest connect RPC cllient.
35pub mod re_export {
36    pub use endhost_api_client::client::{CrpcEndhostApiClient, EndhostApiClient};
37    pub use scion_sdk_reqwest_connect_rpc::{client::CrpcClientError, token_source::*};
38}
39
40/// SNAP control plane API trait.
41#[async_trait]
42pub trait ControlPlaneApi: Send + Sync {
43    /// Creates SNAP data plane sessions for all data planes.
44    async fn create_data_plane_sessions(&self) -> Result<Vec<SessionGrant>, CrpcClientError>;
45    /// Renews a SNAP data plane session for the given address.
46    async fn renew_data_plane_session(
47        &self,
48        addr: SocketAddr,
49    ) -> Result<SessionGrant, CrpcClientError>;
50}
51
52/// Connect RPC client for the SNAP control plane API.
53pub struct CrpcSnapControlClient {
54    client: CrpcEndhostApiClient,
55}
56
57impl Deref for CrpcSnapControlClient {
58    type Target = CrpcEndhostApiClient;
59
60    fn deref(&self) -> &Self::Target {
61        &self.client
62    }
63}
64
65impl CrpcSnapControlClient {
66    /// Creates a new client with default settings
67    pub fn new(base_url: &Url) -> anyhow::Result<Self> {
68        let client = CrpcEndhostApiClient::new(base_url)?;
69        Ok(Self { client })
70    }
71
72    /// Creates a new client with the provided `reqwest::Client`.
73    pub fn new_with_client(base_url: &Url, client: reqwest::Client) -> anyhow::Result<Self> {
74        Ok(Self {
75            client: CrpcEndhostApiClient::new_with_client(base_url, client)?,
76        })
77    }
78
79    /// Uses the provided token source for authentication.
80    pub fn use_token_source(&mut self, token_source: Arc<dyn TokenSource>) -> &mut Self {
81        self.client.use_token_source(token_source);
82        self
83    }
84}
85
86#[async_trait]
87impl ControlPlaneApi for CrpcSnapControlClient {
88    async fn create_data_plane_sessions(&self) -> Result<Vec<SessionGrant>, CrpcClientError> {
89        self.client
90            .unary_request::<GetSnapDataPlaneSessionGrantRequest, GetSnapDataPlaneSessionGrantResponse>(
91                &format!("{SERVICE_PATH}{GET_SNAP_DATA_PLANE_SESSION_GRANT}"),
92                GetSnapDataPlaneSessionGrantRequest::default(),
93            )
94            .await?
95            .try_into()
96            .map_err(
97                |e: SessionGrantError| {
98                    CrpcClientError::DecodeError {
99                        context: "decoding session grants".into(),
100                        source: e.into(),
101                        body: None,
102                    }
103                },
104            )
105    }
106    async fn renew_data_plane_session(
107        &self,
108        address: SocketAddr,
109    ) -> Result<SessionGrant, CrpcClientError> {
110        self.client
111            .unary_request::<RenewSnapDataPlaneSessionGrantRequest, RenewSnapDataPlaneSessionGrantResponse>(
112                &format!("{SERVICE_PATH}{RENEW_SNAP_DATA_PLANE_SESSION_GRANT}"),
113                RenewSnapDataPlaneSessionGrantRequest{
114                    address: address.to_string(),
115                },
116            )
117            .await?
118            .try_into()
119            .map_err(
120                |e: SessionGrantError| {
121                    CrpcClientError::DecodeError {
122                        context: "decoding renewed session grant".into(),
123                        source: e.into(),
124                        body: None,
125                    }
126                },
127            )
128    }
129}