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
14
#[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
    pub(crate) updates: bool,
41
}
42

            
43
/// A single Request received from an RPC client.
44
100
#[derive(Debug, Deserialize)]
45
pub(crate) struct Request {
46
    /// The client's identifier for this request.
47
    ///
48
    /// We'll use this to link all responses to this request.
49
    pub(crate) id: RequestId,
50
    /// The object to receive this request.
51
    pub(crate) obj: rpc::ObjectId,
52
    /// Any metadata to explain how this request is handled.
53
    #[serde(default)]
54
    pub(crate) meta: ReqMeta,
55
    /// The method to actually execute.
56
    ///
57
    /// Using "flatten" here will make it expand to "method" and "params".
58
    ///
59
    /// TODO RPC: Note that our spec says that "params" can be omitted, but I
60
    /// don't think we support that right now.
61
    #[serde(flatten)]
62
    pub(crate) method: Box<dyn rpc::DeserMethod>,
63
}
64

            
65
/// A request that may or may not be valid.
66
///
67
/// If it invalid, it contains information that can be used to construct an error.
68
#[derive(Debug, serde::Deserialize)]
69
#[serde(untagged)]
70
pub(crate) enum FlexibleRequest {
71
    /// A valid request.
72
    Valid(Request),
73
    /// An invalid request.
74
    Invalid(invalid::InvalidRequest),
75
}
76

            
77
/// A Response to send to an RPC client.
78
#[derive(Debug, Serialize)]
79
pub(crate) struct BoxedResponse {
80
    /// An ID for the request that we're responding to.
81
    ///
82
    /// This is always present on a response to every valid request; it is also
83
    /// present on responses to invalid requests if we could discern what their
84
    /// `id` field was. We only omit it when the request id was indeterminate.
85
    /// If we do that, we close the connection immediately afterwards.
86
    #[serde(skip_serializing_if = "Option::is_none")]
87
    pub(crate) id: Option<RequestId>,
88
    /// The body  that we're sending.
89
    #[serde(flatten)]
90
    pub(crate) body: ResponseBody,
91
}
92

            
93
impl BoxedResponse {
94
    /// Construct a BoxedResponse from an error that can be converted into an
95
    /// RpcError.
96
    pub(crate) fn from_error<E>(id: Option<RequestId>, error: E) -> Self
97
    where
98
        E: Into<rpc::RpcError>,
99
    {
100
        let error: rpc::RpcError = error.into();
101
        let body = ResponseBody::Error(Box::new(error));
102
        Self { id, body }
103
    }
104
}
105

            
106
/// The body of a response for an RPC client.
107
#[derive(Serialize)]
108
pub(crate) enum ResponseBody {
109
    /// The request has failed; no more responses will be sent in reply to it.
110
    #[serde(rename = "error")]
111
    Error(Box<rpc::RpcError>),
112
    /// The request has succeeded; no more responses will be sent in reply to
113
    /// it.
114
    ///
115
    /// Note that in the spec, this is called a "result": we don't propagate
116
    /// that terminology into Rust, where `Result` has a different meaning.
117
    #[serde(rename = "result")]
118
    Success(Box<dyn erased_serde::Serialize + Send>),
119
    /// The request included the `updates` flag to increment that incremental
120
    /// progress information is acceptable.
121
    #[serde(rename = "update")]
122
    Update(Box<dyn erased_serde::Serialize + Send>),
123
}
124

            
125
impl ResponseBody {
126
    /// Return true if this body type indicates that no future responses will be
127
    /// sent for this request.
128
    pub(crate) fn is_final(&self) -> bool {
129
        match self {
130
            ResponseBody::Error(_) | ResponseBody::Success(_) => true,
131
            ResponseBody::Update(_) => false,
132
        }
133
    }
134
}
135

            
136
impl From<rpc::RpcError> for ResponseBody {
137
    fn from(inp: rpc::RpcError) -> ResponseBody {
138
        ResponseBody::Error(Box::new(inp))
139
    }
140
}
141

            
142
impl std::fmt::Debug for ResponseBody {
143
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
144
        // We use serde_json to format the output for debugging, since that's all we care about at this point.
145
        let json = |x| match serde_json::to_string(x) {
146
            Ok(s) => s,
147
            Err(e) => format!("«could not serialize: {}»", e),
148
        };
149
        match self {
150
            Self::Error(arg0) => f.debug_tuple("Error").field(arg0).finish(),
151
            Self::Update(arg0) => f.debug_tuple("Update").field(&json(arg0)).finish(),
152
            Self::Success(arg0) => f.debug_tuple("Success").field(&json(arg0)).finish(),
153
        }
154
    }
155
}
156

            
157
#[cfg(test)]
158
mod test {
159
    // @@ begin test lint list maintained by maint/add_warning @@
160
    #![allow(clippy::bool_assert_comparison)]
161
    #![allow(clippy::clone_on_copy)]
162
    #![allow(clippy::dbg_macro)]
163
    #![allow(clippy::mixed_attributes_style)]
164
    #![allow(clippy::print_stderr)]
165
    #![allow(clippy::print_stdout)]
166
    #![allow(clippy::single_char_pattern)]
167
    #![allow(clippy::unwrap_used)]
168
    #![allow(clippy::unchecked_duration_subtraction)]
169
    #![allow(clippy::useless_vec)]
170
    #![allow(clippy::needless_pass_by_value)]
171
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
172
    use super::*;
173
    use derive_deftly::Deftly;
174
    use tor_rpcbase::templates::*;
175

            
176
    /// Assert that two arguments have the same output from `std::fmt::Debug`.
177
    ///
178
    /// This can be handy for testing for some notion of equality on objects
179
    /// that implement `Debug` but not `PartialEq`.
180
    macro_rules! assert_dbg_eq {
181
        ($a:expr, $b:expr) => {
182
            assert_eq!(format!("{:?}", $a), format!("{:?}", $b));
183
        };
184
    }
185

            
186
    // TODO RPC: note that the existence of this method type can potentially
187
    // leak into our real RPC engine when we're compiled with `test` enabled!
188
    // We should consider how bad this is, and maybe use a real method instead.
189
    #[derive(Debug, serde::Deserialize, Deftly)]
190
    #[derive_deftly(DynMethod)]
191
    #[deftly(rpc(method_name = "x-test:dummy"))]
192
    struct DummyMethod {
193
        #[serde(default)]
194
        #[allow(dead_code)]
195
        stuff: u64,
196
    }
197

            
198
    impl rpc::RpcMethod for DummyMethod {
199
        type Output = DummyResponse;
200
        type Update = rpc::NoUpdates;
201
    }
202

            
203
    #[derive(Serialize)]
204
    struct DummyResponse {
205
        hello: i64,
206
        world: String,
207
    }
208

            
209
    #[test]
210
    fn valid_requests() {
211
        let parse_request = |s| match serde_json::from_str::<FlexibleRequest>(s) {
212
            Ok(FlexibleRequest::Valid(req)) => req,
213
            _ => panic!(),
214
        };
215

            
216
        let r =
217
            parse_request(r#"{"id": 7, "obj": "hello", "method": "x-test:dummy", "params": {} }"#);
218
        assert_dbg_eq!(
219
            r,
220
            Request {
221
                id: RequestId::Int(7),
222
                obj: rpc::ObjectId::from("hello"),
223
                meta: ReqMeta::default(),
224
                method: Box::new(DummyMethod { stuff: 0 })
225
            }
226
        );
227
    }
228

            
229
    #[test]
230
    fn invalid_requests() {
231
        use crate::err::RequestParseError as RPE;
232
        fn parsing_error(s: &str) -> RPE {
233
            match serde_json::from_str::<FlexibleRequest>(s) {
234
                Ok(FlexibleRequest::Invalid(req)) => req.error(),
235
                x => panic!("Didn't expect {:?}", x),
236
            }
237
        }
238

            
239
        macro_rules! expect_err {
240
            ($p:pat, $e:expr) => {
241
                let err = parsing_error($e);
242
                assert!(matches!(err, $p), "Unexpected error type {:?}", err);
243
            };
244
        }
245

            
246
        expect_err!(
247
            RPE::IdMissing,
248
            r#"{ "obj": "hello", "method": "x-test:dummy", "params": {} }"#
249
        );
250
        expect_err!(
251
            RPE::IdType,
252
            r#"{ "id": {}, "obj": "hello", "method": "x-test:dummy", "params": {} }"#
253
        );
254
        expect_err!(
255
            RPE::ObjMissing,
256
            r#"{ "id": 3, "method": "x-test:dummy", "params": {} }"#
257
        );
258
        expect_err!(
259
            RPE::ObjType,
260
            r#"{ "id": 3, "obj": 9, "method": "x-test:dummy", "params": {} }"#
261
        );
262
        expect_err!(
263
            RPE::MethodMissing,
264
            r#"{ "id": 3, "obj": "hello",  "params": {} }"#
265
        );
266
        expect_err!(
267
            RPE::MethodType,
268
            r#"{ "id": 3, "obj": "hello", "method": [], "params": {} }"#
269
        );
270
        expect_err!(
271
            RPE::MetaType,
272
            r#"{ "id": 3, "obj": "hello", "meta": 7, "method": "x-test:dummy", "params": {} }"#
273
        );
274
        expect_err!(
275
            RPE::MetaType,
276
            r#"{ "id": 3, "obj": "hello", "meta": { "updates": 3}, "method": "x-test:dummy", "params": {} }"#
277
        );
278
        expect_err!(
279
            RPE::MethodNotFound,
280
            r#"{ "id": 3, "obj": "hello", "method": "arti:this-is-not-a-method", "params": {} }"#
281
        );
282
        expect_err!(
283
            RPE::MissingParams,
284
            r#"{ "id": 3, "obj": "hello", "method": "x-test:dummy" }"#
285
        );
286
        expect_err!(
287
            RPE::ParamType,
288
            r#"{ "id": 3, "obj": "hello", "method": "x-test:dummy", "params": 7 }"#
289
        );
290
    }
291

            
292
    #[test]
293
    fn fmt_replies() {
294
        let resp = BoxedResponse {
295
            id: Some(RequestId::Int(7)),
296
            body: ResponseBody::Success(Box::new(DummyResponse {
297
                hello: 99,
298
                world: "foo".into(),
299
            })),
300
        };
301
        let s = serde_json::to_string(&resp).unwrap();
302
        // NOTE: This is a bit fragile for a test, since nothing in serde or
303
        // serde_json guarantees that the fields will be serialized in this
304
        // exact order.
305
        assert_eq!(s, r#"{"id":7,"result":{"hello":99,"world":"foo"}}"#);
306

            
307
        let resp = BoxedResponse {
308
            id: None,
309
            body: ResponseBody::Error(Box::new(rpc::RpcError::from(
310
                crate::err::RequestParseError::IdMissing,
311
            ))),
312
        };
313
        let s = serde_json::to_string(&resp).unwrap();
314
        // NOTE: as above.
315
        assert_eq!(
316
            s,
317
            r#"{"error":{"message":"error: Request did not have any `id` field.","code":-32600,"kinds":["arti:RpcInvalidRequest"]}}"#
318
        );
319
    }
320
}