1use std::sync::Arc;
5use std::time::{Duration, Instant};
6
7use dashmap::DashMap;
8use serde::{Deserialize, Serialize};
9
10use super::{AdmissionPosture, ApprovalStore, CheckoutGrantStore, LicenceError, LicenceEventSink, SignedApproval, SignedGrant};
11use crate::mesh_delivery::PeerCtx;
12use super::links::LicenceLinks;
13use crate::workload_pkg::TrustAnchors;
14
15pub const RECORDS_PER_SEC: f64 = 2.0;
17pub const RECORD_BURST: f64 = 10.0;
19pub(super) const MAX_TRACKED_CONNS: usize = 1024;
21pub(super) const MAX_SEEN: usize = 4096;
23
24#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
26pub enum ExchangeError {
27 #[error(transparent)]
29 Licence(#[from] LicenceError),
30 #[error("licence records are arriving too fast")]
32 RateLimited,
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub enum Receipt {
38 New,
40 Known,
42 Deferred,
44}
45
46pub trait PeerAdmission: Send + Sync + 'static {
48 fn admitted(&self, ctx: &PeerCtx) -> bool;
50
51 fn peer_admitted(&self, links: &dyn LicenceLinks, peer: &str) -> bool;
54}
55
56#[derive(Debug, Default, Clone, Copy)]
60pub struct CtxAdmission;
61
62impl PeerAdmission for CtxAdmission {
63 fn admitted(&self, ctx: &PeerCtx) -> bool {
64 crate::mesh_artifact_tunnel::licensed_peer(ctx)
65 }
66
67 fn peer_admitted(&self, links: &dyn LicenceLinks, peer: &str) -> bool {
69 links.peer_licensed(peer)
70 }
71}
72
73pub type PostureFn = Arc<dyn Fn() -> AdmissionPosture + Send + Sync>;
75
76#[derive(Debug, Clone)]
78pub struct LicenceExchangeConfig {
79 pub sync_interval: Duration,
81 pub sync_min_gap: Duration,
83 pub sync_ban: Duration,
85 pub max_pages: u32,
87 pub sync_on_connect: bool,
89}
90
91impl Default for LicenceExchangeConfig {
92 fn default() -> Self {
93 Self {
94 sync_interval: Duration::from_secs(30 * 60),
95 sync_min_gap: Duration::from_secs(60),
96 sync_ban: Duration::from_secs(10 * 60),
97 max_pages: 16,
98 sync_on_connect: true,
99 }
100 }
101}
102
103pub struct LicenceExchangeParts {
105 pub store: Arc<CheckoutGrantStore>,
107 pub approvals: Arc<ApprovalStore>,
109 pub anchors: Arc<TrustAnchors>,
111 pub runtime: Arc<dyn LicenceLinks>,
114 pub posture: PostureFn,
116 pub admission: Arc<dyn PeerAdmission>,
118 pub sink: Arc<dyn LicenceEventSink>,
120 pub config: LicenceExchangeConfig,
122}
123
124#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
126#[serde(tag = "kind", content = "record", rename_all = "snake_case")]
127pub enum GrantMsg {
128 Grant(SignedGrant),
130 Approval(SignedApproval),
132}
133
134#[derive(Debug, Clone, Copy, PartialEq, Eq)]
136pub enum Spend {
137 Exempt,
139 Flood(u64),
141 Sync(u64),
143}
144
145#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
147pub(super) enum Budget {
148 Binding,
149 Grant,
150 Approval,
151 Sync,
152}
153
154pub(super) struct Buckets {
156 rate: f64,
157 burst: f64,
158 map: DashMap<(Budget, u64), (f64, Instant)>,
159}
160
161impl Buckets {
162 pub(super) fn new(rate: f64, burst: f64) -> Self {
163 Self { rate, burst, map: DashMap::new() }
164 }
165
166 pub(super) fn take(&self, kind: Budget, conn: u64, cost: f64) -> bool {
168 let key = (kind, conn);
169 if self.map.len() >= MAX_TRACKED_CONNS && !self.map.contains_key(&key) {
170 let oldest = self.map.iter().min_by_key(|b| b.1).map(|b| *b.key());
171 if let Some(k) = oldest {
172 self.map.remove(&k);
173 }
174 }
175 let now = Instant::now();
176 let mut b = self.map.entry(key).or_insert((self.burst, now));
177 let dt = now.duration_since(b.1).as_secs_f64();
178 b.1 = now;
179 b.0 = (b.0 + dt * self.rate).min(self.burst);
180 if b.0 < cost {
181 return false;
182 }
183 b.0 -= cost;
184 true
185 }
186}
187