libsnapshot_fuzzer: use protobuf

Use protobuf because it already has all the fuzzing implemenetations.
Delete fuzz_utils.

Pros:
- Fuzzing protobuf is faster; it is easy to achieve 4K exec/s
- It is more guided; protobufs are fuzzed using mutators, and mutators
  should have better knowledge of the structure of the fuzz data
- No more hand-written parsing code of the fuzz data. That code in
  fuzz_utils.h is deleted.
- Corpus data can be reused even after adding new fields in the protobuf
- Corpus data is human-readable and easily manually written (it is
  the text format of the protobuf)

Cons:
- The "actions" are "declared" in protobuf definition and "defined" in
  C++, so there's more boilerplate to write. Adding a new "Action"
  requires changes in both.

Test: run libsnapshot_fuzzer
Bug: 154633114
Change-Id: Idc2a6b2c087e370e4cfef53142a244b9b275389e
diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp
index 40da1bc..c1213f6 100644
--- a/fs_mgr/libsnapshot/Android.bp
+++ b/fs_mgr/libsnapshot/Android.bp
@@ -254,9 +254,11 @@
 
     native_coverage : true,
     srcs: [
+        // Compile the protobuf definition again with type full.
+        "android/snapshot/snapshot_fuzz.proto",
+        "fuzz_utils.cpp",
         "snapshot_fuzz.cpp",
         "snapshot_fuzz_utils.cpp",
-        "fuzz_utils.cpp",
     ],
     static_libs: [
         "libbase",
@@ -269,12 +271,16 @@
         "liblp",
         "libsnapshot_init", // don't use binder or hwbinder
         "libsnapshot_test_helpers",
-        "libprotobuf-cpp-lite",
+        "libprotobuf-mutator",
         "update_metadata-protos",
     ],
     header_libs: [
         "libstorage_literals_headers",
     ],
+    proto: {
+        type: "full",
+        canonical_path_from_root: false,
+    },
 
     fuzz_config: {
         cc: ["android-virtual-ab+bugs@google.com"],
diff --git a/fs_mgr/libsnapshot/android/snapshot/snapshot_fuzz.proto b/fs_mgr/libsnapshot/android/snapshot/snapshot_fuzz.proto
new file mode 100644
index 0000000..679213c
--- /dev/null
+++ b/fs_mgr/libsnapshot/android/snapshot/snapshot_fuzz.proto
@@ -0,0 +1,76 @@
+// Copyright (C) 2020 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.
+
+syntax = "proto3";
+package android.snapshot;
+
+// Controls the behavior of IDeviceInfo.
+// Next: 6
+message FuzzDeviceInfoData {
+    bool slot_suffix_is_a = 1;
+    bool is_overlayfs_setup = 2;
+    bool allow_set_boot_control_merge_status = 3;
+    bool allow_set_slot_as_unbootable = 4;
+    bool is_recovery = 5;
+}
+
+// Controls the behavior of the test SnapshotManager.
+// Next: 2
+message FuzzSnapshotManagerData {
+    bool is_local_image_manager = 1;
+}
+
+// Mimics the API of ISnapshotManager. Defines one action on the snapshot
+// manager.
+// Next: 18
+message SnapshotManagerActionProto {
+    message NoArgs {}
+    message ProcessUpdateStateArgs {
+        bool has_before_cancel = 1;
+        bool fail_before_cancel = 2;
+    }
+    reserved 7;
+    reserved "create_update_snapshots";
+    reserved 8;
+    reserved "map_update_snapshot";
+    reserved 9;
+    reserved "unmap_update_snapshot";
+    reserved 11;
+    reserved "create_logical_and_snapshot_partitions";
+    reserved 14;
+    reserved "recovery_create_snapshot_devices_with_metadata";
+    oneof value {
+        NoArgs begin_update = 1;
+        NoArgs cancel_update = 2;
+        bool finished_snapshot_writes = 3;
+        NoArgs initiate_merge = 4;
+        ProcessUpdateStateArgs process_update_state = 5;
+        bool get_update_state = 6;
+        NoArgs need_snapshots_in_first_stage_mount = 10;
+        bool handle_imminent_data_wipe = 12;
+        NoArgs recovery_create_snapshot_devices = 13;
+        NoArgs dump = 15;
+        NoArgs ensure_metadata_mounted = 16;
+        NoArgs get_snapshot_merge_stats_instance = 17;
+    }
+}
+
+// Includes all data that needs to be fuzzed.
+message SnapshotFuzzData {
+    FuzzDeviceInfoData device_info_data = 1;
+    FuzzSnapshotManagerData manager_data = 2;
+    // More data used to prep the test before running actions.
+    reserved 3 to 9999;
+    repeated SnapshotManagerActionProto actions = 10000;
+}
diff --git a/fs_mgr/libsnapshot/fuzz.sh b/fs_mgr/libsnapshot/fuzz.sh
index 64d8224..2910129 100755
--- a/fs_mgr/libsnapshot/fuzz.sh
+++ b/fs_mgr/libsnapshot/fuzz.sh
@@ -18,8 +18,8 @@
 
 build_cov() {
     pushd $(gettop)
-    ret=$?
     NATIVE_COVERAGE="true" NATIVE_LINE_COVERAGE="true" COVERAGE_PATHS="${PROJECT_PATH}" m ${FUZZ_TARGET}
+    ret=$?
     popd
     return ${ret}
 }
@@ -46,7 +46,7 @@
 }
 
 # run_snapshot_fuzz -runs=10000
-generate_corpse() {
+generate_corpus() {
     [[ "$@" ]] || { echo "run with -runs=X"; return 1; }
 
     prepare_device &&
diff --git a/fs_mgr/libsnapshot/fuzz_utils.cpp b/fs_mgr/libsnapshot/fuzz_utils.cpp
index 509eb1b..0263f7e 100644
--- a/fs_mgr/libsnapshot/fuzz_utils.cpp
+++ b/fs_mgr/libsnapshot/fuzz_utils.cpp
@@ -22,4 +22,17 @@
     CHECK(value) << msg;
 }
 
+const google::protobuf::OneofDescriptor* GetProtoValueDescriptor(
+        const google::protobuf::Descriptor* action_desc) {
+    CHECK(action_desc);
+    CHECK(action_desc->oneof_decl_count() == 1)
+            << action_desc->oneof_decl_count() << " oneof fields found in " << action_desc->name()
+            << "; only one is expected.";
+    auto* oneof_value_desc = action_desc->oneof_decl(0);
+    CHECK(oneof_value_desc);
+    CHECK(oneof_value_desc->name() == "value")
+            << "oneof field has name " << oneof_value_desc->name();
+    return oneof_value_desc;
+}
+
 }  // namespace android::fuzz
diff --git a/fs_mgr/libsnapshot/fuzz_utils.h b/fs_mgr/libsnapshot/fuzz_utils.h
index 4e14b9c..8504d7b 100644
--- a/fs_mgr/libsnapshot/fuzz_utils.h
+++ b/fs_mgr/libsnapshot/fuzz_utils.h
@@ -12,256 +12,242 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include <optional>
+#pragma once
+
+#include <map>
 #include <string>
 #include <string_view>
-#include <vector>
 
-// Generic classes for fuzzing a collection of APIs.
+#include <google/protobuf/descriptor.h>
+#include <google/protobuf/message.h>
+#include <google/protobuf/repeated_field.h>
+
+// Utilities for using a protobuf definition to fuzz APIs in a class.
+// Terms:
+// The "fuzzed class" is the C++ class definition whose functions are fuzzed.
+// The "fuzzed object" is an instantiated object of the fuzzed class. It is
+//   typically created and destroyed for each test run.
+// An "action" is an operation on the fuzzed object that may mutate its state.
+//   This typically involves one function call into the fuzzed object.
 
 namespace android::fuzz {
 
-// My custom boolean type -- to avoid conflict with (u)int8_t and char.
-struct Bool {
-    bool value;
-    operator bool() const { return value; }
-};
-
-// Helper for FuzzData.
-// A wrapper over an optional const object T. The buffer is maintained elsewhere.
-template <typename T>
-class Optional {
-  public:
-    Optional(const T* ptr) : ptr_(ptr) {}
-    const T& operator*() const { return *ptr_; }
-    const T& value() const { return *ptr_; }
-    bool has_value() const { return ptr_; }
-
-  private:
-    const T* ptr_;
-};
-
-// Helper for FuzzData.
-// A wrapper over an optional boolean. The boolean is owned by this object.
-template <>
-class Optional<Bool> {
-  public:
-    Optional(std::optional<Bool>&& val) : val_(std::move(val)) {}
-    const Bool& operator*() const { return *val_; }
-    const Bool& value() const { return val_.value(); }
-    bool has_value() const { return val_.has_value(); }
-
-  private:
-    std::optional<Bool> val_;
-};
-
-// Helper for FuzzData.
-// A view on a raw data buffer. Client is responsible for maintaining the lifetime of the data
-// buffer.
-class DataView {
-  public:
-    DataView(const uint8_t* data, uint64_t size) : data_(data), size_(size) {}
-    DataView(const void* data, uint64_t size) : DataView(static_cast<const uint8_t*>(data), size) {}
-    inline uint64_t size() const { return size_; }
-    inline const uint8_t* data() const { return data_; }
-    inline bool CanConsume(uint64_t size) { return size_ >= size; }
-    // Consume the first |size| bytes from |this| and return a DataView object that represents
-    // the consumed data. Data pointer in |this| is incremented by |size| bytes.
-    // If not enough bytes, return nullopt.
-    std::optional<DataView> Consume(uint64_t size) {
-        if (!CanConsume(size)) return std::nullopt;
-        DataView ret(data_, size);
-        size_ -= size;
-        data_ += size;
-        return ret;
-    }
-
-  private:
-    const uint8_t* data_;
-    uint64_t size_;
-};
-
-// A view on the fuzz data. Provides APIs to consume typed objects.
-class FuzzData : public DataView {
-  public:
-    // Inherit constructors.
-    using DataView::DataView;
-    // Consume a data object T and return the pointer (into the buffer). No copy is done.
-    // If not enough bytes, return nullptr.
-    template <typename T>
-    inline Optional<T> Consume() {
-        auto data_view = DataView::Consume(sizeof(T));
-        if (!data_view.has_value()) return nullptr;
-        return reinterpret_cast<const T*>(data_view->data());
-    }
-    // To provide enough entropy for booleans, they are consumed bit by bit.
-    // Hence, the returned value is not indexed into the buffer. See Optional<Bool>.
-    template <>
-    Optional<Bool> Consume<Bool>() {
-        if (!boolean_buffer_.has_value() || boolean_bit_offset_ >= sizeof(*boolean_buffer_)) {
-            boolean_buffer_ = Consume<uint8_t>();
-            boolean_bit_offset_ = 0;
-        }
-        if (!boolean_buffer_.has_value()) {
-            return Optional<Bool>(std::nullopt);
-        }
-        const auto& byte = *boolean_buffer_;
-        bool ret = (byte >> boolean_bit_offset_) & 0x1;
-        boolean_bit_offset_++;
-        return Optional<Bool>(Bool{ret});
-    }
-
-  private:
-    // Separate buffer for booleans.
-    Optional<uint8_t> boolean_buffer_ = nullptr;
-    uint8_t boolean_bit_offset_ = 0;
-};
-
-enum class CallResult {
-    SUCCESS,
-    NOT_ENOUGH_DATA,
-};
-
-inline bool AllArgsHasValue() {
-    return true;
-}
-template <typename T>
-inline bool AllArgsHasValue(const Optional<T>& t) {
-    return t.has_value();
-}
-template <typename First, typename... Remaining>
-inline bool AllArgsHasValue(const Optional<First>& first, const Optional<Remaining>&... remaining) {
-    return first.has_value() && AllArgsHasValue(remaining...);
-}
-
-// Base class of FuzzFunction.
-class FuzzFunctionBase {
-  public:
-    virtual ~FuzzFunctionBase() = default;
-    virtual CallResult Call(FuzzData* fuzz_data) const = 0;
-};
-
-template <typename T>
-class FuzzFunction;  // undefined
-
-// A wrapper over a fuzzed function.
-template <typename R, typename... Args>
-class FuzzFunction<R(Args...)> : public FuzzFunctionBase {
-  public:
-    using Function = std::function<R(Args...)>;
-    FuzzFunction(Function&& function) : function_(std::move(function)) {}
-    // Eat necessary data in |fuzz_data| and invoke the function.
-    CallResult Call(FuzzData* fuzz_data) const override {
-        return CallWithOptionalArgs(fuzz_data->Consume<std::remove_reference_t<Args>>()...);
-    }
-
-  private:
-    Function function_;
-
-    CallResult CallWithOptionalArgs(const Optional<std::remove_reference_t<Args>>&... args) const {
-        if (!AllArgsHasValue(args...)) {
-            return CallResult::NOT_ENOUGH_DATA;
-        }
-        (void)function_(args.value()...);  // ignore returned value
-        return CallResult::SUCCESS;
-    }
-};
-
 // CHECK(value) << msg
 void CheckInternal(bool value, std::string_view msg);
 
-// A collection of FuzzFunction's.
-// FunctionsSizeType must be an integral type where
-// functions_.size() <= std::numeric_limits<FunctionSizeType>::max().
-template <typename FunctionsSizeType>
-class FuzzFunctions {
+// Get the oneof descriptor inside Action
+const google::protobuf::OneofDescriptor* GetProtoValueDescriptor(
+        const google::protobuf::Descriptor* action_desc);
+
+template <typename Class>
+using FunctionMapImpl =
+        std::map<int, std::function<void(Class*, const google::protobuf::Message& action_proto,
+                                         const google::protobuf::FieldDescriptor* field_desc)>>;
+
+template <typename Class>
+class FunctionMap : public FunctionMapImpl<Class> {
   public:
-    // Subclass should override this to register functions via AddFunction.
-    FuzzFunctions() = default;
-    virtual ~FuzzFunctions() = default;
-    // Eat some amount of data in |fuzz_data| and call one of the |functions_|.
-    CallResult CallOne(FuzzData* fuzz_data) const {
-        auto opt_number = fuzz_data->Consume<FunctionsSizeType>();
-        if (!opt_number.has_value()) {
-            return CallResult::NOT_ENOUGH_DATA;
-        }
-        auto function_index = opt_number.value() % functions_.size();
-        return functions_[function_index]->Call(fuzz_data);
+    void CheckEmplace(typename FunctionMapImpl<Class>::key_type key,
+                      typename FunctionMapImpl<Class>::mapped_type&& value) {
+        auto [it, inserted] = this->emplace(key, std::move(value));
+        CheckInternal(inserted,
+                      "Multiple implementation registered for tag number " + std::to_string(key));
     }
-
-  private:
-    template <typename R, typename... Args>
-    struct FunctionTraits {
-        using Function = std::function<R(Args...)>;
-    };
-
-  public:
-    // There are no deduction guide from lambda to std::function, so the
-    // signature of the lambda must be specified in the template argument.
-    // FuzzFunctions provide the following 3 ways to specify the signature of
-    // the lambda:
-
-    // AddFunction<R(Args...)>, e.g. AddFunction<ReturnType(ArgType, ArgType)>
-    template <typename T>
-    void AddFunction(std::function<T>&& func) {
-        functions_.push_back(std::make_unique<FuzzFunction<T>>(std::move(func)));
-    }
-
-    // AddFunction<R, Args...>, e.g. AddFunction<ReturnType, ArgType, ArgType>
-    template <typename R, typename... Args>
-    void AddFunction(typename FunctionTraits<R, Args...>::Function&& func) {
-        functions_.push_back(std::make_unique<FuzzFunction<R(Args...)>>(std::move(func)));
-    }
-
-    // AddFunction<ArgType...>. Equivalent to AddFunction<void, Args...>
-    template <typename... Args>
-    void AddFunction(typename FunctionTraits<void, Args...>::Function&& func) {
-        functions_.push_back(std::make_unique<FuzzFunction<void(Args...)>>(std::move(func)));
-    }
-
-    // Use |fuzz_data| as a guide to call |functions_| until |fuzz_data| is
-    // depleted. Return
-    void DepleteData(FuzzData* fuzz_data) const {
-        CallResult result;
-        while ((result = CallOne(fuzz_data)) == CallResult::SUCCESS)
-            ;
-        CheckInternal(result == CallResult::NOT_ENOUGH_DATA,
-                      "result is " + std::to_string(static_cast<int>(result)));
-    }
-
-  protected:
-    // Helper for subclass to check that size of |functions_| is actually within
-    // SizeType. Should be called after all functions are registered.
-    void CheckFunctionsSize() const {
-        CheckInternal(functions_.size() <= std::numeric_limits<FunctionsSizeType>::max(),
-                      "Need to extend number of bits for function count; there are " +
-                              std::to_string(functions_.size()) + " functions now.");
-    }
-
-  private:
-    std::vector<std::unique_ptr<FuzzFunctionBase>> functions_;
 };
 
-// An object whose APIs are being fuzzed.
-template <typename T, typename FunctionsSizeType>
-class FuzzObject : public FuzzFunctions<FunctionsSizeType> {
-  public:
-    // Not thread-safe; client is responsible for ensuring only one thread calls DepleteData.
-    void DepleteData(T* obj, FuzzData* fuzz_data) {
-        obj_ = obj;
-        FuzzFunctions<FunctionsSizeType>::DepleteData(fuzz_data);
-        obj_ = nullptr;
+template <typename Action>
+int CheckConsistency() {
+    const auto* function_map = Action::GetFunctionMap();
+    const auto* action_value_desc = GetProtoValueDescriptor(Action::Proto::GetDescriptor());
+
+    for (int field_index = 0; field_index < action_value_desc->field_count(); ++field_index) {
+        const auto* field_desc = action_value_desc->field(field_index);
+        CheckInternal(function_map->find(field_desc->number()) != function_map->end(),
+                      "Missing impl for function " + field_desc->camelcase_name());
+    }
+    return 0;
+}
+
+template <typename Action>
+void ExecuteActionProto(typename Action::Class* module,
+                        const typename Action::Proto& action_proto) {
+    static auto* action_value_desc = GetProtoValueDescriptor(Action::Proto::GetDescriptor());
+
+    auto* action_refl = Action::Proto::GetReflection();
+    if (!action_refl->HasOneof(action_proto, action_value_desc)) {
+        return;
     }
 
-  protected:
-    // Helper for subclass to get the module under test in the added functions.
-    T* get() const {
-        CheckInternal(obj_ != nullptr, "No module under test is found.");
-        return obj_;
+    const auto* field_desc = action_refl->GetOneofFieldDescriptor(action_proto, action_value_desc);
+    auto number = field_desc->number();
+    const auto& map = *Action::GetFunctionMap();
+    auto it = map.find(number);
+    CheckInternal(it != map.end(), "Missing impl for function " + field_desc->camelcase_name());
+    const auto& func = it->second;
+    func(module, action_proto, field_desc);
+}
+
+template <typename Action>
+void ExecuteAllActionProtos(
+        typename Action::Class* module,
+        const google::protobuf::RepeatedPtrField<typename Action::Proto>& action_protos) {
+    for (const auto& proto : action_protos) {
+        ExecuteActionProto<Action>(module, proto);
+    }
+}
+
+// Safely cast message to T. Returns a pointer to message if cast successfully, otherwise nullptr.
+template <typename T>
+const T* SafeCast(const google::protobuf::Message& message) {
+    if (message.GetDescriptor() != T::GetDescriptor()) {
+        return nullptr;
+    }
+    return static_cast<const T*>(&message);
+}
+
+// Cast message to const T&. Abort if type mismatch.
+template <typename T>
+const T& CheckedCast(const google::protobuf::Message& message) {
+    const auto* ptr = SafeCast<T>(message);
+    CheckInternal(ptr, "Cannot cast " + message.GetDescriptor()->name() + " to " +
+                               T::GetDescriptor()->name());
+    return *ptr;
+}
+
+// A templated way to a primitive field from a message using reflection.
+template <typename T>
+struct PrimitiveGetter;
+#define FUZZ_DEFINE_PRIMITIVE_GETTER(type, func_name)                              \
+    template <>                                                                    \
+    struct PrimitiveGetter<type> {                                                 \
+        static constexpr const auto fp = &google::protobuf::Reflection::func_name; \
     }
 
-  private:
-    T* obj_ = nullptr;
+FUZZ_DEFINE_PRIMITIVE_GETTER(bool, GetBool);
+FUZZ_DEFINE_PRIMITIVE_GETTER(uint32_t, GetUInt32);
+FUZZ_DEFINE_PRIMITIVE_GETTER(int32_t, GetInt32);
+FUZZ_DEFINE_PRIMITIVE_GETTER(uint64_t, GetUInt64);
+FUZZ_DEFINE_PRIMITIVE_GETTER(int64_t, GetInt64);
+FUZZ_DEFINE_PRIMITIVE_GETTER(double, GetDouble);
+FUZZ_DEFINE_PRIMITIVE_GETTER(float, GetFloat);
+
+// ActionPerformer extracts arguments from the protobuf message, and then call FuzzFunction
+// with these arguments.
+template <typename FuzzFunction, typename Signature, typename Enabled = void>
+struct ActionPerfomer;  // undefined
+
+template <typename FuzzFunction, typename MessageProto>
+struct ActionPerfomer<
+        FuzzFunction, void(const MessageProto&),
+        typename std::enable_if_t<std::is_base_of_v<google::protobuf::Message, MessageProto>>> {
+    static void Invoke(typename FuzzFunction::Class* module,
+                       const google::protobuf::Message& action_proto,
+                       const google::protobuf::FieldDescriptor* field_desc) {
+        const MessageProto& arg = CheckedCast<std::remove_reference_t<MessageProto>>(
+                action_proto.GetReflection()->GetMessage(action_proto, field_desc));
+        FuzzFunction::ImplBody(module, arg);
+    }
+};
+
+template <typename FuzzFunction, typename Primitive>
+struct ActionPerfomer<FuzzFunction, void(Primitive),
+                      typename std::enable_if_t<std::is_arithmetic_v<Primitive>>> {
+    static void Invoke(typename FuzzFunction::Class* module,
+                       const google::protobuf::Message& action_proto,
+                       const google::protobuf::FieldDescriptor* field_desc) {
+        Primitive arg = std::invoke(PrimitiveGetter<Primitive>::fp, action_proto.GetReflection(),
+                                    action_proto, field_desc);
+        FuzzFunction::ImplBody(module, arg);
+    }
+};
+
+template <typename FuzzFunction>
+struct ActionPerfomer<FuzzFunction, void()> {
+    static void Invoke(typename FuzzFunction::Class* module, const google::protobuf::Message&,
+                       const google::protobuf::FieldDescriptor*) {
+        FuzzFunction::ImplBody(module);
+    }
 };
 
 }  // namespace android::fuzz
+
+// Fuzz existing C++ class, ClassType, with a collection of functions under the name Action.
+//
+// Prerequisite: ActionProto must be defined in Protobuf to describe possible actions:
+// message FooActionProto {
+//     message NoArgs {}
+//     oneof value {
+//         bool do_foo = 1;
+//         NoArgs do_bar = 1;
+//     }
+// }
+// Use it to fuzz a C++ class Foo by doing the following:
+//   FUZZ_CLASS(Foo, FooAction)
+// After linking functions of Foo to FooAction, execute all actions by:
+//   FooAction::ExecuteAll(foo_object, action_protos)
+#define FUZZ_CLASS(ClassType, Action)                                                            \
+    class Action {                                                                               \
+      public:                                                                                    \
+        using Proto = Action##Proto;                                                             \
+        using Class = ClassType;                                                                 \
+        using FunctionMap = android::fuzz::FunctionMap<Class>;                                   \
+        static FunctionMap* GetFunctionMap() {                                                   \
+            static Action::FunctionMap map;                                                      \
+            return &map;                                                                         \
+        }                                                                                        \
+        static void ExecuteAll(Class* module,                                                    \
+                               const google::protobuf::RepeatedPtrField<Proto>& action_protos) { \
+            [[maybe_unused]] static int consistent = android::fuzz::CheckConsistency<Action>();  \
+            android::fuzz::ExecuteAllActionProtos<Action>(module, action_protos);                \
+        }                                                                                        \
+    }
+
+#define FUZZ_FUNCTION_CLASS_NAME(Action, FunctionName) Action##_##FunctionName
+#define FUZZ_FUNCTION_TAG_NAME(FunctionName) k##FunctionName
+
+// Implement an action defined in protobuf. Example:
+// message FooActionProto {
+//     oneof value {
+//         bool do_foo = 1;
+//     }
+// }
+// class Foo { public: void DoAwesomeFoo(bool arg); };
+// FUZZ_OBJECT(FooAction, Foo);
+// FUZZ_FUNCTION(FooAction, DoFoo, module, bool arg) {
+//   module->DoAwesomeFoo(arg);
+// }
+// The name DoFoo is the camel case name of the action in protobuf definition of FooActionProto.
+#define FUZZ_FUNCTION(Action, FunctionName, module, ...)                                         \
+    class FUZZ_FUNCTION_CLASS_NAME(Action, FunctionName) {                                       \
+      public:                                                                                    \
+        using Class = Action::Class;                                                             \
+        static void ImplBody(Action::Class*, ##__VA_ARGS__);                                     \
+                                                                                                 \
+      private:                                                                                   \
+        static bool registered_;                                                                 \
+    };                                                                                           \
+    auto FUZZ_FUNCTION_CLASS_NAME(Action, FunctionName)::registered_ = ([] {                     \
+        auto tag = Action::Proto::ValueCase::FUZZ_FUNCTION_TAG_NAME(FunctionName);               \
+        auto func =                                                                              \
+                &::android::fuzz::ActionPerfomer<FUZZ_FUNCTION_CLASS_NAME(Action, FunctionName), \
+                                                 void(__VA_ARGS__)>::Invoke;                     \
+        Action::GetFunctionMap()->CheckEmplace(tag, func);                                       \
+        return true;                                                                             \
+    })();                                                                                        \
+    void FUZZ_FUNCTION_CLASS_NAME(Action, FunctionName)::ImplBody(Action::Class* module,         \
+                                                                  ##__VA_ARGS__)
+
+// Implement a simple action by linking it to the function with the same name. Example:
+// message FooActionProto {
+//     message NoArgs {}
+//     oneof value {
+//         NoArgs do_bar = 1;
+//     }
+// }
+// class Foo { public void DoBar(); };
+// FUZZ_OBJECT(FooAction, Foo);
+// FUZZ_FUNCTION(FooAction, DoBar);
+// The name DoBar is the camel case name of the action in protobuf definition of FooActionProto, and
+// also the name of the function of Foo.
+#define FUZZ_SIMPLE_FUNCTION(Action, FunctionName) \
+    FUZZ_FUNCTION(Action, FunctionName, module) { (void)module->FunctionName(); }
diff --git a/fs_mgr/libsnapshot/snapshot_fuzz.cpp b/fs_mgr/libsnapshot/snapshot_fuzz.cpp
index 3671bb0..ef2dd94 100644
--- a/fs_mgr/libsnapshot/snapshot_fuzz.cpp
+++ b/fs_mgr/libsnapshot/snapshot_fuzz.cpp
@@ -21,6 +21,7 @@
 #include <tuple>
 
 #include <android-base/logging.h>
+#include <src/libfuzzer/libfuzzer_macro.h>
 #include <storage_literals/storage_literals.h>
 
 #include "fuzz_utils.h"
@@ -31,11 +32,10 @@
 using android::base::SetLogger;
 using android::base::StderrLogger;
 using android::base::StdioLogger;
-using android::fuzz::Bool;
-using android::fuzz::FuzzData;
-using android::fuzz::FuzzObject;
+using android::fuzz::CheckedCast;
+using android::snapshot::SnapshotFuzzData;
 using android::snapshot::SnapshotFuzzEnv;
-using android::snapshot::SnapshotManagerFuzzData;
+using google::protobuf::RepeatedPtrField;
 
 // Avoid linking to libgsi since it needs disk I/O.
 namespace android::gsi {
@@ -51,49 +51,49 @@
 
 namespace android::snapshot {
 
-class FuzzSnapshotManager : public FuzzObject<ISnapshotManager, uint8_t> {
-  public:
-    FuzzSnapshotManager();
-};
+FUZZ_CLASS(ISnapshotManager, SnapshotManagerAction);
 
-FuzzSnapshotManager::FuzzSnapshotManager() {
-    AddFunction([this]() { (void)get()->BeginUpdate(); });
-    AddFunction([this]() { (void)get()->CancelUpdate(); });
-    AddFunction<Bool>([this](Bool wipe) { (void)get()->FinishedSnapshotWrites(wipe); });
-    AddFunction([this]() { (void)get()->InitiateMerge(); });
-    AddFunction<Bool, Bool>([this](auto has_before_cancel, auto fail_before_cancel) {
-        std::function<bool()> before_cancel;
-        if (has_before_cancel) {
-            before_cancel = [=]() { return fail_before_cancel; };
-        }
-        (void)get()->ProcessUpdateState({}, before_cancel);
-    });
-    AddFunction<Bool>([this](auto has_progress_arg) {
-        double progress;
-        (void)get()->GetUpdateState(has_progress_arg ? &progress : nullptr);
-    });
-    // TODO add CreateUpdateSnapshots according to proto
-    // TODO add MapUpdateSnapshot
-    // TODO add UnmapUpdateSnapshot using names from the dictionary
-    AddFunction([this]() { (void)get()->NeedSnapshotsInFirstStageMount(); });
-    // TODO add CreateLogicalAndSnapshotPartitions
-    AddFunction<Bool>([this](const Bool& has_callback) {
-        std::function<void()> callback;
-        if (has_callback) {
-            callback = []() {};
-        }
-        (void)get()->HandleImminentDataWipe(callback);
-    });
-    AddFunction([this]() { (void)get()->RecoveryCreateSnapshotDevices(); });
-    // TODO add RecoveryCreateSnapshotDevices with metadata_device arg
-    AddFunction([this]() {
-        std::stringstream ss;
-        (void)get()->Dump(ss);
-    });
-    AddFunction([this]() { (void)get()->EnsureMetadataMounted(); });
-    AddFunction([this]() { (void)get()->GetSnapshotMergeStatsInstance(); });
+using ProcessUpdateStateArgs = SnapshotManagerAction::Proto::ProcessUpdateStateArgs;
 
-    CheckFunctionsSize();
+FUZZ_SIMPLE_FUNCTION(SnapshotManagerAction, BeginUpdate);
+FUZZ_SIMPLE_FUNCTION(SnapshotManagerAction, CancelUpdate);
+FUZZ_SIMPLE_FUNCTION(SnapshotManagerAction, InitiateMerge);
+FUZZ_SIMPLE_FUNCTION(SnapshotManagerAction, NeedSnapshotsInFirstStageMount);
+FUZZ_SIMPLE_FUNCTION(SnapshotManagerAction, RecoveryCreateSnapshotDevices);
+FUZZ_SIMPLE_FUNCTION(SnapshotManagerAction, EnsureMetadataMounted);
+FUZZ_SIMPLE_FUNCTION(SnapshotManagerAction, GetSnapshotMergeStatsInstance);
+
+#define SNAPSHOT_FUZZ_FUNCTION(FunctionName, ...) \
+    FUZZ_FUNCTION(SnapshotManagerAction, FunctionName, snapshot, ##__VA_ARGS__)
+
+SNAPSHOT_FUZZ_FUNCTION(FinishedSnapshotWrites, bool wipe) {
+    (void)snapshot->FinishedSnapshotWrites(wipe);
+}
+
+SNAPSHOT_FUZZ_FUNCTION(ProcessUpdateState, const ProcessUpdateStateArgs& args) {
+    std::function<bool()> before_cancel;
+    if (args.has_before_cancel()) {
+        before_cancel = [&]() { return args.fail_before_cancel(); };
+    }
+    (void)snapshot->ProcessUpdateState({}, before_cancel);
+}
+
+SNAPSHOT_FUZZ_FUNCTION(GetUpdateState, bool has_progress_arg) {
+    double progress;
+    (void)snapshot->GetUpdateState(has_progress_arg ? &progress : nullptr);
+}
+
+SNAPSHOT_FUZZ_FUNCTION(HandleImminentDataWipe, bool has_callback) {
+    std::function<void()> callback;
+    if (has_callback) {
+        callback = []() {};
+    }
+    (void)snapshot->HandleImminentDataWipe(callback);
+}
+
+SNAPSHOT_FUZZ_FUNCTION(Dump) {
+    std::stringstream ss;
+    (void)snapshot->Dump(ss);
 }
 
 // During global init, log all messages to stdio. This is only done once.
@@ -111,31 +111,24 @@
 }
 // Stop logging (except fatal messages) after global initialization. This is only done once.
 int StopLoggingAfterGlobalInit() {
+    [[maybe_unused]] static protobuf_mutator::protobuf::LogSilencer log_silincer;
     SetLogger(&FatalOnlyLogger);
     return 0;
 }
 
 }  // namespace android::snapshot
 
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+DEFINE_PROTO_FUZZER(const SnapshotFuzzData& snapshot_fuzz_data) {
     using namespace android::snapshot;
 
     [[maybe_unused]] static auto allow_logging = AllowLoggingDuringGlobalInit();
     static SnapshotFuzzEnv env;
-    static FuzzSnapshotManager fuzz_snapshot_manager;
     [[maybe_unused]] static auto stop_logging = StopLoggingAfterGlobalInit();
 
     env.CheckSoftReset();
-    FuzzData fuzz_data(data, size);
 
-    auto snapshot_manager_data = fuzz_data.Consume<SnapshotManagerFuzzData>();
-    if (!snapshot_manager_data.has_value()) {
-        return 0;
-    }
-    auto snapshot_manager = env.CheckCreateSnapshotManager(snapshot_manager_data.value());
+    auto snapshot_manager = env.CheckCreateSnapshotManager(snapshot_fuzz_data);
     CHECK(snapshot_manager);
 
-    fuzz_snapshot_manager.DepleteData(snapshot_manager.get(), &fuzz_data);
-
-    return 0;
+    SnapshotManagerAction::ExecuteAll(snapshot_manager.get(), snapshot_fuzz_data.actions());
 }
diff --git a/fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp b/fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp
index 7829193..dfac3ef 100644
--- a/fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp
+++ b/fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp
@@ -250,16 +250,16 @@
 }
 
 std::unique_ptr<ISnapshotManager> SnapshotFuzzEnv::CheckCreateSnapshotManager(
-        const SnapshotManagerFuzzData& data) {
+        const SnapshotFuzzData& data) {
     auto partition_opener = std::make_unique<TestPartitionOpener>(super());
     auto metadata_dir = fake_root_->tmp_path() + "/snapshot_metadata";
     PCHECK(Mkdir(metadata_dir));
 
-    auto device_info = new SnapshotFuzzDeviceInfo(data.device_info_data,
+    auto device_info = new SnapshotFuzzDeviceInfo(data.device_info_data(),
                                                   std::move(partition_opener), metadata_dir);
     auto snapshot = SnapshotManager::New(device_info /* takes ownership */);
     snapshot->images_ = CheckCreateFakeImageManager(fake_root_->tmp_path());
-    snapshot->has_local_image_manager_ = data.is_local_image_manager;
+    snapshot->has_local_image_manager_ = data.manager_data().is_local_image_manager();
 
     return snapshot;
 }
diff --git a/fs_mgr/libsnapshot/snapshot_fuzz_utils.h b/fs_mgr/libsnapshot/snapshot_fuzz_utils.h
index 8c0d5dd..3f3c992 100644
--- a/fs_mgr/libsnapshot/snapshot_fuzz_utils.h
+++ b/fs_mgr/libsnapshot/snapshot_fuzz_utils.h
@@ -24,23 +24,10 @@
 // libsnapshot-specific code for fuzzing. Defines fake classes that are depended
 // by SnapshotManager.
 
+#include "android/snapshot/snapshot_fuzz.pb.h"
+
 namespace android::snapshot {
 
-// Controls the behavior of IDeviceInfo.
-typedef struct SnapshotFuzzDeviceInfoData {
-    bool slot_suffix_is_a : 1;
-    bool is_overlayfs_setup : 1;
-    bool allow_set_boot_control_merge_status : 1;
-    bool allow_set_slot_as_unbootable : 1;
-    bool is_recovery : 1;
-} __attribute__((packed)) SnapshotFuzzDeviceInfoData;
-
-// Controls the behavior of the test SnapshotManager.
-typedef struct SnapshotManagerFuzzData {
-    SnapshotFuzzDeviceInfoData device_info_data;
-    bool is_local_image_manager : 1;
-} __attribute__((packed)) SnapshotManagerFuzzData;
-
 class AutoMemBasedDir;
 
 // Prepare test environment. This has a heavy overhead and should be done once.
@@ -60,8 +47,7 @@
     // Create a snapshot manager for this test run.
     // Client is responsible for maintaining the lifetime of |data| over the life time of
     // ISnapshotManager.
-    std::unique_ptr<ISnapshotManager> CheckCreateSnapshotManager(
-            const SnapshotManagerFuzzData& data);
+    std::unique_ptr<ISnapshotManager> CheckCreateSnapshotManager(const SnapshotFuzzData& data);
 
     // Return path to super partition.
     const std::string& super() const;
@@ -82,10 +68,10 @@
 class SnapshotFuzzDeviceInfo : public ISnapshotManager::IDeviceInfo {
   public:
     // Client is responsible for maintaining the lifetime of |data|.
-    SnapshotFuzzDeviceInfo(const SnapshotFuzzDeviceInfoData& data,
+    SnapshotFuzzDeviceInfo(const FuzzDeviceInfoData& data,
                            std::unique_ptr<TestPartitionOpener>&& partition_opener,
                            const std::string& metadata_dir)
-        : data_(data),
+        : data_(&data),
           partition_opener_(std::move(partition_opener)),
           metadata_dir_(metadata_dir) {}
 
@@ -101,17 +87,21 @@
     }
 
     // Following APIs are fuzzed.
-    std::string GetSlotSuffix() const override { return data_.slot_suffix_is_a ? "_a" : "_b"; }
-    std::string GetOtherSlotSuffix() const override { return data_.slot_suffix_is_a ? "_b" : "_a"; }
-    bool IsOverlayfsSetup() const override { return data_.is_overlayfs_setup; }
-    bool SetBootControlMergeStatus(android::hardware::boot::V1_1::MergeStatus) override {
-        return data_.allow_set_boot_control_merge_status;
+    std::string GetSlotSuffix() const override { return data_->slot_suffix_is_a() ? "_a" : "_b"; }
+    std::string GetOtherSlotSuffix() const override {
+        return data_->slot_suffix_is_a() ? "_b" : "_a";
     }
-    bool SetSlotAsUnbootable(unsigned int) override { return data_.allow_set_slot_as_unbootable; }
-    bool IsRecovery() const override { return data_.is_recovery; }
+    bool IsOverlayfsSetup() const override { return data_->is_overlayfs_setup(); }
+    bool SetBootControlMergeStatus(android::hardware::boot::V1_1::MergeStatus) override {
+        return data_->allow_set_boot_control_merge_status();
+    }
+    bool SetSlotAsUnbootable(unsigned int) override {
+        return data_->allow_set_slot_as_unbootable();
+    }
+    bool IsRecovery() const override { return data_->is_recovery(); }
 
   private:
-    SnapshotFuzzDeviceInfoData data_;
+    const FuzzDeviceInfoData* data_;
     std::unique_ptr<TestPartitionOpener> partition_opener_;
     std::string metadata_dir_;
 };