BitStreams

This commit is contained in:
Emily 2021-03-04 13:40:32 +01:00
parent 1b78fb98bc
commit 483587fd3b
6 changed files with 171 additions and 104 deletions

View File

@ -13,7 +13,7 @@
"host": "0.0.0.0", "host": "0.0.0.0",
"port": 7777, "port": 7777,
"hostname": "Python > C", "hostname": "Python > C",
"password": "test", "password": "",
"rcon_password": "changeme", "rcon_password": "changeme",
"max_players": 50, "max_players": 50,
"mode": "debug", "mode": "debug",

View File

@ -62,7 +62,7 @@ BitStream::ReadBit() # hasAcks
CreateInternalPacketFromBitStream() CreateInternalPacketFromBitStream()
caller_addr: 0x0045FB0D caller_addr: 0x0045FB0D
time: 22 39423557 time: 22 39423557
bitStream->numberOfBitsAllocated: 176 bitStream->numberOfBitsAllocated: 176 # 22 bytes
bitStream->numberOfBitsUsed: 176 bitStream->numberOfBitsUsed: 176
bitStream->readOffset: 1 bitStream->readOffset: 1
bitStream->data: bitStream->data:

View File

@ -96,7 +96,7 @@ class QueryClient(BaseClient):
p["ping"] p["ping"]
] ]
return packet + struct.pack(b"<H" + (b"cc%dsII" * data[0]) % tuple(len(p["nick"]) for p in players), *data) return packet + struct.pack(b"<H" + (b"BB%dsII" * data[0]) % tuple(len(p["nick"]) for p in players), *data)
async def query_p(self, packet: bytes) -> bytes: async def query_p(self, packet: bytes) -> bytes:
return packet return packet

View File

@ -7,91 +7,103 @@ logger = logging.getLogger(__name__)
class Bitstream: class Bitstream:
def __init__(self, data: bytes = b""): def __init__(self, data: bytes = b""):
self.offset = 0 self._offset = 0
self.buffer = array("B", data) self._buffer = bytearray(data) # array("B", data)
# This is not finished.. would like to implement bit_length support def _can_access_bits(self, bits_to_access: int) -> bool:
def write(self, value: bool): return self._offset + bits_to_access <= len(self._buffer) << 3
offset = self.offset % 8
if self.offset // 8 == len(self.buffer): def write_bit(self, value: bool) -> bool:
self.buffer.append(0) if not self._can_access_bits(1):
return False
mask = (1 << (7 - (self._offset % 8)))
if value:
self._buffer[self._offset >> 3] |= mask
else:
self._buffer[self._offset >> 3] &= ~mask
self._offset += 1
return True
def read_bit(self) -> (bool, bool):
if not self._can_access_bits(1):
return False, False
mask = (1 << (7 - (self._offset % 8)))
value = self._buffer[self._offset >> 3] & mask
self._offset += 1
return True, value > 0
def write(self, value: bytes, bit_length: int) -> bool:
if not self._can_access_bits(bit_length):
return False
self.offset += 1 if len(value) << 3 < bit_length:
return False
if value == False: byte_from = self._offset >> 3
return byte_to = (self._offset + bit_length + 7) >> 3
bits_written = 0
for byte_index in range(byte_from, byte_to, 1):
byte = self._buffer[byte_index]
for bit_index in range(self._offset % 8, 8, 1):
mask = 1 << (7 - bit_index)
bit = value[bits_written >> 3] & (1 << ((7 - bits_written) % 8))
if bit:
byte |= mask
else:
byte &= ~mask
self._offset += 1
bits_written += 1
if bits_written >= bit_length:
break
self._buffer[byte_index] = byte
return True
def read(self, bit_length: int) -> (bool, bytes):
if not self._can_access_bits(bit_length):
return False, b""
mask = 1 << (7 - offset) byte_from = self._offset >> 3
self.buffer[-1] |= mask byte_to = (self._offset + bit_length + 7) >> 3
""" # Since python doesnt have a max number size,
def write(self, value: int, bit_length: int): # we can read the bits we need and store them into a number,
offset = self.offset % 8 # then convert the number to bytes (as they are all in the same for python)
shifts = offset + bit_length
while shifts > 8:
if offset == 0:
self.buffer.append(0)
v = self.buffer[buffer_index]
mask = (1 << ((self.offset % 8) + 1))
"""
def read(self, bit_length: int) -> int:
byte_read_from = self.offset // 8
byte_read_to = (self.offset + bit_length - 1) // 8
bytes_to_read = byte_read_to - byte_read_from + 1
if byte_read_to >= len(self.buffer):
logger.debug("byte_read_to: %d" % byte_read_to)
logger.debug("len(self.buffer): %d" % len(self.buffer))
raise Exception("Reading beyond the buffer")
value = 0 value = 0
shifts = bytes_to_read - 1
while shifts >= 0:
value += self.buffer[byte_read_to - shifts] << (shifts * 8)
shifts -= 1
value <<= self.offset % 8
value &= (1 << (8 * bytes_to_read)) - 1
value >>= (bytes_to_read * 8) - bit_length
self.offset += bit_length for byte_index in range(byte_from, byte_to, 1):
return value for bit_index in range(self._offset % 8, 8, 1):
mask = 1 << (7 - bit_index)
bit = self._buffer[self._offset >> 3] & (1 << ((7 - self._offset) % 8))
value = (value << 1) + bit
self._offset += 1
if self._offset >= bit_length:
break
#if self._offset % 8 == 0:
#value +=
return True, value.to_bytes((bit_length + 7) >> 3, "big") # bytes(value)
def read_compressed(self, bit_length: int, unsigned: bool): def read_compressed(self):
pass pass
def read_aligned_bytes(self, byte_length: int) -> bytes:
if byte_length <= 0:
return b""
self.align_to_byte()
if self.offset + (byte_length * 8) > len(self.buffer): def pretty(self) -> str:
logger.debug("Reading beyond the buffer") b = bytearray(" " + " ".join(" ".join(format(c, "08b")) for c in self._buffer) + " ", "ascii")
return b"" m = self._offset * 2 + (self._offset >> 3) - (self._offset // (max(1, len(self._buffer)) * 8))
b[m] = 124
byte_read_from = self.offset // 8
byte_read_to = byte_read_from + byte_length
self.offset += byte_length
return self.buffer[byte_read_from:byte_read_to + 1]
def align_to_byte(self):
if self.offset % 8 != 0:
self.offset += 8 - (self.offset % 8)
def __repr__(self) -> str:
b = bytearray(" " + " ".join(" ".join(format(c, "08b")) for c in self.buffer) + " ", "ascii")
m = self.offset * 2 + (self.offset // 8) - (self.offset // (max(1, len(self.buffer)) * 8))
#b[m] = 91 # [
#b[m + 2] = 93 # ]
b[m] = 124 # |
return b.decode() return b.decode()
def __repr__(self) -> str:
return "<Bitstream addr:0x%012x offset:%d len:%d>" % (id(self), self._offset, len(self._buffer) << 3)

View File

@ -22,7 +22,7 @@ class Server:
self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.socket.bind((self.config.host, self.config.port)) self.socket.bind((self.config.host, self.config.port))
self.socket.setblocking(0) # TODO: Check if needed? I dont fully understand this "feature" self.socket.setblocking(0)
logger.debug("Socket created") logger.debug("Socket created")
async def on_command(self, cmd: str): async def on_command(self, cmd: str):

103
test.py
View File

@ -1,30 +1,85 @@
from sampy.raknet.bitstream import Bitstream from sampy.raknet.bitstream import Bitstream
a = Bitstream() bitstream = Bitstream(b"\x00" * 4)
""" MAGIC_BYTES = b"\x01\xff\x02"
a.buffer.append(0x42) MAGIC_BITS = [int(bit) for bit in " ".join([format(byte, "08b") for byte in MAGIC_BYTES]) if bit in ("0", "1")]
a.buffer.append(0x13) fail = 0
a.buffer.append(0xFF)
a.buffer.append(0xAC)
a.buffer.append(0x00)
a.buffer.append(0x69)
"""
"""
a.write(1)
a.write(0)
a.write(1)
a.write(1)
a.write(1)
a.write(1)
a.write(0)
a.write(1)
a.write(1) def print_pretty(prefix, msg):
""" prefix += " " * (40 - len(prefix))
print(prefix, msg)
data = [int(x, 16) for x in "00 00 42 90 0b 61 61 61 61 62 62 62 62 63 63 63 63 66 66 66 66 00".split(" ")] def reset_bitstream():
bitstream._buffer = bytearray(b"\x00" * len(bitstream._buffer))
bitstream._offset = 0
for b in data: def test_write_bit(var_bits):
a.buffer.append(b) global fail
print_pretty("<Test bitstream.write_bit:in>", bitstream.pretty())
for bit in var_bits:
if not bitstream.write_bit(bit):
print_pretty("<Test bitstream.write_bit>", "Failed")
fail += 1
print_pretty("<Test bitstream.write_bit:out>", bitstream.pretty())
print(a) def test_read_bit(var_compare_bits):
global fail
print_pretty("<Test bitstream.read_bit:in>", bitstream.pretty())
for bit in var_compare_bits:
ok, bit2 = bitstream.read_bit()
if not ok:
print_pretty("<Test bitstream.read_bit>", "Failed (ok)")
fail += 1
continue
if bit2 != bit:
print_pretty("<Test bitstream.read_bit>", "Failed (match)")
fail += 1
continue
print_pretty("<Test bitstream.read_bit:out>", bitstream.pretty())
def test_write(var_bytes):
global fail
print_pretty("<Test bitstream.write:in>", bitstream.pretty())
if not bitstream.write(var_bytes, len(var_bytes) << 3):
print_pretty("<Test bitstream.write>", "Failed")
fail += 1
print_pretty("<Test bitstream.write:out>", bitstream.pretty())
def test_read(var_compare_bytes):
global fail
print_pretty("<Test bitstream.read:in>", bitstream.pretty())
ok, bytez = bitstream.read(len(var_compare_bytes) << 3)
if ok:
print(var_compare_bytes, bytez)
if bytez != var_compare_bytes:
print_pretty("<Test bitstream.read>", "Failed (match)")
fail += 1
else:
print_pretty("<Test bitstream.read>", "Failed (ok)")
fail += 1
print_pretty("<Test bitstream.read:out>", bitstream.pretty())
def test_rw_bit(offset):
bitstream._offset = offset
test_write_bit(MAGIC_BITS)
bitstream._offset = offset
test_read_bit(MAGIC_BITS)
def test_rw_bytes(offset):
bitstream._offset = offset
test_write(MAGIC_BYTES)
bitstream._offset = offset
test_read(MAGIC_BYTES)
# Test all edges
for i in range(9):
reset_bitstream()
test_rw_bit(i)
reset_bitstream()
for i in range(9):
reset_bitstream()
test_rw_bytes(i)
print("Failed: %d" % fail)