Merge "Expose binder stability for debugging in dumpsys"
diff --git a/libs/binder/Android.bp b/libs/binder/Android.bp
index f157aa2..6e7f74f 100644
--- a/libs/binder/Android.bp
+++ b/libs/binder/Android.bp
@@ -167,6 +167,10 @@
         binder32bit: {
             cflags: ["-DBINDER_IPC_32BIT=1"],
         },
+
+        debuggable: {
+            cflags: ["-DBINDER_RPC_DEV_SERVERS"],
+        },
     },
 
     shared_libs: [
diff --git a/libs/binder/Binder.cpp b/libs/binder/Binder.cpp
index d5bdd1c..c83c383 100644
--- a/libs/binder/Binder.cpp
+++ b/libs/binder/Binder.cpp
@@ -17,16 +17,24 @@
 #include <binder/Binder.h>
 
 #include <atomic>
-#include <utils/misc.h>
+
+#include <android-base/unique_fd.h>
 #include <binder/BpBinder.h>
 #include <binder/IInterface.h>
+#include <binder/IPCThreadState.h>
 #include <binder/IResultReceiver.h>
 #include <binder/IShellCallback.h>
 #include <binder/Parcel.h>
+#include <binder/RpcServer.h>
+#include <private/android_filesystem_config.h>
+#include <utils/misc.h>
 
+#include <inttypes.h>
 #include <linux/sched.h>
 #include <stdio.h>
 
+#include "RpcState.h"
+
 namespace android {
 
 // Service implementations inherit from BBinder and IBinder, and this is frozen
@@ -39,6 +47,12 @@
 static_assert(sizeof(BBinder) == 20);
 #endif
 
+#ifdef BINDER_RPC_DEV_SERVERS
+constexpr const bool kEnableRpcDevServers = true;
+#else
+constexpr const bool kEnableRpcDevServers = false;
+#endif
+
 // ---------------------------------------------------------------------------
 
 IBinder::IBinder()
@@ -136,6 +150,33 @@
     return OK;
 }
 
+status_t IBinder::setRpcClientDebug(android::base::unique_fd socketFd, uint32_t maxRpcThreads) {
+    if constexpr (!kEnableRpcDevServers) {
+        ALOGW("setRpcClientDebug disallowed because RPC is not enabled");
+        return INVALID_OPERATION;
+    }
+
+    BBinder* local = this->localBinder();
+    if (local != nullptr) {
+        return local->BBinder::setRpcClientDebug(std::move(socketFd), maxRpcThreads);
+    }
+
+    BpBinder* proxy = this->remoteBinder();
+    LOG_ALWAYS_FATAL_IF(proxy == nullptr, "binder object must be either local or remote");
+
+    Parcel data;
+    Parcel reply;
+    status_t status;
+    if (status = data.writeBool(socketFd.ok()); status != OK) return status;
+    if (socketFd.ok()) {
+        // writeUniqueFileDescriptor currently makes an unnecessary dup().
+        status = data.writeFileDescriptor(socketFd.release(), true /* own */);
+        if (status != OK) return status;
+    }
+    if (status = data.writeUint32(maxRpcThreads); status != OK) return status;
+    return transact(SET_RPC_CLIENT_TRANSACTION, data, &reply);
+}
+
 // ---------------------------------------------------------------------------
 
 class BBinder::Extras
@@ -150,6 +191,7 @@
 
     // for below objects
     Mutex mLock;
+    sp<RpcServer> mRpcServer;
     BpBinder::ObjectManager mObjects;
 };
 
@@ -199,6 +241,10 @@
         case DEBUG_PID_TRANSACTION:
             err = reply->writeInt32(getDebugPid());
             break;
+        case SET_RPC_CLIENT_TRANSACTION: {
+            err = setRpcClientDebug(data);
+            break;
+        }
         default:
             err = onTransact(code, data, reply, flags);
             break;
@@ -368,6 +414,75 @@
     e->mExtension = extension;
 }
 
+status_t BBinder::setRpcClientDebug(const Parcel& data) {
+    if constexpr (!kEnableRpcDevServers) {
+        ALOGW("%s: disallowed because RPC is not enabled", __PRETTY_FUNCTION__);
+        return INVALID_OPERATION;
+    }
+    uid_t uid = IPCThreadState::self()->getCallingUid();
+    if (uid != AID_ROOT) {
+        ALOGE("%s: not allowed because client %" PRIu32 " is not root", __PRETTY_FUNCTION__, uid);
+        return PERMISSION_DENIED;
+    }
+    status_t status;
+    bool hasSocketFd;
+    android::base::unique_fd clientFd;
+    uint32_t maxRpcThreads;
+
+    if (status = data.readBool(&hasSocketFd); status != OK) return status;
+    if (hasSocketFd) {
+        if (status = data.readUniqueFileDescriptor(&clientFd); status != OK) return status;
+    }
+    if (status = data.readUint32(&maxRpcThreads); status != OK) return status;
+
+    return setRpcClientDebug(std::move(clientFd), maxRpcThreads);
+}
+
+status_t BBinder::setRpcClientDebug(android::base::unique_fd socketFd, uint32_t maxRpcThreads) {
+    if constexpr (!kEnableRpcDevServers) {
+        ALOGW("%s: disallowed because RPC is not enabled", __PRETTY_FUNCTION__);
+        return INVALID_OPERATION;
+    }
+
+    const int socketFdForPrint = socketFd.get();
+    LOG_RPC_DETAIL("%s(%d, %" PRIu32 ")", __PRETTY_FUNCTION__, socketFdForPrint, maxRpcThreads);
+
+    if (!socketFd.ok()) {
+        ALOGE("%s: No socket FD provided.", __PRETTY_FUNCTION__);
+        return BAD_VALUE;
+    }
+    if (maxRpcThreads <= 0) {
+        ALOGE("%s: RPC is useless with %" PRIu32 " threads.", __PRETTY_FUNCTION__, maxRpcThreads);
+        return BAD_VALUE;
+    }
+
+    // TODO(b/182914638): RPC and binder should share the same thread pool count.
+    size_t binderThreadPoolMaxCount = ProcessState::self()->getThreadPoolMaxThreadCount();
+    if (binderThreadPoolMaxCount <= 1) {
+        ALOGE("%s: ProcessState thread pool max count is %zu. RPC is disabled for this service "
+              "because RPC requires the service to support multithreading.",
+              __PRETTY_FUNCTION__, binderThreadPoolMaxCount);
+        return INVALID_OPERATION;
+    }
+
+    Extras* e = getOrCreateExtras();
+    AutoMutex _l(e->mLock);
+    if (e->mRpcServer != nullptr) {
+        ALOGE("%s: Already have RPC client", __PRETTY_FUNCTION__);
+        return ALREADY_EXISTS;
+    }
+    e->mRpcServer = RpcServer::make();
+    LOG_ALWAYS_FATAL_IF(e->mRpcServer == nullptr, "RpcServer::make returns null");
+    e->mRpcServer->iUnderstandThisCodeIsExperimentalAndIWillNotUseItInProduction();
+    // Weak ref to avoid circular dependency: BBinder -> RpcServer -X-> BBinder
+    e->mRpcServer->setRootObjectWeak(wp<BBinder>::fromExisting(this));
+    e->mRpcServer->setupExternalServer(std::move(socketFd));
+    e->mRpcServer->start();
+    LOG_RPC_DETAIL("%s(%d, %" PRIu32 ") successful", __PRETTY_FUNCTION__, socketFdForPrint,
+                   maxRpcThreads);
+    return OK;
+}
+
 BBinder::~BBinder()
 {
     Extras* e = mExtras.load(std::memory_order_relaxed);
diff --git a/libs/binder/IPCThreadState.cpp b/libs/binder/IPCThreadState.cpp
index d2919e7..e933f7e 100644
--- a/libs/binder/IPCThreadState.cpp
+++ b/libs/binder/IPCThreadState.cpp
@@ -516,14 +516,16 @@
 
 bool IPCThreadState::flushIfNeeded()
 {
-    if (mIsLooper || mServingStackPointer != nullptr) {
+    if (mIsLooper || mServingStackPointer != nullptr || mIsFlushing) {
         return false;
     }
+    mIsFlushing = true;
     // In case this thread is not a looper and is not currently serving a binder transaction,
     // there's no guarantee that this thread will call back into the kernel driver any time
     // soon. Therefore, flush pending commands such as BC_FREE_BUFFER, to prevent them from getting
     // stuck in this thread's out buffer.
     flushCommands();
+    mIsFlushing = false;
     return true;
 }
 
@@ -880,6 +882,7 @@
         mWorkSource(kUnsetWorkSource),
         mPropagateWorkSource(false),
         mIsLooper(false),
+        mIsFlushing(false),
         mStrictModePolicy(0),
         mLastTransactionBinderFlags(0),
         mCallRestriction(mProcess->mCallRestriction) {
diff --git a/libs/binder/ProcessState.cpp b/libs/binder/ProcessState.cpp
index f7ca1e6..3095607 100644
--- a/libs/binder/ProcessState.cpp
+++ b/libs/binder/ProcessState.cpp
@@ -359,6 +359,14 @@
     return result;
 }
 
+size_t ProcessState::getThreadPoolMaxThreadCount() const {
+    // may actually be one more than this, if join is called
+    if (mThreadPoolStarted) return mMaxThreads;
+    // must not be initialized or maybe has poll thread setup, we
+    // currently don't track this in libbinder
+    return 0;
+}
+
 status_t ProcessState::enableOnewaySpamDetection(bool enable) {
     uint32_t enableDetection = enable ? 1 : 0;
     if (ioctl(mDriverFD, BINDER_ENABLE_ONEWAY_SPAM_DETECTION, &enableDetection) == -1) {
diff --git a/libs/binder/RpcServer.cpp b/libs/binder/RpcServer.cpp
index 6dabbb0..e3bf2a5 100644
--- a/libs/binder/RpcServer.cpp
+++ b/libs/binder/RpcServer.cpp
@@ -128,6 +128,17 @@
     return ret;
 }
 
+static void joinRpcServer(sp<RpcServer>&& thiz) {
+    thiz->join();
+}
+
+void RpcServer::start() {
+    LOG_ALWAYS_FATAL_IF(!mAgreedExperimental, "no!");
+    std::lock_guard<std::mutex> _l(mLock);
+    LOG_ALWAYS_FATAL_IF(mJoinThread.get(), "Already started!");
+    mJoinThread = std::make_unique<std::thread>(&joinRpcServer, sp<RpcServer>::fromExisting(this));
+}
+
 void RpcServer::join() {
     LOG_ALWAYS_FATAL_IF(!mAgreedExperimental, "no!");
 
@@ -140,9 +151,11 @@
         LOG_ALWAYS_FATAL_IF(mShutdownTrigger == nullptr, "Cannot create join signaler");
     }
 
-    while (mShutdownTrigger->triggerablePollRead(mServer)) {
+    status_t status;
+    while ((status = mShutdownTrigger->triggerablePollRead(mServer)) == OK) {
         (void)acceptOne();
     }
+    LOG_RPC_DETAIL("RpcServer::join exiting with %s", statusToString(status).c_str());
 
     {
         std::lock_guard<std::mutex> _l(mLock);
@@ -163,9 +176,8 @@
 
     {
         std::lock_guard<std::mutex> _l(mLock);
-        std::thread thread =
-                std::thread(&RpcServer::establishConnection, this,
-                            std::move(sp<RpcServer>::fromExisting(this)), std::move(clientFd));
+        std::thread thread = std::thread(&RpcServer::establishConnection,
+                                         sp<RpcServer>::fromExisting(this), std::move(clientFd));
         mConnectingThreads[thread.get_id()] = std::move(thread);
     }
 
@@ -174,10 +186,29 @@
 
 bool RpcServer::shutdown() {
     std::unique_lock<std::mutex> _l(mLock);
-    if (mShutdownTrigger == nullptr) return false;
+    if (mShutdownTrigger == nullptr) {
+        LOG_RPC_DETAIL("Cannot shutdown. No shutdown trigger installed.");
+        return false;
+    }
 
     mShutdownTrigger->trigger();
-    while (mJoinThreadRunning) mShutdownCv.wait(_l);
+    while (mJoinThreadRunning || !mConnectingThreads.empty()) {
+        ALOGI("Waiting for RpcServer to shut down. Join thread running: %d, Connecting threads: "
+              "%zu",
+              mJoinThreadRunning, mConnectingThreads.size());
+        mShutdownCv.wait(_l);
+    }
+
+    // At this point, we know join() is about to exit, but the thread that calls
+    // join() may not have exited yet.
+    // If RpcServer owns the join thread (aka start() is called), make sure the thread exits;
+    // otherwise ~thread() may call std::terminate(), which may crash the process.
+    // If RpcServer does not own the join thread (aka join() is called directly),
+    // then the owner of RpcServer is responsible for cleaning up that thread.
+    if (mJoinThread.get()) {
+        mJoinThread->join();
+        mJoinThread.reset();
+    }
 
     mShutdownTrigger = nullptr;
     return true;
@@ -199,44 +230,60 @@
 }
 
 void RpcServer::establishConnection(sp<RpcServer>&& server, base::unique_fd clientFd) {
-    LOG_ALWAYS_FATAL_IF(this != server.get(), "Must pass same ownership object");
-
     // TODO(b/183988761): cannot trust this simple ID
-    LOG_ALWAYS_FATAL_IF(!mAgreedExperimental, "no!");
-    bool idValid = true;
+    LOG_ALWAYS_FATAL_IF(!server->mAgreedExperimental, "no!");
+
+    // mShutdownTrigger can only be cleared once connection threads have joined.
+    // It must be set before this thread is started
+    LOG_ALWAYS_FATAL_IF(server->mShutdownTrigger == nullptr);
+
     int32_t id;
-    if (sizeof(id) != read(clientFd.get(), &id, sizeof(id))) {
-        ALOGE("Could not read ID from fd %d", clientFd.get());
-        idValid = false;
+    status_t status =
+            server->mShutdownTrigger->interruptableReadFully(clientFd.get(), &id, sizeof(id));
+    bool idValid = status == OK;
+    if (!idValid) {
+        ALOGE("Failed to read ID for client connecting to RPC server: %s",
+              statusToString(status).c_str());
+        // still need to cleanup before we can return
     }
 
     std::thread thisThread;
     sp<RpcSession> session;
     {
-        std::lock_guard<std::mutex> _l(mLock);
+        std::unique_lock<std::mutex> _l(server->mLock);
 
-        auto threadId = mConnectingThreads.find(std::this_thread::get_id());
-        LOG_ALWAYS_FATAL_IF(threadId == mConnectingThreads.end(),
+        auto threadId = server->mConnectingThreads.find(std::this_thread::get_id());
+        LOG_ALWAYS_FATAL_IF(threadId == server->mConnectingThreads.end(),
                             "Must establish connection on owned thread");
         thisThread = std::move(threadId->second);
         ScopeGuard detachGuard = [&]() { thisThread.detach(); };
-        mConnectingThreads.erase(threadId);
+        server->mConnectingThreads.erase(threadId);
+
+        // TODO(b/185167543): we currently can't disable this because we don't
+        // shutdown sessions as well, only the server itself. So, we need to
+        // keep this separate from the detachGuard, since we temporarily want to
+        // give a notification even when we pass ownership of the thread to
+        // a session.
+        ScopeGuard threadLifetimeGuard = [&]() {
+            _l.unlock();
+            server->mShutdownCv.notify_all();
+        };
 
         if (!idValid) {
             return;
         }
 
         if (id == RPC_SESSION_ID_NEW) {
-            LOG_ALWAYS_FATAL_IF(mSessionIdCounter >= INT32_MAX, "Out of session IDs");
-            mSessionIdCounter++;
+            LOG_ALWAYS_FATAL_IF(server->mSessionIdCounter >= INT32_MAX, "Out of session IDs");
+            server->mSessionIdCounter++;
 
             session = RpcSession::make();
-            session->setForServer(wp<RpcServer>::fromExisting(this), mSessionIdCounter);
+            session->setForServer(wp<RpcServer>(server), server->mSessionIdCounter);
 
-            mSessions[mSessionIdCounter] = session;
+            server->mSessions[server->mSessionIdCounter] = session;
         } else {
-            auto it = mSessions.find(id);
-            if (it == mSessions.end()) {
+            auto it = server->mSessions.find(id);
+            if (it == server->mSessions.end()) {
                 ALOGE("Cannot add thread, no record of session with ID %d", id);
                 return;
             }
@@ -249,10 +296,6 @@
 
     // avoid strong cycle
     server = nullptr;
-    //
-    //
-    // DO NOT ACCESS MEMBER VARIABLES BELOW
-    //
 
     session->join(std::move(clientFd));
 }
diff --git a/libs/binder/RpcSession.cpp b/libs/binder/RpcSession.cpp
index ea82f36..9f26a33 100644
--- a/libs/binder/RpcSession.cpp
+++ b/libs/binder/RpcSession.cpp
@@ -125,25 +125,43 @@
     mWrite.reset();
 }
 
-bool RpcSession::FdTrigger::triggerablePollRead(base::borrowed_fd fd) {
+status_t RpcSession::FdTrigger::triggerablePollRead(base::borrowed_fd fd) {
     while (true) {
-        pollfd pfd[]{{.fd = fd.get(), .events = POLLIN, .revents = 0},
+        pollfd pfd[]{{.fd = fd.get(), .events = POLLIN | POLLHUP, .revents = 0},
                      {.fd = mRead.get(), .events = POLLHUP, .revents = 0}};
         int ret = TEMP_FAILURE_RETRY(poll(pfd, arraysize(pfd), -1));
         if (ret < 0) {
-            ALOGE("Could not poll: %s", strerror(errno));
-            continue;
+            return -errno;
         }
         if (ret == 0) {
             continue;
         }
         if (pfd[1].revents & POLLHUP) {
-            return false;
+            return -ECANCELED;
         }
-        return true;
+        return pfd[0].revents & POLLIN ? OK : DEAD_OBJECT;
     }
 }
 
+status_t RpcSession::FdTrigger::interruptableReadFully(base::borrowed_fd fd, void* data,
+                                                       size_t size) {
+    uint8_t* buffer = reinterpret_cast<uint8_t*>(data);
+    uint8_t* end = buffer + size;
+
+    status_t status;
+    while ((status = triggerablePollRead(fd)) == OK) {
+        ssize_t readSize = TEMP_FAILURE_RETRY(recv(fd.get(), buffer, end - buffer, MSG_NOSIGNAL));
+        if (readSize == 0) return DEAD_OBJECT; // EOF
+
+        if (readSize < 0) {
+            return -errno;
+        }
+        buffer += readSize;
+        if (buffer == end) return OK;
+    }
+    return status;
+}
+
 status_t RpcSession::readId() {
     {
         std::lock_guard<std::mutex> _l(mMutex);
diff --git a/libs/binder/include/binder/Binder.h b/libs/binder/include/binder/Binder.h
index 7e9be41..754f87c 100644
--- a/libs/binder/include/binder/Binder.h
+++ b/libs/binder/include/binder/Binder.h
@@ -94,6 +94,9 @@
 
     pid_t               getDebugPid();
 
+    [[nodiscard]] status_t setRpcClientDebug(android::base::unique_fd clientFd,
+                                             uint32_t maxRpcThreads);
+
 protected:
     virtual             ~BBinder();
 
@@ -111,6 +114,8 @@
 
     Extras*             getOrCreateExtras();
 
+    [[nodiscard]] status_t setRpcClientDebug(const Parcel& data);
+
     std::atomic<Extras*> mExtras;
 
     friend ::android::internal::Stability;
diff --git a/libs/binder/include/binder/IBinder.h b/libs/binder/include/binder/IBinder.h
index 97c826c..ce28d7c 100644
--- a/libs/binder/include/binder/IBinder.h
+++ b/libs/binder/include/binder/IBinder.h
@@ -60,6 +60,7 @@
         SYSPROPS_TRANSACTION = B_PACK_CHARS('_', 'S', 'P', 'R'),
         EXTENSION_TRANSACTION = B_PACK_CHARS('_', 'E', 'X', 'T'),
         DEBUG_PID_TRANSACTION = B_PACK_CHARS('_', 'P', 'I', 'D'),
+        SET_RPC_CLIENT_TRANSACTION = B_PACK_CHARS('_', 'R', 'P', 'C'),
 
         // See android.os.IBinder.TWEET_TRANSACTION
         // Most importantly, messages can be anything not exceeding 130 UTF-8
@@ -152,6 +153,27 @@
      */
     status_t                getDebugPid(pid_t* outPid);
 
+    /**
+     * Set the RPC client fd to this binder service, for debugging. This is only available on
+     * debuggable builds.
+     *
+     * |maxRpcThreads| must be positive because RPC is useless without threads.
+     *
+     * When this is called on a binder service, the service:
+     * 1. sets up RPC server
+     * 2. spawns 1 new thread that calls RpcServer::join()
+     *    - join() spawns at most |maxRpcThreads| threads that accept() connections; see RpcServer
+     *
+     * setRpcClientDebug() may only be called once.
+     * TODO(b/182914638): If allow to shut down the client, addRpcClient can be called repeatedly.
+     *
+     * Note: A thread is spawned for each accept()'ed fd, which may call into functions of the
+     * interface freely. See RpcServer::join(). To avoid such race conditions, implement the service
+     * functions with multithreading support.
+     */
+    [[nodiscard]] status_t setRpcClientDebug(android::base::unique_fd socketFd,
+                                             uint32_t maxRpcThreads);
+
     // NOLINTNEXTLINE(google-default-arguments)
     virtual status_t        transact(   uint32_t code,
                                         const Parcel& data,
diff --git a/libs/binder/include/binder/IPCThreadState.h b/libs/binder/include/binder/IPCThreadState.h
index ee661a5..204926d 100644
--- a/libs/binder/include/binder/IPCThreadState.h
+++ b/libs/binder/include/binder/IPCThreadState.h
@@ -62,7 +62,7 @@
              * call. If not in a binder call, this will return getpid. If the
              * call is oneway, this will return 0.
              */
-            pid_t               getCallingPid() const;
+            [[nodiscard]] pid_t getCallingPid() const;
 
             /**
              * Returns the SELinux security identifier of the process which has
@@ -73,13 +73,13 @@
              * This can't be restored once it's cleared, and it does not return the
              * context of the current process when not in a binder call.
              */
-            const char*         getCallingSid() const;
+            [[nodiscard]] const char* getCallingSid() const;
 
             /**
              * Returns the UID of the process which has made the current binder
              * call. If not in a binder call, this will return 0.
              */
-            uid_t               getCallingUid() const;
+            [[nodiscard]] uid_t getCallingUid() const;
 
             /**
              * Make it an abort to rely on getCalling* for a section of
@@ -243,6 +243,7 @@
             // Whether the work source should be propagated.
             bool                mPropagateWorkSource;
             bool                mIsLooper;
+            bool mIsFlushing;
             int32_t             mStrictModePolicy;
             int32_t             mLastTransactionBinderFlags;
             CallRestriction     mCallRestriction;
diff --git a/libs/binder/include/binder/ProcessState.h b/libs/binder/include/binder/ProcessState.h
index b992c04..72c2ab7 100644
--- a/libs/binder/include/binder/ProcessState.h
+++ b/libs/binder/include/binder/ProcessState.h
@@ -84,6 +84,13 @@
     // before any threads are spawned.
     void setCallRestriction(CallRestriction restriction);
 
+    /**
+     * Get the max number of threads that the kernel can start.
+     *
+     * Note: this is the lower bound. Additional threads may be started.
+     */
+    size_t getThreadPoolMaxThreadCount() const;
+
 private:
     static sp<ProcessState> init(const char* defaultDriver, bool requireDefault);
 
diff --git a/libs/binder/include/binder/RpcServer.h b/libs/binder/include/binder/RpcServer.h
index 365bed6..50770f1 100644
--- a/libs/binder/include/binder/RpcServer.h
+++ b/libs/binder/include/binder/RpcServer.h
@@ -117,6 +117,11 @@
     sp<IBinder> getRootObject();
 
     /**
+     * Runs join() in a background thread. Immediately returns.
+     */
+    void start();
+
+    /**
      * You must have at least one client session before calling this.
      *
      * If a client needs to actively terminate join, call shutdown() in a separate thread.
@@ -150,7 +155,7 @@
     friend sp<RpcServer>;
     RpcServer();
 
-    void establishConnection(sp<RpcServer>&& session, base::unique_fd clientFd);
+    static void establishConnection(sp<RpcServer>&& server, base::unique_fd clientFd);
     bool setupSocketServer(const RpcSocketAddress& address);
     [[nodiscard]] bool acceptOne();
 
@@ -159,12 +164,13 @@
     base::unique_fd mServer; // socket we are accepting sessions on
 
     std::mutex mLock; // for below
+    std::unique_ptr<std::thread> mJoinThread;
+    bool mJoinThreadRunning = false;
     std::map<std::thread::id, std::thread> mConnectingThreads;
     sp<IBinder> mRootObject;
     wp<IBinder> mRootObjectWeak;
     std::map<int32_t, sp<RpcSession>> mSessions;
     int32_t mSessionIdCounter = 0;
-    bool mJoinThreadRunning = false;
     std::unique_ptr<RpcSession::FdTrigger> mShutdownTrigger;
     std::condition_variable mShutdownCv;
 };
diff --git a/libs/binder/include/binder/RpcSession.h b/libs/binder/include/binder/RpcSession.h
index bcef8dc..d6b796f 100644
--- a/libs/binder/include/binder/RpcSession.h
+++ b/libs/binder/include/binder/RpcSession.h
@@ -132,7 +132,16 @@
          *   true - time to read!
          *   false - trigger happened
          */
-        bool triggerablePollRead(base::borrowed_fd fd);
+        status_t triggerablePollRead(base::borrowed_fd fd);
+
+        /**
+         * Read, but allow the read to be interrupted by this trigger.
+         *
+         * Return:
+         *   true - read succeeded at 'size'
+         *   false - interrupted (failure or trigger)
+         */
+        status_t interruptableReadFully(base::borrowed_fd fd, void* data, size_t size);
 
     private:
         base::unique_fd mWrite;
diff --git a/libs/binder/tests/Android.bp b/libs/binder/tests/Android.bp
index ec231b2..9cf433d 100644
--- a/libs/binder/tests/Android.bp
+++ b/libs/binder/tests/Android.bp
@@ -60,6 +60,7 @@
     defaults: ["binder_test_defaults"],
     srcs: ["binderLibTest.cpp"],
     shared_libs: [
+        "libbase",
         "libbinder",
         "libutils",
     ],
@@ -101,6 +102,7 @@
 
     srcs: ["binderLibTest.cpp"],
     shared_libs: [
+        "libbase",
         "libbinder",
         "libutils",
     ],
@@ -133,6 +135,9 @@
         darwin: {
             enabled: false,
         },
+        android: {
+            test_suites: ["vts"],
+        },
     },
     defaults: [
         "binder_test_defaults",
diff --git a/libs/binder/tests/binderLibTest.cpp b/libs/binder/tests/binderLibTest.cpp
index 963d7b3..6006fd3 100644
--- a/libs/binder/tests/binderLibTest.cpp
+++ b/libs/binder/tests/binderLibTest.cpp
@@ -21,20 +21,29 @@
 #include <pthread.h>
 #include <stdio.h>
 #include <stdlib.h>
+
+#include <chrono>
 #include <thread>
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
+#include <android-base/properties.h>
+#include <android-base/result.h>
+#include <android-base/unique_fd.h>
 #include <binder/Binder.h>
 #include <binder/IBinder.h>
 #include <binder/IPCThreadState.h>
 #include <binder/IServiceManager.h>
 #include <binder/ParcelRef.h>
+#include <binder/RpcServer.h>
+#include <binder/RpcSession.h>
 
 #include <linux/sched.h>
 #include <sys/epoll.h>
 #include <sys/prctl.h>
+#include <sys/socket.h>
+#include <sys/un.h>
 
 #include "../binder_module.h"
 #include "binderAbiHelper.h"
@@ -42,7 +51,11 @@
 #define ARRAY_SIZE(array) (sizeof array / sizeof array[0])
 
 using namespace android;
+using namespace std::string_literals;
+using namespace std::chrono_literals;
+using testing::ExplainMatchResult;
 using testing::Not;
+using testing::WithParamInterface;
 
 // e.g. EXPECT_THAT(expr, StatusEq(OK)) << "additional message";
 MATCHER_P(StatusEq, expected, (negation ? "not " : "") + statusToString(expected)) {
@@ -50,6 +63,15 @@
     return expected == arg;
 }
 
+// e.g. Result<int32_t> v = 0; EXPECT_THAT(result, ResultHasValue(0));
+MATCHER_P(ResultHasValue, resultMatcher, "") {
+    if (!arg.ok()) {
+        *result_listener << "contains error " << arg.error();
+        return false;
+    }
+    return ExplainMatchResult(resultMatcher, arg.value(), result_listener);
+}
+
 static ::testing::AssertionResult IsPageAligned(void *buf) {
     if (((unsigned long)buf & ((unsigned long)PAGE_SIZE - 1)) == 0)
         return ::testing::AssertionSuccess();
@@ -98,6 +120,8 @@
     BINDER_LIB_TEST_ECHO_VECTOR,
     BINDER_LIB_TEST_REJECT_BUF,
     BINDER_LIB_TEST_CAN_GET_SID,
+    BINDER_LIB_TEST_USLEEP,
+    BINDER_LIB_TEST_CREATE_TEST_SERVICE,
 };
 
 pid_t start_server_process(int arg2, bool usePoll = false)
@@ -158,6 +182,20 @@
     return pid;
 }
 
+android::base::Result<int32_t> GetId(sp<IBinder> service) {
+    using android::base::Error;
+    Parcel data, reply;
+    data.markForBinder(service);
+    const char *prefix = data.isForRpc() ? "On RPC server, " : "On binder server, ";
+    status_t status = service->transact(BINDER_LIB_TEST_GET_ID_TRANSACTION, data, &reply);
+    if (status != OK)
+        return Error(status) << prefix << "transact(GET_ID): " << statusToString(status);
+    int32_t result = 0;
+    status = reply.readInt32(&result);
+    if (status != OK) return Error(status) << prefix << "readInt32: " << statusToString(status);
+    return result;
+}
+
 class BinderLibTestEnv : public ::testing::Environment {
     public:
         BinderLibTestEnv() {}
@@ -477,12 +515,7 @@
 }
 
 TEST_F(BinderLibTest, GetId) {
-    int32_t id;
-    Parcel data, reply;
-    EXPECT_THAT(m_server->transact(BINDER_LIB_TEST_GET_ID_TRANSACTION, data, &reply),
-                StatusEq(NO_ERROR));
-    EXPECT_THAT(reply.readInt32(&id), StatusEq(NO_ERROR));
-    EXPECT_EQ(0, id);
+    EXPECT_THAT(GetId(m_server), ResultHasValue(0));
 }
 
 TEST_F(BinderLibTest, PtrSize) {
@@ -1173,14 +1206,170 @@
     EXPECT_THAT(server->transact(BINDER_LIB_TEST_CAN_GET_SID, data, nullptr), StatusEq(OK));
 }
 
+class BinderLibRpcTestBase : public BinderLibTest {
+public:
+    void SetUp() override {
+        if (!base::GetBoolProperty("ro.debuggable", false)) {
+            GTEST_SKIP() << "Binder RPC is only enabled on debuggable builds, skipping test on "
+                            "non-debuggable builds.";
+        }
+        BinderLibTest::SetUp();
+    }
+
+    std::tuple<android::base::unique_fd, unsigned int> CreateSocket() {
+        auto rpcServer = RpcServer::make();
+        EXPECT_NE(nullptr, rpcServer);
+        if (rpcServer == nullptr) return {};
+        rpcServer->iUnderstandThisCodeIsExperimentalAndIWillNotUseItInProduction();
+        unsigned int port;
+        if (!rpcServer->setupInetServer(0, &port)) {
+            ADD_FAILURE() << "setupInetServer failed";
+            return {};
+        }
+        return {rpcServer->releaseServer(), port};
+    }
+};
+
+class BinderLibRpcTest : public BinderLibRpcTestBase, public WithParamInterface<bool> {
+public:
+    sp<IBinder> GetService() {
+        return GetParam() ? sp<IBinder>(addServer()) : sp<IBinder>(sp<BBinder>::make());
+    }
+    static std::string ParamToString(const testing::TestParamInfo<ParamType> &info) {
+        return info.param ? "remote" : "local";
+    }
+};
+
+TEST_P(BinderLibRpcTest, SetRpcMaxThreads) {
+    auto binder = GetService();
+    ASSERT_TRUE(binder != nullptr);
+    auto [socket, port] = CreateSocket();
+    ASSERT_TRUE(socket.ok());
+    EXPECT_THAT(binder->setRpcClientDebug(std::move(socket), 1), StatusEq(OK));
+}
+
+TEST_P(BinderLibRpcTest, SetRpcClientNoFd) {
+    auto binder = GetService();
+    ASSERT_TRUE(binder != nullptr);
+    EXPECT_THAT(binder->setRpcClientDebug(android::base::unique_fd(), 1), StatusEq(BAD_VALUE));
+}
+
+TEST_P(BinderLibRpcTest, SetRpcMaxThreadsZero) {
+    auto binder = GetService();
+    ASSERT_TRUE(binder != nullptr);
+    auto [socket, port] = CreateSocket();
+    ASSERT_TRUE(socket.ok());
+    EXPECT_THAT(binder->setRpcClientDebug(std::move(socket), 0), StatusEq(BAD_VALUE));
+}
+
+TEST_P(BinderLibRpcTest, SetRpcMaxThreadsTwice) {
+    auto binder = GetService();
+    ASSERT_TRUE(binder != nullptr);
+
+    auto [socket1, port1] = CreateSocket();
+    ASSERT_TRUE(socket1.ok());
+    EXPECT_THAT(binder->setRpcClientDebug(std::move(socket1), 1), StatusEq(OK));
+
+    auto [socket2, port2] = CreateSocket();
+    ASSERT_TRUE(socket2.ok());
+    EXPECT_THAT(binder->setRpcClientDebug(std::move(socket2), 1), StatusEq(ALREADY_EXISTS));
+}
+
+INSTANTIATE_TEST_CASE_P(BinderLibTest, BinderLibRpcTest, testing::Bool(),
+                        BinderLibRpcTest::ParamToString);
+
+class BinderLibTestService;
+class BinderLibRpcClientTest : public BinderLibRpcTestBase,
+                               public WithParamInterface<std::tuple<bool, uint32_t>> {
+public:
+    static std::string ParamToString(const testing::TestParamInfo<ParamType> &info) {
+        auto [isRemote, numThreads] = info.param;
+        return (isRemote ? "remote" : "local") + "_server_with_"s + std::to_string(numThreads) +
+                "_threads";
+    }
+    sp<IBinder> CreateRemoteService(int32_t id) {
+        Parcel data, reply;
+        status_t status = data.writeInt32(id);
+        EXPECT_THAT(status, StatusEq(OK));
+        if (status != OK) return nullptr;
+        status = m_server->transact(BINDER_LIB_TEST_CREATE_TEST_SERVICE, data, &reply);
+        EXPECT_THAT(status, StatusEq(OK));
+        if (status != OK) return nullptr;
+        sp<IBinder> ret;
+        status = reply.readStrongBinder(&ret);
+        EXPECT_THAT(status, StatusEq(OK));
+        if (status != OK) return nullptr;
+        return ret;
+    }
+};
+
+TEST_P(BinderLibRpcClientTest, Test) {
+    auto [isRemote, numThreadsParam] = GetParam();
+    uint32_t numThreads = numThreadsParam; // ... to be captured in lambda
+    int32_t id = 0xC0FFEE00 + numThreads;
+    sp<IBinder> server = isRemote ? sp<IBinder>(CreateRemoteService(id))
+                                  : sp<IBinder>(sp<BinderLibTestService>::make(id, false));
+    ASSERT_EQ(isRemote, !!server->remoteBinder());
+    ASSERT_THAT(GetId(server), ResultHasValue(id));
+
+    unsigned int port = 0;
+    // Fake servicedispatcher.
+    {
+        auto [socket, socketPort] = CreateSocket();
+        ASSERT_TRUE(socket.ok());
+        port = socketPort;
+        ASSERT_THAT(server->setRpcClientDebug(std::move(socket), numThreads), StatusEq(OK));
+    }
+
+    auto callUsleep = [](sp<IBinder> server, uint64_t us) {
+        Parcel data, reply;
+        data.markForBinder(server);
+        const char *name = data.isForRpc() ? "RPC" : "binder";
+        EXPECT_THAT(data.writeUint64(us), StatusEq(OK));
+        EXPECT_THAT(server->transact(BINDER_LIB_TEST_USLEEP, data, &reply), StatusEq(OK))
+                << "for " << name << " server";
+    };
+
+    auto threadFn = [&](size_t threadNum) {
+        usleep(threadNum * 50 * 1000); // threadNum * 50ms. Need this to avoid SYN flooding.
+        auto rpcSession = RpcSession::make();
+        ASSERT_TRUE(rpcSession->setupInetClient("127.0.0.1", port));
+        auto rpcServerBinder = rpcSession->getRootObject();
+        ASSERT_NE(nullptr, rpcServerBinder);
+
+        EXPECT_EQ(OK, rpcServerBinder->pingBinder());
+
+        // Check that |rpcServerBinder| and |server| points to the same service.
+        EXPECT_THAT(GetId(rpcServerBinder), ResultHasValue(id));
+
+        // Occupy the server thread. The server should still have enough threads to handle
+        // other connections.
+        // (numThreads - threadNum) * 100ms
+        callUsleep(rpcServerBinder, (numThreads - threadNum) * 100 * 1000);
+    };
+    std::vector<std::thread> threads;
+    for (size_t i = 0; i < numThreads; ++i) threads.emplace_back(std::bind(threadFn, i));
+    for (auto &t : threads) t.join();
+}
+
+INSTANTIATE_TEST_CASE_P(BinderLibTest, BinderLibRpcClientTest,
+                        testing::Combine(testing::Bool(), testing::Range(1u, 10u)),
+                        BinderLibRpcClientTest::ParamToString);
+
 class BinderLibTestService : public BBinder {
 public:
-    explicit BinderLibTestService(int32_t id)
-          : m_id(id), m_nextServerId(id + 1), m_serverStartRequested(false), m_callback(nullptr) {
+    explicit BinderLibTestService(int32_t id, bool exitOnDestroy = true)
+          : m_id(id),
+            m_nextServerId(id + 1),
+            m_serverStartRequested(false),
+            m_callback(nullptr),
+            m_exitOnDestroy(exitOnDestroy) {
         pthread_mutex_init(&m_serverWaitMutex, nullptr);
         pthread_cond_init(&m_serverWaitCond, nullptr);
     }
-    ~BinderLibTestService() { exit(EXIT_SUCCESS); }
+    ~BinderLibTestService() {
+        if (m_exitOnDestroy) exit(EXIT_SUCCESS);
+    }
 
     void processPendingCall() {
         if (m_callback != nullptr) {
@@ -1193,7 +1382,8 @@
 
     virtual status_t onTransact(uint32_t code, const Parcel &data, Parcel *reply,
                                 uint32_t flags = 0) {
-        if (getuid() != (uid_t)IPCThreadState::self()->getCallingUid()) {
+        // TODO(b/182914638): also checks getCallingUid() for RPC
+        if (!data.isForRpc() && getuid() != (uid_t)IPCThreadState::self()->getCallingUid()) {
             return PERMISSION_DENIED;
         }
         switch (code) {
@@ -1480,6 +1670,18 @@
             case BINDER_LIB_TEST_CAN_GET_SID: {
                 return IPCThreadState::self()->getCallingSid() == nullptr ? BAD_VALUE : NO_ERROR;
             }
+            case BINDER_LIB_TEST_USLEEP: {
+                uint64_t us;
+                if (status_t status = data.readUint64(&us); status != NO_ERROR) return status;
+                usleep(us);
+                return NO_ERROR;
+            }
+            case BINDER_LIB_TEST_CREATE_TEST_SERVICE: {
+                int32_t id;
+                if (status_t status = data.readInt32(&id); status != NO_ERROR) return status;
+                reply->writeStrongBinder(sp<BinderLibTestService>::make(id, false));
+                return NO_ERROR;
+            }
             default:
                 return UNKNOWN_TRANSACTION;
         };
@@ -1494,6 +1696,7 @@
     sp<IBinder> m_serverStarted;
     sp<IBinder> m_strongRef;
     sp<IBinder> m_callback;
+    bool m_exitOnDestroy;
 };
 
 int run_server(int index, int readypipefd, bool usePoll)
diff --git a/libs/binder/tests/parcel_fuzzer/main.cpp b/libs/binder/tests/parcel_fuzzer/main.cpp
index f426fd3..2a79e85 100644
--- a/libs/binder/tests/parcel_fuzzer/main.cpp
+++ b/libs/binder/tests/parcel_fuzzer/main.cpp
@@ -23,7 +23,8 @@
 #include <iostream>
 
 #include <android-base/logging.h>
-#include <binder/RpcSession.h>
+#include <android/binder_auto_utils.h>
+#include <android/binder_libbinder.h>
 #include <fuzzbinder/random_parcel.h>
 #include <fuzzer/FuzzedDataProvider.h>
 
@@ -33,7 +34,6 @@
 #include <sys/time.h>
 
 using android::fillRandomParcel;
-using android::RpcSession;
 using android::sp;
 
 void fillRandomParcel(::android::hardware::Parcel* p, FuzzedDataProvider&& provider) {
@@ -46,9 +46,22 @@
     fillRandomParcel(p->parcel(), std::move(provider));
 }
 
+template <typename P, typename B>
+void doTransactFuzz(const char* backend, const sp<B>& binder, FuzzedDataProvider&& provider) {
+    uint32_t code = provider.ConsumeIntegral<uint32_t>();
+    uint32_t flag = provider.ConsumeIntegral<uint32_t>();
+
+    FUZZ_LOG() << "backend: " << backend;
+
+    P reply;
+    P data;
+    fillRandomParcel(&data, std::move(provider));
+    (void)binder->transact(code, data, &reply, flag);
+}
+
 template <typename P>
-void doFuzz(const char* backend, const std::vector<ParcelRead<P>>& reads,
-            FuzzedDataProvider&& provider) {
+void doReadFuzz(const char* backend, const std::vector<ParcelRead<P>>& reads,
+                FuzzedDataProvider&& provider) {
     // Allow some majority of the bytes to be dedicated to telling us what to
     // do. The fixed value added here represents that we want to test doing a
     // lot of 'instructions' even on really short parcels.
@@ -59,18 +72,7 @@
             provider.ConsumeIntegralInRange<size_t>(0, maxInstructions));
 
     P p;
-    if constexpr (std::is_same_v<P, android::Parcel>) {
-        if (provider.ConsumeBool()) {
-            auto session = sp<RpcSession>::make();
-            CHECK(session->addNullDebuggingClient());
-            p.markForRpc(session);
-            fillRandomParcelData(&p, std::move(provider));
-        } else {
-            fillRandomParcel(&p, std::move(provider));
-        }
-    } else {
-        fillRandomParcel(&p, std::move(provider));
-    }
+    fillRandomParcel(&p, std::move(provider));
 
     // since we are only using a byte to index
     CHECK(reads.size() <= 255) << reads.size();
@@ -95,6 +97,17 @@
     }
 }
 
+void* NothingClass_onCreate(void* args) {
+    return args;
+}
+void NothingClass_onDestroy(void* /*userData*/) {}
+binder_status_t NothingClass_onTransact(AIBinder*, transaction_code_t, const AParcel*, AParcel*) {
+    return STATUS_UNKNOWN_ERROR;
+}
+static AIBinder_Class* kNothingClass =
+        AIBinder_Class_define("nothing", NothingClass_onCreate, NothingClass_onDestroy,
+                              NothingClass_onTransact);
+
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
     if (size <= 1) return 0;  // no use
 
@@ -103,18 +116,35 @@
 
     FuzzedDataProvider provider = FuzzedDataProvider(data, size);
 
-    const std::function<void(FuzzedDataProvider &&)> fuzzBackend[3] = {
+    const std::function<void(FuzzedDataProvider &&)> fuzzBackend[] = {
             [](FuzzedDataProvider&& provider) {
-                doFuzz<::android::hardware::Parcel>("hwbinder", HWBINDER_PARCEL_READ_FUNCTIONS,
-                                                    std::move(provider));
+                doTransactFuzz<
+                        ::android::hardware::Parcel>("hwbinder",
+                                                     sp<::android::hardware::BHwBinder>::make(),
+                                                     std::move(provider));
             },
             [](FuzzedDataProvider&& provider) {
-                doFuzz<::android::Parcel>("binder", BINDER_PARCEL_READ_FUNCTIONS,
-                                          std::move(provider));
+                doTransactFuzz<::android::Parcel>("binder", sp<::android::BBinder>::make(),
+                                                  std::move(provider));
             },
             [](FuzzedDataProvider&& provider) {
-                doFuzz<NdkParcelAdapter>("binder_ndk", BINDER_NDK_PARCEL_READ_FUNCTIONS,
-                                         std::move(provider));
+                // fuzz from the libbinder layer since it's a superset of the
+                // interface you get at the libbinder_ndk layer
+                auto ndkBinder = ndk::SpAIBinder(AIBinder_new(kNothingClass, nullptr));
+                auto binder = AIBinder_toPlatformBinder(ndkBinder.get());
+                doTransactFuzz<::android::Parcel>("binder_ndk", binder, std::move(provider));
+            },
+            [](FuzzedDataProvider&& provider) {
+                doReadFuzz<::android::hardware::Parcel>("hwbinder", HWBINDER_PARCEL_READ_FUNCTIONS,
+                                                        std::move(provider));
+            },
+            [](FuzzedDataProvider&& provider) {
+                doReadFuzz<::android::Parcel>("binder", BINDER_PARCEL_READ_FUNCTIONS,
+                                              std::move(provider));
+            },
+            [](FuzzedDataProvider&& provider) {
+                doReadFuzz<NdkParcelAdapter>("binder_ndk", BINDER_NDK_PARCEL_READ_FUNCTIONS,
+                                             std::move(provider));
             },
     };
 
diff --git a/libs/binder/tests/parcel_fuzzer/random_parcel.cpp b/libs/binder/tests/parcel_fuzzer/random_parcel.cpp
index b045a22..92fdc72 100644
--- a/libs/binder/tests/parcel_fuzzer/random_parcel.cpp
+++ b/libs/binder/tests/parcel_fuzzer/random_parcel.cpp
@@ -18,6 +18,7 @@
 
 #include <android-base/logging.h>
 #include <binder/IServiceManager.h>
+#include <binder/RpcSession.h>
 #include <fuzzbinder/random_fd.h>
 #include <utils/String16.h>
 
@@ -33,6 +34,14 @@
 };
 
 void fillRandomParcel(Parcel* p, FuzzedDataProvider&& provider) {
+    if (provider.ConsumeBool()) {
+        auto session = sp<RpcSession>::make();
+        CHECK(session->addNullDebuggingClient());
+        p->markForRpc(session);
+        fillRandomParcelData(p, std::move(provider));
+        return;
+    }
+
     while (provider.remaining_bytes() > 0) {
         auto fillFunc = provider.PickValueInArray<const std::function<void()>>({
                 // write data
diff --git a/libs/binder/tests/rpc_fuzzer/main.cpp b/libs/binder/tests/rpc_fuzzer/main.cpp
index 84f5974..e6fd392 100644
--- a/libs/binder/tests/rpc_fuzzer/main.cpp
+++ b/libs/binder/tests/rpc_fuzzer/main.cpp
@@ -20,6 +20,7 @@
 #include <binder/Parcel.h>
 #include <binder/RpcServer.h>
 #include <binder/RpcSession.h>
+#include <fuzzer/FuzzedDataProvider.h>
 
 #include <sys/resource.h>
 #include <sys/un.h>
@@ -53,6 +54,7 @@
 
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
     if (size > 50000) return 0;
+    FuzzedDataProvider provider(data, size);
 
     unlink(kSock.c_str());
 
@@ -84,14 +86,21 @@
     CHECK(base::WriteFully(clientFd, &id, sizeof(id)));
 #endif
 
-    CHECK(base::WriteFully(clientFd, data, size));
+    bool hangupBeforeShutdown = provider.ConsumeBool();
 
-    clientFd.reset();
+    std::vector<uint8_t> writeData = provider.ConsumeRemainingBytes<uint8_t>();
+    CHECK(base::WriteFully(clientFd, writeData.data(), writeData.size()));
+
+    if (hangupBeforeShutdown) {
+        clientFd.reset();
+    }
 
     // TODO(185167543): currently this is okay because we only shutdown the one
     // thread, but once we can shutdown other sessions, we'll need to change
     // this behavior in order to make sure all of the input is actually read.
     while (!server->shutdown()) usleep(100);
+
+    clientFd.reset();
     serverThread.join();
 
     // TODO(b/185167543): better way to force a server to shutdown
diff --git a/libs/binder/tests/unit_fuzzers/BinderFuzzFunctions.h b/libs/binder/tests/unit_fuzzers/BinderFuzzFunctions.h
index 69f1b9d..72c5bc4 100644
--- a/libs/binder/tests/unit_fuzzers/BinderFuzzFunctions.h
+++ b/libs/binder/tests/unit_fuzzers/BinderFuzzFunctions.h
@@ -72,6 +72,11 @@
                               },
                               [](FuzzedDataProvider*, const sp<BBinder>& bbinder) -> void {
                                   bbinder->getDebugPid();
+                              },
+                              [](FuzzedDataProvider* fdp, const sp<BBinder>& bbinder) -> void {
+                                  auto rpcMaxThreads = fdp->ConsumeIntegralInRange<uint32_t>(0, 20);
+                                  (void)bbinder->setRpcClientDebug(android::base::unique_fd(),
+                                                                   rpcMaxThreads);
                               }};
 
 } // namespace android