blob: b18dbbbdec298d04b37e821491c37cf7745762a6 [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::{CurrentChannelManager, TargetChannelManager},
rate_limiter::RateLimiterMonotonic,
},
anyhow::{anyhow, Context as _, Error},
fidl_fuchsia_update_channel::{ProviderRequest, ProviderRequestStream},
fidl_fuchsia_update_channelcontrol::{ChannelControlRequest, ChannelControlRequestStream},
fuchsia_syslog::fx_log_warn,
fuchsia_zircon as zx,
futures::prelude::*,
std::sync::Arc,
};
pub(crate) struct ChannelHandler {
current_channel_manager: Arc<CurrentChannelManager>,
target_channel_manager: Arc<TargetChannelManager>,
warn_rate_limiter: RateLimiterMonotonic,
}
const GET_CURRENT_WARN_DELAY: zx::Duration = zx::Duration::from_minutes(30);
impl ChannelHandler {
pub fn new(
current_channel_manager: Arc<CurrentChannelManager>,
target_channel_manager: Arc<TargetChannelManager>,
) -> Self {
Self {
current_channel_manager,
target_channel_manager,
warn_rate_limiter: RateLimiterMonotonic::from_delay(GET_CURRENT_WARN_DELAY),
}
}
pub(crate) async fn handle_provider_request_stream(
&self,
mut stream: ProviderRequestStream,
) -> Result<(), Error> {
while let Some(request) =
stream.try_next().await.context("extracting request from stream")?
{
match request {
ProviderRequest::GetCurrent { responder } => {
let channel = self.get_current();
responder.send(&channel).context("sending GetCurrent response")?;
}
}
}
Ok(())
}
pub(crate) async fn handle_control_request_stream(
&self,
mut stream: ChannelControlRequestStream,
) -> Result<(), Error> {
while let Some(request) =
stream.try_next().await.context("extracting request from stream")?
{
match request {
ChannelControlRequest::GetCurrent { responder } => {
let channel = self.get_current();
responder.send(&channel).context("sending GetCurrent response")?;
}
ChannelControlRequest::GetTarget { responder } => {
if let Some(channel) = self.target_channel_manager.get_target_channel() {
responder.send(&channel).context("sending GetTarget response")?;
} else {
fx_log_warn!("target channel not available");
}
}
ChannelControlRequest::GetTargetList { responder } => {
let channel_names = self.target_channel_manager.get_channel_list().await?;
responder
.send(&mut channel_names.iter().map(|s| s.as_str()))
.context("sending GetTargetList response")?;
}
ChannelControlRequest::SetTarget { channel, responder } => {
self.target_channel_manager.set_target_channel(channel).await?;
responder.send().context("sending SetTarget response")?;
}
}
}
Ok(())
}
fn get_current(&self) -> String {
self.current_channel_manager.read_current_channel().unwrap_or_else(|err| {
self.warn_rate_limiter.rate_limit(|| {
fx_log_warn!("error getting current channel: {:#}", anyhow!(err));
});
// TODO: Remove this once we have channel in vbmeta (fxbug.dev/39970).
self.target_channel_manager.get_target_channel().unwrap_or_default()
})
}
}
#[cfg(test)]
mod tests {
use {
super::*,
fidl::endpoints::create_proxy_and_stream,
fidl_fuchsia_update_channel::{ProviderMarker, ProviderProxy},
fidl_fuchsia_update_channelcontrol::{ChannelControlMarker, ChannelControlProxy},
fuchsia_async as fasync,
futures::channel::mpsc,
std::{fs, path::Path},
tempfile::TempDir,
};
fn new_test_channel_handler(info_dir: &TempDir) -> ChannelHandler {
let (sender, _) = mpsc::channel(0);
ChannelHandler {
current_channel_manager: Arc::new(CurrentChannelManager::new(
info_dir.path().into(),
sender,
)),
target_channel_manager: Arc::new(TargetChannelManager::new(
crate::connect::ServiceConnector,
info_dir.path(),
)),
warn_rate_limiter: RateLimiterMonotonic::from_delay(GET_CURRENT_WARN_DELAY),
}
}
fn spawn_provider_handler(info_dir: &TempDir) -> ProviderProxy {
let channel_handler = new_test_channel_handler(info_dir);
let (proxy, stream) =
create_proxy_and_stream::<ProviderMarker>().expect("create_proxy_and_stream");
fasync::Task::spawn(async move {
channel_handler.handle_provider_request_stream(stream).map(|_| ()).await
})
.detach();
proxy
}
fn spawn_channel_handler(info_dir: &TempDir) -> ChannelControlProxy {
let channel_handler = new_test_channel_handler(info_dir);
let (proxy, stream) =
create_proxy_and_stream::<ChannelControlMarker>().expect("create_proxy_and_stream");
fasync::Task::spawn(async move {
channel_handler.handle_control_request_stream(stream).map(|_| ()).await
})
.detach();
proxy
}
fn create_info_dir_with_channel(file_name: impl AsRef<Path>) -> TempDir {
let tempdir = TempDir::new().expect("create tempdir");
let path = tempdir.path().join(file_name);
fs::write(&path, r#"{"version":"1","content":{"legacy_amber_source_name":"example"}}"#)
.expect(&format!("write {:?}", path));
tempdir
}
#[fasync::run_singlethreaded(test)]
async fn test_fidl_get_current_works() {
let tempdir = create_info_dir_with_channel("current_channel.json");
let proxy = spawn_provider_handler(&tempdir);
let res = proxy.get_current().await;
assert_eq!(res.map_err(|e| e.to_string()), Ok("example".into()));
}
#[fasync::run_singlethreaded(test)]
async fn test_fidl_channel_control_get_current_works() {
let tempdir = create_info_dir_with_channel("current_channel.json");
let proxy = spawn_channel_handler(&tempdir);
let res = proxy.get_current().await;
assert_eq!(res.map_err(|e| e.to_string()), Ok("example".into()));
}
#[fasync::run_singlethreaded(test)]
async fn test_fidl_get_current_return_target_channel_if_current_channel_missing() {
let tempdir = create_info_dir_with_channel("target_channel.json");
let proxy = spawn_provider_handler(&tempdir);
let res = proxy.get_current().await;
assert_eq!(res.map_err(|e| e.to_string()), Ok("example".into()));
}
#[fasync::run_singlethreaded(test)]
async fn test_fidl_get_current_return_empty_string_if_both_channel_missing() {
let tempdir = TempDir::new().expect("create tempdir");
let proxy = spawn_provider_handler(&tempdir);
let res = proxy.get_current().await;
assert_eq!(res.map_err(|e| e.to_string()), Ok("".into()));
}
#[fasync::run_singlethreaded(test)]
async fn test_fidl_get_target_works() {
let tempdir = create_info_dir_with_channel("target_channel.json");
let proxy = spawn_channel_handler(&tempdir);
let res = proxy.get_target().await;
assert_eq!(res.map_err(|e| e.to_string()), Ok("example".into()));
}
#[fasync::run_singlethreaded(test)]
async fn test_fidl_set_target_works() {
let tempdir = TempDir::new().expect("create tempdir");
let proxy = spawn_channel_handler(&tempdir);
proxy.set_target("target-channel").await.unwrap();
assert_eq!(
fs::read_to_string(tempdir.path().join("target_channel.json")).unwrap(),
r#"{"version":"1","content":{"legacy_amber_source_name":"target-channel"}}"#
);
}
}