Completely overhaul connections and transports

Reduce abstraction leaks. Now the transport can hold any state,
rather than just the tag. It's also responsible to initialize on
the first connection, and they can be cleanly reset.

asyncio connections are no longer used, in favour of raw sockets,
which should avoid some annoyances.

For the time being, more obscure transport modes have been removed,
as well as proxy support, until further cleaning is done.
This commit is contained in:
Lonami Exo
2022-01-15 13:33:50 +01:00
parent 02703e3753
commit f5f0c84553
18 changed files with 221 additions and 853 deletions

View File

@@ -0,0 +1,4 @@
from .transport import Transport
from .abridged import Abridged
from .full import Full
from .intermediate import Intermediate

View File

@@ -0,0 +1,43 @@
from .transport import Transport
import struct
class Abridged(Transport):
def __init__(self):
self._init = False
def recreate_fresh(self):
return type(self)()
def pack(self, input: bytes) -> bytes:
if self._init:
header = b''
else:
header = b'\xef'
self._init = True
length = len(data) >> 2
if length < 127:
length = struct.pack('B', length)
else:
length = b'\x7f' + int.to_bytes(length, 3, 'little')
return header + length + data
def unpack(self, input: bytes) -> (int, bytes):
if len(input) < 4:
raise EOFError()
length = input[0]
if length < 127:
offset = 1
else:
offset = 4
length = struct.unpack('<i', input[1:4] + b'\0')[0]
length = (length << 2) + offset
if len(input) < length:
raise EOFError()
return length, input[offset:length]

View File

@@ -0,0 +1,41 @@
from .transport import Transport
import struct
from zlib import crc32
class Full(Transport):
def __init__(self):
self._send_counter = 0
self._recv_counter = 0
def recreate_fresh(self):
return type(self)()
def pack(self, input: bytes) -> bytes:
# https://core.telegram.org/mtproto#tcp-transport
length = len(input) + 12
data = struct.pack('<ii', length, self._send_counter) + input
crc = struct.pack('<I', crc32(data))
self._send_counter += 1
return data + crc
def unpack(self, input: bytes) -> (int, bytes):
if len(input) < 12:
raise EOFError()
length, seq = struct.unpack('<ii', input[:8])
if len(input) < length:
raise EOFError()
if seq != self._recv_counter:
raise ValueError(f'expected sequence value {self._recv_counter!r}, got {seq!r}')
body = input[8:length - 4]
checksum = struct.unpack('<I', input[length - 4:length])[0]
valid_checksum = crc32(input[:length - 4])
if checksum != valid_checksum:
raise InvalidChecksumError(checksum, valid_checksum)
self._recv_counter += 1
return length, body

View File

@@ -0,0 +1,29 @@
from .transport import Transport
import struct
class Intermediate(Transport):
def __init__(self):
self._init = False
def recreate_fresh(self):
return type(self)()
def pack(self, input: bytes) -> bytes:
if self._init:
header = b''
else:
header = b'\xee\xee\xee\xee'
self._init = True
return header + struct.pack('<i', len(data)) + data
def unpack(self, input: bytes) -> (int, bytes):
if len(input) < 4:
raise EOFError()
length = struct.unpack('<i', input[:4])[0] + 4
if len(input) < length:
raise EOFError()
return length, input[4:length]

View File

@@ -0,0 +1,17 @@
import abc
class Transport(abc.ABC):
# Should return a newly-created instance of itself
@abc.abstractmethod
def recreate_fresh(self):
pass
@abc.abstractmethod
def pack(self, input: bytes) -> bytes:
pass
# Should raise EOFError if it does not have enough bytes
@abc.abstractmethod
def unpack(self, input: bytes) -> (int, bytes):
pass