blob: be3bebdf1b80d9ba9b90c110d2c5ef646b0c5b12 [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},
fidl_fuchsia_session::{
AnnotationError, 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},
fuchsia_syslog::fx_log_info,
futures::{StreamExt, TryStreamExt},
legacy_element_management::{
Element, ElementManager, ElementManagerError, SimpleElementManager,
},
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";
/// The maximum number of concurrent requests.
const NUM_CONCURRENT_REQUESTS: usize = 5;
/// 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> {
fuchsia_syslog::init_with_tags(&["element_session"]).expect("Failed to initialize logger");
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()?;
fs.for_each_concurrent(
NUM_CONCURRENT_REQUESTS,
move |service_request: ExposedServices| async move {
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)
.await
.expect("Failed to run element manager service.");
}
}
},
)
.await;
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")?
{
fx_log_info!("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,
) -> Result<(), Error> {
let mut uncontrolled_elements = vec![];
let realm =
connect_to_service::<fsys::RealmMarker>().context("Could not connect to Realm service.")?;
let element_manager = SimpleElementManager::new(realm);
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) => {
fasync::Task::spawn(handle_element_controller_request_stream(
stream, element,
))
.detach();
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::InvalidServiceList { .. }) => {
Err(ProposeElementError::Rejected)
}
Err(ElementManagerError::AdditionalServicesNotSupported { .. }) => {
Err(ProposeElementError::Rejected)
}
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.
async fn handle_element_controller_request_stream(
mut stream: ElementControllerRequestStream,
mut element: Element,
) {
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 = &mut element
.get_annotations()
.map_err(|_: anyhow::Error| AnnotationError::NotFound);
let _ = responder.send(&mut annotations);
}
}
}
}
#[cfg(test)]
mod tests {
use {
super::*, fidl::endpoints::create_proxy_and_stream, fuchsia_async as fasync,
fuchsia_zircon as zx,
};
/// Tests that an Element is created with an empty vector of Annotations.
#[fasync::run_singlethreaded(test)]
async fn get_annotations_test() {
let url = "component_url";
let collection = "collection";
let name = "name";
let (p1, _) = zx::Channel::create().unwrap();
let element = Element::from_directory_channel(
p1,
&name.to_string(),
&url.to_string(),
&collection.to_string(),
);
let (element_controller_proxy, element_controller_server) =
create_proxy_and_stream::<fidl_fuchsia_session::ElementControllerMarker>()
.expect("Failed to create ElementController proxy and server.");
fasync::Task::spawn(handle_element_controller_request_stream(
element_controller_server,
element,
))
.detach();
let annotations = element_controller_proxy.get_annotations().await;
assert!(annotations.is_ok());
let unwrapped_annotations = annotations.unwrap();
assert!(unwrapped_annotations.is_ok());
assert_eq!(unwrapped_annotations.unwrap().custom_annotations.unwrap().len(), 0);
}
/// Tests that get_annotations properly returns the Annotations passed in via set_annotations().
#[fasync::run_singlethreaded(test)]
async fn set_annotations_test() {
let url = "component_url";
let collection = "collection";
let name = "name";
let (p1, _) = zx::Channel::create().unwrap();
let element = Element::from_directory_channel(
p1,
&name.to_string(),
&url.to_string(),
&collection.to_string(),
);
let (element_controller_proxy, element_controller_server) =
create_proxy_and_stream::<fidl_fuchsia_session::ElementControllerMarker>()
.expect("Failed to create ElementController proxy and server.");
fasync::Task::local(handle_element_controller_request_stream(
element_controller_server,
element,
))
.detach();
let annotation = fidl_fuchsia_session::Annotation {
key: "key".to_string(),
value: Some(Box::new(fidl_fuchsia_session::Value::Text("value".to_string()))),
};
let annotations = fidl_fuchsia_session::Annotations {
custom_annotations: Some(vec![annotation]),
..fidl_fuchsia_session::Annotations::EMPTY
};
// Cannot implement a Copy of an Annotation so just make another variable with idenitical
// fields to test the resulting calls.
let annotation_2 = fidl_fuchsia_session::Annotation {
key: "key".to_string(),
value: Some(Box::new(fidl_fuchsia_session::Value::Text("value".to_string()))),
};
let annotations_2 = fidl_fuchsia_session::Annotations {
custom_annotations: Some(vec![annotation_2]),
..fidl_fuchsia_session::Annotations::EMPTY
};
let _ = element_controller_proxy.set_annotations(annotations).await.unwrap();
let returned_annotations = element_controller_proxy.get_annotations().await;
assert!(returned_annotations.is_ok());
let unwrapped_annotations = returned_annotations.unwrap();
assert!(unwrapped_annotations.is_ok());
assert_eq!(unwrapped_annotations.unwrap(), annotations_2);
}
/// Tests that calling set_annotations() twice with the same key updates the value as expected.
#[fasync::run_singlethreaded(test)]
async fn update_annotations_test() {
let url = "component_url";
let collection = "collection";
let name = "name";
let (p1, _) = zx::Channel::create().unwrap();
let element = Element::from_directory_channel(
p1,
&name.to_string(),
&url.to_string(),
&collection.to_string(),
);
let (element_controller_proxy, element_controller_server) =
create_proxy_and_stream::<fidl_fuchsia_session::ElementControllerMarker>()
.expect("Failed to create ElementController proxy and server.");
fasync::Task::local(handle_element_controller_request_stream(
element_controller_server,
element,
))
.detach();
let annotation = fidl_fuchsia_session::Annotation {
key: "key".to_string(),
value: Some(Box::new(fidl_fuchsia_session::Value::Text("value".to_string()))),
};
let annotations = fidl_fuchsia_session::Annotations {
custom_annotations: Some(vec![annotation]),
..fidl_fuchsia_session::Annotations::EMPTY
};
let annotation_2 = fidl_fuchsia_session::Annotation {
key: "key".to_string(),
value: Some(Box::new(fidl_fuchsia_session::Value::Text("value".to_string()))),
};
let annotations_2 = fidl_fuchsia_session::Annotations {
custom_annotations: Some(vec![annotation_2]),
..fidl_fuchsia_session::Annotations::EMPTY
};
let _ = element_controller_proxy.set_annotations(annotations).await.unwrap();
let returned_annotations = element_controller_proxy.get_annotations().await;
assert!(returned_annotations.is_ok());
let unwrapped_annotations = returned_annotations.unwrap();
assert!(unwrapped_annotations.is_ok());
assert_eq!(unwrapped_annotations.unwrap(), annotations_2);
let annotation_3 = fidl_fuchsia_session::Annotation {
key: "key".to_string(),
value: Some(Box::new(fidl_fuchsia_session::Value::Text("new_value".to_string()))),
};
let annotations_3 = fidl_fuchsia_session::Annotations {
custom_annotations: Some(vec![annotation_3]),
..fidl_fuchsia_session::Annotations::EMPTY
};
let annotation_4 = fidl_fuchsia_session::Annotation {
key: "key".to_string(),
value: Some(Box::new(fidl_fuchsia_session::Value::Text("new_value".to_string()))),
};
let annotations_4 = fidl_fuchsia_session::Annotations {
custom_annotations: Some(vec![annotation_4]),
..fidl_fuchsia_session::Annotations::EMPTY
};
let _ = element_controller_proxy.set_annotations(annotations_3).await.unwrap();
let returned_annotations = element_controller_proxy.get_annotations().await;
assert!(returned_annotations.is_ok());
assert!(returned_annotations.is_ok());
let unwrapped_annotations = returned_annotations.unwrap();
assert!(unwrapped_annotations.is_ok());
assert_eq!(unwrapped_annotations.unwrap(), annotations_4);
}
/// Tests that updating a Annotation to have a Value of None removes it from the custom_annotations
/// vector.
#[fasync::run_singlethreaded(test)]
async fn remove_annotations_test() {
let url = "component_url";
let collection = "collection";
let name = "name";
let (p1, _) = zx::Channel::create().unwrap();
let element = Element::from_directory_channel(
p1,
&name.to_string(),
&url.to_string(),
&collection.to_string(),
);
let (element_controller_proxy, element_controller_server) =
create_proxy_and_stream::<fidl_fuchsia_session::ElementControllerMarker>()
.expect("Failed to create ElementController proxy and server.");
fasync::Task::local(handle_element_controller_request_stream(
element_controller_server,
element,
))
.detach();
let annotation = fidl_fuchsia_session::Annotation {
key: "key".to_string(),
value: Some(Box::new(fidl_fuchsia_session::Value::Text("value".to_string()))),
};
let annotations = fidl_fuchsia_session::Annotations {
custom_annotations: Some(vec![annotation]),
..fidl_fuchsia_session::Annotations::EMPTY
};
let _ = element_controller_proxy.set_annotations(annotations).await.unwrap();
let annotation_2 = fidl_fuchsia_session::Annotation {
key: "key".to_string(),
value: Some(Box::new(fidl_fuchsia_session::Value::Text("value".to_string()))),
};
let annotations_2 = fidl_fuchsia_session::Annotations {
custom_annotations: Some(vec![annotation_2]),
..fidl_fuchsia_session::Annotations::EMPTY
};
// Check that there are Annotations.
let returned_annotations = element_controller_proxy.get_annotations().await;
assert!(returned_annotations.is_ok());
let unwrapped_annotations = returned_annotations.unwrap();
assert!(unwrapped_annotations.is_ok());
assert_eq!(unwrapped_annotations.unwrap(), annotations_2);
// Remove the Annotations.
let annotation_3 = fidl_fuchsia_session::Annotation { key: "key".to_string(), value: None };
let annotations_3 = fidl_fuchsia_session::Annotations {
custom_annotations: Some(vec![annotation_3]),
..fidl_fuchsia_session::Annotations::EMPTY
};
let _ = element_controller_proxy.set_annotations(annotations_3).await.unwrap();
let returned_annotations = element_controller_proxy.get_annotations().await;
assert!(returned_annotations.is_ok());
let unwrapped_annotations = returned_annotations.unwrap();
assert!(unwrapped_annotations.is_ok());
// Verify that there are no Annotations stored.
assert_eq!(unwrapped_annotations.unwrap().custom_annotations.unwrap().len(), 0);
}
}