| // 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 { |
| anyhow::{Context as _, Error}, |
| element_management::{Element, ElementManager, ElementManagerError, SimpleElementManager}, |
| fidl::encoding::Decodable, |
| fidl_fuchsia_session::{ |
| AnnotationError, Annotations, ElementControllerRequest, ElementControllerRequestStream, |
| ElementManagerRequest, ElementManagerRequestStream, ProposeElementError, |
| }, |
| fidl_fuchsia_session_examples::{ElementPingRequest, ElementPingRequestStream}, |
| fidl_fuchsia_sys2 as fsys, fuchsia_async as fasync, |
| fuchsia_component::{client::connect_to_service, server::ServiceFs}, |
| futures::{StreamExt, TryStreamExt}, |
| rand::{distributions::Alphanumeric, thread_rng, Rng}, |
| }; |
| |
| /// This enum allows the session to match on incoming messages. |
| enum ExposedServices { |
| ElementManager(ElementManagerRequestStream), |
| ElementPing(ElementPingRequestStream), |
| } |
| |
| /// The child collection to add elements to. This must match a collection name declared in |
| /// this session's CML file. |
| const ELEMENT_COLLECTION_NAME: &str = "elements"; |
| |
| /// This session exposes one service which is offered to all elements started in the session and |
| /// prints a string when an element sends a request to said service. |
| /// |
| /// It also exposes the [`fidl_fuchsia_session.ElementManager`] service which an element proposer |
| /// can connect to in order to add an element to the session. |
| #[fasync::run_singlethreaded] |
| async fn main() -> Result<(), Error> { |
| let mut fs = ServiceFs::new_local(); |
| fs.dir("svc").add_fidl_service(ExposedServices::ElementPing); |
| fs.dir("svc").add_fidl_service(ExposedServices::ElementManager); |
| |
| fs.take_and_serve_directory_handle()?; |
| |
| let realm = |
| connect_to_service::<fsys::RealmMarker>().context("Could not connect to Realm service.")?; |
| let element_manager = SimpleElementManager::new(realm); |
| |
| while let Some(service_request) = fs.next().await { |
| match service_request { |
| ExposedServices::ElementPing(request_stream) => { |
| handle_element_ping_requests(request_stream) |
| .await |
| .expect("Failed to run element ping service."); |
| } |
| ExposedServices::ElementManager(request_stream) => { |
| handle_element_manager_requests(request_stream, &element_manager) |
| .await |
| .expect("Failed to run element manager service."); |
| } |
| } |
| } |
| Ok(()) |
| } |
| |
| /// Handles the ping requests and prints to the terminal on success. |
| /// |
| /// # Parameters |
| /// `stream`: The input channel which receives [`Ping`] requests. |
| /// |
| /// # Returns |
| /// `Ok` if the service ran successfully, or an `Error` if execution halted unexpectedly. |
| async fn handle_element_ping_requests(mut stream: ElementPingRequestStream) -> Result<(), Error> { |
| while let Some(ElementPingRequest::Ping { control_handle: _ }) = |
| stream.try_next().await.context("Error handling ping request stream")? |
| { |
| println!("Element did ping session."); |
| } |
| Ok(()) |
| } |
| |
| /// Handles the [`ElementManager`] requests and launches the element session on success. |
| /// |
| /// # Parameters |
| /// `stream`: The input channel which receives [`ElementManager`] requests. |
| /// |
| /// # Returns |
| /// `Ok` if the element manager ran successfully, or an `ElementManagerError` if execution halted unexpectedly. |
| async fn handle_element_manager_requests( |
| mut stream: ElementManagerRequestStream, |
| element_manager: &impl ElementManager, |
| ) -> Result<(), Error> { |
| let mut uncontrolled_elements = vec![]; |
| while let Some(request) = |
| stream.try_next().await.context("Error handling element manager request stream")? |
| { |
| match request { |
| ElementManagerRequest::ProposeElement { spec, element_controller, responder } => { |
| let mut child_name: String = |
| thread_rng().sample_iter(&Alphanumeric).take(16).collect(); |
| child_name.make_ascii_lowercase(); |
| |
| let mut result = match element_manager |
| .launch_element(spec, &child_name, ELEMENT_COLLECTION_NAME) |
| .await |
| { |
| Ok(element) => { |
| match element_controller { |
| Some(element_controller) => match element_controller.into_stream() { |
| Ok(stream) => { |
| handle_element_controller_request_stream(stream, element); |
| Ok(()) |
| } |
| Err(_) => Err(ProposeElementError::Rejected), |
| }, |
| // If the element proposer did not provide a controller, add the |
| // element to a vector to keep it alive: |
| None => { |
| uncontrolled_elements.push(element); |
| Ok(()) |
| } |
| } |
| } |
| // Most of the errors which could be encountered when adding an element are |
| // not the result of an error by the FIDL client. This lists all the cases |
| // explicitly, but it's up to each session to decide how to map the errors. |
| Err(ElementManagerError::UrlMissing { .. }) => { |
| Err(ProposeElementError::NotFound) |
| } |
| Err(ElementManagerError::NotCreated { .. }) => { |
| Err(ProposeElementError::Rejected) |
| } |
| Err(ElementManagerError::NotBound { .. }) => Err(ProposeElementError::Rejected), |
| Err(ElementManagerError::NotLaunched { .. }) => { |
| Err(ProposeElementError::Rejected) |
| } |
| }; |
| |
| let _ = responder.send(&mut result); |
| } |
| } |
| } |
| Ok(()) |
| } |
| |
| /// Handles the ElementController requests. |
| /// |
| /// # Parameters |
| /// - `stream`: the input channel which receives [`ElementController`] requests. |
| /// - `element`: the [`Element`] that is being controlled. |
| /// |
| /// # Returns |
| /// () when there are no more valid requests. |
| fn handle_element_controller_request_stream( |
| mut stream: ElementControllerRequestStream, |
| mut element: Element, |
| ) { |
| fasync::spawn(async move { |
| while let Ok(Some(request)) = stream.try_next().await { |
| match request { |
| ElementControllerRequest::SetAnnotations { annotations, responder } => { |
| let _ = responder.send( |
| &mut element |
| .set_annotations(annotations) |
| .map_err(|_: anyhow::Error| AnnotationError::Rejected), |
| ); |
| } |
| ElementControllerRequest::GetAnnotations { responder } => { |
| let mut annotations = element.get_annotations(); |
| if annotations.is_err() { |
| // GetAnnotations does not return errors, so just return empty annotations. |
| annotations = |
| Ok(Annotations { custom_annotations: None, ..Annotations::new_empty() }) |
| } |
| let _ = responder.send(annotations.unwrap()); |
| } |
| } |
| } |
| }); |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| #[test] |
| fn dummy_test() { |
| println!("Don't panic!(), you've got this!"); |
| } |
| } |