swactor/crates/iroh-driver/src/edge_transport.rs

178 lines
5.6 KiB
Rust
Raw Normal View History

2026-07-22 07:50:53 +00:00
//! Driver-owned byte transport for ring-backed MVP edge protocols.
//!
//! This module deliberately owns only transport framing: one edge id preamble per
//! unidirectional stream, followed by opaque byte chunks. Object-record parsing,
//! ring ownership, and stage semantics stay in the MVP/dataplane crates.
use std::sync::Arc;
use distribution::types::NodeId;
use iroh::endpoint::Connection;
use iroh::{Endpoint, EndpointAddr};
use parking_lot::Mutex;
use tokio::io::AsyncWriteExt;
use tokio::runtime::Handle;
use tokio::sync::mpsc as tokio_mpsc;
pub const EDGE_ALPN: &[u8] = b"mvp/pipeline-edge/0";
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum EdgeTransportEvent {
StreamArrived {
peer: NodeId,
edge_id: u64,
stream_id: u64,
},
BytesRead {
peer: NodeId,
edge_id: u64,
stream_id: u64,
bytes: Vec<u8>,
},
StreamEnded {
peer: NodeId,
edge_id: u64,
stream_id: u64,
},
StreamFault {
peer: NodeId,
edge_id: Option<u64>,
stream_id: Option<u64>,
reason: EdgeTransportFault,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum EdgeTransportFault {
ReadError,
WriteError,
ProtocolError,
}
#[derive(Clone)]
pub struct EdgeSendHandle {
tx: tokio_mpsc::UnboundedSender<Vec<u8>>,
}
impl EdgeSendHandle {
pub fn send(&self, bytes: Vec<u8>) -> Result<(), String> {
self.tx
.send(bytes)
.map_err(|_| "edge sender task stopped".to_owned())
}
}
pub(crate) fn spawn_edge_send_pump(
handle: Handle,
endpoint: Endpoint,
peer: EndpointAddr,
edge_id: u64,
) -> Result<EdgeSendHandle, String> {
let (tx, mut rx) = tokio_mpsc::unbounded_channel::<Vec<u8>>();
let (ready_tx, ready_rx) = std::sync::mpsc::channel::<Result<(), String>>();
handle.spawn(async move {
let result: Result<(), String> = async {
let conn = endpoint
.connect(peer, EDGE_ALPN)
.await
.map_err(|e| format!("connect edge {edge_id}: {e}"))?;
let mut send = conn
.open_uni()
.await
.map_err(|e| format!("open edge stream {edge_id}: {e}"))?;
send.write_all(&encode_edge_preamble(edge_id))
.await
.map_err(|e| format!("write edge preamble {edge_id}: {e}"))?;
send.flush()
.await
.map_err(|e| format!("flush edge preamble {edge_id}: {e}"))?;
let _ = ready_tx.send(Ok(()));
while let Some(record) = rx.recv().await {
send.write_all(&record)
.await
.map_err(|e| format!("write edge record {edge_id}: {e}"))?;
send.flush()
.await
.map_err(|e| format!("flush edge record {edge_id}: {e}"))?;
}
send.finish()
.map_err(|e| format!("finish edge stream {edge_id}: {e}"))?;
Ok(())
}
.await;
if let Err(error) = result {
let _ = ready_tx.send(Err(error));
}
});
ready_rx
.recv()
.map_err(|e| format!("edge {edge_id} sender startup channel closed: {e}"))??;
Ok(EdgeSendHandle { tx })
}
pub(crate) fn spawn_edge_recv_pump(
handle: Handle,
conn: Connection,
peer: NodeId,
events: Arc<Mutex<Vec<EdgeTransportEvent>>>,
stream_group: u64,
) {
handle.spawn(async move {
let mut next_uni_stream_id = stream_group << 32;
while let Ok(mut recv) = conn.accept_uni().await {
next_uni_stream_id = next_uni_stream_id.saturating_add(1);
let current_stream_id = next_uni_stream_id;
let mut preamble = [0u8; 8];
if recv.read_exact(&mut preamble).await.is_err() {
events.lock().push(EdgeTransportEvent::StreamFault {
peer,
edge_id: None,
stream_id: Some(current_stream_id),
reason: EdgeTransportFault::ProtocolError,
});
continue;
}
let edge_id = u64::from_le_bytes(preamble);
events.lock().push(EdgeTransportEvent::StreamArrived {
peer,
edge_id,
stream_id: current_stream_id,
});
let mut chunk = vec![0u8; 4096];
loop {
match recv.read(&mut chunk).await {
Ok(Some(0)) | Ok(None) => {
events.lock().push(EdgeTransportEvent::StreamEnded {
peer,
edge_id,
stream_id: current_stream_id,
});
break;
}
Ok(Some(n)) => {
events.lock().push(EdgeTransportEvent::BytesRead {
peer,
edge_id,
stream_id: current_stream_id,
bytes: chunk[..n].to_vec(),
});
}
Err(_) => {
events.lock().push(EdgeTransportEvent::StreamFault {
peer,
edge_id: Some(edge_id),
stream_id: Some(current_stream_id),
reason: EdgeTransportFault::ReadError,
});
break;
}
}
}
}
});
}
fn encode_edge_preamble(edge_id: u64) -> [u8; 8] {
edge_id.to_le_bytes()
}