mirror of
https://github.com/Swordpdf/ProsperoEden.git
synced 2026-10-21 13:12:17 -05:00
* Validate Eden room packets and add offline interoperability checks * Bound proxy decompression and receive queues * Add asynchronous room controller and cancellable connections * Wire room settings and lifecycle into the launcher * Bound outgoing room queues and test stalled peers * Own room receive callbacks per proxy socket * Support proxy polling and release sockets between games * Retain guest sockets across descriptor changes * Record offline qualification and freeze fresh candidates * Record room server and read-only console preflight evidence * Show room members and reported games in a scrollable list * Document Tailscale capabilities and verify room rejection recovery * Record remaining hardware validation prerequisites * Record successful room transport over Tailscale * Record verified console deployment for operator testing * Use a supported keyboard type for masked password entry * Remember room passwords and report connection failures * Report ENet handshake errors and packet counts * Parse numeric room addresses before invoking the resolver * Record operator-confirmed PS5 room connection * Use the room virtual interface for guest wireless networking * Compose room interfaces with the native getifaddrs adaptation * Exercise LDN discovery and peer connection through a real room * Record PS5 Mario Kart wireless lobby validation * Record live LDN traffic, recovery and Tailscale component validation * Prepare official desktop reference and correct room interface guidance * Record public room connection and native DNS failure * Show multiplayer connection status on home and settings * Make external Tailscale qualification optional * Invalidate network compiler cache for generated room headers * Use the pinned Boost version in multiplayer host checks * Show room server ping in the game overlay * Record verified ping overlay deployment to PS5 * Respect detailed logging settings for multiplayer diagnostics * Make the room socket non-blocking on the console so the member loop can send the join request * Add public room browser beside private multiplayer connection * Keep multiplayer destination choice selectable while connected * Record local public browser validation and deferred console test * Explain multiplayer connection failures in a persistent UI panel * Define idle-only multiplayer validation on console .40 * Record console .40 public directory and private room validation * Update public room notes for PR publication * Keep the saved private room on public joins, save the private form as typed, and harden the public room list * Translate the multiplayer and public room texts in all 29 languages
145 lines
5.7 KiB
C++
145 lines
5.7 KiB
C++
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
#include <array>
|
|
#include <cassert>
|
|
#include <cstdint>
|
|
#include <limits>
|
|
#include <string>
|
|
#include <vector>
|
|
#include "network/packet.h"
|
|
#include "multiplayer_validation.h"
|
|
#include "multiplayer_ldn.h"
|
|
#include "multiplayer_proxy.h"
|
|
#include "multiplayer_send.h"
|
|
|
|
int main() {
|
|
Eden::Multiplayer::RoomSendBudget budget;
|
|
for (unsigned i = 0; i < 1024; ++i) assert(budget.Add(0));
|
|
assert(!budget.Add(0));
|
|
budget.Release(0);
|
|
assert(budget.Add(0));
|
|
budget.Reset();
|
|
const auto maximum = Eden::Multiplayer::MaxRoomPacketBytes;
|
|
assert(!budget.Add(maximum + 1));
|
|
for (unsigned i = 0; i < 4; ++i) assert(budget.Add(maximum));
|
|
assert(!budget.Add(1));
|
|
budget.Release(maximum);
|
|
assert(budget.Add(maximum));
|
|
for (unsigned i = 0; i < 4; ++i) budget.Release(maximum);
|
|
assert(budget.Add(maximum));
|
|
using Network::Packet;
|
|
const std::vector<u8> bytes{0, 1, 127, 255};
|
|
const std::vector<std::string> strings{"", "PlayerOne", "PlayerTwo"};
|
|
Packet valid;
|
|
valid.Write(bytes).Write(strings);
|
|
std::vector<u8> decoded;
|
|
std::vector<std::string> names;
|
|
valid.Read(decoded).Read(names);
|
|
assert(valid && valid.EndOfPacket() && decoded == bytes && names == strings);
|
|
const std::array<u8, 8> expected{0, 0, 0, 4, 0, 1, 127, 255};
|
|
assert(std::memcmp(valid.GetData(), expected.data(), expected.size()) == 0);
|
|
|
|
// Exercise every truncation, including a partial count and partial string.
|
|
for (std::size_t size = 0; size < valid.GetDataSize(); ++size) {
|
|
Packet truncated;
|
|
truncated.Append(valid.GetData(), size);
|
|
truncated.Read(decoded).Read(names);
|
|
assert(!truncated);
|
|
}
|
|
Packet hostile_count;
|
|
hostile_count.Write(std::numeric_limits<u32>::max());
|
|
hostile_count.Read(decoded);
|
|
assert(!hostile_count && decoded.empty());
|
|
|
|
Packet partial_strings;
|
|
partial_strings.Write(u32{2}).Write(std::string{"one"});
|
|
partial_strings.Read(names);
|
|
assert(!partial_strings && names.empty());
|
|
|
|
// Overflowing requests and ignored bytes must invalidate the packet without copying.
|
|
Packet overflow;
|
|
overflow.Write(u8{42});
|
|
overflow.IgnoreBytes(1);
|
|
u8 untouched = 17;
|
|
overflow.Read(&untouched, std::numeric_limits<std::size_t>::max());
|
|
assert(!overflow && untouched == 17);
|
|
Packet ignored;
|
|
ignored.IgnoreBytes(1);
|
|
assert(!ignored);
|
|
ignored.Clear();
|
|
ignored.Read(&untouched, 0);
|
|
assert(ignored && ignored.EndOfPacket() && untouched == 17);
|
|
|
|
Packet short_array;
|
|
short_array.Write(u8{99});
|
|
std::array<u8, 4> address{};
|
|
short_array.Read(address);
|
|
assert(!short_array && address[0] == 99 && address[1] == 0);
|
|
|
|
const auto valid_room = [](const Packet& packet) {
|
|
return Eden::Multiplayer::ValidRoomPacket(
|
|
{static_cast<const u8*>(packet.GetData()), packet.GetDataSize()});
|
|
};
|
|
Packet joined;
|
|
joined.Write(u8{Network::IdJoinSuccess}).Write(Network::IPv4Address{192, 168, 0, 1});
|
|
assert(valid_room(joined));
|
|
for (std::size_t size = 0; size < joined.GetDataSize(); ++size) {
|
|
Packet truncated;
|
|
truncated.Append(joined.GetData(), size);
|
|
assert(!valid_room(truncated));
|
|
}
|
|
joined.Write(u8{0});
|
|
assert(!valid_room(joined));
|
|
Packet info;
|
|
info.Write(u8{Network::IdRoomInformation}).Write(std::string{"Test room"})
|
|
.Write(std::string{}).Write(u32{2}).Write(u16{24872})
|
|
.Write(std::string{}).Write(std::string{}).Write(u32{0});
|
|
assert(valid_room(info));
|
|
Packet bad_info;
|
|
bad_info.Append(info.GetData(), info.GetDataSize() - sizeof(u32));
|
|
bad_info.Write(std::numeric_limits<u32>::max());
|
|
assert(!valid_room(bad_info));
|
|
|
|
Network::LDNPacket ldn{};
|
|
ldn.type = Network::LDNPacketType::Scan;
|
|
assert(Eden::Multiplayer::ValidLdnPacket(ldn));
|
|
ldn.type = Network::LDNPacketType::ScanResp;
|
|
assert(!Eden::Multiplayer::ValidLdnPacket(ldn));
|
|
Service::LDN::NetworkInfo network{};
|
|
network.ldn.node_count_max = 8;
|
|
network.ldn.node_count = 1;
|
|
const auto set_network = [&] {
|
|
ldn.data.resize(sizeof(network));
|
|
std::memcpy(ldn.data.data(), &network, sizeof(network));
|
|
};
|
|
set_network();
|
|
assert(Eden::Multiplayer::ValidLdnPacket(ldn));
|
|
network.ldn.node_count = 9;
|
|
set_network();
|
|
assert(!Eden::Multiplayer::ValidLdnPacket(ldn));
|
|
network.ldn.node_count = 1;
|
|
network.ldn.advertise_data_size = Service::LDN::AdvertiseDataSizeMax + 1;
|
|
set_network();
|
|
assert(!Eden::Multiplayer::ValidLdnPacket(ldn));
|
|
ldn.type = Network::LDNPacketType::Connect;
|
|
ldn.data.resize(sizeof(Service::LDN::NodeInfo));
|
|
assert(Eden::Multiplayer::ValidLdnPacket(ldn));
|
|
ldn.data.pop_back();
|
|
assert(!Eden::Multiplayer::ValidLdnPacket(ldn));
|
|
const auto compress = [](const std::vector<u8>& raw) {
|
|
std::vector<u8> compressed(ZSTD_compressBound(raw.size()));
|
|
const auto size = ZSTD_compress(compressed.data(), compressed.size(), raw.data(), raw.size(), 1);
|
|
assert(!ZSTD_isError(size));
|
|
compressed.resize(size);
|
|
return compressed;
|
|
};
|
|
auto compressed = compress(bytes);
|
|
assert(Eden::Multiplayer::DecodeProxyPayload(compressed, decoded) && decoded == bytes);
|
|
for (std::size_t size = 0; size < compressed.size(); ++size)
|
|
assert(!Eden::Multiplayer::DecodeProxyPayload({compressed.data(), size}, decoded));
|
|
compressed = compress({});
|
|
assert(Eden::Multiplayer::DecodeProxyPayload(compressed, decoded) && decoded.empty());
|
|
compressed = compress(std::vector<u8>(Eden::Multiplayer::MaxProxyPayloadBytes + 1, 0));
|
|
assert(!Eden::Multiplayer::DecodeProxyPayload(compressed, decoded) && decoded.empty());
|
|
assert(!Eden::Multiplayer::DecodeProxyPayload(bytes, decoded));
|
|
}
|