普通文本  |  428行  |  17.58 KB

# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import dbus
import logging
import random

from autotest_lib.client.bin import test
from autotest_lib.client.bin import utils
from autotest_lib.client.common_lib import error
from autotest_lib.client.cros.cellular import test_environment
from autotest_lib.client.cros.cellular.pseudomodem import sim

# This is a software only test. Most time delayes are only dbus update delays.
DEFAULT_OPERATION_TIMEOUT=3

class cellular_SIMLocking(test.test):
    """
    Test the SIM locking functionality of shill.

    This test has the following test_cases:
      - Attempt to enable SIM lock with incorrect sim-pin. Verify that the
        attempt fails.
      - Successfully pin-lock the SIM.
      - Unlock a pin-locked SIM.
      - Attempt to unlock a pin-locked SIM with incorrect sim-pin, until it gets
        puk-locked.
      - Unblock a puk-locked SIM.
      - Attempt to unblock a puk-locked SIM with incorrect sim-puk, until the
        SIM gets blocked. At this point, a sim-pin2 might be expected by some
        SIMs. This test does not attempt to unlock the SIM using sim-pin2.
      - Test the functionality to change sim-pin.

    """

    version = 1

    def _bad_pin(self):
        """ Obtain a pin that does not match the valid sim-pin. """
        # Restricting the values to be  >= 1000 ensures four digit string.
        bad_pin = random.randint(1000, 9999)
        if str(bad_pin) == self.current_pin:
            bad_pin += 1
        return str(bad_pin)


    def _bad_puk(self):
        """ Obtain a puk that does not match the valid sim-puk. """
        # Restricting the values to be  >= 10000000 ensures 8 digit string.
        bad_puk = random.randint(10000000, 99999999)
        if str(bad_puk) == self.current_puk:
            bad_puk += 1
        return str(bad_puk)


    def _enter_incorrect_pin(self):
        try:
            self.device.EnterPin(self._bad_pin())
            raise error.TestFail('Cellular device did not complain although '
                                 'an incorrect pin was given')
        except dbus.DBusException as e:
            if e.get_dbus_name() == self.test_env.shill.ERROR_INCORRECT_PIN:
                logging.info('Obtained expected result: EnterPin failed with '
                             'incorrect PIN.')
            else:
                raise


    def _enter_incorrect_puk(self):
        try:
            self.device.UnblockPin(self._bad_puk(), self.current_pin)
            raise error.TestFail('Cellular device did not complain although '
                                 'an incorrect puk was given')
        except dbus.DBusException as e:
            if e.get_dbus_name() == self.test_env.shill.ERROR_INCORRECT_PIN:
                logging.info('Obtained expected result: UnblockPin failed with '
                             'incorrect PUK.')
            else:
                raise


    def _get_sim_lock_status(self):
        """ Helper method to safely obtain SIM lock status. """
        properties = self.device.GetProperties(utf8_strings=True)
        sim_lock_status = properties.get(
                self.test_env.shill.DEVICE_PROPERTY_SIM_LOCK_STATUS,
                None)
        if sim_lock_status is None:
            raise error.TestFail( 'Failed to read SIM_LOCK_STATUS.')
        return self.test_env.shill.dbus2primitive(sim_lock_status)


    def _is_sim_lock_enabled(self):
        """ Helper method to check if the SIM lock is enabled. """
        lock_status = self._get_sim_lock_status()
        lock_enabled = lock_status.get(
                self.test_env.shill.PROPERTY_KEY_SIM_LOCK_ENABLED,
                None)
        if lock_enabled is None:
            raise error.TestFail('Failed to find LockEnabled key in '
                                 'the lock status value.')
        return lock_enabled


    def _is_sim_pin_locked(self):
        """ Helper method to check if the SIM has been pin-locked. """
        lock_status = self._get_sim_lock_status()
        lock_type = lock_status.get(
                self.test_env.shill.PROPERTY_KEY_SIM_LOCK_TYPE,
                None)
        if lock_type is None:
            raise error.TestFail('Failed to find LockType key in the '
                                 'lock status value.')
        return lock_type == self.test_env.shill.VALUE_SIM_LOCK_TYPE_PIN


    def _is_sim_puk_locked(self):
        """ Helper method to check if the SIM has been puk-locked. """
        lock_status = self._get_sim_lock_status()
        lock_type = lock_status.get(
                self.test_env.shill.PROPERTY_KEY_SIM_LOCK_TYPE,
                None)
        if lock_type is None:
            raise error.TestFail('Failed to find LockType key in the '
                                 'lock status value.')
        return lock_type == self.test_env.shill.VALUE_SIM_LOCK_TYPE_PUK


    def _get_retries_left(self):
        """ Helper method to get the number of unlock retries left. """
        lock_status = self._get_sim_lock_status()
        retries_left = lock_status.get(
                self.test_env.shill.PROPERTY_KEY_SIM_LOCK_RETRIES_LEFT,
                None)
        if retries_left is None:
            raise error.TestFail('Failed to find LockRetriesLeft key '
                                 'in the lock status value.')
        if retries_left < 0:
            raise error.TestFail('Malformed RetriesLeft: %s' %
                                 str(retries_left))
        return retries_left

    def _reset_modem_with_sim_lock(self):
        """ Helper method to reset the modem with the SIM locked. """
        # When the SIM is locked, the enable operation fails and
        # hence set expect_powered flag to False.
        # The enable operation is deferred by Shill until the modem goes into
        # the disabled state after the SIM is unlocked.
        self.device, self.service = self.test_env.shill.reset_modem(
                self.device,
                expect_powered=False,
                expect_service=False)

    def _pin_lock_sim(self):
        """ Helper method to pin-lock a SIM, assuming nothing bad happens. """
        self.device.RequirePin(self.current_pin, True)
        self._reset_modem_with_sim_lock()
        if not self._is_sim_pin_locked():
            raise error.TestFail('Expected SIM to be locked after reset.')


    def _puk_lock_sim(self):
        """ Helper method to puk-lock a SIM, assuming nothing bad happens. """
        self._pin_lock_sim()
        while not self._is_sim_puk_locked():
            try:
                self._enter_incorrect_pin()
            except dbus.DBusException as e:
                if e.get_dbus_name() != self.test_env.shill.ERROR_PIN_BLOCKED:
                    raise
        if not self._is_sim_puk_locked():
            raise error.TestFail('Expected SIM to be puk-locked.')




    def test_unsuccessful_enable_lock(self):
        """ Test SIM lock enable failes with incorrect sim-pin. """
        logging.debug('Attempting to enable SIM lock with incorrect PIN.')
        try:
            self.device.RequirePin(self._bad_pin(), True)
            raise error.TestFail('Cellular device did not complain although '
                                 'an incorrect pin was given')
        except dbus.DBusException as e:
            if e.get_dbus_name() == self.test_env.shill.ERROR_INCORRECT_PIN:
                logging.info('Obtained expected result: pin-lock enable failed '
                             'with incorrect PIN.')
            else:
                raise

        if self._is_sim_lock_enabled():
            raise error.TestFail('SIM lock got enabled by incorrect PIN.')

        # SIM lock should not be enabled, and lock not set after reset.
        self.device, self.service = self.test_env.shill.reset_modem(self.device)
        self.test_env.shill.wait_for_property_in(self.service,
                                                 'state',
                                                 ['online'],
                                                 DEFAULT_OPERATION_TIMEOUT)
        if (self._is_sim_lock_enabled() or self._is_sim_pin_locked() or
            self._is_sim_puk_locked()):
            raise error.TestFail('Cellular device locked by an incorrect pin.')


    def test_cause_sim_pin_lock(self):
        """
        Test successfully enabling SIM lock and locking the SIM with
        pin-lock.

        """
        logging.debug('Attempting to enable SIM lock with correct pin.')
        self.device.RequirePin(self.current_pin, True)

        if not self._is_sim_lock_enabled():
            raise error.TestFail('SIM lock was not enabled by correct PIN.')

        self._reset_modem_with_sim_lock()
        # SIM lock should be enabled, and lock set after reset.
        if not self._is_sim_lock_enabled() or not self._is_sim_pin_locked():
            raise error.TestFail('Cellular device not locked after reset.')


    def test_unlock_sim_pin_lock(self):
        """
        Test successfully unlocking the SIM after it has been pin-locked.

        """
        # First, pin-lock the SIM.
        self._pin_lock_sim()

        retries_left = self._get_retries_left()
        self.device.EnterPin(self.current_pin)

        if self._is_sim_pin_locked():
            raise error.TestFail('Failed to unlock a pin-locked SIM with '
                                 'correct pin.')
        if not self._is_sim_lock_enabled():
            raise error.TestFail('SIM lock got disabled when attemping to'
                                 'unlock a pin-locked SIM.')
        if self._get_retries_left() != retries_left:
            raise error.TestFail('Unexpected change in number of retries left '
                                 'after a successful unlock of pin-locked SIM. '
                                 'retries before:%d, after:%d' %
                                 (retries_left, self._get_retries_left()))
        # The shill service reappears after the SIM is unlocked.
        # We need a fresh handle on the service.
        utils.poll_for_condition(
                lambda: self.test_env.shill.get_service_for_device(self.device))
        self.service = self.test_env.shill.get_service_for_device(self.device)
        self.test_env.shill.wait_for_property_in(self.service,
                                                 'state',
                                                 ['online'],
                                                 DEFAULT_OPERATION_TIMEOUT)


    def test_cause_sim_puk_lock(self):
        """ Test the flow that causes a SIM to be puk-locked. """
        # First, pin-lock the SIM.
        self._pin_lock_sim()

        # Expire all unlock pin-lock retries.
        retries_left = self._get_retries_left()
        if retries_left <= 0:
            raise error.TestFail('Expected a positive number of sim-puk '
                                 'retries.')

        while self._get_retries_left() > 1:
            # Don't execute the loop down to 0, as retries_left may be reset to
            # a higher value corresponding to the puk-lock retries.
            self._enter_incorrect_pin()
            if retries_left - self._get_retries_left() != 1:
                raise error.TestFail('RetriesLeft not decremented correctly by '
                                     'an attempt to unlock pin-lock with bad '
                                     'PIN.')
            retries_left = self._get_retries_left()

        # retries_left == 1
        try:
            self._enter_incorrect_pin()
            raise error.TestFail('Shill failed to throw PinBlocked error.')
        except dbus.DBusException as e:
            if e.get_dbus_name() != self.test_env.shill.ERROR_PIN_BLOCKED:
                raise

        # At this point, the SIM should be puk-locked.
        if not self._is_sim_lock_enabled() or not self._is_sim_puk_locked():
            raise error.TestFail('Could not puk-lock the SIM after sufficient '
                                 'incorrect attempts to unlock.')
        if not self._get_retries_left():
            raise error.TestFail('RetriesLeft not updated to puk-lock retries '
                                 'after the SIM got puk-locked.')


    def test_unlock_sim_puk_lock(self):
        """ Unlock a puk-locked SIM. """
        # First, puk-lock the SIM
        self._puk_lock_sim()

        retries_left = self._get_retries_left()
        self.device.UnblockPin(self.current_puk, self.current_pin)

        if self._is_sim_puk_locked():
            raise error.TestFail('Failed to unlock a puk-locked SIM with '
                                 'correct puk.')
        if self._is_sim_pin_locked():
            raise error.TestFail('pin-lock got unlocked while unlocking the '
                                 'puk-lock.')
        if not self._is_sim_lock_enabled():
            raise error.TestFail('SIM lock got disabled when attemping to'
                                 'unlock a pin-locked SIM.')

    def test_brick_sim(self):
        """ Test the flow that expires all pin-lock and puk-lock retries. """
        # First, puk-lock the SIM.
        self._puk_lock_sim()

        # Expire all unlock puk-lock retries.
        retries_left = self._get_retries_left()
        if retries_left <= 0:
            raise error.TestFail('Expected a positive number of sim-puk '
                                 'retries.')

        while self._get_retries_left() > 1:
            # Don't execute the loop down to 0, as the exception raised on the
            # last attempt is different.
            self._enter_incorrect_puk()
            if retries_left - self._get_retries_left() != 1:
                raise error.TestFail('RetriesLeft not decremented correctly by '
                                     'an attempt to unlock puk-lock with bad '
                                     'PUK.')
            retries_left = self._get_retries_left()

        # retries_left == 1
        try:
            self._enter_incorrect_puk()
            raise error.TestFail('Shill failed to throw SimFailure error.')
        except dbus.DBusException as e:
            if e.get_dbus_name() != self.test_env.shill.ERROR_FAILURE:
                raise


    def test_change_pin(self):
        """ Test changing pin successfully and unsuccessfully. """
        # The currently accepted behaviour of ChangePin is -- it succeeds if
        #  (1) SIM locking is enabled.
        #  (2) SIM is currently not locked.
        #  (3) The correct sim-pin is used as the old_pin argument in ChangePin.
        # ChangePin will fail in all other conditions. It sometimes fails
        # obviously, with an error. In other cases, it silently fails to change
        # the sim-pin.
        new_pin = self._bad_pin()
        # Attempt to change the sim-pin when SIM locking is not enabled.
        try:
            self.device.ChangePin(self.current_pin, new_pin)
            raise error.TestFail('Expected ChangePin to fail when SIM lock is '
                                 'not enabled.')
        except dbus.DBusException as e:
            if e.get_dbus_name() != self.test_env.shill.ERROR_FAILURE:
                raise

        self.device.RequirePin(self.current_pin, True)
        # Attempt to change the sim-pin with incorrect current sim_pin.
        try:
            self.device.ChangePin(self._bad_pin(), new_pin)
            raise error.TestFail('Expected ChangePin to fail with incorrect '
                                 'sim-pin.')
        except dbus.DBusException as e:
            if e.get_dbus_name() != self.test_env.shill.ERROR_INCORRECT_PIN:
                raise

        # Change sim-pin successfully.
        self.device.ChangePin(self.current_pin, new_pin)
        self.current_pin = new_pin
        self.device.RequirePin(self.current_pin, False)
        if self._is_sim_lock_enabled():
            raise error.TestFail('Expected to be able to disable SIM lock with '
                                 'the new sim-pin')


    def _run_internal(self, test_to_run):
        """
        Entry point to run all tests.

        @param test_to_run is a function that runs the required test.

        """
        self.current_pin = sim.SIM.DEFAULT_PIN
        self.current_puk = sim.SIM.DEFAULT_PUK

        # Resetting modemmanager invalidates the shill dbus object for the
        # modem.
        self.device = self.test_env.shill.find_cellular_device_object()
        if not self.device:
            raise error.TestFail('Failed to find a cellular device.')

        # Be a little cynical and make sure that SIM locks are as expected
        # before we begin.
        if (self._is_sim_lock_enabled() or self._is_sim_pin_locked() or
            self._is_sim_puk_locked()):
            raise error.TestFail(
                    'Cellular device in bad initial sim-lock state. '
                    'LockEnabled: %b, PinLocked:%b, PukLocked:%b.' %
                    (self._is_sim_lock_enabled(), self._is_sim_pin_locked(),
                     self._is_sim_puk_locked()))

        test_to_run()


    def run_once(self):
        """Entry function into the test."""
        random.seed()
        test_list = [self.test_unsuccessful_enable_lock,
                     self.test_cause_sim_pin_lock,
                     self.test_unlock_sim_pin_lock,
                     self.test_cause_sim_puk_lock,
                     self.test_unlock_sim_puk_lock,
                     self.test_brick_sim,
                     self.test_change_pin]

        # Some of these tests render the modem unusable, so run each test
        # with a fresh pseudomodem.
        for test in test_list:
            self.test_env = test_environment.CellularPseudoMMTestEnvironment(
                    pseudomm_args=({'family': '3GPP'},))
            with self.test_env:
                self._run_internal(test)