blob: 91786ac41ef649b26997dd0ab848c46e9541f659 [file] [log] [blame]
// Copyright 2025 The Fuchsia Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/starnix/tests/syscalls/cpp/binder/client.h"
#include <fcntl.h>
#include <lib/fit/defer.h>
#include <lib/fit/function.h>
#include <sys/ioctl.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <queue>
#include <fbl/unique_fd.h>
#include <gtest/gtest.h>
#include <linux/android/binder.h>
#include "src/starnix/tests/syscalls/cpp/binder/common.h"
#include "src/starnix/tests/syscalls/cpp/binder_helper.h"
#include "src/starnix/tests/syscalls/cpp/syscall_matchers.h"
#include "src/starnix/tests/syscalls/cpp/test_helper.h"
namespace starnix_binder {
namespace {
struct WriteReadIoctl {
const binder_size_t size;
const union {
const AcquireWriteBuffer acquire;
const TransactionWriteBuffer transaction;
} write_buffer;
const bool read_buffer;
const int fd;
};
void ClientBehavior(std::string_view binder_dir, test_helper::Poker completed) {
auto fd_and_mapping = OpenBinderAndMap(binder_dir);
const TransactionWriteBuffer get_handle_write_buffer = {
.command = BC_TRANSACTION,
.data =
{
.target =
{
.handle = kServiceManagerHandle,
},
.cookie = 0,
.code = kGetService,
.flags = TF_ACCEPT_FDS,
.data_size = 0,
.offsets_size = 0,
.data =
{
.ptr =
{
.buffer = (binder_uintptr_t) nullptr,
.offsets = (binder_uintptr_t) nullptr,
},
},
},
};
std::optional<uint32_t> service_provider_handle = std::nullopt;
std::optional<uint32_t> service_provider_binder_fd = std::nullopt;
int8_t transactions_remaining = 3;
std::queue<WriteReadIoctl> queue = {};
queue.push({
.size = sizeof(get_handle_write_buffer),
.write_buffer = {.transaction = get_handle_write_buffer},
.read_buffer = true,
.fd = fd_and_mapping.fd_.get(),
});
while (0 < transactions_remaining) {
std::array<uint32_t, 32> read_buffer = {};
struct binder_write_read write_read;
int fd_for_ioctl;
if (queue.empty()) {
write_read = {
.write_size = 0,
.write_consumed = 0,
.write_buffer = (binder_uintptr_t) nullptr,
.read_size = sizeof(read_buffer),
.read_consumed = 0,
.read_buffer = (binder_uintptr_t)read_buffer.data(),
};
fd_for_ioctl = fd_and_mapping.fd_.get();
} else {
WriteReadIoctl& client_ioctl = queue.front();
write_read = {
.write_size = client_ioctl.size,
.write_consumed = 0,
.write_buffer = (binder_uintptr_t)(&(client_ioctl.write_buffer)),
.read_size = client_ioctl.read_buffer ? sizeof(read_buffer) : 0,
.read_consumed = 0,
.read_buffer = (binder_uintptr_t)read_buffer.data(),
};
fd_for_ioctl = client_ioctl.fd;
}
ASSERT_THAT(ioctl(fd_for_ioctl, BINDER_WRITE_READ, &write_read), SyscallSucceeds());
queue.pop();
binder_uintptr_t cursor = (binder_uintptr_t)read_buffer.data();
binder_uintptr_t limit = cursor + write_read.read_consumed;
while (cursor < limit) {
binder_driver_return_protocol returned = *(binder_driver_return_protocol*)(cursor);
cursor += sizeof(binder_driver_return_protocol);
switch (returned) {
case BR_NOOP:
break;
case BR_TRANSACTION_COMPLETE:
transactions_remaining--;
break;
case BR_INCREFS:
case BR_ACQUIRE:
case BR_RELEASE:
case BR_DECREFS:
cursor += sizeof(struct binder_ptr_cookie);
break;
case BR_TRANSACTION:
cursor += sizeof(struct binder_transaction_data);
break;
case BR_REPLY: {
struct binder_transaction_data& received_transaction_data =
*(struct binder_transaction_data*)cursor;
if (received_transaction_data.code == kGetService) {
ASSERT_EQ(received_transaction_data.offsets_size, sizeof(binder_size_t));
ASSERT_EQ(received_transaction_data.data_size, sizeof(struct flat_binder_object));
binder_size_t offset = *(binder_size_t*)(received_transaction_data.data.ptr.offsets);
const struct flat_binder_object& obj =
*(const struct flat_binder_object*)(received_transaction_data.data.ptr.buffer +
offset);
ASSERT_EQ(obj.hdr.type, BINDER_TYPE_HANDLE);
ASSERT_EQ(service_provider_handle, std::nullopt);
AcquireWriteBuffer acquire_write_buffer = {
.command = BC_ACQUIRE,
.handle = obj.handle,
};
queue.push({
.size = sizeof(acquire_write_buffer),
.write_buffer =
{
.acquire = acquire_write_buffer,
},
.read_buffer = false,
.fd = fd_and_mapping.fd_.get(),
});
service_provider_handle = obj.handle;
TransactionWriteBuffer send_fd_write_buffer = {
.command = BC_TRANSACTION,
.data =
{
.target =
{
.handle = obj.handle,
},
.cookie = 0,
.code = kServiceSendFd,
.flags = TF_ACCEPT_FDS,
.data_size = 0,
.offsets_size = 0,
.data =
{
.ptr =
{
.buffer = (binder_uintptr_t) nullptr,
.offsets = (binder_uintptr_t) nullptr,
},
},
},
};
queue.push({
.size = sizeof(send_fd_write_buffer),
.write_buffer =
{
.transaction = send_fd_write_buffer,
},
.read_buffer = true,
.fd = fd_and_mapping.fd_.get(),
});
} else if (received_transaction_data.code == kServiceSendFd) {
ASSERT_EQ(received_transaction_data.offsets_size, sizeof(binder_size_t));
ASSERT_EQ(received_transaction_data.data_size, sizeof(struct binder_fd_object));
binder_size_t received_offset =
*(binder_size_t*)(binder_uintptr_t)received_transaction_data.data.ptr.offsets;
const struct binder_fd_object& binder_fd_obj =
*(struct binder_fd_object*)(((binder_uintptr_t)
received_transaction_data.data.ptr.buffer) +
received_offset);
ASSERT_EQ(binder_fd_obj.hdr.type, BINDER_TYPE_FD);
struct stat stat_buffer;
ASSERT_THAT(fstat(binder_fd_obj.fd, &stat_buffer), SyscallSucceeds());
const TransactionWriteBuffer impersonate = {
.command = BC_TRANSACTION,
.data =
{
.target =
{
.handle = received_transaction_data.target.handle,
},
.cookie = received_transaction_data.cookie,
.code = received_transaction_data.code,
.flags = TF_ONE_WAY,
.data_size = 0,
.offsets_size = 0,
.data =
{
.ptr =
{
.buffer = (binder_uintptr_t) nullptr,
.offsets = (binder_uintptr_t) nullptr,
},
},
},
};
queue.push({
.size = sizeof(impersonate),
.write_buffer =
{
.transaction = impersonate,
},
.read_buffer = true,
.fd = static_cast<int>(binder_fd_obj.fd),
});
service_provider_binder_fd = binder_fd_obj.fd;
}
cursor += sizeof(received_transaction_data);
break;
}
case BR_DEAD_BINDER:
FAIL() << "Unimplemented BR_DEAD_BINDER case - maybe we will need this in a future test?";
break;
case BR_FAILED_REPLY:
FAIL()
<< "Unimplemented BR_FAILED_REPLY case - maybe we will need this in a future test?";
break;
case BR_DEAD_REPLY:
FAIL() << "Unimplemented BR_DEAD_REPLY case - maybe we will need this in a future test?";
break;
case BR_ERROR:
cursor += sizeof(uint32_t);
break;
default:
FAIL() << "Unexpected binder_driver_return_protocol value " << returned << "!";
break;
}
}
}
completed.poke();
}
} // namespace
fit::deferred_action<fit::closure> ClientProcess(
std::string_view binder_dir,
fit::function<void(test_helper::ForkHelper&, fit::closure)> spawn_client,
test_helper::Poker completed) {
std::unique_ptr<test_helper::ForkHelper> fork_helper =
std::make_unique<test_helper::ForkHelper>();
fork_helper->OnlyWaitForForkedChildren();
spawn_client(*fork_helper, [binder_dir, completed = std::move(completed)]() mutable {
ClientBehavior(binder_dir, std::move(completed));
});
return fit::defer(fit::closure(
[fork_helper = std::move(fork_helper)] { ASSERT_TRUE(fork_helper->WaitForChildren()); }));
}
} // namespace starnix_binder