use std::collections::BTreeMap;
use super::{
SubnetAuthError, SubnetAuthorityConfig, SubnetControlOutcome, SubnetExportBinding,
SubnetFactKind, SubnetRef, TopologySubnetId,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SubnetExportAccess {
SameOrg,
Granted,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NamedSubnetExport {
pub name: String,
pub access: SubnetExportAccess,
pub subnet: SubnetRef,
pub topology_epoch: u32,
}
#[derive(Debug, Default)]
pub struct NamedSubnetExports {
entries: BTreeMap<String, (SubnetExportAccess, SubnetExportBinding)>,
}
impl NamedSubnetExports {
pub fn try_new(
exports: impl IntoIterator<Item = NamedSubnetExport>,
) -> Result<Self, SubnetProvisionError> {
let mut entries = BTreeMap::new();
for e in exports {
if e.name.is_empty() {
return Err(SubnetProvisionError::EmptyExportName);
}
let binding = SubnetExportBinding::new(e.subnet, e.topology_epoch);
if entries
.insert(e.name.clone(), (e.access, binding))
.is_some()
{
return Err(SubnetProvisionError::DuplicateExportName);
}
}
Ok(Self { entries })
}
pub fn resolve(&self, name: &str) -> Option<(SubnetExportAccess, &SubnetExportBinding)> {
self.entries.get(name).map(|(a, b)| (*a, b))
}
pub fn names(&self) -> impl Iterator<Item = &str> {
self.entries.keys().map(String::as_str)
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
}
pub fn validate_subnet_authorities(
authorities: &[SubnetAuthorityConfig],
) -> Result<(), SubnetProvisionError> {
let mut seen = std::collections::BTreeSet::new();
for a in authorities {
if !seen.insert(a.authority.as_bytes()) {
return Err(SubnetProvisionError::DuplicateAuthority);
}
if a.roots.is_empty() {
return Err(SubnetProvisionError::EmptyAuthorityRoots);
}
let mut roots = std::collections::BTreeSet::new();
for r in &a.roots {
if !roots.insert(r.as_bytes()) {
return Err(SubnetProvisionError::DuplicateAuthorityRoot);
}
}
if a.maximum_grant_lifetime_secs == 0 {
return Err(SubnetProvisionError::ZeroGrantLifetime);
}
}
Ok(())
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SubnetProvisionError {
Auth(SubnetAuthError),
EmptyExportName,
DuplicateExportName,
UnknownExportName,
EmptyAuthorityRoots,
DuplicateAuthorityRoot,
DuplicateAuthority,
ZeroGrantLifetime,
InvalidIdHex,
PathTooDeep,
InvalidPathLevel,
InvalidAccess,
}
pub const LOCAL_PROVISION_KINDS: &[&str] = &[
"empty_export_name",
"duplicate_export_name",
"unknown_export_name",
"empty_authority_roots",
"duplicate_authority_root",
"duplicate_authority",
"zero_grant_lifetime",
"invalid_id_hex",
"path_too_deep",
"invalid_path_level",
"invalid_access",
];
impl SubnetProvisionError {
pub const fn wire_kind(&self) -> &'static str {
match self {
Self::Auth(e) => e.wire_kind(),
Self::EmptyExportName => "empty_export_name",
Self::DuplicateExportName => "duplicate_export_name",
Self::UnknownExportName => "unknown_export_name",
Self::EmptyAuthorityRoots => "empty_authority_roots",
Self::DuplicateAuthorityRoot => "duplicate_authority_root",
Self::DuplicateAuthority => "duplicate_authority",
Self::ZeroGrantLifetime => "zero_grant_lifetime",
Self::InvalidIdHex => "invalid_id_hex",
Self::PathTooDeep => "path_too_deep",
Self::InvalidPathLevel => "invalid_path_level",
Self::InvalidAccess => "invalid_access",
}
}
}
impl std::fmt::Display for SubnetProvisionError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "subnet:{}", self.wire_kind())
}
}
impl std::error::Error for SubnetProvisionError {}
impl From<SubnetAuthError> for SubnetProvisionError {
fn from(e: SubnetAuthError) -> Self {
Self::Auth(e)
}
}
pub fn fact_kind_wire(kind: SubnetFactKind) -> &'static str {
match kind {
SubnetFactKind::Descriptor => "descriptor",
SubnetFactKind::GatewayAdvertisement => "gateway_advertisement",
SubnetFactKind::ExportPolicy => "export_policy",
SubnetFactKind::RevocationFloor => "revocation_floor",
}
}
pub mod dto {
use serde::{Deserialize, Serialize};
use super::*;
use crate::adapter::net::identity::EntityId;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SubnetAuthorityConfigDto {
pub authority_hex: String,
pub root_hexes: Vec<String>,
pub maximum_grant_lifetime_secs: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SubnetPathDto {
pub levels: Vec<u8>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SubnetRefDto {
pub authority_hex: String,
pub path: SubnetPathDto,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SubnetBoundaryDeclarationDto {
pub authority_hex: String,
pub topology_epoch: u32,
pub boundaries: Vec<SubnetPathDto>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SubnetExportBindingDto {
pub subnet: SubnetRefDto,
pub topology_epoch: u32,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SubnetNamedExportDto {
pub name: String,
pub access: String,
pub binding: SubnetExportBindingDto,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SubnetControlOutcomeDto {
pub kind: String,
pub applied: bool,
}
impl From<SubnetControlOutcome> for SubnetControlOutcomeDto {
fn from(o: SubnetControlOutcome) -> Self {
Self {
kind: fact_kind_wire(o.kind).to_string(),
applied: o.applied,
}
}
}
pub fn entity_id_from_hex(hex: &str) -> Result<EntityId, SubnetProvisionError> {
let bytes = hex.as_bytes();
if bytes.len() != 64 {
return Err(SubnetProvisionError::InvalidIdHex);
}
let mut out = [0u8; 32];
for (i, chunk) in bytes.chunks_exact(2).enumerate() {
let hi = hex_val(chunk[0]).ok_or(SubnetProvisionError::InvalidIdHex)?;
let lo = hex_val(chunk[1]).ok_or(SubnetProvisionError::InvalidIdHex)?;
out[i] = (hi << 4) | lo;
}
Ok(EntityId::from_bytes(out))
}
fn hex_val(c: u8) -> Option<u8> {
match c {
b'0'..=b'9' => Some(c - b'0'),
b'a'..=b'f' => Some(c - b'a' + 10),
b'A'..=b'F' => Some(c - b'A' + 10),
_ => None,
}
}
impl SubnetPathDto {
pub fn to_core(&self) -> Result<TopologySubnetId, SubnetProvisionError> {
TopologySubnetId::try_new(&self.levels).map_err(|_| SubnetProvisionError::PathTooDeep)
}
}
impl SubnetRefDto {
pub fn to_core(&self) -> Result<SubnetRef, SubnetProvisionError> {
Ok(SubnetRef {
authority: entity_id_from_hex(&self.authority_hex)?,
path: self.path.to_core()?,
})
}
}
impl SubnetAuthorityConfigDto {
pub fn to_core(&self) -> Result<SubnetAuthorityConfig, SubnetProvisionError> {
let authority = entity_id_from_hex(&self.authority_hex)?;
let mut roots = Vec::with_capacity(self.root_hexes.len());
for r in &self.root_hexes {
roots.push(entity_id_from_hex(r)?);
}
Ok(SubnetAuthorityConfig {
authority,
roots,
maximum_grant_lifetime_secs: self.maximum_grant_lifetime_secs,
})
}
}
impl SubnetBoundaryDeclarationDto {
pub fn to_core(
&self,
) -> Result<(EntityId, u32, Vec<TopologySubnetId>), SubnetProvisionError> {
let authority = entity_id_from_hex(&self.authority_hex)?;
let mut boundaries = Vec::with_capacity(self.boundaries.len());
for b in &self.boundaries {
boundaries.push(b.to_core()?);
}
Ok((authority, self.topology_epoch, boundaries))
}
}
impl SubnetNamedExportDto {
pub fn to_core(&self) -> Result<NamedSubnetExport, SubnetProvisionError> {
let access = match self.access.as_str() {
"sameOrg" | "same_org" => SubnetExportAccess::SameOrg,
"granted" => SubnetExportAccess::Granted,
_ => return Err(SubnetProvisionError::InvalidAccess),
};
Ok(NamedSubnetExport {
name: self.name.clone(),
access,
subnet: self.binding.subnet.to_core()?,
topology_epoch: self.binding.topology_epoch,
})
}
}
}