| // Copyright 2019 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::{create_proxy, ServerEnd}, |
| fidl_fuchsia_boot::FactoryItemsMarker, |
| fidl_fuchsia_factory::{ |
| AlphaFactoryStoreProviderMarker, CastCredentialsFactoryStoreProviderMarker, |
| MiscFactoryStoreProviderMarker, PlayReadyFactoryStoreProviderMarker, |
| WeaveFactoryStoreProviderMarker, WidevineFactoryStoreProviderMarker, |
| }, |
| fidl_fuchsia_io::{DirectoryMarker, DirectoryProxy}, |
| files_async::{self, DirentKind}, |
| fuchsia_async as fasync, |
| fuchsia_component::client::connect_to_service, |
| futures::stream::TryStreamExt, |
| io_util, |
| nom::HexDisplay, |
| std::path::PathBuf, |
| structopt::StructOpt, |
| }; |
| |
| #[derive(Debug, StructOpt)] |
| #[structopt( |
| name = "factoryctl command line tool, version 1.0.0", |
| about = "Commands to view factory contents" |
| )] |
| pub enum Opt { |
| #[structopt(name = "alpha")] |
| Alpha(FactoryStoreCmd), |
| #[structopt(name = "cast")] |
| Cast(FactoryStoreCmd), |
| #[structopt(name = "factory-items")] |
| FactoryItems(FactoryItemsCmd), |
| #[structopt(name = "misc")] |
| Misc(FactoryStoreCmd), |
| #[structopt(name = "playready")] |
| PlayReady(FactoryStoreCmd), |
| #[structopt(name = "weave")] |
| Weave(FactoryStoreCmd), |
| #[structopt(name = "widevine")] |
| Widevine(FactoryStoreCmd), |
| } |
| |
| #[derive(Debug, StructOpt)] |
| pub enum FactoryStoreCmd { |
| #[structopt(name = "list")] |
| List, |
| |
| #[structopt(name = "dump")] |
| Dump { name: String }, |
| } |
| |
| #[derive(Debug, StructOpt)] |
| pub enum FactoryItemsCmd { |
| #[structopt(name = "dump")] |
| Dump { extra: u32 }, |
| } |
| |
| const HEX_DISPLAY_CHUNK_SIZE: usize = 16; |
| |
| /// Prints the hexdump of `data` to stdout. |
| fn hexdump(data: &[u8]) { |
| println!("{}", data.to_hex(HEX_DISPLAY_CHUNK_SIZE)); |
| } |
| |
| /// Walks the given `dir`, printing the full path to every file. |
| async fn print_files(dir_proxy: &DirectoryProxy) -> Result<(), Error> { |
| let mut stream = files_async::readdir_recursive(dir_proxy, /*timeout=*/ None); |
| while let Some(entry) = stream.try_next().await? { |
| if entry.kind == DirentKind::File { |
| println!("{}", entry.name); |
| } |
| } |
| Ok(()) |
| } |
| |
| /// Processes a command from the command line. |
| async fn process_cmd<F>(cmd: FactoryStoreCmd, mut connect_fn: F) -> Result<(), Error> |
| where |
| F: FnMut(ServerEnd<DirectoryMarker>) -> (), |
| { |
| let (dir_proxy, dir_server_end) = create_proxy::<DirectoryMarker>()?; |
| connect_fn(dir_server_end); |
| |
| match cmd { |
| FactoryStoreCmd::List => print_files(&dir_proxy).await?, |
| FactoryStoreCmd::Dump { name } => { |
| let file = |
| io_util::open_file(&dir_proxy, &PathBuf::from(name), io_util::OPEN_RIGHT_READABLE)?; |
| let contents = io_util::read_file_bytes(&file).await?; |
| |
| match std::str::from_utf8(&contents) { |
| Ok(value) => { |
| println!("{}", value); |
| } |
| Err(_) => { |
| hexdump(&contents); |
| } |
| }; |
| } |
| }; |
| Ok(()) |
| } |
| |
| #[fasync::run_singlethreaded] |
| async fn main() -> Result<(), Error> { |
| let opt = Opt::from_args(); |
| |
| match opt { |
| Opt::Alpha(cmd) => { |
| let proxy = connect_to_service::<AlphaFactoryStoreProviderMarker>() |
| .expect("Failed to connect to AlphaFactoryStoreProvider service"); |
| process_cmd(cmd, move |server_end| proxy.get_factory_store(server_end).unwrap()).await |
| } |
| Opt::Cast(cmd) => { |
| let proxy = connect_to_service::<CastCredentialsFactoryStoreProviderMarker>() |
| .expect("Failed to connect to CastCredentialsFactoryStoreProvider service"); |
| process_cmd(cmd, move |server_end| proxy.get_factory_store(server_end).unwrap()).await |
| } |
| Opt::FactoryItems(cmd) => { |
| let proxy = connect_to_service::<FactoryItemsMarker>() |
| .expect("Failed to connect to FactoryItems service"); |
| |
| match cmd { |
| FactoryItemsCmd::Dump { extra } => { |
| let (vmo_opt, length) = proxy.get(extra).await.unwrap_or_else(|err| { |
| panic!("Failed to get factory item with extra {}: {:?}", extra, err); |
| }); |
| match vmo_opt { |
| Some(ref vmo) if length > 0 => { |
| let mut buffer = vec![0; length as usize]; |
| vmo.read(&mut buffer, 0)?; |
| hexdump(&buffer); |
| } |
| _ => eprintln!("No valid vmo returned"), |
| }; |
| println!("Length={}", length); |
| } |
| }; |
| Ok(()) |
| } |
| Opt::Misc(cmd) => { |
| let proxy = connect_to_service::<MiscFactoryStoreProviderMarker>() |
| .expect("Failed to connect to PlayReadyFactoryStoreProvider service"); |
| process_cmd(cmd, move |server_end| proxy.get_factory_store(server_end).unwrap()).await |
| } |
| Opt::PlayReady(cmd) => { |
| let proxy = connect_to_service::<PlayReadyFactoryStoreProviderMarker>() |
| .expect("Failed to connect to PlayReadyFactoryStoreProvider service"); |
| process_cmd(cmd, move |server_end| proxy.get_factory_store(server_end).unwrap()).await |
| } |
| Opt::Weave(cmd) => { |
| let proxy = connect_to_service::<WeaveFactoryStoreProviderMarker>() |
| .expect("Failed to connect to WeaveFactoryStoreProvider service"); |
| process_cmd(cmd, move |server_end| proxy.get_factory_store(server_end).unwrap()).await |
| } |
| Opt::Widevine(cmd) => { |
| let proxy = connect_to_service::<WidevineFactoryStoreProviderMarker>() |
| .expect("Failed to connect to WidevineFactoryStoreProvider service"); |
| process_cmd(cmd, move |server_end| proxy.get_factory_store(server_end).unwrap()).await |
| } |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use { |
| super::*, |
| fidl_fuchsia_boot::{FactoryItemsRequest, FactoryItemsRequestStream}, |
| fidl_fuchsia_factory::{ |
| AlphaFactoryStoreProviderRequest, AlphaFactoryStoreProviderRequestStream, |
| CastCredentialsFactoryStoreProviderRequest, |
| CastCredentialsFactoryStoreProviderRequestStream, MiscFactoryStoreProviderRequest, |
| MiscFactoryStoreProviderRequestStream, PlayReadyFactoryStoreProviderRequest, |
| PlayReadyFactoryStoreProviderRequestStream, WeaveFactoryStoreProviderRequest, |
| WeaveFactoryStoreProviderRequestStream, WidevineFactoryStoreProviderRequest, |
| WidevineFactoryStoreProviderRequestStream, |
| }, |
| fidl_fuchsia_io::{DirectoryProxy, MODE_TYPE_DIRECTORY, OPEN_RIGHT_READABLE}, |
| fuchsia_component::{ |
| client::AppBuilder, |
| server::{NestedEnvironment, ServiceFs}, |
| }, |
| fuchsia_vfs_pseudo_fs::{ |
| directory::entry::DirectoryEntry, |
| file::simple::{read_only, read_only_str}, |
| tree_builder::TreeBuilder, |
| }, |
| fuchsia_zircon as zx, |
| futures::{StreamExt, TryStreamExt}, |
| std::{iter, str::from_utf8}, |
| }; |
| |
| const FACTORYCTL_PKG_URL: &str = |
| "fuchsia-pkg://fuchsia.com/factoryctl_tests#meta/factoryctl.cmx"; |
| |
| const ALPHA_TXT_FILE_NAME: &str = "txt/alpha.txt"; |
| const CAST_TXT_FILE_NAME: &str = "txt/cast.txt"; |
| const MISC_TXT_FILE_NAME: &str = "misc/misc.txt"; |
| const PLAYREADY_TXT_FILE_NAME: &str = "txt/playready.txt"; |
| const WEAVE_TXT_FILE_NAME: &str = "txt/weave.txt"; |
| const WIDEVINE_TXT_FILE_NAME: &str = "widevine.txt"; |
| |
| const ALPHA_BIN_FILE_NAME: &str = "alpha.bin"; |
| const CAST_BIN_FILE_NAME: &str = "cast.bin"; |
| const MISC_BIN_FILE_NAME: &str = "bin/misc.bin"; |
| const PLAYREADY_BIN_FILE_NAME: &str = "playready/playready.bin"; |
| const WEAVE_BIN_FILE_NAME: &str = "weave.bin"; |
| const WIDEVINE_BIN_FILE_NAME: &str = "widevine.bin"; |
| |
| const ALPHA_TXT_FILE_CONTENTS: &str = "an alpha file"; |
| const CAST_TXT_FILE_CONTENTS: &str = "a cast file"; |
| const MISC_TXT_FILE_CONTENTS: &str = "a misc file"; |
| const PLAYREADY_TXT_FILE_CONTENTS: &str = "a playready file"; |
| const WEAVE_TXT_FILE_CONTENTS: &str = "a weave file"; |
| const WIDEVINE_TXT_FILE_CONTENTS: &str = "a widevine file"; |
| |
| const FACTORY_ITEM_CONTENTS: &[u8] = &[0xf0, 0xe8, 0x65, 0x94]; |
| const ALPHA_BIN_FILE_CONTENTS: &[u8] = &[0xaa, 0xbb, 0xcc, 0xdd]; |
| const CAST_BIN_FILE_CONTENTS: &[u8] = &[0x0, 0x18, 0xF1, 0x6d]; |
| const MISC_BIN_FILE_CONTENTS: &[u8] = &[0x0, 0xf3, 0x17, 0xb6]; |
| const PLAYREADY_BIN_FILE_CONTENTS: &[u8] = &[0x0e, 0xb8, 0x1a, 0xc6]; |
| const WEAVE_BIN_FILE_CONTENTS: &[u8] = &[0xab, 0xcd, 0xef, 0x1]; |
| const WIDEVINE_BIN_FILE_CONTENTS: &[u8] = &[0x0c, 0xee, 0x8a, 0x6f]; |
| |
| enum IncomingServices { |
| FactoryItems(FactoryItemsRequestStream), |
| AlphaFactoryStoreProvider(AlphaFactoryStoreProviderRequestStream), |
| CastCredentialsFactoryStoreProvider(CastCredentialsFactoryStoreProviderRequestStream), |
| MiscFactoryStoreProvider(MiscFactoryStoreProviderRequestStream), |
| PlayReadyFactoryStoreProvider(PlayReadyFactoryStoreProviderRequestStream), |
| WeaveFactoryStoreProvider(WeaveFactoryStoreProviderRequestStream), |
| WidevineFactoryStoreProvider(WidevineFactoryStoreProviderRequestStream), |
| } |
| |
| fn start_test_dir( |
| name: &'static str, |
| contents: &'static str, |
| name2: &'static str, |
| contents2: &'static [u8], |
| ) -> Result<DirectoryProxy, Error> { |
| let mut tree = TreeBuilder::empty_dir(); |
| tree.add_entry( |
| &name.split("/").collect::<Vec<&str>>(), |
| read_only_str(move || Ok(contents.to_owned())), |
| ) |
| .unwrap(); |
| tree.add_entry( |
| &name2.split("/").collect::<Vec<&str>>(), |
| read_only(move || Ok(contents2.to_vec())), |
| ) |
| .unwrap(); |
| let mut test_dir = tree.build(); |
| |
| let (test_dir_proxy, test_dir_service) = |
| fidl::endpoints::create_proxy::<DirectoryMarker>()?; |
| test_dir.open( |
| OPEN_RIGHT_READABLE, |
| MODE_TYPE_DIRECTORY, |
| &mut iter::empty(), |
| test_dir_service.into_channel().into(), |
| ); |
| fasync::Task::spawn(async move { |
| let _ = test_dir.await; |
| }) |
| .detach(); |
| |
| Ok(test_dir_proxy) |
| } |
| |
| fn run_test_services() -> Result<NestedEnvironment, Error> { |
| let mut fs = ServiceFs::new(); |
| fs.add_fidl_service(IncomingServices::FactoryItems) |
| .add_fidl_service(IncomingServices::AlphaFactoryStoreProvider) |
| .add_fidl_service(IncomingServices::CastCredentialsFactoryStoreProvider) |
| .add_fidl_service(IncomingServices::MiscFactoryStoreProvider) |
| .add_fidl_service(IncomingServices::PlayReadyFactoryStoreProvider) |
| .add_fidl_service(IncomingServices::WeaveFactoryStoreProvider) |
| .add_fidl_service(IncomingServices::WidevineFactoryStoreProvider); |
| |
| let env = fs.create_salted_nested_environment("factoryctl_env"); |
| |
| fasync::Task::spawn(fs.for_each_concurrent(None, |req| async { |
| match req { |
| IncomingServices::FactoryItems(stream) => { |
| stream |
| .err_into::<Error>() |
| .try_for_each( |
| |FactoryItemsRequest::Get { extra: _, responder }| async move { |
| let vmo = zx::Vmo::create(FACTORY_ITEM_CONTENTS.len() as u64)?; |
| vmo.write(&FACTORY_ITEM_CONTENTS, 0)?; |
| responder.send(Some(vmo), FACTORY_ITEM_CONTENTS.len() as u32)?; |
| Ok(()) |
| }, |
| ) |
| .await |
| .unwrap(); |
| } |
| IncomingServices::AlphaFactoryStoreProvider(stream) => { |
| stream |
| .err_into::<Error>() |
| .try_for_each( |
| |AlphaFactoryStoreProviderRequest::GetFactoryStore { |
| dir, |
| control_handle: _, |
| }| { |
| async move { |
| let alpha_proxy = start_test_dir( |
| ALPHA_TXT_FILE_NAME, |
| ALPHA_TXT_FILE_CONTENTS, |
| ALPHA_BIN_FILE_NAME, |
| ALPHA_BIN_FILE_CONTENTS, |
| )?; |
| alpha_proxy |
| .clone(OPEN_RIGHT_READABLE, dir.into_channel().into())?; |
| Ok(()) |
| } |
| }, |
| ) |
| .await |
| .unwrap(); |
| } |
| IncomingServices::CastCredentialsFactoryStoreProvider(stream) => { |
| stream |
| .err_into::<Error>() |
| .try_for_each( |
| |CastCredentialsFactoryStoreProviderRequest::GetFactoryStore { |
| dir, |
| control_handle: _, |
| }| { |
| async move { |
| let cast_proxy = start_test_dir( |
| CAST_TXT_FILE_NAME, |
| CAST_TXT_FILE_CONTENTS, |
| CAST_BIN_FILE_NAME, |
| CAST_BIN_FILE_CONTENTS, |
| )?; |
| cast_proxy |
| .clone(OPEN_RIGHT_READABLE, dir.into_channel().into())?; |
| Ok(()) |
| } |
| }, |
| ) |
| .await |
| .unwrap(); |
| } |
| IncomingServices::MiscFactoryStoreProvider(stream) => { |
| stream |
| .err_into::<Error>() |
| .try_for_each( |
| |MiscFactoryStoreProviderRequest::GetFactoryStore { |
| dir, |
| control_handle: _, |
| }| { |
| async move { |
| let misc_proxy = start_test_dir( |
| MISC_TXT_FILE_NAME, |
| MISC_TXT_FILE_CONTENTS, |
| MISC_BIN_FILE_NAME, |
| MISC_BIN_FILE_CONTENTS, |
| )?; |
| misc_proxy |
| .clone(OPEN_RIGHT_READABLE, dir.into_channel().into())?; |
| Ok(()) |
| } |
| }, |
| ) |
| .await |
| .unwrap(); |
| } |
| IncomingServices::PlayReadyFactoryStoreProvider(stream) => { |
| stream |
| .err_into::<Error>() |
| .try_for_each( |
| |PlayReadyFactoryStoreProviderRequest::GetFactoryStore { |
| dir, |
| control_handle: _, |
| }| { |
| async move { |
| let playready_proxy = start_test_dir( |
| PLAYREADY_TXT_FILE_NAME, |
| PLAYREADY_TXT_FILE_CONTENTS, |
| PLAYREADY_BIN_FILE_NAME, |
| PLAYREADY_BIN_FILE_CONTENTS, |
| )?; |
| playready_proxy |
| .clone(OPEN_RIGHT_READABLE, dir.into_channel().into())?; |
| Ok(()) |
| } |
| }, |
| ) |
| .await |
| .unwrap(); |
| } |
| IncomingServices::WeaveFactoryStoreProvider(stream) => { |
| stream |
| .err_into::<Error>() |
| .try_for_each( |
| |WeaveFactoryStoreProviderRequest::GetFactoryStore { |
| dir, |
| control_handle: _, |
| }| { |
| async move { |
| let weave_proxy = start_test_dir( |
| WEAVE_TXT_FILE_NAME, |
| WEAVE_TXT_FILE_CONTENTS, |
| WEAVE_BIN_FILE_NAME, |
| WEAVE_BIN_FILE_CONTENTS, |
| )?; |
| weave_proxy |
| .clone(OPEN_RIGHT_READABLE, dir.into_channel().into())?; |
| Ok(()) |
| } |
| }, |
| ) |
| .await |
| .unwrap(); |
| } |
| IncomingServices::WidevineFactoryStoreProvider(stream) => { |
| stream |
| .err_into::<Error>() |
| .try_for_each( |
| |WidevineFactoryStoreProviderRequest::GetFactoryStore { |
| dir, |
| control_handle: _, |
| }| { |
| async move { |
| let widevine_proxy = start_test_dir( |
| WIDEVINE_TXT_FILE_NAME, |
| WIDEVINE_TXT_FILE_CONTENTS, |
| WIDEVINE_BIN_FILE_NAME, |
| WIDEVINE_BIN_FILE_CONTENTS, |
| )?; |
| widevine_proxy |
| .clone(OPEN_RIGHT_READABLE, dir.into_channel().into())?; |
| Ok(()) |
| } |
| }, |
| ) |
| .await |
| .unwrap(); |
| } |
| } |
| })) |
| .detach(); |
| |
| env |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn list_files() -> Result<(), Error> { |
| let env = run_test_services()?; |
| |
| for (store, action, bin_file_name, txt_file_name) in vec![ |
| ("alpha", "list", ALPHA_BIN_FILE_NAME, ALPHA_TXT_FILE_NAME), |
| ("cast", "list", CAST_BIN_FILE_NAME, CAST_TXT_FILE_NAME), |
| ("misc", "list", MISC_BIN_FILE_NAME, MISC_TXT_FILE_NAME), |
| ("playready", "list", PLAYREADY_BIN_FILE_NAME, PLAYREADY_TXT_FILE_NAME), |
| ("weave", "list", WEAVE_BIN_FILE_NAME, WEAVE_TXT_FILE_NAME), |
| ("widevine", "list", WIDEVINE_BIN_FILE_NAME, WIDEVINE_TXT_FILE_NAME), |
| ] { |
| let output = AppBuilder::new(FACTORYCTL_PKG_URL) |
| .arg(store) |
| .arg(action) |
| .output(&env.launcher()) |
| .unwrap() |
| .await |
| .unwrap(); |
| |
| let expected_output = format!("{}\n{}\n", bin_file_name, txt_file_name); |
| assert_eq!(expected_output, from_utf8(&output.stdout).unwrap()); |
| } |
| Ok(()) |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn dump_text_files() -> Result<(), Error> { |
| let env = run_test_services()?; |
| |
| for (store, action, file_name, contents) in vec![ |
| ("alpha", "dump", ALPHA_TXT_FILE_NAME, ALPHA_TXT_FILE_CONTENTS), |
| ("cast", "dump", CAST_TXT_FILE_NAME, CAST_TXT_FILE_CONTENTS), |
| ("misc", "dump", MISC_TXT_FILE_NAME, MISC_TXT_FILE_CONTENTS), |
| ("playready", "dump", PLAYREADY_TXT_FILE_NAME, PLAYREADY_TXT_FILE_CONTENTS), |
| ("weave", "dump", WEAVE_TXT_FILE_NAME, WEAVE_TXT_FILE_CONTENTS), |
| ("widevine", "dump", WIDEVINE_TXT_FILE_NAME, WIDEVINE_TXT_FILE_CONTENTS), |
| ] { |
| let output = AppBuilder::new(FACTORYCTL_PKG_URL) |
| .arg(store) |
| .arg(action) |
| .arg(file_name) |
| .output(&env.launcher()) |
| .unwrap() |
| .await |
| .unwrap(); |
| |
| let expected_output = format!("{}\n", contents); |
| assert_eq!(expected_output, from_utf8(&output.stdout).unwrap()); |
| } |
| Ok(()) |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn dump_binary_files() -> Result<(), Error> { |
| let env = run_test_services()?; |
| |
| for (store, action, file_name, contents) in vec![ |
| ( |
| "alpha", |
| "dump", |
| ALPHA_BIN_FILE_NAME, |
| ALPHA_BIN_FILE_CONTENTS.to_hex(HEX_DISPLAY_CHUNK_SIZE), |
| ), |
| ( |
| "cast", |
| "dump", |
| CAST_BIN_FILE_NAME, |
| CAST_BIN_FILE_CONTENTS.to_hex(HEX_DISPLAY_CHUNK_SIZE), |
| ), |
| ( |
| "misc", |
| "dump", |
| MISC_BIN_FILE_NAME, |
| MISC_BIN_FILE_CONTENTS.to_hex(HEX_DISPLAY_CHUNK_SIZE), |
| ), |
| ( |
| "playready", |
| "dump", |
| PLAYREADY_BIN_FILE_NAME, |
| PLAYREADY_BIN_FILE_CONTENTS.to_hex(HEX_DISPLAY_CHUNK_SIZE), |
| ), |
| ( |
| "weave", |
| "dump", |
| WEAVE_BIN_FILE_NAME, |
| WEAVE_BIN_FILE_CONTENTS.to_hex(HEX_DISPLAY_CHUNK_SIZE), |
| ), |
| ( |
| "widevine", |
| "dump", |
| WIDEVINE_BIN_FILE_NAME, |
| WIDEVINE_BIN_FILE_CONTENTS.to_hex(HEX_DISPLAY_CHUNK_SIZE), |
| ), |
| ] { |
| let output = AppBuilder::new(FACTORYCTL_PKG_URL) |
| .arg(store) |
| .arg(action) |
| .arg(file_name) |
| .output(&env.launcher()) |
| .unwrap() |
| .await |
| .unwrap(); |
| |
| let expected_output = format!("{}\n", contents); |
| assert_eq!(expected_output, from_utf8(&output.stdout).unwrap()); |
| } |
| Ok(()) |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn dump_factory_item() -> Result<(), Error> { |
| let env = run_test_services()?; |
| |
| let output = AppBuilder::new(FACTORYCTL_PKG_URL) |
| .args(vec!["factory-items", "dump", "0"]) |
| .output(&env.launcher()) |
| .unwrap() |
| .await |
| .unwrap(); |
| |
| let expected_output = format!( |
| "{}\nLength={}\n", |
| FACTORY_ITEM_CONTENTS.to_hex(HEX_DISPLAY_CHUNK_SIZE), |
| FACTORY_ITEM_CONTENTS.len() |
| ); |
| assert_eq!(expected_output, from_utf8(&output.stdout).unwrap()); |
| Ok(()) |
| } |
| } |