feat(brahman-handshake): Fase 1 — handshake brahman sobre stream libp2p
Segundo paso del plan "el encuentro entre Entes no se restringe a
local". El protocolo brahman (Hello / HelloAck / Ping / Pong /
MatchEvent / Farewell, frames postcard length-prefixed) ahora tambien
viaja sobre streams libp2p de la malla brahman-net — el mismo Init
acepta sesiones por Unix socket Y por libp2p indistintamente, y un
consumer remoto puede dial-ar al multiaddr y completar handshake.
Cambios:
- Session<S> y Client<S> genericos: ambos dejan de estar atados a
UnixStream y pasan a ser genericos sobre S: AsyncRead + AsyncWrite
+ Unpin + Send + 'static. El path Unix queda como
Client = Client<UnixStream> (default generico). Constructores
nuevos: Server::session_from_stream(stream),
Client::connect_with_stream(stream, card, wit).
- Refactor del post-handshake con split: tokio::select! sobre
&mut self.stream requeria S: Sync indirectamente, y libp2p::Stream
no es Sync. Reemplazado por tokio::io::split(stream) -> reader loop
principal + writer task separada que drena el push channel. Writer
compartido bajo Arc<Mutex<WriteHalf<S>>> para serializar Pong/Error
inline con los MatchEvents pusheados. Cleanup garantizado en todas
las ramas. La logica del post-handshake migra a funciones libres
(run_post_handshake, handle_inbound_frame, cleanup,
broadcast_match_diffs, do_handshake, register_session,
validate_hello).
- Nuevo modulo brahman-handshake::network:
- BRAHMAN_HANDSHAKE_PROTOCOL = "/brahman/handshake/1.0.0"
- LibP2pHandshakeStream = Compat<libp2p::Stream>
- run_libp2p_accept_loop(server, net): accept loop que delega cada
stream entrante a session_from_stream(stream.compat()). Sesiones
libp2p y Unix conviven en el mismo Server — comparten broker,
push table, last_matches.
- connect_libp2p(net, peer, card, wit): abre stream libp2p al peer
y arranca handshake.
- NetworkError tipado.
Deps: brahman-handshake gana brahman-net, futures, tokio-util.
brahman-net re-exporta Multiaddr, PeerId, Stream, StreamProtocol,
Protocol, OpenStreamError para que callers no necesiten dep directa
a libp2p.
Tests: 9 verdes en el path Unix (sin regresion). Nuevo
tests/network_libp2p.rs E2E que arma server con BrahmanNet, hace
listen TCP, monta accept loop; cliente con su propio BrahmanNet
dial-ea al peer_id, completa handshake remoto, ping, farewell.
Verifica que la sesion se registro durante la conversacion y se
removio tras farewell.
This commit is contained in:
@@ -6,6 +6,7 @@ use std::time::Duration;
|
||||
|
||||
use brahman_card::{Card, WitInterface, CARD_SCHEMA_VERSION};
|
||||
use thiserror::Error;
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
use tokio::net::UnixStream;
|
||||
|
||||
use crate::codec::{read_frame, write_frame};
|
||||
@@ -34,33 +35,55 @@ pub enum ClientError {
|
||||
/// y tiene su `SessionId`. Los `MatchEvent` recibidos durante operaciones
|
||||
/// request/response se buferean en `pending_events` y se obtienen vía
|
||||
/// [`Client::take_event`] o [`Client::await_event`].
|
||||
///
|
||||
/// Genérico sobre el transport (`AsyncRead + AsyncWrite + Unpin + Send`):
|
||||
/// funciona indistintamente sobre `UnixStream` (path local) o sobre un
|
||||
/// stream libp2p wrapped con `tokio_util::compat` (path remoto, vía
|
||||
/// `brahman_handshake::network`).
|
||||
#[derive(Debug)]
|
||||
pub struct Client {
|
||||
stream: UnixStream,
|
||||
pub struct Client<S = UnixStream> {
|
||||
stream: S,
|
||||
session: SessionId,
|
||||
server_info: HelloAck,
|
||||
pending_events: VecDeque<MatchEvent>,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
/// Conecta como módulo agnóstico (sin WIT). Equivalente a
|
||||
/// `connect_with(path, card, None)`.
|
||||
impl Client<UnixStream> {
|
||||
/// Conecta como módulo agnóstico (sin WIT) sobre Unix socket.
|
||||
/// Equivalente a `connect_with(path, card, None)`.
|
||||
pub async fn connect(path: impl AsRef<Path>, card: Card) -> Result<Self, ClientError> {
|
||||
Self::connect_with(path, card, None).await
|
||||
}
|
||||
|
||||
/// Conecta al socket enviando Hello con la Card dada y opcionalmente
|
||||
/// una `WitInterface` ya extraída. Si `wit` es `Some`, el server
|
||||
/// registra el módulo como "consciente".
|
||||
/// Conecta al socket Unix enviando Hello con la Card dada y
|
||||
/// opcionalmente una `WitInterface` ya extraída. Si `wit` es `Some`,
|
||||
/// el server registra el módulo como "consciente".
|
||||
pub async fn connect_with(
|
||||
path: impl AsRef<Path>,
|
||||
card: Card,
|
||||
wit: Option<WitInterface>,
|
||||
) -> Result<Self, ClientError> {
|
||||
let stream = UnixStream::connect(path).await?;
|
||||
Self::connect_with_stream(stream, card, wit).await
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Client<S>
|
||||
where
|
||||
S: AsyncRead + AsyncWrite + Unpin + Send,
|
||||
{
|
||||
/// Constructor genérico: arranca el handshake sobre un stream
|
||||
/// arbitrario que ya esté abierto. Es el punto de entrada para
|
||||
/// transports alternativos (libp2p, in-memory para tests, etc.)
|
||||
/// que reusan toda la lógica del handshake post-stream-open.
|
||||
pub async fn connect_with_stream(
|
||||
mut stream: S,
|
||||
card: Card,
|
||||
wit: Option<WitInterface>,
|
||||
) -> Result<Self, ClientError> {
|
||||
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(),
|
||||
|
||||
Reference in New Issue
Block a user