Skip to main content

arti_rpcserver/
msgs.rs

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
6mod invalid;
7use serde::{Deserialize, Serialize};
8use 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)]
16pub(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)]
37pub(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)]
51pub(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)]
73pub(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)]
91pub(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
105impl BoxedResponse {
106    /// Construct a BoxedResponse from an error that can be converted into an
107    /// RpcError.
108    pub(crate) fn from_error<E>(id: Option<RequestId>, error: E) -> Self
109    where
110        E: Into<rpc::RpcError>,
111    {
112        let error: rpc::RpcError = error.into();
113        let body = ResponseBody::Error(Box::new(error));
114        Self { id, body }
115    }
116}
117
118/// The body of a response for an RPC client.
119#[derive(Serialize)]
120pub(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
137impl ResponseBody {
138    /// Return true if this body type indicates that no future responses will be
139    /// sent for this request.
140    #[cfg(test)]
141    pub(crate) fn is_final(&self) -> bool {
142        match self {
143            ResponseBody::Error(_) | ResponseBody::Success(_) => true,
144            ResponseBody::Update(_) => false,
145        }
146    }
147}
148
149impl From<rpc::RpcError> for ResponseBody {
150    fn from(inp: rpc::RpcError) -> ResponseBody {
151        ResponseBody::Error(Box::new(inp))
152    }
153}
154
155impl 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)]
171mod 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}