def __init__(self, factory, *args, **kwargs): self.config = ConfigController.instance().data self.core = CoreProtocol(self) self.factory = factory self.buff_type = self.factory.get_buff_type(self.config["version"]) self.recv_buff = self.buff_type() self.cipher = Cipher() self.logger = logging.getLogger("%s{%s}" % ( self.__class__.__name__, "pseudo host")) self.logger.setLevel(self.factory.log_level) self.ticker = self.factory.ticker_type(self.logger) self.ticker.start() self.bots = UpstreamBots(self) def no_bots(): return [] self.bots.get_bots = no_bots self.setup()
def __init__(self, factory, remote_addr): self.factory = factory self.remote_addr = remote_addr self.buff_type = self.factory.buff_type self.recv_buff = self.buff_type() self.cipher = Cipher() self.tasks = Tasks() self.logger = logging.getLogger( "%s{%s}" % (self.__class__.__name__, self.remote_addr.host)) self.logger.setLevel(self.factory.log_level) self.connection_timer = self.tasks.add_delay( self.factory.connection_timeout, self.connection_timed_out) self.setup()
def __init__(self, client: SocketStream): self.client = client self.do_loop = True self.protocol_state = "init" self.protocol_version = packets.default_protocol_version self.messages: List[bytes] = [] self._locks: List[ Dict[str, Union[ str, Event, Optional[AnyBuffer] ]] ] = [] self.server_id = make_server_id() self.verify_token = make_verify_token() self.cipher = Cipher() self.display_name = "" self.uuid: UUID = None
def __init__(self, factory, remote_addr): self.factory = factory self.remote_addr = remote_addr self.buff_type = self.factory.get_buff_type(self.protocol_version) self.recv_buff = self.buff_type() self.cipher = Cipher() self.logger = logging.getLogger( "%s{%s}" % (self.__class__.__name__, self.remote_addr.host)) self.logger.setLevel(self.factory.log_level) self.ticker = self.factory.ticker_type(self.logger) self.ticker.start() self.connection_timer = self.ticker.add_delay( delay=self.factory.connection_timeout / self.ticker.interval, callback=self.connection_timed_out) self.setup()
def __init__(self, factory, remote_addr): self.factory = factory self.remote_addr = remote_addr self.buff_type = self.factory.get_buff_type(self.protocol_version) self.recv_buff = self.buff_type() self.cipher = Cipher() self.logger = logging.getLogger("%s{%s}" % ( self.__class__.__name__, self.remote_addr.host)) self.logger.setLevel(self.factory.log_level) self.ticker = self.factory.ticker_type(self.logger) self.ticker.start() self.connection_timer = self.ticker.add_delay( delay=self.factory.connection_timeout / self.ticker.interval, callback=self.connection_timed_out) self.setup()
class Protocol(protocol.Protocol, PacketDispatcher, object): """Shared logic between the client and server""" #: Usually a reference to a :class:`~quarry.types.buffer.Buffer` class. #: This is useful when constructing a packet payload for use in #: :meth:`send_packet` buff_type = None #: The logger for this protocol. logger = None #: A reference to a :class:`~quarry.net.ticker.Ticker` instance. ticker = None #: A reference to the factory factory = None #: The IP address of the remote. remote_addr = None recv_direction = None send_direction = None protocol_version = packets.default_protocol_version protocol_mode = "init" compression_threshold = -1 in_game = False closed = False def __init__(self, factory, remote_addr): self.factory = factory self.remote_addr = remote_addr self.buff_type = self.factory.get_buff_type(self.protocol_version) self.recv_buff = self.buff_type() self.cipher = Cipher() self.logger = logging.getLogger( "%s{%s}" % (self.__class__.__name__, self.remote_addr.host)) self.logger.setLevel(self.factory.log_level) self.ticker = self.factory.ticker_type(self.logger) self.ticker.start() self.connection_timer = self.ticker.add_delay( delay=self.factory.connection_timeout / self.ticker.interval, callback=self.connection_timed_out) self.setup() # Fix ugly twisted methods ------------------------------------------------ def dataReceived(self, data): return self.data_received(data) def connectionMade(self): return self.connection_made() def connectionLost(self, reason=None): return self.connection_lost(reason) # Convenience functions --------------------------------------------------- def check_protocol_mode_switch(self, mode): transitions = [("init", "status"), ("init", "login"), ("login", "play")] if (self.protocol_mode, mode) not in transitions: raise ProtocolError("Cannot switch protocol mode from %s to %s" % (self.protocol_mode, mode)) def switch_protocol_mode(self, mode): self.check_protocol_mode_switch(mode) self.protocol_mode = mode def set_compression(self, compression_threshold): self.compression_threshold = compression_threshold self.logger.debug("Compression threshold set to %d bytes" % compression_threshold) def close(self, reason=None): """Closes the connection""" if not self.closed: if reason: reason = "Closing connection: %s" % reason else: reason = "Closing connection" if self.in_game: self.logger.info(reason) else: self.logger.debug(reason) self.transport.loseConnection() self.closed = True def log_packet(self, prefix, name): """Logs a packet at debug level""" self.logger.debug("Packet %s %s/%s" % (prefix, self.protocol_mode, name)) # General callbacks ------------------------------------------------------- def setup(self): """Called when the Protocol's initialiser is finished""" pass def protocol_error(self, err): """Called when a protocol error occurs""" self.logger.exception(err) self.close("Protocol error") # Connection callbacks ---------------------------------------------------- def connection_made(self): """Called when the connection is established""" self.logger.debug("Connection made") def connection_lost(self, reason=None): """Called when the connection is lost""" self.closed = True if self.in_game: self.player_left() self.logger.debug("Connection lost") self.ticker.stop() def connection_timed_out(self): """Called when the connection has been idle too long""" self.close("Connection timed out") # Auth callbacks ---------------------------------------------------------- def auth_ok(self, data): """Called when auth with mojang succeeded (online mode only)""" pass def auth_failed(self, err): """Called when auth with mojang failed (online mode only)""" self.logger.warning("Auth failed: %s" % err.value) self.close("Auth failed: %s" % err.value) # Player callbacks -------------------------------------------------------- def player_joined(self): """Called when the player joins the game""" self.in_game = True def player_left(self): """Called when the player leaves the game""" pass # Packet handling --------------------------------------------------------- def get_packet_name(self, ident): key = (self.protocol_version, self.protocol_mode, self.recv_direction, ident) try: return packets.packet_names[key] except KeyError: raise ProtocolError("No name known for packet: %s" % (key, )) def get_packet_ident(self, name): key = (self.protocol_version, self.protocol_mode, self.send_direction, name) try: return packets.packet_idents[key] except KeyError: raise ProtocolError("No ID known for packet: %s" % (key, )) def data_received(self, data): # Decrypt data data = self.cipher.decrypt(data) # Add it to our buffer self.recv_buff.add(data) # Read some packets while not self.closed: # Save the buffer, in case we read an incomplete packet self.recv_buff.save() # Read the packet try: buff = self.recv_buff.unpack_packet(self.buff_type, self.compression_threshold) except BufferUnderrun: self.recv_buff.restore() break try: # Identify the packet name = self.get_packet_name(buff.unpack_varint()) # Dispatch the packet try: self.packet_received(buff, name) except BufferUnderrun: raise ProtocolError("Packet is too short: %s" % name) if len(buff) > 0: raise ProtocolError("Packet is too long: %s" % name) # Reset the inactivity timer self.connection_timer.restart() except ProtocolError as e: self.protocol_error(e) def packet_received(self, buff, name): """ Called when a packet is received from the remote. Usually this method dispatches the packet to a method named ``packet_<packet name>``, or calls :meth:`packet_unhandled` if no such methods exists. You might want to override this to implement your own dispatch logic or logging. """ self.log_packet(". recv", name) dispatched = self.dispatch((name, ), buff) if not dispatched: self.packet_unhandled(buff, name) def packet_unhandled(self, buff, name): """ Called when a packet is received that is not hooked. The default implementation silently discards the packet. """ buff.discard() def send_packet(self, name, *data): """Sends a packet to the remote.""" if self.closed: return self.log_packet("# send", name) data = b"".join(data) # Prepend ident data = self.buff_type.pack_varint(self.get_packet_ident(name)) + data # Pack packet data = self.buff_type.pack_packet(data, self.compression_threshold) # Encrypt data = self.cipher.encrypt(data) # Send self.transport.write(data)
class ClientConnection: def __init__(self, client: SocketStream): self.client = client self.do_loop = True self.protocol_state = "init" self.protocol_version = packets.default_protocol_version self.messages: List[bytes] = [] self._locks: List[ Dict[str, Union[ str, Event, Optional[AnyBuffer] ]] ] = [] self.server_id = make_server_id() self.verify_token = make_verify_token() self.cipher = Cipher() self.display_name = "" self.uuid: UUID = None @property def player(self) -> Player: return PlayerRegistry.get_player(self.uuid) def __repr__(self): return (f"ClientConnection(loop={self.do_loop}, " f"message_queue={len(self.messages)}, " f"lock_queue={len(self._locks)})") async def serve(self): async with create_task_group() as tg: await tg.spawn(self.serve_loop) await tg.spawn(self.write_loop) async def serve_loop(self): data = b"" run_again = False async with create_task_group() as tg: while self.do_loop: if not run_again: try: line = await self.client.receive_some(1024) except ConnectionError: line = b"" if line == b"": try: warn(f"Closing connection to {self.client.server_hostname}") except TLSRequired: pass self.do_loop = False break data += self.cipher.decrypt(line) try: msg = ClientMessage(self, data, self.protocol_version) except BufferUnderrun: run_again = False continue else: data = data[msg.old_len:] if data != b"": run_again = True for lock in self._locks: if lock["name"] == msg.name: self._locks.remove(lock) lock["result"] = msg.buffer await lock["lock"].set() break if msg.name == "handshake": await self.handle_msg(msg) else: await tg.spawn(self.handle_msg, msg) for lock in self._locks: await lock["lock"].set() if self.protocol_state == "play": # User was logged in debug("Player left, removing from game...") # TODO: Fix EventHandler # Requires: client_message.py:22 # EventHandler.event_player_leave(self.player) PlayerRegistry.players.remove(self.player) async def handle_msg(self, msg: ClientMessage): try: coro = PacketHandler.decode(msg) if coro: args = await coro coro2 = EventHandler.handle_event(msg, args) if coro2: await coro2 except Exception: # pylint: disable=broad-except error(f"Exception occurred:\n{format_exc()}") async def write_loop(self): while self.do_loop: if self.messages: msg = self.messages.pop(0) debug(f"Sending to client: {msg}") await self.client.send_all(msg) else: await sleep(0.00001) # Allow other tasks to run async def wait_for_packet(self, packet_name: str) -> AnyBuffer: lock = { "name": packet_name, "lock": create_event(), "result": None } self._locks.append(lock) await lock["lock"].wait() res: AnyBuffer = lock["result"] return res def send_packet(self, packet: bytes): self.messages.append(self.cipher.encrypt(packet))
class Protocol(protocol.Protocol, PacketDispatcher, object): """Shared logic between the client and server""" #: Usually a reference to the :class:`Buffer` class. This is useful when #: constructing a packet payload for use in :meth:`send_packet` buff_type = None #: The logger for this protocol. logger = None #: A reference to a :class:`Tasks` instance. This object has methods for #: setting up repeating or delayed callbacks tasks = None #: A reference to the factory factory = None #: The IP address of the remote. remote_addr = None recv_direction = None send_direction = None protocol_version = packets.default_protocol_version protocol_mode = "init" compression_threshold = None compression_enabled = False in_game = False closed = False def __init__(self, factory, remote_addr): self.factory = factory self.remote_addr = remote_addr self.buff_type = self.factory.buff_type self.recv_buff = self.buff_type() self.cipher = Cipher() self.tasks = Tasks() self.logger = logging.getLogger( "%s{%s}" % (self.__class__.__name__, self.remote_addr.host)) self.logger.setLevel(self.factory.log_level) self.connection_timer = self.tasks.add_delay( self.factory.connection_timeout, self.connection_timed_out) self.setup() ### Fix ugly twisted methods ---------------------------------------------- def dataReceived(self, data): return self.data_received(data) def connectionMade(self): return self.connection_made() def connectionLost(self, reason=None): return self.connection_lost(reason) ### Convenience functions ------------------------------------------------- def check_protocol_mode_switch(self, mode): transitions = [("init", "status"), ("init", "login"), ("login", "play")] if (self.protocol_mode, mode) not in transitions: raise ProtocolError("Cannot switch protocol mode from %s to %s" % (self.protocol_mode, mode)) def switch_protocol_mode(self, mode): self.check_protocol_mode_switch(mode) self.protocol_mode = mode def set_compression(self, compression_threshold): if not self.compression_enabled: self.compression_enabled = True self.logger.debug("Compression enabled") self.compression_threshold = compression_threshold self.logger.debug("Compression threshold set to %d bytes" % compression_threshold) def close(self, reason=None): """Closes the connection""" if not self.closed: if reason: reason = "Closing connection: %s" % reason else: reason = "Closing connection" if self.in_game: self.logger.info(reason) else: self.logger.debug(reason) self.transport.loseConnection() self.closed = True def log_packet(self, prefix, name): """Logs a packet at debug level""" self.logger.debug("Packet %s %s/%s" % (prefix, self.protocol_mode, name)) ### General callbacks ----------------------------------------------------- def setup(self): """Called when the Protocol's initialiser is finished""" pass def protocol_error(self, err): """Called when a protocol error occurs""" msg = "Protocol error: %s" % err self.logger.error(msg) self.close(msg) ### Connection callbacks -------------------------------------------------- def connection_made(self): """Called when the connection is established""" self.logger.debug("Connection made") def connection_lost(self, reason=None): """Called when the connection is lost""" self.closed = True if self.in_game: self.player_left() self.logger.debug("Connection lost") self.tasks.stop_all() def connection_timed_out(self): """Called when the connection has been idle too long""" self.close("Connection timed out") ### Auth callbacks -------------------------------------------------------- def auth_ok(self, data): """Called when auth with mojang succeeded (online mode only)""" pass def auth_failed(self, err): """Called when auth with mojang failed (online mode only)""" self.logger.warn("Auth failed: %s" % err.value) self.close("Auth failed: %s" % err.value) ### Player callbacks ------------------------------------------------------ def player_joined(self): """Called when the player joins the game""" self.in_game = True def player_left(self): """Called when the player leaves the game""" pass ### Packet handling ------------------------------------------------------- def data_received(self, data): # Decrypt data data = self.cipher.decrypt(data) # Add it to our buffer self.recv_buff.add(data) # Read some packets while not self.closed: # Save the buffer, in case we read an incomplete packet self.recv_buff.save() # Try to read a packet try: max_bits = 32 if self.protocol_mode == "play" else 21 packet_length = self.recv_buff.unpack_varint(max_bits=max_bits) packet_body = self.recv_buff.read(packet_length) # Incomplete packet read, restore the buffer. except BufferUnderrun: self.recv_buff.restore() break # Load the packet body into a buffer packet_buff = self.buff_type() packet_buff.add(packet_body) try: # Catch protocol errors try: # Catch buffer overrun/underrun if self.compression_enabled: uncompressed_length = packet_buff.unpack_varint() if uncompressed_length > 0: data = zlib.decompress(packet_buff.read()) packet_buff = Buffer() packet_buff.add(data) ident = packet_buff.unpack_varint() key = (self.protocol_version, self.protocol_mode, self.recv_direction, ident) try: name = packets.packet_names[key] except KeyError: raise ProtocolError("No name known for packet: %s" % (key, )) self.packet_received(packet_buff, name) except BufferUnderrun: raise ProtocolError("Packet is too short!") if len(packet_buff) > 0: raise ProtocolError("Packet is too long!") except ProtocolError as e: self.protocol_error(e) break # We've read a complete packet, so reset the inactivity timeout self.connection_timer.restart() def packet_received(self, buff, name): """ Called when a packet is received from the remote. Usually this method dispatches the packet to a method named ``packet_<packet name>``, or calls :meth:`packet_unhandled` if no such methods exists. You might want to override this to implement your own dispatch logic or logging. """ self.log_packet(". recv", name) dispatched = self.dispatch((name, ), buff) if not dispatched: self.packet_unhandled(buff, name) def packet_unhandled(self, buff, name): """ Called when a packet is received that is not hooked. The default implementation silently discards the packet. """ buff.discard() def send_packet(self, name, *data): """Sends a packet to the remote.""" if self.closed: return self.log_packet("# send", name) data = b"".join(data) # Prepend ident key = (self.protocol_version, self.protocol_mode, self.send_direction, name) try: ident = packets.packet_idents[key] except KeyError: raise ProtocolError("No ID known for packet: %s" % (key, )) data = Buffer.pack_varint(ident) + data if self.compression_enabled: # Compress data and prepend uncompressed data length if len(data) >= self.compression_threshold: data = Buffer.pack_varint(len(data)) + zlib.compress(data) else: data = Buffer.pack_varint(0) + data # Prepend packet length max_bits = 32 if self.protocol_mode == "play" else 21 data = self.buff_type.pack_varint(len(data), max_bits=max_bits) + data # Encrypt data = self.cipher.encrypt(data) # Send self.transport.write(data)
class Protocol(protocol.Protocol, PacketDispatcher, object): """Shared logic between the client and server""" #: Usually a reference to a :class:`Buffer` class. This is useful when #: constructing a packet payload for use in :meth:`send_packet` buff_type = None #: The logger for this protocol. logger = None #: A reference to a :class:`Ticker` instance. ticker = None #: A reference to the factory factory = None #: The IP address of the remote. remote_addr = None recv_direction = None send_direction = None protocol_version = packets.default_protocol_version protocol_mode = "init" compression_threshold = -1 in_game = False closed = False def __init__(self, factory, remote_addr): self.factory = factory self.remote_addr = remote_addr self.buff_type = self.factory.get_buff_type(self.protocol_version) self.recv_buff = self.buff_type() self.cipher = Cipher() self.logger = logging.getLogger("%s{%s}" % ( self.__class__.__name__, self.remote_addr.host)) self.logger.setLevel(self.factory.log_level) self.ticker = self.factory.ticker_type(self.logger) self.ticker.start() self.connection_timer = self.ticker.add_delay( delay=self.factory.connection_timeout / self.ticker.interval, callback=self.connection_timed_out) self.setup() # Fix ugly twisted methods ------------------------------------------------ def dataReceived(self, data): return self.data_received(data) def connectionMade(self): return self.connection_made() def connectionLost(self, reason=None): return self.connection_lost(reason) # Convenience functions --------------------------------------------------- def check_protocol_mode_switch(self, mode): transitions = [ ("init", "status"), ("init", "login"), ("login", "play") ] if (self.protocol_mode, mode) not in transitions: raise ProtocolError("Cannot switch protocol mode from %s to %s" % (self.protocol_mode, mode)) def switch_protocol_mode(self, mode): self.check_protocol_mode_switch(mode) self.protocol_mode = mode def set_compression(self, compression_threshold): self.compression_threshold = compression_threshold self.logger.debug("Compression threshold set to %d bytes" % compression_threshold) def close(self, reason=None): """Closes the connection""" if not self.closed: if reason: reason = "Closing connection: %s" % reason else: reason = "Closing connection" if self.in_game: self.logger.info(reason) else: self.logger.debug(reason) self.transport.loseConnection() self.closed = True def log_packet(self, prefix, name): """Logs a packet at debug level""" self.logger.debug("Packet %s %s/%s" % ( prefix, self.protocol_mode, name)) # General callbacks ------------------------------------------------------- def setup(self): """Called when the Protocol's initialiser is finished""" pass def protocol_error(self, err): """Called when a protocol error occurs""" self.logger.exception(err) self.close("Protocol error") # Connection callbacks ---------------------------------------------------- def connection_made(self): """Called when the connection is established""" self.logger.debug("Connection made") def connection_lost(self, reason=None): """Called when the connection is lost""" self.closed = True if self.in_game: self.player_left() self.logger.debug("Connection lost") self.ticker.stop() def connection_timed_out(self): """Called when the connection has been idle too long""" self.close("Connection timed out") # Auth callbacks ---------------------------------------------------------- def auth_ok(self, data): """Called when auth with mojang succeeded (online mode only)""" pass def auth_failed(self, err): """Called when auth with mojang failed (online mode only)""" self.logger.warning("Auth failed: %s" % err.value) self.close("Auth failed: %s" % err.value) # Player callbacks -------------------------------------------------------- def player_joined(self): """Called when the player joins the game""" self.in_game = True def player_left(self): """Called when the player leaves the game""" pass # Packet handling --------------------------------------------------------- def get_packet_name(self, ident): key = (self.protocol_version, self.protocol_mode, self.recv_direction, ident) try: return packets.packet_names[key] except KeyError: raise ProtocolError("No name known for packet: %s" % (key,)) def get_packet_ident(self, name): key = (self.protocol_version, self.protocol_mode, self.send_direction, name) try: return packets.packet_idents[key] except KeyError: raise ProtocolError("No ID known for packet: %s" % (key,)) def data_received(self, data): # Decrypt data data = self.cipher.decrypt(data) # Add it to our buffer self.recv_buff.add(data) # Read some packets while not self.closed: # Save the buffer, in case we read an incomplete packet self.recv_buff.save() # Read the packet try: buff = self.recv_buff.unpack_packet( self.buff_type, self.compression_threshold) except BufferUnderrun: self.recv_buff.restore() break try: # Identify the packet name = self.get_packet_name(buff.unpack_varint()) # Dispatch the packet try: self.packet_received(buff, name) except BufferUnderrun: raise ProtocolError("Packet is too short: %s" % name) if len(buff) > 0: raise ProtocolError("Packet is too long: %s" % name) # Reset the inactivity timer self.connection_timer.restart() except ProtocolError as e: self.protocol_error(e) def packet_received(self, buff, name): """ Called when a packet is received from the remote. Usually this method dispatches the packet to a method named ``packet_<packet name>``, or calls :meth:`packet_unhandled` if no such methods exists. You might want to override this to implement your own dispatch logic or logging. """ self.log_packet(". recv", name) dispatched = self.dispatch((name,), buff) if not dispatched: self.packet_unhandled(buff, name) def packet_unhandled(self, buff, name): """ Called when a packet is received that is not hooked. The default implementation silently discards the packet. """ buff.discard() def send_packet(self, name, *data): """Sends a packet to the remote.""" if self.closed: return self.log_packet("# send", name) data = b"".join(data) # Prepend ident data = self.buff_type.pack_varint(self.get_packet_ident(name)) + data # Pack packet data = self.buff_type.pack_packet(data, self.compression_threshold) # Encrypt data = self.cipher.encrypt(data) # Send self.transport.write(data)