• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2018 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 logging
6import os
7import time
8
9from autotest_lib.server import test
10from autotest_lib.client.common_lib import error, utils
11from autotest_lib.server.cros import gsutil_wrapper
12from autotest_lib.server.cros.dynamic_suite import constants as ds_constants
13
14
15class FingerprintTest(test.test):
16    """Base class that sets up helpers for fingerprint tests."""
17    version = 1
18
19    _FINGERPRINT_BOARD_NAME_SUFFIX = '_fp'
20
21    # Location of firmware from the build on the DUT
22    _FINGERPRINT_BUILD_FW_DIR = '/opt/google/biod/fw'
23
24    _GENIMAGES_SCRIPT_NAME = 'gen_test_images.sh'
25    _GENIMAGES_OUTPUT_DIR_NAME = 'images'
26
27    _TEST_IMAGE_FORMAT_MAP = {
28        'TEST_IMAGE_ORIGINAL': '%s.bin',
29        'TEST_IMAGE_DEV': '%s.dev',
30        'TEST_IMAGE_CORRUPT_FIRST_BYTE': '%s_corrupt_first_byte.bin',
31        'TEST_IMAGE_CORRUPT_LAST_BYTE': '%s_corrupt_last_byte.bin',
32        'TEST_IMAGE_DEV_RB_ZERO': '%s.dev.rb0',
33        'TEST_IMAGE_DEV_RB_ONE': '%s.dev.rb1',
34        'TEST_IMAGE_DEV_RB_NINE': '%s.dev.rb9'
35    }
36
37    _ROLLBACK_INITIAL_BLOCK_ID = '1'
38    _ROLLBACK_INITIAL_MIN_VERSION = '0'
39    _ROLLBACK_INITIAL_RW_VERSION = '0'
40
41    _SERVER_GENERATED_FW_DIR_NAME = 'generated_fw'
42
43    _DUT_TMP_PATH_BASE = '/tmp/fp_test'
44
45    # Name of key in "futility show" output corresponds to the signing key ID
46    _FUTILITY_KEY_ID_KEY_NAME = 'ID'
47
48    # Types of firmware
49    _FIRMWARE_TYPE_RO = 'RO'
50    _FIRMWARE_TYPE_RW = 'RW'
51
52    # Types of signing keys
53    _KEY_TYPE_DEV = 'dev'
54    _KEY_TYPE_PRE_MP = 'premp'
55    _KEY_TYPE_MP = 'mp'
56
57    # EC board names for FPMCUs
58    _FP_BOARD_NAME_DARTMONKEY = 'dartmonkey'
59    _FP_BOARD_NAME_NOCTURNE = 'nocturne_fp'
60    _FP_BOARD_NAME_NAMI = 'nami_fp'
61
62    # Map from signing key ID to type of signing key
63    _KEY_ID_MAP_ = {
64        # dartmonkey
65        '257a0aa3ac9e81aa4bc3aabdb6d3d079117c5799': _KEY_TYPE_MP,
66
67        # nocturne
68        '6f38c866182bd9bf7a4462c06ac04fa6a0074351': _KEY_TYPE_MP,
69        'f6f7d96c48bd154dbae7e3fe3a3b4c6268a10934': _KEY_TYPE_PRE_MP,
70
71        # nami
72        '754aea623d69975a22998f7b97315dd53115d723': _KEY_TYPE_PRE_MP,
73        '35486c0090ca390408f1fbbf2a182966084fe2f8': _KEY_TYPE_MP
74
75    }
76
77    # RO versions that are flashed in the factory
78    # (for eternity for a given board)
79    _GOLDEN_RO_FIRMWARE_VERSION_MAP = {
80        _FP_BOARD_NAME_DARTMONKEY: 'dartmonkey_v2.0.2887-311310808',
81        _FP_BOARD_NAME_NOCTURNE: 'nocturne_fp_v2.2.64-58cf5974e',
82        _FP_BOARD_NAME_NAMI: 'nami_fp_v2.2.144-7a08e07eb',
83    }
84
85    _FIRMWARE_VERSION_SHA256SUM = 'sha256sum'
86    _FIRMWARE_VERSION_RO_VERSION = 'ro_version'
87    _FIRMWARE_VERSION_RW_VERSION = 'rw_version'
88    _FIRMWARE_VERSION_KEY_ID = 'key_id'
89
90    # Map of attributes for a given board's various firmware file releases
91    #
92    # Two purposes:
93    #   1) Documents the exact versions and keys used for a given firmware file.
94    #   2) Used to verify that files that end up in the build (and therefore
95    #      what we release) is exactly what we expect.
96    _FIRMWARE_VERSION_MAP = {
97        _FP_BOARD_NAME_NOCTURNE: {
98            'nocturne_fp_v2.2.110-b936c0a3c.bin': {
99                _FIRMWARE_VERSION_SHA256SUM: '9da32787d68e2ac408be55a4d8c7de13e0f8c0a4a9912d69108b91794e90ee4b',
100                _FIRMWARE_VERSION_RO_VERSION: 'nocturne_fp_v2.2.64-58cf5974e',
101                _FIRMWARE_VERSION_RW_VERSION: 'nocturne_fp_v2.2.110-b936c0a3c',
102                _FIRMWARE_VERSION_KEY_ID: '6f38c866182bd9bf7a4462c06ac04fa6a0074351',
103            },
104            'nocturne_fp_v2.2.191-1d529566e.bin': {
105                _FIRMWARE_VERSION_SHA256SUM: 'e21223d6a3dbb7a6c6f2905f602f972b8a2b6d0fb52e262931745dae808e7c4d',
106                _FIRMWARE_VERSION_RO_VERSION: 'nocturne_fp_v2.2.64-58cf5974e',
107                _FIRMWARE_VERSION_RW_VERSION: 'nocturne_fp_v2.2.191-1d529566e',
108                _FIRMWARE_VERSION_KEY_ID: '6f38c866182bd9bf7a4462c06ac04fa6a0074351',
109            },
110            'nocturne_fp_v2.0.3266-99b5e2c98.bin': {
111                _FIRMWARE_VERSION_SHA256SUM: '73d822071518cf1b6e705d9c5903c2bcf37bae536784b275b96d916c44d3b6b7',
112                _FIRMWARE_VERSION_RO_VERSION: 'nocturne_fp_v2.2.64-58cf5974e',
113                _FIRMWARE_VERSION_RW_VERSION: 'nocturne_fp_v2.0.3266-99b5e2c98',
114                _FIRMWARE_VERSION_KEY_ID: '6f38c866182bd9bf7a4462c06ac04fa6a0074351',
115            },
116        },
117        _FP_BOARD_NAME_NAMI: {
118            'nami_fp_v2.2.144-7a08e07eb.bin': {
119                _FIRMWARE_VERSION_SHA256SUM: '375d7fcbb3f1fd8837b0572b4ef7fa848189d3ac53ced5dcb1abe3ddca9f11c4',
120                _FIRMWARE_VERSION_RO_VERSION: 'nami_fp_v2.2.144-7a08e07eb',
121                _FIRMWARE_VERSION_RW_VERSION: 'nami_fp_v2.2.144-7a08e07eb',
122                _FIRMWARE_VERSION_KEY_ID: '35486c0090ca390408f1fbbf2a182966084fe2f8',
123            },
124            'nami_fp_v2.2.191-1d529566e.bin': {
125                _FIRMWARE_VERSION_SHA256SUM: '684b63b1a2c929cf2be2fd564e9ae6032fdbbcfe0991d860c540420c40405776',
126                _FIRMWARE_VERSION_RO_VERSION: 'nami_fp_v2.2.144-7a08e07eb',
127                _FIRMWARE_VERSION_RW_VERSION: 'nami_fp_v2.2.191-1d529566e',
128                _FIRMWARE_VERSION_KEY_ID: '35486c0090ca390408f1fbbf2a182966084fe2f8',
129            },
130            'nami_fp_v2.0.3266-99b5e2c98.bin': {
131                _FIRMWARE_VERSION_SHA256SUM: '115bca7045428ce6639b41cc0fdc13d1ca414f6e76842e805a9fbb798a9cd7ad',
132                _FIRMWARE_VERSION_RO_VERSION: 'nami_fp_v2.2.144-7a08e07eb',
133                _FIRMWARE_VERSION_RW_VERSION: 'nami_fp_v2.0.3266-99b5e2c98',
134                _FIRMWARE_VERSION_KEY_ID: '35486c0090ca390408f1fbbf2a182966084fe2f8',
135            },
136        },
137        _FP_BOARD_NAME_DARTMONKEY: {
138            'dartmonkey_v2.0.2887-311310808.bin': {
139                _FIRMWARE_VERSION_SHA256SUM: '90716b73d1db5a1b6108530be1d11addf3b13e643bc6f96d417cbce383f3cb18',
140                _FIRMWARE_VERSION_RO_VERSION: 'dartmonkey_v2.0.2887-311310808',
141                _FIRMWARE_VERSION_RW_VERSION: 'dartmonkey_v2.0.2887-311310808',
142                _FIRMWARE_VERSION_KEY_ID: '257a0aa3ac9e81aa4bc3aabdb6d3d079117c5799',
143            },
144            'dartmonkey_v2.0.3266-99b5e2c98.bin': {
145                _FIRMWARE_VERSION_SHA256SUM: 'ac1c74b5d2676923f041ee1a27bf5b9892fab1d4f82fe924550a9b55917606ae',
146                _FIRMWARE_VERSION_RO_VERSION: 'dartmonkey_v2.0.2887-311310808',
147                _FIRMWARE_VERSION_RW_VERSION: 'dartmonkey_v2.0.3266-99b5e2c98',
148                _FIRMWARE_VERSION_KEY_ID: '257a0aa3ac9e81aa4bc3aabdb6d3d079117c5799',
149            }
150        }
151    }
152
153    _BIOD_UPSTART_JOB_NAME = 'biod'
154    # TODO(crbug.com/925545)
155    _TIMBERSLIDE_UPSTART_JOB_NAME = \
156        'timberslide LOG_PATH=/sys/kernel/debug/cros_fp/console_log'
157
158    _INIT_ENTROPY_CMD = 'bio_wash --factory_init'
159
160    _CROS_FP_ARG = '--name=cros_fp'
161    _CROS_CONFIG_FINGERPRINT_PATH = '/fingerprint'
162    _ECTOOL_RO_VERSION = 'RO version'
163    _ECTOOL_RW_VERSION = 'RW version'
164    _ECTOOL_FIRMWARE_COPY = 'Firmware copy'
165    _ECTOOL_ROLLBACK_BLOCK_ID = 'Rollback block id'
166    _ECTOOL_ROLLBACK_MIN_VERSION = 'Rollback min version'
167    _ECTOOL_ROLLBACK_RW_VERSION = 'RW rollback version'
168
169    @staticmethod
170    def _parse_colon_delimited_output(ectool_output):
171        """
172        Converts ectool's (or any other tool with similar output) colon
173        delimited output into python dict. Ignores any lines that do not have
174        colons.
175
176        Example:
177        RO version:    nocturne_fp_v2.2.64-58cf5974e
178        RW version:    nocturne_fp_v2.2.110-b936c0a3c
179
180        becomes:
181        {
182          'RO version': 'nocturne_fp_v2.2.64-58cf5974e',
183          'RW version': 'nocturne_fp_v2.2.110-b936c0a3c'
184        }
185        """
186        ret = {}
187        try:
188            for line in ectool_output.strip().split('\n'):
189                splits = line.split(':', 1)
190                if len(splits) != 2:
191                    continue
192                key = splits[0].strip()
193                val = splits[1].strip()
194                ret[key] = val
195        except:
196            raise error.TestFail('Unable to parse ectool output: %s'
197                                 % ectool_output)
198        return ret
199
200    def initialize(self, host, test_dir, use_dev_signed_fw=False,
201                   enable_hardware_write_protect=True,
202                   enable_software_write_protect=True,
203                   force_firmware_flashing=False, init_entropy=True):
204        """Performs initialization."""
205        self.host = host
206        self.servo = host.servo
207
208        self._validate_compatible_servo_version()
209
210        self.servo.initialize_dut()
211
212        logging.info('HW write protect enabled: %s',
213                     self.is_hardware_write_protect_enabled())
214
215        # TODO(crbug.com/925545): stop timberslide so /var/log/cros_fp.log
216        # continues to update after flashing.
217        self._timberslide_running = self.host.upstart_status(
218            self._TIMBERSLIDE_UPSTART_JOB_NAME)
219        if self._timberslide_running:
220            logging.info('Stopping %s', self._TIMBERSLIDE_UPSTART_JOB_NAME)
221            self.host.upstart_stop(self._TIMBERSLIDE_UPSTART_JOB_NAME)
222
223        self._biod_running = self.host.upstart_status(
224            self._BIOD_UPSTART_JOB_NAME)
225        if self._biod_running:
226            logging.info('Stopping %s', self._BIOD_UPSTART_JOB_NAME)
227            self.host.upstart_stop(self._BIOD_UPSTART_JOB_NAME)
228
229        # create tmp working directory on device (automatically cleaned up)
230        self._dut_working_dir = self.host.get_tmp_dir(
231            parent=self._DUT_TMP_PATH_BASE)
232        logging.info('Created dut_working_dir: %s', self._dut_working_dir)
233        self.copy_files_to_dut(test_dir, self._dut_working_dir)
234
235        self._build_fw_file = self.get_build_fw_file()
236        self._validate_build_fw_file(self._build_fw_file)
237
238        gen_script = os.path.abspath(os.path.join(self.autodir,
239                                                  'server', 'cros', 'faft',
240                                                  self._GENIMAGES_SCRIPT_NAME))
241        self._dut_firmware_test_images_dir = \
242            self._generate_test_firmware_images(gen_script,
243                                                self._build_fw_file,
244                                                self._dut_working_dir)
245        logging.info('dut_firmware_test_images_dir: %s',
246                     self._dut_firmware_test_images_dir)
247
248        self._initialize_test_firmware_image_attrs(
249            self._dut_firmware_test_images_dir)
250
251        self._initialize_running_fw_version(use_dev_signed_fw,
252                                            force_firmware_flashing)
253        if init_entropy:
254            self._initialize_fw_entropy()
255
256        self._initialize_hw_and_sw_write_protect(enable_hardware_write_protect,
257                                                 enable_software_write_protect)
258
259    def cleanup(self):
260        """Restores original state."""
261        # Once the tests complete we need to make sure we're running the
262        # original firmware (not dev version) and potentially reset rollback.
263        self._initialize_running_fw_version(use_dev_signed_fw=False,
264                                            force_firmware_flashing=False)
265        self._initialize_fw_entropy()
266        self._initialize_hw_and_sw_write_protect(
267            enable_hardware_write_protect=True,
268            enable_software_write_protect=True)
269        if hasattr(self, '_biod_running') and self._biod_running:
270            logging.info('Restarting biod')
271            self.host.upstart_restart(self._BIOD_UPSTART_JOB_NAME)
272        # TODO(crbug.com/925545)
273        if hasattr(self, '_timberslide_running') and self._timberslide_running:
274            logging.info('Restarting timberslide')
275            self.host.upstart_restart(self._TIMBERSLIDE_UPSTART_JOB_NAME)
276
277        super(FingerprintTest, self).cleanup()
278
279    def after_run_once(self):
280        """Logs which iteration just ran."""
281        logging.info('successfully ran iteration %d', self.iteration)
282
283    def _validate_compatible_servo_version(self):
284        """Asserts if a compatible servo version is not attached."""
285        servo_version = self.servo.get_servo_version()
286        logging.info('servo version: %s', servo_version)
287
288    def _generate_test_firmware_images(self, gen_script, build_fw_file,
289                                       dut_working_dir):
290        """
291        Copies the fingerprint firmware from the DUT to the server running
292        the tests, which runs a script to generate various test versions of
293        the firmware.
294
295        @return full path to location of test images on DUT
296        """
297        # create subdirectory under existing tmp dir
298        server_tmp_dir = os.path.join(self.tmpdir,
299                                      self._SERVER_GENERATED_FW_DIR_NAME)
300        os.mkdir(server_tmp_dir)
301        logging.info('server_tmp_dir: %s', server_tmp_dir)
302
303        # Copy firmware from device to server
304        self.get_files_from_dut(build_fw_file, server_tmp_dir)
305
306        # Run the test image generation script on server
307        pushd = os.getcwd()
308        os.chdir(server_tmp_dir)
309        cmd = ' '.join([gen_script,
310                        self.get_fp_board(),
311                        os.path.basename(build_fw_file)])
312        self.run_server_cmd(cmd)
313        os.chdir(pushd)
314
315        # Copy resulting files to DUT tmp dir
316        server_generated_images_dir = \
317            os.path.join(server_tmp_dir, self._GENIMAGES_OUTPUT_DIR_NAME)
318        self.copy_files_to_dut(server_generated_images_dir, dut_working_dir)
319
320        return os.path.join(dut_working_dir, self._GENIMAGES_OUTPUT_DIR_NAME)
321
322    def _initialize_test_firmware_image_attrs(self, dut_fw_test_images_dir):
323        """Sets attributes with full path to test images on DUT.
324
325        Example: self.TEST_IMAGE_DEV = /some/path/images/nocturne_fp.dev
326        """
327        for key, val in self._TEST_IMAGE_FORMAT_MAP.iteritems():
328            full_path = os.path.join(dut_fw_test_images_dir,
329                                     val % self.get_fp_board())
330            setattr(self, key, full_path)
331
332    def _initialize_running_fw_version(self, use_dev_signed_fw,
333                                       force_firmware_flashing):
334        """
335        Ensures that the running firmware version matches build version
336        and factory rollback settings; flashes to correct version if either
337        fails to match is requested to force flashing.
338
339        RO firmware: original version released at factory
340        RW firmware: firmware from current build
341        """
342        build_rw_firmware_version = \
343            self.get_build_rw_firmware_version(use_dev_signed_fw)
344        golden_ro_firmware_version = \
345            self.get_golden_ro_firmware_version(use_dev_signed_fw)
346        logging.info('Build RW firmware version: %s', build_rw_firmware_version)
347        logging.info('Golden RO firmware version: %s',
348                     golden_ro_firmware_version)
349
350        running_rw_firmware = self.ensure_running_rw_firmware()
351
352        fw_versions_match = self.running_fw_version_matches_given_version(
353            build_rw_firmware_version, golden_ro_firmware_version)
354
355        if not running_rw_firmware or not fw_versions_match \
356            or not self.is_rollback_set_to_initial_val() \
357            or force_firmware_flashing:
358            fw_file = self._build_fw_file
359            if use_dev_signed_fw:
360                fw_file = self.TEST_IMAGE_DEV
361            self.flash_rw_ro_firmware(fw_file)
362            if not self.running_fw_version_matches_given_version(
363                build_rw_firmware_version, golden_ro_firmware_version):
364                raise error.TestFail(
365                    'Running firmware version does not match expected version')
366
367    def _initialize_fw_entropy(self):
368        """Sets the entropy (key) in FPMCU flash (if not set)."""
369        result = self.run_cmd(self._INIT_ENTROPY_CMD)
370        if result.exit_status != 0:
371            raise error.TestFail('Unable to initialize entropy')
372
373    def _initialize_hw_and_sw_write_protect(self, enable_hardware_write_protect,
374                                            enable_software_write_protect):
375        """Enables/disables hardware/software write protect."""
376        # sw: 0, hw: 0 => initial_hw(0) -> sw(0) -> hw(0)
377        # sw: 0, hw: 1 => initial_hw(0) -> sw(0) -> hw(1)
378        # sw: 1, hw: 0 => initial_hw(1) -> sw(1) -> hw(0)
379        # sw: 1, hw: 1 => initial_hw(1) -> sw(1) -> hw(1)
380        hardware_write_protect_initial_enabled = True
381        if not enable_software_write_protect:
382            hardware_write_protect_initial_enabled = False
383
384        self.set_hardware_write_protect(hardware_write_protect_initial_enabled)
385
386        self.set_software_write_protect(enable_software_write_protect)
387        self.set_hardware_write_protect(enable_hardware_write_protect)
388
389    def get_fp_board(self):
390        """Returns name of fingerprint EC.
391
392        nocturne and nami are special cases and have "_fp" appended. Newer
393        FPMCUs have unique names.
394        See go/cros-fingerprint-firmware-branching-and-signing.
395        """
396
397        # For devices that don't have unibuild support (which is required to
398        # use cros_config).
399        # TODO(https://crrev.com/i/2313151): nami has unibuild support, but
400        # needs its model.yaml updated.
401        # TODO(https://crbug.com/1030862): remove when nocturne has cros_config
402        #  support.
403        board = self.host.get_board().replace(ds_constants.BOARD_PREFIX, '')
404        if board == 'nami' or board == 'nocturne':
405            return board + self._FINGERPRINT_BOARD_NAME_SUFFIX
406
407        # Use cros_config to get fingerprint board.
408        result = self._run_cros_config_cmd('board')
409        if result.exit_status != 0:
410            raise error.TestFail(
411                'Unable to get fingerprint board with cros_config')
412        return result.stdout.rstrip()
413
414    def get_build_fw_file(self):
415        """Returns full path to build FW file on DUT."""
416
417        fp_board = self.get_fp_board()
418        ls_cmd = 'ls ' + self._FINGERPRINT_BUILD_FW_DIR + '/' + fp_board \
419                 + '*.bin'
420        result = self.run_cmd(ls_cmd)
421        if result.exit_status != 0:
422            raise error.TestFail('Unable to find firmware from build on device')
423        ret = result.stdout.rstrip()
424        logging.info('Build firmware file: %s', ret)
425        return ret
426
427    def check_equal(self, a, b):
428        """Raises exception if "a" does not equal "b"."""
429        if a != b:
430            raise error.TestFail('"%s" does not match expected "%s" for board '
431                                 '%s' % (a, b, self.get_fp_board()))
432
433    def _validate_build_fw_file(self, build_fw_file):
434        """
435        Checks that all attributes in the given firmware file match their
436        expected values.
437        """
438        # check hash
439        actual_hash = self._calculate_sha256sum(build_fw_file)
440        expected_hash = self._get_expected_firmware_hash(build_fw_file)
441        self.check_equal(actual_hash, expected_hash)
442
443        # check signing key_id
444        actual_key_id = self._read_firmware_key_id(build_fw_file)
445        expected_key_id = self._get_expected_firmware_key_id(build_fw_file)
446        self.check_equal(actual_key_id, expected_key_id)
447
448        # check that signing key is "pre mass production" (pre-mp) or
449        # "mass production" (MP) for firmware in the build
450        key_type = self._get_key_type(actual_key_id)
451        if not (key_type == self._KEY_TYPE_MP or
452                key_type == self._KEY_TYPE_PRE_MP):
453            raise error.TestFail('Firmware key type must be MP or PRE-MP '
454                                 'for board: %s' % self.get_fp_board())
455
456        # check ro_version
457        actual_ro_version = self._read_firmware_ro_version(build_fw_file)
458        expected_ro_version =\
459            self._get_expected_firmware_ro_version(build_fw_file)
460        self.check_equal(actual_ro_version, expected_ro_version)
461
462        # check rw_version
463        actual_rw_version = self._read_firmware_rw_version(build_fw_file)
464        expected_rw_version =\
465            self._get_expected_firmware_rw_version(build_fw_file)
466        self.check_equal(actual_rw_version, expected_rw_version)
467
468        logging.info("Validated build firmware metadata.")
469
470    def _get_key_type(self, key_id):
471        """Returns the key "type" for a given "key id"."""
472        key_type = self._KEY_ID_MAP_.get(key_id)
473        if key_type is None:
474            raise error.TestFail('Unable to get key type for key id: %s'
475                                 % key_id)
476        return key_type
477
478    def _get_expected_firmware_info(self, build_fw_file, info_type):
479        """
480        Returns expected firmware info for a given firmware file name.
481        """
482        build_fw_file_name = os.path.basename(build_fw_file)
483
484        board = self.get_fp_board()
485        board_expected_fw_info = self._FIRMWARE_VERSION_MAP.get(board)
486        if board_expected_fw_info is None:
487            raise error.TestFail('Unable to get firmware info for board: %s'
488                                 % board)
489
490        expected_fw_info = board_expected_fw_info.get(build_fw_file_name)
491        if expected_fw_info is None:
492            raise error.TestFail('Unable to get firmware info for file: %s'
493                                 % build_fw_file_name)
494
495        ret = expected_fw_info.get(info_type)
496        if ret is None:
497            raise error.TestFail('Unable to get firmware info type: %s'
498                                 % info_type)
499
500        return ret
501
502    def _get_expected_firmware_hash(self, build_fw_file):
503        """Returns expected hash of firmware file."""
504        return self._get_expected_firmware_info(
505            build_fw_file, self._FIRMWARE_VERSION_SHA256SUM)
506
507    def _get_expected_firmware_key_id(self, build_fw_file):
508        """Returns expected "key id" for firmware file."""
509        return self._get_expected_firmware_info(
510            build_fw_file, self._FIRMWARE_VERSION_KEY_ID)
511
512    def _get_expected_firmware_ro_version(self, build_fw_file):
513        """Returns expected RO version for firmware file."""
514        return self._get_expected_firmware_info(
515            build_fw_file, self._FIRMWARE_VERSION_RO_VERSION)
516
517    def _get_expected_firmware_rw_version(self, build_fw_file):
518        """Returns expected RW version for firmware file."""
519        return self._get_expected_firmware_info(
520            build_fw_file, self._FIRMWARE_VERSION_RW_VERSION)
521
522    def _read_firmware_key_id(self, file_name):
523        """Returns "key id" as read from the given file."""
524        result = self._run_futility_show_cmd(file_name)
525        parsed = self._parse_colon_delimited_output(result)
526        key_id = parsed.get(self._FUTILITY_KEY_ID_KEY_NAME)
527        if key_id is None:
528            raise error.TestFail('Failed to get key ID for file: %s'
529                                 % file_name)
530        return key_id
531
532    def _read_firmware_ro_version(self, file_name):
533        """Returns RO firmware version as read from the given file."""
534        return self._run_dump_fmap_cmd(file_name, 'RO_FRID')
535
536    def _read_firmware_rw_version(self, file_name):
537        """Returns RW firmware version as read from the given file."""
538        return self._run_dump_fmap_cmd(file_name, 'RW_FWID')
539
540    def _calculate_sha256sum(self, file_name):
541        """Returns SHA256 hash of the given file contents."""
542        result = self._run_sha256sum_cmd(file_name)
543        return result.stdout.split()[0]
544
545    def _get_running_firmware_info(self, key):
546        """
547        Returns requested firmware info (RW version, RO version, or firmware
548        type).
549        """
550        result = self._run_ectool_cmd('version')
551        parsed = self._parse_colon_delimited_output(result.stdout)
552        if result.exit_status != 0:
553            raise error.TestFail('Failed to get running firmware info')
554        info = parsed.get(key)
555        if info is None:
556            raise error.TestFail(
557                'Failed to get running firmware info: %s' % key)
558        return info
559
560    def get_running_rw_firmware_version(self):
561        """Returns running RW firmware version."""
562        return self._get_running_firmware_info(self._ECTOOL_RW_VERSION)
563
564    def get_running_ro_firmware_version(self):
565        """Returns running RO firmware version."""
566        return self._get_running_firmware_info(self._ECTOOL_RO_VERSION)
567
568    def get_running_firmware_type(self):
569        """Returns type of firmware we are running (RW or RO)."""
570        return self._get_running_firmware_info(self._ECTOOL_FIRMWARE_COPY)
571
572    def _get_rollback_info(self, info_type):
573        """Returns requested type of rollback info."""
574        result = self._run_ectool_cmd('rollbackinfo')
575        parsed = self._parse_colon_delimited_output(result.stdout)
576        if result.exit_status != 0:
577            raise error.TestFail('Failed to get rollback info')
578        info = parsed.get(info_type)
579        if info is None:
580            raise error.TestFail('Failed to get rollback info: %s' % info_type)
581        return info
582
583    def get_rollback_id(self):
584        """Returns rollback ID."""
585        return self._get_rollback_info(self._ECTOOL_ROLLBACK_BLOCK_ID)
586
587    def get_rollback_min_version(self):
588        """Returns rollback min version."""
589        return self._get_rollback_info(self._ECTOOL_ROLLBACK_MIN_VERSION)
590
591    def get_rollback_rw_version(self):
592        """Returns RW rollback version."""
593        return self._get_rollback_info(self._ECTOOL_ROLLBACK_RW_VERSION)
594
595    def _construct_dev_version(self, orig_version):
596        """
597        Given a "regular" version string from a signed build, returns the
598        special "dev" version that we use when creating the test images.
599        """
600        fw_version = orig_version
601        if len(fw_version) + len('.dev') > 31:
602            fw_version = fw_version[:27]
603        fw_version = fw_version + '.dev'
604        return fw_version
605
606    def get_golden_ro_firmware_version(self, use_dev_signed_fw):
607        """Returns RO firmware version used in factory."""
608        board = self.get_fp_board()
609        golden_version = self._GOLDEN_RO_FIRMWARE_VERSION_MAP.get(board)
610        if golden_version is None:
611            raise error.TestFail('Unable to get golden RO version for board: %s'
612                                 % board)
613        if use_dev_signed_fw:
614            golden_version = self._construct_dev_version(golden_version)
615        return golden_version
616
617    def get_build_rw_firmware_version(self, use_dev_signed_fw):
618        """Returns RW firmware version from build (based on filename)."""
619        fw_file = os.path.basename(self._build_fw_file)
620        if not fw_file.endswith('.bin'):
621            raise error.TestFail('Unexpected filename for RW firmware: %s'
622                                 % fw_file)
623        fw_version = fw_file[:-4]
624        if use_dev_signed_fw:
625            fw_version = self._construct_dev_version(fw_version)
626        return fw_version
627
628    def ensure_running_rw_firmware(self):
629        """
630        Check whether the device is running RW firmware. If not, try rebooting
631        to RW.
632
633        @return true if successfully verified running RW firmware, false
634        otherwise.
635        """
636        try:
637            if self.get_running_firmware_type() != self._FIRMWARE_TYPE_RW:
638                self._reboot_ec()
639                if self.get_running_firmware_type() != self._FIRMWARE_TYPE_RW:
640                    # RW may be corrupted.
641                    return False
642        except:
643            # We may not always be able to read the firmware version.
644            # For example, if the firmware is erased due to RDP1, running any
645            # commands (such as getting the version) won't work.
646            return False
647        return True
648
649    def running_fw_version_matches_given_version(self, rw_version, ro_version):
650        """
651        Returns True if the running RO and RW firmware versions match the
652        provided versions.
653        """
654        try:
655            running_rw_firmware_version = self.get_running_rw_firmware_version()
656            running_ro_firmware_version = self.get_running_ro_firmware_version()
657
658            logging.info('RW firmware, running: %s, expected: %s',
659                         running_rw_firmware_version, rw_version)
660            logging.info('RO firmware, running: %s, expected: %s',
661                         running_ro_firmware_version, ro_version)
662
663            return (running_rw_firmware_version == rw_version and
664                    running_ro_firmware_version == ro_version)
665        except:
666            # We may not always be able to read the firmware version.
667            # For example, if the firmware is erased due to RDP1, running any
668            # commands (such as getting the version) won't work.
669            return False
670
671    def is_rollback_set_to_initial_val(self):
672        """
673        Returns True if rollbackinfo matches the initial value that it
674        should have coming from the factory.
675        """
676        return (self.get_rollback_id() ==
677                self._ROLLBACK_INITIAL_BLOCK_ID
678                and
679                self.get_rollback_min_version() ==
680                self._ROLLBACK_INITIAL_MIN_VERSION
681                and
682                self.get_rollback_rw_version() ==
683                self._ROLLBACK_INITIAL_RW_VERSION)
684
685    def _download_firmware(self, gs_path, dut_file_path):
686        """Downloads firmware from Google Storage bucket."""
687        bucket = os.path.dirname(gs_path)
688        filename = os.path.basename(gs_path)
689        logging.info('Downloading firmware, '
690                     'bucket: %s, filename: %s, dest: %s',
691                     bucket, filename, dut_file_path)
692        gsutil_wrapper.copy_private_bucket(host=self.host,
693                                           bucket=bucket,
694                                           filename=filename,
695                                           destination=dut_file_path)
696        return os.path.join(dut_file_path, filename)
697
698    def flash_rw_firmware(self, fw_path):
699        """Flashes the RW (read-write) firmware."""
700        flash_cmd = os.path.join(self._dut_working_dir,
701                                 'flash_fp_rw.sh' + ' ' + fw_path)
702        result = self.run_cmd(flash_cmd)
703        if result.exit_status != 0:
704            raise error.TestFail('Flashing RW firmware failed')
705
706    def flash_rw_ro_firmware(self, fw_path):
707        """Flashes *all* firmware (both RO and RW)."""
708        self.set_hardware_write_protect(False)
709        flash_cmd = 'flash_fp_mcu' + ' ' + fw_path
710        logging.info('Running flash cmd: %s', flash_cmd)
711        result = self.run_cmd(flash_cmd)
712        self.set_hardware_write_protect(True)
713        if result.exit_status != 0:
714            raise error.TestFail('Flashing RW/RO firmware failed')
715
716    def is_hardware_write_protect_enabled(self):
717        """Returns state of hardware write protect."""
718        fw_wp_state = self.servo.get('fw_wp_state')
719        return fw_wp_state == 'on' or fw_wp_state == 'force_on'
720
721    def set_hardware_write_protect(self, enable):
722        """Enables or disables hardware write protect."""
723        self.servo.set('fw_wp_state', 'force_on' if enable else 'force_off')
724
725    def set_software_write_protect(self, enable):
726        """Enables or disables software write protect."""
727        arg  = 'enable' if enable else 'disable'
728        self._run_ectool_cmd('flashprotect ' + arg)
729        # TODO(b/116396469): The flashprotect command returns an error even on
730        # success.
731        # if result.exit_status != 0:
732        #    raise error.TestFail('Failed to modify software write protect')
733
734        # TODO(b/116396469): "flashprotect enable" command is slow, so wait for
735        # it to complete before attempting to reboot.
736        time.sleep(2)
737        self._reboot_ec()
738
739    def _reboot_ec(self):
740        """Reboots the fingerprint MCU (FPMCU)."""
741        self._run_ectool_cmd('reboot_ec')
742        # TODO(b/116396469): The reboot_ec command returns an error even on
743        # success.
744        # if result.exit_status != 0:
745        #    raise error.TestFail('Failed to reboot ec')
746        time.sleep(2)
747
748    def get_files_from_dut(self, src, dst):
749        """Copes files from DUT to server."""
750        logging.info('Copying files from (%s) to (%s).', src, dst)
751        self.host.get_file(src, dst, delete_dest=True)
752
753    def copy_files_to_dut(self, src_dir, dst_dir):
754        """Copies files from server to DUT."""
755        logging.info('Copying files from (%s) to (%s).', src_dir, dst_dir)
756        self.host.send_file(src_dir, dst_dir, delete_dest=True)
757
758    def run_server_cmd(self, command, timeout=60):
759        """Runs command on server; return result with output and exit code."""
760        logging.info('Server execute: %s', command)
761        result = utils.run(command, timeout=timeout, ignore_status=True)
762        logging.info('exit_code: %d', result.exit_status)
763        logging.info('stdout:\n%s', result.stdout)
764        logging.info('stderr:\n%s', result.stderr)
765        return result
766
767    def run_cmd(self, command, timeout=300):
768        """Runs command on the DUT; return result with output and exit code."""
769        logging.debug('DUT Execute: %s', command)
770        result = self.host.run(command, timeout=timeout, ignore_status=True)
771        logging.info('exit_code: %d', result.exit_status)
772        logging.info('stdout:\n%s', result.stdout)
773        logging.info('stderr:\n%s', result.stderr)
774        return result
775
776    def _run_ectool_cmd(self, command):
777        """Runs ectool on DUT; return result with output and exit code."""
778        cmd = 'ectool ' + self._CROS_FP_ARG + ' ' + command
779        result = self.run_cmd(cmd)
780        return result
781
782    def _run_cros_config_cmd(self, command):
783        """Runs cros_config on DUT; return result with output and exit code."""
784        cmd = 'cros_config ' + self._CROS_CONFIG_FINGERPRINT_PATH + ' ' \
785              + command
786        result = self.run_cmd(cmd)
787        return result
788
789    def _run_dump_fmap_cmd(self, fw_file, section):
790        """
791        Runs "dump_fmap" on DUT for given file.
792        Returns value of given section.
793        """
794        # Write result to stderr while redirecting stderr to stdout
795        # and dropping stdout. This is done because dump_map only writes the
796        # value read from a section to a file (will not just print it to
797        # stdout).
798        cmd = 'dump_fmap -x ' + fw_file + ' ' + section +\
799              ':/dev/stderr /dev/stderr >& /dev/stdout > /dev/null'
800        result = self.run_cmd(cmd)
801        if result.exit_status != 0:
802            raise error.TestFail('Failed to read section: %s' % section)
803        return result.stdout.rstrip('\0')
804
805    def _run_futility_show_cmd(self, fw_file):
806        """
807        Runs "futility show" on DUT for given file.
808        Returns stdout on success.
809        """
810        futility_cmd = 'futility show ' + fw_file
811        result = self.run_cmd(futility_cmd)
812        if result.exit_status != 0:
813            raise error.TestFail('Unable to run futility on device')
814        return result.stdout
815
816    def _run_sha256sum_cmd(self, file_name):
817        """
818        Runs "sha256sum" on DUT for given file.
819        Returns stdout on success.
820        """
821        sha_cmd = 'sha256sum ' + file_name
822        result = self.run_cmd(sha_cmd)
823        if result.exit_status != 0:
824            raise error.TestFail('Unable to calculate sha256sum on device')
825        return result
826
827    def run_test(self, test_name, *args):
828        """Runs test on DUT."""
829        logging.info('Running %s', test_name)
830        # Redirecting stderr to stdout since some commands intentionally fail
831        # and it's easier to read when everything ordered in the same output
832        test_cmd = ' '.join([os.path.join(self._dut_working_dir, test_name)] +
833                            list(args) + ['2>&1'])
834        # Change the working dir so we can write files from within the test
835        # (otherwise defaults to $HOME (/root), which is not usually writable)
836        # Note that dut_working_dir is automatically cleaned up so tests don't
837        # need to worry about files from previous invocations or other tests.
838        test_cmd = '(cd ' + self._dut_working_dir + ' && ' + test_cmd + ')'
839        logging.info('Test command: %s', test_cmd)
840        result = self.run_cmd(test_cmd)
841        if result.exit_status != 0:
842            raise error.TestFail(test_name + ' failed')
843