Add example code for Android Things

Bug: 65385915
Test: Unit tests
Change-Id: I7cc24f02bd4df1b810f3d8b12a3251981fe504f6
diff --git a/Android.bp b/Android.bp
index e8ecaca..646a884 100644
--- a/Android.bp
+++ b/Android.bp
@@ -162,6 +162,13 @@
     srcs: ["libavb/avb_sysdeps_posix.c"],
 }
 
+cc_library_host_static {
+    name: "libavb_things_example",
+    defaults: ["avb_defaults"],
+    export_include_dirs: ["."],
+    srcs: ["examples/things/avb_atx_slot_verify.c"],
+}
+
 cc_test_host {
     name: "libavb_host_unittest",
     defaults: ["avb_defaults"],
@@ -174,6 +181,7 @@
         "libavb",
         "libavb_ab_host",
         "libavb_atx_host",
+        "libavb_things_example",
         "libgmock_host",
         "libgtest_host",
     ],
@@ -188,6 +196,7 @@
     srcs: [
         "test/avb_ab_flow_unittest.cc",
         "test/avb_atx_validate_unittest.cc",
+        "test/avb_atx_slot_verify_unittest.cc",
         "test/avb_slot_verify_unittest.cc",
         "test/avb_unittest_util.cc",
         "test/avb_util_unittest.cc",
diff --git a/README.md b/README.md
index c07ec7e..41c13be 100644
--- a/README.md
+++ b/README.md
@@ -209,6 +209,9 @@
 * `examples/uefi/`
     + Contains the source-code for a UEFI-based boot-loader utilizing
       `libavb/` and `libavb_ab/`.
+* `examples/things/`
+    + Contains the source-code for a slot verification suitable for Android
+      Things.
 * `README.md`
     + This file.
 * `docs/`
diff --git a/examples/things/README.md b/examples/things/README.md
new file mode 100644
index 0000000..45509af
--- /dev/null
+++ b/examples/things/README.md
@@ -0,0 +1,6 @@
+# Android Things Example
+---
+
+This directory contains example source code for an Android Things integration of
+lib_avb and lib_avb_atx. The implementation includes rollback index management
+and Verified Boot Hash (VBH) computation.
diff --git a/examples/things/avb_atx_slot_verify.c b/examples/things/avb_atx_slot_verify.c
new file mode 100644
index 0000000..3129d2e
--- /dev/null
+++ b/examples/things/avb_atx_slot_verify.c
@@ -0,0 +1,202 @@
+/*
+ * 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 "avb_atx_slot_verify.h"
+
+#include <libavb/avb_sha.h>
+#include <libavb/libavb.h>
+#include <libavb_atx/libavb_atx.h>
+
+/* Chosen to be generous but still require a huge number of increase operations
+ * before exhausting the 64-bit space.
+ */
+static const uint64_t kRollbackIndexIncreaseThreshold = 1000000000;
+
+/* By convention, when a rollback index is not used the value remains zero. */
+static const uint64_t kRollbackIndexNotUsed = 0;
+
+typedef struct _AvbAtxOpsContext {
+  size_t key_version_location[AVB_MAX_NUMBER_OF_ROLLBACK_INDEX_LOCATIONS];
+  uint64_t key_version_value[AVB_MAX_NUMBER_OF_ROLLBACK_INDEX_LOCATIONS];
+  size_t next_key_version_slot;
+} AvbAtxOpsContext;
+
+typedef struct _AvbAtxOpsWithContext {
+  AvbAtxOps atx_ops;
+  AvbAtxOpsContext context;
+} AvbAtxOpsWithContext;
+
+/* Returns context associated with |atx_ops| returned by
+ * setup_ops_with_context().
+ */
+static AvbAtxOpsContext* get_ops_context(AvbAtxOps* atx_ops) {
+  return &((AvbAtxOpsWithContext*)atx_ops)->context;
+}
+
+/* An implementation of AvbAtxOps::set_key_version that saves the key version
+ * information to ops context data.
+ */
+static void save_key_version_to_context(AvbAtxOps* atx_ops,
+                                        size_t rollback_index_location,
+                                        uint64_t key_version) {
+  AvbAtxOpsContext* context = get_ops_context(atx_ops);
+  size_t offset = context->next_key_version_slot++;
+  if (offset < AVB_MAX_NUMBER_OF_ROLLBACK_INDEX_LOCATIONS) {
+    context->key_version_location[offset] = rollback_index_location;
+    context->key_version_value[offset] = key_version;
+  }
+}
+
+/* Attaches context data to |existing_ops| and returns new ops. The
+ * |ops_with_context| will be used to store the new combined ops and context.
+ * The set_key_version function will be replaced in order to collect the key
+ * version information in the context.
+ */
+static AvbAtxOps* setup_ops_with_context(
+    const AvbAtxOps* existing_ops, AvbAtxOpsWithContext* ops_with_context) {
+  avb_memset(ops_with_context, 0, sizeof(AvbAtxOpsWithContext));
+  ops_with_context->atx_ops = *existing_ops;
+  // Close the loop on the circular reference.
+  ops_with_context->atx_ops.ops->atx_ops = &ops_with_context->atx_ops;
+  ops_with_context->atx_ops.set_key_version = save_key_version_to_context;
+  return &ops_with_context->atx_ops;
+}
+
+/* Updates the stored rollback index value for |location| to match |value|. */
+static AvbSlotVerifyResult update_rollback_index(AvbOps* ops,
+                                                 size_t location,
+                                                 uint64_t value) {
+  AvbIOResult io_result = AVB_IO_RESULT_OK;
+  uint64_t current_value;
+  io_result = ops->read_rollback_index(ops, location, &current_value);
+  if (io_result == AVB_IO_RESULT_ERROR_OOM) {
+    return AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
+  } else if (io_result != AVB_IO_RESULT_OK) {
+    avb_error("Error getting rollback index for slot.\n");
+    return AVB_SLOT_VERIFY_RESULT_ERROR_IO;
+  }
+  if (current_value == value) {
+    // No update necessary.
+    return AVB_SLOT_VERIFY_RESULT_OK;
+  }
+  // The difference between the new and current value must not exceed the
+  // increase threshold, and the value must not decrease.
+  if (value - current_value > kRollbackIndexIncreaseThreshold) {
+    avb_error("Rollback index value cannot increase beyond the threshold.\n");
+    return AVB_SLOT_VERIFY_RESULT_ERROR_ROLLBACK_INDEX;
+  }
+  // This should have been checked during verification, but check again here as
+  // a safeguard.
+  if (value < current_value) {
+    avb_error("Rollback index value cannot decrease.\n");
+    return AVB_SLOT_VERIFY_RESULT_ERROR_ROLLBACK_INDEX;
+  }
+  io_result = ops->write_rollback_index(ops, location, value);
+  if (io_result == AVB_IO_RESULT_ERROR_OOM) {
+    return AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
+  } else if (io_result != AVB_IO_RESULT_OK) {
+    avb_error("Error setting stored rollback index.\n");
+    return AVB_SLOT_VERIFY_RESULT_ERROR_IO;
+  }
+  return AVB_SLOT_VERIFY_RESULT_OK;
+}
+
+AvbSlotVerifyResult avb_atx_slot_verify(
+    AvbAtxOps* atx_ops,
+    const char* ab_suffix,
+    AvbAtxLockState lock_state,
+    AvbAtxSlotState slot_state,
+    AvbAtxOemDataState oem_data_state,
+    AvbSlotVerifyData** verify_data,
+    uint8_t vbh_extension[AVB_SHA256_DIGEST_SIZE]) {
+  const char* partitions_without_oem[] = {"boot", NULL};
+  const char* partitions_with_oem[] = {"boot", "oem_bootloader", NULL};
+  AvbSlotVerifyResult result = AVB_SLOT_VERIFY_RESULT_OK;
+  AvbSHA256Ctx ctx;
+  size_t i = 0;
+  AvbAtxOpsWithContext ops_with_context;
+
+  atx_ops = setup_ops_with_context(atx_ops, &ops_with_context);
+
+  result = avb_slot_verify(atx_ops->ops,
+                           (oem_data_state == AVB_ATX_OEM_DATA_NOT_USED)
+                               ? partitions_without_oem
+                               : partitions_with_oem,
+                           ab_suffix,
+                           (lock_state == AVB_ATX_UNLOCKED)
+                               ? AVB_SLOT_VERIFY_FLAGS_ALLOW_VERIFICATION_ERROR
+                               : AVB_SLOT_VERIFY_FLAGS_NONE,
+                           AVB_HASHTREE_ERROR_MODE_EIO,
+                           verify_data);
+
+  if (result != AVB_SLOT_VERIFY_RESULT_OK || lock_state == AVB_ATX_UNLOCKED) {
+    return result;
+  }
+
+  /* Compute the Android Things Verified Boot Hash (VBH) extension. */
+  avb_sha256_init(&ctx);
+  for (i = 0; i < (*verify_data)->num_vbmeta_images; i++) {
+    avb_sha256_update(&ctx,
+                      (*verify_data)->vbmeta_images[i].vbmeta_data,
+                      (*verify_data)->vbmeta_images[i].vbmeta_size);
+  }
+  avb_memcpy(vbh_extension, avb_sha256_final(&ctx), AVB_SHA256_DIGEST_SIZE);
+
+  /* Increase rollback index values to match the verified slot. */
+  if (slot_state == AVB_ATX_SLOT_MARKED_SUCCESSFUL) {
+    for (i = 0; i < AVB_MAX_NUMBER_OF_ROLLBACK_INDEX_LOCATIONS; i++) {
+      uint64_t rollback_index_value = (*verify_data)->rollback_indexes[i];
+      if (rollback_index_value != kRollbackIndexNotUsed) {
+        result = update_rollback_index(atx_ops->ops, i, rollback_index_value);
+        if (result != AVB_SLOT_VERIFY_RESULT_OK) {
+          goto out;
+        }
+      }
+    }
+
+    /* Also increase rollback index values for Android Things key version
+     * locations.
+     */
+    for (i = 0; i < AVB_MAX_NUMBER_OF_ROLLBACK_INDEX_LOCATIONS; i++) {
+      size_t rollback_index_location =
+          ops_with_context.context.key_version_location[i];
+      uint64_t rollback_index_value =
+          ops_with_context.context.key_version_value[i];
+      if (rollback_index_value != kRollbackIndexNotUsed) {
+        result = update_rollback_index(
+            atx_ops->ops, rollback_index_location, rollback_index_value);
+        if (result != AVB_SLOT_VERIFY_RESULT_OK) {
+          goto out;
+        }
+      }
+    }
+  }
+
+out:
+  if (result != AVB_SLOT_VERIFY_RESULT_OK) {
+    avb_slot_verify_data_free(*verify_data);
+    *verify_data = NULL;
+  }
+  return result;
+}
diff --git a/examples/things/avb_atx_slot_verify.h b/examples/things/avb_atx_slot_verify.h
new file mode 100644
index 0000000..517b097
--- /dev/null
+++ b/examples/things/avb_atx_slot_verify.h
@@ -0,0 +1,84 @@
+/*
+ * 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 AVB_ATX_SLOT_VERIFY_H_
+#define AVB_ATX_SLOT_VERIFY_H_
+
+#include <libavb_atx/libavb_atx.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum {
+  AVB_ATX_LOCKED,
+  AVB_ATX_UNLOCKED,
+} AvbAtxLockState;
+
+typedef enum {
+  AVB_ATX_SLOT_MARKED_SUCCESSFUL,
+  AVB_ATX_SLOT_NOT_MARKED_SUCCESSFUL,
+} AvbAtxSlotState;
+
+typedef enum {
+  AVB_ATX_OEM_DATA_USED,
+  AVB_ATX_OEM_DATA_NOT_USED,
+} AvbAtxOemDataState;
+
+/* Performs a full verification of the slot identified by |ab_suffix|. If
+ * |lock_state| indicates verified boot is unlocked then verification errors
+ * will be allowed (see AVB_SLOT_VERIFY_FLAGS_ALLOW_VERIFICATION_ERROR for more
+ * details.
+ *
+ * If |slot_state| indicates the slot identified by |ab_suffix| has been marked
+ * successful then minimum rollback index values will be bumped to match the
+ * values in the verified slot (on success).
+ *
+ * If |oem_data_state| indicates that OEM-specific data is not being used, then
+ * verification of the 'oem_bootloader' partition will be skipped and it will
+ * not be represented in |out_data|.
+ *
+ * The semantics of |out_data| are the same as for avb_slot_verify().
+ *
+ * On success, an Android Things |vbh_extension| is populated. This value must
+ * be extended into the Verified Boot Hash value accumulated from earlier boot
+ * stages.
+ *
+ * All of the function pointers in |ops| must be valid except for
+ * set_key_version, which will be ignored and may be NULL.
+ */
+AvbSlotVerifyResult avb_atx_slot_verify(
+    AvbAtxOps* ops,
+    const char* ab_suffix,
+    AvbAtxLockState lock_state,
+    AvbAtxSlotState slot_state,
+    AvbAtxOemDataState oem_data_state,
+    AvbSlotVerifyData** verify_data,
+    uint8_t vbh_extension[AVB_SHA256_DIGEST_SIZE]);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* AVB_SLOT_VERIFY_H_ */
diff --git a/test/avb_atx_slot_verify_unittest.cc b/test/avb_atx_slot_verify_unittest.cc
new file mode 100644
index 0000000..79c1cfd
--- /dev/null
+++ b/test/avb_atx_slot_verify_unittest.cc
@@ -0,0 +1,291 @@
+/*
+ * 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 <stdio.h>
+#include <string.h>
+
+#include <base/files/file_util.h>
+#include <gtest/gtest.h>
+#include <openssl/sha.h>
+
+#include "avb_unittest_util.h"
+#include "examples/things/avb_atx_slot_verify.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 uint64_t kNewRollbackValue = 42;
+
+} /* namespace */
+
+namespace avb {
+
+// A fixture for testing avb_atx_slot_verify() with ATX. This test is
+// parameterized on the initial stored rollback index (same value used in all
+// relevant locations).
+class AvbAtxSlotVerifyExampleTest
+    : public BaseAvbToolTest,
+      public FakeAvbOpsDelegate,
+      public ::testing::WithParamInterface<uint64_t> {
+ public:
+  ~AvbAtxSlotVerifyExampleTest() 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_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 get_preloaded_partition(
+      const char* partition,
+      size_t num_bytes,
+      uint8_t** out_pointer,
+      size_t* out_num_bytes_preloaded) override {
+    return ops_.get_preloaded_partition(
+        partition, num_bytes, out_pointer, out_num_bytes_preloaded);
+  }
+
+  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,
+                                              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 {
+    num_write_rollback_calls_++;
+    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 get_size_of_partition(AvbOps* ops,
+                                    const char* partition,
+                                    uint64_t* out_size) override {
+    return ops_.get_size_of_partition(ops, partition, out_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);
+  }
+
+  void set_key_version(size_t rollback_index_location,
+                       uint64_t key_version) override {
+    num_key_version_calls_++;
+    return ops_.set_key_version(rollback_index_location, key_version);
+  }
+
+  void RunSlotVerify() {
+    ops_.set_stored_rollback_indexes(
+        {{0, initial_rollback_value_},
+         {AVB_ATX_PIK_VERSION_LOCATION, initial_rollback_value_},
+         {AVB_ATX_PSK_VERSION_LOCATION, initial_rollback_value_}});
+    std::string metadata_option = "--public_key_metadata=";
+    metadata_option += kMetadataPath;
+    GenerateVBMetaImage("vbmeta_a.img",
+                        "SHA512_RSA4096",
+                        kNewRollbackValue,
+                        base::FilePath("test/data/testkey_atx_psk.pem"),
+                        metadata_option);
+    SHA256(vbmeta_image_.data(), vbmeta_image_.size(), expected_vbh_extension_);
+
+    ops_.set_expected_public_key(
+        PublicKeyAVB(base::FilePath("test/data/testkey_atx_psk.pem")));
+
+    AvbSlotVerifyData* slot_data = NULL;
+    EXPECT_EQ(expected_result_,
+              avb_atx_slot_verify(ops_.avb_atx_ops(),
+                                  "_a",
+                                  lock_state_,
+                                  slot_state_,
+                                  oem_data_state_,
+                                  &slot_data,
+                                  actual_vbh_extension_));
+    if (expected_result_ == AVB_SLOT_VERIFY_RESULT_OK) {
+      EXPECT_NE(nullptr, slot_data);
+      avb_slot_verify_data_free(slot_data);
+      // Make sure ATX is being run.
+      EXPECT_EQ(1, num_atx_calls_);
+      // Make sure we're hooking set_key_version.
+      EXPECT_EQ(0, num_key_version_calls_);
+    }
+  }
+
+  void CheckVBH() {
+    if (expected_result_ != AVB_SLOT_VERIFY_RESULT_OK ||
+        lock_state_ == AVB_ATX_UNLOCKED) {
+      memset(&expected_vbh_extension_, 0, AVB_SHA256_DIGEST_SIZE);
+    }
+    // Check that the VBH was correctly calculated.
+    EXPECT_EQ(0,
+              memcmp(actual_vbh_extension_,
+                     expected_vbh_extension_,
+                     AVB_SHA256_DIGEST_SIZE));
+  }
+
+  void CheckNewRollbackState() {
+    uint64_t expected_rollback_value = kNewRollbackValue;
+    if (expected_result_ != AVB_SLOT_VERIFY_RESULT_OK ||
+        lock_state_ == AVB_ATX_UNLOCKED ||
+        slot_state_ != AVB_ATX_SLOT_MARKED_SUCCESSFUL) {
+      // Check that rollback indexes were unmodified.
+      expected_rollback_value = initial_rollback_value_;
+    }
+    // Check that all rollback indexes have the expected value.
+    std::map<size_t, uint64_t> stored_rollback_indexes =
+        ops_.get_stored_rollback_indexes();
+    EXPECT_EQ(expected_rollback_value, stored_rollback_indexes[0]);
+    EXPECT_EQ(expected_rollback_value,
+              stored_rollback_indexes[AVB_ATX_PIK_VERSION_LOCATION]);
+    EXPECT_EQ(expected_rollback_value,
+              stored_rollback_indexes[AVB_ATX_PSK_VERSION_LOCATION]);
+    // Check that if the rollback did not need to change, there were no writes.
+    if (initial_rollback_value_ == kNewRollbackValue ||
+        initial_rollback_value_ == expected_rollback_value) {
+      EXPECT_EQ(0, num_write_rollback_calls_);
+    } else {
+      EXPECT_NE(0, num_write_rollback_calls_);
+    }
+  }
+
+ protected:
+  FakeAvbOps ops_;
+  AvbAtxPermanentAttributes attributes_;
+  int num_atx_calls_ = 0;
+  int num_key_version_calls_ = 0;
+  int num_write_rollback_calls_ = 0;
+  AvbSlotVerifyResult expected_result_ = AVB_SLOT_VERIFY_RESULT_OK;
+  uint64_t initial_rollback_value_ = 0;
+  AvbAtxLockState lock_state_ = AVB_ATX_LOCKED;
+  AvbAtxSlotState slot_state_ = AVB_ATX_SLOT_MARKED_SUCCESSFUL;
+  AvbAtxOemDataState oem_data_state_ = AVB_ATX_OEM_DATA_NOT_USED;
+  uint8_t expected_vbh_extension_[AVB_SHA256_DIGEST_SIZE] = {};
+  uint8_t actual_vbh_extension_[AVB_SHA256_DIGEST_SIZE] = {};
+
+ 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_P(AvbAtxSlotVerifyExampleTest, RunWithStartingIndex) {
+  initial_rollback_value_ = GetParam();
+  RunSlotVerify();
+  CheckVBH();
+  CheckNewRollbackState();
+}
+
+INSTANTIATE_TEST_CASE_P(P,
+                        AvbAtxSlotVerifyExampleTest,
+                        ::testing::Values(0,
+                                          1,
+                                          kNewRollbackValue / 2,
+                                          kNewRollbackValue - 1,
+                                          kNewRollbackValue));
+
+TEST_F(AvbAtxSlotVerifyExampleTest, RunUnlocked) {
+  lock_state_ = AVB_ATX_UNLOCKED;
+  RunSlotVerify();
+  CheckVBH();
+  CheckNewRollbackState();
+}
+
+TEST_F(AvbAtxSlotVerifyExampleTest, RunWithSlotNotMarkedSuccessful) {
+  slot_state_ = AVB_ATX_SLOT_NOT_MARKED_SUCCESSFUL;
+  RunSlotVerify();
+  CheckVBH();
+  CheckNewRollbackState();
+}
+
+TEST_F(AvbAtxSlotVerifyExampleTest, RunWithOemData) {
+  oem_data_state_ = AVB_ATX_OEM_DATA_USED;
+  RunSlotVerify();
+  CheckVBH();
+  CheckNewRollbackState();
+}
+
+}  // namespace avb