use bytes::Bytes;
use crate::flow::OFPP_ANY;
use crate::instruction::InstructionList;
use crate::message::{Message, MessageType};
use crate::{Match, Version};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u16)]
pub enum MultipartType {
Desc = 0,
Flow = 1,
Aggregate = 2,
Table = 3,
PortStats = 4,
Queue = 5,
Group = 6,
GroupDesc = 7,
GroupFeatures = 8,
Meter = 9,
MeterConfig = 10,
MeterFeatures = 11,
TableFeatures = 12,
PortDesc = 13,
Experimenter = 0xffff,
}
impl TryFrom<u16> for MultipartType {
type Error = crate::Error;
fn try_from(v: u16) -> Result<Self, Self::Error> {
match v {
0 => Ok(Self::Desc),
1 => Ok(Self::Flow),
2 => Ok(Self::Aggregate),
3 => Ok(Self::Table),
4 => Ok(Self::PortStats),
5 => Ok(Self::Queue),
6 => Ok(Self::Group),
7 => Ok(Self::GroupDesc),
8 => Ok(Self::GroupFeatures),
9 => Ok(Self::Meter),
10 => Ok(Self::MeterConfig),
11 => Ok(Self::MeterFeatures),
12 => Ok(Self::TableFeatures),
13 => Ok(Self::PortDesc),
0xffff => Ok(Self::Experimenter),
_ => Err(crate::Error::Parse(format!(
"unknown multipart type: {v}"
))),
}
}
}
pub mod multipart_flags {
pub const MORE: u16 = 1 << 0;
}
#[derive(Debug, Clone)]
pub struct MultipartHeader {
pub mp_type: MultipartType,
pub flags: u16,
}
impl MultipartHeader {
pub const SIZE: usize = 8;
pub fn encode(&self) -> [u8; 8] {
let mut buf = [0u8; 8];
buf[0..2].copy_from_slice(&(self.mp_type as u16).to_be_bytes());
buf[2..4].copy_from_slice(&self.flags.to_be_bytes());
buf
}
pub fn decode(data: &[u8]) -> crate::Result<Self> {
if data.len() < Self::SIZE {
return Err(crate::Error::Parse("multipart header too short".into()));
}
let mp_type = u16::from_be_bytes([data[0], data[1]]);
let flags = u16::from_be_bytes([data[2], data[3]]);
Ok(Self {
mp_type: MultipartType::try_from(mp_type)?,
flags,
})
}
pub fn has_more(&self) -> bool {
self.flags & multipart_flags::MORE != 0
}
}
#[derive(Debug, Clone)]
pub struct FlowStatsRequest {
pub table_id: u8,
pub out_port: u32,
pub out_group: u32,
pub cookie: u64,
pub cookie_mask: u64,
pub match_fields: Match,
}
impl Default for FlowStatsRequest {
fn default() -> Self {
Self::new()
}
}
impl FlowStatsRequest {
pub fn new() -> Self {
Self {
table_id: 0xff, out_port: OFPP_ANY,
out_group: OFPP_ANY, cookie: 0,
cookie_mask: 0, match_fields: Match::new(),
}
}
pub fn table(mut self, table_id: u8) -> Self {
self.table_id = table_id;
self
}
pub fn match_fields(mut self, m: Match) -> Self {
self.match_fields = m;
self
}
pub fn cookie(mut self, cookie: u64, mask: u64) -> Self {
self.cookie = cookie;
self.cookie_mask = mask;
self
}
pub fn encode(&self) -> Vec<u8> {
let match_bytes = self.match_fields.encode();
let mut buf = Vec::with_capacity(32 + match_bytes.len());
buf.push(self.table_id);
buf.extend([0u8; 3]);
buf.extend(self.out_port.to_be_bytes());
buf.extend(self.out_group.to_be_bytes());
buf.extend([0u8; 4]);
buf.extend(self.cookie.to_be_bytes());
buf.extend(self.cookie_mask.to_be_bytes());
buf.extend(match_bytes);
buf
}
pub fn to_message(&self, version: Version, xid: u32) -> Message {
let header = MultipartHeader {
mp_type: MultipartType::Flow,
flags: 0,
};
let mut body = Vec::new();
body.extend(header.encode());
body.extend(self.encode());
Message::new(version, MessageType::MultipartRequest, xid, Bytes::from(body))
}
}
#[derive(Debug, Clone)]
pub struct FlowStatsEntry {
pub table_id: u8,
pub duration_sec: u32,
pub duration_nsec: u32,
pub priority: u16,
pub idle_timeout: u16,
pub hard_timeout: u16,
pub flags: u16,
pub cookie: u64,
pub packet_count: u64,
pub byte_count: u64,
pub match_fields: Match,
pub instructions: Vec<u8>,
}
impl FlowStatsEntry {
pub const FIXED_SIZE: usize = 48;
#[allow(clippy::similar_names)]
pub fn decode(data: &[u8]) -> crate::Result<(Self, usize)> {
if data.len() < Self::FIXED_SIZE {
return Err(crate::Error::Parse("flow stats entry too short".into()));
}
let length = u16::from_be_bytes([data[0], data[1]]) as usize;
if data.len() < length {
return Err(crate::Error::Parse("flow stats entry truncated".into()));
}
let table_id = data[2];
let duration_sec = u32::from_be_bytes([data[4], data[5], data[6], data[7]]);
let duration_nsec = u32::from_be_bytes([data[8], data[9], data[10], data[11]]);
let priority = u16::from_be_bytes([data[12], data[13]]);
let idle_timeout = u16::from_be_bytes([data[14], data[15]]);
let hard_timeout = u16::from_be_bytes([data[16], data[17]]);
let flags = u16::from_be_bytes([data[18], data[19]]);
let cookie = u64::from_be_bytes([
data[24], data[25], data[26], data[27],
data[28], data[29], data[30], data[31],
]);
let packet_count = u64::from_be_bytes([
data[32], data[33], data[34], data[35],
data[36], data[37], data[38], data[39],
]);
let byte_count = u64::from_be_bytes([
data[40], data[41], data[42], data[43],
data[44], data[45], data[46], data[47],
]);
let match_data = &data[Self::FIXED_SIZE..];
let (match_fields, match_len) = Match::decode(match_data)?;
let instructions_start = Self::FIXED_SIZE + match_len;
let instructions = data[instructions_start..length].to_vec();
Ok((
Self {
table_id,
duration_sec,
duration_nsec,
priority,
idle_timeout,
hard_timeout,
flags,
cookie,
packet_count,
byte_count,
match_fields,
instructions,
},
length,
))
}
pub fn decoded_instructions(&self) -> crate::Result<InstructionList> {
InstructionList::decode(&self.instructions)
}
}
pub fn parse_flow_stats_reply(body: &[u8]) -> crate::Result<(Vec<FlowStatsEntry>, bool)> {
if body.len() < MultipartHeader::SIZE {
return Err(crate::Error::Parse("multipart reply too short".into()));
}
let header = MultipartHeader::decode(body)?;
if header.mp_type != MultipartType::Flow {
return Err(crate::Error::Parse(format!(
"expected Flow multipart type, got {:?}",
header.mp_type
)));
}
let mut entries = Vec::new();
let mut offset = MultipartHeader::SIZE;
while offset < body.len() {
let remaining = &body[offset..];
if remaining.len() < 4 {
break; }
let (entry, consumed) = FlowStatsEntry::decode(remaining)?;
entries.push(entry);
offset += consumed;
}
Ok((entries, header.has_more()))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn multipart_type_values() {
assert_eq!(MultipartType::Desc as u16, 0);
assert_eq!(MultipartType::Flow as u16, 1);
assert_eq!(MultipartType::Aggregate as u16, 2);
assert_eq!(MultipartType::Table as u16, 3);
assert_eq!(MultipartType::PortStats as u16, 4);
}
#[test]
fn multipart_header_encode() {
let header = MultipartHeader {
mp_type: MultipartType::Flow,
flags: 0,
};
let bytes = header.encode();
assert_eq!(bytes.len(), 8);
assert_eq!(bytes[0..2], [0x00, 0x01]); assert_eq!(bytes[2..4], [0x00, 0x00]); assert_eq!(bytes[4..8], [0x00, 0x00, 0x00, 0x00]); }
#[test]
fn multipart_header_decode() {
let data = [0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00];
let header = MultipartHeader::decode(&data).unwrap();
assert_eq!(header.mp_type, MultipartType::Flow);
assert!(header.has_more());
}
#[test]
fn flow_stats_request_default() {
let req = FlowStatsRequest::new();
assert_eq!(req.table_id, 0xff);
assert_eq!(req.out_port, OFPP_ANY);
assert_eq!(req.cookie, 0);
assert_eq!(req.cookie_mask, 0);
}
#[test]
fn flow_stats_request_encode() {
let req = FlowStatsRequest::new().table(0);
let bytes = req.encode();
assert!(bytes.len() >= 32);
assert_eq!(bytes[0], 0); }
#[test]
fn flow_stats_request_to_message() {
let req = FlowStatsRequest::new();
let msg = req.to_message(Version::Of13, 42);
assert_eq!(msg.header.msg_type, MessageType::MultipartRequest);
assert_eq!(msg.header.xid, 42);
assert!(msg.body.len() >= 40);
}
#[test]
fn flow_stats_entry_decode() {
let mut data = vec![0u8; 56];
data[0] = 0x00;
data[1] = 0x38;
data[2] = 0x00;
data[12] = 0x00;
data[13] = 0x64;
data[24] = 0x00;
data[25] = 0x00;
data[26] = 0x00;
data[27] = 0x00;
data[28] = 0x00;
data[29] = 0x00;
data[30] = 0x12;
data[31] = 0x34;
data[32..40].copy_from_slice(&1000u64.to_be_bytes());
data[40..48].copy_from_slice(&64000u64.to_be_bytes());
data[48] = 0x00;
data[49] = 0x01;
data[50] = 0x00;
data[51] = 0x04;
data[52..56].copy_from_slice(&[0u8; 4]);
let (entry, consumed) = FlowStatsEntry::decode(&data).unwrap();
assert_eq!(consumed, 56);
assert_eq!(entry.table_id, 0);
assert_eq!(entry.priority, 100);
assert_eq!(entry.cookie, 0x1234);
assert_eq!(entry.packet_count, 1000);
assert_eq!(entry.byte_count, 64000);
}
}