#!/bin/bash

#
# Copyright 2017 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.
#
# This script is a utility which allows to sign prod CR50 images for release
# and place them in a tarball suitable for uploading to the BCS.
#
# The util/signer/ec_RW-manifest-prod.json manifest present in the EC
# directory is used for signing.
#

set -u

# A very crude RO verification function. The key signature found at a fixed
# offset into the RO blob must match the RO type. Prod keys have bit D2 set to
# one, dev keys have this bit set to zero.
verify_ro() {
  local ro_bin="${1}"
  local type_expected="${2}"
  local key_byte

  if [ ! -f "${ro_bin}" ]; then
    echo "${ro_bin} not a file!" >&2
    exit 1
  fi

  # Key signature's lowest byte is byte #5 in the line at offset 0001a0.
  key_byte="$(od -Ax -t x1 -v "${ro_bin}" | awk '/0001a0/ {print $6};')"
  case "${key_byte}" in
    (?[4567cdef])
      if [ "${type_expected}" == "prod" ]; then
        return 0
      fi
      ;;
    (?[012389ab])
      if [ "${type_expected}" == "dev" ]; then
        return 0
      fi
      ;;
  esac

  echo "RO key in ${ro_bin} does not match type ${type_expected}" >&2
  exit 1
}

# This function prepares a full CR50 image, consisting of two ROs and two RWs
# placed at their respective offsets into the resulting blob. It invokes the
# bs (binary signer) script to actually convert elf versions of RWs into
# binaries and sign them.
#
# The signed image is placed in the directory named as concatenation of RO and
# RW version numbers and board ID fields, if set to non-default. The ebuild
# downloading the tarball from the BCS expects the image to be in that
# directory.
prepare_image() {
  local awk_prog
  local count=0
  local extra_param=
  local image_type="${1}"
  local raw_version
  local ro_a_hex="$(readlink -f "${2}")"
  local ro_b_hex="$(readlink -f "${3}")"
  local rw_a="$(readlink -f "${4}")"
  local rw_b="$(readlink -f "${5}")"
  local version

  for f in "${ro_a_hex}" "${ro_b_hex}"; do
    if ! objcopy -I ihex "${f}" -O binary "${TMPD}/${count}.bin"; then
      echo "failed to convert ${f} from hex to bin" >&2
      exit 1
    fi
    verify_ro "${TMPD}/${count}.bin" "${image_type}"
    : $(( count += 1 ))
  done

  if [ "${image_type}" == "prod" ]; then
    extra_param+=' prod'
  fi

  if ! "${EC_ROOT}/util/signer/bs" ${extra_param} elves \
    "${rw_a}" "${rw_b}" > /dev/null;
  then
    echo "Failed invoking ${EC_ROOT}/util/signer/bs ${extra_param} " \
      "elves ${rw_a} ${rw_b}" >&2
    exit 1
  fi

  dd if="${TMPD}/0.bin" of="${RESULT_FILE}" conv=notrunc
  dd if="${TMPD}/1.bin" of="${RESULT_FILE}" seek=262144 bs=1 conv=notrunc

  # A typical Cr50 version reported by gsctool looks as follows:
  # RO_A:0.0.10 RW_A:0.0.22[ABCD:00000013:00000012] ...(the same for R[OW]_B).
  #
  # In case Board ID field is not set in the image, it is reported as
  # [00000000:00000000:00000000]
  #
  # We want the generated tarball file name to include all relevant version
  # fields. Let's retrieve the version string and process it using awk to
  # generate the proper file name. Only the RO_A and RW_A version numbers are
  # used, this script trusts the user to submit for processing a proper image
  # where both ROs and both RWs are of the same version respectively.
  #
  # As a result, blob versions are converted as follows:
  #     RO_A:0.0.10 RW_A:0.0.22[ABCD:00000013:00000012] into
  #         r0.0.10.w0.0.22_ABCD_00000013_00000012
  #
  #     RO_A:0.0.10 RW_A:0.0.22[00000000:00000000:00000000] into
  #         r0.0.10.w0.0.22
  #
  # The below awk program accomplishes this preprocessing.
  awk_prog='/^RO_A:/ {
    # drop the RO_A/RW_A strings
    gsub(/R[OW]_A:/, "")
    # Drop default mask value completely.
    gsub(/\[00000000:00000000:00000000\]/, "")
    # If there is a non-default mask:
    # - replace opening brackets and colons with underscores.
    gsub(/[\[\:]/, "_")
    #  - drop the trailing bracket.
    gsub(/\]/, "")
    # Print filtered out RO_A and RW_A values
    print "r" $1 ".w" $2
}'

  raw_version="$("${GSCTOOL}" -b "${RESULT_FILE}")" ||
       ( echo "${ME}: Failed to retrieve blob version" >&2 && exit 1 )

  version="$(awk "${awk_prog}" <<< "${raw_version}" )"
  if [ -z "${dest_dir}" ]; then
    # Note that this is a global variable
    dest_dir="cr50.${version}"
    if [ ! -d "${dest_dir}" ]; then
      mkdir "${dest_dir}"
    else
      echo "${dest_dir} already exists, will overwrite" >&2
    fi
  elif [ "${dest_dir}" != "cr50.${version}" ]; then
    echo "dev and prod versions mismatch!" >&2
    exit 1
  fi

  cp "${RESULT_FILE}" "${dest_dir}/cr50.bin.${image_type}"
  echo "saved ${image_type} binary in ${dest_dir}/cr50.bin.${image_type}"
}

# Execution starts here ===========================
ME="$(basename $0)"

if [ -z "${CROS_WORKON_SRCROOT}" ]; then
 echo "${ME}: This script must run inside Chrome OS chroot" >&2
  exit 1
fi

SCRIPT_ROOT="${CROS_WORKON_SRCROOT}/src/scripts"
. "${SCRIPT_ROOT}/build_library/build_common.sh" || exit 1

TMPD="$(mktemp -d /tmp/${ME}.XXXXX)"
trap "/bin/rm -rf ${TMPD}" SIGINT SIGTERM EXIT

EC_ROOT="${CROS_WORKON_SRCROOT}/src/platform/ec"
RESULT_FILE="${TMPD}/release.bin"
dest_dir=
IMAGE_SIZE='524288'
export RESULT_FILE

GSCTOOL="/usr/sbin/gsctool"
if [[ ! -x "${GSCTOOL}" ]]; then
  emerge_command="USE=cr50_onboard sudo -E emerge ec-utils"
  echo "${ME}: gsctool not found, run \"${emerge_command}\"" >&2
  exit 1
fi

DEFINE_string cr50_board_id "" \
  "Optional string representing Board ID field of the Cr50 RW header.
Consists of three fields separated by colon: <RLZ>:<hex mask>:<hex flags>"

# Do not put this before the DEFINE_ invocations - they routinely experience
# error return values.
set -e

FLAGS_HELP="usage: ${ME} [flags] <blobs>

blobs are:
  <prod RO A>.hex <prod RO B>.hex  <RW.elf> <RW_B.elf>
 or
  <prod RO A>.hex <prod RO B>.hex  <dir>
    where <dir> contains files named ec.RW.elf and ec.RW_B.elf
"

# Parse command line.
FLAGS "$@" || exit 1

eval set -- "${FLAGS_ARGV}"

if [[ $# == 3  &&  -d "${3}" ]]; then
  rw_a="${3}/ec.RW.elf"
  rw_b="${3}/ec.RW_B.elf"
elif [[ $# == 4 ]]; then
  rw_a="${3}"
  rw_b="${4}"
else
  flags_help
  exit 1
fi

prod_ro_a="${1}"
prod_ro_b="${2}"

dd if=/dev/zero bs="${IMAGE_SIZE}" count=1  2>/dev/null |
  tr \\000 \\377 > "${RESULT_FILE}"
if [ "$(stat -c '%s' "${RESULT_FILE}")" != "${IMAGE_SIZE}" ]; then
  echo "Failed creating ${RESULT_FILE}" >&2
  exit 1
fi

# Used by the bs script.
export CR50_BOARD_ID="${FLAGS_cr50_board_id}"

prepare_image 'prod' "${prod_ro_a}" "${prod_ro_b}" "${rw_a}" "${rw_b}"
tarball="${dest_dir}.tbz2"
tar jcf  "${tarball}" "${dest_dir}"
rm -rf "${dest_dir}"

bcs_path="gs://chromeos-localmirror-private/distfiles/cr50"
echo "SUCCESS!!!!!!"
echo "use the below commands to copy the new image to the GFS"
echo "gsutil cp ${tarball} ${bcs_path}"
echo "gsutil acl ch -g cr50-firmware@google.com:R ${bcs_path}/${tarball}"

