use crate::database::{AsinfoCoreRecord, AsinfoFullRecord, RpkiRoaRecord};
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
pub use crate::database::{AsConnectivitySummary, ConnectivityEntry, ConnectivityGroup};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum InspectQueryType {
Asn,
Prefix,
Name,
}
impl std::fmt::Display for InspectQueryType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
InspectQueryType::Asn => write!(f, "asn"),
InspectQueryType::Prefix => write!(f, "prefix"),
InspectQueryType::Name => write!(f, "name"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum InspectDataSection {
Basic,
Prefixes,
Connectivity,
Rpki,
}
impl InspectDataSection {
pub fn all() -> Vec<Self> {
vec![Self::Basic, Self::Prefixes, Self::Connectivity, Self::Rpki]
}
pub fn default_for_asn() -> Vec<Self> {
Self::all()
}
pub fn default_for_prefix() -> Vec<Self> {
Self::all()
}
pub fn default_for_name() -> Vec<Self> {
vec![Self::Basic]
}
#[allow(clippy::should_implement_trait)]
pub fn from_str(s: &str) -> Option<Self> {
match s.to_lowercase().as_str() {
"basic" => Some(Self::Basic),
"prefixes" => Some(Self::Prefixes),
"connectivity" => Some(Self::Connectivity),
"rpki" => Some(Self::Rpki),
_ => None,
}
}
pub fn all_names() -> Vec<&'static str> {
vec!["basic", "prefixes", "connectivity", "rpki"]
}
}
impl std::fmt::Display for InspectDataSection {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Basic => write!(f, "basic"),
Self::Prefixes => write!(f, "prefixes"),
Self::Connectivity => write!(f, "connectivity"),
Self::Rpki => write!(f, "rpki"),
}
}
}
#[derive(Debug, Clone)]
pub struct InspectQueryOptions {
pub select: Option<HashSet<InspectDataSection>>,
pub max_roas: usize,
pub max_prefixes: usize,
pub max_neighbors: usize,
pub max_search_results: usize,
}
impl Default for InspectQueryOptions {
fn default() -> Self {
Self {
select: None, max_roas: 10,
max_prefixes: 10,
max_neighbors: 5,
max_search_results: 20,
}
}
}
impl InspectQueryOptions {
pub fn full() -> Self {
Self {
select: Some(InspectDataSection::all().into_iter().collect()),
max_roas: 0,
max_prefixes: 0,
max_neighbors: 0,
max_search_results: 0,
}
}
pub fn with_select(mut self, sections: Vec<InspectDataSection>) -> Self {
self.select = Some(sections.into_iter().collect());
self
}
pub fn should_include(
&self,
section: InspectDataSection,
query_type: InspectQueryType,
) -> bool {
match &self.select {
Some(selected) => selected.contains(§ion),
None => {
let defaults = match query_type {
InspectQueryType::Asn => InspectDataSection::default_for_asn(),
InspectQueryType::Prefix => InspectDataSection::default_for_prefix(),
InspectQueryType::Name => InspectDataSection::default_for_name(),
};
defaults.contains(§ion)
}
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RpkiAsnInfo {
pub roas: Option<RoaSummary>,
pub aspa: Option<AspaInfo>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RoaSummary {
pub total_count: usize,
pub ipv4_count: usize,
pub ipv6_count: usize,
pub entries: Vec<RpkiRoaRecord>,
pub truncated: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AspaProvider {
pub asn: u32,
pub name: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AspaInfo {
pub customer_asn: u32,
pub customer_name: Option<String>,
pub customer_country: Option<String>,
pub providers: Vec<AspaProvider>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RpkiPrefixInfo {
pub roas: Vec<RpkiRoaRecord>,
pub roa_count: usize,
pub validation_state: Option<String>,
pub truncated: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConnectivitySection {
pub summary: AsConnectivitySummary,
pub truncated: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Pfx2asInfo {
pub prefix: String,
pub origin_asns: Vec<u32>,
pub match_type: String,
pub validations: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PrefixSection {
pub pfx2as: Option<Pfx2asInfo>,
pub rpki: Option<RpkiPrefixInfo>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AnnouncedPrefixesSection {
pub total_count: usize,
pub ipv4_count: usize,
pub ipv6_count: usize,
pub validation_summary: ValidationSummary,
pub prefixes: Vec<PrefixEntry>,
pub truncated: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PrefixEntry {
pub prefix: String,
pub origin_asn: u32,
pub origin_name: Option<String>,
pub origin_country: Option<String>,
pub validation: String,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ValidationSummary {
pub valid_count: usize,
pub valid_percent: f64,
pub invalid_count: usize,
pub invalid_percent: f64,
pub unknown_count: usize,
pub unknown_percent: f64,
}
impl ValidationSummary {
pub fn from_counts(valid: usize, invalid: usize, unknown: usize) -> Self {
let total = valid + invalid + unknown;
let total_f64 = total as f64;
Self {
valid_count: valid,
valid_percent: if total > 0 {
(valid as f64 / total_f64) * 100.0
} else {
0.0
},
invalid_count: invalid,
invalid_percent: if total > 0 {
(invalid as f64 / total_f64) * 100.0
} else {
0.0
},
unknown_count: unknown,
unknown_percent: if total > 0 {
(unknown as f64 / total_f64) * 100.0
} else {
0.0
},
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AsinfoSection {
pub detail: Option<AsinfoFullRecord>,
pub origins: Option<Vec<AsinfoFullRecord>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SearchResultsSection {
pub total_matches: usize,
pub results: Vec<AsinfoCoreRecord>,
pub truncated: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InspectQueryResult {
pub query: String,
pub query_type: InspectQueryType,
pub asinfo: Option<AsinfoSection>,
pub prefix: Option<PrefixSection>,
pub prefixes: Option<AnnouncedPrefixesSection>,
pub connectivity: Option<ConnectivitySection>,
pub rpki: Option<RpkiAsnInfo>,
pub search_results: Option<SearchResultsSection>,
}
impl InspectQueryResult {
pub fn new_asn(query: String) -> Self {
Self {
query,
query_type: InspectQueryType::Asn,
asinfo: None,
prefix: None,
prefixes: None,
connectivity: None,
rpki: None,
search_results: None,
}
}
pub fn new_prefix(query: String) -> Self {
Self {
query,
query_type: InspectQueryType::Prefix,
asinfo: None,
prefix: None,
prefixes: None,
connectivity: None,
rpki: None,
search_results: None,
}
}
pub fn new_name(query: String) -> Self {
Self {
query,
query_type: InspectQueryType::Name,
asinfo: None,
prefix: None,
prefixes: None,
connectivity: None,
rpki: None,
search_results: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InspectResult {
pub queries: Vec<InspectQueryResult>,
pub meta: InspectResultMeta,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InspectResultMeta {
pub query_count: usize,
pub asn_queries: usize,
pub prefix_queries: usize,
pub name_queries: usize,
pub processing_time_ms: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum MultiAsnDisplayMode {
#[default]
Standard,
Table,
}
#[derive(Debug, Clone)]
pub struct InspectDisplayConfig {
pub terminal_width: usize,
pub show_hegemony: bool,
pub show_population: bool,
pub show_peeringdb: bool,
pub truncate_names: bool,
pub name_max_width: usize,
pub use_markdown_style: bool,
pub force_extended_info: bool,
pub multi_asn_mode: MultiAsnDisplayMode,
}
impl InspectDisplayConfig {
pub fn from_terminal_width(width: usize) -> Self {
match width {
0..=80 => Self {
terminal_width: width,
show_hegemony: false,
show_population: false,
show_peeringdb: false,
truncate_names: true,
name_max_width: 25,
use_markdown_style: false,
force_extended_info: false,
multi_asn_mode: MultiAsnDisplayMode::Standard,
},
81..=120 => Self {
terminal_width: width,
show_hegemony: false,
show_population: false,
show_peeringdb: false,
truncate_names: true,
name_max_width: 35,
use_markdown_style: false,
force_extended_info: false,
multi_asn_mode: MultiAsnDisplayMode::Standard,
},
121..=160 => Self {
terminal_width: width,
show_hegemony: true,
show_population: false,
show_peeringdb: false,
truncate_names: true,
name_max_width: 45,
use_markdown_style: false,
force_extended_info: false,
multi_asn_mode: MultiAsnDisplayMode::Standard,
},
_ => Self {
terminal_width: width,
show_hegemony: true,
show_population: true,
show_peeringdb: true,
truncate_names: false,
name_max_width: 60,
use_markdown_style: false,
force_extended_info: false,
multi_asn_mode: MultiAsnDisplayMode::Standard,
},
}
}
pub fn auto() -> Self {
let width = std::env::var("COLUMNS")
.ok()
.and_then(|s| s.parse::<usize>().ok())
.unwrap_or(80);
Self::from_terminal_width(width)
}
pub fn with_markdown(mut self, use_markdown: bool) -> Self {
self.use_markdown_style = use_markdown;
self
}
pub fn with_extended_info(mut self, force: bool) -> Self {
self.force_extended_info = force;
if force {
self.show_hegemony = true;
self.show_population = true;
self.show_peeringdb = true;
}
self
}
pub fn with_multi_asn_mode(mut self, mode: MultiAsnDisplayMode) -> Self {
self.multi_asn_mode = mode;
self
}
pub fn should_show_hegemony(&self) -> bool {
self.force_extended_info || self.show_hegemony
}
pub fn should_show_population(&self) -> bool {
self.force_extended_info || self.show_population
}
pub fn should_show_peeringdb(&self) -> bool {
self.force_extended_info || self.show_peeringdb
}
}
impl Default for InspectDisplayConfig {
fn default() -> Self {
Self::auto()
}
}