diff --git a/Cargo.lock b/Cargo.lock index 4f80ede3961f86585e0c01c6b96e6b14a95fa9fe..7462c888c69db1d76567ebebf2713874e07cf297 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -457,7 +457,6 @@ dependencies = [ "pest_derive 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "pretty_assertions 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.86 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.86 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.38 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -548,22 +547,23 @@ dependencies = [ name = "durs-ws2p-v1-legacy" version = "0.1.0-a0.1" dependencies = [ + "bincode 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.2.7 (registry+https://github.com/rust-lang/crates.io-index)", "dubp-documents 0.12.0", "duniter-conf 0.1.0-a0.1", "duniter-module 0.1.0-a0.1", "duniter-network 0.1.0-a0.1", "dup-crypto 0.6.0", + "durs-common-tools 0.1.0", "durs-message 0.1.0-a0.1", "durs-network-documents 0.3.1", "durs-wot 0.8.0-a0.9", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.86 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.86 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.38 (registry+https://github.com/rust-lang/crates.io-index)", - "sqlite 0.23.9 (registry+https://github.com/rust-lang/crates.io-index)", "structopt 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", + "unwrap 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "ws 0.7.9 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1145,6 +1145,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" name = "serde" version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "serde_derive 1.0.86 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "serde_derive" @@ -1210,33 +1213,6 @@ name = "snowflake" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "sqlite" -version = "0.23.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)", - "sqlite3-sys 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "sqlite3-src" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cc 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)", - "pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "sqlite3-sys" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)", - "sqlite3-src 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "strsim" version = "0.7.0" @@ -1377,6 +1353,11 @@ dependencies = [ "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "unwrap" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "url" version = "1.7.2" @@ -1571,9 +1552,6 @@ dependencies = [ "checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" "checksum smallvec 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)" = "88aea073965ab29f6edb5493faf96ad662fb18aa9eeb186a3b7057951605ed15" "checksum snowflake 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "27207bb65232eda1f588cf46db2fee75c0808d557f6b3cf19a75f5d6d7c94df1" -"checksum sqlite 0.23.9 (registry+https://github.com/rust-lang/crates.io-index)" = "d18d7b10278336e7fd9dc259399a0f9ff419616738b6a841b136c362e16db626" -"checksum sqlite3-src 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "62cd5d67c1eb94a2a019b8049f625f65ae7faee8f3c54f0197e5dbc68d5f698c" -"checksum sqlite3-sys 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "71fec807a1534bd13eeaaec396175d67c79bdc68df55e18a452726ec62a8fb08" "checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" "checksum structopt 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "670ad348dc73012fcf78c71f06f9d942232cdd4c859d4b6975e27836c3efc0c3" "checksum structopt-derive 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "ef98172b1a00b0bec738508d3726540edcbd186d50dfd326f2b1febbb3559f04" @@ -1592,6 +1570,7 @@ dependencies = [ "checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" "checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" +"checksum unwrap 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e33648dd74328e622c7be51f3b40a303c63f93e6fa5f08778b6203a4c25c20f" "checksum url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" "checksum vcpkg 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "def296d3eb3b12371b2c7d0e83bfe1403e4db2d7a0bba324a12b21c4ee13143d" "checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" diff --git a/lib/modules/ws2p-v1-legacy/Cargo.toml b/lib/modules/ws2p-v1-legacy/Cargo.toml index 30909adcbd8fd8a1b68575c73b674dc764638240..6b2f9788e9322cb7e04df870d65c7b29277a61ed 100644 --- a/lib/modules/ws2p-v1-legacy/Cargo.toml +++ b/lib/modules/ws2p-v1-legacy/Cargo.toml @@ -7,26 +7,27 @@ license = "AGPL-3.0" edition = "2018" [lib] -path = "lib.rs" +path = "src/lib.rs" [dependencies] +bincode = "1.0.1" byteorder = "1.2.3" +dubp-documents= { path = "../../tools/documents" } duniter-conf = { path = "../../core/conf" } +duniter-module = { path = "../../core/module" } +duniter-network = { path = "../../core/network" } dup-crypto = { path = "../../tools/crypto" } -dubp-documents= { path = "../../tools/documents" } durs-network-documents = { path = "../../tools/network-documents" } durs-message = { path = "../../core/message" } -duniter-module = { path = "../../core/module" } -duniter-network = { path = "../../core/network" } +durs-common-tools = { path = "../../tools/common-tools" } durs-wot = { path = "../../tools/wot" } log = "0.4.*" rand = "0.4.*" -sqlite = "0.23.*" -serde = "1.0.*" -serde_derive = "1.0.*" +serde = { version = "1.0.*", features = ["derive"] } serde_json = "1.0.*" structopt= "0.2.*" -ws = { version = "0.7.*", features = ["permessage-deflate"] } +unwrap = "1.2.1" +ws = "0.7.*" [features] ssl = ["ws/ssl"] \ No newline at end of file diff --git a/lib/modules/ws2p-v1-legacy/clippy.toml b/lib/modules/ws2p-v1-legacy/clippy.toml index 05bd01e10385174463601aedba2ae3ab0fbe921b..fbfe7d8acc90ca3b7b85b00e5f8111ce7f9ab078 100644 --- a/lib/modules/ws2p-v1-legacy/clippy.toml +++ b/lib/modules/ws2p-v1-legacy/clippy.toml @@ -1 +1 @@ -cyclomatic-complexity-threshold = 77 \ No newline at end of file +cyclomatic-complexity-threshold = 51 \ No newline at end of file diff --git a/lib/modules/ws2p-v1-legacy/constants.rs b/lib/modules/ws2p-v1-legacy/constants.rs deleted file mode 100644 index aaada76d3146c71a060bf8edea98979815bcad7d..0000000000000000000000000000000000000000 --- a/lib/modules/ws2p-v1-legacy/constants.rs +++ /dev/null @@ -1,12 +0,0 @@ -pub static WS2P_OUTCOMING_INTERVAL_AT_STARTUP: &'static u64 = &75; -pub static WS2P_OUTCOMING_INTERVAL: &'static u64 = &300; -pub static WS2P_DEFAULT_OUTCOMING_QUOTA: &'static usize = &10; -pub static WS2P_NEGOTIATION_TIMEOUT: &'static u64 = &15; -//pub static WS2P_REQUEST_TIMEOUT : &'static u64 = &30; -pub static WS2P_EXPIRE_TIMEOUT: &'static u64 = &120; -pub static WS2P_SPAM_INTERVAL_IN_MILLI_SECS: &'static u64 = &80; -pub static WS2P_SPAM_LIMIT: &'static usize = &6; -pub static WS2P_SPAM_SLEEP_TIME_IN_SEC: &'static u64 = &100; -pub static DURATION_BEFORE_RECORDING_ENDPOINT: &'static u64 = &180; -pub static BLOCKS_REQUEST_INTERVAL: &'static u64 = &60; -pub static PENDING_IDENTITIES_REQUEST_INTERVAL: &'static u64 = &40; diff --git a/lib/modules/ws2p-v1-legacy/datas.rs b/lib/modules/ws2p-v1-legacy/datas.rs deleted file mode 100644 index 89968a51f3c43f48a1a1350b08881efa54a3481d..0000000000000000000000000000000000000000 --- a/lib/modules/ws2p-v1-legacy/datas.rs +++ /dev/null @@ -1,491 +0,0 @@ -// 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/>. - -use crate::*; -use dup_crypto::keys::*; -use durs_message::requests::BlockchainRequest; -use durs_message::*; -use durs_network_documents::network_endpoint::*; -use durs_network_documents::network_head::*; -use durs_network_documents::*; -use std::collections::HashSet; -use std::sync::mpsc; - -#[derive(Debug)] -pub struct WS2PModuleDatas { - pub router_sender: mpsc::Sender<RouterThreadMessage<DursMsg>>, - pub currency: Option<String>, - pub key_pair: Option<KeyPairEnum>, - pub conf: WS2PConf, - pub ssl: bool, - pub node_id: NodeId, - pub main_thread_channel: ( - mpsc::Sender<WS2PThreadSignal>, - mpsc::Receiver<WS2PThreadSignal>, - ), - pub ws2p_endpoints: HashMap<NodeFullId, (EndpointV1, WS2PConnectionState)>, - pub websockets: HashMap<NodeFullId, WsSender>, - pub requests_awaiting_response: - HashMap<ModuleReqId, (OldNetworkRequest, NodeFullId, SystemTime)>, - pub heads_cache: HashMap<NodeFullId, NetworkHead>, - pub my_head: Option<NetworkHead>, - pub uids_cache: HashMap<PubKey, String>, - pub count_dal_requests: u32, -} - -#[inline] -#[cfg(not(feature = "ssl"))] -fn ssl() -> bool { - false -} -#[cfg(feature = "ssl")] -fn ssl() -> bool { - true -} - -impl WS2PModuleDatas { - pub fn new( - router_sender: mpsc::Sender<RouterThreadMessage<DursMsg>>, - conf: WS2PConf, - node_id: NodeId, - ) -> WS2PModuleDatas { - WS2PModuleDatas { - router_sender, - key_pair: None, - currency: None, - conf, - ssl: ssl(), - node_id, - main_thread_channel: mpsc::channel(), - ws2p_endpoints: HashMap::new(), - websockets: HashMap::new(), - requests_awaiting_response: HashMap::new(), - heads_cache: HashMap::new(), - my_head: None, - uids_cache: HashMap::new(), - count_dal_requests: 0, - } - } - pub fn open_db(db_path: &PathBuf) -> Result<sqlite::Connection, sqlite::Error> { - let conn: sqlite::Connection; - if !db_path.as_path().exists() { - conn = sqlite::open(db_path.as_path())?; - conn.execute( - "CREATE TABLE endpoints (hash_full_id TEXT, status INTEGER, node_id INTEGER, pubkey TEXT, - api INTEGER, version INTEGER, endpoint TEXT, last_check INTEGER);", - )?; - } else { - conn = sqlite::open(db_path.as_path())?; - } - Ok(conn) - } - pub fn send_dal_request(&mut self, req: &BlockchainRequest) { - self.count_dal_requests += 1; - if self.count_dal_requests == std::u32::MAX { - self.count_dal_requests = 0; - } - self.router_sender - .send(RouterThreadMessage::ModuleMessage(DursMsg::Request { - req_from: WS2PModule::name(), - req_to: ModuleRole::BlockchainDatas, - req_id: ModuleReqId(self.count_dal_requests), - req_content: DursReqContent::BlockchainRequest(req.clone()), - })) - .expect("Fail to send message to router !"); - } - pub fn send_network_req_response( - &self, - requester: ModuleStaticName, - req_id: ModuleReqId, - response: NetworkResponse, - ) { - self.router_sender - .send(RouterThreadMessage::ModuleMessage(DursMsg::Response { - res_from: WS2PModule::name(), - res_to: requester, - req_id, - res_content: DursResContent::NetworkResponse(response), - })) - .expect("Fail to send message to router !"); - } - pub fn send_network_event(&self, event: &NetworkEvent) { - let module_event = match event { - NetworkEvent::ConnectionStateChange(_, _, _, _) => { - ModuleEvent::ConnectionsChangeNodeNetwork - } - NetworkEvent::ReceiveBlocks(_) => ModuleEvent::NewBlockFromNetwork, - NetworkEvent::ReceiveDocuments(network_docs) => { - if !network_docs.is_empty() { - match network_docs[0] { - BlockchainDocument::Block(_) => ModuleEvent::NewBlockFromNetwork, - BlockchainDocument::Transaction(_) => ModuleEvent::NewTxFromNetwork, - _ => ModuleEvent::NewWotDocFromNetwork, - } - } else { - return; - } - } - NetworkEvent::ReceiveHeads(_) => ModuleEvent::NewValidHeadFromNetwork, - NetworkEvent::ReceivePeers(_) => ModuleEvent::NewValidPeerFromNodeNetwork, - NetworkEvent::NewSelfPeer(_) => ModuleEvent::NewSelfPeer, - }; - self.router_sender - .send(RouterThreadMessage::ModuleMessage(DursMsg::Event { - event_type: module_event, - event_content: DursEvent::NetworkEvent(event.clone()), - })) - .expect("Fail to send network event to router !"); - } - fn count_established_connections(&self) -> usize { - let mut count_established_connections = 0; - for (_ws2p_full_id, (_ep, state)) in self.ws2p_endpoints.clone() { - if let WS2PConnectionState::Established = state { - count_established_connections += 1; - } - } - count_established_connections - } - pub fn connect_to_know_endpoints(&mut self) { - info!("WS2P: connect to know endpoints..."); - let mut count_established_connections = 0; - let mut pubkeys = HashSet::new(); - let mut reachable_endpoints = Vec::new(); - let mut unreachable_endpoints = Vec::new(); - for (_ws2p_full_id, (ep, state)) in self.ws2p_endpoints.clone() { - if ep.issuer == self.key_pair.unwrap().public_key() || !pubkeys.contains(&ep.issuer) { - match state { - WS2PConnectionState::Established => count_established_connections += 1, - WS2PConnectionState::NeverTry - | WS2PConnectionState::Close - | WS2PConnectionState::Denial => { - pubkeys.insert(ep.issuer); - reachable_endpoints.push(ep); - } - _ => { - pubkeys.insert(ep.issuer); - unreachable_endpoints.push(ep); - } - } - } - } - let mut free_outcoming_rooms = - self.conf.clone().outcoming_quota - count_established_connections; - while free_outcoming_rooms > 0 { - let ep = if !reachable_endpoints.is_empty() { - reachable_endpoints - .pop() - .expect("WS2P: Fail to pop() reachable_endpoints !") - } else if !unreachable_endpoints.is_empty() { - unreachable_endpoints - .pop() - .expect("WS2P: Fail to pop() unreachable_endpoints !") - } else { - break; - }; - if !self.ssl && ep.port == 443 { - continue; - } - self.connect_to_without_checking_quotas(&ep); - free_outcoming_rooms -= 1; - } - } - pub fn connect_to(&mut self, endpoint: &EndpointV1) { - // Add endpoint to endpoints list (if there isn't already) - match self.ws2p_endpoints.get( - &endpoint - .node_full_id() - .expect("WS2P: Fail to get ep.node_full_id() !"), - ) { - Some(_) => { - self.ws2p_endpoints - .get_mut( - &endpoint - .node_full_id() - .expect("WS2P: Fail to get ep.node_full_id() !"), - ) - .expect("WS2P: Fail to get_mut() a ws2p_endpoint !") - .1 = WS2PConnectionState::NeverTry; - } - None => { - self.ws2p_endpoints.insert( - endpoint - .node_full_id() - .expect("WS2P: Fail to get ep.node_full_id() !"), - (endpoint.clone(), WS2PConnectionState::NeverTry), - ); - } - }; - if self.conf.clone().outcoming_quota > self.count_established_connections() { - self.connect_to_without_checking_quotas(&endpoint); - } - } - pub fn close_connection( - &mut self, - ws2p_full_id: &NodeFullId, - reason: WS2PCloseConnectionReason, - ) { - match reason { - WS2PCloseConnectionReason::NegociationTimeout => {} - WS2PCloseConnectionReason::AuthMessInvalidSig - | WS2PCloseConnectionReason::Timeout - | WS2PCloseConnectionReason::WsError - | WS2PCloseConnectionReason::Unknow => { - self.ws2p_endpoints - .get_mut(ws2p_full_id) - .expect("Failure : attempt to delete a non-existent connection !") - .1 = WS2PConnectionState::Close - } - } - if let Some(websocket) = self.websockets.get(&ws2p_full_id) { - let _result = websocket.0.close(ws::CloseCode::Normal); - } - let _result = self.websockets.remove(ws2p_full_id); - } - pub fn ws2p_conn_message_pretreatment(&mut self, message: WS2PConnectionMessage) -> WS2PSignal { - let ws2p_full_id = message.0; - match message.1 { - WS2PConnectionMessagePayload::WrongUrl - | WS2PConnectionMessagePayload::FailOpenWS - | WS2PConnectionMessagePayload::FailToSplitWS => { - self.ws2p_endpoints - .get_mut(&ws2p_full_id) - .expect("WS2P: Fail to get mut ep !") - .1 = WS2PConnectionState::WSError; - return WS2PSignal::WSError(ws2p_full_id); - } - WS2PConnectionMessagePayload::TryToSendConnectMess => { - self.ws2p_endpoints - .get_mut(&ws2p_full_id) - .expect("WS2P: Fail to get mut ep !") - .1 = WS2PConnectionState::TryToSendConnectMess; - } - WS2PConnectionMessagePayload::FailSendConnectMess => { - self.ws2p_endpoints - .get_mut(&ws2p_full_id) - .expect("WS2P: Fail to mut ep !") - .1 = WS2PConnectionState::Unreachable; - } - WS2PConnectionMessagePayload::WebsocketOk(sender) => { - self.websockets.insert(ws2p_full_id, sender); - } - WS2PConnectionMessagePayload::ValidConnectMessage(response, new_con_state) => { - self.ws2p_endpoints - .get_mut(&ws2p_full_id) - .expect("WS2P: Fail to get mut ep !") - .1 = new_con_state; - self.ws2p_endpoints - .get_mut(&ws2p_full_id) - .expect("Endpoint don't exist !") - .1 = WS2PConnectionState::ConnectMessOk; - debug!("Send: {:#?}", response); - if let Some(websocket) = self.websockets.get_mut(&ws2p_full_id) { - if websocket.0.send(Message::text(response)).is_err() { - return WS2PSignal::WSError(ws2p_full_id); - } - } else { - // Connection closed by remote peer - self.ws2p_endpoints - .get_mut(&ws2p_full_id) - .expect("Endpoint don't exist !") - .1 = WS2PConnectionState::Close; - } - } - WS2PConnectionMessagePayload::ValidAckMessage(response, new_con_state) => { - self.ws2p_endpoints - .get_mut(&ws2p_full_id) - .expect("WS2P: Fail to get mut ep !") - .1 = new_con_state; - if let WS2PConnectionState::AckMessOk = self.ws2p_endpoints[&ws2p_full_id].1 { - debug!("Send: {:#?}", response); - if let Some(websocket) = self.websockets.get_mut(&ws2p_full_id) { - if websocket.0.send(Message::text(response)).is_err() { - return WS2PSignal::WSError(ws2p_full_id); - } - } else { - panic!("Fatal error : no websocket for {} !", ws2p_full_id); - } - } - } - WS2PConnectionMessagePayload::ValidOk(new_con_state) => { - self.ws2p_endpoints - .get_mut(&ws2p_full_id) - .expect("WS2P: Fail to get mut ep !") - .1 = new_con_state; - match self.ws2p_endpoints[&ws2p_full_id].1 { - WS2PConnectionState::OkMessOkWaitingAckMess => {} - WS2PConnectionState::Established => { - return WS2PSignal::ConnectionEstablished(ws2p_full_id); - } - _ => { - self.close_connection(&ws2p_full_id, WS2PCloseConnectionReason::Unknow); - return WS2PSignal::Empty; - } - } - } - WS2PConnectionMessagePayload::DalRequest(req_id, req_body) => { - return WS2PSignal::DalRequest(ws2p_full_id, req_id, req_body); - } - WS2PConnectionMessagePayload::PeerCard(body, ws2p_endpoints) => { - return WS2PSignal::PeerCard(ws2p_full_id, body, ws2p_endpoints); - } - WS2PConnectionMessagePayload::Heads(heads) => { - let mut applied_heads = Vec::with_capacity(heads.len()); - for head in heads { - if let Ok(head) = NetworkHead::from_json_value(&head) { - if head.verify() - && (self.my_head.is_none() - || head.node_full_id() - != self - .my_head - .clone() - .expect("WS2P: Fail to clone my_head") - .node_full_id()) - && head.apply(&mut self.heads_cache) - { - applied_heads.push(head); - } - } - } - return WS2PSignal::Heads(ws2p_full_id, applied_heads); - } - WS2PConnectionMessagePayload::Document(network_doc) => { - return WS2PSignal::Document(ws2p_full_id, network_doc); - } - WS2PConnectionMessagePayload::ReqResponse(req_id, response) => { - if self.requests_awaiting_response.len() > req_id.0 as usize { - if let Some((ref ws2p_request, ref recipient_fulld_id, ref _timestamp)) = - self.requests_awaiting_response.remove(&req_id) - { - return WS2PSignal::ReqResponse( - req_id, - *ws2p_request, - *recipient_fulld_id, - response, - ); - } - } - } - WS2PConnectionMessagePayload::NegociationTimeout => { - match self.ws2p_endpoints[&ws2p_full_id].1 { - WS2PConnectionState::AckMessOk | WS2PConnectionState::ConnectMessOk => { - self.ws2p_endpoints - .get_mut(&ws2p_full_id) - .expect("WS2P: Fail to get mut ep !") - .1 = WS2PConnectionState::Denial - } - WS2PConnectionState::WaitingConnectMess => { - self.ws2p_endpoints - .get_mut(&ws2p_full_id) - .expect("WS2P: Fail to get mut ep !") - .1 = WS2PConnectionState::NoResponse - } - _ => { - self.ws2p_endpoints - .get_mut(&ws2p_full_id) - .expect("WS2P: Fail to get mut ep !") - .1 = WS2PConnectionState::Unreachable - } - } - self.close_connection(&ws2p_full_id, WS2PCloseConnectionReason::NegociationTimeout); - return WS2PSignal::NegociationTimeout(ws2p_full_id); - } - WS2PConnectionMessagePayload::Timeout => { - self.close_connection(&ws2p_full_id, WS2PCloseConnectionReason::Timeout); - return WS2PSignal::Timeout(ws2p_full_id); - } - WS2PConnectionMessagePayload::UnknowMessage => { - warn!("WS2P : Receive Unknow Message from {}.", &ws2p_full_id.1) - } - WS2PConnectionMessagePayload::WrongFormatMessage => warn!( - "WS2P : Receive Wrong Format Message from {}.", - &ws2p_full_id.1 - ), - WS2PConnectionMessagePayload::InvalidMessage => return WS2PSignal::Empty, - WS2PConnectionMessagePayload::Close => { - if self.websockets.contains_key(&ws2p_full_id) { - self.close_connection( - &ws2p_full_id, - WS2PCloseConnectionReason::AuthMessInvalidSig, - ) - } - } - } - let connections_count = self.websockets.len(); - if connections_count == 0 { - return WS2PSignal::NoConnection; - } - // Detect timeout requests - let mut requests_timeout = Vec::new(); - for &(ref req, ref ws2p_full_id, ref timestamp) in - self.requests_awaiting_response.clone().values() - { - if SystemTime::now().duration_since(*timestamp).unwrap() > Duration::new(20, 0) { - requests_timeout.push(req.get_req_full_id()); - warn!("request timeout : {:?} (sent to {:?})", req, ws2p_full_id); - } - } - // Delete (and resend) timeout requests - for req_id in requests_timeout { - //let ws2p_endpoints = self.ws2p_endpoints.clone(); - let _request_option = self.requests_awaiting_response.remove(&req_id.1); - /*if let Some((request, _, _)) = request_option { - let _request_result = self.send_request_to_specific_node( - &get_random_connection(&ws2p_endpoints), - &request, - ); - }*/ - } - WS2PSignal::Empty - } - - pub fn send_request_to_specific_node( - &mut self, - receiver_ws2p_full_id: &NodeFullId, - ws2p_request: &OldNetworkRequest, - ) -> ws::Result<()> { - self.websockets - .get_mut(receiver_ws2p_full_id) - .expect("WS2P: Fail to get mut websocket !") - .0 - .send(Message::text( - network_request_to_json(ws2p_request).to_string(), - ))?; - self.requests_awaiting_response.insert( - ws2p_request.get_req_id(), - (*ws2p_request, *receiver_ws2p_full_id, SystemTime::now()), - ); - debug!( - "send request {} to {}", - network_request_to_json(ws2p_request).to_string(), - receiver_ws2p_full_id - ); - Ok(()) - } - - fn connect_to_without_checking_quotas(&mut self, endpoint: &EndpointV1) { - let endpoint_copy = endpoint.clone(); - let conductor_sender_copy = self.main_thread_channel.0.clone(); - let currency_copy = self.currency.clone(); - let key_pair_copy = self.key_pair; - thread::spawn(move || { - let _result = connect_to_ws2p_endpoint( - &endpoint_copy, - &conductor_sender_copy, - ¤cy_copy.expect("WS2PError : No currency !"), - key_pair_copy.expect("WS2PError : No key_pair !"), - ); - }); - } -} diff --git a/lib/modules/ws2p-v1-legacy/lib.rs b/lib/modules/ws2p-v1-legacy/lib.rs deleted file mode 100644 index a3e7663e068d2b5ee8ab454e75133c49390b155f..0000000000000000000000000000000000000000 --- a/lib/modules/ws2p-v1-legacy/lib.rs +++ /dev/null @@ -1,1147 +0,0 @@ -// 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/>. - -//! WebSocketToPeer API for the Duniter project. - -#![deny( - missing_debug_implementations, - missing_copy_implementations, - trivial_casts, - unsafe_code, - unstable_features, - unused_import_braces, - unused_qualifications -)] -#![recursion_limit = "256"] - -#[macro_use] -extern crate log; -#[macro_use] -extern crate serde_derive; -#[macro_use] -extern crate serde_json; -#[macro_use] -extern crate structopt; - -mod ack_message; -mod connect_message; -pub mod constants; -mod datas; -mod heads; -mod ok_message; -pub mod parsers; -pub mod serializer; -pub mod ws2p_connection; -pub mod ws2p_db; -pub mod ws2p_requests; - -use crate::ack_message::WS2PAckMessageV1; -use crate::connect_message::WS2PConnectMessageV1; -use crate::constants::*; -use crate::datas::*; -use crate::ok_message::WS2POkMessageV1; -use crate::parsers::blocks::parse_json_block; -use crate::ws2p_connection::*; -use crate::ws2p_requests::network_request_to_json; -use dubp_documents::{Blockstamp, Document}; -use duniter_conf::DuRsConf; -use duniter_module::*; -use duniter_network::cli::sync::SyncOpt; -use duniter_network::documents::*; -use duniter_network::events::*; -use duniter_network::requests::*; -use duniter_network::*; -use dup_crypto::keys::*; -use durs_message::events::*; -use durs_message::requests::*; -use durs_message::responses::*; -use durs_message::*; -use durs_network_documents::network_endpoint::*; -use durs_network_documents::network_head::*; -use durs_network_documents::*; -use std::collections::HashMap; -use std::ops::Deref; -use std::path::PathBuf; -use std::sync::mpsc; -use std::thread; -use std::time::{Duration, SystemTime, UNIX_EPOCH}; -use ws::Message; - -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] -/// WS2P Configuration -pub struct WS2PConf { - /// Limit of outcoming connections - pub outcoming_quota: usize, - /// Default WS2P endpoints provides by configuration file - pub sync_endpoints: Vec<EndpointV1>, -} - -impl Default for WS2PConf { - fn default() -> Self { - WS2PConf { - outcoming_quota: *WS2P_DEFAULT_OUTCOMING_QUOTA, - sync_endpoints: vec![ - EndpointV1::parse_from_raw( - "WS2P c1c39a0a ts.g1.librelois.fr 443 /ws2p", - PubKey::Ed25519( - ed25519::PublicKey::from_base58( - "D9D2zaJoWYWveii1JRYLVK3J4Z7ZH3QczoKrnQeiM6mx", - ) - .unwrap(), - ), - 0, - 0, - ) - .unwrap(), - EndpointV1::parse_from_raw( - "WS2P fb17fcd4 g1.duniter.fr 443 /ws2p", - PubKey::Ed25519( - ed25519::PublicKey::from_base58( - "38MEAZN68Pz1DTvT3tqgxx4yQP6snJCQhPqEFxbDk4aE", - ) - .unwrap(), - ), - 0, - 0, - ) - .unwrap(), - EndpointV1::parse_from_raw( - "WS2P 7b33becd g1.nordstrom.duniter.org 443 /ws2p", - PubKey::Ed25519( - ed25519::PublicKey::from_base58( - "DWoSCRLQyQ48dLxUGr1MDKg4NFcbPbC56LN2hJjCCPpZ", - ) - .unwrap(), - ), - 0, - 0, - ) - .unwrap(), - EndpointV1::parse_from_raw( - "WS2P dff60418 duniter.normandie-libre.fr 443 /ws2p", - PubKey::Ed25519( - ed25519::PublicKey::from_base58( - "8t6Di3pLxxoTEfjXHjF49pNpjSTXuGEQ6BpkT75CkNb2", - ) - .unwrap(), - ), - 0, - 0, - ) - .unwrap(), - ], - } - } -} - -#[derive(Debug)] -/// Store a Signal receive from network (after message treatment) -pub enum WS2PSignal { - /// Receive a websocket error from a connextion. `NodeFullId` store the identifier of connection. - WSError(NodeFullId), - /// A new connection is successfully established with `NodeFullId`. - ConnectionEstablished(NodeFullId), - NegociationTimeout(NodeFullId), - Timeout(NodeFullId), - DalRequest(NodeFullId, ModuleReqId, serde_json::Value), - PeerCard(NodeFullId, serde_json::Value, Vec<EndpointV1>), - Heads(NodeFullId, Vec<NetworkHead>), - Document(NodeFullId, BlockchainDocument), - ReqResponse( - ModuleReqId, - OldNetworkRequest, - NodeFullId, - serde_json::Value, - ), - Empty, - NoConnection, -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub enum NetworkConsensusError { - InsufficientData(usize), - Fork, -} - -#[derive(Debug)] -pub enum SendRequestError { - RequestTypeMustNotBeTransmitted(), - WSError(usize, Vec<ws::Error>), -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub struct WS2PModule {} - -#[derive(Debug)] -pub enum WS2PThreadSignal { - DursMsg(Box<DursMsg>), - WS2PConnectionMessage(WS2PConnectionMessage), -} - -pub trait WS2PMessage: Sized { - fn parse(v: &serde_json::Value, currency: String) -> Option<Self>; - fn to_raw(&self) -> String; - fn sign(&self, key_pair: KeyPairEnum) -> Sig { - key_pair.sign(self.to_raw().as_bytes()) - } - fn verify(&self) -> bool; - //fn parse_and_verify(v: serde_json::Value, currency: String) -> bool; -} - -impl Default for WS2PModule { - fn default() -> WS2PModule { - WS2PModule {} - } -} - -#[derive(Debug)] -/// WS2PFeaturesParseError -pub enum WS2PFeaturesParseError { - /// UnknowApiFeature - UnknowApiFeature(String), -} - -impl ApiModule<DuRsConf, DursMsg> for WS2PModule { - type ParseErr = WS2PFeaturesParseError; - /// Parse raw api features - fn parse_raw_api_features(str_features: &str) -> Result<ApiFeatures, Self::ParseErr> { - let str_features: Vec<&str> = str_features.split(' ').collect(); - let mut api_features = Vec::with_capacity(0); - for str_feature in str_features { - match str_feature { - "DEF" => api_features[0] += 1u8, - "LOW" => api_features[0] += 2u8, - "ABF" => api_features[0] += 4u8, - _ => { - return Err(WS2PFeaturesParseError::UnknowApiFeature(String::from( - str_feature, - ))); - } - } - } - Ok(ApiFeatures(api_features)) - } -} - -impl NetworkModule<DuRsConf, DursMsg> for WS2PModule { - fn sync( - _soft_meta_datas: &SoftwareMetaDatas<DuRsConf>, - _keys: RequiredKeysContent, - _conf: WS2PConf, - _main_sender: mpsc::Sender<RouterThreadMessage<DursMsg>>, - _sync_params: SyncOpt, - ) -> Result<(), ModuleInitError> { - println!("Downlaod blockchain from network..."); - println!("Error : not yet implemented !"); - Ok(()) - } -} - -#[derive(StructOpt, Debug, Copy, Clone)] -#[structopt( - name = "ws2p", - raw(setting = "structopt::clap::AppSettings::ColoredHelp") -)] -/// WS2Pv1 subcommand options -pub struct WS2POpt {} - -impl DursModule<DuRsConf, DursMsg> for WS2PModule { - type ModuleConf = WS2PConf; - type ModuleOpt = WS2POpt; - - fn name() -> ModuleStaticName { - ModuleStaticName("ws2p") - } - fn priority() -> ModulePriority { - ModulePriority::Essential() - } - fn ask_required_keys() -> RequiredKeys { - RequiredKeys::NetworkKeyPair() - } - fn have_subcommand() -> bool { - true - } - fn exec_subcommand( - _soft_meta_datas: &SoftwareMetaDatas<DuRsConf>, - _keys: RequiredKeysContent, - _module_conf: Self::ModuleConf, - _subcommand_args: WS2POpt, - ) { - println!("Succesfully exec ws2p subcommand !") - } - fn start( - soft_meta_datas: &SoftwareMetaDatas<DuRsConf>, - keys: RequiredKeysContent, - conf: WS2PConf, - router_sender: mpsc::Sender<RouterThreadMessage<DursMsg>>, - load_conf_only: bool, - ) -> Result<(), ModuleInitError> { - // Get start time - let start_time = SystemTime::now(); - - // Define WS2PModuleDatas - let mut ws2p_module = WS2PModuleDatas::new( - router_sender.clone(), - conf, - NodeId(soft_meta_datas.conf.my_node_id()), - ); - - // load conf - let key_pair = match keys { - RequiredKeysContent::NetworkKeyPair(key_pair) => key_pair, - _ => panic!("WS2PModule fatal error at load_conf() : keys != NetworkKeyPair"), - }; - let mut ws2p_endpoints = HashMap::new(); - for ep in &ws2p_module.conf.sync_endpoints { - ws2p_endpoints.insert( - ep.node_full_id() - .expect("Fail to get endpoint node_full_id"), - (ep.clone(), WS2PConnectionState::Close), - ); - info!("Load sync endpoint {}", ep.raw_endpoint); - } - ws2p_module.key_pair = Some(key_pair); - ws2p_module.currency = Some(soft_meta_datas.conf.currency().to_string()); - ws2p_module.ws2p_endpoints = ws2p_endpoints; - - // Create ws2p main thread channel - let ws2p_sender_clone = ws2p_module.main_thread_channel.0.clone(); - - // Create proxy channel - let (proxy_sender, proxy_receiver): (mpsc::Sender<DursMsg>, mpsc::Receiver<DursMsg>) = - mpsc::channel(); - let proxy_sender_clone = proxy_sender.clone(); - - // Launch a proxy thread that transform DursMsg to WS2PThreadSignal(DursMsg) - thread::spawn(move || { - // Send proxy sender to main - router_sender - .send(RouterThreadMessage::ModuleRegistration( - WS2PModule::name(), - proxy_sender_clone, - vec![ModuleRole::InterNodesNetwork], - vec![ - ModuleEvent::NewValidBlock, - ModuleEvent::NewWotDocInPool, - ModuleEvent::NewTxinPool, - ], - vec![], - vec![], - )) - .expect("Fatal error : ws2p module fail to send is sender channel !"); - debug!("Send ws2p sender to main thread."); - loop { - match proxy_receiver.recv() { - Ok(message) => { - let stop = if let DursMsg::Stop = message { - true - } else { - false - }; - ws2p_sender_clone - .send(WS2PThreadSignal::DursMsg(Box::new(message))) - .expect( - "Fatal error : fail to relay DursMsgContent to ws2p main thread !", - ); - if stop { - break; - }; - } - Err(e) => panic!(format!("{}", e)), - } - } - }); - - // open ws2p bdd - let mut db_path = - duniter_conf::datas_path(&soft_meta_datas.profile, &soft_meta_datas.conf.currency()); - db_path.push("ws2p.db"); - let db = WS2PModuleDatas::open_db(&db_path).expect("Fatal error : fail to open WS2P DB !"); - - // Get ws2p endpoints in BDD - let mut count = 0; - let dal_enpoints = - ws2p_db::get_endpoints_for_api(&db, &NetworkEndpointApi(String::from("WS2P"))); - for ep in dal_enpoints { - if ep.api == NetworkEndpointApi(String::from("WS2P")) - && (cfg!(feature = "ssl") || ep.port != 443) - { - count += 1; - ws2p_module.ws2p_endpoints.insert( - ep.node_full_id() - .expect("WS2P: Fail to get ep.node_full_id() !"), - (ep.clone(), WS2PConnectionState::from(ep.status)), - ); - } - } - info!("Load {} endpoints from bdd !", count); - - // Stop here in load_conf_only mode - if load_conf_only { - return Ok(()); - } - - // Initialize variables - let mut last_ws2p_connecting_wave = SystemTime::now(); - let mut last_ws2p_connections_print = SystemTime::now(); - let mut endpoints_to_update_status: HashMap<NodeFullId, SystemTime> = HashMap::new(); - let mut last_identities_request = UNIX_EPOCH; - let mut current_blockstamp = Blockstamp::default(); - let mut next_receiver = 0; - - // Request current blockstamp - ws2p_module.send_dal_request(&BlockchainRequest::CurrentBlockstamp()); - - // Start - ws2p_module.connect_to_know_endpoints(); - loop { - match ws2p_module - .main_thread_channel - .1 - .recv_timeout(Duration::from_millis(200)) - { - Ok(message) => match message { - WS2PThreadSignal::DursMsg(ref durs_mesage) => { - match *durs_mesage.deref() { - DursMsg::Stop => break, - DursMsg::Request { - ref req_content, .. - } => { - if let DursReqContent::OldNetworkRequest(ref old_net_request) = - *req_content - { - match *old_net_request { - OldNetworkRequest::GetBlocks( - ref req_id, - ref count, - ref from, - ) => { - let mut receiver_index = 0; - let mut real_receiver = None; - for (ws2p_full_id, (_ep, state)) in - ws2p_module.ws2p_endpoints.clone() - { - if let WS2PConnectionState::Established = state { - if receiver_index == next_receiver { - real_receiver = Some(ws2p_full_id); - break; - } - receiver_index += 1; - } - } - if real_receiver.is_none() { - next_receiver = 0; - for (ws2p_full_id, (_ep, state)) in - &ws2p_module.ws2p_endpoints - { - if let WS2PConnectionState::Established = *state - { - real_receiver = Some(*ws2p_full_id); - break; - } - } - } else { - next_receiver += 1; - } - if let Some(real_receiver) = real_receiver { - debug!("WS2P: send req to: ({:?})", real_receiver); - let _blocks_request_result = ws2p_module - .send_request_to_specific_node( - &real_receiver, - &OldNetworkRequest::GetBlocks( - *req_id, *count, *from, - ), - ); - } else { - warn!("WS2P: not found peer to send request !"); - } - } - OldNetworkRequest::GetEndpoints(ref _request) => {} - _ => {} - } - } - } - DursMsg::Event { - ref event_content, .. - } => { - if let DursEvent::BlockchainEvent(ref bc_event) = *event_content { - match *bc_event.deref() { - BlockchainEvent::StackUpValidBlock(ref block) => { - current_blockstamp = block.deref().blockstamp(); - debug!( - "WS2PModule : current_blockstamp = {}", - current_blockstamp - ); - ws2p_module.my_head = Some(heads::generate_my_head( - &key_pair, - NodeId(soft_meta_datas.conf.my_node_id()), - soft_meta_datas.soft_name, - soft_meta_datas.soft_version, - ¤t_blockstamp, - None, - )); - ws2p_module.send_network_event( - &NetworkEvent::ReceiveHeads(vec![ws2p_module - .my_head - .clone() - .unwrap()]), - ); - // Send my head to all connections - let my_json_head = serializer::serialize_head( - ws2p_module.my_head.clone().unwrap(), - ); - trace!("Send my HEAD: {:#?}", my_json_head); - let _results: Result<(), ws::Error> = ws2p_module - .websockets - .iter_mut() - .map(|ws| { - (ws.1).0.send(Message::text( - json!({ - "name": "HEAD", - "body": { - "heads": [my_json_head] - } - }) - .to_string(), - )) - }) - .collect(); - } - BlockchainEvent::RevertBlocks(ref _blocks) => {} - _ => {} - } - } - } - DursMsg::Response { - ref res_content, .. - } => { - if let DursResContent::BlockchainResponse(ref bc_res) = *res_content - { - match *bc_res.deref() { - BlockchainResponse::CurrentBlockstamp( - ref _requester_id, - ref current_blockstamp_, - ) => { - debug!( - "WS2PModule : receive DALResBc::CurrentBlockstamp({})", - current_blockstamp - ); - current_blockstamp = *current_blockstamp_; - if ws2p_module.my_head.is_none() { - ws2p_module.my_head = - Some(heads::generate_my_head( - &key_pair, - NodeId(soft_meta_datas.conf.my_node_id()), - soft_meta_datas.soft_name, - soft_meta_datas.soft_version, - ¤t_blockstamp, - None, - )); - } - ws2p_module.send_network_event( - &NetworkEvent::ReceiveHeads(vec![ws2p_module - .my_head - .clone() - .unwrap()]), - ); - } - BlockchainResponse::UIDs(ref _req_id, ref uids) => { - // Add uids to heads - for head in ws2p_module.heads_cache.values_mut() { - if let Some(uid_option) = uids.get(&head.pubkey()) { - if let Some(ref uid) = *uid_option { - head.set_uid(uid); - ws2p_module - .uids_cache - .insert(head.pubkey(), uid.to_string()); - } else { - ws2p_module - .uids_cache - .remove(&head.pubkey()); - } - } - } - // Resent heads to other modules - ws2p_module.send_network_event( - &NetworkEvent::ReceiveHeads( - ws2p_module - .heads_cache - .values() - .cloned() - .collect(), - ), - ); - // Resent to other modules connections that match receive uids - for (node_full_id, (ep, conn_state)) in - &ws2p_module.ws2p_endpoints - { - if let Some(uid_option) = uids.get(&node_full_id.1) - { - ws2p_module.send_network_event( - &NetworkEvent::ConnectionStateChange( - *node_full_id, - *conn_state as u32, - uid_option.clone(), - ep.get_url(false, false) - .expect("Endpoint unreachable !"), - ), - ); - } - } - } - _ => {} // Others BlockchainResponse variants - } - } - } - _ => {} // Others DursMsg variants - } - } - WS2PThreadSignal::WS2PConnectionMessage(ws2p_conn_message) => match ws2p_module - .ws2p_conn_message_pretreatment(ws2p_conn_message) - { - WS2PSignal::NoConnection => { - warn!("WS2PSignal::NoConnection"); - } - WS2PSignal::ConnectionEstablished(ws2p_full_id) => { - let req_id = - ModuleReqId(ws2p_module.requests_awaiting_response.len() as u32); - let module_id = WS2PModule::name(); - debug!("WS2P: send req to: ({:?})", ws2p_full_id); - let _current_request_result = ws2p_module - .send_request_to_specific_node( - &ws2p_full_id, - &OldNetworkRequest::GetCurrent(ModuleReqFullId( - module_id, req_id, - )), - ); - if ws2p_module.uids_cache.get(&ws2p_full_id.1).is_none() { - ws2p_module.send_dal_request(&BlockchainRequest::UIDs(vec![ - ws2p_full_id.1, - ])); - } - ws2p_module.send_network_event(&NetworkEvent::ConnectionStateChange( - ws2p_full_id, - WS2PConnectionState::Established as u32, - ws2p_module.uids_cache.get(&ws2p_full_id.1).cloned(), - ws2p_module.ws2p_endpoints[&ws2p_full_id] - .0 - .get_url(false, false) - .expect("Endpoint unreachable !"), - )); - } - WS2PSignal::WSError(ws2p_full_id) => { - endpoints_to_update_status.insert(ws2p_full_id, SystemTime::now()); - ws2p_module.close_connection( - &ws2p_full_id, - WS2PCloseConnectionReason::WsError, - ); - ws2p_module.send_network_event(&NetworkEvent::ConnectionStateChange( - ws2p_full_id, - WS2PConnectionState::WSError as u32, - ws2p_module.uids_cache.get(&ws2p_full_id.1).cloned(), - ws2p_module.ws2p_endpoints[&ws2p_full_id] - .0 - .get_url(false, false) - .expect("Endpoint unreachable !"), - )); - } - WS2PSignal::NegociationTimeout(ws2p_full_id) => { - endpoints_to_update_status.insert(ws2p_full_id, SystemTime::now()); - ws2p_module.send_network_event(&NetworkEvent::ConnectionStateChange( - ws2p_full_id, - WS2PConnectionState::Denial as u32, - ws2p_module.uids_cache.get(&ws2p_full_id.1).cloned(), - ws2p_module.ws2p_endpoints[&ws2p_full_id] - .0 - .get_url(false, false) - .expect("Endpoint unreachable !"), - )); - } - WS2PSignal::Timeout(ws2p_full_id) => { - endpoints_to_update_status.insert(ws2p_full_id, SystemTime::now()); - ws2p_module.send_network_event(&NetworkEvent::ConnectionStateChange( - ws2p_full_id, - WS2PConnectionState::Close as u32, - ws2p_module.uids_cache.get(&ws2p_full_id.1).cloned(), - ws2p_module.ws2p_endpoints[&ws2p_full_id] - .0 - .get_url(false, false) - .expect("Endpoint unreachable !"), - )); - } - WS2PSignal::PeerCard(_ws2p_full_id, _peer_card, ws2p_endpoints) => { - //trace!("WS2PSignal::PeerCard({})", ws2p_full_id); - //ws2p_module.send_network_event(NetworkEvent::ReceivePeers(_)); - for ep in ws2p_endpoints { - match ws2p_module.ws2p_endpoints.get( - &ep.node_full_id() - .expect("WS2P: Fail to get ep.node_full_id() !"), - ) { - Some(_) => {} - None => { - if let Some(_api) = - ws2p_db::string_to_api(&ep.api.0.clone()) - { - endpoints_to_update_status.insert( - ep.node_full_id().expect( - "WS2P: Fail to get ep.node_full_id() !", - ), - SystemTime::now(), - ); - } - if cfg!(feature = "ssl") || ep.port != 443 { - ws2p_module.connect_to(&ep); - } - } - }; - } - } - WS2PSignal::Heads(ws2p_full_id, heads) => { - trace!("WS2PSignal::Heads({}, {:?})", ws2p_full_id, heads.len()); - ws2p_module.send_dal_request(&BlockchainRequest::UIDs( - heads.iter().map(NetworkHead::pubkey).collect(), - )); - ws2p_module.send_network_event(&NetworkEvent::ReceiveHeads( - heads - .iter() - .map(|head| { - let mut new_head = head.clone(); - if let Some(uid) = - ws2p_module.uids_cache.get(&head.pubkey()) - { - new_head.set_uid(uid); - } - new_head - }) - .collect(), - )); - } - WS2PSignal::Document(ws2p_full_id, network_doc) => { - trace!("WS2PSignal::Document({})", ws2p_full_id); - ws2p_module.send_network_event(&NetworkEvent::ReceiveDocuments(vec![ - network_doc, - ])); - } - WS2PSignal::ReqResponse(req_id, req, recipient_full_id, response) => { - match req { - OldNetworkRequest::GetCurrent(ref _req_id) => { - info!("WS2PSignal::ReceiveCurrent({}, {:?})", req_id.0, req); - if let Some(block) = parse_json_block(&response) { - ws2p_module.send_network_req_response( - req.get_req_full_id().0, - req.get_req_full_id().1, - NetworkResponse::CurrentBlock( - ModuleReqFullId(WS2PModule::name(), req_id), - recipient_full_id, - Box::new(block), - ), - ); - } - } - OldNetworkRequest::GetBlocks(ref _req_id, count, from) => { - info!( - "WS2PSignal::ReceiveChunk({}, {} blocks from {})", - req_id.0, count, from - ); - if response.is_array() { - let mut chunk = Vec::new(); - for json_block in response.as_array().unwrap() { - if let Some(block) = parse_json_block(json_block) { - chunk.push(block); - } else { - warn!("WS2PModule: Error : fail to parse one json block !"); - } - } - debug!("Send chunk to followers : {}", from); - ws2p_module.send_network_event( - &NetworkEvent::ReceiveBlocks(chunk), - ); - } - } - OldNetworkRequest::GetRequirementsPending(_req_id, min_cert) => { - info!( - "WS2PSignal::ReceiveRequirementsPending({}, {})", - req_id.0, min_cert - ); - debug!("----------------------------------------"); - debug!("- BEGIN IDENTITIES PENDING -"); - debug!("----------------------------------------"); - debug!("{:#?}", response); - debug!("----------------------------------------"); - debug!("- END IDENTITIES PENDING -"); - debug!("----------------------------------------"); - } - _ => {} - } - } - WS2PSignal::Empty => {} - _ => {} - }, - }, - Err(e) => match e { - mpsc::RecvTimeoutError::Disconnected => { - panic!("Disconnected ws2p module !"); - } - mpsc::RecvTimeoutError::Timeout => {} - }, - } - if SystemTime::now() - .duration_since(last_ws2p_connections_print) - .unwrap() - > Duration::new(5, 0) - { - last_ws2p_connections_print = SystemTime::now(); - let mut connected_nodes = Vec::new(); - for (k, (_ep, state)) in ws2p_module.ws2p_endpoints.clone() { - if let WS2PConnectionState::Established = state { - connected_nodes.push(k); - } - } - // Print current_blockstamp - info!( - "WS2PModule : current_blockstamp() = {:?}", - current_blockstamp - ); - // New WS2P connection wave - if connected_nodes.len() < ws2p_module.conf.clone().outcoming_quota - && (SystemTime::now() - .duration_since(last_ws2p_connecting_wave) - .unwrap() - > Duration::new(*WS2P_OUTCOMING_INTERVAL, 0) - || (SystemTime::now() - .duration_since(last_ws2p_connecting_wave) - .unwrap() - > Duration::new(*WS2P_OUTCOMING_INTERVAL_AT_STARTUP, 0) - && SystemTime::now().duration_since(start_time).unwrap() - < Duration::new(*WS2P_OUTCOMING_INTERVAL, 0))) - { - last_ws2p_connecting_wave = SystemTime::now(); - info!("Connected to know endpoints..."); - ws2p_module.connect_to_know_endpoints(); - } - // Request pending_identities from network - if SystemTime::now() - .duration_since(last_identities_request) - .unwrap() - > Duration::new(*PENDING_IDENTITIES_REQUEST_INTERVAL, 0) - && SystemTime::now().duration_since(start_time).unwrap() > Duration::new(10, 0) - { - /*info!("get pending_identities from all connections..."); - let _blocks_request_result = ws2p_module.send_request_to_all_connections( - &OldNetworkRequest::GetRequirementsPending(ModuleReqId(0 as u32), 5), - );*/ - last_identities_request = SystemTime::now(); - } - // Write pending endpoints - for (ep_full_id, received_time) in endpoints_to_update_status.clone() { - if SystemTime::now().duration_since(received_time).unwrap() - > Duration::new(*DURATION_BEFORE_RECORDING_ENDPOINT, 0) - { - if let Some(&(ref ep, ref state)) = - ws2p_module.ws2p_endpoints.get(&ep_full_id) - { - ws2p_db::write_endpoint( - &db, - &ep, - state.to_u32(), - SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_secs(), - ); - } - endpoints_to_update_status.remove(&ep_full_id); - } else { - info!( - "Write {} endpoint in {} secs.", - ep_full_id, - *DURATION_BEFORE_RECORDING_ENDPOINT - - SystemTime::now() - .duration_since(received_time) - .unwrap() - .as_secs() - ); - } - } - // .. - // Request current blockstamp - ws2p_module.send_dal_request(&BlockchainRequest::CurrentBlockstamp()); - } - } - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use super::parsers::blocks::parse_json_block; - use super::*; - use dubp_documents::documents::block::BlockDocument; - use duniter_module::DursModule; - use dup_crypto::keys::PublicKey; - use durs_network_documents::network_endpoint::NetworkEndpointApi; - use std::fs; - use std::path::PathBuf; - use std::time::{SystemTime, UNIX_EPOCH}; - - #[test] - fn test_parse_json_block() { - let json_block = json!({ - "fork": false, - "version": 10, - "nonce": 10500000059239 as u64, - "number": 109966, - "powMin": 88, - "time": 1523300656, - "medianTime": 1523295259, - "membersCount": 933, - "monetaryMass": 146881563, - "unitbase": 0, - "issuersCount": 44, - "issuersFrame": 221, - "issuersFrameVar": 0, - "currency": "g1", - "issuer": "GRBPV3Y7PQnB9LaZhSGuS3BqBJbSHyibzYq65kTh1nQ4", - "signature": "GCg2Lti3TdxWlhA8JF8pRI+dRQ0XZVtcC4BqO/COTpjTQFdWG6qmUNVvdeYCtR/lu1JQe3N/IhrbyV6L/6I+Cg==", - "hash": "000000EF5B2AA849F4C3AF3D35E1284EA1F34A9F617EA806CE8371619023DC74", - "parameters": "", - "previousHash": "000004C00602F8A27AE078DE6351C0DDA1EA0974A78D2BEFA7DFBE7B7C3146FD", - "previousIssuer": "5SwfQubSat5SunNafCsunEGTY93nVM4kLSsuprNqQb6S", - "inner_hash": "61F02B1A6AE2E4B9A1FD66CE673258B4B21C0076795571EE3C9DC440DD06C46C", - "dividend": null, - "identities": [], - "joiners": [], - "actives": [], - "leavers": [], - "revoked": [], - "excluded": [], - "certifications": [ - "Hm5qjaNuHogNRdGZ4vgnLA9DMZVUu5YWzVup5mubuxCc:8AmdBsimcLziXaCS4AcVUfPx7rkjeic7482dLbBkuZw6:109964:yHKBGMeuxyIqFb295gVNK6neRC+U0tmsX1Zed3TLjS3ZZHYYycE1piLcYsTKll4ifNVp6rm+hd/CLdHYB+29CA==", - "BncjgJeFpGsMCCsUfzNLEexjsbuX3V2mg9P67ov2LkwK:DyBUBNpzpfvjtwYYSaVMM6ST6t2DNg3NCE9CU9bRQFhF:105864:cJEGW9WxJwlMA2+4LNAK4YieyseUy1WIkFh1YLYD+JJtJEoCSnIQRXzhiAoRpGaj0bRz8sTpwI6PRkuVoDJJDQ==" - ], - "transactions": [ - { - "version": 10, - "currency": "g1", - "locktime": 0, - "hash": "80FE1E83DC4D0B722CA5F8363EFC6A3E29071032EBB71C1E0DF8D4FEA589C698", - "blockstamp": "109964-00000168105D4A8A8BC8C0DC70033F45ABE472782C75A7F2074D0F4D4A3B7B2B", - "blockstampTime": 0, - "issuers": [ - "6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT" - ], - "inputs": [ - "1001:0:D:6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT:98284", - "1001:0:D:6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT:98519", - "1001:0:D:6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT:98779", - "1001:0:D:6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT:99054", - "1001:0:D:6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT:99326", - "1001:0:D:6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT:99599", - "1001:0:D:6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT:99884", - "1001:0:D:6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT:100174", - "1001:0:D:6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT:100469", - "1001:0:D:6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT:100746", - "1001:0:D:6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT:101036", - "1001:0:D:6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT:101327" - ], - "outputs": [ - "12000:0:SIG(HmH5beJqKGMeotcQUrSW7Wo5tKvAksHmfYXfiSQ9EbWz)", - "12:0:SIG(6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT)" - ], - "unlocks": [ - "0:SIG(0)", - "1:SIG(0)", - "2:SIG(0)", - "3:SIG(0)", - "4:SIG(0)", - "5:SIG(0)", - "6:SIG(0)", - "7:SIG(0)", - "8:SIG(0)", - "9:SIG(0)", - "10:SIG(0)", - "11:SIG(0)" - ], - "signatures": [ - "MZxoKxYgwufh/s5mwLCsYEZXtIsP1hEKCyAzLipJsvCbR9xj7wXUw0C/ahwvZfBtR7+QVPIfLmwYEol1JcHjDw==" - ], - "comment": "Adhesion 2018" - }, - { - "version": 10, - "currency": "g1", - "locktime": 0, - "hash": "B80507412B35BD5EB437AE0D3EB97E60E3A4974F5CDEA1AF7E2127C0E943481F", - "blockstamp": "109964-00000168105D4A8A8BC8C0DC70033F45ABE472782C75A7F2074D0F4D4A3B7B2B", - "blockstampTime": 0, - "issuers": [ - "8gundJEbfm73Kx3jjw8YivJyz8qD2igjf6baCBLFCxPU" - ], - "inputs": [ - "1001:0:D:8gundJEbfm73Kx3jjw8YivJyz8qD2igjf6baCBLFCxPU:91560", - "1001:0:D:8gundJEbfm73Kx3jjw8YivJyz8qD2igjf6baCBLFCxPU:91850", - "1001:0:D:8gundJEbfm73Kx3jjw8YivJyz8qD2igjf6baCBLFCxPU:92111", - "1001:0:D:8gundJEbfm73Kx3jjw8YivJyz8qD2igjf6baCBLFCxPU:92385", - "1001:0:D:8gundJEbfm73Kx3jjw8YivJyz8qD2igjf6baCBLFCxPU:92635" - ], - "outputs": [ - "5000:0:SIG(BzHnbec1Gov7dLSt1EzJS7vikoQCECeuvZs4wamZAcT1)", - "5:0:SIG(8gundJEbfm73Kx3jjw8YivJyz8qD2igjf6baCBLFCxPU)" - ], - "unlocks": [ - "0:SIG(0)", - "1:SIG(0)", - "2:SIG(0)", - "3:SIG(0)", - "4:SIG(0)" - ], - "signatures": [ - "A+ukwRvLWs1gZQ0KAqAnknEgmRQHdrnOvNuBx/WZqje17BAPrVxSxKpqwU6MiajU+ppigsYp6Bu0FdPf/tGnCQ==" - ], - "comment": "" - }, - { - "version": 10, - "currency": "g1", - "locktime": 0, - "hash": "D8970E6629C0381A78534EEDD86803E9215A7EC4C494BAEA79EB19425F9B4D31", - "blockstamp": "109964-00000168105D4A8A8BC8C0DC70033F45ABE472782C75A7F2074D0F4D4A3B7B2B", - "blockstampTime": 0, - "issuers": [ - "FnSXE7QyBfs4ozoYAt5NEewWhHEPorf38cNXu3kX9xsg" - ], - "inputs": [ - "1000:0:D:FnSXE7QyBfs4ozoYAt5NEewWhHEPorf38cNXu3kX9xsg:36597", - "1000:0:D:FnSXE7QyBfs4ozoYAt5NEewWhHEPorf38cNXu3kX9xsg:36880", - "1000:0:D:FnSXE7QyBfs4ozoYAt5NEewWhHEPorf38cNXu3kX9xsg:37082" - ], - "outputs": [ - "3000:0:SIG(BBC8Rnh4CWN1wBrPLevK7GRFFVDVw7Lu24YNMUmhqoHU)" - ], - "unlocks": [ - "0:SIG(0)", - "1:SIG(0)", - "2:SIG(0)" - ], - "signatures": [ - "OpiF/oQfIigOeAtsteukU0w9FPSELE+BVTxhmsQ8bEeYGlwovG2VF8ZFiJkLLPi6vFuKgwzULJfjNGd97twZCw==" - ], - "comment": "1 billet pour une seance.pour un chouette film" - } - ], - }); - let mut block: BlockDocument = - parse_json_block(&json_block).expect("Fail to parse test json block !"); - assert_eq!( - block - .inner_hash - .expect("Try to get inner_hash of an uncompleted or reduce block !") - .to_hex(), - "61F02B1A6AE2E4B9A1FD66CE673258B4B21C0076795571EE3C9DC440DD06C46C" - ); - block.compute_hash(); - assert_eq!( - block - .hash - .expect("Try to get hash of an uncompleted or reduce block !") - .0 - .to_hex(), - "000000EF5B2AA849F4C3AF3D35E1284EA1F34A9F617EA806CE8371619023DC74" - ); - } - - #[test] - fn endpoint_db_tests() { - let test_db_path = PathBuf::from("test.db"); - if test_db_path.as_path().exists() { - fs::remove_file(&test_db_path).unwrap(); - } - let db = WS2PModuleDatas::open_db(&test_db_path).unwrap(); - - let current_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); - - let mut endpoint = EndpointV1::parse_from_raw( - "WS2P cb06a19b g1.imirhil.fr 53012 /", - PubKey::Ed25519( - ed25519::PublicKey::from_base58("5gJYnQp8v7bWwk7EWRoL8vCLof1r3y9c6VDdnGSM1GLv") - .unwrap(), - ), - 1, - current_time.as_secs(), - ) - .expect("Failt to parse test endpoint !"); - - ws2p_db::write_endpoint(&db, &endpoint, 1, current_time.as_secs()); - let mut written_endpoints = - ws2p_db::get_endpoints_for_api(&db, &NetworkEndpointApi(String::from("WS2P"))); - assert_eq!(endpoint, written_endpoints.pop().unwrap()); - - // Test status update - endpoint.status = 3; - ws2p_db::write_endpoint(&db, &endpoint, 3, current_time.as_secs()); - let mut written_endpoints = - ws2p_db::get_endpoints_for_api(&db, &NetworkEndpointApi(String::from("WS2P"))); - assert_eq!(endpoint, written_endpoints.pop().unwrap()); - } - - #[test] - fn ws2p_requests() { - let module_id = WS2PModule::name(); - let request = - OldNetworkRequest::GetBlocks(ModuleReqFullId(module_id, ModuleReqId(58)), 50, 0); - assert_eq!( - network_request_to_json(&request), - json!({ - "reqId": format!("{:x}", 58), - "body": { - "name": "BLOCKS_CHUNK", - "params": { - "count": 50, - "fromNumber": 0 - } - } - }) - ); - assert_eq!( - network_request_to_json(&request).to_string(), - "{\"body\":{\"name\":\"BLOCKS_CHUNK\",\"params\":{\"count\":50,\"fromNumber\":0}},\"reqId\":\"3a\"}" - ); - } - - #[test] - fn ws2p_parse_head() { - let head = json!({ - "message": "WS2POTMIC:HEAD:1:D9D2zaJoWYWveii1JRYLVK3J4Z7ZH3QczoKrnQeiM6mx:104512-0000051B9CE9C1CA89F269375A6751FB88B9E88DE47A36506057E5BFBCFBB276:c1c39a0a:duniter:1.6.21:3", - "sig": "trtK9GXvTdfND995ohWEderpO3NkIqi1X6mBeVvMcaHckq+lIGqjWvJ9t9Vccz5t+VGaSmGUihDl4q6eldIYBw==", - "messageV2": "WS2POTMIC:HEAD:2:D9D2zaJoWYWveii1JRYLVK3J4Z7ZH3QczoKrnQeiM6mx:104512-0000051B9CE9C1CA89F269375A6751FB88B9E88DE47A36506057E5BFBCFBB276:c1c39a0a:duniter:1.6.21:3:25:22", - "sigV2": "x6ehPMuYjGY+z7wEGnJGyMBxMKUdu01RWaF0b0XCtoVjg67cCvT4H0V/Qcxn4bAGqzy5ux2fA7NiI+81bBnqDw==", - "step": 0 - }); - let mut heads_count = 0; - if let Ok(head) = NetworkHead::from_json_value(&head) { - if let NetworkHead::V2(ref head_v2) = head { - heads_count += 1; - assert_eq!( - head_v2.message.to_string(), - String::from("WS2POTMIC:HEAD:1:D9D2zaJoWYWveii1JRYLVK3J4Z7ZH3QczoKrnQeiM6mx:104512-0000051B9CE9C1CA89F269375A6751FB88B9E88DE47A36506057E5BFBCFBB276:c1c39a0a:duniter:1.6.21:3") - ); - } - assert_eq!(head.verify(), true); - } else { - panic!("Fail to parse head !") - } - assert_eq!(heads_count, 1); - } -} diff --git a/lib/modules/ws2p-v1-legacy/ack_message.rs b/lib/modules/ws2p-v1-legacy/src/ack_message.rs similarity index 67% rename from lib/modules/ws2p-v1-legacy/ack_message.rs rename to lib/modules/ws2p-v1-legacy/src/ack_message.rs index e1fcdeed37934ec8ed568e98d33bad0ea166e74e..f18bf55e7e389f748ddbf0bf6c286c78daea6712 100644 --- a/lib/modules/ws2p-v1-legacy/ack_message.rs +++ b/lib/modules/ws2p-v1-legacy/src/ack_message.rs @@ -1,4 +1,4 @@ -use super::WS2PMessage; +use crate::*; use dup_crypto::keys::*; use serde::ser::{Serialize, SerializeStruct, Serializer}; @@ -11,20 +11,19 @@ pub struct WS2PAckMessageV1 { } impl WS2PMessage for WS2PAckMessageV1 { - fn parse(v: &serde_json::Value, currency: String) -> Option<Self> { + fn parse(v: &serde_json::Value, currency: String) -> Result<Self, WS2PMsgParseErr> { let pubkey = match v.get("pub") { - Some(pubkey) => pubkey.as_str().unwrap().to_string(), - None => return None, + Some(pubkey) => pubkey.as_str().ok_or(WS2PMsgParseErr {})?.to_string(), + None => return Err(WS2PMsgParseErr {}), }; let signature = match v.get("sig") { - Some(signature) => signature.as_str().unwrap().to_string(), - None => return None, + Some(signature) => signature.as_str().ok_or(WS2PMsgParseErr {})?.to_string(), + None => return Err(WS2PMsgParseErr {}), }; - let pubkey = PubKey::Ed25519(ed25519::PublicKey::from_base58(&pubkey).unwrap()); - let signature: Option<Sig> = Some(Sig::Ed25519( - ed25519::Signature::from_base64(&signature).unwrap(), - )); - Some(WS2PAckMessageV1 { + let pubkey = PubKey::Ed25519(ed25519::PublicKey::from_base58(&pubkey)?); + let signature: Option<Sig> = + Some(Sig::Ed25519(ed25519::Signature::from_base64(&signature)?)); + Ok(WS2PAckMessageV1 { currency, pubkey, challenge: "".to_string(), @@ -38,8 +37,11 @@ impl WS2PMessage for WS2PAckMessageV1 { ) } fn verify(&self) -> bool { - self.pubkey - .verify(self.to_raw().as_bytes(), &self.signature.unwrap()) + if let Some(sig) = self.signature { + self.pubkey.verify(self.to_raw().as_bytes(), &sig) + } else { + false + } } } diff --git a/lib/modules/ws2p-v1-legacy/connect_message.rs b/lib/modules/ws2p-v1-legacy/src/connect_message.rs similarity index 66% rename from lib/modules/ws2p-v1-legacy/connect_message.rs rename to lib/modules/ws2p-v1-legacy/src/connect_message.rs index 1f917aded01c469b82019f7af394407ecf343d67..e229cc1def5b603c1ee14e61ec092e0079366620 100644 --- a/lib/modules/ws2p-v1-legacy/connect_message.rs +++ b/lib/modules/ws2p-v1-legacy/src/connect_message.rs @@ -1,4 +1,4 @@ -use super::WS2PMessage; +use crate::*; use dup_crypto::keys::*; use serde::ser::{Serialize, SerializeStruct, Serializer}; @@ -11,24 +11,22 @@ pub struct WS2PConnectMessageV1 { } impl WS2PMessage for WS2PConnectMessageV1 { - fn parse(v: &serde_json::Value, currency: String) -> Option<Self> { + fn parse(v: &serde_json::Value, currency: String) -> Result<Self, WS2PMsgParseErr> { let pubkey = match v.get("pub") { - Some(pubkey) => pubkey.as_str().unwrap().to_string(), - None => return None, + Some(pubkey) => pubkey.as_str().ok_or(WS2PMsgParseErr {})?.to_string(), + None => return Err(WS2PMsgParseErr {}), }; let challenge = match v.get("challenge") { - Some(challenge) => challenge.as_str().unwrap().to_string(), - None => return None, + Some(challenge) => challenge.as_str().ok_or(WS2PMsgParseErr {})?.to_string(), + None => return Err(WS2PMsgParseErr {}), }; let signature = match v.get("sig") { - Some(signature) => signature.as_str().unwrap().to_string(), - None => return None, + Some(signature) => signature.as_str().ok_or(WS2PMsgParseErr {})?.to_string(), + None => return Err(WS2PMsgParseErr {}), }; - let pubkey = PubKey::Ed25519(ed25519::PublicKey::from_base58(&pubkey).unwrap()); - let signature = Some(Sig::Ed25519( - ed25519::Signature::from_base64(&signature).unwrap(), - )); - Some(WS2PConnectMessageV1 { + let pubkey = PubKey::Ed25519(ed25519::PublicKey::from_base58(&pubkey)?); + let signature = Some(Sig::Ed25519(ed25519::Signature::from_base64(&signature)?)); + Ok(WS2PConnectMessageV1 { currency, pubkey, challenge, @@ -42,8 +40,11 @@ impl WS2PMessage for WS2PConnectMessageV1 { ) } fn verify(&self) -> bool { - self.pubkey - .verify(self.to_raw().as_bytes(), &self.signature.unwrap()) + if let Some(sig) = self.signature { + self.pubkey.verify(self.to_raw().as_bytes(), &sig) + } else { + false + } } } diff --git a/lib/modules/ws2p-v1-legacy/src/constants.rs b/lib/modules/ws2p-v1-legacy/src/constants.rs new file mode 100644 index 0000000000000000000000000000000000000000..cdc14af73e1013607c3c924fc838e602d1cdb1a6 --- /dev/null +++ b/lib/modules/ws2p-v1-legacy/src/constants.rs @@ -0,0 +1,55 @@ +// Copyright (C) 2018 The Durs 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/>. + +//! WS2Pv1 constants + +/// API Name +pub static WS2P_API: &'static str = "WS2P"; + +/// Interval between to sequence of general actions +pub static WS2P_GENERAL_STATE_INTERVAL: &'static u64 = &15; + +/// Connection wave interval at startup +pub static WS2P_OUTCOMING_INTERVAL_AT_STARTUP: &'static u64 = &75; + +/// Interval of connection waves after the start-up phase +pub static WS2P_OUTCOMING_INTERVAL: &'static u64 = &300; + +/// Default outgoing connection quota +pub static WS2P_DEFAULT_OUTCOMING_QUOTA: &'static usize = &10; + +/// Maximum duration of a connection negotiation +pub static WS2P_NEGOTIATION_TIMEOUT: &'static u64 = &15; + +/// Maximum waiting time for a response to a request +//pub static WS2P_REQUEST_TIMEOUT : &'static u64 = &30; + +/// Maximum duration of inactivity of a connection (the connection will be closed after this delay) +pub static WS2P_EXPIRE_TIMEOUT: &'static u64 = &120; + +/// Interval between 2 messages from which it''s perhaps a spam (in milliseconds) +pub static WS2P_SPAM_INTERVAL_IN_MILLI_SECS: &'static u64 = &80; + +/// Number of consecutive closed messages from which messages will be considered as spam. +pub static WS2P_SPAM_LIMIT: &'static usize = &6; + +/// Rest time in a situation of proven spam +pub static WS2P_SPAM_SLEEP_TIME_IN_SEC: &'static u64 = &100; + +/// Duration between 2 endpoints saving +pub static DURATION_BETWEEN_2_ENDPOINTS_SAVING: &'static u64 = &180; + +/// Duration between 2 requests from the pool of the wot data +pub static PENDING_IDENTITIES_REQUEST_INTERVAL: &'static u64 = &40; diff --git a/lib/modules/ws2p-v1-legacy/src/events/mod.rs b/lib/modules/ws2p-v1-legacy/src/events/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..0e3731259de36e027add6106ac4b7d949cc746f4 --- /dev/null +++ b/lib/modules/ws2p-v1-legacy/src/events/mod.rs @@ -0,0 +1,19 @@ +// Copyright (C) 2018 The Durs 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/>. + +//! Sub-module managing the events received and sent. + +pub mod received; +pub mod sent; diff --git a/lib/modules/ws2p-v1-legacy/src/events/received.rs b/lib/modules/ws2p-v1-legacy/src/events/received.rs new file mode 100644 index 0000000000000000000000000000000000000000..a8bb4f2785f185b944d75cf63ffce24243bd8d20 --- /dev/null +++ b/lib/modules/ws2p-v1-legacy/src/events/received.rs @@ -0,0 +1,72 @@ +// Copyright (C) 2018 The Durs 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/>. + +//! Sub-module managing events received from other durs modules + +use crate::*; +use dubp_documents::Document; +use duniter_module::*; +use durs_message::events::DursEvent; +use std::ops::Deref; + +pub fn receive_event( + ws2p_module: &mut WS2PModule, + _event_type: ModuleEvent, + event_content: &DursEvent, +) { + if let DursEvent::BlockchainEvent(ref bc_event) = *event_content { + match *bc_event.deref() { + BlockchainEvent::StackUpValidBlock(ref block) => { + ws2p_module.current_blockstamp = block.deref().blockstamp(); + debug!( + "WS2PModule : current_blockstamp = {}", + ws2p_module.current_blockstamp + ); + ws2p_module.my_head = Some(heads::generate_my_head( + &ws2p_module.key_pair, + ws2p_module.node_id, + ws2p_module.soft_name, + ws2p_module.soft_version, + &ws2p_module.current_blockstamp, + None, + )); + super::sent::send_network_event( + ws2p_module, + NetworkEvent::ReceiveHeads(vec![unwrap!(ws2p_module.my_head.clone())]), + ); + // Send my head to all connections + let my_json_head = serializer::serialize_head(unwrap!(ws2p_module.my_head.clone())); + trace!("Send my HEAD: {:#?}", my_json_head); + let _results: Result<(), ws::Error> = ws2p_module + .websockets + .iter_mut() + .map(|ws| { + (ws.1).0.send(Message::text( + json!({ + "name": "HEAD", + "body": { + "heads": [my_json_head] + } + }) + .to_string(), + )) + }) + .collect(); + } + BlockchainEvent::RevertBlocks(ref _blocks) => {} + _ => {} + } + } +} diff --git a/lib/modules/ws2p-v1-legacy/src/events/sent.rs b/lib/modules/ws2p-v1-legacy/src/events/sent.rs new file mode 100644 index 0000000000000000000000000000000000000000..bb5a49b6b86efbd31a3df4fdca1cd3f14e7dfa80 --- /dev/null +++ b/lib/modules/ws2p-v1-legacy/src/events/sent.rs @@ -0,0 +1,59 @@ +// Copyright (C) 2018 The Durs 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/>. + +//! Sub-module managing the events emitted by the blockchain module. + +use crate::WS2PModule; +use duniter_module::{ModuleEvent, RouterThreadMessage}; +use duniter_network::documents::BlockchainDocument; +use duniter_network::events::NetworkEvent; +use durs_message::events::DursEvent; +use durs_message::*; + +pub fn send_network_events(ws2p_module: &mut WS2PModule, events: Vec<NetworkEvent>) { + for event in events { + send_network_event(ws2p_module, event); + } +} + +pub fn send_network_event(ws2p_module: &mut WS2PModule, event: NetworkEvent) { + let module_event = match event { + NetworkEvent::ConnectionStateChange(_, _, _, _) => { + ModuleEvent::ConnectionsChangeNodeNetwork + } + NetworkEvent::ReceiveBlocks(_) => ModuleEvent::NewBlockFromNetwork, + NetworkEvent::ReceiveDocuments(ref network_docs) => { + if !network_docs.is_empty() { + match network_docs[0] { + BlockchainDocument::Block(_) => ModuleEvent::NewBlockFromNetwork, + BlockchainDocument::Transaction(_) => ModuleEvent::NewTxFromNetwork, + _ => ModuleEvent::NewWotDocFromNetwork, + } + } else { + return; + } + } + NetworkEvent::ReceiveHeads(_) => ModuleEvent::NewValidHeadFromNetwork, + NetworkEvent::ReceivePeers(_) => ModuleEvent::NewValidPeerFromNodeNetwork, + NetworkEvent::NewSelfPeer(_) => ModuleEvent::NewSelfPeer, + }; + ws2p_module + .router_sender + .send(RouterThreadMessage::ModuleMessage(DursMsg::Event { + event_type: module_event, + event_content: DursEvent::NetworkEvent(event), + })) + .expect("Fail to send network event to router !"); +} diff --git a/lib/modules/ws2p-v1-legacy/heads.rs b/lib/modules/ws2p-v1-legacy/src/heads.rs similarity index 100% rename from lib/modules/ws2p-v1-legacy/heads.rs rename to lib/modules/ws2p-v1-legacy/src/heads.rs diff --git a/lib/modules/ws2p-v1-legacy/src/lib.rs b/lib/modules/ws2p-v1-legacy/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..c19854b3f2a4dc6cd1960ff93bcd693f1bc69565 --- /dev/null +++ b/lib/modules/ws2p-v1-legacy/src/lib.rs @@ -0,0 +1,1075 @@ +// Copyright (C) 2018 The Durs 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/>. + +//! WebSocketToPeer API for the Durs project. + +#![deny( + missing_debug_implementations, + missing_copy_implementations, + trivial_casts, + unsafe_code, + unstable_features, + unused_import_braces, + unused_qualifications +)] +#![recursion_limit = "256"] + +#[macro_use] +extern crate log; +#[macro_use] +extern crate serde_json; +#[macro_use] +extern crate structopt; + +mod ack_message; +mod connect_message; +pub mod constants; +mod events; +mod heads; +mod ok_message; +pub mod parsers; +mod requests; +mod responses; +pub mod serializer; +pub mod ws2p_db; +pub mod ws_connections; + +use crate::ack_message::WS2PAckMessageV1; +use crate::connect_message::WS2PConnectMessageV1; +use crate::constants::*; +use crate::ok_message::WS2POkMessageV1; +use crate::parsers::blocks::parse_json_block; +use crate::requests::sent::send_dal_request; +use crate::ws2p_db::DbEndpoint; +use crate::ws_connections::messages::WS2PConnectionMessage; +use crate::ws_connections::states::WS2PConnectionState; +use crate::ws_connections::*; +use dubp_documents::Blockstamp; +use duniter_conf::DuRsConf; +use duniter_module::*; +use duniter_network::cli::sync::SyncOpt; +use duniter_network::documents::*; +use duniter_network::events::*; +use duniter_network::requests::*; +use duniter_network::*; +use dup_crypto::keys::*; +use durs_common_tools::fatal_error; +use durs_message::events::*; +use durs_message::requests::*; +use durs_message::responses::*; +use durs_message::*; +use durs_network_documents::network_endpoint::*; +use durs_network_documents::network_head::*; +use durs_network_documents::*; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::fs; +use std::ops::Deref; +use std::path::PathBuf; +use std::sync::mpsc; +use std::thread; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; +use unwrap::unwrap; +use ws::Message; + +#[inline] +#[cfg(not(feature = "ssl"))] +pub fn ssl() -> bool { + false +} +#[inline] +#[cfg(feature = "ssl")] +pub fn ssl() -> bool { + true +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +/// WS2P Configuration +pub struct WS2PConf { + /// Limit of outcoming connections + pub outcoming_quota: usize, + /// Default WS2P endpoints provides by configuration file + pub sync_endpoints: Vec<EndpointV1>, +} + +impl Default for WS2PConf { + fn default() -> Self { + WS2PConf { + outcoming_quota: *WS2P_DEFAULT_OUTCOMING_QUOTA, + sync_endpoints: vec![ + unwrap!(EndpointV1::parse_from_raw( + "WS2P c1c39a0a ts.g1.librelois.fr 443 /ws2p", + PubKey::Ed25519(unwrap!(ed25519::PublicKey::from_base58( + "D9D2zaJoWYWveii1JRYLVK3J4Z7ZH3QczoKrnQeiM6mx", + )),), + 0, + 0, + )), + unwrap!(EndpointV1::parse_from_raw( + "WS2P fb17fcd4 g1.duniter.fr 443 /ws2p", + PubKey::Ed25519(unwrap!(ed25519::PublicKey::from_base58( + "38MEAZN68Pz1DTvT3tqgxx4yQP6snJCQhPqEFxbDk4aE", + ))), + 0, + 0, + )), + unwrap!(EndpointV1::parse_from_raw( + "WS2P 7b33becd g1.nordstrom.duniter.org 443 /ws2p", + PubKey::Ed25519(unwrap!(ed25519::PublicKey::from_base58( + "DWoSCRLQyQ48dLxUGr1MDKg4NFcbPbC56LN2hJjCCPpZ", + ))), + 0, + 0, + )), + unwrap!(EndpointV1::parse_from_raw( + "WS2P dff60418 duniter.normandie-libre.fr 443 /ws2p", + PubKey::Ed25519(unwrap!(ed25519::PublicKey::from_base58( + "8t6Di3pLxxoTEfjXHjF49pNpjSTXuGEQ6BpkT75CkNb2", + ))), + 0, + 0, + )), + ], + } + } +} + +#[derive(Debug)] +/// Store a Signal receive from network (after message treatment) +pub enum WS2PSignal { + /// Receive a websocket error from a connextion. `NodeFullId` store the identifier of connection. + WSError(NodeFullId), + /// A new connection is successfully established with `NodeFullId`. + ConnectionEstablished(NodeFullId), + NegociationTimeout(NodeFullId), + Timeout(NodeFullId), + DalRequest(NodeFullId, ModuleReqId, serde_json::Value), + PeerCard(NodeFullId, serde_json::Value, Vec<EndpointV1>), + Heads(NodeFullId, Vec<NetworkHead>), + Document(NodeFullId, BlockchainDocument), + ReqResponse( + ModuleReqId, + OldNetworkRequest, + NodeFullId, + serde_json::Value, + ), + Empty, + NoConnection, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum NetworkConsensusError { + InsufficientData(usize), + Fork, +} + +#[derive(Debug)] +pub enum SendRequestError { + RequestTypeMustNotBeTransmitted(), + WSError(usize, Vec<ws::Error>), +} + +#[derive(Debug)] +pub struct WS2PModule { + pub conf: WS2PConf, + pub count_dal_requests: u32, + pub currency: Option<String>, + pub current_blockstamp: Blockstamp, + pub ep_file_path: PathBuf, + pub heads_cache: HashMap<NodeFullId, NetworkHead>, + pub key_pair: KeyPairEnum, + pub main_thread_channel: ( + mpsc::Sender<WS2PThreadSignal>, + mpsc::Receiver<WS2PThreadSignal>, + ), + pub my_head: Option<NetworkHead>, + pub next_receiver: usize, + pub node_id: NodeId, + pub requests_awaiting_response: + HashMap<ModuleReqId, (OldNetworkRequest, NodeFullId, SystemTime)>, + pub router_sender: mpsc::Sender<RouterThreadMessage<DursMsg>>, + pub soft_name: &'static str, + pub soft_version: &'static str, + pub ssl: bool, + pub websockets: HashMap<NodeFullId, WsSender>, + pub ws2p_endpoints: HashMap<NodeFullId, DbEndpoint>, + pub uids_cache: HashMap<PubKey, String>, +} + +impl WS2PModule { + pub fn new( + soft_meta_datas: &SoftwareMetaDatas<DuRsConf>, + conf: WS2PConf, + ep_file_path: PathBuf, + key_pair: KeyPairEnum, + router_sender: mpsc::Sender<RouterThreadMessage<DursMsg>>, + ) -> WS2PModule { + WS2PModule { + router_sender, + key_pair, + currency: None, + current_blockstamp: Blockstamp::default(), + conf, + ep_file_path, + soft_name: soft_meta_datas.soft_name, + soft_version: soft_meta_datas.soft_version, + ssl: ssl(), + node_id: NodeId(soft_meta_datas.conf.my_node_id()), + main_thread_channel: mpsc::channel(), + next_receiver: 0, + ws2p_endpoints: HashMap::new(), + websockets: HashMap::new(), + requests_awaiting_response: HashMap::new(), + heads_cache: HashMap::new(), + my_head: None, + uids_cache: HashMap::new(), + count_dal_requests: 0, + } + } +} + +#[derive(Debug)] +pub enum WS2PThreadSignal { + DursMsg(Box<DursMsg>), + WS2PConnectionMessage(WS2PConnectionMessage), +} + +#[derive(Copy, Clone, Debug)] +/// Error when parsing WS2P message +pub struct WS2PMsgParseErr {} + +impl From<dup_crypto::bases::BaseConvertionError> for WS2PMsgParseErr { + fn from(_: dup_crypto::bases::BaseConvertionError) -> Self { + WS2PMsgParseErr {} + } +} + +pub trait WS2PMessage: Sized { + fn parse(v: &serde_json::Value, currency: String) -> Result<Self, WS2PMsgParseErr>; + fn to_raw(&self) -> String; + fn sign(&self, key_pair: KeyPairEnum) -> Sig { + key_pair.sign(self.to_raw().as_bytes()) + } + fn verify(&self) -> bool; + //fn parse_and_verify(v: serde_json::Value, currency: String) -> bool; +} + +#[derive(Debug)] +/// WS2PFeaturesParseError +pub enum WS2PFeaturesParseError { + /// UnknowApiFeature + UnknowApiFeature(String), +} + +impl ApiModule<DuRsConf, DursMsg> for WS2PModule { + type ParseErr = WS2PFeaturesParseError; + /// Parse raw api features + fn parse_raw_api_features(str_features: &str) -> Result<ApiFeatures, Self::ParseErr> { + let str_features: Vec<&str> = str_features.split(' ').collect(); + let mut api_features = Vec::with_capacity(0); + for str_feature in str_features { + match str_feature { + "DEF" => api_features[0] += 1u8, + "LOW" => api_features[0] += 2u8, + "ABF" => api_features[0] += 4u8, + _ => { + return Err(WS2PFeaturesParseError::UnknowApiFeature(String::from( + str_feature, + ))); + } + } + } + Ok(ApiFeatures(api_features)) + } +} + +impl NetworkModule<DuRsConf, DursMsg> for WS2PModule { + fn sync( + _soft_meta_datas: &SoftwareMetaDatas<DuRsConf>, + _keys: RequiredKeysContent, + _conf: WS2PConf, + _main_sender: mpsc::Sender<RouterThreadMessage<DursMsg>>, + _sync_params: SyncOpt, + ) -> Result<(), ModuleInitError> { + println!("Downlaod blockchain from network..."); + println!("Error : not yet implemented !"); + Ok(()) + } +} + +#[derive(StructOpt, Debug, Copy, Clone)] +#[structopt( + name = "ws2p", + raw(setting = "structopt::clap::AppSettings::ColoredHelp") +)] +/// WS2Pv1 subcommand options +pub struct WS2POpt {} + +impl DursModule<DuRsConf, DursMsg> for WS2PModule { + type ModuleConf = WS2PConf; + type ModuleOpt = WS2POpt; + + fn name() -> ModuleStaticName { + ModuleStaticName("ws2p") + } + fn priority() -> ModulePriority { + ModulePriority::Essential() + } + fn ask_required_keys() -> RequiredKeys { + RequiredKeys::NetworkKeyPair() + } + fn have_subcommand() -> bool { + true + } + fn exec_subcommand( + _soft_meta_datas: &SoftwareMetaDatas<DuRsConf>, + _keys: RequiredKeysContent, + _module_conf: Self::ModuleConf, + _subcommand_args: WS2POpt, + ) { + println!("Succesfully exec ws2p subcommand !") + } + fn start( + soft_meta_datas: &SoftwareMetaDatas<DuRsConf>, + keys: RequiredKeysContent, + conf: WS2PConf, + router_sender: mpsc::Sender<RouterThreadMessage<DursMsg>>, + load_conf_only: bool, + ) -> Result<(), ModuleInitError> { + // Get start time + let start_time = SystemTime::now(); + + // Get key_pair + let key_pair = if let RequiredKeysContent::NetworkKeyPair(key_pair) = keys { + key_pair + } else { + return Err(ModuleInitError::FailToLoadConf( + "WS2PModule fatal error at load_conf() : keys != NetworkKeyPair", + )); + }; + + // load conf + let mut ws2p_endpoints = HashMap::new(); + for ep in &conf.sync_endpoints { + info!("Load sync endpoint {}", ep.raw_endpoint); + let node_full_id = ep + .node_full_id() + .expect("Fail to get endpoint node_full_id"); + ws2p_endpoints.insert( + node_full_id, + DbEndpoint { + ep: ep.clone(), + state: WS2PConnectionState::Close, + last_check: 0, + }, + ); + } + + // Get endpoints file path + let mut ep_file_path = + duniter_conf::datas_path(&soft_meta_datas.profile, &soft_meta_datas.conf.currency()); + ep_file_path.push("ws2pv1"); + if !ep_file_path.exists() { + fs::create_dir(ep_file_path.as_path()).expect("Impossible to create ws2pv1 dir !"); + } + ep_file_path.push("endpoints.bin"); + + // Define WS2PModule + let mut ws2p_module = WS2PModule::new( + soft_meta_datas, + conf, + ep_file_path.clone(), + key_pair, + router_sender.clone(), + ); + ws2p_module.currency = Some(soft_meta_datas.conf.currency().to_string()); + ws2p_module.ws2p_endpoints = ws2p_endpoints; + + // Create ws2p main thread channel + let ws2p_sender_clone = ws2p_module.main_thread_channel.0.clone(); + + // Get ws2p endpoints in file + info!("TMP: WS2P SSL={}", ssl()); + let count; + match ws2p_db::get_endpoints(&ep_file_path) { + Ok(ws2p_enpoints) => { + let ws2p_enpoints = ws2p_enpoints + .into_iter() + .filter(|(_, dal_ep)| cfg!(feature = "ssl") || dal_ep.ep.port != 443) + .collect::<Vec<(NodeFullId, DbEndpoint)>>(); + count = ws2p_enpoints.len(); + ws2p_module.ws2p_endpoints.extend(ws2p_enpoints); + } + Err(err) => fatal_error!("WS2Pv1: fail to load endpoints from DB: {:?}", err), + } + info!("Load {} endpoints from DB !", count); + + // Stop here in load_conf_only mode + if load_conf_only { + return Ok(()); + } + + // Create proxy channel + let (proxy_sender, proxy_receiver): (mpsc::Sender<DursMsg>, mpsc::Receiver<DursMsg>) = + mpsc::channel(); + let proxy_sender_clone = proxy_sender.clone(); + + // Launch a proxy thread that transform DursMsg to WS2PThreadSignal(DursMsg) + thread::spawn(move || { + // Send proxy sender to main + router_sender + .send(RouterThreadMessage::ModuleRegistration( + WS2PModule::name(), + proxy_sender_clone, + vec![ModuleRole::InterNodesNetwork], + vec![ + ModuleEvent::NewValidBlock, + ModuleEvent::NewWotDocInPool, + ModuleEvent::NewTxinPool, + ], + vec![], + vec![], + )) + .expect("Fatal error : ws2p module fail to send is sender channel !"); + debug!("Send ws2p sender to main thread."); + loop { + match proxy_receiver.recv() { + Ok(message) => { + let stop = if let DursMsg::Stop = message { + true + } else { + false + }; + ws2p_sender_clone + .send(WS2PThreadSignal::DursMsg(Box::new(message))) + .expect( + "Fatal error : fail to relay DursMsgContent to ws2p main thread !", + ); + if stop { + break; + }; + } + Err(e) => panic!(format!("{}", e)), + } + } + }); + + // Request current blockstamp + send_dal_request(&mut ws2p_module, &BlockchainRequest::CurrentBlockstamp()); + + // Start + connect_to_know_endpoints(&mut ws2p_module); + ws2p_module.main_loop(start_time, soft_meta_datas); + + Ok(()) + } +} + +impl WS2PModule { + fn main_loop(mut self, start_time: SystemTime, soft_meta_datas: &SoftwareMetaDatas<DuRsConf>) { + // Initialize variables + let key_pair = self.key_pair; + let mut last_ws2p_connecting_wave = SystemTime::now(); + let mut last_ws2p_state_print = SystemTime::now(); + let mut last_ws2p_endpoints_write = SystemTime::now(); + let mut endpoints_to_update_status: HashMap<NodeFullId, SystemTime> = HashMap::new(); + let mut last_identities_request = UNIX_EPOCH; + + loop { + match self + .main_thread_channel + .1 + .recv_timeout(Duration::from_millis(200)) + { + Ok(message) => match message { + WS2PThreadSignal::DursMsg(ref durs_mesage) => { + match *durs_mesage.deref() { + DursMsg::Stop => break, + DursMsg::Request { + ref req_content, .. + } => requests::received::receive_req(&mut self, req_content), + DursMsg::Event { + ref event_type, + ref event_content, + .. + } => events::received::receive_event( + &mut self, + *event_type, + event_content, + ), + DursMsg::Response { + ref res_content, .. + } => { + if let DursResContent::BlockchainResponse(ref bc_res) = *res_content + { + match *bc_res.deref() { + BlockchainResponse::CurrentBlockstamp( + ref _requester_id, + ref current_blockstamp_, + ) => { + debug!( + "WS2PModule : receive DALResBc::CurrentBlockstamp({})", + self.current_blockstamp + ); + self.current_blockstamp = *current_blockstamp_; + if self.my_head.is_none() { + self.my_head = Some(heads::generate_my_head( + &key_pair, + NodeId(soft_meta_datas.conf.my_node_id()), + soft_meta_datas.soft_name, + soft_meta_datas.soft_version, + &self.current_blockstamp, + None, + )); + } + let event = + NetworkEvent::ReceiveHeads(vec![unwrap!(self + .my_head + .clone())]); + events::sent::send_network_event(&mut self, event); + } + BlockchainResponse::UIDs(ref _req_id, ref uids) => { + // Add uids to heads + for head in self.heads_cache.values_mut() { + if let Some(uid_option) = uids.get(&head.pubkey()) { + if let Some(ref uid) = *uid_option { + head.set_uid(uid); + self.uids_cache + .insert(head.pubkey(), uid.to_string()); + } else { + self.uids_cache.remove(&head.pubkey()); + } + } + } + // Resent heads to other modules + let event = NetworkEvent::ReceiveHeads( + self.heads_cache.values().cloned().collect(), + ); + events::sent::send_network_event(&mut self, event); + // Resent to other modules connections that match receive uids + let events = self.ws2p_endpoints + .iter() + .filter_map(|(node_full_id, DbEndpoint { ep, state, .. })| { + if let Some(uid_option) = uids.get(&node_full_id.1) { + Some(NetworkEvent::ConnectionStateChange( + *node_full_id, + *state as u32, + uid_option.clone(), + ep.get_url(false, false) + .expect("Endpoint unreachable !"), + )) + } else { + None + } + }) + .collect(); + events::sent::send_network_events(&mut self, events); + } + _ => {} // Others BlockchainResponse variants + } + } + } + _ => {} // Others DursMsg variants + } + } + WS2PThreadSignal::WS2PConnectionMessage(ws2p_conn_message) => { + match crate::ws_connections::messages::ws2p_conn_message_pretreatment( + &mut self, + ws2p_conn_message, + ) { + WS2PSignal::NoConnection => { + warn!("WS2PSignal::NoConnection"); + } + WS2PSignal::ConnectionEstablished(ws2p_full_id) => { + let req_id = + ModuleReqId(self.requests_awaiting_response.len() as u32); + let module_id = WS2PModule::name(); + debug!("WS2P: send req to: ({:?})", ws2p_full_id); + let _current_request_result = + ws_connections::requests::sent::send_request_to_specific_node( + &mut self, + &ws2p_full_id, + &OldNetworkRequest::GetCurrent(ModuleReqFullId( + module_id, req_id, + )), + ); + if self.uids_cache.get(&ws2p_full_id.1).is_none() { + send_dal_request( + &mut self, + &BlockchainRequest::UIDs(vec![ws2p_full_id.1]), + ); + } + let event = NetworkEvent::ConnectionStateChange( + ws2p_full_id, + WS2PConnectionState::Established as u32, + self.uids_cache.get(&ws2p_full_id.1).cloned(), + self.ws2p_endpoints[&ws2p_full_id] + .ep + .get_url(false, false) + .expect("Endpoint unreachable !"), + ); + events::sent::send_network_event(&mut self, event); + } + WS2PSignal::WSError(ws2p_full_id) => { + endpoints_to_update_status.insert(ws2p_full_id, SystemTime::now()); + close_connection( + &mut self, + &ws2p_full_id, + WS2PCloseConnectionReason::WsError, + ); + let event = NetworkEvent::ConnectionStateChange( + ws2p_full_id, + WS2PConnectionState::WSError as u32, + self.uids_cache.get(&ws2p_full_id.1).cloned(), + self.ws2p_endpoints[&ws2p_full_id] + .ep + .get_url(false, false) + .expect("Endpoint unreachable !"), + ); + events::sent::send_network_event(&mut self, event); + } + WS2PSignal::NegociationTimeout(ws2p_full_id) => { + endpoints_to_update_status.insert(ws2p_full_id, SystemTime::now()); + let event = NetworkEvent::ConnectionStateChange( + ws2p_full_id, + WS2PConnectionState::Denial as u32, + self.uids_cache.get(&ws2p_full_id.1).cloned(), + self.ws2p_endpoints[&ws2p_full_id] + .ep + .get_url(false, false) + .expect("Endpoint unreachable !"), + ); + events::sent::send_network_event(&mut self, event); + } + WS2PSignal::Timeout(ws2p_full_id) => { + endpoints_to_update_status.insert(ws2p_full_id, SystemTime::now()); + let event = NetworkEvent::ConnectionStateChange( + ws2p_full_id, + WS2PConnectionState::Close as u32, + self.uids_cache.get(&ws2p_full_id.1).cloned(), + self.ws2p_endpoints[&ws2p_full_id] + .ep + .get_url(false, false) + .expect("Endpoint unreachable !"), + ); + events::sent::send_network_event(&mut self, event); + } + WS2PSignal::PeerCard(_ws2p_full_id, _peer_card, ws2p_endpoints) => { + //trace!("WS2PSignal::PeerCard({})", ws2p_full_id); + //self.send_network_event(NetworkEvent::ReceivePeers(_)); + for ep in ws2p_endpoints { + match self.ws2p_endpoints.get( + &ep.node_full_id() + .expect("WS2P: Fail to get ep.node_full_id() !"), + ) { + Some(_) => {} + None => { + if let Some(_api) = + ws2p_db::string_to_api(&ep.api.0.clone()) + { + endpoints_to_update_status.insert( + ep.node_full_id().expect( + "WS2P: Fail to get ep.node_full_id() !", + ), + SystemTime::now(), + ); + } + if cfg!(feature = "ssl") || ep.port != 443 { + connect_to(&mut self, &ep); + } + } + }; + } + } + WS2PSignal::Heads(ws2p_full_id, heads) => { + trace!("WS2PSignal::Heads({}, {:?})", ws2p_full_id, heads.len()); + send_dal_request( + &mut self, + &BlockchainRequest::UIDs( + heads.iter().map(NetworkHead::pubkey).collect(), + ), + ); + let event = NetworkEvent::ReceiveHeads( + heads + .iter() + .map(|head| { + let mut new_head = head.clone(); + if let Some(uid) = self.uids_cache.get(&head.pubkey()) { + new_head.set_uid(uid); + } + new_head + }) + .collect(), + ); + events::sent::send_network_event(&mut self, event); + } + WS2PSignal::Document(ws2p_full_id, network_doc) => { + trace!("WS2PSignal::Document({})", ws2p_full_id); + events::sent::send_network_event( + &mut self, + NetworkEvent::ReceiveDocuments(vec![network_doc]), + ); + } + WS2PSignal::ReqResponse(req_id, req, recipient_full_id, response) => { + match req { + OldNetworkRequest::GetCurrent(ref _req_id) => { + info!( + "WS2PSignal::ReceiveCurrent({}, {:?})", + req_id.0, req + ); + if let Some(block) = parse_json_block(&response) { + crate::responses::sent::send_network_req_response( + &self, + req.get_req_full_id().0, + req.get_req_full_id().1, + NetworkResponse::CurrentBlock( + ModuleReqFullId(WS2PModule::name(), req_id), + recipient_full_id, + Box::new(block), + ), + ); + } + } + OldNetworkRequest::GetBlocks(ref _req_id, count, from) => { + info!( + "WS2PSignal::ReceiveChunk({}, {} blocks from {})", + req_id.0, count, from + ); + if response.is_array() { + let mut chunk = Vec::new(); + for json_block in unwrap!(response.as_array()) { + if let Some(block) = parse_json_block(json_block) { + chunk.push(block); + } else { + warn!("WS2PModule: Error : fail to parse one json block !"); + } + } + debug!("Send chunk to followers : {}", from); + events::sent::send_network_event( + &mut self, + NetworkEvent::ReceiveBlocks(chunk), + ); + } + } + OldNetworkRequest::GetRequirementsPending( + _req_id, + min_cert, + ) => { + info!( + "WS2PSignal::ReceiveRequirementsPending({}, {})", + req_id.0, min_cert + ); + debug!("----------------------------------------"); + debug!("- BEGIN IDENTITIES PENDING -"); + debug!("----------------------------------------"); + debug!("{:#?}", response); + debug!("----------------------------------------"); + debug!("- END IDENTITIES PENDING -"); + debug!("----------------------------------------"); + } + _ => {} + } + } + WS2PSignal::Empty => {} + _ => {} + } + } + }, + Err(e) => match e { + mpsc::RecvTimeoutError::Disconnected => { + panic!("Disconnected ws2p module !"); + } + mpsc::RecvTimeoutError::Timeout => {} + }, + } + if unwrap!(SystemTime::now().duration_since(last_ws2p_endpoints_write)) + > Duration::new(*DURATION_BETWEEN_2_ENDPOINTS_SAVING, 0) + { + last_ws2p_endpoints_write = SystemTime::now(); + if let Err(err) = ws2p_db::write_endpoints(&self.ep_file_path, &self.ws2p_endpoints) + { + fatal_error!("WS2P1: Fail to write endpoints in DB : {:?}", err); + } + } + if unwrap!(SystemTime::now().duration_since(last_ws2p_state_print)) + > Duration::new(*WS2P_GENERAL_STATE_INTERVAL, 0) + { + last_ws2p_state_print = SystemTime::now(); + let mut connected_nodes = Vec::new(); + for (k, DbEndpoint { state, .. }) in self.ws2p_endpoints.clone() { + if let WS2PConnectionState::Established = state { + connected_nodes.push(k); + } + } + // Print current_blockstamp + info!( + "WS2PModule : current_blockstamp() = {:?}", + self.current_blockstamp + ); + // New WS2P connection wave + if connected_nodes.len() < self.conf.clone().outcoming_quota + && (unwrap!(SystemTime::now().duration_since(last_ws2p_connecting_wave)) + > Duration::new(*WS2P_OUTCOMING_INTERVAL, 0) + || (unwrap!(SystemTime::now().duration_since(last_ws2p_connecting_wave)) + > Duration::new(*WS2P_OUTCOMING_INTERVAL_AT_STARTUP, 0) + && unwrap!(SystemTime::now().duration_since(start_time)) + < Duration::new(*WS2P_OUTCOMING_INTERVAL, 0))) + { + last_ws2p_connecting_wave = SystemTime::now(); + info!("Connected to know endpoints..."); + connect_to_know_endpoints(&mut self); + } + // Request pending_identities from network + if unwrap!(SystemTime::now().duration_since(last_identities_request)) + > Duration::new(*PENDING_IDENTITIES_REQUEST_INTERVAL, 0) + && unwrap!(SystemTime::now().duration_since(start_time)) > Duration::new(10, 0) + { + /*info!("get pending_identities from all connections..."); + let _blocks_request_result = self.send_request_to_all_connections( + &OldNetworkRequest::GetRequirementsPending(ModuleReqId(0 as u32), 5), + );*/ + last_identities_request = SystemTime::now(); + } + // .. + // Request current blockstamp + send_dal_request(&mut self, &BlockchainRequest::CurrentBlockstamp()); + } + } + } +} + +#[cfg(test)] +mod tests { + use super::parsers::blocks::parse_json_block; + use super::*; + use crate::ws_connections::requests::sent::network_request_to_json; + use dubp_documents::documents::block::BlockDocument; + use duniter_module::DursModule; + + #[test] + fn test_parse_json_block() { + let json_block = json!({ + "fork": false, + "version": 10, + "nonce": 10500000059239 as u64, + "number": 109966, + "powMin": 88, + "time": 1523300656, + "medianTime": 1523295259, + "membersCount": 933, + "monetaryMass": 146881563, + "unitbase": 0, + "issuersCount": 44, + "issuersFrame": 221, + "issuersFrameVar": 0, + "currency": "g1", + "issuer": "GRBPV3Y7PQnB9LaZhSGuS3BqBJbSHyibzYq65kTh1nQ4", + "signature": "GCg2Lti3TdxWlhA8JF8pRI+dRQ0XZVtcC4BqO/COTpjTQFdWG6qmUNVvdeYCtR/lu1JQe3N/IhrbyV6L/6I+Cg==", + "hash": "000000EF5B2AA849F4C3AF3D35E1284EA1F34A9F617EA806CE8371619023DC74", + "parameters": "", + "previousHash": "000004C00602F8A27AE078DE6351C0DDA1EA0974A78D2BEFA7DFBE7B7C3146FD", + "previousIssuer": "5SwfQubSat5SunNafCsunEGTY93nVM4kLSsuprNqQb6S", + "inner_hash": "61F02B1A6AE2E4B9A1FD66CE673258B4B21C0076795571EE3C9DC440DD06C46C", + "dividend": null, + "identities": [], + "joiners": [], + "actives": [], + "leavers": [], + "revoked": [], + "excluded": [], + "certifications": [ + "Hm5qjaNuHogNRdGZ4vgnLA9DMZVUu5YWzVup5mubuxCc:8AmdBsimcLziXaCS4AcVUfPx7rkjeic7482dLbBkuZw6:109964:yHKBGMeuxyIqFb295gVNK6neRC+U0tmsX1Zed3TLjS3ZZHYYycE1piLcYsTKll4ifNVp6rm+hd/CLdHYB+29CA==", + "BncjgJeFpGsMCCsUfzNLEexjsbuX3V2mg9P67ov2LkwK:DyBUBNpzpfvjtwYYSaVMM6ST6t2DNg3NCE9CU9bRQFhF:105864:cJEGW9WxJwlMA2+4LNAK4YieyseUy1WIkFh1YLYD+JJtJEoCSnIQRXzhiAoRpGaj0bRz8sTpwI6PRkuVoDJJDQ==" + ], + "transactions": [ + { + "version": 10, + "currency": "g1", + "locktime": 0, + "hash": "80FE1E83DC4D0B722CA5F8363EFC6A3E29071032EBB71C1E0DF8D4FEA589C698", + "blockstamp": "109964-00000168105D4A8A8BC8C0DC70033F45ABE472782C75A7F2074D0F4D4A3B7B2B", + "blockstampTime": 0, + "issuers": [ + "6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT" + ], + "inputs": [ + "1001:0:D:6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT:98284", + "1001:0:D:6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT:98519", + "1001:0:D:6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT:98779", + "1001:0:D:6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT:99054", + "1001:0:D:6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT:99326", + "1001:0:D:6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT:99599", + "1001:0:D:6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT:99884", + "1001:0:D:6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT:100174", + "1001:0:D:6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT:100469", + "1001:0:D:6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT:100746", + "1001:0:D:6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT:101036", + "1001:0:D:6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT:101327" + ], + "outputs": [ + "12000:0:SIG(HmH5beJqKGMeotcQUrSW7Wo5tKvAksHmfYXfiSQ9EbWz)", + "12:0:SIG(6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT)" + ], + "unlocks": [ + "0:SIG(0)", + "1:SIG(0)", + "2:SIG(0)", + "3:SIG(0)", + "4:SIG(0)", + "5:SIG(0)", + "6:SIG(0)", + "7:SIG(0)", + "8:SIG(0)", + "9:SIG(0)", + "10:SIG(0)", + "11:SIG(0)" + ], + "signatures": [ + "MZxoKxYgwufh/s5mwLCsYEZXtIsP1hEKCyAzLipJsvCbR9xj7wXUw0C/ahwvZfBtR7+QVPIfLmwYEol1JcHjDw==" + ], + "comment": "Adhesion 2018" + }, + { + "version": 10, + "currency": "g1", + "locktime": 0, + "hash": "B80507412B35BD5EB437AE0D3EB97E60E3A4974F5CDEA1AF7E2127C0E943481F", + "blockstamp": "109964-00000168105D4A8A8BC8C0DC70033F45ABE472782C75A7F2074D0F4D4A3B7B2B", + "blockstampTime": 0, + "issuers": [ + "8gundJEbfm73Kx3jjw8YivJyz8qD2igjf6baCBLFCxPU" + ], + "inputs": [ + "1001:0:D:8gundJEbfm73Kx3jjw8YivJyz8qD2igjf6baCBLFCxPU:91560", + "1001:0:D:8gundJEbfm73Kx3jjw8YivJyz8qD2igjf6baCBLFCxPU:91850", + "1001:0:D:8gundJEbfm73Kx3jjw8YivJyz8qD2igjf6baCBLFCxPU:92111", + "1001:0:D:8gundJEbfm73Kx3jjw8YivJyz8qD2igjf6baCBLFCxPU:92385", + "1001:0:D:8gundJEbfm73Kx3jjw8YivJyz8qD2igjf6baCBLFCxPU:92635" + ], + "outputs": [ + "5000:0:SIG(BzHnbec1Gov7dLSt1EzJS7vikoQCECeuvZs4wamZAcT1)", + "5:0:SIG(8gundJEbfm73Kx3jjw8YivJyz8qD2igjf6baCBLFCxPU)" + ], + "unlocks": [ + "0:SIG(0)", + "1:SIG(0)", + "2:SIG(0)", + "3:SIG(0)", + "4:SIG(0)" + ], + "signatures": [ + "A+ukwRvLWs1gZQ0KAqAnknEgmRQHdrnOvNuBx/WZqje17BAPrVxSxKpqwU6MiajU+ppigsYp6Bu0FdPf/tGnCQ==" + ], + "comment": "" + }, + { + "version": 10, + "currency": "g1", + "locktime": 0, + "hash": "D8970E6629C0381A78534EEDD86803E9215A7EC4C494BAEA79EB19425F9B4D31", + "blockstamp": "109964-00000168105D4A8A8BC8C0DC70033F45ABE472782C75A7F2074D0F4D4A3B7B2B", + "blockstampTime": 0, + "issuers": [ + "FnSXE7QyBfs4ozoYAt5NEewWhHEPorf38cNXu3kX9xsg" + ], + "inputs": [ + "1000:0:D:FnSXE7QyBfs4ozoYAt5NEewWhHEPorf38cNXu3kX9xsg:36597", + "1000:0:D:FnSXE7QyBfs4ozoYAt5NEewWhHEPorf38cNXu3kX9xsg:36880", + "1000:0:D:FnSXE7QyBfs4ozoYAt5NEewWhHEPorf38cNXu3kX9xsg:37082" + ], + "outputs": [ + "3000:0:SIG(BBC8Rnh4CWN1wBrPLevK7GRFFVDVw7Lu24YNMUmhqoHU)" + ], + "unlocks": [ + "0:SIG(0)", + "1:SIG(0)", + "2:SIG(0)" + ], + "signatures": [ + "OpiF/oQfIigOeAtsteukU0w9FPSELE+BVTxhmsQ8bEeYGlwovG2VF8ZFiJkLLPi6vFuKgwzULJfjNGd97twZCw==" + ], + "comment": "1 billet pour une seance.pour un chouette film" + } + ], + }); + let mut block: BlockDocument = + parse_json_block(&json_block).expect("Fail to parse test json block !"); + assert_eq!( + block + .inner_hash + .expect("Try to get inner_hash of an uncompleted or reduce block !") + .to_hex(), + "61F02B1A6AE2E4B9A1FD66CE673258B4B21C0076795571EE3C9DC440DD06C46C" + ); + block.compute_hash(); + assert_eq!( + block + .hash + .expect("Try to get hash of an uncompleted or reduce block !") + .0 + .to_hex(), + "000000EF5B2AA849F4C3AF3D35E1284EA1F34A9F617EA806CE8371619023DC74" + ); + } + + #[test] + fn ws2p_requests() { + let module_id = WS2PModule::name(); + let request = + OldNetworkRequest::GetBlocks(ModuleReqFullId(module_id, ModuleReqId(58)), 50, 0); + assert_eq!( + network_request_to_json(&request), + json!({ + "reqId": format!("{:x}", 58), + "body": { + "name": "BLOCKS_CHUNK", + "params": { + "count": 50, + "fromNumber": 0 + } + } + }) + ); + assert_eq!( + network_request_to_json(&request).to_string(), + "{\"body\":{\"name\":\"BLOCKS_CHUNK\",\"params\":{\"count\":50,\"fromNumber\":0}},\"reqId\":\"3a\"}" + ); + } + + #[test] + fn ws2p_parse_head() { + let head = json!({ + "message": "WS2POTMIC:HEAD:1:D9D2zaJoWYWveii1JRYLVK3J4Z7ZH3QczoKrnQeiM6mx:104512-0000051B9CE9C1CA89F269375A6751FB88B9E88DE47A36506057E5BFBCFBB276:c1c39a0a:duniter:1.6.21:3", + "sig": "trtK9GXvTdfND995ohWEderpO3NkIqi1X6mBeVvMcaHckq+lIGqjWvJ9t9Vccz5t+VGaSmGUihDl4q6eldIYBw==", + "messageV2": "WS2POTMIC:HEAD:2:D9D2zaJoWYWveii1JRYLVK3J4Z7ZH3QczoKrnQeiM6mx:104512-0000051B9CE9C1CA89F269375A6751FB88B9E88DE47A36506057E5BFBCFBB276:c1c39a0a:duniter:1.6.21:3:25:22", + "sigV2": "x6ehPMuYjGY+z7wEGnJGyMBxMKUdu01RWaF0b0XCtoVjg67cCvT4H0V/Qcxn4bAGqzy5ux2fA7NiI+81bBnqDw==", + "step": 0 + }); + let mut heads_count = 0; + if let Ok(head) = NetworkHead::from_json_value(&head) { + if let NetworkHead::V2(ref head_v2) = head { + heads_count += 1; + assert_eq!( + head_v2.message.to_string(), + String::from("WS2POTMIC:HEAD:1:D9D2zaJoWYWveii1JRYLVK3J4Z7ZH3QczoKrnQeiM6mx:104512-0000051B9CE9C1CA89F269375A6751FB88B9E88DE47A36506057E5BFBCFBB276:c1c39a0a:duniter:1.6.21:3") + ); + } + assert_eq!(head.verify(), true); + } else { + panic!("Fail to parse head !") + } + assert_eq!(heads_count, 1); + } +} diff --git a/lib/modules/ws2p-v1-legacy/ok_message.rs b/lib/modules/ws2p-v1-legacy/src/ok_message.rs similarity index 59% rename from lib/modules/ws2p-v1-legacy/ok_message.rs rename to lib/modules/ws2p-v1-legacy/src/ok_message.rs index 95a63c9dbae39afd26c44fb2919ebe1dbe12e67a..5d5660343ce6ac847fbf90d64f2fe75aa989ab5e 100644 --- a/lib/modules/ws2p-v1-legacy/ok_message.rs +++ b/lib/modules/ws2p-v1-legacy/src/ok_message.rs @@ -1,4 +1,4 @@ -use super::WS2PMessage; +use crate::*; use dup_crypto::keys::*; use serde::ser::{Serialize, SerializeStruct, Serializer}; @@ -11,23 +11,18 @@ pub struct WS2POkMessageV1 { } impl WS2PMessage for WS2POkMessageV1 { - fn parse(v: &serde_json::Value, currency: String) -> Option<Self> { + fn parse(v: &serde_json::Value, currency: String) -> Result<Self, WS2PMsgParseErr> { let signature = match v.get("sig") { - Some(signature) => signature - .as_str() - .expect("Parsing of OK message : fail to convert sig to str") - .to_string(), - None => return None, + Some(signature) => signature.as_str().ok_or(WS2PMsgParseErr {})?.to_string(), + None => return Err(WS2PMsgParseErr {}), }; - let pubkey: PubKey = PubKey::Ed25519( - ed25519::PublicKey::from_base58("969qRJs8KhsnkyzqarpL4RKZGMdVKNbZgu8fhsigM7Lj") - .expect("fail to create default pubkey !"), - ); - let signature: Option<Sig> = Some(Sig::Ed25519( - dup_crypto::keys::Signature::from_base64(&signature) - .expect("fail to parse signature of OK message !"), - )); - Some(WS2POkMessageV1 { + let pubkey: PubKey = PubKey::Ed25519(ed25519::PublicKey::from_base58( + "969qRJs8KhsnkyzqarpL4RKZGMdVKNbZgu8fhsigM7Lj", + )?); + let signature: Option<Sig> = Some(Sig::Ed25519(dup_crypto::keys::Signature::from_base64( + &signature, + )?)); + Ok(WS2POkMessageV1 { currency, pubkey, challenge: "".to_string(), @@ -41,8 +36,11 @@ impl WS2PMessage for WS2POkMessageV1 { ) } fn verify(&self) -> bool { - self.pubkey - .verify(self.to_raw().as_bytes(), &self.signature.unwrap()) + if let Some(sig) = self.signature { + self.pubkey.verify(self.to_raw().as_bytes(), &sig) + } else { + false + } } } diff --git a/lib/modules/ws2p-v1-legacy/parsers/blocks.rs b/lib/modules/ws2p-v1-legacy/src/parsers/blocks.rs similarity index 100% rename from lib/modules/ws2p-v1-legacy/parsers/blocks.rs rename to lib/modules/ws2p-v1-legacy/src/parsers/blocks.rs diff --git a/lib/modules/ws2p-v1-legacy/parsers/excluded.rs b/lib/modules/ws2p-v1-legacy/src/parsers/excluded.rs similarity index 54% rename from lib/modules/ws2p-v1-legacy/parsers/excluded.rs rename to lib/modules/ws2p-v1-legacy/src/parsers/excluded.rs index 8e7dd84b6676ca6dae072635bdabd6a4a6e9cb18..aff225c31fa79fe7ef72c658b496973006f3d37d 100644 --- a/lib/modules/ws2p-v1-legacy/parsers/excluded.rs +++ b/lib/modules/ws2p-v1-legacy/src/parsers/excluded.rs @@ -1,13 +1,14 @@ use dup_crypto::keys::ed25519; use dup_crypto::keys::*; +use unwrap::unwrap; pub fn parse_exclusions(json_datas: &str) -> Option<Vec<PubKey>> { - let raw_exclusions: serde_json::Value = serde_json::from_str(json_datas).unwrap(); + let raw_exclusions: serde_json::Value = unwrap!(serde_json::from_str(json_datas)); if raw_exclusions.is_array() { - Some(parse_exclusions_from_json_value( - raw_exclusions.as_array().unwrap(), - )) + Some(parse_exclusions_from_json_value(unwrap!( + raw_exclusions.as_array() + ))) } else { None } @@ -16,9 +17,9 @@ pub fn parse_exclusions(json_datas: &str) -> Option<Vec<PubKey>> { pub fn parse_exclusions_from_json_value(array_exclusions: &[serde_json::Value]) -> Vec<PubKey> { let mut exclusions: Vec<PubKey> = Vec::new(); for exclusion in array_exclusions.iter() { - exclusions.push(PubKey::Ed25519( - ed25519::PublicKey::from_base58(exclusion.as_str().unwrap()).unwrap(), - )); + exclusions.push(PubKey::Ed25519(unwrap!(ed25519::PublicKey::from_base58( + unwrap!(exclusion.as_str()) + )))); } exclusions } diff --git a/lib/modules/ws2p-v1-legacy/parsers/identities.rs b/lib/modules/ws2p-v1-legacy/src/parsers/identities.rs similarity index 78% rename from lib/modules/ws2p-v1-legacy/parsers/identities.rs rename to lib/modules/ws2p-v1-legacy/src/parsers/identities.rs index 02aab9a9f82b9c130b5453e69fe3677ec5c3c7b4..254214a3fa58208c267ad0a57f527bbe11564045 100644 --- a/lib/modules/ws2p-v1-legacy/parsers/identities.rs +++ b/lib/modules/ws2p-v1-legacy/src/parsers/identities.rs @@ -2,6 +2,7 @@ use dubp_documents::documents::identity::*; use dubp_documents::Blockstamp; use dubp_documents::DocumentBuilder; use dup_crypto::keys::*; +use unwrap::unwrap; #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum IdentityParseError { @@ -9,10 +10,10 @@ pub enum IdentityParseError { } pub fn parse_identities(currency: &str, json_datas: &str) -> Option<Vec<IdentityDocument>> { - let raw_idties: serde_json::Value = serde_json::from_str(json_datas).unwrap(); + let raw_idties: serde_json::Value = unwrap!(serde_json::from_str(json_datas)); if raw_idties.is_array() { return Some( - parse_identities_from_json_value(currency, raw_idties.as_array().unwrap()) + parse_identities_from_json_value(currency, unwrap!(raw_idties.as_array())) .iter() .map(|i| { i.clone() @@ -31,18 +32,18 @@ pub fn parse_identities_from_json_value( array_identities .iter() .map(|idty| { - let idty_datas: Vec<&str> = idty.as_str().unwrap().split(':').collect(); + let idty_datas: Vec<&str> = unwrap!(idty.as_str()).split(':').collect(); if idty_datas.len() == 4 { let idty_doc_builder = IdentityDocumentBuilder { currency, - issuer: &PubKey::Ed25519( - ed25519::PublicKey::from_base58(idty_datas[0]).unwrap(), - ), - blockstamp: &Blockstamp::from_string(idty_datas[2]).unwrap(), + issuer: &PubKey::Ed25519(unwrap!(ed25519::PublicKey::from_base58( + idty_datas[0] + ))), + blockstamp: &unwrap!(Blockstamp::from_string(idty_datas[2])), username: idty_datas[3], }; let idty_sig = - Sig::Ed25519(ed25519::Signature::from_base64(idty_datas[1]).unwrap()); + Sig::Ed25519(unwrap!(ed25519::Signature::from_base64(idty_datas[1]))); //memberships.push(membership_doc_builder.build_with_signature(vec![membership_sig])); Ok(idty_doc_builder.build_with_signature(vec![idty_sig])) } else { @@ -57,7 +58,7 @@ pub fn parse_compact_identity( source: &serde_json::Value, ) -> Option<IdentityDocument> { if source.is_string() { - let idty_elements: Vec<&str> = source.as_str().unwrap().split(':').collect(); + let idty_elements: Vec<&str> = unwrap!(source.as_str()).split(':').collect(); let issuer = match ed25519::PublicKey::from_base58(idty_elements[0]) { Ok(pubkey) => PubKey::Ed25519(pubkey), Err(_) => return None, diff --git a/lib/modules/ws2p-v1-legacy/parsers/memberships.rs b/lib/modules/ws2p-v1-legacy/src/parsers/memberships.rs similarity index 68% rename from lib/modules/ws2p-v1-legacy/parsers/memberships.rs rename to lib/modules/ws2p-v1-legacy/src/parsers/memberships.rs index a04d2d99b24ae6b7951322c8609fbc458b4d21b1..3fb2cf2a9b6e7d9724ec71588ffe6cd5305ea8c1 100644 --- a/lib/modules/ws2p-v1-legacy/parsers/memberships.rs +++ b/lib/modules/ws2p-v1-legacy/src/parsers/memberships.rs @@ -2,6 +2,7 @@ use dubp_documents::documents::membership::*; use dubp_documents::Blockstamp; use dubp_documents::DocumentBuilder; use dup_crypto::keys::*; +use unwrap::unwrap; #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum MembershipParseError { @@ -13,13 +14,13 @@ pub fn parse_memberships( membership_type: MembershipType, json_datas: &str, ) -> Option<Vec<MembershipDocument>> { - let raw_memberships: serde_json::Value = serde_json::from_str(json_datas).unwrap(); + let raw_memberships: serde_json::Value = unwrap!(serde_json::from_str(json_datas)); if raw_memberships.is_array() { return Some( parse_memberships_from_json_value( currency, membership_type, - raw_memberships.as_array().unwrap(), + unwrap!(raw_memberships.as_array()), ) .iter() .map(|m| { @@ -41,20 +42,21 @@ pub fn parse_memberships_from_json_value( array_memberships .iter() .map(|membership| { - let membership_datas: Vec<&str> = membership.as_str().unwrap().split(':').collect(); + let membership_datas: Vec<&str> = unwrap!(membership.as_str()).split(':').collect(); if membership_datas.len() == 5 { let membership_doc_builder = MembershipDocumentBuilder { currency, - issuer: &PubKey::Ed25519( - ed25519::PublicKey::from_base58(membership_datas[0]).unwrap(), - ), - blockstamp: &Blockstamp::from_string(membership_datas[2]).unwrap(), + issuer: &PubKey::Ed25519(unwrap!(ed25519::PublicKey::from_base58( + membership_datas[0] + ))), + blockstamp: &unwrap!(Blockstamp::from_string(membership_datas[2])), membership: membership_type, identity_username: membership_datas[4], - identity_blockstamp: &Blockstamp::from_string(membership_datas[3]).unwrap(), + identity_blockstamp: &unwrap!(Blockstamp::from_string(membership_datas[3])), }; - let membership_sig = - Sig::Ed25519(ed25519::Signature::from_base64(membership_datas[1]).unwrap()); + let membership_sig = Sig::Ed25519(unwrap!(ed25519::Signature::from_base64( + membership_datas[1] + ))); Ok(membership_doc_builder.build_with_signature(vec![membership_sig])) } else { Err(MembershipParseError::WrongFormat()) diff --git a/lib/modules/ws2p-v1-legacy/parsers/mod.rs b/lib/modules/ws2p-v1-legacy/src/parsers/mod.rs similarity index 74% rename from lib/modules/ws2p-v1-legacy/parsers/mod.rs rename to lib/modules/ws2p-v1-legacy/src/parsers/mod.rs index ca9a42a80f33ac97d570c0caeb1eeb1d33b14022..7c9c1edc3c5ea907901cef92df3c89ba288a5628 100644 --- a/lib/modules/ws2p-v1-legacy/parsers/mod.rs +++ b/lib/modules/ws2p-v1-legacy/src/parsers/mod.rs @@ -27,6 +27,7 @@ mod tests { use dubp_documents::DocumentBuilder; use dup_crypto::keys::*; use std::str::FromStr; + use unwrap::unwrap; #[test] fn parse_json_tx() { @@ -57,28 +58,24 @@ mod tests { let tx_builder = TransactionDocumentBuilder { currency: "g1", - blockstamp: &Blockstamp::from_string( + blockstamp: &unwrap!(Blockstamp::from_string( "112533-000002150F2E805E604D9B31212D079570AAD8D3A4D8BB75F2C15A94A345B6B1", - ) - .unwrap(), + )), locktime: &0, - issuers: &vec![PubKey::Ed25519( - ed25519::PublicKey::from_base58("51EFVNZwpfmTXU7BSLpeh3PZFgfdmm5hq5MzCDopdH2") - .unwrap(), - )], - inputs: &vec![TransactionInput::from_str( + issuers: &vec![PubKey::Ed25519(unwrap!(ed25519::PublicKey::from_base58( + "51EFVNZwpfmTXU7BSLpeh3PZFgfdmm5hq5MzCDopdH2" + )))], + inputs: &vec![unwrap!(TransactionInput::from_str( "1000:0:D:51EFVNZwpfmTXU7BSLpeh3PZFgfdmm5hq5MzCDopdH2:46496", - ) - .unwrap()], - outputs: &vec![TransactionOutput::from_str( + ))], + outputs: &vec![unwrap!(TransactionOutput::from_str( "1000:0:SIG(2yN8BRSkARcqE8NCxKMBiHfTpx1EvwULFn56Myf6qRmy)", - ) - .unwrap()], - unlocks: &vec![TransactionInputUnlocks::from_str("0:SIG(0)").unwrap()], + ))], + unlocks: &vec![unwrap!(TransactionInputUnlocks::from_str("0:SIG(0)"))], comment: "Merci pour la calligraphie ;) de Liam", hash: None, }; - let mut tx_doc = tx_builder.build_with_signature(vec![Sig::Ed25519(ed25519::Signature::from_base64("5olrjFylTCsVq8I5Yr7FpXeviynICyvIwe1yG5N0RJF+VZb+bCFBnLAMpmMCU2qzUvK7z41UXOrMRybXiLa2Dw==").unwrap())]); + let mut tx_doc = tx_builder.build_with_signature(vec![Sig::Ed25519(unwrap!(ed25519::Signature::from_base64("5olrjFylTCsVq8I5Yr7FpXeviynICyvIwe1yG5N0RJF+VZb+bCFBnLAMpmMCU2qzUvK7z41UXOrMRybXiLa2Dw==")))]); tx_doc.compute_hash(); assert_eq!( parse_transaction("g1", &tx_json).expect("Fail to parse transaction !"), @@ -116,32 +113,29 @@ mod tests { let tx_builder = TransactionDocumentBuilder { currency: "g1", - blockstamp: &Blockstamp::from_string( + blockstamp: &unwrap!(Blockstamp::from_string( "58-00005B9167EBA1E32C6EAD42AE7F72D8F14B765D3C9E47D233B553D47C5AEE0C", - ) - .unwrap(), + )), locktime: &0, - issuers: &vec![PubKey::Ed25519( - ed25519::PublicKey::from_base58("FVUFRrk1K5TQGsY7PRLwqHgdHRoHrwb1hcucp4C2N5tD") - .unwrap(), - )], - inputs: &vec![TransactionInput::from_str( + issuers: &vec![PubKey::Ed25519(unwrap!(ed25519::PublicKey::from_base58( + "FVUFRrk1K5TQGsY7PRLwqHgdHRoHrwb1hcucp4C2N5tD" + )))], + inputs: &vec![unwrap!(TransactionInput::from_str( "1000:0:D:FVUFRrk1K5TQGsY7PRLwqHgdHRoHrwb1hcucp4C2N5tD:1", - ) - .unwrap()], + ))], outputs: &vec![ - TransactionOutput::from_str("3:0:SIG(7vU9BMDhN6fBuRa2iK3JRbC6pqQKb4qDMGsFcQuT5cz)") - .unwrap(), - TransactionOutput::from_str( + unwrap!(TransactionOutput::from_str( + "3:0:SIG(7vU9BMDhN6fBuRa2iK3JRbC6pqQKb4qDMGsFcQuT5cz)" + )), + unwrap!(TransactionOutput::from_str( "997:0:SIG(FVUFRrk1K5TQGsY7PRLwqHgdHRoHrwb1hcucp4C2N5tD)", - ) - .unwrap(), + )), ], - unlocks: &vec![TransactionInputUnlocks::from_str("0:SIG(0)").unwrap()], + unlocks: &vec![unwrap!(TransactionInputUnlocks::from_str("0:SIG(0)"))], comment: "Un petit cafe ;-)", hash: None, }; - let mut tx_doc = tx_builder.build_with_signature(vec![Sig::Ed25519(ed25519::Signature::from_base64("VWbvsiybM4L2X5+o+6lIiuKNw5KrD1yGZqmV+lHtA28XoRUFzochSIgfoUqBsTAaYEHY45vSX917LDXudTEzBg==").unwrap())]); + let mut tx_doc = tx_builder.build_with_signature(vec![Sig::Ed25519(unwrap!(ed25519::Signature::from_base64("VWbvsiybM4L2X5+o+6lIiuKNw5KrD1yGZqmV+lHtA28XoRUFzochSIgfoUqBsTAaYEHY45vSX917LDXudTEzBg==")))]); tx_doc.compute_hash(); assert_eq!( parse_transaction("g1", &tx_json).expect("Fail to parse transaction !"), diff --git a/lib/modules/ws2p-v1-legacy/parsers/transactions.rs b/lib/modules/ws2p-v1-legacy/src/parsers/transactions.rs similarity index 88% rename from lib/modules/ws2p-v1-legacy/parsers/transactions.rs rename to lib/modules/ws2p-v1-legacy/src/parsers/transactions.rs index f979aa73d19353710d94f0dd884a3aba18edb951..5beacc276a4c13908d207a57cd5c480ef3b045a4 100644 --- a/lib/modules/ws2p-v1-legacy/parsers/transactions.rs +++ b/lib/modules/ws2p-v1-legacy/src/parsers/transactions.rs @@ -96,6 +96,7 @@ pub fn parse_transaction( #[cfg(test)] mod tests { use super::*; + use unwrap::unwrap; #[test] fn parse_compact_tx() { @@ -110,24 +111,20 @@ Merci pour la calligraphie ;) de Liam$\ let _tx_builder = TransactionDocumentBuilder { currency: "g1", - blockstamp: &Blockstamp::from_string( + blockstamp: &unwrap!(Blockstamp::from_string( "112533-000002150F2E805E604D9B31212D079570AAD8D3A4D8BB75F2C15A94A345B6B1", - ) - .unwrap(), + )), locktime: &0, - issuers: &vec![PubKey::Ed25519( - ed25519::PublicKey::from_base58("51EFVNZwpfmTXU7BSLpeh3PZFgfdmm5hq5MzCDopdH2") - .unwrap(), - )], - inputs: &vec![TransactionInput::from_str( + issuers: &vec![PubKey::Ed25519(unwrap!(ed25519::PublicKey::from_base58( + "51EFVNZwpfmTXU7BSLpeh3PZFgfdmm5hq5MzCDopdH2" + )))], + inputs: &vec![unwrap!(TransactionInput::from_str( "1000:0:D:51EFVNZwpfmTXU7BSLpeh3PZFgfdmm5hq5MzCDopdH2:46496", - ) - .unwrap()], - outputs: &vec![TransactionOutput::from_str( + ))], + outputs: &vec![unwrap!(TransactionOutput::from_str( "1000:0:SIG(2yN8BRSkARcqE8NCxKMBiHfTpx1EvwULFn56Myf6qRmy)", - ) - .unwrap()], - unlocks: &vec![TransactionInputUnlocks::from_str("0:SIG(0)").unwrap()], + ))], + unlocks: &vec![unwrap!(TransactionInputUnlocks::from_str("0:SIG(0)"))], comment: "Merci pour la calligraphie ;) de Liam", hash: None, }; diff --git a/lib/modules/ws2p-v1-legacy/src/requests/mod.rs b/lib/modules/ws2p-v1-legacy/src/requests/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..f245617818e5144e6abe8e56576027bdb39bf901 --- /dev/null +++ b/lib/modules/ws2p-v1-legacy/src/requests/mod.rs @@ -0,0 +1,19 @@ +// 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/>. + +//! Sub-module managing the inter-modules requests sent and received. + +pub mod received; +pub mod sent; diff --git a/lib/modules/ws2p-v1-legacy/src/requests/received.rs b/lib/modules/ws2p-v1-legacy/src/requests/received.rs new file mode 100644 index 0000000000000000000000000000000000000000..ab9c2d0edc5c53e7a071b1a3b706078a9e832435 --- /dev/null +++ b/lib/modules/ws2p-v1-legacy/src/requests/received.rs @@ -0,0 +1,66 @@ +// 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/>. + +//! Sub-module managing the inter-modules requests received. + +use crate::ws2p_db::DbEndpoint; +use crate::ws_connections::states::WS2PConnectionState; +use crate::WS2PModule; +use duniter_network::requests::OldNetworkRequest; +use durs_message::requests::DursReqContent; + +pub fn receive_req(ws2p_module: &mut WS2PModule, req_content: &DursReqContent) { + if let DursReqContent::OldNetworkRequest(ref old_net_request) = *req_content { + match *old_net_request { + OldNetworkRequest::GetBlocks(ref req_id, ref count, ref from) => { + let mut receiver_index = 0; + let mut real_receiver = None; + for (ws2p_full_id, DbEndpoint { state, .. }) in ws2p_module.ws2p_endpoints.clone() { + if let WS2PConnectionState::Established = state { + if receiver_index == ws2p_module.next_receiver { + real_receiver = Some(ws2p_full_id); + break; + } + receiver_index += 1; + } + } + if real_receiver.is_none() { + ws2p_module.next_receiver = 0; + for (ws2p_full_id, DbEndpoint { state, .. }) in &ws2p_module.ws2p_endpoints { + if let WS2PConnectionState::Established = *state { + real_receiver = Some(*ws2p_full_id); + break; + } + } + } else { + ws2p_module.next_receiver += 1; + } + if let Some(real_receiver) = real_receiver { + debug!("WS2P: send req to: ({:?})", real_receiver); + let _blocks_request_result = + crate::ws_connections::requests::sent::send_request_to_specific_node( + ws2p_module, + &real_receiver, + &OldNetworkRequest::GetBlocks(*req_id, *count, *from), + ); + } else { + warn!("WS2P: not found peer to send request !"); + } + } + OldNetworkRequest::GetEndpoints(ref _request) => {} + _ => {} + } + } +} diff --git a/lib/modules/ws2p-v1-legacy/src/requests/sent.rs b/lib/modules/ws2p-v1-legacy/src/requests/sent.rs new file mode 100644 index 0000000000000000000000000000000000000000..e07093a98f866de4c6a000d15d6f033bd99c1c08 --- /dev/null +++ b/lib/modules/ws2p-v1-legacy/src/requests/sent.rs @@ -0,0 +1,37 @@ +// Copyright (C) 2018 The Durs 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/>. + +//! Sub-module managing the inter-modules requests sent. + +use crate::WS2PModule; +use duniter_module::{DursModule, ModuleReqId, ModuleRole, RouterThreadMessage}; +use durs_message::requests::{BlockchainRequest, DursReqContent}; +use durs_message::*; + +pub fn send_dal_request(ws2p_module: &mut WS2PModule, req: &BlockchainRequest) { + ws2p_module.count_dal_requests += 1; + if ws2p_module.count_dal_requests == std::u32::MAX { + ws2p_module.count_dal_requests = 0; + } + ws2p_module + .router_sender + .send(RouterThreadMessage::ModuleMessage(DursMsg::Request { + req_from: WS2PModule::name(), + req_to: ModuleRole::BlockchainDatas, + req_id: ModuleReqId(ws2p_module.count_dal_requests), + req_content: DursReqContent::BlockchainRequest(req.clone()), + })) + .expect("Fail to send message to router !"); +} diff --git a/lib/modules/ws2p-v1-legacy/src/responses/mod.rs b/lib/modules/ws2p-v1-legacy/src/responses/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..516f7af7e549f4127da05b1b019695a6c127e704 --- /dev/null +++ b/lib/modules/ws2p-v1-legacy/src/responses/mod.rs @@ -0,0 +1,19 @@ +// Copyright (C) 2018 The Durs 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/>. + +//! Sub-module managing the inter-modules responses sent and received. + +pub mod received; +pub mod sent; diff --git a/lib/modules/ws2p-v1-legacy/src/responses/received.rs b/lib/modules/ws2p-v1-legacy/src/responses/received.rs new file mode 100644 index 0000000000000000000000000000000000000000..64e0f4874ad6423479942df4288f3653b6ee8227 --- /dev/null +++ b/lib/modules/ws2p-v1-legacy/src/responses/received.rs @@ -0,0 +1,16 @@ +// Copyright (C) 2018 The Durs 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/>. + +//! Sub-module managing the inter-modules responses received. diff --git a/lib/modules/ws2p-v1-legacy/src/responses/sent.rs b/lib/modules/ws2p-v1-legacy/src/responses/sent.rs new file mode 100644 index 0000000000000000000000000000000000000000..f102489cbd94ef75a7846a9c122f1e56cfa98e51 --- /dev/null +++ b/lib/modules/ws2p-v1-legacy/src/responses/sent.rs @@ -0,0 +1,36 @@ +// Copyright (C) 2018 The Durs 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/>. + +//! Sub-module managing the inter-modules responses sent. + +use crate::*; +use durs_message::*; + +pub fn send_network_req_response( + ws2p_module: &WS2PModule, + requester: ModuleStaticName, + req_id: ModuleReqId, + response: NetworkResponse, +) { + ws2p_module + .router_sender + .send(RouterThreadMessage::ModuleMessage(DursMsg::Response { + res_from: WS2PModule::name(), + res_to: requester, + req_id, + res_content: DursResContent::NetworkResponse(response), + })) + .expect("Fail to send message to router !"); +} diff --git a/lib/modules/ws2p-v1-legacy/serializer.rs b/lib/modules/ws2p-v1-legacy/src/serializer.rs similarity index 100% rename from lib/modules/ws2p-v1-legacy/serializer.rs rename to lib/modules/ws2p-v1-legacy/src/serializer.rs diff --git a/lib/modules/ws2p-v1-legacy/src/ws2p_db.rs b/lib/modules/ws2p-v1-legacy/src/ws2p_db.rs new file mode 100644 index 0000000000000000000000000000000000000000..0b09fb1f1c79e29d91d712f66b9c5ea20d47062c --- /dev/null +++ b/lib/modules/ws2p-v1-legacy/src/ws2p_db.rs @@ -0,0 +1,97 @@ +// Copyright (C) 2018 The Durs 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/>. + +//! Manage WS2Pv1 storage. + +use crate::ws_connections::states::WS2PConnectionState; +use durs_network_documents::network_endpoint::EndpointV1; +use durs_network_documents::NodeFullId; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::fs::File; +use std::path::Path; + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum EndpointApi { + WS2P, + //WS2PS, + //WS2PTOR, + //DASA, + //BMA, + //BMAS, +} + +pub fn string_to_api(api: &str) -> Option<EndpointApi> { + match api { + "WS2P" => Some(EndpointApi::WS2P), + //"WS2PS" => Some(EndpointApi::WS2PS), + //"WS2PTOR" => Some(EndpointApi::WS2PTOR), + //"DASA" => Some(EndpointApi::DASA), + //"BASIC_MERKLED_API" => Some(EndpointApi::BMA), + //"BMAS" => Some(EndpointApi::BMAS), + &_ => None, + } +} + +#[derive(Debug)] +pub enum Ws2pPeersDbError { + IoErr(std::io::Error), + SerdeErr(bincode::Error), +} + +impl From<std::io::Error> for Ws2pPeersDbError { + fn from(e: std::io::Error) -> Self { + Ws2pPeersDbError::IoErr(e) + } +} + +impl From<bincode::Error> for Ws2pPeersDbError { + fn from(e: bincode::Error) -> Self { + Ws2pPeersDbError::SerdeErr(e) + } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct DbEndpoint { + pub ep: EndpointV1, + pub state: WS2PConnectionState, + pub last_check: u64, +} + +pub fn get_endpoints( + file_path: &Path, +) -> Result<HashMap<NodeFullId, DbEndpoint>, Ws2pPeersDbError> { + if file_path.exists() { + let bin_endpoints = durs_common_tools::read_bin_file(file_path)?; + if bin_endpoints.is_empty() { + Ok(HashMap::new()) + } else { + Ok(bincode::deserialize(&bin_endpoints[..])?) + } + } else { + File::create(file_path)?; + Ok(HashMap::new()) + } +} + +pub fn write_endpoints<S: std::hash::BuildHasher>( + file_path: &Path, + endpoints: &HashMap<NodeFullId, DbEndpoint, S>, +) -> Result<(), Ws2pPeersDbError> { + let bin_endpoints = bincode::serialize(&endpoints)?; + durs_common_tools::write_bin_file(file_path, &bin_endpoints)?; + + Ok(()) +} diff --git a/lib/modules/ws2p-v1-legacy/src/ws_connections/handler.rs b/lib/modules/ws2p-v1-legacy/src/ws_connections/handler.rs new file mode 100644 index 0000000000000000000000000000000000000000..d53b59eab09adab0ed3fc7be570f24c68b429875 --- /dev/null +++ b/lib/modules/ws2p-v1-legacy/src/ws_connections/handler.rs @@ -0,0 +1,241 @@ +// Copyright (C) 2018 The Durs 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/>. + +//! WS2P connections handler. + +use super::messages::*; +use super::meta_datas::WS2PConnectionMetaDatas; +use super::states::WS2PConnectionState; +use crate::constants::*; +use crate::*; +use dup_crypto::keys::*; +use std::sync::mpsc; +#[allow(deprecated)] +use ws::util::{Timeout, Token}; +use ws::{CloseCode, Frame, Handler, Handshake, Message, Sender}; + +const CONNECT: Token = Token(1); +const EXPIRE: Token = Token(2); + +// Our Handler struct. +// Here we explicity indicate that the Client needs a Sender, +// whereas a closure captures the Sender for us automatically. +#[allow(deprecated)] +#[derive(Debug)] +pub struct Client { + ws: Sender, + conductor_sender: mpsc::Sender<WS2PThreadSignal>, + currency: String, + key_pair: KeyPairEnum, + connect_message: Message, + conn_meta_datas: WS2PConnectionMetaDatas, + last_mess_time: SystemTime, + spam_interval: bool, + spam_counter: usize, + timeout: Option<Timeout>, +} + +pub fn connect_to_ws2p_endpoint( + endpoint: &EndpointV1, + conductor_sender: &mpsc::Sender<WS2PThreadSignal>, + currency: &str, + key_pair: KeyPairEnum, +) -> ws::Result<()> { + // Get endpoint url + let ws_url = endpoint.get_url(true, false).expect("Endpoint unreachable"); + + // Create WS2PConnectionMetaDatass + let mut conn_meta_datas = WS2PConnectionMetaDatas::new( + "b60a14fd-0826-4ae0-83eb-1a92cd59fd5308535fd3-78f2-4678-9315-cd6e3b7871b1".to_string(), + ); + conn_meta_datas.remote_pubkey = Some(endpoint.issuer); + conn_meta_datas.remote_uuid = Some( + endpoint + .node_id + .expect("WS2P: Fail to get ep.node_uuid() !"), + ); + + // Generate connect message + let connect_message = + generate_connect_message(currency, key_pair, conn_meta_datas.challenge.clone()); + + // Log + info!("WS2P: Try connection to {} ...", ws_url); + + // Connect to websocket + ws::connect(ws_url, |ws| Client { + ws, + conductor_sender: conductor_sender.clone(), + currency: String::from(currency), + key_pair, + connect_message: connect_message.clone(), + conn_meta_datas: conn_meta_datas.clone(), + last_mess_time: SystemTime::now(), + spam_interval: false, + spam_counter: 0, + timeout: None, + }) +} + +// We implement the Handler trait for Client so that we can get more +// fine-grained control of the connection. +impl Handler for Client { + // `on_open` will be called only after the WebSocket handshake is successful + // so at this point we know that the connection is ready to send/receive messages. + // We ignore the `Handshake` for now, but you could also use this method to setup + // Handler state or reject the connection based on the details of the Request + // or Response, such as by checking cookies or Auth headers. + fn on_open(&mut self, _: Handshake) -> ws::Result<()> { + // Define timeouts + self.ws.timeout(WS2P_NEGOTIATION_TIMEOUT * 1_000, CONNECT)?; + self.ws.timeout(WS2P_EXPIRE_TIMEOUT * 1_000, EXPIRE)?; + // Send ws::Sender to WS2PConductor + let result = self + .conductor_sender + .send(WS2PThreadSignal::WS2PConnectionMessage( + WS2PConnectionMessage( + self.conn_meta_datas.node_full_id(), + WS2PConnectionMessagePayload::WebsocketOk(WsSender(self.ws.clone())), + ), + )); + // If WS2PConductor is unrechable, close connection. + if result.is_err() { + debug!("Close ws2p connection because ws2p main thread is unrechable !"); + self.ws.close(CloseCode::Normal) + } else { + // Send CONNECT Message + self.ws.send(self.connect_message.clone()) + } + } + + // `on_message` is roughly equivalent to the Handler closure. It takes a `Message` + // and returns a `Result<()>`. + fn on_message(&mut self, msg: Message) -> ws::Result<()> { + // Spam ? + if unwrap!(SystemTime::now().duration_since(self.last_mess_time)) + > Duration::new(*WS2P_SPAM_INTERVAL_IN_MILLI_SECS, 0) + { + if self.spam_interval { + self.spam_counter += 1; + } else { + self.spam_interval = true; + self.spam_counter = 2; + } + } else { + self.spam_interval = false; + self.spam_counter = 0; + } + // Spam ? + if self.spam_counter >= *WS2P_SPAM_LIMIT { + thread::sleep(Duration::from_millis(*WS2P_SPAM_SLEEP_TIME_IN_SEC)); + self.last_mess_time = SystemTime::now(); + return Ok(()); + } + self.last_mess_time = SystemTime::now(); + + // Parse and check incoming message + if msg.is_text() { + let s: String = msg + .into_text() + .expect("WS2P: Fail to convert message payload to String !"); + trace!("WS2P: receive mess: {}", s); + let json_message: serde_json::Value = serde_json::from_str(&s) + .expect("WS2P: Fail to convert string message ton json value !"); + let result = self + .conductor_sender + .send(WS2PThreadSignal::WS2PConnectionMessage( + WS2PConnectionMessage( + self.conn_meta_datas.node_full_id(), + self.conn_meta_datas.parse_and_check_incoming_message( + &self.currency, + self.key_pair, + &json_message, + ), + ), + )); + if result.is_err() { + info!("Close ws2p connection because ws2p main thread is unrechable !"); + self.ws.close(CloseCode::Normal)?; + } + } + Ok(()) + } + fn on_timeout(&mut self, event: Token) -> ws::Result<()> { + match event { + CONNECT => { + if self.conn_meta_datas.state != WS2PConnectionState::Established { + let _result = + self.conductor_sender + .send(WS2PThreadSignal::WS2PConnectionMessage( + WS2PConnectionMessage( + self.conn_meta_datas.node_full_id(), + WS2PConnectionMessagePayload::NegociationTimeout, + ), + )); + self.ws.close(CloseCode::Away) + } else { + Ok(()) + } + } + EXPIRE => { + let _result = self + .conductor_sender + .send(WS2PThreadSignal::WS2PConnectionMessage( + WS2PConnectionMessage( + self.conn_meta_datas.node_full_id(), + WS2PConnectionMessagePayload::Timeout, + ), + )); + self.ws.close(CloseCode::Away) + } + _ => Ok(()), + } + } + #[allow(deprecated)] + fn on_new_timeout(&mut self, event: Token, timeout: Timeout) -> ws::Result<()> { + if event == EXPIRE { + if let Some(t) = self.timeout.take() { + self.ws.cancel(t)?; + } + self.timeout = Some(timeout) + } + Ok(()) + } + fn on_frame(&mut self, frame: Frame) -> ws::Result<Option<Frame>> { + // some activity has occurred, let's reset the expiration timeout + self.ws.timeout(WS2P_EXPIRE_TIMEOUT * 1_000, EXPIRE)?; + Ok(Some(frame)) + } + fn on_close(&mut self, code: CloseCode, reason: &str) { + // The WebSocket protocol allows for a utf8 reason for the closing state after the + // close code. WS-RS will attempt to interpret this data as a utf8 description of the + // reason for closing the connection. I many cases, `reason` will be an empty string. + // So, you may not normally want to display `reason` to the user, + // but let's assume that we know that `reason` is human-readable. + match code { + CloseCode::Normal => info!("The remote server close the connection."), + CloseCode::Away => info!("The remote server is leaving."), + _ => warn!("The remote server encountered an error: {}", reason), + } + let _result = self + .conductor_sender + .send(WS2PThreadSignal::WS2PConnectionMessage( + WS2PConnectionMessage( + self.conn_meta_datas.node_full_id(), + WS2PConnectionMessagePayload::Close, + ), + )); + } +} diff --git a/lib/modules/ws2p-v1-legacy/src/ws_connections/messages.rs b/lib/modules/ws2p-v1-legacy/src/ws_connections/messages.rs new file mode 100644 index 0000000000000000000000000000000000000000..d64651843cad411c6746336c732ad829621e31a1 --- /dev/null +++ b/lib/modules/ws2p-v1-legacy/src/ws_connections/messages.rs @@ -0,0 +1,291 @@ +// Copyright (C) 2018 The Durs 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/>. + +//! Define ws2p connections messages. + +use super::*; +use durs_network_documents::NodeFullId; +use ws::Message; + +#[derive(Debug)] +/// WS2Pv1 connection Message +pub struct WS2PConnectionMessage(pub NodeFullId, pub WS2PConnectionMessagePayload); + +#[derive(Debug)] +/// WS2Pv1 connection Message payload +pub enum WS2PConnectionMessagePayload { + FailOpenWS, + WrongUrl, + FailToSplitWS, + TryToSendConnectMess, + FailSendConnectMess, + WebsocketOk(WsSender), + NegociationTimeout, + ValidConnectMessage(String, WS2PConnectionState), + ValidAckMessage(String, WS2PConnectionState), + ValidOk(WS2PConnectionState), + DalRequest(ModuleReqId, serde_json::Value), + PeerCard(serde_json::Value, Vec<EndpointV1>), + Heads(Vec<serde_json::Value>), + Document(BlockchainDocument), + ReqResponse(ModuleReqId, serde_json::Value), + InvalidMessage, + WrongFormatMessage, + UnknowMessage, + Timeout, + Close, +} + +pub fn generate_connect_message( + currency: &str, + key_pair: KeyPairEnum, + challenge: String, +) -> Message { + // Create CONNECT Message + let mut connect_message = WS2PConnectMessageV1 { + currency: String::from(currency), + pubkey: key_pair.public_key(), + challenge, + signature: None, + }; + connect_message.signature = Some(connect_message.sign(key_pair)); + Message::text( + serde_json::to_string(&connect_message).expect("Fail to serialize CONNECT message !"), + ) +} + +pub fn ws2p_conn_message_pretreatment( + ws2p_module: &mut WS2PModule, + message: WS2PConnectionMessage, +) -> WS2PSignal { + let ws2p_full_id = message.0; + match message.1 { + WS2PConnectionMessagePayload::WrongUrl + | WS2PConnectionMessagePayload::FailOpenWS + | WS2PConnectionMessagePayload::FailToSplitWS => { + let dal_ep = ws2p_module + .ws2p_endpoints + .get_mut(&ws2p_full_id) + .expect("WS2P: Fail to get mut ep !"); + dal_ep.state = WS2PConnectionState::WSError; + dal_ep.last_check = durs_common_tools::current_timestamp(); + return WS2PSignal::WSError(ws2p_full_id); + } + WS2PConnectionMessagePayload::TryToSendConnectMess => { + ws2p_module + .ws2p_endpoints + .get_mut(&ws2p_full_id) + .expect("WS2P: Fail to get mut ep !") + .state = WS2PConnectionState::TryToSendConnectMess; + } + WS2PConnectionMessagePayload::FailSendConnectMess => { + let dal_ep = ws2p_module + .ws2p_endpoints + .get_mut(&ws2p_full_id) + .expect("WS2P: Fail to get mut ep !"); + dal_ep.state = WS2PConnectionState::Unreachable; + dal_ep.last_check = durs_common_tools::current_timestamp(); + } + WS2PConnectionMessagePayload::WebsocketOk(sender) => { + ws2p_module.websockets.insert(ws2p_full_id, sender); + } + WS2PConnectionMessagePayload::ValidConnectMessage(response, new_con_state) => { + ws2p_module + .ws2p_endpoints + .get_mut(&ws2p_full_id) + .expect("WS2P: Fail to get mut ep !") + .state = new_con_state; + debug!("Send: {:#?}", response); + if let Some(websocket) = ws2p_module.websockets.get_mut(&ws2p_full_id) { + if websocket.0.send(Message::text(response)).is_err() { + return WS2PSignal::WSError(ws2p_full_id); + } + } else { + // Connection closed by remote peer + let dal_ep = ws2p_module + .ws2p_endpoints + .get_mut(&ws2p_full_id) + .expect("WS2P: Fail to get mut ep !"); + dal_ep.state = WS2PConnectionState::Close; + dal_ep.last_check = durs_common_tools::current_timestamp(); + } + } + WS2PConnectionMessagePayload::ValidAckMessage(response, new_con_state) => { + ws2p_module + .ws2p_endpoints + .get_mut(&ws2p_full_id) + .expect("WS2P: Fail to get mut ep !") + .state = new_con_state; + if let WS2PConnectionState::AckMessOk = ws2p_module.ws2p_endpoints[&ws2p_full_id].state + { + debug!("Send: {:#?}", response); + if let Some(websocket) = ws2p_module.websockets.get_mut(&ws2p_full_id) { + if websocket.0.send(Message::text(response)).is_err() { + return WS2PSignal::WSError(ws2p_full_id); + } + } else { + fatal_error!("Fatal error : no websocket for {} !", ws2p_full_id); + } + } + } + WS2PConnectionMessagePayload::ValidOk(new_con_state) => { + ws2p_module + .ws2p_endpoints + .get_mut(&ws2p_full_id) + .expect("WS2P: Fail to get mut ep !") + .state = new_con_state; + let mut close_conn = false; + let signal = match ws2p_module.ws2p_endpoints[&ws2p_full_id].state { + WS2PConnectionState::OkMessOkWaitingAckMess => WS2PSignal::Empty, + WS2PConnectionState::Established => WS2PSignal::ConnectionEstablished(ws2p_full_id), + _ => { + close_conn = true; + WS2PSignal::Empty + } + }; + if close_conn { + close_connection( + ws2p_module, + &ws2p_full_id, + WS2PCloseConnectionReason::Unknow, + ); + } + + return signal; + } + WS2PConnectionMessagePayload::DalRequest(req_id, req_body) => { + return WS2PSignal::DalRequest(ws2p_full_id, req_id, req_body); + } + WS2PConnectionMessagePayload::PeerCard(body, ws2p_endpoints) => { + return WS2PSignal::PeerCard(ws2p_full_id, body, ws2p_endpoints); + } + WS2PConnectionMessagePayload::Heads(heads) => { + let mut applied_heads = Vec::with_capacity(heads.len()); + for head in heads { + if let Ok(head) = NetworkHead::from_json_value(&head) { + if head.verify() + && (ws2p_module.my_head.is_none() + || head.node_full_id() + != ws2p_module + .my_head + .clone() + .expect("WS2P: Fail to clone my_head") + .node_full_id()) + && head.apply(&mut ws2p_module.heads_cache) + { + applied_heads.push(head); + } + } + } + return WS2PSignal::Heads(ws2p_full_id, applied_heads); + } + WS2PConnectionMessagePayload::Document(network_doc) => { + return WS2PSignal::Document(ws2p_full_id, network_doc); + } + WS2PConnectionMessagePayload::ReqResponse(req_id, response) => { + if ws2p_module.requests_awaiting_response.len() > req_id.0 as usize { + if let Some((ref ws2p_request, ref recipient_fulld_id, ref _timestamp)) = + ws2p_module.requests_awaiting_response.remove(&req_id) + { + return WS2PSignal::ReqResponse( + req_id, + *ws2p_request, + *recipient_fulld_id, + response, + ); + } + } + } + WS2PConnectionMessagePayload::NegociationTimeout => { + match ws2p_module.ws2p_endpoints[&ws2p_full_id].state { + WS2PConnectionState::AckMessOk | WS2PConnectionState::ConnectMessOk => { + ws2p_module + .ws2p_endpoints + .get_mut(&ws2p_full_id) + .expect("WS2P: Fail to get mut ep !") + .state = WS2PConnectionState::Denial + } + WS2PConnectionState::WaitingConnectMess => { + ws2p_module + .ws2p_endpoints + .get_mut(&ws2p_full_id) + .expect("WS2P: Fail to get mut ep !") + .state = WS2PConnectionState::NoResponse + } + _ => { + let dal_ep = ws2p_module + .ws2p_endpoints + .get_mut(&ws2p_full_id) + .expect("WS2P: Fail to get mut ep !"); + dal_ep.state = WS2PConnectionState::Unreachable; + dal_ep.last_check = durs_common_tools::current_timestamp(); + } + } + close_connection( + ws2p_module, + &ws2p_full_id, + WS2PCloseConnectionReason::NegociationTimeout, + ); + return WS2PSignal::NegociationTimeout(ws2p_full_id); + } + WS2PConnectionMessagePayload::Timeout => { + close_connection( + ws2p_module, + &ws2p_full_id, + WS2PCloseConnectionReason::Timeout, + ); + return WS2PSignal::Timeout(ws2p_full_id); + } + WS2PConnectionMessagePayload::UnknowMessage => { + warn!("WS2P : Receive Unknow Message from {}.", &ws2p_full_id.1) + } + WS2PConnectionMessagePayload::WrongFormatMessage => warn!( + "WS2P : Receive Wrong Format Message from {}.", + &ws2p_full_id.1 + ), + WS2PConnectionMessagePayload::InvalidMessage => return WS2PSignal::Empty, + WS2PConnectionMessagePayload::Close => close_connection( + ws2p_module, + &ws2p_full_id, + WS2PCloseConnectionReason::AuthMessInvalidSig, + ), + } + let connections_count = ws2p_module.websockets.len(); + if connections_count == 0 { + return WS2PSignal::NoConnection; + } + // Detect timeout requests + let mut requests_timeout = Vec::new(); + for &(ref req, ref ws2p_full_id, ref timestamp) in + ws2p_module.requests_awaiting_response.clone().values() + { + if unwrap!(SystemTime::now().duration_since(*timestamp)) > Duration::new(20, 0) { + requests_timeout.push(req.get_req_full_id()); + warn!("request timeout : {:?} (sent to {:?})", req, ws2p_full_id); + } + } + // Delete (and resend) timeout requests + for req_id in requests_timeout { + //let ws2p_endpoints = ws2p_module.ws2p_endpoints.clone(); + let _request_option = ws2p_module.requests_awaiting_response.remove(&req_id.1); + /*if let Some((request, _, _)) = request_option { + let _request_result = ws2p_module.send_request_to_specific_node( + &get_random_connection(&ws2p_endpoints), + &request, + ); + }*/ + } + WS2PSignal::Empty +} diff --git a/lib/modules/ws2p-v1-legacy/ws2p_connection.rs b/lib/modules/ws2p-v1-legacy/src/ws_connections/meta_datas.rs similarity index 52% rename from lib/modules/ws2p-v1-legacy/ws2p_connection.rs rename to lib/modules/ws2p-v1-legacy/src/ws_connections/meta_datas.rs index c25f3f608ccad66b3667567d2b6ff115b0288f11..e5b8950d343bae47adee7be7527c19f66540693d 100644 --- a/lib/modules/ws2p-v1-legacy/ws2p_connection.rs +++ b/lib/modules/ws2p-v1-legacy/src/ws_connections/meta_datas.rs @@ -1,4 +1,22 @@ -use crate::constants::*; +// Copyright (C) 2018 The Durs 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/>. + +//! WS2P connections meta datas. + +use super::messages::WS2PConnectionMessagePayload; +use super::states::WS2PConnectionState; use crate::parsers::blocks::parse_json_block; use crate::*; use duniter_module::ModuleReqId; @@ -6,294 +24,7 @@ use duniter_network::documents::BlockchainDocument; use dup_crypto::keys::*; use durs_network_documents::network_endpoint::{EndpointV1, NetworkEndpointApi}; use durs_network_documents::NodeId; -use rand::Rng; -use std::sync::mpsc; -use ws::deflate::DeflateBuilder; -#[allow(deprecated)] -use ws::util::{Timeout, Token}; -use ws::{connect, CloseCode, Frame, Handler, Handshake, Message, Sender}; - -const CONNECT: Token = Token(1); -const EXPIRE: Token = Token(2); - -/// Store a websocket sender -pub struct WsSender(pub Sender); - -impl ::std::fmt::Debug for WsSender { - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - write!(f, "WsSender {{ }}") - } -} - -// Our Handler struct. -// Here we explicity indicate that the Client needs a Sender, -// whereas a closure captures the Sender for us automatically. #[allow(deprecated)] -struct Client { - ws: Sender, - conductor_sender: mpsc::Sender<WS2PThreadSignal>, - currency: String, - key_pair: KeyPairEnum, - connect_message: Message, - conn_meta_datas: WS2PConnectionMetaDatas, - last_mess_time: SystemTime, - spam_interval: bool, - spam_counter: usize, - timeout: Option<Timeout>, -} - -// We implement the Handler trait for Client so that we can get more -// fine-grained control of the connection. -impl Handler for Client { - // `on_open` will be called only after the WebSocket handshake is successful - // so at this point we know that the connection is ready to send/receive messages. - // We ignore the `Handshake` for now, but you could also use this method to setup - // Handler state or reject the connection based on the details of the Request - // or Response, such as by checking cookies or Auth headers. - fn on_open(&mut self, _: Handshake) -> ws::Result<()> { - // Define timeouts - self.ws.timeout(WS2P_NEGOTIATION_TIMEOUT * 1_000, CONNECT)?; - self.ws.timeout(WS2P_EXPIRE_TIMEOUT * 1_000, EXPIRE)?; - // Send ws::Sender to WS2PConductor - let result = self - .conductor_sender - .send(WS2PThreadSignal::WS2PConnectionMessage( - WS2PConnectionMessage( - self.conn_meta_datas.node_full_id(), - WS2PConnectionMessagePayload::WebsocketOk(WsSender(self.ws.clone())), - ), - )); - // If WS2PConductor is unrechable, close connection. - if result.is_err() { - debug!("Close ws2p connection because ws2p main thread is unrechable !"); - self.ws.close(CloseCode::Normal) - } else { - // Send CONNECT Message - self.ws.send(self.connect_message.clone()) - } - } - - // `on_message` is roughly equivalent to the Handler closure. It takes a `Message` - // and returns a `Result<()>`. - fn on_message(&mut self, msg: Message) -> ws::Result<()> { - // Spam ? - if SystemTime::now() - .duration_since(self.last_mess_time) - .unwrap() - > Duration::new(*WS2P_SPAM_INTERVAL_IN_MILLI_SECS, 0) - { - if self.spam_interval { - self.spam_counter += 1; - } else { - self.spam_interval = true; - self.spam_counter = 2; - } - } else { - self.spam_interval = false; - self.spam_counter = 0; - } - // Spam ? - if self.spam_counter >= *WS2P_SPAM_LIMIT { - thread::sleep(Duration::from_millis(*WS2P_SPAM_SLEEP_TIME_IN_SEC)); - self.last_mess_time = SystemTime::now(); - return Ok(()); - } - self.last_mess_time = SystemTime::now(); - - // Parse and check incoming message - if msg.is_text() { - let s: String = msg - .into_text() - .expect("WS2P: Fail to convert message payload to String !"); - trace!("WS2P: receive mess: {}", s); - let json_message: serde_json::Value = serde_json::from_str(&s) - .expect("WS2P: Fail to convert string message ton json value !"); - let result = self - .conductor_sender - .send(WS2PThreadSignal::WS2PConnectionMessage( - WS2PConnectionMessage( - self.conn_meta_datas.node_full_id(), - self.conn_meta_datas.parse_and_check_incoming_message( - &self.currency, - self.key_pair, - &json_message, - ), - ), - )); - if result.is_err() { - info!("Close ws2p connection because ws2p main thread is unrechable !"); - self.ws.close(CloseCode::Normal)?; - } - } - Ok(()) - } - fn on_timeout(&mut self, event: Token) -> ws::Result<()> { - match event { - CONNECT => { - if self.conn_meta_datas.state != WS2PConnectionState::Established { - let _result = - self.conductor_sender - .send(WS2PThreadSignal::WS2PConnectionMessage( - WS2PConnectionMessage( - self.conn_meta_datas.node_full_id(), - WS2PConnectionMessagePayload::NegociationTimeout, - ), - )); - self.ws.close(CloseCode::Away) - } else { - Ok(()) - } - } - EXPIRE => { - let _result = self - .conductor_sender - .send(WS2PThreadSignal::WS2PConnectionMessage( - WS2PConnectionMessage( - self.conn_meta_datas.node_full_id(), - WS2PConnectionMessagePayload::Timeout, - ), - )); - self.ws.close(CloseCode::Away) - } - _ => Ok(()), - } - } - #[allow(deprecated)] - fn on_new_timeout(&mut self, event: Token, timeout: Timeout) -> ws::Result<()> { - if event == EXPIRE { - if let Some(t) = self.timeout.take() { - self.ws.cancel(t)?; - } - self.timeout = Some(timeout) - } - Ok(()) - } - fn on_frame(&mut self, frame: Frame) -> ws::Result<Option<Frame>> { - // some activity has occurred, let's reset the expiration timeout - self.ws.timeout(WS2P_EXPIRE_TIMEOUT * 1_000, EXPIRE)?; - Ok(Some(frame)) - } - fn on_close(&mut self, code: CloseCode, reason: &str) { - // The WebSocket protocol allows for a utf8 reason for the closing state after the - // close code. WS-RS will attempt to interpret this data as a utf8 description of the - // reason for closing the connection. I many cases, `reason` will be an empty string. - // So, you may not normally want to display `reason` to the user, - // but let's assume that we know that `reason` is human-readable. - match code { - CloseCode::Normal => info!("The remote server close the connection."), - CloseCode::Away => info!("The remote server is leaving."), - _ => warn!("The remote server encountered an error: {}", reason), - } - let _result = self - .conductor_sender - .send(WS2PThreadSignal::WS2PConnectionMessage( - WS2PConnectionMessage( - self.conn_meta_datas.node_full_id(), - WS2PConnectionMessagePayload::Close, - ), - )); - } -} - -#[derive(Debug, Copy, Clone)] -pub enum WS2POrderForListeningThread { - Close, -} - -#[derive(Debug, Copy, Clone, PartialEq)] -pub enum WS2PConnectionState { - NeverTry = 0, - TryToOpenWS = 1, - WSError = 2, - TryToSendConnectMess = 3, - Unreachable = 4, - WaitingConnectMess = 5, - NoResponse = 6, - ConnectMessOk = 7, - OkMessOkWaitingAckMess = 8, - AckMessOk = 9, - Denial = 10, - Close = 11, - Established = 12, -} - -impl From<u32> for WS2PConnectionState { - fn from(integer: u32) -> Self { - match integer { - 1 | 2 => WS2PConnectionState::WSError, - 3 | 4 => WS2PConnectionState::Unreachable, - 5 | 6 => WS2PConnectionState::NoResponse, - 7 | 8 | 9 | 10 => WS2PConnectionState::Denial, - 11 | 12 => WS2PConnectionState::Close, - _ => WS2PConnectionState::NeverTry, - } - } -} - -impl WS2PConnectionState { - pub fn from_u32(integer: u32, from_db: bool) -> Self { - if from_db { - WS2PConnectionState::from(integer) - } else { - match integer { - 1 => WS2PConnectionState::TryToOpenWS, - 2 => WS2PConnectionState::WSError, - 3 | 4 => WS2PConnectionState::Unreachable, - 5 | 6 => WS2PConnectionState::NoResponse, - 7 => WS2PConnectionState::ConnectMessOk, - 8 => WS2PConnectionState::OkMessOkWaitingAckMess, - 9 => WS2PConnectionState::AckMessOk, - 10 => WS2PConnectionState::Denial, - 11 => WS2PConnectionState::Close, - 12 => WS2PConnectionState::Established, - _ => WS2PConnectionState::NeverTry, - } - } - } - pub fn to_u32(self) -> u32 { - match self { - WS2PConnectionState::NeverTry => 0, - _ => 1, - } - } -} - -#[derive(Debug)] -pub enum WS2PConnectionMessagePayload { - FailOpenWS, - WrongUrl, - FailToSplitWS, - TryToSendConnectMess, - FailSendConnectMess, - WebsocketOk(WsSender), - NegociationTimeout, - ValidConnectMessage(String, WS2PConnectionState), - ValidAckMessage(String, WS2PConnectionState), - ValidOk(WS2PConnectionState), - DalRequest(ModuleReqId, serde_json::Value), - PeerCard(serde_json::Value, Vec<EndpointV1>), - Heads(Vec<serde_json::Value>), - Document(BlockchainDocument), - ReqResponse(ModuleReqId, serde_json::Value), - InvalidMessage, - WrongFormatMessage, - UnknowMessage, - Timeout, - Close, -} - -#[derive(Debug)] -pub struct WS2PConnectionMessage(pub NodeFullId, pub WS2PConnectionMessagePayload); - -#[derive(Debug, Copy, Clone, PartialEq)] -pub enum WS2PCloseConnectionReason { - AuthMessInvalidSig, - NegociationTimeout, - Timeout, - WsError, - Unknow, -} - #[derive(Debug, Clone)] pub struct WS2PConnectionMetaDatas { pub state: WS2PConnectionState, @@ -304,13 +35,6 @@ pub struct WS2PConnectionMetaDatas { pub current_blockstamp: Option<(u32, String)>, } -#[derive(Debug, Clone)] -pub struct WS2PDatasForListeningThread { - pub conn_meta_datas: WS2PConnectionMetaDatas, - pub currency: String, - pub key_pair: KeyPairEnum, -} - impl WS2PConnectionMetaDatas { pub fn new(challenge: String) -> Self { WS2PConnectionMetaDatas { @@ -340,11 +64,11 @@ impl WS2PConnectionMetaDatas { ) -> WS2PConnectionMessagePayload { if let Some(s) = m.get("auth") { if s.is_string() { - match s.as_str().unwrap() { + match s.as_str().unwrap_or("") { "CONNECT" => { let message = WS2PConnectMessageV1::parse(m, currency.to_string()) .expect("Failed to parsing CONNECT Message !"); - if message.verify() && message.pubkey == self.remote_pubkey.unwrap() { + if message.verify() && message.pubkey == unwrap!(self.remote_pubkey) { match self.state { WS2PConnectionState::WaitingConnectMess => { debug!("CONNECT sig is valid."); @@ -358,7 +82,7 @@ impl WS2PConnectionMetaDatas { }; response.signature = Some(response.sign(key_pair)); return WS2PConnectionMessagePayload::ValidConnectMessage( - serde_json::to_string(&response).unwrap(), + unwrap!(serde_json::to_string(&response)), self.state, ); } @@ -391,7 +115,7 @@ impl WS2PConnectionMetaDatas { }; response.signature = Some(response.sign(key_pair)); return WS2PConnectionMessagePayload::ValidAckMessage( - serde_json::to_string(&response).unwrap(), + unwrap!(serde_json::to_string(&response)), self.state, ); } else { @@ -483,7 +207,7 @@ impl WS2PConnectionMetaDatas { match body.get("name") { Some(s) => { if s.is_string() { - match s.as_str().unwrap() { + match s.as_str().unwrap_or("") { "BLOCK" => match body.get("block") { Some(block) => { if let Some(block_doc) = parse_json_block(&block) { @@ -584,85 +308,3 @@ impl WS2PConnectionMetaDatas { } } } - -pub fn get_random_connection<S: ::std::hash::BuildHasher>( - connections: &HashMap<NodeFullId, (EndpointV1, WS2PConnectionState), S>, -) -> NodeFullId { - let mut rng = rand::thread_rng(); - let mut loop_count = 0; - loop { - for (ws2p_full_id, (_ep, state)) in &(*connections) { - if loop_count > 10 { - return *ws2p_full_id; - } - if let WS2PConnectionState::Established = state { - if rng.gen::<bool>() { - return *ws2p_full_id; - } - } - } - loop_count += 1; - } -} - -pub fn connect_to_ws2p_endpoint( - endpoint: &EndpointV1, - conductor_sender: &mpsc::Sender<WS2PThreadSignal>, - currency: &str, - key_pair: KeyPairEnum, -) -> ws::Result<()> { - // Get endpoint url - let ws_url = endpoint.get_url(true, false).expect("Endpoint unreachable"); - - // Create WS2PConnectionMetaDatass - let mut conn_meta_datas = WS2PConnectionMetaDatas::new( - "b60a14fd-0826-4ae0-83eb-1a92cd59fd5308535fd3-78f2-4678-9315-cd6e3b7871b1".to_string(), - ); - conn_meta_datas.remote_pubkey = Some(endpoint.issuer); - conn_meta_datas.remote_uuid = Some( - endpoint - .node_id - .expect("WS2P: Fail to get ep.node_uuid() !"), - ); - - // Generate connect message - let connect_message = - generate_connect_message(currency, key_pair, conn_meta_datas.challenge.clone()); - - // Log - info!("WS2P: Try connection to {} ...", ws_url); - - // Connect to websocket - connect(ws_url, |ws| { - DeflateBuilder::new().build(Client { - ws, - conductor_sender: conductor_sender.clone(), - currency: String::from(currency), - key_pair, - connect_message: connect_message.clone(), - conn_meta_datas: conn_meta_datas.clone(), - last_mess_time: SystemTime::now(), - spam_interval: false, - spam_counter: 0, - timeout: None, - }) - }) -} - -pub fn generate_connect_message( - currency: &str, - key_pair: KeyPairEnum, - challenge: String, -) -> Message { - // Create CONNECT Message - let mut connect_message = WS2PConnectMessageV1 { - currency: String::from(currency), - pubkey: key_pair.public_key(), - challenge, - signature: None, - }; - connect_message.signature = Some(connect_message.sign(key_pair)); - Message::text( - serde_json::to_string(&connect_message).expect("Fail to serialize CONNECT message !"), - ) -} diff --git a/lib/modules/ws2p-v1-legacy/src/ws_connections/mod.rs b/lib/modules/ws2p-v1-legacy/src/ws_connections/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..f18a33b95847845d26644a60bf35c7e7de98393a --- /dev/null +++ b/lib/modules/ws2p-v1-legacy/src/ws_connections/mod.rs @@ -0,0 +1,184 @@ +// Copyright (C) 2018 The Durs 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/>. + +//! Manage websockets connections. + +pub mod handler; +pub mod messages; +mod meta_datas; +pub mod requests; +pub mod states; + +use crate::*; +use duniter_module::ModuleReqId; +use duniter_network::documents::BlockchainDocument; +use dup_crypto::keys::*; +use durs_network_documents::network_endpoint::EndpointV1; +use rand::Rng; +use states::WS2PConnectionState; +use std::collections::HashSet; +#[allow(deprecated)] +use ws::Sender; + +/// Store a websocket sender +pub struct WsSender(pub Sender); + +impl ::std::fmt::Debug for WsSender { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + write!(f, "WsSender {{ }}") + } +} + +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum WS2PCloseConnectionReason { + AuthMessInvalidSig, + NegociationTimeout, + Timeout, + WsError, + Unknow, +} + +pub fn connect_to_know_endpoints(ws2p_module: &mut WS2PModule) { + info!("WS2P: connect to know endpoints..."); + let mut count_established_connections = 0; + let mut pubkeys = HashSet::new(); + let mut reachable_endpoints = Vec::new(); + let mut unreachable_endpoints = Vec::new(); + for (_ws2p_full_id, DbEndpoint { ep, state, .. }) in ws2p_module.ws2p_endpoints.clone() { + if ep.issuer == ws2p_module.key_pair.public_key() || !pubkeys.contains(&ep.issuer) { + match state { + WS2PConnectionState::Established => count_established_connections += 1, + WS2PConnectionState::NeverTry + | WS2PConnectionState::Close + | WS2PConnectionState::Denial => { + pubkeys.insert(ep.issuer); + reachable_endpoints.push(ep); + } + _ => { + pubkeys.insert(ep.issuer); + unreachable_endpoints.push(ep); + } + } + } + } + let mut free_outcoming_rooms = + ws2p_module.conf.clone().outcoming_quota - count_established_connections; + while free_outcoming_rooms > 0 { + let ep = if !reachable_endpoints.is_empty() { + reachable_endpoints + .pop() + .expect("WS2P: Fail to pop() reachable_endpoints !") + } else if !unreachable_endpoints.is_empty() { + unreachable_endpoints + .pop() + .expect("WS2P: Fail to pop() unreachable_endpoints !") + } else { + break; + }; + if !ws2p_module.ssl && ep.port == 443 { + continue; + } + connect_to_without_checking_quotas(ws2p_module, unwrap!(ep.node_full_id())); + free_outcoming_rooms -= 1; + } +} + +pub fn connect_to(ws2p_module: &mut WS2PModule, ep: &EndpointV1) { + // Add endpoint to endpoints list (if there isn't already) + let node_full_id = ep + .node_full_id() + .expect("WS2P: Fail to get ep.node_full_id() !"); + ws2p_module + .ws2p_endpoints + .entry(node_full_id) + .or_insert(DbEndpoint { + ep: ep.clone(), + state: WS2PConnectionState::NeverTry, + last_check: 0, + }); + let count_established_connections = count_established_connections(&ws2p_module); + if ws2p_module.conf.outcoming_quota > count_established_connections { + connect_to_without_checking_quotas(ws2p_module, node_full_id); + } +} + +pub fn connect_to_without_checking_quotas(ws2p_module: &mut WS2PModule, node_full_id: NodeFullId) { + let endpoint = unwrap!(ws2p_module.ws2p_endpoints.get(&node_full_id)); + let endpoint_copy = endpoint.ep.clone(); + let conductor_sender_copy = ws2p_module.main_thread_channel.0.clone(); + let currency_copy = ws2p_module.currency.clone(); + let key_pair_copy = ws2p_module.key_pair; + thread::spawn(move || { + let _result = crate::ws_connections::handler::connect_to_ws2p_endpoint( + &endpoint_copy, + &conductor_sender_copy, + ¤cy_copy.expect("WS2PError : No currency !"), + key_pair_copy, + ); + }); +} + +pub fn close_connection( + ws2p_module: &mut WS2PModule, + ws2p_full_id: &NodeFullId, + reason: WS2PCloseConnectionReason, +) { + match reason { + WS2PCloseConnectionReason::NegociationTimeout => {} + WS2PCloseConnectionReason::AuthMessInvalidSig + | WS2PCloseConnectionReason::Timeout + | WS2PCloseConnectionReason::WsError + | WS2PCloseConnectionReason::Unknow => { + if let Some(dal_ep) = ws2p_module.ws2p_endpoints.get_mut(ws2p_full_id) { + dal_ep.state = WS2PConnectionState::Close; + dal_ep.last_check = durs_common_tools::current_timestamp(); + } + } + } + if let Some(websocket) = ws2p_module.websockets.get(&ws2p_full_id) { + let _result = websocket.0.close(ws::CloseCode::Normal); + } + let _result = ws2p_module.websockets.remove(ws2p_full_id); +} + +pub fn get_random_connection<S: ::std::hash::BuildHasher>( + connections: &HashMap<NodeFullId, (EndpointV1, WS2PConnectionState), S>, +) -> NodeFullId { + let mut rng = rand::thread_rng(); + let mut loop_count = 0; + loop { + for (ws2p_full_id, (_ep, state)) in &(*connections) { + if loop_count > 10 { + return *ws2p_full_id; + } + if let WS2PConnectionState::Established = state { + if rng.gen::<bool>() { + return *ws2p_full_id; + } + } + } + loop_count += 1; + } +} + +pub fn count_established_connections(ws2p_module: &WS2PModule) -> usize { + let mut count_established_connections = 0; + for DbEndpoint { state, .. } in ws2p_module.ws2p_endpoints.values() { + if let WS2PConnectionState::Established = state { + count_established_connections += 1; + } + } + count_established_connections +} diff --git a/lib/modules/ws2p-v1-legacy/src/ws_connections/requests/mod.rs b/lib/modules/ws2p-v1-legacy/src/ws_connections/requests/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..3dbfe5e950138bd0e1f68c118381e10e9ec1f52f --- /dev/null +++ b/lib/modules/ws2p-v1-legacy/src/ws_connections/requests/mod.rs @@ -0,0 +1,19 @@ +// 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/>. + +//! Sub-module managing the WS2Pv1 requests sent and received. + +pub mod received; +pub mod sent; diff --git a/lib/modules/ws2p-v1-legacy/src/ws_connections/requests/received.rs b/lib/modules/ws2p-v1-legacy/src/ws_connections/requests/received.rs new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/lib/modules/ws2p-v1-legacy/src/ws_connections/requests/received.rs @@ -0,0 +1 @@ + diff --git a/lib/modules/ws2p-v1-legacy/src/ws_connections/requests/sent.rs b/lib/modules/ws2p-v1-legacy/src/ws_connections/requests/sent.rs new file mode 100644 index 0000000000000000000000000000000000000000..6a3b539bfbca2a3daeefe177237817eaab4b3c16 --- /dev/null +++ b/lib/modules/ws2p-v1-legacy/src/ws_connections/requests/sent.rs @@ -0,0 +1,77 @@ +// Copyright (C) 2018 The Durs 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/>. + +//! Sub-module managing the WS2Pv1 requests sent. + +use crate::WS2PModule; +use duniter_network::requests::OldNetworkRequest; +use durs_network_documents::NodeFullId; +use std::time::SystemTime; +use ws::Message; + +pub fn send_request_to_specific_node( + ws2p_module: &mut WS2PModule, + ws2p_full_id: &NodeFullId, + ws2p_request: &OldNetworkRequest, +) -> ws::Result<()> { + if let Some(ws) = ws2p_module.websockets.get_mut(ws2p_full_id) { + let json_req = network_request_to_json(ws2p_request).to_string(); + debug!("send request {} to {}", json_req, ws2p_full_id); + ws.0.send(Message::text(json_req))?; + ws2p_module.requests_awaiting_response.insert( + ws2p_request.get_req_id(), + (*ws2p_request, *ws2p_full_id, SystemTime::now()), + ); + } else { + warn!("WS2P: Fail to get mut websocket !"); + } + Ok(()) +} + +pub fn network_request_to_json(request: &OldNetworkRequest) -> serde_json::Value { + let (request_id, request_type, request_params) = match *request { + OldNetworkRequest::GetCurrent(ref req_full_id) => (req_full_id.1, "CURRENT", json!({})), + OldNetworkRequest::GetBlocks(ref req_full_id, count, from_mumber) => ( + req_full_id.1, + "BLOCKS_CHUNK", + json!({ + "count": count, + "fromNumber": from_mumber + }), + ), + OldNetworkRequest::GetRequirementsPending(ref req_full_id, min_cert) => ( + req_full_id.1, + "WOT_REQUIREMENTS_OF_PENDING", + json!({ "minCert": min_cert }), + ), + OldNetworkRequest::GetConsensus(_) => { + panic!("GetConsensus() request must be not convert to json !"); + } + OldNetworkRequest::GetHeadsCache(_) => { + panic!("GetHeadsCache() request must be not convert to json !"); + } + OldNetworkRequest::GetEndpoints(_) => { + panic!("GetEndpoints() request must be not convert to json !"); + } + }; + + json!({ + "reqId": request_id, + "body" : { + "name": request_type, + "params": request_params + } + }) +} diff --git a/lib/modules/ws2p-v1-legacy/src/ws_connections/states.rs b/lib/modules/ws2p-v1-legacy/src/ws_connections/states.rs new file mode 100644 index 0000000000000000000000000000000000000000..48e9142536735327af3acb08784ccfa3d91b40bb --- /dev/null +++ b/lib/modules/ws2p-v1-legacy/src/ws_connections/states.rs @@ -0,0 +1,76 @@ +// Copyright (C) 2018 The Durs 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/>. + +//! Define ws2p connections states. + +use serde::{Deserialize, Serialize}; + +#[derive(Copy, Clone, Debug, Deserialize, PartialEq, Serialize)] +pub enum WS2PConnectionState { + NeverTry = 0, + TryToOpenWS = 1, + WSError = 2, + TryToSendConnectMess = 3, + Unreachable = 4, + WaitingConnectMess = 5, + NoResponse = 6, + ConnectMessOk = 7, + OkMessOkWaitingAckMess = 8, + AckMessOk = 9, + Denial = 10, + Close = 11, + Established = 12, +} + +impl From<u32> for WS2PConnectionState { + fn from(integer: u32) -> Self { + match integer { + 1 | 2 => WS2PConnectionState::WSError, + 3 | 4 => WS2PConnectionState::Unreachable, + 5 | 6 => WS2PConnectionState::NoResponse, + 7 | 8 | 9 | 10 => WS2PConnectionState::Denial, + 11 | 12 => WS2PConnectionState::Close, + _ => WS2PConnectionState::NeverTry, + } + } +} + +impl WS2PConnectionState { + pub fn from_u32(integer: u32, from_db: bool) -> Self { + if from_db { + WS2PConnectionState::from(integer) + } else { + match integer { + 1 => WS2PConnectionState::TryToOpenWS, + 2 => WS2PConnectionState::WSError, + 3 | 4 => WS2PConnectionState::Unreachable, + 5 | 6 => WS2PConnectionState::NoResponse, + 7 => WS2PConnectionState::ConnectMessOk, + 8 => WS2PConnectionState::OkMessOkWaitingAckMess, + 9 => WS2PConnectionState::AckMessOk, + 10 => WS2PConnectionState::Denial, + 11 => WS2PConnectionState::Close, + 12 => WS2PConnectionState::Established, + _ => WS2PConnectionState::NeverTry, + } + } + } + pub fn to_u32(self) -> u32 { + match self { + WS2PConnectionState::NeverTry => 0, + _ => 1, + } + } +} diff --git a/lib/modules/ws2p-v1-legacy/ws2p_db.rs b/lib/modules/ws2p-v1-legacy/ws2p_db.rs deleted file mode 100644 index c23229dced15baccab7ec2c2358005e90ee9404f..0000000000000000000000000000000000000000 --- a/lib/modules/ws2p-v1-legacy/ws2p_db.rs +++ /dev/null @@ -1,128 +0,0 @@ -use dup_crypto::keys::*; -use durs_network_documents::network_endpoint::{EndpointV1, NetworkEndpointApi}; -use sqlite::*; - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub enum EndpointApi { - WS2P, - //WS2PS, - //WS2PTOR, - //DASA, - //BMA, - //BMAS, -} - -impl From<u32> for EndpointApi { - fn from(integer: u32) -> Self { - match integer { - _ => EndpointApi::WS2P, - } - } -} - -pub fn string_to_api(api: &str) -> Option<EndpointApi> { - match api { - "WS2P" => Some(EndpointApi::WS2P), - //"WS2PS" => Some(EndpointApi::WS2PS), - //"WS2PTOR" => Some(EndpointApi::WS2PTOR), - //"DASA" => Some(EndpointApi::DASA), - //"BASIC_MERKLED_API" => Some(EndpointApi::BMA), - //"BMAS" => Some(EndpointApi::BMAS), - &_ => None, - } -} - -pub fn api_to_integer(api: &NetworkEndpointApi) -> i64 { - match api.0.as_str() { - "WS2P" => 1, - //EndpointApi::WS2PS => 2, - //EndpointApi::WS2PTOR => 3, - //EndpointApi::DASA => 4, - //EndpointApi::BMA => 5, - //EndpointApi::BMAS => 6, - _ => 0, - } -} - -pub fn get_endpoints_for_api(db: &Connection, api: &NetworkEndpointApi) -> Vec<EndpointV1> { - let mut cursor: Cursor = db - .prepare("SELECT hash_full_id, status, node_id, pubkey, api, version, endpoint, last_check FROM endpoints WHERE api=? ORDER BY status DESC;") - .expect("get_endpoints_for_api() : Error in SQL request !") - .cursor(); - - cursor - .bind(&[Value::Integer(api_to_integer(&api))]) - .expect("get_endpoints_for_api() : Error in cursor binding !"); - let mut endpoints = Vec::new(); - while let Some(row) = cursor - .next() - .expect("get_endpoints_for_api() : Error in cursor.next()") - { - let raw_ep = row[6].as_string().unwrap().to_string(); - let ep_issuer = - PubKey::Ed25519(ed25519::PublicKey::from_base58(row[3].as_string().unwrap()).unwrap()); - let mut ep = match EndpointV1::parse_from_raw( - &raw_ep, - ep_issuer, - row[1].as_integer().unwrap() as u32, - row[7].as_integer().unwrap() as u64, - ) { - Ok(ep) => ep, - Err(e) => panic!(format!( - "Fail to parse endpoint : {} (Error: {:?})", - raw_ep, e - )), - }; - ep.status = row[1].as_integer().unwrap() as u32; - ep.last_check = row[7].as_integer().unwrap() as u64; - - endpoints.push(ep); - } - endpoints -} - -pub fn write_endpoint( - db: &Connection, - endpoint: &EndpointV1, - new_status: u32, - new_last_check: u64, -) { - let hash_full_id = endpoint - .node_full_id() - .expect("Fail to write endpoint : node_full_id() return None !") - .sha256(); - // Check if endpoint it's already written - let mut cursor: Cursor = db - .prepare("SELECT status FROM endpoints WHERE hash_full_id=? ORDER BY status DESC;") - .expect("write_endpoint() : Error in SQL request !") - .cursor(); - cursor - .bind(&[Value::String(hash_full_id.to_string())]) - .expect("write_endpoint() : Error in cursor binding !"); - - // If endpoint it's already written, update status - if let Some(row) = cursor - .next() - .expect("write_endpoint() : Error in cursor.next()") - { - if row[0].as_integer().expect("fail to read ep status !") as u32 != endpoint.status { - db.execute(format!( - "UPDATE endpoints SET status={} WHERE hash_full_id='{}'", - endpoint.status, hash_full_id - )) - .expect("Fail to parse SQL request update endpoint status !"); - } - } else { - let ep_v10 = endpoint; - db - .execute( - format!( - "INSERT INTO endpoints (hash_full_id, status, node_id, pubkey, api, version, endpoint, last_check) VALUES ('{}', {}, {}, '{}', {}, {}, '{}', {});", - ep_v10.hash_full_id.expect("ep_v10.hash_full_id = None"), new_status, ep_v10.node_id.expect("ep_v10.node_id = None").0, - ep_v10.issuer.to_string(), api_to_integer(&ep_v10.api), - 1, ep_v10.raw_endpoint, new_last_check - ) - ) - .expect("Fail to parse SQL request INSERT endpoint !"); - } -} diff --git a/lib/modules/ws2p-v1-legacy/ws2p_requests.rs b/lib/modules/ws2p-v1-legacy/ws2p_requests.rs deleted file mode 100644 index 7ba0058b69cc05270e25207f0c8283e0d2fd94ec..0000000000000000000000000000000000000000 --- a/lib/modules/ws2p-v1-legacy/ws2p_requests.rs +++ /dev/null @@ -1,37 +0,0 @@ -use duniter_network::requests::OldNetworkRequest; - -pub fn network_request_to_json(request: &OldNetworkRequest) -> serde_json::Value { - let (request_id, request_type, request_params) = match *request { - OldNetworkRequest::GetCurrent(ref req_full_id) => (req_full_id.1, "CURRENT", json!({})), - OldNetworkRequest::GetBlocks(ref req_full_id, count, from_mumber) => ( - req_full_id.1, - "BLOCKS_CHUNK", - json!({ - "count": count, - "fromNumber": from_mumber - }), - ), - OldNetworkRequest::GetRequirementsPending(ref req_full_id, min_cert) => ( - req_full_id.1, - "WOT_REQUIREMENTS_OF_PENDING", - json!({ "minCert": min_cert }), - ), - OldNetworkRequest::GetConsensus(_) => { - panic!("GetConsensus() request must be not convert to json !"); - } - OldNetworkRequest::GetHeadsCache(_) => { - panic!("GetHeadsCache() request must be not convert to json !"); - } - OldNetworkRequest::GetEndpoints(_) => { - panic!("GetEndpoints() request must be not convert to json !"); - } - }; - - json!({ - "reqId": request_id, - "body" : { - "name": request_type, - "params": request_params - } - }) -} diff --git a/lib/tools/common-tools/src/lib.rs b/lib/tools/common-tools/src/lib.rs index f726482e81abc33714cdd6568f1ed7c61dcef69a..be0eee5c79ed280a92f9d2804510bb618aa28e8f 100644 --- a/lib/tools/common-tools/src/lib.rs +++ b/lib/tools/common-tools/src/lib.rs @@ -26,6 +26,13 @@ unused_import_braces )] +use std::fs::File; +use std::io::Read; +use std::io::Write; +use std::path::Path; +use std::time::SystemTime; +use std::time::UNIX_EPOCH; + /// Interrupts the program and log error message /// WARNING: this macro must not be called before the logger is initialized ! #[macro_export] @@ -44,14 +51,35 @@ macro_rules! fatal_error { }); } -/*macro_rules! error { - (target: $target:expr, $($arg:tt)+) => ( - log!(target: $target, $crate::Level::Error, $($arg)+); - ); - ($($arg:tt)+) => ( - log!($crate::Level::Error, $($arg)+); - ) -}*/ +#[inline] +/// Get current timestamp in seconds +pub fn current_timestamp() -> u64 { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("SystemTime::duration_since failed") + .as_secs() +} + +/// Read bin file +pub fn read_bin_file(file_path: &Path) -> Result<Vec<u8>, std::io::Error> { + let mut file = File::open(file_path)?; + if file.metadata()?.len() == 0 { + Ok(vec![]) + } else { + let mut bin_datas = Vec::new(); + file.read_to_end(&mut bin_datas)?; + + Ok(bin_datas) + } +} + +/// Write bin file +pub fn write_bin_file(file_path: &Path, datas: &[u8]) -> Result<(), std::io::Error> { + let mut file = File::create(file_path)?; + file.write_all(&datas[..])?; + + Ok(()) +} /// Unescape backslash pub fn unescape_str(source: &str) -> String { diff --git a/lib/tools/network-documents/Cargo.toml b/lib/tools/network-documents/Cargo.toml index d53595f8fac4df3824c306b366cda94e3f5c14d8..f218c13302f30109c8c6db85e3d541bb759b1f47 100644 --- a/lib/tools/network-documents/Cargo.toml +++ b/lib/tools/network-documents/Cargo.toml @@ -18,8 +18,7 @@ dubp-documents= { path = "../documents" } hex = "0.3.*" pest = "2.1.0" pest_derive = "2.1.0" -serde = "1.0.*" -serde_derive = "1.0.*" +serde = { version = "1.0.*", features = ["derive"] } serde_json = "1.0.*" [dev-dependencies] diff --git a/lib/tools/network-documents/src/lib.rs b/lib/tools/network-documents/src/lib.rs index dd95a0f3487a9171c1764b98251a262003f00b6c..a5eeec12b3471e759b21ba937929b3fbf28f0d4d 100644 --- a/lib/tools/network-documents/src/lib.rs +++ b/lib/tools/network-documents/src/lib.rs @@ -30,8 +30,6 @@ extern crate pest_derive; #[cfg(test)] #[macro_use] extern crate pretty_assertions; -#[macro_use] -extern crate serde_derive; pub mod network_endpoint; pub mod network_head; @@ -48,6 +46,7 @@ use dup_crypto::hashs::*; use dup_crypto::keys::*; use pest::iterators::Pair; use pest::Parser; +use serde::{Deserialize, Serialize}; use std::fmt::{Display, Error, Formatter}; #[derive(Parser)] @@ -116,7 +115,7 @@ impl<'a> From<&'a str> for NodeId { } } -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[derive(Copy, Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] /// Complete identifier of a duniter node. pub struct NodeFullId(pub NodeId, pub PubKey); diff --git a/lib/tools/network-documents/src/network_head.rs b/lib/tools/network-documents/src/network_head.rs index b3d50090775474171623cb3fdc09d3dab5d3eead..fddce933d645e1a1c5b5c7421af59aeef17893fd 100644 --- a/lib/tools/network-documents/src/network_head.rs +++ b/lib/tools/network-documents/src/network_head.rs @@ -21,6 +21,7 @@ use crate::{NodeFullId, NodeId}; use dubp_documents::blockstamp::*; use dup_crypto::bases::BaseConvertionError; use dup_crypto::keys::*; +use serde::{Deserialize, Serialize}; use serde_json; use std::collections::HashMap; use std::num::ParseIntError; diff --git a/lib/tools/network-documents/src/network_head_v2.rs b/lib/tools/network-documents/src/network_head_v2.rs index f8e8b596f360b932042c026cb132f1c046425d21..cd113c9fc076c5da0e26804f24694a79b56e2131 100644 --- a/lib/tools/network-documents/src/network_head_v2.rs +++ b/lib/tools/network-documents/src/network_head_v2.rs @@ -19,6 +19,7 @@ use crate::NodeId; use dubp_documents::blockstamp::*; use dup_crypto::bases::BaseConvertionError; use dup_crypto::keys::*; +use serde::{Deserialize, Serialize}; use std::cmp::Ordering; use std::num::ParseIntError; use std::ops::Deref;