use super::{AuthError, AuthFuture};
use futures::Future;
use futures::future::{self, Either, Loop};
use maidsafe_utilities::serialisation::{deserialise, serialise};
use routing::{ClientError, EntryActions, EntryError};
use safe_core::{Client, CoreError, FutureExt};
use safe_core::ipc::IpcError;
use safe_core::ipc::req::AppExchangeInfo;
use safe_core::ipc::resp::AppKeys;
use serde::Serialize;
use serde::de::DeserializeOwned;
use std::collections::{HashMap, VecDeque};
use tiny_keccak::sha3_256;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AppInfo {
pub info: AppExchangeInfo,
pub keys: AppKeys,
}
pub const KEY_APPS: &[u8] = b"apps";
pub const KEY_APP_REVOCATION_QUEUE: &[u8] = b"revocation-queue";
pub type Apps = HashMap<[u8; 32], AppInfo>;
pub type RevocationQueue = VecDeque<String>;
pub fn next_version(version: Option<u64>) -> u64 {
version.map(|v| v + 1).unwrap_or(0)
}
pub fn list_apps(client: &Client<()>) -> Box<AuthFuture<(Option<u64>, Apps)>> {
get_entry(client, KEY_APPS)
}
pub fn get_app(client: &Client<()>, app_id: &str) -> Box<AuthFuture<AppInfo>> {
let app_id_hash = sha3_256(app_id.as_bytes());
list_apps(client)
.and_then(move |(_, config)| {
config.get(&app_id_hash).cloned().ok_or_else(|| {
AuthError::IpcError(IpcError::UnknownApp)
})
})
.into_box()
}
pub fn insert_app(
client: &Client<()>,
apps: Apps,
new_version: u64,
app: AppInfo,
) -> Box<AuthFuture<(u64, Apps)>> {
let client = client.clone();
let hash = sha3_256(app.info.id.as_bytes());
mutate_entry(&client, KEY_APPS, apps, new_version, move |apps| {
apps.insert(hash, app.clone()).is_none()
})
}
pub fn remove_app(
client: &Client<()>,
apps: Apps,
new_version: u64,
app_id: &str,
) -> Box<AuthFuture<(u64, Apps)>> {
let hash = sha3_256(app_id.as_bytes());
mutate_entry(client, KEY_APPS, apps, new_version, move |apps| {
apps.remove(&hash).is_some()
})
}
pub fn get_app_revocation_queue(
client: &Client<()>,
) -> Box<AuthFuture<(Option<u64>, RevocationQueue)>> {
get_entry(client, KEY_APP_REVOCATION_QUEUE)
}
pub fn push_to_app_revocation_queue(
client: &Client<()>,
queue: RevocationQueue,
new_version: u64,
app_id: String,
) -> Box<AuthFuture<(u64, RevocationQueue)>> {
mutate_entry(
client,
KEY_APP_REVOCATION_QUEUE,
queue,
new_version,
move |queue| if !queue.contains(&app_id) {
queue.push_back(app_id.clone());
true
} else {
false
},
)
}
pub fn remove_from_app_revocation_queue(
client: &Client<()>,
queue: RevocationQueue,
new_version: u64,
app_id: String,
) -> Box<AuthFuture<(u64, RevocationQueue)>> {
mutate_entry(
client,
KEY_APP_REVOCATION_QUEUE,
queue,
new_version,
move |queue| if let Some(index) = queue.iter().position(|item| *item == app_id) {
let _ = queue.remove(index);
true
} else {
false
},
)
}
fn get_entry<T>(client: &Client<()>, key: &[u8]) -> Box<AuthFuture<(Option<u64>, T)>>
where
T: Default + DeserializeOwned + Serialize + 'static,
{
let parent = fry!(client.config_root_dir());
let key = fry!(parent.enc_entry_key(key));
client
.get_mdata_value(parent.name, parent.type_tag, key)
.and_then(move |value| {
let decoded = parent.decrypt(&value.content)?;
let decoded = if !decoded.is_empty() {
deserialise(&decoded)?
} else {
Default::default()
};
Ok((Some(value.entry_version), decoded))
})
.or_else(|error| match error {
CoreError::RoutingClientError(ClientError::NoSuchEntry) => Ok(
(None, Default::default()),
),
_ => Err(AuthError::from(error)),
})
.into_box()
}
fn update_entry<T>(
client: &Client<()>,
key: &[u8],
content: &T,
new_version: u64,
) -> Box<AuthFuture<()>>
where
T: Serialize,
{
let parent = fry!(client.config_root_dir());
let key = fry!(parent.enc_entry_key(key));
let encoded = fry!(serialise(content));
let encoded = fry!(parent.enc_entry_value(&encoded));
let actions = if new_version == 0 {
EntryActions::new().ins(key.clone(), encoded, 0)
} else {
EntryActions::new().update(key.clone(), encoded, new_version)
};
client
.mutate_mdata_entries(parent.name, parent.type_tag, actions.into())
.or_else(move |error| {
if let CoreError::RoutingClientError(ClientError::InvalidEntryActions(ref errors)) =
error
{
if let Some(error) = errors.get(&key) {
match *error {
EntryError::InvalidSuccessor(version) |
EntryError::EntryExists(version) => {
return Err(CoreError::RoutingClientError(
ClientError::InvalidSuccessor(version),
));
}
_ => (),
}
}
}
Err(error)
})
.map_err(From::from)
.into_box()
}
fn mutate_entry<T, F>(
client: &Client<()>,
key: &[u8],
item: T,
new_version: u64,
f: F,
) -> Box<AuthFuture<(u64, T)>>
where
T: Default + DeserializeOwned + Serialize + 'static,
F: Fn(&mut T) -> bool + 'static,
{
let client = client.clone();
let key = key.to_vec();
future::loop_fn((key, new_version, item), move |(key,
new_version,
mut item)| {
let c2 = client.clone();
let c3 = client.clone();
if f(&mut item) {
let f = update_entry(&c2, &key, &item, new_version)
.map(move |_| Loop::Break((new_version, item)))
.or_else(move |error| match error {
AuthError::CoreError(
CoreError::RoutingClientError(ClientError::InvalidSuccessor(_))
) => {
let f = get_entry(&c3, &key)
.map(move |(version, item)| {
Loop::Continue((key, next_version(version), item))
});
Either::A(f)
}
_ => Either::B(future::err(error)),
});
Either::A(f)
} else {
Either::B(future::ok(Loop::Break((new_version - 1, item))))
}
}).into_box()
}