use anyhow::{Context, Result};
use std::net::SocketAddr;
use tokio::net::TcpStream;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use crate::config::ClientConfig;
use super::protocol::{Command, Response};
pub struct ControlClient {
server_addr: SocketAddr,
client_id: String,
hmac_secret: String,
}
impl ControlClient {
pub fn new(config: &ClientConfig) -> Result<Self> {
let server_addr: SocketAddr = format!(
"{}:{}",
config.server_address, config.server_port
)
.parse()
.context("Invalid server address")?;
Ok(Self {
server_addr,
client_id: config.client_id.clone(),
hmac_secret: config.hmac_secret.clone(),
})
}
pub async fn send_command(&self, command: &Command) -> Result<Response> {
let mut stream = TcpStream::connect(&self.server_addr)
.await
.context(format!("Failed to connect to server at {}", self.server_addr))?;
let data = command.to_bytes()?;
let len = data.len() as u32;
stream.write_all(&len.to_le_bytes()).await?;
stream.write_all(&data).await?;
let mut len_buf = [0u8; 4];
stream.read_exact(&mut len_buf).await?;
let resp_len = u32::from_le_bytes(len_buf) as usize;
let mut resp_buf = vec![0u8; resp_len];
stream.read_exact(&mut resp_buf).await?;
let response = Response::from_bytes(&resp_buf)?;
Ok(response)
}
pub async fn register_ports(&self, ports: &[crate::control::protocol::PortEntry]) -> Result<Response> {
let command = Command::RegisterPorts {
client_id: self.client_id.clone(),
ports: ports.to_vec(),
};
self.send_command(&command).await
}
pub async fn heartbeat(&self) -> Result<Response> {
let command = Command::Heartbeat {
client_id: self.client_id.clone(),
timestamp: chrono::Utc::now().timestamp(),
};
self.send_command(&command).await
}
}