use std::{cmp::Reverse, collections::HashMap};
use cidr::IpCidr;
use cidr_utils::combiner::{Ipv4CidrCombiner, Ipv6CidrCombiner};
use crate::{ClientIpConfigBuildError, TrustedProxyRule, config::TrustedProxyMetadata};
pub(crate) fn ensure_no_cross_metadata_overlap(
rules: &[TrustedProxyRule],
) -> Result<(), ClientIpConfigBuildError> {
let mut order: Vec<&TrustedProxyRule> = rules.iter().collect();
order.sort_by_key(|rule| (rule.cidr.first_address(), Reverse(rule.cidr.last_address())));
let mut open: Option<&TrustedProxyRule> = None;
for rule in order {
match open {
Some(outer) if rule.cidr.first_address() <= outer.cidr.last_address() => {
if outer.metadata != rule.metadata {
return Err(ClientIpConfigBuildError::OverlappingTrustedProxyRules {
left: Box::new(outer.clone()),
right: Box::new(rule.clone()),
});
}
},
_ => open = Some(rule),
}
}
Ok(())
}
pub(crate) fn merge_rules_by_metadata(rules: Vec<TrustedProxyRule>) -> Vec<TrustedProxyRule> {
let mut groups: HashMap<TrustedProxyMetadata, Vec<IpCidr>> = HashMap::new();
for rule in rules {
groups.entry(rule.metadata).or_default().push(rule.cidr);
}
let mut merged = Vec::new();
for (metadata, cidrs) in groups {
merged.extend(merge_cidrs(cidrs).into_iter().map(|cidr| TrustedProxyRule {
cidr,
metadata: metadata.clone(),
}));
}
merged
}
fn merge_cidrs(cidrs: Vec<IpCidr>) -> Vec<IpCidr> {
let mut ipv4 = Ipv4CidrCombiner::new();
let mut ipv6 = Ipv6CidrCombiner::new();
for cidr in cidrs {
match cidr {
IpCidr::V4(cidr) => ipv4.push(cidr),
IpCidr::V6(cidr) => ipv6.push(cidr),
}
}
let mut merged = Vec::new();
for cidr in ipv4.into_ipv4_cidr_vec() {
merged.push(IpCidr::V4(cidr));
}
for cidr in ipv6.into_ipv6_cidr_vec() {
merged.push(IpCidr::V6(cidr));
}
merged
}