xref: /aosp_15_r20/external/autotest/client/cros/cellular/pseudomodem/sim.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
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