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 {
17 fidl_fuchsia_component_decl as fdecl, fidl_fuchsia_component_test as ftest,
18 fidl_fuchsia_driver_development as fdd, fidl_fuchsia_driver_test as fdt,
19 fidl_fuchsia_io as fio, fuchsia_async as fasync,
20};
21
22fn clone(
23 dir: &fio::DirectoryProxy,
24) -> Result<fidl::endpoints::ClientEnd<fio::DirectoryMarker>, fidl::Error> {
25 let (client_end, server_end) = fidl::endpoints::create_endpoints::<fio::DirectoryMarker>();
26 dir.clone(fidl::endpoints::ServerEnd::new(server_end.into_channel()))?;
27 Ok(client_end)
28}
29
30async fn internal_serve(
31 stream: fidl_fuchsia_driver_test::InternalRequestStream,
32 test_pkg_dir: Arc<fio::DirectoryProxy>,
33 test_resolution_context: Arc<Option<fidl_fuchsia_component_resolution::Context>>,
34 boot_dir: Arc<Option<fio::DirectoryProxy>>,
35 boot_driver_components: Arc<Option<Vec<String>>>,
36) {
37 stream
38 .try_for_each_concurrent(None, |request| {
39 let test_pkg_dir = test_pkg_dir.clone();
40 let test_resolution_context = test_resolution_context.clone();
41 let boot_dir = boot_dir.clone();
42 let boot_driver_components = boot_driver_components.clone();
43 async move {
44 match request {
45 fidl_fuchsia_driver_test::InternalRequest::GetTestPackage { responder } => {
46 let cloned = clone(test_pkg_dir.as_ref())?;
47 responder.send(Ok(Some(cloned)))?;
48 }
49 fidl_fuchsia_driver_test::InternalRequest::GetTestResolutionContext {
50 responder,
51 } => responder.send(Ok(test_resolution_context.as_ref().as_ref()))?,
52 fidl_fuchsia_driver_test::InternalRequest::GetBootDirectory { responder } => {
53 match boot_dir.as_ref() {
54 Some(boot_dir) => {
55 let cloned = clone(boot_dir)?;
56 responder.send(Ok(Some(cloned)))?;
57 }
58 None => responder.send(Ok(None))?,
59 }
60 }
61 fidl_fuchsia_driver_test::InternalRequest::GetBootDriverOverrides {
62 responder,
63 } => responder.send(Ok(boot_driver_components
64 .as_ref()
65 .clone()
66 .unwrap_or(vec![])
67 .as_slice()))?,
68 };
69 Ok(())
70 }
71 })
72 .await
73 .expect("fuchsia.driver.test.Internal failed.");
74}
75
76async fn resource_provider_serve(
77 stream: fidl_fuchsia_driver_test::ResourceProviderRequestStream,
78 devicetree: Arc<Option<zx::Vmo>>,
79) {
80 stream
81 .try_for_each_concurrent(None, |request| {
82 let devicetree = devicetree.clone();
83 async move {
84 match request {
85 fidl_fuchsia_driver_test::ResourceProviderRequest::GetDeviceTree {
86 responder,
87 } => {
88 if let Some(vmo) = devicetree.as_ref().as_ref().map(|d| {
89 d.duplicate_handle(zx::Rights::SAME_RIGHTS).expect("duplicate")
90 }) {
91 responder.send(Ok(vmo))?;
92 } else {
93 responder.send(Err(zx::Status::NOT_FOUND.into_raw()))?;
94 }
95 }
96 };
97 Ok(())
98 }
99 })
100 .await
101 .expect("fuchsia.driver.test.Internal failed.");
102}
103
104async fn run_internal_server(
105 handles: LocalComponentHandles,
106 test_pkg_dir: Arc<fio::DirectoryProxy>,
107 test_resolution_context: Arc<Option<fidl_fuchsia_component_resolution::Context>>,
108 boot_dir: Arc<Option<fio::DirectoryProxy>>,
109 boot_driver_components: Arc<Option<Vec<String>>>,
110 devicetree: Arc<Option<zx::Vmo>>,
111) -> Result<()> {
112 let mut fs = ServiceFs::new();
113
114 fs.dir("svc").add_fidl_service(
115 move |stream: fidl_fuchsia_driver_test::InternalRequestStream| {
116 fasync::Task::spawn(internal_serve(
117 stream,
118 test_pkg_dir.clone(),
119 test_resolution_context.clone(),
120 boot_dir.clone(),
121 boot_driver_components.clone(),
122 ))
123 .detach();
124 },
125 );
126 fs.dir("svc").add_fidl_service(
127 move |stream: fidl_fuchsia_driver_test::ResourceProviderRequestStream| {
128 fasync::Task::spawn(resource_provider_serve(stream, devicetree.clone())).detach();
129 },
130 );
131 fs.serve_connection(handles.outgoing_dir)?;
132 fs.collect::<()>().await;
133 Ok(())
134}
135
136fn capabilities_eq_name(a: &ftest::Capability, b: &ftest::Capability) -> bool {
138 match (a, b) {
139 (ftest::Capability::Protocol(a), ftest::Capability::Protocol(b)) => a.name == b.name,
140 (ftest::Capability::Directory(a), ftest::Capability::Directory(b)) => a.name == b.name,
141 (ftest::Capability::Storage(a), ftest::Capability::Storage(b)) => a.name == b.name,
142 (ftest::Capability::Service(a), ftest::Capability::Service(b)) => a.name == b.name,
143 (ftest::Capability::EventStream(a), ftest::Capability::EventStream(b)) => a.name == b.name,
144 (ftest::Capability::Config(a), ftest::Capability::Config(b)) => a.name == b.name,
145 (ftest::Capability::Dictionary(a), ftest::Capability::Dictionary(b)) => a.name == b.name,
146 (ftest::Capability::Resolver(a), ftest::Capability::Resolver(b)) => a.name == b.name,
147 (ftest::Capability::Runner(a), ftest::Capability::Runner(b)) => a.name == b.name,
148 _ => false,
149 }
150}
151
152#[derive(Debug, Clone, Default)]
153pub struct Options {
154 using_subpackage: Option<bool>,
155 driver_offers: Option<(Ref, Vec<ftest::Capability>)>,
156 driver_exposes: Option<Vec<ftest::Capability>>,
157 extra_realm_capabilities: Vec<(ftest::Capability, Ref)>,
158}
159
160impl Options {
161 pub fn new() -> Self {
162 Self::default()
163 }
164
165 pub fn using_subpackage(mut self, using_subpackage: bool) -> Self {
166 self.using_subpackage = Some(using_subpackage);
167 self
168 }
169
170 pub fn driver_offers(mut self, provider: Ref, offers: Vec<ftest::Capability>) -> Self {
171 self.driver_offers = Some((provider, offers));
172 self
173 }
174
175 pub fn driver_exposes(mut self, exposes: Vec<ftest::Capability>) -> Self {
176 self.driver_exposes = Some(exposes);
177 self
178 }
179
180 pub fn add_extra_realm_capability(
181 mut self,
182 capability: ftest::Capability,
183 from: impl Into<Ref>,
184 ) -> Self {
185 self.extra_realm_capabilities.push((capability, from.into()));
186 self
187 }
188}
189
190#[async_trait::async_trait]
191pub trait DriverTestRealmBuilder {
192 async fn driver_test_realm_setup(
196 &self,
197 options: Options,
198 args: fdt::RealmArgs,
199 ) -> Result<&Self>;
200}
201
202#[async_trait::async_trait]
203impl DriverTestRealmBuilder for RealmBuilder {
204 async fn driver_test_realm_setup(
205 &self,
206 options: Options,
207 args: fdt::RealmArgs,
208 ) -> Result<&Self> {
209 let manifest_provider =
210 fuchsia_component::client::connect_to_protocol::<fdt::ManifestProviderMarker>()?;
211 let stream = manifest_provider
212 .get_manifest(&fdt::GetManifestRequest {
213 using_subpackage: options.using_subpackage,
214 ..Default::default()
215 })
216 .await?
217 .expect("manifest stream");
218
219 let mut manifest: Vec<u8> = vec![];
220 loop {
221 let mut read = stream.read_to_vec(
222 zx::StreamReadOptions::empty(),
223 fidl_fuchsia_io::MAX_TRANSFER_SIZE as usize,
224 )?;
225 if read.is_empty() {
226 break;
227 }
228 manifest.append(&mut read);
229 }
230
231 let component = fidl::unpersist::<fdecl::Component>(manifest.as_slice())
232 .context("unpersisting the manifest vector")?;
233
234 let realm = self
237 .add_child_realm_from_decl(
238 "driver_test_realm",
239 component.fidl_into_native(),
240 ChildOptions::new(),
241 )
242 .await?;
243
244 self.add_route(
246 Route::new()
247 .capability(Capability::protocol_by_name("fuchsia.diagnostics.ArchiveAccessor"))
248 .from(Ref::parent())
249 .to(&realm),
250 )
251 .await?;
252
253 let dtr_support: ChildRef = "dtr_support".into();
254 let fake_resolver: ChildRef = "fake_resolver".into();
255 let driver_manager: ChildRef = "driver_manager".into();
256 let driver_index: ChildRef = "driver_index".into();
257
258 let boot_drivers: CollectionRef = "boot-drivers".into();
259 let base_drivers: CollectionRef = "base-drivers".into();
260 let full_drivers: CollectionRef = "full-drivers".into();
261
262 let devicetree = Arc::new(args.devicetree);
263
264 let test_component = if let Some(test_component) = args.test_component {
266 test_component
267 } else {
268 let realm = fuchsia_component::client::connect_to_protocol::<
269 fidl_fuchsia_component::RealmMarker,
270 >()?;
271
272 realm.get_resolved_info().await.unwrap().unwrap()
273 };
274
275 let test_resolution_context = Arc::new(test_component.resolution_context);
276 let test_pkg_dir = Arc::new(
277 test_component.package.expect("a pkg").directory.expect("a directory").into_proxy(),
278 );
279
280 let boot_dir = Arc::new(match args.boot {
281 Some(boot_dir) => {
282 if !boot_dir.is_invalid_handle() {
283 Some(boot_dir.into_proxy())
284 } else {
285 None
286 }
287 }
288 None => None,
289 });
290 let boot_driver_components = Arc::new(args.boot_driver_components);
291
292 self.add_route(
294 Route::new()
295 .capability(
296 Capability::protocol_by_name("fuchsia.component.resolution.Resolver-hermetic")
297 .optional(),
298 )
299 .capability(
300 Capability::protocol_by_name("fuchsia.pkg.PackageResolver-hermetic").optional(),
301 )
302 .from(Ref::parent())
303 .to(&realm),
304 )
305 .await?;
306
307 let driver_test_internal = realm
309 .add_local_child(
310 "driver_test_internal",
311 move |handles| {
312 let test_pkg_dir = test_pkg_dir.clone();
313 let test_resolution_context = test_resolution_context.clone();
314 let boot_dir = boot_dir.clone();
315 let boot_driver_components = boot_driver_components.clone();
316 let devicetree = devicetree.clone();
317
318 Box::pin(run_internal_server(
319 handles,
320 test_pkg_dir,
321 test_resolution_context,
322 boot_dir,
323 boot_driver_components,
324 devicetree,
325 ))
326 },
327 ChildOptions::new(),
328 )
329 .await?;
330
331 let mut tunnel_boot_items = false;
334 let mut voided_offers: Vec<ftest::Capability> = vec![
335 Capability::protocol_by_name("fuchsia.tracing.provider.Registry").optional().into(),
336 Capability::protocol_by_name("fuchsia.boot.WriteOnlyLog").optional().into(),
337 Capability::protocol_by_name("fuchsia.scheduler.RoleManager").optional().into(),
338 Capability::protocol_by_name("fuchsia.boot.Items").optional().into(),
339 Capability::protocol_by_name("fuchsia.kernel.IommuResource").optional().into(),
340 Capability::protocol_by_name("fuchsia.diagnostics.LogFlusher").optional().into(),
341 Capability::protocol_by_name("fuchsia.kernel.MexecResource").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(),
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(),
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(),
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(),
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(),
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,
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(names.into()),
533 ),
534 }))
535 .await?;
536 realm
537 .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
538 name: "fuchsia.platform.bus.SoftwareDeviceIds".parse()?,
539 value: cm_rust::ConfigValue::Vector(
540 cm_rust::ConfigVectorValue::Uint32Vector(ids.into()),
541 ),
542 }))
543 .await?;
544 Ref::self_()
545 }
546 None => Ref::void(),
547 };
548
549 realm
551 .add_route(
552 Route::new()
553 .capability(Capability::configuration("fuchsia.driver.BindEager"))
554 .capability(Capability::configuration("fuchsia.driver.DisabledDrivers"))
555 .capability(Capability::configuration(
556 "fuchsia.driver.index.StopOnIdleTimeoutMillis",
557 ))
558 .from(Ref::self_())
559 .to(&driver_index),
560 )
561 .await?;
562
563 realm
564 .add_route(
565 Route::new()
566 .capability(Capability::configuration("fuchsia.driver.manager.RootDriver"))
567 .from(Ref::self_())
568 .to(&driver_manager),
569 )
570 .await?;
571
572 realm
573 .add_route(
574 Route::new()
575 .capability(
576 Capability::configuration(
577 "fuchsia.driver.manager.SetRootDriverHostCritical",
578 )
579 .optional(),
580 )
581 .capability(
582 Capability::configuration("fuchsia.driver.manager.SuspendTimeoutFallback")
583 .optional(),
584 )
585 .from(Ref::void())
586 .to(&driver_manager),
587 )
588 .await?;
589
590 realm
591 .add_route(
592 Route::new()
593 .capability(Capability::configuration(
594 "fuchsia.driver.testrealm.TunnelBootItems",
595 ))
596 .capability(Capability::configuration("fuchsia.driver.testrealm.BoardName"))
597 .capability(Capability::configuration("fuchsia.driver.testrealm.PlatformVid"))
598 .capability(Capability::configuration("fuchsia.driver.testrealm.PlatformPid"))
599 .from(Ref::self_())
600 .to(&dtr_support),
601 )
602 .await?;
603
604 realm
605 .add_route(
606 Route::new()
607 .capability(Capability::configuration("fuchsia.power.WaitForSuspendingToken"))
608 .from(Ref::self_())
609 .to(&driver_manager),
610 )
611 .await?;
612
613 realm
614 .add_route(
615 Route::new()
616 .capability(
617 Capability::configuration("fuchsia.platform.bus.SoftwareDeviceNames")
618 .optional(),
619 )
620 .capability(
621 Capability::configuration("fuchsia.platform.bus.SoftwareDeviceIds")
622 .optional(),
623 )
624 .from(software_devs_src)
625 .to(&boot_drivers),
626 )
627 .await?;
628
629 realm
631 .add_route(
632 Route::new()
633 .capability(Capability::protocol_by_name("fuchsia.driver.test.Internal"))
634 .from(&driver_test_internal)
635 .to(&fake_resolver),
636 )
637 .await?;
638
639 self.add_route(
641 Route::new()
642 .capability(Capability::directory("dev-class"))
643 .capability(Capability::directory("dev-topological"))
644 .capability(Capability::protocol_by_name(
645 "fuchsia.driver.registrar.DriverRegistrar",
646 ))
647 .capability(Capability::protocol_by_name("fuchsia.driver.development.Manager"))
648 .capability(Capability::protocol_by_name(
649 "fuchsia.driver.framework.CompositeNodeManager",
650 ))
651 .capability(Capability::protocol_by_name("fuchsia.system.state.Administrator"))
652 .from(&realm)
653 .to(Ref::parent()),
654 )
655 .await?;
656 Ok(&self)
658 }
659}
660
661#[async_trait::async_trait]
662pub trait DriverTestRealmInstance {
663 fn driver_test_realm_connect_to_dev(&self) -> Result<fio::DirectoryProxy>;
665
666 async fn wait_for_bootup(&self) -> Result<()>;
670
671 async fn wait_for_node(&self, moniker: &str) -> Result<fdd::NodeInfo>;
673}
674
675#[async_trait::async_trait]
676impl DriverTestRealmInstance for RealmInstance {
677 fn driver_test_realm_connect_to_dev(&self) -> Result<fio::DirectoryProxy> {
678 fuchsia_fs::directory::open_directory_async(
679 self.root.get_exposed_dir(),
680 "dev-topological",
681 fio::Flags::empty(),
682 )
683 .map_err(Into::into)
684 }
685
686 async fn wait_for_bootup(&self) -> Result<()> {
687 let manager: fdd::ManagerProxy = self.root.connect_to_protocol_at_exposed_dir()?;
688 manager.wait_for_bootup().await?;
689 Ok(())
690 }
691
692 async fn wait_for_node(&self, moniker: &str) -> Result<fdd::NodeInfo> {
693 let manager: fdd::ManagerProxy = self.root.connect_to_protocol_at_exposed_dir()?;
694 loop {
695 let (iterator, iterator_server) =
696 fidl::endpoints::create_proxy::<fdd::NodeInfoIteratorMarker>();
697 manager.get_node_info(&[moniker.to_string()], iterator_server, true)?;
698 let next = iterator.get_next().await;
699 if let Ok(nodes) = next
700 && !nodes.is_empty()
701 && nodes[0].moniker == Some(moniker.to_string())
702 {
703 return Ok(nodes[0].clone());
704 }
705 }
706 }
707}