Passed
Pull Request — master (#452)
by macartur
01:55
created

PacketOut   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 105
Duplicated Lines 0 %

Test Coverage

Coverage 91.07%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 22
c 1
b 0
f 0
dl 0
loc 105
ccs 51
cts 56
cp 0.9107
rs 10
1
"""For the controller to send a packet out through the datapath."""
2 1
from copy import deepcopy
3
4 1
from pyof.foundation.base import GenericMessage
5 1
from pyof.foundation.basic_types import BinaryData, UBInt16, UBInt32
6 1
from pyof.foundation.exceptions import PackException, ValidationError
7 1
from pyof.v0x01.common.action import ListOfActions
8 1
from pyof.v0x01.common.constants import NO_BUFFER
9 1
from pyof.v0x01.common.header import Header, Type
10 1
from pyof.v0x01.common.phy_port import Port
11
12 1
__all__ = ('PacketOut',)
13
14
# Classes
15
16
#: in_port valid virtual port values, for validation
17 1
_VIRT_IN_PORTS = (Port.OFPP_LOCAL, Port.OFPP_CONTROLLER, Port.OFPP_NONE)
18
19
20 1
class PacketOut(GenericMessage):
21
    """Send packet (controller -> datapath)."""
22
23
    #: :class:`~pyof.v0x01.common.header.Header`
24 1
    header = Header(message_type=Type.OFPT_PACKET_OUT)
25 1
    buffer_id = UBInt32()
26 1
    in_port = UBInt16()
27 1
    actions_len = UBInt16()
28 1
    actions = ListOfActions()
29 1
    data = BinaryData()
30
31 1
    def __init__(self, xid=None, buffer_id=NO_BUFFER, in_port=Port.OFPP_NONE,
32
                 actions=None, data=b''):
33
        """Create a PacketOut with the optional parameters below.
34
35
        Args:
36
            xid (int): xid of the message header.
37
            buffer_id (int): ID assigned by datapath (-1 if none).
38
            in_port (:class:`int` / :class:`~pyof.v0x01.common.phy_port.Port`):
39
                Packet's input port (:attr:`.Port.OFPP_NONE` if none). Virtual
40
                ports :attr:`Port.OFPP_IN_PORT`, :attr:`Port.OFPP_TABLE`,
41
                :attr:`Port.OFPP_NORMAL`, :attr:`Port.OFPP_FLOOD`, and
42
                :attr:`Port.OFPP_ALL` cannot be used as input port.
43
            actions (~pyof.v0x01.common.action.ListOfActions): List of Actions.
44
            data (bytes): Packet data. The length is inferred from the length
45
                field in the header. (Only meaningful if ``buffer_id`` == -1).
46
        """
47 1
        super().__init__(xid)
48 1
        self.buffer_id = buffer_id
49 1
        self.in_port = in_port
50 1
        self.actions = [] if actions is None else actions
51 1
        self.data = data
52
53 1
    def validate(self):
54
        """Validate the entire message."""
55 1
        if not super().is_valid():
56
            raise ValidationError()
57 1
        self._validate_in_port()
58
59 1
    def is_valid(self):
60
        """Answer if this message is valid."""
61 1
        try:
62 1
            self.validate()
63 1
            return True
64 1
        except ValidationError:
65 1
            return False
66
67 1
    def pack(self, value=None):
68
        """Update the action_len attribute and call super's pack."""
69 1
        if value is None:
70 1
            self._update_actions_len()
71 1
            return super().pack()
72
        elif isinstance(value, type(self)):
73
            return value.pack()
74
        else:
75
            msg = "{} is not an instance of {}".format(value,
76
                                                       type(self).__name__)
77
            raise PackException(msg)
78
79 1
    def unpack(self, buff, offset=0):
80
        """Unpack a binary message into this object's attributes.
81
82
        Unpack the binary value *buff* and update this object attributes based
83
        on the results. It is an inplace method and it receives the binary data
84
        of the message **without the header**.
85
86
        This class' unpack method is like the :meth:`.GenericMessage.unpack`
87
        one, except for the ``actions`` attribute which has a length determined
88
        by the ``actions_len`` attribute.
89
90
        Args:
91
            buff (bytes): Binary data package to be unpacked, without the
92
                header.
93
            offset (int): Where to begin unpacking.
94
        """
95 1
        begin = offset
96 1
        for attribute_name, class_attribute in self.get_class_attributes():
97 1
            if type(class_attribute).__name__ != "Header":
98 1
                attribute = deepcopy(class_attribute)
99 1
                if attribute_name == 'actions':
100 1
                    length = self.actions_len.value
101 1
                    attribute.unpack(buff[begin:begin+length])
102
                else:
103 1
                    attribute.unpack(buff, begin)
104 1
                setattr(self, attribute_name, attribute)
105 1
                begin += attribute.get_size()
106
107 1
    def _update_actions_len(self):
108
        """Update the actions_len field based on actions value."""
109 1
        if isinstance(self.actions, ListOfActions):
110 1
            self.actions_len = self.actions.get_size()
111
        else:
112 1
            self.actions_len = ListOfActions(self.actions).get_size()
113
114 1
    def _validate_in_port(self):
115
        """Validate in_port attribute.
116
117
        Raises:
118
            ValidationError: If in_port is a invalid port.
119
120
        """
121 1
        is_valid_range = self.in_port > 0 and self.in_port <= Port.OFPP_MAX
122 1
        is_valid_virtual_in_ports = self.in_port in _VIRT_IN_PORTS
123
124 1
        if (is_valid_range or is_valid_virtual_in_ports) is False:
125
            raise ValidationError(f'{self.in_port} is not a valid input port.')
126