1use anyhow::{Context as _, Result};
6use fidl::HandleBased;
7
8use cm_rust::FidlIntoNative;
9use fuchsia_component::server::ServiceFs;
10use fuchsia_component_test::{
11 Capability, ChildOptions, ChildRef, CollectionRef, LocalComponentHandles, RealmBuilder,
12 RealmInstance, Ref, Route,
13};
14use futures::{StreamExt, TryStreamExt};
15use std::sync::Arc;
16use zx::AsHandleRef;
17use {
18 fidl_fuchsia_component_decl as fdecl, fidl_fuchsia_component_test as ftest,
19 fidl_fuchsia_driver_development as fdd, fidl_fuchsia_driver_test as fdt,
20 fidl_fuchsia_io as fio, fuchsia_async as fasync,
21};
22
23fn clone(
24 dir: &fio::DirectoryProxy,
25) -> Result<fidl::endpoints::ClientEnd<fio::DirectoryMarker>, fidl::Error> {
26 let (client_end, server_end) = fidl::endpoints::create_endpoints::<fio::DirectoryMarker>();
27 dir.clone(fidl::endpoints::ServerEnd::new(server_end.into_channel()))?;
28 Ok(client_end)
29}
30
31async fn internal_serve(
32 stream: fidl_fuchsia_driver_test::InternalRequestStream,
33 test_pkg_dir: Arc<fio::DirectoryProxy>,
34 test_resolution_context: Arc<Option<fidl_fuchsia_component_resolution::Context>>,
35 boot_dir: Arc<Option<fio::DirectoryProxy>>,
36 boot_driver_components: Arc<Option<Vec<String>>>,
37) {
38 stream
39 .try_for_each_concurrent(None, |request| {
40 let test_pkg_dir = test_pkg_dir.clone();
41 let test_resolution_context = test_resolution_context.clone();
42 let boot_dir = boot_dir.clone();
43 let boot_driver_components = boot_driver_components.clone();
44 async move {
45 match request {
46 fidl_fuchsia_driver_test::InternalRequest::GetTestPackage { responder } => {
47 let cloned = clone(test_pkg_dir.as_ref())?;
48 responder.send(Ok(Some(cloned)))?;
49 }
50 fidl_fuchsia_driver_test::InternalRequest::GetTestResolutionContext {
51 responder,
52 } => responder.send(Ok(test_resolution_context.as_ref().as_ref()))?,
53 fidl_fuchsia_driver_test::InternalRequest::GetBootDirectory { responder } => {
54 match boot_dir.as_ref() {
55 Some(boot_dir) => {
56 let cloned = clone(boot_dir)?;
57 responder.send(Ok(Some(cloned)))?;
58 }
59 None => responder.send(Ok(None))?,
60 }
61 }
62 fidl_fuchsia_driver_test::InternalRequest::GetBootDriverOverrides {
63 responder,
64 } => responder.send(Ok(boot_driver_components
65 .as_ref()
66 .clone()
67 .unwrap_or(vec![])
68 .as_slice()))?,
69 };
70 Ok(())
71 }
72 })
73 .await
74 .expect("fuchsia.driver.test.Internal failed.");
75}
76
77async fn resource_provider_serve(
78 stream: fidl_fuchsia_driver_test::ResourceProviderRequestStream,
79 devicetree: Arc<Option<zx::Vmo>>,
80) {
81 stream
82 .try_for_each_concurrent(None, |request| {
83 let devicetree = devicetree.clone();
84 async move {
85 match request {
86 fidl_fuchsia_driver_test::ResourceProviderRequest::GetDeviceTree {
87 responder,
88 } => {
89 if let Some(vmo) = devicetree.as_ref().as_ref().map(|d| {
90 d.duplicate_handle(zx::Rights::SAME_RIGHTS).expect("duplicate")
91 }) {
92 responder.send(Ok(vmo))?;
93 } else {
94 responder.send(Err(zx::Status::NOT_FOUND.into_raw()))?;
95 }
96 }
97 };
98 Ok(())
99 }
100 })
101 .await
102 .expect("fuchsia.driver.test.Internal failed.");
103}
104
105async fn run_internal_server(
106 handles: LocalComponentHandles,
107 test_pkg_dir: Arc<fio::DirectoryProxy>,
108 test_resolution_context: Arc<Option<fidl_fuchsia_component_resolution::Context>>,
109 boot_dir: Arc<Option<fio::DirectoryProxy>>,
110 boot_driver_components: Arc<Option<Vec<String>>>,
111 devicetree: Arc<Option<zx::Vmo>>,
112) -> Result<()> {
113 let mut fs = ServiceFs::new();
114
115 fs.dir("svc").add_fidl_service(
116 move |stream: fidl_fuchsia_driver_test::InternalRequestStream| {
117 fasync::Task::spawn(internal_serve(
118 stream,
119 test_pkg_dir.clone(),
120 test_resolution_context.clone(),
121 boot_dir.clone(),
122 boot_driver_components.clone(),
123 ))
124 .detach();
125 },
126 );
127 fs.dir("svc").add_fidl_service(
128 move |stream: fidl_fuchsia_driver_test::ResourceProviderRequestStream| {
129 fasync::Task::spawn(resource_provider_serve(stream, devicetree.clone())).detach();
130 },
131 );
132 fs.serve_connection(handles.outgoing_dir)?;
133 fs.collect::<()>().await;
134 Ok(())
135}
136
137fn capabilities_eq_name(a: &ftest::Capability, b: &ftest::Capability) -> bool {
139 match (a, b) {
140 (ftest::Capability::Protocol(a), ftest::Capability::Protocol(b)) => a.name == b.name,
141 (ftest::Capability::Directory(a), ftest::Capability::Directory(b)) => a.name == b.name,
142 (ftest::Capability::Storage(a), ftest::Capability::Storage(b)) => a.name == b.name,
143 (ftest::Capability::Service(a), ftest::Capability::Service(b)) => a.name == b.name,
144 (ftest::Capability::EventStream(a), ftest::Capability::EventStream(b)) => a.name == b.name,
145 (ftest::Capability::Config(a), ftest::Capability::Config(b)) => a.name == b.name,
146 (ftest::Capability::Dictionary(a), ftest::Capability::Dictionary(b)) => a.name == b.name,
147 (ftest::Capability::Resolver(a), ftest::Capability::Resolver(b)) => a.name == b.name,
148 (ftest::Capability::Runner(a), ftest::Capability::Runner(b)) => a.name == b.name,
149 _ => false,
150 }
151}
152
153#[derive(Debug, Clone, Default)]
154pub struct Options {
155 using_subpackage: Option<bool>,
156 driver_offers: Option<(Ref, Vec<ftest::Capability>)>,
157 driver_exposes: Option<Vec<ftest::Capability>>,
158 extra_realm_capabilities: Vec<(ftest::Capability, Ref)>,
159}
160
161impl Options {
162 pub fn new() -> Self {
163 Self::default()
164 }
165
166 pub fn using_subpackage(mut self, using_subpackage: bool) -> Self {
167 self.using_subpackage = Some(using_subpackage);
168 self
169 }
170
171 pub fn driver_offers(mut self, provider: Ref, offers: Vec<ftest::Capability>) -> Self {
172 self.driver_offers = Some((provider, offers));
173 self
174 }
175
176 pub fn driver_exposes(mut self, exposes: Vec<ftest::Capability>) -> Self {
177 self.driver_exposes = Some(exposes);
178 self
179 }
180
181 pub fn add_extra_realm_capability(
182 mut self,
183 capability: ftest::Capability,
184 from: impl Into<Ref>,
185 ) -> Self {
186 self.extra_realm_capabilities.push((capability, from.into()));
187 self
188 }
189}
190
191#[async_trait::async_trait]
192pub trait DriverTestRealmBuilder {
193 async fn driver_test_realm_setup(
197 &self,
198 options: Options,
199 args: fdt::RealmArgs,
200 ) -> Result<&Self>;
201}
202
203#[async_trait::async_trait]
204impl DriverTestRealmBuilder for RealmBuilder {
205 async fn driver_test_realm_setup(
206 &self,
207 options: Options,
208 args: fdt::RealmArgs,
209 ) -> Result<&Self> {
210 let manifest_provider =
211 fuchsia_component::client::connect_to_protocol::<fdt::ManifestProviderMarker>()?;
212 let stream = manifest_provider
213 .get_manifest(&fdt::GetManifestRequest {
214 using_subpackage: options.using_subpackage,
215 ..Default::default()
216 })
217 .await?
218 .expect("manifest stream");
219
220 let mut manifest: Vec<u8> = vec![];
221 loop {
222 let mut read = stream.read_to_vec(
223 zx::StreamReadOptions::empty(),
224 fidl_fuchsia_io::MAX_TRANSFER_SIZE as usize,
225 )?;
226 if read.is_empty() {
227 break;
228 }
229 manifest.append(&mut read);
230 }
231
232 let component = fidl::unpersist::<fdecl::Component>(manifest.as_slice())
233 .context("unpersisting the manifest vector")?;
234
235 let realm = self
238 .add_child_realm_from_decl(
239 "driver_test_realm",
240 component.fidl_into_native(),
241 ChildOptions::new(),
242 )
243 .await?;
244
245 self.add_route(
247 Route::new()
248 .capability(Capability::protocol_by_name("fuchsia.diagnostics.ArchiveAccessor"))
249 .from(Ref::parent())
250 .to(&realm),
251 )
252 .await?;
253
254 let dtr_support: ChildRef = "dtr_support".into();
255 let fake_resolver: ChildRef = "fake_resolver".into();
256 let driver_manager: ChildRef = "driver_manager".into();
257 let driver_index: ChildRef = "driver_index".into();
258
259 let boot_drivers: CollectionRef = "boot-drivers".into();
260 let base_drivers: CollectionRef = "base-drivers".into();
261 let full_drivers: CollectionRef = "full-drivers".into();
262
263 let devicetree = Arc::new(args.devicetree);
264
265 let test_component = if let Some(test_component) = args.test_component {
267 test_component
268 } else {
269 let realm = fuchsia_component::client::connect_to_protocol::<
270 fidl_fuchsia_component::RealmMarker,
271 >()?;
272
273 realm.get_resolved_info().await.unwrap().unwrap()
274 };
275
276 let test_resolution_context = Arc::new(test_component.resolution_context);
277 let test_pkg_dir = Arc::new(
278 test_component.package.expect("a pkg").directory.expect("a directory").into_proxy(),
279 );
280
281 let boot_dir = Arc::new(match args.boot {
282 Some(boot_dir) => {
283 if !boot_dir.as_handle_ref().is_invalid() {
284 Some(boot_dir.into_proxy())
285 } else {
286 None
287 }
288 }
289 None => None,
290 });
291 let boot_driver_components = Arc::new(args.boot_driver_components);
292
293 self.add_route(
295 Route::new()
296 .capability(
297 Capability::protocol_by_name("fuchsia.component.resolution.Resolver-hermetic")
298 .optional(),
299 )
300 .capability(
301 Capability::protocol_by_name("fuchsia.pkg.PackageResolver-hermetic").optional(),
302 )
303 .from(Ref::parent())
304 .to(&realm),
305 )
306 .await?;
307
308 let driver_test_internal = realm
310 .add_local_child(
311 "driver_test_internal",
312 move |handles| {
313 let test_pkg_dir = test_pkg_dir.clone();
314 let test_resolution_context = test_resolution_context.clone();
315 let boot_dir = boot_dir.clone();
316 let boot_driver_components = boot_driver_components.clone();
317 let devicetree = devicetree.clone();
318
319 Box::pin(run_internal_server(
320 handles,
321 test_pkg_dir,
322 test_resolution_context,
323 boot_dir,
324 boot_driver_components,
325 devicetree,
326 ))
327 },
328 ChildOptions::new(),
329 )
330 .await?;
331
332 let mut tunnel_boot_items = false;
335 let mut voided_offers: Vec<ftest::Capability> = vec![
336 Capability::protocol_by_name("fuchsia.tracing.provider.Registry").optional().into(),
337 Capability::protocol_by_name("fuchsia.boot.WriteOnlyLog").optional().into(),
338 Capability::protocol_by_name("fuchsia.scheduler.RoleManager").optional().into(),
339 Capability::protocol_by_name("fuchsia.boot.Items").optional().into(),
340 Capability::protocol_by_name("fuchsia.kernel.IommuResource").optional().into(),
341 Capability::protocol_by_name("fuchsia.diagnostics.LogFlusher").optional().into(),
342 Capability::protocol_by_name("fuchsia.kernel.MexecResource").optional().into(),
343 Capability::protocol_by_name("fuchsia.kernel.PowerResource").optional().into(),
344 ];
345 for (capability, from) in options.extra_realm_capabilities {
346 voided_offers.retain(|voided| !capabilities_eq_name(voided, &capability));
348
349 if from != Ref::void()
350 && capabilities_eq_name(
351 &Capability::protocol_by_name("fuchsia.boot.Items").into(),
352 &capability,
353 )
354 {
355 tunnel_boot_items = true;
356 }
357
358 self.add_route(Route::new().capability(capability).from(from).to(&realm)).await?;
359 }
360
361 for voided in voided_offers {
363 self.add_route(Route::new().capability(voided).from(Ref::void()).to(&realm)).await?;
364 }
365
366 if args.dtr_offers.is_some() {
369 panic!("Please use |Options::driver_offers| instead of dtr_offers.")
370 }
371 if let Some((provider, offers)) = options.driver_offers {
372 for offer in offers {
373 self.add_route(
374 Route::new().capability(offer.clone()).from(provider.clone()).to(&realm),
375 )
376 .await?;
377 realm
378 .add_route(
379 Route::new()
380 .capability(offer)
381 .from(Ref::parent())
382 .to(&boot_drivers)
383 .to(&base_drivers)
384 .to(&full_drivers),
385 )
386 .await?;
387 }
388 }
389
390 if args.dtr_exposes.is_some() {
392 panic!("Please use |Options::driver_exposes| instead of dtr_exposes.")
393 }
394 if let Some(exposes) = options.driver_exposes {
395 for expose in exposes {
396 realm
397 .add_route(
398 Route::new()
399 .capability(expose.clone())
400 .from(&boot_drivers)
401 .to(Ref::parent()),
402 )
403 .await?;
404 realm
405 .add_route(
406 Route::new()
407 .capability(expose.clone())
408 .from(&base_drivers)
409 .to(Ref::parent()),
410 )
411 .await?;
412 realm
413 .add_route(
414 Route::new()
415 .capability(expose.clone())
416 .from(&full_drivers)
417 .to(Ref::parent()),
418 )
419 .await?;
420
421 self.add_route(Route::new().capability(expose).from(&realm).to(Ref::parent()))
422 .await?;
423 }
424 }
425
426 realm
428 .add_route(
429 Route::new()
430 .capability(Capability::protocol_by_name(
431 "fuchsia.driver.test.ResourceProvider",
432 ))
433 .from(&driver_test_internal)
434 .to(&dtr_support),
435 )
436 .await?;
437
438 realm
440 .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
441 name: "fuchsia.driver.testrealm.TunnelBootItems".parse()?,
442 value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::Bool(
443 tunnel_boot_items,
444 )),
445 }))
446 .await?;
447
448 realm
449 .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
450 name: "fuchsia.driver.testrealm.BoardName".parse()?,
451 value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::String(
452 args.board_name.unwrap_or_default(),
453 )),
454 }))
455 .await?;
456
457 realm
458 .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
459 name: "fuchsia.driver.testrealm.PlatformVid".parse()?,
460 value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::String(
461 args.platform_vid.map(|v| v.to_string()).unwrap_or_default(),
462 )),
463 }))
464 .await?;
465
466 realm
467 .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
468 name: "fuchsia.driver.testrealm.PlatformPid".parse()?,
469 value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::String(
470 args.platform_pid.map(|v| v.to_string()).unwrap_or_default(),
471 )),
472 }))
473 .await?;
474
475 let bind_eager = args.driver_bind_eager.unwrap_or_default();
476 realm
477 .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
478 name: "fuchsia.driver.BindEager".parse()?,
479 value: cm_rust::ConfigValue::Vector(cm_rust::ConfigVectorValue::StringVector(
480 bind_eager.into(),
481 )),
482 }))
483 .await?;
484
485 let driver_disable = args.driver_disable.unwrap_or_default();
486 realm
487 .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
488 name: "fuchsia.driver.DisabledDrivers".parse()?,
489 value: cm_rust::ConfigValue::Vector(cm_rust::ConfigVectorValue::StringVector(
490 driver_disable.into(),
491 )),
492 }))
493 .await?;
494
495 let driver_index_stop_timeout_millis = args.driver_index_stop_timeout_millis.unwrap_or(-1);
496 realm
497 .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
498 name: "fuchsia.driver.index.StopOnIdleTimeoutMillis".parse()?,
499 value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::Int64(
500 driver_index_stop_timeout_millis,
501 )),
502 }))
503 .await?;
504 realm
505 .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
506 name: "fuchsia.power.WaitForSuspendingToken".parse()?,
507 value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::Bool(false)),
508 }))
509 .await?;
510
511 let root_driver = match args.root_driver {
512 Some(val) => val,
513 None => "fuchsia-boot:///dtr#meta/test-parent-sys.cm".to_string(),
514 };
515 realm
516 .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
517 name: "fuchsia.driver.manager.RootDriver".parse()?,
518 value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::String(
519 root_driver,
520 )),
521 }))
522 .await?;
523
524 let software_devs_src = match args.software_devices {
526 Some(devs) => {
527 let names = devs.iter().map(|dev| dev.device_name.clone()).collect::<Vec<_>>();
528 let ids = devs.iter().map(|dev| dev.device_id).collect::<Vec<_>>();
529 realm
530 .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
531 name: "fuchsia.platform.bus.SoftwareDeviceNames".parse()?,
532 value: cm_rust::ConfigValue::Vector(
533 cm_rust::ConfigVectorValue::StringVector(names.into()),
534 ),
535 }))
536 .await?;
537 realm
538 .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
539 name: "fuchsia.platform.bus.SoftwareDeviceIds".parse()?,
540 value: cm_rust::ConfigValue::Vector(
541 cm_rust::ConfigVectorValue::Uint32Vector(ids.into()),
542 ),
543 }))
544 .await?;
545 Ref::self_()
546 }
547 None => Ref::void(),
548 };
549
550 realm
552 .add_route(
553 Route::new()
554 .capability(Capability::configuration("fuchsia.driver.BindEager"))
555 .capability(Capability::configuration("fuchsia.driver.DisabledDrivers"))
556 .capability(Capability::configuration(
557 "fuchsia.driver.index.StopOnIdleTimeoutMillis",
558 ))
559 .from(Ref::self_())
560 .to(&driver_index),
561 )
562 .await?;
563
564 realm
565 .add_route(
566 Route::new()
567 .capability(Capability::configuration("fuchsia.driver.manager.RootDriver"))
568 .from(Ref::self_())
569 .to(&driver_manager),
570 )
571 .await?;
572
573 realm
574 .add_route(
575 Route::new()
576 .capability(
577 Capability::configuration(
578 "fuchsia.driver.manager.SetRootDriverHostCritical",
579 )
580 .optional(),
581 )
582 .capability(
583 Capability::configuration("fuchsia.driver.manager.SuspendTimeoutFallback")
584 .optional(),
585 )
586 .from(Ref::void())
587 .to(&driver_manager),
588 )
589 .await?;
590
591 realm
592 .add_route(
593 Route::new()
594 .capability(Capability::configuration(
595 "fuchsia.driver.testrealm.TunnelBootItems",
596 ))
597 .capability(Capability::configuration("fuchsia.driver.testrealm.BoardName"))
598 .capability(Capability::configuration("fuchsia.driver.testrealm.PlatformVid"))
599 .capability(Capability::configuration("fuchsia.driver.testrealm.PlatformPid"))
600 .from(Ref::self_())
601 .to(&dtr_support),
602 )
603 .await?;
604
605 realm
606 .add_route(
607 Route::new()
608 .capability(Capability::configuration("fuchsia.power.WaitForSuspendingToken"))
609 .from(Ref::self_())
610 .to(&driver_manager),
611 )
612 .await?;
613
614 realm
615 .add_route(
616 Route::new()
617 .capability(
618 Capability::configuration("fuchsia.platform.bus.SoftwareDeviceNames")
619 .optional(),
620 )
621 .capability(
622 Capability::configuration("fuchsia.platform.bus.SoftwareDeviceIds")
623 .optional(),
624 )
625 .from(software_devs_src)
626 .to(&boot_drivers),
627 )
628 .await?;
629
630 realm
632 .add_route(
633 Route::new()
634 .capability(Capability::protocol_by_name("fuchsia.driver.test.Internal"))
635 .from(&driver_test_internal)
636 .to(&fake_resolver),
637 )
638 .await?;
639
640 self.add_route(
642 Route::new()
643 .capability(Capability::directory("dev-class"))
644 .capability(Capability::directory("dev-topological"))
645 .capability(Capability::protocol_by_name(
646 "fuchsia.driver.registrar.DriverRegistrar",
647 ))
648 .capability(Capability::protocol_by_name("fuchsia.driver.development.Manager"))
649 .capability(Capability::protocol_by_name(
650 "fuchsia.driver.framework.CompositeNodeManager",
651 ))
652 .capability(Capability::protocol_by_name("fuchsia.system.state.Administrator"))
653 .from(&realm)
654 .to(Ref::parent()),
655 )
656 .await?;
657 Ok(&self)
659 }
660}
661
662#[async_trait::async_trait]
663pub trait DriverTestRealmInstance {
664 fn driver_test_realm_connect_to_dev(&self) -> Result<fio::DirectoryProxy>;
666
667 async fn wait_for_bootup(&self) -> Result<()>;
671
672 async fn wait_for_node(&self, moniker: &str) -> Result<fdd::NodeInfo>;
674}
675
676#[async_trait::async_trait]
677impl DriverTestRealmInstance for RealmInstance {
678 fn driver_test_realm_connect_to_dev(&self) -> Result<fio::DirectoryProxy> {
679 fuchsia_fs::directory::open_directory_async(
680 self.root.get_exposed_dir(),
681 "dev-topological",
682 fio::Flags::empty(),
683 )
684 .map_err(Into::into)
685 }
686
687 async fn wait_for_bootup(&self) -> Result<()> {
688 let manager: fdd::ManagerProxy = self.root.connect_to_protocol_at_exposed_dir()?;
689 manager.wait_for_bootup().await?;
690 Ok(())
691 }
692
693 async fn wait_for_node(&self, moniker: &str) -> Result<fdd::NodeInfo> {
694 let manager: fdd::ManagerProxy = self.root.connect_to_protocol_at_exposed_dir()?;
695 loop {
696 let (iterator, iterator_server) =
697 fidl::endpoints::create_proxy::<fdd::NodeInfoIteratorMarker>();
698 manager.get_node_info(&[moniker.to_string()], iterator_server, true)?;
699 let next = iterator.get_next().await;
700 if let Ok(nodes) = next
701 && !nodes.is_empty()
702 && nodes[0].moniker == Some(moniker.to_string())
703 {
704 return Ok(nodes[0].clone());
705 }
706 }
707 }
708}