use std::net::SocketAddr;
use std::panic::Location;
use aquatic_udp_protocol::ConnectionId;
use torrust_tracker_clock::time_extent::{Extent, TimeExtent};
use zerocopy::network_endian::I64;
use zerocopy::AsBytes;
use super::error::Error;
pub type Cookie = [u8; 8];
pub type SinceUnixEpochTimeExtent = TimeExtent;
pub const COOKIE_LIFETIME: TimeExtent = TimeExtent::from_sec(2, &60);
#[must_use]
pub fn from_connection_id(connection_id: &ConnectionId) -> Cookie {
let mut cookie = [0u8; 8];
connection_id.write_to(&mut cookie);
cookie
}
#[must_use]
pub fn into_connection_id(connection_cookie: &Cookie) -> ConnectionId {
ConnectionId(I64::new(i64::from_be_bytes(*connection_cookie)))
}
#[must_use]
pub fn make(remote_address: &SocketAddr) -> Cookie {
let time_extent = cookie_builder::get_last_time_extent();
cookie_builder::build(remote_address, &time_extent)
}
pub fn check(remote_address: &SocketAddr, connection_cookie: &Cookie) -> Result<SinceUnixEpochTimeExtent, Error> {
for offset in 0..=COOKIE_LIFETIME.amount {
let checking_time_extent = cookie_builder::get_last_time_extent().decrease(offset).unwrap();
let checking_cookie = cookie_builder::build(remote_address, &checking_time_extent);
if *connection_cookie == checking_cookie {
return Ok(checking_time_extent);
}
}
Err(Error::InvalidConnectionId {
location: Location::caller(),
})
}
mod cookie_builder {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use std::net::SocketAddr;
use torrust_tracker_clock::time_extent::{Extent, Make, TimeExtent};
use super::{Cookie, SinceUnixEpochTimeExtent, COOKIE_LIFETIME};
use crate::shared::crypto::keys::seeds::{Current, Keeper};
use crate::DefaultTimeExtentMaker;
pub(super) fn get_last_time_extent() -> SinceUnixEpochTimeExtent {
DefaultTimeExtentMaker::now(&COOKIE_LIFETIME.increment)
.unwrap()
.unwrap()
.increase(COOKIE_LIFETIME.amount)
.unwrap()
}
pub(super) fn build(remote_address: &SocketAddr, time_extent: &TimeExtent) -> Cookie {
let seed = Current::get_seed();
let mut hasher = DefaultHasher::new();
remote_address.hash(&mut hasher);
time_extent.hash(&mut hasher);
seed.hash(&mut hasher);
hasher.finish().to_le_bytes()
}
}
#[cfg(test)]
mod tests {
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
use torrust_tracker_clock::clock::stopped::Stopped as _;
use torrust_tracker_clock::clock::{self};
use torrust_tracker_clock::time_extent::{self, Extent};
use super::cookie_builder::{self};
use crate::servers::udp::connection_cookie::{check, make, Cookie, COOKIE_LIFETIME};
#[test]
fn it_should_make_a_connection_cookie() {
const ID_COOKIE_OLD_HASHER: Cookie = [41, 166, 45, 246, 249, 24, 108, 203];
const ID_COOKIE_NEW_HASHER: Cookie = [185, 122, 191, 238, 6, 43, 2, 198];
clock::Stopped::local_set_to_unix_epoch();
let cookie = make(&SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0));
assert!(cookie == ID_COOKIE_OLD_HASHER || cookie == ID_COOKIE_NEW_HASHER);
}
#[test]
fn it_should_make_the_same_connection_cookie_for_the_same_input_data() {
let remote_address = SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0);
let time_extent_zero = time_extent::ZERO;
let cookie = cookie_builder::build(&remote_address, &time_extent_zero);
let cookie_2 = cookie_builder::build(&remote_address, &time_extent_zero);
println!("remote_address: {remote_address:?}, time_extent: {time_extent_zero:?}, cookie: {cookie:?}");
println!("remote_address: {remote_address:?}, time_extent: {time_extent_zero:?}, cookie: {cookie_2:?}");
assert_eq!(cookie, cookie_2);
}
#[test]
fn it_should_make_the_different_connection_cookie_for_different_ip() {
let remote_address = SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0);
let remote_address_2 = SocketAddr::new(IpAddr::V4(Ipv4Addr::BROADCAST), 0);
let time_extent_zero = time_extent::ZERO;
let cookie = cookie_builder::build(&remote_address, &time_extent_zero);
let cookie_2 = cookie_builder::build(&remote_address_2, &time_extent_zero);
println!("remote_address: {remote_address:?}, time_extent: {time_extent_zero:?}, cookie: {cookie:?}");
println!("remote_address: {remote_address_2:?}, time_extent: {time_extent_zero:?}, cookie: {cookie_2:?}");
assert_ne!(cookie, cookie_2);
}
#[test]
fn it_should_make_the_different_connection_cookie_for_different_ip_version() {
let remote_address = SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0);
let remote_address_2 = SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), 0);
let time_extent_zero = time_extent::ZERO;
let cookie = cookie_builder::build(&remote_address, &time_extent_zero);
let cookie_2 = cookie_builder::build(&remote_address_2, &time_extent_zero);
println!("remote_address: {remote_address:?}, time_extent: {time_extent_zero:?}, cookie: {cookie:?}");
println!("remote_address: {remote_address_2:?}, time_extent: {time_extent_zero:?}, cookie: {cookie_2:?}");
assert_ne!(cookie, cookie_2);
}
#[test]
fn it_should_make_the_different_connection_cookie_for_different_socket() {
let remote_address = SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0);
let remote_address_2 = SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 1);
let time_extent_zero = time_extent::ZERO;
let cookie = cookie_builder::build(&remote_address, &time_extent_zero);
let cookie_2 = cookie_builder::build(&remote_address_2, &time_extent_zero);
println!("remote_address: {remote_address:?}, time_extent: {time_extent_zero:?}, cookie: {cookie:?}");
println!("remote_address: {remote_address_2:?}, time_extent: {time_extent_zero:?}, cookie: {cookie_2:?}");
assert_ne!(cookie, cookie_2);
}
#[test]
fn it_should_make_the_different_connection_cookie_for_different_time_extents() {
let remote_address = SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0);
let time_extent_zero = time_extent::ZERO;
let time_extent_max = time_extent::MAX;
let cookie = cookie_builder::build(&remote_address, &time_extent_zero);
let cookie_2 = cookie_builder::build(&remote_address, &time_extent_max);
println!("remote_address: {remote_address:?}, time_extent: {time_extent_zero:?}, cookie: {cookie:?}");
println!("remote_address: {remote_address:?}, time_extent: {time_extent_max:?}, cookie: {cookie_2:?}");
assert_ne!(cookie, cookie_2);
}
#[test]
fn it_should_make_different_cookies_for_the_next_time_extent() {
let remote_address = SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0);
let cookie = make(&remote_address);
clock::Stopped::local_add(&COOKIE_LIFETIME.increment).unwrap();
let cookie_next = make(&remote_address);
assert_ne!(cookie, cookie_next);
}
#[test]
fn it_should_be_valid_for_this_time_extent() {
let remote_address = SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0);
let cookie = make(&remote_address);
check(&remote_address, &cookie).unwrap();
}
#[test]
fn it_should_be_valid_for_the_next_time_extent() {
let remote_address = SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0);
let cookie = make(&remote_address);
clock::Stopped::local_add(&COOKIE_LIFETIME.increment).unwrap();
check(&remote_address, &cookie).unwrap();
}
#[test]
fn it_should_be_valid_for_the_last_time_extent() {
let remote_address = SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0);
clock::Stopped::local_set_to_unix_epoch();
let cookie = make(&remote_address);
clock::Stopped::local_set(&COOKIE_LIFETIME.total().unwrap().unwrap());
check(&remote_address, &cookie).unwrap();
}
#[test]
#[should_panic = "InvalidConnectionId"]
fn it_should_be_not_valid_after_their_last_time_extent() {
let remote_address = SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0);
let cookie = make(&remote_address);
clock::Stopped::local_set(&COOKIE_LIFETIME.total_next().unwrap().unwrap());
check(&remote_address, &cookie).unwrap();
}
}