Skip to main content

bt_test_harness/
host_realm.rs

1// Copyright 2024 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use 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    // Route config capabilities from root to bt-init
45    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    // Add directory routing between components within CoreRealm
202    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        // We need to resolve our test component manually. Eventually component framework could provide
221        // an introspection way of resolving your own component.
222        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        // Mock the fuchsia.bluetooth.host.Receiver API by creating a channel where the client end
238        // of the Host protocol can be extracted from |receiver|.
239        // Note: The word "receiver" is overloaded. One refers to the Receiver API, the other
240        // refers to the receiver end of the mpsc channel.
241        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        // Create bt-host collection
254        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        // Route capabilities between realm components and bt-host-collection
271        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        // Start DriverTestRealm
300        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    // Create bt-host component with |filename| and add it to bt-host collection in HostRealm.
315    // Wait for the component to register itself with Receiver and get the client end of the Host
316    // protocol.
317    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}"); // Name must only contain [a-z0-9-_]
322        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}