#![deny(missing_docs)]
use serde::Deserialize;
use serde::Serialize;
use crate::dht::Did;
use crate::error::Error;
use crate::error::Result;
#[derive(Deserialize, Serialize, Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ReportReturnPolicy {
#[default]
Path,
Routed {
destination: Did,
},
}
impl ReportReturnPolicy {
pub fn validate_authorized_by(&self, signer: Did) -> Result<()> {
match self {
Self::Path => Ok(()),
Self::Routed { destination } if *destination == signer => Ok(()),
Self::Routed { destination } => Err(Error::InvalidMessage(format!(
"routed report return destination {destination} is not signed by that destination"
))),
}
}
}
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
pub struct MessageRelay {
pub path: Vec<Did>,
pub next_hop: Did,
pub destination: Did,
}
impl MessageRelay {
pub fn new(path: Vec<Did>, next_hop: Did, destination: Did) -> Self {
Self {
path,
next_hop,
destination,
}
}
pub fn forward(&self, current: Did, next_hop: Did) -> Result<Self> {
self.validate(current)?;
if self.next_hop != current {
return Err(Error::InvalidNextHop);
}
let mut path = self.path.clone();
path.push(current);
Ok(Self {
path,
next_hop,
destination: self.destination,
})
}
pub fn path_report(&self, current: Did) -> Result<Self> {
self.validate(current)?;
if self.path.is_empty() {
return Err(Error::CannotInferNextHop);
}
Ok(Self {
path: vec![current],
next_hop: self.path.last().copied().ok_or(Error::CannotInferNextHop)?,
destination: self.try_origin_sender()?,
})
}
pub fn routed_report(&self, current: Did, destination: Did, next_hop: Did) -> Result<Self> {
self.validate(current)?;
Ok(Self {
path: vec![current],
next_hop,
destination,
})
}
pub fn report(
&self,
current: Did,
policy: ReportReturnPolicy,
routed_next_hop: Option<Did>,
) -> Result<Self> {
match policy {
ReportReturnPolicy::Path => self.path_report(current),
ReportReturnPolicy::Routed { destination } => self.routed_report(
current,
destination,
routed_next_hop.ok_or(Error::CannotInferNextHop)?,
),
}
}
pub fn reset_destination(&self, destination: Did) -> Self {
let mut relay = self.clone();
relay.destination = destination;
relay
}
pub fn validate(&self, current: Did) -> Result<()> {
if self.next_hop != current {
return Err(Error::InvalidNextHop);
}
if self
.path
.windows(2)
.any(|window| matches!(window, [left, right] if left == right))
{
return Err(Error::InvalidRelayPath);
}
if has_infinite_loop(&self.path) {
tracing::error!("Infinite path detected {:?}", self.path);
return Err(Error::InfiniteRelayPath);
}
Ok(())
}
#[deprecated(note = "please use `origin_sender` instead")]
pub fn sender(&self) -> Did {
self.origin_sender()
}
pub fn try_origin_sender(&self) -> Result<Did> {
self.path.first().copied().ok_or(Error::CannotInferNextHop)
}
pub fn origin_sender(&self) -> Did {
self.path.first().copied().unwrap_or(self.destination)
}
}
const INFINITE_LOOP_TOLERANCE: usize = 3;
fn has_infinite_loop<T>(path: &[T]) -> bool
where T: PartialEq {
for period in 1..=path.len() / INFINITE_LOOP_TOLERANCE {
let repeated_len = period * INFINITE_LOOP_TOLERANCE;
let start = path.len() - repeated_len;
let Some(suffix) = path.get(start..) else {
continue;
};
let mut chunks = suffix.chunks_exact(period);
let Some(first) = chunks.next() else {
continue;
};
if chunks.all(|chunk| chunk == first) {
return true;
}
}
false
}
#[cfg(test)]
mod test_relay;