1use std::sync::{Arc, Mutex, Weak};
28use std::time::Instant;
29
30use dashmap::DashMap;
31use ed25519_dalek::SigningKey;
32use serde::Deserialize;
33
34use super::binding::require_operator;
35pub use super::exchange_types::{
36 CtxAdmission, ExchangeError, GrantMsg, LicenceExchangeConfig, LicenceExchangeParts,
37 PeerAdmission, PostureFn, Receipt, Spend,
38};
39use super::exchange_types::{Buckets, MAX_SEEN, RECORDS_PER_SEC, RECORD_BURST};
40pub(super) use super::exchange_types::Budget;
41use super::{
42 Approval, ApprovalStore, BindState, BindingRecord, CheckoutGrant,
43 CheckoutGrantStore, LicenceError, LicenceEventSink, NoExtraChecks, Outcome, SignedApproval,
44 SignedBinding, SignedEnvelope, SignedGrant, envelope_key, sign_binding,
45};
46use crate::ipc::{KernelMessage, MessagePayload, MessageTarget};
47use crate::mesh_delivery::PeerCtx;
48use super::links::LicenceLinks;
49use crate::mesh_runtime::{COG_BINDING_TOPIC, COG_GRANT_TOPIC, COG_SYNC_TOPIC, PeerControlSink};
50use crate::workload_pkg::TrustAnchors;
51
52type Seen = (std::collections::HashSet<[u8; 32]>, std::collections::VecDeque<[u8; 32]>);
53
54pub struct LicenceExchange {
56 pub(super) store: Arc<CheckoutGrantStore>,
57 pub(super) approvals: Arc<ApprovalStore>,
58 pub(super) anchors: Arc<TrustAnchors>,
59 pub(super) runtime: Arc<dyn LicenceLinks>,
60 pub(super) posture: PostureFn,
61 pub(super) admission: Arc<dyn PeerAdmission>,
62 pub(super) sink: Arc<dyn LicenceEventSink>,
63 pub(super) config: LicenceExchangeConfig,
64 pub(super) me: Weak<Self>,
65 flood_buckets: Buckets,
66 pub(super) sync_buckets: Buckets,
67 seen: Mutex<Seen>,
68 pub(super) pending: DashMap<String, super::exchange_sync::Pending>,
70 pub(super) served: DashMap<String, Instant>,
72 pub(super) banned: DashMap<String, Instant>,
74 pub(super) retries: DashMap<String, u8>,
76 pub(super) sessions: DashMap<String, super::exchange_sync::ServeSession>,
78 pub(super) flooded: std::sync::atomic::AtomicU64,
80}
81
82impl LicenceExchange {
83 pub fn start(p: LicenceExchangeParts) -> Arc<Self> {
87 let rt = p.runtime.clone();
88 let me = Arc::new_cyclic(|w| Self {
89 store: p.store,
90 approvals: p.approvals,
91 anchors: p.anchors,
92 runtime: p.runtime,
93 posture: p.posture,
94 admission: p.admission,
95 sink: p.sink,
96 config: p.config,
97 me: w.clone(),
98 flood_buckets: Buckets::new(RECORDS_PER_SEC, RECORD_BURST),
99 sync_buckets: Buckets::new(super::exchange_sync::SYNC_ENTRIES_PER_SEC, super::exchange_sync::SYNC_BURST),
100 seen: Default::default(),
101 pending: DashMap::new(),
102 served: DashMap::new(),
103 banned: DashMap::new(),
104 retries: DashMap::new(),
105 sessions: DashMap::new(),
106 flooded: Default::default(),
107 });
108 rt.set_control_sink(COG_BINDING_TOPIC, Arc::new(BindingSink(me.clone())));
109 rt.set_control_sink(COG_GRANT_TOPIC, Arc::new(GrantSink(me.clone())));
110 rt.set_control_sink(COG_SYNC_TOPIC, Arc::new(super::exchange_sync::SyncSink(me.clone())));
111 me.spawn_sync_tasks();
112 me
113 }
114
115 pub fn approvals(&self) -> &Arc<ApprovalStore> {
118 &self.approvals
119 }
120
121 pub(super) fn local(&self) -> Result<super::MeshId, LicenceError> {
124 self.store.local_mesh_id().get().ok_or(LicenceError::NoLocalMesh)
125 }
126
127 fn record_key(tag: u8, env: &SignedEnvelope) -> [u8; 32] {
128 let mut h = blake3::Hasher::new();
129 h.update(&[tag]);
130 h.update(env.payload.as_bytes());
131 h.update(env.public_key.as_bytes());
132 h.update(env.signature.as_bytes());
133 *h.finalize().as_bytes()
134 }
135
136 pub(super) fn already_seen(&self, key: &[u8; 32]) -> bool {
137 self.seen.lock().unwrap_or_else(|p| p.into_inner()).0.contains(key)
138 }
139
140 #[cfg(test)]
142 pub(super) fn seen_len(&self) -> usize {
143 self.seen.lock().unwrap_or_else(|p| p.into_inner()).0.len()
144 }
145
146 fn remember_conflict(&self, key: [u8; 32], e: &LicenceError) {
149 if matches!(e, LicenceError::Conflict(_)) {
150 self.remember(key);
151 }
152 }
153
154 pub(super) fn remember(&self, key: [u8; 32]) {
155 let mut g = self.seen.lock().unwrap_or_else(|p| p.into_inner());
156 if g.0.insert(key) {
157 g.1.push_back(key);
158 if g.1.len() > MAX_SEEN
159 && let Some(old) = g.1.pop_front()
160 {
161 g.0.remove(&old);
162 }
163 }
164 }
165
166 fn budget(&self, kind: Budget, spend: Spend) -> Result<(), ExchangeError> {
168 let ok = match spend {
169 Spend::Exempt => true,
170 Spend::Flood(c) => self.flood_buckets.take(kind, c, 1.0),
171 Spend::Sync(c) => self.sync_buckets.take(Budget::Sync, c, 1.0),
172 };
173 if ok { Ok(()) } else { Err(ExchangeError::RateLimited) }
174 }
175
176 fn receipt(o: Outcome) -> Receipt {
177 match o {
178 Outcome::Applied | Outcome::AppliedUnsaved => Receipt::New,
179 Outcome::Duplicate | Outcome::Ignored => Receipt::Known,
180 }
181 }
182
183 pub fn accept_binding(
186 &self,
187 env: &SignedBinding,
188 spend: Spend,
189 ) -> Result<Receipt, ExchangeError> {
190 let local = self.local()?;
191 let pk = envelope_key(env)?;
192 require_operator(&self.anchors, &pk)?;
193 let rec: BindingRecord = serde_json::from_str(&env.payload)
194 .map_err(|e| LicenceError::Malformed(e.to_string()))?;
195 if rec.mesh_id != local.to_hex() {
196 return Err(LicenceError::WrongMesh.into());
197 }
198 if let Some((seq, held)) = self.store.held_signed_binding()
199 && (rec.seq < seq || (rec.seq == seq && held.payload == env.payload))
200 {
201 return Ok(Receipt::Known);
202 }
203 let key = Self::record_key(1, env);
204 if self.already_seen(&key) {
205 return Ok(Receipt::Known);
206 }
207 self.budget(Budget::Binding, spend)?;
208 let out = self
209 .store
210 .accept_binding(env, (self.posture)(), &NoExtraChecks)
211 .inspect_err(|e| self.remember_conflict(key, e))?;
212 self.remember(key);
213 Ok(Self::receipt(out))
214 }
215
216 pub fn accept_grant(
218 &self,
219 env: &SignedGrant,
220 spend: Spend,
221 ) -> Result<Receipt, ExchangeError> {
222 self.local()?;
223 let pk = envelope_key(env)?;
224 let bound = self.store.bound_grant_key().ok_or(LicenceError::NoBinding)?;
225 if super::hex_encode(&pk) != bound {
226 return Err(LicenceError::UntrustedKey.into());
227 }
228 let g: CheckoutGrant = serde_json::from_str(&env.payload)
229 .map_err(|e| LicenceError::Malformed(e.to_string()))?;
230 if let Some((seq, held)) = self.store.held_grant(&g.cog_id, &g.version)
231 && (g.seq < seq || (g.seq == seq && held.payload == env.payload))
232 {
233 return Ok(Receipt::Known);
234 }
235 let key = Self::record_key(2, env);
236 if self.already_seen(&key) {
237 return Ok(Receipt::Known);
238 }
239 self.budget(Budget::Grant, spend)?;
240 match self.store.accept_grant(env) {
241 Ok(o) => {
242 self.remember(key);
243 Ok(Self::receipt(o))
244 }
245 Err(LicenceError::NotYetValid) => Ok(Receipt::Deferred),
246 Err(e) => {
247 self.remember_conflict(key, &e);
248 Err(e.into())
249 }
250 }
251 }
252
253 pub fn accept_approval(
255 &self,
256 env: &SignedApproval,
257 spend: Spend,
258 ) -> Result<Receipt, ExchangeError> {
259 let local = self.local()?;
260 let pk = envelope_key(env)?;
261 require_operator(&self.anchors, &pk)?;
262 let a: Approval = serde_json::from_str(&env.payload)
263 .map_err(|e| LicenceError::Malformed(e.to_string()))?;
264 if a.mesh_id != local.to_hex() {
265 return Err(LicenceError::WrongMesh.into());
266 }
267 if self.approvals.holds(&a.content_key()) {
268 return Ok(Receipt::Known);
269 }
270 let key = Self::record_key(3, env);
271 if self.already_seen(&key) {
272 return Ok(Receipt::Known);
273 }
274 self.budget(Budget::Approval, spend)?;
275 let out = self.approvals.accept(env)?;
276 self.remember(key);
277 Ok(Self::receipt(out))
278 }
279
280 pub async fn issue_binding(&self, env: SignedBinding) -> Result<Receipt, ExchangeError> {
284 let r = self.accept_binding(&env, Spend::Exempt)?;
285 self.flood_binding(&env, None).await;
286 Ok(r)
287 }
288
289 pub async fn issue_grant(&self, env: SignedGrant) -> Result<Receipt, ExchangeError> {
291 let r = self.accept_grant(&env, Spend::Exempt)?;
292 self.flood_grant(&GrantMsg::Grant(env), None).await;
293 Ok(r)
294 }
295
296 pub async fn issue_approval(&self, env: SignedApproval) -> Result<Receipt, ExchangeError> {
298 let r = self.accept_approval(&env, Spend::Exempt)?;
299 self.flood_grant(&GrantMsg::Approval(env), None).await;
300 Ok(r)
301 }
302
303 pub(super) async fn flood_binding(&self, env: &SignedBinding, except: Option<&str>) {
304 if let Ok(v) = serde_json::to_value(env) {
305 self.send_all(COG_BINDING_TOPIC, v, except).await;
306 }
307 }
308
309 pub(super) async fn flood_grant(&self, msg: &GrantMsg, except: Option<&str>) {
310 if let Ok(v) = serde_json::to_value(msg) {
311 self.send_all(COG_GRANT_TOPIC, v, except).await;
312 }
313 }
314
315 async fn send_all(&self, topic: &str, value: serde_json::Value, except: Option<&str>) {
316 for peer in self.runtime.peer_ids() {
317 if Some(peer.as_str()) == except || !self.admission.peer_admitted(self.runtime.as_ref(), &peer) {
318 continue;
319 }
320 self.flooded.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
321 self.send_to(&peer, topic, value.clone()).await;
322 }
323 }
324
325 pub(super) async fn send_to(&self, peer: &str, topic: &str, value: serde_json::Value) {
326 let msg = KernelMessage::new(
327 0,
328 MessageTarget::Topic(topic.to_string()),
329 MessagePayload::Json(value),
330 );
331 if let Err(e) = self.runtime.route_to_remote(peer, msg).await {
332 tracing::debug!(peer, topic, error = %e, "licence send failed");
333 }
334 }
335
336 pub(super) fn forward(&self, from: &str, what: Forward) {
339 let Some(me) = self.me.upgrade() else { return };
340 let from = from.to_owned();
341 tokio::spawn(async move {
342 match what {
343 Forward::Binding(b) => me.flood_binding(&b, Some(&from)).await,
344 Forward::Grant(m) => me.flood_grant(&m, Some(&from)).await,
345 }
346 });
347 }
348}
349
350pub(super) enum Forward {
352 Binding(SignedBinding),
353 Grant(GrantMsg),
354}
355
356pub fn sign_unbind(prev: &BindingRecord, operator: &SigningKey) -> Result<SignedBinding, LicenceError> {
363 let rec = BindingRecord {
364 state: BindState::Unbound,
365 seq: prev.seq.saturating_add(1),
366 ..prev.clone()
367 };
368 sign_binding(&rec, operator)
369}
370
371#[async_trait::async_trait]
375impl super::GrantFlood for LicenceExchange {
376 async fn flood(&self, grant: &SignedGrant) {
377 self.flood_grant(&GrantMsg::Grant(grant.clone()), None).await;
378 }
379}
380
381struct BindingSink(Arc<LicenceExchange>);
382struct GrantSink(Arc<LicenceExchange>);
383
384fn refused(kind: &str, ctx: &PeerCtx, e: &ExchangeError) {
385 if matches!(e, ExchangeError::RateLimited) {
386 tracing::debug!(peer = %ctx.peer_id, "licence {kind} rate limited");
387 return;
388 }
389 tracing::warn!(peer = %ctx.peer_id, verified = ctx.node_verified, error = %e,
390 "licence {kind} refused");
391}
392
393impl PeerControlSink for BindingSink {
394 fn on_peer_control(&self, ctx: &PeerCtx, conn: u64, payload: &serde_json::Value) -> Vec<serde_json::Value> {
395 let Ok(env) = SignedBinding::deserialize(payload) else {
396 tracing::warn!(peer = %ctx.peer_id, "malformed licence binding message");
397 return Vec::new();
398 };
399 match self.0.accept_binding(&env, Spend::Flood(conn)) {
400 Ok(Receipt::New) => self.0.forward(&ctx.peer_id, Forward::Binding(env)),
401 Ok(_) => {}
402 Err(ExchangeError::Licence(LicenceError::NoLocalMesh)) => {}
403 Err(e) => refused("binding", ctx, &e),
404 }
405 Vec::new()
406 }
407}
408
409impl PeerControlSink for GrantSink {
410 fn on_peer_control(&self, ctx: &PeerCtx, conn: u64, payload: &serde_json::Value) -> Vec<serde_json::Value> {
411 let Ok(msg) = GrantMsg::deserialize(payload) else {
412 tracing::warn!(peer = %ctx.peer_id, "malformed licence grant message");
413 return Vec::new();
414 };
415 let r = match &msg {
416 GrantMsg::Grant(g) => self.0.accept_grant(g, Spend::Flood(conn)),
417 GrantMsg::Approval(a) => self.0.accept_approval(a, Spend::Flood(conn)),
418 };
419 match r {
420 Ok(Receipt::New) => self.0.forward(&ctx.peer_id, Forward::Grant(msg)),
421 Ok(_) => {}
422 Err(ExchangeError::Licence(LicenceError::NoLocalMesh)) => {}
423 Err(e) => refused("grant", ctx, &e),
424 }
425 Vec::new()
426 }
427}