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
213pub struct HostRealm {
214 realm: RealmInstance,
215 receiver: Mutex<Option<Receiver<ClientEnd<HostMarker>>>>,
216}
217
218impl HostRealm {
219 pub async fn create(test_component: String) -> Result<Self, Error> {
220 let resolved_test_component = {
223 let client = fuchsia_component::client::connect_to_protocol_at_path::<
224 fidl_fuchsia_component_resolution::ResolverMarker,
225 >("/svc/fuchsia.component.resolution.Resolver-hermetic")
226 .unwrap();
227 client
228 .resolve(test_component.as_str())
229 .await
230 .unwrap()
231 .expect("Failed to resolve test component")
232 };
233
234 let builder = RealmBuilder::new().await?;
235 let _ = builder.driver_test_realm_setup().await?;
236
237 let (sender, receiver) = mpsc::channel(128);
242 let host_receiver = builder
243 .add_local_child(
244 constants::receiver::MONIKER,
245 move |handles| {
246 let sender_clone = sender.clone();
247 Box::pin(Self::fake_receiver_component(sender_clone, handles))
248 },
249 ChildOptions::new().eager(),
250 )
251 .await?;
252
253 let mut realm_decl = builder.get_realm_decl().await?;
255 push_box(
256 &mut realm_decl.collections,
257 cm_rust::CollectionDecl {
258 name: BT_HOST_COLLECTION.parse().unwrap(),
259 durability: Durability::SingleRun,
260 environment: None,
261 allowed_offers: cm_types::AllowedOffers::StaticAndDynamic,
262 allow_long_names: false,
263 persistent_storage: None,
264 },
265 );
266 builder.replace_realm_decl(realm_decl).await.unwrap();
267
268 add_host_routes(&builder, Ref::collection(BT_HOST_COLLECTION.to_string())).await?;
269
270 builder
272 .add_route(
273 Route::new()
274 .capability(Capability::protocol::<LogSinkMarker>())
275 .capability(Capability::dictionary("diagnostics"))
276 .from(Ref::parent())
277 .to(Ref::collection(BT_HOST_COLLECTION.to_string())),
278 )
279 .await?;
280 builder
281 .add_route(
282 Route::new()
283 .capability(Capability::protocol::<ReceiverMarker>())
284 .from(&host_receiver)
285 .to(Ref::collection(BT_HOST_COLLECTION.to_string())),
286 )
287 .await?;
288 builder
289 .add_route(
290 Route::new()
291 .capability(Capability::protocol::<RealmMarker>())
292 .from(Ref::framework())
293 .to(Ref::parent()),
294 )
295 .await?;
296
297 let instance = builder.build().await?;
298
299 let args = fdt::RealmArgs {
301 root_driver: Some(EMULATOR_ROOT_DRIVER_URL.to_string()),
302 software_devices: Some(vec![fidl_fuchsia_driver_test::SoftwareDevice {
303 device_name: "bt-hci-emulator".to_string(),
304 device_id: bind_fuchsia_platform::BIND_PLATFORM_DEV_DID_BT_HCI_EMULATOR,
305 }]),
306 test_component: Some(resolved_test_component),
307 ..Default::default()
308 };
309 instance.driver_test_realm_start(args).await?;
310
311 Ok(Self { realm: instance, receiver: Some(receiver).into() })
312 }
313
314 pub async fn create_bt_host_in_collection(
318 realm: &Arc<HostRealm>,
319 filename: &str,
320 ) -> Result<ClientEnd<HostMarker>, Error> {
321 let component_name = format!("{BT_HOST}_{filename}"); let device_path = format!("{DEV_DIR}/{HCI_DEVICE_DIR}/default");
323 let collection_ref = CollectionRef { name: BT_HOST_COLLECTION.to_owned() };
324 let child_decl = Child {
325 name: Some(component_name.to_owned()),
326 url: Some(BT_HOST_URL.to_owned()),
327 startup: Some(StartupMode::Lazy),
328 config_overrides: Some(vec![ConfigOverride {
329 key: Some("device_path".to_string()),
330 value: Some(ConfigValue::Single(ConfigSingleValue::String(device_path))),
331 ..ConfigOverride::default()
332 }]),
333 ..Default::default()
334 };
335
336 let bt_host_offer = Offer::Directory(OfferDirectory {
337 source: Some(CompRef::Child(ChildRef {
338 name: fuchsia_driver_test::COMPONENT_NAME.to_owned(),
339 collection: None,
340 })),
341 source_name: Some("dev-class".to_owned()),
342 target_name: Some("dev-bt-hci-instance".to_owned()),
343 subdir: Some(format!("bt-hci/{filename}")),
344 dependency_type: Some(DependencyType::Strong),
345 rights: Some(
346 Operations::READ_BYTES
347 | Operations::CONNECT
348 | Operations::GET_ATTRIBUTES
349 | Operations::TRAVERSE
350 | Operations::ENUMERATE,
351 ),
352 ..Default::default()
353 });
354
355 let realm_proxy: RealmProxy =
356 realm.instance().connect_to_protocol_at_exposed_dir().unwrap();
357 let _ = realm_proxy
358 .create_child(
359 &collection_ref,
360 &child_decl,
361 CreateChildArgs { dynamic_offers: Some(vec![bt_host_offer]), ..Default::default() },
362 )
363 .await
364 .map_err(|e| format_err!("{e:?}"))?
365 .map_err(|e| format_err!("{e:?}"))?;
366
367 let host = realm.receiver().next().await.unwrap();
368 Ok(host)
369 }
370
371 async fn fake_receiver_component(
372 sender: mpsc::Sender<ClientEnd<HostMarker>>,
373 handles: LocalComponentHandles,
374 ) -> Result<(), Error> {
375 let mut fs = ServiceFs::new();
376 let _ = fs.dir("svc").add_fidl_service(move |mut req_stream: ReceiverRequestStream| {
377 let mut sender_clone = sender.clone();
378 fuchsia_async::Task::local(async move {
379 let (host_server, _) =
380 req_stream.next().await.unwrap().unwrap().into_add_host().unwrap();
381 sender_clone.send(host_server).await.expect("Host sent successfully");
382 })
383 .detach()
384 });
385
386 let _ = fs.serve_connection(handles.outgoing_dir)?;
387 fs.collect::<()>().await;
388 Ok(())
389 }
390
391 pub fn instance(&self) -> &ScopedInstance {
392 &self.realm.root
393 }
394
395 pub fn dev(&self) -> Result<fio::DirectoryProxy, Error> {
396 self.realm.driver_test_realm_connect_to_dev()
397 }
398
399 pub fn receiver(&self) -> Receiver<ClientEnd<HostMarker>> {
400 self.receiver.lock().take().unwrap()
401 }
402}