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