Skip to content
Open
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: 52 additions & 0 deletions bgp/src/messages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8984,4 +8984,56 @@ mod tests {
assert!(Header::from_wire(&wire).is_err());
}
}

/// Exhaustive test: duplicate ORIGIN attributes are deduplicated to
/// the first occurrence. PathOrigin has 3 variants, so 9 cases total.
#[test]
fn duplicate_origin_attrs_deduplicated() {
let origins =
[PathOrigin::Igp, PathOrigin::Egp, PathOrigin::Incomplete];
for origin1 in origins {
for origin2 in origins {
let mut wire = Vec::new();
wire.extend_from_slice(&0u16.to_be_bytes());

let attrs = vec![
path_attribute_flags::TRANSITIVE,
PathAttributeTypeCode::Origin as u8,
1,
origin1 as u8,
path_attribute_flags::TRANSITIVE,
PathAttributeTypeCode::Origin as u8,
1,
origin2 as u8,
];

wire.extend_from_slice(&(attrs.len() as u16).to_be_bytes());
wire.extend_from_slice(&attrs);

let decoded =
UpdateMessage::from_wire(&wire).expect("should decode");

let decoded_origins: Vec<_> = decoded
.path_attributes
.iter()
.filter_map(|a| match &a.value {
PathAttributeValue::Origin(o) => Some(*o),
_ => None,
})
.collect();

assert_eq!(
decoded_origins.len(),
1,
"origin1={origin1:?} origin2={origin2:?}: \
expected exactly one ORIGIN after dedup",
);
assert_eq!(
decoded_origins[0], origin1,
"origin1={origin1:?} origin2={origin2:?}: \
should keep first ORIGIN value",
);
}
}
}
}
44 changes: 0 additions & 44 deletions bgp/src/proptest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -616,50 +616,6 @@ proptest! {
}
}

/// Property: Duplicate non-MP-BGP attributes are deduplicated to first occurrence
#[test]
fn prop_duplicate_attrs_deduplicated(
origin1 in path_origin_strategy(),
origin2 in path_origin_strategy()
) {
// Manually construct wire bytes with duplicate ORIGIN attributes
let mut wire = Vec::new();

// Withdrawn routes length (0)
wire.extend_from_slice(&0u16.to_be_bytes());

// Path attributes: two ORIGIN attributes (second should be discarded)
let attrs = vec![
// First ORIGIN attribute
path_attribute_flags::TRANSITIVE,
PathAttributeTypeCode::Origin as u8,
1, // length
origin1 as u8,
// Second ORIGIN attribute (should be discarded)
path_attribute_flags::TRANSITIVE,
PathAttributeTypeCode::Origin as u8,
1, // length
origin2 as u8,
];

// Path attributes length
wire.extend_from_slice(&(attrs.len() as u16).to_be_bytes());
wire.extend_from_slice(&attrs);

let decoded = UpdateMessage::from_wire(&wire).expect("should decode");

// Should only have one ORIGIN attribute
let origins: Vec<_> = decoded.path_attributes.iter()
.filter_map(|a| match &a.value {
PathAttributeValue::Origin(o) => Some(*o),
_ => None,
})
.collect();

prop_assert_eq!(origins.len(), 1, "Should have exactly one ORIGIN after dedup");
prop_assert_eq!(origins[0], origin1, "Should keep first ORIGIN value");
}

/// Property: Encoding then decoding produces semantically equivalent message
#[test]
fn prop_encode_decode_semantic_equivalence(update in update_strategy()) {
Expand Down
22 changes: 0 additions & 22 deletions rdb/src/proptest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -259,28 +259,6 @@ proptest! {
);
}

/// Property: Prefix enum V4 is never within V6 and vice versa
#[test]
fn prop_prefix_enum_no_cross_family(p4 in ipv4_prefix_strategy(), p6 in ipv6_prefix_strategy()) {
let v4 = Prefix::V4(p4);
let v6 = Prefix::V6(p6);

prop_assert!(!v4.within(&v6), "IPv4 should not be within IPv6");
prop_assert!(!v6.within(&v4), "IPv6 should not be within IPv4");
}

/// Property: IPv4 prefix length bounds are validated (0-32)
#[test]
fn prop_ipv4_length_in_bounds(prefix in ipv4_prefix_strategy()) {
prop_assert!(prefix.length <= 32u8, "IPv4 prefix length must be <= 32");
}

/// Property: IPv6 prefix length bounds are validated (0-128)
#[test]
fn prop_ipv6_length_in_bounds(prefix in ipv6_prefix_strategy()) {
prop_assert!(prefix.length <= 128u8, "IPv6 prefix length must be <= 128");
}

/// Property: IPv4 host bits unset operation is idempotent
#[test]
fn prop_ipv4_unset_host_bits_idempotent(prefix in ipv4_prefix_strategy()) {
Expand Down
8 changes: 8 additions & 0 deletions rdb/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1042,4 +1042,12 @@ mod test {
let st = static_path(ip1);
assert_eq!(bgp.cmp(&st), st.cmp(&bgp).reverse());
}

#[test]
fn prefix_no_cross_family_within() {
let v4 = Prefix::V4(Prefix4::new(Ipv4Addr::new(10, 0, 0, 0), 8));
let v6 = Prefix::V6(Prefix6::new(Ipv6Addr::LOCALHOST, 128));
assert!(!v4.within(&v6));
assert!(!v6.within(&v4));
}
}