use std::net::SocketAddr;
use std::ops::RangeInclusive;
use std::time::Instant;
use packtool::Packet;
use crate::constants;
use crate::core::EstablishedSession;
use crate::packet::{DataHeader, Handshake};
const REPLAY_WORDS: usize = constants::REPLAY_WINDOW / 64;
const _: () = assert!(constants::REPLAY_WINDOW.is_multiple_of(64));
const _: () = assert!(REPLAY_WORDS == 32);
#[derive(Debug, Clone)]
pub(crate) struct ReplayWindow {
greatest: Option<u64>,
bits: [u64; REPLAY_WORDS],
}
impl Default for ReplayWindow {
fn default() -> Self {
Self::new()
}
}
impl ReplayWindow {
pub(crate) const fn new() -> Self {
Self {
greatest: None,
bits: [0; REPLAY_WORDS],
}
}
pub(crate) fn greatest(&self) -> Option<u64> {
self.greatest
}
pub(crate) fn would_accept(&self, counter: u64) -> bool {
match self.greatest {
None => true,
Some(greatest) => match greatest.checked_sub(counter) {
None => true,
Some(0) => false,
Some(offset) if offset > constants::REPLAY_WINDOW as u64 => false,
Some(offset) => !self.bit(offset - 1),
},
}
}
pub(crate) fn check_and_mark(&mut self, counter: u64) -> bool {
match self.greatest {
None => {
self.greatest = Some(counter);
true
}
Some(greatest) if counter > greatest => {
let delta = counter - greatest;
self.shift(delta);
self.set_bit(delta - 1);
self.greatest = Some(counter);
true
}
Some(greatest) => {
let offset = greatest - counter;
if offset == 0 || offset > constants::REPLAY_WINDOW as u64 {
return false;
}
let index = offset - 1;
if self.bit(index) {
false
} else {
self.set_bit(index);
true
}
}
}
}
pub(crate) fn ranges_desc(&self) -> impl Iterator<Item = RangeInclusive<u64>> + '_ {
RangesDesc {
window: self,
offset: 0,
}
}
fn received_at(&self, offset: u64) -> bool {
offset == 0 || self.bit(offset - 1)
}
fn bit(&self, index: u64) -> bool {
if index >= constants::REPLAY_WINDOW as u64 {
return false;
}
let index = index as usize;
self.bits[index / 64] & (1u64 << (index % 64)) != 0
}
fn set_bit(&mut self, index: u64) {
if index >= constants::REPLAY_WINDOW as u64 {
return;
}
let index = index as usize;
self.bits[index / 64] |= 1u64 << (index % 64);
}
fn shift(&mut self, delta: u64) {
if delta >= constants::REPLAY_WINDOW as u64 {
self.bits = [0; REPLAY_WORDS];
return;
}
let delta = delta as usize;
let words = delta / 64;
let bits = delta % 64;
let mut out = [0u64; REPLAY_WORDS];
for i in (words..REPLAY_WORDS).rev() {
let src = i - words;
let mut value = self.bits[src] << bits;
if bits > 0 && src > 0 {
value |= self.bits[src - 1] >> (64 - bits);
}
out[i] = value;
}
self.bits = out;
}
}
struct RangesDesc<'a> {
window: &'a ReplayWindow,
offset: u64,
}
impl Iterator for RangesDesc<'_> {
type Item = RangeInclusive<u64>;
fn next(&mut self) -> Option<RangeInclusive<u64>> {
let greatest = self.window.greatest?;
let last = (constants::REPLAY_WINDOW as u64).min(greatest);
while self.offset <= last && !self.window.received_at(self.offset) {
self.offset += 1;
}
if self.offset > last {
return None;
}
let start = self.offset;
while self.offset <= last && self.window.received_at(self.offset) {
self.offset += 1;
}
let end = self.offset - 1;
Some((greatest - end)..=(greatest - start))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct Liveness {
last_authenticated_recv: Instant,
last_send: Instant,
armed: bool,
received_since_marking_send: bool,
}
impl Liveness {
pub(crate) fn pinned_at_install(now: Instant) -> Self {
Self {
last_authenticated_recv: now,
last_send: now,
armed: true,
received_since_marking_send: false,
}
}
pub(crate) fn deadline(&self) -> Option<Instant> {
self.armed
.then(|| self.last_authenticated_recv + constants::DEAD_TIMEOUT)
}
fn on_send(&mut self, now: Instant, marking: bool, ack_eliciting: bool) {
if marking {
self.last_send = now;
self.received_since_marking_send = false;
}
if marking || ack_eliciting {
self.armed = true;
}
}
fn on_authenticated_fresh_recv(&mut self, now: Instant) {
self.last_authenticated_recv = now;
self.armed = false;
self.received_since_marking_send = true;
}
pub(crate) fn last_send(&self) -> Instant {
self.last_send
}
pub(crate) fn last_authenticated_recv(&self) -> Instant {
self.last_authenticated_recv
}
pub(crate) fn owes_passive_keepalive(&self) -> bool {
self.received_since_marking_send
}
pub(crate) fn is_armed(&self) -> bool {
self.armed
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct Sealed {
pub(crate) counter: u64,
pub(crate) datagram: Vec<u8>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct SealFailed;
pub(crate) struct Session<C: Handshake> {
established: EstablishedSession<C>,
replay: ReplayWindow,
liveness: Liveness,
#[cfg(test)]
fail_next_seal: bool,
}
impl<C: Handshake> Session<C> {
pub(crate) fn install(now: Instant, established: EstablishedSession<C>) -> Self {
Self {
established,
replay: ReplayWindow::new(),
liveness: Liveness::pinned_at_install(now),
#[cfg(test)]
fail_next_seal: false,
}
}
pub(crate) fn established(&self) -> &EstablishedSession<C> {
&self.established
}
pub(crate) fn roam_to(&mut self, addr: SocketAddr) -> SocketAddr {
std::mem::replace(&mut self.established.anchor, addr)
}
pub(crate) fn replay(&self) -> &ReplayWindow {
&self.replay
}
pub(crate) fn liveness(&self) -> &Liveness {
&self.liveness
}
pub(crate) fn next_counter(&self) -> u64 {
C::next_counter(&self.established.seal)
}
pub(crate) fn seal(
&mut self,
now: Instant,
plaintext: &[u8],
ack_eliciting: bool,
) -> Result<Sealed, SealFailed> {
self.seal_inner(now, plaintext, true, ack_eliciting)
}
pub(crate) fn seal_quiet(
&mut self,
now: Instant,
plaintext: &[u8],
ack_eliciting: bool,
) -> Result<Sealed, SealFailed> {
self.seal_inner(now, plaintext, false, ack_eliciting)
}
fn seal_inner(
&mut self,
now: Instant,
plaintext: &[u8],
marking: bool,
ack_eliciting: bool,
) -> Result<Sealed, SealFailed> {
debug_assert!(
plaintext.len() <= constants::MAX_PLAINTEXT,
"§8.6: a slither seal is at most MAX_PLAINTEXT + AEAD_TAG_LEN"
);
let counter = self.next_counter();
if counter == u64::MAX {
return Err(SealFailed);
}
#[cfg(test)]
if self.fail_next_seal {
self.fail_next_seal = false;
return Err(SealFailed);
}
let header = Packet::pack(&DataHeader::new(self.established.peer_index, counter));
let header: &[u8] = header.as_ref();
let mut datagram = vec![0u8; header.len() + plaintext.len() + constants::AEAD_TAG_LEN];
datagram[..header.len()].copy_from_slice(header);
let (sealed_at, written) = match C::seal(
&mut self.established.seal,
header,
plaintext,
&mut datagram[header.len()..],
) {
Ok(out) => out,
Err(_error) => {
debug_assert_eq!(
self.next_counter(),
counter,
"a failed seal must leave the counter unchanged"
);
return Err(SealFailed);
}
};
debug_assert_eq!(
sealed_at, counter,
"§3.4: the counter in the AD must be the one the seal used"
);
datagram.truncate(header.len() + written);
self.liveness.on_send(now, marking, ack_eliciting);
Ok(Sealed {
counter: sealed_at,
datagram,
})
}
pub(crate) fn open<'a>(
&mut self,
now: Instant,
counter: u64,
ad: &[u8],
ciphertext: &[u8],
scratch: &'a mut Vec<u8>,
) -> Option<&'a [u8]> {
let plaintext_len = ciphertext.len().checked_sub(constants::AEAD_TAG_LEN)?;
if plaintext_len > constants::MAX_PLAINTEXT {
return None;
}
scratch.clear();
scratch.resize(plaintext_len, 0);
let written = C::open(
&mut self.established.open,
counter,
ad,
ciphertext,
scratch.as_mut_slice(),
)
.ok()?;
if !self.replay.check_and_mark(counter) {
tracing::debug!(
target: "slither::replay",
counter,
greatest = ?self.replay.greatest(),
"a received packet was rejected by the replay window"
);
return None;
}
self.liveness.on_authenticated_fresh_recv(now);
scratch.truncate(written);
Some(&scratch[..written])
}
#[cfg(test)]
pub(crate) fn fail_next_seal(&mut self) {
self.fail_next_seal = true;
}
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use super::*;
#[test]
fn a_fresh_window_accepts_anything_once() {
let mut w = ReplayWindow::new();
assert_eq!(w.greatest(), None);
assert!(w.check_and_mark(7));
assert_eq!(w.greatest(), Some(7));
assert!(!w.check_and_mark(7), "a duplicate is not fresh");
}
#[test]
fn out_of_order_within_the_window_is_fresh_once_each() {
let mut w = ReplayWindow::new();
assert!(w.check_and_mark(9));
assert!(w.check_and_mark(7), "opening 7 after 9 is fine");
assert!(!w.check_and_mark(7));
assert!(w.check_and_mark(8));
assert_eq!(
w.greatest(),
Some(9),
"an older counter is not the greatest"
);
}
#[test]
fn the_window_edge_is_two_sided() {
let window = constants::REPLAY_WINDOW as u64;
let mut w = ReplayWindow::new();
assert!(w.check_and_mark(10_000));
assert!(
w.would_accept(10_000 - window),
"exactly REPLAY_WINDOW behind is not *more than* REPLAY_WINDOW behind"
);
assert!(w.check_and_mark(10_000 - window));
assert!(
!w.would_accept(10_000 - window - 1),
"one further back is dropped"
);
assert!(!w.check_and_mark(10_000 - window - 1));
}
#[test]
fn advancing_the_greatest_slides_the_window() {
let window = constants::REPLAY_WINDOW as u64;
let mut w = ReplayWindow::new();
assert!(w.check_and_mark(1));
assert!(w.check_and_mark(1 + window));
assert!(!w.check_and_mark(1), "still remembered at the tail");
assert!(w.check_and_mark(2 + window));
assert!(!w.check_and_mark(1));
}
#[test]
fn a_jump_past_the_whole_window_clears_it() {
let mut w = ReplayWindow::new();
for c in 0..100 {
assert!(w.check_and_mark(c));
}
assert!(w.check_and_mark(1_000_000));
assert!(!w.would_accept(99));
assert!(w.check_and_mark(999_999));
}
#[test]
fn the_previous_greatest_is_remembered_after_an_advance() {
for delta in [1u64, 2, 63, 64, 65, 127, 128, 2047, 2048] {
let mut w = ReplayWindow::new();
assert!(w.check_and_mark(5000));
assert!(w.check_and_mark(5000 + delta));
assert!(
!w.check_and_mark(5000),
"counter 5000 must still be marked after a {delta}-step advance"
);
}
}
#[test]
fn ranges_desc_is_empty_before_anything_is_received() {
let w = ReplayWindow::new();
assert_eq!(w.ranges_desc().collect::<Vec<_>>(), Vec::new());
}
#[test]
fn ranges_desc_is_newest_first_and_descending() {
let mut w = ReplayWindow::new();
for c in [10u64, 11, 12, 15, 16, 20] {
assert!(w.check_and_mark(c));
}
assert_eq!(
w.ranges_desc().collect::<Vec<_>>(),
vec![20..=20, 15..=16, 10..=12]
);
}
#[test]
fn ranges_desc_stops_at_counter_zero() {
let mut w = ReplayWindow::new();
for c in [0u64, 1, 2] {
assert!(w.check_and_mark(c));
}
assert_eq!(w.ranges_desc().collect::<Vec<_>>(), vec![0..=2]);
}
#[test]
fn ranges_desc_handles_the_alternating_worst_case() {
let mut w = ReplayWindow::new();
for c in (0u64..64).step_by(2) {
assert!(w.check_and_mark(c));
}
let ranges: Vec<_> = w.ranges_desc().collect();
assert_eq!(ranges.len(), 32);
assert_eq!(ranges[0], 62..=62);
assert_eq!(ranges[31], 0..=0);
}
#[test]
fn install_pins_both_clocks_and_arms_the_deadline() {
let now = Instant::now();
let liveness = Liveness::pinned_at_install(now);
assert_eq!(liveness.last_send(), now);
assert_eq!(liveness.last_authenticated_recv(), now);
assert!(liveness.is_armed(), "§7.4: pinned *armed*");
assert_eq!(
liveness.deadline(),
Some(now + constants::DEAD_TIMEOUT),
"a session that receives nothing dies at install + DEAD_TIMEOUT"
);
}
#[test]
fn a_quiet_send_does_not_move_last_send() {
let now = Instant::now();
let mut liveness = Liveness::pinned_at_install(now);
let later = now + Duration::from_secs(3);
liveness.on_send(later, false, false);
assert_eq!(
liveness.last_send(),
now,
"seal_quiet must not touch last_send"
);
liveness.on_send(later, true, false);
assert_eq!(liveness.last_send(), later, "seal marks it");
}
#[test]
fn arming_and_marking_are_independent_axes() {
let now = Instant::now();
let mut a = Liveness::pinned_at_install(now);
a.on_authenticated_fresh_recv(now);
assert!(!a.is_armed());
a.on_send(now, false, true);
assert!(a.is_armed(), "an ack-eliciting quiet send arms");
assert_eq!(a.last_send(), now, "…and still does not mark");
let mut b = Liveness::pinned_at_install(now);
b.on_authenticated_fresh_recv(now);
b.on_send(now, true, false);
assert!(b.is_armed(), "a marking send arms");
let mut c = Liveness::pinned_at_install(now);
c.on_authenticated_fresh_recv(now);
c.on_send(now, false, false);
assert!(!c.is_armed(), "a quiet, non-eliciting send does neither");
}
#[test]
fn later_sends_do_not_re_arm_or_defer_the_deadline() {
let now = Instant::now();
let mut liveness = Liveness::pinned_at_install(now);
let deadline = liveness.deadline();
for step in 1..10 {
liveness.on_send(now + Duration::from_secs(step), true, true);
assert_eq!(
liveness.deadline(),
deadline,
"the deadline hangs from the receive clock, not the send clock"
);
}
}
#[test]
fn an_authenticated_fresh_receive_disarms_and_re_anchors() {
let now = Instant::now();
let mut liveness = Liveness::pinned_at_install(now);
let recv = now + Duration::from_secs(4);
liveness.on_authenticated_fresh_recv(recv);
assert!(!liveness.is_armed());
assert_eq!(liveness.deadline(), None);
liveness.on_send(recv, false, true);
assert_eq!(liveness.deadline(), Some(recv + constants::DEAD_TIMEOUT));
}
}
#[cfg(test)]
mod transport_tests {
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
use super::*;
use crate::identity::Identity;
use crate::packet::{Inbound, ReferenceSuite, classify};
use crate::testutil::CountingIdentity;
type Suite = ReferenceSuite;
type Id = CountingIdentity<Suite>;
fn addr(port: u16) -> SocketAddr {
SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, port))
}
fn established_pair() -> (Session<Suite>, Session<Suite>) {
let initiator = Id::seeded([7u8; 32]);
let responder = Id::seeded([9u8; 32]);
let now = Instant::now();
let (ip, ik) = initiator.open().expect("software identity opens");
let state =
<Suite as Handshake>::initiator(ip, constants::PROLOGUE, *responder.public_static());
let (msg1, state) =
<Suite as Handshake>::write_msg1(state, ik, &(), &[0u8; constants::MSG1_PAYLOAD_LEN])
.expect("msg1");
let (rp, rk) = responder.open().expect("software identity opens");
let r = <Suite as Handshake>::responder(rp, constants::PROLOGUE, rk).expect("responder");
let (_claimed, mid) = <Suite as Handshake>::read_msg1_intro(r, &msg1).expect("intro");
let (_payload, read) = <Suite as Handshake>::complete(mid, &()).expect("complete");
let (msg2, r_transport) = <Suite as Handshake>::write_msg2(read).expect("msg2");
let i_transport = <Suite as Handshake>::read_msg2(state, &msg2).expect("read msg2");
let epoch = crate::config::Config::DEFAULT_EPOCH_SIZE;
let (i_seal, i_open) = <Suite as Handshake>::into_datagram(i_transport, epoch);
let (r_seal, r_open) = <Suite as Handshake>::into_datagram(r_transport, epoch);
let i = EstablishedSession::<Suite> {
seal: i_seal,
open: i_open,
our_index: 11,
peer_index: 22,
anchor: addr(2222),
};
let r = EstablishedSession::<Suite> {
seal: r_seal,
open: r_open,
our_index: 22,
peer_index: 11,
anchor: addr(1111),
};
(Session::install(now, i), Session::install(now, r))
}
fn deliver<'a>(
to: &mut Session<Suite>,
now: Instant,
datagram: &[u8],
scratch: &'a mut Vec<u8>,
) -> Option<&'a [u8]> {
let Some(Inbound::Data {
header,
ad,
ciphertext,
}) = classify::<Suite>(datagram)
else {
return None;
};
to.open(now, header.counter, ad, ciphertext, scratch)
}
#[test]
fn a_sealed_packet_opens_at_the_peer() {
let (mut i, mut r) = established_pair();
let now = Instant::now();
assert_eq!(i.next_counter(), 0, "§7.1: the counter runs from 0");
let sealed = i.seal_quiet(now, b"hello frames", false).expect("seal");
assert_eq!(sealed.counter, 0);
assert_eq!(i.next_counter(), 1, "§16.7: the seal is synchronous");
assert_eq!(
sealed.datagram.len(),
constants::DATA_HEADER_LEN + 12 + constants::AEAD_TAG_LEN
);
assert_eq!(sealed.datagram[0], constants::PKT_DATA);
assert_eq!(&sealed.datagram[2..6], &22u32.to_le_bytes());
assert_eq!(&sealed.datagram[6..14], &0u64.to_le_bytes());
let mut scratch = Vec::new();
assert_eq!(
deliver(&mut r, now, &sealed.datagram, &mut scratch),
Some(&b"hello frames"[..])
);
}
#[test]
fn a_forgery_at_a_counter_does_not_burn_it() {
let (mut i, mut r) = established_pair();
let now = Instant::now();
let sealed = i.seal_quiet(now, b"genuine", false).expect("seal");
let mut forged = sealed.datagram.clone();
let last = forged.len() - 1;
forged[last] ^= 0xff;
let mut scratch = Vec::new();
assert_eq!(
deliver(&mut r, now, &forged, &mut scratch),
None,
"a bad tag is dropped"
);
assert_eq!(r.replay().greatest(), None, "and marks nothing");
assert_eq!(
deliver(&mut r, now, &sealed.datagram, &mut scratch),
Some(&b"genuine"[..]),
"the genuine packet at the same counter still arrives"
);
}
#[test]
fn a_replay_is_dropped_and_does_not_refresh_liveness() {
let (mut i, mut r) = established_pair();
let now = Instant::now();
let sealed = i.seal_quiet(now, b"once", false).expect("seal");
let mut scratch = Vec::new();
let first = now + std::time::Duration::from_secs(1);
assert!(deliver(&mut r, first, &sealed.datagram, &mut scratch).is_some());
assert_eq!(r.liveness().last_authenticated_recv(), first);
let later = now + std::time::Duration::from_secs(9);
assert_eq!(deliver(&mut r, later, &sealed.datagram, &mut scratch), None);
assert_eq!(
r.liveness().last_authenticated_recv(),
first,
"§7.2: no replayed packet ever refreshes liveness"
);
}
#[test]
fn an_empty_plaintext_is_a_thirty_byte_datagram() {
let (mut i, mut r) = established_pair();
let now = Instant::now();
let sealed = i.seal(now, b"", false).expect("seal");
assert_eq!(sealed.datagram.len(), 30);
let mut scratch = Vec::new();
assert_eq!(
deliver(&mut r, now, &sealed.datagram, &mut scratch),
Some(&[][..])
);
}
#[test]
fn packets_open_out_of_order_once_each() {
let (mut i, mut r) = established_pair();
let now = Instant::now();
let a = i.seal_quiet(now, b"a", false).expect("seal");
let b = i.seal_quiet(now, b"b", false).expect("seal");
let c = i.seal_quiet(now, b"c", false).expect("seal");
assert_eq!((a.counter, b.counter, c.counter), (0, 1, 2));
let mut scratch = Vec::new();
assert_eq!(
deliver(&mut r, now, &c.datagram, &mut scratch),
Some(&b"c"[..])
);
assert_eq!(
deliver(&mut r, now, &a.datagram, &mut scratch),
Some(&b"a"[..])
);
assert_eq!(
deliver(&mut r, now, &b.datagram, &mut scratch),
Some(&b"b"[..])
);
assert_eq!(r.replay().greatest(), Some(2));
assert_eq!(r.replay().ranges_desc().collect::<Vec<_>>(), vec![0..=2]);
}
#[test]
fn an_injected_seal_failure_moves_nothing() {
let (mut i, _r) = established_pair();
let now = Instant::now();
let before = i.next_counter();
let last_send = i.liveness().last_send();
i.fail_next_seal();
assert_eq!(
i.seal(now + std::time::Duration::from_secs(1), b"x", true),
Err(SealFailed)
);
assert_eq!(
i.next_counter(),
before,
"§16.7: on seal failure nothing moved"
);
assert_eq!(
i.liveness().last_send(),
last_send,
"and the liveness commit did not run either"
);
}
#[test]
fn seal_quiet_does_not_mark_and_seal_does() {
let (mut i, _r) = established_pair();
let install = i.liveness().last_send();
let later = install + std::time::Duration::from_secs(3);
let quiet = i.seal_quiet(later, b"quiet", false).expect("seal");
assert_eq!(i.liveness().last_send(), install);
let marking = i.seal(later, b"quiet", false).expect("seal");
assert_eq!(i.liveness().last_send(), later);
assert_eq!(quiet.datagram.len(), marking.datagram.len());
assert_eq!(marking.counter, quiet.counter + 1);
}
}