Skip to content
Snippets Groups Projects
Commit 94ce417a authored by nanocryk's avatar nanocryk
Browse files

Merge branch...

Merge branch '43-refractor-wotb-to-have-separate-traits-for-wot-memory-layout-and-wot-calculations' into 'dev'

Resolve "Refractor wotb to have separate traits for WoT memory layout and WoT calculations."

Closes #43

See merge request !32
parents 93d4012d cdcfbf11
No related branches found
No related tags found
1 merge request!32Resolve "Refractor wotb to have separate traits for WoT memory layout and WoT calculations."
......@@ -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)",
......
[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"
......
......@@ -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,
);
}
}
}
lookup_step
impl WebOfTrust for LegacyWebOfTrust {
fn new(max_cert: usize) -> LegacyWebOfTrust {
LegacyWebOfTrust {
nodes: vec![],
max_cert,
}
}
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>();
}
}
// 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>;
}
......@@ -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>();
}
}
......@@ -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,
#![deny(missing_docs, missing_debug_implementations, missing_copy_implementations, trivial_casts,
trivial_numeric_casts, unsafe_code, unstable_features, unused_import_braces,
unused_qualifications
)]
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>;
/// 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>>;
pub mod data;
pub mod operations;
/// 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 {
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 {
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 {
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!(
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!(
wot.is_outdistanced(WotDistanceParameters {
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 {
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 {
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 {
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!(
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!(
wot.is_outdistanced(WotDistanceParameters {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
......
// 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)
}
}
// 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(())
}
}
// 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;
// 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
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment