use std::{fmt, str::FromStr};
use anyhow::{Context, Error};
use base64::{Engine as _, engine::general_purpose};
use lazy_static::lazy_static;
use regex::Regex;
use super::keys::{Key, Nonce};
lazy_static! {
static ref ENCODER: base64::engine::GeneralPurpose = general_purpose::STANDARD;
static ref PATTERN: Regex =
Regex::new(r"^EJ\[\d:[A-Za-z0-9+=/]{44}:[A-Za-z0-9+=/]{32}:(.+)\]$").unwrap();
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct Message {
pub version: u8,
pub key: Key,
pub nonce: Nonce,
pub value: Vec<u8>,
}
impl Message {
pub fn is_valid(encoded_str: &str) -> bool {
PATTERN.is_match(encoded_str)
}
}
impl fmt::Display for Message {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"EJ[{}:{}:{}:{}]",
self.version,
ENCODER.encode(self.key.0),
ENCODER.encode(self.nonce.0),
ENCODER.encode(self.value.as_slice()),
)
}
}
impl FromStr for Message {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if !Self::is_valid(s) {
return Err(anyhow::anyhow!("Oops"));
}
let key = ENCODER.decode(&s[5..49]).context("decoding key")?;
let nonce = ENCODER.decode(&s[50..82]).context("decoding nonce")?;
let value = ENCODER.decode(&s[83..s.len() - 1]).context("decoding value")?;
Ok(Self {
version: s[3..4].parse()?,
key: Key(key.as_slice().try_into()?),
nonce: Nonce(nonce.as_slice().try_into()?),
value,
})
}
}
#[cfg(test)]
mod test {
use super::*;
const VALID_KEY: &str = "EJ[1:AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE=:AgICAgICAgICAgICAgICAgICAgICAgIC:AwMD]";
#[test]
fn is_valid() {
let cases = [(VALID_KEY, true), ("nope", false), ("EJ[]", false)];
cases.into_iter().for_each(|(given, exp)| {
assert_eq!(exp, Message::is_valid(given), "{} != {}", given, exp);
});
}
#[test]
fn display() {
let msg = Message {
version: 1,
key: Key::all(1),
nonce: Nonce::all(2),
value: vec![3, 3, 3],
};
assert_eq!(VALID_KEY, msg.to_string());
}
#[test]
fn from_str() {
assert!("nope".parse::<Message>().is_err());
let parsed: Message = VALID_KEY.parse().unwrap();
let exp = Message {
version: 1,
key: Key::all(1),
nonce: Nonce::all(2),
value: vec![3, 3, 3],
};
assert_eq!(exp, parsed);
}
}