rust_ipv8/src/community/mod.rs
//! In this module, all communities are defined. Currently none as
//! communities are still handled by py-ipv8 but the tools to move them soon
//! are already in place.
use crate::serialization::{Packet, PacketDeserializer};
use crate::serialization::header::Header;
use std::error::Error;
use std::collections::HashMap;
use crate::networking::address::Address;
use crate::networking::NetworkSender;
#[cfg(test)]
use std::sync::atomic::AtomicUsize;
pub mod peer;
create_error!(
HeaderUnwrapError,
"The community experienced an error trying to deserialize the header of a packet"
);
create_error!(MidError, "Failed to get the mid");
create_error!(
UnknownCommunityError,
"No community with matching mid found"
);
#[cfg(test)]
static WARN_DEPRECATED_CALLS: AtomicUsize = AtomicUsize::new(0);
/// # Community struct
/// This is the main struct defining a community
///
/// ## Example Community
/// This is an example of how to create a community
///
/// _**Note:** Try to avoid the use of .unwrap() in actual production code, this is just an example_
///
pub trait Community {
/// Every community should have a constructor.
/// It will receive an endpoint from the [NetworkSender](crate::networking::NetworkSender) which constructs
/// the community. An endpoint is used to send messages over the network to other
/// communities.
fn new(endpoint: &NetworkSender) -> Result<Self, Box<dyn Error>>
where
Self: Sized;
/// Returns a unique (currently 20 byte) sequence identifying a community.
///
/// This is used to be the SHA1 hash of its public key. You are free to choose whatever.
///
/// As OpenSSL keys are deprecated, this library provides no way of calculating the sha1 of an OpenSSL key.
/// Master peer keys still can be OpenSSL keys. This SHA1 has to be hardcoded for communities that are
/// compatible with old communities. New communities recommended to use ED25519 in the future which
/// sha1 hashes can be calculated.
///
/// The sha1 of a key does not serve any purpose besides uniquely identifying communities and as such can be any
/// unique 20 byte sequence.
fn get_mid(&self) -> Vec<u8>;
/// Gets called whenever a packet is received directed at this community
/// DO NOT OVERRIDE
#[doc(hidden)]
fn receive(
&self,
header: Header,
deserializer: PacketDeserializer,
address: Address,
) -> Result<(), Box<dyn Error>> {
// DO NOT OVERRIDE
//! used to pre-decode the header and filter out messages
//!
// Used in on_receive. Has to be outside to be testable.
#[doc(hidden)]
fn warn_deprecated(message: &str, address: Address) -> Result<(), Box<dyn Error>> {
warn!(
"Received deprecated message {} from ({:?})",
message, address
);
#[cfg(test)]
{
WARN_DEPRECATED_CALLS.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
}
Ok(())
}
match header.message_type.ok_or(HeaderUnwrapError)? {
255 => warn_deprecated("reserved-255", address),
254 => warn_deprecated("on-missing-sequence", address),
253 => warn_deprecated("missing-proof", address),
252 => warn_deprecated("signature-request", address),
251 => warn_deprecated("signature-response", address),
248 => warn_deprecated("on-identity", address),
247 => warn_deprecated("on-missing-identity", address),
244 => warn_deprecated("destroy-community", address),
243 => warn_deprecated("authorize", address),
242 => warn_deprecated("revoke", address),
241 => warn_deprecated("subjective-set", address),
240 => warn_deprecated("missing-subjective-set", address),
239 => warn_deprecated("on-missing-message", address),
238 => warn_deprecated("undo-own", address),
237 => warn_deprecated("undo-other", address),
236 => warn_deprecated("dynamic-settings", address),
235 => warn_deprecated("missing-last-message", address),
_ => self.on_receive(header, deserializer, address),
}
}
/// This method called for every incoming message, directed at this community, which is not captured.
///
/// Messages are captured whenever they have a reserved message_type (235 ~ 255). These are used for legacy support
/// and some default responses which every community should give.
fn on_receive(
&self,
header: Header,
deserializer: PacketDeserializer,
address: Address,
) -> Result<(), Box<dyn Error>>;
}
/// Every different kind of community is registered here with it's MID.
///
/// So that incoming messages can be distributed to the right communities. Makes use of a hashmap to achieve
/// O(1) lookup time.
pub struct CommunityRegistry {
// mid, community
#[cfg(test)]
pub communities: HashMap<Vec<u8>, Box<dyn Community>>,
#[cfg(not(test))]
/// A HashMap of all the communities so we can know who to send what packet
communities: HashMap<Vec<u8>, Box<dyn Community>>,
}
impl CommunityRegistry {
/// Adds a community to the registry.
pub fn add_community(&mut self, item: Box<dyn Community>) -> Result<(), Box<dyn Error>> {
match self.communities.insert(item.get_mid(), item) {
// none means the key wasn't already present in the map, some means it was and it returns it.
// We don't care about this.
_ => Ok(()),
}
}
/// Forwards the message to the corresponding community
pub fn forward_message(&self, packet: Packet, address: Address) -> Result<(), Box<dyn Error>> {
// deserialize the header
let deserializer = packet.start_deserialize();
// We use peek here instead of get, even though we give the header along with the receive call
// this is because at this point, the header is not verified yet so we still assume the message is valid.
// We can't verify the header here yet as not all messages have a signature. Communities will have to decide
// on their own if they want to verify the header. We do give it along as only having to deserialize the header once
// makes it slightly more efficient.
let header = deserializer.peek_header()?;
// get the mid from the header and use it for a hashtable lookup
let mid = header.mid_hash.as_ref().ok_or(MidError)?;
let community = self.communities.get(mid).ok_or(UnknownCommunityError)?;
// Actually forward it
community.receive(header, deserializer, address)
}
}
impl Default for CommunityRegistry {
/// Returns a new community registry with all the built-in communities already registered.
/// All custom communities can be added with the [add_community](#method.add_community) method.
fn default() -> Self {
Self {
communities: HashMap::new(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::networking::address::Address;
use std::net::{SocketAddr, IpAddr};
use std::error::Error;
use crate::networking::NetworkSender;
use crate::community::peer::Peer;
use crate::community::{Community, CommunityRegistry};
use crate::serialization::header::Header;
use crate::serialization::{PacketDeserializer, Packet};
use std::net::Ipv4Addr;
use crate::IPv8;
use crate::configuration::Config;
use crate::serialization::header::HeaderVersion::PyIPV8Header;
use std::sync::atomic::Ordering;
use crate::networking::test_helper::localhost;
use crate::crypto::signature::KeyPair;
pub struct TestCommunity {
peer: Peer,
}
impl Community for TestCommunity {
fn new(_endpoint: &NetworkSender) -> Result<Self, Box<dyn Error>> {
let pk = KeyPair::from_seed_unchecked(&[
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
23, 24, 25, 26, 27, 28, 29, 30, 31,
])
.unwrap();
// Actually create the community
Ok(TestCommunity {
peer: Peer::new(
pk.public_key().unwrap(),
Address(SocketAddr::new(
IpAddr::V4(Ipv4Addr::new(42, 42, 42, 42)),
8000,
)),
true,
),
})
}
// Returns the hash of our master peer
fn get_mid(&self) -> Vec<u8> {
self.peer.get_sha1()
}
// The function which will be called when the community receives a packet
fn on_receive(
&self,
header: Header,
_deserializer: PacketDeserializer,
_address: Address,
) -> Result<(), Box<dyn Error>> {
assert_eq!(header.mid_hash.unwrap(), self.get_mid());
assert_eq!(header.version, PyIPV8Header);
assert_eq!(header.message_type, Some(42));
Ok(())
}
}
#[test]
fn test_deprecated() {
let mut config = Config::default();
config.sending_address = Address(SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 0));
config.receiving_address =
Address(SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 0));
let ipv8 = IPv8::new(config).unwrap();
let community = TestCommunity::new(&ipv8.network_sender).unwrap();
for i in &[
255, 254, 253, 252, 251, 248, 247, 244, 243, 242, 241, 240, 239, 238, 237, 236, 235,
] {
let address = Address(SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0));
let packet = Packet::new(Header {
size: 23,
version: PyIPV8Header,
mid_hash: Some(community.get_mid()),
message_type: Some(*i),
})
.unwrap();
let deser = packet.start_deserialize();
let header = deser.peek_header().unwrap();
community.receive(header, deser, address).unwrap();
}
assert_eq!(17, WARN_DEPRECATED_CALLS.load(Ordering::SeqCst))
}
#[test]
fn test_add_community() {
let config = Config::default();
let ipv8 = IPv8::new(config).unwrap();
let community = Box::new(TestCommunity::new(&ipv8.network_sender).unwrap());
let the_same = Box::new(TestCommunity::new(&ipv8.network_sender).unwrap());
let mid = &*community.get_mid();
let mut registry: CommunityRegistry = CommunityRegistry::default();
registry.add_community(community).unwrap();
let get = registry.communities.get(mid).unwrap();
assert_eq!(the_same.get_mid(), get.get_mid()); // TODO: More thorough comparison
}
#[test]
fn test_networking() {
let mut config = Config::default();
config.receiving_address = localhost();
config.sending_address = localhost();
config.buffersize = 2048;
let mut ipv8 = IPv8::new(config).unwrap();
let community = TestCommunity::new(&ipv8.network_sender).unwrap();
let mid = community.get_mid();
ipv8.communities.add_community(Box::new(community)).unwrap();
// now simulate a packet coming in
// Create a packet to test the community with
let packet = Packet::new(Header {
size: 23,
version: PyIPV8Header,
mid_hash: Some(mid),
message_type: Some(42),
})
.unwrap();
// Normally you would want to sign the packet here
// Send the packet
ipv8.communities
.forward_message(
packet,
Address(SocketAddr::new(
IpAddr::V4(Ipv4Addr::new(42, 42, 42, 42)),
42,
)),
)
.unwrap();
}
}