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