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 fuchsia_sync::Mutex;
28use futures::channel::mpsc;
29use futures::{SinkExt, StreamExt};
30use std::sync::Arc;
31use {fidl_fuchsia_driver_test as fdt, fidl_fuchsia_io as fio};
32
33mod constants {
34 pub mod receiver {
35 pub const MONIKER: &str = "receiver";
36 }
37}
38
39pub async fn add_host_routes(
40 builder: &RealmBuilder,
41 to: impl Into<fuchsia_component_test::Ref> + Clone,
42) -> Result<(), Error> {
43 builder
45 .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
46 name: "fuchsia.bluetooth.LegacyPairing".parse()?,
47 value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::Bool(false)),
48 }))
49 .await?;
50 builder
51 .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
52 name: "fuchsia.bluetooth.ScoOffloadPathIndex".parse()?,
53 value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::Uint8(6)),
54 }))
55 .await?;
56 builder
57 .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
58 name: "fuchsia.bluetooth.OverrideVendorCapabilitiesVersion".parse()?,
59 value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::Uint16(0)),
60 }))
61 .await?;
62 builder
63 .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
64 name: "fuchsia.power.SuspendEnabled".parse()?,
65 value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::Bool(false)),
66 }))
67 .await?;
68
69 macro_rules! add_capability_route {
70 ($name:expr) => {
71 builder.add_route(
72 Route::new()
73 .capability(Capability::configuration($name))
74 .from(Ref::self_())
75 .to(to.clone()),
76 )
77 };
78 }
79
80 add_capability_route!("fuchsia.bluetooth.LegacyPairing").await?;
81 add_capability_route!("fuchsia.bluetooth.ScoOffloadPathIndex").await?;
82 add_capability_route!("fuchsia.bluetooth.OverrideVendorCapabilitiesVersion").await?;
83 add_capability_route!("fuchsia.power.SuspendEnabled").await?;
84
85 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 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 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 let mut realm_decl = builder.get_realm_decl().await?;
139 push_box(
140 &mut realm_decl.collections,
141 cm_rust::CollectionDecl {
142 name: BT_HOST_COLLECTION.parse().unwrap(),
143 durability: Durability::SingleRun,
144 environment: None,
145 allowed_offers: cm_types::AllowedOffers::StaticAndDynamic,
146 allow_long_names: false,
147 persistent_storage: None,
148 },
149 );
150 builder.replace_realm_decl(realm_decl).await.unwrap();
151
152 add_host_routes(&builder, Ref::collection(BT_HOST_COLLECTION.to_string())).await?;
153
154 builder
156 .add_route(
157 Route::new()
158 .capability(Capability::protocol::<LogSinkMarker>())
159 .capability(Capability::dictionary("diagnostics"))
160 .from(Ref::parent())
161 .to(Ref::collection(BT_HOST_COLLECTION.to_string())),
162 )
163 .await?;
164 builder
165 .add_route(
166 Route::new()
167 .capability(Capability::protocol::<ReceiverMarker>())
168 .from(&host_receiver)
169 .to(Ref::collection(BT_HOST_COLLECTION.to_string())),
170 )
171 .await?;
172 builder
173 .add_route(
174 Route::new()
175 .capability(Capability::protocol::<RealmMarker>())
176 .from(Ref::framework())
177 .to(Ref::parent()),
178 )
179 .await?;
180
181 let instance = builder.build().await?;
182
183 let args = fdt::RealmArgs {
185 root_driver: Some(EMULATOR_ROOT_DRIVER_URL.to_string()),
186 software_devices: Some(vec![fidl_fuchsia_driver_test::SoftwareDevice {
187 device_name: "bt-hci-emulator".to_string(),
188 device_id: bind_fuchsia_platform::BIND_PLATFORM_DEV_DID_BT_HCI_EMULATOR,
189 }]),
190 test_component: Some(resolved_test_component),
191 ..Default::default()
192 };
193 instance.driver_test_realm_start(args).await?;
194
195 Ok(Self { realm: instance, receiver: Some(receiver).into() })
196 }
197
198 pub async fn create_bt_host_in_collection(
202 realm: &Arc<HostRealm>,
203 filename: &str,
204 ) -> Result<ClientEnd<HostMarker>, Error> {
205 let component_name = format!("{BT_HOST}_{filename}"); let device_path = format!("{DEV_DIR}/{HCI_DEVICE_DIR}/default");
207 let collection_ref = CollectionRef { name: BT_HOST_COLLECTION.to_owned() };
208 let child_decl = Child {
209 name: Some(component_name.to_owned()),
210 url: Some(BT_HOST_URL.to_owned()),
211 startup: Some(StartupMode::Lazy),
212 config_overrides: Some(vec![ConfigOverride {
213 key: Some("device_path".to_string()),
214 value: Some(ConfigValue::Single(ConfigSingleValue::String(device_path))),
215 ..ConfigOverride::default()
216 }]),
217 ..Default::default()
218 };
219
220 let bt_host_offer = Offer::Directory(OfferDirectory {
221 source: Some(CompRef::Child(ChildRef {
222 name: fuchsia_driver_test::COMPONENT_NAME.to_owned(),
223 collection: None,
224 })),
225 source_name: Some("dev-class".to_owned()),
226 target_name: Some("dev-bt-hci-instance".to_owned()),
227 subdir: Some(format!("bt-hci/{filename}")),
228 dependency_type: Some(DependencyType::Strong),
229 rights: Some(
230 Operations::READ_BYTES
231 | Operations::CONNECT
232 | Operations::GET_ATTRIBUTES
233 | Operations::TRAVERSE
234 | Operations::ENUMERATE,
235 ),
236 ..Default::default()
237 });
238
239 let realm_proxy: RealmProxy =
240 realm.instance().connect_to_protocol_at_exposed_dir().unwrap();
241 let _ = realm_proxy
242 .create_child(
243 &collection_ref,
244 &child_decl,
245 CreateChildArgs { dynamic_offers: Some(vec![bt_host_offer]), ..Default::default() },
246 )
247 .await
248 .map_err(|e| format_err!("{e:?}"))?
249 .map_err(|e| format_err!("{e:?}"))?;
250
251 let host = realm.receiver().next().await.unwrap();
252 Ok(host)
253 }
254
255 async fn fake_receiver_component(
256 sender: mpsc::Sender<ClientEnd<HostMarker>>,
257 handles: LocalComponentHandles,
258 ) -> Result<(), Error> {
259 let mut fs = ServiceFs::new();
260 let _ = fs.dir("svc").add_fidl_service(move |mut req_stream: ReceiverRequestStream| {
261 let mut sender_clone = sender.clone();
262 fuchsia_async::Task::local(async move {
263 let (host_server, _) =
264 req_stream.next().await.unwrap().unwrap().into_add_host().unwrap();
265 sender_clone.send(host_server).await.expect("Host sent successfully");
266 })
267 .detach()
268 });
269
270 let _ = fs.serve_connection(handles.outgoing_dir)?;
271 fs.collect::<()>().await;
272 Ok(())
273 }
274
275 pub fn instance(&self) -> &ScopedInstance {
276 &self.realm.root
277 }
278
279 pub fn dev(&self) -> Result<fio::DirectoryProxy, Error> {
280 self.realm.driver_test_realm_connect_to_dev()
281 }
282
283 pub fn receiver(&self) -> Receiver<ClientEnd<HostMarker>> {
284 self.receiver.lock().take().unwrap()
285 }
286}