1//! Support for encoding and decoding RPC Requests.
2//!
3//! There are several types in this module:
4//!
5//! - [`Request`] is for requests that are generated from within this crate,
6//! to implement authentication, negotiation, and other functionality.
7//! - `ParsedRequestFields` (internal) is for a request we've completely validated,
8//! with all of its fields present.
9//! - [`ValidatedRequest`] is for a string that we have validated as a request.
1011use std::sync::Arc;
1213use serde::{Deserialize, Serialize};
1415/// Alias for a Map as used by the serde_json.
16pub(crate) type JsonMap = serde_json::Map<String, serde_json::Value>;
1718use crate::conn::ProtoError;
1920use super::{AnyRequestId, JsonAnyObj, ObjectId};
2122/// An outbound request that we have generated from within this crate.
23///
24/// It lacks a required `id` field (since we will generate one when sending it),
25/// and it allows any Serialize for its `params`.
26#[derive(Serialize, Debug)]
27// Testing only. Don't implement Deserialize here; this is not the type you should parse into!
28#[cfg_attr(test, derive(Eq, PartialEq, Deserialize))]
29#[allow(clippy::missing_docs_in_private_items)] // Fields are as for ParsedRequest.
30pub(crate) struct Request<T> {
31#[serde(skip_serializing_if = "Option::is_none")]
32pub(crate) id: Option<AnyRequestId>,
33pub(crate) obj: ObjectId,
34#[serde(skip_serializing_if = "Option::is_none")]
35pub(crate) meta: Option<RequestMeta>,
36pub(crate) method: String,
37pub(crate) params: T,
38}
3940/// An error that has prevented us from validating an request.
41#[derive(Clone, Debug, thiserror::Error)]
42#[non_exhaustive]
43pub enum InvalidRequestError {
44/// We failed to turn the request into any kind of json.
45#[error("Request was not valid Json")]
46InvalidJson(#[source] Arc<serde_json::Error>),
47/// We got the request into json, but we couldn't find the fields we wanted.
48#[error("Request's fields were invalid or missing")]
49InvalidFormat(#[source] Arc<serde_json::Error>),
50/// We validated the request, but couldn't re-encode it.
51#[error("Unable to re-encode or format request")]
52ReencodeFailed(#[source] Arc<serde_json::Error>),
53}
5455impl<T: Serialize> Request<T> {
56/// Construct a new outbound Request.
57pub(crate) fn new(obj: ObjectId, method: impl Into<String>, params: T) -> Self {
58Self {
59 id: None,
60 obj,
61 meta: Default::default(),
62 method: method.into(),
63 params,
64 }
65 }
66/// Try to encode this request as a String.
67 ///
68 /// The string may not yet be a valid request; it might need to get an ID assigned.
69pub(crate) fn encode(&self) -> Result<String, ProtoError> {
70 serde_json::to_string(self).map_err(|e| ProtoError::CouldNotEncode(Arc::new(e)))
71 }
72}
7374/// A request in its decoded (or unencoded) format.
75///
76/// We use this type to validate outbound requests from the application.
77#[derive(Deserialize, Debug)]
78// Don't implement Serialize here; this is not for generating requests!
79#[allow(dead_code)] // The fields here are only used for validating serde objects.
80struct ParsedRequestFields {
81/// The identifier for this request.
82 ///
83 /// Used to match a request with its responses.
84id: AnyRequestId,
85/// The ID for the object to which this request is addressed.
86 ///
87 /// (Every request goes to a single object.)
88obj: ObjectId,
89/// Additional information for Arti about how to handle the request.
90#[serde(skip_serializing_if = "Option::is_none")]
91meta: Option<RequestMeta>,
92/// The name of the method to invoke.
93method: String,
94/// Parameters to pass to the method.
95params: JsonAnyObj,
96}
9798/// A known-valid request, encoded as a string (in a single line, with a terminating newline).
99#[derive(derive_more::AsRef, Debug, Clone)]
100pub(crate) struct ValidatedRequest {
101/// The message itself, as encoded.
102#[as_ref]
103msg: String,
104/// The ID for this request.
105id: AnyRequestId,
106}
107108impl ValidatedRequest {
109/// Return the Id associated with this request.
110pub(crate) fn id(&self) -> &AnyRequestId {
111&self.id
112 }
113114/// Try to construct a validated request from a `serde_json::Value`.
115fn from_json_value(val: serde_json::Value) -> Result<Self, InvalidRequestError> {
116let mut msg = serde_json::to_string(&val)
117 .map_err(|e| InvalidRequestError::ReencodeFailed(Arc::new(e)))?;
118debug_assert!(!msg.contains('\n'));
119 msg.push('\n');
120121let req: ParsedRequestFields = serde_json::from_value(val)
122 .map_err(|e| InvalidRequestError::InvalidFormat(Arc::new(e)))?;
123let id = req.id;
124125Ok(ValidatedRequest { id, msg })
126 }
127128/// Try to construct a validated request using `s`.
129pub(crate) fn from_string_strict(s: &str) -> Result<Self, InvalidRequestError> {
130let value: serde_json::Value =
131 serde_json::from_str(s).map_err(|e| InvalidRequestError::InvalidJson(Arc::new(e)))?;
132Self::from_json_value(value)
133 }
134135/// Try to construct a ValidatedRequest from the string in `s`.
136 ///
137 /// If it has no `id`, add one using `id_generator`.
138pub(crate) fn from_string_loose<F>(
139 s: &str,
140 id_generator: F,
141 ) -> Result<Self, InvalidRequestError>
142where
143F: FnOnce() -> AnyRequestId,
144 {
145let mut value: serde_json::Value =
146 serde_json::from_str(s).map_err(|e| InvalidRequestError::InvalidJson(Arc::new(e)))?;
147148if let Some(obj) = value.as_object_mut() {
149 obj.entry("id")
150 .or_insert_with(|| id_generator().into_json_value());
151 }
152153Self::from_json_value(value)
154 }
155}
156157/// Crate-internal: The "meta" field in a request.
158#[derive(Deserialize, Serialize, Debug, Default)]
159#[cfg_attr(test, derive(Eq, PartialEq))]
160pub(crate) struct RequestMeta {
161/// If true, the application wants to receive incremental updates
162 /// about the request that it sent.
163 ///
164 /// (Default: false)
165#[serde(default)]
166pub(crate) updates: bool,
167/// Any unrecognized fields that we received from the user.
168 /// (We re-encode these in case the user knows about fields that we don't.)
169#[serde(flatten)]
170pub(crate) unrecognized_fields: JsonMap,
171}
172173/// A helper to return unique Request identifiers.
174///
175/// All identifiers are prefixed with `"!aut o!--"`:
176/// if you don't use that string in your own IDs,
177/// you won't have any collisions.
178#[derive(Debug, Default)]
179pub(crate) struct IdGenerator {
180/// The number
181next_id: u64,
182}
183184impl IdGenerator {
185/// Return a previously unyielded identifier.
186pub(crate) fn next_id(&mut self) -> AnyRequestId {
187let id = self.next_id;
188self.next_id += 1;
189format!("!auto!--{id}").into()
190 }
191}
192193#[cfg(test)]
194mod test {
195// @@ begin test lint list maintained by maint/add_warning @@
196#![allow(clippy::bool_assert_comparison)]
197 #![allow(clippy::clone_on_copy)]
198 #![allow(clippy::dbg_macro)]
199 #![allow(clippy::mixed_attributes_style)]
200 #![allow(clippy::print_stderr)]
201 #![allow(clippy::print_stdout)]
202 #![allow(clippy::single_char_pattern)]
203 #![allow(clippy::unwrap_used)]
204 #![allow(clippy::unchecked_duration_subtraction)]
205 #![allow(clippy::useless_vec)]
206 #![allow(clippy::needless_pass_by_value)]
207//! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
208209impl ParsedRequestFields {
210/// Return true if this request is asking for updates.
211fn updates_requested(&self) -> bool {
212self.meta.as_ref().map(|m| m.updates).unwrap_or(false)
213 }
214 }
215216use crate::util::assert_same_json;
217218use super::*;
219const REQ1: &str = r#"{"id":7, "obj": "hi", "meta": {"updates": true}, "method":"twiddle", "params":{"stuff": "nonsense"} }"#;
220const REQ2: &str = r#"{"id":"fred", "obj": "hi", "method":"twiddle", "params":{} }"#;
221const REQ3: &str =
222r#"{"id":"fred", "obj": "hi", "method":"twiddle", "params":{},"unrecognized":"waffles"}"#;
223224#[test]
225fn parse_requests() {
226let req1: ParsedRequestFields = serde_json::from_str(REQ1).unwrap();
227assert_eq!(req1.id, 7.into());
228assert_eq!(req1.obj.as_ref(), "hi");
229assert_eq!(req1.updates_requested(), true);
230assert_eq!(req1.method, "twiddle");
231232let req2: ParsedRequestFields = serde_json::from_str(REQ2).unwrap();
233assert_eq!(req2.id, "fred".to_string().into());
234assert_eq!(req2.obj.as_ref(), "hi");
235assert_eq!(req2.updates_requested(), false);
236assert_eq!(req2.method, "twiddle");
237238let _req3: ParsedRequestFields = serde_json::from_str(REQ2).unwrap();
239 }
240241#[test]
242fn reencode_requests() {
243for r in [REQ1, REQ2, REQ3] {
244let val1 = ValidatedRequest::from_string_strict(r).unwrap();
245let val2 = ValidatedRequest::from_string_loose(r, || panic!()).unwrap();
246247assert_same_json!(val1.as_ref(), val2.as_ref());
248assert_same_json!(val1.as_ref(), r);
249 }
250 }
251252#[test]
253fn bad_requests() {
254for text in [
255// not an object.
256"123",
257// missing most parts.
258r#"{"id":12,}"#,
259// no id.
260r#"{"obj":"hi", "method":"twiddle", "params":{"stuff":"nonsense"}}"#,
261// no params
262r#"{"obj":"hi", "id": 7, "method":"twiddle"}"#,
263// bad params type
264r#"{"obj":"hi", "id": 7, "method":"twiddle", "params": []}"#,
265// weird obj.
266r#"{"obj":7, "id": 7, "method":"twiddle", "params":{"stuff":"nonsense"}}"#,
267// weird id.
268r#"{"obj":"hi", "id": [], "method":"twiddle", "params":{"stuff":"nonsense"}}"#,
269// weird method
270r#"{"obj":"hi", "id": 7, "method":6", "params":{"stuff":"nonsense"}}"#,
271 ] {
272let r: Result<ParsedRequestFields, _> = serde_json::from_str(dbg!(text));
273assert!(r.is_err());
274 }
275 }
276277#[test]
278fn fix_requests() {
279let no_id = r#"{"obj":"hi", "method":"twiddle", "params":{"stuff":"nonsense"}}"#;
280let validated = ValidatedRequest::from_string_loose(no_id, || 7.into()).unwrap();
281let expected_with_id =
282r#"{"id": 7, "obj":"hi", "method":"twiddle", "params":{"stuff":"nonsense"}}"#;
283assert_same_json!(validated.as_ref(), expected_with_id);
284 }
285286#[test]
287fn preserve_fields() {
288let orig = r#"
289 {"obj":"hi",
290 "meta": { "updates": true, "waffles": "yesplz" },
291 "method":"twiddle",
292 "params":{"stuff":"nonsense"},
293 "explosions": -70
294 }"#;
295let validated = ValidatedRequest::from_string_loose(orig, || 77.into()).unwrap();
296let expected_with_id = r#"
297 {"id":77,
298 "obj":"hi",
299 "meta": { "updates": true, "waffles": "yesplz" },
300 "method":"twiddle",
301 "params":{"stuff":"nonsense"},
302 "explosions": -70
303 }"#;
304assert_same_json!(validated.as_ref(), expected_with_id);
305 }
306307#[test]
308fn ok_request_encode() {
309let expected_encoded_request =
310r#"{"obj":"connection","method":"arti:get_rpc_proxy_info","params":"123"}"#;
311let obj_id = ObjectId::connection_id();
312let encoded_request = Request::new(obj_id, "arti:get_rpc_proxy_info", "123")
313 .encode()
314 .unwrap();
315assert_eq!(expected_encoded_request, encoded_request);
316 }
317318// This should not be possible
319#[test]
320fn err_request_encode() {
321struct FailingSerialization;
322323impl serde::Serialize for FailingSerialization {
324fn serialize<S>(&self, _serializer: S) -> Result<S::Ok, S::Error>
325where
326S: serde::Serializer,
327 {
328Err(serde::ser::Error::custom(
329"Intentional serialization failure",
330 ))
331 }
332 }
333334let obj_id = ObjectId::connection_id();
335let failing_request = Request::new(obj_id, "arti:get_rpc_proxy_info", FailingSerialization);
336337let err = failing_request.encode().unwrap_err();
338assert!(matches!(err, ProtoError::CouldNotEncode(_)));
339 }
340}