From e909e04c6c22d37da5c39f86f873d607d372c024 Mon Sep 17 00:00:00 2001 From: Alexis Sellier Date: Tue, 4 Oct 2022 11:55:41 +0200 Subject: [PATCH] Implement proper `node::Features` type Signed-off-by: Alexis Sellier --- radicle-node/src/service.rs | 5 +- radicle-node/src/service/message.rs | 8 +- radicle-node/src/test/arbitrary.rs | 2 +- radicle-node/src/wire.rs | 15 ++++ radicle/src/node.rs | 4 + radicle/src/node/features.rs | 130 ++++++++++++++++++++++++++++ 6 files changed, 156 insertions(+), 8 deletions(-) create mode 100644 radicle/src/node/features.rs diff --git a/radicle-node/src/service.rs b/radicle-node/src/service.rs index 10fefcc9..860afdb7 100644 --- a/radicle-node/src/service.rs +++ b/radicle-node/src/service.rs @@ -28,6 +28,7 @@ use crate::crypto::{Signer, Verified}; use crate::git; use crate::git::Url; use crate::identity::{Doc, Id}; +use crate::node; use crate::service::config::ProjectTracking; use crate::service::message::{Address, Announcement, AnnouncementMessage}; use crate::service::message::{NodeAnnouncement, RefsAnnouncement}; @@ -40,7 +41,7 @@ pub use crate::service::message::{Envelope, Message}; pub use crate::service::peer::Session; use self::gossip::Gossip; -use self::message::{InventoryAnnouncement, NodeFeatures}; +use self::message::InventoryAnnouncement; use self::reactor::Reactor; pub const DEFAULT_PORT: u16 = 8776; @@ -985,7 +986,7 @@ mod gossip { } pub fn node(timestamp: Timestamp, config: &Config) -> NodeAnnouncement { - let features = NodeFeatures::default(); + let features = node::Features::SEED; let alias = config.alias(); let addresses = vec![]; // TODO diff --git a/radicle-node/src/service/message.rs b/radicle-node/src/service/message.rs index 6e98db1e..836c29ad 100644 --- a/radicle-node/src/service/message.rs +++ b/radicle-node/src/service/message.rs @@ -6,6 +6,7 @@ use thiserror::Error; use crate::crypto; use crate::git; use crate::identity::Id; +use crate::node; use crate::service::filter::Filter; use crate::service::{NodeId, Timestamp, PROTOCOL_VERSION}; use crate::storage::refs::Refs; @@ -20,9 +21,6 @@ pub struct Envelope { pub msg: Message, } -/// Advertized node feature. Signals what services the node supports. -pub type NodeFeatures = [u8; 32]; - #[derive(Debug, Clone, PartialEq, Eq)] // TODO: We should check the length and charset when deserializing. pub struct Hostname(String); @@ -128,7 +126,7 @@ pub struct Subscribe { #[derive(Debug, Clone, PartialEq, Eq)] pub struct NodeAnnouncement { /// Advertized features. - pub features: NodeFeatures, + pub features: node::Features, /// Monotonic timestamp. pub timestamp: Timestamp, /// Non-unique alias. Must be valid UTF-8. @@ -152,7 +150,7 @@ impl wire::Encode for NodeAnnouncement { impl wire::Decode for NodeAnnouncement { fn decode(reader: &mut R) -> Result { - let features = NodeFeatures::decode(reader)?; + let features = node::Features::decode(reader)?; let timestamp = Timestamp::decode(reader)?; let alias = wire::Decode::decode(reader)?; let addresses = Vec::
::decode(reader)?; diff --git a/radicle-node/src/test/arbitrary.rs b/radicle-node/src/test/arbitrary.rs index 57308d03..55f005ba 100644 --- a/radicle-node/src/test/arbitrary.rs +++ b/radicle-node/src/test/arbitrary.rs @@ -72,7 +72,7 @@ impl Arbitrary for Message { .into(), MessageType::NodeAnnouncement => { let message = NodeAnnouncement { - features: ByteArray::<32>::arbitrary(g).into_inner(), + features: u64::arbitrary(g).into(), timestamp: Timestamp::arbitrary(g), alias: ByteArray::<32>::arbitrary(g).into_inner(), addresses: Arbitrary::arbitrary(g), diff --git a/radicle-node/src/wire.rs b/radicle-node/src/wire.rs index 42bebfb6..e7a37653 100644 --- a/radicle-node/src/wire.rs +++ b/radicle-node/src/wire.rs @@ -18,6 +18,7 @@ use crate::git; use crate::git::fmt; use crate::hash::Digest; use crate::identity::Id; +use crate::node; use crate::service; use crate::service::reactor::Io; use crate::service::{filter, routing}; @@ -438,6 +439,20 @@ impl Decode for SignedRefs { } } +impl Encode for node::Features { + fn encode(&self, writer: &mut W) -> Result { + self.deref().encode(writer) + } +} + +impl Decode for node::Features { + fn decode(reader: &mut R) -> Result { + let features = u64::decode(reader)?; + + Ok(Self::from(features)) + } +} + #[derive(Debug)] pub struct Wire { inboxes: HashMap, diff --git a/radicle/src/node.rs b/radicle/src/node.rs index 7bdfc47e..0cebc2b3 100644 --- a/radicle/src/node.rs +++ b/radicle/src/node.rs @@ -1,3 +1,5 @@ +mod features; + use std::fmt; use std::io; use std::io::{BufRead, BufReader, Write}; @@ -6,6 +8,8 @@ use std::path::Path; use crate::identity::Id; +pub use features::Features; + /// Default name for control socket file. pub const DEFAULT_SOCKET_NAME: &str = "radicle.sock"; diff --git a/radicle/src/node/features.rs b/radicle/src/node/features.rs new file mode 100644 index 00000000..9633e905 --- /dev/null +++ b/radicle/src/node/features.rs @@ -0,0 +1,130 @@ +//! Node features advertized on the network. +use std::{fmt, ops}; + +/// Advertized node features. Signals what services the node supports. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Features(u64); + +impl Features { + /// `NONE` means no features are supported. + pub const NONE: Features = Features(0b00000000); + + /// `SEED` is the base feature set all seed nodes must support. + pub const SEED: Features = Features(0b00000001); + + /// Returns [`Features`] with the other features added. + #[must_use] + pub fn with(self, other: Features) -> Features { + Self(self.0 | other.0) + } + + /// Returns [`Features`] without the other features. + #[must_use] + pub fn without(self, other: Features) -> Features { + Self(self.0 ^ other.0) + } + + /// Check whether [`Features`] are included. + pub fn has(self, flags: Features) -> bool { + (self.0 | flags.0) == self.0 + } +} + +impl Default for Features { + fn default() -> Self { + Self::NONE + } +} + +impl ops::Deref for Features { + type Target = u64; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl fmt::LowerHex for Features { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::LowerHex::fmt(&self.0, f) + } +} + +impl fmt::UpperHex for Features { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::UpperHex::fmt(&self.0, f) + } +} + +impl fmt::Display for Features { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if *self == Features::NONE { + write!(f, "Features(NONE)") + } else { + write!(f, "Features(0x{:x})", self.0) + } + } +} + +impl From for Features { + fn from(f: u64) -> Self { + Features(f) + } +} + +impl From for u64 { + fn from(flags: Features) -> Self { + flags.0 + } +} + +impl ops::BitOr for Features { + type Output = Self; + + fn bitor(self, rhs: Self) -> Self { + self.with(rhs) + } +} + +impl ops::BitOrAssign for Features { + fn bitor_assign(&mut self, rhs: Self) { + *self = self.with(rhs); + } +} + +impl ops::BitXor for Features { + type Output = Self; + + fn bitxor(self, rhs: Self) -> Self { + self.without(rhs) + } +} + +impl ops::BitXorAssign for Features { + fn bitxor_assign(&mut self, rhs: Self) { + *self = self.without(rhs); + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_operations() { + assert_eq!(Features::NONE.with(Features::SEED), Features::SEED); + + assert!(!Features::from(u64::MAX) + .without(Features::SEED) + .has(Features::SEED)); + + assert!(Features::from(u64::MIN) + .with(Features::SEED) + .has(Features::SEED)); + + assert_eq!( + Features::NONE.with(Features::SEED).without(Features::SEED), + Features::NONE + ); + } +}