use crate::error::BgpValidationWarning;
use crate::models::*;
use crate::parser::bgp::parse_bgp_message;
use crate::parser::rislive::error::ParserRisliveError;
use crate::parser::rislive::messages::{RisLiveMessage, RisMessage};
use crate::Elementor;
use bytes::Bytes;
use serde::Serialize;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
pub fn parse_raw_bytes(msg_str: &str) -> Result<Vec<BgpElem>, ParserRisliveError> {
parse_ris_live_message_raw(msg_str)
}
pub fn parse_ris_live_message_raw(msg_str: &str) -> Result<Vec<BgpElem>, ParserRisliveError> {
let (ris_msg, raw_bytes) = decode_ris_live_raw(msg_str)?;
parse_ris_live_raw_bgp_message(raw_bytes, ris_msg.timestamp, ris_msg.peer, ris_msg.peer_asn)
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RisLiveMeta {
pub host: String,
pub id: String,
pub peer: IpAddr,
pub peer_asn: Asn,
pub timestamp: f64,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RisLiveRawFull {
pub meta: RisLiveMeta,
pub elems: Vec<BgpElem>,
pub attributes: Attributes,
pub validation_warnings: Vec<BgpValidationWarning>,
}
pub fn parse_ris_live_message_raw_full(
msg_str: &str,
) -> Result<RisLiveRawFull, ParserRisliveError> {
let (ris_msg, raw_bytes) = decode_ris_live_raw(msg_str)?;
let bgp_msg = parse_raw_bgp_message_with_asn_fallback(Bytes::from(raw_bytes.clone()))?;
let (attributes, validation_warnings) = match &bgp_msg {
BgpMessage::Update(update) => (
update.attributes.clone(),
update.attributes.validation_warnings().to_vec(),
),
_ => (Attributes::default(), vec![]),
};
let elems = parse_ris_live_raw_bgp_message(
raw_bytes,
ris_msg.timestamp,
ris_msg.peer,
ris_msg.peer_asn,
)?;
Ok(RisLiveRawFull {
meta: RisLiveMeta {
host: ris_msg.host,
id: ris_msg.id,
peer: ris_msg.peer,
peer_asn: ris_msg.peer_asn,
timestamp: ris_msg.timestamp,
},
elems,
attributes,
validation_warnings,
})
}
fn decode_ris_live_raw(msg_str: &str) -> Result<(RisMessage, Vec<u8>), ParserRisliveError> {
let msg: RisLiveMessage = serde_json::from_str(msg_str)
.map_err(|e| ParserRisliveError::IncorrectJson(e.to_string()))?;
let mut ris_msg = match msg {
RisLiveMessage::RisMessage(ris_msg) => ris_msg,
RisLiveMessage::RisError(_)
| RisLiveMessage::RisRrcList(_)
| RisLiveMessage::RisSubscribeOk(_)
| RisLiveMessage::Pong(_) => return Err(ParserRisliveError::UnsupportedMessage),
};
let raw = ris_msg
.raw
.take()
.ok_or(ParserRisliveError::IncorrectRawBytes)?;
let raw_bytes = hex::decode(raw).map_err(|_| ParserRisliveError::IncorrectRawBytes)?;
Ok((ris_msg, raw_bytes))
}
fn parse_raw_bgp_message_with_asn_fallback(bytes: Bytes) -> Result<BgpMessage, ParserRisliveError> {
let mut bytes_32bit_asn = bytes.clone();
let mut bytes_16bit_asn = bytes;
parse_bgp_message(&mut bytes_32bit_asn, false, &AsnLength::Bits32)
.or_else(|_| parse_bgp_message(&mut bytes_16bit_asn, false, &AsnLength::Bits16))
.map_err(|_| ParserRisliveError::IncorrectRawBytes)
}
fn parse_ris_live_raw_bgp_message(
raw_bytes: Vec<u8>,
timestamp: f64,
peer_ip: IpAddr,
peer_asn: Asn,
) -> Result<Vec<BgpElem>, ParserRisliveError> {
let bgp_msg = parse_raw_bgp_message_with_asn_fallback(Bytes::from(raw_bytes))?;
let local_ip = match peer_ip.is_ipv4() {
true => IpAddr::V4(Ipv4Addr::UNSPECIFIED),
false => IpAddr::V6(Ipv6Addr::UNSPECIFIED),
};
let header = CommonHeader {
timestamp: timestamp as u32,
microsecond_timestamp: Some(get_micro_seconds(timestamp)),
entry_type: EntryType::BGP4MP,
entry_subtype: Bgp4MpType::MessageAs4 as u16,
length: 0,
};
let record = MrtRecord {
common_header: header,
message: MrtMessage::Bgp4Mp(Bgp4MpEnum::Message(Bgp4MpMessage {
msg_type: Bgp4MpType::MessageAs4,
peer_asn,
local_asn: Asn::RESERVED,
interface_index: 0,
peer_ip,
local_ip,
bgp_message: bgp_msg,
})),
};
Ok(Elementor::new().record_to_elems(record))
}
fn get_micro_seconds(sec: f64) -> u32 {
((sec.fract().abs() * 1_000_000.0).round() as u32).min(999_999)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ris_live_message() {
let message = r#"
{
"type": "ris_message",
"data": {
"timestamp": 1636245154.8,
"peer": "2001:7f8:b:100:1d1:a520:1333:74",
"peer_asn": "201333",
"id": "10-183678-175313836",
"host": "rrc10",
"type": "UPDATE",
"path": [
201333,
6762,
174,
20473
],
"community": [
[
6762,
30
],
[
6762,
14400
]
],
"origin": "igp",
"announcements": [
{
"next_hop": "2001:7f8:b:100:1d1:a520:1333:74",
"prefixes": [
"2a0e:97c7::/48",
"2a0e:97c6:fe::/48",
"2a10:cc42:17b7::/48",
"2a10:cc42:1feb::/48"
]
},
{
"next_hop": "fe80::217:a3ff:fefe:2905",
"prefixes": [
"2a0e:97c7::/48",
"2a0e:97c6:fe::/48",
"2a10:cc42:17b7::/48",
"2a10:cc42:1feb::/48"
]
}
],
"raw": "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0086020000006F4001010040021202040003127500001A6A000000AE00004FF980040400000000C008081A6A001E1A6A3840800E4100020120200107F8000B010001D1A52013330074FE800000000000000217A3FFFEFE290500302A0E97C70000302A0E97C600FE302A10CC4217B7302A10CC421FEB"
}
}
"#;
let res = parse_raw_bytes(message);
for elem in res.unwrap() {
println!("{elem}");
}
}
fn ris_message_with_raw(raw: Option<&str>) -> String {
let raw_field = raw
.map(|raw| format!(r#", "raw": "{raw}""#))
.unwrap_or_default();
format!(
r#"{{
"type": "ris_message",
"data": {{
"timestamp": 1636245154.8,
"peer": "192.0.2.1",
"peer_asn": "64496",
"id": "00-192-0-2-1-1",
"host": "rrc00",
"type": "UPDATE"{raw_field}
}}
}}"#
)
}
#[test]
fn ris_live_message_missing_raw_returns_incorrect_raw_bytes() {
let err = parse_raw_bytes(&ris_message_with_raw(None)).unwrap_err();
assert!(matches!(err, ParserRisliveError::IncorrectRawBytes));
}
#[test]
fn ris_live_message_malformed_hex_returns_incorrect_raw_bytes() {
let err = parse_raw_bytes(&ris_message_with_raw(Some("not-hex"))).unwrap_err();
assert!(matches!(err, ParserRisliveError::IncorrectRawBytes));
}
#[test]
fn ris_live_message_invalid_bgp_raw_returns_incorrect_raw_bytes() {
let err = parse_raw_bytes(&ris_message_with_raw(Some("00"))).unwrap_err();
assert!(matches!(err, ParserRisliveError::IncorrectRawBytes));
}
#[test]
fn non_ris_message_returns_unsupported_message() {
let err = parse_raw_bytes(r#"{"type":"pong","data":null}"#).unwrap_err();
assert!(matches!(err, ParserRisliveError::UnsupportedMessage));
}
#[test]
fn ris_live_message_missing_required_id_or_host_returns_incorrect_json() {
let err = parse_raw_bytes(
r#"{
"type": "ris_message",
"data": {
"timestamp": 1636245154.8,
"peer": "192.0.2.1",
"peer_asn": "64496",
"type": "UPDATE",
"raw": "00"
}
}"#,
)
.unwrap_err();
assert!(matches!(err, ParserRisliveError::IncorrectJson(_)));
}
const TIER2_UPDATE_RAW: &str = "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF004102000000264001010040020602010000FC00400304C00002018009040A000001800A080A0000010A00000218C63364";
const MISSING_MANDATORY_RAW: &str = "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF001B020000000018C63364";
const KEEPALIVE_RAW: &str = "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF001304";
#[test]
fn raw_full_exposes_tier2_attributes() {
let full =
parse_ris_live_message_raw_full(&ris_message_with_raw(Some(TIER2_UPDATE_RAW))).unwrap();
assert_eq!(full.meta.host, "rrc00");
assert_eq!(full.meta.id, "00-192-0-2-1-1");
assert_eq!(full.meta.peer.to_string(), "192.0.2.1");
assert_eq!(full.meta.peer_asn, Asn::new_32bit(64496));
assert_eq!(full.meta.timestamp, 1636245154.8);
assert_eq!(full.elems.len(), 1);
assert_eq!(full.elems[0].prefix.to_string(), "198.51.100.0/24");
assert!(full.attributes.get_attr(AttrType::ORIGIN).is_some());
assert!(full.attributes.get_attr(AttrType::AS_PATH).is_some());
assert!(full.attributes.get_attr(AttrType::ORIGINATOR_ID).is_some());
assert!(full.attributes.get_attr(AttrType::CLUSTER_LIST).is_some());
}
#[test]
fn raw_full_elems_match_legacy_raw_parser() {
let message = ris_message_with_raw(Some(TIER2_UPDATE_RAW));
let legacy = parse_ris_live_message_raw(&message).unwrap();
let full = parse_ris_live_message_raw_full(&message).unwrap();
assert_eq!(full.elems, legacy);
}
#[test]
fn raw_full_collects_validation_warnings() {
let full =
parse_ris_live_message_raw_full(&ris_message_with_raw(Some(MISSING_MANDATORY_RAW)))
.unwrap();
assert!(!full.validation_warnings.is_empty());
assert!(
full.validation_warnings
.iter()
.any(|w| matches!(w, BgpValidationWarning::MissingWellKnownAttribute { .. })),
"expected a MissingWellKnownAttribute warning, got: {:?}",
full.validation_warnings
);
}
#[test]
fn raw_full_non_update_returns_empty_payloads() {
let message = format!(
r#"{{"type": "ris_message", "data": {{
"timestamp": 1636245154.8,
"peer": "192.0.2.1",
"peer_asn": "64496",
"id": "00-192-0-2-1-1",
"host": "rrc00",
"type": "KEEPALIVE",
"raw": "{KEEPALIVE_RAW}"
}}}}"#
);
let full = parse_ris_live_message_raw_full(&message).unwrap();
assert!(full.elems.is_empty());
assert!(full.validation_warnings.is_empty());
assert!(serde_json::to_value(&full.attributes)
.unwrap()
.as_array()
.unwrap()
.is_empty());
}
#[test]
fn raw_full_missing_raw_returns_incorrect_raw_bytes() {
let err = parse_ris_live_message_raw_full(&ris_message_with_raw(None)).unwrap_err();
assert!(matches!(err, ParserRisliveError::IncorrectRawBytes));
}
}