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::{format_err, Error};
8use fidl::endpoints::ClientEnd;
9use fidl_fuchsia_bluetooth_host::{HostMarker, ReceiverMarker, ReceiverRequestStream};
10use fidl_fuchsia_component::{CreateChildArgs, RealmMarker};
11use fidl_fuchsia_component_decl::{
12    Child, CollectionRef, ConfigOverride, ConfigSingleValue, ConfigValue, Durability, StartupMode,
13};
14use fidl_fuchsia_logger::LogSinkMarker;
15use fuchsia_bluetooth::constants::{
16    BT_HOST, BT_HOST_COLLECTION, BT_HOST_URL, DEV_DIR, HCI_DEVICE_DIR,
17};
18use fuchsia_component::server::ServiceFs;
19use fuchsia_component_test::{
20    Capability, ChildOptions, LocalComponentHandles, RealmBuilder, RealmInstance, Ref, Route,
21    ScopedInstance,
22};
23use fuchsia_driver_test::{DriverTestRealmBuilder, DriverTestRealmInstance};
24use futures::channel::mpsc;
25use futures::{SinkExt, StreamExt};
26use std::sync::{Arc, Mutex};
27use {fidl_fuchsia_driver_test as fdt, fidl_fuchsia_io as fio};
28
29mod constants {
30    pub mod receiver {
31        pub const MONIKER: &str = "receiver";
32    }
33}
34
35pub async fn add_host_routes(
36    builder: &RealmBuilder,
37    to: impl Into<fuchsia_component_test::Ref> + Clone,
38) -> Result<(), Error> {
39    // Route config capabilities from root to bt-init
40    builder
41        .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
42            name: "fuchsia.bluetooth.LegacyPairing".parse()?,
43            value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::Bool(false)),
44        }))
45        .await?;
46    builder
47        .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
48            name: "fuchsia.bluetooth.ScoOffloadPathIndex".parse()?,
49            value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::Uint8(6)),
50        }))
51        .await?;
52    builder
53        .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
54            name: "fuchsia.power.SuspendEnabled".parse()?,
55            value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::Bool(false)),
56        }))
57        .await?;
58
59    builder
60        .add_route(
61            Route::new()
62                .capability(Capability::configuration("fuchsia.bluetooth.LegacyPairing"))
63                .from(Ref::self_())
64                .to(to.clone()),
65        )
66        .await?;
67    builder
68        .add_route(
69            Route::new()
70                .capability(Capability::configuration("fuchsia.bluetooth.ScoOffloadPathIndex"))
71                .from(Ref::self_())
72                .to(to.clone()),
73        )
74        .await?;
75
76    builder
77        .add_route(
78            Route::new()
79                .capability(Capability::configuration("fuchsia.power.SuspendEnabled"))
80                .from(Ref::self_())
81                .to(to.clone()),
82        )
83        .await?;
84
85    // Add directory routing between components within CoreRealm
86    builder
87        .add_route(
88            Route::new()
89                .capability(Capability::directory("dev-class").subdir("bt-hci").as_("dev-bt-hci"))
90                .from(Ref::child(fuchsia_driver_test::COMPONENT_NAME))
91                .to(to),
92        )
93        .await?;
94    Ok(())
95}
96
97pub struct HostRealm {
98    realm: RealmInstance,
99    receiver: Mutex<Option<Receiver<ClientEnd<HostMarker>>>>,
100}
101
102impl HostRealm {
103    pub async fn create(test_component: String) -> Result<Self, Error> {
104        // We need to resolve our test component manually. Eventually component framework could provide
105        // an introspection way of resolving your own component.
106        let resolved_test_component = {
107            let client = fuchsia_component::client::connect_to_protocol_at_path::<
108                fidl_fuchsia_component_resolution::ResolverMarker,
109            >("/svc/fuchsia.component.resolution.Resolver-hermetic")
110            .unwrap();
111            client
112                .resolve(test_component.as_str())
113                .await
114                .unwrap()
115                .expect("Failed to resolve test component")
116        };
117
118        let builder = RealmBuilder::new().await?;
119        let _ = builder.driver_test_realm_setup().await?;
120
121        // Mock the fuchsia.bluetooth.host.Receiver API by creating a channel where the client end
122        // of the Host protocol can be extracted from |receiver|.
123        // Note: The word "receiver" is overloaded. One refers to the Receiver API, the other
124        // refers to the receiver end of the mpsc channel.
125        let (sender, receiver) = mpsc::channel(128);
126        let host_receiver = builder
127            .add_local_child(
128                constants::receiver::MONIKER,
129                move |handles| {
130                    let sender_clone = sender.clone();
131                    Box::pin(Self::fake_receiver_component(sender_clone, handles))
132                },
133                ChildOptions::new().eager(),
134            )
135            .await?;
136
137        // Create bt-host collection
138        let mut realm_decl = builder.get_realm_decl().await?;
139        realm_decl.collections.push(cm_rust::CollectionDecl {
140            name: BT_HOST_COLLECTION.parse().unwrap(),
141            durability: Durability::SingleRun,
142            environment: None,
143            allowed_offers: cm_types::AllowedOffers::StaticOnly,
144            allow_long_names: false,
145            persistent_storage: None,
146        });
147        builder.replace_realm_decl(realm_decl).await.unwrap();
148
149        add_host_routes(&builder, Ref::collection(BT_HOST_COLLECTION.to_string())).await?;
150
151        // Route capabilities between realm components and bt-host-collection
152        builder
153            .add_route(
154                Route::new()
155                    .capability(Capability::protocol::<LogSinkMarker>())
156                    .capability(Capability::dictionary("diagnostics"))
157                    .from(Ref::parent())
158                    .to(Ref::collection(BT_HOST_COLLECTION.to_string())),
159            )
160            .await?;
161        builder
162            .add_route(
163                Route::new()
164                    .capability(Capability::protocol::<ReceiverMarker>())
165                    .from(&host_receiver)
166                    .to(Ref::collection(BT_HOST_COLLECTION.to_string())),
167            )
168            .await?;
169        builder
170            .add_route(
171                Route::new()
172                    .capability(Capability::protocol::<RealmMarker>())
173                    .from(Ref::framework())
174                    .to(Ref::parent()),
175            )
176            .await?;
177
178        let instance = builder.build().await?;
179
180        // Start DriverTestRealm
181        let args = fdt::RealmArgs {
182            root_driver: Some(EMULATOR_ROOT_DRIVER_URL.to_string()),
183            software_devices: Some(vec![fidl_fuchsia_driver_test::SoftwareDevice {
184                device_name: "bt-hci-emulator".to_string(),
185                device_id: bind_fuchsia_platform::BIND_PLATFORM_DEV_DID_BT_HCI_EMULATOR,
186            }]),
187            test_component: Some(resolved_test_component),
188            ..Default::default()
189        };
190        instance.driver_test_realm_start(args).await?;
191
192        Ok(Self { realm: instance, receiver: Some(receiver).into() })
193    }
194
195    // Create bt-host component with |filename| and add it to bt-host collection in HostRealm.
196    // Wait for the component to register itself with Receiver and get the client end of the Host
197    // protocol.
198    pub async fn create_bt_host_in_collection(
199        realm: &Arc<HostRealm>,
200        filename: &str,
201    ) -> Result<ClientEnd<HostMarker>, Error> {
202        let component_name = format!("{BT_HOST}_{filename}"); // Name must only contain [a-z0-9-_]
203        let device_path = format!("{DEV_DIR}/{HCI_DEVICE_DIR}/{filename}");
204        let collection_ref = CollectionRef { name: BT_HOST_COLLECTION.to_owned() };
205        let child_decl = Child {
206            name: Some(component_name.to_owned()),
207            url: Some(BT_HOST_URL.to_owned()),
208            startup: Some(StartupMode::Lazy),
209            config_overrides: Some(vec![ConfigOverride {
210                key: Some("device_path".to_string()),
211                value: Some(ConfigValue::Single(ConfigSingleValue::String(
212                    device_path.to_string(),
213                ))),
214                ..ConfigOverride::default()
215            }]),
216            ..Default::default()
217        };
218
219        let realm_proxy =
220            realm.instance().connect_to_protocol_at_exposed_dir::<RealmMarker>().unwrap();
221        let _ = realm_proxy
222            .create_child(&collection_ref, &child_decl, CreateChildArgs::default())
223            .await
224            .map_err(|e| format_err!("{e:?}"))?
225            .map_err(|e| format_err!("{e:?}"))?;
226
227        let host = realm.receiver().next().await.unwrap();
228        Ok(host)
229    }
230
231    async fn fake_receiver_component(
232        sender: mpsc::Sender<ClientEnd<HostMarker>>,
233        handles: LocalComponentHandles,
234    ) -> Result<(), Error> {
235        let mut fs = ServiceFs::new();
236        let _ = fs.dir("svc").add_fidl_service(move |mut req_stream: ReceiverRequestStream| {
237            let mut sender_clone = sender.clone();
238            fuchsia_async::Task::local(async move {
239                let (host_server, _) =
240                    req_stream.next().await.unwrap().unwrap().into_add_host().unwrap();
241                sender_clone.send(host_server).await.expect("Host sent successfully");
242            })
243            .detach()
244        });
245
246        let _ = fs.serve_connection(handles.outgoing_dir)?;
247        fs.collect::<()>().await;
248        Ok(())
249    }
250
251    pub fn instance(&self) -> &ScopedInstance {
252        &self.realm.root
253    }
254
255    pub fn dev(&self) -> Result<fio::DirectoryProxy, Error> {
256        self.realm.driver_test_realm_connect_to_dev()
257    }
258
259    pub fn receiver(&self) -> Receiver<ClientEnd<HostMarker>> {
260        self.receiver.lock().expect("REASON").take().unwrap()
261    }
262}