No public description

PiperOrigin-RevId: 554371233
diff --git a/.github/workflows/bazel_test.yml b/.github/workflows/bazel_test.yml
index eca5cf6..580e546 100644
--- a/.github/workflows/bazel_test.yml
+++ b/.github/workflows/bazel_test.yml
@@ -28,7 +28,7 @@
   run_tests:
     name: Run tests
     runs-on: ubuntu-latest
-    timeout-minutes: 30
+    timeout-minutes: 60
     strategy:
       matrix:
         config: ['default', 'fuzztest']
@@ -62,10 +62,10 @@
         if: matrix.config == 'default'
         run: |
           bazel test --build_tests_only --test_output=errors \
-            -c ${{ matrix.compilation_mode }} -- //... -//centipede/...
+            -c ${{ matrix.compilation_mode }} --//rpc_fuzzing:enable_rpc_fuzzing -- //... -//centipede/...
       - name: Run end-to-end tests with --config=fuzztest
         if: matrix.config == 'fuzztest'
         run: |
           bazel test --build_tests_only --test_output=errors \
-            -c ${{ matrix.compilation_mode }} --config=fuzztest //e2e_tests:all
+            -c ${{ matrix.compilation_mode }} --config=fuzztest //e2e_tests:all --//rpc_fuzzing:enable_rpc_fuzzing
 
diff --git a/.github/workflows/cmake_test.yml b/.github/workflows/cmake_test.yml
index 55bc145..257b8ca 100644
--- a/.github/workflows/cmake_test.yml
+++ b/.github/workflows/cmake_test.yml
@@ -26,7 +26,7 @@
   run_tests:
     name: Run CMake tests
     runs-on: ubuntu-latest
-    timeout-minutes: 30
+    timeout-minutes: 60
     strategy:
       matrix:
         mode: ['default', 'fuzzing']
diff --git a/WORKSPACE b/WORKSPACE
index b685cdc..d8a02a2 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -36,9 +36,9 @@
 
 http_archive(
     name = "com_google_absl",
-    sha256 = "3ea49a7d97421b88a8c48a0de16c16048e17725c7ec0f1d3ea2683a2a75adc21",
-    strip_prefix = "abseil-cpp-20230125.0",
-    url = "https://github.com/abseil/abseil-cpp/archive/refs/tags/20230125.0.tar.gz",
+    sha256 = "5366d7e7fa7ba0d915014d387b66d0d002c03236448e1ba9ef98122c13b35c36",
+    strip_prefix = "abseil-cpp-20230125.3",
+    url = "https://github.com/abseil/abseil-cpp/archive/refs/tags/20230125.3.tar.gz",
 )
 
 http_archive(
@@ -55,6 +55,18 @@
     url = "https://www.antlr.org/download/antlr4-cpp-runtime-4.12.0-source.zip",
 )
 
+http_archive(
+    name = "com_github_grpc_grpc",
+    urls = [
+        "https://github.com/grpc/grpc/archive/49dcbce9aba5f5f0959871354df90e38f0c5ed00.tar.gz", # v1.55.3
+    ],
+    strip_prefix = "grpc-49dcbce9aba5f5f0959871354df90e38f0c5ed00",
+)
+load("@com_github_grpc_grpc//bazel:grpc_deps.bzl", "grpc_deps")
+grpc_deps()
+load("@com_github_grpc_grpc//bazel:grpc_extra_deps.bzl", "grpc_extra_deps")
+grpc_extra_deps()
+
 ################################################################################
 # Direct dependencies that are only required for running tests
 ################################################################################
diff --git a/domain_tests/BUILD b/domain_tests/BUILD
index 4e24aa6..28c201f 100644
--- a/domain_tests/BUILD
+++ b/domain_tests/BUILD
@@ -22,7 +22,10 @@
     name = "domain_testing",
     testonly = 1,
     hdrs = ["domain_testing.h"],
-    visibility = ["@com_google_fuzztest//fuzztest:__subpackages__"],
+    visibility = [
+        "@com_google_fuzztest//fuzztest:__subpackages__",
+        "@com_google_fuzztest//rpc_fuzzing:__pkg__",
+    ],
     deps = [
         "@com_google_absl//absl/container:flat_hash_set",
         "@com_google_absl//absl/hash",
diff --git a/e2e_tests/BUILD b/e2e_tests/BUILD
index b146dd3..3df931e 100644
--- a/e2e_tests/BUILD
+++ b/e2e_tests/BUILD
@@ -26,6 +26,7 @@
     srcs = ["functional_test.cc"],
     data = [
         "@com_google_fuzztest//e2e_tests/testdata:fuzz_tests_for_functional_testing.stripped",
+        "@com_google_fuzztest//e2e_tests/testdata:fuzz_tests_for_rpc_session_grpc_functional_testing.stripped",
         "@com_google_fuzztest//e2e_tests/testdata:fuzz_tests_with_invalid_seeds.stripped",
     ],
     shard_count = 50,
diff --git a/e2e_tests/functional_test.cc b/e2e_tests/functional_test.cc
index 070451e..e54fca8 100644
--- a/e2e_tests/functional_test.cc
+++ b/e2e_tests/functional_test.cc
@@ -1273,6 +1273,28 @@
   EXPECT_EQ(1, CountSubstrs(std_err, "<<FixtureTest::~FixtureTest()>>"));
 }
 
+TEST_F(FuzzingModeTest, RpcSessionFuzzingSupportsFuzzingGrpcService) {
+  auto [status, std_out, std_err] = RunBinaryWith(
+      BinaryPath("testdata/fuzz_tests_for_rpc_session_grpc_functional_testing"),
+      "--fuzz=MiniBloggerGrpcTest."
+      "ServiceDoesNotCrashWithAnyRpcSequence",
+      {}, /*timeout=*/absl::Seconds(60));
+  EXPECT_THAT(std_err, Not(HasSubstr("Failed to execute !")));
+  EXPECT_THAT(std_err, HasSubstr("Using an inactive session id!"));
+  EXPECT_THAT(status, Eq(Signal(SIGABRT)));
+}
+
+TEST_F(FuzzingModeTest, RpcSessionOfForGrpcFindsStatefulBugInFuzzingMode) {
+  auto [status, std_out, std_err] = RunBinaryWith(
+      BinaryPath("testdata/fuzz_tests_for_rpc_session_grpc_functional_testing"),
+      "--fuzz=MiniBloggerGrpcTest."
+      "TestRpcSessionOfSetup",
+      {{"FUZZTEST_MAX_FUZZING_RUNS", "-1"}}, /*timeout=*/absl::Seconds(60));
+  EXPECT_THAT(std_err, Not(HasSubstr("Failed to execute !")));
+  EXPECT_THAT(std_err, HasSubstr("Using an inactive session id!"));
+  EXPECT_THAT(status, Eq(Signal(SIGABRT)));
+}
+
 TEST_F(FuzzingModeTest,
        GoogleTestPerIterationFixtureInstantiatedOncePerIteration) {
   auto [status, std_out, std_err] = RunWith(
diff --git a/e2e_tests/testdata/BUILD b/e2e_tests/testdata/BUILD
index cd3896e..6cc5059 100644
--- a/e2e_tests/testdata/BUILD
+++ b/e2e_tests/testdata/BUILD
@@ -59,6 +59,26 @@
     ],
 )
 
+cc_binary(
+    name = "fuzz_tests_for_rpc_session_grpc_functional_testing",
+    testonly = 1,
+    srcs = [
+        "fuzz_tests_for_rpc_session_grpc_functional_testing.cc",
+    ],
+    deps = [
+        "@com_github_grpc_grpc//:grpc++",
+        "@com_google_absl//absl/status",
+        "@com_google_absl//absl/strings",
+        "@com_google_fuzztest//fuzztest",
+        "@com_google_fuzztest//fuzztest:fuzztest_gtest_main",
+        "@com_google_fuzztest//rpc_fuzzing:grpc_stub",
+        "@com_google_fuzztest//rpc_fuzzing:rpc_session",
+        "@com_google_fuzztest//rpc_fuzzing/testdata:mini_blogger_cc_grpc_proto",
+        "@com_google_fuzztest//rpc_fuzzing/testdata:mini_blogger_cc_proto",
+        "@com_google_fuzztest//rpc_fuzzing/testdata:mini_blogger_grpc_service",
+    ],
+)
+
 # Must be a separate binary, as the invalid seeds make the binary crash at
 # process start time. Used by `functional_test` only.
 cc_binary(
diff --git a/e2e_tests/testdata/fuzz_tests_for_rpc_session_grpc_functional_testing.cc b/e2e_tests/testdata/fuzz_tests_for_rpc_session_grpc_functional_testing.cc
new file mode 100644
index 0000000..fc4f295
--- /dev/null
+++ b/e2e_tests/testdata/fuzz_tests_for_rpc_session_grpc_functional_testing.cc
@@ -0,0 +1,76 @@
+// Copyright 2023 Google LLC
+//
+// 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.
+
+// Rpc session fuzz test examples to be used for e2e functional testing.
+//
+// Specifically, used by `functional_test` only.
+
+#include <cstdlib>
+#include <iostream>
+#include <memory>
+
+#include "absl/status/status.h"
+#include "absl/strings/string_view.h"
+#include "./fuzztest/fuzztest.h"
+#include "./rpc_fuzzing/grpc_stub.h"
+#include "./rpc_fuzzing/rpc_session.h"
+#include "./rpc_fuzzing/testdata/grpc/mini_blogger_service.h"
+#include "./rpc_fuzzing/testdata/mini_blogger.grpc.pb.h"
+#include "grpcpp//security/server_credentials.h"
+#include "grpcpp//server.h"
+#include "grpcpp//server_builder.h"
+#include "grpcpp//support/channel_arguments.h"
+
+namespace {
+
+std::string_view ServiceNameFactory() {
+  return "fuzztest.internal.MiniBlogger";
+}
+
+class MiniBloggerGrpcTest {
+ public:
+  MiniBloggerGrpcTest()
+      : server_(grpc::ServerBuilder()
+                    .RegisterService(&mini_blogger_service_)
+                    .BuildAndStart()),
+        stub_(server_->InProcessChannel(grpc::ChannelArguments())) {}
+
+  ~MiniBloggerGrpcTest() { server_->Shutdown(); }
+
+  void ServiceDoesNotCrashWithAnyRpcSequence(
+      fuzztest::RpcSequence rpc_sequence) {
+    absl::Status status = fuzztest::ExecuteRpcSequence(stub_, rpc_sequence);
+    if (!status.ok()) {
+      std::cerr << "Failed to execute !" << status.message() << "\n";
+      std::abort();
+    }
+  }
+
+  void TestRpcSessionOfSetup(fuzztest::RpcSequence rpc_sequence) {
+    ServiceDoesNotCrashWithAnyRpcSequence(rpc_sequence);
+  }
+
+ private:
+  fuzztest::internal::MiniBloggerGrpcService mini_blogger_service_;
+  std::unique_ptr<grpc::Server> server_ = nullptr;
+  fuzztest::GrpcStub stub_;
+};
+
+FUZZ_TEST_F(MiniBloggerGrpcTest, ServiceDoesNotCrashWithAnyRpcSequence)
+    .WithDomains(fuzztest::RpcSession<fuzztest::internal::MiniBlogger>());
+
+FUZZ_TEST_F(MiniBloggerGrpcTest, TestRpcSessionOfSetup)
+    .WithDomains(fuzztest::RpcSessionOf(ServiceNameFactory));
+
+}  // namespace
diff --git a/fuzztest/BUILD b/fuzztest/BUILD
index 041e9cf..e75c0c9 100644
--- a/fuzztest/BUILD
+++ b/fuzztest/BUILD
@@ -128,6 +128,7 @@
     visibility = [
         "@com_google_fuzztest//centipede:__subpackages__",
         "@com_google_fuzztest//domain_tests:__pkg__",
+        "@com_google_fuzztest//rpc_fuzzing:__pkg__",
     ],
     deps = [
         ":absl_helpers",
diff --git a/rpc_fuzzing/BUILD b/rpc_fuzzing/BUILD
new file mode 100644
index 0000000..1940b24
--- /dev/null
+++ b/rpc_fuzzing/BUILD
@@ -0,0 +1,221 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+package(default_visibility = [
+    "@com_google_fuzztest//e2e_tests/testdata:__pkg__",
+    "@com_google_fuzztest//fuzztest:__subpackages__",
+])
+
+load("@bazel_skylib//rules:common_settings.bzl", "bool_flag")
+
+bool_flag(
+    name = "enable_rpc_fuzzing",
+    build_setting_default = False,
+    visibility = ["//visibility:public"],
+)
+
+config_setting(
+    name = "rpc_fuzzing_is_enabled",
+    flag_values = {
+        ":enable_rpc_fuzzing": "true",
+    },
+)
+
+cc_library(
+    name = "proto_field_path",
+    srcs = ["proto_field_path.cc"],
+    hdrs = ["proto_field_path.h"],
+    target_compatible_with = select({
+        ":rpc_fuzzing_is_enabled": [],
+        "//conditions:default": ["@platforms//:incompatible"],
+    }),
+    deps = [
+        "@com_google_absl//absl/container:flat_hash_set",
+        "@com_google_absl//absl/strings",
+        "@com_google_fuzztest//fuzztest:logging",
+        "@com_google_protobuf//:protobuf",
+    ],
+)
+
+cc_test(
+    name = "proto_field_path_test",
+    srcs = ["proto_field_path_test.cc"],
+    deps = [
+        ":proto_field_path",
+        "@com_google_fuzztest//fuzztest:test_protobuf_cc_proto",
+        "@com_google_fuzztest//rpc_fuzzing/testdata:mini_blogger_cc_proto",
+        "@com_google_googletest//:gtest_main",
+        "@com_google_protobuf//:protobuf",
+    ],
+)
+
+cc_library(
+    name = "rpc_sequence",
+    srcs = ["rpc_sequence.cc"],
+    hdrs = ["rpc_sequence.h"],
+    deps = [
+        ":proto_field_path",
+        "@com_google_absl//absl/container:flat_hash_map",
+        "@com_google_absl//absl/container:flat_hash_set",
+        "@com_google_absl//absl/memory",
+        "@com_google_absl//absl/random",
+        "@com_google_absl//absl/random:bit_gen_ref",
+        "@com_google_fuzztest//fuzztest:logging",
+        "@com_google_protobuf//:protobuf",
+    ],
+)
+
+cc_test(
+    name = "rpc_sequence_test",
+    srcs = ["rpc_sequence_test.cc"],
+    deps = [
+        ":proto_field_path",
+        ":rpc_sequence",
+        "@com_google_absl//absl/container:flat_hash_set",
+        "@com_google_absl//absl/log:check",
+        "@com_google_fuzztest//rpc_fuzzing/testdata:mini_blogger_cc_proto",
+        "@com_google_googletest//:gtest_main",
+        "@com_google_protobuf//:protobuf",
+    ],
+)
+
+# TODO(changochen): Combine the stubs into a single lib.
+cc_library(
+    name = "rpc_stub",
+    hdrs = ["rpc_stub.h"],
+    deps = [
+        "@com_google_absl//absl/status:statusor",
+        "@com_google_protobuf//:protobuf",
+    ],
+)
+
+cc_library(
+    name = "grpc_stub",
+    srcs = ["grpc_stub.cc"],
+    hdrs = ["grpc_stub.h"],
+    deps = [
+        ":rpc_stub",
+        "@com_github_grpc_grpc//:grpc++",
+        "@com_github_grpc_grpc//:grpc++_codegen_proto",
+        "@com_google_absl//absl/memory",
+        "@com_google_absl//absl/status",
+        "@com_google_absl//absl/status:statusor",
+        "@com_google_absl//absl/strings",
+        "@com_google_absl//absl/synchronization",
+        "@com_google_protobuf//:protobuf",
+    ],
+)
+
+cc_library(
+    name = "rpc_executor",
+    srcs = ["rpc_executor.cc"],
+    hdrs = ["rpc_executor.h"],
+    deps = [
+        ":rpc_sequence",
+        ":rpc_stub",
+        "@com_google_absl//absl/log:die_if_null",
+        "@com_google_absl//absl/status",
+        "@com_google_absl//absl/status:statusor",
+        "@com_google_absl//absl/strings",
+        "@com_google_fuzztest//fuzztest:logging",
+        "@com_google_protobuf//:protobuf",
+    ],
+)
+
+cc_test(
+    name = "rpc_executor_test",
+    srcs = ["rpc_executor_test.cc"],
+    deps = [
+        ":grpc_stub",
+        ":proto_field_path",
+        ":rpc_executor",
+        ":rpc_sequence",
+        "@com_github_grpc_grpc//:grpc++",
+        "@com_google_absl//absl/log:check",
+        "@com_google_absl//absl/strings",
+        "@com_google_fuzztest//domain_tests:domain_testing",
+        "@com_google_fuzztest//rpc_fuzzing/testdata:mini_blogger_cc_proto",
+        "@com_google_fuzztest//rpc_fuzzing/testdata:mini_blogger_grpc_service",
+        "@com_google_googletest//:gtest_main",
+        "@com_google_protobuf//:protobuf",
+    ],
+)
+
+cc_library(
+    name = "rpc_potential_dfg",
+    srcs = ["rpc_potential_dfg.cc"],
+    hdrs = ["rpc_potential_dfg.h"],
+    deps = [
+        ":proto_field_path",
+        "@com_google_absl//absl/container:flat_hash_map",
+        "@com_google_absl//absl/strings",
+        "@com_google_fuzztest//fuzztest:logging",
+        "@com_google_protobuf//:protobuf",
+    ],
+)
+
+cc_test(
+    name = "rpc_potential_dfg_test",
+    srcs = ["rpc_potential_dfg_test.cc"],
+    deps = [
+        ":proto_field_path",
+        ":rpc_potential_dfg",
+        "@com_github_grpc_grpc//:grpc++",
+        "@com_google_fuzztest//rpc_fuzzing/testdata:mini_blogger_cc_grpc_proto",
+        "@com_google_fuzztest//rpc_fuzzing/testdata:mini_blogger_cc_proto",
+        "@com_google_googletest//:gtest_main",
+        "@com_google_protobuf//:protobuf",
+    ],
+)
+
+cc_library(
+    name = "rpc_session",
+    hdrs = ["rpc_session.h"],
+    deps = [
+        ":proto_field_path",
+        ":rpc_executor",
+        ":rpc_potential_dfg",
+        ":rpc_sequence",
+        "@com_google_absl//absl/container:flat_hash_map",
+        "@com_google_absl//absl/random:bit_gen_ref",
+        "@com_google_absl//absl/status",
+        "@com_google_absl//absl/strings",
+        "@com_google_fuzztest//fuzztest:domain",
+        "@com_google_fuzztest//fuzztest:domain_core",
+        "@com_google_fuzztest//fuzztest:logging",
+        "@com_google_fuzztest//fuzztest:serialization",
+        "@com_google_fuzztest//fuzztest:table_of_recent_compares",
+        "@com_google_fuzztest//fuzztest:type_support",
+        "@com_google_protobuf//:protobuf",
+    ],
+)
+
+cc_test(
+    name = "rpc_session_test",
+    srcs = ["rpc_session_test.cc"],
+    deps = [
+        ":proto_field_path",
+        ":rpc_potential_dfg",
+        ":rpc_sequence",
+        ":rpc_session",
+        "@com_github_grpc_grpc//:grpc++",
+        "@com_google_absl//absl/random",
+        "@com_google_fuzztest//domain_tests:domain_testing",
+        "@com_google_fuzztest//fuzztest:serialization",
+        "@com_google_fuzztest//rpc_fuzzing/testdata:mini_blogger_cc_grpc_proto",
+        "@com_google_fuzztest//rpc_fuzzing/testdata:mini_blogger_cc_proto",
+        "@com_google_googletest//:gtest_main",
+        "@com_google_protobuf//:protobuf",
+    ],
+)
diff --git a/rpc_fuzzing/grpc_stub.cc b/rpc_fuzzing/grpc_stub.cc
new file mode 100644
index 0000000..fa4a954
--- /dev/null
+++ b/rpc_fuzzing/grpc_stub.cc
@@ -0,0 +1,57 @@
+#include "./rpc_fuzzing/grpc_stub.h"
+
+#include <memory>
+
+#include "absl/memory/memory.h"
+#include "absl/status/status.h"
+#include "absl/status/statusor.h"
+#include "absl/strings/str_cat.h"
+#include "absl/synchronization/notification.h"
+#include "grpcpp//impl/proto_utils.h"
+#include "google/protobuf/descriptor.h"
+#include "google/protobuf/message.h"
+#include "grpcpp//client_context.h"
+#include "grpcpp//generic/generic_stub.h"
+#include "grpcpp//support/status.h"
+#include "grpcpp//support/stub_options.h"
+
+namespace fuzztest {
+
+absl::StatusOr<std::unique_ptr<google::protobuf::Message>> GrpcStub::CallMethod(
+    const google::protobuf::MethodDescriptor& method_descriptor,
+    const google::protobuf::Message& request) {
+  grpc::ClientContext cli_ctx;
+  const google::protobuf::Message* response_prototype =
+      google::protobuf::MessageFactory::generated_factory()->GetPrototype(
+          method_descriptor.output_type());
+  if (!response_prototype) {
+    return absl::InternalError(
+        absl::StrCat("Cannot find prototype for ",
+                     method_descriptor.output_type()->full_name(),
+                     " in the generated proto MessageFactory"));
+  }
+  std::unique_ptr<google::protobuf::Message> response =
+      absl::WrapUnique(response_prototype->New());
+  absl::Notification notification;
+  absl::Status status = absl::OkStatus();
+  grpc_stub_->UnaryCall(
+      &cli_ctx,
+      absl::StrCat("/", method_descriptor.service()->full_name(), "/",
+                   method_descriptor.name()),
+      grpc::StubOptions(), &request, response.get(),
+      [&notification, &status](grpc::Status s) {
+        // TODO(changochen): Better conversion from grpc::Status to absl::Status.
+        if (!s.ok()) {
+          status = absl::InternalError(s.error_message());
+        }
+        notification.Notify();
+      });
+  notification.WaitForNotification();
+  if (status.ok()) {
+    return response;
+  } else {
+    return status;
+  }
+}
+
+}  // namespace fuzztest
diff --git a/rpc_fuzzing/grpc_stub.h b/rpc_fuzzing/grpc_stub.h
new file mode 100644
index 0000000..1e046b0
--- /dev/null
+++ b/rpc_fuzzing/grpc_stub.h
@@ -0,0 +1,32 @@
+#ifndef FUZZTEST_RPC_FUZZING_GRPC_STUB_H_
+#define FUZZTEST_RPC_FUZZING_GRPC_STUB_H_
+#include <memory>
+
+#include "google/protobuf/message.h"
+#include "absl/status/statusor.h"
+#include "./rpc_fuzzing/rpc_stub.h"
+#include "grpcpp//generic/generic_stub.h"
+
+namespace fuzztest {
+
+using GrpcGenericStub =
+    grpc::TemplatedGenericStub<google::protobuf::Message, google::protobuf::Message>;
+
+class GrpcStub : public RpcStub {
+ public:
+  GrpcStub(std::shared_ptr<grpc::ChannelInterface> channel)
+      : grpc_stub_(
+            std::make_unique<
+                grpc::TemplatedGenericStub<google::protobuf::Message, google::protobuf::Message>>(
+                channel)) {}
+  absl::StatusOr<std::unique_ptr<google::protobuf::Message>> CallMethod(
+      const google::protobuf::MethodDescriptor& method_descriptor,
+      const google::protobuf::Message& request) override;
+
+ private:
+  std::unique_ptr<GrpcGenericStub> grpc_stub_ = nullptr;
+};
+
+}  // namespace fuzztest
+
+#endif  // FUZZTEST_RPC_FUZZING_GRPC_STUB_H_
diff --git a/rpc_fuzzing/proto_field_path.cc b/rpc_fuzzing/proto_field_path.cc
new file mode 100644
index 0000000..b1d7b37
--- /dev/null
+++ b/rpc_fuzzing/proto_field_path.cc
@@ -0,0 +1,313 @@
+// Copyright 2023 Google LLC
+//
+// 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.
+
+#include "./rpc_fuzzing/proto_field_path.h"
+
+#include "google/protobuf/descriptor.h"
+#include "google/protobuf/message.h"
+#include "absl/container/flat_hash_set.h"
+#include "absl/strings/str_join.h"
+#include "absl/strings/str_split.h"
+#include "./fuzztest/internal/logging.h"
+
+namespace fuzztest::internal {
+
+bool Contains(const google::protobuf::FieldDescriptor& parent,
+              const google::protobuf::FieldDescriptor& child) {
+  return parent.message_type() == child.containing_type();
+}
+
+bool operator==(const FieldPath& lhs, const FieldPath& rhs) {
+  return lhs.field_descriptors_ == rhs.field_descriptors_;
+}
+
+bool FieldPath::CanAppend(const google::protobuf::FieldDescriptor& field) const {
+  return field_descriptors_.empty() ||
+         Contains(*field_descriptors_.back(), field);
+}
+
+void FieldPath::AppendField(const google::protobuf::FieldDescriptor& field) {
+  FUZZTEST_INTERNAL_CHECK(
+      CanAppend(field),
+      "The current last field in the path must be a message containing the "
+      "appended field.");
+  field_descriptors_.push_back(&field);
+}
+
+void FieldPath::AppendPath(const FieldPath& other) {
+  if (other.field_descriptors_.empty()) {
+    return;
+  }
+  field_descriptors_.reserve(field_descriptors_.size() +
+                             other.field_descriptors_.size());
+  FUZZTEST_INTERNAL_CHECK(
+      CanAppend(*other.GetAllFields()[0]),
+      "The current last field in the path must be a message containing the "
+      "first field in the other path.");
+  field_descriptors_.insert(field_descriptors_.end(),
+                            other.field_descriptors_.begin(),
+                            other.field_descriptors_.end());
+}
+
+std::string FieldPath::ToString() const {
+  return absl::StrJoin(
+      field_descriptors_, /*separator=*/".",
+      [](std::string* result, const google::protobuf::FieldDescriptor* field) {
+        absl::StrAppend(result, field->name());
+      });
+}
+
+FieldPath GetFieldPathWithDescriptor(const google::protobuf::Descriptor& descriptor,
+                                     std::string_view field_path_str) {
+  std::vector<std::string> parts = absl::StrSplit(field_path_str, '.');
+  FUZZTEST_INTERNAL_CHECK(!parts.empty(), "Invalid path string!");
+
+  FieldPath result;
+  const google::protobuf::Descriptor* descriptor_ptr = &descriptor;
+
+  for (const std::string& part : parts) {
+    const google::protobuf::FieldDescriptor* field =
+        descriptor_ptr->FindFieldByName(part);
+    FUZZTEST_INTERNAL_CHECK(field != nullptr, "Invalid field name!");
+    result.AppendField(*field);
+    descriptor_ptr = field->message_type();
+  }
+  return result;
+}
+
+const google::protobuf::Message* FieldPath::GetContainingMessageOfLastField(
+    const google::protobuf::Message& message) const {
+  FUZZTEST_INTERNAL_CHECK_PRECONDITION(!field_descriptors_.empty(),
+                                       "Empty field path!");
+  const google::protobuf::Message* parent = &message;
+  for (size_t i = 0; i < field_descriptors_.size() - 1; ++i) {
+    if (field_descriptors_[i]->is_repeated()) {
+      if (parent->GetReflection()->FieldSize(*parent, field_descriptors_[i]) ==
+          0) {
+        return nullptr;
+      }
+      parent = &(parent->GetReflection()->GetRepeatedMessage(
+          *parent, field_descriptors_[i], 0));
+    } else {
+      parent = &(
+          parent->GetReflection()->GetMessage(*parent, field_descriptors_[i]));
+    }
+  }
+  return parent;
+}
+
+google::protobuf::Message* FieldPath::MutableContainingMessageOfLastField(
+    google::protobuf::Message& message) const {
+  FUZZTEST_INTERNAL_CHECK_PRECONDITION(!field_descriptors_.empty(),
+                                       "Empty field path!");
+  google::protobuf::Message* parent = &message;
+  for (size_t i = 0; i < field_descriptors_.size() - 1; ++i) {
+    if (field_descriptors_[i]->is_repeated()) {
+      if (parent->GetReflection()->FieldSize(*parent, field_descriptors_[i]) ==
+          0) {
+        parent->GetReflection()->AddMessage(parent, field_descriptors_[i]);
+      }
+      parent = parent->GetReflection()->MutableRepeatedMessage(
+          parent, field_descriptors_[i], 0);
+    } else {
+      parent = parent->GetReflection()->MutableMessage(parent,
+                                                       field_descriptors_[i]);
+    }
+  }
+  return parent;
+}
+
+const google::protobuf::FieldDescriptor& FieldPath::GetLastField() const {
+  FUZZTEST_INTERNAL_CHECK_PRECONDITION(!field_descriptors_.empty(),
+                                       "Empty field path!");
+  return *field_descriptors_.back();
+}
+
+const std::vector<const google::protobuf::FieldDescriptor*>& FieldPath::GetAllFields()
+    const {
+  return field_descriptors_;
+}
+
+void CopyField(const FieldPath& from_field, const google::protobuf::Message& from,
+               const FieldPath& to_field, google::protobuf::Message& to) {
+  const google::protobuf::Message* from_inner_most_message =
+      from_field.GetContainingMessageOfLastField(from);
+  if (from_inner_most_message == nullptr) {
+    return;
+  }
+  const google::protobuf::FieldDescriptor& from_last_field = from_field.GetLastField();
+  google::protobuf::Message* to_inner_most_message =
+      to_field.MutableContainingMessageOfLastField(to);
+  if (to_inner_most_message == nullptr) {
+    return;
+  }
+  const google::protobuf::FieldDescriptor& to_last_field = to_field.GetLastField();
+
+  const google::protobuf::Reflection* from_refl =
+      from_inner_most_message->GetReflection();
+  const google::protobuf::Reflection* to_refl = to_inner_most_message->GetReflection();
+  FUZZTEST_INTERNAL_CHECK(from_last_field.type() == to_last_field.type(),
+                          "Fields of mismatch types cannot be copied!");
+  // TODO(changochen): We might make this condition optional.
+  FUZZTEST_INTERNAL_CHECK(from_last_field.name() == to_last_field.name(),
+                          "Fields of mismatch names cannot be copied!");
+  switch (from_last_field.type()) {
+#define HANDLE_TYPE(UPPERCASE, CAMEL)                                         \
+  case google::protobuf::FieldDescriptor::TYPE_##UPPERCASE:                             \
+    if (from_last_field.is_repeated() && to_last_field.is_repeated()) {       \
+      to_refl->ClearField(to_inner_most_message, &to_last_field);             \
+      for (int i = 0; i < from_refl->FieldSize(*from_inner_most_message,      \
+                                               &from_last_field);             \
+           ++i) {                                                             \
+        to_refl->Add##CAMEL(                                                  \
+            to_inner_most_message, &to_last_field,                            \
+            from_refl->GetRepeated##CAMEL(*from_inner_most_message,           \
+                                          &from_last_field, i));              \
+      }                                                                       \
+    } else if (from_last_field.is_repeated()) {                               \
+      if (from_refl->FieldSize(*from_inner_most_message, &from_last_field) == \
+          0) {                                                                \
+        to_refl->ClearField(to_inner_most_message, &to_last_field);           \
+      } else {                                                                \
+        to_refl->Set##CAMEL(                                                  \
+            to_inner_most_message, &to_last_field,                            \
+            from_refl->GetRepeated##CAMEL(*from_inner_most_message,           \
+                                          &from_last_field, 0));              \
+      }                                                                       \
+    } else if (to_last_field.is_repeated()) {                                 \
+      to_refl->ClearField(to_inner_most_message, &to_last_field);             \
+      to_refl->Add##CAMEL(                                                    \
+          to_inner_most_message, &to_last_field,                              \
+          from_refl->Get##CAMEL(*from_inner_most_message, &from_last_field)); \
+    } else {                                                                  \
+      to_refl->Set##CAMEL(                                                    \
+          to_inner_most_message, &to_last_field,                              \
+          from_refl->Get##CAMEL(*from_inner_most_message, &from_last_field)); \
+    }                                                                         \
+    break;
+
+    HANDLE_TYPE(DOUBLE, Double);
+    HANDLE_TYPE(FLOAT, Float);
+    HANDLE_TYPE(INT64, Int64);
+    HANDLE_TYPE(UINT64, UInt64);
+    HANDLE_TYPE(INT32, Int32);
+    HANDLE_TYPE(FIXED64, UInt64);
+    HANDLE_TYPE(FIXED32, UInt32);
+    HANDLE_TYPE(BOOL, Bool);
+    HANDLE_TYPE(STRING, String);
+    HANDLE_TYPE(BYTES, String);
+    HANDLE_TYPE(UINT32, UInt32);
+    HANDLE_TYPE(ENUM, Enum);
+    HANDLE_TYPE(SFIXED64, Int64);
+    HANDLE_TYPE(SFIXED32, Int32);
+    HANDLE_TYPE(SINT64, Int64);
+    HANDLE_TYPE(SINT32, Int32);
+
+#undef HANDLE_TYPE
+    case google::protobuf::FieldDescriptor::TYPE_GROUP:
+    case google::protobuf::FieldDescriptor::TYPE_MESSAGE:
+      if (from_last_field.is_repeated() && to_last_field.is_repeated()) {
+        to_refl->ClearField(to_inner_most_message, &to_last_field);
+        for (int i = 0; i < from_refl->FieldSize(*from_inner_most_message,
+                                                 &from_last_field);
+             ++i) {
+          to_refl->AddMessage(to_inner_most_message, &to_last_field)
+              ->CopyFrom(from_refl->GetRepeatedMessage(*from_inner_most_message,
+                                                       &from_last_field, i));
+        }
+      } else if (from_last_field.is_repeated()) {
+        if (from_refl->FieldSize(*from_inner_most_message, &from_last_field) ==
+            0) {
+          to_refl->ClearField(to_inner_most_message, &to_last_field);
+        } else {
+          to_refl->MutableMessage(to_inner_most_message, &to_last_field)
+              ->CopyFrom(from_refl->GetRepeatedMessage(*from_inner_most_message,
+                                                       &from_last_field, 0));
+        }
+      } else if (to_last_field.is_repeated()) {
+        to_refl->ClearField(to_inner_most_message, &to_last_field);
+        to_refl->AddMessage(to_inner_most_message, &to_last_field)
+            ->CopyFrom(from_refl->GetMessage(*from_inner_most_message,
+                                             &from_last_field));
+      } else {
+        to_refl->MutableMessage(to_inner_most_message, &to_last_field)
+            ->CopyFrom(from_refl->GetMessage(*from_inner_most_message,
+                                             &from_last_field));
+      }
+      break;
+    default:
+      FUZZTEST_INTERNAL_CHECK(
+          false, absl::StrCat("Unexpected type ", from_last_field.type_name()));
+  }
+}
+
+std::vector<FieldPath> CollectAllFieldsImpl(
+    const google::protobuf::Descriptor& message_descriptor,
+    absl::flat_hash_set<const google::protobuf::Descriptor*>& visited_messages) {
+  std::vector<FieldPath> results;
+  if (visited_messages.contains(&message_descriptor)) {
+    return results;
+  }
+  visited_messages.insert(&message_descriptor);
+  for (size_t i = 0; i < message_descriptor.field_count(); ++i) {
+    const google::protobuf::FieldDescriptor& field = *message_descriptor.field(i);
+    FieldPath field_path;
+    field_path.AppendField(field);
+    results.push_back(field_path);
+    // `GROUP` field is a deprecated way of expressing inner message.
+    if (field.type() == google::protobuf::FieldDescriptor::TYPE_MESSAGE ||
+        field.type() == google::protobuf::FieldDescriptor::TYPE_GROUP) {
+      const google::protobuf::Descriptor& inner = *field.message_type();
+      std::vector<FieldPath> inner_fields =
+          CollectAllFieldsImpl(inner, visited_messages);
+      for (const auto& loc : inner_fields) {
+        FieldPath inner_field_path(field_path);
+        inner_field_path.AppendPath(loc);
+        results.push_back(inner_field_path);
+      }
+    }
+  }
+  return results;
+}
+
+std::vector<FieldPath> CollectAllFields(
+    const google::protobuf::Descriptor& message_descriptor) {
+  absl::flat_hash_set<const google::protobuf::Descriptor*> visited_messages;
+  return CollectAllFieldsImpl(message_descriptor, visited_messages);
+}
+
+bool AreDifferentFieldsInSameOneOf(const google::protobuf::FieldDescriptor& a,
+                                   const google::protobuf::FieldDescriptor& b) {
+  if (&a == &b || a.containing_oneof() == nullptr ||
+      b.containing_oneof() == nullptr)
+    return false;
+  return a.containing_oneof() == b.containing_oneof();
+}
+
+bool AreOneOfAltearnatives(const FieldPath& a, const FieldPath& b) {
+  const std::vector<const google::protobuf::FieldDescriptor*>& a_fields =
+      a.GetAllFields();
+  const std::vector<const google::protobuf::FieldDescriptor*>& b_fields =
+      b.GetAllFields();
+  for (size_t i = 0; i < std::min(a_fields.size(), b_fields.size()); ++i) {
+    const google::protobuf::FieldDescriptor& a_field = *a_fields[i];
+    const google::protobuf::FieldDescriptor& b_field = *b_fields[i];
+    if (AreDifferentFieldsInSameOneOf(a_field, b_field)) {
+      return true;
+    }
+  }
+  return false;
+}
+
+}  // namespace fuzztest::internal
diff --git a/rpc_fuzzing/proto_field_path.h b/rpc_fuzzing/proto_field_path.h
new file mode 100644
index 0000000..1042fe6
--- /dev/null
+++ b/rpc_fuzzing/proto_field_path.h
@@ -0,0 +1,120 @@
+// Copyright 2023 Google LLC
+//
+// 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.
+
+#ifndef FUZZTEST_RPC_FUZZING_PROTO_FIELD_PATH_H_
+#define FUZZTEST_RPC_FUZZING_PROTO_FIELD_PATH_H_
+
+#include <vector>
+
+#include "google/protobuf/descriptor.h"
+#include "google/protobuf/message.h"
+
+namespace fuzztest::internal {
+
+// Represent a path to a (sub)field in a message.
+class FieldPath {
+ public:
+  // Append a field as a part to the path.
+  void AppendField(const google::protobuf::FieldDescriptor& field);
+  // Append another path to the path.
+  void AppendPath(const FieldPath& other);
+
+  // Given a message, follow the field path and return the parent message of the
+  // last field. If any repeated field in the middle is empty, return nullptr
+  const google::protobuf::Message* GetContainingMessageOfLastField(
+      const google::protobuf::Message& message) const;
+  // The mutable version fills in empty fields along the path, and thus never
+  // returns a null pointer.
+  google::protobuf::Message* MutableContainingMessageOfLastField(
+      google::protobuf::Message& message) const;
+
+  // Get the last field in the path.
+  const google::protobuf::FieldDescriptor& GetLastField() const;
+  // Get all the parts of the path.
+  const std::vector<const google::protobuf::FieldDescriptor*>& GetAllFields() const;
+
+  // Returns a string concatenating all the field parts by ".". For example, a
+  // field path with two fields "field1" and "field2" will return
+  // "field1.field2".
+  std::string ToString() const;
+
+ private:
+  bool CanAppend(const google::protobuf::FieldDescriptor& field) const;
+
+  // Make FieldPath hashable.
+  template <typename H>
+  friend H AbslHashValue(H h, const FieldPath& m) {
+    h = H::combine_contiguous(std::move(h), m.field_descriptors_.data(),
+                              m.field_descriptors_.size());
+    return h;
+  }
+  friend bool operator==(const FieldPath& lhs, const FieldPath& rhs);
+
+  // A list of fields that allow us to locate inner fields.
+  std::vector<const google::protobuf::FieldDescriptor*> field_descriptors_;
+};
+
+bool operator==(const FieldPath& lhs, const FieldPath& rhs);
+
+// Utility function for copying fields in different proto messages, the fields
+// should have the same names and types, otherwise an assertion will be
+// triggered. If the source field is repeated but the sink is not, we copy the
+// first element of the source. If the source field is not repeated but the sink
+// is, we clear the sink and copy the source as the first element of the sink.
+// TODO(changochen): Make the condition of same name optional.
+// TODO(changochen): Select random element of a repeated fields for copying.
+void CopyField(const FieldPath& from_field, const google::protobuf::Message& from,
+               const FieldPath& to_field, google::protobuf::Message& to);
+
+FieldPath GetFieldPathWithDescriptor(const google::protobuf::Descriptor& descriptor,
+                                     std::string_view field_path_str);
+
+// Get a field path in a message from a path string. The path string should be
+// in format like "field1.subfield2.subfield3.xxx".
+// TODO(changochen): Return absl::StatusOr<FieldPath> instead of aborting. Make
+// this a constructor of field path.
+template <typename MessageT>
+FieldPath GetFieldPath(std::string_view field_path_str) {
+  const google::protobuf::Descriptor* descriptor = MessageT::GetDescriptor();
+  return GetFieldPathWithDescriptor(*descriptor, field_path_str);
+}
+
+// Utility function for collecting all (sub)fields in message type. If the
+// message type is recursive, then we only collect fields in a message when we
+// see its type for the first time.
+std::vector<FieldPath> CollectAllFields(
+    const google::protobuf::Descriptor& message_descriptor);
+
+// Checks whether two fields path are alternatives to each other (i.e., within
+// the same oneof group). If yes, only at most one of them should be set at any
+// given time.
+// For example, the following message: message Message {
+//    oneof test_one_of {
+//        Message1 a = 1;
+//        int b = 2;
+//    }
+// }
+//
+// message Message1 {
+//   int a1 = 1;
+//   int a2 = 2;
+// }
+//
+// We consider that a.a1 and b are within the same oneof, therefore
+// "alternatives", while a.a1 and a.a2 are not.
+bool AreOneOfAltearnatives(const FieldPath& a, const FieldPath& b);
+
+}  // namespace fuzztest::internal
+
+#endif  // FUZZTEST_RPC_FUZZING_PROTO_FIELD_PATH_H_
diff --git a/rpc_fuzzing/proto_field_path_test.cc b/rpc_fuzzing/proto_field_path_test.cc
new file mode 100644
index 0000000..c00801a
--- /dev/null
+++ b/rpc_fuzzing/proto_field_path_test.cc
@@ -0,0 +1,514 @@
+// Copyright 2023 Google LLC
+//
+// 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.
+
+#include "./rpc_fuzzing/proto_field_path.h"
+
+#include <cstdint>
+#include <vector>
+
+#include "google/protobuf/descriptor.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "./fuzztest/internal/test_protobuf.pb.h"
+#include "./rpc_fuzzing/testdata/mini_blogger.pb.h"
+
+namespace fuzztest::internal {
+
+using ::testing::ElementsAre;
+using ::testing::IsNull;
+using ::testing::NotNull;
+using ::testing::UnorderedElementsAre;
+
+namespace {
+
+TEST(FieldPathTest, GetFieldPathParseStringIntoAFieldPath) {
+  const FieldPath field_path =
+      GetFieldPath<LogOutUserRequest>("log_out_info.session_info.session_id");
+  EXPECT_THAT(
+      field_path.GetAllFields(),
+      ElementsAre(
+          LogOutUserRequest::GetDescriptor()->FindFieldByName("log_out_info"),
+          LogOutInfo::GetDescriptor()->FindFieldByName("session_info"),
+          SessionInfo::GetDescriptor()->FindFieldByName("session_id")));
+}
+
+TEST(FieldPathTest, GetFieldPathAbortsAtInvalidField) {
+  EXPECT_DEATH_IF_SUPPORTED(
+      GetFieldPath<LogOutUserRequest>("log_out_info.session_info.invalid"),
+      "Invalid field name!");
+}
+
+MATCHER_P(FieldPathAsString, path_str, "") {
+  if (arg.GetAllFields().empty()) return path_str == "";
+  std::string result;
+  for (const google::protobuf::FieldDescriptor* part : arg.GetAllFields()) {
+    absl::StrAppend(&result, part->name(), ".");
+  }
+  return result.substr(0, result.size() - 1) == path_str;
+}
+
+TEST(FieldPathTest, HashAndEqualityCheck) {
+  const FieldPath path1 = GetFieldPath<LogInUserResponse>("session_id");
+  const FieldPath path2 = GetFieldPath<LogInUserResponse>("session_id");
+  EXPECT_EQ(path1, path2);
+}
+
+TEST(FieldPathTest, CopyFieldOfSameTypeAndSameNameInMessagesSucceeds) {
+  // Test copying LogInUserResponse.session_id to
+  // LogOutUserRequest.log_out_info.session_info.session_id.
+  LogInUserResponse log_in_response;
+  const FieldPath session_id_source_path =
+      GetFieldPath<LogInUserResponse>("session_id");
+  LogOutUserRequest log_out_request;
+  const FieldPath session_id_sink_path =
+      GetFieldPath<LogOutUserRequest>("log_out_info.session_info.session_id");
+
+  constexpr int64_t kSessionId = 0xdeadbeef;
+  log_in_response.set_session_id(kSessionId);
+  CopyField(session_id_source_path, log_in_response, session_id_sink_path,
+            log_out_request);
+
+  EXPECT_EQ(kSessionId,
+            log_out_request.log_out_info().session_info().session_id());
+}
+
+TEST(FieldPathTest, CopyingRepeatedFieldToNonRepeatedCopiesTheFirstElement) {
+  // Copy repeated `{val1, val2}` to non repeated `val2` results in `val2`.
+  constexpr int64_t kValue1 = 123;
+  constexpr int64_t kValue2 = 234;
+  MessageContainingRepeatedFields message_containing_repeated_fields;
+  message_containing_repeated_fields.add_field(kValue1);
+  message_containing_repeated_fields.add_field(kValue2);
+  const FieldPath source_path =
+      GetFieldPath<MessageContainingRepeatedFields>("field");
+  MessageNotContainingRepeatedFields message_not_containing_repeated_fields;
+  message_not_containing_repeated_fields.set_field(kValue2);
+  const FieldPath sink_path =
+      GetFieldPath<MessageNotContainingRepeatedFields>("field");
+
+  CopyField(source_path, message_containing_repeated_fields, sink_path,
+            message_not_containing_repeated_fields);
+
+  MessageNotContainingRepeatedFields expected_result;
+  expected_result.set_field(kValue1);
+  EXPECT_EQ(message_not_containing_repeated_fields.DebugString(),
+            expected_result.DebugString());
+}
+
+TEST(FieldPathTest,
+     CopyFieldDoesNothingIfSourceContainsEmptyRepeatedFieldInTheMiddle) {
+  MessageContainingRepeatedFields message_containing_repeated_fields;
+  const FieldPath source_path = GetFieldPath<MessageContainingRepeatedFields>(
+      "repeated_msg_field.field2");
+  MessageNotContainingRepeatedFields message_not_containing_repeated_fields;
+  message_not_containing_repeated_fields.set_field2(123);
+  const MessageNotContainingRepeatedFields expected_result =
+      message_not_containing_repeated_fields;
+
+  const FieldPath sink_path =
+      GetFieldPath<MessageNotContainingRepeatedFields>("field2");
+
+  CopyField(source_path, message_containing_repeated_fields, sink_path,
+            message_not_containing_repeated_fields);
+  EXPECT_EQ(message_not_containing_repeated_fields.DebugString(),
+            expected_result.DebugString());
+}
+
+TEST(FieldPathTest,
+     CopyFieldSucceedsIfSinkContainEmptyRepeatedFieldInTheMiddle) {
+  constexpr int64_t kValue1 = 123;
+  MessageContainingRepeatedFields message_containing_repeated_fields;
+  const FieldPath sink_path = GetFieldPath<MessageContainingRepeatedFields>(
+      "repeated_msg_field.field2");
+  MessageNotContainingRepeatedFields message_not_containing_repeated_fields;
+  message_not_containing_repeated_fields.set_field2(kValue1);
+
+  const FieldPath source_path =
+      GetFieldPath<MessageNotContainingRepeatedFields>("field2");
+
+  CopyField(source_path, message_not_containing_repeated_fields, sink_path,
+            message_containing_repeated_fields);
+  MessageContainingRepeatedFields expected_result;
+  expected_result.add_repeated_msg_field()->set_field2(kValue1);
+  EXPECT_EQ(message_containing_repeated_fields.DebugString(),
+            expected_result.DebugString());
+}
+
+TEST(FieldPathTest, CopyingEmptyRepeatedFieldToNonRepeatedClearsTheField) {
+  // Copy empty repeated `{}` to non repeated `val2` results in cleared field.
+  MessageContainingRepeatedFields message_containing_repeated_fields;
+  const FieldPath source_path =
+      GetFieldPath<MessageContainingRepeatedFields>("field");
+  MessageNotContainingRepeatedFields message_not_containing_repeated_fields;
+  message_not_containing_repeated_fields.set_field(123);
+  const FieldPath sink_path =
+      GetFieldPath<MessageNotContainingRepeatedFields>("field");
+
+  CopyField(source_path, message_containing_repeated_fields, sink_path,
+            message_not_containing_repeated_fields);
+
+  EXPECT_EQ(message_not_containing_repeated_fields.DebugString(),
+            MessageNotContainingRepeatedFields{}.DebugString());
+}
+
+TEST(FieldPathTest, CopyingNonRepeatedFieldToRepeatedFieldSetsFirstElement) {
+  // Copy non repeated `val2` to repeated `{val1, val2}` results in `{val2}`.
+  constexpr int64_t kValue1 = 123;
+  constexpr int64_t kValue2 = 234;
+  MessageNotContainingRepeatedFields message_not_containing_repeated_fields;
+  message_not_containing_repeated_fields.set_field(kValue2);
+  const FieldPath source_path =
+      GetFieldPath<MessageNotContainingRepeatedFields>("field");
+
+  MessageContainingRepeatedFields message_containing_repeated_fields;
+  message_containing_repeated_fields.add_field(kValue1);
+  message_containing_repeated_fields.add_field(kValue2);
+  const FieldPath sink_path =
+      GetFieldPath<MessageContainingRepeatedFields>("field");
+
+  CopyField(source_path, message_not_containing_repeated_fields, sink_path,
+            message_containing_repeated_fields);
+
+  MessageContainingRepeatedFields expected_result;
+  expected_result.add_field(kValue2);
+  EXPECT_EQ(message_containing_repeated_fields.DebugString(),
+            expected_result.DebugString());
+}
+
+TEST(FieldPathTest, CopyingRepeatedFieldHandlesFieldOfMessageTypeCorrectly) {
+  MessageContainingRepeatedFields source_message, sink_message;
+  source_message.add_repeated_msg_field()->set_field2(0x123);
+  source_message.add_repeated_msg_field()->set_field2(0x234);
+  const FieldPath field_path =
+      GetFieldPath<MessageContainingRepeatedFields>("repeated_msg_field");
+
+  CopyField(field_path, source_message, field_path, sink_message);
+
+  EXPECT_EQ(sink_message.DebugString(), source_message.DebugString());
+}
+
+TEST(FieldPathTest,
+     CopyingSIngularSrcToRepeatedDstHandlesFieldOfMessageTypeCorrectly) {
+  MessageWithSingleInnerRepeated source_message;
+  source_message.mutable_repeated_msg_field()->set_field2(123);
+  MessageContainingRepeatedFields sink_message;
+  sink_message.add_repeated_msg_field()->set_field2(1);
+  sink_message.add_repeated_msg_field()->set_field2(1);
+  const FieldPath sink_field_path =
+      GetFieldPath<MessageContainingRepeatedFields>("repeated_msg_field");
+  const FieldPath source_field_path =
+      GetFieldPath<MessageWithSingleInnerRepeated>("repeated_msg_field");
+
+  CopyField(source_field_path, source_message, sink_field_path, sink_message);
+
+  MessageContainingRepeatedFields expected_result;
+  expected_result.add_repeated_msg_field()->set_field2(123);
+
+  EXPECT_EQ(sink_message.DebugString(), expected_result.DebugString());
+}
+
+TEST(FieldPathTest, CopyingRepeatedFieldToRepeatedFieldCopiesTheWholeVector) {
+  // Copy repeated `{val1, val2}` to repeated `{}` results in `{val1, val2}`.
+  constexpr int64_t kValue1 = 123;
+  constexpr int64_t kValue2 = 234;
+
+  MessageContainingRepeatedFields source, sink;
+  source.add_field(kValue1);
+  source.add_field(kValue2);
+
+  const FieldPath source_path =
+      GetFieldPath<MessageContainingRepeatedFields>("field");
+  const FieldPath sink_path =
+      GetFieldPath<MessageContainingRepeatedFields>("field");
+
+  CopyField(source_path, source, sink_path, sink);
+
+  MessageContainingRepeatedFields expected_result;
+  expected_result.add_field(kValue1);
+  expected_result.add_field(kValue2);
+  EXPECT_EQ(sink.DebugString(), expected_result.DebugString());
+}
+
+TEST(FieldPathTest, CopyFieldOfSameTypeAndDifferentNameInMessagesFails) {
+  // Test copying GetUserPostsOptions.order to
+  // GetUserPostsRequest.options.
+  GetUserPostsOptions options;
+  const FieldPath order_source_path =
+      GetFieldPath<GetUserPostsOptions>("order");
+  GetUserPostsOptionsWithDifferentFieldNames options_with_different_field_names;
+  const FieldPath ordering_sink_path =
+      GetFieldPath<GetUserPostsOptionsWithDifferentFieldNames>("ordering");
+
+  EXPECT_DEATH_IF_SUPPORTED(
+      CopyField(order_source_path, options, ordering_sink_path,
+                options_with_different_field_names),
+      "Fields of mismatch names cannot be copied!");
+}
+
+TEST(FieldPathTest, CopyFieldOfDifferentTypeInMessagesFails) {
+  // Test copying LogInUserResponse.session_id to
+  // GetUserPostsRequest.options.
+  const LogInUserResponse log_in_response;
+  const FieldPath session_id_source_path =
+      GetFieldPath<LogInUserResponse>("session_id");
+  GetUserPostsRequest get_user_posts_request;
+  const FieldPath session_id_sink_path =
+      GetFieldPath<GetUserPostsRequest>("options");
+
+  EXPECT_DEATH_IF_SUPPORTED(
+      CopyField(session_id_source_path, log_in_response, session_id_sink_path,
+                get_user_posts_request),
+      "Fields of mismatch types cannot be copied!");
+}
+
+TEST(FieldPathTest, CopyFieldHandlesEnumCorrectly) {
+  GetUserPostsOptions source_options, sink_options;
+  const FieldPath field_path = GetFieldPath<GetUserPostsOptions>("order");
+  source_options.set_order(SortingOrder::ASCENDING);
+
+  CopyField(field_path, source_options, field_path, sink_options);
+
+  EXPECT_EQ(sink_options.order(), SortingOrder::ASCENDING);
+}
+
+TEST(FieldPathTest, GetContainingMessageOfLastFieldReturnsContainingMessage) {
+  const LogOutUserRequest log_out_request;
+  const FieldPath session_id_sink_path =
+      GetFieldPath<LogOutUserRequest>("log_out_info.session_info.session_id");
+
+  const LogOutInfo& log_out_info = log_out_request.log_out_info();
+  const SessionInfo& session_info = log_out_info.session_info();
+
+  const google::protobuf::Message* parent_msg =
+      session_id_sink_path.GetContainingMessageOfLastField(log_out_request);
+
+  EXPECT_EQ(parent_msg, &session_info);
+}
+
+TEST(FieldPathTest,
+     GetContainingMessageReturnsNonNullForUnsetMiddleSingularField) {
+  LogOutUserRequest log_out_request;
+  const FieldPath session_id_sink_path =
+      GetFieldPath<LogOutUserRequest>("log_out_info.session_info.session_id");
+
+  EXPECT_THAT(
+      session_id_sink_path.GetContainingMessageOfLastField(log_out_request),
+      NotNull());
+}
+
+TEST(FieldPathTest,
+     GetContainingMessageReturnsNullForMissingMiddleRepeatedField) {
+  MessageContainingRepeatedFields message_containing_repeated_fields;
+  const FieldPath message_containing_repeated_fields_sink_path =
+      GetFieldPath<MessageContainingRepeatedFields>(
+          "repeated_msg_field.field2");
+  EXPECT_THAT(
+      message_containing_repeated_fields_sink_path
+          .GetContainingMessageOfLastField(message_containing_repeated_fields),
+      IsNull());
+}
+
+TEST(FieldPathTest,
+     MutableContainingMessageAddMissingRepeatedFieldAndReturnsNonNull) {
+  MessageContainingRepeatedFields message_containing_repeated_fields;
+  const FieldPath message_containing_repeated_fields_sink_path =
+      GetFieldPath<MessageContainingRepeatedFields>(
+          "repeated_msg_field.field2");
+  EXPECT_THAT(message_containing_repeated_fields_sink_path
+                  .MutableContainingMessageOfLastField(
+                      message_containing_repeated_fields),
+              NotNull());
+}
+
+TEST(FieldPathTest,
+     MutableContainingMessageAtNonEmptyRepeatedFieldWillNotAddField) {
+  MessageContainingRepeatedFields message_containing_repeated_fields;
+  message_containing_repeated_fields.add_repeated_msg_field()->set_field2(
+      0x123);
+  const FieldPath message_containing_repeated_fields_sink_path =
+      GetFieldPath<MessageContainingRepeatedFields>(
+          "repeated_msg_field.field2");
+  (void)message_containing_repeated_fields_sink_path
+      .MutableContainingMessageOfLastField(message_containing_repeated_fields);
+  EXPECT_EQ(
+      message_containing_repeated_fields.GetReflection()->FieldSize(
+          message_containing_repeated_fields,
+          message_containing_repeated_fields.GetDescriptor()->FindFieldByName(
+              "repeated_msg_field")),
+      1);
+}
+
+TEST(FieldPathTest, MutableContainingMessageAtEmptyRepeatedFieldWillAddField) {
+  MessageContainingRepeatedFields message_containing_repeated_fields;
+  const FieldPath message_containing_repeated_fields_sink_path =
+      GetFieldPath<MessageContainingRepeatedFields>(
+          "repeated_msg_field.field2");
+  (void)message_containing_repeated_fields_sink_path
+      .MutableContainingMessageOfLastField(message_containing_repeated_fields);
+  EXPECT_EQ(
+      message_containing_repeated_fields.GetReflection()->FieldSize(
+          message_containing_repeated_fields,
+          message_containing_repeated_fields.GetDescriptor()->FindFieldByName(
+              "repeated_msg_field")),
+      1);
+}
+
+TEST(FieldPathTest,
+     GetContainingMessageOfLastFieldReturnsFirstIndexOfRepeatedFields) {
+  MessageContainingRepeatedFields message;
+  message.add_repeated_msg_field();
+  message.add_repeated_msg_field();
+
+  FieldPath field_path = GetFieldPath<MessageContainingRepeatedFields>(
+      "repeated_msg_field.field2");
+
+  const google::protobuf::Message* parent_msg =
+      field_path.GetContainingMessageOfLastField(message);
+  EXPECT_EQ(parent_msg, &message.repeated_msg_field(0));
+}
+
+TEST(FieldPathTest, MutableContainingMessageOfLastFieldReturnsMutableMessage) {
+  LogOutUserRequest log_out_request;
+  const FieldPath session_id_sink_path =
+      GetFieldPath<LogOutUserRequest>("log_out_info.session_info.session_id");
+
+  const LogOutInfo& log_out_info = log_out_request.log_out_info();
+  const SessionInfo& session_info = log_out_info.session_info();
+
+  google::protobuf::Message* parent_msg =
+      session_id_sink_path.MutableContainingMessageOfLastField(log_out_request);
+  // MutableContainingMessageOfLastField might create new message so the address
+  // will be different.
+  EXPECT_NE(parent_msg, &session_info);
+}
+
+TEST(FieldPathTest, GetLastFieldReturnsLastFieldOfThePath) {
+  const LogOutUserRequest log_out_request;
+  const FieldPath session_id_sink_path =
+      GetFieldPath<LogOutUserRequest>("log_out_info.session_info.session_id");
+
+  EXPECT_EQ(&session_id_sink_path.GetLastField(),
+            SessionInfo::GetDescriptor()->FindFieldByName("session_id"));
+}
+
+TEST(FieldPathTest,
+     AppendFieldSuccessIfTheLastFieldIsTheAppendedFieldParentType) {
+  const FieldPath ground_truth =
+      GetFieldPath<LogOutUserRequest>("log_out_info.session_info");
+  FieldPath field_path = GetFieldPath<LogOutUserRequest>("log_out_info");
+  field_path.AppendField(
+      *LogOutInfo::GetDescriptor()->FindFieldByName("session_info"));
+  EXPECT_EQ(ground_truth, field_path);
+}
+
+TEST(FieldPathTest,
+     AppendFieldFailsIfTheLastFieldIsNotTheAppendedFieldParentType) {
+  FieldPath field_path = GetFieldPath<LogInUserResponse>("success");
+
+  EXPECT_DEATH_IF_SUPPORTED(
+      field_path.AppendField(field_path.GetLastField()),
+      "The current last field in the path must be a message "
+      "containing the appended field.");
+}
+
+TEST(FieldPathTest,
+     AppendPathSuccessIfLastFieldOfAppendedPathIsContainedByTheCurrentPath) {
+  const FieldPath ground_truth =
+      GetFieldPath<LogOutUserRequest>("log_out_info.session_info.session_id");
+  FieldPath field_path1 = GetFieldPath<LogOutUserRequest>("log_out_info");
+  const FieldPath field_path2 =
+      GetFieldPath<LogOutInfo>("session_info.session_id");
+  field_path1.AppendPath(field_path2);
+  EXPECT_EQ(field_path1, ground_truth);
+}
+
+TEST(FieldPathTest,
+     AppendPathFailsIfLastFieldOfAppendedPathIsNotContainedByTheCurrentPath) {
+  FieldPath field_path1 = GetFieldPath<LogOutUserRequest>("log_out_info");
+  const FieldPath field_path2 = GetFieldPath<SessionInfo>("session_id");
+
+  EXPECT_DEATH_IF_SUPPORTED(
+      field_path1.AppendPath(field_path2),
+      "The current last field in the path must be a message "
+      "containing the first field in the other path.");
+}
+
+TEST(FieldPathTest, CollectAllFieldsReturnAllInnerFields) {
+  std::vector<FieldPath> all_fields =
+      CollectAllFields(*LogOutUserRequest::descriptor());
+
+  EXPECT_THAT(all_fields,
+              UnorderedElementsAre(
+                  FieldPathAsString("log_out_info"),
+                  FieldPathAsString("log_out_info.session_info"),
+                  FieldPathAsString("log_out_info.session_id"),
+                  FieldPathAsString("log_out_info.session_info.session_id")));
+}
+
+TEST(FieldPathTest, CollectAllFieldsSupportsGroupField) {
+  std::vector<FieldPath> all_fields =
+      CollectAllFields(*MessageWithGroup::descriptor());
+
+  EXPECT_THAT(all_fields,
+              UnorderedElementsAre(FieldPathAsString("groupfield"),
+                                   FieldPathAsString("groupfield.field1"),
+                                   FieldPathAsString("groupfield.field2")));
+}
+
+TEST(FieldPathTest, CopyFieldsSupportsGroupField) {
+  MessageWithGroup message_with_group1, message_with_group2;
+
+  message_with_group1.mutable_groupfield()->set_field1(123);
+  CopyField(GetFieldPath<MessageWithGroup>("groupfield"), message_with_group1,
+            GetFieldPath<MessageWithGroup>("groupfield"), message_with_group2);
+  EXPECT_EQ(message_with_group2.groupfield().field1(), 123);
+}
+
+TEST(FieldPathTest, CollectAllFieldsInRecursiveMessageSkipVisitedMessages) {
+  std::vector<FieldPath> all_fields =
+      CollectAllFields(*RecursiveNode::descriptor());
+  EXPECT_THAT(all_fields, ElementsAre(FieldPathAsString("value"),
+                                      FieldPathAsString("children")));
+}
+
+TEST(FieldPathTest, ToStringConcatAllFieldPartWithDot) {
+  const FieldPath field_path =
+      GetFieldPath<LogOutUserRequest>("log_out_info.session_info.session_id");
+  EXPECT_EQ(field_path.ToString(), "log_out_info.session_info.session_id");
+}
+
+TEST(FieldPathTest, AlternativeToReturnsTrueForFieldsWithinTheSameOneOf) {
+  EXPECT_TRUE(
+      AreOneOfAltearnatives(GetFieldPath<OneOfMessage>("oneof1_field1.v1"),
+                            GetFieldPath<OneOfMessage>("oneof1_field2")));
+  EXPECT_TRUE(
+      AreOneOfAltearnatives(GetFieldPath<OneOfMessage>("oneof1_field1.v2"),
+                            GetFieldPath<OneOfMessage>("oneof1_field2")));
+}
+
+TEST(FieldPathTest, AlternativeToReturnsFalseForFieldsNotWithinTheSameOneOf) {
+  EXPECT_FALSE(
+      AreOneOfAltearnatives(GetFieldPath<OneOfMessage>("oneof1_field1"),
+                            GetFieldPath<OneOfMessage>("oneof1_field1.v1")));
+  EXPECT_FALSE(
+      AreOneOfAltearnatives(GetFieldPath<OneOfMessage>("oneof1_field1.v1"),
+                            GetFieldPath<OneOfMessage>("oneof1_field1.v1")));
+  EXPECT_FALSE(
+      AreOneOfAltearnatives(GetFieldPath<OneOfMessage>("oneof1_field2"),
+                            GetFieldPath<OneOfMessage>("oneof2_field2")));
+}
+
+}  // namespace
+
+}  // namespace fuzztest::internal
diff --git a/rpc_fuzzing/rpc_executor.cc b/rpc_fuzzing/rpc_executor.cc
new file mode 100644
index 0000000..c00dc2d
--- /dev/null
+++ b/rpc_fuzzing/rpc_executor.cc
@@ -0,0 +1,58 @@
+#include "./rpc_fuzzing/rpc_executor.h"
+
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "google/protobuf/descriptor.h"
+#include "google/protobuf/message.h"
+#include "absl/status/status.h"
+#include "absl/status/statusor.h"
+#include "absl/strings/str_cat.h"
+#include "./fuzztest/internal/logging.h"
+
+namespace fuzztest::internal {
+
+absl::Status RpcExecutor::Execute(RpcSequence& sequence) {
+  responses_.clear();
+  responses_.reserve(sequence.size());
+  absl::Status status;
+  for (RpcNode& node : sequence) {
+    status = ExecuteOne(node);
+    if (!status.ok()) {
+      return status;
+    }
+  }
+  return status;
+}
+
+const google::protobuf::Message& RpcExecutor::GetRpcNodeResponse(RpcNodeID node_id) {
+  FUZZTEST_INTERNAL_CHECK(node_id < responses_.size(),
+                          "The dependency source doesn't exist!");
+  return *responses_[node_id];
+}
+
+std::vector<const google::protobuf::Message*> RpcExecutor::GetResponses() const {
+  std::vector<const google::protobuf::Message*> responses;
+  responses.reserve(responses_.size());
+  for (const std::unique_ptr<google::protobuf::Message>& response : responses_) {
+    responses.push_back(response.get());
+  }
+  return responses;
+}
+
+absl::Status RpcExecutor::ExecuteOne(RpcNode& node) {
+  for (const RpcDataFlowEdge& dep : node.dependencies()) {
+    // Assign value to dynamic fields.
+    const google::protobuf::Message& save_response = GetRpcNodeResponse(dep.from_node_id);
+    CopyField(dep.from_field, save_response, dep.to_field, node.request());
+  }
+
+  absl::StatusOr<std::unique_ptr<google::protobuf::Message>> response =
+      stub_->CallMethod(node.method(), node.request());
+  if (!response.ok()) return std::move(response).status();
+  responses_.push_back(*std::move(response));
+  return absl::OkStatus();
+}
+
+}  // namespace fuzztest::internal
diff --git a/rpc_fuzzing/rpc_executor.h b/rpc_fuzzing/rpc_executor.h
new file mode 100644
index 0000000..31463ab
--- /dev/null
+++ b/rpc_fuzzing/rpc_executor.h
@@ -0,0 +1,38 @@
+#ifndef FUZZTEST_RPC_FUZZING_RPC_EXECUTOR_H_
+#define FUZZTEST_RPC_FUZZING_RPC_EXECUTOR_H_
+
+#include <memory>
+#include <vector>
+
+#include "google/protobuf/message.h"
+#include "absl/log/die_if_null.h"
+#include "./rpc_fuzzing/rpc_sequence.h"
+#include "./rpc_fuzzing/rpc_stub.h"
+namespace fuzztest::internal {
+
+// The RpcExecutor fills the dynamic values from the previous responses
+// according to the dependency in the RpcSequence and sends modified requests to
+// the fuzzed service.
+class RpcExecutor {
+ public:
+  RpcExecutor(RpcStub* stub) : stub_(ABSL_DIE_IF_NULL(stub)) {}
+  // Send the rpc calls specified in `sequence`. The requests of `sequence` will
+  // be modified according to the dependency. Every time this method is called,
+  // the previous responses will be cleared.
+  absl::Status Execute(RpcSequence& sequence);
+  // Get the responses of the last executed sequence.
+  std::vector<const google::protobuf::Message*> GetResponses() const;
+
+ private:
+  // Fill the dynamic fields in the request, execute a single RPC call, saves
+  // and returns the status of the rpc.
+  absl::Status ExecuteOne(RpcNode& node);
+  const google::protobuf::Message& GetRpcNodeResponse(RpcNodeID node_id);
+
+  std::vector<std::unique_ptr<google::protobuf::Message>> responses_;
+  RpcStub* stub_;
+};
+
+}  // namespace fuzztest::internal
+
+#endif  // FUZZTEST_RPC_FUZZING_RPC_EXECUTOR_H_
diff --git a/rpc_fuzzing/rpc_executor_test.cc b/rpc_fuzzing/rpc_executor_test.cc
new file mode 100644
index 0000000..178daa2
--- /dev/null
+++ b/rpc_fuzzing/rpc_executor_test.cc
@@ -0,0 +1,86 @@
+#include "./rpc_fuzzing/rpc_executor.h"
+
+#include <cstdint>
+#include <memory>
+#include <utility>
+
+#include "google/protobuf/descriptor.h"
+#include "google/protobuf/message.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/log/check.h"
+#include "absl/strings/string_view.h"
+#include "./domain_tests/domain_testing.h"
+#include "./rpc_fuzzing/grpc_stub.h"
+#include "./rpc_fuzzing/proto_field_path.h"
+#include "./rpc_fuzzing/rpc_sequence.h"
+#include "./rpc_fuzzing/testdata/grpc/mini_blogger_service.h"
+#include "./rpc_fuzzing/testdata/mini_blogger.pb.h"
+#include "grpcpp//security/server_credentials.h"
+#include "grpcpp//server.h"
+#include "grpcpp//server_builder.h"
+#include "grpcpp//support/channel_arguments.h"
+
+namespace fuzztest::internal {
+
+namespace {
+
+using ::testing::AllOf;
+using ::testing::ElementsAre;
+using ::testing::Eq;
+using ::testing::IsTrue;
+using ::testing::Ne;
+using ::testing::NotNull;
+using ::testing::Pointee;
+using ::testing::ResultOf;
+
+const google::protobuf::FieldDescriptor* GetField(const google::protobuf::Message& message,
+                                        absl::string_view field_name) {
+  return message.GetDescriptor()->FindFieldByName(field_name);
+}
+
+class RpcExecutorGrpcTest : public ::testing::Test {
+ protected:
+  RpcExecutorGrpcTest()
+      : server_(grpc::ServerBuilder()
+                    .RegisterService(&mini_blogger_service_)
+                    .BuildAndStart()),
+        stub_(server_->InProcessChannel(grpc::ChannelArguments())) {}
+
+  ~RpcExecutorGrpcTest() override { server_->Shutdown(); }
+
+  RpcNode GetRegisterUserRpcNode(absl::string_view user_name,
+                                 absl::string_view email,
+                                 absl::string_view password) {
+    auto request = std::make_unique<RegisterUserRequest>();
+    request->set_user_name(user_name);
+    request->set_password(password);
+    request->set_email(password);
+    const google::protobuf::MethodDescriptor* method =
+        google::protobuf::DescriptorPool::generated_pool()->FindMethodByName(
+            "fuzztest.internal.MiniBlogger.RegisterUser");
+    CHECK(method != nullptr);
+    return RpcNode(*method, std::move(request));
+  }
+
+  MiniBloggerGrpcService mini_blogger_service_;
+  std::unique_ptr<grpc::Server> server_ = nullptr;
+  GrpcStub stub_;
+};
+
+TEST_F(RpcExecutorGrpcTest, ExecutorCorrectlyExecutesRpcCalls) {
+  RpcNode node = GetRegisterUserRpcNode("test", "test_email@gmail.com", "123");
+  RpcSequence sequence = {node};
+  RpcExecutor executor(&stub_);
+  ASSERT_OK(executor.Execute(sequence));
+  EXPECT_THAT(executor.GetResponses(),
+              ElementsAre(Pointee(ResultOf(
+                  [](const google::protobuf::Message& response) {
+                    return response.GetReflection()->GetBool(
+                        response, GetField(response, "success"));
+                  },
+                  IsTrue()))));
+}
+
+}  // namespace
+}  // namespace fuzztest::internal
diff --git a/rpc_fuzzing/rpc_potential_dfg.cc b/rpc_fuzzing/rpc_potential_dfg.cc
new file mode 100644
index 0000000..ed9b04b
--- /dev/null
+++ b/rpc_fuzzing/rpc_potential_dfg.cc
@@ -0,0 +1,155 @@
+#include "./rpc_fuzzing/rpc_potential_dfg.h"
+
+#include <vector>
+
+#include "google/protobuf/descriptor.h"
+#include "./fuzztest/internal/logging.h"
+#include "./rpc_fuzzing/proto_field_path.h"
+
+namespace fuzztest::internal {
+
+namespace {
+
+// Get the type name of a field. If it is a message type, then return its
+// concrete message type name.
+std::string_view GetTypeNameForField(const google::protobuf::FieldDescriptor& field) {
+  if (field.type() == google::protobuf::FieldDescriptor::TYPE_MESSAGE) {
+    return field.message_type()->name();
+  } else {
+    return field.type_name();
+  }
+}
+
+// Describe a field path in a specific method's request or response.
+struct DetailedFieldInfo {
+  // Use pointer to keep it copy assignable.
+  const google::protobuf::MethodDescriptor* method;
+  bool in_request;
+  FieldPath field_path;
+};
+
+// Collect either all the fields in the response/request of a method.
+std::vector<DetailedFieldInfo> CollectFieldInfoInMethod(
+    const google::protobuf::MethodDescriptor& method, bool collect_request) {
+  const google::protobuf::Descriptor& message_descriptor =
+      collect_request ? *method.input_type() : *method.output_type();
+  std::vector<FieldPath> all_field_paths = CollectAllFields(message_descriptor);
+  std::vector<DetailedFieldInfo> result;
+  result.reserve(all_field_paths.size());
+  for (const auto& field_path : all_field_paths) {
+    result.push_back(DetailedFieldInfo{&method, collect_request, field_path});
+  }
+  return result;
+}
+
+std::vector<DetailedFieldInfo> CollectDefinitions(
+    const google::protobuf::MethodDescriptor& method) {
+  return CollectFieldInfoInMethod(method, /*collect_request=*/false);
+}
+
+std::vector<DetailedFieldInfo> CollectUses(
+    const google::protobuf::MethodDescriptor& method) {
+  return CollectFieldInfoInMethod(method, /*collect_request=*/true);
+}
+
+bool InSameMethod(const DetailedFieldInfo& a, const DetailedFieldInfo& b) {
+  return a.method->full_name() == b.method->full_name();
+}
+
+bool HasSameNameAndType(const DetailedFieldInfo& a,
+                        const DetailedFieldInfo& b) {
+  const google::protobuf::FieldDescriptor& a_field = a.field_path.GetLastField();
+  const google::protobuf::FieldDescriptor& b_field = b.field_path.GetLastField();
+  return a_field.name() == b_field.name() &&
+         GetTypeNameForField(a_field) == GetTypeNameForField(b_field);
+}
+
+}  // namespace
+
+void RpcPotentialDfgNode::AddDependency(
+    const google::protobuf::MethodDescriptor& source_method,
+    const FieldPath& source_field, const FieldPath& sink_field) {
+  dependencies_[sink_field].push_back(
+      PotentialDependencySource{&source_method, source_field});
+}
+
+void RpcPotentialDataFlowGraph::AddDependency(
+    const google::protobuf::MethodDescriptor& source_method,
+    const FieldPath& source_field, const google::protobuf::MethodDescriptor& sink_method,
+    const FieldPath& sink_field) {
+  FUZZTEST_INTERNAL_CHECK_PRECONDITION(
+      nodes_.find(&source_method) != nodes_.end(), "No such source method!");
+  FUZZTEST_INTERNAL_CHECK_PRECONDITION(
+      nodes_.find(&sink_method) != nodes_.end(), "No such sink method!");
+  RpcPotentialDfgNode& sink_node = nodes_.find(&sink_method)->second;
+  sink_node.AddDependency(source_method, source_field, sink_field);
+}
+
+RpcPotentialDataFlowGraph::RpcPotentialDataFlowGraph(
+    const google::protobuf::ServiceDescriptor& service) {
+  for (int i = 0; i < service.method_count(); ++i) {
+    const google::protobuf::MethodDescriptor& method = *service.method(i);
+    nodes_.emplace(&method, RpcPotentialDfgNode(method));
+  }
+}
+
+RpcPotentialDataFlowGraph RpcPotentialDataFlowGraph::Create(
+    const google::protobuf::ServiceDescriptor& service_descriptor) {
+  // Collect all the definitions and uses.
+  std::vector<DetailedFieldInfo> all_definitions, all_uses;
+  for (int i = 0; i < service_descriptor.method_count(); ++i) {
+    const google::protobuf::MethodDescriptor& method = *service_descriptor.method(i);
+    std::vector<DetailedFieldInfo> definitions = CollectDefinitions(method);
+    std::vector<DetailedFieldInfo> uses = CollectUses(method);
+    all_definitions.insert(all_definitions.end(), definitions.begin(),
+                           definitions.end());
+    all_uses.insert(all_uses.end(), uses.begin(), uses.end());
+  }
+
+  // For each pair of definition and use with same name and type, add a
+  // dependency (edge) in the graph.
+  RpcPotentialDataFlowGraph graph(service_descriptor);
+  for (const DetailedFieldInfo& define_field_info : all_definitions) {
+    for (const DetailedFieldInfo& use_field_info : all_uses) {
+      if (!InSameMethod(define_field_info, use_field_info) &&
+          HasSameNameAndType(define_field_info, use_field_info)) {
+        graph.AddDependency(*define_field_info.method,
+                            define_field_info.field_path,
+                            *use_field_info.method, use_field_info.field_path);
+      }
+    }
+  }
+  return graph;
+}
+
+const std::vector<RpcPotentialDfgNode::PotentialDependencySource>&
+RpcPotentialDfgNode::GetDependencies(const FieldPath& sink) const {
+  FUZZTEST_INTERNAL_CHECK_PRECONDITION(
+      dependencies_.find(sink) != dependencies_.end(), "No such sink!");
+  return dependencies_.at(sink);
+}
+
+const RpcPotentialDfgNode& RpcPotentialDataFlowGraph::GetNode(
+    const google::protobuf::MethodDescriptor& method) const {
+  FUZZTEST_INTERNAL_CHECK_PRECONDITION(nodes_.find(&method) != nodes_.end(),
+                                       "No such method!");
+  return nodes_.at(&method);
+}
+
+const google::protobuf::ServiceDescriptor& GetServiceDescriptorByServiceName(
+    absl::string_view service_name) {
+  const google::protobuf::ServiceDescriptor* service =
+      google::protobuf::DescriptorPool::generated_pool()->FindServiceByName(
+          service_name.data());
+  FUZZTEST_INTERNAL_CHECK(service != nullptr,
+                          std::string(service_name) + " Service not found!");
+  return *service;
+}
+
+RpcPotentialDataFlowGraph CreatePotentialDfgByServiceName(
+    absl::string_view service_name) {
+  return RpcPotentialDataFlowGraph::Create(
+      GetServiceDescriptorByServiceName(service_name));
+}
+
+}  // namespace fuzztest::internal
diff --git a/rpc_fuzzing/rpc_potential_dfg.h b/rpc_fuzzing/rpc_potential_dfg.h
new file mode 100644
index 0000000..ace3f37
--- /dev/null
+++ b/rpc_fuzzing/rpc_potential_dfg.h
@@ -0,0 +1,129 @@
+#ifndef FUZZTEST_RPC_FUZZING_RPC_POTENTIAL_DFG_H_
+#define FUZZTEST_RPC_FUZZING_RPC_POTENTIAL_DFG_H_
+
+#include <type_traits>
+#include <vector>
+
+#include "google/protobuf/descriptor.h"
+#include "google/protobuf/service.h"
+#include "absl/container/flat_hash_map.h"
+#include "absl/strings/string_view.h"
+#include "./fuzztest/internal/logging.h"
+#include "./rpc_fuzzing/proto_field_path.h"
+
+namespace fuzztest::internal {
+
+// A `RpcPotentialDfgNode` describes the potential dependencies for the
+// fields in a request of a rpc method.
+class RpcPotentialDfgNode {
+ public:
+  RpcPotentialDfgNode(const google::protobuf::MethodDescriptor& method)
+      : method_(method) {}
+
+  // Establish a dependency from the `source_field` to the `sink_field` of
+  // current method.
+  void AddDependency(const google::protobuf::MethodDescriptor& source_method,
+                     const FieldPath& source_field,
+                     const FieldPath& sink_field);
+
+  // Get the method representing by the node.
+  const google::protobuf::MethodDescriptor& GetMethod() const { return method_; }
+
+  // Source of dependency, which should be in the response of a method.
+  struct PotentialDependencySource {
+    // `method` will never be null. Use a pointer to keep it copy assignable.
+    const google::protobuf::MethodDescriptor* method;
+    FieldPath field_path;
+  };
+
+  const absl::flat_hash_map<FieldPath, std::vector<PotentialDependencySource>>&
+  GetAllDependencies() const {
+    return dependencies_;
+  }
+
+  const std::vector<PotentialDependencySource>& GetDependencies(
+      const FieldPath& sink) const;
+  bool HasDependency() const { return !dependencies_.empty(); }
+
+ private:
+  const google::protobuf::MethodDescriptor& method_;
+  absl::flat_hash_map<FieldPath /*sink*/,
+                      std::vector<PotentialDependencySource> /*source*/>
+      dependencies_;
+};
+
+// A `RpcPotentialDataFlowGraph` describes all the potential dependencies among
+// methods in a service. Each method is represented as a node in the graph.
+class RpcPotentialDataFlowGraph {
+ public:
+  // Get the node representing `method`.
+  const RpcPotentialDfgNode& GetNode(
+      const google::protobuf::MethodDescriptor& method) const;
+
+  // Create an `RpcPotentialDfg` with each method in the service being
+  // a node, and add dependencies between the methods.
+  static RpcPotentialDataFlowGraph Create(
+      const google::protobuf::ServiceDescriptor& service);
+
+ private:
+  RpcPotentialDataFlowGraph(const google::protobuf::ServiceDescriptor& service);
+  // Add a dependency on the field from the response of `source_method` to the
+  // request of `sink_method`.
+  void AddDependency(const google::protobuf::MethodDescriptor& source_method,
+                     const FieldPath& source_field,
+                     const google::protobuf::MethodDescriptor& sink_method,
+                     const FieldPath& sink_field);
+
+  absl::flat_hash_map<const google::protobuf::MethodDescriptor*, RpcPotentialDfgNode>
+      nodes_;
+};
+
+template <typename T, typename = void>
+struct is_stubby_service : std::false_type {};
+
+template <typename ServiceT>
+struct is_stubby_service<ServiceT, std::void_t<decltype(ServiceT::descriptor)>>
+    : std::true_type {};
+
+template <typename T, typename = void>
+struct is_grpc_service : std::false_type {};
+
+template <typename ServiceT>
+struct is_grpc_service<ServiceT,
+                       std::void_t<decltype(ServiceT::service_full_name)>>
+    : std::true_type {};
+
+// Get the service descriptor based on the service type. Currently support
+// protobuf services and stubby services.
+template <typename ServiceT>
+const google::protobuf::ServiceDescriptor& GetServiceDescriptor() {
+  if constexpr (std::is_same_v<ServiceT, google::protobuf::Service>) {
+    return *ServiceT::descriptor();
+  } else if constexpr (is_stubby_service<ServiceT>::value) {
+    return *google::protobuf::DescriptorPool::generated_pool()->FindServiceByName(
+        ServiceT::descriptor.full_name());
+  } else if constexpr (is_grpc_service<ServiceT>::value) {
+    return *google::protobuf::DescriptorPool::generated_pool()->FindServiceByName(
+        ServiceT::service_full_name());
+  } else {
+    FUZZTEST_INTERNAL_CHECK(false, "Unsupported service type!");
+  }
+}
+
+// Get the service descriptor based on the fully qualified service name such as
+// "package.ServiceName".
+const google::protobuf::ServiceDescriptor& GetServiceDescriptorByServiceName(
+    absl::string_view service_name);
+
+// Create an potential data flow graph for a service type.
+template <typename ServiceT>
+RpcPotentialDataFlowGraph CreatePotentialDfg() {
+  return RpcPotentialDataFlowGraph::Create(GetServiceDescriptor<ServiceT>());
+}
+
+RpcPotentialDataFlowGraph CreatePotentialDfgByServiceName(
+    absl::string_view service_name);
+
+}  // namespace fuzztest::internal
+
+#endif  // FUZZTEST_RPC_FUZZING_RPC_POTENTIAL_DFG_H_
diff --git a/rpc_fuzzing/rpc_potential_dfg_test.cc b/rpc_fuzzing/rpc_potential_dfg_test.cc
new file mode 100644
index 0000000..c5c54a1
--- /dev/null
+++ b/rpc_fuzzing/rpc_potential_dfg_test.cc
@@ -0,0 +1,104 @@
+#include "./rpc_fuzzing/rpc_potential_dfg.h"
+
+#include <vector>
+
+#include "google/protobuf/descriptor.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "./rpc_fuzzing/proto_field_path.h"
+#include "./rpc_fuzzing/testdata/mini_blogger.pb.h"
+#include "./rpc_fuzzing/testdata/mini_blogger.grpc.pb.h"
+
+namespace fuzztest::internal {
+
+namespace {
+
+using ::testing::FieldsAre;
+using ::testing::UnorderedElementsAre;
+
+class RpcPotentialDfgTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    const google::protobuf::DescriptorPool* pool =
+        google::protobuf::DescriptorPool::generated_pool();
+    mini_blogger_serivce_ =
+        pool->FindServiceByName("fuzztest.internal.MiniBlogger");
+    ASSERT_TRUE(mini_blogger_serivce_ != nullptr);
+    log_out_user_method_ =
+        mini_blogger_serivce_->FindMethodByName("LogOutUser");
+    ASSERT_TRUE(log_out_user_method_ != nullptr);
+    log_in_user_method_ = mini_blogger_serivce_->FindMethodByName("LogInUser");
+    ASSERT_TRUE(log_in_user_method_ != nullptr);
+    register_user_method_ =
+        mini_blogger_serivce_->FindMethodByName("RegisterUser");
+    ASSERT_TRUE(register_user_method_ != nullptr);
+    get_user_posts_method_ =
+        mini_blogger_serivce_->FindMethodByName("GetUserPosts");
+    ASSERT_TRUE(get_user_posts_method_ != nullptr);
+  }
+  const google::protobuf::ServiceDescriptor* mini_blogger_serivce_;
+  const google::protobuf::MethodDescriptor* log_out_user_method_;
+  const google::protobuf::MethodDescriptor* log_in_user_method_;
+  const google::protobuf::MethodDescriptor* register_user_method_;
+  const google::protobuf::MethodDescriptor* get_user_posts_method_;
+};
+
+TEST_F(RpcPotentialDfgTest,
+       NodesNotDependingOnOthersHaveNoPotentialDependencies) {
+  RpcPotentialDataFlowGraph dfg = CreatePotentialDfg<MiniBlogger>();
+
+  const RpcPotentialDfgNode& register_user_node =
+      dfg.GetNode(*register_user_method_);
+  // RegisterUser doesn't depend on others.
+  EXPECT_FALSE(register_user_node.HasDependency());
+
+  const RpcPotentialDfgNode& log_in_user_node =
+      dfg.GetNode(*log_in_user_method_);
+  // LogInUser doesn't depend on others.
+  EXPECT_FALSE(log_in_user_node.HasDependency());
+}
+
+TEST_F(RpcPotentialDfgTest, DependneciesSetUpBasedOnNameAndType) {
+  RpcPotentialDataFlowGraph dfg = CreatePotentialDfg<MiniBlogger>();
+  const RpcPotentialDfgNode& get_user_posts_node =
+      dfg.GetNode(*get_user_posts_method_);
+  // GetUserPostsRequest.session_id depends on LogInUserResponse.session_id.
+  EXPECT_EQ(get_user_posts_node.GetAllDependencies().size(), 1);
+
+  FieldPath get_user_posts_request_field =
+      GetFieldPath<GetUserPostsRequest>("session_id");
+  std::vector<RpcPotentialDfgNode::PotentialDependencySource> all_dep_sources =
+      get_user_posts_node.GetDependencies(get_user_posts_request_field);
+
+  // Depends on the session_id of the response in `LogInUser`.
+  EXPECT_THAT(
+      all_dep_sources,
+      UnorderedElementsAre(FieldsAre(
+          log_in_user_method_, GetFieldPath<LogInUserResponse>("session_id"))));
+}
+
+TEST_F(RpcPotentialDfgTest, DependneciesSetUpOnInnerFields) {
+  RpcPotentialDataFlowGraph dfg = CreatePotentialDfg<MiniBlogger>();
+  const RpcPotentialDfgNode& log_out_user_node =
+      dfg.GetNode(*log_out_user_method_);
+  // LogOutUserRequest.log_out_info.session_info.session_id and
+  // LogOutUserRequest.log_out_info.session_id depends on
+  // LogInUserResponse.session_id.
+  EXPECT_EQ(log_out_user_node.GetAllDependencies().size(), 2);
+
+  FieldPath log_out_user_request_field =
+      GetFieldPath<LogOutUserRequest>("log_out_info.session_info.session_id");
+  std::vector<RpcPotentialDfgNode::PotentialDependencySource> all_dep_sources =
+      log_out_user_node.GetDependencies(log_out_user_request_field);
+  EXPECT_EQ(all_dep_sources.size(), 1);
+
+  // Depends on the session_id of the response in `LogInUser`.
+  EXPECT_THAT(
+      all_dep_sources,
+      UnorderedElementsAre(FieldsAre(
+          log_in_user_method_, GetFieldPath<LogInUserResponse>("session_id"))));
+}
+
+}  // namespace
+
+}  // namespace fuzztest::internal
diff --git a/rpc_fuzzing/rpc_sequence.cc b/rpc_fuzzing/rpc_sequence.cc
new file mode 100644
index 0000000..cb69534
--- /dev/null
+++ b/rpc_fuzzing/rpc_sequence.cc
@@ -0,0 +1,184 @@
+#include "./rpc_fuzzing/rpc_sequence.h"
+
+#include "google/protobuf/descriptor.h"
+#include "google/protobuf/message.h"
+#include "google/protobuf/util/message_differencer.h"
+#include "absl/container/flat_hash_set.h"
+#include "absl/memory/memory.h"
+#include "absl/random/bit_gen_ref.h"
+#include "absl/random/random.h"
+#include "./fuzztest/internal/logging.h"
+
+namespace fuzztest::internal {
+
+namespace {
+
+std::vector<int> GetShuffledIndices(size_t size, absl::BitGenRef gen) {
+  std::vector<int> shuffled_indices(size);
+  std::iota(shuffled_indices.begin(), shuffled_indices.end(), 0);
+  std::shuffle(shuffled_indices.begin(), shuffled_indices.end(), gen);
+  return shuffled_indices;
+}
+
+std::vector<RpcNodeID> GetShuffledNodeIdsInGraph(const RpcDataFlowGraph& graph,
+                                                 absl::BitGenRef gen) {
+  std::vector<RpcNodeID> shuffled_node_ids;
+  shuffled_node_ids.reserve(graph.NodeNum());
+  for (const auto& [node_id, unused_node] : graph.GetAllNodes()) {
+    shuffled_node_ids.push_back(node_id);
+  }
+  std::shuffle(shuffled_node_ids.begin(), shuffled_node_ids.end(), gen);
+  return shuffled_node_ids;
+}
+
+void RemapRpcNodeIdToSequenceIndex(
+    RpcSequence& sequence,
+    const absl::flat_hash_map<RpcNodeID, RpcNodeID>& id_map) {
+  for (RpcNode& node : sequence) {
+    for (RpcDataFlowEdge& edge : node.dependencies()) {
+      edge.from_node_id = id_map.at(edge.from_node_id);
+    }
+  }
+}
+
+void CollectTopologicalSortingOrder(const RpcDataFlowGraph& graph,
+                                    RpcNodeID current_id, absl::BitGenRef gen,
+                                    absl::flat_hash_set<RpcNodeID>& visited,
+                                    std::vector<RpcNodeID>& sorted_ids) {
+  if (visited.contains(current_id)) return;
+
+  visited.insert(current_id);
+  const RpcNode& node = graph.GetNode(current_id);
+  std::vector<RpcNodeID> shuffled_edge_indices =
+      GetShuffledIndices(node.dependencies().size(), gen);
+  for (int edge_index : shuffled_edge_indices) {
+    CollectTopologicalSortingOrder(graph,
+                                   node.dependencies()[edge_index].from_node_id,
+                                   gen, visited, sorted_ids);
+  }
+  sorted_ids.push_back(current_id);
+}
+
+std::vector<RpcNodeID> GetRandomTopologicalOrdering(
+    const RpcDataFlowGraph& graph) {
+  std::vector<RpcNodeID> sorted_node_ids;
+  sorted_node_ids.reserve(graph.NodeNum());
+  absl::flat_hash_set<RpcNodeID> visited;
+  absl::BitGen gen;
+  std::vector<RpcNodeID> shuffled_node_ids =
+      GetShuffledNodeIdsInGraph(graph, gen);
+  for (RpcNodeID node_id : shuffled_node_ids) {
+    CollectTopologicalSortingOrder(graph, node_id, gen, visited,
+                                   sorted_node_ids);
+  }
+  FUZZTEST_INTERNAL_CHECK(
+      sorted_node_ids.size() == graph.NodeNum(),
+      "Topological sort results in different number of nodes!");
+  return sorted_node_ids;
+}
+
+RpcSequence SequentializeGraphByOrder(
+    const RpcDataFlowGraph& graph, const std::vector<RpcNodeID>& node_order) {
+  RpcSequence result;
+  result.reserve(graph.NodeNum());
+  absl::flat_hash_map<RpcNodeID /* original node id */,
+                      RpcNodeID /* sequence index */>
+      id_map;
+  for (size_t seq_idx = 0; seq_idx < node_order.size(); ++seq_idx) {
+    RpcNodeID original_node_id = node_order[seq_idx];
+    id_map[original_node_id] = seq_idx;
+    result.push_back(graph.GetNode(original_node_id));
+  }
+  RemapRpcNodeIdToSequenceIndex(result, id_map);
+  return result;
+}
+
+}  // namespace
+
+RpcNode::RpcNode(const RpcNode& other)
+    : method_(other.method_),
+      request_(absl::WrapUnique(other.request_->New())),
+      dependencies_(other.dependencies_) {
+  request_->CopyFrom(*other.request_);
+}
+
+RpcNode& RpcNode::operator=(const RpcNode& other) {
+  method_ = other.method_;
+  request_ = absl::WrapUnique(other.request_->New());
+  request_->CopyFrom(*other.request_);
+  dependencies_ = other.dependencies_;
+  return *this;
+}
+
+bool operator==(const RpcDataFlowEdge& lhs, const RpcDataFlowEdge& rhs) {
+  return lhs.from_node_id == rhs.from_node_id &&
+         lhs.from_field == rhs.from_field && lhs.to_field == rhs.to_field;
+}
+
+bool operator!=(const RpcDataFlowEdge& lhs, const RpcDataFlowEdge& rhs) {
+  return !(lhs == rhs);
+}
+
+bool operator==(const RpcNode& lhs, const RpcNode& rhs) {
+  return lhs.method_->full_name() == rhs.method_->full_name() &&
+         google::protobuf::util::MessageDifferencer::Equals(*lhs.request_,
+                                                  *rhs.request_) &&
+         lhs.dependencies_ == rhs.dependencies_;
+}
+
+bool operator!=(const RpcNode& lhs, const RpcNode& rhs) {
+  return !(lhs == rhs);
+}
+
+void RpcDataFlowGraph::AddNode(RpcNodeID id, RpcNode rpc_node) {
+  InvalidateNodeOrder();
+  rpc_nodes_.emplace(id, rpc_node);
+}
+
+void RpcDataFlowGraph::RemoveNode(RpcNodeID id) {
+  InvalidateNodeOrder();
+  rpc_nodes_.erase(id);
+}
+
+const RpcNode& RpcDataFlowGraph::GetNode(RpcNodeID id) const {
+  FUZZTEST_INTERNAL_CHECK(rpc_nodes_.contains(id), "Invalid id!");
+  return rpc_nodes_.at(id);
+}
+
+size_t RpcDataFlowGraph::NodeNum() const { return rpc_nodes_.size(); }
+
+RpcDataFlowGraph RpcDataFlowGraph::FromSequence(const RpcSequence& sequence) {
+  RpcDataFlowGraph graph;
+  for (int node_idx = 0; node_idx < sequence.size(); ++node_idx) {
+    graph.AddNode(node_idx, sequence[node_idx]);
+  }
+  graph.node_order_ = std::vector<RpcNodeID>(sequence.size());
+  std::iota(graph.node_order_->begin(), graph.node_order_->end(), 0);
+  return graph;
+}
+
+RpcSequence RpcDataFlowGraph::GetSequence() const {
+  if (!node_order_.has_value()) {
+    node_order_ = GetRandomTopologicalOrdering(*this);
+  }
+  return SequentializeGraphByOrder(*this, *node_order_);
+}
+
+std::vector<RpcNodeID>& RpcDataFlowGraph::GetOrderedNodeIds() const {
+  if (!node_order_.has_value()) {
+    node_order_ = GetRandomTopologicalOrdering(*this);
+  }
+  return *node_order_;
+}
+
+bool operator==(const RpcDataFlowGraph& lhs, const RpcDataFlowGraph& rhs) {
+  return lhs.GetSequence() == rhs.GetSequence();
+}
+
+bool operator!=(const RpcDataFlowGraph& lhs, const RpcDataFlowGraph& rhs) {
+  return !(lhs == rhs);
+}
+
+void RpcDataFlowGraph::RandomizeTopologicalOrdering() { InvalidateNodeOrder(); }
+
+}  // namespace fuzztest::internal
diff --git a/rpc_fuzzing/rpc_sequence.h b/rpc_fuzzing/rpc_sequence.h
new file mode 100644
index 0000000..b014022
--- /dev/null
+++ b/rpc_fuzzing/rpc_sequence.h
@@ -0,0 +1,116 @@
+#ifndef FUZZTEST_RPC_FUZZING_RPC_SEQUENCE_H_
+#define FUZZTEST_RPC_FUZZING_RPC_SEQUENCE_H_
+
+#include <memory>
+#include <optional>
+#include <vector>
+
+#include "google/protobuf/descriptor.h"
+#include "google/protobuf/message.h"
+#include "absl/container/flat_hash_map.h"
+#include "./rpc_fuzzing/proto_field_path.h"
+
+namespace fuzztest::internal {
+
+// An RPC call is identified by an id because we might have multiple calls to
+// the same rpc method. In the sequence, the id is also the index of the node.
+using RpcNodeID = std::int32_t;
+
+// Represents a data-flow edge between two RPC calls.
+struct RpcDataFlowEdge {
+  RpcNodeID from_node_id = -1;
+  // The message field in the response.
+  FieldPath from_field;
+  // The dynamic field in the request.
+  FieldPath to_field;
+};
+
+bool operator==(const RpcDataFlowEdge& lhs, const RpcDataFlowEdge& rhs);
+bool operator!=(const RpcDataFlowEdge& lhs, const RpcDataFlowEdge& rhs);
+
+// Represents a remote procedure call (a request). Parts of the request that are
+// coming from a previous response are represented with a data flow edge
+// (`RpcDataFlowEdge`).
+class RpcNode {
+ public:
+  RpcNode(const google::protobuf::MethodDescriptor& method,
+          std::unique_ptr<google::protobuf::Message> request)
+      : method_(&method), request_(std::move(request)) {}
+  RpcNode(const RpcNode&);
+  RpcNode(RpcNode&&) noexcept;
+  RpcNode& operator=(const RpcNode&);
+  RpcNode& operator=(RpcNode&&) = default;
+  const google::protobuf::MethodDescriptor& method() const { return *method_; }
+  google::protobuf::Message& request() { return *request_; }
+  const google::protobuf::Message& request() const { return *request_; }
+  void AddDependency(RpcDataFlowEdge dep) { dependencies_.push_back(dep); }
+  const std::vector<RpcDataFlowEdge>& dependencies() const {
+    return dependencies_;
+  }
+  std::vector<RpcDataFlowEdge>& dependencies() { return dependencies_; }
+
+  friend bool operator==(const RpcNode& lhs, const RpcNode& rhs);
+
+ private:
+  // The `method_` is ensured to be non-null.
+  const google::protobuf::MethodDescriptor* method_;
+  std::unique_ptr<google::protobuf::Message> request_;
+  std::vector<RpcDataFlowEdge> dependencies_;
+};
+
+bool operator==(const RpcNode& lhs, const RpcNode& rhs);
+bool operator!=(const RpcNode& lhs, const RpcNode& rhs);
+
+inline RpcNode::RpcNode(RpcNode&&) noexcept = default;
+
+// The RpcSequence represents a topologically sorted RpcDataFlowGraph.
+using RpcSequence = std::vector<RpcNode>;
+
+// The RpcDataFlowGraph represents a "remote procedure call session", i.e., a
+// set of RPC method calls and the data-flow dependencies between them.
+class RpcDataFlowGraph {
+ public:
+  void AddNode(RpcNodeID id, RpcNode rpc_node);
+  void RemoveNode(RpcNodeID id);
+  const RpcNode& GetNode(RpcNodeID id) const;
+  size_t NodeNum() const;
+
+  absl::flat_hash_map<RpcNodeID, RpcNode>& GetAllNodes() { return rpc_nodes_; }
+  const absl::flat_hash_map<RpcNodeID, RpcNode>& GetAllNodes() const {
+    return rpc_nodes_;
+  }
+
+  // Returns a topologically sorted sequence representation of the graph. Note
+  // that there are multiple possible topological orderings, from which it
+  // returns a random one. Multiple calls to the method return the same ordering
+  // (unless RandomizeTopologicalOrdering() is called).
+  RpcSequence GetSequence() const;
+
+  // Converts a sequence to a graph with the sequence node ordering.
+  static RpcDataFlowGraph FromSequence(const RpcSequence& sequence);
+
+  // Randomizes the topological ordering of the nodes.
+  void RandomizeTopologicalOrdering();
+
+  // Returns the ordering of the nodes corresponding to that returned by
+  // `GetSequence`.
+  std::vector<RpcNodeID>& GetOrderedNodeIds() const;
+
+ private:
+  // Invalidates the node order so that if the order is needed again it will be
+  // re-computed on demand. This optimization avoids unnecessary recomputation
+  // every time the graph is changed because the recomputation can be costly.
+  void InvalidateNodeOrder() const { node_order_.reset(); }
+  absl::flat_hash_map<RpcNodeID, RpcNode> rpc_nodes_;
+  // `GetSequence` might change the order so we need `mutable` for `GetSequence`
+  // to be used in `FromValue` in the RpcSession domain.
+  mutable std::optional<std::vector<RpcNodeID>> node_order_;
+};
+
+// Returns true if the graphs have the same nodes and ordering.
+bool operator==(const RpcDataFlowGraph& lhs, const RpcDataFlowGraph& rhs);
+bool operator!=(const RpcDataFlowGraph& lhs, const RpcDataFlowGraph& rhs);
+
+}  // namespace fuzztest::internal
+
+#endif  // FUZZTEST_RPC_FUZZING_RPC_SEQUENCE_H_
diff --git a/rpc_fuzzing/rpc_sequence_test.cc b/rpc_fuzzing/rpc_sequence_test.cc
new file mode 100644
index 0000000..768d630
--- /dev/null
+++ b/rpc_fuzzing/rpc_sequence_test.cc
@@ -0,0 +1,280 @@
+#include "./rpc_fuzzing/rpc_sequence.h"
+
+#include "google/protobuf/descriptor.h"
+#include "google/protobuf/util/message_differencer.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/container/flat_hash_set.h"
+#include "absl/log/check.h"
+#include "./rpc_fuzzing/proto_field_path.h"
+#include "./rpc_fuzzing/testdata/mini_blogger.pb.h"
+
+namespace fuzztest::internal {
+
+namespace {
+
+using ::testing::Address;
+using ::testing::ElementsAre;
+using ::testing::Field;
+using ::testing::Property;
+using ::testing::UnorderedElementsAreArray;
+
+TEST(RpcNodeTest, AssignOperator) {
+  constexpr RpcNodeID kFromNodeId = 1;
+  const google::protobuf::MethodDescriptor* method_descriptor =
+      google::protobuf::DescriptorPool::generated_pool()->FindMethodByName(
+          "fuzztest.internal.MiniBlogger.RegisterUser");
+  CHECK(method_descriptor != nullptr);
+  RpcNode node1 =
+      RpcNode(*method_descriptor, std::make_unique<RegisterUserRequest>());
+  node1.AddDependency(RpcDataFlowEdge{kFromNodeId});
+
+  RpcNode node2 = node1;
+
+  // Check that the two objects are equal.
+  EXPECT_EQ(node1.method().full_name(), node2.method().full_name());
+  EXPECT_TRUE(google::protobuf::util::MessageDifferencer::Equals(node1.request(),
+                                                       node2.request()));
+  EXPECT_THAT(node2.dependencies(),
+              ElementsAre(Field(&RpcDataFlowEdge::from_node_id, kFromNodeId)));
+}
+
+TEST(RpcNodeTest, MoveAssignOperator) {
+  constexpr RpcNodeID kFromNodeId = 1;
+  const google::protobuf::MethodDescriptor* method_descriptor =
+      google::protobuf::DescriptorPool::generated_pool()->FindMethodByName(
+          "fuzztest.internal.MiniBlogger.RegisterUser");
+  CHECK(method_descriptor != nullptr);
+  RpcNode node1 =
+      RpcNode(*method_descriptor, std::make_unique<RegisterUserRequest>());
+  node1.AddDependency(RpcDataFlowEdge{kFromNodeId});
+
+  RpcNode clone = node1;
+  RpcNode node2 = std::move(clone);
+
+  // Check that the two objects are equal.
+  EXPECT_EQ(node1.method().full_name(), node2.method().full_name());
+  EXPECT_TRUE(google::protobuf::util::MessageDifferencer::Equals(node1.request(),
+                                                       node2.request()));
+  EXPECT_THAT(node2.dependencies(),
+              ElementsAre(Field(&RpcDataFlowEdge::from_node_id, kFromNodeId)));
+}
+
+TEST(RpcNodeTest, CopyConstructor) {
+  constexpr RpcNodeID kFromNodeId = 1;
+  const google::protobuf::MethodDescriptor* method_descriptor =
+      google::protobuf::DescriptorPool::generated_pool()->FindMethodByName(
+          "fuzztest.internal.MiniBlogger.RegisterUser");
+  CHECK(method_descriptor != nullptr);
+  RpcNode node1 =
+      RpcNode(*method_descriptor, std::make_unique<RegisterUserRequest>());
+  node1.AddDependency(RpcDataFlowEdge{kFromNodeId});
+
+  RpcNode node2(node1);
+
+  // Check that the two objects are equal.
+  EXPECT_EQ(node1.method().full_name(), node2.method().full_name());
+  EXPECT_TRUE(google::protobuf::util::MessageDifferencer::Equals(node1.request(),
+                                                       node2.request()));
+  EXPECT_THAT(node2.dependencies(),
+              ElementsAre(Field(&RpcDataFlowEdge::from_node_id, kFromNodeId)));
+}
+
+TEST(RpcNodeTest, EqualityTest) {
+  constexpr RpcNodeID kFromNodeId = 1;
+  const google::protobuf::MethodDescriptor* method_descriptor =
+      google::protobuf::DescriptorPool::generated_pool()->FindMethodByName(
+          "fuzztest.internal.MiniBlogger.RegisterUser");
+  ASSERT_NE(method_descriptor, nullptr);
+  RpcNode node1 =
+      RpcNode(*method_descriptor, std::make_unique<RegisterUserRequest>());
+  node1.AddDependency(RpcDataFlowEdge{kFromNodeId});
+
+  RpcNode node2(node1);
+
+  EXPECT_EQ(node1, node2);
+}
+
+TEST(RpcNodeTest, InEqualityTest) {
+  constexpr RpcNodeID kFromNodeId = 1;
+  const google::protobuf::MethodDescriptor* method_descriptor =
+      google::protobuf::DescriptorPool::generated_pool()->FindMethodByName(
+          "fuzztest.internal.MiniBlogger.RegisterUser");
+  ASSERT_NE(method_descriptor, nullptr);
+  RpcNode node1 =
+      RpcNode(*method_descriptor, std::make_unique<RegisterUserRequest>());
+  node1.AddDependency(RpcDataFlowEdge{kFromNodeId});
+
+  RpcNode node2 =
+      RpcNode(*method_descriptor, std::make_unique<RegisterUserRequest>());
+
+  EXPECT_NE(node1, node2);
+}
+
+TEST(RpcDataFlowEdgeTest, EqualityTest) {
+  RpcDataFlowEdge edge1 =
+      RpcDataFlowEdge{0, GetFieldPath<LogInUserResponse>("session_id"),
+                      GetFieldPath<GetUserPostsRequest>("session_id")};
+  RpcDataFlowEdge edge2 =
+      RpcDataFlowEdge{0, GetFieldPath<LogInUserResponse>("session_id"),
+                      GetFieldPath<GetUserPostsRequest>("session_id")};
+
+  EXPECT_EQ(edge1, edge2);
+}
+
+TEST(RpcDataFlowEdgeTest, InEqualityTest) {
+  RpcDataFlowEdge edge1 =
+      RpcDataFlowEdge{0, GetFieldPath<LogInUserResponse>("session_id"),
+                      GetFieldPath<GetUserPostsRequest>("session_id")};
+  RpcDataFlowEdge edge2 = RpcDataFlowEdge{
+      0, GetFieldPath<LogInUserResponse>("session_id"),
+      GetFieldPath<LogOutUserRequest>("log_out_info.session_info.session_id")};
+
+  EXPECT_NE(edge1, edge2);
+}
+
+class RpcDataFlowGraphTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    log_in_user_method_ =
+        google::protobuf::DescriptorPool::generated_pool()->FindMethodByName(
+            "fuzztest.internal.MiniBlogger.LogInUser");
+    log_out_user_method_ =
+        google::protobuf::DescriptorPool::generated_pool()->FindMethodByName(
+            "fuzztest.internal.MiniBlogger.LogOutUser");
+    register_user_method_ =
+        google::protobuf::DescriptorPool::generated_pool()->FindMethodByName(
+            "fuzztest.internal.MiniBlogger.RegisterUser");
+  }
+
+  RpcNode CreateLogInUserNode(RpcNodeID log_in_node_id) {
+    return RpcNode(*log_in_user_method_, std::make_unique<LogInUserRequest>());
+  }
+
+  RpcNode CreateRegisterUserNode(RpcNodeID log_out_node_id) {
+    return RpcNode(*register_user_method_,
+                   std::make_unique<RegisterUserRequest>());
+  }
+
+  std::pair<RpcNode, RpcNode> CreateLogInOutUserNodePairWithDep(
+      RpcNodeID log_in_node_id) {
+    // Set up a node for LogInUser and LogOutUser. Establish a dependency from
+    // LogInUserResponse.session_id to
+    // LogOutUserRequest.log_out_info.session_info.session_id.
+    RpcNode log_in_user_node(*log_in_user_method_,
+                             std::make_unique<LogInUserResponse>());
+    RpcNode log_out_user_node(*log_out_user_method_,
+                              std::make_unique<LogOutUserRequest>());
+    log_out_user_node.AddDependency(RpcDataFlowEdge{
+        log_in_node_id, GetFieldPath<LogInUserResponse>("session_id"),
+        GetFieldPath<LogOutUserRequest>(
+            "log_out_info.session_info.session_id")});
+    return std::make_pair(log_in_user_node, log_out_user_node);
+  }
+
+  const google::protobuf::MethodDescriptor *log_in_user_method_, *log_out_user_method_,
+      *register_user_method_;
+  RpcNodeID log_in_node_id_ = 0x123;
+  RpcNodeID log_out_node_id_ = 0x234;
+};
+
+TEST_F(RpcDataFlowGraphTest, GetSequenceReturnTologicalSortedSequence) {
+  RpcDataFlowGraph graph;
+  auto [log_in_user_node, log_out_user_node] =
+      CreateLogInOutUserNodePairWithDep(log_in_node_id_);
+  // Add LogOutUser first, so that the topological sort should change its order.
+  graph.AddNode(log_out_node_id_, log_out_user_node);
+  graph.AddNode(log_in_node_id_, log_in_user_node);
+
+  RpcSequence sequence = graph.GetSequence();
+  EXPECT_THAT(
+      sequence,
+      ElementsAre(Property(&RpcNode::method, Address(log_in_user_method_)),
+                  Property(&RpcNode::method, Address(log_out_user_method_))));
+}
+
+TEST_F(RpcDataFlowGraphTest, GetSequenceReMapAllTheRpcNodeIdToSequenceIndex) {
+  auto [log_in_user_node, log_out_user_node] =
+      CreateLogInOutUserNodePairWithDep(log_in_node_id_);
+  RpcDataFlowGraph graph;
+  graph.AddNode(log_in_node_id_, log_in_user_node);
+  graph.AddNode(log_out_node_id_, log_out_user_node);
+
+  RpcSequence sequence = graph.GetSequence();
+  EXPECT_EQ(&sequence[1].method(), log_out_user_method_);
+  EXPECT_THAT(sequence[1].dependencies(),
+              ElementsAre(Field(&RpcDataFlowEdge::from_node_id,
+                                0 /* remap `kNode1Id` to 0*/)));
+}
+
+TEST_F(RpcDataFlowGraphTest, GetSequenceReturnRandomizedTopoSortedSequence) {
+  const std::string_view expected_methods_of_first_nodes[] = {"LogInUser",
+                                                              "RegisterUser"};
+  absl::flat_hash_set<std::string> methods_of_first_nodes;
+  for (int i = 0; i < 100; ++i) {
+    RpcDataFlowGraph graph;
+    graph.AddNode(0, CreateLogInUserNode(log_in_node_id_));
+    graph.AddNode(1, CreateRegisterUserNode(log_out_node_id_));
+    methods_of_first_nodes.insert(graph.GetSequence()[0].method().name());
+  }
+
+  EXPECT_THAT(methods_of_first_nodes,
+              UnorderedElementsAreArray(expected_methods_of_first_nodes));
+}
+
+TEST_F(RpcDataFlowGraphTest, FromSequenceRecoversNodesAndOrdering) {
+  RpcSequence sequence = {CreateLogInUserNode(log_in_node_id_),
+                          CreateRegisterUserNode(log_out_node_id_)};
+  RpcDataFlowGraph graph = RpcDataFlowGraph::FromSequence(sequence);
+  EXPECT_EQ(graph.GetSequence(), sequence);
+}
+
+TEST_F(RpcDataFlowGraphTest,
+       GetSequenceReturnTheSameRandomizedTopoSortedSequence) {
+  RpcDataFlowGraph graph;
+  graph.AddNode(0, CreateLogInUserNode(log_in_node_id_));
+  graph.AddNode(1, CreateRegisterUserNode(log_out_node_id_));
+  absl::string_view method_name = graph.GetSequence()[0].method().name();
+  for (int i = 0; i < 100; ++i) {
+    EXPECT_EQ(method_name, graph.GetSequence()[0].method().name());
+  }
+}
+
+TEST_F(
+    RpcDataFlowGraphTest,
+    GetSequenceReturnDifferentRandomizedTopoSortedSequenceAfterModification) {
+  const std::string_view expected_methods_of_first_nodes[] = {"LogInUser",
+                                                              "RegisterUser"};
+  absl::flat_hash_set<std::string> methods_of_first_nodes;
+  RpcDataFlowGraph graph;
+  graph.AddNode(0, CreateLogInUserNode(log_in_node_id_));
+  graph.AddNode(1, CreateRegisterUserNode(log_out_node_id_));
+  for (int i = 0; i < 100; ++i) {
+    graph.RemoveNode(1);
+    graph.AddNode(1, CreateRegisterUserNode(log_out_node_id_));
+    methods_of_first_nodes.insert(graph.GetSequence()[0].method().name());
+  }
+  EXPECT_THAT(methods_of_first_nodes,
+              UnorderedElementsAreArray(expected_methods_of_first_nodes));
+}
+
+TEST_F(RpcDataFlowGraphTest,
+       GetRandomSequenceAlwaysReturnRandomizedTopoSortedSequence) {
+  const std::string_view expected_methods_of_first_nodes[] = {"LogInUser",
+                                                              "RegisterUser"};
+  absl::flat_hash_set<std::string> methods_of_first_nodes;
+  RpcDataFlowGraph graph;
+  graph.AddNode(0, CreateLogInUserNode(log_in_node_id_));
+  graph.AddNode(1, CreateRegisterUserNode(log_out_node_id_));
+  for (int i = 0; i < 100; ++i) {
+    graph.RandomizeTopologicalOrdering();
+    methods_of_first_nodes.insert(graph.GetSequence()[0].method().name());
+  }
+
+  EXPECT_THAT(methods_of_first_nodes,
+              UnorderedElementsAreArray(expected_methods_of_first_nodes));
+}
+
+}  // namespace
+
+}  // namespace fuzztest::internal
diff --git a/rpc_fuzzing/rpc_session.h b/rpc_fuzzing/rpc_session.h
new file mode 100644
index 0000000..588ace8
--- /dev/null
+++ b/rpc_fuzzing/rpc_session.h
@@ -0,0 +1,608 @@
+#ifndef FUZZTEST_RPC_FUZZING_RPC_SESSION_H_
+#define FUZZTEST_RPC_FUZZING_RPC_SESSION_H_
+
+#include <algorithm>
+#include <memory>
+#include <optional>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+#include "google/protobuf/descriptor.h"
+#include "google/protobuf/message.h"
+#include "absl/container/flat_hash_map.h"
+#include "absl/random/bit_gen_ref.h"
+#include "absl/status/status.h"
+#include "absl/strings/string_view.h"
+#include "./fuzztest/internal/domains/domain_base.h"
+#include "./fuzztest/internal/domains/protobuf_domain_impl.h"
+#include "./fuzztest/internal/domains/value_mutation_helpers.h"
+#include "./fuzztest/internal/logging.h"
+#include "./fuzztest/internal/serialization.h"
+#include "./fuzztest/internal/table_of_recent_compares.h"
+#include "./fuzztest/internal/type_support.h"
+#include "./rpc_fuzzing/proto_field_path.h"
+#include "./rpc_fuzzing/rpc_executor.h"
+#include "./rpc_fuzzing/rpc_potential_dfg.h"
+#include "./rpc_fuzzing/rpc_sequence.h"
+
+namespace fuzztest {
+
+namespace internal {
+
+// A helper domain that allows its inner domain to be lazily initialized.
+template <typename DomainT, typename... Args>
+class Lazy : public DomainBase<Lazy<DomainT, Args...>, value_type_t<DomainT>,
+                               corpus_type_t<DomainT>> {
+ public:
+  using typename Lazy::DomainBase::corpus_type;
+  using typename Lazy::DomainBase::value_type;
+
+  Lazy(Args&&... args) : args_(std::forward<Args>(args)...) {}
+
+  Lazy(const Lazy& other) {
+    if (other.inner_ != nullptr) {
+      inner_ = std::make_unique<DomainT>(*other.inner_);
+    }
+    args_ = other.args_;
+  }
+
+  Lazy(Lazy&& other) noexcept = default;
+  Lazy& operator=(Lazy&& other) = default;
+  corpus_type Init(absl::BitGenRef prng) { return GetInnerDomain().Init(prng); }
+
+  void Mutate(corpus_type& corpus_value, absl::BitGenRef prng,
+              bool shrink_only) {
+    GetInnerDomain().Mutate(corpus_value, prng, shrink_only);
+  }
+
+  value_type GetValue(const corpus_type& corpus_value) const {
+    return GetInnerDomain().GetValue(corpus_value);
+  }
+
+  std::optional<corpus_type> FromValue(const value_type& v) const {
+    return GetInnerDomain().FromValue(v);
+  }
+
+  std::optional<corpus_type> ParseCorpus(const IRObject& obj) const {
+    return GetInnerDomain().ParseCorpus(obj);
+  }
+
+  IRObject SerializeCorpus(const corpus_type& corpus_value) const {
+    return GetInnerDomain().SerializeCorpus(corpus_value);
+  }
+
+  absl::Status ValidateCorpusValue(const corpus_type& corpus_value) const {
+    return GetInnerDomain().ValidateCorpusValue(corpus_value);
+  }
+
+  auto GetPrinter() const { return GetInnerDomain().GetPrinter(); }
+
+ private:
+  template <std::size_t... Is>
+  std::unique_ptr<DomainT> CreateInner(std::tuple<Args...> tup,
+                                       std::index_sequence<Is...>) const {
+    return std::make_unique<DomainT>(std::move(std::get<Is>(tup))...);
+  }
+
+  const DomainT& GetInnerDomain() const {
+    if (inner_ == nullptr) {
+      inner_ = CreateInner(args_, std::index_sequence_for<Args...>{});
+    }
+    return *inner_;
+  }
+
+  DomainT& GetInnerDomain() {
+    if (inner_ == nullptr) {
+      inner_ = CreateInner(args_, std::index_sequence_for<Args...>{});
+    }
+    return *inner_;
+  }
+  mutable std::unique_ptr<DomainT> inner_ = nullptr;
+  mutable std::tuple<Args...> args_;
+};
+
+template <class T>
+const T& PickRandomElement(const std::vector<T>& vec, absl::BitGenRef prng) {
+  FUZZTEST_INTERNAL_CHECK(!vec.empty(),
+                          "Cannot pick elements from an empy vector.");
+  return vec[absl::Uniform(prng, size_t{0}, vec.size())];
+}
+
+// Rpc session domain. It generates RPC sequences with data-flows between
+// requests and responses of calls. The provided RpcSequence can be consumed by
+// the RpcExecutor.
+template <typename ServiceT = void>
+class RpcSessionImpl : public DomainBase<RpcSessionImpl<ServiceT>, RpcSequence,
+                                         RpcDataFlowGraph> {
+ public:
+  using typename RpcSessionImpl::DomainBase::corpus_type;
+  using typename RpcSessionImpl::DomainBase::value_type;
+
+  // `service_factory` should return the fully qualified service name such as
+  // `package.Service`.
+  RpcSessionImpl(std::function<absl::string_view()> service_factory)
+      : abstract_dfg_(CreatePotentialDfgByServiceName(service_factory())) {
+    const google::protobuf::ServiceDescriptor& desc =
+        GetServiceDescriptorByServiceName(service_factory());
+    Initialize(desc);
+  }
+
+  template <typename T = ServiceT,
+            typename = std::enable_if_t<is_stubby_service<T>::value ||
+                                        is_grpc_service<T>::value ||
+                                        std::is_same_v<T, google::protobuf::Service>>>
+  RpcSessionImpl() : abstract_dfg_(CreatePotentialDfg<ServiceT>()) {
+    const google::protobuf::ServiceDescriptor& desc = GetServiceDescriptor<ServiceT>();
+    Initialize(desc);
+  }
+
+  // Generate a sequence containing only a single rpc call. Such calls should be
+  // the "roots" in the data flow graph and depend on no other nodes.
+  corpus_type Init(absl::BitGenRef prng) {
+    const google::protobuf::MethodDescriptor& method =
+        *PickRandomElement(all_methods_, prng);
+    RpcDataFlowGraph result;
+    auto& request_domain = GetRequestDomain(method);
+    RpcNode call_node(method,
+                      request_domain.GetValue(request_domain.Init(prng)));
+    result.AddNode(0 /* the first node */, std::move(call_node));
+    return result;
+  }
+
+  void Mutate(corpus_type& graph, absl::BitGenRef prng, bool only_shrink) {
+    if (only_shrink) {
+      RunOne(
+          prng, [&] { DeleteTailCall(graph, prng); },
+          [&] { MutateStaticField(graph, prng, only_shrink); });
+    } else {
+      RunOne(
+          prng, [&] { InsertTailCall(graph, prng); },
+          [&] { DeleteTailCall(graph, prng); },
+          [&] { MutateStaticField(graph, prng, only_shrink); });
+    }
+  }
+
+  value_type GetValue(const corpus_type& graph) const {
+    return graph.GetSequence();
+  }
+
+  std::optional<corpus_type> FromValue(const value_type& v) const {
+    corpus_type result;
+    for (size_t i = 0; i < v.size(); ++i) {
+      result.AddNode(i, v[i]);
+    }
+    return result;
+  }
+
+  std::optional<corpus_type> ParseCorpus(const IRObject& obj) const {
+    auto subs = obj.Subs();
+    if (!subs || subs->empty()) {
+      return std::nullopt;
+    }
+    RpcSequence sequence;
+    sequence.reserve(subs->size());
+    for (const auto& node_obj : *subs) {
+      if (!ValidateRpcNodeObjFormat(node_obj)) return std::nullopt;
+      auto node = ParseRpcNodeMethodAndRequest(node_obj);
+      if (!node) return std::nullopt;
+      auto edges_sub = (*node_obj.Subs())[2].Subs();
+      for (const auto& edge_obj : *edges_sub) {
+        std::optional<RpcDataFlowEdge> edge =
+            ParseRpcDataFlowEdge(edge_obj, sequence, node->method());
+        if (!edge) return std::nullopt;
+        node->AddDependency(*edge);
+      }
+      sequence.push_back(*std::move(node));
+    }
+    return RpcDataFlowGraph::FromSequence(sequence);
+  }
+
+  // Serializes the graph in the topological-sorting order.
+  IRObject SerializeCorpus(const corpus_type& graph) const {
+    IRObject result;
+    std::vector<IRObject>& result_subs = result.MutableSubs();
+    // We serialize the sequence instead to avoid storing the RpcNodeID.
+    for (const RpcNode& node : graph.GetSequence()) {
+      result_subs.push_back(SerializeRpcNode(node));
+    }
+    return result;
+  }
+
+  // TODO(changochen): Implemented in later CL.
+  auto GetPrinter() const { return MonostatePrinter{}; }
+
+  // Check whether every RpcNode:
+  // 1. Only depend on previous nodes. (No cyclic dependencies)
+  // 2. Every dependency matches one of the potential dependencies.
+  absl::Status ValidateCorpusValue(const corpus_type& corpus_value) const {
+    absl::flat_hash_set<RpcNodeID> previous_node_ids;
+    for (RpcNodeID node_id : corpus_value.GetOrderedNodeIds()) {
+      const RpcNode& node = corpus_value.GetNode(node_id);
+      const RpcPotentialDfgNode& dfg_node =
+          abstract_dfg_.GetNode(node.method());
+      absl::flat_hash_set<FieldPath> all_sink_paths;
+      for (const RpcDataFlowEdge& edge : node.dependencies()) {
+        // The current node should only depend on previous nodes.
+        if (!previous_node_ids.contains(edge.from_node_id))
+          return absl::InvalidArgumentError(
+              "The dependencies should only come from previously executed "
+              "nodes.");
+        if (!dfg_node.GetAllDependencies().contains(edge.to_field)) {
+          return absl::InvalidArgumentError(
+              "The sink field should be defined in the potential data flow "
+              "graph.");
+        }
+
+        if (all_sink_paths.contains(edge.to_field)) {
+          return absl::InvalidArgumentError(
+              "One sink field should have at most one concrete dependency!");
+        }
+        all_sink_paths.insert(edge.to_field);
+
+        // Check whether the dependency source from `edge` actually matches one
+        // of the potential dependency source.
+        if (!HasPotentialSourceForEdge(edge, corpus_value, dfg_node)) {
+          return absl::InvalidArgumentError(
+              "The dependency is not defined in the potential data flow "
+              "graph.");
+        }
+      }
+
+      previous_node_ids.insert(node_id);
+    }
+    return absl::OkStatus();
+  }
+
+ private:
+  void Initialize(const google::protobuf::ServiceDescriptor& desc) {
+    for (int i = 0; i < desc.method_count(); ++i) {
+      const google::protobuf::MethodDescriptor* method = desc.method(i);
+      all_methods_.push_back(method);
+
+      std::function<const google::protobuf::Message*()> get_request_prototype =
+          [method]() {
+            return google::protobuf::MessageFactory::generated_factory()->GetPrototype(
+                method->input_type());
+          };
+      request_domains_.emplace(
+          method, ProtobufDomainUntypedImpl<google::protobuf::Message>(
+                      PrototypePtr<google::protobuf::Message>(get_request_prototype),
+                      /*use_lazy_initialization=*/false));
+    }
+  }
+
+  bool AllPotentialDepsInDfgNodeCanBeSatisfied(
+      const absl::flat_hash_set<FieldPath>& satisfied_field_paths,
+      const RpcPotentialDfgNode& dfg_node) const {
+    for (const auto& [field_path, unused] : dfg_node.GetAllDependencies()) {
+      if (!satisfied_field_paths.contains(field_path)) {
+        bool can_be_satisfied = false;
+        for (const FieldPath& satisfied_field_path : satisfied_field_paths) {
+          if (AreOneOfAltearnatives(satisfied_field_path, field_path)) {
+            can_be_satisfied = true;
+            break;
+          }
+        }
+        if (!can_be_satisfied) return false;
+      }
+    }
+    return true;
+  }
+  // Check whether the source of the edge is defined in the potential data flow
+  // graph.
+  bool HasPotentialSourceForEdge(const RpcDataFlowEdge& edge,
+                                 const RpcDataFlowGraph& graph,
+                                 const RpcPotentialDfgNode& dfg_node) const {
+    for (const RpcPotentialDfgNode::PotentialDependencySource&
+             potential_source : dfg_node.GetDependencies(edge.to_field)) {
+      const RpcNode& from_node = graph.GetNode(edge.from_node_id);
+      if (potential_source.method == &from_node.method() &&
+          potential_source.field_path == edge.from_field) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  bool ValidateRpcNodeObjFormat(const IRObject& obj) const {
+    auto node_subs = obj.Subs();
+    // Each node has a sub of size 3: method, request, dependencies.
+    if (!node_subs || node_subs->size() != 3) {
+      return false;
+    }
+    auto edge_subs = (*node_subs)[2].Subs();
+    if (!edge_subs) {
+      return false;
+    }
+    // Each edge has a sub of size 3: from_id, from_field, to_field.
+    for (const auto& edge_sub : *edge_subs) {
+      if (!edge_sub.Subs() || edge_sub.Subs()->size() != 3) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  IRObject SerializeRpcDataFlowEdge(const RpcDataFlowEdge& edge) const {
+    IRObject edge_obj;
+    std::vector<IRObject>& edge_subs = edge_obj.MutableSubs();
+    edge_subs.push_back(IRObject::FromCorpus(edge.from_node_id));
+    edge_subs.push_back(IRObject::FromCorpus(edge.from_field.ToString()));
+    edge_subs.push_back(IRObject::FromCorpus(edge.to_field.ToString()));
+    return edge_obj;
+  }
+
+  IRObject SerializeRpcNode(const RpcNode& node) const {
+    // The serialized object will be like
+    // [ method_name, request_proto_buf_string, [edges]]
+    IRObject node_obj;
+    std::vector<IRObject>& node_subs = node_obj.MutableSubs();
+    node_subs.push_back(IRObject::FromCorpus(node.method().full_name()));
+    node_subs.push_back(
+        IRObject::FromCorpus(node.request().SerializeAsString()));
+    IRObject edges_obj;
+    if (!node.dependencies().empty()) {
+      std::vector<IRObject>& edges_subs = edges_obj.MutableSubs();
+      for (const RpcDataFlowEdge& edge : node.dependencies()) {
+        edges_subs.push_back(SerializeRpcDataFlowEdge(edge));
+      }
+    }
+    node_subs.push_back(std::move(edges_obj));
+    return node_obj;
+  }
+
+  std::optional<RpcDataFlowEdge> ParseRpcDataFlowEdge(
+      const IRObject& edge_obj, const RpcSequence& sequence,
+      const google::protobuf::MethodDescriptor& to_method) const {
+    auto edge_subs = edge_obj.Subs();
+    FUZZTEST_INTERNAL_CHECK(edge_subs && edge_subs->size() == 3,
+                            "Invalid edge format!");
+    auto from_node_id = (*edge_subs)[0].ToCorpus<RpcNodeID>();
+    auto from_field_str = (*edge_subs)[1].ToCorpus<std::string>();
+    auto to_field_str = (*edge_subs)[2].ToCorpus<std::string>();
+    if (!from_node_id || !from_field_str || !to_field_str) {
+      return std::nullopt;
+    }
+    if (*from_node_id >= sequence.size()) return std::nullopt;
+    FieldPath from_field_path = GetFieldPathWithDescriptor(
+        *sequence[*from_node_id].method().output_type(), *from_field_str);
+    FieldPath to_field_path =
+        GetFieldPathWithDescriptor(*to_method.input_type(), *to_field_str);
+    return RpcDataFlowEdge{*from_node_id, from_field_path, to_field_path};
+  }
+
+  std::optional<RpcNode> ParseRpcNodeMethodAndRequest(
+      const IRObject& obj) const {
+    const google::protobuf::DescriptorPool& pool =
+        *google::protobuf::DescriptorPool::generated_pool();
+    auto node_subs = obj.Subs();
+    std::optional<std::string> method_name =
+        (*node_subs)[0].ToCorpus<std::string>();
+    if (!method_name) return std::nullopt;
+    const google::protobuf::MethodDescriptor* method =
+        pool.FindMethodByName(*method_name);
+
+    if (!method) return std::nullopt;
+
+    std::optional<std::string> request_str =
+        (*node_subs)[1].ToCorpus<std::string>();
+
+    std::unique_ptr<google::protobuf::Message> request =
+        absl::WrapUnique(google::protobuf::MessageFactory::generated_factory()
+                             ->GetPrototype(method->input_type())
+                             ->New());
+    if (!request->ParseFromString(*request_str)) return std::nullopt;
+
+    return RpcNode(*method, std::move(request));
+  }
+
+  absl::flat_hash_set<FieldPath> CollectSatisfiableFields(
+      const RpcPotentialDfgNode& dfg_node,
+      const absl::flat_hash_map<const google::protobuf::MethodDescriptor*,
+                                std::vector<RpcNodeID>>& existing_nodes) {
+    absl::flat_hash_set<FieldPath> satisfied_fields;
+    for (const auto& [field_path, source_vec] : dfg_node.GetAllDependencies()) {
+      for (const RpcPotentialDfgNode::PotentialDependencySource& source :
+           source_vec) {
+        if (existing_nodes.contains(source.method)) {
+          satisfied_fields.insert(field_path);
+          break;
+        }
+      }
+    }
+    return satisfied_fields;
+  }
+
+  std::vector<FieldPath> RemoveAlternativeFieldPath(
+      const absl::flat_hash_set<FieldPath>& field_paths, absl::BitGenRef prng) {
+    std::vector<FieldPath> result;
+    std::vector<FieldPath> field_paths_vec(field_paths.begin(),
+                                           field_paths.end());
+    std::shuffle(field_paths_vec.begin(), field_paths_vec.end(), prng);
+    for (const FieldPath& field_path : field_paths_vec) {
+      bool found_same_one_of_field = false;
+      for (const FieldPath& other_field_path : result) {
+        if (AreOneOfAltearnatives(field_path, other_field_path)) {
+          found_same_one_of_field = true;
+          break;
+        }
+      }
+      if (!found_same_one_of_field) {
+        result.push_back(field_path);
+      }
+    }
+    return result;
+  }
+
+  // Checks whether all the dependencies of `dfg_node` can be satisfied by the
+  // nodes in `existing_nodes`. "Satisfy" means there is at least one
+  // potential dependency of any field in the node that has its source in the
+  // rpc data flow graph.
+  bool DependsOnAndCanBeSatisfiedBy(
+      const RpcPotentialDfgNode& dfg_node,
+      const absl::flat_hash_map<const google::protobuf::MethodDescriptor*,
+                                std::vector<RpcNodeID>>& existing_nodes) {
+    if (!dfg_node.HasDependency()) return false;
+    absl::flat_hash_set<FieldPath> satisfied_fields =
+        CollectSatisfiableFields(dfg_node, existing_nodes);
+    // As long as we can satisfy any of the sink field, we consider it as
+    // satisfiable.
+    return !satisfied_fields.empty();
+  }
+
+  // Randomly selects a potential dependency for each dynamic field in `node`
+  // and establishes the dependency between the node and the dependency source.
+  // As we might have multiple calls to the same method in the existing graph
+  // that can serve as the source, we randomly select one of them.
+  void ConnectToExistingNodes(
+      const absl::flat_hash_map<const google::protobuf::MethodDescriptor*,
+                                std::vector<RpcNodeID>>
+          existing_nodes,
+      RpcNode& node, absl::BitGenRef prng) {
+    const RpcPotentialDfgNode& dfg_node = abstract_dfg_.GetNode(node.method());
+
+    // If we have multiple satisfiable field paths that are alternative to each
+    // other (e.g., in the same `oneof`), we only need to pick one of them and
+    // establish the concrete dependency.
+    std::vector<FieldPath> satisfied_fields = RemoveAlternativeFieldPath(
+        CollectSatisfiableFields(dfg_node, existing_nodes), prng);
+    FUZZTEST_INTERNAL_CHECK(!satisfied_fields.empty(), "Impossible");
+    for (const FieldPath& sink_field : satisfied_fields) {
+      const auto& source_vec = dfg_node.GetDependencies(sink_field);
+      std::vector<const RpcPotentialDfgNode::PotentialDependencySource*>
+          satisfiable_sources;
+      for (const RpcPotentialDfgNode::PotentialDependencySource& source :
+           source_vec) {
+        if (existing_nodes.contains(source.method))
+          satisfiable_sources.push_back(&source);
+      }
+      if (satisfiable_sources.empty()) continue;
+      FUZZTEST_INTERNAL_CHECK(!satisfiable_sources.empty(),
+                              "Some dependencies are not satisified!");
+      const RpcPotentialDfgNode::PotentialDependencySource& chosen_source =
+          *PickRandomElement(satisfiable_sources, prng);
+      const std::vector<RpcNodeID>& candidate_nodes =
+          existing_nodes.find(chosen_source.method)->second;
+      node.AddDependency(
+          RpcDataFlowEdge{PickRandomElement(candidate_nodes, prng),
+                          chosen_source.field_path, sink_field});
+    }
+  }
+
+  // Inserts a tail call to the graph. Returns true if a call is inserted.
+  bool InsertTailCall(corpus_type& graph, absl::BitGenRef prng) {
+    absl::flat_hash_map<const google::protobuf::MethodDescriptor*, std::vector<RpcNodeID>>
+        existing_nodes;
+    RpcNodeID max_node_id = 0;
+    for (const auto& [node_id, node] : graph.GetAllNodes()) {
+      existing_nodes[&node.method()].push_back(node_id);
+      max_node_id = std::max(max_node_id, node_id);
+    }
+    RpcNodeID new_node_id = max_node_id + 1;
+
+    // Collect all the methods that can be inserted: They depend on existing
+    // nodes and all their their dependencies can be satisfied by existing
+    // nodes.
+    std::vector<const google::protobuf::MethodDescriptor*> candidate_methods;
+    for (auto& [method, unused_domain] : request_domains_) {
+      const RpcPotentialDfgNode& dfg_node = abstract_dfg_.GetNode(*method);
+      if (DependsOnAndCanBeSatisfiedBy(dfg_node, existing_nodes)) {
+        candidate_methods.push_back(method);
+      }
+    }
+
+    if (candidate_methods.empty()) return false;
+
+    const google::protobuf::MethodDescriptor& method_to_insert =
+        *PickRandomElement(candidate_methods, prng);
+    auto& request_domain = GetRequestDomain(method_to_insert);
+    RpcNode node_to_insert(method_to_insert,
+                           request_domain.GetValue(request_domain.Init(prng)));
+    ConnectToExistingNodes(existing_nodes, node_to_insert, prng);
+    graph.AddNode(new_node_id, std::move(node_to_insert));
+    return true;
+  }
+
+  bool DeleteTailCall(corpus_type& graph, absl::BitGenRef prng) {
+    if (graph.NodeNum() == 1) return false;
+    // Non-tail nodes (nodes with dependents).
+    absl::flat_hash_set<RpcNodeID> non_tail_nodes;
+    for (const auto& [node_id, node] : graph.GetAllNodes()) {
+      for (const auto& edge : node.dependencies()) {
+        non_tail_nodes.insert(edge.from_node_id);
+      }
+    }
+    FUZZTEST_INTERNAL_CHECK(non_tail_nodes.size() != graph.NodeNum(),
+                            "Loop dependence!");
+    std::vector<RpcNodeID> tail_nodes;
+    for (const auto& [node_id, node] : graph.GetAllNodes()) {
+      if (!non_tail_nodes.contains(node_id)) tail_nodes.push_back(node_id);
+    }
+
+    graph.RemoveNode(PickRandomElement(tail_nodes, prng));
+    return true;
+  }
+
+  bool MutateStaticField(corpus_type& graph, absl::BitGenRef prng,
+                         bool only_shrink) {
+    RpcNode& selected_node = std::next(graph.GetAllNodes().begin(),
+                                       ChooseOffset(graph.NodeNum(), prng))
+                                 ->second;
+    auto& request_domain = GetRequestDomain(selected_node.method());
+    auto request_domain_corpus_value =
+        request_domain.FromValue(selected_node.request());
+    FUZZTEST_INTERNAL_CHECK(request_domain_corpus_value.has_value(),
+                            "Invalid proto!");
+    request_domain.Mutate(*request_domain_corpus_value, prng, only_shrink);
+    selected_node.request().CopyFrom(
+        *request_domain.GetValue(*request_domain_corpus_value));
+    return true;
+  }
+
+  ProtobufDomainUntypedImpl<google::protobuf::Message>& GetRequestDomain(
+      const google::protobuf::MethodDescriptor& method) {
+    auto iter = request_domains_.find(&method);
+    FUZZTEST_INTERNAL_CHECK(iter != request_domains_.end(), "Invalid method!");
+    return iter->second;
+  }
+
+  // We keep a domain for the request type for each method in the service to
+  // generate the request message in the RpcNode.
+  // TODO(changochen): We are now directly storing the concrete requests in
+  // RpcNode, and serialize them as string. We should use the corpus_type of the
+  // proto domain in the future to allow proto customization.
+  absl::flat_hash_map<const google::protobuf::MethodDescriptor*,
+                      ProtobufDomainUntypedImpl<google::protobuf::Message>>
+      request_domains_;
+  std::vector<const google::protobuf::MethodDescriptor*> all_methods_;
+  RpcPotentialDataFlowGraph abstract_dfg_;
+};
+}  // namespace internal
+
+using internal::RpcSequence;
+
+inline absl::Status ExecuteRpcSequence(RpcStub& stub, RpcSequence& sequence) {
+  return internal::RpcExecutor(&stub).Execute(sequence);
+}
+
+template <typename ServiceT>
+inline auto RpcSession() {
+  return internal::Lazy<internal::RpcSessionImpl<ServiceT>>();
+}
+
+// Creates an RpcSession with a service name factory. This is for when the
+// service name is unknown at compile time (i.e., getting the name from the
+// generated pool.). This factory function will be called after main().
+// If the service name/type is known at compile time, please use the template
+// version above: RpcSession<ServiceT>().
+inline auto RpcSessionOf(
+    std::function<absl::string_view()> service_name_factory) {
+  FUZZTEST_INTERNAL_CHECK(service_name_factory, "Invalid service factory!");
+  return internal::Lazy<internal::RpcSessionImpl<>,
+                        std::function<absl::string_view()>>(
+      std::move(service_name_factory));
+}
+
+}  // namespace fuzztest
+#endif  // FUZZTEST_RPC_FUZZING_RPC_SESSION_H_
diff --git a/rpc_fuzzing/rpc_session_test.cc b/rpc_fuzzing/rpc_session_test.cc
new file mode 100644
index 0000000..3b07361
--- /dev/null
+++ b/rpc_fuzzing/rpc_session_test.cc
@@ -0,0 +1,501 @@
+#include "./rpc_fuzzing/rpc_session.h"
+
+#include <cstdint>
+#include <optional>
+#include <variant>
+
+#include "google/protobuf/descriptor.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/random/random.h"
+#include "./domain_tests/domain_testing.h"
+#include "./fuzztest/internal/serialization.h"
+#include "./rpc_fuzzing/proto_field_path.h"
+#include "./rpc_fuzzing/rpc_potential_dfg.h"
+#include "./rpc_fuzzing/rpc_sequence.h"
+#include "./rpc_fuzzing/testdata/mini_blogger.pb.h"
+#include "./rpc_fuzzing/testdata/mini_blogger.grpc.pb.h"
+
+namespace fuzztest::internal {
+
+namespace {
+
+using ::testing::AnyOf;
+using ::testing::Conditional;
+using ::testing::ElementsAre;
+using ::testing::Eq;
+using ::testing::FieldsAre;
+using ::testing::IsTrue;
+using ::testing::NanSensitiveDoubleEq;
+using ::testing::Optional;
+using ::testing::Pair;
+using ::testing::ResultOf;
+using ::testing::UnorderedElementsAre;
+using ::testing::UnorderedElementsAreArray;
+using ::testing::VariantWith;
+
+template <typename T>
+auto ValueIs(const T& v) {
+  if constexpr (std::is_same_v<T, double>) {
+    return FieldsAre(VariantWith<double>(NanSensitiveDoubleEq(v)));
+  } else {
+    return FieldsAre(VariantWith<T>(v));
+  }
+}
+
+template <typename... T>
+auto SubsAre(const T&... v) {
+  return FieldsAre(VariantWith<std::vector<IRObject>>(ElementsAre(v...)));
+}
+
+class RpcDomainTest : public ::testing::Test {
+ public:
+  RpcDomainTest()
+      : pool_(*ABSL_DIE_IF_NULL(google::protobuf::DescriptorPool::generated_pool())),
+        mini_blogger_serivce_(*ABSL_DIE_IF_NULL(
+            pool_.FindServiceByName("fuzztest.internal.MiniBlogger"))),
+        log_in_user_method_(*ABSL_DIE_IF_NULL(
+            mini_blogger_serivce_.FindMethodByName("LogInUser"))),
+        log_out_user_method_(*ABSL_DIE_IF_NULL(
+            mini_blogger_serivce_.FindMethodByName("LogOutUser"))),
+        get_user_posts_method_(*ABSL_DIE_IF_NULL(
+            mini_blogger_serivce_.FindMethodByName("GetUserPosts"))) {}
+
+ protected:
+  RpcNode GetLogInUserNode() const {
+    return RpcNode(log_in_user_method_, std::make_unique<LogInUserRequest>());
+  }
+
+  RpcNode GetLogOutUserNode() const {
+    return RpcNode(log_out_user_method_, std::make_unique<LogOutUserRequest>());
+  }
+
+  RpcNode GetGetUserPostsNode() const {
+    return RpcNode(get_user_posts_method_,
+                   std::make_unique<GetUserPostsRequest>());
+  }
+
+  RpcDataFlowGraph CreateRandomGraph(RpcSessionImpl<MiniBlogger>& rpc_domain) {
+    absl::BitGen gen;
+    RpcDataFlowGraph graph = rpc_domain.Init(gen);
+    for (int i = 0; i < 100; ++i) {
+      rpc_domain.Mutate(graph, gen, false);
+    }
+    // Generate an order.
+    (void)graph.GetOrderedNodeIds();
+    return graph;
+  }
+
+  const google::protobuf::DescriptorPool& pool_;
+  const google::protobuf::ServiceDescriptor& mini_blogger_serivce_;
+  const google::protobuf::MethodDescriptor& log_in_user_method_;
+  const google::protobuf::MethodDescriptor& log_out_user_method_;
+  const google::protobuf::MethodDescriptor& get_user_posts_method_;
+};
+
+TEST_F(RpcDomainTest, InitGeneratesARandomSingleCallWithoutDependencies) {
+  RpcSessionImpl<MiniBlogger> rpc_domain;
+  absl::BitGen bitgen;
+  for (int i = 0; i < 100; ++i) {
+    RpcDataFlowGraph value = rpc_domain.Init(bitgen);
+    EXPECT_THAT(value.GetAllNodes(),
+                ElementsAre(Pair(0, ResultOf(
+                                        [](const RpcNode& node) {
+                                          return node.dependencies().empty();
+                                        },
+                                        IsTrue()))));
+  }
+}
+
+TEST_F(RpcDomainTest, MutationOnlyInsertsNodesThatDependOnExistingNodes) {
+  constexpr std::string_view kMethodsDependingLogInUser[] = {"GetUserPosts",
+                                                             "LogOutUser"};
+
+  RpcDataFlowGraph graph;
+  graph.AddNode(0, GetLogInUserNode());
+  absl::flat_hash_set<std::string> inserted_methods;
+  RpcSessionImpl<MiniBlogger> rpc_domain;
+  absl::BitGen bitgen;
+  for (int j = 0; j < 100; ++j) {
+    RpcDataFlowGraph mutated_graph = graph;
+    rpc_domain.Mutate(mutated_graph, bitgen, false);
+    if (mutated_graph.NodeNum() != 2) continue;
+    // Mutated through insertion.
+    inserted_methods.insert(mutated_graph.GetSequence()[1].method().name());
+  }
+  EXPECT_THAT(inserted_methods,
+              UnorderedElementsAreArray(kMethodsDependingLogInUser));
+}
+
+TEST_F(RpcDomainTest, InsertedNodesHaveDependenciesOnExistingNodes) {
+  constexpr RpcNodeID kFromNodeID = 0;
+  RpcDataFlowGraph graph;
+  graph.AddNode(kFromNodeID, GetLogInUserNode());
+
+  bool insertion_triggerred = false;
+  RpcSessionImpl<MiniBlogger> rpc_domain;
+  absl::BitGen bitgen;
+  for (int j = 0; j < 100; ++j) {
+    RpcDataFlowGraph mutated_graph = graph;
+    rpc_domain.Mutate(mutated_graph, bitgen, false);
+    if (mutated_graph.NodeNum() != 2) continue;
+    // Mutated through insertion.
+    insertion_triggerred = true;
+
+    const RpcNode inserted_node = mutated_graph.GetSequence()[1];
+    EXPECT_THAT(
+        inserted_node.dependencies(),
+        ElementsAre(FieldsAre(
+            kFromNodeID, GetFieldPath<LogInUserResponse>("session_id"),
+            Conditional(inserted_node.method().name() == "LogOutUser",
+                        AnyOf(GetFieldPath<LogOutUserRequest>(
+                                  "log_out_info.session_info.session_id"),
+                              GetFieldPath<LogOutUserRequest>(
+                                  "log_out_info.session_id")),
+                        GetFieldPath<GetUserPostsRequest>("session_id")))));
+  }
+
+  EXPECT_TRUE(insertion_triggerred);
+}
+
+TEST_F(RpcDomainTest, InsertedNodeSelectsRandomAlternativeDepWithinSameOneOf) {
+  constexpr RpcNodeID kFromNodeID = 0;
+  RpcDataFlowGraph graph;
+  graph.AddNode(kFromNodeID, GetLogInUserNode());
+
+  RpcSessionImpl<MiniBlogger> rpc_domain;
+  absl::flat_hash_set<std::string> sink_fields;
+  absl::BitGen bitgen;
+  for (int j = 0; j < 200; ++j) {
+    RpcDataFlowGraph mutated_graph = graph;
+    rpc_domain.Mutate(mutated_graph, bitgen, false);
+    if (mutated_graph.NodeNum() != 2 ||
+        mutated_graph.GetSequence()[1].method().name() != "LogOutUser")
+      continue;
+    // Either `log_out_info.session_info.session_id` or
+    // `log_out_info.session_id` will be selected as the sink.
+    EXPECT_EQ(mutated_graph.GetSequence()[1].dependencies().size(), 1);
+    sink_fields.insert(
+        mutated_graph.GetSequence()[1].dependencies()[0].to_field.ToString());
+  }
+  EXPECT_THAT(sink_fields,
+              UnorderedElementsAre("log_out_info.session_info.session_id",
+                                   "log_out_info.session_id"));
+}
+
+TEST_F(RpcDomainTest, MutationOnlyDeletesTailNodes) {
+  constexpr std::string_view kMethodsDependingLogInUser[] = {"GetUserPosts",
+                                                             "LogOutUser"};
+  // Node id 0.
+  RpcNode log_in_user_node = GetLogInUserNode();
+  // Node id 1.
+  RpcNode log_out_user_node = GetLogOutUserNode();
+  // Node id 2.
+  RpcNode get_user_posts_node = GetGetUserPostsNode();
+
+  log_out_user_node.AddDependency(RpcDataFlowEdge{
+      0, GetFieldPath<LogInUserResponse>("session_id"),
+      GetFieldPath<LogOutUserRequest>("log_out_info.session_info.session_id")});
+
+  get_user_posts_node.AddDependency(
+      RpcDataFlowEdge{0, GetFieldPath<LogInUserResponse>("session_id"),
+                      GetFieldPath<GetUserPostsRequest>("session_id")});
+
+  RpcDataFlowGraph graph;
+  graph.AddNode(0, log_in_user_node);
+  graph.AddNode(1, log_out_user_node);
+  graph.AddNode(2, get_user_posts_node);
+
+  absl::flat_hash_set<std::string> deleted_methods;
+  RpcSessionImpl<MiniBlogger> rpc_domain;
+  absl::BitGen bitgen;
+
+  for (int j = 0; j < 100; ++j) {
+    RpcDataFlowGraph mutated_graph = graph;
+    rpc_domain.Mutate(mutated_graph, bitgen, false);
+    if (mutated_graph.NodeNum() != 2) continue;
+    // Mutated through deletion.
+    if (mutated_graph.GetSequence()[1].method().name() == "LogOutUser") {
+      deleted_methods.insert("GetUserPosts");
+    } else {
+      deleted_methods.insert("LogOutUser");
+    }
+  }
+  EXPECT_THAT(deleted_methods,
+              UnorderedElementsAreArray(kMethodsDependingLogInUser));
+}
+
+TEST_F(RpcDomainTest, OnlyShrinkMutationDecreasesNodeNumOrRequest) {
+  RpcNode log_in_user_node = GetLogInUserNode();
+  auto get_user_post_request = std::make_unique<GetUserPostsRequest>();
+  get_user_post_request->set_max_posts(100);
+  RpcNode log_out_user_node(get_user_posts_method_,
+                            std::move(get_user_post_request));
+  log_out_user_node.AddDependency(RpcDataFlowEdge{
+      0, GetFieldPath<LogInUserResponse>("session_id"),
+      GetFieldPath<LogOutUserRequest>("log_out_info.session_info.session_id")});
+
+  RpcDataFlowGraph graph;
+  graph.AddNode(0, log_in_user_node);
+  graph.AddNode(1, log_out_user_node);
+
+  RpcSessionImpl<MiniBlogger> rpc_domain;
+  absl::BitGen bitgen;
+  bool static_field_shrinked = false;
+  for (int i = 0; i < 100; ++i) {
+    RpcDataFlowGraph mutated_graph = graph;
+    rpc_domain.Mutate(mutated_graph, bitgen, true);
+
+    if (mutated_graph.NodeNum() == 2) {
+      const RpcNode& node = mutated_graph.GetNode(1);
+      int new_max_posts = node.request().GetReflection()->GetInt32(
+          node.request(),
+          node.request().GetDescriptor()->FindFieldByName("max_posts"));
+      EXPECT_LE(new_max_posts, 100);
+      if (new_max_posts < 100) {
+        static_field_shrinked = true;
+      }
+    } else {
+      EXPECT_EQ(mutated_graph.NodeNum(), 1);
+    }
+  }
+  EXPECT_TRUE(static_field_shrinked);
+}
+
+TEST_F(RpcDomainTest, MutateEventuallyChangesStaticFieldInRequest) {
+  RpcDataFlowGraph graph;
+  graph.AddNode(0, GetLogInUserNode());
+
+  RpcSessionImpl<MiniBlogger> rpc_domain;
+  absl::BitGen bitgen;
+  bool static_field_changed = false;
+  for (int i = 0; i < 100; ++i) {
+    RpcDataFlowGraph mutated_graph = graph;
+    rpc_domain.Mutate(mutated_graph, bitgen, false);
+    if (mutated_graph.NodeNum() == graph.NodeNum() &&
+        mutated_graph.GetNode(0).method().name() ==
+            graph.GetNode(0).method().name()) {
+      // Mutated through static field mutation.
+      if (!google::protobuf::util::MessageDifferencer::Equals(
+              mutated_graph.GetNode(0).request(), graph.GetNode(0).request())) {
+        static_field_changed = true;
+        break;
+      }
+    }
+  }
+
+  EXPECT_TRUE(static_field_changed);
+}
+
+TEST_F(RpcDomainTest, FromValueTransformsSequenceToRpcGraph) {
+  RpcSequence sequence = {GetLogInUserNode(), GetLogOutUserNode(),
+                          GetGetUserPostsNode()};
+
+  RpcSessionImpl<MiniBlogger> rpc_domain;
+  std::optional<RpcDataFlowGraph> graph = rpc_domain.FromValue(sequence);
+
+  ASSERT_TRUE(graph.has_value());
+
+  ASSERT_EQ(graph->NodeNum(), 3);
+  std::vector<RpcNode> all_nodes{graph->GetNode(0), graph->GetNode(1),
+                                 graph->GetNode(2)};
+  EXPECT_EQ(all_nodes, sequence);
+}
+
+TEST_F(RpcDomainTest, ParseCorpusInClearTextFromReturnsRpcDataFlowGraph) {
+  constexpr absl::string_view kObjectText =
+      R"(FUZZTESTv1
+        sub {
+          sub { s: "fuzztest.internal.MiniBlogger.LogInUser" }
+          sub { s: "" }
+          sub {  }
+        }
+        sub {
+          sub { s: "fuzztest.internal.MiniBlogger.LogOutUser" }
+          sub { s: "" }
+          sub {
+            sub {
+              sub { i: 0 }
+              sub { s: "session_id" }
+              sub { s: "log_out_info.session_info.session_id" }
+            }
+          }
+        })";
+  std::optional<IRObject> obj = IRObject::FromString(kObjectText);
+  ASSERT_TRUE(obj.has_value());
+
+  RpcNode log_in_user_node = GetLogInUserNode();
+  RpcNode log_out_user_node = GetLogOutUserNode();
+  RpcDataFlowEdge edge = {
+      0, GetFieldPath<LogInUserResponse>("session_id"),
+      GetFieldPath<LogOutUserRequest>("log_out_info.session_info.session_id")};
+  log_out_user_node.AddDependency(edge);
+  RpcDataFlowGraph graph;
+  graph.AddNode(0, log_in_user_node);
+  graph.AddNode(1, log_out_user_node);
+  RpcSessionImpl<MiniBlogger> rpc_domain;
+  auto obj2 = rpc_domain.SerializeCorpus(graph);
+  EXPECT_EQ(obj->ToString(), obj2.ToString());
+}
+
+TEST_F(RpcDomainTest, ParseCorpusReturnsRpcDataFlowGraph) {
+  RpcNode log_in_user_node = GetLogInUserNode();
+  RpcNode log_out_user_node = GetLogOutUserNode();
+  RpcDataFlowEdge edge = {
+      0, GetFieldPath<LogInUserResponse>("session_id"),
+      GetFieldPath<LogOutUserRequest>("log_out_info.session_info.session_id")};
+  log_out_user_node.AddDependency(edge);
+
+  /* Set up log_in_user_node */
+  IRObject log_in_user_node_obj;
+  auto& log_in_user_node_subs = log_in_user_node_obj.MutableSubs();
+  log_in_user_node_subs.push_back(
+      IRObject::FromCorpus(log_in_user_node.method().full_name()));
+  log_in_user_node_subs.push_back(
+      IRObject::FromCorpus(log_in_user_node.request().SerializeAsString()));
+  log_in_user_node_subs.push_back(IRObject{});
+
+  /* Set up log_out_user_node*/
+  IRObject log_out_user_node_obj;
+  auto& log_out_user_node_subs = log_out_user_node_obj.MutableSubs();
+  log_out_user_node_subs.push_back(
+      IRObject::FromCorpus(log_out_user_node.method().full_name()));
+  log_out_user_node_subs.push_back(
+      IRObject::FromCorpus(log_out_user_node.request().SerializeAsString()));
+  IRObject edge_obj;
+  auto& edge_subs = edge_obj.MutableSubs();
+  edge_subs.push_back(IRObject::FromCorpus(edge.from_node_id));
+  edge_subs.push_back(IRObject::FromCorpus(edge.from_field.ToString()));
+  edge_subs.push_back(IRObject::FromCorpus(edge.to_field.ToString()));
+  IRObject edges;
+  edges.MutableSubs().push_back(edge_obj);
+  log_out_user_node_subs.push_back(edges);
+
+  IRObject obj;
+  auto& subs = obj.MutableSubs();
+  subs.push_back(log_in_user_node_obj);
+  subs.push_back(log_out_user_node_obj);
+
+  RpcSessionImpl<MiniBlogger> rpc_domain;
+  auto graph = rpc_domain.ParseCorpus(obj);
+  ASSERT_TRUE(graph.has_value());
+  EXPECT_EQ(graph->NodeNum(), 2);
+  EXPECT_EQ(graph->GetNode(0), log_in_user_node);
+  EXPECT_EQ(graph->GetNode(1), log_out_user_node);
+}
+
+TEST_F(RpcDomainTest, SerializesCorpusReturnsIRObjectOfSpecificStructure) {
+  RpcNode log_in_user_node = GetLogInUserNode();
+  RpcNode log_out_user_node = GetLogOutUserNode();
+  log_out_user_node.AddDependency(RpcDataFlowEdge{
+      0, GetFieldPath<LogInUserResponse>("session_id"),
+      GetFieldPath<LogOutUserRequest>("log_out_info.session_info.session_id")});
+  RpcDataFlowGraph graph;
+  graph.AddNode(0, log_in_user_node);
+  graph.AddNode(1, log_out_user_node);
+
+  RpcSessionImpl<MiniBlogger> rpc_domain;
+  IRObject obj = rpc_domain.SerializeCorpus(graph);
+
+  EXPECT_THAT(
+      obj,
+      SubsAre(
+          /*log_in_user_node*/
+          SubsAre(ValueIs<std::string>(log_in_user_method_.full_name()),
+                  ValueIs<std::string>(
+                      log_in_user_node.request().SerializeAsString()),
+                  ValueIs<std::monostate>({})),
+          /*log_out_user_node*/
+          SubsAre(
+              ValueIs<std::string>(log_out_user_method_.full_name()),
+              ValueIs<std::string>(
+                  log_out_user_node.request().SerializeAsString()),
+              /*dependencies*/
+              SubsAre(SubsAre(
+                  ValueIs<std::uint64_t>(0) /*from_node_id*/,
+                  ValueIs<std::string>("session_id") /*from_field*/,
+                  ValueIs<
+                      std::string>(/*to_field*/
+                                   "log_out_info.session_info.session_id"))))));
+}
+
+TEST_F(RpcDomainTest, SerializesCorpusAndParsesCorpusReturnTheSameObject) {
+  RpcSessionImpl<MiniBlogger> rpc_domain;
+  for (int i = 0; i < 100; ++i) {
+    RpcDataFlowGraph graph = CreateRandomGraph(rpc_domain);
+    EXPECT_THAT(rpc_domain.ParseCorpus(rpc_domain.SerializeCorpus(graph)),
+                Optional(Eq(graph)));
+  }
+}
+
+TEST_F(RpcDomainTest, ValidRpcNodeShouldOnlyDependOnPreviousNodes) {
+  RpcNode log_out_user_node = GetLogOutUserNode();
+  log_out_user_node.AddDependency(RpcDataFlowEdge{
+      0, GetFieldPath<LogInUserResponse>("session_id"),
+      GetFieldPath<LogOutUserRequest>("log_out_info.session_info.session_id")});
+
+  RpcDataFlowGraph graph;
+  graph.AddNode(0, log_out_user_node);
+
+  RpcSessionImpl<MiniBlogger> rpc_domain;
+  EXPECT_THAT(
+      rpc_domain.ValidateCorpusValue(graph),
+      IsInvalid(
+          "The dependencies should only come from previously executed nodes."));
+}
+
+TEST_F(RpcDomainTest, ValidRpcNodeDependencyMatchesPotentialDependency) {
+  RpcNode log_out_user_node = GetLogOutUserNode();
+  log_out_user_node.AddDependency(RpcDataFlowEdge{
+      0, GetFieldPath<RegisterUserResponse>("success"),
+      GetFieldPath<LogOutUserRequest>("log_out_info.session_info.session_id")});
+
+  RpcDataFlowGraph graph;
+  graph.AddNode(0, GetLogInUserNode());
+  graph.AddNode(1, log_out_user_node);
+
+  RpcSessionImpl<MiniBlogger> rpc_domain;
+  EXPECT_THAT(
+      rpc_domain.ValidateCorpusValue(graph),
+      IsInvalid(
+          "The dependency is not defined in the potential data flow graph."));
+}
+
+TEST_F(RpcDomainTest,
+       ValidRpcNodeShouldHaveAtMostOneDependencyForEachDynamicField) {
+  RpcNode log_in_user_node = GetLogInUserNode();
+  RpcNode log_out_user_node = GetLogOutUserNode();
+  log_out_user_node.AddDependency(RpcDataFlowEdge{
+      0, GetFieldPath<LogInUserResponse>("session_id"),
+      GetFieldPath<LogOutUserRequest>("log_out_info.session_info.session_id")});
+  log_out_user_node.AddDependency(RpcDataFlowEdge{
+      0, GetFieldPath<LogInUserResponse>("session_id"),
+      GetFieldPath<LogOutUserRequest>("log_out_info.session_info.session_id")});
+
+  RpcDataFlowGraph graph;
+  graph.AddNode(0, log_in_user_node);
+  graph.AddNode(1, log_out_user_node);
+
+  RpcSessionImpl<MiniBlogger> rpc_domain;
+  EXPECT_THAT(
+      rpc_domain.ValidateCorpusValue(graph),
+      IsInvalid("One sink field should have at most one concrete dependency!"));
+}
+
+TEST_F(RpcDomainTest, ValidRpcNodePassValidationTest) {
+  RpcNode log_in_user_node = GetLogInUserNode();
+  RpcNode log_out_user_node = GetLogOutUserNode();
+  log_out_user_node.AddDependency(RpcDataFlowEdge{
+      0, GetFieldPath<LogInUserResponse>("session_id"),
+      GetFieldPath<LogOutUserRequest>("log_out_info.session_info.session_id")});
+
+  RpcDataFlowGraph graph;
+  graph.AddNode(0, log_in_user_node);
+  graph.AddNode(1, log_out_user_node);
+
+  RpcSessionImpl<MiniBlogger> rpc_domain;
+  EXPECT_OK(rpc_domain.ValidateCorpusValue(graph));
+}
+}  // namespace
+
+}  // namespace fuzztest::internal
diff --git a/rpc_fuzzing/rpc_stub.h b/rpc_fuzzing/rpc_stub.h
new file mode 100644
index 0000000..3969912
--- /dev/null
+++ b/rpc_fuzzing/rpc_stub.h
@@ -0,0 +1,25 @@
+#ifndef FUZZTEST_RPC_FUZZING_RPC_STUB_H_
+#define FUZZTEST_RPC_FUZZING_RPC_STUB_H_
+
+#include <memory>
+
+#include "google/protobuf/descriptor.h"
+#include "google/protobuf/message.h"
+#include "absl/status/statusor.h"
+
+namespace fuzztest {
+
+// RPC service stub interface.
+class RpcStub {
+ public:
+  virtual ~RpcStub() = default;
+  // Calls the RPC method described by `method_descriptor`, sending it `request`
+  // and returning the obtained response.
+  virtual absl::StatusOr<std::unique_ptr<google::protobuf::Message>> CallMethod(
+      const google::protobuf::MethodDescriptor& method_descriptor,
+      const google::protobuf::Message& request) = 0;
+};
+
+}  // namespace fuzztest
+
+#endif  // FUZZTEST_RPC_FUZZING_RPC_STUB_H_
diff --git a/rpc_fuzzing/scaffolding_stub.cc b/rpc_fuzzing/scaffolding_stub.cc
new file mode 100644
index 0000000..b008bb7
--- /dev/null
+++ b/rpc_fuzzing/scaffolding_stub.cc
@@ -0,0 +1,58 @@
+#include "./rpc_fuzzing/scaffolding_stub.h"
+
+#include <memory>
+#include <string>
+
+#include "net/base/sslconstant.h"
+#include "google/protobuf/descriptor.h"
+#include "google/protobuf/message.h"
+#include "net/rpc/anonymous-stub.h"
+#include "net/rpc2/rpc2.h"
+#include "absl/memory/memory.h"
+#include "absl/status/status.h"
+#include "absl/status/statusor.h"
+#include "absl/strings/str_cat.h"
+
+namespace fuzztest {
+
+absl::StatusOr<std::unique_ptr<google::protobuf::Message>> ScaffoldingStub::CallMethod(
+    const google::protobuf::MethodDescriptor& method_descriptor,
+    const google::protobuf::Message& request) {
+  const google::protobuf::Message* response_prototype =
+      google::protobuf::MessageFactory::generated_factory()->GetPrototype(
+          method_descriptor.output_type());
+  if (!response_prototype) {
+    return absl::InternalError(
+        absl::StrCat("Cannot find prototype for ",
+                     method_descriptor.output_type()->full_name(),
+                     " in the generated proto MessageFactory"));
+  }
+  std::unique_ptr<google::protobuf::Message> response =
+      absl::WrapUnique(response_prototype->New());
+  if (!response) {
+    return absl::InternalError(
+        absl::StrCat("Cannot create a new instance of response type ",
+                     method_descriptor.output_type()->full_name()));
+  }
+
+  RPC rpc;
+  // AnonymousStub doesn't have access to the method options, so we need to set
+  // this manually.
+  if (method_descriptor.options().has_security_level()) {
+    rpc.set_requested_security_level(net_base::SSLSecurityLevel(
+        method_descriptor.options().security_level()));
+  }
+  const google::protobuf::ServiceDescriptor& service_descriptor =
+      *method_descriptor.service();
+  const std::string method_name = absl::StrCat("/", service_descriptor.name(),
+                                               ".", method_descriptor.name());
+  stub_->Send(/*package_name=*/"", method_name, &rpc, &request, response.get(),
+              /* done= */ nullptr);
+  rpc.WaitRespectingFiberCancellation();
+  if (rpc.util_status().ok()) {
+    return response;
+  }
+  return rpc.util_status();
+}
+
+}  // namespace fuzztest
diff --git a/rpc_fuzzing/scaffolding_stub.h b/rpc_fuzzing/scaffolding_stub.h
new file mode 100644
index 0000000..807c8d6
--- /dev/null
+++ b/rpc_fuzzing/scaffolding_stub.h
@@ -0,0 +1,31 @@
+#ifndef FUZZTEST_RPC_FUZZING_SCAFFOLDING_STUB_H_
+#define FUZZTEST_RPC_FUZZING_SCAFFOLDING_STUB_H_
+
+#include <memory>
+#include <utility>
+
+#include "net/rpc/anonymous-stub.h"
+#include "net/rpc2/rpc2.h"
+#include "absl/status/statusor.h"
+#include "./rpc_fuzzing/rpc_stub.h"
+
+namespace fuzztest {
+
+class ScaffoldingStub : public RpcStub {
+ public:
+  ScaffoldingStub(absl::string_view socket)
+      : stub_(std::make_unique<AnonymousStub>(
+            rpc2::CreateClientChannel(socket))) {}
+  ScaffoldingStub(std::unique_ptr<AnonymousStub> stub)
+      : stub_(std::move(stub)) {}
+  absl::StatusOr<std::unique_ptr<google::protobuf::Message>> CallMethod(
+      const google::protobuf::MethodDescriptor& method_descriptor,
+      const google::protobuf::Message& request) override;
+
+ private:
+  std::unique_ptr<AnonymousStub> stub_;
+};
+
+}  // namespace fuzztest
+
+#endif  // FUZZTEST_RPC_FUZZING_SCAFFOLDING_STUB_H_
diff --git a/rpc_fuzzing/testdata/BUILD b/rpc_fuzzing/testdata/BUILD
new file mode 100644
index 0000000..ba59853
--- /dev/null
+++ b/rpc_fuzzing/testdata/BUILD
@@ -0,0 +1,54 @@
+load("@com_github_grpc_grpc//bazel:cc_grpc_library.bzl", "cc_grpc_library")
+
+package(default_visibility = [
+    "@com_google_fuzztest//e2e_tests/testdata:__pkg__",
+    "@com_google_fuzztest//fuzztest:__subpackages__",
+    "@com_google_fuzztest//rpc_fuzzing:__pkg__",
+])
+
+proto_library(
+    name = "mini_blogger",
+    srcs = ["mini_blogger.proto"],
+    target_compatible_with = select({
+        "@com_google_fuzztest//rpc_fuzzing:rpc_fuzzing_is_enabled": [],
+        "//conditions:default": ["@platforms//:incompatible"],
+    }),
+)
+
+cc_proto_library(
+    name = "mini_blogger_cc_proto",
+    deps = [":mini_blogger"],
+)
+
+cc_grpc_library(
+    name = "mini_blogger_cc_grpc_proto",
+    srcs = [":mini_blogger"],
+    grpc_only = True,
+    deps = [":mini_blogger_cc_proto"],
+)
+
+cc_library(
+    name = "mini_blogger_grpc_service",
+    srcs = ["grpc/mini_blogger_service.cc"],
+    hdrs = ["grpc/mini_blogger_service.h"],
+    deps = [
+        ":mini_blogger_cc_grpc_proto",
+        ":mini_blogger_cc_proto",
+        ":mini_blogger_context",
+        "@com_github_grpc_grpc//:grpc++",
+    ],
+)
+
+cc_library(
+    name = "mini_blogger_context",
+    srcs = ["mini_blogger_context.cc"],
+    hdrs = ["mini_blogger_context.h"],
+    deps = [
+        ":mini_blogger_cc_proto",
+        "@com_google_absl//absl/container:flat_hash_map",
+        "@com_google_absl//absl/container:flat_hash_set",
+        "@com_google_absl//absl/random",
+        "@com_google_absl//absl/strings",
+        "@com_google_absl//absl/synchronization",
+    ],
+)
diff --git a/rpc_fuzzing/testdata/grpc/mini_blogger_server.cc b/rpc_fuzzing/testdata/grpc/mini_blogger_server.cc
new file mode 100644
index 0000000..a49bd61
--- /dev/null
+++ b/rpc_fuzzing/testdata/grpc/mini_blogger_server.cc
@@ -0,0 +1,36 @@
+// A grpc server that can run locally for testing.
+#include <cstdint>
+#include <memory>
+#include <string>
+
+#include "base/init_google.h"
+#include "absl/base/log_severity.h"
+#include "absl/flags/flag.h"
+#include "absl/log/globals.h"
+#include "absl/log/log.h"
+#include "absl/strings/str_cat.h"
+#include "./rpc_fuzzing/testdata/grpc/mini_blogger_service.h"
+#include "grpcpp//security/server_credentials.h"
+#include "grpcpp//server.h"
+#include "grpcpp//server_builder.h"
+
+ABSL_FLAG(int32_t, port, 5000, "port to listen on");
+
+int main(int argc, char** argv) {
+  InitGoogle(argv[0], &argc, &argv, /*remove_flags=*/true);
+
+  absl::SetStderrThreshold(absl::LogSeverityAtLeast::kInfo);
+
+  std::string server_address = absl::StrCat("[::]:", absl::GetFlag(FLAGS_port));
+
+  fuzztest::internal::MiniBloggerGrpcService mini_blogger;
+  grpc::ServerBuilder builder;
+  builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
+  builder.RegisterService(&mini_blogger);
+  std::unique_ptr<grpc::Server> server(builder.BuildAndStart());
+  LOG(INFO) << "Server listening on " << server_address;
+
+  server->Wait();
+
+  return 0;
+}
diff --git a/rpc_fuzzing/testdata/grpc/mini_blogger_service.cc b/rpc_fuzzing/testdata/grpc/mini_blogger_service.cc
new file mode 100644
index 0000000..d6cc705
--- /dev/null
+++ b/rpc_fuzzing/testdata/grpc/mini_blogger_service.cc
@@ -0,0 +1,36 @@
+#include "./rpc_fuzzing/testdata/grpc/mini_blogger_service.h"
+
+#include "./rpc_fuzzing/testdata/mini_blogger.pb.h"
+#include "grpcpp//server_context.h"
+#include "grpcpp//support/status.h"
+
+namespace fuzztest::internal {
+
+grpc::Status MiniBloggerGrpcService::RegisterUser(
+    grpc::ServerContext*, const RegisterUserRequest* request,
+    RegisterUserResponse* response) {
+  mini_blogger_context_.RegisterUser(*request, *response);
+  return grpc::Status::OK;
+}
+
+grpc::Status MiniBloggerGrpcService::LogInUser(grpc::ServerContext*,
+                                               const LogInUserRequest* request,
+                                               LogInUserResponse* response) {
+  mini_blogger_context_.LogInUser(*request, *response);
+  return grpc::Status::OK;
+}
+
+grpc::Status MiniBloggerGrpcService::GetUserPosts(
+    grpc::ServerContext* unused_service_context,
+    const GetUserPostsRequest* request, GetUserPostsResponse* response) {
+  mini_blogger_context_.GetUserPosts(*request, *response);
+  return grpc::Status::OK;
+}
+
+grpc::Status MiniBloggerGrpcService::LogOutUser(
+    grpc::ServerContext* unused_service_context,
+    const LogOutUserRequest* request, LogOutUserResponse* response) {
+  mini_blogger_context_.LogOutUser(*request, *response);
+  return grpc::Status::OK;
+}
+}  // namespace fuzztest::internal
diff --git a/rpc_fuzzing/testdata/grpc/mini_blogger_service.h b/rpc_fuzzing/testdata/grpc/mini_blogger_service.h
new file mode 100644
index 0000000..d627b37
--- /dev/null
+++ b/rpc_fuzzing/testdata/grpc/mini_blogger_service.h
@@ -0,0 +1,34 @@
+#ifndef FUZZTEST_RPC_FUZZING_TESTDATA_MINI_BLOGGER_GRPC_SERVICE_H_
+#define FUZZTEST_RPC_FUZZING_TESTDATA_MINI_BLOGGER_GRPC_SERVICE_H_
+
+#include "./rpc_fuzzing/testdata/mini_blogger.grpc.pb.h"
+#include "./rpc_fuzzing/testdata/mini_blogger.pb.h"
+#include "./rpc_fuzzing/testdata/mini_blogger_context.h"
+#include "grpcpp//support/status.h"
+
+namespace fuzztest::internal {
+
+// The gRpc server implementation of MiniBlogger.
+class MiniBloggerGrpcService : public MiniBlogger::Service {
+ public:
+  grpc::Status RegisterUser(grpc::ServerContext*,
+                            const RegisterUserRequest* request,
+                            RegisterUserResponse* response) override;
+
+  grpc::Status LogInUser(grpc::ServerContext*, const LogInUserRequest* request,
+                         LogInUserResponse* response) override;
+
+  grpc::Status GetUserPosts(grpc::ServerContext*,
+                            const GetUserPostsRequest* request,
+                            GetUserPostsResponse* response) override;
+
+  grpc::Status LogOutUser(grpc::ServerContext*,
+                          const LogOutUserRequest* request,
+                          LogOutUserResponse* response) override;
+
+ private:
+  MiniBloggerContext mini_blogger_context_;
+};
+}  // namespace fuzztest::internal
+
+#endif  // FUZZTEST_RPC_FUZZING_TESTDATA_MINI_BLOGGER_GRPC_SERVICE_H_
diff --git a/rpc_fuzzing/testdata/mini_blogger.proto b/rpc_fuzzing/testdata/mini_blogger.proto
new file mode 100644
index 0000000..50c2da0
--- /dev/null
+++ b/rpc_fuzzing/testdata/mini_blogger.proto
@@ -0,0 +1,127 @@
+syntax = "proto3";
+
+package fuzztest.internal;
+
+service MiniBlogger {
+  // Register a user.
+  rpc RegisterUser(RegisterUserRequest) returns (RegisterUserResponse);
+  // Log in a user and get a session id.
+  rpc LogInUser(LogInUserRequest) returns (LogInUserResponse);
+  // Get a user's posts by session id.
+  rpc GetUserPosts(GetUserPostsRequest) returns (GetUserPostsResponse);
+  // Log out the user (invaliding the session id).
+  rpc LogOutUser(LogOutUserRequest) returns (LogOutUserResponse);
+}
+
+message RegisterUserRequest {
+  string user_name = 1;
+  string email = 2;
+  string password = 3;
+}
+
+message RegisterUserResponse {
+  bool success = 1;
+}
+
+message LogInUserRequest {
+  string name = 1;
+  string password = 2;
+}
+
+message LogInUserResponse {
+  bool success = 1;
+  // A unique id for the user logging in session.
+  int64 session_id = 2;
+}
+
+enum SortingOrder {
+  ASCENDING = 0;
+  DESCENDING = 1;
+}
+
+enum SortedBy {
+  DATE = 0;
+  POPULARITY = 1;
+}
+
+// For testing only.
+message GetUserPostsOptionsWithDifferentFieldNames {
+  SortingOrder ordering = 1;
+}
+
+message GetUserPostsOptions {
+  SortingOrder order = 1;
+  SortedBy sort_by = 2;
+}
+
+message GetUserPostsRequest {
+  int64 session_id = 1;
+  int32 max_posts = 2;  // Maximum number of posts returned.
+  GetUserPostsOptions options = 3;
+}
+
+message GetUserPostsResponse {
+  repeated string posts = 1;
+}
+
+// For testing dependency established at inner fields.
+message LogOutInfo {
+  oneof log_out_info {
+    SessionInfo session_info = 1;
+    int64 session_id = 2;
+  }
+}
+
+message SessionInfo {
+  int64 session_id = 1;
+}
+
+message LogOutUserRequest {
+  LogOutInfo log_out_info = 1;
+}
+
+message LogOutUserResponse {
+  bool success = 1;
+}
+
+// This is only for testing that the util of collecting all fields in a message
+// won't break with recursive messages.
+message RecursiveNode {
+  int64 value = 1;
+  repeated RecursiveNode children = 2;
+}
+
+// For testing only.
+message OneOfMessage {
+  oneof oneof1 {
+    DummyMessage oneof1_field1 = 1;
+    int64 oneof1_field2 = 2;
+  }
+  oneof oneof2 {
+    DummyMessage oneof2_field1 = 3;
+    int64 oneof2_field2 = 4;
+  }
+}
+
+message DummyMessage {
+  int64 v1 = 1;
+  int64 v2 = 2;
+}
+
+message InnerRepeated {
+  int64 field2 = 1;
+}
+
+message MessageWithSingleInnerRepeated {
+  InnerRepeated repeated_msg_field = 1;
+}
+
+message MessageContainingRepeatedFields {
+  repeated int64 field = 1;
+  repeated InnerRepeated repeated_msg_field = 2;
+}
+
+message MessageNotContainingRepeatedFields {
+  int64 field = 1;
+  int64 field2 = 2;
+}
diff --git a/rpc_fuzzing/testdata/mini_blogger_context.cc b/rpc_fuzzing/testdata/mini_blogger_context.cc
new file mode 100644
index 0000000..d46863b
--- /dev/null
+++ b/rpc_fuzzing/testdata/mini_blogger_context.cc
@@ -0,0 +1,102 @@
+#include "./rpc_fuzzing/testdata/mini_blogger_context.h"
+
+#include <cstdint>
+#include <optional>
+#include <string>
+#include <vector>
+
+#include "absl/container/flat_hash_map.h"
+#include "absl/container/flat_hash_set.h"
+#include "absl/random/random.h"
+#include "absl/strings/string_view.h"
+#include "absl/synchronization/mutex.h"
+#include "./rpc_fuzzing/testdata/mini_blogger.pb.h"
+
+namespace fuzztest::internal {
+
+void MiniBloggerContext::RegisterUser(const RegisterUserRequest& request,
+                                      RegisterUserResponse& response) {
+  bool result = RegisterUserHandler(request.user_name(), request.email(),
+                                    request.password());
+  response.set_success(result);
+}
+
+void MiniBloggerContext::LogInUser(const LogInUserRequest& request,
+                                   LogInUserResponse& response) {
+  std::optional<std::int64_t> result =
+      LoginUserHandler(request.name(), request.password());
+  if (!result) {
+    response.set_success(false);
+  } else {
+    response.set_success(true);
+    response.set_session_id(result.value());
+  }
+}
+
+void MiniBloggerContext::GetUserPosts(const GetUserPostsRequest& request,
+                                      GetUserPostsResponse& response) {
+  for (const std::string& post : GetUserPostsHandler(request.session_id())) {
+    response.add_posts(post);
+  }
+}
+
+void MiniBloggerContext::LogOutUser(const LogOutUserRequest& request,
+                                    LogOutUserResponse& response) {
+  if (!request.has_log_out_info() ||
+      !request.log_out_info().has_session_info()) {
+    response.set_success(false);
+  } else {
+    bool result =
+        LogOutUserHandler(request.log_out_info().session_info().session_id());
+    response.set_success(result);
+  }
+}
+bool MiniBloggerContext::RegisterUserHandler(absl::string_view name,
+                                             absl::string_view email,
+                                             absl::string_view passwd) {
+  if (name.empty() || email.empty() || passwd.empty()) return false;
+  absl::WriterMutexLock l(&lock_);
+  if (users_.contains(name)) return false;
+  users_.emplace(std::string(name),
+                 MiniBloggerUser{std::string(name), std::string(email),
+                                 std::string(passwd)});
+  return true;
+}
+
+std::optional<std::int64_t> MiniBloggerContext::LoginUserHandler(
+    absl::string_view name, absl::string_view passwd) {
+  absl::WriterMutexLock l(&lock_);
+  if (!name.empty()) {
+    if (!users_.contains(name)) return std::nullopt;
+    if (users_[name].passwd != passwd) return std::nullopt;
+  }
+  absl::BitGen gen;
+  std::int64_t sid =
+      absl::Uniform<int64_t>(gen, 0, std::numeric_limits<int64_t>::max());
+  active_session_ids_.insert(sid);
+  return sid;
+}
+
+bool MiniBloggerContext::LogOutUserHandler(std::int64_t sid) {
+  absl::WriterMutexLock l(&lock_);
+  if (!active_session_ids_.contains(sid)) return false;
+  inactive_session_ids_.insert(sid);
+  active_session_ids_.erase(sid);
+  return true;
+}
+
+std::vector<std::string> MiniBloggerContext::GetUserPostsHandler(
+    std::int64_t sid) {
+  std::vector<std::string> posts;
+  absl::ReaderMutexLock l(&lock_);
+  if (inactive_session_ids_.contains(sid)) {
+    std::cerr << "Using an inactive session id!\n";
+    std::abort();
+  }
+  if (active_session_ids_.contains(sid)) {
+    posts.push_back("Random post");
+  }
+  return posts;
+}
+
+}  // namespace fuzztest::internal
diff --git a/rpc_fuzzing/testdata/mini_blogger_context.h b/rpc_fuzzing/testdata/mini_blogger_context.h
new file mode 100644
index 0000000..7bde0ac
--- /dev/null
+++ b/rpc_fuzzing/testdata/mini_blogger_context.h
@@ -0,0 +1,59 @@
+#ifndef THIRD_PARTY_GOOGLEFUZZTEST_rpc_fuzzing_RCP_TEST_SERVER_CONTEXT_H_
+#define THIRD_PARTY_GOOGLEFUZZTEST_rpc_fuzzing_RCP_TEST_SERVER_CONTEXT_H_
+
+#include <cstdint>
+#include <optional>
+#include <string>
+#include <vector>
+
+#include "absl/container/flat_hash_map.h"
+#include "absl/container/flat_hash_set.h"
+#include "absl/strings/string_view.h"
+#include "absl/synchronization/mutex.h"
+#include "./rpc_fuzzing/testdata/mini_blogger.pb.h"
+
+namespace fuzztest::internal {
+
+struct MiniBloggerUser {
+  std::string name;
+  std::string email;
+  std::string passwd;
+};
+
+// The real handling logics of MiniBlogger, shared by by gRPC and other
+// frameworks.
+class MiniBloggerContext {
+ public:
+  // Adds a new user into the database.
+  void RegisterUser(const RegisterUserRequest& request,
+                    RegisterUserResponse& response);
+
+  // Logs in a user if the user info exists in the database and generates a
+  // session id.
+  void LogInUser(const LogInUserRequest& request, LogInUserResponse& response);
+
+  // Gets the posts for a user given the session id.
+  void GetUserPosts(const GetUserPostsRequest& request,
+                    GetUserPostsResponse& response);
+
+  // Logs out a user given the session id. The session id will be invalidated.
+  void LogOutUser(const LogOutUserRequest& request,
+                  LogOutUserResponse& response);
+
+ private:
+  bool RegisterUserHandler(absl::string_view name, absl::string_view email,
+                           absl::string_view passwd);
+  std::optional<std::int64_t> LoginUserHandler(absl::string_view name,
+                                               absl::string_view passwd);
+  bool LogOutUserHandler(std::int64_t sid);
+  std::vector<std::string> GetUserPostsHandler(std::int64_t sid);
+
+  absl::flat_hash_map<std::string /*name*/, MiniBloggerUser> users_;
+  absl::flat_hash_set<std::int64_t> active_session_ids_;
+  absl::flat_hash_set<std::int64_t> inactive_session_ids_;
+  absl::Mutex lock_;  // Serialize operation.
+};
+
+}  // namespace fuzztest::internal
+
+#endif  // THIRD_PARTY_GOOGLEFUZZTEST_rpc_fuzzing_RCP_TEST_SERVER_CONTEXT_H_