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