use std::net::IpAddr;
use base64::Engine as _;
use sha1::{Digest, Sha1};
pub fn community_id_v1(
proto: u8,
src_ip: IpAddr,
src_port: u16,
dst_ip: IpAddr,
dst_port: u16,
seed: u16,
) -> String {
let ((a_ip, a_port), (b_ip, b_port)) = if (src_ip, src_port) <= (dst_ip, dst_port) {
((src_ip, src_port), (dst_ip, dst_port))
} else {
((dst_ip, dst_port), (src_ip, src_port))
};
let mut h = Sha1::new();
h.update(seed.to_be_bytes());
feed_ip(&mut h, a_ip);
feed_ip(&mut h, b_ip);
h.update([proto, 0u8]); h.update(a_port.to_be_bytes());
h.update(b_port.to_be_bytes());
let digest = h.finalize();
let mut out = String::with_capacity(2 + 28);
out.push_str("1:");
base64::engine::general_purpose::STANDARD.encode_string(digest.as_slice(), &mut out);
out
}
fn feed_ip(h: &mut Sha1, ip: IpAddr) {
match ip {
IpAddr::V4(v4) => h.update(v4.octets()),
IpAddr::V6(v6) => h.update(v6.octets()),
}
}
pub fn community_id_for_key<K: crate::KeyFields + ?Sized>(key: &K, seed: u16) -> Option<String> {
Some(community_id_v1(
key.protocol_identifier()?,
key.src_ip()?,
key.src_port()?,
key.dest_ip()?,
key.dest_port()?,
seed,
))
}
#[cfg(test)]
mod tests {
use super::*;
use std::net::IpAddr;
fn ip(s: &str) -> IpAddr {
s.parse().unwrap()
}
#[test]
fn tcp_golden_vector() {
let id = community_id_v1(6, ip("128.232.110.120"), 34855, ip("66.35.250.204"), 80, 0);
assert_eq!(id, "1:LQU9qZlK+B5F3KDmev6m5PMibrg=");
}
#[test]
fn direction_invariant() {
let fwd = community_id_v1(6, ip("10.0.0.1"), 1111, ip("10.0.0.2"), 80, 0);
let rev = community_id_v1(6, ip("10.0.0.2"), 80, ip("10.0.0.1"), 1111, 0);
assert_eq!(fwd, rev);
}
#[test]
fn seed_changes_id() {
let s0 = community_id_v1(6, ip("10.0.0.1"), 1111, ip("10.0.0.2"), 80, 0);
let s1 = community_id_v1(6, ip("10.0.0.1"), 1111, ip("10.0.0.2"), 80, 1);
assert_ne!(s0, s1);
}
#[test]
fn udp_golden_vector() {
let id = community_id_v1(17, ip("192.168.1.52"), 54585, ip("8.8.8.8"), 53, 0);
assert_eq!(id, "1:d/FP5EW3wiY1vCndhwleRRKHowQ=");
}
#[test]
fn ipv6_is_stable_and_prefixed() {
let id = community_id_v1(6, ip("fe80::1"), 5000, ip("fe80::2"), 443, 0);
assert!(id.starts_with("1:"));
let rev = community_id_v1(6, ip("fe80::2"), 443, ip("fe80::1"), 5000, 0);
assert_eq!(id, rev);
}
}