fuchsia_audio_dai/
test.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use anyhow::format_err;
6use derivative::Derivative;
7use fidl_fuchsia_hardware_audio::*;
8use fuchsia_async as fasync;
9use fuchsia_sync::Mutex;
10use futures::{Future, StreamExt};
11use log::warn;
12use std::sync::Arc;
13use vfs::directory::entry_container::Directory;
14use vfs::{pseudo_directory, service};
15
16use crate::DigitalAudioInterface;
17use crate::driver::{ensure_dai_format_is_supported, ensure_pcm_format_is_supported};
18
19/// The status of the current device.  Retrievable via `TestHandle::status`.
20#[derive(Derivative, Clone, Debug)]
21#[derivative(Default)]
22pub enum TestStatus {
23    #[derivative(Default)]
24    Idle,
25    Configured {
26        dai_format: DaiFormat,
27        pcm_format: PcmFormat,
28    },
29    Started {
30        dai_format: DaiFormat,
31        pcm_format: PcmFormat,
32    },
33}
34
35impl TestStatus {
36    fn start(&mut self) -> Result<(), ()> {
37        if let Self::Configured { dai_format, pcm_format } = self {
38            *self = Self::Started { dai_format: *dai_format, pcm_format: *pcm_format };
39            Ok(())
40        } else {
41            Err(())
42        }
43    }
44
45    fn stop(&mut self) {
46        if let Self::Started { dai_format, pcm_format } = *self {
47            *self = Self::Configured { dai_format, pcm_format };
48        }
49    }
50}
51
52#[derive(Clone)]
53pub struct TestHandle(Arc<Mutex<TestStatus>>);
54
55impl TestHandle {
56    pub fn new() -> Self {
57        Self(Arc::new(Mutex::new(TestStatus::default())))
58    }
59
60    pub fn status(&self) -> TestStatus {
61        self.0.lock().clone()
62    }
63
64    pub fn is_started(&self) -> bool {
65        let lock = self.0.lock();
66        match *lock {
67            TestStatus::Started { .. } => true,
68            _ => false,
69        }
70    }
71
72    fn set_configured(&self, dai_format: DaiFormat, pcm_format: PcmFormat) {
73        let mut lock = self.0.lock();
74        *lock = TestStatus::Configured { dai_format, pcm_format };
75    }
76
77    fn start(&self) -> Result<(), ()> {
78        self.0.lock().start()
79    }
80
81    fn stop(&self) {
82        self.0.lock().stop()
83    }
84}
85
86/// Logs and breaks out of the loop if the result is an Error.
87macro_rules! log_error {
88    ($result:expr, $tag:expr) => {
89        if let Err(e) = $result {
90            warn!("Error sending {}: {:?}", $tag, e);
91            break;
92        }
93    };
94}
95
96async fn handle_ring_buffer(mut requests: RingBufferRequestStream, handle: TestHandle) {
97    while let Some(req) = requests.next().await {
98        if let Err(e) = req {
99            warn!("Error processing RingBuffer request stream: {:?}", e);
100            break;
101        }
102        match req.unwrap() {
103            RingBufferRequest::Start { responder } => match handle.start() {
104                Ok(()) => log_error!(responder.send(0), "ring buffer start response"),
105                Err(()) => {
106                    warn!("Started when we couldn't expect it, shutting down");
107                }
108            },
109            RingBufferRequest::Stop { responder } => {
110                handle.stop();
111                log_error!(responder.send(), "ring buffer stop response");
112            }
113            x => unimplemented!("RingBuffer Request not implemented: {:?}", x),
114        };
115    }
116}
117
118async fn test_handle_dai_requests(
119    dai_formats: DaiSupportedFormats,
120    pcm_formats: SupportedFormats,
121    as_input: bool,
122    mut requests: DaiRequestStream,
123    handle: TestHandle,
124) {
125    use std::slice::from_ref;
126    let properties = DaiProperties {
127        is_input: Some(as_input),
128        manufacturer: Some("Fuchsia".to_string()),
129        ..Default::default()
130    };
131
132    #[allow(clippy::collection_is_never_read)]
133    let mut _rb_task = None;
134    while let Some(req) = requests.next().await {
135        if let Err(e) = req {
136            warn!("Error processing DAI request stream: {:?}", e);
137            break;
138        }
139        match req.unwrap() {
140            DaiRequest::GetProperties { responder } => {
141                log_error!(responder.send(&properties), "properties response");
142            }
143            DaiRequest::GetDaiFormats { responder } => {
144                log_error!(responder.send(Ok(from_ref(&dai_formats))), "formats response");
145            }
146            DaiRequest::GetRingBufferFormats { responder } => {
147                log_error!(responder.send(Ok(from_ref(&pcm_formats))), "pcm response");
148            }
149            DaiRequest::CreateRingBuffer {
150                dai_format, ring_buffer_format, ring_buffer, ..
151            } => {
152                let shutdown_bad_args =
153                    |server: fidl::endpoints::ServerEnd<RingBufferMarker>, err: anyhow::Error| {
154                        warn!("CreateRingBuffer: {:?}", err);
155                        if let Err(e) = server.close_with_epitaph(zx::Status::INVALID_ARGS) {
156                            warn!("Couldn't send ring buffer epitaph: {:?}", e);
157                        }
158                    };
159                if let Err(e) = ensure_dai_format_is_supported(from_ref(&dai_formats), &dai_format)
160                {
161                    shutdown_bad_args(ring_buffer, e);
162                    continue;
163                }
164                let pcm_format = match ring_buffer_format.pcm_format {
165                    Some(f) => f,
166                    None => {
167                        shutdown_bad_args(ring_buffer, format_err!("Only PCM format supported"));
168                        continue;
169                    }
170                };
171                if let Err(e) = ensure_pcm_format_is_supported(from_ref(&pcm_formats), &pcm_format)
172                {
173                    shutdown_bad_args(ring_buffer, e);
174                    continue;
175                }
176                handle.set_configured(dai_format, pcm_format);
177                let requests = ring_buffer.into_stream();
178                _rb_task = Some(fasync::Task::spawn(handle_ring_buffer(requests, handle.clone())));
179            }
180            x => unimplemented!("DAI request not implemented: {:?}", x),
181        };
182    }
183}
184
185/// Represents a mock DAI device that processes `Dai` requests from the provided `requests`
186/// stream.
187/// Returns a Future representing the processing task and a `TestHandle` which can be used
188/// to validate behavior.
189fn mock_dai_device(
190    as_input: bool,
191    requests: DaiRequestStream,
192) -> (impl Future<Output = ()>, TestHandle) {
193    let supported_dai_formats = DaiSupportedFormats {
194        number_of_channels: vec![1],
195        sample_formats: vec![DaiSampleFormat::PcmSigned],
196        frame_formats: vec![DaiFrameFormat::FrameFormatStandard(DaiFrameFormatStandard::Tdm1)],
197        frame_rates: vec![8000, 16000, 32000, 48000, 96000],
198        bits_per_slot: vec![16],
199        bits_per_sample: vec![16],
200    };
201
202    let number_of_channels = 1usize;
203    let attributes = vec![ChannelAttributes::default(); number_of_channels];
204    let channel_set = ChannelSet { attributes: Some(attributes), ..Default::default() };
205    let supported_pcm_formats = SupportedFormats {
206        pcm_supported_formats: Some(PcmSupportedFormats {
207            channel_sets: Some(vec![channel_set]),
208            sample_formats: Some(vec![SampleFormat::PcmSigned]),
209            bytes_per_sample: Some(vec![2]),
210            valid_bits_per_sample: Some(vec![16]),
211            frame_rates: Some(vec![8000, 16000, 32000, 48000, 96000]),
212            ..Default::default()
213        }),
214        ..Default::default()
215    };
216
217    let handle = TestHandle::new();
218
219    let handler_fut = test_handle_dai_requests(
220        supported_dai_formats,
221        supported_pcm_formats,
222        as_input,
223        requests,
224        handle.clone(),
225    );
226    (handler_fut, handle)
227}
228
229/// Builds and returns a DigitalAudioInterface for testing scenarios. Returns the
230/// `TestHandle` associated with this device which can be used to validate behavior.
231pub fn test_digital_audio_interface(as_input: bool) -> (DigitalAudioInterface, TestHandle) {
232    let (proxy, requests) = fidl::endpoints::create_proxy_and_stream::<DaiMarker>();
233
234    let (handler, handle) = mock_dai_device(as_input, requests);
235    fasync::Task::spawn(handler).detach();
236
237    (DigitalAudioInterface::from_proxy(proxy), handle)
238}
239
240async fn handle_dai_connect_requests(as_input: bool, mut stream: DaiConnectorRequestStream) {
241    while let Some(request) = stream.next().await {
242        if let Ok(DaiConnectorRequest::Connect { dai_protocol, .. }) = request {
243            let (handler, _test_handle) = mock_dai_device(as_input, dai_protocol.into_stream());
244            fasync::Task::spawn(handler).detach();
245        }
246    }
247}
248
249/// Builds and returns a VFS with a mock input and output DAI device.
250pub fn mock_dai_dev_with_io_devices(input: String, output: String) -> Arc<dyn Directory> {
251    pseudo_directory! {
252        "class" => pseudo_directory! {
253            "dai" => pseudo_directory! {
254                &input => service::host(
255                    move |stream: DaiConnectorRequestStream| handle_dai_connect_requests(true,
256                                                                                         stream)
257                ),
258                &output => service::host(
259                    move |stream: DaiConnectorRequestStream| handle_dai_connect_requests(false,
260                                                                                         stream)
261                ),
262            }
263        }
264    }
265}