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