From b01ab44475ea5cb2d3ba2d81f4a0b0dfb7397b13 Mon Sep 17 00:00:00 2001 From: WhoNeedszZz <2114674+WhoNeedszZz@users.noreply.github.com> Date: Wed, 29 Jan 2020 07:44:33 -0700 Subject: [PATCH] Resolve JSON RPC 2.0 non-compliance (#3213) Currently, the JSON RPC requests and responses are not compliant with the spec as the server only accepts strings. This change is *non-breaking* as it simply adds the ability to understand integers as expected. Any old code continuing to only send string requests will continue to work as before. Unit tests are included to ensure (de)serialization works on both strings and integers. --- servers/src/mining/stratumserver.rs | 164 +++++++++++++++++++++++++++- 1 file changed, 159 insertions(+), 5 deletions(-) diff --git a/servers/src/mining/stratumserver.rs b/servers/src/mining/stratumserver.rs index 190c8ee4..26e94204 100644 --- a/servers/src/mining/stratumserver.rs +++ b/servers/src/mining/stratumserver.rs @@ -52,17 +52,26 @@ type Tx = mpsc::UnboundedSender; // http://www.jsonrpc.org/specification // RPC Methods -#[derive(Serialize, Deserialize, Debug)] +/// Represents a compliant JSON RPC 2.0 id. +/// Valid id: Integer, String. +#[derive(Serialize, Deserialize, Debug, PartialEq)] +#[serde(untagged)] +enum JsonId { + IntId(u32), + StrId(String), +} + +#[derive(Serialize, Deserialize, Debug, PartialEq)] struct RpcRequest { - id: String, + id: JsonId, jsonrpc: String, method: String, params: Option, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, PartialEq)] struct RpcResponse { - id: String, + id: JsonId, jsonrpc: String, method: String, result: Option, @@ -501,7 +510,7 @@ impl Handler { // Issue #1159 - use a serde_json Value type to avoid extra quoting let job_template_value: Value = serde_json::from_str(&job_template_json).unwrap(); let job_request = RpcRequest { - id: String::from("Stratum"), + id: JsonId::StrId(String::from("Stratum")), jsonrpc: String::from("2.0"), method: String::from("job"), params: Some(job_template_value), @@ -865,3 +874,148 @@ where .and_then(|v| serde_json::from_value(v).ok()) .ok_or(RpcError::invalid_request()) } + +#[cfg(test)] +mod tests { + use super::*; + + /// Tests deserializing an `RpcRequest` given a String as the id. + #[test] + fn test_request_deserialize_str() { + let expected = RpcRequest { + id: JsonId::StrId(String::from("1")), + method: String::from("login"), + jsonrpc: String::from("2.0"), + params: None, + }; + let json = r#"{"id":"1","method":"login","jsonrpc":"2.0","params":null}"#; + let serialized: RpcRequest = serde_json::from_str(json).unwrap(); + + assert_eq!(expected, serialized); + } + + /// Tests serializing an `RpcRequest` given a String as the id. + /// The extra step of deserializing again is due to associative structures not maintaining order. + #[test] + fn test_request_serialize_str() { + let expected = r#"{"id":"1","method":"login","jsonrpc":"2.0","params":null}"#; + let rpc = RpcRequest { + id: JsonId::StrId(String::from("1")), + method: String::from("login"), + jsonrpc: String::from("2.0"), + params: None, + }; + let json_actual = serde_json::to_string(&rpc).unwrap(); + + let expected_deserialized: RpcRequest = serde_json::from_str(expected).unwrap(); + let actual_deserialized: RpcRequest = serde_json::from_str(&json_actual).unwrap(); + + assert_eq!(expected_deserialized, actual_deserialized); + } + + /// Tests deserializing an `RpcResponse` given a String as the id. + #[test] + fn test_response_deserialize_str() { + let expected = RpcResponse { + id: JsonId::StrId(String::from("1")), + method: String::from("login"), + jsonrpc: String::from("2.0"), + result: None, + error: None, + }; + let json = r#"{"id":"1","method":"login","jsonrpc":"2.0","params":null}"#; + let serialized: RpcResponse = serde_json::from_str(json).unwrap(); + + assert_eq!(expected, serialized); + } + + /// Tests serializing an `RpcResponse` given a String as the id. + /// The extra step of deserializing again is due to associative structures not maintaining order. + #[test] + fn test_response_serialize_str() { + let expected = r#"{"id":"1","method":"login","jsonrpc":"2.0","params":null}"#; + let rpc = RpcResponse { + id: JsonId::StrId(String::from("1")), + method: String::from("login"), + jsonrpc: String::from("2.0"), + result: None, + error: None, + }; + let json_actual = serde_json::to_string(&rpc).unwrap(); + + let expected_deserialized: RpcResponse = serde_json::from_str(expected).unwrap(); + let actual_deserialized: RpcResponse = serde_json::from_str(&json_actual).unwrap(); + + assert_eq!(expected_deserialized, actual_deserialized); + } + + /// Tests deserializing an `RpcRequest` given an integer as the id. + #[test] + fn test_request_deserialize_int() { + let expected = RpcRequest { + id: JsonId::IntId(1), + method: String::from("login"), + jsonrpc: String::from("2.0"), + params: None, + }; + let json = r#"{"id":1,"method":"login","jsonrpc":"2.0","params":null}"#; + let serialized: RpcRequest = serde_json::from_str(json).unwrap(); + + assert_eq!(expected, serialized); + } + + /// Tests serializing an `RpcRequest` given an integer as the id. + /// The extra step of deserializing again is due to associative structures not maintaining order. + #[test] + fn test_request_serialize_int() { + let expected = r#"{"id":1,"method":"login","jsonrpc":"2.0","params":null}"#; + let rpc = RpcRequest { + id: JsonId::IntId(1), + method: String::from("login"), + jsonrpc: String::from("2.0"), + params: None, + }; + let json_actual = serde_json::to_string(&rpc).unwrap(); + + let expected_deserialized: RpcRequest = serde_json::from_str(expected).unwrap(); + let actual_deserialized: RpcRequest = serde_json::from_str(&json_actual).unwrap(); + + assert_eq!(expected_deserialized, actual_deserialized); + } + + /// Tests deserializing an `RpcResponse` given an integer as the id. + #[test] + fn test_response_deserialize_int() { + let expected = RpcResponse { + id: JsonId::IntId(1), + method: String::from("login"), + jsonrpc: String::from("2.0"), + result: None, + error: None, + }; + let json = r#"{"id":1,"method":"login","jsonrpc":"2.0","params":null}"#; + let serialized: RpcResponse = serde_json::from_str(json).unwrap(); + + assert_eq!(expected, serialized); + } + + /// Tests serializing an `RpcResponse` given an integer as the id. + /// The extra step of deserializing again is due to associative structures not maintaining order. + #[test] + fn test_response_serialize_int() { + let expected = r#"{"id":1,"method":"login","jsonrpc":"2.0","params":null}"#; + let rpc = RpcResponse { + id: JsonId::IntId(1), + method: String::from("login"), + jsonrpc: String::from("2.0"), + result: None, + error: None, + }; + let json_actual = serde_json::to_string(&rpc).unwrap(); + + let expected_deserialized: RpcResponse = serde_json::from_str(expected).unwrap(); + let actual_deserialized: RpcResponse = serde_json::from_str(&json_actual).unwrap(); + + assert_eq!(expected_deserialized, actual_deserialized); + } +}