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 5import dbus 6import dbus.service 7import logging 8 9import dbus_std_ifaces 10import pm_constants 11import pm_errors 12import utils 13 14from autotest_lib.client.cros.cellular import mm1_constants 15 16class 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 24class 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 32class 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 40class 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