use oxidize_pdf::encryption::compute_hash_r6_algorithm_2b;
#[test]
fn test_algorithm_2b_qpdf_compatibility() {
let password = b"user6";
let validation_salt = [0xfd, 0x0f, 0x02, 0xfd, 0xee, 0x2f, 0xff, 0xe1];
let expected_hash = [
0x30, 0x0d, 0x98, 0xeb, 0x38, 0x16, 0xf4, 0x5e, 0x79, 0x00, 0x7d, 0x78, 0xd2, 0x85, 0xfd,
0x18, 0x78, 0x4e, 0x35, 0x4b, 0x12, 0x79, 0xaf, 0x3b, 0x47, 0x04, 0xf6, 0xbb, 0xa1, 0xac,
0x02, 0x70,
];
let computed_hash = compute_hash_r6_algorithm_2b(password, &validation_salt, &[]).unwrap();
println!("Expected: {:02x?}", expected_hash);
println!("Computed: {:02x?}", computed_hash);
assert_eq!(
computed_hash, expected_hash,
"Algorithm 2.B output should match qpdf R6 implementation"
);
}
#[test]
fn test_algorithm_2b_basic_execution() {
let password = b"test_password";
let salt = b"12345678";
let hash = compute_hash_r6_algorithm_2b(password, salt, &[]).unwrap();
assert_eq!(hash.len(), 32, "Algorithm 2.B must return 32 bytes");
assert!(hash.iter().any(|&b| b != 0), "Hash should not be all zeros");
}
#[test]
fn test_algorithm_2b_deterministic() {
let password = b"deterministic_test";
let salt = b"saltsalt";
let hash1 = compute_hash_r6_algorithm_2b(password, salt, &[]).unwrap();
let hash2 = compute_hash_r6_algorithm_2b(password, salt, &[]).unwrap();
assert_eq!(hash1, hash2, "Algorithm 2.B must be deterministic");
}
#[test]
fn test_algorithm_2b_different_passwords() {
let salt = b"12345678";
let hash1 = compute_hash_r6_algorithm_2b(b"password1", salt, &[]).unwrap();
let hash2 = compute_hash_r6_algorithm_2b(b"password2", salt, &[]).unwrap();
assert_ne!(
hash1, hash2,
"Different passwords must produce different hashes"
);
}
#[test]
fn test_algorithm_2b_different_salts() {
let password = b"test_password";
let hash1 = compute_hash_r6_algorithm_2b(password, b"salt1234", &[]).unwrap();
let hash2 = compute_hash_r6_algorithm_2b(password, b"abcd5678", &[]).unwrap();
assert_ne!(
hash1, hash2,
"Different salts must produce different hashes"
);
}
#[test]
fn test_algorithm_2b_empty_password() {
let hash = compute_hash_r6_algorithm_2b(b"", b"12345678", &[]).unwrap();
assert_eq!(hash.len(), 32);
}
#[test]
fn test_algorithm_2b_unicode_password() {
let unicode_pwd = "café🔒".as_bytes();
let salt = b"12345678";
let hash = compute_hash_r6_algorithm_2b(unicode_pwd, salt, &[]).unwrap();
assert_eq!(hash.len(), 32);
}
#[test]
fn test_algorithm_2b_with_u_entry() {
let password = b"test";
let salt = b"12345678";
let u_entry = [0x42u8; 48];
let hash_with_u = compute_hash_r6_algorithm_2b(password, salt, &u_entry).unwrap();
let hash_without_u = compute_hash_r6_algorithm_2b(password, salt, &[]).unwrap();
assert_eq!(hash_with_u.len(), 32);
assert_ne!(
hash_with_u, hash_without_u,
"U entry must affect the hash output"
);
}
#[test]
fn test_algorithm_2b_minimum_rounds() {
let password = b"test";
let salt = b"12345678";
let start = std::time::Instant::now();
let hash = compute_hash_r6_algorithm_2b(password, salt, &[]).unwrap();
let elapsed = start.elapsed();
assert_eq!(hash.len(), 32);
assert!(
elapsed.as_micros() > 100,
"Algorithm 2.B should take measurable time, got {:?}",
elapsed
);
}
#[test]
fn test_algorithm_2b_short_salt() {
let result = compute_hash_r6_algorithm_2b(b"test", b"short", &[]);
assert!(result.is_ok(), "Short salt should not cause error");
assert_eq!(result.unwrap().len(), 32);
}
#[test]
fn test_algorithm_2b_password_at_max_length() {
let max_pwd = vec![b'a'; 127];
let result = compute_hash_r6_algorithm_2b(&max_pwd, b"12345678", &[]);
assert!(result.is_ok(), "127-byte password should work");
assert_eq!(result.unwrap().len(), 32);
}
#[test]
fn test_algorithm_2b_password_exceeds_max_length() {
let too_long_pwd = vec![b'a'; 128];
let result = compute_hash_r6_algorithm_2b(&too_long_pwd, b"12345678", &[]);
assert!(result.is_err(), "Password > 127 bytes should be rejected");
let err = result.unwrap_err();
let err_msg = format!("{}", err);
assert!(
err_msg.contains("too long") || err_msg.contains("128"),
"Error should mention password length: {}",
err_msg
);
}
#[test]
fn test_algorithm_2b_partial_u_entry() {
let short_u = [0x42u8; 32]; let result = compute_hash_r6_algorithm_2b(b"test", b"12345678", &short_u);
assert!(result.is_ok(), "Partial U entry should work");
assert_eq!(result.unwrap().len(), 32);
}
#[test]
fn test_algorithm_2b_hash_selector_math_equivalence() {
let bytes1 = [0x01u8, 0x00];
let sum1: u64 = bytes1.iter().map(|&b| b as u64).sum();
let bigint1: u64 = 0x0100; assert_eq!(sum1 % 3, bigint1 % 3, "Equivalence for [0x01, 0x00]");
let bytes2 = [0xFFu8, 0xFF];
let sum2: u64 = bytes2.iter().map(|&b| b as u64).sum();
let bigint2: u64 = 0xFFFF;
assert_eq!(sum2 % 3, bigint2 % 3, "Equivalence for [0xFF, 0xFF]");
let bytes3 = [
0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
0x88,
];
let sum3: u64 = bytes3.iter().map(|&b| b as u64).sum();
let mut bigint_mod3: u64 = 0;
for &b in &bytes3 {
bigint_mod3 = (bigint_mod3 * 256 + b as u64) % 3;
}
assert_eq!(sum3 % 3, bigint_mod3, "Equivalence for 16 random bytes");
}