use super::*;
#[test]
fn test_encrypt_with_counter_no_aad_roundtrip() {
let keypair1 = generate_keypair();
let keypair2 = generate_keypair();
let mut init = HandshakeState::new_initiator(keypair1, keypair2.public_key());
init.set_local_epoch(generate_epoch());
let mut resp = HandshakeState::new_responder(keypair2);
resp.set_local_epoch(generate_epoch());
let msg1 = init.write_message_1().unwrap();
resp.read_message_1(&msg1).unwrap();
let msg2 = resp.write_message_2().unwrap();
init.read_message_2(&msg2).unwrap();
let sender = init.into_session().unwrap();
let mut receiver = resp.into_session().unwrap();
let send_cipher = sender.send_cipher_clone().unwrap();
let counter = 0u64;
let plaintext = b"off-task encrypt";
let nonce = CipherState::counter_to_nonce(counter);
let mut buf = plaintext.to_vec();
send_cipher
.seal_in_place_append_tag(nonce, ring::aead::Aad::empty(), &mut buf)
.expect("worker AEAD encrypt");
let ciphertext = buf;
let decrypted = receiver
.decrypt_with_replay_check(&ciphertext, counter)
.unwrap();
assert_eq!(decrypted, plaintext);
}
#[test]
fn test_encrypt_with_counter_matches_internal_counter() {
let key = [0x42u8; 32];
let mut a = CipherState::new(key);
let b = CipherState::new(key);
let plaintext = b"same key, same counter, same output";
let counter_a = a.nonce();
let ct_a = a.encrypt(plaintext).unwrap();
let ct_b = b.encrypt_with_counter(plaintext, counter_a).unwrap();
assert_eq!(
ct_a, ct_b,
"explicit-counter encrypt must be byte-identical"
);
assert_eq!(b.nonce(), 0);
}
#[test]
fn test_encrypt_with_counter_and_aad_roundtrip_via_session() {
let keypair1 = generate_keypair();
let keypair2 = generate_keypair();
let mut init = HandshakeState::new_initiator(keypair1, keypair2.public_key());
init.set_local_epoch(generate_epoch());
let mut resp = HandshakeState::new_responder(keypair2);
resp.set_local_epoch(generate_epoch());
let msg1 = init.write_message_1().unwrap();
resp.read_message_1(&msg1).unwrap();
let msg2 = resp.write_message_2().unwrap();
init.read_message_2(&msg2).unwrap();
let sender = init.into_session().unwrap();
let mut receiver = resp.into_session().unwrap();
let aad = b"outer header bytes";
let plaintext = b"pipelined send";
let counter = sender.take_send_counter().unwrap();
assert_eq!(counter, 0);
assert_eq!(sender.send_nonce(), 1, "counter reserved → nonce advanced");
let cipher = sender.send_cipher_clone().unwrap();
let nonce = CipherState::counter_to_nonce(counter);
let mut buf = plaintext.to_vec();
cipher
.seal_in_place_append_tag(nonce, ring::aead::Aad::from(aad), &mut buf)
.unwrap();
let ciphertext = buf;
let decrypted = receiver
.decrypt_with_replay_check_and_aad(&ciphertext, counter, aad)
.unwrap();
assert_eq!(decrypted, plaintext);
}
#[test]
fn test_pipelined_send_counter_reservation_is_single_owner() {
let keypair1 = generate_keypair();
let keypair2 = generate_keypair();
let mut init = HandshakeState::new_initiator(keypair1, keypair2.public_key());
init.set_local_epoch(generate_epoch());
let mut resp = HandshakeState::new_responder(keypair2);
resp.set_local_epoch(generate_epoch());
let msg1 = init.write_message_1().unwrap();
resp.read_message_1(&msg1).unwrap();
let msg2 = resp.write_message_2().unwrap();
init.read_message_2(&msg2).unwrap();
let mut sender = init.into_session().unwrap();
let mut receiver = resp.into_session().unwrap();
let aad = b"reserved-counter header";
let first_counter = sender.take_send_counter().unwrap();
let second_counter = sender.take_send_counter().unwrap();
assert_eq!(first_counter, 0);
assert_eq!(second_counter, 1);
assert_eq!(
sender.current_send_counter(),
2,
"only the coordinator-owned reservation path advances the session counter"
);
let cipher = sender.send_cipher_clone().unwrap();
let mut first = b"first reserved packet".to_vec();
cipher
.seal_in_place_append_tag(
CipherState::counter_to_nonce(first_counter),
ring::aead::Aad::from(aad),
&mut first,
)
.unwrap();
let mut second = b"second reserved packet".to_vec();
cipher
.seal_in_place_append_tag(
CipherState::counter_to_nonce(second_counter),
ring::aead::Aad::from(aad),
&mut second,
)
.unwrap();
assert_eq!(
sender.current_send_counter(),
2,
"worker-side cloned cipher use must not mutate session counter ownership"
);
assert_eq!(
receiver
.decrypt_with_replay_check_and_aad(&first, first_counter, aad)
.unwrap(),
b"first reserved packet"
);
assert_eq!(
receiver
.decrypt_with_replay_check_and_aad(&second, second_counter, aad)
.unwrap(),
b"second reserved packet"
);
let third_counter = sender.current_send_counter();
let third = sender
.encrypt_with_aad(b"inline packet after workers", aad)
.unwrap();
assert_eq!(third_counter, 2);
assert_eq!(sender.current_send_counter(), 3);
assert_eq!(
receiver
.decrypt_with_replay_check_and_aad(&third, third_counter, aad)
.unwrap(),
b"inline packet after workers"
);
}
#[test]
fn test_send_counter_authority_clone_reserves_unique_contiguous_ranges() {
let keypair1 = generate_keypair();
let keypair2 = generate_keypair();
let mut init = HandshakeState::new_initiator(keypair1, keypair2.public_key());
init.set_local_epoch(generate_epoch());
let mut resp = HandshakeState::new_responder(keypair2);
resp.set_local_epoch(generate_epoch());
let msg1 = init.write_message_1().unwrap();
resp.read_message_1(&msg1).unwrap();
let msg2 = resp.write_message_2().unwrap();
init.read_message_2(&msg2).unwrap();
let mut sender = init.into_session().unwrap();
let mut receiver = resp.into_session().unwrap();
let primary = sender.send_counter_authority();
let mover_clone = primary.clone();
let range = mover_clone.reserve_range(3).unwrap();
assert_eq!(range, 0..3);
assert_eq!(primary.reserve().unwrap(), 3);
assert_eq!(sender.current_send_counter(), 4);
let aad = b"shared counter authority";
let cipher = sender.send_cipher_clone().unwrap();
let mut worker_ciphertexts = Vec::new();
for counter in 0..4 {
let plaintext = format!("worker packet {counter}");
let mut buf = plaintext.as_bytes().to_vec();
cipher
.seal_in_place_append_tag(
CipherState::counter_to_nonce(counter),
ring::aead::Aad::from(aad),
&mut buf,
)
.unwrap();
worker_ciphertexts.push((counter, plaintext, buf));
}
let inline_counter = sender.current_send_counter();
let inline_ciphertext = sender
.encrypt_with_aad(b"inline after cloned authority", aad)
.unwrap();
assert_eq!(inline_counter, 4);
assert_eq!(sender.current_send_counter(), 5);
for (counter, plaintext, ciphertext) in worker_ciphertexts {
assert_eq!(
receiver
.decrypt_with_replay_check_and_aad(&ciphertext, counter, aad)
.unwrap(),
plaintext.as_bytes()
);
}
assert_eq!(
receiver
.decrypt_with_replay_check_and_aad(&inline_ciphertext, inline_counter, aad)
.unwrap(),
b"inline after cloned authority"
);
}
#[test]
fn test_recv_cipher_clone_matches_decrypt_with_counter_and_aad() {
let keypair1 = generate_keypair();
let keypair2 = generate_keypair();
let mut init = HandshakeState::new_initiator(keypair1, keypair2.public_key());
init.set_local_epoch(generate_epoch());
let mut resp = HandshakeState::new_responder(keypair2);
resp.set_local_epoch(generate_epoch());
let msg1 = init.write_message_1().unwrap();
resp.read_message_1(&msg1).unwrap();
let msg2 = resp.write_message_2().unwrap();
init.read_message_2(&msg2).unwrap();
let mut sender = init.into_session().unwrap();
let mut receiver = resp.into_session().unwrap();
let aad = b"AAD-bound transport header";
let plaintext = b"off-task decrypt";
let counter = sender.current_send_counter();
let ciphertext = sender.encrypt_with_aad(plaintext, aad).unwrap();
assert!(receiver.check_replay(counter).is_ok());
let cipher = receiver.recv_cipher_clone().unwrap();
let nonce = CipherState::counter_to_nonce(counter);
let mut buf = ciphertext.clone();
let worker_plaintext = cipher
.open_in_place(nonce, ring::aead::Aad::from(aad), &mut buf)
.unwrap()
.to_vec();
assert_eq!(worker_plaintext, plaintext);
receiver.accept_replay(counter);
assert!(receiver.check_replay(counter).is_err());
}