1use fidl::client::QueryResponseFut;
32use fidl::endpoints::create_request_stream;
33use fidl_fuchsia_bluetooth as fidl_bt;
34use fidl_fuchsia_bluetooth_bredr as bredr;
35use fuchsia_bluetooth::types::{Channel, PeerId};
36use futures::FutureExt;
37use futures::stream::{FusedStream, Stream, StreamExt};
38use futures::task::{Context, Poll, Waker};
39use log::trace;
40use std::pin::Pin;
41
42mod error;
44
45pub use crate::error::Error;
46
47pub type Result<T> = std::result::Result<T, Error>;
48
49#[derive(Debug)]
50pub enum ProfileEvent {
51 PeerConnected { id: PeerId, protocol: Vec<bredr::ProtocolDescriptor>, channel: Channel },
53 SearchResult {
55 id: PeerId,
56 protocol: Option<Vec<bredr::ProtocolDescriptor>>,
57 attributes: Vec<bredr::Attribute>,
58 },
59}
60
61impl ProfileEvent {
62 pub fn peer_id(&self) -> PeerId {
63 match self {
64 Self::PeerConnected { id, .. } => *id,
65 Self::SearchResult { id, .. } => *id,
66 }
67 }
68}
69
70impl TryFrom<bredr::SearchResultsRequest> for ProfileEvent {
71 type Error = Error;
72 fn try_from(value: bredr::SearchResultsRequest) -> Result<Self> {
73 let bredr::SearchResultsRequest::ServiceFound { peer_id, protocol, attributes, responder } =
74 value
75 else {
76 return Err(Error::search_result(fidl::Error::Invalid));
77 };
78 let id: PeerId = peer_id.into();
79 responder.send()?;
80 trace!(id:%, protocol:?, attributes:?; "Profile Search Result");
81 Ok(ProfileEvent::SearchResult { id, protocol, attributes })
82 }
83}
84
85impl TryFrom<bredr::ConnectionReceiverRequest> for ProfileEvent {
86 type Error = Error;
87 fn try_from(value: bredr::ConnectionReceiverRequest) -> Result<Self> {
88 let bredr::ConnectionReceiverRequest::Connected { peer_id, channel, protocol, .. } = value
89 else {
90 return Err(Error::connection_receiver(fidl::Error::Invalid));
91 };
92 let id = peer_id.into();
93 let channel = channel.try_into().map_err(Error::connection_receiver)?;
94 trace!(id:%, protocol:?; "Incoming connection");
95 Ok(ProfileEvent::PeerConnected { id, channel, protocol })
96 }
97}
98
99pub struct ProfileClient {
113 proxy: bredr::ProfileProxy,
115 advertisement: Option<QueryResponseFut<bredr::ProfileAdvertiseResult>>,
117 connection_receiver: Option<bredr::ConnectionReceiverRequestStream>,
118 searches: Vec<bredr::SearchResultsRequestStream>,
120 stream_waker: Option<Waker>,
122 terminated: bool,
124}
125
126impl ProfileClient {
127 pub fn new(proxy: bredr::ProfileProxy) -> Self {
129 Self {
130 proxy,
131 advertisement: None,
132 connection_receiver: None,
133 searches: Vec::new(),
134 stream_waker: None,
135 terminated: false,
136 }
137 }
138
139 pub fn advertise(
142 proxy: bredr::ProfileProxy,
143 services: Vec<bredr::ServiceDefinition>,
144 channel_params: fidl_bt::ChannelParameters,
145 ) -> Result<Self> {
146 if services.is_empty() {
147 return Ok(Self::new(proxy));
148 }
149 let (connect_client, connection_receiver) = create_request_stream();
150 let advertisement = proxy
151 .advertise(bredr::ProfileAdvertiseRequest {
152 services: Some(services),
153 parameters: Some(channel_params),
154 receiver: Some(connect_client),
155 ..Default::default()
156 })
157 .check()?;
158 Ok(Self {
159 advertisement: Some(advertisement),
160 connection_receiver: Some(connection_receiver),
161 ..Self::new(proxy)
162 })
163 }
164
165 pub fn add_search(
166 &mut self,
167 service_uuid: bredr::ServiceClassProfileIdentifier,
168 attributes: Option<Vec<u16>>,
169 ) -> Result<()> {
170 if self.terminated {
171 return Err(Error::AlreadyTerminated);
172 }
173
174 let (results_client, results_stream) = create_request_stream();
175 self.proxy.search(bredr::ProfileSearchRequest {
176 service_uuid: Some(service_uuid),
177 attr_ids: attributes,
178 results: Some(results_client),
179 ..Default::default()
180 })?;
181 self.searches.push(results_stream);
182
183 if let Some(waker) = self.stream_waker.take() {
184 waker.wake();
185 }
186 Ok(())
187 }
188
189 }
192
193impl FusedStream for ProfileClient {
194 fn is_terminated(&self) -> bool {
195 self.terminated
196 }
197}
198
199impl Stream for ProfileClient {
200 type Item = Result<ProfileEvent>;
201
202 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
203 if self.terminated {
204 panic!("Profile polled after terminated");
205 }
206
207 if let Some(advertisement) = self.advertisement.as_mut() {
208 if let Poll::Ready(_result) = advertisement.poll_unpin(cx) {
209 self.advertisement = None;
212 };
213 }
214
215 if let Some(receiver) = self.connection_receiver.as_mut() {
216 if let Poll::Ready(item) = receiver.poll_next_unpin(cx) {
217 match item {
218 Some(Ok(request)) => return Poll::Ready(Some(request.try_into())),
219 Some(Err(e)) => return Poll::Ready(Some(Err(Error::connection_receiver(e)))),
220 None => {
221 self.terminated = true;
222 return Poll::Ready(None);
223 }
224 };
225 };
226 }
227
228 for search in &mut self.searches {
229 if let Poll::Ready(item) = search.poll_next_unpin(cx) {
230 match item {
231 Some(Ok(request)) => return Poll::Ready(Some(request.try_into())),
232 Some(Err(e)) => return Poll::Ready(Some(Err(Error::search_result(e)))),
233 None => {
234 self.terminated = true;
235 return Poll::Ready(None);
236 }
237 }
238 }
239 }
240
241 self.stream_waker = Some(cx.waker().clone());
243 Poll::Pending
244 }
245}
246
247#[cfg(test)]
248mod tests {
249 use super::*;
250 use bt_channel_test_support::{Transport, create_test_channels};
251 use fidl::endpoints::create_proxy_and_stream;
252 use fuchsia_async as fasync;
253 use fuchsia_bluetooth::types::Uuid;
254 use futures::Future;
255 use futures_test::task::new_count_waker;
256 use std::pin::pin;
257 use test_case::test_case;
258
259 fn make_profile_service_definition(service_uuid: Uuid) -> bredr::ServiceDefinition {
260 bredr::ServiceDefinition {
261 service_class_uuids: Some(vec![service_uuid.into()]),
262 protocol_descriptor_list: Some(vec![
263 bredr::ProtocolDescriptor {
264 protocol: Some(bredr::ProtocolIdentifier::L2Cap),
265 params: Some(vec![bredr::DataElement::Uint16(bredr::PSM_AVDTP)]),
266 ..Default::default()
267 },
268 bredr::ProtocolDescriptor {
269 protocol: Some(bredr::ProtocolIdentifier::Avdtp),
270 params: Some(vec![bredr::DataElement::Uint16(0x0103)]), ..Default::default()
272 },
273 ]),
274 profile_descriptors: Some(vec![bredr::ProfileDescriptor {
275 profile_id: Some(bredr::ServiceClassProfileIdentifier::AdvancedAudioDistribution),
276 major_version: Some(1),
277 minor_version: Some(2),
278 ..Default::default()
279 }]),
280 ..Default::default()
281 }
282 }
283
284 #[test]
285 fn service_advertisement_result_is_no_op() {
286 let mut exec = fasync::TestExecutor::new();
287 let (proxy, mut profile_stream) = create_proxy_and_stream::<bredr::ProfileMarker>();
288
289 let source_uuid =
290 Uuid::new16(bredr::ServiceClassProfileIdentifier::AudioSource.into_primitive());
291 let defs = vec![make_profile_service_definition(source_uuid)];
292 let channel_params = fidl_bt::ChannelParameters {
293 channel_mode: Some(fidl_bt::ChannelMode::Basic),
294 ..Default::default()
295 };
296
297 let mut profile = ProfileClient::advertise(proxy, defs.clone(), channel_params.clone())
298 .expect("Advertise succeeds");
299
300 let (_connect_proxy, adv_responder) = expect_advertisement_registration(
301 &mut exec,
302 &mut profile_stream,
303 defs,
304 Some(channel_params.into()),
305 );
306
307 {
308 let event_fut = profile.next();
309 let mut event_fut = pin!(event_fut);
310 assert!(exec.run_until_stalled(&mut event_fut).is_pending());
311
312 adv_responder
315 .send(Ok(&bredr::ProfileAdvertiseResponse::default()))
316 .expect("able to respond");
317
318 match exec.run_until_stalled(&mut event_fut) {
319 Poll::Pending => {}
320 x => panic!("Expected pending but got {x:?}"),
321 };
322 }
323
324 assert!(!profile.is_terminated());
325 }
326
327 #[test_case(Transport::Socket ; "socket")]
328 #[test_case(Transport::Fidl ; "fidl")]
329 #[fuchsia::test]
330 fn connection_request_relayed_to_stream(transport: Transport) {
331 let mut exec = fasync::TestExecutor::new();
332 let (proxy, mut profile_stream) = create_proxy_and_stream::<bredr::ProfileMarker>();
333
334 let source_uuid =
335 Uuid::new16(bredr::ServiceClassProfileIdentifier::AudioSource.into_primitive());
336 let defs = vec![make_profile_service_definition(source_uuid)];
337 let channel_params = fidl_bt::ChannelParameters {
338 channel_mode: Some(fidl_bt::ChannelMode::Basic),
339 ..Default::default()
340 };
341
342 let mut profile = ProfileClient::advertise(proxy, defs.clone(), channel_params.clone())
343 .expect("Advertise succeeds");
344
345 let (connect_proxy, _adv_responder) = expect_advertisement_registration(
346 &mut exec,
347 &mut profile_stream,
348 defs,
349 Some(channel_params.into()),
350 );
351
352 let remote_peer = PeerId(12343);
353 {
354 let event_fut = profile.next();
355 let mut event_fut = pin!(event_fut);
356 assert!(exec.run_until_stalled(&mut event_fut).is_pending());
357
358 let (remote_chan, _local) = create_test_channels(transport);
359 connect_proxy
360 .connected(&remote_peer.into(), bredr::Channel::try_from(remote_chan).unwrap(), &[])
361 .expect("connection should work");
362
363 match exec.run_until_stalled(&mut event_fut) {
364 Poll::Ready(Some(Ok(ProfileEvent::PeerConnected { id, .. }))) => {
365 assert_eq!(id, remote_peer);
366 }
367 x => panic!("Expected an error from the advertisement, got {:?}", x),
368 };
369 }
370
371 drop(connect_proxy);
373
374 match exec.run_until_stalled(&mut profile.next()) {
375 Poll::Ready(None) => {}
376 x => panic!("Expected profile to end on advertisement drop, got {:?}", x),
377 };
378
379 assert!(profile.is_terminated());
380 }
381
382 #[track_caller]
383 fn expect_advertisement_registration(
384 exec: &mut fasync::TestExecutor,
385 profile_stream: &mut bredr::ProfileRequestStream,
386 expected_defs: Vec<bredr::ServiceDefinition>,
387 expected_params: Option<fidl_bt::ChannelParameters>,
388 ) -> (bredr::ConnectionReceiverProxy, bredr::ProfileAdvertiseResponder) {
389 match exec.run_until_stalled(&mut profile_stream.next()) {
390 Poll::Ready(Some(Ok(bredr::ProfileRequest::Advertise { payload, responder }))) => {
391 assert!(payload.services.is_some());
392 assert_eq!(payload.services.unwrap(), expected_defs);
393 assert_eq!(payload.parameters, expected_params);
394 assert!(payload.receiver.is_some());
395 (payload.receiver.unwrap().into_proxy(), responder)
396 }
397 x => panic!("Expected ready advertisement request, got {:?}", x),
398 }
399 }
400
401 #[track_caller]
402 fn expect_search_registration(
403 exec: &mut fasync::TestExecutor,
404 profile_stream: &mut bredr::ProfileRequestStream,
405 search_uuid: bredr::ServiceClassProfileIdentifier,
406 search_attrs: &[u16],
407 ) -> bredr::SearchResultsProxy {
408 match exec.run_until_stalled(&mut profile_stream.next()) {
409 Poll::Ready(Some(Ok(bredr::ProfileRequest::Search { payload, .. }))) => {
410 let bredr::ProfileSearchRequest {
411 service_uuid: Some(service_uuid),
412 attr_ids,
413 results: Some(results),
414 ..
415 } = payload
416 else {
417 panic!("invalid parameters");
418 };
419 let attr_ids = attr_ids.unwrap_or_default();
420 assert_eq!(&attr_ids[..], search_attrs);
421 assert_eq!(service_uuid, search_uuid);
422 results.into_proxy()
423 }
424 x => panic!("Expected ready request for a search, got: {:?}", x),
425 }
426 }
427
428 #[test]
429 fn responds_to_search_results() {
430 let mut exec = fasync::TestExecutor::new();
431 let (proxy, mut profile_stream) = create_proxy_and_stream::<bredr::ProfileMarker>();
432
433 let mut profile = ProfileClient::new(proxy);
434
435 let search_attrs = vec![bredr::ATTR_BLUETOOTH_PROFILE_DESCRIPTOR_LIST];
436
437 let source_uuid = bredr::ServiceClassProfileIdentifier::AudioSource;
438 profile
439 .add_search(source_uuid, Some(search_attrs.clone()))
440 .expect("adding search succeeds");
441
442 let sink_uuid = bredr::ServiceClassProfileIdentifier::AudioSink;
443 profile.add_search(sink_uuid, Some(search_attrs.clone())).expect("adding search succeeds");
444
445 let source_results_proxy = expect_search_registration(
447 &mut exec,
448 &mut profile_stream,
449 source_uuid,
450 &search_attrs[..],
451 );
452 let sink_results_proxy = expect_search_registration(
453 &mut exec,
454 &mut profile_stream,
455 sink_uuid,
456 &search_attrs[..],
457 );
458
459 let attributes = &[];
463 let found_peer_id = PeerId(1);
464 let results_fut =
465 source_results_proxy.service_found(&found_peer_id.into(), None, attributes);
466 let mut results_fut = pin!(results_fut);
467
468 match exec.run_until_stalled(&mut profile.next()) {
469 Poll::Ready(Some(Ok(ProfileEvent::SearchResult { id, .. }))) => {
470 assert_eq!(found_peer_id, id);
471 }
472 x => panic!("Expected search result to be ready: {:?}", x),
473 }
474
475 match exec.run_until_stalled(&mut results_fut) {
476 Poll::Ready(Ok(())) => {}
477 x => panic!("Expected a response from the source result, got {:?}", x),
478 };
479
480 let results_fut = sink_results_proxy.service_found(&found_peer_id.into(), None, attributes);
481 let mut results_fut = pin!(results_fut);
482
483 match exec.run_until_stalled(&mut profile.next()) {
484 Poll::Ready(Some(Ok(ProfileEvent::SearchResult { id, .. }))) => {
485 assert_eq!(found_peer_id, id);
486 }
487 x => panic!("Expected search result to be ready: {:?}", x),
488 }
489
490 match exec.run_until_stalled(&mut results_fut) {
491 Poll::Ready(Ok(())) => {}
492 x => panic!("Expected a response from the sink result, got {:?}", x),
493 };
494
495 drop(source_results_proxy);
497
498 match exec.run_until_stalled(&mut profile.next()) {
499 Poll::Ready(None) => {}
500 x => panic!("Expected profile to end on search result drop, got {:?}", x),
501 };
502
503 assert!(profile.is_terminated());
504
505 assert!(profile.add_search(sink_uuid, None).is_err());
507 }
508
509 #[test]
510 fn waker_gets_awoken_when_search_added() {
511 let mut exec = fasync::TestExecutor::new();
512 let (proxy, mut profile_stream) = create_proxy_and_stream::<bredr::ProfileMarker>();
513
514 let mut profile = ProfileClient::new(proxy);
515
516 let profile_fut = profile.next();
519
520 let (waker, profile_fut_wake_count) = new_count_waker();
521 let mut counting_ctx = Context::from_waker(&waker);
522
523 let profile_fut = pin!(profile_fut);
524 assert!(profile_fut.poll(&mut counting_ctx).is_pending());
525
526 let initial_count = profile_fut_wake_count.get();
529
530 let source_uuid = bredr::ServiceClassProfileIdentifier::AudioSource;
533 profile.add_search(source_uuid, None).expect("adding search succeeds");
534 let search_proxy =
535 expect_search_registration(&mut exec, &mut profile_stream, source_uuid, &[]);
536
537 let after_search_count = profile_fut_wake_count.get();
539 assert_eq!(after_search_count, initial_count + 1);
540
541 let attributes = &[];
543 let found_peer_id = PeerId(123);
544 let results_fut = search_proxy.service_found(&found_peer_id.into(), None, attributes);
545 let mut results_fut = pin!(results_fut);
546
547 match exec.run_until_stalled(&mut profile.next()) {
548 Poll::Ready(Some(Ok(ProfileEvent::SearchResult { id, .. }))) => {
549 assert_eq!(found_peer_id, id);
550 }
551 x => panic!("Expected search result to be ready: {:?}", x),
552 }
553
554 match exec.run_until_stalled(&mut results_fut) {
555 Poll::Ready(Ok(())) => {}
556 x => panic!("Expected a response from the source result, got {:?}", x),
557 };
558 }
559}