use memmap::{MmapMut, MmapOptions};
use std::convert::TryInto;
use std::error::Error;
use std::fs::{File, OpenOptions};
use std::io::{Read, Write};
use std::path::{Path, PathBuf};
use sha2::{Digest, Sha256};
use spake2::{Ed25519Group, Identity, Password, Spake2};
#[cfg(test)]
mod tests;
pub mod errors;
use errors::PortalError::*;
pub mod protocol;
pub use protocol::*;
pub const DEFAULT_PORT: u16 = 13265;
pub const CHUNK_SIZE: usize = 65536;
pub const NO_VERIFY_CALLBACK: Option<fn(&TransferInfo) -> bool> = None::<fn(&TransferInfo) -> bool>;
pub const NO_PROGRESS_CALLBACK: Option<fn(usize)> = None::<fn(usize)>;
#[derive(PartialEq, Eq, Debug)]
pub struct Portal {
pub id: String,
pub direction: Direction,
pub exchange: PortalKeyExchange,
nseq: NonceSequence,
pub state: Option<Spake2<Ed25519Group>>,
key: Option<Vec<u8>>,
}
impl Portal {
pub fn init(
direction: Direction,
id: String,
password: String,
) -> Result<Portal, Box<dyn Error>> {
let mut hasher = Sha256::new();
hasher.update(&id);
let id_bytes = hasher.finalize();
let id_hash = hex::encode(id_bytes);
let (s1, outbound_msg) = Spake2::<Ed25519Group>::start_symmetric(
&Password::new(password.as_bytes()),
&Identity::new(&id_bytes),
);
Ok(Portal {
direction,
id: id_hash,
exchange: outbound_msg.try_into().or(Err(CryptoError))?,
nseq: NonceSequence::new(),
state: Some(s1),
key: None,
})
}
pub fn handshake<P: Read + Write>(&mut self, peer: &mut P) -> Result<(), Box<dyn Error>> {
let confirm =
Protocol::connect(peer, &self.id, self.direction, self.exchange).or(Err(NoPeer))?;
let state = self.state.take().ok_or(BadState)?;
let key = Protocol::derive_key(state, &confirm).or(Err(BadMsg))?;
Protocol::confirm_peer(peer, &self.id, self.direction, &key)?;
self.key = Some(key);
Ok(())
}
pub fn outgoing<'a, W>(
&mut self,
peer: &mut W,
info: &'a TransferInfo,
) -> Result<impl Iterator<Item = (&'a PathBuf, &'a Metadata)>, Box<dyn Error>>
where
W: Write,
{
let key = self.key.as_ref().ok_or(NoPeer)?;
Protocol::encrypt_and_write_object(peer, key, &mut self.nseq, info)?;
Ok(info.localpaths.iter().zip(info.all.iter()))
}
pub fn incoming<R, V>(
&mut self,
peer: &mut R,
verify: Option<V>,
) -> Result<impl Iterator<Item = Metadata>, Box<dyn Error>>
where
R: Read,
V: Fn(&TransferInfo) -> bool,
{
let key = self.key.as_ref().ok_or(NoPeer)?;
let info: TransferInfo = Protocol::read_encrypted_from(peer, key)?;
match verify.as_ref().map_or(true, |c| c(&info)) {
true => {}
false => return Err(Cancelled.into()),
}
Ok(info.all.into_iter())
}
pub fn send_file<W, D>(
&mut self,
peer: &mut W,
path: &PathBuf,
callback: Option<D>,
) -> Result<usize, Box<dyn Error>>
where
W: Write,
D: Fn(usize),
{
let key = self.key.as_ref().ok_or(NoPeer)?;
let filename = path
.file_name()
.ok_or(BadFileName)?
.to_str()
.ok_or(BadFileName)?;
let mut mmap = self.map_readable_file(path)?;
let metadata = Metadata {
filesize: mmap.len() as u64,
filename: filename.to_string(),
};
Protocol::encrypt_and_write_object(peer, key, &mut self.nseq, &metadata)?;
let mut total_sent = 0;
for chunk in mmap[..].chunks_mut(CHUNK_SIZE) {
Protocol::encrypt_and_write_header_only(peer, key, &mut self.nseq, chunk)?;
peer.write_all(chunk)?;
total_sent += chunk.len();
if let Some(c) = callback.as_ref() {
c(total_sent);
}
}
Ok(total_sent)
}
pub fn recv_file<R, D>(
&mut self,
peer: &mut R,
outdir: &Path,
expected: Option<&Metadata>,
display: Option<D>,
) -> Result<Metadata, Box<dyn Error>>
where
R: Read,
D: Fn(usize),
{
let key = self.key.as_ref().ok_or(NoPeer)?;
if !outdir.is_dir() {
return Err(BadDirectory.into());
}
let metadata: Metadata = Protocol::read_encrypted_from(peer, key)?;
if expected.map_or(false, |exp| metadata != *exp) {
return Err(BadMsg.into());
}
let path = match Path::new(&metadata.filename).file_name() {
Some(s) => outdir.join(s),
_ => return Err(BadFileName.into()),
};
let mut mmap = self.map_writeable_file(&path, metadata.filesize)?;
let mut total = 0;
for chunk in mmap[..].chunks_mut(CHUNK_SIZE) {
Protocol::read_encrypted_zero_copy(peer, key, chunk)?;
total += chunk.len();
if let Some(c) = display.as_ref() {
c(total);
}
}
if total != metadata.filesize as usize {
return Err(Incomplete.into());
}
Ok(metadata)
}
fn map_readable_file(&self, f: &PathBuf) -> Result<MmapMut, Box<dyn Error>> {
let file = File::open(f)?;
let mmap = unsafe { MmapOptions::new().map_copy(&file)? };
Ok(mmap)
}
fn map_writeable_file(&self, f: &PathBuf, size: u64) -> Result<MmapMut, Box<dyn Error>> {
let file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(f)?;
file.set_len(size)?;
let mmap = unsafe { MmapOptions::new().map_mut(&file)? };
Ok(mmap)
}
pub fn get_direction(&self) -> Direction {
self.direction
}
pub fn set_direction(&mut self, direction: Direction) {
self.direction = direction;
}
pub fn get_id(&self) -> &String {
&self.id
}
pub fn set_id(&mut self, id: String) {
self.id = id;
}
pub fn get_key(&self) -> &Option<Vec<u8>> {
&self.key
}
pub fn set_key(&mut self, key: Vec<u8>) {
self.key = Some(key);
}
}