use super::utils::create_containers_req;
use Authenticator;
use errors::AuthError;
use futures::Future;
use revocation;
use routing::{AccountInfo, EntryActions, User};
use safe_core::{CoreError, MDataInfo, app_container_name};
use safe_core::ipc::{AuthReq, Permission};
use safe_core::nfs::NfsError;
use std::collections::HashMap;
use test_utils::{access_container, create_account_and_login, create_authenticator, create_file,
fetch_file, get_container_from_authenticator_entry, rand_app, register_app,
register_rand_app, revoke, run, try_access_container};
#[cfg(feature = "use-mock-routing")]
mod mock_routing {
use super::*;
use AuthFuture;
use access_container;
use app_auth::{AppState, app_state};
use config;
use ffi::ipc::auth_flush_app_revocation_queue;
use ffi_utils::test_utils::call_0;
use futures::future;
use maidsafe_utilities::SeededRng;
use routing::{ClientError, Request, Response};
use safe_core::{Client, FutureExt};
use safe_core::MockRouting;
use safe_core::ipc::{IpcError, Permission};
use safe_core::ipc::req::container_perms_into_permission_set;
use safe_core::ipc::resp::AccessContainerEntry;
use safe_core::utils::test_utils::Synchronizer;
use std::collections::HashMap;
use std::iter;
use std::sync::{Arc, Barrier};
use std::thread;
use test_utils::{get_container_from_authenticator_entry, register_rand_app, try_revoke};
use tiny_keccak::sha3_256;
#[test]
fn app_revocation_recovery() {
let (auth, locator, password) = create_authenticator();
let auth_req = AuthReq {
app: rand_app(),
app_container: false,
containers: create_containers_req(),
};
let app_id = auth_req.app.id.clone();
let auth_granted = unwrap!(register_app(&auth, &auth_req));
let mut ac_entries = access_container(&auth, app_id.clone(), auth_granted.clone());
let (videos_md, _) = unwrap!(ac_entries.remove("_videos"));
let (docs_md, _) = unwrap!(ac_entries.remove("_documents"));
unwrap!(create_file(
&auth,
videos_md.clone(),
"video.mp4",
vec![1; 10],
));
unwrap!(create_file(&auth, docs_md.clone(), "test.doc", vec![2; 10]));
let docs_name = docs_md.name;
let routing_hook = move |mut routing: MockRouting| -> MockRouting {
routing.set_request_hook(move |req| {
match *req {
Request::MutateMDataEntries { name, msg_id, .. } if name == docs_name => {
Some(Response::MutateMDataEntries {
msg_id,
res: Err(ClientError::LowBalance),
})
}
_ => None,
}
});
routing
};
let auth = unwrap!(Authenticator::login_with_hook(
locator.clone(),
password.clone(),
|| (),
routing_hook,
));
match try_revoke(&auth, &app_id) {
Err(AuthError::CoreError(CoreError::RoutingClientError(ClientError::LowBalance))) => (),
x => panic!("Unexpected {:?}", x),
}
let _ = unwrap!(fetch_file(&auth, docs_md.clone(), "test.doc"));
let new_videos_md = unwrap!(get_container_from_authenticator_entry(&auth, "_videos"));
let success = match fetch_file(&auth, new_videos_md, "video.mp4") {
Ok(_) => true,
Err(AuthError::NfsError(NfsError::FileNotFound)) => false,
x => panic!("Unexpected {:?}", x),
};
if !success {
let _ = unwrap!(fetch_file(&auth, videos_md.clone(), "video.mp4"));
}
let auth_keys = run(&auth, move |client| {
client
.list_auth_keys_and_version()
.map(move |(auth_keys, _version)| auth_keys)
.map_err(AuthError::from)
});
assert!(!auth_keys.contains(&auth_granted.app_keys.sign_pk));
let auth = unwrap!(Authenticator::login(
locator.clone(),
password.clone(),
|| (),
));
revoke(&auth, &app_id);
match fetch_file(&auth, docs_md, "test.doc") {
Err(AuthError::NfsError(NfsError::CoreError(CoreError::EncodeDecodeError(..)))) => (),
x => panic!("Unexpected {:?}", x),
}
match fetch_file(&auth, videos_md, "video.mp4") {
Err(AuthError::NfsError(NfsError::CoreError(CoreError::EncodeDecodeError(..)))) => (),
x => panic!("Unexpected {:?}", x),
}
let ac_entries = try_access_container(&auth, app_id.clone(), auth_granted.clone());
assert!(ac_entries.is_none());
let new_docs_md = unwrap!(get_container_from_authenticator_entry(&auth, "_documents"));
let new_videos_md = unwrap!(get_container_from_authenticator_entry(&auth, "_videos"));
let _ = unwrap!(fetch_file(&auth, new_docs_md, "test.doc"));
let _ = unwrap!(fetch_file(&auth, new_videos_md, "video.mp4"));
}
#[test]
fn app_authentication_during_pending_revocation() {
let (auth, locator, password) = create_authenticator();
let auth_req = AuthReq {
app: rand_app(),
app_container: false,
containers: create_containers_req(),
};
let app_id = auth_req.app.id.clone();
let _ = unwrap!(register_app(&auth, &auth_req));
simulate_revocation_failure(&locator, &password, iter::once(&app_id));
match register_app(&auth, &auth_req) {
Err(_) => (), x => panic!("Unexpected {:?}", x),
}
revoke(&auth, &app_id);
let _ = unwrap!(register_app(&auth, &auth_req));
}
#[test]
fn flushing_app_revocation_queue() {
let (auth, locator, password) = create_authenticator();
let auth_req = AuthReq {
app: rand_app(),
app_container: false,
containers: create_containers_req(),
};
let _ = unwrap!(register_app(&auth, &auth_req));
let app_id_0 = auth_req.app.id.clone();
let auth_req = AuthReq {
app: rand_app(),
app_container: false,
containers: create_containers_req(),
};
let _ = unwrap!(register_app(&auth, &auth_req));
let app_id_1 = auth_req.app.id.clone();
simulate_revocation_failure(&locator, &password, &[&app_id_0, &app_id_1]);
{
let app_id_0 = app_id_0.clone();
let app_id_1 = app_id_1.clone();
run(&auth, |client| {
let client = client.clone();
config::list_apps(&client)
.then(move |res| {
let (_, apps) = unwrap!(res);
let f_0 = app_state(&client, &apps, &app_id_0);
let f_1 = app_state(&client, &apps, &app_id_1);
f_0.join(f_1)
})
.then(|res| {
let (state_0, state_1) = unwrap!(res);
assert_eq!(state_0, AppState::Authenticated);
assert_eq!(state_1, AppState::Authenticated);
Ok(())
})
})
}
let auth = unwrap!(Authenticator::login(locator, password, || ()));
unsafe {
unwrap!(call_0(
|ud, cb| auth_flush_app_revocation_queue(&auth, ud, cb),
))
}
run(&auth, |client| {
let c2 = client.clone();
config::list_apps(client)
.then(move |res| {
let (_, apps) = unwrap!(res);
let f_0 = app_state(&c2, &apps, &app_id_0);
let f_1 = app_state(&c2, &apps, &app_id_1);
f_0.join(f_1)
})
.then(move |res| {
let (state_0, state_1) = unwrap!(res);
assert_eq!(state_0, AppState::Revoked);
assert_eq!(state_1, AppState::Revoked);
Ok(())
})
})
}
#[test]
fn concurrent_revocation_of_single_app() {
let rng = SeededRng::new();
let concurrency = 2;
let (auth, locator, password) = create_authenticator();
let mut containers_req = HashMap::new();
let _ = containers_req.insert(
"_documents".to_owned(),
btree_set![
Permission::Read,
Permission::Insert,
Permission::Update,
Permission::Delete,
],
);
let (app_id_0, auth_granted_0) =
unwrap!(register_rand_app(&auth, true, containers_req.clone()));
let (app_id_1, _) = unwrap!(register_rand_app(&auth, true, containers_req));
let ac_entries_0 = access_container(&auth, app_id_0.clone(), auth_granted_0);
let info = unwrap!(get_container_from_authenticator_entry(&auth, "_documents"));
unwrap!(create_file(&auth, info, "shared.txt", vec![0; 10]));
for app_id in &[&app_id_0, &app_id_1] {
let info = unwrap!(get_container_from_authenticator_entry(
&auth,
&app_container_name(app_id),
));
unwrap!(create_file(&auth, info, "private.txt", vec![0; 10]));
}
let barrier = Arc::new(Barrier::new(concurrency));
let sync = Synchronizer::new(rng);
let join_handles: Vec<_> = (0..concurrency)
.map(|_| {
let locator = locator.clone();
let password = password.clone();
let app_id = app_id_0.clone();
let barrier = Arc::clone(&barrier);
let sync = sync.clone();
thread::spawn(move || {
let auth = unwrap!(Authenticator::login_with_hook(
locator,
password,
|| (),
move |routing| sync.hook(routing),
));
let _ = barrier.wait();
try_revoke(&auth, &app_id)
})
})
.collect();
let success = join_handles.into_iter().fold(
false,
|success, handle| match unwrap!(
handle.join()
) {
Ok(_) |
Err(AuthError::IpcError(IpcError::UnknownApp)) => true,
_ => success,
},
);
if !success {
unwrap!(try_revoke(&auth, &app_id_0));
}
run(&auth, move |client| {
let app_0 = verify_app_is_revoked(client, app_id_0, ac_entries_0);
let app_1 = verify_app_is_authenticated(client, app_id_1);
app_0.join(app_1).map(|_| ())
});
}
#[test]
fn concurrent_revocation_of_multiple_apps() {
let rng = SeededRng::new();
let (auth, locator, password) = create_authenticator();
let mut containers_req = HashMap::new();
let _ = containers_req.insert(
"_documents".to_owned(),
btree_set![
Permission::Read,
Permission::Insert,
Permission::Update,
Permission::Delete,
],
);
let (app_id_0, auth_granted_0) =
unwrap!(register_rand_app(&auth, true, containers_req.clone()));
let (app_id_1, auth_granted_1) =
unwrap!(register_rand_app(&auth, true, containers_req.clone()));
let (app_id_2, _) = unwrap!(register_rand_app(&auth, true, containers_req));
let ac_entries_0 = access_container(&auth, app_id_0.clone(), auth_granted_0);
let ac_entries_1 = access_container(&auth, app_id_1.clone(), auth_granted_1);
let info = unwrap!(get_container_from_authenticator_entry(&auth, "_documents"));
unwrap!(create_file(&auth, info, "shared.txt", vec![0; 10]));
for app_id in &[&app_id_0, &app_id_1, &app_id_2] {
let info = unwrap!(get_container_from_authenticator_entry(
&auth,
&app_container_name(app_id),
));
unwrap!(create_file(&auth, info, "private.txt", vec![0; 10]));
}
let apps_to_revoke = [app_id_0.clone(), app_id_1.clone()];
let barrier = Arc::new(Barrier::new(apps_to_revoke.len()));
let sync = Synchronizer::new(rng);
let join_handles: Vec<_> = apps_to_revoke
.iter()
.map(|app_id| {
let locator = locator.clone();
let password = password.clone();
let app_id = app_id.to_string();
let barrier = Arc::clone(&barrier);
let sync = sync.clone();
thread::spawn(move || {
let auth = unwrap!(Authenticator::login_with_hook(
locator,
password,
|| (),
move |routing| sync.hook(routing),
));
let _ = barrier.wait();
try_revoke(&auth, &app_id)
})
})
.collect();
let results: Vec<_> = join_handles
.into_iter()
.map(|handle| unwrap!(handle.join()))
.collect();
for (app_id, result) in apps_to_revoke.iter().zip(results) {
if result.is_err() {
match try_revoke(&auth, app_id) {
Ok(_) |
Err(AuthError::IpcError(IpcError::UnknownApp)) => (),
Err(error) => panic!("Unexpected revocation failure: {:?}", error),
}
}
}
run(&auth, move |client| {
let app_0 = verify_app_is_revoked(client, app_id_0, ac_entries_0);
let app_1 = verify_app_is_revoked(client, app_id_1, ac_entries_1);
let app_2 = verify_app_is_authenticated(client, app_id_2);
app_0.join3(app_1, app_2).map(|_| ())
});
}
fn simulate_revocation_failure<T, S>(locator: &str, password: &str, app_ids: T)
where
T: IntoIterator<Item = S>,
S: AsRef<str>,
{
let auth = unwrap!(Authenticator::login(locator, password, || ()));
let ac_info = run(&auth, |client| Ok(unwrap!(client.access_container())));
let auth = unwrap!(Authenticator::login_with_hook(
locator,
password,
|| (),
move |mut routing| {
let ac_info = ac_info.clone();
routing.set_request_hook(move |request| match *request {
Request::MutateMDataEntries { name, tag, msg_id, .. } => {
if name == ac_info.name && tag == ac_info.type_tag {
Some(Response::MutateMDataEntries {
res: Err(ClientError::LowBalance),
msg_id,
})
} else {
None
}
}
_ => None,
});
routing
},
));
for app_id in app_ids {
match try_revoke(&auth, app_id.as_ref()) {
Err(_) => (),
x => panic!("Unexpected {:?}", x),
}
}
}
fn verify_app_is_revoked(
client: &Client<()>,
app_id: String,
prev_ac_entry: AccessContainerEntry,
) -> Box<AuthFuture<()>> {
let c0 = client.clone();
let c1 = client.clone();
config::list_apps(client)
.then(move |res| {
let (_, apps) = unwrap!(res);
let auth_keys = c0.list_auth_keys_and_version().map_err(AuthError::from);
let state = app_state(&c0, &apps, &app_id);
let app_hash = sha3_256(app_id.as_bytes());
let app_key = unwrap!(apps.get(&app_hash)).keys.sign_pk;
auth_keys.join(state).map(move |((auth_keys, _), state)| {
(auth_keys, app_key, state)
})
})
.then(move |res| -> Result<_, AuthError> {
let (auth_keys, app_key, state) = unwrap!(res);
assert!(!auth_keys.contains(&app_key));
assert_match!(state, AppState::Revoked);
Ok(app_key)
})
.then(move |res| {
let app_key = unwrap!(res);
let futures = prev_ac_entry.into_iter().map(move |(_, (mdata_info, _))| {
let perms = c1.list_mdata_user_permissions(
mdata_info.name,
mdata_info.type_tag,
User::Key(app_key),
).then(|res| {
assert_match!(
res,
Err(CoreError::RoutingClientError(ClientError::NoSuchKey))
);
Ok(())
});
let entries = c1.list_mdata_entries(mdata_info.name, mdata_info.type_tag)
.then(move |res| {
let entries = unwrap!(res);
for (key, value) in entries {
if value.content.is_empty() {
continue;
}
assert_match!(mdata_info.decrypt(&key), Err(_));
assert_match!(mdata_info.decrypt(&value.content), Err(_));
}
Ok(())
});
perms.join(entries).map(|_| ())
});
future::join_all(futures).map(|_| ())
})
.into_box()
}
fn verify_app_is_authenticated(client: &Client<()>, app_id: String) -> Box<AuthFuture<()>> {
let c0 = client.clone();
let c1 = client.clone();
let c2 = client.clone();
config::list_apps(client)
.then(move |res| {
let (_, mut apps) = unwrap!(res);
let app_hash = sha3_256(app_id.as_bytes());
let app_keys = unwrap!(apps.remove(&app_hash)).keys;
c0.list_auth_keys_and_version()
.map_err(AuthError::from)
.map(move |(auth_keys, _)| (auth_keys, app_id, app_keys))
})
.then(move |res| {
let (auth_keys, app_id, app_keys) = unwrap!(res);
let app_key = app_keys.sign_pk;
assert!(auth_keys.contains(&app_key));
access_container::fetch_entry(&c1, &app_id, app_keys).map(
move |(_, entry)| (app_key, entry),
)
})
.then(move |res| {
let (app_key, ac_entry) = unwrap!(res);
let user = User::Key(app_key);
let ac_entry = unwrap!(ac_entry);
let futures = ac_entry.into_iter().map(
move |(_, (mdata_info, permissions))| {
let expected_perms = container_perms_into_permission_set(&permissions);
let perms = c2.list_mdata_user_permissions(
mdata_info.name,
mdata_info.type_tag,
user,
).then(move |res| {
let perms = unwrap!(res);
assert_eq!(perms, expected_perms);
Ok(())
});
let entries = c2.list_mdata_entries(mdata_info.name, mdata_info.type_tag)
.then(move |res| {
let entries = unwrap!(res);
for (key, value) in entries {
if value.content.is_empty() {
continue;
}
let _ = unwrap!(mdata_info.decrypt(&key));
let _ = unwrap!(mdata_info.decrypt(&value.content));
}
Ok(())
});
perms.join(entries).map(|_| ())
},
);
future::join_all(futures).map(|_| ())
})
.into_box()
}
}
#[test]
fn app_revocation() {
let authenticator = create_account_and_login();
let auth_req1 = AuthReq {
app: rand_app(),
app_container: false,
containers: create_containers_req(),
};
let app_id1 = auth_req1.app.id.clone();
let auth_granted1 = unwrap!(register_app(&authenticator, &auth_req1));
let auth_req2 = AuthReq {
app: rand_app(),
app_container: true,
containers: create_containers_req(),
};
let app_id2 = auth_req2.app.id.clone();
let auth_granted2 = unwrap!(register_app(&authenticator, &auth_req2));
let mut ac_entries = access_container(&authenticator, app_id1.clone(), auth_granted1.clone());
let (videos_md1, _) = unwrap!(ac_entries.remove("_videos"));
unwrap!(create_file(
&authenticator,
videos_md1.clone(),
"1.mp4",
vec![1; 10],
));
let mut ac_entries = access_container(&authenticator, app_id2.clone(), auth_granted2.clone());
let (videos_md2, _) = unwrap!(ac_entries.remove("_videos"));
unwrap!(create_file(
&authenticator,
videos_md2.clone(),
"2.mp4",
vec![1; 10],
));
let app_container_name = app_container_name(&app_id2);
let (app_container_md, _) = unwrap!(ac_entries.remove(&app_container_name));
unwrap!(create_file(
&authenticator,
app_container_md.clone(),
"3.mp4",
vec![1; 10],
));
assert_eq!(count_mdata_entries(&authenticator, videos_md1.clone()), 2);
let _ = unwrap!(fetch_file(&authenticator, videos_md1.clone(), "1.mp4"));
let _ = unwrap!(fetch_file(&authenticator, videos_md1.clone(), "2.mp4"));
let _ = unwrap!(fetch_file(&authenticator, videos_md2.clone(), "1.mp4"));
let _ = unwrap!(fetch_file(&authenticator, videos_md2.clone(), "2.mp4"));
revoke(&authenticator, &app_id1);
assert_eq!(count_mdata_entries(&authenticator, videos_md1.clone()), 4);
let ac = try_access_container(&authenticator, app_id1.clone(), auth_granted1.clone());
assert!(ac.is_none());
let (name, tag) = (videos_md2.name, videos_md2.type_tag);
let perms = run(&authenticator, move |client| {
client.list_mdata_permissions(name, tag).map_err(From::from)
});
assert!(!perms.contains_key(
&User::Key(auth_granted1.app_keys.sign_pk),
));
assert!(perms.contains_key(
&User::Key(auth_granted2.app_keys.sign_pk),
));
match fetch_file(&authenticator, videos_md1.clone(), "1.mp4") {
Err(AuthError::NfsError(NfsError::CoreError(CoreError::EncodeDecodeError(..)))) => (),
x => panic!("Unexpected {:?}", x),
}
match fetch_file(&authenticator, videos_md1.clone(), "2.mp4") {
Err(AuthError::NfsError(NfsError::CoreError(CoreError::EncodeDecodeError(..)))) => (),
x => panic!("Unexpected {:?}", x),
}
let mut ac_entries = access_container(&authenticator, app_id2.clone(), auth_granted2.clone());
let (videos_md2, _) = unwrap!(ac_entries.remove("_videos"));
let _ = unwrap!(fetch_file(&authenticator, videos_md2.clone(), "1.mp4"));
let _ = unwrap!(fetch_file(&authenticator, videos_md2.clone(), "2.mp4"));
let auth_granted1 = unwrap!(register_app(&authenticator, &auth_req1));
let mut ac_entries = access_container(&authenticator, app_id1.clone(), auth_granted1.clone());
let (videos_md1, _) = unwrap!(ac_entries.remove("_videos"));
let _ = unwrap!(fetch_file(&authenticator, videos_md1.clone(), "1.mp4"));
let _ = unwrap!(fetch_file(&authenticator, videos_md1.clone(), "2.mp4"));
let _ = unwrap!(fetch_file(&authenticator, videos_md2.clone(), "1.mp4"));
let _ = unwrap!(fetch_file(&authenticator, videos_md2.clone(), "2.mp4"));
revoke(&authenticator, &app_id1);
assert_eq!(count_mdata_entries(&authenticator, videos_md1.clone()), 6);
match fetch_file(&authenticator, videos_md1.clone(), "1.mp4") {
Err(AuthError::NfsError(NfsError::CoreError(CoreError::EncodeDecodeError(..)))) => (),
x => panic!("Unexpected {:?}", x),
}
match fetch_file(&authenticator, videos_md1.clone(), "2.mp4") {
Err(AuthError::NfsError(NfsError::CoreError(CoreError::EncodeDecodeError(..)))) => (),
x => panic!("Unexpected {:?}", x),
}
let mut ac_entries = access_container(&authenticator, app_id2.clone(), auth_granted2.clone());
let (videos_md2, _) = unwrap!(ac_entries.remove("_videos"));
let _ = unwrap!(fetch_file(&authenticator, videos_md2.clone(), "1.mp4"));
let _ = unwrap!(fetch_file(&authenticator, videos_md2.clone(), "2.mp4"));
revoke(&authenticator, &app_id2);
match fetch_file(&authenticator, videos_md2.clone(), "1.mp4") {
Err(AuthError::NfsError(NfsError::CoreError(CoreError::EncodeDecodeError(..)))) => (),
x => panic!("Unexpected {:?}", x),
}
let auth_granted2 = unwrap!(register_app(&authenticator, &auth_req2));
let mut ac_entries = access_container(&authenticator, app_id2.clone(), auth_granted2.clone());
let (app_container_md, _) = unwrap!(ac_entries.remove(&app_container_name));
assert_eq!(
count_mdata_entries(&authenticator, app_container_md.clone()),
2
);
let _ = unwrap!(fetch_file(
&authenticator,
app_container_md.clone(),
"3.mp4",
));
revoke(&authenticator, &app_id2);
}
#[test]
fn flushing_empty_app_revocation_queue_does_not_mutate_network() {
let (auth, ..) = create_authenticator();
let account_info_0 = get_account_info(&auth);
run(
&auth,
|client| revocation::flush_app_revocation_queue(client),
);
let account_info_1 = get_account_info(&auth);
assert_eq!(account_info_0, account_info_1);
let auth_req = AuthReq {
app: rand_app(),
app_container: false,
containers: create_containers_req(),
};
let _ = unwrap!(register_app(&auth, &auth_req));
let app_id = auth_req.app.id;
revoke(&auth, &app_id);
let account_info_2 = get_account_info(&auth);
run(
&auth,
|client| revocation::flush_app_revocation_queue(client),
);
let account_info_3 = get_account_info(&auth);
assert_eq!(account_info_2, account_info_3);
}
#[test]
fn revocation_with_unencrypted_container_entries() {
let (auth, ..) = create_authenticator();
let mut containers_req = HashMap::new();
let _ = containers_req.insert(
"_documents".to_owned(),
btree_set![
Permission::Read,
Permission::Insert,
],
);
let (app_id, _) = unwrap!(register_rand_app(&auth, true, containers_req));
let shared_info = unwrap!(get_container_from_authenticator_entry(&auth, "_documents"));
let shared_info2 = shared_info.clone();
let shared_key = b"shared-key".to_vec();
let shared_content = b"shared-value".to_vec();
let shared_actions = EntryActions::new()
.ins(shared_key.clone(), shared_content.clone(), 0)
.into();
let dedicated_info = unwrap!(get_container_from_authenticator_entry(
&auth,
&app_container_name(&app_id),
));
let dedicated_info2 = dedicated_info.clone();
let dedicated_key = b"dedicated-key".to_vec();
let dedicated_content = b"dedicated-value".to_vec();
let dedicated_actions = EntryActions::new()
.ins(dedicated_key.clone(), dedicated_content.clone(), 0)
.into();
run(&auth, move |client| {
let f0 =
client.mutate_mdata_entries(shared_info.name, shared_info.type_tag, shared_actions);
let f1 = client.mutate_mdata_entries(
dedicated_info.name,
dedicated_info.type_tag,
dedicated_actions,
);
f0.join(f1).map(|_| ()).map_err(AuthError::from)
});
revoke(&auth, &app_id);
run(&auth, move |client| {
let f0 = client.get_mdata_value(shared_info2.name, shared_info2.type_tag, shared_key);
let f1 = client.get_mdata_value(
dedicated_info2.name,
dedicated_info2.type_tag,
dedicated_key,
);
f0.join(f1).then(move |res| {
let (shared_value, dedicated_value) = unwrap!(res);
assert_eq!(shared_value.content, shared_content);
assert_eq!(dedicated_value.content, dedicated_content);
Ok(())
})
})
}
fn count_mdata_entries(authenticator: &Authenticator, info: MDataInfo) -> usize {
run(authenticator, move |client| {
client
.list_mdata_entries(info.name, info.type_tag)
.map(|entries| entries.len())
.map_err(From::from)
})
}
fn get_account_info(authenticator: &Authenticator) -> AccountInfo {
run(authenticator, |client| {
client.get_account_info().map_err(AuthError::from)
})
}