Skip to main content

rings_node/onion/circuit/
codec.rs

1use bytes::Bytes;
2use rings_core::dht::Did;
3use serde::Deserialize;
4use serde::Serialize;
5
6use super::cell::OnionCellBucket;
7use super::cell::OnionWireCell;
8use super::OnionBackwardFrame;
9use super::OnionCircuitId;
10use super::OnionForwardFrame;
11use super::OnionForwardLayer;
12use crate::error::Error;
13use crate::error::Result;
14use crate::extension::ext::Reject;
15use crate::extension::ext::Wire;
16
17#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
18pub(super) enum OnionWireMessage {
19    Forward(OnionForwardFrame),
20    Backward(OnionBackwardFrame),
21    /// One-hop link padding. It is authenticated to the immediate neighbor and never forwarded.
22    Cover,
23}
24
25#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
26pub(super) enum OnionLocalMessage {
27    CellReady {
28        from: Did,
29        received_at_ms: u128,
30        bucket: OnionCellBucket,
31        message: OnionWireMessage,
32    },
33    ForwardReady {
34        from: Did,
35        received_at_ms: u128,
36        bucket: OnionCellBucket,
37        circuit_id: OnionCircuitId,
38        layer: OnionForwardLayer,
39    },
40}
41
42#[derive(Clone, Debug, Eq, PartialEq)]
43pub(super) enum OnionCircuitInput {
44    CellObserved {
45        from: Did,
46        bucket: OnionCellBucket,
47        sealed: rings_core::ecc::elgamal::impls::secp256k1::AeadCiphertext,
48    },
49    CellReady {
50        from: Did,
51        received_at_ms: u128,
52        bucket: OnionCellBucket,
53        message: OnionWireMessage,
54    },
55    ForwardReady {
56        from: Did,
57        received_at_ms: u128,
58        bucket: OnionCellBucket,
59        circuit_id: OnionCircuitId,
60        layer: OnionForwardLayer,
61    },
62}
63
64/// One typed onion-circuit input accepted by the pure reducer.
65#[derive(Clone, Debug, Eq, PartialEq)]
66pub struct OnionCircuitEvent {
67    pub(super) input: OnionCircuitInput,
68}
69
70pub(super) fn decode_event(wire: Wire<'_>) -> std::result::Result<OnionCircuitEvent, Reject> {
71    if wire.from == wire.me {
72        decode_local_message(wire.payload)
73    } else {
74        decode_wire_message(wire.from, wire.payload)
75    }
76}
77
78fn decode_wire_message(
79    from: Did,
80    payload: &[u8],
81) -> std::result::Result<OnionCircuitEvent, Reject> {
82    const MAX_SERIALIZED_CELL_OVERHEAD: usize = 4 * 1024;
83    const AEAD_TAG_BYTES: usize = 16;
84    let max_wire_len = OnionCellBucket::MiB12
85        .plaintext_len()
86        .saturating_add(MAX_SERIALIZED_CELL_OVERHEAD);
87    if payload.len() > max_wire_len {
88        return Err(Reject(
89            "encrypted onion cell exceeds wire bound".to_string(),
90        ));
91    }
92    let cell = rings_codec::deserialize::<OnionWireCell>(payload)
93        .map_err(|error| Reject(format!("bad encrypted onion cell: {error}")))?;
94    let expected_ciphertext_len = cell
95        .bucket
96        .plaintext_len()
97        .checked_add(AEAD_TAG_BYTES)
98        .ok_or_else(|| Reject("encrypted onion cell length overflow".to_string()))?;
99    if cell.sealed.ciphertext.len() != expected_ciphertext_len {
100        return Err(Reject(
101            "encrypted onion cell does not match its size class".to_string(),
102        ));
103    }
104    Ok(OnionCircuitEvent {
105        input: OnionCircuitInput::CellObserved {
106            from,
107            bucket: cell.bucket,
108            sealed: cell.sealed,
109        },
110    })
111}
112
113fn decode_local_message(payload: &[u8]) -> std::result::Result<OnionCircuitEvent, Reject> {
114    let message = rings_codec::deserialize::<OnionLocalMessage>(payload)
115        .map_err(|error| Reject(format!("bad local onion circuit message: {error}")))?;
116    let input = match message {
117        OnionLocalMessage::CellReady {
118            from,
119            received_at_ms,
120            bucket,
121            message,
122        } => OnionCircuitInput::CellReady {
123            from,
124            received_at_ms,
125            bucket,
126            message,
127        },
128        OnionLocalMessage::ForwardReady {
129            from,
130            received_at_ms,
131            bucket,
132            circuit_id,
133            layer,
134        } => OnionCircuitInput::ForwardReady {
135            from,
136            received_at_ms,
137            bucket,
138            circuit_id,
139            layer,
140        },
141    };
142    Ok(OnionCircuitEvent { input })
143}
144
145pub(super) fn encode_local_message(message: OnionLocalMessage) -> Result<Bytes> {
146    rings_codec::serialize(&message)
147        .map(Bytes::from)
148        .map_err(|_| Error::EncodeError)
149}