1use sim_kernel::{CapabilityName, Cx, Error, Expr, Result, Symbol};
4use sim_lib_view_device::{ConsentReceipt, EdgeId, require_with_consent};
5use sim_value::{access, build};
6
7use crate::{BoundedContentStore, ContentFrame, RetentionWindow, StoreEvicted, StoreKey};
8
9pub const CAP_GLASSES_POSE: &str = "glasses/pose";
11
12pub const CAP_GLASSES_CAMERA: &str = "glasses/camera";
14
15pub const CAP_GLASSES_WORLD_ANCHOR: &str = "glasses/world-anchor";
17
18pub const CAP_GLASSES_HAND: &str = "glasses/hand";
20
21pub const CAP_GLASSES_MIC: &str = "glasses/mic";
23
24pub const CAP_GLASSES_VENDOR_REPORT: &str = "glasses/vendor-report";
26
27const XR_NAMESPACE: &str = "xr";
28const DEVICE_SAMPLE_NAMESPACE: &str = "stream/device-sample";
29const GLASSES_NAMESPACE: &str = "glasses";
30
31#[derive(Clone, Copy, Debug, PartialEq, Eq)]
33pub enum GlassesCapability {
34 Pose,
36 Camera,
38 WorldAnchor,
40 Hand,
42 Mic,
44 VendorReport,
46}
47
48impl GlassesCapability {
49 pub const ALL: [Self; 6] = [
51 Self::Pose,
52 Self::Camera,
53 Self::WorldAnchor,
54 Self::Hand,
55 Self::Mic,
56 Self::VendorReport,
57 ];
58
59 pub fn as_str(self) -> &'static str {
61 match self {
62 Self::Pose => CAP_GLASSES_POSE,
63 Self::Camera => CAP_GLASSES_CAMERA,
64 Self::WorldAnchor => CAP_GLASSES_WORLD_ANCHOR,
65 Self::Hand => CAP_GLASSES_HAND,
66 Self::Mic => CAP_GLASSES_MIC,
67 Self::VendorReport => CAP_GLASSES_VENDOR_REPORT,
68 }
69 }
70
71 pub fn local_name(self) -> &'static str {
73 match self {
74 Self::Pose => "pose",
75 Self::Camera => "camera",
76 Self::WorldAnchor => "world-anchor",
77 Self::Hand => "hand",
78 Self::Mic => "mic",
79 Self::VendorReport => "vendor-report",
80 }
81 }
82
83 pub fn capability_name(self) -> CapabilityName {
85 CapabilityName::new(self.as_str())
86 }
87
88 pub fn grant_symbol(self) -> Symbol {
90 Symbol::qualified(GLASSES_NAMESPACE, self.local_name())
91 }
92
93 pub fn from_name(name: &str) -> Option<Self> {
95 Self::ALL
96 .into_iter()
97 .find(|capability| capability.as_str() == name)
98 }
99}
100
101pub fn glasses_pose_grant() -> Symbol {
103 GlassesCapability::Pose.grant_symbol()
104}
105
106pub fn glasses_camera_grant() -> Symbol {
108 GlassesCapability::Camera.grant_symbol()
109}
110
111pub fn glasses_world_anchor_grant() -> Symbol {
113 GlassesCapability::WorldAnchor.grant_symbol()
114}
115
116pub fn glasses_hand_grant() -> Symbol {
118 GlassesCapability::Hand.grant_symbol()
119}
120
121pub fn glasses_mic_grant() -> Symbol {
123 GlassesCapability::Mic.grant_symbol()
124}
125
126pub fn glasses_vendor_report_grant() -> Symbol {
128 GlassesCapability::VendorReport.grant_symbol()
129}
130
131pub fn glasses_capability_for_sample(sample: &Expr) -> Result<GlassesCapability> {
133 if access::field(sample, "world-anchor").is_some() {
134 return Ok(GlassesCapability::WorldAnchor);
135 }
136 let symbol = access::field_sym(sample, "sample")
137 .or_else(|| access::field_sym(sample, "kind"))
138 .ok_or_else(|| Error::Eval("missing glasses sample or kind field".to_owned()))?;
139 match (symbol.namespace.as_deref(), symbol.name.as_ref()) {
140 (Some(XR_NAMESPACE), "pose") => Ok(GlassesCapability::Pose),
141 (Some(XR_NAMESPACE), "camera-frame") => Ok(GlassesCapability::Camera),
142 (Some(XR_NAMESPACE), "hand") => Ok(GlassesCapability::Hand),
143 (Some(XR_NAMESPACE), "mic-chunk") => Ok(GlassesCapability::Mic),
144 (Some(DEVICE_SAMPLE_NAMESPACE), "xr/pose") => Ok(GlassesCapability::Pose),
145 (Some(DEVICE_SAMPLE_NAMESPACE), "xr/camera-frame") => Ok(GlassesCapability::Camera),
146 (Some(DEVICE_SAMPLE_NAMESPACE), "xr/hand") => Ok(GlassesCapability::Hand),
147 (Some(DEVICE_SAMPLE_NAMESPACE), "xr/mic-chunk") => Ok(GlassesCapability::Mic),
148 (Some(GLASSES_NAMESPACE), "world-anchor") => Ok(GlassesCapability::WorldAnchor),
149 (Some(GLASSES_NAMESPACE), "vendor-report") => Ok(GlassesCapability::VendorReport),
150 _ => Err(Error::HostError(format!(
151 "no glasses consent capability for {}",
152 symbol.as_qualified_str()
153 ))),
154 }
155}
156
157pub fn require_glasses_consent(
159 cx: &Cx,
160 capability: GlassesCapability,
161 receipt: &ConsentReceipt,
162 session: &EdgeId,
163) -> Result<()> {
164 require_with_consent(cx, capability.as_str(), receipt, session)
165}
166
167pub fn require_glasses_sample_ingest(
169 cx: &Cx,
170 sample: &Expr,
171 receipt: &ConsentReceipt,
172 session: &EdgeId,
173) -> Result<GlassesCapability> {
174 let capability = glasses_capability_for_sample(sample)?;
175 require_glasses_consent(cx, capability, receipt, session)?;
176 Ok(capability)
177}
178
179pub fn store_glasses_frame(
181 store: &mut BoundedContentStore,
182 capability: GlassesCapability,
183 ref_id: Symbol,
184 value: Expr,
185 receipt: &ConsentReceipt,
186 inserted_tick: u64,
187 size_bytes: usize,
188) -> Result<(StoreKey, Vec<StoreEvicted>)> {
189 let key = StoreKey::new(ref_id);
190 let evicted = store.insert(ContentFrame::new(
191 key.clone(),
192 receipt.session.as_symbol().clone(),
193 receipt.seq,
194 inserted_tick,
195 size_bytes,
196 stored_glasses_value(capability, value),
197 ))?;
198 Ok((key, evicted))
199}
200
201pub fn glasses_retention_windows(receipts: &[ConsentReceipt]) -> Vec<RetentionWindow> {
203 receipts
204 .iter()
205 .filter(|receipt| {
206 receipt
207 .grants
208 .iter()
209 .any(|grant| grant.namespace.as_deref() == Some(GLASSES_NAMESPACE))
210 })
211 .map(|receipt| {
212 RetentionWindow::new(
213 receipt.session.as_symbol().clone(),
214 receipt.seq,
215 receipt.retain_ms,
216 )
217 })
218 .collect()
219}
220
221pub fn sweep_glasses_retention(
223 store: &mut BoundedContentStore,
224 receipts: &[ConsentReceipt],
225 now_tick: u64,
226 adapt_hz: u16,
227) -> Vec<StoreEvicted> {
228 store.sweep_retention(now_tick, adapt_hz, &glasses_retention_windows(receipts))
229}
230
231fn stored_glasses_value(capability: GlassesCapability, value: Expr) -> Expr {
232 build::map(vec![
233 ("kind", build::qsym("glasses", "content-frame")),
234 ("capability", Expr::Symbol(capability.grant_symbol())),
235 (capability.local_name(), value),
236 ])
237}