use super::timestamp;
use hex::encode;
use nanoid::nanoid;
use sha2::{Digest, Sha256};
use std::time::{SystemTime, UNIX_EPOCH};
use tibba_error::Error;
use uuid::{NoContext, Timestamp, Uuid};
type Result<T> = std::result::Result<T, Error>;
const SIGNATURE_TTL_SECS: i64 = 5 * 60;
pub fn uuid() -> String {
let d = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default();
let ts = Timestamp::from_unix(NoContext, d.as_secs(), d.subsec_nanos());
Uuid::new_v7(ts).to_string()
}
pub fn nanoid(size: usize) -> String {
nanoid!(size)
}
pub fn float_to_fixed(value: f64, precision: usize) -> String {
let p = precision.min(4);
format!("{value:.p$}")
}
fn sha256_multi(parts: &[&[u8]]) -> String {
let mut hasher = Sha256::new();
for part in parts {
hasher.update(part);
}
encode(hasher.finalize())
}
pub fn sha256(data: &[u8]) -> String {
sha256_multi(&[data])
}
pub fn sign_hash(value: &str, secret: &str) -> String {
sha256_multi(&[value.as_bytes(), b":", secret.as_bytes()])
}
pub fn timestamp_hash(value: &str, secret: &str) -> (i64, String) {
let ts = timestamp();
let ts_str = ts.to_string();
let hash = sha256_multi(&[
ts_str.as_bytes(),
b":",
value.as_bytes(),
b":",
secret.as_bytes(),
]);
(ts, hash)
}
pub fn validate_sign_hash(value: &str, hash: &str, secret: &str) -> Result<()> {
if sign_hash(value, secret) != hash {
return Err(Error::new("signature is invalid").with_category("sign_hash"));
}
Ok(())
}
pub fn validate_timestamp_hash(ts: i64, value: &str, hash: &str, secret: &str) -> Result<()> {
let category = "timestamp_hash";
if (timestamp() - ts).abs() > SIGNATURE_TTL_SECS {
return Err(Error::new("signature is expired").with_category(category));
}
let ts_str = ts.to_string();
let expected_hash = sha256_multi(&[
ts_str.as_bytes(),
b":",
value.as_bytes(),
b":",
secret.as_bytes(),
]);
if expected_hash != hash {
return Err(Error::new("signature is invalid").with_category(category));
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::float_to_fixed;
use pretty_assertions::assert_eq;
#[test]
fn to_fixed() {
assert_eq!("1", float_to_fixed(1.123412, 0));
assert_eq!("1.1", float_to_fixed(1.123412, 1));
assert_eq!("1.12", float_to_fixed(1.123412, 2));
assert_eq!("1.123", float_to_fixed(1.123412, 3));
assert_eq!("1.1234", float_to_fixed(1.123412, 4));
}
}