Skip to main content

rings_core/message/protocols/relay/
mod.rs

1#![deny(missing_docs)]
2
3use serde::Deserialize;
4use serde::Serialize;
5
6use crate::dht::Did;
7use crate::error::Error;
8use crate::error::Result;
9
10/// Policy used when sending a report for a request payload.
11///
12/// The default preserves the legacy path-return behavior. Routed returns are
13/// opt-in and send the report as a fresh Chord-routed payload to the declared
14/// destination.
15#[derive(Deserialize, Serialize, Debug, Clone, Copy, PartialEq, Eq, Default)]
16pub enum ReportReturnPolicy {
17    /// Return the report through the reversed relay path.
18    #[default]
19    Path,
20    /// Route the report normally through Chord to this destination.
21    Routed {
22        /// DID that should receive the report.
23        destination: Did,
24    },
25}
26
27impl ReportReturnPolicy {
28    /// Validate that this policy is authorized by the signed request origin.
29    pub fn validate_authorized_by(&self, signer: Did) -> Result<()> {
30        match self {
31            Self::Path => Ok(()),
32            Self::Routed { destination } if *destination == signer => Ok(()),
33            Self::Routed { destination } => Err(Error::InvalidMessage(format!(
34                "routed report return destination {destination} is not signed by that destination"
35            ))),
36        }
37    }
38}
39
40/// MessageRelay guide message passing on rings network by relay.
41///
42/// All messages should be sent with `MessageRelay`.
43/// By calling `relay` method in correct place, `MessageRelay` help to do things:
44/// - Record the whole transport path for inspection.
45/// - Get the sender of a message.
46#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
47pub struct MessageRelay {
48    /// A push only stack. Record routes when handling messages.
49    pub path: Vec<Did>,
50
51    /// The next node to handle the message.
52    /// A message handler will pick transport by this field.
53    pub next_hop: Did,
54
55    /// The destination of the message.
56    /// It may help the handler to find out `next_hop` in some situations.
57    pub destination: Did,
58}
59
60impl MessageRelay {
61    /// Create a new `MessageRelay`.
62    pub fn new(path: Vec<Did>, next_hop: Did, destination: Did) -> Self {
63        Self {
64            path,
65            next_hop,
66            destination,
67        }
68    }
69
70    /// Validate relay, then create a new `MessageRelay` that have `current` did in the end of path.
71    /// The new relay will use `next_hop` as `next_hop` and `self.destination` as `destination`.
72    pub fn forward(&self, current: Did, next_hop: Did) -> Result<Self> {
73        self.validate(current)?;
74
75        if self.next_hop != current {
76            return Err(Error::InvalidNextHop);
77        }
78
79        let mut path = self.path.clone();
80        path.push(current);
81
82        Ok(Self {
83            path,
84            next_hop,
85            destination: self.destination,
86        })
87    }
88
89    /// Validate relay, then create a new `MessageRelay` that used to report the message.
90    /// The new relay will use `self.path[self.path.len() - 1]` as `next_hop` and `self.sender()` as `destination`.
91    /// In the new relay, the path will be cleared and only have `current` did.
92    pub fn path_report(&self, current: Did) -> Result<Self> {
93        self.validate(current)?;
94
95        if self.path.is_empty() {
96            return Err(Error::CannotInferNextHop);
97        }
98
99        Ok(Self {
100            path: vec![current],
101            next_hop: self.path.last().copied().ok_or(Error::CannotInferNextHop)?,
102            destination: self.try_origin_sender()?,
103        })
104    }
105
106    /// Validate relay, then create a fresh Chord-routed report relay.
107    ///
108    /// The caller must infer `next_hop` from the destination before invoking
109    /// this constructor.
110    pub fn routed_report(&self, current: Did, destination: Did, next_hop: Did) -> Result<Self> {
111        self.validate(current)?;
112
113        Ok(Self {
114            path: vec![current],
115            next_hop,
116            destination,
117        })
118    }
119
120    /// Create a report relay with an explicit return policy.
121    pub fn report(
122        &self,
123        current: Did,
124        policy: ReportReturnPolicy,
125        routed_next_hop: Option<Did>,
126    ) -> Result<Self> {
127        match policy {
128            ReportReturnPolicy::Path => self.path_report(current),
129            ReportReturnPolicy::Routed { destination } => self.routed_report(
130                current,
131                destination,
132                routed_next_hop.ok_or(Error::CannotInferNextHop)?,
133            ),
134        }
135    }
136
137    /// Sometime the sender may not know the destination of the message. They just use next_hop as destination.
138    /// The next node can find a new next_hop, and may use this function to set that next_hop as destination again.
139    pub fn reset_destination(&self, destination: Did) -> Self {
140        let mut relay = self.clone();
141        relay.destination = destination;
142        relay
143    }
144
145    /// Check if path and destination is valid.
146    pub fn validate(&self, current: Did) -> Result<()> {
147        if self.next_hop != current {
148            return Err(Error::InvalidNextHop);
149        }
150
151        // Adjacent elements in self.path cannot be equal
152        if self
153            .path
154            .windows(2)
155            .any(|window| matches!(window, [left, right] if left == right))
156        {
157            return Err(Error::InvalidRelayPath);
158        }
159
160        // Prevent infinite loop
161        if has_infinite_loop(&self.path) {
162            tracing::error!("Infinite path detected {:?}", self.path);
163            return Err(Error::InfiniteRelayPath);
164        }
165
166        Ok(())
167    }
168
169    /// Get the origin sender of current message.
170    /// Should be the first element of path.
171    #[deprecated(note = "please use `origin_sender` instead")]
172    pub fn sender(&self) -> Did {
173        self.origin_sender()
174    }
175
176    /// Get the origin sender of current message as a checked relay-path boundary.
177    pub fn try_origin_sender(&self) -> Result<Did> {
178        self.path.first().copied().ok_or(Error::CannotInferNextHop)
179    }
180
181    /// Get the origin sender of current message.
182    ///
183    /// The origin should be the first element of `path`. Empty relay paths keep
184    /// the legacy fallback to `destination`; callers that must distinguish an
185    /// invalid relay boundary from a real origin should use
186    /// [`try_origin_sender`](Self::try_origin_sender).
187    pub fn origin_sender(&self) -> Did {
188        self.path.first().copied().unwrap_or(self.destination)
189    }
190}
191
192// Since rust cannot zip N iterators, when you change this number,
193// you should also change the code of `has_infinite_loop` below.
194const INFINITE_LOOP_TOLERANCE: usize = 3;
195
196fn has_infinite_loop<T>(path: &[T]) -> bool
197where T: PartialEq {
198    // Invariant: a relay loop is witnessed by a non-empty suffix period P such
199    // that the final path segment is P repeated INFINITE_LOOP_TOLERANCE times.
200    for period in 1..=path.len() / INFINITE_LOOP_TOLERANCE {
201        let repeated_len = period * INFINITE_LOOP_TOLERANCE;
202        let start = path.len() - repeated_len;
203        let Some(suffix) = path.get(start..) else {
204            continue;
205        };
206        let mut chunks = suffix.chunks_exact(period);
207        let Some(first) = chunks.next() else {
208            continue;
209        };
210        if chunks.all(|chunk| chunk == first) {
211            return true;
212        }
213    }
214
215    false
216}
217
218#[cfg(test)]
219mod test_relay;