diff --git a/Cargo.toml b/Cargo.toml index 3502477..455cdbd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ members = [ "codecs", "ffi", "firmware", + "stratum-v1", "test-vectors", "ur", "urtypes", diff --git a/stratum-v1/Cargo.toml b/stratum-v1/Cargo.toml new file mode 100644 index 0000000..cd1f6e0 --- /dev/null +++ b/stratum-v1/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "stratum-v1" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = { version = "1", default-features = false } +heapless = { version = "0.7", features = ["serde"] } +hex = { version = "0.4.3", default-features = false, features = ["serde", "heapless"], git = "https://github.com/Georges760/rust-hex.git", branch = "heapless" } +json-rpc-types = { version = "1.3.4", features = ["id-number-only"] } +serde = { version = "1", default-features = false, features = ["serde_derive"] } +serde-json-core = { version = "0.5.1", features = ["custom-error-messages"] } diff --git a/stratum-v1/src/client_not.rs b/stratum-v1/src/client_not.rs new file mode 100644 index 0000000..cef10c9 --- /dev/null +++ b/stratum-v1/src/client_not.rs @@ -0,0 +1,72 @@ +use anyhow::{Error, Result}; +use heapless::{String, Vec}; +use serde::Deserialize; + +/// # Parse Notification Method +/// +/// In order to know which kind of notification we are dealing with, we need to parse the notification method. +/// +/// ## Parameters +/// +/// resp: The slice notification to parse. +/// +/// ## Examples +/// ``` +/// use stratum_v1::client_not::parse_notification_method; +/// use heapless::String; +/// +/// let resp = br#"{"params":["00003000"], "id":null, "method": "mining.set_version_mask"}"#; +/// assert_eq!(parse_notification_method(resp).unwrap(), String::<32>::from("mining.set_version_mask")); +/// +/// let resp = br#"{"params": ["bf", "4d16b6f85af6e2198f44ae2a6de67f78487ae5611b77c6c0440b921e00000000","01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff20020862062f503253482f04b8864e5008","072f736c7573682f000000000100f2052a010000001976a914d23fcdf86f7e756a64a7a9688ef9903327048ed988ac00000000", [],"00000002", "1c2ac4af", "504e86b9", false], "id": null, "method": "mining.notify"}"#; +/// assert_eq!(parse_notification_method(resp).unwrap(), String::<32>::from("mining.notify")); +/// +/// let resp = br#"{ "id": null, "method": "mining.set_difficulty", "params": [2]}"#; +/// assert_eq!(parse_notification_method(resp).unwrap(), String::<32>::from("mining.set_difficulty")); +/// ``` +pub fn parse_notification_method(resp: &[u8]) -> Result> { + #[derive(Deserialize)] + struct MethodOnly { + method: String<32>, + } + Ok(serde_json_core::from_slice::(resp) + .map_err(Error::msg)? + .0 + .method) +} + +/// # Parse Notification Set Version Mask +/// +/// Parses the notification with the `set_version_mask` method. +/// +/// ## Parameters +/// +/// resp: The notification slice to parse. +/// +/// ## Examples +/// ``` +/// use stratum_v1::client_not::parse_notification_set_version_mask; +/// +/// let resp = br#"{"params":["00003000"], "id":null, "method": "mining.set_version_mask"}"#; +/// let r = parse_notification_set_version_mask(resp); +/// println!("{:?}", r); +/// assert_eq!(parse_notification_set_version_mask(resp).unwrap(), 0x00003000); +/// ``` +pub fn parse_notification_set_version_mask(resp: &[u8]) -> Result { + #[derive(Deserialize)] + struct SetVersionMaskNotificationParams( + // mask: The meaning is the same as the "version-rolling.mask" return parameter. + #[serde(deserialize_with = "hex::deserialize")] Vec, + ); + let v: Vec = hex::decode_heapless( + serde_json_core::from_slice::, 1>, String<64>>>(resp) + .map_err(Error::msg)? + .0 + .params + .unwrap() + .pop() + .unwrap(), + ) + .expect("decode error"); + Ok(u32::from_be_bytes(v[0..4].try_into().unwrap())) +} diff --git a/stratum-v1/src/client_req.rs b/stratum-v1/src/client_req.rs new file mode 100644 index 0000000..395afe4 --- /dev/null +++ b/stratum-v1/src/client_req.rs @@ -0,0 +1,361 @@ +use anyhow::{Error, Result}; +use heapless::{String, Vec}; +use hex::ToHex; +use json_rpc_types::{Id, Version}; +use serde::Serialize; + +#[derive(Debug)] +pub struct VersionRolling { + // Bits set to 1 can be changed by the miner. + // If a miner changes bits with mask value 0, the server will reject the submit. + pub mask: Option, + // Minimum number of bits that it needs for efficient version rolling in hardware. + pub min_bit_count: u8, +} + +#[derive(Debug)] +pub struct Info { + // Exact URL used by the mining software to connect to the stratum server. + pub connection_url: Option>, + // Manufacturer specific hardware revision string. + pub hw_version: Option>, + // Manufacturer specific software version. + pub sw_version: Option>, + // Unique identifier of the mining device. + pub hw_id: Option>, +} + +#[derive(Debug)] +pub struct Extensions { + // This extension allows the miner to change the value of some bits in the version field + // in the block header. Currently there are no standard bits used for version rolling + // so they need to be negotiated between a miner and a server. + pub version_rolling: Option, + // This extension allows miner to request a minimum difficulty for the connected machine. + // It solves a problem in the original stratum protocol where there is no way how to + // communicate hard limit of the connected device. + pub minimum_difficulty: Option, + // Miner advertises its capability of receiving message "mining.set_extranonce" message + // (useful for hash rate routing scenarios). + pub subscribe_extranonce: Option<()>, + // Miner provides additional text-based information. + pub info: Option, +} + +/// # Configure Request +/// +/// Create a client configure request. +/// +/// ## Parameters +/// +/// id: must be an unique integer to identify the request. +/// Will be used when a response is received to match corresponding request. +/// +/// exts: a list of extensions to configure. +/// +/// ## Example +/// ``` +/// use stratum_v1::client_req::{configure_request, Extensions, VersionRolling}; +/// use heapless::Vec; +/// +/// let mut buf = [0u8; 1024]; +/// let mut exts = Extensions { +/// version_rolling: Some(VersionRolling{mask: Some(0x1fffe000), min_bit_count: 2}), +/// minimum_difficulty: None, +/// subscribe_extranonce: None, +/// info: None, +/// }; +/// let len = configure_request(0, exts, buf.as_mut_slice()); +/// assert!(len.is_ok()); +/// assert_eq!(len.unwrap(),153); +/// assert_eq!(&buf[0..153], br#"{"jsonrpc":"2.0","method":"mining.configure","params":[["version-rolling"],{"version-rolling.mask":"1fffe000","version-rolling.min-bit-count":2}],"id":0}"#); +/// +/// let mut exts = Extensions { +/// version_rolling: Some(VersionRolling{mask: Some(0x1fffe000), min_bit_count: 2}), +/// minimum_difficulty: Some(2048), +/// subscribe_extranonce: None, +/// info: None, +/// }; +/// let len = configure_request(0, exts, buf.as_mut_slice()); +/// assert!(len.is_ok()); +/// assert_eq!(len.unwrap(),206); +/// assert_eq!(&buf[0..206], br#"{"jsonrpc":"2.0","method":"mining.configure","params":[["version-rolling","minimum-difficulty"],{"version-rolling.mask":"1fffe000","version-rolling.min-bit-count":2,"minimum-difficulty.value":2048}],"id":0}"#); +/// ``` +pub fn configure_request(id: u64, exts: Extensions, buf: &mut [u8]) -> Result { + let method = String::from("mining.configure"); + + type ExtList = Vec, 4>; + + #[derive(Debug, Serialize)] + struct ExtParams { + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(rename = "version-rolling.mask")] + version_rolling_mask: Option>, + + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(rename = "version-rolling.min-bit-count")] + version_rolling_min_bit_count: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(rename = "minimum-difficulty.value")] + minimum_difficulty_value: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(rename = "info.connection-url")] + info_connection_url: Option>, + + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(rename = "info.hw-version")] + info_hw_version: Option>, + + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(rename = "info.sw-version")] + info_sw_version: Option>, + + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(rename = "info.hw-id")] + info_hw_id: Option>, + } + + #[derive(Debug, Serialize)] + struct ConfigureParams(ExtList, ExtParams); + + let mut ext_list = Vec::new(); + let mut ext_params = ExtParams { + version_rolling_mask: None, + version_rolling_min_bit_count: None, + minimum_difficulty_value: None, + info_connection_url: None, + info_hw_version: None, + info_sw_version: None, + info_hw_id: None, + }; + if let Some(version_rolling) = &exts.version_rolling { + ext_list.push(String::from("version-rolling")).unwrap(); + if let Some(mask) = &version_rolling.mask { + ext_params.version_rolling_mask = Some(mask.to_be_bytes().encode_hex()); + } + ext_params.version_rolling_min_bit_count = Some(version_rolling.min_bit_count); + } + if let Some(minimum_difficulty) = &exts.minimum_difficulty { + ext_list.push(String::from("minimum-difficulty")).unwrap(); + ext_params.minimum_difficulty_value = Some(*minimum_difficulty); + } + if let Some(()) = &exts.subscribe_extranonce { + ext_list.push(String::from("subscribe-extranonce")).unwrap(); + } + if let Some(info) = &exts.info { + ext_list.push(String::from("info")).unwrap(); + if let Some(connection_url) = &info.connection_url { + ext_params.info_connection_url = Some(connection_url.clone()); + } + if let Some(hw_version) = &info.hw_version { + ext_params.info_hw_version = Some(hw_version.clone()); + } + if let Some(sw_version) = &info.sw_version { + ext_params.info_sw_version = Some(sw_version.clone()); + } + if let Some(hw_id) = &info.hw_id { + ext_params.info_hw_id = Some(hw_id.clone()); + } + } + let params = Some(ConfigureParams(ext_list, ext_params)); + let req = json_rpc_types::Request::> { + jsonrpc: Version::V2, + method, + params, + id: Some(Id::Num(id)), + }; + serde_json_core::to_slice(&req, buf).map_err(Error::msg) +} + +/// # Connect Request +/// +/// Create a client connection request. +/// +/// ## Parameters +/// +/// id: must be an unique integer to identify the request. +/// Will be used when a response is received to match corresponding request. +/// +/// identifier: a string to identify the client to the pool. +/// +/// ## Example +/// ``` +/// use stratum_v1::client_req::connect_request; +/// use heapless::String; +/// +/// let mut buf = [0u8; 1024]; +/// let len = connect_request(0, Some(String::<32>::from("test")), buf.as_mut_slice()); +/// assert!(len.is_ok()); +/// assert_eq!(len.unwrap(),70); +/// assert_eq!(&buf[0..70], br#"{"jsonrpc":"2.0","method":"mining.subscribe","params":["test"],"id":0}"#); +/// +/// let len = connect_request(1, Some(String::<32>::from("")), buf.as_mut_slice()); +/// assert!(len.is_ok()); +/// assert_eq!(len.unwrap(),66); +/// assert_eq!(&buf[0..66], br#"{"jsonrpc":"2.0","method":"mining.subscribe","params":[""],"id":1}"#); +/// +/// let len = connect_request(1, None, buf.as_mut_slice()); +/// assert!(len.is_ok()); +/// assert_eq!(len.unwrap(),64); +/// assert_eq!(&buf[0..64], br#"{"jsonrpc":"2.0","method":"mining.subscribe","params":[],"id":1}"#); +/// ``` +pub fn connect_request(id: u64, identifier: Option>, buf: &mut [u8]) -> Result { + let method = String::from("mining.subscribe"); + let mut vec = Vec::, 1>::new(); + if let Some(identifier) = identifier { + vec.push(identifier).map_err(Error::msg)?; + } + let params = Some(vec); + let req = json_rpc_types::Request::, 1>, String<16>> { + jsonrpc: Version::V2, + method, + params, + id: Some(Id::Num(id)), + }; + serde_json_core::to_slice(&req, buf).map_err(Error::msg) +} + +/// # Authorize Request +/// +/// Create a client authorize request. +/// +/// ## Parameters +/// +/// id: must be an unique integer to identify the request. +/// Will be used when a response is received to match corresponding request. +/// +/// user: a string with user name. +/// Usually composed by ".". +/// +/// pass: a string with user password. +/// +/// ## Example +/// ``` +/// use stratum_v1::client_req::authorize_request; +/// use heapless::String; +/// +/// let mut buf = [0u8; 1024]; +/// let len = authorize_request(1, String::<32>::from("slush.miner1"), String::<32>::from("password"), buf.as_mut_slice()); +/// assert!(len.is_ok()); +/// assert_eq!(len.unwrap(),89); +/// assert_eq!(&buf[0..89], br#"{"jsonrpc":"2.0","method":"mining.authorize","params":["slush.miner1","password"],"id":1}"#); +/// +/// let len = authorize_request(2, String::<32>::from(""), String::<32>::from(""), buf.as_mut_slice()); +/// assert!(len.is_ok()); +/// assert_eq!(len.unwrap(),69); +/// assert_eq!(&buf[0..69], br#"{"jsonrpc":"2.0","method":"mining.authorize","params":["",""],"id":2}"#); +/// ``` +pub fn authorize_request( + id: u64, + user: String<32>, + pass: String<32>, + buf: &mut [u8], +) -> Result { + let method = String::from("mining.authorize"); + let mut vec = Vec::, 2>::new(); + vec.push(user).map_err(Error::msg)?; + vec.push(pass).map_err(Error::msg)?; + let params = Some(vec); + let req = json_rpc_types::Request::, 2>, String<16>> { + jsonrpc: Version::V2, + method, + params, + id: Some(Id::Num(id)), + }; + serde_json_core::to_slice(&req, buf).map_err(Error::msg) +} + +pub struct Share { + pub user: String<32>, + pub job_id: String<32>, + pub extranonce2: u32, + pub ntime: u32, + pub nonce: u32, + pub version_bits: Option, +} + +/// # Submit Request +/// +/// Create a client submit request. +/// +/// ## Parameters +/// +/// id: must be an unique integer to identify the request. +/// Will be used when a response is received to match corresponding request. +/// +/// user: a string with user name. Max 32 characters. +/// Usually composed by ".". +/// +/// job_id: a string with the Job ID given in the Mining Job Notification. +/// +/// extranonce2: a 32-bits unsigned integer with the share's Extranonce2. +/// +/// ntime: a 32-bits unsigned integer with the share's nTime. +/// +/// nonce: a 32-bits unsigned integer with the share's nOnce. +/// +/// version_bits: an optional 32-bits unsigned integer with the share's version_bits. +/// +/// ## Example +/// ``` +/// use stratum_v1::client_req::{submit_request, Share}; +/// use heapless::String; +/// +/// let mut buf = [0u8; 1024]; +/// let share = Share { +/// user: String::<32>::from("slush.miner1"), +/// job_id: String::<32>::from("bf"), +/// extranonce2: 1, +/// ntime: 1347323629, +/// nonce: 0xb2957c02, +/// version_bits: None, +/// }; +/// let len = submit_request(1, share, buf.as_mut_slice()); +/// assert!(len.is_ok()); +/// assert_eq!(len.unwrap(),113); +/// assert_eq!(&buf[0..113], br#"{"jsonrpc":"2.0","method":"mining.submit","params":["slush.miner1","bf","00000001","504e86ed","b2957c02"],"id":1}"#); +/// ``` +pub fn submit_request(id: u64, share: Share, buf: &mut [u8]) -> Result { + let method = String::from("mining.submit"); + let mut vec = Vec::, 6>::new(); + vec.push(share.user).map_err(Error::msg)?; + vec.push(share.job_id).map_err(Error::msg)?; + vec.push( + share + .extranonce2 + .to_be_bytes() + .as_ref() + .encode_hex::>(), + ) + .map_err(Error::msg)?; + vec.push( + share + .ntime + .to_be_bytes() + .as_ref() + .encode_hex::>(), + ) + .map_err(Error::msg)?; + vec.push( + share + .nonce + .to_be_bytes() + .as_ref() + .encode_hex::>(), + ) + .map_err(Error::msg)?; + if let Some(v) = share.version_bits { + vec.push(v.to_be_bytes().as_ref().encode_hex::>()) + .map_err(Error::msg)?; + } + let params = Some(vec); + let req = json_rpc_types::Request::, 6>, String<13>> { + jsonrpc: Version::V2, + method, + params, + id: Some(Id::Num(id)), + }; + serde_json_core::to_slice(&req, buf).map_err(Error::msg) +} diff --git a/stratum-v1/src/client_resp.rs b/stratum-v1/src/client_resp.rs new file mode 100644 index 0000000..45896a9 --- /dev/null +++ b/stratum-v1/src/client_resp.rs @@ -0,0 +1,237 @@ +use anyhow::{Error, Result}; +use heapless::{String, Vec}; +use serde::Deserialize; + +/// # Parse Response ID +/// +/// In order to know which kind of response we are dealing with, we need to parse the response ID. +/// +/// ## Parameters +/// +/// resp: The slice response to parse. +/// +/// ## Examples +/// ``` +/// use stratum_v1::client_resp::parse_response_id; +/// +/// let resp = br#"{"id": 1, "result": [ [ ["mining.set_difficulty", "b4b6693b72a50c7116db18d6497cac52"], ["mining.notify", "ae6812eb4cd7735a302a8a9dd95cf71f"]], "08000002", 4], "error": null}"#; +/// assert_eq!(parse_response_id(resp).unwrap(), Some(1)); +/// +/// let resp = br#"{"error":null,"id":2,"result":[[["mining.notify","e26e1928"]],"e26e1928",4]}"#; +/// assert_eq!(parse_response_id(resp).unwrap(), Some(2)); +/// +/// let resp = br#"{ "id": null, "method": "mining.set_difficulty", "params": [2]}"#; +/// assert_eq!(parse_response_id(resp).unwrap(), None); +/// +/// let resp = br#"{ "id": "ab", "method": "mining.set_difficulty", "params": [2]}"#; +/// assert!(parse_response_id(resp).is_err()); +/// ``` +pub fn parse_response_id(resp: &[u8]) -> Result> { + #[derive(Deserialize)] + struct IdOnly { + id: Option, + } + let id = serde_json_core::from_slice::(resp) + .map_err(Error::msg)? + .0 + .id; + match id { + None => Ok(None), + Some(id) => Ok(Some(id)), + } +} + +#[derive(Debug, Deserialize, PartialEq)] +pub struct ConfigureResp { + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(rename = "version-rolling")] + pub version_rolling: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(rename = "version-rolling.mask")] + pub version_rolling_mask: Option>, + + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(rename = "version-rolling.min-bit-count")] + pub version_rolling_min_bit_count: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(rename = "minimum-difficulty")] + pub minimum_difficulty: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(rename = "minimum-difficulty.value")] + pub minimum_difficulty_value: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(rename = "subscribe-extranonce")] + pub subscribe_extranonce: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(rename = "info")] + pub info: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(rename = "info.connection-url")] + pub info_connection_url: Option>, + + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(rename = "info.hw-version")] + pub info_hw_version: Option>, + + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(rename = "info.sw-version")] + pub info_sw_version: Option>, + + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(rename = "info.hw-id")] + pub info_hw_id: Option>, +} + +/// # Parse Response Configure +/// +/// Parses the response to the `configure` method. +/// +/// ## Parameters +/// +/// resp: The response slice to parse. +/// +/// ## Examples +/// ``` +/// use stratum_v1::client_resp::{ConfigureResp, parse_response_configure}; +/// use heapless::String; +/// +/// let resp = br#"{"error": null,"id": 1,"result": {"version-rolling": true,"version-rolling.mask": "18000000","minimum-difficulty": true}}"#; +/// assert_eq!(parse_response_configure(resp).unwrap(), ConfigureResp { +/// version_rolling: Some(true), +/// version_rolling_mask: Some(String::from("18000000")), +/// version_rolling_min_bit_count: None, +/// minimum_difficulty: Some(true), +/// minimum_difficulty_value: None, +/// subscribe_extranonce: None, +/// info: None, +/// info_connection_url: None, +/// info_hw_version: None, +/// info_sw_version: None, +/// info_hw_id: None +/// }); +/// ``` +pub fn parse_response_configure(resp: &[u8]) -> Result { + serde_json_core::from_slice::>>(resp) + .map_err(Error::msg)? + .0 + .payload + .map_err(Error::msg) +} + +#[derive(Debug, Deserialize, PartialEq)] +pub struct ConnectResp( + // Subscriptions details - 2-tuple with name of subscribed notification and subscription ID. Teoretically it may be used for unsubscribing, but obviously miners won't use it. + Vec, 2>, 2>, + // Extranonce1 - Hex-encoded, per-connection unique string which will be used for coinbase serialization later. Keep it safe! + String<8>, + // Extranonce2_size - Represents expected length of extranonce2 which will be generated by the miner. + usize, +); + +impl ConnectResp { + pub fn subscriptions(&self) -> &Vec, 2>, 2> { + &self.0 + } + + pub fn extranonce1(&self) -> u32 { + let v: Vec = hex::decode_heapless(&self.1).expect("decode error"); + u32::from_be_bytes(v[0..4].try_into().unwrap()) + } + + pub fn extranonce2_size(&self) -> usize { + self.2 + } +} + +/// # Parse Response Connect +/// +/// Parses the response to the `connect` method. +/// +/// ## Parameters +/// +/// resp: The response slice to parse. +/// +/// ## Examples +/// ``` +/// use stratum_v1::client_resp::{ConnectResp, parse_response_connect}; +/// +/// let resp = br#"{"id": 10, "result": null, "error": [20, "Other/Unknown", null]}"#; +/// assert!(parse_response_connect(resp).is_err()); +/// +/// let resp = br#"{"id": 1, "result": [ [ ["mining.set_difficulty", "b4b6693b72a50c7116db18d6497cac52"], ["mining.notify", "ae6812eb4cd7735a302a8a9dd95cf71f"]], "08000002", 4], "error": null}"#; +/// let r = parse_response_connect(resp).unwrap(); +/// assert_eq!(r.subscriptions().len(), 2); +/// assert_eq!(r.extranonce1(), 0x08000002); +/// assert_eq!(r.extranonce2_size(), 4); +/// +/// let resp = br#"{"id":2,"error":null,"result":[[["mining.notify","e26e1928"]],"e26e1928",4]}"#; +/// let r = parse_response_connect(resp).unwrap(); +/// assert_eq!(r.subscriptions().len(), 1); +/// assert_eq!(r.extranonce1(), 0xe26e1928); +/// assert_eq!(r.extranonce2_size(), 4); +/// ``` +pub fn parse_response_connect(resp: &[u8]) -> Result { + serde_json_core::from_slice::>>(resp) + .map_err(Error::msg)? + .0 + .payload + .map_err(Error::msg) +} + +/// # Parse Response Authorize +/// +/// Parses the response to the `authorize` method. +/// +/// ## Parameters +/// +/// resp: The response slice to parse. +/// +/// ## Examples +/// ``` +/// use stratum_v1::client_resp::parse_response_authorize; +/// +/// let resp = br#"{"id": 10, "result": null, "error": [25, "Not subscribed", null]}"#; +/// assert!(parse_response_authorize(resp).is_err()); +/// +/// let resp = br#"{"id": 2, "result": true, "error": null}"#; +/// assert_eq!(parse_response_authorize(resp).unwrap(), true); +/// ``` +pub fn parse_response_authorize(resp: &[u8]) -> Result { + serde_json_core::from_slice::>>(resp) + .map_err(Error::msg)? + .0 + .payload + .map_err(Error::msg) +} + +/// # Parse Response Submit +/// +/// Parses the response to the `submit` method. +/// +/// ## Parameters +/// +/// resp: The response slice to parse. +/// +/// ## Examples +/// ``` +/// use stratum_v1::client_resp::parse_response_submit; +/// +/// let resp = br#"{"id": 10, "result": null, "error": [25, "Not subscribed", null]}"#; +/// assert!(parse_response_submit(resp).is_err()); +/// +/// let resp = br#"{"id": 2, "result": true, "error": null}"#; +/// assert_eq!(parse_response_submit(resp).unwrap(), true); +/// ``` +pub fn parse_response_submit(resp: &[u8]) -> Result { + serde_json_core::from_slice::>>(resp) + .map_err(Error::msg)? + .0 + .payload + .map_err(Error::msg) +} diff --git a/stratum-v1/src/lib.rs b/stratum-v1/src/lib.rs new file mode 100644 index 0000000..1c6ad93 --- /dev/null +++ b/stratum-v1/src/lib.rs @@ -0,0 +1,8 @@ +//! Stratum v1 client. +//! +//! This library provides client side functions to create requests and parse responses for Stratum v1 protocol. +#![no_std] + +pub mod client_not; +pub mod client_req; +pub mod client_resp;