blob: 467f8b8de1fb0b72eedf543f8512f1de3f70a8d2 [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 {
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!");
}
}