blob: bbbe864ff5ec4a4e4d287e9d70040bd3bfe13d84 [file] [log] [blame]
// Copyright 2022 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 fuchsia_url::UnpinnedAbsolutePackageUrl;
use omaha_client::cup_ecdsa::PublicKeys;
use omaha_client::version::Version;
use serde::{Deserialize, Serialize};
#[cfg(target_os = "fuchsia")]
use {
fidl_fuchsia_io as fio, fuchsia_zircon as zx, futures::stream::StreamExt as _,
std::collections::HashSet,
};
#[cfg(target_os = "fuchsia")]
const EAGER_PACKAGE_CONFIG_PATH: &str = "/config/data/eager_package_config.json";
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub struct EagerPackageConfigs {
pub packages: Vec<EagerPackageConfig>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub struct EagerPackageConfig {
pub url: UnpinnedAbsolutePackageUrl,
#[serde(default, skip_serializing_if = "is_false")]
pub executable: bool,
pub public_keys: PublicKeys,
pub minimum_required_version: Version,
#[serde(default = "return_true", skip_serializing_if = "bool::clone")]
pub cache_fallback: bool,
}
#[cfg(target_os = "fuchsia")]
impl EagerPackageConfigs {
/// Read eager config from namespace. Returns an empty instance of `EagerPackageConfigs` in
/// case config was not found.
pub async fn from_namespace() -> Result<Self, EagerPackageConfigsError> {
Self::from_path(EAGER_PACKAGE_CONFIG_PATH).await
}
async fn from_path(path: &str) -> Result<Self, EagerPackageConfigsError> {
let proxy = fuchsia_fs::file::open_in_namespace(
path,
fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DESCRIBE,
)?;
match fuchsia_fs::file::read(&proxy).await {
Ok(json) => Self::from_json(&json),
Err(e) => {
if matches!(
proxy.take_event_stream().next().await,
Some(Ok(fio::FileEvent::OnOpen_{s, ..}))
if s == zx::Status::NOT_FOUND.into_raw()
) {
Ok(EagerPackageConfigs { packages: Vec::new() })
} else {
Err(EagerPackageConfigsError::Read(e))
}
}
}
}
fn from_json(json: &[u8]) -> Result<Self, EagerPackageConfigsError> {
let configs: Self = serde_json::from_slice(json)?;
if configs.packages.iter().map(|config| config.url.path()).collect::<HashSet<_>>().len()
< configs.packages.len()
{
return Err(EagerPackageConfigsError::DuplicatePath);
}
Ok(configs)
}
}
#[cfg(target_os = "fuchsia")]
#[derive(Debug, thiserror::Error)]
pub enum EagerPackageConfigsError {
#[error("open eager package config")]
Open(#[from] fuchsia_fs::node::OpenError),
#[error("read eager package config")]
Read(#[from] fuchsia_fs::file::ReadError),
#[error("parse eager package config from json")]
Json(#[from] serde_json::Error),
#[error("eager package URL must have unique path")]
DuplicatePath,
}
fn return_true() -> bool {
true
}
fn is_false(b: &bool) -> bool {
!b
}
#[cfg(test)]
mod tests {
use super::*;
use assert_matches::assert_matches;
use fuchsia_async as fasync;
use omaha_client::cup_ecdsa::test_support::{
make_default_json_public_keys_for_test, make_default_public_keys_for_test,
};
#[fasync::run_singlethreaded(test)]
async fn not_found_tmp() {
let configs = EagerPackageConfigs::from_path("/tmp/not-found").await.unwrap();
assert_eq!(configs.packages, vec![]);
}
#[fasync::run_singlethreaded(test)]
async fn not_found_pkg() {
let configs = EagerPackageConfigs::from_path("/pkg/not-found").await.unwrap();
assert_eq!(configs.packages, vec![]);
}
#[fasync::run_singlethreaded(test)]
async fn success() {
let json = serde_json::json!({
"packages":[
{
"url": "fuchsia-pkg://example.com/package_service_1",
"public_keys": make_default_json_public_keys_for_test(),
"minimum_required_version": "1.2.3.4"
}
]
});
assert_eq!(
EagerPackageConfigs::from_json(json.to_string().as_bytes()).unwrap(),
EagerPackageConfigs {
packages: vec![EagerPackageConfig {
url: "fuchsia-pkg://example.com/package_service_1".parse().unwrap(),
executable: false,
public_keys: make_default_public_keys_for_test(),
minimum_required_version: [1, 2, 3, 4].into(),
cache_fallback: true,
}]
}
);
}
#[fasync::run_singlethreaded(test)]
async fn duplicate_path() {
let json = serde_json::json!({
"packages":[
{
"url": "fuchsia-pkg://example.com/package_service_1",
"public_keys": make_default_json_public_keys_for_test(),
"minimum_required_version": "1.2.3.4"
},
{
"url": "fuchsia-pkg://another-example.com/package_service_1",
"public_keys": make_default_json_public_keys_for_test(),
"minimum_required_version": "1.2.3.4"
}
]
});
assert_matches!(
EagerPackageConfigs::from_json(json.to_string().as_bytes()),
Err(EagerPackageConfigsError::DuplicatePath)
);
}
}