2019-05-02 19:46:27 +02:00
|
|
|
// Copyright 2019 Parity Technologies (UK) Ltd.
|
|
|
|
//
|
|
|
|
// Permission is hereby granted, free of charge, to any person obtaining a
|
|
|
|
// copy of this software and associated documentation files (the "Software"),
|
|
|
|
// to deal in the Software without restriction, including without limitation
|
|
|
|
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
|
|
// and/or sell copies of the Software, and to permit persons to whom the
|
|
|
|
// Software is furnished to do so, subject to the following conditions:
|
|
|
|
//
|
|
|
|
// The above copyright notice and this permission notice shall be included in
|
|
|
|
// all copies or substantial portions of the Software.
|
|
|
|
//
|
|
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
|
|
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
|
|
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
|
|
// DEALINGS IN THE SOFTWARE.
|
|
|
|
|
2019-07-04 14:47:59 +02:00
|
|
|
use libp2p_core::Multiaddr;
|
2019-05-02 19:46:27 +02:00
|
|
|
use smallvec::SmallVec;
|
2020-11-18 15:52:33 +01:00
|
|
|
use std::ops::{Add, Sub};
|
|
|
|
use std::{cmp::Ordering, collections::VecDeque, num::NonZeroUsize};
|
2019-05-02 19:46:27 +02:00
|
|
|
|
2020-11-18 15:52:33 +01:00
|
|
|
/// A ranked collection of [`Multiaddr`] values.
|
|
|
|
///
|
|
|
|
/// Every address has an associated [score](`AddressScore`) and iterating
|
|
|
|
/// over the addresses will return them in order from highest to lowest score.
|
|
|
|
///
|
|
|
|
/// In addition to the currently held addresses and their score, the collection
|
|
|
|
/// keeps track of a limited history of the most-recently added addresses.
|
|
|
|
/// This history determines how address scores are reduced over time as old
|
|
|
|
/// scores expire in the context of new addresses being added:
|
|
|
|
///
|
|
|
|
/// * An address's score is increased by a given amount whenever it is
|
|
|
|
/// [(re-)added](Addresses::add) to the collection.
|
|
|
|
/// * An address's score is decreased by the same amount used when it
|
|
|
|
/// was added when the least-recently seen addition is (as per the
|
|
|
|
/// limited history) for this address in the context of [`Addresses::add`].
|
|
|
|
/// * If an address's score reaches 0 in the context of [`Addresses::add`],
|
|
|
|
/// it is removed from the collection.
|
2019-05-02 19:46:27 +02:00
|
|
|
///
|
2019-06-18 10:23:26 +02:00
|
|
|
#[derive(Debug, Clone)]
|
2019-05-02 19:46:27 +02:00
|
|
|
pub struct Addresses {
|
2020-11-18 15:52:33 +01:00
|
|
|
/// The ranked sequence of addresses, from highest to lowest score.
|
|
|
|
///
|
|
|
|
/// By design, the number of finitely scored addresses stored here is
|
|
|
|
/// never larger (but may be smaller) than the number of historic `reports`
|
|
|
|
/// at any time.
|
|
|
|
registry: SmallVec<[AddressRecord; 8]>,
|
|
|
|
/// The configured limit of the `reports` history of added addresses,
|
|
|
|
/// and thus also of the size of the `registry` w.r.t. finitely scored
|
|
|
|
/// addresses.
|
2019-06-25 13:32:27 +02:00
|
|
|
limit: NonZeroUsize,
|
2020-11-18 15:52:33 +01:00
|
|
|
/// The limited history of added addresses. If the queue reaches the `limit`,
|
|
|
|
/// the first record, i.e. the least-recently added, is removed in the
|
|
|
|
/// context of [`Addresses::add`] and the corresponding record in the
|
|
|
|
/// `registry` has its score reduced accordingly.
|
|
|
|
reports: VecDeque<Report>,
|
2019-05-02 19:46:27 +02:00
|
|
|
}
|
|
|
|
|
2020-11-18 15:52:33 +01:00
|
|
|
/// An record in a prioritised list of addresses.
|
2019-05-02 19:46:27 +02:00
|
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
2020-11-18 15:52:33 +01:00
|
|
|
#[non_exhaustive]
|
|
|
|
pub struct AddressRecord {
|
|
|
|
pub addr: Multiaddr,
|
|
|
|
pub score: AddressScore,
|
|
|
|
}
|
|
|
|
|
|
|
|
/// A report tracked for a finitely scored address.
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
struct Report {
|
|
|
|
addr: Multiaddr,
|
2019-05-02 19:46:27 +02:00
|
|
|
score: u32,
|
2020-11-18 15:52:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
impl AddressRecord {
|
|
|
|
fn new(addr: Multiaddr, score: AddressScore) -> Self {
|
|
|
|
AddressRecord { addr, score }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// The "score" of an address w.r.t. an ordered collection of addresses.
|
|
|
|
///
|
|
|
|
/// A score is a measure of the trusworthyness of a particular
|
|
|
|
/// observation of an address. The same address may be repeatedly
|
|
|
|
/// reported with the same or differing scores.
|
|
|
|
#[derive(PartialEq, Eq, Debug, Clone, Copy, Hash)]
|
|
|
|
pub enum AddressScore {
|
|
|
|
/// The score is "infinite", i.e. an address with this score is never
|
|
|
|
/// purged from the associated address records and remains sorted at
|
|
|
|
/// the beginning (possibly with other `Infinite`ly scored addresses).
|
|
|
|
Infinite,
|
|
|
|
/// The score is finite, i.e. an address with this score has
|
|
|
|
/// its score increased and decreased as per the frequency of
|
|
|
|
/// reports (i.e. additions) of the same address relative to
|
|
|
|
/// the reports of other addresses.
|
|
|
|
Finite(u32),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl AddressScore {
|
|
|
|
fn is_zero(&self) -> bool {
|
|
|
|
&AddressScore::Finite(0) == self
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl PartialOrd for AddressScore {
|
|
|
|
fn partial_cmp(&self, other: &AddressScore) -> Option<Ordering> {
|
|
|
|
Some(self.cmp(other))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Ord for AddressScore {
|
|
|
|
fn cmp(&self, other: &AddressScore) -> Ordering {
|
|
|
|
// Semantics of cardinal numbers with a single infinite cardinal.
|
|
|
|
match (self, other) {
|
|
|
|
(AddressScore::Infinite, AddressScore::Infinite) => Ordering::Equal,
|
|
|
|
(AddressScore::Infinite, AddressScore::Finite(_)) => Ordering::Greater,
|
|
|
|
(AddressScore::Finite(_), AddressScore::Infinite) => Ordering::Less,
|
|
|
|
(AddressScore::Finite(a), AddressScore::Finite(b)) => a.cmp(b),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Add for AddressScore {
|
|
|
|
type Output = AddressScore;
|
|
|
|
|
|
|
|
fn add(self, rhs: AddressScore) -> Self::Output {
|
|
|
|
// Semantics of cardinal numbers with a single infinite cardinal.
|
|
|
|
match (self, rhs) {
|
|
|
|
(AddressScore::Infinite, AddressScore::Infinite) => AddressScore::Infinite,
|
|
|
|
(AddressScore::Infinite, AddressScore::Finite(_)) => AddressScore::Infinite,
|
|
|
|
(AddressScore::Finite(_), AddressScore::Infinite) => AddressScore::Infinite,
|
|
|
|
(AddressScore::Finite(a), AddressScore::Finite(b)) => {
|
|
|
|
AddressScore::Finite(a.saturating_add(b))
|
2021-08-11 13:12:12 +02:00
|
|
|
}
|
2020-11-18 15:52:33 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Sub<u32> for AddressScore {
|
|
|
|
type Output = AddressScore;
|
|
|
|
|
|
|
|
fn sub(self, rhs: u32) -> Self::Output {
|
|
|
|
// Semantics of cardinal numbers with a single infinite cardinal.
|
|
|
|
match self {
|
|
|
|
AddressScore::Infinite => AddressScore::Infinite,
|
|
|
|
AddressScore::Finite(score) => AddressScore::Finite(score.saturating_sub(rhs)),
|
|
|
|
}
|
|
|
|
}
|
2019-05-02 19:46:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for Addresses {
|
|
|
|
fn default() -> Self {
|
2019-06-25 13:32:27 +02:00
|
|
|
Addresses::new(NonZeroUsize::new(200).expect("200 > 0"))
|
2019-05-02 19:46:27 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-18 15:52:33 +01:00
|
|
|
/// The result of adding an address to an ordered list of
|
|
|
|
/// addresses with associated scores.
|
|
|
|
pub enum AddAddressResult {
|
2021-03-24 17:21:53 +01:00
|
|
|
Inserted {
|
|
|
|
expired: SmallVec<[AddressRecord; 8]>,
|
|
|
|
},
|
|
|
|
Updated {
|
|
|
|
expired: SmallVec<[AddressRecord; 8]>,
|
|
|
|
},
|
2020-11-18 15:52:33 +01:00
|
|
|
}
|
|
|
|
|
2019-05-02 19:46:27 +02:00
|
|
|
impl Addresses {
|
2020-11-18 15:52:33 +01:00
|
|
|
/// Create a new ranked address collection with the given size limit
|
|
|
|
/// for [finitely scored](AddressScore::Finite) addresses.
|
2019-05-02 19:46:27 +02:00
|
|
|
pub fn new(limit: NonZeroUsize) -> Self {
|
|
|
|
Addresses {
|
2019-06-25 13:32:27 +02:00
|
|
|
registry: SmallVec::new(),
|
2019-05-02 19:46:27 +02:00
|
|
|
limit,
|
2019-06-25 13:32:27 +02:00
|
|
|
reports: VecDeque::with_capacity(limit.get()),
|
2019-05-02 19:46:27 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Add a [`Multiaddr`] to the collection.
|
|
|
|
///
|
2020-11-18 15:52:33 +01:00
|
|
|
/// If the given address already exists in the collection,
|
|
|
|
/// the given score is added to the current score of the address.
|
|
|
|
///
|
|
|
|
/// If the collection has already observed the configured
|
|
|
|
/// number of address additions, the least-recently added address
|
|
|
|
/// as per this limited history has its score reduced by the amount
|
|
|
|
/// used in this prior report, with removal from the collection
|
|
|
|
/// occurring when the score drops to 0.
|
|
|
|
pub fn add(&mut self, addr: Multiaddr, score: AddressScore) -> AddAddressResult {
|
|
|
|
// If enough reports (i.e. address additions) occurred, reduce
|
|
|
|
// the score of the least-recently added address.
|
|
|
|
if self.reports.len() == self.limit.get() {
|
|
|
|
let old_report = self.reports.pop_front().expect("len = limit > 0");
|
|
|
|
// If the address is still in the collection, decrease its score.
|
|
|
|
if let Some(record) = self.registry.iter_mut().find(|r| r.addr == old_report.addr) {
|
|
|
|
record.score = record.score - old_report.score;
|
2019-05-02 19:46:27 +02:00
|
|
|
isort(&mut self.registry);
|
|
|
|
}
|
|
|
|
}
|
2019-06-25 13:32:27 +02:00
|
|
|
|
|
|
|
// Remove addresses that have a score of 0.
|
2021-03-24 17:21:53 +01:00
|
|
|
let mut expired = SmallVec::new();
|
2020-11-18 15:52:33 +01:00
|
|
|
while self
|
|
|
|
.registry
|
|
|
|
.last()
|
|
|
|
.map(|e| e.score.is_zero())
|
|
|
|
.unwrap_or(false)
|
|
|
|
{
|
2021-03-24 17:21:53 +01:00
|
|
|
if let Some(addr) = self.registry.pop() {
|
|
|
|
expired.push(addr);
|
|
|
|
}
|
2019-05-02 19:46:27 +02:00
|
|
|
}
|
2019-06-25 13:32:27 +02:00
|
|
|
|
2020-11-18 15:52:33 +01:00
|
|
|
// If the address score is finite, remember this report.
|
|
|
|
if let AddressScore::Finite(score) = score {
|
|
|
|
self.reports.push_back(Report {
|
|
|
|
addr: addr.clone(),
|
|
|
|
score,
|
|
|
|
});
|
|
|
|
}
|
2019-06-25 13:32:27 +02:00
|
|
|
|
2020-11-18 15:52:33 +01:00
|
|
|
// If the address is already in the collection, increase its score.
|
2019-06-25 13:32:27 +02:00
|
|
|
for r in &mut self.registry {
|
2020-11-18 15:52:33 +01:00
|
|
|
if r.addr == addr {
|
|
|
|
r.score = r.score + score;
|
2019-06-25 13:32:27 +02:00
|
|
|
isort(&mut self.registry);
|
2021-03-24 17:21:53 +01:00
|
|
|
return AddAddressResult::Updated { expired };
|
2019-06-25 13:32:27 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-18 15:52:33 +01:00
|
|
|
// It is a new record.
|
|
|
|
self.registry.push(AddressRecord::new(addr, score));
|
2021-03-24 17:21:53 +01:00
|
|
|
AddAddressResult::Inserted { expired }
|
2020-11-18 15:52:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Explicitly remove an address from the collection.
|
|
|
|
///
|
|
|
|
/// Returns `true` if the address existed in the collection
|
|
|
|
/// and was thus removed, false otherwise.
|
|
|
|
pub fn remove(&mut self, addr: &Multiaddr) -> bool {
|
|
|
|
if let Some(pos) = self.registry.iter().position(|r| &r.addr == addr) {
|
|
|
|
self.registry.remove(pos);
|
|
|
|
true
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
2019-05-02 19:46:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Return an iterator over all [`Multiaddr`] values.
|
|
|
|
///
|
|
|
|
/// The iteration is ordered by descending score.
|
|
|
|
pub fn iter(&self) -> AddressIter<'_> {
|
|
|
|
AddressIter {
|
|
|
|
items: &self.registry,
|
|
|
|
offset: 0,
|
|
|
|
}
|
|
|
|
}
|
2019-06-18 10:23:26 +02:00
|
|
|
|
|
|
|
/// Return an iterator over all [`Multiaddr`] values.
|
|
|
|
///
|
|
|
|
/// The iteration is ordered by descending score.
|
|
|
|
pub fn into_iter(self) -> AddressIntoIter {
|
|
|
|
AddressIntoIter {
|
|
|
|
items: self.registry,
|
|
|
|
}
|
|
|
|
}
|
2019-05-02 19:46:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/// An iterator over [`Multiaddr`] values.
|
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct AddressIter<'a> {
|
2020-11-18 15:52:33 +01:00
|
|
|
items: &'a [AddressRecord],
|
2019-05-02 19:46:27 +02:00
|
|
|
offset: usize,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> Iterator for AddressIter<'a> {
|
2020-11-18 15:52:33 +01:00
|
|
|
type Item = &'a AddressRecord;
|
2019-05-02 19:46:27 +02:00
|
|
|
|
|
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
|
|
if self.offset == self.items.len() {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
let item = &self.items[self.offset];
|
|
|
|
self.offset += 1;
|
2021-09-14 16:00:05 +03:00
|
|
|
Some(item)
|
2019-05-02 19:46:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
fn size_hint(&self) -> (usize, Option<usize>) {
|
|
|
|
let n = self.items.len() - self.offset;
|
|
|
|
(n, Some(n))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> ExactSizeIterator for AddressIter<'a> {}
|
|
|
|
|
2019-06-18 10:23:26 +02:00
|
|
|
/// An iterator over [`Multiaddr`] values.
|
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct AddressIntoIter {
|
2020-11-18 15:52:33 +01:00
|
|
|
items: SmallVec<[AddressRecord; 8]>,
|
2019-06-18 10:23:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Iterator for AddressIntoIter {
|
2020-11-18 15:52:33 +01:00
|
|
|
type Item = AddressRecord;
|
2019-06-18 10:23:26 +02:00
|
|
|
|
|
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
|
|
if !self.items.is_empty() {
|
2020-11-18 15:52:33 +01:00
|
|
|
Some(self.items.remove(0))
|
2019-06-18 10:23:26 +02:00
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn size_hint(&self) -> (usize, Option<usize>) {
|
|
|
|
let n = self.items.len();
|
|
|
|
(n, Some(n))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ExactSizeIterator for AddressIntoIter {}
|
|
|
|
|
2019-05-02 19:46:27 +02:00
|
|
|
// Reverse insertion sort.
|
2020-11-18 15:52:33 +01:00
|
|
|
fn isort(xs: &mut [AddressRecord]) {
|
2019-05-02 19:46:27 +02:00
|
|
|
for i in 1..xs.len() {
|
|
|
|
for j in (1..=i).rev() {
|
|
|
|
if xs[j].score <= xs[j - 1].score {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
xs.swap(j, j - 1)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2020-11-18 15:52:33 +01:00
|
|
|
use super::*;
|
2019-07-04 14:47:59 +02:00
|
|
|
use libp2p_core::multiaddr::{Multiaddr, Protocol};
|
2020-11-18 15:52:33 +01:00
|
|
|
use quickcheck::*;
|
|
|
|
use std::num::{NonZeroU8, NonZeroUsize};
|
|
|
|
|
|
|
|
impl Arbitrary for AddressScore {
|
2022-09-22 12:48:32 +04:00
|
|
|
fn arbitrary(g: &mut Gen) -> AddressScore {
|
|
|
|
if g.gen_range(0..10u8) == 0 {
|
2020-11-18 15:52:33 +01:00
|
|
|
// ~10% "Infinitely" scored addresses
|
|
|
|
AddressScore::Infinite
|
|
|
|
} else {
|
2022-09-22 12:48:32 +04:00
|
|
|
AddressScore::Finite(Arbitrary::arbitrary(g))
|
2020-11-18 15:52:33 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Arbitrary for AddressRecord {
|
2022-09-22 12:48:32 +04:00
|
|
|
fn arbitrary(g: &mut Gen) -> Self {
|
|
|
|
let addr = Protocol::Tcp(g.gen_range(0..256)).into();
|
2020-11-18 15:52:33 +01:00
|
|
|
let score = AddressScore::arbitrary(g);
|
|
|
|
AddressRecord::new(addr, score)
|
|
|
|
}
|
|
|
|
}
|
2019-05-02 19:46:27 +02:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn isort_sorts() {
|
2020-11-18 15:52:33 +01:00
|
|
|
fn property(xs: Vec<AddressScore>) {
|
2019-05-02 19:46:27 +02:00
|
|
|
let mut xs = xs
|
|
|
|
.into_iter()
|
2020-11-18 15:52:33 +01:00
|
|
|
.map(|score| AddressRecord::new(Multiaddr::empty(), score))
|
2019-05-02 19:46:27 +02:00
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
|
|
|
isort(&mut xs);
|
|
|
|
|
|
|
|
for i in 1..xs.len() {
|
|
|
|
assert!(xs[i - 1].score >= xs[i].score)
|
|
|
|
}
|
|
|
|
}
|
2020-11-18 15:52:33 +01:00
|
|
|
|
|
|
|
quickcheck(property as fn(_));
|
2019-05-02 19:46:27 +02:00
|
|
|
}
|
2019-06-25 13:32:27 +02:00
|
|
|
|
|
|
|
#[test]
|
2020-11-18 15:52:33 +01:00
|
|
|
fn score_retention() {
|
|
|
|
fn prop(first: AddressRecord, other: AddressRecord) -> TestResult {
|
2022-09-22 12:48:32 +04:00
|
|
|
if first.addr == other.addr || first.score.is_zero() {
|
2020-11-18 15:52:33 +01:00
|
|
|
return TestResult::discard();
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut addresses = Addresses::default();
|
|
|
|
|
|
|
|
// Add the first address.
|
|
|
|
addresses.add(first.addr.clone(), first.score);
|
|
|
|
assert!(addresses.iter().any(|a| &a.addr == &first.addr));
|
|
|
|
|
|
|
|
// Add another address so often that the initial report of
|
|
|
|
// the first address may be purged and, since it was the
|
|
|
|
// only report, the address removed.
|
|
|
|
for _ in 0..addresses.limit.get() + 1 {
|
|
|
|
addresses.add(other.addr.clone(), other.score);
|
|
|
|
}
|
|
|
|
|
|
|
|
let exists = addresses.iter().any(|a| &a.addr == &first.addr);
|
|
|
|
|
|
|
|
match (first.score, other.score) {
|
|
|
|
// Only finite scores push out other finite scores.
|
|
|
|
(AddressScore::Finite(_), AddressScore::Finite(_)) => assert!(!exists),
|
|
|
|
_ => assert!(exists),
|
|
|
|
}
|
|
|
|
|
|
|
|
TestResult::passed()
|
2019-06-25 13:32:27 +02:00
|
|
|
}
|
|
|
|
|
2020-11-18 15:52:33 +01:00
|
|
|
quickcheck(prop as fn(_, _) -> _);
|
2019-06-25 13:32:27 +02:00
|
|
|
}
|
|
|
|
|
2022-09-22 12:48:32 +04:00
|
|
|
#[test]
|
|
|
|
fn score_retention_finite_0() {
|
|
|
|
let first = {
|
|
|
|
let addr = Protocol::Tcp(42).into();
|
|
|
|
let score = AddressScore::Finite(0);
|
|
|
|
AddressRecord::new(addr, score)
|
|
|
|
};
|
|
|
|
let other = {
|
|
|
|
let addr = Protocol::Udp(42).into();
|
|
|
|
let score = AddressScore::Finite(42);
|
|
|
|
AddressRecord::new(addr, score)
|
|
|
|
};
|
|
|
|
|
|
|
|
let mut addresses = Addresses::default();
|
|
|
|
|
|
|
|
// Add the first address.
|
|
|
|
addresses.add(first.addr.clone(), first.score);
|
|
|
|
assert!(addresses.iter().any(|a| &a.addr == &first.addr));
|
|
|
|
|
|
|
|
// Add another address so the first will address be purged,
|
|
|
|
// because its score is finite(0)
|
|
|
|
addresses.add(other.addr.clone(), other.score);
|
|
|
|
|
|
|
|
assert!(addresses.iter().any(|a| &a.addr == &other.addr));
|
|
|
|
assert!(!addresses.iter().any(|a| &a.addr == &first.addr));
|
|
|
|
}
|
|
|
|
|
2019-06-25 13:32:27 +02:00
|
|
|
#[test]
|
2020-11-18 15:52:33 +01:00
|
|
|
fn finitely_scored_address_limit() {
|
|
|
|
fn prop(reports: Vec<AddressRecord>, limit: NonZeroU8) {
|
|
|
|
let mut addresses = Addresses::new(limit.into());
|
2019-06-25 13:32:27 +02:00
|
|
|
|
2020-11-18 15:52:33 +01:00
|
|
|
// Add all reports.
|
|
|
|
for r in reports {
|
|
|
|
addresses.add(r.addr, r.score);
|
2019-06-25 13:32:27 +02:00
|
|
|
}
|
2020-11-18 15:52:33 +01:00
|
|
|
|
|
|
|
// Count the finitely scored addresses.
|
|
|
|
let num_finite = addresses
|
|
|
|
.iter()
|
|
|
|
.filter(|r| match r {
|
|
|
|
AddressRecord {
|
|
|
|
score: AddressScore::Finite(_),
|
|
|
|
..
|
|
|
|
} => true,
|
|
|
|
_ => false,
|
|
|
|
})
|
|
|
|
.count();
|
|
|
|
|
|
|
|
// Check against the limit.
|
|
|
|
assert!(num_finite <= limit.get() as usize);
|
2019-06-25 13:32:27 +02:00
|
|
|
}
|
|
|
|
|
2020-11-18 15:52:33 +01:00
|
|
|
quickcheck(prop as fn(_, _));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn record_score_sum() {
|
|
|
|
fn prop(records: Vec<AddressRecord>) -> bool {
|
|
|
|
// Make sure the address collection can hold all reports.
|
|
|
|
let n = std::cmp::max(records.len(), 1);
|
|
|
|
let mut addresses = Addresses::new(NonZeroUsize::new(n).unwrap());
|
|
|
|
|
|
|
|
// Add all address reports to the collection.
|
|
|
|
for r in records.iter() {
|
|
|
|
addresses.add(r.addr.clone(), r.score.clone());
|
2019-06-25 13:32:27 +02:00
|
|
|
}
|
2020-11-18 15:52:33 +01:00
|
|
|
|
|
|
|
// Check that each address in the registry has the expected score.
|
2019-06-25 13:32:27 +02:00
|
|
|
for r in &addresses.registry {
|
2020-11-18 15:52:33 +01:00
|
|
|
let expected_score = records.iter().fold(None::<AddressScore>, |sum, rec| {
|
|
|
|
if &rec.addr == &r.addr {
|
|
|
|
sum.map_or(Some(rec.score), |s| Some(s + rec.score))
|
|
|
|
} else {
|
|
|
|
sum
|
2021-08-11 13:12:12 +02:00
|
|
|
}
|
2020-11-18 15:52:33 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
if Some(r.score) != expected_score {
|
2019-06-25 13:32:27 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
2020-11-18 15:52:33 +01:00
|
|
|
|
2019-06-25 13:32:27 +02:00
|
|
|
true
|
|
|
|
}
|
|
|
|
|
2020-11-18 15:52:33 +01:00
|
|
|
quickcheck(prop as fn(_) -> _)
|
2019-06-25 13:32:27 +02:00
|
|
|
}
|
2019-05-02 19:46:27 +02:00
|
|
|
}
|