//! Cliente de handshake. Conecta a un Unix socket y mantiene la sesión. use std::path::Path; use brahman_card::{Card, CARD_SCHEMA_VERSION}; use thiserror::Error; use tokio::net::UnixStream; use crate::codec::{read_frame, write_frame}; use crate::messages::{Farewell, Frame, HandshakeError, Hello, HelloAck, Ping, SessionId}; /// Errores del cliente. #[derive(Debug, Error)] pub enum ClientError { #[error("E/S: {0}")] Io(#[from] std::io::Error), /// El servidor respondió con un error explícito. #[error("servidor: {0}")] Server(#[source] HandshakeError), /// El servidor envió un frame que no esperábamos en este punto del protocolo. #[error("frame inesperado: {got}")] UnexpectedFrame { got: &'static str }, /// La Card que el cliente intentó enviar no pasa su propia validación. #[error("card inválida pre-envío: {0}")] InvalidCard(String), } /// Cliente conectado y autenticado. Tras `connect` ya completó el handshake /// y tiene su `SessionId`. #[derive(Debug)] pub struct Client { stream: UnixStream, session: SessionId, server_info: HelloAck, } impl Client { /// Conecta al socket, envía Hello con la Card dada y procesa la respuesta. pub async fn connect(path: impl AsRef, card: Card) -> Result { // Pre-validamos para fallar local antes de hablar con el servidor. card.validate() .map_err(|e| ClientError::InvalidCard(e.to_string()))?; let mut stream = UnixStream::connect(path).await?; let hello = Hello { schema_version: CARD_SCHEMA_VERSION, protocol_version: brahman_card::PROTOCOL_VERSION.to_string(), card, }; write_frame(&mut stream, &Frame::Hello(hello)).await?; let frame = read_frame(&mut stream).await?; let ack = match frame { Frame::HelloAck(a) => a, Frame::Error(e) => return Err(ClientError::Server(e)), Frame::Hello(_) => return Err(ClientError::UnexpectedFrame { got: "Hello" }), Frame::Ping(_) => return Err(ClientError::UnexpectedFrame { got: "Ping" }), Frame::Pong(_) => return Err(ClientError::UnexpectedFrame { got: "Pong" }), Frame::Farewell(_) => return Err(ClientError::UnexpectedFrame { got: "Farewell" }), }; Ok(Self { stream, session: ack.session, server_info: ack, }) } /// `SessionId` asignado por el servidor. pub fn session(&self) -> SessionId { self.session } /// Información del servidor recibida en el handshake. pub fn server_info(&self) -> &HelloAck { &self.server_info } /// Envía un Ping y devuelve el timestamp del servidor. pub async fn ping(&mut self) -> Result { write_frame( &mut self.stream, &Frame::Ping(Ping { session: self.session, }), ) .await?; match read_frame(&mut self.stream).await? { Frame::Pong(p) => Ok(p.timestamp_ms), Frame::Error(e) => Err(ClientError::Server(e)), _ => Err(ClientError::UnexpectedFrame { got: "non-pong" }), } } /// Cierre cooperativo. Consume el cliente. pub async fn farewell(mut self) -> Result<(), ClientError> { write_frame( &mut self.stream, &Frame::Farewell(Farewell { session: self.session, }), ) .await?; Ok(()) } }