diff --git a/Cargo.lock b/Cargo.lock index 9d5feab3c0c49df44bdc1e7d604f4be71e507561..73d671bfc9e556e3726f32884b3d78b403cdc2c9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -319,6 +319,7 @@ dependencies = [ name = "duniter-network" version = "0.1.0-a0.1" dependencies = [ + "byteorder 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "duniter-crypto 0.2.0-a0.1", "duniter-documents 0.8.0-a0.1", "duniter-module 0.1.0-a0.1", diff --git a/network/Cargo.toml b/network/Cargo.toml index 0a7a6e99dba9b53f3ab4cb6acb446b12ac4093be..f9e6c5df9d36b1a7cf1fcfef00fa9d8e2bd0a484 100644 --- a/network/Cargo.toml +++ b/network/Cargo.toml @@ -9,6 +9,7 @@ license = "AGPL-3.0" path = "lib.rs" [dependencies] +byteorder = "1.2.3" duniter-crypto = { path = "../crypto" } duniter-documents = { path = "../documents" } duniter-module = { path = "../module" } diff --git a/network/lib.rs b/network/lib.rs index 466f4476365ee92bba234d180f80240d51359130..4df1a8168d0ca02df5611f09e6f2b95cdc57e9c6 100644 --- a/network/lib.rs +++ b/network/lib.rs @@ -27,6 +27,7 @@ extern crate lazy_static; #[macro_use] extern crate serde_derive; +extern crate byteorder; extern crate crypto; extern crate duniter_crypto; extern crate duniter_documents; @@ -337,8 +338,8 @@ mod tests { let node_id = NodeUUID(u32::from_str_radix("c1c39a0a", 16).unwrap()); let full_id = NodeFullId(node_id, issuer); assert_eq!( - NetworkEndpoint::parse_from_raw("WS2P c1c39a0a i3.ifee.fr 80 /ws2p", issuer, 0, 0), - Some(NetworkEndpoint::V1(NetworkEndpointV1 { + NetworkEndpoint::parse_from_raw("WS2P c1c39a0a i3.ifee.fr 80 /ws2p", issuer, 0, 0, 1), + Ok(NetworkEndpoint::V10(NetworkEndpointV10 { version: 1, issuer, api: NetworkEndpointApi(String::from("WS2P")), @@ -363,8 +364,8 @@ mod tests { let node_id = NodeUUID(u32::from_str_radix("cb06a19b", 16).unwrap()); let full_id = NodeFullId(node_id, issuer); assert_eq!( - NetworkEndpoint::parse_from_raw("WS2P cb06a19b g1.imirhil.fr 53012 /", issuer, 0, 0), - Some(NetworkEndpoint::V1(NetworkEndpointV1 { + NetworkEndpoint::parse_from_raw("WS2P cb06a19b g1.imirhil.fr 53012 /", issuer, 0, 0, 1), + Ok(NetworkEndpoint::V10(NetworkEndpointV10 { version: 1, issuer, api: NetworkEndpointApi(String::from("WS2P")), diff --git a/network/network_endpoint.rs b/network/network_endpoint.rs index 6e5ccc23701123ba3ff570ba11a4dfafc2811d00..85180159f25b470db77da34585602b6298e14331 100644 --- a/network/network_endpoint.rs +++ b/network/network_endpoint.rs @@ -24,8 +24,21 @@ extern crate serde; use self::regex::Regex; use super::{NodeFullId, NodeUUID}; +use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use duniter_crypto::keys::PubKey; use duniter_documents::Hash; +use std::io::Cursor; +use std::mem; +use std::net::{AddrParseError, Ipv4Addr, Ipv6Addr}; +use std::num::ParseIntError; +use std::str::FromStr; + +/// Total size of all fixed size fields of an EndpointV11 +pub static ENDPOINTV11_FIXED_SIZE: &'static usize = &9; +/// Maximum number of network features +pub static MAX_NETWORK_FEATURES_COUNT: &'static usize = &2040; +/// Maximum number of api features +pub static MAX_API_FEATURES_COUNT: &'static usize = &2040; lazy_static! { #[derive(Debug)] @@ -35,13 +48,81 @@ lazy_static! { ).unwrap(); } +#[derive(Debug, Clone, PartialEq, Eq)] +/// ParseEndpointError +pub enum ParseEndpointError { + /// VersionNotSupported + VersionNotSupported(), + /// WrongV10Format + WrongV10Format(), + /// WrongV11Format (human-readable explanation) + WrongV11Format(String), + /// ApiNameTooLong + ApiNameTooLong(), + /// ParseIntError + ParseIntError(ParseIntError), + /// UnknowNetworkFeature (feature name) + UnknowNetworkFeature(String), + /// Maximum number of network features exceeded + MaxNetworkFeatures(), + /// Maximum number of api features exceeded + MaxApiFeatures(), + /// UnknowApiFeature (feature name) + UnknowApiFeature(String), + /// TooHighApiFeature + TooHighApiFeature(), + /// IP Parse error + AddrParseError(AddrParseError), +} + +impl From<ParseIntError> for ParseEndpointError { + fn from(e: ParseIntError) -> Self { + ParseEndpointError::ParseIntError(e) + } +} + +impl From<AddrParseError> for ParseEndpointError { + fn from(e: AddrParseError) -> Self { + ParseEndpointError::AddrParseError(e) + } +} + +#[derive(Debug)] +/// Error when converting a byte vector to Endpoint +pub enum EndpointReadBytesError { + /// Bytes vector is too short + TooShort(), + /// Bytes vector is too long + TooLong(), + /// Wrong api datas Length + WrongApiDatasLen(), + /// Unknow api name + UnknowApiName(), + /// IoError + IoError(::std::io::Error), + /// FromUtf8Error + FromUtf8Error(::std::string::FromUtf8Error), +} + +impl From<::std::io::Error> for EndpointReadBytesError { + fn from(e: ::std::io::Error) -> Self { + EndpointReadBytesError::IoError(e) + } +} + +impl From<::std::string::FromUtf8Error> for EndpointReadBytesError { + fn from(e: ::std::string::FromUtf8Error) -> Self { + EndpointReadBytesError::FromUtf8Error(e) + } +} + #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] /// Identifies the API of an endpoint pub struct NetworkEndpointApi(pub String); #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] /// Endpoint v1 -pub struct NetworkEndpointV1 { +pub struct NetworkEndpointV10 { /// API version pub version: usize, /// API Name @@ -66,20 +147,604 @@ pub struct NetworkEndpointV1 { pub last_check: u64, } +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +/// Api know by Duniter +pub enum ApiKnownByDuniter { + /// BASIC_MERKLED_API + BMA(), + /// WebSocket To Peer + WS2P(), + /// GraphQL Verification Api + GVA(), + /// Duniter Advanced Statistic Api + DASA(), +} + +impl ApiKnownByDuniter { + /// Convert ApiKnownByDuniter is their 8-bit binary value + pub fn into_u8(self) -> u8 { + match self { + ApiKnownByDuniter::BMA() => 0u8, + ApiKnownByDuniter::WS2P() => 1u8, + ApiKnownByDuniter::GVA() => 2u8, + ApiKnownByDuniter::DASA() => 3u8, + } + } +} + +impl ToString for ApiKnownByDuniter { + fn to_string(&self) -> String { + match *self { + ApiKnownByDuniter::BMA() => String::from("BMA"), + ApiKnownByDuniter::WS2P() => String::from("WS2P"), + ApiKnownByDuniter::GVA() => String::from("GVA"), + ApiKnownByDuniter::DASA() => String::from("DASA"), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +/// Identifies the API of an endpointV2 +pub enum EndpointV11Api { + /// Api name is an 8-bit binary value + Bin(ApiKnownByDuniter), + /// Api name is a string utf8 + Str(String), +} + +impl FromStr for EndpointV11Api { + type Err = ParseEndpointError; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + match s { + "BMA" => Ok(EndpointV11Api::Bin(ApiKnownByDuniter::BMA())), + "WS2P" => Ok(EndpointV11Api::Bin(ApiKnownByDuniter::WS2P())), + "GVA" => Ok(EndpointV11Api::Bin(ApiKnownByDuniter::GVA())), + "DASA" => Ok(EndpointV11Api::Bin(ApiKnownByDuniter::DASA())), + _ => { + if s.len() <= ::std::u8::MAX as usize { + Ok(EndpointV11Api::Str(String::from(s))) + } else { + Err(ParseEndpointError::ApiNameTooLong()) + } + } + } + } +} + +impl ToString for EndpointV11Api { + fn to_string(&self) -> String { + match *self { + EndpointV11Api::Bin(ref api_bin_name) => api_bin_name.to_string(), + EndpointV11Api::Str(ref api_name) => api_name.clone(), + } + } +} + +impl EndpointV11Api { + /// Get size of api name field + pub fn size(&self) -> u8 { + match *self { + EndpointV11Api::Bin(_) => 0u8, + EndpointV11Api::Str(ref api_name) => api_name.len() as u8, + } + } + /// Convert api name into bytes vector + pub fn into_bytes(&self) -> Vec<u8> { + match *self { + EndpointV11Api::Bin(api_bin_name) => vec![api_bin_name.into_u8()], + EndpointV11Api::Str(ref api_name) => api_name.as_bytes().to_vec(), + } + } + /// Get api from bytes + pub fn api_from_bytes( + api_size: usize, + api_datas: &[u8], + ) -> Result<EndpointV11Api, EndpointReadBytesError> { + if api_size > 0 { + if api_datas.len() == api_size { + Ok(EndpointV11Api::Str(String::from_utf8(api_datas.to_vec())?)) + } else { + Err(EndpointReadBytesError::WrongApiDatasLen()) + } + } else if api_datas.len() == 1 { + match api_datas[0] { + 0u8 => Ok(EndpointV11Api::Bin(ApiKnownByDuniter::BMA())), + 1u8 => Ok(EndpointV11Api::Bin(ApiKnownByDuniter::WS2P())), + 2u8 => Ok(EndpointV11Api::Bin(ApiKnownByDuniter::GVA())), + 3u8 => Ok(EndpointV11Api::Bin(ApiKnownByDuniter::DASA())), + _ => Err(EndpointReadBytesError::UnknowApiName()), + } + } else { + Err(EndpointReadBytesError::WrongApiDatasLen()) + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +/// Network features +pub struct EndpointV11NetworkFeatures(pub Vec<u8>); + +impl EndpointV11NetworkFeatures { + /// Parse network features from utf8 string's array + pub fn from_str_array( + str_array: &[&str], + ) -> Result<EndpointV11NetworkFeatures, ParseEndpointError> { + let mut network_features = 0u8; + for nf_str in str_array { + match *nf_str { + "IP4" => network_features += 1u8, + "IP6" => network_features += 2u8, + "TLS" => network_features += 4u8, + "TOR" => network_features += 8u8, + &_ => { + return Err(ParseEndpointError::UnknowNetworkFeature(String::from( + *nf_str, + ))) + } + } + } + Ok(EndpointV11NetworkFeatures(vec![network_features])) + } + /// Network features size + pub fn size(&self) -> u8 { + self.0.len() as u8 + } + /// Convert Self into bytes + pub fn into_bytes(&self) -> &[u8] { + &self.0 + } + /// network feature ip_v4 is enable ? + pub fn ip_v4(&self) -> bool { + self.0[0] & 0b0000_0001 == 1u8 + } + /// network feature ip_v6 is enable ? + pub fn ip_v6(&self) -> bool { + self.0[0] & 0b0000_0010 == 2u8 + } + /// TLS feature is enable ? + pub fn tls(&self) -> bool { + self.0[0] & 0b0000_0100 == 4u8 + } + /// TOR feature is enable ? + pub fn tor(&self) -> bool { + self.0[0] & 0b0000_1000 == 8u8 + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +/// Endpoint v2 +pub struct EndpointV11 { + /// API Name + pub api: EndpointV11Api, + /// API version + pub api_version: u16, + /// Network features + pub network_features: EndpointV11NetworkFeatures, + /// API features + pub api_features: Vec<u8>, + /// IPv4 + pub ip_v4: Option<Ipv4Addr>, + /// IPv6 + pub ip_v6: Option<Ipv6Addr>, + /// hostname + pub host: Option<String>, + /// port number + pub port: u16, + /// Optional path + pub path: Option<String>, + /// Accessibility status of this endpoint (updated regularly) + pub status: u32, + /// Timestamp of the last connection attempt to this endpoint + pub last_check: u64, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +/// Size informations of Endpoint v2 +pub struct EndpointV11Size { + /// Api nalme size + pub api_size: u8, + /// Hostname size + pub host_size: u8, + /// Optional path size + pub path_size: u8, + /// Network features size + pub nf_size: u8, + /// Network feature ip_v4 + pub ip_v4: bool, + /// Network feature ip_v6 + pub ip_v6: bool, + /// Api features size + pub af_size: u8, +} + +impl EndpointV11Size { + /// Compute total size of endpoint in binary format + pub fn total_size(self) -> usize { + let mut total_size = self.api_size as usize + + self.host_size as usize + + self.path_size as usize + + self.nf_size as usize + + self.af_size as usize + + ENDPOINTV11_FIXED_SIZE; + if self.api_size == 0u8 { + total_size += 1; + } + if self.ip_v4 { + total_size += 4; + } + if self.ip_v6 { + total_size += 16; + } + total_size + } +} + +impl EndpointV11 { + /// Generate endpoint url + pub fn get_url(&self, get_protocol: bool, supported_ip_v6: bool) -> Option<String> { + let protocol = self.api.to_string(); + let tls = match self.port { + 443 => "s", + _ => "", + }; + let host = if let Some(ref host) = self.host { + host.clone() + } else if supported_ip_v6 && self.ip_v6.is_some() { + let ip_v6 = self.ip_v6.unwrap(); + format!("{}", ip_v6) + } else if self.ip_v4.is_some() { + let ip_v4 = self.ip_v4.unwrap(); + format!("{}", ip_v4) + } else { + // Unreacheable endpoint + return None; + }; + let path = match self.path { + Some(ref path_string) => path_string.clone(), + None => String::new(), + }; + if get_protocol { + Some(format!( + "{}{}://{}:{}/{}", + protocol, tls, host, self.port, path + )) + } else { + Some(format!("{}:{}/{}", host, self.port, path)) + } + } + /// get size of endpoint for binary format + pub fn compute_endpoint_size(&self) -> EndpointV11Size { + EndpointV11Size { + api_size: self.api.size(), + host_size: if let Some(ref host) = self.host { + host.len() as u8 + } else { + 0u8 + }, + path_size: if let Some(ref path) = self.path { + path.len() as u8 + } else { + 0u8 + }, + nf_size: self.network_features.size(), + ip_v4: self.network_features.ip_v4(), + ip_v6: self.network_features.ip_v6(), + af_size: self.api_features.len() as u8, + } + } + /// Convert endpoint into bytes vector + pub fn into_bytes(self) -> Vec<u8> { + let endpoint_size = self.compute_endpoint_size(); + let mut binary_endpoint = Vec::with_capacity(endpoint_size.total_size()); + binary_endpoint.push(endpoint_size.api_size); + binary_endpoint.push(endpoint_size.host_size); + binary_endpoint.push(endpoint_size.path_size); + binary_endpoint.append(&mut self.api.into_bytes()); + // api_version + let mut buffer = [0u8; mem::size_of::<u16>()]; + buffer + .as_mut() + .write_u16::<BigEndian>(self.api_version) + .expect("Unable to write"); + binary_endpoint.extend_from_slice(&buffer); + // nf_size + binary_endpoint.push(endpoint_size.nf_size); + // network_features + binary_endpoint.extend_from_slice(&self.network_features.into_bytes()); + binary_endpoint.push(endpoint_size.af_size); + binary_endpoint.append(&mut self.api_features.clone()); + if let Some(ip_v4) = self.ip_v4 { + binary_endpoint.extend_from_slice(&ip_v4.octets()); + } + if let Some(ip_v6) = self.ip_v6 { + binary_endpoint.extend_from_slice(&ip_v6.octets()); + } + if let Some(host) = self.host { + binary_endpoint.extend_from_slice(host.as_bytes()); + } + // port + let mut buffer = [0u8; mem::size_of::<u16>()]; + buffer + .as_mut() + .write_u16::<BigEndian>(self.port) + .expect("Unable to write"); + binary_endpoint.extend_from_slice(&buffer); + // path + if let Some(path) = self.path { + binary_endpoint.extend_from_slice(path.as_bytes()); + } + binary_endpoint + } + /// Create endpoint from bytes vector + pub fn from_bytes(binary_ep: &[u8]) -> Result<EndpointV11, EndpointReadBytesError> { + if binary_ep.len() < *ENDPOINTV11_FIXED_SIZE { + return Err(EndpointReadBytesError::TooShort()); + } + let api_size = binary_ep[0] as usize; + let host_size = binary_ep[1] as usize; + let path_size = binary_ep[2] as usize; + if binary_ep.len() < (*ENDPOINTV11_FIXED_SIZE + api_size + host_size + path_size) { + return Err(EndpointReadBytesError::TooShort()); + } + let mut index: usize = 3; + // read api + let api_datas = if api_size == 0 { + index += 1; + &binary_ep[index - 1..index] + } else { + index += api_size; + &binary_ep[index - api_size..index] + }; + let api = EndpointV11Api::api_from_bytes(api_size, api_datas)?; + // read api_version + let mut api_version_bytes = Cursor::new(binary_ep[index..index + 2].to_vec()); + index += 2; + let api_version = api_version_bytes.read_u16::<BigEndian>()?; + // read nf_size + let nf_size = binary_ep[index] as usize; + index += 1; + if binary_ep.len() < index + nf_size + 1 { + return Err(EndpointReadBytesError::TooShort()); + } + // read network_features + let network_features = + EndpointV11NetworkFeatures(binary_ep[index..index + nf_size].to_vec()); + index += nf_size; + // read af_size + let af_size = binary_ep[index] as usize; + index += 1; + if binary_ep.len() < index + af_size + 1 { + return Err(EndpointReadBytesError::TooShort()); + } + // read api_features + let api_features = binary_ep[index..index + nf_size].to_vec(); + index += af_size; + // read ip_v4 + let ip_v4 = network_features.ip_v4(); + if binary_ep.len() < index + 4 && ip_v4 { + return Err(EndpointReadBytesError::TooShort()); + } + let ip_v4 = if ip_v4 { + index += 4; + Some(Ipv4Addr::new( + binary_ep[index - 4], + binary_ep[index - 3], + binary_ep[index - 2], + binary_ep[index - 1], + )) + } else { + None + }; + // read ip_v6 + let ip_v6 = network_features.ip_v6(); + if binary_ep.len() < index + 16 && ip_v6 { + return Err(EndpointReadBytesError::TooShort()); + } + let ip_v6 = if ip_v6 { + index += 16; + let mut ip_v6_datas: [u8; 16] = [0u8; 16]; + ip_v6_datas.copy_from_slice(&binary_ep[index - 16..index]); + Some(Ipv6Addr::from(ip_v6_datas)) + } else { + None + }; + // read host + if binary_ep.len() < index + host_size + 2 { + return Err(EndpointReadBytesError::TooShort()); + } + let host = if host_size > 0 { + index += host_size; + Some(String::from_utf8( + binary_ep[index - host_size..index].to_vec(), + )?) + } else { + None + }; + // read port + let mut port_bytes = Cursor::new((&binary_ep[index..index + 2]).to_vec()); + index += 2; + let port = port_bytes.read_u16::<BigEndian>()?; + // read path + if binary_ep.len() < index + path_size { + return Err(EndpointReadBytesError::TooShort()); + } else if binary_ep.len() > index + path_size { + return Err(EndpointReadBytesError::TooLong()); + } + let path = if path_size > 0 { + Some(String::from_utf8( + binary_ep[index..index + path_size].to_vec(), + )?) + } else { + None + }; + Ok(EndpointV11 { + api, + api_version, + network_features, + api_features, + ip_v4, + ip_v6, + host, + port, + path, + status: 0, + last_check: 0, + }) + } + /// parse from ut8 format + pub fn parse_from_raw( + raw_endpoint: &str, + status: u32, + last_check: u64, + ) -> Result<NetworkEndpoint, ParseEndpointError> { + let raw_ep_elements: Vec<&str> = raw_endpoint.split(' ').collect(); + if raw_ep_elements.len() >= 6 { + let api = EndpointV11Api::from_str(raw_ep_elements[0])?; + let api_version: u16 = raw_ep_elements[1].parse()?; + let network_features_count: usize = raw_ep_elements[2].parse()?; + if network_features_count > *MAX_NETWORK_FEATURES_COUNT { + Err(ParseEndpointError::MaxNetworkFeatures()) + } else if raw_ep_elements.len() >= 6 + network_features_count { + let network_features = EndpointV11NetworkFeatures::from_str_array( + &raw_ep_elements[3..(3 + network_features_count)], + )?; + let api_features_count: usize = + raw_ep_elements[3 + network_features_count].parse()?; + if network_features_count > *MAX_API_FEATURES_COUNT { + Err(ParseEndpointError::MaxApiFeatures()) + } else { + let mut af_bytes_count = network_features_count / 8; + if network_features_count % 8 != 0 { + af_bytes_count += 1; + } + let mut api_features = vec![0u8; af_bytes_count]; + if raw_ep_elements.len() < 4 + network_features_count + api_features_count { + return Err(ParseEndpointError::WrongV11Format(String::from( + "All api features must be declared !", + ))); + } + for i in (4 + network_features_count) + ..(4 + network_features_count + api_features_count) + { + if let Ok(feature) = raw_ep_elements[i].parse::<usize>() { + if feature > *MAX_API_FEATURES_COUNT { + return Err(ParseEndpointError::TooHighApiFeature()); + } + let byte_index = feature / 8; + let feature = (feature % 8) as u8; + api_features[byte_index] += feature.pow(2); + } else if let EndpointV11Api::Bin(know_api) = api { + if let ApiKnownByDuniter::WS2P() = know_api { + match raw_ep_elements[i] { + "DEF" => api_features[0] += 1u8, + "LOW" => api_features[0] += 2u8, + "ABF" => api_features[0] += 4u8, + _ => { + return Err(ParseEndpointError::UnknowApiFeature( + String::from(raw_ep_elements[i]), + )) + } + } + } else { + return Err(ParseEndpointError::UnknowApiFeature(String::from( + raw_ep_elements[i], + ))); + } + } else { + return Err(ParseEndpointError::UnknowApiFeature(String::from( + raw_ep_elements[i], + ))); + } + } + let mut index = 4 + network_features_count + api_features_count; + let ip_v4 = if network_features.ip_v4() { + let ip = Ipv4Addr::from_str(raw_ep_elements[index])?; + index += 1; + Some(ip) + } else { + None + }; + let ip_v6 = if network_features.ip_v6() { + let ip = Ipv6Addr::from_str(raw_ep_elements[index])?; + index += 1; + Some(ip) + } else { + None + }; + let (host, port) = if let Ok(port) = raw_ep_elements[index].parse::<u16>() { + index += 1; + (None, Some(port)) + } else if raw_ep_elements.len() > index { + index += 2; + if let Ok(port) = raw_ep_elements[index - 1].parse::<u16>() { + (Some(String::from(raw_ep_elements[index - 2])), Some(port)) + } else { + (None, None) + } + } else { + (None, None) + }; + if port.is_none() { + Err(ParseEndpointError::WrongV11Format(String::from( + "Missing port or is not integer !", + ))) + } else { + let port = port.unwrap(); + let path = if raw_ep_elements.len() > index { + index += 1; + Some(String::from(raw_ep_elements[index - 1])) + } else { + None + }; + if raw_ep_elements.len() > index { + Err(ParseEndpointError::WrongV11Format(String::from( + "Too many fields !", + ))) + } else { + Ok(NetworkEndpoint::V11(EndpointV11 { + api, + api_version, + network_features, + api_features: api_features.to_vec(), + ip_v4, + ip_v6, + host, + port, + path, + status, + last_check, + })) + } + } + } + } else { + Err(ParseEndpointError::WrongV11Format(String::from( + "All network features must be declared !", + ))) + } + } else { + Err(ParseEndpointError::WrongV11Format(String::from( + "An endpoint must contain at least 6 elements", + ))) + } + } +} + #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] /// Endpoint pub enum NetworkEndpoint { /// Endpoint v1 - V1(NetworkEndpointV1), + V10(NetworkEndpointV10), /// Endpoint v2 - V2(), + V11(EndpointV11), } impl ToString for NetworkEndpoint { fn to_string(&self) -> String { match *self { - NetworkEndpoint::V1(ref ep) => ep.raw_endpoint.clone(), - _ => panic!("Endpoint version is not supported !"), + NetworkEndpoint::V10(ref ep) => ep.raw_endpoint.clone(), + NetworkEndpoint::V11(ref _ep_v11) => panic!("Endpoint version is not supported !"), } } } @@ -88,21 +753,21 @@ impl NetworkEndpoint { /// Accessors providing API name pub fn api(&self) -> NetworkEndpointApi { match *self { - NetworkEndpoint::V1(ref ep) => ep.api.clone(), + NetworkEndpoint::V10(ref ep) => ep.api.clone(), _ => panic!("Endpoint version is not supported !"), } } /// Accessors providing node unique identifier pub fn node_uuid(&self) -> Option<NodeUUID> { match *self { - NetworkEndpoint::V1(ref ep) => ep.node_id, + NetworkEndpoint::V10(ref ep) => ep.node_id, _ => panic!("Endpoint version is not supported !"), } } /// Accessors providing node public key pub fn pubkey(&self) -> PubKey { match *self { - NetworkEndpoint::V1(ref ep) => ep.issuer, + NetworkEndpoint::V10(ref ep) => ep.issuer, _ => panic!("Endpoint version is not supported !"), } } @@ -116,42 +781,42 @@ impl NetworkEndpoint { /// Accessors providing port number pub fn port(&self) -> usize { match *self { - NetworkEndpoint::V1(ref ep) => ep.port, + NetworkEndpoint::V10(ref ep) => ep.port, _ => panic!("Endpoint version is not supported !"), } } /// Accessors providing raw format pub fn raw(&self) -> String { match *self { - NetworkEndpoint::V1(ref ep) => ep.raw_endpoint.clone(), + NetworkEndpoint::V10(ref ep) => ep.raw_endpoint.clone(), _ => panic!("Endpoint version is not supported !"), } } /// Accessors providing endpoint accessibility status pub fn status(&self) -> u32 { match *self { - NetworkEndpoint::V1(ref ep) => ep.status, + NetworkEndpoint::V10(ref ep) => ep.status, _ => panic!("Endpoint version is not supported !"), } } /// Set status pub fn set_status(&mut self, new_status: u32) { match *self { - NetworkEndpoint::V1(ref mut ep) => ep.status = new_status, + NetworkEndpoint::V10(ref mut ep) => ep.status = new_status, _ => panic!("Endpoint version is not supported !"), } } /// Set last_check pub fn set_last_check(&mut self, new_last_check: u64) { match *self { - NetworkEndpoint::V1(ref mut ep) => ep.last_check = new_last_check, + NetworkEndpoint::V10(ref mut ep) => ep.last_check = new_last_check, _ => panic!("Endpoint version is not supported !"), } } /// Generate endpoint url - pub fn get_url(&self, get_protocol: bool) -> String { + pub fn get_url(&self, get_protocol: bool, supported_ip_v6: bool) -> Option<String> { match *self { - NetworkEndpoint::V1(ref ep) => { + NetworkEndpoint::V10(ref ep) => { let protocol = match &ep.api.0[..] { "WS2P" | "WS2PTOR" => "ws", _ => "http", @@ -165,12 +830,15 @@ impl NetworkEndpoint { None => String::new(), }; if get_protocol { - format!("{}{}://{}:{}/{}", protocol, tls, ep.host, ep.port, path) + Some(format!( + "{}{}://{}:{}/{}", + protocol, tls, ep.host, ep.port, path + )) } else { - format!("{}:{}/{}", ep.host, ep.port, path) + Some(format!("{}:{}/{}", ep.host, ep.port, path)) } } - _ => panic!("Endpoint version is not supported !"), + NetworkEndpoint::V11(ref ep_v11) => ep_v11.get_url(get_protocol, supported_ip_v6), } } /// Parse Endpoint from raw format @@ -179,38 +847,187 @@ impl NetworkEndpoint { issuer: PubKey, status: u32, last_check: u64, - ) -> Option<NetworkEndpoint> { - match ENDPOINT_V1_REGEX.captures(raw_endpoint) { - Some(caps) => { - let node_id = match caps.name("uuid") { - Some(caps_node_id) => match u32::from_str_radix(caps_node_id.as_str(), 16) { - Ok(node_id) => Some(NodeUUID(node_id)), - Err(_) => None, - }, - None => None, - }; - let hash_full_id = match node_id { - Some(node_id_) => Some(NodeFullId(node_id_, issuer).sha256()), - None => None, - }; - Some(NetworkEndpoint::V1(NetworkEndpointV1 { - version: 1, - issuer, - api: NetworkEndpointApi(String::from(&caps["api"])), - node_id, - hash_full_id, - host: String::from(&caps["host"]), - port: caps["port"].parse().unwrap_or(80), - path: match caps.name("path") { - Some(m) => Some(m.as_str().to_string()), + endpoint_version: u16, + ) -> Result<NetworkEndpoint, ParseEndpointError> { + match endpoint_version { + 1 => match ENDPOINT_V1_REGEX.captures(raw_endpoint) { + Some(caps) => { + let node_id = match caps.name("uuid") { + Some(caps_node_id) => { + match u32::from_str_radix(caps_node_id.as_str(), 16) { + Ok(node_id) => Some(NodeUUID(node_id)), + Err(_) => None, + } + } None => None, - }, - raw_endpoint: String::from(raw_endpoint), - status, - last_check, - })) - } - None => None, + }; + let hash_full_id = match node_id { + Some(node_id_) => Some(NodeFullId(node_id_, issuer).sha256()), + None => None, + }; + Ok(NetworkEndpoint::V10(NetworkEndpointV10 { + version: 1, + issuer, + api: NetworkEndpointApi(String::from(&caps["api"])), + node_id, + hash_full_id, + host: String::from(&caps["host"]), + port: caps["port"].parse().unwrap_or(80), + path: match caps.name("path") { + Some(m) => Some(m.as_str().to_string()), + None => None, + }, + raw_endpoint: String::from(raw_endpoint), + status, + last_check, + })) + } + None => Err(ParseEndpointError::WrongV10Format()), + }, + 2 => EndpointV11::parse_from_raw(raw_endpoint, status, last_check), + _ => Err(ParseEndpointError::VersionNotSupported()), } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_and_read_endpoint() { + let str_endpoint = "WS2P 2 1 TLS 3 DEF LOW ABF g1.durs.ifee.fr 443 ws2p"; + let endpoint = EndpointV11 { + api: EndpointV11Api::Bin(ApiKnownByDuniter::WS2P()), + api_version: 2, + network_features: EndpointV11NetworkFeatures(vec![4u8]), + api_features: vec![7u8], + ip_v4: None, + ip_v6: None, + host: Some(String::from("g1.durs.ifee.fr")), + port: 443u16, + path: Some(String::from("ws2p")), + status: 0, + last_check: 0, + }; + assert_eq!( + EndpointV11::parse_from_raw(str_endpoint, 0, 0), + Ok(NetworkEndpoint::V11(endpoint.clone())), + ); + let binary_endpoint = endpoint.clone().into_bytes(); + assert_eq!( + EndpointV11::from_bytes(&binary_endpoint) + .expect("Fail to convert byte vector into endpoint !"), + endpoint, + ) + } + + #[test] + fn test_endpoint_to_bytes() { + let endpoint_v11 = EndpointV11 { + api: EndpointV11Api::Bin(ApiKnownByDuniter::WS2P()), + api_version: 2, + network_features: EndpointV11NetworkFeatures(vec![4u8]), + api_features: vec![7u8], + ip_v4: None, + ip_v6: None, + host: Some(String::from("g1.durs.ifee.fr")), + port: 443u16, + path: Some(String::from("ws2p")), + status: 0, + last_check: 0, + }; + assert_eq!( + endpoint_v11.into_bytes(), + vec![ + 0, 15, 4, 1, 0, 2, 1, 4, 1, 7, 103, 49, 46, 100, 117, 114, 115, 46, 105, 102, 101, + 101, 46, 102, 114, 1, 187, 119, 115, 50, 112, + ], + ) + } + + #[test] + fn test_parse_and_read_endpoint_with_ipv4() { + let str_endpoint = "WS2P 2 2 IP4 TLS 3 DEF LOW ABF 84.16.72.210 443 ws2p"; + let endpoint = EndpointV11 { + api: EndpointV11Api::Bin(ApiKnownByDuniter::WS2P()), + api_version: 2, + network_features: EndpointV11NetworkFeatures(vec![5u8]), + api_features: vec![7u8], + ip_v4: Some(Ipv4Addr::from_str("84.16.72.210").unwrap()), + ip_v6: None, + host: None, + port: 443u16, + path: Some(String::from("ws2p")), + status: 0, + last_check: 0, + }; + assert_eq!( + EndpointV11::parse_from_raw(str_endpoint, 0, 0), + Ok(NetworkEndpoint::V11(endpoint.clone())), + ); + let binary_endpoint = endpoint.clone().into_bytes(); + assert_eq!( + EndpointV11::from_bytes(&binary_endpoint) + .expect("Fail to convert byte vector into endpoint !"), + endpoint + ) + } + + #[test] + fn test_parse_and_read_endpoint_with_ipv6() { + let str_endpoint = "WS2P 2 2 IP6 TLS 3 DEF LOW ABF 2001:41d0:8:c5aa::1 443 ws2p"; + let endpoint = EndpointV11 { + api: EndpointV11Api::Bin(ApiKnownByDuniter::WS2P()), + api_version: 2, + network_features: EndpointV11NetworkFeatures(vec![6u8]), + api_features: vec![7u8], + ip_v4: None, + ip_v6: Some(Ipv6Addr::from_str("2001:41d0:8:c5aa::1").unwrap()), + host: None, + port: 443u16, + path: Some(String::from("ws2p")), + status: 0, + last_check: 0, + }; + assert_eq!( + EndpointV11::parse_from_raw(str_endpoint, 0, 0), + Ok(NetworkEndpoint::V11(endpoint.clone())), + ); + let binary_endpoint = endpoint.clone().into_bytes(); + assert_eq!( + EndpointV11::from_bytes(&binary_endpoint) + .expect("Fail to convert byte vector into endpoint !"), + endpoint + ) + } + + #[test] + fn test_parse_and_read_endpoint_with_ipv4_and_ip_v6() { + let str_endpoint = + "WS2P 2 3 IP4 IP6 TLS 3 DEF LOW ABF 5.135.188.170 2001:41d0:8:c5aa::1 443 ws2p"; + let endpoint = EndpointV11 { + api: EndpointV11Api::Bin(ApiKnownByDuniter::WS2P()), + api_version: 2, + network_features: EndpointV11NetworkFeatures(vec![7u8]), + api_features: vec![7u8], + ip_v4: Some(Ipv4Addr::from_str("5.135.188.170").unwrap()), + ip_v6: Some(Ipv6Addr::from_str("2001:41d0:8:c5aa::1").unwrap()), + host: None, + port: 443u16, + path: Some(String::from("ws2p")), + status: 0, + last_check: 0, + }; + assert_eq!( + EndpointV11::parse_from_raw(str_endpoint, 0, 0), + Ok(NetworkEndpoint::V11(endpoint.clone())), + ); + let binary_endpoint = endpoint.clone().into_bytes(); + assert_eq!( + EndpointV11::from_bytes(&binary_endpoint) + .expect("Fail to convert byte vector into endpoint !"), + endpoint + ) + } +} diff --git a/ws2p/lib.rs b/ws2p/lib.rs index 87ccffe675b9d31326eb1fd753e4da216468676d..ec3fe6bd6d3a22776a5685a53ba0840880da13f6 100644 --- a/ws2p/lib.rs +++ b/ws2p/lib.rs @@ -104,6 +104,7 @@ impl Default for WS2PConf { ), 0, 0, + 1u16, ).unwrap(), NetworkEndpoint::parse_from_raw( "WS2P b48824f0 g1.monnaielibreoccitanie.org 443 /ws2p", @@ -114,6 +115,7 @@ impl Default for WS2PConf { ), 0, 0, + 1u16, ).unwrap(), ], } @@ -536,7 +538,8 @@ impl DuniterModule<DuRsConf, DuniterMessage> for WS2PModule { *node_full_id, *conn_state as u32, uid_option.clone(), - ep.get_url(false), + ep.get_url(false, false) + .expect("Endpoint unreachable !"), ), ); } @@ -577,7 +580,10 @@ impl DuniterModule<DuRsConf, DuniterMessage> for WS2PModule { 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), + ws2p_module.ws2p_endpoints[&ws2p_full_id] + .0 + .get_url(false, false) + .expect("Endpoint unreachable !"), )); } WS2PSignal::WSError(ws2p_full_id) => { @@ -586,7 +592,10 @@ impl DuniterModule<DuRsConf, DuniterMessage> for WS2PModule { 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), + ws2p_module.ws2p_endpoints[&ws2p_full_id] + .0 + .get_url(false, false) + .expect("Endpoint unreachable !"), )); } WS2PSignal::NegociationTimeout(ws2p_full_id) => { @@ -595,7 +604,10 @@ impl DuniterModule<DuRsConf, DuniterMessage> for WS2PModule { 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), + ws2p_module.ws2p_endpoints[&ws2p_full_id] + .0 + .get_url(false, false) + .expect("Endpoint unreachable !"), )); } WS2PSignal::Timeout(ws2p_full_id) => { @@ -604,7 +616,10 @@ impl DuniterModule<DuRsConf, DuniterMessage> for WS2PModule { 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), + ws2p_module.ws2p_endpoints[&ws2p_full_id] + .0 + .get_url(false, false) + .expect("Endpoint unreachable !"), )); } WS2PSignal::PeerCard(_ws2p_full_id, _peer_card, ws2p_endpoints) => { @@ -1062,6 +1077,7 @@ mod tests { ), 1, current_time.as_secs(), + 1, ).expect("Failt to parse test endpoint !"); ws2p_db::write_endpoint(&db, &endpoint, 1, current_time.as_secs()); diff --git a/ws2p/ws2p_connection.rs b/ws2p/ws2p_connection.rs index 3a578a968252096887caac9e042ddbea6cea236e..d4a50aa45f860900f6624aa0be667084ca0acebb 100644 --- a/ws2p/ws2p_connection.rs +++ b/ws2p/ws2p_connection.rs @@ -539,11 +539,12 @@ impl WS2PConnectionMetaDatas { Some(endpoints) => match endpoints.as_array() { Some(array_endpoints) => { for endpoint in array_endpoints { - if let Some(ep) = NetworkEndpoint::parse_from_raw( + if let Ok(ep) = NetworkEndpoint::parse_from_raw( endpoint.as_str().unwrap_or(""), PubKey::Ed25519(pubkey), 0, 0, + 1u16, ) { if ep.api() == NetworkEndpointApi(String::from("WS2P")) { @@ -597,7 +598,7 @@ pub fn connect_to_ws2p_endpoint( key_pair: KeyPairEnum, ) -> ws::Result<()> { // Get endpoint url - let ws_url = endpoint.get_url(true); + let ws_url = endpoint.get_url(true, false).expect("Endpoint unreachable"); // Create WS2PConnectionMetaDatass let mut conn_meta_datas = WS2PConnectionMetaDatas::new( diff --git a/ws2p/ws2p_db.rs b/ws2p/ws2p_db.rs index 5d34ff28aeb62a6c6bf5f08f3df319972295497b..740ef46af0b32fa1820a1c7f176f17949e141d0e 100644 --- a/ws2p/ws2p_db.rs +++ b/ws2p/ws2p_db.rs @@ -76,9 +76,10 @@ pub fn get_endpoints_for_api( ep_issuer, row[1].as_integer().unwrap() as u32, row[7].as_integer().unwrap() as u64, + 1u16, ) { - Some(ep) => ep, - None => panic!(format!("Fail to parse endpoint : {}", raw_ep)), + Ok(ep) => ep, + Err(_) => panic!(format!("Fail to parse endpoint : {}", raw_ep)), }; ep.set_status(row[1].as_integer().unwrap() as u32); ep.set_last_check(row[7].as_integer().unwrap() as u64); @@ -119,14 +120,14 @@ pub fn write_endpoint( hash_full_id )).expect("Fail to parse SQL request update endpoint status !"); } - } else if let NetworkEndpoint::V1(ref ep_v1) = *endpoint { + } else if let NetworkEndpoint::V10(ref ep_v10) = *endpoint { db .execute( format!( "INSERT INTO endpoints (hash_full_id, status, node_id, pubkey, api, version, endpoint, last_check) VALUES ('{}', {}, {}, '{}', {}, {}, '{}', {});", - ep_v1.hash_full_id.expect("ep_v1.hash_full_id = None"), new_status, ep_v1.node_id.expect("ep_v1.node_id = None").0, - ep_v1.issuer.to_string(), api_to_integer(&ep_v1.api), - ep_v1.version, ep_v1.raw_endpoint, new_last_check + 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), + ep_v10.version, ep_v10.raw_endpoint, new_last_check ) ) .expect("Fail to parse SQL request INSERT endpoint !");