summaryrefslogtreecommitdiff
path: root/meap/meap-code/ch9/ch9-clock3
diff options
context:
space:
mode:
Diffstat (limited to 'meap/meap-code/ch9/ch9-clock3')
-rw-r--r--meap/meap-code/ch9/ch9-clock3/Cargo.toml19
-rw-r--r--meap/meap-code/ch9/ch9-clock3/Dockerfile10
-rw-r--r--meap/meap-code/ch9/ch9-clock3/src/main.rs333
3 files changed, 362 insertions, 0 deletions
diff --git a/meap/meap-code/ch9/ch9-clock3/Cargo.toml b/meap/meap-code/ch9/ch9-clock3/Cargo.toml
new file mode 100644
index 0000000..c0cb2d9
--- /dev/null
+++ b/meap/meap-code/ch9/ch9-clock3/Cargo.toml
@@ -0,0 +1,19 @@
+[package]
+name = "clock"
+version = "0.1.3"
+authors = ["Tim McNamara <code@timmcnamara.co.nz>"]
+
+[dependencies]
+byteorder = "1"
+chrono = "0.4"
+clap = "2.32"
+
+[target.'cfg(windows)'.dependencies]
+winapi = "0.2"
+kernel32-sys = "0.2"
+
+[target.'cfg(not(windows))'.dependencies]
+libc = "0.2"
+
+[profile.release]
+lto = true
diff --git a/meap/meap-code/ch9/ch9-clock3/Dockerfile b/meap/meap-code/ch9/ch9-clock3/Dockerfile
new file mode 100644
index 0000000..36055d0
--- /dev/null
+++ b/meap/meap-code/ch9/ch9-clock3/Dockerfile
@@ -0,0 +1,10 @@
+FROM rust:latest
+
+WORKDIR /opt/clock
+COPY . .
+
+# docker run --rm --user "$(id -u)":"$(id -g)" -v "$PWD":/opt/clock -w /opt/clock rust:latest cargo build --release
+
+# RUN cargo build
+
+# CMD ["clock"]
diff --git a/meap/meap-code/ch9/ch9-clock3/src/main.rs b/meap/meap-code/ch9/ch9-clock3/src/main.rs
new file mode 100644
index 0000000..ad3ee9f
--- /dev/null
+++ b/meap/meap-code/ch9/ch9-clock3/src/main.rs
@@ -0,0 +1,333 @@
+extern crate byteorder;
+extern crate chrono;
+extern crate clap;
+#[cfg(not(windows))] extern crate libc;
+#[cfg(windows)] extern crate kernel32;
+#[cfg(windows)] extern crate winapi;
+
+use byteorder::{BigEndian, ReadBytesExt};
+use chrono::DateTime;
+use chrono::Duration as ChronoDuration;
+use chrono::{Datelike, TimeZone, Timelike};
+use chrono::{Local, Utc};
+use clap::{App, Arg};
+use std::mem::zeroed;
+use std::net::UdpSocket;
+use std::time::Duration;
+
+const NTP_MESSAGE_LENGTH: usize = 48; // 12*4 bytes
+const NTP_TO_UNIX_SECONDS: i64 = 2_208_988_800;
+const LOCAL_ADDR: &'static str = "0.0.0.0:12300";
+
+#[derive(Default, Debug, Copy, Clone)]
+struct NTPTimestamp {
+ seconds: u32,
+ fraction: u32,
+}
+
+struct NTPMessage {
+ data: [u8; NTP_MESSAGE_LENGTH],
+}
+
+#[derive(Debug)]
+struct NTPResult {
+ t1: DateTime<Utc>,
+ t2: DateTime<Utc>,
+ t3: DateTime<Utc>,
+ t4: DateTime<Utc>,
+}
+
+impl NTPResult {
+ fn offset(&self) -> i64 {
+ let duration = (self.t2 - self.t1) + (self.t3 - self.t4);
+ duration.num_milliseconds() / 2
+ }
+
+ fn delay(&self) -> i64 {
+ let duration = (self.t4 - self.t1) - (self.t3 - self.t2);
+ duration.num_milliseconds()
+ }
+}
+
+impl From<NTPTimestamp> for DateTime<Utc> {
+ fn from(ntp: NTPTimestamp) -> Self {
+ let secs = ntp.seconds as i64 - NTP_TO_UNIX_SECONDS;
+ let mut nanos = ntp.fraction as f64;
+ nanos *= 1e9;
+ nanos /= 2_f64.powi(32);
+
+ Utc.timestamp(secs, nanos as u32)
+ }
+}
+
+impl From<DateTime<Utc>> for NTPTimestamp {
+ fn from(utc: DateTime<Utc>) -> Self {
+ let secs = utc.timestamp() + NTP_TO_UNIX_SECONDS;
+ let mut fraction = utc.nanosecond() as f64;
+ fraction *= 2_f64.powi(32);
+ fraction /= 1e9;
+
+ NTPTimestamp {
+ seconds: secs as u32,
+ fraction: fraction as u32,
+ }
+ }
+}
+
+impl NTPMessage {
+ fn new() -> Self {
+ NTPMessage {
+ data: [0; NTP_MESSAGE_LENGTH],
+ }
+ }
+
+ fn client() -> Self {
+ const VERSION: u8 = 3;
+ const MODE: u8 = 3;
+
+ let mut msg = NTPMessage::new();
+
+ // the first byte of every NTP message contains three fields,
+ // but we only need to set two of them
+ msg.data[0] |= VERSION << 3;
+ msg.data[0] |= MODE;
+ msg
+ }
+
+ fn parse_timestamp(&self, i: usize) -> Result<NTPTimestamp, std::io::Error> {
+ let mut reader = &self.data[i..i + 8];
+ let seconds = reader.read_u32::<BigEndian>()?;
+ let fraction = reader.read_u32::<BigEndian>()?;
+
+ Ok(NTPTimestamp {
+ seconds: seconds,
+ fraction: fraction,
+ })
+ }
+
+ fn rx_time(&self) -> Result<NTPTimestamp, std::io::Error> {
+ self.parse_timestamp(32)
+ }
+
+ fn tx_time(&self) -> Result<NTPTimestamp, std::io::Error> {
+ self.parse_timestamp(40)
+ }
+}
+
+fn weighted_mean(values: &[f64], weights: &[f64]) -> f64 {
+ let mut result = 0.0;
+ let mut sum_of_weights = 0.0;
+
+ for (v, w) in values.iter().zip(weights) {
+ result += v * w;
+ sum_of_weights += w;
+ }
+
+ result / sum_of_weights
+}
+
+fn ntp_roundtrip(host: &str, port: u16) -> Result<NTPResult, std::io::Error> {
+ let destination = format!("{}:{}", host, port);
+ let timeout = Duration::from_secs(1);
+
+ let request = NTPMessage::client();
+ let mut response = NTPMessage::new();
+
+ let message = request.data;
+
+ let udp = UdpSocket::bind(LOCAL_ADDR)?;
+ udp.connect(&destination).expect("unable to connect");
+
+ let t1 = Utc::now();
+ udp.send(&message)?;
+ udp.set_read_timeout(Some(timeout))?;
+ udp.recv_from(&mut response.data)?;
+ let t4 = Utc::now();
+
+ let t2: DateTime<Utc> = response.rx_time().unwrap().into();
+ let t3: DateTime<Utc> = response.tx_time().unwrap().into();
+
+ Ok(NTPResult {
+ t1: t1,
+ t2: t2,
+ t3: t3,
+ t4: t4,
+ })
+}
+
+fn check_time() -> Result<f64, std::io::Error> {
+ const NTP_PORT: u16 = 123;
+
+ let servers = [
+ "time.nist.gov",
+ "time.apple.com",
+ "time.euro.apple.com",
+ "time.google.com",
+ "time2.google.com",
+ //"time.windows.com",
+ ];
+
+ let mut times = Vec::with_capacity(servers.len());
+
+ for &server in servers.iter() {
+ print!("{} =>", server);
+
+ let calc = ntp_roundtrip(&server, NTP_PORT);
+
+ match calc {
+ Ok(time) => {
+ println!(" {}ms away from local system time", time.offset());
+ times.push(time);
+ }
+ Err(_) => println!(" ? [response took too long]"),
+ };
+ }
+
+ let mut offsets = Vec::with_capacity(servers.len());
+ let mut offset_weights = Vec::with_capacity(servers.len());
+
+ for time in &times {
+ let offset = time.offset() as f64;
+ let delay = time.delay() as f64;
+
+ let weight = 1_000_000.0 / (delay * delay); // <3>
+ if weight.is_finite() {
+ offsets.push(offset);
+ offset_weights.push(weight);
+ }
+ }
+
+ let avg_offset = weighted_mean(&offsets, &offset_weights);
+
+ Ok(avg_offset)
+}
+
+struct Clock;
+
+impl Clock {
+ fn get() -> DateTime<Local> {
+ Local::now()
+ }
+
+ #[cfg(windows)]
+ fn set<Tz: TimeZone>(t: DateTime<Tz>) -> () {
+ use chrono::Weekday;
+ use kernel32::SetSystemTime;
+ use winapi::{SYSTEMTIME, WORD};
+
+ let t = t.with_timezone(&Local);
+
+ let mut systime: SYSTEMTIME = unsafe { zeroed() };
+
+ let dow = match t.weekday() {
+ Weekday::Mon => 1,
+ Weekday::Tue => 2,
+ Weekday::Wed => 3,
+ Weekday::Thu => 4,
+ Weekday::Fri => 5,
+ Weekday::Sat => 6,
+ Weekday::Sun => 0,
+ };
+
+ let mut ns = t.nanosecond();
+ let is_leap_second = ns > 1_000_000_000;
+
+ if is_leap_second {
+ ns -= 1_000_000_000;
+ }
+
+ systime.wYear = t.year() as WORD;
+ systime.wMonth = t.month() as WORD;
+ systime.wDayOfWeek = dow as WORD;
+ systime.wDay = t.day() as WORD;
+ systime.wHour = t.hour() as WORD;
+ systime.wMinute = t.minute() as WORD;
+ systime.wSecond = t.second() as WORD;
+ systime.wMilliseconds = (ns / 1_000_000) as WORD;
+
+ let systime_ptr = &systime as *const SYSTEMTIME; // as *mut SYSTEMTIME; // convert to a pointer, then change its mutability
+
+ unsafe {
+ SetSystemTime(systime_ptr); // giving a pointer to a value to something outside of Rust's control is unsafe
+ }
+ }
+
+ #[cfg(not(windows))]
+ fn set<Tz: TimeZone>(t: DateTime<Tz>) -> () {
+ use libc::settimeofday;
+ use libc::{suseconds_t, time_t, timeval, timezone};
+
+ let t = t.with_timezone(&Local); // variable names indicate the data's progression
+ let mut u: timeval = unsafe { zeroed() }; // through the function
+
+ u.tv_sec = t.timestamp() as time_t;
+ u.tv_usec = t.timestamp_subsec_micros() as suseconds_t;
+
+ unsafe {
+ let mock_tz: *const timezone = std::ptr::null();
+ settimeofday(&u as *const timeval, mock_tz);
+ }
+ }
+}
+
+fn main() {
+ let app = App::new("clock")
+ .version("0.1.3")
+ .about("Gets and sets the time.")
+ .after_help("Note: UNIX timestamps are parsed as whole seconds since 1st January 1970 0:00:00 UTC. For more accuracy, use another format.")
+ .arg(Arg::with_name("action")
+ .takes_value(true)
+ .possible_values(&["get", "set", "check-ntp"])
+ .default_value("get"))
+ .arg(Arg::with_name("std")
+ .short("s")
+ .long("use-standard")
+ .takes_value(true)
+ .possible_values(&["rfc2822", "rfc3339", "timestamp"])
+ .default_value("rfc3339"))
+ .arg(Arg::with_name("datetime")
+ .help("When <action> is 'set', apply <datetime>. Otherwise, ignore."));
+
+ let args = app.get_matches();
+
+ let action = args.value_of("action").unwrap(); // default_value() has been supplied,
+ let std = args.value_of("std").unwrap(); // so it's safe to use .unwrap()
+
+ if action == "set" {
+ let t_ = args.value_of("datetime").unwrap();
+
+ let parser = match std {
+ "rfc2822" => DateTime::parse_from_rfc2822,
+ "rfc3339" => DateTime::parse_from_rfc3339,
+ _ => unimplemented!(),
+ };
+
+ let err_msg = format!("Unable to parse {} according to {}", t_, std);
+ let t = parser(t_).expect(&err_msg);
+
+ Clock::set(t);
+ } else if action == "check-ntp" {
+ let offset = check_time().unwrap() as isize;
+ let adjust_ms = offset.signum() * offset.abs().min(200) / 5;
+ let adjust_ms_ = ChronoDuration::milliseconds(adjust_ms as i64);
+ let now: DateTime<Utc> = Utc::now() + adjust_ms_;
+ Clock::set(now);
+ }
+
+ let maybe_error = std::io::Error::last_os_error();
+ let os_error_code = &maybe_error.raw_os_error();
+ match os_error_code {
+ Some(0) => (),
+ None => (),
+ _ => eprintln!("Unable to set the time: {:?}", maybe_error),
+ }
+
+ let now = Clock::get();
+
+ match std {
+ "timestamp" => println!("{}", now.timestamp()),
+ "rfc2822" => println!("{}", now.to_rfc2822()),
+ "rfc3339" => println!("{}", now.to_rfc3339()),
+ _ => unreachable!(),
+ }
+}