use bytes::{Buf, BufMut};
use num_enum::{IntoPrimitive, TryFromPrimitive};
use crate::{Origin, OriginList, Path, coding::*};
use super::{Message, Version};
const ANNOUNCE_START: u64 = 0;
const ANNOUNCE_END: u64 = 1;
const ANNOUNCE_RESTART: u64 = 2;
pub fn restart_supported(version: Version) -> bool {
!matches!(
version,
Version::Lite01 | Version::Lite02 | Version::Lite03 | Version::Lite04
)
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum AnnounceBroadcast<'a> {
Active {
suffix: Path<'a>,
hops: OriginList,
cost: RouteCost,
},
Ended { suffix: Path<'a>, hops: OriginList },
EndedId { id: u64 },
Restart { id: u64, hops: OriginList, cost: RouteCost },
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct RouteCost(pub u64);
impl RouteCost {
pub fn charged(self, link_cost: u64) -> Self {
Self(self.0.saturating_add(link_cost))
}
}
impl Encode<Version> for RouteCost {
fn encode<W: BufMut>(&self, w: &mut W, version: Version) -> Result<(), EncodeError> {
if !version.has_route_cost() {
return Ok(());
}
self.0.encode(w, version)
}
}
impl Decode<Version> for RouteCost {
fn decode<B: Buf>(buf: &mut B, version: Version) -> Result<Self, DecodeError> {
if !version.has_route_cost() {
return Ok(Self::default());
}
Ok(Self(u64::decode(buf, version)?))
}
}
impl Encode<Version> for AnnounceBroadcast<'_> {
fn encode<W: BufMut>(&self, w: &mut W, version: Version) -> Result<(), EncodeError> {
if version.has_announce_id() {
let mut body = Vec::new();
let typ = match self {
Self::Active { suffix, hops, cost } => {
suffix.encode(&mut body, version)?;
hops.encode(&mut body, version)?;
cost.encode(&mut body, version)?;
ANNOUNCE_START
}
Self::EndedId { id } => {
id.encode(&mut body, version)?;
ANNOUNCE_END
}
Self::Restart { id, hops, cost } => {
id.encode(&mut body, version)?;
hops.encode(&mut body, version)?;
cost.encode(&mut body, version)?;
ANNOUNCE_RESTART
}
Self::Ended { .. } => return Err(EncodeError::Version),
};
typ.encode(w, version)?;
(body.len() as u64).encode(w, version)?;
w.put_slice(&body);
return Ok(());
}
let mut body = Vec::new();
match self {
Self::Active { suffix, hops, .. } => {
AnnounceStatus::Active.encode(&mut body, version)?;
suffix.encode(&mut body, version)?;
encode_hops(&mut body, version, hops)?;
}
Self::Ended { suffix, hops } => {
AnnounceStatus::Ended.encode(&mut body, version)?;
suffix.encode(&mut body, version)?;
encode_hops(&mut body, version, hops)?;
}
Self::EndedId { .. } | Self::Restart { .. } => return Err(EncodeError::Version),
}
(body.len() as u64).encode(w, version)?;
w.put_slice(&body);
Ok(())
}
}
impl Decode<Version> for AnnounceBroadcast<'_> {
fn decode<B: Buf>(buf: &mut B, version: Version) -> Result<Self, DecodeError> {
if version.has_announce_id() {
let typ = u64::decode(buf, version)?;
let size = usize::decode(buf, version)?;
if buf.remaining() < size {
return Err(DecodeError::Short);
}
let mut body = buf.take(size);
let msg = match typ {
ANNOUNCE_START => Self::Active {
suffix: Path::decode(&mut body, version)?,
hops: OriginList::decode(&mut body, version)?,
cost: RouteCost::decode(&mut body, version)?,
},
ANNOUNCE_END => Self::EndedId {
id: u64::decode(&mut body, version)?,
},
ANNOUNCE_RESTART => Self::Restart {
id: u64::decode(&mut body, version)?,
hops: OriginList::decode(&mut body, version)?,
cost: RouteCost::decode(&mut body, version)?,
},
_ => return Err(DecodeError::InvalidMessage(typ)),
};
if body.remaining() > 0 {
return Err(DecodeError::Long);
}
return Ok(msg);
}
let size = usize::decode(buf, version)?;
if buf.remaining() < size {
return Err(DecodeError::Short);
}
let mut body = buf.take(size);
let msg = Self::decode_legacy(&mut body, version)?;
if body.remaining() > 0 {
return Err(DecodeError::Long);
}
Ok(msg)
}
}
impl AnnounceBroadcast<'_> {
fn decode_legacy<R: Buf>(r: &mut R, version: Version) -> Result<Self, DecodeError> {
let status = AnnounceStatus::decode(r, version)?;
let suffix = Path::decode(r, version)?;
let hops = match version {
Version::Lite01 | Version::Lite02 => OriginList::new(),
Version::Lite03 => {
let count = u64::decode(r, version)? as usize;
let mut list = OriginList::new();
for _ in 0..count {
list.push(Origin::UNKNOWN)?;
}
list
}
_ => OriginList::decode(r, version)?,
};
Ok(match status {
AnnounceStatus::Active => Self::Active {
suffix,
hops,
cost: RouteCost::default(),
},
AnnounceStatus::Ended => Self::Ended { suffix, hops },
AnnounceStatus::Restart if restart_supported(version) => Self::Active {
suffix,
hops,
cost: RouteCost::default(),
},
AnnounceStatus::Restart => return Err(DecodeError::InvalidValue),
})
}
}
fn encode_hops<W: bytes::BufMut>(w: &mut W, version: Version, hops: &OriginList) -> Result<(), EncodeError> {
match version {
Version::Lite01 | Version::Lite02 => Ok(()),
Version::Lite03 => (hops.len() as u64).encode(w, version),
_ => hops.encode(w, version),
}
}
#[derive(Clone, Debug)]
pub struct AnnounceRequest<'a> {
pub prefix: Path<'a>,
pub exclude_hop: u64,
}
impl Message for AnnounceRequest<'_> {
fn decode_msg<R: bytes::Buf>(r: &mut R, version: Version) -> Result<Self, DecodeError> {
let prefix = Path::decode(r, version)?;
let exclude_hop = match version {
Version::Lite01 | Version::Lite02 | Version::Lite03 => 0,
_ => u64::decode(r, version)?,
};
Ok(Self { prefix, exclude_hop })
}
fn encode_msg<W: bytes::BufMut>(&self, w: &mut W, version: Version) -> Result<(), EncodeError> {
self.prefix.encode(w, version)?;
match version {
Version::Lite01 | Version::Lite02 | Version::Lite03 => {}
_ => {
self.exclude_hop.encode(w, version)?;
}
}
Ok(())
}
}
#[derive(Clone, Copy, Debug, IntoPrimitive, TryFromPrimitive)]
#[repr(u8)]
enum AnnounceStatus {
Ended = 0,
Active = 1,
Restart = 2,
}
impl Decode<Version> for AnnounceStatus {
fn decode<R: bytes::Buf>(r: &mut R, version: Version) -> Result<Self, DecodeError> {
let status = u8::decode(r, version)?;
status.try_into().map_err(|_| DecodeError::InvalidValue)
}
}
impl Encode<Version> for AnnounceStatus {
fn encode<W: bytes::BufMut>(&self, w: &mut W, version: Version) -> Result<(), EncodeError> {
(*self as u8).encode(w, version)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AnnounceInit<'a> {
pub suffixes: Vec<Path<'a>>,
}
impl Message for AnnounceInit<'_> {
fn decode_msg<R: bytes::Buf>(r: &mut R, version: Version) -> Result<Self, DecodeError> {
match version {
Version::Lite01 | Version::Lite02 => {}
_ => {
return Err(DecodeError::Version);
}
}
let count = u64::decode(r, version)?;
let mut paths = Vec::with_capacity(count.min(1024) as usize);
for _ in 0..count {
paths.push(Path::decode(r, version)?);
}
Ok(Self { suffixes: paths })
}
fn encode_msg<W: bytes::BufMut>(&self, w: &mut W, version: Version) -> Result<(), EncodeError> {
match version {
Version::Lite01 | Version::Lite02 => {}
_ => {
return Err(EncodeError::Version);
}
}
(self.suffixes.len() as u64).encode(w, version)?;
for path in &self.suffixes {
path.encode(w, version)?;
}
Ok(())
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct AnnounceOk {
pub origin: Origin,
pub active: u64,
}
impl Message for AnnounceOk {
fn decode_msg<R: bytes::Buf>(r: &mut R, version: Version) -> Result<Self, DecodeError> {
if !version.has_announce_ok() {
return Err(DecodeError::Version);
}
let origin = Origin::decode(r, version)?;
let active = u64::decode(r, version)?;
Ok(Self { origin, active })
}
fn encode_msg<W: bytes::BufMut>(&self, w: &mut W, version: Version) -> Result<(), EncodeError> {
if !version.has_announce_ok() {
return Err(EncodeError::Version);
}
self.origin.encode(w, version)?;
self.active.encode(w, version)
}
}
#[cfg(test)]
mod tests {
use super::*;
use bytes::Buf;
fn encode_forged_restart(version: Version) -> bytes::Bytes {
let mut buf = bytes::BytesMut::new();
AnnounceBroadcast::Active {
suffix: Path::new("foo/bar"),
hops: OriginList::new(),
cost: RouteCost::default(),
}
.encode(&mut buf, version)
.expect("encode");
assert_eq!(
buf[1],
u8::from(AnnounceStatus::Active),
"expected an Active status byte"
);
buf[1] = u8::from(AnnounceStatus::Restart);
buf.freeze()
}
#[test]
fn decodes_explicit_restart_status_as_active_on_lite05() {
let version = Version::Lite05;
let mut slice = encode_forged_restart(version);
let decoded = AnnounceBroadcast::decode(&mut slice, version).expect("explicit restart must decode");
assert!(!slice.has_remaining(), "trailing bytes after decode");
assert!(
matches!(decoded, AnnounceBroadcast::Active { .. }),
"restart should decode as Active"
);
}
#[test]
fn rejects_explicit_restart_status_before_lite05() {
let version = Version::Lite04;
let mut slice = encode_forged_restart(version);
assert!(
matches!(
AnnounceBroadcast::decode(&mut slice, version),
Err(DecodeError::InvalidValue)
),
"restart status must be rejected before lite-05"
);
}
fn round_trip(msg: &AnnounceOk) -> AnnounceOk {
let mut buf = bytes::BytesMut::new();
msg.encode(&mut buf, Version::Lite05).unwrap();
let mut slice = &buf[..];
let got = AnnounceOk::decode(&mut slice, Version::Lite05).unwrap();
assert!(slice.is_empty(), "trailing bytes after decode");
got
}
#[test]
fn announce_ok_round_trip() {
let msg = AnnounceOk {
origin: Origin::new(42).unwrap(),
active: 3,
};
assert_eq!(round_trip(&msg), msg);
}
#[test]
fn announce_ok_zero_active() {
let msg = AnnounceOk {
origin: Origin::new(7).unwrap(),
active: 0,
};
assert_eq!(round_trip(&msg), msg);
}
fn broadcast_round_trip(msg: &AnnounceBroadcast, version: Version) -> AnnounceBroadcast<'static> {
let mut buf = bytes::BytesMut::new();
msg.encode(&mut buf, version).unwrap();
let mut slice = &buf[..];
let got = AnnounceBroadcast::decode(&mut slice, version).unwrap();
assert!(slice.is_empty(), "trailing bytes after decode");
match got {
AnnounceBroadcast::Active { suffix, hops, cost } => AnnounceBroadcast::Active {
suffix: suffix.to_owned(),
hops,
cost,
},
AnnounceBroadcast::Ended { suffix, hops } => AnnounceBroadcast::Ended {
suffix: suffix.to_owned(),
hops,
},
AnnounceBroadcast::EndedId { id } => AnnounceBroadcast::EndedId { id },
AnnounceBroadcast::Restart { id, hops, cost } => AnnounceBroadcast::Restart { id, hops, cost },
}
}
#[test]
fn announce_broadcast_round_trip_on_lite05() {
let mut hops = OriginList::new();
hops.push(Origin::new(7).unwrap()).unwrap();
let msg = AnnounceBroadcast::Active {
suffix: Path::new("room/cam"),
hops: hops.clone(),
cost: RouteCost::default(),
};
assert_eq!(broadcast_round_trip(&msg, Version::Lite05), msg);
let ended = AnnounceBroadcast::Ended {
suffix: Path::new("room/cam"),
hops: OriginList::new(),
};
assert_eq!(broadcast_round_trip(&ended, Version::Lite05), ended);
}
#[test]
fn announce_broadcast_round_trip_on_lite06() {
let mut hops = OriginList::new();
hops.push(Origin::new(7).unwrap()).unwrap();
let cost = RouteCost(12);
let active = AnnounceBroadcast::Active {
suffix: Path::new("room/cam"),
hops: hops.clone(),
cost,
};
assert_eq!(broadcast_round_trip(&active, Version::Lite06Wip), active);
let ended = AnnounceBroadcast::EndedId { id: 3 };
assert_eq!(broadcast_round_trip(&ended, Version::Lite06Wip), ended);
let restart = AnnounceBroadcast::Restart { id: 3, hops, cost };
assert_eq!(broadcast_round_trip(&restart, Version::Lite06Wip), restart);
}
#[test]
fn announce_broadcast_rejects_cross_version_forms() {
let mut buf = bytes::BytesMut::new();
assert!(matches!(
AnnounceBroadcast::EndedId { id: 1 }.encode(&mut buf, Version::Lite05),
Err(EncodeError::Version)
));
assert!(matches!(
AnnounceBroadcast::Restart {
id: 1,
hops: OriginList::new(),
cost: RouteCost::default()
}
.encode(&mut buf, Version::Lite05),
Err(EncodeError::Version)
));
assert!(matches!(
AnnounceBroadcast::Ended {
suffix: Path::new("room/cam"),
hops: OriginList::new()
}
.encode(&mut buf, Version::Lite06Wip),
Err(EncodeError::Version)
));
}
#[test]
fn route_cost_is_dropped_before_lite06() {
let msg = AnnounceBroadcast::Active {
suffix: Path::new("room/cam"),
hops: OriginList::new(),
cost: RouteCost(9),
};
let got = broadcast_round_trip(&msg, Version::Lite05);
assert_eq!(
got,
AnnounceBroadcast::Active {
suffix: Path::new("room/cam"),
hops: OriginList::new(),
cost: RouteCost::default(),
}
);
}
#[test]
fn route_cost_charge_saturates() {
assert_eq!(RouteCost(4).charged(5), RouteCost(9));
assert_eq!(RouteCost(u64::MAX).charged(10), RouteCost(u64::MAX));
}
#[test]
fn ended_by_id_is_three_bytes() {
let mut buf = bytes::BytesMut::new();
AnnounceBroadcast::EndedId { id: 42 }
.encode(&mut buf, Version::Lite06Wip)
.unwrap();
assert_eq!(buf.len(), 3);
}
#[test]
fn announce_ok_rejects_old_versions() {
let msg = AnnounceOk {
origin: Origin::new(1).unwrap(),
active: 0,
};
let mut buf = bytes::BytesMut::new();
assert!(matches!(
msg.encode(&mut buf, Version::Lite04),
Err(EncodeError::Version)
));
}
#[test]
fn announce_ok_accepts_zero_origin() {
let mut buf = bytes::BytesMut::new();
AnnounceOk {
origin: Origin::new(1).unwrap(),
active: 0,
}
.encode(&mut buf, Version::Lite05)
.unwrap();
let bytes = &buf[..];
let mut patched = bytes.to_vec();
patched[1] = 0x00;
let mut slice = &patched[..];
let got = AnnounceOk::decode(&mut slice, Version::Lite05).unwrap();
assert_eq!(got.origin.id(), 0);
assert_eq!(got.active, 0);
}
}