blob: 969210622df4861b4eda44b858f6745abe933a2c [file] [log] [blame]
// 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(())
}
}