Harden Protocol V2 test packet framing
This commit is contained in:
@@ -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()
|
||||
server.stop()
|
||||
Reference in New Issue
Block a user