1#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
6pub enum NotificationCode {
7 MessageHeader,
9 OpenMessage,
11 UpdateMessage,
13 HoldTimerExpired,
15 FsmError,
17 Cease,
19 Unknown(u8),
22}
23
24impl NotificationCode {
25 #[must_use]
27 pub fn from_u8(value: u8) -> Self {
28 match value {
29 1 => Self::MessageHeader,
30 2 => Self::OpenMessage,
31 3 => Self::UpdateMessage,
32 4 => Self::HoldTimerExpired,
33 5 => Self::FsmError,
34 6 => Self::Cease,
35 other => Self::Unknown(other),
36 }
37 }
38
39 #[must_use]
41 pub fn as_u8(self) -> u8 {
42 match self {
43 Self::MessageHeader => 1,
44 Self::OpenMessage => 2,
45 Self::UpdateMessage => 3,
46 Self::HoldTimerExpired => 4,
47 Self::FsmError => 5,
48 Self::Cease => 6,
49 Self::Unknown(v) => v,
50 }
51 }
52}
53
54impl std::fmt::Display for NotificationCode {
55 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56 match self {
57 Self::MessageHeader => write!(f, "Message Header Error"),
58 Self::OpenMessage => write!(f, "OPEN Message Error"),
59 Self::UpdateMessage => write!(f, "UPDATE Message Error"),
60 Self::HoldTimerExpired => write!(f, "Hold Timer Expired"),
61 Self::FsmError => write!(f, "Finite State Machine Error"),
62 Self::Cease => write!(f, "Cease"),
63 Self::Unknown(code) => write!(f, "Unknown({code})"),
64 }
65 }
66}
67
68pub mod header_subcode {
70 pub const CONNECTION_NOT_SYNCHRONIZED: u8 = 1;
72 pub const BAD_MESSAGE_LENGTH: u8 = 2;
74 pub const BAD_MESSAGE_TYPE: u8 = 3;
76}
77
78pub mod open_subcode {
80 pub const UNSUPPORTED_VERSION: u8 = 1;
82 pub const BAD_PEER_AS: u8 = 2;
84 pub const BAD_BGP_IDENTIFIER: u8 = 3;
86 pub const UNSUPPORTED_OPTIONAL_PARAMETER: u8 = 4;
88 pub const UNACCEPTABLE_HOLD_TIME: u8 = 6;
91 pub const UNSUPPORTED_CAPABILITY: u8 = 7;
93}
94
95pub mod update_subcode {
97 pub const MALFORMED_ATTRIBUTE_LIST: u8 = 1;
99 pub const UNRECOGNIZED_WELLKNOWN: u8 = 2;
101 pub const MISSING_WELLKNOWN: u8 = 3;
103 pub const ATTRIBUTE_FLAGS_ERROR: u8 = 4;
105 pub const ATTRIBUTE_LENGTH_ERROR: u8 = 5;
107 pub const INVALID_ORIGIN: u8 = 6;
109 pub const INVALID_NEXT_HOP: u8 = 8;
112 pub const OPTIONAL_ATTRIBUTE_ERROR: u8 = 9;
114 pub const INVALID_NETWORK_FIELD: u8 = 10;
116 pub const MALFORMED_AS_PATH: u8 = 11;
118}
119
120pub mod cease_subcode {
122 pub const MAX_PREFIXES: u8 = 1;
124 pub const ADMINISTRATIVE_SHUTDOWN: u8 = 2;
126 pub const PEER_DECONFIGURED: u8 = 3;
128 pub const ADMINISTRATIVE_RESET: u8 = 4;
130 pub const OUT_OF_RESOURCES: u8 = 8;
132 pub const CONNECTION_COLLISION_RESOLUTION: u8 = 7;
134 pub const HARD_RESET: u8 = 9;
136}
137
138#[must_use]
144pub fn encode_shutdown_communication(reason: &str) -> bytes::Bytes {
145 let mut end = reason.len().min(128);
147 while end > 0 && !reason.is_char_boundary(end) {
148 end -= 1;
149 }
150 let truncated = &reason[..end];
151 #[expect(clippy::cast_possible_truncation)]
153 let len = truncated.len() as u8;
154 let mut buf = Vec::with_capacity(1 + truncated.len());
155 buf.push(len);
156 buf.extend_from_slice(truncated.as_bytes());
157 bytes::Bytes::from(buf)
158}
159
160#[must_use]
166pub fn decode_shutdown_communication(data: &[u8]) -> Option<String> {
167 if data.is_empty() {
168 return None;
169 }
170 let len = data[0] as usize;
171 if data.len() < 1 + len {
172 return None;
173 }
174 let raw = &data[1..=len];
175 Some(String::from_utf8_lossy(raw).into_owned())
176}
177
178#[must_use]
180pub fn description(code: NotificationCode, subcode: u8) -> &'static str {
181 match (code, subcode) {
182 (NotificationCode::MessageHeader, 1) => "Connection Not Synchronized",
184 (NotificationCode::MessageHeader, 2) => "Bad Message Length",
185 (NotificationCode::MessageHeader, 3) => "Bad Message Type",
186 (NotificationCode::OpenMessage, 1) => "Unsupported Version Number",
188 (NotificationCode::OpenMessage, 2) => "Bad Peer AS",
189 (NotificationCode::OpenMessage, 3) => "Bad BGP Identifier",
190 (NotificationCode::OpenMessage, 4) => "Unsupported Optional Parameter",
191 (NotificationCode::OpenMessage, 6) => "Unacceptable Hold Time",
192 (NotificationCode::OpenMessage, 7) => "Unsupported Capability",
193 (NotificationCode::UpdateMessage, 1) => "Malformed Attribute List",
195 (NotificationCode::UpdateMessage, 2) => "Unrecognized Well-known Attribute",
196 (NotificationCode::UpdateMessage, 3) => "Missing Well-known Attribute",
197 (NotificationCode::UpdateMessage, 4) => "Attribute Flags Error",
198 (NotificationCode::UpdateMessage, 5) => "Attribute Length Error",
199 (NotificationCode::UpdateMessage, 6) => "Invalid ORIGIN Attribute",
200 (NotificationCode::UpdateMessage, 8) => "Invalid NEXT_HOP Attribute",
201 (NotificationCode::UpdateMessage, 9) => "Optional Attribute Error",
202 (NotificationCode::UpdateMessage, 10) => "Invalid Network Field",
203 (NotificationCode::UpdateMessage, 11) => "Malformed AS_PATH",
204 (NotificationCode::HoldTimerExpired, _) => "Hold Timer Expired",
206 (NotificationCode::FsmError, _) => "Finite State Machine Error",
208 (NotificationCode::Cease, 1) => "Maximum Number of Prefixes Reached",
210 (NotificationCode::Cease, 2) => "Administrative Shutdown",
211 (NotificationCode::Cease, 3) => "Peer De-configured",
212 (NotificationCode::Cease, 4) => "Administrative Reset",
213 (NotificationCode::Cease, 8) => "Out of Resources",
214 (NotificationCode::Cease, 7) => "Connection Collision Resolution",
215 (NotificationCode::Cease, 9) => "Hard Reset",
216 (NotificationCode::Unknown(_), _) => "Unknown Error Code",
218 (_, _) => "Unknown",
220 }
221}
222
223#[cfg(test)]
224mod tests {
225 use super::*;
226
227 #[test]
228 fn from_u8_roundtrip() {
229 for code_val in 1..=6u8 {
230 let code = NotificationCode::from_u8(code_val);
231 assert_eq!(code.as_u8(), code_val);
232 assert!(!matches!(code, NotificationCode::Unknown(_)));
233 }
234 }
235
236 #[test]
237 fn from_u8_unknown_preserved() {
238 assert_eq!(NotificationCode::from_u8(0), NotificationCode::Unknown(0));
239 assert_eq!(NotificationCode::from_u8(7), NotificationCode::Unknown(7));
240 assert_eq!(
241 NotificationCode::from_u8(255),
242 NotificationCode::Unknown(255)
243 );
244 assert_eq!(NotificationCode::from_u8(42).as_u8(), 42);
246 }
247
248 #[test]
249 fn description_returns_nonempty_for_known_pairs() {
250 let pairs = [
251 (NotificationCode::MessageHeader, 1),
252 (NotificationCode::MessageHeader, 2),
253 (NotificationCode::MessageHeader, 3),
254 (NotificationCode::OpenMessage, 1),
255 (NotificationCode::OpenMessage, 6),
256 (NotificationCode::UpdateMessage, 1),
257 (NotificationCode::UpdateMessage, 11),
258 (NotificationCode::Cease, 2),
259 (NotificationCode::Cease, 4),
260 ];
261 for (code, subcode) in pairs {
262 let desc = description(code, subcode);
263 assert!(
264 !desc.is_empty(),
265 "empty description for ({code}, {subcode})"
266 );
267 assert_ne!(desc, "Unknown", "got Unknown for ({code}, {subcode})");
268 }
269 }
270
271 #[test]
272 fn shutdown_communication_roundtrip() {
273 let reason = "maintenance window";
274 let encoded = encode_shutdown_communication(reason);
275 assert_eq!(encoded[0] as usize, reason.len());
276 let decoded = decode_shutdown_communication(&encoded).unwrap();
277 assert_eq!(decoded, reason);
278 }
279
280 #[test]
281 fn shutdown_communication_empty() {
282 let encoded = encode_shutdown_communication("");
283 assert_eq!(encoded.as_ref(), &[0]);
284 assert_eq!(decode_shutdown_communication(&encoded).as_deref(), Some(""));
285 assert_eq!(decode_shutdown_communication(&[]), None);
286 }
287
288 #[test]
289 fn shutdown_communication_truncates_at_128() {
290 let long = "a".repeat(200);
291 let encoded = encode_shutdown_communication(&long);
292 assert_eq!(encoded[0], 128);
293 assert_eq!(encoded.len(), 129);
294 let decoded = decode_shutdown_communication(&encoded).unwrap();
295 assert_eq!(decoded.len(), 128);
296 }
297
298 #[test]
299 fn shutdown_communication_truncates_at_char_boundary() {
300 let reason = format!("{}é", "x".repeat(127));
302 assert_eq!(reason.len(), 129);
303 let encoded = encode_shutdown_communication(&reason);
304 assert_eq!(encoded[0], 127);
306 let decoded = decode_shutdown_communication(&encoded).unwrap();
307 assert_eq!(decoded, "x".repeat(127));
308 }
309
310 #[test]
311 fn shutdown_communication_invalid_utf8() {
312 let data = [3, 0xff, 0xfe, 0xfd];
314 let decoded = decode_shutdown_communication(&data).unwrap();
315 assert!(decoded.contains('\u{FFFD}')); }
317
318 #[test]
319 fn shutdown_communication_ignores_trailing_bytes() {
320 let data = [3, b'f', b'o', b'o', b'x'];
321 assert_eq!(decode_shutdown_communication(&data).as_deref(), Some("foo"));
322 }
323}