bettola/server/src/network_manager.cpp
Ritchie Cunningham cf48324516 [Refactor] Givin' the client some luv!
Refactored client-side rendering code to use a Color struct rather than
raw float arrays.
2025-09-27 22:58:04 +01:00

134 lines
4.3 KiB
C++

#include <cstdio>
#include <exception>
#include <functional>
#include <memory>
#include "network_manager.h"
#include "asio/error_code.hpp"
#include "asio/ip/tcp.hpp"
#include "command_processor.h"
#include "player.h"
#include "machine.h"
#include "net/tcp_connection.h"
#include "vfs.h"
NetworkManager::NetworkManager(void) : _acceptor(_io_context) {
/* World setup. */
_world_machines["8.8.8.8"] = _machine_manager.create_machine(1000, "dns.google", "npc");
_world_machines["10.0.2.15"] = _machine_manager.create_machine(1001, "corp.internal", "npc");
/* Specific npc services. */
_world_machines["8.8.8.8"]->services[80] = "HTTPD v2.4";
_world_machines["10.0.2.15"]->services[21] = "FTPd v3.0";
fprintf(stderr, "Created world with %zu networks\n", _world_machines.size());
}
NetworkManager::~NetworkManager(void) {
for(auto const& [ip, machine] : _world_machines) {
delete machine;
}
stop();
}
void NetworkManager::stop(void) {
_io_context.stop();
if(_context_thread.joinable()) {
_context_thread.join();
}
}
void NetworkManager::start(uint16_t port) {
try {
asio::ip::tcp::endpoint endpoint(asio::ip::tcp::v4(), port);
_acceptor.open(endpoint.protocol());
_acceptor.set_option(asio::ip::tcp::acceptor::reuse_address(true));
_acceptor.bind(endpoint);
_acceptor.listen();
fprintf(stderr, "BettolaServer started on port %d\n", port);
start_accept();
/* Run io_context in its own thread so it doesn't block main. */
_context_thread = std::thread([this]() { _io_context.run(); });
} catch (const std::exception& e) {
fprintf(stderr, "BettolaServer exception: %s\n", e.what());
}
}
void NetworkManager::start_accept(void) {
_acceptor.async_accept(
[this](const asio::error_code& ec, asio::ip::tcp::socket socket) {
if(!ec) {
fprintf(stderr, "New connection from: %s\n",
socket.remote_endpoint().address().to_string().c_str());
auto new_connection =
std::make_shared<net::TcpConnection>(_io_context, std::move(socket));
/* Create a new player for this connection. */
uint32_t player_id = _next_player_id++;
Machine* player_machine = _machine_manager.create_machine(player_id, "player.home",
"player");
auto new_player = std::make_unique<Player>(player_id, player_machine, _world_machines);
Player* new_player_ptr = new_player.get();
_players[player_id] = std::move(new_player);
new_connection->set_id(player_id);
/* Callback for new connection. Capture new_connection's shared_ptr
* to keep it alive and ident it.
*/
new_connection->start(
[this, new_connection](const std::string& msg) {
this->on_message(new_connection, msg);
},
[this, new_connection]() { this->on_disconnect(new_connection); });
/* Send initial prompt. */
std::string prompt = "\n" + get_full_path(new_player_ptr->cmd_processor->get_current_dir());
new_connection->send(prompt);
_connections.push_back(new_connection);
} else {
fprintf(stderr, "Accept error: %s\n", ec.message().c_str());
}
/* Continue listening for the next connection. */
start_accept();
});
}
void NetworkManager::on_message(std::shared_ptr<net::TcpConnection> connection,
const std::string& message) {
Player* player = _players[connection->get_id()].get();
if(!player) {
fprintf(stderr, "Error: Receiving message from unknown player.\n");
return;
}
fprintf(stderr, "[Player %u] Command: '%s'\n", player->id, message.c_str());
std::string response = player->cmd_processor->process_command(message);
if(response == "__CLOSE_CONNECTION__") {
connection->send(response);
/* Just let me close the f.cking terminal? */
return;
}
std::string new_prompt = get_full_path(player->cmd_processor->get_current_dir());
response += "\n" + new_prompt;
connection->send(response);
}
void NetworkManager::on_disconnect(std::shared_ptr<net::TcpConnection> connection) {
uint32_t player_id = connection->get_id();
fprintf(stderr, "[Player %u] Disconnected.\n", player_id);
_connections.erase(
std::remove(_connections.begin(), _connections.end(), connection), _connections.end());
_players.erase(player_id);
}