parent
b20878a633
commit
f22bda2f9a
@ -1,2 +1,3 @@
|
|||||||
/target
|
/target
|
||||||
*~
|
*~
|
||||||
|
src-old/
|
||||||
|
@ -1,85 +0,0 @@
|
|||||||
//! Arg parsing and process info
|
|
||||||
use super::*;
|
|
||||||
use std::fmt;
|
|
||||||
|
|
||||||
lazy_static!{
|
|
||||||
static ref EXEC: String = std::env::args().next().unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn program_name() -> &'static str
|
|
||||||
{
|
|
||||||
&EXEC[..]
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Program usage
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Usage;
|
|
||||||
|
|
||||||
impl Usage
|
|
||||||
{
|
|
||||||
pub fn print_and_exit(self, code: i32) -> !
|
|
||||||
{
|
|
||||||
if code == 0 {
|
|
||||||
print!("{}", self);
|
|
||||||
} else {
|
|
||||||
eprint!("{}", self);
|
|
||||||
}
|
|
||||||
std::process::exit(code)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn splash(f: &mut fmt::Formatter<'_>) -> fmt::Result
|
|
||||||
{
|
|
||||||
writeln!(f, "transfer v{} - simple network file transfer", env!("CARGO_PKG_VERSION"))?;
|
|
||||||
writeln!(f, " written by {} with <3. License GPL3+", env!("CARGO_PKG_AUTHORS"))?;
|
|
||||||
writeln!(f, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Usage
|
|
||||||
{
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
|
||||||
{
|
|
||||||
splash(f)?;
|
|
||||||
|
|
||||||
writeln!(f, "Usage: {} S <bind> --send|--recv [OPTIONS] <file...>", program_name())?;
|
|
||||||
writeln!(f, "Usage: {} C <connect> --send|--recv [OPTIONS] <output...>", program_name())?;
|
|
||||||
writeln!(f, "Usage: {} --help", program_name())?;
|
|
||||||
writeln!(f, "\nNetworking mode:")?;
|
|
||||||
writeln!(f, " S: Server mode. Bind to an address/port")?;
|
|
||||||
writeln!(f, " C: Client mode. Connect to a listening address/port")?;
|
|
||||||
writeln!(f, "\nSEND OPTIONS:")?;
|
|
||||||
writeln!(f, " -e\t\t\tEncrypt file(s)")?;
|
|
||||||
writeln!(f, " -c\t\t\tCompress files")?;
|
|
||||||
writeln!(f, " --buffer-size <bytes>\tSize of file buffer")?;
|
|
||||||
writeln!(f, " -a\t\t\tSend file names")?;
|
|
||||||
writeln!(f, " -k\t\t\tSupport continuation of failed downloads")?;
|
|
||||||
|
|
||||||
writeln!(f, "\nRECV OPTIONS:")?;
|
|
||||||
writeln!(f, " -i\t\t\tAsk before starting downloads")?;
|
|
||||||
writeln!(f, " -k\t\t\tContinue a previously started download")?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The process parsed from command line
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
pub struct Process
|
|
||||||
{
|
|
||||||
/// The parsed config (includes mode)
|
|
||||||
pub config: config::Config,
|
|
||||||
/// The listed paths
|
|
||||||
pub paths: Vec<String>,
|
|
||||||
/// Use stdin/out
|
|
||||||
pub stdio: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An operation parsed from command line arguments
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
pub enum Op
|
|
||||||
{
|
|
||||||
Process(Box<Process>),
|
|
||||||
Help,
|
|
||||||
}
|
|
||||||
|
|
||||||
mod parse;
|
|
@ -1,96 +0,0 @@
|
|||||||
//! Parsing args
|
|
||||||
use super::*;
|
|
||||||
use ext::*;
|
|
||||||
use std::iter;
|
|
||||||
|
|
||||||
/// Arg state
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
struct State
|
|
||||||
{
|
|
||||||
is_server: bool,
|
|
||||||
is_sending: bool,
|
|
||||||
|
|
||||||
enc: Option<bool>,
|
|
||||||
comp: Option<bool>,
|
|
||||||
bufsz: Option<usize>,
|
|
||||||
arc: Option<bool>,
|
|
||||||
contin: Option<bool>,
|
|
||||||
oneshot: Option<bool>,
|
|
||||||
|
|
||||||
inter: Option<bool>,
|
|
||||||
|
|
||||||
files: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl State
|
|
||||||
{
|
|
||||||
fn mode(&self) -> impl fmt::Display + 'static
|
|
||||||
{
|
|
||||||
let send = r#if!(self.is_sending, "send", "recv");
|
|
||||||
let serve = r#if!(self.is_server, "server", "client");
|
|
||||||
lazy_format!("{} ({})", send, serve)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_schain<I>(state: &mut State, single: I) -> eyre::Result<()>
|
|
||||||
where I: IntoIterator<Item=char>
|
|
||||||
{
|
|
||||||
for ch in single.into_iter().map(char::to_lowercase).flatten()
|
|
||||||
{
|
|
||||||
match ch {
|
|
||||||
'e' => state.enc = Some(true),
|
|
||||||
'c' => state.comp = Some(true),
|
|
||||||
'a' => state.arc = Some(true),
|
|
||||||
'k' => state.contin = Some(true),
|
|
||||||
'1' if state.is_server => state.oneshot = Some(true),
|
|
||||||
'i' if !state.is_sending => state.inter = Some(true),
|
|
||||||
x => return Err(eyre!("Unknown option for mode {}", state.mode()))
|
|
||||||
.with_section(move || x.header("Option was"))
|
|
||||||
.with_note(move || "Some options are only valid for certain modes"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Try to parse an iterator of strings (usually the command-line arguments) into an `Op`.
|
|
||||||
pub fn parse_iter<I>(args: &mut I) -> eyre::Result<Op>
|
|
||||||
where I: Iterator<Item= String> + ?Sized
|
|
||||||
{
|
|
||||||
let state = parse_iter_raw(args)
|
|
||||||
.wrap_err(eyre!("Invalid arguments"))
|
|
||||||
.with_suggestion(|| "Try passing `--help`")?; // Send help message here, since it's unlikely to be helpful when returning from state's validation compared to here.
|
|
||||||
|
|
||||||
todo!("TODO: `impl TryFrom<State> for Op`, etc")
|
|
||||||
}
|
|
||||||
fn parse_iter_raw<I>(args: &mut I) -> eyre::Result<State>
|
|
||||||
where I: Iterator<Item= String> + ?Sized
|
|
||||||
{
|
|
||||||
let mut state = State::default();
|
|
||||||
//TODO: Parse modes before this.
|
|
||||||
while let Some(arg) = args.next()
|
|
||||||
{
|
|
||||||
let mut chars = arg.chars();
|
|
||||||
match (&mut chars).take(2).collect_array::<2>() {
|
|
||||||
['-', '-'] => {
|
|
||||||
// Long option
|
|
||||||
let opt = &arg[2..];
|
|
||||||
match opt {
|
|
||||||
"--" => break,
|
|
||||||
//TODO: Long options, pulling option param from `args` if needed, etc.
|
|
||||||
unknown => return Err(eyre!("Unknown option for mode {}", state.mode()))
|
|
||||||
.with_section(|| format!("--{}", unknown).header("Option was"))
|
|
||||||
.with_note(|| "Some options are only valid for certain modes"),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
['-', n] => {
|
|
||||||
// Small option
|
|
||||||
parse_schain(&mut state, iter::once(n).chain(chars))?;
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
// Not an option
|
|
||||||
state.files.push(arg);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(state)
|
|
||||||
}
|
|
@ -1,40 +0,0 @@
|
|||||||
|
|
||||||
use openssl::{
|
|
||||||
symm::{
|
|
||||||
Cipher, Crypter, Mode,
|
|
||||||
},
|
|
||||||
error::ErrorStack,
|
|
||||||
};
|
|
||||||
use crate::key::{Key, IV};
|
|
||||||
|
|
||||||
/// Size of the key used for the cipher
|
|
||||||
pub const KEY_SIZE: usize = 32;
|
|
||||||
/// Size of the IV used for the cipher
|
|
||||||
pub const IV_SIZE: usize = 12;
|
|
||||||
|
|
||||||
static NEW_CIPHER: fn() -> Cipher = Cipher::chacha20_poly1305;
|
|
||||||
|
|
||||||
#[inline] pub fn decrypter(key: impl AsRef<Key>, iv: impl AsRef<IV>) -> Result<Crypter, ErrorStack>
|
|
||||||
{
|
|
||||||
Crypter::new(
|
|
||||||
NEW_CIPHER(),
|
|
||||||
Mode::Decrypt,
|
|
||||||
key.as_ref().as_ref(),
|
|
||||||
Some(iv.as_ref().as_ref())
|
|
||||||
)
|
|
||||||
}
|
|
||||||
#[inline] pub fn encrypter(key: impl AsRef<Key>, iv: impl AsRef<IV>) -> Result<Crypter, ErrorStack>
|
|
||||||
{
|
|
||||||
Crypter::new(
|
|
||||||
NEW_CIPHER(),
|
|
||||||
Mode::Encrypt,
|
|
||||||
key.as_ref().as_ref(),
|
|
||||||
Some(iv.as_ref().as_ref())
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generate a random key and IV for the chacha20_poly1305 cipher
|
|
||||||
#[inline(always)] pub fn keygen() -> (Key, IV)
|
|
||||||
{
|
|
||||||
(Key::new(), IV::new())
|
|
||||||
}
|
|
@ -1,93 +1,103 @@
|
|||||||
//! Configuration
|
//! Configuration
|
||||||
use super::*;
|
use super::*;
|
||||||
use std::net::SocketAddr;
|
use sock::SocketAddr;
|
||||||
|
|
||||||
pub const DEFAULT_BUFFER_SIZE: usize = 4096;
|
pub const DEFAULT_BUFFER_SIZE: usize = 4096;
|
||||||
|
|
||||||
/// Configuration for sending
|
/// What kind of compression to use
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Copy)]
|
||||||
pub struct SendConfig
|
#[repr(u8)]
|
||||||
|
pub enum CompressionKind
|
||||||
{
|
{
|
||||||
encrypt: bool,
|
Brotli,
|
||||||
compress: bool,
|
GZip,
|
||||||
buffer_size: usize,
|
BZ2,
|
||||||
archive: bool,
|
|
||||||
//oneshot: bool, // Server specific
|
|
||||||
continuation: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for SendConfig
|
// -- serve / conn --
|
||||||
{
|
|
||||||
#[inline]
|
/// Server configuration
|
||||||
fn default() -> Self
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub struct ServerConfig
|
||||||
{
|
{
|
||||||
Self {
|
pub bind: SocketAddr,
|
||||||
encrypt: false,
|
pub oneshot: bool,
|
||||||
compress: false,
|
|
||||||
buffer_size: DEFAULT_BUFFER_SIZE,
|
|
||||||
archive: false,
|
|
||||||
//oneshot: false,
|
|
||||||
continuation: false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Client configuration
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub struct ClientConfig
|
||||||
|
{
|
||||||
|
pub connect: SocketAddr,
|
||||||
|
pub retry: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configuration for receiving
|
// -- send / recv ---
|
||||||
|
|
||||||
|
/// Specifying a sending name, or send the filename
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
pub struct RecvConfig
|
pub enum SendingName
|
||||||
{
|
{
|
||||||
interactive: bool,
|
Filename,
|
||||||
continuation: bool,
|
Specific(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for RecvConfig
|
impl Default for SendingName
|
||||||
{
|
{
|
||||||
#[inline]
|
#[inline]
|
||||||
fn default() -> Self
|
fn default() -> Self
|
||||||
{
|
{
|
||||||
Self {
|
Self::Filename
|
||||||
interactive: false,
|
|
||||||
continuation: false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Instructions for binding (server) mode
|
/// Configuration for sending file
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
pub struct Server
|
pub struct SendConfig
|
||||||
{
|
{
|
||||||
listen: SocketAddr, //TODO: Allow multiple?
|
pub encrypt: bool,
|
||||||
|
pub sign: bool,
|
||||||
|
pub compress: Option<CompressionKind>,
|
||||||
|
pub buffer_size: usize,
|
||||||
|
pub continuation: bool,
|
||||||
|
|
||||||
|
pub name: Option<SendingName>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Instructions for connecting (client) mode
|
/// Configuration for receiving file
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
pub struct Client
|
pub struct RecvConfig
|
||||||
{
|
{
|
||||||
connect: SocketAddr,
|
pub interactive: bool,
|
||||||
|
pub name: SendingName,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A send or recv operation
|
// -- modes --
|
||||||
|
|
||||||
|
/// Mode of file
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
pub enum Operation
|
pub enum TransferMode
|
||||||
{
|
{
|
||||||
Send(SendConfig),
|
Send(SendConfig),
|
||||||
Recv(RecvConfig),
|
Recv(RecvConfig),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether to serve (listen) or connect directly.
|
/// Configuration of connection
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
pub enum Mode
|
pub enum ConnectionMode
|
||||||
{
|
{
|
||||||
Server(Server),
|
Server(ServerConfig),
|
||||||
Client(Client),
|
Client(ClientConfig),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Program full configuration
|
// --
|
||||||
|
|
||||||
|
/// Full program configuration
|
||||||
|
///
|
||||||
|
/// You should box this, it's big.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
pub struct Config
|
pub struct Config
|
||||||
{
|
{
|
||||||
/// Which operation (send/recv) are we performing?
|
pub file: TransferMode,
|
||||||
pub op: Operation,
|
pub connection: ConnectionMode,
|
||||||
/// How are we performing it? (Bind/connect)
|
|
||||||
pub mode: Mode,
|
|
||||||
}
|
}
|
||||||
|
@ -1,43 +0,0 @@
|
|||||||
//! Encodings
|
|
||||||
use super::*;
|
|
||||||
use ext::*;
|
|
||||||
use std::{fmt, error};
|
|
||||||
use bytes::{
|
|
||||||
Buf,
|
|
||||||
Bytes,
|
|
||||||
};
|
|
||||||
use std::io;
|
|
||||||
use tokio::io::{
|
|
||||||
AsyncRead, AsyncWrite,
|
|
||||||
AsyncReadExt, AsyncWriteExt,
|
|
||||||
};
|
|
||||||
use serde::{
|
|
||||||
Serialize,
|
|
||||||
de::DeserializeOwned
|
|
||||||
};
|
|
||||||
use cryptohelpers::{
|
|
||||||
sha256,
|
|
||||||
rsa,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Size of buffer to use when copying a stream.
|
|
||||||
pub const STREAMING_BUFFER_SIZE: usize = 4096;
|
|
||||||
|
|
||||||
pub mod ser;
|
|
||||||
|
|
||||||
/// Copy `from` to `to`, transforming the data with the provided key and IV.
|
|
||||||
///
|
|
||||||
/// # Stream cipher usage
|
|
||||||
/// The copy is buffered by `STREAMING_BUFFER_SIZE` bytes, and the cipher applied to each read buffer.
|
|
||||||
/// If the buffer cannot be filled (because the stream reached EOF before filling it), then only the full portion of the buffer is transformed and written.
|
|
||||||
#[inline] pub async fn cc20_copy_stream<F, T, K>(from: &mut F, to: &mut T, keys: K, decrypt: bool) -> io::Result<(usize, usize)>
|
|
||||||
where K: key::CC20Key,
|
|
||||||
F: AsyncRead + Unpin + ?Sized,
|
|
||||||
T: AsyncWrite + Unpin + ?Sized
|
|
||||||
{
|
|
||||||
if decrypt {
|
|
||||||
ser::cha_copy::<F, T, STREAMING_BUFFER_SIZE, true>(from, to, keys.key(), keys.iv()).await
|
|
||||||
} else {
|
|
||||||
ser::cha_copy::<F, T, STREAMING_BUFFER_SIZE, false>(from, to, keys.key(), keys.iv()).await
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,741 +0,0 @@
|
|||||||
//! Data serialisation
|
|
||||||
use super::*;
|
|
||||||
use bytes::BufMut;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy, PartialOrd, Ord)]
|
|
||||||
pub enum CompressionKind
|
|
||||||
{
|
|
||||||
Brotli,
|
|
||||||
|
|
||||||
//TODO: Add cases (and async_compression features) for these three
|
|
||||||
Xz,
|
|
||||||
GZip,
|
|
||||||
Bz2,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for CompressionKind
|
|
||||||
{
|
|
||||||
#[inline]
|
|
||||||
fn default() -> Self
|
|
||||||
{
|
|
||||||
//TODO: Should Brotli be default? Check sizes of compressed binary encoded stuffs and compare modes.
|
|
||||||
Self::Brotli
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy)]
|
|
||||||
pub enum EncryptionKind
|
|
||||||
{
|
|
||||||
Chacha20((key::Key, key::IV))
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for EncryptionKind
|
|
||||||
{
|
|
||||||
#[inline]
|
|
||||||
fn default() -> Self
|
|
||||||
{
|
|
||||||
Self::Chacha20(cha::keygen())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy)]
|
|
||||||
pub enum SerialFormat
|
|
||||||
{
|
|
||||||
/// CBOR
|
|
||||||
Binary,
|
|
||||||
/// JSON
|
|
||||||
Text,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for SerialFormat
|
|
||||||
{
|
|
||||||
#[inline]
|
|
||||||
fn default() -> Self
|
|
||||||
{
|
|
||||||
Self::Binary
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy)]
|
|
||||||
pub struct SendOpt
|
|
||||||
{
|
|
||||||
comp: Option<CompressionKind>,
|
|
||||||
encrypt: Option<EncryptionKind>,
|
|
||||||
format: SerialFormat,
|
|
||||||
hash: bool,
|
|
||||||
//pub sign: Option<???>, //TODO: RSA private + public key types
|
|
||||||
}
|
|
||||||
ref_self!(SendOpt);
|
|
||||||
|
|
||||||
impl Default for SendOpt
|
|
||||||
{
|
|
||||||
#[inline]
|
|
||||||
fn default() -> Self
|
|
||||||
{
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
impl SendOpt
|
|
||||||
{
|
|
||||||
pub const NORMAL: Self = Self::new();
|
|
||||||
pub const CHECKED: Self = Self::new_checked();
|
|
||||||
pub const COMPRESSED: Self = Self::new_compressed();
|
|
||||||
|
|
||||||
/// Add compression
|
|
||||||
pub const fn compress(self, k: CompressionKind) -> Self
|
|
||||||
{
|
|
||||||
Self {
|
|
||||||
comp: Some(k),
|
|
||||||
..self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Change the output format
|
|
||||||
///
|
|
||||||
/// Default: **Binary**
|
|
||||||
///
|
|
||||||
/// # Text format note
|
|
||||||
/// When using compression and/or encryption, the text format will end up unreadable anyway.
|
|
||||||
/// Likewise when using signing or hashing, a binary header is prepended to the message regardless of format.
|
|
||||||
///
|
|
||||||
/// 2 ASCII whitespace characters are prepended to the message regardless of any other options (`\t`, ` |\n`). These are used to determine if the message is valid and if a header needs to be read from it.
|
|
||||||
/// Most external text-format parsing software should ignore these and be able to parse a non-headered message.
|
|
||||||
pub const fn format(self, format: SerialFormat) -> Self
|
|
||||||
{
|
|
||||||
Self {
|
|
||||||
format,
|
|
||||||
..self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Enable or disable hashing
|
|
||||||
///
|
|
||||||
/// Default: *Disabled*
|
|
||||||
pub const fn hash(self, hash: bool) -> Self
|
|
||||||
{
|
|
||||||
Self {
|
|
||||||
hash,
|
|
||||||
..self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add encryption with constant parameters
|
|
||||||
pub const fn encrypt(self, k: EncryptionKind) -> Self
|
|
||||||
{
|
|
||||||
Self {
|
|
||||||
encrypt: Some(k),
|
|
||||||
..self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add default encryption with a randomly generated key and IV.
|
|
||||||
pub fn encrypt_cc20_gen(self) -> Self
|
|
||||||
{
|
|
||||||
self.encrypt(EncryptionKind::Chacha20(cha::keygen()))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Normal options.
|
|
||||||
///
|
|
||||||
/// Does not enable any features.
|
|
||||||
pub const fn new() -> Self
|
|
||||||
{
|
|
||||||
Self {
|
|
||||||
comp: None,
|
|
||||||
encrypt: None,
|
|
||||||
format: SerialFormat::Binary,
|
|
||||||
hash: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Normal options with data compression.
|
|
||||||
///
|
|
||||||
/// Uses Brotli compression by default.
|
|
||||||
pub const fn new_compressed() -> Self
|
|
||||||
{
|
|
||||||
Self {
|
|
||||||
comp: Some(CompressionKind::Brotli),
|
|
||||||
..Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Normal options with added integrity checks.
|
|
||||||
///
|
|
||||||
/// Increases final size of object but provided data integrity and source validation.
|
|
||||||
//TODO: Give sig param
|
|
||||||
pub const fn new_checked() -> Self
|
|
||||||
{
|
|
||||||
Self {
|
|
||||||
hash: true,
|
|
||||||
//sig: ???
|
|
||||||
..Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Should a header be generated for this data?
|
|
||||||
#[inline(always)] fn needs_header(&self) -> bool
|
|
||||||
{
|
|
||||||
self.hash || /*self.sig*/ false
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline] fn creates_header(&self) -> bool
|
|
||||||
{
|
|
||||||
self.needs_header()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Does the binary data of this format require special handling?
|
|
||||||
///
|
|
||||||
/// True if encryption and/or compression are specified.
|
|
||||||
#[inline(always)] fn is_spec(&self) -> bool
|
|
||||||
{
|
|
||||||
self.comp.is_some() || self.encrypt.is_some()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type RecvOpt = SendOpt;
|
|
||||||
|
|
||||||
/// Default buffer size for encryption transform stream copying.
|
|
||||||
pub const DEFAULT_BUFSIZE: usize = 4096;
|
|
||||||
|
|
||||||
pub(super) async fn cha_copy<F, T, const BUFSIZE: usize, const DECRYPT: bool>(from: &mut F, to: &mut T, key: &key::Key, iv: &key::IV) -> io::Result<(usize, usize)>
|
|
||||||
where F: AsyncRead + Unpin + ?Sized,
|
|
||||||
T: AsyncWrite + Unpin + ?Sized
|
|
||||||
{
|
|
||||||
let mut written=0;
|
|
||||||
let mut read=0;
|
|
||||||
let mut r;
|
|
||||||
let mut buffer = [0u8; BUFSIZE];
|
|
||||||
let mut cbuffer = [0u8; BUFSIZE];
|
|
||||||
|
|
||||||
let mut crypter = if DECRYPT {
|
|
||||||
cha::decrypter(key, iv)
|
|
||||||
} else {
|
|
||||||
cha::encrypter(key, iv)
|
|
||||||
}?;
|
|
||||||
|
|
||||||
while { r = from.read(&mut buffer[..]).await?; r > 0 } {
|
|
||||||
read += r;
|
|
||||||
r = crypter.update(&buffer[..r], &mut cbuffer[..])?;
|
|
||||||
to.write(&cbuffer[..r]).await?;
|
|
||||||
written += r;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok((written, read))
|
|
||||||
}
|
|
||||||
|
|
||||||
const H_SALT_SIZE: usize = 32;
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
|
|
||||||
struct FormatHeader
|
|
||||||
{
|
|
||||||
hash: Option<(sha256::Sha256Hash, [u8; H_SALT_SIZE])>,
|
|
||||||
sig: Option<rsa::Signature>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum HeaderValidationError
|
|
||||||
{
|
|
||||||
Malformed,
|
|
||||||
Hash,
|
|
||||||
Signature,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl error::Error for HeaderValidationError{}
|
|
||||||
impl fmt::Display for HeaderValidationError
|
|
||||||
{
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
|
||||||
{
|
|
||||||
match self {
|
|
||||||
Self::Malformed => write!(f, "header was malformed"),
|
|
||||||
Self::Hash => write!(f, "invalid hash"),
|
|
||||||
Self::Signature => write!(f, "signature could not be verified"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
impl FormatHeader
|
|
||||||
{
|
|
||||||
pub const SIZE: usize = sha256::SIZE + H_SALT_SIZE + cryptohelpers::consts::RSA_SIG_SIZE + 2;
|
|
||||||
const fn empty_array() -> [u8; Self::SIZE]
|
|
||||||
{
|
|
||||||
[0u8; Self::SIZE]
|
|
||||||
}
|
|
||||||
fn gen_salt() -> [u8; H_SALT_SIZE]
|
|
||||||
{
|
|
||||||
let mut out = [0u8; H_SALT_SIZE];
|
|
||||||
getrandom::getrandom(&mut out[..]).expect("rng fatal");
|
|
||||||
out
|
|
||||||
}
|
|
||||||
fn generate(data: impl AsRef<[u8]>, opt: &SendOpt) -> Self
|
|
||||||
{
|
|
||||||
let hash = if opt.hash {
|
|
||||||
let salt = Self::gen_salt();
|
|
||||||
Some((sha256::compute_slices(iter![data.as_ref(), &salt[..]]), salt))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
let sig = if false /*let Some(sign_with) = opt.sign*/ {
|
|
||||||
unimplemented!()
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
Self {
|
|
||||||
hash,
|
|
||||||
sig //TODO
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn validate(&self, data: impl AsRef<[u8]>, opt: &RecvOpt) -> Result<(), HeaderValidationError>
|
|
||||||
{
|
|
||||||
if opt.hash {
|
|
||||||
if !self.hash.as_ref().map(|(hash, salt)| &sha256::compute_slices(iter![data.as_ref(), &salt[..]]) == hash).unwrap_or(true) {
|
|
||||||
return Err(HeaderValidationError::Hash);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if /*opt.sig*/ false {
|
|
||||||
unimplemented!();
|
|
||||||
//if let Some(verify_with) = opt.sig //XXX: How will this work? We will need to store **either** a private or public key in Send/RecvOpt and dynamically dispatch over it.
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
fn to_buffer(&self, mut to: impl BufMut)
|
|
||||||
{
|
|
||||||
if let Some(hash) = &self.hash
|
|
||||||
{
|
|
||||||
to.put_u8(1);
|
|
||||||
to.put_slice(hash.0.as_ref());
|
|
||||||
to.put_slice(hash.1.as_ref());
|
|
||||||
} else {
|
|
||||||
to.put_u8(0);
|
|
||||||
to.put_bytes(0, sha256::SIZE + H_SALT_SIZE);
|
|
||||||
}
|
|
||||||
if let Some(sig) = &self.sig
|
|
||||||
{
|
|
||||||
to.put_u8(1);
|
|
||||||
to.put_slice(sig.as_ref());
|
|
||||||
} else {
|
|
||||||
to.put_u8(0);
|
|
||||||
to.put_bytes(0, cryptohelpers::consts::RSA_SIG_SIZE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn from_buffer(mut from: impl Buf) -> Self
|
|
||||||
{
|
|
||||||
let hash = if from.get_u8() == 1 {
|
|
||||||
let mut hash = sha256::Sha256Hash::default();
|
|
||||||
let mut salt = [0u8; H_SALT_SIZE];
|
|
||||||
from.copy_to_slice(hash.as_mut());
|
|
||||||
from.copy_to_slice(&mut salt[..]);
|
|
||||||
Some((hash,salt))
|
|
||||||
} else {
|
|
||||||
from.advance(sha256::SIZE + H_SALT_SIZE);
|
|
||||||
None
|
|
||||||
};
|
|
||||||
let sig = if from.get_u8() == 1 {
|
|
||||||
let mut sig = rsa::Signature::default();
|
|
||||||
from.copy_to_slice(sig.as_mut());
|
|
||||||
Some(sig)
|
|
||||||
} else {
|
|
||||||
from.advance(sha256::SIZE);
|
|
||||||
None
|
|
||||||
};
|
|
||||||
Self {
|
|
||||||
hash, sig
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[inline] fn to_array(&self) -> [u8; Self::SIZE]
|
|
||||||
{
|
|
||||||
let mut ar = [0u8; Self::SIZE];
|
|
||||||
self.to_buffer(&mut &mut ar[..]);
|
|
||||||
ar
|
|
||||||
}
|
|
||||||
#[inline] fn from_array(ar: [u8; Self::SIZE]) -> Self
|
|
||||||
{
|
|
||||||
Self::from_buffer(&ar[..])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const INFO_ASSERT_VALID: u8 = b'\t';
|
|
||||||
const INFO_WITH_HEADER: u8 = b' ';
|
|
||||||
const INFO_NO_HEADER: u8 = b'\n';
|
|
||||||
|
|
||||||
/// If passing an externally generated message to be deserialised here, it must be prefixed with this regardless of its format.
|
|
||||||
///
|
|
||||||
/// Operations that generate/require a message header will not work on these messages and if they are needed must be handled elsewhere by the user. (Hash and signature validation)
|
|
||||||
pub const BARE_MESSAGE_PREFIX: [u8; 2] = [INFO_ASSERT_VALID, INFO_NO_HEADER];
|
|
||||||
|
|
||||||
pub(super) async fn de_singleton_inner<T: DeserializeOwned, B, F>(buf: F, from: &[u8], how: &RecvOpt) -> Result<T, TransformErrorKind>
|
|
||||||
where B: AsRef<[u8]> + AsyncWrite + Unpin + Default,
|
|
||||||
F: FnOnce(&[u8]) -> B
|
|
||||||
{
|
|
||||||
|
|
||||||
// Read header
|
|
||||||
let mut header = FormatHeader::empty_array();
|
|
||||||
if from.len() < 2 || from[0] != INFO_ASSERT_VALID {
|
|
||||||
return Err(TransformErrorKind::InvalidHeader(HeaderValidationError::Malformed));
|
|
||||||
}
|
|
||||||
let (inf, mut from) = {
|
|
||||||
(&from[..2], &from[2..])
|
|
||||||
};
|
|
||||||
from = {
|
|
||||||
if inf[1] == INFO_WITH_HEADER {
|
|
||||||
if from.len() < FormatHeader::SIZE {
|
|
||||||
return Err(TransformErrorKind::InvalidHeader(HeaderValidationError::Malformed));
|
|
||||||
}
|
|
||||||
let hf = &from[..FormatHeader::SIZE];
|
|
||||||
header.copy_from_slice(hf);
|
|
||||||
&from[FormatHeader::SIZE..]
|
|
||||||
} else {
|
|
||||||
&from[..]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// Decompressor
|
|
||||||
// The output is written to this (through writer)
|
|
||||||
let mut is_spec = false; // This is set later. The value will sometimes differ from `how.is_spec()` depending on combinations of options.
|
|
||||||
// The `spec` output buffer. Used if there are transformations that need to be done to the data before deserialisation
|
|
||||||
let mut buf = if how.is_spec() {
|
|
||||||
buf(&from)
|
|
||||||
} else {
|
|
||||||
Default::default()
|
|
||||||
};
|
|
||||||
//let mut buf = Vec::with_capacity(from.len());
|
|
||||||
from = {
|
|
||||||
let mut b;
|
|
||||||
let writer: &mut (dyn AsyncWrite + Unpin) =
|
|
||||||
if let Some(comp) = &how.comp {
|
|
||||||
is_spec = true;
|
|
||||||
match comp {
|
|
||||||
CompressionKind::Brotli => {
|
|
||||||
b = async_compression::tokio::write::BrotliDecoder::new(&mut buf);
|
|
||||||
&mut b
|
|
||||||
},
|
|
||||||
_ => unimplemented!(),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
&mut buf
|
|
||||||
};
|
|
||||||
// Decrypt into `writer`.
|
|
||||||
|
|
||||||
if let Some(dec) = &how.encrypt {
|
|
||||||
// There is decryption to be done, decrypt into `writer` (which will handle decompression if needed).
|
|
||||||
// Return its output buffer
|
|
||||||
match dec {
|
|
||||||
EncryptionKind::Chacha20((k, iv)) => {
|
|
||||||
self::cha_copy::<_, _, DEFAULT_BUFSIZE, true>(&mut &from[..], writer, k, iv).await?;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
// Required for decompression to complete
|
|
||||||
writer.flush().await?;
|
|
||||||
writer.shutdown().await?;
|
|
||||||
|
|
||||||
&buf.as_ref()[..]
|
|
||||||
} else if is_spec {
|
|
||||||
// There is decompression to be done through `writer`. Return its output buffer
|
|
||||||
writer.write_all(from).await?;
|
|
||||||
|
|
||||||
// Required for decompression to complete
|
|
||||||
writer.flush().await?;
|
|
||||||
writer.shutdown().await?;
|
|
||||||
|
|
||||||
&buf.as_ref()[..]
|
|
||||||
} else {
|
|
||||||
// There is neither decompression nor decryption to be done, return the input reference itself
|
|
||||||
from
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// Deserialise
|
|
||||||
|
|
||||||
FormatHeader::from_array(header).validate(from, how)?;
|
|
||||||
|
|
||||||
let v = match how.format {
|
|
||||||
SerialFormat::Text => serde_json::from_slice(&from[..])?,
|
|
||||||
SerialFormat::Binary => serde_cbor::from_slice(&from[..])?,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) async fn ser_singleton_inner<T: Serialize, V: AsyncWrite + Unpin, F>(to: F, value: &T, how: impl AsRef<SendOpt>) -> Result<(V, usize), TransformErrorKind>
|
|
||||||
where F: FnOnce(&Vec<u8>) -> V,
|
|
||||||
{
|
|
||||||
let how = how.as_ref();
|
|
||||||
let ser = match how.format {
|
|
||||||
SerialFormat::Text => serde_json::to_vec(value)?,
|
|
||||||
SerialFormat::Binary => serde_cbor::to_vec(value)?,
|
|
||||||
};
|
|
||||||
let header = if how.needs_header() {
|
|
||||||
let header = FormatHeader::generate(&ser, how);
|
|
||||||
header.to_array()
|
|
||||||
} else {
|
|
||||||
FormatHeader::empty_array()
|
|
||||||
};
|
|
||||||
let mut a;
|
|
||||||
let mut b;
|
|
||||||
let reader: &mut (dyn AsyncRead + Unpin) =
|
|
||||||
if let Some(comp) = &how.comp {
|
|
||||||
match comp {
|
|
||||||
CompressionKind::Brotli => {
|
|
||||||
a = async_compression::tokio::bufread::BrotliEncoder::new(tokio::io::BufReader::new(&ser[..]));
|
|
||||||
&mut a
|
|
||||||
},
|
|
||||||
_ => unimplemented!("Xz and GZip currently unimplemented."),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
b = &ser[..];
|
|
||||||
&mut b
|
|
||||||
};
|
|
||||||
let mut ser = to(&ser);
|
|
||||||
if how.needs_header() {
|
|
||||||
ser.write_all(&[INFO_ASSERT_VALID, INFO_WITH_HEADER]).await?;
|
|
||||||
ser.write_all(&header[..]).await?;
|
|
||||||
} else {
|
|
||||||
ser.write_all(&[INFO_ASSERT_VALID, INFO_NO_HEADER]).await?;
|
|
||||||
}
|
|
||||||
let w= if let Some(enc) = &how.encrypt {
|
|
||||||
let n = match enc {
|
|
||||||
EncryptionKind::Chacha20((k, iv)) => {
|
|
||||||
self::cha_copy::<_, _, DEFAULT_BUFSIZE, false>(reader, &mut ser, k, iv).await?.0
|
|
||||||
},
|
|
||||||
};
|
|
||||||
// Required for compression to complete
|
|
||||||
ser.flush().await?;
|
|
||||||
ser.shutdown().await?;
|
|
||||||
n
|
|
||||||
} else {
|
|
||||||
tokio::io::copy(reader, &mut ser).await? as usize
|
|
||||||
};
|
|
||||||
Ok((ser, w))
|
|
||||||
// inner(value, how).map(|res| res.map_err(|k| SendError(Box::new((k, how.clone())))))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)] pub fn de_singleton<'a, T: DeserializeOwned + 'a, B: ?Sized + AsRef<[u8]> + 'a>(from: &'a B, how: &'a RecvOpt) -> impl Future<Output = Result<T, RecvError>> + 'a
|
|
||||||
{
|
|
||||||
use futures::prelude::*;
|
|
||||||
de_singleton_inner(|from| Vec::with_capacity(from.as_ref().len()), from.as_ref(), how)
|
|
||||||
.map_err(|k| RecvError(Box::new((k, how.clone()))))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)] pub fn ser_singleton<'a, T: Serialize>(value: &'a T, how: &'a SendOpt) -> impl Future<Output = Result<Vec<u8>, SendError>> + 'a
|
|
||||||
{
|
|
||||||
use futures::prelude::*;
|
|
||||||
// hack to avoid having to enable `try{}` feature :/
|
|
||||||
ser_singleton_inner(|c| Vec::with_capacity(c.len()), value, how)
|
|
||||||
.map_ok(|(v, _)| v)
|
|
||||||
.map_err(|k| SendError(Box::new((k, how.clone()))))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Deserialise a single object from a stream with the method described by `how`.
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
/// The deserialised value and the number of bytes read from the stream.
|
|
||||||
pub async fn read_singleton<T: DeserializeOwned, S: ?Sized + AsyncRead + Unpin>(from: &mut S, how: &RecvOpt) -> Result<(T, usize), RecvError>
|
|
||||||
{
|
|
||||||
let (r, v) = async move {
|
|
||||||
let mut ibuf = [0u8; std::mem::size_of::<u64>()];
|
|
||||||
from.read_exact(&mut ibuf[..]).await?;
|
|
||||||
let n = u64::from_be_bytes(ibuf);
|
|
||||||
let mut v = Vec::with_capacity(n as usize);
|
|
||||||
tokio::io::copy(&mut from.take(n), &mut v).await
|
|
||||||
.map(move |_| (v.len() + ibuf.len(), v))
|
|
||||||
}.await
|
|
||||||
.map_err(|err| RecvError(Box::new((err.into(), how.to_owned()))))?;
|
|
||||||
let v = de_singleton(&v[..], how).await?;
|
|
||||||
Ok((v, r))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Serialise a single object to a stream with the method described by `how`.
|
|
||||||
#[inline] pub async fn write_singleton<T: Serialize, S: ?Sized + AsyncWrite + Unpin>(to: &mut S, value: &T, how: &SendOpt) -> Result<usize, SendError>
|
|
||||||
{
|
|
||||||
let (cont, v) = ser_singleton_inner(|n| Vec::with_capacity(n.len()), value, &how).await
|
|
||||||
.map_err(|k| SendError(Box::new((k, how.to_owned()))))?;
|
|
||||||
|
|
||||||
|
|
||||||
let n = async move {
|
|
||||||
to.write_all(&(v as u64).to_be_bytes()[..]).await?;
|
|
||||||
to.write_all(&cont).await
|
|
||||||
.map(|_| std::mem::size_of::<u64>() + cont.len())
|
|
||||||
}
|
|
||||||
.await
|
|
||||||
.map_err(|k| SendError(Box::new((k.into(), how.to_owned()))))?;
|
|
||||||
|
|
||||||
|
|
||||||
Ok(n)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Kind of error for a send (serialise) or receive (deserialise) operation
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum TransformErrorKind
|
|
||||||
{
|
|
||||||
/// Invalid serialised format
|
|
||||||
Format,
|
|
||||||
/// Compression
|
|
||||||
Compress,
|
|
||||||
/// Encryption
|
|
||||||
Encrypt,
|
|
||||||
/// Misc. IO
|
|
||||||
//TODO: Disambiguate when this happens into the two above cases.
|
|
||||||
IO(io::Error),
|
|
||||||
/// The object header was invalid.
|
|
||||||
InvalidHeader(HeaderValidationError),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An error when sending / serialising an object.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct RecvError(Box<(TransformErrorKind, RecvOpt)>);
|
|
||||||
|
|
||||||
impl RecvError
|
|
||||||
{
|
|
||||||
#[inline] pub fn kind(&self) -> &TransformErrorKind
|
|
||||||
{
|
|
||||||
&self.0.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl SendError
|
|
||||||
{
|
|
||||||
#[inline] pub fn kind(&self) -> &TransformErrorKind
|
|
||||||
{
|
|
||||||
&self.0.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl error::Error for RecvError
|
|
||||||
{
|
|
||||||
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
|
|
||||||
Some(match &self.0.0
|
|
||||||
{
|
|
||||||
TransformErrorKind::IO(io) => io,
|
|
||||||
TransformErrorKind::InvalidHeader(ih) => ih,
|
|
||||||
_ => return None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl fmt::Display for RecvError
|
|
||||||
{
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
|
||||||
{
|
|
||||||
write!(f, "error when deserialising object with params {:?}: ", self.0.1)?;
|
|
||||||
match self.0.0 {
|
|
||||||
TransformErrorKind::Format => write!(f, "failed to deserialise object to data"),
|
|
||||||
TransformErrorKind::Compress => write!(f, "failed to decompress data"),
|
|
||||||
TransformErrorKind::Encrypt => write!(f, "failed to decrypt data"),
|
|
||||||
TransformErrorKind::IO(_) => write!(f, "i/o failure"),
|
|
||||||
TransformErrorKind::InvalidHeader(_) => write!(f, "invalid header"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// An error when sending / serialising an object.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct SendError(Box<(TransformErrorKind, SendOpt)>);
|
|
||||||
|
|
||||||
impl error::Error for SendError
|
|
||||||
{
|
|
||||||
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
|
|
||||||
Some(match &self.0.0
|
|
||||||
{
|
|
||||||
TransformErrorKind::IO(io) => io,
|
|
||||||
TransformErrorKind::InvalidHeader(ih) => ih,
|
|
||||||
_ => return None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for SendError
|
|
||||||
{
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
|
||||||
{
|
|
||||||
write!(f, "error when serialising object with params {:?}: ", self.0.1)?;
|
|
||||||
match self.0.0 {
|
|
||||||
TransformErrorKind::Format => write!(f, "failed to serialise object to data"),
|
|
||||||
TransformErrorKind::Compress => write!(f, "failed to compress data"),
|
|
||||||
TransformErrorKind::Encrypt => write!(f, "failed to encrypt data"),
|
|
||||||
TransformErrorKind::IO(_) => write!(f, "i/o failure"),
|
|
||||||
TransformErrorKind::InvalidHeader(_) => write!(f, "invalid header"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<io::Error> for TransformErrorKind
|
|
||||||
{
|
|
||||||
fn from(from: io::Error) -> Self
|
|
||||||
{
|
|
||||||
Self::IO(from)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<HeaderValidationError> for TransformErrorKind
|
|
||||||
{
|
|
||||||
fn from(from: HeaderValidationError) -> Self
|
|
||||||
{
|
|
||||||
Self::InvalidHeader(from)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
impl From<serde_cbor::Error> for TransformErrorKind
|
|
||||||
{
|
|
||||||
#[inline] fn from(_: serde_cbor::Error) -> Self
|
|
||||||
{
|
|
||||||
Self::Format
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<serde_json::Error> for TransformErrorKind
|
|
||||||
{
|
|
||||||
#[inline] fn from(_: serde_json::Error) -> Self
|
|
||||||
{
|
|
||||||
Self::Format
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test
|
|
||||||
{
|
|
||||||
use super::*;
|
|
||||||
async fn ser_de_with(how: SendOpt) -> eyre::Result<()>
|
|
||||||
{
|
|
||||||
use ext::*;
|
|
||||||
|
|
||||||
let obj = String::from("Hello world");
|
|
||||||
|
|
||||||
let var = ser_singleton(&obj, &how).await?;
|
|
||||||
eprintln!("Ser ({} bytes): {}", var.len(), var.hex());
|
|
||||||
let des: String = de_singleton(&var, &how).await?;
|
|
||||||
eprintln!("De: {:?}", des);
|
|
||||||
assert_eq!(obj, des);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn ser_de() -> eyre::Result<()>
|
|
||||||
{
|
|
||||||
ser_de_with(Default::default()).await
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn ser_de_comp() -> eyre::Result<()>
|
|
||||||
{
|
|
||||||
ser_de_with(SendOpt {
|
|
||||||
comp: Some(CompressionKind::Brotli),
|
|
||||||
..Default::default()
|
|
||||||
}).await
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn ser_de_enc() -> eyre::Result<()>
|
|
||||||
{
|
|
||||||
ser_de_with(SendOpt {
|
|
||||||
encrypt: Some(EncryptionKind::Chacha20(cha::keygen())),
|
|
||||||
//hash: true,
|
|
||||||
..Default::default()
|
|
||||||
}).await
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn ser_de_comp_enc() -> eyre::Result<()>
|
|
||||||
{
|
|
||||||
ser_de_with(SendOpt {
|
|
||||||
encrypt: Some(EncryptionKind::Chacha20(cha::keygen())),
|
|
||||||
comp: Some(CompressionKind::Brotli),
|
|
||||||
..Default::default()
|
|
||||||
}).await
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,233 +0,0 @@
|
|||||||
use std::{
|
|
||||||
mem,
|
|
||||||
iter::{
|
|
||||||
self,
|
|
||||||
ExactSizeIterator,
|
|
||||||
FusedIterator,
|
|
||||||
},
|
|
||||||
slice,
|
|
||||||
fmt,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub use std::{
|
|
||||||
marker::{
|
|
||||||
Send, Sync, Unpin,
|
|
||||||
},
|
|
||||||
borrow::{ Borrow, BorrowMut },
|
|
||||||
convert::{
|
|
||||||
Infallible,
|
|
||||||
TryFrom,
|
|
||||||
TryInto
|
|
||||||
},
|
|
||||||
};
|
|
||||||
pub use tokio::{
|
|
||||||
io::{
|
|
||||||
AsyncWriteExt,
|
|
||||||
AsyncReadExt,
|
|
||||||
},
|
|
||||||
task::JoinHandle,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Make this type act as a reference to itself.
|
|
||||||
///
|
|
||||||
/// Implements `AsRef<T>` for type `T`.
|
|
||||||
#[macro_export] macro_rules! ref_self {
|
|
||||||
($type:ty) => {
|
|
||||||
impl AsRef<$type> for $type
|
|
||||||
{
|
|
||||||
#[inline] fn as_ref(&self) -> &$type
|
|
||||||
{
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct HexStringIter<I>(I, [u8; 2]);
|
|
||||||
|
|
||||||
impl<I: Iterator<Item = u8>> HexStringIter<I>
|
|
||||||
{
|
|
||||||
/// Write this hex string iterator to a formattable buffer
|
|
||||||
pub fn consume<F>(self, f: &mut F) -> fmt::Result
|
|
||||||
where F: std::fmt::Write
|
|
||||||
{
|
|
||||||
if self.1[0] != 0 {
|
|
||||||
write!(f, "{}", self.1[0] as char)?;
|
|
||||||
}
|
|
||||||
if self.1[1] != 0 {
|
|
||||||
write!(f, "{}", self.1[1] as char)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
for x in self.0 {
|
|
||||||
write!(f, "{:02x}", x)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Consume into a string
|
|
||||||
pub fn into_string(self) -> String
|
|
||||||
{
|
|
||||||
let mut output = match self.size_hint() {
|
|
||||||
(0, None) => String::new(),
|
|
||||||
(_, Some(x)) |
|
|
||||||
(x, None) => String::with_capacity(x),
|
|
||||||
};
|
|
||||||
self.consume(&mut output).unwrap();
|
|
||||||
output
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait HexStringIterExt<I>: Sized
|
|
||||||
{
|
|
||||||
fn into_hex(self) -> HexStringIter<I>;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type HexStringSliceIter<'a> = HexStringIter<iter::Copied<slice::Iter<'a, u8>>>;
|
|
||||||
|
|
||||||
pub trait HexStringSliceIterExt
|
|
||||||
{
|
|
||||||
fn hex(&self) -> HexStringSliceIter<'_>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S> HexStringSliceIterExt for S
|
|
||||||
where S: AsRef<[u8]>
|
|
||||||
{
|
|
||||||
fn hex(&self) -> HexStringSliceIter<'_>
|
|
||||||
{
|
|
||||||
self.as_ref().iter().copied().into_hex()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<I: IntoIterator<Item=u8>> HexStringIterExt<I::IntoIter> for I
|
|
||||||
{
|
|
||||||
#[inline] fn into_hex(self) -> HexStringIter<I::IntoIter> {
|
|
||||||
HexStringIter(self.into_iter(), [0u8; 2])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<I: Iterator<Item = u8>> Iterator for HexStringIter<I>
|
|
||||||
{
|
|
||||||
type Item = char;
|
|
||||||
fn next(&mut self) -> Option<Self::Item>
|
|
||||||
{
|
|
||||||
match self.1 {
|
|
||||||
[_, 0] => {
|
|
||||||
use std::io::Write;
|
|
||||||
write!(&mut self.1[..], "{:02x}", self.0.next()?).unwrap();
|
|
||||||
|
|
||||||
Some(mem::replace(&mut self.1[0], 0) as char)
|
|
||||||
},
|
|
||||||
[0, _] => Some(mem::replace(&mut self.1[1], 0) as char),
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
|
||||||
let (l, h) = self.0.size_hint();
|
|
||||||
|
|
||||||
(l * 2, h.map(|x| x*2))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<I: Iterator<Item = u8> + ExactSizeIterator> ExactSizeIterator for HexStringIter<I>{}
|
|
||||||
impl<I: Iterator<Item = u8> + FusedIterator> FusedIterator for HexStringIter<I>{}
|
|
||||||
|
|
||||||
impl<I: Iterator<Item = u8>> From<HexStringIter<I>> for String
|
|
||||||
{
|
|
||||||
fn from(from: HexStringIter<I>) -> Self
|
|
||||||
{
|
|
||||||
from.into_string()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<I: Iterator<Item = u8> + Clone> fmt::Display for HexStringIter<I>
|
|
||||||
{
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
|
||||||
{
|
|
||||||
self.clone().consume(f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait CollectArrayExt<T>: Sized
|
|
||||||
{
|
|
||||||
/// Collect an iterator into an array.
|
|
||||||
///
|
|
||||||
/// If the iterator has more elements than `N`, the rest are discarded.
|
|
||||||
///
|
|
||||||
/// # Panics
|
|
||||||
/// If the iterator has **less** elements than `N`.
|
|
||||||
fn collect_array<const N: usize>(self) -> [T; N];
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<I> CollectArrayExt<I::Item> for I
|
|
||||||
where I: Iterator
|
|
||||||
{
|
|
||||||
fn collect_array<const N: usize>(self) -> [I::Item; N] {
|
|
||||||
use std::mem::MaybeUninit;
|
|
||||||
|
|
||||||
// SAFETY: This pattern is safe. The array elements are still maybeuninit.
|
|
||||||
let mut out = unsafe { MaybeUninit::<[MaybeUninit::<I::Item>; N]>::uninit().assume_init() };
|
|
||||||
let mut init_to = 0;
|
|
||||||
|
|
||||||
if N == 0 {
|
|
||||||
// SAFETY: This is valid, [I::Item; N] is 0 sized. (i uhh think...)
|
|
||||||
return unsafe { MaybeUninit::<[I::Item; N]>::uninit().assume_init() };
|
|
||||||
}
|
|
||||||
|
|
||||||
let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
|
|
||||||
#[cold]
|
|
||||||
#[inline(never)]
|
|
||||||
fn _panic_bad_size(exp: usize, got: usize) -> !
|
|
||||||
{
|
|
||||||
panic!("tried to collect into array of size {}, when iterator is only {} elements", exp, got)
|
|
||||||
}
|
|
||||||
init_to = out.iter_mut().zip(self)
|
|
||||||
.map(|(o, i)| *o = MaybeUninit::new(i)).count();
|
|
||||||
match init_to
|
|
||||||
{
|
|
||||||
n if n == N => (),
|
|
||||||
got => _panic_bad_size(N, got),
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
match res {
|
|
||||||
Ok(()) => {
|
|
||||||
// SAFETY: Transmuting MaybeUninit<T> to T is fine.
|
|
||||||
// All elements are initialised by this point
|
|
||||||
unsafe {
|
|
||||||
|
|
||||||
#[inline(always)] unsafe fn assume_init_array<T, const N: usize>(array: [MaybeUninit<T>; N]) -> [T; N]
|
|
||||||
{
|
|
||||||
//std::intrinsics::assert_inhabited::<[T; N]>();
|
|
||||||
(&array as *const _ as *const [T; N]).read()
|
|
||||||
}
|
|
||||||
//MaybeUninit::array_assume_init(out)
|
|
||||||
assume_init_array(out)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(e) => {
|
|
||||||
// Drop all initialised elements before resuming unwind.
|
|
||||||
unsafe {
|
|
||||||
std::ptr::drop_in_place(&mut out[..init_to] as *mut [MaybeUninit<I::Item>] as *mut [I::Item]);
|
|
||||||
}
|
|
||||||
std::panic::resume_unwind(e)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export] macro_rules! prog1 {
|
|
||||||
($first:expr, $($rest:expr);+ $(;)?) => {
|
|
||||||
($first, $( $rest ),+).0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export] macro_rules! r#if {
|
|
||||||
($if:expr, $then:expr, $else:expr) => {
|
|
||||||
if $if { $then } else { $else }
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,158 +0,0 @@
|
|||||||
//! File-watching
|
|
||||||
//!
|
|
||||||
//! When serving a directory not in oneshot-mode, this can be used to update listings.
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
use std::{path::PathBuf, time::Duration};
|
|
||||||
use std::sync::Arc;
|
|
||||||
use std::ops::Deref;
|
|
||||||
use notify::{
|
|
||||||
Watcher,
|
|
||||||
RecursiveMode,
|
|
||||||
watcher,
|
|
||||||
//TODO: set up wrapper around the notify callback thread that puts events into a async tokio::mpsc (or broadcast?) sender.
|
|
||||||
};
|
|
||||||
use tokio::sync::{
|
|
||||||
broadcast,
|
|
||||||
mpsc,
|
|
||||||
};
|
|
||||||
//use tokio_uring // Don't do this here, have a seperate thread using this (if we end up using it, we probably should since we probably don't need multiple threads reading/writing files at once.)
|
|
||||||
|
|
||||||
pub trait Receiver<T>
|
|
||||||
{
|
|
||||||
type Error;
|
|
||||||
fn recv(&mut self) -> Result<T, Self::Error>;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Sender<T>
|
|
||||||
{
|
|
||||||
type Error;
|
|
||||||
fn send(&self, val: T) -> Result<(), Self::Error>;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Channel<T>: Sized
|
|
||||||
{
|
|
||||||
type Sender: Sender<T>;
|
|
||||||
type Receiver: Receiver<T>;
|
|
||||||
|
|
||||||
fn split(self) -> (Self::Sender, Self::Receiver);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, S,R> Channel<T> for (S, R)
|
|
||||||
where S: Sender<T>,
|
|
||||||
R: Receiver<T>
|
|
||||||
{
|
|
||||||
type Sender = S;
|
|
||||||
type Receiver = R;
|
|
||||||
#[inline(always)] fn split(self) -> (Self::Sender, Self::Receiver) {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Sender<T> for broadcast::Sender<T>
|
|
||||||
{
|
|
||||||
type Error = broadcast::error::SendError<T>;
|
|
||||||
fn send(&self, val: T) -> Result<(), Self::Error> {
|
|
||||||
self.send(val)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Receiver<T> for broadcast::Receiver<T>
|
|
||||||
where T: Clone
|
|
||||||
{
|
|
||||||
type Error = broadcast::error::TryRecvError;
|
|
||||||
fn recv(&mut self) -> Result<T, Self::Error> {
|
|
||||||
broadcast::Receiver::try_recv(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Sender<T> for mpsc::Sender<T>
|
|
||||||
{
|
|
||||||
type Error = mpsc::error::TrySendError<T>;
|
|
||||||
fn send(&self, val: T) -> Result<(), Self::Error> {
|
|
||||||
self.try_send(val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Receiver<T> for mpsc::Receiver<T>
|
|
||||||
{
|
|
||||||
type Error = mpsc::error::TryRecvError;
|
|
||||||
fn recv(&mut self) -> Result<T, Self::Error> {
|
|
||||||
self.try_recv()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Sender<T> for mpsc::UnboundedSender<T>
|
|
||||||
{
|
|
||||||
type Error = mpsc::error::SendError<T>;
|
|
||||||
fn send(&self, val: T) -> Result<(), Self::Error> {
|
|
||||||
self.send(val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Receiver<T> for mpsc::UnboundedReceiver<T>
|
|
||||||
{
|
|
||||||
type Error = mpsc::error::TryRecvError;
|
|
||||||
fn recv(&mut self) -> Result<T, Self::Error> {
|
|
||||||
self.try_recv()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
|
||||||
pub struct WatchEvent(Arc<notify::DebouncedEvent>);
|
|
||||||
|
|
||||||
impl WatchEvent
|
|
||||||
{
|
|
||||||
#[inline(always)] pub fn debounced(&self) -> ¬ify::DebouncedEvent
|
|
||||||
{
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsRef<notify::DebouncedEvent> for WatchEvent
|
|
||||||
{
|
|
||||||
#[inline] fn as_ref(&self) -> ¬ify::DebouncedEvent
|
|
||||||
{
|
|
||||||
self.debounced()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for WatchEvent
|
|
||||||
{
|
|
||||||
type Target = notify::DebouncedEvent;
|
|
||||||
#[inline] fn deref(&self) -> &Self::Target {
|
|
||||||
self.debounced()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
pub struct Mode
|
|
||||||
{
|
|
||||||
pub recurse: RecursiveMode,
|
|
||||||
pub delay: Duration,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Start a new watcher thread.
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
/// * A receiver that gets the events from the watcher
|
|
||||||
/// * A future that completes when the thread exits
|
|
||||||
pub fn watch<'a, C>(path: PathBuf, mode: Mode, chan: impl FnOnce() -> C + 'a) -> (C::Receiver, impl Future<Output = ()> + Send + Sync + 'static)
|
|
||||||
where C: Channel<WatchEvent>,
|
|
||||||
C::Sender: Send + 'static,
|
|
||||||
{
|
|
||||||
let (otx, orx) = chan().split();
|
|
||||||
let (stx, trx) = std::sync::mpsc::channel();
|
|
||||||
let mut watcher = watcher(stx, mode.delay).unwrap();
|
|
||||||
let passing = tokio::spawn(async move {
|
|
||||||
match trx.try_recv() {
|
|
||||||
Ok(ev) => otx.send(WatchEvent(Arc::new(ev))).map_err(|_| ()).unwrap(),
|
|
||||||
Err(_) => (),//tokio::time::sleep(mode.delay).await, FUCK, WHY can't we await here... ALL of this bullshit above with the traits is useless. just return the damn sync `Receiver`.
|
|
||||||
}
|
|
||||||
});
|
|
||||||
{
|
|
||||||
use futures::prelude::*;
|
|
||||||
(orx, passing.map(|_| ()))
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,317 +0,0 @@
|
|||||||
//! Key and IV structures for the cipher
|
|
||||||
use getrandom::getrandom;
|
|
||||||
use std::{fmt, str};
|
|
||||||
pub use crate::cha::{
|
|
||||||
KEY_SIZE,
|
|
||||||
IV_SIZE,
|
|
||||||
};
|
|
||||||
use crate::ext::*;
|
|
||||||
|
|
||||||
/// A trait for objects that contain a key and IV.
|
|
||||||
pub trait CC20Key
|
|
||||||
{
|
|
||||||
fn key(&self) -> &Key;
|
|
||||||
fn iv(&self) -> &IV;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, T: ?Sized> CC20Key for &'a T
|
|
||||||
where T: CC20Key {
|
|
||||||
#[inline] fn key(&self) -> &Key
|
|
||||||
{
|
|
||||||
T::key(self)
|
|
||||||
}
|
|
||||||
#[inline] fn iv(&self) -> &IV
|
|
||||||
{
|
|
||||||
T::iv(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<T, U> CC20Key for (T, U)
|
|
||||||
where T: AsRef<Key>, U: AsRef<IV>
|
|
||||||
{
|
|
||||||
fn key(&self) -> &Key
|
|
||||||
{
|
|
||||||
self.0.as_ref()
|
|
||||||
}
|
|
||||||
fn iv(&self) -> &IV
|
|
||||||
{
|
|
||||||
self.1.as_ref()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A 32 byte key for the chacha20_poly1305 cipher
|
|
||||||
///
|
|
||||||
/// # Generation
|
|
||||||
/// You can generate a random key with `Key::new()`.
|
|
||||||
/// To create a key structure from bytes, you can use `Key::from_bytes()` if the size of the buffer is exact, or you can write to an empty `Key` as it implements `Default`.
|
|
||||||
/// ```
|
|
||||||
/// # use chacha20stream::{Key, key::KEY_SIZE};
|
|
||||||
/// # let key_bytes = [0u8; 32];
|
|
||||||
/// let mut key = Key::default();
|
|
||||||
/// key.as_mut().copy_from_slice(&key_bytes[..KEY_SIZE]);
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// You can also generate a random key/IV pair with `chacha20stream::keygen()`.
|
|
||||||
///
|
|
||||||
/// # Encoding
|
|
||||||
/// This type implements `std::fmt::Display`, which prints the key as a base64 string.
|
|
||||||
/// Additionally, it implements `std::str::FromStr`, which decodes a base64 string into a `Key` instance.
|
|
||||||
/// If the input base64 string data decoded is shorter than `KEY_SIZE`, the rest of the key instance is padded with 0s.
|
|
||||||
/// If it is longer, the rest is ignored.
|
|
||||||
///
|
|
||||||
/// The key can also be lazily formatted as a hex string, with the method `to_hex_string()`.
|
|
||||||
/// ```
|
|
||||||
/// # use chacha20stream::Key;
|
|
||||||
/// let key = Key::new();
|
|
||||||
/// let key_encoded = key.to_string();
|
|
||||||
///
|
|
||||||
/// println!("Key base64: {}", key_encoded);
|
|
||||||
/// println!("Key hex: {}", key.to_hex_string());
|
|
||||||
///
|
|
||||||
/// assert_eq!(key_encoded.parse::<Key>().unwrap(), key);
|
|
||||||
/// ```
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy, Default, Serialize, Deserialize)]
|
|
||||||
#[repr(transparent)]
|
|
||||||
pub struct Key([u8; KEY_SIZE]);
|
|
||||||
|
|
||||||
/// A 12 byte IV for the chacha20_poly1305 cipher
|
|
||||||
///
|
|
||||||
/// # Generation
|
|
||||||
/// You can generate a random IV with `IV::new()`.
|
|
||||||
/// To create an IV structure from bytes, you can use `IV::from_bytes()` if the size of the buffer is exact, or you can write to an empty `IV` as it implements `Default`.
|
|
||||||
/// ```
|
|
||||||
/// # use chacha20stream::{IV, key::IV_SIZE};
|
|
||||||
/// # let iv_bytes = [0u8; 12];
|
|
||||||
/// let mut iv = IV::default();
|
|
||||||
/// iv.as_mut().copy_from_slice(&iv_bytes[..IV_SIZE]);
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// You can also generate a random key/IV pair with `chacha20stream::keygen()`.
|
|
||||||
///
|
|
||||||
/// # Encoding
|
|
||||||
/// This type implements `std::fmt::Display`, which prints the IV as a base64 string.
|
|
||||||
/// Additionally, it implements `std::str::FromStr`, which decodes a base64 string into a `IV` instance.
|
|
||||||
/// If the input base64 string data decoded is shorter than `IV_SIZE`, the rest of the IV instance is padded with 0s.
|
|
||||||
/// If it is longer, the rest is ignored.
|
|
||||||
///
|
|
||||||
/// The IV can also be lazily formatted as a hex string, with the method `to_hex_string()`.
|
|
||||||
/// ```
|
|
||||||
/// # use chacha20stream::IV;
|
|
||||||
/// let iv = IV::new();
|
|
||||||
/// let iv_encoded = iv.to_string();
|
|
||||||
///
|
|
||||||
/// println!("IV base64: {}", iv_encoded);
|
|
||||||
/// println!("IV hex: {}", iv.to_hex_string());
|
|
||||||
///
|
|
||||||
/// assert_eq!(iv_encoded.parse::<IV>().unwrap(), iv);
|
|
||||||
/// ```
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy, Default, Serialize, Deserialize)]
|
|
||||||
#[repr(transparent)]
|
|
||||||
pub struct IV([u8; IV_SIZE]);
|
|
||||||
|
|
||||||
impl Key
|
|
||||||
{
|
|
||||||
/// Construct a `Key` from an exact length (32 bytes) buffer.
|
|
||||||
#[inline] pub fn from_bytes(k: [u8; KEY_SIZE]) -> Self
|
|
||||||
{
|
|
||||||
Self(k)
|
|
||||||
}
|
|
||||||
/// Create a new random 32 byte chacha20_poly1305 `Key`.
|
|
||||||
pub fn new() -> Self
|
|
||||||
{
|
|
||||||
let mut output = [0u8; KEY_SIZE];
|
|
||||||
getrandom(&mut output[..]).expect("rng fatal");
|
|
||||||
Self(output)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Format this key as a hex string
|
|
||||||
///
|
|
||||||
/// Returns an opaque type that lazily formats the key into a hex string when written.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
/// ```
|
|
||||||
/// # use chacha20stream::Key;
|
|
||||||
/// fn print_key_info(key: &Key) {
|
|
||||||
/// println!("Key base64: {}", key);
|
|
||||||
/// println!("Key hex: {}", key.to_hex_string());
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
/// Formatting to `String`
|
|
||||||
/// ```
|
|
||||||
/// # use chacha20stream::Key;
|
|
||||||
/// # let key = Key::new();
|
|
||||||
/// let key_hex_string = key.to_hex_string().to_string();
|
|
||||||
/// ```
|
|
||||||
pub fn to_hex_string(&self) -> impl fmt::Display + '_
|
|
||||||
{
|
|
||||||
self.0.iter().copied().into_hex()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IV
|
|
||||||
{
|
|
||||||
|
|
||||||
/// Construct a `IV` from an exact length (12 bytes) buffer.
|
|
||||||
#[inline] pub fn from_bytes(k: [u8; IV_SIZE]) -> Self
|
|
||||||
{
|
|
||||||
Self(k)
|
|
||||||
}
|
|
||||||
/// Create a new random 12 byte chacha20_poly1305 `IV`.
|
|
||||||
pub fn new() -> Self
|
|
||||||
{
|
|
||||||
let mut output = [0u8; IV_SIZE];
|
|
||||||
getrandom(&mut output[..]).expect("rng fatal");
|
|
||||||
Self(output)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Format this IV as a hex string
|
|
||||||
///
|
|
||||||
/// Returns an opaque type that lazily formats the IV into a hex string when written.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
/// ```
|
|
||||||
/// # use chacha20stream::IV;
|
|
||||||
/// fn print_iv_info(iv: &IV) {
|
|
||||||
/// println!("IV base64: {}", iv);
|
|
||||||
/// println!("IV hex: {}", iv.to_hex_string());
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
/// Formatting to `String`
|
|
||||||
/// ```
|
|
||||||
/// # use chacha20stream::IV;
|
|
||||||
/// # let iv = IV::new();
|
|
||||||
/// let iv_hex_string = iv.to_hex_string().to_string();
|
|
||||||
/// ```
|
|
||||||
pub fn to_hex_string(&self) -> impl fmt::Display + '_
|
|
||||||
{
|
|
||||||
self.0.iter().copied().into_hex()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<[u8; KEY_SIZE]> for Key
|
|
||||||
{
|
|
||||||
#[inline] fn from(from: [u8; KEY_SIZE]) -> Self
|
|
||||||
{
|
|
||||||
Self(from)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<[u8; IV_SIZE]> for IV
|
|
||||||
{
|
|
||||||
fn from(from: [u8; IV_SIZE]) -> Self
|
|
||||||
{
|
|
||||||
Self(from)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
impl AsRef<[u8]> for Key
|
|
||||||
{
|
|
||||||
fn as_ref(&self) -> &[u8]
|
|
||||||
{
|
|
||||||
&self.0[..]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl AsRef<[u8]> for IV
|
|
||||||
{
|
|
||||||
fn as_ref(&self) -> &[u8]
|
|
||||||
{
|
|
||||||
&self.0[..]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsMut<[u8]> for Key
|
|
||||||
{
|
|
||||||
fn as_mut(&mut self) -> &mut [u8]
|
|
||||||
{
|
|
||||||
&mut self.0[..]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsMut<[u8]> for IV
|
|
||||||
{
|
|
||||||
fn as_mut(&mut self) -> &mut [u8]
|
|
||||||
{
|
|
||||||
&mut self.0[..]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsRef<Key> for Key
|
|
||||||
{
|
|
||||||
#[inline] fn as_ref(&self) -> &Key
|
|
||||||
{
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl AsRef<IV> for IV
|
|
||||||
{
|
|
||||||
#[inline] fn as_ref(&self) -> &IV
|
|
||||||
{
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Key
|
|
||||||
{
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
|
||||||
{
|
|
||||||
write!(f, "{}", base64::encode(&self.0[..]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for IV
|
|
||||||
{
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
|
||||||
{
|
|
||||||
write!(f, "{}", base64::encode(&self.0[..]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl str::FromStr for Key
|
|
||||||
{
|
|
||||||
type Err = base64::DecodeError;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
let mut buffer = Vec::with_capacity(KEY_SIZE);
|
|
||||||
base64::decode_config_buf(s.as_bytes(), base64::STANDARD, &mut buffer)?;
|
|
||||||
|
|
||||||
let mut this = Self::default();
|
|
||||||
let sz = std::cmp::min(KEY_SIZE, buffer.len());
|
|
||||||
(&mut this.0[..sz]).copy_from_slice(&buffer[..sz]);
|
|
||||||
Ok(this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl str::FromStr for IV
|
|
||||||
{
|
|
||||||
type Err = base64::DecodeError;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
let mut buffer = Vec::with_capacity(IV_SIZE);
|
|
||||||
base64::decode_config_buf(s.as_bytes(), base64::STANDARD, &mut buffer)?;
|
|
||||||
|
|
||||||
let mut this = Self::default();
|
|
||||||
let sz = std::cmp::min(IV_SIZE, buffer.len());
|
|
||||||
(&mut this.0[..sz]).copy_from_slice(&buffer[..sz]);
|
|
||||||
Ok(this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests
|
|
||||||
{
|
|
||||||
use super::{Key, IV};
|
|
||||||
#[test]
|
|
||||||
fn enc_dec()
|
|
||||||
{
|
|
||||||
let (key, iv) = crate::cha::keygen();
|
|
||||||
|
|
||||||
let key_str = key.to_string();
|
|
||||||
let iv_str = iv.to_string();
|
|
||||||
|
|
||||||
let (key2, iv2): (Key, IV) = (key_str.parse().expect("key"),
|
|
||||||
iv_str.parse().expect("iv"));
|
|
||||||
|
|
||||||
assert_eq!(key, key2);
|
|
||||||
assert_eq!(iv, iv2);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
//! Module handles setting up streams for files
|
|
||||||
use super::*;
|
|
||||||
use std::marker::Unpin;
|
|
||||||
use tokio::io::{
|
|
||||||
AsyncRead,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/// A read request from the client.
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
||||||
pub struct Request
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
pub async fn read_req<T>(mut from: T, key: ) -> eyre::Result<Request>
|
|
||||||
where T: AsyncRead + Unpin
|
|
||||||
{
|
|
||||||
todo!("how do we handle encryption of the request data? eh... boring")
|
|
||||||
}
|
|
||||||
|
|
||||||
*/
|
|
@ -1,5 +0,0 @@
|
|||||||
//! Sending
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
pub mod handshake;
|
|
@ -0,0 +1,64 @@
|
|||||||
|
//! Socket handling
|
||||||
|
use super::*;
|
||||||
|
use std::str;
|
||||||
|
use std::path::{
|
||||||
|
Path, PathBuf
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
|
pub struct SocketAddrUnix
|
||||||
|
{
|
||||||
|
pub path: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl str::FromStr for SocketAddrUnix
|
||||||
|
{
|
||||||
|
type Err = AddrParseError;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let path = Path::new(s);
|
||||||
|
if path.exists() && !path.is_dir() {
|
||||||
|
Ok(Self{path: path.into()})
|
||||||
|
} else {
|
||||||
|
Err(AddrParseError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
|
pub enum SocketAddr
|
||||||
|
{
|
||||||
|
Unix(SocketAddrUnix),
|
||||||
|
IP(std::net::SocketAddr),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<std::net::SocketAddr> for SocketAddr
|
||||||
|
{
|
||||||
|
fn from(from: std::net::SocketAddr) -> Self
|
||||||
|
{
|
||||||
|
Self::IP(from)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct AddrParseError;
|
||||||
|
impl From<std::net::AddrParseError> for AddrParseError
|
||||||
|
{
|
||||||
|
fn from(_: std::net::AddrParseError) -> Self
|
||||||
|
{
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const UNIX_SOCK_PREFIX: &str = "unix:/";
|
||||||
|
impl str::FromStr for SocketAddr
|
||||||
|
{
|
||||||
|
type Err = AddrParseError;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
Ok(if s.starts_with(UNIX_SOCK_PREFIX) {
|
||||||
|
Self::Unix(s[(UNIX_SOCK_PREFIX.len())..].parse()?)
|
||||||
|
} else {
|
||||||
|
Self::IP(s.parse()?)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in new issue