Skip to main content

rovs_openflow/
instruction.rs

1//! OpenFlow instruction encoding (OF 1.3+).
2//!
3//! Instructions are the top-level processing directives in OpenFlow 1.1+.
4//! They wrap actions and control table pipeline behavior.
5
6use std::fmt;
7
8use crate::action::ActionList;
9
10/// OpenFlow instruction type wire values (OF 1.3+).
11#[allow(dead_code)]
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13#[repr(u16)]
14pub enum InstructionType {
15    /// Go to specified table
16    GotoTable = 1,
17    /// Write metadata value
18    WriteMetadata = 2,
19    /// Write actions to action set
20    WriteActions = 3,
21    /// Apply actions immediately
22    ApplyActions = 4,
23    /// Clear action set
24    ClearActions = 5,
25    /// Apply meter
26    Meter = 6,
27}
28
29impl TryFrom<u16> for InstructionType {
30    type Error = crate::Error;
31
32    fn try_from(v: u16) -> Result<Self, Self::Error> {
33        match v {
34            1 => Ok(Self::GotoTable),
35            2 => Ok(Self::WriteMetadata),
36            3 => Ok(Self::WriteActions),
37            4 => Ok(Self::ApplyActions),
38            5 => Ok(Self::ClearActions),
39            6 => Ok(Self::Meter),
40            _ => Err(crate::Error::Parse(format!(
41                "unknown instruction type: {v}"
42            ))),
43        }
44    }
45}
46
47/// An OpenFlow instruction.
48#[derive(Debug, Clone)]
49pub enum Instruction {
50    /// Go to another table in the pipeline.
51    GotoTable(u8),
52
53    /// Write metadata value with optional mask.
54    WriteMetadata { metadata: u64, mask: u64 },
55
56    /// Apply actions immediately to the packet.
57    ApplyActions(ActionList),
58
59    /// Write actions to the action set (executed at end of pipeline).
60    WriteActions(ActionList),
61
62    /// Clear all actions in the action set.
63    ClearActions,
64
65    /// Apply a meter to the packet.
66    Meter(u32),
67}
68
69impl fmt::Display for Instruction {
70    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71        match self {
72            Self::GotoTable(table) => write!(f, "goto_table:{table}"),
73            Self::WriteMetadata { metadata, mask } => {
74                write!(f, "write_metadata:0x{metadata:x}/0x{mask:x}")
75            }
76            Self::ApplyActions(actions) => write!(f, "apply_actions({actions})"),
77            Self::WriteActions(actions) => write!(f, "write_actions({actions})"),
78            Self::ClearActions => write!(f, "clear_actions"),
79            Self::Meter(id) => write!(f, "meter:{id}"),
80        }
81    }
82}
83
84impl fmt::Display for InstructionList {
85    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86        // Common case: a single ApplyActions instruction unwraps to just the actions
87        if self.instructions.len() == 1 {
88            if let Instruction::ApplyActions(actions) = &self.instructions[0] {
89                return write!(f, "{actions}");
90            }
91        }
92        if self.instructions.is_empty() {
93            return write!(f, "drop");
94        }
95        for (i, inst) in self.instructions.iter().enumerate() {
96            if i > 0 {
97                write!(f, ",")?;
98            }
99            write!(f, "{inst}")?;
100        }
101        Ok(())
102    }
103}
104
105impl Instruction {
106    /// Encode instruction to OpenFlow wire format.
107    pub fn encode(&self) -> Vec<u8> {
108        match self {
109            Self::GotoTable(table_id) => encode_goto_table(*table_id),
110            Self::WriteMetadata { metadata, mask } => encode_write_metadata(*metadata, *mask),
111            Self::ApplyActions(actions) => encode_apply_actions(actions),
112            Self::WriteActions(actions) => encode_write_actions(actions),
113            Self::ClearActions => encode_clear_actions(),
114            Self::Meter(meter_id) => encode_meter(*meter_id),
115        }
116    }
117
118    /// Decode instruction from OpenFlow wire format.
119    ///
120    /// Returns the decoded instruction and the number of bytes consumed.
121    pub fn decode(data: &[u8]) -> crate::Result<(Self, usize)> {
122        if data.len() < 4 {
123            return Err(crate::Error::Parse("instruction too short".into()));
124        }
125
126        let inst_type = u16::from_be_bytes([data[0], data[1]]);
127        let length = u16::from_be_bytes([data[2], data[3]]) as usize;
128
129        if data.len() < length {
130            return Err(crate::Error::Parse("instruction truncated".into()));
131        }
132
133        let inst_type = InstructionType::try_from(inst_type)?;
134
135        let instruction = match inst_type {
136            InstructionType::GotoTable => {
137                if length < 8 {
138                    return Err(crate::Error::Parse("goto_table instruction too short".into()));
139                }
140                let table_id = data[4];
141                Self::GotoTable(table_id)
142            }
143            InstructionType::WriteMetadata => {
144                if length < 24 {
145                    return Err(crate::Error::Parse(
146                        "write_metadata instruction too short".into(),
147                    ));
148                }
149                // data[4..8] is padding
150                let metadata = u64::from_be_bytes([
151                    data[8], data[9], data[10], data[11], data[12], data[13], data[14], data[15],
152                ]);
153                let mask = u64::from_be_bytes([
154                    data[16], data[17], data[18], data[19], data[20], data[21], data[22], data[23],
155                ]);
156                Self::WriteMetadata { metadata, mask }
157            }
158            InstructionType::ApplyActions => {
159                // data[4..8] is padding, actions start at offset 8
160                let actions_data = &data[8..length];
161                let actions = ActionList::decode(actions_data)?;
162                Self::ApplyActions(actions)
163            }
164            InstructionType::WriteActions => {
165                // data[4..8] is padding, actions start at offset 8
166                let actions_data = &data[8..length];
167                let actions = ActionList::decode(actions_data)?;
168                Self::WriteActions(actions)
169            }
170            InstructionType::ClearActions => Self::ClearActions,
171            InstructionType::Meter => {
172                if length < 8 {
173                    return Err(crate::Error::Parse("meter instruction too short".into()));
174                }
175                let meter_id = u32::from_be_bytes([data[4], data[5], data[6], data[7]]);
176                Self::Meter(meter_id)
177            }
178        };
179
180        Ok((instruction, length))
181    }
182}
183
184/// A list of instructions.
185#[derive(Debug, Clone, Default)]
186pub struct InstructionList {
187    instructions: Vec<Instruction>,
188}
189
190impl InstructionList {
191    /// Create a new empty instruction list.
192    pub fn new() -> Self {
193        Self::default()
194    }
195
196    /// Add an instruction to the list.
197    pub fn push(&mut self, instruction: Instruction) {
198        self.instructions.push(instruction);
199    }
200
201    /// Go to another table.
202    pub fn goto_table(mut self, table_id: u8) -> Self {
203        self.instructions.push(Instruction::GotoTable(table_id));
204        self
205    }
206
207    /// Write metadata with mask.
208    pub fn write_metadata(mut self, metadata: u64, mask: u64) -> Self {
209        self.instructions
210            .push(Instruction::WriteMetadata { metadata, mask });
211        self
212    }
213
214    /// Apply actions immediately.
215    pub fn apply_actions(mut self, actions: ActionList) -> Self {
216        self.instructions.push(Instruction::ApplyActions(actions));
217        self
218    }
219
220    /// Write actions to action set.
221    pub fn write_actions(mut self, actions: ActionList) -> Self {
222        self.instructions.push(Instruction::WriteActions(actions));
223        self
224    }
225
226    /// Clear action set.
227    pub fn clear_actions(mut self) -> Self {
228        self.instructions.push(Instruction::ClearActions);
229        self
230    }
231
232    /// Apply meter.
233    pub fn meter(mut self, meter_id: u32) -> Self {
234        self.instructions.push(Instruction::Meter(meter_id));
235        self
236    }
237
238    /// Get the instructions.
239    pub fn instructions(&self) -> &[Instruction] {
240        &self.instructions
241    }
242
243    /// Check if the list is empty.
244    pub fn is_empty(&self) -> bool {
245        self.instructions.is_empty()
246    }
247
248    /// Get the number of instructions.
249    pub fn len(&self) -> usize {
250        self.instructions.len()
251    }
252
253    /// Encode all instructions to wire format.
254    pub fn encode(&self) -> Vec<u8> {
255        let mut buf = Vec::new();
256        for instruction in &self.instructions {
257            buf.extend(instruction.encode());
258        }
259        buf
260    }
261
262    /// Decode all instructions from wire format.
263    ///
264    /// Reads instructions until the data is exhausted.
265    pub fn decode(data: &[u8]) -> crate::Result<Self> {
266        let mut instructions = Vec::new();
267        let mut offset = 0;
268
269        while offset < data.len() {
270            // Need at least 4 bytes for instruction header
271            if data.len() - offset < 4 {
272                break;
273            }
274
275            // Check for zero-length padding at end
276            let length = u16::from_be_bytes([data[offset + 2], data[offset + 3]]) as usize;
277            if length == 0 {
278                break;
279            }
280
281            let (instruction, consumed) = Instruction::decode(&data[offset..])?;
282            instructions.push(instruction);
283            offset += consumed;
284        }
285
286        Ok(Self { instructions })
287    }
288}
289
290// ============================================================================
291// Instruction Encoding Functions
292// ============================================================================
293
294/// Encode GotoTable instruction (8 bytes).
295///
296/// ```text
297/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
298/// |         type (1)            |          length (8)             |
299/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
300/// |  table_id   |                    pad (zeros)                  |
301/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
302/// ```
303fn encode_goto_table(table_id: u8) -> Vec<u8> {
304    let mut buf = Vec::with_capacity(8);
305    buf.extend((InstructionType::GotoTable as u16).to_be_bytes());
306    buf.extend(8u16.to_be_bytes()); // length
307    buf.push(table_id);
308    buf.extend([0u8; 3]); // padding
309    buf
310}
311
312/// Encode WriteMetadata instruction (24 bytes).
313///
314/// ```text
315/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
316/// |         type (2)            |          length (24)            |
317/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
318/// |                           pad (zeros)                         |
319/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
320/// |                           metadata                            |
321/// |                                                               |
322/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
323/// |                         metadata_mask                         |
324/// |                                                               |
325/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
326/// ```
327fn encode_write_metadata(metadata: u64, mask: u64) -> Vec<u8> {
328    let mut buf = Vec::with_capacity(24);
329    buf.extend((InstructionType::WriteMetadata as u16).to_be_bytes());
330    buf.extend(24u16.to_be_bytes()); // length
331    buf.extend([0u8; 4]); // padding
332    buf.extend(metadata.to_be_bytes());
333    buf.extend(mask.to_be_bytes());
334    buf
335}
336
337/// Encode ApplyActions instruction (variable size).
338///
339/// ```text
340/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
341/// |         type (4)            |          length                 |
342/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
343/// |                           pad (zeros)                         |
344/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
345/// |                       actions (variable)                      |
346/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
347/// ```
348fn encode_apply_actions(actions: &ActionList) -> Vec<u8> {
349    let actions_bytes = actions.encode();
350    let len = 8 + actions_bytes.len(); // header (4) + pad (4) + actions
351
352    let mut buf = Vec::with_capacity(len);
353    buf.extend((InstructionType::ApplyActions as u16).to_be_bytes());
354    buf.extend((len as u16).to_be_bytes());
355    buf.extend([0u8; 4]); // padding
356    buf.extend(actions_bytes);
357    buf
358}
359
360/// Encode WriteActions instruction (variable size).
361///
362/// Same format as ApplyActions but with type=3.
363fn encode_write_actions(actions: &ActionList) -> Vec<u8> {
364    let actions_bytes = actions.encode();
365    let len = 8 + actions_bytes.len();
366
367    let mut buf = Vec::with_capacity(len);
368    buf.extend((InstructionType::WriteActions as u16).to_be_bytes());
369    buf.extend((len as u16).to_be_bytes());
370    buf.extend([0u8; 4]); // padding
371    buf.extend(actions_bytes);
372    buf
373}
374
375/// Encode ClearActions instruction (8 bytes).
376///
377/// ```text
378/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
379/// |         type (5)            |          length (8)             |
380/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
381/// |                           pad (zeros)                         |
382/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
383/// ```
384fn encode_clear_actions() -> Vec<u8> {
385    let mut buf = Vec::with_capacity(8);
386    buf.extend((InstructionType::ClearActions as u16).to_be_bytes());
387    buf.extend(8u16.to_be_bytes()); // length
388    buf.extend([0u8; 4]); // padding
389    buf
390}
391
392/// Encode Meter instruction (8 bytes).
393///
394/// ```text
395/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
396/// |         type (6)            |          length (8)             |
397/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
398/// |                          meter_id                             |
399/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
400/// ```
401fn encode_meter(meter_id: u32) -> Vec<u8> {
402    let mut buf = Vec::with_capacity(8);
403    buf.extend((InstructionType::Meter as u16).to_be_bytes());
404    buf.extend(8u16.to_be_bytes()); // length
405    buf.extend(meter_id.to_be_bytes());
406    buf
407}
408
409// ============================================================================
410// Tests
411// ============================================================================
412
413#[cfg(test)]
414mod tests {
415    use super::*;
416    use crate::action::OutputPort;
417
418    #[test]
419    fn instruction_type_wire_values() {
420        assert_eq!(InstructionType::GotoTable as u16, 1);
421        assert_eq!(InstructionType::WriteMetadata as u16, 2);
422        assert_eq!(InstructionType::WriteActions as u16, 3);
423        assert_eq!(InstructionType::ApplyActions as u16, 4);
424        assert_eq!(InstructionType::ClearActions as u16, 5);
425        assert_eq!(InstructionType::Meter as u16, 6);
426    }
427
428    #[test]
429    fn encode_goto_table_instruction() {
430        let bytes = encode_goto_table(5);
431        assert_eq!(bytes.len(), 8);
432        // type = 1 (GotoTable)
433        assert_eq!(&bytes[0..2], &[0x00, 0x01]);
434        // length = 8
435        assert_eq!(&bytes[2..4], &[0x00, 0x08]);
436        // table_id = 5
437        assert_eq!(bytes[4], 5);
438        // padding
439        assert_eq!(&bytes[5..8], &[0, 0, 0]);
440    }
441
442    #[test]
443    fn encode_write_metadata_instruction() {
444        let bytes = encode_write_metadata(0x1234_5678_9abc_def0, 0xffff_ffff_ffff_ffff);
445        assert_eq!(bytes.len(), 24);
446        // type = 2 (WriteMetadata)
447        assert_eq!(&bytes[0..2], &[0x00, 0x02]);
448        // length = 24
449        assert_eq!(&bytes[2..4], &[0x00, 0x18]);
450        // padding
451        assert_eq!(&bytes[4..8], &[0, 0, 0, 0]);
452        // metadata
453        assert_eq!(
454            &bytes[8..16],
455            &[0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0]
456        );
457        // mask
458        assert_eq!(
459            &bytes[16..24],
460            &[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]
461        );
462    }
463
464    #[test]
465    fn encode_apply_actions_instruction() {
466        let actions = ActionList::new().output(OutputPort::Port(1));
467        let bytes = encode_apply_actions(&actions);
468        // Header (4) + pad (4) + Output action (16) = 24
469        assert_eq!(bytes.len(), 24);
470        // type = 4 (ApplyActions)
471        assert_eq!(&bytes[0..2], &[0x00, 0x04]);
472        // length = 24
473        assert_eq!(&bytes[2..4], &[0x00, 0x18]);
474        // padding
475        assert_eq!(&bytes[4..8], &[0, 0, 0, 0]);
476        // First action should be Output (type=0)
477        assert_eq!(&bytes[8..10], &[0x00, 0x00]);
478    }
479
480    #[test]
481    fn encode_apply_actions_empty() {
482        let actions = ActionList::new();
483        let bytes = encode_apply_actions(&actions);
484        // Header (4) + pad (4) = 8
485        assert_eq!(bytes.len(), 8);
486        // type = 4
487        assert_eq!(&bytes[0..2], &[0x00, 0x04]);
488        // length = 8
489        assert_eq!(&bytes[2..4], &[0x00, 0x08]);
490    }
491
492    #[test]
493    fn encode_write_actions_instruction() {
494        let actions = ActionList::new().output(OutputPort::Port(2));
495        let bytes = encode_write_actions(&actions);
496        // Header (4) + pad (4) + Output action (16) = 24
497        assert_eq!(bytes.len(), 24);
498        // type = 3 (WriteActions)
499        assert_eq!(&bytes[0..2], &[0x00, 0x03]);
500        // length = 24
501        assert_eq!(&bytes[2..4], &[0x00, 0x18]);
502    }
503
504    #[test]
505    fn encode_clear_actions_instruction() {
506        let bytes = encode_clear_actions();
507        assert_eq!(bytes.len(), 8);
508        // type = 5 (ClearActions)
509        assert_eq!(&bytes[0..2], &[0x00, 0x05]);
510        // length = 8
511        assert_eq!(&bytes[2..4], &[0x00, 0x08]);
512        // padding
513        assert_eq!(&bytes[4..8], &[0, 0, 0, 0]);
514    }
515
516    #[test]
517    fn encode_meter_instruction() {
518        let bytes = encode_meter(100);
519        assert_eq!(bytes.len(), 8);
520        // type = 6 (Meter)
521        assert_eq!(&bytes[0..2], &[0x00, 0x06]);
522        // length = 8
523        assert_eq!(&bytes[2..4], &[0x00, 0x08]);
524        // meter_id = 100
525        assert_eq!(&bytes[4..8], &[0x00, 0x00, 0x00, 0x64]);
526    }
527
528    #[test]
529    fn instruction_goto_table_encode() {
530        let inst = Instruction::GotoTable(10);
531        let bytes = inst.encode();
532        assert_eq!(bytes.len(), 8);
533        assert_eq!(bytes[4], 10);
534    }
535
536    #[test]
537    fn instruction_write_metadata_encode() {
538        let inst = Instruction::WriteMetadata {
539            metadata: 0x1234,
540            mask: 0xffff,
541        };
542        let bytes = inst.encode();
543        assert_eq!(bytes.len(), 24);
544    }
545
546    #[test]
547    fn instruction_apply_actions_encode() {
548        let actions = ActionList::new().pop_vlan().output(OutputPort::Port(1));
549        let inst = Instruction::ApplyActions(actions);
550        let bytes = inst.encode();
551        // Header (4) + pad (4) + PopVlan (8) + Output (16) = 32
552        assert_eq!(bytes.len(), 32);
553    }
554
555    #[test]
556    fn instruction_clear_actions_encode() {
557        let inst = Instruction::ClearActions;
558        let bytes = inst.encode();
559        assert_eq!(bytes.len(), 8);
560        assert_eq!(&bytes[0..2], &[0x00, 0x05]);
561    }
562
563    #[test]
564    fn instruction_meter_encode() {
565        let inst = Instruction::Meter(42);
566        let bytes = inst.encode();
567        assert_eq!(bytes.len(), 8);
568        assert_eq!(&bytes[4..8], &[0x00, 0x00, 0x00, 0x2a]);
569    }
570
571    #[test]
572    fn instruction_list_encode_empty() {
573        let list = InstructionList::new();
574        let bytes = list.encode();
575        assert!(bytes.is_empty());
576    }
577
578    #[test]
579    fn instruction_list_encode_single() {
580        let list = InstructionList::new().goto_table(5);
581        let bytes = list.encode();
582        assert_eq!(bytes.len(), 8);
583        assert_eq!(&bytes[0..2], &[0x00, 0x01]);
584        assert_eq!(bytes[4], 5);
585    }
586
587    #[test]
588    fn instruction_list_encode_multiple() {
589        let actions = ActionList::new().output(OutputPort::Port(1));
590        let list = InstructionList::new()
591            .apply_actions(actions)
592            .goto_table(10);
593        let bytes = list.encode();
594        // ApplyActions (24) + GotoTable (8) = 32
595        assert_eq!(bytes.len(), 32);
596        // First instruction: ApplyActions (type=4)
597        assert_eq!(&bytes[0..2], &[0x00, 0x04]);
598        // Second instruction: GotoTable (type=1)
599        assert_eq!(&bytes[24..26], &[0x00, 0x01]);
600    }
601
602    #[test]
603    fn instruction_list_builder_methods() {
604        let actions = ActionList::new().output(OutputPort::Port(1));
605        let list = InstructionList::new()
606            .write_metadata(0x1234, 0xffff)
607            .apply_actions(actions)
608            .clear_actions()
609            .meter(100)
610            .goto_table(5);
611        assert_eq!(list.len(), 5);
612        assert!(!list.is_empty());
613    }
614
615    // ========================================================================
616    // Decode tests
617    // ========================================================================
618
619    #[test]
620    fn decode_goto_table_instruction() {
621        let inst = Instruction::GotoTable(10);
622        let encoded = inst.encode();
623        let (decoded, len) = Instruction::decode(&encoded).unwrap();
624        assert_eq!(len, 8);
625        match decoded {
626            Instruction::GotoTable(table_id) => assert_eq!(table_id, 10),
627            _ => panic!("expected GotoTable instruction"),
628        }
629    }
630
631    #[test]
632    fn decode_write_metadata_instruction() {
633        let inst = Instruction::WriteMetadata {
634            metadata: 0x1234_5678_9abc_def0,
635            mask: 0xffff_ffff_0000_0000,
636        };
637        let encoded = inst.encode();
638        let (decoded, len) = Instruction::decode(&encoded).unwrap();
639        assert_eq!(len, 24);
640        match decoded {
641            Instruction::WriteMetadata { metadata, mask } => {
642                assert_eq!(metadata, 0x1234_5678_9abc_def0);
643                assert_eq!(mask, 0xffff_ffff_0000_0000);
644            }
645            _ => panic!("expected WriteMetadata instruction"),
646        }
647    }
648
649    #[test]
650    fn decode_apply_actions_instruction() {
651        let actions = ActionList::new()
652            .pop_vlan()
653            .output(OutputPort::Port(2));
654        let inst = Instruction::ApplyActions(actions);
655        let encoded = inst.encode();
656        let (decoded, _) = Instruction::decode(&encoded).unwrap();
657        match decoded {
658            Instruction::ApplyActions(actions) => {
659                assert_eq!(actions.len(), 2);
660            }
661            _ => panic!("expected ApplyActions instruction"),
662        }
663    }
664
665    #[test]
666    fn decode_write_actions_instruction() {
667        let actions = ActionList::new().output(OutputPort::Port(1));
668        let inst = Instruction::WriteActions(actions);
669        let encoded = inst.encode();
670        let (decoded, _) = Instruction::decode(&encoded).unwrap();
671        match decoded {
672            Instruction::WriteActions(actions) => {
673                assert_eq!(actions.len(), 1);
674            }
675            _ => panic!("expected WriteActions instruction"),
676        }
677    }
678
679    #[test]
680    fn decode_clear_actions_instruction() {
681        let inst = Instruction::ClearActions;
682        let encoded = inst.encode();
683        let (decoded, len) = Instruction::decode(&encoded).unwrap();
684        assert_eq!(len, 8);
685        assert!(matches!(decoded, Instruction::ClearActions));
686    }
687
688    #[test]
689    fn decode_meter_instruction() {
690        let inst = Instruction::Meter(42);
691        let encoded = inst.encode();
692        let (decoded, len) = Instruction::decode(&encoded).unwrap();
693        assert_eq!(len, 8);
694        match decoded {
695            Instruction::Meter(meter_id) => assert_eq!(meter_id, 42),
696            _ => panic!("expected Meter instruction"),
697        }
698    }
699
700    #[test]
701    fn decode_instruction_list_multiple() {
702        let actions = ActionList::new().output(OutputPort::Port(1));
703        let original = InstructionList::new()
704            .apply_actions(actions)
705            .goto_table(10);
706        let encoded = original.encode();
707        let decoded = InstructionList::decode(&encoded).unwrap();
708        assert_eq!(decoded.len(), 2);
709        assert!(matches!(decoded.instructions()[0], Instruction::ApplyActions(_)));
710        assert!(matches!(decoded.instructions()[1], Instruction::GotoTable(10)));
711    }
712
713    #[test]
714    fn decode_instruction_list_empty() {
715        let original = InstructionList::new();
716        let encoded = original.encode();
717        let decoded = InstructionList::decode(&encoded).unwrap();
718        assert!(decoded.is_empty());
719    }
720
721    #[test]
722    fn roundtrip_instruction_list() {
723        let actions = ActionList::new()
724            .push_vlan(0x8100)
725            .set_vlan_vid(100)
726            .output(OutputPort::Port(3));
727        let original = InstructionList::new()
728            .write_metadata(0xabcd, 0xffff)
729            .apply_actions(actions)
730            .meter(50)
731            .goto_table(5);
732
733        let encoded = original.encode();
734        let decoded = InstructionList::decode(&encoded).unwrap();
735
736        assert_eq!(decoded.len(), 4);
737        match &decoded.instructions()[0] {
738            Instruction::WriteMetadata { metadata, mask } => {
739                assert_eq!(*metadata, 0xabcd);
740                assert_eq!(*mask, 0xffff);
741            }
742            _ => panic!("expected WriteMetadata"),
743        }
744        match &decoded.instructions()[1] {
745            Instruction::ApplyActions(actions) => assert_eq!(actions.len(), 3),
746            _ => panic!("expected ApplyActions"),
747        }
748        match &decoded.instructions()[2] {
749            Instruction::Meter(meter_id) => assert_eq!(*meter_id, 50),
750            _ => panic!("expected Meter"),
751        }
752        match &decoded.instructions()[3] {
753            Instruction::GotoTable(table_id) => assert_eq!(*table_id, 5),
754            _ => panic!("expected GotoTable"),
755        }
756    }
757
758    #[test]
759    fn instruction_type_try_from() {
760        assert_eq!(InstructionType::try_from(1).unwrap(), InstructionType::GotoTable);
761        assert_eq!(InstructionType::try_from(2).unwrap(), InstructionType::WriteMetadata);
762        assert_eq!(InstructionType::try_from(3).unwrap(), InstructionType::WriteActions);
763        assert_eq!(InstructionType::try_from(4).unwrap(), InstructionType::ApplyActions);
764        assert_eq!(InstructionType::try_from(5).unwrap(), InstructionType::ClearActions);
765        assert_eq!(InstructionType::try_from(6).unwrap(), InstructionType::Meter);
766        assert!(InstructionType::try_from(99).is_err());
767    }
768}