use std::io::{BufRead, Error as IoError};
use std::str::FromStr;
use std::sync::Arc;
use hex::FromHex;
use failure::Fail;
use futures::{future::*, Future, Stream};
use parking_lot::Mutex;
use reqwest::r#async::{Chunk, Client, Response};
use url::Url;
use keys::Signature;
use crate::network_config::{NetworkConfig, Seed};
use network_primitives::address::peer_address::PeerAddress;
use network_primitives::address::peer_uri::{PeerUri, PeerUriError};
use network_primitives::networks::{ get_network_info, NetworkId };
use utils::observer::Notifier;
pub enum PeerAddressSeederEvent {
Seeds(Vec<PeerAddress>),
End,
}
#[derive(Fail, Debug)]
pub enum PeerAddressSeederError {
#[fail(display = "The seed list file didn't contain any parseable seed peer address")]
EmptySeedAddresses,
#[fail(display = "The fetching of the seed list file failed with error '{}'", _0)]
FetchError(#[cause] reqwest::Error),
#[fail(display = "Failed while reading a line from the seed list with io::error '{}'", _0)]
IoError(IoError),
#[fail(display = "Seed node address parsing failed with error '{}'", _0)]
PeerUriParsingError(#[cause] PeerUriError),
#[fail(display = "The seed list file didn't contain any parseable signature")]
SignatureMissing,
#[fail(display = "The signature in the file was in a line other than the last one")]
SignatureNotInLastLine,
#[fail(display = "The signature verification for the seed list file failed")]
SignatureVerificationFailed,
#[fail(display = "The remote server responded with status code '{}'", _0)]
UnexpectedHttpStatus(reqwest::StatusCode),
}
impl From<reqwest::Error> for PeerAddressSeederError {
fn from(error: reqwest::Error) -> Self {
PeerAddressSeederError::FetchError(error)
}
}
impl From<PeerUriError> for PeerAddressSeederError {
fn from(error: PeerUriError) -> Self {
PeerAddressSeederError::PeerUriParsingError(error)
}
}
pub struct PeerAddressSeeder {
pub notifier: Arc<Mutex<Notifier<'static, PeerAddressSeederEvent>>>,
}
impl PeerAddressSeeder {
pub fn new() -> Self {
Self {
notifier: Arc::new(Mutex::new(Notifier::new())),
}
}
pub fn collect(&self, network_id: NetworkId, network_config: Arc<NetworkConfig>) {
let network_info = get_network_info(network_id).expect("This was validated by PeerAddressBook::new()");
let additional_seedlists = network_config.additional_seeds().iter()
.filter_map(|seed| {
match seed {
Seed::List(seed_list) => Some(&**seed_list),
Seed::Peer(_) => None,
}
});
let seed_lists = network_info.seed_lists.iter().chain(additional_seedlists);
for seed_list in seed_lists.cloned() {
let notifier = Arc::clone(&self.notifier);
let seed_list_url = seed_list.url().clone();
trace!("Start processing remote seed list: {}", &seed_list.url());
let task = Self::fetch(seed_list.url().clone())
.and_then(move |response_body| {
let mut signature = None;
let mut seed_addresses = Vec::new();
for line in response_body.lines() {
if let Err(e) = line {
return err(PeerAddressSeederError::IoError(e));
}
let line = line.expect("Validated this above");
if signature.is_some() {
return err(PeerAddressSeederError::SignatureNotInLastLine);
}
let line = line.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
match PeerUri::from_str(line) {
Ok(seed_address) => {
match seed_address.as_seed_peer_address() {
Ok(peer_address) => seed_addresses.push(peer_address),
Err(e) => return err(PeerAddressSeederError::PeerUriParsingError(e)),
}
},
_ => signature = Signature::from_hex(line).ok(),
}
}
if seed_addresses.is_empty() {
return err(PeerAddressSeederError::EmptySeedAddresses);
}
if let Some(public_key) = seed_list.public_key() {
if let Some(signature) = signature {
let data = seed_addresses.iter().filter_map(PeerAddress::to_seed_string).collect::<Vec<String>>().join("\n");
let data = data.as_bytes();
if !public_key.verify(&signature, data) {
return err(PeerAddressSeederError::SignatureVerificationFailed)
}
} else { return err(PeerAddressSeederError::SignatureMissing);
}
}
notifier.lock().notify(PeerAddressSeederEvent::Seeds(seed_addresses));
ok(())
})
.map_err(move |err| warn!("Failed to retrieve seed list from {}: {}", seed_list_url, err));
tokio::spawn(task);
}
self.notifier.lock().notify(PeerAddressSeederEvent::End);
}
fn fetch(url: Url) -> impl Future<Item=Chunk, Error=PeerAddressSeederError> {
Client::new().get(url).send()
.map_err(PeerAddressSeederError::from)
.and_then(Self::fetch_callback)
}
fn fetch_callback(res: Response) -> Box<dyn Future<Item=Chunk, Error=PeerAddressSeederError> + Send> {
let status = res.status();
if status == 200 {
Box::new(res.into_body().concat2().map_err(PeerAddressSeederError::from))
} else {
Box::new(err(PeerAddressSeederError::UnexpectedHttpStatus(status)))
}
}
}