blob: 1152e1f46f3fc460c90df8d1d631cb01f168efa8 [file] [log] [blame]
// Copyright 2018 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::account_handler_context::AccountHandlerContext;
use account_common::{AccountManagerError, LocalAccountId, ResultExt as AccountResultExt};
use anyhow::Context as _;
use async_trait::async_trait;
use core::fmt::Debug;
use fidl_fuchsia_identity_account::{Error as ApiError, Lifetime};
use fidl_fuchsia_identity_internal::{AccountHandlerControlMarker, AccountHandlerControlProxy};
use fidl_fuchsia_stash::StoreMarker;
use fidl_fuchsia_sys::EnvironmentControllerProxy;
use fuchsia_async as fasync;
use fuchsia_component::client::App;
use fuchsia_component::fuchsia_single_component_package_url;
use fuchsia_component::server::ServiceFs;
use futures::prelude::*;
use log::{error, info, warn};
use std::fmt;
use std::sync::Arc;
/// The url used to launch new AccountHandler component instances.
const ACCOUNT_HANDLER_URL: &str = fuchsia_single_component_package_url!("account_handler");
/// The url used to launch new ephemeral AccountHandler component instances.
const ACCOUNT_HANDLER_EPHEMERAL_URL: &str =
"fuchsia-pkg://fuchsia.com/account_handler#meta/account_handler_ephemeral.cmx";
/// This trait is an abstraction over a connection to an account handler
/// component. It contains both static methods for creating connections
/// either by loading or creating a new account as well as instance methods for
/// calling the active account handler.
#[async_trait]
pub trait AccountHandlerConnection: Send + Sized + Debug {
/// Create a new uninitialized AccountHandlerConnection.
fn new(
account_id: LocalAccountId,
lifetime: Lifetime,
context: Arc<AccountHandlerContext>,
) -> Result<Self, AccountManagerError>;
/// Returns the lifetime of the account.
fn get_account_id(&self) -> &LocalAccountId;
/// Returns the lifetime of the account.
fn get_lifetime(&self) -> &Lifetime;
/// An AccountHandlerControlProxy for this connection.
fn proxy(&self) -> &AccountHandlerControlProxy;
/// Requests that the AccountHandler component instance terminate gracefully.
///
/// Any subsequent operations that attempt to use `proxy()` will fail after this call. The
/// resources associated with the connection when only be freed once the
/// `AccountHandlerConnectionImpl` is dropped.
async fn terminate(&self);
}
/// Implementation of an AccountHandlerConnection which creates real component
/// instances.
pub struct AccountHandlerConnectionImpl {
/// An `App` object for the launched AccountHandler.
///
/// Note: This must remain in scope for the component to remain running, but never needs to be
/// read.
_app: App,
/// An `EnvController` object for the launched AccountHandler.
///
/// Note: This must remain in scope for the component to remain running, but never needs to be
/// read.
_env_controller: EnvironmentControllerProxy,
/// The local account id of the account.
account_id: LocalAccountId,
/// The lifetime of the account.
lifetime: Lifetime,
/// A `Proxy` connected to the AccountHandlerControl interface on the launched AccountHandler.
proxy: AccountHandlerControlProxy,
}
impl fmt::Debug for AccountHandlerConnectionImpl {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "AccountHandlerConnectionImpl {{ lifetime: {:?} }}", self.lifetime)
}
}
#[async_trait]
impl AccountHandlerConnection for AccountHandlerConnectionImpl {
fn new(
account_id: LocalAccountId,
lifetime: Lifetime,
context: Arc<AccountHandlerContext>,
) -> Result<Self, AccountManagerError> {
let account_handler_url = if lifetime == Lifetime::Ephemeral {
info!("Launching new ephemeral AccountHandler instance");
ACCOUNT_HANDLER_EPHEMERAL_URL
} else {
info!("Launching new persistent AccountHandler instance");
ACCOUNT_HANDLER_URL
};
// Note: The combination of component URL and environment label determines the location of
// the data directory for the launched component. It is critical that the label is unique
// and stable per-account, which we achieve through using the local account id as
// the environment name. We also pass in the account id as a flag, because the environment
// name is not known to the launched component.
let account_id_string = account_id.to_canonical_string();
let mut fs_for_account_handler = ServiceFs::new();
if lifetime == Lifetime::Persistent {
fs_for_account_handler.add_proxy_service::<StoreMarker, _>();
}
fs_for_account_handler.add_fidl_service(move |stream| {
let context_clone = context.clone();
fasync::Task::spawn(async move {
context_clone
.handle_requests_from_stream(stream)
.await
.unwrap_or_else(|err| error!("Error handling AccountHandlerContext: {:?}", err))
})
.detach();
});
let (env_controller, app) = fs_for_account_handler
.launch_component_in_nested_environment(
account_handler_url.to_string(),
Some(vec![format!("--account_id={}", account_id_string)]),
account_id_string.as_ref(),
)
.context("Failed to start launcher")
.account_manager_error(ApiError::Resource)?;
fasync::Task::spawn(fs_for_account_handler.collect()).detach();
let proxy = app
.connect_to_service::<AccountHandlerControlMarker>()
.context("Failed to connect to AccountHandlerControl")
.account_manager_error(ApiError::Resource)?;
Ok(AccountHandlerConnectionImpl {
_app: app,
_env_controller: env_controller,
account_id,
lifetime,
proxy,
})
}
fn get_account_id(&self) -> &LocalAccountId {
&self.account_id
}
fn get_lifetime(&self) -> &Lifetime {
&self.lifetime
}
fn proxy(&self) -> &AccountHandlerControlProxy {
&self.proxy
}
async fn terminate(&self) {
let mut event_stream = self.proxy.take_event_stream();
if let Err(err) = self.proxy.terminate() {
warn!("Error gracefully terminating account handler {:?}", err);
} else {
while let Ok(Some(_)) = event_stream.try_next().await {}
info!("Gracefully terminated AccountHandler instance");
}
}
}