snap_control/pg_wap/crpc/model.rs
1// Copyright 2026 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
15//! Models of the WAP control plane API, and the trait that serves them.
16//!
17//! There is one model per message of the `anapaya.wap.v1.WapControl` service. A model is the
18//! parsed and validated form of its message: domains are [`CustomerDomain`]s, segments are
19//! [`SignedPathSegment`]s and timestamps are [`SystemTime`]s, so a
20//! [`ControlServiceAPIHandler`] never sees an unvalidated string or an untyped number.
21
22use std::{collections::BTreeMap, net::IpAddr, time::SystemTime};
23
24use sciparse::segment::SignedPathSegment;
25
26use crate::pg_wap::sni::CustomerDomain;
27
28/// A request to authorize a client for a set of targets.
29///
30/// The model of `anapaya.wap.v1.AuthorizeTargetsRequest`.
31#[derive(Debug)]
32pub struct AuthorizeTargetsRequest {
33 /// The targets to authorize, with the private segments granted towards each of them.
34 ///
35 /// The key is the customer domain of the target, i.e. the part of the SNI that follows the
36 /// WAP ID and the namespace. A client cannot know the WAP ID it has to use before it has the
37 /// response to this request, and grants are held per customer domain, see
38 /// [`GrantManager::authorize`](crate::pg_wap::grants::GrantManager::authorize).
39 pub targets: BTreeMap<CustomerDomain, AuthSegments>,
40}
41
42/// The private segments granted towards one target.
43///
44/// The model of `anapaya.wap.v1.AuthSegments`. The segments a client grants here are the ones
45/// the WAP cannot look up itself, typically because they come from a Hidden Segment Directory.
46///
47/// The three kinds are kept apart because that is how a client obtains them. The grants they turn
48/// into only distinguish core from non-core segments, see
49/// [`GrantedSegmentId`](crate::pg_wap::grants::GrantedSegmentId).
50#[derive(Debug, Default)]
51pub struct AuthSegments {
52 /// The up segments granted for the target.
53 pub up_segments: Vec<SignedPathSegment>,
54 /// The down segments granted for the target.
55 pub down_segments: Vec<SignedPathSegment>,
56 /// The core segments granted for the target.
57 pub core_segments: Vec<SignedPathSegment>,
58}
59
60impl AuthSegments {
61 /// The number of segments granted here, over all three kinds.
62 pub fn count(&self) -> usize {
63 self.up_segments.len() + self.down_segments.len() + self.core_segments.len()
64 }
65}
66
67/// The grant handed out in response to an [`AuthorizeTargetsRequest`].
68///
69/// The model of `anapaya.wap.v1.AuthorizeTargetsResponse`.
70#[derive(Debug)]
71pub struct AuthorizeTargetsResponse {
72 /// The IP address the grant was handed out for, reflected back to the client.
73 pub client_ip: IpAddr,
74 /// The ID of the WAP that handled the request.
75 ///
76 /// The grant is only valid on this WAP, so the client has to address it in the SNI of the
77 /// data plane connections that follow, see [`WapSNI`](crate::pg_wap::sni::WapSNI).
78 pub wap_id: String,
79 /// The port the data plane of that WAP listens on.
80 pub data_plane_port: u16,
81 /// When the grant expires, i.e. by when the client has to have authorized again.
82 pub expiry_time: SystemTime,
83}
84
85/// The failures a [`ControlServiceAPIHandler`] reports.
86#[derive(Debug, thiserror::Error)]
87pub enum AuthorizeTargetsError {
88 /// The client has to send a different request.
89 #[error("{0}")]
90 InvalidRequest(String),
91 /// The client is over a grant limit.
92 #[error("{0}")]
93 LimitReached(String),
94 /// Internal error. The endpoint logs it and returns a fixed message.
95 #[error(transparent)]
96 Internal(#[from] anyhow::Error),
97}
98
99/// Serves the requests of the `anapaya.wap.v1.WapControl` service.
100///
101/// Implemented by the WAP control plane and served by
102/// [`nest_crpc_api`](super::api::nest_crpc_api).
103pub trait ControlServiceAPIHandler: Send + Sync {
104 /// Authorizes `client_ip` for the targets and segments of `request`, as of `now`.
105 ///
106 /// `client_ip` is the address the request arrived from, not one the request itself names, so a
107 /// client cannot ask for a grant on somebody else's address.
108 ///
109 /// Grants are additive and are never shortened, so a client refreshes its authorization by
110 /// calling this again before the returned expiry, see
111 /// [`GrantManager::authorize`](crate::pg_wap::grants::GrantManager::authorize).
112 ///
113 /// Returned error carries an HTTP status code and a human-readable message.
114 fn authorize_targets(
115 &self,
116 client_ip: IpAddr,
117 request: AuthorizeTargetsRequest,
118 now: SystemTime,
119 ) -> Result<AuthorizeTargetsResponse, AuthorizeTargetsError>;
120}