1use std::fmt;
2
3use sim_kernel::{Cx, Error as KernelError, Result, Symbol};
4
5#[derive(Clone, Debug, PartialEq, Eq)]
7pub struct DeviceProfile {
8 pub device: Symbol,
10 pub streams: Vec<Symbol>,
12 pub inputs: Vec<Symbol>,
14 pub outputs: Vec<Symbol>,
16 pub sample_kinds: Vec<Symbol>,
18}
19
20impl DeviceProfile {
21 pub fn new(
23 device: Symbol,
24 streams: Vec<Symbol>,
25 inputs: Vec<Symbol>,
26 outputs: Vec<Symbol>,
27 sample_kinds: Vec<Symbol>,
28 ) -> Self {
29 Self {
30 device,
31 streams,
32 inputs,
33 outputs,
34 sample_kinds,
35 }
36 }
37
38 pub fn modeled_edge() -> Self {
40 Self::new(
41 Symbol::qualified("device", "modeled-edge"),
42 vec![
43 Symbol::qualified("device/stream", "battery"),
44 Symbol::qualified("device/stream", "motion"),
45 ],
46 vec![Symbol::qualified("device/input", "button")],
47 vec![
48 Symbol::qualified("device/output", "screen"),
49 Symbol::qualified("device/output", "haptic"),
50 ],
51 vec![Symbol::qualified("device/sample", "caps")],
52 )
53 }
54
55 pub fn supports_sample_kind(&self, sample_kind: &Symbol) -> bool {
57 self.sample_kinds.contains(sample_kind)
58 }
59}
60
61#[derive(Clone, Copy, Debug, PartialEq, Eq)]
63pub enum DeviceSiteLocality {
64 EdgeLocal,
66 HostLocal,
68 Remote,
70}
71
72#[derive(Clone, Debug, PartialEq, Eq)]
74pub struct DeviceSite {
75 pub symbol: Symbol,
77 pub profile: DeviceProfile,
79 pub surface_codec_id: Symbol,
81 pub locality: DeviceSiteLocality,
83}
84
85impl DeviceSite {
86 pub fn new(
88 symbol: Symbol,
89 profile: DeviceProfile,
90 surface_codec_id: Symbol,
91 locality: DeviceSiteLocality,
92 ) -> Self {
93 Self {
94 symbol,
95 profile,
96 surface_codec_id,
97 locality,
98 }
99 }
100
101 pub fn edge_local(symbol: Symbol, profile: DeviceProfile, surface_codec_id: Symbol) -> Self {
103 Self::new(
104 symbol,
105 profile,
106 surface_codec_id,
107 DeviceSiteLocality::EdgeLocal,
108 )
109 }
110
111 pub fn host_local(symbol: Symbol, profile: DeviceProfile, surface_codec_id: Symbol) -> Self {
113 Self::new(
114 symbol,
115 profile,
116 surface_codec_id,
117 DeviceSiteLocality::HostLocal,
118 )
119 }
120
121 pub fn remote(symbol: Symbol, profile: DeviceProfile, surface_codec_id: Symbol) -> Self {
123 Self::new(
124 symbol,
125 profile,
126 surface_codec_id,
127 DeviceSiteLocality::Remote,
128 )
129 }
130
131 pub fn is_edge_local(&self) -> bool {
133 self.locality == DeviceSiteLocality::EdgeLocal
134 }
135}
136
137#[derive(Clone, Debug, PartialEq, Eq)]
139pub struct DevicePlacement {
140 pub encoder: DeviceSite,
142 pub adapter: DeviceSite,
144}
145
146impl DevicePlacement {
147 pub fn new(encoder: DeviceSite, adapter: DeviceSite) -> Self {
149 Self { encoder, adapter }
150 }
151
152 pub fn validate(&self) -> std::result::Result<(), DevicePlacementError> {
154 if self.adapter.is_edge_local() {
155 Ok(())
156 } else {
157 Err(DevicePlacementError::AdapterMustBeEdgeLocal)
158 }
159 }
160}
161
162#[derive(Clone, Copy, Debug, PartialEq, Eq)]
164pub enum DevicePlacementError {
165 AdapterMustBeEdgeLocal,
167}
168
169impl fmt::Display for DevicePlacementError {
170 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
171 match self {
172 Self::AdapterMustBeEdgeLocal => f.write_str("device adapter must be edge-local"),
173 }
174 }
175}
176
177impl std::error::Error for DevicePlacementError {}
178
179pub trait DeviceProvider: Send {
181 fn open(&self) -> Result<Box<dyn DeviceSession>>;
183}
184
185pub trait DeviceSession: Send {
187 fn profile(&self) -> &DeviceProfile;
189
190 fn start(&mut self) -> Result<()>;
192
193 fn stop(&mut self) -> Result<()>;
195}
196
197#[derive(Clone, Debug, PartialEq, Eq)]
199pub struct StubProvider {
200 profile: DeviceProfile,
201}
202
203impl StubProvider {
204 pub fn new(profile: DeviceProfile) -> Self {
206 Self { profile }
207 }
208
209 pub fn profile(&self) -> &DeviceProfile {
211 &self.profile
212 }
213
214 pub fn session(&self) -> StubSession {
216 StubSession::new(self.profile.clone())
217 }
218}
219
220impl DeviceProvider for StubProvider {
221 fn open(&self) -> Result<Box<dyn DeviceSession>> {
222 Ok(Box::new(self.session()))
223 }
224}
225
226#[derive(Clone, Debug, PartialEq, Eq)]
228pub struct StubSession {
229 profile: DeviceProfile,
230 started: bool,
231}
232
233impl StubSession {
234 pub fn new(profile: DeviceProfile) -> Self {
236 Self {
237 profile,
238 started: false,
239 }
240 }
241
242 pub fn is_started(&self) -> bool {
244 self.started
245 }
246}
247
248impl DeviceSession for StubSession {
249 fn profile(&self) -> &DeviceProfile {
250 &self.profile
251 }
252
253 fn start(&mut self) -> Result<()> {
254 self.started = true;
255 Ok(())
256 }
257
258 fn stop(&mut self) -> Result<()> {
259 self.started = false;
260 Ok(())
261 }
262}
263
264#[derive(Clone, Debug, PartialEq, Eq)]
266pub struct RouteArg {
267 symbol: Symbol,
268}
269
270impl RouteArg {
271 pub fn new(symbol: Symbol) -> Self {
273 Self { symbol }
274 }
275
276 pub fn headless() -> Self {
278 Self::new(Symbol::qualified("device/route", "headless"))
279 }
280
281 pub fn symbol(&self) -> &Symbol {
283 &self.symbol
284 }
285}
286
287#[derive(Clone, Copy, Debug, PartialEq, Eq)]
289pub enum DeviceHostStalePolicy {
290 HoldLast,
292 PredictClamp,
294 Blank,
296 Refuse,
298}
299
300#[derive(Clone, Debug, PartialEq, Eq)]
302pub enum DeviceConsentPolicy {
303 Headless,
305 RequireReceipt {
307 subject: Symbol,
309 },
310}
311
312#[derive(Clone, Copy, Debug, PartialEq, Eq)]
314pub enum DeviceRateClass {
315 Sparse,
317 Interactive,
319 Surface,
321}
322
323impl DeviceRateClass {
324 fn interval_ms(self) -> u64 {
325 match self {
326 Self::Sparse => 1_000,
327 Self::Interactive => 100,
328 Self::Surface => 50,
329 }
330 }
331}
332
333pub fn derive_device_rate_class(profile: &DeviceProfile) -> DeviceRateClass {
335 if !profile.outputs.is_empty() {
336 DeviceRateClass::Surface
337 } else if !profile.inputs.is_empty() {
338 DeviceRateClass::Interactive
339 } else {
340 DeviceRateClass::Sparse
341 }
342}
343
344#[derive(Clone, Debug, PartialEq, Eq)]
346pub struct DeviceHostSpec {
347 pub profile: DeviceProfile,
349 pub route: RouteArg,
351 pub placement: DevicePlacement,
353 pub stale: DeviceHostStalePolicy,
355 pub consent: DeviceConsentPolicy,
357}
358
359impl DeviceHostSpec {
360 pub fn new(
362 profile: DeviceProfile,
363 route: RouteArg,
364 placement: DevicePlacement,
365 stale: DeviceHostStalePolicy,
366 consent: DeviceConsentPolicy,
367 ) -> Self {
368 Self {
369 profile,
370 route,
371 placement,
372 stale,
373 consent,
374 }
375 }
376}
377
378#[derive(Clone, Copy, Debug, PartialEq, Eq)]
380pub enum DeviceProviderKind {
381 Instance,
383 Stub,
385}
386
387#[derive(Clone, Copy, Debug, PartialEq, Eq)]
389pub struct AdapterTick {
390 sequence: u64,
391 interval_ms: u64,
392}
393
394impl AdapterTick {
395 pub fn sequence(&self) -> u64 {
397 self.sequence
398 }
399
400 pub fn interval_ms(&self) -> u64 {
402 self.interval_ms
403 }
404}
405
406#[derive(Clone, Debug, PartialEq, Eq)]
408pub struct DeviceAdapterLoopPlan {
409 rate_class: DeviceRateClass,
410 stale: DeviceHostStalePolicy,
411 route: RouteArg,
412 device: Symbol,
413 sequence: u64,
414}
415
416impl DeviceAdapterLoopPlan {
417 pub fn for_profile(
419 profile: &DeviceProfile,
420 route: RouteArg,
421 stale: DeviceHostStalePolicy,
422 ) -> Self {
423 Self {
424 rate_class: derive_device_rate_class(profile),
425 stale,
426 route,
427 device: profile.device.clone(),
428 sequence: 0,
429 }
430 }
431
432 pub fn rate_class(&self) -> DeviceRateClass {
434 self.rate_class
435 }
436
437 pub fn stale_policy(&self) -> DeviceHostStalePolicy {
439 self.stale
440 }
441
442 pub fn route(&self) -> &RouteArg {
444 &self.route
445 }
446
447 pub fn device(&self) -> &Symbol {
449 &self.device
450 }
451
452 pub fn sequence(&self) -> u64 {
454 self.sequence
455 }
456
457 pub fn interval_ms(&self) -> u64 {
459 self.rate_class.interval_ms()
460 }
461
462 pub fn next_tick(&mut self) -> AdapterTick {
464 self.sequence += 1;
465 AdapterTick {
466 sequence: self.sequence,
467 interval_ms: self.interval_ms(),
468 }
469 }
470}
471
472#[derive(Clone, Debug, PartialEq, Eq)]
474pub struct DeviceSurfaceHubJoin {
475 route: RouteArg,
476 device: Symbol,
477 adapter_site: Symbol,
478}
479
480impl DeviceSurfaceHubJoin {
481 pub fn new(route: RouteArg, device: Symbol, adapter_site: Symbol) -> Self {
483 Self {
484 route,
485 device,
486 adapter_site,
487 }
488 }
489
490 pub fn route(&self) -> &RouteArg {
492 &self.route
493 }
494
495 pub fn device(&self) -> &Symbol {
497 &self.device
498 }
499
500 pub fn adapter_site(&self) -> &Symbol {
502 &self.adapter_site
503 }
504}
505
506pub struct DeviceEdgeSession {
508 spec: DeviceHostSpec,
509 provider_kind: DeviceProviderKind,
510 session: Box<dyn DeviceSession>,
511 adapter_loop: DeviceAdapterLoopPlan,
512 hub_join: DeviceSurfaceHubJoin,
513 live: bool,
514}
515
516impl DeviceEdgeSession {
517 pub fn is_live(&self) -> bool {
519 self.live
520 }
521
522 pub fn provider_kind(&self) -> DeviceProviderKind {
524 self.provider_kind
525 }
526
527 pub fn profile(&self) -> &DeviceProfile {
529 &self.spec.profile
530 }
531
532 pub fn route(&self) -> &RouteArg {
534 &self.spec.route
535 }
536
537 pub fn placement(&self) -> &DevicePlacement {
539 &self.spec.placement
540 }
541
542 pub fn stale_policy(&self) -> DeviceHostStalePolicy {
544 self.spec.stale
545 }
546
547 pub fn consent_policy(&self) -> &DeviceConsentPolicy {
549 &self.spec.consent
550 }
551
552 pub fn adapter_loop(&self) -> &DeviceAdapterLoopPlan {
554 &self.adapter_loop
555 }
556
557 pub fn adapter_loop_mut(&mut self) -> &mut DeviceAdapterLoopPlan {
559 &mut self.adapter_loop
560 }
561
562 pub fn hub_join(&self) -> &DeviceSurfaceHubJoin {
564 &self.hub_join
565 }
566
567 pub fn device_session(&self) -> &dyn DeviceSession {
569 self.session.as_ref()
570 }
571
572 pub fn device_session_mut(&mut self) -> &mut dyn DeviceSession {
574 self.session.as_mut()
575 }
576}
577
578pub fn install_device_bases(cx: &mut Cx) -> Result<()> {
580 cx.factory().nil().map(|_| ())
581}
582
583pub fn compose_device_host(cx: &mut Cx, spec: DeviceHostSpec) -> Result<DeviceEdgeSession> {
585 let provider = StubProvider::new(spec.profile.clone());
586 join_device_session(cx, spec, DeviceProviderKind::Stub, provider.open()?)
587}
588
589pub fn compose_device_host_with_provider<P>(
591 cx: &mut Cx,
592 spec: DeviceHostSpec,
593 provider: &P,
594) -> Result<DeviceEdgeSession>
595where
596 P: DeviceProvider + ?Sized,
597{
598 join_device_session(cx, spec, DeviceProviderKind::Instance, provider.open()?)
599}
600
601fn join_device_session(
602 cx: &mut Cx,
603 spec: DeviceHostSpec,
604 provider_kind: DeviceProviderKind,
605 session: Box<dyn DeviceSession>,
606) -> Result<DeviceEdgeSession> {
607 install_device_bases(cx)?;
608 spec.placement
609 .validate()
610 .map_err(|error| KernelError::HostError(error.to_string()))?;
611 let mut session = session;
612 if session.profile() != &spec.profile {
613 return Err(KernelError::HostError(format!(
614 "device provider profile {} did not match requested profile {}",
615 session.profile().device,
616 spec.profile.device
617 )));
618 }
619 session.start()?;
620 let adapter_loop =
621 DeviceAdapterLoopPlan::for_profile(&spec.profile, spec.route.clone(), spec.stale);
622 let hub_join = DeviceSurfaceHubJoin::new(
623 spec.route.clone(),
624 spec.profile.device.clone(),
625 spec.placement.adapter.symbol.clone(),
626 );
627 Ok(DeviceEdgeSession {
628 spec,
629 provider_kind,
630 session,
631 adapter_loop,
632 hub_join,
633 live: true,
634 })
635}