1
//! Helpers for capability declaration and negotiation.
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
use tor_cell::relaycell::hs::intro_payload::IntroduceHandshakePayload;
8
use tor_protover::Protocols;
9

            
10
use crate::IntroRequestError;
11

            
12
cfg_if! {
13
    if #[cfg(feature = "negotiate-extensions")] {
14
        use std::sync::LazyLock;
15
        use tor_protover::{NamedSubver, named::*};
16

            
17
        /// A set of protocols that we can advertise in our HS descriptor,
18
        /// if we support them.
19
        static ADVERTIZABLE_PROTOCOLS: LazyLock<Protocols> = LazyLock::new(
20
2
           || [RELAY_CRYPT_CGO, RELAY_NEGOTIATE_SUBPROTO].into_iter().collect()
21
        );
22

            
23
        /// An array of protocols that can be requested via a subprotocol
24
        /// request extension.
25
        ///
26
        /// Note that we don't necessarily support all of these ourselves!
27
        static REQUESTABLE_LIST: &[NamedSubver] = &[
28
             RELAY_CRYPT_CGO
29
        ];
30

            
31
        /// A set of protocols that can be requested via a subprotocol request extension.
32
        static REQUESTABLE: LazyLock<Protocols> = LazyLock::new(
33
            || REQUESTABLE_LIST.iter().copied().collect()
34
        );
35

            
36
        /// A set of protocols that we should advertise, if supported,
37
        /// in our `flow-control` line.
38
        static ADVERTISE_FLOWCTRL: LazyLock<Protocols> = LazyLock::new(
39
2
            || [FLOWCTRL_AUTH_SENDME, FLOWCTRL_CC].into_iter().collect()
40
        );
41
    }
42
}
43

            
44
/// Return the list of protocols that we should advertise in our hsdesc.
45
160
pub(crate) fn declared_protocols() -> Protocols {
46
    cfg_if! {
47
        if #[cfg(feature = "negotiate-extensions")] {
48
160
            tor_proto::supported_client_protocols().intersection(&ADVERTIZABLE_PROTOCOLS)
49
        } else {
50
            Protocols::new()
51
        }
52
    }
53
160
}
54

            
55
/// Return the flow control information that we should include in our hsdesc.
56
160
pub(crate) fn declared_flowctrl(
57
160
    params: &tor_netdir::params::NetParameters,
58
160
) -> Option<(Protocols, u8)> {
59
    cfg_if! {
60
        if #[cfg(feature = "negotiate-extensions")] {
61
160
            let supported = tor_proto::supported_client_protocols();
62
240
            supported.supports_named_subver(FLOWCTRL_CC).then(|| {
63
160
                (
64
160
                    supported.intersection(&ADVERTISE_FLOWCTRL),
65
160
                    params.cc_sendme_inc.into()
66
160
                )
67
160
            })
68
        } else {
69
            None
70
        }
71
    }
72
160
}
73

            
74
/// Return a set of capabilities requested by the client in an INTRODUCE2 message payload.
75
///
76
/// The returned set will include capabilities that are...
77
///    - Requested explicitly by using `SUBPROTOCOL_REQUEST`
78
///    - Requested implicitly by using `FLOWCTRL_CC`
79
///
80
/// Returns an error if the client requested any capability that we do not support,
81
/// or which may not be included in a list of requested protocols.
82
#[cfg(feature = "negotiate-extensions")]
83
pub(crate) fn negotiated_capabilities(
84
    intro: &IntroduceHandshakePayload,
85
) -> Result<Protocols, IntroRequestError> {
86
    let mut requested = Vec::new();
87

            
88
    // A list of capabilities (at the tor_proto level!) that we actually support.
89
    //
90
    // (We could possibly stop checking this once the flowctrl-cc and counter-galois-onion features
91
    // are always-on.)
92
    //
93
    // We use this instead of annotating NEGOTIABLE_REQUEST_LIST members with `cfg`,
94
    // because we don't want to add our own set of flowctrl-cc/counter-galois-onion features
95
    // to match tor-proto.
96
    let supported_protocols = tor_proto::supported_client_protocols();
97

            
98
    if intro.cc_request_extension().is_some() {
99
        // The client asked for FLOWCTRL_CC.  If we support it, add it to `requested`.
100
        // Otherwise, reject the request.
101
        if supported_protocols.supports_named_subver(FLOWCTRL_CC) {
102
            requested.push(FLOWCTRL_CC);
103
        } else {
104
            return Err(IntroRequestError::UnsupportedCapability);
105
        }
106
    }
107

            
108
    if let Some(sp) = intro.subprotocol_request_extension() {
109
        if !sp.contains_only(&REQUESTABLE) {
110
            // Some capability was listed that is not supported at all with this extension.
111
            // Reject.
112
            return Err(IntroRequestError::UnsupportedCapability);
113
        }
114
        for cap in REQUESTABLE_LIST {
115
            if sp.contains(*cap) {
116
                if !supported_protocols.supports_named_subver(*cap) {
117
                    // They requested something that we don't support. Reject the request.
118
                    return Err(IntroRequestError::UnsupportedCapability);
119
                }
120
                requested.push(*cap);
121
            }
122
        }
123
    }
124

            
125
    let requested: Protocols = requested.into_iter().collect();
126

            
127
    // We cannot provide CGO unless we also have negotiated CC .
128
    if requested.supports_named_subver(RELAY_CRYPT_CGO)
129
        && !requested.supports_named_subver(FLOWCTRL_CC)
130
    {
131
        return Err(IntroRequestError::UnsupportedCapability);
132
    }
133

            
134
    Ok(requested)
135
}
136

            
137
/// Legacy implementation of negotiated_capabilities.
138
///
139
// TODO: Remove this, and make the entire negotiate-extensions feature always-on.
140
#[cfg(not(feature = "negotiate-extensions"))]
141
pub(crate) fn negotiated_capabilities(
142
    intro: &IntroduceHandshakePayload,
143
) -> Result<Protocols, IntroRequestError> {
144
    // In this case, we "do not recognize" SUBPROTO_REQUEST,
145
    // and so we ignore it.
146

            
147
    Ok(Protocols::new())
148
}