use std::fmt::{Write, Display};
use sha2::{Sha512, Digest};
use chrono::Utc;
#[derive(Debug, Clone)]
pub struct RCPConfig {
pub shared_secret: String,
pub use_time_component: bool,
pub time_delta: i64,
}
impl Default for RCPConfig {
fn default() -> Self {
RCPConfig {
shared_secret: String::new(),
use_time_component: true,
time_delta: 5
}
}
}
impl RCPConfig {
pub fn get_checksum<R: Request>(&self, request: R, salt: &str) -> String {
let mut string = pre_assemble(request, &self.shared_secret, salt);
if self.use_time_component {
write!(string, "{}", Utc::now().timestamp()).unwrap();
}
sha512(string)
}
pub fn validate_checksum<R: Request>(&self, request: R, salt: &str, checksum: &str) -> bool {
if self.use_time_component {
let string = pre_assemble(request, &self.shared_secret, salt);
let now = Utc::now().timestamp();
for delta in (-self.time_delta)..(self.time_delta) {
let string = format!("{}{}", string, now + delta);
if sha512(string) == checksum {
return true;
}
}
false
} else {
self.get_checksum(request, salt) == checksum
}
}
}
fn pre_assemble(request: impl Request, shared_secret: &str, salt: &str) -> String {
let mut pairs = request.into_pairs();
pairs.sort_by(|(k1, _), (k2, _)| k1.as_ref().cmp(k2.as_ref()));
let mut string = salt.to_string();
for (key, value) in pairs.into_iter() {
write!(string, "{}", key.as_ref()).unwrap();
write!(string, "{}", value).unwrap();
}
write!(string, "{}", shared_secret).unwrap();
string
}
fn sha512(data: impl AsRef<[u8]>) -> String {
let mut hasher = Sha512::new();
hasher.update(data);
let bytes = &hasher.finalize()[..];
let mut string = String::with_capacity(bytes.len() * 2);
for byte in bytes {
write!(string, "{:02x}", byte).unwrap();
}
string
}
pub trait Request {
type Key: AsRef<str>;
type Value: Display;
fn into_pairs(self) -> Vec<(Self::Key, Self::Value)>;
}
impl<I, K, V> Request for I
where
I: IntoIterator<Item=(K, V)>,
K: AsRef<str>,
V: Display,
{
type Key = K;
type Value = V;
fn into_pairs(self) -> Vec<(Self::Key, Self::Value)> {
self.into_iter().collect()
}
}
#[cfg(test)]
mod test {
use std::collections::HashMap;
use std::fmt::Display;
use crate::RCPConfig;
#[test]
fn syntax() {
let config = RCPConfig::default();
let mut request = HashMap::new();
request.insert("foo".to_string(), 4);
request.insert("bar".to_string(), 10);
request.insert("bar".to_string(), -7);
config.get_checksum(request, "");
let mut request = Vec::new();
request.push(("foo", "4"));
request.push(("bar", "10"));
request.push(("baz", "-7"));
config.get_checksum(request, "");
let request = [
("foo", 4.0),
("bar", 10.0),
("baz", -7.0),
];
config.get_checksum(request, "");
let request = [
("foo", &4 as &dyn Display),
("bar", &"10" as &dyn Display),
("baz", &-7.0 as &dyn Display),
];
config.get_checksum(request, "");
config.get_checksum::<[(&'static str, u8); 0]>([], "");
}
#[test]
fn validate_checksum() {
let mut config = RCPConfig {
use_time_component: false,
shared_secret: "Hallo-123".to_string(),
time_delta: 5,
};
let request = [("b", "test"), ("a", " long test")];
let checksum = "477cec82c1c05f7acd42e4c9bd354f3021a59f9a0e8f6cca451c74511a75a8ee0aa4cddcf0a966e91de09b5708d26ce2a7737b65f286a368c87e751135cdc706";
assert!(config.validate_checksum::<[(&str, u8); 0]>([], "", checksum));
let checksum = "a85a29e01f295cba43de859a097b6f816826a0ef47bad9d210ab1410cc6ea8490f72a99e62c27b3aefd3b334b1a034d1b8ba1b8b0c6599c27674aeb96cebd591";
assert!(config.validate_checksum(request, "TestSalt", checksum));
config.use_time_component = true;
let checksum = config.get_checksum(request, "TestSalt");
assert!(config.validate_checksum(request, "TestSalt", &checksum));
}
#[test]
fn get_checksum() {
let config = RCPConfig {
use_time_component: false,
shared_secret: "Hallo-123".to_string(),
time_delta: 5,
};
let checksum = "477cec82c1c05f7acd42e4c9bd354f3021a59f9a0e8f6cca451c74511a75a8ee0aa4cddcf0a966e91de09b5708d26ce2a7737b65f286a368c87e751135cdc706";
assert_eq!(config.get_checksum::<[(&str, u8); 0]>([], ""), checksum);
let checksum = "50acbd16790dc2ebcc246ea9050acf4bee79088d1a9b0a0cd9f812a3b054b7c39e6ce44aa9c6e53b6d31c9d7da527cdd9a85ecaf2f5d007533d4cde289432683";
assert_eq!(config.get_checksum::<[(&str, u8); 0]>([], "TestSalt"), checksum);
let checksum = "a85a29e01f295cba43de859a097b6f816826a0ef47bad9d210ab1410cc6ea8490f72a99e62c27b3aefd3b334b1a034d1b8ba1b8b0c6599c27674aeb96cebd591";
assert_eq!(config.get_checksum([("b", "test"), ("a", " long test")], "TestSalt"), checksum);
}
}