1use crate::dai::DaiFormatSet;
6use crate::format_set::PcmFormatSet;
7use camino::Utf8PathBuf;
8use flex_fuchsia_audio_controller as fac;
9use flex_fuchsia_audio_device as fadevice;
10use flex_fuchsia_hardware_audio as fhaudio;
11use flex_fuchsia_io as fio;
12use std::collections::BTreeMap;
13use std::fmt::Display;
14use std::str::FromStr;
15use thiserror::Error;
16use zx_types;
17use fadevice::DeviceType as AdrDevType;
19
20#[cfg(feature = "fdomain")]
21use fuchsia_fs_fdomain as fuchsia_fs;
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub struct Type(fac::DeviceType);
26
27impl Type {
28 pub fn devfs_class(&self) -> &str {
32 match self.0 {
33 fac::DeviceType::Codec => "codec",
34 fac::DeviceType::Composite => "audio-composite",
35 fac::DeviceType::Dai => "dai",
36 fac::DeviceType::Input => "audio-input",
37 fac::DeviceType::Output => "audio-output",
38 _ => panic!("Unexpected device type"),
39 }
40 }
41}
42
43impl From<Type> for fac::DeviceType {
44 fn from(value: Type) -> Self {
45 value.0
46 }
47}
48
49impl From<AdrDevType> for Type {
50 fn from(value: AdrDevType) -> Self {
51 let device_type = match value {
52 AdrDevType::Codec => fac::DeviceType::Codec,
53 AdrDevType::Composite => fac::DeviceType::Composite,
54 _ => panic!("Unexpected device type"),
55 };
56 Self(device_type)
57 }
58}
59
60impl From<fac::DeviceType> for Type {
61 fn from(value: fac::DeviceType) -> Self {
62 Self(value)
63 }
64}
65
66impl From<Type> for AdrDevType {
67 fn from(value: Type) -> Self {
68 match value.0 {
69 fac::DeviceType::Codec => AdrDevType::Codec,
70 fac::DeviceType::Composite => AdrDevType::Composite,
71 _ => panic!("Unexpected device type"),
72 }
73 }
74}
75
76impl FromStr for Type {
77 type Err = String;
78
79 fn from_str(s: &str) -> Result<Self, Self::Err> {
80 let device_type = match s.to_lowercase().as_str() {
81 "codec" => Ok(fac::DeviceType::Codec),
82 "composite" => Ok(fac::DeviceType::Composite),
83 "dai" => Ok(fac::DeviceType::Dai),
84 "input" => Ok(fac::DeviceType::Input),
85 "output" => Ok(fac::DeviceType::Output),
86 _ => Err(format!(
87 "Invalid device type: {}. Expected one of: Codec, Composite, Dai, Input, Output",
88 s
89 )),
90 }?;
91
92 Ok(Self(device_type))
93 }
94}
95
96impl Display for Type {
97 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
98 let s = match self.0 {
99 fac::DeviceType::Codec => "Codec",
100 fac::DeviceType::Composite => "Composite",
101 fac::DeviceType::Dai => "Dai",
102 fac::DeviceType::Input => "Input",
103 fac::DeviceType::Output => "Output",
104 _ => "<unknown>",
105 };
106 f.write_str(s)
107 }
108}
109
110impl TryFrom<(HardwareType, Option<Direction>)> for Type {
111 type Error = String;
112
113 fn try_from(value: (HardwareType, Option<Direction>)) -> Result<Self, Self::Error> {
114 let (type_, direction) = value;
115 let device_type = match type_.0 {
116 fhaudio::DeviceType::Codec => Ok(fac::DeviceType::Codec),
117 fhaudio::DeviceType::Composite => Ok(fac::DeviceType::Composite),
118 fhaudio::DeviceType::Dai => Ok(fac::DeviceType::Dai),
119 fhaudio::DeviceType::StreamConfig => Ok(
120 match direction
121 .ok_or_else(|| "direction is missing for StreamConfig type".to_string())?
122 {
123 Direction::Input => fac::DeviceType::Input,
124 Direction::Output => fac::DeviceType::Output,
125 },
126 ),
127 _ => Err("unknown device type".to_string()),
128 }?;
129 Ok(Self(device_type))
130 }
131}
132
133#[derive(Debug, Clone, Copy, PartialEq, Eq)]
135pub struct HardwareType(pub fhaudio::DeviceType);
136
137impl FromStr for HardwareType {
138 type Err = String;
139
140 fn from_str(s: &str) -> Result<Self, Self::Err> {
141 let device_type = match s.to_lowercase().as_str() {
142 "codec" => Ok(fhaudio::DeviceType::Codec),
143 "composite" => Ok(fhaudio::DeviceType::Composite),
144 "dai" => Ok(fhaudio::DeviceType::Dai),
145 "streamconfig" => Ok(fhaudio::DeviceType::StreamConfig),
146 _ => Err(format!(
147 "Invalid type: {}. Expected one of: Codec, Composite, Dai, StreamConfig",
148 s
149 )),
150 }?;
151 Ok(Self(device_type))
152 }
153}
154
155impl Display for HardwareType {
156 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
157 let s = match self.0 {
158 fhaudio::DeviceType::Codec => "Codec",
159 fhaudio::DeviceType::Composite => "Composite",
160 fhaudio::DeviceType::Dai => "Dai",
161 fhaudio::DeviceType::StreamConfig => "StreamConfig",
162 _ => "<unknown>",
163 };
164 f.write_str(s)
165 }
166}
167
168impl From<Type> for HardwareType {
169 fn from(value: Type) -> Self {
170 let hw_type = match value.0 {
171 fac::DeviceType::Codec => fhaudio::DeviceType::Codec,
172 fac::DeviceType::Composite => fhaudio::DeviceType::Composite,
173 fac::DeviceType::Dai => fhaudio::DeviceType::Dai,
174 fac::DeviceType::Input | fac::DeviceType::Output => fhaudio::DeviceType::StreamConfig,
175 _ => panic!("Unexpected device type"),
176 };
177 Self(hw_type)
178 }
179}
180
181#[derive(Debug, Clone, Copy, PartialEq, Eq)]
183pub enum Direction {
184 Input,
186
187 Output,
189}
190
191impl FromStr for Direction {
192 type Err = String;
193
194 fn from_str(s: &str) -> Result<Self, Self::Err> {
195 match s.to_lowercase().as_str() {
196 "input" => Ok(Self::Input),
197 "output" => Ok(Self::Output),
198 _ => Err(format!("Invalid direction: {}. Expected one of: input, output", s)),
199 }
200 }
201}
202
203#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
205pub enum Selector {
206 Devfs(DevfsSelector),
207 Registry(RegistrySelector),
208}
209
210impl TryFrom<fac::DeviceSelector> for Selector {
211 type Error = String;
212
213 fn try_from(value: fac::DeviceSelector) -> Result<Self, Self::Error> {
214 match value {
215 fac::DeviceSelector::Devfs(devfs) => Ok(Self::Devfs(devfs.into())),
216 fac::DeviceSelector::Registry(token_id) => Ok(Self::Registry(token_id.into())),
217 _ => Err("unknown selector variant".to_string()),
218 }
219 }
220}
221
222impl From<fac::Devfs> for Selector {
223 fn from(value: fac::Devfs) -> Self {
224 Self::Devfs(value.into())
225 }
226}
227
228impl From<Selector> for fac::DeviceSelector {
229 fn from(value: Selector) -> Self {
230 match value {
231 Selector::Devfs(devfs_selector) => devfs_selector.into(),
232 Selector::Registry(registry_selector) => registry_selector.into(),
233 }
234 }
235}
236
237#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
239pub struct DevfsSelector(pub fac::Devfs);
240
241impl DevfsSelector {
242 pub fn path(&self) -> Utf8PathBuf {
244 Utf8PathBuf::from("/dev/class").join(self.relative_path())
245 }
246
247 pub fn relative_path(&self) -> Utf8PathBuf {
249 Utf8PathBuf::from(self.device_type().devfs_class()).join(self.0.name.clone())
250 }
251
252 pub fn device_type(&self) -> Type {
254 Type(self.0.device_type)
255 }
256}
257
258impl Display for DevfsSelector {
259 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
260 f.write_str(self.path().as_str())
261 }
262}
263
264impl TryFrom<fac::DeviceSelector> for DevfsSelector {
265 type Error = String;
266
267 fn try_from(value: fac::DeviceSelector) -> Result<Self, Self::Error> {
268 match value {
269 fac::DeviceSelector::Devfs(devfs) => Ok(Self(devfs)),
270 _ => Err("unknown selector type".to_string()),
271 }
272 }
273}
274
275impl From<fac::Devfs> for DevfsSelector {
276 fn from(value: fac::Devfs) -> Self {
277 Self(value)
278 }
279}
280
281impl From<DevfsSelector> for fac::DeviceSelector {
282 fn from(value: DevfsSelector) -> Self {
283 Self::Devfs(value.0)
284 }
285}
286
287#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
289pub struct RegistrySelector(pub fadevice::TokenId);
290
291impl RegistrySelector {
292 pub fn token_id(&self) -> fadevice::TokenId {
293 self.0
294 }
295}
296
297impl Display for RegistrySelector {
298 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
299 write!(f, "{}", self.0)
300 }
301}
302
303impl TryFrom<fac::DeviceSelector> for RegistrySelector {
304 type Error = String;
305
306 fn try_from(value: fac::DeviceSelector) -> Result<Self, Self::Error> {
307 match value {
308 fac::DeviceSelector::Registry(token_id) => Ok(Self(token_id)),
309 _ => Err("unknown selector type".to_string()),
310 }
311 }
312}
313
314impl From<fadevice::TokenId> for RegistrySelector {
315 fn from(value: fadevice::TokenId) -> Self {
316 Self(value)
317 }
318}
319
320impl From<RegistrySelector> for fac::DeviceSelector {
321 fn from(value: RegistrySelector) -> Self {
322 Self::Registry(value.0)
323 }
324}
325
326#[derive(Debug, Clone, PartialEq)]
328pub struct Info(pub fadevice::Info);
329
330impl Info {
331 pub fn token_id(&self) -> fadevice::TokenId {
332 self.0.token_id.expect("missing 'token_id'")
333 }
334
335 pub fn registry_selector(&self) -> RegistrySelector {
336 RegistrySelector(self.token_id())
337 }
338
339 pub fn device_type(&self) -> Type {
340 Type::from(self.0.device_type.expect("missing 'device_type'"))
341 }
342
343 pub fn device_name(&self) -> &str {
344 self.0.device_name.as_ref().expect("missing 'device_name'")
345 }
346
347 pub fn unique_instance_id(&self) -> Option<UniqueInstanceId> {
348 self.0.unique_instance_id.map(UniqueInstanceId)
349 }
350
351 pub fn plug_detect_capabilities(&self) -> Option<PlugDetectCapabilities> {
352 self.0.plug_detect_caps.map(PlugDetectCapabilities::from)
353 }
354
355 pub fn gain_capabilities(&self) -> Option<GainCapabilities> {
356 None
357 }
358
359 pub fn clock_domain(&self) -> Option<ClockDomain> {
360 self.0.clock_domain.map(ClockDomain)
361 }
362
363 pub fn supported_ring_buffer_formats(
364 &self,
365 ) -> Result<BTreeMap<fadevice::ElementId, Vec<PcmFormatSet>>, String> {
366 self.0
367 .ring_buffer_format_sets
368 .as_ref()
369 .map_or_else(
370 || Ok(BTreeMap::new()),
371 |element_rb_format_sets| {
372 element_rb_format_sets
373 .iter()
374 .cloned()
375 .map(|element_rb_format_set| {
376 let element_id = element_rb_format_set
377 .element_id
378 .ok_or_else(|| "missing element_id".to_string())?;
379 let fidl_format_sets = element_rb_format_set
380 .format_sets
381 .ok_or_else(|| "missing format_sets".to_string())?;
382
383 let format_sets: Vec<PcmFormatSet> = fidl_format_sets
384 .into_iter()
385 .map(TryInto::try_into)
386 .collect::<Result<Vec<_>, _>>()
387 .map_err(|err| format!("invalid format set: {}", err))?;
388
389 Ok((element_id, format_sets))
390 })
391 .collect::<Result<BTreeMap<_, _>, String>>()
392 },
393 )
394 .map_err(|err| format!("invalid ring buffer format sets: {}", err))
395 }
396
397 pub fn supported_dai_formats(
398 &self,
399 ) -> Result<BTreeMap<fadevice::ElementId, Vec<DaiFormatSet>>, String> {
400 self.0
401 .dai_format_sets
402 .as_ref()
403 .map_or_else(
404 || Ok(BTreeMap::new()),
405 |element_dai_format_sets| {
406 element_dai_format_sets
407 .iter()
408 .cloned()
409 .map(|element_dai_format_set| {
410 let element_id = element_dai_format_set
411 .element_id
412 .ok_or_else(|| "missing element_id".to_string())?;
413 let fidl_format_sets = element_dai_format_set
414 .format_sets
415 .ok_or_else(|| "missing format_sets".to_string())?;
416
417 let dai_format_sets: Vec<DaiFormatSet> = fidl_format_sets
418 .into_iter()
419 .map(TryInto::try_into)
420 .collect::<Result<Vec<_>, _>>()
421 .map_err(|err| format!("invalid DAI format set: {}", err))?;
422
423 Ok((element_id, dai_format_sets))
424 })
425 .collect::<Result<BTreeMap<_, _>, String>>()
426 },
427 )
428 .map_err(|err| format!("invalid ring buffer format sets: {}", err))
429 }
430}
431
432impl From<fadevice::Info> for Info {
433 fn from(value: fadevice::Info) -> Self {
434 Self(value)
435 }
436}
437
438#[derive(Debug, Clone, PartialEq)]
439pub struct UniqueInstanceId(pub [u8; fadevice::UNIQUE_INSTANCE_ID_SIZE as usize]);
440
441impl Display for UniqueInstanceId {
442 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
443 for byte in self.0 {
444 write!(f, "{:02x}", byte)?;
445 }
446 Ok(())
447 }
448}
449
450impl From<[u8; fadevice::UNIQUE_INSTANCE_ID_SIZE as usize]> for UniqueInstanceId {
451 fn from(value: [u8; fadevice::UNIQUE_INSTANCE_ID_SIZE as usize]) -> Self {
452 Self(value)
453 }
454}
455
456#[derive(Debug, Clone, PartialEq)]
457pub struct PlugDetectCapabilities(pub fadevice::PlugDetectCapabilities);
458
459impl Display for PlugDetectCapabilities {
460 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
461 let s = match self.0 {
462 fadevice::PlugDetectCapabilities::Hardwired => "Hardwired",
463 fadevice::PlugDetectCapabilities::Pluggable => "Pluggable (can async notify)",
464 _ => "<unknown>",
465 };
466 f.write_str(s)
467 }
468}
469
470impl From<fadevice::PlugDetectCapabilities> for PlugDetectCapabilities {
471 fn from(value: fadevice::PlugDetectCapabilities) -> Self {
472 Self(value)
473 }
474}
475
476impl From<PlugDetectCapabilities> for fadevice::PlugDetectCapabilities {
477 fn from(value: PlugDetectCapabilities) -> Self {
478 value.0
479 }
480}
481
482impl From<fhaudio::PlugDetectCapabilities> for PlugDetectCapabilities {
483 fn from(value: fhaudio::PlugDetectCapabilities) -> Self {
484 let plug_detect_caps = match value {
485 fhaudio::PlugDetectCapabilities::Hardwired => {
486 fadevice::PlugDetectCapabilities::Hardwired
487 }
488 fhaudio::PlugDetectCapabilities::CanAsyncNotify => {
489 fadevice::PlugDetectCapabilities::Pluggable
490 }
491 };
492 Self(plug_detect_caps)
493 }
494}
495
496impl TryFrom<PlugDetectCapabilities> for fhaudio::PlugDetectCapabilities {
497 type Error = String;
498
499 fn try_from(value: PlugDetectCapabilities) -> Result<Self, Self::Error> {
500 match value.0 {
501 fadevice::PlugDetectCapabilities::Hardwired => Ok(Self::Hardwired),
502 fadevice::PlugDetectCapabilities::Pluggable => Ok(Self::CanAsyncNotify),
503 _ => Err("unsupported PlugDetectCapabilities value".to_string()),
504 }
505 }
506}
507
508#[derive(Debug, Clone, Copy, PartialEq)]
510pub struct PlugEvent {
511 pub state: PlugState,
512
513 pub time: zx_types::zx_time_t,
515}
516
517impl From<(fadevice::PlugState, i64 )> for PlugEvent {
518 fn from(value: (fadevice::PlugState, i64)) -> Self {
519 let (state, time) = value;
520 Self { state: state.into(), time }
521 }
522}
523
524impl TryFrom<fhaudio::PlugState> for PlugEvent {
525 type Error = String;
526
527 fn try_from(value: fhaudio::PlugState) -> Result<Self, Self::Error> {
528 let plugged = value.plugged.ok_or_else(|| "missing 'plugged'".to_string())?;
529 let time = value.plug_state_time.ok_or_else(|| "missing 'plug_state_time'".to_string())?;
530 let state = PlugState(if plugged {
531 fadevice::PlugState::Plugged
532 } else {
533 fadevice::PlugState::Unplugged
534 });
535 Ok(Self { state, time })
536 }
537}
538
539#[derive(Debug, Clone, Copy, PartialEq)]
540pub struct PlugState(pub fadevice::PlugState);
541
542impl Display for PlugState {
543 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
544 let s = match self.0 {
545 fadevice::PlugState::Plugged => "Plugged",
546 fadevice::PlugState::Unplugged => "Unplugged",
547 _ => "<unknown>",
548 };
549 f.write_str(s)
550 }
551}
552
553impl From<fadevice::PlugState> for PlugState {
554 fn from(value: fadevice::PlugState) -> Self {
555 Self(value)
556 }
557}
558
559impl From<PlugState> for fadevice::PlugState {
560 fn from(value: PlugState) -> Self {
561 value.0
562 }
563}
564
565#[derive(Debug, Clone, Copy, PartialEq)]
566pub struct GainState {
567 pub gain_db: f32,
568 pub muted: Option<bool>,
569 pub agc_enabled: Option<bool>,
570}
571
572impl TryFrom<fhaudio::GainState> for GainState {
573 type Error = String;
574
575 fn try_from(value: fhaudio::GainState) -> Result<Self, Self::Error> {
576 Ok(Self {
577 gain_db: value.gain_db.ok_or_else(|| "missing 'gain_db'".to_string())?,
578 muted: value.muted,
579 agc_enabled: value.agc_enabled,
580 })
581 }
582}
583
584#[derive(Debug, Clone, Copy, PartialEq)]
585pub struct GainCapabilities {
586 pub min_gain_db: f32,
587 pub max_gain_db: f32,
588 pub gain_step_db: f32,
589 pub can_mute: Option<bool>,
590 pub can_agc: Option<bool>,
591}
592
593impl TryFrom<&fhaudio::StreamProperties> for GainCapabilities {
594 type Error = String;
595
596 fn try_from(value: &fhaudio::StreamProperties) -> Result<Self, Self::Error> {
597 Ok(Self {
598 min_gain_db: value.min_gain_db.ok_or_else(|| "missing 'min_gain_db'".to_string())?,
599 max_gain_db: value.max_gain_db.ok_or_else(|| "missing 'max_gain_db'".to_string())?,
600 gain_step_db: value.gain_step_db.ok_or_else(|| "missing 'gain_step_db'".to_string())?,
601 can_mute: value.can_mute,
602 can_agc: value.can_agc,
603 })
604 }
605}
606
607#[derive(Debug, Clone, Copy, PartialEq, Eq)]
608pub struct ClockDomain(pub fhaudio::ClockDomain);
609
610impl Display for ClockDomain {
611 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
612 write!(f, "{}", self.0)?;
613 match self.0 {
614 fhaudio::CLOCK_DOMAIN_MONOTONIC => f.write_str(" (monotonic)"),
615 fhaudio::CLOCK_DOMAIN_EXTERNAL => f.write_str(" (external)"),
616 _ => Ok(()),
617 }
618 }
619}
620
621impl From<fhaudio::ClockDomain> for ClockDomain {
622 fn from(value: fhaudio::ClockDomain) -> Self {
623 Self(value)
624 }
625}
626
627impl From<ClockDomain> for fhaudio::ClockDomain {
628 fn from(value: ClockDomain) -> Self {
629 value.0
630 }
631}
632
633#[derive(Error, Debug)]
634pub enum ListDevfsError {
635 #[error("Failed to open directory {}: {:?}", name, err)]
636 Open {
637 name: String,
638 #[source]
639 err: fuchsia_fs::node::OpenError,
640 },
641
642 #[error("Failed to read directory {} entries: {:?}", name, err)]
643 Readdir {
644 name: String,
645 #[source]
646 err: fuchsia_fs::directory::EnumerateError,
647 },
648}
649
650pub async fn list_devfs(
654 dev_class: &fio::DirectoryProxy,
655) -> Result<Vec<DevfsSelector>, ListDevfsError> {
656 const TYPES: &[Type] = &[
657 Type(fac::DeviceType::Codec),
658 Type(fac::DeviceType::Composite),
659 Type(fac::DeviceType::Dai),
660 Type(fac::DeviceType::Input),
661 Type(fac::DeviceType::Output),
662 ];
663
664 let mut selectors = vec![];
665
666 for device_type in TYPES {
667 let subdir_name = device_type.devfs_class();
668 let subdir = match fuchsia_fs::directory::open_directory(
669 dev_class,
670 subdir_name,
671 fio::Flags::empty(),
672 )
673 .await
674 {
675 Ok(d) => d,
676 Err(err) if err.is_not_found_error() => continue,
679 Err(err) => return Err(ListDevfsError::Open { name: subdir_name.to_string(), err }),
680 };
681 let entries = fuchsia_fs::directory::readdir(&subdir)
682 .await
683 .map_err(|err| ListDevfsError::Readdir { name: subdir_name.to_string(), err })?;
684 selectors.extend(entries.into_iter().map(|entry| {
685 DevfsSelector(fac::Devfs { name: entry.name, device_type: device_type.0 })
686 }));
687 }
688
689 Ok(selectors)
690}
691
692#[derive(Error, Debug)]
693pub enum ListRegistryError {
694 #[error(transparent)]
695 Fidl(#[from] fidl::Error),
696
697 #[error("failed to get devices: {:?}", .0)]
698 WatchDevicesAdded(fadevice::RegistryWatchDevicesAddedError),
699}
700
701pub async fn list_registry(
703 registry: &fadevice::RegistryProxy,
704) -> Result<Vec<Info>, ListRegistryError> {
705 Ok(registry
706 .watch_devices_added()
707 .await
708 .map_err(ListRegistryError::Fidl)?
709 .map_err(ListRegistryError::WatchDevicesAdded)?
710 .devices
711 .expect("missing devices")
712 .into_iter()
713 .map(Info::from)
714 .collect())
715}
716
717#[cfg(test)]
718mod test {
719 use super::*;
720 #[cfg(feature = "fdomain")]
721 use fidl_test_util::fdomain::spawn_stream_handler;
722 #[cfg(not(feature = "fdomain"))]
723 use fidl_test_util::spawn_stream_handler;
724 use std::sync::Arc;
725 use test_case::test_case;
726 #[cfg(not(feature = "fdomain"))]
727 use vfs::pseudo_directory;
728
729 #[test_case("composite", fac::DeviceType::Composite; "composite")]
730 #[test_case("input", fac::DeviceType::Input; "input")]
731 #[test_case("output", fac::DeviceType::Output; "output")]
732 fn test_parse_type(s: &str, expected_type: fac::DeviceType) {
733 assert_eq!(Type(expected_type), s.parse::<Type>().unwrap());
734 }
735
736 #[test]
737 fn test_parse_type_invalid() {
738 assert!("not a valid device type".parse::<Type>().is_err());
739 }
740
741 #[test_case("Codec", fhaudio::DeviceType::Codec; "Codec")]
742 #[test_case("Composite", fhaudio::DeviceType::Composite; "Composite")]
743 #[test_case("Dai", fhaudio::DeviceType::Dai; "Dai")]
744 #[test_case("StreamConfig", fhaudio::DeviceType::StreamConfig; "StreamConfig")]
745 fn test_parse_hardware_type(s: &str, expected_type: fhaudio::DeviceType) {
746 assert_eq!(HardwareType(expected_type), s.parse::<HardwareType>().unwrap());
747 }
748
749 #[test]
750 fn test_parse_hardware_type_invalid() {
751 assert!("not a valid hardware device type".parse::<Type>().is_err());
752 }
753
754 #[test_case(fhaudio::DeviceType::Codec, None, fac::DeviceType::Codec; "Codec")]
755 #[test_case(fhaudio::DeviceType::Composite, None, fac::DeviceType::Composite; "Composite")]
756 #[test_case(fhaudio::DeviceType::Dai, None, fac::DeviceType::Dai; "Dai")]
757 #[test_case(
758 fhaudio::DeviceType::StreamConfig,
759 Some(Direction::Input),
760 fac::DeviceType::Input;
761 "StreamConfig input"
762 )]
763 #[test_case(
764 fhaudio::DeviceType::StreamConfig,
765 Some(Direction::Output),
766 fac::DeviceType::Output;
767 "StreamConfig output"
768 )]
769 fn test_from_hardware_type_with_direction(
770 hardware_type: fhaudio::DeviceType,
771 direction: Option<Direction>,
772 expected_type: fac::DeviceType,
773 ) {
774 assert_eq!(
775 Type(expected_type),
776 (HardwareType(hardware_type), direction).try_into().unwrap()
777 )
778 }
779
780 #[test_case(
781 fac::Devfs { name: "3d99d780".to_string(), device_type: fac::DeviceType::Codec },
782 "/dev/class/codec/3d99d780";
783 "codec"
784 )]
785 #[test_case(
786 fac::Devfs { name: "3d99d780".to_string(), device_type: fac::DeviceType::Composite },
787 "/dev/class/audio-composite/3d99d780";
788 "composite"
789 )]
790 #[test_case(
791 fac::Devfs { name: "3d99d780".to_string(), device_type: fac::DeviceType::Dai },
792 "/dev/class/dai/3d99d780";
793 "dai"
794 )]
795 #[test_case(
796 fac::Devfs { name: "3d99d780".to_string(), device_type: fac::DeviceType::Input },
797 "/dev/class/audio-input/3d99d780";
798 "input"
799 )]
800 #[test_case(
801 fac::Devfs { name: "3d99d780".to_string(), device_type: fac::DeviceType::Output },
802 "/dev/class/audio-output/3d99d780";
803 "output"
804 )]
805 fn test_devfs_selector_path(devfs: fac::Devfs, expected_path: &str) {
806 assert_eq!(expected_path, DevfsSelector(devfs).path());
807 }
808
809 #[cfg(not(feature = "fdomain"))]
810 fn placeholder_node() -> Arc<vfs::service::Service> {
811 vfs::service::endpoint(move |_scope, _channel| {
812 })
814 }
815
816 #[cfg(not(feature = "fdomain"))]
817 #[fuchsia::test]
818 async fn test_list_devfs() {
819 let placeholder = placeholder_node();
822
823 let dev_class_vfs = pseudo_directory! {
824 "codec" => pseudo_directory! {
825 "codec-0" => placeholder.clone(),
826 },
827 "audio-composite" => pseudo_directory! {
828 "composite-0" => placeholder.clone(),
829 },
830 "dai" => pseudo_directory! {
831 "dai-0" => placeholder.clone(),
832 },
833 "audio-input" => pseudo_directory! {
834 "input-0" => placeholder.clone(),
835 "input-1" => placeholder.clone(),
836 },
837 "audio-output" => pseudo_directory! {
838 "output-0" => placeholder.clone(),
839 },
840 };
841
842 let execution_scope = vfs::execution_scope::ExecutionScope::new();
843 let dev_class = vfs::directory::serve_read_only(dev_class_vfs, execution_scope);
844 let selectors = list_devfs(&dev_class).await.unwrap();
845
846 assert_eq!(
847 vec![
848 DevfsSelector(fac::Devfs {
849 name: "codec-0".to_string(),
850 device_type: fac::DeviceType::Codec,
851 }),
852 DevfsSelector(fac::Devfs {
853 name: "composite-0".to_string(),
854 device_type: fac::DeviceType::Composite,
855 }),
856 DevfsSelector(fac::Devfs {
857 name: "dai-0".to_string(),
858 device_type: fac::DeviceType::Dai,
859 }),
860 DevfsSelector(fac::Devfs {
861 name: "input-0".to_string(),
862 device_type: fac::DeviceType::Input,
863 }),
864 DevfsSelector(fac::Devfs {
865 name: "input-1".to_string(),
866 device_type: fac::DeviceType::Input,
867 }),
868 DevfsSelector(fac::Devfs {
869 name: "output-0".to_string(),
870 device_type: fac::DeviceType::Output,
871 }),
872 ],
873 selectors
874 );
875 }
876
877 #[cfg(not(feature = "fdomain"))]
878 #[fuchsia::test]
879 async fn test_list_devfs_missing_dirs() {
880 let placeholder = placeholder_node();
884
885 let dev_class_vfs = pseudo_directory! {
888 "audio-composite" => pseudo_directory! {
889 "composite-0" => placeholder.clone(),
890 },
891 "dai" => pseudo_directory! {
892 "dai-0" => placeholder.clone(),
893 },
894 "audio-output" => pseudo_directory! {
895 "output-0" => placeholder.clone(),
896 },
897 };
898
899 let execution_scope = vfs::execution_scope::ExecutionScope::new();
900 let dev_class = vfs::directory::serve_read_only(dev_class_vfs, execution_scope);
901 let selectors = list_devfs(&dev_class).await.unwrap();
902
903 assert_eq!(
904 vec![
905 DevfsSelector(fac::Devfs {
906 name: "composite-0".to_string(),
907 device_type: fac::DeviceType::Composite,
908 }),
909 DevfsSelector(fac::Devfs {
910 name: "dai-0".to_string(),
911 device_type: fac::DeviceType::Dai,
912 }),
913 DevfsSelector(fac::Devfs {
914 name: "output-0".to_string(),
915 device_type: fac::DeviceType::Output,
916 }),
917 ],
918 selectors
919 );
920 }
921
922 #[cfg(not(feature = "fdomain"))]
923 #[fuchsia::test]
924 async fn test_list_devfs_all_missing() {
925 let dev_class_vfs = pseudo_directory! {};
929
930 let execution_scope = vfs::execution_scope::ExecutionScope::new();
931 let dev_class = vfs::directory::serve_read_only(dev_class_vfs, execution_scope);
932 let selectors = list_devfs(&dev_class).await.unwrap();
933
934 assert_eq!(Vec::<DevfsSelector>::new(), selectors);
935 }
936
937 #[cfg(not(feature = "fdomain"))]
938 #[fuchsia::test]
939 async fn test_list_devfs_one_class_present() {
940 let placeholder = placeholder_node();
943
944 let dev_class_vfs = pseudo_directory! {
945 "dai" => pseudo_directory! {
946 "dai-0" => placeholder.clone(),
947 },
948 };
949
950 let execution_scope = vfs::execution_scope::ExecutionScope::new();
951 let dev_class = vfs::directory::serve_read_only(dev_class_vfs, execution_scope);
952 let selectors = list_devfs(&dev_class).await.unwrap();
953
954 assert_eq!(
955 vec![DevfsSelector(fac::Devfs {
956 name: "dai-0".to_string(),
957 device_type: fac::DeviceType::Dai,
958 })],
959 selectors
960 );
961 }
962
963 #[cfg(not(feature = "fdomain"))]
964 #[fuchsia::test]
965 async fn test_list_devfs_present_but_empty() {
966 let placeholder = placeholder_node();
972
973 let dev_class_vfs = pseudo_directory! {
974 "codec" => pseudo_directory! {},
975 "dai" => pseudo_directory! {
976 "dai-0" => placeholder.clone(),
977 },
978 };
979
980 let execution_scope = vfs::execution_scope::ExecutionScope::new();
981 let dev_class = vfs::directory::serve_read_only(dev_class_vfs, execution_scope);
982 let selectors = list_devfs(&dev_class).await.unwrap();
983
984 assert_eq!(
985 vec![DevfsSelector(fac::Devfs {
986 name: "dai-0".to_string(),
987 device_type: fac::DeviceType::Dai,
988 })],
989 selectors
990 );
991 }
992
993 #[cfg(not(feature = "fdomain"))]
994 #[fuchsia::test]
995 async fn test_list_devfs_unknown_class_ignored() {
996 let placeholder = placeholder_node();
1001
1002 let dev_class_vfs = pseudo_directory! {
1003 "dai" => pseudo_directory! {
1004 "dai-0" => placeholder.clone(),
1005 },
1006 "thermal" => pseudo_directory! {
1007 "thermal-0" => placeholder.clone(),
1008 },
1009 };
1010
1011 let execution_scope = vfs::execution_scope::ExecutionScope::new();
1012 let dev_class = vfs::directory::serve_read_only(dev_class_vfs, execution_scope);
1013 let selectors = list_devfs(&dev_class).await.unwrap();
1014
1015 assert_eq!(
1016 vec![DevfsSelector(fac::Devfs {
1017 name: "dai-0".to_string(),
1018 device_type: fac::DeviceType::Dai,
1019 })],
1020 selectors
1021 );
1022 }
1023
1024 fn serve_registry(
1025 #[cfg(feature = "fdomain")] client: &Arc<fdomain_client::Client>,
1026 devices: Vec<fadevice::Info>,
1027 ) -> fadevice::RegistryProxy {
1028 let devices = Arc::new(devices);
1029 spawn_stream_handler(
1030 #[cfg(feature = "fdomain")]
1031 client,
1032 move |request| {
1033 let devices = devices.clone();
1034 async move {
1035 match request {
1036 fadevice::RegistryRequest::WatchDevicesAdded { responder } => responder
1037 .send(Ok(&fadevice::RegistryWatchDevicesAddedResponse {
1038 devices: Some((*devices).clone()),
1039 ..Default::default()
1040 }))
1041 .unwrap(),
1042 _ => unimplemented!(),
1043 }
1044 }
1045 },
1046 )
1047 }
1048
1049 #[fuchsia::test]
1050 async fn test_list_registry() {
1051 #[cfg(feature = "fdomain")]
1052 let client = fdomain_local::local_client_empty();
1053 let devices = vec![
1054 fadevice::Info { token_id: Some(1), ..Default::default() },
1055 fadevice::Info { token_id: Some(2), ..Default::default() },
1056 fadevice::Info { token_id: Some(3), ..Default::default() },
1057 ];
1058
1059 let registry = serve_registry(
1060 #[cfg(feature = "fdomain")]
1061 &client,
1062 devices,
1063 );
1064
1065 let infos = list_registry(®istry).await.unwrap();
1066
1067 assert_eq!(
1068 infos,
1069 vec![
1070 Info::from(fadevice::Info { token_id: Some(1), ..Default::default() }),
1071 Info::from(fadevice::Info { token_id: Some(2), ..Default::default() }),
1072 Info::from(fadevice::Info { token_id: Some(3), ..Default::default() }),
1073 ]
1074 );
1075 }
1076
1077 #[test]
1078 fn test_unique_instance_id_display() {
1079 let id = UniqueInstanceId([
1080 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
1081 0x0e, 0x0f,
1082 ]);
1083 let expected = "000102030405060708090a0b0c0d0e0f";
1084 assert_eq!(id.to_string(), expected);
1085 }
1086}