Skip to main content

clawft_kernel/licence/
floor_preview.rs

1//! What an Admin `reset_floor` would change, shown before it is confirmed.
2//!
3//! A reset forgets the clock high-water mark of the bound grant key and
4//! restarts it from now. A grant that the floor currently holds expired can
5//! then be valid again; the operator should see which before agreeing.
6
7use serde::Serialize;
8
9use super::store::{CheckoutGrantStore, grant_valid};
10use super::LicenceError;
11
12/// A grant a reset would make valid again.
13#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
14pub struct RevivedGrant {
15    /// Cog id.
16    pub cog_id: String,
17    /// Version.
18    pub version: String,
19    /// When the grant expires, unix seconds.
20    pub expires_at: u64,
21}
22
23/// The floor now, after a reset, and the grants that would come back.
24#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
25pub struct FloorPreview {
26    /// The local clock, unix seconds.
27    pub now: u64,
28    /// The floor now (`max(highest issued_at, high-water mark)`).
29    pub floor: u64,
30    /// The floor right after a reset.
31    pub floor_after: u64,
32    /// Grants expired by the floor now that a reset would make valid.
33    pub revived: Vec<RevivedGrant>,
34}
35
36impl CheckoutGrantStore {
37    /// Preview [`Self::reset_floor`] without changing anything. Needs a
38    /// binding in effect, like the reset.
39    pub fn floor_preview(&self) -> Result<FloorPreview, LicenceError> {
40        self.run(|inner, ev| {
41            let b = self.binding_in_effect(inner, ev)?;
42            Ok(preview_of(inner, &b.grant_pubkey, &b.mesh_id, (self.clock)()))
43        })
44    }
45
46    /// Reset the floor and return the preview it was applied on, in one
47    /// critical section. With `expected_floor`, refuses (`floor_changed`)
48    /// when the floor is no longer the value the operator was shown.
49    pub fn reset_floor_checked(&self, expected_floor: Option<u64>) -> Result<FloorPreview, LicenceError> {
50        self.run(|inner, ev| {
51            let b = self.binding_in_effect(inner, ev)?;
52            let now = (self.clock)();
53            let p = preview_of(inner, &b.grant_pubkey, &b.mesh_id, now);
54            if expected_floor.is_some_and(|f| f != p.floor) {
55                return Err(LicenceError::CheckFailed("floor_changed".into()));
56            }
57            let mut next = inner.clone();
58            next.floors.entry(b.grant_pubkey).or_default().reset(now);
59            ev.push(super::LicenceEvent::FloorReset(now));
60            self.commit(inner, next)?;
61            Ok(p)
62        })
63    }
64}
65
66fn preview_of(inner: &super::store::Inner, key: &str, mesh_id: &str, now: u64) -> FloorPreview {
67    let cur = inner.floors.get(key).copied().unwrap_or_default();
68    let mut after = cur;
69    after.reset(now);
70    let (eff_now, eff_after) = (cur.effective_now(now), after.effective_now(now));
71    let mut revived: Vec<RevivedGrant> = inner
72        .slots
73        .values()
74        .filter_map(|s| s.current.as_ref())
75        .filter(|h| {
76            h.body.mesh_id == mesh_id && !grant_valid(&h.body, eff_now) && grant_valid(&h.body, eff_after)
77        })
78        .map(|h| RevivedGrant {
79            cog_id: h.body.cog_id.clone(),
80            version: h.body.version.clone(),
81            expires_at: h.body.expires_at,
82        })
83        .collect();
84    revived.sort_by(|a, c| (&a.cog_id, &a.version).cmp(&(&c.cog_id, &c.version)));
85    FloorPreview { now, floor: cur.floor(), floor_after: after.floor(), revived }
86}