blob: 14fa5dc8f372801f304a01e484a86f3375059a9a [file] [log] [blame]
// Copyright 2024 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "patchpanel/lifeline_fd_service.h"
#include <fcntl.h>
#include <sys/socket.h>
#include <unistd.h>
#include <utility>
#include <vector>
#include <base/files/scoped_file.h>
#include <base/functional/bind.h>
#include <base/functional/callback.h>
#include <base/test/task_environment.h>
#include <gtest/gtest.h>
using testing::ElementsAre;
namespace patchpanel {
namespace {
std::pair<base::ScopedFD, base::ScopedFD> CreatePipeFDs() {
int pipe_fds[2] = {-1, -1};
if (pipe2(pipe_fds, O_CLOEXEC) < 0) {
return {};
}
return {base::ScopedFD(pipe_fds[0]), base::ScopedFD(pipe_fds[1])};
}
bool IsValidFD(int fd) {
return fcntl(fd, F_GETFD) != -1 || errno != EBADF;
}
std::vector<base::ScopedFD> AssignFDs(int n) {
std::vector<base::ScopedFD> fds;
for (int i = 0; i < n; i++) {
fds.push_back(base::ScopedFD(socket(AF_INET, SOCK_STREAM, 0)));
}
return fds;
}
bool ContainsFD(const std::vector<base::ScopedFD>& fds, int target_fd) {
for (const auto& fd : fds) {
if (fd.get() == target_fd) {
return true;
}
}
return false;
}
} // namespace
class LifelineFDServiceTest : public testing::Test {
public:
MOCK_METHOD(void, UserCallback, (), ());
protected:
// The environment instances which are required for using
// base::FileDescriptorWatcher::WatchReadable. Declared them first to ensure
// they are the last things to be cleaned up.
base::test::TaskEnvironment task_environment_{
base::test::TaskEnvironment::MainThreadType::IO};
LifelineFDService lifeline_svc_;
};
TEST_F(LifelineFDServiceTest, LifelineFDIsDestroyedFirst) {
// Pre-create some file descriptors to force the fds used in the test to be
// created at an offset. This ensures that asserts on the underlying fd values
// are safe to do.
auto fds = AssignFDs(20);
auto [fd_for_client, fd_for_service] = CreatePipeFDs();
ASSERT_TRUE(fd_for_client.is_valid());
ASSERT_TRUE(fd_for_service.is_valid());
int raw_fd_for_service = fd_for_service.get();
fds.clear();
// The user callback will not be invoked if the user destroys the
// ScopedClosureRunner on their side first.
EXPECT_CALL(*this, UserCallback).Times(0);
base::OnceClosure user_callback = base::BindOnce(
&LifelineFDServiceTest::UserCallback, base::Unretained(this));
auto closure = lifeline_svc_.AddLifelineFD(std::move(fd_for_service),
std::move(user_callback));
ASSERT_TRUE(closure);
EXPECT_THAT(lifeline_svc_.get_lifeline_fds_for_testing(),
ElementsAre(raw_fd_for_service));
// The underlying int value of |fd_for_service| is valid
ASSERT_TRUE(IsValidFD(raw_fd_for_service));
// The underlying int value of |fd_for_service| is reserved.
EXPECT_FALSE(ContainsFD(AssignFDs(50), raw_fd_for_service));
// The local owner of |closure| destroys it before the remote client
// invalidates |fd_for_service|.
closure = base::ScopedClosureRunner();
// The lifeline fd is unregistered in the LifelineFD service.
EXPECT_THAT(lifeline_svc_.get_lifeline_fds_for_testing(), ElementsAre());
// The underlying int value of |fd_for_service| is not a valid fd anymore.
ASSERT_FALSE(IsValidFD(raw_fd_for_service));
// Remotely invalidating the local fd has no effect.
fd_for_client.reset();
task_environment_.RunUntilIdle();
}
TEST_F(LifelineFDServiceTest, RemoteClientInvalidatesFDFirst) {
// Pre-create some file descriptors to force the fds used in the test to be
// created at an offset. This ensures that asserts on the underlying fd values
// are safe to do.
auto fds = AssignFDs(20);
auto [fd_for_client, fd_for_service] = CreatePipeFDs();
ASSERT_TRUE(fd_for_client.is_valid());
ASSERT_TRUE(fd_for_service.is_valid());
int raw_fd_for_service = fd_for_service.get();
fds.clear();
// The user callback will be invoked if the user fd gets remotely invalidated
// first.
EXPECT_CALL(*this, UserCallback);
base::OnceClosure user_callback = base::BindOnce(
&LifelineFDServiceTest::UserCallback, base::Unretained(this));
auto closure = lifeline_svc_.AddLifelineFD(std::move(fd_for_service),
std::move(user_callback));
ASSERT_TRUE(closure);
EXPECT_THAT(lifeline_svc_.get_lifeline_fds_for_testing(),
ElementsAre(raw_fd_for_service));
// The underlying int value of |fd_for_service| is valid
ASSERT_TRUE(IsValidFD(raw_fd_for_service));
// The underlying int value of |fd_for_service| is reserved.
EXPECT_FALSE(ContainsFD(AssignFDs(50), raw_fd_for_service));
// The remote client invalidates |fd_for_service| before the local owner of
// |closure| destroys it.
fd_for_client.reset();
task_environment_.RunUntilIdle();
// The lifeline fd is unregistered in the LifelineFD service.
EXPECT_THAT(lifeline_svc_.get_lifeline_fds_for_testing(), ElementsAre());
// The underlying int value of |fd_for_service| is not a valid fd anymore.
ASSERT_FALSE(IsValidFD(raw_fd_for_service));
// The local owner is notified via |user_callback| and now destroys its
// LifelineFD instance. This has no effect.
closure = base::ScopedClosureRunner();
}
} // namespace patchpanel