// Copyright (C) 2021 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package filesystem

import (
	"fmt"
	"strconv"
	"strings"

	"github.com/google/blueprint"
	"github.com/google/blueprint/proptools"

	"android/soong/android"
)

type bootimg struct {
	android.ModuleBase

	properties       BootimgProperties
	commonProperties CommonBootimgProperties

	output     android.Path
	installDir android.InstallPath

	bootImageType bootImageType
}

type BootimgProperties struct {
	// Set the name of the output. Defaults to <module_name>.img.
	Stem *string

	// Path to the linux kernel prebuilt file
	Kernel_prebuilt proptools.Configurable[string] `android:"replace_instead_of_append,arch_variant,path"`

	// Filesystem module that is used as ramdisk
	Ramdisk_module *string

	// Filesystem module that is used as --vendor_ramdisk_fragment for mkbootimg
	// Only supported for vendor_boot partition type.
	Ramdisk_fragment_modules []string

	// Path to the device tree blob (DTB) prebuilt file to add to this boot image
	Dtb_prebuilt *string `android:"arch_variant,path"`

	// Optional kernel commandline arguments
	Cmdline []string `android:"arch_variant"`

	// File that contains bootconfig parameters. This can be set only when `vendor_boot` is true
	// and `header_version` is greater than or equal to 4.
	Bootconfig *string `android:"arch_variant,path"`
}

// properties common between bootimg and prebuilt_bootimg module types.
type CommonBootimgProperties struct {
	// Header version number. Must be set to one of the version numbers that are currently
	// supported. Refer to
	// https://source.android.com/devices/bootloader/boot-image-header
	Header_version *string

	// Determines the specific type of boot image this module is building. Can be boot,
	// vendor_boot, vendor_kernel_boot or init_boot. Defaults to boot.
	// Refer to https://source.android.com/devices/bootloader/partitions/vendor-boot-partitions
	// for vendor_boot.
	// Refer to https://source.android.com/docs/core/architecture/partitions/generic-boot for
	// init_boot.
	Boot_image_type *string

	// The size of the partition on the device. It will be a build error if this built partition
	// image exceeds this size.
	Partition_size *int64

	// When set to true, sign the image with avbtool. Default is false.
	Use_avb *bool

	// This can either be "default", or "make_legacy". "make_legacy" will sign the boot image
	// like how build/make/core/Makefile does, to get bit-for-bit backwards compatibility. But
	// we may want to reconsider if it's necessary to have two modes in the future. The default
	// is "default"
	Avb_mode *string

	// Name of the partition stored in vbmeta desc. Defaults to the name of this module.
	Partition_name *string

	// Path to the private key that avbtool will use to sign this filesystem image.
	// TODO(jiyong): allow apex_key to be specified here
	Avb_private_key *string `android:"path_device_first"`

	// Hash and signing algorithm for avbtool. Default is SHA256_RSA4096.
	Avb_algorithm *string

	// The index used to prevent rollback of the image on device.
	Avb_rollback_index *int64

	// Rollback index location of this image. Must be 0, 1, 2, etc.
	Avb_rollback_index_location *int64

	// The security patch passed to as the com.android.build.<type>.security_patch avb property.
	// Replacement for the make variables BOOT_SECURITY_PATCH / INIT_BOOT_SECURITY_PATCH.
	Security_patch *string
}

type bootImageType int

const (
	unsupported bootImageType = iota
	boot
	vendorBoot
	initBoot
	vendorKernelBoot
)

func toBootImageType(ctx android.ModuleContext, bootImageType string) bootImageType {
	switch bootImageType {
	case "boot":
		return boot
	case "vendor_boot":
		return vendorBoot
	case "init_boot":
		return initBoot
	case "vendor_kernel_boot":
		return vendorKernelBoot
	default:
		ctx.ModuleErrorf("Unknown boot_image_type %s. Must be one of \"boot\", \"vendor_boot\", \"vendor_kernel_boot\", or \"init_boot\"", bootImageType)
	}
	return unsupported
}

func (b bootImageType) String() string {
	switch b {
	case boot:
		return "boot"
	case vendorBoot:
		return "vendor_boot"
	case initBoot:
		return "init_boot"
	case vendorKernelBoot:
		return "vendor_kernel_boot"
	default:
		panic("unknown boot image type")
	}
}

func (b bootImageType) isBoot() bool {
	return b == boot
}

func (b bootImageType) isVendorBoot() bool {
	return b == vendorBoot
}

func (b bootImageType) isVendorKernelBoot() bool {
	return b == vendorKernelBoot
}

func (b bootImageType) isInitBoot() bool {
	return b == initBoot
}

// bootimg is the image for the boot partition. It consists of header, kernel, ramdisk, and dtb.
func BootimgFactory() android.Module {
	module := &bootimg{}
	module.AddProperties(&module.properties, &module.commonProperties)
	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibFirst)
	return module
}

type bootimgDep struct {
	blueprint.BaseDependencyTag
	kind string
}

var bootimgRamdiskDep = bootimgDep{kind: "ramdisk"}
var bootimgRamdiskFragmentDep = bootimgDep{kind: "ramdisk_fragment"}

func (b *bootimg) DepsMutator(ctx android.BottomUpMutatorContext) {
	ramdisk := proptools.String(b.properties.Ramdisk_module)
	if ramdisk != "" {
		ctx.AddDependency(ctx.Module(), bootimgRamdiskDep, ramdisk)
	}
	for _, fragment := range b.properties.Ramdisk_fragment_modules {
		ctx.AddDependency(ctx.Module(), bootimgRamdiskFragmentDep, fragment)
	}
}

func (b *bootimg) installFileName() string {
	return proptools.StringDefault(b.properties.Stem, b.BaseModuleName()+".img")
}

func (b *bootimg) partitionName() string {
	return proptools.StringDefault(b.commonProperties.Partition_name, b.BaseModuleName())
}

func (b *bootimg) GenerateAndroidBuildActions(ctx android.ModuleContext) {
	b.bootImageType = toBootImageType(ctx, proptools.StringDefault(b.commonProperties.Boot_image_type, "boot"))
	if b.bootImageType == unsupported {
		return
	}
	if !b.bootImageType.isVendorBoot() && len(b.properties.Ramdisk_fragment_modules) > 0 {
		ctx.PropertyErrorf("ramdisk_fragment_modules", "Ramdisk_fragment_modules is only supported for vendor_boot partition type.")
	}

	kernelProp := b.properties.Kernel_prebuilt.GetOrDefault(ctx, "")
	if b.bootImageType.isVendorBoot() && kernelProp != "" {
		ctx.PropertyErrorf("kernel_prebuilt", "vendor_boot partition can't have kernel")
		return
	}
	if b.bootImageType.isBoot() && kernelProp == "" {
		ctx.PropertyErrorf("kernel_prebuilt", "boot partition must have kernel")
		return
	}

	kernelPath := b.getKernelPath(ctx)
	unsignedOutput := b.buildBootImage(ctx, kernelPath)

	output := unsignedOutput
	if proptools.Bool(b.commonProperties.Use_avb) {
		// This bootimg module supports 2 modes of avb signing. It is not clear to this author
		// why there are differences, but one of them is to match the behavior of make-built boot
		// images.
		switch proptools.StringDefault(b.commonProperties.Avb_mode, "default") {
		case "default":
			output = b.signImage(ctx, unsignedOutput)
		case "make_legacy":
			output = b.addAvbFooter(ctx, unsignedOutput, kernelPath)
		default:
			ctx.PropertyErrorf("avb_mode", `Unknown value for avb_mode, expected "default" or "make_legacy", got: %q`, *b.commonProperties.Avb_mode)
		}
	}

	b.installDir = android.PathForModuleInstall(ctx, "etc")
	ctx.InstallFile(b.installDir, b.installFileName(), output)

	ctx.SetOutputFiles([]android.Path{output}, "")
	b.output = output

	// Set the Filesystem info of the ramdisk dependency.
	// `android_device` will use this info to package `target_files.zip`
	// TODO: Move this under BootimgInfo, as is, it's easy to confuse the bootImg module for
	// the underlying ramdisk module.
	if ramdisk := proptools.String(b.properties.Ramdisk_module); ramdisk != "" {
		ramdiskModule := ctx.GetDirectDepProxyWithTag(ramdisk, bootimgRamdiskDep)
		fsInfo, _ := android.OtherModuleProvider(ctx, ramdiskModule, FilesystemProvider)
		android.SetProvider(ctx, FilesystemProvider, fsInfo)
	} else {
		setCommonFilesystemInfo(ctx, b)
	}

	// Set BootimgInfo for building target_files.zip
	dtbPath := b.getDtbPath(ctx)
	android.SetProvider(ctx, BootimgInfoProvider, BootimgInfo{
		Type:                b.bootImageType,
		Cmdline:             b.properties.Cmdline,
		Kernel:              kernelPath,
		Dtb:                 dtbPath,
		Bootconfig:          b.getBootconfigPath(ctx),
		Output:              output,
		SignedOutput:        b.SignedOutputPath(),
		PropFileForMiscInfo: b.buildPropFileForMiscInfo(ctx),
		HeaderVersion:       proptools.String(b.commonProperties.Header_version),
	})

	extractedPublicKey := android.PathForModuleOut(ctx, b.partitionName()+".avbpubkey")
	if b.commonProperties.Avb_private_key != nil {
		key := android.PathForModuleSrc(ctx, proptools.String(b.commonProperties.Avb_private_key))
		ctx.Build(pctx, android.BuildParams{
			Rule:   extractPublicKeyRule,
			Input:  key,
			Output: extractedPublicKey,
		})
	}
	var ril int
	if b.commonProperties.Avb_rollback_index_location != nil {
		ril = proptools.Int(b.commonProperties.Avb_rollback_index_location)
	}

	android.SetProvider(ctx, vbmetaPartitionProvider, vbmetaPartitionInfo{
		Name:                  b.partitionName(),
		RollbackIndexLocation: ril,
		PublicKey:             extractedPublicKey,
		Output:                output,
	})

	// Dump compliance metadata
	complianceMetadataInfo := ctx.ComplianceMetadataInfo()
	prebuiltFilesCopied := make([]string, 0)
	if kernelPath != nil {
		prebuiltFilesCopied = append(prebuiltFilesCopied, kernelPath.String()+":kernel")
	}
	if dtbPath != nil {
		prebuiltFilesCopied = append(prebuiltFilesCopied, dtbPath.String()+":dtb.img")
	}
	complianceMetadataInfo.SetPrebuiltFilesCopied(prebuiltFilesCopied)

	if ramdisk := proptools.String(b.properties.Ramdisk_module); ramdisk != "" {
		buildComplianceMetadata(ctx, bootimgRamdiskDep)
	}
}

var BootimgInfoProvider = blueprint.NewProvider[BootimgInfo]()

type BootimgInfo struct {
	Type                bootImageType
	Cmdline             []string
	Kernel              android.Path
	Dtb                 android.Path
	Bootconfig          android.Path
	Output              android.Path
	SignedOutput        android.Path
	PropFileForMiscInfo android.Path
	HeaderVersion       string
	IsPrebuilt          bool
}

type ramdiskFragmentInfo struct {
	// Path to the vendor ramdisk fragment.
	// Will be used as --vendor_ramdisk_fragment
	Output android.Path

	// Name of the ramdisk fragment.
	// Will be used as --ramdisk_name
	Ramdisk_name string

	// The root staging directory of the filesystem.
	RootDir android.Path
}

type ramdiskFragmentsInfo []ramdiskFragmentInfo

var ramdiskFragmentInfoProvider = blueprint.NewProvider[ramdiskFragmentInfo]()
var ramdiskFragmentsInfoProvider = blueprint.NewProvider[ramdiskFragmentsInfo]()

func (b *bootimg) getKernelPath(ctx android.ModuleContext) android.Path {
	var kernelPath android.Path
	kernelName := b.properties.Kernel_prebuilt.GetOrDefault(ctx, "")
	if kernelName != "" {
		kernelPath = android.PathForModuleSrc(ctx, kernelName)
	}
	return kernelPath
}

func (b *bootimg) getDtbPath(ctx android.ModuleContext) android.Path {
	var dtbPath android.Path
	dtbName := proptools.String(b.properties.Dtb_prebuilt)
	if dtbName != "" {
		dtbPath = android.PathForModuleSrc(ctx, dtbName)
	}
	return dtbPath
}

func (b *bootimg) getBootconfigPath(ctx android.ModuleContext) android.Path {
	var bootconfigPath android.Path
	bootconfigName := proptools.String(b.properties.Bootconfig)
	if bootconfigName != "" {
		bootconfigPath = android.PathForModuleSrc(ctx, bootconfigName)
	}
	return bootconfigPath
}

func (b *bootimg) buildBootImage(ctx android.ModuleContext, kernel android.Path) android.Path {
	output := android.PathForModuleOut(ctx, "unsigned", b.installFileName())

	builder := android.NewRuleBuilder(pctx, ctx)
	cmd := builder.Command().BuiltTool("mkbootimg")

	if kernel != nil {
		cmd.FlagWithInput("--kernel ", kernel)
	}

	// These arguments are passed for boot.img and init_boot.img generation
	if b.bootImageType.isBoot() || b.bootImageType.isInitBoot() {
		cmd.FlagWithArg("--os_version ", ctx.Config().PlatformVersionLastStable())
		cmd.FlagWithArg("--os_patch_level ", ctx.Config().PlatformSecurityPatch())
	}

	if b.getDtbPath(ctx) != nil {
		cmd.FlagWithInput("--dtb ", b.getDtbPath(ctx))
	}

	cmdline := strings.Join(b.properties.Cmdline, " ")
	if cmdline != "" {
		flag := "--cmdline "
		if b.bootImageType.isVendorBoot() {
			flag = "--vendor_cmdline "
		}
		cmd.FlagWithArg(flag, proptools.ShellEscapeIncludingSpaces(cmdline))
	}

	headerVersion := proptools.String(b.commonProperties.Header_version)
	if headerVersion == "" {
		ctx.PropertyErrorf("header_version", "must be set")
		return output
	}
	verNum, err := strconv.Atoi(headerVersion)
	if err != nil {
		ctx.PropertyErrorf("header_version", "%q is not a number", headerVersion)
		return output
	}
	if b.bootImageType.isVendorBoot() {
		if verNum < 3 {
			ctx.PropertyErrorf("header_version", "must be 3 or higher for vendor_boot")
			return output
		}
	}

	cmd.FlagWithArg("--header_version ", headerVersion)

	ramdiskName := proptools.String(b.properties.Ramdisk_module)
	if ramdiskName != "" {
		ramdisk := ctx.GetDirectDepProxyWithTag(ramdiskName, bootimgRamdiskDep)
		if fsInfo, ok := android.OtherModuleProvider(ctx, ramdisk, FilesystemProvider); ok {
			flag := "--ramdisk "
			if b.bootImageType.isVendorBoot() || b.bootImageType.isVendorKernelBoot() {
				flag = "--vendor_ramdisk "
			}
			cmd.FlagWithInput(flag, fsInfo.Output)
		} else {
			ctx.PropertyErrorf("ramdisk", "%q is not android_filesystem module", ramdisk.Name())
			return output
		}
	}

	bootconfig := proptools.String(b.properties.Bootconfig)
	if bootconfig != "" {
		if !b.bootImageType.isVendorBoot() {
			ctx.PropertyErrorf("bootconfig", "requires vendor_boot: true")
			return output
		}
		if verNum < 4 {
			ctx.PropertyErrorf("bootconfig", "requires header_version: 4 or later")
			return output
		}
		cmd.FlagWithInput("--vendor_bootconfig ", android.PathForModuleSrc(ctx, bootconfig))
	}

	var rfi ramdiskFragmentsInfo
	for _, fragment := range b.properties.Ramdisk_fragment_modules {
		fragmentModule := ctx.GetDirectDepProxyWithTag(fragment, bootimgRamdiskFragmentDep)
		if info, exists := android.OtherModuleProvider(ctx, fragmentModule, ramdiskFragmentInfoProvider); exists {
			cmd.FlagWithArg("--ramdisk_name ", info.Ramdisk_name)
			cmd.FlagWithInput("--vendor_ramdisk_fragment ", info.Output)
			rfi = append(rfi, info)
		} else {
			ctx.ModuleErrorf("%s does not set RamdiskFragmentInfo", fragment)
		}
	}

	// Output flag for boot.img and init_boot.img
	flag := "--output "
	if b.bootImageType.isVendorBoot() || b.bootImageType.isVendorKernelBoot() {
		flag = "--vendor_boot "
	}
	cmd.FlagWithOutput(flag, output)

	if b.commonProperties.Partition_size != nil {
		assertMaxImageSize(builder, output, *b.commonProperties.Partition_size, proptools.Bool(b.commonProperties.Use_avb))
	}

	android.SetProvider(ctx, ramdiskFragmentsInfoProvider, rfi)

	builder.Build("build_bootimg", fmt.Sprintf("Creating %s", b.BaseModuleName()))
	return output
}

func (b *bootimg) addAvbFooter(ctx android.ModuleContext, unsignedImage android.Path, kernel android.Path) android.Path {
	output := android.PathForModuleOut(ctx, b.installFileName())
	builder := android.NewRuleBuilder(pctx, ctx)
	builder.Command().Text("cp").Input(unsignedImage).Output(output)
	cmd := builder.Command().BuiltTool("avbtool").
		Text("add_hash_footer").
		FlagWithInput("--image ", output)

	if b.commonProperties.Partition_size != nil {
		cmd.FlagWithArg("--partition_size ", strconv.FormatInt(*b.commonProperties.Partition_size, 10))
	} else {
		cmd.Flag("--dynamic_partition_size")
	}

	// If you don't provide a salt, avbtool will use random bytes for the salt.
	// This is bad for determinism (cached builds and diff tests are affected), so instead,
	// we try to provide a salt. The requirements for a salt are not very clear, one aspect of it
	// is that if it's unpredictable, attackers trying to change the contents of a partition need
	// to find a new hash collision every release, because the salt changed.
	if kernel != nil {
		cmd.Textf(`--salt $(sha256sum "%s" | cut -d " " -f 1)`, kernel.String())
		cmd.Implicit(kernel)
	} else {
		cmd.Textf(`--salt $(sha256sum "%s" "%s" | cut -d " " -f 1 | tr -d '\n')`, ctx.Config().BuildNumberFile(ctx), ctx.Config().Getenv("BUILD_DATETIME_FILE"))
		cmd.OrderOnly(ctx.Config().BuildNumberFile(ctx))
	}

	cmd.FlagWithArg("--partition_name ", b.bootImageType.String())

	if b.commonProperties.Avb_algorithm != nil {
		cmd.FlagWithArg("--algorithm ", proptools.NinjaAndShellEscape(*b.commonProperties.Avb_algorithm))
	}

	if b.commonProperties.Avb_private_key != nil {
		key := android.PathForModuleSrc(ctx, proptools.String(b.commonProperties.Avb_private_key))
		cmd.FlagWithInput("--key ", key)
	}

	if !b.bootImageType.isVendorBoot() && !b.bootImageType.isVendorKernelBoot() {
		cmd.FlagWithArg("--prop ", proptools.NinjaAndShellEscape(fmt.Sprintf(
			"com.android.build.%s.os_version:%s", b.bootImageType.String(), ctx.Config().PlatformVersionLastStable())))
	}

	fingerprintFile := ctx.Config().BuildFingerprintFile(ctx)
	cmd.FlagWithArg("--prop ", fmt.Sprintf("com.android.build.%s.fingerprint:$(cat %s)", b.bootImageType.String(), fingerprintFile.String()))
	cmd.Implicit(fingerprintFile)

	if b.commonProperties.Security_patch != nil {
		cmd.FlagWithArg("--prop ", proptools.NinjaAndShellEscape(fmt.Sprintf(
			"com.android.build.%s.security_patch:%s", b.bootImageType.String(), *b.commonProperties.Security_patch)))
	}

	if b.commonProperties.Avb_rollback_index != nil {
		cmd.FlagWithArg("--rollback_index ", strconv.FormatInt(*b.commonProperties.Avb_rollback_index, 10))
	}

	builder.Build("add_avb_footer", fmt.Sprintf("Adding avb footer to %s", b.BaseModuleName()))
	return output
}

func (b *bootimg) signImage(ctx android.ModuleContext, unsignedImage android.Path) android.Path {
	propFile, toolDeps := b.buildPropFile(ctx)

	output := android.PathForModuleOut(ctx, b.installFileName())
	builder := android.NewRuleBuilder(pctx, ctx)
	builder.Command().Text("cp").Input(unsignedImage).Output(output)
	builder.Command().BuiltTool("verity_utils").
		Input(propFile).
		Implicits(toolDeps).
		Output(output)

	builder.Build("sign_bootimg", fmt.Sprintf("Signing %s", b.BaseModuleName()))
	return output
}

func (b *bootimg) buildPropFile(ctx android.ModuleContext) (android.Path, android.Paths) {
	var sb strings.Builder
	var deps android.Paths
	addStr := func(name string, value string) {
		fmt.Fprintf(&sb, "%s=%s\n", name, value)
	}
	addPath := func(name string, path android.Path) {
		addStr(name, path.String())
		deps = append(deps, path)
	}

	addStr("avb_hash_enable", "true")
	addPath("avb_avbtool", ctx.Config().HostToolPath(ctx, "avbtool"))
	algorithm := proptools.StringDefault(b.commonProperties.Avb_algorithm, "SHA256_RSA4096")
	addStr("avb_algorithm", algorithm)
	key := android.PathForModuleSrc(ctx, proptools.String(b.commonProperties.Avb_private_key))
	addPath("avb_key_path", key)
	addStr("avb_add_hash_footer_args", "") // TODO(jiyong): add --rollback_index
	partitionName := proptools.StringDefault(b.commonProperties.Partition_name, b.Name())
	addStr("partition_name", partitionName)

	propFile := android.PathForModuleOut(ctx, "prop")
	android.WriteFileRule(ctx, propFile, sb.String())
	return propFile, deps
}

func (b *bootimg) getAvbHashFooterArgs(ctx android.ModuleContext) (string, android.Paths) {
	var deps android.Paths
	ret := ""
	if !b.bootImageType.isVendorBoot() && !b.bootImageType.isVendorKernelBoot() {
		ret += "--prop " + fmt.Sprintf("com.android.build.%s.os_version:%s", b.bootImageType.String(), ctx.Config().PlatformVersionLastStable())
	}

	fingerprintFile := ctx.Config().BuildFingerprintFile(ctx)
	ret += " --prop " + fmt.Sprintf("com.android.build.%s.fingerprint:{CONTENTS_OF:%s}", b.bootImageType.String(), fingerprintFile.String())
	deps = append(deps, fingerprintFile)

	if b.commonProperties.Security_patch != nil {
		ret += " --prop " + fmt.Sprintf("com.android.build.%s.security_patch:%s", b.bootImageType.String(), *b.commonProperties.Security_patch)
	}

	if b.commonProperties.Avb_rollback_index != nil {
		ret += " --rollback_index " + strconv.FormatInt(*b.commonProperties.Avb_rollback_index, 10)
	}
	return strings.TrimSpace(ret), deps
}

func (b *bootimg) buildPropFileForMiscInfo(ctx android.ModuleContext) android.Path {
	var sb strings.Builder
	addStr := func(name string, value string) {
		fmt.Fprintf(&sb, "%s=%s\n", name, value)
	}

	footerArgs, footerArgsDeps := b.getAvbHashFooterArgs(ctx)

	bootImgType := proptools.String(b.commonProperties.Boot_image_type)
	addStr("avb_"+bootImgType+"_add_hash_footer_args", footerArgs)
	if ramdisk := proptools.String(b.properties.Ramdisk_module); ramdisk != "" {
		ramdiskModule := ctx.GetDirectDepProxyWithTag(ramdisk, bootimgRamdiskDep)
		fsInfo, _ := android.OtherModuleProvider(ctx, ramdiskModule, FilesystemProvider)
		if fsInfo.HasOrIsRecovery {
			// Create a dup entry for recovery
			addStr("avb_recovery_add_hash_footer_args", strings.ReplaceAll(footerArgs, bootImgType, "recovery"))
		}
	}
	if b.commonProperties.Avb_private_key != nil {
		addStr("avb_"+bootImgType+"_algorithm", proptools.StringDefault(b.commonProperties.Avb_algorithm, "SHA256_RSA4096"))
		addStr("avb_"+bootImgType+"_key_path", android.PathForModuleSrc(ctx, proptools.String(b.commonProperties.Avb_private_key)).String())
		addStr("avb_"+bootImgType+"_rollback_index_location", strconv.Itoa(proptools.Int(b.commonProperties.Avb_rollback_index_location)))
	}
	if b.commonProperties.Partition_size != nil {
		addStr(bootImgType+"_size", strconv.FormatInt(*b.commonProperties.Partition_size, 10))
	}
	if bootImgType != "boot" {
		addStr(bootImgType, "true")
	}

	propFilePreProcessing := android.PathForModuleOut(ctx, "prop_for_misc_info_pre_processing")
	android.WriteFileRuleVerbatim(ctx, propFilePreProcessing, sb.String())
	propFile := android.PathForModuleOut(ctx, "prop_file_for_misc_info")
	ctx.Build(pctx, android.BuildParams{
		Rule:      textFileProcessorRule,
		Input:     propFilePreProcessing,
		Output:    propFile,
		Implicits: footerArgsDeps,
	})

	return propFile
}

var _ android.AndroidMkEntriesProvider = (*bootimg)(nil)

// Implements android.AndroidMkEntriesProvider
func (b *bootimg) AndroidMkEntries() []android.AndroidMkEntries {
	return []android.AndroidMkEntries{android.AndroidMkEntries{
		Class:      "ETC",
		OutputFile: android.OptionalPathForPath(b.output),
		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
				entries.SetString("LOCAL_MODULE_PATH", b.installDir.String())
				entries.SetString("LOCAL_INSTALLED_MODULE_STEM", b.installFileName())
			},
		},
	}}
}

var _ Filesystem = (*bootimg)(nil)

func (b *bootimg) OutputPath() android.Path {
	return b.output
}

func (b *bootimg) SignedOutputPath() android.Path {
	if proptools.Bool(b.commonProperties.Use_avb) {
		return b.OutputPath()
	}
	return nil
}

type prebuiltBootImg struct {
	android.ModuleBase

	properties       PrebuiltBootImgProperties
	commonProperties CommonBootimgProperties

	bootImageType bootImageType
	output        android.Path
}

type PrebuiltBootImgProperties struct {
	// Path to the prebuilt boot img file
	Src *string `android:"arch_variant,path"`
}

var _ Filesystem = (*prebuiltBootImg)(nil)

func (b *prebuiltBootImg) OutputPath() android.Path {
	return b.output
}

func (b *prebuiltBootImg) SignedOutputPath() android.Path {
	if proptools.Bool(b.commonProperties.Use_avb) {
		return b.OutputPath()
	}
	return nil
}

func PrebuiltBootimgFactory() android.Module {
	module := &prebuiltBootImg{}
	module.AddProperties(&module.properties, &module.commonProperties)
	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibFirst)
	return module
}

func (b *prebuiltBootImg) GenerateAndroidBuildActions(ctx android.ModuleContext) {
	b.bootImageType = toBootImageType(ctx, proptools.StringDefault(b.commonProperties.Boot_image_type, "boot"))
	if b.bootImageType == unsupported {
		return
	}

	src := android.PathForModuleSrc(ctx, proptools.String(b.properties.Src))
	b.output = src
	var kernelPath android.Path
	if proptools.Bool(b.commonProperties.Use_avb) {
		b.output, kernelPath = b.signWithAvb(ctx, src)
	}
	installDir := android.PathForModuleInstall(ctx, "etc")
	ctx.InstallFile(installDir, src.Base(), b.output)

	ctx.SetOutputFiles([]android.Path{b.output}, "")

	android.SetProvider(ctx, BootimgInfoProvider, BootimgInfo{
		Type:                b.bootImageType,
		Output:              b.output,
		SignedOutput:        b.SignedOutputPath(),
		PropFileForMiscInfo: b.buildPropFileForMiscInfo(ctx),
		HeaderVersion:       proptools.String(b.commonProperties.Header_version),
		Kernel:              kernelPath,
		IsPrebuilt:          true,
	})

	extractedPublicKey := android.PathForModuleOut(ctx, b.bootImageType.String()+".avbpubkey")
	if b.commonProperties.Avb_private_key != nil {
		key := android.PathForModuleSrc(ctx, proptools.String(b.commonProperties.Avb_private_key))
		ctx.Build(pctx, android.BuildParams{
			Rule:   extractPublicKeyRule,
			Input:  key,
			Output: extractedPublicKey,
		})
	}
	var ril int
	if b.commonProperties.Avb_rollback_index_location != nil {
		ril = proptools.Int(b.commonProperties.Avb_rollback_index_location)
	}

	android.SetProvider(ctx, vbmetaPartitionProvider, vbmetaPartitionInfo{
		Name:                  b.bootImageType.String(),
		RollbackIndexLocation: ril,
		PublicKey:             extractedPublicKey,
		Output:                b.output,
	})
}

// Unpacks the kernel from a prebuilt *_boot.img and uses it as salt for add_hash_footer.
// Returns the signed *_boot.img and unpacked kernel.
func (b *prebuiltBootImg) signWithAvb(ctx android.ModuleContext, src android.Path) (android.Path, android.Path) {
	signed := android.PathForModuleOut(ctx, "avb", src.Base())
	unpackDir := android.PathForModuleOut(ctx, "avb", "unpack")
	kernel := unpackDir.Join(ctx, "kernel")

	builder := android.NewRuleBuilder(pctx, ctx)
	builder.Command().Text("cp").Input(src).Output(signed)

	builder.Command().
		BuiltTool("unpack_bootimg").
		FlagWithInput("--boot_img ", src).
		FlagWithArg("--out ", unpackDir.String()).
		ImplicitOutput(kernel)

	cmd := builder.Command()
	cmd.BuiltTool("avbtool").
		Flag("add_hash_footer").
		FlagWithArg("--image ", signed.String()).
		Textf(`--salt $(sha256sum "%s" | cut -d " " -f 1)`, unpackDir.Join(ctx, "kernel")).
		FlagWithArg("--partition_name ", b.bootImageType.String())

	if b.commonProperties.Partition_size != nil {
		cmd.FlagWithArg("--partition_size ", strconv.FormatInt(*b.commonProperties.Partition_size, 10))
	} else {
		cmd.Flag("--dynamic_partition_size")
	}

	if b.commonProperties.Avb_algorithm != nil {
		cmd.FlagWithArg("--algorithm ", proptools.NinjaAndShellEscape(*b.commonProperties.Avb_algorithm))
	}

	if b.commonProperties.Avb_private_key != nil {
		key := android.PathForModuleSrc(ctx, proptools.String(b.commonProperties.Avb_private_key))
		cmd.FlagWithInput("--key ", key)
	}

	if !b.bootImageType.isVendorBoot() && !b.bootImageType.isVendorKernelBoot() {
		cmd.FlagWithArg("--prop ", proptools.NinjaAndShellEscape(fmt.Sprintf(
			"com.android.build.%s.os_version:%s", b.bootImageType.String(), ctx.Config().PlatformVersionLastStable())))
	}

	fingerprintFile := ctx.Config().BuildFingerprintFile(ctx)
	cmd.FlagWithArg("--prop ", fmt.Sprintf("com.android.build.%s.fingerprint:$(cat %s)", b.bootImageType.String(), fingerprintFile.String()))
	cmd.Implicit(fingerprintFile)

	if b.commonProperties.Security_patch != nil {
		cmd.FlagWithArg("--prop ", proptools.NinjaAndShellEscape(fmt.Sprintf(
			"com.android.build.%s.security_patch:%s", b.bootImageType.String(), *b.commonProperties.Security_patch)))
	}

	if b.commonProperties.Avb_rollback_index != nil {
		cmd.FlagWithArg("--rollback_index ", strconv.FormatInt(*b.commonProperties.Avb_rollback_index, 10))
	}

	builder.Build("unpack_and_sign", "unpack_and_sign")

	return signed, kernel
}

func (b *prebuiltBootImg) buildPropFileForMiscInfo(ctx android.ModuleContext) android.Path {
	var sb strings.Builder
	addStr := func(name string, value string) {
		fmt.Fprintf(&sb, "%s=%s\n", name, value)
	}
	footerArgs, footerArgDeps := b.getAvbHashFooterArgs(ctx)
	bootImgType := proptools.String(b.commonProperties.Boot_image_type)
	addStr("avb_"+bootImgType+"_add_hash_footer_args", footerArgs)
	if b.commonProperties.Avb_private_key != nil {
		addStr("avb_"+bootImgType+"_algorithm", proptools.StringDefault(b.commonProperties.Avb_algorithm, "SHA256_RSA4096"))
		addStr("avb_"+bootImgType+"_key_path", android.PathForModuleSrc(ctx, proptools.String(b.commonProperties.Avb_private_key)).String())
		addStr("avb_"+bootImgType+"_rollback_index_location", strconv.Itoa(proptools.Int(b.commonProperties.Avb_rollback_index_location)))
	}
	if b.commonProperties.Partition_size != nil {
		addStr(bootImgType+"_size", strconv.FormatInt(*b.commonProperties.Partition_size, 10))
	}
	addStr("boot_images", bootImgType+".img")

	propFilePreProcessing := android.PathForModuleOut(ctx, "prop_for_misc_info_pre_processing")
	android.WriteFileRuleVerbatim(ctx, propFilePreProcessing, sb.String())
	propFile := android.PathForModuleOut(ctx, "prop_file_for_misc_info")
	ctx.Build(pctx, android.BuildParams{
		Rule:      textFileProcessorRule,
		Input:     propFilePreProcessing,
		Output:    propFile,
		Implicits: footerArgDeps,
	})

	return propFile
}

func (b *prebuiltBootImg) getAvbHashFooterArgs(ctx android.ModuleContext) (string, android.Paths) {
	var deps android.Paths
	ret := ""
	bootImgType := proptools.String(b.commonProperties.Boot_image_type)
	if bootImgType != "vendor_boot" {
		ret += "--prop " + fmt.Sprintf("com.android.build.%s.os_version:%s", bootImgType, ctx.Config().PlatformVersionLastStable())
	}

	fingerprintFile := ctx.Config().BuildFingerprintFile(ctx)
	ret += " --prop " + fmt.Sprintf("com.android.build.%s.fingerprint:{CONTENTS_OF:%s}", bootImgType, fingerprintFile.String())
	deps = append(deps, fingerprintFile)

	if b.commonProperties.Security_patch != nil {
		ret += " --prop " + fmt.Sprintf("com.android.build.%s.security_patch:%s", bootImgType, *b.commonProperties.Security_patch)
	}

	if b.commonProperties.Avb_rollback_index != nil {
		ret += " --rollback_index " + strconv.FormatInt(*b.commonProperties.Avb_rollback_index, 10)
	}
	return strings.TrimSpace(ret), deps
}
