Skip to main content

sim/runtime/reference_device/
consent.rs

1use sim_kernel::{Cx, Expr, Result};
2use sim_lib_view_device::{
3    ConsentReceipt, DeviceCapability, DeviceSampleStore, EdgeId, FrameClock, RateClass,
4    RetentionReaper, StoreKey, StoredSample, require_with_consent,
5};
6use sim_value::build;
7
8/// Result of the hardware-free consent and retention proof.
9#[derive(Clone, Debug, PartialEq, Eq)]
10pub struct ConsentProof {
11    /// Whether visible consent without a kernel grant fails closed.
12    pub denied_without_kernel_grant: bool,
13    /// Whether missing visible consent fails closed.
14    pub denied_without_visible_grant: bool,
15    /// Whether the sample is absent after the retention sweep.
16    pub sample_evicted: bool,
17    /// Whether content referenced only by the sample is absent after the sweep.
18    pub content_evicted: bool,
19}
20
21impl ConsentProof {
22    /// Encodes the proof as expression data for cookbook recipes.
23    pub fn to_expr(&self) -> Expr {
24        build::map(vec![
25            ("kind", build::qsym("device/reference", "consent-proof")),
26            (
27                "denied-without-kernel-grant",
28                Expr::Bool(self.denied_without_kernel_grant),
29            ),
30            (
31                "denied-without-visible-grant",
32                Expr::Bool(self.denied_without_visible_grant),
33            ),
34            ("sample-evicted", Expr::Bool(self.sample_evicted)),
35            ("content-evicted", Expr::Bool(self.content_evicted)),
36        ])
37    }
38}
39
40/// Builds the stable reference device edge id.
41pub fn reference_edge_id() -> EdgeId {
42    EdgeId::named("reference-device")
43}
44
45/// Builds a visible pose-consent receipt for the reference edge.
46pub fn reference_pose_receipt(seq: u64, retain_ms: u64) -> ConsentReceipt {
47    ConsentReceipt::new(
48        vec![DeviceCapability::Pose.grant_symbol()],
49        retain_ms,
50        Vec::new(),
51        reference_edge_id(),
52        seq,
53    )
54}
55
56/// Requires a pose read against both kernel capability and visible consent.
57pub fn require_reference_pose(cx: &Cx, receipt: &ConsentReceipt) -> Result<()> {
58    require_with_consent(
59        cx,
60        DeviceCapability::Pose.as_str(),
61        receipt,
62        &reference_edge_id(),
63    )
64}
65
66/// Runs the consent and retention portions that do not need test-only grants.
67pub fn prove_consent_without_kernel_grant(cx: &Cx) -> ConsentProof {
68    let receipt = reference_pose_receipt(7, 5);
69    let empty_receipt = ConsentReceipt::new(Vec::new(), 5, Vec::new(), reference_edge_id(), 8);
70    let denied_without_kernel_grant = require_reference_pose(cx, &receipt).is_err();
71    let denied_without_visible_grant = require_reference_pose(cx, &empty_receipt).is_err();
72    let retention = prove_retention_reaper(&receipt);
73    ConsentProof {
74        denied_without_kernel_grant,
75        denied_without_visible_grant,
76        sample_evicted: retention.sample_evicted,
77        content_evicted: retention.content_evicted,
78    }
79}
80
81/// Result of the deterministic retention sweep.
82#[derive(Clone, Debug, PartialEq, Eq)]
83pub struct RetentionProof {
84    /// Whether the sample is absent after the sweep.
85    pub sample_evicted: bool,
86    /// Whether its referenced content is absent after the sweep.
87    pub content_evicted: bool,
88}
89
90/// Runs the retention reaper over a modeled sample store.
91pub fn prove_retention_reaper(receipt: &ConsentReceipt) -> RetentionProof {
92    let sample_key = StoreKey::named("pose-sample");
93    let content_key = StoreKey::named("pose-content");
94    let mut store = DeviceSampleStore::new();
95    store.insert_content(content_key.clone(), build::text("pose payload"));
96    store.insert_sample(StoredSample::new(
97        sample_key.clone(),
98        receipt.seq,
99        0,
100        vec![content_key.clone()],
101        build::map(vec![("pose", build::uint(1))]),
102    ));
103    let _evicted = RetentionReaper::new().sweep(
104        &mut store,
105        std::slice::from_ref(receipt),
106        FrameClock::new(
107            receipt.retain_ms.saturating_add(2),
108            RateClass::safe_default(),
109        ),
110    );
111    RetentionProof {
112        sample_evicted: !store.contains_sample(&sample_key),
113        content_evicted: !store.contains_content(&content_key),
114    }
115}