diff --git a/shroom-server/Cargo.lock b/shroom-server/Cargo.lock new file mode 100644 index 0000000..6a1f410 --- /dev/null +++ b/shroom-server/Cargo.lock @@ -0,0 +1,428 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" + +[[package]] +name = "bitflags" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" + +[[package]] +name = "bytes" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "crc" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "getrandom" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +dependencies = [ + "cfg-if", + "libc", + "wasi", + "windows-targets", +] + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "indexmap" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "libc" +version = "0.2.169" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "log" +version = "0.4.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "multimap" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" + +[[package]] +name = "once_cell" +version = "1.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" + +[[package]] +name = "petgraph" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "prettyplease" +version = "0.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6924ced06e1f7dfe3fa48d57b9f74f55d8915f5036121bef647ef4b204895fac" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prost" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf" +dependencies = [ + "heck", + "itertools", + "log", + "multimap", + "once_cell", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "regex", + "syn", + "tempfile", +] + +[[package]] +name = "prost-derive" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "prost-types" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16" +dependencies = [ + "prost", +] + +[[package]] +name = "quote" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "shroom-server" +version = "0.1.0" +dependencies = [ + "crc", + "prost", + "prost-build", + "tempfile", +] + +[[package]] +name = "syn" +version = "2.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91" +dependencies = [ + "cfg-if", + "fastrand", + "getrandom", + "once_cell", + "rustix", + "windows-sys", +] + +[[package]] +name = "unicode-ident" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" + +[[package]] +name = "wasi" +version = "0.13.3+wasi-0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "wit-bindgen-rt" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +dependencies = [ + "bitflags", +] diff --git a/shroom-server/Cargo.toml b/shroom-server/Cargo.toml new file mode 100644 index 0000000..4ea387e --- /dev/null +++ b/shroom-server/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "shroom-server" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +crc = "3.2.1" +prost = "0.13.5" + +[build-dependencies] +prost-build = "0.13.5" + +[dev-dependencies] +tempfile = "3.16.0" diff --git a/shroom-server/build.rs b/shroom-server/build.rs new file mode 100644 index 0000000..f946a73 --- /dev/null +++ b/shroom-server/build.rs @@ -0,0 +1,7 @@ +use std::io::Result; + +fn main() -> Result<()> { + println!("cargo:rerun-if-changed=entry.proto"); + prost_build::compile_protos(&["entry.proto"], &["."])?; + Ok(()) +} diff --git a/shroom-server/entry.proto b/shroom-server/entry.proto new file mode 100644 index 0000000..2b7365d --- /dev/null +++ b/shroom-server/entry.proto @@ -0,0 +1,11 @@ +syntax = "proto2"; + +package internalstore; + +message Entry { + optional uint64 timestamp = 1; + repeated float humidity = 2; + optional float temp = 3; + repeated bool humidifer = 4; + optional bool fan = 5; +} diff --git a/shroom-server/src/io.rs b/shroom-server/src/io.rs new file mode 100644 index 0000000..e69de29 diff --git a/shroom-server/src/main.rs b/shroom-server/src/main.rs new file mode 100644 index 0000000..4699e44 --- /dev/null +++ b/shroom-server/src/main.rs @@ -0,0 +1,8 @@ +mod proto { + include!(concat!(env!("OUT_DIR"), "/internalstore.rs")); +} +mod store; + +fn main() { + println!("Hello, world!"); +} diff --git a/shroom-server/src/store.rs b/shroom-server/src/store.rs new file mode 100644 index 0000000..29f67d4 --- /dev/null +++ b/shroom-server/src/store.rs @@ -0,0 +1,405 @@ +use std::fs::{File, OpenOptions}; +use std::io::{Cursor, Read, Seek, SeekFrom, Write}; +use std::path::Path; + +use prost::Message; + +pub use crate::proto::Entry; + +const X25: crc::Crc = crc::Crc::::new(&crc::CRC_16_IBM_SDLC); + +// data is stored in append-only logs +// each data entry starts with a checksum (CRC16), +// followed by a one-byte length, and the +// actual entry encoded in protocol buffer +// upon opening a file, all the entries within it are +// read, any corrupted contents at the end truncated/overwritten + +pub struct Store { + contents: T, + entries: Vec, + start_ts: Option, + end_ts: Option, +} + +pub type Error = std::io::Error; + +pub type Result = std::result::Result; + +pub fn open_read_only>(p: P) -> Result>>> { + let data = std::fs::read(p)?; + + Ok(Store::new(Cursor::new(data))) +} + +pub fn open>(p: P) -> Result> { + let mut f = OpenOptions::new() + .read(true) + .write(true) + .create(true) + .open(p)?; + let _ = f.seek(SeekFrom::Start(0))?; + + Ok(Store::new(f)) +} + +impl Store { + pub fn new(mut t: T) -> Store { + let mut entries = Vec::new(); + let mut start_ts = None; + let mut end_ts = None; + + // start of the next unread entry + let mut cur_pos = 0; + let mut data: Vec = Vec::new(); + loop { + let mut crc16 = [0u8; 2]; + let mut len = [0u8; 1]; + if let Err(_) = t.read_exact(&mut crc16) { + break; + } + if let Err(_) = t.read_exact(&mut len) { + break; + } + + data.resize(len[0] as usize, 0); + if let Err(_) = t.read_exact(&mut data) { + break; + } + + // CRC check data + let calculated = X25.checksum(&data); + let crc = (crc16[0] as u16) | ((crc16[1] as u16) << 8); + if calculated != crc { + break; + } + + // decode data into an entry + if let Ok(entry) = Entry::decode(&data[..]) { + cur_pos = t.stream_position().unwrap(); + + if start_ts.is_none() { + start_ts = entry.timestamp.clone(); + } + if entry.timestamp.is_some() { + end_ts = entry.timestamp.clone(); + } + entries.push(entry); + } else { + break; + } + } + + let _ = t.seek(SeekFrom::Start(cur_pos)).unwrap(); + + Store { + contents: t, + entries, + start_ts, + end_ts, + } + } + + pub fn lookup_range(&self, start: u64, end: Option) -> Vec { + if let Some(start_ts) = self.start_ts { + if let Some(end_range) = end { + if end_range < start_ts { + return Vec::new(); + } + } + } + if let Some(end_ts) = self.end_ts { + if start > end_ts { + return Vec::new(); + } + } + let mut ret = Vec::new(); + for e in &self.entries { + if let Some(ts) = e.timestamp { + if ts >= start { + if let Some(end_range) = end { + if ts > end_range { + continue; + } + } + ret.push(e.clone()); + } + } + } + + ret + } +} + +impl Store { + pub fn append(&mut self, e: &Entry) -> Result<()> { + let payload = e.encode_to_vec(); + let len = payload.len(); + let len_u8: [u8; 1] = if len < 256 { + [len as u8] + } else { + return Err(Error::new(std::io::ErrorKind::Other, "payload too large")); + }; + + // CRC check data + let calculated = X25.checksum(&payload); + let crc: [u8; 2] = [(calculated & 0xff) as u8, ((calculated >> 8) & 0xff) as u8]; + + self.contents.write(&crc)?; + self.contents.write(&len_u8)?; + self.contents.write(&payload)?; + + self.entries.push(e.clone()); + if self.start_ts.is_none() { + self.start_ts = e.timestamp; + } + if let Some(ts) = e.timestamp { + self.end_ts = Some(ts); + } + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::Entry; + + #[test] + fn append_read() { + let temp_file = tempfile::NamedTempFile::new().unwrap(); + let entry = Entry { + timestamp: Some(7), + humidity: vec![0.95], + temp: Some(32.2), + fan: Some(false), + humidifer: vec![false], + }; + + { + let mut store = super::open(temp_file.path()).unwrap(); + store.append(&entry).unwrap(); + + let entries = store.lookup_range(0, None); + assert_eq!(entries, vec![entry.clone()]); + } + + { + let store = super::open_read_only(temp_file.path()).unwrap(); + let entries = store.lookup_range(0, None); + assert_eq!(entries, vec![entry.clone()]); + } + } + + #[test] + fn append2_read2() { + let temp_file = tempfile::NamedTempFile::new().unwrap(); + let entry = Entry { + timestamp: Some(7), + humidity: vec![0.95], + temp: Some(32.2), + fan: Some(false), + humidifer: vec![false], + }; + + let entry2 = Entry { + timestamp: Some(16), + humidity: vec![0.92], + temp: Some(31.2), + fan: Some(true), + humidifer: vec![false], + }; + + { + let mut store = super::open(temp_file.path()).unwrap(); + store.append(&entry).unwrap(); + store.append(&entry2).unwrap(); + + let entries = store.lookup_range(0, None); + assert_eq!(entries, vec![entry.clone(), entry2.clone()]); + } + + { + let store = super::open_read_only(temp_file.path()).unwrap(); + let entries = store.lookup_range(0, None); + assert_eq!(entries, vec![entry.clone(), entry2.clone()]); + } + } + + #[test] + fn append2_read2_interrupted() { + let temp_file = tempfile::NamedTempFile::new().unwrap(); + let entry = Entry { + timestamp: Some(7), + humidity: vec![0.95], + temp: Some(32.2), + fan: Some(false), + humidifer: vec![false], + }; + + let entry2 = Entry { + timestamp: Some(16), + humidity: vec![0.92], + temp: Some(31.2), + fan: Some(true), + humidifer: vec![false], + }; + + { + let mut store = super::open(temp_file.path()).unwrap(); + store.append(&entry).unwrap(); + + let entries = store.lookup_range(0, None); + assert_eq!(entries, vec![entry.clone()]); + } + { + let mut store = super::open(temp_file.path()).unwrap(); + store.append(&entry2).unwrap(); + + let entries = store.lookup_range(0, None); + assert_eq!(entries, vec![entry.clone(), entry2.clone()]); + } + + { + let store = super::open_read_only(temp_file.path()).unwrap(); + let entries = store.lookup_range(0, None); + assert_eq!(entries, vec![entry.clone(), entry2.clone()]); + } + } + + #[test] + fn append2_read2_with_corruption() { + let temp_file = tempfile::NamedTempFile::new().unwrap(); + let entry = Entry { + timestamp: Some(7), + humidity: vec![0.95], + temp: Some(32.2), + fan: Some(false), + humidifer: vec![false], + }; + let entry2 = Entry { + timestamp: Some(16), + humidity: vec![0.92], + temp: Some(31.2), + fan: Some(true), + humidifer: vec![false], + }; + let entry3 = Entry { + timestamp: Some(17), + humidity: vec![0.95], + temp: Some(31.4), + fan: Some(false), + humidifer: vec![true], + }; + + { + use std::io::Write; + let mut store = super::open(temp_file.path()).unwrap(); + store.append(&entry).unwrap(); + store.append(&entry2).unwrap(); + + let entries = store.lookup_range(0, None); + assert_eq!(entries, vec![entry.clone(), entry2.clone()]); + + store.contents.write(&[45, 34, 44]).unwrap(); + store.append(&entry3).unwrap(); + + let entries = store.lookup_range(0, None); + assert_eq!(entries, vec![entry.clone(), entry2.clone(), entry3.clone()]); + } + + { + let store = super::open_read_only(temp_file.path()).unwrap(); + let entries = store.lookup_range(0, None); + assert_eq!(entries, vec![entry.clone(), entry2.clone()]); + } + } + + #[test] + fn append2_with_corruption_write() { + let temp_file = tempfile::NamedTempFile::new().unwrap(); + let entry = Entry { + timestamp: Some(7), + humidity: vec![0.95], + temp: Some(32.2), + fan: Some(false), + humidifer: vec![false], + }; + let entry2 = Entry { + timestamp: Some(16), + humidity: vec![0.92], + temp: Some(31.2), + fan: Some(true), + humidifer: vec![false], + }; + let entry3 = Entry { + timestamp: Some(17), + humidity: vec![0.95], + temp: Some(31.4), + fan: Some(false), + humidifer: vec![true], + }; + + { + use std::io::Write; + let mut store = super::open(temp_file.path()).unwrap(); + store.append(&entry).unwrap(); + store.append(&entry2).unwrap(); + store.contents.write(&[45, 34, 44]).unwrap(); + let entries = store.lookup_range(0, None); + assert_eq!(entries, vec![entry.clone(), entry2.clone()]); + } + + { + let mut store = super::open(temp_file.path()).unwrap(); + store.append(&entry3).unwrap(); + } + + { + let store = super::open_read_only(temp_file.path()).unwrap(); + let entries = store.lookup_range(0, None); + assert_eq!(entries, vec![entry.clone(), entry2.clone(), entry3.clone()]); + } + } + + #[test] + fn read_different_ranges() { + let temp_file = tempfile::NamedTempFile::new().unwrap(); + let entry = Entry { + timestamp: Some(7), + humidity: vec![0.95], + temp: Some(32.2), + fan: Some(false), + humidifer: vec![false], + }; + let entry2 = Entry { + timestamp: Some(16), + humidity: vec![0.92], + temp: Some(31.2), + fan: Some(true), + humidifer: vec![false], + }; + let entry3 = Entry { + timestamp: Some(17), + humidity: vec![0.95], + temp: Some(31.4), + fan: Some(false), + humidifer: vec![true], + }; + + let mut store = super::open(temp_file.path()).unwrap(); + store.append(&entry).unwrap(); + store.append(&entry2).unwrap(); + store.append(&entry3).unwrap(); + let entries = store.lookup_range(4, Some(8)); + assert_eq!(entries, vec![entry.clone()]); + let entries = store.lookup_range(4, Some(16)); + assert_eq!(entries, vec![entry.clone(), entry2.clone()]); + let entries = store.lookup_range(8, Some(16)); + assert_eq!(entries, vec![entry2.clone()]); + let entries = store.lookup_range(4, Some(17)); + assert_eq!(entries, vec![entry.clone(), entry2.clone(), entry3.clone()]); + let entries = store.lookup_range(8, None); + assert_eq!(entries, vec![entry2.clone(), entry3.clone()]); + } +}