<commit_before># vim: tabstop=4 shiftwidth=4 softtabstop=4

# Copyright 2013 IBM Corporation
# Copyright 2015-2017 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# This represents the low layer message framing portion of IPMI

from itertools import chain
import pyghmi.constants as const
import pyghmi.exceptions as exc

import pyghmi.ipmi.events as sel
import pyghmi.ipmi.fru as fru
from pyghmi.ipmi.oem.lookup import get_oem_handler

try:
    from pyghmi.ipmi.private import session
except ImportError:
    session = None
try:
    from pyghmi.ipmi.private import localsession
except ImportError:
    localsession = None
import pyghmi.ipmi.private.util as pygutil
import pyghmi.ipmi.sdr as sdr
import socket
import struct

try:
    range = xrange
except NameError:
    pass
try:
    buffer
except NameError:
    buffer = memoryview

boot_devices = {
    'net': 4,
    'network': 4,
    'pxe': 4,
    'hd': 8,
    'safe': 0xc,
    'cd': 0x14,
    'cdrom': 0x14,
    'optical': 0x14,
    'dvd': 0x14,
    'floppy': 0x3c,
    'default': 0x0,
    'setup': 0x18,
    'bios': 0x18,
    'f1': 0x18,
    1: 'network',
    2: 'hd',
    3: 'safe',
    5: 'optical',
    6: 'setup',
    15: 'floppy',
    0: 'default'
}

power_states = {
    "off": 0,
    "on": 1,
    "reset": 3,
    "diag": 4,
    "softoff": 5,
    "shutdown": 5,
    # NOTE(jbjohnso): -1 is not a valid direct boot state,
    #                 but here for convenience of 'in' statement
    "boot": -1,
}


def _mask_to_cidr(mask):
    maskn = struct.unpack_from('>I', mask)[0]
    cidr = 32
    while maskn & 0b1 == 0 and cidr > 0:
        cidr -= 1
        maskn >>= 1
    return cidr


def _cidr_to_mask(prefix):
    return struct.pack('>I', 2 ** prefix - 1 << (32 - prefix))


class Command(object):
    """Send IPMI commands to BMCs.

    This object represents a persistent session to an IPMI device (bmc) and
    allows the caller to reuse a single session to issue multiple commands.
    This class can be used in a synchronous (wait for answer and return) or
    asynchronous fashion (return immediately and provide responses by
    callbacks).  Synchronous mode is the default behavior.

    For asynchronous mode, simply pass in a callback function.  It is
    recommended to pass in an instance method to callback and ignore the
    callback_args parameter. However, callback_args can optionally be populated
    if desired.

    :param bmc: hostname or ip address of the BMC (default is local)
    :param userid: username to use to connect (default to no user)
    :param password: password to connect to the BMC (defaults to no password)
    :param onlogon: function to run when logon completes in an asynchronous
                    fashion.  This will result in a greenthread behavior.
    :param kg: Optional parameter to use if BMC has a particular Kg configured
    """

    def __init__(self, bmc=None, userid=None, password=<PASSWORD>, port=623,
                 onlogon=None, kg=None):
        # TODO(jbjohnso): accept tuples and lists of each parameter for mass
        # operations without pushing the async complexities up the stack
        self.onlogon = onlogon
        self.bmc = bmc
        self._sdr = None
        self._oem = None
        self._netchannel = None
        self._ipv6support = None
        self.certverify = None
        if bmc is None:
            self.ipmi_session = localsession.Session()
        elif onlogon is not None:
            self.ipmi_session = session.Session(bmc=bmc,
                                                userid=userid,
                                                password=password,
                                                onlogon=self.logged,
                                                port=port,
                                                kg=kg)
            # induce one iteration of the loop, now that we would be
            # prepared for it in theory
            session.Session.wait_for_rsp(0)
        else:
            self.ipmi_session = session.Session(bmc=bmc,
                                                userid=userid,
                                                password=password,
                                                port=port,
                                                kg=kg)

    def register_key_handler(self, callback, type='tls'):
        """Assign a verification handler for a public key

        When the library attempts to communicate with the management target
        using a non-IPMI protocol, it will try to verify a key.  This
        allows a caller to register a key handler for accepting or rejecting
        a public key/certificate.  The callback will be passed the peer public
        key or certificate.

        :param callback:  The function to call with public key/certificate
        :param type: Whether the callback is meant to handle 'tls' or 'ssh',
                     defaults to 'tls'
        """
        if type == 'tls':
            self.certverify = callback

    def logged(self, response):
        self.onlogon(response, self)
        self.onlogon = None

    @classmethod
    def eventloop(cls):
        while session.Session.wait_for_rsp():
            pass

    @classmethod
    def wait_for_rsp(cls, timeout):
        """Delay for no longer than timeout for next response.

        This acts like a sleep that exits on activity.

        :param timeout: Maximum number of seconds before returning
        """
        return session.Session.wait_for_rsp(timeout=timeout)

    def _get_device_id(self):
        response = self.raw_command(netfn=0x06, command=0x01)
        if 'error' in response:
            raise exc.IpmiException(response['error'], code=response['code'])
        return {
            'device_id': response['data'][0],
            'device_revision': response['data'][1] & 0b1111,
            'manufacturer_id': struct.unpack(
                '<I', struct.pack('3B', *response['data'][6:9]) + b'\x00')[0],
            'product_id': struct.unpack(
                '<H', struct.pack('2B', *response['data'][9:11]))[0],
        }

    def oem_init(self):
        """Initialize the command object for OEM capabilities

        A number of capabilities are either totally OEM defined or
        else augmented somehow by knowledge of the OEM.  This
        method does an interrogation to identify the OEM.

        """
        if self._oem:
            return
        self._oem = get_oem_handler(self._get_device_id(), self)

    def get_bootdev(self):
        """Get current boot device override information.

        Provides the current requested boot device.  Be aware that not all IPMI
        devices support this.  Even in BMCs that claim to, occasionally the
        BIOS or UEFI fail to honor it. This is usually only applicable to the
        next reboot.

        :raises: IpmiException on an error.
        :returns: dict --The response will be provided in the return as a dict
        """
        response = self.raw_command(netfn=0, command=9, data=(5, 0, 0))
        # interpret response per 'get system boot options'
        if 'error' in response:
            raise exc.IpmiException(response['error'])
        # this should only be invoked for get system boot option complying to
        # ipmi spec and targeting the 'boot flags' parameter
        assert (response['command'] == 9 and
                response['netfn'] == 1 and
                response['data'][0] == 1 and
                (response['data'][1] & 0b1111111) == 5)
        if (response['data'][1] & 0b10000000 or
                not response['data'][2] & 0b10000000):
            return {'bootdev': 'default', 'persistent': True}
        else:  # will consult data2 of the boot flags parameter for the data
            persistent = False
            uefimode = False
            if response['data'][2] & 0b1000000:
                persistent = True
            if response['data'][2] & 0b100000:
                uefimode = True
            bootnum = (response['data'][3] & 0b111100) >> 2
            bootdev = boot_devices.get(bootnum)
            if bootdev:
                return {'bootdev': bootdev,
                        'persistent': persistent,
                        'uefimode': uefimode}
            else:
                return {'bootdev': bootnum,
                        'persistent': persistent,
                        'uefimode': uefimode}

    def reseat_bay(self, bay):
        """Request the reseat of a bay

        Request the enclosure manager to reseat the system in a particular
        bay.

        :param bay: The bay identifier to reseat
        :return:
        """
        self.oem_init()
        self._oem.reseat_bay(bay)

    def set_power(self, powerstate, wait=False):
        """Request power state change (helper)

        :param powerstate:
                            * on -- Request system turn on
                            * off -- Request system turn off without waiting
                                     for OS to shutdown
                            * shutdown -- Have system request OS proper
                                          shutdown
                            * reset -- Request system reset without waiting for
                              OS
                            * boot -- If system is off, then 'on', else 'reset'
        :param wait: If True, do not return until system actually completes
                     requested state change for 300 seconds.
                     If a non-zero number, adjust the wait time to the
                     requested number of seconds
        :raises: IpmiException on an error
        :returns: dict -- A dict describing the response retrieved
        """
        if powerstate not in power_states:
            raise exc.InvalidParameterValue(
                "Unknown power state %s requested" % powerstate)
        newpowerstate = powerstate
        response = self.raw_command(netfn=0, command=1)
        if 'error' in response:
            raise exc.IpmiException(response['error'])
        oldpowerstate = 'on' if (response['data'][0] & 1) else 'off'
        if oldpowerstate == newpowerstate:
            return {'powerstate': oldpowerstate}
        if newpowerstate == 'boot':
            newpowerstate = 'on' if oldpowerstate == 'off' else 'reset'
        response = self.raw_command(
            netfn=0, command=2, data=[power_states[newpowerstate]])
        if 'error' in response:
            raise exc.IpmiException(response['error'])
        lastresponse = {'pendingpowerstate': newpowerstate}
        waitattempts = 300
        if not isinstance(wait, bool):
            waitattempts = wait
        if (wait and
           newpowerstate in ('on', 'off', 'shutdown', 'softoff')):
            if newpowerstate in ('softoff', 'shutdown'):
                waitpowerstate = 'off'
            else:
                waitpowerstate = newpowerstate
            currpowerstate = None
            while currpowerstate != waitpowerstate and waitattempts > 0:
                response = self.raw_command(netfn=0, command=1, delay_xmit=1)
                if 'error' in response:
                    raise exc.IpmiException(response['error'])
                currpowerstate = 'on' if (response['data'][0] & 1) else 'off'
                waitattempts -= 1
            if currpowerstate != waitpowerstate:
                raise exc.IpmiException(
                    "System did not accomplish power state change")
            return {'powerstate': currpowerstate}
        else:
            return lastresponse

    def get_video_launchdata(self):
        """Get data required to launch a remote video session to target.

        This is a highly proprietary scenario, the return data may vary greatly
        host to host.  The return should be a dict describing the type of data
        and the data.  For example {'jnlp': jnlpstring}
        """
        self.oem_init()
        return self._oem.get_video_launchdata()

    def reset_bmc(self):
        """Do a cold reset in BMC
        """
        response = self.raw_command(netfn=6, command=2)
        if 'error' in response:
            raise exc.IpmiException(response['error'])

    def set_bootdev(self,
                    bootdev,
                    persist=False,
                    uefiboot=False):
        """Set boot device to use on next reboot (helper)

        :param bootdev:
                        *network -- Request network boot
                        *hd -- Boot from hard drive
                        *safe -- Boot from hard drive, requesting 'safe mode'
                        *optical -- boot from CD/DVD/BD drive
                        *setup -- Boot into setup utility
                        *default -- remove any IPMI directed boot device
                                    request
        :param persist: If true, ask that system firmware use this device
                        beyond next boot.  Be aware many systems do not honor
                        this
        :param uefiboot: If true, request UEFI boot explicitly.  Strictly
                         speaking, the spec sugests that if not set, the system
                         should BIOS boot and offers no "don't care" option.
                         In practice, this flag not being set does not preclude
                         UEFI boot on any system I've encountered.
        :raises: IpmiException on an error.
        :returns: dict or True -- If callback is not provided, the response
        """
        if bootdev not in boot_devices:
            return {'error': "Unknown bootdevice %s requested" % bootdev}
        bootdevnum = boot_devices[bootdev]
        # first, we disable timer by way of set system boot options,
        # then move on to set chassis capabilities
        # Set System Boot Options is netfn=0, command=8, data
        response = self.raw_command(netfn=0, command=8, data=(3, 8))
        if 'error' in response:
            raise exc.IpmiException(response['error'])
        bootflags = 0x80
        if uefiboot:
            bootflags |= 1 << 5
        if persist:
            bootflags |= 1 << 6
        if bootdevnum == 0:
            bootflags = 0
        data = (5, bootflags, bootdevnum, 0, 0, 0)
        response = self.raw_command(netfn=0, command=8, data=data)
        if 'error' in response:
            raise exc.IpmiException(response['error'])
        return {'bootdev': bootdev}

    def xraw_command(self, netfn, command, bridge_request=(), data=(),
                     delay_xmit=None, retry=True, timeout=None):
        """Send raw ipmi command to BMC, raising exception on error

        This is identical to raw_command, except it raises exceptions
        on IPMI errors and returns data as a buffer.  This is the recommend
        function to use.  The response['data'] being a buffer allows
        traditional indexed access as well as works nicely with
        struct.unpack_from when certain data is coming back.

        :param netfn: Net function number
        :param command: Command value
        :param bridge_request: The target slave address and channel number for
                               the bridge request.
        :param data: Command data as a tuple or list
        :param retry: Whether to retry this particular payload or not, defaults
                      to true.
        :param timeout: A custom time to wait for initial reply, useful for
                        a slow command.  This may interfere with retry logic.
        :returns: dict -- The response from IPMI device
        """
        rsp = self.ipmi_session.raw_command(netfn=netfn, command=command,
                                            bridge_request=bridge_request,
                                            data=data, delay_xmit=delay_xmit,
                                            retry=retry, timeout=timeout)
        if 'error' in rsp:
            raise exc.IpmiException(rsp['error'], rsp['code'])
        rsp['data'] = buffer(bytearray(rsp['data']))
        return rsp

    def raw_command(self, netfn, command, bridge_request=(), data=(),
                    delay_xmit=None, retry=True, timeout=None):
        """Send raw ipmi command to BMC

        This allows arbitrary IPMI bytes to be issued.  This is commonly used
        for certain vendor specific commands.

        Example: ipmicmd.raw_command(netfn=0,command=4,data=(5))

        :param netfn: Net function number
        :param command: Command value
        :param bridge_request: The target slave address and channel number for
                               the bridge request.
        :param data: Command data as a tuple or list
        :param retry: Whether or not to retry command if no response received.
                      Defaults to True
        :param timeout: A custom amount of time to wait for initial reply
        :returns: dict -- The response from IPMI device
        """
        return self.ipmi_session.raw_command(netfn=netfn, command=command,
                                             bridge_request=bridge_request,
                                             data=data, delay_xmit=delay_xmit,
                                             retry=retry, timeout=timeout)

    def get_power(self):
        """Get current power state of the managed system

        The response, if successful, should contain 'powerstate' key and
        either 'on' or 'off' to indicate current state.

        :returns: dict -- {'powerstate': value}
        """
        response = self.raw_command(netfn=0, command=1)
        if 'error' in response:
            raise exc.IpmiException(response['error'])
        assert (response['command'] == 1 and response['netfn'] == 1)
        powerstate = 'on' if (response['data'][0] & 1) else 'off'
        return {'powerstate': powerstate}

    def set_identify(self, on=True, duration=None):
        """Request identify light

        Request the identify light to turn off, on for a duration,
        or on indefinitely.  Other than error exceptions,

        :param on: Set to True to force on or False to force off
        :param duration: Set if wanting to request turn on for a duration
                         rather than indefinitely on
        """
        self.oem_init()
        try:
            self._oem.set_identify(on, duration)
            return
        except exc.UnsupportedFunctionality:
            pass
        if duration is not None:
            duration = int(duration)
            if duration > 255:
                duration = 255
            if duration < 0:
                duration = 0
            response = self.raw_command(netfn=0, command=4, data=[duration])
            if 'error' in response:
                raise exc.IpmiException(response['error'])
            return
        forceon = 0
        if on:
            forceon = 1
        if self.ipmi_session.ipmiversion < 2.0:
            # ipmi 1.5 made due with just one byte, make best effort
            # to imitate indefinite as close as possible
            identifydata = [255 * forceon]
        else:
            identifydata = [0, forceon]
        response = self.raw_command(netfn=0, command=4, data=identifydata)
        if 'error' in response:
            raise exc.IpmiException(response['error'])

    def init_sdr(self):
        """Initialize SDR

        Do the appropriate action to have a relevant sensor description
        repository for the current management controller
        """
        # For now, return current sdr if it exists and still connected
        # future, check SDR timestamp for continued relevance
        # further future, optionally support a cache directory/file
        # to store cached copies for given device id, product id, mfg id,
        # sdr timestamp, our data version revision, aux firmware revision,
        # and oem defined field
        if self._sdr is None:
            self._sdr = sdr.SDR(self)
        return self._sdr

    def get_event_log(self, clear=False):
        """Retrieve the log of events, optionally clearing

        The contents of the SEL are returned as an iterable.  Timestamps
        are given as local time, ISO 8601 (whether the target has an accurate
        clock or not).  Timestamps may be omitted for events that cannot be
        given a timestamp, leaving only the raw timecode to provide relative
        time information.  clear set to true will result in the log being
        cleared as it is returned.  This allows an atomic fetch and clear
        behavior so that no log entries will be lost between the fetch and
        clear actions.  There is no 'clear_event_log' function to encourage
        users to create code that is not at risk for losing events.

        :param clear:  Whether to remove the SEL entries from the target BMC
        """
        self.oem_init()
        return sel.EventHandler(self.init_sdr(), self).fetch_sel(self, clear)

    def decode_pet(self, specifictrap, petdata):
        """Decode PET to an event

        In IPMI, the alert format are PET alerts.  It is a particular set of
        data put into an SNMPv1 trap and sent. It bears no small resemblence
        to the SEL entries.  This function takes data that would have been
        received by an SNMP trap handler, and provides an event decode, similar
        to one entry of get_event_log.

        :param specifictrap: The specific trap, as either a bytearray or int
        :param petdata: An iterable of the octet data of varbind for
                        1.3.6.1.4.1.3183.1.1.1
        :returns: A dict event similar to one iteration of get_event_log
        """
        self.oem_init()
        return sel.EventHandler(self.init_sdr(), self).decode_pet(specifictrap,
                                                                  petdata)

    def get_inventory_descriptions(self):
        """Retrieve list of things that could be inventoried

        This permits a caller to examine the available items
        without actually causing the inventory data to be gathered.  It
        returns an iterable of string descriptions
        """
        yield "System"
        if self._sdr is None:
            self._sdr = sdr.SDR(self)
        for fruid in self._sdr.fru:
            yield self._sdr.fru[fruid].fru_name
        self.oem_init()
        for compname in self._oem.get_oem_inventory_descriptions():
            yield compname

    def get_inventory_of_component(self, component):
        """Retrieve inventory of a component

        Retrieve detailed inventory information for only the requested
        component.
        """
        self.oem_init()
        if component == 'System':
            return self._get_zero_fru()
        if self._sdr is None:
            self._sdr = sdr.SDR(self)
        for fruid in self._sdr.fru:
            if self._sdr.fru[fruid].fru_name == component:
                return self._oem.process_fru(fru.FRU(
                    ipmicmd=self, fruid=fruid, sdr=self._sdr.fru[fruid]).info)
        return self._oem.get_inventory_of_component(component)

    def _get_zero_fru(self):
        # It is expected that a manufacturer matches SMBIOS to IPMI
        # get system uuid return data.  If a manufacturer does not
        # do so, they should handle either deletion or fixup in the
        # OEM processing pass.  Code optimistically assumes that if
        # data is returned, than the vendor is properly using it.
        zerofru = fru.FRU(ipmicmd=self).info
        if zerofru is None:
            zerofru = {}
        guiddata = self.raw_command(netfn=6, command=0x37)
        if 'error' not in guiddata:
            zerofru['UUID'] = pygutil.decode_wireformat_uuid(
                guiddata['data'])
        # Add some fields returned by get device ID command to FRU 0
        # Also rename them to something more in line with FRU 0 field naming
        # standards
        device_id = self._get_device_id()
        device_id['Device ID'] = device_id.pop('device_id')
        device_id['Device Revision'] = device_id.pop('device_revision')
        device_id['Manufacturer ID'] = device_id.pop('manufacturer_id')
        device_id['Product ID'] = device_id.pop('product_id')
        zerofru.update(device_id)
        return self._oem.process_fru(zerofru)

    def get_inventory(self):
        """Retrieve inventory of system

        Retrieve inventory of the targeted system.  This frequently includes
        serial numbers, sometimes hardware addresses, sometimes memory modules
        This function will retrieve whatever the underlying platform provides
        and apply some structure.  Iterating over the return yields tuples
        of a name for the inventoried item and dictionary of descriptions
        or None for items not present.
        """
        self.oem_init()
        yield ("System", self._get_zero_fru())
        if self._sdr is None:
            self._sdr = sdr.SDR(self)
        for fruid in self._sdr.fru:
            fruinf = fru.FRU(
                ipmicmd=self, fruid=fruid, sdr=self._sdr.fru[fruid]).info
            if fruinf is not None:
                fruinf = self._oem.process_fru(fruinf)
            yield (self._sdr.fru[fruid].fru_name, fruinf)
        for componentpair in self._oem.get_oem_inventory():
            yield componentpair

    def get_leds(self):
        """Get LED status information

        This provides a detailed view of the LEDs of the managed system.
        """
        self.oem_init()
        return self._oem.get_leds()

    def get_ntp_enabled(self):
        self.oem_init()
        return self._oem.get_ntp_enabled()

    def set_ntp_enabled(self, enable):
        self.oem_init()
        return self._oem.set_ntp_enabled(enable)

    def get_ntp_servers(self):<commit_msg>Sort the inventory data<commit_after># vim: tabstop=4 shiftwidth=4 softtabstop=4

# Copyright 2013 IBM Corporation
# Copyright 2015-2017 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# This represents the low layer message framing portion of IPMI

from itertools import chain
import pyghmi.constants as const
import pyghmi.exceptions as exc

import pyghmi.ipmi.events as sel
import pyghmi.ipmi.fru as fru
from pyghmi.ipmi.oem.lookup import get_oem_handler

try:
    from pyghmi.ipmi.private import session
except ImportError:
    session = None
try:
    from pyghmi.ipmi.private import localsession
except ImportError:
    localsession = None
import pyghmi.ipmi.private.util as pygutil
import pyghmi.ipmi.sdr as sdr
import socket
import struct

try:
    range = xrange
except NameError:
    pass
try:
    buffer
except NameError:
    buffer = memoryview

boot_devices = {
    'net': 4,
    'network': 4,
    'pxe': 4,
    'hd': 8,
    'safe': 0xc,
    'cd': 0x14,
    'cdrom': 0x14,
    'optical': 0x14,
    'dvd': 0x14,
    'floppy': 0x3c,
    'default': 0x0,
    'setup': 0x18,
    'bios': 0x18,
    'f1': 0x18,
    1: 'network',
    2: 'hd',
    3: 'safe',
    5: 'optical',
    6: 'setup',
    15: 'floppy',
    0: 'default'
}

power_states = {
    "off": 0,
    "on": 1,
    "reset": 3,
    "diag": 4,
    "softoff": 5,
    "shutdown": 5,
    # NOTE(jbjohnso): -1 is not a valid direct boot state,
    #                 but here for convenience of 'in' statement
    "boot": -1,
}


def _mask_to_cidr(mask):
    maskn = struct.unpack_from('>I', mask)[0]
    cidr = 32
    while maskn & 0b1 == 0 and cidr > 0:
        cidr -= 1
        maskn >>= 1
    return cidr


def _cidr_to_mask(prefix):
    return struct.pack('>I', 2 ** prefix - 1 << (32 - prefix))


class Command(object):
    """Send IPMI commands to BMCs.

    This object represents a persistent session to an IPMI device (bmc) and
    allows the caller to reuse a single session to issue multiple commands.
    This class can be used in a synchronous (wait for answer and return) or
    asynchronous fashion (return immediately and provide responses by
    callbacks).  Synchronous mode is the default behavior.

    For asynchronous mode, simply pass in a callback function.  It is
    recommended to pass in an instance method to callback and ignore the
    callback_args parameter. However, callback_args can optionally be populated
    if desired.

    :param bmc: hostname or ip address of the BMC (default is local)
    :param userid: username to use to connect (default to no user)
    :param password: password to connect to the BMC (defaults to no password)
    :param onlogon: function to run when logon completes in an asynchronous
                    fashion.  This will result in a greenthread behavior.
    :param kg: Optional parameter to use if BMC has a particular Kg configured
    """

    def __init__(self, bmc=None, userid=None, password=<PASSWORD>, port=623,
                 onlogon=None, kg=None):
        # TODO(jbjohnso): accept tuples and lists of each parameter for mass
        # operations without pushing the async complexities up the stack
        self.onlogon = onlogon
        self.bmc = bmc
        self._sdr = None
        self._oem = None
        self._netchannel = None
        self._ipv6support = None
        self.certverify = None
        if bmc is None:
            self.ipmi_session = localsession.Session()
        elif onlogon is not None:
            self.ipmi_session = session.Session(bmc=bmc,
                                                userid=userid,
                                                password=password,
                                                onlogon=self.logged,
                                                port=port,
                                                kg=kg)
            # induce one iteration of the loop, now that we would be
            # prepared for it in theory
            session.Session.wait_for_rsp(0)
        else:
            self.ipmi_session = session.Session(bmc=bmc,
                                                userid=userid,
                                                password=password,
                                                port=port,
                                                kg=kg)

    def register_key_handler(self, callback, type='tls'):
        """Assign a verification handler for a public key

        When the library attempts to communicate with the management target
        using a non-IPMI protocol, it will try to verify a key.  This
        allows a caller to register a key handler for accepting or rejecting
        a public key/certificate.  The callback will be passed the peer public
        key or certificate.

        :param callback:  The function to call with public key/certificate
        :param type: Whether the callback is meant to handle 'tls' or 'ssh',
                     defaults to 'tls'
        """
        if type == 'tls':
            self.certverify = callback

    def logged(self, response):
        self.onlogon(response, self)
        self.onlogon = None

    @classmethod
    def eventloop(cls):
        while session.Session.wait_for_rsp():
            pass

    @classmethod
    def wait_for_rsp(cls, timeout):
        """Delay for no longer than timeout for next response.

        This acts like a sleep that exits on activity.

        :param timeout: Maximum number of seconds before returning
        """
        return session.Session.wait_for_rsp(timeout=timeout)

    def _get_device_id(self):
        response = self.raw_command(netfn=0x06, command=0x01)
        if 'error' in response:
            raise exc.IpmiException(response['error'], code=response['code'])
        return {
            'device_id': response['data'][0],
            'device_revision': response['data'][1] & 0b1111,
            'manufacturer_id': struct.unpack(
                '<I', struct.pack('3B', *response['data'][6:9]) + b'\x00')[0],
            'product_id': struct.unpack(
                '<H', struct.pack('2B', *response['data'][9:11]))[0],
        }

    def oem_init(self):
        """Initialize the command object for OEM capabilities

        A number of capabilities are either totally OEM defined or
        else augmented somehow by knowledge of the OEM.  This
        method does an interrogation to identify the OEM.

        """
        if self._oem:
            return
        self._oem = get_oem_handler(self._get_device_id(), self)

    def get_bootdev(self):
        """Get current boot device override information.

        Provides the current requested boot device.  Be aware that not all IPMI
        devices support this.  Even in BMCs that claim to, occasionally the
        BIOS or UEFI fail to honor it. This is usually only applicable to the
        next reboot.

        :raises: IpmiException on an error.
        :returns: dict --The response will be provided in the return as a dict
        """
        response = self.raw_command(netfn=0, command=9, data=(5, 0, 0))
        # interpret response per 'get system boot options'
        if 'error' in response:
            raise exc.IpmiException(response['error'])
        # this should only be invoked for get system boot option complying to
        # ipmi spec and targeting the 'boot flags' parameter
        assert (response['command'] == 9 and
                response['netfn'] == 1 and
                response['data'][0] == 1 and
                (response['data'][1] & 0b1111111) == 5)
        if (response['data'][1] & 0b10000000 or
                not response['data'][2] & 0b10000000):
            return {'bootdev': 'default', 'persistent': True}
        else:  # will consult data2 of the boot flags parameter for the data
            persistent = False
            uefimode = False
            if response['data'][2] & 0b1000000:
                persistent = True
            if response['data'][2] & 0b100000:
                uefimode = True
            bootnum = (response['data'][3] & 0b111100) >> 2
            bootdev = boot_devices.get(bootnum)
            if bootdev:
                return {'bootdev': bootdev,
                        'persistent': persistent,
                        'uefimode': uefimode}
            else:
                return {'bootdev': bootnum,
                        'persistent': persistent,
                        'uefimode': uefimode}

    def reseat_bay(self, bay):
        """Request the reseat of a bay

        Request the enclosure manager to reseat the system in a particular
        bay.

        :param bay: The bay identifier to reseat
        :return:
        """
        self.oem_init()
        self._oem.reseat_bay(bay)

    def set_power(self, powerstate, wait=False):
        """Request power state change (helper)

        :param powerstate:
                            * on -- Request system turn on
                            * off -- Request system turn off without waiting
                                     for OS to shutdown
                            * shutdown -- Have system request OS proper
                                          shutdown
                            * reset -- Request system reset without waiting for
                              OS
                            * boot -- If system is off, then 'on', else 'reset'
        :param wait: If True, do not return until system actually completes
                     requested state change for 300 seconds.
                     If a non-zero number, adjust the wait time to the
                     requested number of seconds
        :raises: IpmiException on an error
        :returns: dict -- A dict describing the response retrieved
        """
        if powerstate not in power_states:
            raise exc.InvalidParameterValue(
                "Unknown power state %s requested" % powerstate)
        newpowerstate = powerstate
        response = self.raw_command(netfn=0, command=1)
        if 'error' in response:
            raise exc.IpmiException(response['error'])
        oldpowerstate = 'on' if (response['data'][0] & 1) else 'off'
        if oldpowerstate == newpowerstate:
            return {'powerstate': oldpowerstate}
        if newpowerstate == 'boot':
            newpowerstate = 'on' if oldpowerstate == 'off' else 'reset'
        response = self.raw_command(
            netfn=0, command=2, data=[power_states[newpowerstate]])
        if 'error' in response:
            raise exc.IpmiException(response['error'])
        lastresponse = {'pendingpowerstate': newpowerstate}
        waitattempts = 300
        if not isinstance(wait, bool):
            waitattempts = wait
        if (wait and
           newpowerstate in ('on', 'off', 'shutdown', 'softoff')):
            if newpowerstate in ('softoff', 'shutdown'):
                waitpowerstate = 'off'
            else:
                waitpowerstate = newpowerstate
            currpowerstate = None
            while currpowerstate != waitpowerstate and waitattempts > 0:
                response = self.raw_command(netfn=0, command=1, delay_xmit=1)
                if 'error' in response:
                    raise exc.IpmiException(response['error'])
                currpowerstate = 'on' if (response['data'][0] & 1) else 'off'
                waitattempts -= 1
            if currpowerstate != waitpowerstate:
                raise exc.IpmiException(
                    "System did not accomplish power state change")
            return {'powerstate': currpowerstate}
        else:
            return lastresponse

    def get_video_launchdata(self):
        """Get data required to launch a remote video session to target.

        This is a highly proprietary scenario, the return data may vary greatly
        host to host.  The return should be a dict describing the type of data
        and the data.  For example {'jnlp': jnlpstring}
        """
        self.oem_init()
        return self._oem.get_video_launchdata()

    def reset_bmc(self):
        """Do a cold reset in BMC
        """
        response = self.raw_command(netfn=6, command=2)
        if 'error' in response:
            raise exc.IpmiException(response['error'])

    def set_bootdev(self,
                    bootdev,
                    persist=False,
                    uefiboot=False):
        """Set boot device to use on next reboot (helper)

        :param bootdev:
                        *network -- Request network boot
                        *hd -- Boot from hard drive
                        *safe -- Boot from hard drive, requesting 'safe mode'
                        *optical -- boot from CD/DVD/BD drive
                        *setup -- Boot into setup utility
                        *default -- remove any IPMI directed boot device
                                    request
        :param persist: If true, ask that system firmware use this device
                        beyond next boot.  Be aware many systems do not honor
                        this
        :param uefiboot: If true, request UEFI boot explicitly.  Strictly
                         speaking, the spec sugests that if not set, the system
                         should BIOS boot and offers no "don't care" option.
                         In practice, this flag not being set does not preclude
                         UEFI boot on any system I've encountered.
        :raises: IpmiException on an error.
        :returns: dict or True -- If callback is not provided, the response
        """
        if bootdev not in boot_devices:
            return {'error': "Unknown bootdevice %s requested" % bootdev}
        bootdevnum = boot_devices[bootdev]
        # first, we disable timer by way of set system boot options,
        # then move on to set chassis capabilities
        # Set System Boot Options is netfn=0, command=8, data
        response = self.raw_command(netfn=0, command=8, data=(3, 8))
        if 'error' in response:
            raise exc.IpmiException(response['error'])
        bootflags = 0x80
        if uefiboot:
            bootflags |= 1 << 5
        if persist:
            bootflags |= 1 << 6
        if bootdevnum == 0:
            bootflags = 0
        data = (5, bootflags, bootdevnum, 0, 0, 0)
        response = self.raw_command(netfn=0, command=8, data=data)
        if 'error' in response:
            raise exc.IpmiException(response['error'])
        return {'bootdev': bootdev}

    def xraw_command(self, netfn, command, bridge_request=(), data=(),
                     delay_xmit=None, retry=True, timeout=None):
        """Send raw ipmi command to BMC, raising exception on error

        This is identical to raw_command, except it raises exceptions
        on IPMI errors and returns data as a buffer.  This is the recommend
        function to use.  The response['data'] being a buffer allows
        traditional indexed access as well as works nicely with
        struct.unpack_from when certain data is coming back.

        :param netfn: Net function number
        :param command: Command value
        :param bridge_request: The target slave address and channel number for
                               the bridge request.
        :param data: Command data as a tuple or list
        :param retry: Whether to retry this particular payload or not, defaults
                      to true.
        :param timeout: A custom time to wait for initial reply, useful for
                        a slow command.  This may interfere with retry logic.
        :returns: dict -- The response from IPMI device
        """
        rsp = self.ipmi_session.raw_command(netfn=netfn, command=command,
                                            bridge_request=bridge_request,
                                            data=data, delay_xmit=delay_xmit,
                                            retry=retry, timeout=timeout)
        if 'error' in rsp:
            raise exc.IpmiException(rsp['error'], rsp['code'])
        rsp['data'] = buffer(bytearray(rsp['data']))
        return rsp

    def raw_command(self, netfn, command, bridge_request=(), data=(),
                    delay_xmit=None, retry=True, timeout=None):
        """Send raw ipmi command to BMC

        This allows arbitrary IPMI bytes to be issued.  This is commonly used
        for certain vendor specific commands.

        Example: ipmicmd.raw_command(netfn=0,command=4,data=(5))

        :param netfn: Net function number
        :param command: Command value
        :param bridge_request: The target slave address and channel number for
                               the bridge request.
        :param data: Command data as a tuple or list
        :param retry: Whether or not to retry command if no response received.
                      Defaults to True
        :param timeout: A custom amount of time to wait for initial reply
        :returns: dict -- The response from IPMI device
        """
        return self.ipmi_session.raw_command(netfn=netfn, command=command,
                                             bridge_request=bridge_request,
                                             data=data, delay_xmit=delay_xmit,
                                             retry=retry, timeout=timeout)

    def get_power(self):
        """Get current power state of the managed system

        The response, if successful, should contain 'powerstate' key and
        either 'on' or 'off' to indicate current state.

        :returns: dict -- {'powerstate': value}
        """
        response = self.raw_command(netfn=0, command=1)
        if 'error' in response:
            raise exc.IpmiException(response['error'])
        assert (response['command'] == 1 and response['netfn'] == 1)
        powerstate = 'on' if (response['data'][0] & 1) else 'off'
        return {'powerstate': powerstate}

    def set_identify(self, on=True, duration=None):
        """Request identify light

        Request the identify light to turn off, on for a duration,
        or on indefinitely.  Other than error exceptions,

        :param on: Set to True to force on or False to force off
        :param duration: Set if wanting to request turn on for a duration
                         rather than indefinitely on
        """
        self.oem_init()
        try:
            self._oem.set_identify(on, duration)
            return
        except exc.UnsupportedFunctionality:
            pass
        if duration is not None:
            duration = int(duration)
            if duration > 255:
                duration = 255
            if duration < 0:
                duration = 0
            response = self.raw_command(netfn=0, command=4, data=[duration])
            if 'error' in response:
                raise exc.IpmiException(response['error'])
            return
        forceon = 0
        if on:
            forceon = 1
        if self.ipmi_session.ipmiversion < 2.0:
            # ipmi 1.5 made due with just one byte, make best effort
            # to imitate indefinite as close as possible
            identifydata = [255 * forceon]
        else:
            identifydata = [0, forceon]
        response = self.raw_command(netfn=0, command=4, data=identifydata)
        if 'error' in response:
            raise exc.IpmiException(response['error'])

    def init_sdr(self):
        """Initialize SDR

        Do the appropriate action to have a relevant sensor description
        repository for the current management controller
        """
        # For now, return current sdr if it exists and still connected
        # future, check SDR timestamp for continued relevance
        # further future, optionally support a cache directory/file
        # to store cached copies for given device id, product id, mfg id,
        # sdr timestamp, our data version revision, aux firmware revision,
        # and oem defined field
        if self._sdr is None:
            self._sdr = sdr.SDR(self)
        return self._sdr

    def get_event_log(self, clear=False):
        """Retrieve the log of events, optionally clearing

        The contents of the SEL are returned as an iterable.  Timestamps
        are given as local time, ISO 8601 (whether the target has an accurate
        clock or not).  Timestamps may be omitted for events that cannot be
        given a timestamp, leaving only the raw timecode to provide relative
        time information.  clear set to true will result in the log being
        cleared as it is returned.  This allows an atomic fetch and clear
        behavior so that no log entries will be lost between the fetch and
        clear actions.  There is no 'clear_event_log' function to encourage
        users to create code that is not at risk for losing events.

        :param clear:  Whether to remove the SEL entries from the target BMC
        """
        self.oem_init()
        return sel.EventHandler(self.init_sdr(), self).fetch_sel(self, clear)

    def decode_pet(self, specifictrap, petdata):
        """Decode PET to an event

        In IPMI, the alert format are PET alerts.  It is a particular set of
        data put into an SNMPv1 trap and sent. It bears no small resemblence
        to the SEL entries.  This function takes data that would have been
        received by an SNMP trap handler, and provides an event decode, similar
        to one entry of get_event_log.

        :param specifictrap: The specific trap, as either a bytearray or int
        :param petdata: An iterable of the octet data of varbind for
                        1.3.6.1.4.1.3183.1.1.1
        :returns: A dict event similar to one iteration of get_event_log
        """
        self.oem_init()
        return sel.EventHandler(self.init_sdr(), self).decode_pet(specifictrap,
                                                                  petdata)

    def get_inventory_descriptions(self):
        """Retrieve list of things that could be inventoried

        This permits a caller to examine the available items
        without actually causing the inventory data to be gathered.  It
        returns an iterable of string descriptions
        """
        yield "System"
        if self._sdr is None:
            self._sdr = sdr.SDR(self)
        for fruid in sorted(self._sdr.fru):
            yield self._sdr.fru[fruid].fru_name
        self.oem_init()
        for compname in self._oem.get_oem_inventory_descriptions():
            yield compname

    def get_inventory_of_component(self, component):
        """Retrieve inventory of a component

        Retrieve detailed inventory information for only the requested
        component.
        """
        self.oem_init()
        if component == 'System':
            return self._get_zero_fru()
        if self._sdr is None:
            self._sdr = sdr.SDR(self)
        for fruid in self._sdr.fru:
            if self._sdr.fru[fruid].fru_name == component:
                return self._oem.process_fru(fru.FRU(
                    ipmicmd=self, fruid=fruid, sdr=self._sdr.fru[fruid]).info)
        return self._oem.get_inventory_of_component(component)

    def _get_zero_fru(self):
        # It is expected that a manufacturer matches SMBIOS to IPMI
        # get system uuid return data.  If a manufacturer does not
        # do so, they should handle either deletion or fixup in the
        # OEM processing pass.  Code optimistically assumes that if
        # data is returned, than the vendor is properly using it.
        zerofru = fru.FRU(ipmicmd=self).info
        if zerofru is None:
            zerofru = {}
        guiddata = self.raw_command(netfn=6, command=0x37)
        if 'error' not in guiddata:
            zerofru['UUID'] = pygutil.decode_wireformat_uuid(
                guiddata['data'])
        # Add some fields returned by get device ID command to FRU 0
        # Also rename them to something more in line with FRU 0 field naming
        # standards
        device_id = self._get_device_id()
        device_id['Device ID'] = device_id.pop('device_id')
        device_id['Device Revision'] = device_id.pop('device_revision')
        device_id['Manufacturer ID'] = device_id.pop('manufacturer_id')
        device_id['Product ID'] = device_id.pop('product_id')
        zerofru.update(device_id)
        return self._oem.process_fru(zerofru)

    def get_inventory(self):
        """Retrieve inventory of system

        Retrieve inventory of the targeted system.  This frequently includes
        serial numbers, sometimes hardware addresses, sometimes memory modules
        This function will retrieve whatever the underlying platform provides
        and apply some structure.  Iterating over the return yields tuples
        of a name for the inventoried item and dictionary of descriptions
        or None for items not present.
        """
        self.oem_init()
        yield ("System", self._get_zero_fru())
        if self._sdr is None:
            self._sdr = sdr.SDR(self)
        for fruid in sorted(self._sdr.fru):
            fruinf = fru.FRU(
                ipmicmd=self, fruid=fruid, sdr=self._sdr.fru[fruid]).info
            if fruinf is not None:
                fruinf = self._oem.process_fru(fruinf)
            yield (self._sdr.fru[fruid].fru_name, fruinf)
        for componentpair in self._oem.get_oem_inventory():
            yield componentpair

    def get_leds(self):
        """Get LED status information

        This provides a detailed view of the LEDs of the managed system.
        """
        self.oem_init()
        return self._oem.get_leds()

    def get_ntp_enabled(self):
        self.oem_init()
        return self._oem.get_ntp_enabled()

    def set_ntp_enabled(self, enable):
        self.oem_init()
        return self._oem.set_ntp_enabled(enable)

    def get_ntp_servers(self):
{'fraction_non_alphanumeric': 0.07606212882594793, 'fraction_numerical': 0.01031673519110705, 'mean_word_length': 1.9245713649521266, 'pattern_counts': {'":': 14, '<': 25, '<?xml version=': 0, '>': 23, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 4, 'substrings_counts': 6, 'word_list_counts': {'cursed_substrings.json': 80, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1840268', 'n_tokens_mistral': 14242, 'n_tokens_neox': 13601, 'n_words': 5100}