asic_rs/miners/backends/vnish/
web.rs1use crate::miners::{
2 api::{APIClient, WebAPIClient},
3 commands::MinerCommand,
4};
5use anyhow::{Result, anyhow};
6use async_trait::async_trait;
7use reqwest::{Client, Method, Response};
8use serde_json::Value;
9use std::{net::IpAddr, sync::RwLock, time::Duration};
10
11#[derive(Debug)]
14pub struct VnishWebAPI {
15 client: Client,
16 pub ip: IpAddr,
17 port: u16,
18 timeout: Duration,
19 bearer_token: RwLock<Option<String>>,
20 password: Option<String>,
21}
22
23#[async_trait]
24impl APIClient for VnishWebAPI {
25 async fn get_api_result(&self, command: &MinerCommand) -> Result<Value> {
26 match command {
27 MinerCommand::WebAPI {
28 command,
29 parameters,
30 } => self
31 .send_command(command, false, parameters.clone(), Method::GET)
32 .await
33 .map_err(|e| anyhow!(e.to_string())),
34 _ => Err(anyhow!("Cannot send non web command to web API")),
35 }
36 }
37}
38
39#[async_trait]
40impl WebAPIClient for VnishWebAPI {
41 async fn send_command(
43 &self,
44 command: &str,
45 _privileged: bool,
46 parameters: Option<Value>,
47 method: Method,
48 ) -> Result<Value> {
49 if let Err(e) = self.ensure_authenticated().await {
51 return Err(anyhow!("Failed to authenticate: {}", e));
52 }
53
54 let url = format!("http://{}:{}/api/v1/{}", self.ip, self.port, command);
55
56 let response = self.execute_request(&url, &method, parameters).await?;
57
58 let status = response.status();
59 if status.is_success() {
60 let json_data = response
61 .json()
62 .await
63 .map_err(|e| VnishError::ParseError(e.to_string()))?;
64 Ok(json_data)
65 } else {
66 Err(VnishError::HttpError(status.as_u16()))?
67 }
68 }
69}
70
71impl VnishWebAPI {
72 pub fn new(ip: IpAddr, port: u16) -> Self {
74 let client = Client::builder()
75 .timeout(Duration::from_secs(10))
76 .build()
77 .expect("Failed to create HTTP client");
78
79 Self {
80 client,
81 ip,
82 port,
83 timeout: Duration::from_secs(5),
84 bearer_token: RwLock::new(None),
85 password: Some("admin".to_string()), }
87 }
88
89 async fn ensure_authenticated(&self) -> Result<(), VnishError> {
91 if self.bearer_token.read().unwrap().is_none() && self.password.is_some() {
92 if let Some(ref password) = self.password {
93 match self.authenticate(password).await {
94 Ok(token) => {
95 *self.bearer_token.write().unwrap() = Some(token);
96 Ok(())
97 }
98 Err(e) => Err(e),
99 }
100 } else {
101 Err(VnishError::AuthenticationFailed)
102 }
103 } else {
104 Ok(())
105 }
106 }
107
108 async fn authenticate(&self, password: &str) -> Result<String, VnishError> {
109 let unlock_payload = serde_json::json!({ "pw": password });
110 let url = format!("http://{}:{}/api/v1/unlock", self.ip, self.port);
111
112 let response = self
113 .client
114 .post(&url)
115 .json(&unlock_payload)
116 .timeout(self.timeout)
117 .send()
118 .await
119 .map_err(|e| VnishError::NetworkError(e.to_string()))?;
120
121 if !response.status().is_success() {
122 return Err(VnishError::AuthenticationFailed);
123 }
124
125 let unlock_response: Value = response
126 .json()
127 .await
128 .map_err(|e| VnishError::ParseError(e.to_string()))?;
129
130 unlock_response
131 .pointer("/token")
132 .and_then(|t| t.as_str())
133 .map(String::from)
134 .ok_or(VnishError::AuthenticationFailed)
135 }
136
137 async fn execute_request(
139 &self,
140 url: &str,
141 method: &Method,
142 parameters: Option<Value>,
143 ) -> Result<Response, VnishError> {
144 let request_builder = match *method {
145 Method::GET => self.client.get(url),
146 Method::POST => {
147 let mut builder = self.client.post(url);
148 if let Some(params) = parameters {
149 builder = builder.json(¶ms);
150 }
151 builder
152 }
153 Method::PATCH => {
154 let mut builder = self.client.patch(url);
155 if let Some(params) = parameters {
156 builder = builder.json(¶ms);
157 }
158 builder
159 }
160 _ => return Err(VnishError::UnsupportedMethod(method.to_string())),
161 };
162
163 let mut request_builder = request_builder.timeout(self.timeout);
164
165 if let Some(ref token) = *self.bearer_token.read().unwrap() {
167 request_builder = request_builder.header("Authorization", format!("Bearer {token}"));
168 }
169
170 let request = request_builder
171 .build()
172 .map_err(|e| VnishError::RequestError(e.to_string()))?;
173
174 let response = self
175 .client
176 .execute(request)
177 .await
178 .map_err(|e| VnishError::NetworkError(e.to_string()))?;
179
180 Ok(response)
181 }
182}
183
184#[derive(Debug, Clone)]
186pub enum VnishError {
187 NetworkError(String),
189 HttpError(u16),
191 ParseError(String),
193 RequestError(String),
195 Timeout,
197 UnsupportedMethod(String),
199 MaxRetriesExceeded,
201 AuthenticationFailed,
203 Unauthorized,
205}
206
207impl std::fmt::Display for VnishError {
208 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
209 match self {
210 VnishError::NetworkError(msg) => write!(f, "Network error: {msg}"),
211 VnishError::HttpError(code) => write!(f, "HTTP error: {code}"),
212 VnishError::ParseError(msg) => write!(f, "Parse error: {msg}"),
213 VnishError::RequestError(msg) => write!(f, "Request error: {msg}"),
214 VnishError::Timeout => write!(f, "Request timeout"),
215 VnishError::UnsupportedMethod(method) => write!(f, "Unsupported method: {method}"),
216 VnishError::MaxRetriesExceeded => write!(f, "Maximum retries exceeded"),
217 VnishError::AuthenticationFailed => write!(f, "Authentication failed"),
218 VnishError::Unauthorized => write!(f, "Unauthorized access"),
219 }
220 }
221}
222
223impl std::error::Error for VnishError {}