X-Git-Url: http://git.scottworley.com/tattlekey/blobdiff_plain/143d53ed1d795c5c5f6e0773ab3c71700d3ef9a0..3c8c23bba013dd7aad787692bc86d58e713977d2:/server/src/main.rs?ds=sidebyside diff --git a/server/src/main.rs b/server/src/main.rs index c4c0b19..ddf5217 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -14,32 +14,50 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; use std::net::UdpSocket; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; const MESSAGE_SIZE: usize = 12; +const LOGFILENAME: &str = "log.csv"; -#[derive(Debug)] +#[derive(Eq, Debug, Hash, PartialEq)] struct MessageKey { epoch: u32, device: u16, seq: u16, } -#[derive(Debug)] +#[derive(Debug, Deserialize, Serialize)] struct Message { - key: MessageKey, - ago: u32, + epoch: u32, + device: u16, + seq: u16, + t: u64, +} +impl Message { + fn key(&self) -> MessageKey { + MessageKey { + epoch: self.epoch, + device: self.device, + seq: self.seq, + } + } } impl From<&[u8; MESSAGE_SIZE]> for Message { fn from(value: &[u8; MESSAGE_SIZE]) -> Self { + let ago = u32::from_be_bytes(value[8..=11].try_into().expect("I can't count")); + let press_time = SystemTime::now() - Duration::new(ago.into(), 0); Self { - key: MessageKey { - epoch: u32::from_be_bytes(value[0..=3].try_into().expect("I can't count")), - device: u16::from_be_bytes(value[4..=5].try_into().expect("I can't count")), - seq: u16::from_be_bytes(value[6..=7].try_into().expect("I can't count")), - }, - ago: u32::from_be_bytes(value[8..=11].try_into().expect("I can't count")), + epoch: u32::from_be_bytes(value[0..=3].try_into().expect("I can't count")), + device: u16::from_be_bytes(value[4..=5].try_into().expect("I can't count")), + seq: u16::from_be_bytes(value[6..=7].try_into().expect("I can't count")), + t: press_time + .duration_since(UNIX_EPOCH) + .expect("Bad time?") + .as_secs(), } } } @@ -53,8 +71,69 @@ impl TryFrom<&[u8]> for Message { } } +#[derive(Debug)] +struct Range { + start: u64, + end: u64, +} +impl Range { + fn new(t: &u64) -> Self { + Self { start: *t, end: *t } + } + fn contains(&self, t: &u64) -> bool { + t > &self.start && t < &self.end + } + fn extend(&mut self, t: &u64) { + if t < &self.start { + self.start = *t; + } + if t > &self.end { + self.end = *t; + } + } +} + +fn merge_message(presses: &mut HashMap, message: Message) { + let key = message.key(); + if let Some(r) = presses.get_mut(&key) { + if !r.contains(&message.t) { + r.extend(&message.t); + } + } else { + presses.insert(key, Range::new(&message.t)); + } +} + +fn replay_log() -> HashMap { + let mut presses = HashMap::new(); + if std::path::Path::new(LOGFILENAME).exists() { + let mut log = csv::Reader::from_path(LOGFILENAME).expect("Couldn't open log for replay"); + for message in log.deserialize() { + merge_message( + &mut presses, + message.expect("Error reading log during replay"), + ); + } + } + presses +} + +fn open_log_for_writing() -> csv::Writer { + let log_file_exists = std::path::Path::new(LOGFILENAME).exists(); + let logfile = std::fs::OpenOptions::new() + .create_new(!log_file_exists) + .append(true) + .open(LOGFILENAME) + .expect("Coudln't open log file"); + csv::WriterBuilder::new() + .has_headers(!log_file_exists) + .from_writer(logfile) +} + fn main() { let socket = UdpSocket::bind("0.0.0.0:29803").expect("couldn't bind to address"); + let mut presses = replay_log(); + let mut log = open_log_for_writing(); loop { let mut buf = [0; MESSAGE_SIZE]; match socket.recv_from(&mut buf) { @@ -66,7 +145,9 @@ fn main() { continue; } let message = Message::try_from(filled_buf).expect("I can't count"); - println!("Got packet from {src_addr}: {message:?}"); + log.serialize(&message).expect("Couldn't write log"); + log.flush().expect("Couldn't flush log"); + merge_message(&mut presses, message); } } }