1
//! Message types used in the Arti's RPC protocol.
2
//
3
// TODO: This could become a more zero-copy-friendly with some effort, but it's
4
// not really sure if it's needed.
5

            
6
mod invalid;
7
use serde::{Deserialize, Serialize};
8
use tor_rpcbase as rpc;
9

            
10
/// An identifier for a Request within the context of a Session.
11
///
12
/// Multiple inflight requests can share the same `RequestId`,
13
/// but doing so may make Arti's responses ambiguous.
14
#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize)]
15
#[serde(untagged)]
16
pub(crate) enum RequestId {
17
    /// A client-provided string.
18
    //
19
    // (We use Box<str> to save a word here, since these don't have to be
20
    // mutable ever.)
21
    Str(Box<str>),
22
    /// A client-provided integer.
23
    ///
24
    /// [I-JSON] says that we don't have to handle any integer that can't be
25
    /// represented as an `f64`, but we do anyway.  This won't confuse clients,
26
    /// since we won't send them any integer that they didn't send us first.
27
    ///
28
    /// [I-JSON]: https://www.rfc-editor.org/rfc/rfc7493
29
    Int(i64),
30
}
31

            
32
/// Metadata associated with a single Request.
33
//
34
// NOTE: When adding new fields to this type, make sure that `Default` gives
35
// the correct value for an absent metadata.
36
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
37
pub(crate) struct ReqMeta {
38
    /// If true, the client will accept intermediate Updates other than the
39
    /// final Request or Response.
40
    #[serde(default)]
41
    pub(crate) updates: bool,
42

            
43
    /// A list of features which must be implemented in order to understand the request.
44
    /// If any feature in this list is not available, the request must be rejected.
45
    #[serde(default)]
46
    pub(crate) require: Vec<String>,
47
}
48

            
49
/// A single Request received from an RPC client.
50
#[derive(Debug, Deserialize)]
51
pub(crate) struct Request {
52
    /// The client's identifier for this request.
53
    ///
54
    /// We'll use this to link all responses to this request.
55
    pub(crate) id: RequestId,
56
    /// The object to receive this request.
57
    pub(crate) obj: rpc::ObjectId,
58
    /// Any metadata to explain how this request is handled.
59
    #[serde(default)]
60
    pub(crate) meta: ReqMeta,
61
    /// The method to actually execute.
62
    ///
63
    /// Using "flatten" here will make it expand to "method" and "params".
64
    #[serde(flatten)]
65
    pub(crate) method: Box<dyn rpc::DeserMethod>,
66
}
67

            
68
/// A request that may or may not be valid.
69
///
70
/// If it invalid, it contains information that can be used to construct an error.
71
#[derive(Debug, serde::Deserialize)]
72
#[serde(untagged)]
73
pub(crate) enum FlexibleRequest {
74
    /// A valid request.
75
    Valid(Request),
76
    /// An invalid request.
77
    Invalid(invalid::InvalidRequest),
78
    // TODO RPC: Right now `InvalidRequest` should handle any Json Object,
79
    // but we might additionally want to parse any Json _Value_
80
    // (and reject it without killing the connection).
81
    // If we do, we ought to add a third variant here.
82
    //
83
    // Without this change, our implementation will be slightly more willing to close connections
84
    // than the spec requires:
85
    // The spec says we need to kill a connection on anything that can't be parsed as Json;
86
    // we kill a connection on anything that can't be parsed as a Json _Object_.
87
}
88

            
89
/// A Response to send to an RPC client.
90
#[derive(Debug, Serialize)]
91
pub(crate) struct BoxedResponse {
92
    /// An ID for the request that we're responding to.
93
    ///
94
    /// This is always present on a response to every valid request; it is also
95
    /// present on responses to invalid requests if we could discern what their
96
    /// `id` field was. We only omit it when the request id was indeterminate.
97
    /// If we do that, we close the connection immediately afterwards.
98
    #[serde(skip_serializing_if = "Option::is_none")]
99
    pub(crate) id: Option<RequestId>,
100
    /// The body  that we're sending.
101
    #[serde(flatten)]
102
    pub(crate) body: ResponseBody,
103
}
104

            
105
impl BoxedResponse {
106
    /// Construct a BoxedResponse from an error that can be converted into an
107
    /// RpcError.
108
2
    pub(crate) fn from_error<E>(id: Option<RequestId>, error: E) -> Self
109
2
    where
110
2
        E: Into<rpc::RpcError>,
111
    {
112
2
        let error: rpc::RpcError = error.into();
113
2
        let body = ResponseBody::Error(Box::new(error));
114
2
        Self { id, body }
115
2
    }
116
}
117

            
118
/// The body of a response for an RPC client.
119
#[derive(Serialize)]
120
pub(crate) enum ResponseBody {
121
    /// The request has failed; no more responses will be sent in reply to it.
122
    #[serde(rename = "error")]
123
    Error(Box<rpc::RpcError>),
124
    /// The request has succeeded; no more responses will be sent in reply to
125
    /// it.
126
    ///
127
    /// Note that in the spec, this is called a "result": we don't propagate
128
    /// that terminology into Rust, where `Result` has a different meaning.
129
    #[serde(rename = "result")]
130
    Success(Box<dyn erased_serde::Serialize + Send>),
131
    /// The request included the `updates` flag to increment that incremental
132
    /// progress information is acceptable.
133
    #[serde(rename = "update")]
134
    Update(Box<dyn erased_serde::Serialize + Send>),
135
}
136

            
137
impl ResponseBody {
138
    /// Return true if this body type indicates that no future responses will be
139
    /// sent for this request.
140
    #[cfg(test)]
141
6
    pub(crate) fn is_final(&self) -> bool {
142
6
        match self {
143
4
            ResponseBody::Error(_) | ResponseBody::Success(_) => true,
144
2
            ResponseBody::Update(_) => false,
145
        }
146
6
    }
147
}
148

            
149
impl From<rpc::RpcError> for ResponseBody {
150
2
    fn from(inp: rpc::RpcError) -> ResponseBody {
151
2
        ResponseBody::Error(Box::new(inp))
152
2
    }
153
}
154

            
155
impl std::fmt::Debug for ResponseBody {
156
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
157
        // We use serde_json to format the output for debugging, since that's all we care about at this point.
158
        let json = |x| match serde_json::to_string(x) {
159
            Ok(s) => s,
160
            Err(e) => format!("«could not serialize: {}»", e),
161
        };
162
        match self {
163
            Self::Error(arg0) => f.debug_tuple("Error").field(arg0).finish(),
164
            Self::Update(arg0) => f.debug_tuple("Update").field(&json(arg0)).finish(),
165
            Self::Success(arg0) => f.debug_tuple("Success").field(&json(arg0)).finish(),
166
        }
167
    }
168
}
169

            
170
#[cfg(test)]
171
mod test {
172
    // @@ begin test lint list maintained by maint/add_warning @@
173
    #![allow(clippy::bool_assert_comparison)]
174
    #![allow(clippy::clone_on_copy)]
175
    #![allow(clippy::dbg_macro)]
176
    #![allow(clippy::mixed_attributes_style)]
177
    #![allow(clippy::print_stderr)]
178
    #![allow(clippy::print_stdout)]
179
    #![allow(clippy::single_char_pattern)]
180
    #![allow(clippy::unwrap_used)]
181
    #![allow(clippy::unchecked_time_subtraction)]
182
    #![allow(clippy::useless_vec)]
183
    #![allow(clippy::needless_pass_by_value)]
184
    #![allow(clippy::string_slice)] // See arti#2571
185
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
186
    use super::*;
187
    use derive_deftly::Deftly;
188
    use tor_rpcbase::templates::*;
189

            
190
    /// Assert that two arguments have the same output from `std::fmt::Debug`.
191
    ///
192
    /// This can be handy for testing for some notion of equality on objects
193
    /// that implement `Debug` but not `PartialEq`.
194
    macro_rules! assert_dbg_eq {
195
        ($a:expr, $b:expr) => {
196
            assert_eq!(format!("{:?}", $a), format!("{:?}", $b));
197
        };
198
    }
199

            
200
    // TODO RPC: note that the existence of this method type can potentially
201
    // leak into our real RPC engine when we're compiled with `test` enabled!
202
    // We should consider how bad this is, and maybe use a real method instead.
203
    #[derive(Debug, serde::Deserialize, Deftly)]
204
    #[derive_deftly(DynMethod)]
205
    #[deftly(rpc(method_name = "x-test:dummy"))]
206
    struct DummyMethod {
207
        #[serde(default)]
208
        #[allow(dead_code)]
209
        stuff: u64,
210
    }
211

            
212
    impl rpc::RpcMethod for DummyMethod {
213
        type Output = DummyResponse;
214
        type Update = rpc::NoUpdates;
215
    }
216

            
217
    #[derive(Serialize)]
218
    struct DummyResponse {
219
        hello: i64,
220
        world: String,
221
    }
222

            
223
    #[test]
224
    fn valid_requests() {
225
        let parse_request = |s| match serde_json::from_str::<FlexibleRequest>(s) {
226
            Ok(FlexibleRequest::Valid(req)) => req,
227
            other => panic!("{:?}", other),
228
        };
229

            
230
        let r =
231
            parse_request(r#"{"id": 7, "obj": "hello", "method": "x-test:dummy", "params": {} }"#);
232
        assert_dbg_eq!(
233
            r,
234
            Request {
235
                id: RequestId::Int(7),
236
                obj: rpc::ObjectId::from("hello"),
237
                meta: ReqMeta::default(),
238
                method: Box::new(DummyMethod { stuff: 0 })
239
            }
240
        );
241
    }
242

            
243
    #[test]
244
    fn invalid_requests() {
245
        use crate::err::RequestParseError as RPE;
246
        fn parsing_error(s: &str) -> RPE {
247
            match serde_json::from_str::<FlexibleRequest>(s) {
248
                Ok(FlexibleRequest::Invalid(req)) => req.error(),
249
                x => panic!("Didn't expect {:?}", x),
250
            }
251
        }
252

            
253
        macro_rules! expect_err {
254
            ($p:pat, $e:expr) => {
255
                let err = parsing_error($e);
256
                assert!(matches!(err, $p), "Unexpected error type {:?}", err);
257
            };
258
        }
259

            
260
        expect_err!(
261
            RPE::IdMissing,
262
            r#"{ "obj": "hello", "method": "x-test:dummy", "params": {} }"#
263
        );
264
        expect_err!(
265
            RPE::IdType,
266
            r#"{ "id": {}, "obj": "hello", "method": "x-test:dummy", "params": {} }"#
267
        );
268
        expect_err!(
269
            RPE::ObjMissing,
270
            r#"{ "id": 3, "method": "x-test:dummy", "params": {} }"#
271
        );
272
        expect_err!(
273
            RPE::ObjType,
274
            r#"{ "id": 3, "obj": 9, "method": "x-test:dummy", "params": {} }"#
275
        );
276
        expect_err!(
277
            RPE::MethodMissing,
278
            r#"{ "id": 3, "obj": "hello",  "params": {} }"#
279
        );
280
        expect_err!(
281
            RPE::MethodType,
282
            r#"{ "id": 3, "obj": "hello", "method": [], "params": {} }"#
283
        );
284
        expect_err!(
285
            RPE::MetaType,
286
            r#"{ "id": 3, "obj": "hello", "meta": 7, "method": "x-test:dummy", "params": {} }"#
287
        );
288
        expect_err!(
289
            RPE::MetaType,
290
            r#"{ "id": 3, "obj": "hello", "meta": { "updates": 3}, "method": "x-test:dummy", "params": {} }"#
291
        );
292
        expect_err!(
293
            RPE::NoSuchMethod,
294
            r#"{ "id": 3, "obj": "hello", "method": "arti:this-is-not-a-method", "params": {} }"#
295
        );
296
        expect_err!(
297
            RPE::MissingParams,
298
            r#"{ "id": 3, "obj": "hello", "method": "x-test:dummy" }"#
299
        );
300
        expect_err!(
301
            RPE::ParamType,
302
            r#"{ "id": 3, "obj": "hello", "method": "x-test:dummy", "params": 7 }"#
303
        );
304
    }
305

            
306
    #[test]
307
    fn fmt_replies() {
308
        let resp = BoxedResponse {
309
            id: Some(RequestId::Int(7)),
310
            body: ResponseBody::Success(Box::new(DummyResponse {
311
                hello: 99,
312
                world: "foo".into(),
313
            })),
314
        };
315
        let s = serde_json::to_string(&resp).unwrap();
316
        // NOTE: This is a bit fragile for a test, since nothing in serde or
317
        // serde_json guarantees that the fields will be serialized in this
318
        // exact order.
319
        assert_eq!(s, r#"{"id":7,"result":{"hello":99,"world":"foo"}}"#);
320

            
321
        let resp = BoxedResponse::from_error(
322
            None,
323
            rpc::RpcError::from(crate::err::RequestParseError::IdMissing),
324
        );
325
        let s = serde_json::to_string(&resp).unwrap();
326
        // NOTE: as above.
327
        assert_eq!(
328
            s,
329
            r#"{"error":{"message":"Request did not have any `id` field.","code":-32600,"kinds":["rpc:InvalidRequest"]}}"#
330
        );
331
    }
332

            
333
    #[test]
334
    fn response_body_is_final() {
335
        let response_body_error = ResponseBody::from(rpc::RpcError::new(
336
            "This is a test!".to_string(),
337
            rpc::RpcErrorKind::ObjectNotFound,
338
        ));
339
        assert!(response_body_error.is_final());
340

            
341
        let response_body_success = ResponseBody::Success(Box::new(DummyResponse {
342
            hello: 99,
343
            world: "foo".into(),
344
        }));
345
        assert!(response_body_success.is_final());
346

            
347
        let response_body_update = ResponseBody::Update(Box::new(DummyResponse {
348
            hello: 99,
349
            world: "foo".into(),
350
        }));
351
        assert!(!response_body_update.is_final());
352
    }
353
}