Sync from GitHub main #1
@@ -9,12 +9,19 @@ import pytest
|
|||||||
from server_core import PROTOCOL_VERSION, FalloutTogetherServer, states_share_interest
|
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:
|
def recv_packet(sock: socket.socket, timeout: float = 2.0) -> dict:
|
||||||
sock.settimeout(timeout)
|
sock.settimeout(timeout)
|
||||||
data = b""
|
data = _RECV_BUFFERS.get(sock, b"")
|
||||||
while b"\n" not in data:
|
while b"\n" not in data:
|
||||||
data += sock.recv(4096)
|
chunk = sock.recv(4096)
|
||||||
line, _ = data.split(b"\n", 1)
|
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"))
|
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
|
deadline = time.monotonic() + timeout
|
||||||
while time.monotonic() < deadline:
|
while time.monotonic() < deadline:
|
||||||
remaining = max(0.01, deadline - time.monotonic())
|
remaining = max(0.01, deadline - time.monotonic())
|
||||||
|
try:
|
||||||
packet = recv_packet(sock, timeout=remaining)
|
packet = recv_packet(sock, timeout=remaining)
|
||||||
|
except socket.timeout:
|
||||||
|
break
|
||||||
if packet.get("type") == packet_type:
|
if packet.get("type") == packet_type:
|
||||||
return packet
|
return packet
|
||||||
raise AssertionError(f"did not receive packet type {packet_type}")
|
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:
|
def send_packet(sock: socket.socket, packet: dict) -> None:
|
||||||
sock.sendall(json.dumps(packet, separators=(",", ":")).encode() + b"\n")
|
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)
|
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():
|
def test_idle_transport_does_not_take_world_authority():
|
||||||
server = start_server()
|
server = start_server()
|
||||||
idle, _ = connect(server)
|
idle, _ = connect(server)
|
||||||
@@ -103,8 +130,7 @@ def test_max_players_applies_to_activated_sessions_not_probes():
|
|||||||
hello(first)
|
hello(first)
|
||||||
second, _ = connect(server)
|
second, _ = connect(server)
|
||||||
send_packet(second, {"type": "hello", "protocolVersion": PROTOCOL_VERSION})
|
send_packet(second, {"type": "hello", "protocolVersion": PROTOCOL_VERSION})
|
||||||
ended = recv_packet(second)
|
ended = recv_until(second, "sessionEnded")
|
||||||
assert ended["type"] == "sessionEnded"
|
|
||||||
assert ended["code"] == "server_full"
|
assert ended["code"] == "server_full"
|
||||||
first.close()
|
first.close()
|
||||||
second.close()
|
second.close()
|
||||||
@@ -115,8 +141,7 @@ def test_protocol_mismatch_is_rejected():
|
|||||||
server = start_server()
|
server = start_server()
|
||||||
sock, _ = connect(server)
|
sock, _ = connect(server)
|
||||||
send_packet(sock, {"type": "hello", "protocolVersion": PROTOCOL_VERSION + 1})
|
send_packet(sock, {"type": "hello", "protocolVersion": PROTOCOL_VERSION + 1})
|
||||||
ended = recv_packet(sock)
|
ended = recv_until(sock, "sessionEnded")
|
||||||
assert ended["type"] == "sessionEnded"
|
|
||||||
assert ended["code"] == "protocol_mismatch"
|
assert ended["code"] == "protocol_mismatch"
|
||||||
sock.close()
|
sock.close()
|
||||||
server.stop()
|
server.stop()
|
||||||
@@ -147,9 +172,7 @@ def test_transform_interest_filters_distant_peer():
|
|||||||
send_packet(b, transform("00000020", 25000, 25000))
|
send_packet(b, transform("00000020", 25000, 25000))
|
||||||
time.sleep(0.05)
|
time.sleep(0.05)
|
||||||
send_packet(a, transform("00000010", 0, 0))
|
send_packet(a, transform("00000010", 0, 0))
|
||||||
b.settimeout(0.2)
|
assert_no_packet_type(b, "transform")
|
||||||
with pytest.raises(socket.timeout):
|
|
||||||
b.recv(4096)
|
|
||||||
assert server.get_stats()["transformPacketsInterestFiltered"] >= 1
|
assert server.get_stats()["transformPacketsInterestFiltered"] >= 1
|
||||||
a.close()
|
a.close()
|
||||||
b.close()
|
b.close()
|
||||||
@@ -174,9 +197,7 @@ def test_rejected_normal_teleport_is_corrected_and_never_relayed():
|
|||||||
assert correction["x"] == 0.0
|
assert correction["x"] == 0.0
|
||||||
assert correction["cellId"] == "00000010"
|
assert correction["cellId"] == "00000010"
|
||||||
|
|
||||||
b.settimeout(0.2)
|
assert_no_packet_type(b, "transform")
|
||||||
with pytest.raises(socket.timeout):
|
|
||||||
b.recv(4096)
|
|
||||||
|
|
||||||
client = server._find_client_by_player_id(a_welcome["playerId"])
|
client = server._find_client_by_player_id(a_welcome["playerId"])
|
||||||
assert client is not None
|
assert client is not None
|
||||||
@@ -243,8 +264,7 @@ def test_oversized_unterminated_packet_closes_session():
|
|||||||
server = start_server()
|
server = start_server()
|
||||||
sock, _ = connect(server)
|
sock, _ = connect(server)
|
||||||
sock.sendall(b"x" * (64 * 1024 + 1))
|
sock.sendall(b"x" * (64 * 1024 + 1))
|
||||||
ended = recv_packet(sock)
|
ended = recv_until(sock, "sessionEnded")
|
||||||
assert ended["type"] == "sessionEnded"
|
|
||||||
assert ended["code"] == "packet_too_large"
|
assert ended["code"] == "packet_too_large"
|
||||||
sock.close()
|
sock.close()
|
||||||
server.stop()
|
server.stop()
|
||||||
Reference in New Issue
Block a user