blob: 2515b220dc8b86d2b3843f38c373f8de83c1d011 [file] [log] [blame] [edit]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use {
anyhow::Error,
fidl::endpoints::ServerEnd,
fidl::endpoints::{create_proxy, ClientEnd, Proxy},
fidl_fuchsia_component_decl as fdecl, fidl_fuchsia_component_resolution as fresolution,
fidl_fuchsia_driver_test as fdt, fidl_fuchsia_io as fio, fidl_fuchsia_mem as fmem,
fuchsia_async::Task,
fuchsia_component_test::{
Capability, ChildOptions, LocalComponentHandles, RealmBuilder, Ref, Route,
},
fuchsia_driver_test::{DriverTestRealmBuilder, DriverTestRealmInstance},
futures::{FutureExt as _, TryStreamExt as _},
std::{collections::HashMap, sync::Arc},
vfs::{
directory::entry::{DirectoryEntry, EntryInfo},
execution_scope::ExecutionScope,
file::vmo::VmoFile,
},
};
type Directory =
Arc<vfs::directory::simple::Simple<vfs::directory::immutable::connection::ImmutableConnection>>;
struct FakePackageVariant {
dir: Directory,
meta_file: Arc<VmoFile>,
}
impl FakePackageVariant {
/// Creates a new struct that acts a directory serving `dir`. If the "meta"
/// node within the directory is read as a file then `meta_file` is served
/// as the contents of the file.
pub fn new(dir: Directory, meta_file: Arc<VmoFile>) -> Self {
Self { dir, meta_file }
}
}
impl DirectoryEntry for FakePackageVariant {
fn open(
self: Arc<Self>,
scope: ExecutionScope,
flags: fio::OpenFlags,
path: vfs::path::Path,
server_end: ServerEnd<fio::NodeMarker>,
) {
fn open_as_file(flags: fio::OpenFlags) -> bool {
!flags.intersects(fio::OpenFlags::DIRECTORY | fio::OpenFlags::NODE_REFERENCE)
}
// vfs::path::Path::as_str() is an object relative path expression [1],
// except that it may:
// 1. have a trailing "/"
// 2. be exactly "."
// 3. be longer than 4,095 bytes
// The .is_empty() check above rules out "." and the following line
// removes the possible trailing "/".
// [1] https://fuchsia.dev/fuchsia-src/concepts/process/namespaces?hl=en#object_relative_path_expressions
let canonical_path = path.as_ref().strip_suffix("/").unwrap_or_else(|| path.as_ref());
if canonical_path == "meta" && open_as_file(flags) {
let meta_file = Arc::clone(&self.meta_file);
meta_file.open(scope, flags, vfs::path::Path::dot(), server_end);
} else {
let dir = Arc::clone(&self.dir);
dir.open(scope, flags, path, server_end);
}
}
fn entry_info(&self) -> EntryInfo {
EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Directory)
}
}
struct FakeBaseComponentResolver {
base_packages: HashMap<String, fio::DirectoryProxy>,
}
impl FakeBaseComponentResolver {
pub fn new(base_packages: HashMap<String, fio::DirectoryProxy>) -> Self {
Self { base_packages }
}
pub async fn handle_request_stream(
self: Arc<Self>,
mut stream: fresolution::ResolverRequestStream,
) {
while let Some(req) = stream
.try_next()
.await
.expect("read fuchsia.component.resolution/Resolver request stream")
{
match req {
fresolution::ResolverRequest::Resolve { component_url, responder } => {
let pkg = match self.base_packages.get(&component_url) {
Some(proxy) => {
let proxy_clone = fuchsia_fs::directory::clone_no_describe(proxy, None)
.expect("failed to clone");
ClientEnd::new(proxy_clone.into_channel().unwrap().into_zx_channel())
}
None => panic!(
"FakeBaseComponentResolver resolve unknown component {component_url}"
),
};
responder
.send(Ok(fresolution::Component {
decl: Some(fmem::Data::Bytes(
fidl::persist(&fdecl::Component::default()).unwrap(),
)),
package: Some(fresolution::Package {
directory: Some(pkg),
..Default::default()
}),
..Default::default()
}))
.expect("error sending response");
}
req => panic!("unexpected fuchsia.component.resolution/Resolver request {req:?}"),
}
}
}
}
fn serve_vfs_dir(
root: Arc<impl DirectoryEntry>,
server_end: ServerEnd<fio::NodeMarker>,
) -> Task<()> {
let fs_scope = ExecutionScope::new();
root.open(
fs_scope.clone(),
fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_EXECUTABLE,
vfs::path::Path::dot(),
server_end,
);
Task::spawn(async move { fs_scope.wait().await })
}
async fn serve_fake_filesystem(
base_packages: HashMap<String, fio::DirectoryProxy>,
handles: LocalComponentHandles,
) -> Result<(), anyhow::Error> {
let root: Directory = vfs::pseudo_directory! {
"svc" => vfs::pseudo_directory! {
"fuchsia.component.resolution.Resolver-base" => vfs::service::host(move |stream| {
let base_packages = base_packages.clone();
Arc::new(FakeBaseComponentResolver::new(base_packages)).handle_request_stream(stream)
}
),
},
"boot" => vfs::pseudo_directory! {
"meta" => vfs::pseudo_directory! {},
},
};
serve_vfs_dir(root, ServerEnd::new(handles.outgoing_dir.into_channel())).await;
Ok::<(), anyhow::Error>(())
}
async fn create_realm(
base_packages: HashMap<String, fio::DirectoryProxy>,
) -> Result<fuchsia_component_test::RealmInstance, Error> {
let builder = RealmBuilder::new().await?;
let fake_filesystem = builder
.add_local_child(
"fake_filesystem",
move |h: LocalComponentHandles| serve_fake_filesystem(base_packages.clone(), h).boxed(),
ChildOptions::new().eager(),
)
.await
.expect("mock component added");
let driver_manager = builder
.add_child(
"driver_manager",
"fuchsia-pkg://fuchsia.com/ddk-firmware-test#meta/driver-manager-realm.cm",
ChildOptions::new(),
)
.await?;
builder
.add_route(
Route::new()
.capability(Capability::protocol_by_name(
"fuchsia.component.resolution.Resolver-base",
))
.capability(Capability::directory("boot").path("/boot").rights(fio::R_STAR_DIR))
.from(&fake_filesystem)
.to(&driver_manager),
)
.await?;
builder
.add_route(
Route::new()
.capability(Capability::directory("dev-topological"))
.capability(Capability::protocol_by_name("fuchsia.device.manager.Administrator"))
.capability(Capability::protocol_by_name("fuchsia.driver.test.Realm"))
.from(&driver_manager)
.to(Ref::parent()),
)
.await?;
builder
.add_route(
Route::new()
.capability(Capability::protocol_by_name("fuchsia.logger.LogSink"))
.capability(Capability::protocol_by_name("fuchsia.process.Launcher"))
.from(Ref::parent())
.to(&driver_manager),
)
.await?;
let realm = builder.build().await?;
realm.driver_test_realm_start(fdt::RealmArgs::default()).await?;
Ok(realm)
}
// TODO(fxbug.dev/116950): Re-enable after merging test into driver test realm proper.
#[ignore]
#[fuchsia::test]
async fn load_package_firmware_test() -> Result<(), Error> {
let firmware_file = vfs::file::vmo::read_only(b"this is some firmware\n");
let driver_dir = vfs::remote::remote_dir(fuchsia_fs::directory::open_in_namespace(
"/pkg/driver",
fuchsia_fs::OpenFlags::RIGHT_READABLE | fuchsia_fs::OpenFlags::RIGHT_EXECUTABLE,
)?);
let meta_dir = vfs::remote::remote_dir(fuchsia_fs::directory::open_in_namespace(
"/pkg/meta",
fuchsia_fs::OpenFlags::RIGHT_READABLE,
)?);
let bind_dir = vfs::remote::remote_dir(fuchsia_fs::directory::open_in_namespace(
"/pkg/bind",
fuchsia_fs::OpenFlags::RIGHT_READABLE,
)?);
let base_manifest = vfs::file::vmo::read_only(
r#"[{"driver_url": "fuchsia-pkg://fuchsia.com/my-package#meta/ddk-firmware-test-driver.cm"}]"#,
);
let my_package = FakePackageVariant::new(
vfs::pseudo_directory! {
"driver" => driver_dir,
"meta" => meta_dir,
"bind" => bind_dir,
"lib" => vfs::pseudo_directory! {
"firmware" => vfs::pseudo_directory! {
"package-firmware" => firmware_file,
},
},
},
// Hash is arbitrary and is not read in tests, however, some value needs
// to be provided, otherwise, the driver will fail to load.
vfs::file::vmo::read_only(
"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20",
),
);
let (config_client, config_server) = create_proxy::<fio::DirectoryMarker>().unwrap();
let _config_task = serve_vfs_dir(
vfs::pseudo_directory! {
"config" => vfs::pseudo_directory! { "base-driver-manifest.json" => base_manifest },
},
ServerEnd::new(config_server.into_channel()),
);
let (pkg_client, pkg_server) = create_proxy::<fio::DirectoryMarker>().unwrap();
let _pkg_task = serve_vfs_dir(my_package.into(), ServerEnd::new(pkg_server.into_channel()));
let base_packages = HashMap::from([
("fuchsia-pkg://fuchsia.com/driver-manager-base-config".to_owned(), config_client),
("fuchsia-pkg://fuchsia.com/my-package".to_owned(), pkg_client),
]);
let instance = create_realm(base_packages).await?;
// This is unused but connecting to it causes DriverManager to start.
let _admin = instance
.root
.connect_to_protocol_at_exposed_dir::<fidl_fuchsia_device_manager::AdministratorMarker>()?;
let out_dir = instance.root.get_exposed_dir();
let driver_proxy = device_watcher::recursive_wait_and_open::<
fidl_fuchsia_device_firmware_test::TestDeviceMarker,
>(&out_dir, "dev-topological/sys/test/ddk-firmware-test-device-0")
.await?;
// Check that we can load firmware out of /boot.
driver_proxy.load_firmware("test-firmware").await?.unwrap();
// Check that we can load firmware from our package.
driver_proxy.load_firmware("package-firmware").await?.unwrap();
// Check that loading unknown name fails.
assert!(
driver_proxy.load_firmware("test-bad").await? == Err(fuchsia_zircon::sys::ZX_ERR_NOT_FOUND)
);
Ok(())
}
#[fuchsia::test]
async fn load_package_firmware_test_dfv2() -> Result<(), Error> {
// Create the RealmBuilder.
let builder = RealmBuilder::new().await?;
builder.driver_test_realm_setup().await?;
let instance = builder.build().await?;
// Start DriverTestRealm
let args = fdt::RealmArgs {
use_driver_framework_v2: Some(true),
root_driver: Some("fuchsia-boot:///#meta/test-parent-sys.cm".to_string()),
..Default::default()
};
instance.driver_test_realm_start(args).await?;
// Connect to our driver.
let dev = instance.driver_test_realm_connect_to_dev()?;
let driver_proxy = device_watcher::recursive_wait_and_open::<
fidl_fuchsia_device_firmware_test::TestDeviceMarker,
>(&dev, "sys/test/ddk-firmware-test-device-0")
.await?;
// Check that we can load firmware from our package.
driver_proxy.load_firmware("test-firmware").await?.unwrap();
// Check that loading unknown name fails.
assert_eq!(
driver_proxy.load_firmware("test-bad").await?,
Err(fuchsia_zircon::sys::ZX_ERR_NOT_FOUND)
);
Ok(())
}