asic_rs/miners/backends/btminer/v3/
rpc.rs1use crate::miners::api::RPCAPIClient;
2use crate::miners::api::rpc::status::RPCCommandStatus;
3use crate::miners::api::{APIClient, rpc::errors::RPCError};
4use crate::miners::commands::MinerCommand;
5use anyhow::{Result, anyhow};
6use async_trait::async_trait;
7use serde_json::{Value, json};
8use std::net::IpAddr;
9use tokio::io::{AsyncReadExt, AsyncWriteExt};
10
11#[derive(Debug)]
12pub struct BTMinerRPCAPI {
13 ip: IpAddr,
14 port: u16,
15}
16
17#[async_trait]
18impl APIClient for BTMinerRPCAPI {
19 async fn get_api_result(&self, command: &MinerCommand) -> Result<Value> {
20 match command {
21 MinerCommand::RPC {
22 command,
23 parameters,
24 } => self
25 .send_command(command, false, parameters.clone())
26 .await
27 .map_err(|e| anyhow!(e.to_string())),
28 _ => Err(anyhow!("Cannot send non RPC command to RPC API")),
29 }
30 }
31}
32
33impl RPCCommandStatus {
34 fn from_btminer_v3(response: &str) -> Result<Self, RPCError> {
35 let value: serde_json::Value = serde_json::from_str(response)?;
36
37 match value["code"].as_i64() {
38 None => {
39 let message = value["msg"].as_str();
40
41 Err(RPCError::StatusCheckFailed(
42 message
43 .unwrap_or("Unknown error when looking for status code")
44 .to_owned(),
45 ))
46 }
47 Some(code) => match code {
48 0 => Ok(Self::Success),
49 _ => {
50 let message = value["msg"].as_str();
51 Err(RPCError::StatusCheckFailed(
52 message
53 .unwrap_or("Unknown error when parsing status")
54 .to_owned(),
55 ))
56 }
57 },
58 }
59 }
60}
61
62#[async_trait]
63impl RPCAPIClient for BTMinerRPCAPI {
64 async fn send_command(
65 &self,
66 command: &str,
67 _privileged: bool,
68 parameters: Option<Value>,
69 ) -> Result<Value> {
70 let mut stream = tokio::net::TcpStream::connect((self.ip, self.port))
71 .await
72 .map_err(|_| RPCError::ConnectionFailed)?;
73
74 let request = match parameters {
75 Some(Value::Object(mut obj)) => {
76 obj.insert("cmd".to_string(), json!(command));
78 Value::Object(obj)
79 }
80 Some(other) => {
81 json!({ "cmd": command, "param": other })
83 }
84 None => {
85 json!({ "cmd": command })
87 }
88 };
89 let json_str = request.to_string();
90 let json_bytes = json_str.as_bytes();
91 let length = json_bytes.len() as u32;
92
93 stream.write_all(&length.to_le_bytes()).await?;
94 stream.write_all(json_bytes).await?;
95
96 let mut len_buf = [0u8; 4];
97 stream.read_exact(&mut len_buf).await?;
98 let response_len = u32::from_le_bytes(len_buf) as usize;
99
100 let mut resp_buf = vec![0u8; response_len];
101 stream.read_exact(&mut resp_buf).await?;
102
103 let response_str = String::from_utf8_lossy(&resp_buf).into_owned();
104
105 self.parse_rpc_result(&response_str)
106 }
107}
108
109impl BTMinerRPCAPI {
110 pub fn new(ip: IpAddr, port: Option<u16>) -> Self {
111 Self {
112 ip,
113 port: port.unwrap_or(4433),
114 }
115 }
116
117 fn parse_rpc_result(&self, response: &str) -> Result<Value> {
118 let status = RPCCommandStatus::from_btminer_v3(response)?;
119 match status.into_result() {
120 Ok(_) => Ok(serde_json::from_str(response)?),
121 Err(e) => Err(e)?,
122 }
123 }
124}