#![cfg(feature = "net")]
use std::net::SocketAddr;
use std::sync::Arc;
use std::time::Duration;
use net::adapter::net::behavior::capability::CapabilitySet;
use net::adapter::net::{
ChannelConfig, ChannelConfigRegistry, ChannelId, ChannelName, EntityKeypair, MeshNode,
MeshNodeConfig, OriginBinding, SocketBufferConfig,
};
const TEST_BUFFER_SIZE: usize = 256 * 1024;
const PSK: [u8; 32] = [0x37u8; 32];
const PREFIX: &str = "svc.replies.";
fn test_config() -> MeshNodeConfig {
let addr: SocketAddr = "127.0.0.1:0".parse().unwrap();
let mut cfg = MeshNodeConfig::new(addr, PSK)
.with_heartbeat_interval(Duration::from_millis(200))
.with_session_timeout(Duration::from_secs(5))
.with_handshake(3, Duration::from_secs(2));
cfg.socket_buffers = SocketBufferConfig {
send_buffer_size: TEST_BUFFER_SIZE,
recv_buffer_size: TEST_BUFFER_SIZE,
};
cfg
}
struct Node {
mesh: Arc<MeshNode>,
keypair: EntityKeypair,
registry: Arc<ChannelConfigRegistry>,
}
impl Node {
fn own_reply_channel(&self) -> ChannelName {
ChannelName::new(&format!(
"{PREFIX}{:016x}",
self.keypair.entity_id().origin_hash()
))
.unwrap()
}
}
async fn build_node() -> Node {
let keypair = EntityKeypair::generate();
let mut node = MeshNode::new(keypair.clone(), test_config())
.await
.expect("MeshNode::new");
let registry = Arc::new(ChannelConfigRegistry::new());
node.set_channel_configs(registry.clone());
Node {
mesh: Arc::new(node),
keypair,
registry,
}
}
fn register_bound_prefix(node: &Node) {
let sentinel = ChannelName::new("svc.replies.prefix").unwrap();
node.registry.insert_prefix(
PREFIX,
ChannelConfig::new(ChannelId::new(sentinel))
.with_subscriber_origin_binding(OriginBinding::OriginHashHex16),
);
}
async fn handshake(initiator: &Arc<MeshNode>, responder: &Arc<MeshNode>) {
let i_id = initiator.node_id();
let r_id = responder.node_id();
let r_pub = *responder.public_key();
let r_addr = responder.local_addr();
let r_clone = responder.clone();
let accept = tokio::spawn(async move { r_clone.accept(i_id).await });
initiator
.connect(r_addr, &r_pub, r_id)
.await
.expect("connect failed");
accept
.await
.expect("accept task panicked")
.expect("accept failed");
}
async fn wait_until<F: FnMut() -> bool>(mut cond: F) -> bool {
let deadline = tokio::time::Instant::now() + Duration::from_secs(2);
while tokio::time::Instant::now() < deadline {
if cond() {
return true;
}
tokio::time::sleep(Duration::from_millis(25)).await;
}
cond()
}
async fn pinned_pair() -> (Node, Node, Node) {
let publisher = build_node().await;
let alice = build_node().await;
let mallory = build_node().await;
register_bound_prefix(&publisher);
handshake(&alice.mesh, &publisher.mesh).await;
handshake(&mallory.mesh, &publisher.mesh).await;
publisher.mesh.start();
alice.mesh.start();
mallory.mesh.start();
for n in [&alice, &mallory] {
n.mesh
.announce_capabilities(CapabilitySet::new())
.await
.expect("announce");
}
let learned = wait_until(|| {
publisher
.mesh
.peer_entity_id(alice.mesh.node_id())
.is_some()
&& publisher
.mesh
.peer_entity_id(mallory.mesh.node_id())
.is_some()
})
.await;
assert!(learned, "publisher never pinned both subscriber entities");
(publisher, alice, mallory)
}
#[tokio::test]
async fn own_reply_channel_subscribe_is_admitted() {
let (publisher, alice, _mallory) = pinned_pair().await;
alice
.mesh
.subscribe_channel(publisher.mesh.node_id(), alice.own_reply_channel())
.await
.expect("a peer must be able to subscribe to its own reply channel");
}
#[tokio::test]
async fn other_peers_reply_channel_subscribe_is_rejected() {
let (publisher, alice, mallory) = pinned_pair().await;
let victim_channel = alice.own_reply_channel();
let result = mallory
.mesh
.subscribe_channel(publisher.mesh.node_id(), victim_channel.clone())
.await;
assert!(
result.is_err(),
"a peer must not be able to subscribe to another peer's origin-bound \
reply channel (H3 cross-caller response disclosure)"
);
assert!(
!publisher
.mesh
.roster()
.is_subscribed(mallory.mesh.node_id(), &ChannelId::new(victim_channel)),
"rejected subscriber must not appear in the roster"
);
}
#[tokio::test]
async fn binding_still_admits_each_peer_to_its_own_channel() {
let (publisher, _alice, mallory) = pinned_pair().await;
mallory
.mesh
.subscribe_channel(publisher.mesh.node_id(), mallory.own_reply_channel())
.await
.expect("each peer keeps access to the channel naming its own origin");
}
#[tokio::test]
async fn malformed_suffix_is_rejected() {
let (publisher, alice, _mallory) = pinned_pair().await;
for suffix in ["deadbeef", "notahexvalueatall", "00000000000000000"] {
let name = ChannelName::new(&format!("{PREFIX}{suffix}")).unwrap();
let result = alice
.mesh
.subscribe_channel(publisher.mesh.node_id(), name)
.await;
assert!(
result.is_err(),
"suffix {suffix:?} is not this peer's origin and must be rejected"
);
}
}