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 =
669 fuchsia_fs::directory::open_directory(dev_class, subdir_name, fio::Flags::empty())
670 .await
671 .map_err(|err| ListDevfsError::Open { name: subdir_name.to_string(), err })?;
672 let entries = fuchsia_fs::directory::readdir(&subdir)
673 .await
674 .map_err(|err| ListDevfsError::Readdir { name: subdir_name.to_string(), err })?;
675 selectors.extend(entries.into_iter().map(|entry| {
676 DevfsSelector(fac::Devfs { name: entry.name, device_type: device_type.0 })
677 }));
678 }
679
680 Ok(selectors)
681}
682
683#[derive(Error, Debug)]
684pub enum ListRegistryError {
685 #[error(transparent)]
686 Fidl(#[from] fidl::Error),
687
688 #[error("failed to get devices: {:?}", .0)]
689 WatchDevicesAdded(fadevice::RegistryWatchDevicesAddedError),
690}
691
692pub async fn list_registry(
694 registry: &fadevice::RegistryProxy,
695) -> Result<Vec<Info>, ListRegistryError> {
696 Ok(registry
697 .watch_devices_added()
698 .await
699 .map_err(ListRegistryError::Fidl)?
700 .map_err(ListRegistryError::WatchDevicesAdded)?
701 .devices
702 .expect("missing devices")
703 .into_iter()
704 .map(Info::from)
705 .collect())
706}
707
708#[cfg(test)]
709mod test {
710 use super::*;
711 #[cfg(feature = "fdomain")]
712 use fidl_test_util::fdomain::spawn_stream_handler;
713 #[cfg(not(feature = "fdomain"))]
714 use fidl_test_util::spawn_stream_handler;
715 use std::sync::Arc;
716 use test_case::test_case;
717 #[cfg(not(feature = "fdomain"))]
718 use vfs::pseudo_directory;
719
720 #[test_case("composite", fac::DeviceType::Composite; "composite")]
721 #[test_case("input", fac::DeviceType::Input; "input")]
722 #[test_case("output", fac::DeviceType::Output; "output")]
723 fn test_parse_type(s: &str, expected_type: fac::DeviceType) {
724 assert_eq!(Type(expected_type), s.parse::<Type>().unwrap());
725 }
726
727 #[test]
728 fn test_parse_type_invalid() {
729 assert!("not a valid device type".parse::<Type>().is_err());
730 }
731
732 #[test_case("Codec", fhaudio::DeviceType::Codec; "Codec")]
733 #[test_case("Composite", fhaudio::DeviceType::Composite; "Composite")]
734 #[test_case("Dai", fhaudio::DeviceType::Dai; "Dai")]
735 #[test_case("StreamConfig", fhaudio::DeviceType::StreamConfig; "StreamConfig")]
736 fn test_parse_hardware_type(s: &str, expected_type: fhaudio::DeviceType) {
737 assert_eq!(HardwareType(expected_type), s.parse::<HardwareType>().unwrap());
738 }
739
740 #[test]
741 fn test_parse_hardware_type_invalid() {
742 assert!("not a valid hardware device type".parse::<Type>().is_err());
743 }
744
745 #[test_case(fhaudio::DeviceType::Codec, None, fac::DeviceType::Codec; "Codec")]
746 #[test_case(fhaudio::DeviceType::Composite, None, fac::DeviceType::Composite; "Composite")]
747 #[test_case(fhaudio::DeviceType::Dai, None, fac::DeviceType::Dai; "Dai")]
748 #[test_case(
749 fhaudio::DeviceType::StreamConfig,
750 Some(Direction::Input),
751 fac::DeviceType::Input;
752 "StreamConfig input"
753 )]
754 #[test_case(
755 fhaudio::DeviceType::StreamConfig,
756 Some(Direction::Output),
757 fac::DeviceType::Output;
758 "StreamConfig output"
759 )]
760 fn test_from_hardware_type_with_direction(
761 hardware_type: fhaudio::DeviceType,
762 direction: Option<Direction>,
763 expected_type: fac::DeviceType,
764 ) {
765 assert_eq!(
766 Type(expected_type),
767 (HardwareType(hardware_type), direction).try_into().unwrap()
768 )
769 }
770
771 #[test_case(
772 fac::Devfs { name: "3d99d780".to_string(), device_type: fac::DeviceType::Codec },
773 "/dev/class/codec/3d99d780";
774 "codec"
775 )]
776 #[test_case(
777 fac::Devfs { name: "3d99d780".to_string(), device_type: fac::DeviceType::Composite },
778 "/dev/class/audio-composite/3d99d780";
779 "composite"
780 )]
781 #[test_case(
782 fac::Devfs { name: "3d99d780".to_string(), device_type: fac::DeviceType::Dai },
783 "/dev/class/dai/3d99d780";
784 "dai"
785 )]
786 #[test_case(
787 fac::Devfs { name: "3d99d780".to_string(), device_type: fac::DeviceType::Input },
788 "/dev/class/audio-input/3d99d780";
789 "input"
790 )]
791 #[test_case(
792 fac::Devfs { name: "3d99d780".to_string(), device_type: fac::DeviceType::Output },
793 "/dev/class/audio-output/3d99d780";
794 "output"
795 )]
796 fn test_devfs_selector_path(devfs: fac::Devfs, expected_path: &str) {
797 assert_eq!(expected_path, DevfsSelector(devfs).path());
798 }
799
800 #[cfg(not(feature = "fdomain"))]
801 fn placeholder_node() -> Arc<vfs::service::Service> {
802 vfs::service::endpoint(move |_scope, _channel| {
803 })
805 }
806
807 #[cfg(not(feature = "fdomain"))]
808 #[fuchsia::test]
809 async fn test_list_devfs() {
810 let placeholder = placeholder_node();
813
814 let dev_class_vfs = pseudo_directory! {
815 "codec" => pseudo_directory! {
816 "codec-0" => placeholder.clone(),
817 },
818 "audio-composite" => pseudo_directory! {
819 "composite-0" => placeholder.clone(),
820 },
821 "dai" => pseudo_directory! {
822 "dai-0" => placeholder.clone(),
823 },
824 "audio-input" => pseudo_directory! {
825 "input-0" => placeholder.clone(),
826 "input-1" => placeholder.clone(),
827 },
828 "audio-output" => pseudo_directory! {
829 "output-0" => placeholder.clone(),
830 },
831 };
832
833 let dev_class = vfs::directory::serve_read_only(dev_class_vfs);
834 let selectors = list_devfs(&dev_class).await.unwrap();
835
836 assert_eq!(
837 vec![
838 DevfsSelector(fac::Devfs {
839 name: "codec-0".to_string(),
840 device_type: fac::DeviceType::Codec,
841 }),
842 DevfsSelector(fac::Devfs {
843 name: "composite-0".to_string(),
844 device_type: fac::DeviceType::Composite,
845 }),
846 DevfsSelector(fac::Devfs {
847 name: "dai-0".to_string(),
848 device_type: fac::DeviceType::Dai,
849 }),
850 DevfsSelector(fac::Devfs {
851 name: "input-0".to_string(),
852 device_type: fac::DeviceType::Input,
853 }),
854 DevfsSelector(fac::Devfs {
855 name: "input-1".to_string(),
856 device_type: fac::DeviceType::Input,
857 }),
858 DevfsSelector(fac::Devfs {
859 name: "output-0".to_string(),
860 device_type: fac::DeviceType::Output,
861 }),
862 ],
863 selectors
864 );
865 }
866
867 fn serve_registry(
868 #[cfg(feature = "fdomain")] client: &Arc<fdomain_client::Client>,
869 devices: Vec<fadevice::Info>,
870 ) -> fadevice::RegistryProxy {
871 let devices = Arc::new(devices);
872 spawn_stream_handler(
873 #[cfg(feature = "fdomain")]
874 client,
875 move |request| {
876 let devices = devices.clone();
877 async move {
878 match request {
879 fadevice::RegistryRequest::WatchDevicesAdded { responder } => responder
880 .send(Ok(&fadevice::RegistryWatchDevicesAddedResponse {
881 devices: Some((*devices).clone()),
882 ..Default::default()
883 }))
884 .unwrap(),
885 _ => unimplemented!(),
886 }
887 }
888 },
889 )
890 }
891
892 #[fuchsia::test]
893 async fn test_list_registry() {
894 #[cfg(feature = "fdomain")]
895 let client = fdomain_local::local_client_empty();
896 let devices = vec![
897 fadevice::Info { token_id: Some(1), ..Default::default() },
898 fadevice::Info { token_id: Some(2), ..Default::default() },
899 fadevice::Info { token_id: Some(3), ..Default::default() },
900 ];
901
902 let registry = serve_registry(
903 #[cfg(feature = "fdomain")]
904 &client,
905 devices,
906 );
907
908 let infos = list_registry(®istry).await.unwrap();
909
910 assert_eq!(
911 infos,
912 vec![
913 Info::from(fadevice::Info { token_id: Some(1), ..Default::default() }),
914 Info::from(fadevice::Info { token_id: Some(2), ..Default::default() }),
915 Info::from(fadevice::Info { token_id: Some(3), ..Default::default() }),
916 ]
917 );
918 }
919
920 #[test]
921 fn test_unique_instance_id_display() {
922 let id = UniqueInstanceId([
923 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
924 0x0e, 0x0f,
925 ]);
926 let expected = "000102030405060708090a0b0c0d0e0f";
927 assert_eq!(id.to_string(), expected);
928 }
929}