| // 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 fuchsia_url::pkg_url::PkgUrl; |
| use log::{error, warn}; |
| use omaha_client::installer::Plan; |
| use omaha_client::{ |
| protocol::{ |
| request::InstallSource, |
| response::{OmahaStatus, Response}, |
| }, |
| request_builder::RequestParams, |
| }; |
| use thiserror::Error; |
| |
| #[derive(Debug, PartialEq)] |
| pub struct FuchsiaInstallPlan { |
| /// The fuchsia TUF repo URL, e.g. fuchsia-pkg://fuchsia.com/update/0?hash=... |
| pub url: PkgUrl, |
| pub install_source: InstallSource, |
| } |
| |
| #[derive(Debug, Error, PartialEq)] |
| pub enum InstallPlanErrors { |
| #[error("Fuchsia Install Plan could not be created from response")] |
| Failed, |
| } |
| |
| impl Plan for FuchsiaInstallPlan { |
| type Error = InstallPlanErrors; |
| |
| fn try_create_from( |
| request_params: &RequestParams, |
| resp: &Response, |
| ) -> Result<Self, Self::Error> { |
| let (app, rest) = if let Some((app, rest)) = resp.apps.split_first() { |
| (app, rest) |
| } else { |
| error!("No app in Omaha response."); |
| return Err(InstallPlanErrors::Failed); |
| }; |
| |
| if !rest.is_empty() { |
| warn!("Only 1 app is supported, found {}", resp.apps.len()); |
| } |
| |
| if app.status != OmahaStatus::Ok { |
| error!("Found non-ok app status: {:?}", app.status); |
| return Err(InstallPlanErrors::Failed); |
| } |
| |
| let update_check = if let Some(update_check) = &app.update_check { |
| update_check |
| } else { |
| error!("No update_check in Omaha response."); |
| return Err(InstallPlanErrors::Failed); |
| }; |
| |
| let urls = match update_check.status { |
| OmahaStatus::Ok => { |
| if let Some(urls) = &update_check.urls { |
| &urls.url |
| } else { |
| error!("No urls in Omaha response."); |
| return Err(InstallPlanErrors::Failed); |
| } |
| } |
| OmahaStatus::NoUpdate => { |
| error!("Was asked to create an install plan for a NoUpdate Omaha response"); |
| return Err(InstallPlanErrors::Failed); |
| } |
| _ => { |
| warn!("Unexpected update check status: {:?}", update_check.status); |
| if let Some(info) = &update_check.info { |
| warn!("update check status info: {}", info); |
| } |
| return Err(InstallPlanErrors::Failed); |
| } |
| }; |
| let (url, rest) = if let Some((url, rest)) = urls.split_first() { |
| (url, rest) |
| } else { |
| error!("No url in Omaha response."); |
| return Err(InstallPlanErrors::Failed); |
| }; |
| |
| if !rest.is_empty() { |
| warn!("Only 1 url is supported, found {}", urls.len()); |
| } |
| |
| let manifest = if let Some(manifest) = &update_check.manifest { |
| manifest |
| } else { |
| error!("No manifest in Omaha response."); |
| return Err(InstallPlanErrors::Failed); |
| }; |
| |
| let (package, rest) = if let Some((package, rest)) = manifest.packages.package.split_first() |
| { |
| (package, rest) |
| } else { |
| error!("No package in Omaha response."); |
| return Err(InstallPlanErrors::Failed); |
| }; |
| |
| if !rest.is_empty() { |
| warn!("Only 1 package is supported, found {}", manifest.packages.package.len()); |
| } |
| |
| let full_url = url.codebase.clone() + &package.name; |
| match PkgUrl::parse(&full_url) { |
| Ok(url) => { |
| Ok(FuchsiaInstallPlan { url, install_source: request_params.source.clone() }) |
| } |
| Err(err) => { |
| error!("Failed to parse {} to PkgUrl: {}", url.codebase, err); |
| Err(InstallPlanErrors::Failed) |
| } |
| } |
| } |
| |
| fn id(&self) -> String { |
| self.url.to_string() |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| use omaha_client::protocol::response::{App, Manifest, Package, Packages, UpdateCheck}; |
| |
| const TEST_URL_BASE: &str = "fuchsia-pkg://fuchsia.com/"; |
| const TEST_PACKAGE_NAME: &str = "update/0"; |
| |
| #[test] |
| fn test_simple_response() { |
| let request_params = RequestParams::default(); |
| let mut update_check = UpdateCheck::ok(vec![TEST_URL_BASE.to_string()]); |
| update_check.manifest = Some(Manifest { |
| packages: Packages { |
| package: vec![Package { |
| name: TEST_PACKAGE_NAME.to_string(), |
| ..Package::default() |
| }], |
| }, |
| ..Manifest::default() |
| }); |
| let response = Response { |
| apps: vec![App { update_check: Some(update_check), ..App::default() }], |
| ..Response::default() |
| }; |
| |
| let install_plan = FuchsiaInstallPlan::try_create_from(&request_params, &response).unwrap(); |
| assert_eq!(install_plan.url.to_string(), TEST_URL_BASE.to_string() + TEST_PACKAGE_NAME); |
| assert_eq!(install_plan.install_source, request_params.source); |
| } |
| |
| #[test] |
| fn test_no_app() { |
| let request_params = RequestParams::default(); |
| let response = Response::default(); |
| |
| assert_eq!( |
| FuchsiaInstallPlan::try_create_from(&request_params, &response), |
| Err(InstallPlanErrors::Failed) |
| ); |
| } |
| |
| #[test] |
| fn test_multiple_app() { |
| let request_params = RequestParams::default(); |
| let mut update_check = UpdateCheck::ok(vec![TEST_URL_BASE.to_string()]); |
| update_check.manifest = Some(Manifest { |
| packages: Packages { |
| package: vec![Package { |
| name: TEST_PACKAGE_NAME.to_string(), |
| ..Package::default() |
| }], |
| }, |
| ..Manifest::default() |
| }); |
| let response = Response { |
| apps: vec![App { update_check: Some(update_check), ..App::default() }], |
| ..Response::default() |
| }; |
| |
| let install_plan = FuchsiaInstallPlan::try_create_from(&request_params, &response).unwrap(); |
| assert_eq!(install_plan.url.to_string(), TEST_URL_BASE.to_string() + TEST_PACKAGE_NAME); |
| assert_eq!(install_plan.install_source, request_params.source); |
| } |
| |
| #[test] |
| fn test_no_update_check() { |
| let request_params = RequestParams::default(); |
| let response = Response { apps: vec![App::default()], ..Response::default() }; |
| |
| assert_eq!( |
| FuchsiaInstallPlan::try_create_from(&request_params, &response), |
| Err(InstallPlanErrors::Failed) |
| ); |
| } |
| |
| #[test] |
| fn test_no_urls() { |
| let request_params = RequestParams::default(); |
| let response = Response { |
| apps: vec![App { update_check: Some(UpdateCheck::default()), ..App::default() }], |
| ..Response::default() |
| }; |
| |
| assert_eq!( |
| FuchsiaInstallPlan::try_create_from(&request_params, &response), |
| Err(InstallPlanErrors::Failed) |
| ); |
| } |
| |
| #[test] |
| fn test_app_error_status() { |
| let request_params = RequestParams::default(); |
| let response = Response { |
| apps: vec![App { |
| status: OmahaStatus::Error("error-unknownApplication".to_string()), |
| ..App::default() |
| }], |
| ..Response::default() |
| }; |
| |
| assert_eq!( |
| FuchsiaInstallPlan::try_create_from(&request_params, &response), |
| Err(InstallPlanErrors::Failed) |
| ); |
| } |
| |
| #[test] |
| fn test_no_update() { |
| let request_params = RequestParams::default(); |
| let response = Response { |
| apps: vec![App { update_check: Some(UpdateCheck::no_update()), ..App::default() }], |
| ..Response::default() |
| }; |
| |
| assert_eq!( |
| FuchsiaInstallPlan::try_create_from(&request_params, &response), |
| Err(InstallPlanErrors::Failed) |
| ); |
| } |
| |
| #[test] |
| fn test_invalid_url() { |
| let request_params = RequestParams::default(); |
| let response = Response { |
| apps: vec![App { |
| update_check: Some(UpdateCheck::ok(vec!["invalid-url".to_string()])), |
| ..App::default() |
| }], |
| ..Response::default() |
| }; |
| assert_eq!( |
| FuchsiaInstallPlan::try_create_from(&request_params, &response), |
| Err(InstallPlanErrors::Failed) |
| ); |
| } |
| |
| #[test] |
| fn test_no_manifest() { |
| let request_params = RequestParams::default(); |
| let response = Response { |
| apps: vec![App { |
| update_check: Some(UpdateCheck::ok(vec![TEST_URL_BASE.to_string()])), |
| ..App::default() |
| }], |
| ..Response::default() |
| }; |
| |
| assert_eq!( |
| FuchsiaInstallPlan::try_create_from(&request_params, &response), |
| Err(InstallPlanErrors::Failed) |
| ); |
| } |
| |
| #[test] |
| fn test_install_plan_id() { |
| let url = TEST_URL_BASE.to_string() + TEST_PACKAGE_NAME; |
| let install_plan = FuchsiaInstallPlan { |
| url: PkgUrl::parse(&url).unwrap(), |
| install_source: InstallSource::ScheduledTask, |
| }; |
| |
| assert_eq!(install_plan.id(), url); |
| } |
| } |