1use crate::errors::{data_sources, load_methods, modules};
9use crate::{BgpkitCommons, BgpkitCommonsError, LazyLoadable, Result};
10use serde::{Deserialize, Serialize};
11use std::collections::{HashMap, HashSet};
12use std::hash::Hash;
13use tracing::info;
14
15#[allow(dead_code)]
16const AS2REL_LATEST_COMBINED: &str = "https://data.bgpkit.com/as2rel/as2rel-latest.json.bz2";
17
18const AS2REL_LATEST_V4: &str = "https://data.bgpkit.com/as2rel/as2rel-v4-latest.json.bz2";
19const AS2REL_LATEST_V6: &str = "https://data.bgpkit.com/as2rel/as2rel-v6-latest.json.bz2";
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
22pub enum AsRelationship {
23 ProviderCustomer,
24 CustomerProvider,
25 PeerPeer,
26}
27
28impl Serialize for AsRelationship {
29 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
30 where
31 S: serde::Serializer,
32 {
33 match self {
34 AsRelationship::ProviderCustomer => serializer.serialize_str("pc"),
35 AsRelationship::CustomerProvider => serializer.serialize_str("cp"),
36 AsRelationship::PeerPeer => serializer.serialize_str("pp"),
37 }
38 }
39}
40
41impl<'de> Deserialize<'de> for AsRelationship {
42 fn deserialize<D>(deserializer: D) -> std::result::Result<AsRelationship, D::Error>
43 where
44 D: serde::Deserializer<'de>,
45 {
46 let s = i8::deserialize(deserializer)?;
47 match s {
48 -1 | 1 => Ok(AsRelationship::ProviderCustomer),
49 0 => Ok(AsRelationship::PeerPeer),
50 _ => Err(serde::de::Error::custom("invalid relationship")),
51 }
52 }
53}
54
55#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
56struct As2relEntry {
57 asn1: u32,
58 asn2: u32,
59 paths_count: u32,
60 peers_count: u32,
61 rel: AsRelationship,
62}
63
64impl PartialEq for As2relEntry {
65 fn eq(&self, other: &Self) -> bool {
66 self.asn1 == other.asn1 && self.asn2 == other.asn2 && self.rel == other.rel
67 }
68}
69impl Eq for As2relEntry {}
70
71impl Hash for As2relEntry {
72 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
73 self.asn1.hash(state);
74 self.asn2.hash(state);
75 self.rel.hash(state);
76 }
77}
78
79impl As2relEntry {
80 fn reverse(&self) -> Self {
81 Self {
82 asn1: self.asn2,
83 asn2: self.asn1,
84 paths_count: self.paths_count,
85 peers_count: self.peers_count,
86 rel: match self.rel {
87 AsRelationship::ProviderCustomer => AsRelationship::CustomerProvider,
88 AsRelationship::CustomerProvider => AsRelationship::ProviderCustomer,
89 AsRelationship::PeerPeer => AsRelationship::PeerPeer,
90 },
91 }
92 }
93}
94
95#[derive(Debug, Clone, Serialize, Deserialize)]
96pub struct As2relBgpkit {
97 v4_rels_map: HashMap<(u32, u32), HashSet<As2relEntry>>,
98 v6_rels_map: HashMap<(u32, u32), HashSet<As2relEntry>>,
99 v4_max_peer_count: u32,
100 v6_max_peer_count: u32,
101}
102
103#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
104pub struct As2relBgpkitData {
105 pub rel: AsRelationship,
106 pub peers_count: u32,
107 pub max_peer_count: u32,
108}
109
110impl As2relBgpkit {
111 pub fn new() -> Result<Self> {
112 let v4_rels = parse_as2rel_data(AS2REL_LATEST_V4)?;
113 let v6_rels = parse_as2rel_data(AS2REL_LATEST_V6)?;
114 let mut v4_rels_map = HashMap::new();
115 let mut v6_rels_map = HashMap::new();
116 let mut v4_max_peer_count = 0;
117 let mut v6_max_peer_count = 0;
118 for entry in v4_rels {
119 v4_rels_map
120 .entry((entry.asn1, entry.asn2))
121 .or_insert_with(HashSet::new)
122 .insert(entry);
123 v4_rels_map
124 .entry((entry.asn2, entry.asn1))
125 .or_insert_with(HashSet::new)
126 .insert(entry.reverse());
127
128 v4_max_peer_count = v4_max_peer_count.max(entry.peers_count);
129 }
130 for entry in v6_rels {
131 v6_rels_map
132 .entry((entry.asn1, entry.asn2))
133 .or_insert_with(HashSet::new)
134 .insert(entry);
135 v6_rels_map
136 .entry((entry.asn2, entry.asn1))
137 .or_insert_with(HashSet::new)
138 .insert(entry.reverse());
139
140 v6_max_peer_count = v6_max_peer_count.max(entry.peers_count);
141 }
142 Ok(Self {
143 v4_rels_map,
144 v6_rels_map,
145 v4_max_peer_count,
146 v6_max_peer_count,
147 })
148 }
149
150 pub fn lookup_pair(
151 &self,
152 asn1: u32,
153 asn2: u32,
154 ) -> (Vec<As2relBgpkitData>, Vec<As2relBgpkitData>) {
155 let v4_entry_set = self.v4_rels_map.get(&(asn1, asn2));
156 let v6_entry_set = self.v6_rels_map.get(&(asn1, asn2));
157
158 let v4_entries = v4_entry_set
159 .map(|set| {
160 set.iter()
161 .map(|entry| As2relBgpkitData {
162 rel: entry.rel,
163 peers_count: entry.peers_count,
164 max_peer_count: self.v4_max_peer_count,
165 })
166 .collect()
167 })
168 .unwrap_or_default();
169
170 let v6_entries = v6_entry_set
171 .map(|set| {
172 set.iter()
173 .map(|entry| As2relBgpkitData {
174 rel: entry.rel,
175 peers_count: entry.peers_count,
176 max_peer_count: self.v6_max_peer_count,
177 })
178 .collect()
179 })
180 .unwrap_or_default();
181
182 (v4_entries, v6_entries)
183 }
184}
185
186impl LazyLoadable for As2relBgpkit {
187 fn reload(&mut self) -> Result<()> {
188 *self = As2relBgpkit::new().map_err(|e| {
189 BgpkitCommonsError::data_source_error(data_sources::BGPKIT, e.to_string())
190 })?;
191 Ok(())
192 }
193
194 fn is_loaded(&self) -> bool {
195 !self.v4_rels_map.is_empty() || !self.v6_rels_map.is_empty()
196 }
197
198 fn loading_status(&self) -> &'static str {
199 if self.is_loaded() {
200 "AS2Rel data loaded"
201 } else {
202 "AS2Rel data not loaded"
203 }
204 }
205}
206
207fn parse_as2rel_data(url: &str) -> Result<Vec<As2relEntry>> {
208 info!("loading AS2REL data from {}", url);
209 let data: Vec<As2relEntry> = oneio::read_json_struct(url)?;
210 Ok(data)
211}
212
213impl BgpkitCommons {
214 pub fn as2rel_lookup(
215 &self,
216 asn1: u32,
217 asn2: u32,
218 ) -> Result<(Vec<As2relBgpkitData>, Vec<As2relBgpkitData>)> {
219 if self.as2rel.is_none() {
220 return Err(BgpkitCommonsError::module_not_loaded(
221 modules::AS2REL,
222 load_methods::LOAD_AS2REL,
223 ));
224 }
225
226 Ok(self.as2rel.as_ref().unwrap().lookup_pair(asn1, asn2))
227 }
228}