use std::fmt;
use serde::{Deserialize, Serialize};
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
pub const MAX_MESSAGE_SIZE: u32 = 1024;
pub const MAX_AUTH_TOKEN_LEN: usize = 512;
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ApiResponseStatus {
Accepted,
Running,
Completed,
Failed,
Cancelled,
NotFound,
Error,
}
#[derive(Serialize, Deserialize, Clone)]
pub enum MinerMessage {
Ready {
token: String,
},
NewJob(MiningRequest),
JobResult(MiningResult),
}
impl fmt::Debug for MinerMessage {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Ready { .. } => f.write_str("Ready { token: \"[REDACTED]\" }"),
Self::NewJob(req) => f.debug_tuple("NewJob").field(req).finish(),
Self::JobResult(res) => f.debug_tuple("JobResult").field(res).finish(),
}
}
}
pub async fn write_message<W: AsyncWrite + Unpin>(
writer: &mut W,
msg: &MinerMessage,
) -> std::io::Result<()> {
let json = serde_json::to_vec(msg)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
let len = json.len() as u32;
writer.write_all(&len.to_be_bytes()).await?;
writer.write_all(&json).await?;
Ok(())
}
pub async fn read_message<R: AsyncRead + Unpin>(reader: &mut R) -> std::io::Result<MinerMessage> {
let mut len_buf = [0u8; 4];
reader.read_exact(&mut len_buf).await?;
let len = u32::from_be_bytes(len_buf);
if len > MAX_MESSAGE_SIZE {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("Message size {} exceeds maximum {}", len, MAX_MESSAGE_SIZE),
));
}
let mut buf = vec![0u8; len as usize];
reader.read_exact(&mut buf).await?;
serde_json::from_slice(&buf)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct MiningRequest {
pub job_id: String,
pub mining_hash: String,
pub difficulty: String,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct MiningResponse {
pub status: ApiResponseStatus,
pub job_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub message: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct MiningResult {
pub status: ApiResponseStatus,
pub job_id: String,
pub nonce: Option<String>,
pub work: Option<String>,
pub hash_count: u64,
pub elapsed_time: f64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub miner_id: Option<u64>,
}