#!/usr/bin/python3
# vim:expandtab:autoindent:tabstop=4:shiftwidth=4:filetype=python:tw=0

  #############################################################################
  #
  # Copyright (c) 2005 Dell Computer Corporation
  # Dual Licenced under GNU GPL and OSL
  #
  #############################################################################
"""smbios-wireless-ctl"""



# import arranged alphabetically
import gettext
import locale
import os
import sys
import traceback

# the following vars are all substituted on install
# this bin isnt byte-compiled, so this is ok
pythondir="/usr/lib/python3.9/site-packages"
clidir="/usr/share/smbios-utils"
# end vars

# import all local modules after this.
sys.path.insert(0,pythondir)
sys.path.insert(0,clidir)

import cli
from libsmbios_c import smbios_token, smbios, smi, system_info as sysinfo, localedir, GETTEXT_PACKAGE
from libsmbios_c.trace_decorator import traceLog, getLog

__VERSION__="2.4.3"

locale.setlocale(locale.LC_ALL, '')
gettext.install(GETTEXT_PACKAGE, localedir)

moduleLog = getLog()
verboseLog = getLog(prefix="verbose.")

class RuntimeControlUnsupported(Exception): pass

def command_parse():
    parser = cli.OptionParser(usage=__doc__, version=__VERSION__)

    parser.add_option('--info', action="store_true", default=False, help= _("Show wireless configuration (default)"))

    parser.add_option('--boot',    action="store_true", default=False, dest="boot", help= _("Set BIOS boot-time setting."))
    parser.add_option('--no-boot', action="store_false",               dest="boot", help= _("Do not set BIOS boot-time setting."))

    parser.add_option('--runtime',    action="store_true",  default=True, dest="runtime", help= _("Set BIOS runtime setting (default)."))
    parser.add_option( "--no-runtime", "--boot-only", '--boot_only',
                                      action="store_false",               dest="runtime", help= _("Do not set BIOS runtime setting."))

    parser.add_option('--wlan', action="store", type="int", default=None, dest="wlan",
            help= _("Set radio runtime status for wireless LAN"))
    parser.add_option('--bt',   action="store", type="int", default=None, dest="bt",
            help= _("Set radio runtime status for Bluetooth"))
    parser.add_option('--wwan', action="store", type="int", default=None, dest="wwan",
            help= _("Set radio runtime status for cellular (wireless WAN)"))
    parser.add_option('--uwb',  action="store", type="int", default=None, dest="uwb",
            help= _("Set radio runtime status for UWB"))
    parser.add_option('--wigig',action="store", type="int", default=None, dest="wigig",
            help= _("Set radio runtime status for WiGig"))

    parser.add_option('--sw_wlan', action="store", type="int", default=None, dest="sw_wlan",
            help= _("Set hardware switch so that it controls radio for wireless LAN"))
    parser.add_option('--sw_bt',   action="store", type="int", default=None, dest="sw_bt",
            help= _("Set hardware switch so that it controls radio for Bluetooth"))
    parser.add_option('--sw_wwan', action="store", type="int", default=None, dest="sw_wwan",
            help= _("Set hardware switch so that it controls radio for cellular (wireless WAN)"))
    parser.add_option('--sw_uwb',  action="store", type="int", default=None, dest="sw_uwb",
            help= _("Set hardware switch so that it controls radio for UWB"))
    parser.add_option('--sw_wigig',action="store", type="int", default=None, dest="sw_wigig",
            help= _("Set hardware switch so that it controls radio for WiGig"))

    parser.add_option('--sw_locator', action="store", type="int", default=None, dest="sw_locator",
            help= _("Enable or disable the WIFI locator switch"))

    parser.add_option('--st_wlan', action="store_true", default=False, dest="st_wlan",
            help= _("Get status for wireless LAN. 0 = enabled. 1 = disabled. 2 = not present. 3 = unsupported. 4 = unknown."))
    parser.add_option('--st_bt',   action="store_true", default=False, dest="st_bt",
            help= _("Get status for Bluetooth. 0 = enabled. 1 = disabled. 2 = not present. 3 = unsupported. 4 = unknown."))
    parser.add_option('--st_wwan', action="store_true", default=False, dest="st_wwan",
            help= _("Get status for cellular (wireless WAN). 0 = enabled. 1 = disabled. 2 = not present. 3 = unsupported. 4 = unknown."))
    parser.add_option('--st_uwb',  action="store_true", default=False, dest="st_uwb",
            help= _("Get status for UWB. 0 = enabled. 1 = disabled. 2 = not present. 3 = unsupported. 4 = unknown."))
    parser.add_option('--st_wigig',action="store_true", default=False, dest="st_wigig",
            help= _("Get status for WiGig. 0 = enabled. 1 = disabled. 2 = not present. 3 = unsupported. 4 = unknown."))

    parser.add_option('--st_locator', action="store", type="int", default=None, dest="st_locator",
            help= _("Get status for locator switch"))

    cli.addStdOptions(parser, passwordOpts=True, securityKeyOpt=True)
    return parser.parse_args()

radios = {
    "wlan": {
            "radioEnable" : 0x0180, "radioDisable" : 0x017F, "radioRuntimeId": 1,
            "switchEnable": 0x0186, "switchDisable": 0x0185, "switchRuntime" : (1, 1),
            "switch_bit": 0,
             "supported_bit": 2, "installed_bit": 8, "disabled_bit": 17, "name": _("WLAN"),
             "legacyEnable": 0x010c, "legacyDisable": 0x010d,
             "legacySwitchEnable": 0x0116, "legacySwitchDisable": 0x0115,
            },

    "bt":   {
            "radioEnable" : 0x0152, "radioDisable" : 0x0153, "radioRuntimeId": 2,
            "switchEnable": 0x0182, "switchDisable": 0x0181, "switchRuntime" : (1, 2),
            "switch_bit": 1,
             "supported_bit": 3, "installed_bit": 9, "disabled_bit": 18, "name": _("Bluetooth"),
            },

    "wwan": {
            "radioEnable" : 0x017C, "radioDisable" : 0x017B, "radioRuntimeId": 3,
            "switchEnable": 0x0184, "switchDisable": 0x0183, "switchRuntime" : (1, 4),
            "switch_bit": 2,
             "supported_bit": 4, "installed_bit": 10, "disabled_bit": 19, "name": _("WWAN"),
            },

    "uwb":  {
            "radioEnable" : 0x027A, "radioDisable" : 0x0279, "radioRuntimeId": 4,
            "switchEnable": 0x0278, "switchDisable": 0x0277, "switchRuntime" : (1, 8),
            "switch_bit": 3,
             "supported_bit": 6, "installed_bit": 11, "disabled_bit": 20, "name": _("UWB"),
            },

    "wigig":{
            "radioEnable" : 0x0000, "radioDisable" : 0x0000, "radioRuntimeId": 5,
            "switchEnable": 0x0380, "switchDisable": 0x037F, "switchRuntime" : (1, 16),
            "switch_bit": 4,
             "supported_bit": 7, "installed_bit": 12, "disabled_bit": 21, "name": _("WiGig"),
            },

    "locator": {
            "name": _("Locator"),
            "switch_bit": 8,
            "switchEnable": 0x017E, "switchDisable": 0x017D, "switchRuntime": (2, 1),
            },
    }

def radioBootCtl(radio, enable):
    print(_("Set boot settings for %s") % radio["name"])
    try:
        tok = radio["radioDisable"]
        legacy = radio.get("legacyDisable", None)
        if enable:
            tok = radio["radioEnable"]
            legacy = radio.get("legacyEnable", None)

        tokenTable = smbios_token.TokenTable()
        try:
            tokenTable[ tok ].activate()
        except IndexError as e:
            tokenTable[ legacy ].activate()
    except IndexError:
        print(_("\tThis system does not support boot time settings for %s") % radio["name"])

class InvalidConfig(Exception): pass

def radioRuntimeCtl(radio, enable):
    try:
        print(_("Set runtime settings for %s") % radio['name'])
        tokenTable = smbios_token.TokenTable()
        if enable and not tokenTable[radio["radioEnable"]].isActive():
            raise InvalidConfig( _("Runtime setting has no effect when boot time config is disabled. Ignoring.") )

        disable = 1
        if enable: disable=0
        res = smi.simple_ci_smi( 17, 11, (1 | ((radio['radioRuntimeId'])<<8) | ((disable)<<16)) )
        if res[smi.cbRES1] != 0:
            raise RuntimeControlUnsupported( _("This system does not support runtime control of wireless settings.") )

    except (InvalidConfig, RuntimeControlUnsupported) as e:
        print("\t%s" % e)
        raise

def switchBootCtl(radio, enable):
    print(_("Set boot switch settings for %s") % radio["name"])

    try:
        switch = radio["switchDisable"]
        legacy = radio.get("legacySwitchDisable", None)
        if enable:
            switch = radio["switchEnable"]
            legacy = radio.get("legacySwitchEnable", None)

        tokenTable = smbios_token.TokenTable()
        try:
            tokenTable[switch].activate()
        except IndexError as e:
            tokenTable[legacy].activate()

    except IndexError:
        print("\t%s" % _("This system does not support boot-time wireless switch configuration for %s.") % radio["name"])

def switchRuntimeCtl(radio, enable):
    print(_("\nSet runtime switch settings for %s") % radio["name"])
    try:
        whichConfig, whichSwitch = radio["switchRuntime"]
        verboseLog.debug("config: %s switch: %s" % (whichConfig, whichSwitch))
        oldconfig=0
        if whichConfig == 1:
            res = smi.simple_ci_smi( 17, 11, 2 )
            if res[smi.cbRES1] != 0:
                raise RuntimeControlUnsupported( _("This system does not support runtime control of wireless switch settings.") )
            oldconfig = res[smi.cbRES2] & 0xFF

        verboseLog.debug("oldconfig %s" % oldconfig)
        newconfig = (oldconfig & ~whichSwitch)
        if enable:
            verboseLog.debug("enabling: 0x%08x -> 0x%08x" % (newconfig, (newconfig|whichSwitch))) 
            newconfig = newconfig | whichSwitch

        verboseLog.debug("set: 0x%08x" % (0x2 | (whichConfig << 8) | (newconfig << 16)))
        res = smi.simple_ci_smi( 17, 11, (0x2 | (whichConfig << 8) | (newconfig << 16)))
        verboseLog.debug("cbRES1: 0x%04x" % res[smi.cbRES1])
        verboseLog.debug("cbRES2: 0x%04x" % res[smi.cbRES2])
        verboseLog.debug("cbRES3: 0x%04x" % res[smi.cbRES3])
        verboseLog.debug("cbRES4: 0x%04x" % res[smi.cbRES4])
        if res[smi.cbRES1] != 0:
            raise RuntimeControlUnsupported( _("This system does not support runtime control of wireless switch settings.") )
    except (RuntimeControlUnsupported,) as e:
        print("\t%s" % e)

def onOff(thing, trueval="on", falseval="off"):
    if thing:
        return trueval
    else:
        return falseval

STATUS_ENABLED = 0
STATUS_DISABLED = 1
STATUS_NOT_PRESENT = 2
STATUS_UNSUPPORTED = 3
STATUS_UNKNOWN = 4

def wirelessInfo(radio):
    print(_("\nRadio Status for %s:") % radio["name"]);
    ret = STATUS_UNKNOWN
    tokenTable = smbios_token.TokenTable()
    try:
        res = smi.simple_ci_smi( 17, 11 )
        if res[smi.cbRES1] != 0:
            raise RuntimeControlUnsupported( _("This system does not support runtime control of wireless settings.") )

        try:
            print(onOff(tokenTable[radio["radioEnable"]].isActive(), _("\t%s enabled at boot"), _("\t%s disabled at boot")) % radio["name"])
        except (IndexError,) as e:
            print(_("\t%s boot-time setting not present") % radio["name"])

        if res[smi.cbRES2] & (1 << radio["supported_bit"]):
            print(_("\t%s supported") % radio["name"])

            if res[smi.cbRES2] & (1<< radio["disabled_bit"]):
                ret = STATUS_DISABLED;
                print(_("\t%s disabled") % radio["name"])
            else:
                ret = STATUS_ENABLED;
                print(_("\t%s enabled") % radio["name"])

            if res[smi.cbRES2] & (1<< radio["installed_bit"]):
                print(_("\t%s installed") % radio["name"])
            else:
                ret = STATUS_NOT_PRESENT;
                print(_("\t%s not installed") % radio["name"])

            try:
                print(onOff(tokenTable[radio["switchEnable"]].isActive(), _("\t%s controlled by wireless switch at boot"), _("\t%s not controlled by wireless switch at boot")) % radio["name"])
            except (IndexError,) as e:
                print(_("\t%s boot-time wireless switch setting not present") % radio["name"])

            switch = smi.simple_ci_smi( 17, 11, 2 )
            print(onOff((switch[smi.cbRES2] & (1<<radio["switch_bit"])), _("\t%s runtime switch control currently enabled"), _("\t%s runtime switch control currently disabled")) % radio["name"])

        else:
            ret = STATUS_UNSUPPORTED
            print(_("\t%s not supported") % radio["name"])
    except RuntimeControlUnsupported as e:
        print("\t%s" % e)

    # print legacy status, if avail:
    try:
        print(onOff(tokenTable[radio["legacyEnable"]], "\t%s legacy boot setting enabled", "\t%s legacy boot setting disabled" ) % radio["name"])
        print(onOff(tokenTable[radio["legacySwitchEnable"]], _("\t%s legacy switch enabled"), _("\t%s legacy switch disabled") ) % radio["name"])
    except (IndexError, KeyError): 
        # token doesnt exist, or we dont have legacy settings for this radio, it's ok.
        pass

    print(_("\tStatus Code: %d") % ret)

def outputWirelessInfo():
    print(_("Wireless Info:"))
    try:
        res = smi.simple_ci_smi( 17, 11 )
        if res[smi.cbRES1] != 0:
            raise RuntimeControlUnsupported( _("This system does not support runtime control of wireless settings.") )
        if res[smi.cbRES2] & (1<<0):
            print(_("\tHardware switch supported"))
            print("\tHardware switch is %s" % onOff(res[smi.cbRES2] & (1 << 16), "On", "Off"))
        else:
            print(_("\tHardware switch not supported"))

        print(onOff(res[smi.cbRES2] & (1<<1), _("\tWiFi Locator supported"), _("\tWiFi Locator not supported")))
        print(onOff(res[smi.cbRES2] & (1<<5), _("\tWireless Keyboard supported"), _("\tWireless Keyboard not supported")))
        print(_("\tNVRAM Size: %d bytes") % res[smi.cbRES3])
        print(_("\tNVRAM format version: %d") % (res[smi.cbRES4] & 0xFF))
    except RuntimeControlUnsupported as e:
        print("\t%s" % e)

def outputSwitchStatus():
    print(_("\nWireless Switch Configuration"))
    try:
        res = smi.simple_ci_smi( 17, 11, 2 )
        if res[smi.cbRES1] != 0:
            raise RuntimeControlUnsupported( _("This system does not support runtime control of wireless switch settings.") )
        print(_("\tWLAN switch control: %s") %      onOff((res[smi.cbRES2] & (1<<0)), _("on"), _("off")))
        print(_("\tBluetooth switch control: %s") % onOff((res[smi.cbRES2] & (1<<1)), _("on"), _("off")))
        print(_("\tWWAN switch control: %s") %      onOff((res[smi.cbRES2] & (1<<2)), _("on"), _("off")))
        print(_("\tUWB switch control: %s") %       onOff((res[smi.cbRES2] & (1<<3)), _("on"), _("off")))
        print(_("\tWiGig switch control: %s") %     onOff((res[smi.cbRES2] & (1<<4)), _("on"), _("off")))
        print(_("\tswitch config: %s") %            onOff((res[smi.cbRES2] & (1<<7)), _("locked"), _("not locked")))
        print(_("\tWiFi locator: %s") %             onOff((res[smi.cbRES2] & (1<<8)), _("enabled"), _("disabled")))
        print(_("\tWiFi Locator config: %s") %      onOff((res[smi.cbRES2] & (1<<15)), _("locked"), _("not locked")))
    except RuntimeControlUnsupported as e:
        print("\t%s" % e)


def main():
    exit_code = 0
    (options, args) = command_parse()
    cli.setup_std_options(options)

    # need to do one or the other.
    if not options.runtime:
        options.boot=1

    try:
        if options.info:
            print(_("Libsmbios version : %s") % sysinfo.get_library_version_string())
            print(_("smbios-wireless-ctl version : %s") % __VERSION__)
            outputWirelessInfo()
            options.st_wlan = 1
            options.st_bt = 1
            options.st_wwan = 1
            options.st_uwb = 1
            options.st_wigig = 1
            options.st_locator = 1

        for i in ("wlan", "bt", "wwan", "uwb", "wigig"):
            if getattr(options, i, None) is not None:
                if options.boot:
                    radioBootCtl(radios[i], getattr(options, i))
                if options.runtime:
                    radioRuntimeCtl(radios[i], getattr(options, i))
                setattr(options, "st_%s" % i, 1)

            if getattr(options, "sw_%s" % i, None) is not None:
                if options.boot:
                    switchBootCtl(radios[i], getattr(options, "sw_%s" % i))
                if options.runtime:
                    switchRuntimeCtl(radios[i], getattr(options, "sw_%s" % i))
                setattr(options, "st_%s" % i, 1)

            if getattr(options, "st_%s" % i, None):
                wirelessInfo(radios[i])

        if options.sw_locator is not None:
            switchRuntimeCtl(radios["locator"], options.sw_locator)
            options.st_locator = 1

        if options.st_locator:
            outputSwitchStatus()


    except (smi.SMIExecutionError, ) as e:
        exit_code=3
        moduleLog.info( _("ERROR: Could not execute SMI.") )
        verboseLog.info( _("The smi library returned this error:") )
        verboseLog.info( str(e) )
        moduleLog.info( cli.standardFailMessage )

    except (smbios.TableParseError, smbios_token.TokenTableParseError) as e:
        exit_code=3
        moduleLog.info( _("ERROR: Could not parse system SMBIOS table.") )
        verboseLog.info( _("The smbios library returned this error:") )
        verboseLog.info( str(e) )
        moduleLog.info( cli.standardFailMessage )

    except (smbios_token.TokenManipulationFailure,) as e:
        exit_code=4
        moduleLog.info( _("ERROR: Could not manipulate system token.") )
        verboseLog.info( _("The token library returned this error:") )
        verboseLog.info( str(e) )
        moduleLog.info( cli.standardFailMessage )


    return exit_code

if __name__ == "__main__":
    sys.exit( main() )



# cbSelect 17, Value 11
#
# Return Wireless Info
# cbArg1, byte0 = 0x00
#
#     cbRes1 Standard return codes (0, -1, -2)
#     cbRes2 Info bit flags:
#
#     0 Hardware switch supported (1)
#     1 WiFi locator supported (1)
#     2 WLAN supported (1)
#     3 Bluetooth (BT) supported (1)
#     4 WWAN supported (1)
#     5 Wireless KBD supported (1)
#     6 Uw b supported (1)
#     7 WiGig supported (1)
#     8 WLAN installed (1)
#     9 BT installed (1)
#     10 WWAN installed (1)
#     11 Uw b installed (1)
#     12 WiGig installed (1)
#     13-15 Reserved (0)
#     16 Hardware (HW) switch is On (1)
#     17 WLAN disabled (1)
#     18 BT disabled (1)
#     19 WWAN disabled (1)
#     20 Uw b disabled (1)
#     21 WiGig disabled (1)
#     20-31 Reserved (0)
#
#     cbRes3 NVRAM size in bytes
#     cbRes4, byte 0 NVRAM format version number
#
#
# Set QuickSet Radio Disable Flag
#     cbArg1, byte0 = 0x01
#     cbArg1, byte1
#     Radio ID     value:
#     0        Radio Status
#     1        WLAN ID
#     2        BT ID
#     3        WWAN ID
#     4        UWB ID
#     5        WIGIG ID
#     cbArg1, byte2    Flag bits:
#             0 QuickSet disables radio (1)
#             1-7 Reserved (0)
#
#     cbRes1    Standard return codes (0, -1, -2)
#     cbRes2    QuickSet (QS) radio disable bit map:
#     0 QS disables WLAN
#     1 QS disables BT
#     2 QS disables WWAN
#     3 QS disables UWB
#     4 QS disables WIGIG
#     5-31 Reserved (0)
#
# Wireless Switch Configuration
#     cbArg1, byte0 = 0x02
#
#     cbArg1, byte1
#     Subcommand:
#     0 Get config
#     1 Set config
#     2 Set WiFi locator enable/disable
#     cbArg1,byte2
#     Switch settings (if byte 1==1):
#     0 WLAN sw itch control (1)
#     1 BT sw itch control (1)
#     2 WWAN sw itch control (1)
#     3 UWB sw itch control (1)
#     4 WiGig sw itch control (1)
#     5-7 Reserved (0)
#    cbArg1, byte2 Enable bits (if byte 1==2):
#     0 Enable WiFi locator (1)
#
#    cbRes1     Standard return codes (0, -1, -2)
#    cbRes2 QuickSet radio disable bit map:
#     0 WLAN controlled by sw itch (1)
#     1 BT controlled by sw itch (1)
#     2 WWAN controlled by sw itch (1)
#     3 UWB controlled by sw itch (1)
#     4 WiGig controlled by sw itch (1)
#     5-6 Reserved (0)
#     7 Wireless sw itch config locked (1)
#     8 WiFi locator enabled (1)
#     9-14 Reserved (0)
#     15 WiFi locator setting locked (1)
#     16-31 Reserved (0)
#
# Read Local Config Data (LCD)
#     cbArg1, byte0 = 0x10
#     cbArg1, byte1 NVRAM index low byte
#     cbArg1, byte2 NVRAM index high byte
#     cbRes1 Standard return codes (0, -1, -2)
#     cbRes2 4 bytes read from LCD[index]
#     cbRes3 4 bytes read from LCD[index+4]
#     cbRes4 4 bytes read from LCD[index+8]
#
# Write Local Config Data (LCD)
#     cbArg1, byte0 = 0x11
#     cbArg1, byte1 NVRAM index low byte
#     cbArg1, byte2 NVRAM index high byte
#     cbArg2 4 bytes to w rite at LCD[index]
#     cbArg3 4 bytes to w rite at LCD[index+4]
#     cbArg4 4 bytes to w rite at LCD[index+8]
#     cbRes1 Standard return codes (0, -1, -2)
#
# Populate Local Config Data from NVRAM
#     cbArg1, byte0 = 0x12
#     cbRes1 Standard return codes (0, -1, -2)
#
# Commit Local Config Data to NVRAM
#     cbArg1, byte0 = 0x13
#     cbRes1 Standard return codes (0, -1, -2)
