import os
import struct
import threading
import StringIO

def maybeOrd(char):
    if isinstance(char, str):
        return ord(char)
    if isinstance(char, int):
        return char
    raise TypeError, "Don't know how to maybeOrd %s" % `char`

def maybeChr(char):
    if isinstance(char, str) and len(char) == 1:
        return char
    if isinstance(char, int):
        return chr(char)
    raise TypeError, "Don't know how to maybeChr %s" % `char`

def printableAscii(buf):
    ascii = []
    for c in buf:
        if ord(' ') <= maybeOrd(c) <= ord('z'):
            ascii.append(maybeChr(c))
        else:
            ascii.append('.')
    return "".join(ascii)

def hexDump(data, terse = True, prefix = ""):
    data = list(data)
    addr = 0
    lines = []
    while len(data):
        buf = data[0:16]
        chars = ["%2.2x" % maybeOrd(c) for c in buf]
        if terse:
            out = "%s%s  %s" % (prefix,
                                " ".join(chars),
                                printableAscii(buf))
        else:
            out = "%s%8.8x  %s  %s" % (prefix, addr,
                                       " ".join(chars).ljust(3*16-1),
                                       printableAscii(buf))
        if terse:
            return out
        lines.append(out)
        addr += 16
        del data[0:16]
    return "\n".join(lines)

def binDump(data, terse = True, prefix = ""):
    data = list(data)
    lines = []
    for addr, char in enumerate(data):
        bits = [bool(maybeOrd(char) & (1<<bit)) for bit in range(8)]
        buf = [str(int(bit)) for bit in bits]
        buf.reverse()
        buf = "".join(buf)
        out = "%s%8.8x  %2.2x %s  %s" % (prefix, addr, maybeOrd(char), buf,
                                         printableAscii(maybeChr(char)))
        if terse:
            return out
        lines.append(out)
    return "\n".join(lines)

class PcapWriter(object):
    def __init__(self, filename):
        self.filename = filename
        if filename is None:
            # XXX This doesn't work right now.  Tethereal can't read
            # from a pipe, so this ultimately needs to go to a real
            # file
            raise NotImplementedError
            self.fp = StringIO()
        else:
            self.fp = file(filename, "w")
        self.__snaplen = 65536
        self.__writeHeader()

    def __writeHeader(self):
        h = struct.pack("LHHlLLL", 0xa1b2c3d4, 2, 4, 0, 0, self.__snaplen, 1)
        self.fp.write(h)
        self.fp.flush()

    def putPacket(self, secs, packet):
        if isinstance(packet, EthernetPacket):
            packet = packet.getBytes()
        origsize = len(packet)
        inclsize = min(origsize, self.__snaplen)
        h = struct.pack("LLLL", int(secs), int(secs*1000000) % 1000000,
                        inclsize, origsize)
        self.fp.write(h)
        self.fp.write(packet[:inclsize])
        self.fp.flush()

    def decodeToText(self):
        return self.__decode(("-V",))

    def decodeToXml(self):
        return self.__decode(("-T","pdml"))

    def __decode(self, args):
        cmd = "tethereal %s -r %s" % (" ".join(args), self.filename)
        pin = os.popen(cmd, "r")
        data = pin.read()
        ret = pin.close()
        if isinstance(ret, int) and \
           (not os.WIFEXITED(ret) or os.WEXITSTATUS(ret) != 0):
            return None
        return data

class MacAddr(object):
    def __init__(self, macaddr):
        m = None
        if (hasattr(macaddr, "__len__") and hasattr(macaddr, "__iter__") and
            len(macaddr) == 6):
            m = self.__listToLong(macaddr)
        elif isinstance(macaddr, int) or isinstance(macaddr, long):
            m = macaddr
        elif isinstance(macaddr, str):
            if len(macaddr) == 6:
                # 6 raw bytes
                m = self.__listToLong([ord(x) for x in macaddr])
            else:
                # Octet-colon
                elts = macaddr.split(":")
                if len(elts) == 6:
                    m = self.__listToLong([int(x, 16) for x in elts])
        if m is None:
            raise ValueError, "Expecting 6-tuple, long, or string"
        self.__macaddr = m

    def __listToLong(self, lst):
        m = 0
        for elt in lst:
            m = m << 8
            m |= elt
        return m

    def toLong(self):
        return self.__macaddr

    def toTuple(self):
        ret = ()
        for i in range(6):
            ret += ((self.__macaddr >> ((5-i)*8)) & 0xFF,)
        return ret

    def toString(self):
        return str(self)

    def __str__(self):
        return ":".join(["%2.2x" % o for o in self.toTuple()])

    def isBroadcast(self):
        return (self.__macaddr == 0xFFFFFFFFFFFF)

    def isMulticast(self):
        return ((self.__macaddr & 0x010000000000) != 0)

    def __eq__(self, other):
        return self.__macaddr == other.__macaddr

    def __hash__(self):
        return hash(self.__macaddr)

class EthernetPacket(object):
    def __init__(self, data):
        assert isinstance(data, str)
        self.__data = data

    def getBytes(self):
        return self.__data

    def getDestMAC(self):
        return MacAddr(self.__data[0:6])

    def getSrcMAC(self):
        return MacAddr(self.__data[6:12])

    def getType(self):
        return (ord(self.__data[12]) << 8) | ord(self.__data[13])

    def __repr__(self):
        return "<packet %s->%s [%d] %s>" % (self.getSrcMAC(),
                                            self.getDestMAC(),
                                            len(self.getBytes()),
                                            self.classify())

    def classify(self):
        res = []
        if self.getType() == 0x0806:
            res.append("ARP")
        elif self.getType() == 0x0800:
            res.append("IP")
            ipType = ord(self.__data[0x17])
            if ipType == 0x01:
                res.append("ICMP")
                icmpType = ord(self.__data[0x22])
                if icmpType == 0x08:
                    res.append("Ping")
                elif icmpType == 0x00:
                    res.append("Pong")
                else:
                    res.append("Unknown")
            else:
                res.append("Unknown")
        else:
            res.append("Unknown eth")
        return "/".join(res)

class ThreadCommander(object):
    def __init__(self, getThread):
        self.__getThread = getThread

        self.__command = None
        self.__commandResponse = None
        self.__commandPutLock = threading.Lock()
        self.__commandResponseReady = threading.Event()

    def doCommand(self, cmd):
        # This is thread-safe
        assert threading.currentThread() != self.__getThread
        self.__commandPutLock.acquire()
        self.__command = cmd
        self.__commandResponseReady.wait()
        res = self.__commandResponse
        self.__commandResponse = None
        self.__commandResponseReady.clear()
        self.__commandPutLock.release()
        return res

    def getCommand(self):
        # This is not thread-safe (in the sense that multiple threads
        # cannot call this at once; it is thread-safe with multiple
        # command putters)
        assert threading.currentThread() == self.__getThread
        if self.__command is None:
            return None
        else:
            cmd = self.__command
            self.__command = None
            return cmd

    def putResponse(self, res):
        assert threading.currentThread() == self.__getThread
        self.__commandResponse = res
        self.__commandResponseReady.set()
