1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
//! Malformed/resilience coverage for the RIP decode entrypoint.
//!
//! The crate's decoders must never panic on truncated or malformed input;
//! instead they return the structured length error
//! ([`CrafterError::BufferTooShort`]) carrying `context`/`required`/`available`
//! so generated tools can inspect exactly what was missing. This test pins that
//! behavior for [`crafter::protocols::rip::decode`]: a too-short header, a
//! header plus a partial entry, and a header plus a non-multiple-of-20 body each
//! surface a structured error whose context names the RIP element. An unknown
//! command still decodes the 4-octet header while preserving the raw command
//! value, and an over-length body (more than the RFC 2453 §4 25-entry guideline)
//! still decodes successfully.
//!
//! The test binary running to completion is itself the no-panic guarantee.
use std::net::Ipv4Addr;
use crafter::prelude::*;
use crafter::protocols::rip::decode;
/// A well-formed 20-octet RIPv2 route entry body (AFI 2, route 192.0.2.0/24,
/// metric 1) used to pad over-length bodies. Mirrors the RFC 2453 entry layout.
fn route_entry_bytes() -> [u8; 20] {
[
0x00, 0x02, // address family identifier (IP)
0x00, 0x00, // route tag
0xC0, 0x00, 0x02, 0x00, // IPv4 address 192.0.2.0
0xFF, 0xFF, 0xFF, 0x00, // subnet mask 255.255.255.0
0x00, 0x00, 0x00, 0x00, // next hop 0.0.0.0
0x00, 0x00, 0x00, 0x01, // metric 1
]
}
/// Assert the error is a structured `BufferTooShort` whose context mentions the
/// RIP element, and that `required`/`available` are exposed and consistent.
fn assert_rip_length_error(case: &str, error: CrafterError) {
match error {
CrafterError::BufferTooShort {
context,
required,
available,
} => {
assert!(
context.contains("RIP"),
"{case}: error context should mention the RIP element, got {context:?}"
);
assert!(
available < required,
"{case}: available ({available}) should be less than required ({required})"
);
}
other => panic!("{case}: expected BufferTooShort, got {other:?}"),
}
}
#[test]
fn truncated_two_octet_header_returns_structured_error() {
// Only the command and version octets are present; the 4-octet header is
// incomplete, so decode must fault on the header rather than panic.
let bytes = [0x01u8, 0x02];
let error = decode(&bytes).expect_err("a 2-octet truncated header must not decode");
assert_rip_length_error("truncated-header", error);
}
#[test]
fn header_plus_partial_entry_returns_structured_error() {
// A valid 4-octet header followed by a 10-octet partial route entry: the
// trailing run is shorter than a whole 20-octet entry slot.
let mut bytes = vec![0x01u8, 0x02, 0x00, 0x00];
bytes.extend_from_slice(&route_entry_bytes()[..10]);
let error = decode(&bytes).expect_err("a header plus a 10-octet partial entry must not decode");
assert_rip_length_error("header-plus-partial-entry", error);
}
#[test]
fn header_plus_non_multiple_of_twenty_body_returns_structured_error() {
// A valid header followed by a 21-octet body (total 25 octets): the trailing
// run is not a whole multiple of the 20-octet entry length, so the final
// partial entry must surface a structured error.
let mut bytes = vec![0x01u8, 0x02, 0x00, 0x00];
bytes.extend_from_slice(&route_entry_bytes());
bytes.push(0x00); // one extra octet => 21-octet trailing run
assert_eq!(bytes.len(), 25, "body should be exactly 25 octets");
let error = decode(&bytes).expect_err("a non-multiple-of-20 body must not decode");
assert_rip_length_error("header-plus-25-octet-body", error);
}
#[test]
fn unknown_command_decodes_header_and_preserves_raw_value() {
// An unrecognized command (0xC8 = 200) with no entries must still decode the
// 4-octet header and preserve the raw command value via RipCommand::Other.
let bytes = [0xC8u8, 0x02, 0x00, 0x00];
let rip = decode(&bytes).expect("an unknown command with a complete header must decode");
assert_eq!(
rip.command_value(),
0xC8,
"the raw command octet must be preserved verbatim"
);
assert_eq!(
rip.command(),
RipCommand::Other(0xC8),
"an unknown command must surface as RipCommand::Other(raw)"
);
assert!(
rip.entries().is_empty(),
"a bare header carries no route entries"
);
}
#[test]
fn over_length_body_with_more_than_twenty_five_entries_decodes() {
// The RFC 2453 §4 25-entry limit is a generation guideline, not a decode-time
// rejection: a body with 30 entries (well over 25) must decode successfully.
// Build the over-length message through the public packet abstraction
// (`Packet`/`Rip`/`RipEntry` from the prelude and the rip module) and compile
// it to the wire bytes the decode entrypoint consumes.
let entry_count = 30usize;
let entries: Vec<RipEntry> = (0..entry_count)
.map(|_| RipEntry::ipv1_route(Ipv4Addr::new(192, 0, 2, 0), 1))
.collect();
let rip = Rip::response().with_entries(entries);
let bytes = Packet::from_layer(rip)
.compile()
.expect("compiling an over-length RIP message must succeed");
assert!(
bytes.len() > 4 + 25 * 20,
"the body should carry more than the 25-entry guideline"
);
let decoded = decode(&bytes).expect("an over-length body must still decode");
assert_eq!(
decoded.entries().len(),
entry_count,
"every present entry must be decoded, with no 25-entry cap"
);
}