blob: c91e33be2822b4e76b68f9b3950f7633c73752c1 [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::rewrite_manager::{CommitError, RewriteManager},
failure::Error,
fidl_fuchsia_pkg_rewrite::{
EditTransactionRequest, EditTransactionRequestStream, EngineRequest, EngineRequestStream,
RuleIteratorRequest, RuleIteratorRequestStream,
},
fuchsia_async as fasync,
fuchsia_syslog::fx_log_err,
fuchsia_zircon::Status,
futures::prelude::*,
parking_lot::RwLock,
std::{convert::TryInto, sync::Arc},
};
const LIST_CHUNK_SIZE: usize = 100;
#[derive(Debug, Clone)]
pub struct RewriteService {
state: Arc<RwLock<RewriteManager>>,
}
impl RewriteService {
pub fn new(state: Arc<RwLock<RewriteManager>>) -> Self {
RewriteService { state }
}
pub async fn handle_client(&mut self, mut stream: EngineRequestStream) -> Result<(), Error> {
while let Some(request) = await!(stream.try_next())? {
match request {
EngineRequest::List { iterator, control_handle: _control_handle } => {
let iterator = iterator.into_stream()?;
self.serve_list(iterator);
}
EngineRequest::StartEditTransaction {
transaction,
control_handle: _control_handle,
} => {
let transaction = transaction.into_stream()?;
self.serve_edit_transaction(transaction);
}
}
}
Ok(())
}
pub(self) fn serve_list(&mut self, mut stream: RuleIteratorRequestStream) {
let mut rules: Vec<_> = self.state.read().list().map(|rule| rule.clone().into()).collect();
fasync::spawn(
async move {
let mut iter = rules.iter_mut();
while let Some(request) = await!(stream.try_next())? {
let RuleIteratorRequest::Next { responder } = request;
responder.send(&mut iter.by_ref().take(LIST_CHUNK_SIZE))?;
}
Ok(())
}
.unwrap_or_else(|e: Error| fx_log_err!("while serving rewrite rule list: {:?}", e)),
)
}
pub(self) fn serve_edit_transaction(&mut self, mut stream: EditTransactionRequestStream) {
let state = self.state.clone();
let mut transaction = state.read().transaction();
fasync::spawn(
async move {
while let Some(request) = await!(stream.try_next())? {
match request {
EditTransactionRequest::ResetAll { control_handle: _control_handle } => {
transaction.reset_all();
}
EditTransactionRequest::Add { rule, responder } => {
let status = match rule.try_into() {
Ok(rule) => {
transaction.add(rule);
Status::OK
}
Err(_) => Status::INVALID_ARGS,
};
responder.send(status.into_raw())?;
}
EditTransactionRequest::Commit { responder } => {
let status = match state.write().apply(transaction) {
Ok(()) => Status::OK,
Err(CommitError::TooLate) => Status::UNAVAILABLE,
};
responder.send(status.into_raw())?;
return Ok(());
}
}
}
Ok(())
}
.unwrap_or_else(|e: Error| {
fx_log_err!("while serving rewrite rule edit transaction: {:?}", e)
}),
)
}
}
#[cfg(test)]
mod tests {
use {
super::*,
crate::rewrite_manager::{tests::make_rule_config, RewriteManagerBuilder, Transaction},
fidl::endpoints::create_proxy_and_stream,
fidl_fuchsia_pkg_rewrite::{EditTransactionMarker, RuleIteratorMarker},
};
macro_rules! rule {
($host_match:expr => $host_replacement:expr,
$path_prefix_match:expr => $path_prefix_replacement:expr) => {
fuchsia_uri_rewrite::Rule::new(
$host_match.to_owned(),
$host_replacement.to_owned(),
$path_prefix_match.to_owned(),
$path_prefix_replacement.to_owned(),
)
.unwrap()
};
}
async fn verify_list_call(
state: Arc<RwLock<RewriteManager>>,
expected: Vec<fidl_fuchsia_pkg_rewrite::Rule>,
) {
let (client, request_stream) = create_proxy_and_stream::<RuleIteratorMarker>().unwrap();
RewriteService::new(state).serve_list(request_stream);
let mut rules = Vec::new();
loop {
let mut more = await!(client.next()).unwrap();
if more.is_empty() {
break;
}
rules.append(&mut more);
}
assert_eq!(rules, expected);
assert!(await!(client.next()).unwrap().is_empty());
}
#[fasync::run_until_stalled(test)]
async fn test_list() {
let rules = vec![
rule!("fuchsia.com" => "fuchsia.com", "/rolldice" => "/rolldice"),
rule!("fuchsia.com" => "fuchsia.com", "/rolldice/" => "/rolldice/"),
];
let dynamic_config = make_rule_config(rules.clone());
let state =
Arc::new(RwLock::new(RewriteManagerBuilder::new(&dynamic_config).unwrap().build()));
let expected = vec![
rule!("fuchsia.com" => "fuchsia.com", "/rolldice" => "/rolldice").into(),
rule!("fuchsia.com" => "fuchsia.com", "/rolldice/" => "/rolldice/").into(),
];
await!(verify_list_call(state.clone(), expected));
}
#[fasync::run_until_stalled(test)]
async fn test_reset_all() {
let rules = vec![
rule!("fuchsia.com" => "fuchsia.com", "/rolldice" => "/rolldice"),
rule!("fuchsia.com" => "fuchsia.com", "/rolldice/" => "/rolldice/"),
];
let dynamic_config = make_rule_config(rules.clone());
let state =
Arc::new(RwLock::new(RewriteManagerBuilder::new(&dynamic_config).unwrap().build()));
let mut service = RewriteService::new(state.clone());
let (client, request_stream) = create_proxy_and_stream::<EditTransactionMarker>().unwrap();
service.serve_edit_transaction(request_stream);
client.reset_all().unwrap();
assert_eq!(Status::from_raw(await!(client.commit()).unwrap()), Status::OK);
assert_eq!(state.read().transaction(), Transaction::new(vec![], 1));
}
#[fasync::run_until_stalled(test)]
async fn test_concurrent_edit() {
let rules = vec![
rule!("fuchsia.com" => "fuchsia.com", "/rolldice" => "/rolldice"),
rule!("fuchsia.com" => "fuchsia.com", "/rolldice/" => "/rolldice/"),
];
let dynamic_config = make_rule_config(rules.clone());
let state =
Arc::new(RwLock::new(RewriteManagerBuilder::new(&dynamic_config).unwrap().build()));
let mut service = RewriteService::new(state.clone());
let client1 = {
let (client, request_stream) =
create_proxy_and_stream::<EditTransactionMarker>().unwrap();
service.serve_edit_transaction(request_stream);
client
};
let client2 = {
let (client, request_stream) =
create_proxy_and_stream::<EditTransactionMarker>().unwrap();
service.serve_edit_transaction(request_stream);
client
};
client1.reset_all().unwrap();
client2.reset_all().unwrap();
let rule = rule!("fuchsia.com" => "fuchsia.com", "/foo" => "/foo");
assert_eq!(
Status::from_raw(await!(client1.add(&mut rule.clone().into())).unwrap()),
Status::OK
);
assert_eq!(Status::from_raw(await!(client1.commit()).unwrap()), Status::OK);
assert_eq!(Status::from_raw(await!(client2.commit()).unwrap()), Status::UNAVAILABLE);
assert_eq!(state.read().transaction(), Transaction::new(vec![rule], 1),);
let client2 = {
let (client, request_stream) =
create_proxy_and_stream::<EditTransactionMarker>().unwrap();
service.serve_edit_transaction(request_stream);
client
};
client2.reset_all().unwrap();
assert_eq!(Status::from_raw(await!(client2.commit()).unwrap()), Status::OK);
assert_eq!(state.read().transaction(), Transaction::new(vec![], 2));
}
#[fasync::run_until_stalled(test)]
async fn test_concurrent_list_and_edit() {
let dynamic_config = make_rule_config(vec![]);
let state =
Arc::new(RwLock::new(RewriteManagerBuilder::new(&dynamic_config).unwrap().build()));
let mut service = RewriteService::new(state.clone());
await!(verify_list_call(state.clone(), vec![]));
let edit_client = {
let (client, request_stream) =
create_proxy_and_stream::<EditTransactionMarker>().unwrap();
service.serve_edit_transaction(request_stream);
client
};
let rule = rule!("fuchsia.com" => "devhost.fuchsia.com", "/" => "/");
assert_eq!(
Status::from_raw(await!(edit_client.add(&mut rule.clone().into())).unwrap()),
Status::OK
);
await!(verify_list_call(state.clone(), vec![]));
let long_list_call = {
let (client, request_stream) = create_proxy_and_stream::<RuleIteratorMarker>().unwrap();
RewriteService::new(state.clone()).serve_list(request_stream);
client
};
assert_eq!(Status::from_raw(await!(edit_client.commit()).unwrap()), Status::OK);
assert_eq!(await!(long_list_call.next()).unwrap(), vec![]);
await!(verify_list_call(state.clone(), vec![rule.into()]));
}
}