#include "dvlnet/tcp_server.h" #include #include #include #include #include #include "dvlnet/base.h" #include "player.h" #include "utils/log.hpp" namespace devilution::net { tcp_server::tcp_server(asio::io_context &ioc, const std::string &bindaddr, unsigned short port, packet_factory &pktfty) : ioc(ioc) , pktfty(pktfty) { auto addr = asio::ip::make_address(bindaddr); auto ep = asio::ip::tcp::endpoint(addr, port); acceptor = std::make_unique(ioc, ep, true); StartAccept(); } std::string tcp_server::LocalhostSelf() { auto addr = acceptor->local_endpoint().address(); if (addr.is_unspecified()) { if (addr.is_v4()) { return asio::ip::address_v4::loopback().to_string(); } if (addr.is_v6()) { return asio::ip::address_v6::loopback().to_string(); } ABORT(); } return addr.to_string(); } tcp_server::scc tcp_server::MakeConnection() { return std::make_shared(ioc); } plr_t tcp_server::NextFree() { for (plr_t i = 0; i < Players.size(); ++i) if (!connections[i]) return i; return PLR_BROADCAST; } bool tcp_server::Empty() { for (plr_t i = 0; i < Players.size(); ++i) if (connections[i]) return false; return true; } void tcp_server::StartReceive(const scc &con) { con->socket.async_receive( asio::buffer(con->recv_buffer), std::bind(&tcp_server::HandleReceive, this, con, std::placeholders::_1, std::placeholders::_2)); } void tcp_server::HandleReceive(const scc &con, const asio::error_code &ec, size_t bytesRead) { if (ec || bytesRead == 0) { DropConnection(con); return; } con->recv_buffer.resize(bytesRead); con->recv_queue.Write(std::move(con->recv_buffer)); con->recv_buffer.resize(frame_queue::max_frame_size); while (true) { tl::expected ready = con->recv_queue.PacketReady(); if (!ready.has_value()) { Log("PacketReady: {}", ready.error().what()); DropConnection(con); return; } if (!*ready) break; tl::expected pktData = con->recv_queue.ReadPacket(); if (!pktData.has_value()) { Log("ReadPacket: {}", pktData.error().what()); DropConnection(con); return; } tl::expected, PacketError> pkt = pktfty.make_packet(*pktData); if (!pkt.has_value()) { Log("make_packet: {}", pkt.error().what()); if (pkt.error().code() == PacketError::ErrorCode::DecryptionFailed) StartSend(con, pkt.error().code()); DropConnection(con); return; } if (con->plr == PLR_BROADCAST) { tl::expected result = HandleReceiveNewPlayer(con, **pkt); if (!result.has_value()) { Log("HandleReceiveNewPlayer: {}", result.error().what()); DropConnection(con); return; } } else { con->timeout = timeout_active; tl::expected result = HandleReceivePacket(**pkt); if (!result.has_value()) { Log("Network error: {}", result.error().what()); DropConnection(con); return; } } } StartReceive(con); } tl::expected tcp_server::HandleReceiveNewPlayer(const scc &con, packet &inPkt) { auto newplr = NextFree(); if (newplr == PLR_BROADCAST) return tl::make_unexpected(ServerError()); if (Empty()) { tl::expected pktInfo = inPkt.Info(); if (!pktInfo.has_value()) return tl::make_unexpected(pktInfo.error()); game_init_info = **pktInfo; } for (plr_t player = 0; player < Players.size(); player++) { if (connections[player]) { tl::expected result = pktfty.make_packet(PLR_MASTER, PLR_BROADCAST, newplr) .and_then([&](std::unique_ptr &&pkt) { return StartSend(connections[player], *pkt); }) .and_then([&]() { return pktfty.make_packet(PLR_MASTER, PLR_BROADCAST, player); }) .and_then([&](std::unique_ptr &&pkt) { return StartSend(con, *pkt); }); if (!result.has_value()) return result; } } tl::expected result = inPkt.Cookie() .and_then([&](cookie_t &&cookie) { return pktfty.make_packet(PLR_MASTER, PLR_BROADCAST, cookie, newplr, game_init_info); }) .and_then([&](std::unique_ptr &&pkt) { return StartSend(con, *pkt); }); if (!result.has_value()) return result; con->plr = newplr; connections[newplr] = con; con->timeout = timeout_active; return {}; } tl::expected tcp_server::HandleReceivePacket(packet &pkt) { return SendPacket(pkt); } tl::expected tcp_server::SendPacket(packet &pkt) { if (pkt.Destination() == PLR_BROADCAST) { for (size_t i = 0; i < Players.size(); ++i) { if (i == pkt.Source() || !connections[i]) continue; tl::expected result = StartSend(connections[i], pkt); if (!result.has_value()) LogError("Failed to send packet {} to player {}: {}", static_cast(pkt.Type()), i, result.error().what()); } return {}; } if (pkt.Destination() >= MAX_PLRS) return tl::make_unexpected(ServerError()); if (pkt.Destination() == pkt.Source() || !connections[pkt.Destination()]) return {}; return StartSend(connections[pkt.Destination()], pkt); } tl::expected tcp_server::StartSend(const scc &con, packet &pkt) { return StartSend(con, pkt.Data(), 0); } tl::expected tcp_server::StartSend(const scc &con, PacketError::ErrorCode errorCode) { buffer_t pktData; pktData.push_back(static_cast(errorCode)); return StartSend(con, pktData, TcpErrorCodeFlags); } tl::expected tcp_server::StartSend(const scc &con, buffer_t pktData, uint16_t flags) { tl::expected frame = frame_queue::MakeFrame(pktData, flags); if (!frame.has_value()) return tl::make_unexpected(frame.error()); std::unique_ptr framePtr = std::make_unique(*frame); const asio::mutable_buffer buf = asio::buffer(*framePtr); asio::async_write(con->socket, buf, [this, con, frame = std::move(framePtr)](const asio::error_code &ec, size_t bytesSent) { HandleSend(con, ec, bytesSent); }); return {}; } void tcp_server::HandleSend(const scc &con, const asio::error_code &ec, size_t bytesSent) { if (ec) { Log("Network error: {}", ec.message()); DropConnection(con); } } void tcp_server::StartAccept() { auto nextcon = MakeConnection(); acceptor->async_accept( nextcon->socket, std::bind(&tcp_server::HandleAccept, this, nextcon, std::placeholders::_1)); } void tcp_server::HandleAccept(const scc &con, const asio::error_code &ec) { if (ec) { const PacketError packetError = IoHandlerError(ec.message()); RaiseIoHandlerError(packetError); return; } if (NextFree() == PLR_BROADCAST) { DropConnection(con); } else { asio::error_code errorCode; const asio::ip::tcp::no_delay option(true); con->socket.set_option(option, errorCode); if (errorCode) LogError("Server error setting socket option: {}", errorCode.message()); con->timeout = timeout_connect; StartReceive(con); StartTimeout(con); } StartAccept(); } void tcp_server::StartTimeout(const scc &con) { con->timer.expires_after(std::chrono::seconds(1)); con->timer.async_wait( std::bind(&tcp_server::HandleTimeout, this, con, std::placeholders::_1)); } void tcp_server::HandleTimeout(const scc &con, const asio::error_code &ec) { if (ec) { DropConnection(con); return; } if (con->timeout > 0) con->timeout -= 1; if (con->timeout <= 0) { con->timeout = 0; DropConnection(con); return; } StartTimeout(con); } void tcp_server::DropConnection(const scc &con) { const plr_t plr = con->plr; con->timer.cancel(); con->socket.close(); if (plr == PLR_BROADCAST) { return; } connections[plr] = nullptr; tl::expected, PacketError> pkt = pktfty.make_packet(PLR_MASTER, PLR_BROADCAST, plr, leaveinfo_t::LEAVE_DROP); if (pkt.has_value()) { SendPacket(**pkt); } else { LogError("make_packet: {}", pkt.error().what()); } } void tcp_server::RaiseIoHandlerError(const PacketError &error) { ioHandlerResult.emplace(error); } tl::expected tcp_server::CheckIoHandlerError() { if (ioHandlerResult == std::nullopt) return {}; tl::expected packetError = tl::make_unexpected(*ioHandlerResult); ioHandlerResult = std::nullopt; return packetError; } void tcp_server::DisconnectNet(plr_t plr) { scc &con = connections[plr]; if (con == nullptr) return; con->timer.cancel(); con->socket.close(); con = nullptr; } void tcp_server::Close() { acceptor->close(); } tcp_server::~tcp_server() = default; } // namespace devilution::net