Skip to content

Commit

Permalink
test: fix UDP conn ID generation test by mocking current time
Browse files Browse the repository at this point in the history
The current implementation of `get_connection_id` seems to be OK.
I've added some tests. I needed to mock the current time becuase it's
used in the ID generation.

For now, I only neded to mock the current time (now). Not the clock
service.

I have also changed the signature of the `handle_connect` method.
Now it panics if we cannot get the system time. In the previous behavior
it was returning always a hardcoded connection ID:

ConnectionId(0x7FFFFFFFFFFFFFFF)
  • Loading branch information
josecelano committed Aug 12, 2022
1 parent d8b13e5 commit 6c2f312
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 27 deletions.
20 changes: 20 additions & 0 deletions src/protocol/clock.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use std::time::{SystemTime};

pub trait Clock {
fn now_as_timestamp(&self) -> u64;
}

/// A [`Clock`] which uses the operating system to determine the time.
struct SystemClock;

impl Clock for SystemClock {
fn now_as_timestamp(&self) -> u64 {
SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs()
}
}

/// It returns the current timestamp using the system clock.
pub fn current_timestamp_from_system_clock() -> u64 {
let system_clock = SystemClock;
system_clock.now_as_timestamp()
}
1 change: 1 addition & 0 deletions src/protocol/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod common;
pub mod utils;
pub mod clock;
99 changes: 73 additions & 26 deletions src/protocol/utils.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
use std::net::SocketAddr;
use std::time::SystemTime;
use std::{net::SocketAddr, time::SystemTime};
use aquatic_udp_protocol::ConnectionId;

/// It generates a connection id needed for the BitTorrent UDP Tracker Protocol
pub fn get_connection_id(remote_address: &SocketAddr) -> ConnectionId {
match std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH) {
Ok(duration) => ConnectionId(((duration.as_secs() / 3600) | ((remote_address.port() as u64) << 36)) as i64),
Err(_) => ConnectionId(0x7FFFFFFFFFFFFFFF),
}
pub fn get_connection_id(remote_address: &SocketAddr, current_timestamp: u64) -> ConnectionId {
ConnectionId(((current_timestamp / 3600) | ((remote_address.port() as u64) << 36)) as i64)
}

/// It returns the current time in Unix Epoch.
pub fn current_time() -> u64 {
SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH).unwrap()
Expand All @@ -33,47 +28,99 @@ pub fn ser_instant<S: serde::Serializer>(inst: &std::time::Instant, ser: S) -> R

#[cfg(test)]
mod tests {
use core::time;
use std::{time::Instant, net::{SocketAddr, IpAddr, Ipv4Addr}, thread::sleep};
use std::{time::Instant, net::{SocketAddr, IpAddr, Ipv4Addr}};

#[test]
fn connection_id_is_generated_based_on_remote_client_port_an_hours_passed_since_unix_epoch() {
let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0001);

let timestamp = 946684800u64; // GTM: Sat Jan 01 2000 00:00:00 GMT+0000

// Timestamp in hours 946684800u64 / 3600 = 262968 = 0x_0000_0000_0004_0338 = 262968
// Port 0001 = 0x_0000_0000_0000_0001 = 1
// Port 0001 << 36 = 0x_0000_0010_0000_0000 = 68719476736
//
// 0x_0000_0000_0004_0338 | 0x_0000_0010_0000_0000 = 0x_0000_0010_0004_0338 = 68719739704
//
// HEX BIN DEC
// --------------------------------------------------------------------------------
// 0x_0000_0000_0004_0338 = ... 0000000000000000001000000001100111000 = 262968
// OR
// 0x_0000_0010_0000_0000 = ... 1000000000000000000000000000000000000 = 68719476736
// -------------------------------------------------------------------
// 0x_0000_0010_0004_0338 = ... 1000000000000000001000000001100111000 = 68719739704

// Assert intermediary values
assert_eq!(timestamp / 3600, 0x_0000_0000_0004_0338);
assert_eq!((client_addr.port() as u64), 1);
assert_eq!(((client_addr.port() as u64) << 36), 0x_0000_0010_0000_0000); // 68719476736
assert_eq!((0x_0000_0000_0004_0338u64 | 0x_0000_0010_0000_0000u64), 0x_0000_0010_0004_0338u64); // 68719739704
assert_eq!(0x_0000_0010_0004_0338u64 as i64, 68719739704); // 68719739704

let connection_id = super::get_connection_id(&client_addr, timestamp);

assert_eq!(connection_id, ConnectionId(68719739704));
}

#[test]
fn connection_id_in_udp_tracker_should_be_the_same_for_one_client_during_some_hours() {
fn connection_id_in_udp_tracker_should_be_the_same_for_one_client_during_one_hour() {
let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080);

let connection_id_1 = get_connection_id(&client_addr);
let now = 946684800u64;

// TODO: mock time passing
sleep(time::Duration::from_secs(10));
let connection_id = get_connection_id(&client_addr, now);

let connection_id_2 = get_connection_id(&client_addr);
let in_one_hour = now + 3600 - 1;

assert_eq!(connection_id_1, connection_id_2);
let connection_id_after_one_hour = get_connection_id(&client_addr, in_one_hour);

assert_eq!(connection_id, connection_id_after_one_hour);
}

#[test]
fn connection_id_in_udp_tracker_should_be_different_for_each_tracker_client() {
let client_1_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080);
let client_2_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 2)), 8080);
fn connection_id_in_udp_tracker_should_change_for_the_same_client_and_port_after_one_hour() {
let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080);

let connection_id_1 = get_connection_id(&client_1_addr);
let connection_id_2 = get_connection_id(&client_2_addr);
let now = 946684800u64;

assert_ne!(connection_id_1, connection_id_2);
let connection_id = get_connection_id(&client_addr, now);

let after_one_hour = now + 3600;

let connection_id_after_one_hour = get_connection_id(&client_addr, after_one_hour);

assert_ne!(connection_id, connection_id_after_one_hour);
}

#[test]
fn connection_id_in_udp_tracker_should_be_different_for_each_client_at_the_same_time_if_they_use_a_different_port() {
let client_1_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0001);
let client_2_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0002);

let now = 946684800u64;

let connection_id_for_client_1 = get_connection_id(&client_1_addr, now);
let connection_id_for_client_2 = get_connection_id(&client_2_addr, now);

assert_ne!(connection_id_for_client_1, connection_id_for_client_2);
}

#[test]
fn connection_id_in_udp_tracker_should_be_different_for_the_same_tracker_client_at_different_times() {
fn connection_id_in_udp_tracker_should_expire_after_one_hour() {
let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080);

let connection_id_1 = get_connection_id(&client_addr);
let now = 946684800u64;

let connection_id_1 = get_connection_id(&client_addr, now);

sleep(time::Duration::from_secs(2));
let in_one_hour = now + 3600;

let connection_id_2 = get_connection_id(&client_addr);
let connection_id_2 = get_connection_id(&client_addr, in_one_hour);

assert_ne!(connection_id_1, connection_id_2);
}

use aquatic_udp_protocol::ConnectionId;
use serde::Serialize;

use crate::protocol::utils::get_connection_id;
Expand Down
3 changes: 2 additions & 1 deletion src/udp/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use crate::udp::request::AnnounceRequestWrapper;
use crate::tracker::statistics::TrackerStatisticsEvent;
use crate::tracker::tracker::TorrentTracker;
use crate::protocol::utils::get_connection_id;
use crate::protocol::clock::current_timestamp_from_system_clock;

pub async fn authenticate(info_hash: &InfoHash, tracker: Arc<TorrentTracker>) -> Result<(), ServerError> {
match tracker.authenticate_request(info_hash, &None).await {
Expand Down Expand Up @@ -70,7 +71,7 @@ pub async fn handle_request(request: Request, remote_addr: SocketAddr, tracker:
}

pub async fn handle_connect(remote_addr: SocketAddr, request: &ConnectRequest, tracker: Arc<TorrentTracker>) -> Result<Response, ServerError> {
let connection_id = get_connection_id(&remote_addr);
let connection_id = get_connection_id(&remote_addr, current_timestamp_from_system_clock());

let response = Response::from(ConnectResponse {
transaction_id: request.transaction_id,
Expand Down

0 comments on commit 6c2f312

Please sign in to comment.