time.rs 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. use async_std::{
  2. io::{ReadExt, WriteExt},
  3. net::TcpStream,
  4. };
  5. use chrono::{NaiveDateTime, Utc};
  6. use log::debug;
  7. use serde_json::Value;
  8. use crate::{
  9. util::serial::{SerialDecodable, SerialEncodable},
  10. Error, Result,
  11. };
  12. /// Wrapper struct to represent [`chrono`] UTC timestamps.
  13. #[derive(Debug, Copy, Clone, PartialEq, SerialDecodable, SerialEncodable)]
  14. pub struct Timestamp(pub i64);
  15. impl Timestamp {
  16. /// Generate a `Timestamp` of the current time.
  17. pub fn current_time() -> Self {
  18. Self(Utc::now().timestamp())
  19. }
  20. /// Calculates elapsed time of a `Timestamp`.
  21. pub fn elapsed(&self) -> u64 {
  22. let start_time = NaiveDateTime::from_timestamp(self.0, 0);
  23. let end_time = NaiveDateTime::from_timestamp(Utc::now().timestamp(), 0);
  24. let diff = end_time - start_time;
  25. diff.num_seconds() as u64
  26. }
  27. /// Increment a 'Timestamp'.
  28. pub fn add(&mut self, inc: i64) {
  29. self.0 += inc;
  30. }
  31. }
  32. // Clock sync parameters
  33. const RETRIES: u8 = 5;
  34. const WORLDTIMEAPI_ADDRESS: &str = "worldtimeapi.org";
  35. const WORLDTIMEAPI_ADDRESS_WITH_PORT: &str = "worldtimeapi.org:443";
  36. 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";
  37. const NTP_ADDRESS: &str = "0.pool.ntp.org:123";
  38. const EPOCH: i64 = 2208988800; //1900
  39. // Raw https request execution for worldtimeapi
  40. async fn worldtimeapi_request() -> Result<Value> {
  41. // Create connection
  42. let stream = TcpStream::connect(WORLDTIMEAPI_ADDRESS_WITH_PORT).await?;
  43. let mut stream = async_native_tls::connect(WORLDTIMEAPI_ADDRESS, stream).await?;
  44. stream.write_all(WORLDTIMEAPI_PAYLOAD).await?;
  45. // Execute request
  46. let mut res = vec![0_u8; 1024];
  47. stream.read(&mut res).await?;
  48. // Parse response
  49. let reply = String::from_utf8(res)?;
  50. let lines = reply.split('\n');
  51. // JSON data exist in last row of response
  52. let last = lines.last().unwrap().trim_matches(char::from(0));
  53. debug!("worldtimeapi json response: {:#?}", last);
  54. let reply = serde_json::from_str(last)?;
  55. Ok(reply)
  56. }
  57. // This is a very simple check to verify that system time is correct.
  58. // Retry loop is used to in case discrepancies are found.
  59. // If all retries fail, system clock is considered invalid.
  60. // TODO: 1. Add proxy functionality in order not to leak connections
  61. // 2. Improve requests and/or add extra protocols
  62. pub async fn check_clock() -> Result<()> {
  63. debug!("System clock check started...");
  64. let mut r = 0;
  65. while r < RETRIES {
  66. if let Err(e) = clock_check().await {
  67. debug!("Error during clock check: {:#?}", e);
  68. r += 1;
  69. continue
  70. };
  71. break
  72. }
  73. debug!("System clock check finished. Retries: {:#?}", r);
  74. match r {
  75. RETRIES => Err(Error::InvalidClock),
  76. _ => Ok(()),
  77. }
  78. }
  79. async fn clock_check() -> Result<()> {
  80. // Start elapsed time counter to cover for all requests and processing time
  81. let requests_start = Timestamp::current_time();
  82. // Poll worldtimeapi.org for current UTC timestamp
  83. let worldtimeapi_response = worldtimeapi_request().await?;
  84. // Start elapsed time counter to cover for ntp request and processing time
  85. let ntp_request_start = Timestamp::current_time();
  86. // Poll ntp.org for current timestamp
  87. let ntp_response: ntp::packet::Packet = ntp::request(NTP_ADDRESS)?;
  88. // Extract worldtimeapi timestamp from json
  89. let mut worldtimeapi_time = Timestamp(worldtimeapi_response["unixtime"].as_i64().unwrap());
  90. // Remove 1900 epoch to reach UTC timestamp for ntp timestamp
  91. let mut ntp_time = Timestamp(ntp_response.transmit_time.sec as i64 - EPOCH);
  92. // Add elapsed time to respone times
  93. ntp_time.add(ntp_request_start.elapsed() as i64);
  94. worldtimeapi_time.add(requests_start.elapsed() as i64);
  95. // Current system time
  96. let system_time = Timestamp::current_time();
  97. debug!("worldtimeapi_time: {:#?}", worldtimeapi_time);
  98. debug!("ntp_time: {:#?}", ntp_time);
  99. debug!("system_time: {:#?}", system_time);
  100. // We verify that system time is equal to worldtimeapi and ntp
  101. let check = (system_time == worldtimeapi_time) && (system_time == ntp_time);
  102. match check {
  103. true => Ok(()),
  104. false => Err(Error::InvalidClock),
  105. }
  106. }