use crate::btree_map;
use crate::client::{core_client::CoreClient, MDataInfo};
use crate::crypto::shared_secretbox;
use crate::errors::CoreError;
use crate::nfs::{
create_directory,
file_helper::{self, Version},
File, Mode, NfsError,
};
use crate::utils::{self, generate_random_vector, test_utils::random_client};
use crate::DIR_TAG;
use log::trace;
use safe_nd::{Error as SndError, MDataKind};
use self_encryption::MIN_CHUNK_SIZE;
use tokio::{sync::mpsc, task::LocalSet};
use unwrap::unwrap;
const APPEND_SIZE: usize = 10;
const ORIG_SIZE: usize = 5555;
const NEW_SIZE: usize = 50;
async fn create_test_file_with_size(
client: &CoreClient,
published: bool,
size: usize,
) -> Result<(MDataInfo, File), NfsError> {
let c2 = client.clone();
let c3 = client.clone();
let root = unwrap!(MDataInfo::random_private(MDataKind::Seq, DIR_TAG));
let root2 = root.clone();
let res = create_directory(&client.clone(), &root, btree_map![], btree_map![]).await;
assert!(res.is_ok());
let writer = file_helper::write(
c2.clone(),
File::new(Vec::new(), published),
Mode::Overwrite,
root.enc_key().cloned(),
)
.await?;
let bytes = vec![0u8; size];
writer.write(&bytes).await?;
let file = writer.close().await?;
let _ = file_helper::insert(c3, root2.clone(), "hello.txt", &file).await;
Ok((root2, file))
}
async fn create_test_file(
client: &CoreClient,
published: bool,
) -> Result<(MDataInfo, File), NfsError> {
create_test_file_with_size(client, published, ORIG_SIZE).await
}
#[tokio::test]
async fn file_fetch_public_md() -> Result<(), NfsError> {
let client: CoreClient = random_client()?;
let c2 = client.clone();
let c3 = client.clone();
let c4 = client.clone();
let c5 = client.clone();
let c6 = client.clone();
let c7 = client.clone();
let mut root = unwrap!(MDataInfo::random_public(MDataKind::Unseq, DIR_TAG));
root.enc_info = Some((shared_secretbox::gen_key(), utils::generate_nonce()));
root.new_enc_info = Some((shared_secretbox::gen_key(), utils::generate_nonce()));
let root2 = root.clone();
create_directory(&client, &root, btree_map![], btree_map![]).await?;
let writer = file_helper::write(
c2.clone(),
File::new(Vec::new(), true),
Mode::Overwrite,
root.enc_key().cloned(),
)
.await?;
writer.write(&[0u8; ORIG_SIZE]).await?;
let file = writer.close().await?;
file_helper::insert(c3, root2.clone(), "", &file).await?;
let dir = root2;
let (_version, file) = file_helper::fetch(c4, dir.clone(), "").await?;
let reader = file_helper::read(c5, &file, dir.enc_key().cloned()).await?;
let size = reader.size().await;
trace!("reading {} bytes", size);
let data = reader.read(0, size).await?;
assert_eq!(data, vec![0u8; ORIG_SIZE]);
std::thread::sleep(std::time::Duration::new(3, 0));
let (_version, file) = file_helper::fetch(c6, dir.clone(), "").await?;
let reader = file_helper::read(c7, &file, dir.enc_key().cloned()).await?;
let size = reader.size().await;
trace!("reading {} bytes", size);
let data = reader.read(0, size).await?;
assert_eq!(data, vec![0u8; ORIG_SIZE]);
Ok(())
}
#[tokio::test]
async fn files_stored_in_unpublished_idata() -> Result<(), NfsError> {
let (mut client1_tx, mut client1_rx) = mpsc::channel(1);
let (mut client2_tx, mut client2_rx) = mpsc::channel(1);
let (mut finish_tx, mut finish_rx) = mpsc::channel(1);
let local = LocalSet::new();
let _join_handle1 = local.spawn_local(async move {
let client: CoreClient = random_client()?;
let c2 = client.clone();
let c3 = client.clone();
let c4 = client.clone();
let c5 = client.clone();
let c6 = client.clone();
let c7 = client.clone();
let root = unwrap!(MDataInfo::random_public(MDataKind::Unseq, DIR_TAG));
create_directory(&client, &root, btree_map![], btree_map![]).await?;
let writer =
file_helper::write(c2, File::new(Vec::new(), false), Mode::Overwrite, None).await?;
writer.write(&[0u8; ORIG_SIZE]).await?;
let file = writer.close().await?;
file_helper::insert(c3, root.clone(), "", &file).await?;
let (_version, file) = file_helper::fetch(c4, root.clone(), "").await?;
let reader = file_helper::read(c5, &file, None).await?;
let size = reader.size().await;
trace!("reading {} bytes", size);
let data = reader.read(0, size).await?;
assert_eq!(data, vec![0u8; ORIG_SIZE]);
unwrap!(client1_tx.send(root.clone()).await);
unwrap!(client2_rx.recv().await);
let _ = file_helper::delete(c6, root.clone(), "", false, Version::Custom(1)).await?;
let res = file_helper::fetch(c7, root, "").await;
match res {
Err(NfsError::FileNotFound) => (),
Ok(_) => panic!("Unexpected success"),
Err(e) => panic!("Unexpected error {:?}", e),
}
unwrap!(finish_tx.send(()).await);
Ok::<_, NfsError>(())
});
let _join_handle2 = local.spawn_local(async move {
let dir: MDataInfo = unwrap!(client1_rx.recv().await);
let client: CoreClient = random_client()?;
let res = file_helper::fetch(client.clone(), dir, "").await;
match res {
Ok(_) => panic!("Unexpected success"),
Err(NfsError::CoreError(CoreError::DataError(SndError::AccessDenied))) => (),
Err(err) => panic!("Unexpected error: {:?}", err),
}
unwrap!(client2_tx.send(()).await);
unwrap!(finish_rx.recv().await);
Ok::<_, NfsError>(())
});
local.await;
Ok(())
}
#[tokio::test]
async fn file_read() -> Result<(), NfsError> {
let client: CoreClient = random_client()?;
let c2 = client.clone();
let (dir, file) = create_test_file(&client, true).await?;
let creation_time = *file.created_time();
let reader = file_helper::read(c2, &file, dir.enc_key().cloned()).await?;
let size = reader.size().await;
trace!("reading {} bytes", size);
let data = reader.read(0, size).await?;
assert_eq!(creation_time, *file.created_time());
assert!(creation_time <= *file.modified_time());
assert_eq!(data, vec![0u8; ORIG_SIZE]);
Ok(())
}
#[tokio::test]
async fn file_read_chunks() -> Result<(), NfsError> {
const CHUNK_SIZE: u64 = 1000;
let client: CoreClient = random_client()?;
let c2 = client.clone();
let (dir, file) = create_test_file(&client, true).await?;
let reader = file_helper::read(c2, &file, dir.enc_key().cloned()).await?;
let size = reader.size().await;
assert_eq!(size, ORIG_SIZE as u64);
let mut size_read = 0;
let mut result = Vec::new();
let mut done_looping = false;
while !done_looping {
let to_read = if size_read + CHUNK_SIZE >= size {
size - size_read
} else {
CHUNK_SIZE
};
trace!("reading {} bytes", to_read);
let mut data = reader.read(size_read, to_read).await?;
size_read += data.len() as u64;
result.append(&mut data);
if size_read < size {
} else {
done_looping = true;
}
}
assert_eq!(size, size_read);
assert_eq!(result, vec![0u8; ORIG_SIZE]);
trace!("reading 0 bytes");
let data = reader.read(size, 0).await?;
assert_eq!(data, Vec::<u8>::new());
match reader.read(size, 1).await {
Ok(_) => {
panic!("Read past end of file successfully")
}
Err(_) => Ok(()),
}
}
#[tokio::test]
async fn file_write_chunks() -> Result<(), NfsError> {
const CHUNK_SIZE: usize = 1000;
const GOAL_SIZE: usize = 5555;
let content = [0u8; GOAL_SIZE];
let client: CoreClient = random_client()?;
let c2 = client.clone();
let c3 = client.clone();
let c4 = client.clone();
let (dir, file) = create_test_file(&client, true).await?;
let writer = file_helper::write(c2, file, Mode::Overwrite, dir.enc_key().cloned()).await?;
let mut size_written = 0;
let mut done_looping = false;
while !done_looping {
let to_write = if size_written + CHUNK_SIZE >= GOAL_SIZE {
GOAL_SIZE - size_written
} else {
CHUNK_SIZE
};
trace!("writing {} bytes", to_write);
writer
.write(&content[size_written..size_written + to_write])
.await?;
size_written += to_write;
if size_written < GOAL_SIZE {
} else {
done_looping = true
}
}
let empty: [u8; 0] = [];
writer.write(&empty).await?;
let file = writer.close().await?;
let writer = file_helper::write(c3, file, Mode::Append, dir.enc_key().cloned()).await?;
let mut size_written = 0;
let mut done_looping_again = false;
while !done_looping_again {
let to_write = if size_written + CHUNK_SIZE >= GOAL_SIZE {
GOAL_SIZE - size_written
} else {
CHUNK_SIZE
};
trace!("writing {} bytes", to_write);
writer
.write(&content[size_written..size_written + to_write])
.await?;
size_written += to_write;
if size_written < GOAL_SIZE {
} else {
done_looping_again = true;
}
}
writer.write(&empty).await?;
let file = writer.close().await?;
let reader = file_helper::read(c4, &file, dir.enc_key().cloned()).await?;
let size = reader.size().await;
assert_eq!(size, 2 * GOAL_SIZE as u64);
let data = reader.read(0, size).await?;
assert_eq!(data, vec![0u8; 2 * GOAL_SIZE]);
Ok(())
}
#[tokio::test]
async fn file_update_overwrite() -> Result<(), NfsError> {
let client: CoreClient = random_client()?;
let c2 = client.clone();
let c3 = client.clone();
let c4 = client.clone();
let c5 = client.clone();
let (dir, file) = create_test_file(&client, true).await?;
let creation_time = *file.created_time();
let writer = file_helper::write(c2, file, Mode::Overwrite, dir.enc_key().cloned()).await?;
writer.write(&[1u8; NEW_SIZE]).await?;
let file = writer.close().await?;
let _ = file_helper::update(c3, dir.clone(), "hello.txt", &file, Version::Custom(1)).await?;
let (_version, file) = file_helper::fetch(c4, dir.clone(), "hello.txt").await?;
assert_eq!(creation_time, *file.created_time());
assert!(creation_time <= *file.modified_time());
let reader = file_helper::read(c5, &file, dir.enc_key().cloned()).await?;
let size = reader.size().await;
trace!("reading {} bytes", size);
let data = reader.read(0, size).await?;
assert_eq!(data, vec![1u8; NEW_SIZE]);
Ok(())
}
#[tokio::test]
async fn file_update_append() -> Result<(), NfsError> {
let client: CoreClient = random_client()?;
for i in 0..3 {
let c2 = client.clone();
let c3 = client.clone();
let creation_size = i * MIN_CHUNK_SIZE as usize;
trace!("Testing with size {}", creation_size);
let (dir, file) = create_test_file_with_size(&client, true, creation_size).await?;
let writer = file_helper::write(c2, file, Mode::Append, dir.enc_key().cloned()).await?;
writer.write(&[2u8; APPEND_SIZE]).await?;
let file = writer.close().await?;
let reader = file_helper::read(c3, &file, dir.enc_key().cloned()).await?;
let size = reader.size().await;
trace!("reading {} bytes", size);
let data = reader.read(0, size).await?;
assert_eq!(data.len(), creation_size + APPEND_SIZE);
assert_eq!(data[0..creation_size].to_owned(), vec![0u8; creation_size]);
assert_eq!(&data[creation_size..], [2u8; APPEND_SIZE]);
}
Ok(())
}
#[tokio::test]
async fn file_update_metadata() -> Result<(), NfsError> {
let client: CoreClient = random_client()?;
let c2 = client.clone();
let c3 = client.clone();
let (dir, mut file) = create_test_file(&client, true).await?;
file.set_user_metadata(vec![12u8; 10]);
let version =
file_helper::update(c2, dir.clone(), "hello.txt", &file, Version::GetNext).await?;
assert_eq!(version, 1);
let (_version, file) = file_helper::fetch(c3, dir, "hello.txt").await?;
assert_eq!(*file.user_metadata(), [12u8; 10][..]);
Ok(())
}
#[tokio::test]
async fn file_delete() -> Result<(), NfsError> {
let client: CoreClient = random_client()?;
let c2 = client.clone();
let c3 = client.clone();
let (dir, _file) = create_test_file(&client, true).await?;
let version =
file_helper::delete(c2, dir.clone(), "hello.txt", true, Version::Custom(1)).await?;
assert_eq!(version, 1);
let res = file_helper::fetch(c3, dir, "hello.txt").await;
match res {
Ok(_) => {
panic!("Fetched non-existing file succesfully")
}
Err(_) => Ok(()),
}
}
#[tokio::test]
async fn file_delete_then_add() -> Result<(), NfsError> {
let client: CoreClient = random_client()?;
let c2 = client.clone();
let c3 = client.clone();
let c4 = client.clone();
let c5 = client.clone();
let c6 = client.clone();
let (dir, file) = create_test_file(&client, true).await?;
let _ = file_helper::delete(c2, dir.clone(), "hello.txt", true, Version::Custom(1)).await?;
let writer = file_helper::write(c3, file, Mode::Overwrite, dir.enc_key().cloned()).await?;
writer.write(&[1u8; NEW_SIZE]).await?;
let file = writer.close().await?;
file_helper::insert(c4, dir.clone(), "hello.txt", &file).await?;
let (version, file) = file_helper::fetch(c5, dir.clone(), "hello.txt").await?;
assert_eq!(version, 0);
let reader = file_helper::read(c6, &file, dir.enc_key().cloned()).await?;
let size = reader.size().await;
trace!("reading {} bytes", size);
let data = reader.read(0, size).await?;
assert_eq!(data, vec![1u8; NEW_SIZE]);
Ok(())
}
#[tokio::test]
async fn file_open_close() -> Result<(), NfsError> {
let client: CoreClient = random_client()?;
let c2 = client.clone();
let c3 = client.clone();
let c4 = client.clone();
let c5 = client.clone();
let (dir, file) = create_test_file(&client, true).await?;
let _reader = file_helper::read(c2, &file, dir.enc_key().cloned()).await?;
let writer =
file_helper::write(c3, file.clone(), Mode::Overwrite, dir.enc_key().cloned()).await?;
let _ = writer.close().await?;
let writer = file_helper::write(c4, file.clone(), Mode::Append, dir.enc_key().cloned()).await?;
let _ = writer.close();
let reader = file_helper::read(c5, &file, dir.enc_key().cloned()).await?;
let size = reader.size().await;
let data = reader.read(0, size).await?;
assert_eq!(data, vec![0u8; ORIG_SIZE]);
Ok(())
}
#[tokio::test]
async fn file_open_concurrent() -> Result<(), NfsError> {
let client: CoreClient = random_client()?;
let c2 = client.clone();
let c3 = client.clone();
let c4 = client.clone();
let c5 = client.clone();
let c6 = client.clone();
let (dir, file) = create_test_file(&client, true).await?;
let writer1 =
file_helper::write(c2, file.clone(), Mode::Overwrite, dir.enc_key().cloned()).await?;
let writer2 =
file_helper::write(c3, file.clone(), Mode::Overwrite, dir.enc_key().cloned()).await?;
let reader = file_helper::read(c4, &file, dir.enc_key().cloned()).await?;
writer1.write(&[1u8; NEW_SIZE]).await?;
let _ = writer1.close().await?;
writer2.write(&[2u8; NEW_SIZE]).await?;
let file2 = writer2.close().await?;
let size = reader.size().await;
let data = reader.read(0, size).await?;
assert_eq!(data, vec![0u8; ORIG_SIZE]);
let reader = file_helper::read(c5, &file, dir.enc_key().cloned()).await?;
let size = reader.size().await;
let data = reader.read(0, size).await?;
assert_eq!(data, vec![0u8; ORIG_SIZE]);
let reader = file_helper::read(c6, &file2, dir.enc_key().cloned()).await?;
let size = reader.size().await;
let data = reader.read(0, size).await?;
assert_eq!(data, vec![2u8; NEW_SIZE]);
Ok(())
}
#[tokio::test]
async fn encryption() -> Result<(), NfsError> {
let client = random_client()?;
let c2 = client.clone();
let c3 = client.clone();
let c4 = client.clone();
let content: Vec<u8> = unwrap!(generate_random_vector(ORIG_SIZE));
let content2 = content.clone();
let key = shared_secretbox::gen_key();
let wrong_key = shared_secretbox::gen_key();
let writer = file_helper::write(
client.clone(),
File::new(Vec::new(), true),
Mode::Overwrite,
Some(key.clone()),
)
.await?;
writer.write(&content).await?;
let file = writer.close().await?;
let _: Result<_, NfsError> = match file_helper::read(c2, &file, None).await {
Ok(_) => return Err(NfsError::from("Unexpected success")),
Err(_) => {
Ok(())
}
};
match file_helper::read(c3, &file, Some(wrong_key)).await {
Ok(_) => return Err(NfsError::from("Unexpected success")),
Err(error) => match error {
NfsError::CoreError(CoreError::SymmetricDecipherFailure) => Ok(()),
error => Err(error),
},
}?;
let reader = file_helper::read(c4, &file, Some(key)).await?;
let size = reader.size().await;
let retrieved_content = reader.read(0, size).await?;
assert_eq!(retrieved_content, content2);
Ok(())
}