blob: 007b9124630f8b2bd60b07f1eeef8e4dbb54c6d0 [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::{format_err, Error},
fidl::endpoints::{ProtocolMarker, ServerEnd},
fidl_fuchsia_component as fcomponent, fidl_fuchsia_io as fio,
fuchsia_component::client::connect_to_protocol_at_path,
fuchsia_zircon as zx,
lazy_static::lazy_static,
std::collections::VecDeque,
thiserror::Error,
};
lazy_static! {
/// The path of the static event stream that, by convention, synchronously listens for
/// Resolved events.
pub static ref START_COMPONENT_TREE_STREAM: String = "StartComponentTree".into();
}
/// Returns the string name for the given `event_type`
pub fn event_name(event_type: &fcomponent::EventType) -> String {
match event_type {
fcomponent::EventType::CapabilityRequested => "capability_requested",
fcomponent::EventType::Discovered => "discovered",
fcomponent::EventType::Destroyed => "destroyed",
fcomponent::EventType::Resolved => "resolved",
fcomponent::EventType::Unresolved => "unresolved",
fcomponent::EventType::Started => "started",
fcomponent::EventType::Stopped => "stopped",
fcomponent::EventType::DebugStarted => "debug_started",
}
.to_string()
}
enum InternalStream {
New(fcomponent::EventStreamProxy),
}
pub struct EventStream {
stream: InternalStream,
buffer: VecDeque<fcomponent::Event>,
}
#[derive(Debug, Error, Clone)]
pub enum EventStreamError {
#[error("Stream terminated unexpectedly")]
StreamClosed,
}
impl EventStream {
pub fn new(stream: fcomponent::EventStreamProxy) -> Self {
Self { stream: InternalStream::New(stream), buffer: VecDeque::new() }
}
pub fn open_at_path_pipelined(path: impl Into<String>) -> Result<Self, Error> {
Ok(Self::new(connect_to_protocol_at_path::<fcomponent::EventStreamMarker>(&path.into())?))
}
pub async fn open_at_path(path: impl Into<String>) -> Result<Self, Error> {
let event_stream =
connect_to_protocol_at_path::<fcomponent::EventStreamMarker>(&path.into())?;
event_stream.wait_for_ready().await?;
Ok(Self::new(event_stream))
}
pub async fn open() -> Result<Self, Error> {
let event_stream = connect_to_protocol_at_path::<fcomponent::EventStreamMarker>(
"/svc/fuchsia.component.EventStream",
)?;
event_stream.wait_for_ready().await?;
Ok(Self::new(event_stream))
}
pub fn open_pipelined() -> Result<Self, Error> {
Ok(Self::new(connect_to_protocol_at_path::<fcomponent::EventStreamMarker>(
"/svc/fuchsia.component.EventStream",
)?))
}
pub async fn next(&mut self) -> Result<fcomponent::Event, EventStreamError> {
if let Some(event) = self.buffer.pop_front() {
return Ok(event);
}
match &mut self.stream {
InternalStream::New(stream) => {
match stream.get_next().await {
Ok(events) => {
let mut iter = events.into_iter();
if let Some(real_event) = iter.next() {
let ret = real_event;
while let Some(value) = iter.next() {
self.buffer.push_back(value);
}
return Ok(ret);
} else {
// This should never happen, we should always
// have at least one event.
Err(EventStreamError::StreamClosed)
}
}
Err(_) => Err(EventStreamError::StreamClosed),
}
}
}
}
}
/// Common features of any event - event type, target moniker, conversion function
pub trait Event: TryFrom<fcomponent::Event, Error = anyhow::Error> {
const TYPE: fcomponent::EventType;
const NAME: &'static str;
fn target_moniker(&self) -> &str;
fn component_url(&self) -> &str;
fn timestamp(&self) -> zx::Time;
fn is_ok(&self) -> bool;
fn is_err(&self) -> bool;
}
#[derive(Copy, Debug, PartialEq, Eq, Clone, Ord, PartialOrd)]
/// Simplifies the exit status represented by an Event. All stop status values
/// that indicate failure are crushed into `Crash`.
pub enum ExitStatus {
Clean,
Crash(i32),
}
impl From<i32> for ExitStatus {
fn from(exit_status: i32) -> Self {
match exit_status {
0 => ExitStatus::Clean,
_ => ExitStatus::Crash(exit_status),
}
}
}
#[derive(Debug)]
struct EventHeader {
event_type: fcomponent::EventType,
component_url: String,
moniker: String,
timestamp: zx::Time,
}
impl TryFrom<fcomponent::EventHeader> for EventHeader {
type Error = anyhow::Error;
fn try_from(header: fcomponent::EventHeader) -> Result<Self, Self::Error> {
let event_type = header.event_type.ok_or(format_err!("No event type"))?;
let component_url = header.component_url.ok_or(format_err!("No component url"))?;
let moniker = header.moniker.ok_or(format_err!("No moniker"))?;
let timestamp = zx::Time::from_nanos(
header.timestamp.ok_or(format_err!("Missing timestamp from the Event object"))?,
);
Ok(EventHeader { event_type, component_url, moniker, timestamp })
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct EventError {
pub description: String,
}
/// The macro defined below will automatically create event classes corresponding
/// to their events.fidl and hooks.rs counterparts. Every event class implements
/// the Event and Handler traits. These minimum requirements allow every event to
/// be handled by the events client library.
/// Creates an event class based on event type and an optional payload
/// * event_type -> FIDL name for event type
/// * payload -> If an event has a payload, describe the additional params:
/// * name -> FIDL name for the payload
/// * data -> If a payload contains data items, describe the additional params:
/// * name -> FIDL name for the data item
/// * ty -> Rust type for the data item
/// * client_protocols -> If a payload contains client-side protocols, describe
/// the additional params:
/// * name -> FIDL name for the protocol
/// * ty -> Rust type for the protocol proxy
/// * server_protocols -> If a payload contains server-side protocols, describe
/// the additional params:
/// * name -> FIDL name for the protocol
// TODO(https://fxbug.dev/42131403): This marco is getting complicated. Consider replacing it
// with a procedural macro.
macro_rules! create_event {
// Entry points
(
event_type: $event_type:ident,
event_name: $event_name:ident,
payload: {
data: {$(
{
name: $data_name:ident,
ty: $data_ty:ty,
}
)*},
client_protocols: {$(
{
name: $client_protocol_name:ident,
ty: $client_protocol_ty:ty,
}
)*},
server_protocols: {$(
{
name: $server_protocol_name:ident,
}
)*},
},
error_payload: {
$(
{
name: $error_data_name:ident,
ty: $error_data_ty:ty,
}
)*
}
) => {
paste::paste! {
#[derive(Debug)]
pub struct [<$event_type Payload>] {
$(pub $client_protocol_name: $client_protocol_ty,)*
$(pub $server_protocol_name: Option<zx::Channel>,)*
$(pub $data_name: $data_ty,)*
}
#[derive(Debug)]
pub struct [<$event_type Error>] {
$(pub $error_data_name: $error_data_ty,)*
pub description: String,
}
#[derive(Debug)]
pub struct $event_type {
header: EventHeader,
result: Result<[<$event_type Payload>], [<$event_type Error>]>,
}
impl $event_type {
pub fn result<'a>(&'a self) -> Result<&'a [<$event_type Payload>], &'a [<$event_type Error>]> {
self.result.as_ref()
}
$(
pub fn [<take_ $server_protocol_name>]<T: ProtocolMarker>(&mut self)
-> Option<T::RequestStream> {
self.result.as_mut()
.ok()
.and_then(|payload| payload.$server_protocol_name.take())
.and_then(|channel| {
let server_end = ServerEnd::<T>::new(channel);
server_end.into_stream().ok()
})
}
)*
}
impl Event for $event_type {
const TYPE: fcomponent::EventType = fcomponent::EventType::$event_type;
const NAME: &'static str = stringify!($event_name);
fn target_moniker(&self) -> &str {
&self.header.moniker
}
fn component_url(&self) -> &str {
&self.header.component_url
}
fn timestamp(&self) -> zx::Time {
self.header.timestamp
}
fn is_ok(&self) -> bool {
self.result.is_ok()
}
fn is_err(&self) -> bool {
self.result.is_err()
}
}
impl TryFrom<fcomponent::Event> for $event_type {
type Error = anyhow::Error;
fn try_from(event: fcomponent::Event) -> Result<Self, Self::Error> {
// Extract the payload from the Event object.
let result = match event.payload {
Some(payload) => {
// This payload will be unused for event types that have no additional
// fields.
#[allow(unused)]
let payload = match payload {
fcomponent::EventPayload::$event_type(payload) => Ok(payload),
_ => Err(format_err!("Incorrect payload type, {:?}", payload)),
}?;
// Extract the additional data from the Payload object.
$(
let $data_name: $data_ty = payload.$data_name.ok_or(
format_err!("Missing $data_name from $event_type object")
)?.into();
)*
// Extract the additional protocols from the Payload object.
$(
let $client_protocol_name: $client_protocol_ty = payload.$client_protocol_name.ok_or(
format_err!("Missing $client_protocol_name from $event_type object")
)?.into_proxy()?;
)*
$(
let $server_protocol_name: Option<zx::Channel> =
Some(payload.$server_protocol_name.ok_or(
format_err!("Missing $server_protocol_name from $event_type object")
)?);
)*
#[allow(dead_code)]
let payload = paste::paste! {
[<$event_type Payload>] {
$($data_name,)*
$($client_protocol_name,)*
$($server_protocol_name,)*
}
};
Ok(Ok(payload))
},
None => Err(format_err!("Missing event_result from Event object")),
}?;
let event = {
let header = event.header
.ok_or(format_err!("Missing Event header"))
.and_then(|header| EventHeader::try_from(header))?;
if header.event_type != Self::TYPE {
return Err(format_err!("Incorrect event type"));
}
$event_type { header, result }
};
Ok(event)
}
}
}
};
($event_type:ident, $event_name:ident) => {
create_event!(event_type: $event_type, event_name: $event_name,
payload: {
data: {},
client_protocols: {},
server_protocols: {},
},
error_payload: {});
};
}
// To create a class for an event, use the above macro here.
create_event!(Discovered, discovered);
create_event!(Destroyed, destroyed);
create_event!(Resolved, resolved);
create_event!(Unresolved, unresolved);
create_event!(Started, started);
create_event!(
event_type: Stopped,
event_name: stopped,
payload: {
data: {
{
name: status,
ty: ExitStatus,
}
},
client_protocols: {},
server_protocols: {},
},
error_payload: {}
);
create_event!(
event_type: CapabilityRequested,
event_name: capability_requested,
payload: {
data: {
{
name: name,
ty: String,
}
},
client_protocols: {},
server_protocols: {
{
name: capability,
}
},
},
error_payload: {
{
name: name,
ty: String,
}
}
);
create_event!(
event_type: DebugStarted,
event_name: debug_started,
payload: {
data: {
{
name: break_on_start,
ty: zx::EventPair,
}
},
client_protocols: {
{
name: runtime_dir,
ty: fio::DirectoryProxy,
}
},
server_protocols: {},
},
error_payload: {}
);