use crate::error::VecboostError;
use rand::Rng;
#[derive(Debug, Clone)]
pub struct SaltStore {
salt: [u8; 16],
}
impl SaltStore {
pub fn generate() -> Self {
let mut salt = [0u8; 16];
rand::rng().fill_bytes(&mut salt);
Self { salt }
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, VecboostError> {
if bytes.len() != 16 {
return Err(VecboostError::security_error(format!(
"salt must be 16 bytes, got {}",
bytes.len()
)));
}
let mut salt = [0u8; 16];
salt.copy_from_slice(bytes);
Ok(Self { salt })
}
pub fn from_hex(hex_str: &str) -> Result<Self, VecboostError> {
let bytes = hex::decode(hex_str)
.map_err(|e| VecboostError::security_error(format!("invalid salt hex: {}", e)))?;
Self::from_bytes(&bytes)
}
pub fn to_hex(&self) -> String {
hex::encode(self.salt)
}
pub fn as_bytes(&self) -> &[u8] {
&self.salt
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_generate_produces_16_byte_salt() {
let salt = SaltStore::generate();
assert_eq!(salt.as_bytes().len(), 16);
}
#[test]
fn test_generate_produces_distinct_salts() {
let s1 = SaltStore::generate();
let s2 = SaltStore::generate();
assert_ne!(s1.as_bytes(), s2.as_bytes(), "salts should be distinct");
}
#[test]
fn test_from_bytes_roundtrip() {
let original = SaltStore::generate();
let bytes = original.as_bytes().to_vec();
let restored = SaltStore::from_bytes(&bytes).expect("from_bytes should succeed");
assert_eq!(original.as_bytes(), restored.as_bytes());
}
#[test]
fn test_from_bytes_rejects_wrong_length() {
let too_short = vec![0u8; 15];
let result = SaltStore::from_bytes(&too_short);
assert!(result.is_err(), "15-byte salt should be rejected");
let too_long = vec![0u8; 17];
let result = SaltStore::from_bytes(&too_long);
assert!(result.is_err(), "17-byte salt should be rejected");
}
#[test]
fn test_from_hex_roundtrip() {
let original = SaltStore::generate();
let hex = original.to_hex();
assert_eq!(hex.len(), 32, "16 bytes → 32 hex chars");
let restored = SaltStore::from_hex(&hex).expect("from_hex should succeed");
assert_eq!(original.as_bytes(), restored.as_bytes());
}
#[test]
fn test_from_hex_rejects_invalid_hex() {
let result = SaltStore::from_hex("not_valid_hex");
assert!(result.is_err(), "invalid hex should be rejected");
}
#[test]
fn test_from_hex_rejects_wrong_byte_count() {
let result = SaltStore::from_hex("000000000000000000000000000000");
assert!(
result.is_err(),
"30 hex chars (15 bytes) should be rejected"
);
let result = SaltStore::from_hex("0000000000000000000000000000000000");
assert!(
result.is_err(),
"34 hex chars (17 bytes) should be rejected"
);
}
}