#include #include #include #include #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(_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_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 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 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); }