use std::collections::HashSet;
pub const OPENRTC_ALPN: &[u8] = b"plutonium/p2p/1";
pub const MAX_ROUTER_PROTOCOLS: usize = 32;
pub const MAX_ALPN_BYTES: usize = 255;
pub fn complete_router_alpns(
extra_alpns: Vec<Vec<u8>>,
include_standard_protocols: bool,
) -> anyhow::Result<Vec<Vec<u8>>> {
let mut alpns = extra_alpns;
alpns.push(OPENRTC_ALPN.to_vec());
if include_standard_protocols {
#[cfg(feature = "iroh-protocols-wasm")]
alpns.extend([
iroh_blobs::ALPN.to_vec(),
iroh_gossip::ALPN.to_vec(),
iroh_docs::ALPN.to_vec(),
]);
#[cfg(not(feature = "iroh-protocols-wasm"))]
anyhow::bail!("standard Iroh protocols are not compiled into this runtime");
}
anyhow::ensure!(
alpns.len() <= MAX_ROUTER_PROTOCOLS,
"Iroh router protocol count exceeds {MAX_ROUTER_PROTOCOLS}"
);
let mut seen = HashSet::with_capacity(alpns.len());
for alpn in &alpns {
anyhow::ensure!(!alpn.is_empty(), "Iroh ALPN must not be empty");
anyhow::ensure!(
alpn.len() <= MAX_ALPN_BYTES,
"Iroh ALPN exceeds {MAX_ALPN_BYTES} bytes"
);
anyhow::ensure!(seen.insert(alpn.clone()), "duplicate Iroh ALPN");
}
Ok(alpns)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn complete_router_alpns_rejects_duplicate_and_invalid_values() {
assert!(complete_router_alpns(vec![OPENRTC_ALPN.to_vec()], false).is_err());
assert!(complete_router_alpns(vec![Vec::new()], false).is_err());
assert!(complete_router_alpns(vec![vec![1; MAX_ALPN_BYTES + 1]], false).is_err());
}
#[test]
fn complete_router_alpns_preserves_declared_order_and_adds_openrtc() {
assert_eq!(
complete_router_alpns(vec![b"example/custom/1".to_vec()], false).unwrap(),
vec![b"example/custom/1".to_vec(), OPENRTC_ALPN.to_vec()]
);
}
#[cfg(feature = "iroh-protocols-wasm")]
#[test]
fn standard_protocols_are_declared_once() {
let alpns = complete_router_alpns(Vec::new(), true).unwrap();
assert_eq!(alpns.len(), 4);
assert!(alpns.contains(&iroh_docs::ALPN.to_vec()));
assert!(alpns.contains(&iroh_blobs::ALPN.to_vec()));
assert!(alpns.contains(&iroh_gossip::ALPN.to_vec()));
}
}