time.rs 5.0 KB

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