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 link_local_next_hop: None,
416 announced: vec![NlriEntry {
417 path_id: 0,
418 prefix: Prefix::V4(announced[0].prefix),
419 }],
420 flowspec_announced: vec![],
421 evpn_announced: vec![],
422 }),
423 ];
424
425 let msg = UpdateMessage::build(
426 &announced,
427 &[],
428 &attrs,
429 true,
430 false,
431 Ipv4UnicastMode::MpReach,
432 );
433 assert!(msg.withdrawn_routes.is_empty());
434 assert!(msg.nlri.is_empty());
435
436 let parsed = msg.parse(true, false, &[]).unwrap();
437 assert!(parsed.announced.is_empty());
438 let mp = parsed
439 .attributes
440 .iter()
441 .find_map(|attr| match attr {
442 PathAttribute::MpReachNlri(mp) => Some(mp),
443 _ => None,
444 })
445 .unwrap();
446 assert_eq!(mp.afi, Afi::Ipv4);
447 assert_eq!(mp.safi, Safi::Unicast);
448 assert_eq!(mp.announced.len(), 1);
449 assert_eq!(mp.announced[0].prefix, Prefix::V4(announced[0].prefix));
450 assert_eq!(mp.next_hop, IpAddr::V6(Ipv6Addr::LOCALHOST));
451 }
452
453 #[test]
454 fn build_withdrawal_only() {
455 let withdrawn = vec![entry(Ipv4Prefix::new(
456 std::net::Ipv4Addr::new(10, 0, 0, 0),
457 24,
458 ))];
459 let msg = UpdateMessage::build(&[], &withdrawn, &[], true, false, Ipv4UnicastMode::Body);
460 let parsed = msg.parse(true, false, &[]).unwrap();
461 assert!(parsed.announced.is_empty());
462 assert_eq!(parsed.withdrawn, withdrawn);
463 assert!(parsed.attributes.is_empty());
464 }
465
466 #[test]
467 fn build_announce_only() {
468 use crate::attribute::Origin;
469
470 let announced = vec![entry(Ipv4Prefix::new(
471 std::net::Ipv4Addr::new(10, 1, 0, 0),
472 16,
473 ))];
474 let attrs = vec![
475 PathAttribute::Origin(Origin::Igp),
476 PathAttribute::NextHop(std::net::Ipv4Addr::new(10, 0, 0, 1)),
477 ];
478 let msg = UpdateMessage::build(&announced, &[], &attrs, true, false, Ipv4UnicastMode::Body);
479
480 let mut encoded = BytesMut::with_capacity(msg.encoded_len());
482 msg.encode(&mut encoded).unwrap();
483
484 let mut bytes = encoded.freeze();
485 let header = BgpHeader::decode(&mut bytes, MAX_MESSAGE_LEN).unwrap();
486 let body_len = usize::from(header.length) - HEADER_LEN;
487 let decoded = UpdateMessage::decode(&mut bytes, body_len).unwrap();
488 let parsed = decoded.parse(true, false, &[]).unwrap();
489 assert_eq!(parsed.announced, announced);
490 assert_eq!(parsed.attributes, attrs);
491 }
492
493 #[test]
494 fn build_mixed() {
495 use crate::attribute::Origin;
496
497 let announced = vec![entry(Ipv4Prefix::new(
498 std::net::Ipv4Addr::new(10, 0, 0, 0),
499 24,
500 ))];
501 let withdrawn = vec![entry(Ipv4Prefix::new(
502 std::net::Ipv4Addr::new(172, 16, 0, 0),
503 16,
504 ))];
505 let attrs = vec![
506 PathAttribute::Origin(Origin::Igp),
507 PathAttribute::NextHop(std::net::Ipv4Addr::new(10, 0, 0, 1)),
508 ];
509
510 let msg = UpdateMessage::build(
511 &announced,
512 &withdrawn,
513 &attrs,
514 true,
515 false,
516 Ipv4UnicastMode::Body,
517 );
518 let parsed = msg.parse(true, false, &[]).unwrap();
519 assert_eq!(parsed.announced, announced);
520 assert_eq!(parsed.withdrawn, withdrawn);
521 assert_eq!(parsed.attributes, attrs);
522 }
523
524 #[test]
525 fn build_roundtrip_with_add_path() {
526 use crate::attribute::{AsPath, AsPathSegment, Origin};
527
528 let announced = vec![
529 Ipv4NlriEntry {
530 path_id: 1,
531 prefix: Ipv4Prefix::new(std::net::Ipv4Addr::new(10, 0, 0, 0), 24),
532 },
533 Ipv4NlriEntry {
534 path_id: 2,
535 prefix: Ipv4Prefix::new(std::net::Ipv4Addr::new(10, 0, 0, 0), 24),
536 },
537 ];
538 let withdrawn = vec![Ipv4NlriEntry {
539 path_id: 3,
540 prefix: Ipv4Prefix::new(std::net::Ipv4Addr::new(192, 168, 0, 0), 16),
541 }];
542 let attrs = vec![
543 PathAttribute::Origin(Origin::Igp),
544 PathAttribute::AsPath(AsPath {
545 segments: vec![AsPathSegment::AsSequence(vec![65001])],
546 }),
547 PathAttribute::NextHop(std::net::Ipv4Addr::new(10, 0, 0, 1)),
548 ];
549
550 let msg = UpdateMessage::build(
551 &announced,
552 &withdrawn,
553 &attrs,
554 true,
555 true,
556 Ipv4UnicastMode::Body,
557 );
558 let parsed = msg.parse(true, true, &[]).unwrap();
559 assert_eq!(parsed.announced, announced);
560 assert_eq!(parsed.withdrawn, withdrawn);
561 assert_eq!(parsed.attributes, attrs);
562 }
563
564 #[test]
565 fn reject_message_too_long() {
566 let msg = UpdateMessage {
567 withdrawn_routes: Bytes::new(),
568 path_attributes: Bytes::from(vec![0u8; 4096]),
569 nlri: Bytes::new(),
570 };
571 let mut buf = BytesMut::with_capacity(5000);
572 assert!(matches!(
573 msg.encode(&mut buf),
574 Err(EncodeError::MessageTooLong { .. })
575 ));
576 }
577}