From 6f7b994684032060f0a2d7af82f4f3572293b8d6 Mon Sep 17 00:00:00 2001 From: littleblack111 Date: Thu, 12 Jun 2025 21:41:41 +0800 Subject: internal!: organized internal structure, using cmake now --- src/core/session.cpp | 273 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 273 insertions(+) create mode 100644 src/core/session.cpp (limited to 'src/core/session.cpp') diff --git a/src/core/session.cpp b/src/core/session.cpp new file mode 100644 index 0000000..7b0b459 --- /dev/null +++ b/src/core/session.cpp @@ -0,0 +1,273 @@ +#include "session.hpp" +#include "chatManager.hpp" +#include "../command/commandHandler.hpp" +#include "../debug/format.hpp" +#include "../debug/log.hpp" +#include "server.hpp" +#include "sessionManager.hpp" +#include +#include +#include +#include +#include +#include +#include + +CSession::CSession() { + m_sockfd = std::make_shared(accept(g_pServer->getSocket()->get(), reinterpret_cast(&m_addr), &m_addrLen)); // if this is in the init list, it will run before m_addrLen, so it won't work :/ + + if (!m_sockfd->isValid()) + throw std::runtime_error("session: Failed to create socket"); + + m_ip.resize(INET_ADDRSTRLEN); + inet_ntop(AF_INET, &m_addr.sin_addr, &m_ip[0], INET_ADDRSTRLEN); + m_ip.resize(strlen(m_ip.c_str())); + m_port = ntohs(m_addr.sin_port); + m_isAdmin = std::ranges::any_of(m_adminIps, [this](const char *ip) { return m_ip == ip; }); + + log(LOG, "Client {} connected on port {}", m_ip, m_port); + + log(TRACE, "session: initialized"); +} + +CSession::~CSession() { +#ifdef DEBUG + onDisconnect(); +#endif + m_sockfd.reset(); + + log(LOG, "Client {}@{} disconnected", m_name, m_ip); + + log(TRACE, "session({}@{}): bye", m_name, m_ip); +} + +#ifdef DEBUG +void CSession::onConnect() { + log(TRACE, "Client {}@{} connected", m_name, m_ip); +} + +void CSession::onDisconnect() { + log(TRACE, "Client {}@{} disconnected", m_name, m_ip); +} +#endif + +void CSession::onErrno(eEventType eventType) { + char errbuf[256]; + const char *err = strerror_r(errno, errbuf, sizeof(errbuf)); + + switch (eventType) { + case READ: + log(WARN, "Error while reading: {}", err); + break; + case WRITE: + log(WARN, "Error while writing: {}", err); + break; + } +} + +#ifdef DEBUG +void CSession::onRecv(const SRecvData &data) { + log(TRACE, "Received: {}", data.data); +} + +void CSession::onSend(const std::string &msg) { + log(LOG, "Sent: {}", msg); +} +#endif + +void CSession::recvLoop() { + while (true) { + auto recvData = read(NFormatter::fmt(NONEWLINE, "{}: ", m_name)); + if (!recvData->good) + break; + + recvData->sanitize(); + if (recvData->isEmpty()) { + log(TRACE, "Empty message from {}", m_name); + continue; + } + + const auto isCommand = g_pCommandHandler->isCommand(recvData->data); + if (isCommand) + g_pCommandHandler->handleCommand(recvData->data, self.lock()); + + g_pChatManager->newMessage({.msg = recvData->data, .username = m_name, .sender = self, .admin = isCommand}); + } +} + +bool CSession::SRecvData::isEmpty() const { + return data.empty() || std::ranges::all_of(data, [](char c) { return std::isspace(c); }); +} + +void CSession::SRecvData::sanitize() { + if (!data.empty() && data.back() == '\n') + data.pop_back(); + + data.erase(std::remove(data.begin(), data.end(), asciiEscape), data.end()); // don't accept ASCII code, might mess up terminal // NOLINT + + if (const auto start = data.find_first_not_of(" \t\r\n"); start != std::string::npos) { + const auto end = data.find_last_not_of(" \t\r\n"); + data = data.substr(start, end - start + 1); + } else + data.clear(); +} + +std::unique_ptr CSession::read() { + auto recvData = std::make_unique(); + + recvData->data.resize(recvData->size); + ssize_t size = recv(m_sockfd->get(), &recvData->data[0], recvData->size, 0); + + if (size < 0) { + recvData->good = false; + onErrno(READ); + } else if (size == 0) + recvData->good = false; + else { + recvData->data.resize(size); + recvData->good = true; + } + +#ifdef DEBUG + onRecv(*recvData); +#endif + m_szReading.reset(); + return recvData; +} + +std::unique_ptr CSession::read(const std::string &msg, bool bypassDeaf) { + if (bypassDeaf && m_bDeaf) + m_bDeaf = false; + write(NONEWLINE, "{}", msg); + if (bypassDeaf && !m_bDeaf) + m_bDeaf = true; + m_szReading = msg; + return read(); +} + +void CSession::run() { +#ifdef DEBUG + onConnect(); +#endif + if (registerSession()) { + for (const auto &chat : g_pChatManager->getChat()) + writeChat(chat); + recvLoop(); + } else + log(LOG, "Client {} exited without even registering", m_ip); + + g_pSessionManager->kick(self); +} + +bool CSession::isMuted() const { + return m_bMuted; +} + +void CSession::setMuted(bool mute) { + m_bMuted = mute; +} + +bool CSession::isDeaf() const { + return m_bDeaf; +} + +void CSession::setDeaf(bool deaf) { + m_bDeaf = deaf; +} + +bool CSession::registerSession() { + auto recvData = read("Name: ", true); + if (!recvData->good) + return false; + + recvData->sanitize(); + if (recvData->isEmpty()) { + m_bDeaf = false; + write("Name cannot be empty"); + m_bDeaf = true; + return registerSession(); + } + + if (g_pSessionManager->nameExists(recvData->data)) { + m_bDeaf = false; + write("Name already exists"); + m_bDeaf = true; + return registerSession(); + } + + this->m_name = recvData->data; + + log(LOG, "Client {} registered as: {}", m_ip, m_name); + + g_pSessionManager->broadcast(NFormatter::fmt(NONEWLINE, "{} has joined the chat", m_name), self); + + m_bDeaf = false; + return true; +} + +bool CSession::isValid() { + if (!m_sockfd->isValid() || m_name.empty()) + return false; + + int err = 0; + socklen_t size = sizeof(err); + return getsockopt(m_sockfd->get(), SOL_SOCKET, SO_ERROR, &err, &size) >= 0 && err == 0; +} + +bool CSession::write(const std::string &msg) { + return write("{}", msg); +} + +bool CSession::writeChat(const CChatManager::SMessage &msg) { + const auto sender = msg.sender ? msg.sender->lock() : nullptr; + if (sender ? sender != self.lock() : msg.username != m_name) { + if (msg.admin && !m_isAdmin) + return false; + + return write(g_pChatManager->fmtBroadcastMessage(msg)); + } + return false; +} + +const std::string &CSession::getName() const { + return m_name; +} + +const std::string &CSession::getIp() const { + return m_ip; +} + +bool CSession::isAdmin() const { + return m_isAdmin; +} + +template +bool CSession::write(std::format_string fmt, Args &&...args) { + if (m_bDeaf) + return false; + return write(NONE, fmt, std::forward(args)...); +} + +template +bool CSession::write(eFormatType type, std::format_string fmt, Args &&...args) { + if (m_bDeaf) + return false; + + std::string msg = NFormatter::fmt(type, fmt, std::forward(args)...); + if (m_szReading) { + msg.insert(0, "\r"); + msg.append(*m_szReading); + } + + if (send(m_sockfd->get(), msg.c_str(), msg.size(), 0) < 0) { + onErrno(WRITE); + return false; + } +#ifdef DEBUG + onSend(msg); +#endif + return true; +} + +template bool CSession::write(std::format_string, const std::string &); +template bool CSession::write(eFormatType, std::format_string, const std::string &); -- cgit v1.2.3