mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 18:13:23 +09:00
470 lines
20 KiB
C++
470 lines
20 KiB
C++
// MobileGL - MobileGL/MG_Test/Wire/SocketTransportTest.cpp
|
|
// Copyright (c) 2025-2026 MobileGL-Dev
|
|
// Licensed under the GNU Lesser General Public License v3.0:
|
|
// https://www.gnu.org/licenses/gpl-3.0.txt
|
|
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
|
// SPDX-License-Identifier: LGPL-3.0-only
|
|
// End of Source File Header
|
|
|
|
// The SAME nine ITransport-contract cases InProcessTransportTest runs, driven
|
|
// over a real socketpair() with real threads. Package `so`.
|
|
//
|
|
// THIS FILE IS THE PACKAGE'S WHOLE POINT. CONTRACT-P6.md §8 and the spawn plan
|
|
// both require SocketTransport green over a plain socketpair BEFORE a second
|
|
// process exists, for the reason SessionRings.h gives about the attach half:
|
|
// a transport first exercised on the day the child appears is a transport
|
|
// whose bugs all arrive at once, on the hardest day to debug them.
|
|
//
|
|
// WHY THE BODIES ARE COPIED RATHER THAN SHARED. The two suites assert the same
|
|
// contract but not through the same handle: InProcessTransport hands out
|
|
// condvar doorbells its tests reach into, SocketTransport does not have them
|
|
// (the spawn design pairs it with SocketDoorbell, which FdPassingTest already
|
|
// covers). Folding both into one parameterised fixture would rename the
|
|
// sixteen existing cases, and G14 says test names only ever grow. Nine new
|
|
// names, zero removed.
|
|
//
|
|
// The seven Doorbell* cases in InProcessTransportTest are deliberately NOT
|
|
// here: they exercise CondVarDoorbell through a transport that happens to own
|
|
// one, not ITransport. SocketDoorbell's equivalents live in FdPassingTest.
|
|
//
|
|
// WHAT A SOCKET MAKES REAL THAT A MESSAGE QUEUE DID NOT. InProcessTransport
|
|
// hands the peer whole messages by construction; a stream hands you arbitrary
|
|
// fragments, so every one of these cases now also exercises Framing.h's
|
|
// reassembler - which is why `PreservesOrder` sends 64 messages without
|
|
// reading, forcing coalescing in the socket buffer, and why
|
|
// `BufferTooSmallKeepsTheMessage` matters more here: the message it must keep
|
|
// is one this side already reassembled out of bytes nobody else can re-deliver.
|
|
|
|
#include <MG_Remote/Transport/Doorbell.h> // kWaitForever
|
|
#include <MG_Remote/Transport/FdPassing.h>
|
|
#include <MG_Remote/Transport/SocketTransport.h>
|
|
|
|
#include <gtest/gtest.h>
|
|
|
|
#include <atomic>
|
|
#include <chrono>
|
|
#include <cstring>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <thread>
|
|
#include <vector>
|
|
|
|
#if !defined(_WIN32)
|
|
#include <sys/socket.h>
|
|
#include <netinet/in.h>
|
|
#include <netinet/tcp.h>
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
using namespace MobileGL::MG_Remote::Transport;
|
|
|
|
#if !defined(_WIN32)
|
|
|
|
TEST(SocketTransportTest, TcpCarriesControlAndIndependentDataWithKeepalive) {
|
|
// Find an available loopback port; Listen deliberately refuses ambiguous port 0 URIs.
|
|
int reservation = ::socket(AF_INET, SOCK_STREAM, 0);
|
|
ASSERT_GE(reservation, 0);
|
|
sockaddr_in address{};
|
|
address.sin_family = AF_INET;
|
|
address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
|
ASSERT_EQ(::bind(reservation, reinterpret_cast<sockaddr*>(&address), sizeof(address)), 0);
|
|
socklen_t addressSize = sizeof(address);
|
|
ASSERT_EQ(::getsockname(reservation, reinterpret_cast<sockaddr*>(&address), &addressSize), 0);
|
|
const auto endpoint = std::string("tcp://127.0.0.1:") + std::to_string(ntohs(address.sin_port));
|
|
::close(reservation);
|
|
int listener = -1;
|
|
ASSERT_EQ(SocketTransport::Listen(endpoint, &listener), MOBILEGL_OK);
|
|
std::unique_ptr<SocketTransport> client, server;
|
|
ASSERT_EQ(SocketTransport::ConnectTo(endpoint, 1000, client), MOBILEGL_OK);
|
|
ASSERT_EQ(SocketTransport::AcceptPair(listener, 1000, server), MOBILEGL_OK);
|
|
::close(listener);
|
|
ASSERT_TRUE(client->IsTcp());
|
|
ASSERT_TRUE(server->IsTcp());
|
|
for (auto* transport : {client.get(), server.get()}) {
|
|
int option = 0;
|
|
socklen_t size = sizeof(option);
|
|
ASSERT_EQ(::getsockopt(transport->StreamFd(), IPPROTO_TCP, TCP_NODELAY, &option, &size), 0);
|
|
EXPECT_EQ(option, 1);
|
|
ASSERT_EQ(::getsockopt(transport->StreamFd(), SOL_SOCKET, SO_KEEPALIVE, &option, &size), 0);
|
|
EXPECT_EQ(option, 1);
|
|
EXPECT_EQ(transport->ShareFd(0, {nullptr, 0}), MOBILEGL_ERR_UNSUPPORTED);
|
|
}
|
|
const int clientData = client->TakeDataFd(), serverData = server->TakeDataFd();
|
|
ASSERT_GE(clientData, 0);
|
|
ASSERT_GE(serverData, 0);
|
|
EXPECT_EQ(client->TakeDataFd(), -1);
|
|
const char message[] = "independent";
|
|
ASSERT_EQ(::send(clientData, message, sizeof(message), MSG_NOSIGNAL), sizeof(message));
|
|
char received[sizeof(message)]{};
|
|
ASSERT_EQ(::recv(serverData, received, sizeof(received), MSG_WAITALL), sizeof(received));
|
|
EXPECT_EQ(std::memcmp(message, received, sizeof(message)), 0);
|
|
ASSERT_EQ(server->SendFrame({message, sizeof(message)}), MOBILEGL_OK);
|
|
std::uint64_t size = 0;
|
|
ASSERT_EQ(client->ReceiveFrame({received, sizeof(received)}, &size, 1000), MOBILEGL_OK);
|
|
EXPECT_EQ(size, sizeof(message));
|
|
::close(clientData);
|
|
::close(serverData);
|
|
}
|
|
|
|
TEST(SocketTransportTest, TcpRejectsMalformedNamesAndUnauthenticatedWildcard) {
|
|
int listener = -1;
|
|
for (const char* endpoint : {"tcp://", "tcp://localhost", "tcp://localhost:0", "tcp://localhost:65536",
|
|
"tcp://localhost:-1", "tcp://[::1]broken", "tcp://::1:40000"}) {
|
|
EXPECT_EQ(SocketTransport::Listen(endpoint, &listener), MOBILEGL_ERR_INVALID_ARGUMENT) << endpoint;
|
|
EXPECT_EQ(listener, -1);
|
|
}
|
|
const char* prior = std::getenv("MOBILEGL_IPC_TOKEN");
|
|
const std::string saved = prior ? prior : "";
|
|
const bool wasSet = prior != nullptr;
|
|
::unsetenv("MOBILEGL_IPC_TOKEN");
|
|
EXPECT_EQ(SocketTransport::Listen("tcp://0.0.0.0:40613", &listener), MOBILEGL_ERR_PROTOCOL_MISMATCH);
|
|
EXPECT_EQ(listener, -1);
|
|
if (wasSet) ::setenv("MOBILEGL_IPC_TOKEN", saved.c_str(), 1);
|
|
}
|
|
|
|
namespace {
|
|
|
|
MobileGLByteSpan Span(const std::string& text) {
|
|
return MobileGLByteSpan{text.data(), text.size()};
|
|
}
|
|
|
|
std::string Receive(ITransport& transport, std::uint32_t timeoutMs = 1000) {
|
|
std::vector<std::uint8_t> buffer(4096);
|
|
std::uint64_t size = 0;
|
|
MobileGLMutableByteSpan span{buffer.data(), buffer.size()};
|
|
const MobileGLResult result = transport.ReceiveFrame(span, &size, timeoutMs);
|
|
if (result != MOBILEGL_OK) {
|
|
return std::string("<result=") + std::to_string(static_cast<int>(result)) + ">";
|
|
}
|
|
return std::string(reinterpret_cast<const char*>(buffer.data()),
|
|
static_cast<std::size_t>(size));
|
|
}
|
|
|
|
struct Pair {
|
|
std::unique_ptr<SocketTransport> client;
|
|
std::unique_ptr<SocketTransport> server;
|
|
};
|
|
|
|
Pair MakePair() {
|
|
Pair pair;
|
|
const MobileGLResult result = SocketTransport::CreatePair(pair.client, pair.server);
|
|
EXPECT_EQ(result, MOBILEGL_OK);
|
|
return pair;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST(SocketTransportTest, CarriesFramesInBothDirections) {
|
|
Pair pair = MakePair();
|
|
ASSERT_TRUE(pair.client && pair.server);
|
|
EXPECT_EQ(pair.client->Role(), TransportRole::Client);
|
|
EXPECT_EQ(pair.server->Role(), TransportRole::Server);
|
|
|
|
const std::string hello = "Hello{abiMajor=1}";
|
|
const std::string welcome = "Welcome{serverPid=42}";
|
|
ASSERT_EQ(pair.client->SendFrame(Span(hello)), MOBILEGL_OK);
|
|
// PeekFrameSize pumps, so it is allowed to need the bytes to have arrived;
|
|
// on a socketpair a completed send is already in the peer's buffer.
|
|
EXPECT_EQ(pair.server->PeekFrameSize(), hello.size());
|
|
// A message goes to the PEER, never back to the sender.
|
|
EXPECT_EQ(pair.client->PeekFrameSize(), 0u);
|
|
EXPECT_EQ(Receive(*pair.server), hello);
|
|
|
|
ASSERT_EQ(pair.server->SendFrame(Span(welcome)), MOBILEGL_OK);
|
|
EXPECT_EQ(Receive(*pair.client), welcome);
|
|
}
|
|
|
|
TEST(SocketTransportTest, RequestHalfCloseStillDeliversTheFinalReply) {
|
|
std::unique_ptr<SocketTransport> client, server;
|
|
ASSERT_EQ(SocketTransport::CreatePair(client, server), MOBILEGL_OK);
|
|
ASSERT_EQ(client->SendFrame(Span("last request")), MOBILEGL_OK);
|
|
EXPECT_EQ(Receive(*server), "last request");
|
|
ASSERT_EQ(client->ShutdownSend(), MOBILEGL_OK);
|
|
std::uint64_t bytes = 0;
|
|
char buffer[32]{};
|
|
EXPECT_EQ(server->ReceiveFrame({buffer, sizeof buffer}, &bytes, 1000), MOBILEGL_ERR_TRANSPORT_CLOSED);
|
|
ASSERT_EQ(server->SendFrame(Span("cleanup complete")), MOBILEGL_OK);
|
|
EXPECT_EQ(Receive(*client), "cleanup complete");
|
|
server->Shutdown();
|
|
EXPECT_EQ(client->ReceiveFrame({buffer, sizeof buffer}, &bytes, 1000), MOBILEGL_ERR_TRANSPORT_CLOSED);
|
|
}
|
|
|
|
TEST(SocketTransportTest, PreservesOrder) {
|
|
Pair pair = MakePair();
|
|
|
|
// 64 unread messages coalesce in the socket buffer, so the reassembler has
|
|
// to find every boundary itself. That is the case InProcessTransport could
|
|
// not have: its queue kept the boundaries for it.
|
|
for (int i = 0; i < 64; ++i) {
|
|
const std::string message = "msg-" + std::to_string(i);
|
|
ASSERT_EQ(pair.client->SendFrame(Span(message)), MOBILEGL_OK);
|
|
}
|
|
for (int i = 0; i < 64; ++i) {
|
|
EXPECT_EQ(Receive(*pair.server), "msg-" + std::to_string(i));
|
|
}
|
|
}
|
|
|
|
TEST(SocketTransportTest, BufferTooSmallKeepsTheMessage) {
|
|
Pair pair = MakePair();
|
|
|
|
const std::string message(300, 'x');
|
|
ASSERT_EQ(pair.client->SendFrame(Span(message)), MOBILEGL_OK);
|
|
|
|
std::vector<std::uint8_t> small(16);
|
|
std::uint64_t required = 0;
|
|
MobileGLMutableByteSpan smallSpan{small.data(), small.size()};
|
|
// Zero timeout: the bytes may still be in flight, so give the reassembler a
|
|
// real chance first. PeekFrameSize is the documented way to do that.
|
|
while (pair.server->PeekFrameSize() == 0) {
|
|
std::this_thread::yield();
|
|
}
|
|
EXPECT_EQ(pair.server->ReceiveFrame(smallSpan, &required, 0), MOBILEGL_ERR_BUFFER_TOO_SMALL);
|
|
EXPECT_EQ(required, message.size());
|
|
// Still there. On a stream this is the load-bearing half: the bytes have
|
|
// already been consumed from the socket, so if the reassembler dropped the
|
|
// message nothing could re-deliver it and the stream would be wedged.
|
|
EXPECT_EQ(pair.server->PeekFrameSize(), message.size());
|
|
EXPECT_EQ(Receive(*pair.server), message);
|
|
}
|
|
|
|
TEST(SocketTransportTest, PollAndTimeoutDoNotBlockForever) {
|
|
Pair pair = MakePair();
|
|
|
|
std::vector<std::uint8_t> buffer(64);
|
|
std::uint64_t size = 0;
|
|
MobileGLMutableByteSpan span{buffer.data(), buffer.size()};
|
|
EXPECT_EQ(pair.server->ReceiveFrame(span, &size, 0), MOBILEGL_ERR_TIMEOUT);
|
|
|
|
const auto start = std::chrono::steady_clock::now();
|
|
EXPECT_EQ(pair.server->ReceiveFrame(span, &size, 30), MOBILEGL_ERR_TIMEOUT);
|
|
EXPECT_GE(std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
std::chrono::steady_clock::now() - start)
|
|
.count(),
|
|
20);
|
|
}
|
|
|
|
TEST(SocketTransportTest, ShutdownDrainsBeforeItCloses) {
|
|
Pair pair = MakePair();
|
|
|
|
const std::string last = "Fatal{code=DeviceLost}";
|
|
ASSERT_EQ(pair.client->SendFrame(Span(last)), MOBILEGL_OK);
|
|
pair.client->Shutdown();
|
|
|
|
// A peer that shuts down right after sending must not lose its last
|
|
// message - that is usually the one that says why it is going away. On a
|
|
// socket the bytes are already in the peer's receive buffer when close()
|
|
// runs, and the peer must reassemble them before it reports EOF.
|
|
EXPECT_EQ(Receive(*pair.server), last);
|
|
|
|
std::vector<std::uint8_t> buffer(64);
|
|
std::uint64_t size = 0;
|
|
MobileGLMutableByteSpan span{buffer.data(), buffer.size()};
|
|
EXPECT_EQ(pair.server->ReceiveFrame(span, &size, 100), MOBILEGL_ERR_TRANSPORT_CLOSED);
|
|
EXPECT_EQ(pair.server->SendFrame(Span(last)), MOBILEGL_ERR_TRANSPORT_CLOSED);
|
|
}
|
|
|
|
TEST(SocketTransportTest, BlockedReceiverWakesOnSendAndOnShutdown) {
|
|
Pair pair = MakePair();
|
|
|
|
std::atomic<bool> got{false};
|
|
std::thread reader([&] { got.store(Receive(*pair.server, kWaitForever) == "wake"); });
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
|
ASSERT_EQ(pair.client->SendFrame(Span(std::string("wake"))), MOBILEGL_OK);
|
|
reader.join();
|
|
EXPECT_TRUE(got.load());
|
|
|
|
std::thread closer([&] {
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
|
pair.client->Shutdown();
|
|
});
|
|
std::vector<std::uint8_t> buffer(64);
|
|
std::uint64_t size = 0;
|
|
MobileGLMutableByteSpan span{buffer.data(), buffer.size()};
|
|
// The wake is a real POLLHUP here, not a condvar notify: this is the
|
|
// mechanism Doorbell.h:390-398 chose a stream socket for, and the reason
|
|
// death is an event rather than a hang.
|
|
EXPECT_EQ(pair.server->ReceiveFrame(span, &size, kWaitForever), MOBILEGL_ERR_TRANSPORT_CLOSED);
|
|
closer.join();
|
|
}
|
|
|
|
TEST(SocketTransportTest, RefusesAPayloadOverTheFrameCap) {
|
|
Pair pair = MakePair();
|
|
|
|
// Not allocated: the cap is checked before the bytes are touched, and it is
|
|
// the SAME cap InProcessTransport enforces, so nothing passes CI on inproc
|
|
// and then fails after the switch to spawn.
|
|
const std::uint8_t dummy = 0;
|
|
MobileGLByteSpan huge{&dummy, 64ull * 1024 * 1024 + 1};
|
|
EXPECT_EQ(pair.client->SendFrame(huge), MOBILEGL_ERR_INVALID_ARGUMENT);
|
|
}
|
|
|
|
TEST(SocketTransportTest, HandsOverADescriptorAndItsSideband) {
|
|
Pair pair = MakePair();
|
|
|
|
int pipeFds[2] = {-1, -1};
|
|
ASSERT_EQ(::pipe(pipeFds), 0);
|
|
|
|
const std::string sideband = "SegmentRef{id=1,kind=Cmd}";
|
|
ASSERT_EQ(pair.client->ShareFd(pipeFds[0], Span(sideband)), MOBILEGL_OK);
|
|
|
|
// A short sideband buffer is refused before anything is consumed, so the
|
|
// descriptor is never dropped. This is the real SCM_RIGHTS path, not an
|
|
// in-process hand-off: the fd the peer gets is renumbered by the kernel.
|
|
std::vector<std::uint8_t> small(8);
|
|
int fd = -1;
|
|
std::uint64_t required = 0;
|
|
MobileGLMutableByteSpan smallSpan{small.data(), small.size()};
|
|
EXPECT_EQ(pair.server->ReceiveFd(&fd, smallSpan, &required, 0), MOBILEGL_ERR_BUFFER_TOO_SMALL);
|
|
EXPECT_EQ(required, FdPassing::kMaxSidebandBytes);
|
|
EXPECT_EQ(fd, -1);
|
|
|
|
std::vector<std::uint8_t> big(FdPassing::kMaxSidebandBytes);
|
|
std::uint64_t sidebandSize = 0;
|
|
MobileGLMutableByteSpan bigSpan{big.data(), big.size()};
|
|
ASSERT_EQ(pair.server->ReceiveFd(&fd, bigSpan, &sidebandSize, 100), MOBILEGL_OK);
|
|
ASSERT_GE(fd, 0);
|
|
EXPECT_EQ(std::string(reinterpret_cast<const char*>(big.data()),
|
|
static_cast<std::size_t>(sidebandSize)),
|
|
sideband);
|
|
|
|
// Same open file description, independent descriptor - which is exactly
|
|
// what the four segments will ride on in `sm`.
|
|
const char payload[] = "bytes";
|
|
ASSERT_EQ(::write(pipeFds[1], payload, sizeof(payload)), static_cast<ssize_t>(sizeof(payload)));
|
|
char readBack[sizeof(payload)] = {};
|
|
ASSERT_EQ(::read(fd, readBack, sizeof(readBack)), static_cast<ssize_t>(sizeof(payload)));
|
|
EXPECT_STREQ(readBack, payload);
|
|
|
|
::close(fd);
|
|
::close(pipeFds[0]);
|
|
::close(pipeFds[1]);
|
|
}
|
|
|
|
TEST(SocketTransportTest, AFrameWakeupIsNotEatenByAWaiterOnDescriptors) {
|
|
Pair pair = MakePair();
|
|
|
|
// Two readers on the SAME endpoint, blocked on two different predicates.
|
|
// The socket transport keeps them on two different descriptors - the stream
|
|
// and the aux - so neither can eat the other's wakeup by construction. That
|
|
// is a stronger property than the inproc transport's, and asserting it here
|
|
// is what would catch a future version that folded them onto one fd.
|
|
std::atomic<bool> fdWaiterStarted{false};
|
|
std::thread fdWaiter([&] {
|
|
std::vector<std::uint8_t> sideband(FdPassing::kMaxSidebandBytes);
|
|
MobileGLMutableByteSpan span{sideband.data(), sideband.size()};
|
|
int fd = -1;
|
|
std::uint64_t size = 0;
|
|
fdWaiterStarted.store(true);
|
|
// Never offered a descriptor: this one ends on its own deadline. NOT on
|
|
// Shutdown, the way the inproc suite's does - closing an fd out from
|
|
// under a thread already blocked in poll() does not reliably wake it,
|
|
// and a test that depended on that would be asserting a platform
|
|
// accident. The deadline is short because all this waiter has to do is
|
|
// BE BLOCKED while the frame below arrives.
|
|
const MobileGLResult result = pair.client->ReceiveFd(&fd, span, &size, 600);
|
|
EXPECT_NE(result, MOBILEGL_OK);
|
|
EXPECT_EQ(fd, -1);
|
|
});
|
|
while (!fdWaiterStarted.load()) {
|
|
std::this_thread::yield();
|
|
}
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
|
|
|
std::atomic<bool> frameWaiterStarted{false};
|
|
std::string got;
|
|
std::thread frameWaiter([&] {
|
|
frameWaiterStarted.store(true);
|
|
got = Receive(*pair.client, 4000);
|
|
});
|
|
while (!frameWaiterStarted.load()) {
|
|
std::this_thread::yield();
|
|
}
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
|
|
|
const std::string message = "wake the right waiter";
|
|
const auto start = std::chrono::steady_clock::now();
|
|
ASSERT_EQ(pair.server->SendFrame(Span(message)), MOBILEGL_OK);
|
|
frameWaiter.join();
|
|
const auto elapsedMs = std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
std::chrono::steady_clock::now() - start)
|
|
.count();
|
|
|
|
EXPECT_EQ(got, message);
|
|
// Not "eventually, when the receive timed out and re-checked".
|
|
EXPECT_LT(elapsedMs, 2000);
|
|
|
|
fdWaiter.join();
|
|
}
|
|
|
|
// ---- cases a socket has and a message queue does not ------------------------
|
|
|
|
TEST(SocketTransportTest, ReassemblesAMessageSplitAcrossReads) {
|
|
Pair pair = MakePair();
|
|
|
|
// 256 KiB is comfortably past a default socketpair buffer, so the sender
|
|
// blocks and the payload provably crosses in several reads. If PumpOnce
|
|
// returned a partial frame as a whole one, or if it dropped the tail, this
|
|
// is where it shows.
|
|
const std::string big(256 * 1024, 'q');
|
|
std::thread sender([&] { EXPECT_EQ(pair.client->SendFrame(Span(big)), MOBILEGL_OK); });
|
|
|
|
std::vector<std::uint8_t> buffer(big.size());
|
|
std::uint64_t size = 0;
|
|
MobileGLMutableByteSpan span{buffer.data(), buffer.size()};
|
|
ASSERT_EQ(pair.server->ReceiveFrame(span, &size, kWaitForever), MOBILEGL_OK);
|
|
sender.join();
|
|
|
|
EXPECT_EQ(size, big.size());
|
|
EXPECT_EQ(std::string(reinterpret_cast<const char*>(buffer.data()),
|
|
static_cast<std::size_t>(size)),
|
|
big);
|
|
}
|
|
|
|
TEST(SocketTransportTest, AGarbageStreamIsLatchedFailedAndNeverRecovers) {
|
|
Pair pair = MakePair();
|
|
|
|
// Write bytes that are not a frame directly onto the peer's stream fd,
|
|
// bypassing SendFrame. A corrupt control stream must latch, by name, and
|
|
// must not be retry-able past: the alternative is a reader that resyncs
|
|
// onto a byte boundary it invented, which is how one bad frame becomes a
|
|
// silently misparsed session.
|
|
const std::uint8_t garbage[16] = {0xDE, 0xAD, 0xBE, 0xEF, 0, 0, 0, 0,
|
|
0xFF, 0xFF, 0xFF, 0xFF, 1, 2, 3, 4};
|
|
ASSERT_EQ(::write(pair.client->StreamFd(), garbage, sizeof(garbage)),
|
|
static_cast<ssize_t>(sizeof(garbage)));
|
|
|
|
std::vector<std::uint8_t> buffer(64);
|
|
std::uint64_t size = 0;
|
|
MobileGLMutableByteSpan span{buffer.data(), buffer.size()};
|
|
EXPECT_EQ(pair.server->ReceiveFrame(span, &size, 200), MOBILEGL_ERR_PROTOCOL_MISMATCH);
|
|
// Latched: a second call says the same thing rather than resyncing.
|
|
EXPECT_EQ(pair.server->ReceiveFrame(span, &size, 200), MOBILEGL_ERR_PROTOCOL_MISMATCH);
|
|
EXPECT_EQ(pair.server->PeekFrameSize(), 0u);
|
|
}
|
|
|
|
TEST(SocketTransportTest, AdoptsAnAlreadyConnectedFdAndRefusesFdPassingWithoutAnAux) {
|
|
// What `sm` does on each side of the fork: adopt fd 3, and - for a link
|
|
// that has no descriptor channel - answer UNSUPPORTED to ShareFd rather
|
|
// than pretending. Rule G: a transport declares what it supports.
|
|
int fds[2] = {-1, -1};
|
|
ASSERT_EQ(::socketpair(AF_UNIX, SOCK_STREAM, 0, fds), 0);
|
|
|
|
SocketTransport a(fds[0], -1, TransportRole::Client);
|
|
SocketTransport b(fds[1], -1, TransportRole::Server);
|
|
|
|
const std::string message = "Hello{adopted}";
|
|
ASSERT_EQ(a.SendFrame(Span(message)), MOBILEGL_OK);
|
|
EXPECT_EQ(Receive(b), message);
|
|
|
|
const std::uint8_t dummy = 0;
|
|
EXPECT_EQ(a.ShareFd(1, MobileGLByteSpan{&dummy, 1}), MOBILEGL_ERR_UNSUPPORTED);
|
|
int fd = -1;
|
|
std::uint64_t size = 0;
|
|
std::vector<std::uint8_t> sideband(FdPassing::kMaxSidebandBytes);
|
|
MobileGLMutableByteSpan span{sideband.data(), sideband.size()};
|
|
EXPECT_EQ(b.ReceiveFd(&fd, span, &size, 0), MOBILEGL_ERR_UNSUPPORTED);
|
|
EXPECT_EQ(fd, -1);
|
|
}
|
|
|
|
#endif // !_WIN32
|