use bytes::Buf;
use crate::coding::{Decode, DecodeError, Encode, EncodeError};
use crate::{Origin, OriginList};
use super::{Param, Version};
pub const RELAY_HOPS: u64 = 0x40B55;
pub const RELAY_COST: u64 = 0x40B56;
pub const HOP_PATH: u64 = 0x40B57;
pub const ROUTE_COST: u64 = 0x40B58;
pub const DEFAULT_COST: u64 = 1;
pub fn supported(version: Version) -> bool {
!matches!(version, Version::Draft14 | Version::Draft15 | Version::Draft16)
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct HopPath(OriginList);
impl HopPath {
pub fn new(hops: OriginList) -> Self {
Self(hops)
}
pub fn hops(&self) -> &OriginList {
&self.0
}
fn validate(&self) -> Result<(), DecodeError> {
if self.0.is_empty() {
return Err(DecodeError::InvalidValue);
}
let hops = self.0.as_slice();
for (i, hop) in hops.iter().enumerate() {
if *hop == Origin::UNKNOWN {
continue;
}
if hops[i + 1..].contains(hop) {
return Err(DecodeError::InvalidValue);
}
}
Ok(())
}
}
impl Param for HopPath {
fn param_encode<W: bytes::BufMut>(&self, w: &mut W, version: Version) -> Result<(), EncodeError> {
let mut buf = Vec::new();
for hop in &self.0 {
hop.encode(&mut buf, version)?;
}
buf.encode(w, version)
}
fn param_decode<R: Buf>(r: &mut R, version: Version) -> Result<Self, DecodeError> {
let value = Vec::<u8>::decode(r, version)?;
let mut buf = bytes::Bytes::from(value);
let mut hops = OriginList::new();
while buf.has_remaining() {
hops.push(Origin::decode(&mut buf, version)?)?;
}
let path = Self(hops);
path.validate()?;
Ok(path)
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Advert {
pub hops: HopPath,
pub cost: u64,
}
impl Advert {
pub fn forward(hops: &OriginList, cost: u64, self_origin: Origin) -> Result<Self, crate::TooManyOrigins> {
let mut hops = hops.clone();
hops.push(self_origin)?;
Ok(Self {
hops: HopPath::new(hops),
cost,
})
}
pub fn loops(&self, self_origin: Origin) -> bool {
self_origin != Origin::UNKNOWN && self.hops.hops().contains(&self_origin)
}
pub fn route(&self, link_cost: u64) -> crate::broadcast::Route {
let mut route = crate::broadcast::Route::new()
.with_hops(self.hops.hops().clone())
.with_cost(self.cost.saturating_add(link_cost))
.with_announce(true);
route.advertised = self.cost;
route
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Peer {
pub origin: Option<Origin>,
pub cost: Option<u64>,
}
impl Peer {
pub fn negotiated(&self) -> bool {
self.origin.is_some()
}
pub fn exclude(&self) -> Origin {
self.origin.unwrap_or(Origin::UNKNOWN)
}
}
pub fn link_cost(local: Option<u64>, peer: &Peer) -> u64 {
local.or(peer.cost).unwrap_or(DEFAULT_COST)
}
#[derive(Clone, Default)]
pub(crate) struct PeerSetup(kio::Shared<Option<Peer>>);
impl PeerSetup {
pub fn set(&self, peer: Peer) {
let mut slot = self.0.lock();
if slot.is_none() {
*slot = Some(peer);
}
}
pub async fn get(&self) -> Peer {
let slot = self
.0
.wait(|peer| match peer.is_some() {
true => std::task::Poll::Ready(()),
false => std::task::Poll::Pending,
})
.await;
(*slot).expect("waited for Some")
}
}
pub fn peer_from_setup(params: &super::Parameters, version: Version) -> Result<Peer, DecodeError> {
if !supported(version) {
return Ok(Peer::default());
}
let origin = match params.get_bytes(super::ParameterBytes::RelayHops) {
Some(mut bytes) => {
let origin = Origin::decode(&mut bytes, version)?;
if bytes.has_remaining() {
return Err(DecodeError::TrailingBytes);
}
Some(origin)
}
None => None,
};
Ok(Peer {
origin,
cost: params.get_varint(super::ParameterVarInt::RelayCost),
})
}
pub fn peer_into_setup(params: &mut super::Parameters, self_origin: Origin, cost: Option<u64>, version: Version) {
if !supported(version) {
return;
}
let mut id = Vec::new();
self_origin
.encode(&mut id, version)
.expect("a varint always fits a Vec");
params.set_bytes(super::ParameterBytes::RelayHops, id);
if let Some(cost) = cost {
params.set_varint(super::ParameterVarInt::RelayCost, cost);
}
}
#[cfg(test)]
mod tests {
use super::*;
use bytes::BytesMut;
const VERSION: Version = Version::Draft19;
fn origin(id: u64) -> Origin {
Origin::new(id).unwrap()
}
fn hop_path(ids: &[u64]) -> HopPath {
let hops = ids
.iter()
.map(|&id| match id {
0 => Origin::UNKNOWN,
id => origin(id),
})
.collect::<Vec<_>>();
HopPath::new(OriginList::try_from(hops).unwrap())
}
fn round_trip(path: &HopPath) -> Result<HopPath, DecodeError> {
let mut buf = BytesMut::new();
path.param_encode(&mut buf, VERSION).unwrap();
let mut bytes = buf.freeze();
let decoded = HopPath::param_decode(&mut bytes, VERSION)?;
assert!(!bytes.has_remaining(), "trailing bytes after decode");
Ok(decoded)
}
#[test]
fn code_points() {
assert_eq!(RELAY_HOPS, 0x40B55);
assert_eq!(RELAY_COST, 0x40B56);
assert_eq!(HOP_PATH, 0x40B57);
assert_eq!(ROUTE_COST, 0x40B58);
assert_eq!(RELAY_HOPS % 2, 1);
assert_eq!(RELAY_COST % 2, 0);
assert_eq!(HOP_PATH % 2, 1);
assert_eq!(ROUTE_COST % 2, 0);
}
#[test]
fn hop_path_round_trips() {
let path = hop_path(&[7, 9, 11]);
assert_eq!(round_trip(&path).unwrap(), path);
}
#[test]
fn hop_path_has_no_inner_count() {
let mut buf = BytesMut::new();
hop_path(&[1, 2, 3]).param_encode(&mut buf, VERSION).unwrap();
assert_eq!(buf.to_vec(), vec![0x03, 0x01, 0x02, 0x03]);
}
#[test]
fn hop_path_allows_repeated_zero() {
let path = hop_path(&[0, 5, 0]);
assert_eq!(round_trip(&path).unwrap(), path);
}
#[test]
fn hop_path_rejects_repeated_hop() {
let mut buf = BytesMut::new();
hop_path(&[4, 8, 4]).param_encode(&mut buf, VERSION).unwrap();
let mut bytes = buf.freeze();
assert!(matches!(
HopPath::param_decode(&mut bytes, VERSION),
Err(DecodeError::InvalidValue)
));
}
#[test]
fn hop_path_rejects_empty() {
let mut buf = BytesMut::new();
HopPath::default().param_encode(&mut buf, VERSION).unwrap();
let mut bytes = buf.freeze();
assert!(matches!(
HopPath::param_decode(&mut bytes, VERSION),
Err(DecodeError::InvalidValue)
));
}
#[test]
fn hop_path_rejects_partial_entry() {
let mut value = Vec::new();
origin(300).encode(&mut value, VERSION).unwrap();
assert!(value.len() > 1, "300 should not fit in one byte");
value.pop();
let mut buf = BytesMut::new();
value.encode(&mut buf, VERSION).unwrap();
let mut bytes = buf.freeze();
assert!(HopPath::param_decode(&mut bytes, VERSION).is_err());
}
#[test]
fn forward_appends_self() {
let mut hops = OriginList::new();
hops.push(origin(1)).unwrap();
hops.push(origin(2)).unwrap();
let advert = Advert::forward(&hops, 5, origin(3)).unwrap();
assert_eq!(advert.hops, hop_path(&[1, 2, 3]));
assert_eq!(advert.hops.hops().iter().next().copied(), Some(origin(1)));
assert_eq!(advert.cost, 5);
}
#[test]
fn loops_ignores_unknown() {
let advert = Advert {
hops: hop_path(&[0, 5]),
cost: 0,
};
assert!(!advert.loops(Origin::UNKNOWN));
assert!(advert.loops(origin(5)));
assert!(!advert.loops(origin(6)));
}
#[test]
fn route_charges_the_link_and_saturates() {
let advert = Advert {
hops: hop_path(&[1, 2]),
cost: 4,
};
let route = advert.route(3);
assert_eq!(route.cost, 7);
assert_eq!(&route.hops, hop_path(&[1, 2]).hops());
assert!(route.announce);
let absurd = Advert {
hops: hop_path(&[1]),
cost: u64::MAX,
};
assert_eq!(absurd.route(10).cost, u64::MAX);
}
#[test]
fn peer_defaults() {
let peer = Peer::default();
assert!(!peer.negotiated());
assert_eq!(peer.exclude(), Origin::UNKNOWN);
let anonymous = Peer {
origin: Some(Origin::UNKNOWN),
cost: Some(0),
};
assert!(anonymous.negotiated());
assert_eq!(anonymous.exclude(), Origin::UNKNOWN);
}
#[test]
fn link_cost_prefers_local_policy_over_the_peer() {
let unpriced = Peer::default();
let priced = Peer {
origin: Some(origin(9)),
cost: Some(7),
};
let free = Peer {
origin: Some(origin(9)),
cost: Some(0),
};
assert_eq!(link_cost(None, &priced), 7);
assert_eq!(link_cost(None, &free), 0);
assert_eq!(link_cost(Some(3), &priced), 3);
assert_eq!(link_cost(Some(0), &priced), 0);
assert_eq!(link_cost(None, &unpriced), DEFAULT_COST);
}
#[test]
fn setup_options_round_trip() {
for (self_origin, cost) in [(origin(42), Some(0)), (origin(42), Some(9)), (Origin::UNKNOWN, None)] {
let mut params = super::super::Parameters::default();
peer_into_setup(&mut params, self_origin, cost, VERSION);
let mut buf = BytesMut::new();
params.encode(&mut buf, VERSION).unwrap();
let mut bytes = buf.freeze();
let decoded = super::super::Parameters::decode(&mut bytes, VERSION).unwrap();
let peer = peer_from_setup(&decoded, VERSION).unwrap();
assert_eq!(peer.origin, Some(self_origin));
assert_eq!(peer.cost, cost);
}
}
#[tokio::test]
async fn peer_setup_first_write_wins() {
let slot = PeerSetup::default();
slot.set(Peer {
origin: Some(origin(42)),
cost: Some(3),
});
slot.set(Peer {
origin: Some(origin(99)),
cost: Some(0),
});
let peer = slot.get().await;
assert_eq!(peer.origin, Some(origin(42)));
assert_eq!(peer.cost, Some(3));
}
#[test]
fn setup_without_relay_hops_is_not_negotiated() {
let params = super::super::Parameters::default();
let peer = peer_from_setup(¶ms, VERSION).unwrap();
assert!(!peer.negotiated());
}
#[test]
fn setup_options_skipped_before_draft17() {
let mut params = super::super::Parameters::default();
peer_into_setup(&mut params, origin(42), Some(3), Version::Draft16);
assert!(params.get_bytes(super::super::ParameterBytes::RelayHops).is_none());
assert!(!peer_from_setup(¶ms, Version::Draft16).unwrap().negotiated());
}
}