use std::time::Duration;
use chacha20poly1305::{
aead::{Aead, AeadCore, KeyInit, OsRng},
ChaCha20Poly1305, Key, Nonce,
};
use peasub::{CoverStrategy, Node, NodeConfig, ID_SIZE};
const PAYLOAD: usize = 256 - ID_SIZE; const NONCE: usize = 12;
const TAG: usize = 16;
const INNER: usize = PAYLOAD - NONCE - TAG; const LEN_HDR: usize = 2;
const MAX_DATA: usize = INNER - LEN_HDR;
fn seal(cipher: &ChaCha20Poly1305, data: &[u8]) -> Vec<u8> {
assert!(data.len() <= MAX_DATA, "payload exceeds {MAX_DATA} bytes");
let mut inner = vec![0u8; INNER]; inner[..LEN_HDR].copy_from_slice(&(data.len() as u16).to_be_bytes());
inner[LEN_HDR..LEN_HDR + data.len()].copy_from_slice(data);
let nonce = ChaCha20Poly1305::generate_nonce(&mut OsRng);
let ct = cipher.encrypt(&nonce, inner.as_ref()).expect("encrypt");
let mut out = Vec::with_capacity(PAYLOAD); out.extend_from_slice(&nonce);
out.extend_from_slice(&ct);
out
}
fn open(cipher: &ChaCha20Poly1305, frame: &[u8]) -> Option<Vec<u8>> {
let payload = frame.get(ID_SIZE..)?; let nonce = Nonce::from_slice(payload.get(..NONCE)?);
let inner = cipher.decrypt(nonce, payload.get(NONCE..)?).ok()?;
let len = u16::from_be_bytes(inner.get(..LEN_HDR)?.try_into().ok()?) as usize;
inner.get(LEN_HDR..LEN_HDR + len).map(<[u8]>::to_vec)
}
#[tokio::main(flavor = "multi_thread", worker_threads = 2)]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let cipher = ChaCha20Poly1305::new(Key::from_slice(&[0x42; 32]));
let mk = |name: &str| -> Result<Node, Box<dyn std::error::Error>> {
Ok(Node::new(NodeConfig {
name: Some(name.into()),
listener_addr: Some("127.0.0.1:0".parse()?),
cover: CoverStrategy::Constant {
interval: Duration::from_millis(100),
},
..Default::default()
}))
};
let alice = mk("alice")?;
let bob = mk("bob")?;
alice.spawn().await?;
bob.spawn().await?;
let bob_addr = bob.local_addr().await?;
alice.connect(bob_addr).await?;
for _ in 0..50 {
if alice.connected_peers().contains(&bob_addr) {
break;
}
tokio::time::sleep(Duration::from_millis(10)).await;
}
let mut bob_rx = bob.subscribe();
alice.publish(&seal(&cipher, b"hello, peasub"))?;
println!("Alice published an encrypted message.");
let deadline = tokio::time::Instant::now() + Duration::from_secs(2);
let mut got = None;
while tokio::time::Instant::now() < deadline {
if let Ok(Ok(frame)) = tokio::time::timeout(Duration::from_millis(200), bob_rx.recv()).await
{
if let Some(plaintext) = open(&cipher, &frame) {
got = Some(plaintext);
break;
}
}
}
match got {
Some(p) => println!("Bob decrypted: {:?}", String::from_utf8_lossy(&p)),
None => println!("Bob did not receive the message within the deadline."),
}
alice.shutdown().await;
bob.shutdown().await;
Ok(())
}