1use bytes::Bytes;
7
8use crate::flow::OFPP_ANY;
9use crate::instruction::InstructionList;
10use crate::message::{Message, MessageType};
11use crate::{Match, Version};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15#[repr(u16)]
16pub enum MultipartType {
17 Desc = 0,
19 Flow = 1,
21 Aggregate = 2,
23 Table = 3,
25 PortStats = 4,
27 Queue = 5,
29 Group = 6,
31 GroupDesc = 7,
33 GroupFeatures = 8,
35 Meter = 9,
37 MeterConfig = 10,
39 MeterFeatures = 11,
41 TableFeatures = 12,
43 PortDesc = 13,
45 Experimenter = 0xffff,
47}
48
49impl TryFrom<u16> for MultipartType {
50 type Error = crate::Error;
51
52 fn try_from(v: u16) -> Result<Self, Self::Error> {
53 match v {
54 0 => Ok(Self::Desc),
55 1 => Ok(Self::Flow),
56 2 => Ok(Self::Aggregate),
57 3 => Ok(Self::Table),
58 4 => Ok(Self::PortStats),
59 5 => Ok(Self::Queue),
60 6 => Ok(Self::Group),
61 7 => Ok(Self::GroupDesc),
62 8 => Ok(Self::GroupFeatures),
63 9 => Ok(Self::Meter),
64 10 => Ok(Self::MeterConfig),
65 11 => Ok(Self::MeterFeatures),
66 12 => Ok(Self::TableFeatures),
67 13 => Ok(Self::PortDesc),
68 0xffff => Ok(Self::Experimenter),
69 _ => Err(crate::Error::Parse(format!(
70 "unknown multipart type: {v}"
71 ))),
72 }
73 }
74}
75
76pub mod multipart_flags {
78 pub const MORE: u16 = 1 << 0;
80}
81
82#[derive(Debug, Clone)]
92pub struct MultipartHeader {
93 pub mp_type: MultipartType,
95 pub flags: u16,
97}
98
99impl MultipartHeader {
100 pub const SIZE: usize = 8;
102
103 pub fn encode(&self) -> [u8; 8] {
105 let mut buf = [0u8; 8];
106 buf[0..2].copy_from_slice(&(self.mp_type as u16).to_be_bytes());
107 buf[2..4].copy_from_slice(&self.flags.to_be_bytes());
108 buf
110 }
111
112 pub fn decode(data: &[u8]) -> crate::Result<Self> {
114 if data.len() < Self::SIZE {
115 return Err(crate::Error::Parse("multipart header too short".into()));
116 }
117
118 let mp_type = u16::from_be_bytes([data[0], data[1]]);
119 let flags = u16::from_be_bytes([data[2], data[3]]);
120
121 Ok(Self {
122 mp_type: MultipartType::try_from(mp_type)?,
123 flags,
124 })
125 }
126
127 pub fn has_more(&self) -> bool {
129 self.flags & multipart_flags::MORE != 0
130 }
131}
132
133#[derive(Debug, Clone)]
155pub struct FlowStatsRequest {
156 pub table_id: u8,
158 pub out_port: u32,
160 pub out_group: u32,
162 pub cookie: u64,
164 pub cookie_mask: u64,
166 pub match_fields: Match,
168}
169
170impl Default for FlowStatsRequest {
171 fn default() -> Self {
172 Self::new()
173 }
174}
175
176impl FlowStatsRequest {
177 pub fn new() -> Self {
179 Self {
180 table_id: 0xff, out_port: OFPP_ANY,
182 out_group: OFPP_ANY, cookie: 0,
184 cookie_mask: 0, match_fields: Match::new(),
186 }
187 }
188
189 pub fn table(mut self, table_id: u8) -> Self {
191 self.table_id = table_id;
192 self
193 }
194
195 pub fn match_fields(mut self, m: Match) -> Self {
197 self.match_fields = m;
198 self
199 }
200
201 pub fn cookie(mut self, cookie: u64, mask: u64) -> Self {
203 self.cookie = cookie;
204 self.cookie_mask = mask;
205 self
206 }
207
208 pub fn encode(&self) -> Vec<u8> {
210 let match_bytes = self.match_fields.encode();
211 let mut buf = Vec::with_capacity(32 + match_bytes.len());
212
213 buf.push(self.table_id);
215 buf.extend([0u8; 3]);
216
217 buf.extend(self.out_port.to_be_bytes());
219
220 buf.extend(self.out_group.to_be_bytes());
222
223 buf.extend([0u8; 4]);
225
226 buf.extend(self.cookie.to_be_bytes());
228
229 buf.extend(self.cookie_mask.to_be_bytes());
231
232 buf.extend(match_bytes);
234
235 buf
236 }
237
238 pub fn to_message(&self, version: Version, xid: u32) -> Message {
240 let header = MultipartHeader {
241 mp_type: MultipartType::Flow,
242 flags: 0,
243 };
244
245 let mut body = Vec::new();
246 body.extend(header.encode());
247 body.extend(self.encode());
248
249 Message::new(version, MessageType::MultipartRequest, xid, Bytes::from(body))
250 }
251}
252
253#[derive(Debug, Clone)]
284pub struct FlowStatsEntry {
285 pub table_id: u8,
287 pub duration_sec: u32,
289 pub duration_nsec: u32,
291 pub priority: u16,
293 pub idle_timeout: u16,
295 pub hard_timeout: u16,
297 pub flags: u16,
299 pub cookie: u64,
301 pub packet_count: u64,
303 pub byte_count: u64,
305 pub match_fields: Match,
307 pub instructions: Vec<u8>,
309}
310
311impl FlowStatsEntry {
312 pub const FIXED_SIZE: usize = 48;
314
315 #[allow(clippy::similar_names)]
317 pub fn decode(data: &[u8]) -> crate::Result<(Self, usize)> {
318 if data.len() < Self::FIXED_SIZE {
319 return Err(crate::Error::Parse("flow stats entry too short".into()));
320 }
321
322 let length = u16::from_be_bytes([data[0], data[1]]) as usize;
323 if data.len() < length {
324 return Err(crate::Error::Parse("flow stats entry truncated".into()));
325 }
326
327 let table_id = data[2];
328 let duration_sec = u32::from_be_bytes([data[4], data[5], data[6], data[7]]);
330 let duration_nsec = u32::from_be_bytes([data[8], data[9], data[10], data[11]]);
331 let priority = u16::from_be_bytes([data[12], data[13]]);
332 let idle_timeout = u16::from_be_bytes([data[14], data[15]]);
333 let hard_timeout = u16::from_be_bytes([data[16], data[17]]);
334 let flags = u16::from_be_bytes([data[18], data[19]]);
335 let cookie = u64::from_be_bytes([
337 data[24], data[25], data[26], data[27],
338 data[28], data[29], data[30], data[31],
339 ]);
340 let packet_count = u64::from_be_bytes([
341 data[32], data[33], data[34], data[35],
342 data[36], data[37], data[38], data[39],
343 ]);
344 let byte_count = u64::from_be_bytes([
345 data[40], data[41], data[42], data[43],
346 data[44], data[45], data[46], data[47],
347 ]);
348
349 let match_data = &data[Self::FIXED_SIZE..];
351 let (match_fields, match_len) = Match::decode(match_data)?;
352
353 let instructions_start = Self::FIXED_SIZE + match_len;
355 let instructions = data[instructions_start..length].to_vec();
356
357 Ok((
358 Self {
359 table_id,
360 duration_sec,
361 duration_nsec,
362 priority,
363 idle_timeout,
364 hard_timeout,
365 flags,
366 cookie,
367 packet_count,
368 byte_count,
369 match_fields,
370 instructions,
371 },
372 length,
373 ))
374 }
375
376 pub fn decoded_instructions(&self) -> crate::Result<InstructionList> {
381 InstructionList::decode(&self.instructions)
382 }
383}
384
385pub fn parse_flow_stats_reply(body: &[u8]) -> crate::Result<(Vec<FlowStatsEntry>, bool)> {
387 if body.len() < MultipartHeader::SIZE {
388 return Err(crate::Error::Parse("multipart reply too short".into()));
389 }
390
391 let header = MultipartHeader::decode(body)?;
392 if header.mp_type != MultipartType::Flow {
393 return Err(crate::Error::Parse(format!(
394 "expected Flow multipart type, got {:?}",
395 header.mp_type
396 )));
397 }
398
399 let mut entries = Vec::new();
400 let mut offset = MultipartHeader::SIZE;
401
402 while offset < body.len() {
403 let remaining = &body[offset..];
404 if remaining.len() < 4 {
405 break; }
407
408 let (entry, consumed) = FlowStatsEntry::decode(remaining)?;
409 entries.push(entry);
410 offset += consumed;
411 }
412
413 Ok((entries, header.has_more()))
414}
415
416#[cfg(test)]
417mod tests {
418 use super::*;
419
420 #[test]
421 fn multipart_type_values() {
422 assert_eq!(MultipartType::Desc as u16, 0);
423 assert_eq!(MultipartType::Flow as u16, 1);
424 assert_eq!(MultipartType::Aggregate as u16, 2);
425 assert_eq!(MultipartType::Table as u16, 3);
426 assert_eq!(MultipartType::PortStats as u16, 4);
427 }
428
429 #[test]
430 fn multipart_header_encode() {
431 let header = MultipartHeader {
432 mp_type: MultipartType::Flow,
433 flags: 0,
434 };
435 let bytes = header.encode();
436 assert_eq!(bytes.len(), 8);
437 assert_eq!(bytes[0..2], [0x00, 0x01]); assert_eq!(bytes[2..4], [0x00, 0x00]); assert_eq!(bytes[4..8], [0x00, 0x00, 0x00, 0x00]); }
441
442 #[test]
443 fn multipart_header_decode() {
444 let data = [0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00];
445 let header = MultipartHeader::decode(&data).unwrap();
446 assert_eq!(header.mp_type, MultipartType::Flow);
447 assert!(header.has_more());
448 }
449
450 #[test]
451 fn flow_stats_request_default() {
452 let req = FlowStatsRequest::new();
453 assert_eq!(req.table_id, 0xff);
454 assert_eq!(req.out_port, OFPP_ANY);
455 assert_eq!(req.cookie, 0);
456 assert_eq!(req.cookie_mask, 0);
457 }
458
459 #[test]
460 fn flow_stats_request_encode() {
461 let req = FlowStatsRequest::new().table(0);
462 let bytes = req.encode();
463
464 assert!(bytes.len() >= 32);
466 assert_eq!(bytes[0], 0); }
468
469 #[test]
470 fn flow_stats_request_to_message() {
471 let req = FlowStatsRequest::new();
472 let msg = req.to_message(Version::Of13, 42);
473
474 assert_eq!(msg.header.msg_type, MessageType::MultipartRequest);
475 assert_eq!(msg.header.xid, 42);
476 assert!(msg.body.len() >= 40);
478 }
479
480 #[test]
481 fn flow_stats_entry_decode() {
482 let mut data = vec![0u8; 56];
484
485 data[0] = 0x00;
487 data[1] = 0x38;
488
489 data[2] = 0x00;
491
492 data[12] = 0x00;
494 data[13] = 0x64;
495
496 data[24] = 0x00;
498 data[25] = 0x00;
499 data[26] = 0x00;
500 data[27] = 0x00;
501 data[28] = 0x00;
502 data[29] = 0x00;
503 data[30] = 0x12;
504 data[31] = 0x34;
505
506 data[32..40].copy_from_slice(&1000u64.to_be_bytes());
508
509 data[40..48].copy_from_slice(&64000u64.to_be_bytes());
511
512 data[48] = 0x00;
514 data[49] = 0x01;
515 data[50] = 0x00;
516 data[51] = 0x04;
517 data[52..56].copy_from_slice(&[0u8; 4]);
519
520 let (entry, consumed) = FlowStatsEntry::decode(&data).unwrap();
521
522 assert_eq!(consumed, 56);
523 assert_eq!(entry.table_id, 0);
524 assert_eq!(entry.priority, 100);
525 assert_eq!(entry.cookie, 0x1234);
526 assert_eq!(entry.packet_count, 1000);
527 assert_eq!(entry.byte_count, 64000);
528 }
529}