Skip to main content

tor_linkspec/
owned.rs

1//! Owned variants of [`ChanTarget`] and [`CircTarget`].
2
3use safelog::Redactable;
4use serde::{Deserialize, Serialize};
5use std::fmt::{self, Display};
6use std::net::SocketAddr;
7use tor_config::impl_standard_builder;
8use tor_llcrypto::pk;
9
10use crate::{
11    ChanTarget, ChannelMethod, CircTarget, HasAddrs, HasChanMethod, HasRelayIds, RelayIdRef,
12    RelayIdType,
13};
14
15/// RelayIds is an owned copy of the set of known identities of a relay.
16///
17/// Note that an object of this type will not necessarily have every type of
18/// identity: it's possible that we don't know all the identities, or that one
19/// of the identity types has become optional.
20#[derive(
21    Debug,
22    Clone,
23    Eq,
24    PartialEq,
25    Hash,
26    PartialOrd,
27    Ord,
28    Serialize,
29    Deserialize,
30    derive_builder::Builder,
31)]
32#[builder(derive(Debug))]
33pub struct RelayIds {
34    /// Copy of the ed25519 id from the underlying ChanTarget.
35    //
36    // NOTE: we added serde(default) for ed25519 and rsa around April 2026.
37    // Do not assume that they are present in older versions!
38    #[serde(rename = "ed25519", default)]
39    #[builder(default, setter(strip_option))]
40    ed_identity: Option<pk::ed25519::Ed25519Identity>,
41
42    /// Copy of the rsa id from the underlying ChanTarget.
43    #[serde(rename = "rsa", default)]
44    #[builder(default, setter(strip_option))]
45    rsa_identity: Option<pk::rsa::RsaIdentity>,
46}
47impl_standard_builder! { RelayIds : !Deserialize + !Builder + !Default }
48
49impl HasRelayIds for RelayIds {
50    fn identity(&self, key_type: RelayIdType) -> Option<crate::RelayIdRef<'_>> {
51        match key_type {
52            RelayIdType::Ed25519 => self.ed_identity.as_ref().map(RelayIdRef::from),
53            RelayIdType::Rsa => self.rsa_identity.as_ref().map(RelayIdRef::from),
54        }
55    }
56}
57
58impl RelayIds {
59    /// Return an empty set of identities.
60    ///
61    /// This is _not_ a `Default` method, since this is not what you should
62    /// usually construct.
63    pub const fn empty() -> Self {
64        Self {
65            ed_identity: None,
66            rsa_identity: None,
67        }
68    }
69
70    /// Construct a new `RelayIds` object from another object that implements
71    /// [`HasRelayIds`].
72    ///
73    /// Note that it is possible to construct an _empty_ `RelayIds` object if
74    /// the input does not contain any recognized identity type.
75    pub fn from_relay_ids<T: HasRelayIds + ?Sized>(other: &T) -> Self {
76        Self {
77            ed_identity: other
78                .identity(RelayIdType::Ed25519)
79                .map(|r| *r.unwrap_ed25519()),
80            rsa_identity: other.identity(RelayIdType::Rsa).map(|r| *r.unwrap_rsa()),
81        }
82    }
83}
84
85impl std::fmt::Display for RelayIds {
86    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
87        write!(f, "{}", self.display_relay_ids())
88    }
89}
90impl Redactable for RelayIds {
91    fn display_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
92        write!(f, "{}", self.display_relay_ids().redacted())
93    }
94}
95
96impl RelayIdsBuilder {
97    /// Construct a new `RelayIdsBuilder` object from an object implementing
98    /// [`HasRelayIds`].
99    ///
100    /// Note that it is possible to construct an _empty_ `RelayIds` object if
101    /// the input does not contain any recognized identity type.
102    pub fn from_relay_ids<T: HasRelayIds + ?Sized>(ids: &T) -> Self {
103        let mut builder = Self::default();
104        if let Some(ed_id) = ids.ed_identity() {
105            builder.ed_identity(*ed_id);
106        }
107        if let Some(rsa_id) = ids.rsa_identity() {
108            builder.rsa_identity(*rsa_id);
109        }
110        builder
111    }
112}
113
114/// OwnedChanTarget is a summary of a [`ChanTarget`] that owns all of its
115/// members.
116#[derive(Debug, Clone, derive_builder::Builder)]
117#[builder(derive(Debug))]
118pub struct OwnedChanTarget {
119    /// Copy of the addresses from the underlying ChanTarget.
120    #[builder(default)]
121    addrs: Vec<SocketAddr>,
122    /// Copy of the channel methods from the underlying ChanTarget.
123    //
124    // TODO: in many cases this will be redundant with addrs; if we allocate a
125    // lot of these objects, we might want to handle that.
126    #[builder(default = "self.make_method()")]
127    method: ChannelMethod,
128    /// Identities that this relay provides.
129    #[builder(sub_builder)]
130    ids: RelayIds,
131}
132impl_standard_builder! { OwnedChanTarget : !Deserialize + !Builder + !Default }
133
134impl OwnedChanTargetBuilder {
135    /// Set the ed25519 identity in this builder to `id`.
136    pub fn ed_identity(&mut self, id: pk::ed25519::Ed25519Identity) -> &mut Self {
137        self.ids().ed_identity(id);
138        self
139    }
140
141    /// Set the RSA identity in this builder to `id`.
142    pub fn rsa_identity(&mut self, id: pk::rsa::RsaIdentity) -> &mut Self {
143        self.ids().rsa_identity(id);
144        self
145    }
146
147    /// Helper: make a channel method if none was specified.
148    fn make_method(&self) -> ChannelMethod {
149        ChannelMethod::Direct(self.addrs.clone().unwrap_or_default())
150    }
151}
152
153impl HasAddrs for OwnedChanTarget {
154    fn addrs(&self) -> impl Iterator<Item = SocketAddr> {
155        self.addrs.iter().copied()
156    }
157}
158
159impl HasChanMethod for OwnedChanTarget {
160    fn chan_method(&self) -> ChannelMethod {
161        self.method.clone()
162    }
163}
164
165impl HasRelayIds for OwnedChanTarget {
166    fn identity(&self, key_type: RelayIdType) -> Option<RelayIdRef<'_>> {
167        self.ids.identity(key_type)
168    }
169}
170
171impl ChanTarget for OwnedChanTarget {}
172
173impl OwnedChanTarget {
174    /// Construct a OwnedChanTarget from a given ChanTarget.
175    pub fn from_chan_target<C>(target: &C) -> Self
176    where
177        C: ChanTarget + ?Sized,
178    {
179        OwnedChanTarget {
180            addrs: target.addrs().collect(),
181            method: target.chan_method(),
182            ids: RelayIds::from_relay_ids(target),
183        }
184    }
185
186    /// Return a mutable reference to this [`OwnedChanTarget`]'s [`ChannelMethod`]
187    ///
188    pub fn chan_method_mut(&mut self) -> &mut ChannelMethod {
189        &mut self.method
190    }
191}
192
193/// Primarily for error reporting and logging
194impl Display for OwnedChanTarget {
195    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
196        write!(f, "{}", self.display_chan_target())
197    }
198}
199
200impl Redactable for OwnedChanTarget {
201    fn display_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
202        self.display_chan_target().display_redacted(f)
203    }
204
205    fn debug_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
206        self.display_chan_target().debug_redacted(f)
207    }
208}
209
210/// OwnedCircTarget is a summary of a [`CircTarget`] that owns all its
211/// members.
212#[derive(Debug, Clone, derive_builder::Builder)]
213#[builder(derive(Debug))]
214pub struct OwnedCircTarget {
215    /// The fields from this object when considered as a ChanTarget.
216    #[builder(sub_builder)]
217    chan_target: OwnedChanTarget,
218    /// The ntor key to use when extending to this CircTarget
219    ntor_onion_key: pk::curve25519::PublicKey,
220    /// The subprotocol versions that this CircTarget supports.
221    protocols: tor_protover::Protocols,
222}
223impl_standard_builder! { OwnedCircTarget : !Deserialize + !Builder + !Default }
224
225impl OwnedCircTarget {
226    /// Construct an OwnedCircTarget from a given CircTarget.
227    pub fn from_circ_target<C>(target: &C) -> Self
228    where
229        C: CircTarget + ?Sized,
230    {
231        OwnedCircTarget {
232            chan_target: OwnedChanTarget::from_chan_target(target),
233            ntor_onion_key: *target.ntor_onion_key(),
234            protocols: target.protovers().clone(),
235        }
236    }
237
238    /// Return a mutable view of this OwnedCircTarget as an [`OwnedChanTarget`].
239    pub fn chan_target_mut(&mut self) -> &mut OwnedChanTarget {
240        &mut self.chan_target
241    }
242
243    /// Return a  view of this OwnedCircTarget as an [`OwnedChanTarget`].
244    pub fn chan_target(&self) -> &OwnedChanTarget {
245        &self.chan_target
246    }
247}
248
249impl HasAddrs for OwnedCircTarget {
250    fn addrs(&self) -> impl Iterator<Item = SocketAddr> {
251        self.chan_target.addrs()
252    }
253}
254
255impl HasRelayIds for OwnedCircTarget {
256    fn identity(&self, key_type: RelayIdType) -> Option<RelayIdRef<'_>> {
257        self.chan_target.identity(key_type)
258    }
259}
260impl HasChanMethod for OwnedCircTarget {
261    fn chan_method(&self) -> ChannelMethod {
262        self.chan_target.chan_method()
263    }
264}
265
266impl ChanTarget for OwnedCircTarget {}
267
268impl CircTarget for OwnedCircTarget {
269    fn ntor_onion_key(&self) -> &pk::curve25519::PublicKey {
270        &self.ntor_onion_key
271    }
272    fn protovers(&self) -> &tor_protover::Protocols {
273        &self.protocols
274    }
275}
276
277/// A value that can be converted into an OwnedChanTarget.
278pub trait IntoOwnedChanTarget {
279    /// Convert this value into an [`OwnedChanTarget`].
280    fn to_owned(self) -> OwnedChanTarget;
281
282    /// Convert this value into an [`LoggedChanTarget`].
283    fn to_logged(self) -> LoggedChanTarget
284    where
285        Self: Sized,
286    {
287        self.to_owned().into()
288    }
289}
290
291impl<'a, T: ChanTarget + ?Sized> IntoOwnedChanTarget for &'a T {
292    fn to_owned(self) -> OwnedChanTarget {
293        OwnedChanTarget::from_chan_target(self)
294    }
295}
296
297impl IntoOwnedChanTarget for OwnedChanTarget {
298    fn to_owned(self) -> OwnedChanTarget {
299        self
300    }
301}
302
303/// An `OwnedChanTarget` suitable for logging and including in errors
304pub type LoggedChanTarget = safelog::BoxSensitive<OwnedChanTarget>;
305
306#[cfg(test)]
307mod test {
308    // @@ begin test lint list maintained by maint/add_warning @@
309    #![allow(clippy::bool_assert_comparison)]
310    #![allow(clippy::clone_on_copy)]
311    #![allow(clippy::dbg_macro)]
312    #![allow(clippy::mixed_attributes_style)]
313    #![allow(clippy::print_stderr)]
314    #![allow(clippy::print_stdout)]
315    #![allow(clippy::single_char_pattern)]
316    #![allow(clippy::unwrap_used)]
317    #![allow(clippy::unchecked_time_subtraction)]
318    #![allow(clippy::useless_vec)]
319    #![allow(clippy::needless_pass_by_value)]
320    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
321    use super::*;
322    use itertools::Itertools;
323
324    #[test]
325    #[allow(clippy::redundant_clone)]
326    fn chan_target() {
327        let ti = OwnedChanTarget::builder()
328            .addrs(vec!["127.0.0.1:11".parse().unwrap()])
329            .ed_identity([42; 32].into())
330            .rsa_identity([45; 20].into())
331            .build()
332            .unwrap();
333
334        let ti2 = OwnedChanTarget::from_chan_target(&ti);
335        assert_eq!(ti.addrs().collect_vec(), ti2.addrs().collect_vec());
336        assert!(ti.same_relay_ids(&ti2));
337
338        assert_eq!(format!("{:?}", ti), format!("{:?}", ti2));
339        assert_eq!(format!("{:?}", ti), format!("{:?}", ti.clone()));
340    }
341
342    #[test]
343    #[allow(clippy::redundant_clone)]
344    fn circ_target() {
345        let mut builder = OwnedCircTarget::builder();
346        builder
347            .chan_target()
348            .addrs(vec!["127.0.0.1:11".parse().unwrap()])
349            .ed_identity([42; 32].into())
350            .rsa_identity([45; 20].into());
351        let ct = builder
352            .ntor_onion_key([99; 32].into())
353            .protocols("FlowCtrl=7".parse().unwrap())
354            .build()
355            .unwrap();
356        let ch = ct.chan_target.clone();
357
358        assert_eq!(ct.addrs().collect_vec(), ch.addrs().collect_vec());
359        assert!(ct.same_relay_ids(&ch));
360        assert_eq!(ct.ntor_onion_key().as_bytes(), &[99; 32]);
361        assert_eq!(&ct.protovers().to_string(), "FlowCtrl=7");
362        let ct2 = OwnedCircTarget::from_circ_target(&ct);
363        assert_eq!(format!("{:?}", ct), format!("{:?}", ct2));
364        assert_eq!(format!("{:?}", ct), format!("{:?}", ct.clone()));
365    }
366
367    #[test]
368    fn format_relay_ids() {
369        let mut builder = RelayIds::builder();
370        builder
371            .ed_identity([42; 32].into())
372            .rsa_identity([45; 20].into());
373        let ids = builder.build().unwrap();
374        assert_eq!(
375            format!("{}", ids),
376            "ed25519:KioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKio $2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d"
377        );
378        assert_eq!(format!("{}", ids.redacted()), "ed25519:Ki…");
379    }
380}