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