use std::{
collections::{HashMap, HashSet},
ops::BitXor,
};
use crate::{
metrics::{Event, Metrics},
Data, PublicKey,
};
pub struct DirectedPeers<PK: PublicKey, A: Data> {
own_id: PK,
outgoing: HashMap<PK, A>,
incoming: HashSet<PK>,
metrics: Metrics,
}
fn should_we_call(own_id: &[u8], remote_id: &[u8]) -> bool {
let xor_sum_parity = (own_id.iter().fold(0u8, BitXor::bitxor)
^ remote_id.iter().fold(0u8, BitXor::bitxor))
.count_ones()
% 2;
match xor_sum_parity == 0 {
true => own_id < remote_id,
false => own_id > remote_id,
}
}
impl<PK: PublicKey, A: Data> DirectedPeers<PK, A> {
pub fn new(own_id: PK, metrics: Metrics) -> Self {
DirectedPeers {
own_id,
outgoing: HashMap::new(),
incoming: HashSet::new(),
metrics,
}
}
pub fn add_peer(&mut self, peer_id: PK, address: A) -> bool {
use Event::*;
match should_we_call(self.own_id.as_ref(), peer_id.as_ref()) {
true => match self.outgoing.insert(peer_id, address).is_none() {
true => {
self.metrics.report_event(NewOutgoing);
true
}
false => false,
},
false => {
if self.incoming.insert(peer_id) {
self.metrics.report_event(NewIncoming);
}
false
}
}
}
pub fn peer_address(&self, peer_id: &PK) -> Option<A> {
self.outgoing.get(peer_id).cloned()
}
pub fn interested(&self, peer_id: &PK) -> bool {
self.incoming.contains(peer_id) || self.outgoing.contains_key(peer_id)
}
pub fn incoming_peers(&self) -> impl Iterator<Item = &PK> {
self.incoming.iter()
}
pub fn outgoing_peers(&self) -> impl Iterator<Item = &PK> {
self.outgoing.keys()
}
pub fn remove_peer(&mut self, peer_id: &PK) {
use Event::*;
if self.incoming.remove(peer_id) {
self.metrics.report_event(DelIncoming);
}
if self.outgoing.remove(peer_id).is_some() {
self.metrics.report_event(DelOutgoing);
}
}
}
#[cfg(test)]
mod tests {
use super::DirectedPeers;
use crate::{
metrics::Metrics,
mock::{key, MockPublicKey},
};
type Address = String;
fn container_with_id() -> (DirectedPeers<MockPublicKey, Address>, MockPublicKey) {
let (id, _) = key();
let container = DirectedPeers::new(id.clone(), Metrics::noop());
(container, id)
}
fn some_address() -> Address {
String::from("43.43.43.43:43000")
}
#[test]
fn exactly_one_direction_attempts_connections() {
let (mut container0, id0) = container_with_id();
let (mut container1, id1) = container_with_id();
let address = some_address();
assert!(container0.add_peer(id1, address.clone()) != container1.add_peer(id0, address));
}
fn container_with_added_connecting_peer(
) -> (DirectedPeers<MockPublicKey, Address>, MockPublicKey) {
let (mut container0, id0) = container_with_id();
let (mut container1, id1) = container_with_id();
let address = some_address();
match container0.add_peer(id1.clone(), address.clone()) {
true => (container0, id1),
false => {
container1.add_peer(id0.clone(), address);
(container1, id0)
}
}
}
fn container_with_added_nonconnecting_peer(
) -> (DirectedPeers<MockPublicKey, Address>, MockPublicKey) {
let (mut container0, id0) = container_with_id();
let (mut container1, id1) = container_with_id();
let address = some_address();
match container0.add_peer(id1.clone(), address.clone()) {
false => (container0, id1),
true => {
container1.add_peer(id0.clone(), address);
(container1, id0)
}
}
}
#[test]
fn no_connecting_on_subsequent_add() {
let (mut container0, id1) = container_with_added_connecting_peer();
let address = some_address();
assert!(!container0.add_peer(id1, address));
}
#[test]
fn peer_address_when_connecting() {
let (container0, id1) = container_with_added_connecting_peer();
assert!(container0.peer_address(&id1).is_some());
}
#[test]
fn no_peer_address_when_nonconnecting() {
let (container0, id1) = container_with_added_nonconnecting_peer();
assert!(container0.peer_address(&id1).is_none());
}
#[test]
fn interested_in_connecting() {
let (container0, id1) = container_with_added_connecting_peer();
assert!(container0.interested(&id1));
}
#[test]
fn interested_in_nonconnecting() {
let (container0, id1) = container_with_added_nonconnecting_peer();
assert!(container0.interested(&id1));
}
#[test]
fn uninterested_in_unknown() {
let (container0, _) = container_with_id();
let (_, id1) = container_with_id();
assert!(!container0.interested(&id1));
}
#[test]
fn connecting_are_outgoing() {
let (container0, id1) = container_with_added_connecting_peer();
assert_eq!(container0.outgoing_peers().collect::<Vec<_>>(), vec![&id1]);
assert_eq!(container0.incoming_peers().next(), None);
}
#[test]
fn nonconnecting_are_incoming() {
let (container0, id1) = container_with_added_nonconnecting_peer();
assert_eq!(container0.incoming_peers().collect::<Vec<_>>(), vec![&id1]);
assert_eq!(container0.outgoing_peers().next(), None);
}
#[test]
fn uninterested_in_removed() {
let (mut container0, id1) = container_with_added_connecting_peer();
assert!(container0.interested(&id1));
container0.remove_peer(&id1);
assert!(!container0.interested(&id1));
}
}