initial commit

master
Avril 4 years ago
commit 0c97a5fcc8
Signed by: flanchan
GPG Key ID: 284488987C31F630

2
.gitignore vendored

@ -0,0 +1,2 @@
/target
*~

1062
Cargo.lock generated

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…
Cancel
Save