Add top level message parser
This commit is contained in:
1 parent
5ae8f6e249
commit
b26160ff40
2 files changed
+198
-142
No files matched your search
+25
-140
@@ -1,4 +1,5 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use strum::FromRepr;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Error, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Error, Serialize, Deserialize)]
|
||||||
@@ -46,19 +47,20 @@ impl BgpError {
|
|||||||
|
|
||||||
pub fn code_subcode(&self) -> (u8, u8) {
|
pub fn code_subcode(&self) -> (u8, u8) {
|
||||||
match self {
|
match self {
|
||||||
Self::MsgHeader(s) => (1, u8::from(s)),
|
Self::MsgHeader(s) => (1, *s as u8),
|
||||||
Self::OpenMsg(s) => (2, u8::from(s)),
|
Self::OpenMsg(s) => (2, *s as u8),
|
||||||
Self::UpdateMsg(s) => (3, u8::from(s)),
|
Self::UpdateMsg(s) => (3, *s as u8),
|
||||||
Self::HoldTimer => (4, 0),
|
Self::HoldTimer => (4, 0),
|
||||||
Self::FsmError(s) => (5, u8::from(s)),
|
Self::FsmError(s) => (5, *s as u8),
|
||||||
Self::Cease(s) => (6, u8::from(s)),
|
Self::Cease(s) => (6, *s as u8),
|
||||||
Self::RouteRefresh(s) => (7, u8::from(s)),
|
Self::RouteRefresh(s) => (7, *s as u8),
|
||||||
Self::SendHoldTimer => (8, 0),
|
Self::SendHoldTimer => (8, 0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Error, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Error, Serialize, Deserialize, FromRepr)]
|
||||||
|
#[repr(u8)]
|
||||||
pub enum MsgHeaderSubcode {
|
pub enum MsgHeaderSubcode {
|
||||||
#[error("Connection not synchronized")]
|
#[error("Connection not synchronized")]
|
||||||
ConnNotSynchronized = 1,
|
ConnNotSynchronized = 1,
|
||||||
@@ -73,26 +75,12 @@ pub enum MsgHeaderSubcode {
|
|||||||
impl TryFrom<u8> for MsgHeaderSubcode {
|
impl TryFrom<u8> for MsgHeaderSubcode {
|
||||||
type Error = ();
|
type Error = ();
|
||||||
fn try_from(v: u8) -> Result<Self, ()> {
|
fn try_from(v: u8) -> Result<Self, ()> {
|
||||||
match v {
|
Self::from_repr(v).ok_or(())
|
||||||
1 => Ok(Self::ConnNotSynchronized),
|
|
||||||
2 => Ok(Self::BadMessageLength),
|
|
||||||
3 => Ok(Self::BadMessagType),
|
|
||||||
_ => Err(()),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&MsgHeaderSubcode> for u8 {
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Error, Serialize, Deserialize, FromRepr)]
|
||||||
fn from(s: &MsgHeaderSubcode) -> u8 {
|
#[repr(u8)]
|
||||||
match s {
|
|
||||||
MsgHeaderSubcode::ConnNotSynchronized => 1,
|
|
||||||
MsgHeaderSubcode::BadMessageLength => 2,
|
|
||||||
MsgHeaderSubcode::BadMessagType => 3,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Error, Serialize, Deserialize)]
|
|
||||||
pub enum OpenMsgSubcode {
|
pub enum OpenMsgSubcode {
|
||||||
#[error("Unsupported BGP version number")]
|
#[error("Unsupported BGP version number")]
|
||||||
UnsupportedVersion = 1,
|
UnsupportedVersion = 1,
|
||||||
@@ -119,34 +107,12 @@ pub enum OpenMsgSubcode {
|
|||||||
impl TryFrom<u8> for OpenMsgSubcode {
|
impl TryFrom<u8> for OpenMsgSubcode {
|
||||||
type Error = ();
|
type Error = ();
|
||||||
fn try_from(v: u8) -> Result<Self, ()> {
|
fn try_from(v: u8) -> Result<Self, ()> {
|
||||||
match v {
|
Self::from_repr(v).ok_or(())
|
||||||
1 => Ok(Self::UnsupportedVersion),
|
|
||||||
2 => Ok(Self::BadPeerAs),
|
|
||||||
3 => Ok(Self::BadBgpId),
|
|
||||||
4 => Ok(Self::UnsupportedOptionalParam),
|
|
||||||
6 => Ok(Self::UnacceptableHoldTime),
|
|
||||||
7 => Ok(Self::UnsupportedCapability),
|
|
||||||
8 => Ok(Self::RoleMismatch),
|
|
||||||
_ => Err(()),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&OpenMsgSubcode> for u8 {
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Error, Serialize, Deserialize, FromRepr)]
|
||||||
fn from(s: &OpenMsgSubcode) -> u8 {
|
#[repr(u8)]
|
||||||
match s {
|
|
||||||
OpenMsgSubcode::UnsupportedVersion => 1,
|
|
||||||
OpenMsgSubcode::BadPeerAs => 2,
|
|
||||||
OpenMsgSubcode::BadBgpId => 3,
|
|
||||||
OpenMsgSubcode::UnsupportedOptionalParam => 4,
|
|
||||||
OpenMsgSubcode::UnacceptableHoldTime => 6,
|
|
||||||
OpenMsgSubcode::UnsupportedCapability => 7,
|
|
||||||
OpenMsgSubcode::RoleMismatch => 8,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Error, Serialize, Deserialize)]
|
|
||||||
pub enum UpdateMsgSubcode {
|
pub enum UpdateMsgSubcode {
|
||||||
#[error("Malformed attribute list")]
|
#[error("Malformed attribute list")]
|
||||||
MalforedAttrs = 1,
|
MalforedAttrs = 1,
|
||||||
@@ -182,40 +148,12 @@ pub enum UpdateMsgSubcode {
|
|||||||
impl TryFrom<u8> for UpdateMsgSubcode {
|
impl TryFrom<u8> for UpdateMsgSubcode {
|
||||||
type Error = ();
|
type Error = ();
|
||||||
fn try_from(v: u8) -> Result<Self, ()> {
|
fn try_from(v: u8) -> Result<Self, ()> {
|
||||||
match v {
|
Self::from_repr(v).ok_or(())
|
||||||
1 => Ok(Self::MalforedAttrs),
|
|
||||||
2 => Ok(Self::UnrecognizedWellKnownAttr),
|
|
||||||
3 => Ok(Self::MissingWellKnown),
|
|
||||||
4 => Ok(Self::AttributeFlags),
|
|
||||||
5 => Ok(Self::AttributeLength),
|
|
||||||
6 => Ok(Self::InvalidOrigin),
|
|
||||||
8 => Ok(Self::InvalidNextHop),
|
|
||||||
9 => Ok(Self::OptionalAttribute),
|
|
||||||
10 => Ok(Self::InvalidNetworkField),
|
|
||||||
11 => Ok(Self::MalformedAsPath),
|
|
||||||
_ => Err(()),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&UpdateMsgSubcode> for u8 {
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Error, Serialize, Deserialize, FromRepr)]
|
||||||
fn from(s: &UpdateMsgSubcode) -> u8 {
|
#[repr(u8)]
|
||||||
match s {
|
|
||||||
UpdateMsgSubcode::MalforedAttrs => 1,
|
|
||||||
UpdateMsgSubcode::UnrecognizedWellKnownAttr => 2,
|
|
||||||
UpdateMsgSubcode::MissingWellKnown => 3,
|
|
||||||
UpdateMsgSubcode::AttributeFlags => 4,
|
|
||||||
UpdateMsgSubcode::AttributeLength => 5,
|
|
||||||
UpdateMsgSubcode::InvalidOrigin => 6,
|
|
||||||
UpdateMsgSubcode::InvalidNextHop => 8,
|
|
||||||
UpdateMsgSubcode::OptionalAttribute => 9,
|
|
||||||
UpdateMsgSubcode::InvalidNetworkField => 10,
|
|
||||||
UpdateMsgSubcode::MalformedAsPath => 11,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Error, Serialize, Deserialize)]
|
|
||||||
pub enum FsmSubcode {
|
pub enum FsmSubcode {
|
||||||
#[error("Received an unexpected message in OpenSent state")]
|
#[error("Received an unexpected message in OpenSent state")]
|
||||||
UnexpectedOpenSent = 1,
|
UnexpectedOpenSent = 1,
|
||||||
@@ -230,26 +168,12 @@ pub enum FsmSubcode {
|
|||||||
impl TryFrom<u8> for FsmSubcode {
|
impl TryFrom<u8> for FsmSubcode {
|
||||||
type Error = ();
|
type Error = ();
|
||||||
fn try_from(v: u8) -> Result<Self, ()> {
|
fn try_from(v: u8) -> Result<Self, ()> {
|
||||||
match v {
|
Self::from_repr(v).ok_or(())
|
||||||
1 => Ok(Self::UnexpectedOpenSent),
|
|
||||||
2 => Ok(Self::UnexpectedOpenConfirm),
|
|
||||||
3 => Ok(Self::UnexpectedEstablished),
|
|
||||||
_ => Err(()),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&FsmSubcode> for u8 {
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Error, Serialize, Deserialize, FromRepr)]
|
||||||
fn from(s: &FsmSubcode) -> u8 {
|
#[repr(u8)]
|
||||||
match s {
|
|
||||||
FsmSubcode::UnexpectedOpenSent => 1,
|
|
||||||
FsmSubcode::UnexpectedOpenConfirm => 2,
|
|
||||||
FsmSubcode::UnexpectedEstablished => 3,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Error, Serialize, Deserialize)]
|
|
||||||
pub enum CeaseSubcode {
|
pub enum CeaseSubcode {
|
||||||
#[error("Maximum number of prefixes reached")]
|
#[error("Maximum number of prefixes reached")]
|
||||||
MaxPrefixes = 1,
|
MaxPrefixes = 1,
|
||||||
@@ -285,40 +209,12 @@ pub enum CeaseSubcode {
|
|||||||
impl TryFrom<u8> for CeaseSubcode {
|
impl TryFrom<u8> for CeaseSubcode {
|
||||||
type Error = ();
|
type Error = ();
|
||||||
fn try_from(v: u8) -> Result<Self, ()> {
|
fn try_from(v: u8) -> Result<Self, ()> {
|
||||||
match v {
|
Self::from_repr(v).ok_or(())
|
||||||
1 => Ok(Self::MaxPrefixes),
|
|
||||||
2 => Ok(Self::AdminShutdown),
|
|
||||||
3 => Ok(Self::PeerDeconf),
|
|
||||||
4 => Ok(Self::AdminReset),
|
|
||||||
5 => Ok(Self::ConnRejected),
|
|
||||||
6 => Ok(Self::ConfChange),
|
|
||||||
7 => Ok(Self::ConnCollisionResolution),
|
|
||||||
8 => Ok(Self::OutOfResources),
|
|
||||||
9 => Ok(Self::HardReset),
|
|
||||||
10 => Ok(Self::BfdDown),
|
|
||||||
_ => Err(()),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&CeaseSubcode> for u8 {
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Error, Serialize, Deserialize, FromRepr)]
|
||||||
fn from(s: &CeaseSubcode) -> u8 {
|
#[repr(u8)]
|
||||||
match s {
|
|
||||||
CeaseSubcode::MaxPrefixes => 1,
|
|
||||||
CeaseSubcode::AdminShutdown => 2,
|
|
||||||
CeaseSubcode::PeerDeconf => 3,
|
|
||||||
CeaseSubcode::AdminReset => 4,
|
|
||||||
CeaseSubcode::ConnRejected => 5,
|
|
||||||
CeaseSubcode::ConfChange => 6,
|
|
||||||
CeaseSubcode::ConnCollisionResolution => 7,
|
|
||||||
CeaseSubcode::OutOfResources => 8,
|
|
||||||
CeaseSubcode::HardReset => 9,
|
|
||||||
CeaseSubcode::BfdDown => 10,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Error, Serialize, Deserialize)]
|
|
||||||
pub enum RouteRefreshSubcode {
|
pub enum RouteRefreshSubcode {
|
||||||
#[error("Invalid message length")]
|
#[error("Invalid message length")]
|
||||||
InvalidLength = 1,
|
InvalidLength = 1,
|
||||||
@@ -327,17 +223,6 @@ pub enum RouteRefreshSubcode {
|
|||||||
impl TryFrom<u8> for RouteRefreshSubcode {
|
impl TryFrom<u8> for RouteRefreshSubcode {
|
||||||
type Error = ();
|
type Error = ();
|
||||||
fn try_from(v: u8) -> Result<Self, ()> {
|
fn try_from(v: u8) -> Result<Self, ()> {
|
||||||
match v {
|
Self::from_repr(v).ok_or(())
|
||||||
1 => Ok(Self::InvalidLength),
|
|
||||||
_ => Err(()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&RouteRefreshSubcode> for u8 {
|
|
||||||
fn from(s: &RouteRefreshSubcode) -> u8 {
|
|
||||||
match s {
|
|
||||||
RouteRefreshSubcode::InvalidLength => 1,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -34,6 +34,14 @@ pub const AS_TRANS: u16 = 23456;
|
|||||||
/// Version number used in the BGP4 protocol.
|
/// Version number used in the BGP4 protocol.
|
||||||
pub const BGP4_VERSION: u8 = 4;
|
pub const BGP4_VERSION: u8 = 4;
|
||||||
|
|
||||||
|
/// Total length of the BGP message header (marker + length + type).
|
||||||
|
pub const BGP_HEADER_LEN: u16 = 19;
|
||||||
|
|
||||||
|
/// Maximum BGP message length per RFC 4271 §4.1.
|
||||||
|
pub const BGP_MAX_MSG_LEN: u16 = 4096;
|
||||||
|
|
||||||
|
const BGP_MARKER: [u8; 16] = [0xFF; 16];
|
||||||
|
|
||||||
/// Message represents the top-level messages in the BGP protocol.
|
/// Message represents the top-level messages in the BGP protocol.
|
||||||
#[derive(Debug, Serialize, Deserialize, EnumDiscriminants)]
|
#[derive(Debug, Serialize, Deserialize, EnumDiscriminants)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
@@ -46,7 +54,112 @@ pub enum Message {
|
|||||||
RouteRefresh = 5,
|
RouteRefresh = 5,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Message {}
|
impl Message {
|
||||||
|
/// Parses a complete BGP message including the 19-byte header from `buf`.
|
||||||
|
/// Returns the unparsed remainder and the decoded `Message`.
|
||||||
|
pub fn from_wire<'a>(
|
||||||
|
ctx: &ParserContext,
|
||||||
|
buf: &'a [u8],
|
||||||
|
) -> IResult<&'a [u8], Self, BgpParserError<&'a [u8]>> {
|
||||||
|
let (buf, marker) = nom::bytes::take(16usize).parse(buf)?;
|
||||||
|
if marker != BGP_MARKER {
|
||||||
|
return Err(Failure(BgpParserError::Eyre(eyre!(
|
||||||
|
"Invalid BGP marker: expected 16 x 0xFF"
|
||||||
|
))));
|
||||||
|
}
|
||||||
|
|
||||||
|
let (buf, length) = be_u16(buf)?;
|
||||||
|
if length < BGP_HEADER_LEN {
|
||||||
|
return Err(Failure(BgpParserError::Eyre(eyre!(
|
||||||
|
"BGP message length {length} is below minimum {BGP_HEADER_LEN}"
|
||||||
|
))));
|
||||||
|
}
|
||||||
|
if length > BGP_MAX_MSG_LEN {
|
||||||
|
return Err(Failure(BgpParserError::Eyre(eyre!(
|
||||||
|
"BGP message length {length} exceeds maximum {BGP_MAX_MSG_LEN}"
|
||||||
|
))));
|
||||||
|
}
|
||||||
|
|
||||||
|
let (buf, msg_type) = be_u8(buf)?;
|
||||||
|
let body_len = length - BGP_HEADER_LEN;
|
||||||
|
let (buf, body) = nom::bytes::take(body_len as usize).parse(buf)?;
|
||||||
|
|
||||||
|
let msg = match msg_type {
|
||||||
|
1 => {
|
||||||
|
let (_, open) = OpenMessage::from_wire(ctx, body)?;
|
||||||
|
Self::Open(open)
|
||||||
|
}
|
||||||
|
2 => {
|
||||||
|
let (_, update) = UpdateMessage::from_wire(ctx, body)?;
|
||||||
|
Self::Update(update)
|
||||||
|
}
|
||||||
|
3 => {
|
||||||
|
let (_, notif) = NotificationMessage::from_wire(ctx, body)?;
|
||||||
|
Self::Notification(notif)
|
||||||
|
}
|
||||||
|
4 => {
|
||||||
|
if !body.is_empty() {
|
||||||
|
return Err(Failure(BgpParserError::Eyre(eyre!(
|
||||||
|
"KEEPALIVE body must be empty, got {} bytes",
|
||||||
|
body.len()
|
||||||
|
))));
|
||||||
|
}
|
||||||
|
Self::KeepAlive
|
||||||
|
}
|
||||||
|
5 => Self::RouteRefresh,
|
||||||
|
t => {
|
||||||
|
return Err(Failure(BgpParserError::Eyre(eyre!(
|
||||||
|
"Unknown BGP message type {t}"
|
||||||
|
))));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((buf, msg))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Serializes the message (header + body) into `out`.
|
||||||
|
pub fn to_wire(&self, ctx: &ParserContext, out: &mut BytesMut) -> Result<()> {
|
||||||
|
// Serialize the body first so we can compute the total length.
|
||||||
|
let mut body = BytesMut::new();
|
||||||
|
match self {
|
||||||
|
Self::Open(m) => m.to_wire(ctx, &mut body)?,
|
||||||
|
Self::Update(m) => m.to_wire(ctx, &mut body)?,
|
||||||
|
Self::Notification(m) => m.to_wire(ctx, &mut body)?,
|
||||||
|
Self::KeepAlive | Self::RouteRefresh => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
let total_len = BGP_HEADER_LEN
|
||||||
|
.checked_add(body.len() as u16)
|
||||||
|
.filter(|&l| l <= BGP_MAX_MSG_LEN)
|
||||||
|
.ok_or_else(|| eyre!("BGP message body too large to serialize"))?;
|
||||||
|
|
||||||
|
out.put_slice(&BGP_MARKER);
|
||||||
|
out.put_u16(total_len);
|
||||||
|
out.put_u8(self.type_code());
|
||||||
|
out.put(body);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn wire_len(&self, ctx: &ParserContext) -> Result<u16> {
|
||||||
|
let body_len: u16 = match self {
|
||||||
|
Self::Open(m) => m.wire_len(ctx)?,
|
||||||
|
Self::Update(m) => m.wire_len(ctx)?,
|
||||||
|
Self::Notification(m) => m.wire_len(),
|
||||||
|
Self::KeepAlive | Self::RouteRefresh => 0,
|
||||||
|
};
|
||||||
|
Ok(BGP_HEADER_LEN + body_len)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn type_code(&self) -> u8 {
|
||||||
|
match self {
|
||||||
|
Self::Open(_) => 1,
|
||||||
|
Self::Update(_) => 2,
|
||||||
|
Self::Notification(_) => 3,
|
||||||
|
Self::KeepAlive => 4,
|
||||||
|
Self::RouteRefresh => 5,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Represents a BGP Update message.
|
/// Represents a BGP Update message.
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
@@ -1367,7 +1480,7 @@ mod tests {
|
|||||||
use crate::message::{
|
use crate::message::{
|
||||||
AggregatorPathAttribute, AsPathAttribute, AsPathSegment, AtomicAggregatePathAttribute,
|
AggregatorPathAttribute, AsPathAttribute, AsPathSegment, AtomicAggregatePathAttribute,
|
||||||
CommunitiesPathAttribute, ExtendedCommunitiesPathAttribute, ExtendedCommunity,
|
CommunitiesPathAttribute, ExtendedCommunitiesPathAttribute, ExtendedCommunity,
|
||||||
LargeCommunitiesPathAttribute, LargeCommunity, LocalPrefPathAttribute,
|
LargeCommunitiesPathAttribute, LargeCommunity, LocalPrefPathAttribute, Message,
|
||||||
MpReachNlriPathAttribute, MpUnreachNlriPathAttribute, MultiExitDiscPathAttribute,
|
MpReachNlriPathAttribute, MpUnreachNlriPathAttribute, MultiExitDiscPathAttribute,
|
||||||
NextHopPathAttribute, NlriNextHop, NotificationMessage, OriginPathAttribute, PathAttribute,
|
NextHopPathAttribute, NlriNextHop, NotificationMessage, OriginPathAttribute, PathAttribute,
|
||||||
PathAttributeFlags, UpdateMessage,
|
PathAttributeFlags, UpdateMessage,
|
||||||
@@ -1813,4 +1926,62 @@ mod tests {
|
|||||||
assert_eq!(parsed, msg);
|
assert_eq!(parsed, msg);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_message_keepalive_roundtrip() -> Result<()> {
|
||||||
|
let ctx = &default_v6_context();
|
||||||
|
let mut buf = BytesMut::new();
|
||||||
|
Message::KeepAlive.to_wire(ctx, &mut buf)?;
|
||||||
|
|
||||||
|
// 16-byte marker + 2-byte length (19) + 1-byte type (4)
|
||||||
|
assert_eq!(buf.len(), 19);
|
||||||
|
assert!(buf[..16].iter().all(|&b| b == 0xFF));
|
||||||
|
assert_eq!(&buf[16..19], &[0x00, 0x13, 0x04]);
|
||||||
|
|
||||||
|
let (rem, parsed) = Message::from_wire(ctx, &buf).unwrap();
|
||||||
|
assert!(rem.is_empty());
|
||||||
|
assert!(matches!(parsed, Message::KeepAlive));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_message_notification_roundtrip() -> Result<()> {
|
||||||
|
use crate::errors::{BgpError, CeaseSubcode};
|
||||||
|
|
||||||
|
let ctx = &default_v6_context();
|
||||||
|
let inner = NotificationMessage {
|
||||||
|
error: BgpError::Cease(CeaseSubcode::AdminShutdown),
|
||||||
|
data: vec![],
|
||||||
|
};
|
||||||
|
let msg = Message::Notification(inner);
|
||||||
|
|
||||||
|
let mut buf = BytesMut::new();
|
||||||
|
msg.to_wire(ctx, &mut buf)?;
|
||||||
|
assert_eq!(buf.len(), msg.wire_len(ctx)? as usize);
|
||||||
|
|
||||||
|
let (rem, parsed) = Message::from_wire(ctx, &buf).unwrap();
|
||||||
|
assert!(rem.is_empty());
|
||||||
|
assert!(matches!(parsed, Message::Notification(_)));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_message_rejects_bad_marker() {
|
||||||
|
let ctx = default_v6_context();
|
||||||
|
let mut buf = vec![0u8; 19];
|
||||||
|
buf[16] = 0x00;
|
||||||
|
buf[17] = 0x13;
|
||||||
|
buf[18] = 0x04;
|
||||||
|
assert!(Message::from_wire(&ctx, &buf).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_message_rejects_short_length() {
|
||||||
|
let ctx = default_v6_context();
|
||||||
|
let mut buf = vec![0xFF; 19];
|
||||||
|
buf[16] = 0x00;
|
||||||
|
buf[17] = 0x12; // length = 18, below minimum 19
|
||||||
|
buf[18] = 0x04;
|
||||||
|
assert!(Message::from_wire(&ctx, &buf).is_err());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user