Harden Protocol V2 test packet framing

This commit is contained in:
Nomads_Reach
2026-08-15 22:51:50 -04:00
parent d310d77428
commit cbaf322759
+36 -16
View File
@@ -9,12 +9,19 @@ import pytest
from server_core import PROTOCOL_VERSION, FalloutTogetherServer, states_share_interest
_RECV_BUFFERS: dict[socket.socket, bytes] = {}
def recv_packet(sock: socket.socket, timeout: float = 2.0) -> dict:
sock.settimeout(timeout)
data = b""
data = _RECV_BUFFERS.get(sock, b"")
while b"\n" not in data:
data += sock.recv(4096)
line, _ = data.split(b"\n", 1)
chunk = sock.recv(4096)
if not chunk:
raise ConnectionError("socket closed before a complete packet was received")
data += chunk
line, remainder = data.split(b"\n", 1)
_RECV_BUFFERS[sock] = remainder
return json.loads(line.decode("utf-8"))
@@ -22,12 +29,20 @@ def recv_until(sock: socket.socket, packet_type: str, timeout: float = 2.0) -> d
deadline = time.monotonic() + timeout
while time.monotonic() < deadline:
remaining = max(0.01, deadline - time.monotonic())
packet = recv_packet(sock, timeout=remaining)
try:
packet = recv_packet(sock, timeout=remaining)
except socket.timeout:
break
if packet.get("type") == packet_type:
return packet
raise AssertionError(f"did not receive packet type {packet_type}")
def assert_no_packet_type(sock: socket.socket, packet_type: str, timeout: float = 0.2) -> None:
with pytest.raises(AssertionError):
recv_until(sock, packet_type, timeout=timeout)
def send_packet(sock: socket.socket, packet: dict) -> None:
sock.sendall(json.dumps(packet, separators=(",", ":")).encode() + b"\n")
@@ -82,6 +97,18 @@ def test_interest_exact_cell_and_exterior_radius():
assert not states_share_interest(a, other_world)
def test_recv_packet_preserves_coalesced_lines():
reader, writer = socket.socketpair()
try:
writer.sendall(b'{"type":"first"}\n{"type":"second"}\n')
assert recv_packet(reader)["type"] == "first"
assert recv_packet(reader)["type"] == "second"
finally:
_RECV_BUFFERS.pop(reader, None)
reader.close()
writer.close()
def test_idle_transport_does_not_take_world_authority():
server = start_server()
idle, _ = connect(server)
@@ -103,8 +130,7 @@ def test_max_players_applies_to_activated_sessions_not_probes():
hello(first)
second, _ = connect(server)
send_packet(second, {"type": "hello", "protocolVersion": PROTOCOL_VERSION})
ended = recv_packet(second)
assert ended["type"] == "sessionEnded"
ended = recv_until(second, "sessionEnded")
assert ended["code"] == "server_full"
first.close()
second.close()
@@ -115,8 +141,7 @@ def test_protocol_mismatch_is_rejected():
server = start_server()
sock, _ = connect(server)
send_packet(sock, {"type": "hello", "protocolVersion": PROTOCOL_VERSION + 1})
ended = recv_packet(sock)
assert ended["type"] == "sessionEnded"
ended = recv_until(sock, "sessionEnded")
assert ended["code"] == "protocol_mismatch"
sock.close()
server.stop()
@@ -147,9 +172,7 @@ def test_transform_interest_filters_distant_peer():
send_packet(b, transform("00000020", 25000, 25000))
time.sleep(0.05)
send_packet(a, transform("00000010", 0, 0))
b.settimeout(0.2)
with pytest.raises(socket.timeout):
b.recv(4096)
assert_no_packet_type(b, "transform")
assert server.get_stats()["transformPacketsInterestFiltered"] >= 1
a.close()
b.close()
@@ -174,9 +197,7 @@ def test_rejected_normal_teleport_is_corrected_and_never_relayed():
assert correction["x"] == 0.0
assert correction["cellId"] == "00000010"
b.settimeout(0.2)
with pytest.raises(socket.timeout):
b.recv(4096)
assert_no_packet_type(b, "transform")
client = server._find_client_by_player_id(a_welcome["playerId"])
assert client is not None
@@ -243,8 +264,7 @@ def test_oversized_unterminated_packet_closes_session():
server = start_server()
sock, _ = connect(server)
sock.sendall(b"x" * (64 * 1024 + 1))
ended = recv_packet(sock)
assert ended["type"] == "sessionEnded"
ended = recv_until(sock, "sessionEnded")
assert ended["code"] == "packet_too_large"
sock.close()
server.stop()