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 pub const ROLE_MISMATCH: u8 = 11;
95}
96
97pub mod update_subcode {
99 pub const MALFORMED_ATTRIBUTE_LIST: u8 = 1;
101 pub const UNRECOGNIZED_WELLKNOWN: u8 = 2;
103 pub const MISSING_WELLKNOWN: u8 = 3;
105 pub const ATTRIBUTE_FLAGS_ERROR: u8 = 4;
107 pub const ATTRIBUTE_LENGTH_ERROR: u8 = 5;
109 pub const INVALID_ORIGIN: u8 = 6;
111 pub const INVALID_NEXT_HOP: u8 = 8;
114 pub const OPTIONAL_ATTRIBUTE_ERROR: u8 = 9;
116 pub const INVALID_NETWORK_FIELD: u8 = 10;
118 pub const MALFORMED_AS_PATH: u8 = 11;
120}
121
122pub mod cease_subcode {
124 pub const MAX_PREFIXES: u8 = 1;
126 pub const ADMINISTRATIVE_SHUTDOWN: u8 = 2;
128 pub const PEER_DECONFIGURED: u8 = 3;
130 pub const ADMINISTRATIVE_RESET: u8 = 4;
132 pub const OUT_OF_RESOURCES: u8 = 8;
134 pub const CONNECTION_COLLISION_RESOLUTION: u8 = 7;
136 pub const HARD_RESET: u8 = 9;
138}
139
140#[must_use]
146pub fn encode_shutdown_communication(reason: &str) -> bytes::Bytes {
147 let mut end = reason.len().min(128);
149 while end > 0 && !reason.is_char_boundary(end) {
150 end -= 1;
151 }
152 let truncated = &reason[..end];
153 #[expect(clippy::cast_possible_truncation)]
155 let len = truncated.len() as u8;
156 let mut buf = Vec::with_capacity(1 + truncated.len());
157 buf.push(len);
158 buf.extend_from_slice(truncated.as_bytes());
159 bytes::Bytes::from(buf)
160}
161
162#[must_use]
168pub fn decode_shutdown_communication(data: &[u8]) -> Option<String> {
169 if data.is_empty() {
170 return None;
171 }
172 let len = data[0] as usize;
173 if data.len() < 1 + len {
174 return None;
175 }
176 let raw = &data[1..=len];
177 Some(String::from_utf8_lossy(raw).into_owned())
178}
179
180#[must_use]
182pub fn description(code: NotificationCode, subcode: u8) -> &'static str {
183 match (code, subcode) {
184 (NotificationCode::MessageHeader, 1) => "Connection Not Synchronized",
186 (NotificationCode::MessageHeader, 2) => "Bad Message Length",
187 (NotificationCode::MessageHeader, 3) => "Bad Message Type",
188 (NotificationCode::OpenMessage, 1) => "Unsupported Version Number",
190 (NotificationCode::OpenMessage, 2) => "Bad Peer AS",
191 (NotificationCode::OpenMessage, 3) => "Bad BGP Identifier",
192 (NotificationCode::OpenMessage, 4) => "Unsupported Optional Parameter",
193 (NotificationCode::OpenMessage, 6) => "Unacceptable Hold Time",
194 (NotificationCode::OpenMessage, 7) => "Unsupported Capability",
195 (NotificationCode::OpenMessage, 11) => "Role Mismatch",
196 (NotificationCode::UpdateMessage, 1) => "Malformed Attribute List",
198 (NotificationCode::UpdateMessage, 2) => "Unrecognized Well-known Attribute",
199 (NotificationCode::UpdateMessage, 3) => "Missing Well-known Attribute",
200 (NotificationCode::UpdateMessage, 4) => "Attribute Flags Error",
201 (NotificationCode::UpdateMessage, 5) => "Attribute Length Error",
202 (NotificationCode::UpdateMessage, 6) => "Invalid ORIGIN Attribute",
203 (NotificationCode::UpdateMessage, 8) => "Invalid NEXT_HOP Attribute",
204 (NotificationCode::UpdateMessage, 9) => "Optional Attribute Error",
205 (NotificationCode::UpdateMessage, 10) => "Invalid Network Field",
206 (NotificationCode::UpdateMessage, 11) => "Malformed AS_PATH",
207 (NotificationCode::HoldTimerExpired, _) => "Hold Timer Expired",
209 (NotificationCode::FsmError, _) => "Finite State Machine Error",
211 (NotificationCode::Cease, 1) => "Maximum Number of Prefixes Reached",
213 (NotificationCode::Cease, 2) => "Administrative Shutdown",
214 (NotificationCode::Cease, 3) => "Peer De-configured",
215 (NotificationCode::Cease, 4) => "Administrative Reset",
216 (NotificationCode::Cease, 8) => "Out of Resources",
217 (NotificationCode::Cease, 7) => "Connection Collision Resolution",
218 (NotificationCode::Cease, 9) => "Hard Reset",
219 (NotificationCode::Unknown(_), _) => "Unknown Error Code",
221 (_, _) => "Unknown",
223 }
224}
225
226#[cfg(test)]
227mod tests {
228 use super::*;
229
230 #[test]
231 fn from_u8_roundtrip() {
232 for code_val in 1..=6u8 {
233 let code = NotificationCode::from_u8(code_val);
234 assert_eq!(code.as_u8(), code_val);
235 assert!(!matches!(code, NotificationCode::Unknown(_)));
236 }
237 }
238
239 #[test]
240 fn from_u8_unknown_preserved() {
241 assert_eq!(NotificationCode::from_u8(0), NotificationCode::Unknown(0));
242 assert_eq!(NotificationCode::from_u8(7), NotificationCode::Unknown(7));
243 assert_eq!(
244 NotificationCode::from_u8(255),
245 NotificationCode::Unknown(255)
246 );
247 assert_eq!(NotificationCode::from_u8(42).as_u8(), 42);
249 }
250
251 #[test]
252 fn description_returns_nonempty_for_known_pairs() {
253 let pairs = [
254 (NotificationCode::MessageHeader, 1),
255 (NotificationCode::MessageHeader, 2),
256 (NotificationCode::MessageHeader, 3),
257 (NotificationCode::OpenMessage, 1),
258 (NotificationCode::OpenMessage, 6),
259 (NotificationCode::UpdateMessage, 1),
260 (NotificationCode::UpdateMessage, 11),
261 (NotificationCode::Cease, 2),
262 (NotificationCode::Cease, 4),
263 ];
264 for (code, subcode) in pairs {
265 let desc = description(code, subcode);
266 assert!(
267 !desc.is_empty(),
268 "empty description for ({code}, {subcode})"
269 );
270 assert_ne!(desc, "Unknown", "got Unknown for ({code}, {subcode})");
271 }
272 }
273
274 #[test]
275 fn shutdown_communication_roundtrip() {
276 let reason = "maintenance window";
277 let encoded = encode_shutdown_communication(reason);
278 assert_eq!(encoded[0] as usize, reason.len());
279 let decoded = decode_shutdown_communication(&encoded).unwrap();
280 assert_eq!(decoded, reason);
281 }
282
283 #[test]
284 fn shutdown_communication_empty() {
285 let encoded = encode_shutdown_communication("");
286 assert_eq!(encoded.as_ref(), &[0]);
287 assert_eq!(decode_shutdown_communication(&encoded).as_deref(), Some(""));
288 assert_eq!(decode_shutdown_communication(&[]), None);
289 }
290
291 #[test]
292 fn shutdown_communication_truncates_at_128() {
293 let long = "a".repeat(200);
294 let encoded = encode_shutdown_communication(&long);
295 assert_eq!(encoded[0], 128);
296 assert_eq!(encoded.len(), 129);
297 let decoded = decode_shutdown_communication(&encoded).unwrap();
298 assert_eq!(decoded.len(), 128);
299 }
300
301 #[test]
302 fn shutdown_communication_truncates_at_char_boundary() {
303 let reason = format!("{}é", "x".repeat(127));
305 assert_eq!(reason.len(), 129);
306 let encoded = encode_shutdown_communication(&reason);
307 assert_eq!(encoded[0], 127);
309 let decoded = decode_shutdown_communication(&encoded).unwrap();
310 assert_eq!(decoded, "x".repeat(127));
311 }
312
313 #[test]
314 fn shutdown_communication_invalid_utf8() {
315 let data = [3, 0xff, 0xfe, 0xfd];
317 let decoded = decode_shutdown_communication(&data).unwrap();
318 assert!(decoded.contains('\u{FFFD}')); }
320
321 #[test]
322 fn shutdown_communication_ignores_trailing_bytes() {
323 let data = [3, b'f', b'o', b'o', b'x'];
324 assert_eq!(decode_shutdown_communication(&data).as_deref(), Some("foo"));
325 }
326}