use aes_gcm::aead::Aead;
use aes_gcm::aead::KeyInit;
use aes_gcm::Aes256Gcm;
use aes_gcm::Nonce;
use anyhow::Error;
use rand::RngCore;
pub fn aes_encrypt(data: &[u8], key: &[u8]) -> Result<Vec<u8>, Error> {
if key.is_empty() {
return Err(anyhow::anyhow!("encryption key must not be empty"));
}
let key_hash = blake3::hash(key);
let cipher = Aes256Gcm::new_from_slice(key_hash.as_bytes())
.map_err(|_| anyhow::anyhow!("failed to initialize AES-256-GCM"))?;
let mut nonce_bytes = [0_u8; 12];
rand::rngs::OsRng.fill_bytes(&mut nonce_bytes);
let nonce = Nonce::from_slice(&nonce_bytes);
let mut output = Vec::with_capacity(12 + data.len() + 16);
output.extend_from_slice(&nonce_bytes);
let ciphertext = cipher
.encrypt(nonce, data)
.map_err(|_| anyhow::anyhow!("AES-256-GCM encryption failed"))?;
output.extend_from_slice(&ciphertext);
Ok(output)
}
#[must_use]
pub fn calculate_distance(lat1: f64, lon1: f64, lat2: f64, lon2: f64) -> f64 {
const EARTH_RADIUS_KM: f64 = 6371.0;
let dlat = (lat2 - lat1).to_radians();
let dlon = (lon2 - lon1).to_radians();
let lat1_rad = lat1.to_radians();
let lat2_rad = lat2.to_radians();
let a =
(dlat / 2.0).sin().powi(2) + lat1_rad.cos() * lat2_rad.cos() * (dlon / 2.0).sin().powi(2);
let c = 2.0 * a.sqrt().atan2((1.0 - a).sqrt());
EARTH_RADIUS_KM * c
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn aes_encrypt_returns_non_plaintext_with_nonce_prefix() {
let key = b"this-is-a-very-strong-test-key-material";
let data = b"sensitive-payload";
let encrypted = aes_encrypt(data, key).expect("encryption should succeed");
assert!(encrypted.len() > data.len());
assert_ne!(&encrypted[12..], data);
}
#[test]
fn haversine_distance_is_reasonable() {
let distance = calculate_distance(24.7136, 46.6753, 21.4858, 39.1925);
assert!((distance - 847.0).abs() < 25.0);
}
}