1# Copyright (c) 2013 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 logging
7import random
8
9from autotest_lib.client.bin import test
10from autotest_lib.client.bin import utils
11from autotest_lib.client.common_lib import error
12from autotest_lib.client.cros.cellular import test_environment
13from autotest_lib.client.cros.cellular.pseudomodem import sim
14
15# This is a software only test. Most time delayes are only dbus update delays.
16DEFAULT_OPERATION_TIMEOUT=3
17
18class cellular_SIMLocking(test.test):
19    """
20    Test the SIM locking functionality of shill.
21
22    This test has the following test_cases:
23      - Attempt to enable SIM lock with incorrect sim-pin. Verify that the
24        attempt fails.
25      - Successfully pin-lock the SIM.
26      - Unlock a pin-locked SIM.
27      - Attempt to unlock a pin-locked SIM with incorrect sim-pin, until it gets
28        puk-locked.
29      - Unblock a puk-locked SIM.
30      - Attempt to unblock a puk-locked SIM with incorrect sim-puk, until the
31        SIM gets blocked. At this point, a sim-pin2 might be expected by some
32        SIMs. This test does not attempt to unlock the SIM using sim-pin2.
33      - Test the functionality to change sim-pin.
34
35    """
36
37    version = 1
38
39    def _bad_pin(self):
40        """ Obtain a pin that does not match the valid sim-pin. """
41        # Restricting the values to be  >= 1000 ensures four digit string.
42        bad_pin = random.randint(1000, 9999)
43        if str(bad_pin) == self.current_pin:
44            bad_pin += 1
45        return str(bad_pin)
46
47
48    def _bad_puk(self):
49        """ Obtain a puk that does not match the valid sim-puk. """
50        # Restricting the values to be  >= 10000000 ensures 8 digit string.
51        bad_puk = random.randint(10000000, 99999999)
52        if str(bad_puk) == self.current_puk:
53            bad_puk += 1
54        return str(bad_puk)
55
56
57    def _enter_incorrect_pin(self):
58        try:
59            self.device.EnterPin(self._bad_pin())
60            raise error.TestFail('Cellular device did not complain although '
61                                 'an incorrect pin was given')
62        except dbus.DBusException as e:
63            if e.get_dbus_name() == self.test_env.shill.ERROR_INCORRECT_PIN:
64                logging.info('Obtained expected result: EnterPin failed with '
65                             'incorrect PIN.')
66            else:
67                raise
68
69
70    def _enter_incorrect_puk(self):
71        try:
72            self.device.UnblockPin(self._bad_puk(), self.current_pin)
73            raise error.TestFail('Cellular device did not complain although '
74                                 'an incorrect puk was given')
75        except dbus.DBusException as e:
76            if e.get_dbus_name() == self.test_env.shill.ERROR_INCORRECT_PIN:
77                logging.info('Obtained expected result: UnblockPin failed with '
78                             'incorrect PUK.')
79            else:
80                raise
81
82
83    def _get_sim_lock_status(self):
84        """ Helper method to safely obtain SIM lock status. """
85        properties = self.device.GetProperties(utf8_strings=True)
86        sim_lock_status = properties.get(
87                self.test_env.shill.DEVICE_PROPERTY_SIM_LOCK_STATUS,
88                None)
89        if sim_lock_status is None:
90            raise error.TestFail( 'Failed to read SIM_LOCK_STATUS.')
91        return self.test_env.shill.dbus2primitive(sim_lock_status)
92
93
94    def _is_sim_lock_enabled(self):
95        """ Helper method to check if the SIM lock is enabled. """
96        lock_status = self._get_sim_lock_status()
97        lock_enabled = lock_status.get(
98                self.test_env.shill.PROPERTY_KEY_SIM_LOCK_ENABLED,
99                None)
100        if lock_enabled is None:
101            raise error.TestFail('Failed to find LockEnabled key in '
102                                 'the lock status value.')
103        return lock_enabled
104
105
106    def _is_sim_pin_locked(self):
107        """ Helper method to check if the SIM has been pin-locked. """
108        lock_status = self._get_sim_lock_status()
109        lock_type = lock_status.get(
110                self.test_env.shill.PROPERTY_KEY_SIM_LOCK_TYPE,
111                None)
112        if lock_type is None:
113            raise error.TestFail('Failed to find LockType key in the '
114                                 'lock status value.')
115        return lock_type == self.test_env.shill.VALUE_SIM_LOCK_TYPE_PIN
116
117
118    def _is_sim_puk_locked(self):
119        """ Helper method to check if the SIM has been puk-locked. """
120        lock_status = self._get_sim_lock_status()
121        lock_type = lock_status.get(
122                self.test_env.shill.PROPERTY_KEY_SIM_LOCK_TYPE,
123                None)
124        if lock_type is None:
125            raise error.TestFail('Failed to find LockType key in the '
126                                 'lock status value.')
127        return lock_type == self.test_env.shill.VALUE_SIM_LOCK_TYPE_PUK
128
129
130    def _get_retries_left(self):
131        """ Helper method to get the number of unlock retries left. """
132        lock_status = self._get_sim_lock_status()
133        retries_left = lock_status.get(
134                self.test_env.shill.PROPERTY_KEY_SIM_LOCK_RETRIES_LEFT,
135                None)
136        if retries_left is None:
137            raise error.TestFail('Failed to find LockRetriesLeft key '
138                                 'in the lock status value.')
139        if retries_left < 0:
140            raise error.TestFail('Malformed RetriesLeft: %s' %
141                                 str(retries_left))
142        return retries_left
143
144    def _reset_modem_with_sim_lock(self):
145        """ Helper method to reset the modem with the SIM locked. """
146        # When the SIM is locked, the enable operation fails and
147        # hence set expect_powered flag to False.
148        # The enable operation is deferred by Shill until the modem goes into
149        # the disabled state after the SIM is unlocked.
150        self.device, self.service = self.test_env.shill.reset_modem(
151                self.device,
152                expect_powered=False,
153                expect_service=False)
154
155    def _pin_lock_sim(self):
156        """ Helper method to pin-lock a SIM, assuming nothing bad happens. """
157        self.device.RequirePin(self.current_pin, True)
158        self._reset_modem_with_sim_lock()
159        if not self._is_sim_pin_locked():
160            raise error.TestFail('Expected SIM to be locked after reset.')
161
162
163    def _puk_lock_sim(self):
164        """ Helper method to puk-lock a SIM, assuming nothing bad happens. """
165        self._pin_lock_sim()
166        while not self._is_sim_puk_locked():
167            try:
168                self._enter_incorrect_pin()
169            except dbus.DBusException as e:
170                if e.get_dbus_name() != self.test_env.shill.ERROR_PIN_BLOCKED:
171                    raise
172        if not self._is_sim_puk_locked():
173            raise error.TestFail('Expected SIM to be puk-locked.')
174
175
176
177
178    def test_unsuccessful_enable_lock(self):
179        """ Test SIM lock enable failes with incorrect sim-pin. """
180        logging.debug('Attempting to enable SIM lock with incorrect PIN.')
181        try:
182            self.device.RequirePin(self._bad_pin(), True)
183            raise error.TestFail('Cellular device did not complain although '
184                                 'an incorrect pin was given')
185        except dbus.DBusException as e:
186            if e.get_dbus_name() == self.test_env.shill.ERROR_INCORRECT_PIN:
187                logging.info('Obtained expected result: pin-lock enable failed '
188                             'with incorrect PIN.')
189            else:
190                raise
191
192        if self._is_sim_lock_enabled():
193            raise error.TestFail('SIM lock got enabled by incorrect PIN.')
194
195        # SIM lock should not be enabled, and lock not set after reset.
196        self.device, self.service = self.test_env.shill.reset_modem(self.device)
197        self.test_env.shill.wait_for_property_in(self.service,
198                                                 'state',
199                                                 ['online'],
200                                                 DEFAULT_OPERATION_TIMEOUT)
201        if (self._is_sim_lock_enabled() or self._is_sim_pin_locked() or
202            self._is_sim_puk_locked()):
203            raise error.TestFail('Cellular device locked by an incorrect pin.')
204
205
206    def test_cause_sim_pin_lock(self):
207        """
208        Test successfully enabling SIM lock and locking the SIM with
209        pin-lock.
210
211        """
212        logging.debug('Attempting to enable SIM lock with correct pin.')
213        self.device.RequirePin(self.current_pin, True)
214
215        if not self._is_sim_lock_enabled():
216            raise error.TestFail('SIM lock was not enabled by correct PIN.')
217
218        self._reset_modem_with_sim_lock()
219        # SIM lock should be enabled, and lock set after reset.
220        if not self._is_sim_lock_enabled() or not self._is_sim_pin_locked():
221            raise error.TestFail('Cellular device not locked after reset.')
222
223
224    def test_unlock_sim_pin_lock(self):
225        """
226        Test successfully unlocking the SIM after it has been pin-locked.
227
228        """
229        # First, pin-lock the SIM.
230        self._pin_lock_sim()
231
232        retries_left = self._get_retries_left()
233        self.device.EnterPin(self.current_pin)
234
235        if self._is_sim_pin_locked():
236            raise error.TestFail('Failed to unlock a pin-locked SIM with '
237                                 'correct pin.')
238        if not self._is_sim_lock_enabled():
239            raise error.TestFail('SIM lock got disabled when attemping to'
240                                 'unlock a pin-locked SIM.')
241        if self._get_retries_left() != retries_left:
242            raise error.TestFail('Unexpected change in number of retries left '
243                                 'after a successful unlock of pin-locked SIM. '
244                                 'retries before:%d, after:%d' %
245                                 (retries_left, self._get_retries_left()))
246        # The shill service reappears after the SIM is unlocked.
247        # We need a fresh handle on the service.
248        utils.poll_for_condition(
249                lambda: self.test_env.shill.get_service_for_device(self.device))
250        self.service = self.test_env.shill.get_service_for_device(self.device)
251        self.test_env.shill.wait_for_property_in(self.service,
252                                                 'state',
253                                                 ['online'],
254                                                 DEFAULT_OPERATION_TIMEOUT)
255
256
257    def test_cause_sim_puk_lock(self):
258        """ Test the flow that causes a SIM to be puk-locked. """
259        # First, pin-lock the SIM.
260        self._pin_lock_sim()
261
262        # Expire all unlock pin-lock retries.
263        retries_left = self._get_retries_left()
264        if retries_left <= 0:
265            raise error.TestFail('Expected a positive number of sim-puk '
266                                 'retries.')
267
268        while self._get_retries_left() > 1:
269            # Don't execute the loop down to 0, as retries_left may be reset to
270            # a higher value corresponding to the puk-lock retries.
271            self._enter_incorrect_pin()
272            if retries_left - self._get_retries_left() != 1:
273                raise error.TestFail('RetriesLeft not decremented correctly by '
274                                     'an attempt to unlock pin-lock with bad '
275                                     'PIN.')
276            retries_left = self._get_retries_left()
277
278        # retries_left == 1
279        try:
280            self._enter_incorrect_pin()
281            raise error.TestFail('Shill failed to throw PinBlocked error.')
282        except dbus.DBusException as e:
283            if e.get_dbus_name() != self.test_env.shill.ERROR_PIN_BLOCKED:
284                raise
285
286        # At this point, the SIM should be puk-locked.
287        if not self._is_sim_lock_enabled() or not self._is_sim_puk_locked():
288            raise error.TestFail('Could not puk-lock the SIM after sufficient '
289                                 'incorrect attempts to unlock.')
290        if not self._get_retries_left():
291            raise error.TestFail('RetriesLeft not updated to puk-lock retries '
292                                 'after the SIM got puk-locked.')
293
294
295    def test_unlock_sim_puk_lock(self):
296        """ Unlock a puk-locked SIM. """
297        # First, puk-lock the SIM
298        self._puk_lock_sim()
299
300        retries_left = self._get_retries_left()
301        self.device.UnblockPin(self.current_puk, self.current_pin)
302
303        if self._is_sim_puk_locked():
304            raise error.TestFail('Failed to unlock a puk-locked SIM with '
305                                 'correct puk.')
306        if self._is_sim_pin_locked():
307            raise error.TestFail('pin-lock got unlocked while unlocking the '
308                                 'puk-lock.')
309        if not self._is_sim_lock_enabled():
310            raise error.TestFail('SIM lock got disabled when attemping to'
311                                 'unlock a pin-locked SIM.')
312
313    def test_brick_sim(self):
314        """ Test the flow that expires all pin-lock and puk-lock retries. """
315        # First, puk-lock the SIM.
316        self._puk_lock_sim()
317
318        # Expire all unlock puk-lock retries.
319        retries_left = self._get_retries_left()
320        if retries_left <= 0:
321            raise error.TestFail('Expected a positive number of sim-puk '
322                                 'retries.')
323
324        while self._get_retries_left() > 1:
325            # Don't execute the loop down to 0, as the exception raised on the
326            # last attempt is different.
327            self._enter_incorrect_puk()
328            if retries_left - self._get_retries_left() != 1:
329                raise error.TestFail('RetriesLeft not decremented correctly by '
330                                     'an attempt to unlock puk-lock with bad '
331                                     'PUK.')
332            retries_left = self._get_retries_left()
333
334        # retries_left == 1
335        try:
336            self._enter_incorrect_puk()
337            raise error.TestFail('Shill failed to throw SimFailure error.')
338        except dbus.DBusException as e:
339            if e.get_dbus_name() != self.test_env.shill.ERROR_FAILURE:
340                raise
341
342
343    def test_change_pin(self):
344        """ Test changing pin successfully and unsuccessfully. """
345        # The currently accepted behaviour of ChangePin is -- it succeeds if
346        #  (1) SIM locking is enabled.
347        #  (2) SIM is currently not locked.
348        #  (3) The correct sim-pin is used as the old_pin argument in ChangePin.
349        # ChangePin will fail in all other conditions. It sometimes fails
350        # obviously, with an error. In other cases, it silently fails to change
351        # the sim-pin.
352        new_pin = self._bad_pin()
353        # Attempt to change the sim-pin when SIM locking is not enabled.
354        try:
355            self.device.ChangePin(self.current_pin, new_pin)
356            raise error.TestFail('Expected ChangePin to fail when SIM lock is '
357                                 'not enabled.')
358        except dbus.DBusException as e:
359            if e.get_dbus_name() != self.test_env.shill.ERROR_FAILURE:
360                raise
361
362        self.device.RequirePin(self.current_pin, True)
363        # Attempt to change the sim-pin with incorrect current sim_pin.
364        try:
365            self.device.ChangePin(self._bad_pin(), new_pin)
366            raise error.TestFail('Expected ChangePin to fail with incorrect '
367                                 'sim-pin.')
368        except dbus.DBusException as e:
369            if e.get_dbus_name() != self.test_env.shill.ERROR_INCORRECT_PIN:
370                raise
371
372        # Change sim-pin successfully.
373        self.device.ChangePin(self.current_pin, new_pin)
374        self.current_pin = new_pin
375        self.device.RequirePin(self.current_pin, False)
376        if self._is_sim_lock_enabled():
377            raise error.TestFail('Expected to be able to disable SIM lock with '
378                                 'the new sim-pin')
379
380
381    def _run_internal(self, test_to_run):
382        """
383        Entry point to run all tests.
384
385        @param test_to_run is a function that runs the required test.
386
387        """
388        self.current_pin = sim.SIM.DEFAULT_PIN
389        self.current_puk = sim.SIM.DEFAULT_PUK
390
391        # Resetting modemmanager invalidates the shill dbus object for the
392        # modem.
393        self.device = self.test_env.shill.find_cellular_device_object()
394        if not self.device:
395            raise error.TestFail('Failed to find a cellular device.')
396
397        # Be a little cynical and make sure that SIM locks are as expected
398        # before we begin.
399        if (self._is_sim_lock_enabled() or self._is_sim_pin_locked() or
400            self._is_sim_puk_locked()):
401            raise error.TestFail(
402                    'Cellular device in bad initial sim-lock state. '
403                    'LockEnabled: %b, PinLocked:%b, PukLocked:%b.' %
404                    (self._is_sim_lock_enabled(), self._is_sim_pin_locked(),
405                     self._is_sim_puk_locked()))
406
407        test_to_run()
408
409
410    def run_once(self):
411        """Entry function into the test."""
412        random.seed()
413        test_list = [self.test_unsuccessful_enable_lock,
414                     self.test_cause_sim_pin_lock,
415                     self.test_unlock_sim_pin_lock,
416                     self.test_cause_sim_puk_lock,
417                     self.test_unlock_sim_puk_lock,
418                     self.test_brick_sim,
419                     self.test_change_pin]
420
421        # Some of these tests render the modem unusable, so run each test
422        # with a fresh pseudomodem.
423        for test in test_list:
424            self.test_env = test_environment.CellularPseudoMMTestEnvironment(
425                    pseudomm_args=({'family': '3GPP'},))
426            with self.test_env:
427                self._run_internal(test)
428