#! /usr/bin/env python
# Script that process stdout from pcf and creates IRI messages
#
# Copyright (C) 2014 Martin Holkovic
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import sys
import socket

import threading
import subprocess
import ctypes
import time
import json
import os
import inspect
import random
import signal
import string

from pcapy import *
from thread import *
from parser_functions import *
from parser_protocols import *
from subprocess import call


modules = {}
interfaces = {}
lastDowns = []

realSocket = socket
s = None


def handler(signum, frame):
    s.close()
    bye()
    sys.exit(0)


# odstraneni vsetkych informacii o module
def delete(socketx):
    global modules, interfaces
    
    if not socketx in modules: # uz sa zaznam vymazal
        return
    info = modules[socketx] # zisteni parameter pro rerun
    del modules[socketx] # zmazani ze zeznamu modulu
    
    for threadId in interfaces:
        maximum = len(interfaces[threadId][1])
        minus = 0
        for protocol in range(0, maximum):
            protocol -= minus
            if protocol == maximum - minus:
                break
            
            if socketx in interfaces[threadId][1][protocol][1]:
                interfaces[threadId][1][protocol][1].remove(socketx)
                if interfaces[threadId][1][protocol][1] == []:
                    interfaces[threadId][1].remove([interfaces[threadId][1][protocol][0],[]])
                    minus += 1
    
    return info # vratim info o moduly


# zmaze secky zaznamy kere nespadly za poslednych 600 sekund
def last_down_clear():
    global lastDowns
    for i in range(0, len(lastDowns)):
        if lastDowns[i][1]+600 < time.time():
            lastDowns.remove(lastDowns[i])


# v pripade ze este taky modul nebyl spusteny, vytvori sa zaznam
def last_down_add(getInterest, getProtocols):
    global lastDowns
    last_down_clear()
    moduleId = getInterest + "-" + getProtocols
    for i in range(0, len(lastDowns)):
        if lastDowns[i][0] == moduleId: # existuje uz zaznam
            return
    
    # zaznam este neexistuje
    lastDowns.append([moduleId, 0, 0])


# v pripade ze modul za poslednych 60 sec nespadel 3x znovuspustim modul
def rerun(moduleId, rerun):
    global lastDowns
    
    for i in range(0, len(lastDowns)):
        if lastDowns[i][0] == moduleId: # existuje uz zaznam
            if lastDowns[i][2]+60 < time.time(): # nespadel 3x za poslednych 60 sec
                lastDowns[i][2] = lastDowns[i][1]
                lastDowns[i][1] = time.time()
            else:
                return
    
    #print "rerun: " + rerun
    os.system("sudo python " + rerun + " &")
    #os.system("./test.py eth0 TCP > ~/Desktop/out.txt &")


# odeslani dat na socket
def send(socketx, data):
    try:
        socketx.send(data)
    except :#IOError, e:
        info = delete(socketx) # zmazem secky mista kde byl modul ulozeny
        socketx.close() # uzavriti modulu
        print lineno(), info
        print "Module who wants " + info["protocols"] + " from " + info["interfaces"] + " is down"
        rerun(info["interfaces"] + "-" + info["protocols"], info["rerun"])
        
        threadId = threading.current_thread().ident
        ctypes.pythonapi.PyThreadState_SetAsyncExc(threadId, None) # zabiti vlakna


# spojenie s 1 modulom
def module_thread(socketx, ip, port):
    global interfaces, modules
    threadId = threading.current_thread().ident
    
    getInterest = socketx.recv(256) # ziskani zeznamu rozhrani
    send(socketx, "OK")
    getProtocols = socketx.recv(256) # ziskani zeznamu protokolu
    send(socketx, "OK")
    getParameters = socketx.recv(256) # ziskani jak znovu spustit
    
    print 'Pripojeny modul ' + ip + ':' + str(port) + " zaujima " + getProtocols + " z rozhrani " + getInterest
    modules[socketx] = {"thread": threadId, "interfaces": getInterest, "protocols": getProtocols, "rerun": getParameters}
    last_down_add(getInterest, getProtocols)
    
    # pridani modulu k vsetkym interfacom o ktore ma zaujem
    protocolArray = string.split(getProtocols, ",")
    for newProtocol in protocolArray:
        added = False
        for threadId in interfaces:
            for protocol in range(0, len(interfaces[threadId][1])):
                if interfaces[threadId][1][protocol][0] == newProtocol:
                    interfaces[threadId][1][protocol][1].append(socketx)
                    added = True
            # protokol este nie je ulozeny
            if added == False:
                interfaces[threadId][1].append([newProtocol, [socketx]])
    
    
    data = None
    try:
        while data != "Bye": # cakam na ukoncenie modulu
            data = socketx.recv(256)
        print "Bye od " + ip + ":" + port# + " who wants " + getProtocols + " from " + getInterest
    except:
        pass
    
    delete(socketx)
    socketx.close()


# spracovanie packetu
def callback(hdr, data):
    threadId = threading.current_thread().ident # ziskanie ID vlakna
    if len(interfaces[threadId][1]) != 0: # neprazdne pole
        lastLayer, lowerLayers, header, appdata = parse(data) # rozparsovanie dat po najvyssiu vrstvu
        if lastLayer == "loopback":
            return
        for protocol in range(0, len(interfaces[threadId][1])): # prejdem vsetky protokoly ziadane na danom rozhrani
            if interfaces[threadId][1][protocol][0] == lastLayer: # aktualny paket obsahuje ziadany protokol
                appdata = str_to_hex(appdata) # prekonvertujem binarnu podobu dat do HEXa formatu
                jsonString = json.dumps([lowerLayers, header, {"data": appdata}]) # serializovanie dat
                data = str(len(jsonString)) + jsonString
                for socketx in interfaces[threadId][1][protocol][1]: # prejde vsetky sokety ktore ziadaju dany protokol
                    send(socketx, data)


# odpocuvanie na jednom interfaci
def listening_for_packets(interfaceName):
    global interfaces
    
    threadId = threading.current_thread().ident # zistenie ID vlakna
    
    interfaces[threadId] = [interfaceName, []]
    
    reader = open_live(interfaceName, 1500, 0, 100) # aktivovanie zachytavania paketov
    reader.setfilter("")
    reader.loop(0, callback)


# vytvori socket pre spojenie s modulmi
def create_socket(portNumber):
    global s
    s = realSocket.socket(realSocket.AF_INET, realSocket.SOCK_STREAM)
    #s.setsockopt(realSocket.SOL_SOCKET, realSocket.SO_REUSEADDR, 1)
    
    try:
        s.bind(('localhost', portNumber))
        s.listen(10) # !!! uz sem zabudel co znamena ta 10 !!!
    except:# obsadeny port
        return


# spusti odpocuvanie na kazdom interfaci zvlast 
def open_all_interfaces():
    print "Parser pocuva na rozhraniach:",
    for interfaceName in findalldevs(): # prejde vsetky rozhrania
        #if not (interfaceName in ["lo", "any", "nflog"] or interfaceName.startswith('usb')): # ignoruje rozhrania loopback, any, nflog a usb
        if not (interfaceName in ["any", "nflog"] or interfaceName.startswith('usb')): # ignoruje rozhrania any, nflog a usb
            start_new_thread(listening_for_packets ,(interfaceName,))
            print interfaceName,
    
    print ""


# vytvorenie socketu a cakanie na moduly
def waiting_for_modules():
    global s
    try:
        while True:
            socket, addr = s.accept() # cakanie na moduly
            ip = addr[0]
            port = str(addr[1])
            print 'Pripojeny novy modul z ' + ip + ':' + port
            try:
                start_new_thread(module_thread ,(socket, ip, port))
            except:
                print lineno()
                delete(socket)
                socket.close()
    except KeyboardInterrupt, e:
        s.close()
        return


# odeslani Bye vsetkym modulom
def bye():
    for socketx in modules:
        send(socketx, "Bye")
        socketx.close()
        ctypes.pythonapi.PyThreadState_SetAsyncExc(modules[socketx]["thread"], None) # ukoncenie vlakna
    
    print "Bye"


def run_parser_main(portNumber):
    os.system("python " + parserMainFilename + " " + str(portNumber) + " &")
    time.sleep(0.2) 
    return get_running_port()


def print_help():
    print "\tpython " + parserMainFilename + " start - spusti parser"
    print "\tpython " + parserMainFilename + " stop - slusne zastavi parser"
    print "\tpython " + parserMainFilename + " kill - hrubo (signalem SIGKILL) zabije proces"
    print "\tpython " + parserMainFilename + " - vypise napovedu a zisti status"
    
    portNumber = get_running_port()
    print "\t"
    
    if portNumber == "": # parser nie je spusteny
        print "\tstatus: parser nie je spusteny"
    else:
        pid = get_running_pid()
        print "\tstatus: parser je spusteny na porte " + portNumber + " s PID " + pid


def parser_start():
    portNumber = get_running_port()
    if portNumber == "": # parser nie je spusteny
        portNumber = run_parser_main(random.randint(10000, 60000))
        while portNumber == "":
            portNumber = run_parser_main(random.randint(10000, 60000))
        
        pid = get_running_pid()
        print "Parser sa spustil na porte " + portNumber + " s PID " + pid
    else: # parser je spusteny
        pid = get_running_pid()
        print "Parser uz je spusteny na porte " + portNumber + " s PID " + pid



# vyhleda proces parser a ukonci ho
def parser_stop(method):
    pid = get_running_pid()
    if pid != "":
        if method == "stop":
            os.system("kill " + pid)
        else:
            os.system("kill -9 " + pid)


if __name__ == "__main__":
    signal.signal(signal.SIGTERM, handler)
    realSocket = socket
    
    if len(sys.argv) > 1 and sys.argv[1] == "start":
        parser_start()
    elif len(sys.argv) > 1 and sys.argv[1] == "stop":
        parser_stop("stop")
    elif len(sys.argv) > 1 and sys.argv[1] == "kill":
        parser_stop("kill")
    elif len(sys.argv) > 1:
        portNumber = int(sys.argv[1])
        try:
            create_socket(portNumber)
            open_all_interfaces()
            waiting_for_modules()
        except KeyboardInterrupt: # v pripade ukonceni programu sa odesle Bye kazdemu modulu
            bye()
        except Exception:
            bye()
            raise
    else:
        print_help()
