177 lines
6.2 KiB
C++
177 lines
6.2 KiB
C++
#include "Connection.hpp"
|
|
#include "World.hpp"
|
|
|
|
#include <optional>
|
|
#include <algorithm>
|
|
#include <boost/asio.hpp>
|
|
|
|
|
|
|
|
namespace Connection {
|
|
asio::awaitable<Packet> Client::receivePacket() {
|
|
Packet packet;
|
|
// Receive operation
|
|
packet.op = co_await receiveProtobuf<Generic::Operation>();
|
|
// Receive data
|
|
switch (packet.op.code()) {
|
|
case Generic::Operation::Error: {
|
|
packet.data = std::make_unique<Generic::Error>(co_await receiveProtobuf<Generic::Error>());
|
|
} break;
|
|
case Generic::Operation::SimpleAuth: {
|
|
packet.data = std::make_unique<Generic::SimpleAuth>(co_await receiveProtobuf<Generic::SimpleAuth>(30));
|
|
} break;
|
|
case Generic::Operation::RobotUpdate: {
|
|
packet.data = std::make_unique<Generic::Robot>(co_await receiveProtobuf<Generic::Robot>());
|
|
} 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<void> 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<char> 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<void> Client::sendPacket(const Packet& packet) {
|
|
// Send operation
|
|
co_await sendProtobuf(packet.op);
|
|
// Send data
|
|
if (packet.data) {
|
|
co_await sendProtobuf(*packet.data);
|
|
}
|
|
}
|
|
|
|
asio::awaitable<void> 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<Generic::SimpleAuth*>(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<Generic::UserInfo>(userInfo);
|
|
} break;
|
|
case Generic::Operation::MakeRoom: {
|
|
// Create room
|
|
auto room = std::make_shared<World::Room>(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<void> Client::connect() {
|
|
// Send initial "OK"
|
|
try {
|
|
co_await sendOK();
|
|
} catch (...) {
|
|
good = false;
|
|
}
|
|
// Run while connection is good
|
|
while (good) {
|
|
std::optional<Error> 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<World::Room> Global::getRandomRoom() const {
|
|
//TODO: Real rng
|
|
for (const auto& room : rooms) {
|
|
if (room->isOpen()) {
|
|
return room;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
asio::awaitable<void> 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<Client>(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;
|
|
}
|
|
}
|
|
}
|