Skip to main content

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