pyof.v0x04.common.action   A
last analyzed

Complexity

Total Complexity 32

Size/Duplication

Total Lines 452
Duplicated Lines 5.75 %

Test Coverage

Coverage 85.96%

Importance

Changes 0
Metric Value
wmc 32
eloc 181
dl 26
loc 452
ccs 147
cts 171
cp 0.8596
rs 9.84
c 0
b 0
f 0

25 Methods

Rating   Name   Duplication   Size   Complexity  
A ActionPopVLAN.__init__() 0 3 1
A ActionCopyTTLOut.__init__() 0 3 1
A ActionSetNWTTL.__init__() 0 8 1
A ActionPopPBB.__init__() 0 3 1
A ActionSetMPLSTTL.__init__() 0 8 1
A ActionCopyTTLIn.__init__() 0 3 1
A ActionGroup.__init__() 0 10 1
A ActionDecNWTTL.__init__() 0 3 1
A ActionDecMPLSTTL.__init__() 0 3 1
A ActionHeader.get_size() 0 8 3
A ActionExperimenter.__init__() 0 13 1
A ActionHeader.__init__() 0 11 1
A ActionHeader.unpack() 26 26 3
A ActionHeader.get_allowed_types() 0 4 1
A ActionOutput.__init__() 0 11 1
A ActionPush.__init__() 0 10 1
A ActionPopMPLS.__init__() 0 8 1
A ActionSetField.__init__() 0 12 2
A ActionSetField.pack() 0 5 1
A ActionSetField._update_length() 0 7 2
A ActionSetField.__repr__() 0 2 1
A ListOfActions.__init__() 0 8 1
A ActionSetField._complete_last_byte() 0 7 2
A ActionOutput.__repr__() 0 2 1
A ActionSetQueue.__init__() 0 8 1

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
"""Defines actions that may be associated with flows packets."""
2
# System imports
3 1
from enum import IntEnum
4 1
from math import ceil
5
6
# Local source tree imports
7 1
from pyof.foundation.base import GenericStruct
8 1
from pyof.foundation.basic_types import (
9
    BinaryData, FixedTypeList, Pad, UBInt8, UBInt16, UBInt32)
10 1
from pyof.v0x04.common.flow_match import OxmTLV
11
12
# Third-party imports
13
14 1
__all__ = ('ActionExperimenter', 'ActionGroup', 'ActionHeader',
15
           'ActionCopyTTLIn', 'ActionCopyTTLOut', 'ActionDecMPLSTTL',
16
           'ActionSetMPLSTTL', 'ActionDecNWTTL', 'ActionSetNWTTL',
17
           'ActionOutput', 'ActionPopMPLS', 'ActionPopPBB', 'ActionPopVLAN',
18
           'ActionPush', 'ActionSetField', 'ActionSetQueue', 'ActionType',
19
           'ControllerMaxLen', 'ListOfActions')
20
21
# Enums
22
23
24 1
class ActionType(IntEnum):
25
    """Actions associated with flows and packets."""
26
27
    #: Output to switch port.
28 1
    OFPAT_OUTPUT = 0
29
    #: Copy TTL "outwards" -- from next-to-outermost to outermost
30 1
    OFPAT_COPY_TTL_OUT = 11
31
    #: Copy TTL "inwards" -- from outermost to next-to-outermost
32 1
    OFPAT_COPY_TTL_IN = 12
33
    #: MPLS TTL
34 1
    OFPAT_SET_MPLS_TTL = 15
35
    #: Decrement MPLS TTL
36 1
    OFPAT_DEC_MPLS_TTL = 16
37
    #: Push a new VLAN tag
38 1
    OFPAT_PUSH_VLAN = 17
39
    #: Pop the outer VLAN tag
40 1
    OFPAT_POP_VLAN = 18
41
    #: Push a new MPLS tag
42 1
    OFPAT_PUSH_MPLS = 19
43
    #: Pop the outer MPLS tag
44 1
    OFPAT_POP_MPLS = 20
45
    #: Set queue id when outputting to a port
46 1
    OFPAT_SET_QUEUE = 21
47
    #: Apply group.
48 1
    OFPAT_GROUP = 22
49
    #: IP TTL.
50 1
    OFPAT_SET_NW_TTL = 23
51
    #: Decrement IP TTL.
52 1
    OFPAT_DEC_NW_TTL = 24
53
    #: Set a header field using OXM TLV format.
54 1
    OFPAT_SET_FIELD = 25
55
    #: Push a new PBB service tag (I-TAG)
56 1
    OFPAT_PUSH_PBB = 26
57
    #: Pop the outer PBB service tag (I-TAG)
58 1
    OFPAT_POP_PBB = 27
59
    #: Experimenter type
60 1
    OFPAT_EXPERIMENTER = 0xffff
61
62
63 1
class ControllerMaxLen(IntEnum):
64
    """A max_len of OFPCML_NO_BUFFER means not to buffer.
65
66
    The packet should be sent.
67
    """
68
69
    #: maximum max_len value which can be used to request a specific byte
70
    #:     length.
71 1
    OFPCML_MAX = 0xffe5
72
    #: indicates that no buffering should be applied and the whole packet is to
73
    #:     be sent to the controller.
74 1
    OFPCML_NO_BUFFER = 0xffff
75
76
77
# Classes
78
79
80 1
class ActionHeader(GenericStruct):
81
    """Action header that is common to all actions.
82
83
    The length includes the header and any padding used to make the action
84
    64-bit aligned.
85
    NB: The length of an action *must* always be a multiple of eight.
86
    """
87
88
    #: One of OFPAT_*.
89 1
    action_type = UBInt16(enum_ref=ActionType)
90
    #: Length of action, including this header. This is the length of actions,
91
    #:    including any padding to make it 64-bit aligned.
92 1
    length = UBInt16()
93
    # Pad for 64-bit alignment.
94
    # This should not be implemented, as each action type has its own padding.
95
    # pad = Pad(4)
96
97 1
    _allowed_types = ()
98
99 1
    def __init__(self, action_type=None, length=None):
100
        """Create an ActionHeader with the optional parameters below.
101
102
        Args:
103
            action_type (~pyof.v0x04.common.action.ActionType):
104
                The type of the action.
105
            length (int): Length of action, including this header.
106
        """
107 1
        super().__init__()
108 1
        self.action_type = action_type
109 1
        self.length = length
110
111 1
    def get_size(self, value=None):
112
        """Return the action length including the padding (multiple of 8)."""
113 1
        if isinstance(value, ActionHeader):
114
            return value.get_size()
115 1
        if value is None:
116 1
            current_size = super().get_size()
117 1
            return ceil(current_size / 8) * 8
118
        raise ValueError(f'Invalid value "{value}" for Action*.get_size()')
119
120 1 View Code Duplication
    def unpack(self, buff, offset=0):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
121
        """Unpack a binary message into this object's attributes.
122
123
        Unpack the binary value *buff* and update this object attributes based
124
        on the results.
125
126
        Args:
127
            buff (bytes): Binary data package to be unpacked.
128
            offset (int): Where to begin unpacking.
129
130
        Raises:
131
            Exception: If there is a struct unpacking error.
132
133
        """
134 1
        self.action_type = UBInt16(enum_ref=ActionType)
135 1
        self.action_type.unpack(buff, offset)
136
137 1
        self.length = UBInt16()
138 1
        self.length.unpack(buff, offset=offset+2)
139
140 1
        for cls in ActionHeader.__subclasses__():
141 1
            if self.action_type.value in cls.get_allowed_types():
142 1
                self.__class__ = cls
143 1
                break
144
145 1
        super().unpack(buff[:offset+self.length], offset)
146
147 1
    @classmethod
148
    def get_allowed_types(cls):
149
        """Return allowed types for the class."""
150 1
        return cls._allowed_types
151
152
153 1
class ActionExperimenter(ActionHeader):
154
    """Action structure for OFPAT_EXPERIMENTER."""
155
156 1
    experimenter = UBInt32()
157 1
    body = BinaryData()
158
159 1
    _allowed_types = (ActionType.OFPAT_EXPERIMENTER,)
160
161 1
    def __init__(self, length=None, experimenter=None, body=None):
162
        """Create ActionExperimenterHeader with the optional parameters below.
163
164
        Args:
165
            experimenter (int): The experimenter field is the Experimenter ID,
166
                which takes the same form as in struct ofp_experimenter.
167
            body(bytes): The body of the experimenter. It is vendor-defined,
168
                so it is left as it is.
169
        """
170 1
        super().__init__(action_type=ActionType.OFPAT_EXPERIMENTER)
171 1
        self.length = length
172 1
        self.experimenter = experimenter
173 1
        self.body = body
174
175
176 1
class ActionGroup(ActionHeader):
177
    """Action structure for OFPAT_GROUP."""
178
179 1
    group_id = UBInt32()
180
181 1
    _allowed_types = (ActionType.OFPAT_GROUP,)
182
183 1
    def __init__(self, group_id=None):
184
        """Create an ActionGroup with the optional parameters below.
185
186
        Args:
187
            group_id (int): The group_id indicates the group used to process
188
                this packet. The set of buckets to apply depends on the group
189
                type.
190
        """
191
        super().__init__(action_type=ActionType.OFPAT_GROUP, length=8)
192
        self.group_id = group_id
193
194
195 1
class ActionDecMPLSTTL(ActionHeader):
196
    """Action structure for OFPAT_DEC_MPLS_TTL."""
197
198 1
    pad = Pad(4)
199
200 1
    _allowed_types = (ActionType.OFPAT_DEC_MPLS_TTL,)
201
202 1
    def __init__(self):
203
        """Create an ActionDecMPLSTTL."""
204
        super().__init__(action_type=ActionType.OFPAT_DEC_MPLS_TTL, length=8)
205
206
207 1
class ActionSetMPLSTTL(ActionHeader):
208
    """Action structure for OFPAT_SET_MPLS_TTL."""
209
210 1
    mpls_ttl = UBInt8()
211 1
    pad = Pad(3)
212
213 1
    _allowed_types = (ActionType.OFPAT_SET_MPLS_TTL,)
214
215 1
    def __init__(self, mpls_ttl=None):
216
        """Create an ActionSetMPLSTTL with the optional parameters below.
217
218
        Args:
219
            mpls_ttl (int): The mpls_ttl field is the MPLS TTL to set.
220
        """
221
        super().__init__(action_type=ActionType.OFPAT_SET_MPLS_TTL, length=8)
222
        self.mpls_ttl = mpls_ttl
223
224
225 1
class ActionCopyTTLIn(ActionHeader):
226
    """Action structure for OFPAT_COPY_TTL_IN."""
227
228 1
    pad = Pad(4)
229
230 1
    _allowed_types = (ActionType.OFPAT_COPY_TTL_IN,)
231
232 1
    def __init__(self):
233
        """Create an ActionCopyTTLIn."""
234
        super().__init__(action_type=ActionType.OFPAT_COPY_TTL_IN, length=8)
235
236
237 1
class ActionCopyTTLOut(ActionHeader):
238
    """Action structure for OFPAT_COPY_TTL_OUT."""
239
240 1
    pad = Pad(4)
241
242 1
    _allowed_types = (ActionType.OFPAT_COPY_TTL_OUT,)
243
244 1
    def __init__(self):
245
        """Create an ActionCopyTTLOut."""
246
        super().__init__(action_type=ActionType.OFPAT_COPY_TTL_OUT, length=8)
247
248
249 1
class ActionPopVLAN(ActionHeader):
250
    """Action structure for OFPAT_POP_VLAN."""
251
252 1
    pad = Pad(4)
253
254 1
    _allowed_types = (ActionType.OFPAT_POP_VLAN,)
255
256 1
    def __init__(self):
257
        """Create an ActionPopVLAN."""
258
        super().__init__(action_type=ActionType.OFPAT_POP_VLAN, length=8)
259
260
261 1
class ActionPopPBB(ActionHeader):
262
    """Action structure for OFPAT_POP_PBB."""
263
264 1
    pad = Pad(4)
265
266 1
    _allowed_types = (ActionType.OFPAT_POP_PBB,)
267
268 1
    def __init__(self):
269
        """Create an ActionPopPBB."""
270
        super().__init__(action_type=ActionType.OFPAT_POP_PBB, length=8)
271
272
273 1
class ActionDecNWTTL(ActionHeader):
274
    """Action structure for OFPAT_DEC_NW_TTL."""
275
276 1
    pad = Pad(4)
277
278 1
    _allowed_types = (ActionType.OFPAT_DEC_NW_TTL,)
279
280 1
    def __init__(self):
281
        """Create a ActionDecNWTTL."""
282
        super().__init__(action_type=ActionType.OFPAT_DEC_NW_TTL, length=8)
283
284
285 1
class ActionSetNWTTL(ActionHeader):
286
    """Action structure for OFPAT_SET_NW_TTL."""
287
288 1
    nw_ttl = UBInt8()
289 1
    pad = Pad(3)
290
291 1
    _allowed_types = (ActionType.OFPAT_SET_NW_TTL,)
292
293 1
    def __init__(self, nw_ttl=None):
294
        """Create an ActionSetNWTTL with the optional parameters below.
295
296
        Args:
297
            nw_ttl (int): the TTL address to set in the IP header.
298
        """
299
        super().__init__(action_type=ActionType.OFPAT_SET_NW_TTL, length=8)
300
        self.nw_ttl = nw_ttl
301
302
303 1
class ActionOutput(ActionHeader):
304
    """Defines the actions output.
305
306
    Action structure for :attr:`ActionType.OFPAT_OUTPUT`, which sends packets
307
    out :attr:`port`. When the :attr:`port` is the
308
    :attr:`.Port.OFPP_CONTROLLER`, :attr:`max_length` indicates the max number
309
    of bytes to send. A :attr:`max_length` of zero means no bytes of the packet
310
    should be sent.
311
    """
312
313 1
    port = UBInt32()
314 1
    max_length = UBInt16()
315 1
    pad = Pad(6)
316
317 1
    _allowed_types = (ActionType.OFPAT_OUTPUT,)
318
319 1
    def __init__(self, port=None,
320
                 max_length=ControllerMaxLen.OFPCML_NO_BUFFER):
321
        """Create a ActionOutput with the optional parameters below.
322
323
        Args:
324
            port (:class:`Port` or :class:`int`): Output port.
325
            max_length (int): Max length to send to controller.
326
        """
327 1
        super().__init__(action_type=ActionType.OFPAT_OUTPUT, length=16)
328 1
        self.port = port
329 1
        self.max_length = max_length
330
331 1
    def __repr__(self):
332
        return f"{type(self).__name__}(port={self.port})"
333
334
335 1
class ActionPopMPLS(ActionHeader):
336
    """Action structure for OFPAT_POP_MPLS."""
337
338 1
    ethertype = UBInt16()
339 1
    pad = Pad(2)
340
341 1
    _allowed_types = (ActionType.OFPAT_POP_MPLS,)
342
343 1
    def __init__(self, ethertype=None):
344
        """Create an ActionPopMPLS with the optional parameters below.
345
346
        Args:
347
            ethertype (int): indicates the Ethertype of the payload.
348
        """
349
        super().__init__(action_type=ActionType.OFPAT_POP_MPLS)
350
        self.ethertype = ethertype
351
352
353 1
class ActionPush(ActionHeader):
354
    """Action structure for OFPAT_PUSH_[VLAN/MPLS/PBB]."""
355
356 1
    ethertype = UBInt16()
357 1
    pad = Pad(2)
358
359 1
    _allowed_types = (ActionType.OFPAT_PUSH_VLAN, ActionType.OFPAT_PUSH_MPLS,
360
                      ActionType.OFPAT_PUSH_PBB)
361
362 1
    def __init__(self, action_type=None, ethertype=None):
363
        """Create a ActionPush with the optional parameters below.
364
365
        Args:
366
            action_type (:class:`ActionType`): indicates which tag will be
367
                pushed (VLAN, MPLS, PBB).
368
            ethertype (int): indicates the Ethertype of the new tag.
369
        """
370
        super().__init__(action_type, length=8)
371
        self.ethertype = ethertype
372
373
374 1
class ActionSetField(ActionHeader):
375
    """Action structure for OFPAT_SET_FIELD."""
376
377 1
    field = OxmTLV()
378
379 1
    _allowed_types = (ActionType.OFPAT_SET_FIELD,)
380
381 1
    def __init__(self, field=None):
382
        """Create a ActionSetField with the optional parameters below.
383
384
        Args:
385
            length (int): length padded to 64 bits, followed by exactly
386
                          oxm_len bytes containing a single OXM TLV, then
387
                          exactly ((oxm_len + 4) + 7)/8*8 - (oxm_len + 4)
388
                          (between 0 and 7) bytes of all-zero bytes
389
            field (:class:`OxmTLV`): OXM field and value.
390
        """
391 1
        super().__init__(action_type=ActionType.OFPAT_SET_FIELD)
392 1
        self.field = OxmTLV() if field is None else field
393
394 1
    def __repr__(self):
395
        return (f"{type(self).__name__}({self.field.oxm_field!s}, "
396
                f"{self.field.oxm_value})")
397
398 1
    def pack(self, value=None):
399
        """Pack this structure updating the length and padding it."""
400 1
        self._update_length()
401 1
        packet = super().pack()
402 1
        return self._complete_last_byte(packet)
403
404 1
    def _update_length(self):
405
        """Update the length field of the struct."""
406 1
        action_length = 4 + len(self.field.pack())
407 1
        overflow = action_length % 8
408 1
        self.length = action_length
409 1
        if overflow:
410
            self.length = action_length + 8 - overflow
411
412 1
    def _complete_last_byte(self, packet):
413
        """Pad until the packet length is a multiple of 8 (bytes)."""
414 1
        padded_size = self.length
415 1
        padding_bytes = padded_size - len(packet)
416 1
        if padding_bytes > 0:
417
            packet += Pad(padding_bytes).pack()
418 1
        return packet
419
420
421 1
class ActionSetQueue(ActionHeader):
422
    """Action structure for OFPAT_SET_QUEUE."""
423
424 1
    queue_id = UBInt32()
425
426 1
    _allowed_types = (ActionType.OFPAT_SET_QUEUE,)
427
428 1
    def __init__(self, queue_id=None):
429
        """Create an ActionSetQueue with the optional parameters below.
430
431
        Args:
432
            queue_id (int): The queue_id send packets to given queue on port.
433
        """
434
        super().__init__(action_type=ActionType.OFPAT_SET_QUEUE, length=8)
435
        self.queue_id = queue_id
436
437
438 1
class ListOfActions(FixedTypeList):
439
    """List of actions.
440
441
    Represented by instances of ActionHeader and used on ActionHeader objects.
442
    """
443
444 1
    def __init__(self, items=None):
445
        """Create a ListOfActions with the optional parameters below.
446
447
        Args:
448
            items (~pyof.v0x04.common.action.ActionHeader):
449
                Instance or a list of instances.
450
        """
451
        super().__init__(pyof_class=ActionHeader, items=items)
452