diff --git a/network/Cargo.toml b/network/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..15c8ac50e1bf844ad486dceb5bd93c463007c041 --- /dev/null +++ b/network/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "duniter-network" +version = "0.1.0" +authors = ["librelois <elois@duniter.org>"] +description = "network model for the Duniter project." +license = "AGPL-3.0" + +[lib] +path = "lib.rs" + +[dependencies] +duniter-crypto = { path = "../crypto" } +duniter-documents = { path = "../documents" } +duniter-module = { path = "../module" } +lazy_static = "1.0.0" +regex = "0.2.6" +rust-crypto = "0.2.36" +serde = "1.0.24" +serde_derive = "1.0.24" +serde_json = "1.0.9" \ No newline at end of file diff --git a/network/lib.rs b/network/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..e20366615812c1322992196a843c3e1ac6461d62 --- /dev/null +++ b/network/lib.rs @@ -0,0 +1,338 @@ +// Copyright (C) 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/>. + +//! Defined all aspects of the inter-node network that concern all modules and are therefore independent of one implementation or another of this network layer. + +#![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 +)] + +#[macro_use] +extern crate lazy_static; + +extern crate crypto; +extern crate duniter_crypto; +extern crate duniter_documents; +extern crate duniter_module; +extern crate serde; +extern crate serde_json; + +pub mod network_endpoint; +pub mod network_head; +pub mod network_peer; + +use self::network_head::NetworkHead; +use self::network_peer::NetworkPeer; +use crypto::digest::Digest; +use crypto::sha2::Sha256; +use duniter_crypto::keys::{ed25519, PublicKey}; +use duniter_documents::blockchain::v10::documents::{ + BlockDocument, CertificationDocument, IdentityDocument, MembershipDocument, RevocationDocument, + TransactionDocument, +}; +use duniter_documents::blockchain::Document; +use duniter_documents::{BlockHash, BlockId, Blockstamp, Hash}; +use duniter_module::{ModuleReqFullId, ModuleReqId}; +use std::fmt::{Debug, Display, Error, Formatter}; +use std::ops::Deref; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +/// Random identifier with which several Duniter nodes with the same network keypair can be differentiated +pub struct NodeUUID(pub u32); + +impl Default for NodeUUID { + fn default() -> NodeUUID { + NodeUUID(0) + } +} + +impl Display for NodeUUID { + fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { + write!(f, "{:x}", self.0) + } +} + +impl<'a> From<&'a str> for NodeUUID { + fn from(source: &'a str) -> NodeUUID { + NodeUUID(u32::from_str_radix(source, 16).expect("Fail to parse NodeUUID")) + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +/// Complete identifier of a duniter node. +pub struct NodeFullId(pub NodeUUID, pub ed25519::PublicKey); + +impl Default for NodeFullId { + fn default() -> NodeFullId { + NodeFullId( + NodeUUID::default(), + PublicKey::from_base58("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA").unwrap(), + ) + } +} + +impl Display for NodeFullId { + fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { + write!(f, "{}-{}", self.0, self.1) + } +} + +impl NodeFullId { + /// Compute sha256 hash + pub fn sha256(&self) -> Hash { + let mut sha256 = Sha256::new(); + sha256.input_str(&format!("{}", self)); + Hash::from_hex(&sha256.result_str()).unwrap() + } +} + +/// Trait to be implemented by the configuration object of the module managing the inter-node network. +pub trait NetworkConf: Debug + Copy + Clone + PartialEq {} + +#[derive(Debug, Clone)] +/// Block v10 in network format (Some events require a blockchain access to reconstitute the corresponding document) +pub struct NetworkBlockV10 { + /// Uncompleted block document + pub uncompleted_block_doc: BlockDocument, + /// Joiners + pub joiners: Vec<serde_json::Value>, + /// actives + pub actives: Vec<serde_json::Value>, + /// leavers + pub leavers: Vec<serde_json::Value>, + /// revoked + pub revoked: Vec<serde_json::Value>, + /// certifications + pub certifications: Vec<serde_json::Value>, +} + +#[derive(Debug, Clone)] +/// Block in network format (Some events require a blockchain access to reconstitute the corresponding document) +pub enum NetworkBlock { + /// Block V10 + V10(Box<NetworkBlockV10>), + /// Block V11 + V11(), +} + +impl NetworkBlock { + /// Return blockstamp + pub fn blockstamp(&self) -> Blockstamp { + match self { + &NetworkBlock::V10(ref network_block_v10) => { + network_block_v10.deref().uncompleted_block_doc.blockstamp() + } + _ => panic!("Block version not supported !"), + } + } + /// Return previous blockstamp + pub fn previous_blockstamp(&self) -> Blockstamp { + match self { + &NetworkBlock::V10(ref network_block_v10) => Blockstamp { + id: BlockId(network_block_v10.deref().uncompleted_block_doc.number.0 - 1), + hash: BlockHash( + network_block_v10 + .deref() + .uncompleted_block_doc + .previous_hash, + ), + }, + _ => panic!("Block version not supported !"), + } + } +} + +#[derive(Debug, Clone)] +/// Network Document +pub enum NetworkDocument { + /// Network Block + Block(NetworkBlock), + /// Identity Document + Identity(IdentityDocument), + /// Membership Document + Membership(MembershipDocument), + /// Certification Document + Certification(CertificationDocument), + /// Revocation Document + Revocation(RevocationDocument), + /// Transaction Document + Transaction(TransactionDocument), +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +/// Type returned when the network module fails to determine the current network consensus +pub enum NetworkConsensusError { + /// The network module does not have enough data to determine consensus + InsufficientData(usize), + /// The network module does not determine consensus, there is most likely a fork + Fork(), +} + +#[derive(Debug, Copy, Clone)] +/// Type containing a request addressed to the network module +pub enum NetworkRequest { + /// Get a current block of a specific node + GetCurrent(ModuleReqFullId, NodeFullId), + //GetBlock(ModuleReqFullId, NodeFullId, u64), + /// Get a blocks chunk from specified node + GetBlocks(ModuleReqFullId, NodeFullId, u32, u32), + /// Get pending wot documents from specified node + GetRequirementsPending(ModuleReqFullId, NodeFullId, u32), + /// Obtain the current network consensus + GetConsensus(ModuleReqFullId), + /// Getting the heads cache + GetHeadsCache(ModuleReqFullId), + /// Get a list of known endpoints + GetEndpoints(ModuleReqFullId), +} + +impl NetworkRequest { + /// Get request full identitifier + pub fn get_req_full_id(&self) -> ModuleReqFullId { + match *self { + NetworkRequest::GetCurrent(ref req_id, _) + | NetworkRequest::GetBlocks(ref req_id, _, _, _) + | NetworkRequest::GetRequirementsPending(ref req_id, _, _) + | NetworkRequest::GetConsensus(ref req_id) + | NetworkRequest::GetHeadsCache(ref req_id) + | NetworkRequest::GetEndpoints(ref req_id) => req_id.clone(), + } + } + /// Get request identitifier + pub fn get_req_id(&self) -> ModuleReqId { + self.get_req_full_id().1 + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +/// Type returned when the network module does not get a satisfying answer to a request +pub enum NetworkRequestError { + /// Receiving an invalid format response + WrongFormat(), + /// Unknow error + UnknowError(), + /// No response received + NoResponse(), + /// Unable to reach the target node + ReceiverUnreachable(), +} + +#[derive(Debug, Clone)] +/// Type containing the response to a network request +pub enum NetworkResponse { + /// CurrentBlock + CurrentBlock(ModuleReqFullId, NodeFullId, Box<NetworkBlock>), + /// Block + Block(ModuleReqFullId, NodeFullId, Box<NetworkBlock>), + /// Chunk + Chunk(ModuleReqFullId, NodeFullId, Vec<Box<NetworkBlock>>), + /// PendingDocuments + PendingDocuments(ModuleReqFullId, Vec<NetworkDocument>), + /// Consensus + Consensus(ModuleReqFullId, Result<Blockstamp, NetworkConsensusError>), + /// HeadsCache + HeadsCache(ModuleReqFullId, Box<NetworkHead>), +} + +impl NetworkResponse { + /// Get request full identifier + pub fn get_req_full_id(&self) -> ModuleReqFullId { + match *self { + NetworkResponse::CurrentBlock(ref req_id, _, _) + | NetworkResponse::Block(ref req_id, _, _) + | NetworkResponse::Chunk(ref req_id, _, _) + | NetworkResponse::PendingDocuments(ref req_id, _) + | NetworkResponse::Consensus(ref req_id, _) + | NetworkResponse::HeadsCache(ref req_id, _) => req_id.clone(), + } + } + /// Get request identifier + pub fn get_req_id(&self) -> ModuleReqId { + self.get_req_full_id().1 + } +} + +#[derive(Debug, Clone)] +/// Type containing a network event, each time a network event occurs it's relayed to all modules +pub enum NetworkEvent { + /// Receiving a response to a network request + ReqResponse(Box<NetworkResponse>), + /// A connection has changed state(`u32` is the new state, `Option<String>` est l'uid du noeud) + ConnectionStateChange(NodeFullId, u32, Option<String>), + /// Receiving Pending Documents + ReceiveDocuments(Vec<NetworkDocument>), + /// Receipt of peer cards + ReceivePeers(Vec<NetworkPeer>), + /// Receiving heads + ReceiveHeads(Vec<NetworkHead>), +} + +#[cfg(test)] +mod tests { + + use super::network_endpoint::*; + use super::*; + + #[test] + fn parse_endpoint() { + let issuer = + PublicKey::from_base58("D9D2zaJoWYWveii1JRYLVK3J4Z7ZH3QczoKrnQeiM6mx").unwrap(); + let node_id = NodeUUID(u32::from_str_radix("c1c39a0a", 16).unwrap()); + let full_id = NodeFullId(node_id, issuer); + assert_eq!( + NetworkEndpoint::parse_from_raw("WS2P c1c39a0a i3.ifee.fr 80 /ws2p", issuer, 0, 0), + Some(NetworkEndpoint::V1(NetworkEndpointV1 { + version: 1, + issuer, + api: NetworkEndpointApi(String::from("WS2P")), + node_id: Some(node_id), + hash_full_id: Some(full_id.sha256()), + host: String::from("i3.ifee.fr"), + port: 80, + path: Some(String::from("ws2p")), + raw_endpoint: String::from("WS2P c1c39a0a i3.ifee.fr 80 /ws2p"), + last_check: 0, + status: 0, + },)) + ); + } + + #[test] + fn parse_endpoint2() { + let issuer = + PublicKey::from_base58("5gJYnQp8v7bWwk7EWRoL8vCLof1r3y9c6VDdnGSM1GLv").unwrap(); + let node_id = NodeUUID(u32::from_str_radix("cb06a19b", 16).unwrap()); + let full_id = NodeFullId(node_id, issuer); + assert_eq!( + NetworkEndpoint::parse_from_raw("WS2P cb06a19b g1.imirhil.fr 53012 /", issuer, 0, 0), + Some(NetworkEndpoint::V1(NetworkEndpointV1 { + version: 1, + issuer, + api: NetworkEndpointApi(String::from("WS2P")), + node_id: Some(node_id), + hash_full_id: Some(full_id.sha256()), + host: String::from("g1.imirhil.fr"), + port: 53012, + path: None, + raw_endpoint: String::from("WS2P cb06a19b g1.imirhil.fr 53012 /"), + last_check: 0, + status: 0, + },)) + ); + } +} diff --git a/network/network_endpoint.rs b/network/network_endpoint.rs new file mode 100644 index 0000000000000000000000000000000000000000..554148207e109966750ca6ba2fe4bff53593d209 --- /dev/null +++ b/network/network_endpoint.rs @@ -0,0 +1,213 @@ +// Copyright (C) 2017 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/>. + +//! Module defining the format of network endpoints and how to handle them. + +extern crate crypto; +extern crate duniter_crypto; +extern crate duniter_documents; +extern crate duniter_module; +extern crate regex; +extern crate serde; +extern crate serde_json; + +use self::regex::Regex; +use super::{NodeFullId, NodeUUID}; +use duniter_crypto::keys::ed25519; +use duniter_documents::Hash; + +lazy_static! { + #[derive(Debug)] + /// Regex match all endpoint in V1 format (works for all api) + pub static ref ENDPOINT_V1_REGEX: Regex = Regex::new( + r"^(?P<api>[A-Z0-9_]+) (?P<version>[1-9][0-9]*)? ?(?P<uuid>[a-f0-9]{6,8})? ?(?P<host>[a-z_][a-z0-9-_.]*|[0-9.]+|[0-9a-f:]+) (?P<port>[0-9]+)(?: /?(?P<path>.+)?)? *$" + ).unwrap(); +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +/// Identifies the API of an endpoint +pub struct NetworkEndpointApi(pub String); + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +/// Endpoint v1 +pub struct NetworkEndpointV1 { + /// API version + pub version: usize, + /// API Name + pub api: NetworkEndpointApi, + /// Node unique identifier + pub node_id: Option<NodeUUID>, + /// Public key of the node declaring this endpoint + pub issuer: ed25519::PublicKey, + /// NodeFullID hash + pub hash_full_id: Option<Hash>, + /// hostname + pub host: String, + /// port number + pub port: usize, + /// Optional path + pub path: Option<String>, + /// Endpoint in raw format (as it appears on the peer card) + pub raw_endpoint: String, + /// Accessibility status of this endpoint (updated regularly) + pub status: u32, + /// Timestamp of the last connection attempt to this endpoint + pub last_check: u64, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +/// Endpoint +pub enum NetworkEndpoint { + /// Endpoint v1 + V1(NetworkEndpointV1), + /// Endpoint v2 + V2(), +} + +impl ToString for NetworkEndpoint { + fn to_string(&self) -> String { + match *self { + NetworkEndpoint::V1(ref ep) => ep.raw_endpoint.clone(), + _ => panic!("Endpoint version is not supported !"), + } + } +} + +impl NetworkEndpoint { + /// Accessors providing API name + pub fn api(&self) -> NetworkEndpointApi { + match *self { + NetworkEndpoint::V1(ref ep) => ep.api.clone(), + _ => panic!("Endpoint version is not supported !"), + } + } + /// Accessors providing node unique identifier + pub fn node_uuid(&self) -> Option<NodeUUID> { + match *self { + NetworkEndpoint::V1(ref ep) => ep.node_id, + _ => panic!("Endpoint version is not supported !"), + } + } + /// Accessors providing node public key + pub fn pubkey(&self) -> ed25519::PublicKey { + match *self { + NetworkEndpoint::V1(ref ep) => ep.issuer, + _ => panic!("Endpoint version is not supported !"), + } + } + /// Accessors providing node full identifier + pub fn node_full_id(&self) -> Option<NodeFullId> { + match self.node_uuid() { + Some(node_id) => Some(NodeFullId(node_id, self.pubkey())), + None => None, + } + } + /// Accessors providing port number + pub fn port(&self) -> usize { + match *self { + NetworkEndpoint::V1(ref ep) => ep.port, + _ => panic!("Endpoint version is not supported !"), + } + } + /// Accessors providing raw format + pub fn raw(&self) -> String { + match *self { + NetworkEndpoint::V1(ref ep) => ep.raw_endpoint.clone(), + _ => panic!("Endpoint version is not supported !"), + } + } + /// Accessors providing endpoint accessibility status + pub fn status(&self) -> u32 { + match *self { + NetworkEndpoint::V1(ref ep) => ep.status, + _ => panic!("Endpoint version is not supported !"), + } + } + /// Set status + pub fn set_status(&mut self, new_status: u32) { + match *self { + NetworkEndpoint::V1(ref mut ep) => ep.status = new_status, + _ => panic!("Endpoint version is not supported !"), + } + } + /// Set last_check + pub fn set_last_check(&mut self, new_last_check: u64) { + match *self { + NetworkEndpoint::V1(ref mut ep) => ep.last_check = new_last_check, + _ => panic!("Endpoint version is not supported !"), + } + } + /// Generate endpoint url + pub fn get_url(&self) -> String { + match *self { + NetworkEndpoint::V1(ref ep) => { + let protocol = match &ep.api.0[..] { + "WS2P" | "WS2PTOR" => "ws", + _ => "http", + }; + let tls = match ep.port { + 443 => "s", + _ => "", + }; + let path = match ep.path { + Some(ref path_string) => path_string.clone(), + None => String::new(), + }; + format!("{}{}://{}:{}/{}", protocol, tls, ep.host, ep.port, path) + } + _ => panic!("Endpoint version is not supported !"), + } + } + /// Parse Endpoint from rax format + pub fn parse_from_raw( + raw_endpoint: &str, + issuer: ed25519::PublicKey, + status: u32, + last_check: u64, + ) -> Option<NetworkEndpoint> { + match ENDPOINT_V1_REGEX.captures(raw_endpoint) { + Some(caps) => { + let node_id = match caps.name("uuid") { + Some(caps_node_id) => match u32::from_str_radix(caps_node_id.as_str(), 16) { + Ok(node_id) => Some(NodeUUID(node_id)), + Err(_) => None, + }, + None => None, + }; + let hash_full_id = match node_id { + Some(node_id_) => Some(NodeFullId(node_id_, issuer).sha256()), + None => None, + }; + Some(NetworkEndpoint::V1(NetworkEndpointV1 { + version: 1, + issuer, + api: NetworkEndpointApi(String::from(&caps["api"])), + node_id, + hash_full_id, + host: String::from(&caps["host"]), + port: caps["port"].parse().unwrap_or(80), + path: match caps.name("path") { + Some(m) => Some(m.as_str().to_string()), + None => None, + }, + raw_endpoint: String::from(raw_endpoint), + status, + last_check, + })) + } + None => None, + } + } +} diff --git a/network/network_head.rs b/network/network_head.rs new file mode 100644 index 0000000000000000000000000000000000000000..8559dbf3734e29f405cbb19645c422b244d62aa3 --- /dev/null +++ b/network/network_head.rs @@ -0,0 +1,429 @@ +// Copyright (C) 2017 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/>. + +//! Module defining the format of network heads and how to handle them. + +extern crate duniter_crypto; +extern crate duniter_documents; +extern crate serde_json; + +use super::{NodeFullId, NodeUUID}; +use duniter_crypto::keys::{ed25519, PublicKey, Signature}; +use duniter_documents::Blockstamp; +use std::cmp::Ordering; +use std::collections::HashMap; +use std::ops::Deref; + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +/// Head Message V2 +pub struct NetworkHeadMessageV2 { + /// API details + pub api: String, + /// Head version + pub version: usize, + /// Head pubkey + pub pubkey: ed25519::PublicKey, + /// Head blockstamp + pub blockstamp: Blockstamp, + /// Head node id + pub node_uuid: NodeUUID, + /// Issuer node software + pub software: String, + /// Issuer node soft version + pub soft_version: String, + /// Issuer node prefix + pub prefix: usize, + /// Issuer node free member room + pub free_member_room: Option<usize>, + /// Issuer node free mirror room + pub free_mirror_room: Option<usize>, +} + +impl PartialOrd for NetworkHeadMessageV2 { + fn partial_cmp(&self, other: &NetworkHeadMessageV2) -> Option<Ordering> { + Some(self.cmp(other)) + } +} + +impl Ord for NetworkHeadMessageV2 { + fn cmp(&self, other: &NetworkHeadMessageV2) -> Ordering { + self.blockstamp.cmp(&other.blockstamp) + } +} + +impl NetworkHeadMessageV2 { + /// To human readable string + pub fn to_human_string(&self, max_len: usize, uid: Option<String>) -> String { + let short_api = &self.api[4..]; + + if max_len > 80 && uid.is_some() { + format!( + "{node_id:8}-{pubkey:.8} {blockstamp:.16} {soft:7}:{ver:7} {pre:3} [{api:5}] {mer:02}:{mir:02} {uid}", + node_id = self.node_uuid.to_string(), + pubkey = self.pubkey.to_string(), + blockstamp = self.blockstamp.to_string(), + soft = self.software, + ver = self.soft_version, + pre = self.prefix, + api = short_api, + mer = self.free_member_room.unwrap_or(0), + mir = self.free_mirror_room.unwrap_or(0), + uid = uid.unwrap(), + ) + } else if max_len > 67 { + format!( + "{node_id:8}-{pubkey:.8} {blockstamp:.16} {soft:7}:{ver:7} {pre:3} [{api:5}] {mer:02}:{mir:02}", + node_id = self.node_uuid.to_string(), + pubkey = self.pubkey.to_string(), + blockstamp = self.blockstamp.to_string(), + soft = self.software, + ver = self.soft_version, + pre = self.prefix, + api = short_api, + mer = self.free_member_room.unwrap_or(0), + mir = self.free_mirror_room.unwrap_or(0), + ) + } else if max_len > 63 { + format!( + "{node_id:8}-{pubkey:.8} {blockstamp:.16} {soft:7}:{ver:7} [{api:5}] {mer:02}:{mir:02}", + node_id = self.node_uuid.to_string(), + pubkey = self.pubkey.to_string(), + blockstamp = self.blockstamp.to_string(), + soft = self.software, + ver = self.soft_version, + api = short_api, + mer = self.free_member_room.unwrap_or(0), + mir = self.free_mirror_room.unwrap_or(0), + ) + } else if max_len > 47 { + format!( + "{node_id:8}-{pubkey:.8} {blockstamp:.16} [{api:5}] {mer:02}:{mir:02}", + node_id = self.node_uuid.to_string(), + pubkey = self.pubkey.to_string(), + blockstamp = self.blockstamp.to_string(), + api = short_api, + mer = self.free_member_room.unwrap_or(0), + mir = self.free_mirror_room.unwrap_or(0), + ) + } else if max_len > 41 { + format!( + "{node_id:8}-{pubkey:.8} {blockstamp:.16} [{api:5}]", + node_id = self.node_uuid.to_string(), + pubkey = self.pubkey.to_string(), + blockstamp = self.blockstamp.to_string(), + api = short_api, + ) + } else { + format!("Term width insufficient") + } + } +} + +#[derive(Debug, Clone, Eq, Ord, PartialOrd, PartialEq, Hash)] +/// Head Message +pub enum NetworkHeadMessage { + /// Head Message V2 + V2(NetworkHeadMessageV2), + /// Head Message V3 + V3(), +} + +impl NetworkHeadMessage { + /// To human readable string + pub fn to_human_string(&self, max_len: usize, uid: Option<String>) -> String { + match *self { + NetworkHeadMessage::V2(ref mess_v2) => mess_v2.deref().to_human_string(max_len, uid), + _ => panic!("NetworkHead version not supported !"), + } + } + /// Parse head from string + fn from_str(source: &str) -> Option<NetworkHeadMessage> { + let source_array: Vec<&str> = source.split(':').collect(); + if let Ok(pubkey) = PublicKey::from_base58(&source_array[3].to_string()) { + Some(NetworkHeadMessage::V2(NetworkHeadMessageV2 { + api: source_array[0].to_string(), + version: source_array[2].parse().unwrap(), + pubkey, + blockstamp: Blockstamp::from_string(source_array[4]).unwrap(), + node_uuid: NodeUUID(u32::from_str_radix(source_array[5], 16).unwrap()), + software: source_array[6].to_string(), + soft_version: source_array[7].to_string(), + prefix: source_array[8].parse().unwrap(), + free_member_room: if let Some(field) = source_array.get(9) { + Some(field.parse().unwrap()) + } else { + None + }, + free_mirror_room: if let Some(field) = source_array.get(10) { + Some(field.parse().unwrap()) + } else { + None + }, + })) + } else { + None + } + } + /// Get head blockcstamp + fn blockstamp(&self) -> Blockstamp { + match *self { + NetworkHeadMessage::V2(ref head_message_v2) => head_message_v2.blockstamp, + _ => panic!("This HEAD version is not supported !"), + } + } + /// Get head node id + fn node_uuid(&self) -> NodeUUID { + match *self { + NetworkHeadMessage::V2(ref head_message_v2) => head_message_v2.node_uuid, + _ => panic!("This HEAD version is not supported !"), + } + } + /// Get head issuer public key + fn _pubkey(&self) -> ed25519::PublicKey { + match *self { + NetworkHeadMessage::V2(ref head_message_v2) => head_message_v2.pubkey, + _ => panic!("This HEAD version is not supported !"), + } + } +} + +impl ToString for NetworkHeadMessageV2 { + fn to_string(&self) -> String { + match self.version { + 1 => format!( + "{}:HEAD:1:{}:{}:{}:{}:{}:{}", + self.api, + self.pubkey, + self.blockstamp, + self.node_uuid, + self.software, + self.soft_version, + self.prefix + ), + 2 => format!( + "{}:HEAD:2:{}:{}:{}:{}:{}:{}:{}:{}", + self.api, + self.pubkey, + self.blockstamp, + self.node_uuid, + self.software, + self.soft_version, + self.prefix, + self.free_member_room.unwrap(), + self.free_mirror_room.unwrap() + ), + _ => panic!("NetworkHeadMessage is wrongly parsed !"), + } + } +} + +impl ToString for NetworkHeadMessage { + fn to_string(&self) -> String { + match *self { + NetworkHeadMessage::V2(ref head_message) => head_message.to_string(), + _ => panic!("This HEADMessage version is not supported !"), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +/// Head V2 +pub struct NetworkHeadV2 { + /// Head V1 Message + pub message: NetworkHeadMessage, + /// signature of V1 Message + pub sig: ed25519::Signature, + /// Head V2 Message + pub message_v2: NetworkHeadMessage, + /// signature of V2 Message + pub sig_v2: ed25519::Signature, + /// Head step + pub step: u32, + /// Head issuer uid + pub uid: Option<String>, +} + +impl ToString for NetworkHeadV2 { + fn to_string(&self) -> String { + self.message_v2.to_string() + } +} + +impl PartialOrd for NetworkHeadV2 { + fn partial_cmp(&self, other: &NetworkHeadV2) -> Option<Ordering> { + Some(self.cmp(other)) + } +} + +impl Ord for NetworkHeadV2 { + fn cmp(&self, other: &NetworkHeadV2) -> Ordering { + self.message.cmp(&other.message) + } +} + +impl NetworkHeadV2 { + /// To human readable string + pub fn to_human_string(&self, max_len: usize) -> String { + if max_len > 2 { + format!( + "{} {}", + self.step, + self.message_v2 + .to_human_string(max_len - 2, self.uid.clone()) + ) + } else { + format!(".") + } + } + /// Get uid of head issuer + pub fn uid(&self) -> Option<String> { + self.uid.clone() + } +} + +#[derive(Debug, Clone, Eq, Ord, PartialOrd, PartialEq)] +/// Network Head : Set of information on the current state of a node, the central information being the blockstamp of its current block (= the head of its blockchain). +pub enum NetworkHead { + /// Head V2 + V2(Box<NetworkHeadV2>), + /// head V3 + V3(), +} + +impl ToString for NetworkHead { + fn to_string(&self) -> String { + match *self { + NetworkHead::V2(ref head_v2) => head_v2.deref().to_string(), + _ => panic!("NetworkHead version not supported !"), + } + } +} + +impl NetworkHead { + /// Get HEAD version + pub fn version(&self) -> u32 { + match *self { + NetworkHead::V2(_) => 2, + _ => panic!("This HEAD version is not supported !"), + } + } + /// Get HEAD blockstamp + pub fn blockstamp(&self) -> Blockstamp { + match *self { + NetworkHead::V2(ref head_v2) => head_v2.message_v2.blockstamp(), + _ => panic!("This HEAD version is not supported !"), + } + } + /// Get pubkey of head issuer + pub fn pubkey(&self) -> ed25519::PublicKey { + match *self { + NetworkHead::V2(ref head_v2) => match head_v2.message_v2 { + NetworkHeadMessage::V2(ref head_message_v2) => head_message_v2.pubkey, + _ => panic!("This HEAD message version is not supported !"), + }, + _ => panic!("This HEAD version is not supported !"), + } + } + /// Get uid of head issuer + pub fn uid(&self) -> Option<String> { + match *self { + NetworkHead::V2(ref head_v2) => head_v2.uid(), + _ => panic!("This HEAD version is not supported !"), + } + } + /// Change uid of head issuer + pub fn set_uid(&mut self, uid: &str) { + match *self { + NetworkHead::V2(ref mut head_v2) => head_v2.uid = Some(String::from(uid)), + _ => panic!("This HEAD version is not supported !"), + } + } + /// return the HEAD Step + pub fn step(&self) -> u32 { + match *self { + NetworkHead::V2(ref head_v2) => head_v2.step, + _ => panic!("This HEAD version is not supported !"), + } + } + /// Checks the validity of all head signatures + pub fn verify(&self) -> bool { + match *self { + NetworkHead::V2(ref head_v2) => { + let pubkey: ed25519::PublicKey = + PublicKey::from_base58(&self.pubkey().to_string()).unwrap(); + pubkey.verify(head_v2.message.to_string().as_bytes(), &head_v2.sig) + && pubkey.verify(head_v2.message_v2.to_string().as_bytes(), &head_v2.sig_v2) + } + _ => panic!("This HEAD version is not supported !"), + } + } + /// Returns issuer node id + pub fn node_uuid(&self) -> NodeUUID { + match *self { + NetworkHead::V2(ref head_v2) => head_v2.message_v2.node_uuid(), + _ => panic!("This HEAD version is not supported !"), + } + } + /// Returns issuer node full identifier + pub fn node_full_id(&self) -> NodeFullId { + NodeFullId(self.node_uuid(), self.pubkey()) + } + /// Returns true only if this head is to replace the old head of the same issuer in the head cache (or if it's the 1st head of this issuer) + pub fn apply(&self, heads_cache: &mut HashMap<NodeFullId, NetworkHead>) -> bool { + let heads_cache_copy = heads_cache.clone(); + if let Some(head) = heads_cache_copy.get(&self.node_full_id()) { + if self.blockstamp().id.0 > head.blockstamp().id.0 + || (self.blockstamp().id.0 == head.blockstamp().id.0 + && (self.version() >= head.version() || self.step() <= head.step())) + { + if let Some(head_mut) = heads_cache.get_mut(&self.node_full_id()) { + *head_mut = self.clone(); + true + } else { + false + } + } else { + false + } + } else { + heads_cache.insert(self.node_full_id(), self.clone()); + true + } + } + /// Parse Json Head + pub fn from_json_value(source: &serde_json::Value) -> Option<NetworkHead> { + let message = NetworkHeadMessage::from_str(source.get("message")?.as_str().unwrap())?; + match message { + NetworkHeadMessage::V2(_) => Some(NetworkHead::V2(Box::new(NetworkHeadV2 { + message, + sig: Signature::from_base64(source.get("sig")?.as_str().unwrap()).unwrap(), + message_v2: NetworkHeadMessage::from_str( + source.get("messageV2")?.as_str().unwrap(), + )?, + sig_v2: Signature::from_base64(source.get("sigV2")?.as_str().unwrap()).unwrap(), + step: source.get("step")?.as_u64().unwrap() as u32, + uid: None, + }))), + _ => None, + } + } + /// To human readable string + pub fn to_human_string(&self, max_len: usize) -> String { + match *self { + NetworkHead::V2(ref head_v2) => head_v2.deref().to_human_string(max_len), + _ => panic!("NetworkHead version not supported !"), + } + } +} diff --git a/network/network_peer.rs b/network/network_peer.rs new file mode 100644 index 0000000000000000000000000000000000000000..5efe5ed188f3e4e939ac08ffdc67eb681c3d97b7 --- /dev/null +++ b/network/network_peer.rs @@ -0,0 +1,79 @@ +// Copyright (C) 2017 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/>. + +//! Module defining the format of network peer cards and how to handle them. + +extern crate crypto; +extern crate duniter_crypto; +extern crate duniter_documents; +extern crate duniter_module; +extern crate serde; +extern crate serde_json; + +use super::network_endpoint::NetworkEndpoint; +use duniter_crypto::keys::ed25519; +use duniter_documents::Blockstamp; + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +/// Peer card V10 +pub struct NetworkPeerV10 { + /// Peer card Blockstamp + pub blockstamp: Blockstamp, + /// Peer card issuer + pub issuer: ed25519::PublicKey, + /// Peer card endpoints list + pub endpoints: Vec<NetworkEndpoint>, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +/// Peer card +pub enum NetworkPeer { + /// Peer card V10 + V10(NetworkPeerV10), + /// Peer card V11 + V11(), +} + +impl NetworkPeer { + /// Get peer card version + pub fn version(&self) -> u32 { + match *self { + NetworkPeer::V10(ref _peer_v10) => 10, + _ => panic!("Peer version is not supported !"), + } + } + /// Get peer card blockstamp + pub fn blockstamp(&self) -> Blockstamp { + match *self { + NetworkPeer::V10(ref peer_v10) => peer_v10.blockstamp, + _ => panic!("Peer version is not supported !"), + } + } + /// Get peer card issuer + pub fn issuer(&self) -> ed25519::PublicKey { + match *self { + NetworkPeer::V10(ref peer_v10) => peer_v10.issuer, + _ => panic!("Peer version is not supported !"), + } + } + /// Verify validity of peer card signature + pub fn verify(&self) -> bool { + false + } + /// Get peer card endpoint + pub fn get_endpoints(&self) -> Vec<NetworkEndpoint> { + Vec::with_capacity(0) + } +}