diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ce1201b..392d2446 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,12 +4,24 @@ All notable changes to this project will be documented in this file. ## Unreleased +### Breaking changes + +* **MSRV raised to Rust 1.88** ([#338](https://github.com/bgpkit/bgpkit-parser/pull/338)): `rust-version` in `Cargo.toml` is now 1.88.0. The `idna_adapter` dev-dependency pin is removed as it is no longer needed. + +### Dependencies + +* **Updated `oneio` dependency from 0.25 to 0.26** ([#338](https://github.com/bgpkit/bgpkit-parser/pull/338)): clears the suppaftp advisory ([RUSTSEC-2026-0271](https://rustsec.org/advisories/RUSTSEC-2026-0271.html)) that `cargo audit` reported via oneio 0.25's lockfile entry. + ### Added * **Layered text output format** (`render::text::format_record`, `--format text`): one human-readable, indented block per MRT record — session context (`TIME`/`TYPE`/`FROM`/`TO`), `UPDATE:` sections with withdrawn/announced prefixes (including those carried in MP_REACH/MP_UNREACH) and every path attribute, `OPEN:` capabilities, session states, RIB entries, the peer table, and full legacy type-5 records. RFC 7606 validation findings render under `WARNINGS:` when present. The format is designed around this crate's own models and `Display` vocabulary — inspired by bgpdump's human-readable output, not byte-compatible with it. Rendering is a pure function of the record. In the CLI, `--format text` always uses record-level output (implies `--level records`); the other formats follow `--level` and default to elems. * **`--hex` flag**: include each record's original wire bytes as hex in record-level output — a `HEX:` line at the end of `--format text` blocks, a `hex` field in `--format json`/`json-pretty` records. Designed as the pipe from filtered CLI output into byte-level tools (e.g. wirescope's hex paste input): `bgpkit-parser -p 8.8.8.0/24 --format text --hex updates.mrt.gz` yields blocks whose `HEX:` line can be pasted straight into a dissector. Bytes come from a new library iterator, `BgpkitParser::into_filtered_raw_record_iter()` — raw MRT records with the same record-level filter semantics as `into_record_iter`, yielding the untouched original bytes (no re-encoding, so attribute-ordering quirks such as BGP-LS hash-map iteration can never alter the output). The flag implies `--level records`, requires text/json/json-pretty formats, is mutually exclusive with `--recover`, and is ignored by count-only runs (`-e`/`-r`), which keep the normal counting pipelines and their per-elem filter semantics. * Record-level filter semantics are now explicit: records that produce no elems (KEEPALIVE, OPEN, NOTIFICATION, state changes) never match elem-oriented filters and are dropped from record iteration while filters are active — a `debug!` line notes each drop. +### Fixed + +* **RIS Live peer-state message type** ([#338](https://github.com/bgpkit/bgpkit-parser/pull/338)): the live stream sends peer-state messages with `"type": "STATE"`, not `"type": "RIS_PEER_STATE"` as documented in the RIPE RIS Live documentation and schema. `RisMessageEnum::RIS_PEER_STATE` now deserializes from both `STATE` and `RIS_PEER_STATE` and serializes as `STATE`, matching the live stream. To be re-validated against RIPE's documentation and schema on/after January 2027. + ## v0.21.0 - 2026-08-21 ### Examples restructure diff --git a/Cargo.toml b/Cargo.toml index cd618e1a..717b1a12 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ keywords = ["bgp", "bgpkit", "mrt"] categories = ["network-programming"] default-run = "bgpkit-parser" readme = "README.md" -rust-version = "1.87.0" +rust-version = "1.88.0" exclude = [ "tests/fixtures/packetlife/**", "tests/fixtures/ripe/**", @@ -56,7 +56,7 @@ serde = { version = "1.0", features = ["derive"], optional = true } bytes = { version = "1.7" } zerocopy = { version = "0.8", features = ["derive"], optional = true } hex = { version = "0.4.3", optional = true } # bmp/openbmp parsing -oneio = { version = "0.25", default-features = false, features = ["http", "gz", "bz", "zstd"], optional = true } +oneio = { version = "0.26", default-features = false, features = ["http", "gz", "bz", "zstd"], optional = true } regex = { version = "1", optional = true } # used in parser filter chrono = { version = "0.4.38", optional = true } # parser filter serde_json = { version = "1.0", optional = true } # RIS Live parsing @@ -141,8 +141,6 @@ name = "bench_main" harness = false [dev-dependencies] -# MSRV pin - newer idna_adapter requires rust 1.88 -idna_adapter = "=1.2.0" regex = "1" anyhow = "1" bgpkit-broker = "0.12" diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 00000000..22770733 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "1.98" +components = ["rustfmt", "clippy"] diff --git a/src/models/bgp/capabilities.rs b/src/models/bgp/capabilities.rs index 56694aad..296bf79f 100644 --- a/src/models/bgp/capabilities.rs +++ b/src/models/bgp/capabilities.rs @@ -724,8 +724,8 @@ mod tests { #[test] fn test_reserved_for_experimental() { - let experimental_ranges = [239..=254]; - for code in <[_; 1]>::into_iter(experimental_ranges).flatten() { + let experimental_range = 239..=254; + for code in experimental_range { let ty = BgpCapabilityType::from(code); assert_eq!(ty, BgpCapabilityType::Unknown(code)); assert!(ty.is_reserved_for_experimental_use()); diff --git a/src/parser/rislive/messages/client/ris_subscribe.rs b/src/parser/rislive/messages/client/ris_subscribe.rs index fe47d55a..09805409 100644 --- a/src/parser/rislive/messages/client/ris_subscribe.rs +++ b/src/parser/rislive/messages/client/ris_subscribe.rs @@ -10,6 +10,10 @@ pub enum RisSubscribeType { OPEN, NOTIFICATION, KEEPALIVE, + /// Subscribe requests use `RIS_PEER_STATE` because that is what the server + /// accepts. + /// + /// The resulting messages will have `"type":"STATE"` (see [`RisMessageEnum::RIS_PEER_STATE`](crate::rislive::messages::RisMessageEnum)). RIS_PEER_STATE, } diff --git a/src/parser/rislive/messages/server/ris_message.rs b/src/parser/rislive/messages/server/ris_message.rs index 2e898493..065a06f6 100644 --- a/src/parser/rislive/messages/server/ris_message.rs +++ b/src/parser/rislive/messages/server/ris_message.rs @@ -60,6 +60,14 @@ pub enum RisMessageEnum { /// RIS Peer State message /// /// Schema: + /// + /// The live stream sends this message with `"type":"STATE"`, not `RIS_PEER_STATE` as documented + /// in the documentation and schema. + /// + /// Serialise to `STATE` and accept `RIS_PEER_STATE` when deserialising. + /// + /// TODO: re-validate the documentation state on/after January 2027 + #[serde(rename = "STATE", alias = "RIS_PEER_STATE")] RIS_PEER_STATE { state: String }, } @@ -153,6 +161,31 @@ mod tests { let _msg: RisMessage = serde_json::from_str(msg_str).unwrap(); } + #[test] + fn test_peer_state_change_msg_state_alias() { + // The live stream sends "type":"STATE" instead of "RIS_PEER_STATE" + let fixtures = [ + r#"{"type":"ris_message","data":{"timestamp":1788948301.310,"peer":"2001:7f8:13::a503:4927:1","peer_asn":"34927","id":"2001:7f8:13::a503:4927:1-01a085a0c5fe0002","host":"rrc03.ripe.net","type":"STATE","state":"connected"}}"#, + r#"{"type":"ris_message","data":{"timestamp":1788948302.310,"peer":"2001:7f8:13::a503:4927:1","peer_asn":"34927","id":"2001:7f8:13::a503:4927:1-01a085a0c9e60009","host":"rrc03.ripe.net","type":"STATE","state":"down"}}"#, + ]; + for (fixture, expected_state) in fixtures.iter().zip(["connected", "down"]) { + let msg: RisLiveMessage = serde_json::from_str(fixture).unwrap(); + let RisLiveMessage::RisMessage(msg) = msg else { + panic!("incorrect message type"); + }; + match &msg.msg { + Some(RisMessageEnum::RIS_PEER_STATE { state }) => { + assert_eq!(state, expected_state); + } + other => panic!("incorrect message type: {:?}", other), + } + // serializes back as "STATE", not "RIS_PEER_STATE" + let serialized = serde_json::to_string(&msg).unwrap(); + assert!(serialized.contains(r#""type":"STATE""#)); + assert!(!serialized.contains("RIS_PEER_STATE")); + } + } + #[test] fn test_withdrawals() { let msg_str = r#"{ "type": "ris_message", "data": { "timestamp": 1740561857.910, "peer": "2606:6dc0:1301::1", "peer_asn": "13781", "id": "2606:6dc0:1301::1-019541923d760008", "host": "rrc25.ripe.net", "type": "UPDATE", "path": [], "community": [], "announcements": [], "withdrawals": [ "2605:de00:bb:0:0:0:0:0/48" ] } }"#;