TL Schema moved to JSON mode.

Important comments added
This commit is contained in:
Anton Grigoryev 2015-03-11 19:55:55 +03:00
parent 0f5927909d
commit 80b7367a02

View File

@ -5,57 +5,33 @@ Created on Tue Sep 2 19:26:15 2014
@author: agrigoryev @author: agrigoryev
""" """
from binascii import crc32 from binascii import crc32
import struct
import socket
import re
from datetime import datetime from datetime import datetime
import sys
import io import io
import configparser import json
import socket
import struct
current_module = sys.modules[__name__]
def vis(bs): def vis(bs):
l = len(bs) """
n = len(bs) // 8 Function to visualize byte streams. Split into bytes, print to console.
:param bs: BYTE STRING
"""
symbols_in_one_line = 8
n = len(bs) // symbols_in_one_line
for i in range(n): for i in range(n):
print(" ".join(["%02X" % b for b in bs[i*8:i*8+8]])) print(" ".join(["%02X" % b for b in bs[i*symbols_in_one_line:(i+1)*symbols_in_one_line]])) # for every 8 symbols line
print(" ".join(["%02X" % b for b in bs[i*8+8:]])+"\n") print(" ".join(["%02X" % b for b in bs[(i+1)*symbols_in_one_line:]])+"\n") # for last line
class TlElement:
def __init__(self, type_string):
tl_re = re.compile("""([a-z]\w*) #name
\#?([0-9a-f]{8})?\s+ #id
(\{.*\}\s+)? #subtype
(.*)\s+ #arguments list
=\s+([A-Z]\w*) #result
(\s+(\w+))?; #subresult""", re.X)
assert isinstance(type_string, str)
x = tl_re.match(type_string)
if x is not None:
self.name = x.groups()[0]
if x.groups()[1] is not None:
self.id = int(x.groups()[1], 16)
else:
self.id = crc32(re.sub("(#[0-9a-f]{8})|([;\n{}])", "", type_string).encode())
self.args = self.get_arg_list(x.groups()[3])
self.result = x.groups()[4]
else:
raise SyntaxError
@staticmethod
def get_arg_list(arg_string):
arg_re = re.compile("([\w0-9]+)?:?([\w0-9]+)(<([\w0-0]+)>)?")
res = []
for s in arg_re.findall(arg_string):
d = {'name': s[0], 'type': s[1], 'subtype': s[3] if s[2] is not None else None}
res.append(d)
return res
class TL: class TL:
def __init__(self): def __init__(self):
with open("TL_schema.JSON", 'r') as f:
TL_dict = json.load(f)
self.methods = TL_dict['methods']
self.constructors = TL_dict['constructors']
self.func_dict_id = {} self.func_dict_id = {}
self.func_dict_name = {} self.func_dict_name = {}
self.obj_dict_id = {} self.obj_dict_id = {}
@ -63,83 +39,52 @@ class TL:
# Read constructors # Read constructors
f = open("TL_schema", 'r') for elem in self.constructors:
for line in f: z = TlElement(elem)
if line.startswith("---functions---"): self.func_dict_id = {}
break
try:
z = TlElement(line)
self.obj_dict_id[z.id] = z
self.obj_dict_name[z.result] = z
except SyntaxError:
pass
# Read methods def serialize(self, type_, data):
# 1. Type constructor ID
# 2. type costructor params
for line in f: # Bare types
if line.startswith("---functions---"): if type_ == 'string':
break assert isinstance(data, str)
try: struct.pack("<L", len(data))
z = TlElement(line) if type_ == 'int':
self.func_dict_id[z.id] = z assert isinstance(data, str)
self.func_dict_name[z.name] = z struct.pack("<L", len(data))
except SyntaxError:
pass
def deserialize(self, byte_string, type_=None, subtype=None):
def tl_serialize(self, elem, kwargs): if isinstance(byte_string, io.BytesIO):
assert isinstance(elem, TlElement) bytes_io = byte_string
for arg in elem.args: elif isinstance(byte_string, bytes):
pass bytes_io = io.BytesIO(byte_string)
return struct.pack("<L", )
def tl_function_generator(elem, session, tl):
def dummy(**kwargs):
message = tl_serialize(elem, kwargs)
session.send_message(message)
answer = session.recv_message()
return deserialize(answer, TL)
def class_generator(self, tl_element):
class Dummy:
def __init__(self, bstring):
assert isinstance(bstring, bytes)
f = io.BytesIO(bstring)
dict = self.deserialize(f, type=tl_element.type)
def serialize(self, type=None, subtype=None):
pass
def deserialize(self, string, type=None, subtype=None):
if isinstance(string, io.BytesIO):
bytes_io = string
elif isinstance(string, bytes):
bytes_io = io.BytesIO(string)
else: else:
raise Exception("Bad input type, use bytes string or BytesIO object") raise TypeError("Bad input type, use bytes string or BytesIO object")
# Built-in bare types # Built-in bare types
if type == 'int': if type_ == 'int':
x = struct.unpack('<i', bytes_io.read(4))[0] x = struct.unpack('<i', bytes_io.read(4))[0]
elif type == '#': elif type_ == '#':
x = struct.unpack('<I', bytes_io.read(4))[0] x = struct.unpack('<I', bytes_io.read(4))[0]
elif type == 'long': elif type_ == 'long':
x = struct.unpack('<q', bytes_io.read(8))[0] x = struct.unpack('<q', bytes_io.read(8))[0]
elif type == 'double': elif type_ == 'double':
x = struct.unpack('<d', bytes_io.read(8))[0] x = struct.unpack('<d', bytes_io.read(8))[0]
elif type == 'int128': elif type_ == 'int128':
t = struct.unpack('<16s', bytes_io.read(16))[0] t = struct.unpack('<16s', bytes_io.read(16))[0]
x = int.from_bytes(t, 'little') x = int.from_bytes(t, 'little')
elif type == 'int256': elif type_ == 'int256':
t = struct.unpack('<32s', bytes_io.read(32))[0] t = struct.unpack('<32s', bytes_io.read(32))[0]
x = int.from_bytes(t, 'little') x = int.from_bytes(t, 'little')
elif type == 'bytes': elif type_ == 'bytes':
l = int.from_bytes(bytes_io.read(1), 'little') l = int.from_bytes(bytes_io.read(1), 'little')
x = bytes_io.read(l) x = bytes_io.read(l)
bytes_io.read(-(l+1) % 4) # skip padding bytes bytes_io.read(-(l+1) % 4) # skip padding bytes
elif type == 'string': elif type_ == 'string':
l = int.from_bytes(bytes_io.read(1), 'little') l = int.from_bytes(bytes_io.read(1), 'little')
assert l <=254 assert l <=254
if l == 254: if l == 254:
@ -152,10 +97,10 @@ class TL:
x = bytes_io.read(l) x = bytes_io.read(l)
bytes_io.read(-(l+1) % 4) # skip padding bytes bytes_io.read(-(l+1) % 4) # skip padding bytes
assert isinstance(x, bytes) assert isinstance(x, bytes)
elif type == 'vector': elif type_ == 'vector':
assert subtype is not None assert subtype is not None
count = int.from_bytes(bytes_io.read(4), 'little') count = int.from_bytes(bytes_io.read(4), 'little')
x = [self.deserialize(bytes_io, type=subtype) for i in range(count)] x = [self.deserialize(bytes_io, type_=subtype) for i in range(count)]
else: else:
# Boxed types # Boxed types
@ -163,46 +108,72 @@ class TL:
try: try:
tl_elem = self.obj_dict_id[i] tl_elem = self.obj_dict_id[i]
except: except:
raise Exception("Could not extract type: %s" % type) raise Exception("Could not extract type: %s" % type_)
base_boxed_types = ["Vector", "Int", "Long", "Double", "String", "Int128", "Int256"] base_boxed_types = ["Vector", "Int", "Long", "Double", "String", "Int128", "Int256"]
if tl_elem.result in base_boxed_types: if tl_elem.result in base_boxed_types:
x = self.deserialize(bytes_io, type=tl_elem.name, subtype=subtype) x = self.deserialize(bytes_io, type_=tl_elem.name, subtype=subtype)
else: # other types else: # other types
x = {} x = {}
for arg in tl_elem.args: for arg in tl_elem.args:
x[arg['name']] = self.deserialize(bytes_io, type=arg['type'], subtype=arg['subtype']) x[arg['name']] = self.deserialize(bytes_io, type_=arg['type'], subtype=arg['subtype'])
return x return x
class Session: class Session:
def __init__(self, credentials): def __init__(self, ip, port):
config = configparser.ConfigParser() # creating socket
config.read(credentials)
self.sock = socket.socket() self.sock = socket.socket()
self.sock.connect((config['App data']['ip_adress'], config['App data'].getint('port'))) self.sock.connect((ip, port))
self.api_id = config['App data'].getint('api_id') self.auth_key_id = None
self.api_hash = config['App data']['api_hash']
self.auth_key_id = b'\x00\x00\x00\x00\x00\x00\x00\x00'
self.number = 0 self.number = 0
self.TL = TL()
def header(self, message): def __del__(self):
return (self.auth_key_id + # closing socket when session object is deleted
struct.pack('<Q', int(datetime.utcnow().timestamp()*2**32)) + self.sock.close()
@staticmethod
def header_unencrypted(message):
"""
Creating header for the unencrypted message:
:param message: byte string to send
"""
# Basic instructions: https://core.telegram.org/mtproto/description#unencrypted-message
# Message id: https://core.telegram.org/mtproto/description#message-identifier-msg-id
msg_id = int(datetime.utcnow().timestamp()*2**30)*4
return (b'\x00\x00\x00\x00\x00\x00\x00\x00' +
struct.pack('<Q', msg_id) +
struct.pack('<L', len(message))) struct.pack('<L', len(message)))
# TCP Transport
# Instructions may be found here: https://core.telegram.org/mtproto#tcp-transport
# If a payload (packet) needs to be transmitted from server to client or from client to server,
# it is encapsulated as follows: 4 length bytes are added at the front (to include the length,
# the sequence number, and CRC32; always divisible by 4) and 4 bytes with the packet sequence number
# within this TCP connection (the first packet sent is numbered 0, the next one 1, etc.),
# and 4 CRC32 bytes at the end (length, sequence number, and payload together).
def send_message(self, message): def send_message(self, message):
"""
Forming the message frame and sending message to server
:param message: byte string to send
"""
print('>>') print('>>')
vis(message) vis(message) # Sending message visualisation to console
#self.number += 1 data = self.header_unencrypted(message) + message
data = self.header(message) + message
step1 = struct.pack('<LL', len(data)+12, self.number) + data step1 = struct.pack('<LL', len(data)+12, self.number) + data
step2 = step1 + struct.pack('<L', crc32(step1)) step2 = step1 + struct.pack('<L', crc32(step1))
self.sock.send(step2) self.sock.send(step2)
self.number += 1
def recv_message(self): def recv_message(self):
"""
Reading socket and receiving message from server. Check the CRC32 and
"""
packet_length_data = self.sock.recv(4) packet_length_data = self.sock.recv(4)
if len(packet_length_data) > 0: # if we have smth. in the socket if len(packet_length_data) > 0: # if we have smth. in the socket
packet_length = struct.unpack("<L", packet_length_data)[0] packet_length = struct.unpack("<L", packet_length_data)[0]
@ -213,7 +184,9 @@ class Session:
message_length = struct.unpack("<I", packet[20:24])[0] message_length = struct.unpack("<I", packet[20:24])[0]
data = packet[24:24+message_length] data = packet[24:24+message_length]
crc = packet[-4:] crc = packet[-4:]
# Checking the CRC32 correctness of received data
if crc32(packet_length_data + packet[0:-4]).to_bytes(4, 'little') == crc: if crc32(packet_length_data + packet[0:-4]).to_bytes(4, 'little') == crc:
print('<<') print('<<')
vis(data) vis(data) # Received message visualisation to console
return data return data