| // 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"); |
| } |
| } |
| } |