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