1use fuchsia_bluetooth::types::PeerId;
6use futures::{Future, StreamExt};
7use log::{debug, info, warn};
8use std::collections::HashSet;
9use {fidl_fuchsia_bluetooth as fidl_bt, fidl_fuchsia_bluetooth_bredr as bredr};
10
11use crate::codec_id::CodecId;
12
13use super::{ConnectError, Connection};
14
15#[derive(Clone)]
16pub struct Connector {
17 proxy: bredr::ProfileProxy,
18 controller_codecs: HashSet<CodecId>,
19}
20
21fn common_sco_params() -> bredr::ScoConnectionParameters {
22 bredr::ScoConnectionParameters {
23 air_frame_size: Some(60), io_coding_format: Some(fidl_bt::AssignedCodingFormat::LinearPcm),
26 io_frame_size: Some(16),
27 io_pcm_data_format: Some(fidl_fuchsia_hardware_audio::SampleFormat::PcmSigned),
28 path: Some(bredr::DataPath::Offload),
29 ..Default::default()
30 }
31}
32
33fn sco_params_fallback() -> bredr::ScoConnectionParameters {
36 bredr::ScoConnectionParameters {
37 parameter_set: Some(bredr::HfpParameterSet::D1),
38 air_coding_format: Some(fidl_bt::AssignedCodingFormat::Cvsd),
39 io_bandwidth: Some(16000),
41 ..common_sco_params()
42 }
43}
44
45fn params_with_data_path(
46 sco_params: bredr::ScoConnectionParameters,
47 in_band_sco: bool,
48) -> bredr::ScoConnectionParameters {
49 bredr::ScoConnectionParameters {
50 path: in_band_sco.then_some(bredr::DataPath::Host).or(Some(bredr::DataPath::Offload)),
51 ..sco_params
52 }
53}
54
55pub(crate) fn parameter_sets_for_codec(
57 codec_id: CodecId,
58 in_band_sco: bool,
59) -> Vec<bredr::ScoConnectionParameters> {
60 use bredr::HfpParameterSet::*;
61 match codec_id {
66 CodecId::MSBC => {
67 let (air_coding_format, io_bandwidth, io_coding_format) = if in_band_sco {
68 (
69 Some(fidl_bt::AssignedCodingFormat::Transparent),
70 Some(16000),
71 Some(fidl_bt::AssignedCodingFormat::Transparent),
72 )
73 } else {
74 (
76 Some(fidl_bt::AssignedCodingFormat::Msbc),
77 Some(32000),
78 Some(fidl_bt::AssignedCodingFormat::LinearPcm),
79 )
80 };
81 let params_fn = |set| bredr::ScoConnectionParameters {
82 parameter_set: Some(set),
83 air_coding_format,
84 io_coding_format,
85 io_bandwidth,
86 ..params_with_data_path(common_sco_params(), in_band_sco)
87 };
88 vec![params_fn(T2)]
91 }
92 _ => {
94 let (io_bandwidth, io_frame_size, io_coding_format) = if in_band_sco {
95 (Some(16000), Some(8), Some(fidl_bt::AssignedCodingFormat::Cvsd))
96 } else {
97 (Some(16000), Some(16), Some(fidl_bt::AssignedCodingFormat::LinearPcm))
98 };
99
100 let params_fn = |set| bredr::ScoConnectionParameters {
101 parameter_set: Some(set),
102 io_bandwidth,
103 io_frame_size,
104 io_coding_format,
105 ..params_with_data_path(sco_params_fallback(), in_band_sco)
106 };
107 vec![params_fn(S4), params_fn(S1), params_fn(D1)]
108 }
109 }
110}
111
112#[derive(Debug, Clone, PartialEq, Copy)]
113enum InitiatorRole {
114 Initiate,
115 Accept,
116}
117
118impl Connector {
119 pub fn build(proxy: bredr::ProfileProxy, controller_codecs: HashSet<CodecId>) -> Self {
120 Self { proxy, controller_codecs }
121 }
122
123 async fn setup_sco_connection(
124 profile_proxy: bredr::ProfileProxy,
125 peer_id: PeerId,
126 role: InitiatorRole,
127 params: Vec<bredr::ScoConnectionParameters>,
128 ) -> Result<Connection, ConnectError> {
129 let (connection_proxy, server) =
130 fidl::endpoints::create_proxy::<bredr::ScoConnectionMarker>();
131 profile_proxy.connect_sco(bredr::ProfileConnectScoRequest {
132 peer_id: Some(peer_id.into()),
133 initiator: Some(role == InitiatorRole::Initiate),
134 params: Some(params),
135 connection: Some(server),
136 ..Default::default()
137 })?;
138
139 match connection_proxy.take_event_stream().next().await {
140 Some(Ok(bredr::ScoConnectionEvent::OnConnectionComplete { payload })) => {
141 match payload {
142 bredr::ScoConnectionOnConnectionCompleteRequest::ConnectedParams(params) => {
143 let params =
144 params.try_into().map_err(|_| ConnectError::InvalidArguments)?;
145 Ok(Connection { peer_id, params, proxy: connection_proxy })
146 }
147 bredr::ScoConnectionOnConnectionCompleteRequest::Error(err) => Err(err.into()),
148 _ => {
149 warn!("Received unknown ScoConnectionOnConnectionCompleteRequest");
150 Err(ConnectError::Canceled)
151 }
152 }
153 }
154 Some(Ok(bredr::ScoConnectionEvent::_UnknownEvent { .. })) => {
155 warn!("Received unknown ScoConnectionEvent");
156 Err(ConnectError::Canceled)
157 }
158 Some(Err(e)) => Err(e.into()),
159 None => Err(ConnectError::Canceled),
160 }
161 }
162
163 fn parameters_for_codecs(&self, codecs: Vec<CodecId>) -> Vec<bredr::ScoConnectionParameters> {
164 codecs
165 .into_iter()
166 .map(|id| parameter_sets_for_codec(id, !self.controller_codecs.contains(&id)))
167 .flatten()
168 .collect()
169 }
170
171 pub fn connect(
172 &self,
173 peer_id: PeerId,
174 codecs: Vec<CodecId>,
175 ) -> impl Future<Output = Result<Connection, ConnectError>> + 'static {
176 let params = self.parameters_for_codecs(codecs);
177 info!(peer_id:%, params:?; "Initiating SCO connection");
178
179 let proxy = self.proxy.clone();
180 async move {
181 for param in params {
182 let result = Self::setup_sco_connection(
183 proxy.clone(),
184 peer_id,
185 InitiatorRole::Initiate,
186 vec![param.clone()],
187 )
188 .await;
189 match &result {
190 Err(ConnectError::Fidl { .. }) | Ok(_) => return result,
192 Err(e) => {
194 debug!(peer_id:%, param:?, e:?; "Connection failed, trying next set..")
195 }
196 }
197 }
198 info!(peer_id:%; "Exhausted SCO connection parameters");
199 Err(ConnectError::Failed)
200 }
201 }
202
203 pub fn accept(
204 &self,
205 peer_id: PeerId,
206 codecs: Vec<CodecId>,
207 ) -> impl Future<Output = Result<Connection, ConnectError>> + 'static {
208 let params = self.parameters_for_codecs(codecs);
209 info!(peer_id:%, params:?; "Accepting SCO connection");
210
211 let proxy = self.proxy.clone();
212 Self::setup_sco_connection(proxy, peer_id, InitiatorRole::Accept, params)
213 }
214}
215
216#[cfg(test)]
217pub mod test {
218 use super::*;
219
220 use fidl_fuchsia_bluetooth_bredr::HfpParameterSet;
221
222 #[track_caller]
223 pub fn connection_for_codec(
224 peer_id: PeerId,
225 codec_id: CodecId,
226 in_band: bool,
227 ) -> (Connection, bredr::ScoConnectionRequestStream) {
228 let sco_params = parameter_sets_for_codec(codec_id, in_band).pop().unwrap();
229 let (proxy, stream) =
230 fidl::endpoints::create_proxy_and_stream::<bredr::ScoConnectionMarker>();
231 let connection = Connection::build(peer_id, sco_params, proxy);
232 (connection, stream)
233 }
234
235 #[fuchsia::test]
236 fn codec_parameters() {
237 let _exec = fuchsia_async::TestExecutor::new();
238 let all_codecs = vec![CodecId::MSBC, CodecId::CVSD];
239
240 let test_profile_server::TestProfileServerEndpoints { proxy: profile_svc, .. } =
242 test_profile_server::TestProfileServer::new(None, None);
243 let sco = Connector::build(profile_svc.clone(), all_codecs.iter().cloned().collect());
244 let res = sco.parameters_for_codecs(all_codecs.clone());
245 assert_eq!(res.len(), 4);
246 assert_eq!(
247 res,
248 vec![
249 bredr::ScoConnectionParameters {
250 parameter_set: Some(HfpParameterSet::T2),
251 air_coding_format: Some(fidl_bt::AssignedCodingFormat::Msbc),
252 io_bandwidth: Some(32000),
253 path: Some(bredr::DataPath::Offload),
254 ..common_sco_params()
255 },
256 bredr::ScoConnectionParameters {
257 parameter_set: Some(HfpParameterSet::S4),
258 path: Some(bredr::DataPath::Offload),
259 ..sco_params_fallback()
260 },
261 bredr::ScoConnectionParameters {
262 parameter_set: Some(HfpParameterSet::S1),
263 path: Some(bredr::DataPath::Offload),
264 ..sco_params_fallback()
265 },
266 bredr::ScoConnectionParameters {
267 path: Some(bredr::DataPath::Offload),
268 ..sco_params_fallback()
269 },
270 ]
271 );
272
273 let sco = Connector::build(profile_svc.clone(), HashSet::new());
275 let res = sco.parameters_for_codecs(all_codecs.clone());
276 assert_eq!(res.len(), 4);
277 assert_eq!(
278 res,
279 vec![
280 bredr::ScoConnectionParameters {
281 parameter_set: Some(HfpParameterSet::T2),
282 air_coding_format: Some(fidl_bt::AssignedCodingFormat::Transparent),
283 io_bandwidth: Some(16000),
284 io_coding_format: Some(fidl_bt::AssignedCodingFormat::Transparent),
285 path: Some(bredr::DataPath::Host),
286 ..common_sco_params()
287 },
288 bredr::ScoConnectionParameters {
289 parameter_set: Some(HfpParameterSet::S4),
290 io_coding_format: Some(fidl_bt::AssignedCodingFormat::Cvsd),
291 io_frame_size: Some(8),
292 path: Some(bredr::DataPath::Host),
293 ..sco_params_fallback()
294 },
295 bredr::ScoConnectionParameters {
296 parameter_set: Some(HfpParameterSet::S1),
297 io_coding_format: Some(fidl_bt::AssignedCodingFormat::Cvsd),
298 io_frame_size: Some(8),
299 path: Some(bredr::DataPath::Host),
300 ..sco_params_fallback()
301 },
302 bredr::ScoConnectionParameters {
303 io_coding_format: Some(fidl_bt::AssignedCodingFormat::Cvsd),
304 io_frame_size: Some(8),
305 path: Some(bredr::DataPath::Host),
306 ..sco_params_fallback()
307 },
308 ]
309 );
310
311 let only_cvsd_set = [CodecId::CVSD].iter().cloned().collect();
313 let sco = Connector::build(profile_svc.clone(), only_cvsd_set);
314 let res = sco.parameters_for_codecs(all_codecs);
315 assert_eq!(res.len(), 4);
316 assert_eq!(
317 res,
318 vec![
319 bredr::ScoConnectionParameters {
320 parameter_set: Some(HfpParameterSet::T2),
321 air_coding_format: Some(fidl_bt::AssignedCodingFormat::Transparent),
322 io_bandwidth: Some(16000),
323 io_coding_format: Some(fidl_bt::AssignedCodingFormat::Transparent),
324 path: Some(bredr::DataPath::Host),
325 ..common_sco_params()
326 },
327 bredr::ScoConnectionParameters {
328 parameter_set: Some(HfpParameterSet::S4),
329 path: Some(bredr::DataPath::Offload),
330 ..sco_params_fallback()
331 },
332 bredr::ScoConnectionParameters {
333 parameter_set: Some(HfpParameterSet::S1),
334 path: Some(bredr::DataPath::Offload),
335 ..sco_params_fallback()
336 },
337 bredr::ScoConnectionParameters {
338 path: Some(bredr::DataPath::Offload),
339 ..sco_params_fallback()
340 },
341 ]
342 );
343 }
344}