use crate::EzNaclError;
use base85::{decode, encode};
use regex::Regex;
use std::fmt;
use std::str::FromStr;
lazy_static! {
static ref RE_CRYPTOSTRING_FORMAT: regex::Regex =
Regex::new(r"^([A-Z0-9-]{1,24}):([0-9A-Za-z!#$%&()*+-;<=>?@^_`{|}~]+)$").unwrap();
static ref RE_CRYPTOSTRING_PREFIX: regex::Regex = Regex::new(r"^([A-Z0-9-]{1,24})$").unwrap();
}
#[derive(Debug, Clone, PartialEq, PartialOrd)]
#[cfg_attr(feature = "use_serde", derive(serde::Serialize, serde::Deserialize))]
pub struct CryptoString {
string: String,
}
impl fmt::Display for CryptoString {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.string)
}
}
impl FromStr for CryptoString {
type Err = ();
fn from_str(input: &str) -> Result<CryptoString, Self::Err> {
match CryptoString::from(input) {
Some(v) => Ok(v),
None => Err(()),
}
}
}
impl std::convert::TryFrom<&str> for CryptoString {
type Error = EzNaclError;
fn try_from(input: &str) -> Result<Self, Self::Error> {
match CryptoString::from(input) {
Some(v) => Ok(v),
None => Err(EzNaclError::ValueError),
}
}
}
impl CryptoString {
pub fn from(s: &str) -> Option<CryptoString> {
let caps = RE_CRYPTOSTRING_FORMAT.captures(s);
match caps {
Some(_) => Some(CryptoString {
string: String::from(s),
}),
_ => None,
}
}
pub fn from_bytes(algorithm: &str, buffer: &[u8]) -> Option<CryptoString> {
if !RE_CRYPTOSTRING_PREFIX.is_match(algorithm) {
return None;
}
let mut out = CryptoString {
string: String::from(algorithm) + ":",
};
let encstr = encode(buffer);
if encstr.len() == 0 {
return None;
}
out.string.push_str(&encstr);
Some(out)
}
pub fn as_bytes(&self) -> &[u8] {
self.string.as_bytes()
}
pub fn as_raw(&self) -> Vec<u8> {
let list: Vec<&str> = self.string.split(":").collect();
return decode(list[1]).unwrap();
}
pub fn as_str(&self) -> &str {
self.string.as_str()
}
pub fn is_empty(&self) -> bool {
self.string.is_empty()
}
pub fn prefix(&self) -> &str {
let list: Vec<&str> = self.string.split(":").collect();
list[0]
}
pub fn data(&self) -> &str {
let list: Vec<&str> = self.string.split(":").collect();
list[1]
}
}
#[cfg(test)]
mod tests {
#[test]
fn cs_test_from() {
let testlist = [
"ED25519:r#r*RiXIN-0n)BzP3bv`LA&t4LFEQNF0Q@$N~RF*",
"BLAKE2B-256:-Zz4O7J;m#-rB)2llQ*xTHjtblwm&kruUVa_v(&W",
"CURVE25519:SNhj2K`hgBd8>G>lW$!pXiM7S-B!Fbd9jT2&{{Az",
];
for test in testlist.iter() {
match crate::CryptoString::from(test) {
None => panic!("from() test failure"),
_ => { }
}
}
let faillist = ["ED25519", ":123456789", "$ILLEGAL:123456789", " ", ""];
for test in faillist.iter() {
match crate::CryptoString::from(test) {
None => { }
_ => panic!("from() bad value test failure"),
}
}
}
#[test]
fn cs_test_from_bytes_plus() {
let faillist = [("", ":123456789"), ("$ILLEGAL", "123456789"), ("TEST", "")];
for test in faillist.iter() {
match crate::CryptoString::from_bytes(test.0, test.1.as_bytes()) {
None => { }
_ => panic!("from_bytes() bad value test failure"),
}
}
let cs = match crate::CryptoString::from_bytes("TEST", b"aaaaaa") {
Some(v) => v,
_ => panic!("from_bytes() test failure"),
};
assert_eq!(cs.prefix(), "TEST");
assert_eq!(cs.as_str(), "TEST:VPRomVPO");
assert_eq!(cs.as_bytes(), b"TEST:VPRomVPO");
assert_eq!(cs.as_raw(), b"aaaaaa");
assert_eq!(cs.is_empty(), false);
}
}