node: Limit number of Refs in RefAnnouncements
Use the BoundedVec to limit the supported number of refs in RefAnnouncement messages. The size (235) is the maximum supported by message wire size. To reduce the complexity of the changes, BoundedVec is used again instead of using a new bounded Refs or BTreeMap. This requires defining new Decode/Encode logic for Ref's element types, and workarounds for arbitrary instances. Signed-off-by: Slack Coder <slackcoder@server.ky>
This commit is contained in:
parent
c676d8b99a
commit
e7f2637126
|
|
@ -18,6 +18,25 @@ impl<T, const N: usize> BoundedVec<T, N> {
|
||||||
BoundedVec { v: Vec::new() }
|
BoundedVec { v: Vec::new() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Build a `BoundedVec` by consuming from the given iterator up to its limit.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use radicle_node::bounded;
|
||||||
|
///
|
||||||
|
/// let mut iter = (0..4).into_iter();
|
||||||
|
/// let bounded: bounded::BoundedVec<i32,3> = bounded::BoundedVec::collect_from(&mut iter);
|
||||||
|
///
|
||||||
|
/// assert_eq!(bounded.len(), 3);
|
||||||
|
/// assert_eq!(iter.count(), 1);
|
||||||
|
/// ```
|
||||||
|
pub fn collect_from<I: Iterator<Item = T>>(iter: &mut I) -> Self {
|
||||||
|
BoundedVec {
|
||||||
|
v: iter.into_iter().take(N).collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a new `BoundedVec<T,N>` which takes upto the first N values of its argument, taking
|
/// Create a new `BoundedVec<T,N>` which takes upto the first N values of its argument, taking
|
||||||
/// ownership.
|
/// ownership.
|
||||||
///
|
///
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,8 @@ pub const MAX_CONNECTION_ATTEMPTS: usize = 3;
|
||||||
pub use message::ADDRESS_LIMIT;
|
pub use message::ADDRESS_LIMIT;
|
||||||
/// Maximum inventory limit imposed by message size limits.
|
/// Maximum inventory limit imposed by message size limits.
|
||||||
pub use message::INVENTORY_LIMIT;
|
pub use message::INVENTORY_LIMIT;
|
||||||
|
/// Maximum number of project git references imposed by message size limits.
|
||||||
|
pub use message::REF_LIMIT;
|
||||||
|
|
||||||
/// A service event.
|
/// A service event.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
@ -959,12 +961,22 @@ where
|
||||||
|
|
||||||
/// Announce local refs for given id.
|
/// Announce local refs for given id.
|
||||||
fn announce_refs(&mut self, id: Id) -> Result<(), storage::Error> {
|
fn announce_refs(&mut self, id: Id) -> Result<(), storage::Error> {
|
||||||
|
type Refs = BoundedVec<Id, REF_LIMIT>;
|
||||||
|
|
||||||
let node = self.node_id();
|
let node = self.node_id();
|
||||||
let repo = self.storage.repository(id)?;
|
let repo = self.storage.repository(id)?;
|
||||||
let remote = repo.remote(&node)?;
|
let remote = repo.remote(&node)?;
|
||||||
let peers = self.sessions.negotiated().map(|(_, _, p)| p);
|
let peers = self.sessions.negotiated().map(|(_, _, p)| p);
|
||||||
let refs = remote.refs.into();
|
|
||||||
let timestamp = self.clock.as_secs();
|
let timestamp = self.clock.as_secs();
|
||||||
|
|
||||||
|
if remote.refs.len() > Refs::max() {
|
||||||
|
log::error!(
|
||||||
|
"refs announcement limit ({}) exceeded, other nodes will see only some of your project references",
|
||||||
|
Refs::max(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let refs = BoundedVec::collect_from(&mut remote.refs.iter().map(|(a, b)| (a.clone(), *b)));
|
||||||
|
|
||||||
let msg = AnnouncementMessage::from(RefsAnnouncement {
|
let msg = AnnouncementMessage::from(RefsAnnouncement {
|
||||||
id,
|
id,
|
||||||
refs,
|
refs,
|
||||||
|
|
|
||||||
|
|
@ -4,16 +4,18 @@ use std::{fmt, io, mem, net};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::crypto;
|
use crate::crypto;
|
||||||
|
use crate::git;
|
||||||
use crate::identity::Id;
|
use crate::identity::Id;
|
||||||
use crate::node;
|
use crate::node;
|
||||||
use crate::prelude::BoundedVec;
|
use crate::prelude::BoundedVec;
|
||||||
use crate::service::filter::Filter;
|
use crate::service::filter::Filter;
|
||||||
use crate::service::{NodeId, Timestamp, PROTOCOL_VERSION};
|
use crate::service::{NodeId, Timestamp, PROTOCOL_VERSION};
|
||||||
use crate::storage::refs::Refs;
|
|
||||||
use crate::wire;
|
use crate::wire;
|
||||||
|
|
||||||
/// Maximum number of addresses which can be announced to other nodes.
|
/// Maximum number of addresses which can be announced to other nodes.
|
||||||
pub const ADDRESS_LIMIT: usize = 16;
|
pub const ADDRESS_LIMIT: usize = 16;
|
||||||
|
/// Maximum number of project git references.
|
||||||
|
pub const REF_LIMIT: usize = 235;
|
||||||
/// Maximum number of inventory which can be announced to other nodes.
|
/// Maximum number of inventory which can be announced to other nodes.
|
||||||
pub const INVENTORY_LIMIT: usize = 2973;
|
pub const INVENTORY_LIMIT: usize = 2973;
|
||||||
|
|
||||||
|
|
@ -225,7 +227,7 @@ pub struct RefsAnnouncement {
|
||||||
/// Repository identifier.
|
/// Repository identifier.
|
||||||
pub id: Id,
|
pub id: Id,
|
||||||
/// Updated refs.
|
/// Updated refs.
|
||||||
pub refs: Refs,
|
pub refs: BoundedVec<(git::RefString, git::Oid), REF_LIMIT>,
|
||||||
/// Time of announcement.
|
/// Time of announcement.
|
||||||
pub timestamp: Timestamp,
|
pub timestamp: Timestamp,
|
||||||
}
|
}
|
||||||
|
|
@ -508,8 +510,43 @@ mod tests {
|
||||||
|
|
||||||
use crate::crypto::test::signer::MockSigner;
|
use crate::crypto::test::signer::MockSigner;
|
||||||
use crate::test::arbitrary;
|
use crate::test::arbitrary;
|
||||||
|
use fastrand;
|
||||||
use qcheck_macros::quickcheck;
|
use qcheck_macros::quickcheck;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_ref_limit() {
|
||||||
|
let mut refs = Refs::default();
|
||||||
|
while refs.len() < REF_LIMIT {
|
||||||
|
refs.insert(arbitrary::refstring(u8::MAX as usize), arbitrary::oid());
|
||||||
|
}
|
||||||
|
|
||||||
|
let bounded_refs = BoundedVec::collect_from(&mut refs.iter().map(|(a, b)| (a.clone(), *b)));
|
||||||
|
let msg: Message = AnnouncementMessage::from(RefsAnnouncement {
|
||||||
|
id: arbitrary::gen(1),
|
||||||
|
refs: bounded_refs,
|
||||||
|
timestamp: LocalTime::now().as_secs(),
|
||||||
|
})
|
||||||
|
.signed(&MockSigner::default())
|
||||||
|
.into();
|
||||||
|
|
||||||
|
let mut buf: Vec<u8> = Vec::new();
|
||||||
|
assert!(
|
||||||
|
msg.encode(&mut buf).is_ok(),
|
||||||
|
"REF_LIMIT is too big to support message encoding",
|
||||||
|
);
|
||||||
|
|
||||||
|
let decoded = wire::deserialize(buf.as_slice());
|
||||||
|
assert!(
|
||||||
|
decoded.is_ok(),
|
||||||
|
"REF_LIMIT is too big to support message decoding"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
msg,
|
||||||
|
decoded.unwrap(),
|
||||||
|
"encoding and decoding should be safe for message at REF_LIMIT",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_inventory_limit() {
|
fn test_inventory_limit() {
|
||||||
let msg = Message::inventory(
|
let msg = Message::inventory(
|
||||||
|
|
@ -543,9 +580,10 @@ mod tests {
|
||||||
fn prop_refs_announcement_signing(id: Id, refs: Refs) {
|
fn prop_refs_announcement_signing(id: Id, refs: Refs) {
|
||||||
let signer = MockSigner::new(&mut fastrand::Rng::new());
|
let signer = MockSigner::new(&mut fastrand::Rng::new());
|
||||||
let timestamp = 0;
|
let timestamp = 0;
|
||||||
|
|
||||||
let message = AnnouncementMessage::Refs(RefsAnnouncement {
|
let message = AnnouncementMessage::Refs(RefsAnnouncement {
|
||||||
id,
|
id,
|
||||||
refs,
|
refs: BoundedVec::collect_from(&mut refs.iter().map(|(k, v)| (k.clone(), *v))),
|
||||||
timestamp,
|
timestamp,
|
||||||
});
|
});
|
||||||
let ann = message.signed(&signer);
|
let ann = message.signed(&signer);
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,9 @@ impl Arbitrary for Message {
|
||||||
node: NodeId::arbitrary(g),
|
node: NodeId::arbitrary(g),
|
||||||
message: RefsAnnouncement {
|
message: RefsAnnouncement {
|
||||||
id: Id::arbitrary(g),
|
id: Id::arbitrary(g),
|
||||||
refs: Refs::arbitrary(g),
|
refs: BoundedVec::collect_from(
|
||||||
|
&mut Refs::arbitrary(g).iter().map(|(k, v)| (k.clone(), *v)),
|
||||||
|
),
|
||||||
timestamp: Timestamp::arbitrary(g),
|
timestamp: Timestamp::arbitrary(g),
|
||||||
}
|
}
|
||||||
.into(),
|
.into(),
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
use std::collections::BTreeMap;
|
|
||||||
use std::iter;
|
use std::iter;
|
||||||
use std::net;
|
use std::net;
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
@ -13,7 +12,7 @@ use crate::crypto::test::signer::MockSigner;
|
||||||
use crate::crypto::Signer;
|
use crate::crypto::Signer;
|
||||||
use crate::identity::Id;
|
use crate::identity::Id;
|
||||||
use crate::node;
|
use crate::node;
|
||||||
use crate::prelude::NodeId;
|
use crate::prelude::*;
|
||||||
use crate::service;
|
use crate::service;
|
||||||
use crate::service::message::*;
|
use crate::service::message::*;
|
||||||
use crate::service::reactor::Io;
|
use crate::service::reactor::Io;
|
||||||
|
|
@ -214,7 +213,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn refs_announcement(&self, id: Id) -> Message {
|
pub fn refs_announcement(&self, id: Id) -> Message {
|
||||||
let refs = BTreeMap::new().into();
|
let refs = BoundedVec::new();
|
||||||
let ann = AnnouncementMessage::from(RefsAnnouncement {
|
let ann = AnnouncementMessage::from(RefsAnnouncement {
|
||||||
id,
|
id,
|
||||||
refs,
|
refs,
|
||||||
|
|
|
||||||
|
|
@ -226,6 +226,24 @@ impl Encode for Refs {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<A, B> Encode for (A, B)
|
||||||
|
where
|
||||||
|
A: Encode,
|
||||||
|
B: Encode,
|
||||||
|
{
|
||||||
|
fn encode<W: io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error> {
|
||||||
|
let mut n = self.0.encode(writer)?;
|
||||||
|
n += self.1.encode(writer)?;
|
||||||
|
Ok(n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encode for git::RefString {
|
||||||
|
fn encode<W: io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error> {
|
||||||
|
self.as_str().encode(writer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Encode for Signature {
|
impl Encode for Signature {
|
||||||
fn encode<W: io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error> {
|
fn encode<W: io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error> {
|
||||||
self.deref().encode(writer)
|
self.deref().encode(writer)
|
||||||
|
|
@ -266,6 +284,25 @@ impl Decode for Refs {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Decode for git::RefString {
|
||||||
|
fn decode<R: io::Read + ?Sized>(reader: &mut R) -> Result<Self, Error> {
|
||||||
|
let ref_str = String::decode(reader)?;
|
||||||
|
git::RefString::try_from(ref_str).map_err(Error::from)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A, B> Decode for (A, B)
|
||||||
|
where
|
||||||
|
A: Decode,
|
||||||
|
B: Decode,
|
||||||
|
{
|
||||||
|
fn decode<R: io::Read + ?Sized>(reader: &mut R) -> Result<Self, Error> {
|
||||||
|
let a = A::decode(reader)?;
|
||||||
|
let b = B::decode(reader)?;
|
||||||
|
Ok((a, b))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Decode for git::Oid {
|
impl Decode for git::Oid {
|
||||||
fn decode<R: io::Read + ?Sized>(reader: &mut R) -> Result<Self, Error> {
|
fn decode<R: io::Read + ?Sized>(reader: &mut R) -> Result<Self, Error> {
|
||||||
let len = Size::decode(reader)? as usize;
|
let len = Size::decode(reader)? as usize;
|
||||||
|
|
@ -628,6 +665,7 @@ impl<R, S, W, G, H: Handshake> Iterator for Wire<R, S, W, G, H> {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use qcheck;
|
||||||
use qcheck_macros::quickcheck;
|
use qcheck_macros::quickcheck;
|
||||||
|
|
||||||
use crate::crypto::Unverified;
|
use crate::crypto::Unverified;
|
||||||
|
|
@ -700,6 +738,14 @@ mod tests {
|
||||||
assert_eq!(deserialize::<Refs>(&serialize(&input)).unwrap(), input);
|
assert_eq!(deserialize::<Refs>(&serialize(&input)).unwrap(), input);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[quickcheck]
|
||||||
|
fn prop_tuple(input: (String, String)) {
|
||||||
|
assert_eq!(
|
||||||
|
deserialize::<(String, String)>(&serialize(&input)).unwrap(),
|
||||||
|
input
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[quickcheck]
|
#[quickcheck]
|
||||||
fn prop_signature(input: [u8; 64]) {
|
fn prop_signature(input: [u8; 64]) {
|
||||||
let signature = Signature::from(input);
|
let signature = Signature::from(input);
|
||||||
|
|
|
||||||
|
|
@ -130,7 +130,7 @@ impl wire::Encode for RefsAnnouncement {
|
||||||
impl wire::Decode for RefsAnnouncement {
|
impl wire::Decode for RefsAnnouncement {
|
||||||
fn decode<R: std::io::Read + ?Sized>(reader: &mut R) -> Result<Self, wire::Error> {
|
fn decode<R: std::io::Read + ?Sized>(reader: &mut R) -> Result<Self, wire::Error> {
|
||||||
let id = Id::decode(reader)?;
|
let id = Id::decode(reader)?;
|
||||||
let refs = Refs::decode(reader)?;
|
let refs = BoundedVec::decode(reader)?;
|
||||||
let timestamp = Timestamp::decode(reader)?;
|
let timestamp = Timestamp::decode(reader)?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,23 @@ use crate::storage;
|
||||||
use crate::storage::refs::{Refs, SignedRefs};
|
use crate::storage::refs::{Refs, SignedRefs};
|
||||||
use crate::test::storage::MockStorage;
|
use crate::test::storage::MockStorage;
|
||||||
|
|
||||||
|
pub fn oid() -> storage::Oid {
|
||||||
|
let oid_bytes: [u8; 20] = gen(1);
|
||||||
|
storage::Oid::try_from(oid_bytes.as_slice()).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn refstring(len: usize) -> git::RefString {
|
||||||
|
let mut buf = Vec::<u8>::new();
|
||||||
|
for _ in 0..len {
|
||||||
|
buf.push(fastrand::u8(0x61..0x7a));
|
||||||
|
}
|
||||||
|
std::str::from_utf8(&buf)
|
||||||
|
.unwrap()
|
||||||
|
.to_string()
|
||||||
|
.try_into()
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set<T: Eq + Hash + Arbitrary>(range: impl RangeBounds<usize>) -> HashSet<T> {
|
pub fn set<T: Eq + Hash + Arbitrary>(range: impl RangeBounds<usize>) -> HashSet<T> {
|
||||||
let size = fastrand::usize(range);
|
let size = fastrand::usize(range);
|
||||||
let mut set = HashSet::with_capacity(size);
|
let mut set = HashSet::with_capacity(size);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue