Fake time for simulation

This commit is contained in:
Vecna 2024-05-15 20:58:42 -04:00
parent 88914084e3
commit 72103f95af
4 changed files with 209 additions and 2 deletions

View File

@ -13,6 +13,7 @@ chrono = "0.4"
clap = { version = "4.4.14", features = ["derive"] }
curve25519-dalek = { version = "4", default-features = false, features = ["serde", "rand_core", "digest"] }
ed25519-dalek = { version = "2", features = ["serde", "rand_core"] }
faketime = { version = "0.2", optional = true }
futures = "0.3.30"
hkdf = "0.12"
http = "1"
@ -41,6 +42,7 @@ x25519-dalek = { version = "2", features = ["serde", "static_secrets"] }
[dev-dependencies]
base64 = "0.21.7"
faketime = "0.2"
[features]
simulation = ["lox_cli"]
simulation = ["faketime", "lox_cli"]

View File

@ -12,6 +12,13 @@ use std::{
};
use x25519_dalek::{PublicKey, StaticSecret};
#[cfg(any(feature = "simulation", test))]
use {
chrono::{DateTime, Utc},
julianday::JulianDay,
std::{path::Path, time::UNIX_EPOCH},
};
pub mod analysis;
pub mod bridge_verification_info;
pub mod crypto;
@ -46,8 +53,20 @@ lazy_static! {
/// We will accept reports up to this many days old.
pub const MAX_BACKDATE: u32 = 3;
/// Get Julian date
#[cfg(any(feature = "simulation", test))]
const FAKETIME_FILE: &str = "/tmp/troll-patrol-faketime";
/// Get real or simulated Julian date
pub fn get_date() -> u32 {
// If this is a simulation, get the simulated date
#[cfg(any(feature = "simulation", test))]
return get_simulated_date();
// If we're not running a simulation, return today's date
get_real_date()
}
fn get_real_date() -> u32 {
time::OffsetDateTime::now_utc()
.date()
.to_julian_day()
@ -55,6 +74,38 @@ pub fn get_date() -> u32 {
.unwrap()
}
#[cfg(any(feature = "simulation", test))]
fn get_simulated_date() -> u32 {
faketime::enable(Path::new(FAKETIME_FILE));
JulianDay::from(DateTime::<Utc>::from(UNIX_EPOCH + faketime::unix_time()).date_naive())
.inner()
.try_into()
.unwrap()
}
#[cfg(any(feature = "simulation", test))]
pub fn set_simulated_date(date: u32) {
faketime::enable(Path::new(FAKETIME_FILE));
let unix_date_ms = DateTime::<Utc>::from_naive_utc_and_offset(
JulianDay::new(date.try_into().unwrap()).to_date().into(),
Utc,
)
.timestamp_millis();
//str.push_str(format!("\nbridge-stats-end {} 23:59:59 (86400 s)", date).as_str());
faketime::write_millis(Path::new(FAKETIME_FILE), unix_date_ms.try_into().unwrap()).unwrap();
}
#[cfg(any(feature = "simulation", test))]
pub fn increment_simulated_date() {
let date = get_date();
set_simulated_date(date + 1);
}
#[cfg(any(feature = "simulation", test))]
pub fn reset_simulated_date() {
set_simulated_date(get_real_date());
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
pub enum BridgeDistributor {
Lox,

View File

@ -8,3 +8,4 @@ mod reports {
mod negative_reports;
mod positive_reports;
}
mod simulated_time;

153
src/tests/simulated_time.rs Normal file
View File

@ -0,0 +1,153 @@
use crate::*;
use hyper::{
body,
header::HeaderValue,
service::{make_service_fn, service_fn},
Body, Client, Method, Request, Response, Server,
};
use std::{convert::Infallible, net::SocketAddr, time::Duration};
use tokio::{spawn, time::sleep};
use lox_library::bridge_table::BridgeLine;
use rand::RngCore;
// Prepare HTTP Response for successful Server Request
fn prepare_header(response: String) -> Response<Body> {
let mut resp = Response::new(Body::from(response));
resp.headers_mut()
.insert("Access-Control-Allow-Origin", HeaderValue::from_static("*"));
resp
}
// Lets the client get or set the simulated date
async fn date_server(req: Request<Body>) -> Result<Response<Body>, Infallible> {
match req.method() {
&Method::OPTIONS => Ok(Response::builder()
.header("Access-Control-Allow-Origin", HeaderValue::from_static("*"))
.header("Access-Control-Allow-Headers", "accept, content-type")
.header("Access-Control-Allow-Methods", "POST")
.status(200)
.body(Body::from("Allow POST"))
.unwrap()),
_ => match req.uri().path() {
"/getdate" => Ok::<_, Infallible>(prepare_header(get_date().to_string())),
"/setdate" => Ok::<_, Infallible>({
let bytes = body::to_bytes(req.into_body()).await.unwrap();
let date: u32 = std::str::from_utf8(&bytes).unwrap().parse().unwrap();
set_simulated_date(date);
prepare_header("OK".to_string())
}),
_ => Ok::<_, Infallible>(prepare_header("Wrong path".to_string())),
},
}
}
// Serves the function to let the client get or set the simulated date
async fn server() {
let addr = SocketAddr::from(([127, 0, 0, 1], 9999));
let make_svc = make_service_fn(|_conn| async { Ok::<_, Infallible>(service_fn(date_server)) });
let server = Server::bind(&addr).serve(make_svc);
if let Err(e) = server.await {
eprintln!("server error: {}", e);
}
}
async fn get_date_from_server() -> u32 {
download("http://localhost:9999/getdate")
.await
.unwrap()
.parse()
.unwrap()
}
async fn set_date_on_server(date: u32) {
let client = Client::new();
let req = Request::builder()
.method(Method::POST)
.uri(
"http://localhost:9999/setdate"
.parse::<hyper::Uri>()
.unwrap(),
)
.body(Body::from(date.to_string()))
.unwrap();
client.request(req).await.unwrap();
}
#[tokio::test]
async fn test_simulated_time() {
// Reset date in case we had a previous simulated date
reset_simulated_date();
// Get date
let date = get_date();
// Check that simulated date matches real date
let real_date: u32 = time::OffsetDateTime::now_utc()
.date()
.to_julian_day()
.try_into()
.unwrap();
assert_eq!(date, real_date);
// Check that incrementing simulated date works
increment_simulated_date();
assert_eq!(date + 1, get_date());
// Create dummy bridge
let mut rng = rand::thread_rng();
let mut bl = BridgeLine::default();
rng.fill_bytes(&mut bl.fingerprint);
let negative_report =
NegativeReport::from_bridgeline(bl, "ru".to_string(), BridgeDistributor::Lox);
// No issue
let negative_report = negative_report
.to_serializable_report()
.to_report()
.unwrap();
// Advance time so the report is no longer valid
set_simulated_date(get_date() + MAX_BACKDATE + 1);
// Report fails to deserialize
let negative_report_result = negative_report.to_serializable_report().to_report();
assert!(negative_report_result.is_err());
// Ensure one thread CAN influence the time for other threads
spawn(async move {
server().await;
});
// Give server time to start
sleep(Duration::new(1, 0)).await;
// Increment date
increment_simulated_date();
let date = get_date();
// Get date from server
let remote_date = get_date_from_server().await;
assert_eq!(date, remote_date);
// Increase date a lot
set_simulated_date(get_date() + 100);
let date = get_date();
// Check that date from server matches
let remote_date = get_date_from_server().await;
assert_eq!(date, remote_date);
// Have server increase date
let old_date = get_date();
let new_date = old_date + 500;
set_date_on_server(new_date).await;
// Check that we have the date as changed in the other thread
assert_eq!(get_date(), new_date);
}