#[test]
fn freshness_strict_returns_fresh_outcome() {
let offer = create_traversal_offer(
"sess-1".to_string(),
TraversalSignalTiming::new(1_700_000_000_000, 60_000),
"offer-1".to_string(),
"npub1client".to_string(),
"npub1server".to_string(),
observed(
Some(addr("203.0.113.10", 62000)),
vec![addr("192.168.1.10", 62000)],
Some("stun:example.org:3478"),
),
);
let result = validate_offer_freshness(
&offer,
1_700_000_000_500,
60_000,
"npub1client",
"npub1server",
)
.expect("strict-fresh offer should validate");
assert_eq!(result, FreshnessOutcome::Fresh);
}
#[test]
fn freshness_responder_clock_ahead_within_tolerance_is_tolerated() {
let offer = create_traversal_offer(
"sess-1".to_string(),
TraversalSignalTiming::new(1_700_000_000_000, 60_000), "offer-1".to_string(),
"npub1client".to_string(),
"npub1server".to_string(),
observed(
Some(addr("203.0.113.10", 62000)),
vec![addr("192.168.1.10", 62000)],
None,
),
);
let result = validate_offer_freshness(
&offer,
1_700_000_090_000,
60_000,
"npub1client",
"npub1server",
)
.expect("offer just past strict expiry should be tolerated");
assert_eq!(result, FreshnessOutcome::FreshWithinSkewTolerance);
}
#[test]
fn freshness_responder_clock_far_ahead_is_rejected() {
let offer = create_traversal_offer(
"sess-1".to_string(),
TraversalSignalTiming::new(1_700_000_000_000, 60_000),
"offer-1".to_string(),
"npub1client".to_string(),
"npub1server".to_string(),
observed(
Some(addr("203.0.113.10", 62000)),
vec![addr("192.168.1.10", 62000)],
None,
),
);
let err = validate_offer_freshness(
&offer,
1_700_000_130_000,
60_000,
"npub1client",
"npub1server",
)
.expect_err("offer past tolerated expiry should be rejected");
assert!(err.to_string().contains("expired-offer"), "{}", err);
}
#[test]
fn freshness_future_dated_offer_is_bounded_by_skew_tolerance() {
let now = 1_700_000_000_000;
let within = create_traversal_offer(
"within".to_string(),
TraversalSignalTiming::new(now + FRESHNESS_SKEW_TOLERANCE_MS, 60_000),
"nonce".to_string(),
"npub1client".to_string(),
"npub1server".to_string(),
observed(None, vec![addr("192.168.1.10", 62000)], None),
);
assert_eq!(
validate_offer_freshness(&within, now, 60_000, "npub1client", "npub1server").unwrap(),
FreshnessOutcome::FreshWithinSkewTolerance
);
let mut beyond = within;
beyond.issued_at = now + FRESHNESS_SKEW_TOLERANCE_MS + 1;
beyond.expires_at = beyond.issued_at + 60_000;
let error =
validate_offer_freshness(&beyond, now, 60_000, "npub1client", "npub1server").unwrap_err();
assert!(error.to_string().contains("future-dated-offer"));
}
#[test]
fn freshness_sender_cannot_extend_declared_expiry_past_ttl() {
let issued_at = 1_700_000_000_000;
let mut offer = create_traversal_offer(
"extended".to_string(),
TraversalSignalTiming::new(issued_at, 60_000),
"nonce".to_string(),
"npub1client".to_string(),
"npub1server".to_string(),
observed(None, vec![addr("192.168.1.10", 62000)], None),
);
offer.expires_at = u64::MAX;
let error = validate_offer_freshness(
&offer,
issued_at + 120_001,
60_000,
"npub1client",
"npub1server",
)
.unwrap_err();
assert!(error.to_string().contains("expired-offer"));
}
#[test]
fn estimate_clock_skew_matches_responder_offset() {
let offer = create_traversal_offer(
"sess-1".to_string(),
TraversalSignalTiming::new(1_700_000_000_000, 60_000),
"offer-1".to_string(),
"npub1client".to_string(),
"npub1server".to_string(),
observed(None, vec![addr("192.168.1.10", 62000)], None),
);
let answer = create_traversal_answer(
&offer,
TraversalSignalTiming::new(1_700_000_000_550, 60_000), "answer-1".to_string(),
"npub1server".to_string(),
observed(Some(addr("198.51.100.20", 63000)), vec![], None),
None,
Some(1_700_000_000_550), );
let answer_received_at = 1_700_000_000_100;
let skew = estimate_clock_skew(&offer, &answer, answer_received_at)
.expect("offer_received_at populated -> Some");
assert_eq!(skew, 500);
}
#[test]
fn estimate_clock_skew_returns_none_without_responder_timestamp() {
let offer = create_traversal_offer(
"sess-1".to_string(),
TraversalSignalTiming::new(1_700_000_000_000, 60_000),
"offer-1".to_string(),
"npub1client".to_string(),
"npub1server".to_string(),
observed(None, vec![], None),
);
let answer = create_traversal_answer(
&offer,
TraversalSignalTiming::new(1_700_000_000_500, 60_000),
"answer-1".to_string(),
"npub1server".to_string(),
observed(Some(addr("198.51.100.20", 63000)), vec![], None),
None,
None, );
assert!(estimate_clock_skew(&offer, &answer, 1_700_000_000_900).is_none());
}