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 #[allow(dead_code)]
190 pub fn all_entries(&self) -> impl Iterator<Item = As2relExportEntry> + '_ {
191 let v4 = self
192 .v4_rels_map
193 .iter()
194 .filter(move |((a, b), _)| *a < *b)
195 .flat_map(|(_, set)| {
196 set.iter().map(|e| As2relExportEntry {
197 asn1: e.asn1,
198 asn2: e.asn2,
199 rel: e.rel,
200 paths_count: e.paths_count,
201 peers_count: e.peers_count,
202 address_family: 4,
203 })
204 });
205 let v6 = self
206 .v6_rels_map
207 .iter()
208 .filter(move |((a, b), _)| *a < *b)
209 .flat_map(|(_, set)| {
210 set.iter().map(|e| As2relExportEntry {
211 asn1: e.asn1,
212 asn2: e.asn2,
213 rel: e.rel,
214 paths_count: e.paths_count,
215 peers_count: e.peers_count,
216 address_family: 6,
217 })
218 });
219 v4.chain(v6)
220 }
221}
222
223#[derive(Debug, Clone, Serialize, Deserialize)]
225pub struct As2relExportEntry {
226 pub asn1: u32,
227 pub asn2: u32,
228 pub rel: AsRelationship,
229 pub paths_count: u32,
230 pub peers_count: u32,
231 pub address_family: u8,
232}
233
234impl LazyLoadable for As2relBgpkit {
235 fn reload(&mut self) -> Result<()> {
236 *self = As2relBgpkit::new().map_err(|e| {
237 BgpkitCommonsError::data_source_error(data_sources::BGPKIT, e.to_string())
238 })?;
239 Ok(())
240 }
241
242 fn is_loaded(&self) -> bool {
243 !self.v4_rels_map.is_empty() || !self.v6_rels_map.is_empty()
244 }
245
246 fn loading_status(&self) -> &'static str {
247 if self.is_loaded() {
248 "AS2Rel data loaded"
249 } else {
250 "AS2Rel data not loaded"
251 }
252 }
253}
254
255fn parse_as2rel_data(url: &str) -> Result<Vec<As2relEntry>> {
256 info!("loading AS2REL data from {}", url);
257 let data: Vec<As2relEntry> = oneio::read_json_struct(url)?;
258 Ok(data)
259}
260
261impl BgpkitCommons {
262 pub fn as2rel_lookup(
263 &self,
264 asn1: u32,
265 asn2: u32,
266 ) -> Result<(Vec<As2relBgpkitData>, Vec<As2relBgpkitData>)> {
267 if self.as2rel.is_none() {
268 return Err(BgpkitCommonsError::module_not_loaded(
269 modules::AS2REL,
270 load_methods::LOAD_AS2REL,
271 ));
272 }
273
274 Ok(self.as2rel.as_ref().unwrap().lookup_pair(asn1, asn2))
275 }
276
277 #[cfg(feature = "export")]
281 pub fn as2rel_all_entries(&self) -> Result<impl Iterator<Item = As2relExportEntry> + '_> {
282 self.as2rel
283 .as_ref()
284 .ok_or_else(|| {
285 BgpkitCommonsError::module_not_loaded(modules::AS2REL, load_methods::LOAD_AS2REL)
286 })
287 .map(|data| data.all_entries())
288 }
289}