use super::{RoutingRange, SubnetCatalog, SubnetInfo};
use crate::subnet_catalog::{
CATALOG_SCHEMA_VERSION, CatalogError, parse_principal, principal_bytes,
resolver::routing_range_sorts_after,
};
use std::collections::BTreeSet;
impl SubnetCatalog {
pub fn validate(&self) -> Result<(), CatalogError> {
if self.catalog_schema_version != CATALOG_SCHEMA_VERSION {
return Err(CatalogError::UnsupportedSchemaVersion {
found: self.catalog_schema_version,
supported: CATALOG_SCHEMA_VERSION,
});
}
if self.subnets.is_empty() {
return Err(CatalogError::EmptySubnets);
}
if self.routing_ranges.is_empty() {
return Err(CatalogError::EmptyRoutingRanges);
}
parse_principal(&self.registry_canister_id, "registry_canister_id")?;
let mut subnet_principals = BTreeSet::new();
for subnet in &self.subnets {
parse_principal(&subnet.subnet_principal, "subnet_principal")?;
if !subnet_principals.insert(subnet.subnet_principal.clone()) {
return Err(CatalogError::DuplicateSubnet {
subnet_principal: subnet.subnet_principal.clone(),
});
}
}
let mut validated_ranges = Vec::with_capacity(self.routing_ranges.len());
for range in &self.routing_ranges {
if !subnet_principals.contains(&range.subnet_principal) {
return Err(CatalogError::UnknownRoutingSubnet {
subnet_principal: range.subnet_principal.clone(),
});
}
let start = principal_bytes(&range.start_canister_id, "start_canister_id")?;
let end = principal_bytes(&range.end_canister_id, "end_canister_id")?;
parse_principal(&range.subnet_principal, "routing_range.subnet_principal")?;
if routing_range_sorts_after(&start, &end) {
return Err(CatalogError::InvalidRoutingRange {
subnet_principal: range.subnet_principal.clone(),
start_canister_id: range.start_canister_id.clone(),
end_canister_id: range.end_canister_id.clone(),
});
}
validated_ranges.push((range, start, end));
}
validated_ranges
.sort_by(|left, right| left.1.cmp(&right.1).then_with(|| left.2.cmp(&right.2)));
for pair in validated_ranges.windows(2) {
let (first, _, first_end) = &pair[0];
let (second, second_start, _) = &pair[1];
if second_start <= first_end {
return Err(CatalogError::OverlappingRoutingRanges {
first: Box::new((*first).clone()),
second: Box::new((*second).clone()),
});
}
}
Ok(())
}
#[must_use]
pub fn subnet_by_principal(&self, subnet_principal: &str) -> Option<&SubnetInfo> {
self.subnets
.iter()
.find(|subnet| subnet.subnet_principal == subnet_principal)
}
#[must_use]
pub fn routing_ranges_for_subnet(&self, subnet_principal: &str) -> Vec<&RoutingRange> {
self.routing_ranges
.iter()
.filter(|range| range.subnet_principal == subnet_principal)
.collect()
}
}