use std::collections::HashMap;
use std::net::{IpAddr, SocketAddr};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use serde::{Deserialize, Serialize};
use tokio::net::{TcpListener, TcpStream};
use tokio_util::sync::CancellationToken;
use crate::connect::gate::ConnectDenialReason;
use crate::connect::{evaluate_connect_gate, ConnectDiagnostics, ConnectPolicy};
use crate::error::{NetworkError, NetworkResult};
use crate::identity::{AgentId, MachineId};
use crate::streams::PeerStream;
use crate::trust::TrustDecision;
use ant_quic::{HighLevelRecvStream, HighLevelSendStream};
const RESP_CONNECTED: u8 = 0x01;
const RESP_DENIED: u8 = 0x00;
const MAX_HEADER_BYTES: u32 = 256;
const CONNECT_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
const HEADER_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
const MAX_INBOUND_STREAMS: usize = 256;
const MAX_OUTBOUND_STREAMS: usize = 256;
const MAX_STREAMS_PER_PEER: u32 = 32;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ForwardHeader {
pub target_host: String,
pub target_port: u16,
}
impl ForwardHeader {
#[must_use]
pub fn encode(&self) -> Vec<u8> {
let body = bincode::serialize(self).unwrap_or_default();
let mut out = Vec::with_capacity(4 + body.len());
out.extend_from_slice(&(body.len() as u32).to_be_bytes());
out.extend_from_slice(&body);
out
}
pub fn decode(buf: &[u8]) -> Result<(Self, usize), ForwardError> {
if buf.len() < 4 {
return Err(ForwardError::Truncated);
}
let len = u32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]]);
if len > MAX_HEADER_BYTES {
return Err(ForwardError::Oversize(len));
}
let end = 4 + len as usize;
if buf.len() < end {
return Err(ForwardError::Truncated);
}
let header: Self =
bincode::deserialize(&buf[4..end]).map_err(|e| ForwardError::Decode(e.to_string()))?;
Ok((header, end))
}
}
#[must_use]
fn encode_response_connected() -> [u8; 1] {
[RESP_CONNECTED]
}
#[must_use]
fn encode_response_denied(reason: ConnectDenialReason) -> Vec<u8> {
let reason_bytes = serde_json::to_vec(&reason).unwrap_or_default();
let mut out = Vec::with_capacity(1 + 4 + reason_bytes.len());
out.push(RESP_DENIED);
out.extend_from_slice(&(reason_bytes.len() as u32).to_be_bytes());
out.extend_from_slice(&reason_bytes);
out
}
#[must_use]
fn response_connected(byte: u8) -> Option<bool> {
match byte {
RESP_CONNECTED => Some(true),
RESP_DENIED => Some(false),
_ => None,
}
}
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum ForwardError {
#[error("truncated forward frame")]
Truncated,
#[error("oversize forward frame: {0} bytes")]
Oversize(u32),
#[error("forward frame decode failed: {0}")]
Decode(String),
#[error("target host is not a numeric loopback IP: {0}")]
NotLoopbackTarget(String),
}
fn resolve_loopback_target(
target_host: &str,
target_port: u16,
) -> Result<SocketAddr, ForwardError> {
let ip: IpAddr = target_host
.parse()
.map_err(|_| ForwardError::NotLoopbackTarget(target_host.to_string()))?;
if !crate::connect::is_loopback(ip) {
return Err(ForwardError::NotLoopbackTarget(target_host.to_string()));
}
Ok(SocketAddr::new(ip, target_port))
}
fn decide_inbound(
header: &ForwardHeader,
policy: &ConnectPolicy,
agent_id: &AgentId,
machine_id: &MachineId,
) -> Result<SocketAddr, ConnectDenialReason> {
let target = resolve_loopback_target(&header.target_host, header.target_port)
.map_err(|_| ConnectDenialReason::TargetNotLoopback)?;
evaluate_connect_gate(
true,
Some(TrustDecision::Accept),
policy,
agent_id,
machine_id,
&target,
)?;
Ok(target)
}
#[derive(Debug, Default)]
pub struct ForwardDiagnostics {
connect_failed: AtomicU64,
active_streams: AtomicU64,
streams_over_cap: AtomicU64,
revoked_mid_flight: AtomicU64,
header_timeout: AtomicU64,
}
impl ForwardDiagnostics {
pub fn record_connect_failed(&self) {
self.connect_failed.fetch_add(1, Ordering::Relaxed);
}
pub fn enter_stream(&self) -> u64 {
self.active_streams.fetch_add(1, Ordering::Relaxed) + 1
}
pub fn leave_stream(&self) {
self.active_streams.fetch_sub(1, Ordering::Relaxed);
}
pub fn record_over_cap(&self) {
self.streams_over_cap.fetch_add(1, Ordering::Relaxed);
}
pub fn record_revoked_mid_flight(&self) {
self.revoked_mid_flight.fetch_add(1, Ordering::Relaxed);
}
pub fn record_header_timeout(&self) {
self.header_timeout.fetch_add(1, Ordering::Relaxed);
}
#[must_use]
pub fn connect_failed(&self) -> u64 {
self.connect_failed.load(Ordering::Relaxed)
}
#[must_use]
pub fn active_streams(&self) -> u64 {
self.active_streams.load(Ordering::Relaxed)
}
#[must_use]
pub fn streams_over_cap(&self) -> u64 {
self.streams_over_cap.load(Ordering::Relaxed)
}
#[must_use]
pub fn revoked_mid_flight(&self) -> u64 {
self.revoked_mid_flight.load(Ordering::Relaxed)
}
#[must_use]
pub fn header_timeout(&self) -> u64 {
self.header_timeout.load(Ordering::Relaxed)
}
}
pub(crate) async fn handle_inbound(
mut stream: PeerStream,
policy: Arc<ConnectPolicy>,
connect_diag: Arc<ConnectDiagnostics>,
fwd_diag: Arc<ForwardDiagnostics>,
revocation_set: Arc<tokio::sync::RwLock<crate::revocation::RevocationSet>>,
) {
let agent_id = stream.agent();
let machine_id = stream.peer();
let peer = machine_id;
{
let revoked = revocation_set.read().await;
if revoked.is_agent_revoked(&agent_id) || revoked.is_machine_revoked(&machine_id) {
fwd_diag.record_revoked_mid_flight();
tracing::info!(
target: "x0x::forward",
agent = %hex::encode(agent_id.as_bytes()),
machine = %hex::encode(peer.as_bytes()),
outcome = "drop_revoked_mid_flight",
"inbound forward: peer revoked after accept — dropping before header read"
);
return;
}
}
let header =
match tokio::time::timeout(HEADER_READ_TIMEOUT, read_header(stream.recv_mut())).await {
Ok(Ok(h)) => h,
Ok(Err(e)) => {
tracing::info!(
target: "x0x::forward",
peer = %hex::encode(peer.as_bytes()),
error = %e,
"inbound forward: header read failed — closing stream"
);
return;
}
Err(_) => {
fwd_diag.record_header_timeout();
tracing::info!(
target: "x0x::forward",
peer = %hex::encode(peer.as_bytes()),
"inbound forward: header read timed out — resetting stream"
);
return;
}
};
let target = match decide_inbound(&header, &policy, &agent_id, &machine_id) {
Ok(addr) => addr,
Err(reason) => {
connect_diag.record_denied(reason);
let _ = stream
.send_mut()
.write_all(&encode_response_denied(reason))
.await;
tracing::info!(
target: "x0x::forward",
peer = %hex::encode(peer.as_bytes()),
?reason,
target = %header.target_host,
port = header.target_port,
"inbound forward denied at connect gate"
);
return;
}
};
if !target.ip().is_loopback() {
connect_diag.record_denied(ConnectDenialReason::TargetNotLoopback);
let _ = stream
.send_mut()
.write_all(&encode_response_denied(
ConnectDenialReason::TargetNotLoopback,
))
.await;
return;
}
let local = match tokio::time::timeout(CONNECT_TIMEOUT, TcpStream::connect(target)).await {
Ok(Ok(tcp)) => tcp,
Ok(Err(e)) => {
fwd_diag.record_connect_failed();
tracing::info!(
target: "x0x::forward",
peer = %hex::encode(peer.as_bytes()),
target = %target,
error = %e,
"inbound forward: local connect failed"
);
return;
}
Err(_) => {
fwd_diag.record_connect_failed();
tracing::info!(
target: "x0x::forward",
peer = %hex::encode(peer.as_bytes()),
target = %target,
"inbound forward: local connect timed out"
);
return;
}
};
connect_diag.record_allowed();
if stream
.send_mut()
.write_all(&encode_response_connected())
.await
.is_err()
{
return;
}
let (send, recv) = stream.into_split();
fwd_diag.enter_stream();
let _guard = StreamLeaveGuard(Arc::clone(&fwd_diag));
bridge(local, send, recv).await;
}
struct StreamLeaveGuard(Arc<ForwardDiagnostics>);
impl Drop for StreamLeaveGuard {
fn drop(&mut self) {
self.0.leave_stream();
}
}
async fn bridge(tcp: TcpStream, mut send: HighLevelSendStream, mut recv: HighLevelRecvStream) {
let (mut tcp_read, mut tcp_write) = tcp.into_split();
let to_stream = tokio::io::copy(&mut tcp_read, &mut send);
let from_stream = tokio::io::copy(&mut recv, &mut tcp_write);
let _ = tokio::join!(to_stream, from_stream);
}
async fn read_header<R: tokio::io::AsyncRead + Unpin>(
r: &mut R,
) -> Result<ForwardHeader, ForwardError> {
use tokio::io::AsyncReadExt;
let mut len_buf = [0u8; 4];
r.read_exact(&mut len_buf)
.await
.map_err(|_| ForwardError::Truncated)?;
let len = u32::from_be_bytes(len_buf);
if len > MAX_HEADER_BYTES {
return Err(ForwardError::Oversize(len));
}
let mut body = vec![0u8; len as usize];
r.read_exact(&mut body)
.await
.map_err(|_| ForwardError::Truncated)?;
bincode::deserialize(&body).map_err(|e| ForwardError::Decode(e.to_string()))
}
async fn write_header<W: tokio::io::AsyncWrite + Unpin>(
w: &mut W,
header: &ForwardHeader,
) -> Result<(), NetworkError> {
use tokio::io::AsyncWriteExt;
let frame = header.encode();
w.write_all(&frame)
.await
.map_err(|e| NetworkError::StreamError(format!("write forward header: {e}")))
}
#[derive(Debug, Clone, Serialize)]
pub struct ForwardSpec {
pub local_addr: SocketAddr,
pub peer_agent: AgentId,
pub target_host: String,
pub target_port: u16,
}
impl ForwardSpec {
#[must_use]
pub fn peer_agent_hex(&self) -> String {
hex::encode(self.peer_agent.as_bytes())
}
}
struct ForwardEntry {
spec: ForwardSpec,
cancel: CancellationToken,
}
pub struct ForwardService {
agent: Arc<crate::Agent>,
policy: Arc<ConnectPolicy>,
connect_diag: Arc<ConnectDiagnostics>,
fwd_diag: Arc<ForwardDiagnostics>,
forwards: std::sync::Mutex<Vec<ForwardEntry>>,
inbound_token: CancellationToken,
inbound_permits: Arc<tokio::sync::Semaphore>,
outbound_permits: Arc<tokio::sync::Semaphore>,
per_peer: Arc<std::sync::Mutex<HashMap<AgentId, u32>>>,
revocation_set: Arc<tokio::sync::RwLock<crate::revocation::RevocationSet>>,
}
struct PeerSlot {
peer: AgentId,
map: Arc<std::sync::Mutex<HashMap<AgentId, u32>>>,
}
impl Drop for PeerSlot {
fn drop(&mut self) {
if let Ok(mut m) = self.map.lock() {
if let Some(count) = m.get_mut(&self.peer) {
*count = count.saturating_sub(1);
if *count == 0 {
m.remove(&self.peer);
}
}
}
}
}
struct Admission {
_permit: tokio::sync::OwnedSemaphorePermit,
_slot: PeerSlot,
}
fn admit_to(
permits: &Arc<tokio::sync::Semaphore>,
per_peer: &Arc<std::sync::Mutex<HashMap<AgentId, u32>>>,
peer: AgentId,
) -> Option<Admission> {
let permit = permits.clone().try_acquire_owned().ok()?;
let slot = try_peer_slot(per_peer, peer)?;
Some(Admission {
_permit: permit,
_slot: slot,
})
}
fn try_peer_slot(
map: &Arc<std::sync::Mutex<HashMap<AgentId, u32>>>,
peer: AgentId,
) -> Option<PeerSlot> {
let Ok(mut m) = map.lock() else {
return None;
};
let count = m.entry(peer).or_insert(0);
if *count >= MAX_STREAMS_PER_PEER {
return None;
}
*count += 1;
Some(PeerSlot {
peer,
map: Arc::clone(map),
})
}
impl ForwardService {
#[must_use]
pub fn new(
agent: Arc<crate::Agent>,
policy: Arc<ConnectPolicy>,
connect_diag: Arc<ConnectDiagnostics>,
) -> Self {
let revocation_set = agent.revocation_set();
Self {
agent,
policy,
connect_diag,
fwd_diag: Arc::new(ForwardDiagnostics::default()),
forwards: std::sync::Mutex::new(Vec::new()),
inbound_token: CancellationToken::new(),
inbound_permits: Arc::new(tokio::sync::Semaphore::new(MAX_INBOUND_STREAMS)),
outbound_permits: Arc::new(tokio::sync::Semaphore::new(MAX_OUTBOUND_STREAMS)),
per_peer: Arc::new(std::sync::Mutex::new(HashMap::new())),
revocation_set,
}
}
#[must_use]
pub fn diagnostics(&self) -> &ForwardDiagnostics {
&self.fwd_diag
}
fn admit(&self, peer: AgentId, inbound: bool) -> Option<Admission> {
let permits = if inbound {
&self.inbound_permits
} else {
&self.outbound_permits
};
admit_to(permits, &self.per_peer, peer)
}
pub fn spawn_inbound(self: &Arc<Self>) {
let this = Arc::clone(self);
let token = self.inbound_token.clone();
tokio::spawn(async move {
tracing::info!(target: "x0x::forward", "inbound forward consumer started");
loop {
let stream = tokio::select! {
_ = token.cancelled() => break,
s = this.agent.next_incoming_stream() => match s {
Some(s) => s,
None => break,
},
};
if stream.protocol() != crate::streams::StreamProtocol::ForwardV1 {
tracing::debug!(
target: "x0x::forward",
protocol = ?stream.protocol(),
"non-forward inbound stream — not handled by the forwarder"
);
continue;
}
let peer_agent = stream.agent();
let admission = match this.admit(peer_agent, true) {
Some(a) => a,
None => {
this.fwd_diag.record_over_cap();
tracing::info!(
target: "x0x::forward",
agent = %hex::encode(peer_agent.as_bytes()),
"inbound forward refused: concurrency cap reached — resetting"
);
continue;
}
};
let this = Arc::clone(&this);
tokio::spawn(async move {
let _admission = admission;
this.fwd_diag.enter_stream();
let _leave = StreamLeaveGuard(Arc::clone(&this.fwd_diag));
handle_inbound(
stream,
Arc::clone(&this.policy),
Arc::clone(&this.connect_diag),
Arc::clone(&this.fwd_diag),
Arc::clone(&this.revocation_set),
)
.await;
});
}
});
}
pub async fn add_forward(&self, spec: ForwardSpec) -> NetworkResult<SocketAddr> {
let listener = TcpListener::bind(spec.local_addr).await.map_err(|e| {
NetworkError::ConnectionFailed(format!("bind {}: {e}", spec.local_addr))
})?;
let bound = listener
.local_addr()
.map_err(|e| NetworkError::ConnectionFailed(format!("local_addr: {e}")))?;
let cancel = CancellationToken::new();
if let Ok(mut forwards) = self.forwards.lock() {
forwards.push(ForwardEntry {
spec: spec.clone(),
cancel: cancel.clone(),
});
}
let agent = Arc::clone(&self.agent);
let fwd_diag = Arc::clone(&self.fwd_diag);
let outbound_permits = Arc::clone(&self.outbound_permits);
let per_peer = Arc::clone(&self.per_peer);
let peer_agent = spec.peer_agent;
let header = ForwardHeader {
target_host: spec.target_host,
target_port: spec.target_port,
};
tokio::spawn(async move {
tracing::info!(
target: "x0x::forward",
local = %bound,
peer = %hex::encode(peer_agent.as_bytes()),
"outbound forward listener started"
);
loop {
let (tcp, _) = tokio::select! {
_ = cancel.cancelled() => break,
a = listener.accept() => match a {
Ok(a) => a,
Err(e) => {
tracing::warn!(
target: "x0x::forward",
local = %bound,
error = %e,
"accept failed; listener stopping"
);
break;
}
},
};
let admission = match admit_to(&outbound_permits, &per_peer, peer_agent) {
Some(a) => a,
None => {
fwd_diag.record_over_cap();
tracing::info!(
target: "x0x::forward",
local = %bound,
"outbound forward refused: concurrency cap reached — closing local TCP"
);
continue;
}
};
let agent = Arc::clone(&agent);
let fwd_diag = Arc::clone(&fwd_diag);
let header = header.clone();
tokio::spawn(async move {
let _admission = admission;
fwd_diag.enter_stream();
let _guard = StreamLeaveGuard(Arc::clone(&fwd_diag));
drive_outbound(agent, peer_agent, header, tcp).await;
});
}
});
Ok(bound)
}
#[must_use]
pub fn list_forwards(&self) -> Vec<ForwardSpec> {
self.forwards
.lock()
.map(|f| f.iter().map(|e| e.spec.clone()).collect())
.unwrap_or_default()
}
pub fn remove_forward(&self, local_addr: SocketAddr) -> bool {
let mut removed = false;
if let Ok(mut forwards) = self.forwards.lock() {
if let Some(pos) = forwards
.iter()
.position(|e| e.spec.local_addr == local_addr)
{
forwards[pos].cancel.cancel();
forwards.remove(pos);
removed = true;
}
}
removed
}
pub fn shutdown(&self) {
self.inbound_token.cancel();
if let Ok(forwards) = self.forwards.lock() {
for entry in forwards.iter() {
entry.cancel.cancel();
}
}
}
}
async fn drive_outbound(
agent: Arc<crate::Agent>,
peer_agent: AgentId,
header: ForwardHeader,
tcp: TcpStream,
) {
let mut stream = match agent
.open_peer_stream(&peer_agent, crate::streams::StreamProtocol::ForwardV1)
.await
{
Ok(s) => s,
Err(e) => {
tracing::info!(
target: "x0x::forward",
peer = %hex::encode(peer_agent.as_bytes()),
error = %e,
"outbound forward: could not open peer stream"
);
return;
}
};
if write_header(stream.send_mut(), &header).await.is_err() {
return;
}
let mut resp = [0u8; 1];
if stream.recv_mut().read_exact(&mut resp).await.is_err() {
return;
}
match response_connected(resp[0]) {
Some(true) => {
let (send, recv) = stream.into_split();
bridge(tcp, send, recv).await;
}
Some(false) => {
tracing::info!(
target: "x0x::forward",
peer = %hex::encode(peer_agent.as_bytes()),
"outbound forward: peer denied at connect gate — closing local TCP"
);
}
None => {
tracing::info!(
target: "x0x::forward",
"outbound forward: malformed connect response — closing local TCP"
);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::connect::acl::{ConnectAcl, ConnectAllowEntry};
fn header(host: &str, port: u16) -> ForwardHeader {
ForwardHeader {
target_host: host.to_string(),
target_port: port,
}
}
#[test]
fn header_frame_round_trips() {
let h = header("127.0.0.1", 22);
let bytes = h.encode();
let (decoded, n) = ForwardHeader::decode(&bytes).expect("decode");
assert_eq!(decoded, h);
assert_eq!(n, bytes.len());
}
fn h_frame_with_len(len: u32, body: &[u8]) -> Vec<u8> {
let mut out = len.to_be_bytes().to_vec();
out.extend_from_slice(body);
out
}
#[test]
fn header_decode_rejects_truncated_and_oversize() {
assert_eq!(
ForwardHeader::decode(&[0u8, 0]).unwrap_err(),
ForwardError::Truncated
);
let bytes = h_frame_with_len(10, &[]);
assert_eq!(
ForwardHeader::decode(&bytes).unwrap_err(),
ForwardError::Truncated
);
let bytes = h_frame_with_len(MAX_HEADER_BYTES + 1, &[]);
assert_eq!(
ForwardHeader::decode(&bytes).unwrap_err(),
ForwardError::Oversize(MAX_HEADER_BYTES + 1)
);
}
#[test]
fn resolve_rejects_hostnames_and_non_loopback() {
assert!(resolve_loopback_target("127.0.0.1", 22).is_ok());
assert!(resolve_loopback_target("::1", 22).is_ok());
assert_eq!(
resolve_loopback_target("localhost", 22).unwrap_err(),
ForwardError::NotLoopbackTarget("localhost".to_string())
);
assert_eq!(
resolve_loopback_target("10.0.0.1", 22).unwrap_err(),
ForwardError::NotLoopbackTarget("10.0.0.1".to_string())
);
}
fn policy_with_allow(agent: AgentId, machine: MachineId, target: SocketAddr) -> ConnectPolicy {
ConnectPolicy::Enabled(ConnectAcl {
loaded_from: "test".into(),
loaded_at_unix_ms: 0,
allow: vec![ConnectAllowEntry {
description: None,
agent_id: agent,
machine_id: machine,
targets: vec![target],
}],
})
}
#[test]
fn decide_inbound_matrix() {
let agent = AgentId([1u8; 32]);
let machine = MachineId([2u8; 32]);
let target: SocketAddr = "127.0.0.1:22".parse().unwrap();
let disabled = ConnectPolicy::default();
assert_eq!(
decide_inbound(&header("127.0.0.1", 22), &disabled, &agent, &machine).unwrap_err(),
ConnectDenialReason::ConnectDisabled
);
let other_agent = AgentId([9u8; 32]);
let policy = policy_with_allow(other_agent, machine, target);
assert_eq!(
decide_inbound(&header("127.0.0.1", 22), &policy, &agent, &machine).unwrap_err(),
ConnectDenialReason::AgentMachineNotInAcl
);
let policy = policy_with_allow(agent, machine, "127.0.0.1:2222".parse().unwrap());
assert_eq!(
decide_inbound(&header("127.0.0.1", 22), &policy, &agent, &machine).unwrap_err(),
ConnectDenialReason::TargetNotAllowed
);
let policy = policy_with_allow(agent, machine, target);
assert_eq!(
decide_inbound(&header("127.0.0.1", 22), &policy, &agent, &machine).unwrap(),
target
);
let policy = policy_with_allow(agent, machine, target);
assert_eq!(
decide_inbound(&header("10.0.0.1", 22), &policy, &agent, &machine).unwrap_err(),
ConnectDenialReason::TargetNotLoopback
);
}
#[test]
fn response_frames_are_well_formed() {
let connected = encode_response_connected();
assert_eq!(response_connected(connected[0]), Some(true));
let denied = encode_response_denied(ConnectDenialReason::ConnectDisabled);
assert_eq!(denied[0], RESP_DENIED);
let len = u32::from_be_bytes([denied[1], denied[2], denied[3], denied[4]]) as usize;
assert_eq!(
denied.len(),
5 + len,
"denied frame length must match the prefix"
);
assert!(len > 0, "a serialized ConnectDenialReason is non-empty");
assert_eq!(response_connected(RESP_DENIED), Some(false));
assert_eq!(response_connected(0xFF), None);
}
#[test]
fn admit_enforces_global_and_per_peer_caps() {
let global = Arc::new(tokio::sync::Semaphore::new(2));
let per_peer: Arc<std::sync::Mutex<HashMap<AgentId, u32>>> =
Arc::new(std::sync::Mutex::new(HashMap::new()));
let peer_a = AgentId([1u8; 32]);
let peer_b = AgentId([2u8; 32]);
let a1 = admit_to(&global, &per_peer, peer_a).expect("first admit");
let a2 = admit_to(&global, &per_peer, peer_b).expect("second admit");
assert!(
admit_to(&global, &per_peer, peer_a).is_none(),
"global cap must refuse the third admit"
);
drop(a2);
let a3 = admit_to(&global, &per_peer, peer_b).expect("readmit after release");
drop(a1);
drop(a3);
let big = Arc::new(tokio::sync::Semaphore::new(1024));
let pp: Arc<std::sync::Mutex<HashMap<AgentId, u32>>> =
Arc::new(std::sync::Mutex::new(HashMap::new()));
let mut held = Vec::new();
for _ in 0..MAX_STREAMS_PER_PEER {
held.push(admit_to(&big, &pp, peer_a).expect("under per-peer cap"));
}
assert!(
admit_to(&big, &pp, peer_a).is_none(),
"per-peer cap must refuse the (N+1)th admit for the same peer"
);
assert!(
admit_to(&big, &pp, peer_b).is_some(),
"a second peer must still be admitted under its own per-peer cap"
);
held.pop();
assert!(
admit_to(&big, &pp, peer_a).is_some(),
"per-peer cap must re-admit after a slot is released"
);
}
}