use crate::error::{NetworkError, NetworkResult};
use crate::identity::MachineId;
pub(crate) struct StreamAccept {
tx: tokio::sync::mpsc::Sender<PeerStream>,
rx: std::sync::Arc<tokio::sync::Mutex<tokio::sync::mpsc::Receiver<PeerStream>>>,
started: std::sync::atomic::AtomicBool,
}
impl StreamAccept {
pub(crate) fn new(capacity: usize) -> Self {
let (tx, rx) = tokio::sync::mpsc::channel(capacity);
Self {
tx,
rx: std::sync::Arc::new(tokio::sync::Mutex::new(rx)),
started: std::sync::atomic::AtomicBool::new(false),
}
}
pub(crate) fn sender(&self) -> &tokio::sync::mpsc::Sender<PeerStream> {
&self.tx
}
pub(crate) fn receiver(
&self,
) -> &std::sync::Arc<tokio::sync::Mutex<tokio::sync::mpsc::Receiver<PeerStream>>> {
&self.rx
}
pub(crate) fn start_once(&self) -> bool {
!self.started.swap(true, std::sync::atomic::Ordering::AcqRel)
}
}
pub(crate) fn stream_gate(
agent_id: &crate::identity::AgentId,
trust_decision: Option<crate::trust::TrustDecision>,
revoked_agent: bool,
revoked_machine: bool,
expired: bool,
) -> NetworkResult<()> {
use crate::trust::TrustDecision;
if revoked_agent || revoked_machine {
return Err(NetworkError::PeerRevoked {
agent_id: agent_id.0,
});
}
if expired {
return Err(NetworkError::PeerNotVerified {
agent_id: agent_id.0,
});
}
if trust_decision != Some(TrustDecision::Accept) {
return Err(NetworkError::PeerTrustRejected {
agent_id: agent_id.0,
});
}
Ok(())
}
pub const PREFIX_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StreamProtocol {
ForwardV1 = 0x01,
SocksV1 = 0x02,
ForwardV2 = 0x03,
}
impl StreamProtocol {
#[must_use]
pub fn from_u8(byte: u8) -> Option<Self> {
match byte {
0x01 => Some(Self::ForwardV1),
0x02 => Some(Self::SocksV1),
0x03 => Some(Self::ForwardV2),
_ => None,
}
}
#[must_use]
pub const fn as_u8(self) -> u8 {
self as u8
}
}
pub struct PeerStream {
agents: Vec<crate::identity::AgentId>,
peer: MachineId,
protocol: StreamProtocol,
send: ant_quic::HighLevelSendStream,
recv: ant_quic::HighLevelRecvStream,
}
impl PeerStream {
pub(crate) fn new(
agents: Vec<crate::identity::AgentId>,
peer: MachineId,
protocol: StreamProtocol,
send: ant_quic::HighLevelSendStream,
recv: ant_quic::HighLevelRecvStream,
) -> Self {
Self {
agents,
peer,
protocol,
send,
recv,
}
}
#[must_use]
pub fn agent(&self) -> crate::identity::AgentId {
self.agents[0]
}
#[must_use]
pub fn peer_agents(&self) -> &[crate::identity::AgentId] {
&self.agents
}
#[must_use]
pub fn peer(&self) -> MachineId {
self.peer
}
#[must_use]
pub fn protocol(&self) -> StreamProtocol {
self.protocol
}
pub fn send_mut(&mut self) -> &mut ant_quic::HighLevelSendStream {
&mut self.send
}
pub fn recv_mut(&mut self) -> &mut ant_quic::HighLevelRecvStream {
&mut self.recv
}
pub fn into_split(self) -> (ant_quic::HighLevelSendStream, ant_quic::HighLevelRecvStream) {
(self.send, self.recv)
}
}
pub(crate) async fn write_protocol_prefix(
send: &mut ant_quic::HighLevelSendStream,
protocol: StreamProtocol,
) -> NetworkResult<()> {
send.write_all(&[protocol.as_u8()])
.await
.map_err(|e| NetworkError::StreamError(format!("write protocol prefix: {e}")))
}
pub(crate) async fn read_protocol_prefix(
recv: &mut ant_quic::HighLevelRecvStream,
) -> NetworkResult<StreamProtocol> {
let mut buf = [0u8; 1];
recv.read_exact(&mut buf)
.await
.map_err(|e| NetworkError::StreamError(format!("read protocol prefix: {e}")))?;
StreamProtocol::from_u8(buf[0]).ok_or(NetworkError::StreamProtocolUnknown {
protocol_byte: buf[0],
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn protocol_prefix_round_trips() {
for p in [
StreamProtocol::ForwardV1,
StreamProtocol::SocksV1,
StreamProtocol::ForwardV2,
] {
assert_eq!(StreamProtocol::from_u8(p.as_u8()), Some(p));
}
}
#[test]
fn reserved_and_unassigned_bytes_are_unknown() {
for byte in 0x00u8..=0xFF {
let parsed = StreamProtocol::from_u8(byte);
match byte {
0x01..=0x03 => {
assert!(parsed.is_some(), "byte {byte:#x} should parse")
}
_ => assert_eq!(parsed, None, "byte {byte:#x} must be unknown"),
}
}
}
#[test]
fn stream_gate_matrix() {
use crate::error::NetworkError;
use crate::identity::AgentId;
use crate::trust::TrustDecision;
let agent = AgentId([9u8; 32]);
let accept = Some(TrustDecision::Accept);
let accept_with_flag = Some(TrustDecision::AcceptWithFlag);
let reject = Some(TrustDecision::RejectBlocked);
assert!(stream_gate(&agent, accept, false, false, false).is_ok());
assert!(matches!(
stream_gate(&agent, accept, true, false, false),
Err(NetworkError::PeerRevoked { agent_id }) if agent_id == agent.0
));
assert!(matches!(
stream_gate(&agent, accept, false, true, false),
Err(NetworkError::PeerRevoked { .. })
));
assert!(matches!(
stream_gate(&agent, reject, true, false, false),
Err(NetworkError::PeerRevoked { .. })
));
for decision in [accept_with_flag, reject, None] {
assert!(
matches!(
stream_gate(&agent, decision, false, false, false),
Err(NetworkError::PeerTrustRejected { agent_id }) if agent_id == agent.0
),
"decision {decision:?} must deny (only plain Accept passes)"
);
}
}
#[test]
fn stream_gate_rejects_expired_cert() {
use crate::error::NetworkError;
use crate::identity::AgentId;
use crate::trust::TrustDecision;
let agent = AgentId([9u8; 32]);
let accept = Some(TrustDecision::Accept);
assert!(matches!(
stream_gate(&agent, accept, false, false, true),
Err(NetworkError::PeerNotVerified { agent_id }) if agent_id == agent.0
));
assert!(stream_gate(&agent, accept, false, false, false).is_ok());
assert!(matches!(
stream_gate(&agent, accept, true, false, true),
Err(NetworkError::PeerRevoked { agent_id }) if agent_id == agent.0
));
}
}