diff --git a/Cargo.lock b/Cargo.lock index b75da19b441f5ba03b5c8680958eeaac40efb270..e7db55386932a1efe17fd46f07f735466144f23b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -63,9 +63,10 @@ dependencies = [ [[package]] name = "duniter-wotb" -version = "0.5.0" +version = "0.6.0" dependencies = [ "bincode 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/wotb/Cargo.toml b/wotb/Cargo.toml index b9d1e661b2457938ae409b04bf6bb66807baa0a0..fe318989e46c84174bf933ded504670604082f9a 100644 --- a/wotb/Cargo.toml +++ b/wotb/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "duniter-wotb" -version = "0.5.0" -authors = ["nanocryk <nanocryk@duniter.org>"] +version = "0.6.0" +authors = ["nanocryk <nanocryk@duniter.org>", "elois <elois@duniter.org>"] description = "Makes Web of Trust computations for the Duniter project." repository = "https://git.duniter.org/nodes/rust/duniter-rs" readme = "README.md" @@ -14,4 +14,5 @@ path = "lib.rs" [dependencies] serde = "1.0.24" serde_derive = "1.0.24" -bincode = "0.9.2" \ No newline at end of file +bincode = "0.9.2" +byteorder = "1.2.1" \ No newline at end of file diff --git a/wotb/legacy.rs b/wotb/legacy.rs index 02707178a6fd2ffcd36d05e8325e256674ca1087..fb8ffbe778aec2b0f28a5a5cff0c5d07f46c42ef 100644 --- a/wotb/legacy.rs +++ b/wotb/legacy.rs @@ -144,7 +144,7 @@ impl LegacyWebOfTrust { } /// Read *WoT* from file. - pub fn from_file(path: &str) -> Option<LegacyWebOfTrust> { + pub fn legacy_from_file(path: &str) -> Option<LegacyWebOfTrust> { let mut file = match File::open(path) { Ok(file) => file, Err(_) => return None, @@ -162,7 +162,7 @@ impl LegacyWebOfTrust { } /// Write *WoT* to file. - pub fn to_file(&self, path: &str) -> bool { + pub fn legacy_to_file(&self, path: &str) -> bool { let encoded: Vec<u8> = serialize(self, Infinite).unwrap(); match File::create(path) { @@ -373,7 +373,7 @@ impl WebOfTrust for LegacyWebOfTrust { } } - fn issued_count(&mut self, id: NodeId) -> Option<usize> { + fn issued_count(&self, id: NodeId) -> Option<usize> { if id.0 >= self.size() { None } else { @@ -554,7 +554,8 @@ mod tests { #[test] fn wot_tests() { let mut wot = LegacyWebOfTrust::new(3); - generic_wot_test(&mut wot); + let mut wot2 = LegacyWebOfTrust::new(3); + generic_wot_test(&mut wot, &mut wot2); // should be able to make a mem copy { @@ -568,11 +569,11 @@ mod tests { } // serialization - assert_eq!(wot.to_file("test.wot"), true); + assert_eq!(wot.legacy_to_file("test.wot"), true); // deserialization { - let wot2 = LegacyWebOfTrust::from_file("test.wot").unwrap(); + let wot2 = LegacyWebOfTrust::legacy_from_file("test.wot").unwrap(); assert_eq!(wot.size(), wot2.size()); assert_eq!( diff --git a/wotb/lib.rs b/wotb/lib.rs index 566fe7023bd2d9ad56669bbaf9888e5479563bdc..3cc916f5f2f6b81495a4cdf02b5acb63b3dc0080 100644 --- a/wotb/lib.rs +++ b/wotb/lib.rs @@ -31,6 +31,7 @@ unused_qualifications)] extern crate bincode; +extern crate byteorder; extern crate serde; #[macro_use] extern crate serde_derive; @@ -38,6 +39,12 @@ extern crate serde_derive; pub mod legacy; pub use legacy::LegacyWebOfTrust; +use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; + +use std::io::prelude::*; +use std::fs; +use std::fs::File; + /// Wrapper for a node id. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct NodeId(pub usize); @@ -74,6 +81,41 @@ pub enum RemLinkResult { UnknownTarget(), } +/// Results of WebOfTrust parsing from binary file. +#[derive(Debug)] +pub enum WotParseError { + /// FailToOpenFile + FailToOpenFile(std::io::Error), + + /// IOError + IOError(std::io::Error), +} + +impl From<std::io::Error> for WotParseError { + fn from(e: std::io::Error) -> WotParseError { + WotParseError::IOError(e) + } +} + +/// Results of WebOfTrust writing to binary file. +#[derive(Debug)] +pub enum WotWriteError { + /// WrongWotSize + WrongWotSize(), + + /// FailToCreateFile + FailToCreateFile(std::io::Error), + + /// FailToWriteInFile + FailToWriteInFile(std::io::Error), +} + +impl From<std::io::Error> for WotWriteError { + fn from(e: std::io::Error) -> WotWriteError { + WotWriteError::FailToWriteInFile(e) + } +} + /// Results of a certification test. #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum HasLinkResult { @@ -160,7 +202,7 @@ pub trait WebOfTrust { /// Get the number of issued links by a node. /// Returns `None` if this node doesn't exist. - fn issued_count(&mut self, id: NodeId) -> Option<usize>; + fn issued_count(&self, id: NodeId) -> Option<usize>; /// Get sentries array. fn get_sentries(&self, sentry_requirement: usize) -> Vec<NodeId>; @@ -178,6 +220,163 @@ pub trait WebOfTrust { /// Test if a node is outdistanced in the network. /// Returns `Node` if this node doesn't exist. fn is_outdistanced(&self, params: WotDistanceParameters) -> Option<bool>; + + /// Load WebOfTrust from binary file + fn from_file(&mut self, path: &str) -> Result<Vec<u8>, WotParseError> { + let file_size = fs::metadata(path).expect("fail to read wotb file !").len(); + let mut file_pointing_to_blockstamp_size: Vec<u8> = vec![0; file_size as usize]; + match File::open(path) { + Ok(mut file) => { + file.read_exact(&mut file_pointing_to_blockstamp_size.as_mut_slice())?; + } + Err(e) => return Err(WotParseError::FailToOpenFile(e)), + }; + // Read up to 4 bytes (blockstamp_size) + let mut file_pointing_to_blockstamp = file_pointing_to_blockstamp_size.split_off(4); + // Get blockstamp size + let mut buf = &file_pointing_to_blockstamp_size[..]; + let blockstamp_size = buf.read_u32::<BigEndian>().unwrap(); + // Read up to blockstamp_size bytes (blockstamp) + let mut file_pointing_to_nodes_count = + file_pointing_to_blockstamp.split_off(blockstamp_size as usize); + // Read up to 4 bytes (nodes_count) + let mut file_pointing_to_nodes_states = file_pointing_to_nodes_count.split_off(4); + // Read nodes_count + let mut buf = &file_pointing_to_nodes_count[..]; + let nodes_count = buf.read_u32::<BigEndian>().unwrap(); + // Calcule nodes_state size + let nodes_states_size = match nodes_count % 8 { + 0 => nodes_count / 8, + _ => (nodes_count / 8) + 1, + }; + // Read up to nodes_states_size bytes (nodes_states) + let file_pointing_to_links = + file_pointing_to_nodes_states.split_off(nodes_states_size as usize); + // Apply nodes state + let mut count_remaining_nodes = nodes_count; + for byte in file_pointing_to_nodes_states { + let mut byte_integer = u8::from_be(byte); + let mut factor: u8 = 128; + for _i in 0..8 { + if count_remaining_nodes > 0 { + self.add_node(); + if byte_integer >= factor { + byte_integer -= factor; + } else { + println!( + "DEBUG : set_enabled({})", + (nodes_count - count_remaining_nodes) + ); + let test = self.set_enabled( + NodeId((nodes_count - count_remaining_nodes) as usize), + false, + ); + println!("DEBUG {:?}", test); + } + count_remaining_nodes -= 1; + } + factor /= 2; + } + } + // Apply links + let mut buffer_3b: Vec<u8> = Vec::with_capacity(3); + let mut count_bytes = 0; + let mut remaining_links: u8 = 0; + let mut source: u32 = 0; + let mut target: u32 = 0; + for byte in file_pointing_to_links { + if remaining_links == 0 { + target += 1; + remaining_links = u8::from_be(byte); + count_bytes = 0; + } else { + buffer_3b.push(byte); + if count_bytes % 3 == 2 { + let mut buf = &buffer_3b.clone()[..]; + source = buf.read_u24::<BigEndian>().expect("fail to parse source"); + self.add_link(NodeId(source as usize), NodeId((target - 1) as usize)); + remaining_links -= 1; + buffer_3b.clear(); + } + count_bytes += 1; + } + } + Ok(file_pointing_to_blockstamp) + } + + /// Write WebOfTrust to binary file + fn to_file(&self, path: &str, blockstamp: &[u8]) -> Result<(), WotWriteError> { + let mut buffer: Vec<u8> = Vec::new(); + // Write blockstamp size + let blockstamp_size = blockstamp.len() as u32; + let mut bytes: Vec<u8> = Vec::with_capacity(4); + bytes.write_u32::<BigEndian>(blockstamp_size).unwrap(); + buffer.append(&mut bytes); + // Write blockstamp + buffer.append(&mut blockstamp.to_vec()); + // Write nodes_count + let nodes_count = self.size() as u32; + let mut bytes: Vec<u8> = Vec::with_capacity(4); + bytes.write_u32::<BigEndian>(nodes_count).unwrap(); + buffer.append(&mut bytes); + // Write enable state by groups of 8 (count links at the same time) + let mut enable_states: u8 = 0; + let mut factor: u8 = 128; + for n in 0..nodes_count { + match self.is_enabled(NodeId(n as usize)) { + Some(enable) => { + if enable { + enable_states += factor; + } + } + None => { + return Err(WotWriteError::WrongWotSize()); + } + } + if n % 8 == 7 { + factor = 128; + let mut tmp_buf = Vec::with_capacity(1); + tmp_buf.write_u8(enable_states).unwrap(); + buffer.append(&mut tmp_buf); + enable_states = 0; + } else { + factor /= 2; + } + } + // nodes_states padding + if nodes_count % 8 != 7 { + let mut tmp_buf = Vec::with_capacity(1); + tmp_buf.write_u8(enable_states).unwrap(); + buffer.append(&mut tmp_buf); + } + // Write links + for n in 0..nodes_count { + match self.get_links_source(NodeId(n as usize)) { + Some(sources) => { + // Write sources_counts + let mut bytes = Vec::with_capacity(1); + bytes.write_u8(sources.len() as u8).unwrap(); + buffer.append(&mut bytes); + for source in sources.iter() { + // Write source + let mut bytes: Vec<u8> = Vec::with_capacity(3); + bytes.write_u24::<BigEndian>(source.0 as u32).unwrap(); + buffer.append(&mut bytes); + } + } + None => {} + }; + } + // Create or open file + let mut file = match File::create(path) { + Ok(file) => file, + Err(e) => return Err(WotWriteError::FailToCreateFile(e)), + }; + // Write buffer in file + file.write_all(&buffer)?; + + Ok(()) + } } #[cfg(test)] @@ -188,7 +387,7 @@ mod tests { /// /// Clone and file tests are not included in this generic test and should be done in /// the implementation test. - pub fn generic_wot_test<T: WebOfTrust>(wot: &mut T) { + pub fn generic_wot_test<T: WebOfTrust>(wot: &mut T, wot2: &mut T) { // should have an initial size of 0 assert_eq!(wot.size(), 0); @@ -545,5 +744,39 @@ mod tests { }), Some(false) ); // OK : Disabled + + // Write wot in file + assert_eq!( + wot.to_file( + "test.bin", + &[0b0000_0000, 0b0000_0001, 0b0000_0001, 0b0000_0000] + ).unwrap(), + () + ); + + // Read wot from file + { + assert_eq!( + wot2.from_file("test.bin").unwrap(), + vec![0b0000_0000, 0b0000_0001, 0b0000_0001, 0b0000_0000] + ); + assert_eq!(wot.size(), wot2.size()); + assert_eq!( + wot.get_non_sentries(1).len(), + wot2.get_non_sentries(1).len() + ); + assert_eq!(wot.get_disabled().len(), wot2.get_disabled().len()); + assert_eq!(wot2.get_disabled().len(), 1); + assert_eq!(wot2.is_enabled(NodeId(3)), Some(false)); + assert_eq!( + wot2.is_outdistanced(WotDistanceParameters { + node: NodeId(0), + sentry_requirement: 2, + step_max: 1, + x_percent: 1.0, + }), + Some(false) + ); + } } }