"""ICMP packets.
"""

# Copyright 1997, Corporation for National Research Initiatives
# written by Jeremy Hylton, jeremy@cnri.reston.va.us

import inet
import array
import struct

ICMP_MINLEN = 8
ICMP_MASKLEN = 12
ICMP_ECHOREPLY = 0
ICMP_UNREACH = 3
ICMP_UNREACH_NET = 0
ICMP_UNREACH_HOST = 1
ICMP_UNREACH_PROTOCOL = 2
ICMP_UNREACH_PORT = 3
ICMP_UNREACH_NEEDFRAG = 4
ICMP_UNREACH_SRCFAIL = 5
ICMP_SOURCEQUENCH = 4
ICMP_REDIRECT = 5
ICMP_REDIRECT_NET = 0
ICMP_REDIRECT_HOST = 1
ICMP_REDIRECT_TOSNET = 2
ICMP_REDIRECT_TOSHOST = 3
ICMP_ECHO = 8
ICMP_TIMXCEED = 11
ICMP_TIMXCEED_INTRANS = 0
ICMP_TIMXCEED_REASS = 1
ICMP_PARAMPROB = 12
ICMP_TSTAMP = 13
ICMP_TSTAMPREPLY = 14
ICMP_IREQ = 15
ICMP_IREQREPLY = 16
ICMP_MASKREQ = 17
ICMP_MASKREPLY = 18

class Packet:
    """Basic ICMP packet definition.

    Equivalent to ICMP_ECHO_REQUEST and ICMP_REPLY packets.
    Other packets are defined as subclasses probably.
    """

    def __init__(self, packet=None, cksum=1):
        if packet:
            self.__disassemble(packet, cksum)
        else:
            self.type = 0
            self.code = 0
            self.cksum = 0
            self.id = 0
            self.seq = 0
            self.data = ''

    def __repr__(self):
        return "<ICMP packet %d %d %d %d>" % (self.type, self.code,
                                              self.id, self.seq)

    def assemble(self, cksum=1):
        idseq = struct.pack('hh', self.id, self.seq)
        packet = chr(self.type) + chr(self.code) + '\000\000' + idseq \
                 + self.data
        if cksum:
            self.cksum = inet.cksum(packet)
            packet = chr(self.type) + chr(self.code) \
                     + struct.pack('H', self.cksum) + idseq + self.data
        # Don't need to do any byte-swapping, because idseq is
        # appplication defined and others are single byte values.
        self.__packet = packet
        return self.__packet

    def __disassemble(self, packet, cksum=1):
        if cksum:
            our_cksum = inet.cksum(packet)
            if our_cksum != 0:
                raise ValueError, packet

        self.type = ord(packet[0])
        self.code = ord(packet[1])
        elts = struct.unpack('hhh', packet[2:8])
        [self.cksum, self.id, self.seq] = map(lambda x:x & 0xffff, elts)
        self.data = packet[8:]

    def __compute_cksum(self):
        "Use inet.cksum instead"
        packet = self.packet
        if len(packet) & 1:
            packet = packet + '\0'
        words = array.array('h', packet)
        sum = 0
        for word in words:
            sum = sum + (word & 0xffff)
        hi = sum >> 16
        lo = sum & 0xffff
        sum = hi + lo
        sum = sum + (sum >> 16)
        return (~sum) & 0xffff
        

class TimeExceeded(Packet):
    
    def __init__(self, packet=None, cksum=1):
        Packet.__init__(self, packet, cksum)
        if packet:
            if self.type != ICMP_TIMXCEED:
                raise ValueError, "supplied packet of wrong type"
        else:
            self.type = ICMP_TIMXCEED
            self.id = self.seq = 0

class Unreachable(Packet):
    
    def __init__(self, packet=None, cksum=1):
        Packet.__init__(self, packet, cksum)
        if packet:
            if self.type != ICMP_UNREACH:
                raise ValueError, "supplied packet of wrong type"
        else:
            self.type = ICMP_UNREACH
            self.id = self.seq = 0

