#include "Connection.hpp" #include "World.hpp" #include #include #include namespace Connection { asio::awaitable Client::receivePacket() { Packet packet; // Receive operation packet.op = co_await receiveProtobuf(); // Receive data switch (packet.op.code()) { case Generic::Operation::Error: { packet.data = std::make_unique(co_await receiveProtobuf()); } break; case Generic::Operation::SimpleAuth: { packet.data = std::make_unique(co_await receiveProtobuf(30)); } break; case Generic::Operation::RobotUpdate: { packet.data = std::make_unique(co_await receiveProtobuf()); } break; case Generic::Operation::UserInfoSync: case Generic::Operation::MakeRoom: case Generic::Operation::JoinRandomRoom: case Generic::Operation::Disconnect: case Generic::Operation::OK: break; default: throw FatalError("Invalid opcode: "+std::to_string(packet.op.code()), Generic::Error::InvalidOpcode); } } asio::awaitable Client::sendProtobuf(const google::protobuf::Message& buffer) { // Send size uint16_t size = buffer.ByteSizeLong(); co_await asio::async_write(socket, asio::buffer(&size, sizeof(size)), asio::use_awaitable); // Send buffer std::vector rawBuffer(size); buffer.SerializeToArray(rawBuffer.data(), rawBuffer.size()); co_await asio::async_write(socket, asio::buffer(rawBuffer.data(), rawBuffer.size()), asio::use_awaitable); } asio::awaitable Client::sendPacket(const Packet& packet) { // Send operation co_await sendProtobuf(packet.op); // Send data if (packet.data) { co_await sendProtobuf(*packet.data); } } asio::awaitable Client::handlePacket(const Packet& packet) { // Check if authentication is required and completed if (packet.op.code() >= Generic::Operation::_MinAuth && !isAuthed()) { throw FatalError("Authentication required", Generic::Error::AuthenticationRequired); } // Handle the packet switch (packet.op.code()) { case Generic::Operation::Disconnect: { good = 0; } break; case Generic::Operation::SimpleAuth: { const Generic::SimpleAuth& authData = *static_cast(packet.data.get()); // Check that username is not already taken if (std::find_if(global.clients.begin(), global.clients.end(), [&, this] (const auto& o) {return o->userInfo.publicinfo().name() == authData.username();}) != global.clients.end()) { throw Error("Username has already been taken", Generic::Error::UsernameAlreadyTaken); } // Check that username is "good" (no unprintable characters and stuff like that) //TODO... // Set username userInfo.mutable_publicinfo()->set_name(std::move(authData.username())); // Set temporary user and client id auto userRef = userInfo.mutable_publicinfo()->mutable_reference(); userRef->set_userid(global.getTemporaryId()); userRef->set_clientid(global.getTemporaryId()); // Mark as authed authed = true; // Send user info Packet userInfoSync; userInfoSync.op.set_code(Generic::Operation::UserInfoSync); userInfoSync.data = std::make_unique(userInfo); } break; case Generic::Operation::MakeRoom: { // Create room auto room = std::make_shared(global); global.rooms.push_back(room); co_await room->addClient(shared_from_this()); } break; case Generic::Operation::JoinRandomRoom: { // Get random room auto room = global.getRandomRoom(); // Check that room was found if (!room) { throw Error("No available room could be found", Generic::Error::RoomNotAvailable); } // Add client to it co_await room->addClient(shared_from_this()); } break; case Generic::Operation::LeaveRoom: { // Check that client is in any room if (!currentRoom) { throw Error("You are not in any room", Generic::Error::IllegalOperation); } // Remove client from room co_await currentRoom->removeClient(shared_from_this()); } break; default: {} } // Send OK co_await sendOK(); } asio::awaitable Client::connect() { // Send initial "OK" try { co_await sendOK(); } catch (...) { good = false; } // Run while connection is good while (good) { std::optional error; try { co_await handlePacket(co_await receivePacket()); } catch (FatalError& e) { error = std::move(e); good = false; } catch (Error& e) { error = std::move(e); } catch (std::exception& e) { error = Error("Internal server error: "+std::string(e.what()), Generic::Error::Unknown); good = false; } if (error.has_value()) { co_await sendPacket(error.value().packet); } } // Try to send disconnect try { Packet packet; packet.op.set_code(Generic::Operation::Disconnect); co_await sendPacket(packet); } catch (...) {} // Close socket boost::system::error_code ec; socket.close(ec); } std::shared_ptr Global::getRandomRoom() const { //TODO: Real rng for (const auto& room : rooms) { if (room->isOpen()) { return room; } } return nullptr; } asio::awaitable Server::listen(int port) { try { // Create acceptor asio::ip::tcp::acceptor acceptor(ioc, asio::ip::tcp::endpoint(asio::ip::tcp::v4(), port)); // Handle all incoming connections while (true) { auto client = std::make_shared(ioc, global); co_await acceptor.async_accept(client->getSocket(), asio::use_awaitable); asio::co_spawn(ioc, client->connect(), asio::detached); global.clients.push_back(std::move(client)); } } catch (std::exception& e) { std::cerr << "Failed to stay alive: " << e.what() << std::endl; } } }