1use bytes::{Buf, BufMut, Bytes};
2
3use crate::attribute::PathAttribute;
4use crate::constants::{HEADER_LEN, MAX_MESSAGE_LEN};
5use crate::error::{DecodeError, EncodeError};
6use crate::header::{BgpHeader, MessageType};
7use crate::nlri::{Ipv4NlriEntry, Ipv4Prefix};
8use crate::{Afi, Safi};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum Ipv4UnicastMode {
13 Body,
15 MpReach,
18}
19
20#[derive(Debug, Clone, PartialEq, Eq)]
25pub struct UpdateMessage {
26 pub withdrawn_routes: Bytes,
28 pub path_attributes: Bytes,
30 pub nlri: Bytes,
32}
33
34#[derive(Debug, Clone, PartialEq, Eq)]
39pub struct ParsedUpdate {
40 pub withdrawn: Vec<Ipv4NlriEntry>,
42 pub attributes: Vec<PathAttribute>,
44 pub announced: Vec<Ipv4NlriEntry>,
46}
47
48impl UpdateMessage {
49 pub fn decode(buf: &mut impl Buf, body_len: usize) -> Result<Self, DecodeError> {
59 if body_len < 4 {
61 return Err(DecodeError::UpdateLengthMismatch {
62 detail: format!("body too short: {body_len} bytes (need at least 4)"),
63 });
64 }
65
66 if buf.remaining() < body_len {
67 return Err(DecodeError::Incomplete {
68 needed: body_len,
69 available: buf.remaining(),
70 });
71 }
72
73 let withdrawn_routes_len = buf.get_u16();
74
75 let after_withdrawn = body_len
78 .checked_sub(2)
79 .and_then(|v| v.checked_sub(usize::from(withdrawn_routes_len)))
80 .ok_or_else(|| DecodeError::UpdateLengthMismatch {
81 detail: format!("withdrawn routes length {withdrawn_routes_len} exceeds body"),
82 })?;
83
84 if after_withdrawn < 2 {
85 return Err(DecodeError::UpdateLengthMismatch {
86 detail: format!(
87 "no room for path attributes length after {withdrawn_routes_len} \
88 bytes of withdrawn routes"
89 ),
90 });
91 }
92
93 let withdrawn_routes = buf.copy_to_bytes(usize::from(withdrawn_routes_len));
94
95 let path_attributes_len = buf.get_u16();
96
97 let nlri_len = after_withdrawn
98 .checked_sub(2)
99 .and_then(|v| v.checked_sub(usize::from(path_attributes_len)))
100 .ok_or_else(|| DecodeError::UpdateLengthMismatch {
101 detail: format!(
102 "path attributes length {path_attributes_len} exceeds remaining body"
103 ),
104 })?;
105
106 let path_attributes = buf.copy_to_bytes(usize::from(path_attributes_len));
107 let nlri = buf.copy_to_bytes(nlri_len);
108
109 Ok(Self {
110 withdrawn_routes,
111 path_attributes,
112 nlri,
113 })
114 }
115
116 pub fn parse(
128 &self,
129 four_octet_as: bool,
130 add_path_ipv4: bool,
131 add_path_families: &[(Afi, Safi)],
132 ) -> Result<ParsedUpdate, DecodeError> {
133 let withdrawn = if add_path_ipv4 {
134 crate::nlri::decode_nlri_addpath(&self.withdrawn_routes)?
135 } else {
136 crate::nlri::decode_nlri(&self.withdrawn_routes)?
137 .into_iter()
138 .map(|prefix| Ipv4NlriEntry { path_id: 0, prefix })
139 .collect()
140 };
141 let attributes = crate::attribute::decode_path_attributes(
142 &self.path_attributes,
143 four_octet_as,
144 add_path_families,
145 )?;
146 let announced = if add_path_ipv4 {
147 crate::nlri::decode_nlri_addpath(&self.nlri)?
148 } else {
149 crate::nlri::decode_nlri(&self.nlri)?
150 .into_iter()
151 .map(|prefix| Ipv4NlriEntry { path_id: 0, prefix })
152 .collect()
153 };
154
155 Ok(ParsedUpdate {
156 withdrawn,
157 attributes,
158 announced,
159 })
160 }
161
162 pub fn encode_with_limit(
172 &self,
173 buf: &mut impl BufMut,
174 max_message_len: u16,
175 ) -> Result<(), EncodeError> {
176 let body_len =
177 2 + self.withdrawn_routes.len() + 2 + self.path_attributes.len() + self.nlri.len();
178 let total_len = HEADER_LEN + body_len;
179
180 if total_len > usize::from(max_message_len) {
181 return Err(EncodeError::MessageTooLong { size: total_len });
182 }
183
184 let header = BgpHeader {
185 #[expect(clippy::cast_possible_truncation)]
186 length: total_len as u16,
187 message_type: MessageType::Update,
188 };
189 header.encode(buf);
190
191 #[expect(clippy::cast_possible_truncation)]
192 buf.put_u16(self.withdrawn_routes.len() as u16);
193 buf.put_slice(&self.withdrawn_routes);
194
195 #[expect(clippy::cast_possible_truncation)]
196 buf.put_u16(self.path_attributes.len() as u16);
197 buf.put_slice(&self.path_attributes);
198
199 buf.put_slice(&self.nlri);
200
201 Ok(())
202 }
203
204 pub fn encode(&self, buf: &mut impl BufMut) -> Result<(), EncodeError> {
211 self.encode_with_limit(buf, MAX_MESSAGE_LEN)
212 }
213
214 #[must_use]
222 pub fn build(
223 announced: &[Ipv4NlriEntry],
224 withdrawn: &[Ipv4NlriEntry],
225 attributes: &[PathAttribute],
226 four_octet_as: bool,
227 add_path: bool,
228 ipv4_unicast_mode: Ipv4UnicastMode,
229 ) -> Self {
230 let mut withdrawn_buf = Vec::new();
231 if matches!(ipv4_unicast_mode, Ipv4UnicastMode::Body) {
232 if add_path {
233 crate::nlri::encode_nlri_addpath(withdrawn, &mut withdrawn_buf);
234 } else {
235 let prefixes: Vec<Ipv4Prefix> = withdrawn.iter().map(|e| e.prefix).collect();
236 crate::nlri::encode_nlri(&prefixes, &mut withdrawn_buf);
237 }
238 }
239
240 let mut attrs_buf = Vec::new();
241 if !attributes.is_empty() {
242 crate::attribute::encode_path_attributes(
243 attributes,
244 &mut attrs_buf,
245 four_octet_as,
246 add_path,
247 );
248 }
249
250 let mut nlri_buf = Vec::new();
251 if matches!(ipv4_unicast_mode, Ipv4UnicastMode::Body) {
252 if add_path {
253 crate::nlri::encode_nlri_addpath(announced, &mut nlri_buf);
254 } else {
255 let prefixes: Vec<Ipv4Prefix> = announced.iter().map(|e| e.prefix).collect();
256 crate::nlri::encode_nlri(&prefixes, &mut nlri_buf);
257 }
258 }
259
260 Self {
261 withdrawn_routes: Bytes::from(withdrawn_buf),
262 path_attributes: Bytes::from(attrs_buf),
263 nlri: Bytes::from(nlri_buf),
264 }
265 }
266
267 #[must_use]
269 pub fn encoded_len(&self) -> usize {
270 HEADER_LEN
271 + 2
272 + self.withdrawn_routes.len()
273 + 2
274 + self.path_attributes.len()
275 + self.nlri.len()
276 }
277}
278
279#[cfg(test)]
280mod tests {
281 use bytes::BytesMut;
282
283 use super::*;
284 use crate::constants::MAX_MESSAGE_LEN;
285 use crate::{NlriEntry, Prefix};
286
287 #[test]
288 fn decode_minimal_update() {
289 let body: &[u8] = &[0, 0, 0, 0];
291 let mut buf = Bytes::copy_from_slice(body);
292 let msg = UpdateMessage::decode(&mut buf, 4).unwrap();
293 assert!(msg.withdrawn_routes.is_empty());
294 assert!(msg.path_attributes.is_empty());
295 assert!(msg.nlri.is_empty());
296 }
297
298 #[test]
299 fn decode_with_withdrawn_routes() {
300 let body: &[u8] = &[0, 3, 0x18, 0x0A, 0x00, 0, 0];
302 let mut buf = Bytes::copy_from_slice(body);
303 let msg = UpdateMessage::decode(&mut buf, 7).unwrap();
304 assert_eq!(msg.withdrawn_routes.as_ref(), &[0x18, 0x0A, 0x00]);
305 assert!(msg.path_attributes.is_empty());
306 assert!(msg.nlri.is_empty());
307 }
308
309 #[test]
310 fn decode_with_all_sections() {
311 let mut body = BytesMut::new();
312 body.put_u16(2); body.put_slice(&[0x10, 0x0A]); body.put_u16(3); body.put_slice(&[0x40, 0x01, 0x00]); body.put_slice(&[0x18, 0xC0, 0xA8]); let total = body.len();
319 let mut buf = body.freeze();
320 let msg = UpdateMessage::decode(&mut buf, total).unwrap();
321 assert_eq!(msg.withdrawn_routes.len(), 2);
322 assert_eq!(msg.path_attributes.len(), 3);
323 assert_eq!(msg.nlri.len(), 3);
324 }
325
326 #[test]
327 fn reject_withdrawn_overflow() {
328 let body: &[u8] = &[0, 100, 0, 0, 0, 0];
330 let mut buf = Bytes::copy_from_slice(body);
331 assert!(matches!(
332 UpdateMessage::decode(&mut buf, 6),
333 Err(DecodeError::UpdateLengthMismatch { .. })
334 ));
335 }
336
337 #[test]
338 fn reject_attrs_overflow() {
339 let body: &[u8] = &[0, 0, 0, 100];
341 let mut buf = Bytes::copy_from_slice(body);
342 assert!(matches!(
343 UpdateMessage::decode(&mut buf, 4),
344 Err(DecodeError::UpdateLengthMismatch { .. })
345 ));
346 }
347
348 #[test]
349 fn encode_decode_roundtrip() {
350 let original = UpdateMessage {
351 withdrawn_routes: Bytes::from_static(&[0x18, 0x0A, 0x00]),
352 path_attributes: Bytes::from_static(&[0x40, 0x01, 0x00]),
353 nlri: Bytes::from_static(&[0x18, 0xC0, 0xA8]),
354 };
355
356 let mut encoded = BytesMut::with_capacity(original.encoded_len());
357 original.encode(&mut encoded).unwrap();
358
359 let mut bytes = encoded.freeze();
360 let header = BgpHeader::decode(&mut bytes, MAX_MESSAGE_LEN).unwrap();
361 assert_eq!(header.message_type, MessageType::Update);
362
363 let body_len = usize::from(header.length) - HEADER_LEN;
364 let decoded = UpdateMessage::decode(&mut bytes, body_len).unwrap();
365 assert_eq!(original, decoded);
366 }
367
368 fn entry(prefix: Ipv4Prefix) -> Ipv4NlriEntry {
370 Ipv4NlriEntry { path_id: 0, prefix }
371 }
372
373 #[test]
374 fn build_roundtrip() {
375 use crate::attribute::{AsPath, AsPathSegment, Origin};
376
377 let announced = vec![
378 entry(Ipv4Prefix::new(std::net::Ipv4Addr::new(10, 0, 0, 0), 24)),
379 entry(Ipv4Prefix::new(std::net::Ipv4Addr::new(192, 168, 1, 0), 24)),
380 ];
381 let attrs = vec![
382 PathAttribute::Origin(Origin::Igp),
383 PathAttribute::AsPath(AsPath {
384 segments: vec![AsPathSegment::AsSequence(vec![65001])],
385 }),
386 PathAttribute::NextHop(std::net::Ipv4Addr::new(10, 0, 0, 1)),
387 ];
388
389 let msg = UpdateMessage::build(&announced, &[], &attrs, true, false, Ipv4UnicastMode::Body);
390 let parsed = msg.parse(true, false, &[]).unwrap();
391 assert_eq!(parsed.announced, announced);
392 assert!(parsed.withdrawn.is_empty());
393 assert_eq!(parsed.attributes, attrs);
394 }
395
396 #[test]
397 fn build_ipv4_mp_mode_omits_body_nlri() {
398 use std::net::{IpAddr, Ipv6Addr};
399
400 use crate::attribute::{AsPath, AsPathSegment, MpReachNlri, Origin};
401
402 let announced = vec![entry(Ipv4Prefix::new(
403 std::net::Ipv4Addr::new(10, 0, 0, 0),
404 24,
405 ))];
406 let attrs = vec![
407 PathAttribute::Origin(Origin::Igp),
408 PathAttribute::AsPath(AsPath {
409 segments: vec![AsPathSegment::AsSequence(vec![65001])],
410 }),
411 PathAttribute::MpReachNlri(MpReachNlri {
412 afi: Afi::Ipv4,
413 safi: Safi::Unicast,
414 next_hop: IpAddr::V6(Ipv6Addr::LOCALHOST),
415 announced: vec![NlriEntry {
416 path_id: 0,
417 prefix: Prefix::V4(announced[0].prefix),
418 }],
419 flowspec_announced: vec![],
420 evpn_announced: vec![],
421 }),
422 ];
423
424 let msg = UpdateMessage::build(
425 &announced,
426 &[],
427 &attrs,
428 true,
429 false,
430 Ipv4UnicastMode::MpReach,
431 );
432 assert!(msg.withdrawn_routes.is_empty());
433 assert!(msg.nlri.is_empty());
434
435 let parsed = msg.parse(true, false, &[]).unwrap();
436 assert!(parsed.announced.is_empty());
437 let mp = parsed
438 .attributes
439 .iter()
440 .find_map(|attr| match attr {
441 PathAttribute::MpReachNlri(mp) => Some(mp),
442 _ => None,
443 })
444 .unwrap();
445 assert_eq!(mp.afi, Afi::Ipv4);
446 assert_eq!(mp.safi, Safi::Unicast);
447 assert_eq!(mp.announced.len(), 1);
448 assert_eq!(mp.announced[0].prefix, Prefix::V4(announced[0].prefix));
449 assert_eq!(mp.next_hop, IpAddr::V6(Ipv6Addr::LOCALHOST));
450 }
451
452 #[test]
453 fn build_withdrawal_only() {
454 let withdrawn = vec![entry(Ipv4Prefix::new(
455 std::net::Ipv4Addr::new(10, 0, 0, 0),
456 24,
457 ))];
458 let msg = UpdateMessage::build(&[], &withdrawn, &[], true, false, Ipv4UnicastMode::Body);
459 let parsed = msg.parse(true, false, &[]).unwrap();
460 assert!(parsed.announced.is_empty());
461 assert_eq!(parsed.withdrawn, withdrawn);
462 assert!(parsed.attributes.is_empty());
463 }
464
465 #[test]
466 fn build_announce_only() {
467 use crate::attribute::Origin;
468
469 let announced = vec![entry(Ipv4Prefix::new(
470 std::net::Ipv4Addr::new(10, 1, 0, 0),
471 16,
472 ))];
473 let attrs = vec![
474 PathAttribute::Origin(Origin::Igp),
475 PathAttribute::NextHop(std::net::Ipv4Addr::new(10, 0, 0, 1)),
476 ];
477 let msg = UpdateMessage::build(&announced, &[], &attrs, true, false, Ipv4UnicastMode::Body);
478
479 let mut encoded = BytesMut::with_capacity(msg.encoded_len());
481 msg.encode(&mut encoded).unwrap();
482
483 let mut bytes = encoded.freeze();
484 let header = BgpHeader::decode(&mut bytes, MAX_MESSAGE_LEN).unwrap();
485 let body_len = usize::from(header.length) - HEADER_LEN;
486 let decoded = UpdateMessage::decode(&mut bytes, body_len).unwrap();
487 let parsed = decoded.parse(true, false, &[]).unwrap();
488 assert_eq!(parsed.announced, announced);
489 assert_eq!(parsed.attributes, attrs);
490 }
491
492 #[test]
493 fn build_mixed() {
494 use crate::attribute::Origin;
495
496 let announced = vec![entry(Ipv4Prefix::new(
497 std::net::Ipv4Addr::new(10, 0, 0, 0),
498 24,
499 ))];
500 let withdrawn = vec![entry(Ipv4Prefix::new(
501 std::net::Ipv4Addr::new(172, 16, 0, 0),
502 16,
503 ))];
504 let attrs = vec![
505 PathAttribute::Origin(Origin::Igp),
506 PathAttribute::NextHop(std::net::Ipv4Addr::new(10, 0, 0, 1)),
507 ];
508
509 let msg = UpdateMessage::build(
510 &announced,
511 &withdrawn,
512 &attrs,
513 true,
514 false,
515 Ipv4UnicastMode::Body,
516 );
517 let parsed = msg.parse(true, false, &[]).unwrap();
518 assert_eq!(parsed.announced, announced);
519 assert_eq!(parsed.withdrawn, withdrawn);
520 assert_eq!(parsed.attributes, attrs);
521 }
522
523 #[test]
524 fn build_roundtrip_with_add_path() {
525 use crate::attribute::{AsPath, AsPathSegment, Origin};
526
527 let announced = vec![
528 Ipv4NlriEntry {
529 path_id: 1,
530 prefix: Ipv4Prefix::new(std::net::Ipv4Addr::new(10, 0, 0, 0), 24),
531 },
532 Ipv4NlriEntry {
533 path_id: 2,
534 prefix: Ipv4Prefix::new(std::net::Ipv4Addr::new(10, 0, 0, 0), 24),
535 },
536 ];
537 let withdrawn = vec![Ipv4NlriEntry {
538 path_id: 3,
539 prefix: Ipv4Prefix::new(std::net::Ipv4Addr::new(192, 168, 0, 0), 16),
540 }];
541 let attrs = vec![
542 PathAttribute::Origin(Origin::Igp),
543 PathAttribute::AsPath(AsPath {
544 segments: vec![AsPathSegment::AsSequence(vec![65001])],
545 }),
546 PathAttribute::NextHop(std::net::Ipv4Addr::new(10, 0, 0, 1)),
547 ];
548
549 let msg = UpdateMessage::build(
550 &announced,
551 &withdrawn,
552 &attrs,
553 true,
554 true,
555 Ipv4UnicastMode::Body,
556 );
557 let parsed = msg.parse(true, true, &[]).unwrap();
558 assert_eq!(parsed.announced, announced);
559 assert_eq!(parsed.withdrawn, withdrawn);
560 assert_eq!(parsed.attributes, attrs);
561 }
562
563 #[test]
564 fn reject_message_too_long() {
565 let msg = UpdateMessage {
566 withdrawn_routes: Bytes::new(),
567 path_attributes: Bytes::from(vec![0u8; 4096]),
568 nlri: Bytes::new(),
569 };
570 let mut buf = BytesMut::with_capacity(5000);
571 assert!(matches!(
572 msg.encode(&mut buf),
573 Err(EncodeError::MessageTooLong { .. })
574 ));
575 }
576}