1use std::fmt;
2
3use serde::{Deserialize, Serialize};
4use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
5
6pub const MAX_MESSAGE_SIZE: u32 = 1024;
11
12pub const MAX_AUTH_TOKEN_LEN: usize = 512;
16
17#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
19#[serde(rename_all = "snake_case")]
20pub enum ApiResponseStatus {
21 Accepted,
22 Running,
23 Completed,
24 Failed,
25 Cancelled,
26 NotFound,
27 Error,
28}
29
30#[derive(Serialize, Deserialize, Clone)]
38pub enum MinerMessage {
39 Ready {
42 token: String,
44 },
45
46 NewJob(MiningRequest),
49
50 JobResult(MiningResult),
52}
53
54impl fmt::Debug for MinerMessage {
55 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56 match self {
57 Self::Ready { .. } => f.write_str("Ready { token: \"[REDACTED]\" }"),
58 Self::NewJob(req) => f.debug_tuple("NewJob").field(req).finish(),
59 Self::JobResult(res) => f.debug_tuple("JobResult").field(res).finish(),
60 }
61 }
62}
63
64pub async fn write_message<W: AsyncWrite + Unpin>(
68 writer: &mut W,
69 msg: &MinerMessage,
70) -> std::io::Result<()> {
71 let json = serde_json::to_vec(msg)
72 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
73 let len = json.len() as u32;
74 writer.write_all(&len.to_be_bytes()).await?;
75 writer.write_all(&json).await?;
76 Ok(())
77}
78
79pub async fn read_message<R: AsyncRead + Unpin>(reader: &mut R) -> std::io::Result<MinerMessage> {
84 let mut len_buf = [0u8; 4];
85 reader.read_exact(&mut len_buf).await?;
86 let len = u32::from_be_bytes(len_buf);
87
88 if len > MAX_MESSAGE_SIZE {
89 return Err(std::io::Error::new(
90 std::io::ErrorKind::InvalidData,
91 format!("Message size {} exceeds maximum {}", len, MAX_MESSAGE_SIZE),
92 ));
93 }
94
95 let mut buf = vec![0u8; len as usize];
96 reader.read_exact(&mut buf).await?;
97 serde_json::from_slice(&buf)
98 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))
99}
100
101#[derive(Serialize, Deserialize, Debug, Clone)]
106pub struct MiningRequest {
107 pub job_id: String,
108 pub mining_hash: String,
110 pub difficulty: String,
112}
113
114#[derive(Serialize, Deserialize, Debug, Clone)]
116pub struct MiningResponse {
117 pub status: ApiResponseStatus,
118 pub job_id: String,
119 #[serde(skip_serializing_if = "Option::is_none")]
120 pub message: Option<String>,
121}
122
123#[derive(Serialize, Deserialize, Debug, Clone)]
125pub struct MiningResult {
126 pub status: ApiResponseStatus,
127 pub job_id: String,
128 pub nonce: Option<String>,
130 pub work: Option<String>,
133 pub hash_count: u64,
134 pub elapsed_time: f64,
135 #[serde(default, skip_serializing_if = "Option::is_none")]
137 pub miner_id: Option<u64>,
138}