diff --git a/Cargo.toml b/Cargo.toml index fa473b2b..4cb020dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -108,8 +108,8 @@ anyhow = "1" bgpkit-broker = "0.7.0-beta.5" env_logger = "0.11" kafka = "0.10.0" -tungstenite = "0.24.0" -tokio-tungstenite = "0.24.0" +tungstenite = "0.27.0" +tokio-tungstenite = "0.27.0" tokio = { version = "1", features = ["full"] } futures-util = "0.3.30" criterion = { version = "0.5.1", features = ["html_reports"] } @@ -145,3 +145,7 @@ required-features = ["serde"] [package.metadata.binstall] pkg-url = "{ repo }/releases/download/v{ version }/{ name }-{ target }.tar.gz" pkg-fmt = "tgz" + + +[lints.clippy] +uninlined_format_args = "allow" \ No newline at end of file diff --git a/README.md b/README.md index 1aa1c8bb..1250de65 100644 --- a/README.md +++ b/README.md @@ -156,7 +156,7 @@ fn main() { // subscribe to messages from one collector let msg = json!({"type": "ris_subscribe", "data": {"host": "rrc21"}}).to_string(); - socket.send(Message::Text(msg)).unwrap(); + socket.send(Message::Text(msg.into())).unwrap(); loop { let msg = socket.read().expect("Error reading message").to_string(); diff --git a/benches/bench_main.rs b/benches/bench_main.rs index 00c231d6..195d167a 100644 --- a/benches/bench_main.rs +++ b/benches/bench_main.rs @@ -117,7 +117,7 @@ fn benchmark(c: &mut Criterion) { programs_to_test.push((program, path)); } Err(err) => { - println!("Unable to locate executable for {}: {}", name, err); + println!("Unable to locate executable for {name}: {err}"); } } } diff --git a/benches/data_source.rs b/benches/data_source.rs index 060f316f..fc274810 100644 --- a/benches/data_source.rs +++ b/benches/data_source.rs @@ -62,7 +62,7 @@ pub fn download_test_data() { ), }; - if format!("{:x}", computed_checksum).as_str() == checksum { + if format!("{computed_checksum:x}").as_str() == checksum { continue; } } @@ -75,8 +75,8 @@ pub fn download_test_data() { match download_file(url, &data_file_path) { Ok(download_checksum) => { - if format!("{:x}", download_checksum).as_str() != checksum { - println!("MD5 checksum for downloaded file ({:x}) does not match expected checksum ({:?}).", download_checksum, checksum); + if format!("{download_checksum:x}").as_str() != checksum { + println!("MD5 checksum for downloaded file ({download_checksum:x}) does not match expected checksum ({checksum:?})."); println!("Perhaps a different file is being used?"); panic!("Unable to find expected test data file") @@ -96,7 +96,7 @@ fn md5_checksum_for_file>(path: P) -> io::Result { let mut file = File::open(path)?; io::copy(&mut file, &mut context)?; - Ok(context.compute()) + Ok(context.finalize()) } struct HashingWriter { @@ -130,7 +130,7 @@ fn download_file>(url: &str, target: P) -> io::Result { io::copy(&mut reader, &mut writer)?; writer.flush()?; - Ok(writer.hasher.compute()) + Ok(writer.hasher.finalize()) } pub fn test_data_dir() -> PathBuf { diff --git a/examples/bmp_listener.rs b/examples/bmp_listener.rs index 6fbd7a55..2a082c5e 100644 --- a/examples/bmp_listener.rs +++ b/examples/bmp_listener.rs @@ -19,10 +19,10 @@ fn handle_client(mut stream: TcpStream) { // Convert the received data to a hexadecimal string let hex_string = buffer[..bytes_read] .iter() - .map(|b| format!("{:02X}", b)) + .map(|b| format!("{b:02X}")) .collect::>() .join(" "); - println!("Received data (hex): {}", hex_string); + println!("Received data (hex): {hex_string}"); let mut data = Bytes::from(buffer[..bytes_read].to_vec()); while data.remaining() > 0 { @@ -36,7 +36,7 @@ fn handle_client(mut stream: TcpStream) { } } Err(e) => { - eprintln!("Error reading from socket: {}", e); + eprintln!("Error reading from socket: {e}"); break; } } @@ -58,7 +58,7 @@ fn main() { child_thread.join().expect("Thread panicked"); } Err(e) => { - eprintln!("Error accepting connection: {}", e); + eprintln!("Error accepting connection: {e}"); } } } diff --git a/examples/count_elems.rs b/examples/count_elems.rs index b42220bf..95fea2a6 100644 --- a/examples/count_elems.rs +++ b/examples/count_elems.rs @@ -4,5 +4,5 @@ use bgpkit_parser::BgpkitParser; fn main() { let url = "http://archive.routeviews.org/bgpdata/2021.10/UPDATES/updates.20211001.0000.bz2"; let count = BgpkitParser::new(url).unwrap().into_iter().count(); - println!("{}", count); + println!("{count}"); } diff --git a/examples/mrt_filter_archiver.rs b/examples/mrt_filter_archiver.rs index 7dff99b2..be3b859b 100644 --- a/examples/mrt_filter_archiver.rs +++ b/examples/mrt_filter_archiver.rs @@ -36,10 +36,7 @@ fn main() { // make sure to properly flush bytes from writer drop(mrt_writer); - println!( - "Found and archived {} MRT records, {} BGP messages", - records_count, elems_count - ); + println!("Found and archived {records_count} MRT records, {elems_count} BGP messages"); let elems = bgpkit_parser::BgpkitParser::new(OUTPUT_FILE) .unwrap() diff --git a/examples/real-time-ris-live-websocket-async.rs b/examples/real-time-ris-live-websocket-async.rs index 912a67ba..ae1437e1 100644 --- a/examples/real-time-ris-live-websocket-async.rs +++ b/examples/real-time-ris-live-websocket-async.rs @@ -15,7 +15,7 @@ async fn main() { let msg = RisSubscribe::new().host("rrc21"); let (mut write, mut read) = ws_stream.split(); write - .send(Message::Text(msg.to_json_string())) + .send(Message::Text(msg.to_json_string().into())) .await .unwrap(); @@ -30,11 +30,11 @@ async fn main() { match parse_ris_live_message(msg_str.as_str()) { Ok(elems) => { for elem in elems { - println!("{}", elem); + println!("{elem}"); } } Err(err) => { - eprintln!("{}", err); + eprintln!("{err}"); } } } diff --git a/examples/real-time-ris-live-websocket.rs b/examples/real-time-ris-live-websocket.rs index af768cbd..dfa9a6bc 100644 --- a/examples/real-time-ris-live-websocket.rs +++ b/examples/real-time-ris-live-websocket.rs @@ -14,7 +14,9 @@ fn main() { // subscribe to messages from one collector let msg = RisSubscribe::new().host("rrc21"); - socket.send(Message::Text(msg.to_json_string())).unwrap(); + socket + .send(Message::Text(msg.to_json_string().into())) + .unwrap(); loop { let msg = socket.read().expect("Error reading message").to_string(); @@ -24,11 +26,11 @@ fn main() { match parse_ris_live_message(msg.as_str()) { Ok(elems) => { for elem in elems { - println!("{}", elem); + println!("{elem}"); } } Err(error) => { - println!("{:?}", error); + println!("{error:?}"); } } } diff --git a/src/bin/main.rs b/src/bin/main.rs index e2cf01b7..c2db69b4 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -114,7 +114,7 @@ fn main() { let mut parser = match parser_opt { Ok(p) => p, Err(err) => { - eprintln!("{}", err); + eprintln!("{err}"); std::process::exit(1); } }; @@ -186,8 +186,8 @@ fn main() { records_count += 1; elems_count += elementor.record_to_elems(record).len(); } - println!("total records: {}", records_count); - println!("total elems: {}", elems_count); + println!("total records: {records_count}"); + println!("total elems: {elems_count}"); } (false, true) => { println!("total records: {}", parser.into_record_iter().count()); @@ -216,7 +216,7 @@ fn main() { }; if let Err(e) = writeln!(stdout, "{}", &output_str) { if e.kind() != std::io::ErrorKind::BrokenPipe { - eprintln!("{}", e); + eprintln!("{e}"); } std::process::exit(1); } diff --git a/src/error.rs b/src/error.rs index be713203..fbe913f0 100644 --- a/src/error.rs +++ b/src/error.rs @@ -43,15 +43,15 @@ impl Error for ParserErrorWithBytes {} impl Display for ParserError { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { - ParserError::IoError(e) => write!(f, "Error: {}", e), - ParserError::EofError(e) => write!(f, "Error: {}", e), - ParserError::ParseError(s) => write!(f, "Error: {}", s), - ParserError::TruncatedMsg(s) => write!(f, "Error: {}", s), - ParserError::Unsupported(s) => write!(f, "Error: {}", s), + ParserError::IoError(e) => write!(f, "Error: {e}"), + ParserError::EofError(e) => write!(f, "Error: {e}"), + ParserError::ParseError(s) => write!(f, "Error: {s}"), + ParserError::TruncatedMsg(s) => write!(f, "Error: {s}"), + ParserError::Unsupported(s) => write!(f, "Error: {s}"), ParserError::EofExpected => write!(f, "Error: reach end of file"), #[cfg(feature = "oneio")] - ParserError::OneIoError(e) => write!(f, "Error: {}", e), - ParserError::FilterError(e) => write!(f, "Error: {}", e), + ParserError::OneIoError(e) => write!(f, "Error: {e}"), + ParserError::FilterError(e) => write!(f, "Error: {e}"), } } } diff --git a/src/lib.rs b/src/lib.rs index 8d154606..39adb7ff 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -147,7 +147,7 @@ fn main() { // subscribe to messages from one collector let msg = json!({"type": "ris_subscribe", "data": {"host": "rrc21"}}).to_string(); - socket.send(Message::Text(msg)).unwrap(); + socket.send(Message::Text(msg.into())).unwrap(); loop { let msg = socket.read().expect("Error reading message").to_string(); @@ -406,8 +406,6 @@ We support normal communities, extended communities, and large communities. html_logo_url = "https://raw.githubusercontent.com/bgpkit/assets/main/logos/icon-transparent.png", html_favicon_url = "https://raw.githubusercontent.com/bgpkit/assets/main/logos/favicon.ico" )] -#![allow(clippy::new_without_default)] -#![allow(clippy::needless_range_loop)] #[cfg(feature = "parser")] pub mod encoder; diff --git a/src/models/bgp/attributes/aspath.rs b/src/models/bgp/attributes/aspath.rs index 8cf22587..6db2457e 100644 --- a/src/models/bgp/attributes/aspath.rs +++ b/src/models/bgp/attributes/aspath.rs @@ -764,10 +764,10 @@ impl Display for AsPath { AsPathSegment::AsSequence(v) | AsPathSegment::ConfedSequence(v) => { let mut asn_iter = v.iter(); if let Some(first_element) = asn_iter.next() { - write!(f, "{}", first_element)?; + write!(f, "{first_element}")?; for asn in asn_iter { - write!(f, " {}", asn)?; + write!(f, " {asn}")?; } } } @@ -775,10 +775,10 @@ impl Display for AsPath { write!(f, "{{")?; let mut asn_iter = v.iter(); if let Some(first_element) = asn_iter.next() { - write!(f, "{}", first_element)?; + write!(f, "{first_element}")?; for asn in asn_iter { - write!(f, ",{}", asn)?; + write!(f, ",{asn}")?; } } write!(f, "}}")?; diff --git a/src/models/bgp/attributes/mod.rs b/src/models/bgp/attributes/mod.rs index 212c150e..8811aba2 100644 --- a/src/models/bgp/attributes/mod.rs +++ b/src/models/bgp/attributes/mod.rs @@ -691,7 +691,7 @@ mod tests { assert!(!aspath_attr.is_optional()); for attr in attributes.iter() { - println!("{:?}", attr); + println!("{attr:?}"); } } diff --git a/src/models/bgp/attributes/origin.rs b/src/models/bgp/attributes/origin.rs index 33345ac3..e066c072 100644 --- a/src/models/bgp/attributes/origin.rs +++ b/src/models/bgp/attributes/origin.rs @@ -52,10 +52,10 @@ mod tests { #[test] fn test_display() { let origin = Origin::IGP; - assert_eq!(format!("{}", origin), "IGP"); + assert_eq!(format!("{origin}"), "IGP"); let origin = Origin::EGP; - assert_eq!(format!("{}", origin), "EGP"); + assert_eq!(format!("{origin}"), "EGP"); let origin = Origin::INCOMPLETE; - assert_eq!(format!("{}", origin), "INCOMPLETE"); + assert_eq!(format!("{origin}"), "INCOMPLETE"); } } diff --git a/src/models/bgp/community.rs b/src/models/bgp/community.rs index bc1a574a..c2142bc3 100644 --- a/src/models/bgp/community.rs +++ b/src/models/bgp/community.rs @@ -192,7 +192,7 @@ struct ToHexString<'a>(&'a [u8]); impl Display for ToHexString<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { for byte in self.0 { - write!(f, "{:02X}", byte)?; + write!(f, "{byte:02X}")?; } Ok(()) } @@ -204,7 +204,7 @@ impl Display for Community { Community::NoExport => write!(f, "no-export"), Community::NoAdvertise => write!(f, "no-advertise"), Community::NoExportSubConfed => write!(f, "no-export-sub-confed"), - Community::Custom(asn, value) => write!(f, "{}:{}", asn, value), + Community::Custom(asn, value) => write!(f, "{asn}:{value}"), } } } @@ -283,10 +283,10 @@ impl Display for Ipv6AddrExtCommunity { impl Display for MetaCommunity { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { - MetaCommunity::Plain(c) => write!(f, "{}", c), - MetaCommunity::Extended(c) => write!(f, "{}", c), - MetaCommunity::Large(c) => write!(f, "{}", c), - MetaCommunity::Ipv6Extended(c) => write!(f, "{}", c), + MetaCommunity::Plain(c) => write!(f, "{c}"), + MetaCommunity::Extended(c) => write!(f, "{c}"), + MetaCommunity::Large(c) => write!(f, "{c}"), + MetaCommunity::Ipv6Extended(c) => write!(f, "{c}"), } } } @@ -335,7 +335,7 @@ mod tests { #[test] fn test_display_large_community() { let large_community = LargeCommunity::new(1, [2, 3]); - assert_eq!(format!("{}", large_community), "1:2:3"); + assert_eq!(format!("{large_community}"), "1:2:3"); } #[test] @@ -346,7 +346,7 @@ mod tests { local_admin: [0; 4], }; let extended_community = ExtendedCommunity::TransitiveTwoOctetAs(two_octet_as_ext_comm); - assert_eq!(format!("{}", extended_community), "0:0:0:00000000"); + assert_eq!(format!("{extended_community}"), "0:0:0:00000000"); let two_octet_as_ext_comm = TwoOctetAsExtCommunity { subtype: 0, @@ -354,7 +354,7 @@ mod tests { local_admin: [0; 4], }; let extended_community = ExtendedCommunity::NonTransitiveTwoOctetAs(two_octet_as_ext_comm); - assert_eq!(format!("{}", extended_community), "64:0:0:00000000"); + assert_eq!(format!("{extended_community}"), "64:0:0:00000000"); let ipv4_ext_comm = Ipv4AddrExtCommunity { subtype: 1, @@ -362,7 +362,7 @@ mod tests { local_admin: [5, 6], }; let extended_community = ExtendedCommunity::TransitiveIpv4Addr(ipv4_ext_comm); - assert_eq!(format!("{}", extended_community), "1:1:192.168.1.1:0506"); + assert_eq!(format!("{extended_community}"), "1:1:192.168.1.1:0506"); let ipv4_ext_comm = Ipv4AddrExtCommunity { subtype: 1, @@ -370,7 +370,7 @@ mod tests { local_admin: [5, 6], }; let extended_community = ExtendedCommunity::NonTransitiveIpv4Addr(ipv4_ext_comm); - assert_eq!(format!("{}", extended_community), "65:1:192.168.1.1:0506"); + assert_eq!(format!("{extended_community}"), "65:1:192.168.1.1:0506"); let four_octet_as_ext_comm = FourOctetAsExtCommunity { subtype: 2, @@ -378,7 +378,7 @@ mod tests { local_admin: [7, 8], }; let extended_community = ExtendedCommunity::TransitiveFourOctetAs(four_octet_as_ext_comm); - assert_eq!(format!("{}", extended_community), "2:2:64512:0708"); + assert_eq!(format!("{extended_community}"), "2:2:64512:0708"); let four_octet_as_ext_comm = FourOctetAsExtCommunity { subtype: 2, @@ -387,25 +387,25 @@ mod tests { }; let extended_community = ExtendedCommunity::NonTransitiveFourOctetAs(four_octet_as_ext_comm); - assert_eq!(format!("{}", extended_community), "66:2:64512:0708"); + assert_eq!(format!("{extended_community}"), "66:2:64512:0708"); let opaque_ext_comm = OpaqueExtCommunity { subtype: 3, value: [9, 10, 11, 12, 13, 14], }; let extended_community = ExtendedCommunity::TransitiveOpaque(opaque_ext_comm); - assert_eq!(format!("{}", extended_community), "3:3:090A0B0C0D0E"); + assert_eq!(format!("{extended_community}"), "3:3:090A0B0C0D0E"); let opaque_ext_comm = OpaqueExtCommunity { subtype: 3, value: [9, 10, 11, 12, 13, 14], }; let extended_community = ExtendedCommunity::NonTransitiveOpaque(opaque_ext_comm); - assert_eq!(format!("{}", extended_community), "67:3:090A0B0C0D0E"); + assert_eq!(format!("{extended_community}"), "67:3:090A0B0C0D0E"); let raw_ext_comm = [0, 1, 2, 3, 4, 5, 6, 7]; let extended_community = ExtendedCommunity::Raw(raw_ext_comm); - assert_eq!(format!("{}", extended_community), "0001020304050607"); + assert_eq!(format!("{extended_community}"), "0001020304050607"); } #[test] @@ -417,7 +417,7 @@ mod tests { local_admin: [0, 1], }; assert_eq!( - format!("{}", ipv6_addr_ext_comm), + format!("{ipv6_addr_ext_comm}"), "0:0:2001:db8::8a2e:370:7334:0001" ); } @@ -426,7 +426,31 @@ mod tests { fn test_display_meta_community() { let large_community = LargeCommunity::new(1, [2, 3]); let meta_community = MetaCommunity::Large(large_community); - assert_eq!(format!("{}", meta_community), "1:2:3"); + assert_eq!(format!("{meta_community}"), "1:2:3"); + } + + #[test] + fn test_to_hex_string() { + // Test empty array + assert_eq!(format!("{}", ToHexString(&[])), ""); + + // Test single byte + assert_eq!(format!("{}", ToHexString(&[0x0A])), "0A"); + + // Test multiple bytes + assert_eq!(format!("{}", ToHexString(&[0x0A, 0x0B, 0x0C])), "0A0B0C"); + + // Test zero byte + assert_eq!(format!("{}", ToHexString(&[0x00])), "00"); + + // Test byte with value > 0x0F (needs two hex digits) + assert_eq!(format!("{}", ToHexString(&[0x10])), "10"); + + // Test mixed bytes + assert_eq!( + format!("{}", ToHexString(&[0x00, 0x0F, 0x10, 0xFF])), + "000F10FF" + ); } #[test] diff --git a/src/models/bgp/elem.rs b/src/models/bgp/elem.rs index 1eb70604..f7796c3d 100644 --- a/src/models/bgp/elem.rs +++ b/src/models/bgp/elem.rs @@ -208,7 +208,7 @@ impl Display for OptionToStr<'_, T> { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self.0 { None => Ok(()), - Some(x) => write!(f, "{}", x), + Some(x) => write!(f, "{x}"), } } } diff --git a/src/models/err.rs b/src/models/err.rs index 2a4bb49e..ed6f30be 100644 --- a/src/models/err.rs +++ b/src/models/err.rs @@ -11,7 +11,7 @@ impl Display for BgpModelsError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { BgpModelsError::PrefixParsingError(msg) => { - write!(f, "cannot convert str to IP prefix: {}", msg) + write!(f, "cannot convert str to IP prefix: {msg}") } } } diff --git a/src/models/network/asn.rs b/src/models/network/asn.rs index 063f9000..a58ede65 100644 --- a/src/models/network/asn.rs +++ b/src/models/network/asn.rs @@ -363,9 +363,9 @@ mod tests { let asn = Asn::from_str("AS12345").unwrap(); assert_eq!(12345, asn.to_u32()); let asn = Asn::new_32bit(12345); - assert_eq!("12345", format!("{}", asn)); + assert_eq!("12345", format!("{asn}")); let asn = Asn::new_32bit(12345); - assert_eq!("12345", format!("{:?}", asn)); + assert_eq!("12345", format!("{asn:?}")); } #[test] diff --git a/src/models/network/nexthop.rs b/src/models/network/nexthop.rs index f3e000de..a10a2928 100644 --- a/src/models/network/nexthop.rs +++ b/src/models/network/nexthop.rs @@ -36,9 +36,9 @@ impl NextHopAddress { impl Debug for NextHopAddress { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { - NextHopAddress::Ipv4(x) => write!(f, "{}", x), - NextHopAddress::Ipv6(x) => write!(f, "{}", x), - NextHopAddress::Ipv6LinkLocal(x, y) => write!(f, "Ipv6LinkLocal({}, {})", x, y), + NextHopAddress::Ipv4(x) => write!(f, "{x}"), + NextHopAddress::Ipv6(x) => write!(f, "{x}"), + NextHopAddress::Ipv6LinkLocal(x, y) => write!(f, "Ipv6LinkLocal({x}, {y})"), } } } @@ -46,9 +46,9 @@ impl Debug for NextHopAddress { impl Display for NextHopAddress { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { - NextHopAddress::Ipv4(v) => write!(f, "{}", v), - NextHopAddress::Ipv6(v) => write!(f, "{}", v), - NextHopAddress::Ipv6LinkLocal(v, _) => write!(f, "{}", v), + NextHopAddress::Ipv4(v) => write!(f, "{v}"), + NextHopAddress::Ipv6(v) => write!(f, "{v}"), + NextHopAddress::Ipv6LinkLocal(v, _) => write!(f, "{v}"), } } } @@ -134,10 +134,10 @@ mod tests { let next_hop_ipv6_link_local = NextHopAddress::Ipv6LinkLocal(ipv6_link_local_addrs.0, ipv6_link_local_addrs.1); - assert_eq!(format!("{:?}", next_hop_ipv4), "192.0.2.1"); - assert_eq!(format!("{:?}", next_hop_ipv6), "2001:db8::1"); + assert_eq!(format!("{next_hop_ipv4:?}"), "192.0.2.1"); + assert_eq!(format!("{next_hop_ipv6:?}"), "2001:db8::1"); assert_eq!( - format!("{:?}", next_hop_ipv6_link_local), + format!("{next_hop_ipv6_link_local:?}"), "Ipv6LinkLocal(fe80::, fe80::)" ); } @@ -156,8 +156,8 @@ mod tests { let next_hop_ipv6_link_local = NextHopAddress::Ipv6LinkLocal(ipv6_link_local_addrs.0, ipv6_link_local_addrs.1); - assert_eq!(format!("{}", next_hop_ipv4), "192.0.2.1"); - assert_eq!(format!("{}", next_hop_ipv6), "2001:db8::1"); - assert_eq!(format!("{}", next_hop_ipv6_link_local), "fe80::"); + assert_eq!(format!("{next_hop_ipv4}"), "192.0.2.1"); + assert_eq!(format!("{next_hop_ipv6}"), "2001:db8::1"); + assert_eq!(format!("{next_hop_ipv6_link_local}"), "fe80::"); } } diff --git a/src/models/network/prefix.rs b/src/models/network/prefix.rs index cbd1e0f6..c58761f2 100644 --- a/src/models/network/prefix.rs +++ b/src/models/network/prefix.rs @@ -192,6 +192,6 @@ mod tests { fn test_debug() { let prefix = IpNet::from_str("192.168.0.0/24").unwrap(); let network_prefix = NetworkPrefix::new(prefix, 1); - assert_eq!(format!("{:?}", network_prefix), "192.168.0.0/24#1"); + assert_eq!(format!("{network_prefix:?}"), "192.168.0.0/24#1"); } } diff --git a/src/parser/bgp/attributes/attr_02_17_as_path.rs b/src/parser/bgp/attributes/attr_02_17_as_path.rs index 05f093f8..1ac8c30f 100644 --- a/src/parser/bgp/attributes/attr_02_17_as_path.rs +++ b/src/parser/bgp/attributes/attr_02_17_as_path.rs @@ -34,8 +34,7 @@ fn parse_as_path_segment( AS_PATH_CONFED_SEQUENCE => Ok(AsPathSegment::ConfedSequence(path)), AS_PATH_CONFED_SET => Ok(AsPathSegment::ConfedSet(path)), _ => Err(ParserError::ParseError(format!( - "Invalid AS path segment type: {}", - segment_type + "Invalid AS path segment type: {segment_type}" ))), } } diff --git a/src/parser/bgp/attributes/attr_03_next_hop.rs b/src/parser/bgp/attributes/attr_03_next_hop.rs index 6247be94..81224397 100644 --- a/src/parser/bgp/attributes/attr_03_next_hop.rs +++ b/src/parser/bgp/attributes/attr_03_next_hop.rs @@ -25,8 +25,7 @@ pub fn parse_mp_next_hop(mut input: Bytes) -> Result, Par )), v => { return Err(ParserError::ParseError(format!( - "Invalid next hop length found: {}", - v + "Invalid next hop length found: {v}" ))); } }; diff --git a/src/parser/bgp/attributes/attr_14_15_nlri.rs b/src/parser/bgp/attributes/attr_14_15_nlri.rs index 877a6403..647cd20f 100644 --- a/src/parser/bgp/attributes/attr_14_15_nlri.rs +++ b/src/parser/bgp/attributes/attr_14_15_nlri.rs @@ -270,7 +270,7 @@ mod tests { assert_eq!(nlri.prefixes[0], prefix); assert_eq!(nlri.prefixes[0].path_id, prefix.path_id); } else { - panic!("Unexpected result: {:?}", res); + panic!("Unexpected result: {res:?}"); } } @@ -331,4 +331,90 @@ mod tests { ]) ); } + + #[test] + fn test_parsing_unreachable_nlri() { + // Test unreachable NLRI (withdrawals) + let test_bytes = Bytes::from(vec![ + 0x00, 0x01, // address family: IPv4 + 0x01, // safi: unicast + // No next hop for unreachable NLRI + // No reserved byte for unreachable NLRI + // NLRI + 0x18, // 24 bits prefix length + 0xC0, 0x00, 0x02, // 192.0.2 + ]); + let res = parse_nlri(test_bytes, &None, &None, &None, false, false); + + if let Ok(AttributeValue::MpUnreachNlri(nlri)) = res { + assert_eq!(nlri.afi, Afi::Ipv4); + assert_eq!(nlri.safi, Safi::Unicast); + assert_eq!(nlri.next_hop, None); + assert_eq!( + nlri.prefixes, + vec![NetworkPrefix::from_str("192.0.2.0/24").unwrap()] + ); + } else { + panic!("Unexpected result: {res:?}"); + } + } + + #[test] + fn test_encode_unreachable_nlri() { + // Test encoding unreachable NLRI (withdrawals) + let nlri = Nlri { + afi: Afi::Ipv4, + safi: Safi::Unicast, + next_hop: None, + prefixes: vec![NetworkPrefix { + prefix: IpNet::from_str("192.0.1.0/24").unwrap(), + path_id: 0, + }], + }; + let bytes = encode_nlri(&nlri, false, false); + assert_eq!( + bytes, + Bytes::from(vec![ + 0x00, 0x01, // address family: IPv4 + 0x01, // safi: unicast + // No next hop for unreachable NLRI + // No reserved byte for unreachable NLRI + // NLRI + 0x18, // 24 bits prefix length + 0xC0, 0x00, 0x01, // 192.0.1 + ]) + ); + + // Test that the encoded bytes can be parsed back correctly + let parsed_nlri = parse_nlri(bytes, &None, &None, &None, false, false).unwrap(); + assert_eq!(parsed_nlri, AttributeValue::MpUnreachNlri(nlri)); + + // Test with next_hop set (should be encoded for unreachable NLRI) + let nlri_with_next_hop = Nlri { + afi: Afi::Ipv4, + safi: Safi::Unicast, + next_hop: Some(NextHopAddress::Ipv4( + Ipv4Addr::from_str("10.0.0.1").unwrap(), + )), + prefixes: vec![NetworkPrefix { + prefix: IpNet::from_str("192.0.1.0/24").unwrap(), + path_id: 0, + }], + }; + let bytes = encode_nlri(&nlri_with_next_hop, false, false); + // The encoded bytes should include the next_hop + assert_eq!( + bytes, + Bytes::from(vec![ + 0x00, 0x01, // address family: IPv4 + 0x01, // safi: unicast + 0x04, // next hop length: 4 + 0x0A, 0x00, 0x00, 0x01, // next hop: 10.0.0.1 + // No reserved byte for unreachable NLRI + // NLRI + 0x18, // 24 bits prefix length + 0xC0, 0x00, 0x01, // 192.0.1 + ]) + ); + } } diff --git a/src/parser/bgp/attributes/mod.rs b/src/parser/bgp/attributes/mod.rs index fa9814c7..a9aff886 100644 --- a/src/parser/bgp/attributes/mod.rs +++ b/src/parser/bgp/attributes/mod.rs @@ -185,8 +185,7 @@ pub fn parse_attributes( } AttrType::ONLY_TO_CUSTOMER => parse_only_to_customer(attr_data), _ => Err(ParserError::Unsupported(format!( - "unsupported attribute type: {:?}", - attr_type + "unsupported attribute type: {attr_type:?}" ))), }; diff --git a/src/parser/bgp/messages.rs b/src/parser/bgp/messages.rs index d083e31b..7b040613 100644 --- a/src/parser/bgp/messages.rs +++ b/src/parser/bgp/messages.rs @@ -49,8 +49,7 @@ pub fn parse_bgp_message( let length = data.get_u16(); if !(19..=4096).contains(&length) { return Err(ParserError::ParseError(format!( - "invalid BGP message length {}", - length + "invalid BGP message length {length}" ))); } diff --git a/src/parser/bmp/messages/initiation_message.rs b/src/parser/bmp/messages/initiation_message.rs index 017298de..59df7319 100644 --- a/src/parser/bmp/messages/initiation_message.rs +++ b/src/parser/bmp/messages/initiation_message.rs @@ -92,7 +92,7 @@ mod tests { }], }; assert_eq!( - format!("{:?}", initiation_message), + format!("{initiation_message:?}"), "InitiationMessage { tlvs: [InitiationTlv { info_type: SysDescr, info_len: 5, info: \"Test1\" }] }" ); } diff --git a/src/parser/bmp/messages/route_monitoring.rs b/src/parser/bmp/messages/route_monitoring.rs index 59582b80..be85d0d2 100644 --- a/src/parser/bmp/messages/route_monitoring.rs +++ b/src/parser/bmp/messages/route_monitoring.rs @@ -65,7 +65,7 @@ mod tests { bgp_message: BgpMessage::Update(msg), }; assert_eq!( - format!("{:?}", mon_msg), + format!("{mon_msg:?}"), "RouteMonitoring { bgp_message: Update(BgpUpdateMessage { withdrawn_prefixes: [], attributes: Attributes { inner: [] }, announced_prefixes: [] }) }" ); } diff --git a/src/parser/bmp/messages/termination_message.rs b/src/parser/bmp/messages/termination_message.rs index d0f892f2..a2e0c286 100644 --- a/src/parser/bmp/messages/termination_message.rs +++ b/src/parser/bmp/messages/termination_message.rs @@ -122,7 +122,59 @@ mod tests { TerminationTlvValue::Reason(TerminationReason::UnspecifiedReason) ); } - Err(e) => panic!("Failed to parse: {}", e), + Err(e) => panic!("Failed to parse: {e}"), } } + + #[test] + fn test_parse_termination_message_truncated() { + // Test case where there's not enough bytes to read for a TLV + + // Case 1: Not enough bytes for info_len + let mut data = Bytes::copy_from_slice(&[ + 0, 0, // info_type: String + 0, // Only one byte for info_len, should be two + ]); + + let result = parse_termination_message(&mut data); + assert!(result.is_ok()); + assert_eq!(result.unwrap().tlvs.len(), 0); + + // Case 2: Not enough bytes for info_value + let mut data = Bytes::copy_from_slice(&[ + 0, 0, // info_type: String + 0, 5, // info_len: 5 + 67, 79, 68, // Only 3 bytes for info_value, should be 5 + ]); + + let result = parse_termination_message(&mut data); + // This should still succeed, but with an empty TLV list + // because the function breaks out of the loop when there's not enough bytes + assert!(result.is_ok()); + assert_eq!(result.unwrap().tlvs.len(), 0); + + // Case 3: Not enough bytes for the second TLV's info_value + let mut data = Bytes::copy_from_slice(&[ + 0, 0, // info_type: String + 0, 5, // info_len: 5 + 67, 79, 68, 69, 83, // info: "CODES" + 0, 1, // info_type: Reason + 0, 2, // info_len: 2 + 0, // Only one byte for info_value, should be two + ]); + + let result = parse_termination_message(&mut data); + // This should still succeed, but with only the first TLV + assert!(result.is_ok()); + let termination_message = result.unwrap(); + assert_eq!(termination_message.tlvs.len(), 1); + assert_eq!( + termination_message.tlvs[0].info_type, + TerminationTlvType::String + ); + assert_eq!( + termination_message.tlvs[0].info_value, + TerminationTlvValue::String("CODES".to_string()) + ); + } } diff --git a/src/parser/filter.rs b/src/parser/filter.rs index a749cbe1..cd35a6f3 100644 --- a/src/parser/filter.rs +++ b/src/parser/filter.rs @@ -118,43 +118,37 @@ impl Filter { "origin_asn" => match u32::from_str(filter_value) { Ok(v) => Ok(Filter::OriginAsn(v)), Err(_) => Err(FilterError(format!( - "cannot parse origin asn from {}", - filter_value + "cannot parse origin asn from {filter_value}" ))), }, "prefix" => match IpNet::from_str(filter_value) { Ok(v) => Ok(Filter::Prefix(v, PrefixMatchType::Exact)), Err(_) => Err(FilterError(format!( - "cannot parse prefix from {}", - filter_value + "cannot parse prefix from {filter_value}" ))), }, "prefix_super" => match IpNet::from_str(filter_value) { Ok(v) => Ok(Filter::Prefix(v, PrefixMatchType::IncludeSuper)), Err(_) => Err(FilterError(format!( - "cannot parse prefix from {}", - filter_value + "cannot parse prefix from {filter_value}" ))), }, "prefix_sub" => match IpNet::from_str(filter_value) { Ok(v) => Ok(Filter::Prefix(v, PrefixMatchType::IncludeSub)), Err(_) => Err(FilterError(format!( - "cannot parse prefix from {}", - filter_value + "cannot parse prefix from {filter_value}" ))), }, "prefix_super_sub" => match IpNet::from_str(filter_value) { Ok(v) => Ok(Filter::Prefix(v, PrefixMatchType::IncludeSuperSub)), Err(_) => Err(FilterError(format!( - "cannot parse prefix from {}", - filter_value + "cannot parse prefix from {filter_value}" ))), }, "peer_ip" => match IpAddr::from_str(filter_value) { Ok(v) => Ok(Filter::PeerIp(v)), Err(_) => Err(FilterError(format!( - "cannot parse peer IP from {}", - filter_value + "cannot parse peer IP from {filter_value}" ))), }, "peer_ips" => { @@ -163,10 +157,7 @@ impl Filter { match IpAddr::from_str(ip_str) { Ok(v) => ips.push(v), Err(_) => { - return Err(FilterError(format!( - "cannot parse peer IP from {}", - ip_str - ))) + return Err(FilterError(format!("cannot parse peer IP from {ip_str}"))) } } } @@ -175,55 +166,48 @@ impl Filter { "peer_asn" => match u32::from_str(filter_value) { Ok(v) => Ok(Filter::PeerAsn(v)), Err(_) => Err(FilterError(format!( - "cannot parse peer asn from {}", - filter_value + "cannot parse peer asn from {filter_value}" ))), }, "type" => match filter_value { "w" | "withdraw" | "withdrawal" => Ok(Filter::Type(ElemType::WITHDRAW)), "a" | "announce" | "announcement" => Ok(Filter::Type(ElemType::ANNOUNCE)), _ => Err(FilterError(format!( - "cannot parse elem type from {}", - filter_value + "cannot parse elem type from {filter_value}" ))), }, "ts_start" | "start_ts" => match parse_time_str(filter_value) { Some(t) => Ok(Filter::TsStart(t.and_utc().timestamp() as f64)), None => Err(FilterError(format!( - "cannot parse TsStart filter from {}", - filter_value + "cannot parse TsStart filter from {filter_value}" ))), }, "ts_end" | "end_ts" => match parse_time_str(filter_value) { Some(t) => Ok(Filter::TsEnd(t.and_utc().timestamp() as f64)), None => Err(FilterError(format!( - "cannot parse TsEnd filter from {}", - filter_value + "cannot parse TsEnd filter from {filter_value}" ))), }, "as_path" => match ComparableRegex::new(filter_value) { Ok(v) => Ok(Filter::AsPath(v)), Err(_) => Err(FilterError(format!( - "cannot parse AS path regex from {}", - filter_value + "cannot parse AS path regex from {filter_value}" ))), }, "community" => match ComparableRegex::new(filter_value) { Ok(v) => Ok(Filter::Community(v)), Err(_) => Err(FilterError(format!( - "cannot parse Community regex from {}", - filter_value + "cannot parse Community regex from {filter_value}" ))), }, "ip_version" | "ip" => match filter_value { "4" | "v4" | "ipv4" => Ok(Filter::IpVersion(IpVersion::Ipv4)), "6" | "v6" | "ipv6" => Ok(Filter::IpVersion(IpVersion::Ipv6)), _ => Err(FilterError(format!( - "cannot parse IP version from {}", - filter_value + "cannot parse IP version from {filter_value}" ))), }, - _ => Err(FilterError(format!("unknown filter type: {}", filter_type))), + _ => Err(FilterError(format!("unknown filter type: {filter_type}"))), } } } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 6a7acae6..872b9b7f 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -87,12 +87,12 @@ fn add_suffix_to_filename(filename: &str, suffix: &str) -> String { let mut parts: Vec<&str> = filename.split('.').collect(); // Split filename by dots if parts.len() > 1 { let last_part = parts.pop().unwrap(); // Remove the last part (suffix) from the parts vector - let new_last_part = format!("{}.{}", suffix, last_part); // Add the suffix to the last part + let new_last_part = format!("{suffix}.{last_part}"); // Add the suffix to the last part parts.push(&new_last_part); // Add the updated last part back to the parts vector parts.join(".") // Join the parts back into a filename string with dots } else { // If the filename does not have any dots, simply append the suffix to the end - format!("{}.{}", filename, suffix) + format!("{filename}.{suffix}") } } @@ -184,4 +184,37 @@ mod tests { let count = parser.into_elem_iter().count(); assert_eq!(8160, count); } + + #[test] + fn test_add_suffix_to_filename() { + // Test with a filename that has dots + let filename = "example.txt"; + let suffix = "suffix"; + let result = add_suffix_to_filename(filename, suffix); + assert_eq!(result, "example.suffix.txt"); + + // Test with a filename that has multiple dots + let filename = "example.tar.gz"; + let suffix = "suffix"; + let result = add_suffix_to_filename(filename, suffix); + assert_eq!(result, "example.tar.suffix.gz"); + + // Test with a filename that has no dots + let filename = "example"; + let suffix = "suffix"; + let result = add_suffix_to_filename(filename, suffix); + assert_eq!(result, "example.suffix"); + + // Test with an empty filename + let filename = ""; + let suffix = "suffix"; + let result = add_suffix_to_filename(filename, suffix); + assert_eq!(result, ".suffix"); + + // Test with an empty suffix + let filename = "example.txt"; + let suffix = ""; + let result = add_suffix_to_filename(filename, suffix); + assert_eq!(result, "example..txt"); + } } diff --git a/src/parser/mrt/messages/table_dump.rs b/src/parser/mrt/messages/table_dump.rs index 479b1744..29aaf1f8 100644 --- a/src/parser/mrt/messages/table_dump.rs +++ b/src/parser/mrt/messages/table_dump.rs @@ -51,8 +51,7 @@ pub fn parse_table_dump_message( 2 => Afi::Ipv6, _ => { return Err(ParserError::ParseError(format!( - "Invalid subtype found for TABLE_DUMP (V1) message: {}", - sub_type + "Invalid subtype found for TABLE_DUMP (V1) message: {sub_type}" ))) } }; @@ -247,4 +246,39 @@ mod tests { let encoded = table_dump_message.encode(); assert_eq!(encoded, bytes); } + + #[test] + fn test_parse_table_dump_message_invalid_subtype() { + // Create a simple byte array for testing + let mut bytes_mut = BytesMut::new(); + bytes_mut.put_u16(VIEW_NUMBER); + bytes_mut.put_u16(SEQUENCE_NUMBER); + let bytes = bytes_mut.freeze(); + + // Test with an invalid sub_type (not 1 or 2) + let result = parse_table_dump_message(0, bytes.clone()); + assert!(result.is_err(), "Expected error for invalid sub_type"); + + if let Err(ParserError::ParseError(msg)) = result { + assert!( + msg.contains("Invalid subtype"), + "Expected error message to mention invalid subtype" + ); + } else { + panic!("Expected ParseError for invalid sub_type"); + } + + // Test with another invalid sub_type + let result = parse_table_dump_message(3, bytes); + assert!(result.is_err(), "Expected error for invalid sub_type"); + + if let Err(ParserError::ParseError(msg)) = result { + assert!( + msg.contains("Invalid subtype"), + "Expected error message to mention invalid subtype" + ); + } else { + panic!("Expected ParseError for invalid sub_type"); + } + } } diff --git a/src/parser/mrt/messages/table_dump_v2/rib_afi_entries.rs b/src/parser/mrt/messages/table_dump_v2/rib_afi_entries.rs index b07b89a2..28152e88 100644 --- a/src/parser/mrt/messages/table_dump_v2/rib_afi_entries.rs +++ b/src/parser/mrt/messages/table_dump_v2/rib_afi_entries.rs @@ -29,8 +29,7 @@ fn extract_afi_safi_from_rib_type(rib_type: &TableDumpV2Type) -> Result<(Afi, Sa } _ => { return Err(ParserError::ParseError(format!( - "wrong RIB type for parsing: {:?}", - rib_type + "wrong RIB type for parsing: {rib_type:?}" ))) } }; diff --git a/src/parser/mrt/mrt_elem.rs b/src/parser/mrt/mrt_elem.rs index 5ea0c604..dda33c5a 100644 --- a/src/parser/mrt/mrt_elem.rs +++ b/src/parser/mrt/mrt_elem.rs @@ -12,6 +12,7 @@ use std::collections::HashMap; use std::fmt::{Display, Formatter}; use std::net::{IpAddr, Ipv4Addr}; +#[derive(Default, Debug, Clone)] pub struct Elementor { peer_table: Option, } @@ -156,7 +157,7 @@ fn get_relevant_attributes( impl Elementor { pub fn new() -> Elementor { - Elementor { peer_table: None } + Self::default() } /// Convert a [BgpMessage] to a vector of [BgpElem]s. diff --git a/src/parser/mrt/mrt_record.rs b/src/parser/mrt/mrt_record.rs index 70202e62..94d3cd45 100644 --- a/src/parser/mrt/mrt_record.rs +++ b/src/parser/mrt/mrt_record.rs @@ -118,8 +118,7 @@ pub fn parse_mrt_body( v => { // deprecated return Err(ParserError::Unsupported(format!( - "unsupported MRT type: {:?}", - v + "unsupported MRT type: {v:?}" ))); } }; diff --git a/src/parser/rislive/error.rs b/src/parser/rislive/error.rs index 11b94189..426fb282 100644 --- a/src/parser/rislive/error.rs +++ b/src/parser/rislive/error.rs @@ -19,7 +19,7 @@ impl Display for ParserRisliveError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { ParserRisliveError::IncorrectJson(msg) => { - write!(f, "incorrect json message: {}", msg) + write!(f, "incorrect json message: {msg}") } ParserRisliveError::IncorrectRawBytes => { write!(f, "incorrect raw bytes") @@ -31,16 +31,16 @@ impl Display for ParserRisliveError { write!(f, "irregular ris live format") } ParserRisliveError::ElemIncorrectPrefix(msg) => { - write!(f, "incorrect prefix string: {}", msg) + write!(f, "incorrect prefix string: {msg}") } ParserRisliveError::ElemUnknownOriginType(msg) => { - write!(f, "unknown origin type: {}", msg) + write!(f, "unknown origin type: {msg}") } ParserRisliveError::ElemIncorrectAggregator(msg) => { - write!(f, "incorrect aggregator string: {}", msg) + write!(f, "incorrect aggregator string: {msg}") } ParserRisliveError::ElemIncorrectIp(msg) => { - write!(f, "incorrect IP string: {}", msg) + write!(f, "incorrect IP string: {msg}") } ParserRisliveError::ElemEndOfRibPrefix => { write!(f, "found 'eor' (End of RIB) prefix") @@ -104,30 +104,30 @@ mod tests { #[test] fn test_ris_live_error_debug() { let err = ParserRisliveError::IncorrectJson("test".to_string()); - assert_eq!(format!("{:?}", err), "IncorrectJson(\"test\")"); + assert_eq!(format!("{err:?}"), "IncorrectJson(\"test\")"); let err = ParserRisliveError::IncorrectRawBytes; - assert_eq!(format!("{:?}", err), "IncorrectRawBytes"); + assert_eq!(format!("{err:?}"), "IncorrectRawBytes"); let err = ParserRisliveError::UnsupportedMessage; - assert_eq!(format!("{:?}", err), "UnsupportedMessage"); + assert_eq!(format!("{err:?}"), "UnsupportedMessage"); let err = ParserRisliveError::IrregularRisLiveFormat; - assert_eq!(format!("{:?}", err), "IrregularRisLiveFormat"); + assert_eq!(format!("{err:?}"), "IrregularRisLiveFormat"); let err = ParserRisliveError::ElemIncorrectPrefix("test".to_string()); - assert_eq!(format!("{:?}", err), "ElemIncorrectPrefix(\"test\")"); + assert_eq!(format!("{err:?}"), "ElemIncorrectPrefix(\"test\")"); let err = ParserRisliveError::ElemUnknownOriginType("test".to_string()); - assert_eq!(format!("{:?}", err), "ElemUnknownOriginType(\"test\")"); + assert_eq!(format!("{err:?}"), "ElemUnknownOriginType(\"test\")"); let err = ParserRisliveError::ElemIncorrectAggregator("test".to_string()); - assert_eq!(format!("{:?}", err), "ElemIncorrectAggregator(\"test\")"); + assert_eq!(format!("{err:?}"), "ElemIncorrectAggregator(\"test\")"); let err = ParserRisliveError::ElemIncorrectIp("test".to_string()); - assert_eq!(format!("{:?}", err), "ElemIncorrectIp(\"test\")"); + assert_eq!(format!("{err:?}"), "ElemIncorrectIp(\"test\")"); let err = ParserRisliveError::ElemEndOfRibPrefix; - assert_eq!(format!("{:?}", err), "ElemEndOfRibPrefix"); + assert_eq!(format!("{err:?}"), "ElemEndOfRibPrefix"); } } diff --git a/src/parser/rislive/messages/server/raw_bytes.rs b/src/parser/rislive/messages/server/raw_bytes.rs index fd151c5c..88508234 100644 --- a/src/parser/rislive/messages/server/raw_bytes.rs +++ b/src/parser/rislive/messages/server/raw_bytes.rs @@ -74,7 +74,7 @@ pub fn parse_raw_bytes(msg_str: &str) -> Result, ParserRisliveError } fn get_micro_seconds(sec: f64) -> u32 { - format!("{:.6}", sec).split('.').collect::>()[1] + format!("{sec:.6}").split('.').collect::>()[1] .to_owned() .parse::() .unwrap() @@ -140,7 +140,7 @@ mod tests { let res = parse_raw_bytes(message); for elem in res.unwrap() { - println!("{}", elem); + println!("{elem}"); } } } diff --git a/src/parser/rislive/mod.rs b/src/parser/rislive/mod.rs index 413d72e6..06a2f446 100644 --- a/src/parser/rislive/mod.rs +++ b/src/parser/rislive/mod.rs @@ -24,10 +24,10 @@ fn main() { // subscribe to messages from one collector let msg = json!({"type": "ris_subscribe", "data": {"host": "rrc21"}}).to_string(); - socket.write_message(Message::Text(msg)).unwrap(); + socket.send(Message::Text(msg.into())).unwrap(); loop { - let msg = socket.read_message().expect("Error reading message").to_string(); + let msg = socket.read().expect("Error reading message").to_string(); if let Ok(elems) = parse_ris_live_message(msg.as_str()) { for elem in elems { println!("{}", elem); @@ -231,7 +231,7 @@ mod tests { "#; let msg = parse_ris_live_message(msg_str).unwrap(); for elem in msg { - println!("{}", elem); + println!("{elem}"); } } @@ -242,7 +242,7 @@ mod tests { "#; let msg = parse_ris_live_message(msg_str).unwrap(); for elem in msg { - println!("{}", elem); + println!("{elem}"); } } @@ -253,7 +253,7 @@ mod tests { "#; let msg = parse_ris_live_message(msg_str).unwrap(); for elem in msg { - println!("{}", elem); + println!("{elem}"); } } @@ -264,7 +264,7 @@ mod tests { "#; let msg = parse_ris_live_message(msg_str).unwrap(); for elem in msg { - println!("{}", elem); + println!("{elem}"); } } @@ -306,4 +306,61 @@ mod tests { assert!(parse_result.is_err()); matches!(parse_result, Err(ParserRisliveError::ElemEndOfRibPrefix)); } + + #[test] + fn test_unknown_origin_type() { + // Test with an unknown origin type + let msg_str = r#" + {"type": "ris_message","data":{"timestamp":1636247118.76,"peer":"2001:7f8:24::82","peer_asn":"58299","id":"20-5761-238131559","host":"rrc20","type":"UPDATE","path":[58299,49981,397666],"origin":"unknown","announcements":[{"next_hop":"2001:7f8:24::82","prefixes":["2602:fd9e:f00::/40"]}]}} + "#; + + let result = parse_ris_live_message(msg_str); + assert!(result.is_err()); + + if let Err(ParserRisliveError::ElemUnknownOriginType(origin_type)) = result { + assert_eq!(origin_type, "unknown"); + } else { + panic!("Expected ElemUnknownOriginType error"); + } + } + + #[test] + fn test_incorrect_aggregator_format() { + // Test with an incorrect aggregator format (missing colon) + let msg_str = r#" + {"type": "ris_message","data":{"timestamp":1636247118.76,"peer":"2001:7f8:24::82","peer_asn":"58299","id":"20-5761-238131559","host":"rrc20","type":"UPDATE","path":[58299,49981,397666],"origin":"igp","aggregator":"65000-8.42.232.1","announcements":[{"next_hop":"2001:7f8:24::82","prefixes":["2602:fd9e:f00::/40"]}]}} + "#; + + let result = parse_ris_live_message(msg_str); + assert!(result.is_err()); + + if let Err(ParserRisliveError::ElemIncorrectAggregator(aggregator)) = result { + assert_eq!(aggregator, "65000-8.42.232.1"); + } else { + panic!("Expected ElemIncorrectAggregator error"); + } + } + + #[test] + fn test_non_ris_message() { + // Test with a non-RIS message + let msg_str = r#" + {"type": "other_message","data":{}} + "#; + + let result = parse_ris_live_message(msg_str); + assert!(result.is_err()); + } + + #[test] + fn test_non_update_message() { + // Test with a RIS message that is not an UPDATE message + let msg_str = r#" + {"type": "ris_message","data":{"timestamp":1636247118.76,"peer":"2001:7f8:24::82","peer_asn":"58299","id":"20-5761-238131559","host":"rrc20","type":"OTHER","msg":{"type":"OTHER"}}} + "#; + + let result = parse_ris_live_message(msg_str); + assert!(result.is_ok()); + assert_eq!(result.unwrap().len(), 0); + } } diff --git a/src/parser/utils.rs b/src/parser/utils.rs index b3f02dfb..f1e4df8a 100644 --- a/src/parser/utils.rs +++ b/src/parser/utils.rs @@ -25,8 +25,7 @@ pub trait ReadUtils: Buf { let remaining = self.remaining(); if remaining < n { Err(TruncatedMsg(format!( - "not enough bytes to read. remaining: {}, required: {}", - remaining, n + "not enough bytes to read. remaining: {remaining}, required: {n}" ))) } else { Ok(()) @@ -164,30 +163,24 @@ pub trait ReadUtils: Buf { // 4 bytes -- u32 if byte_len > 4 { return Err(ParserError::ParseError(format!( - "Invalid byte length for IPv4 prefix. byte_len: {}, bit_len: {}", - byte_len, bit_len + "Invalid byte length for IPv4 prefix. byte_len: {byte_len}, bit_len: {bit_len}" ))); } - let mut buff = [0; 4]; self.has_n_remaining(byte_len)?; - for i in 0..byte_len { - buff[i] = self.get_u8(); - } + let mut buff = [0; 4]; + self.copy_to_slice(&mut buff[..byte_len]); IpAddr::V4(Ipv4Addr::from(buff)) } Afi::Ipv6 => { // 16 bytes if byte_len > 16 { return Err(ParserError::ParseError(format!( - "Invalid byte length for IPv6 prefix. byte_len: {}, bit_len: {}", - byte_len, bit_len + "Invalid byte length for IPv6 prefix. byte_len: {byte_len}, bit_len: {bit_len}" ))); } self.has_n_remaining(byte_len)?; let mut buff = [0; 16]; - for i in 0..byte_len { - buff[i] = self.get_u8(); - } + self.copy_to_slice(&mut buff[..byte_len]); IpAddr::V6(Ipv6Addr::from(buff)) } }; @@ -195,8 +188,7 @@ pub trait ReadUtils: Buf { Ok(p) => p, Err(_) => { return Err(ParserError::ParseError(format!( - "Invalid network prefix length: {}", - bit_len + "Invalid network prefix length: {bit_len}" ))) } }; @@ -369,8 +361,7 @@ impl ComparableRegex { Ok(r) => r, Err(_) => { return Err(ParserError::FilterError(format!( - "Invalid regex pattern: {}", - pattern + "Invalid regex pattern: {pattern}" ))) } }; @@ -673,4 +664,64 @@ mod tests { // Test invalid pattern creation ComparableRegex::new(r"(\d+").unwrap(); // Unclosed parenthesis should panic } + + #[test] + fn test_parse_nlri_list() { + // Test normal case with add_path=false + let input = Bytes::from_static(&[0x18, 0xC0, 0xA8, 0x01, 0x18, 0xC0, 0xA8, 0x02]); + let expected = vec![ + NetworkPrefix::new( + IpNet::V4(Ipv4Net::new(Ipv4Addr::new(192, 168, 1, 0), 24).unwrap()), + 0, + ), + NetworkPrefix::new( + IpNet::V4(Ipv4Net::new(Ipv4Addr::new(192, 168, 2, 0), 24).unwrap()), + 0, + ), + ]; + assert_eq!(parse_nlri_list(input, false, &Afi::Ipv4).unwrap(), expected); + + // Test normal case with add_path=true + let input = Bytes::from_static(&[ + 0x00, 0x00, 0x00, 0x01, 0x18, 0xC0, 0xA8, 0x01, 0x00, 0x00, 0x00, 0x02, 0x18, 0xC0, + 0xA8, 0x02, + ]); + let expected = vec![ + NetworkPrefix::new( + IpNet::V4(Ipv4Net::new(Ipv4Addr::new(192, 168, 1, 0), 24).unwrap()), + 1, + ), + NetworkPrefix::new( + IpNet::V4(Ipv4Net::new(Ipv4Addr::new(192, 168, 2, 0), 24).unwrap()), + 2, + ), + ]; + assert_eq!(parse_nlri_list(input, true, &Afi::Ipv4).unwrap(), expected); + + // Test the auto-detection of add_path when first byte is 0 + let input = Bytes::from_static(&[0x00, 0x00, 0x00, 0x01, 0x18, 0xC0, 0xA8, 0x01]); + let expected = vec![NetworkPrefix::new( + IpNet::V4(Ipv4Net::new(Ipv4Addr::new(192, 168, 1, 0), 24).unwrap()), + 1, + )]; + assert_eq!(parse_nlri_list(input, false, &Afi::Ipv4).unwrap(), expected); + } + + #[test] + fn test_convert_timestamp() { + // Test integer timestamp + let (seconds, microseconds) = convert_timestamp(1609459200.0); + assert_eq!(seconds, 1609459200); + assert_eq!(microseconds, 0); + + // Test fractional timestamp + let (seconds, microseconds) = convert_timestamp(1609459200.123456); + assert_eq!(seconds, 1609459200); + assert_eq!(microseconds, 123456); + + // Test rounding + let (seconds, microseconds) = convert_timestamp(1609459200.1234567); + assert_eq!(seconds, 1609459200); + assert_eq!(microseconds, 123456); // Should round to microseconds + } }