# 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()