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