1use std::collections::btree_map::Entry;
2use std::collections::BTreeMap;
3use std::sync::Arc;
4
5use bytes::Bytes;
6use rings_core::dht::Did;
7use rings_core::ecc::elgamal::impls::secp256k1::AeadCiphertext;
8use rings_core::ecc::PublicKey;
9use serde::Deserialize;
10use serde::Serialize;
11
12use super::cell::encode_message;
13use super::cell::OnionCellBucket;
14use super::codec::OnionCircuitInput;
15use super::codec::OnionWireMessage;
16use super::protocol::OnionCircuitCapabilities;
17use super::OnionBackwardFrame;
18use super::OnionCircuitId;
19use super::OnionCircuitPayload;
20use super::OnionClientReturn;
21use super::OnionForwardFrame;
22use super::OnionForwardLayer;
23use super::OnionForwardNonce;
24use super::OnionForwardSequence;
25use super::MAX_ONION_RELAY_CIRCUITS;
26use super::ONION_FORWARD_MAX_VALIDITY_MS;
27use super::ONION_RELAY_RETURN_TTL_MS;
28use crate::error::Error;
29use crate::error::Result;
30use crate::extension::ext::Transition;
31use crate::onion::OnionRouteError;
32
33#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
34pub(super) struct RelayReturnKey {
35 pub(super) circuit_id: OnionCircuitId,
36 pub(super) next_hop: Did,
37}
38
39#[derive(Clone, Copy, Debug, Eq, PartialEq)]
40pub(super) struct RelayReturnEdge {
41 pub(super) key: RelayReturnKey,
42 pub(super) previous_hop: Did,
43 pub(super) previous_circuit_id: OnionCircuitId,
44 pub(super) previous_session_public_key: PublicKey<33>,
45}
46
47#[derive(Clone, Copy, Debug, Eq, PartialEq)]
48struct RelayReturnEntry {
49 previous_hop: Did,
50 previous_circuit_id: OnionCircuitId,
51 previous_session_public_key: PublicKey<33>,
52 expires_at_ms: u128,
53}
54
55#[derive(Clone, Debug, Default, Eq, PartialEq)]
65pub struct OnionCircuitState {
66 relay_returns: Arc<BTreeMap<RelayReturnKey, RelayReturnEntry>>,
67}
68
69impl OnionCircuitState {
70 #[cfg(test)]
71 pub(super) fn relay_return_count(&self) -> usize {
72 self.relay_returns.len()
73 }
74
75 #[cfg(test)]
76 pub(super) fn shares_return_table_with(&self, other: &Self) -> bool {
77 Arc::ptr_eq(&self.relay_returns, &other.relay_returns)
78 }
79}
80
81#[derive(Clone, Debug, Eq, PartialEq)]
83pub enum OnionCircuitEffect {
84 DecryptCell {
86 from: Did,
88 bucket: OnionCellBucket,
90 sealed: AeadCiphertext,
92 },
93 DecryptForward {
95 from: Did,
97 received_at_ms: u128,
99 bucket: OnionCellBucket,
101 circuit_id: OnionCircuitId,
103 payload: AeadCiphertext,
105 },
106 SealAndSend {
108 to: Did,
110 recipient: PublicKey<33>,
112 bucket: OnionCellBucket,
114 encoded_message: Bytes,
116 },
117 Exit {
119 from: Did,
121 circuit_id: OnionCircuitId,
123 return_peer: Did,
125 return_session_public_key: PublicKey<33>,
127 client: OnionClientReturn,
129 forward_nonce: OnionForwardNonce,
131 forward_sequence: OnionForwardSequence,
133 payload: OnionCircuitPayload,
135 },
136 DecryptClient {
138 from: Did,
140 circuit_id: OnionCircuitId,
142 payload: AeadCiphertext,
144 },
145}
146
147#[derive(Clone, Debug)]
161pub(super) struct OnionCircuitReducer {
162 capabilities: OnionCircuitCapabilities,
163}
164
165impl OnionCircuitReducer {
166 pub(super) const fn new(capabilities: OnionCircuitCapabilities) -> Self {
167 Self { capabilities }
168 }
169
170 pub(super) fn apply(
171 &self,
172 state: &OnionCircuitState,
173 input: OnionCircuitInput,
174 ) -> Transition<OnionCircuitState, OnionCircuitEffect> {
175 let mut state = state.clone();
176 let effect = match input {
177 OnionCircuitInput::CellObserved {
178 from,
179 bucket,
180 sealed,
181 } => Ok(Some(OnionCircuitEffect::DecryptCell {
182 from,
183 bucket,
184 sealed,
185 })),
186 OnionCircuitInput::CellReady {
187 from,
188 received_at_ms,
189 bucket,
190 message,
191 } => self.advance_cell(from, received_at_ms, bucket, message, &mut state),
192 OnionCircuitInput::ForwardReady {
193 from,
194 received_at_ms,
195 bucket,
196 circuit_id,
197 layer,
198 } => self
199 .advance_forward(from, received_at_ms, bucket, circuit_id, layer, &mut state)
200 .map(Some),
201 };
202
203 match effect {
204 Ok(Some(effect)) => Transition::with(state, vec![effect]),
205 Ok(None) => Transition::pure(state),
206 Err(error) => {
207 tracing::debug!("drop onion circuit message: {error}");
208 Transition::pure(state)
209 }
210 }
211 }
212
213 fn advance_cell(
214 &self,
215 from: Did,
216 received_at_ms: u128,
217 bucket: OnionCellBucket,
218 message: OnionWireMessage,
219 state: &mut OnionCircuitState,
220 ) -> Result<Option<OnionCircuitEffect>> {
221 match message {
222 OnionWireMessage::Forward(frame) => {
223 if !self.capabilities.accepts_forward_layers() {
224 return Err(Error::NoPermission);
225 }
226 Ok(Some(OnionCircuitEffect::DecryptForward {
227 from,
228 received_at_ms,
229 bucket,
230 circuit_id: frame.circuit_id,
231 payload: frame.layer,
232 }))
233 }
234 OnionWireMessage::Backward(frame) => self
235 .advance_backward(from, received_at_ms, bucket, frame, state)
236 .map(Some),
237 OnionWireMessage::Cover => Ok(None),
238 }
239 }
240
241 fn advance_forward(
242 &self,
243 from: Did,
244 received_at_ms: u128,
245 bucket: OnionCellBucket,
246 circuit_id: OnionCircuitId,
247 layer: OnionForwardLayer,
248 state: &mut OnionCircuitState,
249 ) -> Result<OnionCircuitEffect> {
250 if !self.capabilities.accepts_forward_layers() {
251 return Err(Error::NoPermission);
252 }
253 match layer {
254 OnionForwardLayer::Relay {
255 next_hop,
256 next_circuit_id,
257 next_session_public_key,
258 return_session_public_key,
259 inner,
260 } => {
261 self.validate_relay_forward()?;
262 remember_return_hop(
263 state,
264 MAX_ONION_RELAY_CIRCUITS,
265 ONION_RELAY_RETURN_TTL_MS,
266 RelayReturnEdge {
267 key: RelayReturnKey {
268 circuit_id: next_circuit_id,
269 next_hop,
270 },
271 previous_hop: from,
272 previous_circuit_id: circuit_id,
273 previous_session_public_key: return_session_public_key,
274 },
275 received_at_ms,
276 )?;
277 encode_message(&OnionWireMessage::Forward(OnionForwardFrame {
278 circuit_id: next_circuit_id,
279 layer: inner,
280 }))
281 .map(|encoded_message| OnionCircuitEffect::SealAndSend {
282 to: next_hop,
283 recipient: next_session_public_key,
284 bucket,
285 encoded_message,
286 })
287 }
288 OnionForwardLayer::Exit {
289 client,
290 return_session_public_key,
291 expires_at_ms,
292 forward_nonce,
293 forward_sequence,
294 payload,
295 } => {
296 if !self.capabilities.permits_exit_layer() {
297 return Err(Error::NoPermission);
298 }
299 if expires_at_ms <= received_at_ms
303 || expires_at_ms > received_at_ms.saturating_add(ONION_FORWARD_MAX_VALIDITY_MS)
304 {
305 return Err(Error::OnionRouteError(
306 OnionRouteError::ForwardPayloadExpired,
307 ));
308 }
309 Ok(OnionCircuitEffect::Exit {
310 from,
311 circuit_id,
312 return_peer: from,
313 return_session_public_key,
314 client,
315 forward_nonce,
316 forward_sequence,
317 payload,
318 })
319 }
320 }
321 }
322
323 fn advance_backward(
324 &self,
325 from: Did,
326 received_at_ms: u128,
327 bucket: OnionCellBucket,
328 frame: OnionBackwardFrame,
329 state: &mut OnionCircuitState,
330 ) -> Result<OnionCircuitEffect> {
331 purge_expired_return_hops(state, received_at_ms);
332 let key = RelayReturnKey {
333 circuit_id: frame.circuit_id,
334 next_hop: from,
335 };
336 if let Some(entry) = state.relay_returns.get(&key).copied() {
337 let previous_hop = entry.previous_hop;
338 let previous_circuit_id = entry.previous_circuit_id;
339 let previous_session_public_key = entry.previous_session_public_key;
340 if let Some(entry) = Arc::make_mut(&mut state.relay_returns).get_mut(&key) {
341 entry.expires_at_ms = received_at_ms.saturating_add(ONION_RELAY_RETURN_TTL_MS);
342 }
343 let encoded_message =
344 encode_message(&OnionWireMessage::Backward(OnionBackwardFrame {
345 circuit_id: previous_circuit_id,
346 payload: frame.payload,
347 }))?;
348 return Ok(OnionCircuitEffect::SealAndSend {
349 to: previous_hop,
350 recipient: previous_session_public_key,
351 bucket,
352 encoded_message,
353 });
354 }
355
356 Ok(OnionCircuitEffect::DecryptClient {
357 from,
358 circuit_id: frame.circuit_id,
359 payload: frame.payload,
360 })
361 }
362
363 fn validate_relay_forward(&self) -> Result<()> {
364 if !self.capabilities.permits_relay_layer() {
365 return Err(Error::NoPermission);
366 }
367 Ok(())
371 }
372}
373
374pub(super) fn remember_return_hop(
375 state: &mut OnionCircuitState,
376 max_relay_circuits: usize,
377 ttl_ms: u128,
378 edge: RelayReturnEdge,
379 now_ms: u128,
380) -> Result<()> {
381 let RelayReturnEdge {
382 key,
383 previous_hop,
384 previous_circuit_id,
385 previous_session_public_key,
386 } = edge;
387 purge_expired_return_hops(state, now_ms);
388 let table = Arc::make_mut(&mut state.relay_returns);
389 let table_is_full = table.len() >= max_relay_circuits;
390 let peer_table_is_full = table
391 .values()
392 .filter(|entry| entry.previous_hop == previous_hop)
393 .count()
394 >= max_relay_circuits_per_peer(max_relay_circuits);
395 match table.entry(key) {
396 Entry::Occupied(mut entry) => {
397 if entry.get().previous_hop != previous_hop
398 || entry.get().previous_circuit_id != previous_circuit_id
399 || entry.get().previous_session_public_key != previous_session_public_key
400 {
401 return Err(Error::OnionRouteError(OnionRouteError::ReturnEdgeConflict));
402 }
403 entry.get_mut().expires_at_ms = now_ms.saturating_add(ttl_ms);
404 }
405 Entry::Vacant(entry) => {
406 if table_is_full {
407 return Err(Error::OnionRouteError(OnionRouteError::RelayTableFull));
408 }
409 if peer_table_is_full {
410 return Err(Error::OnionRouteError(OnionRouteError::RelayPeerTableFull));
411 }
412 entry.insert(RelayReturnEntry {
413 previous_hop,
414 previous_circuit_id,
415 previous_session_public_key,
416 expires_at_ms: now_ms.saturating_add(ttl_ms),
417 });
418 }
419 }
420 Ok(())
421}
422
423const fn max_relay_circuits_per_peer(max_relay_circuits: usize) -> usize {
427 max_relay_circuits.div_ceil(16)
428}
429
430fn purge_expired_return_hops(state: &mut OnionCircuitState, now_ms: u128) {
431 if state
432 .relay_returns
433 .values()
434 .any(|entry| entry.expires_at_ms <= now_ms)
435 {
436 Arc::make_mut(&mut state.relay_returns).retain(|_, entry| entry.expires_at_ms > now_ms);
437 }
438}