Project import
diff --git a/avb/.clang-format b/avb/.clang-format
new file mode 100644
index 0000000..51ff646
--- /dev/null
+++ b/avb/.clang-format
@@ -0,0 +1,26 @@
+#
+# Copyright (C) 2017 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.
+#
+
+BasedOnStyle: Google
+AllowShortFunctionsOnASingleLine: Empty
+AllowShortIfStatementsOnASingleLine: true
+AllowShortLoopsOnASingleLine: false
+BinPackArguments: false
+BinPackParameters: false
+CommentPragmas: NOLINT:.*
+DerivePointerAlignment: false
+PointerAlignment: Left
+TabWidth: 2
diff --git a/avb/Android.mk b/avb/Android.mk
new file mode 100644
index 0000000..533c2d8
--- /dev/null
+++ b/avb/Android.mk
@@ -0,0 +1,212 @@
+#
+# Copyright 2016, The Android Open Source Project
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+#
+
+LOCAL_PATH := $(my-dir)
+
+avb_common_cflags := \
+    -D_FILE_OFFSET_BITS=64 \
+    -D_POSIX_C_SOURCE=199309L \
+    -Wa,--noexecstack \
+    -Werror \
+    -Wall \
+    -Wextra \
+    -Wformat=2 \
+    -Wno-psabi \
+    -Wno-unused-parameter \
+    -ffunction-sections \
+    -fstack-protector-strong
+avb_common_cppflags := \
+    -Wnon-virtual-dtor \
+    -fno-strict-aliasing
+avb_common_ldflags := \
+    -Wl,--gc-sections
+avb_lib_common_files := \
+    libavb/avb_chain_partition_descriptor.c \
+    libavb/avb_crc32.c \
+    libavb/avb_crypto.c \
+    libavb/avb_descriptor.c \
+    libavb/avb_footer.c \
+    libavb/avb_hash_descriptor.c \
+    libavb/avb_hashtree_descriptor.c \
+    libavb/avb_kernel_cmdline_descriptor.c \
+    libavb/avb_property_descriptor.c \
+    libavb/avb_rsa.c \
+    libavb/avb_sha256.c \
+    libavb/avb_sha512.c \
+    libavb/avb_slot_verify.c \
+    libavb/avb_util.c \
+    libavb/avb_vbmeta_image.c
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := avbtool
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_REQUIRED_MODULES := fec
+LOCAL_IS_HOST_MODULE := true
+LOCAL_MODULE := avbtool
+include $(BUILD_PREBUILT)
+
+# Build libavb for the target (for e.g. fs_mgr usage).
+include $(CLEAR_VARS)
+LOCAL_MODULE := libavb
+LOCAL_MODULE_HOST_OS := linux
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)
+LOCAL_CLANG := true
+LOCAL_CFLAGS := $(avb_common_cflags) -DAVB_ENABLE_DEBUG -DAVB_COMPILATION
+LOCAL_LDFLAGS := $(avb_common_ldflags)
+LOCAL_SRC_FILES := \
+    $(avb_lib_common_files) \
+    libavb/avb_sysdeps_posix.c
+include $(BUILD_STATIC_LIBRARY)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := libavb_32
+LOCAL_MODULE_HOST_OS := linux
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)
+LOCAL_MULTILIB := 32
+LOCAL_CLANG := false
+LOCAL_CFLAGS := $(avb_common_cflags) -DAVB_ENABLE_DEBUG -DAVB_COMPILATION -fno-unwind-tables -std=c99
+LOCAL_LDFLAGS := $(avb_common_ldflags)
+LOCAL_SRC_FILES := $(avb_lib_common_files)
+include $(BUILD_STATIC_LIBRARY)
+
+# Build libavb for the host (for unit tests).
+include $(CLEAR_VARS)
+LOCAL_MODULE := libavb_host
+LOCAL_MODULE_HOST_OS := linux
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)
+LOCAL_MODULE_CLASS := STATIC_LIBRARIES
+LOCAL_CLANG := true
+LOCAL_CFLAGS := $(avb_common_cflags) -fno-stack-protector -DAVB_ENABLE_DEBUG -DAVB_COMPILATION
+LOCAL_LDFLAGS := $(avb_common_ldflags)
+LOCAL_SRC_FILES := \
+    $(avb_lib_common_files)
+include $(BUILD_HOST_STATIC_LIBRARY)
+
+# Build libavb_ab for the host (for unit tests).
+include $(CLEAR_VARS)
+LOCAL_MODULE := libavb_ab_host
+LOCAL_REQUIRED_MODULES := libavb_host
+LOCAL_MODULE_HOST_OS := linux
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)
+LOCAL_MODULE_CLASS := STATIC_LIBRARIES
+LOCAL_CLANG := true
+LOCAL_CFLAGS := $(avb_common_cflags) -fno-stack-protector -DAVB_ENABLE_DEBUG -DAVB_COMPILATION
+LOCAL_LDFLAGS := $(avb_common_ldflags)
+LOCAL_SRC_FILES := \
+    libavb_ab/avb_ab_flow.c
+include $(BUILD_HOST_STATIC_LIBRARY)
+
+# Build libavb_atx for the host (for unit tests).
+include $(CLEAR_VARS)
+LOCAL_MODULE := libavb_atx_host
+LOCAL_REQUIRED_MODULES := libavb_host
+LOCAL_MODULE_HOST_OS := linux
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)
+LOCAL_MODULE_CLASS := STATIC_LIBRARIES
+LOCAL_CLANG := true
+LOCAL_CFLAGS := $(avb_common_cflags) -fno-stack-protector -DAVB_ENABLE_DEBUG -DAVB_COMPILATION
+LOCAL_LDFLAGS := $(avb_common_ldflags)
+LOCAL_SRC_FILES := \
+    libavb_atx/avb_atx_validate.c
+include $(BUILD_HOST_STATIC_LIBRARY)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := libavb_host_sysdeps
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)
+LOCAL_MODULE_HOST_OS := linux
+LOCAL_MODULE_CLASS := STATIC_LIBRARIES
+LOCAL_CLANG := true
+LOCAL_CFLAGS := $(avb_common_cflags) -DAVB_ENABLE_DEBUG -DAVB_COMPILATION
+LOCAL_LDFLAGS := $(avb_common_ldflags)
+LOCAL_SRC_FILES := \
+    libavb/avb_sysdeps_posix.c
+include $(BUILD_HOST_STATIC_LIBRARY)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := libavb_host_unittest
+LOCAL_REQUIRED_MODULES := simg2img img2simg avbtool
+LOCAL_MODULE_HOST_OS := linux
+LOCAL_CPP_EXTENSION := .cc
+LOCAL_CLANG := true
+LOCAL_CFLAGS := $(avb_common_cflags) -DAVB_ENABLE_DEBUG -DAVB_COMPILATION
+LOCAL_CPPFLAGS := $(avb_common_cppflags)
+LOCAL_LDFLAGS := $(avb_common_ldflags)
+LOCAL_STATIC_LIBRARIES := \
+    libavb_host \
+    libavb_host_sysdeps \
+    libavb_ab_host \
+    libavb_atx_host \
+    libgmock_host \
+    libgtest_host
+LOCAL_SHARED_LIBRARIES := \
+    libchrome \
+    libcrypto
+LOCAL_SRC_FILES := \
+    test/avb_ab_flow_unittest.cc \
+    test/avb_atx_validate_unittest.cc \
+    test/avb_slot_verify_unittest.cc \
+    test/avb_unittest_util.cc \
+    test/avb_util_unittest.cc \
+    test/avb_vbmeta_image_unittest.cc \
+    test/avbtool_unittest.cc \
+    test/fake_avb_ops.cc
+LOCAL_LDLIBS_linux := -lrt
+include $(BUILD_HOST_NATIVE_TEST)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := libavb_host_user_code_test
+LOCAL_MODULE_HOST_OS := linux
+LOCAL_MODULE_CLASS := STATIC_LIBRARIES
+LOCAL_CPP_EXTENSION := .cc
+LOCAL_CLANG := true
+LOCAL_CFLAGS := $(avb_common_cflags)
+LOCAL_CPPFLAGS := $(avb_common_cppflags)
+LOCAL_LDFLAGS := $(avb_common_ldflags)
+LOCAL_SRC_FILES := test/user_code_test.cc
+include $(BUILD_HOST_STATIC_LIBRARY)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := bootctrl.avb
+LOCAL_MODULE_RELATIVE_PATH := hw
+LOCAL_REQUIRED_MODULES := libavb
+LOCAL_SRC_FILES := \
+    boot_control/boot_control_avb.c \
+    boot_control/avb_ops_device.c \
+    libavb_ab/avb_ab_flow.c \
+    libavb/avb_sysdeps_posix.c
+LOCAL_CLANG := true
+LOCAL_CFLAGS := $(avb_common_cflags) -DAVB_COMPILATION
+LOCAL_LDFLAGS := $(avb_common_ldflags)
+LOCAL_SHARED_LIBRARIES := libbase libcutils
+LOCAL_STATIC_LIBRARIES := libfs_mgr libavb
+LOCAL_POST_INSTALL_CMD := \
+	$(hide) mkdir -p $(TARGET_OUT_SHARED_LIBRARIES)/hw && \
+	ln -sf bootctrl.avb.so $(TARGET_OUT_SHARED_LIBRARIES)/hw/bootctrl.default.so
+include $(BUILD_SHARED_LIBRARY)
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := libavb_host_symbols_test
+LOCAL_MODULE_TAGS := debug
+LOCAL_ADDITIONAL_DEPENDENCIES := libavb_ab_host
+include $(BUILD_HOST_PREBUILT)
diff --git a/avb/LICENSE b/avb/LICENSE
new file mode 100644
index 0000000..d21621a
--- /dev/null
+++ b/avb/LICENSE
@@ -0,0 +1,20 @@
+Copyright 2016, The Android Open Source Project
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/avb/MODULE_LICENSE_ANDROID b/avb/MODULE_LICENSE_ANDROID
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/avb/MODULE_LICENSE_ANDROID
diff --git a/avb/PREUPLOAD.cfg b/avb/PREUPLOAD.cfg
new file mode 100644
index 0000000..61948d2
--- /dev/null
+++ b/avb/PREUPLOAD.cfg
@@ -0,0 +1,5 @@
+[Builtin Hooks]
+clang_format = true
+
+[Builtin Hooks Options]
+clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc
diff --git a/avb/README b/avb/README
new file mode 100644
index 0000000..1439785
--- /dev/null
+++ b/avb/README
@@ -0,0 +1,307 @@
+This directory contains avbtool, libavb, and libavb_ab.
+
+The main job of avbtool is to create vbmeta.img which is the
+top-level object for verified boot. This image is designed to go into
+the vbmeta partition (or, if using A/B, the slot in question
+e.g. vbmeta_a or vbmeta_b) and be of minimal size (for out-of-band
+updates). The vbmeta image is cryptographically signed and contains
+verification data (e.g. cryptographic digests) for verifying boot.img,
+system.img, and other partitions/images.
+
+The vbmeta image can also contain references to other partitions where
+verification data is stored as well as a public key indicating who
+should sign the verification data. This indirection provides
+delegation, that is, it allows a 3rd party to control content on a given
+partition by including the public key said 3rd party is using to sign
+the data with, in vbmeta.img. By design, this authority can be easily
+revoked by simply updating vbmeta.img with new descriptors for the
+partition in question.
+
+Storing signed verification data on other images - for example
+boot.img and system.img - is also done with avbtool.
+
+In addition to avbtool, a library - libavb - is provided. This library
+performs all verification on the device side e.g. it starts by loading
+the vbmeta partition, checks the signature, and then goes on to load
+the boot partition for verification.
+
+The libavb library is intended to be used in both boot loaders and
+inside Android. It has a simple abstraction for system dependencies
+(see libavb/avb_sysdeps.h) as well as operations that the boot loader
+or OS is expected to implement (see libavb/avb_ops.h). The main entry
+point for verification is avb_slot_verify().
+
+It is expected that most devices will use A/B (e.g. multiple copies of
+the OS in separate so-called 'slots') in addition to AVB. While
+managing A/B metadata and associated metadata (e.g. managing
+stored_rollback_index[n] locations) is outside the scope of libavb,
+enough interfaces are exposed so the boot loader can integrate its A/B
+stack with libavb. In particular avb_slot_verify() takes a
+|slot_suffix| parameter and its result struct |AvbSlotVerifyData|
+convey the rollback indexes in the image that was verified.
+
+AVB also includes an A/B implementation that boot loaders may
+optionally use. This implementation is in the libavb_ab library and
+integrates with image verification including updating the
+stored_rollback_index[n] locations on the device as needed. The
+bootloader can use this through the avb_ab_flow() function. This
+library is built on top of libavb.
+
+In libavb_ab, A/B metadata is stored in the 'misc' partition using a
+format private to libavb_ab in the location on 'misc' reserved for
+this. For more information about the misc.img file format see the
+bootable/recovery/bootloader.h file. A/B metadata can be written to
+misc.img using the set_ab_metadata sub-command of avbtool. A/B
+metadata is comprised of (for each slot) a priority field (0 to 15),
+number of tries remaining for attempting to boot the slot (0 to 7),
+and a flag to indicate whether the slot has successfully booted.
+
+A/B metadata integrity is provided by a simple magic marker and a
+CRC-32 checksum. If invalid A/B metadata is detected, the behavior is
+to reset the A/B metadata to a known state where both slots are given
+seven boot tries.
+
+An implementation of a boot_control HAL using AVB-specific A/B
+metadata is also provided.
+
+Android Things has specific requirements and validation logic for the
+vbmeta public key. An extension is provided in libavb_atx which performs
+this validation as an implementatio of libavb's public key validation
+operation (see avb_validate_vbmeta_public_key in avb_ops.h).
+
+-- FILES AND DIRECTORIES
+
+ libavb/
+
+   An implementation of image verification. This code is designed to
+   be highly portable so it can be used in as many contexts as
+   possible. This code requires a C99-compliant C compiler.
+
+   Part of this code is considered internal to the implementation and
+   should not be used outside it. For example, this applies to the
+   avb_rsa.[ch] and avb_sha.[ch] files.
+
+   System dependencies expected to be provided by the platform is
+   defined in avb_sysdeps.h. If the platform provides the standard C
+   runtime avb_sysdeps_posix.c can be used.
+
+ libavb_ab/
+
+   An A/B implementation for use in boot loaders. This is built on top
+   of libavb.
+
+ libavb_atx/
+
+   An Android Things Extension for validating public key metadata.
+
+ boot_control/
+
+   An implemementation of the Android boot_control HAL for use with
+   boot loaders using libavb_ab.
+
+ Android.mk
+
+   Build instructions for building libavb (a static library for use on
+   the device), host-side libraries (for unit tests), and unit tests.
+
+ avbtool
+
+   A tool written in Python for working with images related to
+   verified boot.
+
+ test/
+
+   Contains unit tests for abvtool, libavb, libavb_ab, and libavb_atx.
+
+ examples/uefi/
+
+   Contains the source-code for a UEFI-based boot-loader utilizing
+   libavb/ and libavb_ab/.
+
+-- AUDIENCE AND PORTABILITY NOTES
+
+This code is intended to be used in bootloaders in devices running
+Android. The suggested approach is to copy the appropriate header and
+C files mentioned in the previous section into the boot loader and
+integrate as appropriate.
+
+The libavb/ and libavb_ab/ codebase will evolve over time so
+integration should be as non-invasive as possible. The intention is to
+keep the API of the library stable however it will be broken if
+necessary.
+
+As for portability, the library is intended to be highly portable,
+work on both little- and big-endian architectures and 32- and
+64-bit. It's also intended to work in non-standard environments
+without the standard C library and runtime.
+
+If the AVB_ENABLE_DEBUG preprocessor symbol is set, the code will
+include useful debug information and run-time checks. Production
+builds should not use this.
+
+The preprocessor symbol AVB_COMPILATION should be set only when
+compiling the libraries. The code must be compiled into a separate
+libraries.
+
+Applications using the compiled libavb library must only include the
+libavb/libavb.h file (which will include all public interfaces) and
+must not have the AVB_COMPILATION preprocessor symbol set. This is to
+ensure that internal code that may be change in the future (for
+example avb_sha.[ch] and avb_rsa.[ch]) will not be visible to
+application code.
+
+Applications using the compiled libavb_ab library must only include
+the libavb_ab/libavb_ab.h file for similar reasons.
+
+-- COMPATIBILITY NOTES
+
+The VBMeta structure (as defined in libavb/avb_vbmeta_header.h)
+guarantees forwards- and backwards-compatibility provided the major
+version does not change.
+
+When backwards-compatible changes are made - for example, when a new
+field is added to AvbVBMetaImageHeader - the minor version will be
+bumped. At the same time, libavb will also be modified to test for the
+appropriate minor version before attempting to access the newly added
+field. This ensures that version 1.N of the library is able to read an
+old vbmeta image header produced with version 1.M where N > M.
+
+The usual scenario is that the code parsing the AvbVBMetaImageHeader
+rarely changes (it's usually in the firmware of a device and this
+firmware is rarely updated if ever), let's say it's fixed at version
+1.N. At the same time, the version of the avbtool used to produce the
+vbmeta image is rolling forward and is at version 1.M where M > N. The
+problem with this scenario is that version 1.M may easily and
+inadvertently introduce a seemingly compatible change that isn't. For
+example, consider if a new verification algorithm is added - in this
+case version 1.N of the reference implementation will fail at
+verification time when validating the |algorithm_field| of a 1.M image
+if it's set to the new algorithm.
+
+The best approach for dealing with this problem is to always used a
+pinned version of avbtool (say, use version 1.N to generate images
+targeted for devices running version 1.N) for generating and signing
+images but sometimes this is not always possible nor
+desirable. Therefore, to avoid this compatibility problem, avbtool is
+designed to always take such input as a command-line argument so it
+can be kept constant by the caller. In other words, as long as you
+keep your command-line options passed to the avb tool the same, images
+produced by newer versions of avb will continue to work on the same
+version of the reference implementation.
+
+-- USAGE
+
+The content for the vbmeta partition can be generated as follows:
+
+ $ avbtool make_vbmeta_image                                                    \
+     --output OUTPUT                                                            \
+     [--algorithm ALGORITHM] [--key /path/to/key_used_for_signing_or_pub_key]   \
+     [--public_key_metadata /path/to/pkmd.bin] [--rollback_index NUMBER]        \
+     [--include_descriptors_from_footer /path/to/image.bin]                     \
+     [--setup_rootfs_from_kernel /path/to/image.bin]                            \
+     [--chain_partition part_name:rollback_index_location:/path/to/key1.bin]    \
+     [--signing_helper /path/to/external/signer]
+
+An integrity footer containing the hash for an entire partition can be
+added to an existing image as follows:
+
+ $ avbtool add_hash_footer                                                      \
+     --image IMAGE                                                              \
+     --partition_name PARTNAME --partition_size SIZE                            \
+     [--algorithm ALGORITHM] [--key /path/to/key_used_for_signing_or_pub_key]   \
+     [--public_key_metadata /path/to/pkmd.bin] [--rollback_index NUMBER]        \
+     [--hash_algorithm HASH_ALG] [--salt HEX]                                   \
+     [--include_descriptors_from_footer /path/to/image.bin]                     \
+     [--setup_rootfs_from_kernel /path/to/image.bin]                            \
+     [--signing_helper /path/to/external/signer]
+
+An integrity footer containing the root digest and salt for a hashtree
+for a partition can be added to an existing image as follows. The
+hashtree is also appended to the image.
+
+  $ avbtool add_hashtree_footer                                                 \
+     --image IMAGE                                                              \
+     --partition_name PARTNAME --partition_size SIZE                            \
+     [--algorithm ALGORITHM] [--key /path/to/key_used_for_signing_or_pub_key]   \
+     [--public_key_metadata /path/to/pkmd.bin] [--rollback_index NUMBER]        \
+     [--hash_algorithm HASH_ALG] [--salt HEX] [--block_size SIZE]               \
+     [--include_descriptors_from_footer /path/to/image.bin]                     \
+     [--setup_rootfs_from_kernel /path/to/image.bin]                            \
+     [--generate_fec] [--fec_num_roots FEC_NUM_ROOTS]                           \
+     [--signing_helper /path/to/external/signer]
+
+The integrity footer on an image can be removed from an image. The
+hashtree can optionally be kept in place.
+
+  $ avbtool erase_footer --image IMAGE [--keep_hashtree]
+
+To calculate the maximum size of an image that will fit in a partition
+of a given size after having used the 'avbtool add_hashtree_footer'
+command on it, use the --calc_max_image_size option:
+
+  $ avbtool add_hashtree_footer --partition_size $((10*1024*1024)) \
+     --calc_max_image_size
+  10330112
+
+The --signing_helper option can be used in 'make_vbmeta_image', 'add_hash_footer'
+and 'add_hashtree_footer' commands to specify any external program for signing
+hashes. The data to sign (including padding e.g. PKCS1-v1.5) is fed via STDIN
+and the signed data is returned via STDOUT. If --signing_helper is present in
+a command line, the --key option need only contain a public key. Arguments for
+a signing helper are 'algorithm' and 'public key'. If the signing helper exits
+with a non-zero exit code, it means failure.
+
+Here's an example invocation:
+
+ /path/to/my_signing_program SHA256_RSA2048 /path/to/publickey.pem
+
+-- BUILD SYSTEM INTEGRATION NOTES
+
+Android Verified Boot is enabled by the BOARD_AVB_ENABLE variable
+
+ BOARD_AVB_ENABLE := true
+
+This will make the build system create vbmeta.img which will contain a
+hash descriptor for boot.img, a hashtree descriptor for system.img, a
+kernel-cmdline descriptor for setting up dm-verity for system.img and
+append a hash-tree to system.img.
+
+By default, the algorithm SHA256_RSA4096 is used with a test key from
+this directory. This can be overriden by the BOARD_AVB_ALGORITHM and
+BOARD_AVB_KEY_PATH variables to use e.g. a 4096-bit RSA key and
+SHA-512:
+
+ BOARD_AVB_ALGORITHM := SHA512_RSA4096
+ BOARD_AVB_KEY_PATH := /path/to/rsa_key_4096bits.pem
+
+Remember that the public part of this key needs to be available to the
+bootloader of the device expected to verify resulting images. Use
+'avbtool extract_public_key' to extract the key in the expected format
+("AVB_pk" in the following). If the device is using a different root
+of trust than AVB_pk the --public_key_metadata option can be used to
+embed a blob ("AVB_pkmd" in the following) that can be used to
+e.g. derive AVB_pk. Both AVB_pk and AVB_pkmd is passed to the
+bootloader for validation as part of verifying a slot.
+
+To prevent rollback attacks, the rollback index should be increased on
+a regular basis. The rollback index can be set with the
+BOARD_AVB_ROLLBACK_INDEX variable:
+
+ BOARD_AVB_ROLLBACK_INDEX := 5
+
+If this is not set, the rollback index defaults to 0.
+
+The variable BOARD_AVB_MAKE_VBMETA_IMAGE_ARGS can be used to specify
+additional options passed to 'avbtool make_vbmeta_image'. Typical
+options to be used here include '--prop', '--prop_from_file', and
+'--chain_partition'.
+
+The variable BOARD_AVBTOOL_BOOT_ADD_HASH_FOOTER_ARGS can be used to
+specify additional options passed to 'avbtool add_hash_footer' for
+boot.img. Typical options to be used here include '--hash_algorithm'
+and '--salt'.
+
+The variable BOARD_AVBTOOL_SYSTEM_ADD_HASHTREE_FOOTER_ARGS can be used
+to specify additional options passed to 'avbtool add_hashtree_footer'
+for systems.img. Typical options to be used here include
+'--hash_algorithm', '--salt', and '--block_size'.
diff --git a/avb/avbtool b/avb/avbtool
new file mode 100755
index 0000000..b62bd03
--- /dev/null
+++ b/avb/avbtool
@@ -0,0 +1,3077 @@
+#!/usr/bin/env python
+
+# Copyright 2016, The Android Open Source Project
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+#
+"""Command-line tool for working with Android Verified Boot images."""
+
+import argparse
+import binascii
+import bisect
+import hashlib
+import os
+import struct
+import subprocess
+import sys
+import tempfile
+import time
+
+import Crypto.PublicKey.RSA
+
+# Keep in sync with avb_vbmeta_header.h.
+AVB_VERSION_MAJOR = 1
+AVB_VERSION_MINOR = 0
+AVB_VBMETA_IMAGE_FLAGS_HASHTREE_DISABLED = 1
+
+class AvbError(Exception):
+  """Application-specific errors.
+
+  These errors represent issues for which a stack-trace should not be
+  presented.
+
+  Attributes:
+    message: Error message.
+  """
+
+  def __init__(self, message):
+    Exception.__init__(self, message)
+
+
+class Algorithm(object):
+  """Contains details about an algorithm.
+
+  See the avb_vbmeta_header.h file for more details about
+  algorithms.
+
+  The constant |ALGORITHMS| is a dictionary from human-readable
+  names (e.g 'SHA256_RSA2048') to instances of this class.
+
+  Attributes:
+    algorithm_type: Integer code corresponding to |AvbAlgorithmType|.
+    hash_num_bytes: Number of bytes used to store the hash.
+    signature_num_bytes: Number of bytes used to store the signature.
+    public_key_num_bytes: Number of bytes used to store the public key.
+    padding: Padding used for signature, if any.
+  """
+
+  def __init__(self, algorithm_type, hash_num_bytes, signature_num_bytes,
+               public_key_num_bytes, padding):
+    self.algorithm_type = algorithm_type
+    self.hash_num_bytes = hash_num_bytes
+    self.signature_num_bytes = signature_num_bytes
+    self.public_key_num_bytes = public_key_num_bytes
+    self.padding = padding
+
+# This must be kept in sync with the avb_crypto.h file.
+#
+# The PKC1-v1.5 padding is a blob of binary DER of ASN.1 and is
+# obtained from section 5.2.2 of RFC 4880.
+ALGORITHMS = {
+    'NONE': Algorithm(
+        algorithm_type=0,        # AVB_ALGORITHM_TYPE_NONE
+        hash_num_bytes=0,
+        signature_num_bytes=0,
+        public_key_num_bytes=0,
+        padding=[]),
+    'SHA256_RSA2048': Algorithm(
+        algorithm_type=1,        # AVB_ALGORITHM_TYPE_SHA256_RSA2048
+        hash_num_bytes=32,
+        signature_num_bytes=256,
+        public_key_num_bytes=8 + 2*2048/8,
+        padding=[
+            # PKCS1-v1_5 padding
+            0x00, 0x01] + [0xff]*202 + [0x00] + [
+                # ASN.1 header
+                0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
+                0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05,
+                0x00, 0x04, 0x20,
+            ]),
+    'SHA256_RSA4096': Algorithm(
+        algorithm_type=2,        # AVB_ALGORITHM_TYPE_SHA256_RSA4096
+        hash_num_bytes=32,
+        signature_num_bytes=512,
+        public_key_num_bytes=8 + 2*4096/8,
+        padding=[
+            # PKCS1-v1_5 padding
+            0x00, 0x01] + [0xff]*458 + [0x00] + [
+                # ASN.1 header
+                0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
+                0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05,
+                0x00, 0x04, 0x20,
+            ]),
+    'SHA256_RSA8192': Algorithm(
+        algorithm_type=3,        # AVB_ALGORITHM_TYPE_SHA256_RSA8192
+        hash_num_bytes=32,
+        signature_num_bytes=1024,
+        public_key_num_bytes=8 + 2*8192/8,
+        padding=[
+            # PKCS1-v1_5 padding
+            0x00, 0x01] + [0xff]*970 + [0x00] + [
+                # ASN.1 header
+                0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
+                0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05,
+                0x00, 0x04, 0x20,
+            ]),
+    'SHA512_RSA2048': Algorithm(
+        algorithm_type=4,        # AVB_ALGORITHM_TYPE_SHA512_RSA2048
+        hash_num_bytes=64,
+        signature_num_bytes=256,
+        public_key_num_bytes=8 + 2*2048/8,
+        padding=[
+            # PKCS1-v1_5 padding
+            0x00, 0x01] + [0xff]*170 + [0x00] + [
+                # ASN.1 header
+                0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
+                0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05,
+                0x00, 0x04, 0x40
+            ]),
+    'SHA512_RSA4096': Algorithm(
+        algorithm_type=5,        # AVB_ALGORITHM_TYPE_SHA512_RSA4096
+        hash_num_bytes=64,
+        signature_num_bytes=512,
+        public_key_num_bytes=8 + 2*4096/8,
+        padding=[
+            # PKCS1-v1_5 padding
+            0x00, 0x01] + [0xff]*426 + [0x00] + [
+                # ASN.1 header
+                0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
+                0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05,
+                0x00, 0x04, 0x40
+            ]),
+    'SHA512_RSA8192': Algorithm(
+        algorithm_type=6,        # AVB_ALGORITHM_TYPE_SHA512_RSA8192
+        hash_num_bytes=64,
+        signature_num_bytes=1024,
+        public_key_num_bytes=8 + 2*8192/8,
+        padding=[
+            # PKCS1-v1_5 padding
+            0x00, 0x01] + [0xff]*938 + [0x00] + [
+                # ASN.1 header
+                0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
+                0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05,
+                0x00, 0x04, 0x40
+            ]),
+}
+
+
+def round_to_multiple(number, size):
+  """Rounds a number up to nearest multiple of another number.
+
+  Args:
+    number: The number to round up.
+    size: The multiple to round up to.
+
+  Returns:
+    If |number| is a multiple of |size|, returns |number|, otherwise
+    returns |number| + |size|.
+  """
+  remainder = number % size
+  if remainder == 0:
+    return number
+  return number + size - remainder
+
+
+def round_to_pow2(number):
+  """Rounds a number up to the next power of 2.
+
+  Args:
+    number: The number to round up.
+
+  Returns:
+    If |number| is already a power of 2 then |number| is
+    returned. Otherwise the smallest power of 2 greater than |number|
+    is returned.
+  """
+  return 2**((number - 1).bit_length())
+
+
+def write_long(output, num_bits, value):
+  """Writes a long to an output stream using a given amount of bits.
+
+  This number is written big-endian, e.g. with the most significant
+  bit first.
+
+  Arguments:
+    output: The object to write the output to.
+    num_bits: The number of bits to write, e.g. 2048.
+    value: The value to write.
+  """
+  for bit_pos in range(num_bits, 0, -8):
+    octet = (value >> (bit_pos - 8)) & 0xff
+    output.write(struct.pack('!B', octet))
+
+
+def encode_long(num_bits, value):
+  """Encodes a long to a bytearray() using a given amount of bits.
+
+  This number is written big-endian, e.g. with the most significant
+  bit first.
+
+  Arguments:
+    num_bits: The number of bits to write, e.g. 2048.
+    value: The value to write.
+
+  Returns:
+    A bytearray() with the encoded long.
+  """
+  ret = bytearray()
+  for bit_pos in range(num_bits, 0, -8):
+    octet = (value >> (bit_pos - 8)) & 0xff
+    ret.extend(struct.pack('!B', octet))
+  return ret
+
+
+def egcd(a, b):
+  """Calculate greatest common divisor of two numbers.
+
+  This implementation uses a recursive version of the extended
+  Euclidian algorithm.
+
+  Arguments:
+    a: First number.
+    b: Second number.
+
+  Returns:
+    A tuple (gcd, x, y) that where |gcd| is the greatest common
+    divisor of |a| and |b| and |a|*|x| + |b|*|y| = |gcd|.
+  """
+  if a == 0:
+    return (b, 0, 1)
+  else:
+    g, y, x = egcd(b % a, a)
+    return (g, x - (b // a) * y, y)
+
+
+def modinv(a, m):
+  """Calculate modular multiplicative inverse of |a| modulo |m|.
+
+  This calculates the number |x| such that |a| * |x| == 1 (modulo
+  |m|). This number only exists if |a| and |m| are co-prime - |None|
+  is returned if this isn't true.
+
+  Arguments:
+    a: The number to calculate a modular inverse of.
+    m: The modulo to use.
+
+  Returns:
+    The modular multiplicative inverse of |a| and |m| or |None| if
+    these numbers are not co-prime.
+  """
+  gcd, x, _ = egcd(a, m)
+  if gcd != 1:
+    return None  # modular inverse does not exist
+  else:
+    return x % m
+
+
+def parse_number(string):
+  """Parse a string as a number.
+
+  This is just a short-hand for int(string, 0) suitable for use in the
+  |type| parameter of |ArgumentParser|'s add_argument() function. An
+  improvement to just using type=int is that this function supports
+  numbers in other bases, e.g. "0x1234".
+
+  Arguments:
+    string: The string to parse.
+
+  Returns:
+    The parsed integer.
+
+  Raises:
+    ValueError: If the number could not be parsed.
+  """
+  return int(string, 0)
+
+
+def write_rsa_key(output, key):
+  """Writes a public RSA key in |AvbRSAPublicKeyHeader| format.
+
+  This writes the |AvbRSAPublicKeyHeader| as well as the two large
+  numbers (|key_num_bits| bits long) following it.
+
+  Arguments:
+    output: The object to write the output to.
+    key: A Crypto.PublicKey.RSA object.
+  """
+  # key.e is exponent
+  # key.n is modulus
+  key_num_bits = key.size() + 1
+  # Calculate n0inv = -1/n[0] (mod 2^32)
+  b = 2L**32
+  n0inv = b - modinv(key.n, b)
+  # Calculate rr = r^2 (mod N), where r = 2^(# of key bits)
+  r = 2L**key.n.bit_length()
+  rrmodn = r * r % key.n
+  output.write(struct.pack('!II', key_num_bits, n0inv))
+  write_long(output, key_num_bits, key.n)
+  write_long(output, key_num_bits, rrmodn)
+
+
+def encode_rsa_key(key):
+  """Encodes a public RSA key in |AvbRSAPublicKeyHeader| format.
+
+  This creates a |AvbRSAPublicKeyHeader| as well as the two large
+  numbers (|key_num_bits| bits long) following it.
+
+  Arguments:
+    key: A Crypto.PublicKey.RSA object.
+
+  Returns:
+    A bytearray() with the |AvbRSAPublicKeyHeader|.
+  """
+  ret = bytearray()
+  # key.e is exponent
+  # key.n is modulus
+  key_num_bits = key.size() + 1
+  # Calculate n0inv = -1/n[0] (mod 2^32)
+  b = 2L**32
+  n0inv = b - modinv(key.n, b)
+  # Calculate rr = r^2 (mod N), where r = 2^(# of key bits)
+  r = 2L**key.n.bit_length()
+  rrmodn = r * r % key.n
+  ret.extend(struct.pack('!II', key_num_bits, n0inv))
+  ret.extend(encode_long(key_num_bits, key.n))
+  ret.extend(encode_long(key_num_bits, rrmodn))
+  return ret
+
+
+def lookup_algorithm_by_type(alg_type):
+  """Looks up algorithm by type.
+
+  Arguments:
+    alg_type: The integer representing the type.
+
+  Returns:
+    A tuple with the algorithm name and an |Algorithm| instance.
+
+  Raises:
+    Exception: If the algorithm cannot be found
+  """
+  for alg_name in ALGORITHMS:
+    alg_data = ALGORITHMS[alg_name]
+    if alg_data.algorithm_type == alg_type:
+      return (alg_name, alg_data)
+  raise AvbError('Unknown algorithm type {}'.format(alg_type))
+
+
+def raw_sign(signing_helper, algorithm_name, key_path, raw_data_to_sign):
+  """Computes a raw RSA signature using |signing_helper| or openssl.
+
+  Arguments:
+    signing_helper: Program which signs a hash and returns the signature.
+    algorithm_name: The algorithm name as per the ALGORITHMS dict.
+    key_path: Path to the private key file. Must be PEM format.
+    raw_data_to_sign: Data to sign (bytearray or str expected).
+
+  Returns:
+    A bytearray containing the signature.
+
+  Raises:
+    Exception: If an error occurs.
+  """
+  p = None
+  if signing_helper is not None:
+      p = subprocess.Popen(
+          [signing_helper, algorithm_name, key_path],
+          stdin=subprocess.PIPE,
+          stdout=subprocess.PIPE,
+          stderr=subprocess.PIPE)
+  else:
+      p = subprocess.Popen(
+          ['openssl', 'rsautl', '-sign', '-inkey', key_path, '-raw'],
+          stdin=subprocess.PIPE,
+          stdout=subprocess.PIPE,
+          stderr=subprocess.PIPE)
+  (pout, perr) = p.communicate(str(raw_data_to_sign))
+  retcode = p.wait()
+  if retcode != 0:
+    raise AvbError('Error signing: {}'.format(perr))
+  return bytearray(pout)
+
+
+class ImageChunk(object):
+  """Data structure used for representing chunks in Android sparse files.
+
+  Attributes:
+    chunk_type: One of TYPE_RAW, TYPE_FILL, or TYPE_DONT_CARE.
+    chunk_offset: Offset in the sparse file where this chunk begins.
+    output_offset: Offset in de-sparsified file where output begins.
+    output_size: Number of bytes in output.
+    input_offset: Offset in sparse file for data if TYPE_RAW otherwise None.
+    fill_data: Blob with data to fill if TYPE_FILL otherwise None.
+  """
+
+  FORMAT = '<2H2I'
+  TYPE_RAW = 0xcac1
+  TYPE_FILL = 0xcac2
+  TYPE_DONT_CARE = 0xcac3
+  TYPE_CRC32 = 0xcac4
+
+  def __init__(self, chunk_type, chunk_offset, output_offset, output_size,
+               input_offset, fill_data):
+    """Initializes an ImageChunk object.
+
+    Arguments:
+      chunk_type: One of TYPE_RAW, TYPE_FILL, or TYPE_DONT_CARE.
+      chunk_offset: Offset in the sparse file where this chunk begins.
+      output_offset: Offset in de-sparsified file.
+      output_size: Number of bytes in output.
+      input_offset: Offset in sparse file if TYPE_RAW otherwise None.
+      fill_data: Blob with data to fill if TYPE_FILL otherwise None.
+
+    Raises:
+      ValueError: If data is not well-formed.
+    """
+    self.chunk_type = chunk_type
+    self.chunk_offset = chunk_offset
+    self.output_offset = output_offset
+    self.output_size = output_size
+    self.input_offset = input_offset
+    self.fill_data = fill_data
+    # Check invariants.
+    if self.chunk_type == self.TYPE_RAW:
+      if self.fill_data is not None:
+        raise ValueError('RAW chunk cannot have fill_data set.')
+      if not self.input_offset:
+        raise ValueError('RAW chunk must have input_offset set.')
+    elif self.chunk_type == self.TYPE_FILL:
+      if self.fill_data is None:
+        raise ValueError('FILL chunk must have fill_data set.')
+      if self.input_offset:
+        raise ValueError('FILL chunk cannot have input_offset set.')
+    elif self.chunk_type == self.TYPE_DONT_CARE:
+      if self.fill_data is not None:
+        raise ValueError('DONT_CARE chunk cannot have fill_data set.')
+      if self.input_offset:
+        raise ValueError('DONT_CARE chunk cannot have input_offset set.')
+    else:
+      raise ValueError('Invalid chunk type')
+
+
+class ImageHandler(object):
+  """Abstraction for image I/O with support for Android sparse images.
+
+  This class provides an interface for working with image files that
+  may be using the Android Sparse Image format. When an instance is
+  constructed, we test whether it's an Android sparse file. If so,
+  operations will be on the sparse file by interpreting the sparse
+  format, otherwise they will be directly on the file. Either way the
+  operations do the same.
+
+  For reading, this interface mimics a file object - it has seek(),
+  tell(), and read() methods. For writing, only truncation
+  (truncate()) and appending is supported (append_raw() and
+  append_dont_care()). Additionally, data can only be written in units
+  of the block size.
+
+  Attributes:
+    is_sparse: Whether the file being operated on is sparse.
+    block_size: The block size, typically 4096.
+    image_size: The size of the unsparsified file.
+  """
+  # See system/core/libsparse/sparse_format.h for details.
+  MAGIC = 0xed26ff3a
+  HEADER_FORMAT = '<I4H4I'
+
+  # These are formats and offset of just the |total_chunks| and
+  # |total_blocks| fields.
+  NUM_CHUNKS_AND_BLOCKS_FORMAT = '<II'
+  NUM_CHUNKS_AND_BLOCKS_OFFSET = 16
+
+  def __init__(self, image_filename):
+    """Initializes an image handler.
+
+    Arguments:
+      image_filename: The name of the file to operate on.
+
+    Raises:
+      ValueError: If data in the file is invalid.
+    """
+    self._image_filename = image_filename
+    self._read_header()
+
+  def _read_header(self):
+    """Initializes internal data structures used for reading file.
+
+    This may be called multiple times and is typically called after
+    modifying the file (e.g. appending, truncation).
+
+    Raises:
+      ValueError: If data in the file is invalid.
+    """
+    self.is_sparse = False
+    self.block_size = 4096
+    self._file_pos = 0
+    self._image = open(self._image_filename, 'r+b')
+    self._image.seek(0, os.SEEK_END)
+    self.image_size = self._image.tell()
+
+    self._image.seek(0, os.SEEK_SET)
+    header_bin = self._image.read(struct.calcsize(self.HEADER_FORMAT))
+    (magic, major_version, minor_version, file_hdr_sz, chunk_hdr_sz,
+     block_size, self._num_total_blocks, self._num_total_chunks,
+     _) = struct.unpack(self.HEADER_FORMAT, header_bin)
+    if magic != self.MAGIC:
+      # Not a sparse image, our job here is done.
+      return
+    if not (major_version == 1 and minor_version == 0):
+      raise ValueError('Encountered sparse image format version {}.{} but '
+                       'only 1.0 is supported'.format(major_version,
+                                                      minor_version))
+    if file_hdr_sz != struct.calcsize(self.HEADER_FORMAT):
+      raise ValueError('Unexpected file_hdr_sz value {}.'.
+                       format(file_hdr_sz))
+    if chunk_hdr_sz != struct.calcsize(ImageChunk.FORMAT):
+      raise ValueError('Unexpected chunk_hdr_sz value {}.'.
+                       format(chunk_hdr_sz))
+
+    self.block_size = block_size
+
+    # Build an list of chunks by parsing the file.
+    self._chunks = []
+
+    # Find the smallest offset where only "Don't care" chunks
+    # follow. This will be the size of the content in the sparse
+    # image.
+    offset = 0
+    output_offset = 0
+    for _ in xrange(1, self._num_total_chunks + 1):
+      chunk_offset = self._image.tell()
+
+      header_bin = self._image.read(struct.calcsize(ImageChunk.FORMAT))
+      (chunk_type, _, chunk_sz, total_sz) = struct.unpack(ImageChunk.FORMAT,
+                                                          header_bin)
+      data_sz = total_sz - struct.calcsize(ImageChunk.FORMAT)
+
+      if chunk_type == ImageChunk.TYPE_RAW:
+        if data_sz != (chunk_sz * self.block_size):
+          raise ValueError('Raw chunk input size ({}) does not match output '
+                           'size ({})'.
+                           format(data_sz, chunk_sz*self.block_size))
+        self._chunks.append(ImageChunk(ImageChunk.TYPE_RAW,
+                                       chunk_offset,
+                                       output_offset,
+                                       chunk_sz*self.block_size,
+                                       self._image.tell(),
+                                       None))
+        self._image.read(data_sz)
+
+      elif chunk_type == ImageChunk.TYPE_FILL:
+        if data_sz != 4:
+          raise ValueError('Fill chunk should have 4 bytes of fill, but this '
+                           'has {}'.format(data_sz))
+        fill_data = self._image.read(4)
+        self._chunks.append(ImageChunk(ImageChunk.TYPE_FILL,
+                                       chunk_offset,
+                                       output_offset,
+                                       chunk_sz*self.block_size,
+                                       None,
+                                       fill_data))
+      elif chunk_type == ImageChunk.TYPE_DONT_CARE:
+        if data_sz != 0:
+          raise ValueError('Don\'t care chunk input size is non-zero ({})'.
+                           format(data_sz))
+        self._chunks.append(ImageChunk(ImageChunk.TYPE_DONT_CARE,
+                                       chunk_offset,
+                                       output_offset,
+                                       chunk_sz*self.block_size,
+                                       None,
+                                       None))
+      elif chunk_type == ImageChunk.TYPE_CRC32:
+        if data_sz != 4:
+          raise ValueError('CRC32 chunk should have 4 bytes of CRC, but '
+                           'this has {}'.format(data_sz))
+        self._image.read(4)
+      else:
+        raise ValueError('Unknown chunk type {}'.format(chunk_type))
+
+      offset += chunk_sz
+      output_offset += chunk_sz*self.block_size
+
+    # Record where sparse data end.
+    self._sparse_end = self._image.tell()
+
+    # Now that we've traversed all chunks, sanity check.
+    if self._num_total_blocks != offset:
+      raise ValueError('The header said we should have {} output blocks, '
+                       'but we saw {}'.format(self._num_total_blocks, offset))
+    junk_len = len(self._image.read())
+    if junk_len > 0:
+      raise ValueError('There were {} bytes of extra data at the end of the '
+                       'file.'.format(junk_len))
+
+    # Assign |image_size|.
+    self.image_size = output_offset
+
+    # This is used when bisecting in read() to find the initial slice.
+    self._chunk_output_offsets = [i.output_offset for i in self._chunks]
+
+    self.is_sparse = True
+
+  def _update_chunks_and_blocks(self):
+    """Helper function to update the image header.
+
+    The the |total_chunks| and |total_blocks| fields in the header
+    will be set to value of the |_num_total_blocks| and
+    |_num_total_chunks| attributes.
+
+    """
+    self._image.seek(self.NUM_CHUNKS_AND_BLOCKS_OFFSET, os.SEEK_SET)
+    self._image.write(struct.pack(self.NUM_CHUNKS_AND_BLOCKS_FORMAT,
+                                  self._num_total_blocks,
+                                  self._num_total_chunks))
+
+  def append_dont_care(self, num_bytes):
+    """Appends a DONT_CARE chunk to the sparse file.
+
+    The given number of bytes must be a multiple of the block size.
+
+    Arguments:
+      num_bytes: Size in number of bytes of the DONT_CARE chunk.
+    """
+    assert num_bytes % self.block_size == 0
+
+    if not self.is_sparse:
+      self._image.seek(0, os.SEEK_END)
+      # This is more efficient that writing NUL bytes since it'll add
+      # a hole on file systems that support sparse files (native
+      # sparse, not Android sparse).
+      self._image.truncate(self._image.tell() + num_bytes)
+      self._read_header()
+      return
+
+    self._num_total_chunks += 1
+    self._num_total_blocks += num_bytes / self.block_size
+    self._update_chunks_and_blocks()
+
+    self._image.seek(self._sparse_end, os.SEEK_SET)
+    self._image.write(struct.pack(ImageChunk.FORMAT,
+                                  ImageChunk.TYPE_DONT_CARE,
+                                  0,  # Reserved
+                                  num_bytes / self.block_size,
+                                  struct.calcsize(ImageChunk.FORMAT)))
+    self._read_header()
+
+  def append_raw(self, data):
+    """Appends a RAW chunk to the sparse file.
+
+    The length of the given data must be a multiple of the block size.
+
+    Arguments:
+      data: Data to append.
+    """
+    assert len(data) % self.block_size == 0
+
+    if not self.is_sparse:
+      self._image.seek(0, os.SEEK_END)
+      self._image.write(data)
+      self._read_header()
+      return
+
+    self._num_total_chunks += 1
+    self._num_total_blocks += len(data) / self.block_size
+    self._update_chunks_and_blocks()
+
+    self._image.seek(self._sparse_end, os.SEEK_SET)
+    self._image.write(struct.pack(ImageChunk.FORMAT,
+                                  ImageChunk.TYPE_RAW,
+                                  0,  # Reserved
+                                  len(data) / self.block_size,
+                                  len(data) +
+                                  struct.calcsize(ImageChunk.FORMAT)))
+    self._image.write(data)
+    self._read_header()
+
+  def append_fill(self, fill_data, size):
+    """Appends a fill chunk to the sparse file.
+
+    The total length of the fill data must be a multiple of the block size.
+
+    Arguments:
+      fill_data: Fill data to append - must be four bytes.
+      size: Number of chunk - must be a multiple of four and the block size.
+    """
+    assert len(fill_data) == 4
+    assert size % 4 == 0
+    assert size % self.block_size == 0
+
+    if not self.is_sparse:
+      self._image.seek(0, os.SEEK_END)
+      self._image.write(fill_data * (size/4))
+      self._read_header()
+      return
+
+    self._num_total_chunks += 1
+    self._num_total_blocks += size / self.block_size
+    self._update_chunks_and_blocks()
+
+    self._image.seek(self._sparse_end, os.SEEK_SET)
+    self._image.write(struct.pack(ImageChunk.FORMAT,
+                                  ImageChunk.TYPE_FILL,
+                                  0,  # Reserved
+                                  size / self.block_size,
+                                  4 + struct.calcsize(ImageChunk.FORMAT)))
+    self._image.write(fill_data)
+    self._read_header()
+
+  def seek(self, offset):
+    """Sets the cursor position for reading from unsparsified file.
+
+    Arguments:
+      offset: Offset to seek to from the beginning of the file.
+    """
+    self._file_pos = offset
+
+  def read(self, size):
+    """Reads data from the unsparsified file.
+
+    This method may return fewer than |size| bytes of data if the end
+    of the file was encountered.
+
+    The file cursor for reading is advanced by the number of bytes
+    read.
+
+    Arguments:
+      size: Number of bytes to read.
+
+    Returns:
+      The data.
+
+    """
+    if not self.is_sparse:
+      self._image.seek(self._file_pos)
+      data = self._image.read(size)
+      self._file_pos += len(data)
+      return data
+
+    # Iterate over all chunks.
+    chunk_idx = bisect.bisect_right(self._chunk_output_offsets,
+                                    self._file_pos) - 1
+    data = bytearray()
+    to_go = size
+    while to_go > 0:
+      chunk = self._chunks[chunk_idx]
+      chunk_pos_offset = self._file_pos - chunk.output_offset
+      chunk_pos_to_go = min(chunk.output_size - chunk_pos_offset, to_go)
+
+      if chunk.chunk_type == ImageChunk.TYPE_RAW:
+        self._image.seek(chunk.input_offset + chunk_pos_offset)
+        data.extend(self._image.read(chunk_pos_to_go))
+      elif chunk.chunk_type == ImageChunk.TYPE_FILL:
+        all_data = chunk.fill_data*(chunk_pos_to_go/len(chunk.fill_data) + 2)
+        offset_mod = chunk_pos_offset % len(chunk.fill_data)
+        data.extend(all_data[offset_mod:(offset_mod + chunk_pos_to_go)])
+      else:
+        assert chunk.chunk_type == ImageChunk.TYPE_DONT_CARE
+        data.extend('\0' * chunk_pos_to_go)
+
+      to_go -= chunk_pos_to_go
+      self._file_pos += chunk_pos_to_go
+      chunk_idx += 1
+      # Generate partial read in case of EOF.
+      if chunk_idx >= len(self._chunks):
+        break
+
+    return data
+
+  def tell(self):
+    """Returns the file cursor position for reading from unsparsified file.
+
+    Returns:
+      The file cursor position for reading.
+    """
+    return self._file_pos
+
+  def truncate(self, size):
+    """Truncates the unsparsified file.
+
+    Arguments:
+      size: Desired size of unsparsified file.
+
+    Raises:
+      ValueError: If desired size isn't a multiple of the block size.
+    """
+    if not self.is_sparse:
+      self._image.truncate(size)
+      self._read_header()
+      return
+
+    if size % self.block_size != 0:
+      raise ValueError('Cannot truncate to a size which is not a multiple '
+                       'of the block size')
+
+    if size == self.image_size:
+      # Trivial where there's nothing to do.
+      return
+    elif size < self.image_size:
+      chunk_idx = bisect.bisect_right(self._chunk_output_offsets, size) - 1
+      chunk = self._chunks[chunk_idx]
+      if chunk.output_offset != size:
+        # Truncation in the middle of a trunk - need to keep the chunk
+        # and modify it.
+        chunk_idx_for_update = chunk_idx + 1
+        num_to_keep = size - chunk.output_offset
+        assert num_to_keep % self.block_size == 0
+        if chunk.chunk_type == ImageChunk.TYPE_RAW:
+          truncate_at = (chunk.chunk_offset +
+                         struct.calcsize(ImageChunk.FORMAT) + num_to_keep)
+          data_sz = num_to_keep
+        elif chunk.chunk_type == ImageChunk.TYPE_FILL:
+          truncate_at = (chunk.chunk_offset +
+                         struct.calcsize(ImageChunk.FORMAT) + 4)
+          data_sz = 4
+        else:
+          assert chunk.chunk_type == ImageChunk.TYPE_DONT_CARE
+          truncate_at = chunk.chunk_offset + struct.calcsize(ImageChunk.FORMAT)
+          data_sz = 0
+        chunk_sz = num_to_keep/self.block_size
+        total_sz = data_sz + struct.calcsize(ImageChunk.FORMAT)
+        self._image.seek(chunk.chunk_offset)
+        self._image.write(struct.pack(ImageChunk.FORMAT,
+                                      chunk.chunk_type,
+                                      0,  # Reserved
+                                      chunk_sz,
+                                      total_sz))
+        chunk.output_size = num_to_keep
+      else:
+        # Truncation at trunk boundary.
+        truncate_at = chunk.chunk_offset
+        chunk_idx_for_update = chunk_idx
+
+      self._num_total_chunks = chunk_idx_for_update
+      self._num_total_blocks = 0
+      for i in range(0, chunk_idx_for_update):
+        self._num_total_blocks += self._chunks[i].output_size / self.block_size
+      self._update_chunks_and_blocks()
+      self._image.truncate(truncate_at)
+
+      # We've modified the file so re-read all data.
+      self._read_header()
+    else:
+      # Truncating to grow - just add a DONT_CARE section.
+      self.append_dont_care(size - self.image_size)
+
+
+class AvbDescriptor(object):
+  """Class for AVB descriptor.
+
+  See the |AvbDescriptor| C struct for more information.
+
+  Attributes:
+    tag: The tag identifying what kind of descriptor this is.
+    data: The data in the descriptor.
+  """
+
+  SIZE = 16
+  FORMAT_STRING = ('!QQ')  # tag, num_bytes_following (descriptor header)
+
+  def __init__(self, data):
+    """Initializes a new property descriptor.
+
+    Arguments:
+      data: If not None, must be a bytearray().
+
+    Raises:
+      LookupError: If the given descriptor is malformed.
+    """
+    assert struct.calcsize(self.FORMAT_STRING) == self.SIZE
+
+    if data:
+      (self.tag, num_bytes_following) = (
+          struct.unpack(self.FORMAT_STRING, data[0:self.SIZE]))
+      self.data = data[self.SIZE:self.SIZE + num_bytes_following]
+    else:
+      self.tag = None
+      self.data = None
+
+  def print_desc(self, o):
+    """Print the descriptor.
+
+    Arguments:
+      o: The object to write the output to.
+    """
+    o.write('    Unknown descriptor:\n')
+    o.write('      Tag:  {}\n'.format(self.tag))
+    if len(self.data) < 256:
+      o.write('      Data: {} ({} bytes)\n'.format(
+          repr(str(self.data)), len(self.data)))
+    else:
+      o.write('      Data: {} bytes\n'.format(len(self.data)))
+
+  def encode(self):
+    """Serializes the descriptor.
+
+    Returns:
+      A bytearray() with the descriptor data.
+    """
+    num_bytes_following = len(self.data)
+    nbf_with_padding = round_to_multiple(num_bytes_following, 8)
+    padding_size = nbf_with_padding - num_bytes_following
+    desc = struct.pack(self.FORMAT_STRING, self.tag, nbf_with_padding)
+    padding = struct.pack(str(padding_size) + 'x')
+    ret = desc + self.data + padding
+    return bytearray(ret)
+
+
+class AvbPropertyDescriptor(AvbDescriptor):
+  """A class for property descriptors.
+
+  See the |AvbPropertyDescriptor| C struct for more information.
+
+  Attributes:
+    key: The key.
+    value: The key.
+  """
+
+  TAG = 0
+  SIZE = 32
+  FORMAT_STRING = ('!QQ'  # tag, num_bytes_following (descriptor header)
+                   'Q'  # key size (bytes)
+                   'Q')  # value size (bytes)
+
+  def __init__(self, data=None):
+    """Initializes a new property descriptor.
+
+    Arguments:
+      data: If not None, must be a bytearray of size |SIZE|.
+
+    Raises:
+      LookupError: If the given descriptor is malformed.
+    """
+    AvbDescriptor.__init__(self, None)
+    assert struct.calcsize(self.FORMAT_STRING) == self.SIZE
+
+    if data:
+      (tag, num_bytes_following, key_size,
+       value_size) = struct.unpack(self.FORMAT_STRING, data[0:self.SIZE])
+      expected_size = round_to_multiple(
+          self.SIZE - 16 + key_size + 1 + value_size + 1, 8)
+      if tag != self.TAG or num_bytes_following != expected_size:
+        raise LookupError('Given data does not look like a property '
+                          'descriptor.')
+      self.key = data[self.SIZE:(self.SIZE + key_size)]
+      self.value = data[(self.SIZE + key_size + 1):(self.SIZE + key_size + 1 +
+                                                    value_size)]
+    else:
+      self.key = ''
+      self.value = ''
+
+  def print_desc(self, o):
+    """Print the descriptor.
+
+    Arguments:
+      o: The object to write the output to.
+    """
+    if len(self.value) < 256:
+      o.write('    Prop: {} -> {}\n'.format(self.key, repr(str(self.value))))
+    else:
+      o.write('    Prop: {} -> ({} bytes)\n'.format(self.key, len(self.value)))
+
+  def encode(self):
+    """Serializes the descriptor.
+
+    Returns:
+      A bytearray() with the descriptor data.
+    """
+    num_bytes_following = self.SIZE + len(self.key) + len(self.value) + 2 - 16
+    nbf_with_padding = round_to_multiple(num_bytes_following, 8)
+    padding_size = nbf_with_padding - num_bytes_following
+    desc = struct.pack(self.FORMAT_STRING, self.TAG, nbf_with_padding,
+                       len(self.key), len(self.value))
+    padding = struct.pack(str(padding_size) + 'x')
+    ret = desc + self.key + '\0' + self.value + '\0' + padding
+    return bytearray(ret)
+
+
+class AvbHashtreeDescriptor(AvbDescriptor):
+  """A class for hashtree descriptors.
+
+  See the |AvbHashtreeDescriptor| C struct for more information.
+
+  Attributes:
+    dm_verity_version: dm-verity version used.
+    image_size: Size of the image, after rounding up to |block_size|.
+    tree_offset: Offset of the hash tree in the file.
+    tree_size: Size of the tree.
+    data_block_size: Data block size
+    hash_block_size: Hash block size
+    fec_num_roots: Number of roots used for FEC (0 if FEC is not used).
+    fec_offset: Offset of FEC data (0 if FEC is not used).
+    fec_size: Size of FEC data (0 if FEC is not used).
+    hash_algorithm: Hash algorithm used.
+    partition_name: Partition name.
+    salt: Salt used.
+    root_digest: Root digest.
+  """
+
+  TAG = 1
+  RESERVED = 64
+  SIZE = 116 + RESERVED
+  FORMAT_STRING = ('!QQ'  # tag, num_bytes_following (descriptor header)
+                   'L'  # dm-verity version used
+                   'Q'  # image size (bytes)
+                   'Q'  # tree offset (bytes)
+                   'Q'  # tree size (bytes)
+                   'L'  # data block size (bytes)
+                   'L'  # hash block size (bytes)
+                   'L'  # FEC number of roots
+                   'Q'  # FEC offset (bytes)
+                   'Q'  # FEC size (bytes)
+                   '32s'  # hash algorithm used
+                   'L'  # partition name (bytes)
+                   'L'  # salt length (bytes)
+                   'L' +  # root digest length (bytes)
+                   str(RESERVED) + 's')  # reserved
+
+  def __init__(self, data=None):
+    """Initializes a new hashtree descriptor.
+
+    Arguments:
+      data: If not None, must be a bytearray of size |SIZE|.
+
+    Raises:
+      LookupError: If the given descriptor is malformed.
+    """
+    AvbDescriptor.__init__(self, None)
+    assert struct.calcsize(self.FORMAT_STRING) == self.SIZE
+
+    if data:
+      (tag, num_bytes_following, self.dm_verity_version, self.image_size,
+       self.tree_offset, self.tree_size, self.data_block_size,
+       self.hash_block_size, self.fec_num_roots, self.fec_offset, self.fec_size,
+       self.hash_algorithm, partition_name_len, salt_len,
+       root_digest_len, _) = struct.unpack(self.FORMAT_STRING,
+                                           data[0:self.SIZE])
+      expected_size = round_to_multiple(
+          self.SIZE - 16 + partition_name_len + salt_len + root_digest_len, 8)
+      if tag != self.TAG or num_bytes_following != expected_size:
+        raise LookupError('Given data does not look like a hashtree '
+                          'descriptor.')
+      # Nuke NUL-bytes at the end.
+      self.hash_algorithm = self.hash_algorithm.split('\0', 1)[0]
+      o = 0
+      self.partition_name = str(data[(self.SIZE + o):(self.SIZE + o +
+                                                      partition_name_len)])
+      # Validate UTF-8 - decode() raises UnicodeDecodeError if not valid UTF-8.
+      self.partition_name.decode('utf-8')
+      o += partition_name_len
+      self.salt = data[(self.SIZE + o):(self.SIZE + o + salt_len)]
+      o += salt_len
+      self.root_digest = data[(self.SIZE + o):(self.SIZE + o + root_digest_len)]
+      if root_digest_len != len(hashlib.new(name=self.hash_algorithm).digest()):
+        raise LookupError('root_digest_len doesn\'t match hash algorithm')
+
+    else:
+      self.dm_verity_version = 0
+      self.image_size = 0
+      self.tree_offset = 0
+      self.tree_size = 0
+      self.data_block_size = 0
+      self.hash_block_size = 0
+      self.fec_num_roots = 0
+      self.fec_offset = 0
+      self.fec_size = 0
+      self.hash_algorithm = ''
+      self.partition_name = ''
+      self.salt = bytearray()
+      self.root_digest = bytearray()
+
+  def print_desc(self, o):
+    """Print the descriptor.
+
+    Arguments:
+      o: The object to write the output to.
+    """
+    o.write('    Hashtree descriptor:\n')
+    o.write('      Version of dm-verity:  {}\n'.format(self.dm_verity_version))
+    o.write('      Image Size:            {} bytes\n'.format(self.image_size))
+    o.write('      Tree Offset:           {}\n'.format(self.tree_offset))
+    o.write('      Tree Size:             {} bytes\n'.format(self.tree_size))
+    o.write('      Data Block Size:       {} bytes\n'.format(
+        self.data_block_size))
+    o.write('      Hash Block Size:       {} bytes\n'.format(
+        self.hash_block_size))
+    o.write('      FEC num roots:         {}\n'.format(self.fec_num_roots))
+    o.write('      FEC offset:            {}\n'.format(self.fec_offset))
+    o.write('      FEC size:              {} bytes\n'.format(self.fec_size))
+    o.write('      Hash Algorithm:        {}\n'.format(self.hash_algorithm))
+    o.write('      Partition Name:        {}\n'.format(self.partition_name))
+    o.write('      Salt:                  {}\n'.format(str(self.salt).encode(
+        'hex')))
+    o.write('      Root Digest:           {}\n'.format(str(
+        self.root_digest).encode('hex')))
+
+  def encode(self):
+    """Serializes the descriptor.
+
+    Returns:
+      A bytearray() with the descriptor data.
+    """
+    encoded_name = self.partition_name.encode('utf-8')
+    num_bytes_following = (self.SIZE + len(encoded_name) + len(self.salt) +
+                           len(self.root_digest) - 16)
+    nbf_with_padding = round_to_multiple(num_bytes_following, 8)
+    padding_size = nbf_with_padding - num_bytes_following
+    desc = struct.pack(self.FORMAT_STRING, self.TAG, nbf_with_padding,
+                       self.dm_verity_version, self.image_size,
+                       self.tree_offset, self.tree_size, self.data_block_size,
+                       self.hash_block_size, self.fec_num_roots,
+                       self.fec_offset, self.fec_size, self.hash_algorithm,
+                       len(encoded_name), len(self.salt), len(self.root_digest),
+                       self.RESERVED*'\0')
+    padding = struct.pack(str(padding_size) + 'x')
+    ret = desc + encoded_name + self.salt + self.root_digest + padding
+    return bytearray(ret)
+
+
+class AvbHashDescriptor(AvbDescriptor):
+  """A class for hash descriptors.
+
+  See the |AvbHashDescriptor| C struct for more information.
+
+  Attributes:
+    image_size: Image size, in bytes.
+    hash_algorithm: Hash algorithm used.
+    partition_name: Partition name.
+    salt: Salt used.
+    digest: The hash value of salt and data combined.
+  """
+
+  TAG = 2
+  RESERVED = 64
+  SIZE = 68 + RESERVED
+  FORMAT_STRING = ('!QQ'  # tag, num_bytes_following (descriptor header)
+                   'Q'  # image size (bytes)
+                   '32s'  # hash algorithm used
+                   'L'  # partition name (bytes)
+                   'L'  # salt length (bytes)
+                   'L' +  # digest length (bytes)
+                   str(RESERVED) + 's')  # reserved
+
+  def __init__(self, data=None):
+    """Initializes a new hash descriptor.
+
+    Arguments:
+      data: If not None, must be a bytearray of size |SIZE|.
+
+    Raises:
+      LookupError: If the given descriptor is malformed.
+    """
+    AvbDescriptor.__init__(self, None)
+    assert struct.calcsize(self.FORMAT_STRING) == self.SIZE
+
+    if data:
+      (tag, num_bytes_following, self.image_size, self.hash_algorithm,
+       partition_name_len, salt_len,
+       digest_len, _) = struct.unpack(self.FORMAT_STRING, data[0:self.SIZE])
+      expected_size = round_to_multiple(
+          self.SIZE - 16 + partition_name_len + salt_len + digest_len, 8)
+      if tag != self.TAG or num_bytes_following != expected_size:
+        raise LookupError('Given data does not look like a hash ' 'descriptor.')
+      # Nuke NUL-bytes at the end.
+      self.hash_algorithm = self.hash_algorithm.split('\0', 1)[0]
+      o = 0
+      self.partition_name = str(data[(self.SIZE + o):(self.SIZE + o +
+                                                      partition_name_len)])
+      # Validate UTF-8 - decode() raises UnicodeDecodeError if not valid UTF-8.
+      self.partition_name.decode('utf-8')
+      o += partition_name_len
+      self.salt = data[(self.SIZE + o):(self.SIZE + o + salt_len)]
+      o += salt_len
+      self.digest = data[(self.SIZE + o):(self.SIZE + o + digest_len)]
+      if digest_len != len(hashlib.new(name=self.hash_algorithm).digest()):
+        raise LookupError('digest_len doesn\'t match hash algorithm')
+
+    else:
+      self.image_size = 0
+      self.hash_algorithm = ''
+      self.partition_name = ''
+      self.salt = bytearray()
+      self.digest = bytearray()
+
+  def print_desc(self, o):
+    """Print the descriptor.
+
+    Arguments:
+      o: The object to write the output to.
+    """
+    o.write('    Hash descriptor:\n')
+    o.write('      Image Size:            {} bytes\n'.format(self.image_size))
+    o.write('      Hash Algorithm:        {}\n'.format(self.hash_algorithm))
+    o.write('      Partition Name:        {}\n'.format(self.partition_name))
+    o.write('      Salt:                  {}\n'.format(str(self.salt).encode(
+        'hex')))
+    o.write('      Digest:                {}\n'.format(str(self.digest).encode(
+        'hex')))
+
+  def encode(self):
+    """Serializes the descriptor.
+
+    Returns:
+      A bytearray() with the descriptor data.
+    """
+    encoded_name = self.partition_name.encode('utf-8')
+    num_bytes_following = (
+        self.SIZE + len(encoded_name) + len(self.salt) + len(self.digest) - 16)
+    nbf_with_padding = round_to_multiple(num_bytes_following, 8)
+    padding_size = nbf_with_padding - num_bytes_following
+    desc = struct.pack(self.FORMAT_STRING, self.TAG, nbf_with_padding,
+                       self.image_size, self.hash_algorithm, len(encoded_name),
+                       len(self.salt), len(self.digest), self.RESERVED*'\0')
+    padding = struct.pack(str(padding_size) + 'x')
+    ret = desc + encoded_name + self.salt + self.digest + padding
+    return bytearray(ret)
+
+
+class AvbKernelCmdlineDescriptor(AvbDescriptor):
+  """A class for kernel command-line descriptors.
+
+  See the |AvbKernelCmdlineDescriptor| C struct for more information.
+
+  Attributes:
+    flags: Flags.
+    kernel_cmdline: The kernel command-line.
+  """
+
+  TAG = 3
+  SIZE = 24
+  FORMAT_STRING = ('!QQ'  # tag, num_bytes_following (descriptor header)
+                   'L'  # flags
+                   'L')  # cmdline length (bytes)
+
+  FLAGS_USE_ONLY_IF_HASHTREE_NOT_DISABLED = (1 << 0)
+  FLAGS_USE_ONLY_IF_HASHTREE_DISABLED = (1 << 1)
+
+  def __init__(self, data=None):
+    """Initializes a new kernel cmdline descriptor.
+
+    Arguments:
+      data: If not None, must be a bytearray of size |SIZE|.
+
+    Raises:
+      LookupError: If the given descriptor is malformed.
+    """
+    AvbDescriptor.__init__(self, None)
+    assert struct.calcsize(self.FORMAT_STRING) == self.SIZE
+
+    if data:
+      (tag, num_bytes_following, self.flags, kernel_cmdline_length) = (
+          struct.unpack(self.FORMAT_STRING, data[0:self.SIZE]))
+      expected_size = round_to_multiple(self.SIZE - 16 + kernel_cmdline_length,
+                                        8)
+      if tag != self.TAG or num_bytes_following != expected_size:
+        raise LookupError('Given data does not look like a kernel cmdline '
+                          'descriptor.')
+      # Nuke NUL-bytes at the end.
+      self.kernel_cmdline = str(data[self.SIZE:(self.SIZE +
+                                                kernel_cmdline_length)])
+      # Validate UTF-8 - decode() raises UnicodeDecodeError if not valid UTF-8.
+      self.kernel_cmdline.decode('utf-8')
+    else:
+      self.flags = 0
+      self.kernel_cmdline = ''
+
+  def print_desc(self, o):
+    """Print the descriptor.
+
+    Arguments:
+      o: The object to write the output to.
+    """
+    o.write('    Kernel Cmdline descriptor:\n')
+    o.write('      Flags:                 {}\n'.format(self.flags))
+    o.write('      Kernel Cmdline:        {}\n'.format(repr(
+        self.kernel_cmdline)))
+
+  def encode(self):
+    """Serializes the descriptor.
+
+    Returns:
+      A bytearray() with the descriptor data.
+    """
+    encoded_str = self.kernel_cmdline.encode('utf-8')
+    num_bytes_following = (self.SIZE + len(encoded_str) - 16)
+    nbf_with_padding = round_to_multiple(num_bytes_following, 8)
+    padding_size = nbf_with_padding - num_bytes_following
+    desc = struct.pack(self.FORMAT_STRING, self.TAG, nbf_with_padding,
+                       self.flags, len(encoded_str))
+    padding = struct.pack(str(padding_size) + 'x')
+    ret = desc + encoded_str + padding
+    return bytearray(ret)
+
+
+class AvbChainPartitionDescriptor(AvbDescriptor):
+  """A class for chained partition descriptors.
+
+  See the |AvbChainPartitionDescriptor| C struct for more information.
+
+  Attributes:
+    rollback_index_location: The rollback index location to use.
+    partition_name: Partition name.
+    public_key: Bytes for the public key.
+  """
+
+  TAG = 4
+  RESERVED = 64
+  SIZE = 28 + RESERVED
+  FORMAT_STRING = ('!QQ'  # tag, num_bytes_following (descriptor header)
+                   'L'  # rollback_index_location
+                   'L'  # partition_name_size (bytes)
+                   'L' +  # public_key_size (bytes)
+                   str(RESERVED) + 's')  # reserved
+
+  def __init__(self, data=None):
+    """Initializes a new chain partition descriptor.
+
+    Arguments:
+      data: If not None, must be a bytearray of size |SIZE|.
+
+    Raises:
+      LookupError: If the given descriptor is malformed.
+    """
+    AvbDescriptor.__init__(self, None)
+    assert struct.calcsize(self.FORMAT_STRING) == self.SIZE
+
+    if data:
+      (tag, num_bytes_following, self.rollback_index_location,
+       partition_name_len,
+       public_key_len, _) = struct.unpack(self.FORMAT_STRING, data[0:self.SIZE])
+      expected_size = round_to_multiple(
+          self.SIZE - 16 + partition_name_len + public_key_len, 8)
+      if tag != self.TAG or num_bytes_following != expected_size:
+        raise LookupError('Given data does not look like a chain partition '
+                          'descriptor.')
+      o = 0
+      self.partition_name = str(data[(self.SIZE + o):(self.SIZE + o +
+                                                      partition_name_len)])
+      # Validate UTF-8 - decode() raises UnicodeDecodeError if not valid UTF-8.
+      self.partition_name.decode('utf-8')
+      o += partition_name_len
+      self.public_key = data[(self.SIZE + o):(self.SIZE + o + public_key_len)]
+
+    else:
+      self.rollback_index_location = 0
+      self.partition_name = ''
+      self.public_key = bytearray()
+
+  def print_desc(self, o):
+    """Print the descriptor.
+
+    Arguments:
+      o: The object to write the output to.
+    """
+    o.write('    Chain Partition descriptor:\n')
+    o.write('      Partition Name:          {}\n'.format(self.partition_name))
+    o.write('      Rollback Index Location: {}\n'.format(
+        self.rollback_index_location))
+    # Just show the SHA1 of the key, for size reasons.
+    hexdig = hashlib.sha1(self.public_key).hexdigest()
+    o.write('      Public key (sha1):       {}\n'.format(hexdig))
+
+  def encode(self):
+    """Serializes the descriptor.
+
+    Returns:
+      A bytearray() with the descriptor data.
+    """
+    encoded_name = self.partition_name.encode('utf-8')
+    num_bytes_following = (
+        self.SIZE + len(encoded_name) + len(self.public_key) - 16)
+    nbf_with_padding = round_to_multiple(num_bytes_following, 8)
+    padding_size = nbf_with_padding - num_bytes_following
+    desc = struct.pack(self.FORMAT_STRING, self.TAG, nbf_with_padding,
+                       self.rollback_index_location, len(encoded_name),
+                       len(self.public_key), self.RESERVED*'\0')
+    padding = struct.pack(str(padding_size) + 'x')
+    ret = desc + encoded_name + self.public_key + padding
+    return bytearray(ret)
+
+
+DESCRIPTOR_CLASSES = [
+    AvbPropertyDescriptor, AvbHashtreeDescriptor, AvbHashDescriptor,
+    AvbKernelCmdlineDescriptor, AvbChainPartitionDescriptor
+]
+
+
+def parse_descriptors(data):
+  """Parses a blob of data into descriptors.
+
+  Arguments:
+    data: A bytearray() with encoded descriptors.
+
+  Returns:
+    A list of instances of objects derived from AvbDescriptor. For
+    unknown descriptors, the class AvbDescriptor is used.
+  """
+  o = 0
+  ret = []
+  while o < len(data):
+    tag, nb_following = struct.unpack('!2Q', data[o:o + 16])
+    if tag < len(DESCRIPTOR_CLASSES):
+      c = DESCRIPTOR_CLASSES[tag]
+    else:
+      c = AvbDescriptor
+    ret.append(c(bytearray(data[o:o + 16 + nb_following])))
+    o += 16 + nb_following
+  return ret
+
+
+class AvbFooter(object):
+  """A class for parsing and writing footers.
+
+  Footers are stored at the end of partitions and point to where the
+  AvbVBMeta blob is located. They also contain the original size of
+  the image before AVB information was added.
+
+  Attributes:
+    magic: Magic for identifying the footer, see |MAGIC|.
+    version_major: The major version of avbtool that wrote the footer.
+    version_minor: The minor version of avbtool that wrote the footer.
+    original_image_size: Original image size.
+    vbmeta_offset: Offset of where the AvbVBMeta blob is stored.
+    vbmeta_size: Size of the AvbVBMeta blob.
+  """
+
+  MAGIC = 'AVBf'
+  SIZE = 64
+  RESERVED = 28
+  FORMAT_STRING = ('!4s2L'  # magic, 2 x version.
+                   'Q'  # Original image size.
+                   'Q'  # Offset of VBMeta blob.
+                   'Q' +  # Size of VBMeta blob.
+                   str(RESERVED) + 'x')  # padding for reserved bytes
+
+  def __init__(self, data=None):
+    """Initializes a new footer object.
+
+    Arguments:
+      data: If not None, must be a bytearray of size 4096.
+
+    Raises:
+      LookupError: If the given footer is malformed.
+      struct.error: If the given data has no footer.
+    """
+    assert struct.calcsize(self.FORMAT_STRING) == self.SIZE
+
+    if data:
+      (self.magic, self.version_major, self.version_minor,
+       self.original_image_size, self.vbmeta_offset,
+       self.vbmeta_size) = struct.unpack(self.FORMAT_STRING, data)
+      if self.magic != self.MAGIC:
+        raise LookupError('Given data does not look like a AVB footer.')
+    else:
+      self.magic = self.MAGIC
+      self.version_major = AVB_VERSION_MAJOR
+      self.version_minor = AVB_VERSION_MINOR
+      self.original_image_size = 0
+      self.vbmeta_offset = 0
+      self.vbmeta_size = 0
+
+  def encode(self):
+    """Gets a string representing the binary encoding of the footer.
+
+    Returns:
+      A bytearray() with a binary representation of the footer.
+    """
+    return struct.pack(self.FORMAT_STRING, self.magic, self.version_major,
+                       self.version_minor, self.original_image_size,
+                       self.vbmeta_offset, self.vbmeta_size)
+
+
+class AvbVBMetaHeader(object):
+  """A class for parsing and writing AVB vbmeta images.
+
+  Attributes:
+    The attributes correspond to the |AvbVBMetaHeader| struct
+    defined in avb_vbmeta_header.h.
+  """
+
+  SIZE = 256
+
+  # Keep in sync with |reserved| field of |AvbVBMetaImageHeader|.
+  RESERVED = 132
+
+  # Keep in sync with |AvbVBMetaImageHeader|.
+  FORMAT_STRING = ('!4s2L'  # magic, 2 x version
+                   '2Q'  # 2 x block size
+                   'L'  # algorithm type
+                   '2Q'  # offset, size (hash)
+                   '2Q'  # offset, size (signature)
+                   '2Q'  # offset, size (public key)
+                   '2Q'  # offset, size (public key metadata)
+                   '2Q'  # offset, size (descriptors)
+                   'Q'  # rollback_index
+                   'L' +  # flags
+                   str(RESERVED) + 'x')  # padding for reserved bytes
+
+  def __init__(self, data=None):
+    """Initializes a new header object.
+
+    Arguments:
+      data: If not None, must be a bytearray of size 8192.
+
+    Raises:
+      Exception: If the given data is malformed.
+    """
+    assert struct.calcsize(self.FORMAT_STRING) == self.SIZE
+
+    if data:
+      (self.magic, self.header_version_major, self.header_version_minor,
+       self.authentication_data_block_size, self.auxiliary_data_block_size,
+       self.algorithm_type, self.hash_offset, self.hash_size,
+       self.signature_offset, self.signature_size, self.public_key_offset,
+       self.public_key_size, self.public_key_metadata_offset,
+       self.public_key_metadata_size, self.descriptors_offset,
+       self.descriptors_size,
+       self.rollback_index,
+       self.flags) = struct.unpack(self.FORMAT_STRING, data)
+      # Nuke NUL-bytes at the end of the string.
+      if self.magic != 'AVB0':
+        raise AvbError('Given image does not look like a vbmeta image.')
+    else:
+      self.magic = 'AVB0'
+      self.header_version_major = AVB_VERSION_MAJOR
+      self.header_version_minor = AVB_VERSION_MINOR
+      self.authentication_data_block_size = 0
+      self.auxiliary_data_block_size = 0
+      self.algorithm_type = 0
+      self.hash_offset = 0
+      self.hash_size = 0
+      self.signature_offset = 0
+      self.signature_size = 0
+      self.public_key_offset = 0
+      self.public_key_size = 0
+      self.public_key_metadata_offset = 0
+      self.public_key_metadata_size = 0
+      self.descriptors_offset = 0
+      self.descriptors_size = 0
+      self.rollback_index = 0
+      self.flags = 0
+
+  def save(self, output):
+    """Serializes the header (256 bytes) to disk.
+
+    Arguments:
+      output: The object to write the output to.
+    """
+    output.write(struct.pack(
+        self.FORMAT_STRING, self.magic, self.header_version_major,
+        self.header_version_minor, self.authentication_data_block_size,
+        self.auxiliary_data_block_size, self.algorithm_type, self.hash_offset,
+        self.hash_size, self.signature_offset, self.signature_size,
+        self.public_key_offset, self.public_key_size,
+        self.public_key_metadata_offset, self.public_key_metadata_size,
+        self.descriptors_offset, self.descriptors_size, self.rollback_index,
+        self.flags))
+
+  def encode(self):
+    """Serializes the header (256) to a bytearray().
+
+    Returns:
+      A bytearray() with the encoded header.
+    """
+    return struct.pack(self.FORMAT_STRING, self.magic,
+                       self.header_version_major, self.header_version_minor,
+                       self.authentication_data_block_size,
+                       self.auxiliary_data_block_size, self.algorithm_type,
+                       self.hash_offset, self.hash_size, self.signature_offset,
+                       self.signature_size, self.public_key_offset,
+                       self.public_key_size, self.public_key_metadata_offset,
+                       self.public_key_metadata_size, self.descriptors_offset,
+                       self.descriptors_size, self.rollback_index, self.flags)
+
+
+class Avb(object):
+  """Business logic for avbtool command-line tool."""
+
+  # Keep in sync with avb_ab_flow.h.
+  AB_FORMAT_NO_CRC = '!4sBB2xBBBxBBBx12x'
+  AB_MAGIC = '\0AB0'
+  AB_MAJOR_VERSION = 1
+  AB_MINOR_VERSION = 0
+  AB_MISC_METADATA_OFFSET = 2048
+
+  # Constants for maximum metadata size. These are used to give
+  # meaningful errors if the value passed in via --partition_size is
+  # too small and when --calc_max_image_size is used. We use
+  # conservative figures.
+  MAX_VBMETA_SIZE = 64 * 1024
+  MAX_FOOTER_SIZE = 4096
+
+  def erase_footer(self, image_filename, keep_hashtree):
+    """Implements the 'erase_footer' command.
+
+    Arguments:
+      image_filename: File to erase a footer from.
+      keep_hashtree: If True, keep the hashtree and FEC around.
+
+    Raises:
+      AvbError: If there's no footer in the image.
+    """
+
+    image = ImageHandler(image_filename)
+
+    (footer, _, descriptors, _) = self._parse_image(image)
+
+    if not footer:
+      raise AvbError('Given image does not have a footer.')
+
+    new_image_size = None
+    if not keep_hashtree:
+      new_image_size = footer.original_image_size
+    else:
+      # If requested to keep the hashtree, search for a hashtree
+      # descriptor to figure out the location and size of the hashtree
+      # and FEC.
+      for desc in descriptors:
+        if isinstance(desc, AvbHashtreeDescriptor):
+          # The hashtree is always just following the main data so the
+          # new size is easily derived.
+          new_image_size = desc.tree_offset + desc.tree_size
+          # If the image has FEC codes, also keep those.
+          if desc.fec_offset > 0:
+            fec_end = desc.fec_offset + desc.fec_size
+            new_image_size = max(new_image_size, fec_end)
+          break
+      if not new_image_size:
+        raise AvbError('Requested to keep hashtree but no hashtree '
+                       'descriptor was found.')
+
+    # And cut...
+    image.truncate(new_image_size)
+
+  def set_ab_metadata(self, misc_image, slot_data):
+    """Implements the 'set_ab_metadata' command.
+
+    The |slot_data| argument must be of the form 'A_priority:A_tries_remaining:
+    A_successful_boot:B_priority:B_tries_remaining:B_successful_boot'.
+
+    Arguments:
+      misc_image: The misc image to write to.
+      slot_data: Slot data as a string
+
+    Raises:
+      AvbError: If slot data is malformed.
+    """
+    tokens = slot_data.split(':')
+    if len(tokens) != 6:
+      raise AvbError('Malformed slot data "{}".'.format(slot_data))
+    a_priority = int(tokens[0])
+    a_tries_remaining = int(tokens[1])
+    a_success = True if int(tokens[2]) != 0 else False
+    b_priority = int(tokens[3])
+    b_tries_remaining = int(tokens[4])
+    b_success = True if int(tokens[5]) != 0 else False
+
+    ab_data_no_crc = struct.pack(self.AB_FORMAT_NO_CRC,
+                                 self.AB_MAGIC,
+                                 self.AB_MAJOR_VERSION, self.AB_MINOR_VERSION,
+                                 a_priority, a_tries_remaining, a_success,
+                                 b_priority, b_tries_remaining, b_success)
+    # Force CRC to be unsigned, see https://bugs.python.org/issue4903 for why.
+    crc_value = binascii.crc32(ab_data_no_crc) & 0xffffffff
+    ab_data = ab_data_no_crc + struct.pack('!I', crc_value)
+    misc_image.seek(self.AB_MISC_METADATA_OFFSET)
+    misc_image.write(ab_data)
+
+  def info_image(self, image_filename, output):
+    """Implements the 'info_image' command.
+
+    Arguments:
+      image_filename: Image file to get information from (file object).
+      output: Output file to write human-readable information to (file object).
+    """
+
+    image = ImageHandler(image_filename)
+
+    o = output
+
+    (footer, header, descriptors, image_size) = self._parse_image(image)
+
+    if footer:
+      o.write('Footer version:           {}.{}\n'.format(footer.version_major,
+                                                         footer.version_minor))
+      o.write('Image size:               {} bytes\n'.format(image_size))
+      o.write('Original image size:      {} bytes\n'.format(
+          footer.original_image_size))
+      o.write('VBMeta offset:            {}\n'.format(footer.vbmeta_offset))
+      o.write('VBMeta size:              {} bytes\n'.format(footer.vbmeta_size))
+      o.write('--\n')
+
+    (alg_name, _) = lookup_algorithm_by_type(header.algorithm_type)
+
+    o.write('VBMeta image version:     {}.{}{}\n'.format(
+        header.header_version_major, header.header_version_minor,
+        ' (Sparse)' if image.is_sparse else ''))
+    o.write('Header Block:             {} bytes\n'.format(AvbVBMetaHeader.SIZE))
+    o.write('Authentication Block:     {} bytes\n'.format(
+        header.authentication_data_block_size))
+    o.write('Auxiliary Block:          {} bytes\n'.format(
+        header.auxiliary_data_block_size))
+    o.write('Algorithm:                {}\n'.format(alg_name))
+    o.write('Rollback Index:           {}\n'.format(header.rollback_index))
+    o.write('Flags:                    {}\n'.format(header.flags))
+
+    # Print descriptors.
+    num_printed = 0
+    o.write('Descriptors:\n')
+    for desc in descriptors:
+      desc.print_desc(o)
+      num_printed += 1
+    if num_printed == 0:
+      o.write('    (none)\n')
+
+  def _parse_image(self, image):
+    """Gets information about an image.
+
+    The image can either be a vbmeta or an image with a footer.
+
+    Arguments:
+      image: An ImageHandler (vbmeta or footer) with a hashtree descriptor.
+
+    Returns:
+      A tuple where the first argument is a AvbFooter (None if there
+      is no footer on the image), the second argument is a
+      AvbVBMetaHeader, the third argument is a list of
+      AvbDescriptor-derived instances, and the fourth argument is the
+      size of |image|.
+    """
+    assert isinstance(image, ImageHandler)
+    footer = None
+    image.seek(image.image_size - AvbFooter.SIZE)
+    try:
+      footer = AvbFooter(image.read(AvbFooter.SIZE))
+    except (LookupError, struct.error):
+      # Nope, just seek back to the start.
+      image.seek(0)
+
+    vbmeta_offset = 0
+    if footer:
+      vbmeta_offset = footer.vbmeta_offset
+
+    image.seek(vbmeta_offset)
+    h = AvbVBMetaHeader(image.read(AvbVBMetaHeader.SIZE))
+
+    auth_block_offset = vbmeta_offset + AvbVBMetaHeader.SIZE
+    aux_block_offset = auth_block_offset + h.authentication_data_block_size
+    desc_start_offset = aux_block_offset + h.descriptors_offset
+    image.seek(desc_start_offset)
+    descriptors = parse_descriptors(image.read(h.descriptors_size))
+
+    return footer, h, descriptors, image.image_size
+
+  def _get_cmdline_descriptors_for_dm_verity(self, image):
+    """Generate kernel cmdline descriptors for dm-verity.
+
+    Arguments:
+      image: An ImageHandler (vbmeta or footer) with a hashtree descriptor.
+
+    Returns:
+      A list with two AvbKernelCmdlineDescriptor with dm-verity kernel cmdline
+      instructions. There is one for when hashtree is not disabled and one for
+      when it is.
+
+    Raises:
+      AvbError: If  |image| doesn't have a hashtree descriptor.
+
+    """
+
+    (_, _, descriptors, _) = self._parse_image(image)
+
+    ht = None
+    for desc in descriptors:
+      if isinstance(desc, AvbHashtreeDescriptor):
+        ht = desc
+        break
+
+    if not ht:
+      raise AvbError('No hashtree descriptor in given image')
+
+    c = 'dm="1 vroot none ro 1,'
+    c += '0'  # start
+    c += ' {}'.format((ht.image_size / 512))  # size (# sectors)
+    c += ' verity {}'.format(ht.dm_verity_version)  # type and version
+    c += ' PARTUUID=$(ANDROID_SYSTEM_PARTUUID)'  # data_dev
+    c += ' PARTUUID=$(ANDROID_SYSTEM_PARTUUID)'  # hash_dev
+    c += ' {}'.format(ht.data_block_size)  # data_block
+    c += ' {}'.format(ht.hash_block_size)  # hash_block
+    c += ' {}'.format(ht.image_size / ht.data_block_size)  # #blocks
+    c += ' {}'.format(ht.image_size / ht.data_block_size)  # hash_offset
+    c += ' {}'.format(ht.hash_algorithm)  # hash_alg
+    c += ' {}'.format(str(ht.root_digest).encode('hex'))  # root_digest
+    c += ' {}'.format(str(ht.salt).encode('hex'))  # salt
+    if ht.fec_num_roots > 0:
+      c += ' 10'  # number of optional args
+      c += ' restart_on_corruption'
+      c += ' ignore_zero_blocks'
+      c += ' use_fec_from_device PARTUUID=$(ANDROID_SYSTEM_PARTUUID)'
+      c += ' fec_roots {}'.format(ht.fec_num_roots)
+      # Note that fec_blocks is the size that FEC covers, *not* the
+      # size of the FEC data. Since we use FEC for everything up until
+      # the FEC data, it's the same as the offset.
+      c += ' fec_blocks {}'.format(ht.fec_offset/ht.data_block_size)
+      c += ' fec_start {}'.format(ht.fec_offset/ht.data_block_size)
+    else:
+      c += ' 2'  # number of optional args
+      c += ' restart_on_corruption'
+      c += ' ignore_zero_blocks'
+    c += '" root=0xfd00'
+
+    # Now that we have the command-line, generate the descriptor.
+    desc = AvbKernelCmdlineDescriptor()
+    desc.kernel_cmdline = c
+    desc.flags = (
+        AvbKernelCmdlineDescriptor.FLAGS_USE_ONLY_IF_HASHTREE_NOT_DISABLED)
+
+    # The descriptor for when hashtree verification is disabled is a lot
+    # simpler - we just set the root to the partition.
+    desc_no_ht = AvbKernelCmdlineDescriptor()
+    desc_no_ht.kernel_cmdline = 'root=PARTUUID=$(ANDROID_SYSTEM_PARTUUID)'
+    desc_no_ht.flags = (
+        AvbKernelCmdlineDescriptor.FLAGS_USE_ONLY_IF_HASHTREE_DISABLED)
+
+    return [desc, desc_no_ht]
+
+  def make_vbmeta_image(self, output, chain_partitions, algorithm_name,
+                        key_path, public_key_metadata_path, rollback_index,
+                        flags, props, props_from_file, kernel_cmdlines,
+                        setup_rootfs_from_kernel,
+                        include_descriptors_from_image, signing_helper):
+    """Implements the 'make_vbmeta_image' command.
+
+    Arguments:
+      output: File to write the image to.
+      chain_partitions: List of partitions to chain.
+      algorithm_name: Name of algorithm to use.
+      key_path: Path to key to use or None.
+      public_key_metadata_path: Path to public key metadata or None.
+      rollback_index: The rollback index to use.
+      flags: Flags value to use in the image.
+      props: Properties to insert (list of strings of the form 'key:value').
+      props_from_file: Properties to insert (list of strings 'key:<path>').
+      kernel_cmdlines: Kernel cmdlines to insert (list of strings).
+      setup_rootfs_from_kernel: None or file to generate from.
+      include_descriptors_from_image: List of file objects with descriptors.
+      signing_helper: Program which signs a hash and return signature.
+
+    Raises:
+      AvbError: If a chained partition is malformed.
+    """
+
+    descriptors = []
+
+    # Insert chained partition descriptors.
+    if chain_partitions:
+      for cp in chain_partitions:
+        cp_tokens = cp.split(':')
+        if len(cp_tokens) != 3:
+          raise AvbError('Malformed chained partition "{}".'.format(cp))
+        desc = AvbChainPartitionDescriptor()
+        desc.partition_name = cp_tokens[0]
+        desc.rollback_index_location = int(cp_tokens[1])
+        if desc.rollback_index_location < 1:
+          raise AvbError('Rollback index location must be 1 or larger.')
+        file_path = cp_tokens[2]
+        desc.public_key = open(file_path, 'rb').read()
+        descriptors.append(desc)
+
+    vbmeta_blob = self._generate_vbmeta_blob(
+        algorithm_name, key_path, public_key_metadata_path, descriptors,
+        rollback_index, flags, props, props_from_file, kernel_cmdlines,
+        setup_rootfs_from_kernel,
+        include_descriptors_from_image, signing_helper)
+
+    # Write entire vbmeta blob (header, authentication, auxiliary).
+    output.seek(0)
+    output.write(vbmeta_blob)
+
+  def _generate_vbmeta_blob(self, algorithm_name, key_path,
+                            public_key_metadata_path, descriptors,
+                            rollback_index, flags, props, props_from_file,
+                            kernel_cmdlines,
+                            setup_rootfs_from_kernel,
+                            include_descriptors_from_image, signing_helper):
+    """Generates a VBMeta blob.
+
+    This blob contains the header (struct AvbVBMetaHeader), the
+    authentication data block (which contains the hash and signature
+    for the header and auxiliary block), and the auxiliary block
+    (which contains descriptors, the public key used, and other data).
+
+    The |key| parameter can |None| only if the |algorithm_name| is
+    'NONE'.
+
+    Arguments:
+      algorithm_name: The algorithm name as per the ALGORITHMS dict.
+      key_path: The path to the .pem file used to sign the blob.
+      public_key_metadata_path: Path to public key metadata or None.
+      descriptors: A list of descriptors to insert or None.
+      rollback_index: The rollback index to use.
+      flags: Flags to use in the image.
+      props: Properties to insert (List of strings of the form 'key:value').
+      props_from_file: Properties to insert (List of strings 'key:<path>').
+      kernel_cmdlines: Kernel cmdlines to insert (list of strings).
+      setup_rootfs_from_kernel: None or file to generate
+        dm-verity kernel cmdline from.
+      include_descriptors_from_image: List of file objects for which
+        to insert descriptors from.
+      signing_helper: Program which signs a hash and return signature.
+
+    Returns:
+      A bytearray() with the VBMeta blob.
+
+    Raises:
+      Exception: If the |algorithm_name| is not found, if no key has
+        been given and the given algorithm requires one, or the key is
+        of the wrong size.
+
+    """
+    try:
+      alg = ALGORITHMS[algorithm_name]
+    except KeyError:
+      raise AvbError('Unknown algorithm with name {}'.format(algorithm_name))
+
+    # Descriptors.
+    encoded_descriptors = bytearray()
+    if descriptors:
+      for desc in descriptors:
+        encoded_descriptors.extend(desc.encode())
+
+    # Add properties.
+    if props:
+      for prop in props:
+        idx = prop.find(':')
+        if idx == -1:
+          raise AvbError('Malformed property "{}".'.format(prop))
+        desc = AvbPropertyDescriptor()
+        desc.key = prop[0:idx]
+        desc.value = prop[(idx + 1):]
+        encoded_descriptors.extend(desc.encode())
+    if props_from_file:
+      for prop in props_from_file:
+        idx = prop.find(':')
+        if idx == -1:
+          raise AvbError('Malformed property "{}".'.format(prop))
+        desc = AvbPropertyDescriptor()
+        desc.key = prop[0:idx]
+        desc.value = prop[(idx + 1):]
+        file_path = prop[(idx + 1):]
+        desc.value = open(file_path, 'rb').read()
+        encoded_descriptors.extend(desc.encode())
+
+    # Add AvbKernelCmdline descriptor for dm-verity, if requested.
+    if setup_rootfs_from_kernel:
+      image_handler = ImageHandler(
+          setup_rootfs_from_kernel.name)
+      cmdline_desc = self._get_cmdline_descriptors_for_dm_verity(image_handler)
+      encoded_descriptors.extend(cmdline_desc[0].encode())
+      encoded_descriptors.extend(cmdline_desc[1].encode())
+
+    # Add kernel command-lines.
+    if kernel_cmdlines:
+      for i in kernel_cmdlines:
+        desc = AvbKernelCmdlineDescriptor()
+        desc.kernel_cmdline = i
+        encoded_descriptors.extend(desc.encode())
+
+    # Add descriptors from other images.
+    if include_descriptors_from_image:
+      for image in include_descriptors_from_image:
+        image_handler = ImageHandler(image.name)
+        (_, _, image_descriptors, _) = self._parse_image(image_handler)
+        for desc in image_descriptors:
+          encoded_descriptors.extend(desc.encode())
+
+    # Load public key metadata blob, if requested.
+    pkmd_blob = []
+    if public_key_metadata_path:
+      with open(public_key_metadata_path) as f:
+        pkmd_blob = f.read()
+
+    key = None
+    encoded_key = bytearray()
+    if alg.public_key_num_bytes > 0:
+      if not key_path:
+        raise AvbError('Key is required for algorithm {}'.format(
+            algorithm_name))
+      key = Crypto.PublicKey.RSA.importKey(open(key_path).read())
+      encoded_key = encode_rsa_key(key)
+      if len(encoded_key) != alg.public_key_num_bytes:
+        raise AvbError('Key is wrong size for algorithm {}'.format(
+            algorithm_name))
+
+    h = AvbVBMetaHeader()
+
+    # For the Auxiliary data block, descriptors are stored at offset 0,
+    # followed by the public key, followed by the public key metadata blob.
+    h.auxiliary_data_block_size = round_to_multiple(
+        len(encoded_descriptors) + len(encoded_key) + len(pkmd_blob), 64)
+    h.descriptors_offset = 0
+    h.descriptors_size = len(encoded_descriptors)
+    h.public_key_offset = h.descriptors_size
+    h.public_key_size = len(encoded_key)
+    h.public_key_metadata_offset = h.public_key_offset + h.public_key_size
+    h.public_key_metadata_size = len(pkmd_blob)
+
+    # For the Authentication data block, the hash is first and then
+    # the signature.
+    h.authentication_data_block_size = round_to_multiple(
+        alg.hash_num_bytes + alg.signature_num_bytes, 64)
+    h.algorithm_type = alg.algorithm_type
+    h.hash_offset = 0
+    h.hash_size = alg.hash_num_bytes
+    # Signature offset and size - it's stored right after the hash
+    # (in Authentication data block).
+    h.signature_offset = alg.hash_num_bytes
+    h.signature_size = alg.signature_num_bytes
+
+    h.rollback_index = rollback_index
+    h.flags = flags
+
+    # Generate Header data block.
+    header_data_blob = h.encode()
+
+    # Generate Auxiliary data block.
+    aux_data_blob = bytearray()
+    aux_data_blob.extend(encoded_descriptors)
+    aux_data_blob.extend(encoded_key)
+    aux_data_blob.extend(pkmd_blob)
+    padding_bytes = h.auxiliary_data_block_size - len(aux_data_blob)
+    aux_data_blob.extend('\0' * padding_bytes)
+
+    # Calculate the hash.
+    binary_hash = bytearray()
+    binary_signature = bytearray()
+    if algorithm_name != 'NONE':
+      if algorithm_name[0:6] == 'SHA256':
+        ha = hashlib.sha256()
+      elif algorithm_name[0:6] == 'SHA512':
+        ha = hashlib.sha512()
+      else:
+        raise AvbError('Unsupported algorithm {}.'.format(algorithm_name))
+      ha.update(header_data_blob)
+      ha.update(aux_data_blob)
+      binary_hash.extend(ha.digest())
+
+      # Calculate the signature.
+      padding_and_hash = str(bytearray(alg.padding)) + binary_hash
+      binary_signature.extend(raw_sign(signing_helper, algorithm_name, key_path,
+                                       padding_and_hash))
+
+    # Generate Authentication data block.
+    auth_data_blob = bytearray()
+    auth_data_blob.extend(binary_hash)
+    auth_data_blob.extend(binary_signature)
+    padding_bytes = h.authentication_data_block_size - len(auth_data_blob)
+    auth_data_blob.extend('\0' * padding_bytes)
+
+    return header_data_blob + auth_data_blob + aux_data_blob
+
+  def extract_public_key(self, key_path, output):
+    """Implements the 'extract_public_key' command.
+
+    Arguments:
+      key_path: The path to a RSA private key file.
+      output: The file to write to.
+    """
+    key = Crypto.PublicKey.RSA.importKey(open(key_path).read())
+    write_rsa_key(output, key)
+
+  def add_hash_footer(self, image_filename, partition_size, partition_name,
+                      hash_algorithm, salt, algorithm_name, key_path,
+                      public_key_metadata_path, rollback_index, props,
+                      props_from_file, kernel_cmdlines,
+                      setup_rootfs_from_kernel,
+                      include_descriptors_from_image, signing_helper):
+    """Implementation of the add_hash_footer on unsparse images.
+
+    Arguments:
+      image_filename: File to add the footer to.
+      partition_size: Size of partition.
+      partition_name: Name of partition (without A/B suffix).
+      hash_algorithm: Hash algorithm to use.
+      salt: Salt to use as a hexadecimal string or None to use /dev/urandom.
+      algorithm_name: Name of algorithm to use.
+      key_path: Path to key to use or None.
+      public_key_metadata_path: Path to public key metadata or None.
+      rollback_index: Rollback index.
+      props: Properties to insert (List of strings of the form 'key:value').
+      props_from_file: Properties to insert (List of strings 'key:<path>').
+      kernel_cmdlines: Kernel cmdlines to insert (list of strings).
+      setup_rootfs_from_kernel: None or file to generate
+        dm-verity kernel cmdline from.
+      include_descriptors_from_image: List of file objects for which
+        to insert descriptors from.
+      signing_helper: Program which signs a hash and return signature.
+
+    Raises:
+      AvbError: If an argument is incorrect.
+    """
+    image = ImageHandler(image_filename)
+
+    if partition_size % image.block_size != 0:
+      raise AvbError('Partition size of {} is not a multiple of the image '
+                     'block size {}.'.format(partition_size,
+                                             image.block_size))
+
+    # If there's already a footer, truncate the image to its original
+    # size. This way 'avbtool add_hash_footer' is idempotent (modulo
+    # salts).
+    image.seek(image.image_size - AvbFooter.SIZE)
+    try:
+      footer = AvbFooter(image.read(AvbFooter.SIZE))
+      # Existing footer found. Just truncate.
+      original_image_size = footer.original_image_size
+      image.truncate(footer.original_image_size)
+    except (LookupError, struct.error):
+      original_image_size = image.image_size
+
+    # If anything goes wrong from here-on, restore the image back to
+    # its original size.
+    try:
+      # First, calculate the maximum image size such that an image
+      # this size + metadata (footer + vbmeta struct) fits in
+      # |partition_size|.
+      max_metadata_size = self.MAX_VBMETA_SIZE + self.MAX_FOOTER_SIZE
+      max_image_size = partition_size - max_metadata_size
+
+      # If image size exceeds the maximum image size, fail.
+      if image.image_size > max_image_size:
+        raise AvbError('Image size of {} exceeds maximum image '
+                       'size of {} in order to fit in a partition '
+                       'size of {}.'.format(image.image_size, max_image_size,
+                                            partition_size))
+
+      digest_size = len(hashlib.new(name=hash_algorithm).digest())
+      if salt:
+        salt = salt.decode('hex')
+      else:
+        if salt is None:
+          # If salt is not explicitly specified, choose a hash
+          # that's the same size as the hash size.
+          hash_size = digest_size
+          salt = open('/dev/urandom').read(hash_size)
+        else:
+          salt = ''
+
+      hasher = hashlib.new(name=hash_algorithm, string=salt)
+      # TODO(zeuthen): might want to read this in chunks to avoid
+      # memory pressure, then again, this is only supposed to be used
+      # on kernel/initramfs partitions. Possible optimization.
+      image.seek(0)
+      hasher.update(image.read(image.image_size))
+      digest = hasher.digest()
+
+      h_desc = AvbHashDescriptor()
+      h_desc.image_size = image.image_size
+      h_desc.hash_algorithm = hash_algorithm
+      h_desc.partition_name = partition_name
+      h_desc.salt = salt
+      h_desc.digest = digest
+
+      # Flags are only allowed on top-level vbmeta struct.
+      flags = 0
+
+      # Generate the VBMeta footer.
+      vbmeta_blob = self._generate_vbmeta_blob(
+          algorithm_name, key_path, public_key_metadata_path, [h_desc],
+          rollback_index, flags, props, props_from_file, kernel_cmdlines,
+          setup_rootfs_from_kernel,
+          include_descriptors_from_image, signing_helper)
+
+      # If the image isn't sparse, its size might not be a multiple of
+      # the block size. This will screw up padding later so just grow it.
+      if image.image_size % image.block_size != 0:
+        assert not image.is_sparse
+        padding_needed = image.block_size - (image.image_size%image.block_size)
+        image.truncate(image.image_size + padding_needed)
+
+      # The append_raw() method requires content with size being a
+      # multiple of |block_size| so add padding as needed. Also record
+      # where this is written to since we'll need to put that in the
+      # footer.
+      vbmeta_offset = image.image_size
+      padding_needed = (round_to_multiple(len(vbmeta_blob), image.block_size) -
+                        len(vbmeta_blob))
+      vbmeta_blob_with_padding = vbmeta_blob + '\0'*padding_needed
+      image.append_raw(vbmeta_blob_with_padding)
+      vbmeta_end_offset = vbmeta_offset + len(vbmeta_blob_with_padding)
+
+      # Now insert a DONT_CARE chunk with enough bytes such that the
+      # final Footer block is at the end of partition_size..
+      image.append_dont_care(partition_size - vbmeta_end_offset -
+                             1*image.block_size)
+
+      # Generate the Footer that tells where the VBMeta footer
+      # is. Also put enough padding in the front of the footer since
+      # we'll write out an entire block.
+      footer = AvbFooter()
+      footer.original_image_size = original_image_size
+      footer.vbmeta_offset = vbmeta_offset
+      footer.vbmeta_size = len(vbmeta_blob)
+      footer_blob = footer.encode()
+      footer_blob_with_padding = ('\0'*(image.block_size - AvbFooter.SIZE) +
+                                  footer_blob)
+      image.append_raw(footer_blob_with_padding)
+
+    except:
+      # Truncate back to original size, then re-raise
+      image.truncate(original_image_size)
+      raise
+
+  def add_hashtree_footer(self, image_filename, partition_size, partition_name,
+                          generate_fec, fec_num_roots, hash_algorithm,
+                          block_size, salt, algorithm_name, key_path,
+                          public_key_metadata_path, rollback_index,
+                          props, props_from_file, kernel_cmdlines,
+                          setup_rootfs_from_kernel,
+                          include_descriptors_from_image,
+                          calc_max_image_size, signing_helper):
+    """Implements the 'add_hashtree_footer' command.
+
+    See https://gitlab.com/cryptsetup/cryptsetup/wikis/DMVerity for
+    more information about dm-verity and these hashes.
+
+    Arguments:
+      image_filename: File to add the footer to.
+      partition_size: Size of partition.
+      partition_name: Name of partition (without A/B suffix).
+      generate_fec: If True, generate FEC codes.
+      fec_num_roots: Number of roots for FEC.
+      hash_algorithm: Hash algorithm to use.
+      block_size: Block size to use.
+      salt: Salt to use as a hexadecimal string or None to use /dev/urandom.
+      algorithm_name: Name of algorithm to use.
+      key_path: Path to key to use or None.
+      public_key_metadata_path: Path to public key metadata or None.
+      rollback_index: Rollback index.
+      props: Properties to insert (List of strings of the form 'key:value').
+      props_from_file: Properties to insert (List of strings 'key:<path>').
+      kernel_cmdlines: Kernel cmdlines to insert (list of strings).
+      setup_rootfs_from_kernel: None or file to generate
+        dm-verity kernel cmdline from.
+      include_descriptors_from_image: List of file objects for which
+        to insert descriptors from.
+      calc_max_image_size: Don't store the hashtree or footer - instead
+        calculate the maximum image size leaving enough room for hashtree
+        and metadata with the given |partition_size|.
+      signing_helper: Program which signs a hash and return signature.
+
+    Raises:
+      AvbError: If an argument is incorrect.
+    """
+    digest_size = len(hashlib.new(name=hash_algorithm).digest())
+    digest_padding = round_to_pow2(digest_size) - digest_size
+
+    # First, calculate the maximum image size such that an image
+    # this size + the hashtree + metadata (footer + vbmeta struct)
+    # fits in |partition_size|. We use very conservative figures for
+    # metadata.
+    (_, max_tree_size) = calc_hash_level_offsets(
+        partition_size, block_size, digest_size + digest_padding)
+    max_fec_size = 0
+    if generate_fec:
+      max_fec_size = calc_fec_data_size(partition_size, fec_num_roots)
+    max_metadata_size = (max_fec_size + max_tree_size +
+                         self.MAX_VBMETA_SIZE +
+                         self.MAX_FOOTER_SIZE)
+    max_image_size = partition_size - max_metadata_size
+
+    # If we're asked to only calculate the maximum image size, we're done.
+    if calc_max_image_size:
+      print '{}'.format(max_image_size)
+      return
+
+    image = ImageHandler(image_filename)
+
+    if partition_size % image.block_size != 0:
+      raise AvbError('Partition size of {} is not a multiple of the image '
+                     'block size {}.'.format(partition_size,
+                                             image.block_size))
+
+    # If there's already a footer, truncate the image to its original
+    # size. This way 'avbtool add_hashtree_footer' is idempotent
+    # (modulo salts).
+    image.seek(image.image_size - AvbFooter.SIZE)
+    try:
+      footer = AvbFooter(image.read(AvbFooter.SIZE))
+      # Existing footer found. Just truncate.
+      original_image_size = footer.original_image_size
+      image.truncate(footer.original_image_size)
+    except (LookupError, struct.error):
+      original_image_size = image.image_size
+
+    # If anything goes wrong from here-on, restore the image back to
+    # its original size.
+    try:
+      # Ensure image is multiple of block_size.
+      rounded_image_size = round_to_multiple(image.image_size, block_size)
+      if rounded_image_size > image.image_size:
+        image.append_raw('\0' * (rounded_image_size - image.image_size))
+
+      # If image size exceeds the maximum image size, fail.
+      if image.image_size > max_image_size:
+        raise AvbError('Image size of {} exceeds maximum image '
+                       'size of {} in order to fit in a partition '
+                       'size of {}.'.format(image.image_size, max_image_size,
+                                            partition_size))
+
+      if salt:
+        salt = salt.decode('hex')
+      else:
+        if salt is None:
+          # If salt is not explicitly specified, choose a hash
+          # that's the same size as the hash size.
+          hash_size = digest_size
+          salt = open('/dev/urandom').read(hash_size)
+        else:
+          salt = ''
+
+      # Hashes are stored upside down so we need to calculate hash
+      # offsets in advance.
+      (hash_level_offsets, tree_size) = calc_hash_level_offsets(
+          image.image_size, block_size, digest_size + digest_padding)
+
+      # If the image isn't sparse, its size might not be a multiple of
+      # the block size. This will screw up padding later so just grow it.
+      if image.image_size % image.block_size != 0:
+        assert not image.is_sparse
+        padding_needed = image.block_size - (image.image_size%image.block_size)
+        image.truncate(image.image_size + padding_needed)
+
+      # Generate the tree and add padding as needed.
+      tree_offset = image.image_size
+      root_digest, hash_tree = generate_hash_tree(image, image.image_size,
+                                                  block_size,
+                                                  hash_algorithm, salt,
+                                                  digest_padding,
+                                                  hash_level_offsets,
+                                                  tree_size)
+
+      # Generate HashtreeDescriptor with details about the tree we
+      # just generated.
+      ht_desc = AvbHashtreeDescriptor()
+      ht_desc.dm_verity_version = 1
+      ht_desc.image_size = image.image_size
+      ht_desc.tree_offset = tree_offset
+      ht_desc.tree_size = tree_size
+      ht_desc.data_block_size = block_size
+      ht_desc.hash_block_size = block_size
+      ht_desc.hash_algorithm = hash_algorithm
+      ht_desc.partition_name = partition_name
+      ht_desc.salt = salt
+      ht_desc.root_digest = root_digest
+
+      # Write the hash tree
+      padding_needed = (round_to_multiple(len(hash_tree), image.block_size) -
+                        len(hash_tree))
+      hash_tree_with_padding = hash_tree + '\0'*padding_needed
+      image.append_raw(hash_tree_with_padding)
+      len_hashtree_and_fec = len(hash_tree_with_padding)
+
+      # Generate FEC codes, if requested.
+      if generate_fec:
+        fec_data = generate_fec_data(image_filename, fec_num_roots)
+        padding_needed = (round_to_multiple(len(fec_data), image.block_size) -
+                          len(fec_data))
+        fec_data_with_padding = fec_data + '\0'*padding_needed
+        fec_offset = image.image_size
+        image.append_raw(fec_data_with_padding)
+        len_hashtree_and_fec += len(fec_data_with_padding)
+        # Update the hashtree descriptor.
+        ht_desc.fec_num_roots = fec_num_roots
+        ht_desc.fec_offset = fec_offset
+        ht_desc.fec_size = len(fec_data)
+
+      # Flags are only allowed on top-level vbmeta struct.
+      flags = 0
+
+      # Generate the VBMeta footer and add padding as needed.
+      vbmeta_offset = tree_offset + len_hashtree_and_fec
+      vbmeta_blob = self._generate_vbmeta_blob(
+          algorithm_name, key_path, public_key_metadata_path, [ht_desc],
+          rollback_index, flags, props, props_from_file, kernel_cmdlines,
+          setup_rootfs_from_kernel,
+          include_descriptors_from_image, signing_helper)
+      padding_needed = (round_to_multiple(len(vbmeta_blob), image.block_size) -
+                        len(vbmeta_blob))
+      vbmeta_blob_with_padding = vbmeta_blob + '\0'*padding_needed
+      image.append_raw(vbmeta_blob_with_padding)
+
+      # Now insert a DONT_CARE chunk with enough bytes such that the
+      # final Footer block is at the end of partition_size..
+      image.append_dont_care(partition_size - image.image_size -
+                             1*image.block_size)
+
+      # Generate the Footer that tells where the VBMeta footer
+      # is. Also put enough padding in the front of the footer since
+      # we'll write out an entire block.
+      footer = AvbFooter()
+      footer.original_image_size = original_image_size
+      footer.vbmeta_offset = vbmeta_offset
+      footer.vbmeta_size = len(vbmeta_blob)
+      footer_blob = footer.encode()
+      footer_blob_with_padding = ('\0'*(image.block_size - AvbFooter.SIZE) +
+                                  footer_blob)
+      image.append_raw(footer_blob_with_padding)
+
+    except:
+      # Truncate back to original size, then re-raise.
+      image.truncate(original_image_size)
+      raise
+
+  def make_atx_certificate(self, output, authority_key_path, subject_key,
+                           subject_key_version, subject,
+                           is_intermediate_authority, signing_helper):
+    """Implements the 'make_atx_certificate' command.
+
+    Android Things certificates are required for Android Things public key
+    metadata. They chain the vbmeta signing key for a particular product back to
+    a fused, permanent root key. These certificates are fixed-length and fixed-
+    format with the explicit goal of not parsing ASN.1 in bootloader code.
+
+    Arguments:
+      output: Certificate will be written to this file on success.
+      authority_key_path: A PEM file path with the authority private key.
+                          If None, then a certificate will be created without a
+                          signature. The signature can be created out-of-band
+                          and appended.
+      subject_key: A PEM or DER subject public key.
+      subject_key_version: A 64-bit version value. If this is None, the number
+                           of seconds since the epoch is used.
+      subject: A subject identifier. For Product Signing Key certificates this
+               should be the same Product ID found in the permanent attributes.
+      is_intermediate_authority: True if the certificate is for an intermediate
+                                 authority.
+      signing_helper: Program which signs a hash and returns the signature.
+    """
+    signed_data = bytearray()
+    signed_data.extend(struct.pack('<I', 1))  # Format Version
+    signed_data.extend(
+        encode_rsa_key(Crypto.PublicKey.RSA.importKey(subject_key)))
+    hasher = hashlib.sha256()
+    hasher.update(subject)
+    signed_data.extend(hasher.digest())
+    usage = 'com.google.android.things.vboot'
+    if is_intermediate_authority:
+      usage += '.ca'
+    hasher = hashlib.sha256()
+    hasher.update(usage)
+    signed_data.extend(hasher.digest())
+    if not subject_key_version:
+      subject_key_version = int(time.time())
+    signed_data.extend(struct.pack('<Q', subject_key_version))
+    signature = bytearray()
+    if authority_key_path:
+      padding_and_hash = bytearray()
+      algorithm_name = None
+      hasher = None
+      if is_intermediate_authority:
+        hasher = hashlib.sha512()
+        algorithm_name = 'SHA512_RSA4096'
+      else:
+        hasher = hashlib.sha256()
+        algorithm_name = 'SHA256_RSA2048'
+      padding_and_hash.extend(ALGORITHMS[algorithm_name].padding)
+      hasher.update(signed_data)
+      padding_and_hash.extend(hasher.digest())
+      signature.extend(raw_sign(signing_helper, algorithm_name,
+                                authority_key_path, padding_and_hash))
+    output.write(signed_data)
+    output.write(signature)
+
+  def make_atx_permanent_attributes(self, output, root_authority_key,
+                                    product_id):
+    """Implements the 'make_atx_permanent_attributes' command.
+
+    Android Things permanent attributes are designed to be permanent for a
+    particular product and a hash of these attributes should be fused into
+    hardware to enforce this.
+
+    Arguments:
+      output: Attributes will be written to this file on success.
+      root_authority_key: A PEM or DER public key for the root authority.
+      product_id: A 16-byte Product ID.
+
+    Raises:
+      AvbError: If an argument is incorrect.
+    """
+    if len(product_id) != 16:
+      raise AvbError('Invalid Product ID length.')
+    output.write(struct.pack('<I', 1))  # Format Version
+    write_rsa_key(output, Crypto.PublicKey.RSA.importKey(root_authority_key))
+    output.write(product_id)
+
+  def make_atx_metadata(self, output, intermediate_key_certificate,
+                        product_key_certificate, google_key_version):
+    """Implements the 'make_atx_metadata' command.
+
+    Android Things metadata are included in vbmeta images to facilitate
+    verification. The output of this command can be used as the
+    public_key_metadata argument to other commands.
+
+    Arguments:
+      output: Metadata will be written to this file on success.
+      intermediate_key_certificate: A certificate file as output by
+                                    make_atx_certificate with
+                                    is_intermediate_authority set to true.
+      product_key_certificate: A certificate file as output by
+                               make_atx_certificate with
+                               is_intermediate_authority set to false.
+      google_key_version: The version of the Google Signing Key used in the
+                          associated vbmeta image.
+
+    Raises:
+      AvbError: If an argument is incorrect.
+    """
+    if len(intermediate_key_certificate) != 1108:
+      raise AvbError('Invalid intermediate key certificate length.')
+    if len(product_key_certificate) != 852:
+      raise AvbError('Invalid product key certificate length.')
+    output.write(struct.pack('<I', 1))  # Format Version
+    output.write(intermediate_key_certificate)
+    output.write(product_key_certificate)
+    output.write(struct.pack('<Q', google_key_version))
+
+
+def calc_hash_level_offsets(image_size, block_size, digest_size):
+  """Calculate the offsets of all the hash-levels in a Merkle-tree.
+
+  Arguments:
+    image_size: The size of the image to calculate a Merkle-tree for.
+    block_size: The block size, e.g. 4096.
+    digest_size: The size of each hash, e.g. 32 for SHA-256.
+
+  Returns:
+    A tuple where the first argument is an array of offsets and the
+    second is size of the tree, in bytes.
+  """
+  level_offsets = []
+  level_sizes = []
+  tree_size = 0
+
+  num_levels = 0
+  size = image_size
+  while size > block_size:
+    num_blocks = (size + block_size - 1) / block_size
+    level_size = round_to_multiple(num_blocks * digest_size, block_size)
+
+    level_sizes.append(level_size)
+    tree_size += level_size
+    num_levels += 1
+
+    size = level_size
+
+  for n in range(0, num_levels):
+    offset = 0
+    for m in range(n + 1, num_levels):
+      offset += level_sizes[m]
+    level_offsets.append(offset)
+
+  return level_offsets, tree_size
+
+
+# See system/extras/libfec/include/fec/io.h for these definitions.
+FEC_FOOTER_FORMAT = '<LLLLLQ32s'
+FEC_MAGIC = 0xfecfecfe
+
+
+def calc_fec_data_size(image_size, num_roots):
+  """Calculates how much space FEC data will take.
+
+  Args:
+    image_size: The size of the image.
+    num_roots: Number of roots.
+
+  Returns:
+    The number of bytes needed for FEC for an image of the given size
+    and with the requested number of FEC roots.
+
+  Raises:
+    ValueError: If output from the 'fec' tool is invalid.
+
+  """
+  p = subprocess.Popen(
+      ['fec', '--print-fec-size', str(image_size), '--roots', str(num_roots)],
+      stdout=subprocess.PIPE,
+      stderr=subprocess.PIPE)
+  (pout, perr) = p.communicate()
+  retcode = p.wait()
+  if retcode != 0:
+    raise ValueError('Error invoking fec: {}'.format(perr))
+  return int(pout)
+
+
+def generate_fec_data(image_filename, num_roots):
+  """Generate FEC codes for an image.
+
+  Args:
+    image_filename: The filename of the image.
+    num_roots: Number of roots.
+
+  Returns:
+    The FEC data blob.
+
+  Raises:
+    ValueError: If output from the 'fec' tool is invalid.
+  """
+  fec_tmpfile = tempfile.NamedTemporaryFile()
+  subprocess.check_call(
+      ['fec', '--encode', '--roots', str(num_roots), image_filename,
+       fec_tmpfile.name],
+      stderr=open(os.devnull))
+  fec_data = fec_tmpfile.read()
+  footer_size = struct.calcsize(FEC_FOOTER_FORMAT)
+  footer_data = fec_data[-footer_size:]
+  (magic, _, _, num_roots, fec_size, _, _) = struct.unpack(FEC_FOOTER_FORMAT,
+                                                           footer_data)
+  if magic != FEC_MAGIC:
+    raise ValueError('Unexpected magic in FEC footer')
+  return fec_data[0:fec_size]
+
+
+def generate_hash_tree(image, image_size, block_size, hash_alg_name, salt,
+                       digest_padding, hash_level_offsets, tree_size):
+  """Generates a Merkle-tree for a file.
+
+  Args:
+    image: The image, as a file.
+    image_size: The size of the image.
+    block_size: The block size, e.g. 4096.
+    hash_alg_name: The hash algorithm, e.g. 'sha256' or 'sha1'.
+    salt: The salt to use.
+    digest_padding: The padding for each digest.
+    hash_level_offsets: The offsets from calc_hash_level_offsets().
+    tree_size: The size of the tree, in number of bytes.
+
+  Returns:
+    A tuple where the first element is the top-level hash and the
+    second element is the hash-tree.
+  """
+  hash_ret = bytearray(tree_size)
+  hash_src_offset = 0
+  hash_src_size = image_size
+  level_num = 0
+  while hash_src_size > block_size:
+    level_output = ''
+    remaining = hash_src_size
+    while remaining > 0:
+      hasher = hashlib.new(name=hash_alg_name, string=salt)
+      # Only read from the file for the first level - for subsequent
+      # levels, access the array we're building.
+      if level_num == 0:
+        image.seek(hash_src_offset + hash_src_size - remaining)
+        data = image.read(min(remaining, block_size))
+      else:
+        offset = hash_level_offsets[level_num - 1] + hash_src_size - remaining
+        data = hash_ret[offset:offset + block_size]
+      hasher.update(data)
+
+      remaining -= len(data)
+      if len(data) < block_size:
+        hasher.update('\0' * (block_size - len(data)))
+      level_output += hasher.digest()
+      if digest_padding > 0:
+        level_output += '\0' * digest_padding
+
+    padding_needed = (round_to_multiple(
+        len(level_output), block_size) - len(level_output))
+    level_output += '\0' * padding_needed
+
+    # Copy level-output into resulting tree.
+    offset = hash_level_offsets[level_num]
+    hash_ret[offset:offset + len(level_output)] = level_output
+
+    # Continue on to the next level.
+    hash_src_size = len(level_output)
+    level_num += 1
+
+  hasher = hashlib.new(name=hash_alg_name, string=salt)
+  hasher.update(level_output)
+  return hasher.digest(), hash_ret
+
+
+class AvbTool(object):
+  """Object for avbtool command-line tool."""
+
+  def __init__(self):
+    """Initializer method."""
+    self.avb = Avb()
+
+  def _add_common_args(self, sub_parser):
+    """Adds arguments used by several sub-commands.
+
+    Arguments:
+      sub_parser: The parser to add arguments to.
+    """
+    sub_parser.add_argument('--algorithm',
+                            help='Algorithm to use (default: NONE)',
+                            metavar='ALGORITHM',
+                            default='NONE')
+    sub_parser.add_argument('--key',
+                            help='Path to RSA private key file',
+                            metavar='KEY',
+                            required=False)
+    sub_parser.add_argument('--signing_helper',
+                            help='Path to helper used for signing',
+                            metavar='APP',
+                            default=None,
+                            required=False)
+    sub_parser.add_argument('--public_key_metadata',
+                            help='Path to public key metadata file',
+                            metavar='KEY_METADATA',
+                            required=False)
+    sub_parser.add_argument('--rollback_index',
+                            help='Rollback Index',
+                            type=parse_number,
+                            default=0)
+    sub_parser.add_argument('--prop',
+                            help='Add property',
+                            metavar='KEY:VALUE',
+                            action='append')
+    sub_parser.add_argument('--prop_from_file',
+                            help='Add property from file',
+                            metavar='KEY:PATH',
+                            action='append')
+    sub_parser.add_argument('--kernel_cmdline',
+                            help='Add kernel cmdline',
+                            metavar='CMDLINE',
+                            action='append')
+    # TODO(zeuthen): the --setup_rootfs_from_kernel option used to be called
+    # --generate_dm_verity_cmdline_from_hashtree. Remove support for the latter
+    # at some future point.
+    sub_parser.add_argument('--setup_rootfs_from_kernel',
+                            '--generate_dm_verity_cmdline_from_hashtree',
+                            metavar='IMAGE',
+                            help='Adds kernel cmdline to set up IMAGE',
+                            type=argparse.FileType('rb'))
+    sub_parser.add_argument('--include_descriptors_from_image',
+                            help='Include descriptors from image',
+                            metavar='IMAGE',
+                            action='append',
+                            type=argparse.FileType('rb'))
+
+  def run(self, argv):
+    """Command-line processor.
+
+    Arguments:
+      argv: Pass sys.argv from main.
+    """
+    parser = argparse.ArgumentParser()
+    subparsers = parser.add_subparsers(title='subcommands')
+
+    sub_parser = subparsers.add_parser('version',
+                                       help='Prints version of avbtool.')
+    sub_parser.set_defaults(func=self.version)
+
+    sub_parser = subparsers.add_parser('extract_public_key',
+                                       help='Extract public key.')
+    sub_parser.add_argument('--key',
+                            help='Path to RSA private key file',
+                            required=True)
+    sub_parser.add_argument('--output',
+                            help='Output file name',
+                            type=argparse.FileType('wb'),
+                            required=True)
+    sub_parser.set_defaults(func=self.extract_public_key)
+
+    sub_parser = subparsers.add_parser('make_vbmeta_image',
+                                       help='Makes a vbmeta image.')
+    sub_parser.add_argument('--output',
+                            help='Output file name',
+                            type=argparse.FileType('wb'),
+                            required=True)
+    self._add_common_args(sub_parser)
+    sub_parser.add_argument('--chain_partition',
+                            help='Allow signed integrity-data for partition',
+                            metavar='PART_NAME:ROLLBACK_SLOT:KEY_PATH',
+                            action='append')
+    sub_parser.add_argument('--flags',
+                            help='VBMeta flags',
+                            type=parse_number,
+                            default=0)
+    sub_parser.add_argument('--set_hashtree_disabled_flag',
+                            help='Set the HASHTREE_DISABLED flag',
+                            action='store_true')
+    sub_parser.set_defaults(func=self.make_vbmeta_image)
+
+    sub_parser = subparsers.add_parser('add_hash_footer',
+                                       help='Add hashes and footer to image.')
+    sub_parser.add_argument('--image',
+                            help='Image to add hashes to',
+                            type=argparse.FileType('rab+'))
+    sub_parser.add_argument('--partition_size',
+                            help='Partition size',
+                            type=parse_number,
+                            required=True)
+    sub_parser.add_argument('--partition_name',
+                            help='Partition name',
+                            required=True)
+    sub_parser.add_argument('--hash_algorithm',
+                            help='Hash algorithm to use (default: sha256)',
+                            default='sha256')
+    sub_parser.add_argument('--salt',
+                            help='Salt in hex (default: /dev/urandom)')
+    self._add_common_args(sub_parser)
+    sub_parser.set_defaults(func=self.add_hash_footer)
+
+    sub_parser = subparsers.add_parser('add_hashtree_footer',
+                                       help='Add hashtree and footer to image.')
+    sub_parser.add_argument('--image',
+                            help='Image to add hashtree to',
+                            type=argparse.FileType('rab+'))
+    sub_parser.add_argument('--partition_size',
+                            help='Partition size',
+                            type=parse_number,
+                            required=True)
+    sub_parser.add_argument('--partition_name',
+                            help='Partition name',
+                            default=None)
+    sub_parser.add_argument('--hash_algorithm',
+                            help='Hash algorithm to use (default: sha1)',
+                            default='sha1')
+    sub_parser.add_argument('--salt',
+                            help='Salt in hex (default: /dev/urandom)')
+    sub_parser.add_argument('--block_size',
+                            help='Block size (default: 4096)',
+                            type=parse_number,
+                            default=4096)
+    sub_parser.add_argument('--generate_fec',
+                            help='Add forward-error-correction codes',
+                            action='store_true')
+    sub_parser.add_argument('--fec_num_roots',
+                            help='Number of roots for FEC (default: 2)',
+                            type=parse_number,
+                            default=2)
+    sub_parser.add_argument('--calc_max_image_size',
+                            help=('Don\'t store the hashtree or footer - '
+                                  'instead calculate the maximum image size '
+                                  'leaving enough room for hashtree '
+                                  'and metadata with the given partition '
+                                  'size.'),
+                            action='store_true')
+    self._add_common_args(sub_parser)
+    sub_parser.set_defaults(func=self.add_hashtree_footer)
+
+    sub_parser = subparsers.add_parser('erase_footer',
+                                       help='Erase footer from an image.')
+    sub_parser.add_argument('--image',
+                            help='Image with a footer',
+                            type=argparse.FileType('rwb+'),
+                            required=True)
+    sub_parser.add_argument('--keep_hashtree',
+                            help='Keep the hashtree and FEC in the image',
+                            action='store_true')
+    sub_parser.set_defaults(func=self.erase_footer)
+
+    sub_parser = subparsers.add_parser(
+        'info_image',
+        help='Show information about vbmeta or footer.')
+    sub_parser.add_argument('--image',
+                            help='Image to show information about',
+                            type=argparse.FileType('rb'),
+                            required=True)
+    sub_parser.add_argument('--output',
+                            help='Write info to file',
+                            type=argparse.FileType('wt'),
+                            default=sys.stdout)
+    sub_parser.set_defaults(func=self.info_image)
+
+    sub_parser = subparsers.add_parser('set_ab_metadata',
+                                       help='Set A/B metadata.')
+    sub_parser.add_argument('--misc_image',
+                            help=('The misc image to modify. If the image does '
+                                  'not exist, it will be created.'),
+                            type=argparse.FileType('r+b'),
+                            required=True)
+    sub_parser.add_argument('--slot_data',
+                            help=('Slot data of the form "priority", '
+                                  '"tries_remaining", "sucessful_boot" for '
+                                  'slot A followed by the same for slot B, '
+                                  'separated by colons. The default value '
+                                  'is 15:7:0:14:7:0.'),
+                            default='15:7:0:14:7:0')
+    sub_parser.set_defaults(func=self.set_ab_metadata)
+
+    sub_parser = subparsers.add_parser(
+        'make_atx_certificate',
+        help='Create an Android Things eXtension (ATX) certificate.')
+    sub_parser.add_argument('--output',
+                            help='Write certificate to file',
+                            type=argparse.FileType('wb'),
+                            default=sys.stdout)
+    sub_parser.add_argument('--subject',
+                            help=('Path to subject file'),
+                            type=argparse.FileType('rb'),
+                            required=True)
+    sub_parser.add_argument('--subject_key',
+                            help=('Path to subject RSA public key file'),
+                            type=argparse.FileType('rb'),
+                            required=True)
+    sub_parser.add_argument('--subject_key_version',
+                            help=('Version of the subject key'),
+                            type=parse_number,
+                            required=False)
+    sub_parser.add_argument('--subject_is_intermediate_authority',
+                            help=('Generate an intermediate authority '
+                                  'certificate'),
+                            action='store_true')
+    sub_parser.add_argument('--authority_key',
+                            help='Path to authority RSA private key file',
+                            required=False)
+    sub_parser.add_argument('--signing_helper',
+                            help='Path to helper used for signing',
+                            metavar='APP',
+                            default=None,
+                            required=False)
+    sub_parser.set_defaults(func=self.make_atx_certificate)
+
+    sub_parser = subparsers.add_parser(
+        'make_atx_permanent_attributes',
+        help='Create Android Things eXtension (ATX) permanent attributes.')
+    sub_parser.add_argument('--output',
+                            help='Write attributes to file',
+                            type=argparse.FileType('wb'),
+                            default=sys.stdout)
+    sub_parser.add_argument('--root_authority_key',
+                            help='Path to authority RSA public key file',
+                            type=argparse.FileType('rb'),
+                            required=True)
+    sub_parser.add_argument('--product_id',
+                            help=('Path to Product ID file'),
+                            type=argparse.FileType('rb'),
+                            required=True)
+    sub_parser.set_defaults(func=self.make_atx_permanent_attributes)
+
+    sub_parser = subparsers.add_parser(
+        'make_atx_metadata',
+        help='Create Android Things eXtension (ATX) metadata.')
+    sub_parser.add_argument('--output',
+                            help='Write metadata to file',
+                            type=argparse.FileType('wb'),
+                            default=sys.stdout)
+    sub_parser.add_argument('--intermediate_key_certificate',
+                            help='Path to intermediate key certificate file',
+                            type=argparse.FileType('rb'),
+                            required=True)
+    sub_parser.add_argument('--product_key_certificate',
+                            help='Path to product key certificate file',
+                            type=argparse.FileType('rb'),
+                            required=True)
+    sub_parser.add_argument('--google_key_version',
+                            help=('Version of the Google signing key'),
+                            type=parse_number,
+                            default=0)
+    sub_parser.set_defaults(func=self.make_atx_metadata)
+
+    args = parser.parse_args(argv[1:])
+    try:
+      args.func(args)
+    except AvbError as e:
+      sys.stderr.write('{}: {}\n'.format(argv[0], e.message))
+      sys.exit(1)
+
+  def version(self, _):
+    """Implements the 'version' sub-command."""
+    print '{}.{}'.format(AVB_VERSION_MAJOR, AVB_VERSION_MINOR)
+
+  def extract_public_key(self, args):
+    """Implements the 'extract_public_key' sub-command."""
+    self.avb.extract_public_key(args.key, args.output)
+
+  def make_vbmeta_image(self, args):
+    """Implements the 'make_vbmeta_image' sub-command."""
+    if args.set_hashtree_disabled_flag:
+      args.flags |= AVB_VBMETA_IMAGE_FLAGS_HASHTREE_DISABLED
+    self.avb.make_vbmeta_image(args.output, args.chain_partition,
+                               args.algorithm, args.key,
+                               args.public_key_metadata, args.rollback_index,
+                               args.flags, args.prop, args.prop_from_file,
+                               args.kernel_cmdline,
+                               args.setup_rootfs_from_kernel,
+                               args.include_descriptors_from_image, args.signing_helper)
+
+  def add_hash_footer(self, args):
+    """Implements the 'add_hash_footer' sub-command."""
+    self.avb.add_hash_footer(args.image.name, args.partition_size,
+                             args.partition_name, args.hash_algorithm,
+                             args.salt, args.algorithm, args.key,
+                             args.public_key_metadata, args.rollback_index,
+                             args.prop, args.prop_from_file,
+                             args.kernel_cmdline,
+                             args.setup_rootfs_from_kernel,
+                             args.include_descriptors_from_image, args.signing_helper)
+
+  def add_hashtree_footer(self, args):
+    """Implements the 'add_hashtree_footer' sub-command."""
+    self.avb.add_hashtree_footer(args.image.name if args.image else None,
+                                 args.partition_size,
+                                 args.partition_name,
+                                 args.generate_fec, args.fec_num_roots,
+                                 args.hash_algorithm, args.block_size,
+                                 args.salt, args.algorithm, args.key,
+                                 args.public_key_metadata,
+                                 args.rollback_index, args.prop,
+                                 args.prop_from_file,
+                                 args.kernel_cmdline,
+                                 args.setup_rootfs_from_kernel,
+                                 args.include_descriptors_from_image,
+                                 args.calc_max_image_size, args.signing_helper)
+
+  def erase_footer(self, args):
+    """Implements the 'erase_footer' sub-command."""
+    self.avb.erase_footer(args.image.name, args.keep_hashtree)
+
+  def set_ab_metadata(self, args):
+    """Implements the 'set_ab_metadata' sub-command."""
+    self.avb.set_ab_metadata(args.misc_image, args.slot_data)
+
+  def info_image(self, args):
+    """Implements the 'info_image' sub-command."""
+    self.avb.info_image(args.image.name, args.output)
+
+  def make_atx_certificate(self, args):
+    """Implements the 'make_atx_certificate' sub-command."""
+    self.avb.make_atx_certificate(args.output, args.authority_key,
+                                  args.subject_key.read(),
+                                  args.subject_key_version,
+                                  args.subject.read(),
+                                  args.subject_is_intermediate_authority,
+                                  args.signing_helper)
+
+  def make_atx_permanent_attributes(self, args):
+    """Implements the 'make_atx_permanent_attributes' sub-command."""
+    self.avb.make_atx_permanent_attributes(args.output,
+                                           args.root_authority_key.read(),
+                                           args.product_id.read())
+
+  def make_atx_metadata(self, args):
+    """Implements the 'make_atx_metadata' sub-command."""
+    self.avb.make_atx_metadata(args.output,
+                               args.intermediate_key_certificate.read(),
+                               args.product_key_certificate.read(),
+                               args.google_key_version)
+
+
+if __name__ == '__main__':
+  tool = AvbTool()
+  tool.run(sys.argv)
diff --git a/avb/boot_control/avb_ops_device.c b/avb/boot_control/avb_ops_device.c
new file mode 100644
index 0000000..fbee76c
--- /dev/null
+++ b/avb/boot_control/avb_ops_device.c
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/fs.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <cutils/properties.h>
+#include <fs_mgr.h>
+
+#include "avb_ops_device.h"
+
+/* Open the appropriate fstab file and fallback to /fstab.device if
+ * that's what's being used.
+ */
+static struct fstab* open_fstab(void) {
+  char propbuf[PROPERTY_VALUE_MAX];
+  char fstab_name[PROPERTY_VALUE_MAX + 32];
+  struct fstab* fstab;
+
+  property_get("ro.hardware", propbuf, "");
+  snprintf(fstab_name, sizeof(fstab_name), "/fstab.%s", propbuf);
+  fstab = fs_mgr_read_fstab(fstab_name);
+  if (fstab != NULL) {
+    return fstab;
+  }
+
+  fstab = fs_mgr_read_fstab("/fstab.device");
+  return fstab;
+}
+
+static int open_partition(const char* name, int flags) {
+  char* path;
+  int fd;
+  struct fstab* fstab;
+  struct fstab_rec* record;
+
+  /* We can't use fs_mgr to look up |name| because fstab doesn't list
+   * every slot partition (it uses the slotselect option to mask the
+   * suffix) and |slot| is expected to be of that form, e.g. boot_a.
+   *
+   * We can however assume that there's an entry for the /misc mount
+   * point and use that to get the device file for the misc
+   * partition. From there we'll assume that a by-name scheme is used
+   * so we can just replace the trailing "misc" by the given |name|,
+   * e.g.
+   *
+   *   /dev/block/platform/soc.0/7824900.sdhci/by-name/misc ->
+   *   /dev/block/platform/soc.0/7824900.sdhci/by-name/boot_a
+   *
+   * If needed, it's possible to relax this assumption in the future
+   * by trawling /sys/block looking for the appropriate sibling of
+   * misc and then finding an entry in /dev matching the sysfs entry.
+   */
+
+  fstab = open_fstab();
+  if (fstab == NULL) {
+    return -1;
+  }
+  record = fs_mgr_get_entry_for_mount_point(fstab, "/misc");
+  if (record == NULL) {
+    fs_mgr_free_fstab(fstab);
+    return -1;
+  }
+  if (strcmp(name, "misc") == 0) {
+    path = strdup(record->blk_device);
+  } else {
+    size_t trimmed_len, name_len;
+    const char* end_slash = strrchr(record->blk_device, '/');
+    if (end_slash == NULL) {
+      fs_mgr_free_fstab(fstab);
+      return -1;
+    }
+    trimmed_len = end_slash - record->blk_device + 1;
+    name_len = strlen(name);
+    path = calloc(trimmed_len + name_len + 1, 1);
+    strncpy(path, record->blk_device, trimmed_len);
+    strncpy(path + trimmed_len, name, name_len);
+  }
+  fs_mgr_free_fstab(fstab);
+
+  fd = open(path, flags);
+  free(path);
+
+  return fd;
+}
+
+static AvbIOResult read_from_partition(AvbOps* ops,
+                                       const char* partition,
+                                       int64_t offset,
+                                       size_t num_bytes,
+                                       void* buffer,
+                                       size_t* out_num_read) {
+  int fd;
+  off_t where;
+  ssize_t num_read;
+  AvbIOResult ret;
+
+  fd = open_partition(partition, O_RDONLY);
+  if (fd == -1) {
+    avb_errorv("Error opening \"", partition, "\" partition.\n", NULL);
+    ret = AVB_IO_RESULT_ERROR_IO;
+    goto out;
+  }
+
+  where = lseek(fd, offset, SEEK_SET);
+  if (where == -1) {
+    avb_error("Error seeking to offset.\n");
+    ret = AVB_IO_RESULT_ERROR_IO;
+    goto out;
+  }
+  if (where != offset) {
+    avb_error("Error seeking to offset.\n");
+    ret = AVB_IO_RESULT_ERROR_RANGE_OUTSIDE_PARTITION;
+    goto out;
+  }
+
+  /* On Linux, we never get partial reads from block devices (except
+   * for EOF).
+   */
+  num_read = read(fd, buffer, num_bytes);
+  if (num_read == -1) {
+    avb_error("Error reading data.\n");
+    ret = AVB_IO_RESULT_ERROR_IO;
+    goto out;
+  }
+  if (out_num_read != NULL) {
+    *out_num_read = num_read;
+  }
+
+  ret = AVB_IO_RESULT_OK;
+
+out:
+  if (fd != -1) {
+    if (close(fd) != 0) {
+      avb_error("Error closing file descriptor.\n");
+    }
+  }
+  return ret;
+}
+
+static AvbIOResult write_to_partition(AvbOps* ops,
+                                      const char* partition,
+                                      int64_t offset,
+                                      size_t num_bytes,
+                                      const void* buffer) {
+  int fd;
+  off_t where;
+  ssize_t num_written;
+  AvbIOResult ret;
+
+  fd = open_partition(partition, O_WRONLY);
+  if (fd == -1) {
+    avb_errorv("Error opening \"", partition, "\" partition.\n", NULL);
+    ret = AVB_IO_RESULT_ERROR_IO;
+    goto out;
+  }
+
+  where = lseek(fd, offset, SEEK_SET);
+  if (where == -1) {
+    avb_error("Error seeking to offset.\n");
+    ret = AVB_IO_RESULT_ERROR_IO;
+    goto out;
+  }
+  if (where != offset) {
+    avb_error("Error seeking to offset.\n");
+    ret = AVB_IO_RESULT_ERROR_RANGE_OUTSIDE_PARTITION;
+    goto out;
+  }
+
+  /* On Linux, we never get partial writes on block devices. */
+  num_written = write(fd, buffer, num_bytes);
+  if (num_written == -1) {
+    avb_error("Error writing data.\n");
+    ret = AVB_IO_RESULT_ERROR_IO;
+    goto out;
+  }
+
+  ret = AVB_IO_RESULT_OK;
+
+out:
+  if (fd != -1) {
+    if (close(fd) != 0) {
+      avb_error("Error closing file descriptor.\n");
+    }
+  }
+  return ret;
+}
+
+AvbABOps* avb_ops_device_new(void) {
+  AvbABOps* ab_ops;
+
+  ab_ops = calloc(1, sizeof(AvbABOps));
+  if (ab_ops == NULL) {
+    avb_error("Error allocating memory for AvbABOps.\n");
+    goto out;
+  }
+
+  ab_ops->ops = calloc(1, sizeof(AvbOps));
+  if (ab_ops->ops == NULL) {
+    avb_error("Error allocating memory for AvbOps.\n");
+    free(ab_ops);
+    goto out;
+  }
+
+  /* We only need these operations since that's all what is being used
+   * by the A/B routines.
+   */
+  ab_ops->ops->read_from_partition = read_from_partition;
+  ab_ops->ops->write_to_partition = write_to_partition;
+  ab_ops->read_ab_metadata = avb_ab_data_read;
+  ab_ops->write_ab_metadata = avb_ab_data_write;
+
+out:
+  return ab_ops;
+}
+
+void avb_ops_device_free(AvbABOps* ab_ops) {
+  free(ab_ops->ops);
+  free(ab_ops);
+}
diff --git a/avb/boot_control/avb_ops_device.h b/avb/boot_control/avb_ops_device.h
new file mode 100644
index 0000000..ad7bddb
--- /dev/null
+++ b/avb/boot_control/avb_ops_device.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef AVB_OPS_DEVICE_H_
+#define AVB_OPS_DEVICE_H_
+
+#include <libavb_ab/libavb_ab.h>
+
+/* Allocates an AvbOps instance suitable for use on the
+ * device. Returns NULL on OOM.
+ *
+ * Free with avb_ops_device_free().
+ */
+AvbABOps* avb_ops_device_new(void);
+
+/* Frees an AvbOps instance previously allocated with avb_ops_device_new(). */
+void avb_ops_device_free(AvbABOps* ab_ops);
+
+#endif /* AVB_OPS_DEVICE_H_ */
diff --git a/avb/boot_control/boot_control_avb.c b/avb/boot_control/boot_control_avb.c
new file mode 100644
index 0000000..8775d09
--- /dev/null
+++ b/avb/boot_control/boot_control_avb.c
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <errno.h>
+#include <string.h>
+
+#include <cutils/properties.h>
+#include <hardware/boot_control.h>
+#include <hardware/hardware.h>
+
+#include "avb_ops_device.h"
+
+static AvbABOps* ab_ops = NULL;
+
+static void module_init(boot_control_module_t* module) {
+  if (ab_ops != NULL) {
+    return;
+  }
+
+  ab_ops = avb_ops_device_new();
+  if (ab_ops == NULL) {
+    avb_error("Unable to allocate AvbOps instance.\n");
+  }
+}
+
+static unsigned int module_getNumberSlots(boot_control_module_t* module) {
+  return 2;
+}
+
+static unsigned int module_getCurrentSlot(boot_control_module_t* module) {
+  char propbuf[PROPERTY_VALUE_MAX];
+
+  property_get("ro.boot.slot_suffix", propbuf, "");
+  if (strcmp(propbuf, "_a") == 0) {
+    return 0;
+  } else if (strcmp(propbuf, "_b") == 0) {
+    return 1;
+  } else {
+    avb_errorv("Unexpected slot suffix '", propbuf, "'.\n", NULL);
+    return 0;
+  }
+  return 0;
+}
+
+static int module_markBootSuccessful(boot_control_module_t* module) {
+  if (avb_ab_mark_slot_successful(ab_ops, module_getCurrentSlot(module)) ==
+      AVB_IO_RESULT_OK) {
+    return 0;
+  } else {
+    return -EIO;
+  }
+}
+
+static int module_setActiveBootSlot(boot_control_module_t* module,
+                                    unsigned int slot) {
+  if (avb_ab_mark_slot_active(ab_ops, slot) == AVB_IO_RESULT_OK) {
+    return 0;
+  } else {
+    return -EIO;
+  }
+}
+
+static int module_setSlotAsUnbootable(struct boot_control_module* module,
+                                      unsigned int slot) {
+  if (avb_ab_mark_slot_unbootable(ab_ops, slot) == AVB_IO_RESULT_OK) {
+    return 0;
+  } else {
+    return -EIO;
+  }
+}
+
+static int module_isSlotBootable(struct boot_control_module* module,
+                                 unsigned int slot) {
+  AvbABData ab_data;
+  bool is_bootable;
+
+  avb_assert(slot < 2);
+
+  if (avb_ab_data_read(ab_ops, &ab_data) != AVB_IO_RESULT_OK) {
+    return -EIO;
+  }
+
+  is_bootable = (ab_data.slots[slot].priority > 0) &&
+                (ab_data.slots[slot].successful_boot ||
+                 (ab_data.slots[slot].tries_remaining > 0));
+
+  return is_bootable ? 1 : 0;
+}
+
+static int module_isSlotMarkedSuccessful(struct boot_control_module* module,
+                                         unsigned int slot) {
+  AvbABData ab_data;
+  bool is_marked_successful;
+
+  avb_assert(slot < 2);
+
+  if (avb_ab_data_read(ab_ops, &ab_data) != AVB_IO_RESULT_OK) {
+    return -EIO;
+  }
+
+  is_marked_successful = ab_data.slots[slot].successful_boot;
+
+  return is_marked_successful ? 1 : 0;
+}
+
+static const char* module_getSuffix(boot_control_module_t* module,
+                                    unsigned int slot) {
+  static const char* suffix[2] = {"_a", "_b"};
+  if (slot >= 2) {
+    return NULL;
+  }
+  return suffix[slot];
+}
+
+static struct hw_module_methods_t module_methods = {
+    .open = NULL,
+};
+
+boot_control_module_t HAL_MODULE_INFO_SYM = {
+    .common =
+        {
+            .tag = HARDWARE_MODULE_TAG,
+            .module_api_version = BOOT_CONTROL_MODULE_API_VERSION_0_1,
+            .hal_api_version = HARDWARE_HAL_API_VERSION,
+            .id = BOOT_CONTROL_HARDWARE_MODULE_ID,
+            .name = "AVB implementation of boot_control HAL",
+            .author = "The Android Open Source Project",
+            .methods = &module_methods,
+        },
+    .init = module_init,
+    .getNumberSlots = module_getNumberSlots,
+    .getCurrentSlot = module_getCurrentSlot,
+    .markBootSuccessful = module_markBootSuccessful,
+    .setActiveBootSlot = module_setActiveBootSlot,
+    .setSlotAsUnbootable = module_setSlotAsUnbootable,
+    .isSlotBootable = module_isSlotBootable,
+    .getSuffix = module_getSuffix,
+    .isSlotMarkedSuccessful = module_isSlotMarkedSuccessful,
+};
diff --git a/avb/examples/uefi/Makefile b/avb/examples/uefi/Makefile
new file mode 100644
index 0000000..eae589f
--- /dev/null
+++ b/avb/examples/uefi/Makefile
@@ -0,0 +1,98 @@
+#
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+#
+
+# TODO(zeuthen): This is not invoked by Android build system and is
+# intended to aid users to locally build the EFI application. It would
+# be nice to add gnu-efi/ to external and rewrite this as an
+# Android.mk file.
+
+EFI_CC      = gcc
+EFI_LD      = ld
+EFI_OBJCOPY = objcopy
+AVB         = ../..
+GNUEFI      = /usr/include/efi
+
+EFI_SRC_FILES = \
+    main.c \
+    uefi_avb_sysdeps.c \
+    uefi_avb_ops.c \
+    uefi_avb_util.c \
+    uefi_avb_boot.c \
+	$(AVB)/libavb/avb_chain_partition_descriptor.c \
+    $(AVB)/libavb/avb_crc32.c \
+    $(AVB)/libavb/avb_crypto.c \
+    $(AVB)/libavb/avb_descriptor.c \
+    $(AVB)/libavb/avb_footer.c \
+    $(AVB)/libavb/avb_hash_descriptor.c \
+    $(AVB)/libavb/avb_hashtree_descriptor.c \
+    $(AVB)/libavb/avb_kernel_cmdline_descriptor.c \
+    $(AVB)/libavb/avb_property_descriptor.c \
+    $(AVB)/libavb/avb_rsa.c \
+    $(AVB)/libavb/avb_sha256.c \
+    $(AVB)/libavb/avb_sha512.c \
+    $(AVB)/libavb/avb_slot_verify.c \
+    $(AVB)/libavb/avb_util.c \
+    $(AVB)/libavb/avb_vbmeta_image.c \
+    $(AVB)/libavb_ab/avb_ab_flow.c
+
+EFI_OBJ_FILES   = $(notdir $(patsubst %.S,%.o,$(patsubst %.c,%.o,$(EFI_SRC_FILES))))
+EFI_TARGET      = avb_bootloader.efi
+EFI_SHARED_OBJ  = $(patsubst %.efi,%.so,$(EFI_TARGET))
+
+EFI_CFLAGS = \
+    -DAVB_COMPILATION -std=gnu99 \
+	-I$(ANDROID_BUILD_TOP)/system/core/mkbootimg/ \
+    -I$(AVB) \
+    -I$(GNUEFI) \
+    -I$(GNUEFI)/x86_64 \
+    -I$(GNUEFI)/protocol \
+    -fno-stack-protector -fpic \
+    -fshort-wchar -mno-red-zone -Wall \
+    -DEFI_FUNCTION_WRAPPER
+
+EFI_LDFLAGS = \
+    -nostdlib -znocombreloc -T /usr/lib/elf_x86_64_efi.lds -shared \
+    -Bsymbolic -L /usr/lib/ /usr/lib/crt0-efi-x86_64.o \
+    /usr/lib/elf_x86_64_efi.lds
+
+EFI_LDLIBS  = -lefi -lgnuefi
+
+EFI_OBJCOPY_SECTIONS    = -j .text -j .sdata -j .data -j .dynamic \
+                            -j .dynsym  -j .rel -j .rela -j .reloc
+
+EFI_OBJCOPY_TARGET      = --target=efi-app-x86_64
+
+all: $(EFI_TARGET)
+
+$(EFI_OBJ_FILES): $(EFI_SRC_FILES)
+	$(EFI_CC) $(EFI_CFLAGS) -c $(EFI_SRC_FILES)
+
+$(EFI_SHARED_OBJ): $(EFI_OBJ_FILES)
+	$(EFI_LD) $(EFI_LDFLAGS) $(EFI_OBJ_FILES) -o $@ $(EFI_LDLIBS)
+
+$(EFI_TARGET): $(EFI_SHARED_OBJ)
+	$(EFI_OBJCOPY) $(EFI_OBJCOPY_SECTIONS) $(EFI_OBJCOPY_TARGET) $^ $@
+
+clean:
+	rm -f  $(EFI_OBJ_FILES) $(EFI_SHARED_OBJ) $(EFI_TARGET) *~
diff --git a/avb/examples/uefi/main.c b/avb/examples/uefi/main.c
new file mode 100644
index 0000000..8d9c0ee
--- /dev/null
+++ b/avb/examples/uefi/main.c
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <efi.h>
+#include <efilib.h>
+
+#include <libavb_ab/libavb_ab.h>
+
+#include "uefi_avb_boot.h"
+#include "uefi_avb_ops.h"
+
+EFI_STATUS EFIAPI efi_main(EFI_HANDLE ImageHandle,
+                           EFI_SYSTEM_TABLE* SystemTable) {
+  AvbOps* ops;
+  AvbABFlowResult ab_result;
+  AvbSlotVerifyData* slot_data;
+  UEFIAvbBootKernelResult boot_result;
+  const char* requested_partitions[] = {"boot", NULL};
+  bool unlocked = true;
+
+  InitializeLib(ImageHandle, SystemTable);
+
+  avb_print("UEFI AVB-based bootloader\n");
+
+  ops = uefi_avb_ops_new(ImageHandle);
+  if (ops == NULL) {
+    avb_fatal("Error allocating AvbOps.\n");
+  }
+
+  if (ops->read_is_device_unlocked(ops, &unlocked) != AVB_IO_RESULT_OK) {
+    avb_fatal("Error determining whether device is unlocked.\n");
+  }
+  avb_printv("read_is_device_unlocked() ops returned that device is ",
+             unlocked ? "UNLOCKED" : "LOCKED",
+             "\n",
+             NULL);
+
+  ab_result = avb_ab_flow(ops->ab_ops,
+                          requested_partitions,
+                          unlocked /* allow_verification_error */,
+                          &slot_data);
+  avb_printv("avb_ab_flow() returned ",
+             avb_ab_flow_result_to_string(ab_result),
+             "\n",
+             NULL);
+  switch (ab_result) {
+    case AVB_AB_FLOW_RESULT_OK:
+    case AVB_AB_FLOW_RESULT_OK_WITH_VERIFICATION_ERROR:
+      avb_printv("slot_suffix: ", slot_data->ab_suffix, "\n", NULL);
+      avb_printv("cmdline:     ", slot_data->cmdline, "\n", NULL);
+      /* Pass 'skip_initramfs' since we're not booting into recovery mode. */
+      boot_result =
+          uefi_avb_boot_kernel(ImageHandle, slot_data, "skip_initramfs");
+      avb_fatalv("uefi_avb_boot_kernel() failed with error ",
+                 uefi_avb_boot_kernel_result_to_string(boot_result),
+                 "\n",
+                 NULL);
+      avb_slot_verify_data_free(slot_data);
+      break;
+    case AVB_AB_FLOW_RESULT_ERROR_OOM:
+      avb_fatal("OOM error while doing A/B select flow.\n");
+      break;
+    case AVB_AB_FLOW_RESULT_ERROR_IO:
+      avb_fatal("I/O error while doing A/B select flow.\n");
+      break;
+    case AVB_AB_FLOW_RESULT_ERROR_NO_BOOTABLE_SLOTS:
+      avb_fatal("No bootable slots - enter repair mode\n");
+      break;
+  }
+  uefi_avb_ops_free(ops);
+
+  return EFI_SUCCESS;
+}
diff --git a/avb/examples/uefi/uefi_avb_boot.c b/avb/examples/uefi/uefi_avb_boot.c
new file mode 100644
index 0000000..800c9a1
--- /dev/null
+++ b/avb/examples/uefi/uefi_avb_boot.c
@@ -0,0 +1,327 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <efi.h>
+#include <efilib.h>
+
+#include "bootimg.h"
+
+#include "uefi_avb_boot.h"
+#include "uefi_avb_util.h"
+
+/* See Documentation/x86/boot.txt for this struct and more information
+ * about the boot/handover protocol.
+ */
+
+#define SETUP_MAGIC 0x53726448 /* "HdrS" */
+
+struct SetupHeader {
+  UINT8 boot_sector[0x01f1];
+  UINT8 setup_secs;
+  UINT16 root_flags;
+  UINT32 sys_size;
+  UINT16 ram_size;
+  UINT16 video_mode;
+  UINT16 root_dev;
+  UINT16 signature;
+  UINT16 jump;
+  UINT32 header;
+  UINT16 version;
+  UINT16 su_switch;
+  UINT16 setup_seg;
+  UINT16 start_sys;
+  UINT16 kernel_ver;
+  UINT8 loader_id;
+  UINT8 load_flags;
+  UINT16 movesize;
+  UINT32 code32_start;
+  UINT32 ramdisk_start;
+  UINT32 ramdisk_len;
+  UINT32 bootsect_kludge;
+  UINT16 heap_end;
+  UINT8 ext_loader_ver;
+  UINT8 ext_loader_type;
+  UINT32 cmd_line_ptr;
+  UINT32 ramdisk_max;
+  UINT32 kernel_alignment;
+  UINT8 relocatable_kernel;
+  UINT8 min_alignment;
+  UINT16 xloadflags;
+  UINT32 cmdline_size;
+  UINT32 hardware_subarch;
+  UINT64 hardware_subarch_data;
+  UINT32 payload_offset;
+  UINT32 payload_length;
+  UINT64 setup_data;
+  UINT64 pref_address;
+  UINT32 init_size;
+  UINT32 handover_offset;
+} __attribute__((packed));
+
+#ifdef __x86_64__
+typedef VOID (*handover_f)(VOID* image,
+                           EFI_SYSTEM_TABLE* table,
+                           struct SetupHeader* setup);
+static inline VOID linux_efi_handover(EFI_HANDLE image,
+                                      struct SetupHeader* setup) {
+  handover_f handover;
+
+  asm volatile("cli");
+  handover =
+      (handover_f)((UINTN)setup->code32_start + 512 + setup->handover_offset);
+  handover(image, ST, setup);
+}
+#else
+typedef VOID (*handover_f)(VOID* image,
+                           EFI_SYSTEM_TABLE* table,
+                           struct SetupHeader* setup)
+    __attribute__((regparm(0)));
+static inline VOID linux_efi_handover(EFI_HANDLE image,
+                                      struct SetupHeader* setup) {
+  handover_f handover;
+
+  handover = (handover_f)((UINTN)setup->code32_start + setup->handover_offset);
+  handover(image, ST, setup);
+}
+#endif
+
+static size_t round_up(size_t value, size_t size) {
+  size_t ret = value + size - 1;
+  ret /= size;
+  ret *= size;
+  return ret;
+}
+
+UEFIAvbBootKernelResult uefi_avb_boot_kernel(EFI_HANDLE efi_image_handle,
+                                             AvbSlotVerifyData* slot_data,
+                                             const char* cmdline_extra) {
+  UEFIAvbBootKernelResult ret;
+  const boot_img_hdr* header;
+  EFI_STATUS err;
+  UINT8* kernel_buf = NULL;
+  UINT8* initramfs_buf = NULL;
+  UINT8* cmdline_utf8 = NULL;
+  AvbPartitionData* boot;
+  size_t offset;
+  uint64_t total_size;
+  size_t initramfs_size;
+  size_t cmdline_first_len;
+  size_t cmdline_second_len;
+  size_t cmdline_extra_len;
+  size_t cmdline_utf8_len;
+  struct SetupHeader* image_setup;
+  struct SetupHeader* setup;
+  EFI_PHYSICAL_ADDRESS addr;
+
+  if (slot_data->num_loaded_partitions != 1) {
+    avb_error("No boot partition.\n");
+    ret = UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_PARTITION_INVALID_FORMAT;
+    goto out;
+  }
+
+  boot = &slot_data->loaded_partitions[0];
+  if (avb_strcmp(boot->partition_name, "boot") != 0) {
+    avb_errorv(
+        "Unexpected partition name '", boot->partition_name, "'.\n", NULL);
+    ret = UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_PARTITION_INVALID_FORMAT;
+    goto out;
+  }
+
+  header = (const boot_img_hdr*)boot->data;
+
+  /* Check boot image header magic field. */
+  if (avb_memcmp(BOOT_MAGIC, header->magic, BOOT_MAGIC_SIZE)) {
+    avb_error("Wrong boot image header magic.\n");
+    ret = UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_PARTITION_INVALID_FORMAT;
+    goto out;
+  }
+
+  /* Sanity check header. */
+  total_size = header->kernel_size;
+  if (!avb_safe_add_to(&total_size, header->ramdisk_size) ||
+      !avb_safe_add_to(&total_size, header->second_size)) {
+    avb_error("Overflow while adding sizes of kernel and initramfs.\n");
+    ret = UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_PARTITION_INVALID_FORMAT;
+    goto out;
+  }
+  if (total_size > boot->data_size) {
+    avb_error("Invalid kernel/initramfs sizes.\n");
+    ret = UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_PARTITION_INVALID_FORMAT;
+    goto out;
+  }
+
+  /* The kernel has to be in its own specific memory pool. */
+  err = uefi_call_wrapper(BS->AllocatePool,
+                          NUM_ARGS_ALLOCATE_POOL,
+                          EfiLoaderCode,
+                          header->kernel_size,
+                          &kernel_buf);
+  if (EFI_ERROR(err)) {
+    avb_error("Could not allocate kernel buffer.\n");
+    ret = UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_OOM;
+    goto out;
+  }
+  avb_memcpy(kernel_buf, boot->data + header->page_size, header->kernel_size);
+
+  /* Ditto for the initrd. */
+  initramfs_buf = NULL;
+  initramfs_size = header->ramdisk_size + header->second_size;
+  if (initramfs_size > 0) {
+    err = uefi_call_wrapper(BS->AllocatePool,
+                            NUM_ARGS_ALLOCATE_POOL,
+                            EfiLoaderCode,
+                            initramfs_size,
+                            &initramfs_buf);
+    if (EFI_ERROR(err)) {
+      avb_error("Could not allocate initrd buffer.\n");
+      ret = UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_OOM;
+      goto out;
+    }
+    /* Concatente the first and second initramfs. */
+    offset = header->page_size;
+    offset += round_up(header->kernel_size, header->page_size);
+    avb_memcpy(initramfs_buf, boot->data + offset, header->ramdisk_size);
+    offset += round_up(header->ramdisk_size, header->page_size);
+    avb_memcpy(initramfs_buf, boot->data + offset, header->second_size);
+  }
+
+  /* Prepare the command-line. */
+  cmdline_first_len = avb_strlen((const char*)header->cmdline);
+  cmdline_second_len = avb_strlen(slot_data->cmdline);
+  cmdline_extra_len = cmdline_extra != NULL ? avb_strlen(cmdline_extra) : 0;
+  if (cmdline_extra_len > 0) {
+    cmdline_extra_len += 1;
+  }
+  cmdline_utf8_len =
+      cmdline_first_len + 1 + cmdline_second_len + 1 + cmdline_extra_len;
+  err = uefi_call_wrapper(BS->AllocatePool,
+                          NUM_ARGS_ALLOCATE_POOL,
+                          EfiLoaderCode,
+                          cmdline_utf8_len,
+                          &cmdline_utf8);
+  if (EFI_ERROR(err)) {
+    avb_error("Could not allocate kernel cmdline.\n");
+    ret = UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_OOM;
+    goto out;
+  }
+  offset = 0;
+  avb_memcpy(cmdline_utf8, header->cmdline, cmdline_first_len);
+  offset += cmdline_first_len;
+  cmdline_utf8[offset] = ' ';
+  offset += 1;
+  avb_memcpy(cmdline_utf8 + offset, slot_data->cmdline, cmdline_second_len);
+  offset += cmdline_second_len;
+  if (cmdline_extra_len > 0) {
+    cmdline_utf8[offset] = ' ';
+    avb_memcpy(cmdline_utf8 + offset + 1, cmdline_extra, cmdline_extra_len - 1);
+    offset += cmdline_extra_len;
+  }
+  cmdline_utf8[offset] = '\0';
+  offset += 1;
+  avb_assert(offset == cmdline_utf8_len);
+
+  /* Now set up the EFI handover. */
+  image_setup = (struct SetupHeader*)kernel_buf;
+  if (image_setup->signature != 0xAA55 || image_setup->header != SETUP_MAGIC) {
+    avb_error("Wrong kernel header magic.\n");
+    ret = UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_KERNEL_INVALID_FORMAT;
+    goto out;
+  }
+
+  if (image_setup->version < 0x20b) {
+    avb_error("Wrong version.\n");
+    ret = UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_KERNEL_INVALID_FORMAT;
+    goto out;
+  }
+
+  if (!image_setup->relocatable_kernel) {
+    avb_error("Kernel is not relocatable.\n");
+    ret = UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_KERNEL_INVALID_FORMAT;
+    goto out;
+  }
+
+  addr = 0x3fffffff;
+  err = uefi_call_wrapper(BS->AllocatePages,
+                          4,
+                          AllocateMaxAddress,
+                          EfiLoaderData,
+                          EFI_SIZE_TO_PAGES(0x4000),
+                          &addr);
+  if (EFI_ERROR(err)) {
+    avb_error("Could not allocate setup buffer.\n");
+    ret = UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_OOM;
+    goto out;
+  }
+  setup = (struct SetupHeader*)(UINTN)addr;
+  avb_memset(setup, '\0', 0x4000);
+  avb_memcpy(setup, image_setup, sizeof(struct SetupHeader));
+  setup->loader_id = 0xff;
+  setup->code32_start =
+      ((uintptr_t)kernel_buf) + (image_setup->setup_secs + 1) * 512;
+  setup->cmd_line_ptr = (uintptr_t)cmdline_utf8;
+
+  setup->ramdisk_start = (uintptr_t)initramfs_buf;
+  setup->ramdisk_len = (uintptr_t)initramfs_size;
+
+  /* Jump to the kernel. */
+  linux_efi_handover(efi_image_handle, setup);
+
+  ret = UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_START_KERNEL;
+out:
+  return ret;
+}
+
+const char* uefi_avb_boot_kernel_result_to_string(
+    UEFIAvbBootKernelResult result) {
+  const char* ret = NULL;
+
+  switch (result) {
+    case UEFI_AVB_BOOT_KERNEL_RESULT_OK:
+      ret = "OK";
+      break;
+    case UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_OOM:
+      ret = "ERROR_OEM";
+      break;
+    case UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_IO:
+      ret = "ERROR_IO";
+      break;
+    case UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_PARTITION_INVALID_FORMAT:
+      ret = "ERROR_PARTITION_INVALID_FORMAT";
+      break;
+    case UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_KERNEL_INVALID_FORMAT:
+      ret = "ERROR_KERNEL_INVALID_FORMAT";
+      break;
+    case UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_START_KERNEL:
+      ret = "ERROR_START_KERNEL";
+      break;
+      /* Do not add a 'default:' case here because of -Wswitch. */
+  }
+
+  if (ret == NULL) {
+    avb_error("Unknown UEFIAvbBootKernelResult value.\n");
+    ret = "(unknown)";
+  }
+
+  return ret;
+}
diff --git a/avb/examples/uefi/uefi_avb_boot.h b/avb/examples/uefi/uefi_avb_boot.h
new file mode 100644
index 0000000..d2b8b38
--- /dev/null
+++ b/avb/examples/uefi/uefi_avb_boot.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef UEFI_AVB_BOOT_H_
+#define UEFI_AVB_BOOT_H_
+
+#include <efi.h>
+#include <libavb/libavb.h>
+
+/* Return codes used in uefi_avb_boot_kernel().
+ *
+ * Use uefi_avb_boot_kernel_result_to_string to get a textual
+ * representation usable for error/debug output.
+ */
+typedef enum {
+  UEFI_AVB_BOOT_KERNEL_RESULT_OK,
+  UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_OOM,
+  UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_IO,
+  UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_PARTITION_INVALID_FORMAT,
+  UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_KERNEL_INVALID_FORMAT,
+  UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_START_KERNEL
+} UEFIAvbBootKernelResult;
+
+/* Jumps to the Linux kernel embedded in the Android boot image which
+ * has been loaded in |slot_data|. This also supports setting up the
+ * initramfs from the boot image. The command-line to be passed to the
+ * kernel will be the concatenation of
+ *
+ *  1. the cmdline in the Android boot image; and
+ *  2. the cmdline in |slot_data|; and
+ *  3. |cmdline_extra| if not NULL
+ *
+ * in that order with spaces inserted as necessary.
+ *
+ * This function only returns if an error occurs.
+ */
+UEFIAvbBootKernelResult uefi_avb_boot_kernel(EFI_HANDLE efi_image_handle,
+                                             AvbSlotVerifyData* slot_data,
+                                             const char* cmdline_extra);
+
+/* Get a textual representation of |result|. */
+const char* uefi_avb_boot_kernel_result_to_string(
+    UEFIAvbBootKernelResult result);
+
+#endif /* UEFI_AVB_BOOT_H_ */
diff --git a/avb/examples/uefi/uefi_avb_ops.c b/avb/examples/uefi/uefi_avb_ops.c
new file mode 100644
index 0000000..3f22d03
--- /dev/null
+++ b/avb/examples/uefi/uefi_avb_ops.c
@@ -0,0 +1,631 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <efi.h>
+#include <efilib.h>
+
+#include <libavb_ab/libavb_ab.h>
+
+#include "uefi_avb_ops.h"
+#include "uefi_avb_util.h"
+
+#include <efi.h>
+#include <efilib.h>
+
+/* GPT related constants. */
+#define GPT_REVISION 0x00010000
+#define GPT_MAGIC "EFI PART"
+#define GPT_MIN_SIZE 92
+#define GPT_ENTRIES_LBA 2
+#define AVB_BLOCK_SIZE 512
+#define ENTRIES_PER_BLOCK 4
+#define ENTRY_NAME_LEN 36
+#define MAX_GPT_ENTRIES 128
+
+typedef struct {
+  uint8_t signature[8];
+  uint32_t revision;
+  uint32_t header_size;
+  uint32_t header_crc32;
+  uint32_t reserved;
+  uint64_t header_lba;
+  uint64_t alternate_header_lba;
+  uint64_t first_usable_lba;
+  uint64_t last_usable_lba;
+  uint8_t disk_guid[16];
+  uint64_t entry_lba;
+  uint32_t entry_count;
+  uint32_t entry_size;
+  uint32_t entry_crc32;
+  uint8_t reserved2[420];
+} GPTHeader;
+
+typedef struct {
+  uint8_t type_GUID[16];
+  uint8_t unique_GUID[16];
+  uint64_t first_lba;
+  uint64_t last_lba;
+  uint64_t flags;
+  uint16_t name[ENTRY_NAME_LEN];
+} GPTEntry;
+
+static EFI_STATUS find_partition_entry_by_name(IN EFI_BLOCK_IO* block_io,
+                                               const char* partition_name,
+                                               GPTEntry** entry_buf) {
+  EFI_STATUS err;
+  GPTHeader* gpt_header = NULL;
+  GPTEntry all_gpt_entries[MAX_GPT_ENTRIES];
+  uint16_t* partition_name_ucs2 = NULL;
+  size_t partition_name_bytes;
+  size_t partition_name_ucs2_capacity;
+  size_t partition_name_ucs2_len;
+
+  gpt_header = (GPTHeader*)avb_malloc(sizeof(GPTHeader));
+  if (gpt_header == NULL) {
+    avb_error("Could not allocate for GPT header\n");
+    return EFI_NOT_FOUND;
+  }
+
+  *entry_buf = (GPTEntry*)avb_malloc(sizeof(GPTEntry) * ENTRIES_PER_BLOCK);
+  if (entry_buf == NULL) {
+    avb_error("Could not allocate for partition entry\n");
+    avb_free(gpt_header);
+    return EFI_NOT_FOUND;
+  }
+
+  err = uefi_call_wrapper(block_io->ReadBlocks,
+                          NUM_ARGS_READ_BLOCKS,
+                          block_io,
+                          block_io->Media->MediaId,
+                          1,
+                          sizeof(GPTHeader),
+                          gpt_header);
+  if (EFI_ERROR(err)) {
+    avb_error("Could not ReadBlocks for gpt header\n");
+    avb_free(gpt_header);
+    avb_free(*entry_buf);
+    *entry_buf = NULL;
+    return EFI_NOT_FOUND;
+  }
+
+  partition_name_bytes = avb_strlen(partition_name);
+  partition_name_ucs2_capacity = sizeof(uint16_t) * (partition_name_bytes + 1);
+  partition_name_ucs2 = avb_calloc(partition_name_ucs2_capacity);
+  if (partition_name_ucs2 == NULL) {
+    avb_error("Could not allocate for ucs2 partition name\n");
+    avb_free(gpt_header);
+    avb_free(*entry_buf);
+    *entry_buf = NULL;
+    return EFI_NOT_FOUND;
+  }
+  if (!uefi_avb_utf8_to_ucs2((const uint8_t*)partition_name,
+                             partition_name_bytes,
+                             partition_name_ucs2,
+                             partition_name_ucs2_capacity,
+                             NULL)) {
+    avb_error("Could not convert partition name to UCS-2\n");
+    avb_free(gpt_header);
+    avb_free(partition_name_ucs2);
+    avb_free(*entry_buf);
+    *entry_buf = NULL;
+    return EFI_NOT_FOUND;
+  }
+  partition_name_ucs2_len = StrLen(partition_name_ucs2);
+
+  /* Block-aligned bytes for entries. */
+  UINTN entries_num_bytes =
+      block_io->Media->BlockSize * (MAX_GPT_ENTRIES / ENTRIES_PER_BLOCK);
+
+  err = uefi_call_wrapper(block_io->ReadBlocks,
+                          NUM_ARGS_READ_BLOCKS,
+                          block_io,
+                          block_io->Media->MediaId,
+                          GPT_ENTRIES_LBA,
+                          entries_num_bytes,
+                          &all_gpt_entries);
+  if (EFI_ERROR(err)) {
+    avb_error("Could not ReadBlocks for GPT header\n");
+    avb_free(gpt_header);
+    avb_free(partition_name_ucs2);
+    avb_free(*entry_buf);
+    *entry_buf = NULL;
+    return EFI_NOT_FOUND;
+  }
+
+  /* Find matching partition name. */
+  for (int n = 0; n < gpt_header->entry_count; n++) {
+    if ((partition_name_ucs2_len == StrLen(all_gpt_entries[n].name)) &&
+        avb_memcmp(all_gpt_entries[n].name,
+                   partition_name_ucs2,
+                   partition_name_ucs2_len * 2) == 0) {
+      avb_memcpy((*entry_buf), &all_gpt_entries[n], sizeof(GPTEntry));
+      avb_free(partition_name_ucs2);
+      avb_free(gpt_header);
+      return EFI_SUCCESS;
+    }
+  }
+
+  avb_free(partition_name_ucs2);
+  avb_free(gpt_header);
+  avb_free(*entry_buf);
+  *entry_buf = NULL;
+  return EFI_NOT_FOUND;
+}
+
+static AvbIOResult read_from_partition(AvbOps* ops,
+                                       const char* partition_name,
+                                       int64_t offset_from_partition,
+                                       size_t num_bytes,
+                                       void* buf,
+                                       size_t* out_num_read) {
+  EFI_STATUS err;
+  GPTEntry* partition_entry;
+  uint64_t partition_size;
+  UEFIAvbOpsData* data = (UEFIAvbOpsData*)ops->user_data;
+
+  avb_assert(partition_name != NULL);
+  avb_assert(buf != NULL);
+  avb_assert(out_num_read != NULL);
+
+  err = find_partition_entry_by_name(
+      data->block_io, partition_name, &partition_entry);
+  if (EFI_ERROR(err)) {
+    return AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION;
+  }
+
+  partition_size = (partition_entry->last_lba - partition_entry->first_lba) *
+                   data->block_io->Media->BlockSize;
+
+  if (offset_from_partition < 0) {
+    if ((-offset_from_partition) > partition_size) {
+      avb_error("Offset outside range.\n");
+      avb_free(partition_entry);
+      return AVB_IO_RESULT_ERROR_RANGE_OUTSIDE_PARTITION;
+    }
+    offset_from_partition = partition_size - (-offset_from_partition);
+  }
+
+  /* Check if num_bytes goes beyond partition end. If so, don't read beyond
+   * this boundary -- do a partial I/O instead.
+   */
+  if (num_bytes > partition_size - offset_from_partition)
+    *out_num_read = partition_size - offset_from_partition;
+  else
+    *out_num_read = num_bytes;
+
+  err = uefi_call_wrapper(
+      data->disk_io->ReadDisk,
+      5,
+      data->disk_io,
+      data->block_io->Media->MediaId,
+      (partition_entry->first_lba * data->block_io->Media->BlockSize) +
+          offset_from_partition,
+      *out_num_read,
+      buf);
+  if (EFI_ERROR(err)) {
+    avb_error("Could not read from Disk.\n");
+    *out_num_read = 0;
+    avb_free(partition_entry);
+    return AVB_IO_RESULT_ERROR_IO;
+  }
+
+  avb_free(partition_entry);
+  return AVB_IO_RESULT_OK;
+}
+
+static AvbIOResult write_to_partition(AvbOps* ops,
+                                      const char* partition_name,
+                                      int64_t offset_from_partition,
+                                      size_t num_bytes,
+                                      const void* buf) {
+  EFI_STATUS err;
+  GPTEntry* partition_entry;
+  uint64_t partition_size;
+  UEFIAvbOpsData* data = (UEFIAvbOpsData*)ops->user_data;
+
+  avb_assert(partition_name != NULL);
+  avb_assert(buf != NULL);
+
+  err = find_partition_entry_by_name(
+      data->block_io, partition_name, &partition_entry);
+  if (EFI_ERROR(err)) {
+    return AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION;
+  }
+
+  partition_size = (partition_entry->last_lba - partition_entry->first_lba) *
+                   data->block_io->Media->BlockSize;
+
+  if (offset_from_partition < 0) {
+    if ((-offset_from_partition) > partition_size) {
+      avb_error("Offset outside range.\n");
+      avb_free(partition_entry);
+      return AVB_IO_RESULT_ERROR_RANGE_OUTSIDE_PARTITION;
+    }
+    offset_from_partition = partition_size - (-offset_from_partition);
+  }
+
+  /* Check if num_bytes goes beyond partition end. If so, error out -- no
+   * partial I/O.
+   */
+  if (num_bytes > partition_size - offset_from_partition) {
+    avb_error("Cannot write beyond partition boundary.\n");
+    avb_free(partition_entry);
+    return AVB_IO_RESULT_ERROR_RANGE_OUTSIDE_PARTITION;
+  }
+
+  err = uefi_call_wrapper(
+      data->disk_io->WriteDisk,
+      5,
+      data->disk_io,
+      data->block_io->Media->MediaId,
+      (partition_entry->first_lba * data->block_io->Media->BlockSize) +
+          offset_from_partition,
+      num_bytes,
+      buf);
+
+  if (EFI_ERROR(err)) {
+    avb_error("Could not write to Disk.\n");
+    avb_free(partition_entry);
+    return AVB_IO_RESULT_ERROR_IO;
+  }
+
+  avb_free(partition_entry);
+  return AVB_IO_RESULT_OK;
+}
+
+/* Helper method to get the parent path to the current |walker| path
+ * given the initial path, |init|. Resulting path is stored in |next|.
+ * Caller is responsible for freeing |next|. Stores allocated bytes
+ * for |next| in |out_bytes|. Returns EFI_SUCCESS on success.
+ */
+static EFI_STATUS walk_path(IN EFI_DEVICE_PATH* init,
+                            IN EFI_DEVICE_PATH* walker,
+                            OUT EFI_DEVICE_PATH** next,
+                            OUT UINTN* out_bytes) {
+  /* Number of bytes from initial path to current walker. */
+  UINTN walker_bytes = (uint8_t*)NextDevicePathNode(walker) - (uint8_t*)init;
+  *out_bytes = sizeof(EFI_DEVICE_PATH) + walker_bytes;
+
+  *next = (EFI_DEVICE_PATH*)avb_malloc(*out_bytes);
+  if (*next == NULL) {
+    *out_bytes = 0;
+    return EFI_NOT_FOUND;
+  }
+
+  /* Copy in the previous paths. */
+  avb_memcpy((*next), init, walker_bytes);
+  /* Copy in the new ending of the path. */
+  avb_memcpy(
+      (uint8_t*)(*next) + walker_bytes, EndDevicePath, sizeof(EFI_DEVICE_PATH));
+  return EFI_SUCCESS;
+}
+
+/* Helper method to validate a GPT header, |gpth|.
+ *
+ * @return EFI_STATUS EFI_SUCCESS on success.
+ */
+static EFI_STATUS validate_gpt(const IN GPTHeader* gpth) {
+  if (avb_memcmp(gpth->signature, GPT_MAGIC, sizeof(gpth->signature)) != 0) {
+    avb_error("GPT signature does not match.\n");
+    return EFI_NOT_FOUND;
+  }
+  /* Make sure GPT header bytes are within minimun and block size. */
+  if (gpth->header_size < GPT_MIN_SIZE) {
+    avb_error("GPT header too small.\n");
+    return EFI_NOT_FOUND;
+  }
+  if (gpth->header_size > AVB_BLOCK_SIZE) {
+    avb_error("GPT header too big.\n");
+    return EFI_NOT_FOUND;
+  }
+
+  GPTHeader gpth_tmp = {{0}};
+  avb_memcpy(&gpth_tmp, gpth, sizeof(GPTHeader));
+  uint32_t gpt_header_crc = gpth_tmp.header_crc32;
+  gpth_tmp.header_crc32 = 0;
+  uint32_t gpt_header_crc_calc =
+      CalculateCrc((uint8_t*)&gpth_tmp, gpth_tmp.header_size);
+
+  if (gpt_header_crc != gpt_header_crc_calc) {
+    avb_error("GPT header crc invalid.\n");
+    return EFI_NOT_FOUND;
+  }
+
+  if (gpth->revision != GPT_REVISION) {
+    avb_error("GPT header wrong revision.\n");
+    return EFI_NOT_FOUND;
+  }
+
+  return EFI_SUCCESS;
+}
+
+/* Queries |disk_handle| for a |block_io| device and the corresponding
+ * path, |block_path|.  The |block_io| device is found by iteratively
+ * querying parent devices and checking for a GPT Header.  This
+ * ensures the resulting |block_io| device is the top level block
+ * device having access to partition entries. Returns EFI_STATUS
+ * EFI_NOT_FOUND on failure, EFI_SUCCESS otherwise.
+ */
+static EFI_STATUS get_disk_block_io(IN EFI_HANDLE* block_handle,
+                                    OUT EFI_BLOCK_IO** block_io,
+                                    OUT EFI_DISK_IO** disk_io,
+                                    OUT EFI_DEVICE_PATH** io_path) {
+  EFI_STATUS err;
+  EFI_HANDLE disk_handle;
+  UINTN path_bytes;
+  EFI_DEVICE_PATH* disk_path;
+  EFI_DEVICE_PATH* walker_path;
+  EFI_DEVICE_PATH* init_path;
+  GPTHeader gpt_header = {{0}};
+  init_path = DevicePathFromHandle(block_handle);
+
+  if (!init_path) {
+    return EFI_NOT_FOUND;
+  }
+
+  walker_path = init_path;
+  while (!IsDevicePathEnd(walker_path)) {
+    walker_path = NextDevicePathNode(walker_path);
+
+    err = walk_path(init_path, walker_path, &(*io_path), &path_bytes);
+    if (EFI_ERROR(err)) {
+      avb_error("Cannot walk device path.\n");
+      return EFI_NOT_FOUND;
+    }
+
+    disk_path = (EFI_DEVICE_PATH*)avb_malloc(path_bytes);
+    avb_memcpy(disk_path, *io_path, path_bytes);
+    err = uefi_call_wrapper(BS->LocateDevicePath,
+                            NUM_ARGS_LOCATE_DEVICE_PATH,
+                            &BlockIoProtocol,
+                            &(*io_path),
+                            &block_handle);
+    if (EFI_ERROR(err)) {
+      avb_free(*io_path);
+      avb_free(disk_path);
+      continue;
+    }
+    err = uefi_call_wrapper(BS->LocateDevicePath,
+                            NUM_ARGS_LOCATE_DEVICE_PATH,
+                            &DiskIoProtocol,
+                            &disk_path,
+                            &disk_handle);
+    if (EFI_ERROR(err)) {
+      avb_error("LocateDevicePath, DISK_IO_PROTOCOL.\n");
+      avb_free(*io_path);
+      avb_free(disk_path);
+      continue;
+    }
+
+    /* Handle Block and Disk I/O. Attempt to get handle on device,
+     * must be Block/Disk Io type.
+     */
+    err = uefi_call_wrapper(BS->HandleProtocol,
+                            NUM_ARGS_HANDLE_PROTOCOL,
+                            block_handle,
+                            &BlockIoProtocol,
+                            (VOID**)&(*block_io));
+    if (EFI_ERROR(err)) {
+      avb_error("Cannot get handle on block device.\n");
+      avb_free(*io_path);
+      avb_free(disk_path);
+      continue;
+    }
+    err = uefi_call_wrapper(BS->HandleProtocol,
+                            NUM_ARGS_HANDLE_PROTOCOL,
+                            disk_handle,
+                            &DiskIoProtocol,
+                            (VOID**)&(*disk_io));
+    if (EFI_ERROR(err)) {
+      avb_error("Cannot get handle on disk device.\n");
+      avb_free(*io_path);
+      avb_free(disk_path);
+      continue;
+    }
+
+    if ((*block_io)->Media->LogicalPartition ||
+        !(*block_io)->Media->MediaPresent) {
+      avb_error("Logical partion or No Media Present, continue...\n");
+      avb_free(*io_path);
+      avb_free(disk_path);
+      continue;
+    }
+
+    err = uefi_call_wrapper((*block_io)->ReadBlocks,
+                            NUM_ARGS_READ_BLOCKS,
+                            (*block_io),
+                            (*block_io)->Media->MediaId,
+                            1,
+                            sizeof(GPTHeader),
+                            &gpt_header);
+
+    if (EFI_ERROR(err)) {
+      avb_error("ReadBlocks, Block Media error.\n");
+      avb_free(*io_path);
+      avb_free(disk_path);
+      continue;
+    }
+
+    err = validate_gpt(&gpt_header);
+    if (EFI_ERROR(err)) {
+      avb_error("Invalid GPTHeader\n");
+      avb_free(*io_path);
+      avb_free(disk_path);
+      continue;
+    }
+
+    return EFI_SUCCESS;
+  }
+
+  (*block_io) = NULL;
+  return EFI_NOT_FOUND;
+}
+
+static AvbIOResult validate_vbmeta_public_key(
+    AvbOps* ops,
+    const uint8_t* public_key_data,
+    size_t public_key_length,
+    const uint8_t* public_key_metadata,
+    size_t public_key_metadata_length,
+    bool* out_key_is_trusted) {
+  /* For now we just allow any key. */
+  if (out_key_is_trusted != NULL) {
+    *out_key_is_trusted = true;
+  }
+  avb_debug("TODO: implement validate_vbmeta_public_key().\n");
+  return AVB_IO_RESULT_OK;
+}
+
+static AvbIOResult read_rollback_index(AvbOps* ops,
+                                       size_t rollback_index_slot,
+                                       uint64_t* out_rollback_index) {
+  /* For now we always return 0 as the stored rollback index. */
+  avb_debug("TODO: implement read_rollback_index().\n");
+  if (out_rollback_index != NULL) {
+    *out_rollback_index = 0;
+  }
+  return AVB_IO_RESULT_OK;
+}
+
+static AvbIOResult write_rollback_index(AvbOps* ops,
+                                        size_t rollback_index_slot,
+                                        uint64_t rollback_index) {
+  /* For now this is a no-op. */
+  avb_debug("TODO: implement write_rollback_index().\n");
+  return AVB_IO_RESULT_OK;
+}
+
+static AvbIOResult read_is_device_unlocked(AvbOps* ops, bool* out_is_unlocked) {
+  /* For now we always return that the device is unlocked. */
+  avb_debug("TODO: implement read_is_device_unlocked().\n");
+  *out_is_unlocked = true;
+  return AVB_IO_RESULT_OK;
+}
+
+static void set_hex(char* buf, uint8_t value) {
+  char hex_digits[17] = "0123456789abcdef";
+  buf[0] = hex_digits[value >> 4];
+  buf[1] = hex_digits[value & 0x0f];
+}
+
+static AvbIOResult get_unique_guid_for_partition(AvbOps* ops,
+                                                 const char* partition,
+                                                 char* guid_buf,
+                                                 size_t guid_buf_size) {
+  EFI_STATUS err;
+  GPTEntry* partition_entry;
+  UEFIAvbOpsData* data = (UEFIAvbOpsData*)ops->user_data;
+
+  avb_assert(partition != NULL);
+  avb_assert(guid_buf != NULL);
+
+  err =
+      find_partition_entry_by_name(data->block_io, partition, &partition_entry);
+  if (EFI_ERROR(err)) {
+    avb_error("Error getting unique GUID for partition.\n");
+    return AVB_IO_RESULT_ERROR_IO;
+  }
+
+  if (guid_buf_size < 37) {
+    avb_error("GUID buffer size too small.\n");
+    return AVB_IO_RESULT_ERROR_IO;
+  }
+
+  /* The GUID encoding is somewhat peculiar in terms of byte order. It
+   * is what it is.
+   */
+  set_hex(guid_buf + 0, partition_entry->unique_GUID[3]);
+  set_hex(guid_buf + 2, partition_entry->unique_GUID[2]);
+  set_hex(guid_buf + 4, partition_entry->unique_GUID[1]);
+  set_hex(guid_buf + 6, partition_entry->unique_GUID[0]);
+  guid_buf[8] = '-';
+  set_hex(guid_buf + 9, partition_entry->unique_GUID[5]);
+  set_hex(guid_buf + 11, partition_entry->unique_GUID[4]);
+  guid_buf[13] = '-';
+  set_hex(guid_buf + 14, partition_entry->unique_GUID[7]);
+  set_hex(guid_buf + 16, partition_entry->unique_GUID[6]);
+  guid_buf[18] = '-';
+  set_hex(guid_buf + 19, partition_entry->unique_GUID[8]);
+  set_hex(guid_buf + 21, partition_entry->unique_GUID[9]);
+  guid_buf[23] = '-';
+  set_hex(guid_buf + 24, partition_entry->unique_GUID[10]);
+  set_hex(guid_buf + 26, partition_entry->unique_GUID[11]);
+  set_hex(guid_buf + 28, partition_entry->unique_GUID[12]);
+  set_hex(guid_buf + 30, partition_entry->unique_GUID[13]);
+  set_hex(guid_buf + 32, partition_entry->unique_GUID[14]);
+  set_hex(guid_buf + 34, partition_entry->unique_GUID[15]);
+  guid_buf[36] = '\0';
+  return AVB_IO_RESULT_OK;
+}
+
+AvbOps* uefi_avb_ops_new(EFI_HANDLE app_image) {
+  UEFIAvbOpsData* data;
+  EFI_STATUS err;
+  EFI_LOADED_IMAGE* loaded_app_image = NULL;
+  EFI_GUID loaded_image_protocol = LOADED_IMAGE_PROTOCOL;
+
+  data = avb_calloc(sizeof(UEFIAvbOpsData));
+  data->ops.user_data = data;
+
+  data->efi_image_handle = app_image;
+  err = uefi_call_wrapper(BS->HandleProtocol,
+                          NUM_ARGS_HANDLE_PROTOCOL,
+                          app_image,
+                          &loaded_image_protocol,
+                          (VOID**)&loaded_app_image);
+  if (EFI_ERROR(err)) {
+    avb_error("HandleProtocol, LOADED_IMAGE_PROTOCOL.\n");
+    return 0;
+  }
+
+  /* Get parent device disk and block I/O. */
+  err = get_disk_block_io(loaded_app_image->DeviceHandle,
+                          &data->block_io,
+                          &data->disk_io,
+                          &data->path);
+  if (EFI_ERROR(err)) {
+    avb_error("Could not acquire block or disk device handle.\n");
+    return 0;
+  }
+
+  data->ops.ab_ops = &data->ab_ops;
+  data->ops.read_from_partition = read_from_partition;
+  data->ops.write_to_partition = write_to_partition;
+  data->ops.validate_vbmeta_public_key = validate_vbmeta_public_key;
+  data->ops.read_rollback_index = read_rollback_index;
+  data->ops.write_rollback_index = write_rollback_index;
+  data->ops.read_is_device_unlocked = read_is_device_unlocked;
+  data->ops.get_unique_guid_for_partition = get_unique_guid_for_partition;
+
+  data->ab_ops.ops = &data->ops;
+  data->ab_ops.read_ab_metadata = avb_ab_data_read;
+  data->ab_ops.write_ab_metadata = avb_ab_data_write;
+
+  return &data->ops;
+}
+
+void uefi_avb_ops_free(AvbOps* ops) {
+  UEFIAvbOpsData* data = ops->user_data;
+  avb_free(data);
+}
diff --git a/avb/examples/uefi/uefi_avb_ops.h b/avb/examples/uefi/uefi_avb_ops.h
new file mode 100644
index 0000000..80d13ee
--- /dev/null
+++ b/avb/examples/uefi/uefi_avb_ops.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef UEFI_AVB_OPS_H_
+#define UEFI_AVB_OPS_H_
+
+#include <efi.h>
+#include <libavb_ab/libavb_ab.h>
+
+/* The |user_data| member of AvbOps points to a struct of this type. */
+typedef struct UEFIAvbOpsData {
+  AvbOps ops;
+  AvbABOps ab_ops;
+
+  EFI_HANDLE efi_image_handle;
+  EFI_DEVICE_PATH* path;
+  EFI_BLOCK_IO* block_io;
+  EFI_DISK_IO* disk_io;
+} UEFIAvbOpsData;
+
+/* Returns an AvbOps for use with UEFI. */
+AvbOps* uefi_avb_ops_new(EFI_HANDLE app_image);
+
+/* Frees the AvbOps allocated with uefi_avb_ops_new(). */
+void uefi_avb_ops_free(AvbOps* ops);
+
+#endif /* UEFI_AVB_OPS_H_ */
diff --git a/avb/examples/uefi/uefi_avb_sysdeps.c b/avb/examples/uefi/uefi_avb_sysdeps.c
new file mode 100644
index 0000000..4a41c4f
--- /dev/null
+++ b/avb/examples/uefi/uefi_avb_sysdeps.c
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <efi.h>
+#include <efilib.h>
+#include <efistdarg.h>
+
+#include <libavb/libavb.h>
+
+#include "uefi_avb_util.h"
+
+int avb_memcmp(const void* src1, const void* src2, size_t n) {
+  return (int)CompareMem((VOID*)src1, (VOID*)src2, (UINTN)n);
+}
+
+int avb_strcmp(const char* s1, const char* s2) {
+  return (int)strcmpa((CHAR8*)s1, (CHAR8*)s2);
+}
+
+void* avb_memcpy(void* dest, const void* src, size_t n) {
+  CopyMem(dest, (VOID*)src, (UINTN)n);
+  return dest;
+}
+
+void* avb_memset(void* dest, const int c, size_t n) {
+  SetMem(dest, (UINTN)n, (UINT8)c);
+  return dest;
+}
+
+void avb_print(const char* message) {
+  size_t utf8_num_bytes = avb_strlen(message) + 1;
+  size_t max_ucs2_bytes = utf8_num_bytes * 2;
+  uint16_t* message_ucs2 = (uint16_t*)avb_calloc(max_ucs2_bytes);
+  if (message_ucs2 == NULL) {
+    return;
+  }
+  if (uefi_avb_utf8_to_ucs2((const uint8_t*)message,
+                            utf8_num_bytes,
+                            message_ucs2,
+                            max_ucs2_bytes,
+                            NULL)) {
+    Print(message_ucs2);
+  }
+  avb_free(message_ucs2);
+}
+
+void avb_printv(const char* message, ...) {
+  va_list ap;
+
+  va_start(ap, message);
+  do {
+    avb_print(message);
+    message = va_arg(ap, const char*);
+  } while (message != NULL);
+  va_end(ap);
+}
+
+void avb_abort(void) {
+  avb_print("\nABORTING...\n");
+  uefi_call_wrapper(BS->Stall, 1, 5 * 1000 * 1000);
+  uefi_call_wrapper(BS->Exit, 4, NULL, EFI_NOT_FOUND, 0, NULL);
+  while (true) {
+    ;
+  }
+}
+
+void* avb_malloc_(size_t size) {
+  EFI_STATUS err;
+  void* x;
+
+  err = uefi_call_wrapper(
+      BS->AllocatePool, 3, EfiBootServicesData, (UINTN)size, &x);
+  if (EFI_ERROR(err)) {
+    return NULL;
+  }
+
+  return x;
+}
+
+void avb_free(void* ptr) {
+  EFI_STATUS err;
+  err = uefi_call_wrapper(BS->FreePool, 1, ptr);
+
+  if (EFI_ERROR(err)) {
+    Print(L"Warning: Bad avb_free: %r\n", err);
+    uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000);
+  }
+}
+
+size_t avb_strlen(const char* str) {
+  return strlena((CHAR8*)str);
+}
diff --git a/avb/examples/uefi/uefi_avb_util.c b/avb/examples/uefi/uefi_avb_util.c
new file mode 100644
index 0000000..1d59af7
--- /dev/null
+++ b/avb/examples/uefi/uefi_avb_util.c
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "uefi_avb_util.h"
+
+bool uefi_avb_utf8_to_ucs2(const uint8_t* utf8_data,
+                           size_t utf8_num_bytes,
+                           uint16_t* ucs2_data,
+                           size_t ucs2_data_capacity_num_bytes,
+                           size_t* out_ucs2_data_num_bytes) {
+  uint32_t i8 = 0;
+  uint32_t i2 = 0;
+
+  do {
+    if (i2 >= ucs2_data_capacity_num_bytes) {
+      break;
+    }
+
+    // cannot represent this character, too long
+    if ((utf8_data[i8] & 0xF8) == 0xF0) {
+      return false;
+    } else if ((utf8_data[i8] & 0xF0) == 0xE0) {
+      ucs2_data[i2] = (uint16_t)((uint16_t)utf8_data[i8] << 12) |
+                      (uint16_t)(((uint16_t)utf8_data[i8 + 1] << 6) & 0x0FC0) |
+                      (uint16_t)((uint16_t)utf8_data[i8 + 2] & 0x003F);
+      i8 += 3;
+    } else if ((utf8_data[i8] & 0xE0) == 0XC0) {
+      ucs2_data[i2] = (uint16_t)(((uint16_t)utf8_data[i8] << 6) & 0x07C0) |
+                      (uint16_t)((uint16_t)utf8_data[i8 + 1] & 0x003F);
+      i8 += 2;
+    } else if (!(utf8_data[i8] >> 7)) {
+      ucs2_data[i2] = (uint16_t)((uint16_t)utf8_data[i8] & 0x00FF);
+      i8++;
+    } else {
+      // invalid utf-8
+      return false;
+    }
+    i2++;
+  } while (i8 < utf8_num_bytes);
+
+  if (out_ucs2_data_num_bytes != NULL) {
+    *out_ucs2_data_num_bytes = i2 * 2;
+  }
+  return true;
+}
diff --git a/avb/examples/uefi/uefi_avb_util.h b/avb/examples/uefi/uefi_avb_util.h
new file mode 100644
index 0000000..6a70557
--- /dev/null
+++ b/avb/examples/uefi/uefi_avb_util.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+// NOTE: See avb_sysdeps.h
+
+#ifndef UEFI_AVB_UTIL_H_
+#define UEFI_AVB_UTIL_H_
+
+#include <libavb/libavb.h>
+
+/* Converts UTF-8 to UCS-2. Returns |false| if invalid UTF-8 or if a
+ * rune cannot be represented as UCS-2.
+ */
+bool uefi_avb_utf8_to_ucs2(const uint8_t* utf8_data,
+                           size_t utf8_num_bytes,
+                           uint16_t* ucs2_data,
+                           size_t ucs2_data_capacity_num_bytes,
+                           size_t* out_ucs2_data_num_bytes);
+
+#endif /* UEFI_AVB_UTIL_H_ */
diff --git a/avb/libavb/avb_chain_partition_descriptor.c b/avb/libavb/avb_chain_partition_descriptor.c
new file mode 100644
index 0000000..3f14232
--- /dev/null
+++ b/avb/libavb/avb_chain_partition_descriptor.c
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "avb_chain_partition_descriptor.h"
+#include "avb_util.h"
+
+bool avb_chain_partition_descriptor_validate_and_byteswap(
+    const AvbChainPartitionDescriptor* src, AvbChainPartitionDescriptor* dest) {
+  uint64_t expected_size;
+
+  avb_memcpy(dest, src, sizeof(AvbChainPartitionDescriptor));
+
+  if (!avb_descriptor_validate_and_byteswap((const AvbDescriptor*)src,
+                                            (AvbDescriptor*)dest))
+    return false;
+
+  if (dest->parent_descriptor.tag != AVB_DESCRIPTOR_TAG_CHAIN_PARTITION) {
+    avb_error("Invalid tag for chain partition descriptor.\n");
+    return false;
+  }
+
+  dest->rollback_index_location = avb_be32toh(dest->rollback_index_location);
+  dest->partition_name_len = avb_be32toh(dest->partition_name_len);
+  dest->public_key_len = avb_be32toh(dest->public_key_len);
+
+  if (dest->rollback_index_location < 1) {
+    avb_error("Invalid rollback index location value.\n");
+    return false;
+  }
+
+  /* Check that partition_name and public_key are fully contained. */
+  expected_size = sizeof(AvbChainPartitionDescriptor) - sizeof(AvbDescriptor);
+  if (!avb_safe_add_to(&expected_size, dest->partition_name_len) ||
+      !avb_safe_add_to(&expected_size, dest->public_key_len)) {
+    avb_error("Overflow while adding up sizes.\n");
+    return false;
+  }
+  if (expected_size > dest->parent_descriptor.num_bytes_following) {
+    avb_error("Descriptor payload size overflow.\n");
+    return false;
+  }
+  return true;
+}
diff --git a/avb/libavb/avb_chain_partition_descriptor.h b/avb/libavb/avb_chain_partition_descriptor.h
new file mode 100644
index 0000000..f2c9250
--- /dev/null
+++ b/avb/libavb/avb_chain_partition_descriptor.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#if !defined(AVB_INSIDE_LIBAVB_H) && !defined(AVB_COMPILATION)
+#error "Never include this file directly, include libavb.h instead."
+#endif
+
+#ifndef AVB_CHAIN_PARTITION_DESCRIPTOR_H_
+#define AVB_CHAIN_PARTITION_DESCRIPTOR_H_
+
+#include "avb_descriptor.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* A descriptor containing a pointer to signed integrity data stored
+ * on another partition. The descriptor contains the partition name in
+ * question (without the A/B suffix), the public key used to sign the
+ * integrity data, and rollback index location to use for rollback
+ * protection.
+ *
+ * Following this struct are |partition_name_len| bytes of the
+ * partition name (UTF-8 encoded) and |public_key_len| bytes of the
+ * public key.
+ *
+ * The |reserved| field is for future expansion and must be set to NUL
+ * bytes.
+ */
+typedef struct AvbChainPartitionDescriptor {
+  AvbDescriptor parent_descriptor;
+  uint32_t rollback_index_location;
+  uint32_t partition_name_len;
+  uint32_t public_key_len;
+  uint8_t reserved[64];
+} AVB_ATTR_PACKED AvbChainPartitionDescriptor;
+
+/* Copies |src| to |dest| and validates, byte-swapping fields in the
+ * process if needed. Returns true if valid, false if invalid.
+ *
+ * Data following the struct is not validated nor copied.
+ */
+bool avb_chain_partition_descriptor_validate_and_byteswap(
+    const AvbChainPartitionDescriptor* src,
+    AvbChainPartitionDescriptor* dest) AVB_ATTR_WARN_UNUSED_RESULT;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* AVB_CHAIN_PARTITION_DESCRIPTOR_H_ */
diff --git a/avb/libavb/avb_crc32.c b/avb/libavb/avb_crc32.c
new file mode 100644
index 0000000..9abed54
--- /dev/null
+++ b/avb/libavb/avb_crc32.c
@@ -0,0 +1,113 @@
+/*-
+ *  COPYRIGHT (C) 1986 Gary S. Brown.  You may use this program, or
+ *  code or tables extracted from it, as desired without restriction.
+ */
+
+/*
+ *  First, the polynomial itself and its table of feedback terms.  The
+ *  polynomial is
+ *  X^32+X^26+X^23+X^22+X^16+X^12+X^11+X^10+X^8+X^7+X^5+X^4+X^2+X^1+X^0
+ *
+ *  Note that we take it "backwards" and put the highest-order term in
+ *  the lowest-order bit.  The X^32 term is "implied"; the LSB is the
+ *  X^31 term, etc.  The X^0 term (usually shown as "+1") results in
+ *  the MSB being 1
+ *
+ *  Note that the usual hardware shift register implementation, which
+ *  is what we're using (we're merely optimizing it by doing eight-bit
+ *  chunks at a time) shifts bits into the lowest-order term.  In our
+ *  implementation, that means shifting towards the right.  Why do we
+ *  do it this way?  Because the calculated CRC must be transmitted in
+ *  order from highest-order term to lowest-order term.  UARTs transmit
+ *  characters in order from LSB to MSB.  By storing the CRC this way
+ *  we hand it to the UART in the order low-byte to high-byte; the UART
+ *  sends each low-bit to hight-bit; and the result is transmission bit
+ *  by bit from highest- to lowest-order term without requiring any bit
+ *  shuffling on our part.  Reception works similarly
+ *
+ *  The feedback terms table consists of 256, 32-bit entries.  Notes
+ *
+ *      The table can be generated at runtime if desired; code to do so
+ *      is shown later.  It might not be obvious, but the feedback
+ *      terms simply represent the results of eight shift/xor opera
+ *      tions for all combinations of data and CRC register values
+ *
+ *      The values must be right-shifted by eight bits by the "updcrc
+ *      logic; the shift must be unsigned (bring in zeroes).  On some
+ *      hardware you could probably optimize the shift in assembler by
+ *      using byte-swap instructions
+ *      polynomial $edb88320
+ *
+ *
+ * CRC32 code derived from work by Gary S. Brown.
+ */
+
+#include "avb_sysdeps.h"
+
+/* Code taken from FreeBSD 8 */
+
+static uint32_t crc32_tab[] = {
+    0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
+    0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
+    0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
+    0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
+    0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
+    0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
+    0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
+    0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
+    0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
+    0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
+    0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
+    0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
+    0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
+    0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
+    0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
+    0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
+    0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
+    0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
+    0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
+    0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
+    0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
+    0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
+    0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
+    0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
+    0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
+    0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
+    0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
+    0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
+    0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
+    0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
+    0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
+    0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
+    0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
+    0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
+    0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
+    0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
+    0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
+    0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
+    0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
+    0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
+    0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
+    0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
+    0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d};
+
+/*
+ * A function that calculates the CRC-32 based on the table above is
+ * given below for documentation purposes. An equivalent implementation
+ * of this function that's actually used in the kernel can be found
+ * in sys/libkern.h, where it can be inlined.
+ */
+
+static uint32_t crc32(uint32_t crc_in, const uint8_t* buf, int size) {
+  const uint8_t* p = buf;
+  uint32_t crc;
+
+  crc = crc_in ^ ~0U;
+  while (size--)
+    crc = crc32_tab[(crc ^ *p++) & 0xFF] ^ (crc >> 8);
+  return crc ^ ~0U;
+}
+
+uint32_t avb_crc32(const uint8_t* buf, size_t size) {
+  return crc32(0, buf, size);
+}
diff --git a/avb/libavb/avb_crypto.c b/avb/libavb/avb_crypto.c
new file mode 100644
index 0000000..a428443
--- /dev/null
+++ b/avb/libavb/avb_crypto.c
@@ -0,0 +1,373 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "avb_crypto.h"
+#include "avb_rsa.h"
+#include "avb_sha.h"
+#include "avb_util.h"
+
+/* NOTE: The PKC1-v1.5 padding is a blob of binary DER of ASN.1 and is
+ * obtained from section 5.2.2 of RFC 4880.
+ */
+
+static const uint8_t
+    padding_RSA2048_SHA256[AVB_RSA2048_NUM_BYTES - AVB_SHA256_DIGEST_SIZE] = {
+        0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0x00, 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65,
+        0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20};
+
+static const uint8_t
+    padding_RSA4096_SHA256[AVB_RSA4096_NUM_BYTES - AVB_SHA256_DIGEST_SIZE] = {
+        0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0x00, 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60,
+        0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20};
+
+static const uint8_t
+    padding_RSA8192_SHA256[AVB_RSA8192_NUM_BYTES - AVB_SHA256_DIGEST_SIZE] = {
+        0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0x00, 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65,
+        0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20};
+
+static const uint8_t
+    padding_RSA2048_SHA512[AVB_RSA2048_NUM_BYTES - AVB_SHA512_DIGEST_SIZE] = {
+        0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0x00, 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60,
+        0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, 0x00, 0x04, 0x40};
+
+static const uint8_t
+    padding_RSA4096_SHA512[AVB_RSA4096_NUM_BYTES - AVB_SHA512_DIGEST_SIZE] = {
+        0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x30, 0x51, 0x30,
+        0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03,
+        0x05, 0x00, 0x04, 0x40};
+
+static const uint8_t
+    padding_RSA8192_SHA512[AVB_RSA8192_NUM_BYTES - AVB_SHA512_DIGEST_SIZE] = {
+        0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0x00, 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60,
+        0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, 0x00, 0x04, 0x40};
+
+static AvbAlgorithmData algorithm_data[_AVB_ALGORITHM_NUM_TYPES] = {
+    /* AVB_ALGORITHM_TYPE_NONE */
+    {.padding = NULL, .padding_len = 0, .hash_len = 0},
+    /* AVB_ALGORITHM_TYPE_SHA256_RSA2048 */
+    {.padding = padding_RSA2048_SHA256,
+     .padding_len = sizeof(padding_RSA2048_SHA256),
+     .hash_len = AVB_SHA256_DIGEST_SIZE},
+    /* AVB_ALGORITHM_TYPE_SHA256_RSA4096 */
+    {.padding = padding_RSA4096_SHA256,
+     .padding_len = sizeof(padding_RSA4096_SHA256),
+     .hash_len = AVB_SHA256_DIGEST_SIZE},
+    /* AVB_ALGORITHM_TYPE_SHA256_RSA8192 */
+    {.padding = padding_RSA8192_SHA256,
+     .padding_len = sizeof(padding_RSA8192_SHA256),
+     .hash_len = AVB_SHA256_DIGEST_SIZE},
+    /* AVB_ALGORITHM_TYPE_SHA512_RSA2048 */
+    {.padding = padding_RSA2048_SHA512,
+     .padding_len = sizeof(padding_RSA2048_SHA512),
+     .hash_len = AVB_SHA512_DIGEST_SIZE},
+    /* AVB_ALGORITHM_TYPE_SHA512_RSA4096 */
+    {.padding = padding_RSA4096_SHA512,
+     .padding_len = sizeof(padding_RSA4096_SHA512),
+     .hash_len = AVB_SHA512_DIGEST_SIZE},
+    /* AVB_ALGORITHM_TYPE_SHA512_RSA8192 */
+    {.padding = padding_RSA8192_SHA512,
+     .padding_len = sizeof(padding_RSA8192_SHA512),
+     .hash_len = AVB_SHA512_DIGEST_SIZE},
+};
+
+const AvbAlgorithmData* avb_get_algorithm_data(AvbAlgorithmType algorithm) {
+  if (algorithm >= AVB_ALGORITHM_TYPE_NONE &&
+      algorithm < _AVB_ALGORITHM_NUM_TYPES) {
+    return &algorithm_data[algorithm];
+  }
+  return NULL;
+}
+
+bool avb_rsa_public_key_header_validate_and_byteswap(
+    const AvbRSAPublicKeyHeader* src, AvbRSAPublicKeyHeader* dest) {
+  avb_memcpy(dest, src, sizeof(AvbRSAPublicKeyHeader));
+
+  dest->key_num_bits = avb_be32toh(dest->key_num_bits);
+  dest->n0inv = avb_be32toh(dest->n0inv);
+
+  return true;
+}
diff --git a/avb/libavb/avb_crypto.h b/avb/libavb/avb_crypto.h
new file mode 100644
index 0000000..7e8d7e2
--- /dev/null
+++ b/avb/libavb/avb_crypto.h
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#if !defined(AVB_INSIDE_LIBAVB_H) && !defined(AVB_COMPILATION)
+#error "Never include this file directly, include libavb.h instead."
+#endif
+
+#ifndef AVB_CRYPTO_H_
+#define AVB_CRYPTO_H_
+
+#include "avb_sysdeps.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Size of a RSA-2048 signature. */
+#define AVB_RSA2048_NUM_BYTES 256
+
+/* Size of a RSA-4096 signature. */
+#define AVB_RSA4096_NUM_BYTES 512
+
+/* Size of a RSA-8192 signature. */
+#define AVB_RSA8192_NUM_BYTES 1024
+
+/* Size in bytes of a SHA-256 digest. */
+#define AVB_SHA256_DIGEST_SIZE 32
+
+/* Size in bytes of a SHA-512 digest. */
+#define AVB_SHA512_DIGEST_SIZE 64
+
+/* Algorithms that can be used in the vbmeta image for
+ * verification. An algorithm consists of a hash type and a signature
+ * type.
+ *
+ * The data used to calculate the hash is the three blocks mentioned
+ * in the documentation for |AvbVBMetaImageHeader| except for the data
+ * in the "Authentication data" block.
+ *
+ * For signatures with RSA keys, PKCS v1.5 padding is used. The public
+ * key data is stored in the auxiliary data block, see
+ * |AvbRSAPublicKeyHeader| for the serialization format.
+ *
+ * Each algorithm type is described below:
+ *
+ * AVB_ALGORITHM_TYPE_NONE: There is no hash, no signature of the
+ * data, and no public key. The data cannot be verified. The fields
+ * |hash_size|, |signature_size|, and |public_key_size| must be zero.
+ *
+ * AVB_ALGORITHM_TYPE_SHA256_RSA2048: The hash function used is
+ * SHA-256, resulting in 32 bytes of hash digest data. This hash is
+ * signed with a 2048-bit RSA key. The field |hash_size| must be 32,
+ * |signature_size| must be 256, and the public key data must have
+ * |key_num_bits| set to 2048.
+ *
+ * AVB_ALGORITHM_TYPE_SHA256_RSA4096: Like above, but only with
+ * a 4096-bit RSA key and |signature_size| set to 512.
+ *
+ * AVB_ALGORITHM_TYPE_SHA256_RSA8192: Like above, but only with
+ * a 8192-bit RSA key and |signature_size| set to 1024.
+ *
+ * AVB_ALGORITHM_TYPE_SHA512_RSA2048: The hash function used is
+ * SHA-512, resulting in 64 bytes of hash digest data. This hash is
+ * signed with a 2048-bit RSA key. The field |hash_size| must be 64,
+ * |signature_size| must be 256, and the public key data must have
+ * |key_num_bits| set to 2048.
+ *
+ * AVB_ALGORITHM_TYPE_SHA512_RSA4096: Like above, but only with
+ * a 4096-bit RSA key and |signature_size| set to 512.
+ *
+ * AVB_ALGORITHM_TYPE_SHA512_RSA8192: Like above, but only with
+ * a 8192-bit RSA key and |signature_size| set to 1024.
+ */
+typedef enum {
+  AVB_ALGORITHM_TYPE_NONE,
+  AVB_ALGORITHM_TYPE_SHA256_RSA2048,
+  AVB_ALGORITHM_TYPE_SHA256_RSA4096,
+  AVB_ALGORITHM_TYPE_SHA256_RSA8192,
+  AVB_ALGORITHM_TYPE_SHA512_RSA2048,
+  AVB_ALGORITHM_TYPE_SHA512_RSA4096,
+  AVB_ALGORITHM_TYPE_SHA512_RSA8192,
+  _AVB_ALGORITHM_NUM_TYPES
+} AvbAlgorithmType;
+
+/* Holds algorithm-specific data. The |padding| is needed by avb_rsa_verify. */
+typedef struct {
+  const uint8_t* padding;
+  size_t padding_len;
+  size_t hash_len;
+} AvbAlgorithmData;
+
+/* Provides algorithm-specific data for a given |algorithm|. Returns NULL if
+ * |algorithm| is invalid.
+ */
+const AvbAlgorithmData* avb_get_algorithm_data(AvbAlgorithmType algorithm)
+    AVB_ATTR_WARN_UNUSED_RESULT;
+
+/* The header for a serialized RSA public key.
+ *
+ * The size of the key is given by |key_num_bits|, for example 2048
+ * for a RSA-2048 key. By definition, a RSA public key is the pair (n,
+ * e) where |n| is the modulus (which can be represented in
+ * |key_num_bits| bits) and |e| is the public exponent. The exponent
+ * is not stored since it's assumed to always be 65537.
+ *
+ * To optimize verification, the key block includes two precomputed
+ * values, |n0inv| (fits in 32 bits) and |rr| and can always be
+ * represented in |key_num_bits|.
+
+ * The value |n0inv| is the value -1/n[0] (mod 2^32). The value |rr|
+ * is (2^key_num_bits)^2 (mod n).
+ *
+ * Following this header is |key_num_bits| bits of |n|, then
+ * |key_num_bits| bits of |rr|. Both values are stored with most
+ * significant bit first. Each serialized number takes up
+ * |key_num_bits|/8 bytes.
+ *
+ * All fields in this struct are stored in network byte order when
+ * serialized.  To generate a copy with fields swapped to native byte
+ * order, use the function avb_rsa_public_key_header_validate_and_byteswap().
+ *
+ * The avb_rsa_verify() function expects a key in this serialized
+ * format.
+ *
+ * The 'avbtool extract_public_key' command can be used to generate a
+ * serialized RSA public key.
+ */
+typedef struct AvbRSAPublicKeyHeader {
+  uint32_t key_num_bits;
+  uint32_t n0inv;
+} AVB_ATTR_PACKED AvbRSAPublicKeyHeader;
+
+/* Copies |src| to |dest| and validates, byte-swapping fields in the
+ * process if needed. Returns true if valid, false if invalid.
+ */
+bool avb_rsa_public_key_header_validate_and_byteswap(
+    const AvbRSAPublicKeyHeader* src,
+    AvbRSAPublicKeyHeader* dest) AVB_ATTR_WARN_UNUSED_RESULT;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* AVB_CRYPTO_H_ */
diff --git a/avb/libavb/avb_descriptor.c b/avb/libavb/avb_descriptor.c
new file mode 100644
index 0000000..4f8e925
--- /dev/null
+++ b/avb/libavb/avb_descriptor.c
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "avb_descriptor.h"
+#include "avb_util.h"
+#include "avb_vbmeta_image.h"
+
+bool avb_descriptor_validate_and_byteswap(const AvbDescriptor* src,
+                                          AvbDescriptor* dest) {
+  dest->tag = avb_be64toh(src->tag);
+  dest->num_bytes_following = avb_be64toh(src->num_bytes_following);
+
+  if ((dest->num_bytes_following & 0x07) != 0) {
+    avb_error("Descriptor size is not divisible by 8.\n");
+    return false;
+  }
+  return true;
+}
+
+bool avb_descriptor_foreach(const uint8_t* image_data,
+                            size_t image_size,
+                            AvbDescriptorForeachFunc foreach_func,
+                            void* user_data) {
+  const AvbVBMetaImageHeader* header = NULL;
+  bool ret = false;
+  const uint8_t* image_end;
+  const uint8_t* desc_start;
+  const uint8_t* desc_end;
+  const uint8_t* p;
+
+  if (image_data == NULL) {
+    avb_error("image_data is NULL\n.");
+    goto out;
+  }
+
+  if (foreach_func == NULL) {
+    avb_error("foreach_func is NULL\n.");
+    goto out;
+  }
+
+  if (image_size < sizeof(AvbVBMetaImageHeader)) {
+    avb_error("Length is smaller than header.\n");
+    goto out;
+  }
+
+  /* Ensure magic is correct. */
+  if (avb_memcmp(image_data, AVB_MAGIC, AVB_MAGIC_LEN) != 0) {
+    avb_error("Magic is incorrect.\n");
+    goto out;
+  }
+
+  /* Careful, not byteswapped - also ensure it's aligned properly. */
+  avb_assert_aligned(image_data);
+  header = (const AvbVBMetaImageHeader*)image_data;
+  image_end = image_data + image_size;
+
+  desc_start = image_data + sizeof(AvbVBMetaImageHeader) +
+               avb_be64toh(header->authentication_data_block_size) +
+               avb_be64toh(header->descriptors_offset);
+
+  desc_end = desc_start + avb_be64toh(header->descriptors_size);
+
+  if (desc_start < image_data || desc_start > image_end ||
+      desc_end < image_data || desc_end > image_end || desc_end < desc_start) {
+    avb_error("Descriptors not inside passed-in data.\n");
+    goto out;
+  }
+
+  for (p = desc_start; p < desc_end;) {
+    const AvbDescriptor* dh = (const AvbDescriptor*)p;
+    avb_assert_aligned(dh);
+    uint64_t nb_following = avb_be64toh(dh->num_bytes_following);
+    uint64_t nb_total = sizeof(AvbDescriptor) + nb_following;
+
+    if ((nb_total & 7) != 0) {
+      avb_error("Invalid descriptor length.\n");
+      goto out;
+    }
+
+    if (nb_total + p < desc_start || nb_total + p > desc_end) {
+      avb_error("Invalid data in descriptors array.\n");
+      goto out;
+    }
+
+    if (foreach_func(dh, user_data) == 0) {
+      goto out;
+    }
+
+    p += nb_total;
+  }
+
+  ret = true;
+
+out:
+  return ret;
+}
+
+static bool count_descriptors(const AvbDescriptor* descriptor,
+                              void* user_data) {
+  size_t* num_descriptors = user_data;
+  *num_descriptors += 1;
+  return true;
+}
+
+typedef struct {
+  size_t descriptor_number;
+  const AvbDescriptor** descriptors;
+} SetDescriptorData;
+
+static bool set_descriptors(const AvbDescriptor* descriptor, void* user_data) {
+  SetDescriptorData* data = user_data;
+  data->descriptors[data->descriptor_number++] = descriptor;
+  return true;
+}
+
+const AvbDescriptor** avb_descriptor_get_all(const uint8_t* image_data,
+                                             size_t image_size,
+                                             size_t* out_num_descriptors) {
+  size_t num_descriptors = 0;
+  SetDescriptorData data;
+
+  avb_descriptor_foreach(
+      image_data, image_size, count_descriptors, &num_descriptors);
+
+  data.descriptor_number = 0;
+  data.descriptors =
+      avb_calloc(sizeof(const AvbDescriptor*) * (num_descriptors + 1));
+  if (data.descriptors == NULL) {
+    return NULL;
+  }
+  avb_descriptor_foreach(image_data, image_size, set_descriptors, &data);
+  avb_assert(data.descriptor_number == num_descriptors);
+
+  if (out_num_descriptors != NULL) {
+    *out_num_descriptors = num_descriptors;
+  }
+
+  return data.descriptors;
+}
diff --git a/avb/libavb/avb_descriptor.h b/avb/libavb/avb_descriptor.h
new file mode 100644
index 0000000..5d0f0c6
--- /dev/null
+++ b/avb/libavb/avb_descriptor.h
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#if !defined(AVB_INSIDE_LIBAVB_H) && !defined(AVB_COMPILATION)
+#error "Never include this file directly, include libavb.h instead."
+#endif
+
+#ifndef AVB_DESCRIPTOR_H_
+#define AVB_DESCRIPTOR_H_
+
+#include "avb_sysdeps.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Well-known descriptor tags.
+ *
+ * AVB_DESCRIPTOR_TAG_PROPERTY: see |AvbPropertyDescriptor| struct.
+ * AVB_DESCRIPTOR_TAG_HASHTREE: see |AvbHashtreeDescriptor| struct.
+ * AVB_DESCRIPTOR_TAG_HASH: see |AvbHashDescriptor| struct.
+ * AVB_DESCRIPTOR_TAG_KERNEL_CMDLINE: see |AvbKernelCmdlineDescriptor| struct.
+ * AVB_DESCRIPTOR_TAG_CHAIN_PARTITION: see |AvbChainPartitionDescriptor| struct.
+ */
+typedef enum {
+  AVB_DESCRIPTOR_TAG_PROPERTY,
+  AVB_DESCRIPTOR_TAG_HASHTREE,
+  AVB_DESCRIPTOR_TAG_HASH,
+  AVB_DESCRIPTOR_TAG_KERNEL_CMDLINE,
+  AVB_DESCRIPTOR_TAG_CHAIN_PARTITION,
+} AvbDescriptorTag;
+
+/* The header for a serialized descriptor.
+ *
+ * A descriptor always have two fields, a |tag| (denoting its type,
+ * see the |AvbDescriptorTag| enumeration) and the size of the bytes
+ * following, |num_bytes_following|.
+ *
+ * For padding, |num_bytes_following| is always a multiple of 8.
+ */
+typedef struct AvbDescriptor {
+  uint64_t tag;
+  uint64_t num_bytes_following;
+} AVB_ATTR_PACKED AvbDescriptor;
+
+/* Copies |src| to |dest| and validates, byte-swapping fields in the
+ * process if needed. Returns true if valid, false if invalid.
+ *
+ * Data following the struct is not validated nor copied.
+ */
+bool avb_descriptor_validate_and_byteswap(
+    const AvbDescriptor* src, AvbDescriptor* dest) AVB_ATTR_WARN_UNUSED_RESULT;
+
+/* Signature for callback function used in avb_descriptor_foreach().
+ * The passed in descriptor is given by |descriptor| and the
+ * |user_data| passed to avb_descriptor_foreach() function is in
+ * |user_data|. Return true to continue iterating, false to stop
+ * iterating.
+ *
+ * Note that |descriptor| points into the image passed to
+ * avb_descriptor_foreach() - all fields need to be byteswapped!
+ */
+typedef bool AvbDescriptorForeachFunc(const AvbDescriptor* descriptor,
+                                      void* user_data);
+
+/* Convenience function to iterate over all descriptors in an vbmeta
+ * image.
+ *
+ * The function given by |foreach_func| will be called for each
+ * descriptor. The given function should return true to continue
+ * iterating, false to stop.
+ *
+ * The |user_data| parameter will be passed to |foreach_func|.
+ *
+ * Returns false if the iteration was short-circuited, that is if
+ * an invocation of |foreach_func| returned false.
+ *
+ * Before using this function, you MUST verify |image_data| with
+ * avb_vbmeta_image_verify() and reject it unless it's signed by a known
+ * good public key. Additionally, |image_data| must be word-aligned.
+ */
+bool avb_descriptor_foreach(const uint8_t* image_data,
+                            size_t image_size,
+                            AvbDescriptorForeachFunc foreach_func,
+                            void* user_data);
+
+/* Gets all descriptors in a vbmeta image.
+ *
+ * The return value is a NULL-pointer terminated array of
+ * AvbDescriptor pointers. Free with avb_free() when you are done with
+ * it. If |out_num_descriptors| is non-NULL, the number of descriptors
+ * will be returned there.
+ *
+ * Note that each AvbDescriptor pointer in the array points into
+ * |image_data| - all fields need to be byteswapped!
+ *
+ * Before using this function, you MUST verify |image_data| with
+ * avb_vbmeta_image_verify() and reject it unless it's signed by a known
+ * good public key. Additionally, |image_data| must be word-aligned.
+ */
+const AvbDescriptor** avb_descriptor_get_all(const uint8_t* image_data,
+                                             size_t image_size,
+                                             size_t* out_num_descriptors)
+    AVB_ATTR_WARN_UNUSED_RESULT;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* AVB_DESCRIPTOR_H_ */
diff --git a/avb/libavb/avb_footer.c b/avb/libavb/avb_footer.c
new file mode 100644
index 0000000..5f6ed71
--- /dev/null
+++ b/avb/libavb/avb_footer.c
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "avb_footer.h"
+#include "avb_util.h"
+
+bool avb_footer_validate_and_byteswap(const AvbFooter* src, AvbFooter* dest) {
+  avb_memcpy(dest, src, sizeof(AvbFooter));
+
+  dest->version_major = avb_be32toh(dest->version_major);
+  dest->version_minor = avb_be32toh(dest->version_minor);
+
+  dest->original_image_size = avb_be64toh(dest->original_image_size);
+  dest->vbmeta_offset = avb_be64toh(dest->vbmeta_offset);
+  dest->vbmeta_size = avb_be64toh(dest->vbmeta_size);
+
+  /* Check that magic is correct. */
+  if (avb_safe_memcmp(dest->magic, AVB_FOOTER_MAGIC, AVB_FOOTER_MAGIC_LEN) !=
+      0) {
+    avb_error("Footer magic is incorrect.\n");
+    return false;
+  }
+
+  /* Ensure we don't attempt to access any fields if the footer major
+   * version is not supported.
+   */
+  if (dest->version_major > AVB_FOOTER_MAJOR_VERSION) {
+    avb_error("No support for footer version.\n");
+    return false;
+  }
+
+  return true;
+}
diff --git a/avb/libavb/avb_footer.h b/avb/libavb/avb_footer.h
new file mode 100644
index 0000000..6607c75
--- /dev/null
+++ b/avb/libavb/avb_footer.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#if !defined(AVB_INSIDE_LIBAVB_H) && !defined(AVB_COMPILATION)
+#error "Never include this file directly, include libavb.h instead."
+#endif
+
+#ifndef AVB_FOOTER_H_
+#define AVB_FOOTER_H_
+
+#include "avb_sysdeps.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Magic for the footer. */
+#define AVB_FOOTER_MAGIC "AVBf"
+#define AVB_FOOTER_MAGIC_LEN 4
+
+/* Size of the footer. */
+#define AVB_FOOTER_SIZE 64
+
+/* The current MAJOR and MINOR versions used - keep in sync with avbtool. */
+#define AVB_FOOTER_MAJOR_VERSION 1
+#define AVB_FOOTER_MINOR_VERSION 0
+
+/* The struct used as a footer used on partitions, used to find the
+ * AvbVBMetaImageHeader struct. This struct is always stored at the
+ * end of a partition.
+ */
+typedef struct AvbFooter {
+  /*   0: Four bytes equal to "AVBf" (AVB_FOOTER_MAGIC). */
+  uint8_t magic[AVB_FOOTER_MAGIC_LEN];
+  /*   4: The major version of the footer struct. */
+  uint32_t version_major;
+  /*   8: The minor version of the footer struct. */
+  uint32_t version_minor;
+
+  /*  12: The original size of the image on the partition. */
+  uint64_t original_image_size;
+
+  /*  20: The offset of the |AvbVBMetaImageHeader| struct. */
+  uint64_t vbmeta_offset;
+
+  /*  28: The size of the vbmeta block (header + auth + aux blocks). */
+  uint64_t vbmeta_size;
+
+  /*  36: Padding to ensure struct is size AVB_FOOTER_SIZE bytes. This
+   * must be set to zeroes.
+   */
+  uint8_t reserved[28];
+} AVB_ATTR_PACKED AvbFooter;
+
+/* Copies |src| to |dest| and validates, byte-swapping fields in the
+ * process if needed. Returns true if valid, false if invalid.
+ */
+bool avb_footer_validate_and_byteswap(const AvbFooter* src, AvbFooter* dest)
+    AVB_ATTR_WARN_UNUSED_RESULT;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* AVB_FOOTER_H_ */
diff --git a/avb/libavb/avb_hash_descriptor.c b/avb/libavb/avb_hash_descriptor.c
new file mode 100644
index 0000000..2e427de
--- /dev/null
+++ b/avb/libavb/avb_hash_descriptor.c
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "avb_hash_descriptor.h"
+#include "avb_util.h"
+
+bool avb_hash_descriptor_validate_and_byteswap(const AvbHashDescriptor* src,
+                                               AvbHashDescriptor* dest) {
+  uint64_t expected_size;
+
+  avb_memcpy(dest, src, sizeof(AvbHashDescriptor));
+
+  if (!avb_descriptor_validate_and_byteswap((const AvbDescriptor*)src,
+                                            (AvbDescriptor*)dest))
+    return false;
+
+  if (dest->parent_descriptor.tag != AVB_DESCRIPTOR_TAG_HASH) {
+    avb_error("Invalid tag for hash descriptor.\n");
+    return false;
+  }
+
+  dest->image_size = avb_be64toh(dest->image_size);
+  dest->partition_name_len = avb_be32toh(dest->partition_name_len);
+  dest->salt_len = avb_be32toh(dest->salt_len);
+  dest->digest_len = avb_be32toh(dest->digest_len);
+
+  /* Check that partition_name, salt, and digest are fully contained. */
+  expected_size = sizeof(AvbHashDescriptor) - sizeof(AvbDescriptor);
+  if (!avb_safe_add_to(&expected_size, dest->partition_name_len) ||
+      !avb_safe_add_to(&expected_size, dest->salt_len) ||
+      !avb_safe_add_to(&expected_size, dest->digest_len)) {
+    avb_error("Overflow while adding up sizes.\n");
+    return false;
+  }
+  if (expected_size > dest->parent_descriptor.num_bytes_following) {
+    avb_error("Descriptor payload size overflow.\n");
+    return false;
+  }
+  return true;
+}
diff --git a/avb/libavb/avb_hash_descriptor.h b/avb/libavb/avb_hash_descriptor.h
new file mode 100644
index 0000000..2668118
--- /dev/null
+++ b/avb/libavb/avb_hash_descriptor.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#if !defined(AVB_INSIDE_LIBAVB_H) && !defined(AVB_COMPILATION)
+#error "Never include this file directly, include libavb.h instead."
+#endif
+
+#ifndef AVB_HASH_DESCRIPTOR_H_
+#define AVB_HASH_DESCRIPTOR_H_
+
+#include "avb_descriptor.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* A descriptor containing information about hash for an image.
+ *
+ * This descriptor is typically used for boot partitions to verify the
+ * entire kernel+initramfs image before executing it.
+ *
+ * Following this struct are |partition_name_len| bytes of the
+ * partition name (UTF-8 encoded), |salt_len| bytes of salt, and then
+ * |digest_len| bytes of the digest.
+ *
+ * The |reserved| field is for future expansion and must be set to NUL
+ * bytes.
+ */
+typedef struct AvbHashDescriptor {
+  AvbDescriptor parent_descriptor;
+  uint64_t image_size;
+  uint8_t hash_algorithm[32];
+  uint32_t partition_name_len;
+  uint32_t salt_len;
+  uint32_t digest_len;
+  uint8_t reserved[64];
+} AVB_ATTR_PACKED AvbHashDescriptor;
+
+/* Copies |src| to |dest| and validates, byte-swapping fields in the
+ * process if needed. Returns true if valid, false if invalid.
+ *
+ * Data following the struct is not validated nor copied.
+ */
+bool avb_hash_descriptor_validate_and_byteswap(const AvbHashDescriptor* src,
+                                               AvbHashDescriptor* dest)
+    AVB_ATTR_WARN_UNUSED_RESULT;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* AVB_HASH_DESCRIPTOR_H_ */
diff --git a/avb/libavb/avb_hashtree_descriptor.c b/avb/libavb/avb_hashtree_descriptor.c
new file mode 100644
index 0000000..b961e47
--- /dev/null
+++ b/avb/libavb/avb_hashtree_descriptor.c
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "avb_hashtree_descriptor.h"
+#include "avb_util.h"
+
+bool avb_hashtree_descriptor_validate_and_byteswap(
+    const AvbHashtreeDescriptor* src, AvbHashtreeDescriptor* dest) {
+  uint64_t expected_size;
+
+  avb_memcpy(dest, src, sizeof(AvbHashtreeDescriptor));
+
+  if (!avb_descriptor_validate_and_byteswap((const AvbDescriptor*)src,
+                                            (AvbDescriptor*)dest))
+    return false;
+
+  if (dest->parent_descriptor.tag != AVB_DESCRIPTOR_TAG_HASHTREE) {
+    avb_error("Invalid tag for hashtree descriptor.\n");
+    return false;
+  }
+
+  dest->dm_verity_version = avb_be32toh(dest->dm_verity_version);
+  dest->image_size = avb_be64toh(dest->image_size);
+  dest->tree_offset = avb_be64toh(dest->tree_offset);
+  dest->tree_size = avb_be64toh(dest->tree_size);
+  dest->data_block_size = avb_be32toh(dest->data_block_size);
+  dest->hash_block_size = avb_be32toh(dest->hash_block_size);
+  dest->fec_num_roots = avb_be32toh(dest->fec_num_roots);
+  dest->fec_offset = avb_be64toh(dest->fec_offset);
+  dest->fec_size = avb_be64toh(dest->fec_size);
+  dest->partition_name_len = avb_be32toh(dest->partition_name_len);
+  dest->salt_len = avb_be32toh(dest->salt_len);
+  dest->root_digest_len = avb_be32toh(dest->root_digest_len);
+
+  /* Check that partition_name, salt, and root_digest are fully contained. */
+  expected_size = sizeof(AvbHashtreeDescriptor) - sizeof(AvbDescriptor);
+  if (!avb_safe_add_to(&expected_size, dest->partition_name_len) ||
+      !avb_safe_add_to(&expected_size, dest->salt_len) ||
+      !avb_safe_add_to(&expected_size, dest->root_digest_len)) {
+    avb_error("Overflow while adding up sizes.\n");
+    return false;
+  }
+  if (expected_size > dest->parent_descriptor.num_bytes_following) {
+    avb_error("Descriptor payload size overflow.\n");
+    return false;
+  }
+  return true;
+}
diff --git a/avb/libavb/avb_hashtree_descriptor.h b/avb/libavb/avb_hashtree_descriptor.h
new file mode 100644
index 0000000..a5aafbf
--- /dev/null
+++ b/avb/libavb/avb_hashtree_descriptor.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#if !defined(AVB_INSIDE_LIBAVB_H) && !defined(AVB_COMPILATION)
+#error "Never include this file directly, include libavb.h instead."
+#endif
+
+#ifndef AVB_HASHTREE_DESCRIPTOR_H_
+#define AVB_HASHTREE_DESCRIPTOR_H_
+
+#include "avb_descriptor.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* A descriptor containing information about a dm-verity hashtree.
+ *
+ * Hash-trees are used to verify large partitions typically containing
+ * file systems. See
+ * https://gitlab.com/cryptsetup/cryptsetup/wikis/DMVerity for more
+ * information about dm-verity.
+ *
+ * Following this struct are |partition_name_len| bytes of the
+ * partition name (UTF-8 encoded), |salt_len| bytes of salt, and then
+ * |root_digest_len| bytes of the root digest.
+ *
+ * The |reserved| field is for future expansion and must be set to NUL
+ * bytes.
+ */
+typedef struct AvbHashtreeDescriptor {
+  AvbDescriptor parent_descriptor;
+  uint32_t dm_verity_version;
+  uint64_t image_size;
+  uint64_t tree_offset;
+  uint64_t tree_size;
+  uint32_t data_block_size;
+  uint32_t hash_block_size;
+  uint32_t fec_num_roots;
+  uint64_t fec_offset;
+  uint64_t fec_size;
+  uint8_t hash_algorithm[32];
+  uint32_t partition_name_len;
+  uint32_t salt_len;
+  uint32_t root_digest_len;
+  uint8_t reserved[64];
+} AVB_ATTR_PACKED AvbHashtreeDescriptor;
+
+/* Copies |src| to |dest| and validates, byte-swapping fields in the
+ * process if needed. Returns true if valid, false if invalid.
+ *
+ * Data following the struct is not validated nor copied.
+ */
+bool avb_hashtree_descriptor_validate_and_byteswap(
+    const AvbHashtreeDescriptor* src,
+    AvbHashtreeDescriptor* dest) AVB_ATTR_WARN_UNUSED_RESULT;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* AVB_HASHTREE_DESCRIPTOR_H_ */
diff --git a/avb/libavb/avb_kernel_cmdline_descriptor.c b/avb/libavb/avb_kernel_cmdline_descriptor.c
new file mode 100644
index 0000000..67521f2
--- /dev/null
+++ b/avb/libavb/avb_kernel_cmdline_descriptor.c
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "avb_kernel_cmdline_descriptor.h"
+#include "avb_util.h"
+
+bool avb_kernel_cmdline_descriptor_validate_and_byteswap(
+    const AvbKernelCmdlineDescriptor* src, AvbKernelCmdlineDescriptor* dest) {
+  uint64_t expected_size;
+
+  avb_memcpy(dest, src, sizeof(AvbKernelCmdlineDescriptor));
+
+  if (!avb_descriptor_validate_and_byteswap((const AvbDescriptor*)src,
+                                            (AvbDescriptor*)dest))
+    return false;
+
+  if (dest->parent_descriptor.tag != AVB_DESCRIPTOR_TAG_KERNEL_CMDLINE) {
+    avb_error("Invalid tag for kernel cmdline descriptor.\n");
+    return false;
+  }
+
+  dest->flags = avb_be32toh(dest->flags);
+  dest->kernel_cmdline_length = avb_be32toh(dest->kernel_cmdline_length);
+
+  /* Check that kernel_cmdline is fully contained. */
+  expected_size = sizeof(AvbKernelCmdlineDescriptor) - sizeof(AvbDescriptor);
+  if (!avb_safe_add_to(&expected_size, dest->kernel_cmdline_length)) {
+    avb_error("Overflow while adding up sizes.\n");
+    return false;
+  }
+  if (expected_size > dest->parent_descriptor.num_bytes_following) {
+    avb_error("Descriptor payload size overflow.\n");
+    return false;
+  }
+
+  return true;
+}
diff --git a/avb/libavb/avb_kernel_cmdline_descriptor.h b/avb/libavb/avb_kernel_cmdline_descriptor.h
new file mode 100644
index 0000000..6908b3b
--- /dev/null
+++ b/avb/libavb/avb_kernel_cmdline_descriptor.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#if !defined(AVB_INSIDE_LIBAVB_H) && !defined(AVB_COMPILATION)
+#error "Never include this file directly, include libavb.h instead."
+#endif
+
+#ifndef AVB_KERNEL_CMDLINE_DESCRIPTOR_H_
+#define AVB_KERNEL_CMDLINE_DESCRIPTOR_H_
+
+#include "avb_descriptor.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Flags for kernel command-line descriptors.
+ *
+ * AVB_KERNEL_CMDLINE_FLAGS_USE_ONLY_IF_HASHTREE_NOT_DISABLED: The
+ * cmdline will only be applied if hashtree verification is not
+ * disabled (cf. AVB_VBMETA_IMAGE_FLAGS_HASHTREE_DISABLED).
+ *
+ * AVB_KERNEL_CMDLINE_FLAGS_USE_ONLY_IF_HASHTREE_DISABLED: The cmdline
+ * will only be applied if hashtree verification is disabled
+ * (cf. AVB_VBMETA_IMAGE_FLAGS_HASHTREE_DISABLED).
+ */
+typedef enum {
+  AVB_KERNEL_CMDLINE_FLAGS_USE_ONLY_IF_HASHTREE_NOT_DISABLED = (1 << 0),
+  AVB_KERNEL_CMDLINE_FLAGS_USE_ONLY_IF_HASHTREE_DISABLED = (1 << 1)
+} AvbKernelCmdlineFlags;
+
+/* A descriptor containing information to be appended to the kernel
+ * command-line.
+ *
+ * The |flags| field contains flags from the AvbKernelCmdlineFlags
+ * enumeration.
+ *
+ * Following this struct are |kernel_cmdline_len| bytes with the
+ * kernel command-line (UTF-8 encoded).
+ */
+typedef struct AvbKernelCmdlineDescriptor {
+  AvbDescriptor parent_descriptor;
+  uint32_t flags;
+  uint32_t kernel_cmdline_length;
+} AVB_ATTR_PACKED AvbKernelCmdlineDescriptor;
+
+/* Copies |src| to |dest| and validates, byte-swapping fields in the
+ * process if needed. Returns true if valid, false if invalid.
+ *
+ * Data following the struct is not validated nor copied.
+ */
+bool avb_kernel_cmdline_descriptor_validate_and_byteswap(
+    const AvbKernelCmdlineDescriptor* src,
+    AvbKernelCmdlineDescriptor* dest) AVB_ATTR_WARN_UNUSED_RESULT;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* AVB_KERNEL_CMDLINE_DESCRIPTOR_H_ */
diff --git a/avb/libavb/avb_ops.h b/avb/libavb/avb_ops.h
new file mode 100644
index 0000000..908c66c
--- /dev/null
+++ b/avb/libavb/avb_ops.h
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#if !defined(AVB_INSIDE_LIBAVB_H) && !defined(AVB_COMPILATION)
+#error "Never include this file directly, include libavb.h instead."
+#endif
+
+#ifndef AVB_OPS_H_
+#define AVB_OPS_H_
+
+#include "avb_sysdeps.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Return codes used for I/O operations.
+ *
+ * AVB_IO_RESULT_OK is returned if the requested operation was
+ * successful.
+ *
+ * AVB_IO_RESULT_ERROR_IO is returned if the underlying hardware (disk
+ * or other subsystem) encountered an I/O error.
+ *
+ * AVB_IO_RESULT_ERROR_OOM is returned if unable to allocate memory.
+ *
+ * AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION is returned if the requested
+ * partition does not exist.
+ *
+ * AVB_IO_RESULT_ERROR_RANGE_OUTSIDE_PARTITION is returned if the
+ * range of bytes requested to be read or written is outside the range
+ * of the partition.
+ */
+typedef enum {
+  AVB_IO_RESULT_OK,
+  AVB_IO_RESULT_ERROR_OOM,
+  AVB_IO_RESULT_ERROR_IO,
+  AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION,
+  AVB_IO_RESULT_ERROR_RANGE_OUTSIDE_PARTITION
+} AvbIOResult;
+
+struct AvbOps;
+typedef struct AvbOps AvbOps;
+
+/* Forward-declaration of operations in libavb_ab. */
+struct AvbABOps;
+
+/* Forward-declaration of operations in libavb_atx. */
+struct AvbAtxOps;
+
+/* High-level operations/functions/methods that are platform
+ * dependent.
+ */
+struct AvbOps {
+  /* This pointer can be used by the application/bootloader using
+   * libavb and is typically used in each operation to get a pointer
+   * to platform-specific resources. It cannot be used by libraries.
+   */
+  void* user_data;
+
+  /* If libavb_ab is used, this should point to the
+   * AvbABOps. Otherwise it must be set to NULL.
+   */
+  struct AvbABOps* ab_ops;
+
+  /* If libavb_atx is used, this should point to the
+   * AvbAtxOps. Otherwise it must be set to NULL.
+   */
+  struct AvbAtxOps* atx_ops;
+
+  /* Reads |num_bytes| from offset |offset| from partition with name
+   * |partition| (NUL-terminated UTF-8 string). If |offset| is
+   * negative, its absolute value should be interpreted as the number
+   * of bytes from the end of the partition.
+   *
+   * This function returns AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION if
+   * there is no partition with the given name,
+   * AVB_IO_RESULT_ERROR_RANGE_OUTSIDE_PARTITION if the requested
+   * |offset| is outside the partition, and AVB_IO_RESULT_ERROR_IO if
+   * there was an I/O error from the underlying I/O subsystem.  If the
+   * operation succeeds as requested AVB_IO_RESULT_OK is returned and
+   * the data is available in |buffer|.
+   *
+   * The only time partial I/O may occur is if reading beyond the end
+   * of the partition. In this case the value returned in
+   * |out_num_read| may be smaller than |num_bytes|.
+   */
+  AvbIOResult (*read_from_partition)(AvbOps* ops,
+                                     const char* partition,
+                                     int64_t offset,
+                                     size_t num_bytes,
+                                     void* buffer,
+                                     size_t* out_num_read);
+
+  /* Writes |num_bytes| from |bffer| at offset |offset| to partition
+   * with name |partition| (NUL-terminated UTF-8 string). If |offset|
+   * is negative, its absolute value should be interpreted as the
+   * number of bytes from the end of the partition.
+   *
+   * This function returns AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION if
+   * there is no partition with the given name,
+   * AVB_IO_RESULT_ERROR_RANGE_OUTSIDE_PARTITION if the requested
+   * byterange goes outside the partition, and AVB_IO_RESULT_ERROR_IO
+   * if there was an I/O error from the underlying I/O subsystem.  If
+   * the operation succeeds as requested AVB_IO_RESULT_OK is
+   * returned.
+   *
+   * This function never does any partial I/O, it either transfers all
+   * of the requested bytes or returns an error.
+   */
+  AvbIOResult (*write_to_partition)(AvbOps* ops,
+                                    const char* partition,
+                                    int64_t offset,
+                                    size_t num_bytes,
+                                    const void* buffer);
+
+  /* Checks if the given public key used to sign the 'vbmeta'
+   * partition is trusted. Boot loaders typically compare this with
+   * embedded key material generated with 'avbtool
+   * extract_public_key'.
+   *
+   * The public key is in the array pointed to by |public_key_data|
+   * and is of |public_key_length| bytes.
+   *
+   * If there is no public key metadata (set with the avbtool option
+   * --public_key_metadata) then |public_key_metadata| will be set to
+   * NULL. Otherwise this field points to the data which is
+   * |public_key_metadata_length| bytes long.
+   *
+   * If AVB_IO_RESULT_OK is returned then |out_is_trusted| is set -
+   * true if trusted or false if untrusted.
+   */
+  AvbIOResult (*validate_vbmeta_public_key)(AvbOps* ops,
+                                            const uint8_t* public_key_data,
+                                            size_t public_key_length,
+                                            const uint8_t* public_key_metadata,
+                                            size_t public_key_metadata_length,
+                                            bool* out_is_trusted);
+
+  /* Gets the rollback index corresponding to the location given by
+   * |rollback_index_location|. The value is returned in
+   * |out_rollback_index|. Returns AVB_IO_RESULT_OK if the rollback
+   * index was retrieved, otherwise an error code.
+   *
+   * A device may have a limited amount of rollback index locations (say,
+   * one or four) so may error out if |rollback_index_location| exceeds
+   * this number.
+   */
+  AvbIOResult (*read_rollback_index)(AvbOps* ops,
+                                     size_t rollback_index_location,
+                                     uint64_t* out_rollback_index);
+
+  /* Sets the rollback index corresponding to the location given by
+   * |rollback_index_location| to |rollback_index|. Returns
+   * AVB_IO_RESULT_OK if the rollback index was set, otherwise an
+   * error code.
+   *
+   * A device may have a limited amount of rollback index locations (say,
+   * one or four) so may error out if |rollback_index_location| exceeds
+   * this number.
+   */
+  AvbIOResult (*write_rollback_index)(AvbOps* ops,
+                                      size_t rollback_index_location,
+                                      uint64_t rollback_index);
+
+  /* Gets whether the device is unlocked. The value is returned in
+   * |out_is_unlocked| (true if unlocked, false otherwise). Returns
+   * AVB_IO_RESULT_OK if the state was retrieved, otherwise an error
+   * code.
+   */
+  AvbIOResult (*read_is_device_unlocked)(AvbOps* ops, bool* out_is_unlocked);
+
+  /* Gets the unique partition GUID for a partition with name in
+   * |partition| (NUL-terminated UTF-8 string). The GUID is copied as
+   * a string into |guid_buf| of size |guid_buf_size| and will be NUL
+   * terminated. The string must be lower-case and properly
+   * hyphenated. For example:
+   *
+   *  527c1c6d-6361-4593-8842-3c78fcd39219
+   *
+   * Returns AVB_IO_RESULT_OK on success, otherwise an error code.
+   */
+  AvbIOResult (*get_unique_guid_for_partition)(AvbOps* ops,
+                                               const char* partition,
+                                               char* guid_buf,
+                                               size_t guid_buf_size);
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* AVB_OPS_H_ */
diff --git a/avb/libavb/avb_property_descriptor.c b/avb/libavb/avb_property_descriptor.c
new file mode 100644
index 0000000..7eba2c0
--- /dev/null
+++ b/avb/libavb/avb_property_descriptor.c
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "avb_property_descriptor.h"
+#include "avb_util.h"
+
+bool avb_property_descriptor_validate_and_byteswap(
+    const AvbPropertyDescriptor* src, AvbPropertyDescriptor* dest) {
+  uint64_t expected_size;
+
+  avb_memcpy(dest, src, sizeof(AvbPropertyDescriptor));
+
+  if (!avb_descriptor_validate_and_byteswap((const AvbDescriptor*)src,
+                                            (AvbDescriptor*)dest))
+    return false;
+
+  if (dest->parent_descriptor.tag != AVB_DESCRIPTOR_TAG_PROPERTY) {
+    avb_error("Invalid tag for property descriptor.\n");
+    return false;
+  }
+
+  dest->key_num_bytes = avb_be64toh(dest->key_num_bytes);
+  dest->value_num_bytes = avb_be64toh(dest->value_num_bytes);
+
+  /* Check that key and value are fully contained. */
+  expected_size = sizeof(AvbPropertyDescriptor) - sizeof(AvbDescriptor) + 2;
+  if (!avb_safe_add_to(&expected_size, dest->key_num_bytes) ||
+      !avb_safe_add_to(&expected_size, dest->value_num_bytes)) {
+    avb_error("Overflow while adding up sizes.\n");
+    return false;
+  }
+  if (expected_size > dest->parent_descriptor.num_bytes_following) {
+    avb_error("Descriptor payload size overflow.\n");
+    return false;
+  }
+
+  return true;
+}
+
+typedef struct {
+  const char* key;
+  size_t key_size;
+  const char* ret_value;
+  size_t ret_value_size;
+} PropertyIteratorData;
+
+static bool property_lookup_desc_foreach(const AvbDescriptor* header,
+                                         void* user_data) {
+  PropertyIteratorData* data = (PropertyIteratorData*)user_data;
+  AvbPropertyDescriptor prop_desc;
+  const uint8_t* p;
+  bool ret = true;
+
+  if (header->tag != AVB_DESCRIPTOR_TAG_PROPERTY) {
+    goto out;
+  }
+
+  if (!avb_property_descriptor_validate_and_byteswap(
+          (const AvbPropertyDescriptor*)header, &prop_desc)) {
+    goto out;
+  }
+
+  p = (const uint8_t*)header;
+  if (p[sizeof(AvbPropertyDescriptor) + prop_desc.key_num_bytes] != 0) {
+    avb_error("No terminating NUL byte in key.\n");
+    goto out;
+  }
+
+  if (data->key_size == prop_desc.key_num_bytes) {
+    if (avb_memcmp(p + sizeof(AvbPropertyDescriptor),
+                   data->key,
+                   data->key_size) == 0) {
+      data->ret_value = (const char*)(p + sizeof(AvbPropertyDescriptor) +
+                                      prop_desc.key_num_bytes + 1);
+      data->ret_value_size = prop_desc.value_num_bytes;
+      /* Stop iterating. */
+      ret = false;
+      goto out;
+    }
+  }
+
+out:
+  return ret;
+}
+
+const char* avb_property_lookup(const uint8_t* image_data,
+                                size_t image_size,
+                                const char* key,
+                                size_t key_size,
+                                size_t* out_value_size) {
+  PropertyIteratorData data;
+
+  if (key_size == 0) {
+    key_size = avb_strlen(key);
+  }
+
+  data.key = key;
+  data.key_size = key_size;
+
+  if (avb_descriptor_foreach(
+          image_data, image_size, property_lookup_desc_foreach, &data) == 0) {
+    if (out_value_size != NULL) {
+      *out_value_size = data.ret_value_size;
+    }
+    return data.ret_value;
+  }
+
+  if (out_value_size != NULL) {
+    *out_value_size = 0;
+  }
+  return NULL;
+}
+
+bool avb_property_lookup_uint64(const uint8_t* image_data,
+                                size_t image_size,
+                                const char* key,
+                                size_t key_size,
+                                uint64_t* out_value) {
+  const char* value;
+  bool ret = false;
+  uint64_t parsed_val;
+  int base;
+  int n;
+
+  value = avb_property_lookup(image_data, image_size, key, key_size, NULL);
+  if (value == NULL) {
+    goto out;
+  }
+
+  base = 10;
+  if (avb_memcmp(value, "0x", 2) == 0) {
+    base = 16;
+    value += 2;
+  }
+
+  parsed_val = 0;
+  for (n = 0; value[n] != '\0'; n++) {
+    int c = value[n];
+    int digit;
+
+    parsed_val *= base;
+
+    if (c >= '0' && c <= '9') {
+      digit = c - '0';
+    } else if (base == 16 && c >= 'a' && c <= 'f') {
+      digit = c - 'a' + 10;
+    } else if (base == 16 && c >= 'A' && c <= 'F') {
+      digit = c - 'A' + 10;
+    } else {
+      avb_error("Invalid digit.\n");
+      goto out;
+    }
+
+    parsed_val += digit;
+  }
+
+  ret = true;
+  if (out_value != NULL) {
+    *out_value = parsed_val;
+  }
+
+out:
+  return ret;
+}
diff --git a/avb/libavb/avb_property_descriptor.h b/avb/libavb/avb_property_descriptor.h
new file mode 100644
index 0000000..a2fef69
--- /dev/null
+++ b/avb/libavb/avb_property_descriptor.h
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#if !defined(AVB_INSIDE_LIBAVB_H) && !defined(AVB_COMPILATION)
+#error "Never include this file directly, include libavb.h instead."
+#endif
+
+#ifndef AVB_PROPERTY_DESCRIPTOR_H_
+#define AVB_PROPERTY_DESCRIPTOR_H_
+
+#include "avb_descriptor.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* A descriptor for properties (free-form key/value pairs).
+ *
+ * Following this struct are |key_num_bytes| bytes of key data,
+ * followed by a NUL byte, then |value_num_bytes| bytes of value data,
+ * followed by a NUL byte and then enough padding to make the combined
+ * size a multiple of 8.
+ */
+typedef struct AvbPropertyDescriptor {
+  AvbDescriptor parent_descriptor;
+  uint64_t key_num_bytes;
+  uint64_t value_num_bytes;
+} AVB_ATTR_PACKED AvbPropertyDescriptor;
+
+/* Copies |src| to |dest| and validates, byte-swapping fields in the
+ * process if needed. Returns true if valid, false if invalid.
+ *
+ * Data following the struct is not validated nor copied.
+ */
+bool avb_property_descriptor_validate_and_byteswap(
+    const AvbPropertyDescriptor* src,
+    AvbPropertyDescriptor* dest) AVB_ATTR_WARN_UNUSED_RESULT;
+
+/* Convenience function for looking up the value for a property with
+ * name |key| in a vbmeta image. If |key_size| is 0, |key| must be
+ * NUL-terminated.
+ *
+ * The |image_data| parameter must be a pointer to a vbmeta image of
+ * size |image_size|.
+ *
+ * This function returns a pointer to the value inside the passed-in
+ * image or NULL if not found. Note that the value is always
+ * guaranteed to be followed by a NUL byte.
+ *
+ * If the value was found and |out_value_size| is not NULL, the size
+ * of the value is returned there.
+ *
+ * This function is O(n) in number of descriptors so if you need to
+ * look up a lot of values, you may want to build a more efficient
+ * lookup-table by manually walking all descriptors using
+ * avb_descriptor_foreach().
+ *
+ * Before using this function, you MUST verify |image_data| with
+ * avb_vbmeta_image_verify() and reject it unless it's signed by a
+ * known good public key.
+ */
+const char* avb_property_lookup(const uint8_t* image_data,
+                                size_t image_size,
+                                const char* key,
+                                size_t key_size,
+                                size_t* out_value_size)
+    AVB_ATTR_WARN_UNUSED_RESULT;
+
+/* Like avb_property_lookup() but parses the intial portions of the
+ * value as an unsigned 64-bit integer. Both decimal and hexadecimal
+ * representations (e.g. "0x2a") are supported. Returns false on
+ * failure and true on success. On success, the parsed value is
+ * returned in |out_value|.
+ */
+bool avb_property_lookup_uint64(const uint8_t* image_data,
+                                size_t image_size,
+                                const char* key,
+                                size_t key_size,
+                                uint64_t* out_value)
+    AVB_ATTR_WARN_UNUSED_RESULT;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* AVB_PROPERTY_DESCRIPTOR_H_ */
diff --git a/avb/libavb/avb_rsa.c b/avb/libavb/avb_rsa.c
new file mode 100644
index 0000000..dcecc16
--- /dev/null
+++ b/avb/libavb/avb_rsa.c
@@ -0,0 +1,299 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/* Copyright (c) 2011 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.
+ */
+
+/* Implementation of RSA signature verification which uses a pre-processed
+ * key for computation. The code extends libmincrypt RSA verification code to
+ * support multiple RSA key lengths and hash digest algorithms.
+ */
+
+#include "avb_rsa.h"
+#include "avb_sha.h"
+#include "avb_util.h"
+#include "avb_vbmeta_image.h"
+
+typedef struct Key {
+  unsigned int len; /* Length of n[] in number of uint32_t */
+  uint32_t n0inv;   /* -1 / n[0] mod 2^32 */
+  uint32_t* n;      /* modulus as array (host-byte order) */
+  uint32_t* rr;     /* R^2 as array (host-byte order) */
+} Key;
+
+Key* parse_key_data(const uint8_t* data, size_t length) {
+  AvbRSAPublicKeyHeader h;
+  Key* key = NULL;
+  size_t expected_length;
+  unsigned int i;
+  const uint8_t* n;
+  const uint8_t* rr;
+
+  if (!avb_rsa_public_key_header_validate_and_byteswap(
+          (const AvbRSAPublicKeyHeader*)data, &h)) {
+    avb_error("Invalid key.\n");
+    goto fail;
+  }
+
+  if (!(h.key_num_bits == 2048 || h.key_num_bits == 4096 ||
+        h.key_num_bits == 8192)) {
+    avb_error("Unexpected key length.\n");
+    goto fail;
+  }
+
+  expected_length = sizeof(AvbRSAPublicKeyHeader) + 2 * h.key_num_bits / 8;
+  if (length != expected_length) {
+    avb_error("Key does not match expected length.\n");
+    goto fail;
+  }
+
+  n = data + sizeof(AvbRSAPublicKeyHeader);
+  rr = data + sizeof(AvbRSAPublicKeyHeader) + h.key_num_bits / 8;
+
+  /* Store n and rr following the key header so we only have to do one
+   * allocation.
+   */
+  key = (Key*)(avb_malloc(sizeof(Key) + 2 * h.key_num_bits / 8));
+  if (key == NULL) {
+    goto fail;
+  }
+
+  key->len = h.key_num_bits / 32;
+  key->n0inv = h.n0inv;
+  key->n = (uint32_t*)(key + 1); /* Skip ahead sizeof(Key) bytes. */
+  key->rr = key->n + key->len;
+
+  /* Crypto-code below (modpowF4() and friends) expects the key in
+   * little-endian format (rather than the format we're storing the
+   * key in), so convert it.
+   */
+  for (i = 0; i < key->len; i++) {
+    key->n[i] = avb_be32toh(((uint32_t*)n)[key->len - i - 1]);
+    key->rr[i] = avb_be32toh(((uint32_t*)rr)[key->len - i - 1]);
+  }
+  return key;
+
+fail:
+  if (key != NULL) {
+    avb_free(key);
+  }
+  return NULL;
+}
+
+void free_parsed_key(Key* key) {
+  avb_free(key);
+}
+
+/* a[] -= mod */
+static void subM(const Key* key, uint32_t* a) {
+  int64_t A = 0;
+  uint32_t i;
+  for (i = 0; i < key->len; ++i) {
+    A += (uint64_t)a[i] - key->n[i];
+    a[i] = (uint32_t)A;
+    A >>= 32;
+  }
+}
+
+/* return a[] >= mod */
+static int geM(const Key* key, uint32_t* a) {
+  uint32_t i;
+  for (i = key->len; i;) {
+    --i;
+    if (a[i] < key->n[i]) {
+      return 0;
+    }
+    if (a[i] > key->n[i]) {
+      return 1;
+    }
+  }
+  return 1; /* equal */
+}
+
+/* montgomery c[] += a * b[] / R % mod */
+static void montMulAdd(const Key* key,
+                       uint32_t* c,
+                       const uint32_t a,
+                       const uint32_t* b) {
+  uint64_t A = (uint64_t)a * b[0] + c[0];
+  uint32_t d0 = (uint32_t)A * key->n0inv;
+  uint64_t B = (uint64_t)d0 * key->n[0] + (uint32_t)A;
+  uint32_t i;
+
+  for (i = 1; i < key->len; ++i) {
+    A = (A >> 32) + (uint64_t)a * b[i] + c[i];
+    B = (B >> 32) + (uint64_t)d0 * key->n[i] + (uint32_t)A;
+    c[i - 1] = (uint32_t)B;
+  }
+
+  A = (A >> 32) + (B >> 32);
+
+  c[i - 1] = (uint32_t)A;
+
+  if (A >> 32) {
+    subM(key, c);
+  }
+}
+
+/* montgomery c[] = a[] * b[] / R % mod */
+static void montMul(const Key* key, uint32_t* c, uint32_t* a, uint32_t* b) {
+  uint32_t i;
+  for (i = 0; i < key->len; ++i) {
+    c[i] = 0;
+  }
+  for (i = 0; i < key->len; ++i) {
+    montMulAdd(key, c, a[i], b);
+  }
+}
+
+/* In-place public exponentiation. (65537}
+ * Input and output big-endian byte array in inout.
+ */
+static void modpowF4(const Key* key, uint8_t* inout) {
+  uint32_t* a = (uint32_t*)avb_malloc(key->len * sizeof(uint32_t));
+  uint32_t* aR = (uint32_t*)avb_malloc(key->len * sizeof(uint32_t));
+  uint32_t* aaR = (uint32_t*)avb_malloc(key->len * sizeof(uint32_t));
+  if (a == NULL || aR == NULL || aaR == NULL) {
+    goto out;
+  }
+
+  uint32_t* aaa = aaR; /* Re-use location. */
+  int i;
+
+  /* Convert from big endian byte array to little endian word array. */
+  for (i = 0; i < (int)key->len; ++i) {
+    uint32_t tmp = (inout[((key->len - 1 - i) * 4) + 0] << 24) |
+                   (inout[((key->len - 1 - i) * 4) + 1] << 16) |
+                   (inout[((key->len - 1 - i) * 4) + 2] << 8) |
+                   (inout[((key->len - 1 - i) * 4) + 3] << 0);
+    a[i] = tmp;
+  }
+
+  montMul(key, aR, a, key->rr); /* aR = a * RR / R mod M   */
+  for (i = 0; i < 16; i += 2) {
+    montMul(key, aaR, aR, aR);  /* aaR = aR * aR / R mod M */
+    montMul(key, aR, aaR, aaR); /* aR = aaR * aaR / R mod M */
+  }
+  montMul(key, aaa, aR, a); /* aaa = aR * a / R mod M */
+
+  /* Make sure aaa < mod; aaa is at most 1x mod too large. */
+  if (geM(key, aaa)) {
+    subM(key, aaa);
+  }
+
+  /* Convert to bigendian byte array */
+  for (i = (int)key->len - 1; i >= 0; --i) {
+    uint32_t tmp = aaa[i];
+    *inout++ = (uint8_t)(tmp >> 24);
+    *inout++ = (uint8_t)(tmp >> 16);
+    *inout++ = (uint8_t)(tmp >> 8);
+    *inout++ = (uint8_t)(tmp >> 0);
+  }
+
+out:
+  if (a != NULL) {
+    avb_free(a);
+  }
+  if (aR != NULL) {
+    avb_free(aR);
+  }
+  if (aaR != NULL) {
+    avb_free(aaR);
+  }
+}
+
+/* Verify a RSA PKCS1.5 signature against an expected hash.
+ * Returns false on failure, true on success.
+ */
+bool avb_rsa_verify(const uint8_t* key,
+                    size_t key_num_bytes,
+                    const uint8_t* sig,
+                    size_t sig_num_bytes,
+                    const uint8_t* hash,
+                    size_t hash_num_bytes,
+                    const uint8_t* padding,
+                    size_t padding_num_bytes) {
+  uint8_t* buf = NULL;
+  Key* parsed_key = NULL;
+  bool success = false;
+
+  if (key == NULL || sig == NULL || hash == NULL || padding == NULL) {
+    avb_error("Invalid input.\n");
+    goto out;
+  }
+
+  parsed_key = parse_key_data(key, key_num_bytes);
+  if (parsed_key == NULL) {
+    avb_error("Error parsing key.\n");
+    goto out;
+  }
+
+  if (sig_num_bytes != (parsed_key->len * sizeof(uint32_t))) {
+    avb_error("Signature length does not match key length.\n");
+    goto out;
+  }
+
+  if (padding_num_bytes != sig_num_bytes - hash_num_bytes) {
+    avb_error("Padding length does not match hash and signature lengths.\n");
+    goto out;
+  }
+
+  buf = (uint8_t*)avb_malloc(sig_num_bytes);
+  if (buf == NULL) {
+    avb_error("Error allocating memory.\n");
+    goto out;
+  }
+  avb_memcpy(buf, sig, sig_num_bytes);
+
+  modpowF4(parsed_key, buf);
+
+  /* Check padding bytes.
+   *
+   * Even though there are probably no timing issues here, we use
+   * avb_safe_memcmp() just to be on the safe side.
+   */
+  if (avb_safe_memcmp(buf, padding, padding_num_bytes)) {
+    avb_error("Padding check failed.\n");
+    goto out;
+  }
+
+  /* Check hash. */
+  if (avb_safe_memcmp(buf + padding_num_bytes, hash, hash_num_bytes)) {
+    avb_error("Hash check failed.\n");
+    goto out;
+  }
+
+  success = true;
+
+out:
+  if (parsed_key != NULL) {
+    free_parsed_key(parsed_key);
+  }
+  if (buf != NULL) {
+    avb_free(buf);
+  }
+  return success;
+}
diff --git a/avb/libavb/avb_rsa.h b/avb/libavb/avb_rsa.h
new file mode 100644
index 0000000..c2dcf47
--- /dev/null
+++ b/avb/libavb/avb_rsa.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/* Copyright (c) 2011 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.
+ */
+
+#ifdef AVB_INSIDE_LIBAVB_H
+#error "You can't include avb_rsa.h in the public header libavb.h."
+#endif
+
+#ifndef AVB_COMPILATION
+#error "Never include this file, it may only be used from internal avb code."
+#endif
+
+#ifndef AVB_RSA_H_
+#define AVB_RSA_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "avb_crypto.h"
+#include "avb_sysdeps.h"
+
+/* Using the key given by |key|, verify a RSA signature |sig| of
+ * length |sig_num_bytes| against an expected |hash| of length
+ * |hash_num_bytes|. The padding to expect must be passed in using
+ * |padding| of length |padding_num_bytes|.
+ *
+ * The data in |key| must match the format defined in
+ * |AvbRSAPublicKeyHeader|, including the two large numbers
+ * following. The |key_num_bytes| must be the size of the entire
+ * serialized key.
+ *
+ * Returns false if verification fails, true otherwise.
+ */
+bool avb_rsa_verify(const uint8_t* key,
+                    size_t key_num_bytes,
+                    const uint8_t* sig,
+                    size_t sig_num_bytes,
+                    const uint8_t* hash,
+                    size_t hash_num_bytes,
+                    const uint8_t* padding,
+                    size_t padding_num_bytes) AVB_ATTR_WARN_UNUSED_RESULT;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* AVB_RSA_H_ */
diff --git a/avb/libavb/avb_sha.h b/avb/libavb/avb_sha.h
new file mode 100644
index 0000000..c5a6a4c
--- /dev/null
+++ b/avb/libavb/avb_sha.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifdef AVB_INSIDE_LIBAVB_H
+#error "You can't include avb_sha.h in the public header libavb.h."
+#endif
+
+#ifndef AVB_COMPILATION
+#error "Never include this file, it may only be used from internal avb code."
+#endif
+
+#ifndef AVB_SHA_H_
+#define AVB_SHA_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "avb_crypto.h"
+#include "avb_sysdeps.h"
+
+/* Block size in bytes of a SHA-256 digest. */
+#define AVB_SHA256_BLOCK_SIZE 64
+
+
+/* Block size in bytes of a SHA-512 digest. */
+#define AVB_SHA512_BLOCK_SIZE 128
+
+/* Data structure used for SHA-256. */
+typedef struct {
+  uint32_t h[8];
+  uint32_t tot_len;
+  uint32_t len;
+  uint8_t block[2 * AVB_SHA256_BLOCK_SIZE];
+  uint8_t buf[AVB_SHA256_DIGEST_SIZE]; /* Used for storing the final digest. */
+} AvbSHA256Ctx;
+
+/* Data structure used for SHA-512. */
+typedef struct {
+  uint64_t h[8];
+  uint32_t tot_len;
+  uint32_t len;
+  uint8_t block[2 * AVB_SHA512_BLOCK_SIZE];
+  uint8_t buf[AVB_SHA512_DIGEST_SIZE]; /* Used for storing the final digest. */
+} AvbSHA512Ctx;
+
+/* Initializes the SHA-256 context. */
+void avb_sha256_init(AvbSHA256Ctx* ctx);
+
+/* Updates the SHA-256 context with |len| bytes from |data|. */
+void avb_sha256_update(AvbSHA256Ctx* ctx, const uint8_t* data, uint32_t len);
+
+/* Returns the SHA-256 digest. */
+uint8_t* avb_sha256_final(AvbSHA256Ctx* ctx) AVB_ATTR_WARN_UNUSED_RESULT;
+
+/* Initializes the SHA-512 context. */
+void avb_sha512_init(AvbSHA512Ctx* ctx);
+
+/* Updates the SHA-512 context with |len| bytes from |data|. */
+void avb_sha512_update(AvbSHA512Ctx* ctx, const uint8_t* data, uint32_t len);
+
+/* Returns the SHA-512 digest. */
+uint8_t* avb_sha512_final(AvbSHA512Ctx* ctx) AVB_ATTR_WARN_UNUSED_RESULT;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* AVB_SHA_H_ */
diff --git a/avb/libavb/avb_sha256.c b/avb/libavb/avb_sha256.c
new file mode 100644
index 0000000..cdd143a
--- /dev/null
+++ b/avb/libavb/avb_sha256.c
@@ -0,0 +1,390 @@
+/* SHA-256 and SHA-512 implementation based on code by Oliver Gay
+ * <olivier.gay@a3.epfl.ch> under a BSD-style license. See below.
+ */
+
+/*
+ * FIPS 180-2 SHA-224/256/384/512 implementation
+ * Last update: 02/02/2007
+ * Issue date:  04/30/2005
+ *
+ * Copyright (C) 2005, 2007 Olivier Gay <olivier.gay@a3.epfl.ch>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the project nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "avb_sha.h"
+
+#define SHFR(x, n) (x >> n)
+#define ROTR(x, n) ((x >> n) | (x << ((sizeof(x) << 3) - n)))
+#define ROTL(x, n) ((x << n) | (x >> ((sizeof(x) << 3) - n)))
+#define CH(x, y, z) ((x & y) ^ (~x & z))
+#define MAJ(x, y, z) ((x & y) ^ (x & z) ^ (y & z))
+
+#define SHA256_F1(x) (ROTR(x, 2) ^ ROTR(x, 13) ^ ROTR(x, 22))
+#define SHA256_F2(x) (ROTR(x, 6) ^ ROTR(x, 11) ^ ROTR(x, 25))
+#define SHA256_F3(x) (ROTR(x, 7) ^ ROTR(x, 18) ^ SHFR(x, 3))
+#define SHA256_F4(x) (ROTR(x, 17) ^ ROTR(x, 19) ^ SHFR(x, 10))
+
+#define UNPACK32(x, str)                 \
+  {                                      \
+    *((str) + 3) = (uint8_t)((x));       \
+    *((str) + 2) = (uint8_t)((x) >> 8);  \
+    *((str) + 1) = (uint8_t)((x) >> 16); \
+    *((str) + 0) = (uint8_t)((x) >> 24); \
+  }
+
+#define PACK32(str, x)                                                    \
+  {                                                                       \
+    *(x) = ((uint32_t) * ((str) + 3)) | ((uint32_t) * ((str) + 2) << 8) | \
+           ((uint32_t) * ((str) + 1) << 16) |                             \
+           ((uint32_t) * ((str) + 0) << 24);                              \
+  }
+
+/* Macros used for loops unrolling */
+
+#define SHA256_SCR(i) \
+  { w[i] = SHA256_F4(w[i - 2]) + w[i - 7] + SHA256_F3(w[i - 15]) + w[i - 16]; }
+
+#define SHA256_EXP(a, b, c, d, e, f, g, h, j)                               \
+  {                                                                         \
+    t1 = wv[h] + SHA256_F2(wv[e]) + CH(wv[e], wv[f], wv[g]) + sha256_k[j] + \
+         w[j];                                                              \
+    t2 = SHA256_F1(wv[a]) + MAJ(wv[a], wv[b], wv[c]);                       \
+    wv[d] += t1;                                                            \
+    wv[h] = t1 + t2;                                                        \
+  }
+
+static const uint32_t sha256_h0[8] = {0x6a09e667,
+                                      0xbb67ae85,
+                                      0x3c6ef372,
+                                      0xa54ff53a,
+                                      0x510e527f,
+                                      0x9b05688c,
+                                      0x1f83d9ab,
+                                      0x5be0cd19};
+
+static const uint32_t sha256_k[64] = {
+    0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1,
+    0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
+    0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,
+    0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
+    0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147,
+    0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
+    0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
+    0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
+    0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a,
+    0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
+    0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2};
+
+/* SHA-256 implementation */
+void avb_sha256_init(AvbSHA256Ctx* ctx) {
+#ifndef UNROLL_LOOPS
+  int i;
+  for (i = 0; i < 8; i++) {
+    ctx->h[i] = sha256_h0[i];
+  }
+#else
+  ctx->h[0] = sha256_h0[0];
+  ctx->h[1] = sha256_h0[1];
+  ctx->h[2] = sha256_h0[2];
+  ctx->h[3] = sha256_h0[3];
+  ctx->h[4] = sha256_h0[4];
+  ctx->h[5] = sha256_h0[5];
+  ctx->h[6] = sha256_h0[6];
+  ctx->h[7] = sha256_h0[7];
+#endif /* !UNROLL_LOOPS */
+
+  ctx->len = 0;
+  ctx->tot_len = 0;
+}
+
+static void SHA256_transform(AvbSHA256Ctx* ctx,
+                             const uint8_t* message,
+                             unsigned int block_nb) {
+  uint32_t w[64];
+  uint32_t wv[8];
+  uint32_t t1, t2;
+  const unsigned char* sub_block;
+  int i;
+
+#ifndef UNROLL_LOOPS
+  int j;
+#endif
+
+  for (i = 0; i < (int)block_nb; i++) {
+    sub_block = message + (i << 6);
+
+#ifndef UNROLL_LOOPS
+    for (j = 0; j < 16; j++) {
+      PACK32(&sub_block[j << 2], &w[j]);
+    }
+
+    for (j = 16; j < 64; j++) {
+      SHA256_SCR(j);
+    }
+
+    for (j = 0; j < 8; j++) {
+      wv[j] = ctx->h[j];
+    }
+
+    for (j = 0; j < 64; j++) {
+      t1 = wv[7] + SHA256_F2(wv[4]) + CH(wv[4], wv[5], wv[6]) + sha256_k[j] +
+           w[j];
+      t2 = SHA256_F1(wv[0]) + MAJ(wv[0], wv[1], wv[2]);
+      wv[7] = wv[6];
+      wv[6] = wv[5];
+      wv[5] = wv[4];
+      wv[4] = wv[3] + t1;
+      wv[3] = wv[2];
+      wv[2] = wv[1];
+      wv[1] = wv[0];
+      wv[0] = t1 + t2;
+    }
+
+    for (j = 0; j < 8; j++) {
+      ctx->h[j] += wv[j];
+    }
+#else
+    PACK32(&sub_block[0], &w[0]);
+    PACK32(&sub_block[4], &w[1]);
+    PACK32(&sub_block[8], &w[2]);
+    PACK32(&sub_block[12], &w[3]);
+    PACK32(&sub_block[16], &w[4]);
+    PACK32(&sub_block[20], &w[5]);
+    PACK32(&sub_block[24], &w[6]);
+    PACK32(&sub_block[28], &w[7]);
+    PACK32(&sub_block[32], &w[8]);
+    PACK32(&sub_block[36], &w[9]);
+    PACK32(&sub_block[40], &w[10]);
+    PACK32(&sub_block[44], &w[11]);
+    PACK32(&sub_block[48], &w[12]);
+    PACK32(&sub_block[52], &w[13]);
+    PACK32(&sub_block[56], &w[14]);
+    PACK32(&sub_block[60], &w[15]);
+
+    SHA256_SCR(16);
+    SHA256_SCR(17);
+    SHA256_SCR(18);
+    SHA256_SCR(19);
+    SHA256_SCR(20);
+    SHA256_SCR(21);
+    SHA256_SCR(22);
+    SHA256_SCR(23);
+    SHA256_SCR(24);
+    SHA256_SCR(25);
+    SHA256_SCR(26);
+    SHA256_SCR(27);
+    SHA256_SCR(28);
+    SHA256_SCR(29);
+    SHA256_SCR(30);
+    SHA256_SCR(31);
+    SHA256_SCR(32);
+    SHA256_SCR(33);
+    SHA256_SCR(34);
+    SHA256_SCR(35);
+    SHA256_SCR(36);
+    SHA256_SCR(37);
+    SHA256_SCR(38);
+    SHA256_SCR(39);
+    SHA256_SCR(40);
+    SHA256_SCR(41);
+    SHA256_SCR(42);
+    SHA256_SCR(43);
+    SHA256_SCR(44);
+    SHA256_SCR(45);
+    SHA256_SCR(46);
+    SHA256_SCR(47);
+    SHA256_SCR(48);
+    SHA256_SCR(49);
+    SHA256_SCR(50);
+    SHA256_SCR(51);
+    SHA256_SCR(52);
+    SHA256_SCR(53);
+    SHA256_SCR(54);
+    SHA256_SCR(55);
+    SHA256_SCR(56);
+    SHA256_SCR(57);
+    SHA256_SCR(58);
+    SHA256_SCR(59);
+    SHA256_SCR(60);
+    SHA256_SCR(61);
+    SHA256_SCR(62);
+    SHA256_SCR(63);
+
+    wv[0] = ctx->h[0];
+    wv[1] = ctx->h[1];
+    wv[2] = ctx->h[2];
+    wv[3] = ctx->h[3];
+    wv[4] = ctx->h[4];
+    wv[5] = ctx->h[5];
+    wv[6] = ctx->h[6];
+    wv[7] = ctx->h[7];
+
+    SHA256_EXP(0, 1, 2, 3, 4, 5, 6, 7, 0);
+    SHA256_EXP(7, 0, 1, 2, 3, 4, 5, 6, 1);
+    SHA256_EXP(6, 7, 0, 1, 2, 3, 4, 5, 2);
+    SHA256_EXP(5, 6, 7, 0, 1, 2, 3, 4, 3);
+    SHA256_EXP(4, 5, 6, 7, 0, 1, 2, 3, 4);
+    SHA256_EXP(3, 4, 5, 6, 7, 0, 1, 2, 5);
+    SHA256_EXP(2, 3, 4, 5, 6, 7, 0, 1, 6);
+    SHA256_EXP(1, 2, 3, 4, 5, 6, 7, 0, 7);
+    SHA256_EXP(0, 1, 2, 3, 4, 5, 6, 7, 8);
+    SHA256_EXP(7, 0, 1, 2, 3, 4, 5, 6, 9);
+    SHA256_EXP(6, 7, 0, 1, 2, 3, 4, 5, 10);
+    SHA256_EXP(5, 6, 7, 0, 1, 2, 3, 4, 11);
+    SHA256_EXP(4, 5, 6, 7, 0, 1, 2, 3, 12);
+    SHA256_EXP(3, 4, 5, 6, 7, 0, 1, 2, 13);
+    SHA256_EXP(2, 3, 4, 5, 6, 7, 0, 1, 14);
+    SHA256_EXP(1, 2, 3, 4, 5, 6, 7, 0, 15);
+    SHA256_EXP(0, 1, 2, 3, 4, 5, 6, 7, 16);
+    SHA256_EXP(7, 0, 1, 2, 3, 4, 5, 6, 17);
+    SHA256_EXP(6, 7, 0, 1, 2, 3, 4, 5, 18);
+    SHA256_EXP(5, 6, 7, 0, 1, 2, 3, 4, 19);
+    SHA256_EXP(4, 5, 6, 7, 0, 1, 2, 3, 20);
+    SHA256_EXP(3, 4, 5, 6, 7, 0, 1, 2, 21);
+    SHA256_EXP(2, 3, 4, 5, 6, 7, 0, 1, 22);
+    SHA256_EXP(1, 2, 3, 4, 5, 6, 7, 0, 23);
+    SHA256_EXP(0, 1, 2, 3, 4, 5, 6, 7, 24);
+    SHA256_EXP(7, 0, 1, 2, 3, 4, 5, 6, 25);
+    SHA256_EXP(6, 7, 0, 1, 2, 3, 4, 5, 26);
+    SHA256_EXP(5, 6, 7, 0, 1, 2, 3, 4, 27);
+    SHA256_EXP(4, 5, 6, 7, 0, 1, 2, 3, 28);
+    SHA256_EXP(3, 4, 5, 6, 7, 0, 1, 2, 29);
+    SHA256_EXP(2, 3, 4, 5, 6, 7, 0, 1, 30);
+    SHA256_EXP(1, 2, 3, 4, 5, 6, 7, 0, 31);
+    SHA256_EXP(0, 1, 2, 3, 4, 5, 6, 7, 32);
+    SHA256_EXP(7, 0, 1, 2, 3, 4, 5, 6, 33);
+    SHA256_EXP(6, 7, 0, 1, 2, 3, 4, 5, 34);
+    SHA256_EXP(5, 6, 7, 0, 1, 2, 3, 4, 35);
+    SHA256_EXP(4, 5, 6, 7, 0, 1, 2, 3, 36);
+    SHA256_EXP(3, 4, 5, 6, 7, 0, 1, 2, 37);
+    SHA256_EXP(2, 3, 4, 5, 6, 7, 0, 1, 38);
+    SHA256_EXP(1, 2, 3, 4, 5, 6, 7, 0, 39);
+    SHA256_EXP(0, 1, 2, 3, 4, 5, 6, 7, 40);
+    SHA256_EXP(7, 0, 1, 2, 3, 4, 5, 6, 41);
+    SHA256_EXP(6, 7, 0, 1, 2, 3, 4, 5, 42);
+    SHA256_EXP(5, 6, 7, 0, 1, 2, 3, 4, 43);
+    SHA256_EXP(4, 5, 6, 7, 0, 1, 2, 3, 44);
+    SHA256_EXP(3, 4, 5, 6, 7, 0, 1, 2, 45);
+    SHA256_EXP(2, 3, 4, 5, 6, 7, 0, 1, 46);
+    SHA256_EXP(1, 2, 3, 4, 5, 6, 7, 0, 47);
+    SHA256_EXP(0, 1, 2, 3, 4, 5, 6, 7, 48);
+    SHA256_EXP(7, 0, 1, 2, 3, 4, 5, 6, 49);
+    SHA256_EXP(6, 7, 0, 1, 2, 3, 4, 5, 50);
+    SHA256_EXP(5, 6, 7, 0, 1, 2, 3, 4, 51);
+    SHA256_EXP(4, 5, 6, 7, 0, 1, 2, 3, 52);
+    SHA256_EXP(3, 4, 5, 6, 7, 0, 1, 2, 53);
+    SHA256_EXP(2, 3, 4, 5, 6, 7, 0, 1, 54);
+    SHA256_EXP(1, 2, 3, 4, 5, 6, 7, 0, 55);
+    SHA256_EXP(0, 1, 2, 3, 4, 5, 6, 7, 56);
+    SHA256_EXP(7, 0, 1, 2, 3, 4, 5, 6, 57);
+    SHA256_EXP(6, 7, 0, 1, 2, 3, 4, 5, 58);
+    SHA256_EXP(5, 6, 7, 0, 1, 2, 3, 4, 59);
+    SHA256_EXP(4, 5, 6, 7, 0, 1, 2, 3, 60);
+    SHA256_EXP(3, 4, 5, 6, 7, 0, 1, 2, 61);
+    SHA256_EXP(2, 3, 4, 5, 6, 7, 0, 1, 62);
+    SHA256_EXP(1, 2, 3, 4, 5, 6, 7, 0, 63);
+
+    ctx->h[0] += wv[0];
+    ctx->h[1] += wv[1];
+    ctx->h[2] += wv[2];
+    ctx->h[3] += wv[3];
+    ctx->h[4] += wv[4];
+    ctx->h[5] += wv[5];
+    ctx->h[6] += wv[6];
+    ctx->h[7] += wv[7];
+#endif /* !UNROLL_LOOPS */
+  }
+}
+
+void avb_sha256_update(AvbSHA256Ctx* ctx, const uint8_t* data, uint32_t len) {
+  unsigned int block_nb;
+  unsigned int new_len, rem_len, tmp_len;
+  const uint8_t* shifted_data;
+
+  tmp_len = AVB_SHA256_BLOCK_SIZE - ctx->len;
+  rem_len = len < tmp_len ? len : tmp_len;
+
+  avb_memcpy(&ctx->block[ctx->len], data, rem_len);
+
+  if (ctx->len + len < AVB_SHA256_BLOCK_SIZE) {
+    ctx->len += len;
+    return;
+  }
+
+  new_len = len - rem_len;
+  block_nb = new_len / AVB_SHA256_BLOCK_SIZE;
+
+  shifted_data = data + rem_len;
+
+  SHA256_transform(ctx, ctx->block, 1);
+  SHA256_transform(ctx, shifted_data, block_nb);
+
+  rem_len = new_len % AVB_SHA256_BLOCK_SIZE;
+
+  avb_memcpy(ctx->block, &shifted_data[block_nb << 6], rem_len);
+
+  ctx->len = rem_len;
+  ctx->tot_len += (block_nb + 1) << 6;
+}
+
+uint8_t* avb_sha256_final(AvbSHA256Ctx* ctx) {
+  unsigned int block_nb;
+  unsigned int pm_len;
+  unsigned int len_b;
+#ifndef UNROLL_LOOPS
+  int i;
+#endif
+
+  block_nb =
+      (1 + ((AVB_SHA256_BLOCK_SIZE - 9) < (ctx->len % AVB_SHA256_BLOCK_SIZE)));
+
+  len_b = (ctx->tot_len + ctx->len) << 3;
+  pm_len = block_nb << 6;
+
+  avb_memset(ctx->block + ctx->len, 0, pm_len - ctx->len);
+  ctx->block[ctx->len] = 0x80;
+  UNPACK32(len_b, ctx->block + pm_len - 4);
+
+  SHA256_transform(ctx, ctx->block, block_nb);
+
+#ifndef UNROLL_LOOPS
+  for (i = 0; i < 8; i++) {
+    UNPACK32(ctx->h[i], &ctx->buf[i << 2]);
+  }
+#else
+  UNPACK32(ctx->h[0], &ctx->buf[0]);
+  UNPACK32(ctx->h[1], &ctx->buf[4]);
+  UNPACK32(ctx->h[2], &ctx->buf[8]);
+  UNPACK32(ctx->h[3], &ctx->buf[12]);
+  UNPACK32(ctx->h[4], &ctx->buf[16]);
+  UNPACK32(ctx->h[5], &ctx->buf[20]);
+  UNPACK32(ctx->h[6], &ctx->buf[24]);
+  UNPACK32(ctx->h[7], &ctx->buf[28]);
+#endif /* !UNROLL_LOOPS */
+
+  return ctx->buf;
+}
diff --git a/avb/libavb/avb_sha512.c b/avb/libavb/avb_sha512.c
new file mode 100644
index 0000000..8df6319
--- /dev/null
+++ b/avb/libavb/avb_sha512.c
@@ -0,0 +1,388 @@
+/* SHA-256 and SHA-512 implementation based on code by Oliver Gay
+ * <olivier.gay@a3.epfl.ch> under a BSD-style license. See below.
+ */
+
+/*
+ * FIPS 180-2 SHA-224/256/384/512 implementation
+ * Last update: 02/02/2007
+ * Issue date:  04/30/2005
+ *
+ * Copyright (C) 2005, 2007 Olivier Gay <olivier.gay@a3.epfl.ch>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the project nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "avb_sha.h"
+
+#define SHFR(x, n) (x >> n)
+#define ROTR(x, n) ((x >> n) | (x << ((sizeof(x) << 3) - n)))
+#define ROTL(x, n) ((x << n) | (x >> ((sizeof(x) << 3) - n)))
+#define CH(x, y, z) ((x & y) ^ (~x & z))
+#define MAJ(x, y, z) ((x & y) ^ (x & z) ^ (y & z))
+
+#define SHA512_F1(x) (ROTR(x, 28) ^ ROTR(x, 34) ^ ROTR(x, 39))
+#define SHA512_F2(x) (ROTR(x, 14) ^ ROTR(x, 18) ^ ROTR(x, 41))
+#define SHA512_F3(x) (ROTR(x, 1) ^ ROTR(x, 8) ^ SHFR(x, 7))
+#define SHA512_F4(x) (ROTR(x, 19) ^ ROTR(x, 61) ^ SHFR(x, 6))
+
+#define UNPACK32(x, str)                 \
+  {                                      \
+    *((str) + 3) = (uint8_t)((x));       \
+    *((str) + 2) = (uint8_t)((x) >> 8);  \
+    *((str) + 1) = (uint8_t)((x) >> 16); \
+    *((str) + 0) = (uint8_t)((x) >> 24); \
+  }
+
+#define UNPACK64(x, str)                         \
+  {                                              \
+    *((str) + 7) = (uint8_t)x;                   \
+    *((str) + 6) = (uint8_t)((uint64_t)x >> 8);  \
+    *((str) + 5) = (uint8_t)((uint64_t)x >> 16); \
+    *((str) + 4) = (uint8_t)((uint64_t)x >> 24); \
+    *((str) + 3) = (uint8_t)((uint64_t)x >> 32); \
+    *((str) + 2) = (uint8_t)((uint64_t)x >> 40); \
+    *((str) + 1) = (uint8_t)((uint64_t)x >> 48); \
+    *((str) + 0) = (uint8_t)((uint64_t)x >> 56); \
+  }
+
+#define PACK64(str, x)                                                        \
+  {                                                                           \
+    *(x) =                                                                    \
+        ((uint64_t) * ((str) + 7)) | ((uint64_t) * ((str) + 6) << 8) |        \
+        ((uint64_t) * ((str) + 5) << 16) | ((uint64_t) * ((str) + 4) << 24) | \
+        ((uint64_t) * ((str) + 3) << 32) | ((uint64_t) * ((str) + 2) << 40) | \
+        ((uint64_t) * ((str) + 1) << 48) | ((uint64_t) * ((str) + 0) << 56);  \
+  }
+
+/* Macros used for loops unrolling */
+
+#define SHA512_SCR(i) \
+  { w[i] = SHA512_F4(w[i - 2]) + w[i - 7] + SHA512_F3(w[i - 15]) + w[i - 16]; }
+
+#define SHA512_EXP(a, b, c, d, e, f, g, h, j)                               \
+  {                                                                         \
+    t1 = wv[h] + SHA512_F2(wv[e]) + CH(wv[e], wv[f], wv[g]) + sha512_k[j] + \
+         w[j];                                                              \
+    t2 = SHA512_F1(wv[a]) + MAJ(wv[a], wv[b], wv[c]);                       \
+    wv[d] += t1;                                                            \
+    wv[h] = t1 + t2;                                                        \
+  }
+
+static const uint64_t sha512_h0[8] = {0x6a09e667f3bcc908ULL,
+                                      0xbb67ae8584caa73bULL,
+                                      0x3c6ef372fe94f82bULL,
+                                      0xa54ff53a5f1d36f1ULL,
+                                      0x510e527fade682d1ULL,
+                                      0x9b05688c2b3e6c1fULL,
+                                      0x1f83d9abfb41bd6bULL,
+                                      0x5be0cd19137e2179ULL};
+
+static const uint64_t sha512_k[80] = {
+    0x428a2f98d728ae22ULL, 0x7137449123ef65cdULL, 0xb5c0fbcfec4d3b2fULL,
+    0xe9b5dba58189dbbcULL, 0x3956c25bf348b538ULL, 0x59f111f1b605d019ULL,
+    0x923f82a4af194f9bULL, 0xab1c5ed5da6d8118ULL, 0xd807aa98a3030242ULL,
+    0x12835b0145706fbeULL, 0x243185be4ee4b28cULL, 0x550c7dc3d5ffb4e2ULL,
+    0x72be5d74f27b896fULL, 0x80deb1fe3b1696b1ULL, 0x9bdc06a725c71235ULL,
+    0xc19bf174cf692694ULL, 0xe49b69c19ef14ad2ULL, 0xefbe4786384f25e3ULL,
+    0x0fc19dc68b8cd5b5ULL, 0x240ca1cc77ac9c65ULL, 0x2de92c6f592b0275ULL,
+    0x4a7484aa6ea6e483ULL, 0x5cb0a9dcbd41fbd4ULL, 0x76f988da831153b5ULL,
+    0x983e5152ee66dfabULL, 0xa831c66d2db43210ULL, 0xb00327c898fb213fULL,
+    0xbf597fc7beef0ee4ULL, 0xc6e00bf33da88fc2ULL, 0xd5a79147930aa725ULL,
+    0x06ca6351e003826fULL, 0x142929670a0e6e70ULL, 0x27b70a8546d22ffcULL,
+    0x2e1b21385c26c926ULL, 0x4d2c6dfc5ac42aedULL, 0x53380d139d95b3dfULL,
+    0x650a73548baf63deULL, 0x766a0abb3c77b2a8ULL, 0x81c2c92e47edaee6ULL,
+    0x92722c851482353bULL, 0xa2bfe8a14cf10364ULL, 0xa81a664bbc423001ULL,
+    0xc24b8b70d0f89791ULL, 0xc76c51a30654be30ULL, 0xd192e819d6ef5218ULL,
+    0xd69906245565a910ULL, 0xf40e35855771202aULL, 0x106aa07032bbd1b8ULL,
+    0x19a4c116b8d2d0c8ULL, 0x1e376c085141ab53ULL, 0x2748774cdf8eeb99ULL,
+    0x34b0bcb5e19b48a8ULL, 0x391c0cb3c5c95a63ULL, 0x4ed8aa4ae3418acbULL,
+    0x5b9cca4f7763e373ULL, 0x682e6ff3d6b2b8a3ULL, 0x748f82ee5defb2fcULL,
+    0x78a5636f43172f60ULL, 0x84c87814a1f0ab72ULL, 0x8cc702081a6439ecULL,
+    0x90befffa23631e28ULL, 0xa4506cebde82bde9ULL, 0xbef9a3f7b2c67915ULL,
+    0xc67178f2e372532bULL, 0xca273eceea26619cULL, 0xd186b8c721c0c207ULL,
+    0xeada7dd6cde0eb1eULL, 0xf57d4f7fee6ed178ULL, 0x06f067aa72176fbaULL,
+    0x0a637dc5a2c898a6ULL, 0x113f9804bef90daeULL, 0x1b710b35131c471bULL,
+    0x28db77f523047d84ULL, 0x32caab7b40c72493ULL, 0x3c9ebe0a15c9bebcULL,
+    0x431d67c49c100d4cULL, 0x4cc5d4becb3e42b6ULL, 0x597f299cfc657e2aULL,
+    0x5fcb6fab3ad6faecULL, 0x6c44198c4a475817ULL};
+
+/* SHA-512 implementation */
+
+void avb_sha512_init(AvbSHA512Ctx* ctx) {
+#ifdef UNROLL_LOOPS_SHA512
+  ctx->h[0] = sha512_h0[0];
+  ctx->h[1] = sha512_h0[1];
+  ctx->h[2] = sha512_h0[2];
+  ctx->h[3] = sha512_h0[3];
+  ctx->h[4] = sha512_h0[4];
+  ctx->h[5] = sha512_h0[5];
+  ctx->h[6] = sha512_h0[6];
+  ctx->h[7] = sha512_h0[7];
+#else
+  int i;
+
+  for (i = 0; i < 8; i++)
+    ctx->h[i] = sha512_h0[i];
+#endif /* UNROLL_LOOPS_SHA512 */
+
+  ctx->len = 0;
+  ctx->tot_len = 0;
+}
+
+static void SHA512_transform(AvbSHA512Ctx* ctx,
+                             const uint8_t* message,
+                             unsigned int block_nb) {
+  uint64_t w[80];
+  uint64_t wv[8];
+  uint64_t t1, t2;
+  const uint8_t* sub_block;
+  int i, j;
+
+  for (i = 0; i < (int)block_nb; i++) {
+    sub_block = message + (i << 7);
+
+#ifdef UNROLL_LOOPS_SHA512
+    PACK64(&sub_block[0], &w[0]);
+    PACK64(&sub_block[8], &w[1]);
+    PACK64(&sub_block[16], &w[2]);
+    PACK64(&sub_block[24], &w[3]);
+    PACK64(&sub_block[32], &w[4]);
+    PACK64(&sub_block[40], &w[5]);
+    PACK64(&sub_block[48], &w[6]);
+    PACK64(&sub_block[56], &w[7]);
+    PACK64(&sub_block[64], &w[8]);
+    PACK64(&sub_block[72], &w[9]);
+    PACK64(&sub_block[80], &w[10]);
+    PACK64(&sub_block[88], &w[11]);
+    PACK64(&sub_block[96], &w[12]);
+    PACK64(&sub_block[104], &w[13]);
+    PACK64(&sub_block[112], &w[14]);
+    PACK64(&sub_block[120], &w[15]);
+
+    SHA512_SCR(16);
+    SHA512_SCR(17);
+    SHA512_SCR(18);
+    SHA512_SCR(19);
+    SHA512_SCR(20);
+    SHA512_SCR(21);
+    SHA512_SCR(22);
+    SHA512_SCR(23);
+    SHA512_SCR(24);
+    SHA512_SCR(25);
+    SHA512_SCR(26);
+    SHA512_SCR(27);
+    SHA512_SCR(28);
+    SHA512_SCR(29);
+    SHA512_SCR(30);
+    SHA512_SCR(31);
+    SHA512_SCR(32);
+    SHA512_SCR(33);
+    SHA512_SCR(34);
+    SHA512_SCR(35);
+    SHA512_SCR(36);
+    SHA512_SCR(37);
+    SHA512_SCR(38);
+    SHA512_SCR(39);
+    SHA512_SCR(40);
+    SHA512_SCR(41);
+    SHA512_SCR(42);
+    SHA512_SCR(43);
+    SHA512_SCR(44);
+    SHA512_SCR(45);
+    SHA512_SCR(46);
+    SHA512_SCR(47);
+    SHA512_SCR(48);
+    SHA512_SCR(49);
+    SHA512_SCR(50);
+    SHA512_SCR(51);
+    SHA512_SCR(52);
+    SHA512_SCR(53);
+    SHA512_SCR(54);
+    SHA512_SCR(55);
+    SHA512_SCR(56);
+    SHA512_SCR(57);
+    SHA512_SCR(58);
+    SHA512_SCR(59);
+    SHA512_SCR(60);
+    SHA512_SCR(61);
+    SHA512_SCR(62);
+    SHA512_SCR(63);
+    SHA512_SCR(64);
+    SHA512_SCR(65);
+    SHA512_SCR(66);
+    SHA512_SCR(67);
+    SHA512_SCR(68);
+    SHA512_SCR(69);
+    SHA512_SCR(70);
+    SHA512_SCR(71);
+    SHA512_SCR(72);
+    SHA512_SCR(73);
+    SHA512_SCR(74);
+    SHA512_SCR(75);
+    SHA512_SCR(76);
+    SHA512_SCR(77);
+    SHA512_SCR(78);
+    SHA512_SCR(79);
+
+    wv[0] = ctx->h[0];
+    wv[1] = ctx->h[1];
+    wv[2] = ctx->h[2];
+    wv[3] = ctx->h[3];
+    wv[4] = ctx->h[4];
+    wv[5] = ctx->h[5];
+    wv[6] = ctx->h[6];
+    wv[7] = ctx->h[7];
+
+    j = 0;
+
+    do {
+      SHA512_EXP(0, 1, 2, 3, 4, 5, 6, 7, j);
+      j++;
+      SHA512_EXP(7, 0, 1, 2, 3, 4, 5, 6, j);
+      j++;
+      SHA512_EXP(6, 7, 0, 1, 2, 3, 4, 5, j);
+      j++;
+      SHA512_EXP(5, 6, 7, 0, 1, 2, 3, 4, j);
+      j++;
+      SHA512_EXP(4, 5, 6, 7, 0, 1, 2, 3, j);
+      j++;
+      SHA512_EXP(3, 4, 5, 6, 7, 0, 1, 2, j);
+      j++;
+      SHA512_EXP(2, 3, 4, 5, 6, 7, 0, 1, j);
+      j++;
+      SHA512_EXP(1, 2, 3, 4, 5, 6, 7, 0, j);
+      j++;
+    } while (j < 80);
+
+    ctx->h[0] += wv[0];
+    ctx->h[1] += wv[1];
+    ctx->h[2] += wv[2];
+    ctx->h[3] += wv[3];
+    ctx->h[4] += wv[4];
+    ctx->h[5] += wv[5];
+    ctx->h[6] += wv[6];
+    ctx->h[7] += wv[7];
+#else
+    for (j = 0; j < 16; j++) {
+      PACK64(&sub_block[j << 3], &w[j]);
+    }
+
+    for (j = 16; j < 80; j++) {
+      SHA512_SCR(j);
+    }
+
+    for (j = 0; j < 8; j++) {
+      wv[j] = ctx->h[j];
+    }
+
+    for (j = 0; j < 80; j++) {
+      t1 = wv[7] + SHA512_F2(wv[4]) + CH(wv[4], wv[5], wv[6]) + sha512_k[j] +
+           w[j];
+      t2 = SHA512_F1(wv[0]) + MAJ(wv[0], wv[1], wv[2]);
+      wv[7] = wv[6];
+      wv[6] = wv[5];
+      wv[5] = wv[4];
+      wv[4] = wv[3] + t1;
+      wv[3] = wv[2];
+      wv[2] = wv[1];
+      wv[1] = wv[0];
+      wv[0] = t1 + t2;
+    }
+
+    for (j = 0; j < 8; j++)
+      ctx->h[j] += wv[j];
+#endif /* UNROLL_LOOPS_SHA512 */
+  }
+}
+
+void avb_sha512_update(AvbSHA512Ctx* ctx, const uint8_t* data, uint32_t len) {
+  unsigned int block_nb;
+  unsigned int new_len, rem_len, tmp_len;
+  const uint8_t* shifted_data;
+
+  tmp_len = AVB_SHA512_BLOCK_SIZE - ctx->len;
+  rem_len = len < tmp_len ? len : tmp_len;
+
+  avb_memcpy(&ctx->block[ctx->len], data, rem_len);
+
+  if (ctx->len + len < AVB_SHA512_BLOCK_SIZE) {
+    ctx->len += len;
+    return;
+  }
+
+  new_len = len - rem_len;
+  block_nb = new_len / AVB_SHA512_BLOCK_SIZE;
+
+  shifted_data = data + rem_len;
+
+  SHA512_transform(ctx, ctx->block, 1);
+  SHA512_transform(ctx, shifted_data, block_nb);
+
+  rem_len = new_len % AVB_SHA512_BLOCK_SIZE;
+
+  avb_memcpy(ctx->block, &shifted_data[block_nb << 7], rem_len);
+
+  ctx->len = rem_len;
+  ctx->tot_len += (block_nb + 1) << 7;
+}
+
+uint8_t* avb_sha512_final(AvbSHA512Ctx* ctx) {
+  unsigned int block_nb;
+  unsigned int pm_len;
+  unsigned int len_b;
+
+#ifndef UNROLL_LOOPS_SHA512
+  int i;
+#endif
+
+  block_nb =
+      1 + ((AVB_SHA512_BLOCK_SIZE - 17) < (ctx->len % AVB_SHA512_BLOCK_SIZE));
+
+  len_b = (ctx->tot_len + ctx->len) << 3;
+  pm_len = block_nb << 7;
+
+  avb_memset(ctx->block + ctx->len, 0, pm_len - ctx->len);
+  ctx->block[ctx->len] = 0x80;
+  UNPACK32(len_b, ctx->block + pm_len - 4);
+
+  SHA512_transform(ctx, ctx->block, block_nb);
+
+#ifdef UNROLL_LOOPS_SHA512
+  UNPACK64(ctx->h[0], &ctx->buf[0]);
+  UNPACK64(ctx->h[1], &ctx->buf[8]);
+  UNPACK64(ctx->h[2], &ctx->buf[16]);
+  UNPACK64(ctx->h[3], &ctx->buf[24]);
+  UNPACK64(ctx->h[4], &ctx->buf[32]);
+  UNPACK64(ctx->h[5], &ctx->buf[40]);
+  UNPACK64(ctx->h[6], &ctx->buf[48]);
+  UNPACK64(ctx->h[7], &ctx->buf[56]);
+#else
+  for (i = 0; i < 8; i++)
+    UNPACK64(ctx->h[i], &ctx->buf[i << 3]);
+#endif /* UNROLL_LOOPS_SHA512 */
+
+  return ctx->buf;
+}
diff --git a/avb/libavb/avb_slot_verify.c b/avb/libavb/avb_slot_verify.c
new file mode 100644
index 0000000..296d831
--- /dev/null
+++ b/avb/libavb/avb_slot_verify.c
@@ -0,0 +1,1091 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "avb_slot_verify.h"
+#include "avb_chain_partition_descriptor.h"
+#include "avb_footer.h"
+#include "avb_hash_descriptor.h"
+#include "avb_kernel_cmdline_descriptor.h"
+#include "avb_sha.h"
+#include "avb_util.h"
+#include "avb_vbmeta_image.h"
+
+/* Maximum allow length (in bytes) of a partition name, including
+ * ab_suffix.
+ */
+#define PART_NAME_MAX_SIZE 32
+
+/* Maximum number of partitions that can be loaded with avb_slot_verify(). */
+#define MAX_NUMBER_OF_LOADED_PARTITIONS 32
+
+/* Maximum number of vbmeta images that can be loaded with avb_slot_verify(). */
+#define MAX_NUMBER_OF_VBMETA_IMAGES 32
+
+/* Maximum size of a vbmeta image - 64 KiB. */
+#define VBMETA_MAX_SIZE (64 * 1024)
+
+/* Helper function to see if we should continue with verification in
+ * allow_verification_error=true mode if something goes wrong. See the
+ * comments for the avb_slot_verify() function for more information.
+ */
+static inline bool result_should_continue(AvbSlotVerifyResult result) {
+  switch (result) {
+    case AVB_SLOT_VERIFY_RESULT_ERROR_OOM:
+    case AVB_SLOT_VERIFY_RESULT_ERROR_IO:
+    case AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA:
+      return false;
+
+    case AVB_SLOT_VERIFY_RESULT_OK:
+    case AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION:
+    case AVB_SLOT_VERIFY_RESULT_ERROR_ROLLBACK_INDEX:
+    case AVB_SLOT_VERIFY_RESULT_ERROR_PUBLIC_KEY_REJECTED:
+      return true;
+  }
+
+  return false;
+}
+
+static AvbSlotVerifyResult load_and_verify_hash_partition(
+    AvbOps* ops,
+    const char* const* requested_partitions,
+    const char* ab_suffix,
+    bool allow_verification_error,
+    const AvbDescriptor* descriptor,
+    AvbSlotVerifyData* slot_data) {
+  AvbHashDescriptor hash_desc;
+  const uint8_t* desc_partition_name = NULL;
+  const uint8_t* desc_salt;
+  const uint8_t* desc_digest;
+  char part_name[PART_NAME_MAX_SIZE];
+  AvbSlotVerifyResult ret;
+  AvbIOResult io_ret;
+  uint8_t* image_buf = NULL;
+  size_t part_num_read;
+  uint8_t* digest;
+  size_t digest_len;
+  const char* found;
+
+  if (!avb_hash_descriptor_validate_and_byteswap(
+          (const AvbHashDescriptor*)descriptor, &hash_desc)) {
+    ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA;
+    goto out;
+  }
+
+  desc_partition_name =
+      ((const uint8_t*)descriptor) + sizeof(AvbHashDescriptor);
+  desc_salt = desc_partition_name + hash_desc.partition_name_len;
+  desc_digest = desc_salt + hash_desc.salt_len;
+
+  if (!avb_validate_utf8(desc_partition_name, hash_desc.partition_name_len)) {
+    avb_error("Partition name is not valid UTF-8.\n");
+    ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA;
+    goto out;
+  }
+
+  if (!avb_str_concat(part_name,
+                      sizeof part_name,
+                      (const char*)desc_partition_name,
+                      hash_desc.partition_name_len,
+                      ab_suffix,
+                      avb_strlen(ab_suffix))) {
+    avb_error("Partition name and suffix does not fit.\n");
+    ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA;
+    goto out;
+  }
+
+  image_buf = avb_malloc(hash_desc.image_size);
+  if (image_buf == NULL) {
+    ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
+    goto out;
+  }
+
+  io_ret = ops->read_from_partition(ops,
+                                    part_name,
+                                    0 /* offset */,
+                                    hash_desc.image_size,
+                                    image_buf,
+                                    &part_num_read);
+  if (io_ret == AVB_IO_RESULT_ERROR_OOM) {
+    ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
+    goto out;
+  } else if (io_ret != AVB_IO_RESULT_OK) {
+    avb_errorv(part_name, ": Error loading data from partition.\n", NULL);
+    ret = AVB_SLOT_VERIFY_RESULT_ERROR_IO;
+    goto out;
+  }
+  if (part_num_read != hash_desc.image_size) {
+    avb_errorv(part_name, ": Read fewer than requested bytes.\n", NULL);
+    ret = AVB_SLOT_VERIFY_RESULT_ERROR_IO;
+    goto out;
+  }
+
+  if (avb_strcmp((const char*)hash_desc.hash_algorithm, "sha256") == 0) {
+    AvbSHA256Ctx sha256_ctx;
+    avb_sha256_init(&sha256_ctx);
+    avb_sha256_update(&sha256_ctx, desc_salt, hash_desc.salt_len);
+    avb_sha256_update(&sha256_ctx, image_buf, hash_desc.image_size);
+    digest = avb_sha256_final(&sha256_ctx);
+    digest_len = AVB_SHA256_DIGEST_SIZE;
+  } else if (avb_strcmp((const char*)hash_desc.hash_algorithm, "sha512") == 0) {
+    AvbSHA512Ctx sha512_ctx;
+    avb_sha512_init(&sha512_ctx);
+    avb_sha512_update(&sha512_ctx, desc_salt, hash_desc.salt_len);
+    avb_sha512_update(&sha512_ctx, image_buf, hash_desc.image_size);
+    digest = avb_sha512_final(&sha512_ctx);
+    digest_len = AVB_SHA512_DIGEST_SIZE;
+  } else {
+    avb_errorv(part_name, ": Unsupported hash algorithm.\n", NULL);
+    ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA;
+    goto out;
+  }
+
+  if (digest_len != hash_desc.digest_len) {
+    avb_errorv(
+        part_name, ": Digest in descriptor not of expected size.\n", NULL);
+    ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA;
+    goto out;
+  }
+
+  if (avb_safe_memcmp(digest, desc_digest, digest_len) != 0) {
+    avb_errorv(part_name,
+               ": Hash of data does not match digest in descriptor.\n",
+               NULL);
+    ret = AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION;
+    goto out;
+  }
+
+  ret = AVB_SLOT_VERIFY_RESULT_OK;
+
+out:
+
+  if (ret == AVB_SLOT_VERIFY_RESULT_OK || result_should_continue(ret)) {
+    /* If this is the requested partition, copy to slot_data. */
+    found = avb_strv_find_str(requested_partitions,
+                              (const char*)desc_partition_name,
+                              hash_desc.partition_name_len);
+    if (found != NULL) {
+      AvbPartitionData* loaded_partition;
+      if (slot_data->num_loaded_partitions == MAX_NUMBER_OF_LOADED_PARTITIONS) {
+        avb_errorv(part_name, ": Too many loaded partitions.\n", NULL);
+        ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
+        goto fail;
+      }
+      loaded_partition =
+          &slot_data->loaded_partitions[slot_data->num_loaded_partitions++];
+      loaded_partition->partition_name = avb_strdup(found);
+      loaded_partition->data_size = hash_desc.image_size;
+      loaded_partition->data = image_buf;
+      image_buf = NULL;
+    }
+  }
+
+fail:
+  if (image_buf != NULL) {
+    avb_free(image_buf);
+  }
+  return ret;
+}
+
+static AvbSlotVerifyResult load_and_verify_vbmeta(
+    AvbOps* ops,
+    const char* const* requested_partitions,
+    const char* ab_suffix,
+    bool allow_verification_error,
+    AvbVBMetaImageFlags toplevel_vbmeta_flags,
+    int rollback_index_location,
+    const char* partition_name,
+    size_t partition_name_len,
+    const uint8_t* expected_public_key,
+    size_t expected_public_key_length,
+    AvbSlotVerifyData* slot_data,
+    AvbAlgorithmType* out_algorithm_type) {
+  char full_partition_name[PART_NAME_MAX_SIZE];
+  AvbSlotVerifyResult ret;
+  AvbIOResult io_ret;
+  size_t vbmeta_offset;
+  size_t vbmeta_size;
+  uint8_t* vbmeta_buf = NULL;
+  size_t vbmeta_num_read;
+  AvbVBMetaVerifyResult vbmeta_ret;
+  const uint8_t* pk_data;
+  size_t pk_len;
+  AvbVBMetaImageHeader vbmeta_header;
+  uint64_t stored_rollback_index;
+  const AvbDescriptor** descriptors = NULL;
+  size_t num_descriptors;
+  size_t n;
+  int is_main_vbmeta;
+  AvbVBMetaData* vbmeta_image_data = NULL;
+
+  ret = AVB_SLOT_VERIFY_RESULT_OK;
+
+  avb_assert(slot_data != NULL);
+
+  is_main_vbmeta = (avb_strcmp(partition_name, "vbmeta") == 0);
+
+  if (!avb_validate_utf8((const uint8_t*)partition_name, partition_name_len)) {
+    avb_error("Partition name is not valid UTF-8.\n");
+    ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA;
+    goto out;
+  }
+
+  /* Construct full partition name. */
+  if (!avb_str_concat(full_partition_name,
+                      sizeof full_partition_name,
+                      partition_name,
+                      partition_name_len,
+                      ab_suffix,
+                      avb_strlen(ab_suffix))) {
+    avb_error("Partition name and suffix does not fit.\n");
+    ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA;
+    goto out;
+  }
+
+  avb_debugv("Loading vbmeta struct from partition '",
+             full_partition_name,
+             "'.\n",
+             NULL);
+
+  /* If we're loading from the main vbmeta partition, the vbmeta
+   * struct is in the beginning. Otherwise we have to locate it via a
+   * footer.
+   */
+  if (is_main_vbmeta) {
+    vbmeta_offset = 0;
+    vbmeta_size = VBMETA_MAX_SIZE;
+  } else {
+    uint8_t footer_buf[AVB_FOOTER_SIZE];
+    size_t footer_num_read;
+    AvbFooter footer;
+
+    io_ret = ops->read_from_partition(ops,
+                                      full_partition_name,
+                                      -AVB_FOOTER_SIZE,
+                                      AVB_FOOTER_SIZE,
+                                      footer_buf,
+                                      &footer_num_read);
+    if (io_ret == AVB_IO_RESULT_ERROR_OOM) {
+      ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
+      goto out;
+    } else if (io_ret != AVB_IO_RESULT_OK) {
+      avb_errorv(full_partition_name, ": Error loading footer.\n", NULL);
+      ret = AVB_SLOT_VERIFY_RESULT_ERROR_IO;
+      goto out;
+    }
+    avb_assert(footer_num_read == AVB_FOOTER_SIZE);
+
+    if (!avb_footer_validate_and_byteswap((const AvbFooter*)footer_buf,
+                                          &footer)) {
+      avb_errorv(full_partition_name, ": Error validating footer.\n", NULL);
+      ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA;
+      goto out;
+    }
+
+    /* Basic footer sanity check since the data is untrusted. */
+    if (footer.vbmeta_size > VBMETA_MAX_SIZE) {
+      avb_errorv(
+          full_partition_name, ": Invalid vbmeta size in footer.\n", NULL);
+      ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA;
+      goto out;
+    }
+
+    vbmeta_offset = footer.vbmeta_offset;
+    vbmeta_size = footer.vbmeta_size;
+  }
+
+  vbmeta_buf = avb_malloc(vbmeta_size);
+  if (vbmeta_buf == NULL) {
+    ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
+    goto out;
+  }
+
+  io_ret = ops->read_from_partition(ops,
+                                    full_partition_name,
+                                    vbmeta_offset,
+                                    vbmeta_size,
+                                    vbmeta_buf,
+                                    &vbmeta_num_read);
+  if (io_ret == AVB_IO_RESULT_ERROR_OOM) {
+    ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
+    goto out;
+  } else if (io_ret != AVB_IO_RESULT_OK) {
+    avb_errorv(full_partition_name, ": Error loading vbmeta data.\n", NULL);
+    ret = AVB_SLOT_VERIFY_RESULT_ERROR_IO;
+    goto out;
+  }
+  avb_assert(vbmeta_num_read <= vbmeta_size);
+
+  /* Check if the image is properly signed and get the public key used
+   * to sign the image.
+   */
+  vbmeta_ret =
+      avb_vbmeta_image_verify(vbmeta_buf, vbmeta_num_read, &pk_data, &pk_len);
+  switch (vbmeta_ret) {
+    case AVB_VBMETA_VERIFY_RESULT_OK:
+      avb_assert(pk_data != NULL && pk_len > 0);
+      break;
+
+    case AVB_VBMETA_VERIFY_RESULT_OK_NOT_SIGNED:
+    case AVB_VBMETA_VERIFY_RESULT_HASH_MISMATCH:
+    case AVB_VBMETA_VERIFY_RESULT_SIGNATURE_MISMATCH:
+      ret = AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION;
+      avb_errorv(full_partition_name,
+                 ": Error verifying vbmeta image: ",
+                 avb_vbmeta_verify_result_to_string(vbmeta_ret),
+                 "\n",
+                 NULL);
+      if (!allow_verification_error) {
+        goto out;
+      }
+      break;
+
+    case AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER:
+      /* No way to continue this case. */
+      ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA;
+      avb_errorv(full_partition_name,
+                 ": Error verifying vbmeta image: invalid vbmeta header\n",
+                 NULL);
+      goto out;
+  }
+
+  /* Byteswap the header. */
+  avb_vbmeta_image_header_to_host_byte_order((AvbVBMetaImageHeader*)vbmeta_buf,
+                                             &vbmeta_header);
+
+  /* If we're the toplevel, assign flags so they'll be passed down. */
+  if (is_main_vbmeta) {
+    toplevel_vbmeta_flags = (AvbVBMetaImageFlags)vbmeta_header.flags;
+  } else {
+    if (vbmeta_header.flags != 0) {
+      ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA;
+      avb_errorv(full_partition_name,
+                 ": chained vbmeta image has non-zero flags\n",
+                 NULL);
+      goto out;
+    }
+  }
+
+  /* Check if key used to make signature matches what is expected. */
+  if (pk_data != NULL) {
+    if (expected_public_key != NULL) {
+      avb_assert(!is_main_vbmeta);
+      if (expected_public_key_length != pk_len ||
+          avb_safe_memcmp(expected_public_key, pk_data, pk_len) != 0) {
+        avb_errorv(full_partition_name,
+                   ": Public key used to sign data does not match key in chain "
+                   "partition descriptor.\n",
+                   NULL);
+        ret = AVB_SLOT_VERIFY_RESULT_ERROR_PUBLIC_KEY_REJECTED;
+        if (!allow_verification_error) {
+          goto out;
+        }
+      }
+    } else {
+      bool key_is_trusted = false;
+      const uint8_t* pk_metadata = NULL;
+      size_t pk_metadata_len = 0;
+
+      if (vbmeta_header.public_key_metadata_size > 0) {
+        pk_metadata = vbmeta_buf + sizeof(AvbVBMetaImageHeader) +
+                      vbmeta_header.authentication_data_block_size +
+                      vbmeta_header.public_key_metadata_offset;
+        pk_metadata_len = vbmeta_header.public_key_metadata_size;
+      }
+
+      avb_assert(is_main_vbmeta);
+      io_ret = ops->validate_vbmeta_public_key(
+          ops, pk_data, pk_len, pk_metadata, pk_metadata_len, &key_is_trusted);
+      if (io_ret == AVB_IO_RESULT_ERROR_OOM) {
+        ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
+        goto out;
+      } else if (io_ret != AVB_IO_RESULT_OK) {
+        avb_errorv(full_partition_name,
+                   ": Error while checking public key used to sign data.\n",
+                   NULL);
+        ret = AVB_SLOT_VERIFY_RESULT_ERROR_IO;
+        goto out;
+      }
+      if (!key_is_trusted) {
+        avb_errorv(full_partition_name,
+                   ": Public key used to sign data rejected.\n",
+                   NULL);
+        ret = AVB_SLOT_VERIFY_RESULT_ERROR_PUBLIC_KEY_REJECTED;
+        if (!allow_verification_error) {
+          goto out;
+        }
+      }
+    }
+  }
+
+  /* Check rollback index. */
+  io_ret = ops->read_rollback_index(
+      ops, rollback_index_location, &stored_rollback_index);
+  if (io_ret == AVB_IO_RESULT_ERROR_OOM) {
+    ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
+    goto out;
+  } else if (io_ret != AVB_IO_RESULT_OK) {
+    avb_errorv(full_partition_name,
+               ": Error getting rollback index for location.\n",
+               NULL);
+    ret = AVB_SLOT_VERIFY_RESULT_ERROR_IO;
+    goto out;
+  }
+  if (vbmeta_header.rollback_index < stored_rollback_index) {
+    avb_errorv(
+        full_partition_name,
+        ": Image rollback index is less than the stored rollback index.\n",
+        NULL);
+    ret = AVB_SLOT_VERIFY_RESULT_ERROR_ROLLBACK_INDEX;
+    if (!allow_verification_error) {
+      goto out;
+    }
+  }
+
+  /* Copy vbmeta to vbmeta_images before recursing. */
+  if (is_main_vbmeta) {
+    avb_assert(slot_data->num_vbmeta_images == 0);
+  } else {
+    avb_assert(slot_data->num_vbmeta_images > 0);
+  }
+  if (slot_data->num_vbmeta_images == MAX_NUMBER_OF_VBMETA_IMAGES) {
+    avb_errorv(full_partition_name, ": Too many vbmeta images.\n", NULL);
+    ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
+    goto out;
+  }
+  vbmeta_image_data = &slot_data->vbmeta_images[slot_data->num_vbmeta_images++];
+  vbmeta_image_data->partition_name = avb_strdup(partition_name);
+  vbmeta_image_data->vbmeta_data = vbmeta_buf;
+  /* Note that |vbmeta_buf| is actually |vbmeta_num_read| bytes long
+   * and this includes data past the end of the image. Pass the
+   * actual size of the vbmeta image. Also, no need to use
+   * avb_safe_add() since the header has already been verified.
+   */
+  vbmeta_image_data->vbmeta_size =
+      sizeof(AvbVBMetaImageHeader) +
+      vbmeta_header.authentication_data_block_size +
+      vbmeta_header.auxiliary_data_block_size;
+  vbmeta_image_data->verify_result = vbmeta_ret;
+
+  /* Now go through all descriptors and take the appropriate action:
+   *
+   * - hash descriptor: Load data from partition, calculate hash, and
+   *   checks that it matches what's in the hash descriptor.
+   *
+   * - hashtree descriptor: Do nothing since verification happens
+   *   on-the-fly from within the OS.
+   *
+   * - chained partition descriptor: Load the footer, load the vbmeta
+   *   image, verify vbmeta image (includes rollback checks, hash
+   *   checks, bail on chained partitions).
+   */
+  descriptors =
+      avb_descriptor_get_all(vbmeta_buf, vbmeta_num_read, &num_descriptors);
+  for (n = 0; n < num_descriptors; n++) {
+    AvbDescriptor desc;
+
+    if (!avb_descriptor_validate_and_byteswap(descriptors[n], &desc)) {
+      avb_errorv(full_partition_name, ": Descriptor is invalid.\n", NULL);
+      ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA;
+      goto out;
+    }
+
+    switch (desc.tag) {
+      case AVB_DESCRIPTOR_TAG_HASH: {
+        AvbSlotVerifyResult sub_ret;
+        sub_ret = load_and_verify_hash_partition(ops,
+                                                 requested_partitions,
+                                                 ab_suffix,
+                                                 allow_verification_error,
+                                                 descriptors[n],
+                                                 slot_data);
+        if (sub_ret != AVB_SLOT_VERIFY_RESULT_OK) {
+          ret = sub_ret;
+          if (!allow_verification_error || !result_should_continue(ret)) {
+            goto out;
+          }
+        }
+      } break;
+
+      case AVB_DESCRIPTOR_TAG_CHAIN_PARTITION: {
+        AvbSlotVerifyResult sub_ret;
+        AvbChainPartitionDescriptor chain_desc;
+        const uint8_t* chain_partition_name;
+        const uint8_t* chain_public_key;
+
+        /* Only allow CHAIN_PARTITION descriptors in the main vbmeta image. */
+        if (!is_main_vbmeta) {
+          avb_errorv(full_partition_name,
+                     ": Encountered chain descriptor not in main image.\n",
+                     NULL);
+          ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA;
+          goto out;
+        }
+
+        if (!avb_chain_partition_descriptor_validate_and_byteswap(
+                (AvbChainPartitionDescriptor*)descriptors[n], &chain_desc)) {
+          avb_errorv(full_partition_name,
+                     ": Chain partition descriptor is invalid.\n",
+                     NULL);
+          ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA;
+          goto out;
+        }
+
+        chain_partition_name = ((const uint8_t*)descriptors[n]) +
+                               sizeof(AvbChainPartitionDescriptor);
+        chain_public_key = chain_partition_name + chain_desc.partition_name_len;
+
+        sub_ret = load_and_verify_vbmeta(ops,
+                                         requested_partitions,
+                                         ab_suffix,
+                                         allow_verification_error,
+                                         toplevel_vbmeta_flags,
+                                         chain_desc.rollback_index_location,
+                                         (const char*)chain_partition_name,
+                                         chain_desc.partition_name_len,
+                                         chain_public_key,
+                                         chain_desc.public_key_len,
+                                         slot_data,
+                                         NULL /* out_algorithm_type */);
+        if (sub_ret != AVB_SLOT_VERIFY_RESULT_OK) {
+          ret = sub_ret;
+          if (!result_should_continue(ret)) {
+            goto out;
+          }
+        }
+      } break;
+
+      case AVB_DESCRIPTOR_TAG_KERNEL_CMDLINE: {
+        const uint8_t* kernel_cmdline;
+        AvbKernelCmdlineDescriptor kernel_cmdline_desc;
+        bool apply_cmdline;
+
+        if (!avb_kernel_cmdline_descriptor_validate_and_byteswap(
+                (AvbKernelCmdlineDescriptor*)descriptors[n],
+                &kernel_cmdline_desc)) {
+          avb_errorv(full_partition_name,
+                     ": Kernel cmdline descriptor is invalid.\n",
+                     NULL);
+          ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA;
+          goto out;
+        }
+
+        kernel_cmdline = ((const uint8_t*)descriptors[n]) +
+                         sizeof(AvbKernelCmdlineDescriptor);
+
+        if (!avb_validate_utf8(kernel_cmdline,
+                               kernel_cmdline_desc.kernel_cmdline_length)) {
+          avb_errorv(full_partition_name,
+                     ": Kernel cmdline is not valid UTF-8.\n",
+                     NULL);
+          ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA;
+          goto out;
+        }
+
+        /* Compare the flags for top-level VBMeta struct with flags in
+         * the command-line descriptor so command-line snippets only
+         * intended for a certain mode (dm-verity enabled/disabled)
+         * are skipped if applicable.
+         */
+        apply_cmdline = true;
+        if (toplevel_vbmeta_flags & AVB_VBMETA_IMAGE_FLAGS_HASHTREE_DISABLED) {
+          if (kernel_cmdline_desc.flags &
+              AVB_KERNEL_CMDLINE_FLAGS_USE_ONLY_IF_HASHTREE_NOT_DISABLED) {
+            apply_cmdline = false;
+          }
+        } else {
+          if (kernel_cmdline_desc.flags &
+              AVB_KERNEL_CMDLINE_FLAGS_USE_ONLY_IF_HASHTREE_DISABLED) {
+            apply_cmdline = false;
+          }
+        }
+
+        if (apply_cmdline) {
+          if (slot_data->cmdline == NULL) {
+            slot_data->cmdline =
+                avb_calloc(kernel_cmdline_desc.kernel_cmdline_length + 1);
+            if (slot_data->cmdline == NULL) {
+              ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
+              goto out;
+            }
+            avb_memcpy(slot_data->cmdline,
+                       kernel_cmdline,
+                       kernel_cmdline_desc.kernel_cmdline_length);
+          } else {
+            /* new cmdline is: <existing_cmdline> + ' ' + <newcmdline> + '\0' */
+            size_t orig_size = avb_strlen(slot_data->cmdline);
+            size_t new_size =
+                orig_size + 1 + kernel_cmdline_desc.kernel_cmdline_length + 1;
+            char* new_cmdline = avb_calloc(new_size);
+            if (new_cmdline == NULL) {
+              ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
+              goto out;
+            }
+            avb_memcpy(new_cmdline, slot_data->cmdline, orig_size);
+            new_cmdline[orig_size] = ' ';
+            avb_memcpy(new_cmdline + orig_size + 1,
+                       kernel_cmdline,
+                       kernel_cmdline_desc.kernel_cmdline_length);
+            avb_free(slot_data->cmdline);
+            slot_data->cmdline = new_cmdline;
+          }
+        }
+      } break;
+
+      /* Explicit fall-through */
+      case AVB_DESCRIPTOR_TAG_PROPERTY:
+      case AVB_DESCRIPTOR_TAG_HASHTREE:
+        /* Do nothing. */
+        break;
+    }
+  }
+
+  if (rollback_index_location >= AVB_MAX_NUMBER_OF_ROLLBACK_INDEX_LOCATIONS) {
+    avb_errorv(
+        full_partition_name, ": Invalid rollback_index_location.\n", NULL);
+    ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA;
+    goto out;
+  }
+
+  slot_data->rollback_indexes[rollback_index_location] =
+      vbmeta_header.rollback_index;
+
+  if (out_algorithm_type != NULL) {
+    *out_algorithm_type = (AvbAlgorithmType)vbmeta_header.algorithm_type;
+  }
+
+out:
+  /* If |vbmeta_image_data| isn't NULL it means that it adopted
+   * |vbmeta_buf| so in that case don't free it here.
+   */
+  if (vbmeta_image_data == NULL) {
+    if (vbmeta_buf != NULL) {
+      avb_free(vbmeta_buf);
+    }
+  }
+  if (descriptors != NULL) {
+    avb_free(descriptors);
+  }
+  return ret;
+}
+
+#define NUM_GUIDS 3
+
+/* Substitutes all variables (e.g. $(ANDROID_SYSTEM_PARTUUID)) with
+ * values. Returns NULL on OOM, otherwise the cmdline with values
+ * replaced.
+ */
+static char* sub_cmdline(AvbOps* ops,
+                         const char* cmdline,
+                         const char* ab_suffix) {
+  const char* part_name_str[NUM_GUIDS] = {"system", "boot", "vbmeta"};
+  const char* replace_str[NUM_GUIDS] = {"$(ANDROID_SYSTEM_PARTUUID)",
+                                        "$(ANDROID_BOOT_PARTUUID)",
+                                        "$(ANDROID_VBMETA_PARTUUID)"};
+  char* ret = NULL;
+  AvbIOResult io_ret;
+
+  /* Replace unique partition GUIDs */
+  for (size_t n = 0; n < NUM_GUIDS; n++) {
+    char part_name[PART_NAME_MAX_SIZE];
+    char guid_buf[37];
+    char* new_ret;
+
+    if (!avb_str_concat(part_name,
+                        sizeof part_name,
+                        part_name_str[n],
+                        avb_strlen(part_name_str[n]),
+                        ab_suffix,
+                        avb_strlen(ab_suffix))) {
+      avb_error("Partition name and suffix does not fit.\n");
+      goto fail;
+    }
+
+    io_ret = ops->get_unique_guid_for_partition(
+        ops, part_name, guid_buf, sizeof guid_buf);
+    if (io_ret == AVB_IO_RESULT_ERROR_OOM) {
+      return NULL;
+    } else if (io_ret != AVB_IO_RESULT_OK) {
+      avb_error("Error getting unique GUID for partition.\n");
+      goto fail;
+    }
+
+    if (ret == NULL) {
+      new_ret = avb_replace(cmdline, replace_str[n], guid_buf);
+    } else {
+      new_ret = avb_replace(ret, replace_str[n], guid_buf);
+    }
+    if (new_ret == NULL) {
+      goto fail;
+    }
+    ret = new_ret;
+  }
+
+  return ret;
+
+fail:
+  if (ret != NULL) {
+    avb_free(ret);
+  }
+  return NULL;
+}
+
+static int cmdline_append_option(AvbSlotVerifyData* slot_data,
+                                 const char* key,
+                                 const char* value) {
+  size_t offset, key_len, value_len;
+  char* new_cmdline;
+
+  key_len = avb_strlen(key);
+  value_len = avb_strlen(value);
+
+  offset = 0;
+  if (slot_data->cmdline != NULL) {
+    offset = avb_strlen(slot_data->cmdline);
+    if (offset > 0) {
+      offset += 1;
+    }
+  }
+
+  new_cmdline = avb_calloc(offset + key_len + value_len + 2);
+  if (new_cmdline == NULL) {
+    return 0;
+  }
+  if (offset > 0) {
+    avb_memcpy(new_cmdline, slot_data->cmdline, offset - 1);
+    new_cmdline[offset - 1] = ' ';
+  }
+  avb_memcpy(new_cmdline + offset, key, key_len);
+  new_cmdline[offset + key_len] = '=';
+  avb_memcpy(new_cmdline + offset + key_len + 1, value, value_len);
+  if (slot_data->cmdline != NULL) {
+    avb_free(slot_data->cmdline);
+  }
+  slot_data->cmdline = new_cmdline;
+
+  return 1;
+}
+
+static int cmdline_append_uint64_base10(AvbSlotVerifyData* slot_data,
+                                        const char* key,
+                                        uint64_t value) {
+  const int MAX_DIGITS = 32;
+  char rev_digits[MAX_DIGITS];
+  char digits[MAX_DIGITS];
+  size_t n, num_digits;
+
+  for (num_digits = 0; num_digits < MAX_DIGITS - 1;) {
+    rev_digits[num_digits++] = (value % 10) + '0';
+    value /= 10;
+    if (value == 0) {
+      break;
+    }
+  }
+
+  for (n = 0; n < num_digits; n++) {
+    digits[n] = rev_digits[num_digits - 1 - n];
+  }
+  digits[n] = '\0';
+
+  return cmdline_append_option(slot_data, key, digits);
+}
+
+static int cmdline_append_hex(AvbSlotVerifyData* slot_data,
+                              const char* key,
+                              const uint8_t* data,
+                              size_t data_len) {
+  char hex_digits[17] = "0123456789abcdef";
+  char* hex_data;
+  int ret;
+  size_t n;
+
+  hex_data = avb_malloc(data_len * 2 + 1);
+  if (hex_data == NULL) {
+    return 0;
+  }
+
+  for (n = 0; n < data_len; n++) {
+    hex_data[n * 2] = hex_digits[data[n] >> 4];
+    hex_data[n * 2 + 1] = hex_digits[data[n] & 0x0f];
+  }
+  hex_data[n * 2] = '\0';
+
+  ret = cmdline_append_option(slot_data, key, hex_data);
+  avb_free(hex_data);
+  return ret;
+}
+
+AvbSlotVerifyResult avb_slot_verify(AvbOps* ops,
+                                    const char* const* requested_partitions,
+                                    const char* ab_suffix,
+                                    bool allow_verification_error,
+                                    AvbSlotVerifyData** out_data) {
+  AvbSlotVerifyResult ret;
+  AvbSlotVerifyData* slot_data = NULL;
+  AvbAlgorithmType algorithm_type = AVB_ALGORITHM_TYPE_NONE;
+  AvbIOResult io_ret;
+
+  if (out_data != NULL) {
+    *out_data = NULL;
+  }
+
+  slot_data = avb_calloc(sizeof(AvbSlotVerifyData));
+  if (slot_data == NULL) {
+    ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
+    goto fail;
+  }
+  slot_data->vbmeta_images =
+      avb_calloc(sizeof(AvbVBMetaData) * MAX_NUMBER_OF_VBMETA_IMAGES);
+  if (slot_data->vbmeta_images == NULL) {
+    ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
+    goto fail;
+  }
+  slot_data->loaded_partitions =
+      avb_calloc(sizeof(AvbPartitionData) * MAX_NUMBER_OF_LOADED_PARTITIONS);
+  if (slot_data->loaded_partitions == NULL) {
+    ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
+    goto fail;
+  }
+
+  ret = load_and_verify_vbmeta(ops,
+                               requested_partitions,
+                               ab_suffix,
+                               allow_verification_error,
+                               0, /* toplevel_vbmeta_flags */
+                               0 /* rollback_index_location */,
+                               "vbmeta",
+                               avb_strlen("vbmeta"),
+                               NULL /* expected_public_key */,
+                               0 /* expected_public_key_length */,
+                               slot_data,
+                               &algorithm_type);
+  if (!allow_verification_error && ret != AVB_SLOT_VERIFY_RESULT_OK) {
+    goto fail;
+  }
+
+  /* If things check out, mangle the kernel command-line as needed. */
+  if (result_should_continue(ret)) {
+    /* Fill in |ab_suffix| field. */
+    slot_data->ab_suffix = avb_strdup(ab_suffix);
+    if (slot_data->ab_suffix == NULL) {
+      ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
+      goto fail;
+    }
+
+    /* Add androidboot.vbmeta.device option. */
+    if (!cmdline_append_option(slot_data,
+                               "androidboot.vbmeta.device",
+                               "PARTUUID=$(ANDROID_VBMETA_PARTUUID)")) {
+      ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
+      goto fail;
+    }
+
+    /* Substitute $(ANDROID_SYSTEM_PARTUUID) and friends. */
+    if (slot_data->cmdline != NULL) {
+      char* new_cmdline = sub_cmdline(ops, slot_data->cmdline, ab_suffix);
+      if (new_cmdline == NULL) {
+        ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
+        goto fail;
+      }
+      avb_free(slot_data->cmdline);
+      slot_data->cmdline = new_cmdline;
+    }
+
+    /* Add androidboot.slot_suffix, if applicable. */
+    if (avb_strlen(ab_suffix) > 0) {
+      if (!cmdline_append_option(
+              slot_data, "androidboot.slot_suffix", ab_suffix)) {
+        ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
+        goto fail;
+      }
+    }
+
+    /* Set androidboot.avb.device_state to "locked" or "unlocked". */
+    bool is_device_unlocked;
+    io_ret = ops->read_is_device_unlocked(ops, &is_device_unlocked);
+    if (io_ret == AVB_IO_RESULT_ERROR_OOM) {
+      ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
+      goto fail;
+    } else if (io_ret != AVB_IO_RESULT_OK) {
+      avb_error("Error getting device state.\n");
+      ret = AVB_SLOT_VERIFY_RESULT_ERROR_IO;
+      goto fail;
+    }
+    if (!cmdline_append_option(slot_data,
+                               "androidboot.vbmeta.device_state",
+                               is_device_unlocked ? "unlocked" : "locked")) {
+      ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
+      goto fail;
+    }
+
+    /* Set androidboot.vbmeta.{hash_alg, size, digest} - use same hash
+     * function as is used to sign vbmeta.
+     */
+    switch (algorithm_type) {
+      /* Explicit fallthrough. */
+      case AVB_ALGORITHM_TYPE_NONE:
+      case AVB_ALGORITHM_TYPE_SHA256_RSA2048:
+      case AVB_ALGORITHM_TYPE_SHA256_RSA4096:
+      case AVB_ALGORITHM_TYPE_SHA256_RSA8192: {
+        AvbSHA256Ctx ctx;
+        size_t n, total_size = 0;
+        avb_sha256_init(&ctx);
+        for (n = 0; n < slot_data->num_vbmeta_images; n++) {
+          avb_sha256_update(&ctx,
+                            slot_data->vbmeta_images[n].vbmeta_data,
+                            slot_data->vbmeta_images[n].vbmeta_size);
+          total_size += slot_data->vbmeta_images[n].vbmeta_size;
+        }
+        if (!cmdline_append_option(
+                slot_data, "androidboot.vbmeta.hash_alg", "sha256") ||
+            !cmdline_append_uint64_base10(
+                slot_data, "androidboot.vbmeta.size", total_size) ||
+            !cmdline_append_hex(slot_data,
+                                "androidboot.vbmeta.digest",
+                                avb_sha256_final(&ctx),
+                                AVB_SHA256_DIGEST_SIZE)) {
+          ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
+          goto fail;
+        }
+      } break;
+      /* Explicit fallthrough. */
+      case AVB_ALGORITHM_TYPE_SHA512_RSA2048:
+      case AVB_ALGORITHM_TYPE_SHA512_RSA4096:
+      case AVB_ALGORITHM_TYPE_SHA512_RSA8192: {
+        AvbSHA512Ctx ctx;
+        size_t n, total_size = 0;
+        avb_sha512_init(&ctx);
+        for (n = 0; n < slot_data->num_vbmeta_images; n++) {
+          avb_sha512_update(&ctx,
+                            slot_data->vbmeta_images[n].vbmeta_data,
+                            slot_data->vbmeta_images[n].vbmeta_size);
+          total_size += slot_data->vbmeta_images[n].vbmeta_size;
+        }
+        if (!cmdline_append_option(
+                slot_data, "androidboot.vbmeta.hash_alg", "sha512") ||
+            !cmdline_append_uint64_base10(
+                slot_data, "androidboot.vbmeta.size", total_size) ||
+            !cmdline_append_hex(slot_data,
+                                "androidboot.vbmeta.digest",
+                                avb_sha512_final(&ctx),
+                                AVB_SHA512_DIGEST_SIZE)) {
+          ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
+          goto fail;
+        }
+      } break;
+      case _AVB_ALGORITHM_NUM_TYPES:
+        avb_assert_not_reached();
+        break;
+    }
+
+    if (out_data != NULL) {
+      *out_data = slot_data;
+    } else {
+      avb_slot_verify_data_free(slot_data);
+    }
+  }
+
+  if (!allow_verification_error) {
+    avb_assert(ret == AVB_SLOT_VERIFY_RESULT_OK);
+  }
+
+  return ret;
+
+fail:
+  if (slot_data != NULL) {
+    avb_slot_verify_data_free(slot_data);
+  }
+  return ret;
+}
+
+void avb_slot_verify_data_free(AvbSlotVerifyData* data) {
+  if (data->ab_suffix != NULL) {
+    avb_free(data->ab_suffix);
+  }
+  if (data->cmdline != NULL) {
+    avb_free(data->cmdline);
+  }
+  if (data->vbmeta_images != NULL) {
+    size_t n;
+    for (n = 0; n < data->num_vbmeta_images; n++) {
+      AvbVBMetaData* vbmeta_image = &data->vbmeta_images[n];
+      if (vbmeta_image->partition_name != NULL) {
+        avb_free(vbmeta_image->partition_name);
+      }
+      if (vbmeta_image->vbmeta_data != NULL) {
+        avb_free(vbmeta_image->vbmeta_data);
+      }
+    }
+    avb_free(data->vbmeta_images);
+  }
+  if (data->loaded_partitions != NULL) {
+    size_t n;
+    for (n = 0; n < data->num_loaded_partitions; n++) {
+      AvbPartitionData* loaded_partition = &data->loaded_partitions[n];
+      if (loaded_partition->partition_name != NULL) {
+        avb_free(loaded_partition->partition_name);
+      }
+      if (loaded_partition->data != NULL) {
+        avb_free(loaded_partition->data);
+      }
+    }
+    avb_free(data->loaded_partitions);
+  }
+  avb_free(data);
+}
+
+const char* avb_slot_verify_result_to_string(AvbSlotVerifyResult result) {
+  const char* ret = NULL;
+
+  switch (result) {
+    case AVB_SLOT_VERIFY_RESULT_OK:
+      ret = "OK";
+      break;
+    case AVB_SLOT_VERIFY_RESULT_ERROR_OOM:
+      ret = "ERROR_OOM";
+      break;
+    case AVB_SLOT_VERIFY_RESULT_ERROR_IO:
+      ret = "ERROR_IO";
+      break;
+    case AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION:
+      ret = "ERROR_VERIFICATION";
+      break;
+    case AVB_SLOT_VERIFY_RESULT_ERROR_ROLLBACK_INDEX:
+      ret = "ERROR_ROLLBACK_INDEX";
+      break;
+    case AVB_SLOT_VERIFY_RESULT_ERROR_PUBLIC_KEY_REJECTED:
+      ret = "ERROR_PUBLIC_KEY_REJECTED";
+      break;
+    case AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA:
+      ret = "ERROR_INVALID_METADATA";
+      break;
+      /* Do not add a 'default:' case here because of -Wswitch. */
+  }
+
+  if (ret == NULL) {
+    avb_error("Unknown AvbSlotVerifyResult value.\n");
+    ret = "(unknown)";
+  }
+
+  return ret;
+}
diff --git a/avb/libavb/avb_slot_verify.h b/avb/libavb/avb_slot_verify.h
new file mode 100644
index 0000000..93c12ba
--- /dev/null
+++ b/avb/libavb/avb_slot_verify.h
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#if !defined(AVB_INSIDE_LIBAVB_H) && !defined(AVB_COMPILATION)
+#error "Never include this file directly, include libavb.h instead."
+#endif
+
+#ifndef AVB_SLOT_VERIFY_H_
+#define AVB_SLOT_VERIFY_H_
+
+#include "avb_ops.h"
+#include "avb_vbmeta_image.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Return codes used in avb_slot_verify(), see that function for
+ * documentation for each field.
+ *
+ * Use avb_slot_verify_result_to_string() to get a textual
+ * representation usable for error/debug output.
+ */
+typedef enum {
+  AVB_SLOT_VERIFY_RESULT_OK,
+  AVB_SLOT_VERIFY_RESULT_ERROR_OOM,
+  AVB_SLOT_VERIFY_RESULT_ERROR_IO,
+  AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION,
+  AVB_SLOT_VERIFY_RESULT_ERROR_ROLLBACK_INDEX,
+  AVB_SLOT_VERIFY_RESULT_ERROR_PUBLIC_KEY_REJECTED,
+  AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA
+} AvbSlotVerifyResult;
+
+/* Get a textual representation of |result|. */
+const char* avb_slot_verify_result_to_string(AvbSlotVerifyResult result);
+
+/* Maximum number of rollback index locations supported. */
+#define AVB_MAX_NUMBER_OF_ROLLBACK_INDEX_LOCATIONS 32
+
+/* AvbPartitionData contains data loaded from partitions when using
+ * avb_slot_verify(). The |partition_name| field contains the name of
+ * the partition (without A/B suffix), |data| points to the loaded
+ * data which is |data_size| bytes long.
+ *
+ * Note that this is strictly less than the partition size - it's only
+ * the image stored there, not the entire partition nor any of the
+ * metadata.
+ */
+typedef struct {
+  char* partition_name;
+  uint8_t* data;
+  size_t data_size;
+} AvbPartitionData;
+
+/* AvbVBMetaData contains a vbmeta struct loaded from a partition when
+ * using avb_slot_verify(). The |partition_name| field contains the
+ * name of the partition (without A/B suffix), |vbmeta_data| points to
+ * the loaded data which is |vbmeta_size| bytes long.
+ *
+ * The |verify_result| field contains the result of
+ * avb_vbmeta_image_verify() on the data. This is guaranteed to be
+ * AVB_VBMETA_VERIFY_RESULT_OK for all vbmeta images if
+ * avb_slot_verify() returns AVB_SLOT_VERIFY_RESULT_OK.
+ *
+ * You can use avb_descriptor_get_all(), avb_descriptor_foreach(), and
+ * avb_vbmeta_image_header_to_host_byte_order() with this data.
+ */
+typedef struct {
+  char* partition_name;
+  uint8_t* vbmeta_data;
+  size_t vbmeta_size;
+  AvbVBMetaVerifyResult verify_result;
+} AvbVBMetaData;
+
+/* AvbSlotVerifyData contains data needed to boot a particular slot
+ * and is returned by avb_slot_verify() if partitions in a slot are
+ * successfully verified.
+ *
+ * All data pointed to by this struct - including data in each item in
+ * the |partitions| array - will be freed when the
+ * avb_slot_verify_data_free() function is called.
+ *
+ * The |ab_suffix| field is the copy of the of |ab_suffix| field
+ * passed to avb_slot_verify(). It is the A/B suffix of the slot.
+ *
+ * The VBMeta images that were checked are available in the
+ * |vbmeta_images| field. The field |num_vbmeta_images| contains the
+ * number of elements in this array. The first element -
+ * vbmeta_images[0] - is guaranteed to be from the "vbmeta" partition
+ * in the requested slot.
+ *
+ * The partitions loaded and verified from from the slot are
+ * accessible in the |loaded_partitions| array. The field
+ * |num_loaded_partitions| contains the number of elements in this
+ * array. The order of partitions in this array may not necessarily be
+ * the same order as in the passed-in |requested_partitions| array.
+ *
+ * Rollback indexes for the verified slot are stored in the
+ * |rollback_indexes| field. Note that avb_slot_verify() will NEVER
+ * modify stored_rollback_index[n] locations e.g. it will never use
+ * the write_rollback_index() AvbOps operation. Instead it is the job
+ * of the caller of avb_slot_verify() to do this based on e.g. A/B
+ * policy and other factors. See libavb_ab/avb_ab_flow.c for an
+ * example of how to do this.
+ *
+ * The |cmdline| field is a NUL-terminated string in UTF-8 resulting
+ * from concatenating all |AvbKernelCmdlineDescriptor| and then
+ * performing proper substitution of the variables
+ * $(ANDROID_SYSTEM_PARTUUID), $(ANDROID_BOOT_PARTUUID), and
+ * $(ANDROID_VBMETA_PARTUUID) using the
+ * get_unique_guid_for_partition() operation in |AvbOps|.
+ *
+ * Additionally, the |cmdline| field will have the following kernel
+ * command-line options set:
+ *
+ *   androidboot.vbmeta.device_state: set to "locked" or "unlocked"
+ *   depending on the result of the result of AvbOps's
+ *   read_is_unlocked() function.
+ *
+ *   androidboot.slot_suffix: If |ab_suffix| as passed into
+ *   avb_slot_verify() is non-empty, this variable will be set to its
+ *   value.
+ *
+ *   androidboot.vbmeta.{hash_alg, size, digest}: Will be set to
+ *   the digest of all images in |vbmeta_images|.
+ *
+ *   androidboot.vbmeta.device: This is set to the value
+ *   PARTUUID=$(ANDROID_VBMETA_PARTUUID) before substitution so it
+ *   will end up pointing to the vbmeta partition for the verified
+ *   slot.
+ *
+ * This struct may grow in the future without it being considered an
+ * ABI break.
+ */
+typedef struct {
+  char* ab_suffix;
+  AvbVBMetaData* vbmeta_images;
+  size_t num_vbmeta_images;
+  AvbPartitionData* loaded_partitions;
+  size_t num_loaded_partitions;
+  char* cmdline;
+  uint64_t rollback_indexes[AVB_MAX_NUMBER_OF_ROLLBACK_INDEX_LOCATIONS];
+} AvbSlotVerifyData;
+
+/* Frees a |AvbSlotVerifyData| including all data it points to. */
+void avb_slot_verify_data_free(AvbSlotVerifyData* data);
+
+/* Performs a full verification of the slot identified by |ab_suffix|
+ * and load the contents of the partitions whose name is in the
+ * NULL-terminated string array |requested_partitions| (each partition
+ * must use hash verification). If not using A/B, pass an empty string
+ * (e.g. "", not NULL) for |ab_suffix|.
+ *
+ * Typically the |requested_partitions| array only contains a single
+ * item for the boot partition, 'boot'.
+ *
+ * Verification includes loading data from the 'vbmeta', all hash
+ * partitions, and possibly other partitions (with |ab_suffix|
+ * appended), inspecting rollback indexes, and checking if the public
+ * key used to sign the data is acceptable. The functions in |ops|
+ * will be used to do this.
+ *
+ * If |out_data| is not NULL, it will be set to a newly allocated
+ * |AvbSlotVerifyData| struct containing all the data needed to
+ * actually boot the slot. This data structure should be freed with
+ * avb_slot_verify_data_free() when you are done with it. See below
+ * for when this is returned.
+ *
+ * If |allow_verification_error| is false this function will bail out
+ * as soon as an error is encountered and |out_data| is set only if
+ * AVB_SLOT_VERIFY_RESULT_OK is returned.
+ *
+ * Otherwise if |allow_verification_error| is true the function will
+ * continue verification efforts and |out_data| is also set if
+ * AVB_SLOT_VERIFY_RESULT_ERROR_PUBLIC_KEY_REJECTED,
+ * AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION, or
+ * AVB_SLOT_VERIFY_RESULT_ERROR_ROLLBACK_INDEX is returned. It is
+ * undefined which error is returned if more than one distinct error
+ * is encountered. It is guaranteed that AVB_SLOT_VERIFY_RESULT_OK is
+ * returned if, and only if, there are no errors. This mode is needed
+ * to boot valid but unverified slots when the device is unlocked.
+ *
+ * Also note that |out_data| is never set if
+ * AVB_SLOT_VERIFY_RESULT_ERROR_OOM, AVB_SLOT_VERIFY_RESULT_ERROR_IO,
+ * or AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA is returned.
+ *
+ * AVB_SLOT_VERIFY_RESULT_OK is returned if everything is verified
+ * correctly and all public keys are accepted.
+ *
+ * AVB_SLOT_VERIFY_RESULT_ERROR_PUBLIC_KEY_REJECTED is returned if
+ * everything is verified correctly out but one or more public keys
+ * are not accepted. This includes the case where integrity data is
+ * not signed.
+ *
+ * AVB_SLOT_VERIFY_RESULT_ERROR_OOM is returned if unable to
+ * allocate memory.
+ *
+ * AVB_SLOT_VERIFY_RESULT_ERROR_IO is returned if an I/O error
+ * occurred while trying to load data or get a rollback index.
+ *
+ * AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION is returned if the data
+ * did not verify, e.g. the digest didn't match or signature checks
+ * failed.
+ *
+ * AVB_SLOT_VERIFY_RESULT_ERROR_ROLLBACK_INDEX is returned if a
+ * rollback index was less than its stored value.
+ *
+ * AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA is returned if some
+ * of the metadata is invalid or inconsistent.
+ */
+AvbSlotVerifyResult avb_slot_verify(AvbOps* ops,
+                                    const char* const* requested_partitions,
+                                    const char* ab_suffix,
+                                    bool allow_verification_error,
+                                    AvbSlotVerifyData** out_data);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* AVB_SLOT_VERIFY_H_ */
diff --git a/avb/libavb/avb_sysdeps.h b/avb/libavb/avb_sysdeps.h
new file mode 100644
index 0000000..aea837a
--- /dev/null
+++ b/avb/libavb/avb_sysdeps.h
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#if !defined(AVB_INSIDE_LIBAVB_H) && !defined(AVB_COMPILATION)
+#error "Never include this file directly, include libavb.h instead."
+#endif
+
+#ifndef AVB_SYSDEPS_H_
+#define AVB_SYSDEPS_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Change these includes to match your platform to bring in the
+ * equivalent types available in a normal C runtime. At least things
+ * like uint8_t, uint64_t, and bool (with |false|, |true| keywords)
+ * must be present.
+ */
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+/* If you don't have gcc or clang, these attribute macros may need to
+ * be adjusted.
+ */
+#define AVB_ATTR_WARN_UNUSED_RESULT __attribute__((warn_unused_result))
+#define AVB_ATTR_PACKED __attribute__((packed))
+#define AVB_ATTR_NO_RETURN __attribute__((noreturn))
+#define AVB_ATTR_SENTINEL __attribute__((__sentinel__))
+
+/* Size in bytes used for alignment. */
+#ifdef __LP64__
+#define AVB_ALIGNMENT_SIZE 8
+#else
+#define AVB_ALIGNMENT_SIZE 4
+#endif
+
+/* Compare |n| bytes in |src1| and |src2|.
+ *
+ * Returns an integer less than, equal to, or greater than zero if the
+ * first |n| bytes of |src1| is found, respectively, to be less than,
+ * to match, or be greater than the first |n| bytes of |src2|. */
+int avb_memcmp(const void* src1,
+               const void* src2,
+               size_t n) AVB_ATTR_WARN_UNUSED_RESULT;
+
+/* Compare two strings.
+ *
+ * Return an integer less than, equal to, or greater than zero if |s1|
+ * is found, respectively, to be less than, to match, or be greater
+ * than |s2|.
+ */
+int avb_strcmp(const char* s1, const char* s2);
+
+/* Copy |n| bytes from |src| to |dest|. */
+void* avb_memcpy(void* dest, const void* src, size_t n);
+
+/* Set |n| bytes starting at |s| to |c|.  Returns |dest|. */
+void* avb_memset(void* dest, const int c, size_t n);
+
+/* Prints out a message. The string passed must be a NUL-terminated
+ * UTF-8 string.
+ */
+void avb_print(const char* message);
+
+/* Prints out a vector of strings. Each argument must point to a
+ * NUL-terminated UTF-8 string and NULL should be the last argument.
+ */
+void avb_printv(const char* message, ...) AVB_ATTR_SENTINEL;
+
+/* Aborts the program or reboots the device. */
+void avb_abort(void) AVB_ATTR_NO_RETURN;
+
+/* Allocates |size| bytes. Returns NULL if no memory is available,
+ * otherwise a pointer to the allocated memory.
+ *
+ * The memory is not initialized.
+ *
+ * The pointer returned is guaranteed to be word-aligned.
+ *
+ * The memory should be freed with avb_free() when you are done with it.
+ */
+void* avb_malloc_(size_t size) AVB_ATTR_WARN_UNUSED_RESULT;
+
+/* Frees memory previously allocated with avb_malloc(). */
+void avb_free(void* ptr);
+
+/* Returns the lenght of |str|, excluding the terminating NUL-byte. */
+size_t avb_strlen(const char* str) AVB_ATTR_WARN_UNUSED_RESULT;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* AVB_SYSDEPS_H_ */
diff --git a/avb/libavb/avb_sysdeps_posix.c b/avb/libavb/avb_sysdeps_posix.c
new file mode 100644
index 0000000..ea40d08
--- /dev/null
+++ b/avb/libavb/avb_sysdeps_posix.c
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <endian.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "avb_sysdeps.h"
+
+int avb_memcmp(const void* src1, const void* src2, size_t n) {
+  return memcmp(src1, src2, n);
+}
+
+void* avb_memcpy(void* dest, const void* src, size_t n) {
+  return memcpy(dest, src, n);
+}
+
+void* avb_memset(void* dest, const int c, size_t n) {
+  return memset(dest, c, n);
+}
+
+int avb_strcmp(const char* s1, const char* s2) {
+  return strcmp(s1, s2);
+}
+
+size_t avb_strlen(const char* str) {
+  return strlen(str);
+}
+
+void avb_abort(void) {
+  abort();
+}
+
+void avb_print(const char* message) {
+  fprintf(stderr, "%s", message);
+}
+
+void avb_printv(const char* message, ...) {
+  va_list ap;
+  const char* m;
+
+  va_start(ap, message);
+  for (m = message; m != NULL; m = va_arg(ap, const char*)) {
+    fprintf(stderr, "%s", m);
+  }
+  va_end(ap);
+}
+
+void* avb_malloc_(size_t size) {
+  return malloc(size);
+}
+
+void avb_free(void* ptr) {
+  free(ptr);
+}
diff --git a/avb/libavb/avb_util.c b/avb/libavb/avb_util.c
new file mode 100644
index 0000000..5a57b62
--- /dev/null
+++ b/avb/libavb/avb_util.c
@@ -0,0 +1,338 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "avb_util.h"
+
+uint32_t avb_be32toh(uint32_t in) {
+  uint8_t* d = (uint8_t*)&in;
+  uint32_t ret;
+  ret = ((uint32_t)d[0]) << 24;
+  ret |= ((uint32_t)d[1]) << 16;
+  ret |= ((uint32_t)d[2]) << 8;
+  ret |= ((uint32_t)d[3]);
+  return ret;
+}
+
+uint64_t avb_be64toh(uint64_t in) {
+  uint8_t* d = (uint8_t*)&in;
+  uint64_t ret;
+  ret = ((uint64_t)d[0]) << 56;
+  ret |= ((uint64_t)d[1]) << 48;
+  ret |= ((uint64_t)d[2]) << 40;
+  ret |= ((uint64_t)d[3]) << 32;
+  ret |= ((uint64_t)d[4]) << 24;
+  ret |= ((uint64_t)d[5]) << 16;
+  ret |= ((uint64_t)d[6]) << 8;
+  ret |= ((uint64_t)d[7]);
+  return ret;
+}
+
+/* Converts a 32-bit unsigned integer from host to big-endian byte order. */
+uint32_t avb_htobe32(uint32_t in) {
+  union {
+    uint32_t word;
+    uint8_t bytes[4];
+  } ret;
+  ret.bytes[0] = (in >> 24) & 0xff;
+  ret.bytes[1] = (in >> 16) & 0xff;
+  ret.bytes[2] = (in >> 8) & 0xff;
+  ret.bytes[3] = in & 0xff;
+  return ret.word;
+}
+
+/* Converts a 64-bit unsigned integer from host to big-endian byte order. */
+uint64_t avb_htobe64(uint64_t in) {
+  union {
+    uint64_t word;
+    uint8_t bytes[8];
+  } ret;
+  ret.bytes[0] = (in >> 56) & 0xff;
+  ret.bytes[1] = (in >> 48) & 0xff;
+  ret.bytes[2] = (in >> 40) & 0xff;
+  ret.bytes[3] = (in >> 32) & 0xff;
+  ret.bytes[4] = (in >> 24) & 0xff;
+  ret.bytes[5] = (in >> 16) & 0xff;
+  ret.bytes[6] = (in >> 8) & 0xff;
+  ret.bytes[7] = in & 0xff;
+  return ret.word;
+}
+
+int avb_safe_memcmp(const void* s1, const void* s2, size_t n) {
+  const unsigned char* us1 = s1;
+  const unsigned char* us2 = s2;
+  int result = 0;
+
+  if (0 == n) {
+    return 0;
+  }
+
+  /*
+   * Code snippet without data-dependent branch due to Nate Lawson
+   * (nate@root.org) of Root Labs.
+   */
+  while (n--) {
+    result |= *us1++ ^ *us2++;
+  }
+
+  return result != 0;
+}
+
+bool avb_safe_add_to(uint64_t* value, uint64_t value_to_add) {
+  uint64_t original_value;
+
+  avb_assert(value != NULL);
+
+  original_value = *value;
+
+  *value += value_to_add;
+  if (*value < original_value) {
+    avb_error("Overflow when adding values.\n");
+    return false;
+  }
+
+  return true;
+}
+
+bool avb_safe_add(uint64_t* out_result, uint64_t a, uint64_t b) {
+  uint64_t dummy;
+  if (out_result == NULL) {
+    out_result = &dummy;
+  }
+  *out_result = a;
+  return avb_safe_add_to(out_result, b);
+}
+
+bool avb_validate_utf8(const uint8_t* data, size_t num_bytes) {
+  size_t n;
+  unsigned int num_cc;
+
+  for (n = 0, num_cc = 0; n < num_bytes; n++) {
+    uint8_t c = data[n];
+
+    if (num_cc > 0) {
+      if ((c & (0x80 | 0x40)) == 0x80) {
+        /* 10xx xxxx */
+      } else {
+        goto fail;
+      }
+      num_cc--;
+    } else {
+      if (c < 0x80) {
+        num_cc = 0;
+      } else if ((c & (0x80 | 0x40 | 0x20)) == (0x80 | 0x40)) {
+        /* 110x xxxx */
+        num_cc = 1;
+      } else if ((c & (0x80 | 0x40 | 0x20 | 0x10)) == (0x80 | 0x40 | 0x20)) {
+        /* 1110 xxxx */
+        num_cc = 2;
+      } else if ((c & (0x80 | 0x40 | 0x20 | 0x10 | 0x08)) ==
+                 (0x80 | 0x40 | 0x20 | 0x10)) {
+        /* 1111 0xxx */
+        num_cc = 3;
+      } else {
+        goto fail;
+      }
+    }
+  }
+
+  if (num_cc != 0) {
+    goto fail;
+  }
+
+  return true;
+
+fail:
+  return false;
+}
+
+bool avb_str_concat(char* buf,
+                    size_t buf_size,
+                    const char* str1,
+                    size_t str1_len,
+                    const char* str2,
+                    size_t str2_len) {
+  uint64_t combined_len;
+
+  if (!avb_safe_add(&combined_len, str1_len, str2_len)) {
+    avb_error("Overflow when adding string sizes.\n");
+    return false;
+  }
+
+  if (combined_len > buf_size - 1) {
+    avb_error("Insufficient buffer space.\n");
+    return false;
+  }
+
+  avb_memcpy(buf, str1, str1_len);
+  avb_memcpy(buf + str1_len, str2, str2_len);
+  buf[combined_len] = '\0';
+
+  return true;
+}
+
+void* avb_malloc(size_t size) {
+  void* ret = avb_malloc_(size);
+  if (ret == NULL) {
+    avb_error("Failed to allocate memory.\n");
+    return NULL;
+  }
+  return ret;
+}
+
+void* avb_calloc(size_t size) {
+  void* ret = avb_malloc(size);
+  if (ret == NULL) {
+    return NULL;
+  }
+
+  avb_memset(ret, '\0', size);
+  return ret;
+}
+
+char* avb_strdup(const char* str) {
+  size_t len = avb_strlen(str);
+  char* ret = avb_malloc(len + 1);
+  if (ret == NULL) {
+    return NULL;
+  }
+
+  avb_memcpy(ret, str, len);
+  ret[len] = '\0';
+
+  return ret;
+}
+
+const char* avb_strstr(const char* haystack, const char* needle) {
+  size_t n, m;
+
+  /* Look through |haystack| and check if the first character of
+   * |needle| matches. If so, check the rest of |needle|.
+   */
+  for (n = 0; haystack[n] != '\0'; n++) {
+    if (haystack[n] != needle[0]) {
+      continue;
+    }
+
+    for (m = 1;; m++) {
+      if (needle[m] == '\0') {
+        return haystack + n;
+      }
+
+      if (haystack[n + m] != needle[m]) {
+        break;
+      }
+    }
+  }
+
+  return NULL;
+}
+
+const char* avb_strv_find_str(const char* const* strings,
+                              const char* str,
+                              size_t str_size) {
+  size_t n;
+  for (n = 0; strings[n] != NULL; n++) {
+    if (avb_strlen(strings[n]) == str_size &&
+        avb_memcmp(strings[n], str, str_size) == 0) {
+      return strings[n];
+    }
+  }
+  return NULL;
+}
+
+char* avb_replace(const char* str, const char* search, const char* replace) {
+  char* ret = NULL;
+  size_t ret_len = 0;
+  size_t search_len, replace_len;
+  const char* str_after_last_replace;
+
+  search_len = avb_strlen(search);
+  replace_len = avb_strlen(replace);
+
+  str_after_last_replace = str;
+  while (*str != '\0') {
+    const char* s;
+    size_t num_before;
+    size_t num_new;
+
+    s = avb_strstr(str, search);
+    if (s == NULL) {
+      break;
+    }
+
+    num_before = s - str;
+
+    if (ret == NULL) {
+      num_new = num_before + replace_len + 1;
+      ret = avb_malloc(num_new);
+      if (ret == NULL) {
+        goto out;
+      }
+      avb_memcpy(ret, str, num_before);
+      avb_memcpy(ret + num_before, replace, replace_len);
+      ret[num_new - 1] = '\0';
+      ret_len = num_new - 1;
+    } else {
+      char* new_str;
+      num_new = ret_len + num_before + replace_len + 1;
+      new_str = avb_malloc(num_new);
+      if (ret == NULL) {
+        goto out;
+      }
+      avb_memcpy(new_str, ret, ret_len);
+      avb_memcpy(new_str + ret_len, str, num_before);
+      avb_memcpy(new_str + ret_len + num_before, replace, replace_len);
+      new_str[num_new - 1] = '\0';
+      avb_free(ret);
+      ret = new_str;
+      ret_len = num_new - 1;
+    }
+
+    str = s + search_len;
+    str_after_last_replace = str;
+  }
+
+  if (ret == NULL) {
+    ret = avb_strdup(str_after_last_replace);
+    if (ret == NULL) {
+      goto out;
+    }
+  } else {
+    size_t num_remaining = avb_strlen(str_after_last_replace);
+    size_t num_new = ret_len + num_remaining + 1;
+    char* new_str = avb_malloc(num_new);
+    if (ret == NULL) {
+      goto out;
+    }
+    avb_memcpy(new_str, ret, ret_len);
+    avb_memcpy(new_str + ret_len, str_after_last_replace, num_remaining);
+    new_str[num_new - 1] = '\0';
+    avb_free(ret);
+    ret = new_str;
+    ret_len = num_new - 1;
+  }
+
+out:
+  return ret;
+}
diff --git a/avb/libavb/avb_util.h b/avb/libavb/avb_util.h
new file mode 100644
index 0000000..352829a
--- /dev/null
+++ b/avb/libavb/avb_util.h
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#if !defined(AVB_INSIDE_LIBAVB_H) && !defined(AVB_COMPILATION)
+#error "Never include this file directly, include libavb.h instead."
+#endif
+
+#ifndef AVB_UTIL_H_
+#define AVB_UTIL_H_
+
+#include "avb_sysdeps.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define AVB_STRINGIFY(x) #x
+#define AVB_TO_STRING(x) AVB_STRINGIFY(x)
+
+#ifdef AVB_ENABLE_DEBUG
+/* Aborts the program if |expr| is false.
+ *
+ * This has no effect unless AVB_ENABLE_DEBUG is defined.
+ */
+#define avb_assert(expr)                     \
+  do {                                       \
+    if (!(expr)) {                           \
+      avb_fatal("assert fail: " #expr "\n"); \
+    }                                        \
+  } while (0)
+#else
+#define avb_assert(expr)
+#endif
+
+/* Aborts the program if reached.
+ *
+ * This has no effect unless AVB_ENABLE_DEBUG is defined.
+ */
+#ifdef AVB_ENABLE_DEBUG
+#define avb_assert_not_reached()         \
+  do {                                   \
+    avb_fatal("assert_not_reached()\n"); \
+  } while (0)
+#else
+#define avb_assert_not_reached()
+#endif
+
+/* Aborts the program if |addr| is not word-aligned.
+ *
+ * This has no effect unless AVB_ENABLE_DEBUG is defined.
+ */
+#define avb_assert_aligned(addr) \
+  avb_assert((((uintptr_t)addr) & (AVB_ALIGNMENT_SIZE - 1)) == 0)
+
+#ifdef AVB_ENABLE_DEBUG
+/* Print functions, used for diagnostics.
+ *
+ * These have no effect unless AVB_ENABLE_DEBUG is defined.
+ */
+#define avb_debug(message)                                       \
+  do {                                                           \
+    avb_print(__FILE__ ":" AVB_TO_STRING(__LINE__) ": DEBUG: "); \
+    avb_print(message);                                          \
+  } while (0)
+#define avb_debugv(message, ...)                                 \
+  do {                                                           \
+    avb_print(__FILE__ ":" AVB_TO_STRING(__LINE__) ": DEBUG: "); \
+    avb_printv(message, ##__VA_ARGS__);                          \
+  } while (0)
+#else
+#define avb_debug(message)
+#define avb_debugv(message, ...)
+#endif
+
+/* Prints out a message. This is typically used if a runtime-error
+ * occurs.
+ */
+#define avb_error(message)                                       \
+  do {                                                           \
+    avb_print(__FILE__ ":" AVB_TO_STRING(__LINE__) ": ERROR: "); \
+    avb_print(message);                                          \
+  } while (0)
+#define avb_errorv(message, ...)                                 \
+  do {                                                           \
+    avb_print(__FILE__ ":" AVB_TO_STRING(__LINE__) ": ERROR: "); \
+    avb_printv(message, ##__VA_ARGS__);                          \
+  } while (0)
+
+/* Prints out a message and calls avb_abort().
+ */
+#define avb_fatal(message)                                       \
+  do {                                                           \
+    avb_print(__FILE__ ":" AVB_TO_STRING(__LINE__) ": FATAL: "); \
+    avb_print(message);                                          \
+    avb_abort();                                                 \
+  } while (0)
+#define avb_fatalv(message, ...)                                 \
+  do {                                                           \
+    avb_print(__FILE__ ":" AVB_TO_STRING(__LINE__) ": FATAL: "); \
+    avb_printv(message, ##__VA_ARGS__);                          \
+    avb_abort();                                                 \
+  } while (0)
+
+/* Converts a 32-bit unsigned integer from big-endian to host byte order. */
+uint32_t avb_be32toh(uint32_t in) AVB_ATTR_WARN_UNUSED_RESULT;
+
+/* Converts a 64-bit unsigned integer from big-endian to host byte order. */
+uint64_t avb_be64toh(uint64_t in) AVB_ATTR_WARN_UNUSED_RESULT;
+
+/* Converts a 32-bit unsigned integer from host to big-endian byte order. */
+uint32_t avb_htobe32(uint32_t in) AVB_ATTR_WARN_UNUSED_RESULT;
+
+/* Converts a 64-bit unsigned integer from host to big-endian byte order. */
+uint64_t avb_htobe64(uint64_t in) AVB_ATTR_WARN_UNUSED_RESULT;
+
+/* Compare |n| bytes starting at |s1| with |s2| and return 0 if they
+ * match, 1 if they don't.  Returns 0 if |n|==0, since no bytes
+ * mismatched.
+ *
+ * Time taken to perform the comparison is only dependent on |n| and
+ * not on the relationship of the match between |s1| and |s2|.
+ *
+ * Note that unlike avb_memcmp(), this only indicates inequality, not
+ * whether |s1| is less than or greater than |s2|.
+ */
+int avb_safe_memcmp(const void* s1,
+                    const void* s2,
+                    size_t n) AVB_ATTR_WARN_UNUSED_RESULT;
+
+/* Adds |value_to_add| to |value| with overflow protection.
+ *
+ * Returns false if the addition overflows, true otherwise. In either
+ * case, |value| is always modified.
+ */
+bool avb_safe_add_to(uint64_t* value,
+                     uint64_t value_to_add) AVB_ATTR_WARN_UNUSED_RESULT;
+
+/* Adds |a| and |b| with overflow protection, returning the value in
+ * |out_result|.
+ *
+ * It's permissible to pass NULL for |out_result| if you just want to
+ * check that the addition would not overflow.
+ *
+ * Returns false if the addition overflows, true otherwise.
+ */
+bool avb_safe_add(uint64_t* out_result,
+                  uint64_t a,
+                  uint64_t b) AVB_ATTR_WARN_UNUSED_RESULT;
+
+/* Checks if |num_bytes| data at |data| is a valid UTF-8
+ * string. Returns true if valid UTF-8, false otherwise.
+ */
+bool avb_validate_utf8(const uint8_t* data,
+                       size_t num_bytes) AVB_ATTR_WARN_UNUSED_RESULT;
+
+/* Concatenates |str1| (of |str1_len| bytes) and |str2| (of |str2_len|
+ * bytes) and puts the result in |buf| which holds |buf_size|
+ * bytes. The result is also guaranteed to be NUL terminated. Fail if
+ * there is not enough room in |buf| for the resulting string plus
+ * terminating NUL byte.
+ *
+ * Returns true if the operation succeeds, false otherwise.
+ */
+bool avb_str_concat(char* buf,
+                    size_t buf_size,
+                    const char* str1,
+                    size_t str1_len,
+                    const char* str2,
+                    size_t str2_len);
+
+/* Like avb_malloc_() but prints a error using avb_error() if memory
+ * allocation fails.
+ */
+void* avb_malloc(size_t size) AVB_ATTR_WARN_UNUSED_RESULT;
+
+/* Like avb_malloc() but sets the memory with zeroes. */
+void* avb_calloc(size_t size) AVB_ATTR_WARN_UNUSED_RESULT;
+
+/* Duplicates a NUL-terminated string. Returns NULL on OOM. */
+char* avb_strdup(const char* str) AVB_ATTR_WARN_UNUSED_RESULT;
+
+/* Finds the first occurrence of |needle| in the string |haystack|
+ * where both strings are NUL-terminated strings. The terminating NUL
+ * bytes are not compared.
+ *
+ * Returns NULL if not found, otherwise points into |haystack| for the
+ * first occurrence of |needle|.
+ */
+const char* avb_strstr(const char* haystack,
+                       const char* needle) AVB_ATTR_WARN_UNUSED_RESULT;
+
+/* Finds the first occurrence of |str| in the NULL-terminated string
+ * array |strings|. Each element in |strings| must be
+ * NUL-terminated. The string given by |str| need not be
+ * NUL-terminated but its size must be given in |str_size|.
+ *
+ * Returns NULL if not found, otherwise points into |strings| for the
+ * first occurrence of |str|.
+ */
+const char* avb_strv_find_str(const char* const* strings,
+                              const char* str,
+                              size_t str_size);
+
+/* Replaces all occurrences of |search| with |replace| in |str|.
+ *
+ * Returns a newly allocated string or NULL if out of memory.
+ */
+char* avb_replace(const char* str,
+                  const char* search,
+                  const char* replace) AVB_ATTR_WARN_UNUSED_RESULT;
+
+/* Calculates the CRC-32 for data in |buf| of size |buf_size|. */
+uint32_t avb_crc32(const uint8_t* buf, size_t buf_size);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* AVB_UTIL_H_ */
diff --git a/avb/libavb/avb_vbmeta_image.c b/avb/libavb/avb_vbmeta_image.c
new file mode 100644
index 0000000..61b1eda
--- /dev/null
+++ b/avb/libavb/avb_vbmeta_image.c
@@ -0,0 +1,294 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "avb_vbmeta_image.h"
+#include "avb_crypto.h"
+#include "avb_rsa.h"
+#include "avb_sha.h"
+#include "avb_util.h"
+
+AvbVBMetaVerifyResult avb_vbmeta_image_verify(
+    const uint8_t* data,
+    size_t length,
+    const uint8_t** out_public_key_data,
+    size_t* out_public_key_length) {
+  AvbVBMetaVerifyResult ret;
+  AvbVBMetaImageHeader h;
+  uint8_t* computed_hash;
+  const AvbAlgorithmData* algorithm;
+  AvbSHA256Ctx sha256_ctx;
+  AvbSHA512Ctx sha512_ctx;
+  const uint8_t* header_block;
+  const uint8_t* authentication_block;
+  const uint8_t* auxiliary_block;
+  int verification_result;
+
+  ret = AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER;
+
+  if (out_public_key_data != NULL) {
+    *out_public_key_data = NULL;
+  }
+  if (out_public_key_length != NULL) {
+    *out_public_key_length = 0;
+  }
+
+  /* Ensure magic is correct. */
+  if (avb_safe_memcmp(data, AVB_MAGIC, AVB_MAGIC_LEN) != 0) {
+    avb_error("Magic is incorrect.\n");
+    goto out;
+  }
+
+  /* Before we byteswap, ensure length is long enough. */
+  if (length < sizeof(AvbVBMetaImageHeader)) {
+    avb_error("Length is smaller than header.\n");
+    goto out;
+  }
+  avb_vbmeta_image_header_to_host_byte_order((const AvbVBMetaImageHeader*)data,
+                                             &h);
+
+  /* Ensure we don't attempt to access any fields if the major version
+   * is not supported.
+   */
+  if (h.header_version_major > AVB_MAJOR_VERSION) {
+    avb_error("No support for given major version.\n");
+    goto out;
+  }
+
+  /* Ensure inner block sizes are multiple of 64. */
+  if ((h.authentication_data_block_size & 0x3f) != 0 ||
+      (h.auxiliary_data_block_size & 0x3f) != 0) {
+    avb_error("Block size is not a multiple of 64.\n");
+    goto out;
+  }
+
+  /* Ensure block sizes all add up to at most |length|. */
+  uint64_t block_total = sizeof(AvbVBMetaImageHeader);
+  if (!avb_safe_add_to(&block_total, h.authentication_data_block_size) ||
+      !avb_safe_add_to(&block_total, h.auxiliary_data_block_size)) {
+    avb_error("Overflow while computing size of boot image.\n");
+    goto out;
+  }
+  if (block_total > length) {
+    avb_error("Block sizes add up to more than given length.\n");
+    goto out;
+  }
+
+  uintptr_t data_ptr = (uintptr_t)data;
+  /* Ensure passed in memory doesn't wrap. */
+  if (!avb_safe_add(NULL, (uint64_t)data_ptr, length)) {
+    avb_error("Boot image location and length mismatch.\n");
+    goto out;
+  }
+
+  /* Ensure hash and signature are entirely in the Authentication data block. */
+  uint64_t hash_end;
+  if (!avb_safe_add(&hash_end, h.hash_offset, h.hash_size) ||
+      hash_end > h.authentication_data_block_size) {
+    avb_error("Hash is not entirely in its block.\n");
+    goto out;
+  }
+  uint64_t signature_end;
+  if (!avb_safe_add(&signature_end, h.signature_offset, h.signature_size) ||
+      signature_end > h.authentication_data_block_size) {
+    avb_error("Signature is not entirely in its block.\n");
+    goto out;
+  }
+
+  /* Ensure public key is entirely in the Auxiliary data block. */
+  uint64_t pubkey_end;
+  if (!avb_safe_add(&pubkey_end, h.public_key_offset, h.public_key_size) ||
+      pubkey_end > h.auxiliary_data_block_size) {
+    avb_error("Public key is not entirely in its block.\n");
+    goto out;
+  }
+
+  /* Ensure public key metadata (if set) is entirely in the Auxiliary
+   * data block. */
+  if (h.public_key_metadata_size > 0) {
+    uint64_t pubkey_md_end;
+    if (!avb_safe_add(&pubkey_md_end,
+                      h.public_key_metadata_offset,
+                      h.public_key_metadata_size) ||
+        pubkey_md_end > h.auxiliary_data_block_size) {
+      avb_error("Public key metadata is not entirely in its block.\n");
+      goto out;
+    }
+  }
+
+  /* Bail early if there's no hash or signature. */
+  if (h.algorithm_type == AVB_ALGORITHM_TYPE_NONE) {
+    ret = AVB_VBMETA_VERIFY_RESULT_OK_NOT_SIGNED;
+    goto out;
+  }
+
+  /* Ensure algorithm field is supported. */
+  algorithm = avb_get_algorithm_data(h.algorithm_type);
+  if (!algorithm) {
+    avb_error("Invalid or unknown algorithm.\n");
+    goto out;
+  }
+
+  /* Bail if the embedded hash size doesn't match the chosen algorithm. */
+  if (h.hash_size != algorithm->hash_len) {
+    avb_error("Embedded hash has wrong size.\n");
+    goto out;
+  }
+
+  /* No overflow checks needed from here-on after since all block
+   * sizes and offsets have been verified above.
+   */
+
+  header_block = data;
+  authentication_block = header_block + sizeof(AvbVBMetaImageHeader);
+  auxiliary_block = authentication_block + h.authentication_data_block_size;
+
+  switch (h.algorithm_type) {
+    /* Explicit fall-through: */
+    case AVB_ALGORITHM_TYPE_SHA256_RSA2048:
+    case AVB_ALGORITHM_TYPE_SHA256_RSA4096:
+    case AVB_ALGORITHM_TYPE_SHA256_RSA8192:
+      avb_sha256_init(&sha256_ctx);
+      avb_sha256_update(
+          &sha256_ctx, header_block, sizeof(AvbVBMetaImageHeader));
+      avb_sha256_update(
+          &sha256_ctx, auxiliary_block, h.auxiliary_data_block_size);
+      computed_hash = avb_sha256_final(&sha256_ctx);
+      break;
+    /* Explicit fall-through: */
+    case AVB_ALGORITHM_TYPE_SHA512_RSA2048:
+    case AVB_ALGORITHM_TYPE_SHA512_RSA4096:
+    case AVB_ALGORITHM_TYPE_SHA512_RSA8192:
+      avb_sha512_init(&sha512_ctx);
+      avb_sha512_update(
+          &sha512_ctx, header_block, sizeof(AvbVBMetaImageHeader));
+      avb_sha512_update(
+          &sha512_ctx, auxiliary_block, h.auxiliary_data_block_size);
+      computed_hash = avb_sha512_final(&sha512_ctx);
+      break;
+    default:
+      avb_error("Unknown algorithm.\n");
+      goto out;
+  }
+
+  if (avb_safe_memcmp(authentication_block + h.hash_offset,
+                      computed_hash,
+                      h.hash_size) != 0) {
+    avb_error("Hash does not match!\n");
+    ret = AVB_VBMETA_VERIFY_RESULT_HASH_MISMATCH;
+    goto out;
+  }
+
+  verification_result =
+      avb_rsa_verify(auxiliary_block + h.public_key_offset,
+                     h.public_key_size,
+                     authentication_block + h.signature_offset,
+                     h.signature_size,
+                     authentication_block + h.hash_offset,
+                     h.hash_size,
+                     algorithm->padding,
+                     algorithm->padding_len);
+
+  if (verification_result == 0) {
+    ret = AVB_VBMETA_VERIFY_RESULT_SIGNATURE_MISMATCH;
+    goto out;
+  }
+
+  if (h.public_key_size > 0) {
+    if (out_public_key_data != NULL) {
+      *out_public_key_data = auxiliary_block + h.public_key_offset;
+    }
+    if (out_public_key_length != NULL) {
+      *out_public_key_length = h.public_key_size;
+    }
+  }
+
+  ret = AVB_VBMETA_VERIFY_RESULT_OK;
+
+out:
+  return ret;
+}
+
+void avb_vbmeta_image_header_to_host_byte_order(const AvbVBMetaImageHeader* src,
+                                                AvbVBMetaImageHeader* dest) {
+  avb_memcpy(dest, src, sizeof(AvbVBMetaImageHeader));
+
+  dest->header_version_major = avb_be32toh(dest->header_version_major);
+  dest->header_version_minor = avb_be32toh(dest->header_version_minor);
+
+  dest->authentication_data_block_size =
+      avb_be64toh(dest->authentication_data_block_size);
+  dest->auxiliary_data_block_size =
+      avb_be64toh(dest->auxiliary_data_block_size);
+
+  dest->algorithm_type = avb_be32toh(dest->algorithm_type);
+
+  dest->hash_offset = avb_be64toh(dest->hash_offset);
+  dest->hash_size = avb_be64toh(dest->hash_size);
+
+  dest->signature_offset = avb_be64toh(dest->signature_offset);
+  dest->signature_size = avb_be64toh(dest->signature_size);
+
+  dest->public_key_offset = avb_be64toh(dest->public_key_offset);
+  dest->public_key_size = avb_be64toh(dest->public_key_size);
+
+  dest->public_key_metadata_offset =
+      avb_be64toh(dest->public_key_metadata_offset);
+  dest->public_key_metadata_size = avb_be64toh(dest->public_key_metadata_size);
+
+  dest->descriptors_offset = avb_be64toh(dest->descriptors_offset);
+  dest->descriptors_size = avb_be64toh(dest->descriptors_size);
+
+  dest->rollback_index = avb_be64toh(dest->rollback_index);
+  dest->flags = avb_be32toh(dest->flags);
+}
+
+const char* avb_vbmeta_verify_result_to_string(AvbVBMetaVerifyResult result) {
+  const char* ret = NULL;
+
+  switch (result) {
+    case AVB_VBMETA_VERIFY_RESULT_OK:
+      ret = "OK";
+      break;
+    case AVB_VBMETA_VERIFY_RESULT_OK_NOT_SIGNED:
+      ret = "OK_NOT_SIGNED";
+      break;
+    case AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER:
+      ret = "INVALID_VBMETA_HEADER";
+      break;
+    case AVB_VBMETA_VERIFY_RESULT_HASH_MISMATCH:
+      ret = "HASH_MISMATCH";
+      break;
+    case AVB_VBMETA_VERIFY_RESULT_SIGNATURE_MISMATCH:
+      ret = "SIGNATURE_MISMATCH";
+      break;
+      /* Do not add a 'default:' case here because of -Wswitch. */
+  }
+
+  if (ret == NULL) {
+    avb_error("Unknown AvbVBMetaVerifyResult value.\n");
+    ret = "(unknown)";
+  }
+
+  return ret;
+}
diff --git a/avb/libavb/avb_vbmeta_image.h b/avb/libavb/avb_vbmeta_image.h
new file mode 100644
index 0000000..832773a
--- /dev/null
+++ b/avb/libavb/avb_vbmeta_image.h
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#if !defined(AVB_INSIDE_LIBAVB_H) && !defined(AVB_COMPILATION)
+#error "Never include this file directly, include libavb.h instead."
+#endif
+
+#ifndef AVB_VBMETA_IMAGE_H_
+#define AVB_VBMETA_IMAGE_H_
+
+#include "avb_sysdeps.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "avb_crypto.h"
+#include "avb_descriptor.h"
+
+/* Size of the vbmeta image header. */
+#define AVB_VBMETA_IMAGE_HEADER_SIZE 256
+
+/* Magic for the vbmeta image header. */
+#define AVB_MAGIC "AVB0"
+#define AVB_MAGIC_LEN 4
+
+/* The current MAJOR and MINOR versions used - keep in sync with avbtool. */
+#define AVB_MAJOR_VERSION 1
+#define AVB_MINOR_VERSION 0
+
+/* Flags for the vbmeta image.
+ *
+ * AVB_VBMETA_IMAGE_FLAGS_HASHTREE_DISABLED: If this flag is set,
+ * hashtree image verification will be disabled.
+ */
+typedef enum {
+  AVB_VBMETA_IMAGE_FLAGS_HASHTREE_DISABLED = (1 << 0)
+} AvbVBMetaImageFlags;
+
+/* Binary format for header of the vbmeta image.
+ *
+ * The vbmeta image consists of three blocks:
+ *
+ *  +-----------------------------------------+
+ *  | Header data - fixed size                |
+ *  +-----------------------------------------+
+ *  | Authentication data - variable size     |
+ *  +-----------------------------------------+
+ *  | Auxiliary data - variable size          |
+ *  +-----------------------------------------+
+ *
+ * The "Header data" block is described by this struct and is always
+ * |AVB_VBMETA_IMAGE_HEADER_SIZE| bytes long.
+ *
+ * The "Authentication data" block is |authentication_data_block_size|
+ * bytes long and contains the hash and signature used to authenticate
+ * the vbmeta image. The type of the hash and signature is defined by
+ * the |algorithm_type| field.
+ *
+ * The "Auxiliary data" is |auxiliary_data_block_size| bytes long and
+ * contains the auxiliary data including the public key used to make
+ * the signature and descriptors.
+ *
+ * The public key is at offset |public_key_offset| with size
+ * |public_key_size| in this block. The size of the public key data is
+ * defined by the |algorithm_type| field. The format of the public key
+ * data is described in the |AvbRSAPublicKeyHeader| struct.
+ *
+ * The descriptors starts at |descriptors_offset| from the beginning
+ * of the "Auxiliary Data" block and take up |descriptors_size|
+ * bytes. Each descriptor is stored as a |AvbDescriptor| with tag and
+ * number of bytes following. The number of descriptors can be
+ * determined by walking this data until |descriptors_size| is
+ * exhausted.
+ *
+ * The size of each of the "Authentication data" and "Auxiliary data"
+ * blocks must be divisible by 64. This is to ensure proper alignment.
+ *
+ * Descriptors are free-form blocks stored in a part of the vbmeta
+ * image subject to the same integrity checks as the rest of the
+ * image. See the documentation for |AvbDescriptor| for well-known
+ * descriptors. See avb_descriptor_foreach() for a convenience
+ * function to iterate over descriptors.
+ *
+ * This struct is versioned, see the |header_version_major| and
+ * |header_version_minor| fields. Compatibility is guaranteed only
+ * within the same major version.
+ *
+ * All fields are stored in network byte order when serialized. To
+ * generate a copy with fields swapped to native byte order, use the
+ * function avb_vbmeta_image_header_to_host_byte_order().
+ *
+ * Before reading and/or using any of this data, you MUST verify it
+ * using avb_vbmeta_image_verify() and reject it unless it's signed by
+ * a known good public key.
+ */
+typedef struct AvbVBMetaImageHeader {
+  /*   0: Four bytes equal to "AVB0" (AVB_MAGIC). */
+  uint8_t magic[AVB_MAGIC_LEN];
+  /*   4: The major version of the vbmeta image header. */
+  uint32_t header_version_major;
+  /*   8: The minor version of the vbmeta image header. */
+  uint32_t header_version_minor;
+
+  /*  12: The size of the signature block. */
+  uint64_t authentication_data_block_size;
+  /*  20: The size of the auxiliary data block. */
+  uint64_t auxiliary_data_block_size;
+
+  /*  28: The verification algorithm used, see |AvbAlgorithmType| enum. */
+  uint32_t algorithm_type;
+
+  /*  32: Offset into the "Authentication data" block of hash data. */
+  uint64_t hash_offset;
+  /*  40: Length of the hash data. */
+  uint64_t hash_size;
+
+  /*  48: Offset into the "Authentication data" block of signature data. */
+  uint64_t signature_offset;
+  /*  56: Length of the signature data. */
+  uint64_t signature_size;
+
+  /*  64: Offset into the "Auxiliary data" block of public key data. */
+  uint64_t public_key_offset;
+  /*  72: Length of the public key data. */
+  uint64_t public_key_size;
+
+  /*  80: Offset into the "Auxiliary data" block of public key metadata. */
+  uint64_t public_key_metadata_offset;
+  /*  88: Length of the public key metadata. Must be set to zero if there
+   *  is no public key metadata.
+   */
+  uint64_t public_key_metadata_size;
+
+  /*  96: Offset into the "Auxiliary data" block of descriptor data. */
+  uint64_t descriptors_offset;
+  /* 104: Length of descriptor data. */
+  uint64_t descriptors_size;
+
+  /* 112: The rollback index which can be used to prevent rollback to
+   *  older versions.
+   */
+  uint64_t rollback_index;
+
+  /* 120: Flags from the AvbVBMetaImageFlags enumeration. This must be
+   * set to zero if the vbmeta image is not a top-level image.
+   */
+  uint32_t flags;
+
+  /* 124: Padding to ensure struct is size AVB_VBMETA_IMAGE_HEADER_SIZE
+   * bytes. This must be set to zeroes.
+   */
+  uint8_t reserved[132];
+} AVB_ATTR_PACKED AvbVBMetaImageHeader;
+
+/* Copies |src| to |dest|, byte-swapping fields in the process.
+ *
+ * Make sure you've verified |src| using avb_vbmeta_image_verify()
+ * before accessing the data and/or using this function.
+ */
+void avb_vbmeta_image_header_to_host_byte_order(const AvbVBMetaImageHeader* src,
+                                                AvbVBMetaImageHeader* dest);
+
+/* Return codes used in avb_vbmeta_image_verify().
+ *
+ * AVB_VBMETA_VERIFY_RESULT_OK is returned if the vbmeta image header
+ * is valid, the hash is correct and the signature is correct. Keep in
+ * mind that you still need to check that you know the public key used
+ * to sign the image, see avb_vbmeta_image_verify() for details.
+ *
+ * AVB_VBMETA_VERIFY_RESULT_OK_NOT_SIGNED is returned if the vbmeta
+ * image header is valid but there is no signature or hash.
+ *
+ * AVB_VERIFY_INVALID_VBMETA_HEADER is returned if the header of
+ * the vbmeta image is invalid, for example, invalid magic or
+ * inconsistent data.
+ *
+ * AVB_VERIFY_HASH_MISMATCH is returned if the hash stored in the
+ * "Authentication data" block does not match the calculated hash.
+ *
+ * AVB_VERIFY_SIGNATURE_MISMATCH is returned if the signature stored
+ * in the "Authentication data" block is invalid or doesn't match the
+ * public key stored in the vbmeta image.
+ */
+typedef enum {
+  AVB_VBMETA_VERIFY_RESULT_OK,
+  AVB_VBMETA_VERIFY_RESULT_OK_NOT_SIGNED,
+  AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER,
+  AVB_VBMETA_VERIFY_RESULT_HASH_MISMATCH,
+  AVB_VBMETA_VERIFY_RESULT_SIGNATURE_MISMATCH,
+} AvbVBMetaVerifyResult;
+
+/* Get a textual representation of |result|. */
+const char* avb_vbmeta_verify_result_to_string(AvbVBMetaVerifyResult result);
+
+/* Checks that vbmeta image at |data| of size |length| is a valid
+ * vbmeta image. The complete contents of the vbmeta image must be
+ * passed in. It's fine if |length| is bigger than the actual image,
+ * typically callers of this function will load the entire contents of
+ * the 'vbmeta_a' or 'vbmeta_b' partition and pass in its length (for
+ * example, 1 MiB).
+ *
+ * See the |AvbVBMetaImageHeader| struct for information about the
+ * three blocks (header, authentication, auxiliary) that make up a
+ * vbmeta image.
+ *
+ * If the function returns |AVB_VBMETA_VERIFY_RESULT_OK| and
+ * |out_public_key_data| is non-NULL, it will be set to point inside
+ * |data| for where the serialized public key data is stored and
+ * |out_public_key_length|, if non-NULL, will be set to the length of
+ * the public key data. If there is no public key in the metadata then
+ * |out_public_key_data| is set to NULL.
+ *
+ * See the |AvbVBMetaVerifyResult| enum for possible return values.
+ *
+ * VERY IMPORTANT:
+ *
+ *   1. Even if |AVB_VBMETA_VERIFY_RESULT_OK| is returned, you still
+ *      need to check that the public key embedded in the image
+ *      matches a known key! You can use 'avbtool extract_public_key'
+ *      to extract the key (at build time, then store it along your
+ *      code) and compare it to what is returned in
+ *      |out_public_key_data|.
+ *
+ *   2. You need to check the |rollback_index| field against a stored
+ *      value in NVRAM and reject the vbmeta image if the value in
+ *      NVRAM is bigger than |rollback_index|. You must also update
+ *      the value stored in NVRAM to the smallest value of
+ *      |rollback_index| field from boot images in all bootable and
+ *      authentic slots marked as GOOD.
+ *
+ * This is a low-level function to only verify the vbmeta data - you
+ * are likely looking for avb_slot_verify() instead for verifying
+ * integrity data for a whole set of partitions.
+ */
+AvbVBMetaVerifyResult avb_vbmeta_image_verify(
+    const uint8_t* data,
+    size_t length,
+    const uint8_t** out_public_key_data,
+    size_t* out_public_key_length) AVB_ATTR_WARN_UNUSED_RESULT;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* AVB_VBMETA_IMAGE_H_ */
diff --git a/avb/libavb/libavb.h b/avb/libavb/libavb.h
new file mode 100644
index 0000000..b74307c
--- /dev/null
+++ b/avb/libavb/libavb.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef LIBAVB_H_
+#define LIBAVB_H_
+
+/* The AVB_INSIDE_LIBAVB_H preprocessor symbol is used to enforce
+ * library users to include only this file. All public interfaces, and
+ * only public interfaces, must be included here.
+ */
+
+#define AVB_INSIDE_LIBAVB_H
+#include "avb_chain_partition_descriptor.h"
+#include "avb_crypto.h"
+#include "avb_descriptor.h"
+#include "avb_footer.h"
+#include "avb_hash_descriptor.h"
+#include "avb_hashtree_descriptor.h"
+#include "avb_kernel_cmdline_descriptor.h"
+#include "avb_ops.h"
+#include "avb_property_descriptor.h"
+#include "avb_slot_verify.h"
+#include "avb_sysdeps.h"
+#include "avb_util.h"
+#include "avb_vbmeta_image.h"
+#undef AVB_INSIDE_LIBAVB_H
+
+#endif /* LIBAVB_H_ */
diff --git a/avb/libavb_ab/avb_ab_flow.c b/avb/libavb_ab/avb_ab_flow.c
new file mode 100644
index 0000000..006e617
--- /dev/null
+++ b/avb/libavb_ab/avb_ab_flow.c
@@ -0,0 +1,515 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "avb_ab_flow.h"
+
+bool avb_ab_data_verify_and_byteswap(const AvbABData* src, AvbABData* dest) {
+  /* Ensure magic is correct. */
+  if (avb_safe_memcmp(src->magic, AVB_AB_MAGIC, AVB_AB_MAGIC_LEN) != 0) {
+    avb_error("Magic is incorrect.\n");
+    return false;
+  }
+
+  avb_memcpy(dest, src, sizeof(AvbABData));
+  dest->crc32 = avb_be32toh(dest->crc32);
+
+  /* Ensure we don't attempt to access any fields if the major version
+   * is not supported.
+   */
+  if (dest->version_major > AVB_AB_MAJOR_VERSION) {
+    avb_error("No support for given major version.\n");
+    return false;
+  }
+
+  /* Bail if CRC32 doesn't match. */
+  if (dest->crc32 !=
+      avb_crc32((const uint8_t*)dest, sizeof(AvbABData) - sizeof(uint32_t))) {
+    avb_error("CRC32 does not match.\n");
+    return false;
+  }
+
+  return true;
+}
+
+void avb_ab_data_update_crc_and_byteswap(const AvbABData* src,
+                                         AvbABData* dest) {
+  avb_memcpy(dest, src, sizeof(AvbABData));
+  dest->crc32 = avb_htobe32(
+      avb_crc32((const uint8_t*)dest, sizeof(AvbABData) - sizeof(uint32_t)));
+}
+
+void avb_ab_data_init(AvbABData* data) {
+  avb_memset(data, '\0', sizeof(AvbABData));
+  avb_memcpy(data->magic, AVB_AB_MAGIC, AVB_AB_MAGIC_LEN);
+  data->version_major = AVB_AB_MAJOR_VERSION;
+  data->version_minor = AVB_AB_MINOR_VERSION;
+  data->slots[0].priority = AVB_AB_MAX_PRIORITY;
+  data->slots[0].tries_remaining = AVB_AB_MAX_TRIES_REMAINING;
+  data->slots[0].successful_boot = 0;
+  data->slots[1].priority = AVB_AB_MAX_PRIORITY - 1;
+  data->slots[1].tries_remaining = AVB_AB_MAX_TRIES_REMAINING;
+  data->slots[1].successful_boot = 0;
+}
+
+/* The AvbABData struct is stored 2048 bytes into the 'misc' partition
+ * following the 'struct bootloader_message' field. The struct is
+ * compatible with the guidelines in bootable/recovery/bootloader.h -
+ * e.g. it is stored in the |slot_suffix| field, starts with a
+ * NUL-byte, and is 32 bytes long.
+ */
+#define AB_METADATA_MISC_PARTITION_OFFSET 2048
+
+AvbIOResult avb_ab_data_read(AvbABOps* ab_ops, AvbABData* data) {
+  AvbOps* ops = ab_ops->ops;
+  AvbABData serialized;
+  AvbIOResult io_ret;
+  size_t num_bytes_read;
+
+  io_ret = ops->read_from_partition(ops,
+                                    "misc",
+                                    AB_METADATA_MISC_PARTITION_OFFSET,
+                                    sizeof(AvbABData),
+                                    &serialized,
+                                    &num_bytes_read);
+  if (io_ret == AVB_IO_RESULT_ERROR_OOM) {
+    return AVB_IO_RESULT_ERROR_OOM;
+  } else if (io_ret != AVB_IO_RESULT_OK ||
+             num_bytes_read != sizeof(AvbABData)) {
+    avb_error("Error reading A/B metadata.\n");
+    return AVB_IO_RESULT_ERROR_IO;
+  }
+
+  if (!avb_ab_data_verify_and_byteswap(&serialized, data)) {
+    avb_error(
+        "Error validating A/B metadata from disk. "
+        "Resetting and writing new A/B metadata to disk.\n");
+    avb_ab_data_init(data);
+    return avb_ab_data_write(ab_ops, data);
+  }
+
+  return AVB_IO_RESULT_OK;
+}
+
+AvbIOResult avb_ab_data_write(AvbABOps* ab_ops, const AvbABData* data) {
+  AvbOps* ops = ab_ops->ops;
+  AvbABData serialized;
+  AvbIOResult io_ret;
+
+  avb_ab_data_update_crc_and_byteswap(data, &serialized);
+  io_ret = ops->write_to_partition(ops,
+                                   "misc",
+                                   AB_METADATA_MISC_PARTITION_OFFSET,
+                                   sizeof(AvbABData),
+                                   &serialized);
+  if (io_ret == AVB_IO_RESULT_ERROR_OOM) {
+    return AVB_IO_RESULT_ERROR_OOM;
+  } else if (io_ret != AVB_IO_RESULT_OK) {
+    avb_error("Error writing A/B metadata.\n");
+    return AVB_IO_RESULT_ERROR_IO;
+  }
+  return AVB_IO_RESULT_OK;
+}
+
+static bool slot_is_bootable(AvbABSlotData* slot) {
+  return slot->priority > 0 &&
+         (slot->successful_boot || (slot->tries_remaining > 0));
+}
+
+static void slot_set_unbootable(AvbABSlotData* slot) {
+  slot->priority = 0;
+  slot->tries_remaining = 0;
+  slot->successful_boot = 0;
+}
+
+/* Ensure all unbootable and/or illegal states are marked as the
+ * canonical 'unbootable' state, e.g. priority=0, tries_remaining=0,
+ * and successful_boot=0.
+ */
+static void slot_normalize(AvbABSlotData* slot) {
+  if (slot->priority > 0) {
+    if (slot->tries_remaining == 0 && !slot->successful_boot) {
+      /* We've exhausted all tries -> unbootable. */
+      slot_set_unbootable(slot);
+    }
+    if (slot->tries_remaining > 0 && slot->successful_boot) {
+      /* Illegal state - avb_ab_mark_slot_successful() will clear
+       * tries_remaining when setting successful_boot.
+       */
+      slot_set_unbootable(slot);
+    }
+  } else {
+    slot_set_unbootable(slot);
+  }
+}
+
+static const char* slot_suffixes[2] = {"_a", "_b"};
+
+/* Helper function to load metadata - returns AVB_IO_RESULT_OK on
+ * success, error code otherwise.
+ */
+static AvbIOResult load_metadata(AvbABOps* ab_ops,
+                                 AvbABData* ab_data,
+                                 AvbABData* ab_data_orig) {
+  AvbIOResult io_ret;
+
+  io_ret = ab_ops->read_ab_metadata(ab_ops, ab_data);
+  if (io_ret != AVB_IO_RESULT_OK) {
+    avb_error("I/O error while loading A/B metadata.\n");
+    return io_ret;
+  }
+  *ab_data_orig = *ab_data;
+
+  /* Ensure data is normalized, e.g. illegal states will be marked as
+   * unbootable and all unbootable states are represented with
+   * (priority=0, tries_remaining=0, successful_boot=0).
+   */
+  slot_normalize(&ab_data->slots[0]);
+  slot_normalize(&ab_data->slots[1]);
+  return AVB_IO_RESULT_OK;
+}
+
+/* Writes A/B metadata to disk only if it has changed - returns
+ * AVB_IO_RESULT_OK on success, error code otherwise.
+ */
+static AvbIOResult save_metadata_if_changed(AvbABOps* ab_ops,
+                                            AvbABData* ab_data,
+                                            AvbABData* ab_data_orig) {
+  if (avb_safe_memcmp(ab_data, ab_data_orig, sizeof(AvbABData)) != 0) {
+    avb_debug("Writing A/B metadata to disk.\n");
+    return ab_ops->write_ab_metadata(ab_ops, ab_data);
+  }
+  return AVB_IO_RESULT_OK;
+}
+
+AvbABFlowResult avb_ab_flow(AvbABOps* ab_ops,
+                            const char* const* requested_partitions,
+                            bool allow_verification_error,
+                            AvbSlotVerifyData** out_data) {
+  AvbOps* ops = ab_ops->ops;
+  AvbSlotVerifyData* slot_data[2] = {NULL, NULL};
+  AvbSlotVerifyData* data = NULL;
+  AvbABFlowResult ret;
+  AvbABData ab_data, ab_data_orig;
+  size_t slot_index_to_boot, n;
+  AvbIOResult io_ret;
+  bool saw_and_allowed_verification_error = false;
+
+  io_ret = load_metadata(ab_ops, &ab_data, &ab_data_orig);
+  if (io_ret == AVB_IO_RESULT_ERROR_OOM) {
+    ret = AVB_AB_FLOW_RESULT_ERROR_OOM;
+    goto out;
+  } else if (io_ret != AVB_IO_RESULT_OK) {
+    ret = AVB_AB_FLOW_RESULT_ERROR_IO;
+    goto out;
+  }
+
+  /* Validate all bootable slots. */
+  for (n = 0; n < 2; n++) {
+    if (slot_is_bootable(&ab_data.slots[n])) {
+      AvbSlotVerifyResult verify_result;
+      bool set_slot_unbootable = false;
+
+      verify_result = avb_slot_verify(ops,
+                                      requested_partitions,
+                                      slot_suffixes[n],
+                                      allow_verification_error,
+                                      &slot_data[n]);
+      switch (verify_result) {
+        case AVB_SLOT_VERIFY_RESULT_ERROR_OOM:
+          ret = AVB_AB_FLOW_RESULT_ERROR_OOM;
+          goto out;
+
+        case AVB_SLOT_VERIFY_RESULT_ERROR_IO:
+          ret = AVB_AB_FLOW_RESULT_ERROR_IO;
+          goto out;
+
+        case AVB_SLOT_VERIFY_RESULT_OK:
+          break;
+
+        case AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA:
+          /* Even with |allow_verification_error| this is game over. */
+          set_slot_unbootable = true;
+          break;
+
+        /* explicit fallthrough. */
+        case AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION:
+        case AVB_SLOT_VERIFY_RESULT_ERROR_ROLLBACK_INDEX:
+        case AVB_SLOT_VERIFY_RESULT_ERROR_PUBLIC_KEY_REJECTED:
+          if (allow_verification_error) {
+            /* Do nothing since we allow this. */
+            avb_debugv("Allowing slot ",
+                       slot_suffixes[n],
+                       " which verified "
+                       "with result ",
+                       avb_slot_verify_result_to_string(verify_result),
+                       " because |allow_verification_error| is true.\n",
+                       NULL);
+            saw_and_allowed_verification_error = true;
+          } else {
+            set_slot_unbootable = true;
+          }
+          break;
+      }
+
+      if (set_slot_unbootable) {
+        avb_errorv("Error verifying slot ",
+                   slot_suffixes[n],
+                   " with result ",
+                   avb_slot_verify_result_to_string(verify_result),
+                   " - setting unbootable.\n",
+                   NULL);
+        slot_set_unbootable(&ab_data.slots[n]);
+      }
+    }
+  }
+
+  if (slot_is_bootable(&ab_data.slots[0]) &&
+      slot_is_bootable(&ab_data.slots[1])) {
+    if (ab_data.slots[1].priority > ab_data.slots[0].priority) {
+      slot_index_to_boot = 1;
+    } else {
+      slot_index_to_boot = 0;
+    }
+  } else if (slot_is_bootable(&ab_data.slots[0])) {
+    slot_index_to_boot = 0;
+  } else if (slot_is_bootable(&ab_data.slots[1])) {
+    slot_index_to_boot = 1;
+  } else {
+    /* No bootable slots! */
+    avb_error("No bootable slots found.\n");
+    ret = AVB_AB_FLOW_RESULT_ERROR_NO_BOOTABLE_SLOTS;
+    goto out;
+  }
+
+  /* Update stored rollback index such that the stored rollback index
+   * is the largest value supporting all currently bootable slots. Do
+   * this for every rollback index location.
+   */
+  for (n = 0; n < AVB_MAX_NUMBER_OF_ROLLBACK_INDEX_LOCATIONS; n++) {
+    uint64_t rollback_index_value = 0;
+
+    if (slot_data[0] != NULL && slot_data[1] != NULL) {
+      uint64_t a_rollback_index = slot_data[0]->rollback_indexes[n];
+      uint64_t b_rollback_index = slot_data[1]->rollback_indexes[n];
+      rollback_index_value =
+          (a_rollback_index < b_rollback_index ? a_rollback_index
+                                               : b_rollback_index);
+    } else if (slot_data[0] != NULL) {
+      rollback_index_value = slot_data[0]->rollback_indexes[n];
+    } else if (slot_data[1] != NULL) {
+      rollback_index_value = slot_data[1]->rollback_indexes[n];
+    }
+
+    if (rollback_index_value != 0) {
+      uint64_t current_rollback_index_value;
+      io_ret = ops->read_rollback_index(ops, n, &current_rollback_index_value);
+      if (io_ret == AVB_IO_RESULT_ERROR_OOM) {
+        ret = AVB_AB_FLOW_RESULT_ERROR_OOM;
+        goto out;
+      } else if (io_ret != AVB_IO_RESULT_OK) {
+        avb_error("Error getting rollback index for slot.\n");
+        ret = AVB_AB_FLOW_RESULT_ERROR_IO;
+        goto out;
+      }
+      if (current_rollback_index_value != rollback_index_value) {
+        io_ret = ops->write_rollback_index(ops, n, rollback_index_value);
+        if (io_ret == AVB_IO_RESULT_ERROR_OOM) {
+          ret = AVB_AB_FLOW_RESULT_ERROR_OOM;
+          goto out;
+        } else if (io_ret != AVB_IO_RESULT_OK) {
+          avb_error("Error setting stored rollback index.\n");
+          ret = AVB_AB_FLOW_RESULT_ERROR_IO;
+          goto out;
+        }
+      }
+    }
+  }
+
+  /* Finally, select this slot. */
+  avb_assert(slot_data[slot_index_to_boot] != NULL);
+  data = slot_data[slot_index_to_boot];
+  slot_data[slot_index_to_boot] = NULL;
+  if (saw_and_allowed_verification_error) {
+    avb_assert(allow_verification_error);
+    ret = AVB_AB_FLOW_RESULT_OK_WITH_VERIFICATION_ERROR;
+  } else {
+    ret = AVB_AB_FLOW_RESULT_OK;
+  }
+
+  /* ... and decrement tries remaining, if applicable. */
+  if (!ab_data.slots[slot_index_to_boot].successful_boot &&
+      ab_data.slots[slot_index_to_boot].tries_remaining > 0) {
+    ab_data.slots[slot_index_to_boot].tries_remaining -= 1;
+  }
+
+out:
+  io_ret = save_metadata_if_changed(ab_ops, &ab_data, &ab_data_orig);
+  if (io_ret != AVB_IO_RESULT_OK) {
+    if (io_ret == AVB_IO_RESULT_ERROR_OOM) {
+      ret = AVB_AB_FLOW_RESULT_ERROR_OOM;
+    } else {
+      ret = AVB_AB_FLOW_RESULT_ERROR_IO;
+    }
+    if (data != NULL) {
+      avb_slot_verify_data_free(data);
+      data = NULL;
+    }
+  }
+
+  for (n = 0; n < 2; n++) {
+    if (slot_data[n] != NULL) {
+      avb_slot_verify_data_free(slot_data[n]);
+    }
+  }
+
+  if (out_data != NULL) {
+    *out_data = data;
+  } else {
+    if (data != NULL) {
+      avb_slot_verify_data_free(data);
+    }
+  }
+
+  return ret;
+}
+
+AvbIOResult avb_ab_mark_slot_active(AvbABOps* ab_ops,
+                                    unsigned int slot_number) {
+  AvbABData ab_data, ab_data_orig;
+  unsigned int other_slot_number;
+  AvbIOResult ret;
+
+  avb_assert(slot_number < 2);
+
+  ret = load_metadata(ab_ops, &ab_data, &ab_data_orig);
+  if (ret != AVB_IO_RESULT_OK) {
+    goto out;
+  }
+
+  /* Make requested slot top priority, unsuccessful, and with max tries. */
+  ab_data.slots[slot_number].priority = AVB_AB_MAX_PRIORITY;
+  ab_data.slots[slot_number].tries_remaining = AVB_AB_MAX_TRIES_REMAINING;
+  ab_data.slots[slot_number].successful_boot = 0;
+
+  /* Ensure other slot doesn't have as high a priority. */
+  other_slot_number = 1 - slot_number;
+  if (ab_data.slots[other_slot_number].priority == AVB_AB_MAX_PRIORITY) {
+    ab_data.slots[other_slot_number].priority = AVB_AB_MAX_PRIORITY - 1;
+  }
+
+  ret = AVB_IO_RESULT_OK;
+
+out:
+  if (ret == AVB_IO_RESULT_OK) {
+    ret = save_metadata_if_changed(ab_ops, &ab_data, &ab_data_orig);
+  }
+  return ret;
+}
+
+AvbIOResult avb_ab_mark_slot_unbootable(AvbABOps* ab_ops,
+                                        unsigned int slot_number) {
+  AvbABData ab_data, ab_data_orig;
+  AvbIOResult ret;
+
+  avb_assert(slot_number < 2);
+
+  ret = load_metadata(ab_ops, &ab_data, &ab_data_orig);
+  if (ret != AVB_IO_RESULT_OK) {
+    goto out;
+  }
+
+  slot_set_unbootable(&ab_data.slots[slot_number]);
+
+  ret = AVB_IO_RESULT_OK;
+
+out:
+  if (ret == AVB_IO_RESULT_OK) {
+    ret = save_metadata_if_changed(ab_ops, &ab_data, &ab_data_orig);
+  }
+  return ret;
+}
+
+AvbIOResult avb_ab_mark_slot_successful(AvbABOps* ab_ops,
+                                        unsigned int slot_number) {
+  AvbABData ab_data, ab_data_orig;
+  AvbIOResult ret;
+
+  avb_assert(slot_number < 2);
+
+  ret = load_metadata(ab_ops, &ab_data, &ab_data_orig);
+  if (ret != AVB_IO_RESULT_OK) {
+    goto out;
+  }
+
+  if (!slot_is_bootable(&ab_data.slots[slot_number])) {
+    avb_error("Cannot mark unbootable slot as successful.\n");
+    ret = AVB_IO_RESULT_OK;
+    goto out;
+  }
+
+  ab_data.slots[slot_number].tries_remaining = 0;
+  ab_data.slots[slot_number].successful_boot = 1;
+
+  ret = AVB_IO_RESULT_OK;
+
+out:
+  if (ret == AVB_IO_RESULT_OK) {
+    ret = save_metadata_if_changed(ab_ops, &ab_data, &ab_data_orig);
+  }
+  return ret;
+}
+
+const char* avb_ab_flow_result_to_string(AvbABFlowResult result) {
+  const char* ret = NULL;
+
+  switch (result) {
+    case AVB_AB_FLOW_RESULT_OK:
+      ret = "OK";
+      break;
+
+    case AVB_AB_FLOW_RESULT_OK_WITH_VERIFICATION_ERROR:
+      ret = "OK_WITH_VERIFICATION_ERROR";
+      break;
+
+    case AVB_AB_FLOW_RESULT_ERROR_OOM:
+      ret = "ERROR_OOM";
+      break;
+
+    case AVB_AB_FLOW_RESULT_ERROR_IO:
+      ret = "ERROR_IO";
+      break;
+
+    case AVB_AB_FLOW_RESULT_ERROR_NO_BOOTABLE_SLOTS:
+      ret = "ERROR_NO_BOOTABLE_SLOTS";
+      break;
+      /* Do not add a 'default:' case here because of -Wswitch. */
+  }
+
+  if (ret == NULL) {
+    avb_error("Unknown AvbABFlowResult value.\n");
+    ret = "(unknown)";
+  }
+
+  return ret;
+}
diff --git a/avb/libavb_ab/avb_ab_flow.h b/avb/libavb_ab/avb_ab_flow.h
new file mode 100644
index 0000000..6f6a3d2
--- /dev/null
+++ b/avb/libavb_ab/avb_ab_flow.h
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#if !defined(AVB_INSIDE_LIBAVB_AB_H) && !defined(AVB_COMPILATION)
+#error \
+    "Never include this file directly, include libavb_ab/libavb_ab.h instead."
+#endif
+
+#ifndef AVB_AB_FLOW_H_
+#define AVB_AB_FLOW_H_
+
+#include "avb_ab_ops.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Magic for the A/B struct when serialized. */
+#define AVB_AB_MAGIC "\0AB0"
+#define AVB_AB_MAGIC_LEN 4
+
+/* Versioning for the on-disk A/B metadata - keep in sync with avbtool. */
+#define AVB_AB_MAJOR_VERSION 1
+#define AVB_AB_MINOR_VERSION 0
+
+/* Size of AvbABData struct. */
+#define AVB_AB_DATA_SIZE 32
+
+/* Maximum values for slot data */
+#define AVB_AB_MAX_PRIORITY 15
+#define AVB_AB_MAX_TRIES_REMAINING 7
+
+/* Struct used for recording per-slot metadata.
+ *
+ * When serialized, data is stored in network byte-order.
+ */
+typedef struct AvbABSlotData {
+  /* Slot priority. Valid values range from 0 to AVB_AB_MAX_PRIORITY,
+   * both inclusive with 1 being the lowest and AVB_AB_MAX_PRIORITY
+   * being the highest. The special value 0 is used to indicate the
+   * slot is unbootable.
+   */
+  uint8_t priority;
+
+  /* Number of times left attempting to boot this slot ranging from 0
+   * to AVB_AB_MAX_TRIES_REMAINING.
+   */
+  uint8_t tries_remaining;
+
+  /* Non-zero if this slot has booted successfully, 0 otherwise. */
+  uint8_t successful_boot;
+
+  /* Reserved for future use. */
+  uint8_t reserved[1];
+} AVB_ATTR_PACKED AvbABSlotData;
+
+/* Struct used for recording A/B metadata.
+ *
+ * When serialized, data is stored in network byte-order.
+ */
+typedef struct AvbABData {
+  /* Magic number used for identification - see AVB_AB_MAGIC. */
+  uint8_t magic[AVB_AB_MAGIC_LEN];
+
+  /* Version of on-disk struct - see AVB_AB_{MAJOR,MINOR}_VERSION. */
+  uint8_t version_major;
+  uint8_t version_minor;
+
+  /* Padding to ensure |slots| field start eight bytes in. */
+  uint8_t reserved1[2];
+
+  /* Per-slot metadata. */
+  AvbABSlotData slots[2];
+
+  /* Reserved for future use. */
+  uint8_t reserved2[12];
+
+  /* CRC32 of all 28 bytes preceding this field. */
+  uint32_t crc32;
+} AVB_ATTR_PACKED AvbABData;
+
+/* Copies |src| to |dest|, byte-swapping fields in the
+ * process. Returns false if the data is invalid (e.g. wrong magic,
+ * wrong CRC32 etc.), true otherwise.
+ */
+bool avb_ab_data_verify_and_byteswap(const AvbABData* src, AvbABData* dest);
+
+/* Copies |src| to |dest|, byte-swapping fields in the process. Also
+ * updates the |crc32| field in |dest|.
+ */
+void avb_ab_data_update_crc_and_byteswap(const AvbABData* src, AvbABData* dest);
+
+/* Initializes |data| such that it has two slots and both slots have
+ * maximum tries remaining. The CRC is not set.
+ */
+void avb_ab_data_init(AvbABData* data);
+
+/* Reads A/B metadata from the 'misc' partition using |ops|. Returned
+ * data is properly byteswapped. Returns AVB_IO_RESULT_OK on
+ * success, error code otherwise.
+ *
+ * If the data read from disk is invalid (e.g. wrong magic or CRC
+ * checksum failure), the metadata will be reset using
+ * avb_ab_data_init() and then written to disk.
+ */
+AvbIOResult avb_ab_data_read(AvbABOps* ab_ops, AvbABData* data);
+
+/* Writes A/B metadata to the 'misc' partition using |ops|. This will
+ * byteswap and update the CRC as needed. Returns AVB_IO_RESULT_OK on
+ * success, error code otherwise.
+ */
+AvbIOResult avb_ab_data_write(AvbABOps* ab_ops, const AvbABData* data);
+
+/* Return codes used in avb_ab_flow(), see that function for
+ * documentation of each value.
+ */
+typedef enum {
+  AVB_AB_FLOW_RESULT_OK,
+  AVB_AB_FLOW_RESULT_OK_WITH_VERIFICATION_ERROR,
+  AVB_AB_FLOW_RESULT_ERROR_OOM,
+  AVB_AB_FLOW_RESULT_ERROR_IO,
+  AVB_AB_FLOW_RESULT_ERROR_NO_BOOTABLE_SLOTS
+} AvbABFlowResult;
+
+/* Get a textual representation of |result|. */
+const char* avb_ab_flow_result_to_string(AvbABFlowResult result);
+
+/* High-level function to select a slot to boot. The following
+ * algorithm is used:
+ *
+ * 1. A/B metadata is loaded and validated using the
+ * read_ab_metadata() operation. Typically this means it's read from
+ * the 'misc' partition and if it's invalid then it's reset using
+ * avb_ab_data_init() and this reset metadata is returned.
+ *
+ * 2. All bootable slots listed in the A/B metadata are verified using
+ * avb_slot_verify(). If a slot is invalid or if it fails verification
+ * (and |allow_verification_error| is false, see below), it will be
+ * marked as unbootable in the A/B metadata and the metadata will be
+ * saved to disk before returning.
+ *
+ * 3. If there are no bootable slots, the value
+ * AVB_AB_FLOW_RESULT_ERROR_NO_BOOTABLE_SLOTS is returned.
+ *
+ * 4. For each bootable slot, the Stored Rollback Indexes are updated
+ * such that for each rollback index location, the Stored Rollback
+ * Index is the largest number smaller than or equal to the Rollback
+ * Index of each slot.
+ *
+ * 5. The bootable slot with the highest priority is selected and
+ * returned in |out_data|. If this slot is already marked as
+ * successful, the A/B metadata is not modified. However, if the slot
+ * is not marked as bootable its |tries_remaining| count is
+ * decremented and the A/B metadata is saved to disk before returning.
+ * In either case the value AVB_AB_FLOW_RESULT_OK is returning.
+ *
+ * The partitions to load is given in |requested_partitions| as a
+ * NULL-terminated array of NUL-terminated strings. Typically the
+ * |requested_partitions| array only contains a single item for the
+ * boot partition, 'boot'.
+ *
+ * If the device is unlocked (and _only_ if it's unlocked), true
+ * should be passed in the |allow_verification_error| parameter. This
+ * will allow considering slots as verified even when
+ * avb_slot_verify() returns
+ * AVB_SLOT_VERIFY_RESULT_ERROR_PUBLIC_KEY_REJECTED,
+ * AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION, or
+ * AVB_SLOT_VERIFY_RESULT_ERROR_ROLLBACK_INDEX for the slot in
+ * question.
+ *
+ * If a slot was selected and it verified then AVB_AB_FLOW_RESULT_OK
+ * is returned.
+ *
+ * If a slot was selected but it didn't verify then
+ * AVB_AB_FLOW_RESULT_OK_WITH_VERIFICATION_ERROR is returned. This can
+ * only happen when |allow_verification_error| is true.
+ *
+ * If an I/O operation - such as loading/saving metadata or checking
+ * rollback indexes - fail, the value AVB_AB_FLOW_RESULT_ERROR_IO is
+ * returned.
+ *
+ * If memory allocation fails, AVB_AB_FLOW_RESULT_ERROR_OOM is
+ * returned.
+ *
+ * Reasonable behavior for handling AVB_AB_FLOW_RESULT_ERROR_NO_BOOTABLE_SLOTS
+ * is to initiate device repair (which is device-dependent).
+ */
+AvbABFlowResult avb_ab_flow(AvbABOps* ab_ops,
+                            const char* const* requested_partitions,
+                            bool allow_verification_error,
+                            AvbSlotVerifyData** out_data);
+
+/* Marks the slot with the given slot number as active. Returns
+ * AVB_IO_RESULT_OK on success, error code otherwise.
+ *
+ * This function is typically used by the OS updater when completing
+ * an update. It can also used by the firmware for implementing the
+ * "set_active" command.
+ */
+AvbIOResult avb_ab_mark_slot_active(AvbABOps* ab_ops, unsigned int slot_number);
+
+/* Marks the slot with the given slot number as unbootable. Returns
+ * AVB_IO_RESULT_OK on success, error code otherwise.
+ *
+ * This function is typically used by the OS updater before writing to
+ * a slot.
+ */
+AvbIOResult avb_ab_mark_slot_unbootable(AvbABOps* ab_ops,
+                                        unsigned int slot_number);
+
+/* Marks the slot with the given slot number as having booted
+ * successfully. Returns AVB_IO_RESULT_OK on success, error code
+ * otherwise.
+ *
+ * Calling this on an unbootable slot is an error - AVB_IO_RESULT_OK
+ * will be returned yet the function will have no side-effects.
+ *
+ * This function is typically used by the OS updater after having
+ * confirmed that the slot works as intended.
+ */
+AvbIOResult avb_ab_mark_slot_successful(AvbABOps* ab_ops,
+                                        unsigned int slot_number);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* AVB_AB_FLOW_H_ */
diff --git a/avb/libavb_ab/avb_ab_ops.h b/avb/libavb_ab/avb_ab_ops.h
new file mode 100644
index 0000000..8d8fde7
--- /dev/null
+++ b/avb/libavb_ab/avb_ab_ops.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#if !defined(AVB_INSIDE_LIBAVB_AB_H) && !defined(AVB_COMPILATION)
+#error \
+    "Never include this file directly, include libavb_ab/libavb_ab.h instead."
+#endif
+
+#ifndef AVB_AB_OPS_H_
+#define AVB_AB_OPS_H_
+
+#include <libavb/libavb.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct AvbABOps;
+typedef struct AvbABOps AvbABOps;
+
+struct AvbABData;
+
+/* High-level operations/functions/methods for A/B that are platform
+ * dependent.
+ */
+struct AvbABOps {
+  /* Operations from libavb. */
+  AvbOps* ops;
+
+  /* Reads A/B metadata from persistent storage. Returned data is
+   * properly byteswapped. Returns AVB_IO_RESULT_OK on success, error
+   * code otherwise.
+   *
+   * If the data read is invalid (e.g. wrong magic or CRC checksum
+   * failure), the metadata shoule be reset using avb_ab_data_init()
+   * and then written to persistent storage.
+   *
+   * Implementations will typically want to use avb_ab_data_read()
+   * here to use the 'misc' partition for persistent storage.
+   */
+  AvbIOResult (*read_ab_metadata)(AvbABOps* ab_ops, struct AvbABData* data);
+
+  /* Writes A/B metadata to persistent storage. This will byteswap and
+   * update the CRC as needed. Returns AVB_IO_RESULT_OK on success,
+   * error code otherwise.
+   *
+   * Implementations will typically want to use avb_ab_data_write()
+   * here to use the 'misc' partition for persistent storage.
+   */
+  AvbIOResult (*write_ab_metadata)(AvbABOps* ab_ops,
+                                   const struct AvbABData* data);
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* AVB_AB_OPS_H_ */
diff --git a/avb/libavb_ab/libavb_ab.h b/avb/libavb_ab/libavb_ab.h
new file mode 100644
index 0000000..0dcf3e9
--- /dev/null
+++ b/avb/libavb_ab/libavb_ab.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef LIBAVB_AB_H_
+#define LIBAVB_AB_H_
+
+#include <libavb/libavb.h>
+
+/* The AVB_INSIDE_LIBAVB_AB_H preprocessor symbol is used to enforce
+ * library users to include only this file. All public interfaces, and
+ * only public interfaces, must be included here.
+ */
+
+#define AVB_INSIDE_LIBAVB_AB_H
+#include "avb_ab_flow.h"
+#include "avb_ab_ops.h"
+#undef AVB_INSIDE_LIBAVB_AB_H
+
+#endif /* LIBAVB_AB_H_ */
diff --git a/avb/libavb_atx/avb_atx_ops.h b/avb/libavb_atx/avb_atx_ops.h
new file mode 100644
index 0000000..e05ce99
--- /dev/null
+++ b/avb/libavb_atx/avb_atx_ops.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#if !defined(AVB_INSIDE_LIBAVB_ATX_H) && !defined(AVB_COMPILATION)
+#error \
+    "Never include this file directly, include libavb_atx/libavb_atx.h instead."
+#endif
+
+#ifndef AVB_ATX_OPS_H_
+#define AVB_ATX_OPS_H_
+
+#include <libavb/libavb.h>
+
+#include "libavb_atx/avb_atx_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct AvbAtxOps;
+typedef struct AvbAtxOps AvbAtxOps;
+
+/* An extension to AvbOps required by avb_atx_validate_vbmeta_public_key(). */
+struct AvbAtxOps {
+  /* Operations from libavb. */
+  AvbOps* ops;
+
+  /* Reads permanent |attributes| data. There are no restrictions on where this
+   * data is stored. On success, returns AVB_IO_RESULT_OK and populates
+   * |attributes|.
+   */
+  AvbIOResult (*read_permanent_attributes)(
+      AvbAtxOps* atx_ops, AvbAtxPermanentAttributes* attributes);
+
+  /* Reads a |hash| of permanent attributes. This hash MUST be retrieved from a
+   * permanently read-only location (e.g. fuses) when a device is LOCKED. On
+   * success, returned AVB_IO_RESULT_OK and populates |hash|.
+   */
+  AvbIOResult (*read_permanent_attributes_hash)(
+      AvbAtxOps* atx_ops, uint8_t hash[AVB_SHA256_DIGEST_SIZE]);
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* AVB_ATX_OPS_H_ */
diff --git a/avb/libavb_atx/avb_atx_types.h b/avb/libavb_atx/avb_atx_types.h
new file mode 100644
index 0000000..ae1b438
--- /dev/null
+++ b/avb/libavb_atx/avb_atx_types.h
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#if !defined(AVB_INSIDE_LIBAVB_ATX_H) && !defined(AVB_COMPILATION)
+#error \
+    "Never include this file directly, include libavb_atx/libavb_atx.h instead."
+#endif
+
+#ifndef AVB_ATX_TYPES_H_
+#define AVB_ATX_TYPES_H_
+
+#include <libavb/libavb.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Size in bytes of an Android Things product ID. */
+#define AVB_ATX_PRODUCT_ID_SIZE 16
+
+/* Size in bytes of a serialized public key with a 2048-bit modulus. */
+#define AVB_ATX_PUBLIC_KEY_SIZE_2048 (sizeof(AvbRSAPublicKeyHeader) + 512)
+
+/* Size in bytes of a serialized public key with a 4096-bit modulus. */
+#define AVB_ATX_PUBLIC_KEY_SIZE_4096 (sizeof(AvbRSAPublicKeyHeader) + 1024)
+
+/* Data structure of Android Things permanent attributes. */
+typedef struct AvbAtxPermanentAttributes {
+  uint32_t version;
+  uint8_t product_root_public_key[AVB_ATX_PUBLIC_KEY_SIZE_4096];
+  uint8_t product_id[AVB_ATX_PRODUCT_ID_SIZE];
+} AVB_ATTR_PACKED AvbAtxPermanentAttributes;
+
+/* Data structure of signed fields in an Android Things certificate. */
+typedef struct AvbAtxCertificateSignedData {
+  uint32_t version;
+  uint8_t public_key[AVB_ATX_PUBLIC_KEY_SIZE_2048];
+  uint8_t subject[AVB_SHA256_DIGEST_SIZE];
+  uint8_t usage[AVB_SHA256_DIGEST_SIZE];
+  uint64_t key_version;
+} AVB_ATTR_PACKED AvbAtxCertificateSignedData;
+
+/* Data structure of a certificate signed by a 4096-bit key. */
+typedef struct AvbAtxCertificate4096 {
+  AvbAtxCertificateSignedData signed_data;
+  uint8_t signature[AVB_RSA4096_NUM_BYTES];
+} AVB_ATTR_PACKED AvbAtxCertificate4096;
+
+/* Data structure of a certificate signed by a 2048-bit key. */
+typedef struct AvbAtxCertificate2048 {
+  AvbAtxCertificateSignedData signed_data;
+  uint8_t signature[AVB_RSA2048_NUM_BYTES];
+} AVB_ATTR_PACKED AvbAtxCertificate2048;
+
+/* Data structure of Android Things public key metadata in vbmeta. */
+typedef struct AvbAtxPublicKeyMetadata {
+  uint32_t version;
+  AvbAtxCertificate4096 product_intermediate_key_certificate;
+  AvbAtxCertificate2048 product_signing_key_certificate;
+  uint64_t google_signing_key_version;
+} AVB_ATTR_PACKED AvbAtxPublicKeyMetadata;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* AVB_ATX_TYPES_H_ */
diff --git a/avb/libavb_atx/avb_atx_validate.c b/avb/libavb_atx/avb_atx_validate.c
new file mode 100644
index 0000000..7a29d66
--- /dev/null
+++ b/avb/libavb_atx/avb_atx_validate.c
@@ -0,0 +1,269 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "libavb_atx/avb_atx_validate.h"
+
+#include "libavb/avb_rsa.h"
+#include "libavb/avb_sha.h"
+#include "libavb/avb_sysdeps.h"
+#include "libavb/avb_util.h"
+
+/* Computes the SHA256 |hash| of |length| bytes of |data|. */
+static void sha256(const uint8_t* data,
+                   uint32_t length,
+                   uint8_t hash[AVB_SHA256_DIGEST_SIZE]) {
+  AvbSHA256Ctx context;
+  avb_sha256_init(&context);
+  avb_sha256_update(&context, data, length);
+  uint8_t* tmp = avb_sha256_final(&context);
+  avb_memcpy(hash, tmp, AVB_SHA256_DIGEST_SIZE);
+}
+
+/* Computes the SHA512 |hash| of |length| bytes of |data|. */
+static void sha512(const uint8_t* data,
+                   uint32_t length,
+                   uint8_t hash[AVB_SHA512_DIGEST_SIZE]) {
+  AvbSHA512Ctx context;
+  avb_sha512_init(&context);
+  avb_sha512_update(&context, data, length);
+  uint8_t* tmp = avb_sha512_final(&context);
+  avb_memcpy(hash, tmp, AVB_SHA512_DIGEST_SIZE);
+}
+
+/* Computes the SHA256 |hash| of a NUL-terminated |str|. */
+static void sha256_str(const char* str, uint8_t hash[AVB_SHA256_DIGEST_SIZE]) {
+  sha256((const uint8_t*)str, avb_strlen(str), hash);
+}
+
+/* Verifies structure and |expected_hash| of permanent |attributes|. */
+static bool verify_permanent_attributes(
+    const AvbAtxPermanentAttributes* attributes,
+    uint8_t expected_hash[AVB_SHA256_DIGEST_SIZE]) {
+  uint8_t hash[AVB_SHA256_DIGEST_SIZE];
+
+  if (attributes->version != 1) {
+    avb_error("Unsupported permanent attributes version.\n");
+    return false;
+  }
+  sha256((const uint8_t*)attributes, sizeof(AvbAtxPermanentAttributes), hash);
+  if (0 != avb_safe_memcmp(hash, expected_hash, AVB_SHA256_DIGEST_SIZE)) {
+    avb_error("Invalid permanent attributes.\n");
+    return false;
+  }
+  return true;
+}
+
+/* Verifies signature and fields of a PIK certificate. */
+static bool verify_pik_certificate(
+    AvbAtxCertificate4096* certificate,
+    uint8_t authority[AVB_ATX_PUBLIC_KEY_SIZE_4096],
+    uint64_t minimum_version) {
+  const AvbAlgorithmData* algorithm_data;
+  uint8_t certificate_hash[AVB_SHA512_DIGEST_SIZE];
+  uint8_t expected_usage[AVB_SHA256_DIGEST_SIZE];
+
+  if (certificate->signed_data.version != 1) {
+    avb_error("Unsupported PIK certificate format.\n");
+    return false;
+  }
+  algorithm_data = avb_get_algorithm_data(AVB_ALGORITHM_TYPE_SHA512_RSA4096);
+  sha512((const uint8_t*)&certificate->signed_data,
+         sizeof(AvbAtxCertificateSignedData),
+         certificate_hash);
+  if (!avb_rsa_verify(authority,
+                      AVB_ATX_PUBLIC_KEY_SIZE_4096,
+                      certificate->signature,
+                      AVB_RSA4096_NUM_BYTES,
+                      certificate_hash,
+                      AVB_SHA512_DIGEST_SIZE,
+                      algorithm_data->padding,
+                      algorithm_data->padding_len)) {
+    avb_error("Invalid PIK certificate signature.\n");
+    return false;
+  }
+  sha256_str("com.google.android.things.vboot.ca", expected_usage);
+  if (0 != avb_safe_memcmp(certificate->signed_data.usage,
+                           expected_usage,
+                           AVB_SHA256_DIGEST_SIZE)) {
+    avb_error("Invalid PIK certificate usage.\n");
+    return false;
+  }
+  if (certificate->signed_data.key_version < minimum_version) {
+    avb_error("PIK rollback detected.\n");
+    return false;
+  }
+  return true;
+}
+
+/* Verifies signature and fields of a PSK certificate. */
+static bool verify_psk_certificate(
+    AvbAtxCertificate2048* certificate,
+    uint8_t authority[AVB_ATX_PUBLIC_KEY_SIZE_2048],
+    uint64_t minimum_version,
+    uint8_t product_id[AVB_ATX_PRODUCT_ID_SIZE]) {
+  const AvbAlgorithmData* algorithm_data;
+  uint8_t certificate_hash[AVB_SHA256_DIGEST_SIZE];
+  uint8_t expected_subject[AVB_SHA256_DIGEST_SIZE];
+  uint8_t expected_usage[AVB_SHA256_DIGEST_SIZE];
+  if (certificate->signed_data.version != 1) {
+    avb_error("Unsupported PSK certificate format.\n");
+    return false;
+  }
+  algorithm_data = avb_get_algorithm_data(AVB_ALGORITHM_TYPE_SHA256_RSA2048);
+  sha256((const uint8_t*)&certificate->signed_data,
+         sizeof(AvbAtxCertificateSignedData),
+         certificate_hash);
+  if (!avb_rsa_verify(authority,
+                      AVB_ATX_PUBLIC_KEY_SIZE_2048,
+                      certificate->signature,
+                      AVB_RSA2048_NUM_BYTES,
+                      certificate_hash,
+                      AVB_SHA256_DIGEST_SIZE,
+                      algorithm_data->padding,
+                      algorithm_data->padding_len)) {
+    avb_error("Invalid PSK certificate signature.\n");
+    return false;
+  }
+  sha256(product_id, AVB_ATX_PRODUCT_ID_SIZE, expected_subject);
+  if (0 != avb_safe_memcmp(certificate->signed_data.subject,
+                           expected_subject,
+                           AVB_SHA256_DIGEST_SIZE)) {
+    avb_error("Product ID mismatch.\n");
+    return false;
+  }
+  sha256_str("com.google.android.things.vboot", expected_usage);
+  if (0 != avb_safe_memcmp(certificate->signed_data.usage,
+                           expected_usage,
+                           AVB_SHA256_DIGEST_SIZE)) {
+    avb_error("Invalid PSK certificate usage.\n");
+    return false;
+  }
+  if (certificate->signed_data.key_version < minimum_version) {
+    avb_error("PSK rollback detected.\n");
+    return false;
+  }
+  return true;
+}
+
+AvbIOResult avb_atx_validate_vbmeta_public_key(
+    AvbOps* ops,
+    const uint8_t* public_key_data,
+    size_t public_key_length,
+    const uint8_t* public_key_metadata,
+    size_t public_key_metadata_length,
+    bool* out_is_trusted) {
+  AvbIOResult result = AVB_IO_RESULT_OK;
+  AvbAtxPermanentAttributes permanent_attributes;
+  uint8_t permanent_attributes_hash[AVB_SHA256_DIGEST_SIZE];
+  AvbAtxPublicKeyMetadata metadata;
+  uint64_t minimum_version;
+
+  /* Be pessimistic so we can exit early without having to remember to clear. */
+  *out_is_trusted = false;
+
+  /* Read and verify permanent attributes. */
+  result = ops->atx_ops->read_permanent_attributes(ops->atx_ops,
+                                                   &permanent_attributes);
+  if (result != AVB_IO_RESULT_OK) {
+    avb_error("Failed to read permanent attributes.\n");
+    return result;
+  }
+  result = ops->atx_ops->read_permanent_attributes_hash(
+      ops->atx_ops, permanent_attributes_hash);
+  if (result != AVB_IO_RESULT_OK) {
+    avb_error("Failed to read permanent attributes hash.\n");
+    return result;
+  }
+  if (!verify_permanent_attributes(&permanent_attributes,
+                                   permanent_attributes_hash)) {
+    return AVB_IO_RESULT_OK;
+  }
+
+  /* Sanity check public key metadata. */
+  if (public_key_metadata_length != sizeof(AvbAtxPublicKeyMetadata)) {
+    avb_error("Invalid public key metadata.\n");
+    return AVB_IO_RESULT_OK;
+  }
+  avb_memcpy(&metadata, public_key_metadata, sizeof(AvbAtxPublicKeyMetadata));
+  if (metadata.version != 1) {
+    avb_error("Unsupported public key metadata.\n");
+    return AVB_IO_RESULT_OK;
+  }
+
+  /* Verify the PIK certificate. */
+  result = ops->read_rollback_index(
+      ops, AVB_ATX_PIK_VERSION_LOCATION, &minimum_version);
+  if (result != AVB_IO_RESULT_OK) {
+    avb_error("Failed to read PIK minimum version.\n");
+    return result;
+  }
+  if (!verify_pik_certificate(&metadata.product_intermediate_key_certificate,
+                              permanent_attributes.product_root_public_key,
+                              minimum_version)) {
+    return AVB_IO_RESULT_OK;
+  }
+
+  /* Verify the PSK certificate. */
+  result = ops->read_rollback_index(
+      ops, AVB_ATX_PSK_VERSION_LOCATION, &minimum_version);
+  if (result != AVB_IO_RESULT_OK) {
+    avb_error("Failed to read PSK minimum version.\n");
+    return result;
+  }
+  if (!verify_psk_certificate(
+          &metadata.product_signing_key_certificate,
+          metadata.product_intermediate_key_certificate.signed_data.public_key,
+          minimum_version,
+          permanent_attributes.product_id)) {
+    return AVB_IO_RESULT_OK;
+  }
+
+  /* Verify the PSK is the same key that verified vbmeta. */
+  if (public_key_length != AVB_ATX_PUBLIC_KEY_SIZE_2048) {
+    avb_error("Public key length mismatch.\n");
+    return AVB_IO_RESULT_OK;
+  }
+  if (0 != avb_safe_memcmp(
+               metadata.product_signing_key_certificate.signed_data.public_key,
+               public_key_data,
+               AVB_ATX_PUBLIC_KEY_SIZE_2048)) {
+    avb_error("Public key mismatch.\n");
+    return AVB_IO_RESULT_OK;
+  }
+
+  /* Verify the GSK minimum version. */
+  result = ops->read_rollback_index(
+      ops, AVB_ATX_GSK_VERSION_LOCATION, &minimum_version);
+  if (result != AVB_IO_RESULT_OK) {
+    avb_error("Failed to read GSK minimum version.\n");
+    return result;
+  }
+  if (metadata.google_signing_key_version < minimum_version) {
+    avb_error("Google signing key rollback detected.\n");
+    return AVB_IO_RESULT_OK;
+  }
+
+  *out_is_trusted = true;
+  return AVB_IO_RESULT_OK;
+}
diff --git a/avb/libavb_atx/avb_atx_validate.h b/avb/libavb_atx/avb_atx_validate.h
new file mode 100644
index 0000000..07c154e
--- /dev/null
+++ b/avb/libavb_atx/avb_atx_validate.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#if !defined(AVB_INSIDE_LIBAVB_ATX_H) && !defined(AVB_COMPILATION)
+#error \
+    "Never include this file directly, include libavb_atx/libavb_atx.h instead."
+#endif
+
+#ifndef AVB_ATX_VALIDATE_H_
+#define AVB_ATX_VALIDATE_H_
+
+#include "libavb_atx/avb_atx_ops.h"
+#include "libavb_atx/avb_atx_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Rollback index locations for Android Things key versions. */
+#define AVB_ATX_PIK_VERSION_LOCATION 0x1000
+#define AVB_ATX_PSK_VERSION_LOCATION 0x1001
+#define AVB_ATX_GSK_VERSION_LOCATION 0x1002
+
+/* An implementation of validate_vbmeta_public_key for Android Things. See
+ * libavb/avb_ops.h for details on validate_vbmeta_public_key in general. This
+ * implementation uses the metadata expected with Android Things vbmeta images
+ * to perform validation on the public key. The ATX ops must be implemented.
+ * That is, |ops->atx_ops| must be valid.
+ *
+ * There are a multiple values that need verification:
+ *   - Permanent Product Attributes: A hash of these attributes is fused into
+ *                                   hardware. Consistency is checked.
+ *   - Product Root Key (PRK): This key is provided in permanent attributes and
+ *                             is the root authority for all Android Things
+ *                             products.
+ *   - Product Intermediate Key (PIK): This key is a rotated intermediary. It is
+ *                                     certified by the PRK.
+ *   - Product Signing Key (PSK): This key is a rotated authority for a specific
+ *                                Android Things product. It is certified by a
+ *                                PIK and must match |public_key_data|.
+ *   - Google Signing Key (GSK): This key is a rotated authority for system and
+ *                               boot partitions built by Google. The key is
+ *                               already verified as part of vbmeta so only the
+ *                               key version needs verification here.
+ *   - Product ID: This value is provided in permanent attributes and is unique
+ *                 to a specific Android Things product. This value must match
+ *                 the subject of the PSK certificate.
+ */
+AvbIOResult avb_atx_validate_vbmeta_public_key(
+    AvbOps* ops,
+    const uint8_t* public_key_data,
+    size_t public_key_length,
+    const uint8_t* public_key_metadata,
+    size_t public_key_metadata_length,
+    bool* out_is_trusted);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* AVB_ATX_VALIDATE_H_ */
diff --git a/avb/libavb_atx/libavb_atx.h b/avb/libavb_atx/libavb_atx.h
new file mode 100644
index 0000000..839c0af
--- /dev/null
+++ b/avb/libavb_atx/libavb_atx.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef LIBAVB_ATX_H_
+#define LIBAVB_ATX_H_
+
+#include <libavb/libavb.h>
+
+/* The AVB_INSIDE_LIBAVB_ATX_H preprocessor symbol is used to enforce
+ * library users to include only this file. All public interfaces, and
+ * only public interfaces, must be included here.
+ */
+
+#define AVB_INSIDE_LIBAVB_ATX_H
+#include "avb_atx_ops.h"
+#include "avb_atx_types.h"
+#include "avb_atx_validate.h"
+#undef AVB_INSIDE_LIBAVB_ATX_H
+
+#endif /* LIBAVB_ATX_H_ */
diff --git a/avb/test/avb_ab_flow_unittest.cc b/avb/test/avb_ab_flow_unittest.cc
new file mode 100644
index 0000000..e13fccb
--- /dev/null
+++ b/avb/test/avb_ab_flow_unittest.cc
@@ -0,0 +1,1321 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <string.h>
+
+#include <map>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include <libavb_ab/libavb_ab.h>
+
+#include "avb_unittest_util.h"
+#include "fake_avb_ops.h"
+
+namespace avb {
+
+static_assert(sizeof(AvbABSlotData) == 4, "AvbABSlotData has wrong size");
+static_assert(sizeof(AvbABData) == AVB_AB_DATA_SIZE,
+              "AvbABData has wrong size");
+static_assert(offsetof(AvbABData, slots) % 8 == 0,
+              "AvbABData slots member has wrong offset");
+
+TEST(ABTest, InitData) {
+  AvbABData data;
+  avb_ab_data_init(&data);
+  EXPECT_EQ(0,
+            strncmp(reinterpret_cast<const char*>(data.magic),
+                    AVB_AB_MAGIC,
+                    AVB_AB_MAGIC_LEN));
+  EXPECT_EQ(AVB_AB_MAX_PRIORITY, data.slots[0].priority);
+  EXPECT_EQ(AVB_AB_MAX_TRIES_REMAINING, data.slots[0].tries_remaining);
+  EXPECT_EQ(0, data.slots[0].successful_boot);
+  EXPECT_EQ(AVB_AB_MAX_PRIORITY - 1, data.slots[1].priority);
+  EXPECT_EQ(AVB_AB_MAX_TRIES_REMAINING, data.slots[1].tries_remaining);
+  EXPECT_EQ(0, data.slots[1].successful_boot);
+  EXPECT_EQ(uint32_t(0), data.crc32);
+}
+
+TEST(ABTest, DataSerialization) {
+  AvbABData data;
+  AvbABData serialized;
+  AvbABData restored;
+
+  avb_ab_data_init(&data);
+  EXPECT_EQ(uint32_t(0), data.crc32);
+  avb_ab_data_update_crc_and_byteswap(&data, &serialized);
+  EXPECT_NE(uint32_t(0), serialized.crc32);
+  EXPECT_TRUE(avb_ab_data_verify_and_byteswap(&serialized, &restored));
+  EXPECT_EQ(std::string(reinterpret_cast<const char*>(data.magic), 4),
+            std::string(reinterpret_cast<const char*>(restored.magic), 4));
+  EXPECT_EQ(data.version_major, restored.version_major);
+  EXPECT_EQ(data.version_minor, restored.version_minor);
+  EXPECT_EQ(0,
+            memcmp(reinterpret_cast<void*>(data.slots),
+                   reinterpret_cast<void*>(restored.slots),
+                   sizeof(AvbABSlotData) * 2));
+}
+
+TEST(ABTest, CatchBadCRC) {
+  AvbABData data;
+  AvbABData serialized;
+  AvbABData restored;
+
+  avb_ab_data_init(&data);
+  avb_ab_data_update_crc_and_byteswap(&data, &serialized);
+  serialized.crc32 += 1;
+  EXPECT_FALSE(avb_ab_data_verify_and_byteswap(&serialized, &restored));
+}
+
+TEST(ABTest, CatchUnsupportedMajorVersion) {
+  AvbABData data;
+  AvbABData serialized;
+  AvbABData restored;
+
+  avb_ab_data_init(&data);
+  data.version_major += 1;
+  avb_ab_data_update_crc_and_byteswap(&data, &serialized);
+  EXPECT_FALSE(avb_ab_data_verify_and_byteswap(&serialized, &restored));
+}
+
+TEST(ABTest, SupportSameMajorFutureMinorVersion) {
+  AvbABData data;
+  AvbABData serialized;
+  AvbABData restored;
+
+  avb_ab_data_init(&data);
+  data.version_minor += 1;
+  avb_ab_data_update_crc_and_byteswap(&data, &serialized);
+  EXPECT_TRUE(avb_ab_data_verify_and_byteswap(&serialized, &restored));
+}
+
+#define MISC_PART_SIZE 8 * 1024
+
+// These values are kept short since they are used in SetMD() and it's
+// helpful if the information for a slot fits in one 80-character
+// line.
+enum SlotValidity {
+  SV_OK,   // Slot is valid and verified.
+  SV_INV,  // Slot is invalid.
+  SV_UNV,  // Slot is valid but unverified.
+};
+
+class AvbABFlowTest : public BaseAvbToolTest {
+ public:
+  AvbABFlowTest() {}
+
+  virtual void SetUp() override {
+    BaseAvbToolTest::SetUp();
+    ops_.set_partition_dir(testdir_);
+    ops_.set_stored_rollback_indexes({{0, 0}, {1, 0}, {2, 0}, {3, 0}});
+    ops_.set_stored_is_device_unlocked(false);
+
+    // Create large enough 'misc' partition and initialize it with
+    // zeroes.
+    std::vector<uint8_t> misc;
+    misc.resize(MISC_PART_SIZE);
+    base::FilePath misc_path = testdir_.Append("misc.img");
+    EXPECT_EQ(misc.size(),
+              static_cast<const size_t>(
+                  base::WriteFile(misc_path,
+                                  reinterpret_cast<const char*>(misc.data()),
+                                  misc.size())));
+
+    // We're going to use this key for all images.
+    ops_.set_expected_public_key(
+        PublicKeyAVB(base::FilePath("test/data/testkey_rsa2048.pem")));
+  }
+
+  void GenerateSlot(unsigned int slot_number,
+                    SlotValidity slot_validity,
+                    uint64_t rollback_boot,
+                    uint64_t rollback_odm) {
+    std::string boot_name = "boot_a.img";
+    std::string vbmeta_name = "vbmeta_a.img";
+    std::string odm_name = "odm_a.img";
+    if (slot_number > 0) {
+      boot_name = "boot_b.img";
+      vbmeta_name = "vbmeta_b.img";
+      odm_name = "odm_b.img";
+    }
+
+    // If asked to make an invalid slot, just generate 1MiB garbage
+    // for each the three images in the slot.
+    if (slot_validity == SV_INV) {
+      GenerateImage(boot_name, 1024 * 1024);
+      GenerateImage(vbmeta_name, 1024 * 1024);
+      GenerateImage(odm_name, 1024 * 1024);
+      return;
+    }
+
+    const size_t boot_partition_size = 16 * 1024 * 1024;
+    const size_t boot_image_size = 5 * 1024 * 1024;
+    base::FilePath boot_path = GenerateImage(boot_name, boot_image_size);
+    EXPECT_COMMAND(0,
+                   "./avbtool add_hash_footer"
+                   " --image %s"
+                   " --rollback_index %" PRIu64
+                   " --partition_name boot"
+                   " --partition_size %zd"
+                   " --salt deadbeef",
+                   boot_path.value().c_str(),
+                   rollback_boot,
+                   boot_partition_size);
+
+    const size_t odm_partition_size = 512 * 1024;
+    const size_t odm_image_size = 80 * 1024;
+    base::FilePath odm_path = GenerateImage(odm_name, odm_image_size);
+    EXPECT_COMMAND(0,
+                   "./avbtool add_hashtree_footer"
+                   " --image %s"
+                   " --rollback_index %" PRIu64
+                   " --partition_name odm"
+                   " --partition_size %zd"
+                   " --salt deadbeef"
+                   " --algorithm SHA512_RSA4096 "
+                   " --key test/data/testkey_rsa4096.pem",
+                   odm_path.value().c_str(),
+                   rollback_odm,
+                   odm_partition_size);
+
+    base::FilePath pk_path = testdir_.Append("testkey_rsa4096.avbpubkey");
+    EXPECT_COMMAND(
+        0,
+        "./avbtool extract_public_key --key test/data/testkey_rsa4096.pem"
+        " --output %s",
+        pk_path.value().c_str());
+
+    // If requested to make the image unverified, just use another key
+    // in the chain_partition descriptor since this will cause
+    // avb_slot_verify() to return ERROR_PUBLIC_KEY_REJECTED.
+    if (slot_validity == SV_UNV) {
+      pk_path = GenerateImage("dummy.avbpubkey", 32);
+    }
+
+    GenerateVBMetaImage(vbmeta_name,
+                        "SHA256_RSA2048",
+                        rollback_boot,
+                        base::FilePath("test/data/testkey_rsa2048.pem"),
+                        base::StringPrintf("--include_descriptors_from_image %s"
+                                           " --chain_partition odm:1:%s",
+                                           boot_path.value().c_str(),
+                                           pk_path.value().c_str()));
+  }
+
+  void SetMD(int a_pri,
+             int a_tries,
+             bool a_success,
+             SlotValidity a_slot_validity,
+             uint64_t a_rollback_boot,
+             uint64_t a_rollback_odm,
+             int b_pri,
+             int b_tries,
+             bool b_success,
+             SlotValidity b_slot_validity,
+             uint64_t b_rollback_boot,
+             uint64_t b_rollback_odm,
+             const std::map<size_t, uint64_t>& stored_rollback_indexes) {
+    AvbABData data;
+    avb_ab_data_init(&data);
+    data.slots[0].priority = a_pri;
+    data.slots[0].tries_remaining = a_tries;
+    data.slots[0].successful_boot = (a_success ? 1 : 0);
+    data.slots[1].priority = b_pri;
+    data.slots[1].tries_remaining = b_tries;
+    data.slots[1].successful_boot = (b_success ? 1 : 0);
+    EXPECT_EQ(AVB_IO_RESULT_OK,
+              ops_.avb_ab_ops()->write_ab_metadata(ops_.avb_ab_ops(), &data));
+    GenerateSlot(0, a_slot_validity, a_rollback_boot, a_rollback_odm);
+    GenerateSlot(1, b_slot_validity, b_rollback_boot, b_rollback_odm);
+    ops_.set_stored_rollback_indexes(stored_rollback_indexes);
+  }
+
+  std::map<size_t, uint64_t> MakeRollbackIndexes(uint64_t slot_0_value,
+                                                 uint64_t slot_1_value) {
+    return std::map<size_t, uint64_t>{{0, slot_0_value}, {1, slot_1_value}};
+  }
+
+  FakeAvbOps ops_;
+};
+
+#define ExpMD(a_pri,                                                          \
+              a_tries,                                                        \
+              a_success,                                                      \
+              b_pri,                                                          \
+              b_tries,                                                        \
+              b_success,                                                      \
+              stored_rollback_indexes)                                        \
+  do {                                                                        \
+    AvbABData data;                                                           \
+    EXPECT_EQ(AVB_IO_RESULT_OK,                                               \
+              ops_.avb_ab_ops()->read_ab_metadata(ops_.avb_ab_ops(), &data)); \
+    EXPECT_EQ(a_pri, data.slots[0].priority);                                 \
+    EXPECT_EQ(a_tries, data.slots[0].tries_remaining);                        \
+    EXPECT_EQ(a_success ? 1 : 0, data.slots[0].successful_boot);              \
+    EXPECT_EQ(b_pri, data.slots[1].priority);                                 \
+    EXPECT_EQ(b_tries, data.slots[1].tries_remaining);                        \
+    EXPECT_EQ(b_success ? 1 : 0, data.slots[1].successful_boot);              \
+    EXPECT_EQ(stored_rollback_indexes, ops_.get_stored_rollback_indexes());   \
+  } while (0);
+
+TEST_F(AvbABFlowTest, MetadataReadAndWrite) {
+  AvbABData data;
+  AvbABData loaded;
+
+  // First load from an uninitialized 'misc' partition. This should
+  // not fail and just returned initialized data.
+  EXPECT_EQ(AVB_IO_RESULT_OK, avb_ab_data_read(ops_.avb_ab_ops(), &loaded));
+  EXPECT_EQ(AVB_AB_MAX_PRIORITY, loaded.slots[0].priority);
+  EXPECT_EQ(AVB_AB_MAX_TRIES_REMAINING, loaded.slots[0].tries_remaining);
+  EXPECT_EQ(0, loaded.slots[0].successful_boot);
+  EXPECT_EQ(AVB_AB_MAX_PRIORITY - 1, loaded.slots[1].priority);
+  EXPECT_EQ(AVB_AB_MAX_TRIES_REMAINING, loaded.slots[1].tries_remaining);
+  EXPECT_EQ(0, loaded.slots[1].successful_boot);
+
+  // Then initialize and save well-known A/B metadata and check we
+  // read back the same thing.
+  avb_ab_data_init(&data);
+  data.slots[0].priority = 2;
+  data.slots[0].tries_remaining = 3;
+  EXPECT_EQ(AVB_IO_RESULT_OK, avb_ab_data_write(ops_.avb_ab_ops(), &data));
+  EXPECT_EQ(AVB_IO_RESULT_OK, avb_ab_data_read(ops_.avb_ab_ops(), &loaded));
+  EXPECT_EQ(2, loaded.slots[0].priority);
+  EXPECT_EQ(3, loaded.slots[0].tries_remaining);
+}
+
+TEST_F(AvbABFlowTest, EverythingIsValid) {
+  AvbSlotVerifyData* data;
+  const char* requested_partitions[] = {"boot", NULL};
+
+  SetMD(14,
+        0,
+        1,
+        SV_OK,
+        0,
+        0,  // A: pri, tries, success, slot_validity, RIs
+        15,
+        0,
+        1,
+        SV_OK,
+        0,
+        0,  // B: pri, tries, success, slot_validity, RIs
+        MakeRollbackIndexes(0, 0));  // stored_rollback_indexes
+  EXPECT_EQ(AVB_AB_FLOW_RESULT_OK,
+            avb_ab_flow(ops_.avb_ab_ops(),
+                        requested_partitions,
+                        false /* allow_verification_error */,
+                        &data));
+  ExpMD(14,
+        0,
+        1,  // A: pri, tries, successful
+        15,
+        0,
+        1,                           // B: pri, tries, successful
+        MakeRollbackIndexes(0, 0));  // stored_rollback_indexes
+  ASSERT_NE(nullptr, data);
+  EXPECT_EQ("_b", std::string(data->ab_suffix));
+  avb_slot_verify_data_free(data);
+
+  // Also check the other slot.
+  SetMD(15,
+        0,
+        1,
+        SV_OK,
+        0,
+        0,  // A: pri, tries, success, slot_validity, RIs
+        14,
+        0,
+        1,
+        SV_OK,
+        0,
+        0,  // B: pri, tries, success, slot_validity, RIs
+        MakeRollbackIndexes(0, 0));  // stored_rollback_indexes
+  EXPECT_EQ(AVB_AB_FLOW_RESULT_OK,
+            avb_ab_flow(ops_.avb_ab_ops(),
+                        requested_partitions,
+                        false /* allow_verification_error */,
+                        &data));
+  ExpMD(15,
+        0,
+        1,  // A: pri, tries, successful
+        14,
+        0,
+        1,                           // B: pri, tries, successful
+        MakeRollbackIndexes(0, 0));  // stored_rollback_indexes
+  ASSERT_NE(nullptr, data);
+  EXPECT_EQ("_a", std::string(data->ab_suffix));
+  avb_slot_verify_data_free(data);
+}
+
+TEST_F(AvbABFlowTest, NoBootableSlots) {
+  AvbSlotVerifyData* data;
+  const char* requested_partitions[] = {"boot", NULL};
+
+  SetMD(0,
+        0,
+        0,
+        SV_OK,
+        0,
+        0,  // A: pri, tries, success, slot_validity, RIs
+        0,
+        0,
+        0,
+        SV_OK,
+        0,
+        0,  // B: pri, tries, success, slot_validity, RIs
+        MakeRollbackIndexes(0, 0));  // stored_rollback_indexes
+  EXPECT_EQ(AVB_AB_FLOW_RESULT_ERROR_NO_BOOTABLE_SLOTS,
+            avb_ab_flow(ops_.avb_ab_ops(),
+                        requested_partitions,
+                        false /* allow_verification_error */,
+                        &data));
+  ExpMD(0,
+        0,
+        0,  // A: pri, tries, successful
+        0,
+        0,
+        0,                           // B: pri, tries, successful
+        MakeRollbackIndexes(0, 0));  // stored_rollback_indexes
+  ASSERT_EQ(nullptr, data);
+}
+
+TEST_F(AvbABFlowTest, TriesRemainingDecreasing) {
+  AvbSlotVerifyData* data;
+  const char* requested_partitions[] = {"boot", NULL};
+
+  SetMD(15,
+        3,
+        0,
+        SV_OK,
+        0,
+        0,  // A: pri, tries, success, slot_validity, RIs
+        0,
+        0,
+        0,
+        SV_OK,
+        0,
+        0,  // B: pri, tries, success, slot_validity, RIs
+        MakeRollbackIndexes(0, 0));  // stored_rollback_indexes
+
+  EXPECT_EQ(AVB_AB_FLOW_RESULT_OK,
+            avb_ab_flow(ops_.avb_ab_ops(),
+                        requested_partitions,
+                        false /* allow_verification_error */,
+                        &data));
+  ExpMD(15,
+        2,
+        0,  // A: pri, tries, successful
+        0,
+        0,
+        0,                           // B: pri, tries, successful
+        MakeRollbackIndexes(0, 0));  // stored_rollback_indexes
+  ASSERT_NE(nullptr, data);
+  EXPECT_EQ("_a", std::string(data->ab_suffix));
+  avb_slot_verify_data_free(data);
+
+  // Keep counting down...
+  EXPECT_EQ(AVB_AB_FLOW_RESULT_OK,
+            avb_ab_flow(ops_.avb_ab_ops(),
+                        requested_partitions,
+                        false /* allow_verification_error */,
+                        &data));
+  ExpMD(15,
+        1,
+        0,  // A: pri, tries, successful
+        0,
+        0,
+        0,                           // B: pri, tries, successful
+        MakeRollbackIndexes(0, 0));  // stored_rollback_indexes
+  ASSERT_NE(nullptr, data);
+  EXPECT_EQ("_a", std::string(data->ab_suffix));
+  avb_slot_verify_data_free(data);
+
+  // Last try...
+  EXPECT_EQ(AVB_AB_FLOW_RESULT_OK,
+            avb_ab_flow(ops_.avb_ab_ops(),
+                        requested_partitions,
+                        false /* allow_verification_error */,
+                        &data));
+  ExpMD(15,
+        0,
+        0,  // A: pri, tries, successful
+        0,
+        0,
+        0,                           // B: pri, tries, successful
+        MakeRollbackIndexes(0, 0));  // stored_rollback_indexes
+  ASSERT_NE(nullptr, data);
+  EXPECT_EQ("_a", std::string(data->ab_suffix));
+  avb_slot_verify_data_free(data);
+
+  // And we're out of tries. At this point, (15, 0, 0) is normalized
+  // to (0, 0, 0) so expect that.
+  EXPECT_EQ(AVB_AB_FLOW_RESULT_ERROR_NO_BOOTABLE_SLOTS,
+            avb_ab_flow(ops_.avb_ab_ops(),
+                        requested_partitions,
+                        false /* allow_verification_error */,
+                        &data));
+  ExpMD(0,
+        0,
+        0,  // A: pri, tries, successful
+        0,
+        0,
+        0,                           // B: pri, tries, successful
+        MakeRollbackIndexes(0, 0));  // stored_rollback_indexes
+  ASSERT_EQ(nullptr, data);
+}
+
+TEST_F(AvbABFlowTest, TryingThenFallback) {
+  AvbSlotVerifyData* data;
+  const char* requested_partitions[] = {"boot", NULL};
+
+  SetMD(15,
+        2,
+        0,
+        SV_OK,
+        0,
+        0,  // A: pri, tries, success, slot_validity, RIs
+        14,
+        0,
+        1,
+        SV_OK,
+        0,
+        0,  // B: pri, tries, success, slot_validity, RIs
+        MakeRollbackIndexes(0, 0));  // stored_rollback_indexes
+  EXPECT_EQ(AVB_AB_FLOW_RESULT_OK,
+            avb_ab_flow(ops_.avb_ab_ops(),
+                        requested_partitions,
+                        false /* allow_verification_error */,
+                        &data));
+  ExpMD(15,
+        1,
+        0,  // A: pri, tries, successful
+        14,
+        0,
+        1,                           // B: pri, tries, successful
+        MakeRollbackIndexes(0, 0));  // stored_rollback_indexes
+  ASSERT_NE(nullptr, data);
+  EXPECT_EQ("_a", std::string(data->ab_suffix));
+  avb_slot_verify_data_free(data);
+
+  // Last try...
+  EXPECT_EQ(AVB_AB_FLOW_RESULT_OK,
+            avb_ab_flow(ops_.avb_ab_ops(),
+                        requested_partitions,
+                        false /* allow_verification_error */,
+                        &data));
+  ExpMD(15,
+        0,
+        0,  // A: pri, tries, successful
+        14,
+        0,
+        1,                           // B: pri, tries, successful
+        MakeRollbackIndexes(0, 0));  // stored_rollback_indexes
+  ASSERT_NE(nullptr, data);
+  EXPECT_EQ("_a", std::string(data->ab_suffix));
+  avb_slot_verify_data_free(data);
+
+  // And we're out of tries. Check we fall back to slot B.
+  EXPECT_EQ(AVB_AB_FLOW_RESULT_OK,
+            avb_ab_flow(ops_.avb_ab_ops(),
+                        requested_partitions,
+                        false /* allow_verification_error */,
+                        &data));
+  ExpMD(0,
+        0,
+        0,  // A: pri, tries, successful
+        14,
+        0,
+        1,                           // B: pri, tries, successful
+        MakeRollbackIndexes(0, 0));  // stored_rollback_indexes
+  ASSERT_NE(nullptr, data);
+  EXPECT_EQ("_b", std::string(data->ab_suffix));
+  avb_slot_verify_data_free(data);
+}
+
+TEST_F(AvbABFlowTest, TriesRemainingNotDecreasingIfNotPriority) {
+  AvbSlotVerifyData* data;
+  const char* requested_partitions[] = {"boot", NULL};
+
+  SetMD(15,
+        0,
+        1,
+        SV_OK,
+        0,
+        0,  // A: pri, tries, success, slot_validity, RIs
+        14,
+        7,
+        0,
+        SV_OK,
+        0,
+        0,  // B: pri, tries, success, slot_validity, RIs
+        MakeRollbackIndexes(0, 0));  // stored_rollback_indexes
+  EXPECT_EQ(AVB_AB_FLOW_RESULT_OK,
+            avb_ab_flow(ops_.avb_ab_ops(),
+                        requested_partitions,
+                        false /* allow_verification_error */,
+                        &data));
+  ExpMD(15,
+        0,
+        1,  // A: pri, tries, successful
+        14,
+        7,
+        0,                           // B: pri, tries, successful
+        MakeRollbackIndexes(0, 0));  // stored_rollback_indexes
+  ASSERT_NE(nullptr, data);
+  EXPECT_EQ("_a", std::string(data->ab_suffix));
+  avb_slot_verify_data_free(data);
+}
+
+TEST_F(AvbABFlowTest, InvalidSlotIsMarkedAsSuch) {
+  AvbSlotVerifyData* data;
+  const char* requested_partitions[] = {"boot", NULL};
+
+  // Slot A is invalid.
+  SetMD(15,
+        0,
+        1,
+        SV_INV,
+        0,
+        0,  // A: pri, tries, success, slot_validity, RIs
+        14,
+        0,
+        1,
+        SV_OK,
+        0,
+        0,  // B: pri, tries, success, slot_validity, RIs
+        MakeRollbackIndexes(0, 0));  // stored_rollback_indexes
+  EXPECT_EQ(AVB_AB_FLOW_RESULT_OK,
+            avb_ab_flow(ops_.avb_ab_ops(),
+                        requested_partitions,
+                        false /* allow_verification_error */,
+                        &data));
+  ExpMD(0,
+        0,
+        0,  // A: pri, tries, successful
+        14,
+        0,
+        1,                           // B: pri, tries, successful
+        MakeRollbackIndexes(0, 0));  // stored_rollback_indexes
+  ASSERT_NE(nullptr, data);
+  EXPECT_EQ("_b", std::string(data->ab_suffix));
+  avb_slot_verify_data_free(data);
+
+  // Slot B is invalid.
+  SetMD(15,
+        0,
+        1,
+        SV_OK,
+        0,
+        0,  // A: pri, tries, success, slot_validity, RIs
+        14,
+        0,
+        1,
+        SV_INV,
+        0,
+        0,  // B: pri, tries, success, slot_validity, RIs
+        MakeRollbackIndexes(0, 0));  // stored_rollback_indexes
+  EXPECT_EQ(AVB_AB_FLOW_RESULT_OK,
+            avb_ab_flow(ops_.avb_ab_ops(),
+                        requested_partitions,
+                        false /* allow_verification_error */,
+                        &data));
+  ExpMD(15,
+        0,
+        1,  // A: pri, tries, successful
+        0,
+        0,
+        0,                           // B: pri, tries, successful
+        MakeRollbackIndexes(0, 0));  // stored_rollback_indexes
+  ASSERT_NE(nullptr, data);
+  EXPECT_EQ("_a", std::string(data->ab_suffix));
+  avb_slot_verify_data_free(data);
+
+  // Both slots are invalid.
+  SetMD(15,
+        0,
+        1,
+        SV_INV,
+        0,
+        0,  // A: pri, tries, success, slot_validity, RIs
+        14,
+        0,
+        1,
+        SV_INV,
+        0,
+        0,  // B: pri, tries, success, slot_validity, RIs
+        MakeRollbackIndexes(0, 0));  // stored_rollback_indexes
+  EXPECT_EQ(AVB_AB_FLOW_RESULT_ERROR_NO_BOOTABLE_SLOTS,
+            avb_ab_flow(ops_.avb_ab_ops(),
+                        requested_partitions,
+                        false /* allow_verification_error */,
+                        &data));
+  ExpMD(0,
+        0,
+        0,  // A: pri, tries, successful
+        0,
+        0,
+        0,                           // B: pri, tries, successful
+        MakeRollbackIndexes(0, 0));  // stored_rollback_indexes
+  ASSERT_EQ(nullptr, data);
+}
+
+TEST_F(AvbABFlowTest, UnverifiedSlotIsMarkedAsSuch) {
+  AvbSlotVerifyData* data;
+  const char* requested_partitions[] = {"boot", NULL};
+
+  // Slot A fails verification.
+  SetMD(15,
+        0,
+        1,
+        SV_UNV,
+        0,
+        0,  // A: pri, tries, success, slot_validity, RIs
+        14,
+        0,
+        1,
+        SV_OK,
+        0,
+        0,  // B: pri, tries, success, slot_validity, RIs
+        MakeRollbackIndexes(0, 0));  // stored_rollback_indexes
+  EXPECT_EQ(AVB_AB_FLOW_RESULT_OK,
+            avb_ab_flow(ops_.avb_ab_ops(),
+                        requested_partitions,
+                        false /* allow_verification_error */,
+                        &data));
+  ExpMD(0,
+        0,
+        0,  // A: pri, tries, successful
+        14,
+        0,
+        1,                           // B: pri, tries, successful
+        MakeRollbackIndexes(0, 0));  // stored_rollback_indexes
+  ASSERT_NE(nullptr, data);
+  EXPECT_EQ("_b", std::string(data->ab_suffix));
+  avb_slot_verify_data_free(data);
+
+  // Slot B fails verification.
+  SetMD(15,
+        0,
+        1,
+        SV_OK,
+        0,
+        0,  // A: pri, tries, success, slot_validity, RIs
+        14,
+        0,
+        1,
+        SV_UNV,
+        0,
+        0,  // B: pri, tries, success, slot_validity, RIs
+        MakeRollbackIndexes(0, 0));  // stored_rollback_indexes
+  EXPECT_EQ(AVB_AB_FLOW_RESULT_OK,
+            avb_ab_flow(ops_.avb_ab_ops(),
+                        requested_partitions,
+                        false /* allow_verification_error */,
+                        &data));
+  ExpMD(15,
+        0,
+        1,  // A: pri, tries, successful
+        0,
+        0,
+        0,                           // B: pri, tries, successful
+        MakeRollbackIndexes(0, 0));  // stored_rollback_indexes
+  ASSERT_NE(nullptr, data);
+  EXPECT_EQ("_a", std::string(data->ab_suffix));
+  avb_slot_verify_data_free(data);
+
+  // Both slots fail verification.
+  SetMD(15,
+        0,
+        1,
+        SV_UNV,
+        0,
+        0,  // A: pri, tries, success, slot_validity, RIs
+        14,
+        0,
+        1,
+        SV_UNV,
+        0,
+        0,  // B: pri, tries, success, slot_validity, RIs
+        MakeRollbackIndexes(0, 0));  // stored_rollback_indexes
+  EXPECT_EQ(AVB_AB_FLOW_RESULT_ERROR_NO_BOOTABLE_SLOTS,
+            avb_ab_flow(ops_.avb_ab_ops(),
+                        requested_partitions,
+                        false /* allow_verification_error */,
+                        &data));
+  ExpMD(0,
+        0,
+        0,  // A: pri, tries, successful
+        0,
+        0,
+        0,                           // B: pri, tries, successful
+        MakeRollbackIndexes(0, 0));  // stored_rollback_indexes
+  ASSERT_EQ(nullptr, data);
+}
+
+TEST_F(AvbABFlowTest, RollbackIndexFailures) {
+  AvbSlotVerifyData* data;
+  const char* requested_partitions[] = {"boot", NULL};
+
+  // Slot A rollback index failure for 'boot'.
+  SetMD(15,
+        0,
+        1,
+        SV_OK,
+        0,
+        2,  // A: pri, tries, success, slot_validity, RIs
+        14,
+        0,
+        1,
+        SV_OK,
+        2,
+        2,  // B: pri, tries, success, slot_validity, RIs
+        MakeRollbackIndexes(2, 2));  // stored_rollback_indexes
+  EXPECT_EQ(AVB_AB_FLOW_RESULT_OK,
+            avb_ab_flow(ops_.avb_ab_ops(),
+                        requested_partitions,
+                        false /* allow_verification_error */,
+                        &data));
+  ExpMD(0,
+        0,
+        0,  // A: pri, tries, successful
+        14,
+        0,
+        1,                           // B: pri, tries, successful
+        MakeRollbackIndexes(2, 2));  // stored_rollback_indexes
+  ASSERT_NE(nullptr, data);
+  EXPECT_EQ("_b", std::string(data->ab_suffix));
+  avb_slot_verify_data_free(data);
+
+  // Slot A rollback index failure for 'odm'.
+  SetMD(15,
+        0,
+        1,
+        SV_OK,
+        2,
+        0,  // A: pri, tries, success, slot_validity, RIs
+        14,
+        0,
+        1,
+        SV_OK,
+        2,
+        2,  // B: pri, tries, success, slot_validity, RIs
+        MakeRollbackIndexes(2, 2));  // stored_rollback_indexes
+  EXPECT_EQ(AVB_AB_FLOW_RESULT_OK,
+            avb_ab_flow(ops_.avb_ab_ops(),
+                        requested_partitions,
+                        false /* allow_verification_error */,
+                        &data));
+  ExpMD(0,
+        0,
+        0,  // A: pri, tries, successful
+        14,
+        0,
+        1,                           // B: pri, tries, successful
+        MakeRollbackIndexes(2, 2));  // stored_rollback_indexes
+  ASSERT_NE(nullptr, data);
+  EXPECT_EQ("_b", std::string(data->ab_suffix));
+  avb_slot_verify_data_free(data);
+}
+
+TEST_F(AvbABFlowTest, StoredRollbackIndexBumped) {
+  AvbSlotVerifyData* data;
+  const char* requested_partitions[] = {"boot", NULL};
+
+  SetMD(15,
+        0,
+        1,
+        SV_OK,
+        3,
+        3,  // A: pri, tries, success, slot_validity, RIs
+        14,
+        0,
+        1,
+        SV_OK,
+        3,
+        3,  // B: pri, tries, success, slot_validity, RIs
+        MakeRollbackIndexes(2, 2));  // stored_rollback_indexes
+  EXPECT_EQ(AVB_AB_FLOW_RESULT_OK,
+            avb_ab_flow(ops_.avb_ab_ops(),
+                        requested_partitions,
+                        false /* allow_verification_error */,
+                        &data));
+  ExpMD(15,
+        0,
+        1,  // A: pri, tries, successful
+        14,
+        0,
+        1,                           // B: pri, tries, successful
+        MakeRollbackIndexes(3, 3));  // stored_rollback_indexes
+  ASSERT_NE(nullptr, data);
+  EXPECT_EQ("_a", std::string(data->ab_suffix));
+  avb_slot_verify_data_free(data);
+
+  // The case where different partitions have different rollback
+  // index values.
+  SetMD(15,
+        0,
+        1,
+        SV_OK,
+        4,
+        9,  // A: pri, tries, success, slot_validity, RIs
+        14,
+        0,
+        1,
+        SV_OK,
+        5,
+        7,  // B: pri, tries, success, slot_validity, RIs
+        MakeRollbackIndexes(0, 0));  // stored_rollback_indexes
+  EXPECT_EQ(AVB_AB_FLOW_RESULT_OK,
+            avb_ab_flow(ops_.avb_ab_ops(),
+                        requested_partitions,
+                        false /* allow_verification_error */,
+                        &data));
+  ExpMD(15,
+        0,
+        1,  // A: pri, tries, successful
+        14,
+        0,
+        1,                           // B: pri, tries, successful
+        MakeRollbackIndexes(4, 7));  // stored_rollback_indexes
+  ASSERT_NE(nullptr, data);
+  EXPECT_EQ("_a", std::string(data->ab_suffix));
+  avb_slot_verify_data_free(data);
+
+  // If the slot with the low RI fails verification (or is invalid),
+  // check that these low Rollback Indexs are not taken into account
+  // after marking it as unbootable.
+  SetMD(15,
+        0,
+        1,
+        SV_INV,
+        4,
+        9,  // A: pri, tries, success, slot_validity, RIs
+        14,
+        0,
+        1,
+        SV_OK,
+        5,
+        7,  // B: pri, tries, success, slot_validity, RIs
+        MakeRollbackIndexes(0, 0));  // stored_rollback_indexes
+  EXPECT_EQ(AVB_AB_FLOW_RESULT_OK,
+            avb_ab_flow(ops_.avb_ab_ops(),
+                        requested_partitions,
+                        false /* allow_verification_error */,
+                        &data));
+  ExpMD(0,
+        0,
+        0,  // A: pri, tries, successful
+        14,
+        0,
+        1,                           // B: pri, tries, successful
+        MakeRollbackIndexes(5, 7));  // stored_rollback_indexes
+  ASSERT_NE(nullptr, data);
+  EXPECT_EQ("_b", std::string(data->ab_suffix));
+  avb_slot_verify_data_free(data);
+}
+
+TEST_F(AvbABFlowTest, MarkSlotActive) {
+  SetMD(15,
+        0,
+        1,
+        SV_INV,
+        0,
+        0,  // A: pri, tries, success, slot_validity, RIs
+        11,
+        0,
+        1,
+        SV_OK,
+        0,
+        0,  // B: pri, tries, success, slot_validity, RIs
+        MakeRollbackIndexes(0, 0));  // stored_rollback_indexes
+  EXPECT_EQ(AVB_IO_RESULT_OK, avb_ab_mark_slot_active(ops_.avb_ab_ops(), 0));
+  ExpMD(15,
+        7,
+        0,  // A: pri, tries, successful
+        11,
+        0,
+        1,                           // B: pri, tries, successful
+        MakeRollbackIndexes(0, 0));  // stored_rollback_indexes
+
+  // Note how priority of slot A is altered to make room for newly
+  // activated slot.
+  SetMD(15,
+        0,
+        1,
+        SV_INV,
+        0,
+        0,  // A: pri, tries, success, slot_validity, RIs
+        14,
+        0,
+        1,
+        SV_OK,
+        0,
+        0,  // B: pri, tries, success, slot_validity, RIs
+        MakeRollbackIndexes(0, 0));  // stored_rollback_indexes
+  EXPECT_EQ(AVB_IO_RESULT_OK, avb_ab_mark_slot_active(ops_.avb_ab_ops(), 1));
+  ExpMD(14,
+        0,
+        1,  // A: pri, tries, successful
+        15,
+        7,
+        0,                           // B: pri, tries, successful
+        MakeRollbackIndexes(0, 0));  // stored_rollback_indexes
+}
+
+TEST_F(AvbABFlowTest, MarkSlotUnbootable) {
+  SetMD(15,
+        0,
+        1,
+        SV_INV,
+        0,
+        0,  // A: pri, tries, success, slot_validity, RIs
+        11,
+        0,
+        1,
+        SV_OK,
+        0,
+        0,  // B: pri, tries, success, slot_validity, RIs
+        MakeRollbackIndexes(0, 0));  // stored_rollback_indexes
+  EXPECT_EQ(AVB_IO_RESULT_OK,
+            avb_ab_mark_slot_unbootable(ops_.avb_ab_ops(), 0));
+  ExpMD(0,
+        0,
+        0,  // A: pri, tries, successful
+        11,
+        0,
+        1,                           // B: pri, tries, successful
+        MakeRollbackIndexes(0, 0));  // stored_rollback_indexes
+
+  SetMD(15,
+        0,
+        1,
+        SV_INV,
+        0,
+        0,  // A: pri, tries, success, slot_validity, RIs
+        14,
+        0,
+        1,
+        SV_OK,
+        0,
+        0,  // B: pri, tries, success, slot_validity, RIs
+        MakeRollbackIndexes(0, 0));  // stored_rollback_indexes
+  EXPECT_EQ(AVB_IO_RESULT_OK,
+            avb_ab_mark_slot_unbootable(ops_.avb_ab_ops(), 1));
+  ExpMD(15,
+        0,
+        1,  // A: pri, tries, successful
+        0,
+        0,
+        0,                           // B: pri, tries, successful
+        MakeRollbackIndexes(0, 0));  // stored_rollback_indexes
+}
+
+TEST_F(AvbABFlowTest, MarkSlotSuccessful) {
+  SetMD(15,
+        5,
+        0,
+        SV_INV,
+        0,
+        0,  // A: pri, tries, success, slot_validity, RIs
+        11,
+        3,
+        0,
+        SV_OK,
+        0,
+        0,  // B: pri, tries, success, slot_validity, RIs
+        MakeRollbackIndexes(0, 0));  // stored_rollback_indexes
+  EXPECT_EQ(AVB_IO_RESULT_OK,
+            avb_ab_mark_slot_successful(ops_.avb_ab_ops(), 0));
+  ExpMD(15,
+        0,
+        1,  // A: pri, tries, successful
+        11,
+        3,
+        0,                           // B: pri, tries, successful
+        MakeRollbackIndexes(0, 0));  // stored_rollback_indexes
+
+  SetMD(15,
+        5,
+        0,
+        SV_INV,
+        0,
+        0,  // A: pri, tries, success, slot_validity, RIs
+        14,
+        0,
+        1,
+        SV_OK,
+        0,
+        0,  // B: pri, tries, success, slot_validity, RIs
+        MakeRollbackIndexes(0, 0));  // stored_rollback_indexes
+  EXPECT_EQ(AVB_IO_RESULT_OK,
+            avb_ab_mark_slot_successful(ops_.avb_ab_ops(), 1));
+  ExpMD(15,
+        5,
+        0,  // A: pri, tries, successful
+        14,
+        0,
+        1,                           // B: pri, tries, successful
+        MakeRollbackIndexes(0, 0));  // stored_rollback_indexes
+
+  // Marking an unbootable slot (A) as successful won't work (it's a
+  // programmer error to do so)... notice however that the unbootable
+  // slot is normalized in the process.
+  SetMD(0,
+        3,
+        2,
+        SV_INV,
+        0,
+        0,  // A: pri, tries, success, slot_validity, RIs
+        14,
+        0,
+        1,
+        SV_OK,
+        0,
+        0,  // B: pri, tries, success, slot_validity, RIs
+        MakeRollbackIndexes(0, 0));  // stored_rollback_indexes
+  EXPECT_EQ(AVB_IO_RESULT_OK,
+            avb_ab_mark_slot_successful(ops_.avb_ab_ops(), 0));
+  ExpMD(0,
+        0,
+        0,  // A: pri, tries, successful
+        14,
+        0,
+        1,                           // B: pri, tries, successful
+        MakeRollbackIndexes(0, 0));  // stored_rollback_indexes
+}
+
+static AvbABData my_serialized_data;
+
+static AvbIOResult my_write_ab_metadata(AvbABOps* ops,
+                                        const struct AvbABData* data) {
+  avb_ab_data_update_crc_and_byteswap(data, &my_serialized_data);
+  return AVB_IO_RESULT_OK;
+}
+
+static AvbIOResult my_read_ab_metadata(AvbABOps* ops, struct AvbABData* data) {
+  if (!avb_ab_data_verify_and_byteswap(&my_serialized_data, data)) {
+    avb_error(
+        "Error validating A/B metadata from persistent storage. "
+        "Resetting and writing new A/B metadata to persistent storage.\n");
+    avb_ab_data_init(data);
+    return my_write_ab_metadata(ops, data);
+  }
+  return AVB_IO_RESULT_OK;
+}
+
+TEST_F(AvbABFlowTest, OtherMetadataStorage) {
+  AvbSlotVerifyData* data;
+  const char* requested_partitions[] = {"boot", NULL};
+
+  // Use our own A/B storage routines (see above).
+  ops_.avb_ab_ops()->read_ab_metadata = my_read_ab_metadata;
+  ops_.avb_ab_ops()->write_ab_metadata = my_write_ab_metadata;
+
+  SetMD(14,
+        0,
+        1,
+        SV_OK,
+        0,
+        0,  // A: pri, tries, success, slot_validity, RIs
+        15,
+        0,
+        1,
+        SV_OK,
+        0,
+        0,  // B: pri, tries, success, slot_validity, RIs
+        MakeRollbackIndexes(0, 0));  // stored_rollback_indexes
+  EXPECT_EQ(AVB_AB_FLOW_RESULT_OK,
+            avb_ab_flow(ops_.avb_ab_ops(),
+                        requested_partitions,
+                        false /* allow_verification_error */,
+                        &data));
+  ExpMD(14,
+        0,
+        1,  // A: pri, tries, successful
+        15,
+        0,
+        1,                           // B: pri, tries, successful
+        MakeRollbackIndexes(0, 0));  // stored_rollback_indexes
+  ASSERT_NE(nullptr, data);
+  EXPECT_EQ("_b", std::string(data->ab_suffix));
+  avb_slot_verify_data_free(data);
+
+  // Also check the other slot.
+  SetMD(15,
+        0,
+        1,
+        SV_OK,
+        0,
+        0,  // A: pri, tries, success, slot_validity, RIs
+        14,
+        0,
+        1,
+        SV_OK,
+        0,
+        0,  // B: pri, tries, success, slot_validity, RIs
+        MakeRollbackIndexes(0, 0));  // stored_rollback_indexes
+  EXPECT_EQ(AVB_AB_FLOW_RESULT_OK,
+            avb_ab_flow(ops_.avb_ab_ops(),
+                        requested_partitions,
+                        false /* allow_verification_error */,
+                        &data));
+  ExpMD(15,
+        0,
+        1,  // A: pri, tries, successful
+        14,
+        0,
+        1,                           // B: pri, tries, successful
+        MakeRollbackIndexes(0, 0));  // stored_rollback_indexes
+  ASSERT_NE(nullptr, data);
+  EXPECT_EQ("_a", std::string(data->ab_suffix));
+  avb_slot_verify_data_free(data);
+
+  // Check that 'misc' hasn't been written to at all.
+  std::string misc_data;
+  base::FilePath misc_path = testdir_.Append("misc.img");
+  ASSERT_TRUE(base::ReadFileToString(misc_path, &misc_data));
+  EXPECT_EQ(size_t(MISC_PART_SIZE), misc_data.size());
+  for (size_t n = 0; n < misc_data.size(); n++) {
+    ASSERT_EQ(uint8_t(misc_data[n]), 0);
+  }
+}
+
+TEST_F(AvbABFlowTest, UnlockedUnverifiedSlot) {
+  AvbSlotVerifyData* data;
+  const char* requested_partitions[] = {"boot", NULL};
+
+  SetMD(14,
+        0,
+        1,
+        SV_OK,
+        0,
+        0,  // A: pri, tries, success, slot_validity, RIs
+        15,
+        0,
+        1,
+        SV_UNV,
+        0,
+        0,  // B: pri, tries, success, slot_validity, RIs
+        MakeRollbackIndexes(0, 0));  // stored_rollback_indexes
+  EXPECT_EQ(AVB_AB_FLOW_RESULT_OK_WITH_VERIFICATION_ERROR,
+            avb_ab_flow(ops_.avb_ab_ops(),
+                        requested_partitions,
+                        true /* allow_verification_error */,
+                        &data));
+  ExpMD(14,
+        0,
+        1,  // A: pri, tries, successful
+        15,
+        0,
+        1,                           // B: pri, tries, successful
+        MakeRollbackIndexes(0, 0));  // stored_rollback_indexes
+  ASSERT_NE(nullptr, data);
+  EXPECT_EQ("_b", std::string(data->ab_suffix));
+  avb_slot_verify_data_free(data);
+
+  // Also check the other slot.
+  SetMD(15,
+        0,
+        1,
+        SV_UNV,
+        0,
+        0,  // A: pri, tries, success, slot_validity, RIs
+        14,
+        0,
+        1,
+        SV_OK,
+        0,
+        0,  // B: pri, tries, success, slot_validity, RIs
+        MakeRollbackIndexes(0, 0));  // stored_rollback_indexes
+  EXPECT_EQ(AVB_AB_FLOW_RESULT_OK_WITH_VERIFICATION_ERROR,
+            avb_ab_flow(ops_.avb_ab_ops(),
+                        requested_partitions,
+                        true /* allow_verification_error */,
+                        &data));
+  ExpMD(15,
+        0,
+        1,  // A: pri, tries, successful
+        14,
+        0,
+        1,                           // B: pri, tries, successful
+        MakeRollbackIndexes(0, 0));  // stored_rollback_indexes
+  ASSERT_NE(nullptr, data);
+  EXPECT_EQ("_a", std::string(data->ab_suffix));
+  avb_slot_verify_data_free(data);
+}
+
+TEST_F(AvbABFlowTest, AvbtoolMetadataGeneratorEmptyFile) {
+  AvbABData data;
+
+  base::FilePath misc_path = testdir_.Append("misc.img");
+  EXPECT_COMMAND(0,
+                 "./avbtool set_ab_metadata"
+                 " --misc_image %s"
+                 " --slot_data 13:3:0:11:2:1",
+                 misc_path.value().c_str());
+
+  EXPECT_EQ(AVB_IO_RESULT_OK,
+            ops_.avb_ab_ops()->read_ab_metadata(ops_.avb_ab_ops(), &data));
+  EXPECT_EQ(13, data.slots[0].priority);
+  EXPECT_EQ(3, data.slots[0].tries_remaining);
+  EXPECT_EQ(0, data.slots[0].successful_boot);
+  EXPECT_EQ(11, data.slots[1].priority);
+  EXPECT_EQ(2, data.slots[1].tries_remaining);
+  EXPECT_EQ(1, data.slots[1].successful_boot);
+}
+
+TEST_F(AvbABFlowTest, AvbtoolMetadataGeneratorExistingFile) {
+  AvbABData data;
+  size_t n;
+
+  size_t misc_size = 1024 * 1024;
+  base::FilePath misc_path = GenerateImage("misc.img", misc_size);
+  EXPECT_COMMAND(0,
+                 "./avbtool set_ab_metadata"
+                 " --misc_image %s"
+                 " --slot_data 12:2:1:10:5:0",
+                 misc_path.value().c_str());
+
+  EXPECT_EQ(AVB_IO_RESULT_OK,
+            ops_.avb_ab_ops()->read_ab_metadata(ops_.avb_ab_ops(), &data));
+  EXPECT_EQ(12, data.slots[0].priority);
+  EXPECT_EQ(2, data.slots[0].tries_remaining);
+  EXPECT_EQ(1, data.slots[0].successful_boot);
+  EXPECT_EQ(10, data.slots[1].priority);
+  EXPECT_EQ(5, data.slots[1].tries_remaining);
+  EXPECT_EQ(0, data.slots[1].successful_boot);
+
+  std::string misc_data;
+  ASSERT_TRUE(base::ReadFileToString(misc_path, &misc_data));
+  EXPECT_EQ(misc_size, misc_data.size());
+  for (n = 0; n < 2048; n++) {
+    ASSERT_EQ(uint8_t(misc_data[n]), uint8_t(n));
+  }
+  for (n = 2048 + 32; n < misc_data.size(); n++) {
+    ASSERT_EQ(uint8_t(misc_data[n]), uint8_t(n));
+  }
+}
+
+}  // namespace avb
diff --git a/avb/test/avb_atx_validate_unittest.cc b/avb/test/avb_atx_validate_unittest.cc
new file mode 100644
index 0000000..6efa856
--- /dev/null
+++ b/avb/test/avb_atx_validate_unittest.cc
@@ -0,0 +1,689 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <string.h>
+
+#include <base/files/file_util.h>
+#include <gtest/gtest.h>
+#include <openssl/objects.h>
+#include <openssl/pem.h>
+#include <openssl/rsa.h>
+#include <openssl/sha.h>
+
+#include <libavb_atx/libavb_atx.h>
+
+#include "avb_unittest_util.h"
+#include "fake_avb_ops.h"
+
+namespace {
+
+const char kMetadataPath[] = "test/data/atx_metadata.bin";
+const char kPermanentAttributesPath[] =
+    "test/data/atx_permanent_attributes.bin";
+const char kPRKPrivateKeyPath[] = "test/data/testkey_rsa4096.pem";
+const char kPIKPrivateKeyPath[] = "test/data/testkey_rsa2048.pem";
+
+class ScopedRSA {
+ public:
+  ScopedRSA(const char* pem_key_path) {
+    FILE* file = fopen(pem_key_path, "r");
+    rsa_ = PEM_read_RSAPrivateKey(file, nullptr, nullptr, nullptr);
+    fclose(file);
+  }
+
+  ~ScopedRSA() {
+    if (rsa_) {
+      RSA_free(rsa_);
+    }
+  }
+
+  // PKCS #1 v1.5 signature using SHA512 if |rsa_| is 4096 bits, SHA256
+  // otherwise. Returns true on success.
+  bool Sign(const void* data_to_sign, size_t length, uint8_t signature[]) {
+    bool use_sha512 = (RSA_size(rsa_) == AVB_RSA4096_NUM_BYTES);
+    uint8_t digest[AVB_SHA512_DIGEST_SIZE];
+    unsigned int digest_size =
+        use_sha512 ? AVB_SHA512_DIGEST_SIZE : AVB_SHA256_DIGEST_SIZE;
+    const unsigned char* data_to_sign_buf =
+        reinterpret_cast<const unsigned char*>(data_to_sign);
+    if (use_sha512) {
+      SHA512(data_to_sign_buf, length, digest);
+    } else {
+      SHA256(data_to_sign_buf, length, digest);
+    }
+    unsigned int signature_length = 0;
+    return (1 == RSA_sign(use_sha512 ? NID_sha512 : NID_sha256,
+                          digest,
+                          digest_size,
+                          signature,
+                          &signature_length,
+                          rsa_));
+  }
+
+ private:
+  RSA* rsa_;
+};
+
+} /* namespace */
+
+namespace avb {
+
+class AvbAtxValidateTest : public ::testing::Test, public FakeAvbOpsDelegate {
+ public:
+  ~AvbAtxValidateTest() override {}
+
+  void SetUp() override {
+    ReadDefaultData();
+    ops_.set_delegate(this);
+    ops_.set_permanent_attributes(attributes_);
+    ops_.set_stored_rollback_indexes({{AVB_ATX_PIK_VERSION_LOCATION, 0},
+                                      {AVB_ATX_PSK_VERSION_LOCATION, 0},
+                                      {AVB_ATX_GSK_VERSION_LOCATION, 0}});
+  }
+
+  // FakeAvbOpsDelegate methods.
+  AvbIOResult read_from_partition(const char* partition,
+                                  int64_t offset,
+                                  size_t num_bytes,
+                                  void* buffer,
+                                  size_t* out_num_read) override {
+    // Expect method not used.
+    return AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION;
+  }
+
+  AvbIOResult write_to_partition(const char* partition,
+                                 int64_t offset,
+                                 size_t num_bytes,
+                                 const void* buffer) override {
+    // Expect method not used.
+    return AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION;
+  }
+
+  AvbIOResult validate_vbmeta_public_key(AvbOps* ops,
+                                         const uint8_t* public_key_data,
+                                         size_t public_key_length,
+                                         const uint8_t* public_key_metadata,
+                                         size_t public_key_metadata_length,
+                                         bool* out_key_is_trusted) override {
+    // Expect method not used.
+    return AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION;
+  }
+
+  AvbIOResult read_rollback_index(AvbOps* ops,
+                                  size_t rollback_index_slot,
+                                  uint64_t* out_rollback_index) override {
+    if ((fail_read_pik_rollback_index_ &&
+         rollback_index_slot == AVB_ATX_PIK_VERSION_LOCATION) ||
+        (fail_read_psk_rollback_index_ &&
+         rollback_index_slot == AVB_ATX_PSK_VERSION_LOCATION) ||
+        (fail_read_gsk_rollback_index_ &&
+         rollback_index_slot == AVB_ATX_GSK_VERSION_LOCATION)) {
+      return AVB_IO_RESULT_ERROR_IO;
+    }
+    return ops_.read_rollback_index(
+        ops, rollback_index_slot, out_rollback_index);
+  }
+
+  AvbIOResult write_rollback_index(AvbOps* ops,
+                                   size_t rollback_index_slot,
+                                   uint64_t rollback_index) override {
+    // Expect method not used.
+    return AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION;
+  }
+
+  AvbIOResult read_is_device_unlocked(AvbOps* ops,
+                                      bool* out_is_device_unlocked) override {
+    // Expect method not used.
+    return AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION;
+  }
+
+  AvbIOResult get_unique_guid_for_partition(AvbOps* ops,
+                                            const char* partition,
+                                            char* guid_buf,
+                                            size_t guid_buf_size) override {
+    // Expect method not used.
+    return AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION;
+  }
+
+  AvbIOResult read_permanent_attributes(
+      AvbAtxPermanentAttributes* attributes) override {
+    if (fail_read_permanent_attributes_) {
+      return AVB_IO_RESULT_ERROR_IO;
+    }
+    return ops_.read_permanent_attributes(attributes);
+  }
+
+  AvbIOResult read_permanent_attributes_hash(
+      uint8_t hash[AVB_SHA256_DIGEST_SIZE]) override {
+    if (fail_read_permanent_attributes_hash_) {
+      return AVB_IO_RESULT_ERROR_IO;
+    }
+    return ops_.read_permanent_attributes_hash(hash);
+  }
+
+ protected:
+  virtual AvbIOResult Validate(bool* is_trusted) {
+    return avb_atx_validate_vbmeta_public_key(
+        ops_.avb_ops(),
+        metadata_.product_signing_key_certificate.signed_data.public_key,
+        AVB_ATX_PUBLIC_KEY_SIZE_2048,
+        reinterpret_cast<const uint8_t*>(&metadata_),
+        sizeof(metadata_),
+        is_trusted);
+  }
+
+  void SignPIKCertificate() {
+    memset(metadata_.product_intermediate_key_certificate.signature,
+           0,
+           AVB_RSA4096_NUM_BYTES);
+    ScopedRSA key(kPRKPrivateKeyPath);
+    ASSERT_TRUE(
+        key.Sign(&metadata_.product_intermediate_key_certificate.signed_data,
+                 sizeof(AvbAtxCertificateSignedData),
+                 metadata_.product_intermediate_key_certificate.signature));
+  }
+
+  void SignPSKCertificate() {
+    memset(metadata_.product_signing_key_certificate.signature,
+           0,
+           AVB_RSA2048_NUM_BYTES);
+    ScopedRSA key(kPIKPrivateKeyPath);
+    ASSERT_TRUE(key.Sign(&metadata_.product_signing_key_certificate.signed_data,
+                         sizeof(AvbAtxCertificateSignedData),
+                         metadata_.product_signing_key_certificate.signature));
+  }
+
+  FakeAvbOps ops_;
+  AvbAtxPermanentAttributes attributes_;
+  AvbAtxPublicKeyMetadata metadata_;
+  bool fail_read_permanent_attributes_{false};
+  bool fail_read_permanent_attributes_hash_{false};
+  bool fail_read_pik_rollback_index_{false};
+  bool fail_read_psk_rollback_index_{false};
+  bool fail_read_gsk_rollback_index_{false};
+
+ private:
+  void ReadDefaultData() {
+    std::string tmp;
+    ASSERT_TRUE(base::ReadFileToString(base::FilePath(kMetadataPath), &tmp));
+    ASSERT_EQ(tmp.size(), sizeof(AvbAtxPublicKeyMetadata));
+    memcpy(&metadata_, tmp.data(), tmp.size());
+    ASSERT_TRUE(
+        base::ReadFileToString(base::FilePath(kPermanentAttributesPath), &tmp));
+    ASSERT_EQ(tmp.size(), sizeof(AvbAtxPermanentAttributes));
+    memcpy(&attributes_, tmp.data(), tmp.size());
+  }
+};
+
+TEST_F(AvbAtxValidateTest, Success) {
+  bool is_trusted = false;
+  EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted));
+  EXPECT_TRUE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, SuccessAfterNewSign) {
+  std::string old_pik_sig(
+      reinterpret_cast<char*>(
+          metadata_.product_intermediate_key_certificate.signature),
+      AVB_RSA4096_NUM_BYTES);
+  std::string old_psk_sig(
+      reinterpret_cast<char*>(
+          metadata_.product_signing_key_certificate.signature),
+      AVB_RSA2048_NUM_BYTES);
+  SignPIKCertificate();
+  SignPSKCertificate();
+  std::string new_pik_sig(
+      reinterpret_cast<char*>(
+          metadata_.product_intermediate_key_certificate.signature),
+      AVB_RSA4096_NUM_BYTES);
+  std::string new_psk_sig(
+      reinterpret_cast<char*>(
+          metadata_.product_signing_key_certificate.signature),
+      AVB_RSA2048_NUM_BYTES);
+  EXPECT_EQ(old_pik_sig, new_pik_sig);
+  EXPECT_EQ(old_psk_sig, new_psk_sig);
+  bool is_trusted = false;
+  EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted));
+  EXPECT_TRUE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, FailReadPermamentAttributes) {
+  fail_read_permanent_attributes_ = true;
+  bool is_trusted = true;
+  EXPECT_EQ(AVB_IO_RESULT_ERROR_IO, Validate(&is_trusted));
+  EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, FailReadPermamentAttributesHash) {
+  fail_read_permanent_attributes_hash_ = true;
+  bool is_trusted = true;
+  EXPECT_EQ(AVB_IO_RESULT_ERROR_IO, Validate(&is_trusted));
+  EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, UnsupportedPermanentAttributesVersion) {
+  attributes_.version = 25;
+  ops_.set_permanent_attributes(attributes_);
+  bool is_trusted = true;
+  EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted));
+  EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, PermanentAttributesHashMismatch) {
+  ops_.set_permanent_attributes_hash("bad_hash");
+  bool is_trusted = true;
+  EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted));
+  EXPECT_FALSE(is_trusted);
+}
+
+// A fixture with parameterized metadata length.
+class AvbAtxValidateTestWithMetadataLength
+    : public AvbAtxValidateTest,
+      public ::testing::WithParamInterface<size_t> {
+ protected:
+  AvbIOResult Validate(bool* is_trusted) override {
+    return avb_atx_validate_vbmeta_public_key(
+        ops_.avb_ops(),
+        metadata_.product_signing_key_certificate.signed_data.public_key,
+        AVB_ATX_PUBLIC_KEY_SIZE_2048,
+        reinterpret_cast<const uint8_t*>(&metadata_),
+        GetParam(),
+        is_trusted);
+  }
+};
+
+TEST_P(AvbAtxValidateTestWithMetadataLength, InvalidMetadataLength) {
+  bool is_trusted = true;
+  EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted));
+  EXPECT_FALSE(is_trusted);
+}
+
+// Test a bunch of invalid metadata length values.
+INSTANTIATE_TEST_CASE_P(P,
+                        AvbAtxValidateTestWithMetadataLength,
+                        ::testing::Values(0,
+                                          1,
+                                          sizeof(AvbAtxPublicKeyMetadata) - 1,
+                                          sizeof(AvbAtxPublicKeyMetadata) + 1,
+                                          -1));
+
+TEST_F(AvbAtxValidateTest, UnsupportedMetadataVersion) {
+  metadata_.version = 25;
+  bool is_trusted = true;
+  EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted));
+  EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, FailReadPIKRollbackIndex) {
+  fail_read_pik_rollback_index_ = true;
+  bool is_trusted = true;
+  EXPECT_EQ(AVB_IO_RESULT_ERROR_IO, Validate(&is_trusted));
+  EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, UnsupportedPIKCertificateVersion) {
+  metadata_.product_intermediate_key_certificate.signed_data.version = 25;
+  SignPIKCertificate();
+  bool is_trusted = true;
+  EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted));
+  EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, BadPIKCert_ModifiedSubjectPublicKey) {
+  metadata_.product_intermediate_key_certificate.signed_data.public_key[0] ^= 1;
+  bool is_trusted = true;
+  EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted));
+  EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, BadPIKCert_ModifiedSubject) {
+  metadata_.product_intermediate_key_certificate.signed_data.subject[0] ^= 1;
+  bool is_trusted = true;
+  EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted));
+  EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, BadPIKCert_ModifiedUsage) {
+  metadata_.product_intermediate_key_certificate.signed_data.usage[0] ^= 1;
+  bool is_trusted = true;
+  EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted));
+  EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, BadPIKCert_ModifiedKeyVersion) {
+  metadata_.product_intermediate_key_certificate.signed_data.key_version ^= 1;
+  bool is_trusted = true;
+  EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted));
+  EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, BadPIKCert_BadSignature) {
+  metadata_.product_intermediate_key_certificate.signature[0] ^= 1;
+  bool is_trusted = true;
+  EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted));
+  EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, PIKCertSubjectIgnored) {
+  metadata_.product_intermediate_key_certificate.signed_data.subject[0] ^= 1;
+  SignPIKCertificate();
+  bool is_trusted = false;
+  EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted));
+  EXPECT_TRUE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, PIKCertUnexpectedUsage) {
+  metadata_.product_intermediate_key_certificate.signed_data.usage[0] ^= 1;
+  SignPIKCertificate();
+  bool is_trusted = true;
+  EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted));
+  EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, PIKRollback) {
+  ops_.set_stored_rollback_indexes(
+      {{AVB_ATX_PIK_VERSION_LOCATION,
+        metadata_.product_intermediate_key_certificate.signed_data.key_version +
+            1},
+       {AVB_ATX_PSK_VERSION_LOCATION, 0},
+       {AVB_ATX_GSK_VERSION_LOCATION, 0}});
+  bool is_trusted = true;
+  EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted));
+  EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, FailReadPSKRollbackIndex) {
+  fail_read_psk_rollback_index_ = true;
+  bool is_trusted = true;
+  EXPECT_EQ(AVB_IO_RESULT_ERROR_IO, Validate(&is_trusted));
+  EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, UnsupportedPSKCertificateVersion) {
+  metadata_.product_signing_key_certificate.signed_data.version = 25;
+  SignPSKCertificate();
+  bool is_trusted = true;
+  EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted));
+  EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, BadPSKCert_ModifiedSubjectPublicKey) {
+  metadata_.product_signing_key_certificate.signed_data.public_key[0] ^= 1;
+  bool is_trusted = true;
+  EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted));
+  EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, BadPSKCert_ModifiedSubject) {
+  metadata_.product_signing_key_certificate.signed_data.subject[0] ^= 1;
+  bool is_trusted = true;
+  EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted));
+  EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, BadPSKCert_ModifiedUsage) {
+  metadata_.product_signing_key_certificate.signed_data.usage[0] ^= 1;
+  bool is_trusted = true;
+  EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted));
+  EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, BadPSKCert_ModifiedKeyVersion) {
+  metadata_.product_signing_key_certificate.signed_data.key_version ^= 1;
+  bool is_trusted = true;
+  EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted));
+  EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, BadPSKCert_BadSignature) {
+  metadata_.product_signing_key_certificate.signature[0] ^= 1;
+  bool is_trusted = true;
+  EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted));
+  EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, PSKCertUnexpectedSubject) {
+  metadata_.product_signing_key_certificate.signed_data.subject[0] ^= 1;
+  SignPSKCertificate();
+  bool is_trusted = true;
+  EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted));
+  EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, PSKCertUnexpectedUsage) {
+  metadata_.product_signing_key_certificate.signed_data.usage[0] ^= 1;
+  SignPSKCertificate();
+  bool is_trusted = true;
+  EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted));
+  EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, PSKRollback) {
+  ops_.set_stored_rollback_indexes(
+      {{AVB_ATX_PIK_VERSION_LOCATION, 0},
+       {AVB_ATX_PSK_VERSION_LOCATION,
+        metadata_.product_signing_key_certificate.signed_data.key_version + 1},
+       {AVB_ATX_GSK_VERSION_LOCATION, 0}});
+  bool is_trusted = true;
+  EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted));
+  EXPECT_FALSE(is_trusted);
+}
+
+// A fixture with parameterized public key length.
+class AvbAtxValidateTestWithPublicKeyLength
+    : public AvbAtxValidateTest,
+      public ::testing::WithParamInterface<size_t> {
+ protected:
+  AvbIOResult Validate(bool* is_trusted) override {
+    return avb_atx_validate_vbmeta_public_key(
+        ops_.avb_ops(),
+        metadata_.product_signing_key_certificate.signed_data.public_key,
+        GetParam(),
+        reinterpret_cast<const uint8_t*>(&metadata_),
+        sizeof(metadata_),
+        is_trusted);
+  }
+};
+
+TEST_P(AvbAtxValidateTestWithPublicKeyLength, InvalidPublicKeyLength) {
+  bool is_trusted = true;
+  EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted));
+  EXPECT_FALSE(is_trusted);
+}
+
+// Test a bunch of invalid public key length values.
+INSTANTIATE_TEST_CASE_P(P,
+                        AvbAtxValidateTestWithPublicKeyLength,
+                        ::testing::Values(0,
+                                          1,
+                                          AVB_ATX_PUBLIC_KEY_SIZE_2048 - 1,
+                                          AVB_ATX_PUBLIC_KEY_SIZE_2048 + 1,
+                                          AVB_ATX_PUBLIC_KEY_SIZE_4096,
+                                          -1));
+
+TEST_F(AvbAtxValidateTest, PSKMismatch) {
+  uint8_t bad_key[AVB_ATX_PUBLIC_KEY_SIZE_2048] = {};
+  bool is_trusted = true;
+  EXPECT_EQ(AVB_IO_RESULT_OK,
+            avb_atx_validate_vbmeta_public_key(
+                ops_.avb_ops(),
+                bad_key,
+                AVB_ATX_PUBLIC_KEY_SIZE_2048,
+                reinterpret_cast<const uint8_t*>(&metadata_),
+                sizeof(metadata_),
+                &is_trusted));
+  EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, FailReadGSKRollbackIndex) {
+  fail_read_gsk_rollback_index_ = true;
+  bool is_trusted = true;
+  EXPECT_EQ(AVB_IO_RESULT_ERROR_IO, Validate(&is_trusted));
+  EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, GSKRollback) {
+  ops_.set_stored_rollback_indexes(
+      {{AVB_ATX_PIK_VERSION_LOCATION, 0},
+       {AVB_ATX_PSK_VERSION_LOCATION, 0},
+       {AVB_ATX_GSK_VERSION_LOCATION,
+        metadata_.google_signing_key_version + 1}});
+  bool is_trusted = true;
+  EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted));
+  EXPECT_FALSE(is_trusted);
+}
+
+// A fixture for testing avb_slot_verify() with ATX.
+class AvbAtxSlotVerifyTest : public BaseAvbToolTest, public FakeAvbOpsDelegate {
+ public:
+  ~AvbAtxSlotVerifyTest() override = default;
+
+  void SetUp() override {
+    BaseAvbToolTest::SetUp();
+    ReadAtxDefaultData();
+    ops_.set_partition_dir(testdir_);
+    ops_.set_delegate(this);
+    ops_.set_permanent_attributes(attributes_);
+    ops_.set_stored_rollback_indexes({{0, 0},
+                                      {1, 0},
+                                      {2, 0},
+                                      {3, 0},
+                                      {AVB_ATX_PIK_VERSION_LOCATION, 0},
+                                      {AVB_ATX_PSK_VERSION_LOCATION, 0},
+                                      {AVB_ATX_GSK_VERSION_LOCATION, 0}});
+    ops_.set_stored_is_device_unlocked(false);
+  }
+
+  // FakeAvbOpsDelegate methods. All forward to FakeAvbOps default except for
+  // validate_vbmeta_public_key().
+  AvbIOResult read_from_partition(const char* partition,
+                                  int64_t offset,
+                                  size_t num_bytes,
+                                  void* buffer,
+                                  size_t* out_num_read) override {
+    return ops_.read_from_partition(
+        partition, offset, num_bytes, buffer, out_num_read);
+  }
+
+  AvbIOResult write_to_partition(const char* partition,
+                                 int64_t offset,
+                                 size_t num_bytes,
+                                 const void* buffer) override {
+    return ops_.write_to_partition(partition, offset, num_bytes, buffer);
+  }
+
+  AvbIOResult validate_vbmeta_public_key(AvbOps* ops,
+                                         const uint8_t* public_key_data,
+                                         size_t public_key_length,
+                                         const uint8_t* public_key_metadata,
+                                         size_t public_key_metadata_length,
+                                         bool* out_key_is_trusted) override {
+    // Send to ATX implementation.
+    ++num_atx_calls_;
+    return avb_atx_validate_vbmeta_public_key(ops_.avb_ops(),
+                                              public_key_data,
+                                              public_key_length,
+                                              public_key_metadata,
+                                              public_key_metadata_length,
+                                              out_key_is_trusted);
+  }
+
+  AvbIOResult read_rollback_index(AvbOps* ops,
+                                  size_t rollback_index_slot,
+                                  uint64_t* out_rollback_index) override {
+    return ops_.read_rollback_index(
+        ops, rollback_index_slot, out_rollback_index);
+  }
+
+  AvbIOResult write_rollback_index(AvbOps* ops,
+                                   size_t rollback_index_slot,
+                                   uint64_t rollback_index) override {
+    return ops_.write_rollback_index(ops, rollback_index_slot, rollback_index);
+  }
+
+  AvbIOResult read_is_device_unlocked(AvbOps* ops,
+                                      bool* out_is_device_unlocked) override {
+    return ops_.read_is_device_unlocked(ops, out_is_device_unlocked);
+  }
+
+  AvbIOResult get_unique_guid_for_partition(AvbOps* ops,
+                                            const char* partition,
+                                            char* guid_buf,
+                                            size_t guid_buf_size) override {
+    return ops_.get_unique_guid_for_partition(
+        ops, partition, guid_buf, guid_buf_size);
+  }
+
+  AvbIOResult read_permanent_attributes(
+      AvbAtxPermanentAttributes* attributes) override {
+    return ops_.read_permanent_attributes(attributes);
+  }
+
+  AvbIOResult read_permanent_attributes_hash(
+      uint8_t hash[AVB_SHA256_DIGEST_SIZE]) override {
+    return ops_.read_permanent_attributes_hash(hash);
+  }
+
+ protected:
+  FakeAvbOps ops_;
+  AvbAtxPermanentAttributes attributes_;
+  int num_atx_calls_ = 0;
+
+ private:
+  void ReadAtxDefaultData() {
+    std::string tmp;
+    ASSERT_TRUE(
+        base::ReadFileToString(base::FilePath(kPermanentAttributesPath), &tmp));
+    ASSERT_EQ(tmp.size(), sizeof(AvbAtxPermanentAttributes));
+    memcpy(&attributes_, tmp.data(), tmp.size());
+  }
+};
+
+TEST_F(AvbAtxSlotVerifyTest, SlotVerifyWithAtx) {
+  std::string metadata_option = "--public_key_metadata=";
+  metadata_option += kMetadataPath;
+  GenerateVBMetaImage("vbmeta_a.img",
+                      "SHA256_RSA2048",
+                      0,
+                      base::FilePath("test/data/testkey_rsa2048.pem"),
+                      metadata_option);
+
+  ops_.set_expected_public_key(
+      PublicKeyAVB(base::FilePath("test/data/testkey_rsa2048.pem")));
+
+  AvbSlotVerifyData* slot_data = NULL;
+  const char* requested_partitions[] = {"boot", NULL};
+  EXPECT_EQ(AVB_SLOT_VERIFY_RESULT_OK,
+            avb_slot_verify(ops_.avb_ops(),
+                            requested_partitions,
+                            "_a",
+                            false /* allow_verification_error */,
+                            &slot_data));
+  EXPECT_NE(nullptr, slot_data);
+  avb_slot_verify_data_free(slot_data);
+  EXPECT_EQ(1, num_atx_calls_);
+}
+
+}  // namespace avb
diff --git a/avb/test/avb_slot_verify_unittest.cc b/avb/test/avb_slot_verify_unittest.cc
new file mode 100644
index 0000000..51cfb23
--- /dev/null
+++ b/avb/test/avb_slot_verify_unittest.cc
@@ -0,0 +1,1243 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <iostream>
+
+#include <base/files/file_util.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+
+#include "avb_unittest_util.h"
+#include "fake_avb_ops.h"
+
+namespace avb {
+
+class AvbSlotVerifyTest : public BaseAvbToolTest {
+ public:
+  AvbSlotVerifyTest() {}
+
+  virtual void SetUp() override {
+    BaseAvbToolTest::SetUp();
+    ops_.set_partition_dir(testdir_);
+    ops_.set_stored_rollback_indexes({{0, 0}, {1, 0}, {2, 0}, {3, 0}});
+    ops_.set_stored_is_device_unlocked(false);
+  }
+
+  void CmdlineWithHashtreeVerification(bool hashtree_verification_on);
+
+  FakeAvbOps ops_;
+};
+
+TEST_F(AvbSlotVerifyTest, Basic) {
+  GenerateVBMetaImage("vbmeta_a.img",
+                      "SHA256_RSA2048",
+                      0,
+                      base::FilePath("test/data/testkey_rsa2048.pem"));
+
+  ops_.set_expected_public_key(
+      PublicKeyAVB(base::FilePath("test/data/testkey_rsa2048.pem")));
+
+  AvbSlotVerifyData* slot_data = NULL;
+  const char* requested_partitions[] = {"boot", NULL};
+  EXPECT_EQ(AVB_SLOT_VERIFY_RESULT_OK,
+            avb_slot_verify(ops_.avb_ops(),
+                            requested_partitions,
+                            "_a",
+                            false /* allow_verification_error */,
+                            &slot_data));
+  EXPECT_NE(nullptr, slot_data);
+  EXPECT_EQ(
+      "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta_a "
+      "androidboot.slot_suffix=_a androidboot.vbmeta.device_state=locked "
+      "androidboot.vbmeta.hash_alg=sha256 androidboot.vbmeta.size=1152 "
+      "androidboot.vbmeta.digest="
+      "4161a7e655eabe16c3fe714de5d43736e7c0a190cf08d36c946d2509ce071e4d",
+      std::string(slot_data->cmdline));
+  avb_slot_verify_data_free(slot_data);
+}
+
+TEST_F(AvbSlotVerifyTest, BasicSha512) {
+  GenerateVBMetaImage("vbmeta_a.img",
+                      "SHA512_RSA2048",
+                      0,
+                      base::FilePath("test/data/testkey_rsa2048.pem"));
+
+  ops_.set_expected_public_key(
+      PublicKeyAVB(base::FilePath("test/data/testkey_rsa2048.pem")));
+
+  AvbSlotVerifyData* slot_data = NULL;
+  const char* requested_partitions[] = {"boot", NULL};
+  EXPECT_EQ(AVB_SLOT_VERIFY_RESULT_OK,
+            avb_slot_verify(ops_.avb_ops(),
+                            requested_partitions,
+                            "_a",
+                            false /* allow_verification_error */,
+                            &slot_data));
+  EXPECT_NE(nullptr, slot_data);
+  EXPECT_EQ(
+      "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta_a "
+      "androidboot.slot_suffix=_a androidboot.vbmeta.device_state=locked "
+      "androidboot.vbmeta.hash_alg=sha512 androidboot.vbmeta.size=1152 "
+      "androidboot.vbmeta.digest="
+      "cb913d2f1a884f4e04c1db5bb181f3133fd16ac02fb367a20ef0776c0b07b3656ad1f081"
+      "e01932cf70f38b8960877470b448f1588dff022808387cc52fa77e77",
+      std::string(slot_data->cmdline));
+  avb_slot_verify_data_free(slot_data);
+}
+
+TEST_F(AvbSlotVerifyTest, BasicUnlocked) {
+  GenerateVBMetaImage("vbmeta_a.img",
+                      "SHA256_RSA2048",
+                      0,
+                      base::FilePath("test/data/testkey_rsa2048.pem"));
+
+  ops_.set_expected_public_key(
+      PublicKeyAVB(base::FilePath("test/data/testkey_rsa2048.pem")));
+
+  ops_.set_stored_is_device_unlocked(true);
+
+  AvbSlotVerifyData* slot_data = NULL;
+  const char* requested_partitions[] = {"boot", NULL};
+  EXPECT_EQ(AVB_SLOT_VERIFY_RESULT_OK,
+            avb_slot_verify(ops_.avb_ops(),
+                            requested_partitions,
+                            "_a",
+                            false /* allow_verification_error */,
+                            &slot_data));
+  EXPECT_NE(nullptr, slot_data);
+  EXPECT_EQ(
+      "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta_a "
+      "androidboot.slot_suffix=_a androidboot.vbmeta.device_state=unlocked "
+      "androidboot.vbmeta.hash_alg=sha256 androidboot.vbmeta.size=1152 "
+      "androidboot.vbmeta.digest="
+      "4161a7e655eabe16c3fe714de5d43736e7c0a190cf08d36c946d2509ce071e4d",
+      std::string(slot_data->cmdline));
+  avb_slot_verify_data_free(slot_data);
+}
+
+TEST_F(AvbSlotVerifyTest, SlotDataIsCorrect) {
+  GenerateVBMetaImage("vbmeta_a.img",
+                      "SHA256_RSA2048",
+                      0,
+                      base::FilePath("test/data/testkey_rsa2048.pem"));
+
+  ops_.set_expected_public_key(
+      PublicKeyAVB(base::FilePath("test/data/testkey_rsa2048.pem")));
+
+  AvbSlotVerifyData* slot_data = NULL;
+  const char* requested_partitions[] = {"boot", NULL};
+  EXPECT_EQ(AVB_SLOT_VERIFY_RESULT_OK,
+            avb_slot_verify(ops_.avb_ops(),
+                            requested_partitions,
+                            "_a",
+                            false /* allow_verification_error */,
+                            &slot_data));
+  EXPECT_NE(nullptr, slot_data);
+  avb_slot_verify_data_free(slot_data);
+}
+
+TEST_F(AvbSlotVerifyTest, WrongPublicKey) {
+  GenerateVBMetaImage("vbmeta_a.img",
+                      "SHA256_RSA2048",
+                      0,
+                      base::FilePath("test/data/testkey_rsa2048.pem"));
+
+  AvbSlotVerifyData* slot_data = NULL;
+  const char* requested_partitions[] = {"boot", NULL};
+  EXPECT_EQ(AVB_SLOT_VERIFY_RESULT_ERROR_PUBLIC_KEY_REJECTED,
+            avb_slot_verify(ops_.avb_ops(),
+                            requested_partitions,
+                            "_a",
+                            false /* allow_verification_error */,
+                            &slot_data));
+  EXPECT_EQ(nullptr, slot_data);
+  EXPECT_EQ(AVB_SLOT_VERIFY_RESULT_ERROR_PUBLIC_KEY_REJECTED,
+            avb_slot_verify(ops_.avb_ops(),
+                            requested_partitions,
+                            "_a",
+                            true /* allow_verification_error */,
+                            &slot_data));
+  EXPECT_NE(nullptr, slot_data);
+  avb_slot_verify_data_free(slot_data);
+}
+
+TEST_F(AvbSlotVerifyTest, NoImage) {
+  const char* requested_partitions[] = {"boot", NULL};
+  AvbSlotVerifyData* slot_data = NULL;
+  EXPECT_EQ(AVB_SLOT_VERIFY_RESULT_ERROR_IO,
+            avb_slot_verify(ops_.avb_ops(),
+                            requested_partitions,
+                            "_a",
+                            false /* allow_verification_error */,
+                            &slot_data));
+  EXPECT_EQ(nullptr, slot_data);
+}
+
+TEST_F(AvbSlotVerifyTest, UnsignedVBMeta) {
+  GenerateVBMetaImage("vbmeta_a.img", "", 0, base::FilePath(""));
+
+  AvbSlotVerifyData* slot_data = NULL;
+  const char* requested_partitions[] = {"boot", NULL};
+  EXPECT_EQ(AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION,
+            avb_slot_verify(ops_.avb_ops(),
+                            requested_partitions,
+                            "_a",
+                            false /* allow_verification_error */,
+                            &slot_data));
+  EXPECT_EQ(nullptr, slot_data);
+  EXPECT_EQ(AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION,
+            avb_slot_verify(ops_.avb_ops(),
+                            requested_partitions,
+                            "_a",
+                            true /* allow_verification_error */,
+                            &slot_data));
+  EXPECT_NE(nullptr, slot_data);
+  avb_slot_verify_data_free(slot_data);
+}
+
+TEST_F(AvbSlotVerifyTest, CorruptedImage) {
+  GenerateVBMetaImage("vbmeta_a.img",
+                      "SHA256_RSA2048",
+                      0,
+                      base::FilePath("test/data/testkey_rsa2048.pem"));
+
+  // Corrupt four bytes of data in the end of the image. Since the aux
+  // data is at the end and this data is signed, this will change the
+  // value of the computed hash.
+  uint8_t corrupt_data[4] = {0xff, 0xff, 0xff, 0xff};
+  EXPECT_EQ(AVB_IO_RESULT_OK,
+            ops_.avb_ops()->write_to_partition(ops_.avb_ops(),
+                                               "vbmeta_a",
+                                               -4,  // offset from end
+                                               sizeof corrupt_data,
+                                               corrupt_data));
+
+  AvbSlotVerifyData* slot_data = NULL;
+  const char* requested_partitions[] = {"boot", NULL};
+  EXPECT_EQ(AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION,
+            avb_slot_verify(ops_.avb_ops(),
+                            requested_partitions,
+                            "_a",
+                            false /* allow_verification_error */,
+                            &slot_data));
+  EXPECT_EQ(nullptr, slot_data);
+  EXPECT_EQ(AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION,
+            avb_slot_verify(ops_.avb_ops(),
+                            requested_partitions,
+                            "_a",
+                            true /* allow_verification_error */,
+                            &slot_data));
+  EXPECT_NE(nullptr, slot_data);
+  avb_slot_verify_data_free(slot_data);
+}
+
+TEST_F(AvbSlotVerifyTest, CorruptedMetadata) {
+  GenerateVBMetaImage("vbmeta_a.img",
+                      "SHA256_RSA2048",
+                      0,
+                      base::FilePath("test/data/testkey_rsa2048.pem"));
+
+  // Corrupt four bytes of data in the beginning of the image. Unlike
+  // the CorruptedImage test-case above (which is valid metadata) this
+  // will make the metadata invalid and render the slot unbootable
+  // even if the device is unlocked. Specifically no AvbSlotVerifyData
+  // is returned.
+  uint8_t corrupt_data[4] = {0xff, 0xff, 0xff, 0xff};
+  EXPECT_EQ(AVB_IO_RESULT_OK,
+            ops_.avb_ops()->write_to_partition(ops_.avb_ops(),
+                                               "vbmeta_a",
+                                               0,  // offset: beginning
+                                               sizeof corrupt_data,
+                                               corrupt_data));
+
+  AvbSlotVerifyData* slot_data = NULL;
+  const char* requested_partitions[] = {"boot", NULL};
+  EXPECT_EQ(AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA,
+            avb_slot_verify(ops_.avb_ops(),
+                            requested_partitions,
+                            "_a",
+                            false /* allow_verification_error */,
+                            &slot_data));
+  EXPECT_EQ(nullptr, slot_data);
+}
+
+TEST_F(AvbSlotVerifyTest, RollbackIndex) {
+  GenerateVBMetaImage("vbmeta_a.img",
+                      "SHA256_RSA2048",
+                      42,
+                      base::FilePath("test/data/testkey_rsa2048.pem"));
+
+  ops_.set_expected_public_key(
+      PublicKeyAVB(base::FilePath("test/data/testkey_rsa2048.pem")));
+
+  AvbSlotVerifyData* slot_data = NULL;
+  const char* requested_partitions[] = {"boot", NULL};
+
+  // First try with 42 as the stored rollback index - this should
+  // succeed since the image rollback index is 42 (as set above).
+  ops_.set_stored_rollback_indexes({{0, 42}});
+  EXPECT_EQ(AVB_SLOT_VERIFY_RESULT_OK,
+            avb_slot_verify(ops_.avb_ops(),
+                            requested_partitions,
+                            "_a",
+                            false /* allow_verification_error */,
+                            &slot_data));
+  EXPECT_NE(nullptr, slot_data);
+  avb_slot_verify_data_free(slot_data);
+
+  // Then try with 43 for the stored rollback index - this should fail
+  // because the image has rollback index 42 which is less than 43.
+  ops_.set_stored_rollback_indexes({{0, 43}});
+  EXPECT_EQ(AVB_SLOT_VERIFY_RESULT_ERROR_ROLLBACK_INDEX,
+            avb_slot_verify(ops_.avb_ops(),
+                            requested_partitions,
+                            "_a",
+                            false /* allow_verification_error */,
+                            &slot_data));
+  EXPECT_EQ(nullptr, slot_data);
+  EXPECT_EQ(AVB_SLOT_VERIFY_RESULT_ERROR_ROLLBACK_INDEX,
+            avb_slot_verify(ops_.avb_ops(),
+                            requested_partitions,
+                            "_a",
+                            true /* allow_verification_error */,
+                            &slot_data));
+  EXPECT_NE(nullptr, slot_data);
+  avb_slot_verify_data_free(slot_data);
+}
+
+TEST_F(AvbSlotVerifyTest, HashDescriptorInVBMeta) {
+  const size_t boot_partition_size = 16 * 1024 * 1024;
+  const size_t boot_image_size = 5 * 1024 * 1024;
+  base::FilePath boot_path = GenerateImage("boot_a.img", boot_image_size);
+
+  EXPECT_COMMAND(
+      0,
+      "./avbtool add_hash_footer"
+      " --image %s"
+      " --rollback_index 0"
+      " --partition_name boot"
+      " --partition_size %zd"
+      " --kernel_cmdline 'cmdline in hash footer $(ANDROID_SYSTEM_PARTUUID)'"
+      " --salt deadbeef",
+      boot_path.value().c_str(),
+      boot_partition_size);
+
+  GenerateVBMetaImage(
+      "vbmeta_a.img",
+      "SHA256_RSA2048",
+      4,
+      base::FilePath("test/data/testkey_rsa2048.pem"),
+      base::StringPrintf(
+          "--include_descriptors_from_image %s"
+          " --kernel_cmdline 'cmdline in vbmeta $(ANDROID_BOOT_PARTUUID)'",
+          boot_path.value().c_str()));
+
+  EXPECT_EQ(
+      "VBMeta image version:     1.0\n"
+      "Header Block:             256 bytes\n"
+      "Authentication Block:     320 bytes\n"
+      "Auxiliary Block:          896 bytes\n"
+      "Algorithm:                SHA256_RSA2048\n"
+      "Rollback Index:           4\n"
+      "Flags:                    0\n"
+      "Descriptors:\n"
+      "    Kernel Cmdline descriptor:\n"
+      "      Flags:                 0\n"
+      "      Kernel Cmdline:        'cmdline in vbmeta "
+      "$(ANDROID_BOOT_PARTUUID)'\n"
+      "    Hash descriptor:\n"
+      "      Image Size:            5242880 bytes\n"
+      "      Hash Algorithm:        sha256\n"
+      "      Partition Name:        boot\n"
+      "      Salt:                  deadbeef\n"
+      "      Digest:                "
+      "184cb36243adb8b87d2d8c4802de32125fe294ec46753d732144ee65df68a23d\n"
+      "    Kernel Cmdline descriptor:\n"
+      "      Flags:                 0\n"
+      "      Kernel Cmdline:        'cmdline in hash footer "
+      "$(ANDROID_SYSTEM_PARTUUID)'\n",
+      InfoImage(vbmeta_image_path_));
+
+  EXPECT_COMMAND(0,
+                 "./avbtool erase_footer"
+                 " --image %s",
+                 boot_path.value().c_str());
+
+  // With no footer, 'avbtool info_image' should fail (exit status 1).
+  EXPECT_COMMAND(
+      1, "./avbtool info_image --image %s", boot_path.value().c_str());
+
+  ops_.set_expected_public_key(
+      PublicKeyAVB(base::FilePath("test/data/testkey_rsa2048.pem")));
+
+  AvbSlotVerifyData* slot_data = NULL;
+  const char* requested_partitions[] = {"boot", NULL};
+  EXPECT_EQ(AVB_SLOT_VERIFY_RESULT_OK,
+            avb_slot_verify(ops_.avb_ops(),
+                            requested_partitions,
+                            "_a",
+                            false /* allow_verification_error */,
+                            &slot_data));
+  EXPECT_NE(nullptr, slot_data);
+
+  // Now verify the slot data. The vbmeta data should match our
+  // vbmeta_image_ member.
+  EXPECT_EQ(size_t(1), slot_data->num_vbmeta_images);
+  EXPECT_EQ("vbmeta", std::string(slot_data->vbmeta_images[0].partition_name));
+  EXPECT_EQ(slot_data->vbmeta_images[0].vbmeta_size, vbmeta_image_.size());
+  EXPECT_EQ(0,
+            memcmp(vbmeta_image_.data(),
+                   slot_data->vbmeta_images[0].vbmeta_data,
+                   slot_data->vbmeta_images[0].vbmeta_size));
+
+  // The boot image data should match what is generated above with
+  // GenerateImage().
+  EXPECT_EQ(size_t(1), slot_data->num_loaded_partitions);
+  EXPECT_EQ("boot",
+            std::string(slot_data->loaded_partitions[0].partition_name));
+  EXPECT_EQ(boot_image_size, slot_data->loaded_partitions[0].data_size);
+  for (size_t n = 0; n < slot_data->loaded_partitions[0].data_size; n++) {
+    EXPECT_EQ(slot_data->loaded_partitions[0].data[n], uint8_t(n));
+  }
+
+  // This should match the two cmdlines with a space (U+0020) between
+  // them and the $(ANDROID_SYSTEM_PARTUUID) and
+  // $(ANDROID_BOOT_PARTUUID) variables replaced.
+  EXPECT_EQ(
+      "cmdline in vbmeta 1234-fake-guid-for:boot_a cmdline in hash footer "
+      "1234-fake-guid-for:system_a "
+      "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta_a "
+      "androidboot.slot_suffix=_a androidboot.vbmeta.device_state=locked "
+      "androidboot.vbmeta.hash_alg=sha256 androidboot.vbmeta.size=1472 "
+      "androidboot.vbmeta.digest="
+      "34cdb59b955aa35d4da97701f304fabf7392eecca8c50ff1a0b7b6e1c9aaa1b8",
+      std::string(slot_data->cmdline));
+  EXPECT_EQ(4UL, slot_data->rollback_indexes[0]);
+  for (size_t n = 1; n < AVB_MAX_NUMBER_OF_ROLLBACK_INDEX_LOCATIONS; n++) {
+    EXPECT_EQ(0UL, slot_data->rollback_indexes[n]);
+  }
+  avb_slot_verify_data_free(slot_data);
+}
+
+TEST_F(AvbSlotVerifyTest, HashDescriptorInVBMetaCorruptBoot) {
+  size_t boot_partition_size = 16 * 1024 * 1024;
+  base::FilePath boot_path = GenerateImage("boot_a.img", 5 * 1024 * 1024);
+  const char* requested_partitions[] = {"boot", NULL};
+
+  EXPECT_COMMAND(0,
+                 "./avbtool add_hash_footer"
+                 " --image %s"
+                 " --rollback_index 0"
+                 " --partition_name boot"
+                 " --partition_size %zd"
+                 " --salt deadbeef",
+                 boot_path.value().c_str(),
+                 boot_partition_size);
+
+  GenerateVBMetaImage("vbmeta_a.img",
+                      "SHA256_RSA2048",
+                      0,
+                      base::FilePath("test/data/testkey_rsa2048.pem"),
+                      base::StringPrintf("--include_descriptors_from_image %s",
+                                         boot_path.value().c_str()));
+
+  EXPECT_COMMAND(0,
+                 "./avbtool erase_footer"
+                 " --image %s",
+                 boot_path.value().c_str());
+
+  ops_.set_expected_public_key(
+      PublicKeyAVB(base::FilePath("test/data/testkey_rsa2048.pem")));
+
+  // So far, so good.
+  AvbSlotVerifyData* slot_data = NULL;
+  EXPECT_EQ(AVB_SLOT_VERIFY_RESULT_OK,
+            avb_slot_verify(ops_.avb_ops(),
+                            requested_partitions,
+                            "_a",
+                            false /* allow_verification_error */,
+                            &slot_data));
+  EXPECT_NE(nullptr, slot_data);
+  avb_slot_verify_data_free(slot_data);
+
+  // Now corrupt boot_a.img and expect verification error.
+  uint8_t corrupt_data[4] = {0xff, 0xff, 0xff, 0xff};
+  EXPECT_EQ(AVB_IO_RESULT_OK,
+            ops_.avb_ops()->write_to_partition(ops_.avb_ops(),
+                                               "boot_a",
+                                               1024 * 1024,  // offset: 1 MiB
+                                               sizeof corrupt_data,
+                                               corrupt_data));
+
+  EXPECT_EQ(AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION,
+            avb_slot_verify(ops_.avb_ops(),
+                            requested_partitions,
+                            "_a",
+                            false /* allow_verification_error */,
+                            &slot_data));
+  EXPECT_EQ(nullptr, slot_data);
+  EXPECT_EQ(AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION,
+            avb_slot_verify(ops_.avb_ops(),
+                            requested_partitions,
+                            "_a",
+                            true /* allow_verification_error */,
+                            &slot_data));
+  EXPECT_NE(nullptr, slot_data);
+  avb_slot_verify_data_free(slot_data);
+}
+
+TEST_F(AvbSlotVerifyTest, HashDescriptorInChainedPartition) {
+  size_t boot_partition_size = 16 * 1024 * 1024;
+  const size_t boot_image_size = 5 * 1024 * 1024;
+  base::FilePath boot_path = GenerateImage("boot_a.img", boot_image_size);
+  const char* requested_partitions[] = {"boot", NULL};
+
+  EXPECT_COMMAND(0,
+                 "./avbtool add_hash_footer"
+                 " --image %s"
+                 " --kernel_cmdline 'cmdline2 in hash footer'"
+                 " --rollback_index 12"
+                 " --partition_name boot"
+                 " --partition_size %zd"
+                 " --algorithm SHA256_RSA4096"
+                 " --key test/data/testkey_rsa4096.pem"
+                 " --salt deadbeef",
+                 boot_path.value().c_str(),
+                 boot_partition_size);
+
+  base::FilePath pk_path = testdir_.Append("testkey_rsa4096.avbpubkey");
+  EXPECT_COMMAND(
+      0,
+      "./avbtool extract_public_key --key test/data/testkey_rsa4096.pem"
+      " --output %s",
+      pk_path.value().c_str());
+
+  GenerateVBMetaImage(
+      "vbmeta_a.img",
+      "SHA256_RSA2048",
+      11,
+      base::FilePath("test/data/testkey_rsa2048.pem"),
+      base::StringPrintf("--chain_partition boot:1:%s"
+                         " --kernel_cmdline 'cmdline2 in vbmeta'",
+                         pk_path.value().c_str()));
+
+  EXPECT_EQ(
+      "VBMeta image version:     1.0\n"
+      "Header Block:             256 bytes\n"
+      "Authentication Block:     320 bytes\n"
+      "Auxiliary Block:          1728 bytes\n"
+      "Algorithm:                SHA256_RSA2048\n"
+      "Rollback Index:           11\n"
+      "Flags:                    0\n"
+      "Descriptors:\n"
+      "    Chain Partition descriptor:\n"
+      "      Partition Name:          boot\n"
+      "      Rollback Index Location: 1\n"
+      "      Public key (sha1):       "
+      "2597c218aae470a130f61162feaae70afd97f011\n"
+      "    Kernel Cmdline descriptor:\n"
+      "      Flags:                 0\n"
+      "      Kernel Cmdline:        'cmdline2 in vbmeta'\n",
+      InfoImage(vbmeta_image_path_));
+
+  EXPECT_EQ(
+      "Footer version:           1.0\n"
+      "Image size:               16777216 bytes\n"
+      "Original image size:      5242880 bytes\n"
+      "VBMeta offset:            5242880\n"
+      "VBMeta size:              2112 bytes\n"
+      "--\n"
+      "VBMeta image version:     1.0\n"
+      "Header Block:             256 bytes\n"
+      "Authentication Block:     576 bytes\n"
+      "Auxiliary Block:          1280 bytes\n"
+      "Algorithm:                SHA256_RSA4096\n"
+      "Rollback Index:           12\n"
+      "Flags:                    0\n"
+      "Descriptors:\n"
+      "    Hash descriptor:\n"
+      "      Image Size:            5242880 bytes\n"
+      "      Hash Algorithm:        sha256\n"
+      "      Partition Name:        boot\n"
+      "      Salt:                  deadbeef\n"
+      "      Digest:                "
+      "184cb36243adb8b87d2d8c4802de32125fe294ec46753d732144ee65df68a23d\n"
+      "    Kernel Cmdline descriptor:\n"
+      "      Flags:                 0\n"
+      "      Kernel Cmdline:        'cmdline2 in hash footer'\n",
+      InfoImage(boot_path));
+
+  ops_.set_expected_public_key(
+      PublicKeyAVB(base::FilePath("test/data/testkey_rsa2048.pem")));
+
+  AvbSlotVerifyData* slot_data = NULL;
+  EXPECT_EQ(AVB_SLOT_VERIFY_RESULT_OK,
+            avb_slot_verify(ops_.avb_ops(),
+                            requested_partitions,
+                            "_a",
+                            false /* allow_verification_error */,
+                            &slot_data));
+  EXPECT_NE(nullptr, slot_data);
+
+  // Now verify the slot data. We should have two vbmeta
+  // structs. Verify both of them. Note that the A/B suffix isn't
+  // appended.
+  EXPECT_EQ(size_t(2), slot_data->num_vbmeta_images);
+  EXPECT_EQ("vbmeta", std::string(slot_data->vbmeta_images[0].partition_name));
+  EXPECT_EQ(slot_data->vbmeta_images[0].vbmeta_size, vbmeta_image_.size());
+  EXPECT_EQ(0,
+            memcmp(vbmeta_image_.data(),
+                   slot_data->vbmeta_images[0].vbmeta_data,
+                   slot_data->vbmeta_images[0].vbmeta_size));
+  // And for the second vbmeta struct we check that the descriptors
+  // match the info_image output from above.
+  EXPECT_EQ("boot", std::string(slot_data->vbmeta_images[1].partition_name));
+  const AvbDescriptor** descriptors =
+      avb_descriptor_get_all(slot_data->vbmeta_images[1].vbmeta_data,
+                             slot_data->vbmeta_images[1].vbmeta_size,
+                             NULL);
+  EXPECT_NE(nullptr, descriptors);
+  AvbHashDescriptor hash_desc;
+  EXPECT_EQ(true,
+            avb_hash_descriptor_validate_and_byteswap(
+                ((AvbHashDescriptor*)descriptors[0]), &hash_desc));
+  const uint8_t* desc_end = reinterpret_cast<const uint8_t*>(descriptors[0]) +
+                            sizeof(AvbHashDescriptor);
+  uint64_t o = 0;
+  EXPECT_EQ("boot",
+            std::string(reinterpret_cast<const char*>(desc_end + o),
+                        hash_desc.partition_name_len));
+  o += hash_desc.partition_name_len;
+  EXPECT_EQ("deadbeef", mem_to_hexstring(desc_end + o, hash_desc.salt_len));
+  o += hash_desc.salt_len;
+  EXPECT_EQ("184cb36243adb8b87d2d8c4802de32125fe294ec46753d732144ee65df68a23d",
+            mem_to_hexstring(desc_end + o, hash_desc.digest_len));
+  AvbKernelCmdlineDescriptor cmdline_desc;
+  EXPECT_EQ(true,
+            avb_kernel_cmdline_descriptor_validate_and_byteswap(
+                ((AvbKernelCmdlineDescriptor*)descriptors[1]), &cmdline_desc));
+  desc_end = reinterpret_cast<const uint8_t*>(descriptors[1]) +
+             sizeof(AvbKernelCmdlineDescriptor);
+  EXPECT_EQ("cmdline2 in hash footer",
+            std::string(reinterpret_cast<const char*>(desc_end),
+                        cmdline_desc.kernel_cmdline_length));
+  avb_free(descriptors);
+
+  // The boot image data should match what is generated above with
+  // GenerateImage().
+  EXPECT_EQ(size_t(1), slot_data->num_loaded_partitions);
+  EXPECT_EQ("boot",
+            std::string(slot_data->loaded_partitions[0].partition_name));
+  EXPECT_EQ(boot_image_size, slot_data->loaded_partitions[0].data_size);
+  for (size_t n = 0; n < slot_data->loaded_partitions[0].data_size; n++) {
+    EXPECT_EQ(slot_data->loaded_partitions[0].data[n], uint8_t(n));
+  }
+
+  // This should match the two cmdlines with a space (U+0020) between them.
+  EXPECT_EQ(
+      "cmdline2 in hash footer cmdline2 in vbmeta "
+      "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta_a "
+      "androidboot.slot_suffix=_a androidboot.vbmeta.device_state=locked "
+      "androidboot.vbmeta.hash_alg=sha256 androidboot.vbmeta.size=4416 "
+      "androidboot.vbmeta.digest="
+      "4a45faa9adfeb94e9154fe682c11fef1a1a3d829b67cbf1a12ac7f0aa4f8e2e4",
+      std::string(slot_data->cmdline));
+  EXPECT_EQ(11UL, slot_data->rollback_indexes[0]);
+  EXPECT_EQ(12UL, slot_data->rollback_indexes[1]);
+  for (size_t n = 2; n < AVB_MAX_NUMBER_OF_ROLLBACK_INDEX_LOCATIONS; n++) {
+    EXPECT_EQ(0UL, slot_data->rollback_indexes[n]);
+  }
+  avb_slot_verify_data_free(slot_data);
+}
+
+TEST_F(AvbSlotVerifyTest, HashDescriptorInChainedPartitionCorruptBoot) {
+  size_t boot_partition_size = 16 * 1024 * 1024;
+  base::FilePath boot_path = GenerateImage("boot_a.img", 5 * 1024 * 1024);
+  const char* requested_partitions[] = {"boot", NULL};
+
+  EXPECT_COMMAND(0,
+                 "./avbtool add_hash_footer"
+                 " --image %s"
+                 " --rollback_index 0"
+                 " --partition_name boot"
+                 " --partition_size %zd"
+                 " --algorithm SHA256_RSA4096"
+                 " --key test/data/testkey_rsa4096.pem"
+                 " --salt deadbeef",
+                 boot_path.value().c_str(),
+                 boot_partition_size);
+
+  base::FilePath pk_path = testdir_.Append("testkey_rsa4096.avbpubkey");
+  EXPECT_COMMAND(
+      0,
+      "./avbtool extract_public_key --key test/data/testkey_rsa4096.pem"
+      " --output %s",
+      pk_path.value().c_str());
+
+  GenerateVBMetaImage("vbmeta_a.img",
+                      "SHA256_RSA2048",
+                      0,
+                      base::FilePath("test/data/testkey_rsa2048.pem"),
+                      base::StringPrintf("--chain_partition boot:1:%s",
+                                         pk_path.value().c_str()));
+
+  ops_.set_expected_public_key(
+      PublicKeyAVB(base::FilePath("test/data/testkey_rsa2048.pem")));
+
+  AvbSlotVerifyData* slot_data = NULL;
+  EXPECT_EQ(AVB_SLOT_VERIFY_RESULT_OK,
+            avb_slot_verify(ops_.avb_ops(),
+                            requested_partitions,
+                            "_a",
+                            false /* allow_verification_error */,
+                            &slot_data));
+  EXPECT_NE(nullptr, slot_data);
+  avb_slot_verify_data_free(slot_data);
+
+  // Now corrupt boot_a.img and expect verification error.
+  uint8_t corrupt_data[4] = {0xff, 0xff, 0xff, 0xff};
+  EXPECT_EQ(AVB_IO_RESULT_OK,
+            ops_.avb_ops()->write_to_partition(ops_.avb_ops(),
+                                               "boot_a",
+                                               1024 * 1024,  // offset: 1 MiB
+                                               sizeof corrupt_data,
+                                               corrupt_data));
+
+  EXPECT_EQ(AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION,
+            avb_slot_verify(ops_.avb_ops(),
+                            requested_partitions,
+                            "_a",
+                            false /* allow_verification_error */,
+                            &slot_data));
+  EXPECT_EQ(nullptr, slot_data);
+  EXPECT_EQ(AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION,
+            avb_slot_verify(ops_.avb_ops(),
+                            requested_partitions,
+                            "_a",
+                            true /* allow_verification_error */,
+                            &slot_data));
+  EXPECT_NE(nullptr, slot_data);
+  avb_slot_verify_data_free(slot_data);
+}
+
+TEST_F(AvbSlotVerifyTest, HashDescriptorInChainedPartitionKeyMismatch) {
+  size_t boot_partition_size = 16 * 1024 * 1024;
+  base::FilePath boot_path = GenerateImage("boot_a.img", 5 * 1024 * 1024);
+  const char* requested_partitions[] = {"boot", NULL};
+
+  // Use different key to sign vbmeta in boot_a (we use the 8192 bit
+  // key) than what's in the chained partition descriptor (which is
+  // the 4096 bit key) and expect
+  // AVB_SLOT_VERIFY_RESULT_ERROR_PUBLIC_KEY_REJECTED.
+
+  EXPECT_COMMAND(0,
+                 "./avbtool add_hash_footer"
+                 " --image %s"
+                 " --rollback_index 0"
+                 " --partition_name boot"
+                 " --partition_size %zd"
+                 " --algorithm SHA256_RSA8192"
+                 " --key test/data/testkey_rsa8192.pem"
+                 " --salt deadbeef",
+                 boot_path.value().c_str(),
+                 boot_partition_size);
+
+  base::FilePath pk_path = testdir_.Append("testkey_rsa4096.avbpubkey");
+  EXPECT_COMMAND(
+      0,
+      "./avbtool extract_public_key --key test/data/testkey_rsa4096.pem"
+      " --output %s",
+      pk_path.value().c_str());
+
+  GenerateVBMetaImage("vbmeta_a.img",
+                      "SHA256_RSA2048",
+                      0,
+                      base::FilePath("test/data/testkey_rsa2048.pem"),
+                      base::StringPrintf("--chain_partition boot:1:%s",
+                                         pk_path.value().c_str()));
+
+  ops_.set_expected_public_key(
+      PublicKeyAVB(base::FilePath("test/data/testkey_rsa2048.pem")));
+
+  AvbSlotVerifyData* slot_data = NULL;
+  EXPECT_EQ(AVB_SLOT_VERIFY_RESULT_ERROR_PUBLIC_KEY_REJECTED,
+            avb_slot_verify(ops_.avb_ops(),
+                            requested_partitions,
+                            "_a",
+                            false /* allow_verification_error */,
+                            &slot_data));
+  EXPECT_EQ(nullptr, slot_data);
+  EXPECT_EQ(AVB_SLOT_VERIFY_RESULT_ERROR_PUBLIC_KEY_REJECTED,
+            avb_slot_verify(ops_.avb_ops(),
+                            requested_partitions,
+                            "_a",
+                            true /* allow_verification_error */,
+                            &slot_data));
+  EXPECT_NE(nullptr, slot_data);
+  avb_slot_verify_data_free(slot_data);
+}
+
+TEST_F(AvbSlotVerifyTest, HashDescriptorInChainedPartitionRollbackIndexFail) {
+  size_t boot_partition_size = 16 * 1024 * 1024;
+  base::FilePath boot_path = GenerateImage("boot_a.img", 5 * 1024 * 1024);
+  const char* requested_partitions[] = {"boot", NULL};
+
+  EXPECT_COMMAND(0,
+                 "./avbtool add_hash_footer"
+                 " --image %s"
+                 " --rollback_index 10"
+                 " --partition_name boot"
+                 " --partition_size %zd"
+                 " --algorithm SHA256_RSA4096"
+                 " --key test/data/testkey_rsa4096.pem"
+                 " --salt deadbeef",
+                 boot_path.value().c_str(),
+                 boot_partition_size);
+
+  base::FilePath pk_path = testdir_.Append("testkey_rsa4096.avbpubkey");
+  EXPECT_COMMAND(
+      0,
+      "./avbtool extract_public_key --key test/data/testkey_rsa4096.pem"
+      " --output %s",
+      pk_path.value().c_str());
+
+  GenerateVBMetaImage("vbmeta_a.img",
+                      "SHA256_RSA2048",
+                      110,
+                      base::FilePath("test/data/testkey_rsa2048.pem"),
+                      base::StringPrintf("--chain_partition boot:1:%s",
+                                         pk_path.value().c_str()));
+
+  ops_.set_expected_public_key(
+      PublicKeyAVB(base::FilePath("test/data/testkey_rsa2048.pem")));
+
+  AvbSlotVerifyData* slot_data = NULL;
+
+  // Both images (vbmeta_a and boot_a) have rollback index 10 and 11
+  // so it should work if the stored rollback indexes are 0 and 0.
+  ops_.set_stored_rollback_indexes({{0, 0}, {1, 0}});
+  EXPECT_EQ(AVB_SLOT_VERIFY_RESULT_OK,
+            avb_slot_verify(ops_.avb_ops(),
+                            requested_partitions,
+                            "_a",
+                            false /* allow_verification_error */,
+                            &slot_data));
+  EXPECT_NE(nullptr, slot_data);
+  avb_slot_verify_data_free(slot_data);
+
+  // Check failure if we set the stored rollback index of the chained
+  // partition to 20 (see AvbSlotVerifyTest.RollbackIndex above
+  // where we test rollback index checks for the vbmeta partition).
+  ops_.set_stored_rollback_indexes({{0, 0}, {1, 20}});
+  EXPECT_EQ(AVB_SLOT_VERIFY_RESULT_ERROR_ROLLBACK_INDEX,
+            avb_slot_verify(ops_.avb_ops(),
+                            requested_partitions,
+                            "_a",
+                            false /* allow_verification_error */,
+                            &slot_data));
+  EXPECT_EQ(nullptr, slot_data);
+  EXPECT_EQ(AVB_SLOT_VERIFY_RESULT_ERROR_ROLLBACK_INDEX,
+            avb_slot_verify(ops_.avb_ops(),
+                            requested_partitions,
+                            "_a",
+                            true /* allow_verification_error */,
+                            &slot_data));
+  EXPECT_NE(nullptr, slot_data);
+  avb_slot_verify_data_free(slot_data);
+
+  // Check failure if there is no rollback index slot 1 - in that case
+  // we expect an I/O error since ops->read_rollback_index() will
+  // fail.
+  ops_.set_stored_rollback_indexes({{0, 0}});
+  EXPECT_EQ(AVB_SLOT_VERIFY_RESULT_ERROR_IO,
+            avb_slot_verify(ops_.avb_ops(),
+                            requested_partitions,
+                            "_a",
+                            false /* allow_verification_error */,
+                            &slot_data));
+  EXPECT_EQ(nullptr, slot_data);
+}
+
+TEST_F(AvbSlotVerifyTest, ChainedPartitionNoSlots) {
+  size_t boot_partition_size = 16 * 1024 * 1024;
+  const size_t boot_image_size = 5 * 1024 * 1024;
+  base::FilePath boot_path = GenerateImage("boot.img", boot_image_size);
+  const char* requested_partitions[] = {"boot", NULL};
+
+  EXPECT_COMMAND(0,
+                 "./avbtool add_hash_footer"
+                 " --image %s"
+                 " --kernel_cmdline 'cmdline2 in hash footer'"
+                 " --rollback_index 12"
+                 " --partition_name boot"
+                 " --partition_size %zd"
+                 " --algorithm SHA256_RSA4096"
+                 " --key test/data/testkey_rsa4096.pem"
+                 " --salt deadbeef",
+                 boot_path.value().c_str(),
+                 boot_partition_size);
+
+  base::FilePath pk_path = testdir_.Append("testkey_rsa4096.avbpubkey");
+  EXPECT_COMMAND(
+      0,
+      "./avbtool extract_public_key --key test/data/testkey_rsa4096.pem"
+      " --output %s",
+      pk_path.value().c_str());
+
+  GenerateVBMetaImage(
+      "vbmeta.img",
+      "SHA256_RSA2048",
+      11,
+      base::FilePath("test/data/testkey_rsa2048.pem"),
+      base::StringPrintf("--chain_partition boot:1:%s"
+                         " --kernel_cmdline 'cmdline2 in vbmeta'",
+                         pk_path.value().c_str()));
+
+  EXPECT_EQ(
+      "VBMeta image version:     1.0\n"
+      "Header Block:             256 bytes\n"
+      "Authentication Block:     320 bytes\n"
+      "Auxiliary Block:          1728 bytes\n"
+      "Algorithm:                SHA256_RSA2048\n"
+      "Rollback Index:           11\n"
+      "Flags:                    0\n"
+      "Descriptors:\n"
+      "    Chain Partition descriptor:\n"
+      "      Partition Name:          boot\n"
+      "      Rollback Index Location: 1\n"
+      "      Public key (sha1):       "
+      "2597c218aae470a130f61162feaae70afd97f011\n"
+      "    Kernel Cmdline descriptor:\n"
+      "      Flags:                 0\n"
+      "      Kernel Cmdline:        'cmdline2 in vbmeta'\n",
+      InfoImage(vbmeta_image_path_));
+
+  ops_.set_expected_public_key(
+      PublicKeyAVB(base::FilePath("test/data/testkey_rsa2048.pem")));
+
+  AvbSlotVerifyData* slot_data = NULL;
+  EXPECT_EQ(AVB_SLOT_VERIFY_RESULT_OK,
+            avb_slot_verify(ops_.avb_ops(),
+                            requested_partitions,
+                            "",
+                            false /* allow_verification_error */,
+                            &slot_data));
+  EXPECT_NE(nullptr, slot_data);
+
+  // Now verify the slot data. The first vbmeta data should match our
+  // vbmeta_image_ member and the second one should be for the 'boot'
+  // partition.
+  EXPECT_EQ(size_t(2), slot_data->num_vbmeta_images);
+  EXPECT_EQ("vbmeta", std::string(slot_data->vbmeta_images[0].partition_name));
+  EXPECT_EQ(slot_data->vbmeta_images[0].vbmeta_size, vbmeta_image_.size());
+  EXPECT_EQ(0,
+            memcmp(vbmeta_image_.data(),
+                   slot_data->vbmeta_images[0].vbmeta_data,
+                   slot_data->vbmeta_images[0].vbmeta_size));
+  EXPECT_EQ("boot", std::string(slot_data->vbmeta_images[1].partition_name));
+
+  // The boot image data should match what is generated above with
+  // GenerateImage().
+  EXPECT_EQ(size_t(1), slot_data->num_loaded_partitions);
+  EXPECT_EQ("boot",
+            std::string(slot_data->loaded_partitions[0].partition_name));
+  EXPECT_EQ(boot_image_size, slot_data->loaded_partitions[0].data_size);
+  for (size_t n = 0; n < slot_data->loaded_partitions[0].data_size; n++) {
+    EXPECT_EQ(slot_data->loaded_partitions[0].data[n], uint8_t(n));
+  }
+
+  // This should match the two cmdlines with a space (U+0020) between
+  // them - note that androidboot.slot_suffix is not set since we
+  // don't have any slots in this setup.
+  EXPECT_EQ(
+      "cmdline2 in hash footer cmdline2 in vbmeta "
+      "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta "
+      "androidboot.vbmeta.device_state=locked "
+      "androidboot.vbmeta.hash_alg=sha256 androidboot.vbmeta.size=4416 "
+      "androidboot.vbmeta.digest="
+      "4a45faa9adfeb94e9154fe682c11fef1a1a3d829b67cbf1a12ac7f0aa4f8e2e4",
+      std::string(slot_data->cmdline));
+  EXPECT_EQ(11UL, slot_data->rollback_indexes[0]);
+  EXPECT_EQ(12UL, slot_data->rollback_indexes[1]);
+  for (size_t n = 2; n < AVB_MAX_NUMBER_OF_ROLLBACK_INDEX_LOCATIONS; n++) {
+    EXPECT_EQ(0UL, slot_data->rollback_indexes[n]);
+  }
+  avb_slot_verify_data_free(slot_data);
+}
+
+TEST_F(AvbSlotVerifyTest, PartitionsOtherThanBoot) {
+  const size_t foo_partition_size = 16 * 1024 * 1024;
+  const size_t bar_partition_size = 32 * 1024 * 1024;
+  const size_t foo_image_size = 5 * 1024 * 1024;
+  const size_t bar_image_size = 10 * 1024 * 1024;
+  base::FilePath foo_path = GenerateImage("foo_a.img", foo_image_size);
+  base::FilePath bar_path = GenerateImage("bar_a.img", bar_image_size);
+
+  EXPECT_COMMAND(0,
+                 "./avbtool add_hash_footer"
+                 " --image %s"
+                 " --partition_name foo"
+                 " --partition_size %zd"
+                 " --salt deadbeef",
+                 foo_path.value().c_str(),
+                 foo_partition_size);
+
+  EXPECT_COMMAND(0,
+                 "./avbtool add_hash_footer"
+                 " --image %s"
+                 " --partition_name bar"
+                 " --partition_size %zd"
+                 " --salt deadbeef",
+                 bar_path.value().c_str(),
+                 bar_partition_size);
+
+  GenerateVBMetaImage("vbmeta_a.img",
+                      "SHA256_RSA2048",
+                      4,
+                      base::FilePath("test/data/testkey_rsa2048.pem"),
+                      base::StringPrintf("--include_descriptors_from_image %s"
+                                         " --include_descriptors_from_image %s",
+                                         foo_path.value().c_str(),
+                                         bar_path.value().c_str()));
+
+  EXPECT_EQ(
+      "VBMeta image version:     1.0\n"
+      "Header Block:             256 bytes\n"
+      "Authentication Block:     320 bytes\n"
+      "Auxiliary Block:          896 bytes\n"
+      "Algorithm:                SHA256_RSA2048\n"
+      "Rollback Index:           4\n"
+      "Flags:                    0\n"
+      "Descriptors:\n"
+      "    Hash descriptor:\n"
+      "      Image Size:            5242880 bytes\n"
+      "      Hash Algorithm:        sha256\n"
+      "      Partition Name:        foo\n"
+      "      Salt:                  deadbeef\n"
+      "      Digest:                "
+      "184cb36243adb8b87d2d8c4802de32125fe294ec46753d732144ee65df68a23d\n"
+      "    Hash descriptor:\n"
+      "      Image Size:            10485760 bytes\n"
+      "      Hash Algorithm:        sha256\n"
+      "      Partition Name:        bar\n"
+      "      Salt:                  deadbeef\n"
+      "      Digest:                "
+      "baea4bbd261d0edf4d1fe5e6e5a36976c291eeba66b6a46fa81dba691327a727\n",
+      InfoImage(vbmeta_image_path_));
+
+  ops_.set_expected_public_key(
+      PublicKeyAVB(base::FilePath("test/data/testkey_rsa2048.pem")));
+
+  AvbSlotVerifyData* slot_data = NULL;
+  const char* requested_partitions[] = {"foo", "bar", NULL};
+  EXPECT_EQ(AVB_SLOT_VERIFY_RESULT_OK,
+            avb_slot_verify(ops_.avb_ops(),
+                            requested_partitions,
+                            "_a",
+                            false /* allow_verification_error */,
+                            &slot_data));
+  EXPECT_NE(nullptr, slot_data);
+
+  // Now verify the slot data. The vbmeta data should match our
+  // vbmeta_image_ member.
+  EXPECT_EQ(size_t(1), slot_data->num_vbmeta_images);
+  EXPECT_EQ("vbmeta", std::string(slot_data->vbmeta_images[0].partition_name));
+  EXPECT_EQ(slot_data->vbmeta_images[0].vbmeta_size, vbmeta_image_.size());
+  EXPECT_EQ(0,
+            memcmp(vbmeta_image_.data(),
+                   slot_data->vbmeta_images[0].vbmeta_data,
+                   slot_data->vbmeta_images[0].vbmeta_size));
+
+  // The 'foo' and 'bar' image data should match what is generated
+  // above with GenerateImage().
+  EXPECT_EQ(size_t(2), slot_data->num_loaded_partitions);
+  EXPECT_EQ("foo", std::string(slot_data->loaded_partitions[0].partition_name));
+  EXPECT_EQ(foo_image_size, slot_data->loaded_partitions[0].data_size);
+  for (size_t n = 0; n < slot_data->loaded_partitions[0].data_size; n++) {
+    EXPECT_EQ(slot_data->loaded_partitions[0].data[n], uint8_t(n));
+  }
+  EXPECT_EQ("bar", std::string(slot_data->loaded_partitions[1].partition_name));
+  EXPECT_EQ(bar_image_size, slot_data->loaded_partitions[1].data_size);
+  for (size_t n = 0; n < slot_data->loaded_partitions[1].data_size; n++) {
+    EXPECT_EQ(slot_data->loaded_partitions[1].data[n], uint8_t(n));
+  }
+
+  avb_slot_verify_data_free(slot_data);
+}
+
+TEST_F(AvbSlotVerifyTest, PublicKeyMetadata) {
+  base::FilePath md_path = GenerateImage("md.bin", 1536);
+
+  GenerateVBMetaImage(
+      "vbmeta_a.img",
+      "SHA256_RSA2048",
+      0,
+      base::FilePath("test/data/testkey_rsa2048.pem"),
+      base::StringPrintf("--public_key_metadata %s", md_path.value().c_str()));
+
+  ops_.set_expected_public_key(
+      PublicKeyAVB(base::FilePath("test/data/testkey_rsa2048.pem")));
+
+  std::string md_data;
+  ASSERT_TRUE(base::ReadFileToString(md_path, &md_data));
+  ops_.set_expected_public_key_metadata(md_data);
+
+  AvbSlotVerifyData* slot_data = NULL;
+  const char* requested_partitions[] = {"boot", NULL};
+  EXPECT_EQ(AVB_SLOT_VERIFY_RESULT_OK,
+            avb_slot_verify(ops_.avb_ops(),
+                            requested_partitions,
+                            "_a",
+                            false /* allow_verification_error */,
+                            &slot_data));
+  EXPECT_NE(nullptr, slot_data);
+  EXPECT_EQ(
+      "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta_a "
+      "androidboot.slot_suffix=_a androidboot.vbmeta.device_state=locked "
+      "androidboot.vbmeta.hash_alg=sha256 androidboot.vbmeta.size=2688 "
+      "androidboot.vbmeta.digest="
+      "5edcaa54f40382ee6a2fc3b86cdf383348b35ed07955e83ea32d84b69a97eaa0",
+      std::string(slot_data->cmdline));
+  avb_slot_verify_data_free(slot_data);
+}
+
+void AvbSlotVerifyTest::CmdlineWithHashtreeVerification(
+    bool hashtree_verification_on) {
+  const size_t rootfs_size = 1028 * 1024;
+  const size_t partition_size = 1536 * 1024;
+
+  // Generate a 1028 KiB file with known content.
+  std::vector<uint8_t> rootfs;
+  rootfs.resize(rootfs_size);
+  for (size_t n = 0; n < rootfs_size; n++)
+    rootfs[n] = uint8_t(n);
+  base::FilePath rootfs_path = testdir_.Append("rootfs.bin");
+  EXPECT_EQ(rootfs_size,
+            static_cast<const size_t>(
+                base::WriteFile(rootfs_path,
+                                reinterpret_cast<const char*>(rootfs.data()),
+                                rootfs.size())));
+
+  EXPECT_COMMAND(0,
+                 "./avbtool add_hashtree_footer --salt d00df00d --image %s "
+                 "--partition_size %d --partition_name foobar "
+                 "--algorithm SHA256_RSA2048 "
+                 "--key test/data/testkey_rsa2048.pem",
+                 rootfs_path.value().c_str(),
+                 (int)partition_size);
+
+  // Check that we correctly generate dm-verity kernel cmdline
+  // snippets, if requested.
+  GenerateVBMetaImage(
+      "vbmeta_a.img",
+      "SHA256_RSA2048",
+      4,
+      base::FilePath("test/data/testkey_rsa2048.pem"),
+      base::StringPrintf("--setup_rootfs_from_kernel %s "
+                         "--kernel_cmdline should_be_in_both=1 "
+                         "--algorithm SHA256_RSA2048 "
+                         "--flags %d ",
+                         rootfs_path.value().c_str(),
+                         hashtree_verification_on
+                             ? 0
+                             : AVB_VBMETA_IMAGE_FLAGS_HASHTREE_DISABLED));
+
+  EXPECT_EQ(
+      base::StringPrintf(
+          "VBMeta image version:     1.0\n"
+          "Header Block:             256 bytes\n"
+          "Authentication Block:     320 bytes\n"
+          "Auxiliary Block:          896 bytes\n"
+          "Algorithm:                SHA256_RSA2048\n"
+          "Rollback Index:           4\n"
+          "Flags:                    %d\n"
+          "Descriptors:\n"
+          "    Kernel Cmdline descriptor:\n"
+          "      Flags:                 1\n"
+          "      Kernel Cmdline:        'dm=\"1 vroot none ro 1,0 2056 verity "
+          "1 PARTUUID=$(ANDROID_SYSTEM_PARTUUID) "
+          "PARTUUID=$(ANDROID_SYSTEM_PARTUUID) 4096 4096 257 257 sha1 "
+          "e811611467dcd6e8dc4324e45f706c2bdd51db67 d00df00d 2 "
+          "restart_on_corruption ignore_zero_blocks\" root=0xfd00'\n"
+          "    Kernel Cmdline descriptor:\n"
+          "      Flags:                 2\n"
+          "      Kernel Cmdline:        "
+          "'root=PARTUUID=$(ANDROID_SYSTEM_PARTUUID)'\n"
+          "    Kernel Cmdline descriptor:\n"
+          "      Flags:                 0\n"
+          "      Kernel Cmdline:        'should_be_in_both=1'\n",
+          hashtree_verification_on ? 0
+                                   : AVB_VBMETA_IMAGE_FLAGS_HASHTREE_DISABLED),
+      InfoImage(vbmeta_image_path_));
+
+  ops_.set_expected_public_key(
+      PublicKeyAVB(base::FilePath("test/data/testkey_rsa2048.pem")));
+
+  // Check that avb_slot_verify() picks the cmdline decsriptors based
+  // on their flags value.
+  AvbSlotVerifyData* slot_data = NULL;
+  const char* requested_partitions[] = {"boot", NULL};
+  EXPECT_EQ(AVB_SLOT_VERIFY_RESULT_OK,
+            avb_slot_verify(ops_.avb_ops(),
+                            requested_partitions,
+                            "_a",
+                            false /* allow_verification_error */,
+                            &slot_data));
+  EXPECT_NE(nullptr, slot_data);
+  if (hashtree_verification_on) {
+    EXPECT_EQ(
+        "dm=\"1 vroot none ro 1,0 2056 verity 1 "
+        "PARTUUID=1234-fake-guid-for:system_a "
+        "PARTUUID=1234-fake-guid-for:system_a 4096 4096 257 257 sha1 "
+        "e811611467dcd6e8dc4324e45f706c2bdd51db67 d00df00d 2 "
+        "restart_on_corruption ignore_zero_blocks\" root=0xfd00 "
+        "should_be_in_both=1 "
+        "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta_a "
+        "androidboot.slot_suffix=_a androidboot.vbmeta.device_state=locked "
+        "androidboot.vbmeta.hash_alg=sha256 androidboot.vbmeta.size=1472 "
+        "androidboot.vbmeta.digest="
+        "32572f7a89fb44cc76e9e3adbc0cb5272d0604578b2179729aa52d2bba4061c3",
+        std::string(slot_data->cmdline));
+  } else {
+    EXPECT_EQ(
+        "root=PARTUUID=1234-fake-guid-for:system_a should_be_in_both=1 "
+        "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta_a "
+        "androidboot.slot_suffix=_a androidboot.vbmeta.device_state=locked "
+        "androidboot.vbmeta.hash_alg=sha256 androidboot.vbmeta.size=1472 "
+        "androidboot.vbmeta.digest="
+        "bb630c27d09e10117092d4444ea57a99bf44101c46cf5424cfb86928a0873249",
+        std::string(slot_data->cmdline));
+  }
+  avb_slot_verify_data_free(slot_data);
+}
+
+TEST_F(AvbSlotVerifyTest, CmdlineWithHashtreeVerificationOff) {
+  CmdlineWithHashtreeVerification(false);
+}
+
+TEST_F(AvbSlotVerifyTest, CmdlineWithHashtreeVerificationOn) {
+  CmdlineWithHashtreeVerification(true);
+}
+
+}  // namespace avb
diff --git a/avb/test/avb_unittest_util.cc b/avb/test/avb_unittest_util.cc
new file mode 100644
index 0000000..e065db8
--- /dev/null
+++ b/avb/test/avb_unittest_util.cc
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "avb_unittest_util.h"
+
+std::string mem_to_hexstring(const uint8_t* data, size_t len) {
+  std::string ret;
+  char digits[17] = "0123456789abcdef";
+  for (size_t n = 0; n < len; n++) {
+    ret.push_back(digits[data[n] >> 4]);
+    ret.push_back(digits[data[n] & 0x0f]);
+  }
+  return ret;
+}
diff --git a/avb/test/avb_unittest_util.h b/avb/test/avb_unittest_util.h
new file mode 100644
index 0000000..e93e68c
--- /dev/null
+++ b/avb/test/avb_unittest_util.h
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef AVB_UNITTEST_UTIL_H_
+#define AVB_UNITTEST_UTIL_H_
+
+#include <inttypes.h>
+
+#include <gtest/gtest.h>
+
+#include <base/files/file_util.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+
+// Encodes |len| bytes of |data| as a lower-case hex-string.
+std::string mem_to_hexstring(const uint8_t* data, size_t len);
+
+/* Utility macro to run the command expressed by the printf()-style string
+ * |command_format| using the system(3) utility function. Will assert unless
+ * the command exits normally with exit status |expected_exit_status|.
+ */
+#define EXPECT_COMMAND(expected_exit_status, command_format, ...)          \
+  do {                                                                     \
+    int rc =                                                               \
+        system(base::StringPrintf(command_format, ##__VA_ARGS__).c_str()); \
+    EXPECT_TRUE(WIFEXITED(rc));                                            \
+    EXPECT_EQ(WEXITSTATUS(rc), expected_exit_status);                      \
+  } while (0);
+
+namespace avb {
+
+/* Base-class used for unit test. */
+class BaseAvbToolTest : public ::testing::Test {
+ public:
+  BaseAvbToolTest() {}
+
+ protected:
+  virtual ~BaseAvbToolTest() {}
+
+  /* Generates a vbmeta image, using avbtoool, with file name
+   * |image_name|. The generated vbmeta image will written to disk,
+   * see the |vbmeta_image_path_| variable for its path and
+   * |vbmeta_image_| for the content.
+   */
+  void GenerateVBMetaImage(const std::string& image_name,
+                           const std::string& algorithm,
+                           uint64_t rollback_index,
+                           const base::FilePath& key_path,
+                           const std::string& additional_options = "") {
+    std::string signing_options;
+    if (algorithm == "") {
+      signing_options = " --algorithm NONE ";
+    } else {
+      signing_options = std::string(" --algorithm ") + algorithm + " --key " +
+                        key_path.value() + " ";
+    }
+    vbmeta_image_path_ = testdir_.Append(image_name);
+    EXPECT_COMMAND(0,
+                   "./avbtool make_vbmeta_image"
+                   " --rollback_index %" PRIu64
+                   " %s %s "
+                   " --output %s",
+                   rollback_index,
+                   additional_options.c_str(),
+                   signing_options.c_str(),
+                   vbmeta_image_path_.value().c_str());
+    int64_t file_size;
+    ASSERT_TRUE(base::GetFileSize(vbmeta_image_path_, &file_size));
+    vbmeta_image_.resize(file_size);
+    ASSERT_TRUE(base::ReadFile(vbmeta_image_path_,
+                               reinterpret_cast<char*>(vbmeta_image_.data()),
+                               vbmeta_image_.size()));
+  }
+
+  /* Generate a file with name |file_name| of size |image_size| with
+   * known content (0x00 0x01 0x02 .. 0xff 0x00 0x01 ..).
+   */
+  base::FilePath GenerateImage(const std::string file_name, size_t image_size) {
+    std::vector<uint8_t> image;
+    image.resize(image_size);
+    for (size_t n = 0; n < image_size; n++) {
+      image[n] = uint8_t(n);
+    }
+    base::FilePath image_path = testdir_.Append(file_name);
+    EXPECT_EQ(image_size,
+              static_cast<const size_t>(
+                  base::WriteFile(image_path,
+                                  reinterpret_cast<const char*>(image.data()),
+                                  image.size())));
+    return image_path;
+  }
+
+  /* Returns the output of 'avbtool info_image' for a given image. */
+  std::string InfoImage(const base::FilePath& image_path) {
+    base::FilePath tmp_path = testdir_.Append("info_output.txt");
+    EXPECT_COMMAND(0,
+                   "./avbtool info_image --image %s --output %s",
+                   image_path.value().c_str(),
+                   tmp_path.value().c_str());
+    std::string info_data;
+    EXPECT_TRUE(base::ReadFileToString(tmp_path, &info_data));
+    return info_data;
+  }
+
+  /* Returns public key in AVB format for a .pem key */
+  std::string PublicKeyAVB(const base::FilePath& key_path) {
+    base::FilePath tmp_path = testdir_.Append("public_key.bin");
+    EXPECT_COMMAND(0,
+                   "./avbtool extract_public_key --key %s"
+                   " --output %s",
+                   key_path.value().c_str(),
+                   tmp_path.value().c_str());
+    std::string key_data;
+    EXPECT_TRUE(base::ReadFileToString(tmp_path, &key_data));
+    return key_data;
+  }
+
+  /* Create temporary directory to stash images in. */
+  virtual void SetUp() override {
+    base::FilePath ret;
+    char* buf = strdup("/tmp/libavb-tests.XXXXXX");
+    ASSERT_TRUE(mkdtemp(buf) != nullptr);
+    testdir_ = base::FilePath(buf);
+    free(buf);
+  }
+
+  /* Nuke temporary directory. */
+  virtual void TearDown() override {
+    ASSERT_EQ(0U, testdir_.value().find("/tmp/libavb-tests"));
+    ASSERT_TRUE(base::DeleteFile(testdir_, true /* recursive */));
+  }
+
+  /* Temporary directory created in SetUp(). */
+  base::FilePath testdir_;
+
+  /* Path to vbmeta image generated with GenerateVBMetaImage(). */
+  base::FilePath vbmeta_image_path_;
+
+  /* Contents of the image generated with GenerateVBMetaImage(). */
+  std::vector<uint8_t> vbmeta_image_;
+};
+
+}  // namespace avb
+
+#endif /* AVB_UNITTEST_UTIL_H_ */
diff --git a/avb/test/avb_util_unittest.cc b/avb/test/avb_util_unittest.cc
new file mode 100644
index 0000000..0c8c375
--- /dev/null
+++ b/avb/test/avb_util_unittest.cc
@@ -0,0 +1,496 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <string.h>
+
+#include <gtest/gtest.h>
+
+#include <libavb/libavb.h>
+
+TEST(UtilTest, RSAPublicKeyHeaderByteswap) {
+  AvbRSAPublicKeyHeader h;
+  AvbRSAPublicKeyHeader s;
+  uint32_t n32;
+  uint64_t n64;
+
+  n32 = 0x11223344;
+  n64 = 0x1122334455667788;
+
+  h.key_num_bits = htobe32(n32);
+  n32++;
+  h.n0inv = htobe32(n32);
+  n32++;
+
+  EXPECT_NE(0, avb_rsa_public_key_header_validate_and_byteswap(&h, &s));
+
+  n32 = 0x11223344;
+  n64 = 0x1122334455667788;
+
+  EXPECT_EQ(n32, s.key_num_bits);
+  n32++;
+  EXPECT_EQ(n32, s.n0inv);
+  n32++;
+}
+
+TEST(UtilTest, FooterByteswap) {
+  AvbFooter h;
+  AvbFooter s;
+  AvbFooter other;
+  AvbFooter bad;
+  uint64_t n64;
+
+  n64 = 0x1122334455667788;
+
+  memcpy(h.magic, AVB_FOOTER_MAGIC, AVB_FOOTER_MAGIC_LEN);
+  h.version_major = htobe32(AVB_FOOTER_MAJOR_VERSION);
+  h.version_minor = htobe32(AVB_FOOTER_MINOR_VERSION);
+  h.original_image_size = htobe64(n64);
+  n64++;
+  h.vbmeta_offset = htobe64(n64);
+  n64++;
+  h.vbmeta_size = htobe64(n64);
+  n64++;
+
+  EXPECT_NE(0, avb_footer_validate_and_byteswap(&h, &s));
+
+  n64 = 0x1122334455667788;
+
+  EXPECT_EQ((uint32_t)AVB_FOOTER_MAJOR_VERSION, s.version_major);
+  EXPECT_EQ((uint32_t)AVB_FOOTER_MINOR_VERSION, s.version_minor);
+  EXPECT_EQ(n64, s.original_image_size);
+  n64++;
+  EXPECT_EQ(n64, s.vbmeta_offset);
+  n64++;
+  EXPECT_EQ(n64, s.vbmeta_size);
+  n64++;
+
+  // Check that the struct still validates if minor is bigger than
+  // what we expect.
+  other = h;
+  h.version_minor = htobe32(AVB_FOOTER_MINOR_VERSION + 1);
+  EXPECT_NE(0, avb_footer_validate_and_byteswap(&other, &s));
+
+  // Check for bad magic.
+  bad = h;
+  bad.magic[0] = 'x';
+  EXPECT_EQ(0, avb_footer_validate_and_byteswap(&bad, &s));
+
+  // Check for bad major version.
+  bad = h;
+  bad.version_major = htobe32(AVB_FOOTER_MAJOR_VERSION + 1);
+  EXPECT_EQ(0, avb_footer_validate_and_byteswap(&bad, &s));
+}
+
+TEST(UtilTest, KernelCmdlineDescriptorByteswap) {
+  AvbKernelCmdlineDescriptor h;
+  AvbKernelCmdlineDescriptor s;
+  AvbKernelCmdlineDescriptor bad;
+  uint64_t nbf;
+  uint32_t n32;
+
+  // Specify 40 bytes of data past the end of the descriptor struct.
+  nbf = 40 + sizeof(AvbKernelCmdlineDescriptor) - sizeof(AvbDescriptor);
+  h.parent_descriptor.num_bytes_following = htobe64(nbf);
+  h.parent_descriptor.tag = htobe64(AVB_DESCRIPTOR_TAG_KERNEL_CMDLINE);
+  h.kernel_cmdline_length = htobe32(40);
+
+  n32 = 0x11223344;
+  h.flags = htobe32(n32);
+  n32++;
+
+  EXPECT_NE(0, avb_kernel_cmdline_descriptor_validate_and_byteswap(&h, &s));
+
+  n32 = 0x11223344;
+  EXPECT_EQ(n32, s.flags);
+  n32++;
+
+  EXPECT_EQ(AVB_DESCRIPTOR_TAG_KERNEL_CMDLINE, s.parent_descriptor.tag);
+  EXPECT_EQ(nbf, s.parent_descriptor.num_bytes_following);
+  EXPECT_EQ(40UL, s.kernel_cmdline_length);
+
+  // Check for bad tag.
+  bad = h;
+  bad.parent_descriptor.tag = htobe64(0xf00dd00d);
+  EXPECT_EQ(0, avb_kernel_cmdline_descriptor_validate_and_byteswap(&bad, &s));
+
+  // Doesn't fit in 41 bytes.
+  bad = h;
+  bad.kernel_cmdline_length = htobe32(41);
+  EXPECT_EQ(0, avb_kernel_cmdline_descriptor_validate_and_byteswap(&bad, &s));
+}
+
+TEST(UtilTest, HashtreeDescriptorByteswap) {
+  AvbHashtreeDescriptor h;
+  AvbHashtreeDescriptor s;
+  AvbHashtreeDescriptor bad;
+  uint64_t nbf;
+  uint32_t n32;
+  uint64_t n64;
+
+  // Specify 44 bytes of data past the end of the descriptor struct.
+  nbf = 44 + sizeof(AvbHashtreeDescriptor) - sizeof(AvbDescriptor);
+  h.parent_descriptor.num_bytes_following = htobe64(nbf);
+  h.parent_descriptor.tag = htobe64(AVB_DESCRIPTOR_TAG_HASHTREE);
+  h.partition_name_len = htobe32(10);
+  h.salt_len = htobe32(10);
+  h.root_digest_len = htobe32(10);
+
+  n32 = 0x11223344;
+  n64 = 0x1122334455667788;
+
+  h.dm_verity_version = htobe32(n32);
+  n32++;
+  h.image_size = htobe64(n64);
+  n64++;
+  h.tree_offset = htobe64(n64);
+  n64++;
+  h.tree_size = htobe64(n64);
+  n64++;
+  h.data_block_size = htobe32(n32);
+  n32++;
+  h.hash_block_size = htobe32(n32);
+  n32++;
+  h.fec_num_roots = htobe32(n32);
+  n32++;
+  h.fec_offset = htobe64(n64);
+  n64++;
+  h.fec_size = htobe64(n64);
+  n64++;
+
+  EXPECT_TRUE(avb_hashtree_descriptor_validate_and_byteswap(&h, &s));
+
+  n32 = 0x11223344;
+  n64 = 0x1122334455667788;
+
+  EXPECT_EQ(n32, s.dm_verity_version);
+  n32++;
+  EXPECT_EQ(n64, s.image_size);
+  n64++;
+  EXPECT_EQ(n64, s.tree_offset);
+  n64++;
+  EXPECT_EQ(n64, s.tree_size);
+  n64++;
+  EXPECT_EQ(n32, s.data_block_size);
+  n32++;
+  EXPECT_EQ(n32, s.hash_block_size);
+  n32++;
+  EXPECT_EQ(n32, s.fec_num_roots);
+  n32++;
+  EXPECT_EQ(n64, s.fec_offset);
+  n64++;
+  EXPECT_EQ(n64, s.fec_size);
+  n64++;
+
+  EXPECT_EQ(AVB_DESCRIPTOR_TAG_HASHTREE, s.parent_descriptor.tag);
+  EXPECT_EQ(nbf, s.parent_descriptor.num_bytes_following);
+  EXPECT_EQ(10UL, s.partition_name_len);
+  EXPECT_EQ(10UL, s.salt_len);
+  EXPECT_EQ(10UL, s.root_digest_len);
+
+  // Check for bad tag.
+  bad = h;
+  bad.parent_descriptor.tag = htobe64(0xf00dd00d);
+  EXPECT_FALSE(avb_hashtree_descriptor_validate_and_byteswap(&bad, &s));
+
+  // Doesn't fit in 44 bytes (30 + 10 + 10 = 50).
+  bad = h;
+  bad.partition_name_len = htobe32(30);
+  EXPECT_FALSE(avb_hashtree_descriptor_validate_and_byteswap(&bad, &s));
+
+  // Doesn't fit in 44 bytes (10 + 30 + 10 = 50).
+  bad = h;
+  bad.salt_len = htobe32(30);
+  EXPECT_FALSE(avb_hashtree_descriptor_validate_and_byteswap(&bad, &s));
+
+  // Doesn't fit in 44 bytes (10 + 10 + 30 = 50).
+  bad = h;
+  bad.root_digest_len = htobe32(30);
+  EXPECT_FALSE(avb_hashtree_descriptor_validate_and_byteswap(&bad, &s));
+}
+
+TEST(UtilTest, HashDescriptorByteswap) {
+  AvbHashDescriptor h;
+  AvbHashDescriptor s;
+  AvbHashDescriptor bad;
+  uint64_t nbf;
+
+  // Specify 44 bytes of data past the end of the descriptor struct.
+  nbf = 44 + sizeof(AvbHashDescriptor) - sizeof(AvbDescriptor);
+  h.parent_descriptor.num_bytes_following = htobe64(nbf);
+  h.parent_descriptor.tag = htobe64(AVB_DESCRIPTOR_TAG_HASH);
+  h.partition_name_len = htobe32(10);
+  h.salt_len = htobe32(10);
+  h.digest_len = htobe32(10);
+
+  EXPECT_NE(0, avb_hash_descriptor_validate_and_byteswap(&h, &s));
+
+  EXPECT_EQ(AVB_DESCRIPTOR_TAG_HASH, s.parent_descriptor.tag);
+  EXPECT_EQ(nbf, s.parent_descriptor.num_bytes_following);
+  EXPECT_EQ(10UL, s.partition_name_len);
+  EXPECT_EQ(10UL, s.salt_len);
+  EXPECT_EQ(10UL, s.digest_len);
+
+  // Check for bad tag.
+  bad = h;
+  bad.parent_descriptor.tag = htobe64(0xf00dd00d);
+  EXPECT_EQ(0, avb_hash_descriptor_validate_and_byteswap(&bad, &s));
+
+  // Doesn't fit in 44 bytes (30 + 10 + 10 = 50).
+  bad = h;
+  bad.partition_name_len = htobe32(30);
+  EXPECT_EQ(0, avb_hash_descriptor_validate_and_byteswap(&bad, &s));
+
+  // Doesn't fit in 44 bytes (10 + 30 + 10 = 50).
+  bad = h;
+  bad.salt_len = htobe32(30);
+  EXPECT_EQ(0, avb_hash_descriptor_validate_and_byteswap(&bad, &s));
+
+  // Doesn't fit in 44 bytes (10 + 10 + 30 = 50).
+  bad = h;
+  bad.digest_len = htobe32(30);
+  EXPECT_EQ(0, avb_hash_descriptor_validate_and_byteswap(&bad, &s));
+}
+
+TEST(UtilTest, ChainPartitionDescriptorByteswap) {
+  AvbChainPartitionDescriptor h;
+  AvbChainPartitionDescriptor s;
+  AvbChainPartitionDescriptor bad;
+  uint64_t nbf;
+
+  // Specify 36 bytes of data past the end of the descriptor struct.
+  nbf = 36 + sizeof(AvbChainPartitionDescriptor) - sizeof(AvbDescriptor);
+  h.parent_descriptor.num_bytes_following = htobe64(nbf);
+  h.parent_descriptor.tag = htobe64(AVB_DESCRIPTOR_TAG_CHAIN_PARTITION);
+  h.rollback_index_location = htobe32(42);
+  h.partition_name_len = htobe32(16);
+  h.public_key_len = htobe32(17);
+
+  EXPECT_NE(0, avb_chain_partition_descriptor_validate_and_byteswap(&h, &s));
+
+  EXPECT_EQ(AVB_DESCRIPTOR_TAG_CHAIN_PARTITION, s.parent_descriptor.tag);
+  EXPECT_EQ(nbf, s.parent_descriptor.num_bytes_following);
+  EXPECT_EQ(42UL, s.rollback_index_location);
+  EXPECT_EQ(16UL, s.partition_name_len);
+  EXPECT_EQ(17UL, s.public_key_len);
+
+  // Check for bad tag.
+  bad = h;
+  bad.parent_descriptor.tag = htobe64(0xf00dd00d);
+  EXPECT_EQ(0, avb_chain_partition_descriptor_validate_and_byteswap(&bad, &s));
+
+  // Check for bad rollback index slot (must be at least 1).
+  bad = h;
+  bad.rollback_index_location = htobe32(0);
+  EXPECT_EQ(0, avb_chain_partition_descriptor_validate_and_byteswap(&bad, &s));
+
+  // Doesn't fit in 40 bytes (24 + 17 = 41).
+  bad = h;
+  bad.partition_name_len = htobe32(24);
+  EXPECT_EQ(0, avb_chain_partition_descriptor_validate_and_byteswap(&bad, &s));
+
+  // Doesn't fit in 40 bytes (16 + 25 = 41).
+  bad = h;
+  bad.public_key_len = htobe32(25);
+  EXPECT_EQ(0, avb_chain_partition_descriptor_validate_and_byteswap(&bad, &s));
+}
+
+TEST(UtilTest, PropertyDescriptorByteswap) {
+  AvbPropertyDescriptor h;
+  AvbPropertyDescriptor s;
+  AvbPropertyDescriptor bad;
+  uint64_t nbf;
+
+  // Specify 40 bytes of data past the end of the descriptor struct.
+  nbf = 40 + sizeof(AvbPropertyDescriptor) - sizeof(AvbDescriptor);
+  h.parent_descriptor.num_bytes_following = htobe64(nbf);
+  h.parent_descriptor.tag = htobe64(AVB_DESCRIPTOR_TAG_PROPERTY);
+  h.key_num_bytes = htobe64(16);
+  h.value_num_bytes = htobe64(17);
+
+  EXPECT_NE(0, avb_property_descriptor_validate_and_byteswap(&h, &s));
+
+  EXPECT_EQ(AVB_DESCRIPTOR_TAG_PROPERTY, s.parent_descriptor.tag);
+  EXPECT_EQ(nbf, s.parent_descriptor.num_bytes_following);
+  EXPECT_EQ(16UL, s.key_num_bytes);
+  EXPECT_EQ(17UL, s.value_num_bytes);
+
+  // Check for bad tag.
+  bad = h;
+  bad.parent_descriptor.tag = htobe64(0xf00dd00d);
+  EXPECT_EQ(0, avb_property_descriptor_validate_and_byteswap(&bad, &s));
+
+  // Doesn't fit in 40 bytes (22 + 17 + 2 = 41).
+  bad = h;
+  bad.key_num_bytes = htobe64(22);
+  EXPECT_EQ(0, avb_property_descriptor_validate_and_byteswap(&bad, &s));
+
+  // Doesn't fit in 40 bytes (16 + 23 + 2 = 41).
+  bad = h;
+  bad.value_num_bytes = htobe64(23);
+  EXPECT_EQ(0, avb_property_descriptor_validate_and_byteswap(&bad, &s));
+}
+
+TEST(UtilTest, DescriptorByteswap) {
+  AvbDescriptor h;
+  AvbDescriptor s;
+  uint64_t n64;
+
+  n64 = 0x1122334455667788;
+
+  h.num_bytes_following = htobe64(n64);
+  n64++;
+  h.tag = htobe64(n64);
+  n64++;
+
+  EXPECT_NE(0, avb_descriptor_validate_and_byteswap(&h, &s));
+
+  n64 = 0x1122334455667788;
+
+  EXPECT_EQ(n64, s.num_bytes_following);
+  n64++;
+  EXPECT_EQ(n64, s.tag);
+  n64++;
+
+  // Check that we catch if |num_bytes_following| isn't divisble by 8.
+  h.num_bytes_following = htobe64(7);
+  EXPECT_EQ(0, avb_descriptor_validate_and_byteswap(&h, &s));
+}
+
+TEST(UtilTest, SafeAddition) {
+  uint64_t value;
+  uint64_t pow2_60 = 1ULL << 60;
+
+  value = 2;
+  EXPECT_NE(0, avb_safe_add_to(&value, 5));
+  EXPECT_EQ(7UL, value);
+
+  /* These should not overflow */
+  value = 1 * pow2_60;
+  EXPECT_NE(0, avb_safe_add_to(&value, 2 * pow2_60));
+  EXPECT_EQ(3 * pow2_60, value);
+  value = 7 * pow2_60;
+  EXPECT_NE(0, avb_safe_add_to(&value, 8 * pow2_60));
+  EXPECT_EQ(15 * pow2_60, value);
+  value = 9 * pow2_60;
+  EXPECT_NE(0, avb_safe_add_to(&value, 3 * pow2_60));
+  EXPECT_EQ(12 * pow2_60, value);
+  value = 0xfffffffffffffffcUL;
+  EXPECT_NE(0, avb_safe_add_to(&value, 2));
+  EXPECT_EQ(0xfffffffffffffffeUL, value);
+
+  /* These should overflow. */
+  value = 8 * pow2_60;
+  EXPECT_EQ(0, avb_safe_add_to(&value, 8 * pow2_60));
+  value = 0xfffffffffffffffcUL;
+  EXPECT_EQ(0, avb_safe_add_to(&value, 4));
+}
+
+static int avb_validate_utf8z(const char* data) {
+  return avb_validate_utf8(reinterpret_cast<const uint8_t*>(data),
+                           strlen(data));
+}
+
+TEST(UtilTest, UTF8Validation) {
+  // These should succeed.
+  EXPECT_NE(0, avb_validate_utf8z("foo bar"));
+  // Encoding of U+00E6 LATIN SMALL LETTER AE: æ
+  EXPECT_NE(0, avb_validate_utf8z("foo \xC3\xA6 bar"));
+  // Encoding of U+20AC EURO SIGN: €
+  EXPECT_NE(0, avb_validate_utf8z("foo \xE2\x82\xAC bar"));
+  // Encoding of U+1F466 BOY: 👦
+  EXPECT_NE(0, avb_validate_utf8z("foo \xF0\x9F\x91\xA6 bar"));
+  // All three runes following each other.
+  EXPECT_NE(0, avb_validate_utf8z("\xC3\xA6\xE2\x82\xAC\xF0\x9F\x91\xA6"));
+
+  // These should fail.
+  EXPECT_EQ(0, avb_validate_utf8z("foo \xF8 bar"));
+  EXPECT_EQ(0, avb_validate_utf8z("\xF8"));
+  // Stops in the middle of Unicode rune.
+  EXPECT_EQ(0, avb_validate_utf8z("foo \xC3"));
+}
+
+TEST(UtilTest, StrConcat) {
+  char buf[8];
+
+  // These should succeed.
+  EXPECT_NE(0, avb_str_concat(buf, sizeof buf, "foo", 3, "bar1", 4));
+
+  // This should fail: Insufficient space.
+  EXPECT_EQ(0, avb_str_concat(buf, sizeof buf, "foo0", 4, "bar1", 4));
+}
+
+TEST(UtilTest, StrStr) {
+  const char* haystack = "abc def abcabc";
+
+  EXPECT_EQ(nullptr, avb_strstr(haystack, "needle"));
+  EXPECT_EQ(haystack, avb_strstr(haystack, "abc"));
+  EXPECT_EQ(haystack + 4, avb_strstr(haystack, "def"));
+  EXPECT_EQ(haystack, avb_strstr(haystack, haystack));
+}
+
+TEST(UtilTest, StrvFindStr) {
+  const char* strings[] = {"abcabc", "abc", "def", nullptr};
+
+  EXPECT_EQ(nullptr, avb_strv_find_str(strings, "not there", 9));
+  EXPECT_EQ(strings[1], avb_strv_find_str(strings, "abc", 3));
+  EXPECT_EQ(strings[2], avb_strv_find_str(strings, "def", 3));
+  EXPECT_EQ(strings[0], avb_strv_find_str(strings, "abcabc", 6));
+}
+
+TEST(UtilTest, StrReplace) {
+  // We don't care about leaking strings from avb_replace().
+  EXPECT_EQ("OK blah bah $(FOO OK blah",
+            std::string(avb_replace(
+                "$(FOO) blah bah $(FOO $(FOO) blah", "$(FOO)", "OK")));
+  EXPECT_EQ("OK", std::string(avb_replace("$(FOO)", "$(FOO)", "OK")));
+  EXPECT_EQ(" OK", std::string(avb_replace(" $(FOO)", "$(FOO)", "OK")));
+  EXPECT_EQ("OK ", std::string(avb_replace("$(FOO) ", "$(FOO)", "OK")));
+  EXPECT_EQ("LONGSTRINGLONGSTRING",
+            std::string(avb_replace("$(FOO)$(FOO)", "$(FOO)", "LONGSTRING")));
+}
+
+TEST(UtilTest, Crc32) {
+  /* Compare with output of crc32(1):
+   *
+   *  $ (echo -n foobar > /tmp/crc32_input); crc32 /tmp/crc32_input
+   *  9ef61f95
+   */
+  EXPECT_EQ(uint32_t(0x9ef61f95), avb_crc32((const uint8_t*)"foobar", 6));
+}
+
+TEST(UtilTest, htobe32) {
+  EXPECT_EQ(avb_htobe32(0x12345678), htobe32(0x12345678));
+}
+
+TEST(UtilTest, be32toh) {
+  EXPECT_EQ(avb_be32toh(0x12345678), be32toh(0x12345678));
+}
+
+TEST(UtilTest, htobe64) {
+  EXPECT_EQ(avb_htobe64(0x123456789abcdef0), htobe64(0x123456789abcdef0));
+}
+
+TEST(UtilTest, be64toh) {
+  EXPECT_EQ(avb_be64toh(0x123456789abcdef0), be64toh(0x123456789abcdef0));
+}
diff --git a/avb/test/avb_vbmeta_image_unittest.cc b/avb/test/avb_vbmeta_image_unittest.cc
new file mode 100644
index 0000000..560dd01
--- /dev/null
+++ b/avb/test/avb_vbmeta_image_unittest.cc
@@ -0,0 +1,567 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <iostream>
+
+#include <endian.h>
+#include <inttypes.h>
+#include <string.h>
+
+#include <base/files/file_util.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+
+#include <libavb/libavb.h>
+
+#include "avb_unittest_util.h"
+
+namespace avb {
+
+class VerifyTest : public BaseAvbToolTest {
+ public:
+  VerifyTest() {}
+
+ protected:
+  // Helper function for ModificationDetection test. Modifies
+  // boot_image_ in a number of places in the sub-array at |offset| of
+  // size |length| and checks that avb_vbmeta_image_verify() returns
+  // |expected_result|.
+  bool test_modification(AvbVBMetaVerifyResult expected_result,
+                         size_t offset,
+                         size_t length);
+};
+
+TEST_F(VerifyTest, BootImageStructSize) {
+  EXPECT_EQ(256UL, sizeof(AvbVBMetaImageHeader));
+}
+
+TEST_F(VerifyTest, CheckSHA256RSA2048) {
+  GenerateVBMetaImage("vbmeta.img",
+                      "SHA256_RSA2048",
+                      0,
+                      base::FilePath("test/data/testkey_rsa2048.pem"));
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_OK,
+            avb_vbmeta_image_verify(
+                vbmeta_image_.data(), vbmeta_image_.size(), NULL, NULL));
+}
+
+TEST_F(VerifyTest, CheckSHA256RSA4096) {
+  GenerateVBMetaImage("vbmeta.img",
+                      "SHA256_RSA4096",
+                      0,
+                      base::FilePath("test/data/testkey_rsa4096.pem"));
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_OK,
+            avb_vbmeta_image_verify(
+                vbmeta_image_.data(), vbmeta_image_.size(), NULL, NULL));
+}
+
+TEST_F(VerifyTest, CheckSHA256RSA8192) {
+  GenerateVBMetaImage("vbmeta.img",
+                      "SHA256_RSA8192",
+                      0,
+                      base::FilePath("test/data/testkey_rsa8192.pem"));
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_OK,
+            avb_vbmeta_image_verify(
+                vbmeta_image_.data(), vbmeta_image_.size(), NULL, NULL));
+}
+
+TEST_F(VerifyTest, CheckSHA512RSA2048) {
+  GenerateVBMetaImage("vbmeta.img",
+                      "SHA512_RSA2048",
+                      0,
+                      base::FilePath("test/data/testkey_rsa2048.pem"));
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_OK,
+            avb_vbmeta_image_verify(
+                vbmeta_image_.data(), vbmeta_image_.size(), NULL, NULL));
+}
+
+TEST_F(VerifyTest, CheckSHA512RSA4096) {
+  GenerateVBMetaImage("vbmeta.img",
+                      "SHA512_RSA4096",
+                      0,
+                      base::FilePath("test/data/testkey_rsa4096.pem"));
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_OK,
+            avb_vbmeta_image_verify(
+                vbmeta_image_.data(), vbmeta_image_.size(), NULL, NULL));
+}
+
+TEST_F(VerifyTest, CheckSHA512RSA8192) {
+  GenerateVBMetaImage("vbmeta.img",
+                      "SHA512_RSA8192",
+                      0,
+                      base::FilePath("test/data/testkey_rsa8192.pem"));
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_OK,
+            avb_vbmeta_image_verify(
+                vbmeta_image_.data(), vbmeta_image_.size(), NULL, NULL));
+}
+
+TEST_F(VerifyTest, CheckUnsigned) {
+  GenerateVBMetaImage("vbmeta.img", "", 0, base::FilePath(""));
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_OK_NOT_SIGNED,
+            avb_vbmeta_image_verify(
+                vbmeta_image_.data(), vbmeta_image_.size(), NULL, NULL));
+}
+
+TEST_F(VerifyTest, CheckBiggerLength) {
+  GenerateVBMetaImage("vbmeta.img",
+                      "SHA256_RSA2048",
+                      0,
+                      base::FilePath("test/data/testkey_rsa2048.pem"));
+  // Check that it's OK if we pass a bigger length than what the
+  // header indicates.
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_OK,
+            avb_vbmeta_image_verify(
+                vbmeta_image_.data(), vbmeta_image_.size() + 8192, NULL, NULL));
+}
+
+TEST_F(VerifyTest, BadMagic) {
+  GenerateVBMetaImage("vbmeta.img",
+                      "SHA256_RSA2048",
+                      0,
+                      base::FilePath("test/data/testkey_rsa2048.pem"));
+  vbmeta_image_[0] = 'Z';
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER,
+            avb_vbmeta_image_verify(
+                vbmeta_image_.data(), vbmeta_image_.size(), NULL, NULL));
+}
+
+TEST_F(VerifyTest, MajorVersionCheck) {
+  GenerateVBMetaImage("vbmeta.img",
+                      "SHA256_RSA2048",
+                      0,
+                      base::FilePath("test/data/testkey_rsa2048.pem"));
+
+  AvbVBMetaImageHeader* h =
+      reinterpret_cast<AvbVBMetaImageHeader*>(vbmeta_image_.data());
+  h->header_version_major = htobe32(1 + be32toh(h->header_version_major));
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER,
+            avb_vbmeta_image_verify(
+                vbmeta_image_.data(), vbmeta_image_.size(), NULL, NULL));
+}
+
+TEST_F(VerifyTest, MinorVersionCheck) {
+  GenerateVBMetaImage("vbmeta.img", "", 0, base::FilePath(""));
+
+  AvbVBMetaImageHeader* h =
+      reinterpret_cast<AvbVBMetaImageHeader*>(vbmeta_image_.data());
+  h->header_version_minor = htobe32(1 + be32toh(h->header_version_minor));
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_OK_NOT_SIGNED,
+            avb_vbmeta_image_verify(
+                vbmeta_image_.data(), vbmeta_image_.size(), NULL, NULL));
+}
+
+TEST_F(VerifyTest, BlockSizesAddUpToLessThanLength) {
+  GenerateVBMetaImage("vbmeta.img",
+                      "SHA256_RSA2048",
+                      0,
+                      base::FilePath("test/data/testkey_rsa2048.pem"));
+
+  AvbVBMetaImageHeader* h =
+      reinterpret_cast<AvbVBMetaImageHeader*>(vbmeta_image_.data());
+  AvbVBMetaImageHeader backup = *h;
+
+  // Check that the sum of the two block lengths is less than passed
+  // in size. Use a size that's a multiple of 64 to avoid failure on
+  // earlier check.
+  uint64_t size = vbmeta_image_.size() & (~0x3f);
+
+  h->authentication_data_block_size = htobe64(size);
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER,
+            avb_vbmeta_image_verify(
+                vbmeta_image_.data(), vbmeta_image_.size(), NULL, NULL));
+  *h = backup;
+
+  h->auxiliary_data_block_size = htobe64(size);
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER,
+            avb_vbmeta_image_verify(
+                vbmeta_image_.data(), vbmeta_image_.size(), NULL, NULL));
+  *h = backup;
+
+  // Overflow checks - choose overflow candidate so it's a multiple of
+  // 64 otherwise we'll fail on an earlier check.
+  size = 0xffffffffffffffc0UL;
+
+  h->authentication_data_block_size = htobe64(size);
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER,
+            avb_vbmeta_image_verify(
+                vbmeta_image_.data(), vbmeta_image_.size(), NULL, NULL));
+  *h = backup;
+
+  h->auxiliary_data_block_size = htobe64(size);
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER,
+            avb_vbmeta_image_verify(
+                vbmeta_image_.data(), vbmeta_image_.size(), NULL, NULL));
+  *h = backup;
+
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_OK,
+            avb_vbmeta_image_verify(
+                vbmeta_image_.data(), vbmeta_image_.size(), NULL, NULL));
+}
+
+TEST_F(VerifyTest, BlockSizesMultipleOf64) {
+  GenerateVBMetaImage("vbmeta.img",
+                      "SHA256_RSA2048",
+                      0,
+                      base::FilePath("test/data/testkey_rsa2048.pem"));
+
+  AvbVBMetaImageHeader* h =
+      reinterpret_cast<AvbVBMetaImageHeader*>(vbmeta_image_.data());
+  AvbVBMetaImageHeader backup = *h;
+
+  h->authentication_data_block_size =
+      htobe32(be32toh(h->authentication_data_block_size) - 32);
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER,
+            avb_vbmeta_image_verify(
+                vbmeta_image_.data(), vbmeta_image_.size() - 32, NULL, NULL));
+  *h = backup;
+
+  h->auxiliary_data_block_size =
+      htobe32(be32toh(h->auxiliary_data_block_size) - 32);
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER,
+            avb_vbmeta_image_verify(
+                vbmeta_image_.data(), vbmeta_image_.size() - 32, NULL, NULL));
+  *h = backup;
+
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_OK,
+            avb_vbmeta_image_verify(
+                vbmeta_image_.data(), vbmeta_image_.size(), NULL, NULL));
+}
+
+TEST_F(VerifyTest, HashOutOfBounds) {
+  GenerateVBMetaImage("vbmeta.img",
+                      "SHA256_RSA2048",
+                      0,
+                      base::FilePath("test/data/testkey_rsa2048.pem"));
+
+  AvbVBMetaImageHeader* h =
+      reinterpret_cast<AvbVBMetaImageHeader*>(vbmeta_image_.data());
+
+  // Check we catch when hash data goes out of bounds.
+  h->hash_offset = htobe64(4);
+  h->hash_size = htobe64(be64toh(h->authentication_data_block_size));
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER,
+            avb_vbmeta_image_verify(
+                vbmeta_image_.data(), vbmeta_image_.size(), NULL, NULL));
+
+  // Overflow checks.
+  h->hash_offset = htobe64(4);
+  h->hash_size = htobe64(0xfffffffffffffffeUL);
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER,
+            avb_vbmeta_image_verify(
+                vbmeta_image_.data(), vbmeta_image_.size(), NULL, NULL));
+}
+
+TEST_F(VerifyTest, SignatureOutOfBounds) {
+  GenerateVBMetaImage("vbmeta.img",
+                      "SHA256_RSA2048",
+                      0,
+                      base::FilePath("test/data/testkey_rsa2048.pem"));
+
+  AvbVBMetaImageHeader* h =
+      reinterpret_cast<AvbVBMetaImageHeader*>(vbmeta_image_.data());
+
+  // Check we catch when signature data goes out of bounds.
+  h->signature_offset = htobe64(4);
+  h->signature_size = htobe64(be64toh(h->authentication_data_block_size));
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER,
+            avb_vbmeta_image_verify(
+                vbmeta_image_.data(), vbmeta_image_.size(), NULL, NULL));
+
+  // Overflow checks.
+  h->signature_offset = htobe64(4);
+  h->signature_size = htobe64(0xfffffffffffffffeUL);
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER,
+            avb_vbmeta_image_verify(
+                vbmeta_image_.data(), vbmeta_image_.size(), NULL, NULL));
+}
+
+TEST_F(VerifyTest, PublicKeyOutOfBounds) {
+  GenerateVBMetaImage("vbmeta.img",
+                      "SHA256_RSA2048",
+                      0,
+                      base::FilePath("test/data/testkey_rsa2048.pem"));
+
+  AvbVBMetaImageHeader* h =
+      reinterpret_cast<AvbVBMetaImageHeader*>(vbmeta_image_.data());
+
+  // Check we catch when public key data goes out of bounds.
+  h->public_key_offset = htobe64(4);
+  h->public_key_size = htobe64(be64toh(h->auxiliary_data_block_size));
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER,
+            avb_vbmeta_image_verify(
+                vbmeta_image_.data(), vbmeta_image_.size(), NULL, NULL));
+
+  // Overflow checks.
+  h->public_key_offset = htobe64(4);
+  h->public_key_size = htobe64(0xfffffffffffffffeUL);
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER,
+            avb_vbmeta_image_verify(
+                vbmeta_image_.data(), vbmeta_image_.size(), NULL, NULL));
+}
+
+TEST_F(VerifyTest, PublicKeyMetadataOutOfBounds) {
+  GenerateVBMetaImage("vbmeta.img",
+                      "SHA256_RSA2048",
+                      0,
+                      base::FilePath("test/data/testkey_rsa2048.pem"));
+
+  AvbVBMetaImageHeader* h =
+      reinterpret_cast<AvbVBMetaImageHeader*>(vbmeta_image_.data());
+
+  // Check we catch when public key metadata data goes out of bounds.
+  h->public_key_metadata_offset = htobe64(4);
+  h->public_key_metadata_size = htobe64(be64toh(h->auxiliary_data_block_size));
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER,
+            avb_vbmeta_image_verify(
+                vbmeta_image_.data(), vbmeta_image_.size(), NULL, NULL));
+
+  // Overflow checks.
+  h->public_key_metadata_offset = htobe64(4);
+  h->public_key_metadata_size = htobe64(0xfffffffffffffffeUL);
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER,
+            avb_vbmeta_image_verify(
+                vbmeta_image_.data(), vbmeta_image_.size(), NULL, NULL));
+}
+
+TEST_F(VerifyTest, InvalidAlgorithmField) {
+  GenerateVBMetaImage("vbmeta.img",
+                      "SHA256_RSA2048",
+                      0,
+                      base::FilePath("test/data/testkey_rsa2048.pem"));
+
+  AvbVBMetaImageHeader* h =
+      reinterpret_cast<AvbVBMetaImageHeader*>(vbmeta_image_.data());
+  AvbVBMetaImageHeader backup = *h;
+
+  // Check we bail on unknown algorithm.
+  h->algorithm_type = htobe32(_AVB_ALGORITHM_NUM_TYPES);
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER,
+            avb_vbmeta_image_verify(
+                vbmeta_image_.data(), vbmeta_image_.size(), NULL, NULL));
+  *h = backup;
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_OK,
+            avb_vbmeta_image_verify(
+                vbmeta_image_.data(), vbmeta_image_.size(), NULL, NULL));
+}
+
+TEST_F(VerifyTest, PublicKeyBlockTooSmall) {
+  GenerateVBMetaImage("vbmeta.img",
+                      "SHA256_RSA2048",
+                      0,
+                      base::FilePath("test/data/testkey_rsa2048.pem"));
+
+  AvbVBMetaImageHeader* h =
+      reinterpret_cast<AvbVBMetaImageHeader*>(vbmeta_image_.data());
+  AvbVBMetaImageHeader backup = *h;
+
+  // Check we bail if the auxiliary data block is too small.
+  uint64_t change = be64toh(h->auxiliary_data_block_size) - 64;
+  h->auxiliary_data_block_size = htobe64(change);
+  EXPECT_EQ(
+      AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER,
+      avb_vbmeta_image_verify(
+          vbmeta_image_.data(), vbmeta_image_.size() - change, NULL, NULL));
+  *h = backup;
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_OK,
+            avb_vbmeta_image_verify(
+                vbmeta_image_.data(), vbmeta_image_.size(), NULL, NULL));
+}
+
+bool VerifyTest::test_modification(AvbVBMetaVerifyResult expected_result,
+                                   size_t offset,
+                                   size_t length) {
+  uint8_t* d = reinterpret_cast<uint8_t*>(vbmeta_image_.data());
+  const int kNumCheckpoints = 16;
+
+  // Test |kNumCheckpoints| modifications in the start, middle, and
+  // end of given sub-array.
+  for (int n = 0; n <= kNumCheckpoints; n++) {
+    size_t o = std::min(length * n / kNumCheckpoints, length - 1) + offset;
+    d[o] ^= 0x80;
+    AvbVBMetaVerifyResult result = avb_vbmeta_image_verify(
+        vbmeta_image_.data(), vbmeta_image_.size(), NULL, NULL);
+    d[o] ^= 0x80;
+    if (result != expected_result) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+TEST_F(VerifyTest, ModificationDetection) {
+  GenerateVBMetaImage("vbmeta.img",
+                      "SHA256_RSA2048",
+                      0,
+                      base::FilePath("test/data/testkey_rsa2048.pem"));
+
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_OK,
+            avb_vbmeta_image_verify(
+                vbmeta_image_.data(), vbmeta_image_.size(), NULL, NULL));
+
+  AvbVBMetaImageHeader h;
+  avb_vbmeta_image_header_to_host_byte_order(
+      reinterpret_cast<AvbVBMetaImageHeader*>(vbmeta_image_.data()), &h);
+
+  size_t header_block_offset = 0;
+  size_t authentication_block_offset =
+      header_block_offset + sizeof(AvbVBMetaImageHeader);
+  size_t auxiliary_block_offset =
+      authentication_block_offset + h.authentication_data_block_size;
+
+  // Ensure we detect modification of the header data block. Do this
+  // in a field that's not validated so INVALID_VBMETA_HEADER
+  // isn't returned.
+  EXPECT_TRUE(test_modification(
+      AVB_VBMETA_VERIFY_RESULT_HASH_MISMATCH,
+      offsetof(AvbVBMetaImageHeader, reserved),
+      sizeof(AvbVBMetaImageHeader) - offsetof(AvbVBMetaImageHeader, reserved)));
+  // Also check the |reserved| field.
+  EXPECT_TRUE(test_modification(AVB_VBMETA_VERIFY_RESULT_HASH_MISMATCH,
+                                offsetof(AvbVBMetaImageHeader, reserved),
+                                sizeof(AvbVBMetaImageHeader().reserved)));
+
+  // Ensure we detect modifications in the auxiliary data block.
+  EXPECT_TRUE(test_modification(AVB_VBMETA_VERIFY_RESULT_HASH_MISMATCH,
+                                auxiliary_block_offset,
+                                h.auxiliary_data_block_size));
+
+  // Modifications in the hash part of the Authentication data block
+  // should also yield HASH_MISMATCH. This is because the hash check
+  // compares the calculated hash against the stored hash.
+  EXPECT_TRUE(test_modification(AVB_VBMETA_VERIFY_RESULT_HASH_MISMATCH,
+                                authentication_block_offset + h.hash_offset,
+                                h.hash_size));
+
+  // Modifications in the signature part of the Authentication data
+  // block, should not cause a hash mismatch ... but will cause a
+  // signature mismatch.
+  EXPECT_TRUE(
+      test_modification(AVB_VBMETA_VERIFY_RESULT_SIGNATURE_MISMATCH,
+                        authentication_block_offset + h.signature_offset,
+                        h.signature_size));
+
+  // Mofications outside the hash and signature parts of the
+  // Authentication data block are not detected. This is because it's
+  // not part of the hash calculation.
+  uint64_t offset = h.signature_offset + h.signature_size;
+  ASSERT_LT(h.hash_offset, h.signature_offset);
+  ASSERT_LT(offset + 1, h.authentication_data_block_size);
+  EXPECT_TRUE(test_modification(AVB_VBMETA_VERIFY_RESULT_OK,
+                                authentication_block_offset + offset,
+                                h.authentication_data_block_size - offset));
+}
+
+TEST_F(VerifyTest, VBMetaHeaderByteswap) {
+  AvbVBMetaImageHeader h;
+  AvbVBMetaImageHeader s;
+  uint32_t n32;
+  uint64_t n64;
+
+  n32 = 0x11223344;
+  n64 = 0x1122334455667788;
+
+  h.header_version_major = htobe32(n32);
+  n32++;
+  h.header_version_minor = htobe32(n32);
+  n32++;
+  h.authentication_data_block_size = htobe64(n64);
+  n64++;
+  h.auxiliary_data_block_size = htobe64(n64);
+  n64++;
+  h.algorithm_type = htobe32(n32);
+  n32++;
+  h.hash_offset = htobe64(n64);
+  n64++;
+  h.hash_size = htobe64(n64);
+  n64++;
+  h.signature_offset = htobe64(n64);
+  n64++;
+  h.signature_size = htobe64(n64);
+  n64++;
+  h.public_key_offset = htobe64(n64);
+  n64++;
+  h.public_key_size = htobe64(n64);
+  n64++;
+  h.public_key_metadata_offset = htobe64(n64);
+  n64++;
+  h.public_key_metadata_size = htobe64(n64);
+  n64++;
+  h.descriptors_offset = htobe64(n64);
+  n64++;
+  h.descriptors_size = htobe64(n64);
+  n64++;
+  h.rollback_index = htobe64(n64);
+  n64++;
+  h.flags = htobe32(n32);
+  n32++;
+
+  avb_vbmeta_image_header_to_host_byte_order(&h, &s);
+
+  n32 = 0x11223344;
+  n64 = 0x1122334455667788;
+
+  EXPECT_EQ(n32, s.header_version_major);
+  n32++;
+  EXPECT_EQ(n32, s.header_version_minor);
+  n32++;
+  EXPECT_EQ(n64, s.authentication_data_block_size);
+  n64++;
+  EXPECT_EQ(n64, s.auxiliary_data_block_size);
+  n64++;
+  EXPECT_EQ(n32, s.algorithm_type);
+  n32++;
+  EXPECT_EQ(n64, s.hash_offset);
+  n64++;
+  EXPECT_EQ(n64, s.hash_size);
+  n64++;
+  EXPECT_EQ(n64, s.signature_offset);
+  n64++;
+  EXPECT_EQ(n64, s.signature_size);
+  n64++;
+  EXPECT_EQ(n64, s.public_key_offset);
+  n64++;
+  EXPECT_EQ(n64, s.public_key_size);
+  n64++;
+  EXPECT_EQ(n64, s.public_key_metadata_offset);
+  n64++;
+  EXPECT_EQ(n64, s.public_key_metadata_size);
+  n64++;
+  EXPECT_EQ(n64, s.descriptors_offset);
+  n64++;
+  EXPECT_EQ(n64, s.descriptors_size);
+  n64++;
+  EXPECT_EQ(n64, s.rollback_index);
+  n64++;
+  EXPECT_EQ(n32, s.flags);
+  n32++;
+
+  // If new fields are added, the following will fail. This is to
+  // remind that byteswapping code (in avb_util.c) and unittests for
+  // this should be updated.
+  static_assert(offsetof(AvbVBMetaImageHeader, reserved) == 124,
+                "Remember to unittest byteswapping of newly added fields");
+}
+
+}  // namespace avb
diff --git a/avb/test/avbtool_signing_helper_test.py b/avb/test/avbtool_signing_helper_test.py
new file mode 100755
index 0000000..7291797
--- /dev/null
+++ b/avb/test/avbtool_signing_helper_test.py
@@ -0,0 +1,71 @@
+#!/usr/bin/env python
+
+#
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+#
+
+# This shell-script checks the symbols in libavb_host.a and fails
+# if a reference not starting with avb_ is referenced. It's intended
+# to catch mistakes where the standard C library is inadvertently
+# used.
+
+import subprocess
+import sys
+import errno
+import os
+
+def rsa_signer(argv):
+  if len(argv) != 3:
+    sys.stderr.write("Wrong number of arguments: {} <alg> <pub key>\n".format(argv[0]))
+    return errno.EINVAL
+
+  data = sys.stdin.read()
+  if len(data) == 0:
+    sys.stderr.write("There is not input data\n")
+    return errno.EINVAL
+
+  if 'SIGNING_HELPER_TEST' not in os.environ or os.environ['SIGNING_HELPER_TEST'] == "":
+    sys.stderr.write("env SIGNING_HELPER_TEST is not set or empty\n")
+    return errno.EINVAL
+
+  test_file_name = os.environ['SIGNING_HELPER_TEST']
+  if os.path.isfile(test_file_name) and not os.access(test_file_name, os.W_OK):
+    sys.stderr.write("no permission to write into {} file\n".format(test_file_name))
+    return errno.EACCESS
+
+  p = subprocess.Popen(
+      ['openssl', 'rsautl', '-sign', '-inkey', argv[2], '-raw'],
+      stdin=subprocess.PIPE)
+
+  p.communicate(data)
+  retcode = p.wait()
+  if retcode != 0:
+    return retcode
+
+  with open(test_file_name, "w") as f:
+    f.write("DONE")
+
+  return 0
+
+if __name__ == '__main__':
+    sys.exit(rsa_signer(sys.argv))
diff --git a/avb/test/avbtool_unittest.cc b/avb/test/avbtool_unittest.cc
new file mode 100644
index 0000000..b089e3c
--- /dev/null
+++ b/avb/test/avbtool_unittest.cc
@@ -0,0 +1,1323 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <iostream>
+
+#include <endian.h>
+#include <inttypes.h>
+#include <string.h>
+
+#include <base/files/file_util.h>
+#include <base/strings/string_split.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+
+#include <libavb/avb_sha.h>
+#include <libavb/libavb.h>
+
+#include "avb_unittest_util.h"
+
+namespace avb {
+
+class AvbToolTest : public BaseAvbToolTest {
+ public:
+  AvbToolTest() {}
+
+  void AddHashFooterTest(bool sparse_image);
+  void AddHashtreeFooterTest(bool sparse_image);
+  void AddHashtreeFooterFECTest(bool sparse_image);
+};
+
+// This test ensure that the version is increased in both
+// avb_boot_image.h and the avb tool.
+TEST_F(AvbToolTest, AvbVersionInSync) {
+  base::FilePath path = testdir_.Append("version.txt");
+  EXPECT_COMMAND(0, "./avbtool version > %s", path.value().c_str());
+  std::string printed_version;
+  ASSERT_TRUE(base::ReadFileToString(path, &printed_version));
+  base::TrimWhitespaceASCII(printed_version, base::TRIM_ALL, &printed_version);
+  std::string expected_version =
+      base::StringPrintf("%d.%d", AVB_MAJOR_VERSION, AVB_MINOR_VERSION);
+  EXPECT_EQ(printed_version, expected_version);
+}
+
+TEST_F(AvbToolTest, ExtractPublicKey) {
+  GenerateVBMetaImage("vbmeta.img",
+                      "SHA256_RSA2048",
+                      0,
+                      base::FilePath("test/data/testkey_rsa2048.pem"));
+
+  std::string key_data =
+      PublicKeyAVB(base::FilePath("test/data/testkey_rsa2048.pem"));
+
+  AvbVBMetaImageHeader h;
+  avb_vbmeta_image_header_to_host_byte_order(
+      reinterpret_cast<AvbVBMetaImageHeader*>(vbmeta_image_.data()), &h);
+  uint8_t* d = reinterpret_cast<uint8_t*>(vbmeta_image_.data());
+  size_t auxiliary_data_block_offset =
+      sizeof(AvbVBMetaImageHeader) + h.authentication_data_block_size;
+  EXPECT_GT(h.auxiliary_data_block_size, key_data.size());
+  EXPECT_EQ(0,
+            memcmp(key_data.data(),
+                   d + auxiliary_data_block_offset + h.public_key_offset,
+                   key_data.size()));
+}
+
+TEST_F(AvbToolTest, CheckDescriptors) {
+  GenerateVBMetaImage("vbmeta.img",
+                      "SHA256_RSA2048",
+                      0,
+                      base::FilePath("test/data/testkey_rsa2048.pem"),
+                      "--prop foo:brillo "
+                      "--prop bar:chromeos "
+                      "--prop prisoner:24601 "
+                      "--prop hexnumber:0xcafe "
+                      "--prop hexnumber_capital:0xCAFE "
+                      "--prop large_hexnumber:0xfedcba9876543210 "
+                      "--prop larger_than_uint64:0xfedcba98765432101 "
+                      "--prop almost_a_number:423x "
+                      "--prop_from_file blob:test/data/small_blob.bin ");
+
+  AvbVBMetaImageHeader h;
+  avb_vbmeta_image_header_to_host_byte_order(
+      reinterpret_cast<AvbVBMetaImageHeader*>(vbmeta_image_.data()), &h);
+
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_OK,
+            avb_vbmeta_image_verify(
+                vbmeta_image_.data(), vbmeta_image_.size(), nullptr, nullptr));
+
+  const char* s;
+  size_t len;
+  uint64_t val;
+
+  // Basic.
+  s = avb_property_lookup(
+      vbmeta_image_.data(), vbmeta_image_.size(), "foo", 0, &len);
+  EXPECT_EQ(0, strcmp(s, "brillo"));
+  EXPECT_EQ(6U, len);
+  s = avb_property_lookup(
+      vbmeta_image_.data(), vbmeta_image_.size(), "bar", 0, &len);
+  EXPECT_EQ(0, strcmp(s, "chromeos"));
+  EXPECT_EQ(8U, len);
+  s = avb_property_lookup(
+      vbmeta_image_.data(), vbmeta_image_.size(), "non-existant", 0, &len);
+  EXPECT_EQ(0U, len);
+  EXPECT_EQ(NULL, s);
+
+  // Numbers.
+  EXPECT_NE(
+      0,
+      avb_property_lookup_uint64(
+          vbmeta_image_.data(), vbmeta_image_.size(), "prisoner", 0, &val));
+  EXPECT_EQ(24601U, val);
+
+  EXPECT_NE(
+      0,
+      avb_property_lookup_uint64(
+          vbmeta_image_.data(), vbmeta_image_.size(), "hexnumber", 0, &val));
+  EXPECT_EQ(0xcafeU, val);
+
+  EXPECT_NE(0,
+            avb_property_lookup_uint64(vbmeta_image_.data(),
+                                       vbmeta_image_.size(),
+                                       "hexnumber_capital",
+                                       0,
+                                       &val));
+  EXPECT_EQ(0xcafeU, val);
+
+  EXPECT_NE(0,
+            avb_property_lookup_uint64(vbmeta_image_.data(),
+                                       vbmeta_image_.size(),
+                                       "large_hexnumber",
+                                       0,
+                                       &val));
+  EXPECT_EQ(0xfedcba9876543210U, val);
+
+  // We could catch overflows and return an error ... but we currently don't.
+  EXPECT_NE(0,
+            avb_property_lookup_uint64(vbmeta_image_.data(),
+                                       vbmeta_image_.size(),
+                                       "larger_than_uint64",
+                                       0,
+                                       &val));
+  EXPECT_EQ(0xedcba98765432101U, val);
+
+  // Number-parsing failures.
+  EXPECT_EQ(0,
+            avb_property_lookup_uint64(
+                vbmeta_image_.data(), vbmeta_image_.size(), "foo", 0, &val));
+
+  EXPECT_EQ(0,
+            avb_property_lookup_uint64(vbmeta_image_.data(),
+                                       vbmeta_image_.size(),
+                                       "almost_a_number",
+                                       0,
+                                       &val));
+
+  // Blobs.
+  //
+  // test/data/small_blob.bin is 21 byte file full of NUL-bytes except
+  // for the string "brillo ftw!" at index 2 and '\n' at the last
+  // byte.
+  s = avb_property_lookup(
+      vbmeta_image_.data(), vbmeta_image_.size(), "blob", 0, &len);
+  EXPECT_EQ(21U, len);
+  EXPECT_EQ(0, memcmp(s, "\0\0", 2));
+  EXPECT_EQ(0, memcmp(s + 2, "brillo ftw!", 11));
+  EXPECT_EQ(0, memcmp(s + 13, "\0\0\0\0\0\0\0", 7));
+  EXPECT_EQ('\n', s[20]);
+}
+
+TEST_F(AvbToolTest, CheckRollbackIndex) {
+  uint64_t rollback_index = 42;
+  GenerateVBMetaImage("vbmeta.img",
+                      "SHA256_RSA2048",
+                      rollback_index,
+                      base::FilePath("test/data/testkey_rsa2048.pem"));
+
+  AvbVBMetaImageHeader h;
+  avb_vbmeta_image_header_to_host_byte_order(
+      reinterpret_cast<AvbVBMetaImageHeader*>(vbmeta_image_.data()), &h);
+
+  EXPECT_EQ(rollback_index, h.rollback_index);
+}
+
+TEST_F(AvbToolTest, CheckPubkeyReturned) {
+  GenerateVBMetaImage("vbmeta.img",
+                      "SHA256_RSA2048",
+                      0,
+                      base::FilePath("test/data/testkey_rsa2048.pem"));
+
+  const uint8_t* pubkey = NULL;
+  size_t pubkey_length = 0;
+
+  EXPECT_EQ(
+      AVB_VBMETA_VERIFY_RESULT_OK,
+      avb_vbmeta_image_verify(
+          vbmeta_image_.data(), vbmeta_image_.size(), &pubkey, &pubkey_length));
+
+  AvbVBMetaImageHeader h;
+  avb_vbmeta_image_header_to_host_byte_order(
+      reinterpret_cast<AvbVBMetaImageHeader*>(vbmeta_image_.data()), &h);
+
+  EXPECT_EQ(pubkey_length, h.public_key_size);
+
+  const uint8_t* expected_pubkey =
+      vbmeta_image_.data() + sizeof(AvbVBMetaImageHeader) +
+      h.authentication_data_block_size + h.public_key_offset;
+  EXPECT_EQ(pubkey, expected_pubkey);
+}
+
+TEST_F(AvbToolTest, Info) {
+  GenerateVBMetaImage("vbmeta.img",
+                      "SHA256_RSA2048",
+                      0,
+                      base::FilePath("test/data/testkey_rsa2048.pem"),
+                      "--prop foo:brillo "
+                      "--prop bar:chromeos "
+                      "--prop prisoner:24601 "
+                      "--prop hexnumber:0xcafe "
+                      "--prop hexnumber_capital:0xCAFE "
+                      "--prop large_hexnumber:0xfedcba9876543210 "
+                      "--prop larger_than_uint64:0xfedcba98765432101 "
+                      "--prop almost_a_number:423x "
+                      "--prop_from_file blob:test/data/small_blob.bin "
+                      "--prop_from_file large_blob:test/data/large_blob.bin");
+
+  ASSERT_EQ(
+      "VBMeta image version:     1.0\n"
+      "Header Block:             256 bytes\n"
+      "Authentication Block:     320 bytes\n"
+      "Auxiliary Block:          3200 bytes\n"
+      "Algorithm:                SHA256_RSA2048\n"
+      "Rollback Index:           0\n"
+      "Flags:                    0\n"
+      "Descriptors:\n"
+      "    Prop: foo -> 'brillo'\n"
+      "    Prop: bar -> 'chromeos'\n"
+      "    Prop: prisoner -> '24601'\n"
+      "    Prop: hexnumber -> '0xcafe'\n"
+      "    Prop: hexnumber_capital -> '0xCAFE'\n"
+      "    Prop: large_hexnumber -> '0xfedcba9876543210'\n"
+      "    Prop: larger_than_uint64 -> '0xfedcba98765432101'\n"
+      "    Prop: almost_a_number -> '423x'\n"
+      "    Prop: blob -> '\\x00\\x00brillo "
+      "ftw!\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\n'\n"
+      "    Prop: large_blob -> (2048 bytes)\n",
+      InfoImage(vbmeta_image_path_));
+}
+
+static bool collect_descriptors(const AvbDescriptor* descriptor,
+                                void* user_data) {
+  std::vector<const AvbDescriptor*>* descriptors =
+      reinterpret_cast<std::vector<const AvbDescriptor*>*>(user_data);
+  descriptors->push_back(descriptor);
+  return true;  // Keep iterating.
+}
+
+void AvbToolTest::AddHashFooterTest(bool sparse_image) {
+  const size_t rootfs_size = 1028 * 1024;
+  const size_t partition_size = 1536 * 1024;
+
+  // Generate a 1028 KiB file with known content. Some content have
+  // been arranged to ensure FILL_DATA segments in the sparse file.
+  std::vector<uint8_t> rootfs;
+  rootfs.resize(rootfs_size);
+  for (size_t n = 0; n < rootfs_size; n++) {
+    if ((n >= 5 * 1000 && n < 105 * 1000) ||
+        (n >= 205 * 1000 && n < 305 * 1000) ||
+        (n >= 505 * 1000 && n < 605 * 1000)) {
+      rootfs[n] = uint8_t(n) & 0x03;
+    } else {
+      rootfs[n] = uint8_t(n);
+    }
+  }
+  base::FilePath rootfs_path = testdir_.Append("rootfs.bin");
+  EXPECT_EQ(rootfs_size,
+            static_cast<const size_t>(
+                base::WriteFile(rootfs_path,
+                                reinterpret_cast<const char*>(rootfs.data()),
+                                rootfs.size())));
+
+  if (sparse_image) {
+    EXPECT_COMMAND(0,
+                   "mv %s %s.unsparse",
+                   rootfs_path.value().c_str(),
+                   rootfs_path.value().c_str());
+    EXPECT_COMMAND(0,
+                   "img2simg %s.unsparse %s",
+                   rootfs_path.value().c_str(),
+                   rootfs_path.value().c_str());
+    EXPECT_COMMAND(0, "rm -f %s.unsparse", rootfs_path.value().c_str());
+  }
+
+  /* Do this twice to check that 'add_hash_footer' is idempotent. */
+  for (int n = 0; n < 2; n++) {
+    EXPECT_COMMAND(0,
+                   "./avbtool add_hash_footer --salt d00df00d "
+                   "--hash_algorithm sha256 --image %s "
+                   "--partition_size %d --partition_name foobar "
+                   "--algorithm SHA256_RSA2048 "
+                   "--key test/data/testkey_rsa2048.pem",
+                   rootfs_path.value().c_str(),
+                   (int)partition_size);
+
+    ASSERT_EQ(base::StringPrintf("Footer version:           1.0\n"
+                                 "Image size:               1572864 bytes\n"
+                                 "Original image size:      1052672 bytes\n"
+                                 "VBMeta offset:            1052672\n"
+                                 "VBMeta size:              1280 bytes\n"
+                                 "--\n"
+                                 "VBMeta image version:     1.0%s\n"
+                                 "Header Block:             256 bytes\n"
+                                 "Authentication Block:     320 bytes\n"
+                                 "Auxiliary Block:          704 bytes\n"
+                                 "Algorithm:                SHA256_RSA2048\n"
+                                 "Rollback Index:           0\n"
+                                 "Flags:                    0\n"
+                                 "Descriptors:\n"
+                                 "    Hash descriptor:\n"
+                                 "      Image Size:            1052672 bytes\n"
+                                 "      Hash Algorithm:        sha256\n"
+                                 "      Partition Name:        foobar\n"
+                                 "      Salt:                  d00df00d\n"
+                                 "      Digest:                "
+                                 "9a58cc996d405e08a1e00f96dbfe9104fedf41cb83b1f"
+                                 "5e4ed357fbcf58d88d9\n",
+                                 sparse_image ? " (Sparse)" : ""),
+              InfoImage(rootfs_path));
+  }
+
+  if (sparse_image) {
+    EXPECT_COMMAND(0,
+                   "mv %s %s.sparse",
+                   rootfs_path.value().c_str(),
+                   rootfs_path.value().c_str());
+    EXPECT_COMMAND(0,
+                   "simg2img %s.sparse %s",
+                   rootfs_path.value().c_str(),
+                   rootfs_path.value().c_str());
+    EXPECT_COMMAND(0, "rm -f %s.sparse", rootfs_path.value().c_str());
+  }
+
+  // Manually calculate the hash to check that it agrees with avbtool.
+  AvbSHA256Ctx hasher_ctx;
+  const uint8_t hasher_salt[4] = {0xd0, 0x0d, 0xf0, 0x0d};
+  avb_sha256_init(&hasher_ctx);
+  avb_sha256_update(&hasher_ctx, hasher_salt, 4);
+  avb_sha256_update(&hasher_ctx, rootfs.data(), rootfs_size);
+  uint8_t* hasher_digest = avb_sha256_final(&hasher_ctx);
+  EXPECT_EQ("9a58cc996d405e08a1e00f96dbfe9104fedf41cb83b1f5e4ed357fbcf58d88d9",
+            mem_to_hexstring(hasher_digest, AVB_SHA256_DIGEST_SIZE));
+
+  // Now check that we can find the VBMeta block again from the footer.
+  std::string part_data;
+  ASSERT_TRUE(base::ReadFileToString(rootfs_path, &part_data));
+
+  // Check footer contains correct data.
+  AvbFooter f;
+  EXPECT_NE(0,
+            avb_footer_validate_and_byteswap(
+                reinterpret_cast<const AvbFooter*>(
+                    part_data.data() + part_data.size() - AVB_FOOTER_SIZE),
+                &f));
+  EXPECT_EQ(
+      std::string(reinterpret_cast<const char*>(f.magic), AVB_FOOTER_MAGIC_LEN),
+      AVB_FOOTER_MAGIC);
+  EXPECT_EQ(AVB_FOOTER_MAJOR_VERSION, (int)f.version_major);
+  EXPECT_EQ(AVB_FOOTER_MINOR_VERSION, (int)f.version_minor);
+  EXPECT_EQ(1052672UL, f.original_image_size);
+  EXPECT_EQ(1052672UL, f.vbmeta_offset);
+  EXPECT_EQ(1280UL, f.vbmeta_size);
+
+  // Check that the vbmeta image at |f.vbmeta_offset| checks out.
+  const uint8_t* vbmeta_data =
+      reinterpret_cast<const uint8_t*>(part_data.data() + f.vbmeta_offset);
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_OK,
+            avb_vbmeta_image_verify(vbmeta_data, f.vbmeta_size, NULL, NULL));
+
+  // Collect all descriptors.
+  std::vector<const AvbDescriptor*> descriptors;
+  avb_descriptor_foreach(
+      vbmeta_data, f.vbmeta_size, collect_descriptors, &descriptors);
+
+  // We should only have a single descriptor and it should be a
+  // hash descriptor.
+  EXPECT_EQ(1UL, descriptors.size());
+  EXPECT_EQ(AVB_DESCRIPTOR_TAG_HASH, avb_be64toh(descriptors[0]->tag));
+  AvbHashDescriptor d;
+  EXPECT_NE(
+      0,
+      avb_hash_descriptor_validate_and_byteswap(
+          reinterpret_cast<const AvbHashDescriptor*>(descriptors[0]), &d));
+  EXPECT_EQ(1052672UL, d.image_size);
+  EXPECT_EQ(6UL, d.partition_name_len);
+  EXPECT_EQ(4UL, d.salt_len);
+  EXPECT_EQ(32UL, d.digest_len);
+  const uint8_t* desc_end = reinterpret_cast<const uint8_t*>(descriptors[0]) +
+                            sizeof(AvbHashDescriptor);
+  uint64_t o = 0;
+  EXPECT_EQ("foobar",
+            std::string(reinterpret_cast<const char*>(desc_end + o),
+                        d.partition_name_len));
+  o += d.partition_name_len;
+  EXPECT_EQ("d00df00d", mem_to_hexstring(desc_end + o, d.salt_len));
+  o += d.salt_len;
+  EXPECT_EQ("9a58cc996d405e08a1e00f96dbfe9104fedf41cb83b1f5e4ed357fbcf58d88d9",
+            mem_to_hexstring(desc_end + o, d.digest_len));
+
+  // Check that the footer is correctly erased.
+  EXPECT_COMMAND(
+      0, "./avbtool erase_footer --image %s", rootfs_path.value().c_str());
+  int64_t erased_footer_file_size;
+  ASSERT_TRUE(base::GetFileSize(rootfs_path, &erased_footer_file_size));
+  EXPECT_EQ(static_cast<size_t>(erased_footer_file_size), rootfs_size);
+}
+
+TEST_F(AvbToolTest, AddHashFooter) {
+  AddHashFooterTest(false);
+}
+
+TEST_F(AvbToolTest, AddHashFooterSparse) {
+  AddHashFooterTest(true);
+}
+
+static std::string RemoveLinesStartingWith(const std::string& str,
+                                           const std::string& prefix) {
+  std::vector<std::string> lines;
+  std::string ret;
+
+  lines = base::SplitString(
+      str, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
+  for (const std::string& line : lines) {
+    if (!base::StartsWith(line, prefix, base::CompareCase::SENSITIVE)) {
+      ret += line;
+      ret += '\n';
+    }
+  }
+  return ret;
+}
+
+TEST_F(AvbToolTest, AddHashFooterSparseWithHoleAtTheEnd) {
+  const size_t partition_size = 10 * 1024 * 1024;
+  const size_t metadata_size = 128 * 1024;
+
+  // It's not enough to run img2simg on a file with a lot of zeroes at
+  // the end since that will turn up as "Fill with value (for value =
+  // 0x00000000)" and not "Don't care". Instead, use make_ext4fs for
+  // this since it will put a big hole (e.g. "Don't care" chunk) at
+  // the end.
+  base::FilePath partition_path = testdir_.Append("partition.bin");
+  EXPECT_COMMAND(0,
+                 "make_ext4fs -s -L test -l %zd %s",
+                 partition_size - metadata_size,
+                 partition_path.value().c_str());
+
+  EXPECT_COMMAND(0,
+                 "./avbtool add_hash_footer --salt d00df00d "
+                 "--hash_algorithm sha256 --image %s "
+                 "--partition_size %d --partition_name foobar "
+                 "--algorithm SHA256_RSA2048 "
+                 "--key test/data/testkey_rsa2048.pem",
+                 partition_path.value().c_str(),
+                 (int)partition_size);
+
+  // Since we may be using an arbritary version of make_ext4fs
+  // (because of different branches) the contents of the resulting
+  // disk image may slightly change. It's enough to just remove the
+  // "Digest:" line from the output to work around this.
+  std::string info =
+      RemoveLinesStartingWith(InfoImage(partition_path), "      Digest:");
+  ASSERT_EQ(
+      "Footer version:           1.0\n"
+      "Image size:               10485760 bytes\n"
+      "Original image size:      10354688 bytes\n"
+      "VBMeta offset:            10354688\n"
+      "VBMeta size:              1280 bytes\n"
+      "--\n"
+      "VBMeta image version:     1.0 (Sparse)\n"
+      "Header Block:             256 bytes\n"
+      "Authentication Block:     320 bytes\n"
+      "Auxiliary Block:          704 bytes\n"
+      "Algorithm:                SHA256_RSA2048\n"
+      "Rollback Index:           0\n"
+      "Flags:                    0\n"
+      "Descriptors:\n"
+      "    Hash descriptor:\n"
+      "      Image Size:            10354688 bytes\n"
+      "      Hash Algorithm:        sha256\n"
+      "      Partition Name:        foobar\n"
+      "      Salt:                  d00df00d\n",
+      info);
+
+  EXPECT_COMMAND(0,
+                 "mv %s %s.sparse",
+                 partition_path.value().c_str(),
+                 partition_path.value().c_str());
+  EXPECT_COMMAND(0,
+                 "simg2img %s.sparse %s",
+                 partition_path.value().c_str(),
+                 partition_path.value().c_str());
+  EXPECT_COMMAND(0, "rm -f %s.sparse", partition_path.value().c_str());
+}
+
+void AvbToolTest::AddHashtreeFooterTest(bool sparse_image) {
+  const size_t rootfs_size = 1028 * 1024;
+  const size_t partition_size = 1536 * 1024;
+
+  // Generate a 1028 KiB file with known content.
+  std::vector<uint8_t> rootfs;
+  rootfs.resize(rootfs_size);
+  for (size_t n = 0; n < rootfs_size; n++)
+    rootfs[n] = uint8_t(n);
+  base::FilePath rootfs_path = testdir_.Append("rootfs.bin");
+  EXPECT_EQ(rootfs_size,
+            static_cast<const size_t>(
+                base::WriteFile(rootfs_path,
+                                reinterpret_cast<const char*>(rootfs.data()),
+                                rootfs.size())));
+
+  if (sparse_image) {
+    EXPECT_COMMAND(0,
+                   "mv %s %s.unsparse",
+                   rootfs_path.value().c_str(),
+                   rootfs_path.value().c_str());
+    EXPECT_COMMAND(0,
+                   "img2simg %s.unsparse %s",
+                   rootfs_path.value().c_str(),
+                   rootfs_path.value().c_str());
+    EXPECT_COMMAND(0, "rm -f %s.unsparse", rootfs_path.value().c_str());
+  }
+
+  /* Do this twice to check that 'add_hashtree_footer' is idempotent. */
+  for (int n = 0; n < 2; n++) {
+    EXPECT_COMMAND(0,
+                   "./avbtool add_hashtree_footer --salt d00df00d --image %s "
+                   "--partition_size %d --partition_name foobar "
+                   "--algorithm SHA256_RSA2048 "
+                   "--key test/data/testkey_rsa2048.pem",
+                   rootfs_path.value().c_str(),
+                   (int)partition_size);
+
+    ASSERT_EQ(base::StringPrintf("Footer version:           1.0\n"
+                                 "Image size:               1572864 bytes\n"
+                                 "Original image size:      1052672 bytes\n"
+                                 "VBMeta offset:            1069056\n"
+                                 "VBMeta size:              1344 bytes\n"
+                                 "--\n"
+                                 "VBMeta image version:     1.0%s\n"
+                                 "Header Block:             256 bytes\n"
+                                 "Authentication Block:     320 bytes\n"
+                                 "Auxiliary Block:          768 bytes\n"
+                                 "Algorithm:                SHA256_RSA2048\n"
+                                 "Rollback Index:           0\n"
+                                 "Flags:                    0\n"
+                                 "Descriptors:\n"
+                                 "    Hashtree descriptor:\n"
+                                 "      Version of dm-verity:  1\n"
+                                 "      Image Size:            1052672 bytes\n"
+                                 "      Tree Offset:           1052672\n"
+                                 "      Tree Size:             16384 bytes\n"
+                                 "      Data Block Size:       4096 bytes\n"
+                                 "      Hash Block Size:       4096 bytes\n"
+                                 "      FEC num roots:         0\n"
+                                 "      FEC offset:            0\n"
+                                 "      FEC size:              0 bytes\n"
+                                 "      Hash Algorithm:        sha1\n"
+                                 "      Partition Name:        foobar\n"
+                                 "      Salt:                  d00df00d\n"
+                                 "      Root Digest:           "
+                                 "e811611467dcd6e8dc4324e45f706c2bdd51db67\n",
+                                 sparse_image ? " (Sparse)" : ""),
+              InfoImage(rootfs_path));
+  }
+
+  if (sparse_image) {
+    EXPECT_COMMAND(0,
+                   "mv %s %s.sparse",
+                   rootfs_path.value().c_str(),
+                   rootfs_path.value().c_str());
+    EXPECT_COMMAND(0,
+                   "simg2img %s.sparse %s",
+                   rootfs_path.value().c_str(),
+                   rootfs_path.value().c_str());
+    EXPECT_COMMAND(0, "rm -f %s.sparse", rootfs_path.value().c_str());
+  }
+
+  // To check that we generate the correct hashtree we can use
+  // veritysetup(1) - another codebase for working with dm-verity
+  // hashtrees - to verify it.
+  //
+  // If we don't want to impose the requirement of having the
+  // veritysetup(1) command available on builders we can comment this
+  // out.
+  EXPECT_COMMAND(0,
+                 "veritysetup --no-superblock --format=1 --hash=sha1 "
+                 "--data-block-size=4096 --hash-block-size=4096 "
+                 "--salt=d00df00d "
+                 "--data-blocks=257 "
+                 "--hash-offset=1052672 "
+                 "verify "
+                 "%s %s "
+                 "e811611467dcd6e8dc4324e45f706c2bdd51db67",
+                 rootfs_path.value().c_str(),
+                 rootfs_path.value().c_str());
+
+  // Now check that we can find the VBMeta block again from the footer.
+  std::string part_data;
+  ASSERT_TRUE(base::ReadFileToString(rootfs_path, &part_data));
+
+  // Check footer contains correct data.
+  AvbFooter f;
+  EXPECT_NE(0,
+            avb_footer_validate_and_byteswap(
+                reinterpret_cast<const AvbFooter*>(
+                    part_data.data() + part_data.size() - AVB_FOOTER_SIZE),
+                &f));
+  EXPECT_EQ(
+      std::string(reinterpret_cast<const char*>(f.magic), AVB_FOOTER_MAGIC_LEN),
+      AVB_FOOTER_MAGIC);
+  EXPECT_EQ(AVB_FOOTER_MAJOR_VERSION, (int)f.version_major);
+  EXPECT_EQ(AVB_FOOTER_MINOR_VERSION, (int)f.version_minor);
+  EXPECT_EQ(1052672UL, f.original_image_size);
+  EXPECT_EQ(1069056UL, f.vbmeta_offset);
+  EXPECT_EQ(1344UL, f.vbmeta_size);
+
+  // Check that the vbmeta image at |f.vbmeta_offset| checks out.
+  const uint8_t* vbmeta_data =
+      reinterpret_cast<const uint8_t*>(part_data.data() + f.vbmeta_offset);
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_OK,
+            avb_vbmeta_image_verify(vbmeta_data, f.vbmeta_size, NULL, NULL));
+
+  // Collect all descriptors.
+  std::vector<const AvbDescriptor*> descriptors;
+  avb_descriptor_foreach(
+      vbmeta_data, f.vbmeta_size, collect_descriptors, &descriptors);
+
+  // We should only have a single descriptor and it should be a
+  // hashtree descriptor.
+  EXPECT_EQ(1UL, descriptors.size());
+  EXPECT_EQ(AVB_DESCRIPTOR_TAG_HASHTREE, avb_be64toh(descriptors[0]->tag));
+  AvbHashtreeDescriptor d;
+  EXPECT_NE(
+      0,
+      avb_hashtree_descriptor_validate_and_byteswap(
+          reinterpret_cast<const AvbHashtreeDescriptor*>(descriptors[0]), &d));
+  EXPECT_EQ(1UL, d.dm_verity_version);
+  EXPECT_EQ(1052672UL, d.image_size);
+  EXPECT_EQ(1052672UL, d.tree_offset);
+  EXPECT_EQ(16384UL, d.tree_size);
+  EXPECT_EQ(4096UL, d.data_block_size);
+  EXPECT_EQ(4096UL, d.hash_block_size);
+  EXPECT_EQ(6UL, d.partition_name_len);
+  EXPECT_EQ(4UL, d.salt_len);
+  EXPECT_EQ(20UL, d.root_digest_len);
+  const uint8_t* desc_end = reinterpret_cast<const uint8_t*>(descriptors[0]) +
+                            sizeof(AvbHashtreeDescriptor);
+  uint64_t o = 0;
+  EXPECT_EQ("foobar",
+            std::string(reinterpret_cast<const char*>(desc_end + o),
+                        d.partition_name_len));
+  o += d.partition_name_len;
+  EXPECT_EQ("d00df00d", mem_to_hexstring(desc_end + o, d.salt_len));
+  o += d.salt_len;
+  EXPECT_EQ("e811611467dcd6e8dc4324e45f706c2bdd51db67",
+            mem_to_hexstring(desc_end + o, d.root_digest_len));
+
+  // Check that we correctly generate dm-verity kernel cmdline
+  // snippets, if requested.
+  base::FilePath vbmeta_dmv_path = testdir_.Append("vbmeta_dm_verity_desc.bin");
+  EXPECT_COMMAND(0,
+                 "./avbtool make_vbmeta_image "
+                 "--output %s "
+                 "--setup_rootfs_from_kernel %s "
+                 "--algorithm SHA256_RSA2048 "
+                 "--key test/data/testkey_rsa2048.pem",
+                 vbmeta_dmv_path.value().c_str(),
+                 rootfs_path.value().c_str());
+
+  ASSERT_EQ(
+      "VBMeta image version:     1.0\n"
+      "Header Block:             256 bytes\n"
+      "Authentication Block:     320 bytes\n"
+      "Auxiliary Block:          896 bytes\n"
+      "Algorithm:                SHA256_RSA2048\n"
+      "Rollback Index:           0\n"
+      "Flags:                    0\n"
+      "Descriptors:\n"
+      "    Kernel Cmdline descriptor:\n"
+      "      Flags:                 1\n"
+      "      Kernel Cmdline:        'dm=\"1 vroot none ro 1,0 2056 verity 1 "
+      "PARTUUID=$(ANDROID_SYSTEM_PARTUUID) PARTUUID=$(ANDROID_SYSTEM_PARTUUID) "
+      "4096 4096 257 257 sha1 e811611467dcd6e8dc4324e45f706c2bdd51db67 "
+      "d00df00d 2 restart_on_corruption ignore_zero_blocks\" root=0xfd00'\n"
+      "    Kernel Cmdline descriptor:\n"
+      "      Flags:                 2\n"
+      "      Kernel Cmdline:        "
+      "'root=PARTUUID=$(ANDROID_SYSTEM_PARTUUID)'\n",
+      InfoImage(vbmeta_dmv_path));
+
+  // Check that the footer is correctly erased and the hashtree
+  // remains - see above for why the constant 1069056 is used.
+  EXPECT_COMMAND(0,
+                 "./avbtool erase_footer --image %s --keep_hashtree",
+                 rootfs_path.value().c_str());
+  int64_t erased_footer_file_size;
+  ASSERT_TRUE(base::GetFileSize(rootfs_path, &erased_footer_file_size));
+  EXPECT_EQ(static_cast<size_t>(erased_footer_file_size), 1069056UL);
+}
+
+TEST_F(AvbToolTest, AddHashtreeFooter) {
+  AddHashtreeFooterTest(false);
+}
+
+TEST_F(AvbToolTest, AddHashtreeFooterSparse) {
+  AddHashtreeFooterTest(true);
+}
+
+void AvbToolTest::AddHashtreeFooterFECTest(bool sparse_image) {
+  const size_t rootfs_size = 1028 * 1024;
+  const size_t partition_size = 1536 * 1024;
+
+  // Generate a 1028 KiB file with known content.
+  std::vector<uint8_t> rootfs;
+  rootfs.resize(rootfs_size);
+  for (size_t n = 0; n < rootfs_size; n++)
+    rootfs[n] = uint8_t(n);
+  base::FilePath rootfs_path = testdir_.Append("rootfs.bin");
+  EXPECT_EQ(rootfs_size,
+            static_cast<const size_t>(
+                base::WriteFile(rootfs_path,
+                                reinterpret_cast<const char*>(rootfs.data()),
+                                rootfs.size())));
+
+  if (sparse_image) {
+    EXPECT_COMMAND(0,
+                   "mv %s %s.unsparse",
+                   rootfs_path.value().c_str(),
+                   rootfs_path.value().c_str());
+    EXPECT_COMMAND(0,
+                   "img2simg %s.unsparse %s",
+                   rootfs_path.value().c_str(),
+                   rootfs_path.value().c_str());
+    EXPECT_COMMAND(0, "rm -f %s.unsparse", rootfs_path.value().c_str());
+  }
+
+  /* Do this twice to check that 'add_hashtree_footer' is idempotent. */
+  for (int n = 0; n < 2; n++) {
+    EXPECT_COMMAND(0,
+                   "./avbtool add_hashtree_footer --salt d00df00d --image %s "
+                   "--partition_size %d --partition_name foobar "
+                   "--generate_fec "
+                   "--algorithm SHA256_RSA2048 "
+                   "--key test/data/testkey_rsa2048.pem",
+                   rootfs_path.value().c_str(),
+                   (int)partition_size);
+
+    ASSERT_EQ(base::StringPrintf("Footer version:           1.0\n"
+                                 "Image size:               1572864 bytes\n"
+                                 "Original image size:      1052672 bytes\n"
+                                 "VBMeta offset:            1085440\n"
+                                 "VBMeta size:              1344 bytes\n"
+                                 "--\n"
+                                 "VBMeta image version:     1.0%s\n"
+                                 "Header Block:             256 bytes\n"
+                                 "Authentication Block:     320 bytes\n"
+                                 "Auxiliary Block:          768 bytes\n"
+                                 "Algorithm:                SHA256_RSA2048\n"
+                                 "Rollback Index:           0\n"
+                                 "Flags:                    0\n"
+                                 "Descriptors:\n"
+                                 "    Hashtree descriptor:\n"
+                                 "      Version of dm-verity:  1\n"
+                                 "      Image Size:            1052672 bytes\n"
+                                 "      Tree Offset:           1052672\n"
+                                 "      Tree Size:             16384 bytes\n"
+                                 "      Data Block Size:       4096 bytes\n"
+                                 "      Hash Block Size:       4096 bytes\n"
+                                 "      FEC num roots:         2\n"
+                                 "      FEC offset:            1069056\n"
+                                 "      FEC size:              16384 bytes\n"
+                                 "      Hash Algorithm:        sha1\n"
+                                 "      Partition Name:        foobar\n"
+                                 "      Salt:                  d00df00d\n"
+                                 "      Root Digest:           "
+                                 "e811611467dcd6e8dc4324e45f706c2bdd51db67\n",
+                                 sparse_image ? " (Sparse)" : ""),
+              InfoImage(rootfs_path));
+  }
+
+  if (sparse_image) {
+    EXPECT_COMMAND(0,
+                   "mv %s %s.sparse",
+                   rootfs_path.value().c_str(),
+                   rootfs_path.value().c_str());
+    EXPECT_COMMAND(0,
+                   "simg2img %s.sparse %s",
+                   rootfs_path.value().c_str(),
+                   rootfs_path.value().c_str());
+    EXPECT_COMMAND(0, "rm -f %s.sparse", rootfs_path.value().c_str());
+  }
+
+  /* TODO: would be nice to verify that the FEC data is correct. */
+
+  // Now check that we can find the VBMeta block again from the footer.
+  std::string part_data;
+  ASSERT_TRUE(base::ReadFileToString(rootfs_path, &part_data));
+
+  // Check footer contains correct data.
+  AvbFooter f;
+  EXPECT_NE(0,
+            avb_footer_validate_and_byteswap(
+                reinterpret_cast<const AvbFooter*>(
+                    part_data.data() + part_data.size() - AVB_FOOTER_SIZE),
+                &f));
+  EXPECT_EQ(
+      std::string(reinterpret_cast<const char*>(f.magic), AVB_FOOTER_MAGIC_LEN),
+      AVB_FOOTER_MAGIC);
+  EXPECT_EQ(AVB_FOOTER_MAJOR_VERSION, (int)f.version_major);
+  EXPECT_EQ(AVB_FOOTER_MINOR_VERSION, (int)f.version_minor);
+  EXPECT_EQ(1052672UL, f.original_image_size);
+  EXPECT_EQ(1085440UL, f.vbmeta_offset);
+  EXPECT_EQ(1344UL, f.vbmeta_size);
+
+  // Check that the vbmeta image at |f.vbmeta_offset| checks out.
+  const uint8_t* vbmeta_data =
+      reinterpret_cast<const uint8_t*>(part_data.data() + f.vbmeta_offset);
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_OK,
+            avb_vbmeta_image_verify(vbmeta_data, f.vbmeta_size, NULL, NULL));
+
+  // Collect all descriptors.
+  std::vector<const AvbDescriptor*> descriptors;
+  avb_descriptor_foreach(
+      vbmeta_data, f.vbmeta_size, collect_descriptors, &descriptors);
+
+  // We should only have a single descriptor and it should be a
+  // hashtree descriptor.
+  EXPECT_EQ(1UL, descriptors.size());
+  EXPECT_EQ(AVB_DESCRIPTOR_TAG_HASHTREE, avb_be64toh(descriptors[0]->tag));
+  AvbHashtreeDescriptor d;
+  EXPECT_NE(
+      0,
+      avb_hashtree_descriptor_validate_and_byteswap(
+          reinterpret_cast<const AvbHashtreeDescriptor*>(descriptors[0]), &d));
+  EXPECT_EQ(1UL, d.dm_verity_version);
+  EXPECT_EQ(1052672UL, d.image_size);
+  EXPECT_EQ(1052672UL, d.tree_offset);
+  EXPECT_EQ(16384UL, d.tree_size);
+  EXPECT_EQ(4096UL, d.data_block_size);
+  EXPECT_EQ(2UL, d.fec_num_roots);
+  EXPECT_EQ(1069056UL, d.fec_offset);
+  EXPECT_EQ(16384UL, d.fec_size);
+  EXPECT_EQ(6UL, d.partition_name_len);
+  EXPECT_EQ(4UL, d.salt_len);
+  EXPECT_EQ(20UL, d.root_digest_len);
+  const uint8_t* desc_end = reinterpret_cast<const uint8_t*>(descriptors[0]) +
+                            sizeof(AvbHashtreeDescriptor);
+  uint64_t o = 0;
+  EXPECT_EQ("foobar",
+            std::string(reinterpret_cast<const char*>(desc_end + o),
+                        d.partition_name_len));
+  o += d.partition_name_len;
+  EXPECT_EQ("d00df00d", mem_to_hexstring(desc_end + o, d.salt_len));
+  o += d.salt_len;
+  EXPECT_EQ("e811611467dcd6e8dc4324e45f706c2bdd51db67",
+            mem_to_hexstring(desc_end + o, d.root_digest_len));
+
+  // Check that we correctly generate dm-verity kernel cmdline
+  // snippets, if requested.
+  base::FilePath vbmeta_dmv_path = testdir_.Append("vbmeta_dm_verity_desc.bin");
+  EXPECT_COMMAND(0,
+                 "./avbtool make_vbmeta_image "
+                 "--output %s "
+                 "--setup_rootfs_from_kernel %s "
+                 "--algorithm SHA256_RSA2048 "
+                 "--key test/data/testkey_rsa2048.pem",
+                 vbmeta_dmv_path.value().c_str(),
+                 rootfs_path.value().c_str());
+
+  ASSERT_EQ(
+      "VBMeta image version:     1.0\n"
+      "Header Block:             256 bytes\n"
+      "Authentication Block:     320 bytes\n"
+      "Auxiliary Block:          960 bytes\n"
+      "Algorithm:                SHA256_RSA2048\n"
+      "Rollback Index:           0\n"
+      "Flags:                    0\n"
+      "Descriptors:\n"
+      "    Kernel Cmdline descriptor:\n"
+      "      Flags:                 1\n"
+      "      Kernel Cmdline:        'dm=\"1 vroot none ro 1,0 2056 verity 1 "
+      "PARTUUID=$(ANDROID_SYSTEM_PARTUUID) PARTUUID=$(ANDROID_SYSTEM_PARTUUID) "
+      "4096 4096 257 257 sha1 e811611467dcd6e8dc4324e45f706c2bdd51db67 "
+      "d00df00d 10 restart_on_corruption ignore_zero_blocks "
+      "use_fec_from_device "
+      "PARTUUID=$(ANDROID_SYSTEM_PARTUUID) fec_roots 2 fec_blocks 261 "
+      "fec_start 261\" root=0xfd00'\n"
+      "    Kernel Cmdline descriptor:\n"
+      "      Flags:                 2\n"
+      "      Kernel Cmdline:        "
+      "'root=PARTUUID=$(ANDROID_SYSTEM_PARTUUID)'\n",
+      InfoImage(vbmeta_dmv_path));
+
+  // Check that the footer is correctly erased and the hashtree and
+  // FEC data remains. The constant 1085440 is used because it's where
+  // the FEC data ends (it's at offset 1069056 and size 16384).
+  EXPECT_COMMAND(0,
+                 "./avbtool erase_footer --image %s --keep_hashtree",
+                 rootfs_path.value().c_str());
+  int64_t erased_footer_file_size;
+  ASSERT_TRUE(base::GetFileSize(rootfs_path, &erased_footer_file_size));
+  EXPECT_EQ(static_cast<size_t>(erased_footer_file_size), 1085440UL);
+}
+
+TEST_F(AvbToolTest, AddHashtreeFooterFEC) {
+  AddHashtreeFooterFECTest(false);
+}
+
+TEST_F(AvbToolTest, AddHashtreeFooterFECSparse) {
+  AddHashtreeFooterFECTest(true);
+}
+
+TEST_F(AvbToolTest, AddHashtreeFooterCalcMaxImageSize) {
+  const size_t partition_size = 10 * 1024 * 1024;
+  base::FilePath output_path = testdir_.Append("max_size.txt");
+
+  EXPECT_COMMAND(0,
+                 "./avbtool add_hashtree_footer "
+                 "--partition_size %zd --calc_max_image_size > %s",
+                 partition_size,
+                 output_path.value().c_str());
+  std::string max_image_size_data;
+  EXPECT_TRUE(base::ReadFileToString(output_path, &max_image_size_data));
+  EXPECT_EQ("10330112\n", max_image_size_data);
+  size_t max_image_size = atoll(max_image_size_data.c_str());
+
+  // Hashtree and metadata takes up 152 KiB - compare to below with
+  // FEC which is 244 KiB.
+  EXPECT_EQ(152 * 1024ULL, partition_size - max_image_size);
+
+  // Check that we can add a hashtree with an image this size for such
+  // a partition size.
+  base::FilePath system_path = GenerateImage("system", max_image_size);
+  EXPECT_COMMAND(0,
+                 "./avbtool add_hashtree_footer"
+                 " --image %s"
+                 " --partition_name system"
+                 " --partition_size %zd"
+                 " --salt deadbeef"
+                 " --algorithm SHA512_RSA4096 "
+                 " --key test/data/testkey_rsa4096.pem",
+                 system_path.value().c_str(),
+                 partition_size);
+}
+
+TEST_F(AvbToolTest, AddHashtreeFooterCalcMaxImageSizeWithFEC) {
+  const size_t partition_size = 10 * 1024 * 1024;
+  base::FilePath output_path = testdir_.Append("max_size.txt");
+
+  EXPECT_COMMAND(0,
+                 "./avbtool add_hashtree_footer "
+                 "--partition_size %zd --generate_fec "
+                 "--calc_max_image_size > %s",
+                 partition_size,
+                 output_path.value().c_str());
+  std::string max_image_size_data;
+  EXPECT_TRUE(base::ReadFileToString(output_path, &max_image_size_data));
+  EXPECT_EQ("10235904\n", max_image_size_data);
+  size_t max_image_size = atoll(max_image_size_data.c_str());
+
+  // Hashtree, FEC codes, and metadata takes up 244 KiB - compare to
+  // above wihtout FEC which is 152 KiB.
+  EXPECT_EQ(244 * 1024ULL, partition_size - max_image_size);
+
+  // Check that we can add a hashtree with an image this size for such
+  // a partition size.
+  base::FilePath system_path = GenerateImage("system", max_image_size);
+  EXPECT_COMMAND(0,
+                 "./avbtool add_hashtree_footer"
+                 " --image %s"
+                 " --partition_name system"
+                 " --partition_size %zd"
+                 " --salt deadbeef"
+                 " --generate_fec "
+                 " --algorithm SHA512_RSA4096 "
+                 " --key test/data/testkey_rsa4096.pem",
+                 system_path.value().c_str(),
+                 partition_size);
+}
+
+TEST_F(AvbToolTest, KernelCmdlineDescriptor) {
+  base::FilePath vbmeta_path =
+      testdir_.Append("vbmeta_kernel_cmdline_desc.bin");
+
+  EXPECT_COMMAND(0,
+                 "./avbtool make_vbmeta_image "
+                 "--output %s "
+                 "--kernel_cmdline 'foo bar baz' "
+                 "--kernel_cmdline 'second cmdline' "
+                 "--algorithm SHA256_RSA2048 "
+                 "--key test/data/testkey_rsa2048.pem",
+                 vbmeta_path.value().c_str());
+
+  ASSERT_EQ(
+      "VBMeta image version:     1.0\n"
+      "Header Block:             256 bytes\n"
+      "Authentication Block:     320 bytes\n"
+      "Auxiliary Block:          640 bytes\n"
+      "Algorithm:                SHA256_RSA2048\n"
+      "Rollback Index:           0\n"
+      "Flags:                    0\n"
+      "Descriptors:\n"
+      "    Kernel Cmdline descriptor:\n"
+      "      Flags:                 0\n"
+      "      Kernel Cmdline:        'foo bar baz'\n"
+      "    Kernel Cmdline descriptor:\n"
+      "      Flags:                 0\n"
+      "      Kernel Cmdline:        'second cmdline'\n",
+      InfoImage(vbmeta_path));
+
+  // Now check the VBMeta image.
+  std::string image_data;
+  ASSERT_TRUE(base::ReadFileToString(vbmeta_path, &image_data));
+
+  const uint8_t* vbmeta_data =
+      reinterpret_cast<const uint8_t*>(image_data.data());
+  const size_t vbmeta_size = image_data.length();
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_OK,
+            avb_vbmeta_image_verify(vbmeta_data, vbmeta_size, NULL, NULL));
+
+  // Collect all descriptors.
+  std::vector<const AvbDescriptor*> descriptors;
+  avb_descriptor_foreach(
+      vbmeta_data, vbmeta_size, collect_descriptors, &descriptors);
+
+  // We should have two descriptors - check them.
+  EXPECT_EQ(2UL, descriptors.size());
+  AvbKernelCmdlineDescriptor d;
+  EXPECT_EQ(AVB_DESCRIPTOR_TAG_KERNEL_CMDLINE,
+            avb_be64toh(descriptors[0]->tag));
+  EXPECT_NE(
+      0,
+      avb_kernel_cmdline_descriptor_validate_and_byteswap(
+          reinterpret_cast<const AvbKernelCmdlineDescriptor*>(descriptors[0]),
+          &d));
+  EXPECT_EQ("foo bar baz",
+            std::string(reinterpret_cast<const char*>(descriptors[0]) +
+                            sizeof(AvbKernelCmdlineDescriptor),
+                        d.kernel_cmdline_length));
+  EXPECT_EQ(AVB_DESCRIPTOR_TAG_KERNEL_CMDLINE,
+            avb_be64toh(descriptors[1]->tag));
+  EXPECT_NE(
+      0,
+      avb_kernel_cmdline_descriptor_validate_and_byteswap(
+          reinterpret_cast<const AvbKernelCmdlineDescriptor*>(descriptors[1]),
+          &d));
+  EXPECT_EQ("second cmdline",
+            std::string(reinterpret_cast<const char*>(descriptors[1]) +
+                            sizeof(AvbKernelCmdlineDescriptor),
+                        d.kernel_cmdline_length));
+}
+
+TEST_F(AvbToolTest, IncludeDescriptor) {
+  base::FilePath vbmeta1_path = testdir_.Append("vbmeta_id1.bin");
+  base::FilePath vbmeta2_path = testdir_.Append("vbmeta_id2.bin");
+  base::FilePath vbmeta3_path = testdir_.Append("vbmeta_id3.bin");
+
+  EXPECT_COMMAND(0,
+                 "./avbtool make_vbmeta_image "
+                 "--output %s "
+                 "--kernel_cmdline 'something' "
+                 "--prop name:value ",
+                 vbmeta1_path.value().c_str());
+
+  EXPECT_COMMAND(0,
+                 "./avbtool make_vbmeta_image "
+                 "--output %s "
+                 "--prop name2:value2 "
+                 "--prop name3:value3 ",
+                 vbmeta2_path.value().c_str());
+
+  EXPECT_COMMAND(0,
+                 "./avbtool make_vbmeta_image "
+                 "--output %s "
+                 "--prop name4:value4 "
+                 "--include_descriptors_from_image %s "
+                 "--include_descriptors_from_image %s ",
+                 vbmeta3_path.value().c_str(),
+                 vbmeta1_path.value().c_str(),
+                 vbmeta2_path.value().c_str());
+
+  ASSERT_EQ(
+      "VBMeta image version:     1.0\n"
+      "Header Block:             256 bytes\n"
+      "Authentication Block:     0 bytes\n"
+      "Auxiliary Block:          256 bytes\n"
+      "Algorithm:                NONE\n"
+      "Rollback Index:           0\n"
+      "Flags:                    0\n"
+      "Descriptors:\n"
+      "    Prop: name4 -> 'value4'\n"
+      "    Prop: name -> 'value'\n"
+      "    Kernel Cmdline descriptor:\n"
+      "      Flags:                 0\n"
+      "      Kernel Cmdline:        'something'\n"
+      "    Prop: name2 -> 'value2'\n"
+      "    Prop: name3 -> 'value3'\n",
+      InfoImage(vbmeta3_path));
+}
+
+TEST_F(AvbToolTest, ChainedPartition) {
+  base::FilePath vbmeta_path = testdir_.Append("vbmeta_cp.bin");
+
+  base::FilePath pk_path = testdir_.Append("testkey_rsa2048.avbpubkey");
+
+  EXPECT_COMMAND(
+      0,
+      "./avbtool extract_public_key --key test/data/testkey_rsa2048.pem"
+      " --output %s",
+      pk_path.value().c_str());
+
+  EXPECT_COMMAND(
+      0,
+      "./avbtool make_vbmeta_image "
+      "--output %s "
+      "--chain_partition system:1:%s "
+      "--algorithm SHA256_RSA2048 --key test/data/testkey_rsa2048.pem",
+      vbmeta_path.value().c_str(),
+      pk_path.value().c_str());
+
+  ASSERT_EQ(
+      "VBMeta image version:     1.0\n"
+      "Header Block:             256 bytes\n"
+      "Authentication Block:     320 bytes\n"
+      "Auxiliary Block:          1152 bytes\n"
+      "Algorithm:                SHA256_RSA2048\n"
+      "Rollback Index:           0\n"
+      "Flags:                    0\n"
+      "Descriptors:\n"
+      "    Chain Partition descriptor:\n"
+      "      Partition Name:          system\n"
+      "      Rollback Index Location: 1\n"
+      "      Public key (sha1):       "
+      "cdbb77177f731920bbe0a0f94f84d9038ae0617d\n",
+      InfoImage(vbmeta_path));
+
+  // Now check the VBMeta image.
+  std::string image_data;
+  ASSERT_TRUE(base::ReadFileToString(vbmeta_path, &image_data));
+
+  const uint8_t* vbmeta_data =
+      reinterpret_cast<const uint8_t*>(image_data.data());
+  const size_t vbmeta_size = image_data.length();
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_OK,
+            avb_vbmeta_image_verify(vbmeta_data, vbmeta_size, NULL, NULL));
+
+  // Collect all descriptors.
+  std::vector<const AvbDescriptor*> descriptors;
+  avb_descriptor_foreach(
+      vbmeta_data, vbmeta_size, collect_descriptors, &descriptors);
+
+  // We should have one descriptor - check it.
+  EXPECT_EQ(1UL, descriptors.size());
+
+  std::string pk_data;
+  ASSERT_TRUE(base::ReadFileToString(pk_path, &pk_data));
+
+  AvbChainPartitionDescriptor d;
+  EXPECT_EQ(AVB_DESCRIPTOR_TAG_CHAIN_PARTITION,
+            avb_be64toh(descriptors[0]->tag));
+  EXPECT_NE(
+      0,
+      avb_chain_partition_descriptor_validate_and_byteswap(
+          reinterpret_cast<const AvbChainPartitionDescriptor*>(descriptors[0]),
+          &d));
+  const uint8_t* desc_end = reinterpret_cast<const uint8_t*>(descriptors[0]) +
+                            sizeof(AvbChainPartitionDescriptor);
+  uint64_t o = 0;
+  EXPECT_EQ("system",
+            std::string(reinterpret_cast<const char*>(desc_end + o),
+                        d.partition_name_len));
+  o += d.partition_name_len;
+  EXPECT_EQ(pk_data,
+            std::string(reinterpret_cast<const char*>(descriptors[0]) +
+                            sizeof(AvbChainPartitionDescriptor) + o,
+                        d.public_key_len));
+}
+
+TEST_F(AvbToolTest, SigningHelperBasic) {
+  base::FilePath vbmeta_path = testdir_.Append("vbmeta.bin");
+  base::FilePath signing_helper_test_path =
+      testdir_.Append("signing_helper_test");
+  EXPECT_COMMAND(
+      0,
+      "SIGNING_HELPER_TEST=\"%s\" ./avbtool make_vbmeta_image "
+      "--output %s "
+      "--algorithm SHA256_RSA2048 --key test/data/testkey_rsa2048.pem "
+      "--signing_helper test/avbtool_signing_helper_test.py",
+      signing_helper_test_path.value().c_str(),
+      vbmeta_path.value().c_str());
+
+  // Now check the value in test file.
+  std::string value;
+  ASSERT_TRUE(base::ReadFileToString(signing_helper_test_path, &value));
+  EXPECT_EQ("DONE", value);
+}
+
+TEST_F(AvbToolTest, SigningHelperReturnError) {
+  base::FilePath vbmeta_path = testdir_.Append("vbmeta.bin");
+  EXPECT_COMMAND(
+      1,
+      "./avbtool make_vbmeta_image "
+      "--output %s "
+      "--algorithm SHA256_RSA2048 --key test/data/testkey_rsa2048.pem "
+      "--signing_helper test/avbtool_signing_helper_test.py",
+      vbmeta_path.value().c_str());
+}
+
+TEST_F(AvbToolTest, MakeAtxPikCertificate) {
+  base::FilePath pubkey_path = testdir_.Append("tmp_pubkey.pem");
+  EXPECT_COMMAND(
+      0,
+      "openssl pkey -pubout -in test/data/testkey_rsa2048.pem -out %s",
+      pubkey_path.value().c_str());
+
+  base::FilePath output_path = testdir_.Append("tmp_certificate.bin");
+  EXPECT_COMMAND(0,
+                 "./avbtool make_atx_certificate"
+                 " --subject test/data/small_blob.bin"
+                 " --subject_key %s"
+                 " --subject_key_version 42"
+                 " --subject_is_intermediate_authority"
+                 " --authority_key test/data/testkey_rsa4096.pem"
+                 " --output %s",
+                 pubkey_path.value().c_str(),
+                 output_path.value().c_str());
+
+  EXPECT_COMMAND(0,
+                 "diff test/data/atx_pik_certificate.bin %s",
+                 output_path.value().c_str());
+}
+
+TEST_F(AvbToolTest, MakeAtxPskCertificate) {
+  base::FilePath pubkey_path = testdir_.Append("tmp_pubkey.pem");
+  EXPECT_COMMAND(
+      0,
+      "openssl pkey -pubout -in test/data/testkey_rsa2048.pem -out %s",
+      pubkey_path.value().c_str());
+
+  base::FilePath output_path = testdir_.Append("tmp_certificate.bin");
+  EXPECT_COMMAND(0,
+                 "./avbtool make_atx_certificate"
+                 " --subject test/data/atx_product_id.bin"
+                 " --subject_key %s"
+                 " --subject_key_version 42"
+                 " --authority_key test/data/testkey_rsa2048.pem"
+                 " --output %s",
+                 pubkey_path.value().c_str(),
+                 output_path.value().c_str());
+
+  EXPECT_COMMAND(0,
+                 "diff test/data/atx_psk_certificate.bin %s",
+                 output_path.value().c_str());
+}
+
+TEST_F(AvbToolTest, MakeAtxPermanentAttributes) {
+  base::FilePath pubkey_path = testdir_.Append("tmp_pubkey.pem");
+  EXPECT_COMMAND(
+      0,
+      "openssl pkey -pubout -in test/data/testkey_rsa4096.pem -out %s",
+      pubkey_path.value().c_str());
+
+  base::FilePath output_path = testdir_.Append("tmp_attributes.bin");
+  EXPECT_COMMAND(0,
+                 "./avbtool make_atx_permanent_attributes"
+                 " --root_authority_key %s"
+                 " --product_id test/data/atx_product_id.bin"
+                 " --output %s",
+                 pubkey_path.value().c_str(),
+                 output_path.value().c_str());
+
+  EXPECT_COMMAND(0,
+                 "diff test/data/atx_permanent_attributes.bin %s",
+                 output_path.value().c_str());
+}
+
+TEST_F(AvbToolTest, MakeAtxMetadata) {
+  base::FilePath output_path = testdir_.Append("tmp_metadata.bin");
+
+  EXPECT_COMMAND(
+      0,
+      "./avbtool make_atx_metadata"
+      " --intermediate_key_certificate test/data/atx_pik_certificate.bin"
+      " --product_key_certificate test/data/atx_psk_certificate.bin"
+      " --google_key_version 42"
+      " --output %s",
+      output_path.value().c_str());
+
+  EXPECT_COMMAND(
+      0, "diff test/data/atx_metadata.bin %s", output_path.value().c_str());
+}
+
+}  // namespace avb
diff --git a/avb/test/data/atx_metadata.bin b/avb/test/data/atx_metadata.bin
new file mode 100644
index 0000000..3b53bbc
--- /dev/null
+++ b/avb/test/data/atx_metadata.bin
Binary files differ
diff --git a/avb/test/data/atx_permanent_attributes.bin b/avb/test/data/atx_permanent_attributes.bin
new file mode 100644
index 0000000..be63c86
--- /dev/null
+++ b/avb/test/data/atx_permanent_attributes.bin
Binary files differ
diff --git a/avb/test/data/atx_pik_certificate.bin b/avb/test/data/atx_pik_certificate.bin
new file mode 100644
index 0000000..d0b4a7b
--- /dev/null
+++ b/avb/test/data/atx_pik_certificate.bin
Binary files differ
diff --git a/avb/test/data/atx_product_id.bin b/avb/test/data/atx_product_id.bin
new file mode 100644
index 0000000..d5648fa
--- /dev/null
+++ b/avb/test/data/atx_product_id.bin
@@ -0,0 +1 @@
+t‹ùAö®÷ ìí=±]
\ No newline at end of file
diff --git a/avb/test/data/atx_psk_certificate.bin b/avb/test/data/atx_psk_certificate.bin
new file mode 100644
index 0000000..abec3ce
--- /dev/null
+++ b/avb/test/data/atx_psk_certificate.bin
Binary files differ
diff --git a/avb/test/data/large_blob.bin b/avb/test/data/large_blob.bin
new file mode 100644
index 0000000..56feeb9
--- /dev/null
+++ b/avb/test/data/large_blob.bin
Binary files differ
diff --git a/avb/test/data/small_blob.bin b/avb/test/data/small_blob.bin
new file mode 100644
index 0000000..d712de6
--- /dev/null
+++ b/avb/test/data/small_blob.bin
Binary files differ
diff --git a/avb/test/data/test_file.bin b/avb/test/data/test_file.bin
new file mode 100644
index 0000000..a3fc7fb
--- /dev/null
+++ b/avb/test/data/test_file.bin
Binary files differ
diff --git a/avb/test/data/test_file.bin.sparse b/avb/test/data/test_file.bin.sparse
new file mode 100644
index 0000000..c4962b2
--- /dev/null
+++ b/avb/test/data/test_file.bin.sparse
Binary files differ
diff --git a/avb/test/data/testkey_rsa2048.pem b/avb/test/data/testkey_rsa2048.pem
new file mode 100644
index 0000000..867dcff
--- /dev/null
+++ b/avb/test/data/testkey_rsa2048.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAxlVR3TIkouAOvH79vaJTgFhpfvVKQIeVkFRZPVXK/zY0Gvrh
+4JAqGjJoW/PfrQv5sdD36qtHH3a+G5hLZ6Ni+t/mtfjucxZfuLGC3kmJ1T3XqEKZ
+gXXI2IR7vVSoImREvDQGEDyJwtHzLANlkbGg0cghVhWZSCAndO8BenalC2v94/rt
+DfkPekH6dgU3Sf40T0sBSeSY94mOzTaqOR2pfV1rWlLRdWmo33zeHBv52Rlbt0dM
+uXAureXWiHztkm5GCBC1dgM+CaxNtizNEgC91KcD0xuRCCM2WxH+r1lpszyIJDct
+YbrFmVEYl/kjQpafhy7Nsk1fqSTyRdriZSYmTQIDAQABAoIBAQC+kJgaCuX8wYAn
+SXWQ0fmdZlXnMNRpcF0a0pD0SAzGb1RdYBXMaXiqtyhiwc53PPxsCDdNecjayIMd
+jJVXPTwLhTruOgMS/bp3gcgWwV34UHV4LJXGOGAE+jbS0hbDBMiudOYmj6RmVshp
+z9G1zZCSQNMXHaWsEYkX59XpzzoB384nRul2QgEtwzUNR9XlpzgtJBLk3SACkvsN
+mQ/DW8IWHXLg8vLn1LzVJ2e3B16H4MoE2TCHxqfMgr03IDRRJogkenQuQsFhevYT
+o/mJyHSWavVgzMHG9I5m+eepF4Wyhj1Y4WyKAuMI+9dHAX/h7Lt8XFCQCh5DbkVG
+zGr34sWBAoGBAOs7n7YZqNaaguovfIdRRsxxZr1yJAyDsr6w3yGImDZYju4c4WY9
+5esO2kP3FA4p0c7FhQF5oOb1rBuHEPp36cpL4aGeK87caqTfq63WZAujoTZpr9Lp
+BRbkL7w/xG7jpQ/clpA8sHzHGQs/nelxoOtC7E118FiRgvD/jdhlMyL9AoGBANfX
+vyoN1pplfT2xR8QOjSZ+Q35S/+SAtMuBnHx3l0qH2bbBjcvM1MNDWjnRDyaYhiRu
+i+KA7tqfib09+XpB3g5D6Ov7ls/Ldx0S/VcmVWtia2HK8y8iLGtokoBZKQ5AaFX2
+iQU8+tC4h69GnJYQKqNwgCUzh8+gHX5Y46oDiTmRAoGAYpOx8lX+czB8/Da6MNrW
+mIZNT8atZLEsDs2ANEVRxDSIcTCZJId7+m1W+nRoaycLTWNowZ1+2ErLvR10+AGY
+b7Ys79Wg9idYaY9yGn9lnZsMzAiuLeyIvXcSqgjvAKlVWrhOQFOughvNWvFl85Yy
+oWSCMlPiTLtt7CCsCKsgKuECgYBgdIp6GZsIfkgclKe0hqgvRoeU4TR3gcjJlM9A
+lBTo+pKhaBectplx9RxR8AnsPobbqwcaHnIfAuKDzjk5mEvKZjClnFXF4HAHbyAF
+nRzZEy9XkWFhc80T5rRpZO7C7qdxmu2aiKixM3V3L3/0U58qULEDbubHMw9bEhAT
+PudI8QKBgHEEiMm/hr9T41hbQi/LYanWnlFw1ue+osKuF8bXQuxnnHNuFT/c+9/A
+vWhgqG6bOEHu+p/IPrYm4tBMYlwsyh4nXCyGgDJLbLIfzKwKAWCtH9LwnyDVhOow
+GH9shdR+sW3Ew97xef02KAH4VlNANEmBV4sQNqWWvsYrcFm2rOdL
+-----END RSA PRIVATE KEY-----
diff --git a/avb/test/data/testkey_rsa4096.pem b/avb/test/data/testkey_rsa4096.pem
new file mode 100644
index 0000000..26db5c3
--- /dev/null
+++ b/avb/test/data/testkey_rsa4096.pem
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIJKQIBAAKCAgEA2ASv49OEbH4NiT3CjNMSVeliyfEPXswWcqtEfCxlSpS1FisA
+uwbvEwdTTPlkuSh6G4SYiNhnpCP5p0vcSg/3OhiuVKgV/rCtrDXaO60nvK/o0y83
+NNZRK2xaJ9eWBq9ruIDK+jC0sYWzTaqqwxY0Grjnx/r5CXerl5PrRK7PILzwgBHb
+IwxHcblt1ntgR4cWVpO3wiqasEwBDDDYk4fw7W6LvjBb9qav3YB8RV6PkZNeRP64
+ggfuecq/MXNiWOPNxLzCER2hSr/+J32h9jWjXsrcVy8+8Mldhmr4r2an7c247aFf
+upuFGtUJrpROO8/LXMl5gPfMpkqoatjTMRH59gJjKhot0RpmGxZBvb33TcBK5SdJ
+X39Y4yct5clmDlI4Fjj7FutTP+b96aJeJVnYeUX/A0wmogBajsJRoRX5e/RcgZsY
+RzXYLQXprQ81dBWjjovMJ9p8XeT6BNMFC7o6sklFL0fHDUE/l4BNP8G1u3Bfpzev
+SCISRS71D4eS4oQB+RIPFBUkzomZ7rnEF3BwFeq+xmwfYrP0LRaH+1YeRauuMuRe
+ke1TZl697a3mEjkNg8noa2wtpe7EWmaujJfXDWxJx/XEkjGLCe4z2qk3tkkY+A5g
+Rcgzke8gVxC+eC2DJtbKYfkv4L8FMFJaEhwAp13MfC7FlYujO/BDLl7dANsCAwEA
+AQKCAgAWoL8P/WsktjuSwb5sY/vKtgzcHH1Ar942GsysuTXPDy686LpF3R8T/jNy
+n7k2UBAia8xSoWCR6BbRuHeV5oA+PLGeOpE7QaSfonB+yc+cy0x3Or3ssfqEsu/q
+toGHp75/8DXS6WE0K04x94u1rdC9b9sPrrGBlWCLGzqM0kbuJfyHXdd3n2SofAUO
+b5QRSgxD+2tHUpEroHqHnWJCaf4J0QegX45yktlfOYNK/PHLDQXV8ly/ejc32M4Y
+Tv7hUtOOJTuq8VCg9OWZm2Zo1QuM9XEJTPCp5l3+o5vzO6yhk2gotDvD32CdA+3k
+tLJRP54M1Sn+IXb1gGKN9rKAtGJbenWIPlNObhQgkbwG89Qd+5rfMXsiPv1Hl1tK
++tqwjD82/H3/ElaaMnwHCpeoGSp95OblAoBjzjMP2KsbvKSdL8O/rf1c3uOw9+DF
+cth0SA8y3ZzI11gJtb2QMGUrCny5n4sPGGbc3x38NdLhwbkPKZy60OiT4g2kNpdY
+dIitmAML2otttiF4AJM6AraPk8YVzkPLTksoL3azPBya5lIoDI2H3QvTtSvpXkXP
+yKchsDSWYbdqfplqC/X0Djp2/Zd8jpN5I6+1aSmpTmbwx/JTllY1N89FRZLIdxoh
+2k81LPiXhE6uRbjioJUlbnEWIpY2y2N2Clmxpjh0/IcXd1XImQKCAQEA7Zai+yjj
+8xit24aO9Tf3mZBXBjSaDodjC2KS1yCcAIXp6S7aH0wZipyZpQjys3zaBQyMRYFG
+bQqIfVAa6inWyDoofbAJHMu5BVcHFBPZvSS5YhDjc8XZ5dqSCxzIz9opIqAbm+b4
+aEV/3A3Jki5Dy8y/5j21GAK4Y4mqQOYzne7bDGi3Hyu041MGM4qfIcIkS5N1eHW4
+sDZJh6+K5tuxN5TX3nDZSpm9luNH8mLGgKAZ15b1LqXAtM5ycoBY9Hv082suPPom
+O+r0ybdRX6nDSH8+11y2KiP2kdVIUHCGkwlqgrux5YZyjCZPwOvEPhzSoOS+vBiF
+UVXA8idnxNLk1QKCAQEA6MIihDSXx+350fWqhQ/3Qc6gA/t2C15JwJ9+uFWA+gjd
+c/hn5HcmnmBJN4R04nLG/aU9SQur87a4mnC/Mp9JIARjHlZ/WNT4U0sJyPEVRg5U
+Z9VajAucWwi0JyJYCO1EMMy68Jp8qlTriK/L7nbD86JJ5ASxjojiN/0psK/Pk60F
+Rr+shKPi3jRQ1BDjDtAxOfo4ctf/nFbUM4bY0FNPQMP7WesoSKU0NBCRR6d0d2tq
+YflMjIQHx+N74P5jEdSCHTVGQm+dj47pUt3lLPLWc0bX1G/GekwXP4NUsR/70Hsi
+bwxkNnK2TSGzkt2rcOnutP125rJu6WpV7SNrq9rm7wKCAQAfMROcnbWviKHqnDPQ
+hdR/2K9UJTvEhInASOS2UZWpi+s1rez9BuSjigOx4wbaAZ4t44PW7C3uyt84dHfU
+HkIQb3I5bg8ENMrJpK9NN33ykwuzkDwMSwFcZ+Gci97hSubzoMl/IkeiiN1MapL4
+GhLUgsD+3UMVL+Y9SymK8637IgyoCGdiND6/SXsa8SwLJo3VTjqx4eKpX7cvlSBL
+RrRxc50TmwUsAhsd4CDl9YnSATLjVvJBeYlfM2tbFPaYwl1aR8v+PWkfnK0efm60
+fHki33HEnGteBPKuGq4vwVYpn6bYGwQz+f6335/A2DMfZHFSpjVURHPcRcHbCMla
+0cUxAoIBAQC25eYNkO478mo+bBbEXJlkoqLmvjAyGrNFo48F9lpVH6Y0vNuWkXJN
+PUgLUhAu6RYotjGENqG17rz8zt/PPY9Ok2P3sOx8t00y1mIn/hlDZXs55FM0fOMu
+PZaiscAPs7HDzvyOmDah+fzi+ZD8H2M3DS2W+YE0iaeJa2vZJS2t02W0BGXiDI33
+IZDqMyLYvwwPjOnShJydEzXID4xLl0tNjzLxo3GSNA7jYqlmbtV8CXIc7rMSL6WV
+ktIDKKJcnmpn3TcKeX6MEjaSIT82pNOS3fY3PmXuL+CMzfw8+u77Eecq78fHaTiL
+P5JGM93F6mzi19EY0tmInUBMCWtQLcENAoIBAQCg0KaOkb8T36qzPrtgbfou0E2D
+ufdpL1ugmD4edOFKQB5fDFQhLnSEVSJq3KUg4kWsXapQdsBd6kLdxS+K6MQrLBzr
+4tf0c7UCF1AzWk6wXMExZ8mRb2RkGZYQB2DdyhFB3TPmnq9CW8JCq+6kxg/wkU4s
+vM4JXzgcqVoSf42QJl+B9waeWhg0BTWx01lal4ds88HvEKmE0ik5GwiDbr7EvDDw
+E6UbZtQcIoSTIIZDgYqVFfR2DAho3wXJRsOXh433lEJ8X7cCDzrngFbQnlKrpwML
+Xgm0SIUc+Nf5poMM3rfLFK77t/ob4w+5PwRKcoSniyAxrHd6bwykYA8Vuydv
+-----END RSA PRIVATE KEY-----
diff --git a/avb/test/data/testkey_rsa8192.pem b/avb/test/data/testkey_rsa8192.pem
new file mode 100644
index 0000000..a383428
--- /dev/null
+++ b/avb/test/data/testkey_rsa8192.pem
@@ -0,0 +1,99 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIISKgIBAAKCBAEA0D3T+dISsmCHm797wsX0vVfqUWDJ/3mvDYozlCabDhnGLlSE
+pAQbf1Z8Ts+OM4pVRHOJUJL0WebNdmPPGjsyWQz6zZE96lQZL3avCEXqYVQR66V5
+3wdK/ohaMSRnGyEMBrqkVVbF3gCr+/irxD3YK+VowO2WKs/6GrMdqTA8Y5CTF/Je
+ptwsSg5MMjr6UaK4qDcrej3hkgBVGvRV3cj1snK6Br8HuYdFnpGGTS0d7UJlHFgl
+trGHU/CBO923hkHgJaWEjC0giSGjhKKtLzrVcpDV2y/lWQP9T/T4djEAIaHqQ++P
+SdOSR6psIGR6hVgSigt7HCnE7nW711/rfV5Ur9EiVpB040mDImKZcy8//TMnXydN
+1KYTVd/34fdpzMpSw5iblErbwOLXVTUmOztYnpl41feHSv/jPesHstPlfklIF2vo
+GZEohf9scQvcuM7wEBfC/aTA9K39zMmkBbcvSZjLyhmcSZWMPPOZyIcl3zY53QhW
+QC/abmIcBfI1S4+r7mC4i2Jn++oEvuGNVGr2SY2Z0ZZxXGL1HI/08D/3+Tcumrcn
+4YjPK/DMFi0F+e+1x41lipuf+cx/2qRNQX/m02STrLYdM6e0g33KvlnFdi2b752y
+/OIaMwxDaJvunMh6EMDWKM1AHbY/ioAoK7eS26HeJLEDllqO4+SWP37c8lMvSEWy
+1GiErR0HcsOj/QwWGPFseoVroMiA2sUQ0Ic/tgVjCTlXg+12XpUnouIweCi8KcL/
+ad2zJkju9hBhJLBQ/2GnivJi3lFgF4Gd//TSJ6rgWuXFfMKt/9z2Sz35ohEX4yA0
+flqlCeLInFEoevbz+XT9aRfDe65MZ79yw3TfP9CrV74hf1RRzveD4zpi3F+hcY2i
+JWsH7gROZeCm6fAX5Trecd3hOxJOfA4N4rvSSCq6BwCvebT8FY25Z/VF7cQrHYDS
+ij5w6lqhMzXHeUEY90Ga9AK4XzaWwGgezq+R7Zs00YSKqFv9qYNKdR7tz3cjijWf
+9q/3R1uh6EQKTMZKo4SEClJiGyjOBvmPK09jMFZTJv00hDxagDPZBl7XpLDJ5/Ln
+1uppvLCNWWY1zeJfaElMyq3/PqKZLidF9rVoA1SIwk2lpdUvPote2oFiwCZoXlwZ
+J2ncjmXgQNs76/8unDJA0rj4JPqccw4M5GxQ7okbgm3F4rmzriCuv8BeMSCkr2ry
+0mY3UhpohX4wCMq0G4x5sEUAz9FVVPZKjxnYBmLDzrJAR+4+G7gZsct01XDJYgDd
+JVYInFP22/cIre8VrFWYtHbgOFdNqUiVq58de6PdZG/E+uaWmEThSlRrgEjTxupi
+OXfgdKW/20j1qAtjOlqFwsY094Q5rqULQ6wPxQIDAQABAoIEAQChmkmlhrRBv42d
+fYUiyxK52b8ath0saJdDz6tlXmxYDgJxM9/XlORt9oTzeDknoEO5olu+rrx4BBgQ
+tzYiaiwRVXRREVTWQ7tjzRvaNL/GFkLt93XTccpuKwyrNE/bitLVagRbwcI+HZFa
+MknCOihHMHoRto8h3FKAY94xzSAgODMek1WG8jhgpCXXmVNnBPt+d4oDDIDAGAfz
+qgf03J5nhIb+80KgZOzPOKnbvJaL6EmlLHbgB3c42dzAw7hHtVmofYGWcvLb2MIY
+DVKO435/sQx1U/8NDH6JjVdACZjLgObXH9K3/Tt46DWPEcrPLmD8xhoc6gFM+Qr0
+AhkzKoBYDNk0CljbhdIBXjktXU6wRQFZ45uP2e4JZ4zrzGBLr/t4lTavZ0SQtLld
+A6kOsGh+dCWFDtnshxYnl/xad/yR+3a5zmDJbo/fJTBXrlf1B4rfQkFtK20etOPQ
+B++FC/rjh3Mm/Kb/p9Gz/2upZdArH97ZvD2LBFfj77lFmAhqAi3wCRlN+ekuYxaZ
+t1pBV9yXig8Dyldg1d7X8pOn2kyrF3rQUDDf4pa7x9vpnbkUlEUifoV9gnYsmdni
+qDzYBtTv2g6MKqwQySXaIUW0YOBPbOellWEwxJqGYQ7y4IfVHfM0iyHnehk2tZcr
++XazLnwGe+Bz4vcguFhJXLyIu//lAOhZtbk6r1QJEUuxaOOQX3wzyceE6nkDsgmr
+P5dj3Zpd7fS2VV2vyGHIFnBJ88LRxreVvgr6Q28UT27SB82zMb7mRZTVE2zeuubT
+5D2D1XbZ0wBo6WiK6eRRrDQ2Haeetkj/uoRy6PWXwnAaTmmIrrXwLqaoJh/U1e+D
+tfsDLWd6IxLjfXvGglrHsrtAz0oprpixUTeVhgTrGk9IQRd5rvxuGUYhFujVaYI6
++QUf+33AFdtncb8y9C9jZmgx8AKbJk+e73SLhB5JVos+WteU7b8d/Mim5mALjnO6
+Z1n/uimsT79sSDqy3XSymtKWXo/22UlrvGCpoEuELPMb6dSFWR7vwrsvhFngY4/K
+UnitnvxboEflQnaIQ4IfRLRzZsX+sC5Esqw9U5tHt4oI+91Dv3KbdbcERgV73K6B
+ZQgC4lkAQquFXiZ5AICkxjiMyZwTtU9KJ7xv17Xu6oywF/3AtbVGETW1D+3maHsD
+y3DASWojyqZdLj+WGzKQRa+swgCDAYKeek2fIAXFSdF63zxJ2RxOJ4GijSaoh+mr
+4HVvcpDaTj+A8T1+QdByM4s98gu4GD7kVtVQGBZdWjutyHvh0hWv1gtVmbhQ/413
+gDMFFDzHIjLTYGYes4hHL22169jVR9sZ1eQxwvTIg3N4pD5cFm0rRuZZTS+oJToF
+G27aBFihAoICAQDyVB62ZDnbxQthk+zITKIzRUrJbLoXrUcANcSHfaN7inF87Ova
+ze7ejT9DNSEhbtfZFJ1G6diOYoSw+2MzFXv0gEkLKY0dETydKgHEu6nVq5eivMgv
+D4hc9YkJMHDSlmv2FDkpL3AXCAmnW9rKp+ddttBZECnmlPEpHLoj6xgBw3pNa1Xs
+IcLVfdugH86Hexj6o0oKgYfcqrX8UUHtUI2/XQqgFrIj8ksjf1fFVWJRJFWmBXqp
+nMEsYarzATeM1kQ/kDeT1ZUpoGPQt02/XqXT4B5A3ATiEtpM2u+l48xtogWWg2Ry
+G9l938StAmhUiW1m7GnKE6EIFvQY85WvbzxOR0JYVUSr7MrasF6nnQlhYxFuIJoJ
+2h/KJQao5GCTvG4+GtbJJm4c2nyZgwyhizMsdgsdcls79aXiMkrZZkamLVUZWOtE
+3pA/oBuz2qnO9HwjbH1HGOccq0TXfmpFScEV3CQGYJdno6Fy7cbmupaL4U9agQ4e
+w+ygL18nq5HV++LStFnVrgs5YijjskfRdE9GUMVDh5pCsd9Y23Fymaad4O/2SRCC
+YkSsyH5OvyDOLpoyUJ6g6Q+45Hqm/3lG4YjNpzFUiMcnp7+3xU35qC0LK8xEfeei
+Ms1mTVEiHNIp6xH/TqRdX73WD7+YuKZSLIfRG7dgrirU6w+mhhvxD51uHQKCAgEA
+2/1mBCR5qm3/0Lt++RQbeyE3tiw40UeyQqucG/+VvY77sSLkI/Lx8iwRlywXcLBn
++A4TvgukmAdWzCs8ndgKNxPA+gfohvBsMOGN9KOB1Ug5vvg2J2kiI64vwYCwzhdZ
+NTUUmL+GMFHUqSsWYg6i7iBFcZmznr4W2T3bBxyTMZki7JStB86e35KXrzc2/W/b
++/p5U2HCSazDHI5mMyuClHc6GmUSVJ7f7LHjL94jviNqobp0Vj603tScHISmNrZw
+TBavkvZGYXsoWKvqavk7jBB9QzaBL+unaFRslg5jTaiKnISj44Us1fjFKu84xifL
+nJaEzjDPt7PBxko7LPgEY7wF39nM9VpoetI7bwR6NwDLSX8UU97MGd+HY+MO1Wi1
+pd2Lapwrx/EK7Oxz335VRK4Je0aZna4j2TyQdMJac9fsGPXv4ZsLfDLj/wD6l1j+
+lLLbBv3ImdSj32LBbhsgF4iCGeXO8HpPO+Q/h9XVsnY52Um2XdNMn03PCGm6ZvtM
+7DXiS+lPF90HjolJVHZTBNtdVRrLr53zLuWEfqT4FeKrDaxdtiXkxLjrB+5/VYu7
+ntyk01ZQ63VNfEwS1irmKl9+qZkTHk3HHV9jNV5RzWViwmJI7Wpr1YzBwmcKCB1O
+oGUADDs8QpnkCz0xkMVtYwHj9qKZlqfbHzrFDUUcF8kCggIAdYvUcgjf//ju8mA8
+5VQ3AcPE6TvycPW+kR2DvW12VcDsF/sc1UA7dHzziPhGn98SmNxlBjb8suSbFPZ8
+QhVT0WBBDkcTilwIGPx9ax7U3S6lGW2VdS6FqQH5fRmgQKZyrCVXLOEz8BgYBrSJ
+xu/3TQAWxH0QtibdbGHg8Pdi58gYlWFRhn9B8Slh1aRYHGPb1AhNLBd0/ddY+5G2
+9xSyDXdmZg1cUA+B3zAwNSqbzFxhp2zU+V1uXsbpk4KtnYV6CZM9QlrCRjTk9iNU
+dVXF/qaiRjfzrm4SsmEpCkEbsrp7F22Y1bkooORglMOsNAWNqfVXw4wN+syXj1ro
+6vZ8PERYrFyAOR1dsQMIhymnmTPjCpaJ4emKrhWTy20sY71thHakZWJc22YoNpbZ
+E6tgIVsJPTlxg/4+fyCCKj5wWr92nhsB1KBZPGO/zFhvMlJpvQ0tH8W2pbN2a0mI
+5x9FqALm/qjwCHfZItSwPM+ZozSht3cOkGHdcD5KXAXfcfsDJc4SHZKVIzq4NusN
+504R/jvD1GP8sglyG7omp75ckgzAmakLdxOP2HhQvIX9tcXpSirNJ6Sl2bwKuuMF
+wxo3r/o/9Y97e4LlfpEYp9eqMdcG+NpR993IwK0UhAWS9H5wdnWBSUHd5e4xtDUt
+iILNRuO46g7R/AIhz1cSSraWWQkCggIBAMhhPP5C9yt9PIm1b0eTwCBctnFSQIKo
+KsA9rll2ab+bMLk9jc8M6MLszy0CtWso09sHf4YY9tifvrkEHRethEh8zscwUuYu
+sm2n1fTixk0ul6LSVgl54uXbMJayENn4PIKRkew8cA8tSma43497w37hmD+MgCb1
+ALzqcco9hfmkgkI6fo1g8Ce3UEECKy2YKSmREdgYcK9JFQO61W6AkFWJcDxAmfzI
+JjFkKwsb7TSw79zWiEdSoM9jm7sCPKATd6Bm/ZAAkUUTuEFkfobn9Ax1rJN/Xxb2
+MKuAUtQv0NYY0gEVdG62jItuKLId6nncH8PG+rsRjPLIYpWqYdJpKx5pUnR+4AkQ
+S6CsRASwcF4PdBvDDBIFG6XpjFo4pPdQhDzL2sTF8b8SWSBLlJQbb7G6UNqgCSau
+SusCFpazvU5NfDmUMuctob2EYVaSXq9jGaj6bTUmDwXHwWilfIk9XfLxnYfXYrJ6
+xhdIpXGmHhuLQtAgK2O1JtLoPc9s9qP8/SkfP7xjjG6xHsP/WvL7QE1pPs9ZM/UI
+C01JNHFi9LKCn8o5mbZjN8jUowi7ffK+76wZUG1L7zM5ytWQOYwo0TQBfc8fpmFw
++RBRJX2kJyDO27ExczoGOKjwqEDaODIB9+9zcCK0BgSoRibSm4ZBvoxzWWD65Kls
+xdPhZUHcFGW5AoICAQC8iG27aD8aRUt94Oek66gFOJx84QVZehWPqtZjWyVenDuc
+T8dink8oejGjcK2UJuQDa83azv90ocVqE0n0ronYyszt9Ib1jlYC+CK1Ar9TYGFg
+WU5OWEDyCzCpqW/w/aG68U8qhKm0MvkLJR+G6evan9TwEhFEVAm3iWllNXs9x29s
+BucwyMMC23zsimxYlS7dA4DtyvVA+zL1omLpSWHbU/qtuI3HV1NeJzsy+gC4mwPh
+j52tdl669fyWLzHzBRLeq6dVOedjnCo+jlU3dL20DEk9SaW08D1CPuZekV1jVPMw
+JoaDcIRh4KLtQ0BYZ7UJeFUTsx1CS/+UqzqYSPOi57a5kvr0Y8YwRnSB8dHVFttX
+JTv83wTQXHPFSBgfnHNe7lsRTfIQfuIkr2bpiU7h85UQ7LsqcI6YHaC07URcsGFF
+FrLWGh91qzAd1diSHla2RnY3n8PPuMnCkguNhLUrYdmyMol7FfWFa9lwplsuTzBq
+B6yj8iaiE3LL+Q/eulJ7S6QPfAI2bU0UJO23Y4koeoIibEEDMSCQ6KYZ2NClRRRT
+ga5fS1YfkDFEcHUQ1/KIkdYHGBKBjoKGExzi8+CgiSySVSYDZl6wIOhLjH2OZ3ol
+ldPN7iNAHirrxg9v8QO6OQlpLUk5Lhp/1dSlZ6sy3UjFqvax3tw6ZjrL88YP5g==
+-----END RSA PRIVATE KEY-----
diff --git a/avb/test/fake_avb_ops.cc b/avb/test/fake_avb_ops.cc
new file mode 100644
index 0000000..cdd0850
--- /dev/null
+++ b/avb/test/fake_avb_ops.cc
@@ -0,0 +1,348 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <iostream>
+
+#include <endian.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <string.h>
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <base/files/file_util.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <openssl/sha.h>
+
+#include "fake_avb_ops.h"
+
+namespace avb {
+
+AvbIOResult FakeAvbOps::read_from_partition(const char* partition,
+                                            int64_t offset,
+                                            size_t num_bytes,
+                                            void* buffer,
+                                            size_t* out_num_read) {
+  base::FilePath path =
+      partition_dir_.Append(std::string(partition)).AddExtension("img");
+
+  if (offset < 0) {
+    int64_t file_size;
+    if (!base::GetFileSize(path, &file_size)) {
+      fprintf(
+          stderr, "Error getting size of file '%s'\n", path.value().c_str());
+      return AVB_IO_RESULT_ERROR_IO;
+    }
+    offset = file_size - (-offset);
+  }
+
+  int fd = open(path.value().c_str(), O_RDONLY);
+  if (fd < 0) {
+    fprintf(stderr,
+            "Error opening file '%s': %s\n",
+            path.value().c_str(),
+            strerror(errno));
+    return AVB_IO_RESULT_ERROR_IO;
+  }
+  if (lseek(fd, offset, SEEK_SET) != offset) {
+    fprintf(stderr,
+            "Error seeking to pos %zd in file %s: %s\n",
+            offset,
+            path.value().c_str(),
+            strerror(errno));
+    close(fd);
+    return AVB_IO_RESULT_ERROR_IO;
+  }
+  ssize_t num_read = read(fd, buffer, num_bytes);
+  if (num_read < 0) {
+    fprintf(stderr,
+            "Error reading %zd bytes from pos %" PRId64 " in file %s: %s\n",
+            num_bytes,
+            offset,
+            path.value().c_str(),
+            strerror(errno));
+    close(fd);
+    return AVB_IO_RESULT_ERROR_IO;
+  }
+  close(fd);
+
+  if (out_num_read != NULL) {
+    *out_num_read = num_read;
+  }
+
+  return AVB_IO_RESULT_OK;
+}
+
+AvbIOResult FakeAvbOps::write_to_partition(const char* partition,
+                                           int64_t offset,
+                                           size_t num_bytes,
+                                           const void* buffer) {
+  base::FilePath path =
+      partition_dir_.Append(std::string(partition)).AddExtension("img");
+
+  if (offset < 0) {
+    int64_t file_size;
+    if (!base::GetFileSize(path, &file_size)) {
+      fprintf(
+          stderr, "Error getting size of file '%s'\n", path.value().c_str());
+      return AVB_IO_RESULT_ERROR_IO;
+    }
+    offset = file_size - (-offset);
+  }
+
+  int fd = open(path.value().c_str(), O_WRONLY);
+  if (fd < 0) {
+    fprintf(stderr,
+            "Error opening file '%s': %s\n",
+            path.value().c_str(),
+            strerror(errno));
+    return AVB_IO_RESULT_ERROR_IO;
+  }
+  if (lseek(fd, offset, SEEK_SET) != offset) {
+    fprintf(stderr,
+            "Error seeking to pos %zd in file %s: %s\n",
+            offset,
+            path.value().c_str(),
+            strerror(errno));
+    close(fd);
+    return AVB_IO_RESULT_ERROR_IO;
+  }
+  ssize_t num_written = write(fd, buffer, num_bytes);
+  if (num_written < 0) {
+    fprintf(stderr,
+            "Error writing %zd bytes at pos %" PRId64 " in file %s: %s\n",
+            num_bytes,
+            offset,
+            path.value().c_str(),
+            strerror(errno));
+    close(fd);
+    return AVB_IO_RESULT_ERROR_IO;
+  }
+  close(fd);
+
+  return AVB_IO_RESULT_OK;
+}
+
+AvbIOResult FakeAvbOps::validate_vbmeta_public_key(
+    AvbOps* ops,
+    const uint8_t* public_key_data,
+    size_t public_key_length,
+    const uint8_t* public_key_metadata,
+    size_t public_key_metadata_length,
+    bool* out_key_is_trusted) {
+  if (out_key_is_trusted != NULL) {
+    bool pk_matches = (public_key_length == expected_public_key_.size() &&
+                       (memcmp(expected_public_key_.c_str(),
+                               public_key_data,
+                               public_key_length) == 0));
+    bool pkmd_matches =
+        (public_key_metadata_length == expected_public_key_metadata_.size() &&
+         (memcmp(expected_public_key_metadata_.c_str(),
+                 public_key_metadata,
+                 public_key_metadata_length) == 0));
+    *out_key_is_trusted = pk_matches && pkmd_matches;
+  }
+  return AVB_IO_RESULT_OK;
+}
+
+AvbIOResult FakeAvbOps::read_rollback_index(AvbOps* ops,
+                                            size_t rollback_index_location,
+                                            uint64_t* out_rollback_index) {
+  if (stored_rollback_indexes_.count(rollback_index_location) == 0) {
+    fprintf(stderr,
+            "No rollback index for location %zd (has %zd locations).\n",
+            rollback_index_location,
+            stored_rollback_indexes_.size());
+    return AVB_IO_RESULT_ERROR_IO;
+  }
+  *out_rollback_index = stored_rollback_indexes_[rollback_index_location];
+  return AVB_IO_RESULT_OK;
+}
+
+AvbIOResult FakeAvbOps::write_rollback_index(AvbOps* ops,
+                                             size_t rollback_index_location,
+                                             uint64_t rollback_index) {
+  if (stored_rollback_indexes_.count(rollback_index_location) == 0) {
+    fprintf(stderr,
+            "No rollback index for location %zd (has %zd locations).\n",
+            rollback_index_location,
+            stored_rollback_indexes_.size());
+    return AVB_IO_RESULT_ERROR_IO;
+  }
+  stored_rollback_indexes_[rollback_index_location] = rollback_index;
+  return AVB_IO_RESULT_OK;
+}
+
+AvbIOResult FakeAvbOps::read_is_device_unlocked(AvbOps* ops,
+                                                bool* out_is_device_unlocked) {
+  *out_is_device_unlocked = stored_is_device_unlocked_ ? 1 : 0;
+  return AVB_IO_RESULT_OK;
+}
+
+AvbIOResult FakeAvbOps::get_unique_guid_for_partition(AvbOps* ops,
+                                                      const char* partition,
+                                                      char* guid_buf,
+                                                      size_t guid_buf_size) {
+  // This is faking it a bit but makes testing easy. It works
+  // because avb_slot_verify.c doesn't check that the returned GUID
+  // is wellformed.
+  snprintf(guid_buf, guid_buf_size, "1234-fake-guid-for:%s", partition);
+  return AVB_IO_RESULT_OK;
+}
+
+AvbIOResult FakeAvbOps::read_permanent_attributes(
+    AvbAtxPermanentAttributes* attributes) {
+  *attributes = permanent_attributes_;
+  return AVB_IO_RESULT_OK;
+}
+
+AvbIOResult FakeAvbOps::read_permanent_attributes_hash(
+    uint8_t hash[AVB_SHA256_DIGEST_SIZE]) {
+  if (permanent_attributes_hash_.empty()) {
+    SHA256(reinterpret_cast<const unsigned char*>(&permanent_attributes_),
+           sizeof(AvbAtxPermanentAttributes),
+           hash);
+    return AVB_IO_RESULT_OK;
+  }
+  memset(hash, 0, AVB_SHA256_DIGEST_SIZE);
+  permanent_attributes_hash_.copy(reinterpret_cast<char*>(hash),
+                                  AVB_SHA256_DIGEST_SIZE);
+  return AVB_IO_RESULT_OK;
+}
+
+static AvbIOResult my_ops_read_from_partition(AvbOps* ops,
+                                              const char* partition,
+                                              int64_t offset,
+                                              size_t num_bytes,
+                                              void* buffer,
+                                              size_t* out_num_read) {
+  return FakeAvbOps::GetInstanceFromAvbOps(ops)
+      ->delegate()
+      ->read_from_partition(partition, offset, num_bytes, buffer, out_num_read);
+}
+
+static AvbIOResult my_ops_write_to_partition(AvbOps* ops,
+                                             const char* partition,
+                                             int64_t offset,
+                                             size_t num_bytes,
+                                             const void* buffer) {
+  return FakeAvbOps::GetInstanceFromAvbOps(ops)->delegate()->write_to_partition(
+      partition, offset, num_bytes, buffer);
+}
+
+static AvbIOResult my_ops_validate_vbmeta_public_key(
+    AvbOps* ops,
+    const uint8_t* public_key_data,
+    size_t public_key_length,
+    const uint8_t* public_key_metadata,
+    size_t public_key_metadata_length,
+    bool* out_key_is_trusted) {
+  return FakeAvbOps::GetInstanceFromAvbOps(ops)
+      ->delegate()
+      ->validate_vbmeta_public_key(ops,
+                                   public_key_data,
+                                   public_key_length,
+                                   public_key_metadata,
+                                   public_key_metadata_length,
+                                   out_key_is_trusted);
+}
+
+static AvbIOResult my_ops_read_rollback_index(AvbOps* ops,
+                                              size_t rollback_index_location,
+                                              uint64_t* out_rollback_index) {
+  return FakeAvbOps::GetInstanceFromAvbOps(ops)
+      ->delegate()
+      ->read_rollback_index(ops, rollback_index_location, out_rollback_index);
+}
+
+static AvbIOResult my_ops_write_rollback_index(AvbOps* ops,
+                                               size_t rollback_index_location,
+                                               uint64_t rollback_index) {
+  return FakeAvbOps::GetInstanceFromAvbOps(ops)
+      ->delegate()
+      ->write_rollback_index(ops, rollback_index_location, rollback_index);
+}
+
+static AvbIOResult my_ops_read_is_device_unlocked(
+    AvbOps* ops, bool* out_is_device_unlocked) {
+  return FakeAvbOps::GetInstanceFromAvbOps(ops)
+      ->delegate()
+      ->read_is_device_unlocked(ops, out_is_device_unlocked);
+}
+
+static AvbIOResult my_ops_get_unique_guid_for_partition(AvbOps* ops,
+                                                        const char* partition,
+                                                        char* guid_buf,
+                                                        size_t guid_buf_size) {
+  return FakeAvbOps::GetInstanceFromAvbOps(ops)
+      ->delegate()
+      ->get_unique_guid_for_partition(ops, partition, guid_buf, guid_buf_size);
+}
+
+static AvbIOResult my_ops_read_permanent_attributes(
+    AvbAtxOps* atx_ops, AvbAtxPermanentAttributes* attributes) {
+  return FakeAvbOps::GetInstanceFromAvbOps(atx_ops->ops)
+      ->delegate()
+      ->read_permanent_attributes(attributes);
+}
+
+static AvbIOResult my_ops_read_permanent_attributes_hash(
+    AvbAtxOps* atx_ops, uint8_t hash[AVB_SHA256_DIGEST_SIZE]) {
+  return FakeAvbOps::GetInstanceFromAvbOps(atx_ops->ops)
+      ->delegate()
+      ->read_permanent_attributes_hash(hash);
+}
+
+FakeAvbOps::FakeAvbOps() {
+  avb_ops_.ab_ops = &avb_ab_ops_;
+  avb_ops_.atx_ops = &avb_atx_ops_;
+  avb_ops_.user_data = this;
+  avb_ops_.read_from_partition = my_ops_read_from_partition;
+  avb_ops_.write_to_partition = my_ops_write_to_partition;
+  avb_ops_.validate_vbmeta_public_key = my_ops_validate_vbmeta_public_key;
+  avb_ops_.read_rollback_index = my_ops_read_rollback_index;
+  avb_ops_.write_rollback_index = my_ops_write_rollback_index;
+  avb_ops_.read_is_device_unlocked = my_ops_read_is_device_unlocked;
+  avb_ops_.get_unique_guid_for_partition = my_ops_get_unique_guid_for_partition;
+
+  // Just use the built-in A/B metadata read/write routines.
+  avb_ab_ops_.ops = &avb_ops_;
+  avb_ab_ops_.read_ab_metadata = avb_ab_data_read;
+  avb_ab_ops_.write_ab_metadata = avb_ab_data_write;
+
+  avb_atx_ops_.ops = &avb_ops_;
+  avb_atx_ops_.read_permanent_attributes = my_ops_read_permanent_attributes;
+  avb_atx_ops_.read_permanent_attributes_hash =
+      my_ops_read_permanent_attributes_hash;
+
+  delegate_ = this;
+}
+
+FakeAvbOps::~FakeAvbOps() {}
+
+}  // namespace avb
diff --git a/avb/test/fake_avb_ops.h b/avb/test/fake_avb_ops.h
new file mode 100644
index 0000000..0a7246d
--- /dev/null
+++ b/avb/test/fake_avb_ops.h
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef FAKE_AVB_OPS_H_
+#define FAKE_AVB_OPS_H_
+
+#include <base/files/file_util.h>
+#include <map>
+#include <string>
+
+#include <libavb_ab/libavb_ab.h>
+#include <libavb_atx/libavb_atx.h>
+
+namespace avb {
+
+// A delegate interface for ops callbacks. This allows tests to override default
+// fake implementations.
+class FakeAvbOpsDelegate {
+ public:
+  virtual ~FakeAvbOpsDelegate() {}
+  virtual AvbIOResult read_from_partition(const char* partition,
+                                          int64_t offset,
+                                          size_t num_bytes,
+                                          void* buffer,
+                                          size_t* out_num_read) = 0;
+
+  virtual AvbIOResult write_to_partition(const char* partition,
+                                         int64_t offset,
+                                         size_t num_bytes,
+                                         const void* buffer) = 0;
+
+  virtual AvbIOResult validate_vbmeta_public_key(
+      AvbOps* ops,
+      const uint8_t* public_key_data,
+      size_t public_key_length,
+      const uint8_t* public_key_metadata,
+      size_t public_key_metadata_length,
+      bool* out_key_is_trusted) = 0;
+
+  virtual AvbIOResult read_rollback_index(AvbOps* ops,
+                                          size_t rollback_index_slot,
+                                          uint64_t* out_rollback_index) = 0;
+
+  virtual AvbIOResult write_rollback_index(AvbOps* ops,
+                                           size_t rollback_index_slot,
+                                           uint64_t rollback_index) = 0;
+
+  virtual AvbIOResult read_is_device_unlocked(AvbOps* ops,
+                                              bool* out_is_device_unlocked) = 0;
+
+  virtual AvbIOResult get_unique_guid_for_partition(AvbOps* ops,
+                                                    const char* partition,
+                                                    char* guid_buf,
+                                                    size_t guid_buf_size) = 0;
+
+  virtual AvbIOResult read_permanent_attributes(
+      AvbAtxPermanentAttributes* attributes) = 0;
+
+  virtual AvbIOResult read_permanent_attributes_hash(
+      uint8_t hash[AVB_SHA256_DIGEST_SIZE]) = 0;
+};
+
+// Provides fake implementations of AVB ops. All instances of this class must be
+// created on the same thread.
+class FakeAvbOps : public FakeAvbOpsDelegate {
+ public:
+  FakeAvbOps();
+  virtual ~FakeAvbOps();
+
+  static FakeAvbOps* GetInstanceFromAvbOps(AvbOps* ops) {
+    return reinterpret_cast<FakeAvbOps*>(ops->user_data);
+  }
+  static FakeAvbOps* GetInstanceFromAvbABOps(AvbABOps* ab_ops) {
+    return reinterpret_cast<FakeAvbOps*>(ab_ops->ops->user_data);
+  }
+
+  AvbOps* avb_ops() {
+    return &avb_ops_;
+  }
+
+  AvbABOps* avb_ab_ops() {
+    return &avb_ab_ops_;
+  }
+
+  AvbAtxOps* avb_atx_ops() {
+    return &avb_atx_ops_;
+  }
+
+  FakeAvbOpsDelegate* delegate() {
+    return delegate_;
+  }
+
+  // Does not take ownership of |delegate|.
+  void set_delegate(FakeAvbOpsDelegate* delegate) {
+    delegate_ = delegate;
+  }
+
+  void set_partition_dir(const base::FilePath& partition_dir) {
+    partition_dir_ = partition_dir;
+  }
+
+  void set_expected_public_key(const std::string& expected_public_key) {
+    expected_public_key_ = expected_public_key;
+  }
+
+  void set_expected_public_key_metadata(
+      const std::string& expected_public_key_metadata) {
+    expected_public_key_metadata_ = expected_public_key_metadata;
+  }
+
+  void set_stored_rollback_indexes(
+      const std::map<size_t, uint64_t>& stored_rollback_indexes) {
+    stored_rollback_indexes_ = stored_rollback_indexes;
+  }
+
+  std::map<size_t, uint64_t> get_stored_rollback_indexes() {
+    return stored_rollback_indexes_;
+  }
+
+  void set_stored_is_device_unlocked(bool stored_is_device_unlocked) {
+    stored_is_device_unlocked_ = stored_is_device_unlocked;
+  }
+
+  void set_permanent_attributes(const AvbAtxPermanentAttributes& attributes) {
+    permanent_attributes_ = attributes;
+  }
+
+  void set_permanent_attributes_hash(const std::string& hash) {
+    permanent_attributes_hash_ = hash;
+  }
+
+  // FakeAvbOpsDelegate methods.
+  AvbIOResult read_from_partition(const char* partition,
+                                  int64_t offset,
+                                  size_t num_bytes,
+                                  void* buffer,
+                                  size_t* out_num_read) override;
+
+  AvbIOResult write_to_partition(const char* partition,
+                                 int64_t offset,
+                                 size_t num_bytes,
+                                 const void* buffer) override;
+
+  AvbIOResult validate_vbmeta_public_key(AvbOps* ops,
+                                         const uint8_t* public_key_data,
+                                         size_t public_key_length,
+                                         const uint8_t* public_key_metadata,
+                                         size_t public_key_metadata_length,
+                                         bool* out_key_is_trusted) override;
+
+  AvbIOResult read_rollback_index(AvbOps* ops,
+                                  size_t rollback_index_location,
+                                  uint64_t* out_rollback_index) override;
+
+  AvbIOResult write_rollback_index(AvbOps* ops,
+                                   size_t rollback_index_location,
+                                   uint64_t rollback_index) override;
+
+  AvbIOResult read_is_device_unlocked(AvbOps* ops,
+                                      bool* out_is_device_unlocked) override;
+
+  AvbIOResult get_unique_guid_for_partition(AvbOps* ops,
+                                            const char* partition,
+                                            char* guid_buf,
+                                            size_t guid_buf_size) override;
+
+  AvbIOResult read_permanent_attributes(
+      AvbAtxPermanentAttributes* attributes) override;
+
+  AvbIOResult read_permanent_attributes_hash(
+      uint8_t hash[AVB_SHA256_DIGEST_SIZE]) override;
+
+ private:
+  AvbOps avb_ops_;
+  AvbABOps avb_ab_ops_;
+  AvbAtxOps avb_atx_ops_;
+
+  FakeAvbOpsDelegate* delegate_;
+
+  base::FilePath partition_dir_;
+
+  std::string expected_public_key_;
+  std::string expected_public_key_metadata_;
+
+  std::map<size_t, uint64_t> stored_rollback_indexes_;
+
+  bool stored_is_device_unlocked_;
+
+  AvbAtxPermanentAttributes permanent_attributes_;
+  std::string permanent_attributes_hash_;
+};
+
+}  // namespace avb
+
+#endif /* FAKE_AVB_OPS_H_ */
diff --git a/avb/test/image_handler_unittest.py b/avb/test/image_handler_unittest.py
new file mode 100755
index 0000000..9eacac0
--- /dev/null
+++ b/avb/test/image_handler_unittest.py
@@ -0,0 +1,211 @@
+#!/usr/bin/python
+
+# Copyright 2016, The Android Open Source Project
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+
+"""Unit-test for ImageHandler."""
+
+
+import imp
+import os
+import sys
+import tempfile
+import unittest
+
+sys.dont_write_bytecode = True
+avbtool = imp.load_source('avbtool', './avbtool')
+
+# The file test_file.bin and test_file.bin.sparse are generated using
+# the following python code:
+#
+#  with open('test_file.bin', 'w+b') as f:
+#    f.write('Barfoo43'*128*12)
+#  os.system('img2simg test_file.bin test_file.bin.sparse')
+#  image = avbtool.ImageHandler('test_file.bin.sparse')
+#  image.append_dont_care(12*1024)
+#  image.append_fill('\x01\x02\x03\x04', 12*1024)
+#  image.append_raw('Foobar42'*128*12)
+#  image.append_dont_care(12*1024)
+#  del image
+#  os.system('rm -f test_file.bin')
+#  os.system('simg2img test_file.bin.sparse test_file.bin')
+#
+# and manually verified to be correct. The content of the raw and
+# sparse files are as follows (the line with "Fill with 0x04030201" is
+# a simg_dump.py bug):
+#
+# $ hexdump -C test_file.bin
+# 00000000  42 61 72 66 6f 6f 34 33  42 61 72 66 6f 6f 34 33  |Barfoo43Barfoo43|
+# *
+# 00003000  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
+# *
+# 00006000  01 02 03 04 01 02 03 04  01 02 03 04 01 02 03 04  |................|
+# *
+# 00009000  46 6f 6f 62 61 72 34 32  46 6f 6f 62 61 72 34 32  |Foobar42Foobar42|
+# *
+# 0000c000  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
+# *
+# 0000f000
+#
+# $ system/core/libsparse/simg_dump.py -v test_file.bin.sparse
+# test_file.bin.sparse: Total of 15 4096-byte output blocks in 5 input chunks.
+#             input_bytes      output_blocks
+# chunk    offset     number  offset  number
+#    1         40      12288       0       3 Raw data
+#    2      12340          0       3       3 Don't care
+#    3      12352          4       6       3 Fill with 0x04030201
+#    4      12368      12288       9       3 Raw data
+#    5      24668          0      12       3 Don't care
+#           24668                 15         End
+#
+
+
+class ImageHandler(unittest.TestCase):
+
+  TEST_FILE_SPARSE_PATH = 'test/data/test_file.bin.sparse'
+  TEST_FILE_PATH = 'test/data/test_file.bin'
+  TEST_FILE_SIZE = 61440
+  TEST_FILE_BLOCK_SIZE = 4096
+
+  def _file_contents_equal(self, path1, path2, size):
+    f1 = open(path1, 'r')
+    f2 = open(path2, 'r')
+    if f1.read(size) != f2.read(size):
+      return False
+    return True
+
+  def _file_size(self, f):
+    old_pos = f.tell()
+    f.seek(0, os.SEEK_END)
+    size = f.tell()
+    f.seek(old_pos)
+    return size
+
+  def _clone_sparse_file(self):
+    f = tempfile.NamedTemporaryFile()
+    f.write(open(self.TEST_FILE_SPARSE_PATH).read())
+    f.flush()
+    return f
+
+  def _unsparsify(self, path):
+    f = tempfile.NamedTemporaryFile()
+    os.system('simg2img {} {}'.format(path, f.name))
+    return f
+
+  def testRead(self):
+    """Checks that reading from a sparse file works as intended."""
+    ih = avbtool.ImageHandler(self.TEST_FILE_SPARSE_PATH)
+
+    # Check that we start at offset 0.
+    self.assertEqual(ih.tell(), 0)
+
+    # Check that reading advances the cursor.
+    self.assertEqual(ih.read(14), bytearray('Barfoo43Barfoo'))
+    self.assertEqual(ih.tell(), 14)
+    self.assertEqual(ih.read(2), bytearray('43'))
+    self.assertEqual(ih.tell(), 16)
+
+    # Check reading in the middle of a fill chunk gets the right data.
+    ih.seek(0x6000 + 1)
+    self.assertEqual(ih.read(4), bytearray('\x02\x03\x04\x01'))
+
+    # Check we can cross the chunk boundary correctly.
+    ih.seek(0x3000 - 10)
+    self.assertEqual(ih.read(12), bytearray('43Barfoo43\x00\x00'))
+    ih.seek(0x9000 - 3)
+    self.assertEqual(ih.read(5), bytearray('\x02\x03\x04Fo'))
+
+    # Check reading at end of file is a partial read.
+    ih.seek(0xf000 - 2)
+    self.assertEqual(ih.read(16), bytearray('\x00\x00'))
+
+  def testTruncate(self):
+    """Checks that we can truncate a sparse file correctly."""
+    # Check truncation at all possible boundaries (including start and end).
+    for size in range(0, self.TEST_FILE_SIZE + self.TEST_FILE_BLOCK_SIZE,
+                      self.TEST_FILE_BLOCK_SIZE):
+      sparse_file = self._clone_sparse_file()
+      ih = avbtool.ImageHandler(sparse_file.name)
+      ih.truncate(size)
+      unsparse_file = self._unsparsify(sparse_file.name)
+      self.assertEqual(self._file_size(unsparse_file), size)
+      self.assertTrue(self._file_contents_equal(unsparse_file.name,
+                                                self.TEST_FILE_PATH,
+                                                size))
+
+    # Check truncation to grow the file.
+    grow_size = 8192
+    sparse_file = self._clone_sparse_file()
+    ih = avbtool.ImageHandler(sparse_file.name)
+    ih.truncate(self.TEST_FILE_SIZE + grow_size)
+    unsparse_file = self._unsparsify(sparse_file.name)
+    self.assertEqual(self._file_size(unsparse_file),
+                     self.TEST_FILE_SIZE + grow_size)
+    self.assertTrue(self._file_contents_equal(unsparse_file.name,
+                                              self.TEST_FILE_PATH,
+                                              self.TEST_FILE_SIZE))
+    unsparse_file.seek(self.TEST_FILE_SIZE)
+    self.assertEqual(unsparse_file.read(), '\0'*grow_size)
+
+  def testAppendRaw(self):
+    """Checks that we can append raw data correctly."""
+    sparse_file = self._clone_sparse_file()
+    ih = avbtool.ImageHandler(sparse_file.name)
+    data = 'SomeData'*4096
+    ih.append_raw(data)
+    unsparse_file = self._unsparsify(sparse_file.name)
+    self.assertTrue(self._file_contents_equal(unsparse_file.name,
+                                              self.TEST_FILE_PATH,
+                                              self.TEST_FILE_SIZE))
+    unsparse_file.seek(self.TEST_FILE_SIZE)
+    self.assertEqual(unsparse_file.read(), data)
+
+  def testAppendFill(self):
+    """Checks that we can append fill data correctly."""
+    sparse_file = self._clone_sparse_file()
+    ih = avbtool.ImageHandler(sparse_file.name)
+    data = 'ABCD'*4096
+    ih.append_fill('ABCD', len(data))
+    unsparse_file = self._unsparsify(sparse_file.name)
+    self.assertTrue(self._file_contents_equal(unsparse_file.name,
+                                              self.TEST_FILE_PATH,
+                                              self.TEST_FILE_SIZE))
+    unsparse_file.seek(self.TEST_FILE_SIZE)
+    self.assertEqual(unsparse_file.read(), data)
+
+  def testDontCare(self):
+    """Checks that we can append DONT_CARE data correctly."""
+    sparse_file = self._clone_sparse_file()
+    ih = avbtool.ImageHandler(sparse_file.name)
+    data = '\0'*40960
+    ih.append_dont_care(len(data))
+    unsparse_file = self._unsparsify(sparse_file.name)
+    self.assertTrue(self._file_contents_equal(unsparse_file.name,
+                                              self.TEST_FILE_PATH,
+                                              self.TEST_FILE_SIZE))
+    unsparse_file.seek(self.TEST_FILE_SIZE)
+    self.assertEqual(unsparse_file.read(), data)
+
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/avb/test/libavb_host_symbols_test b/avb/test/libavb_host_symbols_test
new file mode 100755
index 0000000..db0dcb0
--- /dev/null
+++ b/avb/test/libavb_host_symbols_test
@@ -0,0 +1,49 @@
+#!/bin/bash
+
+#
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+#
+
+# This shell-script checks the symbols in libavb_host.a and fails
+# if a reference not starting with avb_ is referenced. It's intended
+# to catch mistakes where the standard C library is inadvertently
+# used.
+
+set -e
+
+SYMBOLS_FILE=$(mktemp /tmp/libavb_host_symbols.XXXXXXXXXX)
+
+trap "rm -f '${SYMBOLS_FILE}'" EXIT
+
+readelf --symbols --wide "${ANDROID_HOST_OUT}/obj/STATIC_LIBRARIES/libavb_host_intermediates/libavb_host.a" | \
+  awk '$7 == "UND" && $8 != "" {print $8}' | \
+  grep -v ^avb_ | \
+  sort -u > "${SYMBOLS_FILE}"
+
+# If this file is non-empty, it means that the library is using
+# symbols not starting with "avb_".
+if [ -s "${SYMBOLS_FILE}" ] ; then
+  echo "ERROR: $0: Unexpected symbols in libavb_host:" >&2
+  cat "${SYMBOLS_FILE}" >&2
+  exit 1
+fi
diff --git a/avb/test/user_code_test.cc b/avb/test/user_code_test.cc
new file mode 100644
index 0000000..fd851f3
--- /dev/null
+++ b/avb/test/user_code_test.cc
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+// This test ensures that user code can include libavb*.h and compile without
+// defining AVB_COMPILATION (which user code must not).
+#include "libavb/libavb.h"
+#include "libavb_ab/libavb_ab.h"
+#include "libavb_atx/libavb_atx.h"