# 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

"""
NTP (Network Time Protocol).
References : RFC 5905, RC 1305, ntpd source code
"""

from __future__ import absolute_import
import struct
import time
import datetime

from scapy.packet import Packet, bind_layers
from scapy.fields import BitField, BitEnumField, ByteField, ByteEnumField, \
    XByteField, SignedByteField, FlagsField, ShortField, LEShortField, \
    IntField, LEIntField, FixedPointField, IPField, StrField, \
    StrFixedLenField, StrFixedLenEnumField, XStrFixedLenField, PacketField, \
    PacketLenField, PacketListField, FieldListField, ConditionalField, \
    PadField
from scapy.layers.inet6 import IP6Field
from scapy.layers.inet import UDP
from scapy.utils import issubtype, lhex
from scapy.compat import orb
from scapy.config import conf
import scapy.modules.six as six
from scapy.modules.six.moves import range


#############################################################################
# Constants
#############################################################################

_NTP_AUTH_MD5_MIN_SIZE = 68
_NTP_EXT_MIN_SIZE = 16
_NTP_HDR_WITH_EXT_MIN_SIZE = _NTP_AUTH_MD5_MIN_SIZE + _NTP_EXT_MIN_SIZE
_NTP_AUTH_MD5_TAIL_SIZE = 20
_NTP_AUTH_MD5_DGST_SIZE = 16
_NTP_PRIVATE_PACKET_MIN_SIZE = 8

# ntpd "Private" messages are the shortest
_NTP_PACKET_MIN_SIZE = _NTP_PRIVATE_PACKET_MIN_SIZE

_NTP_PRIVATE_REQ_PKT_TAIL_LEN = 28

# seconds between 01-01-1900 and 01-01-1970
_NTP_BASETIME = 2208988800

# include/ntp.h
_NTP_SHIFT = 8
_NTP_HASH_SIZE = 128


#############################################################################
#     Fields and utilities
#############################################################################

class XLEShortField(LEShortField):
    """
    XShortField which value is encoded in little endian.
    """

    def i2repr(self, pkt, x):
        return lhex(self.i2h(pkt, x))


class TimeStampField(FixedPointField):
    """
    This field handles the timestamp fields in the NTP header.
    """

    def __init__(self, name, default):
        FixedPointField.__init__(self, name, default, 64, 32)

    def i2repr(self, pkt, val):
        if val is None:
            return "--"
        val = self.i2h(pkt, val)
        if val < _NTP_BASETIME:
            return val
        return time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.gmtime(val - _NTP_BASETIME))  # noqa: E501

    def any2i(self, pkt, val):
        if isinstance(val, six.string_types):
            val = int(time.mktime(time.strptime(val))) + _NTP_BASETIME
        elif isinstance(val, datetime.datetime):
            val = int(val.strftime("%s")) + _NTP_BASETIME
        return FixedPointField.any2i(self, pkt, val)

    def i2m(self, pkt, val):
        if val is None:
            val = FixedPointField.any2i(self, pkt, time.time() + _NTP_BASETIME)
        return FixedPointField.i2m(self, pkt, val)


#############################################################################
#     NTP
#############################################################################

# RFC 5905 / Section 7.3
_leap_indicator = {
    0: "no warning",
    1: "last minute of the day has 61 seconds",
    2: "last minute of the day has 59 seconds",
    3: "unknown (clock unsynchronized)"
}


# RFC 5905 / Section 7.3
_ntp_modes = {
    0: "reserved",
    1: "symmetric active",
    2: "symmetric passive",
    3: "client",
    4: "server",
    5: "broadcast",
    6: "NTP control message",
    7: "reserved for private use"
}


# RFC 5905 / Section 7.3
_reference_identifiers = {
    "GOES": "Geosynchronous Orbit Environment Satellite",
    "GPS ": "Global Position System",
    "GAL ": "Galileo Positioning System",
    "PPS ": "Generic pulse-per-second",
    "IRIG": "Inter-Range Instrumentation Group",
    "WWVB": "LF Radio WWVB Ft. Collins, CO 60 kHz",
    "DCF ": "LF Radio DCF77 Mainflingen, DE 77.5 kHz",
    "HBG ": "LF Radio HBG Prangins, HB 75 kHz",
    "MSF ": "LF Radio MSF Anthorn, UK 60 kHz",
    "JJY ": "LF Radio JJY Fukushima, JP 40 kHz, Saga, JP 60 kHz",
    "LORC": "MF Radio LORAN C station, 100 kHz",
    "TDF ": "MF Radio Allouis, FR 162 kHz",
    "CHU ": "HF Radio CHU Ottawa, Ontario",
    "WWV ": "HF Radio WWV Ft. Collins, CO",
    "WWVH": "HF Radio WWVH Kauai, HI",
    "NIST": "NIST telephone modem",
    "ACTS": "NIST telephone modem",
    "USNO": "USNO telephone modem",
    "PTB ": "European telephone modem",
}


# RFC 5905 / Section 7.4
_kiss_codes = {
    "ACST": "The association belongs to a unicast server.",
    "AUTH": "Server authentication failed.",
    "AUTO": "Autokey sequence failed.",
    "BCST": "The association belongs to a broadcast server.",
    "CRYP": "Cryptographic authentication or identification failed.",
    "DENY": "Access denied by remote server.",
    "DROP": "Lost peer in symmetric mode.",
    "RSTR": "Access denied due to local policy.",
    "INIT": "The association has not yet synchronized for the first time.",
    "MCST": "The association belongs to a dynamically discovered server.",
    "NKEY": "No key found.",
    "RATE": "Rate exceeded.",
    "RMOT": "Alteration of association from a remote host running ntpdc."
}


# Used by _ntp_dispatcher to instantiate the appropriate class
def _ntp_dispatcher(payload):
    """
    Returns the right class for a given NTP packet.
    """
    # By default, calling NTP() will build a NTP packet as defined in RFC 5905
    # (see the code of NTPHeader). Use NTPHeader for extension fields and MAC.
    if payload is None:
        return NTPHeader
    else:
        length = len(payload)
        if length >= _NTP_PACKET_MIN_SIZE:
            first_byte = orb(payload[0])
            # Extract NTP mode
            mode = first_byte & 7
            return {6: NTPControl, 7: NTPPrivate}.get(mode, NTPHeader)
    return conf.raw_layer


class NTP(Packet):
    """
    Base class that allows easier instantiation of a NTP packet from binary
    data.
    """

    @classmethod
    def dispatch_hook(cls, _pkt=None, *args, **kargs):
        """
        Returns the right class for the given data.
        """

        return _ntp_dispatcher(_pkt)

    def pre_dissect(self, s):
        """
        Check that the payload is long enough to build a NTP packet.
        """
        length = len(s)
        if length < _NTP_PACKET_MIN_SIZE:
            err = " ({}".format(length) + " is < _NTP_PACKET_MIN_SIZE "
            err += "({})).".format(_NTP_PACKET_MIN_SIZE)
            raise _NTPInvalidDataException(err)
        return s

    # NTPHeader, NTPControl and NTPPrivate are NTP packets.
    # This might help, for example when reading a pcap file.
    def haslayer(self, cls):
        """Specific: NTPHeader().haslayer(NTP) should return True."""
        if cls == "NTP":
            if isinstance(self, NTP):
                return True
        elif issubtype(cls, NTP):
            if isinstance(self, cls):
                return True
        return super(NTP, self).haslayer(cls)

    def getlayer(self, cls, nb=1, _track=None, _subclass=True, **flt):
        return super(NTP, self).getlayer(cls, nb=nb, _track=_track,
                                         _subclass=True, **flt)

    def mysummary(self):
        return self.sprintf("NTP v%ir,NTP.version%, %NTP.mode%")


class _NTPAuthenticatorPaddingField(StrField):
    """
    StrField handling the padding that may be found before the
    "authenticator" field.
    """

    def getfield(self, pkt, s):
        ret = None
        remain = s
        length = len(s)

        if length > _NTP_AUTH_MD5_TAIL_SIZE:
            start = length - _NTP_AUTH_MD5_TAIL_SIZE
            ret = s[:start]
            remain = s[start:]
        return remain, ret


class NTPAuthenticator(Packet):
    """
    Packet handling the "authenticator" part of a NTP packet, as
    defined in RFC 5905.
    """

    name = "Authenticator"
    fields_desc = [
        _NTPAuthenticatorPaddingField("padding", ""),
        IntField("key_id", 0),
        XStrFixedLenField("dgst", "", length_from=lambda x: 16)
    ]

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


class NTPExtension(Packet):
    """
    Packet handling a NTPv4 extension.
    """

    #########################################################################
    #
    # RFC 7822
    #########################################################################
    #
    # 7.5.  NTP Extension Field Format
    #
    #    In NTPv3, one or more extension fields can be inserted after the
    #    header and before the MAC, if a MAC is present.
    #
    #    Other than defining the field format, this document makes no use
    #    of the field contents.  An extension field contains a request or
    #    response message in the format shown in Figure 14.
    #
    #     0                   1                   2                   3
    #     0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
    #    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    #    |          Field Type           |            Length             |
    #    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    #    .                                                               .
    #    .                            Value                              .
    #    .                                                               .
    #    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    #    |                       Padding (as needed)                     |
    #    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    #
    #                    Figure 14: Extension Field Format
    #
    #
    #    All extension fields are zero-padded to a word (four octets)
    #    boundary.
    #########################################################################
    #

    name = "extension"
    fields_desc = [
        ShortField("type", 0),
        ShortField("len", 0),
        PadField(PacketField("value", "", Packet), align=4, padwith=b"\x00")
    ]


class NTPExtPacketListField(PacketListField):

    """
    PacketListField handling NTPv4 extensions (NTPExtension list).
    """

    def m2i(self, pkt, m):
        ret = None
        if len(m) >= 16:
            ret = NTPExtension(m)
        else:
            ret = conf.raw_layer(m)
        return ret

    def getfield(self, pkt, s):
        lst = []
        remain = s
        length = len(s)
        if length > _NTP_AUTH_MD5_TAIL_SIZE:
            end = length - _NTP_AUTH_MD5_TAIL_SIZE
            extensions = s[:end]
            remain = s[end:]

            extensions_len = len(extensions)
            while extensions_len >= 16:
                ext_len = struct.unpack("!H", extensions[2:4])[0]
                ext_len = min(ext_len, extensions_len)
                if ext_len < 1:
                    ext_len = extensions_len
                current = extensions[:ext_len]
                extensions = extensions[ext_len:]
                current_packet = self.m2i(pkt, current)
                lst.append(current_packet)
                extensions_len = len(extensions)

            if extensions_len > 0:
                lst.append(self.m2i(pkt, extensions))

        return remain, lst


class NTPExtensions(Packet):
    """
    Packet handling the NTPv4 extensions and the "MAC part" of the packet.
    """

    #########################################################################
    #
    # RFC 5905 / RFC 7822
    #########################################################################
    #
    # 7.5. NTP Extension Field Format
    #
    # In NTPv4, one or more extension fields can be inserted after the
    # header and before the MAC, if a MAC is present.
    #########################################################################
    #

    name = "NTPv4 extensions"
    fields_desc = [
        NTPExtPacketListField("extensions", [], Packet),
        PacketField("mac", NTPAuthenticator(), NTPAuthenticator)
    ]


class NTPHeader(NTP):

    """
    Packet handling the RFC 5905 NTP packet.
    """

    #########################################################################
    #
    # RFC 5905
    #########################################################################
    #
    #   0                   1                   2                   3
    #   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
    #  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    #  |LI | VN  |Mode |    Stratum     |     Poll      |  Precision   |
    #  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    #  |                         Root Delay                            |
    #  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    #  |                         Root Dispersion                       |
    #  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    #  |                          Reference ID                         |
    #  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    #  |                                                               |
    #  +                     Reference Timestamp (64)                  +
    #  |                                                               |
    #  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    #  |                                                               |
    #  +                      Origin Timestamp (64)                    +
    #  |                                                               |
    #  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    #  |                                                               |
    #  +                      Receive Timestamp (64)                   +
    #  |                                                               |
    #  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    #  |                                                               |
    #  +                      Transmit Timestamp (64)                  +
    #  |                                                               |
    #  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    #  |                                                               |
    #  .                                                               .
    #  .                    Extension Field 1 (variable)               .
    #  .                                                               .
    #  |                                                               |
    #  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    #  |                                                               |
    #  .                                                               .
    #  .                    Extension Field 2 (variable)               .
    #  .                                                               .
    #  |                                                               |
    #  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    #  |                          Key Identifier                       |
    #  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    #  |                                                               |
    #  |                            dgst (128)                         |
    #  |                                                               |
    #  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    #
    #                  Figure 8: Packet Header Format
    #########################################################################
    #

    name = "NTPHeader"
    fields_desc = [
        BitEnumField("leap", 0, 2, _leap_indicator),
        BitField("version", 4, 3),
        BitEnumField("mode", 3, 3, _ntp_modes),
        BitField("stratum", 2, 8),
        BitField("poll", 0xa, 8),
        BitField("precision", 0, 8),
        FixedPointField("delay", 0, size=32, frac_bits=16),
        FixedPointField("dispersion", 0, size=32, frac_bits=16),
        ConditionalField(IPField("id", "127.0.0.1"), lambda p: p.stratum > 1),
        ConditionalField(
            StrFixedLenEnumField(
                "ref_id",
                "",
                length=4,
                enum=_reference_identifiers
            ),
            lambda p: p.stratum < 2
        ),
        TimeStampField("ref", 0),
        TimeStampField("orig", None),
        TimeStampField("recv", 0),
        TimeStampField("sent", None),
    ]

    def guess_payload_class(self, payload):
        """
        Handles NTPv4 extensions and MAC part (when authentication is used.)
        """
        plen = len(payload)

        if plen > _NTP_AUTH_MD5_TAIL_SIZE:
            return NTPExtensions
        elif plen == _NTP_AUTH_MD5_TAIL_SIZE:
            return NTPAuthenticator

        return Packet.guess_payload_class(self, payload)


class _NTPInvalidDataException(Exception):
    """
    Raised when it is not possible to instantiate a NTP packet with the
    given data.
    """

    def __init__(self, details):
        Exception.__init__(
            self,
            "Data does not seem to be a valid NTP message" + details
        )


##############################################################################
#     Private (mode 7)
##############################################################################

# Operation codes
_op_codes = {
    0: "CTL_OP_UNSPEC",
    1: "CTL_OP_READSTAT",
    2: "CTL_OP_READVAR",
    3: "CTL_OP_WRITEVAR",
    4: "CTL_OP_READCLOCK",
    5: "CTL_OP_WRITECLOCK",
    6: "CTL_OP_SETTRAP",
    7: "CTL_OP_ASYNCMSG",
    8: "CTL_OP_CONFIGURE",
    9: "CTL_OP_SAVECONFIG",
    10: "CTL_OP_READ_MRU",
    11: "CTL_OP_READ_ORDLIST_A",
    12: "CTL_OP_REQ_NONCE",
    31: "CTL_OP_UNSETTRAP"
}


# System status words
_system_statuses = {
    0: "no warning",
    1: "last minute was 61 seconds",
    2: "last minute was 59 seconds",
    3: "alarm condition (clock not synchronized)"
}


_clock_sources = {
    0: "unspecified or unknown",
    1: " Calibrated atomic clock",
    2: "VLF (band 4) or LF (band 5) radio",
    3: "HF (band 7) radio",
    4: "UHF (band 9) satellite",
    5: "local net",
    6: "UDP/NTP",
    7: "UDP/TIME",
    8: "eyeball-and-wristwatch",
    9: "telephone modem"
}


_system_event_codes = {
    0: "unspecified",
    1: "system restart",
    2: "system or hardware fault",
    3: "system new status word (leap bits or synchronization change)",
    4: "system new synchronization source or stratum (sys.peer or sys.stratum change)",  # noqa: E501
    5: "system clock reset (offset correction exceeds CLOCK.MAX)",
    6: "system invalid time or date",
    7: "system clock exception",
}


# Peer status words
_peer_statuses = {
    0: "configured",
    1: "authentication enabled",
    2: "authentication okay",
    3: "reachability okay",
    4: "reserved"
}


_peer_selection = {
    0: "rejected",
    1: "passed sanity checks",
    2: "passed correctness checks",
    3: "passed candidate checks",
    4: "passed outlyer checks",
    5: "current synchronization source; max distance exceeded",
    6: "current synchronization source; max distance okay",
    7: "reserved"
}


_peer_event_codes = {
    0: "unspecified",
    1: "peer IP error",
    2: "peer authentication failure",
    3: "peer unreachable",
    4: "peer reachable",
    5: "peer clock exception",
}


# Clock status words
_clock_statuses = {
    0: "clock operating within nominals",
    1: "reply timeout",
    2: "bad reply format",
    3: "hardware or software fault",
    4: "propagation failure",
    5: "bad date format or value",
    6: "bad time format or value"
}


# Error status words
_error_statuses = {
    0: "unspecified",
    1: "authentication failure",
    2: "invalid message length or format",
    3: "invalid opcode",
    4: "unknown association identifier",
    5: "unknown variable name",
    6: "invalid variable value",
    7: "administratively prohibited"
}


class NTPStatusPacket(Packet):
    """
    Packet handling a non specific status word.
    """

    name = "status"
    fields_desc = [ShortField("status", 0)]

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


class NTPSystemStatusPacket(Packet):

    """
    Packet handling the system status fields.
    """

    name = "system status"
    fields_desc = [
        BitEnumField("leap_indicator", 0, 2, _system_statuses),
        BitEnumField("clock_source", 0, 6, _clock_sources),
        BitField("system_event_counter", 0, 4),
        BitEnumField("system_event_code", 0, 4, _system_event_codes),
    ]

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


class NTPPeerStatusPacket(Packet):
    """
    Packet handling the peer status fields.
    """

    name = "peer status"
    fields_desc = [
        BitField("configured", 0, 1),
        BitField("auth_enabled", 0, 1),
        BitField("authentic", 0, 1),
        BitField("reachability", 0, 1),
        BitField("reserved", 0, 1),
        BitEnumField("peer_sel", 0, 3, _peer_selection),
        BitField("peer_event_counter", 0, 4),
        BitEnumField("peer_event_code", 0, 4, _peer_event_codes),
    ]

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


class NTPClockStatusPacket(Packet):
    """
    Packet handling the clock status fields.
    """

    name = "clock status"
    fields_desc = [
        BitEnumField("clock_status", 0, 8, _clock_statuses),
        BitField("code", 0, 8)
    ]

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


class NTPErrorStatusPacket(Packet):
    """
    Packet handling the error status fields.
    """

    name = "error status"
    fields_desc = [
        BitEnumField("error_code", 0, 8, _error_statuses),
        BitField("reserved", 0, 8)
    ]

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


class NTPControlStatusField(PacketField):
    """
    This field provides better readability for the "status" field.
    """

    #########################################################################
    #
    # RFC 1305
    #########################################################################
    #
    # Appendix B.3. Commands // ntpd source code: ntp_control.h
    #########################################################################
    #

    def m2i(self, pkt, m):
        ret = None
        association_id = struct.unpack("!H", m[2:4])[0]

        if pkt.err == 1:
            ret = NTPErrorStatusPacket(m)

        # op_code == CTL_OP_READSTAT
        elif pkt.op_code == 1:
            if association_id != 0:
                ret = NTPPeerStatusPacket(m)
            else:
                ret = NTPSystemStatusPacket(m)

        # op_code == CTL_OP_READVAR
        elif pkt.op_code == 2:
            if association_id != 0:
                ret = NTPPeerStatusPacket(m)
            else:
                ret = NTPSystemStatusPacket(m)

        # op_code == CTL_OP_WRITEVAR
        elif pkt.op_code == 3:
            ret = NTPStatusPacket(m)

        # op_code == CTL_OP_READCLOCK or op_code == CTL_OP_WRITECLOCK
        elif pkt.op_code == 4 or pkt.op_code == 5:
            ret = NTPClockStatusPacket(m)

        else:
            ret = NTPStatusPacket(m)

        return ret


class NTPPeerStatusDataPacket(Packet):
    """
    Packet handling the data field when op_code is CTL_OP_READSTAT
    and the association_id field is null.
    """

    name = "data / peer status"
    fields_desc = [
        ShortField("association_id", 0),
        PacketField("peer_status", NTPPeerStatusPacket(), NTPPeerStatusPacket),
    ]


class NTPControlDataPacketLenField(PacketLenField):

    """
    PacketField handling the "data" field of NTP control messages.
    """

    def m2i(self, pkt, m):
        ret = None

        # op_code == CTL_OP_READSTAT
        if pkt.op_code == 1:
            if pkt.association_id == 0:
                # Data contains association ID and peer status
                ret = NTPPeerStatusDataPacket(m)
            else:
                ret = conf.raw_layer(m)
        else:
            ret = conf.raw_layer(m)

        return ret

    def getfield(self, pkt, s):
        length = self.length_from(pkt)
        i = None
        if length > 0:
            # RFC 1305
            # The maximum number of data octets is 468.
            #
            # include/ntp_control.h
            # u_char data[480 + MAX_MAC_LEN]; /* data + auth */
            #
            # Set the minimum length to 480 - 468
            length = max(12, length)
            if length % 4:
                length += (4 - length % 4)
        try:
            i = self.m2i(pkt, s[:length])
        except Exception:
            if conf.debug_dissector:
                raise
            i = conf.raw_layer(load=s[:length])
        return s[length:], i


class NTPControl(NTP):
    """
    Packet handling NTP mode 6 / "Control" messages.
    """

    #########################################################################
    #
    # RFC 1305
    #########################################################################
    #
    # Appendix B.3. Commands // ntpd source code: ntp_control.h
    #########################################################################
    #

    name = "Control message"
    fields_desc = [
        BitField("zeros", 0, 2),
        BitField("version", 2, 3),
        BitField("mode", 6, 3),
        BitField("response", 0, 1),
        BitField("err", 0, 1),
        BitField("more", 0, 1),
        BitEnumField("op_code", 0, 5, _op_codes),
        ShortField("sequence", 0),
        ConditionalField(NTPControlStatusField(
            "status_word", "", Packet), lambda p: p.response == 1),
        ConditionalField(ShortField("status", 0), lambda p: p.response == 0),
        ShortField("association_id", 0),
        ShortField("offset", 0),
        ShortField("count", None),
        NTPControlDataPacketLenField(
            "data", "", Packet, length_from=lambda p: p.count),
        PacketField("authenticator", "", NTPAuthenticator),
    ]

    def post_build(self, p, pay):
        if self.count is None:
            length = 0
            if self.data:
                length = len(self.data)
            p = p[:11] + struct.pack("!H", length) + p[13:]
        return p + pay


##############################################################################
#     Private (mode 7)
##############################################################################

_information_error_codes = {
    0: "INFO_OKAY",
    1: "INFO_ERR_IMPL",
    2: "INFO_ERR_REQ",
    3: "INFO_ERR_FMT",
    4: "INFO_ERR_NODATA",
    7: "INFO_ERR_AUTH"
}


_implementations = {
    0: "IMPL_UNIV",
    2: "IMPL_XNTPD_OLD",
    3: "XNTPD"
}


_request_codes = {
    0: "REQ_PEER_LIST",
    1: "REQ_PEER_LIST_SUM",
    2: "REQ_PEER_INFO",
    3: "REQ_PEER_STATS",
    4: "REQ_SYS_INFO",
    5: "REQ_SYS_STATS",
    6: "REQ_IO_STATS",
    7: "REQ_MEM_STATS",
    8: "REQ_LOOP_INFO",
    9: "REQ_TIMER_STATS",
    10: "REQ_CONFIG",
    11: "REQ_UNCONFIG",
    12: "REQ_SET_SYS_FLAG",
    13: "REQ_CLR_SYS_FLAG",
    14: "REQ_MONITOR",
    15: "REQ_NOMONITOR",
    16: "REQ_GET_RESTRICT",
    17: "REQ_RESADDFLAGS",
    18: "REQ_RESSUBFLAGS",
    19: "REQ_UNRESTRICT",
    20: "REQ_MON_GETLIST",
    21: "REQ_RESET_STATS",
    22: "REQ_RESET_PEER",
    23: "REQ_REREAD_KEYS",
    24: "REQ_DO_DIRTY_HACK",
    25: "REQ_DONT_DIRTY_HACK",
    26: "REQ_TRUSTKEY",
    27: "REQ_UNTRUSTKEY",
    28: "REQ_AUTHINFO",
    29: "REQ_TRAPS",
    30: "REQ_ADD_TRAP",
    31: "REQ_CLR_TRAP",
    32: "REQ_REQUEST_KEY",
    33: "REQ_CONTROL_KEY",
    34: "REQ_GET_CTLSTATS",
    35: "REQ_GET_LEAPINFO",
    36: "REQ_GET_CLOCKINFO",
    37: "REQ_SET_CLKFUDGE",
    38: "REQ_GET_KERNEL",
    39: "REQ_GET_CLKBUGINFO",
    41: "REQ_SET_PRECISION",
    42: "REQ_MON_GETLIST_1",
    43: "REQ_HOSTNAME_ASSOCID",
    44: "REQ_IF_STATS",
    45: "REQ_IF_RELOAD"
}


# Flags in the peer information returns
_peer_flags = [
    "INFO_FLAG_CONFIG",
    "INFO_FLAG_SYSPEER",
    "INFO_FLAG_BURST",
    "INFO_FLAG_REFCLOCK",
    "INFO_FLAG_PREFER",
    "INFO_FLAG_AUTHENABLE",
    "INFO_FLAG_SEL_CANDIDATE",
    "INFO_FLAG_SHORTLIST",
    "INFO_FLAG_IBURST"
]


# Flags in the system information returns
_sys_info_flags = [
    "INFO_FLAG_BCLIENT",
    "INFO_FLAG_AUTHENTICATE",
    "INFO_FLAG_NTP",
    "INFO_FLAG_KERNEL",
    "INFO_FLAG_CAL",
    "INFO_FLAG_PPS_SYNC",
    "INFO_FLAG_MONITOR",
    "INFO_FLAG_FILEGEN",
]


class NTPInfoPeerList(Packet):

    """
    Used to return raw lists of peers.
    """
    name = "info_peer_list"
    fields_desc = [
        IPField("addr", "0.0.0.0"),
        ShortField("port", 0),
        ByteEnumField("hmode", 0, _ntp_modes),
        FlagsField("flags", 0, 8, _peer_flags),
        IntField("v6_flag", 0),
        IntField("unused1", 0),
        IP6Field("addr6", "::")
    ]


class NTPInfoPeerSummary(Packet):
    """
    Sort of the info that ntpdc returns by default.
    """
    name = "info_peer_summary"
    fields_desc = [
        IPField("dstaddr", "0.0.0.0"),
        IPField("srcaddr", "0.0.0.0"),
        ShortField("srcport", 0),
        ByteField("stratum", 0),
        ByteField("hpoll", 0),
        ByteField("ppoll", 0),
        ByteField("reach", 0),
        FlagsField("flags", 0, 8, _peer_flags),
        ByteField("hmode", _ntp_modes),
        FixedPointField("delay", 0, size=32, frac_bits=16),
        TimeStampField("offset", 0),
        FixedPointField("dispersion", 0, size=32, frac_bits=16),
        IntField("v6_flag", 0),
        IntField("unused1", 0),
        IP6Field("dstaddr6", "::"),
        IP6Field("srcaddr6", "::")
    ]


class NTPInfoPeer(Packet):
    """
    Peer information structure.
    """

    name = "info_peer"
    fields_desc = [
        IPField("dstaddr", "0.0.0.0"),
        IPField("srcaddr", "0.0.0.0"),
        ShortField("srcport", 0),
        FlagsField("flags", 0, 8, _peer_flags),
        ByteField("leap", 0),
        ByteEnumField("hmode", 0, _ntp_modes),
        ByteField("pmode", 0),
        ByteField("stratum", 0),
        ByteField("ppoll", 0),
        ByteField("hpoll", 0),
        SignedByteField("precision", 0),
        ByteField("version", 0),
        ByteField("unused8", 0),
        ByteField("reach", 0),
        ByteField("unreach", 0),
        XByteField("flash", 0),
        ByteField("ttl", 0),
        XLEShortField("flash2", 0),
        ShortField("associd", 0),
        LEIntField("keyid", 0),
        IntField("pkeyid", 0),
        IPField("refid", 0),
        IntField("timer", 0),
        FixedPointField("rootdelay", 0, size=32, frac_bits=16),
        FixedPointField("rootdispersion", 0, size=32, frac_bits=16),
        TimeStampField("reftime", 0),
        TimeStampField("org", 0),
        TimeStampField("rec", 0),
        TimeStampField("xmt", 0),
        FieldListField(
            "filtdelay",
            [0.0 for i in range(0, _NTP_SHIFT)],
            FixedPointField("", 0, size=32, frac_bits=16),
            count_from=lambda p: _NTP_SHIFT
        ),
        FieldListField(
            "filtoffset",
            [0.0 for i in range(0, _NTP_SHIFT)],
            TimeStampField("", 0),
            count_from=lambda p: _NTP_SHIFT
        ),
        FieldListField(
            "order",
            [0 for i in range(0, _NTP_SHIFT)],
            ByteField("", 0),
            count_from=lambda p: _NTP_SHIFT
        ),
        FixedPointField("delay", 0, size=32, frac_bits=16),
        FixedPointField("dispersion", 0, size=32, frac_bits=16),
        TimeStampField("offset", 0),
        FixedPointField("selectdisp", 0, size=32, frac_bits=16),
        IntField("unused1", 0),
        IntField("unused2", 0),
        IntField("unused3", 0),
        IntField("unused4", 0),
        IntField("unused5", 0),
        IntField("unused6", 0),
        IntField("unused7", 0),
        FixedPointField("estbdelay", 0, size=32, frac_bits=16),
        IntField("v6_flag", 0),
        IntField("unused9", 0),
        IP6Field("dstaddr6", "::"),
        IP6Field("srcaddr6", "::"),
    ]


class NTPInfoPeerStats(Packet):
    """
    Peer statistics structure.
    """

    name = "info_peer_stats"
    fields_desc = [
        IPField("dstaddr", "0.0.0.0"),
        IPField("srcaddr", "0.0.0.0"),
        ShortField("srcport", 0),
        FlagsField("flags", 0, 16, _peer_flags),
        IntField("timereset", 0),
        IntField("timereceived", 0),
        IntField("timetosend", 0),
        IntField("timereachable", 0),
        IntField("sent", 0),
        IntField("unused1", 0),
        IntField("processed", 0),
        IntField("unused2", 0),
        IntField("badauth", 0),
        IntField("bogusorg", 0),
        IntField("oldpkt", 0),
        IntField("unused3", 0),
        IntField("unused4", 0),
        IntField("seldisp", 0),
        IntField("selbroken", 0),
        IntField("unused5", 0),
        ByteField("candidate", 0),
        ByteField("unused6", 0),
        ByteField("unused7", 0),
        ByteField("unused8", 0),
        IntField("v6_flag", 0),
        IntField("unused9", 0),
        IP6Field("dstaddr6", "::"),
        IP6Field("srcaddr6", "::"),
    ]


class NTPInfoLoop(Packet):
    """
    Loop filter variables.
    """

    name = "info_loop"
    fields_desc = [
        TimeStampField("last_offset", 0),
        TimeStampField("drift_comp", 0),
        IntField("compliance", 0),
        IntField("watchdog_timer", 0)
    ]


class NTPInfoSys(Packet):
    """
    System info. Mostly the sys.* variables, plus a few unique to
    the implementation.
    """

    name = "info_sys"
    fields_desc = [
        IPField("peer", "0.0.0.0"),
        ByteField("peer_mode", 0),
        ByteField("leap", 0),
        ByteField("stratum", 0),
        ByteField("precision", 0),
        FixedPointField("rootdelay", 0, size=32, frac_bits=16),
        FixedPointField("rootdispersion", 0, size=32, frac_bits=16),
        IPField("refid", 0),
        TimeStampField("reftime", 0),
        IntField("poll", 0),
        FlagsField("flags", 0, 8, _sys_info_flags),
        ByteField("unused1", 0),
        ByteField("unused2", 0),
        ByteField("unused3", 0),
        FixedPointField("bdelay", 0, size=32, frac_bits=16),
        FixedPointField("frequency", 0, size=32, frac_bits=16),
        TimeStampField("authdelay", 0),
        FixedPointField("stability", 0, size=32, frac_bits=16),
        IntField("v6_flag", 0),
        IntField("unused4", 0),
        IP6Field("peer6", "::")
    ]


class NTPInfoSysStats(Packet):
    """
    System stats. These are collected in the protocol module.
    """

    name = "info_sys_stats"
    fields_desc = [
        IntField("timeup", 0),
        IntField("timereset", 0),
        IntField("denied", 0),
        IntField("oldversionpkt", 0),
        IntField("newversionpkt", 0),
        IntField("unknownversion", 0),
        IntField("badlength", 0),
        IntField("processed", 0),
        IntField("badauth", 0),
        IntField("received", 0),
        IntField("limitrejected", 0)
    ]


class NTPInfoMemStats(Packet):
    """
    Peer memory statistics.
    """

    name = "info_mem_stats"
    fields_desc = [
        IntField("timereset", 0),
        ShortField("totalpeermem", 0),
        ShortField("freepeermem", 0),
        IntField("findpeer_calls", 0),
        IntField("allocations", 0),
        IntField("demobilizations", 0),
        FieldListField(
            "hashcount",
            [0.0 for i in range(0, _NTP_HASH_SIZE)],
            ByteField("", 0),
            count_from=lambda p: _NTP_HASH_SIZE
        )
    ]


class NTPInfoIOStats(Packet):
    """
    I/O statistics.
    """

    name = "info_io_stats"
    fields_desc = [
        IntField("timereset", 0),
        ShortField("totalrecvbufs", 0),
        ShortField("freerecvbufs", 0),
        ShortField("fullrecvbufs", 0),
        ShortField("lowwater", 0),
        IntField("dropped", 0),
        IntField("ignored", 0),
        IntField("received", 0),
        IntField("sent", 0),
        IntField("notsent", 0),
        IntField("interrupts", 0),
        IntField("int_received", 0)
    ]


class NTPInfoTimerStats(Packet):
    """
    Timer stats.
    """

    name = "info_timer_stats"
    fields_desc = [
        IntField("timereset", 0),
        IntField("alarms", 0),
        IntField("overflows", 0),
        IntField("xmtcalls", 0),
    ]


_conf_peer_flags = [
    "CONF_FLAG_AUTHENABLE",
    "CONF_FLAG_PREFER",
    "CONF_FLAG_BURST",
    "CONF_FLAG_IBURST",
    "CONF_FLAG_NOSELECT",
    "CONF_FLAG_SKEY"
]


class NTPConfPeer(Packet):
    """
    Structure for passing peer configuration information.
    """

    name = "conf_peer"
    fields_desc = [
        IPField("peeraddr", "0.0.0.0"),
        ByteField("hmode", 0),
        ByteField("version", 0),
        ByteField("minpoll", 0),
        ByteField("maxpoll", 0),
        FlagsField("flags", 0, 8, _conf_peer_flags),
        ByteField("ttl", 0),
        ShortField("unused1", 0),
        IntField("keyid", 0),
        StrFixedLenField("keystr", "", length=128),
        IntField("v6_flag", 0),
        IntField("unused2", 0),
        IP6Field("peeraddr6", "::")
    ]


class NTPConfUnpeer(Packet):
    """
    Structure for passing peer deletion information.
    """

    name = "conf_unpeer"
    fields_desc = [
        IPField("peeraddr", "0.0.0.0"),
        IntField("v6_flag", 0),
        IP6Field("peeraddr6", "::")
    ]


_restrict_flags = [
    "RES_IGNORE",
    "RES_DONTSERVE",
    "RES_DONTTRUST",
    "RES_VERSION",
    "RES_NOPEER",
    "RES_LIMITED",
    "RES_NOQUERY",
    "RES_NOMODIFY",
    "RES_NOTRAP",
    "RES_LPTRAP",
    "RES_KOD",
    "RES_MSSNTP",
    "RES_FLAKE",
    "RES_NOMRULIST",
]


class NTPConfRestrict(Packet):
    """
    Structure used for specifying restrict entries.
    """

    name = "conf_restrict"
    fields_desc = [
        IPField("addr", "0.0.0.0"),
        IPField("mask", "0.0.0.0"),
        FlagsField("flags", 0, 16, _restrict_flags),
        ShortField("m_flags", 0),
        IntField("v6_flag", 0),
        IP6Field("addr6", "::"),
        IP6Field("mask6", "::")
    ]


class NTPInfoKernel(Packet):
    """
    Structure used for returning kernel pll/PPS information
    """

    name = "info_kernel"
    fields_desc = [
        IntField("offset", 0),
        IntField("freq", 0),
        IntField("maxerror", 0),
        IntField("esterror", 0),
        ShortField("status", 0),
        ShortField("shift", 0),
        IntField("constant", 0),
        IntField("precision", 0),
        IntField("tolerance", 0),
        IntField("ppsfreq", 0),
        IntField("jitter", 0),
        IntField("stabil", 0),
        IntField("jitcnt", 0),
        IntField("calcnt", 0),
        IntField("errcnt", 0),
        IntField("stbcnt", 0),
    ]


class NTPInfoIfStatsIPv4(Packet):
    """
    Interface statistics.
    """

    name = "info_if_stats"
    fields_desc = [
        PadField(IPField("unaddr", "0.0.0.0"), 16, padwith=b"\x00"),
        PadField(IPField("unbcast", "0.0.0.0"), 16, padwith=b"\x00"),
        PadField(IPField("unmask", "0.0.0.0"), 16, padwith=b"\x00"),
        IntField("v6_flag", 0),
        StrFixedLenField("ifname", "", length=32),
        IntField("flags", 0),
        IntField("last_ttl", 0),
        IntField("num_mcast", 0),
        IntField("received", 0),
        IntField("sent", 0),
        IntField("notsent", 0),
        IntField("uptime", 0),
        IntField("scopeid", 0),
        IntField("ifindex", 0),
        IntField("ifnum", 0),
        IntField("peercnt", 0),
        ShortField("family", 0),
        ByteField("ignore_packets", 0),
        ByteField("action", 0),
        IntField("_filler0", 0)
    ]


class NTPInfoIfStatsIPv6(Packet):
    """
    Interface statistics.
    """

    name = "info_if_stats"
    fields_desc = [
        IP6Field("unaddr", "::"),
        IP6Field("unbcast", "::"),
        IP6Field("unmask", "::"),
        IntField("v6_flag", 0),
        StrFixedLenField("ifname", "", length=32),
        IntField("flags", 0),
        IntField("last_ttl", 0),
        IntField("num_mcast", 0),
        IntField("received", 0),
        IntField("sent", 0),
        IntField("notsent", 0),
        IntField("uptime", 0),
        IntField("scopeid", 0),
        IntField("ifindex", 0),
        IntField("ifnum", 0),
        IntField("peercnt", 0),
        ShortField("family", 0),
        ByteField("ignore_packets", 0),
        ByteField("action", 0),
        IntField("_filler0", 0)
    ]


class NTPInfoMonitor1(Packet):
    """
    Structure used for returning monitor data.
    """

    name = "InfoMonitor1"
    fields_desc = [
        IntField("lasttime", 0),
        IntField("firsttime", 0),
        IntField("lastdrop", 0),
        IntField("count", 0),
        IPField("addr", "0.0.0.0"),
        IPField("daddr", "0.0.0.0"),
        IntField("flags", 0),
        ShortField("port", 0),
        ByteField("mode", 0),
        ByteField("version", 0),
        IntField("v6_flag", 0),
        IntField("unused1", 0),
        IP6Field("addr6", "::"),
        IP6Field("daddr6", "::")
    ]


class NTPInfoAuth(Packet):
    """
    Structure used to return information concerning the authentication module.
    """

    name = "info_auth"
    fields_desc = [
        IntField("timereset", 0),
        IntField("numkeys", 0),
        IntField("numfreekeys", 0),
        IntField("keylookups", 0),
        IntField("keynotfound", 0),
        IntField("encryptions", 0),
        IntField("decryptions", 0),
        IntField("expired", 0),
        IntField("keyuncached", 0),
    ]


class NTPConfTrap(Packet):
    """
    Structure used to pass add/clear trap information to the client
    """

    name = "conf_trap"
    fields_desc = [
        IPField("local_address", "0.0.0.0"),
        IPField("trap_address", "0.0.0.0"),
        ShortField("trap_port", 0),
        ShortField("unused", 0),
        IntField("v6_flag", 0),
        IP6Field("local_address6", "::"),
        IP6Field("trap_address6", "::"),
    ]


class NTPInfoControl(Packet):
    """
    Structure used to return statistics from the control module.
    """

    name = "info_control"
    fields_desc = [
        IntField("ctltimereset", 0),
        IntField("numctlreq", 0),
        IntField("numctlbadpkts", 0),
        IntField("numctlresponses", 0),
        IntField("numctlfrags", 0),
        IntField("numctlerrors", 0),
        IntField("numctltooshort", 0),
        IntField("numctlinputresp", 0),
        IntField("numctlinputfrag", 0),
        IntField("numctlinputerr", 0),
        IntField("numctlbadoffset", 0),
        IntField("numctlbadversion", 0),
        IntField("numctldatatooshort", 0),
        IntField("numctlbadop", 0),
        IntField("numasyncmsgs", 0),
    ]


# ntp_request.h
_ntpd_private_errors = {
    0: "no error",
    1: "incompatible implementation number",
    2: "unimplemented request code",
    3: "format error (wrong data items, data size, packet size etc.)",
    4: "no data available (e.g. request for details on unknown peer)",
    5: "I don\"t know",
    6: "I don\"t know",
    7: "authentication failure (i.e. permission denied)",
}


# dict mapping request codes to the right response data class
_private_data_objects = {
    0: NTPInfoPeerList,  # "REQ_PEER_LIST",
    1: NTPInfoPeerSummary,  # "REQ_PEER_LIST_SUM",
    2: NTPInfoPeer,  # "REQ_PEER_INFO",
    3: NTPInfoPeerStats,  # "REQ_PEER_STATS",
    4: NTPInfoSys,  # "REQ_SYS_INFO",
    5: NTPInfoSysStats,  # "REQ_SYS_STATS",
    6: NTPInfoIOStats,  # "REQ_IO_STATS",
    7: NTPInfoMemStats,  # "REQ_MEM_STATS",
    8: NTPInfoLoop,  # "REQ_LOOP_INFO",
    9: NTPInfoTimerStats,  # "REQ_TIMER_STATS",
    10: NTPConfPeer,  # "REQ_CONFIG",
    11: NTPConfUnpeer,  # "REQ_UNCONFIG",
    28: NTPInfoAuth,  # "REQ_AUTHINFO",
    30: NTPConfTrap,  # "REQ_ADD_TRAP",
    34: NTPInfoControl,  # "REQ_GET_CTLSTATS",
    38: NTPInfoKernel,  # "REQ_GET_KERNEL",
    42: NTPInfoMonitor1,  # "REQ_MON_GETLIST_1",
}


class NTPPrivateRespPacketListField(PacketListField):
    """
    PacketListField handling the response data.
    """

    def m2i(self, pkt, s):
        ret = None

        # info_if_stats
        if pkt.request_code == 44 or pkt.request_code == 45:
            is_v6 = struct.unpack("!I", s[48:52])[0]
            ret = NTPInfoIfStatsIPv6(s) if is_v6 else NTPInfoIfStatsIPv4(s)
        else:
            ret = _private_data_objects.get(pkt.request_code, conf.raw_layer)(s)  # noqa: E501

        return ret

    def getfield(self, pkt, s):
        lst = []
        remain = s
        length = pkt.data_item_size
        if length > 0:
            item_counter = 0
            # Response payloads can be placed in several packets
            while len(remain) >= pkt.data_item_size and item_counter < pkt.nb_items:  # noqa: E501
                current = remain[:length]
                remain = remain[length:]
                current_packet = self.m2i(pkt, current)
                lst.append(current_packet)
                item_counter += 1

        return remain, lst


class NTPPrivateReqPacket(Packet):
    """
    Packet handling request data.
    """

    name = "request data"
    fields_desc = [StrField("req_data", "")]


_request_codes = {
    0: "REQ_PEER_LIST",
    1: "REQ_PEER_LIST_SUM",
    2: "REQ_PEER_INFO",
    3: "REQ_PEER_STATS",
    4: "REQ_SYS_INFO",
    5: "REQ_SYS_STATS",
    6: "REQ_IO_STATS",
    7: "REQ_MEM_STATS",
    8: "REQ_LOOP_INFO",
    9: "REQ_TIMER_STATS",
    10: "REQ_CONFIG",
    11: "REQ_UNCONFIG",
    12: "REQ_SET_SYS_FLAG",
    13: "REQ_CLR_SYS_FLAG",
    14: "REQ_MONITOR",
    15: "REQ_NOMONITOR",
    16: "REQ_GET_RESTRICT",
    17: "REQ_RESADDFLAGS",
    18: "REQ_RESSUBFLAGS",
    19: "REQ_UNRESTRICT",
    20: "REQ_MON_GETLIST",
    21: "REQ_RESET_STATS",
    22: "REQ_RESET_PEER",
    23: "REQ_REREAD_KEYS",
    24: "REQ_DO_DIRTY_HACK",
    25: "REQ_DONT_DIRTY_HACK",
    26: "REQ_TRUSTKEY",
    27: "REQ_UNTRUSTKEY",
    28: "REQ_AUTHINFO",
    29: "REQ_TRAPS",
    30: "REQ_ADD_TRAP",
    31: "REQ_CLR_TRAP",
    32: "REQ_REQUEST_KEY",
    33: "REQ_CONTROL_KEY",
    34: "REQ_GET_CTLSTATS",
    35: "REQ_GET_LEAPINFO",
    36: "REQ_GET_CLOCKINFO",
    37: "REQ_SET_CLKFUDGE",
    38: "REQ_GET_KERNEL",
    39: "REQ_GET_CLKBUGINFO",
    41: "REQ_SET_PRECISION",
    42: "REQ_MON_GETLIST_1",
    43: "REQ_HOSTNAME_ASSOCID",
    44: "REQ_IF_STATS",
    45: "REQ_IF_RELOAD"
}


class NTPPrivateReqPacketListField(PacketListField):
    """
    Handles specific request packets.
    """

    # See ntpdc/ntpdc.c and ntpdc/ntpdc_ops.c

    def m2i(self, pkt, s):
        ret = None

        if pkt.request_code == 2 or pkt.request_code == 3:
            # REQ_PEER_INFO (see ntpdc/ntpdc_ops.c: showpeer())
            # REQ_PEER_STATS (for request only)
            ret = NTPInfoPeerList(s)

        elif pkt.request_code == 10:
            # REQ_CONFIG
            ret = NTPConfPeer(s)

        elif pkt.request_code == 11:
            # REQ_CONFIG
            ret = NTPConfUnpeer(s)

        elif pkt.request_code == 17:
            # REQ_RESADDFLAGS
            ret = NTPConfRestrict(s)

        elif pkt.request_code == 18:
            # REQ_RESSUBFLAGS
            ret = NTPConfRestrict(s)

        elif pkt.request_code == 22:
            # REQ_RESET_PEER
            ret = NTPConfUnpeer(s)

        elif pkt.request_code == 30 or pkt.request_code == 31:
            # REQ_ADD_TRAP
            ret = NTPConfTrap(s)

        else:
            ret = NTPPrivateReqPacket(s)

        return ret

    def getfield(self, pkt, s):
        lst = []
        remain = s
        length = pkt.data_item_size
        if length > 0:
            item_counter = 0
            while len(remain) >= pkt.data_item_size * pkt.nb_items and item_counter < pkt.nb_items:  # noqa: E501
                current = remain[:length]
                remain = remain[length:]
                current_packet = self.m2i(pkt, current)
                lst.append(current_packet)
                item_counter += 1

        # If "auth" bit is set, don't forget the padding bytes
        if pkt.auth:
            padding_end = len(remain) - _NTP_PRIVATE_REQ_PKT_TAIL_LEN
            current_packet = conf.raw_layer(remain[:padding_end])
            lst.append(current_packet)
            remain = remain[padding_end:]

        return remain, lst


class NTPPrivatePktTail(Packet):
    """
    include/ntp_request.h
    The req_pkt_tail structure is used by ntpd to adjust for different
    packet sizes that may arrive.
    """

    name = "req_pkt_tail"
    fields_desc = [
        TimeStampField("tstamp", 0),
        IntField("key_id", 0),
        XStrFixedLenField(
            "dgst", "", length_from=lambda x: _NTP_AUTH_MD5_DGST_SIZE)
    ]


class NTPPrivate(NTP):
    """
    Packet handling the private (mode 7) messages.
    """

    #########################################################################
    # ntpd source code: ntp_request.h
    #########################################################################
    #
    # A mode 7 packet is used exchanging data between an NTP server
    # and a client for purposes other than time synchronization, e.g.
    # monitoring, statistics gathering and configuration.  A mode 7
    # packet has the following format:
    #
    #    0                        1                   2                   3
    #    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
    #   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    #   |R|M| VN  | Mode|A|  Sequence   | Implementation|   Req Code    |
    #   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    #   |  Err  | Number of data items  |  MBZ  |   Size of data item   |
    #   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    #   |                                                               |
    #   |            Data (Minimum 0 octets, maximum 500 octets)        |
    #   |                                                               |
    #                            [...]                                  |
    #   |                                                               |
    #   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    #   |               Encryption Keyid (when A bit set)               |
    #   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    #   |                                                               |
    #   |          Message Authentication Code (when A bit set)         |
    #   |                                                               |
    #   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    #
    # where the fields are (note that the client sends requests, the server
    # responses):
    #
    # Response Bit:  This packet is a response (if clear, packet is a request).
    #
    # More Bit: Set for all packets but the last in a response which
    #           requires more than one packet.
    #
    # Version Number: 2 for current version
    #
    # Mode:     Always 7
    #
    # Authenticated bit: If set, this packet is authenticated.
    #
    # Sequence number: For a multipacket response, contains the sequence
    #           number of this packet.  0 is the first in the sequence,
    #           127 (or less) is the last.  The More Bit must be set in
    #           all packets but the last.
    #
    # Implementation number: The number of the implementation this request code
    #           is defined by.  An implementation number of zero is used
    #           for request codes/data formats which all implementations
    #           agree on.  Implementation number 255 is reserved (for
    #           extensions, in case we run out).
    #
    # Request code: An implementation-specific code which specifies the
    #           operation to be (which has been) performed and/or the
    #           format and semantics of the data included in the packet.
    #
    # Err:      Must be 0 for a request.  For a response, holds an error
    #           code relating to the request.  If nonzero, the operation
    #           requested wasn't performed.
    #
    #           0 - no error
    #           1 - incompatible implementation number
    #           2 - unimplemented request code
    #           3 - format error (wrong data items, data size, packet size etc.)  # noqa: E501
    #           4 - no data available (e.g. request for details on unknown peer)  # noqa: E501
    #           5-6 I don"t know
    #           7 - authentication failure (i.e. permission denied)
    #
    # Number of data items: number of data items in packet.  0 to 500
    #
    # MBZ:      A reserved data field, must be zero in requests and responses.
    #
    # Size of data item: size of each data item in packet.  0 to 500
    #
    # Data:     Variable sized area containing request/response data.  For
    #           requests and responses the size in octets must be greater
    #           than or equal to the product of the number of data items
    #           and the size of a data item.  For requests the data area
    #           must be exactly 40 octets in length.  For responses the
    #           data area may be any length between 0 and 500 octets
    #           inclusive.
    #
    # Message Authentication Code: Same as NTP spec, in definition and function.  # noqa: E501
    #           May optionally be included in requests which require
    #           authentication, is never included in responses.
    #
    # The version number, mode and keyid have the same function and are
    # in the same location as a standard NTP packet.  The request packet
    # is the same size as a standard NTP packet to ease receive buffer
    # management, and to allow the same encryption procedure to be used
    # both on mode 7 and standard NTP packets.  The mac is included when
    # it is required that a request be authenticated, the keyid should be
    # zero in requests in which the mac is not included.
    #
    # The data format depends on the implementation number/request code pair
    # and whether the packet is a request or a response.  The only requirement
    # is that data items start in the octet immediately following the size
    # word and that data items be concatenated without padding between (i.e.
    # if the data area is larger than data_items*size, all padding is at
    # the end).  Padding is ignored, other than for encryption purposes.
    # Implementations using encryption might want to include a time stamp
    # or other data in the request packet padding.  The key used for requests
    # is implementation defined, but key 15 is suggested as a default.
    #########################################################################
    #

    name = "Private (mode 7)"
    fields_desc = [
        BitField("response", 0, 1),
        BitField("more", 0, 1),
        BitField("version", 2, 3),
        BitField("mode", 0, 3),
        BitField("auth", 0, 1),
        BitField("seq", 0, 7),
        ByteEnumField("implementation", 0, _implementations),
        ByteEnumField("request_code", 0, _request_codes),
        BitEnumField("err", 0, 4, _ntpd_private_errors),
        BitField("nb_items", 0, 12),
        BitField("mbz", 0, 4),
        BitField("data_item_size", 0, 12),
        ConditionalField(
            NTPPrivateReqPacketListField(
                "req_data",
                [],
                Packet,
                length_from=lambda p: p.data_item_size,
                count_from=lambda p: p.nb_items
            ),
            lambda p: p.response == 0
        ),
        # Responses
        ConditionalField(
            NTPPrivateRespPacketListField(
                "data",
                [],
                Packet,
                length_from=lambda p: p.data_item_size,
                count_from=lambda p: p.nb_items
            ),
            lambda p: p.response == 1
        ),
        # Responses are not supposed to be authenticated
        ConditionalField(PacketField("authenticator", "", NTPPrivatePktTail),
                         lambda p: p.response == 0 and p.auth == 1),
    ]


##############################################################################
#     Layer bindings
##############################################################################

bind_layers(UDP, NTP, {"sport": 123})
bind_layers(UDP, NTP, {"dport": 123})
bind_layers(UDP, NTP, {"sport": 123, "dport": 123})
