Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions steam/game_servers.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,23 +276,24 @@
packets.append(sock.recv(2048))

# ensure packets are in correct order
packets = sorted(map(lambda pkt: (_unpack_multipacket_header(payload_offset, pkt)[0], pkt),
packets,
),

Check warning on line 281 in steam/game_servers.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace this map call with a comprehension.

See more on https://sonarcloud.io/project/issues?id=ValvePython_steam&issues=AaEqc4YPlUioR2U_30qy&open=AaEqc4YPlUioR2U_30qy&pullRequest=495
key=lambda x: x[0])

# reconstruct full response
data = b''.join(map(lambda x: x[1][payload_offset:], packets))

Check warning on line 285 in steam/game_servers.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace this map call with a comprehension.

See more on https://sonarcloud.io/project/issues?id=ValvePython_steam&issues=AaEqc4YPlUioR2U_30qz&open=AaEqc4YPlUioR2U_30qz&pullRequest=495

# decompress response if needed
if compressed:
size, checksum = _unpack_from('<ll', packet, 10)
size, checksum = _unpack_from('<lL', packet, 10)
data = _bz2_decompress(data)

if len(data) != size:
raise RuntimeError("Response size mismatch - %d %d" % (len(data), size))
if checksum != crc32(data):
raise RuntimeError("Response checksum mismatch - %d %d" % (checksum, crc32(data)))
actual_checksum = crc32(data) & 0xffffffff
if checksum != actual_checksum:
raise RuntimeError("Response checksum mismatch - %d %d" % (checksum, actual_checksum))

return data

Expand Down Expand Up @@ -516,7 +517,7 @@
players = []

while len(players) < num_players:
player = dict()

Check warning on line 520 in steam/game_servers.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace this constructor call with a literal.

See more on https://sonarcloud.io/project/issues?id=ValvePython_steam&issues=AaEqc4YPlUioR2U_30q0&open=AaEqc4YPlUioR2U_30q0&pullRequest=495
player['index'] = data.unpack('<B')[0]
player['name'] = data.read_cstring()
player['score'], player['duration'] = data.unpack('<lf')
Expand Down
67 changes: 67 additions & 0 deletions tests/test_game_servers_compressed.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import bz2
from binascii import crc32
from struct import pack
import unittest

import mock

from steam.game_servers import _handle_a2s_response, a2s_rules


def compressed_packet(data, checksum=None, size=None):
if checksum is None:
checksum = crc32(data) & 0xffffffff
if size is None:
size = len(data)
return (pack('<lLBB', -2, 0x80000001, 1, 0)
+ pack('<LL', size, checksum) + bz2.compress(data))


class TestCompressedA2SResponse(unittest.TestCase):
@mock.patch('socket.socket')
def test_rules_with_checksum_high_bit(self, socket_class):
data = b'\xff\xff\xff\xffE\x01\x00key\x00one\x00'
self.assertGreater(crc32(data) & 0xffffffff, 0x7fffffff)
socket_class.return_value.recv.side_effect = [
b'\xff\xff\xff\xffA\x01\x02\x03\x04',
compressed_packet(data),
]

self.assertEqual({'key': 'one'}, a2s_rules(('addr', 1234)))
socket_class.return_value.close.assert_called_once_with()

def test_checksum_without_high_bit(self):
data = b'\xff\xff\xff\xffE\x01\x00key\x00value\x00'
self.assertLess(crc32(data) & 0xffffffff, 0x80000000)
sock = mock.Mock()
sock.recv.return_value = compressed_packet(data)

self.assertEqual(data, _handle_a2s_response(sock))

def test_signed_crc32_result(self):
data = b'\xff\xff\xff\xffE\x01\x00key\x00one\x00'
sock = mock.Mock()
sock.recv.return_value = compressed_packet(data)
# Python 2 returns a signed crc32; compare the same 32-bit pattern.
with mock.patch('steam.game_servers.crc32',
return_value=crc32(data) - 0x100000000):
self.assertEqual(data, _handle_a2s_response(sock))

def test_corrupted_checksum_still_rejected(self):
data = b'\xff\xff\xff\xffE\x01\x00key\x00one\x00'
sock = mock.Mock()
sock.recv.return_value = compressed_packet(
data, checksum=(crc32(data) & 0xffffffff) ^ 1)

with self.assertRaises(RuntimeError) as raised:
_handle_a2s_response(sock)
self.assertIn('Response checksum mismatch', str(raised.exception))

def test_wrong_decompressed_size_still_rejected(self):
data = b'\xff\xff\xff\xffE\x01\x00key\x00one\x00'
sock = mock.Mock()
sock.recv.return_value = compressed_packet(data, size=len(data) + 1)

with self.assertRaises(RuntimeError) as raised:
_handle_a2s_response(sock)
self.assertIn('Response size mismatch', str(raised.exception))