From dca0c79c4a6c64706a09ce6372390ff88744f983 Mon Sep 17 00:00:00 2001 From: Avril Date: Tue, 28 Sep 2021 14:12:05 +0100 Subject: [PATCH] Start arg parsing. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fortune for transfer's current commit: Blessing − 吉 --- Cargo.lock | 42 +++++++++++++++++++++ Cargo.toml | 2 + src/args.rs | 10 +++-- src/args/parse.rs | 96 +++++++++++++++++++++++++++++++++++++++++++++++ src/config.rs | 40 ++++++++++++++++++-- src/enc/mod.rs | 22 ++++++++++- src/ext.rs | 76 +++++++++++++++++++++++++++++++++++++ src/key.rs | 32 +++++++++++++++- src/main.rs | 3 ++ 9 files changed, 313 insertions(+), 10 deletions(-) create mode 100644 src/args/parse.rs diff --git a/Cargo.lock b/Cargo.lock index 53d41b6..5d881b9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -472,6 +472,12 @@ version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" +[[package]] +name = "lazy_format" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "482170c31d2086032a851765674d916a49db14a808012ada930024cca23adf6b" + [[package]] name = "lazy_static" version = "1.4.0" @@ -800,6 +806,15 @@ version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + [[package]] name = "ryu" version = "1.0.5" @@ -812,6 +827,21 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + [[package]] name = "serde" version = "1.0.130" @@ -890,6 +920,16 @@ dependencies = [ "serde", ] +[[package]] +name = "stackalloc" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4f5c9dd3feb8a4adc8eae861e5f48862a92f9a9f38cf8fc99b92fc6ec016121" +dependencies = [ + "cc", + "rustc_version", +] + [[package]] name = "subtle" version = "2.4.1" @@ -970,6 +1010,7 @@ dependencies = [ "cryptohelpers", "futures", "getrandom 0.2.3", + "lazy_format", "lazy_static", "log", "openssl", @@ -978,6 +1019,7 @@ dependencies = [ "serde_cbor", "serde_json", "smallvec", + "stackalloc", "tokio 1.12.0", ] diff --git a/Cargo.toml b/Cargo.toml index 62bdbe6..85790b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ color-eyre = { version = "0.5.11", default-features = false } cryptohelpers = { version = "1.8.2", features = ["sha256", "async", "rsa", "serde"] } futures = "0.3.17" getrandom = "0.2.3" +lazy_format = "1.9.0" lazy_static = "1.4.0" log = "0.4.14" openssl = "0.10.36" @@ -23,4 +24,5 @@ serde = { version = "1.0.130", features = ["derive"] } serde_cbor = "0.11.2" serde_json = "1.0.68" smallvec = { version = "1.6.1", features = ["serde", "const_generics", "write"] } +stackalloc = "1.1.1" tokio = { version = "1.12.0", features = ["full"] } diff --git a/src/args.rs b/src/args.rs index 9cdc016..7bcff58 100644 --- a/src/args.rs +++ b/src/args.rs @@ -41,15 +41,17 @@ impl fmt::Display for Usage { splash(f)?; - writeln!(f, "Usage: {} --send [SEND OPTIONS] ", program_name())?; - writeln!(f, "Usage: {} --recv [RECV OPTIONS] ", program_name())?; + writeln!(f, "Usage: {} S --send|--recv [OPTIONS] ", program_name())?; + writeln!(f, "Usage: {} C --send|--recv [OPTIONS] ", 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 \tSize of file buffer")?; writeln!(f, " -a\t\t\tSend file names")?; - writeln!(f, " -1\t\t\tExit after 1 client has been served")?; writeln!(f, " -k\t\t\tSupport continuation of failed downloads")?; writeln!(f, "\nRECV OPTIONS:")?; @@ -79,3 +81,5 @@ pub enum Op Process(Box), Help, } + +mod parse; diff --git a/src/args/parse.rs b/src/args/parse.rs new file mode 100644 index 0000000..464432a --- /dev/null +++ b/src/args/parse.rs @@ -0,0 +1,96 @@ +//! Parsing args +use super::*; +use ext::*; +use std::iter; + +/// Arg state +#[derive(Debug, Default)] +struct State +{ + is_server: bool, + is_sending: bool, + + enc: Option, + comp: Option, + bufsz: Option, + arc: Option, + contin: Option, + oneshot: Option, + + inter: Option, + + files: Vec, +} + +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(state: &mut State, single: I) -> eyre::Result<()> +where I: IntoIterator +{ + 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(args: &mut I) -> eyre::Result +where I: Iterator + ?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 for Op`, etc") +} +fn parse_iter_raw(args: &mut I) -> eyre::Result +where I: Iterator + ?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) +} diff --git a/src/config.rs b/src/config.rs index cd3113a..511987e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,5 +1,6 @@ //! Configuration use super::*; +use std::net::SocketAddr; pub const DEFAULT_BUFFER_SIZE: usize = 4096; @@ -11,7 +12,7 @@ pub struct SendConfig compress: bool, buffer_size: usize, archive: bool, - oneshot: bool, + //oneshot: bool, // Server specific continuation: bool, } @@ -25,7 +26,7 @@ impl Default for SendConfig compress: false, buffer_size: DEFAULT_BUFFER_SIZE, archive: false, - oneshot: false, + //oneshot: false, continuation: false, } } @@ -51,11 +52,42 @@ impl Default for RecvConfig } } +/// Instructions for binding (server) mode +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Server +{ + listen: SocketAddr, //TODO: Allow multiple? +} + +/// Instructions for connecting (client) mode +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Client +{ + connect: SocketAddr, +} -/// Program configuration +/// A send or recv operation #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum Config +pub enum Operation { Send(SendConfig), Recv(RecvConfig), } + +/// Whether to serve (listen) or connect directly. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum Mode +{ + Server(Server), + Client(Client), +} + +/// Program full configuration +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Config +{ + /// Which operation (send/recv) are we performing? + pub op: Operation, + /// How are we performing it? (Bind/connect) + pub mode: Mode, +} diff --git a/src/enc/mod.rs b/src/enc/mod.rs index fefae3e..cc03af8 100644 --- a/src/enc/mod.rs +++ b/src/enc/mod.rs @@ -20,6 +20,24 @@ use cryptohelpers::{ rsa, }; -mod ser; -pub use ser::*; +/// 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(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::(from, to, keys.key(), keys.iv()).await + } else { + ser::cha_copy::(from, to, keys.key(), keys.iv()).await + } +} diff --git a/src/ext.rs b/src/ext.rs index d724c9e..b0d5bbf 100644 --- a/src/ext.rs +++ b/src/ext.rs @@ -152,8 +152,84 @@ impl + Clone> fmt::Display for HexStringIter } } +pub trait CollectArrayExt: 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(self) -> [T; N]; +} + +impl CollectArrayExt for I +where I: Iterator +{ + fn collect_array(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::; 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 to T is fine. + // All elements are initialised by this point + unsafe { + + #[inline(always)] unsafe fn assume_init_array(array: [MaybeUninit; 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 [_]); + } + 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 } + } +} diff --git a/src/key.rs b/src/key.rs index 244a5a9..6c39464 100644 --- a/src/key.rs +++ b/src/key.rs @@ -1,5 +1,4 @@ //! Key and IV structures for the cipher - use getrandom::getrandom; use std::{fmt, str}; pub use crate::cha::{ @@ -8,6 +7,37 @@ pub use crate::cha::{ }; 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 CC20Key for (T, U) + where T: AsRef, U: AsRef +{ + 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 diff --git a/src/main.rs b/src/main.rs index 8de7b04..12367e5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,8 +4,11 @@ #[macro_use] extern crate log; #[macro_use] extern crate ad_hoc_iter; #[macro_use] extern crate lazy_static; +//#[macro_use] extern crate lazy_format; #[macro_use] extern crate serde; +use lazy_format::lazy_format; + use color_eyre::{ eyre::{ self,