#![doc(html_logo_url = "https://raw.githubusercontent.com/maidsafe/QA/master/Images/
maidsafe_logo.png",
html_favicon_url = "http://maidsafe.net/img/favicon.ico",
html_root_url = "http://maidsafe.github.io/safe_app")]
#![forbid(exceeding_bitshifts, mutable_transmutes, no_mangle_const_items, unknown_crate_types,
warnings)]
#![deny(bad_style, deprecated, improper_ctypes, missing_docs, non_shorthand_field_patterns,
overflowing_literals, plugin_as_library, private_no_mangle_fns, private_no_mangle_statics,
stable_features, unconditional_recursion, unknown_lints, unsafe_code, unused,
unused_allocation, unused_attributes, unused_comparisons, unused_features, unused_parens,
while_true)]
#![warn(trivial_casts, trivial_numeric_casts, unused_extern_crates, unused_import_braces,
unused_qualifications, unused_results)]
#![allow(box_pointers, fat_ptr_transmutes, missing_copy_implementations,
missing_debug_implementations, variant_size_differences)]
#![cfg_attr(feature = "cargo-clippy",
deny(clippy, unicode_not_nfc, wrong_pub_self_convention, option_unwrap_used))]
#![cfg_attr(feature = "cargo-clippy", allow(use_debug, too_many_arguments))]
extern crate config_file_handler;
#[macro_use]
extern crate ffi_utils;
extern crate futures;
#[macro_use]
extern crate log;
extern crate lru_cache;
extern crate maidsafe_utilities;
#[cfg(test)]
extern crate rand;
extern crate routing;
extern crate rust_sodium;
#[cfg(any(test, feature = "testing"))]
extern crate safe_authenticator;
#[macro_use]
extern crate safe_core;
extern crate self_encryption;
#[macro_use]
extern crate serde_derive;
extern crate tiny_keccak;
extern crate tokio_core;
#[macro_use]
extern crate unwrap;
pub mod ffi;
pub use ffi::*;
pub use ffi::access_container::*;
pub use ffi::cipher_opt::*;
pub use ffi::crypto::*;
pub use ffi::immutable_data::*;
pub use ffi::ipc::*;
pub use ffi::logging::*;
pub use ffi::mdata_info::*;
pub use ffi::mutable_data::*;
pub use ffi::mutable_data::entries::*;
pub use ffi::mutable_data::entry_actions::*;
pub use ffi::mutable_data::metadata::*;
pub use ffi::mutable_data::permissions::*;
pub use ffi::nfs::*;
mod errors;
pub mod object_cache;
#[cfg(test)]
mod tests;
#[cfg(any(test, feature = "testing"))]
pub mod test_utils;
pub use self::errors::*;
use self::object_cache::ObjectCache;
use futures::{Future, future};
use futures::stream::Stream;
use futures::sync::mpsc as futures_mpsc;
use maidsafe_utilities::serialisation::deserialise;
use maidsafe_utilities::thread::{self, Joiner};
use safe_core::{Client, ClientKeys, CoreMsg, CoreMsgTx, FutureExt, MDataInfo, NetworkEvent,
NetworkTx, event_loop, utils};
#[cfg(feature = "use-mock-routing")]
use safe_core::MockRouting as Routing;
use safe_core::crypto::shared_secretbox;
use safe_core::ipc::{AccessContInfo, AppKeys, AuthGranted, BootstrapConfig, Permission};
use safe_core::ipc::resp::access_container_enc_key;
use std::cell::RefCell;
use std::collections::{BTreeSet, HashMap};
use std::rc::Rc;
use std::sync::Mutex;
use std::sync::mpsc as std_mpsc;
#[cfg(feature = "testing")]
pub use test_utils::{test_create_app, test_create_app_with_access};
use tokio_core::reactor::{Core, Handle};
macro_rules! try_tx {
($result:expr, $tx:ident) => {
match $result {
Ok(res) => res,
Err(e) => return unwrap!($tx.send(Err(AppError::from(e)))),
}
}
}
type AppFuture<T> = Future<Item = T, Error = AppError>;
type AccessContainerEntry = HashMap<String, (MDataInfo, BTreeSet<Permission>)>;
pub struct App {
core_tx: Mutex<CoreMsgTx<AppContext>>,
_core_joiner: Joiner,
}
impl App {
pub fn unregistered<N>(
network_observer: N,
config: Option<BootstrapConfig>,
) -> Result<Self, AppError>
where
N: FnMut(Result<NetworkEvent, AppError>) + Send + 'static,
{
Self::new(network_observer, |el_h, core_tx, net_tx| {
let client = Client::unregistered(el_h, core_tx, net_tx, config)?;
let context = AppContext::unregistered();
Ok((client, context))
})
}
pub fn registered<N>(
app_id: String,
auth_granted: AuthGranted,
network_observer: N,
) -> Result<Self, AppError>
where
N: FnMut(Result<NetworkEvent, AppError>) + Send + 'static,
{
Self::registered_impl(app_id, auth_granted, network_observer)
}
fn registered_impl<N>(
app_id: String,
auth_granted: AuthGranted,
network_observer: N,
) -> Result<Self, AppError>
where
N: FnMut(Result<NetworkEvent, AppError>) + Send + 'static,
{
let AuthGranted {
app_keys: AppKeys {
owner_key,
enc_key,
enc_pk,
enc_sk,
sign_pk,
sign_sk,
},
access_container,
bootstrap_config,
} = auth_granted;
let client_keys = ClientKeys {
sign_pk,
sign_sk,
enc_pk,
enc_sk,
enc_key: enc_key.clone(),
};
Self::new(network_observer, move |el_h, core_tx, net_tx| {
let client = Client::from_keys(
client_keys,
owner_key,
el_h,
core_tx,
net_tx,
bootstrap_config,
)?;
let context = AppContext::registered(app_id, enc_key, access_container);
Ok((client, context))
})
}
#[cfg(feature = "use-mock-routing")]
pub fn registered_with_hook<N, F>(
app_id: String,
auth_granted: AuthGranted,
network_observer: N,
routing_wrapper_fn: F,
) -> Result<Self, AppError>
where
N: FnMut(Result<NetworkEvent, AppError>) + Send + 'static,
F: Fn(Routing) -> Routing + Send + 'static,
{
let AuthGranted {
app_keys: AppKeys {
owner_key,
enc_key,
enc_pk,
enc_sk,
sign_pk,
sign_sk,
},
access_container,
bootstrap_config,
} = auth_granted;
let client_keys = ClientKeys {
sign_pk,
sign_sk,
enc_pk,
enc_sk,
enc_key: enc_key.clone(),
};
Self::new(network_observer, move |el_h, core_tx, net_tx| {
let client = Client::from_keys_with_hook(
client_keys,
owner_key,
el_h,
core_tx,
net_tx,
bootstrap_config,
routing_wrapper_fn,
)?;
let context = AppContext::registered(app_id, enc_key, access_container);
Ok((client, context))
})
}
fn new<N, F>(mut network_observer: N, setup: F) -> Result<Self, AppError>
where
N: FnMut(Result<NetworkEvent, AppError>) + Send + 'static,
F: FnOnce(Handle, CoreMsgTx<AppContext>, NetworkTx)
-> Result<(Client<AppContext>, AppContext), AppError>
+ Send
+ 'static,
{
let (tx, rx) = std_mpsc::sync_channel(0);
let joiner = thread::named("App Event Loop", move || {
let el = try_tx!(Core::new(), tx);
let el_h = el.handle();
let (core_tx, core_rx) = futures_mpsc::unbounded();
let (net_tx, net_rx) = futures_mpsc::unbounded();
el_h.spawn(
net_rx
.map(move |event| network_observer(Ok(event)))
.for_each(|_| Ok(())),
);
let core_tx_clone = core_tx.clone();
let (client, context) = try_tx!(setup(el_h, core_tx_clone, net_tx), tx);
unwrap!(tx.send(Ok(core_tx)));
event_loop::run(el, &client, &context, core_rx);
});
let core_tx = rx.recv()??;
Ok(App {
core_tx: Mutex::new(core_tx),
_core_joiner: joiner,
})
}
pub fn send<F>(&self, f: F) -> Result<(), AppError>
where
F: FnOnce(&Client<AppContext>, &AppContext) -> Option<Box<Future<Item = (), Error = ()>>>
+ Send
+ 'static,
{
let msg = CoreMsg::new(f);
let core_tx = unwrap!(self.core_tx.lock());
core_tx.unbounded_send(msg).map_err(AppError::from)
}
}
impl Drop for App {
fn drop(&mut self) {
let core_tx = match self.core_tx.lock() {
Ok(core_tx) => core_tx,
Err(err) => {
info!("Unexpected error in drop: {:?}", err);
return;
}
};
let msg = CoreMsg::build_terminator();
if let Err(err) = core_tx.unbounded_send(msg) {
info!("Unexpected error in drop: {:?}", err);
}
}
}
#[derive(Clone)]
pub enum AppContext {
Unregistered(Rc<Unregistered>),
Registered(Rc<Registered>),
}
#[allow(missing_docs)]
pub struct Unregistered {
object_cache: ObjectCache,
}
#[allow(missing_docs)]
pub struct Registered {
object_cache: ObjectCache,
app_id: String,
sym_enc_key: shared_secretbox::Key,
access_container_info: AccessContInfo,
access_info: RefCell<AccessContainerEntry>,
}
impl AppContext {
fn unregistered() -> Self {
AppContext::Unregistered(Rc::new(Unregistered { object_cache: ObjectCache::new() }))
}
fn registered(
app_id: String,
sym_enc_key: shared_secretbox::Key,
access_container_info: AccessContInfo,
) -> Self {
AppContext::Registered(Rc::new(Registered {
object_cache: ObjectCache::new(),
app_id: app_id,
sym_enc_key: sym_enc_key,
access_container_info: access_container_info,
access_info: RefCell::new(HashMap::new()),
}))
}
pub fn object_cache(&self) -> &ObjectCache {
match *self {
AppContext::Unregistered(ref context) => &context.object_cache,
AppContext::Registered(ref context) => &context.object_cache,
}
}
pub fn sym_enc_key(&self) -> Result<&shared_secretbox::Key, AppError> {
Ok(&self.as_registered()?.sym_enc_key)
}
pub fn refresh_access_info(&self, client: &Client<AppContext>) -> Box<AppFuture<()>> {
let reg = fry!(self.as_registered()).clone();
refresh_access_info(reg, client)
}
pub fn get_access_info(
&self,
client: &Client<AppContext>,
) -> Box<AppFuture<AccessContainerEntry>> {
let reg = fry!(self.as_registered()).clone();
fetch_access_info(reg.clone(), client)
.map(move |_| {
let access_info = reg.access_info.borrow();
access_info.clone()
})
.into_box()
}
fn as_registered(&self) -> Result<&Rc<Registered>, AppError> {
match *self {
AppContext::Registered(ref a) => Ok(a),
AppContext::Unregistered(_) => Err(AppError::OperationForbidden),
}
}
}
fn refresh_access_info(context: Rc<Registered>, client: &Client<AppContext>) -> Box<AppFuture<()>> {
let entry_key = fry!(access_container_enc_key(
&context.app_id,
&context.sym_enc_key,
&context.access_container_info.nonce,
));
client
.get_mdata_value(
context.access_container_info.id,
context.access_container_info.tag,
entry_key,
)
.map_err(AppError::from)
.and_then(move |value| {
let encoded = utils::symmetric_decrypt(&value.content, &context.sym_enc_key)?;
let decoded = deserialise(&encoded)?;
*context.access_info.borrow_mut() = decoded;
Ok(())
})
.into_box()
}
fn fetch_access_info(context: Rc<Registered>, client: &Client<AppContext>) -> Box<AppFuture<()>> {
if context.access_info.borrow().is_empty() {
refresh_access_info(context, client)
} else {
future::ok(()).into_box()
}
}