blob: 0cc6a7231a5ad7bdfde39f19b0d4df06d3fe5592 [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 crate::errors::{self, Error};
use anyhow::{anyhow, Context as _};
use fidl_fuchsia_paver::{Asset, BootManagerMarker, DataSinkMarker, PaverMarker, PaverProxy};
use fidl_fuchsia_pkg::{PackageResolverMarker, PackageResolverProxyInterface, UpdatePolicy};
use fuchsia_component::client::connect_to_service;
use fuchsia_hash::Hash;
use fuchsia_syslog::fx_log_warn;
use fuchsia_zircon as zx;
use std::{cmp::min, convert::TryInto as _, io};
use update_package::{ImageType, UpdatePackage};
const UPDATE_PACKAGE_URL: &str = "fuchsia-pkg://fuchsia.com/update/0";
#[derive(PartialEq, Eq, Debug, Clone)]
pub enum SystemUpdateStatus {
UpToDate { system_image: Hash, update_package: Hash },
UpdateAvailable { current_system_image: Hash, latest_system_image: Hash },
}
pub async fn check_for_system_update(
last_known_update_package: Option<&Hash>,
) -> Result<SystemUpdateStatus, Error> {
let mut file_system = RealFileSystem;
let package_resolver =
connect_to_service::<PackageResolverMarker>().map_err(Error::ConnectPackageResolver)?;
let paver = connect_to_service::<PaverMarker>().map_err(Error::ConnectPaver)?;
check_for_system_update_impl(
&mut file_system,
&package_resolver,
&paver,
last_known_update_package,
)
.await
}
// For mocking
trait FileSystem {
fn read_to_string(&self, path: &str) -> io::Result<String>;
fn remove_file(&mut self, path: &str) -> io::Result<()>;
}
struct RealFileSystem;
impl FileSystem for RealFileSystem {
fn read_to_string(&self, path: &str) -> io::Result<String> {
std::fs::read_to_string(path)
}
fn remove_file(&mut self, path: &str) -> io::Result<()> {
std::fs::remove_file(path)
}
}
async fn check_for_system_update_impl(
file_system: &mut impl FileSystem,
package_resolver: &impl PackageResolverProxyInterface,
paver: &PaverProxy,
last_known_update_package: Option<&Hash>,
) -> Result<SystemUpdateStatus, crate::errors::Error> {
let update_pkg = latest_update_package(package_resolver).await?;
let latest_update_merkle = update_pkg.hash().await.map_err(errors::UpdatePackage::Hash)?;
let current_system_image = current_system_image_merkle(file_system)?;
let latest_system_image = latest_system_image_merkle(&update_pkg).await?;
let update_available =
Ok(SystemUpdateStatus::UpdateAvailable { current_system_image, latest_system_image });
if let Some(last_known_update_package) = last_known_update_package {
if *last_known_update_package == latest_update_merkle {
return Ok(SystemUpdateStatus::UpToDate {
system_image: current_system_image,
update_package: latest_update_merkle,
});
}
}
if current_system_image != latest_system_image {
return update_available;
}
match is_image_up_to_date(
&update_pkg,
paver,
ImageType::FuchsiaVbmeta,
Asset::VerifiedBootMetadata,
)
.await
{
Ok(true) => {}
Ok(false) => {
return update_available;
}
Err(err) => {
// In the case that reading the vbmeta errors or there is no vbmeta, check the ZBI for an update.
fx_log_warn!("Failed to check if vbmeta is up to date: {:#}", anyhow!(err));
match is_image_up_to_date(&update_pkg, paver, ImageType::Zbi, Asset::Kernel).await {
Ok(true) => {}
Ok(false) => {
return update_available;
}
Err(err) => {
fx_log_warn!("Failed to check if zbi is up to date: {:#}", anyhow!(err));
}
}
}
}
Ok(SystemUpdateStatus::UpToDate {
system_image: current_system_image,
update_package: latest_update_merkle,
})
}
fn current_system_image_merkle(
file_system: &impl FileSystem,
) -> Result<Hash, crate::errors::Error> {
Ok(file_system
.read_to_string("/pkgfs/system/meta")
.map_err(Error::ReadSystemMeta)?
.parse::<Hash>()
.map_err(Error::ParseSystemMeta)?)
}
async fn latest_update_package(
package_resolver: &impl PackageResolverProxyInterface,
) -> Result<UpdatePackage, errors::UpdatePackage> {
let (dir_proxy, dir_server_end) =
fidl::endpoints::create_proxy().map_err(errors::UpdatePackage::CreateDirectoryProxy)?;
let fut = package_resolver.resolve(
&UPDATE_PACKAGE_URL,
&mut vec![].into_iter(),
&mut UpdatePolicy { fetch_if_absent: true, allow_old_versions: false },
dir_server_end,
);
let () = fut
.await
.map_err(errors::UpdatePackage::ResolveFidl)?
.map_err(|raw| errors::UpdatePackage::Resolve(zx::Status::from_raw(raw)))?;
Ok(UpdatePackage::new(dir_proxy))
}
async fn latest_system_image_merkle(
update_package: &UpdatePackage,
) -> Result<Hash, errors::UpdatePackage> {
let packages =
update_package.packages().await.map_err(errors::UpdatePackage::ExtractPackagesManifest)?;
let system_image = packages
.into_iter()
.find(|url| url.name() == "system_image" && url.variant() == Some("0"))
.ok_or(errors::UpdatePackage::MissingSystemImage)?;
let hash = system_image
.package_hash()
.ok_or_else(|| errors::UpdatePackage::UnPinnedSystemImage(system_image.clone()))?;
Ok(hash.to_owned())
}
async fn is_image_up_to_date(
update_package: &UpdatePackage,
paver: &PaverProxy,
image_type: ImageType,
asset: Asset,
) -> Result<bool, anyhow::Error> {
let latest_image =
update_package.open_image(&update_package::Image::new(image_type, None)).await?;
let (boot_manager, server_end) = fidl::endpoints::create_proxy::<BootManagerMarker>()?;
let () = paver.find_boot_manager(server_end).context("connect to fuchsia.paver.BootManager")?;
let configuration = boot_manager.query_current_configuration().await?.map_err(|status| {
anyhow!("error querying current configuration {}", zx::Status::from_raw(status))
})?;
let (data_sink, server_end) = fidl::endpoints::create_proxy::<DataSinkMarker>()?;
let () = paver.find_data_sink(server_end).context("connect to fuchsia.paver.DataSink")?;
let current_image = data_sink
.read_asset(configuration, asset)
.await?
.map_err(|status| anyhow!("read_asset responded with {}", zx::Status::from_raw(status)))?;
compare_buffer(latest_image, current_image)
}
// Compare two buffers and return true if they are equal.
// If they have different sizes, only compare up to the smaller size and ignoring the rest.
// This is because when reading asset from paver, we might get extra data after the image.
fn compare_buffer(
a: fidl_fuchsia_mem::Buffer,
b: fidl_fuchsia_mem::Buffer,
) -> Result<bool, anyhow::Error> {
let a_size = a.size.try_into()?;
let b_size = b.size.try_into()?;
let size = min(a_size, b_size);
let max_buffer_size = 32 * 1024;
let step_size = min(size, max_buffer_size);
let mut a_buf = vec![0u8; step_size];
let mut b_buf = vec![0u8; step_size];
for offset in (0..size).step_by(step_size) {
let current_step_size = min(step_size, size - offset);
a.vmo.read(&mut a_buf[..current_step_size], offset as u64)?;
b.vmo.read(&mut b_buf[..current_step_size], offset as u64)?;
if a_buf[..current_step_size] != b_buf[..current_step_size] {
return Ok(false);
}
}
Ok(true)
}
#[cfg(test)]
pub mod test_check_for_system_update_impl {
use super::*;
use fidl_fuchsia_paver::Configuration;
use fidl_fuchsia_pkg::{
PackageResolverGetHashResult, PackageResolverResolveResult, PackageUrl,
};
use fuchsia_async::{self as fasync, futures::future};
use lazy_static::lazy_static;
use maplit::hashmap;
use matches::assert_matches;
use mock_paver::MockPaverServiceBuilder;
use std::{collections::hash_map::HashMap, fs, io::Write, sync::Arc};
const ACTIVE_SYSTEM_IMAGE_MERKLE: &str =
"0000000000000000000000000000000000000000000000000000000000000000";
const NEW_SYSTEM_IMAGE_MERKLE: &str =
"1111111111111111111111111111111111111111111111111111111111111111";
lazy_static! {
static ref UPDATE_PACKAGE_MERKLE: Hash = [0x22; 32].into();
}
struct FakeFileSystem {
contents: HashMap<String, String>,
}
impl FakeFileSystem {
fn new_with_valid_system_meta() -> FakeFileSystem {
FakeFileSystem {
contents: hashmap![
"/pkgfs/system/meta".to_string() => ACTIVE_SYSTEM_IMAGE_MERKLE.to_string()
],
}
}
}
impl FileSystem for FakeFileSystem {
fn read_to_string(&self, path: &str) -> io::Result<String> {
self.contents
.get(path)
.ok_or(io::Error::new(
io::ErrorKind::NotFound,
format!("not present in fake file system: {}", path),
))
.map(|s| s.to_string())
}
fn remove_file(&mut self, path: &str) -> io::Result<()> {
self.contents.remove(path).and(Some(())).ok_or(io::Error::new(
io::ErrorKind::NotFound,
format!("fake file system cannot remove non-existent file: {}", path),
))
}
}
pub struct PackageResolverProxyTempDir {
temp_dir: tempfile::TempDir,
}
impl PackageResolverProxyTempDir {
fn new_with_default_meta() -> PackageResolverProxyTempDir {
let temp_dir = tempfile::tempdir().expect("create temp dir");
fs::write(temp_dir.path().join("meta"), UPDATE_PACKAGE_MERKLE.to_string())
.expect("write meta");
PackageResolverProxyTempDir { temp_dir }
}
fn new_with_empty_packages_json() -> PackageResolverProxyTempDir {
let temp_dir = tempfile::tempdir().expect("create temp dir");
fs::write(temp_dir.path().join("meta"), UPDATE_PACKAGE_MERKLE.to_string())
.expect("write meta");
let mut packages_json = fs::File::create(
temp_dir.path().join("packages.json").to_str().expect("path is utf8"),
)
.expect("create packages.json");
write!(&mut packages_json, r#"{{"version": "1", "content": []}}"#)
.expect("write json with no contents");
PackageResolverProxyTempDir { temp_dir }
}
fn new_with_latest_system_image(merkle: &str) -> PackageResolverProxyTempDir {
let temp_dir = tempfile::tempdir().expect("create temp dir");
fs::write(temp_dir.path().join("meta"), UPDATE_PACKAGE_MERKLE.to_string())
.expect("write meta");
let mut packages_json = fs::File::create(
temp_dir.path().join("packages.json").to_str().expect("path is utf8"),
)
.expect("create packages.json");
write!(
&mut packages_json,
r#"{{"version": "1", "content": ["fuchsia-pkg://fuchsia.com/system_image/0?hash={}"]}}"#,
merkle
)
.expect("write to package file");
PackageResolverProxyTempDir { temp_dir }
}
fn new_with_vbmeta(vbmeta: impl AsRef<[u8]>) -> PackageResolverProxyTempDir {
let PackageResolverProxyTempDir { temp_dir } =
Self::new_with_latest_system_image(ACTIVE_SYSTEM_IMAGE_MERKLE);
fs::write(temp_dir.path().join("fuchsia.vbmeta"), vbmeta).expect("write vbmeta");
PackageResolverProxyTempDir { temp_dir }
}
fn new_with_zbi(zbi: impl AsRef<[u8]>) -> PackageResolverProxyTempDir {
let PackageResolverProxyTempDir { temp_dir } =
Self::new_with_latest_system_image(ACTIVE_SYSTEM_IMAGE_MERKLE);
fs::write(temp_dir.path().join("zbi"), zbi).expect("write zbi");
PackageResolverProxyTempDir { temp_dir }
}
}
impl PackageResolverProxyInterface for PackageResolverProxyTempDir {
type ResolveResponseFut = future::Ready<Result<PackageResolverResolveResult, fidl::Error>>;
fn resolve(
&self,
package_url: &str,
selectors: &mut dyn ExactSizeIterator<Item = &str>,
update_policy: &mut UpdatePolicy,
dir: fidl::endpoints::ServerEnd<fidl_fuchsia_io::DirectoryMarker>,
) -> Self::ResolveResponseFut {
assert_eq!(package_url, UPDATE_PACKAGE_URL);
assert_eq!(selectors.len(), 0);
assert_eq!(
update_policy,
&UpdatePolicy { fetch_if_absent: true, allow_old_versions: false }
);
fdio::service_connect(
self.temp_dir.path().to_str().expect("path is utf8"),
dir.into_channel(),
)
.unwrap();
future::ok(Ok(()))
}
type GetHashResponseFut = future::Ready<Result<PackageResolverGetHashResult, fidl::Error>>;
fn get_hash(&self, _package_url: &mut PackageUrl) -> Self::GetHashResponseFut {
panic!("get_hash not implemented");
}
}
#[fasync::run_singlethreaded(test)]
async fn test_missing_system_meta_file() {
let mut file_system = FakeFileSystem { contents: hashmap![] };
let package_resolver = PackageResolverProxyTempDir::new_with_default_meta();
let mock_paver = Arc::new(MockPaverServiceBuilder::new().build());
let paver = mock_paver.spawn_paver_service();
let result =
check_for_system_update_impl(&mut file_system, &package_resolver, &paver, None).await;
assert_matches!(result, Err(Error::ReadSystemMeta(_)));
}
#[fasync::run_singlethreaded(test)]
async fn test_malformatted_system_meta_file() {
let mut file_system = FakeFileSystem {
contents: hashmap![
"/pkgfs/system/meta".to_string() => "not-a-merkle".to_string()
],
};
let package_resolver = PackageResolverProxyTempDir::new_with_default_meta();
let mock_paver = Arc::new(MockPaverServiceBuilder::new().build());
let paver = mock_paver.spawn_paver_service();
let result =
check_for_system_update_impl(&mut file_system, &package_resolver, &paver, None).await;
assert_matches!(result, Err(Error::ParseSystemMeta(_)));
}
#[fasync::run_singlethreaded(test)]
async fn test_resolve_update_package_fidl_error() {
struct PackageResolverProxyFidlError;
impl PackageResolverProxyInterface for PackageResolverProxyFidlError {
type ResolveResponseFut =
future::Ready<Result<PackageResolverResolveResult, fidl::Error>>;
fn resolve(
&self,
_package_url: &str,
_selectors: &mut dyn ExactSizeIterator<Item = &str>,
_update_policy: &mut UpdatePolicy,
_dir: fidl::endpoints::ServerEnd<fidl_fuchsia_io::DirectoryMarker>,
) -> Self::ResolveResponseFut {
future::err(fidl::Error::Invalid)
}
type GetHashResponseFut =
future::Ready<Result<PackageResolverGetHashResult, fidl::Error>>;
fn get_hash(&self, _package_url: &mut PackageUrl) -> Self::GetHashResponseFut {
panic!("get_hash not implemented");
}
}
let mut file_system = FakeFileSystem::new_with_valid_system_meta();
let package_resolver = PackageResolverProxyFidlError;
let mock_paver = Arc::new(MockPaverServiceBuilder::new().build());
let paver = mock_paver.spawn_paver_service();
let result =
check_for_system_update_impl(&mut file_system, &package_resolver, &paver, None).await;
assert_matches!(result, Err(Error::UpdatePackage(errors::UpdatePackage::ResolveFidl(_))));
}
#[fasync::run_singlethreaded(test)]
async fn test_resolve_update_package_zx_error() {
struct PackageResolverProxyZxError;
impl PackageResolverProxyInterface for PackageResolverProxyZxError {
type ResolveResponseFut =
future::Ready<Result<PackageResolverResolveResult, fidl::Error>>;
fn resolve(
&self,
_package_url: &str,
_selectors: &mut dyn ExactSizeIterator<Item = &str>,
_update_policy: &mut UpdatePolicy,
_dir: fidl::endpoints::ServerEnd<fidl_fuchsia_io::DirectoryMarker>,
) -> Self::ResolveResponseFut {
future::ok(Err(fuchsia_zircon::Status::INTERNAL.into_raw()))
}
type GetHashResponseFut =
future::Ready<Result<PackageResolverGetHashResult, fidl::Error>>;
fn get_hash(&self, _package_url: &mut PackageUrl) -> Self::GetHashResponseFut {
panic!("get_hash not implemented");
}
}
let mut file_system = FakeFileSystem::new_with_valid_system_meta();
let package_resolver = PackageResolverProxyZxError;
let mock_paver = Arc::new(MockPaverServiceBuilder::new().build());
let paver = mock_paver.spawn_paver_service();
let result =
check_for_system_update_impl(&mut file_system, &package_resolver, &paver, None).await;
assert_matches!(result, Err(Error::UpdatePackage(errors::UpdatePackage::Resolve(_))));
}
#[fasync::run_singlethreaded(test)]
async fn test_resolve_update_package_directory_closed() {
struct PackageResolverProxyDirectoryCloser;
impl PackageResolverProxyInterface for PackageResolverProxyDirectoryCloser {
type ResolveResponseFut =
future::Ready<Result<PackageResolverResolveResult, fidl::Error>>;
fn resolve(
&self,
_package_url: &str,
_selectors: &mut dyn ExactSizeIterator<Item = &str>,
_update_policy: &mut UpdatePolicy,
_dir: fidl::endpoints::ServerEnd<fidl_fuchsia_io::DirectoryMarker>,
) -> Self::ResolveResponseFut {
future::ok(Ok(()))
}
type GetHashResponseFut =
future::Ready<Result<PackageResolverGetHashResult, fidl::Error>>;
fn get_hash(&self, _package_url: &mut PackageUrl) -> Self::GetHashResponseFut {
panic!("get_hash not implemented");
}
}
let mut file_system = FakeFileSystem::new_with_valid_system_meta();
let package_resolver = PackageResolverProxyDirectoryCloser;
let mock_paver = Arc::new(MockPaverServiceBuilder::new().build());
let paver = mock_paver.spawn_paver_service();
let result =
check_for_system_update_impl(&mut file_system, &package_resolver, &paver, None).await;
assert_matches!(result, Err(Error::UpdatePackage(errors::UpdatePackage::Hash(_))));
}
#[fasync::run_singlethreaded(test)]
async fn test_update_package_missing_packages_json() {
let mut file_system = FakeFileSystem::new_with_valid_system_meta();
let package_resolver = PackageResolverProxyTempDir::new_with_default_meta();
let mock_paver = Arc::new(MockPaverServiceBuilder::new().build());
let paver = mock_paver.spawn_paver_service();
let result =
check_for_system_update_impl(&mut file_system, &package_resolver, &paver, None).await;
assert_matches!(
result,
Err(Error::UpdatePackage(errors::UpdatePackage::ExtractPackagesManifest(_)))
);
}
#[fasync::run_singlethreaded(test)]
async fn test_update_package_empty_packages_json() {
let mut file_system = FakeFileSystem::new_with_valid_system_meta();
let package_resolver = PackageResolverProxyTempDir::new_with_empty_packages_json();
let mock_paver = Arc::new(MockPaverServiceBuilder::new().build());
let paver = mock_paver.spawn_paver_service();
let result =
check_for_system_update_impl(&mut file_system, &package_resolver, &paver, None).await;
assert_matches!(
result,
Err(Error::UpdatePackage(errors::UpdatePackage::MissingSystemImage))
);
}
#[fasync::run_singlethreaded(test)]
async fn test_update_package_bad_system_image() {
let mut file_system = FakeFileSystem::new_with_valid_system_meta();
let package_resolver =
PackageResolverProxyTempDir::new_with_latest_system_image("bad-merkle");
let mock_paver = Arc::new(MockPaverServiceBuilder::new().build());
let paver = mock_paver.spawn_paver_service();
let result =
check_for_system_update_impl(&mut file_system, &package_resolver, &paver, None).await;
assert_matches!(
result,
Err(Error::UpdatePackage(errors::UpdatePackage::ExtractPackagesManifest(_)))
);
}
#[fasync::run_singlethreaded(test)]
async fn test_up_to_date() {
let mut file_system = FakeFileSystem::new_with_valid_system_meta();
let package_resolver = PackageResolverProxyTempDir::new_with_vbmeta([1]);
let mock_paver = Arc::new(
MockPaverServiceBuilder::new()
.active_config(Configuration::A)
.insert_hook(mock_paver::hooks::read_asset(|configuration, asset| {
assert_eq!(configuration, Configuration::A);
match asset {
Asset::Kernel => panic!("shouldn't read kernel if vbmeta is the same!"),
Asset::VerifiedBootMetadata => Ok(vec![1]),
}
}))
.build(),
);
let paver = mock_paver.spawn_paver_service();
let result = check_for_system_update_impl(
&mut file_system,
&package_resolver,
&paver,
Some(&UPDATE_PACKAGE_MERKLE),
)
.await;
assert_matches!(
result,
Ok(SystemUpdateStatus::UpToDate { system_image, update_package: _ })
if system_image == ACTIVE_SYSTEM_IMAGE_MERKLE
.parse()
.expect("active system image string literal")
);
}
#[fasync::run_singlethreaded(test)]
async fn test_up_to_date_with_missing_vbmeta() {
let mut file_system = FakeFileSystem::new_with_valid_system_meta();
let package_resolver = PackageResolverProxyTempDir::new_with_vbmeta([1]);
let mock_paver = Arc::new(
MockPaverServiceBuilder::new()
.active_config(Configuration::A)
.insert_hook(mock_paver::hooks::read_asset(|configuration, asset| {
assert_eq!(configuration, Configuration::A);
match asset {
Asset::Kernel => Err(zx::Status::NOT_FOUND),
Asset::VerifiedBootMetadata => Err(zx::Status::NOT_FOUND),
}
}))
.build(),
);
let paver = mock_paver.spawn_paver_service();
let result = check_for_system_update_impl(
&mut file_system,
&package_resolver,
&paver,
Some(&UPDATE_PACKAGE_MERKLE),
)
.await;
assert_matches!(
result,
Ok(SystemUpdateStatus::UpToDate { system_image, update_package: _ })
if system_image == ACTIVE_SYSTEM_IMAGE_MERKLE
.parse()
.expect("active system image string literal")
);
}
#[fasync::run_singlethreaded(test)]
async fn test_update_available() {
let mut file_system = FakeFileSystem::new_with_valid_system_meta();
let package_resolver =
PackageResolverProxyTempDir::new_with_latest_system_image(NEW_SYSTEM_IMAGE_MERKLE);
let mock_paver = Arc::new(MockPaverServiceBuilder::new().build());
let paver = mock_paver.spawn_paver_service();
let result =
check_for_system_update_impl(&mut file_system, &package_resolver, &paver, None).await;
assert_matches!(
result,
Ok(SystemUpdateStatus::UpdateAvailable { current_system_image, latest_system_image })
if
current_system_image == ACTIVE_SYSTEM_IMAGE_MERKLE
.parse()
.expect("active system image string literal") &&
latest_system_image == NEW_SYSTEM_IMAGE_MERKLE
.parse()
.expect("new system image string literal")
);
}
#[fasync::run_singlethreaded(test)]
async fn test_different_update_package_hash_does_not_trigger_update() {
let mut file_system = FakeFileSystem::new_with_valid_system_meta();
let package_resolver =
PackageResolverProxyTempDir::new_with_latest_system_image(ACTIVE_SYSTEM_IMAGE_MERKLE);
let mock_paver = Arc::new(MockPaverServiceBuilder::new().build());
let paver = mock_paver.spawn_paver_service();
let previous_update_package = Hash::from([0x44; 32]);
let result = check_for_system_update_impl(
&mut file_system,
&package_resolver,
&paver,
Some(&previous_update_package),
)
.await;
assert_matches!(
result,
Ok(SystemUpdateStatus::UpToDate { system_image, update_package})
if system_image == ACTIVE_SYSTEM_IMAGE_MERKLE
.parse()
.expect("active system image string literal") &&
update_package == *UPDATE_PACKAGE_MERKLE
);
}
#[fasync::run_singlethreaded(test)]
async fn test_vbmeta_only_update_available() {
let mut file_system = FakeFileSystem::new_with_valid_system_meta();
let package_resolver = PackageResolverProxyTempDir::new_with_vbmeta([1]);
let mock_paver = Arc::new(
MockPaverServiceBuilder::new()
.active_config(Configuration::A)
.insert_hook(mock_paver::hooks::read_asset(|configuration, asset| {
assert_eq!(configuration, Configuration::A);
match asset {
Asset::Kernel => panic!("not expecting to read kernel"),
Asset::VerifiedBootMetadata => Ok(vec![0]),
}
}))
.build(),
);
let paver = mock_paver.spawn_paver_service();
let result =
check_for_system_update_impl(&mut file_system, &package_resolver, &paver, None).await;
assert_matches!(
result,
Ok(SystemUpdateStatus::UpdateAvailable { current_system_image, latest_system_image })
if
current_system_image == ACTIVE_SYSTEM_IMAGE_MERKLE
.parse()
.expect("active system image string literal") &&
latest_system_image == ACTIVE_SYSTEM_IMAGE_MERKLE
.parse()
.expect("new system image string literal")
);
}
#[fasync::run_singlethreaded(test)]
async fn test_zbi_only_update_available() {
let mut file_system = FakeFileSystem::new_with_valid_system_meta();
let package_resolver = PackageResolverProxyTempDir::new_with_zbi([1]);
let mock_paver = Arc::new(
MockPaverServiceBuilder::new()
.active_config(Configuration::A)
.insert_hook(mock_paver::hooks::read_asset(|configuration, asset| {
assert_eq!(configuration, Configuration::A);
match asset {
Asset::Kernel => Ok(vec![0]),
Asset::VerifiedBootMetadata => panic!("no vbmeta available"),
}
}))
.build(),
);
let paver = mock_paver.spawn_paver_service();
let result =
check_for_system_update_impl(&mut file_system, &package_resolver, &paver, None).await;
assert_matches!(
result,
Ok(SystemUpdateStatus::UpdateAvailable { current_system_image, latest_system_image })
if
current_system_image == ACTIVE_SYSTEM_IMAGE_MERKLE
.parse()
.expect("active system image string literal") &&
latest_system_image == ACTIVE_SYSTEM_IMAGE_MERKLE
.parse()
.expect("new system image string literal")
);
}
#[fasync::run_singlethreaded(test)]
async fn test_zbi_did_not_require_update() {
let mut file_system = FakeFileSystem::new_with_valid_system_meta();
let package_resolver = PackageResolverProxyTempDir::new_with_zbi([0]);
let mock_paver = Arc::new(
MockPaverServiceBuilder::new()
.active_config(Configuration::A)
.insert_hook(mock_paver::hooks::read_asset(|configuration, asset| {
assert_eq!(configuration, Configuration::A);
match asset {
Asset::Kernel => Ok(vec![0]),
Asset::VerifiedBootMetadata => panic!("no vbmeta available"),
}
}))
.build(),
);
let paver = mock_paver.spawn_paver_service();
let result =
check_for_system_update_impl(&mut file_system, &package_resolver, &paver, None).await;
assert_matches!(
result,
Ok(SystemUpdateStatus::UpToDate { system_image, update_package: _ })
if system_image == ACTIVE_SYSTEM_IMAGE_MERKLE
.parse()
.expect("active system image string literal")
);
}
}
#[cfg(test)]
mod test_compare_buffer {
use {super::*, fidl_fuchsia_mem::Buffer, fuchsia_zircon::Vmo, matches::assert_matches};
fn buffer(data: &[u8]) -> Buffer {
let size = data.len() as u64;
let vmo = Vmo::create(size).unwrap();
vmo.write(data, 0).unwrap();
Buffer { vmo, size }
}
#[test]
fn equal() {
let a = [1; 100];
assert_matches!(compare_buffer(buffer(&a), buffer(&a)), Ok(true));
}
#[test]
fn equal_different_size() {
let a = [1; 100];
let b = [1; 200];
assert_matches!(compare_buffer(buffer(&a), buffer(&b)), Ok(true));
}
#[test]
fn equal_large_buffer() {
let a = [1; 100000];
let b = [1; 100001];
assert_matches!(compare_buffer(buffer(&a), buffer(&b)), Ok(true));
}
#[test]
fn not_equal() {
let a = [1; 100];
let b = [0; 100];
assert_matches!(compare_buffer(buffer(&a), buffer(&b)), Ok(false));
}
#[test]
fn not_equal_different_size() {
let a = [1; 100];
let b = [0; 200];
assert_matches!(compare_buffer(buffer(&a), buffer(&b)), Ok(false));
}
#[test]
fn not_equal_large_buffer() {
let a = [1; 100000];
let mut b = [1; 100001];
b[99999] = 0;
assert_matches!(compare_buffer(buffer(&a), buffer(&b)), Ok(false));
}
#[test]
fn vmo_read_error() {
let a = Buffer { vmo: Vmo::create(100).unwrap(), size: 10000 };
let b = Buffer { vmo: Vmo::create(10000).unwrap(), size: 10000 };
assert_matches!(compare_buffer(a, b), Err(_));
}
}
#[cfg(test)]
mod test_real_file_system {
use super::*;
use matches::assert_matches;
use proptest::prelude::*;
use std::fs;
use std::io::{self, Write};
#[test]
fn test_read_to_string_errors_on_missing_file() {
let dir = tempfile::tempdir().expect("create temp dir");
let read_res = RealFileSystem.read_to_string(
dir.path().join("this-file-does-not-exist").to_str().expect("paths are utf8"),
);
assert_matches!(read_res.map_err(|e| e.kind()), Err(io::ErrorKind::NotFound));
}
proptest! {
#[test]
fn test_read_to_string_preserves_contents(
contents in ".{0, 65}",
file_name in "[^\\.\0/]{1,10}",
) {
let dir = tempfile::tempdir().expect("create temp dir");
let file_path = dir.path().join(file_name);
let mut file = fs::File::create(&file_path).expect("create file");
file.write_all(contents.as_bytes()).expect("write the contents");
let read_contents = RealFileSystem
.read_to_string(file_path.to_str().expect("paths are utf8"))
.expect("read the file");
prop_assert_eq!(read_contents, contents);
}
}
}