458 lines
13 KiB
Rust
458 lines
13 KiB
Rust
use std::{io, mem, net};
|
|
|
|
use byteorder::{NetworkEndian, ReadBytesExt};
|
|
use cyphernet::addr::{Addr, HostName, NetAddr};
|
|
use radicle::node::Address;
|
|
|
|
use crate::prelude::*;
|
|
use crate::service::message::*;
|
|
use crate::wire;
|
|
use crate::wire::{Decode, Encode};
|
|
|
|
/// Message type.
|
|
#[repr(u16)]
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub enum MessageType {
|
|
NodeAnnouncement = 2,
|
|
InventoryAnnouncement = 4,
|
|
RefsAnnouncement = 6,
|
|
Subscribe = 8,
|
|
Ping = 10,
|
|
Pong = 12,
|
|
}
|
|
|
|
impl From<MessageType> for u16 {
|
|
fn from(other: MessageType) -> Self {
|
|
other as u16
|
|
}
|
|
}
|
|
|
|
impl TryFrom<u16> for MessageType {
|
|
type Error = u16;
|
|
|
|
fn try_from(other: u16) -> Result<Self, Self::Error> {
|
|
match other {
|
|
2 => Ok(MessageType::NodeAnnouncement),
|
|
4 => Ok(MessageType::InventoryAnnouncement),
|
|
6 => Ok(MessageType::RefsAnnouncement),
|
|
8 => Ok(MessageType::Subscribe),
|
|
10 => Ok(MessageType::Ping),
|
|
12 => Ok(MessageType::Pong),
|
|
_ => Err(other),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Message {
|
|
/// The maximum supported message size in bytes.
|
|
pub const MAX_SIZE: wire::Size =
|
|
wire::Size::MAX - (mem::size_of::<MessageType>() as wire::Size);
|
|
|
|
pub fn type_id(&self) -> u16 {
|
|
match self {
|
|
Self::Subscribe { .. } => MessageType::Subscribe,
|
|
Self::Announcement(Announcement { message, .. }) => match message {
|
|
AnnouncementMessage::Node(_) => MessageType::NodeAnnouncement,
|
|
AnnouncementMessage::Inventory(_) => MessageType::InventoryAnnouncement,
|
|
AnnouncementMessage::Refs(_) => MessageType::RefsAnnouncement,
|
|
},
|
|
Self::Ping { .. } => MessageType::Ping,
|
|
Self::Pong { .. } => MessageType::Pong,
|
|
}
|
|
.into()
|
|
}
|
|
}
|
|
|
|
impl netservices::Frame for Message {
|
|
type Error = wire::Error;
|
|
|
|
fn unmarshall(mut reader: impl io::Read) -> Result<Option<Self>, Self::Error> {
|
|
match Message::decode(&mut reader) {
|
|
Ok(msg) => Ok(Some(msg)),
|
|
Err(wire::Error::Io(_)) => Ok(None),
|
|
Err(err) => Err(err),
|
|
}
|
|
}
|
|
|
|
fn marshall(&self, mut writer: impl io::Write) -> Result<usize, Self::Error> {
|
|
self.encode(&mut writer).map_err(wire::Error::from)
|
|
}
|
|
}
|
|
|
|
/// Address type.
|
|
#[repr(u8)]
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub enum AddressType {
|
|
Ipv4 = 1,
|
|
Ipv6 = 2,
|
|
Dns = 3,
|
|
Onion = 4,
|
|
}
|
|
|
|
impl From<AddressType> for u8 {
|
|
fn from(other: AddressType) -> Self {
|
|
other as u8
|
|
}
|
|
}
|
|
|
|
impl From<&Address> for AddressType {
|
|
fn from(a: &Address) -> Self {
|
|
match a.host {
|
|
HostName::Ip(net::IpAddr::V4(_)) => AddressType::Ipv4,
|
|
HostName::Ip(net::IpAddr::V6(_)) => AddressType::Ipv6,
|
|
HostName::Dns(_) => AddressType::Dns,
|
|
HostName::Tor(_) => AddressType::Onion,
|
|
_ => todo!(), // FIXME(cloudhead): Maxim will remove `non-exhaustive`
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TryFrom<u8> for AddressType {
|
|
type Error = u8;
|
|
|
|
fn try_from(other: u8) -> Result<Self, Self::Error> {
|
|
match other {
|
|
1 => Ok(AddressType::Ipv4),
|
|
2 => Ok(AddressType::Ipv6),
|
|
3 => Ok(AddressType::Dns),
|
|
4 => Ok(AddressType::Onion),
|
|
_ => Err(other),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl wire::Encode for AnnouncementMessage {
|
|
fn encode<W: std::io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, std::io::Error> {
|
|
match self {
|
|
Self::Node(ann) => ann.encode(writer),
|
|
Self::Inventory(ann) => ann.encode(writer),
|
|
Self::Refs(ann) => ann.encode(writer),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl wire::Encode for RefsAnnouncement {
|
|
fn encode<W: io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error> {
|
|
let mut n = 0;
|
|
|
|
n += self.rid.encode(writer)?;
|
|
n += self.refs.encode(writer)?;
|
|
n += self.timestamp.encode(writer)?;
|
|
|
|
Ok(n)
|
|
}
|
|
}
|
|
|
|
impl wire::Decode for RefsAnnouncement {
|
|
fn decode<R: std::io::Read + ?Sized>(reader: &mut R) -> Result<Self, wire::Error> {
|
|
let rid = Id::decode(reader)?;
|
|
let refs = BoundedVec::decode(reader)?;
|
|
let timestamp = Timestamp::decode(reader)?;
|
|
|
|
Ok(Self {
|
|
rid,
|
|
refs,
|
|
timestamp,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl wire::Encode for InventoryAnnouncement {
|
|
fn encode<W: io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error> {
|
|
let mut n = 0;
|
|
|
|
n += self.inventory.encode(writer)?;
|
|
n += self.timestamp.encode(writer)?;
|
|
|
|
Ok(n)
|
|
}
|
|
}
|
|
|
|
impl wire::Decode for InventoryAnnouncement {
|
|
fn decode<R: std::io::Read + ?Sized>(reader: &mut R) -> Result<Self, wire::Error> {
|
|
let inventory = BoundedVec::decode(reader)?;
|
|
let timestamp = Timestamp::decode(reader)?;
|
|
|
|
Ok(Self {
|
|
inventory,
|
|
timestamp,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl wire::Encode for Message {
|
|
fn encode<W: std::io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, std::io::Error> {
|
|
let mut n = self.type_id().encode(writer)?;
|
|
|
|
match self {
|
|
Self::Subscribe(Subscribe {
|
|
filter,
|
|
since,
|
|
until,
|
|
}) => {
|
|
n += filter.encode(writer)?;
|
|
n += since.encode(writer)?;
|
|
n += until.encode(writer)?;
|
|
}
|
|
Self::Announcement(Announcement {
|
|
node,
|
|
message,
|
|
signature,
|
|
}) => {
|
|
n += node.encode(writer)?;
|
|
n += message.encode(writer)?;
|
|
n += signature.encode(writer)?;
|
|
}
|
|
Self::Ping(Ping { ponglen, zeroes }) => {
|
|
n += ponglen.encode(writer)?;
|
|
n += zeroes.encode(writer)?;
|
|
}
|
|
Self::Pong { zeroes } => {
|
|
n += zeroes.encode(writer)?;
|
|
}
|
|
}
|
|
|
|
if n > wire::Size::MAX as usize {
|
|
return Err(io::Error::new(
|
|
io::ErrorKind::InvalidData,
|
|
"Message exceeds maximum size",
|
|
));
|
|
}
|
|
Ok(n)
|
|
}
|
|
}
|
|
|
|
impl wire::Decode for Message {
|
|
fn decode<R: std::io::Read + ?Sized>(reader: &mut R) -> Result<Self, wire::Error> {
|
|
let type_id = reader.read_u16::<NetworkEndian>()?;
|
|
|
|
match MessageType::try_from(type_id) {
|
|
Ok(MessageType::Subscribe) => {
|
|
let filter = Filter::decode(reader)?;
|
|
let since = Timestamp::decode(reader)?;
|
|
let until = Timestamp::decode(reader)?;
|
|
|
|
Ok(Self::Subscribe(Subscribe {
|
|
filter,
|
|
since,
|
|
until,
|
|
}))
|
|
}
|
|
Ok(MessageType::NodeAnnouncement) => {
|
|
let node = NodeId::decode(reader)?;
|
|
let message = NodeAnnouncement::decode(reader)?.into();
|
|
let signature = Signature::decode(reader)?;
|
|
|
|
Ok(Announcement {
|
|
node,
|
|
message,
|
|
signature,
|
|
}
|
|
.into())
|
|
}
|
|
Ok(MessageType::InventoryAnnouncement) => {
|
|
let node = NodeId::decode(reader)?;
|
|
let message = InventoryAnnouncement::decode(reader)?.into();
|
|
let signature = Signature::decode(reader)?;
|
|
|
|
Ok(Announcement {
|
|
node,
|
|
message,
|
|
signature,
|
|
}
|
|
.into())
|
|
}
|
|
Ok(MessageType::RefsAnnouncement) => {
|
|
let node = NodeId::decode(reader)?;
|
|
let message = RefsAnnouncement::decode(reader)?.into();
|
|
let signature = Signature::decode(reader)?;
|
|
|
|
Ok(Announcement {
|
|
node,
|
|
message,
|
|
signature,
|
|
}
|
|
.into())
|
|
}
|
|
Ok(MessageType::Ping) => {
|
|
let ponglen = u16::decode(reader)?;
|
|
let zeroes = ZeroBytes::decode(reader)?;
|
|
Ok(Self::Ping(Ping { ponglen, zeroes }))
|
|
}
|
|
Ok(MessageType::Pong) => {
|
|
let zeroes = ZeroBytes::decode(reader)?;
|
|
Ok(Self::Pong { zeroes })
|
|
}
|
|
Err(other) => Err(wire::Error::UnknownMessageType(other)),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl wire::Encode for Address {
|
|
fn encode<W: std::io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, std::io::Error> {
|
|
let mut n = 0;
|
|
|
|
match self.host {
|
|
HostName::Ip(net::IpAddr::V4(ip)) => {
|
|
n += u8::from(AddressType::Ipv4).encode(writer)?;
|
|
n += ip.octets().encode(writer)?;
|
|
}
|
|
HostName::Ip(net::IpAddr::V6(ip)) => {
|
|
n += u8::from(AddressType::Ipv6).encode(writer)?;
|
|
n += ip.octets().encode(writer)?;
|
|
}
|
|
HostName::Dns(ref dns) => {
|
|
n += u8::from(AddressType::Dns).encode(writer)?;
|
|
n += dns.encode(writer)?;
|
|
}
|
|
_ => {
|
|
todo!();
|
|
}
|
|
}
|
|
n += self.port().encode(writer)?;
|
|
|
|
Ok(n)
|
|
}
|
|
}
|
|
|
|
impl wire::Decode for Address {
|
|
fn decode<R: std::io::Read + ?Sized>(reader: &mut R) -> Result<Self, wire::Error> {
|
|
let addrtype = reader.read_u8()?;
|
|
let host = match AddressType::try_from(addrtype) {
|
|
Ok(AddressType::Ipv4) => {
|
|
let octets: [u8; 4] = wire::Decode::decode(reader)?;
|
|
let ip = net::Ipv4Addr::from(octets);
|
|
|
|
HostName::Ip(net::IpAddr::V4(ip))
|
|
}
|
|
Ok(AddressType::Ipv6) => {
|
|
let octets: [u8; 16] = wire::Decode::decode(reader)?;
|
|
let ip = net::Ipv6Addr::from(octets);
|
|
|
|
HostName::Ip(net::IpAddr::V6(ip))
|
|
}
|
|
Ok(AddressType::Dns) => {
|
|
let dns: String = wire::Decode::decode(reader)?;
|
|
|
|
HostName::Dns(dns)
|
|
}
|
|
Ok(AddressType::Onion) => {
|
|
todo!();
|
|
}
|
|
Err(other) => return Err(wire::Error::UnknownAddressType(other)),
|
|
};
|
|
let port = u16::decode(reader)?;
|
|
|
|
Ok(Self::from(NetAddr { host, port }))
|
|
}
|
|
}
|
|
|
|
impl wire::Encode for ZeroBytes {
|
|
fn encode<W: io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error> {
|
|
let mut n = (self.len() as u16).encode(writer)?;
|
|
for _ in 0..self.len() {
|
|
n += 0u8.encode(writer)?;
|
|
}
|
|
Ok(n)
|
|
}
|
|
}
|
|
|
|
impl wire::Decode for ZeroBytes {
|
|
fn decode<R: std::io::Read + ?Sized>(reader: &mut R) -> Result<Self, wire::Error> {
|
|
let zeroes = u16::decode(reader)?;
|
|
for _ in 0..zeroes {
|
|
_ = u8::decode(reader)?;
|
|
}
|
|
Ok(ZeroBytes::new(zeroes))
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use qcheck_macros::quickcheck;
|
|
|
|
use crate::deserializer::Deserializer;
|
|
use crate::wire::{self, Encode};
|
|
|
|
#[test]
|
|
fn test_pingpong_encode_max_size() {
|
|
let mut buf = Vec::new();
|
|
|
|
let ping = Message::Ping(Ping {
|
|
ponglen: 0,
|
|
zeroes: ZeroBytes::new(Ping::MAX_PING_ZEROES),
|
|
});
|
|
ping.encode(&mut buf)
|
|
.expect("ping should be within max message size");
|
|
|
|
let pong = Message::Pong {
|
|
zeroes: ZeroBytes::new(Ping::MAX_PONG_ZEROES),
|
|
};
|
|
pong.encode(&mut buf)
|
|
.expect("pong should be within max message size");
|
|
}
|
|
|
|
#[test]
|
|
fn test_pingpong_encode_size_overflow() {
|
|
let ping = Message::Ping(Ping {
|
|
ponglen: 0,
|
|
zeroes: ZeroBytes::new(Ping::MAX_PING_ZEROES + 1),
|
|
});
|
|
|
|
let mut buf = Vec::new();
|
|
ping.encode(&mut buf)
|
|
.expect_err("ping should exceed max message size");
|
|
|
|
let pong = Message::Pong {
|
|
zeroes: ZeroBytes::new(Ping::MAX_PONG_ZEROES + 1),
|
|
};
|
|
|
|
let mut buf = Vec::new();
|
|
pong.encode(&mut buf)
|
|
.expect_err("pong should exceed max message size");
|
|
}
|
|
|
|
#[quickcheck]
|
|
fn prop_message_encode_decode(message: Message) {
|
|
assert_eq!(
|
|
wire::deserialize::<Message>(&wire::serialize(&message)).unwrap(),
|
|
message
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn prop_message_decoder() {
|
|
fn property(items: Vec<Message>) {
|
|
let mut decoder = Deserializer::<Message>::new(8);
|
|
|
|
for item in &items {
|
|
item.encode(&mut decoder).unwrap();
|
|
}
|
|
for item in items {
|
|
assert_eq!(decoder.next().unwrap().unwrap(), item);
|
|
}
|
|
}
|
|
|
|
qcheck::QuickCheck::new()
|
|
.gen(qcheck::Gen::new(16))
|
|
.quickcheck(property as fn(items: Vec<Message>));
|
|
}
|
|
|
|
#[quickcheck]
|
|
fn prop_zero_bytes_encode_decode(zeroes: ZeroBytes) {
|
|
assert_eq!(
|
|
wire::deserialize::<ZeroBytes>(&wire::serialize(&zeroes)).unwrap(),
|
|
zeroes
|
|
);
|
|
}
|
|
|
|
#[quickcheck]
|
|
fn prop_addr(addr: Address) {
|
|
assert_eq!(
|
|
wire::deserialize::<Address>(&wire::serialize(&addr)).unwrap(),
|
|
addr
|
|
);
|
|
}
|
|
}
|