use std::error::Error;
use std::thread::sleep;
use std::time::Duration;
use liminal_protocol::wire::{
ClientRequest, ConnectionIncarnation, EnrollmentKnown, EnrollmentRequest, EnrollmentToken,
ReceiptExpired, ReceiptExpiryReason, ServerValue,
};
use super::ProductionParticipantHandler;
use super::tests::{dispatch, open_disk_store_for_tests, test_participant_config};
use super::tests_receipts::{
GEN_ONE, attach, attach_request, detach, enroll, generation, short_ttl_config,
};
#[test]
fn enrollment_token_replay_inside_provenance_window_is_receipt_expired()
-> Result<(), Box<dyn Error>> {
let home = tempfile::tempdir()?;
let data_dir = home.path().join("durability");
let incarnation = ConnectionIncarnation::new(61, 1);
let store = open_disk_store_for_tests(&data_dir)?;
let handler = ProductionParticipantHandler::new(store, short_ttl_config(300, 600_000))?;
let conversation_id = 601;
let enrollment_token = [61; 16];
let receipt = enroll(&handler, incarnation, conversation_id, enrollment_token)?;
let participant_id = receipt.participant_id();
detach(
&handler,
incarnation,
conversation_id,
participant_id,
GEN_ONE,
[62; 16],
)?;
sleep(Duration::from_millis(500));
let attached = attach(
&handler,
incarnation,
attach_request(
conversation_id,
participant_id,
GEN_ONE,
receipt.attach_secret(),
[63; 16],
),
)?;
assert_eq!(attached.capability_generation(), generation(2)?);
let in_window = dispatch(
&handler,
incarnation,
ClientRequest::Enrollment(EnrollmentRequest {
conversation_id,
enrollment_token: EnrollmentToken::new(enrollment_token),
}),
)?;
let ServerValue::ReceiptExpired(ReceiptExpired::Enrollment {
conversation_id: expired_conversation,
token,
participant_id: expired_participant,
result_generation,
current_generation,
reason,
}) = in_window
else {
return Err(format!(
"enrollment token inside its provenance window must answer ReceiptExpired, got: \
{in_window:?}"
)
.into());
};
assert_eq!(expired_conversation, conversation_id);
assert_eq!(token, EnrollmentToken::new(enrollment_token));
assert_eq!(expired_participant, participant_id);
assert_eq!(
result_generation, GEN_ONE,
"enrollment provenance retains the minted result generation"
);
assert_eq!(current_generation, generation(2)?);
assert_eq!(reason, ReceiptExpiryReason::Deadline);
Ok(())
}
#[test]
fn enrollment_token_replay_after_provenance_window_is_enrollment_known()
-> Result<(), Box<dyn Error>> {
let home = tempfile::tempdir()?;
let data_dir = home.path().join("durability");
let incarnation = ConnectionIncarnation::new(65, 1);
let store = open_disk_store_for_tests(&data_dir)?;
let handler = ProductionParticipantHandler::new(store, short_ttl_config(300, 700))?;
let conversation_id = 605;
let enrollment_token = [75; 16];
let receipt = enroll(&handler, incarnation, conversation_id, enrollment_token)?;
let participant_id = receipt.participant_id();
detach(
&handler,
incarnation,
conversation_id,
participant_id,
GEN_ONE,
[76; 16],
)?;
sleep(Duration::from_millis(900));
let attached = attach(
&handler,
incarnation,
attach_request(
conversation_id,
participant_id,
GEN_ONE,
receipt.attach_secret(),
[77; 16],
),
)?;
assert_eq!(attached.capability_generation(), generation(2)?);
let after_window = dispatch(
&handler,
incarnation,
ClientRequest::Enrollment(EnrollmentRequest {
conversation_id,
enrollment_token: EnrollmentToken::new(enrollment_token),
}),
)?;
let ServerValue::EnrollmentKnown(EnrollmentKnown {
conversation_id: known_conversation,
participant_id: known_participant,
current_generation,
..
}) = after_window
else {
return Err(format!(
"enrollment token after its provenance window must map to EnrollmentKnown, got: \
{after_window:?}"
)
.into());
};
assert_eq!(known_conversation, conversation_id);
assert_eq!(known_participant, participant_id);
assert_eq!(current_generation, generation(2)?);
Ok(())
}
#[test]
fn attach_inside_enrollment_receipt_window_supersedes_the_receipt() -> Result<(), Box<dyn Error>> {
let home = tempfile::tempdir()?;
let data_dir = home.path().join("durability");
let incarnation = ConnectionIncarnation::new(66, 1);
let store = open_disk_store_for_tests(&data_dir)?;
let handler = ProductionParticipantHandler::new(store, test_participant_config())?;
let conversation_id = 606;
let enrollment_token = [78; 16];
let receipt = enroll(&handler, incarnation, conversation_id, enrollment_token)?;
let participant_id = receipt.participant_id();
detach(
&handler,
incarnation,
conversation_id,
participant_id,
GEN_ONE,
[79; 16],
)?;
let attached = attach(
&handler,
incarnation,
attach_request(
conversation_id,
participant_id,
GEN_ONE,
receipt.attach_secret(),
[80; 16],
),
)?;
assert_eq!(attached.capability_generation(), generation(2)?);
let replayed = dispatch(
&handler,
incarnation,
ClientRequest::Enrollment(EnrollmentRequest {
conversation_id,
enrollment_token: EnrollmentToken::new(enrollment_token),
}),
)?;
let ServerValue::ReceiptExpired(ReceiptExpired::Enrollment {
conversation_id: expired_conversation,
token,
participant_id: expired_participant,
result_generation,
current_generation,
reason,
}) = replayed
else {
return Err(format!(
"an enrollment token superseded by a newer generation must answer ReceiptExpired \
(never the invalidated secret payload), got: {replayed:?}"
)
.into());
};
assert_eq!(expired_conversation, conversation_id);
assert_eq!(token, EnrollmentToken::new(enrollment_token));
assert_eq!(expired_participant, participant_id);
assert_eq!(
result_generation, GEN_ONE,
"the ended enrollment receipt minted generation 1"
);
assert_eq!(current_generation, generation(2)?);
assert_eq!(
reason,
ReceiptExpiryReason::Superseded,
"an attach inside the live receipt window records the exact Superseded reason"
);
Ok(())
}
#[test]
fn superseded_enrollment_token_maps_to_enrollment_known_after_provenance()
-> Result<(), Box<dyn Error>> {
let home = tempfile::tempdir()?;
let data_dir = home.path().join("durability");
let incarnation = ConnectionIncarnation::new(67, 1);
let store = open_disk_store_for_tests(&data_dir)?;
let handler = ProductionParticipantHandler::new(store, short_ttl_config(1_500, 1_600))?;
let conversation_id = 607;
let enrollment_token = [81; 16];
let receipt = enroll(&handler, incarnation, conversation_id, enrollment_token)?;
let participant_id = receipt.participant_id();
detach(
&handler,
incarnation,
conversation_id,
participant_id,
GEN_ONE,
[82; 16],
)?;
let attached = attach(
&handler,
incarnation,
attach_request(
conversation_id,
participant_id,
GEN_ONE,
receipt.attach_secret(),
[83; 16],
),
)?;
assert_eq!(attached.capability_generation(), generation(2)?);
sleep(Duration::from_millis(2_000));
let after_window = dispatch(
&handler,
incarnation,
ClientRequest::Enrollment(EnrollmentRequest {
conversation_id,
enrollment_token: EnrollmentToken::new(enrollment_token),
}),
)?;
let ServerValue::EnrollmentKnown(EnrollmentKnown {
conversation_id: known_conversation,
participant_id: known_participant,
current_generation,
..
}) = after_window
else {
return Err(format!(
"a superseded enrollment token after its provenance window must map to \
EnrollmentKnown, got: {after_window:?}"
)
.into());
};
assert_eq!(known_conversation, conversation_id);
assert_eq!(known_participant, participant_id);
assert_eq!(current_generation, generation(2)?);
Ok(())
}