1.0.0
Tschuss Status Quo - Hallo, Zukunft!
This commit is contained in:
351
src/crypto/aes.rs
Normal file
351
src/crypto/aes.rs
Normal file
@@ -0,0 +1,351 @@
|
||||
//! AES
|
||||
|
||||
use aes::Aes256;
|
||||
use ctr::{Ctr128BE, cipher::{KeyIvInit, StreamCipher}};
|
||||
use cbc::{Encryptor as CbcEncryptor, Decryptor as CbcDecryptor};
|
||||
use cbc::cipher::{BlockEncryptMut, BlockDecryptMut, block_padding::NoPadding};
|
||||
use crate::error::{ProxyError, Result};
|
||||
|
||||
type Aes256Ctr = Ctr128BE<Aes256>;
|
||||
type Aes256CbcEnc = CbcEncryptor<Aes256>;
|
||||
type Aes256CbcDec = CbcDecryptor<Aes256>;
|
||||
|
||||
/// AES-256-CTR encryptor/decryptor
|
||||
pub struct AesCtr {
|
||||
cipher: Aes256Ctr,
|
||||
}
|
||||
|
||||
impl AesCtr {
|
||||
pub fn new(key: &[u8; 32], iv: u128) -> Self {
|
||||
let iv_bytes = iv.to_be_bytes();
|
||||
Self {
|
||||
cipher: Aes256Ctr::new(key.into(), (&iv_bytes).into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_key_iv(key: &[u8], iv: &[u8]) -> Result<Self> {
|
||||
if key.len() != 32 {
|
||||
return Err(ProxyError::InvalidKeyLength { expected: 32, got: key.len() });
|
||||
}
|
||||
if iv.len() != 16 {
|
||||
return Err(ProxyError::InvalidKeyLength { expected: 16, got: iv.len() });
|
||||
}
|
||||
|
||||
let key: [u8; 32] = key.try_into().unwrap();
|
||||
let iv = u128::from_be_bytes(iv.try_into().unwrap());
|
||||
Ok(Self::new(&key, iv))
|
||||
}
|
||||
|
||||
/// Encrypt/decrypt data in-place (CTR mode is symmetric)
|
||||
pub fn apply(&mut self, data: &mut [u8]) {
|
||||
self.cipher.apply_keystream(data);
|
||||
}
|
||||
|
||||
/// Encrypt data, returning new buffer
|
||||
pub fn encrypt(&mut self, data: &[u8]) -> Vec<u8> {
|
||||
let mut output = data.to_vec();
|
||||
self.apply(&mut output);
|
||||
output
|
||||
}
|
||||
|
||||
/// Decrypt data (for CTR, identical to encrypt)
|
||||
pub fn decrypt(&mut self, data: &[u8]) -> Vec<u8> {
|
||||
self.encrypt(data)
|
||||
}
|
||||
}
|
||||
|
||||
/// AES-256-CBC Ciphermagic
|
||||
pub struct AesCbc {
|
||||
key: [u8; 32],
|
||||
iv: [u8; 16],
|
||||
}
|
||||
|
||||
impl AesCbc {
|
||||
pub fn new(key: [u8; 32], iv: [u8; 16]) -> Self {
|
||||
Self { key, iv }
|
||||
}
|
||||
|
||||
pub fn from_slices(key: &[u8], iv: &[u8]) -> Result<Self> {
|
||||
if key.len() != 32 {
|
||||
return Err(ProxyError::InvalidKeyLength { expected: 32, got: key.len() });
|
||||
}
|
||||
if iv.len() != 16 {
|
||||
return Err(ProxyError::InvalidKeyLength { expected: 16, got: iv.len() });
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
key: key.try_into().unwrap(),
|
||||
iv: iv.try_into().unwrap(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Encrypt data using CBC mode
|
||||
pub fn encrypt(&self, data: &[u8]) -> Result<Vec<u8>> {
|
||||
if data.len() % 16 != 0 {
|
||||
return Err(ProxyError::Crypto(
|
||||
format!("CBC data must be aligned to 16 bytes, got {}", data.len())
|
||||
));
|
||||
}
|
||||
|
||||
if data.is_empty() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
let mut buffer = data.to_vec();
|
||||
|
||||
let mut encryptor = Aes256CbcEnc::new((&self.key).into(), (&self.iv).into());
|
||||
|
||||
for chunk in buffer.chunks_mut(16) {
|
||||
encryptor.encrypt_block_mut(chunk.into());
|
||||
}
|
||||
|
||||
Ok(buffer)
|
||||
}
|
||||
|
||||
/// Decrypt data using CBC mode
|
||||
pub fn decrypt(&self, data: &[u8]) -> Result<Vec<u8>> {
|
||||
if data.len() % 16 != 0 {
|
||||
return Err(ProxyError::Crypto(
|
||||
format!("CBC data must be aligned to 16 bytes, got {}", data.len())
|
||||
));
|
||||
}
|
||||
|
||||
if data.is_empty() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
let mut buffer = data.to_vec();
|
||||
|
||||
let mut decryptor = Aes256CbcDec::new((&self.key).into(), (&self.iv).into());
|
||||
|
||||
for chunk in buffer.chunks_mut(16) {
|
||||
decryptor.decrypt_block_mut(chunk.into());
|
||||
}
|
||||
|
||||
Ok(buffer)
|
||||
}
|
||||
|
||||
/// Encrypt data in-place
|
||||
pub fn encrypt_in_place(&self, data: &mut [u8]) -> Result<()> {
|
||||
if data.len() % 16 != 0 {
|
||||
return Err(ProxyError::Crypto(
|
||||
format!("CBC data must be aligned to 16 bytes, got {}", data.len())
|
||||
));
|
||||
}
|
||||
|
||||
if data.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut encryptor = Aes256CbcEnc::new((&self.key).into(), (&self.iv).into());
|
||||
|
||||
for chunk in data.chunks_mut(16) {
|
||||
encryptor.encrypt_block_mut(chunk.into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Decrypt data in-place
|
||||
pub fn decrypt_in_place(&self, data: &mut [u8]) -> Result<()> {
|
||||
if data.len() % 16 != 0 {
|
||||
return Err(ProxyError::Crypto(
|
||||
format!("CBC data must be aligned to 16 bytes, got {}", data.len())
|
||||
));
|
||||
}
|
||||
|
||||
if data.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut decryptor = Aes256CbcDec::new((&self.key).into(), (&self.iv).into());
|
||||
|
||||
for chunk in data.chunks_mut(16) {
|
||||
decryptor.decrypt_block_mut(chunk.into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait for unified encryption interface
|
||||
pub trait Encryptor: Send + Sync {
|
||||
fn encrypt(&mut self, data: &[u8]) -> Vec<u8>;
|
||||
}
|
||||
|
||||
/// Trait for unified decryption interface
|
||||
pub trait Decryptor: Send + Sync {
|
||||
fn decrypt(&mut self, data: &[u8]) -> Vec<u8>;
|
||||
}
|
||||
|
||||
impl Encryptor for AesCtr {
|
||||
fn encrypt(&mut self, data: &[u8]) -> Vec<u8> {
|
||||
AesCtr::encrypt(self, data)
|
||||
}
|
||||
}
|
||||
|
||||
impl Decryptor for AesCtr {
|
||||
fn decrypt(&mut self, data: &[u8]) -> Vec<u8> {
|
||||
AesCtr::decrypt(self, data)
|
||||
}
|
||||
}
|
||||
|
||||
/// No-op encryptor for fast mode
|
||||
pub struct PassthroughEncryptor;
|
||||
|
||||
impl Encryptor for PassthroughEncryptor {
|
||||
fn encrypt(&mut self, data: &[u8]) -> Vec<u8> {
|
||||
data.to_vec()
|
||||
}
|
||||
}
|
||||
|
||||
impl Decryptor for PassthroughEncryptor {
|
||||
fn decrypt(&mut self, data: &[u8]) -> Vec<u8> {
|
||||
data.to_vec()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_aes_ctr_roundtrip() {
|
||||
let key = [0u8; 32];
|
||||
let iv = 12345u128;
|
||||
|
||||
let original = b"Hello, MTProto!";
|
||||
|
||||
let mut enc = AesCtr::new(&key, iv);
|
||||
let encrypted = enc.encrypt(original);
|
||||
|
||||
let mut dec = AesCtr::new(&key, iv);
|
||||
let decrypted = dec.decrypt(&encrypted);
|
||||
|
||||
assert_eq!(original.as_slice(), decrypted.as_slice());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_aes_cbc_roundtrip() {
|
||||
let key = [0u8; 32];
|
||||
let iv = [0u8; 16];
|
||||
|
||||
// Must be aligned to 16 bytes
|
||||
let original = [0u8; 32];
|
||||
|
||||
let cipher = AesCbc::new(key, iv);
|
||||
let encrypted = cipher.encrypt(&original).unwrap();
|
||||
let decrypted = cipher.decrypt(&encrypted).unwrap();
|
||||
|
||||
assert_eq!(original.as_slice(), decrypted.as_slice());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_aes_cbc_chaining_works() {
|
||||
let key = [0x42u8; 32];
|
||||
let iv = [0x00u8; 16];
|
||||
|
||||
let plaintext = [0xAA_u8; 32];
|
||||
|
||||
let cipher = AesCbc::new(key, iv);
|
||||
let ciphertext = cipher.encrypt(&plaintext).unwrap();
|
||||
|
||||
// CBC Corrections
|
||||
let block1 = &ciphertext[0..16];
|
||||
let block2 = &ciphertext[16..32];
|
||||
|
||||
assert_ne!(block1, block2, "CBC chaining broken: identical plaintext blocks produced identical ciphertext");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_aes_cbc_known_vector() {
|
||||
let key = [0u8; 32];
|
||||
let iv = [0u8; 16];
|
||||
|
||||
// 3 Datablocks
|
||||
let plaintext = [
|
||||
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
|
||||
0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF,
|
||||
// Block 2
|
||||
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
|
||||
0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF,
|
||||
// Block 3 - different
|
||||
0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA, 0x99, 0x88,
|
||||
0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x00,
|
||||
];
|
||||
|
||||
let cipher = AesCbc::new(key, iv);
|
||||
let ciphertext = cipher.encrypt(&plaintext).unwrap();
|
||||
|
||||
// Decrypt + Verify
|
||||
let decrypted = cipher.decrypt(&ciphertext).unwrap();
|
||||
assert_eq!(plaintext.as_slice(), decrypted.as_slice());
|
||||
|
||||
// Verify Ciphertexts Block 1 != Block 2
|
||||
assert_ne!(&ciphertext[0..16], &ciphertext[16..32]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_aes_cbc_in_place() {
|
||||
let key = [0x12u8; 32];
|
||||
let iv = [0x34u8; 16];
|
||||
|
||||
let original = [0x56u8; 48]; // 3 blocks
|
||||
let mut buffer = original.clone();
|
||||
|
||||
let cipher = AesCbc::new(key, iv);
|
||||
|
||||
cipher.encrypt_in_place(&mut buffer).unwrap();
|
||||
assert_ne!(&buffer[..], &original[..]);
|
||||
|
||||
cipher.decrypt_in_place(&mut buffer).unwrap();
|
||||
assert_eq!(&buffer[..], &original[..]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_aes_cbc_empty_data() {
|
||||
let cipher = AesCbc::new([0u8; 32], [0u8; 16]);
|
||||
|
||||
let encrypted = cipher.encrypt(&[]).unwrap();
|
||||
assert!(encrypted.is_empty());
|
||||
|
||||
let decrypted = cipher.decrypt(&[]).unwrap();
|
||||
assert!(decrypted.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_aes_cbc_unaligned_error() {
|
||||
let cipher = AesCbc::new([0u8; 32], [0u8; 16]);
|
||||
|
||||
// 15 bytes
|
||||
let result = cipher.encrypt(&[0u8; 15]);
|
||||
assert!(result.is_err());
|
||||
|
||||
// 17 bytes
|
||||
let result = cipher.encrypt(&[0u8; 17]);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_aes_cbc_avalanche_effect() {
|
||||
// Cipherplane
|
||||
|
||||
let key = [0xAB; 32];
|
||||
let iv = [0xCD; 16];
|
||||
|
||||
let mut plaintext1 = [0u8; 32];
|
||||
let mut plaintext2 = [0u8; 32];
|
||||
plaintext2[0] = 0x01; // Один бит отличается
|
||||
|
||||
let cipher = AesCbc::new(key, iv);
|
||||
|
||||
let ciphertext1 = cipher.encrypt(&plaintext1).unwrap();
|
||||
let ciphertext2 = cipher.encrypt(&plaintext2).unwrap();
|
||||
|
||||
// First Blocks Diff
|
||||
assert_ne!(&ciphertext1[0..16], &ciphertext2[0..16]);
|
||||
|
||||
// Second Blocks Diff
|
||||
assert_ne!(&ciphertext1[16..32], &ciphertext2[16..32]);
|
||||
}
|
||||
}
|
||||
90
src/crypto/hash.rs
Normal file
90
src/crypto/hash.rs
Normal file
@@ -0,0 +1,90 @@
|
||||
use hmac::{Hmac, Mac};
|
||||
use sha2::Sha256;
|
||||
use md5::Md5;
|
||||
use sha1::Sha1;
|
||||
use sha2::Digest;
|
||||
|
||||
type HmacSha256 = Hmac<Sha256>;
|
||||
|
||||
/// SHA-256
|
||||
pub fn sha256(data: &[u8]) -> [u8; 32] {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(data);
|
||||
hasher.finalize().into()
|
||||
}
|
||||
|
||||
/// SHA-256 HMAC
|
||||
pub fn sha256_hmac(key: &[u8], data: &[u8]) -> [u8; 32] {
|
||||
let mut mac = HmacSha256::new_from_slice(key)
|
||||
.expect("HMAC accepts any key length");
|
||||
mac.update(data);
|
||||
mac.finalize().into_bytes().into()
|
||||
}
|
||||
|
||||
/// SHA-1
|
||||
pub fn sha1(data: &[u8]) -> [u8; 20] {
|
||||
let mut hasher = Sha1::new();
|
||||
hasher.update(data);
|
||||
hasher.finalize().into()
|
||||
}
|
||||
|
||||
/// MD5
|
||||
pub fn md5(data: &[u8]) -> [u8; 16] {
|
||||
let mut hasher = Md5::new();
|
||||
hasher.update(data);
|
||||
hasher.finalize().into()
|
||||
}
|
||||
|
||||
/// CRC32
|
||||
pub fn crc32(data: &[u8]) -> u32 {
|
||||
crc32fast::hash(data)
|
||||
}
|
||||
|
||||
/// Middle Proxy Keygen
|
||||
pub fn derive_middleproxy_keys(
|
||||
nonce_srv: &[u8; 16],
|
||||
nonce_clt: &[u8; 16],
|
||||
clt_ts: &[u8; 4],
|
||||
srv_ip: Option<&[u8]>,
|
||||
clt_port: &[u8; 2],
|
||||
purpose: &[u8],
|
||||
clt_ip: Option<&[u8]>,
|
||||
srv_port: &[u8; 2],
|
||||
secret: &[u8],
|
||||
clt_ipv6: Option<&[u8; 16]>,
|
||||
srv_ipv6: Option<&[u8; 16]>,
|
||||
) -> ([u8; 32], [u8; 16]) {
|
||||
const EMPTY_IP: [u8; 4] = [0, 0, 0, 0];
|
||||
|
||||
let srv_ip = srv_ip.unwrap_or(&EMPTY_IP);
|
||||
let clt_ip = clt_ip.unwrap_or(&EMPTY_IP);
|
||||
|
||||
let mut s = Vec::with_capacity(256);
|
||||
s.extend_from_slice(nonce_srv);
|
||||
s.extend_from_slice(nonce_clt);
|
||||
s.extend_from_slice(clt_ts);
|
||||
s.extend_from_slice(srv_ip);
|
||||
s.extend_from_slice(clt_port);
|
||||
s.extend_from_slice(purpose);
|
||||
s.extend_from_slice(clt_ip);
|
||||
s.extend_from_slice(srv_port);
|
||||
s.extend_from_slice(secret);
|
||||
s.extend_from_slice(nonce_srv);
|
||||
|
||||
if let (Some(clt_v6), Some(srv_v6)) = (clt_ipv6, srv_ipv6) {
|
||||
s.extend_from_slice(clt_v6);
|
||||
s.extend_from_slice(srv_v6);
|
||||
}
|
||||
|
||||
s.extend_from_slice(nonce_clt);
|
||||
|
||||
let md5_1 = md5(&s[1..]);
|
||||
let sha1_sum = sha1(&s);
|
||||
let md5_2 = md5(&s[2..]);
|
||||
|
||||
let mut key = [0u8; 32];
|
||||
key[..12].copy_from_slice(&md5_1[..12]);
|
||||
key[12..].copy_from_slice(&sha1_sum);
|
||||
|
||||
(key, md5_2)
|
||||
}
|
||||
9
src/crypto/mod.rs
Normal file
9
src/crypto/mod.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
//! Crypto
|
||||
|
||||
pub mod aes;
|
||||
pub mod hash;
|
||||
pub mod random;
|
||||
|
||||
pub use aes::{AesCtr, AesCbc};
|
||||
pub use hash::{sha256, sha256_hmac, sha1, md5, crc32};
|
||||
pub use random::{SecureRandom, SECURE_RANDOM};
|
||||
212
src/crypto/random.rs
Normal file
212
src/crypto/random.rs
Normal file
@@ -0,0 +1,212 @@
|
||||
//! Pseudorandom
|
||||
|
||||
use rand::{Rng, RngCore, SeedableRng};
|
||||
use rand::rngs::StdRng;
|
||||
use parking_lot::Mutex;
|
||||
use crate::crypto::AesCtr;
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
/// Global secure random instance
|
||||
pub static SECURE_RANDOM: Lazy<SecureRandom> = Lazy::new(SecureRandom::new);
|
||||
|
||||
/// Cryptographically secure PRNG with AES-CTR
|
||||
pub struct SecureRandom {
|
||||
inner: Mutex<SecureRandomInner>,
|
||||
}
|
||||
|
||||
struct SecureRandomInner {
|
||||
rng: StdRng,
|
||||
cipher: AesCtr,
|
||||
buffer: Vec<u8>,
|
||||
}
|
||||
|
||||
impl SecureRandom {
|
||||
pub fn new() -> Self {
|
||||
let mut rng = StdRng::from_entropy();
|
||||
|
||||
let mut key = [0u8; 32];
|
||||
rng.fill_bytes(&mut key);
|
||||
let iv: u128 = rng.gen();
|
||||
|
||||
Self {
|
||||
inner: Mutex::new(SecureRandomInner {
|
||||
rng,
|
||||
cipher: AesCtr::new(&key, iv),
|
||||
buffer: Vec::with_capacity(1024),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate random bytes
|
||||
pub fn bytes(&self, len: usize) -> Vec<u8> {
|
||||
let mut inner = self.inner.lock();
|
||||
const CHUNK_SIZE: usize = 512;
|
||||
|
||||
while inner.buffer.len() < len {
|
||||
let mut chunk = vec![0u8; CHUNK_SIZE];
|
||||
inner.rng.fill_bytes(&mut chunk);
|
||||
inner.cipher.apply(&mut chunk);
|
||||
inner.buffer.extend_from_slice(&chunk);
|
||||
}
|
||||
|
||||
inner.buffer.drain(..len).collect()
|
||||
}
|
||||
|
||||
/// Generate random number in range [0, max)
|
||||
pub fn range(&self, max: usize) -> usize {
|
||||
if max == 0 {
|
||||
return 0;
|
||||
}
|
||||
let mut inner = self.inner.lock();
|
||||
inner.rng.gen_range(0..max)
|
||||
}
|
||||
|
||||
/// Generate random bits
|
||||
pub fn bits(&self, k: usize) -> u64 {
|
||||
if k == 0 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let bytes_needed = (k + 7) / 8;
|
||||
let bytes = self.bytes(bytes_needed.min(8));
|
||||
|
||||
let mut result = 0u64;
|
||||
for (i, &b) in bytes.iter().enumerate() {
|
||||
if i >= 8 {
|
||||
break;
|
||||
}
|
||||
result |= (b as u64) << (i * 8);
|
||||
}
|
||||
|
||||
// Mask extra bits
|
||||
if k < 64 {
|
||||
result &= (1u64 << k) - 1;
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Choose random element from slice
|
||||
pub fn choose<'a, T>(&self, slice: &'a [T]) -> Option<&'a T> {
|
||||
if slice.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(&slice[self.range(slice.len())])
|
||||
}
|
||||
}
|
||||
|
||||
/// Shuffle slice in place
|
||||
pub fn shuffle<T>(&self, slice: &mut [T]) {
|
||||
let mut inner = self.inner.lock();
|
||||
for i in (1..slice.len()).rev() {
|
||||
let j = inner.rng.gen_range(0..=i);
|
||||
slice.swap(i, j);
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate random u32
|
||||
pub fn u32(&self) -> u32 {
|
||||
let mut inner = self.inner.lock();
|
||||
inner.rng.gen()
|
||||
}
|
||||
|
||||
/// Generate random u64
|
||||
pub fn u64(&self) -> u64 {
|
||||
let mut inner = self.inner.lock();
|
||||
inner.rng.gen()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SecureRandom {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::collections::HashSet;
|
||||
|
||||
#[test]
|
||||
fn test_bytes_uniqueness() {
|
||||
let rng = SecureRandom::new();
|
||||
let a = rng.bytes(32);
|
||||
let b = rng.bytes(32);
|
||||
assert_ne!(a, b);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bytes_length() {
|
||||
let rng = SecureRandom::new();
|
||||
assert_eq!(rng.bytes(0).len(), 0);
|
||||
assert_eq!(rng.bytes(1).len(), 1);
|
||||
assert_eq!(rng.bytes(100).len(), 100);
|
||||
assert_eq!(rng.bytes(1000).len(), 1000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_range() {
|
||||
let rng = SecureRandom::new();
|
||||
|
||||
for _ in 0..1000 {
|
||||
let n = rng.range(10);
|
||||
assert!(n < 10);
|
||||
}
|
||||
|
||||
assert_eq!(rng.range(1), 0);
|
||||
assert_eq!(rng.range(0), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bits() {
|
||||
let rng = SecureRandom::new();
|
||||
|
||||
// Single bit should be 0 or 1
|
||||
for _ in 0..100 {
|
||||
assert!(rng.bits(1) <= 1);
|
||||
}
|
||||
|
||||
// 8 bits should be 0-255
|
||||
for _ in 0..100 {
|
||||
assert!(rng.bits(8) <= 255);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_choose() {
|
||||
let rng = SecureRandom::new();
|
||||
let items = vec![1, 2, 3, 4, 5];
|
||||
|
||||
let mut seen = HashSet::new();
|
||||
for _ in 0..1000 {
|
||||
if let Some(&item) = rng.choose(&items) {
|
||||
seen.insert(item);
|
||||
}
|
||||
}
|
||||
|
||||
// Should have seen all items
|
||||
assert_eq!(seen.len(), 5);
|
||||
|
||||
// Empty slice should return None
|
||||
let empty: Vec<i32> = vec![];
|
||||
assert!(rng.choose(&empty).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_shuffle() {
|
||||
let rng = SecureRandom::new();
|
||||
let original = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
||||
|
||||
let mut shuffled = original.clone();
|
||||
rng.shuffle(&mut shuffled);
|
||||
|
||||
// Should contain same elements
|
||||
let mut sorted = shuffled.clone();
|
||||
sorted.sort();
|
||||
assert_eq!(sorted, original);
|
||||
|
||||
// Should be different order (with very high probability)
|
||||
assert_ne!(shuffled, original);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user