import os
import sys
import string
import base64
import httplib
import time

#configurables
IDIGI_ADDR    = "app.idigi.com"         #hostname of iDigi Server to Connect to
USERNAME      = "tkim"                #username on that account
PASSWORD      = "password"              #password on that account
IP_LIST       = "iplist.txt"            #List of devices to upgrade
GENOUTPUT_CSV = "gen_output.csv"        #CSV file of what was actually done
ERROUTPUT_CSV = "err_output.csv"        #CSV file of what was actually done
WAIT_TIME     = 1800

FW_WANP_NR    = "82001166"
FW_WANP_PN     = FW_WANP_NR + "_J"
FW_WANP_FILE   = FW_WANP_PN + ".bin"
FW_WAN_NR     = "82001160"
FW_WAN_LT     = "M1"
FW_WAN_PN     = FW_WAN_NR + "_" + FW_WAN_LT
FW_WAN_FILE   = FW_WAN_PN + ".bin"

FW_NEMAP_NR    = "82001753"
FW_NEMAP_PN    = FW_NEMAP_NR + "_E"
FW_NEMAP_FILE  = FW_NEMAP_PN + ".bin"
FW_NEMA_NR    = "82001536"
FW_NEMA_LT    = "F2"
FW_NEMA_PN    = FW_NEMA_NR + "_" + FW_NEMA_LT
FW_NEMA_FILE  = FW_NEMA_PN + ".bin"

#non-configurables
DEV_REQUEST_DEVCORE = "/ws/DeviceCore"        #device list request
DEV_REQUEST_SCI = "/ws/sci"
sci_header = "<sci_request version=\"1.0\"><send_message><targets><device id=\""
sci_header_refresh = "<sci_request version=\"1.0\"><send_message cache=\"false\"><targets><device id=\""
sci_end_targets = "\"/></targets>"
sci_tail = "</send_message></sci_request>"
rci_get_mtu_x4 = "<rci_request version=\"1.1\"><query_setting><mobileppp/></query_setting></rci_request>"
rci_get_mtu_wan = "<rci_request version=\"1.1\"><query_setting><ppp index=\"130\"/></query_setting></rci_request>"
rci_update_mtu_x4 = "<rci_request version=\"1.1\"><set_setting><mobileppp><mru>1400</mru><mtu>1400</mtu></mobileppp></set_setting></rci_request>"
rci_update_mtu_wan = "<rci_request version=\"1.1\"><set_setting><ppp index=\"130\"><mru>1400</mru><mtu>1400</mtu></ppp></set_setting></rci_request>"
rci_reboot = "<rci_request version=\"1.1\"><reboot/></rci_request>"
rci_get_fw_ver = "<rci_request version=\"1.1\"><query_state><device_info/></query_state></rci_request>"
rci_generic_query = "<rci_request version=\"1.1\"><query_setting/></rci_request>" 

sci_update_header = "<sci_request version=\"1.0\"><update_firmware><targets><device id=\""
sci_update_end_targets = "\"/></targets><data>"
sci_update_tail = "</data></update_firmware></sci_request>"

DEV_STATE_DISCONNECTED      = "Disconnected"
DEV_STATE_UPDATED           = "Updated"
DEV_STATE_NOT_UPDATED       = "Not Updated"
DEV_STATE_FAILED_UPDATE     = "Failed Update"
PYTHON_DIR                  = "/WEB/python"
NO_DEV_FOUND                = "No Device Found"
DEV_UPDATE_SUCCESS          = "Success"


class device_class:
    #regular stats
    devConnectwareId = ""
    dpDeviceType = ""
    dpContact = ""
    dpFirmwareLevelDesc = ""
    dpLastConnectTime = ""
    dpLastKnownIp = ""
    dpConnectionStatus = ""    
    error_msg = ""
    #extended stats
    mtu_size = ""
    mru_size = ""


def rci_cmd(devID, rci_frame, cache_val):
    status = DEV_STATE_UPDATED

    connection = httplib.HTTPConnection(IDIGI_ADDR)
    connection.putrequest("POST", DEV_REQUEST_SCI)
    connection.putheader("Authorization", "Basic " + auth)
    connection.putheader("Content-type", "text/xml; charset=\"UTF-8\"")
    
    if cache_val == 1:
        scimsg = sci_header + devID + sci_end_targets + rci_frame + sci_tail
    else:
        scimsg = sci_header_refresh + devID + sci_end_targets + rci_frame + sci_tail
        
    connection.putheader("Content-length", "%d" % len(scimsg))    
    connection.endheaders()
    connection.send(scimsg)
    resp = connection.getresponse()
    respdata = resp.read()
    connection.close()
    
    output = respdata
    retA = respdata.find("<error")
    
    if retA >= 0: #error condition
        retB = respdata.find("</error>")
        status = "Error"
        output = respdata[retA:retB+8]
    
    return status, output


def fw_update_request(devID, data):
    connection = httplib.HTTPConnection(IDIGI_ADDR)
    connection.putrequest("POST", DEV_REQUEST_SCI)
    connection.putheader("Authorization", "Basic " + auth)
    connection.putheader("Content-type", "text/xml; charset=\"UTF-8\"")
    scimsg = sci_update_header + devID + sci_update_end_targets + data + sci_update_tail
    connection.putheader("Content-length", "%d" % len(scimsg))    
    connection.endheaders()
    connection.send(scimsg)
    resp = connection.getresponse()
    respdata = resp.read()
    connection.close()
    
    retA = respdata.find("<submitted/>")
      
    if retA >= 0:
        status = DEV_STATE_UPDATED
    else:
        status = "Error"

    return status, respdata

def checkStatus():
    print "Establishing connection to iDigi and requesting Device list"

    connection = httplib.HTTPConnection(IDIGI_ADDR)
    connection.putrequest("GET", DEV_REQUEST_DEVCORE)
    connection.putheader("Authorization", "Basic " + auth)
    connection.endheaders()
    connection.send("")
    response = connection.getresponse()
    data = response.read()
    
    currentdevices = 0
    remainingDevices = 1
    devlist = []
    connected_count = 0
    disconnected_count = 0
        
    while remainingDevices > 0:
        retA = data.find("<resultSize>")
        if retA < 0:
            print "[[Error]] " + data
            return [], 0, 0        

        retB = data.find("</resultSize>")
        resultSize = int(data[retA + 12:retB])
        currentdevices = currentdevices + resultSize
        
        retA = data.find("<remainingSize>")
        if retA > 0:
            retB = data.find("</remainingSize>")
            remainingDevices = int(data[retA + 15:retB])
        else:
            remainingDevices = 0

        print "Recieved " + str(resultSize) + " devices, " + str(remainingDevices) +" remaining"
    
        count = 0
        
        while count < resultSize:
            count = count + 1
            
            tempdevice = device_class()
            tempdevice.devConnectwareId = "Unknown Device ID"
            tempdevice.dpDeviceType = "Unknown Device Type"
            tempdevice.dpContact = "Unknown Contact"
            tempdevice.dpFirmwareLevelDesc = "Unknown Firmware"
            tempdevice.dpLastConnectTime = "Unknown Connect Time"
            tempdevice.dpLastKnownIp = "Unknown IP"
            tempdevice.dpConnectionStatus = "Unknown Connection Status"
            tempdevice.error_msg = ""
            #extended values set to default, found later
            tempdevice.mtu_size = "Unknown MTU Size"
            tempdevice.mru_size = "Unknown MRU Size"

            next_msg = data.find("</DeviceCore>")

            retA = data.find("<devConnectwareId>")
            if (retA > 0) and (retA < next_msg):
                retB = data.find("</devConnectwareId>")
                tempdevice.devConnectwareId = data[retA + 18:retB]

            retA = data.find("<dpDeviceType>")
            if (retA > 0) and (retA < next_msg):
                retB = data.find("</dpDeviceType>")
                tempdevice.dpDeviceType = data[retA + 14:retB]                

            retA = data.find("<dpContact>")
            if (retA > 0) and (retA < next_msg):
                retB = data.find("</dpContact>")
                tempdevice.dpContact = data[retA + 11:retB]

            retA = data.find("<dpFirmwareLevelDesc>")
            if (retA > 0) and (retA < next_msg):
                retB = data.find("</dpFirmwareLevelDesc>")
                tempdevice.dpFirmwareLevelDesc = data[retA + 21:retB]
  
            retA = data.find("<dpLastConnectTime>")
            if (retA > 0) and (retA < next_msg):
                retB = data.find("</dpLastConnectTime>")
                dpLastConnectTimeUTC = data[retA + 19:retB]
                tempdevice.dpLastConnectTime = dpLastConnectTimeUTC[11:19] + " " + dpLastConnectTimeUTC[5:7] + "-" + dpLastConnectTimeUTC[8:10] + "-" + dpLastConnectTimeUTC[:4]
            else:
                tempdevice.dpLastConnectTime = "Never Connected    "
        
            retA = data.find("<dpLastKnownIp>")
            if (retA > 0) and (retA < next_msg):
                retB = data.find("</dpLastKnownIp>")
                tempdevice.dpLastKnownIp = data[retA + 15:retB]
            
            retA = data.find("<dpConnectionStatus>")
            if (retA > 0) and (retA < next_msg):
                retB = data.find("</dpConnectionStatus>")
                dpConnectionStatusNum = data[retA + 20:retB]
        
                if dpConnectionStatusNum == "1":
                    tempdevice.dpConnectionStatus = "Connected"
                    connected_count = connected_count + 1 
                else:
                    tempdevice.dpConnectionStatus = DEV_STATE_DISCONNECTED
                    disconnected_count = disconnected_count + 1

            devlist.append(tempdevice)
            
            data = data[next_msg + 13:]

        if remainingDevices > 0:
            print "Sending another request for devices"
            connection.putrequest("GET", DEV_REQUEST_DEVCORE + "?start=" + str(currentdevices) + "&size=1000")
            connection.putheader("Authorization", "Basic " + auth)
            connection.endheaders()
            connection.send("")
            response = connection.getresponse()
            data = response.read()

    connection.close()
         
    return devlist, connected_count, disconnected_count


def parse_xml(string, substring):
    output = ""
    retA = string.find("<" + substring + ">")
    if retA > 0:
        retB = string.find("</" + substring + ">")
        output = string[retA + len(substring) + 2:retB]
    
    return output    


def process_device(dev):
    current_mtu = 0
    
    if dev.dpDeviceType == "Digi Connect WAN" or dev.dpDeviceType == "Digi Connect WAN Edge10":
        rci_get_mtu = rci_get_mtu_wan
        rci_update_mtu = rci_update_mtu_wan
        device_file_data = wan_file_data
        device_post_data = wan_post_data
        po_pn = FW_WANP_PN
        po_nr = FW_WANP_NR        
        fw_pn = FW_WAN_PN
        fw_nr = FW_WAN_NR
        fm_lt = FW_WAN_LT
    elif dev.dpDeviceType == "ConnectPort X4 NEMA":
        rci_get_mtu = rci_get_mtu_x4
        rci_update_mtu = rci_update_mtu_x4
        device_file_data = x4_file_data
        device_post_data = x4_post_data
        po_pn = FW_NEMAP_PN
        po_nr = FW_NEMAP_NR
        fw_pn = FW_NEMA_PN
        fw_nr = FW_NEMA_NR
        fm_lt = FW_NEMA_LT
    else:
        return #should never get here
    
    #### CHECK THE MTU and MRU VALUES ####
    status, output = rci_cmd(dev.devConnectwareId, rci_get_mtu, 1)
    if status != DEV_STATE_UPDATED:
        dev.error_msg = output
        error_list.append(dev)
        return       

    retstring = parse_xml(output, "mru")
    
    if retstring == "":
        status, output = rci_cmd(dev.devConnectwareId, rci_generic_query, 0)
        status, output = rci_cmd(dev.devConnectwareId, rci_get_mtu, 1)
        if status != DEV_STATE_UPDATED:
            dev.error_msg = output
            error_list.append(dev)
            return       
        retstring = parse_xml(output, "mru")
        
    if retstring != "":
        if retstring == "1500":
            status, output = rci_cmd(dev.devConnectwareId, rci_update_mtu, 1)
            if status != DEV_STATE_UPDATED:
                dev.error_msg = output
                error_list.append(dev)
                return      
    
            status, output = rci_cmd(dev.devConnectwareId, rci_get_mtu, 0)
            if status != DEV_STATE_UPDATED:
                dev.error_msg = output
                error_list.append(dev)
                return      

            retstring = parse_xml(output, "mru")
            if retstring != "":
                dev.mru_size = retstring
                retstring = parse_xml(output, "mtu")
                if retstring != "":
                    dev.mtu_size = retstring
                    current_mtu = 1
        else:
            dev.mru_size = retstring
            retstring = parse_xml(output, "mtu")
            if retstring != "":
                dev.mtu_size = retstring
                current_mtu = 1

    #### CHECK THE FIRMWARE VERSION ####
    status, output = rci_cmd(dev.devConnectwareId, rci_get_fw_ver, 1)
    if status != DEV_STATE_UPDATED:
        dev.error_msg = output
        error_list.append(dev)
        return
    
    firmware_version = parse_xml(output, "firmware")
    post_version = parse_xml(output, "post")
    
    firmware_list = firmware_version.split(" ")
    
    firmware_letter = firmware_list[1][firmware_list[1].find(fw_nr) + len(fw_nr) + 1:]
    
    if firmware_letter >= fm_lt:
        firmware_current = 1
    else:
        firmware_current = 0
    
    if (firmware_current == 1) and (current_mtu == 1):
        dev.error_msg = "Unit is Current!"
        updated_list.append(dev)
        return

    if firmware_current == 1:
        dev.error_msg = "Unit Has Current Firmware!"
        return

    if firmware_version.find(fw_nr) < 0:
        dev.error_msg = "Unknown firmware: " + output
        error_list.append(dev)
        return

    if post_version.find(po_nr) < 0:
        dev.error_msg = "Unknown POST!"
        error_list.append(dev)
        return

    status, output = rci_cmd(dev.devConnectwareId, rci_get_fw_ver, 0)
    if status != DEV_STATE_UPDATED:
        dev.error_msg = output
        error_list.append(dev)
        return
    
    firmware_version = parse_xml(output, "firmware")
    post_version = parse_xml(output, "post")
    
    firmware_list = firmware_version.split(" ")
    
    firmware_letter = firmware_list[1][firmware_list[1].find(fw_nr) + len(fw_nr) + 1:]
    
    if firmware_letter >= fm_lt:
        firmware_current = 1
    else:
        firmware_current = 0
    
    if (firmware_current == 1) and (current_mtu == 1):
        dev.error_msg = "Unit is Current!"
        updated_list.append(dev)
        return

    if firmware_current == 1:
        dev.error_msg = "Unit Has Current Firmware!"
        return
        
    if post_version.find(po_pn) < 0:
        status, output = fw_update_request(dev.devConnectwareId, device_post_data)
        if status != DEV_STATE_UPDATED:
            dev.error_msg = "Post update failed: " + output
            error_list.append(dev)
            return
            
        dev.error_msg = "Unit POST submitted, firmware update on next pass! Prev: " + post_version
        return
        
    status, output = fw_update_request(dev.devConnectwareId, device_file_data)
    if status != DEV_STATE_UPDATED:
        dev.error_msg = "Firmware update failed: " + output
        error_list.append(dev)
        return

    else:
        dev.error_msg = "Unit update submitted updated! Prev: " + firmware_version

#main
auth = base64.b64encode("%s:%s" % (USERNAME, PASSWORD))

fd = open(FW_WANP_FILE, "rb")
fileData = fd.read()
fd.close()
wan_post_data = base64.b64encode(fileData)

fd = open(FW_WAN_FILE, "rb")
fileData = fd.read()
fd.close()
wan_file_data = base64.b64encode(fileData)

fd = open(FW_NEMAP_FILE, "rb")
fileData = fd.read()
fd.close()
x4_post_data = base64.b64encode(fileData)

fd = open(FW_NEMA_FILE, "rb")
fileData = fd.read()
fd.close()
x4_file_data = base64.b64encode(fileData)

updated_list = []
prev_time = time.time()

while True: #need a way to elegantly exit
    devlist, connected_count, disconnected_count = checkStatus()
    
    error_list = []
    disconnected_list = []
    count = 0
    
    if len(devlist) == len(updated_list):
        break

    timestamp = str(time.time())
    fh = open(timestamp + GENOUTPUT_CSV, "w")
    
    for dev in devlist:
        print "[" + str(count)+ "]\tWorking on:\t" + dev.devConnectwareId
        count = count + 1
        updated_check = 0

        for x in range(len(updated_list)):
            if dev.devConnectwareId == updated_list[x].devConnectwareId:
                dev.mru_size = updated_list[x].mru_size
                dev.mtu_size = updated_list[x].mtu_size
                updated_check = 1
        
        if updated_check == 1:
            dev.error_msg = "Verified Updated!"
        
        elif dev.dpConnectionStatus == "Disconnected":
            dev.error_msg = "Disconnected"
            disconnected_list.append(dev)
        
        elif dev.devConnectwareId == "Unknown Device ID":
            dev.error_msg = "No Device ID!!!"
            error_list.append(dev)
        
        elif dev.dpDeviceType == "Digi Connect WAN" or dev.dpDeviceType == "ConnectPort X4 NEMA"\
            or dev.dpDeviceType == "Digi Connect WAN Edge10":
            process_device(dev)

        else:
            dev.error_msg = "Unknown Device Type!!!"
            error_list.append(dev)
            
        line = dev.devConnectwareId + "," + dev.dpLastKnownIp + "," + dev.dpLastConnectTime + "," + \
               dev.dpConnectionStatus + "," + dev.dpContact + "," + dev.dpDeviceType + "," + \
               dev.mtu_size + "," + dev.mru_size + "," + dev.dpFirmwareLevelDesc + "," + dev.error_msg + "\n"
        fh.write(line)

    fh.close()
    
    fh = open(timestamp + ERROUTPUT_CSV, "w")
    for dev in error_list:
        line = dev.devConnectwareId + "," + dev.dpLastKnownIp + "," + dev.dpLastConnectTime + "," + \
               dev.dpConnectionStatus + "," + dev.dpContact + "," + dev.dpDeviceType + "," + \
               dev.mtu_size + "," + dev.mru_size + "," + dev.dpFirmwareLevelDesc + "," + dev.error_msg + "\n"
        fh.write(line)
    fh.close()

    print "[" + time.strftime('%X %x %Z') + "] Complete"
    print "\tConnected: " + str(connected_count)
    print "\tDisconnected: " + str(disconnected_count)
    
    if prev_time + WAIT_TIME > time.time():
        time.sleep((prev_time + WAIT_TIME) - time.time())
    
    prev_time = time.time()

print "Program finished"
