1use crate::emulator::EMULATOR_ROOT_DRIVER_URL;
6use crate::host_realm::mpsc::Receiver;
7use anyhow::{format_err, Error};
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_io::Operations;
17use fidl_fuchsia_logger::LogSinkMarker;
18use fuchsia_bluetooth::constants::{
19 BT_HOST, BT_HOST_COLLECTION, BT_HOST_URL, DEV_DIR, HCI_DEVICE_DIR,
20};
21use fuchsia_component::server::ServiceFs;
22use fuchsia_component_test::{
23 Capability, ChildOptions, LocalComponentHandles, RealmBuilder, RealmInstance, Ref, Route,
24 ScopedInstance,
25};
26use fuchsia_driver_test::{DriverTestRealmBuilder, DriverTestRealmInstance};
27use futures::channel::mpsc;
28use futures::{SinkExt, StreamExt};
29use std::sync::{Arc, Mutex};
30use {fidl_fuchsia_driver_test as fdt, fidl_fuchsia_io as fio};
31
32mod constants {
33 pub mod receiver {
34 pub const MONIKER: &str = "receiver";
35 }
36}
37
38pub async fn add_host_routes(
39 builder: &RealmBuilder,
40 to: impl Into<fuchsia_component_test::Ref> + Clone,
41) -> Result<(), Error> {
42 builder
44 .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
45 name: "fuchsia.bluetooth.LegacyPairing".parse()?,
46 value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::Bool(false)),
47 }))
48 .await?;
49 builder
50 .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
51 name: "fuchsia.bluetooth.ScoOffloadPathIndex".parse()?,
52 value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::Uint8(6)),
53 }))
54 .await?;
55 builder
56 .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
57 name: "fuchsia.power.SuspendEnabled".parse()?,
58 value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::Bool(false)),
59 }))
60 .await?;
61
62 builder
63 .add_route(
64 Route::new()
65 .capability(Capability::configuration("fuchsia.bluetooth.LegacyPairing"))
66 .from(Ref::self_())
67 .to(to.clone()),
68 )
69 .await?;
70 builder
71 .add_route(
72 Route::new()
73 .capability(Capability::configuration("fuchsia.bluetooth.ScoOffloadPathIndex"))
74 .from(Ref::self_())
75 .to(to.clone()),
76 )
77 .await?;
78
79 builder
80 .add_route(
81 Route::new()
82 .capability(Capability::configuration("fuchsia.power.SuspendEnabled"))
83 .from(Ref::self_())
84 .to(to.clone()),
85 )
86 .await?;
87
88 builder
90 .add_route(
91 Route::new()
92 .capability(Capability::directory("dev-class").subdir("bt-hci").as_("dev-bt-hci"))
93 .from(Ref::child(fuchsia_driver_test::COMPONENT_NAME))
94 .to(to),
95 )
96 .await?;
97 Ok(())
98}
99
100pub struct HostRealm {
101 realm: RealmInstance,
102 receiver: Mutex<Option<Receiver<ClientEnd<HostMarker>>>>,
103}
104
105impl HostRealm {
106 pub async fn create(test_component: String) -> Result<Self, Error> {
107 let resolved_test_component = {
110 let client = fuchsia_component::client::connect_to_protocol_at_path::<
111 fidl_fuchsia_component_resolution::ResolverMarker,
112 >("/svc/fuchsia.component.resolution.Resolver-hermetic")
113 .unwrap();
114 client
115 .resolve(test_component.as_str())
116 .await
117 .unwrap()
118 .expect("Failed to resolve test component")
119 };
120
121 let builder = RealmBuilder::new().await?;
122 let _ = builder.driver_test_realm_setup().await?;
123
124 let (sender, receiver) = mpsc::channel(128);
129 let host_receiver = builder
130 .add_local_child(
131 constants::receiver::MONIKER,
132 move |handles| {
133 let sender_clone = sender.clone();
134 Box::pin(Self::fake_receiver_component(sender_clone, handles))
135 },
136 ChildOptions::new().eager(),
137 )
138 .await?;
139
140 let mut realm_decl = builder.get_realm_decl().await?;
142 push_box(
143 &mut realm_decl.collections,
144 cm_rust::CollectionDecl {
145 name: BT_HOST_COLLECTION.parse().unwrap(),
146 durability: Durability::SingleRun,
147 environment: None,
148 allowed_offers: cm_types::AllowedOffers::StaticAndDynamic,
149 allow_long_names: false,
150 persistent_storage: None,
151 },
152 );
153 builder.replace_realm_decl(realm_decl).await.unwrap();
154
155 add_host_routes(&builder, Ref::collection(BT_HOST_COLLECTION.to_string())).await?;
156
157 builder
159 .add_route(
160 Route::new()
161 .capability(Capability::protocol::<LogSinkMarker>())
162 .capability(Capability::dictionary("diagnostics"))
163 .from(Ref::parent())
164 .to(Ref::collection(BT_HOST_COLLECTION.to_string())),
165 )
166 .await?;
167 builder
168 .add_route(
169 Route::new()
170 .capability(Capability::protocol::<ReceiverMarker>())
171 .from(&host_receiver)
172 .to(Ref::collection(BT_HOST_COLLECTION.to_string())),
173 )
174 .await?;
175 builder
176 .add_route(
177 Route::new()
178 .capability(Capability::protocol::<RealmMarker>())
179 .from(Ref::framework())
180 .to(Ref::parent()),
181 )
182 .await?;
183
184 let instance = builder.build().await?;
185
186 let args = fdt::RealmArgs {
188 root_driver: Some(EMULATOR_ROOT_DRIVER_URL.to_string()),
189 software_devices: Some(vec![fidl_fuchsia_driver_test::SoftwareDevice {
190 device_name: "bt-hci-emulator".to_string(),
191 device_id: bind_fuchsia_platform::BIND_PLATFORM_DEV_DID_BT_HCI_EMULATOR,
192 }]),
193 test_component: Some(resolved_test_component),
194 ..Default::default()
195 };
196 instance.driver_test_realm_start(args).await?;
197
198 Ok(Self { realm: instance, receiver: Some(receiver).into() })
199 }
200
201 pub async fn create_bt_host_in_collection(
205 realm: &Arc<HostRealm>,
206 filename: &str,
207 ) -> Result<ClientEnd<HostMarker>, Error> {
208 let component_name = format!("{BT_HOST}_{filename}"); let device_path = format!("{DEV_DIR}/{HCI_DEVICE_DIR}/default");
210 let collection_ref = CollectionRef { name: BT_HOST_COLLECTION.to_owned() };
211 let child_decl = Child {
212 name: Some(component_name.to_owned()),
213 url: Some(BT_HOST_URL.to_owned()),
214 startup: Some(StartupMode::Lazy),
215 config_overrides: Some(vec![ConfigOverride {
216 key: Some("device_path".to_string()),
217 value: Some(ConfigValue::Single(ConfigSingleValue::String(device_path))),
218 ..ConfigOverride::default()
219 }]),
220 ..Default::default()
221 };
222
223 let bt_host_offer = Offer::Directory(OfferDirectory {
224 source: Some(CompRef::Child(ChildRef {
225 name: fuchsia_driver_test::COMPONENT_NAME.to_owned(),
226 collection: None,
227 })),
228 source_name: Some("dev-class".to_owned()),
229 target_name: Some("dev-bt-hci-instance".to_owned()),
230 subdir: Some(format!("bt-hci/{filename}")),
231 dependency_type: Some(DependencyType::Strong),
232 rights: Some(
233 Operations::READ_BYTES
234 | Operations::CONNECT
235 | Operations::GET_ATTRIBUTES
236 | Operations::TRAVERSE
237 | Operations::ENUMERATE,
238 ),
239 ..Default::default()
240 });
241
242 let realm_proxy: RealmProxy =
243 realm.instance().connect_to_protocol_at_exposed_dir().unwrap();
244 let _ = realm_proxy
245 .create_child(
246 &collection_ref,
247 &child_decl,
248 CreateChildArgs { dynamic_offers: Some(vec![bt_host_offer]), ..Default::default() },
249 )
250 .await
251 .map_err(|e| format_err!("{e:?}"))?
252 .map_err(|e| format_err!("{e:?}"))?;
253
254 let host = realm.receiver().next().await.unwrap();
255 Ok(host)
256 }
257
258 async fn fake_receiver_component(
259 sender: mpsc::Sender<ClientEnd<HostMarker>>,
260 handles: LocalComponentHandles,
261 ) -> Result<(), Error> {
262 let mut fs = ServiceFs::new();
263 let _ = fs.dir("svc").add_fidl_service(move |mut req_stream: ReceiverRequestStream| {
264 let mut sender_clone = sender.clone();
265 fuchsia_async::Task::local(async move {
266 let (host_server, _) =
267 req_stream.next().await.unwrap().unwrap().into_add_host().unwrap();
268 sender_clone.send(host_server).await.expect("Host sent successfully");
269 })
270 .detach()
271 });
272
273 let _ = fs.serve_connection(handles.outgoing_dir)?;
274 fs.collect::<()>().await;
275 Ok(())
276 }
277
278 pub fn instance(&self) -> &ScopedInstance {
279 &self.realm.root
280 }
281
282 pub fn dev(&self) -> Result<fio::DirectoryProxy, Error> {
283 self.realm.driver_test_realm_connect_to_dev()
284 }
285
286 pub fn receiver(&self) -> Receiver<ClientEnd<HostMarker>> {
287 self.receiver.lock().expect("REASON").take().unwrap()
288 }
289}