import struct
from socket import AF_INET, AF_INET6, AF_UNSPEC, inet_ntop, inet_pton

from pyroute2.common import AF_MPLS, hexdump, map_namespace
from pyroute2.netlink import nla, nla_string, nlmsg
from pyroute2.netlink.rtnl import rt_proto, rt_scope, rt_type

RTNH_F_DEAD = 1
RTNH_F_PERVASIVE = 2
RTNH_F_ONLINK = 4
RTNH_F_OFFLOAD = 8
RTNH_F_LINKDOWN = 16
(RTNH_F_NAMES, RTNH_F_VALUES) = map_namespace('RTNH_F_', globals())

RT_SCOPE_UNIVERSE = 0
RT_SCOPE_SITE = 200
RT_SCOPE_LINK = 253
RT_SCOPE_HOST = 254
RT_SCOPE_NOWHERE = 255
(RT_SCOPE_NAMES, RT_SCOPE_VALUES) = map_namespace('RT_SCOPE_', globals())

LWTUNNEL_ENCAP_NONE = 0
LWTUNNEL_ENCAP_MPLS = 1
LWTUNNEL_ENCAP_IP = 2
LWTUNNEL_ENCAP_ILA = 3
LWTUNNEL_ENCAP_IP6 = 4
LWTUNNEL_ENCAP_SEG6 = 5
LWTUNNEL_ENCAP_BPF = 6
LWTUNNEL_ENCAP_SEG6_LOCAL = 7

IP6_RT_PRIO_USER = 1024


class nlflags(object):
    def encode(self):
        if isinstance(self['flags'], str):
            self['flags'] = self['flags'].split(',')
        if isinstance(self['flags'], (set, tuple, list)):
            self['flags'] = self.names2flags(self['flags'])
        if isinstance(self.get('scope'), str):
            self['scope'] = self.name2scope(self['scope'])
        return super(nlflags, self).encode()

    @staticmethod
    def scope2name(scope):
        return RT_SCOPE_VALUES[scope].lower()[9:]

    @staticmethod
    def name2scope(scope):
        return RT_SCOPE_NAMES['RT_SCOPE_' + scope.upper()]

    @staticmethod
    def flags2names(flags):
        ret = []
        for flag in RTNH_F_VALUES:
            if (flag & flags) == flag:
                ret.append(RTNH_F_VALUES[flag].lower()[7:])
        return ret

    @staticmethod
    def names2flags(flags):
        ret = 0
        if isinstance(flags, str):
            flags = [flags]
        for flag in flags:
            ret |= RTNH_F_NAMES['RTNH_F_' + flag.upper()]
        return ret


class rtmsg_base(nlflags):
    '''
    Route message

    C structure::

        struct rtmsg {
            unsigned char rtm_family;   /* Address family of route */
            unsigned char rtm_dst_len;  /* Length of destination */
            unsigned char rtm_src_len;  /* Length of source */
            unsigned char rtm_tos;      /* TOS filter */

            unsigned char rtm_table;    /* Routing table ID */
            unsigned char rtm_protocol; /* Routing protocol; see below */
            unsigned char rtm_scope;    /* See below */
            unsigned char rtm_type;     /* See below */

            unsigned int  rtm_flags;
        };
    '''

    __slots__ = ()

    prefix = 'RTA_'
    sql_constraints = {
        'RTA_TABLE': 'NOT NULL DEFAULT 0',
        'RTA_DST': "NOT NULL DEFAULT ''",
        'RTA_OIF': 'NOT NULL DEFAULT 0',
        'RTA_PRIORITY': 'NOT NULL DEFAULT 0',
        'RTA_VIA': "NOT NULL DEFAULT ''",
        'RTA_NEWDST': "NOT NULL DEFAULT ''",
    }

    defaults = {
        'family': AF_INET,
        'scope': rt_scope['universe'],
        'type': rt_type['unspec'],
        'proto': rt_proto['unspec'],
    }

    field_policies = {('field', 'table'): lambda x: 252 if x > 255 else x}

    fields = (
        ('family', 'B'),
        ('dst_len', 'B'),
        ('src_len', 'B'),
        ('tos', 'B'),
        ('table', 'B'),
        ('proto', 'B'),
        ('scope', 'B'),
        ('type', 'B'),
        ('flags', 'I'),
    )

    nla_map = (
        ('RTA_UNSPEC', 'none'),
        ('RTA_DST', 'target'),
        ('RTA_SRC', 'target'),
        ('RTA_IIF', 'uint32'),
        ('RTA_OIF', 'uint32'),
        ('RTA_GATEWAY', 'target'),
        ('RTA_PRIORITY', 'uint32'),
        ('RTA_PREFSRC', 'target'),
        ('RTA_METRICS', 'metrics'),
        ('RTA_MULTIPATH', '*get_nh'),
        ('RTA_PROTOINFO', 'uint32'),
        ('RTA_FLOW', 'uint32'),
        ('RTA_CACHEINFO', 'cacheinfo'),
        ('RTA_SESSION', 'hex'),
        ('RTA_MP_ALGO', 'hex'),
        ('RTA_TABLE', 'uint32'),
        ('RTA_MARK', 'uint32'),
        ('RTA_MFC_STATS', 'rta_mfc_stats'),
        ('RTA_VIA', 'rtvia'),
        ('RTA_NEWDST', 'target'),
        ('RTA_PREF', 'uint8'),
        ('RTA_ENCAP_TYPE', 'uint16'),
        ('RTA_ENCAP', 'encap_info'),
        ('RTA_EXPIRES', 'hex'),
    )

    @staticmethod
    def encap_info(self, *argv, **kwarg):
        encap_type = None

        # Check, if RTA_ENCAP_TYPE is decoded already
        #
        for name, value in self['attrs']:
            if name == 'RTA_ENCAP_TYPE':
                encap_type = value
                break
        else:
            # No RTA_ENCAP_TYPE met, so iterate all the chain.
            # Ugly, but to do otherwise would be too complicated.
            #
            data = kwarg['data']
            offset = kwarg['offset']
            while offset < len(data):
                # Shift offset to the next NLA
                # NLA header:
                #
                # uint16 length
                # uint16 type
                #
                try:
                    offset += struct.unpack('H', data[offset : offset + 2])[0]
                    # 21 == RTA_ENCAP_TYPE
                    # FIXME: should not be hardcoded
                    if (
                        struct.unpack('H', data[offset + 2 : offset + 4])[0]
                        == 21
                    ):
                        encap_type = struct.unpack(
                            'H', data[offset + 4 : offset + 6]
                        )[0]
                        break
                except:
                    # in the case of any decoding error return self.hex
                    break

        # return specific classes
        #
        return self.encaps.get(encap_type, self.hex)

    class mpls_encap_info(nla):
        prefix = 'MPLS_IPTUNNEL_'
        __slots__ = ()

        nla_map = (
            ('MPLS_IPTUNNEL_UNSPEC', 'none'),
            ('MPLS_IPTUNNEL_DST', 'mpls_target'),
            ('MPLS_IPTUNNEL_TTL', 'uint8'),
        )

    class seg6_encap_info(nla):
        __slots__ = ()

        nla_map = (
            ('SEG6_IPTUNNEL_UNSPEC', 'none'),
            ('SEG6_IPTUNNEL_SRH', 'ipv6_sr_hdr'),
        )

        class ipv6_sr_hdr(nla):
            __slots__ = ()

            fields = (
                ('encapmode', 'I'),
                ('nexthdr', 'B'),
                ('hdrlen', 'B'),
                ('type', 'B'),
                ('segments_left', 'B'),
                ('first_segment', 'B'),
                ('flags', 'B'),
                ('reserved', 'H'),
                ('segs', 's'),
                # Potentially several type-length-value
                ('tlvs', 's'),
            )

            # Corresponding values for seg6 encap modes
            SEG6_IPTUN_MODE_INLINE = 0
            SEG6_IPTUN_MODE_ENCAP = 1

            # Mapping string to nla value
            encapmodes = {
                "inline": SEG6_IPTUN_MODE_INLINE,
                "encap": SEG6_IPTUN_MODE_ENCAP,
            }

            # Reverse mapping: mapping nla value to string
            r_encapmodes = {v: k for k, v in encapmodes.items()}

            # Nla value for seg6 type
            SEG6_TYPE = 4

            # Flag value for hmac
            SR6_FLAG1_HMAC = 1 << 3

            # Tlv value for hmac
            SR6_TLV_HMAC = 5

            # Utility function to get the family from the msg
            def get_family(self):
                pointer = self
                while pointer.parent is not None:
                    pointer = pointer.parent
                return pointer.get('family', AF_UNSPEC)

            def encode(self):
                # Retrieve the family
                family = self.get_family()
                # Seg6 can be applied only to IPv6 and IPv4
                if family == AF_INET6 or family == AF_INET:
                    # Get mode
                    mode = self['mode']
                    # Get segs
                    segs = self['segs']
                    # Get hmac
                    hmac = self.get('hmac', None)
                    # With "inline" mode there is not
                    # encap into an outer IPv6 header
                    if mode == "inline":
                        # Add :: to segs
                        segs.insert(0, "::")
                    # Add mode to value
                    self['encapmode'] = self.encapmodes.get(
                        mode, self.SEG6_IPTUN_MODE_ENCAP
                    )
                    # Calculate srlen
                    srhlen = 8 + 16 * len(segs)
                    # If we are using hmac we have a tlv as trailer data
                    if hmac:
                        # Since we can use sha1 or sha256
                        srhlen += 40
                    # Calculate and set hdrlen
                    self['hdrlen'] = (srhlen >> 3) - 1
                    # Add seg6 type
                    self['type'] = self.SEG6_TYPE
                    # Add segments left
                    self['segments_left'] = len(segs) - 1
                    # Add fitst segment
                    self['first_segment'] = len(segs) - 1
                    # If hmac is used we have to set the flags
                    if hmac:
                        # Add SR6_FLAG1_HMAC
                        self['flags'] |= self.SR6_FLAG1_HMAC
                    # Init segs
                    self['segs'] = b''
                    # Iterate over segments
                    for seg in segs:
                        # Convert to network byte order and add to value
                        self['segs'] += inet_pton(AF_INET6, seg)
                    # Initialize tlvs
                    self['tlvs'] = b''
                    # If hmac is used we have to properly init tlvs
                    if hmac:
                        # Put type
                        self['tlvs'] += struct.pack('B', self.SR6_TLV_HMAC)
                        # Put length -> 40-2
                        self['tlvs'] += struct.pack('B', 38)
                        # Put reserved
                        self['tlvs'] += struct.pack('H', 0)
                        # Put hmac key
                        self['tlvs'] += struct.pack('>I', hmac)
                        # Put hmac
                        self['tlvs'] += struct.pack('QQQQ', 0, 0, 0, 0)
                else:
                    raise TypeError(
                        'Family %s not supported for seg6 tunnel' % family
                    )
                # Finally encode as nla
                nla.encode(self)

            # Utility function to verify if hmac is present
            def has_hmac(self):
                # Useful during the decoding
                return self['flags'] & self.SR6_FLAG1_HMAC

            def decode(self):
                # Decode the data
                nla.decode(self)
                # Extract the encap mode
                self['mode'] = self.r_encapmodes.get(
                    self['encapmode'], "encap"
                )
                # Calculate offset of the segs
                offset = self.offset + 16
                # Point the addresses
                addresses = self.data[offset:]
                # Extract the number of segs
                n_segs = self['segments_left'] + 1
                # Init segs
                segs = []
                # Move 128 bit in each step
                for i in range(n_segs):
                    # Save the segment
                    segs.append(
                        inet_ntop(AF_INET6, addresses[i * 16 : i * 16 + 16])
                    )
                # Save segs
                self['segs'] = segs
                # Init tlvs
                self['tlvs'] = ''
                # If hmac is used
                if self.has_hmac():
                    # Point to the start of hmac
                    hmac = addresses[n_segs * 16 : n_segs * 16 + 40]
                    # Save tlvs section
                    self['tlvs'] = hexdump(hmac)
                    # Show also the hmac key
                    self['hmac'] = hexdump(hmac[4:8])

    class bpf_encap_info(nla):
        __slots__ = ()

        nla_map = (
            ('LWT_BPF_UNSPEC', 'none'),
            ('LWT_BPF_IN', 'bpf_obj'),
            ('LWT_BPF_OUT', 'bpf_obj'),
            ('LWT_BPF_XMIT', 'bpf_obj'),
            ('LWT_BPF_XMIT_HEADROOM', 'uint32'),
        )

        class bpf_obj(nla):
            __slots__ = ()

            nla_map = (
                ('LWT_BPF_PROG_UNSPEC', 'none'),
                ('LWT_BPF_PROG_FD', 'uint32'),
                ('LWT_BPF_PROG_NAME', 'asciiz'),
            )

    class seg6local_encap_info(nla):
        __slots__ = ()

        nla_map = (
            ('SEG6_LOCAL_UNSPEC', 'none'),
            ('SEG6_LOCAL_ACTION', 'action'),
            ('SEG6_LOCAL_SRH', 'ipv6_sr_hdr'),
            ('SEG6_LOCAL_TABLE', 'table'),
            ('SEG6_LOCAL_NH4', 'nh4'),
            ('SEG6_LOCAL_NH6', 'nh6'),
            ('SEG6_LOCAL_IIF', 'iif'),
            ('SEG6_LOCAL_OIF', 'oif'),
            ('SEG6_LOCAL_BPF', 'bpf_obj'),
            ('SEG6_LOCAL_VRFTABLE', 'vrf_table'),
        )

        class bpf_obj(nla):
            __slots__ = ()

            nla_map = (
                ('LWT_BPF_PROG_UNSPEC', 'none'),
                ('LWT_BPF_PROG_FD', 'uint32'),
                ('LWT_BPF_PROG_NAME', 'asciiz'),
            )

        class ipv6_sr_hdr(nla):
            __slots__ = ()

            fields = (
                ('nexthdr', 'B'),
                ('hdrlen', 'B'),
                ('type', 'B'),
                ('segments_left', 'B'),
                ('first_segment', 'B'),
                ('flags', 'B'),
                ('reserved', 'H'),
                ('segs', 's'),
                # Potentially several type-length-value
                ('tlvs', 's'),
            )

            # Corresponding values for seg6 encap modes
            SEG6_IPTUN_MODE_INLINE = 0
            SEG6_IPTUN_MODE_ENCAP = 1

            # Mapping string to nla value
            encapmodes = {
                "inline": SEG6_IPTUN_MODE_INLINE,
                "encap": SEG6_IPTUN_MODE_ENCAP,
            }

            # Reverse mapping: mapping nla value to string
            r_encapmodes = {v: k for k, v in encapmodes.items()}

            # Nla value for seg6 type
            SEG6_TYPE = 4

            # Flag value for hmac
            SR6_FLAG1_HMAC = 1 << 3

            # Tlv value for hmac
            SR6_TLV_HMAC = 5

            # Utility function to get the family from the msg
            def get_family(self):
                pointer = self
                while pointer.parent is not None:
                    pointer = pointer.parent
                return pointer.get('family', AF_UNSPEC)

            def encode(self):
                # Retrieve the family
                family = self.get_family()
                # Seg6 can be applied only to IPv6
                if family == AF_INET6:
                    # Get mode
                    mode = self['mode']
                    # Get segs
                    segs = self['segs']
                    # Get hmac
                    hmac = self.get('hmac', None)
                    # With "inline" mode there is not
                    # encap into an outer IPv6 header
                    if mode == "inline":
                        # Add :: to segs
                        segs.insert(0, "::")
                    # Add mode to value
                    self['encapmode'] = self.encapmodes.get(
                        mode, self.SEG6_IPTUN_MODE_ENCAP
                    )
                    # Calculate srlen
                    srhlen = 8 + 16 * len(segs)
                    # If we are using hmac we have a tlv as trailer data
                    if hmac:
                        # Since we can use sha1 or sha256
                        srhlen += 40
                    # Calculate and set hdrlen
                    self['hdrlen'] = (srhlen >> 3) - 1
                    # Add seg6 type
                    self['type'] = self.SEG6_TYPE
                    # Add segments left
                    self['segments_left'] = len(segs) - 1
                    # Add fitst segment
                    self['first_segment'] = len(segs) - 1
                    # If hmac is used we have to set the flags
                    if hmac:
                        # Add SR6_FLAG1_HMAC
                        self['flags'] |= self.SR6_FLAG1_HMAC
                    # Init segs
                    self['segs'] = b''
                    # Iterate over segments
                    for seg in segs:
                        # Convert to network byte order and add to value
                        self['segs'] += inet_pton(family, seg)
                    # Initialize tlvs
                    self['tlvs'] = b''
                    # If hmac is used we have to properly init tlvs
                    if hmac:
                        # Put type
                        self['tlvs'] += struct.pack('B', self.SR6_TLV_HMAC)
                        # Put length -> 40-2
                        self['tlvs'] += struct.pack('B', 38)
                        # Put reserved
                        self['tlvs'] += struct.pack('H', 0)
                        # Put hmac key
                        self['tlvs'] += struct.pack('>I', hmac)
                        # Put hmac
                        self['tlvs'] += struct.pack('QQQQ', 0, 0, 0, 0)
                else:
                    raise TypeError(
                        'Family %s not supported for seg6 tunnel' % family
                    )
                # Finally encode as nla
                nla.encode(self)

            # Utility function to verify if hmac is present
            def has_hmac(self):
                # Useful during the decoding
                return self['flags'] & self.SR6_FLAG1_HMAC

            def decode(self):
                # Decode the data
                nla.decode(self)
                # Extract the encap mode
                self['mode'] = self.r_encapmodes.get(
                    self['encapmode'], "encap"
                )
                # Calculate offset of the segs
                offset = self.offset + 16
                # Point the addresses
                addresses = self.data[offset:]
                # Extract the number of segs
                n_segs = self['segments_left'] + 1
                # Init segs
                segs = []
                # Move 128 bit in each step
                for i in range(n_segs):
                    # Save the segment
                    segs.append(
                        inet_ntop(AF_INET6, addresses[i * 16 : i * 16 + 16])
                    )
                # Save segs
                self['segs'] = segs
                # Init tlvs
                self['tlvs'] = ''
                # If hmac is used
                if self.has_hmac():
                    # Point to the start of hmac
                    hmac = addresses[n_segs * 16 : n_segs * 16 + 40]
                    # Save tlvs section
                    self['tlvs'] = hexdump(hmac)
                    # Show also the hmac key
                    self['hmac'] = hexdump(hmac[4:8])

        class table(nla):
            __slots__ = ()
            # Table ID
            fields = (('value', 'I'),)

        class action(nla):
            __slots__ = ()
            # Action
            fields = (('value', 'I'),)

            SEG6_LOCAL_ACTION_UNSPEC = 0
            SEG6_LOCAL_ACTION_END = 1
            SEG6_LOCAL_ACTION_END_X = 2
            SEG6_LOCAL_ACTION_END_T = 3
            SEG6_LOCAL_ACTION_END_DX2 = 4
            SEG6_LOCAL_ACTION_END_DX6 = 5
            SEG6_LOCAL_ACTION_END_DX4 = 6
            SEG6_LOCAL_ACTION_END_DT6 = 7
            SEG6_LOCAL_ACTION_END_DT4 = 8
            SEG6_LOCAL_ACTION_END_B6 = 9
            SEG6_LOCAL_ACTION_END_B6_ENCAP = 10
            SEG6_LOCAL_ACTION_END_BM = 11
            SEG6_LOCAL_ACTION_END_S = 12
            SEG6_LOCAL_ACTION_END_AS = 13
            SEG6_LOCAL_ACTION_END_AM = 14
            SEG6_LOCAL_ACTION_END_BPF = 15
            SEG6_LOCAL_ACTION_END_DT46 = 16

            actions = {
                'End': SEG6_LOCAL_ACTION_END,
                'End.X': SEG6_LOCAL_ACTION_END_X,
                'End.T': SEG6_LOCAL_ACTION_END_T,
                'End.DX2': SEG6_LOCAL_ACTION_END_DX2,
                'End.DX6': SEG6_LOCAL_ACTION_END_DX6,
                'End.DX4': SEG6_LOCAL_ACTION_END_DX4,
                'End.DT6': SEG6_LOCAL_ACTION_END_DT6,
                'End.DT4': SEG6_LOCAL_ACTION_END_DT4,
                'End.B6': SEG6_LOCAL_ACTION_END_B6,
                'End.B6.Encaps': SEG6_LOCAL_ACTION_END_B6_ENCAP,
                'End.BM': SEG6_LOCAL_ACTION_END_BM,
                'End.S': SEG6_LOCAL_ACTION_END_S,
                'End.AS': SEG6_LOCAL_ACTION_END_AS,
                'End.AM': SEG6_LOCAL_ACTION_END_AM,
                'End.BPF': SEG6_LOCAL_ACTION_END_BPF,
                'End.DT46': SEG6_LOCAL_ACTION_END_DT46,
            }

            def encode(self):
                # Get action type and convert string to value
                action = self['value']
                self['value'] = self.actions.get(
                    action, self.SEG6_LOCAL_ACTION_UNSPEC
                )
                # Convert action type to u32
                self['value'] = self['value'] & 0xFFFFFFFF
                # Finally encode as nla
                nla.encode(self)

        class iif(nla):
            __slots__ = ()

            # Index of the incoming interface
            fields = (('value', 'I'),)

        class oif(nla):
            __slots__ = ()

            # Index of the outcoming interface
            fields = (('value', 'I'),)

        class nh4(nla_string):
            __slots__ = ()

            # Nexthop of the IPv4 family

            def encode(self):
                # Convert to network byte order
                self['value'] = inet_pton(AF_INET, self['value'])
                # Finally encode as nla
                nla_string.encode(self)

            def decode(self):
                # Decode the data
                nla_string.decode(self)
                # Convert the packed IP address to its string representation
                self['value'] = inet_ntop(AF_INET, self['value'])

        class nh6(nla_string):
            __slots__ = ()

            # Nexthop of the IPv6 family

            def encode(self):
                # Convert to network byte order
                self['value'] = inet_pton(AF_INET6, self['value'])
                # Finally encode as nla
                nla_string.encode(self)

            def decode(self):
                # Decode the data
                nla_string.decode(self)
                # Convert the packed IP address to its string representation
                self['value'] = inet_ntop(AF_INET6, self['value'])

        class vrf_table(nla):
            __slots__ = ()

            # VRF Table ID
            fields = (('value', 'I'),)

    #
    # TODO: add here other lwtunnel types
    #
    encaps = {
        LWTUNNEL_ENCAP_MPLS: mpls_encap_info,
        LWTUNNEL_ENCAP_SEG6: seg6_encap_info,
        LWTUNNEL_ENCAP_BPF: bpf_encap_info,
        LWTUNNEL_ENCAP_SEG6_LOCAL: seg6local_encap_info,
    }

    class rta_mfc_stats(nla):
        __slots__ = ()

        fields = (
            ('mfcs_packets', 'uint64'),
            ('mfcs_bytes', 'uint64'),
            ('mfcs_wrong_if', 'uint64'),
        )

    class metrics(nla):
        __slots__ = ()

        prefix = 'RTAX_'
        nla_map = (
            ('RTAX_UNSPEC', 'none'),
            ('RTAX_LOCK', 'uint32'),
            ('RTAX_MTU', 'uint32'),
            ('RTAX_WINDOW', 'uint32'),
            ('RTAX_RTT', 'uint32'),
            ('RTAX_RTTVAR', 'uint32'),
            ('RTAX_SSTHRESH', 'uint32'),
            ('RTAX_CWND', 'uint32'),
            ('RTAX_ADVMSS', 'uint32'),
            ('RTAX_REORDERING', 'uint32'),
            ('RTAX_HOPLIMIT', 'uint32'),
            ('RTAX_INITCWND', 'uint32'),
            ('RTAX_FEATURES', 'uint32'),
            ('RTAX_RTO_MIN', 'uint32'),
            ('RTAX_INITRWND', 'uint32'),
            ('RTAX_QUICKACK', 'uint32'),
        )

    @staticmethod
    def get_nh(self, *argv, **kwarg):
        return nh

    class rtvia(nla_string):
        __slots__ = ()
        sql_type = 'TEXT'

        def encode(self):
            family = self.get('family', AF_UNSPEC)
            if family in (AF_INET, AF_INET6):
                addr = inet_pton(family, self['addr'])
            else:
                raise TypeError('Family %s not supported for RTA_VIA' % family)
            self['value'] = struct.pack('H', family) + addr
            nla_string.encode(self)

        def decode(self):
            nla_string.decode(self)
            family = struct.unpack('H', self['value'][:2])[0]
            addr = self['value'][2:]
            if addr:
                if (family == AF_INET and len(addr) == 4) or (
                    family == AF_INET6 and len(addr) == 16
                ):
                    addr = inet_ntop(family, addr)
                else:
                    addr = hexdump(addr)
            self.value = {'family': family, 'addr': addr}

    class cacheinfo(nla):
        __slots__ = ()

        fields = (
            ('rta_clntref', 'I'),
            ('rta_lastuse', 'I'),
            ('rta_expires', 'i'),
            ('rta_error', 'I'),
            ('rta_used', 'I'),
            ('rta_id', 'I'),
            ('rta_ts', 'I'),
            ('rta_tsage', 'I'),
        )


class rtmsg(rtmsg_base, nlmsg):
    __slots__ = ()

    def encode(self):
        if self.get('family') == AF_MPLS:
            # force fields
            self['dst_len'] = 20
            self['table'] = 254
            self['type'] = 1
            # assert NLA types
            for n in self.get('attrs', []):
                if n[0] not in (
                    'RTA_OIF',
                    'RTA_DST',
                    'RTA_VIA',
                    'RTA_NEWDST',
                    'RTA_MULTIPATH',
                ):
                    raise TypeError('Incorrect NLA type %s for AF_MPLS' % n[0])
        super(rtmsg_base, self).encode()


class nh(rtmsg_base, nla):
    __slots__ = ()

    is_nla = False

    sql_constraints = {}
    cell_header = (('length', 'H'),)
    fields = (('flags', 'B'), ('hops', 'B'), ('oif', 'i'))
