# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
import socket
import time
import sys

from Peach.Engine.engine import Engine
from Peach.Engine.common import PeachException
from Peach.publisher import Publisher
from Peach.publisher import Timeout
from Peach.publisher import PublisherSoftException
from Peach.Utilities.common import *
import Peach


def Debug(msg):
    if Peach.Engine.engine.Engine.debug:
        print(msg)

#class Timeout(SoftException):
#	def __init__(self, msg):
#		self.msg = msg
#	
#	def __str__(self):
#		return self.msg

class Tcp(Publisher):
    """
    A simple TCP client publisher.
    """


    def __init__(self, host, port, timeout=0.25, throttle=0):
        """
        @type	host: string
        @param	host: Remote host
        @type	port: number
        @param	port: Remote port
        @type	timeout: number
        @param	timeout: How long to wait for reponse
        @type	throttle: number
        @param	throttle: How long to wait between connections
        """
        Publisher.__init__(self)
        self._host = host

        try:
            self._port = int(port)
        except:
            raise PeachException("The Tcp publisher parameter for port was not a valid number.")

        try:
            self._timeout = float(timeout)
        except:
            raise PeachException("The Tcp publisher parameter for timeout was not a valid number.")

        try:
            self._throttle = float(throttle)
        except:
            raise PeachException("The Tcp publisher parameter for throttle was not a valid number.")

        self._socket = None

    def start(self):
        pass

    def stop(self):
        self.close()

    def connect(self):
        """
        Create connection.
        """
        self.close()

        if self._throttle > 0:
            time.sleep(self._throttle)

        # Try connecting many times
        # before we crash.
        for i in range(30):
            try:
                self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                self._socket.connect((self._host, self._port))
                exception = None
                break

            except:
                self._socket = None
                exception = sys.exc_info()

            # Wait half sec and try again
            time.sleep(1)

        if self._socket is None:
            value = ""
            try:
                value = str(exception[1])
            except:
                pass

            raise PeachException("TCP onnection attempt failed: %s" % value)

        self.buff = ""
        self.pos = 0

    def close(self):
        """
        Close connection if open.
        """
        try:
            if self._socket is not None:
                self._socket.close()
        finally:
            self._socket = None

        self.buff = ""
        self.pos = 0

    def send(self, data):
        """
        Send data via sendall.

        @type	data: string
        @param	data: Data to send
        """

        if Peach.Engine.engine.Engine.debug:
            print(">>>>>>>>>>>>>>>>")
            print("tcp.Tcp.send():")
            printHex(data)

        try:
            self._socket.sendall(data)
        except:
            if Peach.Engine.engine.Engine.debug:
                print("Tcp: Sendall failed: " + str(sys.exc_info()[1]))
            raise PublisherSoftException("sendall failed: " + str(sys.exc_info()[1]))

    def _receiveBySize(self, size):
        """
        This is now a buffered receiver.

        @rtype: string
        @return: received data.
        """

        # Do we already a have it?
        if size + self.pos < len(self.buff):
            ret = self.buff[self.pos:self.pos + size]
            self.pos += size
            return ret

        # Only ask for the diff of what we don't already have
        diffSize = (self.pos + size) - len(self.buff)

        try:
            if Peach.Engine.engine.Engine.debug:
                print("Asking for %d, need %d, have %d" % (size, diffSize, len(self.buff) - self.pos))

            self._socket.settimeout(self._timeout)
            ret = self._socket.recv(diffSize)

            if not ret:
                # Socket was closed
                if Peach.Engine.engine.Engine.debug:
                    print("Socket is closed")

                raise PublisherSoftException("Socket is closed")

            if Peach.Engine.engine.Engine.debug:
                print("<<<<<<<<<<<<<<<<<")
                print("tcp.Tcp.receive():")
                printHex(ret)

            self.buff += ret

        except socket.error as e:
            if str(e).find('The socket operation could not complete without blocking') != -1:
                if Peach.Engine.engine.Engine.debug:
                    print("timed out waiting for data")

                raise Timeout(
                    "Timed out waiting for data [%d:%d:%d:%d]" % (len(self.buff), (size + self.pos), size, diffSize))

            elif str(e).find('An existing connection was forcibly') != -1:
                if Peach.Engine.engine.Engine.debug:
                    print("Socket was closed!")

                raise PublisherSoftException("Socket is closed")

            else:
                if Peach.Engine.engine.Engine.debug:
                    print("recv failed: " + str(sys.exc_info()[1]))

                raise PublisherSoftException("recv failed: " + str(sys.exc_info()[1]))

        finally:
            self._socket.settimeout(None)

        ret = self.buff[self.pos:]
        self.pos = len(self.buff)
        return ret

    def _receiveByAvailable(self):
        """
        Receive as much as possible prior to timeout.

        @rtype: string
        @return: received data.
        """
        self._socket.settimeout(self._timeout)

        try:
            ret = self._socket.recv(4096)

            if not ret:
                raise PublisherSoftException("Socket is closed")

            if Peach.Engine.engine.Engine.debug:
                print("<<<<<<<<<<<<<<<<<")
                print("tcp.Tcp.receive():")
                printHex(ret)

            self.buff += ret

        except socket.error as e:
            if str(e).find('The socket operation could not complete without blocking') == -1:
                pass

            else:
                raise PublisherSoftException("recv failed: " + str(sys.exc_info()[1]))

        self._socket.settimeout(None)

        ret = self.buff[self.pos:]
        self.pos = len(self.buff)
        return ret

    def receive(self, size=None):

        if size is None:
            return self._receiveByAvailable()
        else:
            return self._receiveBySize(size)


class TcpListener(Tcp):
    """
    A TCP Listener publisher.  This publisher
    supports the following state actions:

     * start - Start listening
     * stop - Stop listening
     * accept - Accept a client connection
     * close - Close a client connection
    """

    def __init__(self, host, port, timeout=0.25):
        Tcp.__init__(self, host, port, timeout)

        self._listen = None
        self._clientAddr = None

    def start(self):
        self.close()

        if self._listen is None:
            for i in range(3):
                try:
                    self._listen = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                    self._listen.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
                    self._listen.bind((self._host, self._port))
                    self._listen.listen(1)
                    exception = None
                    break

                except:
                    self._listen = None
                    exception = sys.exc_info()

                time.sleep(0.5)

            if self._listen is None:
                value = ""
                try:
                    value = str(exception[1])
                except:
                    pass

                raise PeachException("TCP bind attempt failed: %s" % value)

        self.buff = ""
        self.pos = 0

    def stop(self):
        try:
            if self._socket is not None:
                self._socket.close()
        except:
            pass

        finally:
            self._socket = None

        # Avoid TIME_WAIT state by not closing our listener

        #try:
        #	if self._listen != None:
        #		self._listen.close()
        #except:
        #	pass
        #
        #finally:
        #	self._listen = None


    def accept(self):
        self.buff = ""
        self.pos = 0

        conn, addr = self._listen.accept()
        self._socket = conn
        self._clientAddr = addr

    def close(self):
        try:
            if self._socket is not None:
                self._socket.close()
        except:
            pass
        finally:
            self._socket = None

    def connect(self):
        raise PeachException("Action 'connect' not supported")


try:
    import win32gui, win32con, win32process
    import sys, time, os, signal

    class TcpListenerLaunchGui(TcpListener):
        """
        Does TcpListener goodness and also can laun a program.  After
        some defined amount of time we will try and close the
        GUI application by sending WM_CLOSE than kill it.
        """

        def __init__(self, host, port, windowname, timeout=0.25):
            TcpListener.__init__(self, host, port, timeout)
            self._windowName = windowname

        def stop(self):
            self.closeApp(self._windowName)
            TcpListener.stop(self)

        def call(self, method, args):
            """
            Launch program to consume file

            @type	method: string
            @param	method: Command to execute
            @type	args: array of objects
            @param	args: Arguments to pass
            """

            realArgs = [method, "/c", method]
            for a in args:
                realArgs.append(a)

            #print "Spawning %s" % method
            os.spawnv(os.P_NOWAIT, os.path.join(os.getenv('SystemRoot'), 'system32', 'cmd.exe'), realArgs)


        @staticmethod
        def enumCallback(hwnd, windowName):
            """
            Will get called by win32gui.EnumWindows, once for each
            top level application window.
            """

            try:

                # Get window title
                title = win32gui.GetWindowText(hwnd)

                # Is this our guy?
                if title.find(windowName) == -1:
                    return

                (threadId, processId) = win32process.GetWindowThreadProcessId(hwnd)

                # Send WM_CLOSE message
                try:
                    win32gui.PostMessage(hwnd, win32con.WM_CLOSE, 0, 0)
                    win32gui.PostQuitMessage(hwnd)
                except:
                    pass

                # Give it upto 5 sec
                for i in range(100):
                    if win32process.GetExitCodeProcess(processId) != win32con.STILL_ACTIVE:
                        # Process exited already
                        return

                    time.sleep(0.25)

                try:
                    # Kill application
                    win32process.TerminateProcess(processId, 0)
                except:
                    pass
            except:
                pass

        def closeApp(self, title):
            """
            Close Application by window title
            """
            #print "CloseApp: %s" % title
            win32gui.EnumWindows(TcpListenerLaunchGui.enumCallback, title)

except:
    pass


class TcpProxyB(TcpListener):
    def __init__(self, host, port):
        TcpListener.__init__(self, host, port)


class TcpProxyA(Tcp):
    def __init__(self, host, port):
        Tcp.__init__(self, host, port)