1use anyhow::{Context as _, Result};
6use cm_rust::FidlIntoNative;
7use fidl_fuchsia_component_decl as fdecl;
8use fidl_fuchsia_component_test as ftest;
9use fidl_fuchsia_driver_development as fdd;
10use fidl_fuchsia_driver_test as fdt;
11use fidl_fuchsia_io as fio;
12use flyweights::FlyStr;
13use fuchsia_async as fasync;
14use fuchsia_component::server::ServiceFs;
15use fuchsia_component_test::{
16 Capability, ChildOptions, ChildRef, CollectionRef, LocalComponentHandles, RealmBuilder,
17 RealmInstance, Ref, Route,
18};
19use futures::{StreamExt, TryStreamExt};
20use std::sync::Arc;
21use zx::AsHandleRef;
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.PowerResource").optional().into(),
343 ];
344 for (capability, from) in options.extra_realm_capabilities {
345 voided_offers.retain(|voided| !capabilities_eq_name(voided, &capability));
347
348 if from != Ref::void()
349 && capabilities_eq_name(
350 &Capability::protocol_by_name("fuchsia.boot.Items").into(),
351 &capability,
352 )
353 {
354 tunnel_boot_items = true;
355 }
356
357 self.add_route(Route::new().capability(capability).from(from).to(&realm)).await?;
358 }
359
360 for voided in voided_offers {
362 self.add_route(Route::new().capability(voided).from(Ref::void()).to(&realm)).await?;
363 }
364
365 if args.dtr_offers.is_some() {
368 panic!("Please use |Options::driver_offers| instead of dtr_offers.")
369 }
370 if let Some((provider, offers)) = options.driver_offers {
371 for offer in offers {
372 self.add_route(
373 Route::new().capability(offer.clone()).from(provider.clone()).to(&realm),
374 )
375 .await?;
376 realm
377 .add_route(
378 Route::new()
379 .capability(offer)
380 .from(Ref::parent())
381 .to(&boot_drivers)
382 .to(&base_drivers)
383 .to(&full_drivers),
384 )
385 .await?;
386 }
387 }
388
389 if args.dtr_exposes.is_some() {
391 panic!("Please use |Options::driver_exposes| instead of dtr_exposes.")
392 }
393 if let Some(exposes) = options.driver_exposes {
394 for expose in exposes {
395 realm
396 .add_route(
397 Route::new()
398 .capability(expose.clone())
399 .from(&boot_drivers)
400 .to(Ref::parent()),
401 )
402 .await?;
403 realm
404 .add_route(
405 Route::new()
406 .capability(expose.clone())
407 .from(&base_drivers)
408 .to(Ref::parent()),
409 )
410 .await?;
411 realm
412 .add_route(
413 Route::new()
414 .capability(expose.clone())
415 .from(&full_drivers)
416 .to(Ref::parent()),
417 )
418 .await?;
419
420 self.add_route(Route::new().capability(expose).from(&realm).to(Ref::parent()))
421 .await?;
422 }
423 }
424
425 realm
427 .add_route(
428 Route::new()
429 .capability(Capability::protocol_by_name(
430 "fuchsia.driver.test.ResourceProvider",
431 ))
432 .from(&driver_test_internal)
433 .to(&dtr_support),
434 )
435 .await?;
436
437 realm
439 .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
440 name: "fuchsia.driver.testrealm.TunnelBootItems".parse()?,
441 value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::Bool(
442 tunnel_boot_items,
443 )),
444 }))
445 .await?;
446
447 realm
448 .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
449 name: "fuchsia.driver.testrealm.BoardName".parse()?,
450 value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::String(
451 args.board_name.unwrap_or_default().into(),
452 )),
453 }))
454 .await?;
455
456 realm
457 .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
458 name: "fuchsia.driver.testrealm.PlatformVid".parse()?,
459 value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::String(
460 args.platform_vid.map(|v| v.to_string()).unwrap_or_default().into(),
461 )),
462 }))
463 .await?;
464
465 realm
466 .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
467 name: "fuchsia.driver.testrealm.PlatformPid".parse()?,
468 value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::String(
469 args.platform_pid.map(|v| v.to_string()).unwrap_or_default().into(),
470 )),
471 }))
472 .await?;
473
474 let bind_eager = args.driver_bind_eager.unwrap_or_default();
475 realm
476 .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
477 name: "fuchsia.driver.BindEager".parse()?,
478 value: cm_rust::ConfigValue::Vector(cm_rust::ConfigVectorValue::StringVector(
479 bind_eager.into_iter().map(FlyStr::new).collect::<Box<[_]>>(),
480 )),
481 }))
482 .await?;
483
484 let driver_disable = args.driver_disable.unwrap_or_default();
485 realm
486 .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
487 name: "fuchsia.driver.DisabledDrivers".parse()?,
488 value: cm_rust::ConfigValue::Vector(cm_rust::ConfigVectorValue::StringVector(
489 driver_disable.into_iter().map(FlyStr::new).collect::<Box<[_]>>(),
490 )),
491 }))
492 .await?;
493
494 let driver_index_stop_timeout_millis = args.driver_index_stop_timeout_millis.unwrap_or(-1);
495 realm
496 .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
497 name: "fuchsia.driver.index.StopOnIdleTimeoutMillis".parse()?,
498 value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::Int64(
499 driver_index_stop_timeout_millis,
500 )),
501 }))
502 .await?;
503 realm
504 .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
505 name: "fuchsia.power.WaitForSuspendingToken".parse()?,
506 value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::Bool(false)),
507 }))
508 .await?;
509
510 let root_driver = match args.root_driver {
511 Some(val) => val,
512 None => "fuchsia-boot:///dtr#meta/test-parent-sys.cm".to_string(),
513 };
514 realm
515 .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
516 name: "fuchsia.driver.manager.RootDriver".parse()?,
517 value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::String(
518 root_driver.into(),
519 )),
520 }))
521 .await?;
522
523 let software_devs_src = match args.software_devices {
525 Some(devs) => {
526 let names = devs.iter().map(|dev| dev.device_name.clone()).collect::<Vec<_>>();
527 let ids = devs.iter().map(|dev| dev.device_id).collect::<Vec<_>>();
528 realm
529 .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
530 name: "fuchsia.platform.bus.SoftwareDeviceNames".parse()?,
531 value: cm_rust::ConfigValue::Vector(
532 cm_rust::ConfigVectorValue::StringVector(
533 names.into_iter().map(FlyStr::new).collect::<Box<[_]>>(),
534 ),
535 ),
536 }))
537 .await?;
538 realm
539 .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
540 name: "fuchsia.platform.bus.SoftwareDeviceIds".parse()?,
541 value: cm_rust::ConfigValue::Vector(
542 cm_rust::ConfigVectorValue::Uint32Vector(ids.into()),
543 ),
544 }))
545 .await?;
546 Ref::self_()
547 }
548 None => Ref::void(),
549 };
550
551 realm
553 .add_route(
554 Route::new()
555 .capability(Capability::configuration("fuchsia.driver.BindEager"))
556 .capability(Capability::configuration("fuchsia.driver.DisabledDrivers"))
557 .capability(Capability::configuration(
558 "fuchsia.driver.index.StopOnIdleTimeoutMillis",
559 ))
560 .from(Ref::self_())
561 .to(&driver_index),
562 )
563 .await?;
564
565 realm
566 .add_route(
567 Route::new()
568 .capability(Capability::configuration("fuchsia.driver.manager.RootDriver"))
569 .from(Ref::self_())
570 .to(&driver_manager),
571 )
572 .await?;
573
574 realm
575 .add_route(
576 Route::new()
577 .capability(
578 Capability::configuration(
579 "fuchsia.driver.manager.SetRootDriverHostCritical",
580 )
581 .optional(),
582 )
583 .capability(
584 Capability::configuration("fuchsia.driver.manager.SuspendTimeoutFallback")
585 .optional(),
586 )
587 .from(Ref::void())
588 .to(&driver_manager),
589 )
590 .await?;
591
592 realm
593 .add_route(
594 Route::new()
595 .capability(Capability::configuration(
596 "fuchsia.driver.testrealm.TunnelBootItems",
597 ))
598 .capability(Capability::configuration("fuchsia.driver.testrealm.BoardName"))
599 .capability(Capability::configuration("fuchsia.driver.testrealm.PlatformVid"))
600 .capability(Capability::configuration("fuchsia.driver.testrealm.PlatformPid"))
601 .from(Ref::self_())
602 .to(&dtr_support),
603 )
604 .await?;
605
606 realm
607 .add_route(
608 Route::new()
609 .capability(Capability::configuration("fuchsia.power.WaitForSuspendingToken"))
610 .from(Ref::self_())
611 .to(&driver_manager),
612 )
613 .await?;
614
615 realm
616 .add_route(
617 Route::new()
618 .capability(
619 Capability::configuration("fuchsia.platform.bus.SoftwareDeviceNames")
620 .optional(),
621 )
622 .capability(
623 Capability::configuration("fuchsia.platform.bus.SoftwareDeviceIds")
624 .optional(),
625 )
626 .from(software_devs_src)
627 .to(&boot_drivers),
628 )
629 .await?;
630
631 realm
633 .add_route(
634 Route::new()
635 .capability(Capability::protocol_by_name("fuchsia.driver.test.Internal"))
636 .from(&driver_test_internal)
637 .to(&fake_resolver),
638 )
639 .await?;
640
641 self.add_route(
643 Route::new()
644 .capability(Capability::directory("dev-class"))
645 .capability(Capability::directory("dev-topological"))
646 .capability(Capability::protocol_by_name(
647 "fuchsia.driver.registrar.DriverRegistrar",
648 ))
649 .capability(Capability::protocol_by_name("fuchsia.driver.development.Manager"))
650 .capability(Capability::protocol_by_name(
651 "fuchsia.driver.framework.CompositeNodeManager",
652 ))
653 .capability(Capability::protocol_by_name("fuchsia.driver.framework.NodeManager"))
654 .capability(Capability::protocol_by_name("fuchsia.system.state.Administrator"))
655 .from(&realm)
656 .to(Ref::parent()),
657 )
658 .await?;
659 Ok(&self)
661 }
662}
663
664#[async_trait::async_trait]
665pub trait DriverTestRealmInstance {
666 fn driver_test_realm_connect_to_dev(&self) -> Result<fio::DirectoryProxy>;
668
669 async fn wait_for_bootup(&self) -> Result<()>;
673
674 async fn wait_for_node(&self, moniker: &str) -> Result<fdd::NodeInfo>;
676}
677
678#[async_trait::async_trait]
679impl DriverTestRealmInstance for RealmInstance {
680 fn driver_test_realm_connect_to_dev(&self) -> Result<fio::DirectoryProxy> {
681 fuchsia_fs::directory::open_directory_async(
682 self.root.get_exposed_dir(),
683 "dev-topological",
684 fio::Flags::empty(),
685 )
686 .map_err(Into::into)
687 }
688
689 async fn wait_for_bootup(&self) -> Result<()> {
690 let manager: fdd::ManagerProxy = self.root.connect_to_protocol_at_exposed_dir()?;
691 manager.wait_for_bootup().await?;
692 Ok(())
693 }
694
695 async fn wait_for_node(&self, moniker: &str) -> Result<fdd::NodeInfo> {
696 let manager: fdd::ManagerProxy = self.root.connect_to_protocol_at_exposed_dir()?;
697 loop {
698 let (iterator, iterator_server) =
699 fidl::endpoints::create_proxy::<fdd::NodeInfoIteratorMarker>();
700 manager.get_node_info(&[moniker.to_string()], iterator_server, true)?;
701 let next = iterator.get_next().await;
702 if let Ok(nodes) = next
703 && !nodes.is_empty()
704 && nodes[0].moniker == Some(moniker.to_string())
705 {
706 return Ok(nodes[0].clone());
707 }
708 }
709 }
710}