commit
0c97a5fcc8
@ -0,0 +1,2 @@
|
||||
/target
|
||||
*~
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,24 @@
|
||||
[package]
|
||||
name = "rae"
|
||||
version = "0.1.0"
|
||||
authors = ["Avril <flanchan@cumallover.me>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
color-eyre = {version = "0.5.3", default-features =false}
|
||||
lazy_static = "1.4.0"
|
||||
cryptohelpers = {version = "1.1.2", features=["full", "async", "serialise"]}
|
||||
cfg-if = "0.1.10"
|
||||
tokio = {version = "0.2", features=["full"]}
|
||||
serde = {version ="1.0.116", features=["derive"]}
|
||||
toml = "0.5.6"
|
||||
tracing = "0.1.19"
|
||||
tracing-subscriber = "0.2.12"
|
||||
tracing-futures = "0.2.4"
|
||||
tracing-error = "0.1.2"
|
||||
smallmap = "1.1.2"
|
||||
|
||||
[build-dependencies]
|
||||
rustc_version = "0.2"
|
@ -0,0 +1,24 @@
|
||||
|
||||
extern crate rustc_version;
|
||||
use rustc_version::{version, version_meta, Channel};
|
||||
|
||||
fn main() {
|
||||
// Assert we haven't travelled back in time
|
||||
assert!(version().unwrap().major >= 1);
|
||||
|
||||
// Set cfg flags depending on release channel
|
||||
match version_meta().unwrap().channel {
|
||||
Channel::Stable => {
|
||||
println!("cargo:rustc-cfg=stable");
|
||||
}
|
||||
Channel::Beta => {
|
||||
println!("cargo:rustc-cfg=beta");
|
||||
}
|
||||
Channel::Nightly => {
|
||||
println!("cargo:rustc-cfg=nightly");
|
||||
}
|
||||
Channel::Dev => {
|
||||
println!("cargo:rustc-cfg=dev");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
//! Argument related errors
|
||||
use super::*;
|
||||
use std::{
|
||||
error,
|
||||
fmt,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum ParseErrorKind
|
||||
{
|
||||
ExpectedArg(String),
|
||||
UnknownOption(char),
|
||||
UnknownOptionFor(char, char),
|
||||
CannotCombine(char, char),
|
||||
BadTail(char, String),
|
||||
NotUnique(char),
|
||||
}
|
||||
|
||||
impl From<(String, ParseErrorKind)> for Error
|
||||
{
|
||||
#[inline] fn from((froms, fromk): (String, ParseErrorKind)) -> Self
|
||||
{
|
||||
Self::Parse(froms, fromk)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum Error {
|
||||
Parse(String, ParseErrorKind),
|
||||
|
||||
Unknown,
|
||||
AtomInternal,
|
||||
}
|
||||
impl error::Error for Error{}
|
||||
impl fmt::Display for Error
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
||||
{
|
||||
match self {
|
||||
_ => write!(f, "unknown error"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl<T> From<tokio::sync::mpsc::error::SendError<T>> for Error
|
||||
{
|
||||
#[inline] fn from(_: tokio::sync::mpsc::error::SendError<T>) -> Self
|
||||
{
|
||||
Self::AtomInternal
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
//! Argument stuffs
|
||||
use super::*;
|
||||
|
||||
|
||||
mod error;
|
||||
mod parse;
|
||||
|
||||
pub fn program_name() -> &'static str
|
||||
{
|
||||
lazy_static!{
|
||||
static ref NAME: String = std::env::args().next().unwrap();
|
||||
}
|
||||
|
||||
&NAME[..]
|
||||
}
|
||||
|
||||
pub fn usage() -> !
|
||||
{
|
||||
println!(r#"signing encryptor version {pkg_version} (format version {real_version})
|
||||
by {authour} with <3
|
||||
licensed with GPL v3 or later
|
||||
|
||||
Usage: {program} [OPTIONS...] [-e|-d|-a] [-] [<files...>]
|
||||
Usage: {program} --generate RSA [--input <old key>] [--password[=<password>]] [--format BIN|TEXT|PEM] <output> [<public part output>]
|
||||
Usage: {program} --generate AES [--input <old key>] [--password[=<password>]] [--format BIN|TEXT] <output>
|
||||
Usage: {program} --stat <key files...>
|
||||
Usage: {program} --help
|
||||
|
||||
- Stop reading argument flags:
|
||||
-e Encryption mode
|
||||
-d Decryption mode
|
||||
-a Autodetect mode (default)
|
||||
OPTIONS:
|
||||
-t <inp> <out> Process <input> to <output> (autodetect mode)
|
||||
-te <inp> <out> Process <input> to <output> (encryption mode)
|
||||
-td <inp> <out> Process <input> to <output> (decryption mode)
|
||||
-i Process in-place for non-specific files (i.e. do not add or remove `.rae` file extension)
|
||||
|
||||
-k <key> Use this key (autodetect type)
|
||||
-kR <key> Use this key (rsa private)
|
||||
-kr <key> Use this key (rsa public)
|
||||
-ka <key> Use this key (aes)
|
||||
Other options for `-k`
|
||||
-p Read a password from stdin for this key
|
||||
-P <password> The next argument after the key file is a password for the key
|
||||
-s This key is also a signing key
|
||||
-S This key is only a signing key
|
||||
|
||||
EXAMPLE:
|
||||
{program} -kRP key.rsa "super secret password" -ka key.aes -te input_file output_encrypted file
|
||||
"#,
|
||||
pkg_version = env!("CARGO_PKG_VERSION"),
|
||||
real_version = CURRENT_VERSION,
|
||||
authour = env!("CARGO_PKG_AUTHORS"),
|
||||
program = program_name());
|
||||
std::process::exit(1)
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
/// Parse the program args into `Operation`.
|
||||
pub async fn parse_args() -> Result<Spec, eyre::Report>
|
||||
{
|
||||
parse::parse(std::env::args().skip(1))
|
||||
.with_suggestion(|| "Try passing `--help`")
|
||||
}
|
@ -0,0 +1,301 @@
|
||||
//! Arg parsing
|
||||
#![allow(unused_macros)]
|
||||
use super::*;
|
||||
use tokio::{
|
||||
sync::{
|
||||
mpsc,
|
||||
},
|
||||
};
|
||||
use std::{
|
||||
iter,
|
||||
};
|
||||
use eyre::eyre;
|
||||
use config::op::Password;
|
||||
use error::ParseErrorKind;
|
||||
|
||||
#[derive(Debug)]
|
||||
enum IsSigning
|
||||
{
|
||||
No,
|
||||
Yes,
|
||||
Only,
|
||||
}
|
||||
|
||||
impl Default for IsSigning
|
||||
{
|
||||
#[inline]
|
||||
fn default() -> Self
|
||||
{
|
||||
Self::No
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum KeyKind
|
||||
{
|
||||
Autodetect,
|
||||
RsaPublic,
|
||||
RsaPrivate(IsSigning), // is signing key?
|
||||
Aes,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum TranslationMode
|
||||
{
|
||||
Autodetect,
|
||||
Encrypt,
|
||||
Decrypt,
|
||||
}
|
||||
|
||||
impl Default for TranslationMode
|
||||
{
|
||||
#[inline]
|
||||
fn default() -> Self
|
||||
{
|
||||
Self::Autodetect
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Atom
|
||||
{
|
||||
Key(KeyKind, String, Password),
|
||||
|
||||
NonSpecific,
|
||||
FileAuto(String),
|
||||
FileSpecific(String, String, TranslationMode),
|
||||
//TODO:
|
||||
}
|
||||
|
||||
|
||||
#[instrument]
|
||||
pub async fn parse<I: IntoIterator<Item=String> + std::fmt::Debug>(args: I) -> Result<!/* TODO: What type do we make for this? */, eyre::Report>
|
||||
{
|
||||
let (mut tx, mut rx) = mpsc::channel(1);
|
||||
|
||||
macro_rules! unwrap_handler {
|
||||
($handler:expr) => ($handler.await
|
||||
.wrap_err_with(|| eyre!("Atom parser panicked")
|
||||
.with_warning(|| "This usually indicates a bug in the program, not invalid input.")));
|
||||
}
|
||||
|
||||
let mut args = args.into_iter();
|
||||
let handler = if let Some(arg) = args.next()
|
||||
{
|
||||
match arg.to_lowercase().trim() {
|
||||
"--generate" => todo!(),
|
||||
"--help" => todo!(),
|
||||
"--stat" => todo!(),
|
||||
_ => {
|
||||
let handler = tokio::spawn(async move {
|
||||
//TODO: What container type do we make for this?
|
||||
while let Some(atom) = rx.recv().await {
|
||||
debug!("[Normal] recv atom: {:?}", atom);
|
||||
// Construct `Normal` with atoms
|
||||
}
|
||||
todo!()
|
||||
});
|
||||
match parse_normal(std::iter::once(arg)
|
||||
.chain(args.map(Into::into)), &mut tx).await
|
||||
{
|
||||
// The `handler` has dropped `rx` and returned an error, let unwrap_handler down there handle this.
|
||||
Err(error::Error::AtomInternal) => Ok(()),
|
||||
other => other,
|
||||
}
|
||||
.with_note(|| "In parsing of normal operation spec")?;
|
||||
|
||||
|
||||
handler
|
||||
},
|
||||
}
|
||||
} else {
|
||||
return Err(eyre!("No arguments specified"));
|
||||
};
|
||||
|
||||
match unwrap_handler!(handler)? {
|
||||
Err(error) => {
|
||||
return Err(error)
|
||||
.wrap_err_with(|| eyre!("Failed to construct operation from arguments"))
|
||||
.with_note(|| "In constructing of operation from spec");
|
||||
},
|
||||
Ok(result) => {
|
||||
|
||||
},
|
||||
}
|
||||
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn parse_normal<I: IntoIterator<Item=String>>(args: I, atoms: &mut mpsc::Sender<Atom>) -> Result<(), error::Error>
|
||||
{
|
||||
let mut args = args.into_iter();
|
||||
let mut reading=true;
|
||||
|
||||
|
||||
while let Some(arg) = args.next()
|
||||
{
|
||||
|
||||
macro_rules! take_one {
|
||||
($msg:literal $($tt:tt)*) => {
|
||||
match args.next() {
|
||||
Some(v) => v,
|
||||
None => return Err((arg, ParseErrorKind::ExpectedArg(format!($msg $($tt)*))).into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
if reading && arg.starts_with("-") {
|
||||
let mut opt = arg.chars().skip(1);
|
||||
match opt.next() {
|
||||
Some('i') => match opt.next() {
|
||||
None => atoms.send(Atom::NonSpecific).await?,
|
||||
Some(r) => return Err((ParseErrorKind::BadTail('i', iter::once(r).chain(opt).collect()), arg).swap().into()),
|
||||
},
|
||||
Some('t') => {
|
||||
let input = take_one!("t: Expected input file");
|
||||
let output = take_one!("t: Expected output file");
|
||||
let mut trans = TranslationMode::default();
|
||||
|
||||
let mut had = smallmap::Map::new();
|
||||
while let Some(opt) = opt.next() {
|
||||
if (had.contains_key(&opt), had.insert(opt, ())).0 {
|
||||
// arg has been repeated, not allowed
|
||||
return Err((arg, error::ParseErrorKind::NotUnique(opt)).into());
|
||||
}
|
||||
macro_rules! check_combine {
|
||||
($($chars:expr),+) => {
|
||||
$(if had.contains_key(&$chars) {
|
||||
return Err(error::Error::from((arg, error::ParseErrorKind::CannotCombine(opt, $chars))));
|
||||
})*
|
||||
};
|
||||
}
|
||||
|
||||
match opt {
|
||||
'e' => trans= TranslationMode::Encrypt,
|
||||
'd' => trans= TranslationMode::Decrypt,
|
||||
_ => return Err((arg, error::ParseErrorKind::UnknownOptionFor('t', opt)).into()),
|
||||
}
|
||||
}
|
||||
atoms.send(Atom::FileSpecific(input, output, trans)).await?;
|
||||
},
|
||||
Some('k') => {
|
||||
let key_file = take_one!("k: Expected a key file");
|
||||
enum KeySpec {
|
||||
Autodetect,
|
||||
RsaPrivate,
|
||||
RsaPublic,
|
||||
Aes,
|
||||
}
|
||||
let mut kind = KeySpec::Autodetect;
|
||||
let mut password = Password::default();
|
||||
let mut signing = IsSigning::default();
|
||||
|
||||
let mut had = smallmap::Map::new();
|
||||
while let Some(opt) = opt.next() {
|
||||
if (had.contains_key(&opt), had.insert(opt, ())).0 {
|
||||
// arg has been repeated, not allowed
|
||||
return Err((arg, error::ParseErrorKind::NotUnique(opt)).into());
|
||||
}
|
||||
macro_rules! check_combine {
|
||||
($($chars:expr),+) => {
|
||||
$(if had.contains_key(&$chars) {
|
||||
return Err(error::Error::from((arg, error::ParseErrorKind::CannotCombine(opt, $chars))));
|
||||
})*
|
||||
};
|
||||
}
|
||||
|
||||
match opt {
|
||||
'R' => {
|
||||
check_combine!['r','a'];
|
||||
kind = KeySpec::RsaPrivate;
|
||||
},
|
||||
'r' => {
|
||||
check_combine!['R','a'];
|
||||
kind = KeySpec::RsaPublic;
|
||||
},
|
||||
'a' => {
|
||||
check_combine!['r','R'];
|
||||
kind = KeySpec::Aes;
|
||||
},
|
||||
'P' => {
|
||||
check_combine!['p'];
|
||||
password= Password::Yes;
|
||||
},
|
||||
'p' => {
|
||||
check_combine!['P'];
|
||||
password = Password::Specific(take_one!("P: Expected a password"));
|
||||
},
|
||||
'S' => {
|
||||
check_combine!['s', 'a'];
|
||||
signing = IsSigning::Only;
|
||||
},
|
||||
's' => {
|
||||
check_combine!['S', 'a'];
|
||||
signing = IsSigning::Yes;
|
||||
},
|
||||
_ => return Err((arg, error::ParseErrorKind::UnknownOptionFor('k', opt)).into()),
|
||||
}
|
||||
}
|
||||
|
||||
let kind = match kind {
|
||||
KeySpec::Autodetect => KeyKind::Autodetect,
|
||||
KeySpec::RsaPublic => KeyKind::RsaPublic,
|
||||
KeySpec::RsaPrivate => KeyKind::RsaPrivate(signing),
|
||||
KeySpec::Aes => KeyKind::Aes,
|
||||
};
|
||||
atoms.send(Atom::Key(kind, key_file, password)).await?;
|
||||
},
|
||||
Some(unknown) => return Err((arg, error::ParseErrorKind::UnknownOption(unknown)).into()),
|
||||
None => reading=false,
|
||||
}
|
||||
} else {
|
||||
reading=false;
|
||||
atoms.send(Atom::FileAuto(arg)).await?;
|
||||
}
|
||||
}
|
||||
todo!()
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
#[instrument]
|
||||
pub fn parse<T: Into<String>, I: IntoIterator<Item = T> + std::fmt::Debug>(args: I) -> Result<Spec, eyre::Report>
|
||||
{
|
||||
let mut args = args.into_iter();
|
||||
if let Some(arg) = args.next()
|
||||
{
|
||||
let arg =arg.into();
|
||||
match arg.to_lowercase().trim() {
|
||||
"--generate" => Ok(Spec::Concrete(config::Operation::GenerateKey(parse_keygen(args.map(Into::into))
|
||||
.with_note(|| "Mode was `--generate`")?))),
|
||||
"--help" => Ok(Spec::Concrete(config::Operation::Help)),
|
||||
"--stat" => Ok(Spec::Concrete(config::Operation::KeyInfo(parse_stat(args.map(Into::into))
|
||||
.with_note(|| "Mode was `--stat")?))),
|
||||
_ => Ok(parse_normal(std::iter::once(arg)
|
||||
.chain(args.map(Into::into)))
|
||||
.with_note(|| "Mode was crypt")?.into()),
|
||||
}
|
||||
} else {
|
||||
Err(eyre!("No arguments specified"))
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_normal<I: IntoIterator<Item = String>>(args: I) -> Result<OperationSpec, eyre::Report>
|
||||
{
|
||||
todo!()
|
||||
}
|
||||
|
||||
|
||||
fn parse_keygen<I: IntoIterator<Item = String>>(args: I) -> Result<config::op::GenerateKey, eyre::Report>
|
||||
{
|
||||
todo!()
|
||||
}
|
||||
|
||||
|
||||
fn parse_stat<I: IntoIterator<Item = String>>(args: I) -> Result<Vec<(String, config::op::Password)>, eyre::Report>
|
||||
{
|
||||
todo!()
|
||||
}
|
||||
*/
|
@ -0,0 +1,94 @@
|
||||
//! Parsed config
|
||||
use super::*;
|
||||
|
||||
pub mod op {
|
||||
|
||||
/// The crypt mode
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
|
||||
pub enum Mode {
|
||||
/// Detect based on file-type
|
||||
Autodetect,
|
||||
Encrypt,
|
||||
Decrypt,
|
||||
}
|
||||
impl Default for Mode
|
||||
{
|
||||
#[inline]
|
||||
fn default() -> Self
|
||||
{
|
||||
Mode::Autodetect
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone,PartialEq, Eq, Default)]
|
||||
pub struct Normal {
|
||||
pub rsa: Vec<(String, Password)>,
|
||||
pub sign: Vec<(String, Password)>,
|
||||
pub aes: Vec<(String, Password)>,
|
||||
pub mode: Mode,
|
||||
|
||||
pub files: Vec<(String, Option<String>)>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone,PartialEq, Eq)]
|
||||
pub enum GenerateKey {
|
||||
Rsa(Rsa),
|
||||
Aes(Aes),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
|
||||
pub enum Password
|
||||
{
|
||||
No,
|
||||
Yes,
|
||||
Specific(String),
|
||||
}
|
||||
impl Default for Password
|
||||
{
|
||||
#[inline]
|
||||
fn default() -> Self
|
||||
{
|
||||
Self::No
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug ,PartialEq, Eq, Clone, Hash, Default)]
|
||||
pub struct KeyDescription
|
||||
{
|
||||
pub name: String,
|
||||
pub email: String,
|
||||
pub description: String,
|
||||
|
||||
other: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone,PartialEq, Eq)]
|
||||
pub struct Rsa
|
||||
{
|
||||
input: Option<(String, Password)>,
|
||||
|
||||
output_priv: String,
|
||||
output_pub: Option<String>,
|
||||
password: Password,
|
||||
|
||||
description: KeyDescription,
|
||||
}
|
||||
#[derive(Debug,Clone, PartialEq, Eq)]
|
||||
pub struct Aes
|
||||
{
|
||||
input: Option<(String, Password)>,
|
||||
|
||||
output: String,
|
||||
password: Password,
|
||||
|
||||
description: KeyDescription,
|
||||
}
|
||||
}
|
||||
#[derive(Debug, Clone,PartialEq, Eq)]
|
||||
pub enum Operation
|
||||
{
|
||||
Normal(op::Normal),
|
||||
GenerateKey(op::GenerateKey),
|
||||
KeyInfo(Vec<(String, op::Password)>),
|
||||
Help,
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
//! Extensions
|
||||
|
||||
pub trait JoinStrsExt: Sized
|
||||
{
|
||||
/// Join an iterator of `str` with a seperator
|
||||
fn join(self, with: &str) -> String;
|
||||
}
|
||||
|
||||
impl<T,I> JoinStrsExt for I
|
||||
where I: Iterator<Item=T>,
|
||||
T: AsRef<str>
|
||||
{
|
||||
fn join(self, with: &str) -> String
|
||||
{
|
||||
let mut output = String::new();
|
||||
let mut first=true;
|
||||
for string in self
|
||||
{
|
||||
if !first {
|
||||
output.push_str(with);
|
||||
}
|
||||
let string = string.as_ref();
|
||||
output.push_str(string);
|
||||
first=false;
|
||||
}
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
/*macro_rules! typed_swap {
|
||||
(@ [] $($reversed:tt)*) => {
|
||||
fn swap(self) -> ($($reversed)*);
|
||||
};
|
||||
(@ [$first:tt $($rest:tt)*] $($reversed:tt)*) => {
|
||||
typed_swap!{@ [$($rest)*] $first $($reversed)*}
|
||||
};
|
||||
(@impl {$($body:tt)*} [] $($reversed:tt)*) => {
|
||||
fn swap(self) -> ($($reversed)*)
|
||||
{
|
||||
$($body)*
|
||||
}
|
||||
};
|
||||
(@impl {$($body:tt)*} [$first:tt $($rest:tt)*] $($reversed:tt)*) => {
|
||||
typed_swap!{@impl {$($body)*} [$($rest)*] $first $($reversed)*}
|
||||
};
|
||||
() => {};
|
||||
({$($params:tt)*} $($rest:tt)*) => {
|
||||
mod swap {
|
||||
pub trait SwapTupleExt<$($params)*>: Sized
|
||||
{
|
||||
typed_swap!(@ [$($params)*]);
|
||||
}
|
||||
|
||||
impl<$($params)*> SwapTupleExt<$($params)*> for ($($params)*)
|
||||
{
|
||||
typed_swap!(@impl {
|
||||
todo!()
|
||||
} [$($params)*]);
|
||||
}
|
||||
|
||||
typed_swap!($($rest)*);
|
||||
}
|
||||
pub use swap::*;
|
||||
};
|
||||
|
||||
(all $first:tt $($params:tt)+) => {
|
||||
typed_swap!({$first, $($params),+});
|
||||
mod nswap {
|
||||
typed_swap!(all $($params)+);
|
||||
}
|
||||
};
|
||||
(all $($one:tt)?) => {};
|
||||
}
|
||||
|
||||
typed_swap!(all A B C D E F G H I J K L M N O P Q R S T U V W X Y Z);
|
||||
pub use swap::*;
|
||||
|
||||
fn test()
|
||||
{
|
||||
let sw = (1, 2).swap();
|
||||
|
||||
}*/
|
||||
// ^ unfortunately not lol
|
||||
|
||||
pub trait SwapTupleExt<T,U>: Sized
|
||||
{
|
||||
fn swap(self) -> (U,T);
|
||||
}
|
||||
impl<T,U> SwapTupleExt<T,U> for (T,U)
|
||||
{
|
||||
#[inline(always)] fn swap(self) -> (U,T) {
|
||||
(self.1, self.0)
|
||||
}
|
||||
}
|
||||
|
||||
/*typed_swap!({A, B}
|
||||
{A, U, V}
|
||||
{T, U, V, W});*/
|
@ -0,0 +1,86 @@
|
||||
#![cfg_attr(nightly, feature(label_break_value))]
|
||||
|
||||
#![allow(dead_code)]
|
||||
|
||||
#[macro_use] extern crate tracing;
|
||||
use std::{
|
||||
convert::{TryFrom, TryInto},
|
||||
};
|
||||
use color_eyre::{
|
||||
eyre::{
|
||||
self,
|
||||
WrapErr,
|
||||
},
|
||||
Help, SectionExt,
|
||||
};
|
||||
use lazy_static::lazy_static;
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
pub const CURRENT_VERSION: version::Version = version::Version::new(0,0,0,version::Tag::Prerelease);
|
||||
|
||||
mod ext;
|
||||
use ext::*;
|
||||
mod version;
|
||||
mod config;
|
||||
mod args;
|
||||
|
||||
/*/// Dispatch params operations that can be handled at top level (i.e. `Help`)
|
||||
async fn dispatch_args() -> Result<args::Operation, eyre::Report>
|
||||
{
|
||||
match args::parse_args().await
|
||||
.with_section(move || std::env::args().skip(1).join("\n").header("Input args were:"))? {
|
||||
|
||||
}
|
||||
}*/
|
||||
|
||||
macro_rules! cfg_debug {
|
||||
(if {$($if:tt)*} else {$($else:tt)*}) => {
|
||||
{
|
||||
cfg_if::cfg_if!{
|
||||
if #[cfg(debug_assertions)] {
|
||||
$($if)*
|
||||
} else {
|
||||
$($else)*
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn install_tracing() {
|
||||
use tracing_error::ErrorLayer;
|
||||
use tracing_subscriber::prelude::*;
|
||||
use tracing_subscriber::{fmt, EnvFilter};
|
||||
|
||||
let fmt_layer = fmt::layer().with_target(false);
|
||||
let filter_layer = EnvFilter::try_from_default_env()
|
||||
.or_else(|_| EnvFilter::try_new("info"))
|
||||
.unwrap();
|
||||
|
||||
tracing_subscriber::registry()
|
||||
.with(filter_layer)
|
||||
.with(fmt_layer)
|
||||
.with(ErrorLayer::default())
|
||||
.init();
|
||||
}
|
||||
|
||||
async fn work(op: config::Operation) -> Result<(), eyre::Report>
|
||||
{
|
||||
todo!()
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), eyre::Report> {
|
||||
install_tracing();
|
||||
color_eyre::install()?;
|
||||
|
||||
trace!("Parsing args");
|
||||
let args = args::parse_args().await?;
|
||||
|
||||
work(args).await
|
||||
.with_suggestion(|| "Run with `RUST_LOG=\"debug\"` environment variable for more detailed logging")
|
||||
.with_suggestion(|| "Run with `RUST_LOG=\"trace\"` environment variable for extremely detailed logging")?;
|
||||
|
||||
Ok(())
|
||||
}
|
@ -0,0 +1,261 @@
|
||||
use super::*;
|
||||
use std::{
|
||||
path::{
|
||||
PathBuf,
|
||||
},
|
||||
};
|
||||
|
||||
/// The type of key to generate
|
||||
#[derive(Debug,Eq,PartialEq)]
|
||||
pub enum GenerateKey
|
||||
{
|
||||
Rsa,
|
||||
Aes,
|
||||
}
|
||||
|
||||
/// The key serialise format
|
||||
#[derive(Debug,Eq,PartialEq)]
|
||||
pub enum KeyOutputFormat
|
||||
{
|
||||
Binary,
|
||||
Text,
|
||||
/// PEM format only valid for RSA keys
|
||||
PEM,
|
||||
}
|
||||
|
||||
impl FromStr for KeyOutputFormat
|
||||
{
|
||||
type Err = Error;
|
||||
fn from_str(value: &str) -> Result<Self, Self::Err> {
|
||||
Ok(match value.to_uppercase().as_str() {
|
||||
"TEXT" => KeyOutputFormat::Text,
|
||||
"BIN" => KeyOutputFormat::Binary,
|
||||
"PEM" => KeyOutputFormat::PEM,
|
||||
d => return Err(format!("Unknown key output format `{}`. Expected `TEXT`, `BIN` or `PEM`.", d))?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyOutputFormat
|
||||
{
|
||||
pub fn is_aes_okay(&self) -> bool
|
||||
{
|
||||
if let KeyOutputFormat::PEM = self {
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Operation mode for `Operation::Normal`
|
||||
#[derive(Debug,Eq,PartialEq)]
|
||||
pub enum OperationMode
|
||||
{
|
||||
Autodetect,
|
||||
Encrypt,
|
||||
Decrypt,
|
||||
Verify,
|
||||
}
|
||||
|
||||
/// The operation for this run
|
||||
#[derive(Debug,Eq,PartialEq)]
|
||||
pub enum Operation
|
||||
{
|
||||
/// Crypto
|
||||
Normal{
|
||||
mode: OperationMode,
|
||||
rsa: Vec<PathBuf>,
|
||||
sign: Vec<PathBuf>,
|
||||
auto_sign: bool,
|
||||
aes: Vec<PathBuf>,
|
||||
|
||||
translate: Vec<(PathBuf, Option<PathBuf>)>,
|
||||
in_place: bool,
|
||||
verbose: bool,
|
||||
|
||||
#[cfg(feature="threads")]
|
||||
sync: bool,
|
||||
},
|
||||
/// Generate key
|
||||
KeyGen{
|
||||
key_type: GenerateKey,
|
||||
format: KeyOutputFormat,
|
||||
cycle: Option<PathBuf>,
|
||||
use_password: bool,
|
||||
password: Option<String>,
|
||||
output: Option<PathBuf>,
|
||||
private_only: bool,
|
||||
public_only: bool,
|
||||
output_public: Option<PathBuf>,
|
||||
verbose: bool,
|
||||
|
||||
},
|
||||
/// Print help
|
||||
Help,
|
||||
}
|
||||
|
||||
impl Operation
|
||||
{
|
||||
/// Is verbose logging enabled
|
||||
pub fn verbose(&self) -> bool
|
||||
{
|
||||
match self {
|
||||
Self::Normal { verbose, .. } |
|
||||
Self::KeyGen { verbose, .. } => *verbose,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! transcribe {
|
||||
($cond:expr => $if:expr; $else:expr) => {
|
||||
if $cond {$if} else {$else}
|
||||
};
|
||||
}
|
||||
|
||||
use std::fmt;
|
||||
impl fmt::Display for Operation
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
||||
{
|
||||
match &self {
|
||||
Self::Help => writeln!(f, "Display help"),
|
||||
Self::Normal{
|
||||
mode,
|
||||
rsa,
|
||||
sign,
|
||||
auto_sign,
|
||||
aes,
|
||||
in_place,
|
||||
verbose,
|
||||
#[cfg(feature="threads")]
|
||||
sync,
|
||||
|
||||
..
|
||||
} => {
|
||||
write!(f, "Mode: ")?;
|
||||
match mode {
|
||||
OperationMode::Encrypt => writeln!(f, "Encrypt")?,
|
||||
OperationMode::Decrypt => writeln!(f, "Decrypt")?,
|
||||
OperationMode::Verify => writeln!(f, "Verify")?,
|
||||
_ => writeln!(f, "Autodetected")?,
|
||||
}
|
||||
if rsa.len() > 0 {
|
||||
writeln!(f, "RSA encryption enabled for {} keys.", rsa.len())?;
|
||||
}
|
||||
if sign.len() > 0 || *auto_sign {
|
||||
writeln!(f, "Signing with {} keys.", sign.len() + transcribe!(*auto_sign => rsa.len(); 0))?;
|
||||
}
|
||||
writeln!(f, "AES encrypting with {} keys.", aes.len())?;
|
||||
writeln!(f, " (will generate {} session keys.)",
|
||||
transcribe!(aes.len() > 0 =>
|
||||
aes.len() * transcribe!(rsa.len()>0 => rsa.len(); 1);
|
||||
rsa.len()))?;
|
||||
if *in_place {
|
||||
writeln!(f, "In-place")?;
|
||||
}
|
||||
|
||||
#[cfg(feature="threads")]
|
||||
if *sync {
|
||||
writeln!(f, "Single-threaded mode")?;
|
||||
}
|
||||
#[cfg(not(feature="threads"))]
|
||||
writeln!(f, "Single-threaded mode")?;
|
||||
|
||||
if *verbose {
|
||||
writeln!(f, "Verbose mode")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
},
|
||||
Self::KeyGen {
|
||||
key_type: GenerateKey::Rsa,
|
||||
format,
|
||||
cycle,
|
||||
password,
|
||||
use_password,
|
||||
output,
|
||||
verbose,
|
||||
|
||||
private_only,
|
||||
output_public,
|
||||
public_only,
|
||||
} => {
|
||||
writeln!(f, "Generate key: RSA")?;
|
||||
writeln!(f, "Output format: {}", format)?;
|
||||
if let Some(cycle) = cycle {
|
||||
if output.is_some() || output_public.is_some() {
|
||||
writeln!(f, "Input: {:?}", cycle)?;
|
||||
} else {
|
||||
writeln!(f, "In-place: {:?}", cycle)?;
|
||||
}
|
||||
}
|
||||
if password.is_some() || *use_password {
|
||||
writeln!(f, "Using password.")?;
|
||||
}
|
||||
if let Some(output) = output {
|
||||
writeln!(f, "Outputting to: {:?}", output)?;
|
||||
}
|
||||
if let Some(output_public) = output_public {
|
||||
if *public_only {
|
||||
writeln!(f, "Outputting public only to {:?}", output_public)?;
|
||||
} else {
|
||||
writeln!(f, "Outputting public to {:?}", output_public)?;
|
||||
}
|
||||
}
|
||||
if *private_only {
|
||||
writeln!(f, "No public output")?;
|
||||
}
|
||||
if *verbose {
|
||||
writeln!(f, "Verbose logging")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
},
|
||||
Self::KeyGen {
|
||||
key_type: GenerateKey::Aes,
|
||||
format,
|
||||
cycle,
|
||||
password,
|
||||
use_password,
|
||||
output,
|
||||
verbose,
|
||||
..
|
||||
} => {
|
||||
writeln!(f, "Generate key: AES")?;
|
||||
writeln!(f, "Output format: {}", format)?;
|
||||
if let Some(cycle) = cycle {
|
||||
if output.is_some() {
|
||||
writeln!(f, "Input: {:?}", cycle)?;
|
||||
} else {
|
||||
writeln!(f, "In-place: {:?}", cycle)?;
|
||||
}
|
||||
}
|
||||
if password.is_some() || *use_password {
|
||||
writeln!(f, "Using password.")?;
|
||||
}
|
||||
if let Some(output) = output {
|
||||
writeln!(f, "Outputting to: {:?}", output)?;
|
||||
}
|
||||
if *verbose {
|
||||
writeln!(f, "Verbose logging.")?;
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for KeyOutputFormat
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
||||
{
|
||||
write!(f, "{}", match self {
|
||||
Self::PEM => "PEM",
|
||||
Self::Text => "TEXT",
|
||||
Self::Binary => "BIN",
|
||||
})
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
use std::{
|
||||
error,
|
||||
fmt,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error
|
||||
{
|
||||
Unknown,
|
||||
Message(String),
|
||||
MessageStatic(&'static str),
|
||||
FileNotFound(std::path::PathBuf),
|
||||
BadPath(std::path::PathBuf),
|
||||
ExpectedFile(std::path::PathBuf),
|
||||
}
|
||||
|
||||
impl error::Error for Error{}
|
||||
impl fmt::Display for Error
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
||||
{
|
||||
write!(f, "failed to parse args: ")?;
|
||||
match self {
|
||||
Error::Message(message) => write!(f, "{}", message),
|
||||
Error::MessageStatic(message) => write!(f, "{}", message),
|
||||
Error::FileNotFound(file) => write!(f, "file not found: `{:?}`", file),
|
||||
Error::BadPath(file) => write!(f, "bad path: `{:?}`", file),
|
||||
Error::ExpectedFile(file) => write!(f, "expected a file: `{:?}`", file),
|
||||
_ => write!(f, "unknown error"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Error
|
||||
{
|
||||
fn from(string: String) -> Self
|
||||
{
|
||||
Self::Message(string)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&'static str> for Error
|
||||
{
|
||||
fn from(string: &'static str) -> Self
|
||||
{
|
||||
Self::MessageStatic(string)
|
||||
}
|
||||
}
|
||||
|
||||
use std::path::PathBuf;
|
||||
impl From<PathBuf> for Error
|
||||
{
|
||||
fn from(path: PathBuf) -> Self
|
||||
{
|
||||
|
||||
if !path.exists() {
|
||||
Self::FileNotFound(path)
|
||||
} else if path.is_dir() {
|
||||
Self::ExpectedFile(path)
|
||||
} else {
|
||||
Self::BadPath(path)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
//! Argument and option hanlding
|
||||
use super::*;
|
||||
use std::{
|
||||
env::args,
|
||||
str::FromStr,
|
||||
};
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
/// Program executable name
|
||||
#[inline]
|
||||
pub fn program() -> &'static str
|
||||
{
|
||||
lazy_static! {
|
||||
static ref NAME: &'static str = Box::leak(args().next().unwrap().into_boxed_str());
|
||||
}
|
||||
|
||||
&NAME
|
||||
}
|
||||
|
||||
/// Print usage and exit with exit code 1.
|
||||
pub fn usage() -> !
|
||||
{
|
||||
println!("naes: native file encryptor (prog. ver: {}, format ver: {})", env!("CARGO_PKG_VERSION"), crate::CURRENT_VERSION);
|
||||
println!(" made by {} with <3", env!("CARGO_PKG_AUTHORS"));
|
||||
println!(" licensed with GPL v3");
|
||||
#[cfg(feature="threads")] println!(" (compiled with threaded support)");
|
||||
println!();
|
||||
|
||||
println!("Usage: {} [OPTIONS...] [-e|-d|-v] [--] <files...>", program());
|
||||
println!("Usage: {} --keygen RSA [--verbose] [--cycle <old private>] [--format TEXT|BIN|PEM] [--private-only|--public-only] [--password] <output> [<public output>]", program());
|
||||
println!("Usage: {} --keygen AES [--verbose] [--cycle <old key>] [--format TEXT|BIN] [--password] <output>", program());
|
||||
println!("Usage: {} --help", program());
|
||||
println!();
|
||||
println!("Options:");
|
||||
println!(" --rsa[=keys,...] [<key>]\tEncrypt with this (or these) RSA keys.");
|
||||
println!(" --sign[=keys,...] [<key>]\tSign with this (or these) RSA keys.");
|
||||
println!(" --auto-sign\t\t\tWhen RSA encryption keys are provided, also sign with them.");
|
||||
println!(" --aes[=keys,...] [<key>]\tEncrypt with this (or these) AES keys.");
|
||||
println!(" --translate=[<in>,<out>] [<in> <out>]\tSpecify input and output filenames.");
|
||||
println!(" --in-place\t\t\tDo not add/remove `.nenc` extension to/from output file.");
|
||||
println!(" --verbose\t\t\tVerbose logging.");
|
||||
#[cfg(feature="threads")]
|
||||
println!(" --sync\t\t\tDo not process files concurrently.");
|
||||
println!();
|
||||
println!("Additional options:");
|
||||
println!(" -e\t\tForce encryption mode. Default is autodetect.");
|
||||
println!(" -d\t\tForce decryption mode. Default is autodetect.");
|
||||
println!(" -v\t\tVerification mode. Checks integrity of encrypted files and validates signatures with provided signing keys.");
|
||||
println!(" --\t\tStop reading args.");
|
||||
println!();
|
||||
println!("Keygen only options:");
|
||||
println!(" --cycle <old key>\t\tRead <old key> and write out to output instead of generating new key.");
|
||||
println!(" --format TEXT|BIN|PEM\t\tOutput key format. PEM is only valid for RSA outputs. Default is BIN.");
|
||||
println!(" --private-only\t\tOnly valid for RSA outputs. Do not re-output public key.");
|
||||
println!(" --public-only\t\t\tOnly valid for RSA outputs. Only output public key. Only useful with `--cycle`.");
|
||||
println!(" --password[=text]\t\tPassword-protect this key.");
|
||||
|
||||
std::process::exit(1)
|
||||
}
|
||||
|
||||
pub mod error;
|
||||
use error::*;
|
||||
|
||||
mod data;
|
||||
pub use data::*;
|
||||
|
||||
mod parse;
|
||||
|
||||
mod validate;
|
||||
|
||||
mod spec;
|
||||
|
||||
/// Parse the command line arguments passed.
|
||||
pub async fn parse_args() -> Result<Operation, Error>
|
||||
{
|
||||
let mut args = std::env::args();
|
||||
let _ = args.next();
|
||||
|
||||
parse::parse(&mut args).await?.try_into_validated()
|
||||
}
|
@ -0,0 +1,432 @@
|
||||
use super::*;
|
||||
use std::{
|
||||
convert::{
|
||||
TryFrom,TryInto,
|
||||
},
|
||||
};
|
||||
|
||||
mod builder {
|
||||
use super::*;
|
||||
|
||||
#[derive(PartialEq,Eq,Debug)]
|
||||
pub enum OperationMode
|
||||
{
|
||||
Normal,
|
||||
KeyGen(GenerateKey),
|
||||
Help,
|
||||
}
|
||||
|
||||
#[derive(PartialEq,Eq,Debug)]
|
||||
pub struct KeyGenOps
|
||||
{
|
||||
pub format: KeyOutputFormat,
|
||||
pub cycle: Option<String>,
|
||||
pub use_password: bool,
|
||||
pub password: Option<String>,
|
||||
pub output: Option<String>,
|
||||
pub private_only: bool,
|
||||
pub output_public: Option<String>,
|
||||
pub public_only: bool,
|
||||
}
|
||||
|
||||
impl Default for KeyGenOps
|
||||
{
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
format: KeyOutputFormat::Binary,
|
||||
cycle: None,
|
||||
use_password: false,
|
||||
password: None,
|
||||
output: None,
|
||||
private_only: false,
|
||||
output_public: None,
|
||||
public_only: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq,Eq,Debug)]
|
||||
pub struct NormalOps
|
||||
{
|
||||
pub mode: super::super::OperationMode,
|
||||
pub rsa: Vec<String>,
|
||||
pub sign: Vec<String>,
|
||||
pub auto_sign: bool,
|
||||
pub aes: Vec<String>,
|
||||
|
||||
pub translate: Vec<(String, Option<String>)>,
|
||||
pub in_place: bool,
|
||||
pub sync: bool,
|
||||
}
|
||||
|
||||
impl Default for NormalOps
|
||||
{
|
||||
fn default() -> Self
|
||||
{
|
||||
Self {
|
||||
mode: super::super::OperationMode::Autodetect,
|
||||
rsa: Vec::new(),
|
||||
sign: Vec::new(),
|
||||
auto_sign: false,
|
||||
aes: Vec::new(),
|
||||
translate: Vec::new(),
|
||||
in_place: false,
|
||||
sync: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq,Eq,Debug)]
|
||||
pub struct Operation
|
||||
{
|
||||
pub mode: OperationMode,
|
||||
|
||||
pub verbose: bool,
|
||||
pub keygen: KeyGenOps,
|
||||
pub normal: NormalOps,
|
||||
}
|
||||
|
||||
impl Default for Operation
|
||||
{
|
||||
fn default() -> Self
|
||||
{
|
||||
Self {
|
||||
mode: OperationMode::Help,
|
||||
keygen: KeyGenOps::default(),
|
||||
normal: Default::default(),
|
||||
verbose: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn split<'a>(input: &'a str) -> (&'a str, Option<&'a str>)
|
||||
{
|
||||
if let Some(index) = input.find('=') {
|
||||
(&input[..index], Some(&input[(index+1)..]))
|
||||
} else {
|
||||
(input, None)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn parse<I,T>(input: &mut I) -> Result<Operation, Error>
|
||||
where I: Iterator<Item=T>,
|
||||
T: AsRef<str>
|
||||
{
|
||||
let mut i=0;
|
||||
|
||||
let mut op = builder::Operation::default();
|
||||
let mut reading = true;
|
||||
let mut rest = Vec::new();
|
||||
let mut cfg_files = Vec::new();
|
||||
|
||||
macro_rules! take_one
|
||||
{
|
||||
($message:expr) => {(i+=1, input.next().ok_or_else(|| $message)?).1.as_ref()}
|
||||
}
|
||||
|
||||
while let Some(arg) = input.next()
|
||||
{
|
||||
let arg = arg.as_ref();
|
||||
'next: {
|
||||
if i == 0 {
|
||||
// First arg
|
||||
match arg {
|
||||
"--help" => return Ok(Operation::Help),
|
||||
"--keygen" => {
|
||||
op.keygen = builder::KeyGenOps::default();
|
||||
|
||||
op.mode = builder::OperationMode::KeyGen(match take_one!("Expected a key type").to_uppercase().as_str() {
|
||||
"AES" => GenerateKey::Aes,
|
||||
"RSA" => GenerateKey::Rsa,
|
||||
d => return Err(format!("Unknown key type `{}`. Expected `RSA` or `AES`.",d))?,
|
||||
});
|
||||
break 'next;
|
||||
},
|
||||
_ => {
|
||||
op.mode = builder::OperationMode::Normal;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
match op.mode {
|
||||
builder::OperationMode::KeyGen(GenerateKey::Rsa) => {
|
||||
let (name, value) = split(arg);
|
||||
|
||||
'cont: {
|
||||
match name.to_lowercase().as_str() {
|
||||
"-c" => {
|
||||
cfg_files.push(take_one!("Expected a filename").to_owned());
|
||||
},
|
||||
"--verbose" if value.is_some() => op.verbose = value.unwrap().to_lowercase().trim() == "true",
|
||||
"--verbose" => op.verbose = true,
|
||||
"--cycle" if value.is_some() => op.keygen.cycle = Some(value.unwrap().to_owned()),
|
||||
"--cycle" => op.keygen.cycle = Some(take_one!("Expected a filename").to_owned()),
|
||||
"--format" if value.is_some() => op.keygen.format = value.unwrap().parse()?,
|
||||
"--format" => op.keygen.format = take_one!("Expected an output format").parse()?,
|
||||
"--password" if value.is_some() => {
|
||||
op.keygen.use_password = true;
|
||||
op.keygen.password = Some(value.unwrap().to_owned());
|
||||
},
|
||||
"--password" => op.keygen.use_password = true,
|
||||
"--private-only" => op.keygen.private_only = true,
|
||||
"--public-only" => op.keygen.public_only = true,
|
||||
_ => break 'cont,
|
||||
}
|
||||
break 'next;
|
||||
};
|
||||
},
|
||||
builder::OperationMode::KeyGen(GenerateKey::Aes) => {
|
||||
let (name, value) = split(arg);
|
||||
'cont: {
|
||||
match name.to_lowercase().as_str() {
|
||||
"--verbose" if value.is_some() => op.verbose = value.unwrap().to_lowercase().trim() == "true",
|
||||
"--verbose" => op.verbose = true,
|
||||
"--cycle" if value.is_some() => op.keygen.cycle = Some(value.unwrap().to_owned()),
|
||||
"--cycle" => op.keygen.cycle = Some(take_one!("Expected a filename").to_owned()),
|
||||
"--format" if value.is_some() => op.keygen.format = value.unwrap().parse()?,
|
||||
"--format" => op.keygen.format = take_one!("Expected an output format").parse()?,
|
||||
"--password" if value.is_some() => {
|
||||
op.keygen.use_password = true;
|
||||
op.keygen.password = Some(value.unwrap().to_owned());
|
||||
},
|
||||
"--password" => op.keygen.use_password = true,
|
||||
_ => break 'cont,
|
||||
}
|
||||
break 'next;
|
||||
}
|
||||
},
|
||||
builder::OperationMode::Normal => {
|
||||
let (name, value) = split(arg);
|
||||
if reading {
|
||||
'cont: {
|
||||
match name.to_lowercase().as_str() {
|
||||
"--rsa" if value.is_some() => op.normal.rsa.extend(value.unwrap().split(',').map(|x| x.to_owned())),
|
||||
"--rsa" => op.normal.rsa.push(take_one!("Expected an RSA key filename").to_owned()),
|
||||
"--sign" if value.is_some() => op.normal.sign.extend(value.unwrap().split(',').map(|x| x.to_owned())),
|
||||
"--sign" => op.normal.sign.push(take_one!("Expected an RSA signing key filename").to_owned()),
|
||||
"--auto-sign" => op.normal.auto_sign = true,
|
||||
"--aes" if value.is_some() => op.normal.aes.extend(value.unwrap().split(',').map(|x| x.to_owned())),
|
||||
"--aes" => op.normal.aes.push(take_one!("Expected an AES key filename").to_owned()),
|
||||
"--translate" if value.is_some() => {
|
||||
let mut value = value.unwrap().split(',');
|
||||
let input = value.next().ok_or("Invalid --translate singleton")?.to_owned();
|
||||
let output = value.next().ok_or("Invalid --translate singleton (expected an output)")?.to_owned();
|
||||
op.normal.translate.push((input, Some(output)));
|
||||
},
|
||||
"--translate" => {
|
||||
let input = take_one!("No --translate input").to_owned();
|
||||
let output = take_one!("No --translate output").to_owned();
|
||||
op.normal.translate.push((input.to_owned(), Some(output.to_owned())));
|
||||
},
|
||||
"--in-place" => op.normal.in_place = true,
|
||||
"--verbose" => op.verbose = true,
|
||||
"--sync" => op.normal.sync = true,
|
||||
|
||||
"-e" => {
|
||||
op.normal.mode = super::OperationMode::Encrypt;
|
||||
},
|
||||
"-d" => {
|
||||
op.normal.mode = super::OperationMode::Decrypt;
|
||||
},
|
||||
"-v" => {
|
||||
op.normal.mode = super::OperationMode::Verify;
|
||||
},
|
||||
|
||||
"--" => reading = false,
|
||||
_ => {
|
||||
reading = false;
|
||||
break 'cont;
|
||||
},
|
||||
}
|
||||
break 'next;
|
||||
};
|
||||
}
|
||||
},
|
||||
_ => return Err("Try `--help`")?,
|
||||
}
|
||||
rest.push(arg.to_owned());
|
||||
};
|
||||
i+=1;
|
||||
|
||||
}
|
||||
|
||||
// Add `rest`
|
||||
match op.mode {
|
||||
builder::OperationMode::Normal => {
|
||||
if rest.len() < 1 {
|
||||
return Err("Expected at least one input file")?;
|
||||
}
|
||||
op.normal.translate.extend(rest.into_iter().map(|x| (x, None)));
|
||||
},
|
||||
builder::OperationMode::KeyGen(GenerateKey::Aes) => {
|
||||
if rest.len() > 1 {
|
||||
return Err("Expected only one output file")?;
|
||||
} else if rest.len() < 1 && op.keygen.cycle.is_none() {
|
||||
return Err("Expected one output file or `--cycle`")?;
|
||||
} else if rest.len()>0 {
|
||||
op.keygen.output = Some(rest.into_iter().next().unwrap());
|
||||
}
|
||||
},
|
||||
builder::OperationMode::KeyGen(GenerateKey::Rsa) => {
|
||||
if rest.len() > 2 {
|
||||
return Err("Expected one or 2 output files")?;
|
||||
} else if rest.len() < 1 && op.keygen.cycle.is_none() {
|
||||
return Err("Expected at least one output or `--cycle`")?;
|
||||
} else if rest.len() == 2 {
|
||||
let mut iter = rest.into_iter();
|
||||
|
||||
if op.keygen.public_only {
|
||||
return Err("Expected one public output")?;
|
||||
} else {
|
||||
op.keygen.output = Some(iter.next().unwrap());
|
||||
op.keygen.output_public = Some(iter.next().unwrap());
|
||||
}
|
||||
} else {
|
||||
let mut iter = rest.into_iter();
|
||||
if op.keygen.public_only {
|
||||
op.keygen.output_public = Some(iter.next().ok_or("`--public-only` requires one output.")?);
|
||||
} else {
|
||||
op.keygen.output = iter.next();
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => return Err("No params specified. Try `--help`.")?,
|
||||
};
|
||||
|
||||
// Ensure valid
|
||||
let mut op = op.try_into()?;
|
||||
|
||||
// Add spec
|
||||
for spec in cfg_files.into_iter()
|
||||
{
|
||||
spec::Spec::re //TODO: Fuck this whole module. Just rewrite all of this crap shit FUCK
|
||||
}
|
||||
|
||||
Ok(op)
|
||||
}
|
||||
|
||||
|
||||
impl TryFrom<builder::Operation> for Operation
|
||||
{
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(op: builder::Operation) -> Result<Self, Self::Error>
|
||||
{
|
||||
match op.mode {
|
||||
builder::OperationMode::Normal => {
|
||||
|
||||
if op.keygen != builder::KeyGenOps::default() {
|
||||
return Err("Invalid mode for params.")?;
|
||||
}
|
||||
|
||||
if op.normal.rsa.len() < 1 && op.normal.auto_sign {
|
||||
warn!("`--auto-sign` here does nothing.");
|
||||
}
|
||||
if op.normal.aes.len() < 1 && op.normal.rsa.len() < 1 {
|
||||
return Err("No keys specified.")?;
|
||||
}
|
||||
if op.normal.translate.len() < 1 {
|
||||
return Err("No files specified.")?;
|
||||
}
|
||||
#[cfg(not(feature="threads"))]
|
||||
if op.normal.sync {
|
||||
warn!("`--sync` here does nothing, binary not compiled with `threads` feature.");
|
||||
}
|
||||
|
||||
// Number of inputs without specified outputs
|
||||
let autodetect_inputs = op.normal.translate.iter().filter(|x| x.1.is_none()).count();
|
||||
if op.normal.in_place && autodetect_inputs < 1 {
|
||||
warn!("`--in-place` here does nothing, no autodetected outputs.");
|
||||
}
|
||||
|
||||
Ok(Operation::Normal{
|
||||
mode: op.normal.mode,
|
||||
rsa: op.normal.rsa.into_iter().map(|x| x.into()).collect(),
|
||||
sign: op.normal.sign.into_iter().map(|x| x.into()).collect(),
|
||||
auto_sign: op.normal.auto_sign,
|
||||
aes: op.normal.aes.into_iter().map(|x| x.into()).collect(),
|
||||
translate: op.normal.translate.into_iter().map(|x| (x.0.into(), x.1.and_then(|y| Some(y.into())))).collect(),
|
||||
in_place: op.normal.in_place,
|
||||
verbose: op.verbose,
|
||||
#[cfg(feature="threads")]
|
||||
sync: op.normal.sync,
|
||||
})
|
||||
},
|
||||
builder::OperationMode::KeyGen(GenerateKey::Rsa) => {
|
||||
if op.normal != builder::NormalOps::default() {
|
||||
return Err("Invalid mode for params.")?;
|
||||
}
|
||||
|
||||
if op.keygen.public_only && op.keygen.private_only {
|
||||
return Err("`--private-only` and `--public-only` will generate nothing.")?;
|
||||
}
|
||||
if op.keygen.public_only && op.keygen.cycle.is_none() {
|
||||
warn!("`--public-only` is only useful with `--cycle`.");
|
||||
}
|
||||
if op.keygen.output.is_none()
|
||||
&& op.keygen.output_public.is_none()
|
||||
&& op.keygen.cycle.is_none() {
|
||||
return Err("No output found.")?;
|
||||
}
|
||||
if op.keygen.public_only && op.keygen.cycle.is_none() && op.keygen.output_public.is_none()
|
||||
{
|
||||
return Err("`--public-only` requires one output or `--cycle`.")?;
|
||||
}
|
||||
if op.keygen.private_only && op.keygen.output_public.is_some()
|
||||
{
|
||||
warn!("Unused public key output.");
|
||||
}
|
||||
|
||||
Ok(Operation::KeyGen{
|
||||
key_type: GenerateKey::Rsa,
|
||||
format: op.keygen.format,
|
||||
cycle: op.keygen.cycle.and_then(|x| Some(x.into())),
|
||||
use_password: op.keygen.use_password,
|
||||
output: op.keygen.output.and_then(|x| Some(x.into())),
|
||||
private_only: op.keygen.private_only,
|
||||
public_only: op.keygen.public_only,
|
||||
output_public: op.keygen.output_public.and_then(|x| Some(x.into())),
|
||||
password: op.keygen.password,
|
||||
verbose: op.verbose,
|
||||
})
|
||||
},
|
||||
builder::OperationMode::KeyGen(GenerateKey::Aes) => {
|
||||
if op.normal != builder::NormalOps::default() {
|
||||
return Err("Invalid mode for params.")?;
|
||||
}
|
||||
|
||||
if op.keygen.private_only ||
|
||||
op.keygen.public_only ||
|
||||
op.keygen.output_public.is_some()
|
||||
{
|
||||
return Err("Invalid params for mode.")?;
|
||||
}
|
||||
if !op.keygen.format.is_aes_okay()
|
||||
{
|
||||
return Err("This output format is not valid for AES key.")?;
|
||||
}
|
||||
|
||||
|
||||
if op.keygen.cycle.is_none() && op.keygen.output.is_none() {
|
||||
return Err("No output set.")?;
|
||||
}
|
||||
|
||||
Ok(Operation::KeyGen{
|
||||
key_type: GenerateKey::Aes,
|
||||
format: op.keygen.format,
|
||||
cycle: op.keygen.cycle.and_then(|x| Some(x.into())),
|
||||
use_password: op.keygen.use_password,
|
||||
output: op.keygen.output.and_then(|x| Some(x.into())),
|
||||
password: op.keygen.password,
|
||||
verbose: op.verbose,
|
||||
|
||||
private_only: false,
|
||||
public_only: false,
|
||||
output_public: None,
|
||||
})
|
||||
},
|
||||
builder::OperationMode::Help => Ok(Operation::Help),
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,215 @@
|
||||
//! Way of serialising multiple keys in file
|
||||
use super::*;
|
||||
use std::{
|
||||
io,
|
||||
path::{
|
||||
PathBuf,
|
||||
Path,
|
||||
},
|
||||
marker::Unpin,
|
||||
fmt,
|
||||
error,
|
||||
};
|
||||
use tokio::{
|
||||
prelude::*,
|
||||
io::{
|
||||
AsyncRead,
|
||||
AsyncWrite,
|
||||
},
|
||||
fs::{
|
||||
OpenOptions,
|
||||
},
|
||||
};
|
||||
|
||||
/// An error type for serialisation or deserialisation
|
||||
#[derive(Debug)]
|
||||
pub enum SerdeError
|
||||
{
|
||||
Serialise(toml::ser::Error),
|
||||
Deserialise(toml::de::Error),
|
||||
IO(io::Error),
|
||||
}
|
||||
|
||||
impl From<toml::ser::Error> for SerdeError
|
||||
{
|
||||
fn from(from: toml::ser::Error) -> Self
|
||||
{
|
||||
Self::Serialise(from)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<toml::de::Error> for SerdeError
|
||||
{
|
||||
fn from(from: toml::de::Error) -> Self
|
||||
{
|
||||
Self::Deserialise(from)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for SerdeError
|
||||
{
|
||||
fn from(from: io::Error) -> Self
|
||||
{
|
||||
Self::IO(from)
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for SerdeError
|
||||
{
|
||||
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
|
||||
match &self {
|
||||
Self::Serialise(s) => Some(s),
|
||||
Self::Deserialise(d) => Some(d),
|
||||
Self::IO(io) => Some(io),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for SerdeError
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
||||
{
|
||||
match self {
|
||||
Self::Serialise(_) => write!(f, "writing failed"),
|
||||
Self::Deserialise(_) => write!(f, "reading failed"),
|
||||
Self::IO(_) => write!(f, "io error"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Serialisable struct containing key file information
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub struct Spec
|
||||
{
|
||||
rsa: Vec<String>,
|
||||
rsa_sign: Vec<String>,
|
||||
sign_all: Option<bool>,
|
||||
aes: Vec<String>,
|
||||
}
|
||||
|
||||
pub struct Signers<'a>(&'a Spec, usize);
|
||||
|
||||
impl<'a> Iterator for Signers<'a>
|
||||
{
|
||||
type Item = &'a String;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
(if self.1 < self.0.rsa_sign.len() {
|
||||
Some(&self.0.rsa_sign[self.1])
|
||||
} else if self.0.is_sign_all() {
|
||||
let rest = self.1 -self.0.rsa_sign.len();
|
||||
if rest < self.0.rsa.len() {
|
||||
Some(&self.0.rsa[rest])
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}, self.1+=1).0
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
let len = self.0.rsa_sign.len() + if self.0.is_sign_all() {
|
||||
self.0.rsa.len()
|
||||
} else {
|
||||
0
|
||||
};
|
||||
(len, Some(len))
|
||||
}
|
||||
}
|
||||
impl<'a> std::iter::ExactSizeIterator for Signers<'a>{}
|
||||
|
||||
impl Spec
|
||||
{
|
||||
#[inline] fn is_sign_all(&self) -> bool
|
||||
{
|
||||
self.sign_all.unwrap_or(false)
|
||||
}
|
||||
pub fn new() -> Self
|
||||
{
|
||||
Self {
|
||||
rsa: Vec::new(),
|
||||
rsa_sign: Vec::new(),
|
||||
sign_all: Some(false),
|
||||
aes: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// An iterator over all signers
|
||||
pub fn signers(&self) -> Signers<'_>
|
||||
{
|
||||
Signers(self, 0)
|
||||
}
|
||||
|
||||
/// An iterator over all RSA encryptors
|
||||
pub fn rsa(&self) -> std::slice::Iter<'_, String>
|
||||
{
|
||||
self.rsa.iter()
|
||||
}
|
||||
|
||||
/// An iterator over all AES encryptors
|
||||
pub fn aes(&self) -> std::slice::Iter<'_, String>
|
||||
{
|
||||
self.aes.iter()
|
||||
}
|
||||
|
||||
/// Consume this instance into a `Normal` operation.
|
||||
///
|
||||
/// # Panics
|
||||
/// If `op` is not `Normal` mode.
|
||||
pub fn into_operation(self, op: &mut Operation)
|
||||
{
|
||||
match op {
|
||||
Operation::Normal {
|
||||
rsa,
|
||||
aes,
|
||||
sign,
|
||||
auto_sign,
|
||||
..
|
||||
} => {
|
||||
let sign_all = self.is_sign_all();
|
||||
if *auto_sign == sign_all {
|
||||
// Both are true or both are false, no extra conversion needed
|
||||
sign.extend(self.rsa_sign.into_iter().map(PathBuf::from));
|
||||
} else if sign_all {
|
||||
// Need to add `rsa` and `rsa_sign` to `sign`.
|
||||
sign.extend(self.rsa_sign.iter()
|
||||
.chain(self.rsa.iter())
|
||||
.cloned()
|
||||
.map(PathBuf::from));
|
||||
}
|
||||
rsa.extend(self.rsa.into_iter().map(PathBuf::from));
|
||||
aes.extend(self.aes.into_iter().map(PathBuf::from));
|
||||
},
|
||||
_ => panic!("Tried to consume into an operation of invalid type"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Read an instance from a stream
|
||||
pub async fn read_from_stream<S: AsyncRead + ?Sized + Unpin>(from: &mut S) -> eyre::Result<Self>
|
||||
{
|
||||
let mut output = Vec::with_capacity(4096);
|
||||
async fn internal<S: AsyncRead + ?Sized + Unpin>(output: &mut Vec<u8>, from: &mut S) -> Result<Spec, SerdeError> {
|
||||
let mut buffer = [0u8; 4096];
|
||||
let mut read;
|
||||
while {read = from.read(&mut buffer[..]).await?; read>0} {
|
||||
output.extend_from_slice(&buffer[..read]);
|
||||
}
|
||||
|
||||
Ok(toml::from_slice(&output[..])?)
|
||||
}
|
||||
|
||||
internal(&mut output, from).await
|
||||
.with_section(move || String::from_utf8_lossy(&output[..]).to_string().header("Read contents was"))
|
||||
}
|
||||
|
||||
/// Read an instance from a file
|
||||
pub async fn read_from_file<P: AsRef<Path>>(from: P) -> eyre::Result<Self>
|
||||
{
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.open(from.as_ref()).await?;
|
||||
Ok(Self::read_from_stream(&mut file).await
|
||||
.with_section(move || format!("{:?}", from.as_ref()).header("Filename was"))?)
|
||||
}
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
use super::*;
|
||||
|
||||
impl Operation
|
||||
{
|
||||
/// Validate the options i/o.
|
||||
pub fn try_into_validated(self) -> Result<Self, Error>
|
||||
{
|
||||
match &self {
|
||||
Self::KeyGen{
|
||||
key_type: GenerateKey::Aes,
|
||||
|
||||
output,
|
||||
cycle,
|
||||
..
|
||||
} => {
|
||||
if output == cycle {
|
||||
warn!("Cycle output is the same as input.");
|
||||
}
|
||||
|
||||
if let Some(output) = output {
|
||||
if output.is_dir() {
|
||||
return Err(output.clone())?;
|
||||
}
|
||||
if output.exists() {
|
||||
warn!("Output will be overwritten.");
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(cycle) = cycle {
|
||||
if !cycle.exists() || cycle.is_dir() {
|
||||
return Err(cycle.clone())?;
|
||||
}
|
||||
}
|
||||
},
|
||||
Self::KeyGen{
|
||||
key_type: GenerateKey::Rsa,
|
||||
|
||||
output,
|
||||
output_public,
|
||||
cycle,
|
||||
..
|
||||
} => {
|
||||
if output == output_public && output.is_some() {
|
||||
return Err("Output and public key output are the same.")?;
|
||||
}
|
||||
if cycle == output && output.is_some() {
|
||||
warn!("Cycle output is the same as input.");
|
||||
}
|
||||
if let Some(output) = output {
|
||||
if output.is_dir() {
|
||||
return Err(output.clone())?;
|
||||
}
|
||||
if output.exists() {
|
||||
warn!("Output will be overwritten.");
|
||||
}
|
||||
}
|
||||
if let Some(output_public) = output_public {
|
||||
if output_public.is_dir() {
|
||||
return Err(output_public.clone())?;
|
||||
}
|
||||
if output_public.exists() {
|
||||
warn!("Output for public will be overwritten.");
|
||||
}
|
||||
}
|
||||
if let Some(cycle) = cycle {
|
||||
if !cycle.exists() || cycle.is_dir() {
|
||||
return Err(cycle.clone())?;
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
}
|
@ -0,0 +1,203 @@
|
||||
//! Version information for the file container format, semver-like internal version format.
|
||||
|
||||
use super::*;
|
||||
use std::{
|
||||
fmt,
|
||||
mem::size_of,
|
||||
error,
|
||||
};
|
||||
|
||||
/// Represents other states of this container format version
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd, Copy)]
|
||||
#[repr(u8)]
|
||||
pub enum Tag
|
||||
{
|
||||
None =0,
|
||||
Prerelease =1,
|
||||
Unstable =2,
|
||||
}
|
||||
|
||||
/// The error used by `Tag` and `Version` when they parse invalid input.
|
||||
#[derive(Debug)]
|
||||
pub struct ParsingError;
|
||||
impl error::Error for ParsingError{}
|
||||
impl fmt::Display for ParsingError
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
||||
{
|
||||
write!(f, "An attempt was made to parse invalid Version input")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl Tag
|
||||
{
|
||||
/// Try to parse a `u8` as `Tag`.
|
||||
fn try_from_u8(from: u8) -> Result<Self, ParsingError>
|
||||
{
|
||||
macro_rules! branches {
|
||||
($($num:path),*) => {
|
||||
match from {
|
||||
$(
|
||||
x if x == $num as u8 => $num,
|
||||
)*
|
||||
_ => return Err(ParsingError),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(branches! {
|
||||
Self::Prerelease,
|
||||
Self::Unstable,
|
||||
Self::None
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Tag
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
||||
{
|
||||
match self {
|
||||
Self::Prerelease => write!(f, "*"),
|
||||
Self::Unstable => write!(f, "~"),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Tag
|
||||
{
|
||||
#[inline]
|
||||
fn default() -> Self
|
||||
{
|
||||
Self::None
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a container version number in this format: `Major.Minor.Bugfix.Tag`
|
||||
///
|
||||
/// Should adhear to this principle
|
||||
/// * Major - If different, fail parsing.
|
||||
/// * Minor - If higher that current, fail parsing.
|
||||
/// * Bugfix - If higher than current, warn user.
|
||||
/// * Tag - Unused, but may represent things like prerelease or unstable
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd, Copy, Default)]
|
||||
#[repr(packed, C)]
|
||||
pub struct Version(u8,u8,u8,Tag);
|
||||
|
||||
impl fmt::Display for Version
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
||||
{
|
||||
write!(f, "{}.{}.{}{}", self.0, self.1, self.2, self.3)
|
||||
}
|
||||
}
|
||||
|
||||
impl Version
|
||||
{
|
||||
/// Is this read version compatable with a system of version `other`?
|
||||
pub fn is_compat(&self, other: &Self) -> bool
|
||||
{
|
||||
self.0 == other.0 &&
|
||||
self.1 <= other.1
|
||||
}
|
||||
/// Should we warn about parsing with this version when system version is `other`?
|
||||
pub fn should_warn(&self, other: &Self) -> bool
|
||||
{
|
||||
self.2 > other.2 || self.3 == Tag::Unstable
|
||||
}
|
||||
|
||||
/// Create a new instance of `Version`.
|
||||
pub const fn new(major: u8, minor: u8, bugfix: u8, tag: Tag) -> Self
|
||||
{
|
||||
Self(major,minor,bugfix,tag)
|
||||
}
|
||||
|
||||
/// Encode as u32
|
||||
pub fn as_u32(&self) -> u32
|
||||
{
|
||||
debug_assert_eq!(size_of::<Self>(), size_of::<u32>());
|
||||
unsafe{*(self as *const Self as *const u32)}
|
||||
}
|
||||
|
||||
/// Decode u32 as self, assuming `Tag` is valid.
|
||||
pub unsafe fn from_u32_unchecked(from: u32) -> Self
|
||||
{
|
||||
debug_assert_eq!(size_of::<Self>(), size_of::<u32>());
|
||||
*(&from as *const u32 as *const Self)
|
||||
}
|
||||
|
||||
/// Try to parse a `u32` encoded `Version`.
|
||||
pub fn try_from_u32(from: u32) -> Result<Self, ParsingError>
|
||||
{
|
||||
debug_assert_eq!(size_of::<Self>(), size_of::<u32>());
|
||||
debug_assert_eq!(size_of::<Self>(), 4);
|
||||
let parts = &from as *const u32 as *const u8;
|
||||
|
||||
macro_rules! index {
|
||||
($array:ident, $index:expr) => {
|
||||
unsafe{*($array.offset($index))}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self::new(index!(parts, 0),
|
||||
index!(parts, 1),
|
||||
index!(parts, 2),
|
||||
Tag::try_from_u8(index!(parts, 3))?))
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for Tag
|
||||
{
|
||||
type Error = ParsingError;
|
||||
|
||||
#[inline] fn try_from(from: u8) -> Result<Self, Self::Error>
|
||||
{
|
||||
Self::try_from_u8(from)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Tag> for u8
|
||||
{
|
||||
#[inline] fn from(from: Tag) -> Self
|
||||
{
|
||||
from as u8
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u32> for Version
|
||||
{
|
||||
type Error = ParsingError;
|
||||
|
||||
#[inline] fn try_from(from: u32) -> Result<Self, Self::Error>
|
||||
{
|
||||
Self::try_from_u32(from)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Version> for u32
|
||||
{
|
||||
#[inline] fn from(from: Version) -> Self
|
||||
{
|
||||
from.as_u32()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests
|
||||
{
|
||||
use super::*;
|
||||
#[test]
|
||||
fn vers_encode()
|
||||
{
|
||||
let version1 = Version::new(1,0,0,Tag::Unstable);
|
||||
let version2 = Version::try_from_u32(version1.as_u32()).unwrap();
|
||||
let version3 = unsafe{Version::from_u32_unchecked(version1.as_u32())};
|
||||
assert_eq!(format!("{}", version1), "1.0.0~");
|
||||
assert_eq!(version1, version2);
|
||||
assert_eq!(version2, version3);
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in new issue