use bytes::{Buf, Bytes};
use crate::match_fields::Match;
use crate::{Error, Result};
pub const OFP_NO_BUFFER: u32 = 0xffff_ffff;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum PacketInReason {
NoMatch = 0,
Action = 1,
InvalidTtl = 2,
ActionSet = 3,
Group = 4,
PacketOut = 5,
}
impl TryFrom<u8> for PacketInReason {
type Error = Error;
fn try_from(v: u8) -> Result<Self> {
match v {
0 => Ok(Self::NoMatch),
1 => Ok(Self::Action),
2 => Ok(Self::InvalidTtl),
3 => Ok(Self::ActionSet),
4 => Ok(Self::Group),
5 => Ok(Self::PacketOut),
_ => Err(Error::Parse(format!("unknown packet-in reason: {v}"))),
}
}
}
#[derive(Debug, Clone)]
pub struct PacketIn {
pub buffer_id: u32,
pub total_len: u16,
pub reason: PacketInReason,
pub table_id: u8,
pub cookie: u64,
pub match_fields: Match,
pub data: Vec<u8>,
}
impl PacketIn {
pub fn parse(mut buf: Bytes) -> Result<Self> {
if buf.remaining() < 20 {
return Err(Error::Parse("packet-in too short".into()));
}
let buffer_id = buf.get_u32();
let total_len = buf.get_u16();
let reason = PacketInReason::try_from(buf.get_u8())?;
let table_id = buf.get_u8();
let cookie = buf.get_u64();
let match_type = buf.get_u16();
let match_len = buf.get_u16();
if match_type != 1 {
return Err(Error::Parse(format!(
"unsupported match type: {match_type}",
)));
}
let oxm_len = match_len.saturating_sub(4) as usize;
if buf.remaining() < oxm_len {
return Err(Error::Parse("packet-in match truncated".into()));
}
let oxm_bytes = buf.copy_to_bytes(oxm_len);
let match_fields = Match::decode_oxm(&oxm_bytes)?;
let padded_match_len = (match_len as usize + 7) & !7;
let padding = padded_match_len - match_len as usize;
if buf.remaining() < padding {
return Err(Error::Parse("packet-in padding missing".into()));
}
buf.advance(padding);
if buf.remaining() < 2 {
return Err(Error::Parse("packet-in data padding missing".into()));
}
buf.advance(2);
let data = buf.to_vec();
Ok(Self {
buffer_id,
total_len,
reason,
table_id,
cookie,
match_fields,
data,
})
}
pub fn is_buffered(&self) -> bool {
self.buffer_id != OFP_NO_BUFFER
}
pub fn in_port(&self) -> u32 {
self.match_fields.in_port.unwrap_or(0)
}
pub fn buffer_id(&self) -> Option<u32> {
if self.buffer_id == OFP_NO_BUFFER {
None
} else {
Some(self.buffer_id)
}
}
pub fn data(&self) -> &[u8] {
&self.data
}
#[cfg(any(test, feature = "test-support"))]
pub fn new_for_test(buffer_id: u32, in_port: u32, data: Vec<u8>) -> Self {
Self {
buffer_id,
total_len: data.len() as u16,
reason: PacketInReason::Action,
table_id: 0,
cookie: 0,
match_fields: Match::new().in_port(in_port),
data,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_packet_in_reason() {
assert_eq!(PacketInReason::try_from(0).unwrap(), PacketInReason::NoMatch);
assert_eq!(PacketInReason::try_from(1).unwrap(), PacketInReason::Action);
assert!(PacketInReason::try_from(99).is_err());
}
}