blob: 502da40268967a797b0718f6626b6afe3f833b2d [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::channel::ChannelConfigs;
use anyhow::{anyhow, Error};
use fidl_fuchsia_boot::{ArgumentsMarker, ArgumentsProxy};
use log::{error, info, warn};
use omaha_client::{
common::{App, AppSet, UserCounting, Version},
configuration::{Config, Updater},
protocol::{request::OS, Cohort},
};
use std::fs;
use std::io;
#[cfg(not(test))]
use sysconfig_client::channel::read_channel_config;
#[cfg(test)]
use sysconfig_mock::read_channel_config;
/// The source of the channel configuration.
#[derive(Debug, Eq, PartialEq)]
pub enum ChannelSource {
MinFS,
SysConfig,
Default,
VbMeta,
}
pub async fn get_app_set(
version: &str,
channel_configs: &Option<ChannelConfigs>,
) -> (AppSet, ChannelSource) {
let (appid, mut channel) = get_appid_and_channel_from_vbmeta().await.unwrap_or_else(|e| {
warn!("Failed to get app id and channel from vbmeta {:?}", e);
(None, None)
});
let channel_source = if channel.is_some() {
ChannelSource::VbMeta
} else {
let sysconfig_channel_config = read_channel_config();
info!("Channel configuration in sysconfig: {:?}", sysconfig_channel_config);
channel = sysconfig_channel_config.map(|config| config.channel_name().to_string()).ok();
if channel.is_some() {
ChannelSource::SysConfig
} else {
channel = channel_configs.as_ref().and_then(|configs| configs.default_channel.clone());
if channel.is_some() {
ChannelSource::Default
} else {
// Channel will be loaded from `Storage` by state machine.
ChannelSource::MinFS
}
}
};
// If no appid in vbmeta, look up the appid of the channel from channel configs.
let appid = appid.or_else(|| {
channel_configs.as_ref().and_then(|configs| {
channel.as_ref().and_then(|channel| {
configs
.known_channels
.iter()
.find(|c| &c.name == channel)
.and_then(|c| c.appid.clone())
})
})
});
let id = appid.unwrap_or_else(|| match fs::read_to_string("/config/data/omaha_app_id") {
Ok(id) => id,
Err(e) => {
error!("Unable to read omaha app id from config/data: {:?}", e);
String::new()
}
});
let version = match version.parse::<Version>() {
Ok(version) => version,
Err(e) => {
error!("Unable to parse '{}' as Omaha version format: {:?}", version, e);
Version::from([0])
}
};
let cohort = Cohort { hint: channel.clone(), name: channel, ..Cohort::default() };
(
// Fuchsia only has a single app.
AppSet::new(vec![App {
id,
version,
fingerprint: None,
cohort,
user_counting: UserCounting::ClientRegulatedByDate(None),
}]),
channel_source,
)
}
pub async fn get_config(version: &str) -> Config {
// This file does not exist in production, it is only used in integration/e2e testing.
let service_url = match get_service_url_from_vbmeta().await {
Ok(Some(url)) => url,
_ => fs::read_to_string("/config/data/omaha_url")
.unwrap_or("https://clients2.google.com/service/update2/fuchsia/json".to_string()),
};
Config {
updater: Updater { name: "Fuchsia".to_string(), version: Version::from([0, 0, 1, 0]) },
os: OS {
platform: "Fuchsia".to_string(),
version: version.to_string(),
service_pack: "".to_string(),
arch: std::env::consts::ARCH.to_string(),
},
service_url,
}
}
pub fn get_version() -> Result<String, io::Error> {
fs::read_to_string("/config/build-info/version").map(|s| s.trim_end().to_string())
}
async fn get_appid_and_channel_from_vbmeta() -> Result<(Option<String>, Option<String>), Error> {
let proxy = fuchsia_component::client::connect_to_service::<ArgumentsMarker>()?;
get_appid_and_channel_from_vbmeta_impl(proxy).await
}
async fn get_appid_and_channel_from_vbmeta_impl(
proxy: ArgumentsProxy,
) -> Result<(Option<String>, Option<String>), Error> {
let vec = vec!["omaha_app_id", "ota_channel"];
let res = proxy.get_strings(&mut vec.into_iter()).await?;
if res.len() != 2 {
Err(anyhow!("Remote endpoint returned {} values, expected 2", res.len()))
} else {
Ok((res[0].clone(), res[1].clone()))
}
}
async fn get_service_url_from_vbmeta() -> Result<Option<String>, Error> {
let proxy = fuchsia_component::client::connect_to_service::<ArgumentsMarker>()?;
get_service_url_from_vbmeta_impl(proxy).await
}
async fn get_service_url_from_vbmeta_impl(proxy: ArgumentsProxy) -> Result<Option<String>, Error> {
Ok(proxy.get_string("omaha_url").await?)
}
#[cfg(test)]
mod sysconfig_mock {
use std::cell::RefCell;
use sysconfig_client::channel::{ChannelConfigError, OtaUpdateChannelConfig};
thread_local! {
static MOCK_RESULT: RefCell<Result<OtaUpdateChannelConfig, ChannelConfigError>> =
RefCell::new(Err(ChannelConfigError::Magic(0)));
}
pub(super) fn read_channel_config() -> Result<OtaUpdateChannelConfig, ChannelConfigError> {
MOCK_RESULT.with(|result| result.replace(Err(ChannelConfigError::Magic(0))))
}
pub(super) fn set_read_channel_config_result(
new_result: Result<OtaUpdateChannelConfig, ChannelConfigError>,
) {
MOCK_RESULT.with(|result| *result.borrow_mut() = new_result);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::channel::ChannelConfig;
use fidl::endpoints::create_proxy_and_stream;
use fidl_fuchsia_boot::ArgumentsRequest;
use fuchsia_async as fasync;
use futures::prelude::*;
use sysconfig_client::channel::OtaUpdateChannelConfig;
#[fasync::run_singlethreaded(test)]
async fn test_get_config() {
let config = get_config("1.2.3.4").await;
assert_eq!(config.updater.name, "Fuchsia");
let os = config.os;
assert_eq!(os.platform, "Fuchsia");
assert_eq!(os.version, "1.2.3.4");
assert_eq!(os.arch, std::env::consts::ARCH);
assert_eq!(config.service_url, "https://clients2.google.com/service/update2/fuchsia/json");
}
#[fasync::run_singlethreaded(test)]
async fn test_get_app_set() {
let (app_set, channel_source) = get_app_set("1.2.3.4", &None).await;
assert_eq!(channel_source, ChannelSource::MinFS);
let apps = app_set.to_vec().await;
assert_eq!(apps.len(), 1);
assert_eq!(apps[0].id, "fuchsia:test-app-id");
assert_eq!(apps[0].version, Version::from([1, 2, 3, 4]));
assert_eq!(apps[0].cohort.name, None);
assert_eq!(apps[0].cohort.hint, None);
}
#[fasync::run_singlethreaded(test)]
async fn test_get_app_set_default_channel() {
let (app_set, channel_source) = get_app_set(
"1.2.3.4",
&Some(ChannelConfigs {
default_channel: Some("default-channel".to_string()),
known_channels: vec![],
}),
)
.await;
assert_eq!(channel_source, ChannelSource::Default);
let apps = app_set.to_vec().await;
assert_eq!(apps.len(), 1);
assert_eq!(apps[0].id, "fuchsia:test-app-id");
assert_eq!(apps[0].version, Version::from([1, 2, 3, 4]));
assert_eq!(apps[0].cohort.name, Some("default-channel".to_string()));
assert_eq!(apps[0].cohort.hint, Some("default-channel".to_string()));
}
#[fasync::run_singlethreaded(test)]
async fn test_get_app_set_appid_from_channel_configs() {
let (app_set, channel_source) = get_app_set(
"1.2.3.4",
&Some(ChannelConfigs {
default_channel: Some("some-channel".to_string()),
known_channels: vec![
ChannelConfig::new("no-appid-channel"),
ChannelConfig::with_appid("wrong-channel", "wrong-appid"),
ChannelConfig::with_appid("some-channel", "some-appid"),
ChannelConfig::with_appid("some-other-channel", "some-other-appid"),
],
}),
)
.await;
assert_eq!(channel_source, ChannelSource::Default);
let apps = app_set.to_vec().await;
assert_eq!(apps.len(), 1);
assert_eq!(apps[0].id, "some-appid");
assert_eq!(apps[0].version, Version::from([1, 2, 3, 4]));
assert_eq!(apps[0].cohort.name, Some("some-channel".to_string()));
assert_eq!(apps[0].cohort.hint, Some("some-channel".to_string()));
}
#[fasync::run_singlethreaded(test)]
async fn test_get_app_set_appid_from_channel_configs_sysconfig() {
sysconfig_mock::set_read_channel_config_result(OtaUpdateChannelConfig::new(
"some-channel",
"some-repo",
));
let (app_set, channel_source) = get_app_set(
"1.2.3.4",
&Some(ChannelConfigs {
default_channel: Some("some-other-channel".to_string()),
known_channels: vec![
ChannelConfig::new("no-appid-channel"),
ChannelConfig::with_appid("wrong-channel", "wrong-appid"),
ChannelConfig::with_appid("some-channel", "some-appid"),
ChannelConfig::with_appid("some-other-channel", "some-other-appid"),
],
}),
)
.await;
assert_eq!(channel_source, ChannelSource::SysConfig);
let apps = app_set.to_vec().await;
assert_eq!(apps.len(), 1);
assert_eq!(apps[0].id, "some-appid");
assert_eq!(apps[0].version, Version::from([1, 2, 3, 4]));
assert_eq!(apps[0].cohort.name, Some("some-channel".to_string()));
assert_eq!(apps[0].cohort.hint, Some("some-channel".to_string()));
}
#[fasync::run_singlethreaded(test)]
async fn test_get_app_set_invalid_version() {
let (app_set, _) = get_app_set("invalid version", &None).await;
let apps = app_set.to_vec().await;
assert_eq!(apps[0].version, Version::from([0]));
}
#[fasync::run_singlethreaded(test)]
async fn test_get_appid_and_channel_from_vbmeta() {
let (proxy, mut stream) = create_proxy_and_stream::<ArgumentsMarker>().unwrap();
let fut = async move {
let (appid, channel) = get_appid_and_channel_from_vbmeta_impl(proxy).await.unwrap();
assert_eq!(appid, Some("test-appid".to_string()));
assert_eq!(channel, Some("test-channel".to_string()));
};
let stream_fut = async move {
match stream.next().await.unwrap() {
Ok(ArgumentsRequest::GetStrings { keys, responder }) => {
assert_eq!(keys, vec!["omaha_app_id", "ota_channel"]);
let vec: Vec<Option<&str>> = vec![Some("test-appid"), Some("test-channel")];
responder.send(&mut vec.into_iter()).expect("send failed");
}
request => panic!("Unexpected request: {:?}", request),
}
};
future::join(fut, stream_fut).await;
}
#[fasync::run_singlethreaded(test)]
async fn test_get_appid_and_channel_from_vbmeta_missing() {
let (proxy, mut stream) = create_proxy_and_stream::<ArgumentsMarker>().unwrap();
let fut = async move {
let (appid, channel) = get_appid_and_channel_from_vbmeta_impl(proxy).await.unwrap();
assert_eq!(appid, None);
assert_eq!(channel, None);
};
let stream_fut = async move {
match stream.next().await.unwrap() {
Ok(ArgumentsRequest::GetStrings { keys, responder }) => {
assert_eq!(keys.len(), 2);
let ret: Vec<Option<&str>> = vec![None, None];
responder.send(&mut ret.into_iter()).expect("send failed");
}
request => panic!("Unexpected request: {:?}", request),
}
};
future::join(fut, stream_fut).await;
}
#[fasync::run_singlethreaded(test)]
async fn test_get_appid_and_channel_from_vbmeta_error() {
let (proxy, mut stream) = create_proxy_and_stream::<ArgumentsMarker>().unwrap();
let fut = async move {
assert!(get_appid_and_channel_from_vbmeta_impl(proxy).await.is_err());
};
let stream_fut = async move {
match stream.next().await.unwrap() {
Ok(ArgumentsRequest::GetStrings { .. }) => {
// Don't respond.
}
request => panic!("Unexpected request: {:?}", request),
}
};
future::join(fut, stream_fut).await;
}
#[fasync::run_singlethreaded(test)]
async fn test_get_service_url_from_vbmeta() {
let (proxy, mut stream) = create_proxy_and_stream::<ArgumentsMarker>().unwrap();
let fut = async move {
let url = get_service_url_from_vbmeta_impl(proxy).await.unwrap();
assert_eq!(url, Some("test-url".to_string()));
};
let stream_fut = async move {
match stream.next().await.unwrap() {
Ok(ArgumentsRequest::GetString { key, responder }) => {
assert_eq!(key, "omaha_url");
responder.send(Some("test-url")).expect("send failed");
}
request => panic!("Unexpected request: {:?}", request),
}
};
future::join(fut, stream_fut).await;
}
#[fasync::run_singlethreaded(test)]
async fn test_get_service_url_from_vbmeta_missing() {
let (proxy, mut stream) = create_proxy_and_stream::<ArgumentsMarker>().unwrap();
let fut = async move {
let url = get_service_url_from_vbmeta_impl(proxy).await.unwrap();
assert_eq!(url, None);
};
let stream_fut = async move {
match stream.next().await.unwrap() {
Ok(ArgumentsRequest::GetString { key, responder }) => {
assert_eq!(key, "omaha_url");
responder.send(None).expect("send failed");
}
request => panic!("Unexpected request: {:?}", request),
}
};
future::join(fut, stream_fut).await;
}
#[fasync::run_singlethreaded(test)]
async fn test_get_service_url_from_vbmeta_error() {
let (proxy, mut stream) = create_proxy_and_stream::<ArgumentsMarker>().unwrap();
let fut = async move {
assert!(get_service_url_from_vbmeta_impl(proxy).await.is_err());
};
let stream_fut = async move {
match stream.next().await.unwrap() {
Ok(ArgumentsRequest::GetString { .. }) => {
// Don't respond.
}
request => panic!("Unexpected request: {:?}", request),
}
};
future::join(fut, stream_fut).await;
}
}