1use crate::access_policy::{
14 AccessPolicy, AccessRule, Denial, MmioResource, ResourceCeiling, ResourceId,
15};
16use crate::config::{AccessLimitEnforcer, BASELINE_MAX_DEADLINE_NS};
17use std::collections::BTreeMap;
18
19#[derive(Clone, Debug, Default, PartialEq, Eq)]
22pub struct RunContext {
23 pub run_id: String,
25 pub case_id: String,
27 pub plan_digest: String,
29 pub host_tool_version: String,
31}
32
33#[derive(Clone, Debug, PartialEq, Eq)]
36pub struct ProxyIdentity {
37 pub boot_id: String,
39 pub proxy_generation: u64,
41 pub resource_digest: String,
43 pub policy_digest: String,
45}
46
47pub use crate::access_policy::SessionMode;
48
49#[derive(Clone, Debug, PartialEq, Eq)]
51pub enum OpenError {
52 NotAccepting,
54 StaleBootId,
56 StaleProxyGeneration,
58 StaleResourceDigest,
60 StalePolicyDigest,
62 RejectedAllowlist {
64 rule: AccessRule,
66 denial: Denial,
68 },
69 MutationLeaseHeld,
71 MutationNotPermitted,
73 UnsupportedExpectation,
76}
77
78#[derive(Debug)]
80pub struct Session {
81 pub id: u64,
83 pub mode: SessionMode,
85 pub context: RunContext,
87 pub policy: AccessPolicy,
89 pub limit_enforcer: AccessLimitEnforcer,
91}
92
93use std::sync::Arc;
94use std::sync::atomic::{AtomicBool, Ordering};
95
96pub type QuiesceHook = Arc<dyn Fn(bool) + Send + Sync>;
99
100pub struct SessionManager {
102 identity: ProxyIdentity,
103 resources: BTreeMap<ResourceId, MmioResource>,
104 ceiling: BTreeMap<ResourceId, ResourceCeiling>,
105 allow_mutating_sessions: bool,
106 next_id: u64,
107 sessions: BTreeMap<u64, Session>,
108 mutating: Option<u64>,
109 accepting: bool,
110 limit_enforcer: AccessLimitEnforcer,
111 quiesced: Arc<AtomicBool>,
112 quiesce_hook: Option<QuiesceHook>,
113}
114
115impl std::fmt::Debug for SessionManager {
116 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
117 f.debug_struct("SessionManager")
118 .field("identity", &self.identity)
119 .field("resources", &self.resources)
120 .field("ceiling", &self.ceiling)
121 .field("allow_mutating_sessions", &self.allow_mutating_sessions)
122 .field("next_id", &self.next_id)
123 .field("sessions", &self.sessions)
124 .field("mutating", &self.mutating)
125 .field("accepting", &self.accepting)
126 .field("quiesced", &self.quiesced.load(Ordering::SeqCst))
127 .field("has_quiesce_hook", &self.quiesce_hook.is_some())
128 .finish()
129 }
130}
131
132impl SessionManager {
133 pub fn new(
135 identity: ProxyIdentity,
136 resources: BTreeMap<ResourceId, MmioResource>,
137 ceiling: BTreeMap<ResourceId, ResourceCeiling>,
138 allow_mutating_sessions: bool,
139 ) -> Self {
140 Self::with_limits(
141 identity,
142 resources,
143 ceiling,
144 allow_mutating_sessions,
145 AccessLimitEnforcer::new(0, BASELINE_MAX_DEADLINE_NS),
146 )
147 }
148
149 pub fn with_limits(
151 identity: ProxyIdentity,
152 resources: BTreeMap<ResourceId, MmioResource>,
153 ceiling: BTreeMap<ResourceId, ResourceCeiling>,
154 allow_mutating_sessions: bool,
155 limit_enforcer: AccessLimitEnforcer,
156 ) -> Self {
157 Self {
158 identity,
159 resources,
160 ceiling,
161 allow_mutating_sessions,
162 next_id: 1,
163 sessions: BTreeMap::new(),
164 mutating: None,
165 accepting: true,
166 limit_enforcer,
167 quiesced: Arc::new(AtomicBool::new(false)),
168 quiesce_hook: None,
169 }
170 }
171
172 pub fn set_quiesce_hook(&mut self, hook: QuiesceHook) {
175 self.quiesce_hook = Some(hook);
176 }
177
178 pub fn is_quiesced(&self) -> bool {
181 self.quiesced.load(Ordering::SeqCst)
182 }
183
184 pub fn quiesce_flag(&self) -> Arc<AtomicBool> {
187 self.quiesced.clone()
188 }
189
190 pub fn identity(&self) -> &ProxyIdentity {
193 &self.identity
194 }
195
196 pub fn resources(&self) -> &BTreeMap<ResourceId, MmioResource> {
198 &self.resources
199 }
200
201 pub fn open_session(
204 &mut self,
205 context: RunContext,
206 mode: SessionMode,
207 expectations: &ProxyIdentity,
208 allowlist: impl IntoIterator<Item = AccessRule>,
209 ) -> Result<u64, OpenError> {
210 if !self.accepting {
211 return Err(OpenError::NotAccepting);
212 }
213 if expectations.boot_id != self.identity.boot_id {
214 return Err(OpenError::StaleBootId);
215 }
216 if expectations.proxy_generation != self.identity.proxy_generation {
217 return Err(OpenError::StaleProxyGeneration);
218 }
219 if expectations.resource_digest != self.identity.resource_digest {
220 return Err(OpenError::StaleResourceDigest);
221 }
222 if expectations.policy_digest != self.identity.policy_digest {
223 return Err(OpenError::StalePolicyDigest);
224 }
225 if mode == SessionMode::Mutating {
226 if !self.allow_mutating_sessions {
227 return Err(OpenError::MutationNotPermitted);
228 }
229 if self.mutating.is_some() {
230 return Err(OpenError::MutationLeaseHeld);
231 }
232 }
233 let policy =
234 AccessPolicy::new(mode, self.resources.clone(), self.ceiling.clone(), allowlist)
235 .map_err(|(rule, denial)| OpenError::RejectedAllowlist { rule, denial })?;
236 let id = self.next_id;
237 self.next_id += 1;
238 if mode == SessionMode::Mutating {
239 self.mutating = Some(id);
240 self.quiesced.store(true, Ordering::SeqCst);
241 if let Some(hook) = &self.quiesce_hook {
242 hook(true);
243 }
244 }
245 self.sessions.insert(
246 id,
247 Session { id, mode, context, policy, limit_enforcer: self.limit_enforcer.clone() },
248 );
249 Ok(id)
250 }
251
252 pub fn check_access(
254 &mut self,
255 session_id: u64,
256 class: crate::access_policy::AccessClass,
257 now_ns: i64,
258 ) -> Result<(), Denial> {
259 let session = self.sessions.get_mut(&session_id).ok_or(Denial::UnknownResource)?;
260 session.limit_enforcer.check_access(class, now_ns)
261 }
262
263 pub fn check_deadline(&self, session_id: u64, requested_timeout_ns: i64) -> Result<(), Denial> {
265 let session = self.sessions.get(&session_id).ok_or(Denial::UnknownResource)?;
266 session.limit_enforcer.check_deadline(requested_timeout_ns)
267 }
268
269 pub fn session(&self, id: u64) -> Option<&Session> {
271 self.sessions.get(&id)
272 }
273
274 pub fn close_session(&mut self, id: u64) -> bool {
277 let existed = self.sessions.remove(&id).is_some();
278 if existed && self.mutating == Some(id) {
279 self.mutating = None;
280 self.quiesced.store(false, Ordering::SeqCst);
281 if let Some(hook) = &self.quiesce_hook {
282 hook(false);
283 }
284 }
285 existed
286 }
287
288 pub fn reject_new_sessions(&mut self) {
291 self.accepting = false;
292 if self.mutating.take().is_some() {
293 self.quiesced.store(false, Ordering::SeqCst);
294 if let Some(hook) = &self.quiesce_hook {
295 hook(false);
296 }
297 }
298 }
299
300 pub fn is_accepting(&self) -> bool {
302 self.accepting
303 }
304
305 pub fn open_count(&self) -> usize {
307 self.sessions.len()
308 }
309}
310
311#[cfg(test)]
312mod tests {
313 use super::*;
314 use crate::access_policy::{AccessClass, WIDTH32};
315
316 const CTRL: ResourceId = 1;
317
318 fn identity() -> ProxyIdentity {
319 ProxyIdentity {
320 boot_id: "boot-1".to_string(),
321 proxy_generation: 7,
322 resource_digest: "sha256:aa".to_string(),
323 policy_digest: "sha256:bb".to_string(),
324 }
325 }
326
327 fn manager() -> SessionManager {
328 let resources = BTreeMap::from([(
329 CTRL,
330 MmioResource {
331 name: "ctrl".to_string(),
332 kind: crate::access_policy::ResourceKind::Mmio,
333 logical_size: 0x100,
334 mapped_size: 0x100,
335 },
336 )]);
337 let ceiling = BTreeMap::from([(
338 CTRL,
339 ResourceCeiling {
340 hard_denied: vec![],
341 allow_unknown_reads: true,
342 allow_poll: false,
343 writable_registers: vec![],
344 protocol: None,
345 allow_interrupt: false,
346 },
347 )]);
348 SessionManager::new(identity(), resources, ceiling, true)
349 }
350
351 fn open(manager: &mut SessionManager, mode: SessionMode) -> Result<u64, OpenError> {
352 manager.open_session(RunContext::default(), mode, &identity(), [])
353 }
354
355 #[test]
356 fn open_and_close() {
357 let mut manager = manager();
358 let id = open(&mut manager, SessionMode::ReadOnly).unwrap();
359 assert_eq!(manager.open_count(), 1);
360 assert!(manager.session(id).is_some());
361 assert!(manager.close_session(id));
362 assert_eq!(manager.open_count(), 0);
363 assert!(!manager.close_session(id));
364 }
365
366 #[test]
367 fn session_ids_are_unique_and_increasing() {
368 let mut manager = manager();
369 let first = open(&mut manager, SessionMode::ReadOnly).unwrap();
370 let second = open(&mut manager, SessionMode::ReadOnly).unwrap();
371 assert!(second > first);
372 }
373
374 #[test]
375 fn each_stale_expectation_is_distinct() {
376 let mut manager = manager();
377
378 let mut stale = identity();
379 stale.boot_id = "boot-2".to_string();
380 assert_eq!(
381 manager.open_session(RunContext::default(), SessionMode::ReadOnly, &stale, []),
382 Err(OpenError::StaleBootId)
383 );
384
385 let mut stale = identity();
386 stale.proxy_generation = 8;
387 assert_eq!(
388 manager.open_session(RunContext::default(), SessionMode::ReadOnly, &stale, []),
389 Err(OpenError::StaleProxyGeneration)
390 );
391
392 let mut stale = identity();
393 stale.resource_digest = "sha256:cc".to_string();
394 assert_eq!(
395 manager.open_session(RunContext::default(), SessionMode::ReadOnly, &stale, []),
396 Err(OpenError::StaleResourceDigest)
397 );
398
399 let mut stale = identity();
400 stale.policy_digest = "sha256:dd".to_string();
401 assert_eq!(
402 manager.open_session(RunContext::default(), SessionMode::ReadOnly, &stale, []),
403 Err(OpenError::StalePolicyDigest)
404 );
405
406 assert_eq!(manager.open_count(), 0);
407 }
408
409 #[test]
410 fn rejected_allowlist_creates_no_session() {
411 let mut manager = manager();
412 let rule =
413 AccessRule { resource: 99, offset: 0, width: WIDTH32, class: AccessClass::ReadOnce };
414 let result =
415 manager.open_session(RunContext::default(), SessionMode::ReadOnly, &identity(), [rule]);
416 assert_eq!(
417 result,
418 Err(OpenError::RejectedAllowlist { rule, denial: Denial::UnknownResource })
419 );
420 assert_eq!(manager.open_count(), 0);
421 }
422
423 #[test]
424 fn mutation_lease_is_exclusive() {
425 let mut manager = manager();
426 let holder = open(&mut manager, SessionMode::Mutating).unwrap();
427 assert_eq!(open(&mut manager, SessionMode::Mutating), Err(OpenError::MutationLeaseHeld));
428 assert!(open(&mut manager, SessionMode::ReadOnly).is_ok());
430 assert!(manager.close_session(holder));
432 assert!(open(&mut manager, SessionMode::Mutating).is_ok());
433 }
434
435 #[test]
436 fn reject_new_sessions_fails_closed() {
437 let mut manager = manager();
438 manager.reject_new_sessions();
439 assert_eq!(open(&mut manager, SessionMode::ReadOnly), Err(OpenError::NotAccepting));
440 }
441
442 #[test]
443 fn mutating_session_rejected_when_not_permitted() {
444 let resources = BTreeMap::from([(
445 CTRL,
446 MmioResource {
447 name: "ctrl".to_string(),
448 kind: crate::access_policy::ResourceKind::Mmio,
449 logical_size: 0x100,
450 mapped_size: 0x100,
451 },
452 )]);
453 let ceiling = BTreeMap::from([(
454 CTRL,
455 ResourceCeiling {
456 hard_denied: vec![],
457 allow_unknown_reads: true,
458 allow_poll: false,
459 writable_registers: vec![],
460 protocol: None,
461 allow_interrupt: false,
462 },
463 )]);
464 let mut manager = SessionManager::new(identity(), resources, ceiling, false);
465 assert_eq!(open(&mut manager, SessionMode::Mutating), Err(OpenError::MutationNotPermitted));
466 assert!(open(&mut manager, SessionMode::ReadOnly).is_ok());
468 }
469
470 #[test]
471 fn session_rate_and_deadline_limits() {
472 let mut mgr = manager();
473 mgr.limit_enforcer = AccessLimitEnforcer::new(2, 500_000_000);
474 let id = open(&mut mgr, SessionMode::ReadOnly).unwrap();
475
476 let t0 = 1_000_000_000;
477 assert!(mgr.check_access(id, AccessClass::ReadOnce, t0).is_ok());
478 assert!(mgr.check_access(id, AccessClass::ReadOnce, t0 + 100).is_ok());
479 assert_eq!(
480 mgr.check_access(id, AccessClass::ReadOnce, t0 + 200),
481 Err(Denial::LimitExceeded)
482 );
483
484 assert!(mgr.check_deadline(id, 400_000_000).is_ok());
486 assert_eq!(mgr.check_deadline(id, 600_000_000), Err(Denial::LimitExceeded));
487 }
488
489 #[test]
490 fn quiesce_hook_engages_on_mutating_session_and_releases_on_close_or_stop() {
491 let mut mgr = manager();
492 let events = Arc::new(std::sync::Mutex::new(Vec::new()));
493 let events_clone = events.clone();
494 mgr.set_quiesce_hook(Arc::new(move |paused| {
495 events_clone.lock().unwrap().push(paused);
496 }));
497 let flag = mgr.quiesce_flag();
498 assert!(!mgr.is_quiesced());
499 assert!(!flag.load(Ordering::SeqCst));
500
501 let ro_id = open(&mut mgr, SessionMode::ReadOnly).unwrap();
503 assert!(!mgr.is_quiesced());
504 assert!(events.lock().unwrap().is_empty());
505
506 let mut_id = open(&mut mgr, SessionMode::Mutating).unwrap();
508 assert!(mgr.is_quiesced());
509 assert!(flag.load(Ordering::SeqCst));
510 assert_eq!(*events.lock().unwrap(), vec![true]);
511
512 assert_eq!(open(&mut mgr, SessionMode::Mutating), Err(OpenError::MutationLeaseHeld));
514 assert_eq!(*events.lock().unwrap(), vec![true]);
515
516 assert!(mgr.close_session(ro_id));
518 assert!(mgr.is_quiesced());
519 assert_eq!(*events.lock().unwrap(), vec![true]);
520
521 assert!(mgr.close_session(mut_id));
523 assert!(!mgr.is_quiesced());
524 assert!(!flag.load(Ordering::SeqCst));
525 assert_eq!(*events.lock().unwrap(), vec![true, false]);
526
527 let _mut_id2 = open(&mut mgr, SessionMode::Mutating).unwrap();
529 assert!(mgr.is_quiesced());
530 mgr.reject_new_sessions();
531 assert!(!mgr.is_quiesced());
532 assert_eq!(*events.lock().unwrap(), vec![true, false, true, false]);
533 }
534}