# This file is part of Scapy
# See http://www.secdev.org/projects/scapy for more information
# Copyright (C) Philippe Biondi <phil@secdev.org>
# This program is published under a GPLv2 license

# Copyright (C) 2014 Maxence Tury <maxence.tury@ssi.gouv.fr>
# OpenFlow is an open standard used in SDN deployments.
# Based on OpenFlow v1.0.1
# Specifications can be retrieved from https://www.opennetworking.org/

# scapy.contrib.description = Openflow v1.0
# scapy.contrib.status = loads

from __future__ import absolute_import
import struct


from scapy.compat import chb, orb, raw
from scapy.config import conf
from scapy.error import warning
from scapy.fields import BitEnumField, BitField, ByteEnumField, ByteField, FieldLenField, FlagsField, IntEnumField, IntField, IPField, LongField, MACField, PacketField, PacketListField, ShortEnumField, ShortField, StrFixedLenField, X3BytesField, XBitField, XByteField, XIntField, XShortField  # noqa: E501
from scapy.layers.l2 import Ether
from scapy.layers.inet import TCP
from scapy.packet import Packet, Raw, bind_bottom_up, bind_top_down
from scapy.utils import binrepr
from scapy.modules import six


# If prereq_autocomplete is True then match prerequisites will be
# automatically handled. See OFPMatch class.
conf.contribs['OPENFLOW'] = {'prereq_autocomplete': True}

#####################################################
#                 Predefined values                 #
#####################################################

ofp_port_no = {0xfff8: "IN_PORT",
               0xfff9: "TABLE",
               0xfffa: "NORMAL",
               0xfffb: "FLOOD",
               0xfffc: "ALL",
               0xfffd: "CONTROLLER",
               0xfffe: "LOCAL",
               0xffff: "NONE"}

ofp_table = {0xff: "ALL"}

ofp_queue = {0xffffffff: "ALL"}

ofp_buffer = {0xffffffff: "NO_BUFFER"}

ofp_max_len = {0xffff: "NO_BUFFER"}

#####################################################
#                 Common structures                 #
#####################################################

# The following structures will be used in different types
# of OpenFlow messages: ports, matches, actions, queues.


#                     Ports                     #

ofp_port_config = ["PORT_DOWN",
                   "NO_STP",
                   "NO_RECV",
                   "NO_RECV_STP",
                   "NO_FLOOD",
                   "NO_FWD",
                   "NO_PACKET_IN"]

ofp_port_state = ["LINK_DOWN"]

ofp_port_state_stp = {0: "OFPPS_STP_LISTEN",
                      1: "OFPPS_STP_LEARN",
                      2: "OFPPS_STP_FORWARD",
                      3: "OFPPS_STP_BLOCK"}

ofp_port_features = ["10MB_HD",
                     "10MB_FD",
                     "100MB_HD",
                     "100MB_FD",
                     "1GB_HD",
                     "1GB_FD",
                     "10GB_FD",
                     "COPPER",
                     "FIBER",
                     "AUTONEG",
                     "PAUSE",
                     "PAUSE_ASYM"]


class OFPPhyPort(Packet):
    name = "OFP_PHY_PORT"
    fields_desc = [ShortEnumField("port_no", 0, ofp_port_no),
                   MACField("hw_addr", "0"),
                   StrFixedLenField("port_name", "", 16),
                   FlagsField("config", 0, 32, ofp_port_config),
                   BitEnumField("stp_state", 0, 24, ofp_port_state),
                   FlagsField("state", 0, 8, ofp_port_state),
                   FlagsField("curr", 0, 32, ofp_port_features),
                   FlagsField("advertised", 0, 32, ofp_port_features),
                   FlagsField("supported", 0, 32, ofp_port_features),
                   FlagsField("peer", 0, 32, ofp_port_features)]

    def extract_padding(self, s):
        return b"", s


class OFPMatch(Packet):
    name = "OFP_MATCH"
    fields_desc = [FlagsField("wildcards1", None, 12, ["DL_VLAN_PCP",
                                                       "NW_TOS"]),
                   BitField("nw_dst_mask", None, 6),
                   BitField("nw_src_mask", None, 6),
                   FlagsField("wildcards2", None, 8, ["IN_PORT",
                                                      "DL_VLAN",
                                                      "DL_SRC",
                                                      "DL_DST",
                                                      "DL_TYPE",
                                                      "NW_PROTO",
                                                      "TP_SRC",
                                                      "TP_DST"]),
                   ShortEnumField("in_port", None, ofp_port_no),
                   MACField("dl_src", None),
                   MACField("dl_dst", None),
                   ShortField("dl_vlan", None),
                   ByteField("dl_vlan_pcp", None),
                   XByteField("pad1", None),
                   ShortField("dl_type", None),
                   ByteField("nw_tos", None),
                   ByteField("nw_proto", None),
                   XShortField("pad2", None),
                   IPField("nw_src", "0"),
                   IPField("nw_dst", "0"),
                   ShortField("tp_src", None),
                   ShortField("tp_dst", None)]

    def extract_padding(self, s):
        return b"", s

    # with post_build we create the wildcards field bit by bit
    def post_build(self, p, pay):
        # first 10 bits of an ofp_match are always set to 0
        lst_bits = "0" * 10

        # when one field has not been declared, it is assumed to be wildcarded
        if self.wildcards1 is None:
            if self.nw_tos is None:
                lst_bits += "1"
            else:
                lst_bits += "0"
            if self.dl_vlan_pcp is None:
                lst_bits += "1"
            else:
                lst_bits += "0"
        else:
            w1 = binrepr(self.wildcards1)
            lst_bits += "0" * (2 - len(w1))
            lst_bits += w1

        # ip masks use 6 bits each
        if self.nw_dst_mask is None:
            if self.nw_dst == "0":
                lst_bits += "111111"
            # 0x100000 would be ok too (32-bit IP mask)
            else:
                lst_bits += "0" * 6
        else:
            m1 = binrepr(self.nw_dst_mask)
            lst_bits += "0" * (6 - len(m1))
            lst_bits += m1
        if self.nw_src_mask is None:
            if self.nw_src == "0":
                lst_bits += "111111"
            else:
                lst_bits += "0" * 6
        else:
            m2 = binrepr(self.nw_src_mask)
            lst_bits += "0" * (6 - len(m2))
            lst_bits += m2

        # wildcards2 works the same way as wildcards1
        if self.wildcards2 is None:
            if self.tp_dst is None:
                lst_bits += "1"
            else:
                lst_bits += "0"
            if self.tp_src is None:
                lst_bits += "1"
            else:
                lst_bits += "0"
            if self.nw_proto is None:
                lst_bits += "1"
            else:
                lst_bits += "0"
            if self.dl_type is None:
                lst_bits += "1"
            else:
                lst_bits += "0"
            if self.dl_dst is None:
                lst_bits += "1"
            else:
                lst_bits += "0"
            if self.dl_src is None:
                lst_bits += "1"
            else:
                lst_bits += "0"
            if self.dl_vlan is None:
                lst_bits += "1"
            else:
                lst_bits += "0"
            if self.in_port is None:
                lst_bits += "1"
            else:
                lst_bits += "0"
        else:
            w2 = binrepr(self.wildcards2)
            lst_bits += "0" * (8 - len(w2))
            lst_bits += w2

        # In order to write OFPMatch compliant with the specifications,
        # if prereq_autocomplete has been set to True
        # we assume ethertype=IP or nwproto=TCP when appropriate subfields are provided.  # noqa: E501
        if conf.contribs['OPENFLOW']['prereq_autocomplete']:
            if self.dl_type is None:
                if self.nw_src != "0" or self.nw_dst != "0" or \
                        self.nw_proto is not None or self.nw_tos is not None:
                    p = p[:22] + struct.pack("!H", 0x0800) + p[24:]
                    lst_bits = lst_bits[:-5] + "0" + lst_bits[-4:]
            if self.nw_proto is None:
                if self.tp_src is not None or self.tp_dst is not None:
                    p = p[:22] + struct.pack("!H", 0x0800) + p[24:]
                    lst_bits = lst_bits[:-5] + "0" + lst_bits[-4:]
                    p = p[:25] + struct.pack("!B", 0x06) + p[26:]
                    lst_bits = lst_bits[:-6] + "0" + lst_bits[-5:]

        ins = b"".join(chb(int("".join(x), 2)) for x in zip(*[iter(lst_bits)] * 8))  # noqa: E501
        p = ins + p[4:]
        return p + pay


class _ofp_header(Packet):
    name = "Dummy OpenFlow Header for some lower layers"

    def post_build(self, p, pay):
        if self.len is None:
            tmp_len = len(p) + len(pay)
            p = p[:2] + struct.pack("!H", tmp_len) + p[4:]
        return p + pay


class _ofp_header_item(Packet):
    name = "Dummy OpenFlow Header for items layers"

    def post_build(self, p, pay):
        if self.len is None:
            tmp_len = len(p) + len(pay)
            p = struct.pack("!H", tmp_len) + p[2:]
        return p + pay


#                      Actions                      #


class _UnknownOpenFlow(Raw):
    name = "Unknown OpenFlow packet"


class OpenFlow(_ofp_header):
    name = "OpenFlow dissector"

    @classmethod
    def dispatch_hook(cls, _pkt=None, *args, **kargs):
        if _pkt and len(_pkt) >= 2:
            version = orb(_pkt[0])
            if version == 0x04:  # OpenFlow 1.3
                from scapy.contrib.openflow3 import OpenFlow3
                return OpenFlow3.dispatch_hook(_pkt, *args, **kargs)
            elif version == 0x01:  # OpenFlow 1.0
                # port 6653 has been allocated by IANA, port 6633 should no
                # longer be used
                # OpenFlow function may be called with a None
                # self in OFPPacketField
                of_type = orb(_pkt[1])
                if of_type == 1:
                    err_type = orb(_pkt[9])
                    # err_type is a short int, but last byte is enough
                    if err_type == 255:
                        err_type = 65535
                    return ofp_error_cls[err_type]
                elif of_type == 16:
                    mp_type = orb(_pkt[9])
                    if mp_type == 255:
                        mp_type = 65535
                    return ofp_stats_request_cls[mp_type]
                elif of_type == 17:
                    mp_type = orb(_pkt[9])
                    if mp_type == 255:
                        mp_type = 65535
                    return ofp_stats_reply_cls[mp_type]
                else:
                    return ofpt_cls[of_type]
            else:
                warning("Unknown OpenFlow packet")
        return _UnknownOpenFlow


ofp_action_types = {0: "OFPAT_OUTPUT",
                    1: "OFPAT_SET_VLAN_VID",
                    2: "OFPAT_SET_VLAN_PCP",
                    3: "OFPAT_STRIP_VLAN",
                    4: "OFPAT_SET_DL_SRC",
                    5: "OFPAT_SET_DL_DST",
                    6: "OFPAT_SET_NW_SRC",
                    7: "OFPAT_SET_NW_DST",
                    8: "OFPAT_SET_NW_TOS",
                    9: "OFPAT_SET_TP_SRC",
                    10: "OFPAT_SET_TP_DST",
                    11: "OFPAT_ENQUEUE",
                    65535: "OFPAT_VENDOR"}


class OFPATOutput(OpenFlow):
    name = "OFPAT_OUTPUT"
    fields_desc = [ShortEnumField("type", 0, ofp_action_types),
                   ShortField("len", 8),
                   ShortEnumField("port", 0, ofp_port_no),
                   ShortEnumField("max_len", "NO_BUFFER", ofp_max_len)]


class OFPATSetVLANVID(OpenFlow):
    name = "OFPAT_SET_VLAN_VID"
    fields_desc = [ShortEnumField("type", 1, ofp_action_types),
                   ShortField("len", 8),
                   ShortField("vlan_vid", 0),
                   XShortField("pad", 0)]


class OFPATSetVLANPCP(OpenFlow):
    name = "OFPAT_SET_VLAN_PCP"
    fields_desc = [ShortEnumField("type", 2, ofp_action_types),
                   ShortField("len", 8),
                   ByteField("vlan_pcp", 0),
                   X3BytesField("pad", 0)]


class OFPATStripVLAN(OpenFlow):
    name = "OFPAT_STRIP_VLAN"
    fields_desc = [ShortEnumField("type", 3, ofp_action_types),
                   ShortField("len", 8),
                   XIntField("pad", 0)]


class OFPATSetDlSrc(OpenFlow):
    name = "OFPAT_SET_DL_SRC"
    fields_desc = [ShortEnumField("type", 4, ofp_action_types),
                   ShortField("len", 16),
                   MACField("dl_addr", "0"),
                   XBitField("pad", 0, 48)]


class OFPATSetDlDst(OpenFlow):
    name = "OFPAT_SET_DL_DST"
    fields_desc = [ShortEnumField("type", 5, ofp_action_types),
                   ShortField("len", 16),
                   MACField("dl_addr", "0"),
                   XBitField("pad", 0, 48)]


class OFPATSetNwSrc(OpenFlow):
    name = "OFPAT_SET_NW_SRC"
    fields_desc = [ShortEnumField("type", 6, ofp_action_types),
                   ShortField("len", 8),
                   IPField("nw_addr", "0")]


class OFPATSetNwDst(OpenFlow):
    name = "OFPAT_SET_NW_DST"
    fields_desc = [ShortEnumField("type", 7, ofp_action_types),
                   ShortField("len", 8),
                   IPField("nw_addr", "0")]


class OFPATSetNwToS(OpenFlow):
    name = "OFPAT_SET_TP_TOS"
    fields_desc = [ShortEnumField("type", 8, ofp_action_types),
                   ShortField("len", 8),
                   ByteField("nw_tos", 0),
                   X3BytesField("pad", 0)]


class OFPATSetTpSrc(OpenFlow):
    name = "OFPAT_SET_TP_SRC"
    fields_desc = [ShortEnumField("type", 9, ofp_action_types),
                   ShortField("len", 8),
                   ShortField("tp_port", 0),
                   XShortField("pad", 0)]


class OFPATSetTpDst(OpenFlow):
    name = "OFPAT_SET_TP_DST"
    fields_desc = [ShortEnumField("type", 10, ofp_action_types),
                   ShortField("len", 8),
                   ShortField("tp_port", 0),
                   XShortField("pad", 0)]


class OFPATEnqueue(OpenFlow):
    name = "OFPAT_ENQUEUE"
    fields_desc = [ShortEnumField("type", 11, ofp_action_types),
                   ShortField("len", 16),
                   ShortEnumField("port", 0, ofp_port_no),
                   XBitField("pad", 0, 48),
                   IntField("queue_id", 0)]


class OFPATVendor(OpenFlow):
    name = "OFPAT_VENDOR"
    fields_desc = [ShortEnumField("type", 65535, ofp_action_types),
                   ShortField("len", 8),
                   IntField("vendor", 0)]


ofp_action_cls = {0: OFPATOutput,
                  1: OFPATSetVLANVID,
                  2: OFPATSetVLANPCP,
                  3: OFPATStripVLAN,
                  4: OFPATSetDlSrc,
                  5: OFPATSetDlDst,
                  6: OFPATSetNwSrc,
                  7: OFPATSetNwDst,
                  8: OFPATSetNwToS,
                  9: OFPATSetTpSrc,
                  10: OFPATSetTpDst,
                  11: OFPATEnqueue,
                  65535: OFPATVendor}


class OFPAT(Packet):
    @classmethod
    def dispatch_hook(cls, _pkt=None, *args, **kargs):
        if _pkt and len(_pkt) >= 2:
            t = struct.unpack("!H", _pkt[:2])[0]
            return ofp_action_cls.get(t, Raw)
        return Raw

    def extract_padding(self, s):
        return b"", s


#                       Queues                      #


ofp_queue_property_types = {0: "OFPQT_NONE",
                            1: "OFPQT_MIN_RATE"}


class OFPQTNone(_ofp_header):
    name = "OFPQT_NONE"
    fields_desc = [ShortEnumField("type", 0, ofp_queue_property_types),
                   ShortField("len", 8),
                   XIntField("pad", 0)]


class OFPQTMinRate(_ofp_header):
    name = "OFPQT_MIN_RATE"
    fields_desc = [ShortEnumField("type", 1, ofp_queue_property_types),
                   ShortField("len", 16),
                   XIntField("pad", 0),
                   ShortField("rate", 0),
                   XBitField("pad2", 0, 48)]


ofp_queue_property_cls = {0: OFPQTNone,
                          1: OFPQTMinRate}


class OFPQT(Packet):
    @classmethod
    def dispatch_hook(cls, _pkt=None, *args, **kargs):
        if _pkt and len(_pkt) >= 2:
            t = struct.unpack("!H", _pkt[:2])[0]
            return ofp_queue_property_cls.get(t, Raw)
        return Raw

    def extract_padding(self, s):
        return b"", s


class OFPPacketQueue(Packet):
    name = "OFP_PACKET_QUEUE"
    fields_desc = [IntField("queue_id", 0),
                   ShortField("len", None),
                   XShortField("pad", 0),
                   PacketListField("properties", [], OFPQT,
                                    length_from=lambda pkt:pkt.len - 8)]  # noqa: E501

    def extract_padding(self, s):
        return b"", s

    def post_build(self, p, pay):
        if self.properties == []:
            p += raw(OFPQTNone())
        if self.len is None:
            tmp_len = len(p) + len(pay)
            p = p[:4] + struct.pack("!H", tmp_len) + p[6:]
        return p + pay


#####################################################
#              OpenFlow 1.0 Messages                #
#####################################################


ofp_version = {0x01: "OpenFlow 1.0",
               0x02: "OpenFlow 1.1",
               0x03: "OpenFlow 1.2",
               0x04: "OpenFlow 1.3",
               0x05: "OpenFlow 1.4"}

ofp_type = {0: "OFPT_HELLO",
            1: "OFPT_ERROR",
            2: "OFPT_ECHO_REQUEST",
            3: "OFPT_ECHO_REPLY",
            4: "OFPT_VENDOR",
            5: "OFPT_FEATURES_REQUEST",
            6: "OFPT_FEATURES_REPLY",
            7: "OFPT_GET_CONFIG_REQUEST",
            8: "OFPT_GET_CONFIG_REPLY",
            9: "OFPT_SET_CONFIG",
            10: "OFPT_PACKET_IN",
            11: "OFPT_FLOW_REMOVED",
            12: "OFPT_PORT_STATUS",
            13: "OFPT_PACKET_OUT",
            14: "OFPT_FLOW_MOD",
            15: "OFPT_PORT_MOD",
            16: "OFPT_STATS_REQUEST",
            17: "OFPT_STATS_REPLY",
            18: "OFPT_BARRIER_REQUEST",
            19: "OFPT_BARRIER_REPLY",
            20: "OFPT_QUEUE_GET_CONFIG_REQUEST",
            21: "OFPT_QUEUE_GET_CONFIG_REPLY"}


class OFPTHello(_ofp_header):
    name = "OFPT_HELLO"
    fields_desc = [ByteEnumField("version", 0x01, ofp_version),
                   ByteEnumField("type", 0, ofp_type),
                   ShortField("len", None),
                   IntField("xid", 0)]


#####################################################
#                    OFPT_ERROR                     #
#####################################################

# this class will be used to display some messages
# sent back by the switch after an error


class OFPacketField(PacketField):
    def getfield(self, pkt, s):
        try:
            tmp_len = s[2:4]
            tmp_len = struct.unpack("!H", tmp_len)[0]
            ofload = s[:tmp_len]
            remain = s[tmp_len:]
            return remain, OpenFlow(ofload)
        except Exception:
            return "", Raw(s)


ofp_error_type = {0: "OFPET_HELLO_FAILED",
                  1: "OFPET_BAD_REQUEST",
                  2: "OFPET_BAD_ACTION",
                  3: "OFPET_FLOW_MOD_FAILED",
                  4: "OFPET_PORT_MOD_FAILED",
                  5: "OFPET_QUEUE_OP_FAILED"}


class OFPETHelloFailed(_ofp_header):
    name = "OFPET_HELLO_FAILED"
    fields_desc = [ByteEnumField("version", 0x01, ofp_version),
                   ByteEnumField("type", 1, ofp_type),
                   ShortField("len", None),
                   IntField("xid", 0),
                   ShortEnumField("errtype", 0, ofp_error_type),
                   ShortEnumField("errcode", 0, {0: "OFPHFC_INCOMPATIBLE",
                                                 1: "OFPHFC_EPERM"}),
                   OFPacketField("data", "", Raw)]


class OFPETBadRequest(_ofp_header):
    name = "OFPET_BAD_REQUEST"
    fields_desc = [ByteEnumField("version", 0x01, ofp_version),
                   ByteEnumField("type", 1, ofp_type),
                   ShortField("len", None),
                   IntField("xid", 0),
                   ShortEnumField("errtype", 1, ofp_error_type),
                   ShortEnumField("errcode", 0, {0: "OFPBRC_BAD_VERSION",
                                                 1: "OFPBRC_BAD_TYPE",
                                                 2: "OFPBRC_BAD_STAT",
                                                 3: "OFPBRC_BAD_VENDOR",
                                                 4: "OFPBRC_BAD_SUBTYPE",
                                                 5: "OFPBRC_EPERM",
                                                 6: "OFPBRC_BAD_LEN",
                                                 7: "OFPBRC_BUFFER_EMPTY",
                                                 8: "OFPBRC_BUFFER_UNKNOWN"}),
                   OFPacketField("data", "", Raw)]


class OFPETBadAction(_ofp_header):
    name = "OFPET_BAD_ACTION"
    fields_desc = [ByteEnumField("version", 0x01, ofp_version),
                   ByteEnumField("type", 1, ofp_type),
                   ShortField("len", None),
                   IntField("xid", 0),
                   ShortEnumField("errtype", 2, ofp_error_type),
                   ShortEnumField("errcode", 0, {0: "OFPBAC_BAD_TYPE",
                                                 1: "OFPBAC_BAD_LEN",
                                                 2: "OFPBAC_BAD_VENDOR",
                                                 3: "OFPBAC_BAD_VENDOR_TYPE",
                                                 4: "OFPBAC_BAD_OUT_PORT",
                                                 5: "OFPBAC_BAD_ARGUMENT",
                                                 6: "OFPBAC_EPERM",
                                                 7: "OFPBAC_TOO_MANY",
                                                 8: "OFPBAC_BAD_QUEUE"}),
                   OFPacketField("data", "", Raw)]


class OFPETFlowModFailed(_ofp_header):
    name = "OFPET_FLOW_MOD_FAILED"
    fields_desc = [ByteEnumField("version", 0x01, ofp_version),
                   ByteEnumField("type", 1, ofp_type),
                   ShortField("len", None),
                   IntField("xid", 0),
                   ShortEnumField("errtype", 3, ofp_error_type),
                   ShortEnumField("errcode", 0, {0: "OFPFMFC_ALL_TABLES_FULL",
                                                 1: "OFPFMFC_OVERLAP",
                                                 2: "OFPFMFC_EPERM",
                                                 3: "OFPFMFC_BAD_EMERG_TIMEOUT",  # noqa: E501
                                                 4: "OFPFMFC_BAD_COMMAND",
                                                 5: "OFPFMFC_UNSUPPORTED"}),
                   OFPacketField("data", "", Raw)]


class OFPETPortModFailed(_ofp_header):
    name = "OFPET_PORT_MOD_FAILED"
    fields_desc = [ByteEnumField("version", 0x01, ofp_version),
                   ByteEnumField("type", 1, ofp_type),
                   ShortField("len", None),
                   IntField("xid", 0),
                   ShortEnumField("errtype", 4, ofp_error_type),
                   ShortEnumField("errcode", 0, {0: "OFPPMFC_BAD_PORT",
                                                 1: "OFPPMFC_BAD_HW_ADDR"}),
                   OFPacketField("data", "", Raw)]


class OFPETQueueOpFailed(_ofp_header):
    name = "OFPET_QUEUE_OP_FAILED"
    fields_desc = [ByteEnumField("version", 0x01, ofp_version),
                   ByteEnumField("type", 1, ofp_type),
                   ShortField("len", None),
                   IntField("xid", 0),
                   ShortEnumField("errtype", 5, ofp_error_type),
                   ShortEnumField("errcode", 0, {0: "OFPQOFC_BAD_PORT",
                                                 1: "OFPQOFC_BAD_QUEUE",
                                                 2: "OFPQOFC_EPERM"}),
                   OFPacketField("data", "", Raw)]


# ofp_error_cls allows generic method OpenFlow() to choose the right class for dissection  # noqa: E501
ofp_error_cls = {0: OFPETHelloFailed,
                 1: OFPETBadRequest,
                 2: OFPETBadAction,
                 3: OFPETFlowModFailed,
                 4: OFPETPortModFailed,
                 5: OFPETQueueOpFailed}

#                end of OFPT_ERRORS                 #


class OFPTEchoRequest(_ofp_header):
    name = "OFPT_ECHO_REQUEST"
    fields_desc = [ByteEnumField("version", 0x01, ofp_version),
                   ByteEnumField("type", 2, ofp_type),
                   ShortField("len", None),
                   IntField("xid", 0)]


class OFPTEchoReply(_ofp_header):
    name = "OFPT_ECHO_REPLY"
    fields_desc = [ByteEnumField("version", 0x01, ofp_version),
                   ByteEnumField("type", 3, ofp_type),
                   ShortField("len", None),
                   IntField("xid", 0)]


class OFPTVendor(_ofp_header):
    name = "OFPT_VENDOR"
    fields_desc = [ByteEnumField("version", 0x01, ofp_version),
                   ByteEnumField("type", 4, ofp_type),
                   ShortField("len", None),
                   IntField("xid", 0),
                   IntField("vendor", 0)]


class OFPTFeaturesRequest(_ofp_header):
    name = "OFPT_FEATURES_REQUEST"
    fields_desc = [ByteEnumField("version", 0x01, ofp_version),
                   ByteEnumField("type", 5, ofp_type),
                   ShortField("len", None),
                   IntField("xid", 0)]


ofp_action_types_flags = [v for v in six.itervalues(ofp_action_types)
                          if v != 'OFPAT_VENDOR']


class OFPTFeaturesReply(_ofp_header):
    name = "OFPT_FEATURES_REPLY"
    fields_desc = [ByteEnumField("version", 0x01, ofp_version),
                   ByteEnumField("type", 6, ofp_type),
                   ShortField("len", None),
                   IntField("xid", 0),
                   LongField("datapath_id", 0),
                   IntField("n_buffers", 0),
                   ByteField("n_tables", 1),
                   X3BytesField("pad", 0),
                   FlagsField("capabilities", 0, 32, ["FLOW_STATS",
                                                      "TABLE_STATS",
                                                      "PORT_STATS",
                                                      "STP",
                                                      "RESERVED",
                                                      "IP_REASM",
                                                      "QUEUE_STATS",
                                                      "ARP_MATCH_IP"]),
                   FlagsField("actions", 0, 32, ofp_action_types_flags),
                   PacketListField("ports", [], OFPPhyPort,
                                   length_from=lambda pkt:pkt.len - 32)]


class OFPTGetConfigRequest(_ofp_header):
    name = "OFPT_GET_CONFIG_REQUEST"
    fields_desc = [ByteEnumField("version", 0x01, ofp_version),
                   ByteEnumField("type", 7, ofp_type),
                   ShortField("len", None),
                   IntField("xid", 0)]


class OFPTGetConfigReply(_ofp_header):
    name = "OFPT_GET_CONFIG_REPLY"
    fields_desc = [ByteEnumField("version", 0x01, ofp_version),
                   ByteEnumField("type", 8, ofp_type),
                   ShortField("len", None),
                   IntField("xid", 0),
                   ShortEnumField("flags", 0, {0: "FRAG_NORMAL",
                                               1: "FRAG_DROP",
                                               2: "FRAG_REASM",
                                               3: "FRAG_MASK"}),
                   ShortField("miss_send_len", 0)]


class OFPTSetConfig(_ofp_header):
    name = "OFPT_SET_CONFIG"
    fields_desc = [ByteEnumField("version", 0x01, ofp_version),
                   ByteEnumField("type", 9, ofp_type),
                   ShortField("len", None),
                   IntField("xid", 0),
                   ShortEnumField("flags", 0, {0: "FRAG_NORMAL",
                                               1: "FRAG_DROP",
                                               2: "FRAG_REASM",
                                               3: "FRAG_MASK"}),
                   ShortField("miss_send_len", 128)]


class OFPTPacketIn(_ofp_header):
    name = "OFPT_PACKET_IN"
    fields_desc = [ByteEnumField("version", 0x01, ofp_version),
                   ByteEnumField("type", 10, ofp_type),
                   ShortField("len", None),
                   IntField("xid", 0),
                   IntEnumField("buffer_id", "NO_BUFFER", ofp_buffer),
                   ShortField("total_len", 0),
                   ShortEnumField("in_port", 0, ofp_port_no),
                   ByteEnumField("reason", 0, {0: "OFPR_NO_MATCH",
                                               1: "OFPR_ACTION"}),
                   XByteField("pad", 0),
                   PacketField("data", None, Ether)]


class OFPTFlowRemoved(_ofp_header):
    name = "OFPT_FLOW_REMOVED"
    fields_desc = [ByteEnumField("version", 0x01, ofp_version),
                   ByteEnumField("type", 11, ofp_type),
                   ShortField("len", None),
                   IntField("xid", 0),
                   PacketField("match", OFPMatch(), OFPMatch),
                   LongField("cookie", 0),
                   ShortField("priority", 0),
                   ByteEnumField("reason", 0, {0: "OFPRR_IDLE_TIMEOUT",
                                               1: "OFPRR_HARD_TIMEOUT",
                                               2: "OFPRR_DELETE"}),
                   XByteField("pad1", 0),
                   IntField("duration_sec", 0),
                   IntField("duration_nsec", 0),
                   ShortField("idle_timeout", 0),
                   XShortField("pad2", 0),
                   LongField("packet_count", 0),
                   LongField("byte_count", 0)]


class OFPTPortStatus(_ofp_header):
    name = "OFPT_PORT_STATUS"
    fields_desc = [ByteEnumField("version", 0x01, ofp_version),
                   ByteEnumField("type", 12, ofp_type),
                   ShortField("len", None),
                   IntField("xid", 0),
                   ByteEnumField("reason", 0, {0: "OFPPR_ADD",
                                               1: "OFPPR_DELETE",
                                               2: "OFPPR_MODIFY"}),
                   XBitField("pad", 0, 56),
                   PacketField("desc", OFPPhyPort(), OFPPhyPort)]


class OFPTPacketOut(_ofp_header):
    name = "OFPT_PACKET_OUT"
    fields_desc = [ByteEnumField("version", 0x01, ofp_version),
                   ByteEnumField("type", 13, ofp_type),
                   ShortField("len", None),
                   IntField("xid", 0),
                   IntEnumField("buffer_id", "NO_BUFFER", ofp_buffer),
                   ShortEnumField("in_port", "NONE", ofp_port_no),
                   FieldLenField("actions_len", None, fmt="H", length_of="actions"),  # noqa: E501
                   PacketListField("actions", [], OFPAT,
                                   ofp_action_cls,
                                   length_from=lambda pkt:pkt.actions_len),  # noqa: E501
                   PacketField("data", None, Ether)]


class OFPTFlowMod(_ofp_header):
    name = "OFPT_FLOW_MOD"
    fields_desc = [ByteEnumField("version", 0x01, ofp_version),
                   ByteEnumField("type", 14, ofp_type),
                   ShortField("len", None),
                   IntField("xid", 0),
                   PacketField("match", OFPMatch(), OFPMatch),
                   LongField("cookie", 0),
                   ShortEnumField("cmd", 0, {0: "OFPFC_ADD",
                                             1: "OFPFC_MODIFY",
                                             2: "OFPFC_MODIFY_STRICT",
                                             3: "OFPFC_DELETE",
                                             4: "OFPFC_DELETE_STRICT"}),
                   ShortField("idle_timeout", 0),
                   ShortField("hard_timeout", 0),
                   ShortField("priority", 0),
                   IntEnumField("buffer_id", "NO_BUFFER", ofp_buffer),
                   ShortEnumField("out_port", "NONE", ofp_port_no),
                   FlagsField("flags", 0, 16, ["SEND_FLOW_REM",
                                               "CHECK_OVERLAP",
                                               "EMERG"]),
                   PacketListField("actions", [], OFPAT,
                                   ofp_action_cls,
                                   length_from=lambda pkt:pkt.len - 72)]


class OFPTPortMod(_ofp_header):
    name = "OFPT_PORT_MOD"
    fields_desc = [ByteEnumField("version", 0x01, ofp_version),
                   ByteEnumField("type", 15, ofp_type),
                   ShortField("len", None),
                   IntField("xid", 0),
                   ShortEnumField("port_no", 0, ofp_port_no),
                   MACField("hw_addr", "0"),
                   FlagsField("config", 0, 32, ofp_port_config),
                   FlagsField("mask", 0, 32, ofp_port_config),
                   FlagsField("advertise", 0, 32, ofp_port_features),
                   IntField("pad", 0)]


#####################################################
#                     OFPT_STATS                    #
#####################################################


ofp_stats_types = {0: "OFPST_DESC",
                   1: "OFPST_FLOW",
                   2: "OFPST_AGGREGATE",
                   3: "OFPST_TABLE",
                   4: "OFPST_PORT",
                   5: "OFPST_QUEUE",
                   65535: "OFPST_VENDOR"}


class OFPTStatsRequestDesc(_ofp_header):
    name = "OFPST_STATS_REQUEST_DESC"
    fields_desc = [ByteEnumField("version", 0x01, ofp_version),
                   ByteEnumField("type", 16, ofp_type),
                   ShortField("len", None),
                   IntField("xid", 0),
                   ShortEnumField("stats_type", 0, ofp_stats_types),
                   FlagsField("flags", 0, 16, [])]


class OFPTStatsReplyDesc(_ofp_header):
    name = "OFPST_STATS_REPLY_DESC"
    fields_desc = [ByteEnumField("version", 0x01, ofp_version),
                   ByteEnumField("type", 17, ofp_type),
                   ShortField("len", None),
                   IntField("xid", 0),
                   ShortEnumField("stats_type", 0, ofp_stats_types),
                   FlagsField("flags", 0, 16, []),
                   StrFixedLenField("mfr_desc", "", 256),
                   StrFixedLenField("hw_desc", "", 256),
                   StrFixedLenField("sw_desc", "", 256),
                   StrFixedLenField("serial_num", "", 32),
                   StrFixedLenField("dp_desc", "", 256)]


class OFPTStatsRequestFlow(_ofp_header):
    name = "OFPST_STATS_REQUEST_FLOW"
    fields_desc = [ByteEnumField("version", 0x01, ofp_version),
                   ByteEnumField("type", 16, ofp_type),
                   ShortField("len", None),
                   IntField("xid", 0),
                   ShortEnumField("stats_type", 1, ofp_stats_types),
                   FlagsField("flags", 0, 16, []),
                   PacketField("match", OFPMatch(), OFPMatch),
                   ByteEnumField("table_id", "ALL", ofp_table),
                   ByteField("pad", 0),
                   ShortEnumField("out_port", "NONE", ofp_port_no)]


class OFPFlowStats(Packet):
    name = "OFP_FLOW_STATS"
    fields_desc = [ShortField("length", None),
                   ByteField("table_id", 0),
                   XByteField("pad1", 0),
                   PacketField("match", OFPMatch(), OFPMatch),
                   IntField("duration_sec", 0),
                   IntField("duration_nsec", 0),
                   ShortField("priority", 0),
                   ShortField("idle_timeout", 0),
                   ShortField("hard_timeout", 0),
                   XBitField("pad2", 0, 48),
                   LongField("cookie", 0),
                   LongField("packet_count", 0),
                   LongField("byte_count", 0),
                   PacketListField("actions", [], OFPAT,
                                   ofp_action_cls,
                                   length_from=lambda pkt:pkt.length - 88)]

    def post_build(self, p, pay):
        if self.length is None:
            tmp_len = len(p) + len(pay)
            p = struct.pack("!H", tmp_len) + p[2:]
        return p + pay

    def extract_padding(self, s):
        return b"", s


class OFPTStatsReplyFlow(_ofp_header):
    name = "OFPST_STATS_REPLY_FLOW"
    fields_desc = [ByteEnumField("version", 0x01, ofp_version),
                   ByteEnumField("type", 17, ofp_type),
                   ShortField("len", None),
                   IntField("xid", 0),
                   ShortEnumField("stats_type", 1, ofp_stats_types),
                   FlagsField("flags", 0, 16, []),
                   PacketListField("flow_stats", [], OFPFlowStats,
                                   length_from=lambda pkt:pkt.len - 12)]  # noqa: E501


class OFPTStatsRequestAggregate(OFPTStatsRequestFlow):
    name = "OFPST_STATS_REQUEST_AGGREGATE"
    stats_type = 2


class OFPTStatsReplyAggregate(_ofp_header):
    name = "OFPST_STATS_REPLY_AGGREGATE"
    fields_desc = [ByteEnumField("version", 0x01, ofp_version),
                   ByteEnumField("type", 17, ofp_type),
                   ShortField("len", None),
                   IntField("xid", 0),
                   ShortEnumField("stats_type", 2, ofp_stats_types),
                   FlagsField("flags", 0, 16, []),
                   LongField("packet_count", 0),
                   LongField("byte_count", 0),
                   IntField("flow_count", 0),
                   XIntField("pad", 0)]


class OFPTStatsRequestTable(_ofp_header):
    name = "OFPST_STATS_REQUEST_TABLE"
    fields_desc = [ByteEnumField("version", 0x01, ofp_version),
                   ByteEnumField("type", 16, ofp_type),
                   ShortField("len", None),
                   IntField("xid", 0),
                   ShortEnumField("stats_type", 3, ofp_stats_types),
                   FlagsField("flags", 0, 16, [])]


class OFPTableStats(Packet):

    def extract_padding(self, s):
        return b"", s

    name = "OFP_TABLE_STATS"
    fields_desc = [ByteField("table_id", 0),
                   X3BytesField("pad", 0),
                   StrFixedLenField("name", "", 32),
                   FlagsField("wildcards1", 0x003, 12, ["DL_VLAN_PCP",
                                                        "NW_TOS"]),
                   BitField("nw_dst_mask", 63, 6),        # 32 would be enough
                   BitField("nw_src_mask", 63, 6),
                   FlagsField("wildcards2", 0xff, 8, ["IN_PORT",
                                                      "DL_VLAN",
                                                      "DL_SRC",
                                                      "DL_DST",
                                                      "DL_TYPE",
                                                      "NW_PROTO",
                                                      "TP_SRC",
                                                      "TP_DST"]),
                   IntField("max_entries", 0),
                   IntField("active_count", 0),
                   LongField("lookup_count", 0),
                   LongField("matched_count", 0)]


class OFPTStatsReplyTable(_ofp_header):
    name = "OFPST_STATS_REPLY_TABLE"
    fields_desc = [ByteEnumField("version", 0x01, ofp_version),
                   ByteEnumField("type", 17, ofp_type),
                   ShortField("len", None),
                   IntField("xid", 0),
                   ShortEnumField("stats_type", 3, ofp_stats_types),
                   FlagsField("flags", 0, 16, []),
                   PacketListField("table_stats", [], OFPTableStats,
                                   length_from=lambda pkt:pkt.len - 12)]


class OFPTStatsRequestPort(_ofp_header):
    name = "OFPST_STATS_REQUEST_PORT"
    fields_desc = [ByteEnumField("version", 0x01, ofp_version),
                   ByteEnumField("type", 16, ofp_type),
                   ShortField("len", None),
                   IntField("xid", 0),
                   ShortEnumField("stats_type", 4, ofp_stats_types),
                   FlagsField("flags", 0, 16, []),
                   ShortEnumField("port_no", "NONE", ofp_port_no),
                   XBitField("pad", 0, 48)]


class OFPPortStats(Packet):

    def extract_padding(self, s):
        return b"", s

    name = "OFP_PORT_STATS"
    fields_desc = [ShortEnumField("port_no", 0, ofp_port_no),
                   XBitField("pad", 0, 48),
                   LongField("rx_packets", 0),
                   LongField("tx_packets", 0),
                   LongField("rx_bytes", 0),
                   LongField("tx_bytes", 0),
                   LongField("rx_dropped", 0),
                   LongField("tx_dropped", 0),
                   LongField("rx_errors", 0),
                   LongField("tx_errors", 0),
                   LongField("rx_frame_err", 0),
                   LongField("rx_over_err", 0),
                   LongField("rx_crc_err", 0),
                   LongField("collisions", 0)]


class OFPTStatsReplyPort(_ofp_header):
    name = "OFPST_STATS_REPLY_TABLE"
    fields_desc = [ByteEnumField("version", 0x01, ofp_version),
                   ByteEnumField("type", 17, ofp_type),
                   ShortField("len", None),
                   IntField("xid", 0),
                   ShortEnumField("stats_type", 4, ofp_stats_types),
                   FlagsField("flags", 0, 16, []),
                   PacketListField("port_stats", [], OFPPortStats,
                                   length_from=lambda pkt:pkt.len - 12)]


class OFPTStatsRequestQueue(_ofp_header):
    name = "OFPST_STATS_REQUEST_QUEUE"
    fields_desc = [ByteEnumField("version", 0x01, ofp_version),
                   ByteEnumField("type", 16, ofp_type),
                   ShortField("len", None),
                   IntField("xid", 0),
                   ShortEnumField("stats_type", 5, ofp_stats_types),
                   FlagsField("flags", 0, 16, []),
                   ShortEnumField("port_no", "NONE", ofp_port_no),
                   XShortField("pad", 0),
                   IntEnumField("queue_id", "ALL", ofp_queue)]


class OFPTStatsReplyQueue(_ofp_header):
    name = "OFPST_STATS_REPLY_QUEUE"
    fields_desc = [ByteEnumField("version", 0x01, ofp_version),
                   ByteEnumField("type", 17, ofp_type),
                   ShortField("len", None),
                   IntField("xid", 0),
                   ShortEnumField("stats_type", 5, ofp_stats_types),
                   FlagsField("flags", 0, 16, []),
                   ShortEnumField("port_no", "NONE", ofp_port_no),
                   XShortField("pad", 0),
                   IntEnumField("queue_id", "ALL", ofp_queue),
                   LongField("tx_bytes", 0),
                   LongField("tx_packets", 0),
                   LongField("tx_errors", 0)]


class OFPTStatsRequestVendor(_ofp_header):
    name = "OFPST_STATS_REQUEST_VENDOR"
    fields_desc = [ByteEnumField("version", 0x01, ofp_version),
                   ByteEnumField("type", 16, ofp_type),
                   ShortField("len", None),
                   IntField("xid", 0),
                   ShortEnumField("stats_type", 6, ofp_stats_types),
                   FlagsField("flags", 0, 16, []),
                   IntField("vendor", 0)]


class OFPTStatsReplyVendor(_ofp_header):
    name = "OFPST_STATS_REPLY_VENDOR"
    fields_desc = [ByteEnumField("version", 0x01, ofp_version),
                   ByteEnumField("type", 17, ofp_type),
                   ShortField("len", None),
                   IntField("xid", 0),
                   ShortEnumField("stats_type", 6, ofp_stats_types),
                   FlagsField("flags", 0, 16, []),
                   IntField("vendor", 0)]


# ofp_stats_request/reply_cls allows generic method OpenFlow() (end of script)
# to choose the right class for dissection
ofp_stats_request_cls = {0: OFPTStatsRequestDesc,
                         1: OFPTStatsRequestFlow,
                         2: OFPTStatsRequestAggregate,
                         3: OFPTStatsRequestTable,
                         4: OFPTStatsRequestPort,
                         5: OFPTStatsRequestQueue,
                         65535: OFPTStatsRequestVendor}

ofp_stats_reply_cls = {0: OFPTStatsReplyDesc,
                       1: OFPTStatsReplyFlow,
                       2: OFPTStatsReplyAggregate,
                       3: OFPTStatsReplyTable,
                       4: OFPTStatsReplyPort,
                       5: OFPTStatsReplyQueue,
                       65535: OFPTStatsReplyVendor}

#                end of OFPT_STATS                  #


class OFPTBarrierRequest(_ofp_header):
    name = "OFPT_BARRIER_REQUEST"
    fields_desc = [ByteEnumField("version", 0x01, ofp_version),
                   ByteEnumField("type", 18, ofp_type),
                   ShortField("len", None),
                   IntField("xid", 0)]


class OFPTBarrierReply(_ofp_header):
    name = "OFPT_BARRIER_REPLY"
    fields_desc = [ByteEnumField("version", 0x01, ofp_version),
                   ByteEnumField("type", 19, ofp_type),
                   ShortField("len", None),
                   IntField("xid", 0)]


class OFPTQueueGetConfigRequest(_ofp_header):
    name = "OFPT_QUEUE_GET_CONFIG_REQUEST"
    fields_desc = [ByteEnumField("version", 0x01, ofp_version),
                   ByteEnumField("type", 20, ofp_type),
                   ShortField("len", None),
                   IntField("xid", 0),
                   ShortEnumField("port", 0, ofp_port_no),
                   XShortField("pad", 0)]


class OFPTQueueGetConfigReply(_ofp_header):
    name = "OFPT_QUEUE_GET_CONFIG_REPLY"
    fields_desc = [ByteEnumField("version", 0x01, ofp_version),
                   ByteEnumField("type", 21, ofp_type),
                   ShortField("len", None),
                   IntField("xid", 0),
                   ShortEnumField("port", 0, ofp_port_no),
                   XBitField("pad", 0, 48),
                   PacketListField("queues", [], OFPPacketQueue,
                                   length_from=lambda pkt:pkt.len - 16)]


# ofpt_cls allows generic method OpenFlow() to choose the right class for dissection  # noqa: E501
ofpt_cls = {0: OFPTHello,
            # 1: OFPTError,
            2: OFPTEchoRequest,
            3: OFPTEchoReply,
            4: OFPTVendor,
            5: OFPTFeaturesRequest,
            6: OFPTFeaturesReply,
            7: OFPTGetConfigRequest,
            8: OFPTGetConfigReply,
            9: OFPTSetConfig,
            10: OFPTPacketIn,
            11: OFPTFlowRemoved,
            12: OFPTPortStatus,
            13: OFPTPacketOut,
            14: OFPTFlowMod,
            15: OFPTPortMod,
            # 16: OFPTStatsRequest,
            # 17: OFPTStatsReply,
            18: OFPTBarrierRequest,
            19: OFPTBarrierReply,
            20: OFPTQueueGetConfigRequest,
            21: OFPTQueueGetConfigReply}


bind_bottom_up(TCP, OpenFlow, dport=6653)
bind_bottom_up(TCP, OpenFlow, sport=6653)
bind_bottom_up(TCP, OpenFlow, dport=6633)
bind_bottom_up(TCP, OpenFlow, sport=6633)

bind_top_down(TCP, _ofp_header, sport=6653, dport=6653)
