use std::net::Ipv4Addr;
use bytes::{Buf, BufMut, BytesMut};
use crate::capability::{Capability, decode_optional_parameters, encode_optional_parameters};
use crate::constants::{BGP_VERSION, HEADER_LEN, MAX_MESSAGE_LEN};
use crate::error::{DecodeError, EncodeError};
use crate::header::{BgpHeader, MessageType};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OpenMessage {
pub version: u8,
pub my_as: u16,
pub hold_time: u16,
pub bgp_identifier: Ipv4Addr,
pub capabilities: Vec<Capability>,
}
impl OpenMessage {
pub fn decode(buf: &mut impl Buf, body_len: usize) -> Result<Self, DecodeError> {
if body_len < 10 {
return Err(DecodeError::MalformedField {
message_type: "OPEN",
detail: format!("body too short: {body_len} bytes (need at least 10)"),
});
}
if buf.remaining() < body_len {
return Err(DecodeError::Incomplete {
needed: body_len,
available: buf.remaining(),
});
}
let version = buf.get_u8();
if version != BGP_VERSION {
return Err(DecodeError::UnsupportedVersion { version });
}
let my_as = buf.get_u16();
let hold_time = buf.get_u16();
let bgp_identifier = Ipv4Addr::from(buf.get_u32());
let opt_params_len = buf.get_u8();
let expected_body = 10 + usize::from(opt_params_len);
if expected_body != body_len {
return Err(DecodeError::MalformedField {
message_type: "OPEN",
detail: format!(
"optional parameters length {opt_params_len} inconsistent \
with body length {body_len} (expected {expected_body})"
),
});
}
let capabilities = decode_optional_parameters(buf, opt_params_len)?;
Ok(Self {
version,
my_as,
hold_time,
bgp_identifier,
capabilities,
})
}
pub fn encode(&self, buf: &mut impl BufMut) -> Result<(), EncodeError> {
let mut opt_params = BytesMut::new();
encode_optional_parameters(&self.capabilities, &mut opt_params)?;
let opt_params_len = opt_params.len();
if opt_params_len > 255 {
return Err(EncodeError::ValueOutOfRange {
field: "optional_parameters_length",
value: opt_params_len.to_string(),
});
}
let total_len = HEADER_LEN + 10 + opt_params_len;
if total_len > usize::from(MAX_MESSAGE_LEN) {
return Err(EncodeError::MessageTooLong { size: total_len });
}
let header = BgpHeader {
#[expect(clippy::cast_possible_truncation)]
length: total_len as u16,
message_type: MessageType::Open,
};
header.encode(buf);
buf.put_u8(self.version);
buf.put_u16(self.my_as);
buf.put_u16(self.hold_time);
buf.put_u32(u32::from(self.bgp_identifier));
#[expect(clippy::cast_possible_truncation)]
buf.put_u8(opt_params_len as u8);
buf.put_slice(&opt_params);
Ok(())
}
#[must_use]
pub fn encoded_len(&self) -> usize {
let cap_size: usize = self.capabilities.iter().map(Capability::encoded_len).sum();
let opt_params_overhead = if self.capabilities.is_empty() { 0 } else { 2 };
HEADER_LEN + 10 + opt_params_overhead + cap_size
}
#[must_use]
pub fn four_byte_as(&self) -> u32 {
for cap in &self.capabilities {
if let Capability::FourOctetAs { asn } = cap {
return *asn;
}
}
u32::from(self.my_as)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::capability::Afi;
use crate::capability::Safi;
use crate::constants::MAX_MESSAGE_LEN;
fn minimal_open() -> OpenMessage {
OpenMessage {
version: BGP_VERSION,
my_as: 65001,
hold_time: 90,
bgp_identifier: Ipv4Addr::new(10, 0, 0, 1),
capabilities: vec![],
}
}
#[test]
fn encode_decode_minimal_open() {
let original = minimal_open();
let mut encoded = BytesMut::with_capacity(64);
original.encode(&mut encoded).unwrap();
let mut bytes = encoded.freeze();
let header = BgpHeader::decode(&mut bytes, MAX_MESSAGE_LEN).unwrap();
assert_eq!(header.message_type, MessageType::Open);
assert_eq!(header.length, 29);
let body_len = usize::from(header.length) - HEADER_LEN;
let decoded = OpenMessage::decode(&mut bytes, body_len).unwrap();
assert_eq!(original, decoded);
}
#[test]
fn encode_decode_with_capabilities() {
let original = OpenMessage {
version: BGP_VERSION,
my_as: 23456, hold_time: 90,
bgp_identifier: Ipv4Addr::new(10, 0, 0, 1),
capabilities: vec![
Capability::MultiProtocol {
afi: Afi::Ipv4,
safi: Safi::Unicast,
},
Capability::FourOctetAs { asn: 4_200_000_001 },
],
};
let mut encoded = BytesMut::with_capacity(128);
original.encode(&mut encoded).unwrap();
let mut bytes = encoded.freeze();
let header = BgpHeader::decode(&mut bytes, MAX_MESSAGE_LEN).unwrap();
let body_len = usize::from(header.length) - HEADER_LEN;
let decoded = OpenMessage::decode(&mut bytes, body_len).unwrap();
assert_eq!(original, decoded);
}
#[test]
fn four_byte_as_extraction() {
let open = OpenMessage {
version: BGP_VERSION,
my_as: 23456,
hold_time: 90,
bgp_identifier: Ipv4Addr::new(10, 0, 0, 1),
capabilities: vec![Capability::FourOctetAs { asn: 4_200_000_001 }],
};
assert_eq!(open.four_byte_as(), 4_200_000_001);
}
#[test]
fn four_byte_as_fallback_to_my_as() {
let open = minimal_open();
assert_eq!(open.four_byte_as(), 65001);
}
#[test]
fn reject_bad_version() {
let body: &[u8] = &[
3, 0xFD, 0xE9, 0, 90, 10, 0, 0, 1, 0, ];
let mut buf = bytes::Bytes::copy_from_slice(body);
assert!(matches!(
OpenMessage::decode(&mut buf, 10),
Err(DecodeError::UnsupportedVersion { version: 3 })
));
}
#[test]
fn reject_body_too_short() {
let body: &[u8] = &[4, 0, 1]; let mut buf = bytes::Bytes::copy_from_slice(body);
assert!(matches!(
OpenMessage::decode(&mut buf, 3),
Err(DecodeError::MalformedField { .. })
));
}
#[test]
fn reject_inconsistent_opt_params_length() {
let body: &[u8] = &[
4, 0xFD, 0xE9, 0, 90, 10, 0, 0, 1, 5, ];
let mut buf = bytes::Bytes::copy_from_slice(body);
assert!(matches!(
OpenMessage::decode(&mut buf, 10),
Err(DecodeError::MalformedField { .. })
));
}
#[test]
fn unknown_capabilities_preserved() {
let original = OpenMessage {
version: BGP_VERSION,
my_as: 65001,
hold_time: 90,
bgp_identifier: Ipv4Addr::new(10, 0, 0, 1),
capabilities: vec![Capability::Unknown {
code: 128,
data: bytes::Bytes::from_static(&[0xDE, 0xAD]),
}],
};
let mut encoded = BytesMut::with_capacity(64);
original.encode(&mut encoded).unwrap();
let mut bytes = encoded.freeze();
let header = BgpHeader::decode(&mut bytes, MAX_MESSAGE_LEN).unwrap();
let body_len = usize::from(header.length) - HEADER_LEN;
let decoded = OpenMessage::decode(&mut bytes, body_len).unwrap();
assert_eq!(original, decoded);
}
}