test_profile_server/
lib.rs

1// Copyright 2023 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 fidl::endpoints::ServerEnd;
6use fuchsia_bluetooth::types::{self as bt, PeerId};
7use futures::{Stream, StreamExt};
8use profile_client::ProfileClient;
9use std::pin::Pin;
10use std::task::{Context, Poll};
11use {fidl_fuchsia_bluetooth as fidl_bt, fidl_fuchsia_bluetooth_bredr as bredr};
12
13pub struct TestProfileServerEndpoints {
14    pub proxy: bredr::ProfileProxy,
15    pub client: ProfileClient,
16    pub test_server: TestProfileServer,
17}
18
19/// Used to specify the channel to expect on an incoming Connect message
20#[derive(Debug)]
21pub enum ConnectChannel {
22    L2CapPsm(u16),
23    RfcommChannel(u8), // Valid channels are 1-30
24}
25
26/// Holds all the server side resources associated with a `Profile`'s connection to
27/// fuchsia.bluetooth.bredr.Profile. Provides helper methods for common test related tasks.
28/// Some fields are optional because they are not populated until the Profile has completed
29/// registration.
30// TODO(b/333456020): Clean up `advertise_responder`
31pub struct TestProfileServer {
32    profile_request_stream: bredr::ProfileRequestStream,
33    search_results_proxy: Option<bredr::SearchResultsProxy>,
34    connection_receiver_proxy: Option<bredr::ConnectionReceiverProxy>,
35    advertise_responder: Option<bredr::ProfileAdvertiseResponder>,
36}
37
38impl From<bredr::ProfileRequestStream> for TestProfileServer {
39    fn from(profile_request_stream: bredr::ProfileRequestStream) -> Self {
40        Self {
41            profile_request_stream,
42            search_results_proxy: None,
43            connection_receiver_proxy: None,
44            advertise_responder: None,
45        }
46    }
47}
48
49impl TestProfileServer {
50    /// Create a new Profile proxy and stream, and create a profile client that wraps the proxy and a
51    /// test server that wraps the stream.
52    ///
53    /// If service_class_profile_id is Some, add a search for that service class.
54    ///
55    /// If service_definition is Some, advertise with that service definition.
56    ///
57    /// Returns a struct containing the proxy, profile client and test server.
58    pub fn new(
59        service_definition: Option<bredr::ServiceDefinition>,
60        service_class_profile_id: Option<bredr::ServiceClassProfileIdentifier>,
61    ) -> TestProfileServerEndpoints {
62        let (proxy, stream) = fidl::endpoints::create_proxy_and_stream::<bredr::ProfileMarker>();
63
64        let mut client = match service_definition {
65            None => ProfileClient::new(proxy.clone()),
66            Some(service_definition) => {
67                let channel_params = fidl_bt::ChannelParameters::default();
68                ProfileClient::advertise(proxy.clone(), vec![service_definition], channel_params)
69                    .expect("Failed to advertise.")
70            }
71        };
72
73        if let Some(service_class_profile_id) = service_class_profile_id {
74            client.add_search(service_class_profile_id, None).expect("Failed to search for peers.");
75        }
76
77        let test_server = TestProfileServer::from(stream);
78
79        TestProfileServerEndpoints { proxy, client, test_server }
80    }
81
82    pub async fn expect_search(&mut self) {
83        let request = self.profile_request_stream.next().await;
84        match request {
85            Some(Ok(bredr::ProfileRequest::Search { payload, .. })) => {
86                self.search_results_proxy = Some(payload.results.unwrap().into_proxy());
87            }
88            _ => panic!(
89                "unexpected result on profile request stream while waiting for search: {request:?}"
90            ),
91        }
92    }
93
94    pub async fn expect_advertise(&mut self) {
95        let request = self.profile_request_stream.next().await;
96        match request {
97            Some(Ok(bredr::ProfileRequest::Advertise { payload, responder, .. })) => {
98                self.connection_receiver_proxy = Some(payload.receiver.unwrap().into_proxy());
99                if let Some(_old_responder) = self.advertise_responder.replace(responder) {
100                    panic!("Got new advertise request before old request is complete.");
101                }
102            }
103            _ => panic!(
104                "unexpected result on profile request stream while waiting for advertisement: {request:?}"
105            ),
106        }
107    }
108
109    pub async fn expect_connect(
110        &mut self,
111        expected_channel: Option<ConnectChannel>,
112    ) -> bt::Channel {
113        let request = self.profile_request_stream.next().await;
114        match request {
115            Some(Ok(bredr::ProfileRequest::Connect { connection, responder, .. })) => {
116                match (expected_channel, connection) {
117                    (None, _) => {}
118                    (
119                        Some(ConnectChannel::L2CapPsm(expected_psm)),
120                        bredr::ConnectParameters::L2cap(bredr::L2capParameters {
121                            psm: psm_option,
122                            ..
123                        }),
124                    ) => assert_eq!(Some(expected_psm), psm_option),
125                    (
126                        Some(ConnectChannel::RfcommChannel(expected_channel)),
127                        bredr::ConnectParameters::Rfcomm(bredr::RfcommParameters {
128                            channel: channel_option,
129                            ..
130                        }),
131                    ) => assert_eq!(Some(expected_channel), channel_option),
132                    (expected_channel, connection) => {
133                        panic!("On connect, expected {expected_channel:?}, got {connection:?}")
134                    }
135                }
136
137                let (near_bt_channel, far_bt_channel) = bt::Channel::create();
138                let far_bredr_channel: bredr::Channel =
139                    far_bt_channel.try_into().expect("BT Channel into FIDL BREDR Channel");
140                responder.send(Ok(far_bredr_channel)).expect("Send channel");
141                near_bt_channel
142            }
143            _ => panic!(
144                "Unexpected result on profile request stream expecting connection: {request:?}",
145            ),
146        }
147    }
148
149    pub async fn expect_sco_connect(
150        &mut self,
151        expected_initiator: bool,
152    ) -> ServerEnd<bredr::ScoConnectionMarker> {
153        let request = self.profile_request_stream.next().await;
154        let connection = match request {
155            Some(Ok(bredr::ProfileRequest::ConnectSco {
156                payload: bredr::ProfileConnectScoRequest { initiator, connection, .. },
157                ..
158            })) if initiator == Some(expected_initiator) => connection,
159            Some(Ok(bredr::ProfileRequest::ConnectSco {
160                payload: bredr::ProfileConnectScoRequest { initiator, .. },
161                ..
162            })) => {
163                panic!("Got SCO connection request expected initatior: {expected_initiator:}, actual initiator: {initiator:?}");
164            }
165            _ => panic!(
166                "Unexpected result on profile request stream expecting SCO connection: {request:?}",
167            ),
168        };
169
170        connection.expect("Got no connection when expecting SCO connection.")
171    }
172
173    pub fn send_service_found(
174        &mut self,
175        peer_id: PeerId,
176        protocol_list: Option<Vec<bredr::ProtocolDescriptor>>,
177        attributes: Vec<bredr::Attribute>,
178    ) -> fidl::client::QueryResponseFut<()> {
179        let search_results_proxy = self.search_results_proxy.as_ref().expect("Search result proxy");
180        search_results_proxy.service_found(&peer_id.into(), protocol_list.as_deref(), &attributes)
181    }
182
183    pub fn send_connected(
184        &mut self,
185        peer_id: PeerId,
186        protocol_list: Vec<bredr::ProtocolDescriptor>,
187    ) -> bt::Channel {
188        let (near_bt_channel, far_bt_channel) = bt::Channel::create();
189        let far_bredr_channel: bredr::Channel =
190            far_bt_channel.try_into().expect("BT Channel into FIDL BREDR Channel");
191
192        let connection_receiver_proxy =
193            self.connection_receiver_proxy.as_ref().expect("Connection receiver proxy");
194        connection_receiver_proxy
195            .connected(&peer_id.into(), far_bredr_channel, &protocol_list)
196            .expect("Connected");
197
198        near_bt_channel
199    }
200}
201
202/// Expose the underlying ProfileRequestStream
203impl Stream for TestProfileServer {
204    type Item = Result<bredr::ProfileRequest, fidl::Error>;
205
206    fn poll_next(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Option<Self::Item>> {
207        let pinned_stream = Pin::new(&mut self.profile_request_stream);
208        pinned_stream.poll_next(context)
209    }
210}
211
212impl Drop for TestProfileServer {
213    fn drop(&mut self) {
214        // TODO(b/333456020): Clean-up to not store responder.
215        if let Some(responder) = self.advertise_responder.take() {
216            responder
217                .send(Ok(&bredr::ProfileAdvertiseResponse::default()))
218                .expect("Drop responder");
219        }
220    }
221}