Home | History | Annotate | Download | only in pseudomodem
      1 # Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 import dbus
      6 import dbus.service
      7 import logging
      8 
      9 import dbus_std_ifaces
     10 import pm_constants
     11 import pm_errors
     12 import utils
     13 
     14 from autotest_lib.client.cros.cellular import mm1_constants
     15 
     16 class IncorrectPasswordError(pm_errors.MMMobileEquipmentError):
     17     """ Wrapper around MM_MOBILE_EQUIPMENT_ERROR_INCORRECT_PASSWORD. """
     18 
     19     def __init__(self):
     20         pm_errors.MMMobileEquipmentError.__init__(
     21                 self, pm_errors.MMMobileEquipmentError.INCORRECT_PASSWORD,
     22                 'Incorrect password')
     23 
     24 class SimPukError(pm_errors.MMMobileEquipmentError):
     25     """ Wrapper around MM_MOBILE_EQUIPMENT_ERROR_SIM_PUK. """
     26 
     27     def __init__(self):
     28         pm_errors.MMMobileEquipmentError.__init__(
     29                 self, pm_errors.MMMobileEquipmentError.SIM_PUK,
     30                 'SIM PUK required')
     31 
     32 class SimFailureError(pm_errors.MMMobileEquipmentError):
     33     """ Wrapper around MM_MOBILE_EQUIPMENT_ERROR_SIM_FAILURE. """
     34 
     35     def __init__(self):
     36         pm_errors.MMMobileEquipmentError.__init__(
     37                 self, pm_errors.MMMobileEquipmentError.SIM_FAILURE,
     38                 'SIM failure')
     39 
     40 class SIM(dbus_std_ifaces.DBusProperties):
     41     """
     42     Pseudomodem implementation of the org.freedesktop.ModemManager1.Sim
     43     interface.
     44 
     45     Broadband modems usually need a SIM card to operate. Each Modem object will
     46     therefore expose up to one SIM object, which allows SIM-specific actions
     47     such as PIN unlocking.
     48 
     49     The SIM interface handles communication with SIM, USIM, and RUIM (CDMA SIM)
     50     cards.
     51 
     52     """
     53 
     54     # Multiple object paths needs to be supported so that the SIM can be
     55     # "reset". This allows the object to reappear on a new path as if it has
     56     # been reset.
     57     SUPPORTS_MULTIPLE_OBJECT_PATHS = True
     58 
     59     DEFAULT_MSIN = '1234567890'
     60     DEFAULT_IMSI = '888999111'
     61     DEFAULT_PIN = '1111'
     62     DEFAULT_PUK = '12345678'
     63     DEFAULT_PIN_RETRIES = 3
     64     DEFAULT_PUK_RETRIES = 10
     65 
     66     class Carrier:
     67         """
     68         Represents a 3GPP carrier that can be stored by a SIM object.
     69 
     70         """
     71         MCC_LIST = {
     72             'test' : '001',
     73             'us': '310',
     74             'de': '262',
     75             'es': '214',
     76             'fr': '208',
     77             'gb': '234',
     78             'it': '222',
     79             'nl': '204'
     80         }
     81 
     82         CARRIER_LIST = {
     83             'test' : ('test', '000', pm_constants.DEFAULT_TEST_NETWORK_PREFIX),
     84             'banana' : ('us', '001', 'Banana-Comm'),
     85             'att': ('us', '090', 'AT&T'),
     86             'tmobile': ('us', '026', 'T-Mobile'),
     87             'simyo': ('de', '03', 'simyo'),
     88             'movistar': ('es', '07', 'Movistar'),
     89             'sfr': ('fr', '10', 'SFR'),
     90             'three': ('gb', '20', '3'),
     91             'threeita': ('it', '99', '3ITA'),
     92             'kpn': ('nl', '08', 'KPN')
     93         }
     94 
     95         def __init__(self, carrier='test'):
     96            carrier = self.CARRIER_LIST.get(carrier, self.CARRIER_LIST['test'])
     97 
     98            self.mcc = self.MCC_LIST[carrier[0]]
     99            self.mnc = carrier[1]
    100            self.operator_name = carrier[2]
    101            if self.operator_name != 'Banana-Comm':
    102               self.operator_name = self.operator_name + ' - Fake'
    103            self.operator_id = self.mcc + self.mnc
    104 
    105 
    106     def __init__(self,
    107                  carrier,
    108                  access_technology,
    109                  index=0,
    110                  pin=DEFAULT_PIN,
    111                  puk=DEFAULT_PUK,
    112                  pin_retries=DEFAULT_PIN_RETRIES,
    113                  puk_retries=DEFAULT_PUK_RETRIES,
    114                  locked=False,
    115                  msin=DEFAULT_MSIN,
    116                  imsi=DEFAULT_IMSI,
    117                  config=None):
    118         if not carrier:
    119             raise TypeError('A carrier is required.')
    120         path = mm1_constants.MM1 + '/SIM/' + str(index)
    121         self.msin = msin
    122         self._carrier = carrier
    123         self.imsi = carrier.operator_id + imsi
    124         self._index = 0
    125         self._total_pin_retries = pin_retries
    126         self._total_puk_retries = puk_retries
    127         self._lock_data = {
    128             mm1_constants.MM_MODEM_LOCK_SIM_PIN : {
    129                 'code' : pin,
    130                 'retries' : pin_retries
    131             },
    132             mm1_constants.MM_MODEM_LOCK_SIM_PUK : {
    133                 'code' : puk,
    134                 'retries' : puk_retries
    135             }
    136         }
    137         self._lock_enabled = locked
    138         self._show_retries = locked
    139         if locked:
    140             self._lock_type = mm1_constants.MM_MODEM_LOCK_SIM_PIN
    141         else:
    142             self._lock_type = mm1_constants.MM_MODEM_LOCK_NONE
    143         self._modem = None
    144         self.access_technology = access_technology
    145         dbus_std_ifaces.DBusProperties.__init__(self, path, None, config)
    146 
    147 
    148     def IncrementPath(self):
    149         """
    150         Increments the current index at which this modem is exposed on DBus.
    151         E.g. if the current path is org/freedesktop/ModemManager/Modem/0, the
    152         path will change to org/freedesktop/ModemManager/Modem/1.
    153 
    154         Calling this method does not remove the object from its current path,
    155         which means that it will be available via both the old and the new
    156         paths. This is currently only used by Reset, in conjunction with
    157         dbus_std_ifaces.DBusObjectManager.[Add|Remove].
    158 
    159         """
    160         self._index += 1
    161         path = mm1_constants.MM1 + '/SIM/' + str(self._index)
    162         logging.info('SIM coming back as: ' + path)
    163         self.SetPath(path)
    164 
    165 
    166     def Reset(self):
    167         """ Resets the SIM. This will lock the SIM if locks are enabled. """
    168         self.IncrementPath()
    169         if not self.locked and self._lock_enabled:
    170             self._lock_type = mm1_constants.MM_MODEM_LOCK_SIM_PIN
    171 
    172 
    173     @property
    174     def lock_type(self):
    175         """
    176         Returns the current lock type of the SIM. Can be used to determine
    177         whether or not the SIM is locked.
    178 
    179         @returns: The lock type, as a MMModemLock value.
    180 
    181         """
    182         return self._lock_type
    183 
    184 
    185     @property
    186     def unlock_retries(self):
    187         """
    188         Returns the number of unlock retries left.
    189 
    190         @returns: The number of unlock retries for each lock type the SIM
    191                 supports as a dictionary.
    192 
    193         """
    194         retries = dbus.Dictionary(signature='uu')
    195         if not self._show_retries:
    196             return retries
    197         for k, v in self._lock_data.iteritems():
    198             retries[dbus.types.UInt32(k)] = dbus.types.UInt32(v['retries'])
    199         return retries
    200 
    201 
    202     @property
    203     def enabled_locks(self):
    204         """
    205         Returns the currently enabled facility locks.
    206 
    207         @returns: The currently enabled facility locks, as a MMModem3gppFacility
    208                 value.
    209 
    210         """
    211         if self._lock_enabled:
    212             return mm1_constants.MM_MODEM_3GPP_FACILITY_SIM
    213         return mm1_constants.MM_MODEM_3GPP_FACILITY_NONE
    214 
    215 
    216     @property
    217     def locked(self):
    218         """ @returns: True, if the SIM is locked. False, otherwise. """
    219         return not (self._lock_type == mm1_constants.MM_MODEM_LOCK_NONE or
    220             self._lock_type == mm1_constants.MM_MODEM_LOCK_UNKNOWN)
    221 
    222 
    223     @property
    224     def modem(self):
    225         """
    226         @returns: the modem object that this SIM is currently plugged into.
    227 
    228         """
    229         return self._modem
    230 
    231 
    232     @modem.setter
    233     def modem(self, modem):
    234         """
    235         Assigns a modem object to this SIM, so that the modem knows about it.
    236         This should only be called directly by a modem object.
    237 
    238         @param modem: The modem to be associated with this SIM.
    239 
    240         """
    241         self._modem = modem
    242 
    243 
    244     @property
    245     def carrier(self):
    246         """
    247         @returns: An instance of SIM.Carrier that contains the carrier
    248                 information assigned to this SIM.
    249 
    250         """
    251         return self._carrier
    252 
    253 
    254     def _DBusPropertiesDict(self):
    255         imsi = self.imsi
    256         if self.locked:
    257             msin = ''
    258             op_id = ''
    259             op_name = ''
    260         else:
    261             msin = self.msin
    262             op_id = self._carrier.operator_id
    263             op_name = self._carrier.operator_name
    264         return {
    265             'SimIdentifier' : msin,
    266             'Imsi' : imsi,
    267             'OperatorIdentifier' : op_id,
    268             'OperatorName' : op_name
    269         }
    270 
    271 
    272     def _InitializeProperties(self):
    273         return { mm1_constants.I_SIM : self._DBusPropertiesDict() }
    274 
    275 
    276     def _UpdateProperties(self):
    277         self.SetAll(mm1_constants.I_SIM, self._DBusPropertiesDict())
    278 
    279 
    280     def _CheckCode(self, code, lock_data, next_lock, error_to_raise):
    281         # Checks |code| against |lock_data['code']|. If the codes don't match:
    282         #
    283         #   - if the number of retries left for |lock_data| drops down to 0,
    284         #     the current lock type gets set to |next_lock| and
    285         #     |error_to_raise| is raised.
    286         #
    287         #   - otherwise, IncorrectPasswordError is raised.
    288         #
    289         # If the codes match, no error is raised.
    290 
    291         if code == lock_data['code']:
    292             # Codes match, nothing to do.
    293             return
    294 
    295         # Codes didn't match. Figure out which error to raise based on
    296         # remaining retries.
    297         lock_data['retries'] -= 1
    298         self._show_retries = True
    299         if lock_data['retries'] == 0:
    300             logging.info('Retries exceeded the allowed number.')
    301             if next_lock:
    302                 self._lock_type = next_lock
    303                 self._lock_enabled = True
    304         else:
    305             error_to_raise = IncorrectPasswordError()
    306         self._modem.UpdateLockStatus()
    307         raise error_to_raise
    308 
    309 
    310     def _ResetRetries(self, lock_type):
    311         if lock_type == mm1_constants.MM_MODEM_LOCK_SIM_PIN:
    312             value = self._total_pin_retries
    313         elif lock_type == mm1_constants.MM_MODEM_LOCK_SIM_PUK:
    314             value = self._total_puk_retries
    315         else:
    316             raise TypeError('Invalid SIM lock type')
    317         self._lock_data[lock_type]['retries'] = value
    318 
    319 
    320     @utils.log_dbus_method()
    321     @dbus.service.method(mm1_constants.I_SIM, in_signature='s')
    322     def SendPin(self, pin):
    323         """
    324         Sends the PIN to unlock the SIM card.
    325 
    326         @param pin: A string containing the PIN code.
    327 
    328         """
    329         if not self.locked:
    330             logging.info('SIM is not locked. Nothing to do.')
    331             return
    332 
    333         if self._lock_type == mm1_constants.MM_MODEM_LOCK_SIM_PUK:
    334             if self._lock_data[self._lock_type]['retries'] == 0:
    335                 raise SimFailureError()
    336             else:
    337                 raise SimPukError()
    338 
    339         lock_data = self._lock_data.get(self._lock_type, None)
    340         if not lock_data:
    341             raise pm_errors.MMCoreError(
    342                 pm_errors.MMCoreError.FAILED,
    343                 'Current lock type does not match the SIM lock capabilities.')
    344 
    345         self._CheckCode(pin, lock_data, mm1_constants.MM_MODEM_LOCK_SIM_PUK,
    346                         SimPukError())
    347 
    348         logging.info('Entered correct PIN.')
    349         self._ResetRetries(mm1_constants.MM_MODEM_LOCK_SIM_PIN)
    350         self._lock_type = mm1_constants.MM_MODEM_LOCK_NONE
    351         self._modem.UpdateLockStatus()
    352         self._modem.Expose3GPPProperties()
    353         self._UpdateProperties()
    354 
    355 
    356     @utils.log_dbus_method()
    357     @dbus.service.method(mm1_constants.I_SIM, in_signature='ss')
    358     def SendPuk(self, puk, pin):
    359         """
    360         Sends the PUK and a new PIN to unlock the SIM card.
    361 
    362         @param puk: A string containing the PUK code.
    363         @param pin: A string containing the PIN code.
    364 
    365         """
    366         if self._lock_type != mm1_constants.MM_MODEM_LOCK_SIM_PUK:
    367             logging.info('No PUK lock in place. Nothing to do.')
    368             return
    369 
    370         lock_data = self._lock_data.get(self._lock_type, None)
    371         if not lock_data:
    372             raise pm_errors.MMCoreError(
    373                     pm_errors.MMCoreError.FAILED,
    374                     'Current lock type does not match the SIM locks in place.')
    375 
    376         if lock_data['retries'] == 0:
    377             raise SimFailureError()
    378 
    379         self._CheckCode(puk, lock_data, None, SimFailureError())
    380 
    381         logging.info('Entered correct PUK.')
    382         self._ResetRetries(mm1_constants.MM_MODEM_LOCK_SIM_PIN)
    383         self._ResetRetries(mm1_constants.MM_MODEM_LOCK_SIM_PUK)
    384         self._lock_data[mm1_constants.MM_MODEM_LOCK_SIM_PIN]['code'] = pin
    385         self._lock_type = mm1_constants.MM_MODEM_LOCK_NONE
    386         self._modem.UpdateLockStatus()
    387         self._modem.Expose3GPPProperties()
    388         self._UpdateProperties()
    389 
    390 
    391     @utils.log_dbus_method()
    392     @dbus.service.method(mm1_constants.I_SIM, in_signature='sb')
    393     def EnablePin(self, pin, enabled):
    394         """
    395         Enables or disables PIN checking.
    396 
    397         @param pin: A string containing the PIN code.
    398         @param enabled: True to enable PIN, False otherwise.
    399 
    400         """
    401         if enabled:
    402             self._EnablePin(pin)
    403         else:
    404             self._DisablePin(pin)
    405 
    406 
    407     def _EnablePin(self, pin):
    408         # Operation fails if the SIM is locked or PIN lock is already
    409         # enabled.
    410         if self.locked or self._lock_enabled:
    411             raise SimFailureError()
    412 
    413         lock_data = self._lock_data[mm1_constants.MM_MODEM_LOCK_SIM_PIN]
    414         self._CheckCode(pin, lock_data, mm1_constants.MM_MODEM_LOCK_SIM_PUK,
    415                         SimPukError())
    416         self._lock_enabled = True
    417         self._show_retries = True
    418         self._ResetRetries(mm1_constants.MM_MODEM_LOCK_SIM_PIN)
    419         self._UpdateProperties()
    420         self.modem.UpdateLockStatus()
    421 
    422 
    423     def _DisablePin(self, pin):
    424         if not self._lock_enabled:
    425             raise SimFailureError()
    426 
    427         if self.locked:
    428             self.SendPin(pin)
    429         else:
    430             lock_data = self._lock_data[mm1_constants.MM_MODEM_LOCK_SIM_PIN]
    431             self._CheckCode(pin, lock_data,
    432                             mm1_constants.MM_MODEM_LOCK_SIM_PUK, SimPukError())
    433             self._ResetRetries(mm1_constants.MM_MODEM_LOCK_SIM_PIN)
    434         self._lock_enabled = False
    435         self._UpdateProperties()
    436         self.modem.UpdateLockStatus()
    437 
    438 
    439     @utils.log_dbus_method()
    440     @dbus.service.method(mm1_constants.I_SIM, in_signature='ss')
    441     def ChangePin(self, old_pin, new_pin):
    442         """
    443         Changes the PIN code.
    444 
    445         @param old_pin: A string containing the old PIN code.
    446         @param new_pin: A string containing the new PIN code.
    447 
    448         """
    449         if not self._lock_enabled or self.locked:
    450             raise SimFailureError()
    451 
    452         lock_data = self._lock_data[mm1_constants.MM_MODEM_LOCK_SIM_PIN]
    453         self._CheckCode(old_pin, lock_data,
    454                         mm1_constants.MM_MODEM_LOCK_SIM_PUK, SimPukError())
    455         self._ResetRetries(mm1_constants.MM_MODEM_LOCK_SIM_PIN)
    456         self._lock_data[mm1_constants.MM_MODEM_LOCK_SIM_PIN]['code'] = new_pin
    457         self._UpdateProperties()
    458         self.modem.UpdateLockStatus()
    459