diff --git a/Cargo.lock b/Cargo.lock index 24e8cbd3a90f911bf515c1b186c7efdb300449ae..4a85fcb4bfecf048744035ca3d24bedc240329a8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -107,7 +107,7 @@ dependencies = [ [[package]] name = "duniter-wotb" -version = "0.7.1" +version = "0.8.0-a0.1" dependencies = [ "bincode 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/wotb/Cargo.toml b/wotb/Cargo.toml index 2977ee583d4aff8441d85394d8daa3e5377df931..492b4ce786a1020f1de128dd042c991b0500b649 100644 --- a/wotb/Cargo.toml +++ b/wotb/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "duniter-wotb" -version = "0.7.1" +version = "0.8.0-a0.1" 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" diff --git a/wotb/legacy.rs b/wotb/data/legacy.rs similarity index 64% rename from wotb/legacy.rs rename to wotb/data/legacy.rs index 47da7fdfa8c28121311109a7c402d37e6e00ae8a..4aa3262cd10607b2e6957d6bdc2d67ebf75fab60 100644 --- a/wotb/legacy.rs +++ b/wotb/data/legacy.rs @@ -16,21 +16,16 @@ //! Provide a legacy implementation of `WebOfTrust` storage and calculations. //! Its mostly translated directly from the original C++ code. -use std::collections::hash_set::Iter; use std::collections::HashSet; +use std::collections::hash_set::Iter; use std::fs::File; use std::io::prelude::*; -use std::rc::Rc; -use WotDistance; use bincode::{deserialize, serialize, Infinite}; -use HasLinkResult; -use NewLinkResult; -use NodeId; -use RemLinkResult; +use super::{HasLinkResult, NewLinkResult, RemLinkResult}; use WebOfTrust; -use WotDistanceParameters; +use NodeId; #[derive(Debug, Clone, Serialize, Deserialize)] struct Node { @@ -104,19 +99,6 @@ impl Node { } } -#[derive(Debug)] -struct WotStep { - pub previous: Option<Rc<Box<WotStep>>>, - pub node: NodeId, - pub distance: u32, -} - -struct LookupStep { - paths: Vec<Rc<Box<WotStep>>>, - matching_paths: Vec<Rc<Box<WotStep>>>, - distances: Vec<u32>, -} - /// Store a Web of Trust. /// /// Allow to create/remove nodes and links between them. @@ -135,14 +117,6 @@ pub struct LegacyWebOfTrust { } impl LegacyWebOfTrust { - /// Create a new Web of Trust with the maxium certificications count. - pub fn new(max_cert: usize) -> LegacyWebOfTrust { - LegacyWebOfTrust { - nodes: vec![], - max_cert, - } - } - /// Read `WoT` from file. pub fn legacy_from_file(path: &str) -> Option<LegacyWebOfTrust> { let mut file = match File::open(path) { @@ -170,72 +144,16 @@ impl LegacyWebOfTrust { Err(_) => false, } } +} - fn check_matches(&self, node: NodeId, d: u32, d_max: u32, mut checked: Vec<bool>) -> Vec<bool> { - let mut linked_nodes = Vec::new(); - - for linked_node in self.nodes[node.0].links_iter() { - checked[linked_node.0] = true; - linked_nodes.push(*linked_node); - } - - if d < d_max { - for linked_node in &linked_nodes { - checked = self.check_matches(*linked_node, d + 1, d_max, checked); - } - } - - checked - } - - fn lookup( - &self, - source: NodeId, - target: NodeId, - distance: u32, - distance_max: u32, - previous: &Rc<Box<WotStep>>, - mut lookup_step: LookupStep, - ) -> LookupStep { - if source != target && distance <= distance_max { - let mut local_paths: Vec<Rc<Box<WotStep>>> = vec![]; - - for &by in self.nodes[target.0].links_iter() { - if distance < lookup_step.distances[by.0] { - lookup_step.distances[by.0] = distance; - let step = Rc::new(Box::new(WotStep { - previous: Some(Rc::clone(previous)), - node: by, - distance, - })); - - lookup_step.paths.push(Rc::clone(&step)); - local_paths.push(Rc::clone(&step)); - if by == source { - lookup_step.matching_paths.push(Rc::clone(&step)); - } - } - } - - if distance <= distance_max { - for path in &local_paths { - lookup_step = self.lookup( - source, - path.node, - distance + 1, - distance_max, - &Rc::clone(path), - lookup_step, - ); - } - } +impl WebOfTrust for LegacyWebOfTrust { + fn new(max_cert: usize) -> LegacyWebOfTrust { + LegacyWebOfTrust { + nodes: vec![], + max_cert, } - - lookup_step } -} -impl WebOfTrust for LegacyWebOfTrust { fn get_max_link(&self) -> usize { self.max_cert } @@ -342,6 +260,19 @@ impl WebOfTrust for LegacyWebOfTrust { } } + fn is_sentry(&self, node: NodeId, sentry_requirement: usize) -> Option<bool> { + if node.0 >= self.size() { + return None; + } + + let node = &self.nodes[node.0]; + + Some( + node.enabled && node.issued_count() >= sentry_requirement + && node.links_iter().count() >= sentry_requirement, + ) + } + fn get_sentries(&self, sentry_requirement: usize) -> Vec<NodeId> { self.nodes .iter() @@ -380,125 +311,6 @@ impl WebOfTrust for LegacyWebOfTrust { Some(self.nodes[id.0].issued_count) } } - - fn compute_distance(&self, params: WotDistanceParameters) -> Option<WotDistance> { - let WotDistanceParameters { - node, - sentry_requirement, - step_max, - x_percent, - } = params; - - if node.0 >= self.size() { - return None; - } - - let sentry_requirement = sentry_requirement as usize; - - let mut result = WotDistance { - sentries: 0, - success: 0, - success_at_border: 0, - reached: 0, - reached_at_border: 0, - outdistanced: false, - }; - - let mut sentries: Vec<bool> = self.nodes - .iter() - .map(|x| { - x.enabled && x.issued_count() >= sentry_requirement - && x.links_iter().count() >= sentry_requirement - }) - .collect(); - sentries[node.0] = false; - - let mut checked: Vec<bool> = self.nodes.iter().map(|_| false).collect(); - let mut checked_without_border: Vec<bool> = checked.clone(); - - if step_max >= 1 { - checked = self.check_matches(node, 1, step_max, checked); - if step_max >= 2 { - checked_without_border = - self.check_matches(node, 1, step_max - 1, checked_without_border); - } - } - - for ((&sentry, &check), &check_without_border) in sentries - .iter() - .zip(checked.iter()) - .zip(checked_without_border.iter()) - { - if sentry { - result.sentries += 1; - if check { - result.success += 1; - result.reached += 1; - if !check_without_border { - result.success_at_border += 1; - result.reached_at_border += 1; - } - } - } else if check { - result.reached += 1; - } - } - - result.outdistanced = f64::from(result.success) < x_percent * f64::from(result.sentries); - Some(result) - } - - fn is_outdistanced(&self, params: WotDistanceParameters) -> Option<bool> { - let WotDistanceParameters { node, .. } = params; - - if node.0 >= self.size() { - None - } else { - match self.compute_distance(params) { - Some(distance) => Some(distance.outdistanced), - None => None, - } - } - } - - fn get_paths(&self, from: NodeId, to: NodeId, step_max: u32) -> Vec<Vec<NodeId>> { - let mut lookup_step = LookupStep { - paths: vec![], - matching_paths: vec![], - distances: self.nodes.iter().map(|_| step_max + 1).collect(), - }; - - lookup_step.distances[to.0] = 0; - - let root = Rc::new(Box::new(WotStep { - previous: None, - node: to, - distance: 0, - })); - - lookup_step.paths.push(Rc::clone(&root)); - - lookup_step = self.lookup(from, to, 1, step_max, &root, lookup_step); - - let mut result: Vec<Vec<NodeId>> = Vec::with_capacity(lookup_step.matching_paths.len()); - - for step in &lookup_step.matching_paths { - let mut vecpath = vec![]; - let mut step = Rc::clone(step); - - loop { - vecpath.push(step.node); - if step.previous.is_none() { - break; - } - step = step.previous.clone().unwrap(); - } - - result.push(vecpath); - } - - result - } } #[cfg(test)] @@ -565,9 +377,8 @@ mod tests { assert!(node2.has_link_from(&node1)); } - /// This test is a translation of https://github.com/duniter/wotb/blob/master/tests/test.js #[test] fn wot_tests() { - generic_wot_test(LegacyWebOfTrust::new); + generic_wot_test::<LegacyWebOfTrust>(); } } diff --git a/wotb/data/mod.rs b/wotb/data/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..ff079913f72c4bcb90a77a97ff29bc05a0169695 --- /dev/null +++ b/wotb/data/mod.rs @@ -0,0 +1,132 @@ +// Copyright (C) 2017-2018 The Duniter Project Developers. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! Provide data structures to manage web of trusts. +//! `LegacyWebOfTrust` is almost a translation of the legacy C++ coden while +//! `RustyWebOfTrust` is a brand new implementation with a more "rusty" style. + +pub mod legacy; +pub mod rusty; + +/// Wrapper for a node id. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct NodeId(pub usize); + +/// Results of a certification, with the current certification count +/// of the destination as parameter. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum NewLinkResult { + /// Certification worked. + Ok(usize), + /// This certification already exist. + AlreadyCertified(usize), + /// All available certifications has been used. + AllCertificationsUsed(usize), + /// Unknown source. + UnknownSource(), + /// Unknown target. + UnknownTarget(), + /// Self linking is forbidden. + SelfLinkingForbidden(), +} + +/// Results of a certification removal, with the current certification count +/// of the destination as parameter. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum RemLinkResult { + /// Certification has been removed. + Removed(usize), + /// Requested certification doesn't exist. + UnknownCert(usize), + /// Unknown source. + UnknownSource(), + /// Unknown target. + UnknownTarget(), +} + +/// Results of a certification test. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum HasLinkResult { + /// Both nodes are known, here is the result. + Link(bool), + /// Unknown source. + UnknownSource(), + /// Unknown target. + UnknownTarget(), +} + +/// Trait for a Web Of Trust. +/// Allow to provide other implementations of the `WoT` logic instead of the legacy C++ +/// translated one. +pub trait WebOfTrust { + /// Create a new Web of Trust with the maximum of links a node can issue. + fn new(max_links: usize) -> Self; + + /// Get the maximum number of links per user. + fn get_max_link(&self) -> usize; + + /// Set the maximum number of links per user. + fn set_max_link(&mut self, max_link: usize); + + /// Add a new node. + fn add_node(&mut self) -> NodeId; + + /// Remove the last node. + /// Returns `None` if the WoT was empty, otherwise new top node id. + fn rem_node(&mut self) -> Option<NodeId>; + + /// Get the size of the WoT. + fn size(&self) -> usize; + + /// Check if given node is enabled. + /// Returns `None` if this node doesn't exist. + fn is_enabled(&self, id: NodeId) -> Option<bool>; + + /// Set the enabled state of given node. + /// Returns `Null` if this node doesn't exist, `enabled` otherwise. + fn set_enabled(&mut self, id: NodeId, enabled: bool) -> Option<bool>; + + /// Get enabled node array. + fn get_enabled(&self) -> Vec<NodeId>; + + /// Get disabled node array. + fn get_disabled(&self) -> Vec<NodeId>; + + /// Try to add a link from the source to the target. + fn add_link(&mut self, source: NodeId, target: NodeId) -> NewLinkResult; + + /// Try to remove a link from the source to the target. + fn rem_link(&mut self, source: NodeId, target: NodeId) -> RemLinkResult; + + /// Test if there is a link from the source to the target. + fn has_link(&self, source: NodeId, target: NodeId) -> HasLinkResult; + + /// Get the list of links source for this target. + /// Returns `None` if this node doesn't exist. + fn get_links_source(&self, target: NodeId) -> Option<Vec<NodeId>>; + + /// Get the number of issued links by a node. + /// Returns `None` if this node doesn't exist. + fn issued_count(&self, id: NodeId) -> Option<usize>; + + /// Test if a node is a sentry. + fn is_sentry(&self, node: NodeId, sentry_requirement: usize) -> Option<bool>; + + /// Get sentries array. + fn get_sentries(&self, sentry_requirement: usize) -> Vec<NodeId>; + + /// Get non sentries array. + fn get_non_sentries(&self, sentry_requirement: usize) -> Vec<NodeId>; +} diff --git a/wotb/rusty.rs b/wotb/data/rusty.rs similarity index 56% rename from wotb/rusty.rs rename to wotb/data/rusty.rs index b87ed0e89362fda6b7386b0f13d4db360a0f453b..1a47cfc515c12c22ac952a55bb7d94df48c6653f 100644 --- a/wotb/rusty.rs +++ b/wotb/data/rusty.rs @@ -15,16 +15,10 @@ //! Experimental implementation of the Web of Trust in a more "rusty" style. -use rayon::prelude::*; use std::collections::HashSet; - -use HasLinkResult; -use NewLinkResult; -use RemLinkResult; +use rayon::prelude::*; use WebOfTrust; -use WotDistance; -use WotDistanceParameters; - +use super::{HasLinkResult, NewLinkResult, RemLinkResult}; use NodeId; /// A node in the `WoT` graph. @@ -32,10 +26,8 @@ use NodeId; struct Node { /// Is this node enabled ? enabled: bool, - /// Set of links this node is the target. links_source: HashSet<NodeId>, - /// Number of links the node issued. issued_count: usize, } @@ -60,31 +52,14 @@ pub struct RustyWebOfTrust { max_links: usize, } -impl RustyWebOfTrust { - /// Create a new Web of Trust with the maximum of links a node can issue. - pub fn new(max_links: usize) -> RustyWebOfTrust { +impl WebOfTrust for RustyWebOfTrust { + fn new(max_links: usize) -> RustyWebOfTrust { RustyWebOfTrust { nodes: vec![], max_links, } } - /// Test if a node is a sentry. - pub fn is_sentry(&self, node: NodeId, sentry_requirement: usize) -> Option<bool> { - if node.0 >= self.size() { - return None; - } - - let node = &self.nodes[node.0]; - - Some( - node.enabled && node.issued_count >= sentry_requirement - && node.links_source.len() >= sentry_requirement, - ) - } -} - -impl WebOfTrust for RustyWebOfTrust { fn get_max_link(&self) -> usize { self.max_links } @@ -193,6 +168,19 @@ impl WebOfTrust for RustyWebOfTrust { self.nodes.get(id.0).map(|n| n.issued_count) } + fn is_sentry(&self, node: NodeId, sentry_requirement: usize) -> Option<bool> { + if node.0 >= self.size() { + return None; + } + + let node = &self.nodes[node.0]; + + Some( + node.enabled && node.issued_count >= sentry_requirement + && node.links_source.len() >= sentry_requirement, + ) + } + fn get_sentries(&self, sentry_requirement: usize) -> Vec<NodeId> { self.nodes .par_iter() @@ -217,131 +205,6 @@ impl WebOfTrust for RustyWebOfTrust { .map(|(i, _)| NodeId(i)) .collect() } - - fn get_paths(&self, from: NodeId, to: NodeId, k_max: u32) -> Vec<Vec<NodeId>> { - // 1. We explore the k_max area around `to`, and only remember backward - // links of the smallest distance. - - // Stores for each node its distance to `to` node and its backward links. - // By default all nodes are out of range (`k_max + 1`) and links are known. - let mut graph: Vec<(u32, Vec<usize>)> = - self.nodes.iter().map(|_| (k_max + 1, vec![])).collect(); - // `to` node is at distance 0, and have no backward links. - graph[to.0] = (0, vec![]); - // Explored zone border. - let mut border = HashSet::new(); - border.insert(to.0); - - for distance in 1..(k_max + 1) { - let mut next_border = HashSet::new(); - - for node in border { - for source in &self.nodes[node].links_source { - if graph[source.0].0 > distance { - // shorter path, we replace - graph[source.0] = (distance, vec![node]); - next_border.insert(source.0); - } else if graph[source.0].0 == distance { - // same length, we combine - graph[source.0].1.push(node); - next_border.insert(source.0); - } - } - } - - border = next_border; - } - - // 2. If `from` is found, we follow the backward links and build paths. - // For each path, we look at the last element sources and build new paths with them. - let mut paths = vec![vec![from]]; - - for _ in 1..(k_max + 1) { - let mut new_paths = vec![]; - - for path in &paths { - let node = path.last().unwrap(); - - if node == &to { - // If path is complete, we keep it. - new_paths.push(path.clone()) - } else { - // If not complete we comlete paths - let sources = &graph[node.0]; - for source in &sources.1 { - let mut new_path = path.clone(); - new_path.push(NodeId(*source)); - new_paths.push(new_path); - } - } - } - - paths = new_paths; - } - - paths - } - - fn compute_distance(&self, params: WotDistanceParameters) -> Option<WotDistance> { - let WotDistanceParameters { - node, - sentry_requirement, - step_max, - x_percent, - } = params; - - if node.0 >= self.size() { - return None; - } - - let mut area = HashSet::new(); - area.insert(node); - let mut border = HashSet::new(); - border.insert(node); - - for _ in 0..step_max { - border = border - .par_iter() - .map(|&id| { - self.nodes[id.0] - .links_source - .iter() - .filter(|source| !area.contains(source)) - .cloned() - .collect::<HashSet<_>>() - }) - .reduce(HashSet::new, |mut acc, sources| { - for source in sources { - acc.insert(source); - } - acc - }); - area.extend(border.iter()); - } - - let sentries: Vec<_> = self.get_sentries(sentry_requirement as usize); - let mut success = area.iter().filter(|n| sentries.contains(n)).count() as u32; - let success_at_border = border.iter().filter(|n| sentries.contains(n)).count() as u32; - let mut sentries = sentries.len() as u32; - if self.is_sentry(node, sentry_requirement as usize).unwrap() { - sentries -= 1; - success -= 1; - } - - Some(WotDistance { - sentries, - reached: area.len() as u32, - reached_at_border: border.len() as u32, - success, - success_at_border, - outdistanced: f64::from(success) < x_percent * f64::from(sentries), - }) - } - - fn is_outdistanced(&self, params: WotDistanceParameters) -> Option<bool> { - self.compute_distance(params) - .map(|result| result.outdistanced) - } } #[cfg(test)] @@ -351,6 +214,6 @@ mod tests { #[test] fn wot_tests() { - generic_wot_test(RustyWebOfTrust::new); + generic_wot_test::<RustyWebOfTrust>(); } } diff --git a/wotb/lib.rs b/wotb/lib.rs index 8d760fb166a131334acc3fe9163b3b25e068ebc4..5169d7ca657780ab9bb48eab7e02f322c2f284d6 100644 --- a/wotb/lib.rs +++ b/wotb/lib.rs @@ -27,11 +27,9 @@ //! [js-tests]: https://github.com/duniter/wotb/blob/master/wotcpp/webOfTrust.cpp #![cfg_attr(feature = "strict", deny(warnings))] -#![deny( - missing_docs, missing_debug_implementations, missing_copy_implementations, trivial_casts, - trivial_numeric_casts, unsafe_code, unstable_features, unused_import_braces, - unused_qualifications -)] +#![deny(missing_docs, missing_debug_implementations, missing_copy_implementations, trivial_casts, + trivial_numeric_casts, unsafe_code, unstable_features, unused_import_braces, + unused_qualifications)] extern crate bincode; extern crate byteorder; @@ -40,359 +38,30 @@ extern crate serde; #[macro_use] extern crate serde_derive; -pub mod legacy; -pub mod rusty; - -pub use legacy::LegacyWebOfTrust; - -use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; - -use std::fs; -use std::fs::File; -use std::io::prelude::*; - -/// Wrapper for a node id. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub struct NodeId(pub usize); - -/// Results of a certification, with the current certification count -/// of the destination as parameter. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub enum NewLinkResult { - /// Certification worked. - Ok(usize), - /// This certification already exist. - AlreadyCertified(usize), - /// All available certifications has been used. - AllCertificationsUsed(usize), - /// Unknown source. - UnknownSource(), - /// Unknown target. - UnknownTarget(), - /// Self linking is forbidden. - SelfLinkingForbidden(), -} - -/// Results of a certification removal, with the current certification count -/// of the destination as parameter. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub enum RemLinkResult { - /// Certification has been removed. - Removed(usize), - /// Requested certification doesn't exist. - UnknownCert(usize), - /// Unknown source. - UnknownSource(), - /// Unknown target. - 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 { - /// Both nodes are known, here is the result. - Link(bool), - /// Unknown source. - UnknownSource(), - /// Unknown target. - UnknownTarget(), -} - -/// Paramters for `WoT` distance calculations -#[derive(Debug, Copy, Clone, PartialEq)] -pub struct WotDistanceParameters { - /// Node from where distances are calculated. - pub node: NodeId, - /// Links count received AND issued to be a sentry. - pub sentry_requirement: u32, - /// Currency parameter. - pub step_max: u32, - /// Currency parameter. - pub x_percent: f64, -} - -/// Results of `WebOfTrust::compute_distance`. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct WotDistance { - /// Sentries count - pub sentries: u32, - /// Success count - pub success: u32, - /// Succes at border count - pub success_at_border: u32, - /// Reached count - pub reached: u32, - /// Reached at border count - pub reached_at_border: u32, - /// Is the node outdistanced ? - pub outdistanced: bool, -} - -/// Trait for a Web Of Trust. -/// Allow to provide other implementations of the `WoT` logic instead of the legacy C++ -/// translated one. -pub trait WebOfTrust: Clone { - /// Get the maximum number of links per user. - fn get_max_link(&self) -> usize; - - /// Set the maximum number of links per user. - fn set_max_link(&mut self, max_link: usize); - - /// Add a new node. - fn add_node(&mut self) -> NodeId; - - /// Remove the last node. - /// Returns `None` if the WoT was empty, otherwise new top node id. - fn rem_node(&mut self) -> Option<NodeId>; - - /// Get the size of the WoT. - fn size(&self) -> usize; - - /// Check if given node is enabled. - /// Returns `None` if this node doesn't exist. - fn is_enabled(&self, id: NodeId) -> Option<bool>; - - /// Set the enabled state of given node. - /// Returns `Null` if this node doesn't exist, `enabled` otherwise. - fn set_enabled(&mut self, id: NodeId, enabled: bool) -> Option<bool>; +pub mod data; +pub mod operations; - /// Get enabled node array. - fn get_enabled(&self) -> Vec<NodeId>; - - /// Get disabled node array. - fn get_disabled(&self) -> Vec<NodeId>; - - /// Try to add a link from the source to the target. - fn add_link(&mut self, source: NodeId, target: NodeId) -> NewLinkResult; - - /// Try to remove a link from the source to the target. - fn rem_link(&mut self, source: NodeId, target: NodeId) -> RemLinkResult; - - /// Test if there is a link from the source to the target. - fn has_link(&self, source: NodeId, target: NodeId) -> HasLinkResult; - - /// Get the list of links source for this target. - /// Returns `None` if this node doesn't exist. - fn get_links_source(&self, target: NodeId) -> Option<Vec<NodeId>>; - - /// Get the number of issued links by a node. - /// Returns `None` if this node doesn't exist. - fn issued_count(&self, id: NodeId) -> Option<usize>; - - /// Get sentries array. - fn get_sentries(&self, sentry_requirement: usize) -> Vec<NodeId>; - - /// Get non sentries array. - fn get_non_sentries(&self, sentry_requirement: usize) -> Vec<NodeId>; - - /// Get paths from one node to the other. - fn get_paths(&self, from: NodeId, to: NodeId, k_max: u32) -> Vec<Vec<NodeId>>; - - /// Compute distance between a node and the network. - /// Returns `None` if this node doesn't exist. - fn compute_distance(&self, params: WotDistanceParameters) -> Option<WotDistance>; - - /// 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 { - let _test = self.set_enabled( - NodeId((nodes_count - count_remaining_nodes) as usize), - false, - ); - } - 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 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()[..]; - let 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 { - if let Some(sources) = self.get_links_source(NodeId(n as usize)) { - // 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 { - // Write source - let mut bytes: Vec<u8> = Vec::with_capacity(3); - bytes.write_u24::<BigEndian>(source.0 as u32).unwrap(); - buffer.append(&mut bytes); - } - }; - } - // 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(()) - } -} +pub use data::{NodeId, WebOfTrust}; #[cfg(test)] mod tests { use super::*; + use data::*; + use operations::distance::*; + use operations::path::*; + use operations::file::*; /// Test translated from https://github.com/duniter/wotb/blob/master/tests/test.js /// /// 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, F>(generator: F) + pub fn generic_wot_test<W>() where - F: Fn(usize) -> T, + W: WebOfTrust + Sync, { - let mut wot = generator(3); + let path_finder = RustyPathFinder {}; + let distance_calculator = RustyDistanceCalculator {}; + let mut wot = W::new(3); // should have an initial size of 0 assert_eq!(wot.size(), 0); @@ -584,32 +253,41 @@ mod tests { // should successfully use distance rule assert_eq!( - wot.is_outdistanced(WotDistanceParameters { - node: NodeId(0), - sentry_requirement: 1, - step_max: 1, - x_percent: 1.0, - },), + distance_calculator.is_outdistanced( + &wot, + WotDistanceParameters { + node: NodeId(0), + sentry_requirement: 1, + step_max: 1, + x_percent: 1.0, + }, + ), Some(false) ); // => no because 2,4,5 have certified him assert_eq!( - wot.is_outdistanced(WotDistanceParameters { - node: NodeId(0), - sentry_requirement: 2, - step_max: 1, - x_percent: 1.0, - },), + distance_calculator.is_outdistanced( + &wot, + WotDistanceParameters { + node: NodeId(0), + sentry_requirement: 2, + step_max: 1, + x_percent: 1.0, + }, + ), Some(false) ); // => no because only member 2 has 2 certs, and has certified him assert_eq!( - wot.is_outdistanced(WotDistanceParameters { - node: NodeId(0), - sentry_requirement: 3, - step_max: 1, - x_percent: 1.0, - },), + distance_calculator.is_outdistanced( + &wot, + WotDistanceParameters { + node: NodeId(0), + sentry_requirement: 3, + step_max: 1, + x_percent: 1.0, + }, + ), Some(false) ); // => no because no member has issued 3 certifications @@ -633,48 +311,66 @@ mod tests { assert_eq!(wot.get_non_sentries(1).len(), 11); // 12 - 1 assert_eq!(wot.get_non_sentries(2).len(), 12); // 12 - 0 assert_eq!(wot.get_non_sentries(3).len(), 12); // 12 - 0 - assert_eq!(wot.get_paths(NodeId(3), NodeId(0), 1).len(), 0); // KO - assert_eq!(wot.get_paths(NodeId(3), NodeId(0), 2).len(), 1); // It exists 3 -> 2 -> 0 - assert!(wot.get_paths(NodeId(3), NodeId(0), 2).contains(&vec![ - NodeId(3), - NodeId(2), - NodeId(0), - ])); - - assert_eq!( - wot.is_outdistanced(WotDistanceParameters { - node: NodeId(0), - sentry_requirement: 1, - step_max: 1, - x_percent: 1.0, - },), + assert_eq!( + path_finder.find_paths(&wot, NodeId(3), NodeId(0), 1).len(), + 0 + ); // KO + assert_eq!( + path_finder.find_paths(&wot, NodeId(3), NodeId(0), 2).len(), + 1 + ); // It exists 3 -> 2 -> 0 + assert!( + path_finder + .find_paths(&wot, NodeId(3), NodeId(0), 2) + .contains(&vec![NodeId(3), NodeId(2), NodeId(0)]) + ); + + assert_eq!( + distance_calculator.is_outdistanced( + &wot, + WotDistanceParameters { + node: NodeId(0), + sentry_requirement: 1, + step_max: 1, + x_percent: 1.0, + }, + ), Some(false) ); // OK : 2 -> 0 assert_eq!( - wot.is_outdistanced(WotDistanceParameters { - node: NodeId(0), - sentry_requirement: 2, - step_max: 1, - x_percent: 1.0, - },), + distance_calculator.is_outdistanced( + &wot, + WotDistanceParameters { + node: NodeId(0), + sentry_requirement: 2, + step_max: 1, + x_percent: 1.0, + }, + ), Some(false) ); // OK : 2 -> 0 assert_eq!( - wot.is_outdistanced(WotDistanceParameters { - node: NodeId(0), - sentry_requirement: 3, - step_max: 1, - x_percent: 1.0, - },), + distance_calculator.is_outdistanced( + &wot, + WotDistanceParameters { + node: NodeId(0), + sentry_requirement: 3, + step_max: 1, + x_percent: 1.0, + }, + ), Some(false) ); // OK : no stry \w 3 lnk assert_eq!( - wot.is_outdistanced(WotDistanceParameters { - node: NodeId(0), - sentry_requirement: 2, - step_max: 2, - x_percent: 1.0, - },), + distance_calculator.is_outdistanced( + &wot, + WotDistanceParameters { + node: NodeId(0), + sentry_requirement: 2, + step_max: 2, + x_percent: 1.0, + }, + ), Some(false) ); // OK : 2 -> 0 @@ -693,48 +389,66 @@ mod tests { assert_eq!(wot.get_non_sentries(1).len(), 9); // 12 - 3 assert_eq!(wot.get_non_sentries(2).len(), 11); // 12 - 1 assert_eq!(wot.get_non_sentries(3).len(), 12); // 12 - 0 - assert_eq!(wot.get_paths(NodeId(3), NodeId(0), 1).len(), 0); // KO - assert_eq!(wot.get_paths(NodeId(3), NodeId(0), 2).len(), 1); // It exists 3 -> 2 -> 0 - assert!(wot.get_paths(NodeId(3), NodeId(0), 2).contains(&vec![ - NodeId(3), - NodeId(2), - NodeId(0), - ])); - - assert_eq!( - wot.is_outdistanced(WotDistanceParameters { - node: NodeId(0), - sentry_requirement: 1, - step_max: 1, - x_percent: 1.0, - },), + assert_eq!( + path_finder.find_paths(&wot, NodeId(3), NodeId(0), 1).len(), + 0 + ); // KO + assert_eq!( + path_finder.find_paths(&wot, NodeId(3), NodeId(0), 2).len(), + 1 + ); // It exists 3 -> 2 -> 0 + assert!( + path_finder + .find_paths(&wot, NodeId(3), NodeId(0), 2) + .contains(&vec![NodeId(3), NodeId(2), NodeId(0)]) + ); + + assert_eq!( + distance_calculator.is_outdistanced( + &wot, + WotDistanceParameters { + node: NodeId(0), + sentry_requirement: 1, + step_max: 1, + x_percent: 1.0, + }, + ), Some(true) ); // KO : No path 3 -> 0 assert_eq!( - wot.is_outdistanced(WotDistanceParameters { - node: NodeId(0), - sentry_requirement: 2, - step_max: 1, - x_percent: 1.0, - },), + distance_calculator.is_outdistanced( + &wot, + WotDistanceParameters { + node: NodeId(0), + sentry_requirement: 2, + step_max: 1, + x_percent: 1.0, + }, + ), Some(true) ); // KO : No path 3 -> 0 assert_eq!( - wot.is_outdistanced(WotDistanceParameters { - node: NodeId(0), - sentry_requirement: 3, - step_max: 1, - x_percent: 1.0, - },), + distance_calculator.is_outdistanced( + &wot, + WotDistanceParameters { + node: NodeId(0), + sentry_requirement: 3, + step_max: 1, + x_percent: 1.0, + }, + ), Some(false) ); // OK : no stry \w 3 lnk assert_eq!( - wot.is_outdistanced(WotDistanceParameters { - node: NodeId(0), - sentry_requirement: 2, - step_max: 2, - x_percent: 1.0, - },), + distance_calculator.is_outdistanced( + &wot, + WotDistanceParameters { + node: NodeId(0), + sentry_requirement: 2, + step_max: 2, + x_percent: 1.0, + }, + ), Some(false) ); // OK : 3 -> 2 -> 0 @@ -752,30 +466,38 @@ mod tests { assert_eq!(wot.set_enabled(NodeId(3), false), Some(false)); assert_eq!(wot.get_disabled().len(), 1); assert_eq!( - wot.is_outdistanced(WotDistanceParameters { - node: NodeId(0), - sentry_requirement: 2, - step_max: 1, - x_percent: 1.0, - },), + distance_calculator.is_outdistanced( + &wot, + WotDistanceParameters { + node: NodeId(0), + sentry_requirement: 2, + step_max: 1, + x_percent: 1.0, + }, + ), Some(false) ); // OK : Disabled + let file_formater = BinaryFileFormater {}; + // Write wot in file assert_eq!( - wot.to_file( - "test.wot", - &[0b0000_0000, 0b0000_0001, 0b0000_0001, 0b0000_0000] - ).unwrap(), + file_formater + .to_file( + &wot, + &[0b0000_0000, 0b0000_0001, 0b0000_0001, 0b0000_0000], + "test.wot" + ) + .unwrap(), () ); - let mut wot2 = generator(3); + let (wot2, blockstamp2) = file_formater.from_file::<W>("test.wot", 3).unwrap(); // Read wot from file { assert_eq!( - wot2.from_file("test.wot").unwrap(), + blockstamp2, vec![0b0000_0000, 0b0000_0001, 0b0000_0001, 0b0000_0000] ); assert_eq!(wot.size(), wot2.size()); @@ -787,20 +509,25 @@ mod tests { 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, - },), + distance_calculator.is_outdistanced( + &wot2, + WotDistanceParameters { + node: NodeId(0), + sentry_requirement: 2, + step_max: 1, + x_percent: 1.0, + }, + ), Some(false) ); } // Read g1_genesis wot - let mut wot3 = generator(100); + let (wot3, blockstamp3) = file_formater + .from_file::<W>("tests/g1_genesis.bin", 100) + .unwrap(); assert_eq!( - wot3.from_file("tests/g1_genesis.bin").unwrap(), + blockstamp3, vec![ 57, 57, 45, 48, 48, 48, 48, 49, 50, 65, 68, 52, 57, 54, 69, 67, 65, 53, 54, 68, 69, 48, 66, 56, 69, 53, 68, 54, 70, 55, 52, 57, 66, 55, 67, 66, 69, 55, 56, 53, 53, 51, @@ -815,12 +542,15 @@ mod tests { // Test compute_distance in g1_genesis wot assert_eq!( - wot3.compute_distance(WotDistanceParameters { - node: NodeId(37), - sentry_requirement: 3, - step_max: 5, - x_percent: 0.8, - },), + distance_calculator.compute_distance( + &wot3, + WotDistanceParameters { + node: NodeId(37), + sentry_requirement: 3, + step_max: 5, + x_percent: 0.8, + }, + ), Some(WotDistance { sentries: 48, success: 48, @@ -839,7 +569,7 @@ mod tests { let mut centralities = vec![0; wot_size]; for i in 0..wot_size { for j in 0..wot_size { - let paths = wot3.get_paths(NodeId(i), NodeId(j), 5); + let paths = path_finder.find_paths(&wot3, NodeId(i), NodeId(j), 5); let mut intermediate_members: Vec<NodeId> = Vec::new(); for path in paths { if path.len() > 2 { diff --git a/wotb/operations/distance.rs b/wotb/operations/distance.rs new file mode 100644 index 0000000000000000000000000000000000000000..e097fe5bff7ee40be1c0ffaba6d915f44ff919b5 --- /dev/null +++ b/wotb/operations/distance.rs @@ -0,0 +1,128 @@ +// Copyright (C) 2017-2018 The Duniter Project Developers. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! Provide a trait and implementations to compute distances. + +use std::collections::HashSet; +use rayon::prelude::*; +use data::WebOfTrust; +use data::NodeId; + +/// Paramters for `WoT` distance calculations +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct WotDistanceParameters { + /// Node from where distances are calculated. + pub node: NodeId, + /// Links count received AND issued to be a sentry. + pub sentry_requirement: u32, + /// Currency parameter. + pub step_max: u32, + /// Currency parameter. + pub x_percent: f64, +} + +/// Results of `WebOfTrust::compute_distance`. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct WotDistance { + /// Sentries count + pub sentries: u32, + /// Success count + pub success: u32, + /// Succes at border count + pub success_at_border: u32, + /// Reached count + pub reached: u32, + /// Reached at border count + pub reached_at_border: u32, + /// Is the node outdistanced ? + pub outdistanced: bool, +} + +/// Compute distance between nodes of a `WebOfTrust`. +pub trait DistanceCalculator<T: WebOfTrust> { + /// Compute distance between a node and the network. + /// Returns `None` if this node doesn't exist. + fn compute_distance(&self, wot: &T, params: WotDistanceParameters) -> Option<WotDistance>; + + /// Test if a node is outdistanced in the network. + /// Returns `Node` if this node doesn't exist. + fn is_outdistanced(&self, wot: &T, params: WotDistanceParameters) -> Option<bool>; +} + +/// Calculate distances between 2 members in a `WebOfTrust`. +#[derive(Debug, Clone, Copy)] +pub struct RustyDistanceCalculator; + +impl<T: WebOfTrust + Sync> DistanceCalculator<T> for RustyDistanceCalculator { + fn compute_distance(&self, wot: &T, params: WotDistanceParameters) -> Option<WotDistance> { + let WotDistanceParameters { + node, + sentry_requirement, + step_max, + x_percent, + } = params; + + if node.0 >= wot.size() { + return None; + } + + let mut area = HashSet::new(); + area.insert(node); + let mut border = HashSet::new(); + border.insert(node); + + for _ in 0..step_max { + border = border + .par_iter() + .map(|&id| { + wot.get_links_source(id) + .unwrap() + .iter() + .filter(|source| !area.contains(source)) + .cloned() + .collect::<HashSet<_>>() + }) + .reduce(HashSet::new, |mut acc, sources| { + for source in sources { + acc.insert(source); + } + acc + }); + area.extend(border.iter()); + } + + let sentries: Vec<_> = wot.get_sentries(sentry_requirement as usize); + let mut success = area.iter().filter(|n| sentries.contains(n)).count() as u32; + let success_at_border = border.iter().filter(|n| sentries.contains(n)).count() as u32; + let mut sentries = sentries.len() as u32; + if wot.is_sentry(node, sentry_requirement as usize).unwrap() { + sentries -= 1; + success -= 1; + } + + Some(WotDistance { + sentries, + reached: area.len() as u32, + reached_at_border: border.len() as u32, + success, + success_at_border, + outdistanced: f64::from(success) < x_percent * f64::from(sentries), + }) + } + + fn is_outdistanced(&self, wot: &T, params: WotDistanceParameters) -> Option<bool> { + Self::compute_distance(&self, wot, params).map(|result| result.outdistanced) + } +} diff --git a/wotb/operations/file.rs b/wotb/operations/file.rs new file mode 100644 index 0000000000000000000000000000000000000000..7faba196f27be96d49217d1e74e8540c8682c79a --- /dev/null +++ b/wotb/operations/file.rs @@ -0,0 +1,240 @@ +// Copyright (C) 2017-2018 The Duniter Project Developers. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! Provide a trait and implementation to read and write `WebOfTrust` to disk. + +use data::NodeId; +use std::io::prelude::*; +use std::io; +use std::fs; +use std::fs::File; + +use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; + +use data::WebOfTrust; + +/// Results of `WebOfTrust` parsing from binary file. +#[derive(Debug)] +pub enum WotParseError { + /// FailToOpenFile + FailToOpenFile(io::Error), + + /// IOError + IOError(io::Error), +} + +impl From<io::Error> for WotParseError { + fn from(e: io::Error) -> WotParseError { + WotParseError::IOError(e) + } +} + +/// Results of `WebOfTrust` writing to binary file. +#[derive(Debug)] +pub enum WotWriteError { + /// WrongWotSize + WrongWotSize(), + + /// FailToCreateFile + FailToCreateFile(io::Error), + + /// FailToWriteInFile + FailToWriteInFile(io::Error), +} + +impl From<io::Error> for WotWriteError { + fn from(e: io::Error) -> WotWriteError { + WotWriteError::FailToWriteInFile(e) + } +} + +/// Provide Read/Write functions for `WebOfTrust` objects. +pub trait FileFormater { + /// Try to read a `WebOfTrust` from a file. + fn from_file<T: WebOfTrust>( + &self, + path: &str, + max_links: usize, + ) -> Result<(T, Vec<u8>), WotParseError>; + + /// Tru to write a `WebOfTrust` in a file. + fn to_file<T: WebOfTrust>(&self, wot: &T, data: &[u8], path: &str) + -> Result<(), WotWriteError>; +} + +/// Read and write WebOfTrust in a binary format. +#[derive(Debug, Clone, Copy)] +pub struct BinaryFileFormater; + +impl FileFormater for BinaryFileFormater { + /// Try to read a `WebOfTrust` from a file. + fn from_file<T: WebOfTrust>( + &self, + path: &str, + max_links: usize, + ) -> Result<(T, Vec<u8>), WotParseError> { + let mut wot = T::new(max_links); + + 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 { + wot.add_node(); + if byte_integer >= factor { + byte_integer -= factor; + } else { + let _test = wot.set_enabled( + NodeId((nodes_count - count_remaining_nodes) as usize), + false, + ); + } + 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 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()[..]; + let source = buf.read_u24::<BigEndian>().expect("fail to parse source"); + wot.add_link(NodeId(source as usize), NodeId((target - 1) as usize)); + remaining_links -= 1; + buffer_3b.clear(); + } + count_bytes += 1; + } + } + Ok((wot, file_pointing_to_blockstamp)) + } + + /// Try to write a `WebOfTrust` in a file. + fn to_file<T: WebOfTrust>( + &self, + wot: &T, + data: &[u8], + path: &str, + ) -> Result<(), WotWriteError> { + let mut buffer: Vec<u8> = Vec::new(); + // Write blockstamp size + let blockstamp_size = data.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 data.to_vec()); + // Write nodes_count + let nodes_count = wot.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 wot.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 { + if let Some(sources) = wot.get_links_source(NodeId(n as usize)) { + // 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 { + // Write source + let mut bytes: Vec<u8> = Vec::with_capacity(3); + bytes.write_u24::<BigEndian>(source.0 as u32).unwrap(); + buffer.append(&mut bytes); + } + }; + } + // 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(()) + } +} diff --git a/wotb/operations/mod.rs b/wotb/operations/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..2bd2782e2ec65ae636df61d6dde65cb4b055fccb --- /dev/null +++ b/wotb/operations/mod.rs @@ -0,0 +1,20 @@ +// Copyright (C) 2017-2018 The Duniter Project Developers. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! Provide operation traits and implementations on `WebOfTrust` objects. + +pub mod path; +pub mod distance; +pub mod file; diff --git a/wotb/operations/path.rs b/wotb/operations/path.rs new file mode 100644 index 0000000000000000000000000000000000000000..528283128702115dad6bf2d11440487209c50a5b --- /dev/null +++ b/wotb/operations/path.rs @@ -0,0 +1,102 @@ +// Copyright (C) 2017-2018 The Duniter Project Developers. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! Provide a trait and implementations to find paths between nodes. + +use std::collections::HashSet; +use data::NodeId; +use data::WebOfTrust; + +/// Find paths between 2 nodes of a `WebOfTrust`. +pub trait PathFinder<T: WebOfTrust> { + /// Get paths from one node to the other. + fn find_paths(&self, wot: &T, from: NodeId, to: NodeId, k_max: u32) -> Vec<Vec<NodeId>>; +} + +/// A new "rusty-er" implementation of `WoT` path finding. +#[derive(Debug, Clone, Copy)] +pub struct RustyPathFinder; + +impl<T: WebOfTrust> PathFinder<T> for RustyPathFinder { + fn find_paths(&self, wot: &T, from: NodeId, to: NodeId, k_max: u32) -> Vec<Vec<NodeId>> { + if from.0 >= wot.size() || to.0 >= wot.size() { + return vec![]; + } + + // 1. We explore the k_max area around `to`, and only remember backward + // links of the smallest distance. + + // Stores for each node its distance to `to` node and its backward links. + // By default all nodes are out of range (`k_max + 1`) and links are known. + let mut graph: Vec<(u32, Vec<NodeId>)> = (0..wot.size()) + .into_iter() + .map(|_| (k_max + 1, vec![])) + .collect(); + // `to` node is at distance 0, and have no backward links. + graph[to.0] = (0, vec![]); + // Explored zone border. + let mut border = HashSet::new(); + border.insert(to); + + for distance in 1..(k_max + 1) { + let mut next_border = HashSet::new(); + + for node in border { + for source in &wot.get_links_source(node).unwrap() { + if graph[source.0].0 > distance { + // shorter path, we replace + graph[source.0] = (distance, vec![node]); + next_border.insert(*source); + } else if graph[source.0].0 == distance { + // same length, we combine + graph[source.0].1.push(node); + next_border.insert(*source); + } + } + } + + border = next_border; + } + + // 2. If `from` is found, we follow the backward links and build paths. + // For each path, we look at the last element sources and build new paths with them. + let mut paths = vec![vec![from]]; + + for _ in 1..(k_max + 1) { + let mut new_paths = vec![]; + + for path in &paths { + let node = path.last().unwrap(); + + if node == &to { + // If path is complete, we keep it. + new_paths.push(path.clone()) + } else { + // If not complete we comlete paths + let sources = &graph[node.0]; + for source in &sources.1 { + let mut new_path = path.clone(); + new_path.push(*source); + new_paths.push(new_path); + } + } + } + + paths = new_paths; + } + + paths + } +}