Browse Source

script/research/clock_sync: code improvements

aggstam 4 years ago
parent
commit
c27da2b6f4

+ 13 - 2
script/research/clock_sync/Cargo.toml

@@ -3,10 +3,21 @@ name = "clock_sync"
 version = "0.1.0"
 edition = "2021"
 
+[dependencies.darkfi]
+path = "../../../"
+features = ["async-runtime", "rpc"]
+
 [dependencies]
+
+# Ntp request
 ntp = "0.5.0"
-reqwest = "0.11.10"
+
+# Misc
+async-std = "1.11.0"
+async-native-tls = "0.4.0"
+log = "0.4.16"
 serde_json = "1.0.81"
-tokio = { version = "1.18.1", features = ["full"] }
+simplelog = "0.12.0"
+thiserror = "1.0.24"
 
 [workspace]

+ 55 - 0
script/research/clock_sync/src/error.rs

@@ -0,0 +1,55 @@
+#[derive(Debug, thiserror::Error)]
+pub enum ClockError {
+    #[error("AsyncNativeTls error: '{0}'")]
+    AsyncNativeTlsError(String),
+    #[error("FromUtf8 error: '{0}'")]
+    FromUtf8Error(String),
+    #[error("System clock is not correct!")]
+    InvalidClock,
+    #[error("Io error: '{0}'")]
+    IoError(String),
+    #[error("NTP error: '{0}'")]
+    NtpError(String),
+    #[error("SerdeJson error: '{0}'")]
+    SerdeJsonError(String),
+    #[error("SystemTime error: '{0}'")]
+    SysTimeError(String),
+}
+
+pub type ClockResult<T> = std::result::Result<T, ClockError>;
+
+impl From<async_native_tls::Error> for ClockError {
+    fn from(err: async_native_tls::Error) -> ClockError {
+        ClockError::AsyncNativeTlsError(err.to_string())
+    }
+}
+
+impl From<std::string::FromUtf8Error> for ClockError {
+    fn from(err: std::string::FromUtf8Error) -> ClockError {
+        ClockError::FromUtf8Error(err.to_string())
+    }
+}
+
+impl From<ntp::errors::Error> for ClockError {
+    fn from(err: ntp::errors::Error) -> ClockError {
+        ClockError::NtpError(err.to_string())
+    }
+}
+
+impl From<serde_json::Error> for ClockError {
+    fn from(err: serde_json::Error) -> ClockError {
+        ClockError::SerdeJsonError(err.to_string())
+    }
+}
+
+impl From<std::io::Error> for ClockError {
+    fn from(err: std::io::Error) -> ClockError {
+        ClockError::IoError(err.to_string())
+    }
+}
+
+impl From<std::time::SystemTimeError> for ClockError {
+    fn from(err: std::time::SystemTimeError) -> ClockError {
+        ClockError::SysTimeError(err.to_string())
+    }
+}

+ 111 - 17
script/research/clock_sync/src/main.rs

@@ -1,25 +1,95 @@
-use serde_json::Value;
+use async_std::{
+    io::{ReadExt, WriteExt},
+    net::TcpStream,
+};
 use std::{
-    error::Error,
     thread,
-    time::{Duration, SystemTime, UNIX_EPOCH},
+    time::{Duration, Instant, SystemTime, UNIX_EPOCH},
 };
 
+use log::{debug, error, info};
+use serde_json::Value;
+use simplelog::{ColorChoice, Config, LevelFilter, TermLogger, TerminalMode};
+
+use darkfi::Result;
+
+mod error;
+
+use crate::error::{ClockError, ClockResult};
+
+// Execution parameters
+const RETRIES: u8 = 5;
+const WORLDTIMEAPI_ADDRESS: &str = "worldtimeapi.org";
+const WORLDTIMEAPI_ADDRESS_WITH_PORT: &str = "worldtimeapi.org:443";
+const WORLDTIMEAPI_PAYLOAD: &[u8; 88] = b"GET /api/timezone/Etc/UTC HTTP/1.1\r\nHost: worldtimeapi.org\r\nAccept: application/json\r\n\r\n";
+const NTP_ADDRESS: &str = "0.pool.ntp.org:123";
+const EPOCH: u64 = 2208988800; //1900
+
+// Raw https request execution for worldtimeapi
+async fn worldtimeapi_request() -> ClockResult<Value> {
+    // Create connection
+    let stream = TcpStream::connect(WORLDTIMEAPI_ADDRESS_WITH_PORT).await?;
+    let mut stream = async_native_tls::connect(WORLDTIMEAPI_ADDRESS, stream).await?;
+    stream.write_all(WORLDTIMEAPI_PAYLOAD).await?;
+
+    // Execute request
+    let mut res = vec![0_u8; 1024];
+    stream.read(&mut res).await?;
+
+    // Parse response
+    let reply = String::from_utf8(res)?;
+    let lines = reply.split('\n');
+    // JSON data exist in last row of response
+    let last = lines.last().unwrap().trim_matches(char::from(0));
+    debug!("worldtimeapi json response: {}", last);
+    let reply = serde_json::from_str(last)?;
+    Ok(reply)
+}
+
 // This is a very simple check to verify that system time is correct.
-#[tokio::main]
-async fn main() -> Result<(), Box<dyn Error>> {
+// Retry loop is used to in case discrepancies are found.
+// If all retries fail, system clock is considered invalid.
+// TODO: 1. Add proxy functionality in order not to leak connections
+//       2. Improve requests and/or add extra protocols
+async fn check_clock() -> ClockResult<()> {
+    debug!("System clock check started...");
+    let mut r = 0;
+    while r < RETRIES {
+        let check = clock_check().await;
+        match check {
+            Ok(()) => break,
+            Err(e) => error!("Error during clock check: {}", e),
+        }
+        r += 1;
+    }
+
+    debug!("System clock check finished. Retries: {}", r);
+    match r {
+        RETRIES => Err(ClockError::InvalidClock),
+        _ => Ok(()),
+    }
+}
+
+async fn clock_check() -> ClockResult<()> {
+    // Start elapsed time counter to cover for all requests and processing time
+    let requests_start = Instant::now();
     // Poll worldtimeapi.org for current UTC timestamp
-    let response =
-        reqwest::get("https://worldtimeapi.org/api/timezone/Etc/UTC").await?.text().await?;
-    let worldtimeapi: Value = serde_json::from_str(&response).unwrap();
-    println!("worldtimeapi: {}", worldtimeapi["unixtime"].to_string());
+    let worldtimeapi_response = worldtimeapi_request().await?;
 
+    // Start elapsed time counter to cover for ntp request and processing time
+    let ntp_request_start = Instant::now();
     // Poll ntp.org for current timestamp
-    let address = "0.pool.ntp.org:123";
-    let response: ntp::packet::Packet = ntp::request(address).unwrap();
-    // Remove 1900 epoch(2208988800) to reach UTC timestamp
-    let ntp_time = response.transmit_time.sec as u64 - 2208988800;
-    println!("ntp_time: {}", ntp_time);
+    let ntp_response: ntp::packet::Packet = ntp::request(NTP_ADDRESS)?;
+
+    // Extract worldtimeapi timestamp from json
+    let mut worldtimeapi_time = worldtimeapi_response["unixtime"].as_u64().unwrap();
+
+    // Remove 1900 epoch to reach UTC timestamp for ntp timestamp
+    let mut ntp_time = ntp_response.transmit_time.sec as u64 - EPOCH;
+
+    // Add elapsed time to respone times
+    ntp_time += ntp_request_start.elapsed().as_secs();
+    worldtimeapi_time += requests_start.elapsed().as_secs();
 
     // To simulate wrong clock, we sleep some time
     //let one_sec = Duration::new(1, 0);
@@ -27,11 +97,35 @@ async fn main() -> Result<(), Box<dyn Error>> {
 
     // Current system time
     let system_time = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs();
-    println!("SystemTime: {}", system_time);
+
+    debug!("worldtimeapi_time: {}", worldtimeapi_time);
+    debug!("ntp_time: {}", ntp_time);
+    debug!("system_time: {}", system_time);
 
     // We verify that system time is equal to worldtimeapi or ntp
-    let check = (system_time == worldtimeapi) || (system_time == ntp_time);
-    assert!(check, "System clock is not correct!");
+    let check = (system_time == worldtimeapi_time) && (system_time == ntp_time);
+    match check {
+        true => Ok(()),
+        false => Err(ClockError::InvalidClock),
+    }
+}
+
+#[async_std::main]
+async fn main() -> Result<()> {
+    TermLogger::init(
+        LevelFilter::Debug,
+        Config::default(),
+        TerminalMode::Mixed,
+        ColorChoice::Auto,
+    )?;
+
+    match check_clock().await {
+        Ok(()) => info!("System clock is correct!"),
+        Err(_) => {
+            error!("System clock is invalid, terminating...");
+            return Err(darkfi::Error::OperationFailed)
+        }
+    };
 
     Ok(())
 }