diff --git a/aiocraft/server.py b/aiocraft/server.py index 0c61286..3a95d1b 100644 --- a/aiocraft/server.py +++ b/aiocraft/server.py @@ -2,7 +2,7 @@ import asyncio import logging import uuid -from asyncio import Task +from asyncio import Task, StreamReader, StreamWriter, Server from enum import Enum from typing import Dict, List, Callable, Type, Optional, Tuple, AsyncIterator @@ -29,6 +29,7 @@ class Server: _dispatcher_pool : List[Dispatcher] _processing : bool + _server : Server _worker : Task _callbacks = Dict[str, Task] @@ -44,15 +45,11 @@ class Server: self.port = port self.options = options or { - "reconnect" : True, - "rctime" : 5.0, - "keep-alive" : True, - "poll-timeout" : 1, + "online-mode" : False, } self._dispatcher_pool = [] self._processing = False - self._authenticated = False self._logger = LOGGER.getChild(f"@({self.host}:{self.port})") @@ -79,49 +76,108 @@ class Server: self._logger.info("Received SIGINT, stopping for real") loop.run_until_complete(self.stop(wait_tasks=False)) - async def start(self): + async def start(self, block=False): + self._server = await asyncio.start_server( + self._server_worker, self.host, self.port + ) + + addrs = ', '.join(str(sock.getsockname()) for sock in server.sockets) + print(f'Serving on {addrs}') + self._processing = True - self._worker = asyncio.get_event_loop().create_task(self._server_worker()) - self._logger.info("Minecraft server started") + async with self._server: + self._logger.info("Minecraft server started") + if block: + await self._server.serve_forever() + else: + await self._server.start_serving() async def stop(self, block=True, wait_tasks=True): self._processing = False if self.dispatcher.connected: await self.dispatcher.disconnect(block=block) + self._server.close() if block: - await self._worker - self._logger.info("Minecraft server stopped") - if block and wait_tasks: - await asyncio.gather(*list(self._callbacks.values())) + await self._server.wait_closed() + # if block and wait_tasks: # TODO wait for client workers + # await asyncio.gather(*list(self._callbacks.values())) - async def _server_worker(self): - while self._processing: - try: - await self.authenticate() - except AuthException as e: - self._logger.error(str(e)) - break - try: - await self.dispatcher.connect(self.host, self.port) - await self._handshake() - if await self._login(): - await self._play() - except ConnectionRefusedError: - self._logger.error("Server rejected connection") - except Exception: - self._logger.exception("Exception in Client connection") - if self.dispatcher.connected: - await self.dispatcher.disconnect() - if not self.options["reconnect"]: - break - await asyncio.sleep(self.options["rctime"]) - await self.stop(block=False) + async def _disconnect_client(self, dispatcher): + if dispatcher.state == ConnectionState.LOGIN: + await dispatcher.write(PacketDisconnect(dispatcher.proto, reason="Connection terminated")) + else: + await dispatcher.write(PacketKickDisconnect(dispatcher.proto, reason="Connection terminated")) - async def _handshake(self) -> bool: # TODO make this fancier! poll for version and status first - pass + async def _server_worker(self, reader:StreamReader, writer:StreamWriter): + dispatcher = Dispatcher() - async def _login(self) -> bool: - pass + self._logger.debug("Starting dispatcher for client") + await dispatcher.connect( + host=self.host, + port=self.port, + reader=reader, + writer=writer, + ) + + await self._handshake(dispatcher) + if dispatcher.state == ConnectionState.STATUS: + await self._status(dispatcher) + elif dispatcher.state == ConnectionState.LOGIN: + if await self._login(dispatcher): + await self._play(dispatcher) + + if dispatcher.connected: + await self._disconnect_client(dispatcher) + await dispatcher.disconnect() + + async def _handshake(self, dispatcher:Dispatcher) -> bool: # TODO make this fancier! poll for version and status first + async for packet in dispatcher.packets(): + if isinstance(packet, PacketSetProtocol): + dispatcher.proto = packet.protocolVersion + if packet.nextState == ConnectionState.STATUS: + dispatcher.state = ConnectionState.STATUS + return True + elif packet.nextState == ConnectionState.LOGIN: + dispatcher.state = ConnectionState.LOGIN + return True + return False + + async def _status(self, dispatcher:Dispatcher) -> bool: + async for packet in dispatcher.packets(): + pass # TODO handle status! + return False + + async def _login(self, dispatcher:Dispatcher) -> bool: + async for packet in dispatcher.packets(): + if isinstance(packet, PacketLoginStart): + if self.options["online-mode"]: + # await dispatcher.write( + # PacketEncryptionBegin( + # dispatcher.proto, + # serverId="????", + # publicKey=b"??????", + # verifyToken=b"1234", + # ) + # ) + pass + else: + await dispatcher.write( + PacketSuccess( + dispatcher.proto, + uuid=packet.username, + username=packet.username, + ) + ) + return True + elif isinstance(packet, PacketEncryptionResponse): + shared_secret = packet.sharedSecret + verify_token = packet.verifyToken + # TODO enable encryption? + # return True + return False + + async def _play(self, dispatcher:Dispatcher) -> bool: + async for packet in dispatcher.packets(): + pass # TODO handle play + return False - async def _play(self) -> bool: - pass