Skip to main content

tor_hsclient/
caps.rs

1//! Functionality for negotiating protocol capabilities.
2
3// TODO #2598: Remove duplicate code between tor_hsclient::caps,
4// tor_hsservice::caps, and tor_proto::circuit (to the extent possible).
5
6use cfg_if::cfg_if;
7
8use tor_cell::relaycell::hs::intro_payload::IntroduceHandshakePayload;
9use tor_netdoc::doc::hsdesc::HsDesc;
10use tor_protover::Protocols;
11
12cfg_if! {
13    if #[cfg(feature = "negotiate-extensions")] {
14        use tor_protover::{NamedSubver, NumberedSubver, named::*};
15
16        /// An array of protocols that can be requested via a subprotocol
17        /// request extension.
18        ///
19        /// Note that we don't necessarily support all of these ourselves!
20        static REQUESTABLE_LIST: &[NamedSubver] = &[RELAY_CRYPT_CGO];
21    }
22}
23
24/// A set of peer capabilities, derived from a service's hsdesc.
25#[derive(Clone, Debug)]
26pub(crate) struct PeerCaps {
27    /// A list of the protocol capabilities that we share with the service,
28    /// and are able to negotiate with it.  This includes flowctrl,
29    /// which is negotiated with a different extension than the others.
30    #[cfg(feature = "negotiate-extensions")]
31    shared_protos: Protocols,
32
33    /// A list of the protocols that we intend to request from the service
34    /// via the SUBPROTOCOL_REQUEST extension.
35    #[cfg(feature = "negotiate-extensions")]
36    request_protos: Protocols,
37
38    /// Our chosen cc_sendme_inc value to share with the service.
39    ///
40    /// This is present if and only if `shared_protos` contains `FLOWCTRL_CC`
41    #[cfg(feature = "negotiate-extensions")]
42    sendme_inc: Option<u8>,
43}
44
45#[cfg(feature = "negotiate-extensions")]
46impl PeerCaps {
47    /// Construct a new PeerCaps for a peer, given its descriptor.
48    pub(crate) fn new(desc: &HsDesc) -> Self {
49        let supported = tor_proto::supported_client_protocols();
50
51        let (fc_protos, sendme_inc) = if let Some((fcp, inc)) = desc.flow_control()
52            && fcp.supports_named_subver(FLOWCTRL_CC)
53            && supported.supports_named_subver(FLOWCTRL_CC)
54        {
55            ([FLOWCTRL_CC].into_iter().collect(), Some(inc.into()))
56        } else {
57            (Protocols::new(), None)
58        };
59
60        let mut subproto_request = Vec::new();
61        let mutual_protos = desc.declared_capabilities().intersection(&supported);
62
63        // Note that we can't just do "include CGO if present" since it has prerequisites.
64        if mutual_protos.supports_named_subver(RELAY_CRYPT_CGO)
65            && mutual_protos.supports_named_subver(RELAY_NEGOTIATE_SUBPROTO)
66            && fc_protos.supports_named_subver(FLOWCTRL_CC)
67        {
68            subproto_request.push(RELAY_CRYPT_CGO);
69        }
70
71        let request_protos: Protocols = subproto_request.into_iter().collect();
72
73        let shared_protos = request_protos.union(&fc_protos);
74
75        PeerCaps {
76            shared_protos,
77            request_protos,
78            sendme_inc,
79        }
80    }
81
82    /// Add all relevant request extensions for this PeerCaps into `intro`.
83    pub(crate) fn add_extensions(&self, intro: &mut IntroduceHandshakePayload) {
84        use tor_cell::relaycell::extend::{CcRequest, SubprotocolRequest};
85
86        if self.sendme_inc.is_some() {
87            intro.add_extension(CcRequest::default().into());
88        }
89        if !self.request_protos.is_empty() {
90            let ext: SubprotocolRequest = REQUESTABLE_LIST
91                .iter()
92                .filter(|p| self.request_protos.supports_named_subver(**p))
93                .map(|p| NumberedSubver::from(*p))
94                .collect();
95            intro.add_extension(ext.into());
96        }
97    }
98
99    /// Return a list of the negotiable protocols that we share with the peer.
100    pub(crate) fn shared_protos(&self) -> Protocols {
101        self.shared_protos.clone()
102    }
103
104    /// Return the `sendme_inc` value that we decided to use.
105    ///
106    /// Returns None if we aren't using congestion control.
107    pub(crate) fn cc_sendme_inc(&self) -> Option<u8> {
108        self.sendme_inc
109    }
110}
111
112#[cfg(not(feature = "negotiate-extensions"))]
113impl PeerCaps {
114    /// Construct a new PeerCaps for a peer, given its descriptor.
115    pub(crate) fn new(_desc: &HsDesc) -> Self {
116        Self {}
117    }
118
119    /// Add all relevant request extensions for this PeerCaps into `intro`.
120    pub(crate) fn add_extensions(&self, _intro: &mut IntroduceHandshakePayload) {}
121
122    /// Return a list of the negotiable protocols that we share with the peer.
123    pub(crate) fn shared_protos(&self) -> Protocols {
124        // Note that we actually share all the must-implement client protocols.
125        // But we cannot assume that here; some protocols must be negotiated.
126        Protocols::new()
127    }
128
129    /// Return the `sendme_inc` value that we decided to use.
130    ///
131    /// Returns None if we aren't using congestion control.
132    pub(crate) fn cc_sendme_inc(&self) -> Option<u8> {
133        None
134    }
135}