# Copyright 2018 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 logging
import os
import tempfile

from autotest_lib.client.common_lib import error
from autotest_lib.server.cros.faft.firmware_test import FirmwareTest


class platform_FlashErasers(FirmwareTest):
    """
    Test that various erase functions work correctly by calling flashrom to
    erase/write blocks of different sizes.
    """
    version = 1

    def run_cmd(self, command, checkfor=''):
        """
        Log and execute command and return the output.

        @param command: Command to execute on device.
        @param checkfor: If not emmpty, fail test if checkfor not in output.
        @returns the output of command as a list of strings.
        """
        command = command + ' 2>&1'
        logging.info('Execute %s', command)
        output = self.faft_client.system.run_shell_command_get_output(command)
        logging.info('Output >>> %s <<<', output)
        if checkfor and checkfor not in '\n'.join(output):
            raise error.TestFail('Expect %s in output of %s' %
                                 (checkfor, '\n'.join(output)))
        return output

    def _get_section(self, bios, section):
        """Return start address and size of an fmap section.

        @param bios: string, bios file name to retrieve fmap from
        @param section: string, section name to look for

        @return tuple of ints for start and size of the section.
        """
        # Store temp results in the attributes dictionary; when the expected
        # section name is found in the 'area_name:' field, the offset and size
        # of the section would be stored in this dictionary.
        attributes = {}
        for line in [x.strip() for x in self.run_cmd('dump_fmap %s' % bios)]:
            if not line:
                continue
            tokens = line.split()
            if tokens[0] == 'area_name:' and tokens[1] == section:
                return (int(attributes['area_offset:'], 16),
                        int(attributes['area_size:'], 16))

            if len(tokens) > 1:
                attributes[tokens[0]] = tokens[1]

        raise error.TestFail('Could not find section %s in the fmap' % section)

    def _create_test_blob(self, blob_name, blob_size):
        """On the DUT create a blob containing 0xff bytes of the requested size.

        @param blob_name: string, name of the DUT file to save the blob in
        @param blob_size: integer, size of the blob to prepare
        """
        test_blob_data = ('%c' % 0xff) * blob_size

        # Save it into a local file.
        handle, local_file = tempfile.mkstemp()

        try:
            os.write(handle, test_blob_data)
            os.close(handle)

            # Copy the local file to the DUT.
            self._client.send_file(local_file, blob_name)

        finally:
            # Delete local file.
            os.remove(local_file)

    def run_once(self, dev_mode=True):
        """Main method implementing test logic."""

        # Find out which AP firmware section is active to determine which AP
        # firmware section could be overwritten.
        active_fw = self.run_cmd('crossystem mainfw_act')[0]
        if active_fw == 'A':
            section = 'RW_SECTION_B'
        elif active_fw == 'B':
            section = 'RW_SECTION_A'
        else:
            raise error.TestFail('Unexpected active fw %s' % active_fw)

        dut_work_path = self.faft_client.updater.get_work_path()
        # Sizes to try to erase.
        test_sizes = (4096, 4096 * 2, 4096 * 4, 4096 * 8, 4096 * 16)

        # Image read from the AP firmware flash chip.
        bios_image = os.path.join(dut_work_path, 'bios_image')
        self.run_cmd('flashrom -r %s' % bios_image)

        # A blob of all ones to paste into the image.
        test_blob = os.path.join(dut_work_path, 'test_blob')
        self._create_test_blob(test_blob, max(test_sizes))

        # The file to store the 'corrupted' image with all ones pasted.
        junk_image = os.path.join(dut_work_path, 'junk_image')

        # Find in fmap the AP firmware section which can be overwritten.
        start, size = self._get_section(bios_image, section)
        logging.info('Active firmware %s, alternative at %#x:%#x', active_fw,
                     start, start + size -1)

        # Command to paste the all ones blob into the corrupted image.
        dd_template = 'dd if="%s" of="%s" bs=1 conv=notrunc seek="%d"' % (
            test_blob, junk_image, start)

        for test_size in test_sizes:

            logging.info('Verifying section of size %d', test_size)

            self.run_cmd('cp %s %s' % (bios_image, junk_image))

            # Set section in the 'junk' image to 'all erased'
            dd_cmd = dd_template + ' count="%d"' % test_size
            self.run_cmd(dd_cmd)

            # Now program the corrupted image, this would involve erasing the
            # section of test_size bytes.
            self.run_cmd('flashrom -w %s --flash-contents %s --noverify-all' %
                         (junk_image, bios_image))

            # Now restore the image.
            self.run_cmd('flashrom -w %s --flash-contents %s' %
                         (bios_image, junk_image))
