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

            
6
use cfg_if::cfg_if;
7

            
8
use tor_cell::relaycell::hs::intro_payload::IntroduceHandshakePayload;
9
use tor_netdoc::doc::hsdesc::HsDesc;
10
use tor_protover::Protocols;
11

            
12
cfg_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)]
26
pub(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")]
46
impl PeerCaps {
47
    /// Construct a new PeerCaps for a peer, given its descriptor.
48
170
    pub(crate) fn new(desc: &HsDesc) -> Self {
49
170
        let supported = tor_proto::supported_client_protocols();
50

            
51
170
        let (fc_protos, sendme_inc) = if let Some((fcp, inc)) = desc.flow_control()
52
170
            && fcp.supports_named_subver(FLOWCTRL_CC)
53
170
            && supported.supports_named_subver(FLOWCTRL_CC)
54
        {
55
170
            ([FLOWCTRL_CC].into_iter().collect(), Some(inc.into()))
56
        } else {
57
            (Protocols::new(), None)
58
        };
59

            
60
170
        let mut subproto_request = Vec::new();
61
170
        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
170
        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
170
        }
70

            
71
170
        let request_protos: Protocols = subproto_request.into_iter().collect();
72

            
73
170
        let shared_protos = request_protos.union(&fc_protos);
74

            
75
170
        PeerCaps {
76
170
            shared_protos,
77
170
            request_protos,
78
170
            sendme_inc,
79
170
        }
80
170
    }
81

            
82
    /// Add all relevant request extensions for this PeerCaps into `intro`.
83
170
    pub(crate) fn add_extensions(&self, intro: &mut IntroduceHandshakePayload) {
84
        use tor_cell::relaycell::extend::{CcRequest, SubprotocolRequest};
85

            
86
170
        if self.sendme_inc.is_some() {
87
170
            intro.add_extension(CcRequest::default().into());
88
170
        }
89
170
        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
170
        }
97
170
    }
98

            
99
    /// Return a list of the negotiable protocols that we share with the peer.
100
4
    pub(crate) fn shared_protos(&self) -> Protocols {
101
4
        self.shared_protos.clone()
102
4
    }
103

            
104
    /// Return the `sendme_inc` value that we decided to use.
105
    ///
106
    /// Returns None if we aren't using congestion control.
107
4
    pub(crate) fn cc_sendme_inc(&self) -> Option<u8> {
108
4
        self.sendme_inc
109
4
    }
110
}
111

            
112
#[cfg(not(feature = "negotiate-extensions"))]
113
impl 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
}