| // Copyright 2020 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::common_utils::common::{read_json_from_vmo, write_json_to_vmo}; |
| use crate::server::constants::CONCURRENT_REQ_LIMIT; |
| use crate::server::Facade; |
| use anyhow::Error; |
| use async_trait::async_trait; |
| use fidl_fuchsia_testing_sl4f::{ |
| FacadeIteratorRequest, FacadeProviderRequest, FacadeProviderRequestStream, |
| }; |
| use futures::stream::{StreamExt, TryStreamExt}; |
| use serde_json::Value; |
| use std::sync::Arc; |
| use tracing::{error, info, warn}; |
| |
| /// Trait for the implementation of the FacadeProvider protocol. |
| #[async_trait(?Send)] |
| pub trait FacadeProvider { |
| /// Returns the object implementing Facade for the given facade name. |
| /// # Arguments: |
| /// * 'name' - A string representing the name of the facade. |
| fn get_facade(&self, name: &str) -> Option<Arc<dyn Facade>>; |
| |
| /// Returns an iterator over all of the Facade implementations. |
| fn get_facades(&self) -> Box<dyn Iterator<Item = &Arc<dyn Facade>> + '_>; |
| |
| /// Returns an iterator over all of the facade names. |
| fn get_facade_names(&self) -> Vec<String>; |
| |
| /// Invoked on FacadeProvider.GetFacades(). Sends the list of hosted facades back to the client on |
| /// subsequent calls to FacadeIterator.GetNext() over the channel given to GetFacades(). |
| /// # Arguments |
| /// * 'request' - A request from the FacadeProvider client. Must be a GetFacades() request. |
| async fn get_facades_impl(&self, request: FacadeProviderRequest) { |
| let iterator = match request { |
| FacadeProviderRequest::GetFacades { iterator, control_handle: _ } => iterator, |
| _ => panic!( |
| "get_facade_impl() must only be called with a FacadeProviderRequest::GetFacades." |
| ), |
| }; |
| |
| // Wrap operation in an async block in order to capture any error. |
| let get_facades_fut = async { |
| let mut iterator = iterator.into_stream()?; |
| if let Some(FacadeIteratorRequest::GetNext { responder }) = iterator.try_next().await? { |
| // NOTE: if the list of facade names would exceed the channel buffer size, |
| // they should be split over back-to-back responses to GetNext(). |
| responder.send(&self.get_facade_names())?; |
| if let Some(FacadeIteratorRequest::GetNext { responder }) = |
| iterator.try_next().await? |
| { |
| responder.send(&[])?; // Indicates completion. |
| } |
| } |
| Ok::<(), Error>(()) |
| }; |
| if let Err(error) = get_facades_fut.await { |
| error!(%error, "Failed to handle GetFacades()"); |
| } |
| } |
| |
| /// Invoked on FacadeProvider.Execute(). Executes the given command on a hosted facade. |
| /// # Arguments |
| /// * 'request' - A request from a FacadeProvider client. Must be an Execute() request. |
| async fn execute_impl(&self, request: FacadeProviderRequest) { |
| let (facade, command, params_blob, responder) = match request { |
| FacadeProviderRequest::Execute { facade, command, params_blob, responder } => { |
| (facade, command, params_blob, responder) |
| } |
| _ => { |
| panic!("execute_impl() must only be called with a FacadeProviderRequest::Execute.") |
| } |
| }; |
| |
| // Look-up the facade. |
| let facade = if let Some(f) = self.get_facade(&facade) { |
| f |
| } else { |
| let err_str = format!("Could not find facade: {}", facade); |
| error!("{}", err_str); |
| if let Err(send_error) = responder.send(None, Some(&err_str)) { |
| error!("Failed to send response with: {}", send_error); |
| } |
| return; |
| }; |
| |
| // Construct a JSON Value out of the params_blob. |
| let params = match read_json_from_vmo(¶ms_blob) { |
| Ok(value) => value, |
| Err(error) => { |
| if let Err(send_error) = |
| responder.send(None, Some(&format!("Failed to extract params: {}", error))) |
| { |
| error!(error = %send_error, "Failed to send response"); |
| } |
| return; |
| } |
| }; |
| |
| // Execute the command on the facade. On error or empty result, send the response. |
| let result = match facade.handle_request(command, params).await { |
| Ok(Value::Null) => { |
| if let Err(error) = responder.send(None, None) { |
| error!(%error, "Failed to send response"); |
| } |
| return; |
| } |
| Ok(result) => result, |
| Err(error) => { |
| if let Err(error) = responder.send(None, Some(&error.to_string())) { |
| error!(%error, "Failed to send response"); |
| } |
| return; |
| } |
| }; |
| |
| // Write the result blob into a VMO and send the response. |
| if let Err(send_error) = match write_json_to_vmo(¶ms_blob, &result) { |
| Ok(()) => responder.send(Some(params_blob), None), |
| Err(error) => responder.send(None, Some(&format!("Failed to write result: {}", error))), |
| } { |
| error!(error = %send_error, "Failed to send response"); |
| } |
| } |
| |
| /// Cleans up transient state on all hosted facades. Invoked on FacadeProvider.Cleanup(). |
| fn cleanup_impl(&self) { |
| for facade in self.get_facades() { |
| facade.cleanup(); |
| } |
| } |
| |
| /// Prints state for all hosted facades. Invoked on FacadeProvider.Print(). |
| fn print_impl(&self) { |
| for facade in self.get_facades() { |
| facade.print(); |
| } |
| } |
| |
| /// Invoked on each incoming request. Invokes the appropriate handler code. |
| /// # Arguments |
| /// * 'request' - Incoming request on the FacadeProvider connection. |
| async fn handle_request(&self, request: FacadeProviderRequest) { |
| match request { |
| FacadeProviderRequest::GetFacades { iterator, control_handle } => { |
| self.get_facades_impl(FacadeProviderRequest::GetFacades { |
| iterator, |
| control_handle, |
| }) |
| .await; |
| } |
| FacadeProviderRequest::Execute { facade, command, params_blob, responder } => { |
| info!("Received command {}.{}", facade, command); |
| self.execute_impl(FacadeProviderRequest::Execute { |
| facade, |
| command, |
| params_blob, |
| responder, |
| }) |
| .await; |
| } |
| FacadeProviderRequest::Cleanup { responder } => { |
| self.cleanup_impl(); |
| if let Err(error) = responder.send() { |
| warn!(%error, "Failed to notify completion of Cleanup()"); |
| } |
| } |
| FacadeProviderRequest::Print { responder } => { |
| self.print_impl(); |
| if let Err(error) = responder.send() { |
| error!(%error, "Failed to notify completion of Print()"); |
| } |
| } |
| } |
| } |
| |
| /// Invoked on an incoming FacadeProvider connection request. Requests arriving on the stream |
| /// will be handled concurrently. |
| /// NOTE: The main SL4F server doesn't appear to wait before any outstanding requests are |
| /// completed before allowing a cleanup operation to move forward, and this code similarly |
| /// makes no such effort. As such, it is up to the test harness to ensure that cleanup and |
| /// command requests do not interleave in order to ensure that the facades and device remain in |
| /// a consistent state. |
| /// # Arguments |
| /// * 'stream' - Incoming FacadeProvider request stream. |
| async fn run_facade_provider(&self, stream: FacadeProviderRequestStream) { |
| stream |
| .for_each_concurrent(CONCURRENT_REQ_LIMIT, |request| { |
| self.handle_request(request.unwrap()) |
| }) |
| .await; |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| use anyhow::format_err; |
| use fidl_fuchsia_testing_sl4f::FacadeProviderMarker; |
| use fuchsia_async as fasync; |
| use fuchsia_zircon as zx; |
| use serde_json::json; |
| use std::cell::RefCell; |
| use std::collections::HashMap; |
| use std::pin::pin; |
| use std::task::Poll; |
| |
| /// TestFacade provides a trivial Facade implementation which supports commands to interact |
| /// with the state. |
| #[derive(Debug)] |
| struct TestFacade { |
| // Trivial state accessed through TestFacade's implementation of Facade. |
| state: RefCell<bool>, |
| } |
| |
| impl TestFacade { |
| pub fn new() -> TestFacade { |
| TestFacade { state: RefCell::new(false) } |
| } |
| } |
| |
| #[async_trait(?Send)] |
| impl Facade for TestFacade { |
| async fn handle_request(&self, method: String, args: Value) -> Result<Value, Error> { |
| match method.as_str() { |
| "set" => { |
| if let Value::Bool(new_state) = args { |
| *self.state.borrow_mut() = new_state; |
| return Ok(json!(null)); |
| } |
| panic!("Received invalid args"); |
| } |
| "get" => { |
| if let Value::Null = args { |
| return Ok(json!(*self.state.borrow())); |
| } |
| panic!("Received invalid args"); |
| } |
| _ => return Err(format_err!("Invalid TestFacade method {}", method)), |
| } |
| } |
| |
| fn cleanup(&self) { |
| *self.state.borrow_mut() = false; |
| } |
| |
| fn print(&self) {} |
| } |
| |
| /// TestFacadeProvider provides a simple implementation of the FacadeProvider trait and hosts |
| /// the TestFacade. |
| struct TestFacadeProvider { |
| facades: HashMap<String, Arc<dyn Facade>>, |
| } |
| |
| impl TestFacadeProvider { |
| pub fn new() -> TestFacadeProvider { |
| let mut facades: HashMap<String, Arc<dyn Facade>> = HashMap::new(); |
| facades |
| .insert("test_facade".to_string(), Arc::new(TestFacade::new()) as Arc<dyn Facade>); |
| TestFacadeProvider { facades } |
| } |
| } |
| |
| #[async_trait(?Send)] |
| impl FacadeProvider for TestFacadeProvider { |
| fn get_facade(&self, name: &str) -> Option<Arc<dyn Facade>> { |
| self.facades.get(name).map(Arc::clone) |
| } |
| |
| fn get_facades(&self) -> Box<dyn Iterator<Item = &Arc<dyn Facade>> + '_> { |
| Box::new(self.facades.values()) |
| } |
| |
| fn get_facade_names(&self) -> Vec<String> { |
| self.facades.keys().cloned().collect() |
| } |
| } |
| |
| /// This test exercises the default FacadeProvider server implementation provided in the |
| /// FacadeProvider trait using TestFacadeProvider and TestFacade. It creates server and client |
| /// endpoints to a channel, passing each respectively to async blocks for the server and client |
| /// functionality. These async blocks are joined and passed to a single-threaded executor. The |
| /// server simply waits on its endpoint. The client does the following: |
| /// 1. Gets and verifies initial state. |
| /// 2. Sets a new state. |
| /// 3. Gets and verifies a new state. |
| /// 4. Cleans up state. |
| /// 5. Gets and verifies the clean state. |
| /// 6. Releases the client end so that the server end can exit. |
| #[test] |
| fn test_facade_provider() -> Result<(), Error> { |
| let mut executor = fasync::TestExecutor::new(); |
| |
| let (proxy, stream) = fidl::endpoints::create_proxy_and_stream::<FacadeProviderMarker>()?; |
| let server_fut = async { |
| // Run the FacadeProvider server. |
| let sl4f = TestFacadeProvider::new(); |
| sl4f.run_facade_provider(stream).await; |
| }; |
| let client_fut = async { |
| // Get the initial state. |
| let vmo = zx::Vmo::create_with_opts(zx::VmoOptions::RESIZABLE, 0)?; |
| write_json_to_vmo(&vmo, &json!(null))?; |
| if let (Some(vmo), None) = proxy.execute("test_facade", "get", vmo).await? { |
| assert_eq!(false, read_json_from_vmo(&vmo)?.as_bool().unwrap()); |
| } else { |
| panic!("Failed to get initial state."); |
| } |
| |
| // Set the new state. |
| let vmo = zx::Vmo::create_with_opts(zx::VmoOptions::RESIZABLE, 0)?; |
| write_json_to_vmo(&vmo, &json!(true))?; |
| if let (None, None) = proxy.execute("test_facade", "set", vmo).await? { |
| } else { |
| panic!("Failed to set new state."); |
| } |
| |
| // Get the new state. |
| let vmo = zx::Vmo::create_with_opts(zx::VmoOptions::RESIZABLE, 0)?; |
| write_json_to_vmo(&vmo, &json!(null))?; |
| if let (Some(vmo), None) = proxy.execute("test_facade", "get", vmo).await? { |
| assert_eq!(true, read_json_from_vmo(&vmo)?.as_bool().unwrap()); |
| } else { |
| panic!("Failed to get new state."); |
| } |
| |
| // Clean up the state. |
| proxy.cleanup().await?; |
| |
| // Get the final state. |
| let vmo = zx::Vmo::create_with_opts(zx::VmoOptions::RESIZABLE, 0)?; |
| write_json_to_vmo(&vmo, &json!(null))?; |
| if let (Some(vmo), None) = proxy.execute("test_facade", "get", vmo).await? { |
| assert_eq!(false, read_json_from_vmo(&vmo)?.as_bool().unwrap()); |
| } else { |
| panic!("Failed to get initial state."); |
| } |
| |
| // Close the proxy. |
| drop(proxy); |
| Ok::<(), Error>(()) |
| }; |
| let mut combined_fut = pin!(async { |
| let (_, res) = futures::join!(server_fut, client_fut); |
| res.unwrap(); |
| }); |
| |
| assert_eq!(Poll::Ready(()), executor.run_until_stalled(&mut combined_fut)); |
| |
| Ok(()) |
| } |
| } |