blob: 176ba5a0a495f64df5fb20abf9392151e2e63225 [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 failure::{format_err, ResultExt};
use fidl::endpoints::{ClientEnd, RequestStream};
use fidl_fuchsia_auth_account::Status;
use fidl_fuchsia_auth_account_internal::{
AccountHandlerContextMarker, AccountHandlerContextRequestStream, AccountHandlerControlMarker,
AccountHandlerControlProxy,
};
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 fuchsia_zircon as zx;
use futures::prelude::*;
use log::{error, info, warn};
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 information necessary to maintain a connection to an AccountHandler component instance.
pub struct AccountHandlerConnection {
/// 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,
/// A `Proxy` connected to the AccountHandlerControl interface on the launched AccountHandler.
proxy: AccountHandlerControlProxy,
}
impl AccountHandlerConnection {
/// Launches a new AccountHandler component instance and establishes a connection to its
/// control channel.
///
/// Note: This method is not public. Callers should use one of the factory methods that also
/// sends an initialization call to the AccountHandler after connection, such as `load_account`
/// or `create_account`
fn new(account_id: LocalAccountId) -> Result<Self, AccountManagerError> {
info!("Launching new AccountHandler instance");
// 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.
let env_label = account_id.to_canonical_string();
let mut fs_for_account_handler = ServiceFs::new();
let (env_controller, app) = fs_for_account_handler
.launch_component_in_nested_environment(
ACCOUNT_HANDLER_URL.to_string(),
None,
env_label.as_ref(),
)
.context("Failed to start launcher")
.account_manager_status(Status::IoError)?;
fasync::spawn(fs_for_account_handler.collect());
let proxy = app
.connect_to_service::<AccountHandlerControlMarker>()
.context("Failed to connect to AccountHandlerControl")
.account_manager_status(Status::IoError)?;
Ok(AccountHandlerConnection { _app: app, _env_controller: env_controller, proxy })
}
/// Creates a new `AccountHandlerContext` channel, spawns a task to handle requests received on
/// this channel using the supplied `AccountHandlerContext`, and returns the `ClientEnd`.
fn spawn_context_channel(
context: Arc<AccountHandlerContext>,
) -> Result<ClientEnd<AccountHandlerContextMarker>, AccountManagerError> {
let (server_chan, client_chan) = zx::Channel::create()
.context("Failed to create channel")
.account_manager_status(Status::IoError)?;
let server_async_chan = fasync::Channel::from_channel(server_chan)
.context("Failed to create async channel")
.account_manager_status(Status::IoError)?;
let request_stream = AccountHandlerContextRequestStream::from_channel(server_async_chan);
let context_clone = Arc::clone(&context);
fasync::spawn(async move {
await!(context_clone.handle_requests_from_stream(request_stream))
.unwrap_or_else(|err| error!("Error handling AccountHandlerContext: {:?}", err))
});
Ok(ClientEnd::new(client_chan))
}
/// Launches a new AccountHandler component instance, establishes a connection to its control
/// channel, and requests that it loads an existing account.
pub async fn load_account(
account_id: &LocalAccountId,
context: Arc<AccountHandlerContext>,
) -> Result<Self, AccountManagerError> {
let connection = Self::new(account_id.clone())?;
let context_client_end = Self::spawn_context_channel(context)?;
match await!(connection
.proxy
.load_account(context_client_end, account_id.clone().as_mut().into()))
.account_manager_status(Status::IoError)?
{
Status::Ok => Ok(connection),
stat => Err(AccountManagerError::new(stat)
.with_cause(format_err!("Error loading existing account"))),
}
}
/// Launches a new AccountHandler component instance, establishes a connection to its control
/// channel, and requests that it create a new account.
pub async fn create_account(
context: Arc<AccountHandlerContext>,
) -> Result<(Self, LocalAccountId), AccountManagerError> {
let account_id = LocalAccountId::new(rand::random::<u64>());
let connection = Self::new(account_id.clone())?;
let context_client_end = Self::spawn_context_channel(context)?;
match await!(connection
.proxy()
.create_account(context_client_end, account_id.clone().as_mut().into()))
.account_manager_status(Status::IoError)?
{
Status::Ok => {
// TODO(jsankey): Longer term, local ID may need to be related to the global ID
// rather than just a random number.
Ok((connection, account_id))
}
status => Err(AccountManagerError::new(status)
.with_cause(format_err!("Account handler returned error"))),
}
}
/// Returns the AccountHandlerControlProxy for this connection
pub fn proxy(&self) -> &AccountHandlerControlProxy {
&self.proxy
}
/// 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
/// `AccountHandlerConnection` is dropped.
pub 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(_)) = await!(event_stream.try_next()) {}
info!("Gracefully terminated AccountHandler instance");
}
}
}