普通文本  |  459行  |  14.6 KB

# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import dbus
import dbus.service
import logging

import dbus_std_ifaces
import pm_constants
import pm_errors
import utils

from autotest_lib.client.cros.cellular import mm1_constants

class IncorrectPasswordError(pm_errors.MMMobileEquipmentError):
    """ Wrapper around MM_MOBILE_EQUIPMENT_ERROR_INCORRECT_PASSWORD. """

    def __init__(self):
        pm_errors.MMMobileEquipmentError.__init__(
                self, pm_errors.MMMobileEquipmentError.INCORRECT_PASSWORD,
                'Incorrect password')

class SimPukError(pm_errors.MMMobileEquipmentError):
    """ Wrapper around MM_MOBILE_EQUIPMENT_ERROR_SIM_PUK. """

    def __init__(self):
        pm_errors.MMMobileEquipmentError.__init__(
                self, pm_errors.MMMobileEquipmentError.SIM_PUK,
                'SIM PUK required')

class SimFailureError(pm_errors.MMMobileEquipmentError):
    """ Wrapper around MM_MOBILE_EQUIPMENT_ERROR_SIM_FAILURE. """

    def __init__(self):
        pm_errors.MMMobileEquipmentError.__init__(
                self, pm_errors.MMMobileEquipmentError.SIM_FAILURE,
                'SIM failure')

class SIM(dbus_std_ifaces.DBusProperties):
    """
    Pseudomodem implementation of the org.freedesktop.ModemManager1.Sim
    interface.

    Broadband modems usually need a SIM card to operate. Each Modem object will
    therefore expose up to one SIM object, which allows SIM-specific actions
    such as PIN unlocking.

    The SIM interface handles communication with SIM, USIM, and RUIM (CDMA SIM)
    cards.

    """

    # Multiple object paths needs to be supported so that the SIM can be
    # "reset". This allows the object to reappear on a new path as if it has
    # been reset.
    SUPPORTS_MULTIPLE_OBJECT_PATHS = True

    DEFAULT_MSIN = '1234567890'
    DEFAULT_IMSI = '888999111'
    DEFAULT_PIN = '1111'
    DEFAULT_PUK = '12345678'
    DEFAULT_PIN_RETRIES = 3
    DEFAULT_PUK_RETRIES = 10

    class Carrier:
        """
        Represents a 3GPP carrier that can be stored by a SIM object.

        """
        MCC_LIST = {
            'test' : '001',
            'us': '310',
            'de': '262',
            'es': '214',
            'fr': '208',
            'gb': '234',
            'it': '222',
            'nl': '204'
        }

        CARRIER_LIST = {
            'test' : ('test', '000', pm_constants.DEFAULT_TEST_NETWORK_PREFIX),
            'banana' : ('us', '001', 'Banana-Comm'),
            'att': ('us', '090', 'AT&T'),
            'tmobile': ('us', '026', 'T-Mobile'),
            'simyo': ('de', '03', 'simyo'),
            'movistar': ('es', '07', 'Movistar'),
            'sfr': ('fr', '10', 'SFR'),
            'three': ('gb', '20', '3'),
            'threeita': ('it', '99', '3ITA'),
            'kpn': ('nl', '08', 'KPN')
        }

        def __init__(self, carrier='test'):
           carrier = self.CARRIER_LIST.get(carrier, self.CARRIER_LIST['test'])

           self.mcc = self.MCC_LIST[carrier[0]]
           self.mnc = carrier[1]
           self.operator_name = carrier[2]
           if self.operator_name != 'Banana-Comm':
              self.operator_name = self.operator_name + ' - Fake'
           self.operator_id = self.mcc + self.mnc


    def __init__(self,
                 carrier,
                 access_technology,
                 index=0,
                 pin=DEFAULT_PIN,
                 puk=DEFAULT_PUK,
                 pin_retries=DEFAULT_PIN_RETRIES,
                 puk_retries=DEFAULT_PUK_RETRIES,
                 locked=False,
                 msin=DEFAULT_MSIN,
                 imsi=DEFAULT_IMSI,
                 config=None):
        if not carrier:
            raise TypeError('A carrier is required.')
        path = mm1_constants.MM1 + '/SIM/' + str(index)
        self.msin = msin
        self._carrier = carrier
        self.imsi = carrier.operator_id + imsi
        self._index = 0
        self._total_pin_retries = pin_retries
        self._total_puk_retries = puk_retries
        self._lock_data = {
            mm1_constants.MM_MODEM_LOCK_SIM_PIN : {
                'code' : pin,
                'retries' : pin_retries
            },
            mm1_constants.MM_MODEM_LOCK_SIM_PUK : {
                'code' : puk,
                'retries' : puk_retries
            }
        }
        self._lock_enabled = locked
        self._show_retries = locked
        if locked:
            self._lock_type = mm1_constants.MM_MODEM_LOCK_SIM_PIN
        else:
            self._lock_type = mm1_constants.MM_MODEM_LOCK_NONE
        self._modem = None
        self.access_technology = access_technology
        dbus_std_ifaces.DBusProperties.__init__(self, path, None, config)


    def IncrementPath(self):
        """
        Increments the current index at which this modem is exposed on DBus.
        E.g. if the current path is org/freedesktop/ModemManager/Modem/0, the
        path will change to org/freedesktop/ModemManager/Modem/1.

        Calling this method does not remove the object from its current path,
        which means that it will be available via both the old and the new
        paths. This is currently only used by Reset, in conjunction with
        dbus_std_ifaces.DBusObjectManager.[Add|Remove].

        """
        self._index += 1
        path = mm1_constants.MM1 + '/SIM/' + str(self._index)
        logging.info('SIM coming back as: ' + path)
        self.SetPath(path)


    def Reset(self):
        """ Resets the SIM. This will lock the SIM if locks are enabled. """
        self.IncrementPath()
        if not self.locked and self._lock_enabled:
            self._lock_type = mm1_constants.MM_MODEM_LOCK_SIM_PIN


    @property
    def lock_type(self):
        """
        Returns the current lock type of the SIM. Can be used to determine
        whether or not the SIM is locked.

        @returns: The lock type, as a MMModemLock value.

        """
        return self._lock_type


    @property
    def unlock_retries(self):
        """
        Returns the number of unlock retries left.

        @returns: The number of unlock retries for each lock type the SIM
                supports as a dictionary.

        """
        retries = dbus.Dictionary(signature='uu')
        if not self._show_retries:
            return retries
        for k, v in self._lock_data.iteritems():
            retries[dbus.types.UInt32(k)] = dbus.types.UInt32(v['retries'])
        return retries


    @property
    def enabled_locks(self):
        """
        Returns the currently enabled facility locks.

        @returns: The currently enabled facility locks, as a MMModem3gppFacility
                value.

        """
        if self._lock_enabled:
            return mm1_constants.MM_MODEM_3GPP_FACILITY_SIM
        return mm1_constants.MM_MODEM_3GPP_FACILITY_NONE


    @property
    def locked(self):
        """ @returns: True, if the SIM is locked. False, otherwise. """
        return not (self._lock_type == mm1_constants.MM_MODEM_LOCK_NONE or
            self._lock_type == mm1_constants.MM_MODEM_LOCK_UNKNOWN)


    @property
    def modem(self):
        """
        @returns: the modem object that this SIM is currently plugged into.

        """
        return self._modem


    @modem.setter
    def modem(self, modem):
        """
        Assigns a modem object to this SIM, so that the modem knows about it.
        This should only be called directly by a modem object.

        @param modem: The modem to be associated with this SIM.

        """
        self._modem = modem


    @property
    def carrier(self):
        """
        @returns: An instance of SIM.Carrier that contains the carrier
                information assigned to this SIM.

        """
        return self._carrier


    def _DBusPropertiesDict(self):
        imsi = self.imsi
        if self.locked:
            msin = ''
            op_id = ''
            op_name = ''
        else:
            msin = self.msin
            op_id = self._carrier.operator_id
            op_name = self._carrier.operator_name
        return {
            'SimIdentifier' : msin,
            'Imsi' : imsi,
            'OperatorIdentifier' : op_id,
            'OperatorName' : op_name
        }


    def _InitializeProperties(self):
        return { mm1_constants.I_SIM : self._DBusPropertiesDict() }


    def _UpdateProperties(self):
        self.SetAll(mm1_constants.I_SIM, self._DBusPropertiesDict())


    def _CheckCode(self, code, lock_data, next_lock, error_to_raise):
        # Checks |code| against |lock_data['code']|. If the codes don't match:
        #
        #   - if the number of retries left for |lock_data| drops down to 0,
        #     the current lock type gets set to |next_lock| and
        #     |error_to_raise| is raised.
        #
        #   - otherwise, IncorrectPasswordError is raised.
        #
        # If the codes match, no error is raised.

        if code == lock_data['code']:
            # Codes match, nothing to do.
            return

        # Codes didn't match. Figure out which error to raise based on
        # remaining retries.
        lock_data['retries'] -= 1
        self._show_retries = True
        if lock_data['retries'] == 0:
            logging.info('Retries exceeded the allowed number.')
            if next_lock:
                self._lock_type = next_lock
                self._lock_enabled = True
        else:
            error_to_raise = IncorrectPasswordError()
        self._modem.UpdateLockStatus()
        raise error_to_raise


    def _ResetRetries(self, lock_type):
        if lock_type == mm1_constants.MM_MODEM_LOCK_SIM_PIN:
            value = self._total_pin_retries
        elif lock_type == mm1_constants.MM_MODEM_LOCK_SIM_PUK:
            value = self._total_puk_retries
        else:
            raise TypeError('Invalid SIM lock type')
        self._lock_data[lock_type]['retries'] = value


    @utils.log_dbus_method()
    @dbus.service.method(mm1_constants.I_SIM, in_signature='s')
    def SendPin(self, pin):
        """
        Sends the PIN to unlock the SIM card.

        @param pin: A string containing the PIN code.

        """
        if not self.locked:
            logging.info('SIM is not locked. Nothing to do.')
            return

        if self._lock_type == mm1_constants.MM_MODEM_LOCK_SIM_PUK:
            if self._lock_data[self._lock_type]['retries'] == 0:
                raise SimFailureError()
            else:
                raise SimPukError()

        lock_data = self._lock_data.get(self._lock_type, None)
        if not lock_data:
            raise pm_errors.MMCoreError(
                pm_errors.MMCoreError.FAILED,
                'Current lock type does not match the SIM lock capabilities.')

        self._CheckCode(pin, lock_data, mm1_constants.MM_MODEM_LOCK_SIM_PUK,
                        SimPukError())

        logging.info('Entered correct PIN.')
        self._ResetRetries(mm1_constants.MM_MODEM_LOCK_SIM_PIN)
        self._lock_type = mm1_constants.MM_MODEM_LOCK_NONE
        self._modem.UpdateLockStatus()
        self._modem.Expose3GPPProperties()
        self._UpdateProperties()


    @utils.log_dbus_method()
    @dbus.service.method(mm1_constants.I_SIM, in_signature='ss')
    def SendPuk(self, puk, pin):
        """
        Sends the PUK and a new PIN to unlock the SIM card.

        @param puk: A string containing the PUK code.
        @param pin: A string containing the PIN code.

        """
        if self._lock_type != mm1_constants.MM_MODEM_LOCK_SIM_PUK:
            logging.info('No PUK lock in place. Nothing to do.')
            return

        lock_data = self._lock_data.get(self._lock_type, None)
        if not lock_data:
            raise pm_errors.MMCoreError(
                    pm_errors.MMCoreError.FAILED,
                    'Current lock type does not match the SIM locks in place.')

        if lock_data['retries'] == 0:
            raise SimFailureError()

        self._CheckCode(puk, lock_data, None, SimFailureError())

        logging.info('Entered correct PUK.')
        self._ResetRetries(mm1_constants.MM_MODEM_LOCK_SIM_PIN)
        self._ResetRetries(mm1_constants.MM_MODEM_LOCK_SIM_PUK)
        self._lock_data[mm1_constants.MM_MODEM_LOCK_SIM_PIN]['code'] = pin
        self._lock_type = mm1_constants.MM_MODEM_LOCK_NONE
        self._modem.UpdateLockStatus()
        self._modem.Expose3GPPProperties()
        self._UpdateProperties()


    @utils.log_dbus_method()
    @dbus.service.method(mm1_constants.I_SIM, in_signature='sb')
    def EnablePin(self, pin, enabled):
        """
        Enables or disables PIN checking.

        @param pin: A string containing the PIN code.
        @param enabled: True to enable PIN, False otherwise.

        """
        if enabled:
            self._EnablePin(pin)
        else:
            self._DisablePin(pin)


    def _EnablePin(self, pin):
        # Operation fails if the SIM is locked or PIN lock is already
        # enabled.
        if self.locked or self._lock_enabled:
            raise SimFailureError()

        lock_data = self._lock_data[mm1_constants.MM_MODEM_LOCK_SIM_PIN]
        self._CheckCode(pin, lock_data, mm1_constants.MM_MODEM_LOCK_SIM_PUK,
                        SimPukError())
        self._lock_enabled = True
        self._show_retries = True
        self._ResetRetries(mm1_constants.MM_MODEM_LOCK_SIM_PIN)
        self._UpdateProperties()
        self.modem.UpdateLockStatus()


    def _DisablePin(self, pin):
        if not self._lock_enabled:
            raise SimFailureError()

        if self.locked:
            self.SendPin(pin)
        else:
            lock_data = self._lock_data[mm1_constants.MM_MODEM_LOCK_SIM_PIN]
            self._CheckCode(pin, lock_data,
                            mm1_constants.MM_MODEM_LOCK_SIM_PUK, SimPukError())
            self._ResetRetries(mm1_constants.MM_MODEM_LOCK_SIM_PIN)
        self._lock_enabled = False
        self._UpdateProperties()
        self.modem.UpdateLockStatus()


    @utils.log_dbus_method()
    @dbus.service.method(mm1_constants.I_SIM, in_signature='ss')
    def ChangePin(self, old_pin, new_pin):
        """
        Changes the PIN code.

        @param old_pin: A string containing the old PIN code.
        @param new_pin: A string containing the new PIN code.

        """
        if not self._lock_enabled or self.locked:
            raise SimFailureError()

        lock_data = self._lock_data[mm1_constants.MM_MODEM_LOCK_SIM_PIN]
        self._CheckCode(old_pin, lock_data,
                        mm1_constants.MM_MODEM_LOCK_SIM_PUK, SimPukError())
        self._ResetRetries(mm1_constants.MM_MODEM_LOCK_SIM_PIN)
        self._lock_data[mm1_constants.MM_MODEM_LOCK_SIM_PIN]['code'] = new_pin
        self._UpdateProperties()
        self.modem.UpdateLockStatus()