Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 45 additions & 7 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,43 @@ env:
CARGO_TERM_COLOR: always

jobs:
build:
format:
name: Format Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: Swatinem/rust-cache@v2
- name: Run format check
run: cargo fmt --check

- name: Build
doc:
name: Documentation
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: Swatinem/rust-cache@v2
- name: Check documentation
env:
RUSTDOCFLAGS: "-D warnings"
run: cargo doc --all-features --no-deps

clippy:
name: Clippy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: Swatinem/rust-cache@v2
- name: Run clippy
run: cargo clippy --all-targets --all-features -- -D warnings

build-and-test:
name: Build & Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: Swatinem/rust-cache@v2

- name: Build default
run: cargo build

- name: Build cli
Expand All @@ -34,8 +65,15 @@ jobs:
- name: Run tests
run: cargo test --all-features

- name: Run clippy
run: cargo clippy --all-targets --all-features -- -D warnings

- name: Check MSRV
run: cargo install cargo-msrv --locked && cargo msrv verify
msrv:
name: Check MSRV
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: Swatinem/rust-cache@v2
with:
cache-directories: ~/.cargo/bin
- name: Install cargo-msrv
run: cargo install cargo-msrv --locked
- name: Verify MSRV
run: cargo msrv verify
23 changes: 0 additions & 23 deletions .github/workflows/format.yml

This file was deleted.

5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file.

## Unreleased

### Bug fixes

* **WASM `only_to_customer` serialization**: Added test to verify that messages without the OTC (Only To Customer) attribute correctly serialize `only_to_customer` as `null` (not `0`) in JSON output. This ensures JavaScript consumers can properly distinguish between "no OTC attribute" (`null`) and "OTC with AS 0" (`0`).
* **OTC attribute on withdrawal messages**: Withdrawal elements no longer inherit the `only_to_customer` value from their associated UPDATE message. Since the OTC attribute (RFC 9234) is used for route leak detection on route advertisements, it is not semantically meaningful for withdrawals. Withdrawal elements now correctly have `only_to_customer: null`.

### Breaking changes

* **`BgpOpenMessage::sender_ip` renamed to `bgp_identifier`**: The field type remains `Ipv4Addr` (aliased as `BgpIdentifier`), but the name now correctly reflects RFC 4271 terminology — this is the BGP Identifier, not necessarily the sender's IP address.
Expand Down
4 changes: 2 additions & 2 deletions src/models/bgp/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ pub struct BgpUpdateMessage {
/// and will **not** be included here. Accessing this field directly may cause you to miss
/// IPv6 or multi-protocol prefixes.
///
/// Instead, use [`Elementor::bgp_update_to_elems`] to reliably extract all withdrawn prefixes from the update,
/// Instead, use [`Elementor::bgp_update_to_elems`](crate::Elementor::bgp_update_to_elems) to reliably extract all withdrawn prefixes from the update,
/// or combine this field with prefixes found in the `MpUnreachNlri` attribute manually.
///
/// See
Expand All @@ -174,7 +174,7 @@ pub struct BgpUpdateMessage {
/// and will **not** be included here. Accessing this field directly may cause you to miss
/// IPv6 or multi-protocol prefixes.
///
/// Instead, use [`Elementor::bgp_update_to_elems`] to reliably extract all announced prefixes from the update,
/// Instead, use [`Elementor::bgp_update_to_elems`](crate::Elementor::bgp_update_to_elems) to reliably extract all announced prefixes from the update,
/// or combine this field with prefixes found in the `MpReachNlri` attribute manually.
///
/// See
Expand Down
2 changes: 1 addition & 1 deletion src/parser/bgp/messages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ impl BgpNotificationMessage {
///
/// The parsing of BGP OPEN message also includes decoding the BGP capabilities.
///
/// RFC 4271: https://datatracker.ietf.org/doc/html/rfc4271
/// RFC 4271: <https://datatracker.ietf.org/doc/html/rfc4271>
/// ```text
/// 0 1 2 3
/// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
Expand Down
4 changes: 2 additions & 2 deletions src/parser/bmp/messages/headers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,8 +187,8 @@ impl BmpPerPeerHeader {

/// Peer type
///
/// - RFC7854: https://datatracker.ietf.org/doc/html/rfc7854#section-4.2
/// - RFC9069: https://datatracker.ietf.org/doc/html/rfc9069
/// - RFC7854: <https://datatracker.ietf.org/doc/html/rfc7854#section-4.2>
/// - RFC9069: <https://datatracker.ietf.org/doc/html/rfc9069>
#[derive(Debug, Copy, TryFromPrimitive, IntoPrimitive, PartialEq, Eq, Hash, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(u8)]
Expand Down
2 changes: 1 addition & 1 deletion src/parser/bmp/messages/initiation_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ pub struct InitiationTlv {

///Type-Length-Value Type
///
/// https://www.iana.org/assignments/bmp-parameters/bmp-parameters.xhtml#initiation-peer-up-tlvs
/// <https://www.iana.org/assignments/bmp-parameters/bmp-parameters.xhtml#initiation-peer-up-tlvs>
#[derive(Debug, TryFromPrimitive, IntoPrimitive, PartialEq, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(u16)]
Expand Down
2 changes: 1 addition & 1 deletion src/parser/bmp/messages/peer_up_notification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ pub struct PeerUpNotification {

///Type-Length-Value Type
///
/// https://www.iana.org/assignments/bmp-parameters/bmp-parameters.xhtml#initiation-peer-up-tlvs
/// <https://www.iana.org/assignments/bmp-parameters/bmp-parameters.xhtml#initiation-peer-up-tlvs>
#[derive(Debug, TryFromPrimitive, IntoPrimitive, PartialEq, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(u16)]
Expand Down
2 changes: 1 addition & 1 deletion src/parser/bmp/messages/termination_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ pub enum TerminationReason {

///Type-Length-Value Type
///
/// For more, see: https://datatracker.ietf.org/doc/html/rfc1213
/// For more, see: <https://datatracker.ietf.org/doc/html/rfc1213>
#[derive(Debug, TryFromPrimitive, IntoPrimitive, PartialEq, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(u16)]
Expand Down
2 changes: 1 addition & 1 deletion src/parser/mrt/mrt_elem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,7 @@ impl Iterator for BgpUpdateElemIter {
atomic: false,
aggr_asn: None,
aggr_ip: None,
only_to_customer: self.only_to_customer,
only_to_customer: None,
unknown: None,
deprecated: None,
})
Expand Down
2 changes: 1 addition & 1 deletion src/parser/rislive/messages/client/ris_subscribe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ pub struct RisSubscribe {
///
/// Examples:
/// * "789$"
/// * "^123,456,789,[789,10111]$"
/// * "^123,456,789,\[789,10111\]$"
/// * "!6666$"
/// * "!^3333"
#[serde(skip_serializing_if = "Option::is_none")]
Expand Down
57 changes: 57 additions & 0 deletions src/wasm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -630,4 +630,61 @@ mod tests {
assert!(!header.is_post_policy);
assert!(!header.is_adj_rib_out);
}

#[test]
fn test_bgp_update_without_otc_serializes_null() {
// Create a BGP UPDATE without OTC attribute and verify it serializes to null
let data = make_bgp_update_announce([10, 0, 0, 0], 24, [192, 168, 1, 1]);
let json = parse_bgp_update_core(&data).unwrap();
let v: serde_json::Value = serde_json::from_str(&json).unwrap();
let elems = v.as_array().unwrap();
assert_eq!(elems.len(), 1);

// Verify only_to_customer is null (not 0)
let otc = &elems[0]["only_to_customer"];
assert!(
otc.is_null(),
"only_to_customer should be null for messages without OTC, got: {:?}",
otc
);
}
}

#[cfg(test)]
mod otc_tests {
use super::*;

#[test]
fn test_option_asn_serialization() {
// Test that Option<Asn> serializes correctly for WASM
let some_asn: Option<Asn> = Some(Asn::new_32bit(12345));
let json = serde_json::to_string(&some_asn).unwrap();
assert_eq!(json, "12345", "Some(Asn) should serialize as numeric value");

let none_asn: Option<Asn> = None;
let json = serde_json::to_string(&none_asn).unwrap();
assert_eq!(json, "null", "None should serialize as null");

// Test BgpElem with and without OTC
let elem_with_otc = BgpElem {
only_to_customer: Some(Asn::new_32bit(12345)),
..Default::default()
};
let json = serde_json::to_string(&elem_with_otc).unwrap();
let v: serde_json::Value = serde_json::from_str(&json).unwrap();
assert_eq!(v.get("only_to_customer").unwrap(), 12345);

let elem_without_otc = BgpElem {
only_to_customer: None,
..Default::default()
};
let json = serde_json::to_string(&elem_without_otc).unwrap();
let v: serde_json::Value = serde_json::from_str(&json).unwrap();
let otc_field = v.get("only_to_customer").unwrap();
assert!(
otc_field.is_null(),
"BgpElem without OTC should have null, got: {:?}",
otc_field
);
}
}
Loading