# Auto-generated, do not edit from messages import * #from io cimport BytesIO from io import BytesIO from libc.stdlib cimport abort cdef extern from "Python.h": int PyArg_ParseTupleAndKeywords(object args, object kwargs, char* format, char** keywords, ...) cdef class PyMsg: def __cinit__(self): pass ctypedef object PyBytesIO cdef class MessageCodec: """ Implements encode/decode primitives """ cdef list msg_selector def __init__(self, list msg_selector): self.msg_selector = msg_selector @staticmethod cdef read_boolean(PyBytesIO reader): cdef bint b b = reader.read(1)[0] return b == 1 @staticmethod def read_bool_method(PyBytesIO reader): return MessageCodec.read_boolean(reader) @staticmethod cdef read_uint(PyBytesIO reader): cdef unsigned long x = 0 # the result cdef unsigned int s = 0 # the shift (our result is big-ending) cdef int i = 0 # n of byte (max 9 for uint64) cdef bytes b cdef unsigned long num while True: b = reader.read(1) if len(b) == 0: raise IndexError('bytes out of range') num = int.from_bytes(b, "big", signed=False) if num < 0x80: if i > 9 or (i == 9 and num > 1): raise OverflowError() return int(x | num << s) x |= (num & 0x7f) << s s += 7 i += 1 @staticmethod def read_size(PyBytesIO reader): cdef unsigned long size = 0 cdef bytes b cdef unsigned long num for i in range(3): b = reader.read(1) num = int.from_bytes(b, "big", signed=False) size += num << (8*i) return size @staticmethod def read_int(PyBytesIO reader): """ ux, err := ReadUint(reader) x := int64(ux >> 1) if err != nil { return x, err } if ux&1 != 0 { x = ^x } return x, err """ cdef unsigned long ux = MessageCodec.read_uint(reader) cdef long x = int(ux >> 1) if ux & 1 != 0: x = - x - 1 return x @staticmethod def read_string(PyBytesIO reader): cdef unsigned long length = MessageCodec.read_uint(reader) cdef bytes s try: s = reader.read(length) except Exception as e: print(f'Error while reading string of length {length}') raise Exception(e) try: return s.decode("utf-8", errors="replace").replace("\x00", "\uFFFD") except UnicodeDecodeError: return None @staticmethod def read_message_id(PyBytesIO reader): """ Read and return the first byte where the message id is encoded """ cdef unsigned long id_ = MessageCodec.read_uint(reader) return id_ @staticmethod def encode(PyMsg m): ... @staticmethod def decode(bytes b): cdef PyBytesIO reader = BytesIO(b) return MessageCodec.read_head_message(reader) @staticmethod def check_message_id(bytes b): """ todo: make it static and without reader. It's just the first byte Read and return the first byte where the message id is encoded """ cdef PyBytesIO reader = BytesIO(b) cdef unsigned long id_ = MessageCodec.read_uint(reader) return id_ @staticmethod def decode_key(bytes b): """ Decode the message key (encoded with little endian) """ cdef unsigned long decoded try: decoded = int.from_bytes(b, "little", signed=False) except Exception as e: print(f"Error while decoding message key (SessionID) from {b}") raise e return decoded def decode_detailed(self, bytes b): cdef PyBytesIO reader = BytesIO(b) cdef list messages_list cdef int mode try: messages_list = [self.handler(reader, 0)] except IndexError: print('[WARN] Broken batch') return list() if isinstance(messages_list[0], BatchMeta): # Old BatchMeta mode = 0 elif isinstance(messages_list[0], BatchMetadata): # New BatchMeta if messages_list[0].version == 0: mode = 0 else: mode = 1 else: return messages_list while True: try: msg_decoded = self.handler(reader, mode) if msg_decoded is not None: messages_list.append(msg_decoded) except IndexError: break return messages_list def handler(self, PyBytesIO reader, int mode = 0): cdef unsigned long message_id = MessageCodec.read_message_id(reader) cdef int r_size if mode == 1: # We read the three bytes representing the length of message. It can be used to skip unwanted messages r_size = MessageCodec.read_size(reader) if message_id not in self.msg_selector: reader.read(r_size) return None return MessageCodec.read_head_message(reader, message_id) elif mode == 0: # Old format with no bytes for message length return MessageCodec.read_head_message(reader, message_id) else: raise IOError() @staticmethod def read_head_message(PyBytesIO reader, unsigned long message_id): <% $messages.each do |msg| %> if message_id == <%= msg.id %>: return <%= msg.name %>( <%= msg.attributes.map { |attr| "#{attr.name.snake_case}=self.read_#{attr.type.to_s}(reader)" } .join ",\n " %> ) <% end %>