bt_test_harness/
access.rs1use anyhow::{Context, Error};
6use fidl_fuchsia_bluetooth_sys::{
7 AccessConnectResult, AccessDisconnectResult, AccessMakeDiscoverableResult, AccessMarker,
8 AccessProxy, AccessStartDiscoveryResult, ProcedureTokenMarker,
9};
10use fuchsia_bluetooth::expectation::asynchronous::{
11 expectable, Expectable, ExpectableExt, ExpectableState,
12};
13use fuchsia_bluetooth::types::{Peer, PeerId};
14use futures::future::{self, BoxFuture, FutureExt};
15use log::warn;
16use std::collections::HashMap;
17use std::ops::{Deref, DerefMut};
18use std::sync::Arc;
19use test_harness::{SharedState, TestHarness, SHARED_STATE_TEST_COMPONENT_INDEX};
20
21use crate::core_realm::{CoreRealm, SHARED_STATE_INDEX};
22
23#[derive(Clone)]
29pub struct AccessWrapper(AccessProxy);
30
31impl AccessWrapper {
32 pub fn start_discovery(
33 &self,
34 token: fidl::endpoints::ServerEnd<ProcedureTokenMarker>,
35 ) -> fidl::client::QueryResponseFut<AccessStartDiscoveryResult> {
36 self.0.start_discovery(token)
37 }
38
39 pub fn make_discoverable(
40 &self,
41 token: fidl::endpoints::ServerEnd<ProcedureTokenMarker>,
42 ) -> fidl::client::QueryResponseFut<AccessMakeDiscoverableResult> {
43 self.0.make_discoverable(token)
44 }
45
46 pub fn connect(
47 &self,
48 id: &mut fidl_fuchsia_bluetooth::PeerId,
49 ) -> fidl::client::QueryResponseFut<AccessConnectResult> {
50 self.0.connect(id)
51 }
52
53 pub fn disconnect(
54 &self,
55 id: &mut fidl_fuchsia_bluetooth::PeerId,
56 ) -> fidl::client::QueryResponseFut<AccessDisconnectResult> {
57 self.0.disconnect(id)
58 }
59
60 pub fn set_local_name(&self, name: &str) -> Result<(), fidl::Error> {
61 self.0.set_local_name(name)
62 }
63}
64
65#[derive(Clone, Default)]
66pub struct AccessState {
67 pub peers: HashMap<PeerId, Peer>,
69}
70
71#[derive(Clone)]
72pub struct AccessHarness(Expectable<AccessState, AccessWrapper>);
73
74impl Deref for AccessHarness {
75 type Target = Expectable<AccessState, AccessWrapper>;
76
77 fn deref(&self) -> &Self::Target {
78 &self.0
79 }
80}
81
82impl DerefMut for AccessHarness {
83 fn deref_mut(&mut self) -> &mut Self::Target {
84 &mut self.0
85 }
86}
87
88async fn update_peer_state(harness: &AccessHarness) -> Result<(), Error> {
89 let access = harness.aux().clone();
90 let (updated, removed) =
91 access.0.watch_peers().await.context("Error calling Access.watch_peers()")?;
92 for peer in updated.into_iter() {
93 let peer: Peer = peer.try_into().context("Invalid peer received from WatchPeers()")?;
94 let _ = harness.write_state().peers.insert(peer.id, peer);
95 }
96 for id in removed.into_iter() {
97 let id = id.into();
98 if harness.write_state().peers.remove(&id).is_none() {
99 warn!(id:%; "Unknown peer removed from peer state");
100 }
101 }
102 harness.notify_state_changed();
103 Ok(())
104}
105
106async fn run_peer_watcher(harness: AccessHarness) -> Result<(), Error> {
107 loop {
108 update_peer_state(&harness).await?;
109 }
110}
111
112impl TestHarness for AccessHarness {
113 type Env = Arc<CoreRealm>;
114 type Runner = BoxFuture<'static, Result<(), Error>>;
115
116 fn init(
117 shared_state: &Arc<SharedState>,
118 ) -> BoxFuture<'static, Result<(Self, Self::Env, Self::Runner), Error>> {
119 let shared_state = shared_state.clone();
120 async move {
121 let test_component: Arc<String> = shared_state
122 .get(SHARED_STATE_TEST_COMPONENT_INDEX)
123 .expect("SharedState must have TEST-COMPONENT")?;
124 let inserter = move || CoreRealm::create(test_component.to_string());
125 let realm = shared_state.get_or_insert_with(SHARED_STATE_INDEX, inserter).await?;
126 let access = realm
127 .instance()
128 .connect_to_protocol_at_exposed_dir::<AccessMarker>()
129 .context("Failed to connect to access service")?;
130
131 let harness = AccessHarness(expectable(Default::default(), AccessWrapper(access)));
132
133 update_peer_state(&harness).await?;
135 let run_access = run_peer_watcher(harness.clone()).boxed();
136 Ok((harness, realm, run_access))
137 }
138 .boxed()
139 }
140 fn terminate(_env: Self::Env) -> BoxFuture<'static, Result<(), Error>> {
141 future::ok(()).boxed()
142 }
143}
144
145pub mod expectation {
146 use super::*;
147 use crate::host_watcher::HostWatcherState;
148 use fuchsia_bluetooth::expectation::Predicate;
149 use fuchsia_bluetooth::types::{Address, HostId, HostInfo, Peer, PeerId, Uuid};
150
151 mod peer {
152 use super::*;
153
154 pub(crate) fn exists(p: Predicate<Peer>) -> Predicate<AccessState> {
155 let msg = format!("peer exists satisfying {:?}", p);
156 Predicate::predicate(
157 move |state: &AccessState| state.peers.iter().any(|(_, d)| p.satisfied(d)),
158 &msg,
159 )
160 }
161
162 pub(crate) fn with_identifier(id: PeerId) -> Predicate<Peer> {
163 Predicate::<Peer>::predicate(move |d| d.id == id, &format!("identifier == {}", id))
164 }
165
166 pub(crate) fn with_address(address: Address) -> Predicate<Peer> {
167 Predicate::<Peer>::predicate(
168 move |d| d.address == address,
169 &format!("address == {}", address),
170 )
171 }
172
173 pub(crate) fn connected(connected: bool) -> Predicate<Peer> {
174 Predicate::<Peer>::predicate(
175 move |d| d.connected == connected,
176 &format!("connected == {}", connected),
177 )
178 }
179
180 pub(crate) fn with_bredr_service(service_uuid: Uuid) -> Predicate<Peer> {
181 let msg = format!("bredr_services.contains({})", service_uuid.to_string());
182 Predicate::<Peer>::predicate(move |d| d.bredr_services.contains(&service_uuid), &msg)
183 }
184 }
185
186 mod host {
187 use super::*;
188
189 pub(crate) fn with_name<S: ToString>(name: S) -> Predicate<HostInfo> {
190 let name = name.to_string();
191 let msg = format!("name == {}", name);
192 Predicate::<HostInfo>::predicate(move |h| h.local_name.as_ref() == Some(&name), &msg)
193 }
194
195 pub(crate) fn with_id(id: HostId) -> Predicate<HostInfo> {
196 let msg = format!("id == {}", id);
197 Predicate::<HostInfo>::predicate(move |h| h.id == id, &msg)
198 }
199
200 pub(crate) fn discovering(is_discovering: bool) -> Predicate<HostInfo> {
201 let msg = format!("discovering == {}", is_discovering);
202 Predicate::<HostInfo>::predicate(move |h| h.discovering == is_discovering, &msg)
203 }
204
205 pub(crate) fn discoverable(is_discoverable: bool) -> Predicate<HostInfo> {
206 let msg = format!("discoverable == {}", is_discoverable);
207 Predicate::<HostInfo>::predicate(move |h| h.discoverable == is_discoverable, &msg)
208 }
209
210 pub(crate) fn exists(p: Predicate<HostInfo>) -> Predicate<HostWatcherState> {
211 let msg = format!("Host exists satisfying {:?}", p);
212 Predicate::predicate(
213 move |state: &HostWatcherState| state.hosts.values().any(|h| p.satisfied(h)),
214 &msg,
215 )
216 }
217 }
218
219 pub fn peer_connected(id: PeerId, connected: bool) -> Predicate<AccessState> {
220 peer::exists(peer::with_identifier(id).and(peer::connected(connected)))
221 }
222
223 pub fn peer_with_address(address: Address) -> Predicate<AccessState> {
224 peer::exists(peer::with_address(address))
225 }
226
227 pub fn host_with_name<S: ToString>(name: S) -> Predicate<HostWatcherState> {
228 host::exists(host::with_name(name))
229 }
230
231 pub fn peer_bredr_service_discovered(id: PeerId, service_uuid: Uuid) -> Predicate<AccessState> {
232 peer::exists(peer::with_identifier(id).and(peer::with_bredr_service(service_uuid)))
233 }
234
235 pub fn host_discovering(id: HostId, is_discovering: bool) -> Predicate<HostWatcherState> {
236 host::exists(host::with_id(id).and(host::discovering(is_discovering)))
237 }
238 pub fn host_discoverable(id: HostId, is_discoverable: bool) -> Predicate<HostWatcherState> {
239 host::exists(host::with_id(id).and(host::discoverable(is_discoverable)))
240 }
241}