use ntimestamp::Timestamp;
use crate::SignedPacket;
const HTTP_DATE_SECOND_MICROS: u64 = 1_000_000;
#[derive(Clone, Copy, Debug)]
pub(in crate::client) struct CacheContext<'a> {
cached: Option<&'a SignedPacket>,
minimum_ttl: u32,
maximum_ttl: u32,
}
impl<'a> CacheContext<'a> {
pub(in crate::client) const fn new(
cached: Option<&'a SignedPacket>,
minimum_ttl: u32,
maximum_ttl: u32,
) -> Self {
Self {
cached,
minimum_ttl,
maximum_ttl,
}
}
pub(super) fn packet_is_below_floor(self, packet: &SignedPacket) -> bool {
self.cached
.is_some_and(|cached| cached.more_recent_than(packet))
}
pub(super) fn invalid_seq_is_covered(self, seq: i64) -> bool {
seq >= 0
&& self
.cached
.is_some_and(|cached| cached.timestamp().as_u64() >= seq as u64)
}
#[cfg(dht)]
pub(super) fn dht_request_lower_bound(self) -> Option<Timestamp> {
self.cached?
.timestamp()
.as_u64()
.checked_sub(1)
.map(Timestamp::from)
}
pub(super) fn relay_request_lower_bound(self) -> Option<Timestamp> {
self.cached?
.timestamp()
.as_u64()
.checked_sub(HTTP_DATE_SECOND_MICROS)
.map(Timestamp::from)
}
pub(in crate::client) fn accepts_network_packet(self, packet: &SignedPacket) -> bool {
self.maximum_ttl == 0 || !packet.is_expired(self.minimum_ttl, self.maximum_ttl)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Keypair;
#[test]
fn invalid_sequence_must_be_non_negative_and_above_the_cached_sequence() {
let cached = SignedPacket::builder()
.timestamp(Timestamp::from(10))
.sign(&Keypair::random())
.unwrap();
let context = CacheContext::new(Some(&cached), 0, 0);
assert!(!context.invalid_seq_is_covered(-1));
assert!(context.invalid_seq_is_covered(10));
assert!(!context.invalid_seq_is_covered(11));
}
#[cfg(dht)]
#[test]
fn zero_timestamp_has_no_dht_request_lower_bound() {
let cached = SignedPacket::builder()
.timestamp(Timestamp::from(0))
.sign(&Keypair::random())
.unwrap();
assert_eq!(
CacheContext::new(Some(&cached), 0, 0).dht_request_lower_bound(),
None
);
}
#[test]
fn relay_request_lower_bound_uses_previous_http_second() {
let timestamp =
Timestamp::parse_http_date("Sun, 06 Nov 1994 08:49:37 GMT").unwrap() + 500_000;
let cached = SignedPacket::builder()
.timestamp(timestamp)
.sign(&Keypair::random())
.unwrap();
let context = CacheContext::new(Some(&cached), 0, 0);
#[cfg(dht)]
assert_eq!(context.dht_request_lower_bound(), Some(timestamp - 1));
assert_eq!(
context
.relay_request_lower_bound()
.unwrap()
.format_http_date(),
"Sun, 06 Nov 1994 08:49:36 GMT"
);
}
#[test]
fn packet_floor_uses_same_sequence_packet_ordering() {
let keypair = Keypair::random();
let first = SignedPacket::builder()
.txt("foo".try_into().unwrap(), "first".try_into().unwrap(), 30)
.timestamp(Timestamp::from(10))
.sign(&keypair)
.unwrap();
let second = SignedPacket::builder()
.txt("foo".try_into().unwrap(), "second".try_into().unwrap(), 30)
.timestamp(Timestamp::from(10))
.sign(&keypair)
.unwrap();
let (floor, below_floor) = if first.more_recent_than(&second) {
(first, second)
} else {
(second, first)
};
assert!(CacheContext::new(Some(&floor), 0, 0).packet_is_below_floor(&below_floor));
assert!(!CacheContext::new(Some(&below_floor), 0, 0).packet_is_below_floor(&floor));
}
#[test]
fn zero_maximum_ttl_accepts_expired_network_packet() {
let mut packet = SignedPacket::builder()
.txt("foo".try_into().unwrap(), "bar".try_into().unwrap(), 30)
.sign(&Keypair::random())
.unwrap();
packet.set_last_seen(&(Timestamp::now() - 60 * 1_000_000_u64));
assert!(CacheContext::new(None, 0, 0).accepts_network_packet(&packet));
assert!(!CacheContext::new(None, 0, 30).accepts_network_packet(&packet));
}
}