1use crate::emulator::EMULATOR_ROOT_DRIVER_URL;
6use crate::host_realm::mpsc::Receiver;
7use anyhow::{Error, format_err};
8use cm_rust::push_box;
9use fidl::endpoints::ClientEnd;
10use fidl_fuchsia_bluetooth_host::{HostMarker, ReceiverMarker, ReceiverRequestStream};
11use fidl_fuchsia_component::{CreateChildArgs, RealmMarker, RealmProxy};
12use fidl_fuchsia_component_decl::{
13 Child, CollectionRef, ConfigOverride, ConfigSingleValue, ConfigValue, Durability, StartupMode,
14};
15use fidl_fuchsia_driver_test as fdt;
16use fidl_fuchsia_hardware_bluetooth as fhbt;
17use fidl_fuchsia_io as fio;
18use fidl_fuchsia_logger::LogSinkMarker;
19use fuchsia_async::TimeoutExt as _;
20use fuchsia_bluetooth::constants::{
21 BT_HOST, BT_HOST_COLLECTION, BT_HOST_URL, BT_SERVICE_DIR, INTEGRATION_TIMEOUT as WATCH_TIMEOUT,
22};
23use fuchsia_component::client::{Service, ServiceInstanceStream};
24use fuchsia_component::server::ServiceFs;
25use fuchsia_component_test::{
26 Capability, ChildOptions, LocalComponentHandles, RealmBuilder, RealmInstance, Ref, Route,
27 ScopedInstance,
28};
29use fuchsia_driver_test::{DriverTestRealmBuilder, DriverTestRealmInstance};
30use futures::channel::mpsc;
31use futures::{SinkExt, StreamExt, TryFutureExt, TryStreamExt};
32use std::sync::Arc;
33use std::sync::atomic::{AtomicUsize, Ordering};
34
35mod constants {
36 pub mod receiver {
37 pub const MONIKER: &str = "receiver";
38 }
39}
40
41pub async fn add_host_routes(
42 builder: &RealmBuilder,
43 to: impl Into<fuchsia_component_test::Ref> + Clone,
44) -> Result<(), Error> {
45 builder
47 .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
48 name: "fuchsia.bluetooth.HciCommandTimeout".parse()?,
49 value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::Uint16(10)),
50 }))
51 .await?;
52 builder
53 .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
54 name: "fuchsia.bluetooth.LegacyPairing".parse()?,
55 value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::Bool(false)),
56 }))
57 .await?;
58 builder
59 .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
60 name: "fuchsia.bluetooth.ScoOffloadPathIndex".parse()?,
61 value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::Uint8(6)),
62 }))
63 .await?;
64 builder
65 .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
66 name: "fuchsia.bluetooth.OverrideVendorCapabilitiesVersion".parse()?,
67 value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::Uint16(0)),
68 }))
69 .await?;
70 builder
71 .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
72 name: "fuchsia.power.SuspendEnabled".parse()?,
73 value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::Bool(false)),
74 }))
75 .await?;
76
77 let le_configs = vec![
78 "fuchsia.bluetooth.LeSlowAdvIntervalMin",
79 "fuchsia.bluetooth.LeSlowAdvIntervalMax",
80 "fuchsia.bluetooth.LeSlowAdvMaxTxPower",
81 "fuchsia.bluetooth.LeFastAdvIntervalMin",
82 "fuchsia.bluetooth.LeFastAdvIntervalMax",
83 "fuchsia.bluetooth.LeFastAdvMaxTxPower",
84 "fuchsia.bluetooth.LeVeryFastAdvIntervalMin",
85 "fuchsia.bluetooth.LeVeryFastAdvIntervalMax",
86 "fuchsia.bluetooth.LeVeryFastAdvMaxTxPower",
87 "fuchsia.bluetooth.LeActiveScanInterval",
88 "fuchsia.bluetooth.LeActiveScanWindow",
89 "fuchsia.bluetooth.LeBatchedScanningEnabled",
90 "fuchsia.bluetooth.LeScanBatchMaxReadDelaySeconds",
91 "fuchsia.bluetooth.LeScanOffloadFiltersEnabled",
92 ];
93
94 builder
95 .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
96 name: "fuchsia.bluetooth.LeSlowAdvIntervalMin".parse()?,
97 value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::Uint16(0)),
98 }))
99 .await?;
100 builder
101 .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
102 name: "fuchsia.bluetooth.LeSlowAdvIntervalMax".parse()?,
103 value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::Uint16(0)),
104 }))
105 .await?;
106 builder
107 .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
108 name: "fuchsia.bluetooth.LeSlowAdvMaxTxPower".parse()?,
109 value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::Int8(127)),
110 }))
111 .await?;
112 builder
113 .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
114 name: "fuchsia.bluetooth.LeFastAdvIntervalMin".parse()?,
115 value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::Uint16(0)),
116 }))
117 .await?;
118 builder
119 .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
120 name: "fuchsia.bluetooth.LeFastAdvIntervalMax".parse()?,
121 value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::Uint16(0)),
122 }))
123 .await?;
124 builder
125 .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
126 name: "fuchsia.bluetooth.LeFastAdvMaxTxPower".parse()?,
127 value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::Int8(127)),
128 }))
129 .await?;
130 builder
131 .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
132 name: "fuchsia.bluetooth.LeVeryFastAdvIntervalMin".parse()?,
133 value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::Uint16(0)),
134 }))
135 .await?;
136 builder
137 .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
138 name: "fuchsia.bluetooth.LeVeryFastAdvIntervalMax".parse()?,
139 value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::Uint16(0)),
140 }))
141 .await?;
142 builder
143 .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
144 name: "fuchsia.bluetooth.LeVeryFastAdvMaxTxPower".parse()?,
145 value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::Int8(127)),
146 }))
147 .await?;
148 builder
149 .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
150 name: "fuchsia.bluetooth.LeActiveScanInterval".parse()?,
151 value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::Uint16(0)),
152 }))
153 .await?;
154 builder
155 .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
156 name: "fuchsia.bluetooth.LeActiveScanWindow".parse()?,
157 value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::Uint16(0)),
158 }))
159 .await?;
160 builder
161 .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
162 name: "fuchsia.bluetooth.LeBatchedScanningEnabled".parse()?,
163 value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::Bool(false)),
164 }))
165 .await?;
166 builder
167 .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
168 name: "fuchsia.bluetooth.LeScanBatchMaxReadDelaySeconds".parse()?,
169 value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::Uint8(3)),
170 }))
171 .await?;
172 builder
173 .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
174 name: "fuchsia.bluetooth.LeScanOffloadFiltersEnabled".parse()?,
175 value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::Bool(false)),
176 }))
177 .await?;
178
179 builder
180 .add_capability(cm_rust::CapabilityDecl::Dictionary(cm_rust::DictionaryDecl {
181 name: "bluetooth-le-config".parse()?,
182 source_path: None,
183 }))
184 .await?;
185
186 for config in le_configs {
187 builder
188 .add_route(
189 Route::new()
190 .capability(Capability::configuration(config))
191 .from(Ref::self_())
192 .to(Ref::capability("bluetooth-le-config")),
193 )
194 .await?;
195 }
196
197 macro_rules! add_capability_route {
198 ($name:expr) => {
199 builder.add_route(
200 Route::new()
201 .capability(Capability::configuration($name))
202 .from(Ref::self_())
203 .to(to.clone()),
204 )
205 };
206 }
207
208 add_capability_route!("fuchsia.bluetooth.HciCommandTimeout").await?;
209 add_capability_route!("fuchsia.bluetooth.LegacyPairing").await?;
210 add_capability_route!("fuchsia.bluetooth.OverrideVendorCapabilitiesVersion").await?;
211 add_capability_route!("fuchsia.bluetooth.ScoOffloadPathIndex").await?;
212 add_capability_route!("fuchsia.power.SuspendEnabled").await?;
213
214 builder
215 .add_route(
216 Route::new()
217 .capability(Capability::dictionary("bluetooth-le-config"))
218 .from(Ref::self_())
219 .to(to.clone()),
220 )
221 .await?;
222 Ok(())
223}
224
225async fn resolve_test_component(
226 test_components: impl IntoIterator<Item = impl AsRef<str>>,
227) -> Result<fidl_fuchsia_component_resolution::Component, Error> {
228 let resolver = fuchsia_component::client::connect_to_protocol_at_path::<
231 fidl_fuchsia_component_resolution::ResolverMarker,
232 >("/svc/fuchsia.component.resolution.Resolver-hermetic")?;
233
234 let mut resolved_test_component = None;
235 let mut last_err = None;
236
237 for url in test_components {
238 let url_str = url.as_ref();
239 match resolver.resolve(url_str).await {
240 Ok(Ok(component)) => {
241 resolved_test_component = Some(component);
242 break;
243 }
244 Ok(Err(e)) => last_err = Some(format_err!("Failed to resolve {url_str}: {e:?}")),
245 Err(e) => last_err = Some(format_err!("FIDL error resolving {url_str}: {e:?}")),
246 }
247 }
248
249 resolved_test_component.ok_or_else(|| {
250 last_err.unwrap_or_else(|| format_err!("No test component candidate URLs provided"))
251 })
252}
253
254pub struct HostRealm {
255 realm: RealmInstance,
256 receiver: futures::lock::Mutex<Receiver<ClientEnd<HostMarker>>>,
257 service_watcher: futures::lock::Mutex<ServiceInstanceStream<fhbt::ServiceMarker>>,
258 next_host_id: AtomicUsize,
259}
260
261impl HostRealm {
262 pub async fn create(test_component: String) -> Result<Self, Error> {
263 Self::create_with_candidates([test_component]).await
264 }
265
266 pub async fn create_with_candidates(
269 test_components: impl IntoIterator<Item = impl AsRef<str>>,
270 ) -> Result<Self, Error> {
271 let resolved_test_component = resolve_test_component(test_components).await?;
272
273 let builder = RealmBuilder::new().await?;
274 let _ = builder.driver_test_realm_setup().await?;
275
276 let (sender, receiver) = mpsc::channel(128);
281 let host_receiver = builder
282 .add_local_child(
283 constants::receiver::MONIKER,
284 move |handles| {
285 let sender_clone = sender.clone();
286 Box::pin(Self::fake_receiver_component(sender_clone, handles))
287 },
288 ChildOptions::new().eager(),
289 )
290 .await?;
291
292 let mut realm_decl = builder.get_realm_decl().await?;
294 push_box(
295 &mut realm_decl.collections,
296 cm_rust::CollectionDecl {
297 name: BT_HOST_COLLECTION.parse().unwrap(),
298 durability: Durability::SingleRun,
299 environment: None,
300 allowed_offers: cm_types::AllowedOffers::StaticAndDynamic,
301 allow_long_names: false,
302 persistent_storage: None,
303 },
304 );
305 builder.replace_realm_decl(realm_decl).await.unwrap();
306
307 add_host_routes(&builder, Ref::collection(BT_HOST_COLLECTION.to_string())).await?;
308
309 let dtr_exposes = vec![Capability::service::<fhbt::ServiceMarker>().into()];
310 let _ = builder.driver_test_realm_add_dtr_exposes(&dtr_exposes).await?;
311
312 builder
314 .add_route(
315 Route::new()
316 .capability(Capability::service::<fhbt::ServiceMarker>())
317 .from(Ref::child(fuchsia_driver_test::COMPONENT_NAME))
318 .to(Ref::collection(BT_HOST_COLLECTION.to_string())),
319 )
320 .await?;
321 builder
322 .add_route(
323 Route::new()
324 .capability(Capability::protocol::<LogSinkMarker>())
325 .capability(Capability::dictionary("diagnostics"))
326 .from(Ref::parent())
327 .to(Ref::collection(BT_HOST_COLLECTION.to_string())),
328 )
329 .await?;
330 builder
331 .add_route(
332 Route::new()
333 .capability(Capability::protocol::<ReceiverMarker>())
334 .from(&host_receiver)
335 .to(Ref::collection(BT_HOST_COLLECTION.to_string())),
336 )
337 .await?;
338 builder
339 .add_route(
340 Route::new()
341 .capability(Capability::protocol::<RealmMarker>())
342 .from(Ref::framework())
343 .to(Ref::parent()),
344 )
345 .await?;
346
347 let instance = builder.build().await?;
348
349 let args = fdt::RealmArgs {
351 root_driver: Some(EMULATOR_ROOT_DRIVER_URL.to_string()),
352 software_devices: Some(vec![fidl_fuchsia_driver_test::SoftwareDevice {
353 device_name: "bt-hci-emulator".to_string(),
354 device_id: bind_fuchsia_platform::BIND_PLATFORM_DEV_DID_BT_HCI_EMULATOR,
355 }]),
356 test_component: Some(resolved_test_component),
357 dtr_exposes: Some(dtr_exposes),
358 ..Default::default()
359 };
360 instance.driver_test_realm_start(args).await?;
361
362 let service_watcher =
363 Service::open_from_dir(instance.root.get_exposed_dir(), fhbt::ServiceMarker)?
364 .watch()
365 .await?;
366
367 Ok(Self {
368 realm: instance,
369 receiver: futures::lock::Mutex::new(receiver),
370 service_watcher: futures::lock::Mutex::new(service_watcher),
371 next_host_id: AtomicUsize::new(0),
372 })
373 }
374
375 pub async fn create_bt_host_in_collection(
379 realm: &Arc<HostRealm>,
380 ) -> Result<ClientEnd<HostMarker>, Error> {
381 let instance = realm
382 .service_watcher
383 .lock()
384 .await
385 .try_next()
386 .map_err(Error::from)
387 .on_timeout(WATCH_TIMEOUT, || {
388 Err(format_err!(
389 "timed out waiting for fuchsia.hardware.bluetooth.Service instance"
390 ))
391 })
392 .await?
393 .ok_or_else(|| format_err!("fuchsia.hardware.bluetooth.Service watcher closed"))?;
394 let instance_name = instance.instance_name();
395 let id = realm.next_host_id.fetch_add(1, Ordering::SeqCst);
396 let component_name = format!("{BT_HOST}_{id}_{instance_name}"); let device_path = format!("{BT_SERVICE_DIR}/{instance_name}/vendor");
398 let collection_ref = CollectionRef { name: BT_HOST_COLLECTION.to_owned() };
399 let child_decl = Child {
400 name: Some(component_name),
401 url: Some(BT_HOST_URL.to_owned()),
402 startup: Some(StartupMode::Lazy),
403 config_overrides: Some(vec![ConfigOverride {
404 key: Some("device_path".to_string()),
405 value: Some(ConfigValue::Single(ConfigSingleValue::String(device_path))),
406 ..ConfigOverride::default()
407 }]),
408 ..Default::default()
409 };
410
411 let realm_proxy: RealmProxy =
412 realm.instance().connect_to_protocol_at_exposed_dir().unwrap();
413 let _ = realm_proxy
414 .create_child(&collection_ref, &child_decl, CreateChildArgs::default())
415 .await
416 .map_err(|e| format_err!("{e:?}"))?
417 .map_err(|e| format_err!("{e:?}"))?;
418
419 let host = realm.receiver.lock().await.next().await.unwrap();
420 Ok(host)
421 }
422
423 async fn fake_receiver_component(
424 sender: mpsc::Sender<ClientEnd<HostMarker>>,
425 handles: LocalComponentHandles,
426 ) -> Result<(), Error> {
427 let mut fs = ServiceFs::new();
428 let _ = fs.dir("svc").add_fidl_service(move |mut req_stream: ReceiverRequestStream| {
429 let mut sender_clone = sender.clone();
430 fuchsia_async::Task::local(async move {
431 let (host_server, _) =
432 req_stream.next().await.unwrap().unwrap().into_add_host().unwrap();
433 sender_clone.send(host_server).await.expect("Host sent successfully");
434 })
435 .detach()
436 });
437
438 let _ = fs.serve_connection(handles.outgoing_dir)?;
439 fs.collect::<()>().await;
440 Ok(())
441 }
442
443 pub fn instance(&self) -> &ScopedInstance {
444 &self.realm.root
445 }
446
447 pub fn dev(&self) -> Result<fio::DirectoryProxy, Error> {
448 self.realm.driver_test_realm_connect_to_dev()
449 }
450}