Example #1
0
	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()
Example #2
0
    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
Example #4
0
    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()
Example #5
0
    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()
Example #6
0
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))
Example #8
0
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)
Example #9
0
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)