From 7e694e134c35549bd26ba8919ab97f2680d07211 Mon Sep 17 00:00:00 2001 From: rhaas Date: Thu, 30 Jul 2026 17:20:21 +0200 Subject: [PATCH 001/349] Div. memos WIP --- memo/bind_socket.md | 59 +++++++++++++++ memo/ft_irc_en.subject.pdf | Bin 0 -> 1363881 bytes memo/memo.md | 80 ++++++++++++++++++++ memo/poll.md | 147 +++++++++++++++++++++++++++++++++++++ memo/socket.md | 32 ++++++++ 5 files changed, 318 insertions(+) create mode 100644 memo/bind_socket.md create mode 100644 memo/ft_irc_en.subject.pdf create mode 100644 memo/memo.md create mode 100644 memo/poll.md create mode 100644 memo/socket.md diff --git a/memo/bind_socket.md b/memo/bind_socket.md new file mode 100644 index 0000000..a037bf6 --- /dev/null +++ b/memo/bind_socket.md @@ -0,0 +1,59 @@ +# bind a socket to a single address, multiple addresses and/or sockets on linux - what are the implications? + +Binding a socket on Linux involves critical trade-offs between **accessibility**, **security**, and **concurrency**. The implications depend heavily on the specific IP address chosen and the socket options used. + +### 1. Binding to a Single Specific Address (e.g., `192.168.1.5`) +Binding to a specific interface IP restricts the socket to only accept connections arriving on that specific network interface. + +* **Security Implication**: This significantly **reduces the attack surface**. If the machine has a public interface (eth0) and a private management interface (eth1), binding only to `eth1` ensures the service is unreachable from the internet, even if firewall rules are misconfigured. +* **Routing Dependency**: The service will fail to start if the specific IP is not currently assigned to an interface (e.g., DHCP failure or interface down). +* **Isolation**: Useful for multi-tenant servers where different services must listen on the same port but on different IPs (e.g., Virtual Hosting). + +### 2. Binding to Multiple Addresses (Wildcard `0.0.0.0` or `::`) +Binding to `0.0.0.0` (IPv4) or `::` (IPv6) instructs the kernel to listen on **all available network interfaces**. + +* **Security Implication**: This **maximizes the attack surface**. The service becomes accessible from every interface: loopback (`127.0.0.1`), LAN, and potentially the public internet. + * **Risk**: Accidental exposure of internal-only services (like databases or debug ports) to the public network is a common vulnerability. + * **Mitigation**: Requires strict firewall rules (`iptables`/`nftables`) to block unwanted interfaces, as the application itself accepts traffic from anywhere. +* **Convenience**: Ideal for services intended to be universally accessible (e.g., a public web server) or in dynamic IP environments (DHCP) where the specific IP might change. +* **IPv6 Dual-Stack**: On Linux, binding to `::` often listens on both IPv4 and IPv6 (depending on `net.ipv6.bindv6only`), which can lead to unexpected behavior if not managed. + +### 3. Binding Multiple Sockets to the Same Port +By default, Linux enforces exclusive binding: only one socket can bind to a specific `IP:Port` tuple. Attempting to bind a second socket results in `EADDRINUSE`. However, specific socket options alter this behavior. + +#### A. `SO_REUSEADDR` (Address Reuse) +* **Function**: Allows a new socket to bind to a port that is in `TIME_WAIT` state (recently closed). It also allows binding to the same port on **different** specific IP addresses (e.g., `192.168.1.5:80` and `192.168.1.6:80`). +* **Implication**: It does **not** allow two processes to bind to the exact same `IP:Port` combination simultaneously for load balancing (with the exception of UDP multicast). +* **Use Case**: Essential for high-availability servers that need to restart instantly without waiting for the TCP `TIME_WAIT` period (typically 60-120 seconds) to expire. + +#### B. `SO_REUSEPORT` (Port Reuse - Linux 3.9+) +* **Function**: Allows **multiple sockets** (in different processes or threads) to bind to the **exact same** `IP:Port` combination, provided **all** of them set this option before binding. +* **Load Balancing**: The kernel performs load balancing, distributing incoming connections across the bound sockets. This avoids the "thundering herd" problem where one process accepts and distributes work. +* **Security Implication**: + * **Port Hijacking**: Without proper checks, a malicious process could bind to a privileged port and steal connections. Linux mitigates this by requiring all processes in the `SO_REUSEPORT` group to have the **same Effective User ID (UID)**. + * **Risk**: If an attacker gains access to the same user account, they can inject a socket into the group and intercept traffic (e.g., capturing unencrypted TLS handshakes). + + + +### Summary of Implications + +| Binding Strategy | Accessibility | Security Posture | Concurrency | Primary Use Case | +| :--- | :--- | :--- | :--- | :--- | +| **Specific IP** | Restricted to one interface | **High** (Minimal exposure) | Single Socket | Internal services, multi-IP hosting | +| **Wildcard (`0.0.0.0`)** | All interfaces (Public + Private) | **Low** (Requires firewall) | Single Socket | Public-facing services | +| **`SO_REUSEADDR`** | Depends on IP bound | Neutral | Sequential (Time-wait bypass) | Fast server restarts | +| **`SO_REUSEPORT`** | Depends on IP bound | **Moderate** (UID check required) | **Parallel** (Kernel LB) | Multi-process servers (Nginx, Go) | + +### Best Practices +1. **Default to Specific IPs**: Unless the service must be public, bind to `127.0.0.1` or a specific internal LAN IP. +2. **Avoid `0.0.0.0` for Databases**: Never bind databases (Redis, Postgres, MySQL) to `0.0.0.0` unless explicitly intended for remote access; this is a leading cause of ransomware attacks. +3. **Use `SO_REUSEPORT` for Scale**: For high-performance TCP servers needing multiple worker processes, use `SO_REUSEPORT` (Linux 3.9+) instead of a single acceptor thread passing file descriptors. +4. **Verify UID**: When using `SO_REUSEPORT`, ensure strict user permissions to prevent local privilege escalation attacks via port hijacking. + + + + ``` + int optval = 1; + if (setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval))) + throw std::runtime_error("Error while setting socket options!"); +``` \ No newline at end of file diff --git a/memo/ft_irc_en.subject.pdf b/memo/ft_irc_en.subject.pdf new file mode 100644 index 0000000000000000000000000000000000000000..69d287bb9ef8f929a643f2fc41fefb2833c2234c GIT binary patch literal 1363881 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 z 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%0AP5*2p9IqAXP;ozsC= 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�XQHu-j0mfE&{g)~GlU}=0Ialo9fij?Gd5gupR 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¨xs_>EA2%s0N-scww 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 z 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�S5|I*(F(zTi6ndBbY==vQ(0;IaY+<;| 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

dbt-{f{@>ot(++6lz+WfbRDkcivh>48NZ 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 ^bQ*#24?=w8V*P6jVu47bP_W%sT7`ZYz=Zjci# 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%0DtmxE|mv%E{jnW)Ng(q@~SiX!kG! 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

ft_irc - IRC Server

## Description ### 42 Project | Internet Relay Chat in C++98 ### Objective -Build an IRC server in C++98 that implements... - +Build an IRC server in C++98 that handles multiple concurrent clients over TCP, implementing the core IRC protocol (registration, channels, modes, private messaging, and operator commands) over an event-driven sockets layer. ### Overview -... +**ft_irc** is a 42 project that teaches event-driven I/O, the poll/epoll multiplexing model, and the IRC protocol. The server listens on a given port and password, accepts multiple clients via non-blocking sockets, registers them with PASS/NICK/USER, and lets them join channels, exchange private and channel messages, and manage channels with KICK/INVITE/TOPIC/MODE and operator privileges. --- ## Instructions -### Requirements - -- - ### Compile & Run ```sh @@ -31,6 +26,60 @@ make && ./ircserv make bonus && ./ircserv_bonus ``` +**NOTE:** The server binds an IPv4 and an IPv6 listening socket (`IPV6_V6ONLY`) and multiplexes them with `poll`. Connect with any IRC client (e.g. `irssi`) or a raw `nc` session. + +```sh +# connect with netcat and register +nc +PASS +NICK blnunez- +USER blnunez- 0 * :Bastien +JOIN #general +``` + +Port must be in the range `1-65535`, a password must be supplied. + +--- + +## IRC Command & Message Reference + +### Registration +- `PASS ` — authenticate during registration. +- `NICK ` — set/change the nick. +- `USER ` — complete registration credentials. +- `PING`/`PONG` — keepalive and latency check. + +### Channels +- `JOIN <#channel> []` — join (or create) a channel; optional key for `+k`. +- `PART <#channel> []` — leave a channel (deleted when empty). +- `KICK <#channel> []` — remove a member (requires operator rank). +- `INVITE <#channel>` — invite a user (subject to `+i`). +- `TOPIC <#channel> []` — view/set the topic (may require `+t`). +- `NAMES <#channel>` — list current members. +- `MODE <#channel> [modes]` — view/set channel modes and user modes. + +### Messaging +- `PRIVMSG ` — send a message. +- `NOTICE ` — send a notice (no auto-reply / CTCP handling). + +### Server / Operations +- `OPER ` — become an IRC operator. +- `QUIT []` — leave the network. + +### Channel Modes +| Mode | Argument | Meaning | +|------|----------|---------| +| `i` | no | Invite-only channel (`JOIN` requires an invite) | +| `t` | no | Only operators may set the topic | +| `k` | yes | Channel key (password) required to join | +| `o` | yes | Grant/revoke operator privilege to a member | +| `l` | yes | User limit — max concurrent members | + +### Channel User Modes +`q` Founder · `a` Admin · `o` Operator · `h` Half-operator · `v` Voice + +Modes act on the target `nick`: hosts `~` founder, `&` admin, `@` operator, `%` half-op, `+` voice. Privileges are rank-ordered (Founder > Admin > Operator > Half-op > Voice > none) and rules enforcing who may act on whom are checked on every MODE/KICK. + --- ## Clang tool commands @@ -57,46 +106,54 @@ NOTE: Review clang-tidy results and fix only what makes sense for this project. ## Key Challenges & Solutions -### Topic +TODO: add poll and client + +### Protocol-Correct Error Replies -**Challenge:** +**Challenge:** Every command can fail in several ways (wrong channel, no such nick, missing params, insufficient privileges) and each failure must produce the correct RFC 2812 numeric to the client. -**Solution:** +**Solution:** A `Reply` module maps each numeric code to its required parameter layout and trailing text. A central `Error` exception carries a pre-built reply. Command processing throws `Error::reply(...)` on the first invalid condition and the server loop catches it once, formats `:server :\r\n`, and sends it — guaranteeing exactly one well-formed reply per failure and keeping handlers free of repetitive error plumbing. + +### Channel Lifetime & Ownership + +**Challenge:** Channels are created on join, destroyed when the last member parts, and must never hold a pointer to a freed `Client`. + +**Solution:** `ChannelManager` owns `Channel` objects in a case-folded map and cleans up empty channels after PART/KICK/leaveAll. `MemberSet` stores `Client*` + `ModeSet`. `ClientManager::remove` first parts the client from every channel (via `ChannelManager::leaveAll`) before freeing the `Client`, so channel member vectors never dangle. --- ## Resources ### IRC protocol -[RFC 2810 - IRC: Architecture](https://rfc-editor.org/rfc/rfc2810) -[RFC 2811 - IRC: Channel Management](https://rfc-editor.org/rfc/rfc2811) -[RFC 2812 - IRC: Client Protocol](https://rfc-editor.org/rfc/rfc2812) -[RFC 2813 - IRC: Server Protocol](https://rfc-editor.org/rfc/rfc2813) -[ircgod](https://ircgod.com) -[Model IRC client protocol](https://modern.ircdocs.horse) +- [RFC 2810 - IRC: Architecture](https://rfc-editor.org/rfc/rfc2810) +- [RFC 2811 - IRC: Channel Management](https://rfc-editor.org/rfc/rfc2811) +- [RFC 2812 - IRC: Client Protocol](https://rfc-editor.org/rfc/rfc2812) +- [RFC 2813 - IRC: Server Protocol](https://rfc-editor.org/rfc/rfc2813) +- [ircgod](https://ircgod.com) +- [Model IRC client protocol](https://modern.ircdocs.horse) ### rfc reading tips -[Tips for Reading RFCs](https://www.rfc-editor.org/series/rfc-tips/) +- [Tips for Reading RFCs](https://www.rfc-editor.org/series/rfc-tips/) ### Reference IRC client and server -[Server: ergo chat (formerly Oragono)](https://github.com/ergochat/ergo) -[Client: irssi](https://github.com/irssi/irssi) +- [Server: ergo chat (formerly Oragono)](https://github.com/ergochat/ergo) +- [Client: irssi](https://github.com/irssi/irssi) ### Sockets -[Sockets & Networking](https://web.mit.edu/6.031/www/sp19/classes/23-sockets-networking/) -[All about sockets](https://docs.oracle.com/javase/tutorial/networking/sockets/index.html) - -[CS107 Sockets Programming](https://web.stanford.edu/class/archive/cs/cs107/cs107.1238/lectures/20/) -[Beej's Guide to Network Programming](https://beej.us/guide/bgnet/) - What is a +- [Sockets & Networking](https://web.mit.edu/6.031/www/sp19/classes/23-sockets-networking/) +- [All about sockets](https://docs.oracle.com/javase/tutorial/networking/sockets/index.html) +- [CS107 Sockets Programming](https://web.stanford.edu/class/archive/cs/cs107/cs107.1238/lectures/20/) +- [Beej's Guide to Network Programming](https://beej.us/guide/bgnet/) - What is a socket?, IP Addresses, structs, and Data Munging, Jumping from IPv4 to IPv6, System Calls or Bust ### DCC protocol -[Direct Client-to-Client protocol (DCC)](https://modern.ircdocs.horse/dcc.html) +- [Direct Client-to-Client protocol (DCC)](https://modern.ircdocs.horse/dcc.html) ### Use of AI -- -- +- Learning and verification: Clarifying IRC numeric replies, and the RFC 2812 grammar. +- Documentation: Proofreading and refining the README and project descriptions. +- Testing and debugging: Generating protocol-level test scenarios and reviewing edge cases. --- From 69f50ba946132c4357bd26b36da67634d2f3407d Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 17 Sep 2026 17:31:49 +0200 Subject: [PATCH 323/349] feat: rework recv/send, error count and logic of removing clients, bufferdata and sockets --- includes/Server.hpp | 2 +- includes/Socket.hpp | 13 ++--- srcs/server/Server.cpp | 84 +++++++++++++++----------------- srcs/server/Socket.cpp | 107 +++++++++++++++++++++++------------------ 4 files changed, 109 insertions(+), 97 deletions(-) diff --git a/includes/Server.hpp b/includes/Server.hpp index 824cdbd..c4e60a3 100644 --- a/includes/Server.hpp +++ b/includes/Server.hpp @@ -38,7 +38,7 @@ class Server { void processServerSocketData_(); void acceptClient_(int const serverFd); void processClientSockets_(); - void removeClosedSockets_(); + void removeClientSocketPfd_(int const fd, std::string const &msg); void storePollFd_(int const socketFd, bool srvSocket); void removePollFd_(int const fd); void log_(std::string const &msg, Logger::logMode const lm); diff --git a/includes/Socket.hpp b/includes/Socket.hpp index 8a7258d..d89f6f6 100644 --- a/includes/Socket.hpp +++ b/includes/Socket.hpp @@ -8,7 +8,7 @@ #include // for ssize_t #include // for pair -#define RECVBUFFLEN 1024 +#define RECVBUFFLEN 4196 #define MAXMSGLEN 512 #define SOCKETERRORLIMIT 3 @@ -22,29 +22,30 @@ class Socket { size_t sendErrorCount; bool discardMode; bool isSrvSocket; - bool fdClosed; + bool closeFd; SocketIOData(bool srvSocket) : bytesRead(0), recvErrorCount(0), sendErrorCount(0), - discardMode(false), isSrvSocket(srvSocket), fdClosed(false) {} + discardMode(false), isSrvSocket(srvSocket), closeFd(false) {} }; std::map sockIODataStore_; Logger const &logger_; - bool updateBuffer_(int const fd, SocketIOData &sIOD); - size_t bufferLengthAndSanityCheck_(int const fd, bool &msgTooLong); + size_t bufferLengthAndSanityCheck_(SocketIOData &sIOD, bool &msgTooLong); + bool checkSocket4Recv(struct pollfd &pfd, SocketIOData &sIOD); // NOLINT public: Socket(Logger const &logger) : logger_(logger) {} void addSocket(int const fd, bool srvSocket); - bool checkSocket4Recv(struct pollfd &pfd); // NOLINT std::string getCRLFLineFromBuffer(int const fd, bool &buffHasMoreCmds, bool &msgTooLong); bool sendMsg(int const fd, std::string const &msg); + bool &socketCloseMarker(int const fd); void closeSocket(int const fd); + bool updateBuffer_(struct pollfd &pfd); SocketIOData *getSockIOData(int const fd); bool isServerSocket(int const fd); }; diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 7459f69..7e40cc2 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -32,14 +32,7 @@ Server::Server(int const port, std::string const &password) Server::~Server() { while (!pollFds_.empty()) { int const fd = pollFds_.back().fd; - if (!sockets_.isServerSocket(fd)) { - Client *client = clients_.find(fd); - if (client) - client->sendClosingMsg("Server shutdown - goodbye"); - clients_.remove(fd, channels_); - } - sockets_.closeSocket(fd); - this->removePollFd_(fd); + removeClientSocketPfd_(fd, "Server shutdown - goodbye"); } } @@ -123,57 +116,61 @@ void Server::processClientSockets_() { ++idx) { pollfd &pfd = pollFds_.at(idx); - if (!(sockets_.checkSocket4Recv(pfd) && (pfd.revents & POLLIN))) + if (!(pfd.revents & POLLIN)) continue; - bool buffHasMoreCmds = false; - do { + + bool &closeSocket = sockets_.socketCloseMarker(pfd.fd); + if (!sockets_.updateBuffer_(pfd)) + closeSocket = true; + + bool buffHasMoreCmds = true; + while (buffHasMoreCmds && !closeSocket) { bool msgTooLong = false; std::string cmdLine = sockets_.getCRLFLineFromBuffer( pfd.fd, buffHasMoreCmds, msgTooLong); - { - std::ostringstream oss; - oss << "RecvMsg event:\nFrom FD " << pfd.fd << ": " << cmdLine; - logger_.logMsg(oss.str(), Logger::lmIn); - } - Client *client = clients_.find(pfd.fd); - bool closeConnection = false; if (!client) { std::ostringstream oss; - oss << "Internal error: No client data found for " - "fd:" - << pfd.fd; + oss << "Internal error: No client data for fd:" << pfd.fd; log_(oss.str(), Logger::lmError); - closeConnection = true; - } else { - if (msgTooLong) - cmds_.inputTooLong(*this, *client); - if (cmdLine.empty()) - continue; - // returns true if connection is to be closed - closeConnection = cmds_.run(*this, *client, cmdLine); - } - if (closeConnection) { - sockets_.closeSocket(pfd.fd); + closeSocket = true; break; } - } while (buffHasMoreCmds); - } - removeClosedSockets_(); -} -void Server::removeClosedSockets_() { - for (size_t idx = numServerSockets_; idx < pollFds_.size(); ++idx) { - pollfd &pfd = pollFds_.at(idx); - if (sockets_.getSockIOData(pfd.fd) == NULL) { - clients_.remove(pfd.fd, channels_); - removePollFd_(pfd.fd); + if (msgTooLong) + cmds_.inputTooLong(*this, *client); + if (cmdLine.empty()) + continue; + + { + std::ostringstream oss; + oss << "RecvMsg event:\nFrom FD " << pfd.fd << ": " << cmdLine; + logger_.logMsg(oss.str(), Logger::lmIn); + } + + // returns true if connection is to be closed + closeSocket = cmds_.run(*this, *client, cmdLine); + }; + + if (closeSocket) { + removeClientSocketPfd_(pfd.fd, "Wrong client input."); --idx; } } } +void Server::removeClientSocketPfd_(int const fd, std::string const &msg) { + if (!sockets_.isServerSocket(fd)) { + Client *client = clients_.find(fd); + if (client) + client->sendClosingMsg(msg); + clients_.remove(fd, channels_); + } + sockets_.closeSocket(fd); + removePollFd_(fd); +} + void Server::acceptClient_(int const serverFd) { socketAddrX sockAddrIn46 = {}; socklen_t sockLen = 0; @@ -287,6 +284,5 @@ void Server::sendMsg(int const fd, std::string const &msg) { << msg.substr(0, msg.length() - 2); logger_.logMsg(oss.str(), Logger::lmOut); } - if (!sockets_.sendMsg(fd, msg)) - sockets_.closeSocket(fd); + sockets_.sendMsg(fd, msg); } diff --git a/srcs/server/Socket.cpp b/srcs/server/Socket.cpp index 93d48f5..1369248 100644 --- a/srcs/server/Socket.cpp +++ b/srcs/server/Socket.cpp @@ -11,37 +11,44 @@ void Socket::addSocket(int const fd, bool srvSocket) { sockIODataStore_.insert(std::make_pair(fd, SocketIOData(srvSocket))); } -bool Socket::checkSocket4Recv(struct pollfd &pfd) { - SocketIOData *sIODptr = getSockIOData(pfd.fd); - if (!sIODptr) - return false; - +bool Socket::checkSocket4Recv(struct pollfd &pfd, SocketIOData &sIOD) { if ((pfd.revents & POLLERR)) { - if (sIODptr->recvErrorCount >= SOCKETERRORLIMIT) - closeSocket(pfd.fd); - else - ++sIODptr->recvErrorCount; - return false; + ++sIOD.recvErrorCount; + if (sIOD.recvErrorCount >= SOCKETERRORLIMIT) { + sIOD.closeFd = true; + return false; + } } return true; } -bool Socket::updateBuffer_(int const fd, SocketIOData &sIOD) { +bool Socket::updateBuffer_(struct pollfd &pfd) { + SocketIOData *sIODptr = getSockIOData(pfd.fd); + if (!sIODptr || sIODptr->closeFd) + return false; + SocketIOData &sIOD = *sIODptr; + + if (!checkSocket4Recv(pfd, sIOD)) + return false; + char recvBuffer[RECVBUFFLEN]; - sIOD.bytesRead = recv(fd, &recvBuffer[0], RECVBUFFLEN, 0); + sIOD.bytesRead = recv(pfd.fd, &recvBuffer[0], RECVBUFFLEN, 0); if (sIOD.bytesRead <= 0) { ++sIOD.recvErrorCount; if (sIOD.bytesRead == 0 || sIOD.recvErrorCount >= SOCKETERRORLIMIT) { - std::ostringstream oss; - oss << "Client sockFd:" << fd; - if (sIOD.recvErrorCount >= SOCKETERRORLIMIT) - oss << " exceeded error limit (" << SOCKETERRORLIMIT << ")"; - else if (sIOD.bytesRead == 0) - oss << " closed connection on their side"; - logger_.logMsg(oss.str(), Logger::lmInfo); - closeSocket(fd); + { + std::ostringstream oss; + oss << "Client sockFd:" << pfd.fd; + if (sIOD.recvErrorCount >= SOCKETERRORLIMIT) + oss << " exceeded error limit (" << SOCKETERRORLIMIT << ")"; + else if (sIOD.bytesRead == 0) + oss << " closed connection on their side"; + logger_.logMsg(oss.str(), Logger::lmInfo); + } + sIOD.closeFd = true; + return false; } - return false; + return true; } sIOD.readBuffer += std::string(&recvBuffer[0], sIOD.bytesRead); return true; @@ -49,16 +56,14 @@ bool Socket::updateBuffer_(int const fd, SocketIOData &sIOD) { std::string Socket::getCRLFLineFromBuffer(int const fd, bool &buffHasMoreCmds, bool &msgTooLong) { + buffHasMoreCmds = false; + SocketIOData *sIODptr = getSockIOData(fd); if (!sIODptr) return ""; - SocketIOData &sIOD = *sIODptr; - if (!buffHasMoreCmds) { - if (!updateBuffer_(fd, sIOD)) - return ""; - } - size_t lfcrPos = bufferLengthAndSanityCheck_(fd, msgTooLong); + + size_t lfcrPos = bufferLengthAndSanityCheck_(sIOD, msgTooLong); if (lfcrPos == std::string::npos) return ""; @@ -69,37 +74,34 @@ std::string Socket::getCRLFLineFromBuffer(int const fd, bool &buffHasMoreCmds, return cmdLine; } -size_t Socket::bufferLengthAndSanityCheck_(int const fd, bool &msgTooLong) { - SocketIOData *sIODptr = getSockIOData(fd); - if (!sIODptr) - return std::string::npos; - - SocketIOData &sIOD = *sIODptr; - std::string &rdBuffer = sIOD.readBuffer; - size_t lfcrPos = rdBuffer.find("\r\n"); +size_t Socket::bufferLengthAndSanityCheck_(SocketIOData &sIOD, + bool &msgTooLong) { + std::string &rdBuffer = sIOD.readBuffer; + size_t lfcrPos = rdBuffer.find("\r\n"); + msgTooLong = false; if (!sIOD.discardMode) { if ((rdBuffer.length() < MAXMSGLEN) || - (lfcrPos != std::string::npos && lfcrPos <= MAXMSGLEN - 2)) + (lfcrPos != std::string::npos && lfcrPos <= MAXMSGLEN - 2)) { return lfcrPos; - sIOD.discardMode = true; - msgTooLong = true; - ++sIOD.recvErrorCount; + } else { + sIOD.discardMode = true; + msgTooLong = true; + } } - // discard mode while (lfcrPos != std::string::npos && lfcrPos > MAXMSGLEN - 2) { - ++sIOD.recvErrorCount; + if (++sIOD.recvErrorCount >= SOCKETERRORLIMIT) + break; rdBuffer.erase(0, lfcrPos + 2); lfcrPos = rdBuffer.find("\r\n"); } if (sIOD.recvErrorCount >= SOCKETERRORLIMIT) { - closeSocket(fd); + sIOD.closeFd = true; return std::string::npos; } if (lfcrPos != std::string::npos) { sIOD.discardMode = false; - sIOD.recvErrorCount = 0; return lfcrPos; } // lfcrPos == std::string::npos @@ -124,15 +126,20 @@ bool Socket::sendMsg(int const fd, std::string const &msg) { return false; SocketIOData &sIOD = *sIODptr; - if (msg.length() > sIOD.writeBuffer.max_size() - sIOD.writeBuffer.length()) + if (msg.length() > + sIOD.writeBuffer.max_size() - sIOD.writeBuffer.length()) { + sIOD.closeFd = true; return false; + } sIOD.writeBuffer += msg; ssize_t bytesSent = send(fd, sIOD.writeBuffer.c_str(), sIOD.writeBuffer.length(), MSG_NOSIGNAL); if (bytesSent <= 0) { ++sIOD.sendErrorCount; - if (sIOD.sendErrorCount >= SOCKETERRORLIMIT) + if (sIOD.sendErrorCount >= SOCKETERRORLIMIT) { + sIOD.closeFd = true; return false; + } return true; } @@ -144,12 +151,20 @@ bool Socket::sendMsg(int const fd, std::string const &msg) { return true; } +bool &Socket::socketCloseMarker(int const fd) { + static bool closed = true; + SocketIOData *sIODptr = getSockIOData(fd); + if (!sIODptr) + return closed; + return sIODptr->closeFd; +} + void Socket::closeSocket(int const fd) { SocketIOData *sIODptr = getSockIOData(fd); if (!sIODptr) return; + SocketIOData &sIOD = *sIODptr; - SocketIOData &sIOD = *sIODptr; std::ostringstream oss; if (sIOD.isSrvSocket) { oss << "Closing server socket (" From 1728532d2c61f70f5ef4d66adc190a3230297081 Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 17 Sep 2026 18:05:03 +0200 Subject: [PATCH 324/349] fix: discard mode logic --- srcs/server/Socket.cpp | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/srcs/server/Socket.cpp b/srcs/server/Socket.cpp index 1369248..5e0008e 100644 --- a/srcs/server/Socket.cpp +++ b/srcs/server/Socket.cpp @@ -90,26 +90,28 @@ size_t Socket::bufferLengthAndSanityCheck_(SocketIOData &sIOD, } } // discard mode - while (lfcrPos != std::string::npos && lfcrPos > MAXMSGLEN - 2) { + while (sIOD.discardMode) { if (++sIOD.recvErrorCount >= SOCKETERRORLIMIT) break; - rdBuffer.erase(0, lfcrPos + 2); - lfcrPos = rdBuffer.find("\r\n"); + if (lfcrPos != std::string::npos) { + rdBuffer.erase(0, lfcrPos + 2); + lfcrPos = rdBuffer.find("\r\n"); + sIOD.discardMode = + (lfcrPos == std::string::npos || lfcrPos > MAXMSGLEN - 2); + } + if (lfcrPos == std::string::npos) { + if (!rdBuffer.empty() && '\r' == *rdBuffer.rbegin()) + rdBuffer = "\r"; + else + rdBuffer.clear(); + return std::string::npos; + } } if (sIOD.recvErrorCount >= SOCKETERRORLIMIT) { sIOD.closeFd = true; return std::string::npos; } - if (lfcrPos != std::string::npos) { - sIOD.discardMode = false; - return lfcrPos; - } - // lfcrPos == std::string::npos - if (!rdBuffer.empty() && '\r' == *rdBuffer.rbegin()) - rdBuffer = "\r"; - else - rdBuffer.clear(); - return std::string::npos; + return lfcrPos; } /* From 4c2224a32992275638a16753a05d95f2a3e7fa40 Mon Sep 17 00:00:00 2001 From: ArchCipher Date: Thu, 17 Sep 2026 18:27:30 +0200 Subject: [PATCH 325/349] add bonus --- README.md | 55 ++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 40 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index c33f3db..c8b516d 100644 --- a/README.md +++ b/README.md @@ -32,9 +32,9 @@ make bonus && ./ircserv_bonus # connect with netcat and register nc PASS -NICK blnunez- -USER blnunez- 0 * :Bastien -JOIN #general +NICK +USER 0 * : +JOIN #channel ``` Port must be in the range `1-65535`, a password must be supplied. @@ -50,7 +50,7 @@ Port must be in the range `1-65535`, a password must be supplied. - `PING`/`PONG` — keepalive and latency check. ### Channels -- `JOIN <#channel> []` — join (or create) a channel; optional key for `+k`. +- `JOIN <#channel> []` — join (or create) a channel, optional key for `+k`. - `PART <#channel> []` — leave a channel (deleted when empty). - `KICK <#channel> []` — remove a member (requires operator rank). - `INVITE <#channel>` — invite a user (subject to `+i`). @@ -59,13 +59,9 @@ Port must be in the range `1-65535`, a password must be supplied. - `MODE <#channel> [modes]` — view/set channel modes and user modes. ### Messaging -- `PRIVMSG ` — send a message. +- `PRIVMSG ` — send a message. The bonus bot answers PRIVMSG addressed to it. - `NOTICE ` — send a notice (no auto-reply / CTCP handling). -### Server / Operations -- `OPER ` — become an IRC operator. -- `QUIT []` — leave the network. - ### Channel Modes | Mode | Argument | Meaning | |------|----------|---------| @@ -82,21 +78,44 @@ Modes act on the target `nick`: hosts `~` founder, `&` admin, `@` operator, `%` --- +## Bonus: DCC Bot & File Transfer + +Built with `make bonus` → `./ircserv_bonus `. + +### DCCBot + +A bot named `DCCBot` (also reached as `bot` / `BOT` / `#bot`, or by sending a bot command to any channel) that intercepts `PRIVMSG`: bot commands are answered with `PRIVMSG`, CTCP queries with `NOTICE`. + +| Command | Action | +|------------------|--------| +| `!help` | List the available bot commands | +| `!version` | Report the bot/server version | +| `!whoami` | Show the requester's nick | +| `!send ` | Start a DCC SEND offer of `files/` | +| `!accept` | Accept the pending transfer offer | +| `!reject` | Reject the pending transfer offer | + +CTCP queries (`\x01VERSION\x01`, `\x01PING \x01`) sent to the bot via `PRIVMSG` are answered by the bot with `NOTICE`. + +### File transfer (DCC SEND) + +`!send` opens a listener on an OS-picked port and hands the target a CTCP `DCC SEND 127.0.0.1 ` line. The receiver replies with `!accept`, connects to the advertised address/port and receives the file in 1 KiB chunks. Only one transfer is active at a time (`bot busy` otherwise). Pending offers expire after 60 seconds. Offered files must live in a `files/` directory next to the server; filenames containing `/` or `..` are rejected. + +--- + ## Clang tool commands ```sh -# Run clang-format before PR (use clang-format version 21) +# clang-format all headers and sources make format -``` -```sh -# Run clang-tidy on individual file +# clang-tidy one file make tidy FILE=srcs/ -# Run clang-tidy on multiple files (filenames without spaces) +# clang-tidy on multiple files (filenames without spaces) make tidy FILE="srcs/ srcs/" -# Run clang-tidy on all .cpp/.hpp under srcs and includes +# clang-tidy every source and header make tidy-all ``` @@ -120,6 +139,12 @@ TODO: add poll and client **Solution:** `ChannelManager` owns `Channel` objects in a case-folded map and cleans up empty channels after PART/KICK/leaveAll. `MemberSet` stores `Client*` + `ModeSet`. `ClientManager::remove` first parts the client from every channel (via `ChannelManager::leaveAll`) before freeing the `Client`, so channel member vectors never dangle. +### Bonus: CTCP & DCC SEND through the Bot + +**Challenge:** The CTCP mechanic used by DCC is barely documented anywhere. You can forward a CTCP by stripping the wrapping `\x01` bytes and re-sending what is inside. But to actually run a file transfer the server needs to hand the receiver a `\x01DCC SEND \x01` line, open a listener the receiver can connect to, and stream the file. Wiring the two bonuses together (bot + transfer manager) with the bot as the middleman got complicated as the design evolved, and real clients silently abstract all of this away so there was no reference. + +**Solution:** Make the bot the DCC sender. `!send` asks the `TransferManager` to validate the filename, open a listener on an OS-picked port, and return the offer port/size. The bot then builds the `DCC SEND ... 127.0.0.1 ` CTCP and delivers it wrapped in `\x01` to the target over `PRIVMSG`. The receiver just replies `!accept` and connects to the advertised address/port — which means the whole flow works over a plain `nc` session, no IRC client needed. Using the bot to construct and send the CTCP also makes the query path symmetric: the same `PRIVMSG` interception that answers `!help`/`!version` carries the DCC offer, and CTCP responses go back out as `NOTICE`. Transfer state (offer → accept/reject → stream → expire after 60 s) stayed in `TransferManager`, while the bot is a dumb messenger. + --- ## Resources From 478a16d06823f23581269966347933906ae0e532 Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 17 Sep 2026 18:46:25 +0200 Subject: [PATCH 326/349] feat: separate processing of multiple too long messages --- srcs/server/Socket.cpp | 33 +++++++++++++-------------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/srcs/server/Socket.cpp b/srcs/server/Socket.cpp index 5e0008e..ab821b5 100644 --- a/srcs/server/Socket.cpp +++ b/srcs/server/Socket.cpp @@ -70,7 +70,8 @@ std::string Socket::getCRLFLineFromBuffer(int const fd, bool &buffHasMoreCmds, std::string cmdLine = sIOD.readBuffer.substr(0, lfcrPos); sIOD.recvErrorCount = 0; sIOD.readBuffer = sIOD.readBuffer.substr(lfcrPos + 2); - buffHasMoreCmds = (sIOD.readBuffer.find("\r\n") != std::string::npos); + buffHasMoreCmds = ((sIOD.readBuffer.size() > MAXMSGLEN) || + (sIOD.readBuffer.find("\r\n") != std::string::npos)); return cmdLine; } @@ -90,28 +91,20 @@ size_t Socket::bufferLengthAndSanityCheck_(SocketIOData &sIOD, } } // discard mode - while (sIOD.discardMode) { - if (++sIOD.recvErrorCount >= SOCKETERRORLIMIT) - break; - if (lfcrPos != std::string::npos) { - rdBuffer.erase(0, lfcrPos + 2); - lfcrPos = rdBuffer.find("\r\n"); - sIOD.discardMode = - (lfcrPos == std::string::npos || lfcrPos > MAXMSGLEN - 2); - } - if (lfcrPos == std::string::npos) { - if (!rdBuffer.empty() && '\r' == *rdBuffer.rbegin()) - rdBuffer = "\r"; - else - rdBuffer.clear(); - return std::string::npos; - } - } - if (sIOD.recvErrorCount >= SOCKETERRORLIMIT) { + if (++sIOD.recvErrorCount >= SOCKETERRORLIMIT) { sIOD.closeFd = true; return std::string::npos; } - return lfcrPos; + if (lfcrPos != std::string::npos) { + rdBuffer.erase(0, lfcrPos + 2); + sIOD.discardMode = false; + return std::string::npos; + } + if (!rdBuffer.empty() && '\r' == *rdBuffer.rbegin()) + rdBuffer = "\r"; + else + rdBuffer.clear(); + return std::string::npos; } /* From 31523259bb564f031308c6853926c9e16b012e66 Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 17 Sep 2026 18:54:47 +0200 Subject: [PATCH 327/349] fix: include updates from review --- srcs/server/Server.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 7e40cc2..75cfacb 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -154,7 +154,7 @@ void Server::processClientSockets_() { }; if (closeSocket) { - removeClientSocketPfd_(pfd.fd, "Wrong client input."); + removeClientSocketPfd_(pfd.fd, ""); --idx; } } @@ -278,7 +278,7 @@ void Server::run() { NOTE: all send should go though server::sendMsg */ void Server::sendMsg(int const fd, std::string const &msg) { - { + if (msg.length() > 2) { std::ostringstream oss; oss << "SendMsg event:\nSendto FD " << fd << ": " << msg.substr(0, msg.length() - 2); From add1a7955963a45dd9f5b2279c6e091ead5881ef Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 17 Sep 2026 21:17:57 +0200 Subject: [PATCH 328/349] feat: more updates from PR feedback --- includes/Socket.hpp | 5 +++-- srcs/server/Server.cpp | 36 +++++++++++++++++++++++++----------- srcs/server/Socket.cpp | 22 +++++++++++++--------- 3 files changed, 41 insertions(+), 22 deletions(-) diff --git a/includes/Socket.hpp b/includes/Socket.hpp index d89f6f6..ee13af1 100644 --- a/includes/Socket.hpp +++ b/includes/Socket.hpp @@ -8,7 +8,7 @@ #include // for ssize_t #include // for pair -#define RECVBUFFLEN 4196 +#define RECVBUFFLEN 4096 #define MAXMSGLEN 512 #define SOCKETERRORLIMIT 3 @@ -39,10 +39,11 @@ class Socket { Socket(Logger const &logger) : logger_(logger) {} void addSocket(int const fd, bool srvSocket); + bool checkSocketData(int const fd); std::string getCRLFLineFromBuffer(int const fd, bool &buffHasMoreCmds, bool &msgTooLong); bool sendMsg(int const fd, std::string const &msg); - bool &socketCloseMarker(int const fd); + bool socketCloseMarker(int const fd); void closeSocket(int const fd); bool updateBuffer_(struct pollfd &pfd); diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 75cfacb..c83892e 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -116,25 +116,37 @@ void Server::processClientSockets_() { ++idx) { pollfd &pfd = pollFds_.at(idx); - if (!(pfd.revents & POLLIN)) + if (!(pfd.revents & (POLLIN | POLLERR | POLLHUP))) continue; - bool &closeSocket = sockets_.socketCloseMarker(pfd.fd); - if (!sockets_.updateBuffer_(pfd)) - closeSocket = true; + struct { + bool closeSocket; + std::string closeMsg; + } closeData = {.closeSocket = true, .closeMsg = ""}; + + if (!(sockets_.checkSocketData(pfd.fd) && sockets_.updateBuffer_(pfd))) { + closeData.closeSocket = true; + closeData.closeMsg = "Socket error while updating buffer."; + } bool buffHasMoreCmds = true; - while (buffHasMoreCmds && !closeSocket) { + while (buffHasMoreCmds && !closeData.closeSocket) { bool msgTooLong = false; std::string cmdLine = sockets_.getCRLFLineFromBuffer( pfd.fd, buffHasMoreCmds, msgTooLong); + if (sockets_.socketCloseMarker(pfd.fd)) { + closeData.closeSocket = true; + closeData.closeMsg = "Socket error while composing command line from buffer."; + break; + } Client *client = clients_.find(pfd.fd); if (!client) { std::ostringstream oss; oss << "Internal error: No client data for fd:" << pfd.fd; log_(oss.str(), Logger::lmError); - closeSocket = true; + closeData.closeSocket = true; + closeData.closeMsg = oss.str(); break; } @@ -150,18 +162,19 @@ void Server::processClientSockets_() { } // returns true if connection is to be closed - closeSocket = cmds_.run(*this, *client, cmdLine); + closeData.closeSocket = cmds_.run(*this, *client, cmdLine); + closeData.closeMsg = ""; }; - if (closeSocket) { - removeClientSocketPfd_(pfd.fd, ""); + if (closeData.closeSocket) { + removeClientSocketPfd_(pfd.fd, closeData.closeMsg); --idx; } } } void Server::removeClientSocketPfd_(int const fd, std::string const &msg) { - if (!sockets_.isServerSocket(fd)) { + if (!msg.empty() && !sockets_.isServerSocket(fd)) { Client *client = clients_.find(fd); if (client) client->sendClosingMsg(msg); @@ -284,5 +297,6 @@ void Server::sendMsg(int const fd, std::string const &msg) { << msg.substr(0, msg.length() - 2); logger_.logMsg(oss.str(), Logger::lmOut); } - sockets_.sendMsg(fd, msg); + if (!sockets_.sendMsg(fd, msg)) + removeClientSocketPfd_(fd, "Error while sending Msg."); } diff --git a/srcs/server/Socket.cpp b/srcs/server/Socket.cpp index ab821b5..528e0d4 100644 --- a/srcs/server/Socket.cpp +++ b/srcs/server/Socket.cpp @@ -11,6 +11,10 @@ void Socket::addSocket(int const fd, bool srvSocket) { sockIODataStore_.insert(std::make_pair(fd, SocketIOData(srvSocket))); } +bool Socket::checkSocketData(int const fd) { + return (getSockIOData(fd) != NULL); +} + bool Socket::checkSocket4Recv(struct pollfd &pfd, SocketIOData &sIOD) { if ((pfd.revents & POLLERR)) { ++sIOD.recvErrorCount; @@ -146,30 +150,30 @@ bool Socket::sendMsg(int const fd, std::string const &msg) { return true; } -bool &Socket::socketCloseMarker(int const fd) { - static bool closed = true; +bool Socket::socketCloseMarker(int const fd) { SocketIOData *sIODptr = getSockIOData(fd); if (!sIODptr) - return closed; + return true; return sIODptr->closeFd; } void Socket::closeSocket(int const fd) { SocketIOData *sIODptr = getSockIOData(fd); + int const addrFam = srvHelp::sockAddrFamily(fd); + close(fd); + if (!sIODptr) return; - SocketIOData &sIOD = *sIODptr; - + SocketIOData &sIOD = *sIODptr; std::ostringstream oss; if (sIOD.isSrvSocket) { - oss << "Closing server socket (" - << srvHelp::getAdressFamilyStr(srvHelp::sockAddrFamily(fd)) << ")"; + oss << "Closing server socket (" << srvHelp::getAdressFamilyStr(addrFam) + << ")"; } else { oss << "Closing client sockFd:" << fd; } logger_.logMsg(oss.str(), Logger::lmInfo); - close(fd); sockIODataStore_.erase(fd); } @@ -185,4 +189,4 @@ bool Socket::isServerSocket(int const fd) { if (sIODptr) return sIODptr->isSrvSocket; return false; -} \ No newline at end of file +} From 20eec409212a8a2b1935bfa3cffa06ae730e7b4d Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 17 Sep 2026 21:17:57 +0200 Subject: [PATCH 329/349] feat: more updates from PR feedback --- includes/Socket.hpp | 5 +++-- srcs/server/Server.cpp | 36 +++++++++++++++++++++++++----------- srcs/server/Socket.cpp | 22 +++++++++++++--------- 3 files changed, 41 insertions(+), 22 deletions(-) diff --git a/includes/Socket.hpp b/includes/Socket.hpp index d89f6f6..ee13af1 100644 --- a/includes/Socket.hpp +++ b/includes/Socket.hpp @@ -8,7 +8,7 @@ #include // for ssize_t #include // for pair -#define RECVBUFFLEN 4196 +#define RECVBUFFLEN 4096 #define MAXMSGLEN 512 #define SOCKETERRORLIMIT 3 @@ -39,10 +39,11 @@ class Socket { Socket(Logger const &logger) : logger_(logger) {} void addSocket(int const fd, bool srvSocket); + bool checkSocketData(int const fd); std::string getCRLFLineFromBuffer(int const fd, bool &buffHasMoreCmds, bool &msgTooLong); bool sendMsg(int const fd, std::string const &msg); - bool &socketCloseMarker(int const fd); + bool socketCloseMarker(int const fd); void closeSocket(int const fd); bool updateBuffer_(struct pollfd &pfd); diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 75cfacb..6edacd0 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -116,25 +116,37 @@ void Server::processClientSockets_() { ++idx) { pollfd &pfd = pollFds_.at(idx); - if (!(pfd.revents & POLLIN)) + if (!(pfd.revents & (POLLIN | POLLERR | POLLHUP))) continue; - bool &closeSocket = sockets_.socketCloseMarker(pfd.fd); - if (!sockets_.updateBuffer_(pfd)) - closeSocket = true; + struct { + bool closeSocket; + std::string closeMsg; + } closeData = {false, ""}; + + if (!(sockets_.checkSocketData(pfd.fd) && sockets_.updateBuffer_(pfd))) { + closeData.closeSocket = true; + closeData.closeMsg = "Socket error while updating buffer."; + } bool buffHasMoreCmds = true; - while (buffHasMoreCmds && !closeSocket) { + while (buffHasMoreCmds && !closeData.closeSocket) { bool msgTooLong = false; std::string cmdLine = sockets_.getCRLFLineFromBuffer( pfd.fd, buffHasMoreCmds, msgTooLong); + if (sockets_.socketCloseMarker(pfd.fd)) { + closeData.closeSocket = true; + closeData.closeMsg = "Socket error while composing command line from buffer."; + break; + } Client *client = clients_.find(pfd.fd); if (!client) { std::ostringstream oss; oss << "Internal error: No client data for fd:" << pfd.fd; log_(oss.str(), Logger::lmError); - closeSocket = true; + closeData.closeSocket = true; + closeData.closeMsg = oss.str(); break; } @@ -150,18 +162,19 @@ void Server::processClientSockets_() { } // returns true if connection is to be closed - closeSocket = cmds_.run(*this, *client, cmdLine); + closeData.closeSocket = cmds_.run(*this, *client, cmdLine); + closeData.closeMsg = ""; }; - if (closeSocket) { - removeClientSocketPfd_(pfd.fd, ""); + if (closeData.closeSocket) { + removeClientSocketPfd_(pfd.fd, closeData.closeMsg); --idx; } } } void Server::removeClientSocketPfd_(int const fd, std::string const &msg) { - if (!sockets_.isServerSocket(fd)) { + if (!msg.empty() && !sockets_.isServerSocket(fd)) { Client *client = clients_.find(fd); if (client) client->sendClosingMsg(msg); @@ -284,5 +297,6 @@ void Server::sendMsg(int const fd, std::string const &msg) { << msg.substr(0, msg.length() - 2); logger_.logMsg(oss.str(), Logger::lmOut); } - sockets_.sendMsg(fd, msg); + if (!sockets_.sendMsg(fd, msg)) + removeClientSocketPfd_(fd, "Error while sending Msg."); } diff --git a/srcs/server/Socket.cpp b/srcs/server/Socket.cpp index ab821b5..528e0d4 100644 --- a/srcs/server/Socket.cpp +++ b/srcs/server/Socket.cpp @@ -11,6 +11,10 @@ void Socket::addSocket(int const fd, bool srvSocket) { sockIODataStore_.insert(std::make_pair(fd, SocketIOData(srvSocket))); } +bool Socket::checkSocketData(int const fd) { + return (getSockIOData(fd) != NULL); +} + bool Socket::checkSocket4Recv(struct pollfd &pfd, SocketIOData &sIOD) { if ((pfd.revents & POLLERR)) { ++sIOD.recvErrorCount; @@ -146,30 +150,30 @@ bool Socket::sendMsg(int const fd, std::string const &msg) { return true; } -bool &Socket::socketCloseMarker(int const fd) { - static bool closed = true; +bool Socket::socketCloseMarker(int const fd) { SocketIOData *sIODptr = getSockIOData(fd); if (!sIODptr) - return closed; + return true; return sIODptr->closeFd; } void Socket::closeSocket(int const fd) { SocketIOData *sIODptr = getSockIOData(fd); + int const addrFam = srvHelp::sockAddrFamily(fd); + close(fd); + if (!sIODptr) return; - SocketIOData &sIOD = *sIODptr; - + SocketIOData &sIOD = *sIODptr; std::ostringstream oss; if (sIOD.isSrvSocket) { - oss << "Closing server socket (" - << srvHelp::getAdressFamilyStr(srvHelp::sockAddrFamily(fd)) << ")"; + oss << "Closing server socket (" << srvHelp::getAdressFamilyStr(addrFam) + << ")"; } else { oss << "Closing client sockFd:" << fd; } logger_.logMsg(oss.str(), Logger::lmInfo); - close(fd); sockIODataStore_.erase(fd); } @@ -185,4 +189,4 @@ bool Socket::isServerSocket(int const fd) { if (sIODptr) return sIODptr->isSrvSocket; return false; -} \ No newline at end of file +} From ceecd8843f0edf7a291bb77cc37804a6e8b37597 Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 17 Sep 2026 21:17:57 +0200 Subject: [PATCH 330/349] feat: more updates from PR feedback --- includes/Socket.hpp | 5 +++-- srcs/server/Server.cpp | 37 ++++++++++++++++++++++++++----------- srcs/server/Socket.cpp | 22 +++++++++++++--------- 3 files changed, 42 insertions(+), 22 deletions(-) diff --git a/includes/Socket.hpp b/includes/Socket.hpp index d89f6f6..ee13af1 100644 --- a/includes/Socket.hpp +++ b/includes/Socket.hpp @@ -8,7 +8,7 @@ #include // for ssize_t #include // for pair -#define RECVBUFFLEN 4196 +#define RECVBUFFLEN 4096 #define MAXMSGLEN 512 #define SOCKETERRORLIMIT 3 @@ -39,10 +39,11 @@ class Socket { Socket(Logger const &logger) : logger_(logger) {} void addSocket(int const fd, bool srvSocket); + bool checkSocketData(int const fd); std::string getCRLFLineFromBuffer(int const fd, bool &buffHasMoreCmds, bool &msgTooLong); bool sendMsg(int const fd, std::string const &msg); - bool &socketCloseMarker(int const fd); + bool socketCloseMarker(int const fd); void closeSocket(int const fd); bool updateBuffer_(struct pollfd &pfd); diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 75cfacb..506b407 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -116,25 +116,38 @@ void Server::processClientSockets_() { ++idx) { pollfd &pfd = pollFds_.at(idx); - if (!(pfd.revents & POLLIN)) + if (!(pfd.revents & (POLLIN | POLLERR | POLLHUP))) continue; - bool &closeSocket = sockets_.socketCloseMarker(pfd.fd); - if (!sockets_.updateBuffer_(pfd)) - closeSocket = true; + struct { + bool closeSocket; + std::string closeMsg; + } closeData = {false, ""}; + + if (!(sockets_.checkSocketData(pfd.fd) && + sockets_.updateBuffer_(pfd))) { + closeData.closeSocket = true; + closeData.closeMsg = "Socket error while updating buffer."; + } bool buffHasMoreCmds = true; - while (buffHasMoreCmds && !closeSocket) { + while (buffHasMoreCmds && !closeData.closeSocket) { bool msgTooLong = false; std::string cmdLine = sockets_.getCRLFLineFromBuffer( pfd.fd, buffHasMoreCmds, msgTooLong); + if (sockets_.socketCloseMarker(pfd.fd)) { + closeData.closeSocket = true; + closeData.closeMsg = + "Error while composing command line from buffer."; + } Client *client = clients_.find(pfd.fd); if (!client) { std::ostringstream oss; oss << "Internal error: No client data for fd:" << pfd.fd; log_(oss.str(), Logger::lmError); - closeSocket = true; + closeData.closeSocket = true; + closeData.closeMsg = oss.str(); break; } @@ -150,18 +163,19 @@ void Server::processClientSockets_() { } // returns true if connection is to be closed - closeSocket = cmds_.run(*this, *client, cmdLine); + closeData.closeSocket = cmds_.run(*this, *client, cmdLine); + closeData.closeMsg = ""; }; - if (closeSocket) { - removeClientSocketPfd_(pfd.fd, ""); + if (closeData.closeSocket) { + removeClientSocketPfd_(pfd.fd, closeData.closeMsg); --idx; } } } void Server::removeClientSocketPfd_(int const fd, std::string const &msg) { - if (!sockets_.isServerSocket(fd)) { + if (!msg.empty() && !sockets_.isServerSocket(fd)) { Client *client = clients_.find(fd); if (client) client->sendClosingMsg(msg); @@ -284,5 +298,6 @@ void Server::sendMsg(int const fd, std::string const &msg) { << msg.substr(0, msg.length() - 2); logger_.logMsg(oss.str(), Logger::lmOut); } - sockets_.sendMsg(fd, msg); + if (!sockets_.sendMsg(fd, msg)) + removeClientSocketPfd_(fd, "Error while sending Msg."); } diff --git a/srcs/server/Socket.cpp b/srcs/server/Socket.cpp index ab821b5..528e0d4 100644 --- a/srcs/server/Socket.cpp +++ b/srcs/server/Socket.cpp @@ -11,6 +11,10 @@ void Socket::addSocket(int const fd, bool srvSocket) { sockIODataStore_.insert(std::make_pair(fd, SocketIOData(srvSocket))); } +bool Socket::checkSocketData(int const fd) { + return (getSockIOData(fd) != NULL); +} + bool Socket::checkSocket4Recv(struct pollfd &pfd, SocketIOData &sIOD) { if ((pfd.revents & POLLERR)) { ++sIOD.recvErrorCount; @@ -146,30 +150,30 @@ bool Socket::sendMsg(int const fd, std::string const &msg) { return true; } -bool &Socket::socketCloseMarker(int const fd) { - static bool closed = true; +bool Socket::socketCloseMarker(int const fd) { SocketIOData *sIODptr = getSockIOData(fd); if (!sIODptr) - return closed; + return true; return sIODptr->closeFd; } void Socket::closeSocket(int const fd) { SocketIOData *sIODptr = getSockIOData(fd); + int const addrFam = srvHelp::sockAddrFamily(fd); + close(fd); + if (!sIODptr) return; - SocketIOData &sIOD = *sIODptr; - + SocketIOData &sIOD = *sIODptr; std::ostringstream oss; if (sIOD.isSrvSocket) { - oss << "Closing server socket (" - << srvHelp::getAdressFamilyStr(srvHelp::sockAddrFamily(fd)) << ")"; + oss << "Closing server socket (" << srvHelp::getAdressFamilyStr(addrFam) + << ")"; } else { oss << "Closing client sockFd:" << fd; } logger_.logMsg(oss.str(), Logger::lmInfo); - close(fd); sockIODataStore_.erase(fd); } @@ -185,4 +189,4 @@ bool Socket::isServerSocket(int const fd) { if (sIODptr) return sIODptr->isSrvSocket; return false; -} \ No newline at end of file +} From a46eb8423592272740268b6e281d7785ce701440 Mon Sep 17 00:00:00 2001 From: ArchCipher Date: Fri, 11 Sep 2026 16:31:09 +0200 Subject: [PATCH 331/349] fix: Remove throw from CommandProcessor --- includes/CommandProcessor.hpp | 11 +++++---- includes/Errors.hpp | 2 +- includes/Mode.hpp | 2 +- includes/Reply.hpp | 2 +- srcs/commands/CommandProcessor.cpp | 27 ++++++++++++--------- srcs/commands/Connection.cpp | 7 +++--- srcs/commands/Messaging.cpp | 2 +- srcs/commands/Registration.cpp | 39 +++++++++++++++++------------- srcs/server/Server.cpp | 2 -- 9 files changed, 52 insertions(+), 42 deletions(-) diff --git a/includes/CommandProcessor.hpp b/includes/CommandProcessor.hpp index 84f2768..d2d9f08 100644 --- a/includes/CommandProcessor.hpp +++ b/includes/CommandProcessor.hpp @@ -1,5 +1,7 @@ #pragma once +#include "Reply.hpp" + #include #include #include @@ -37,15 +39,14 @@ class Command { class CommandProcessor { std::map commands_; - Command &at_(const std::string &name); + + bool sendError_(Client &client, Reply::Code code, + const std::string ¶ms = ""); public: - CommandProcessor() { - registerCommands(); - } + CommandProcessor(); void registerCommands(); - // parser TODO blnunez- bool parse(const std::string &line, std::string &name, std::vector ¶ms); bool run(Server &server, Client &client, const std::string &line); diff --git a/includes/Errors.hpp b/includes/Errors.hpp index 67bdf1d..fd728aa 100644 --- a/includes/Errors.hpp +++ b/includes/Errors.hpp @@ -49,7 +49,7 @@ class Error : public std::exception { return reply_; } - static Error reply(Reply::Code code, const std::string ¶ms) { + static Error reply(Reply::Code code, const std::string ¶ms = "") { return Error(Reply::reply(code, params)); } diff --git a/includes/Mode.hpp b/includes/Mode.hpp index 308f401..1057260 100644 --- a/includes/Mode.hpp +++ b/includes/Mode.hpp @@ -191,11 +191,11 @@ parseChannelModeChanges(const std::vector ¶ms, changes.push_back(ModeChange(*it, op, "")); break; case UserLimit: - case ChannelOperator: if (op == Remove) { changes.push_back(ModeChange(*it, op, "")); continue; } + case ChannelOperator: case Key: if (idx >= params.size()) changes.push_back(ModeChange(*it, op, "")); diff --git a/includes/Reply.hpp b/includes/Reply.hpp index 63088e8..251d8e4 100644 --- a/includes/Reply.hpp +++ b/includes/Reply.hpp @@ -228,7 +228,7 @@ struct Message { // reply() with default text from text(). // reply(ERR_NOSUCHNICK, "bob") -> code=401, body="bob :No such nick/channel" // reply(RPL_MYINFO, "irc.local 1.0 itkol") -> code=4, body="irc.local 1.0 itkol" -inline Message reply(Code code, const std::string ¶ms) { +inline Message reply(Code code, const std::string ¶ms = "") { const char *t = text(code); if (*t != '\0') return Message(code, params, t); diff --git a/srcs/commands/CommandProcessor.cpp b/srcs/commands/CommandProcessor.cpp index f7c8126..e0c1733 100644 --- a/srcs/commands/CommandProcessor.cpp +++ b/srcs/commands/CommandProcessor.cpp @@ -1,7 +1,6 @@ #include "CommandProcessor.hpp" #include "Client.hpp" -#include "Errors.hpp" #include "Handlers.hpp" #include "Parser.hpp" #include "Registration.hpp" @@ -9,11 +8,8 @@ #include -Command &CommandProcessor::at_(const std::string &name) { - std::map::iterator it = commands_.find(name); - if (it == commands_.end()) - throw Error::reply(Reply::ERR_UNKNOWNCOMMAND, name); - return it->second; +CommandProcessor::CommandProcessor() { + registerCommands(); } bool CommandProcessor::parse(const std::string &line, std::string &cmd, @@ -48,6 +44,12 @@ void CommandProcessor::registerCommands() { add("WHOIS", Command(Handlers::whois, false, 1)); } +bool CommandProcessor::sendError_(Client &client, Reply::Code code, + const std::string ¶ms) { + client.sendReply(Reply::reply(code, params)); + return false; +} + // Run runs this command with the given client/message. bool CommandProcessor::run(Server &server, Client &client, const std::string &line) { @@ -56,16 +58,19 @@ bool CommandProcessor::run(Server &server, Client &client, if (!parse(line, name, params)) return false; - Command &cmd = at_(name); + std::map::iterator it = commands_.find(name); + if (it == commands_.end()) + return sendError_(client, Reply::ERR_UNKNOWNCOMMAND, name); + Command &cmd = it->second; if (!client.isRegistered() && !cmd.isUsablePreReg()) - throw Error::reply(Reply::ERR_NOTREGISTERED, ""); + return sendError_(client, Reply::ERR_NOTREGISTERED); if (client.isRegistered() && (cmd.handler == Handlers::pass || cmd.handler == Handlers::user)) - throw Error::reply(Reply::ERR_ALREADYREGISTRED, ""); + return sendError_(client, Reply::ERR_ALREADYREGISTRED); if (cmd.getRequiresOp() && !client.isOperator()) - throw Error::reply(Reply::ERR_NOPRIVILEGES, ""); + return sendError_(client, Reply::ERR_NOPRIVILEGES); if (params.size() < cmd.getMinParams()) - throw Error::reply(Reply::ERR_NEEDMOREPARAMS, name); + return sendError_(client, Reply::ERR_NEEDMOREPARAMS, name); bool exiting = cmd.handler(server, client, params); // after each command, see if we can send registration to the client diff --git a/srcs/commands/Connection.cpp b/srcs/commands/Connection.cpp index 27da08c..3ee37bf 100644 --- a/srcs/commands/Connection.cpp +++ b/srcs/commands/Connection.cpp @@ -21,7 +21,6 @@ */ #include "Client.hpp" -#include "Errors.hpp" #include "Handlers.hpp" #include "Reply.hpp" #include "Server.hpp" @@ -42,8 +41,10 @@ bool Handlers::quit(Server &server, Client &client, bool Handlers::ping(Server &server, Client &client, const std::vector ¶ms) { - if (params.empty() || params[0].empty()) - throw Error::reply(Reply::ERR_NOORIGIN, ""); + if (params.empty() || params[0].empty()) { + client.sendReply(Reply::reply(Reply::ERR_NOORIGIN)); + return false; + } client.send(Reply::fromClient(server.getServerName(), "PONG", server.getServerName(), params[0])); return false; diff --git a/srcs/commands/Messaging.cpp b/srcs/commands/Messaging.cpp index d9630d2..cd1a4ba 100644 --- a/srcs/commands/Messaging.cpp +++ b/srcs/commands/Messaging.cpp @@ -57,7 +57,7 @@ bool deliver(Server &server, Client &client, } if (params.size() < 2 || params[1].empty()) { if (!silent) - client.sendReply(Reply::reply(Reply::ERR_NOTEXTTOSEND, "")); + client.sendReply(Reply::reply(Reply::ERR_NOTEXTTOSEND)); return false; } const std::vector targets = Utils::split(params[0], ','); diff --git a/srcs/commands/Registration.cpp b/srcs/commands/Registration.cpp index 8a9d436..e4216b2 100644 --- a/srcs/commands/Registration.cpp +++ b/srcs/commands/Registration.cpp @@ -32,7 +32,6 @@ #include "Registration.hpp" #include "Client.hpp" -#include "Errors.hpp" #include "Handlers.hpp" #include "Parser.hpp" #include "Reply.hpp" @@ -91,7 +90,7 @@ void sendWelcome(Server &server, Client &client) { "are supported by this server")); // No MOTD: irssi (and most clients) consider registration finished only // after RPL_ENDOFMOTD (376) or ERR_NOMOTD (422). - client.sendReply(Reply::reply(Reply::ERR_NOMOTD, "")); + client.sendReply(Reply::reply(Reply::ERR_NOMOTD)); } } // namespace @@ -117,10 +116,10 @@ bool Handlers::cap(Server &server, Client &client, // PASS bool Handlers::pass(Server &server, Client &client, const std::vector ¶ms) { - if (client.isRegistered()) - throw Error::reply(Reply::ERR_ALREADYREGISTRED, ""); - if (params[0].empty()) - throw Error::reply(Reply::ERR_NEEDMOREPARAMS, "PASS"); + if (params[0].empty()) { + client.sendReply(Reply::reply(Reply::ERR_NEEDMOREPARAMS, "PASS")); + return false; + } // A wrong password is not reported here: it is reported (464) and the // link closed when NICK+USER complete, see tryRegister(). client.setPassAccepted(params[0] == server.getPassword()); @@ -130,16 +129,22 @@ bool Handlers::pass(Server &server, Client &client, // NICK bool Handlers::nick(Server &server, Client &client, const std::vector ¶ms) { - if (params.empty() || params[0].empty()) - throw Error::reply(Reply::ERR_NONICKNAMEGIVEN, ""); + if (params.empty() || params[0].empty()) { + client.sendReply(Reply::reply(Reply::ERR_NONICKNAMEGIVEN)); + return false; + } const std::string &nick = params[0]; - if (!isValidNick(nick)) - throw Error::reply(Reply::ERR_ERRONEUSNICKNAME, nick); + if (!isValidNick(nick)) { + client.sendReply(Reply::reply(Reply::ERR_ERRONEUSNICKNAME, nick)); + return false; + } ClientManager &clients = server.getClientManager(); const bool sameClient = Utils::foldCase(nick) == client.getNickCaseFolded(); - if (!sameClient && clients.hasNick(nick)) - throw Error::reply(Reply::ERR_NICKNAMEINUSE, nick); + if (!sameClient && clients.hasNick(nick)) { + client.sendReply(Reply::reply(Reply::ERR_NICKNAMEINUSE, nick)); + return false; + } if (client.isRegistered()) { // Registered client renaming itself: everyone who can see it - itself @@ -158,10 +163,10 @@ bool Handlers::nick(Server &server, Client &client, bool Handlers::user(Server &server, Client &client, const std::vector ¶ms) { (void)server; - if (client.isRegistered()) - throw Error::reply(Reply::ERR_ALREADYREGISTRED, ""); - if (params[0].empty() || params[3].empty()) - throw Error::reply(Reply::ERR_NEEDMOREPARAMS, "USER"); + if (params[0].empty() || params[3].empty()) { + client.sendReply(Reply::reply(Reply::ERR_NEEDMOREPARAMS, "USER")); + return false; + } client.setUser(params[0], params[3]); return false; } @@ -170,7 +175,7 @@ bool Registration::tryRegister(Server &server, Client &client) { if (client.isRegistered() || !client.hasIdentity()) return false; if (!client.hasPassAccepted()) { - client.sendReply(Reply::reply(Reply::ERR_PASSWDMISMATCH, "")); + client.sendReply(Reply::reply(Reply::ERR_PASSWDMISMATCH)); client.sendClosingMsg("Password incorrect"); return true; // event loop: flush, then close the connection } diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 879932c..681baaa 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -1,8 +1,6 @@ #include "Server.hpp" #include "Client.hpp" // for Client -#include "Errors.hpp" // for Error -#include "Reply.hpp" // for build #include "ServerHelper.hpp" // for getAdressFamilyStr, composeAddrfamAddrP... #include "SignalHandler.hpp" // for SignalHandler From 2bd3389e3bd0a42c42e875fc5857227cd1f19aa8 Mon Sep 17 00:00:00 2001 From: ArchCipher Date: Sat, 5 Sep 2026 21:48:20 +0200 Subject: [PATCH 332/349] fill readme --- README.md | 113 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 85 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 07d21ed..c33f3db 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,21 @@ *This project has been created as part of the 42 curriculum by blnunez-, kmurugan, rhaas.* -# ft_irc - IRC server +#

ft_irc - IRC Server

## Description ### 42 Project | Internet Relay Chat in C++98 ### Objective -Build an IRC server in C++98 that implements... - +Build an IRC server in C++98 that handles multiple concurrent clients over TCP, implementing the core IRC protocol (registration, channels, modes, private messaging, and operator commands) over an event-driven sockets layer. ### Overview -... +**ft_irc** is a 42 project that teaches event-driven I/O, the poll/epoll multiplexing model, and the IRC protocol. The server listens on a given port and password, accepts multiple clients via non-blocking sockets, registers them with PASS/NICK/USER, and lets them join channels, exchange private and channel messages, and manage channels with KICK/INVITE/TOPIC/MODE and operator privileges. --- ## Instructions -### Requirements - -- - ### Compile & Run ```sh @@ -31,6 +26,60 @@ make && ./ircserv make bonus && ./ircserv_bonus ``` +**NOTE:** The server binds an IPv4 and an IPv6 listening socket (`IPV6_V6ONLY`) and multiplexes them with `poll`. Connect with any IRC client (e.g. `irssi`) or a raw `nc` session. + +```sh +# connect with netcat and register +nc +PASS +NICK blnunez- +USER blnunez- 0 * :Bastien +JOIN #general +``` + +Port must be in the range `1-65535`, a password must be supplied. + +--- + +## IRC Command & Message Reference + +### Registration +- `PASS ` — authenticate during registration. +- `NICK ` — set/change the nick. +- `USER ` — complete registration credentials. +- `PING`/`PONG` — keepalive and latency check. + +### Channels +- `JOIN <#channel> []` — join (or create) a channel; optional key for `+k`. +- `PART <#channel> []` — leave a channel (deleted when empty). +- `KICK <#channel> []` — remove a member (requires operator rank). +- `INVITE <#channel>` — invite a user (subject to `+i`). +- `TOPIC <#channel> []` — view/set the topic (may require `+t`). +- `NAMES <#channel>` — list current members. +- `MODE <#channel> [modes]` — view/set channel modes and user modes. + +### Messaging +- `PRIVMSG ` — send a message. +- `NOTICE ` — send a notice (no auto-reply / CTCP handling). + +### Server / Operations +- `OPER ` — become an IRC operator. +- `QUIT []` — leave the network. + +### Channel Modes +| Mode | Argument | Meaning | +|------|----------|---------| +| `i` | no | Invite-only channel (`JOIN` requires an invite) | +| `t` | no | Only operators may set the topic | +| `k` | yes | Channel key (password) required to join | +| `o` | yes | Grant/revoke operator privilege to a member | +| `l` | yes | User limit — max concurrent members | + +### Channel User Modes +`q` Founder · `a` Admin · `o` Operator · `h` Half-operator · `v` Voice + +Modes act on the target `nick`: hosts `~` founder, `&` admin, `@` operator, `%` half-op, `+` voice. Privileges are rank-ordered (Founder > Admin > Operator > Half-op > Voice > none) and rules enforcing who may act on whom are checked on every MODE/KICK. + --- ## Clang tool commands @@ -57,46 +106,54 @@ NOTE: Review clang-tidy results and fix only what makes sense for this project. ## Key Challenges & Solutions -### Topic +TODO: add poll and client + +### Protocol-Correct Error Replies -**Challenge:** +**Challenge:** Every command can fail in several ways (wrong channel, no such nick, missing params, insufficient privileges) and each failure must produce the correct RFC 2812 numeric to the client. -**Solution:** +**Solution:** A `Reply` module maps each numeric code to its required parameter layout and trailing text. A central `Error` exception carries a pre-built reply. Command processing throws `Error::reply(...)` on the first invalid condition and the server loop catches it once, formats `:server :\r\n`, and sends it — guaranteeing exactly one well-formed reply per failure and keeping handlers free of repetitive error plumbing. + +### Channel Lifetime & Ownership + +**Challenge:** Channels are created on join, destroyed when the last member parts, and must never hold a pointer to a freed `Client`. + +**Solution:** `ChannelManager` owns `Channel` objects in a case-folded map and cleans up empty channels after PART/KICK/leaveAll. `MemberSet` stores `Client*` + `ModeSet`. `ClientManager::remove` first parts the client from every channel (via `ChannelManager::leaveAll`) before freeing the `Client`, so channel member vectors never dangle. --- ## Resources ### IRC protocol -[RFC 2810 - IRC: Architecture](https://rfc-editor.org/rfc/rfc2810) -[RFC 2811 - IRC: Channel Management](https://rfc-editor.org/rfc/rfc2811) -[RFC 2812 - IRC: Client Protocol](https://rfc-editor.org/rfc/rfc2812) -[RFC 2813 - IRC: Server Protocol](https://rfc-editor.org/rfc/rfc2813) -[ircgod](https://ircgod.com) -[Model IRC client protocol](https://modern.ircdocs.horse) +- [RFC 2810 - IRC: Architecture](https://rfc-editor.org/rfc/rfc2810) +- [RFC 2811 - IRC: Channel Management](https://rfc-editor.org/rfc/rfc2811) +- [RFC 2812 - IRC: Client Protocol](https://rfc-editor.org/rfc/rfc2812) +- [RFC 2813 - IRC: Server Protocol](https://rfc-editor.org/rfc/rfc2813) +- [ircgod](https://ircgod.com) +- [Model IRC client protocol](https://modern.ircdocs.horse) ### rfc reading tips -[Tips for Reading RFCs](https://www.rfc-editor.org/series/rfc-tips/) +- [Tips for Reading RFCs](https://www.rfc-editor.org/series/rfc-tips/) ### Reference IRC client and server -[Server: ergo chat (formerly Oragono)](https://github.com/ergochat/ergo) -[Client: irssi](https://github.com/irssi/irssi) +- [Server: ergo chat (formerly Oragono)](https://github.com/ergochat/ergo) +- [Client: irssi](https://github.com/irssi/irssi) ### Sockets -[Sockets & Networking](https://web.mit.edu/6.031/www/sp19/classes/23-sockets-networking/) -[All about sockets](https://docs.oracle.com/javase/tutorial/networking/sockets/index.html) - -[CS107 Sockets Programming](https://web.stanford.edu/class/archive/cs/cs107/cs107.1238/lectures/20/) -[Beej's Guide to Network Programming](https://beej.us/guide/bgnet/) - What is a +- [Sockets & Networking](https://web.mit.edu/6.031/www/sp19/classes/23-sockets-networking/) +- [All about sockets](https://docs.oracle.com/javase/tutorial/networking/sockets/index.html) +- [CS107 Sockets Programming](https://web.stanford.edu/class/archive/cs/cs107/cs107.1238/lectures/20/) +- [Beej's Guide to Network Programming](https://beej.us/guide/bgnet/) - What is a socket?, IP Addresses, structs, and Data Munging, Jumping from IPv4 to IPv6, System Calls or Bust ### DCC protocol -[Direct Client-to-Client protocol (DCC)](https://modern.ircdocs.horse/dcc.html) +- [Direct Client-to-Client protocol (DCC)](https://modern.ircdocs.horse/dcc.html) ### Use of AI -- -- +- Learning and verification: Clarifying IRC numeric replies, and the RFC 2812 grammar. +- Documentation: Proofreading and refining the README and project descriptions. +- Testing and debugging: Generating protocol-level test scenarios and reviewing edge cases. --- From f709f94cb35b4bdadcf0b830eeb9b9fc3e255857 Mon Sep 17 00:00:00 2001 From: ArchCipher Date: Thu, 17 Sep 2026 18:27:30 +0200 Subject: [PATCH 333/349] add bonus --- README.md | 55 ++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 40 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index c33f3db..c8b516d 100644 --- a/README.md +++ b/README.md @@ -32,9 +32,9 @@ make bonus && ./ircserv_bonus # connect with netcat and register nc PASS -NICK blnunez- -USER blnunez- 0 * :Bastien -JOIN #general +NICK +USER 0 * : +JOIN #channel ``` Port must be in the range `1-65535`, a password must be supplied. @@ -50,7 +50,7 @@ Port must be in the range `1-65535`, a password must be supplied. - `PING`/`PONG` — keepalive and latency check. ### Channels -- `JOIN <#channel> []` — join (or create) a channel; optional key for `+k`. +- `JOIN <#channel> []` — join (or create) a channel, optional key for `+k`. - `PART <#channel> []` — leave a channel (deleted when empty). - `KICK <#channel> []` — remove a member (requires operator rank). - `INVITE <#channel>` — invite a user (subject to `+i`). @@ -59,13 +59,9 @@ Port must be in the range `1-65535`, a password must be supplied. - `MODE <#channel> [modes]` — view/set channel modes and user modes. ### Messaging -- `PRIVMSG ` — send a message. +- `PRIVMSG ` — send a message. The bonus bot answers PRIVMSG addressed to it. - `NOTICE ` — send a notice (no auto-reply / CTCP handling). -### Server / Operations -- `OPER ` — become an IRC operator. -- `QUIT []` — leave the network. - ### Channel Modes | Mode | Argument | Meaning | |------|----------|---------| @@ -82,21 +78,44 @@ Modes act on the target `nick`: hosts `~` founder, `&` admin, `@` operator, `%` --- +## Bonus: DCC Bot & File Transfer + +Built with `make bonus` → `./ircserv_bonus `. + +### DCCBot + +A bot named `DCCBot` (also reached as `bot` / `BOT` / `#bot`, or by sending a bot command to any channel) that intercepts `PRIVMSG`: bot commands are answered with `PRIVMSG`, CTCP queries with `NOTICE`. + +| Command | Action | +|------------------|--------| +| `!help` | List the available bot commands | +| `!version` | Report the bot/server version | +| `!whoami` | Show the requester's nick | +| `!send ` | Start a DCC SEND offer of `files/` | +| `!accept` | Accept the pending transfer offer | +| `!reject` | Reject the pending transfer offer | + +CTCP queries (`\x01VERSION\x01`, `\x01PING \x01`) sent to the bot via `PRIVMSG` are answered by the bot with `NOTICE`. + +### File transfer (DCC SEND) + +`!send` opens a listener on an OS-picked port and hands the target a CTCP `DCC SEND 127.0.0.1 ` line. The receiver replies with `!accept`, connects to the advertised address/port and receives the file in 1 KiB chunks. Only one transfer is active at a time (`bot busy` otherwise). Pending offers expire after 60 seconds. Offered files must live in a `files/` directory next to the server; filenames containing `/` or `..` are rejected. + +--- + ## Clang tool commands ```sh -# Run clang-format before PR (use clang-format version 21) +# clang-format all headers and sources make format -``` -```sh -# Run clang-tidy on individual file +# clang-tidy one file make tidy FILE=srcs/ -# Run clang-tidy on multiple files (filenames without spaces) +# clang-tidy on multiple files (filenames without spaces) make tidy FILE="srcs/ srcs/" -# Run clang-tidy on all .cpp/.hpp under srcs and includes +# clang-tidy every source and header make tidy-all ``` @@ -120,6 +139,12 @@ TODO: add poll and client **Solution:** `ChannelManager` owns `Channel` objects in a case-folded map and cleans up empty channels after PART/KICK/leaveAll. `MemberSet` stores `Client*` + `ModeSet`. `ClientManager::remove` first parts the client from every channel (via `ChannelManager::leaveAll`) before freeing the `Client`, so channel member vectors never dangle. +### Bonus: CTCP & DCC SEND through the Bot + +**Challenge:** The CTCP mechanic used by DCC is barely documented anywhere. You can forward a CTCP by stripping the wrapping `\x01` bytes and re-sending what is inside. But to actually run a file transfer the server needs to hand the receiver a `\x01DCC SEND \x01` line, open a listener the receiver can connect to, and stream the file. Wiring the two bonuses together (bot + transfer manager) with the bot as the middleman got complicated as the design evolved, and real clients silently abstract all of this away so there was no reference. + +**Solution:** Make the bot the DCC sender. `!send` asks the `TransferManager` to validate the filename, open a listener on an OS-picked port, and return the offer port/size. The bot then builds the `DCC SEND ... 127.0.0.1 ` CTCP and delivers it wrapped in `\x01` to the target over `PRIVMSG`. The receiver just replies `!accept` and connects to the advertised address/port — which means the whole flow works over a plain `nc` session, no IRC client needed. Using the bot to construct and send the CTCP also makes the query path symmetric: the same `PRIVMSG` interception that answers `!help`/`!version` carries the DCC offer, and CTCP responses go back out as `NOTICE`. Transfer state (offer → accept/reject → stream → expire after 60 s) stayed in `TransferManager`, while the bot is a dumb messenger. + --- ## Resources From 34d5fcfa98f9882c4f2c27db014896650300a595 Mon Sep 17 00:00:00 2001 From: Rapha Date: Fri, 18 Sep 2026 14:37:36 +0200 Subject: [PATCH 334/349] feat: * small changes to the already present info * added a paragraph about poll and buffers --- README.md | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index c8b516d..61c76c2 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ *This project has been created as part of the 42 curriculum by blnunez-, kmurugan, rhaas.* -#

ft_irc - IRC Server

+#ft_irc - IRC Server ## Description @@ -10,7 +10,7 @@ Build an IRC server in C++98 that handles multiple concurrent clients over TCP, implementing the core IRC protocol (registration, channels, modes, private messaging, and operator commands) over an event-driven sockets layer. ### Overview -**ft_irc** is a 42 project that teaches event-driven I/O, the poll/epoll multiplexing model, and the IRC protocol. The server listens on a given port and password, accepts multiple clients via non-blocking sockets, registers them with PASS/NICK/USER, and lets them join channels, exchange private and channel messages, and manage channels with KICK/INVITE/TOPIC/MODE and operator privileges. +**ft_irc** is a 42 project that teaches event-driven I/O, the poll multiplexing model, and the IRC protocol. The server listens on a given port and password, accepts multiple clients via non-blocking sockets, registers them with PASS/NICK/USER, and lets them join channels, exchange private and channel messages, and manage channels with KICK/INVITE/TOPIC/MODE and operator privileges. --- @@ -37,7 +37,8 @@ USER 0 * : JOIN #channel ``` -Port must be in the range `1-65535`, a password must be supplied. +Port must be in the range `1-65535`, a password must be supplied.\ +Please note that you may need system admin permissions to use privileged ports (usually 1–1023). --- @@ -47,14 +48,13 @@ Port must be in the range `1-65535`, a password must be supplied. - `PASS ` — authenticate during registration. - `NICK ` — set/change the nick. - `USER ` — complete registration credentials. -- `PING`/`PONG` — keepalive and latency check. ### Channels - `JOIN <#channel> []` — join (or create) a channel, optional key for `+k`. - `PART <#channel> []` — leave a channel (deleted when empty). - `KICK <#channel> []` — remove a member (requires operator rank). -- `INVITE <#channel>` — invite a user (subject to `+i`). -- `TOPIC <#channel> []` — view/set the topic (may require `+t`). +- `INVITE <#channel>` — invite a user (may require operator rank). +- `TOPIC <#channel> []` — view/set the topic (may require operator rank). - `NAMES <#channel>` — list current members. - `MODE <#channel> [modes]` — view/set channel modes and user modes. @@ -72,9 +72,12 @@ Port must be in the range `1-65535`, a password must be supplied. | `l` | yes | User limit — max concurrent members | ### Channel User Modes -`q` Founder · `a` Admin · `o` Operator · `h` Half-operator · `v` Voice +`o` Operator · none + +Modes act on the target `nick` and are shown as `@` for operator or none for regular users. Privileges are checked on every call to a command that needs elevated privileges (this may also depend on the state of a channel, e.g. whether changing the TOPIC is restricted or not). -Modes act on the target `nick`: hosts `~` founder, `&` admin, `@` operator, `%` half-op, `+` voice. Privileges are rank-ordered (Founder > Admin > Operator > Half-op > Voice > none) and rules enforcing who may act on whom are checked on every MODE/KICK. +### Other +- `PING`/`PONG` — keepalive and latency check. --- @@ -84,6 +87,11 @@ Built with `make bonus` → `./ircserv_bonus `. ### DCCBot +| Abbr.| Meaning | +|------|---------------------------| +| CTCP | Client-to-client protocol | +| DDC | Direct Client-to-Client | + A bot named `DCCBot` (also reached as `bot` / `BOT` / `#bot`, or by sending a bot command to any channel) that intercepts `PRIVMSG`: bot commands are answered with `PRIVMSG`, CTCP queries with `NOTICE`. | Command | Action | @@ -125,14 +133,18 @@ NOTE: Review clang-tidy results and fix only what makes sense for this project. ## Key Challenges & Solutions -TODO: add poll and client - ### Protocol-Correct Error Replies **Challenge:** Every command can fail in several ways (wrong channel, no such nick, missing params, insufficient privileges) and each failure must produce the correct RFC 2812 numeric to the client. **Solution:** A `Reply` module maps each numeric code to its required parameter layout and trailing text. A central `Error` exception carries a pre-built reply. Command processing throws `Error::reply(...)` on the first invalid condition and the server loop catches it once, formats `:server :\r\n`, and sends it — guaranteeing exactly one well-formed reply per failure and keeping handlers free of repetitive error plumbing. +### Usage of `poll` and buffers + +**Challenge:** Apart from setting up sockets correctly for receiving data streams, caching and handling of incomplete or overlong messages needed to be implemented. Additionally, a certain amount of fault tolerance was desired, e.g. for not disconnecting a client on the first too long message. + +**Solution:** Before passing on any data to command parsers and processors, the validity of a full line is checked. No incomplete or overlong data is ever forwarded to the internal handlers. Additionally an error counter is installed separately for each client, which limits the amount of consecutive errors and is reset on every valid input. + ### Channel Lifetime & Ownership **Challenge:** Channels are created on join, destroyed when the last member parts, and must never hold a pointer to a freed `Client`. From 48de72b0f73f8a78f5b90da5be64c8581787d6a1 Mon Sep 17 00:00:00 2001 From: ArchCipher Date: Thu, 17 Sep 2026 22:12:23 +0200 Subject: [PATCH 335/349] bugfix: set +k and +l correctly --- includes/CommandProcessor.hpp | 2 +- includes/Socket.hpp | 2 +- srcs/commands/CommandProcessor.cpp | 10 +-- srcs/server/Channel.cpp | 4 + srcs/server/Server.cpp | 28 +++++-- srcs/server/Socket.cpp | 38 +++++----- tests/churn_test.py | 114 ++++++++++++++++++++++++++++ tests/overlong_test.py | 118 +++++++++++++++++++++++++++++ tests/probe_test.py | 84 ++++++++++++++++++++ tests/reap_test.py | 82 ++++++++++++++++++++ tests/stall_test.py | 38 ++++++++++ tests/throw_test.py | 60 +++++++++++++++ 12 files changed, 544 insertions(+), 36 deletions(-) create mode 100644 tests/churn_test.py create mode 100644 tests/overlong_test.py create mode 100644 tests/probe_test.py create mode 100644 tests/reap_test.py create mode 100644 tests/stall_test.py create mode 100644 tests/throw_test.py diff --git a/includes/CommandProcessor.hpp b/includes/CommandProcessor.hpp index d2d9f08..2b7375d 100644 --- a/includes/CommandProcessor.hpp +++ b/includes/CommandProcessor.hpp @@ -52,5 +52,5 @@ class CommandProcessor { bool run(Server &server, Client &client, const std::string &line); void add(const std::string &name, const Command &cmd); - void inputTooLong(Server &srv, Client &client); + void inputTooLong(Client &client); }; diff --git a/includes/Socket.hpp b/includes/Socket.hpp index ee13af1..380cb02 100644 --- a/includes/Socket.hpp +++ b/includes/Socket.hpp @@ -42,7 +42,7 @@ class Socket { bool checkSocketData(int const fd); std::string getCRLFLineFromBuffer(int const fd, bool &buffHasMoreCmds, bool &msgTooLong); - bool sendMsg(int const fd, std::string const &msg); + void sendMsg(int const fd, std::string const &msg); bool socketCloseMarker(int const fd); void closeSocket(int const fd); diff --git a/srcs/commands/CommandProcessor.cpp b/srcs/commands/CommandProcessor.cpp index e0c1733..d719d6b 100644 --- a/srcs/commands/CommandProcessor.cpp +++ b/srcs/commands/CommandProcessor.cpp @@ -83,10 +83,6 @@ void CommandProcessor::add(const std::string &name, const Command &cmd) { commands_.insert(std::make_pair(name, cmd)); } -void CommandProcessor::inputTooLong(Server &srv, Client &client) { - (void)srv; - std::ostringstream oss; - oss << "Hello fd:" << client.getFd() << " : msg too long.\r\n"; - client.send(oss.str()); - // tbd. -} \ No newline at end of file +void CommandProcessor::inputTooLong(Client &client) { + client.sendReply(Reply::reply(Reply::ERR_INPUTTOOLONG)); +} diff --git a/srcs/server/Channel.cpp b/srcs/server/Channel.cpp index 39e312b..5690431 100644 --- a/srcs/server/Channel.cpp +++ b/srcs/server/Channel.cpp @@ -184,6 +184,7 @@ void Channel::applyKeyMode_(Client &client, const Mode::ModeChange &change, Mode::ModeChanges &applied) { if (change.op == Mode::Remove) { setKey(""); + modes_.setMode(Mode::Key, false); applied.push_back(change); return; } @@ -199,6 +200,7 @@ void Channel::applyKeyMode_(Client &client, const Mode::ModeChange &change, return; } setKey(change.arg); + modes_.setMode(Mode::Key, true); applied.push_back(change); } @@ -210,6 +212,7 @@ void Channel::applyUserLimitMode_(Client &client, "expressed in base 10"; if (change.op == Mode::Remove) { setUserLimit(0); + modes_.setMode(Mode::UserLimit, false); applied.push_back(change); return; } @@ -222,6 +225,7 @@ void Channel::applyUserLimitMode_(Client &client, return; } setUserLimit(val); + modes_.setMode(Mode::UserLimit, true); applied.push_back(change); } diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 681baaa..981a41a 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -114,6 +114,12 @@ void Server::processClientSockets_() { ++idx) { pollfd &pfd = pollFds_.at(idx); + if (sockets_.socketCloseMarker(pfd.fd)) { + removeClientSocketPfd_(pfd.fd, "Socket marked for closing."); + --idx; + continue; + } + if (!(pfd.revents & (POLLIN | POLLERR | POLLHUP))) continue; @@ -122,9 +128,11 @@ void Server::processClientSockets_() { std::string closeMsg; } closeData = {false, ""}; - if (pfd.revents & (POLLERR | POLLHUP)) { + // printf 'PASS pass\r\nNICK bob\r\nUSER u 0 * :Real\r\nJOIN #chan\r\nMODE #chan\r\nQUIT\r\n' | nc 127.0.0.1 6667 + // BUG: on this line POLLHUP wins and it never registers client, directly closes + if (pfd.revents & POLLERR) { closeData.closeSocket = true; - closeData.closeMsg = "Socket POLLERR / POLLHUP."; + closeData.closeMsg = "Socket POLLERR."; } else if (!(sockets_.checkSocketData(pfd.fd) && sockets_.updateBuffer_(pfd))) { closeData.closeSocket = true; @@ -153,7 +161,7 @@ void Server::processClientSockets_() { } if (msgTooLong) - cmds_.inputTooLong(*this, *client); + cmds_.inputTooLong(*client); if (cmdLine.empty()) continue; @@ -176,6 +184,9 @@ void Server::processClientSockets_() { } void Server::removeClientSocketPfd_(int const fd, std::string const &msg) { + // avoid double removal + if (!sockets_.checkSocketData(fd)) + return; if (!sockets_.isServerSocket(fd)) { if (!msg.empty()) { Client *client = clients_.find(fd); @@ -278,7 +289,13 @@ void Server::run() { return; while (!SignalHandler::stopLoopSignalReceived()) { - int pollResult = poll(pollFds_.data(), pollFds_.size(), -1); + // Closing socket in Server::sendMsg messes up processClientSockets_'s loop. + // It erases pollFds while processClientSockets_ is iterating + // So, Socket::sendMsg only marks the failed fds to be closed. + // Reaping client happens in processClientSockets_ when poll() returns. + // With poll(-1), an idle loop would sleep forver and never reap these fds, + // so wake it up every 1000. + int pollResult = poll(pollFds_.data(), pollFds_.size(), 1000); if (pollResult == -1) { if (SignalHandler::stopLoopSignalReceived()) break; @@ -301,6 +318,5 @@ void Server::sendMsg(int const fd, std::string const &msg) { << msg.substr(0, msg.length() - 2); logger_.logMsg(oss.str(), Logger::lmOut); } - if (!sockets_.sendMsg(fd, msg)) - removeClientSocketPfd_(fd, "Error while sending Msg."); + sockets_.sendMsg(fd, msg); } diff --git a/srcs/server/Socket.cpp b/srcs/server/Socket.cpp index 528e0d4..59af356 100644 --- a/srcs/server/Socket.cpp +++ b/srcs/server/Socket.cpp @@ -103,6 +103,8 @@ size_t Socket::bufferLengthAndSanityCheck_(SocketIOData &sIOD, rdBuffer.erase(0, lfcrPos + 2); sIOD.discardMode = false; return std::string::npos; + // TODO handle remaining valid line. + // else client is stalled until next recv. } if (!rdBuffer.empty() && '\r' == *rdBuffer.rbegin()) rdBuffer = "\r"; @@ -115,31 +117,26 @@ size_t Socket::bufferLengthAndSanityCheck_(SocketIOData &sIOD, sendMsg * send string msg to a network socket * uses i/o buffers and error counters in SocketIOData - * returns false in case of too many errors - caller must close socket - - NOTE: caller MUST close socket when sendMsg returns false */ -bool Socket::sendMsg(int const fd, std::string const &msg) { +void Socket::sendMsg(int const fd, std::string const &msg) { SocketIOData *sIODptr = getSockIOData(fd); if (!sIODptr) - return false; + return; SocketIOData &sIOD = *sIODptr; if (msg.length() > sIOD.writeBuffer.max_size() - sIOD.writeBuffer.length()) { sIOD.closeFd = true; - return false; + return; } sIOD.writeBuffer += msg; ssize_t bytesSent = send(fd, sIOD.writeBuffer.c_str(), sIOD.writeBuffer.length(), MSG_NOSIGNAL); if (bytesSent <= 0) { ++sIOD.sendErrorCount; - if (sIOD.sendErrorCount >= SOCKETERRORLIMIT) { + if (sIOD.sendErrorCount >= SOCKETERRORLIMIT) sIOD.closeFd = true; - return false; - } - return true; + return; } sIOD.sendErrorCount = 0; @@ -147,33 +144,32 @@ bool Socket::sendMsg(int const fd, std::string const &msg) { sIOD.writeBuffer.erase(0, bytesSent); else sIOD.writeBuffer.clear(); - return true; } bool Socket::socketCloseMarker(int const fd) { SocketIOData *sIODptr = getSockIOData(fd); - if (!sIODptr) - return true; - return sIODptr->closeFd; + // avoid double removal + return sIODptr && sIODptr->closeFd; } void Socket::closeSocket(int const fd) { SocketIOData *sIODptr = getSockIOData(fd); - int const addrFam = srvHelp::sockAddrFamily(fd); - close(fd); - - if (!sIODptr) + if (!sIODptr) { + close(fd); return; + } + // call sockAddrFamily only on known fd + int const addrFam = srvHelp::sockAddrFamily(fd); SocketIOData &sIOD = *sIODptr; std::ostringstream oss; - if (sIOD.isSrvSocket) { + if (sIOD.isSrvSocket) oss << "Closing server socket (" << srvHelp::getAdressFamilyStr(addrFam) << ")"; - } else { + else oss << "Closing client sockFd:" << fd; - } logger_.logMsg(oss.str(), Logger::lmInfo); + close(fd); sockIODataStore_.erase(fd); } diff --git a/tests/churn_test.py b/tests/churn_test.py new file mode 100644 index 0000000..7907896 --- /dev/null +++ b/tests/churn_test.py @@ -0,0 +1,114 @@ +import socket +import struct +import sys +import time + +HOST = "127.0.0.1" +PORT = int(sys.argv[1]) if len(sys.argv) > 1 else 6667 +PW = sys.argv[2] if len(sys.argv) > 2 else "pass" + +N = 20 +STALL_IDX = N - 1 # the member that stops reading + + +def reg(i): + s = socket.create_connection((HOST, PORT)) + s.sendall(("PASS %s\r\nNICK usr%02d\r\nUSER u%d 0 * :x\r\n" + % (PW, i, i)).encode()) + time.sleep(0.15) + s.setblocking(False) + try: + while s.recv(4096): + pass + except BlockingIOError: + pass + s.setblocking(True) + return s + + +def server_alive(): + try: + c = socket.create_connection((HOST, PORT), timeout=1) + c.close() + return True + except OSError: + return False + + +def drain(s): + s.setblocking(False) + try: + while s.recv(4096): + pass + except BlockingIOError: + pass + s.setblocking(True) + + +clients = [reg(i) for i in range(N)] +for s in clients: + s.sendall(b"JOIN #big\r\n") +time.sleep(0.5) +for s in clients: + drain(s) + +print("members in #big:", N, "(one member, usr%02d, stops reading; rest live)" % STALL_IDX) + +# broadcast storm: 3 senders x 20 msgs -> each message goes to all members, +# the stalled member's socket buffer fills and server send() starts failing +for i in range(3): + for k in range(20): + clients[i].sendall(("PRIVMSG #big :flood %d\r\n" % k).encode()) + +# JOIN/PART churn on one member +m = clients[10] +for k in range(60): + m.sendall(b"PART #big\r\n") + m.sendall(b"JOIN #big\r\n") + time.sleep(0.005) + +# abrupt RST disconnects (no QUIT) mid-flow +for k in (3, 7, 15): + clients[k].setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, + struct.pack("ii", 1, 0)) + clients[k].close() + clients[k] = None + +time.sleep(1.5) + +# every still-healthy, non-stalled member must answer a final PING with PONG +lost = [] +for i, s in enumerate(clients): + if s is None or i == STALL_IDX: + continue + drain(s) + s.sendall(b"PING :final\r\n") + s.settimeout(2.5) + ok = False + got = b"" + try: + while True: + c = s.recv(2048) + if not c: + break + got += c + if b"PONG" in got: + ok = True + break + except (socket.timeout, OSError): + pass + if not ok: + lost.append(i) + +alive = server_alive() +for s in clients: + try: + if s is not None: + s.close() + except Exception: + pass + +print(" server alive :", "YES" if alive else "NO (DIED)") +print(" healthy members that lost their PING/PONG:", len(lost), (lost or "none")) +print(" RESULT:", + "PASS" if (alive and not lost) else "FAIL - inspect state above") diff --git a/tests/overlong_test.py b/tests/overlong_test.py new file mode 100644 index 0000000..85db765 --- /dev/null +++ b/tests/overlong_test.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python3 +# Overlong-line tests for the ft_irc reader. +# Run on the machine that is running the server: +# python3 overlong_test.py [port] [password] +import socket +import sys +import time + +HOST = "127.0.0.1" +PORT = int(sys.argv[1]) if len(sys.argv) > 1 else 6667 +PW = sys.argv[2] if len(sys.argv) > 2 else "pass" + + +def read_until_quiet(s, timeout): + s.setblocking(False) + data = b"" + end = time.time() + timeout + eof = False + while time.time() < end: + try: + chunk = s.recv(4096) + except BlockingIOError: + time.sleep(0.05) + continue + if not chunk: + eof = True + break + data += chunk + s.setblocking(True) + return data, eof + + +def connect(nick, user): + s = socket.create_connection((HOST, PORT)) + s.sendall(("PASS %s\r\nNICK %s\r\nUSER %s 0 * :x\r\n" + % (PW, nick, user)).encode()) + s.setblocking(False) + end = time.time() + 2.5 + data = b"" + while time.time() < end: + try: + chunk = s.recv(4096) + except BlockingIOError: + time.sleep(0.02) + continue + if not chunk: + break + data += chunk + if b" 001 " in data: + break + s.setblocking(True) + return s, data + + +def drain(s): + s.setblocking(False) + try: + while s.recv(4096): + pass + except BlockingIOError: + pass + s.setblocking(True) + + +print("=== Test A: 600-byte line then NAMES, single packet ===") +print(" expect: 417 YES + 366 YES ") +s, reg = connect("ovlA", "u1") +drain(s) +s.sendall(b"A" * 600 + b"\r\nNAMES\r\n") +data, eof = read_until_quiet(s, 2.0) +got417 = b"417" in data +got366 = b"366" in data +print(" 417 (input too long) :", "YES" if got417 else "no") +print(" 366 (End of NAMES) :", "YES" if got366 else "no") +print(" -> PASS (drained at once) " if (got417 and got366) else + " -> FAIL (NAMES stuck until next poll -> apply fix #5) ") +if got417 and not got366: + print(" poke: sending PING to wake the poll loop...") + s.sendall(b"PING :wake\r\n") + data2, eof2 = read_until_quiet(s, 1.0) + print(" 366 after the poke :", + "YES (confirms NAMES was stuck in readBuffer)" if b"366" in data2 + else "no") +s.close() + +print() +print("=== Test B: overflow with no CRLF ever: 417, then close ===") +print(" expect: 417 YES + closed YES (one 417, then close after the grace limit)") +s, reg = connect("ovlB", "u2") +drain(s) +all_data = b"" +any_closed = False +s.sendall(b"B" * 600) # 1st poll: discardMode -> 417 +time.sleep(0.3) +d, e = read_until_quiet(s, 0.5) +all_data += d +any_closed = any_closed or e +s.sendall(b"B" * 600) # 2nd poll: discard keeps counting +time.sleep(0.3) +s.sendall(b"B" * 600) # 3rd poll: limit -> closeFd +d, e = read_until_quiet(s, 1.5) +all_data += d +any_closed = any_closed or e + +got417 = b"417" in all_data +print(" 417 seen :", "YES" if got417 else "no") +print(" connection closed :", "YES" if any_closed else "no") +if got417 and any_closed: + print(" -> PASS (one 417, then closed by the discard-limit)") +elif not got417: + print(" -> FAIL (no 417 ever sent)") + print(" close message from server: %r" % all_data[:160]) +else: + print(" -> FAIL (server kept connection open)") +s.close() +print() +print("note: close message like '[Error while composing command line from buffer.]'") +print("is the generic label of the discard-limit close; the 417 precedes it.") diff --git a/tests/probe_test.py b/tests/probe_test.py new file mode 100644 index 0000000..e8d715b --- /dev/null +++ b/tests/probe_test.py @@ -0,0 +1,84 @@ +import socket +import sys +import time + +HOST = "127.0.0.1" +PORT = int(sys.argv[1]) if len(sys.argv) > 1 else 6667 +PW = sys.argv[2] if len(sys.argv) > 2 else "pass" + + +def server_alive(): + try: + c = socket.create_connection((HOST, PORT), timeout=1) + c.close() + return True + except OSError: + return False + + +def probe(idx, payload): + s = socket.create_connection((HOST, PORT)) + s.sendall(("PASS %s\r\nNICK probe%d\r\nUSER u 0 * :x\r\n" + % (PW, idx)).encode()) + time.sleep(0.3) + s.setblocking(False) + try: + while s.recv(4096): + pass + except BlockingIOError: + pass + s.setblocking(True) + s.sendall(payload) + time.sleep(0.4) + s.setblocking(False) + data = b"" + eof = False + end = time.time() + 0.6 + while time.time() < end: + try: + chunk = s.recv(4096) + except BlockingIOError: + time.sleep(0.02) + continue + if not chunk: + eof = True + break + data += chunk + s.setblocking(True) + s.close() + return server_alive(), eof, data + + +CASES = [ + ("NUL byte in line", b"PING \x00probe\r\n"), + ("raw binary (256 bytes)", bytes(range(256)) * 4), + ("trailing CR (CRCRLF)", b"PING :ok\r\r\n"), + ("bare LF line", b"PING :lf\n"), + ("two bare LF lines", b"PING :a\nPING :b\n"), + ("only CRs", b"\r\r\r\r"), + ("empty lines", b"\r\n\r\n\r\n"), + ("spaces-only line", b" \r\n"), + ("exact max line (510)", b"A" * 510 + b"\r\nPING :ok\r\n"), + ("one over max (511)", b"A" * 511 + b"\r\nPING :ok\r\n"), + ("huge line + trailing cmd", b"B" * 4096 + b"\r\nPING :ok\r\n"), + ("re-register PASS", b"PASS pass\r\nPING :ok\r\n"), + ("long NICK (400 chars)", b"NICK " + b"n" * 400 + b"\r\nPING :ok\r\n"), + ("50 noisy PRIVMSGs", (b"PRIVMSG #ghost :" + b"y" * 480 + b"\r\n") * 50), +] + +print("case alive closed reply") +print("-" * 70) +all_ok = True +for i, (name, payload) in enumerate(CASES): + alive, eof, data = probe(i, payload) + first = repr(data[:40]) if data else "(nothing)" + if not alive: + all_ok = False + mark = "*** SERVER DIED ***" + else: + mark = "ok" + print("%-26s %-5s %-7s %-45s %s" + % (name, "yes" if alive else "NO", "yes" if eof else "no", + first, mark)) +print("-" * 70) +print("server survived every probe:", "YES" if all_ok else "NO - fix the crash") diff --git a/tests/reap_test.py b/tests/reap_test.py new file mode 100644 index 0000000..7b7c0a8 --- /dev/null +++ b/tests/reap_test.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +# Proves the loop reaps a send-failing socket. The stalled member's receive +# buffer is shrunk BEFORE connect and the flood is large enough (>= 30 MB) to +# close its window even with macOS loopback autotuning, forcing the server's +# sends to fail -> closeFd -> marker sweep reaps it. +# python3 reap_test.py [port] [password] +import socket +import sys +import time + +HOST = "127.0.0.1" +PORT = int(sys.argv[1]) if len(sys.argv) > 1 else 6667 +PW = sys.argv[2] if len(sys.argv) > 2 else "pass" +NMSG = int(sys.argv[3]) if len(sys.argv) > 3 else 60000 # ~30 MB + + +def make_client(buf=0): + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + if buf: + s.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, buf) # before connect + s.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, buf) + s.connect((HOST, PORT)) + return s + + +def reg(s, nick): + s.sendall(("PASS %s\r\nNICK %s\r\nUSER u 0 * :x\r\n" % (PW, nick)).encode()) + time.sleep(0.25) + s.setblocking(False) + try: + while s.recv(4096): + pass + except BlockingIOError: + pass + s.setblocking(True) + + +st = make_client(buf=1024) +reg(st, "stall") +sndr = make_client(0) +reg(sndr, "sndr") +st.sendall(b"JOIN #c\r\n") +sndr.sendall(b"JOIN #c\r\n") +time.sleep(0.4) +for s in (sndr, st): + s.setblocking(False) + try: + while s.recv(4096): + pass + except BlockingIOError: + pass + s.setblocking(True) + +st.setblocking(False) +print("flooding #c with %d messages (~%.0f MB)..." % (NMSG, NMSG * 0.00051)) +t0 = time.time() +for k in range(NMSG): + sndr.sendall(("PRIVMSG #c :%08d-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\r\n" % k).encode()) + if k % 500 == 0: + time.sleep(0.002) +print("flood done in %.1fs; watching up to 10s for the close..." % (time.time() - t0)) + +closed = False +end = time.time() + 10 +while time.time() < end: + try: + c = st.recv(4096) + except BlockingIOError: + time.sleep(0.05) + continue + if not c: + closed = True + break +st.close() +sndr.close() +if closed: + print(" stalled connection reaped -> PASS (loop sweep works)") +else: + print(" still open after 10s.") + print(" NEXT: while it runs, watch the server console for 'Sendto FD' lines") + print(" naming the stalled client's fd - if those keep coming and never stop,") + print(" the window never closed (autotune ate the flood).") \ No newline at end of file diff --git a/tests/stall_test.py b/tests/stall_test.py new file mode 100644 index 0000000..3e088ec --- /dev/null +++ b/tests/stall_test.py @@ -0,0 +1,38 @@ +import socket +import sys +import time + +host = "localhost" +port = int(sys.argv[1]) if len(sys.argv) > 1 else 6667 +password = sys.argv[2] if len(sys.argv) > 2 else "pass" + +def connect(nick, user): + s = socket.create_connection((host, port)) + s.sendall(("PASS %s\r\nNICK %s\r\nUSER %s 0 * :x\r\n" + % (password, nick, user)).encode()) + # read until registration completes (welcome numerics) so delivery works + time.sleep(0.3) + s.setblocking(False) + try: + while s.recv(4096): + pass + except BlockingIOError: + pass + s.setblocking(True) + return s + +# A: registers, then NEVER reads again (stalls its TCP receive window) +a = connect("stalled", "u1") + +# B: floods A with enough max-length PRIVMSGs to fill A's kernel buffer +b = connect("flooder", "u2") +body = "x" * 480 +for i in range(400): + b.sendall(("PRIVMSG stalled :%s\r\n" % body).encode()) + time.sleep(0.01) + +print("flood sent; A is silently stalled. sleeping 3s...") +time.sleep(3) +print("done. check lsof now: A should be gone with the sweep, present without it") +# leave sockets open so A lingers if leaked +time.sleep(60) \ No newline at end of file diff --git a/tests/throw_test.py b/tests/throw_test.py new file mode 100644 index 0000000..db5f4fc --- /dev/null +++ b/tests/throw_test.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +# Reproduces the uncaught-Error crash from command handlers. +# Run on the machine running the server: python3 throw_test.py [port] [password] +import socket +import sys +import time + +HOST = "127.0.0.1" +PORT = int(sys.argv[1]) if len(sys.argv) > 1 else 6667 +PW = sys.argv[2] if len(sys.argv) > 2 else "pass" + + +def reg(nick, user): + s = socket.create_connection((HOST, PORT)) + s.sendall(("PASS %s\r\nNICK %s\r\nUSER %s 0 * :x\r\n" + % (PW, nick, user)).encode()) + time.sleep(0.3) + s.setblocking(False) + try: + while s.recv(4096): + pass + except BlockingIOError: + pass + s.setblocking(True) + return s + + +def server_alive(): + try: + t = socket.create_connection((HOST, PORT), timeout=1) + t.close() + return True + except OSError: + return False + + +print("== Trigger 1: MODE on a channel that does not exist ==") +print(" server up:", "YES" if server_alive() else "NO") +a = reg("t1", "u1") +a.sendall(b"MODE #doesnotexist +o x\r\n") +time.sleep(0.8) +print(" server still up:", + "YES (no crash; #doesnotexist may be handled without throwing)" + if server_alive() else "NO -> CRASH reproduced (server died)") +a.close() + +print("== Trigger 2: non-op issues +o to an op (two connections) ==") +print(" server up:", "YES" if server_alive() else "NO") +op = reg("op", "u2") +op.sendall(b"JOIN #c\r\n") +time.sleep(0.2) +mem = reg("mem", "u3") +mem.sendall(b"JOIN #c\r\n") +time.sleep(0.2) +mem.sendall(b"MODE #c +o op\r\n") # member, not op -> Channel.cpp throws 482 +time.sleep(0.8) +print(" server still up:", + "YES (crash fixed)" if server_alive() else "NO -> CRASH reproduced (server died)") +op.close() +mem.close() From 7585c3ee6e5efb15fa8bcb7cfa8086c9fde04bb5 Mon Sep 17 00:00:00 2001 From: ArchCipher Date: Fri, 18 Sep 2026 14:49:59 +0200 Subject: [PATCH 336/349] fixes for poll-loop4 + tests --- srcs/diff-bonus | 1256 ++++++++++++++++++++++++++++++++++++++ srcs/diff-current-pr | 166 +++++ srcs/diff-poll-loop4 | 595 ++++++++++++++++++ srcs/diff-poll-loop4-fix | 209 +++++++ srcs/server/Server.cpp | 12 +- srcs/server/Socket.cpp | 4 +- 6 files changed, 2234 insertions(+), 8 deletions(-) create mode 100644 srcs/diff-bonus create mode 100644 srcs/diff-current-pr create mode 100644 srcs/diff-poll-loop4 create mode 100644 srcs/diff-poll-loop4-fix diff --git a/srcs/diff-bonus b/srcs/diff-bonus new file mode 100644 index 0000000..a07a171 --- /dev/null +++ b/srcs/diff-bonus @@ -0,0 +1,1256 @@ +diff --git a/Makefile b/Makefile +index d8246cb..57acd0f 100644 +--- a/Makefile ++++ b/Makefile +@@ -1,8 +1,8 @@ + # ---- Library and Project names ----------------------------------------------- + NAME = ircserv + DEBUG_NAME = ircserv_debug +-# BONUS = ircserv_bonus +-# DEBUG_BONUS = ircserv_bonus_debug ++BONUS = ircserv_bonus ++DEBUG_BONUS = ircserv_bonus_debug + + # ---- Sources & Objects ------------------------------------------------------- + SRC_DIR = srcs +@@ -29,26 +29,27 @@ SRCS_COMMANDS = CommandProcessor \ + + SRCS_UTILS = MemberSet \ + ModeSet +-# Parser \ +-# Reply + +-# SRCS_BONUS = DCC \ +-# Bot ++SRCS_BONUS = Bot \ ++ TransferManager + + SRCS = $(SRCS_ROOT) \ + $(addprefix server/, $(SRCS_SERVER)) \ + $(addprefix commands/, $(SRCS_COMMANDS)) \ + $(addprefix utils/, $(SRCS_UTILS)) + ++SRCS_ALL_BONUS = $(SRCS) \ ++ $(addprefix bonus/, $(SRCS_BONUS)) ++ + OBJS = $(addprefix $(BUILD_DIR)/$(SRC_DIR)/, $(addsuffix .o, $(SRCS))) + DEBUG_OBJS = $(addprefix $(DEBUG_BUILD_DIR)/$(SRC_DIR)/, $(addsuffix .o, $(SRCS))) +-# BONUS_OBJS = $(OBJS:.o=.bonus.o) +-# DEBUG_BONUS_OBJS = $(DEBUG_OBJS:.o=.bonus.o) ++BONUS_OBJS = $(addprefix $(BUILD_DIR)/$(SRC_DIR)/, $(addsuffix .bonus.o, $(SRCS_ALL_BONUS))) ++DEBUG_BONUS_OBJS= $(addprefix $(DEBUG_BUILD_DIR)/$(SRC_DIR)/, $(addsuffix .bonus.o, $(SRCS_ALL_BONUS))) + + # ---- Auto-generated header deps ---------------------------------------------- + + DEPS = $(OBJS:.o=.d) $(DEBUG_OBJS:.o=.d) +-# BONUS_DEPS = $(BONUS_OBJS:.o=.d) $(DEBUG_BONUS_OBJS:.o=.d) ++BONUS_DEPS = $(BONUS_OBJS:.o=.d) $(DEBUG_BONUS_OBJS:.o=.d) + + # ---- Compiler and Flags ------------------------------------------------------ + +@@ -62,11 +63,11 @@ DEBUG_CXXFLAGS = $(CXXFLAGS) $(DFLAGS) + + all: $(NAME) + +-# bonus: $(BONUS) ++bonus: $(BONUS) + + debug: $(DEBUG_NAME) + +-# debug_bonus: $(DEBUG_BONUS) ++debug_bonus: $(DEBUG_BONUS) + + # clang-format + format: +@@ -91,11 +92,11 @@ $(NAME): $(OBJS) + $(DEBUG_NAME): $(DEBUG_OBJS) + $(CXX) $(DEBUG_CXXFLAGS) $(DEBUG_OBJS) -o $@ + +-# $(BONUS): $(BONUS_OBJS) +-# $(CXX) $(CXXFLAGS) $(BONUS_OBJS) -o $@ ++$(BONUS): $(BONUS_OBJS) ++ $(CXX) $(CXXFLAGS) $(BONUS_OBJS) -o $@ + +-# $(DEBUG_BONUS): $(DEBUG_BONUS_OBJS) +-# $(CXX) $(DEBUG_CXXFLAGS) $(DEBUG_BONUS_OBJS) -o $@ ++$(DEBUG_BONUS): $(DEBUG_BONUS_OBJS) ++ $(CXX) $(DEBUG_CXXFLAGS) $(DEBUG_BONUS_OBJS) -o $@ + + $(BUILD_DIR)/$(SRC_DIR)/%.o: $(SRC_DIR)/%.cpp + @mkdir -p $(@D) +@@ -105,13 +106,13 @@ $(DEBUG_BUILD_DIR)/$(SRC_DIR)/%.o: $(SRC_DIR)/%.cpp + @mkdir -p $(@D) + $(CXX) $(DEBUG_CXXFLAGS) $(IFLAGS) -MMD -MP -c $< -o $@ + +-# $(BUILD_DIR)/$(SRC_DIR)/%.bonus.o: $(SRC_DIR)/%.cpp +-# @mkdir -p $(@D) +-# $(CXX) $(CXXFLAGS) $(IFLAGS) -MMD -MP -c -DBONUS $< -o $@ ++$(BUILD_DIR)/$(SRC_DIR)/%.bonus.o: $(SRC_DIR)/%.cpp ++ @mkdir -p $(@D) ++ $(CXX) $(CXXFLAGS) $(IFLAGS) -MMD -MP -c -DBONUS $< -o $@ + +-# $(DEBUG_BUILD_DIR)/$(SRC_DIR)/%.bonus.o: $(SRC_DIR)/%.cpp +-# @mkdir -p $(@D) +-# $(CXX) $(DEBUG_CXXFLAGS) $(IFLAGS) -MMD -MP -c -DBONUS $< -o $@ ++$(DEBUG_BUILD_DIR)/$(SRC_DIR)/%.bonus.o: $(SRC_DIR)/%.cpp ++ @mkdir -p $(@D) ++ $(CXX) $(DEBUG_CXXFLAGS) $(IFLAGS) -MMD -MP -c -DBONUS $< -o $@ + + # ---- Cleanup ----------------------------------------------------------------- + clean: +@@ -122,7 +123,6 @@ fclean: clean + + re: fclean all + +--include $(DEPS) +-# -include $(DEPS) $(BONUS_DEPS) ++-include $(DEPS) $(BONUS_DEPS) + + .PHONY: all bonus clean fclean re debug debug_bonus format tidy tidy-all +diff --git a/includes/Bot.hpp b/includes/Bot.hpp +new file mode 100644 +index 0000000..d603397 +--- /dev/null ++++ b/includes/Bot.hpp +@@ -0,0 +1,34 @@ ++#ifdef BONUS ++ ++#pragma once ++ ++#include ++#include ++ ++class Client; ++class Server; ++ ++class Bot { ++ std::string name_; ++ bool isBotCommand_(const std::string ¶m) const; ++ ++ public: ++ Bot(); ++ ++ std::string getPrefix(const std::string &serverName) const; ++ std::string getVersion(const std::string &serverName) const; ++ ++ bool isForBot(Server &server, const std::vector ¶ms) const; ++ ++ void handleMessage(Server &server, Client &client, ++ const std::string &body) const; ++ void handleSend(Server &server, Client &client, const std::string &nick, ++ const std::string &filename) const; ++ void handleCTCP(const std::string &serverName, Client &client, ++ const std::string &body) const; ++ ++ std::string unknown() const; ++ std::string help() const; ++}; ++ ++#endif +diff --git a/includes/CTCP.hpp b/includes/CTCP.hpp +new file mode 100644 +index 0000000..f060cdd +--- /dev/null ++++ b/includes/CTCP.hpp +@@ -0,0 +1,31 @@ ++#ifdef BONUS ++ ++#pragma once ++ ++#include ++ ++namespace CTCP { ++static const char SOH = '\x01'; ++static const std::string strSOH = "\x01"; ++ ++static inline bool isCTCP(const std::string &body) { ++ return !body.empty() && body[0] == SOH; ++} ++ ++// strip starting SOF (\x01), strip ending SOH if present ++static inline std::string extractCTCP(const std::string &body) { ++ std::string out = body.substr(1); ++ if (!out.empty() && out[out.size() - 1] == SOH) ++ out.erase(out.size() - 1); ++ return out; ++} ++ ++// Builds "\x01\x01". Used by bot ++static inline std::string buildCTCP(const std::string payload) { ++ std::string out = strSOH + payload + SOH; ++ return out; ++} ++ ++} // namespace CTCP ++ ++#endif +diff --git a/includes/Server.hpp b/includes/Server.hpp +index ce66f81..ac886a8 100644 +--- a/includes/Server.hpp ++++ b/includes/Server.hpp +@@ -6,6 +6,11 @@ + #include "Logger.hpp" // for Logger + #include "Socket.hpp" // for Socket + ++#ifdef BONUS ++#include "Bot.hpp" // for Bot ++#include "TransferManager.hpp" ++#endif ++ + #include // for pollfd + #include // for size_t + #include // for string, basic_string +@@ -30,6 +35,22 @@ class Server { + ClientManager clients_; + CommandProcessor cmds_; + ++#ifdef BONUS ++ struct PollFd { ++ struct pollfd pfd; ++ ++ PollFd(int fd, short events) { ++ pfd.fd = fd; ++ pfd.events = events; ++ pfd.revents = 0; ++ } ++ }; ++ ++ TransferManager tm_; ++ Bot bot_; ++ void processTransferSocket_(); ++#endif ++ + size_t numServerSockets_; + std::vector pollFds_; + +@@ -60,4 +81,9 @@ class Server { + ClientManager &getClientManager(); + + void sendMsg(int const fd, std::string const &msg); ++ ++#ifdef BONUS ++ TransferManager &getTransferManager(); ++ Bot &getBot(); ++#endif + }; +diff --git a/includes/TransferManager.hpp b/includes/TransferManager.hpp +new file mode 100644 +index 0000000..406f41f +--- /dev/null ++++ b/includes/TransferManager.hpp +@@ -0,0 +1,70 @@ ++#ifdef BONUS ++ ++#pragma once ++ ++#include ++#include ++#include ++ ++class Client; ++ ++enum Status { Pending, Accepted, Rejected }; ++ ++struct Transfer { ++ int clientFd; ++ int targetFd; ++ std::string filename; ++ std::string filepath; // "files/" + filename ++ off_t filesize; ++ int listenerFd; ++ int dataFd; ++ int port; ++ off_t offset; ++ Status status; ++ time_t lastActivity; ++ ++ Transfer() ++ : clientFd(-1), targetFd(-1), filesize(0), listenerFd(-1), dataFd(-1), ++ port(0), offset(0), status(Pending), lastActivity(std::time(0)) {}; ++ ++ Transfer(int client, int target, std::string name, std::string path, ++ off_t size) ++ : clientFd(client), targetFd(target), filename(name), filepath(path), ++ filesize(size), listenerFd(-1), dataFd(-1), port(0), offset(0), ++ status(Pending), lastActivity(std::time(0)) {} ++}; ++ ++class TransferManager { ++ bool active_; // one transfer at a time. true = busy. ++ Transfer current_; ++ ++ bool openListener_(); ++ void closeFds_(); ++ bool fileSize_(const std::string &path, off_t &size) const; ++ static void setNonBlocking_(int fd); ++ ++ TransferManager(const TransferManager &); ++ TransferManager &operator=(const TransferManager &); ++ ++ public: ++ TransferManager(); ++ ~TransferManager(); ++ ++ std::string openOffer(int clientFd, int targetFd, ++ const std::string &filename); ++ bool setOfferStatus(int targetFd, bool accept); ++ void closeOffer(); ++ bool expired() const; ++ ++ int getDataFd(); ++ ++ ssize_t sendChunk(); ++ ++ int getListenerFd() const; ++ bool isActive() const; ++ Transfer *current(); ++ bool ownsFd(int fd) const; ++ bool transferDone() const; ++}; ++ ++#endif +diff --git a/includes/Utils.hpp b/includes/Utils.hpp +index 3f84ea1..904456a 100644 +--- a/includes/Utils.hpp ++++ b/includes/Utils.hpp +@@ -57,4 +57,15 @@ static inline std::string join3(const std::string &a, const std::string &b, + return a + " " + b + " " + (c.empty() ? "" : " " + c); + } + ++#ifdef BONUS ++ ++static inline std::string trimPrefix(const std::string &s, ++ const std::string &prefix) { ++ if (s.compare(0, prefix.size(), prefix) == 0) ++ return s.substr(prefix.size()); ++ return s; ++} ++ ++#endif ++ + } // namespace Utils +diff --git a/srcs/bonus/Bot.cpp b/srcs/bonus/Bot.cpp +new file mode 100644 +index 0000000..05f9213 +--- /dev/null ++++ b/srcs/bonus/Bot.cpp +@@ -0,0 +1,167 @@ ++#ifdef BONUS ++ ++#include "Bot.hpp" ++ ++#include "CTCP.hpp" ++#include "Client.hpp" ++#include "Errors.hpp" ++#include "Reply.hpp" ++#include "Server.hpp" ++#include "TransferManager.hpp" ++#include "Utils.hpp" ++ ++#include ++ ++Bot::Bot() : name_("DCCBot") {} ++ ++// ---- Identifiers ------------------------------------------------------------ ++ ++std::string Bot::getPrefix(const std::string &serverName) const { ++ return name_ + "!bot@" + serverName; ++} ++ ++std::string Bot::getVersion(const std::string &serverName) const { ++ return "I am " + name_ + " on " + serverName; ++} ++ ++// ---- Command recognition ---------------------------------------------------- ++ ++/* ++PRIVMSG ++ ++Targeted to bot (bot command or ctcp) ++ bot / #bot / dccbot ++Bot command ++ #chan ++ ++NOTE: Should be called only when params > 1 && !params[0].empty() && !params[1].empty(). ++This will intercept any user with nickname bot / BOT / #bot -> but OK for bonus. ++*/ ++bool Bot::isForBot(Server &server, ++ const std::vector ¶ms) const { ++ const std::string &target = Utils::foldCase(params[0]); ++ if (target == Utils::foldCase(name_) || target == "bot" || target == "#bot") ++ return true; ++ return (server.getChannelManager().isChannelName(params[0]) && ++ isBotCommand_(params[1])); ++} ++ ++bool Bot::isBotCommand_(const std::string &body) const { ++ if (body.empty()) ++ return false; ++ std::vector words = Utils::split(body, ' '); ++ std::string cmd = Utils::trimPrefix(Utils::foldCase(words[0]), "!"); ++ if (cmd.empty()) ++ return false; ++ ++ static const std::string botCmds[] = {"help", "version", "whoami", ++ "send", "accept", "reject"}; ++ static const size_t totalbotCmds = sizeof(botCmds) / sizeof(*botCmds); ++ for (size_t i = 0; i < totalbotCmds; i++) { ++ if (botCmds[i] == cmd) ++ return true; ++ } ++ return false; ++} ++ ++// ---- Command handlers ------------------------------------------------------- ++ ++// bot ++// #chan ++void Bot::handleMessage(Server &server, Client &client, ++ const std::string &body) const { ++ std::vector words = Utils::split(body, ' '); ++ std::string cmd = Utils::foldCase(words.empty() ? "" : words[0]); ++ cmd = Utils::trimPrefix(cmd, "!"); ++ std::string msg; ++ if (cmd.empty() || cmd == "help") ++ msg = help(); ++ else if (cmd == "version") ++ msg = getVersion(server.getServerName()); ++ else if (cmd == "whoami") ++ msg = "You are " + client.getNick(); ++ else if (cmd == "send") { ++ if (words.size() < 3 || words[1].empty() || words[2].empty()) ++ client.sendReply(Reply::reply(Reply::ERR_NEEDMOREPARAMS, "SEND")); ++ else ++ handleSend(server, client, words[1], words[2]); ++ return; ++ } else if (cmd == "accept" || cmd == "reject") { ++ bool ok = server.getTransferManager().setOfferStatus(client.getFd(), ++ cmd == "accept"); ++ msg = ok ? (cmd == "accept" ? "transfer accepted - connect to receive" ++ : "transfer rejected") ++ : "no pending transfers for you"; ++ } else ++ msg = unknown(); ++ client.send(Reply::fromClient(getPrefix(server.getServerName()), "PRIVMSG", ++ client.getNick(), msg)); ++} ++ ++/* ++bot !send ++ bot is initiator, opens a listener, ++ replies buildCTCP("DCC SEND ...") in PRIVMSG to requester ++ ++NOTE: DCC receiver connects to the offer IP. ++127.0.0.1 macOS returns broken accepted socket (SO_ERROR=9 / POLLHUP) when a ++process connects to its own LAN IP, which breaks the transfers for same-host ++receivers ++*/ ++void Bot::handleSend(Server &server, Client &client, const std::string &nick, ++ const std::string &filename) const { ++ try { ++ Client &target = server.getClientManager().at(nick); ++ TransferManager &tm = server.getTransferManager(); ++ std::string err = ++ tm.openOffer(client.getFd(), target.getFd(), filename); ++ if (!err.empty()) { ++ client.send(Reply::fromClient(getPrefix(server.getServerName()), ++ "PRIVMSG", client.getNick(), err)); ++ return; ++ } ++ Transfer *t = tm.current(); ++ std::ostringstream dcc; ++ dcc << "DCC SEND " << t->filename << " 127.0.0.1 " << t->port << " " ++ << t->filesize; ++ std::string msg = CTCP::buildCTCP(dcc.str()); ++ target.send(Reply::fromClient(getPrefix(server.getServerName()), ++ "PRIVMSG", target.getNick(), msg)); ++ client.send(Reply::fromClient(getPrefix(server.getServerName()), ++ "PRIVMSG", client.getNick(), ++ "offer sent to " + target.getNick())); ++ } catch (Error &e) { ++ if (e.hasReply()) ++ client.sendReply(e.reply()); ++ } ++} ++ ++void Bot::handleCTCP(const std::string &serverName, Client &client, ++ const std::string &body) const { ++ std::string ctcp = CTCP::extractCTCP(body); ++ std::vector words = Utils::split(ctcp, ' '); ++ const std::string &text = Utils::foldCase(words.empty() ? "" : words[0]); ++ std::string msg; ++ if (text == "version") ++ msg = getVersion(serverName); ++ else if (text.compare(0, 4, "ping") == 0) ++ msg = ctcp; ++ else ++ msg = unknown(); ++ client.send(Reply::fromClient(getPrefix(serverName), "NOTICE", ++ client.getNick(), CTCP::buildCTCP(msg))); ++} ++ ++// ---- Response text ---------------------------------------------------------- ++ ++// should reply "Do you mean xxx" -> like a compiler. ++// make this a bit smarter? ++std::string Bot::unknown() const { ++ return "Unknown command. Try !help"; ++} ++ ++std::string Bot::help() const { ++ return "Commands: !help, !version, !whoami, !send, !accept, !reject"; ++} ++ ++#endif +diff --git a/srcs/bonus/TransferManager.cpp b/srcs/bonus/TransferManager.cpp +new file mode 100644 +index 0000000..5fc35cf +--- /dev/null ++++ b/srcs/bonus/TransferManager.cpp +@@ -0,0 +1,209 @@ ++#ifdef BONUS ++ ++#include "TransferManager.hpp" ++ ++#include "Client.hpp" ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++static const int CHUNK_SIZE = 1024; ++static const time_t STALE_TIMEOUT = 60; ++static const int LISTENER_BACKLOG = 8; ++static const char *FILES_DIR = "files/"; ++ ++TransferManager::TransferManager() : active_(false) {} ++ ++TransferManager::~TransferManager() { ++ closeFds_(); ++} ++ ++// ---- Offer lifecycle -------------------------------------------------------- ++ ++// returns error msg or "" ++std::string TransferManager::openOffer(int clientFd, int targetFd, ++ const std::string &filename) { ++ if (active_) { ++ if (!expired()) ++ return "bot busy"; ++ closeOffer(); ++ } ++ if (filename.find('/') != std::string::npos || ++ filename.find("..") != std::string::npos) ++ return "invalid filename"; ++ std::string filepath = std::string(FILES_DIR) + filename; ++ off_t size = 0; ++ if (!fileSize_(filepath, size)) ++ return std::string("file not found under ") + FILES_DIR; ++ current_ = Transfer(clientFd, targetFd, filename, filepath, size); ++ if (!openListener_()) { ++ closeFds_(); ++ return "socket error, try again later"; ++ } ++ active_ = true; ++ return ""; ++} ++ ++bool TransferManager::setOfferStatus(int targetFd, bool accept) { ++ if (!active_ || current_.status != Pending || current_.targetFd != targetFd) ++ return false; ++ current_.status = accept ? Accepted : Rejected; ++ if (!accept) ++ closeOffer(); ++ return true; ++} ++ ++void TransferManager::closeOffer() { ++ closeFds_(); ++ active_ = false; ++} ++ ++bool TransferManager::expired() const { ++ return active_ && std::time(0) - current_.lastActivity > STALE_TIMEOUT; ++} ++ ++// ---- Listener & connection -------------------------------------------------- ++ ++bool TransferManager::openListener_() { ++ struct sockaddr_in addr; ++ std::memset(&addr, 0, sizeof(addr)); ++ addr.sin_family = AF_INET; ++ addr.sin_addr.s_addr = htonl(INADDR_ANY); ++ addr.sin_port = htons(0); // let OS pick a port ++ ++ int fd = ::socket(AF_INET, SOCK_STREAM, 0); ++ if (fd < 0) ++ return false; ++ ++ int opt = 1; ++ if (::setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) { ++ ::close(fd); ++ return false; ++ } ++ ++ if (::bind(fd, reinterpret_cast(&addr), sizeof(addr)) < ++ 0) { ++ ::close(fd); ++ return false; ++ } ++ if (::listen(fd, LISTENER_BACKLOG) < 0) { ++ ::close(fd); ++ return false; ++ } ++ setNonBlocking_(fd); ++ ++ socklen_t len = sizeof(addr); ++ if (::getsockname(fd, reinterpret_cast(&addr), &len) < ++ 0) { ++ ::close(fd); ++ return false; ++ } ++ ++ current_.listenerFd = fd; ++ current_.port = ntohs(addr.sin_port); ++ return true; ++} ++ ++// accept() receiver's connection ++int TransferManager::getDataFd() { ++ if (!active_ || current_.status != Accepted || current_.listenerFd == -1) ++ return -1; ++ if (current_.dataFd != -1) ++ return current_.dataFd; ++ ++ struct sockaddr_in addr; ++ socklen_t len = sizeof(addr); ++ int fd = ::accept(current_.listenerFd, ++ reinterpret_cast(&addr), &len); ++ if (fd < 0) ++ return -1; ++ setNonBlocking_(fd); ++ current_.dataFd = fd; ++ return fd; ++} ++ ++// ---- Transfer IO ------------------------------------------------------------ ++ ++// send 1KB ++ssize_t TransferManager::sendChunk() { ++ if (!active_ || current_.status != Accepted || current_.dataFd == -1) ++ return -1; ++ ++ std::ifstream f(current_.filepath.c_str(), std::ios::in | std::ios::binary); ++ if (!f.is_open()) ++ return -1; ++ f.seekg(current_.offset); ++ char buf[CHUNK_SIZE]; ++ f.read(buf, CHUNK_SIZE); ++ std::streamsize n = f.gcount(); ++ if (n <= 0) ++ return 0; // EOF ++ ssize_t sent = ::send(current_.dataFd, buf, n, MSG_NOSIGNAL); ++ if (sent <= 0) ++ return 0; ++ current_.lastActivity = std::time(0); ++ current_.offset += sent; ++ return sent; ++} ++ ++// ---- State ------------------------------------------------------------------ ++ ++int TransferManager::getListenerFd() const { ++ return active_ ? current_.listenerFd : -1; ++} ++ ++bool TransferManager::isActive() const { ++ return active_; ++} ++ ++Transfer *TransferManager::current() { ++ return active_ ? ¤t_ : 0; ++} ++ ++bool TransferManager::ownsFd(int fd) const { ++ return active_ && (fd == current_.listenerFd || fd == current_.dataFd); ++} ++ ++bool TransferManager::transferDone() const { ++ return active_ && (current_.status == Accepted && ++ current_.offset >= current_.filesize); ++} ++ ++// ---- Helpers ---------------------------------------------------------------- ++ ++void TransferManager::closeFds_() { ++ if (current_.listenerFd != -1) { ++ ::close(current_.listenerFd); ++ current_.listenerFd = -1; ++ } ++ if (current_.dataFd != -1) { ++ ::close(current_.dataFd); ++ current_.dataFd = -1; ++ } ++} ++ ++bool TransferManager::fileSize_(const std::string &path, off_t &size) const { ++ std::ifstream f(path.c_str(), std::ios::in | std::ios::binary); ++ if (!f.is_open()) ++ return false; ++ f.seekg(0, std::ios::end); ++ std::streamoff p = f.tellg(); ++ if (p < 0) ++ return false; ++ size = static_cast(p); ++ return true; ++} ++ ++void TransferManager::setNonBlocking_(int fd) { ++ int flags = ::fcntl(fd, F_GETFL, 0); ++ if (flags != -1) ++ ::fcntl(fd, F_SETFL, flags | O_NONBLOCK); ++} ++ ++#endif +diff --git a/srcs/commands/Messaging.cpp b/srcs/commands/Messaging.cpp +index cd1a4ba..0866804 100644 +--- a/srcs/commands/Messaging.cpp ++++ b/srcs/commands/Messaging.cpp +@@ -27,6 +27,11 @@ + #include "Server.hpp" + #include "Utils.hpp" + ++#ifdef BONUS ++#include "Bot.hpp" ++#include "CTCP.hpp" ++#endif ++ + #include + #include + +@@ -46,20 +51,10 @@ void deliverTo(Server &server, Client &from, const std::string &target, + server.getClientManager().at(target).send(line); // throws 401 + } + ++// call checkMinParam() before calling deliver() + bool deliver(Server &server, Client &client, + const std::vector ¶ms, const std::string &cmd, + bool silent) { +- if (params.empty() || params[0].empty()) { +- if (!silent) +- client.sendReply(Reply::reply(Reply::ERR_NORECIPIENT, "", +- "No recipient given (" + cmd + ")")); +- return false; +- } +- if (params.size() < 2 || params[1].empty()) { +- if (!silent) +- client.sendReply(Reply::reply(Reply::ERR_NOTEXTTOSEND)); +- return false; +- } + const std::vector targets = Utils::split(params[0], ','); + for (size_t i = 0; i < targets.size(); i++) { + if (targets[i].empty()) +@@ -76,14 +71,45 @@ bool deliver(Server &server, Client &client, + return false; + } + ++// Checks min parameters. Sends error message and returns false on error. ++bool checkMinParam(Client &client, const std::vector ¶ms, ++ bool silent) { ++ if (params.empty() || params[0].empty()) { ++ if (!silent) ++ client.sendReply(Reply::reply(Reply::ERR_NORECIPIENT, "", ++ "No recipient given (PRIVMSG)")); ++ return false; ++ } ++ if (params.size() < 2 || params[1].empty()) { ++ if (!silent) ++ client.sendReply(Reply::reply(Reply::ERR_NOTEXTTOSEND, "")); ++ return false; ++ } ++ return true; ++} ++ + } // namespace + + bool Handlers::privmsg(Server &server, Client &client, + const std::vector ¶ms) { ++ if (!checkMinParam(client, params, false)) ++ return false; ++#ifdef BONUS ++ Bot &bot = server.getBot(); ++ if (bot.isForBot(server, params)) { ++ if (CTCP::isCTCP(params[1])) ++ bot.handleCTCP(server.getServerName(), client, params[1]); ++ else ++ bot.handleMessage(server, client, params[1]); ++ return false; ++ } ++#endif + return deliver(server, client, params, "PRIVMSG", false); + } + + bool Handlers::notice(Server &server, Client &client, + const std::vector ¶ms) { ++ if (!checkMinParam(client, params, true)) ++ return false; + return deliver(server, client, params, "NOTICE", true); + } +diff --git a/srcs/server/Channel.cpp b/srcs/server/Channel.cpp +index 39e312b..7aef362 100644 +--- a/srcs/server/Channel.cpp ++++ b/srcs/server/Channel.cpp +@@ -255,7 +255,8 @@ bool Channel::applyModeToMember_(Server &server, Client &client, + client.sendReply(Reply::reply(Reply::ERR_USERNOTINCHANNEL, + change.arg + " " + name_)); + } catch (Error &e) { +- client.sendReply(e.reply()); ++ if (e.hasReply()) ++ client.sendReply(e.reply()); + } + return false; + } +diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp +index 055ef86..2e8bb84 100644 +--- a/srcs/server/Server.cpp ++++ b/srcs/server/Server.cpp +@@ -4,6 +4,10 @@ + #include "ServerHelper.hpp" // for getAdressFamilyStr, composeAddrfamAddrP... + #include "SignalHandler.hpp" // for SignalHandler + ++#ifdef BONUS ++#include "Bot.hpp" // for Bot ++#endif ++ + #include // for find_if + #include // for exception + #include // for IPPROTO_IPV6, IPV6_V6ONLY +@@ -120,6 +124,10 @@ void Server::processClientSockets_() { + !SignalHandler::stopLoopSignalReceived() && idx < pollFds_.size(); + ++idx) { + pollfd &pfd = pollFds_.at(idx); ++#ifdef BONUS ++ if (tm_.ownsFd(pfd.fd)) ++ continue; ++#endif + + if (!(sockets_.checkSocket4Recv(pfd) && (pfd.revents & POLLIN))) + continue; +@@ -145,6 +153,10 @@ void Server::processClientSockets_() { + void Server::removeClosedSockets_() { + for (size_t idx = numServerSockets_; idx < pollFds_.size(); ++idx) { + pollfd &pfd = pollFds_.at(idx); ++#ifdef BONUS ++ if (tm_.ownsFd(pfd.fd)) ++ continue; ++#endif + if (sockets_.socketClosed(pfd.fd)) { + clients_.remove(pfd.fd, channels_); + removePollFd_(pfd.fd); +@@ -243,7 +255,12 @@ void Server::run() { + return; + + while (!SignalHandler::stopLoopSignalReceived()) { +- int pollResult = poll(pollFds_.data(), pollFds_.size(), -1); ++ int timeout = -1; ++#ifdef BONUS ++ if (tm_.isActive()) ++ timeout = 100; ++#endif ++ int pollResult = poll(pollFds_.data(), pollFds_.size(), timeout); + if (pollResult == -1) { + if (SignalHandler::stopLoopSignalReceived()) + break; +@@ -253,6 +270,9 @@ void Server::run() { + + processServerSocketData_(); + processClientSockets_(); ++#ifdef BONUS ++ processTransferSocket_(); ++#endif + } + } + +@@ -269,3 +289,73 @@ void Server::sendMsg(int const fd, std::string const &msg) { + #endif + } + } ++ ++#ifdef BONUS ++TransferManager &Server::getTransferManager() { ++ return tm_; ++} ++ ++Bot &Server::getBot() { ++ return bot_; ++} ++ ++void Server::processTransferSocket_() { ++ if (!tm_.isActive()) ++ return; ++ if (tm_.expired()) { ++ tm_.closeOffer(); ++ return; ++ } ++ int listener = tm_.getListenerFd(); ++ if (listener == -1) ++ return; ++ int dataFd = tm_.getDataFd(); ++ struct pollfd pfds[2]; ++ int n = 1; ++ pfds[0] = PollFd(listener, POLLIN).pfd; ++ if (dataFd != -1) { ++ pfds[1] = PollFd(dataFd, POLLOUT).pfd; ++ n = 2; ++ } ++ if (poll(pfds, n, 0) <= 0) ++ return; ++ if (pfds[0].revents & (POLLERR | POLLNVAL)) { ++ tm_.closeOffer(); ++ return; ++ } ++ if (pfds[0].revents & POLLIN) { ++ dataFd = tm_.getDataFd(); ++ if (dataFd < 0) ++ return; ++ pfds[1] = PollFd(dataFd, POLLOUT).pfd; ++ n = 2; ++ if (poll(pfds, n, 0) <= 0) ++ return; ++ } ++ if (n != 2) ++ return; ++ if (pfds[1].revents & (POLLERR | POLLNVAL)) { ++ tm_.closeOffer(); ++ return; ++ } ++ if (!(pfds[1].revents & (POLLOUT | POLLHUP))) ++ return; ++ ssize_t sent = 0; ++ do { ++ sent = tm_.sendChunk(); ++ } while (sent > 0); ++ std::string msg; ++ if (tm_.transferDone()) { ++ msg = "transfer complete"; ++ } else if (sent < 0) ++ msg = "transfer failed"; ++ else ++ return; ++ Client *client = clients_.find(tm_.current()->clientFd); ++ if (client) ++ client->send(Reply::fromClient(bot_.getPrefix(serverName_), "PRIVMSG", ++ client->getNick(), msg)); ++ tm_.closeOffer(); ++} ++ ++#endif +diff --git a/srcs/server/Socket.cpp b/srcs/server/Socket.cpp +index 198558c..85f71b0 100644 +--- a/srcs/server/Socket.cpp ++++ b/srcs/server/Socket.cpp +@@ -48,6 +48,9 @@ bool Socket::updateBuffer_(int const fd) { + return true; + } + ++/* ++Tolerate lines ending with '\n' and "\r\n". ++*/ + std::string Socket::getCRLFLineFromBuffer(int const fd, bool &buffHasMoreCmds) { + if (!buffHasMoreCmds) { + if (!updateBuffer_(fd)) +@@ -55,30 +58,31 @@ std::string Socket::getCRLFLineFromBuffer(int const fd, bool &buffHasMoreCmds) { + } + + bool msgTooLong = false; +- size_t lfcrPos = bufferLengthAndSanityCheck_(fd, msgTooLong); ++ size_t eol = bufferLengthAndSanityCheck_(fd, msgTooLong); + if (msgTooLong) + return std::string(MAXMSGLEN + 1, '\0'); +- if (lfcrPos == std::string::npos) ++ if (eol == std::string::npos) + return ""; + + Socket::SocketIOData &sIOD = sockIODataStore_.at(fd); +- std::string cmdLine; +- cmdLine = sIOD.readBuffer.substr(0, lfcrPos); ++ std::string cmdLine = sIOD.readBuffer.substr(0, eol); ++ if (!cmdLine.empty() && *cmdLine.rbegin() == '\r') ++ cmdLine.erase(cmdLine.size() - 1); + sIOD.recvErrorCount = 0; +- sIOD.readBuffer = sIOD.readBuffer.substr(lfcrPos + 2); +- buffHasMoreCmds = (sIOD.readBuffer.find("\r\n") != std::string::npos); ++ sIOD.readBuffer = sIOD.readBuffer.substr(eol + 1); ++ buffHasMoreCmds = (sIOD.readBuffer.find('\n') != std::string::npos); + return cmdLine; + } + + size_t Socket::bufferLengthAndSanityCheck_(int const fd, bool &msgTooLong) { + Socket::SocketIOData &sIOD = sockIODataStore_.at(fd); + std::string &rdBuffer = sIOD.readBuffer; +- size_t lfcrPos = rdBuffer.find("\r\n"); ++ size_t eol = rdBuffer.find('\n'); + + if (!sIOD.discardMode) { + if ((rdBuffer.length() < MAXMSGLEN) || +- (lfcrPos != std::string::npos && lfcrPos <= MAXMSGLEN - 2)) +- return lfcrPos; ++ (eol != std::string::npos && eol <= MAXMSGLEN - 2)) ++ return eol; + sIOD.discardMode = true; + msgTooLong = true; + } +@@ -87,18 +91,18 @@ size_t Socket::bufferLengthAndSanityCheck_(int const fd, bool &msgTooLong) { + closeSocket(fd); + return std::string::npos; + } +- if (lfcrPos != std::string::npos) { +- rdBuffer.erase(0, lfcrPos + 2); ++ if (eol != std::string::npos) { ++ rdBuffer.erase(0, eol + 1); + sIOD.discardMode = false; + sIOD.recvErrorCount = 0; +- return rdBuffer.find("\r\n"); ++ return rdBuffer.find('\n'); + } +- // lfcrPos == std::string::npos ++ // eol == std::string::npos + if (!rdBuffer.empty() && '\r' == *rdBuffer.rbegin()) + rdBuffer = "\r"; + else + rdBuffer.clear(); +- return lfcrPos; ++ return eol; + } + + /* +diff --git a/tests/test_bot.sh b/tests/test_bot.sh +new file mode 100755 +index 0000000..80fe3b5 +--- /dev/null ++++ b/tests/test_bot.sh +@@ -0,0 +1,103 @@ ++#!/bin/bash ++ ++# DCC bot lifecycle test — no irssi/netcat needed. ++# Uses bash /dev/tcp: one fd per IRC session, kept open for the whole test. ++ ++# Usage: ./test_bot.sh [host] [port] [password] [testfile] ++# Requires: server running (./ircserv_bonus PORT PASS, files/ present) ++ ++set -u ++ ++HOST=${1:-localhost} ++PORT=${2:-6667} ++PASS=${3:-pass} ++FILE=${4:-test.txt} ++BOT=bot ++ ++PASSED=0 ++FAILED=0 ++ ++exec 3<>"/dev/tcp/$HOST/$PORT" 2>/dev/null || { echo "cannot connect"; exit 1; } # alice ++exec 4<>"/dev/tcp/$HOST/$PORT" 2>/dev/null || { echo "cannot connect"; exit 1; } # bob ++ ++send() { # fd rest... ++ local fd=$1; shift ++ printf '%s\r\n' "$@" >&"$fd" ++} ++ ++# wait4

Aiv0V;Ne>_sPG?04NRz#{L;G+T|=`{p` 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 z zi4h^8V_Kv941>I0Z#VG;a5)e2mIN}#X-wK>euNv+kTB+z&2tC!s>dI@4>2_edd`@{ zbsy+v`JQuw(7YGLCQem!8)-#1-}Y}#A@k7L$rCs~w~ba(c#_gxH#uA>kh~Zw_%W(X zgeAs5a4y4(=1t`t2zyy}-FnqM%PjB`Kb3PiOj+VpJ)<0TVp8V+eIq;#L4P-zntlQSR3ChClf!FxD-NP@5OX8 zS86kpq?}y%uP5XI5Y%foO{Pm%p_t3bl{_fNPI*rp)7zY1@)Ddr*Ik(Ot6|-Yuq{C< zpnonY-@8W4?P|;#QX-N1qe%4zv9G5=LLLqN?51@z$PnBDpy8wtUX)RSPLe4*JROmd zm4c+nFY{nvON_T_Xl@;BD>kOe>Euo_y5&)*)xuaZ@sxL0L6bS`#o`rlZ7&E;T?1KZ z@8-%`trlVcAVPuDMaK(m!?;^*;l?LwkE#ESFx;|%J5$%Bo zjQ5tdJx&CFhguM|=?ON%!>%xVeMy;m6JUERH77C*+276`d`fQW*!nxOGlgNFZWW-p zfx@F|m>njHEJbWj*gF%|nN~Ad-iD$=p9a`y60vc?-Y06r0!R#r9kBRlIBRxaUWay) z%9$*@yB1(xJT3W45H3QGh0I>b-bCODn)U?@=Sf;MKNz3oXNS*E*g~#HVhWa(kIVn5 z*>@0a;l_LmZ%t`W+}Y`lrziV`M{5DEChO^6R;xj4GhKri$l?n4;-^_*b$_Kjx}AgR zg(qcx1z_|Pvv*9VnwJP^yc>nwhLY60E=O(o^8|W=+?(A)iy!Cidb3_oqp+9aGw##e zmvYx&Uy1@~u&8I5R=?IM-*4k-{eWF(u=64#}pE*J2zSie4?gME~mN?+%Qi!r)CARgn=cLgVt8E*7^mxRe zATz3d{}z0t$xGA|x#-=eCI8t@aGOB{l(W=|D^^@TL;g+qP;l}1eFg~zI9E<-Z8n?k^A;H*5CFNOTLqzSjf+MFjeNo$NP6F3K! zoS2YLE`4c+g%oI{qurk9SmHOY-naH|8Cq9X;NNWOUWpJ6>u2wE=A6QDh4Wlsgq#~lbPkI-3GBs%TpH-?9!0}T%h z3$Yxy6iZ6tHbu?Ly+;6ISqY=RD>_9neN!I%th1K$OfroOTClq5S14jNrQV;Op|j*& zB0s6}24cA+h5APJ?Fb48=8BK}tLGzQDU$Fto0$i{je-CSo%x22d8X@E#k@+Btl8I= z;CB?-vJhQi8XCnTOWr87c{WNl12`nHx8QJGdT_nI-M)7YZ*J$5$7bG09C#a?bVkI? zL&eno4YQBx3qJrXW+IfH-Wk5mCI_+rDp;BDbz>=T6Xm@N5~C+3V3HE;X&ch&1@)T+ ztrBg>(`jdfOEgy%bleu38V+8*Bpb zLt~^OGp^m3FrC~n8yg?wc!Mu`W#noOS2Qe6N7(NS79==pl<|ORFCu3ZBY~9G`1!ce z8zFi8s~hBv8hRfHXkmFO3}Ug{X81TnQGr$UAEIk-;-A)nV1vx?2#X$ z1A)$g_axW z&uaO~tL@c@l4An_HmiK+Hz`ckA{{wOJmNwm+6=fT5nNaq|Jwk;_vrrIVO{7&foK}ZS;Sj-PDf14i4rJzgH127oE&y?s1>uQMW;Vr=P zESv|Ng2!5<1zhARv*kxRhsbw<5ChZ`l=9ThtqiTiOvj*mmokFt$Sw48^=i8xj;p2- zddA31tE!Na%3{r3TH7Wyjv`rpnWSsJq$t<>w$CmMkNIJft?((>ZYq`ijOi839J`6> z5pzKp3>FLba0D85n4vI*6vMHJP|p*l$e4I+p_imv;*2P)gJZ1P($;#OcZpxqe7NW}^P|*2hxn?HnCPCVOA>Dz z2z#0A0?VIDA^_aRlw8G>ySjJ6aE`%>hvnT_0JMrqB3r?!NV!*Z<8`1lF20p0owT`7 zk7!sM5|%IFwlzntxEkg1cWQeIZWS7Aua#%arR&>BDffAuPuB`AkneCZ4YzwhDVV_^ z(13(7kNrANGbdG2^ZHiEhF(jNfrK#}QZixla7tRcfv;@P`L$I!S#d2)u(_WHhF@1oc3i)R+n$l{ea$r(3tKj1`% zb05g)HL0WfGYmsX6Tf&r5xcsa8c6XWyddmI-p*3MG^R-PP=FwsKD=>89^mSla0Kc9w}5fp|vA-Y}0r8)3P?t<8k_ zL(0kg!`zHcU6P>m#7;zw}L>IK)$zl`qrAiDwtKiVA=I= z@*p-L97dm9Pen{BP0r9MQ#4N`EcfhC&L}GG@mCh@eUL!8_;2CE#>jpX7+wtu(z8pM zeLf8K#rEJswU(O71L-zwc}qzC6Ap{BDW5;*Vg0FyZ;mAkcCr_`a$GZRMwyVIj#+Ke zmRsA637Tb}NyElS^>{M?Q<)*4Mt>Z5CV@{blnx$pHGc9aN$cJRLTRR7P@C1ig*B6@ z>X9XYJ7o)V%Q%ubJ9<;KAm1|u!pVaJ*o>Iw(aW=Y>NxdaA1M}_8D+?yLyQMNKtx;@ zfEe*aYf|LF>ByHgHqN18^nh@C!q65JvNy-$Hl6iVqFG9;Yl^8O3rR4Gnd}(!XME=r zB(X){EbGMCbGBULzr*V`lq7PkH1_dMu1P0!J$SDAlIT?8Mx~7vZntOgE6ytYzm!Tu z=S^J6L%D`l#y(j2VpMg~0?pWksD%yPt8D(ulXK$k1Tjrn10;rSfS+f>@^@vvFhlM zq+}+S?$1nHKnUFzP-g>!y7bCVLa?^~aUI@p>#i$-TjLE>wdr0NfpQX=FOJ`UZHcpN zY?pRf6ek*Y{IFoG5Ukalk>j2v!Vs)dD%QI}D)HhToJGRjjoy=7%0oI)24X%DXTkkQ zFyy%|7+-k2(AQ^nrkrI8i823sa@M6x?1%nIaebPCyD$iU_S774IhYKQkLxq~LXDVp zp%6D7TvTs^7C#TiKc@0oIR5Sd#H|QBp9zH7CBjAJCq(HzM+GLA8%Wj*E_gr1#>O|_ zi{H>$5qu2w z3?yW-vV}Cp#Y?H8QN)mFk6H58v%$CB(K7$@aUw}e3P&rf z77s8dVj%qdyhXPhEW(V*`#_eL-DLUsmwJ8c{y?6Gk%lCaIGD=MgI=(4>s z*|;|*&jAM2FEXB$j!rhg5|#J#x{evv!cpoZ!KP53hOmRxo^cu`#(xutYnl+>{3}W4 z>xB&cnYv$(%rWN`vQ8U6KMAx>UC_hcm}hRrsx?n;{2cs@9I_5al8kJoHLjQfy)`cS3R!2XK8@OB&w807 zQ;`^U+-02)G!DF!IPX^$FCDdjL)Gyf9iQ_ z2&LiaqDDAh9~-c%buM{XXi$TafO0e9R6bB0bO{Bh!0>Be@bhkNt+Ip9vhLqu=4n_z z%wB+^$*oIqwif+xnB&~2rt)FHD{5+OPrANVXPi3l)0}vC!W0X5jb-a#=hpDI-Mlo0vzi?+3J6D7cRrNaC}iGKvB-F|2As}n^w`&*<4?iq;M)id`sMylPyvI zm3d5|FAqzCf%G%|#&2$X?DJ=`6uvIpQ{arwErm;NS^ial5K0}a z2HGsIQLHU|JbT?iR40Li^1c-});MTT=b^SoLt0-k2;ISBOA+ECyV|88Se50vKle@7 zmnPfiLUG07Iib+WG<{^pPUvDMN)8`zn-j(=eZrfnta7em`ewJHEG=joL2|b2h_>$! z<=no7E8lI_KHck`kOAdqZSC>bt7Sr$R}bxzyQ3a^4UFMjp1PtF_A>0}C?AREJtw-ofB8$KYd<`$Wj5lG?TQN#m$N8x9z)5<4>3c{MBAy>ZEE8Vitf=)yE+d6qI{-2#-$t}OZg)>cmTx6}1xy&HTVw5zlh)B$V zxvr?!Eg{xA?iKYrrsxh`v>L94yQPJc>kS=7J?aL)rMmVus9Ruu(rK)wXeItx2CmL* zPl35Y7nc&}YrOOY91wW4z(^#t_eC>;@!=vbPg0XqrGehIG9X437dpfUcy`4IZr*bI zxP@&q6Y&iuQ+T2+T?(`he?0d0RDg1nK5&s7G0TH0X^gfROrVz-Y!sB%7Z-k#Cln1Y zQ+ze2d4PqTV3aOIT`_yYDR~sMtGY&L4y=M@gDds4B-oACNlOaUiq+T$oN7*EX^zP4Likj}!uGc~nu)?W4AKJ`Ae*Iu7jj!iGy0At36kcVY3iMsQg2)-B@ zKA%WdtW}sghru6PtS7kouN#DbxqoYeage=#I9(+wN07Rba!U!s7e<{1aUI({RmN|q ztksUlqafiMw~u+Y73W`e!lQ2wHAPBBnRS<=s6_=SZHcc*5qXD`|2kCH>AHsUyxSc9 zBx6cfzE7Kp#TM_W6>~Dbv?0-3!(9IA4SRRFW8Veh85gAlh!iD!$|?iUo*|-8tGW!Z z>b9FfK(r^42q1Fx49S9rHB!<7znSWQEFDpz#`&y(y-$nvmF3<7BzTz-c> z%TB^tHYM!cU?>IuNZO#i-DCLEU%zGetvmDxRet&6inDl+bHd&|fSGf8tm>uDrdPsq zwAlrGDhB0!jtK23eXXN$W6E#-cG#=~ONkNEzZFK$)c&I@9jm}cUfEAT9NCl1ox&}; z2_04^f3Nn8Q@Os6;IZ9!QAYOEE4G$3|2$Vo<=2C@Tg7D%fcB*FCL)XMTe=VO!S~gR zwgBrr$Wmcq5p7J-Q9HK5VcGFz7sXDx&`_(;X|ZF7%d`B+UZg{!OH?%+#Xb4vO!Ipu zo)T;mUW`$|g;>lxH3Dnc({u-OIA!Y&EC&KBDFJkVTtm0WJEYNhX9|FC9x1=Y$)gKe zZl04j#FE{sp06Zxwt;F9p9rIq9_Z;2?qUXg#;o9;^_w@UW1W9?|1X?#33x26MT5_v zH(u@VF#>;@=Q7_Lf80@P|6bI;2UW0-KR4qIrkkhpB#Fsft^xYnQ8jL)E(msi>XKmO zc>V5bVSVHB^~D|g-%m4I#ZfuR6t};mNI7+l4#;Oq7w{{SoANUc(s{|lX0PUz2?qjf zfj9s9f;%6GRSH9lm8}$6)h7qzrTDP5p0kIo-cbV61p`oODP>KL1g;4`wFuk2RxR@> z>U_7!l!Gf+HM->LOvM%2^{g4cn3UEH`O9U%>flHzY85+=hRSAa3)JG?v%o&F`->&0 zQfv$f(pMtPyq1H^Kq6k!V?0x5ai8smz4x}oUa%{8awyrp=P$kSJ>ht z-6q;b(0$j}-Wz8Zp=!xOX4H45bL{qF)%Kx3(9QcI$52=fnDth1EX1uiW;yw`wuj3KcXt<_9bq$4!bfl>5%*hNSI%XFuQ zUZY#=BIsF~`i`e4JV;$aEN-UZ2p@8>Yqi!1A{xG`4lWA@8MU%G{&`29$1jN}v#s@s zaN*lIXp5rlg5YU8Dc7Kg1vvYJM--YxUqi#w{Jn#zE=Ey{`#n6veT13?#=v;Yjy*t= zHX7XR_W&y$Jw`Cg7O7nFU3tKD+N8Hz5v0ut6a-yjd(MLFHb$g{G%nyNMmlA(ahI^}@fZ@&}*A>Z4xt{L#Nqai-AdTC_=Q_6Y7Q!w;K@u6K|L+GN7 z2uJV`(aV;|{=#TSAOp(PXX=@y!y(#y*A_q;^&i6!G8s%bX66Rz^B!dEv6*G<(V%!PinLz=mW z3lQ)(e5F_@d?NsDJL1Il$f(#@+U1cIdD<=S`~*+Vj)fE4Ocsz#M#8B3Q=js_^H{}a za6v*73x)kqbTw3QZ*Lz{JR`YkF%s%xSUEKTV15-Th-~_$8G7NJ%5hD@iE@88v5UMQ zP#jI+$vl5S1fIB5qMm5*T40(m#mYWpAsf+*bTu$j=LTV?OD+ddB`jt`;6Q7*B9^s0 zaL3)XM>^0dY;-FQ&Z;5TT-o<6a)}GAwKM5(79#_i*1;AWqZFMf&s@^`tvw7h$lRG% zHO+qWcoWw#W+zGXJqBfn@tA+%s%V{n>TF=(kR^BI6#sW|{X?`gq-q-3G4+P*09s0I ziX>)RqJp}({GjMx7phHlsAgoLg1SfO4y}gax&30cu}#23Ludac@$+gO^Rq=>3u>Lw z_h8Waeiy3h)5fu3>4(AwJJ?%w@w>3mwwZEj1SWbXHOvWCu$d{nxX1Pr6#wrhM@PdP zrXo~`phXi)H6#Yh!32NS=g9FDrSGbIsZEeJ)!71!<>?RCAQM`N^F!-mtHIr@=6~FR z{Dmir1T1vO8Z4nX~0BG7vjhHRy-kO4=g0 zwJQlM!o;-eXas3*%;7(XWEkAuiAPa%ubL_|btdf{!3gR)Q$Eo1f$9EyxksZ~v$`Ky zz8&W8NmG*I%a=~3o2-l)er%=J-0_;ss1P&q>yJ_AWVV)o4R+s3zPxHY8z*l0=KSXt z4*ErMr(glUQ}K1WmW|PJsLN3-%uC@@j1i*xG72kk$DTM1RT#}c@UR|a4IQCc@)~zP z>a$k}VsfX)FNf8ME*D|#=Rw1lswOp%-=Z@IeOG8V6!XdoK=kC6;9~FDR%#si>PVTn zC+bMylGR|3IjI~~)h3dEr!Bslr-{ni1!$rZC(1yCxq1$%b?cbS@-Dn9jG%i&N)HNY zmqH&NX0&kp^OCy=sY#sLD@=VnEQsLj?zAlzJDBzkG&;VI?@VaoKJ!Q(DRsv&y(K$6 zdc~do3c^l4ds`P@#Qa6t5< zSFuWtOrVM2TpizEnMEanSR(kiXHCYv@Ry~^7hy@0ONY!g40|7fe%q<8>eR2{%g#Bl z?_#9|x=ETDnNQJAt3GnQ!1L?G^xXk~?W5HiTGUNP18}acRk+{oCh4$NkR5g2+fmJ6(D4_p#BNz^{RGKd?z?+0iC%4?HF>$yTFc^<8*kP_(;$#?z2 zU%F|uF%INY=TcQzYX}<>vMEUO$Wj^lp-QCmoE~7mxyqbxm6Zkb5Vo}at-1LUZsX22 zHJbFftRoRaZjYo}8wEQ2*{t94*p;t_SnUe1eoD@uglhZD^a5S>xKqsev7ig%^4E z1xiY%6lc2z+i#B1?3THM2e#Ou-G#RtWvo_@Hd|SWnM7KnwCEr3TM#|ixtmVC0YoRg zOVl-(3JM4%)?7me@1d7%s(=XR>E=&I?fZ`Bm$H>u4$64r21+wt4bel4tM`w&o&v}a z<#ae(oc$IqzVf5f;|>Iwd_cKztTw&@5sx*0WVnppc>w1laKCg@Y}ud=gFi1PqOqE< z$LL_xOE5>Wk3H}ciaD7S`zO~ZIU$tP4b#<0Z7aAYZkk2nL0}g5)fghlJ4<4 zRVo58fz;P@rFnL~RcE^j%x{Y6OXNLyH(BAS{&vE;-KHVV)-J^Wx$bpYl10D4!{l+; zRUq~t+n%&2LFihTwUg^~s7IVe*YjXIj+NLs4RlYsO+0~G+xjZ0voa6gwpH&i&CUP*&v7{$=P`#6ry$~Xa8W`C{74tW)hIH!|ROHOYa+>m9 z=oYS}YkvEUA>$HGI`v;ltYyU0jr z-2SCGdM3}oF!B6he7&9inc9d!0oR>T_%GDA-%ib3F846yFYRB{wKkN*x1+2ZgYt)C zbeL)M+O6%95e1%=Gsqb`Kq4+XW26B#<56f&OjyS< z7>FbIxG9?Cz@k@&&CF>zQKsf4-onfa-jyq{c?KSko!mt!hc!|=5#;E<<5*?~|0cdD z0PVVL*+5=qD$C&Z&><~0To0N|iBm;MPL!ozJ=w47Lmfd!9Tv|=JhVNyx0d9Ug4o`8TSc|+hev!8p zk#ijAA;+Gno`&NEa7$v7<#_$zvi8oP#7_lO+o=(zhIbN3@bU_~46TFB1Pi1I*Y+hc z)Le;-RR(@_H<>Y3S9LjARB%l0p$(+8@ua0(11;Dq1#|GsjM8R;^l#0vVZM%?Ej-b!a=K-hq!Bl^A*3@HcO_&FkS)HR!Q4m08Pu-Vip)yj0TbwO zSKUFD?7^-gf$porUVF#OtXKSTKj~$S!)1dMbhr$#7=_EH;mq~pN%O*gRnJ@X8A`W% zR^|Ey0r^@%ZQVJ}xVvbcFUPD0*XVL(NAr069V}gVe^q5ZkP4`k`;L#V7YX_dr%n`h zZnJkh4r=}#6&}~2(gwyQdSROAqsK2mKU*`c+!jtgJ5%kUE5-Go-etJ@KdhwSjvSU= z1jvuZ2WI(<#0yZnkj2|a9?ktZGUdYdxiMu9=RyN-t_J#yu0zQ0U^DsLF_3-h8ko0ZEm^%!T~J`n0j=yUL_ zA?YC1N@SY@MAg-!jds1&>I=#Q^Ue!K_DW};3Niyi;j>{*Yb%aE_pcgF_9=MUrZ?2M z<};cBNk#p>ykJ;6WbLf7bXN@gect;Fu>&f`8p&E>?q!6|bv z1(Us*EmWIOIS+}PU$EBdo6k>e46!=nhG7(AJzvA8vEGh~_vae=85&oYj2?{8z3RWa zayt3fOJt{UVM%9K{g~;Y;Ek1%7}tbcdghySo7-aT@2-Mg4}U!?PvH*_xS-0@MLY%p z$ngM(x8@G1&_)87^{@HzV+Sl$=xBJgb3{KQ%unKdY^1s>b+uv=;xIxJ&;=rw0U_5p zYUz|Pnz9%drB&B&i_2@rfJ}*TuTA^nbIPlH0n#OGs2kmn=zx{C?5+pHs_c+0R++5> zH{H1z`N`xkLVcz-B?2Gsv&Bz_kyfEk2Jva z-^<5cuf+ZdZEDq<*n6$PeSll1{(fhJ?>mpc^%Nws8M98phioQuaUCf$k__zQ*hkUo zpRF*}VpF^7mDou82`24qUX40b5Cq zpR}3yh*Zl=W(S&yVxP83^*R8ubQ$raAeLj=1K7r9wKN{Rm#v9gm?|ValoHc}jRxZp}#)sqkN$*`?$7sV^@LSd)cXL^?lvT;KcI zbO8;Y>qT!)24^*s)TY|ME}4qyF-`@eq!Q8~F|+C{_ejDfS;Z8M7Ne5L&g?D#jFFXR z;J=x-;?pgCT;N4M>*dFlplM_Bk##DdAhz+FcZ^7tpR6H>hE=UP)S9Ef;^xc*{()o* zbxbjV2&j9x-s*fA$)PjGJ%I!gVVVYbRB9%sv#+)@w6(y`uSB8SgEX zj$m4qF0Ggi5?9WxAWjcAGhpC;KK%#Ct^{9L#$sgUlEY46Y2_8heny;Bv6%+NqgN7> zW=SkDPVggtCra^_9-{N1uDBBe`(L-QqL10a|MdYCaxXpP@^u`uqHYUDWtzm&v~DcmFg}Fn;|4K=^Q6o=tqq1KRZ^3EcR~d@y72qkO>rCC%9>3#8~F8d^;+vWG#is+~T`7;jbh$l{;WUhnI zx+}msy1Rs;oUu0cB+;{ z31Hm#D(MINOWI;?sHfm+67+dx2n-GT-Q<=6`rHWiCbqO`b zpQ={E913QNLM9?0bLV&kA1IJOEyQ)AZv=QqD}cq|R*OjC_M*ItHc#%OY^Eh7ZRS6svQ1ZL-LC18 z&~|L0r*pEZmQ{jJ9X)$!;$`)(Rx6UKbu8lgJ7n|L`=ss|C;UcX=SB$S<6StTYNd#q z>wH$GwB?cWH%@Nn<7x|ILo0ltb>5~r4fyDm+jTY34jS`XU7M@Eik28zefkbv1j{D+e zdI-=#DXnwNz(AO^$EbS=9u}`SXQMT7249;}ebr8}spJF%@rp;(lyOoHyoZ8VIQ5BP z*F0Lzy>wlJMK(R+If?oMvE7we{}~}-$=Y7-xqj3$WaRwC3uD@zg6yP2H4=f|8b0NM zE=Z?1o*-PUZHkTd1HDf8-{wNn?8W!T7Zc(;s(MMCG(2|t337_%YhS}LWH9LM{q}4Z zZt>aJZ`3P#btPBFpe=u1c%8eP&4jDK- zqjwc7vH}GEAZ{ChLe;Sjv~=dsK~DNx-dF zf6QS~Wg0=fPmmAir7e-#ewxcsRkOS0;F}?sl;HSmw;yumZ+ZlpVBjCloh%s-_H$+B z`}91f@7wXNrvH;5%l=a<(_9uJ@yVu5_a%#?^+2auYjtq<7o-*r^_S|BC2lxKVB+W3 z17j%etJ4jUqg{1&=r^Ms@}DaW=js*T@lh6iFat~2Blk+%wjFX<#Xs^X@QdX2&pU^o z?3eA1C$|BN1U{xA=XlNsLRh7Ff+hNVL8<>3YF3Dx2<6t@l3ts9u!5t6;S4h9 z;B`je^=b^|9dO&ufKSrmJ)$Qq7f2GG=sxNil8Fk^vT=5(g$Fo#LMgI98epeoJ0H_9 znK%jG1`xGA$6lLu;9YtCnbXN*ricFgY=ph%$eML!($z~+SEuALPJv5{y3gtx=+0*$^aMMywD`b@%hT}YX1=^ zuxnfLoP|isf&H0#9!YwHb<&_ac6Njjq&(qFQn(=idj-73{S~}EkZ&!yp_iHWsVp-$ z|4`15E=`t-+*`XFP+S}YH0i@>_*ckD6v}9p{ixbux1r^RAvJT`W{YFVo2sWq1Z#r) zZPWM8%d56l>0^Hl3wX2$xtp*+o^XfMze4EQ9$8%ik=5S$H*eg@3j$n+uYW$&^WgOF z`!g6vcY4v80Q}OAc61{;55pC&--@s4S~BiEFz3|?UA#9FNySFk zH&0+l2Va@sP*q;5zE&4WNul=`4l^j_WX<$k-$pgKDmxTd!A%D@@F)a1iL%n zTBJ(Q9pVt>Jy@VyE%8I<#PlSCQ*QzIa?#TA%Azl@-y%PL$l&618N}>Xogk_xElUX6Y0> z47n;nG<+BX9kvcS4`NOZjI-U7BytuW@;>dLxLVQZ{{Z%`3^XgsTtW7}x9GrT>$Zn& zLMs_XnG`AGrjPT)-@-q^>bkSp?;g26oL=+Zm}POBQrVqw&IQl&?)IDcX2)bAFHB3a z>x>WR)Be6yzmbM62=^U*<^456(rnQ<1FQ6? zCZn%LKv&JcLg53o5}8=E^vt6zcIz9q`V+@-vH`sEL~`*ZP&P!XQbHLUmJC!poS#An z{Wx8&zZpbpWDhHQTi*^-DS&(miRn`!3g}mh&W53I=n@d7LR)zg0u7EJ(3^d}b&oYH z^6PV0`aE+oN&5$&dD{glC<@ObJjal!IdOdko4p$d9a*rV1ALd-;_)rID8PyO*4=E{ zJ^Ommef~RyXnrZ|Iz7k}PSBuR;Av?58ENC!%edPZ*akTx@JXIaGbPb2>^_CtpTZ=w z90Vx(Kr-Qe999UDBO6oBdT<@zb!KZpQ;LelNfIF}O8Y7;-tU-?$+Xzx;3&{!L(qq_j-^L|L(z_R?cc!l@M9R7mf@Q(hCjy#x=)C|NDuqm>K)I-_dlyu z>c+p+!7;#{`&yA5qU5ElfTqt&NRRB&X9+t>W}3IsMe*D-mb^+P=p`^+wF-F7+cN&} zrIE^2w&5x|q@zi@u3xcT?PKI=lHHTVr~}p)q^1!T)Q(AsW;5k9O;%VEOlfc^%i@mi zJlv|4V%)vHpa+zA*DvjF+JLx+P1pzHSdvSrSlgCgO-j>(%P_AHy}vjti-wN~ub~DrQ1hhhsF4NBaUI<>gdGzyB0^qCCOxFhDAzu-v zBMo2IB`0(#Wk1ArFfn+n&(FmU-B+jJPD`XvAsB>g&k$p5}*rtr` zNkXVpcEu>IO(3<(B^xmdl8HLa1|Z~jqd_);+)oo6MYnr0AWOmWYK{DioRl=l%6l0f zPOQWueM(5yt)5K0N-%sID&K8)x3T<|!w%~^?zUS;fPNvl^lw^{MX37;gcBdkDld{J zaBuvGdHxmdi(>|_p!Pkx)<;ob>e%qcW? zo3)R#$YHHgMCCnFYJ%zTc>1iPPT^W>XL(%9X^_Rs`jW($5xbva zJnx{`Kj(BUY*{NOp4*u$*fkbk>mWfi!O|}bMcl?cs1JD(Q0dfdwewMGZXhA^EnR}9 z;>frj2xNq@G$=A}-+EU7;S3d3!Hrk*OS;+>ZhY45h$}1J#qq<;rXd+1i$O`fEVQY< z>m|nD%wqZ{_!ov8ctWuv5FqzoW4XGv{(QE8cn zdB*Ln#)gL?PK5sP_8COL0 zkKRFXC04;kP~e3nk-0kQ$hk75gd&r*18fH5v12>EBH;9fdx<|IDp-6tpjRT|F-6J# zc)J>}JUjux#-IAORjiB`5ah63HUu9yYQrQm{3_NWt3D{3+Q+FSm`kzbS?K)IZ3Yhj zh8H|5dm5-{eYe5=7yIZ0z*KIZKF}GhoW7_=PH#G{ciV=G#I~5(_RpzD zsoG;nMBOD0e$v0!{o$guU`Q|)&~Ah?8Om0^IL-Q5wZ4-QawW8R-3m>kGKZ=;RPivq zyKqG^Sri(Mx+spi?|#|T&ygJ)|Ob<+Y>woA=J2H9PU| zRJUgjJn<2Bkqxbu(s>hcFru({kJ zXk=&o#iqYAw;<(nzpW|9NMp4dCeifSqn4(~$kxJhk51(CsfJamh|W|rF62DNSxzYo z>H(RDj4pJ&AQ4|E*I#@;xqU8BJPRgzyeo+Q%qQfkxbiH!^}xX*Y3^&{SF z{yS}6gkO4yYD}81DN3HU`lk4{Os>)2%4ru6CV_8-VT#^6+R*M9d>UM`Sy>hhV4d=3 zZM2o&T?>l5`v8l%%$Bf<)i2UDRR}E6(%$_k16xwXxAV?e9=! zn}T}dUXWaca6p1+mRY3bytwpD!0N72xgK^sm0@(RLmwK z@}5M0($>MfUJ*v2Mb@%DYtR$-_7})rz9*G*wN0Y61~X`7EgNp|z2_Ve@i$5-``<0` zIndE*fO0EId7h!Uy{``E*YTgdRw|B7D%G9#BQSwy`9}p#vr*?NX z#jH}V%Tq6=`R*e6$>X`(#hixBj6KH_S6w(J3hnKzejaYZntiE@p6UeuzR8i~EbFP> zmL@p7{d++MZpDF(H>7*2KQMed-#Q6Hd^z{ejFBtSON0a{iYJZx9KPVUI@1u(%KqcQ zg|5hoEn)kgvQEo`+N>1Zu~`F>o%>`m`SpK^7T<6zu?^zR#--@^U~n-=!#~&E z!?CLtX8Wi{>nW<=8iyVQXhGSVWL(3_f&IW#0fccSW0|enh7nYwUFenyu_}~Kr?o@x z`W0@J@MDzjkyV+mCR0C&j^p%QSwj+vC8@I>a&I_Uvze|EOQ2>Im4CIE&$5Tra}3Pb zY6u~z;pwt`{NI4lulzR~A8mD+KU82Zr-t5v^P&kipFs|RWd$Rlz z92;pbww=&X_s^WH=s!R-jl{t6TUMP7@eSUf_aT4y!-y% z#|;M(9Etzd^v*36h2ASdj}qFL-Ppp!sF9+)y}B+Y?W)}eSFXrWBTks2n5qq{FKE)V z$R-DiJX-gBFV)#D6l`wFjX~}feSQqGx!um<_)5|mQ-oSsaRIZyyU|!Etm6lQshL&> z?e=Qb45&UAEe=bRdLz(K7c3o2s7#IBehj@Q`xA#pO?m)Bu^u9Z&6PmQ(Ug;qeDi8K z%i*6deZaAbn0oR~&S2_64$9`AnE;{ESuO(5G^c1nxmG-VY?n?Dx(EWOy zZ2BY;%0xQ+kL1DIZ}^2QV9^>7fvi}*Oyy><%9B=p>+-cd(!%zzcp%Swc(q6OW~wjq z%HM0xb3@UG5&W_aY0+R*}Gq@zAV_I13d{?9cba z=fuabq|uw}F=cEmz~E=l+xiKo!L3HMQ0m+J&Nv@$A)YyEy3fR(dA_r1odzeg^U z)R}c7zS$4?o1u%^b{Lx++x3+Vf6=&BQrexGNRi3OQRE}+}ew*7;rl^=3kW2tAa<8ZG6 zvCf1yVudCun;}nFAK`~tnoV`jo8y>sFNjISWuc)jRgiQ^%qVHnS}H|Gkc%K7D{k{D zXX$P~kNGVxyH4vY^8r3Tu8#e3-=A^O$;*PZQo$-2o71EMTbR}?l>0M9ZtuYjAp*om znb&|y3WG|7iDU|uss*uFz;!Z^b|S)oW1#PtnFT9=G-zT)DPLcQfM!PExg^L3zd=qD zj`Z&&3XpgH?(Io@3RLkx2?AXp0*jzvgVe{#5x6061Z2j7BGd;`A>osO%E{=nKqIas z-5`YmSAdGp;1l^H0P&>=5jl#oHX}1gLjZxm;e=2UkL4TiiR=qy0>Kv&k~3qC+XoB- zeVh=1hd}|!fJ>n9Wk~M8{Js-0;f8{al@q0jBf%0n5;5(E5Hoc7lN%A)n?M*D zG`G8EFHWok4EN@o+h6<{i64B@4PN2ffpivuknp=CK@$j=q|gPgoM{eS;T^Kp%AZ%c zFR)~Lc-G$N_}l!*N?1*;?n$6$YLU-7!=w?^1Fhld+<(wNIY3;}WNABEZMj`AuciIB zpA3tHKK%Ls5lcDw2%;v37Vamvq|pUAxG)$cO3X841QGTs2xEK^5)n^^NFY+1M>I%t zjElNp?H8F3QD~RyFJS^OMW`xM()Z)YCGG>e>k#Mp7ak%+H=&OZT}?KT*)(JX1!#TH zD??gZE2jRXK_D$G-(UU1SfE(OGD6?dm;|t9O)&@ZJ%;THnzXvwnuMtlyl*^f%!19@Q6|TAj9rYV}8S+7m zm^R25mlhX~DPq)EAh7HPO~^TZ8|n7cF+-sLf0@BUU` z3K9QxsBmjW2{PwD8Bix}-qx(?&9Ptzre$Q|XXOa7@o%sEO#<;- zc)=HkIp(#sv^DyA)#J%I#qlC(a{Qr)%@)5nD&kPtCLgJ*w=j3!!Q;li2K))qd*@2o z=c`t>WjW|Y#7S!%KGH%X2&wRr7@C}LP5y^fv5bp^5@QQu^LtlSD}h|w-lI$29SUnI zpDHaopBiXZHM%0kElCTr2=2~0Q784Pt_*#7z{6_pn?Ci5RHQ#s28J8cNc3d zt7)~XyPo~Xngc^^)V|Xn;739ZVXYwtpAdc7fEeAMR>@FndHF`QvrD196stXXacYG; z&0ymufP}fSVL)9KyWK?1t1^^~gk|bHwbX8w;5Yk6txmIBy1|P&ASKV{#!8HndDtR) zlaQK4)x@IdAKq?qec)(mr1^OeVl$wu717jHrlT@1CeFKVHN#*H_g6n96~LlaNj2E9 zor;}0@&)KI8T%T)nbq=R9>Ep!UYbO)C*y&`VSziqX#Mzur&R9{!cefQh+Xo<^rLk< zhR_`2`^$O}iRz)3foK-icQ9}y)B^Hy=;V((Vd$=z&qZ2% zcq845yvH&+POtV1CDMFyrzH$D!g)Y0IQmF2pPTit!Cg;7&aj~G$MnCbvzMT|U9q4P zr!J`TjM$lk`l3SJwmu%qsaU-Imr(w&Mf&49HelwU(U~tSuah?c8X|e4mfO|wYmN$s}J=ty2UIOy!FWhw;U~36`W1Q2fTiV zy7IWq!i?Jhdlyvn{yeQ#1Wdoot2KN_*mnblUE_D$r!dYXVzp3hGYu4PJ9>gj5)EN7 zYcn?7Lxg=8D|~8am_4I)A0GbVEjnbft$Zu zoZqB0OhV_wN+sENQp$hk){F`3w*qi(gYK^0$d%5E_HM-gPITl~3S-)+73v|e#d6wH>P1$1j;d?qu z4@{RJr*!7gl>pXt2#mZZ;m6Cc;+LdGWh|#^x9fCSC&lOK8u8Fx5YXeE+FR>=Z>0+C zD`Oo(_Ylb#z3OJ`iR~uKGzDMY=c=&ZMHNU{niwjS3XSrzQGX_^i&2v2jZ{RXN~o{` zKYv6uLar7~u?!W@Ylj+*;-zP=om(Bp+iZWyv=otQ=}dMdHe3$IwhnG;(GS^(;oS}$ zun8y}ml-eOJoGj;)m0Svu2D^F4b^%W8WOoUx(VCQZEEEtx?hResdCH{EB(`mBpOM3 z+N8*NPja)yla#Ak^gMrKZdr4;YQ_Ig~Jp{*ApN3L;IE0^=} z)kZz@s5O>X83CMLpQ{#zc~3Uahl31}b9FwhR8p!REX9R;ESgkjoGqvOX1I*KLcy@J zFbXN&<;wLjFSFc@{^&i{i2`% z<;a^b@p5jSaYt{`|43{!oHFYAsH46_fw+>IVfp(^&f|RwN2?wcYefq_J?X3? zBjXJVD!raEILe@Hg!s#6dZH7D+^dbOb9`02v-9%ZIqOZEpzC_LS10LE$Ek--lt(zk zAdTn9ho_-QZK#q_kMO)*{edmp)vJ$$vf%Q{gy1WFW!tYNsG-Y!cM`m5 zD(gB7Nl4?AI|3ep)um?%r5O48YpniN4o8xWiY67MdVS}nyA(pY75Vk$Z(0h{!3`D< z_pg%L!sF}XnCA>j99(*>8&^KJE*{pq1CRRH{A%y-HOaUpk+u_~(4vOW6U0boRGrC*QipU(A#RQ(;ib=hMol6YHwQokeWk=iZmrefvNoP6!7o8>IVq^O+P5r;&skW-J z_G`b9dfwG-5dS2C8ZM9OBMWJ*YwXQZz$h-#u1H6Laj+2m+!B2^8gj!bYR+A0-R_ihsatH~eO@kbOuBnNLfhC-@pb;yd386`vDC7E^(y!a-nYpO(^b2+ zQnjsb!}G4v8ew@Uwz^I-)O*vN5bZ&+jqz%Em_2F8@qH}h3lSFn>F<}*O?4ggr1)Z4 zd0}&&9oO~ym0*TL+5CWD_}qX|fmYi+c@Lxj zhj>}3e*CMPmnSbb`rKN5W2yb-YqRA>{i&WV)3R5=k!uf>&a&ScYhvSO>_*A)+1hsq z&m`u+2GME=8WTAPGYB*!2_Y0b6Nr}g1!%^Y<8=%@w{9qLYmLn!iN z3>Sv4>(ng_m!VCd@{DXCYaFV;@dnO{6CVFr&^t=mfd{ZoLsGdV$&BbmeOQo#6rSu; zlQpspn~p#b#L#0B80OO9Smu@SV?!y=U;h`g3u8tkJ*Gs%!RaWCf1ik^q2x} zTI`{sY9pCPOFz{8jB%^c*^H(yEMMm(!r;6nKd&&SB|>5OnK#r)(@$tKfTq8c&r%*z zo@?oBcGC>nGnaV|@?qmlgp+1j!E%^yYx+nOApKtyfVuuf2JaTTr}`!4crFTXVm)I1 zVmV^I7Xe7$i~`KskC>k{@3ES-?Xmha?XfOm<`r!JY->gUPP8KcJD7VvV~$?neucis zbo^8?4x8^q1BL-FCPZ|U@MhG*{!>a3-yR6Cw|Q1~T}BlYeU#vf$ln7M?douZG;w3d z40y7Q;_1(oT5?o8e;knZhi?7GIhPG@js<*mPgKS{d#^X%Ax{?a5=1e5J4Wls)#6=m zFeyz}$o~_Zoc{?<=Kn`<7BM09+|zc4OI;Gj!tYi^kzhk9A*`#}wyash$YD|kMv#Sn zy+3AQuU%F`&fdRFPh~--R}sboSl1Ei>@YU19uMS(Ol>x*KRSLvsH-o|>LAn#Z1XO6 zx~=`%tQd2&$lcV`P0O;HcR$9J-+Gw-n(K^w{8iy#ulp@2Na*^x=LgVXT_+SnedDc% zcrD?*{&l+Udl-CXt)Mu^$eG(&(5HN)W_<~I-b)`2I?4s^{HO3Jsa{FtP60Dr zp+(m@6@4=+_!)Epa9!5KYxA~=M3-jub-*cqe4E3z1$H0a;A*_wS8-l9_px+;=&`{& zuE3*FqjK)nTbTFl=j~wibs0mLo%UR$Ok4whZWXqa6SqDZyZ$u|E?uK;rMXu(S-sT5Zb zF$A$(QUfI>DVK`&u(5$gkfOb0m>72Wff#nIpNExi)L|a&)hM&_wF!R&XdB0J4T%Ax zA4@vi6a){smZ?w=X{@j=j$03yGKSt^vV+=T)N$`PTN_k6Zb)3$L4Pk9k1>GO4TW8H zxFZ60NdZYNvS`B3jXo$g>|A`-pxrNVXRz}_XWqG*e_6%+6VZQfLEm^M^oBtvyo9aDa2`XI=8E%Xvtk~t`NJZ1%?lpr4@$^+|Nce+ zJQ!41K7U^2X!(uZX!(aYY5A3XZ?WHgwsS&%Du0mlBjyj* zj{wXnM*xz4s-A>=D0*LHK4Rn{2A$>%QiVRo=t5uOpX-snD=7SFQ?2gYl@#!A3A3ZT zM{WxCJn(i0|B-ozc-B=Oh7_VgLo)l}5}NxsV~hKL%=d-Lxn>{hUi142jO|9yb)NLG ze^NDgl?V6TS1#fICo;MIKSO5H|4-)sPv-v%$;=#B;`mQwa{nhXS^xL+%*{&3#N*qn z3{FHQbpe7r^HzMlO}fs-vx%IrzboyUug>0ZwY8N69e6rFXRWoam(AXt%JKWNd|geQ z44jLl*E5{C-KSZfC9bIF-b%Y$onJ{o{AX_?pSc}otpYJQ(4T}DI2UPG-Ott6|IKI9 zetcHnj?Y!tzig^zX$^A8R~rFR#tG)AP;8pgx2x7({7J;JVQmpQGX>Fgyy*D6j-|;$M_|%|9sb>8(aVz-! za-ib%+1M{ZZ!2Cdw3(y28e7Yq#}1d%?wu2pu0^lHRJRUfxlR8mrnvix;%d#=_0nGm z|FFuyGQma&CLJXZD=-A~4`K)e7I4k4r@*QG_M_iNpwL*Err`PfQ;$zQ=uJ1+6N=N z1o5JKd5%25vzc1^sgFzhvBx6wobBLkxDg0l2Ye%yow|W)S0oqMkdI01G=v4Y$m1y9 zS6iSsaB2w!gLmH~gJ93^oMoRI-gK069tA!kmA?r=8AIH6Ns4)=dEnY3&0;*2I+ky! zNb6$TmW+q6^vsEkE&h`HC@eizUEqYZu$~FD@d7|;ba=wU&tiNS_DotY9o%OEW^^_? zC1zJqjk)xVtuOLc#)RGYWJ=RNruV0F4gfR-ehL393MhXX6Y!C`O>?n!_U z2=2k%-FNcH*0c3KRqt>A*s86qt(vLn+jIKf+f(;@&*@K}xjnQ4aU0EpR2!B~9r!CA zbw6BgPu$-D#T7V?7>lJ0$#kdAD}-|zQwV6 z@KGZvM@hs=^n`pxmO_XPo}#>y*mO7c0Gx#&#-_0BA;yx&ipX~rn5S%Ox??1S*&xaF zGZPRyl7^BvayoE2*!sk-6fvLn^XQz}2}i-!Zrs>i<4r-Trqo>n)UpINpP&lhC42SiF29DP!frU#7n zYAjw<)g=P!ZE#?6B#fQzT5q7{s0|11+A&+LT8oJDSg#^o&~psKcUU7?*^ej<95a;@ zYXzh)_xUi?rUN0HP;t5C%!@C-wxBuLd9cE0a@}J%*dQo$)?+FMH|K(}&n}CZXA8>R zFA5VVOqblBjHf``4Hh{sZEIj-!}?W^7i?m9^8RuhtYT}QVVb;!2{F*{?{Ow9sFhZu7SpPPS)reNyoSk>TZ{x{NHNR+?S$&L zKC^qJ?u*m!EA`919VtqB_651I91`?KM6bDrqsu4n<7~Egx6g|H z;6PY^b0B_Agx`Mi;r6@oKRHkg6_y(JGzLdtsdBCSx|x>pFQW{=%cHh>m8_e z1rsV6xTNA5a(>W(CV9bI!nE^&Dxo#6Q)AQ6kph0Nqx`MtfPma@5M<{*Z?YWsNxoge z#M*N5U`|mN;(FDMMJL`)#n{<-&?H?Ci)pQ9L{!&=L2c2SS5{kJOrFZvJC)Fl)QWpo zzDvLTIp$C()QLgRd+(jmu6O2YcmGSIld3Hm! zdYtEecUmjzF&$E#;TGem3H?+kSsTS{&Vs0k$c0HqjZJ2bSdS@y=+p)3g(HI}Ar{n` zA|Xa1mJJF1)?G4MxJe2pKSUavuI2DoOv_`=GmJvqn^nw2xK%nQo}uvqr6Q>aV+9Ns zTLUq+=|Wv0rQJnxC%@fIS>tsZn1S2M%zzOfyv|<>&u83WjP@Oj(XRTS8rQ)XE$$zT z_MMyRAB-0DH=_-KG1|HpL{pPn>Dg8fpNoX{g0AgU&v>7kzBpQyRaX1WXt`mGcKsB( zl0;vnQq5-$i)rW6fc3L5S2_O|2tv;Q$^~R_GsJJ)7ye9N{VO!Sers#K6(zFF&azh5I#a*KHT%BdRbr`Et}0jYxZGz3H0sN5@x;^VLh0lqduE) z$UYC!HHZzY16X3C;_$&z3Iwo0>0t_zP@kyhAwDc?^vsbMk(nsSQd;mbQw}Z?>hMG1 z2$CmX(*|=nBP|5v(5PzA1SDOU5#k9^@ljcMEcNkwSWMxq2s7$Z<`u51tIhEkYys+6 z!pJLp0CEw*FGx~^5g#x?4HI10%*sXe^Ak4O7d36V2^;KUmj|OH6$EVxvLkw&EW9hR zoYST_@liieJ zV&RM0(pzkrb-_r=<0(*kUEBfu2nU-NJH83HOD~Qz-engv^)F#y)%IC%iUdXbTta_| zZ>AI5<4!roYV{y1T5+KMczTij@@lBN*fVzJJJ>Yh+AA_FE%oeYNPQ+hZ5wrflwYMz zYyAaRADnc;tEu^(W35sY zbNrjk{%h79fS3C}mzlHHkjttBcH14REtbOiFHXKd32W+`5PHlE8Mq2ClA1JI_YCPg z^5tGrD{``ZKU}%7*0{-JJ!9umlS^4TLi3p&vskXH@L-4cqa8V z&=t*+ik5R8(YU{s6J$|5&nLh9EZ1CHl+ebl?4l|BHgX43Z1J*+>(HX)M?i&%;!l&s z9F5ao+5q}M%}|bexbexjK%Awz6x^r=U0;v9XIb2>AF=DkrfQ}J7cwIeZondDB`v$I zuJ=%O0o0bZb}vz5zS|iAMH*WwpP7rz!N=^(*$fSHhXCC1oPlF=;rauTui=R!4)pls zHI$oyi)IqTlm)GcxJ3>4vHYh^@#Cj6T1C5Y?&ss7~ID&aHb}N=U$IToV$AWoaf`%{e zMHwro7kT1_->ApK*jAs$Q+h#|6fxnqrbv*N6&3Vq7#1Gl(0N3qr z9l~b9*G)XdTD<%rm0lMcN5_EdyUMv1jRo5b|8c&xEJoM4UAM}`Hy1(gJdadf7v?53 z2Y7bct>xNASXI?ifoLRyoCm^+1A4K=*gD=K(LaJ8Mj;1b>Q|adR7`V1d-3|i*u#vH zmVrslu5&Fp+EM)WFH#6Swc^HO2X?njBu_7`h!@?*4_nO}aL4?yOtc{^dl{9XD|#Fxdfs2hIP2kZ90%*;_9J!r+Q1AnI1`#tZ2 zR&-s>%KkF8=@7>~jwj0zpLw+j>vwujt8Ilv@kF*8u)GNi|TUp96CxxH9}Vq?*^6W^X4?AHT|=o)Q4+M?KmS-`&+u#Y}4A^Ydn!Kq?aQ2hB7HEJ|B2SNGBV5C*9Pw7C zhdA4QsA$UdsvX@qBnGHv@tx8g6P=c zBe&dxO}8Ju+c$x8f!i-b#^tQNf5?R>k#(-zB1?dL&oI%xi8{~`vy zf6hFsgI%pXtzg;8Om5b;+BW(xf$Th->|9*TXdIHB5PMfJ6ElZ42;yV|`_%HZbg;34 zz)I4vOlAn|J7o)46M#1eVh^zQb+ZA3ZETr2UfKA1yMnDfm|*+(!w}@^A_L2W1~AD8 zadC3-aq@9<0{M9OfIO_6T#THYj4(SDSL^@IL>p}3=4N9JORKhU@~}bUP}i2#XP5JI za^#3sn15$-+qhW&4hw+-{~Wt% z!+rn&hnl5>7Q`8iLlqzh%M+IafjuAq0Y0w(89(yC`u%4B`QPV>9~c>V%<~ZjUR8C? z542i^G$ROS4KuhWdyy3z2!r0)o=FVVSLhN_n1E+t3-Q27M5nOLDvckCL7YMXZf+QGm2#q-4w=rkpnPhtZ%Yv8s<6&%Jw{s6ToS3k`*14l6b>*4vB- zL0%Km9l}YYV~D_UD_EtSG$Ts|n-=o;xsk-;!F}jA=E*{EiLoo*$VQ1Tk#_7d=RitE zs`h}85sTvbCTd~%r^?Ts$0h}L z+n}#XIcSsX0CZSISrYTBc?N-Yp-Tv?!$^v4D4X8UTW_M9OQKO7$)dQ1_}M2L9=I2PciIJ+3_5Qeun8PO9IRQU z5}&ENhssD1$V*A}J9f&;6)TA8Y9Q^xw~Zpbz|&={5B`Y}%v-7;T-GJ0$Rt#jw6EzX zSvMwt;E|ll@>GdfC73igsY?Ve&(paq;i-UHlW2Q+gZr@b+Jkwe)9lse^FCM#NY2_l>mN3|j?bc?rxU+sUBz=C_5wM@vIh+v$z^^cUZXR7uHT53sH# z0e@ZU5&wjEy~i(}%zfK~k(uOFOPoL^W-zU(1OMYP;h2QVB1lbjbE7R9SIE{JnmufB z9xE=Fmj4WYg)-SAokr}LV9>+b4#y-XTwu_f4W=CgPw+nd!Wl?Zq;%6fTU^}##b%kB z$7SV@+q2V8G+Q6L2sXL5dFYk}Ds3+;g`l?X-=kIJCud9i^?NNqXn4aCJxc9_2x^)E zs+t*JN(j62)eR^!xBPi327N_suGqKE_BuTtPu9M4-`E>xO^=s@#fVi#9L1oFRAoDv z36&D0IRVXy({@{7LpPIw>)TF1Xt24=aAXKMlh^IXxV?mp{u0s`d#sm7uZuUJLRQD; zW8@OZ(;Jq?Q~-yE*HldLyLHmIH|@QhRulwCsk~5#W@}i_eIe6hZBA*p?;`wk4Km{7 z{JsT+Dh&uFaq-BOZ?J!ZO?10imG`;LYEOf$$vsf^32yLV0 zv~`Ee?R-Sfc1b902&nh*r#jxQNRE`hl{Yxm;}KE%a%AZf(7a4f3E>?x(`rfIcKlJR zTaf}_If>wqu07sksWsD+J&b1oKXDUWNMD+~d76Ka{>r-&V@b5~Dz6FMEV31~6ZuN~ zJYl)}l%%sm;OR>>ei+RvNos7_)zOzFL|lu?Vxr=G=b;HM>Hv92`e~&+h_1S~dt5xl0ulV&i$-$>3TydurVz4T zZF~M84>I&?66pq`Ha_GPq&9f+<<3Ue{SvviPhmR(lKec@i)xufUKAKc zpSWZs^h$_rdy+AqsE!o19f<#&+?;$L?O1=FqA}?NEx14R8{7!`OsWjU9&PHH{?&P9 z${bD+!Uc%Sq9sBk)oHT&T50U|ntV%cWGykoK#fweIaoR^QrL96=^Rre@!^d!-T=xc zq|^x&)kgXTvKi<&A8c$|#gp$-|RvSO|gWUHFI1yYojtYCu^2-*pc34@3)d9=01PK~ zN{KRD-c2sigpAGU@a%|AW-5vbnH7x}oi6UXmHOL6aH~{w@6pXxT`10UW7UNzPnnw-a=Nb8SZM~Yu2Bhq+%4o6=EMnzkGGizfMSL5FDa{f6v zp;P1U;!|iBH6%~|%Al|2+YkzuUFn1ax9$Oxl`MnI8DVPA`&hE<&`Wf77inhVo_BQJ z_Tj$7l_-6*?U&TXH92$slv#VnsL=o<1KUh$$s#UdmBtAWFfq?uGs0S4<>erqWC3A; zu=@&4TTAr;c_}-E^H~cgNZaMA{z~cP2u^~p6H&3+9XG|5N*}1q`!$|;DdmuN)(6nJ z%T9GVMsfb0uc*pVdiHz0?VFt(d9^^X$NBO6wcD~x6yM`&${aXzW7FX@)kLhd)&>NO z9ZCf?*DUh(Jk4|N&9K=-(@F!Z8TpM1@5${|F7cn?6W##$?2eodYh%aV>z2kfmI*n- z<>fipX+4xdS;!VzFUUV^q%5f}u3=qEKSolWy53klh6nw~Ii-D!efpYnW);|DcAVq$ zP3oi#E`dqv$@=>~!eI^oPmtglmp_J+Vza{~pGC#ePTA{I<>M zNr-mN$=-?O!15V$&dKaHUe4LgMt#}!z14T3dxWR2;wL}u#qY%@>zCA|d_~}CHE$1% z`sTwQwznti)`Gmk1B^}B1~wpJZH08cM3BhPk)W^fZ84SgF@?|=jL^m+T045ap&Ma^ z+X!BR&hUD(6aGXW!3=53I1|l-G^BlP`+UA)YYn9_AN2x)e;iavKuykxd4$70ci*n{Te3~Njoh$yRA99 zpL1r{13MfEWT#muyD{VQj;Qf{F$v;wKcKGj94W1OCOkKs`u6SteLtRln9!*OxPcnTU3-8E4 zSV})QsK0%1yY5Y%#v*lIXuP&4M|K9R<>Mv1L7|8!uAZzB@@tFXJFG?2{3K}zplIuQ z*JOMQMl3OWs&ct}tn+(OlsaTqUS&-SOn(`=@*^b{Kx?V z9vA1yqevsw>nAa=Iu-YAc*uo)*#DQSKK27Nm$%(qt(Psy(DWQ0FQqzVE^3XLZ`R<#<)rNvk@O*`FfcSxMDAfRcO7XHF3|I(fdr- z{Jo(^%u7gVgLfX^zUskd0r>qK?~slKUIWcSh@sXWRgP~A+ijc6T$_t%jogVG`iZ`J zX)V<#LFZJV+tHWoZ_U>a`<7Zh7&ag`5t2eaY*z+PvV7O99EW-p`-4=1SIc%=pF>B9Jpi|*Qv5oh-8j^>qWAu9$E`ym>HHCqj)Qo3_n#3tV;Ln&n3~)sSn$E_;ndiX}|I~6R8jzMYc1^>dzZ# zl%8zNn+?n!OOhvY>7peQ;dAOv{?4%WIfQc0NTevDn1p0Z-?LX?F&;ldK7EwBtA%W| zzLw`ujt8`kftaS7Vw=IFqLyOP=@b=1`lHV#j;iGSH$&e&=hG2E_4MHdfa~I*QHHaR zHc>(|oEmGuOP2w>h)WNyIiw!pLyD>1uw-AstfRW~ZroP>HnyrHGORwap^*hXMV{Hf z?2j}t>TJyO3r;a(w_6PBq6tpd8meU-u6Qcuf? zdAN>qFP2q*wcUuVA?0Z}3XmZ}oQSGF_9}I>buxEhP#m9O=dxF1ng%`dmfBMdn@?DF z;2^sXyyts8i+#EFVTK>4;L0eCH`G?=9`mG1Lz0Eoi7d*QUXL`&vrv~big81al-4Y7 z8ilr7d7uh+t}q|DHOdkaD#WT+6DH)yZ?0zL*Y)zYt&e_ib-M2)+mbe!xohAka@@rE ztc=V~0xY%6Fhf?GEl|cc>Tlq^+nB*s1oSat7X%JgI1=p3#Q#k-JMraGE&1b*im-s zQ8JwN*tkFCSm+?dxWLpz?5Gf#7ino^#HM#f$sA9emuve0Q$g`S2FCZcv8>1hxtg)x zxJ{Ad_P@Ih%vU+c#x7{RXJBwv*J%MV@NXm`pGwluGqlUdbGN^gBR<3#MyOcGr+SY7?iO+g zC=p4-;?tZ@s0CnpY0gI|1!E6Acj537W9s6XJ?LhChF$}8Ypr=>j^hQ~?*9}@vyWSi zZuCN_dgvJz7BNO@s3s5(XE}IAs~r-T26agp>eCHz*ngh;VOFU^S$QO_(tJGEc(_8` zfwgZSxs0V7tr`~!U*1isj=U?0%M&a&is;afd9e+g3d3}8&NbNoYOvUW#7SBoLvdHo z(4c$>pQW!oN`0wc6Ayl4j+ej4lvPXOguG~}CNk}76ubn^fIZj3aGZaZ_7d`Oj$*$S z!`kX~e>TFIG?_-2Ucu320DTiy8nJN+*i5K^O_U7qkamc{YA*58>!Jo+uo{72Jm!GcT#ce`;@ zgU=q^4ubCmAD}$pyt#ylti=Zm#_?`~JsT(=%JGD!tL`RWwcWOs74+FHl{+NLXp6f~ zGew19>ZrUU?yM*?z%?5waY(A(h%eJcWfUuSNIrg~rCqUZ{UZIeg_e_f>O**zmVKJG zbMA99zUKMi3-mF<7;AKN)LK57LdA<`b_`ZnS a0fzX%C^Q<7n~zJ7Qvi*gURF&G?SBAK+XwLg literal 0 HcmV?d00001 diff --git a/memo/memo.md b/memo/memo.md new file mode 100644 index 0000000..f5a033a --- /dev/null +++ b/memo/memo.md @@ -0,0 +1,80 @@ +# Questions +- what is a spanning tree exactly?\ +maybe not relevant, bc we have a single lonely server only + + > In graph theory, a spanning tree is a subgraph of an undirected, connected graph that includes all vertices of the original graph using the minimum possible number of edges. For a graph with $n$ vertices, a spanning tree always contains exactly $n-1$ edges, ensuring the structure is connected without forming any cycles or loops. + > + > Spanning trees are critical for network routing protocols (like the Spanning Tree Protocol or STP) to prevent data loops in mesh topologies, and they serve as intermediate steps in pathfinding algorithms such as Dijkstra’s algorithm and A*. When edges have weights, the goal is often to find a minimum spanning tree using greedy algorithms like Prim’s or Kruskal’s to minimize total connection costs. + > +# RFCs +https://datatracker.ietf.org/doc/html/rfc2810 +- contains links to implementations at the end + +https://datatracker.ietf.org/doc/html/rfc2811 +https://datatracker.ietf.org/doc/html/rfc2812 +https://datatracker.ietf.org/doc/html/rfc2813 + +# Reqs +## Subject + +### However, you only have to implement the following features: +You must be able to ++ authenticate ++ set a nickname ++ a username ++ join a channel ++ send and receive private messages using your reference client ++ All the messages sent from one client to a channel have to be forwarded to every other client that joined the channel. ++ You must have operators and regular users. ++ Then, you have to implement the commands that are specific to **channel operators**: + + KICK - Eject a client from the channel + + INVITE - Invite a client to a channel + + TOPIC - Change or view the channel topic + + MODE - Change the channel’s mode: + + i: Set/remove Invite-only channel + + t: Set/remove the restrictions of the TOPIC command to channel operators + + k: Set/remove the channel key (password) + + o: Give/take channel operator privilege + + l: Set/remove the user limit to channel + +### Verify every possible error and issue, such as receiving partial data, low bandwidth, etc. + +### Client Commands basic + ++ With both nc and the reference IRC client, check that you can authenticate, set a nickname, a username, join a channel. This should be fine (you should have already done this previously). ++ Verify that private messages (PRIVMSG) are fully functional with different parameters. + + +## Eval +https://wormav.github.io/42_eval/Cursus/Ft_irc/index.html ++ Ask and check how many poll() (or equivalent) are present in the code. **There must be only one.** ++ **MacOS only!** ~~Verify that each call to fcntl() is done as follows: fcntl(fd, F_SETFL, O_NONBLOCK); Any other use of fcntl() is forbidden.~~ + +# difference poll() select() kqueue() epoll() + +**Performance and Scalability**: The primary difference lies in computational complexity and file descriptor handling. **`select()`** and **`poll()`** both operate in **O(n)** time, requiring the kernel to scan all monitored file descriptors on every call, whereas **`epoll()`** and **`kqueue()`** operate in **O(1)** time by maintaining kernel-side state, making them suitable for high-concurrency servers. **`select()`** is further limited by a default **1024 file descriptor cap** and carries risks of stack corruption if exceeded, while **`poll()`** removes this hard limit but retains the linear scanning performance penalty. + +**Portability and Use Cases**: **`select()`** and **`poll()`** are **POSIX-standard** and available across all Unix-like systems, making them ideal for **cross-platform applications** or those with **low concurrency** (<1000 FDs). **`epoll()`** is **Linux-only** (available since kernel 2.5.44) and is the standard for **high-performance Linux servers** handling thousands of connections. **`kqueue()`** is the equivalent high-performance alternative for **BSD-derived systems** (macOS, FreeBSD), offering richer event filtering capabilities than epoll. + +**API Design**: **`select()`** uses bitmask structures which are inefficient for sparse file descriptors, while **`poll()`** uses a sparse array of structures for better handling of large FD ranges. **`epoll()`** separates the registration of file descriptors (`epoll_ctl`) from waiting for events (`epoll_wait`), supporting both **edge-triggered** and **level-triggered** modes. **`kqueue()`** integrates event registration and retrieval into a single **`kevent()`** system call, using a more abstracted structure that supports various event types beyond just I/O. + +| API | Complexity | FD Limit | Portability | Best For | +| :--- | :--- | :--- | :--- | :--- | +| **select()** | O(n) | 1024 (default) | Universal | Legacy code, few FDs (<10) | +| **poll()** | O(n) | No hard limit | POSIX (Universal) | Cross-platform, moderate concurrency | +| **epoll()** | O(1) | System limit | Linux only | High-performance Linux servers | +| **kqueue()** | O(1) | System limit | BSD/macOS | High-performance BSD/macOS servers | + +# std conforming clients + +*we can choose one ref client that will also be used in evaluation* + +> **From the subject pdf:**\ +> Several IRC clients exist. You have to choose one of them as a reference. Your reference client will be used during the evaluation process. + ++ HexChat: Open-source, supports multi-server, UTF-8, TLS, SASL, and various scripting languages. + ++ Irssi: A terminal-based client supporting multi-server, UTF-8, TLS, and SASL, often used for always-on connectivity. + +# Reference implementation +https://github.com/42YerevanProjects/ft_irc/blob/master/src/network/Server.cpp#L263 \ No newline at end of file diff --git a/memo/poll.md b/memo/poll.md new file mode 100644 index 0000000..7292e4b --- /dev/null +++ b/memo/poll.md @@ -0,0 +1,147 @@ +The **`poll()`** function in C++ (via ``) is a POSIX system call used for **I/O multiplexing**, allowing a program to monitor multiple file descriptors to see if I/O is possible on any of them. It is similar to `select()` but uses an array of `struct pollfd` instead of bitmasks, avoiding the `FD_SETSIZE` limit and offering better performance for large numbers of descriptors. + +### Syntax and Structure + +```cpp +#include + +int poll(struct pollfd *fds, nfds_t nfds, int timeout); +``` + +* **`fds`**: An array of `struct pollfd` specifying the file descriptors and events to monitor. +* **`nfds`**: The number of elements in the `fds` array. +* **`timeout`**: Time to wait in milliseconds. Use **`0`** for non-blocking, **`-1`** for infinite blocking, or a positive integer for a specific duration. + +### Key Event Flags + +* **`POLLIN`**: Data is available for reading. +* **`POLLOUT`**: Data can be written without blocking. +* **`POLLERR`**: An error occurred. +* **`POLLHUP`**: Connection hung up (disconnected). +* **`POLLNVAL`**: Invalid file descriptor. + +### Basic Example + +```cpp +#include +#include +#include + +int main() { + struct pollfd fds[1]; + + // Monitor stdin (file descriptor 0) for input + fds[0].fd = STDIN_FILENO; + fds[0].events = POLLIN; + + // Wait for 5 seconds (5000 ms) + int ret = poll(fds, 1, 5000); + + if (ret < 0) { + std::cerr << "poll error" << std::endl; + } else if (ret == 0) { + std::cout << "Timeout: No data received." << std::endl; + } else { + // Check if the specific event occurred + if (fds[0].revents & POLLIN) { + std::cout << "Data is available on stdin." << std::endl; + } + if (fds[0].revents & POLLHUP) { + std::cout << "Connection closed." << std::endl; + } + } + return 0; +} +``` + +### Windows Support +Standard `poll()` is not natively supported on Windows. For Winsock, use **`WSAPoll()`** or implement a compatibility layer using `select()`. Linux and macOS support `poll()` natively. + + + +### Performance and Scalability +While `poll()` improves upon `select()` by removing the 1024 file descriptor limit and using a flexible array instead of bitmasks, it still suffers from **O(n) performance complexity**. Every call requires the kernel to copy the entire file descriptor array from user space, linearly scan every descriptor to check for events, and copy the results back. This makes `poll()` inefficient for applications monitoring thousands of connections, as the CPU cost grows linearly with the number of descriptors regardless of how many are actually active. For high-concurrency scenarios, **`epoll`** (Linux) or **`kqueue`** (BSD/macOS) are preferred as they use event-driven callbacks to achieve O(1) or O(active) complexity. + +### Timeout Behavior and Granularity +> must be 0 (non-blocking) for ft_irc! + +The `timeout` argument specifies the wait duration in **milliseconds**. +* **`0`**: Returns immediately (non-blocking poll), useful for checking status without waiting. +* **`-1`**: Blocks indefinitely until an event occurs or a signal interrupts the call. +* **Positive Integer**: Blocks for at least the specified time. Note that system timer granularity may round up the actual wait time slightly. +A return value of **`0`** specifically indicates a timeout occurred with no events, whereas **`-1`** indicates an error (check `errno`). + +### Critical Pitfalls and Event Handling +* **Regular Files**: `poll()` always reports regular files as ready for reading/writing immediately. It cannot be used to wait for file content changes; using it on regular files in a loop will cause a "busy spin" consuming 100% CPU. +* **Race Conditions**: A positive result in `revents` (e.g., `POLLIN`) guarantees that a read *would not block at that exact instant*, but it does not guarantee future state. Data could be consumed by another thread or the connection could close before your `read()` call. Always handle `EAGAIN`/`EWOULDBLOCK` even after `poll()` returns ready. +* **Automatic Flags**: The kernel sets `POLLERR`, `POLLHUP`, and `POLLNVAL` in `revents` **even if you did not request them** in `events`. Robust code must always check for these flags to detect connection resets, errors, or invalid descriptors. +* **Array Limits**: Passing an `nfds` value larger than the process's `RLIMIT_NOFILE` (often 1024 by default) will cause `poll()` to fail immediately with `EINVAL`. + +### Windows Compatibility +Standard `poll()` is part of POSIX and is not natively available in the standard Winsock API. Windows provides **`WSAPoll()`** in newer versions (Vista/Server 2008+), but it has historically had bugs and performance issues. Production C++ applications targeting Windows often use `select()`, Overlapped I/O (IOCP), or cross-platform libraries like **`libuv`** or **`Boost.Asio`** to abstract these differences. + +## --- Pitfalls --- + +Common pitfalls when using `poll()` in C++ often stem from misunderstanding how the kernel updates the `pollfd` structure or mismanaging the event loop logic. + +### 1. Misinterpreting `revents` vs `events` +A frequent error is checking the `events` field after the call instead of `revents`. +* **`events`**: Input only. Specifies what you are interested in. It is **not** modified by `poll()`. +* **`revents`**: Output only. Filled by the kernel with actual events that occurred. +* **Pitfall**: Checking `fds[i].events & POLLIN` after the call will always return true if you requested it, regardless of whether data is actually available. You must check `fds[i].revents & POLLIN`. + +### 2. Failing to Consume Data (The "Busy Loop") +`poll()` is level-triggered. If `POLLIN` is set, it means data is available *now*. +* **Pitfall**: If `poll()` returns `POLLIN` but you do not call `read()` (or `recv()`) to consume the data, the next call to `poll()` will immediately return `POLLIN` again. +* **Result**: This creates a tight, 100% CPU usage loop. Always ensure you read all available data or switch to non-blocking I/O with proper handling. + +### 3. Ignoring `POLLHUP`, `POLLERR`, and `POLLNVAL` +These flags are special because they are **always** reported in `revents` if the condition exists, even if you did not request them in `events`. +* **Pitfall**: Only checking for `POLLIN` or `POLLOUT`. If a socket disconnects (`POLLHUP`) or an error occurs (`POLLERR`), `poll()` returns successfully, but `POLLIN` might not be set (or might be set alongside `POLLHUP` depending on the OS). +* **Consequence**: If you don't explicitly check for `POLLHUP` or `POLLERR`, you might keep a dead socket in your array forever, or worse, attempt to write to a closed connection. +* **Best Practice**: Always check `if (revents & (POLLHUP | POLLERR | POLLNVAL))` and close/remove the descriptor immediately. + +### 4. Not Resetting or Managing the `pollfd` Array +Unlike `select()`, `poll()` does not modify the `events` field, but the logic for managing the array can be tricky. +* **Pitfall**: Passing invalid or closed file descriptors in the array without marking them. If an FD is closed but left in the array, `poll()` will set `POLLNVAL` for that entry. +* **Strategy**: When removing a socket, it is common to either compact the array (shift elements) or set the `fd` to a negative value (e.g., `-1`). The standard dictates that if `fd < 0`, `poll()` ignores the entry and sets `revents` to 0. + +### 5. Performance Scalability (O(N) Complexity) +While `poll()` fixes the 1024 FD limit of `select()`, it retains the **O(N)** performance characteristic. +* **Pitfall**: Using `poll()` for thousands of concurrent connections. The kernel must scan the *entire* array passed by the user on every call, regardless of how many FDs are actually active. +* **Impact**: As the number of connections grows, CPU usage increases linearly, even if traffic is low. For high-concurrency servers (10k+ connections), **`epoll`** (Linux) or **`kqueue`** (BSD/macOS) is required. + +### 6. Signal Interruption (`EINTR`) +* probably n/a for ft_irc, since poll is used non-blocking and hence isn't waiting +* BUT: the server process is maybe stopped by a signal, so this must be handled? in the main for loop?\ +-> Maybe it's only Ctrl+C / SIGINT, that is handled "automagically"?\ +-> NO, see **SIGNALS** below +* **Pitfall**: Not handling the return value `-1` with `errno == EINTR`. +* **Context**: If a signal is caught while `poll()` is waiting, it returns early with an error. +* **Fix**: Always wrap `poll()` in a loop that retries if `errno == EINTR`. + +#### SIGNALS +Yes, handling signals is a mandatory requirement for the ft_irc project. + +According to the project specifications, you must handle specific signals to ensure robust client-server interaction: + ++ SIGINT (CTRL+C): To properly terminate the server or client process. ++ CTRL+D: To handle partial messages or end-of-file conditions. ++ CTRL+Z: To handle suspended processes. + +The guidelines also specify that you should use no memory leaks (including still reachable ones) and implement send buffers for each client to efficiently manage these signal interruptions, particularly for CTRL+Z. + +### 7. ERRNO handling +* subject requires to not implement any logic based on ERRNO +* Key Characteristics\ +EAGAIN and EWOULDBLOCK are POSIX error codes (stored in errno) indicating that an operation cannot be completed immediately without blocking, but may succeed if attempted later.\ +Not a Fatal Error: Receiving these codes is normal behavior in non-blocking I/O. It simply means "try again later." It does not indicate a broken connection or a bug. + +### Summary Checklist for Robust Usage +1. Check return value: `-1` (error), `0` (timeout), `>0` (events). +2. Iterate only if return `> 0`. +3. Check `revents` (never `events`) for status. +4. Prioritize checking `POLLERR`, `POLLHUP`, and `POLLNVAL` before data flags. +5. Consume data immediately upon `POLLIN`. +6. Handle `EINTR` by retrying. + diff --git a/memo/socket.md b/memo/socket.md new file mode 100644 index 0000000..ef38369 --- /dev/null +++ b/memo/socket.md @@ -0,0 +1,32 @@ +### sockets +man 2 socket +man 7 socket +man setsockopt +man listen + +> The accept() system call is used with connection-based socket types (SOCK_STREAM, SOCK_SEQPACKET) + + +## for `int listen(int sockfd, int backlog);` can the backlog parameter be set to unlimited? + +**tl;dr: set `backlog` to `SOMAXCONN`** + +No, the `backlog` parameter in `int listen(int sockfd, int backlog);` **cannot be set to unlimited**. + +### Key Constraints + +1. **Silent Truncation**: The `backlog` argument is a **hint**, not a strict command. If you pass a value larger than the system's maximum limit, the kernel **silently truncates** it to that maximum. It does not error out, nor does it create an infinite queue. +2. **System Limit (`somaxconn`)**: On Linux, the effective maximum is defined by the kernel parameter **`net.core.somaxconn`**. + * **Default**: Historically **128**, but increased to **4096** in Linux kernel 5.4+. + * **Behavior**: If you call `listen(sockfd, 10000)` but `somaxconn` is 128, the queue size becomes 128. +3. **Negative Values**: Passing a negative value (e.g., `-1` intending "unlimited") typically results in the backlog being set to **0** (minimum) or a small default, depending on the OS implementation. It does not trigger unlimited behavior. +4. **`SOMAXCONN` Constant**: The constant `SOMAXCONN` (defined in ``) represents the maximum useful backlog value for the platform at compile time. + * **Linux**: Often defined as 4096 or 128 in headers, but the *runtime* limit is what matters (`/proc/sys/net/core/somaxconn`). + * **Windows**: `SOMAXCONN` is defined as `0x7fffffff` (max int), acting as a special flag to tell the OS to use the "maximum reasonable value," but this is still finite and managed by the OS, not truly unlimited. + +### Best Practice +To maximize the backlog: +1. Pass a large integer (e.g., `4096` or higher) to `listen()`. +2. Ensure the system's `net.core.somaxconn` is tuned accordingly (e.g., via `sysctl -w net.core.somaxconn=65535`). +3. The effective backlog will be `min(requested_backlog, somaxconn)`. + From 586a82d7645fc8388114a60ed427cd473b4ff1ee Mon Sep 17 00:00:00 2001 From: ArchCipher Date: Thu, 30 Jul 2026 18:06:20 +0200 Subject: [PATCH 002/349] clang-format, tidy, makefile, readme edit, includes and srcs folder --- .clang-format | 16 +++++ .clang-tidy | 43 ++++++++++++ .github/workflows/clang-format.yml | 34 ++++++++++ .gitignore | 6 +- Makefile | 101 ++++++++++++++++++++++++++++ README.md | 104 ++++++++++++++++++++++++++++- includes/IRC.hpp | 0 srcs/main.cpp | 44 ++++++++++++ 8 files changed, 345 insertions(+), 3 deletions(-) create mode 100644 .clang-format create mode 100644 .clang-tidy create mode 100644 .github/workflows/clang-format.yml create mode 100644 Makefile create mode 100644 includes/IRC.hpp create mode 100644 srcs/main.cpp diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..45b8729 --- /dev/null +++ b/.clang-format @@ -0,0 +1,16 @@ +# LLVM style +BasedOnStyle: LLVM +Language: Cpp +Standard: c++03 + +IndentWidth: 4 +ColumnLimit: 80 +IndentCaseLabels: true +AllowShortFunctionsOnASingleLine: Empty +AlignConsecutiveDeclarations: AcrossEmptyLinesAndComments +SeparateDefinitionBlocks: Always +ReflowComments: false + +# Include organization +SortIncludes: true +IncludeBlocks: Regroup diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..e35d41a --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,43 @@ +# 42 C++: compile with -- -std=c++98 -Wall -Wextra -Werror +HeaderFilterRegex: '.*' + +Checks: > + clang-diagnostic-* + clang-analyzer-*, + cppcoreguidelines-*, + readability-*, + performance-*, + bugprone-*, + -modernize-*, + -clang-diagnostic-misleading-indentation, + -cppcoreguidelines-non-private-member-variables-in-classes, + -cppcoreguidelines-pro-bounds-constant-array-index, + -cppcoreguidelines-macro-to-enum, + -readability-braces-around-statements, + -readability-implicit-bool-conversion, + -readability-identifier-length + +WarningsAsErrors: true + +CheckOptions: + - key: readability-identifier-naming.ClassCase + value: CamelCase + - key: readability-identifier-naming.ClassMemberCase + value: camelBack + - key: readability-identifier-naming.ClassConstantMemberCase + value: camelBack + - key: readability-identifier-naming.ClassStaticConstantCase + value: camelBack + - key: readability-identifier-naming.MemberCase + value: camelBack + - key: readability-identifier-naming.MethodCase + value: camelBack + - key: readability-identifier-naming.FunctionCase + value: camelBack + - key: readability-identifier-naming.ParameterCase + value: camelBack + - key: readability-identifier-naming.VariableCase + value: camelBack + + - key: readability-identifier-naming.IgnoreMainLikeFunctions + value: 1 diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml new file mode 100644 index 0000000..e4c8522 --- /dev/null +++ b/.github/workflows/clang-format.yml @@ -0,0 +1,34 @@ +name: clang-format + +on: + pull_request: + +jobs: + clang-format: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install clang-format + run: | + wget -qO- https://apt.llvm.org/llvm.sh | sudo bash -s -- 21 all + sudo apt-get install -y clang-format-21 + clang-format-21 --version + + - name: Run clang-format check (srcs + includes only) + run: | + set -euo pipefail + + find srcs includes \ + -type f \( -name "*.cpp" -o -name "*.hpp" \) -print0 \ + | xargs -0 clang-format-21 -i + + if git diff --quiet; then + echo "clang-format check passed (no changes)." + else + echo "clang-format check failed (files would be reformatted)." + git --no-pager diff + exit 1 + fi + diff --git a/.gitignore b/.gitignore index 0ba1c63..0591320 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# subject +en.subject.pdf + # Prerequisites *.d @@ -35,6 +38,8 @@ *.lib # Executables +ircserv +ircserv_bonus *.exe *.out *.app @@ -48,7 +53,6 @@ build-*/ CMakeFiles/ CMakeCache.txt cmake_install.cmake -Makefile install_manifest.txt compile_commands.json diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..456c46e --- /dev/null +++ b/Makefile @@ -0,0 +1,101 @@ +# ---- Library and Project names ----------------------------------------------- +NAME = ircserv +# BONUS = ircserv_bonus + +# ---- Sources & Objects ------------------------------------------------------- +SRC_DIR = srcs +BUILD_DIR = build + +SRCS_ROOT = main + +# SRCS_SERVER = Server \ +# Client \ +# Channel + +# SRCS_COMMANDS = Command \ +# Pass \ +# Nick \ +# User \ +# Quit \ +# Join \ +# Kick \ +# Invite \ +# Topic \ +# Mode + +# SRCS_UTILS = Parser \ +# Reply + +# SRCS_BONUS = DCC \ +# Bot + +SRCS = $(SRCS_ROOT) +# $(addprefix server/, $(SRCS_SERVER)) + +OBJS = $(addprefix $(BUILD_DIR)/$(SRC_DIR)/, $(addsuffix .o, $(SRCS))) + +# BONUS_OBJS = $(OBJS:.o=.bonus.o) + +# ---- Auto-generated header deps ---------------------------------------------- + +DEPS = $(OBJS:.o=.d) +# BONUS_DEPS = $(BONUS_OBJS:.o=.d) + +# ---- Compiler and Flags ------------------------------------------------------ + +CXX = c++ +CXXFLAGS = -Wall -Wextra -Werror -pedantic -std=c++98 +DFLAGS = -DDEBUG -g -fsanitize=address + +# ---- Rules ------------------------------------------------------------------- + +all: $(NAME) + +# bonus: $(BONUS) + +debug: CXXFLAGS += $(DFLAGS) +debug: $(NAME) + +# debug_bonus: CXXFLAGS += $(DFLAGS) +# debug_bonus: $(BONUS) + +# clang-format +format: + @find srcs includes -type f \( -name "*.cpp" -o -name "*.hpp" \) -print0 | xargs -0 clang-format -i + +# Usage: make tidy FILE=srcs/main.cpp or make tidy FILE="srcs/a.cpp srcs/b.cpp" +tidy: + @if [ -z "$(FILE)" ]; then echo "Usage: make tidy FILE=srcs/main.cpp"; exit 1; fi + @clang-tidy $(FILE) -- $(CXXFLAGS) -I. + +tidy-all: + @find srcs includes -type f \( -name "*.cpp" -o -name "*.hpp" \) -print0 \ + | xargs -0 clang-tidy -- $(CXXFLAGS) -I. + +$(NAME): $(OBJS) + $(CXX) $(CXXFLAGS) $(OBJS) -o $@ + +# $(BONUS): $(BONUS_OBJS) +# $(CXX) $(CXXFLAGS) $(BONUS_OBJS) -o $@ + +$(BUILD_DIR)/$(SRC_DIR)/%.o: $(SRC_DIR)/%.cpp + @mkdir -p $(@D) + $(CXX) $(CXXFLAGS) -MMD -MP -c $< -o $@ + +# $(BUILD_DIR)/$(SRC_DIR)/%.bonus.o: $(SRC_DIR)/%.cpp +# @mkdir -p $(@D) +# $(CXX) $(CXXFLAGS) -MMD -MP -c -DBONUS $< -o $@ + +# ---- Cleanup ----------------------------------------------------------------- +clean: + rm -rf $(BUILD_DIR) + +fclean: clean + rm -f $(NAME) $(BONUS) + +re: fclean all + +-include $(DEPS) +# -include $(DEPS) $(BONUS_DEPS) + +.PHONY: all bonus clean fclean re debug debug_bonus format diff --git a/README.md b/README.md index 82a3b38..07d21ed 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,102 @@ -# ft_irc -42Berlin Internet Relay Chat +*This project has been created as part of the 42 curriculum by blnunez-, kmurugan, rhaas.* + +# ft_irc - IRC server + +## Description + +### 42 Project | Internet Relay Chat in C++98 + +### Objective +Build an IRC server in C++98 that implements... + + +### Overview +... + +--- + +## Instructions + +### Requirements + +- + +### Compile & Run + +```sh +# mandatory +make && ./ircserv + +# bonus +make bonus && ./ircserv_bonus +``` + +--- + +## Clang tool commands + +```sh +# Run clang-format before PR (use clang-format version 21) +make format +``` + +```sh +# Run clang-tidy on individual file +make tidy FILE=srcs/ + +# Run clang-tidy on multiple files (filenames without spaces) +make tidy FILE="srcs/ srcs/" + +# Run clang-tidy on all .cpp/.hpp under srcs and includes +make tidy-all +``` + +NOTE: Review clang-tidy results and fix only what makes sense for this project. + +--- + +## Key Challenges & Solutions + +### Topic + +**Challenge:** + +**Solution:** + +--- + +## Resources + +### IRC protocol +[RFC 2810 - IRC: Architecture](https://rfc-editor.org/rfc/rfc2810) +[RFC 2811 - IRC: Channel Management](https://rfc-editor.org/rfc/rfc2811) +[RFC 2812 - IRC: Client Protocol](https://rfc-editor.org/rfc/rfc2812) +[RFC 2813 - IRC: Server Protocol](https://rfc-editor.org/rfc/rfc2813) +[ircgod](https://ircgod.com) +[Model IRC client protocol](https://modern.ircdocs.horse) + +### rfc reading tips +[Tips for Reading RFCs](https://www.rfc-editor.org/series/rfc-tips/) + +### Reference IRC client and server +[Server: ergo chat (formerly Oragono)](https://github.com/ergochat/ergo) +[Client: irssi](https://github.com/irssi/irssi) + +### Sockets +[Sockets & Networking](https://web.mit.edu/6.031/www/sp19/classes/23-sockets-networking/) +[All about sockets](https://docs.oracle.com/javase/tutorial/networking/sockets/index.html) + +[CS107 Sockets Programming](https://web.stanford.edu/class/archive/cs/cs107/cs107.1238/lectures/20/) +[Beej's Guide to Network Programming](https://beej.us/guide/bgnet/) - What is a + socket?, IP Addresses, structs, and Data Munging, Jumping from IPv4 to IPv6, + System Calls or Bust + +### DCC protocol +[Direct Client-to-Client protocol (DCC)](https://modern.ircdocs.horse/dcc.html) + +### Use of AI + +- +- + +--- diff --git a/includes/IRC.hpp b/includes/IRC.hpp new file mode 100644 index 0000000..e69de29 diff --git a/srcs/main.cpp b/srcs/main.cpp new file mode 100644 index 0000000..42df375 --- /dev/null +++ b/srcs/main.cpp @@ -0,0 +1,44 @@ +// # include "IRC.hpp" + +/* + NAME: + ircserv + + ARGUMENTS: + port: The listening port + password: The connection password + + DESCRIPTION: + ircserv is an IRC(Internet Relay Chat) server in C++ 98 + + FEATURES: + • Simultaneous multi-client handling over TCP/IP + • Non-blocking I/O with a single poll() multiplexing all sockets + • Authenticate with server password, set a nickname, a username, join a + channel, send and receive private messages + • Channel operators and regular users + • Operator commands: KICK, INVITE, TOPIC, MODE + • Channel modes: i, t, k, o, l + • Compatible with a chosen reference IRC client + + Bonus features / TODO: + • Handle file transfer + • A bot + + EXTERNAL FUNC(S): + • Everything in C++ 98. + • socket, close, setsockopt, getsockname, + getprotobyname, gethostbyname, getaddrinfo, + freeaddrinfo, bind, connect, listen, accept, + htons, htonl, ntohs, ntohl, inet_addr, inet_ntoa, + inet_ntop, send, recv, signal, sigaction, + sigemptyset, sigfillset, sigaddset, sigdelset, + sigismember, lseek, fstat, fcntl, poll (or + equivalent) +*/ + +int main(int argc, char **argv) { + (void)argv; + if (argc != 3) + return (1); +} From 38b125b30701b69ce60f8f4378d14ddaeabc486c Mon Sep 17 00:00:00 2001 From: Rapha Date: Tue, 4 Aug 2026 15:51:20 +0200 Subject: [PATCH 003/349] play: added playground folder and a tester prog for signal handling in c++ --- .gitignore | 1 + playground/main.cpp | 68 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 playground/main.cpp diff --git a/.gitignore b/.gitignore index 0ba1c63..548f749 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,7 @@ *.lib # Executables +playground/a.out *.exe *.out *.app diff --git a/playground/main.cpp b/playground/main.cpp new file mode 100644 index 0000000..709adf2 --- /dev/null +++ b/playground/main.cpp @@ -0,0 +1,68 @@ +#include +#include +#include +//#include +#include +#include +#include +#include + +// Global flag to indicate when to exit +// std::atomic should_exit(false); +volatile static std::sig_atomic_t should_exit = 0; + +class A { +private: +std::string s; +public: +A() {s = "Hello";} +~A() { + std::cout << "dtor: " << s << std::endl; +} +}; + +// Signal handler - NO I/O operations here! +extern "C" +void handle_sigint(int sig) { + should_exit = sig; // Only set the flag +} + +// Function to release resources +void cleanup_resources(A *pa) { + std::cout << "[Cleanup] Releasing resources...\n"; + // Close files, free memory, close database connections, etc. + delete pa; + std::cout << "[Cleanup] Resources released successfully.\n"; +} + +const std::pairp(SIGINT, "SIGINT"); +static const std::map sigs(&p, &p+1); + +int main() { + // Install the signal handler for SIGINT + if (std::signal(SIGINT, handle_sigint) == SIG_ERR) { + std::cerr << "std::signal failed\n"; + return 1; + } + + std::cout << "Signal handler installed. Press Ctrl+C to exit gracefully.\n"; + + A *pa = new(A); + A b; + + // Main program loop + while (!should_exit) { + std::cout << "Running... (PID: " << getpid() << ")\n"; + sleep(1); // Simulate work + } + + // Release resources before exiting + std::cout << "\n[Signal Handler] Caught signal: " << + /* sigs.at(should_exit) */ + strsignal(should_exit) + << ". Shutting down gracefully...\n"; + cleanup_resources(pa); + + std::cout << "Program terminated cleanly.\n"; + return 0; +} From a394f56a9c8d36f58329ba9f9180897f2f725905 Mon Sep 17 00:00:00 2001 From: Rapha Date: Tue, 4 Aug 2026 16:10:28 +0200 Subject: [PATCH 004/349] doc: add new link to ref repo in memo --- memo/memo.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/memo/memo.md b/memo/memo.md index f5a033a..8d6e31a 100644 --- a/memo/memo.md +++ b/memo/memo.md @@ -77,4 +77,5 @@ https://wormav.github.io/42_eval/Cursus/Ft_irc/index.html + Irssi: A terminal-based client supporting multi-server, UTF-8, TLS, and SASL, often used for always-on connectivity. # Reference implementation -https://github.com/42YerevanProjects/ft_irc/blob/master/src/network/Server.cpp#L263 \ No newline at end of file +https://github.com/42YerevanProjects/ft_irc/blob/master/src/network/Server.cpp#L263 +https://deepwiki.com/42YerevanProjects/ft_irc/4-protocol-implementation \ No newline at end of file From 618e338703a46b6a7f03c475f6674f6238ad7580 Mon Sep 17 00:00:00 2001 From: Rapha Date: Tue, 4 Aug 2026 17:22:16 +0200 Subject: [PATCH 005/349] feat: server poll loop mockup incl. signal handling --- Makefile | 10 ++++++---- includes/IRC.hpp | 3 +++ includes/IRCServer.hpp | 25 +++++++++++++++++++++++++ srcs/main.cpp | 8 +++++++- srcs/server/IRCServer.cpp | 19 +++++++++++++++++++ 5 files changed, 60 insertions(+), 5 deletions(-) create mode 100644 includes/IRCServer.hpp create mode 100644 srcs/server/IRCServer.cpp diff --git a/Makefile b/Makefile index 456c46e..17f3ffd 100644 --- a/Makefile +++ b/Makefile @@ -4,11 +4,12 @@ NAME = ircserv # ---- Sources & Objects ------------------------------------------------------- SRC_DIR = srcs +INC_DIR = includes BUILD_DIR = build SRCS_ROOT = main -# SRCS_SERVER = Server \ +SRCS_SERVER = IRCServer # Client \ # Channel @@ -29,8 +30,8 @@ SRCS_ROOT = main # SRCS_BONUS = DCC \ # Bot -SRCS = $(SRCS_ROOT) -# $(addprefix server/, $(SRCS_SERVER)) +SRCS = $(SRCS_ROOT) \ + $(addprefix server/, $(SRCS_SERVER)) OBJS = $(addprefix $(BUILD_DIR)/$(SRC_DIR)/, $(addsuffix .o, $(SRCS))) @@ -45,6 +46,7 @@ DEPS = $(OBJS:.o=.d) CXX = c++ CXXFLAGS = -Wall -Wextra -Werror -pedantic -std=c++98 +CPPFLAGS = -I$(INC_DIR) DFLAGS = -DDEBUG -g -fsanitize=address # ---- Rules ------------------------------------------------------------------- @@ -80,7 +82,7 @@ $(NAME): $(OBJS) $(BUILD_DIR)/$(SRC_DIR)/%.o: $(SRC_DIR)/%.cpp @mkdir -p $(@D) - $(CXX) $(CXXFLAGS) -MMD -MP -c $< -o $@ + $(CXX) $(CPPFLAGS) $(CXXFLAGS) -MMD -MP -c $< -o $@ # $(BUILD_DIR)/$(SRC_DIR)/%.bonus.o: $(SRC_DIR)/%.cpp # @mkdir -p $(@D) diff --git a/includes/IRC.hpp b/includes/IRC.hpp index e69de29..1e28153 100644 --- a/includes/IRC.hpp +++ b/includes/IRC.hpp @@ -0,0 +1,3 @@ +#include "IRCServer.hpp" + +#include \ No newline at end of file diff --git a/includes/IRCServer.hpp b/includes/IRCServer.hpp new file mode 100644 index 0000000..29914d3 --- /dev/null +++ b/includes/IRCServer.hpp @@ -0,0 +1,25 @@ +#include +#include +#include + +class IRCServer { + private: + volatile static std::sig_atomic_t _stopServerLoop; + static void _handle_sigint(int signal); + + public: + IRCServer() { + _stopServerLoop = 0; + if (std::signal(SIGINT, _handle_sigint) == SIG_ERR) { + throw std::runtime_error("std::signal failed"); + } + } + + ~IRCServer() { + if (std::signal(SIGINT, SIG_DFL) == SIG_ERR) { + // throw std::runtime_error("std::signal failed"); + } + } + + void run(); +}; diff --git a/srcs/main.cpp b/srcs/main.cpp index 42df375..f43c8fd 100644 --- a/srcs/main.cpp +++ b/srcs/main.cpp @@ -1,4 +1,4 @@ -// # include "IRC.hpp" +#include "IRC.hpp" /* NAME: @@ -41,4 +41,10 @@ int main(int argc, char **argv) { (void)argv; if (argc != 3) return (1); + + try { + IRCServer srv; + srv.run(); + } catch (std::exception const &e) { + } } diff --git a/srcs/server/IRCServer.cpp b/srcs/server/IRCServer.cpp new file mode 100644 index 0000000..1343fa5 --- /dev/null +++ b/srcs/server/IRCServer.cpp @@ -0,0 +1,19 @@ +#include "IRCServer.hpp" + +#include +#include +#include + +volatile std::sig_atomic_t IRCServer::_stopServerLoop = 0; + +void IRCServer::_handle_sigint(int signal) { + _stopServerLoop = signal; +} + +void IRCServer::run() { + while (!_stopServerLoop) { + std::cout << "running..." << std::endl; + sleep(1); + } + std::cout << "caught signal: " << _stopServerLoop << "." << std::endl; +} From 3694ab2a3091bff7e0db134fdef5859a03b40d2d Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 5 Aug 2026 13:03:49 +0200 Subject: [PATCH 006/349] rfa: replace signal with sigaction and put signal handling code in its own functions --- includes/IRCServer.hpp | 23 ++++++++++++++--------- srcs/main.cpp | 1 + srcs/server/IRCServer.cpp | 37 ++++++++++++++++++++++++++++++++----- 3 files changed, 47 insertions(+), 14 deletions(-) diff --git a/includes/IRCServer.hpp b/includes/IRCServer.hpp index 29914d3..7f9ac5d 100644 --- a/includes/IRCServer.hpp +++ b/includes/IRCServer.hpp @@ -1,24 +1,29 @@ #include #include #include +#include class IRCServer { + public: + volatile static std::sig_atomic_t g_stopServerLoop; + static std::vector g_signals; + static void g_handleSignal(int signal); + // static void _initSignalHandling(); + // static void _resetSignalHandling(); private: - volatile static std::sig_atomic_t _stopServerLoop; - static void _handle_sigint(int signal); + void _initSignalHandling(); + void _resetSignalHandling(); + + IRCServer(IRCServer const &other); + IRCServer &operator=(IRCServer const &other); public: IRCServer() { - _stopServerLoop = 0; - if (std::signal(SIGINT, _handle_sigint) == SIG_ERR) { - throw std::runtime_error("std::signal failed"); - } + _initSignalHandling(); } ~IRCServer() { - if (std::signal(SIGINT, SIG_DFL) == SIG_ERR) { - // throw std::runtime_error("std::signal failed"); - } + _resetSignalHandling(); } void run(); diff --git a/srcs/main.cpp b/srcs/main.cpp index f43c8fd..115520a 100644 --- a/srcs/main.cpp +++ b/srcs/main.cpp @@ -47,4 +47,5 @@ int main(int argc, char **argv) { srv.run(); } catch (std::exception const &e) { } + std::cout << "Clean exit." << std::endl; } diff --git a/srcs/server/IRCServer.cpp b/srcs/server/IRCServer.cpp index 1343fa5..68ae9f4 100644 --- a/srcs/server/IRCServer.cpp +++ b/srcs/server/IRCServer.cpp @@ -4,16 +4,43 @@ #include #include -volatile std::sig_atomic_t IRCServer::_stopServerLoop = 0; +volatile std::sig_atomic_t IRCServer::g_stopServerLoop = 0; +std::vector IRCServer::g_signals; -void IRCServer::_handle_sigint(int signal) { - _stopServerLoop = signal; +void IRCServer::g_handleSignal(int signal) { + if (signal == SIGINT || signal == SIGTERM) + g_stopServerLoop = signal; +} + +void IRCServer::_initSignalHandling() { + g_stopServerLoop = 0; + g_signals.push_back(SIGINT); + g_signals.push_back(SIGTERM); + + struct sigaction sigact = {}; + sigact.sa_handler = g_handleSignal; + sigemptyset(&sigact.sa_mask); + sigact.sa_flags = 0; + for (size_t i = 0; i < g_signals.size(); ++i) + if (sigaction(g_signals.at(i), &sigact, NULL) != 0) + throw std::runtime_error("std::signal failed"); +} + +void IRCServer::_resetSignalHandling() { + + struct sigaction sigact = {}; + sigact.sa_handler = SIG_DFL; + sigemptyset(&sigact.sa_mask); + sigact.sa_flags = 0; + + for (size_t i = 0; i < g_signals.size(); ++i) + sigaction(g_signals.at(i), &sigact, NULL); } void IRCServer::run() { - while (!_stopServerLoop) { + while (!g_stopServerLoop) { std::cout << "running..." << std::endl; sleep(1); } - std::cout << "caught signal: " << _stopServerLoop << "." << std::endl; + std::cout << "caught signal: " << g_stopServerLoop << "." << std::endl; } From 9ab2c7272f0677521fe0c9a772917d7b509e68db Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 5 Aug 2026 13:04:51 +0200 Subject: [PATCH 007/349] check: add rules to clang-tidy for private and static class members --- .clang-tidy | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index e35d41a..3a38405 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -15,7 +15,8 @@ Checks: > -cppcoreguidelines-macro-to-enum, -readability-braces-around-statements, -readability-implicit-bool-conversion, - -readability-identifier-length + -readability-identifier-length, + -performance-avoid-endl, WarningsAsErrors: true @@ -38,6 +39,16 @@ CheckOptions: value: camelBack - key: readability-identifier-naming.VariableCase value: camelBack - + - key: readability-identifier-naming.ClassMemberPrefix + value: g_ + - key: readability-identifier-naming.ClassMethodPrefix + value: g_ + - key: readability-identifier-naming.PrivateMemberPrefix + value: _ + - key: readability-identifier-naming.PrivateMethodCase + value: camelBack + - key: readability-identifier-naming.PrivateMethodPrefix + value: _ + - key: readability-identifier-naming.IgnoreMainLikeFunctions value: 1 From 0403f662a046c022f9ac48d8a326c0dcbfa1aa0d Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 5 Aug 2026 13:05:25 +0200 Subject: [PATCH 008/349] build: Makefile update clang-tidy targets with INC_DIR --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 17f3ffd..1b04718 100644 --- a/Makefile +++ b/Makefile @@ -68,11 +68,11 @@ format: # Usage: make tidy FILE=srcs/main.cpp or make tidy FILE="srcs/a.cpp srcs/b.cpp" tidy: @if [ -z "$(FILE)" ]; then echo "Usage: make tidy FILE=srcs/main.cpp"; exit 1; fi - @clang-tidy $(FILE) -- $(CXXFLAGS) -I. + @clang-tidy $(FILE) -- $(CXXFLAGS) -I$(INC_DIR) tidy-all: @find srcs includes -type f \( -name "*.cpp" -o -name "*.hpp" \) -print0 \ - | xargs -0 clang-tidy -- $(CXXFLAGS) -I. + | xargs -0 clang-tidy -- $(CXXFLAGS) -I$(INC_DIR) $(NAME): $(OBJS) $(CXX) $(CXXFLAGS) $(OBJS) -o $@ From 3c8f0c6be66bf0248fa903a4c03ea7738a593c66 Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 5 Aug 2026 13:24:10 +0200 Subject: [PATCH 009/349] rfa: cleanup code deduplication and more specialised functions --- includes/IRCServer.hpp | 14 ++++++-------- srcs/server/IRCServer.cpp | 31 ++++++++++++++++--------------- 2 files changed, 22 insertions(+), 23 deletions(-) diff --git a/includes/IRCServer.hpp b/includes/IRCServer.hpp index 7f9ac5d..c5fb002 100644 --- a/includes/IRCServer.hpp +++ b/includes/IRCServer.hpp @@ -4,15 +4,13 @@ #include class IRCServer { - public: - volatile static std::sig_atomic_t g_stopServerLoop; - static std::vector g_signals; - static void g_handleSignal(int signal); - // static void _initSignalHandling(); - // static void _resetSignalHandling(); private: - void _initSignalHandling(); - void _resetSignalHandling(); + volatile static std::sig_atomic_t g_stopServerLoop; + static void g_handleStopSignals(int signal); + std::vector _stopSignals; + void _initSignalHandling(); + void _resetSignalHandling(); + bool _setStopSignals(sighandler_t handler); IRCServer(IRCServer const &other); IRCServer &operator=(IRCServer const &other); diff --git a/srcs/server/IRCServer.cpp b/srcs/server/IRCServer.cpp index 68ae9f4..cd2754b 100644 --- a/srcs/server/IRCServer.cpp +++ b/srcs/server/IRCServer.cpp @@ -5,36 +5,37 @@ #include volatile std::sig_atomic_t IRCServer::g_stopServerLoop = 0; -std::vector IRCServer::g_signals; -void IRCServer::g_handleSignal(int signal) { - if (signal == SIGINT || signal == SIGTERM) +void IRCServer::g_handleStopSignals(int signal) { g_stopServerLoop = signal; } void IRCServer::_initSignalHandling() { g_stopServerLoop = 0; - g_signals.push_back(SIGINT); - g_signals.push_back(SIGTERM); + _stopSignals.push_back(SIGINT); + _stopSignals.push_back(SIGTERM); - struct sigaction sigact = {}; - sigact.sa_handler = g_handleSignal; - sigemptyset(&sigact.sa_mask); - sigact.sa_flags = 0; - for (size_t i = 0; i < g_signals.size(); ++i) - if (sigaction(g_signals.at(i), &sigact, NULL) != 0) - throw std::runtime_error("std::signal failed"); + if (!_setStopSignals(g_handleStopSignals)) + throw std::runtime_error("std::signal failed"); } void IRCServer::_resetSignalHandling() { + _setStopSignals(SIG_DFL); +} + +bool IRCServer::_setStopSignals(sighandler_t handler) { + bool success = true; struct sigaction sigact = {}; - sigact.sa_handler = SIG_DFL; + sigact.sa_handler = handler; sigemptyset(&sigact.sa_mask); sigact.sa_flags = 0; - for (size_t i = 0; i < g_signals.size(); ++i) - sigaction(g_signals.at(i), &sigact, NULL); + for (size_t i = 0; i < _stopSignals.size(); ++i) + if (sigaction(_stopSignals.at(i), &sigact, NULL) != 0) + success = false; + + return success; } void IRCServer::run() { From b2d5dae8ea5d38a84c5460185eb513d78e444d40 Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 5 Aug 2026 16:25:56 +0200 Subject: [PATCH 010/349] feat: add SIGQUIT --- srcs/server/IRCServer.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/srcs/server/IRCServer.cpp b/srcs/server/IRCServer.cpp index cd2754b..0b9f6f3 100644 --- a/srcs/server/IRCServer.cpp +++ b/srcs/server/IRCServer.cpp @@ -13,6 +13,7 @@ void IRCServer::g_handleStopSignals(int signal) { void IRCServer::_initSignalHandling() { g_stopServerLoop = 0; _stopSignals.push_back(SIGINT); + _stopSignals.push_back(SIGQUIT); _stopSignals.push_back(SIGTERM); if (!_setStopSignals(g_handleStopSignals)) From 59769b0107bd0a91898cad85631b723154cc8109 Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 6 Aug 2026 14:22:22 +0200 Subject: [PATCH 011/349] feat: dirty mockup for testing basic functionality of server loop --- includes/IRCServer.hpp | 16 ++++-- srcs/server/IRCServer.cpp | 118 +++++++++++++++++++++++++++++++++++++- 2 files changed, 125 insertions(+), 9 deletions(-) diff --git a/includes/IRCServer.hpp b/includes/IRCServer.hpp index c5fb002..86cc0f4 100644 --- a/includes/IRCServer.hpp +++ b/includes/IRCServer.hpp @@ -1,6 +1,9 @@ #include #include #include +// #include +// #include +// #include #include class IRCServer { @@ -12,17 +15,18 @@ class IRCServer { void _resetSignalHandling(); bool _setStopSignals(sighandler_t handler); + std::vector _pollfds; + IRCServer(IRCServer const &other); IRCServer &operator=(IRCServer const &other); + int _createDummyServerSocket(); + void _acceptClient(); + public: - IRCServer() { - _initSignalHandling(); - } + IRCServer(); - ~IRCServer() { - _resetSignalHandling(); - } + ~IRCServer(); void run(); }; diff --git a/srcs/server/IRCServer.cpp b/srcs/server/IRCServer.cpp index 0b9f6f3..41fd077 100644 --- a/srcs/server/IRCServer.cpp +++ b/srcs/server/IRCServer.cpp @@ -1,19 +1,21 @@ #include "IRCServer.hpp" +#include #include #include #include +#include volatile std::sig_atomic_t IRCServer::g_stopServerLoop = 0; void IRCServer::g_handleStopSignals(int signal) { - g_stopServerLoop = signal; + g_stopServerLoop = signal; } void IRCServer::_initSignalHandling() { g_stopServerLoop = 0; _stopSignals.push_back(SIGINT); - _stopSignals.push_back(SIGQUIT); + _stopSignals.push_back(SIGQUIT); _stopSignals.push_back(SIGTERM); if (!_setStopSignals(g_handleStopSignals)) @@ -39,10 +41,120 @@ bool IRCServer::_setStopSignals(sighandler_t handler) { return success; } +IRCServer::IRCServer() { + _initSignalHandling(); +} + +IRCServer::~IRCServer() { + _resetSignalHandling(); +} + +int IRCServer::_createDummyServerSocket() { + // Create a TCP socket + int serverSocket = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0); + if (serverSocket == -1) { + return -1; + } + + // Bind and listen + struct sockaddr_in serverAddr; + serverAddr.sin_family = AF_INET; + serverAddr.sin_port = htons(8082); + serverAddr.sin_addr.s_addr = INADDR_ANY; + + if (bind(serverSocket, (struct sockaddr *)&serverAddr, + sizeof(serverAddr)) == -1) { + return -1; + } + + listen(serverSocket, 100); + + _pollfds.resize(1); + _pollfds[0].fd = serverSocket; + _pollfds[0].events = POLLIN; // Monitor for incoming connections + + return serverSocket; +} + +void IRCServer::_acceptClient() { + int clientFd; + struct sockaddr_in clientAI; + struct pollfd clientPoll; + socklen_t AISize = sizeof(clientAI); + + // clientFd = accept(_pollfds.front().fd, reinterpret_cast(&clientAI), &AISize); + clientFd = accept4(_pollfds.front().fd, + reinterpret_cast(&clientAI), &AISize, + SOCK_NONBLOCK); + + if (clientFd == -1) { + std::cerr << "[ERROR] Accepting failed." << std::endl; + return; + } + clientPoll.fd = clientFd; + clientPoll.events = POLLIN; + clientPoll.revents = 0; + _pollfds.push_back(clientPoll); + std::cout << "[SERVER] Client connected [FD: " << clientFd << "]" + << std::endl; +} + void IRCServer::run() { + _createDummyServerSocket(); + + // Event loop while (!g_stopServerLoop) { std::cout << "running..." << std::endl; - sleep(1); + sleep(5); + + // poll() timeout in milliseconds (-1 = infinite) + int pollResult = poll(_pollfds.data(), _pollfds.size(), 0); + + if (pollResult == -1) { + // print nice error message and exit the loop + // or throw, but then have ressource cleanup after catch + break; + } else if (pollResult == 0) { + // no data ready to read + continue; + } + + // Check if server socket has activity + struct pollfd &serverPollFds = _pollfds[0]; + if (serverPollFds.revents & POLLERR) { + std::cout << "Socket error\n"; + break; + } else if (serverPollFds.revents & POLLIN) { + _acceptClient(); + } + + // Clients + for (size_t idx = 1; idx < _pollfds.size(); ++idx) { + pollfd &pfd = _pollfds.at(idx); + char buffer[1024] = ""; + int bytesRead = read(pfd.fd, buffer, 1024); + if (bytesRead < 0) { + if (!(errno == EAGAIN || errno == EWOULDBLOCK)) + std::cerr << "[ERROR] Recv failed on FD " << pfd.fd + << std::endl; + } else if (bytesRead == 0) { + // removeClient(fd, pollIndex, "Remote host closed the connection"); + close(pfd.fd); + _pollfds.erase(_pollfds.begin() + idx); + std::cout << "Closed client #" << idx << " ("<<_pollfds.size() -1 << " remaining)"<< std::endl; + } else { + std::cout << "Buffer read from client " << idx << ": \n" + << std::string(buffer) << std::endl; + } + } + } + close(_pollfds[0].fd); + for (size_t idx = 1; idx < _pollfds.size(); ++idx) { + pollfd &pfd = _pollfds.at(idx); + close(pfd.fd); + _pollfds.erase(_pollfds.begin() + idx); + std::cout << "Closed client #" << idx << " ("<<_pollfds.size() - 1 << " remaining)"<< std::endl; } + std::cout << "caught signal: " << g_stopServerLoop << "." << std::endl; } From fa369f2a16e88d915154069f633f8915b0781453 Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 6 Aug 2026 15:10:43 +0200 Subject: [PATCH 012/349] feat: properly shut down clients on client disconnect and server shutdown --- srcs/server/IRCServer.cpp | 60 +++++++++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 21 deletions(-) diff --git a/srcs/server/IRCServer.cpp b/srcs/server/IRCServer.cpp index 41fd077..0743c91 100644 --- a/srcs/server/IRCServer.cpp +++ b/srcs/server/IRCServer.cpp @@ -114,10 +114,10 @@ void IRCServer::run() { // print nice error message and exit the loop // or throw, but then have ressource cleanup after catch break; - } else if (pollResult == 0) { + } /* else if (pollResult == 0) { // no data ready to read continue; - } + } */ // Check if server socket has activity struct pollfd &serverPollFds = _pollfds[0]; @@ -128,33 +128,51 @@ void IRCServer::run() { _acceptClient(); } - // Clients + // Clients for (size_t idx = 1; idx < _pollfds.size(); ++idx) { pollfd &pfd = _pollfds.at(idx); - char buffer[1024] = ""; - int bytesRead = read(pfd.fd, buffer, 1024); - if (bytesRead < 0) { - if (!(errno == EAGAIN || errno == EWOULDBLOCK)) - std::cerr << "[ERROR] Recv failed on FD " << pfd.fd - << std::endl; - } else if (bytesRead == 0) { - // removeClient(fd, pollIndex, "Remote host closed the connection"); - close(pfd.fd); - _pollfds.erase(_pollfds.begin() + idx); - std::cout << "Closed client #" << idx << " ("<<_pollfds.size() -1 << " remaining)"<< std::endl; + std::cout << "Idx: " << idx << std::endl; + if (pfd.revents & POLLERR) { + std::cerr << "[ERROR] Recv failed on FD " << pfd.fd + << std::endl; + // } else if (pfd.revents & POLLHUP) { + // // removeClient(fd, pollIndex, "Remote host closed the connection"); + // close(pfd.fd); + // _pollfds.erase(_pollfds.begin() + idx); + // std::cout << "Closed client #" << idx << " (" + // << _pollfds.size() - 1 << " remaining)" << std::endl; } else { - std::cout << "Buffer read from client " << idx << ": \n" - << std::string(buffer) << std::endl; + std::cout << " revents: " << std::hex << pfd.revents + << std::endl; + } + + if (pfd.revents & POLLIN) { + char buffer[1024] = ""; + int bytesRead = read(pfd.fd, buffer, 1024); + if (bytesRead > 0) { + std::cout << "Buffer read from client " << idx << ": \n" + << std::string(buffer) << std::endl; + } + if (bytesRead == 0) { + // removeClient(fd, pollIndex, "Remote host closed the connection"); + close(pfd.fd); + _pollfds.erase(_pollfds.begin() + idx); + std::cout << "Closed client #" << idx << " (" + << _pollfds.size() - 1 << " remaining)" + << std::endl; + } } } } - close(_pollfds[0].fd); - for (size_t idx = 1; idx < _pollfds.size(); ++idx) { + for (size_t idx = 0; idx < _pollfds.size(); ++idx) { pollfd &pfd = _pollfds.at(idx); close(pfd.fd); - _pollfds.erase(_pollfds.begin() + idx); - std::cout << "Closed client #" << idx << " ("<<_pollfds.size() - 1 << " remaining)"<< std::endl; + if (idx == 0) + std::cout << "Closed server"; + else + std::cout << "Closed client #" << idx; + std::cout << " (" << _pollfds.size() - idx - 1 << " clients remaining)" + << std::endl; } - std::cout << "caught signal: " << g_stopServerLoop << "." << std::endl; } From cf4b4d042c074317add56c7cc1cefbc5921703dd Mon Sep 17 00:00:00 2001 From: ArchCipher Date: Thu, 6 Aug 2026 16:29:11 +0200 Subject: [PATCH 013/349] fix: add inc_dir + .phony tidy, tidy-all --- Makefile | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index 456c46e..42797ef 100644 --- a/Makefile +++ b/Makefile @@ -4,6 +4,7 @@ NAME = ircserv # ---- Sources & Objects ------------------------------------------------------- SRC_DIR = srcs +INC_DIR = includes BUILD_DIR = build SRCS_ROOT = main @@ -21,7 +22,10 @@ SRCS_ROOT = main # Kick \ # Invite \ # Topic \ -# Mode +# Mode \ +# Privmsg \ +# Notice \ +# Ping # SRCS_UTILS = Parser \ # Reply @@ -45,6 +49,7 @@ DEPS = $(OBJS:.o=.d) CXX = c++ CXXFLAGS = -Wall -Wextra -Werror -pedantic -std=c++98 +IFLAGS = -I$(INC_DIR) DFLAGS = -DDEBUG -g -fsanitize=address # ---- Rules ------------------------------------------------------------------- @@ -61,16 +66,17 @@ debug: $(NAME) # clang-format format: - @find srcs includes -type f \( -name "*.cpp" -o -name "*.hpp" \) -print0 | xargs -0 clang-format -i + @find srcs includes -type f \( -name "*.cpp" -o -name "*.hpp" \) -print0 \ + | xargs -0 clang-format -i # Usage: make tidy FILE=srcs/main.cpp or make tidy FILE="srcs/a.cpp srcs/b.cpp" tidy: @if [ -z "$(FILE)" ]; then echo "Usage: make tidy FILE=srcs/main.cpp"; exit 1; fi - @clang-tidy $(FILE) -- $(CXXFLAGS) -I. + @clang-tidy $(FILE) -- $(CXXFLAGS) $(IFLAGS) tidy-all: @find srcs includes -type f \( -name "*.cpp" -o -name "*.hpp" \) -print0 \ - | xargs -0 clang-tidy -- $(CXXFLAGS) -I. + | xargs -0 clang-tidy -- $(CXXFLAGS) $(IFLAGS) $(NAME): $(OBJS) $(CXX) $(CXXFLAGS) $(OBJS) -o $@ @@ -80,11 +86,11 @@ $(NAME): $(OBJS) $(BUILD_DIR)/$(SRC_DIR)/%.o: $(SRC_DIR)/%.cpp @mkdir -p $(@D) - $(CXX) $(CXXFLAGS) -MMD -MP -c $< -o $@ + $(CXX) $(CXXFLAGS) $(IFLAGS) -MMD -MP -c $< -o $@ # $(BUILD_DIR)/$(SRC_DIR)/%.bonus.o: $(SRC_DIR)/%.cpp # @mkdir -p $(@D) -# $(CXX) $(CXXFLAGS) -MMD -MP -c -DBONUS $< -o $@ +# $(CXX) $(CXXFLAGS) $(IFLAGS) -MMD -MP -c -DBONUS $< -o $@ # ---- Cleanup ----------------------------------------------------------------- clean: @@ -98,4 +104,4 @@ re: fclean all -include $(DEPS) # -include $(DEPS) $(BONUS_DEPS) -.PHONY: all bonus clean fclean re debug debug_bonus format +.PHONY: all bonus clean fclean re debug debug_bonus format tidy tidy-all From 05e6a27dee3c8e79f2567a5130c879f125d0c77c Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 6 Aug 2026 16:29:43 +0200 Subject: [PATCH 014/349] rfa: do (partial) code cleanup and use more dedicated functions --- includes/IRC.hpp | 5 +- includes/{ => server}/IRCServer.hpp | 6 +- srcs/server/IRCServer.cpp | 108 ++++++++++++++-------------- 3 files changed, 57 insertions(+), 62 deletions(-) rename includes/{ => server}/IRCServer.hpp (85%) diff --git a/includes/IRC.hpp b/includes/IRC.hpp index 1e28153..1ea901b 100644 --- a/includes/IRC.hpp +++ b/includes/IRC.hpp @@ -1,3 +1,4 @@ -#include "IRCServer.hpp" +#include "server/IRCServer.hpp" -#include \ No newline at end of file +#include +#include \ No newline at end of file diff --git a/includes/IRCServer.hpp b/includes/server/IRCServer.hpp similarity index 85% rename from includes/IRCServer.hpp rename to includes/server/IRCServer.hpp index 86cc0f4..e0ec330 100644 --- a/includes/IRCServer.hpp +++ b/includes/server/IRCServer.hpp @@ -1,9 +1,4 @@ #include -#include -#include -// #include -// #include -// #include #include class IRCServer { @@ -22,6 +17,7 @@ class IRCServer { int _createDummyServerSocket(); void _acceptClient(); + void _disconnectClient(int const idx); public: IRCServer(); diff --git a/srcs/server/IRCServer.cpp b/srcs/server/IRCServer.cpp index 0743c91..60fce74 100644 --- a/srcs/server/IRCServer.cpp +++ b/srcs/server/IRCServer.cpp @@ -1,4 +1,4 @@ -#include "IRCServer.hpp" +#include "server/IRCServer.hpp" #include #include @@ -55,20 +55,17 @@ int IRCServer::_createDummyServerSocket() { if (serverSocket == -1) { return -1; } - - // Bind and listen + // Bind struct sockaddr_in serverAddr; serverAddr.sin_family = AF_INET; serverAddr.sin_port = htons(8082); serverAddr.sin_addr.s_addr = INADDR_ANY; - - if (bind(serverSocket, (struct sockaddr *)&serverAddr, + if (bind(serverSocket, reinterpret_cast(&serverAddr), sizeof(serverAddr)) == -1) { return -1; } - + // listen listen(serverSocket, 100); - _pollfds.resize(1); _pollfds[0].fd = serverSocket; _pollfds[0].events = POLLIN; // Monitor for incoming connections @@ -78,25 +75,39 @@ int IRCServer::_createDummyServerSocket() { void IRCServer::_acceptClient() { int clientFd; + struct pollfd clientPollfd; struct sockaddr_in clientAI; - struct pollfd clientPoll; socklen_t AISize = sizeof(clientAI); - // clientFd = accept(_pollfds.front().fd, reinterpret_cast(&clientAI), &AISize); + // accept4, so we can directly set SOCK_NONBLOCK + // the subject allows fcntl only on macOS clientFd = accept4(_pollfds.front().fd, reinterpret_cast(&clientAI), &AISize, SOCK_NONBLOCK); if (clientFd == -1) { - std::cerr << "[ERROR] Accepting failed." << std::endl; + std::cerr << "Accept failed." << std::endl; return; } - clientPoll.fd = clientFd; - clientPoll.events = POLLIN; - clientPoll.revents = 0; - _pollfds.push_back(clientPoll); - std::cout << "[SERVER] Client connected [FD: " << clientFd << "]" - << std::endl; + clientPollfd.fd = clientFd; + clientPollfd.events = POLLIN; + clientPollfd.revents = 0; + _pollfds.push_back(clientPollfd); + std::cout << "Client connected, fd: " << clientFd << std::endl; +} + +void IRCServer::_disconnectClient(int const idx) { + pollfd &pfd = _pollfds.at(idx); + + if (idx == 0) { + if (_pollfds.size() != 1) + std::cout << "Close clients before closing server!" << std::endl; + std::cout << "Closing server" << std::endl; + } else + std::cout << "Closing client #" << idx << " (" << _pollfds.size() - 2 + << " clients remaining)" << std::endl; + close(pfd.fd); + _pollfds.erase(_pollfds.begin() + idx); } void IRCServer::run() { @@ -104,25 +115,20 @@ void IRCServer::run() { // Event loop while (!g_stopServerLoop) { - std::cout << "running..." << std::endl; - sleep(5); + // std::cout << "running..." << std::endl; + sleep(1); - // poll() timeout in milliseconds (-1 = infinite) int pollResult = poll(_pollfds.data(), _pollfds.size(), 0); - if (pollResult == -1) { // print nice error message and exit the loop // or throw, but then have ressource cleanup after catch break; - } /* else if (pollResult == 0) { - // no data ready to read - continue; - } */ + } // Check if server socket has activity struct pollfd &serverPollFds = _pollfds[0]; if (serverPollFds.revents & POLLERR) { - std::cout << "Socket error\n"; + std::cerr << "Server socket error\n"; break; } else if (serverPollFds.revents & POLLIN) { _acceptClient(); @@ -131,48 +137,40 @@ void IRCServer::run() { // Clients for (size_t idx = 1; idx < _pollfds.size(); ++idx) { pollfd &pfd = _pollfds.at(idx); - std::cout << "Idx: " << idx << std::endl; + // std::cout << "Idx: " << idx << std::endl; + // std::cout << " revents: " << std::hex << pfd.revents << std::endl; + if (pfd.revents & POLLERR) { - std::cerr << "[ERROR] Recv failed on FD " << pfd.fd + std::cerr << "Client socket error on fd: " << pfd.fd << std::endl; - // } else if (pfd.revents & POLLHUP) { - // // removeClient(fd, pollIndex, "Remote host closed the connection"); - // close(pfd.fd); - // _pollfds.erase(_pollfds.begin() + idx); - // std::cout << "Closed client #" << idx << " (" - // << _pollfds.size() - 1 << " remaining)" << std::endl; - } else { - std::cout << " revents: " << std::hex << pfd.revents - << std::endl; - } - - if (pfd.revents & POLLIN) { + // Do we need to disconnect directly in this case? Maybe not. + _disconnectClient(idx); + } else if (pfd.revents & POLLIN) { char buffer[1024] = ""; int bytesRead = read(pfd.fd, buffer, 1024); if (bytesRead > 0) { std::cout << "Buffer read from client " << idx << ": \n" << std::string(buffer) << std::endl; } - if (bytesRead == 0) { - // removeClient(fd, pollIndex, "Remote host closed the connection"); - close(pfd.fd); - _pollfds.erase(_pollfds.begin() + idx); - std::cout << "Closed client #" << idx << " (" - << _pollfds.size() - 1 << " remaining)" - << std::endl; + if (bytesRead <= 0) { + // this means, the client closed the connection + // POLLHUP isn't useful, because + + // When you kill netcat with Ctrl+C: + // Netcat's OS kernel sent a FIN packet to your server + // Your server's poll() detected this with POLLIN + // Your read() returned 0 (zero bytes) = "I got the FIN, connection closed" + // FIN is the TCP mechanism for orderly shutdown. + // That's why POLLIN + read() return 0—the FIN, + // and the zero read is how the kernel tells you "the peer closed." + _disconnectClient(idx); } } } } - for (size_t idx = 0; idx < _pollfds.size(); ++idx) { - pollfd &pfd = _pollfds.at(idx); - close(pfd.fd); - if (idx == 0) - std::cout << "Closed server"; - else - std::cout << "Closed client #" << idx; - std::cout << " (" << _pollfds.size() - idx - 1 << " clients remaining)" - << std::endl; + while (!_pollfds.empty()) { + // disconnecting clients first + _disconnectClient(_pollfds.size() - 1); } std::cout << "caught signal: " << g_stopServerLoop << "." << std::endl; } From dfbc4acc8bfdfac99df0a7725fc8c9f0ed39d14f Mon Sep 17 00:00:00 2001 From: Rapha Date: Fri, 7 Aug 2026 14:28:31 +0200 Subject: [PATCH 015/349] rfa: cleanup for PR --- includes/server/IRCServer.hpp | 3 +- srcs/server/IRCServer.cpp | 82 +++++++++++++++++++++++++++++------ 2 files changed, 70 insertions(+), 15 deletions(-) diff --git a/includes/server/IRCServer.hpp b/includes/server/IRCServer.hpp index e0ec330..6f44771 100644 --- a/includes/server/IRCServer.hpp +++ b/includes/server/IRCServer.hpp @@ -15,13 +15,12 @@ class IRCServer { IRCServer(IRCServer const &other); IRCServer &operator=(IRCServer const &other); - int _createDummyServerSocket(); + int _createServerSocket(); void _acceptClient(); void _disconnectClient(int const idx); public: IRCServer(); - ~IRCServer(); void run(); diff --git a/srcs/server/IRCServer.cpp b/srcs/server/IRCServer.cpp index 60fce74..95570f0 100644 --- a/srcs/server/IRCServer.cpp +++ b/srcs/server/IRCServer.cpp @@ -6,12 +6,27 @@ #include #include +/* + Signal handling + will be refactored in a separate class +*/ volatile std::sig_atomic_t IRCServer::g_stopServerLoop = 0; +/* + Signal callback function + Will set a static class member to the triggering signal, + i.e. != 0, so the state can be used as a bool value directly. + Will be refactored in a separate class, and with a more explicit + stop state request function +*/ void IRCServer::g_handleStopSignals(int signal) { g_stopServerLoop = signal; } +/* + Setup all signal that should trigger the + IRCServer to stop. +*/ void IRCServer::_initSignalHandling() { g_stopServerLoop = 0; _stopSignals.push_back(SIGINT); @@ -22,10 +37,19 @@ void IRCServer::_initSignalHandling() { throw std::runtime_error("std::signal failed"); } +/* + Reset all signal that should trigger the + IRCServer to stop to the default on server exit. +*/ void IRCServer::_resetSignalHandling() { _setStopSignals(SIG_DFL); } +/* + Helper function to actually set the signal behaviour + on system level using sigaction, as this is the most + portable POSIX compliant way to do it. +*/ bool IRCServer::_setStopSignals(sighandler_t handler) { bool success = true; @@ -41,15 +65,31 @@ bool IRCServer::_setStopSignals(sighandler_t handler) { return success; } +/* + IRCServer Constructor + * setup signal handling + * ... +*/ IRCServer::IRCServer() { _initSignalHandling(); } +/* + IRCServer Destructor + * reset signal handling to default + * ... +*/ IRCServer::~IRCServer() { _resetSignalHandling(); } -int IRCServer::_createDummyServerSocket() { +/* + Dummy function to get a usable server socket + for mockup testing. + Implementation explicitely not exhaustive, + will be replaced by an "official" version anyways. +*/ +int IRCServer::_createServerSocket() { // Create a TCP socket int serverSocket = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0); if (serverSocket == -1) { @@ -73,6 +113,12 @@ int IRCServer::_createDummyServerSocket() { return serverSocket; } +/* + Dummy function to get a usable client socket + on connect for mockup testing. + Implementation explicitely not exhaustive, + will be replaced by an "official" version anyways. +*/ void IRCServer::_acceptClient() { int clientFd; struct pollfd clientPollfd; @@ -96,6 +142,14 @@ void IRCServer::_acceptClient() { std::cout << "Client connected, fd: " << clientFd << std::endl; } +/* + Dummy function to disconnet aclient socket + for mockup testing. + Implementation explicitely not exhaustive, + will be replaced by an "official" version anyways. + There is more to be done than just closing and removing + the client socket. +*/ void IRCServer::_disconnectClient(int const idx) { pollfd &pfd = _pollfds.at(idx); @@ -110,22 +164,28 @@ void IRCServer::_disconnectClient(int const idx) { _pollfds.erase(_pollfds.begin() + idx); } +/* + Main server loop + + * sets up its listening socket + * runs an infinit loop to listen for connections + * handles client connect and disconnect scenarios + * on server stop, disconnects all clients and closes + the server socket +*/ void IRCServer::run() { - _createDummyServerSocket(); + _createServerSocket(); - // Event loop while (!g_stopServerLoop) { - // std::cout << "running..." << std::endl; sleep(1); int pollResult = poll(_pollfds.data(), _pollfds.size(), 0); if (pollResult == -1) { - // print nice error message and exit the loop + // nice error message and exit the loop // or throw, but then have ressource cleanup after catch break; } - // Check if server socket has activity struct pollfd &serverPollFds = _pollfds[0]; if (serverPollFds.revents & POLLERR) { std::cerr << "Server socket error\n"; @@ -133,13 +193,8 @@ void IRCServer::run() { } else if (serverPollFds.revents & POLLIN) { _acceptClient(); } - - // Clients for (size_t idx = 1; idx < _pollfds.size(); ++idx) { pollfd &pfd = _pollfds.at(idx); - // std::cout << "Idx: " << idx << std::endl; - // std::cout << " revents: " << std::hex << pfd.revents << std::endl; - if (pfd.revents & POLLERR) { std::cerr << "Client socket error on fd: " << pfd.fd << std::endl; @@ -148,14 +203,16 @@ void IRCServer::run() { } else if (pfd.revents & POLLIN) { char buffer[1024] = ""; int bytesRead = read(pfd.fd, buffer, 1024); + // for mockup only, + // this functionality will go to its own function of course if (bytesRead > 0) { std::cout << "Buffer read from client " << idx << ": \n" << std::string(buffer) << std::endl; } if (bytesRead <= 0) { + // Comment will be deleted later, kept for reference for now // this means, the client closed the connection // POLLHUP isn't useful, because - // When you kill netcat with Ctrl+C: // Netcat's OS kernel sent a FIN packet to your server // Your server's poll() detected this with POLLIN @@ -169,7 +226,6 @@ void IRCServer::run() { } } while (!_pollfds.empty()) { - // disconnecting clients first _disconnectClient(_pollfds.size() - 1); } std::cout << "caught signal: " << g_stopServerLoop << "." << std::endl; From deda3d8d0123b5f1291eb8626bc444844fd9a4e4 Mon Sep 17 00:00:00 2001 From: Rapha Date: Fri, 7 Aug 2026 14:48:02 +0200 Subject: [PATCH 016/349] bug: insert clang-tidy arguments in the right spot --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index cf7916d..7110a60 100644 --- a/Makefile +++ b/Makefile @@ -76,7 +76,7 @@ tidy: tidy-all: @find srcs includes -type f \( -name "*.cpp" -o -name "*.hpp" \) -print0 \ - | xargs -0 clang-tidy -- $(CXXFLAGS) $(IFLAGS) + | xargs -0 -I {} clang-tidy {} -- $(CXXFLAGS) $(IFLAGS) $(NAME): $(OBJS) $(CXX) $(CXXFLAGS) $(OBJS) -o $@ From 13a4e1486ae2c109f3e28382b148e9c0e1f71116 Mon Sep 17 00:00:00 2001 From: Rapha Date: Fri, 7 Aug 2026 14:48:08 +0200 Subject: [PATCH 017/349] rfa: changes based on clang-tidy hints --- includes/server/IRCServer.hpp | 2 +- srcs/main.cpp | 1 + srcs/server/IRCServer.cpp | 18 ++++++++++-------- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/includes/server/IRCServer.hpp b/includes/server/IRCServer.hpp index 6f44771..f1b204d 100644 --- a/includes/server/IRCServer.hpp +++ b/includes/server/IRCServer.hpp @@ -17,7 +17,7 @@ class IRCServer { int _createServerSocket(); void _acceptClient(); - void _disconnectClient(int const idx); + void _disconnectClient(size_t const idx); public: IRCServer(); diff --git a/srcs/main.cpp b/srcs/main.cpp index 115520a..d934d3b 100644 --- a/srcs/main.cpp +++ b/srcs/main.cpp @@ -46,6 +46,7 @@ int main(int argc, char **argv) { IRCServer srv; srv.run(); } catch (std::exception const &e) { + std::cerr << e.what() << std::endl; } std::cout << "Clean exit." << std::endl; } diff --git a/srcs/server/IRCServer.cpp b/srcs/server/IRCServer.cpp index 95570f0..363ec87 100644 --- a/srcs/server/IRCServer.cpp +++ b/srcs/server/IRCServer.cpp @@ -96,7 +96,7 @@ int IRCServer::_createServerSocket() { return -1; } // Bind - struct sockaddr_in serverAddr; + struct sockaddr_in serverAddr = {}; serverAddr.sin_family = AF_INET; serverAddr.sin_port = htons(8082); serverAddr.sin_addr.s_addr = INADDR_ANY; @@ -120,15 +120,15 @@ int IRCServer::_createServerSocket() { will be replaced by an "official" version anyways. */ void IRCServer::_acceptClient() { - int clientFd; - struct pollfd clientPollfd; - struct sockaddr_in clientAI; - socklen_t AISize = sizeof(clientAI); + int clientFd = 0; + struct pollfd clientPollfd = {}; + struct sockaddr_in clientAI = {}; + socklen_t aiSize = sizeof(clientAI); // accept4, so we can directly set SOCK_NONBLOCK // the subject allows fcntl only on macOS clientFd = accept4(_pollfds.front().fd, - reinterpret_cast(&clientAI), &AISize, + reinterpret_cast(&clientAI), &aiSize, SOCK_NONBLOCK); if (clientFd == -1) { @@ -150,7 +150,7 @@ void IRCServer::_acceptClient() { There is more to be done than just closing and removing the client socket. */ -void IRCServer::_disconnectClient(int const idx) { +void IRCServer::_disconnectClient(size_t const idx) { pollfd &pfd = _pollfds.at(idx); if (idx == 0) { @@ -190,7 +190,9 @@ void IRCServer::run() { if (serverPollFds.revents & POLLERR) { std::cerr << "Server socket error\n"; break; - } else if (serverPollFds.revents & POLLIN) { + } + + if (serverPollFds.revents & POLLIN) { _acceptClient(); } for (size_t idx = 1; idx < _pollfds.size(); ++idx) { From ba42e12d809eb1806e0858a0f3bfc4d9166b2ddc Mon Sep 17 00:00:00 2001 From: Kiruthika Murugananthan Date: Tue, 11 Aug 2026 00:44:40 +0200 Subject: [PATCH 018/349] Apply suggestions from code review: separate build for debug Co-authored-by: wenceslao <117309234+numorb@users.noreply.github.com> --- .gitignore | 4 ++++ Makefile | 22 ++++++++++++---------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index 0591320..ab0aa1a 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,9 @@ en.subject.pdf # Executables ircserv +ircserv_debug +ircserv_bonus +ircserv_bonus_debug ircserv_bonus *.exe *.out @@ -46,6 +49,7 @@ ircserv_bonus # Build directories build/ +build_debug/ Build/ build-*/ diff --git a/Makefile b/Makefile index 42797ef..1021da0 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,14 @@ # ---- Library and Project names ----------------------------------------------- NAME = ircserv +DEBUG_NAME = ircserv_debug # BONUS = ircserv_bonus +# DEBUG_BONUS = ircserv_bonus_debug # ---- Sources & Objects ------------------------------------------------------- SRC_DIR = srcs INC_DIR = includes BUILD_DIR = build +DEBUG_BUILD_DIR = build_debug SRCS_ROOT = main @@ -37,13 +40,14 @@ SRCS = $(SRCS_ROOT) # $(addprefix server/, $(SRCS_SERVER)) OBJS = $(addprefix $(BUILD_DIR)/$(SRC_DIR)/, $(addsuffix .o, $(SRCS))) - -# BONUS_OBJS = $(OBJS:.o=.bonus.o) +DEBUG_OBJS = $(addprefix $(DEBUG_BUILD_DIR)/$(SRC_DIR)/, $(addsuffix .o, $(SRCS))) +# BONUS_OBJS = $(OBJS:.o=.bonus.o) +# DEBUG_BONUS_OBJS = $(DEBUG_OBJS:.o=.bonus.o) # ---- Auto-generated header deps ---------------------------------------------- -DEPS = $(OBJS:.o=.d) -# BONUS_DEPS = $(BONUS_OBJS:.o=.d) +DEPS = $(OBJS:.o=.d) $(DEBUG_OBJS:.o=.d) +# BONUS_DEPS = $(BONUS_OBJS:.o=.d) $(DEBUG_BONUS_OBJS:.o=.d) # ---- Compiler and Flags ------------------------------------------------------ @@ -58,11 +62,9 @@ all: $(NAME) # bonus: $(BONUS) -debug: CXXFLAGS += $(DFLAGS) -debug: $(NAME) +debug: $(DEBUG_NAME) -# debug_bonus: CXXFLAGS += $(DFLAGS) -# debug_bonus: $(BONUS) +# debug_bonus: $(DEBUG_BONUS) # clang-format format: @@ -94,10 +96,10 @@ $(BUILD_DIR)/$(SRC_DIR)/%.o: $(SRC_DIR)/%.cpp # ---- Cleanup ----------------------------------------------------------------- clean: - rm -rf $(BUILD_DIR) + rm -rf $(BUILD_DIR) $(DEBUG_BUILD_DIR) fclean: clean - rm -f $(NAME) $(BONUS) + rm -f $(NAME) $(DEBUG_NAME) $(BONUS) $(DEBUG_BONUS) re: fclean all From fd689c20a92081561207cbb753d9d84336ebbabb Mon Sep 17 00:00:00 2001 From: ArchCipher Date: Tue, 11 Aug 2026 01:17:14 +0200 Subject: [PATCH 019/349] bugfix: makefile build rule for debug binaries --- Makefile | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Makefile b/Makefile index 1021da0..1e5c718 100644 --- a/Makefile +++ b/Makefile @@ -55,6 +55,7 @@ CXX = c++ CXXFLAGS = -Wall -Wextra -Werror -pedantic -std=c++98 IFLAGS = -I$(INC_DIR) DFLAGS = -DDEBUG -g -fsanitize=address +DEBUG_CXXFLAGS = $(CXXFLAGS) $(DFLAGS) # ---- Rules ------------------------------------------------------------------- @@ -83,17 +84,31 @@ tidy-all: $(NAME): $(OBJS) $(CXX) $(CXXFLAGS) $(OBJS) -o $@ +$(DEBUG_NAME): $(DEBUG_OBJS) + $(CXX) $(DEBUG_CXXFLAGS) $(DEBUG_OBJS) -o $@ + # $(BONUS): $(BONUS_OBJS) # $(CXX) $(CXXFLAGS) $(BONUS_OBJS) -o $@ +# $(DEBUG_BONUS): $(DEBUG_BONUS_OBJS) +# $(CXX) $(DEBUG_CXXFLAGS) $(DEBUG_BONUS_OBJS) -o $@ + $(BUILD_DIR)/$(SRC_DIR)/%.o: $(SRC_DIR)/%.cpp @mkdir -p $(@D) $(CXX) $(CXXFLAGS) $(IFLAGS) -MMD -MP -c $< -o $@ +$(DEBUG_BUILD_DIR)/$(SRC_DIR)/%.o: $(SRC_DIR)/%.cpp + @mkdir -p $(@D) + $(CXX) $(DEBUG_CXXFLAGS) $(IFLAGS) -MMD -MP -c $< -o $@ + # $(BUILD_DIR)/$(SRC_DIR)/%.bonus.o: $(SRC_DIR)/%.cpp # @mkdir -p $(@D) # $(CXX) $(CXXFLAGS) $(IFLAGS) -MMD -MP -c -DBONUS $< -o $@ +# $(DEBUG_BUILD_DIR)/$(SRC_DIR)/%.bonus.o: $(SRC_DIR)/%.cpp +# @mkdir -p $(@D) +# $(CXX) $(DEBUG_CXXFLAGS) $(IFLAGS) -MMD -MP -c -DBONUS $< -o $@ + # ---- Cleanup ----------------------------------------------------------------- clean: rm -rf $(BUILD_DIR) $(DEBUG_BUILD_DIR) From 6e3fb0c4cc34640d112b1b5a8d82931aebedf302 Mon Sep 17 00:00:00 2001 From: Rapha Date: Tue, 18 Aug 2026 12:50:15 +0200 Subject: [PATCH 020/349] dbg: add vscode debug setup --- .vscode/launch.json | 27 +++++++++++ .vscode/settings.json | 103 ++++++++++++++++++++++++++++++++++++++++++ .vscode/tasks.json | 22 +++++++++ 3 files changed, 152 insertions(+) create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 .vscode/tasks.json diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..de935ed --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,27 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "C/C++ Runner: Debug Session", + "type": "cppdbg", + "request": "launch", + "args": ["6667", "pass"], +// "stopAtEntry": true, + "externalConsole": false, + "cwd": "${workspaceFolder}", + "program": "${workspaceFolder}/ircserv_debug", + "MIMode": "gdb", + "miDebuggerPath": "gdb", + // "miDebuggerArgs": "-ex 'set debuginfod enabled off'", + //"preLaunchTask": "42 C build bonus test.c", + "preLaunchTask": "42 build with Makefile", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + } + ] + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..4eb0aea --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,103 @@ +{ + "C_Cpp_Runner.cCompilerPath": "gcc", + "C_Cpp_Runner.cppCompilerPath": "g++", + "C_Cpp_Runner.debuggerPath": "gdb", + "C_Cpp_Runner.cStandard": "", + "C_Cpp_Runner.cppStandard": "", + "C_Cpp_Runner.msvcBatchPath": "", + "C_Cpp_Runner.useMsvc": false, + "C_Cpp_Runner.warnings": [ + "-Wall", + "-Wextra", + "-Wpedantic", + "-Wshadow", + "-Wformat=2", + "-Wcast-align", + "-Wconversion", + "-Wsign-conversion", + "-Wnull-dereference" + ], + "C_Cpp_Runner.msvcWarnings": [ + "/W4", + "/permissive-", + "/w14242", + "/w14287", + "/w14296", + "/w14311", + "/w14826", + "/w44062", + "/w44242", + "/w14905", + "/w14906", + "/w14263", + "/w44265", + "/w14928" + ], + "C_Cpp_Runner.enableWarnings": true, + "C_Cpp_Runner.warningsAsError": false, + "C_Cpp_Runner.compilerArgs": [ + "-Wall -Werror -Wextra" + ], + "C_Cpp_Runner.linkerArgs": [], + "C_Cpp_Runner.includePaths": [], + "C_Cpp_Runner.includeSearch": [ + "*", + "**/*" + ], + "C_Cpp_Runner.excludeSearch": [ + "**/build", + "**/build/**", + "**/.*", + "**/.*/**", + "**/.vscode", + "**/.vscode/**" + ], + "C_Cpp_Runner.useAddressSanitizer": false, + "C_Cpp_Runner.useUndefinedSanitizer": false, + "C_Cpp_Runner.useLeakSanitizer": false, + "C_Cpp_Runner.showCompilationTime": false, + "C_Cpp_Runner.useLinkTimeOptimization": false, + "C_Cpp_Runner.msvcSecureNoWarnings": false, + "42header.email": "rhaas@student.42berlin.de", +"42header.username": "rhaas", +"files.associations": { + "rh_fmtconv_std.h": "c", + "ft_printf_helper.h": "c", + "unistd.h": "c", + "rh_fmtspec.h": "c", + "stddef.h": "c", + "rh_write.h": "c", + "libft.h": "c", + "push_swap.h": "c", + "ft_printf.h": "c", + "sort.h": "c", + "utils.h": "c", + "output.h": "c", + "get_next_line.h": "c", + "mlx.h": "c", + "keysymdef.h": "c", + "keysym.h": "c", + "bit": "c", + "move_zoom.h": "c", + "location.h": "c", + "xlib.h": "c", + "fractol.h": "c", + "limits": "c", + "math.h": "c", + "typeinfo": "c", + "limits.h": "c", + "fractals.h": "c", + "stdint.h": "c", + "stat.h": "c" +}, +"editor.defaultFoldingRangeProvider": "ms-vscode.cpptools", +"editor.defaultFormatter": "ms-vscode.cpptools", +"editor.insertSpaces": false, +"editor.detectIndentation": false, +"#endif with guard": { + "prefix": "#endif", + "body": "#endif // ${1:GUARD_NAME}", + "description": "Add #endif with guard comment" +} +} + diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..d772cd9 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,22 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "type": "cppbuild", + "label": "42 build with Makefile", + "command": "/usr/bin/make", + "args": [ + // "fclean", + "debug", + ], + "options": { + "cwd": "${workspaceFolder}" + }, + "problemMatcher": [ + "$gcc" + ], + "group": "build", + "detail": "compiler: /usr/bin/c++" + } + ] +} From cfd9e93c4c7182e13a2ecc5acc9dc7e8d7fa6aee Mon Sep 17 00:00:00 2001 From: Rapha Date: Tue, 18 Aug 2026 16:14:58 +0200 Subject: [PATCH 021/349] rfa: handle signals in their own class --- Makefile | 3 +- includes/IRC.hpp | 1 + includes/server/IRCServer.hpp | 10 +--- includes/server/SignalHandler.hpp | 20 ++++++++ srcs/main.cpp | 1 + srcs/server/IRCServer.cpp | 83 ++++--------------------------- srcs/server/SignalHandler.cpp | 71 ++++++++++++++++++++++++++ 7 files changed, 106 insertions(+), 83 deletions(-) create mode 100644 includes/server/SignalHandler.hpp create mode 100644 srcs/server/SignalHandler.cpp diff --git a/Makefile b/Makefile index 5c0a746..30124d7 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,8 @@ DEBUG_BUILD_DIR = build_debug SRCS_ROOT = main -SRCS_SERVER = IRCServer +SRCS_SERVER = IRCServer \ + SignalHandler # Client \ # Channel diff --git a/includes/IRC.hpp b/includes/IRC.hpp index 1ea901b..859afa8 100644 --- a/includes/IRC.hpp +++ b/includes/IRC.hpp @@ -1,4 +1,5 @@ #include "server/IRCServer.hpp" +#include "server/SignalHandler.hpp" #include #include \ No newline at end of file diff --git a/includes/server/IRCServer.hpp b/includes/server/IRCServer.hpp index f1b204d..3208ca4 100644 --- a/includes/server/IRCServer.hpp +++ b/includes/server/IRCServer.hpp @@ -1,15 +1,9 @@ #include #include +#include class IRCServer { private: - volatile static std::sig_atomic_t g_stopServerLoop; - static void g_handleStopSignals(int signal); - std::vector _stopSignals; - void _initSignalHandling(); - void _resetSignalHandling(); - bool _setStopSignals(sighandler_t handler); - std::vector _pollfds; IRCServer(IRCServer const &other); @@ -21,7 +15,7 @@ class IRCServer { public: IRCServer(); - ~IRCServer(); + // ~IRCServer(); void run(); }; diff --git a/includes/server/SignalHandler.hpp b/includes/server/SignalHandler.hpp new file mode 100644 index 0000000..542b58f --- /dev/null +++ b/includes/server/SignalHandler.hpp @@ -0,0 +1,20 @@ +#include +#include + +class SignalHandler { + private: + volatile static std::sig_atomic_t g_stopServerLoop; + static void g_handleStopSignals(int signal); + std::vector _stopSignals; + bool _setStopSignals(sighandler_t handler); + + std::vector _pollfds; + + SignalHandler(SignalHandler const &other); + SignalHandler &operator=(SignalHandler const &other); + + public: + SignalHandler(); + ~SignalHandler(); + static bool stopLoopSignalReceived(); +}; diff --git a/srcs/main.cpp b/srcs/main.cpp index d934d3b..2ef6928 100644 --- a/srcs/main.cpp +++ b/srcs/main.cpp @@ -43,6 +43,7 @@ int main(int argc, char **argv) { return (1); try { + SignalHandler sigHandler; IRCServer srv; srv.run(); } catch (std::exception const &e) { diff --git a/srcs/server/IRCServer.cpp b/srcs/server/IRCServer.cpp index 363ec87..871fb36 100644 --- a/srcs/server/IRCServer.cpp +++ b/srcs/server/IRCServer.cpp @@ -1,4 +1,5 @@ #include "server/IRCServer.hpp" +#include "server/SignalHandler.hpp" #include #include @@ -6,82 +7,17 @@ #include #include -/* - Signal handling - will be refactored in a separate class -*/ -volatile std::sig_atomic_t IRCServer::g_stopServerLoop = 0; - -/* - Signal callback function - Will set a static class member to the triggering signal, - i.e. != 0, so the state can be used as a bool value directly. - Will be refactored in a separate class, and with a more explicit - stop state request function -*/ -void IRCServer::g_handleStopSignals(int signal) { - g_stopServerLoop = signal; -} - -/* - Setup all signal that should trigger the - IRCServer to stop. -*/ -void IRCServer::_initSignalHandling() { - g_stopServerLoop = 0; - _stopSignals.push_back(SIGINT); - _stopSignals.push_back(SIGQUIT); - _stopSignals.push_back(SIGTERM); - - if (!_setStopSignals(g_handleStopSignals)) - throw std::runtime_error("std::signal failed"); -} - -/* - Reset all signal that should trigger the - IRCServer to stop to the default on server exit. -*/ -void IRCServer::_resetSignalHandling() { - _setStopSignals(SIG_DFL); -} - -/* - Helper function to actually set the signal behaviour - on system level using sigaction, as this is the most - portable POSIX compliant way to do it. -*/ -bool IRCServer::_setStopSignals(sighandler_t handler) { - bool success = true; - - struct sigaction sigact = {}; - sigact.sa_handler = handler; - sigemptyset(&sigact.sa_mask); - sigact.sa_flags = 0; - - for (size_t i = 0; i < _stopSignals.size(); ++i) - if (sigaction(_stopSignals.at(i), &sigact, NULL) != 0) - success = false; - - return success; -} - /* IRCServer Constructor - * setup signal handling - * ... + * ... */ -IRCServer::IRCServer() { - _initSignalHandling(); -} +IRCServer::IRCServer() {} -/* - IRCServer Destructor - * reset signal handling to default - * ... -*/ -IRCServer::~IRCServer() { - _resetSignalHandling(); -} +// /* +// IRCServer Destructor +// * ... +// */ +// IRCServer::~IRCServer() {} /* Dummy function to get a usable server socket @@ -176,7 +112,7 @@ void IRCServer::_disconnectClient(size_t const idx) { void IRCServer::run() { _createServerSocket(); - while (!g_stopServerLoop) { + while (!SignalHandler::stopLoopSignalReceived()) { sleep(1); int pollResult = poll(_pollfds.data(), _pollfds.size(), 0); @@ -230,5 +166,4 @@ void IRCServer::run() { while (!_pollfds.empty()) { _disconnectClient(_pollfds.size() - 1); } - std::cout << "caught signal: " << g_stopServerLoop << "." << std::endl; } diff --git a/srcs/server/SignalHandler.cpp b/srcs/server/SignalHandler.cpp new file mode 100644 index 0000000..75dfe0d --- /dev/null +++ b/srcs/server/SignalHandler.cpp @@ -0,0 +1,71 @@ +#include "server/SignalHandler.hpp" + +#include +#include +#include +#include +#include + +/* + Signal handling + will be refactored in a separate class +*/ +volatile std::sig_atomic_t SignalHandler::g_stopServerLoop = 0; + +/* + Signal callback function + Will set a static class member to the triggering signal, + i.e. != 0, so the state can be used as a bool value directly. + Will be refactored in a separate class, and with a more explicit + stop state request function +*/ +void SignalHandler::g_handleStopSignals(int signal) { + std::cout << "Caught stop signal: " << g_stopServerLoop << "." << std::endl; + g_stopServerLoop = signal; +} + +/* + Helper function to actually set the signal behaviour + on system level using sigaction, as this is the most + portable POSIX compliant way to do it. +*/ +bool SignalHandler::_setStopSignals(sighandler_t handler) { + bool success = true; + + struct sigaction sigact = {}; + sigact.sa_handler = handler; + sigemptyset(&sigact.sa_mask); + sigact.sa_flags = 0; + + for (size_t i = 0; success && i < _stopSignals.size(); ++i) + if (sigaction(_stopSignals.at(i), &sigact, NULL) != 0) + success = false; + + return success; +} + +/* + SignalHandler Constructor + * setup signal handling +*/ +SignalHandler::SignalHandler() { + g_stopServerLoop = 0; + _stopSignals.push_back(SIGINT); + _stopSignals.push_back(SIGQUIT); + _stopSignals.push_back(SIGTERM); + + if (!_setStopSignals(g_handleStopSignals)) + throw std::runtime_error("std::signal failed"); +} + +/* + SignalHandler Destructor + * reset signal handling to default +*/ +SignalHandler::~SignalHandler() { + _setStopSignals(SIG_DFL); +} + +bool SignalHandler::stopLoopSignalReceived() { + return (g_stopServerLoop > 0); +} From 48091143be89284331542000b6e90eebd6834ff7 Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 19 Aug 2026 17:36:03 +0200 Subject: [PATCH 022/349] rfa: clang-format dbg: move msg about signal to actually output the received signal number --- includes/server/SignalHandler.hpp | 2 +- srcs/server/SignalHandler.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/includes/server/SignalHandler.hpp b/includes/server/SignalHandler.hpp index 542b58f..cb4759e 100644 --- a/includes/server/SignalHandler.hpp +++ b/includes/server/SignalHandler.hpp @@ -16,5 +16,5 @@ class SignalHandler { public: SignalHandler(); ~SignalHandler(); - static bool stopLoopSignalReceived(); + static bool stopLoopSignalReceived(); }; diff --git a/srcs/server/SignalHandler.cpp b/srcs/server/SignalHandler.cpp index 75dfe0d..6b939dd 100644 --- a/srcs/server/SignalHandler.cpp +++ b/srcs/server/SignalHandler.cpp @@ -20,8 +20,8 @@ volatile std::sig_atomic_t SignalHandler::g_stopServerLoop = 0; stop state request function */ void SignalHandler::g_handleStopSignals(int signal) { - std::cout << "Caught stop signal: " << g_stopServerLoop << "." << std::endl; g_stopServerLoop = signal; + std::cout << "Caught stop signal: " << g_stopServerLoop << "." << std::endl; } /* From 358ee1d071c587c16fcc53355ada49d84079a101 Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 19 Aug 2026 17:37:41 +0200 Subject: [PATCH 023/349] fet: * handle chunked messages * respect max message size 512 (see RFC 2812) --- includes/server/IRCServer.hpp | 10 ++++-- srcs/main.cpp | 4 +-- srcs/server/IRCServer.cpp | 59 ++++++++++++++++++++++++++++++----- 3 files changed, 60 insertions(+), 13 deletions(-) diff --git a/includes/server/IRCServer.hpp b/includes/server/IRCServer.hpp index 3208ca4..5a563c7 100644 --- a/includes/server/IRCServer.hpp +++ b/includes/server/IRCServer.hpp @@ -1,10 +1,14 @@ -#include -#include #include +#include +#include + +#define BUFFLEN 516 +#define MAXMSGLEN 512 class IRCServer { private: - std::vector _pollfds; + std::vector _pollfds; + std::vector _userInputBuffer; IRCServer(IRCServer const &other); IRCServer &operator=(IRCServer const &other); diff --git a/srcs/main.cpp b/srcs/main.cpp index 2ef6928..ae8710c 100644 --- a/srcs/main.cpp +++ b/srcs/main.cpp @@ -43,8 +43,8 @@ int main(int argc, char **argv) { return (1); try { - SignalHandler sigHandler; - IRCServer srv; + SignalHandler sigHandler; + IRCServer srv; srv.run(); } catch (std::exception const &e) { std::cerr << e.what() << std::endl; diff --git a/srcs/server/IRCServer.cpp b/srcs/server/IRCServer.cpp index 871fb36..b1e8b5e 100644 --- a/srcs/server/IRCServer.cpp +++ b/srcs/server/IRCServer.cpp @@ -1,4 +1,5 @@ #include "server/IRCServer.hpp" + #include "server/SignalHandler.hpp" #include @@ -43,6 +44,7 @@ int IRCServer::_createServerSocket() { // listen listen(serverSocket, 100); _pollfds.resize(1); + _userInputBuffer.resize(1); _pollfds[0].fd = serverSocket; _pollfds[0].events = POLLIN; // Monitor for incoming connections @@ -75,6 +77,7 @@ void IRCServer::_acceptClient() { clientPollfd.events = POLLIN; clientPollfd.revents = 0; _pollfds.push_back(clientPollfd); + _userInputBuffer.push_back(std::string()); std::cout << "Client connected, fd: " << clientFd << std::endl; } @@ -98,6 +101,7 @@ void IRCServer::_disconnectClient(size_t const idx) { << " clients remaining)" << std::endl; close(pfd.fd); _pollfds.erase(_pollfds.begin() + idx); + _userInputBuffer.erase(_userInputBuffer.begin() + idx); } /* @@ -113,7 +117,7 @@ void IRCServer::run() { _createServerSocket(); while (!SignalHandler::stopLoopSignalReceived()) { - sleep(1); + usleep(100); int pollResult = poll(_pollfds.data(), _pollfds.size(), 0); if (pollResult == -1) { @@ -136,16 +140,55 @@ void IRCServer::run() { if (pfd.revents & POLLERR) { std::cerr << "Client socket error on fd: " << pfd.fd << std::endl; - // Do we need to disconnect directly in this case? Maybe not. _disconnectClient(idx); } else if (pfd.revents & POLLIN) { - char buffer[1024] = ""; - int bytesRead = read(pfd.fd, buffer, 1024); - // for mockup only, - // this functionality will go to its own function of course + char buffer[BUFFLEN]; + int bytesRead = recv(pfd.fd, buffer, BUFFLEN, 0); + if (bytesRead > 0) { - std::cout << "Buffer read from client " << idx << ": \n" - << std::string(buffer) << std::endl; + std::string &userInput = _userInputBuffer.at(idx); + std::string bufferStr(buffer, bytesRead); + // bool userInputValid = false; + + // // DEBUG for cont'd messages + // if (!userInput.empty() && *userInput.rbegin() != '\r') { + // *userInput.rbegin() = '&'; + // } + userInput += bufferStr; + size_t lfcrPos = userInput.find("\r\n"); + if (userInput.length() >= MAXMSGLEN) { + if (lfcrPos > MAXMSGLEN - 2) { + std::cerr << "Client data error: message too " + "long.\nData sent will be discarded." + << std::endl; + while (bytesRead > 0) + bytesRead = recv(pfd.fd, buffer, BUFFLEN, 0); + userInput.clear(); + continue; + } + } else if (lfcrPos == std::string::npos) { + continue; + } else if (lfcrPos < userInput.length() - 2) { + std::cerr << "Client data error: message " + "malformed, data after CR-LF.\nExtraneous " + "data will be discarded." + << std::endl; + userInput.resize(lfcrPos + 2); + } + // if (lfcrPos == userInput.length() - 2) + // userInputValid = true; + // else { + // std::cerr << "What's going on here?" << std::endl; + // // userInput += bufferStr.substr(0, lfcrPos + 2); + // } + // if (userInputValid) + { + std::cout << "UserInput read from client " << idx + << ": \n" + << std::string(userInput) /* << std::endl */; + userInput.clear(); + } + // } } if (bytesRead <= 0) { // Comment will be deleted later, kept for reference for now From 2e65d43b9cfbf1d50134d8d667fab36034c8d78c Mon Sep 17 00:00:00 2001 From: Rapha Date: Fri, 21 Aug 2026 12:49:04 +0200 Subject: [PATCH 024/349] clang-tidy: * remove warning about const in declarations * change member name styling to trailing underscore --- .clang-tidy | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 3a38405..58d37a8 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -14,8 +14,9 @@ Checks: > -cppcoreguidelines-pro-bounds-constant-array-index, -cppcoreguidelines-macro-to-enum, -readability-braces-around-statements, - -readability-implicit-bool-conversion, + -readability-avoid-const-params-in-decls, -readability-identifier-length, + -readability-implicit-bool-conversion, -performance-avoid-endl, WarningsAsErrors: true @@ -39,15 +40,13 @@ CheckOptions: value: camelBack - key: readability-identifier-naming.VariableCase value: camelBack - - key: readability-identifier-naming.ClassMemberPrefix - value: g_ - - key: readability-identifier-naming.ClassMethodPrefix - value: g_ - - key: readability-identifier-naming.PrivateMemberPrefix + - key: readability-identifier-naming.ClassMemberSuffix + value: _ + - key: readability-identifier-naming.PrivateMemberSuffix value: _ - key: readability-identifier-naming.PrivateMethodCase value: camelBack - - key: readability-identifier-naming.PrivateMethodPrefix + - key: readability-identifier-naming.PrivateMethodSuffix value: _ - key: readability-identifier-naming.IgnoreMainLikeFunctions From 4238445167abf7874b7e6afdcd3b13cab3ffeeed Mon Sep 17 00:00:00 2001 From: Rapha Date: Fri, 21 Aug 2026 12:56:00 +0200 Subject: [PATCH 025/349] rfa: * apply new member name convention * remove explicit d'tor and copy member functions * socket handling: cleanup, restructure, error mgmnt feat: initialise port and password from cla bug: remove _pollfds member from SignalHandler --- includes/server/IRCServer.hpp | 19 ++-- includes/server/SignalHandler.hpp | 10 +- srcs/main.cpp | 40 +++++++- srcs/server/IRCServer.cpp | 164 +++++++++++++++++++++--------- srcs/server/SignalHandler.cpp | 28 ++--- 5 files changed, 178 insertions(+), 83 deletions(-) diff --git a/includes/server/IRCServer.hpp b/includes/server/IRCServer.hpp index 5a563c7..1c14bc2 100644 --- a/includes/server/IRCServer.hpp +++ b/includes/server/IRCServer.hpp @@ -4,21 +4,24 @@ #define BUFFLEN 516 #define MAXMSGLEN 512 +#define MAX_CONNECTIONS 512 class IRCServer { private: - std::vector _pollfds; - std::vector _userInputBuffer; + int port_; + std::string password_; + std::vector pollFds_; + std::vector userInputBuffer_; - IRCServer(IRCServer const &other); - IRCServer &operator=(IRCServer const &other); + // IRCServer(IRCServer const &other); + // IRCServer &operator=(IRCServer const &other); - int _createServerSocket(); - void _acceptClient(); - void _disconnectClient(size_t const idx); + int createServerSocket_(int const domain); + void acceptClient_(); + void disconnectClient_(size_t const idx); public: - IRCServer(); + IRCServer(int const port, std::string const &password); // ~IRCServer(); void run(); diff --git a/includes/server/SignalHandler.hpp b/includes/server/SignalHandler.hpp index cb4759e..eb33a10 100644 --- a/includes/server/SignalHandler.hpp +++ b/includes/server/SignalHandler.hpp @@ -3,12 +3,10 @@ class SignalHandler { private: - volatile static std::sig_atomic_t g_stopServerLoop; - static void g_handleStopSignals(int signal); - std::vector _stopSignals; - bool _setStopSignals(sighandler_t handler); - - std::vector _pollfds; + volatile static std::sig_atomic_t stopServerLoop_; + static void handleStopSignals_(int signal); + std::vector stopSignals_; + bool setStopSignals_(sighandler_t handler); SignalHandler(SignalHandler const &other); SignalHandler &operator=(SignalHandler const &other); diff --git a/srcs/main.cpp b/srcs/main.cpp index ae8710c..4fce3a3 100644 --- a/srcs/main.cpp +++ b/srcs/main.cpp @@ -1,5 +1,13 @@ #include "IRC.hpp" +#include +#include +#include +#include + +static void checkExtractCmdLnArgs(int const argc, char *const argv[], int &port, + std::string &password); + /* NAME: ircserv @@ -37,17 +45,39 @@ equivalent) */ -int main(int argc, char **argv) { - (void)argv; - if (argc != 3) - return (1); +int main(int argc, char **argv) { + int port = 0; + std::string password; try { + checkExtractCmdLnArgs(argc, argv, port, password); + SignalHandler sigHandler; - IRCServer srv; + IRCServer srv(port, password); srv.run(); } catch (std::exception const &e) { std::cerr << e.what() << std::endl; + return 1; } std::cout << "Clean exit." << std::endl; + return 0; +} + +static void checkExtractCmdLnArgs(int const argc, char *const argv[], int &port, + std::string &password) { + std::istringstream iss; + std::string remainder; + if (argc != 3) + throw std::out_of_range( + "Usage: ./ircserv "); + + iss.str(argv[1]); // NOLINT + if (!((iss >> port) && (iss >> remainder).eof())) + throw std::runtime_error( + "Error while extracting port number from commandline parameter."); + + iss.str(argv[2]); // NOLINT + if (!(iss >> password).eof()) + throw std::out_of_range( + "Error while extracting password from commandline parameter."); } diff --git a/srcs/server/IRCServer.cpp b/srcs/server/IRCServer.cpp index b1e8b5e..64243ef 100644 --- a/srcs/server/IRCServer.cpp +++ b/srcs/server/IRCServer.cpp @@ -1,18 +1,23 @@ #include "server/IRCServer.hpp" #include "server/SignalHandler.hpp" +// #include "server/Socket.hpp" #include #include #include +#include #include +// #include +#include #include /* IRCServer Constructor - * ... + * set port and password from command line args */ -IRCServer::IRCServer() {} +IRCServer::IRCServer(int const port, std::string const &password) + : port_(port), password_(password) {} // /* // IRCServer Destructor @@ -26,29 +31,81 @@ IRCServer::IRCServer() {} Implementation explicitely not exhaustive, will be replaced by an "official" version anyways. */ -int IRCServer::_createServerSocket() { - // Create a TCP socket - int serverSocket = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0); - if (serverSocket == -1) { - return -1; - } - // Bind - struct sockaddr_in serverAddr = {}; - serverAddr.sin_family = AF_INET; - serverAddr.sin_port = htons(8082); - serverAddr.sin_addr.s_addr = INADDR_ANY; - if (bind(serverSocket, reinterpret_cast(&serverAddr), - sizeof(serverAddr)) == -1) { - return -1; +static std::string getDomainStr(int const domain) { + if (domain == AF_INET) + return "IPv4"; + if (domain == AF_INET6) + return "IPv6"; + return ""; +} + +static void storeServerSocket(int const sock, std::vector &pFds, + std::vector &usrInBuff) { + pollfd pfd = {}; + pfd.fd = sock; + pfd.events = POLLIN; // Monitor for incoming connections + + pFds.push_back(pfd); + usrInBuff.push_back(std::string()); +} + +static void bindServerSocket(int const socketFd, int const domain, + int const port) { + struct sockaddr *serverSockaddr = 0; + socklen_t socklen = 0; + struct sockaddr_in serverAddr4 = {}; + struct sockaddr_in6 serverAddr6 = {}; + + if (domain == AF_INET) { + serverAddr4.sin_family = AF_INET; + serverAddr4.sin_addr.s_addr = INADDR_ANY; + serverAddr4.sin_port = htons(port); + serverSockaddr = + reinterpret_cast(&serverAddr4); // NOLINT + socklen = sizeof(serverAddr4); + } else if (domain == AF_INET6) { + serverAddr6.sin6_family = AF_INET; + serverAddr6.sin6_addr = in6addr_any; + serverAddr6.sin6_port = htons(port); + serverSockaddr = + reinterpret_cast(&serverAddr6); // NOLINT + socklen = sizeof(serverAddr6); } - // listen - listen(serverSocket, 100); - _pollfds.resize(1); - _userInputBuffer.resize(1); - _pollfds[0].fd = serverSocket; - _pollfds[0].events = POLLIN; // Monitor for incoming connections - - return serverSocket; + if (bind(socketFd, serverSockaddr, socklen) < 0) + throw std::runtime_error("Error while binding server socket for " + + getDomainStr(domain) + " !"); +} + +int IRCServer::createServerSocket_(int const domain) { + if (domain != AF_INET && domain != AF_INET6) + throw std::invalid_argument( + "Internal Error while opening server socket."); + + int socketFd = socket(domain, SOCK_STREAM, 0); + if (socketFd < 0) + throw std::runtime_error("Error while opening server socket for " + + getDomainStr(domain) + " !"); + + int optval = 1; + if (setsockopt(socketFd, SOL_SOCKET, SO_REUSEADDR, &optval, + sizeof(optval)) != 0) + throw std::runtime_error("Error while setting socket options for " + + getDomainStr(domain) + " !"); + + if (fcntl(socketFd, F_SETFL, O_NONBLOCK) != 0) // NOLINT + throw std::runtime_error( + "Error while setting socket to NON-BLOCKING for " + + getDomainStr(domain) + " !"); + + bindServerSocket(socketFd, domain, port_); + + if (listen(socketFd, MAX_CONNECTIONS) < 0) + throw std::runtime_error("Error while listening on socket for " + + getDomainStr(domain) + " !"); + + storeServerSocket(socketFd, pollFds_, userInputBuffer_); + + return socketFd; } /* @@ -57,7 +114,7 @@ int IRCServer::_createServerSocket() { Implementation explicitely not exhaustive, will be replaced by an "official" version anyways. */ -void IRCServer::_acceptClient() { +void IRCServer::acceptClient_() { int clientFd = 0; struct pollfd clientPollfd = {}; struct sockaddr_in clientAI = {}; @@ -65,8 +122,9 @@ void IRCServer::_acceptClient() { // accept4, so we can directly set SOCK_NONBLOCK // the subject allows fcntl only on macOS - clientFd = accept4(_pollfds.front().fd, - reinterpret_cast(&clientAI), &aiSize, + clientFd = accept4(pollFds_.front().fd, + reinterpret_cast(&clientAI), + &aiSize, // NOLINT SOCK_NONBLOCK); if (clientFd == -1) { @@ -76,8 +134,8 @@ void IRCServer::_acceptClient() { clientPollfd.fd = clientFd; clientPollfd.events = POLLIN; clientPollfd.revents = 0; - _pollfds.push_back(clientPollfd); - _userInputBuffer.push_back(std::string()); + pollFds_.push_back(clientPollfd); + userInputBuffer_.push_back(std::string()); std::cout << "Client connected, fd: " << clientFd << std::endl; } @@ -89,19 +147,24 @@ void IRCServer::_acceptClient() { There is more to be done than just closing and removing the client socket. */ -void IRCServer::_disconnectClient(size_t const idx) { - pollfd &pfd = _pollfds.at(idx); +void IRCServer::disconnectClient_(size_t const idx) { + pollfd &pfd = pollFds_.at(idx); if (idx == 0) { - if (_pollfds.size() != 1) + if (pollFds_.size() != 1) std::cout << "Close clients before closing server!" << std::endl; std::cout << "Closing server" << std::endl; } else - std::cout << "Closing client #" << idx << " (" << _pollfds.size() - 2 + std::cout << "Closing client #" << idx << " (" << pollFds_.size() - 2 << " clients remaining)" << std::endl; close(pfd.fd); - _pollfds.erase(_pollfds.begin() + idx); - _userInputBuffer.erase(_userInputBuffer.begin() + idx); + + std::vector::difference_type dist = + static_cast::difference_type>(idx); + pollFds_.erase(pollFds_.begin() + dist); + userInputBuffer_.erase( + userInputBuffer_.begin() + + static_cast::difference_type>(dist)); } /* @@ -114,40 +177,40 @@ void IRCServer::_disconnectClient(size_t const idx) { the server socket */ void IRCServer::run() { - _createServerSocket(); + createServerSocket_(AF_INET); while (!SignalHandler::stopLoopSignalReceived()) { usleep(100); - int pollResult = poll(_pollfds.data(), _pollfds.size(), 0); + int pollResult = poll(pollFds_.data(), pollFds_.size(), 0); if (pollResult == -1) { // nice error message and exit the loop // or throw, but then have ressource cleanup after catch break; } - struct pollfd &serverPollFds = _pollfds[0]; + struct pollfd &serverPollFds = pollFds_[0]; if (serverPollFds.revents & POLLERR) { std::cerr << "Server socket error\n"; break; } if (serverPollFds.revents & POLLIN) { - _acceptClient(); + acceptClient_(); } - for (size_t idx = 1; idx < _pollfds.size(); ++idx) { - pollfd &pfd = _pollfds.at(idx); + for (size_t idx = 1; idx < pollFds_.size(); ++idx) { + pollfd &pfd = pollFds_.at(idx); if (pfd.revents & POLLERR) { std::cerr << "Client socket error on fd: " << pfd.fd << std::endl; - _disconnectClient(idx); + disconnectClient_(idx); } else if (pfd.revents & POLLIN) { - char buffer[BUFFLEN]; - int bytesRead = recv(pfd.fd, buffer, BUFFLEN, 0); + char buffer[BUFFLEN]; + ssize_t bytesRead = recv(pfd.fd, &buffer[0], BUFFLEN, 0); if (bytesRead > 0) { - std::string &userInput = _userInputBuffer.at(idx); - std::string bufferStr(buffer, bytesRead); + std::string &userInput = userInputBuffer_.at(idx); + std::string bufferStr(&buffer[0], bytesRead); // bool userInputValid = false; // // DEBUG for cont'd messages @@ -162,7 +225,8 @@ void IRCServer::run() { "long.\nData sent will be discarded." << std::endl; while (bytesRead > 0) - bytesRead = recv(pfd.fd, buffer, BUFFLEN, 0); + bytesRead = + recv(pfd.fd, &buffer[0], BUFFLEN, 0); userInput.clear(); continue; } @@ -201,12 +265,12 @@ void IRCServer::run() { // FIN is the TCP mechanism for orderly shutdown. // That's why POLLIN + read() return 0—the FIN, // and the zero read is how the kernel tells you "the peer closed." - _disconnectClient(idx); + disconnectClient_(idx); } } } } - while (!_pollfds.empty()) { - _disconnectClient(_pollfds.size() - 1); + while (!pollFds_.empty()) { + disconnectClient_(pollFds_.size() - 1); } } diff --git a/srcs/server/SignalHandler.cpp b/srcs/server/SignalHandler.cpp index 6b939dd..d5b9ae4 100644 --- a/srcs/server/SignalHandler.cpp +++ b/srcs/server/SignalHandler.cpp @@ -10,7 +10,7 @@ Signal handling will be refactored in a separate class */ -volatile std::sig_atomic_t SignalHandler::g_stopServerLoop = 0; +volatile std::sig_atomic_t SignalHandler::stopServerLoop_ = 0; /* Signal callback function @@ -19,9 +19,9 @@ volatile std::sig_atomic_t SignalHandler::g_stopServerLoop = 0; Will be refactored in a separate class, and with a more explicit stop state request function */ -void SignalHandler::g_handleStopSignals(int signal) { - g_stopServerLoop = signal; - std::cout << "Caught stop signal: " << g_stopServerLoop << "." << std::endl; +void SignalHandler::handleStopSignals_(int signal) { + stopServerLoop_ = signal; + std::cout << "Caught stop signal: " << stopServerLoop_ << "." << std::endl; } /* @@ -29,7 +29,7 @@ void SignalHandler::g_handleStopSignals(int signal) { on system level using sigaction, as this is the most portable POSIX compliant way to do it. */ -bool SignalHandler::_setStopSignals(sighandler_t handler) { +bool SignalHandler::setStopSignals_(sighandler_t handler) { bool success = true; struct sigaction sigact = {}; @@ -37,8 +37,8 @@ bool SignalHandler::_setStopSignals(sighandler_t handler) { sigemptyset(&sigact.sa_mask); sigact.sa_flags = 0; - for (size_t i = 0; success && i < _stopSignals.size(); ++i) - if (sigaction(_stopSignals.at(i), &sigact, NULL) != 0) + for (size_t i = 0; success && i < stopSignals_.size(); ++i) + if (sigaction(stopSignals_.at(i), &sigact, NULL) != 0) success = false; return success; @@ -49,12 +49,12 @@ bool SignalHandler::_setStopSignals(sighandler_t handler) { * setup signal handling */ SignalHandler::SignalHandler() { - g_stopServerLoop = 0; - _stopSignals.push_back(SIGINT); - _stopSignals.push_back(SIGQUIT); - _stopSignals.push_back(SIGTERM); + stopServerLoop_ = 0; + stopSignals_.push_back(SIGINT); + stopSignals_.push_back(SIGQUIT); + stopSignals_.push_back(SIGTERM); - if (!_setStopSignals(g_handleStopSignals)) + if (!setStopSignals_(handleStopSignals_)) throw std::runtime_error("std::signal failed"); } @@ -63,9 +63,9 @@ SignalHandler::SignalHandler() { * reset signal handling to default */ SignalHandler::~SignalHandler() { - _setStopSignals(SIG_DFL); + setStopSignals_(SIG_DFL); } bool SignalHandler::stopLoopSignalReceived() { - return (g_stopServerLoop > 0); + return (stopServerLoop_ > 0); } From 05f6ccd0f3d3a57ef8d01a84f404e2b4207b5aae Mon Sep 17 00:00:00 2001 From: Rapha Date: Fri, 21 Aug 2026 14:58:26 +0200 Subject: [PATCH 026/349] feat: accept client implementation (partly) --- includes/server/IRCServer.hpp | 12 ++- srcs/server/IRCServer.cpp | 167 ++++++++++++++++++++++------------ 2 files changed, 120 insertions(+), 59 deletions(-) diff --git a/includes/server/IRCServer.hpp b/includes/server/IRCServer.hpp index 1c14bc2..17b854b 100644 --- a/includes/server/IRCServer.hpp +++ b/includes/server/IRCServer.hpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -10,14 +11,16 @@ class IRCServer { private: int port_; std::string password_; + + size_t numServerSockets_; std::vector pollFds_; - std::vector userInputBuffer_; + std::vector clientInputBuffers_; // IRCServer(IRCServer const &other); // IRCServer &operator=(IRCServer const &other); int createServerSocket_(int const domain); - void acceptClient_(); + void acceptClient_(int const serverFd); void disconnectClient_(size_t const idx); public: @@ -26,3 +29,8 @@ class IRCServer { void run(); }; + +typedef union uSocketAddrX { + struct sockaddr_in sai4; + struct sockaddr_in6 sai6; +} socketAddrX; diff --git a/srcs/server/IRCServer.cpp b/srcs/server/IRCServer.cpp index 64243ef..1abf868 100644 --- a/srcs/server/IRCServer.cpp +++ b/srcs/server/IRCServer.cpp @@ -8,7 +8,6 @@ #include #include #include -// #include #include #include @@ -17,7 +16,7 @@ * set port and password from command line args */ IRCServer::IRCServer(int const port, std::string const &password) - : port_(port), password_(password) {} + : port_(port), password_(password), numServerSockets_(0) {} // /* // IRCServer Destructor @@ -39,43 +38,83 @@ static std::string getDomainStr(int const domain) { return ""; } -static void storeServerSocket(int const sock, std::vector &pFds, - std::vector &usrInBuff) { - pollfd pfd = {}; - pfd.fd = sock; - pfd.events = POLLIN; // Monitor for incoming connections +static int sockAddrFamily(int const socketFd) { + struct sockaddr sockAddr; + socklen_t sockLen = sizeof(sockAddr); + if (getsockname(socketFd, &sockAddr, &sockLen) != 0) + throw; + return sockAddr.sa_family; +} - pFds.push_back(pfd); - usrInBuff.push_back(std::string()); +static void setSocketNonBlock(int const socketFd) { + int const domain = sockAddrFamily(socketFd); + if (fcntl(socketFd, F_SETFL, O_NONBLOCK) != 0) // NOLINT + throw std::runtime_error( + "Error while setting socket to NON-BLOCKING for " + + getDomainStr(domain) + " !"); } -static void bindServerSocket(int const socketFd, int const domain, - int const port) { - struct sockaddr *serverSockaddr = 0; - socklen_t socklen = 0; - struct sockaddr_in serverAddr4 = {}; - struct sockaddr_in6 serverAddr6 = {}; +static void setSocketAddr(int const socketFd, int const port, + socketAddrX &sockAddrIn46, socklen_t &sockLen) { + int domain = sockAddrFamily(socketFd); if (domain == AF_INET) { - serverAddr4.sin_family = AF_INET; - serverAddr4.sin_addr.s_addr = INADDR_ANY; - serverAddr4.sin_port = htons(port); - serverSockaddr = - reinterpret_cast(&serverAddr4); // NOLINT - socklen = sizeof(serverAddr4); + struct sockaddr_in &clientSockAddrIn4 = sockAddrIn46.sai4; + clientSockAddrIn4.sin_family = AF_INET; + clientSockAddrIn4.sin_addr.s_addr = INADDR_ANY; + clientSockAddrIn4.sin_port = htons(port); + sockLen = sizeof(clientSockAddrIn4); } else if (domain == AF_INET6) { - serverAddr6.sin6_family = AF_INET; - serverAddr6.sin6_addr = in6addr_any; - serverAddr6.sin6_port = htons(port); - serverSockaddr = - reinterpret_cast(&serverAddr6); // NOLINT - socklen = sizeof(serverAddr6); + struct sockaddr_in6 &clientSockAddrIn6 = sockAddrIn46.sai6; + clientSockAddrIn6.sin6_family = AF_INET6; + clientSockAddrIn6.sin6_addr = in6addr_any; + clientSockAddrIn6.sin6_port = htons(port); + sockLen = sizeof(clientSockAddrIn6); } - if (bind(socketFd, serverSockaddr, socklen) < 0) +} + +static void bindServerSocket(int const socketFd, int const domain, + int const port) { + // struct sockaddr *serverSockaddr = 0; + // socklen_t socklen = 0; + // struct sockaddr_in serverAddr4 = {}; + // struct sockaddr_in6 serverAddr6 = {}; + // if (domain == AF_INET) { + // serverAddr4.sin_family = AF_INET; + // serverAddr4.sin_addr.s_addr = INADDR_ANY; + // serverAddr4.sin_port = htons(port); + // serverSockaddr = + // reinterpret_cast(&serverAddr4); // NOLINT + // socklen = sizeof(serverAddr4); + // } else if (domain == AF_INET6) { + // serverAddr6.sin6_family = AF_INET; + // serverAddr6.sin6_addr = in6addr_any; + // serverAddr6.sin6_port = htons(port); + // serverSockaddr = + // reinterpret_cast(&serverAddr6); // NOLINT + // socklen = sizeof(serverAddr6); + // } + socketAddrX sockAddrIn46; + socklen_t sockLen; + setSocketAddr(socketFd, port, sockAddrIn46, sockLen); + + if (bind(socketFd, reinterpret_cast(&sockAddrIn46), + sockLen) < 0) throw std::runtime_error("Error while binding server socket for " + getDomainStr(domain) + " !"); } +static void storeServerSocket(int const socketFd, + std::vector &pFds, + std::vector &usrInBuff) { + pollfd pfd = {}; + pfd.fd = socketFd; + pfd.events = POLLIN; // Monitor for incoming connections + + pFds.push_back(pfd); + usrInBuff.push_back(std::string()); +} + int IRCServer::createServerSocket_(int const domain) { if (domain != AF_INET && domain != AF_INET6) throw std::invalid_argument( @@ -92,10 +131,7 @@ int IRCServer::createServerSocket_(int const domain) { throw std::runtime_error("Error while setting socket options for " + getDomainStr(domain) + " !"); - if (fcntl(socketFd, F_SETFL, O_NONBLOCK) != 0) // NOLINT - throw std::runtime_error( - "Error while setting socket to NON-BLOCKING for " + - getDomainStr(domain) + " !"); + setSocketNonBlock(socketFd); bindServerSocket(socketFd, domain, port_); @@ -103,7 +139,8 @@ int IRCServer::createServerSocket_(int const domain) { throw std::runtime_error("Error while listening on socket for " + getDomainStr(domain) + " !"); - storeServerSocket(socketFd, pollFds_, userInputBuffer_); + storeServerSocket(socketFd, pollFds_, clientInputBuffers_); + ++numServerSockets_; return socketFd; } @@ -114,29 +151,44 @@ int IRCServer::createServerSocket_(int const domain) { Implementation explicitely not exhaustive, will be replaced by an "official" version anyways. */ -void IRCServer::acceptClient_() { - int clientFd = 0; - struct pollfd clientPollfd = {}; - struct sockaddr_in clientAI = {}; - socklen_t aiSize = sizeof(clientAI); - - // accept4, so we can directly set SOCK_NONBLOCK - // the subject allows fcntl only on macOS - clientFd = accept4(pollFds_.front().fd, - reinterpret_cast(&clientAI), - &aiSize, // NOLINT - SOCK_NONBLOCK); - - if (clientFd == -1) { - std::cerr << "Accept failed." << std::endl; - return; - } +void IRCServer::acceptClient_(int const serverFd) { + socketAddrX sockAddrIn46; + socklen_t sockLen; + setSocketAddr(serverFd, 0, sockAddrIn46, sockLen); + + int const clientFd = + accept(serverFd, reinterpret_cast(&sockAddrIn46), + &sockLen); // NOLINT + if (clientFd == -1) + //throw ?? + ; + + setSocketNonBlock(clientFd); + + pollfd clientPollfd = {}; clientPollfd.fd = clientFd; clientPollfd.events = POLLIN; - clientPollfd.revents = 0; pollFds_.push_back(clientPollfd); - userInputBuffer_.push_back(std::string()); - std::cout << "Client connected, fd: " << clientFd << std::endl; + clientInputBuffers_.push_back(std::string()); + std::cout << "Client connected:\n" + << " fd: " << clientFd; + if (sockAddrIn46.sai4.sin_family == AF_INET) + std::cout << "\n AF: " << sockAddrIn46.sai4.sin_family + << "\n addr:port: " << sockAddrIn46.sai4.sin_addr.s_addr + << ":" << sockAddrIn46.sai4.sin_port << std::endl; + else if (sockAddrIn46.sai6.sin6_family == AF_INET6) { + std::cout << "\n AF: " << sockAddrIn46.sai6.sin6_family + << "\n addr:port: ["; + for (size_t idx = 0; + idx < sizeof(sockAddrIn46.sai6.sin6_addr) / sizeof(uint8_t); + ++idx) { + if (idx > 0 && idx % 4 == 0) + std::cout << ":"; + std::cout << std::hex << sockAddrIn46.sai6.sin6_addr.s6_addr[idx]; + } + + std::cout << "]:" << sockAddrIn46.sai4.sin_port << std::endl; + } } /* @@ -162,8 +214,8 @@ void IRCServer::disconnectClient_(size_t const idx) { std::vector::difference_type dist = static_cast::difference_type>(idx); pollFds_.erase(pollFds_.begin() + dist); - userInputBuffer_.erase( - userInputBuffer_.begin() + + clientInputBuffers_.erase( + clientInputBuffers_.begin() + static_cast::difference_type>(dist)); } @@ -178,6 +230,7 @@ void IRCServer::disconnectClient_(size_t const idx) { */ void IRCServer::run() { createServerSocket_(AF_INET); + createServerSocket_(AF_INET6); while (!SignalHandler::stopLoopSignalReceived()) { usleep(100); @@ -196,7 +249,7 @@ void IRCServer::run() { } if (serverPollFds.revents & POLLIN) { - acceptClient_(); + acceptClient_(serverPollFds.fd); } for (size_t idx = 1; idx < pollFds_.size(); ++idx) { pollfd &pfd = pollFds_.at(idx); @@ -209,7 +262,7 @@ void IRCServer::run() { ssize_t bytesRead = recv(pfd.fd, &buffer[0], BUFFLEN, 0); if (bytesRead > 0) { - std::string &userInput = userInputBuffer_.at(idx); + std::string &userInput = clientInputBuffers_.at(idx); std::string bufferStr(&buffer[0], bytesRead); // bool userInputValid = false; From 2e9a26b183ac331d8aee7696e7d4fcb361e1b754 Mon Sep 17 00:00:00 2001 From: Rapha Date: Fri, 21 Aug 2026 16:30:02 +0200 Subject: [PATCH 027/349] feat: * finish implementation of acceptClient * dual stack ipv4/6 --- srcs/server/IRCServer.cpp | 76 ++++++++++++++++++++++++--------------- 1 file changed, 47 insertions(+), 29 deletions(-) diff --git a/srcs/server/IRCServer.cpp b/srcs/server/IRCServer.cpp index 1abf868..19987d4 100644 --- a/srcs/server/IRCServer.cpp +++ b/srcs/server/IRCServer.cpp @@ -104,9 +104,8 @@ static void bindServerSocket(int const socketFd, int const domain, getDomainStr(domain) + " !"); } -static void storeServerSocket(int const socketFd, - std::vector &pFds, - std::vector &usrInBuff) { +static void storePollFd(int const socketFd, std::vector &pFds, + std::vector &usrInBuff) { pollfd pfd = {}; pfd.fd = socketFd; pfd.events = POLLIN; // Monitor for incoming connections @@ -126,11 +125,17 @@ int IRCServer::createServerSocket_(int const domain) { getDomainStr(domain) + " !"); int optval = 1; - if (setsockopt(socketFd, SOL_SOCKET, SO_REUSEADDR, &optval, + if (setsockopt(socketFd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &optval, sizeof(optval)) != 0) throw std::runtime_error("Error while setting socket options for " + getDomainStr(domain) + " !"); + if (domain == AF_INET6) { + int v6only = 1; + setsockopt(socketFd, IPPROTO_IPV6, IPV6_V6ONLY, &v6only, + sizeof(v6only)); + } + setSocketNonBlock(socketFd); bindServerSocket(socketFd, domain, port_); @@ -139,7 +144,7 @@ int IRCServer::createServerSocket_(int const domain) { throw std::runtime_error("Error while listening on socket for " + getDomainStr(domain) + " !"); - storeServerSocket(socketFd, pollFds_, clientInputBuffers_); + storePollFd(socketFd, pollFds_, clientInputBuffers_); ++numServerSockets_; return socketFd; @@ -152,8 +157,8 @@ int IRCServer::createServerSocket_(int const domain) { will be replaced by an "official" version anyways. */ void IRCServer::acceptClient_(int const serverFd) { - socketAddrX sockAddrIn46; - socklen_t sockLen; + socketAddrX sockAddrIn46 = {}; + socklen_t sockLen = 0; setSocketAddr(serverFd, 0, sockAddrIn46, sockLen); int const clientFd = @@ -165,29 +170,40 @@ void IRCServer::acceptClient_(int const serverFd) { setSocketNonBlock(clientFd); - pollfd clientPollfd = {}; - clientPollfd.fd = clientFd; - clientPollfd.events = POLLIN; - pollFds_.push_back(clientPollfd); - clientInputBuffers_.push_back(std::string()); + storePollFd(clientFd, pollFds_, clientInputBuffers_); + std::cout << "Client connected:\n" << " fd: " << clientFd; - if (sockAddrIn46.sai4.sin_family == AF_INET) - std::cout << "\n AF: " << sockAddrIn46.sai4.sin_family - << "\n addr:port: " << sockAddrIn46.sai4.sin_addr.s_addr - << ":" << sockAddrIn46.sai4.sin_port << std::endl; - else if (sockAddrIn46.sai6.sin6_family == AF_INET6) { - std::cout << "\n AF: " << sockAddrIn46.sai6.sin6_family + + if (sockAddrIn46.sai4.sin_family == AF_INET) { + std::cout << "\n AF: " << getDomainStr(sockAddrIn46.sai4.sin_family) + << "\n addr:port: "; + uint8_t *addrData = + reinterpret_cast(&sockAddrIn46.sai4.sin_addr.s_addr); + for (size_t idx = 0; + idx < sizeof(sockAddrIn46.sai4.sin_addr) / sizeof(uint8_t); + ++idx) { + if (idx > 0) + std::cout << "."; + std::cout << std::dec << static_cast(addrData[idx]); + } + std::cout << ":" << std::dec << ntohs(sockAddrIn46.sai4.sin_port) + << std::endl; + } else if (sockAddrIn46.sai6.sin6_family == AF_INET6) { + std::cout << "\n AF: " << getDomainStr(sockAddrIn46.sai6.sin6_family) << "\n addr:port: ["; + uint8_t *addrData = + reinterpret_cast(&sockAddrIn46.sai6.sin6_addr.s6_addr); for (size_t idx = 0; idx < sizeof(sockAddrIn46.sai6.sin6_addr) / sizeof(uint8_t); ++idx) { if (idx > 0 && idx % 4 == 0) std::cout << ":"; - std::cout << std::hex << sockAddrIn46.sai6.sin6_addr.s6_addr[idx]; + std::cout << std::hex << static_cast(addrData[idx]); } - std::cout << "]:" << sockAddrIn46.sai4.sin_port << std::endl; + std::cout << "]:" << std::dec << ntohs(sockAddrIn46.sai4.sin_port) + << std::endl; } } @@ -231,6 +247,7 @@ void IRCServer::disconnectClient_(size_t const idx) { void IRCServer::run() { createServerSocket_(AF_INET); createServerSocket_(AF_INET6); + //createServerSocket_(AF_INET); while (!SignalHandler::stopLoopSignalReceived()) { usleep(100); @@ -242,16 +259,17 @@ void IRCServer::run() { break; } - struct pollfd &serverPollFds = pollFds_[0]; - if (serverPollFds.revents & POLLERR) { - std::cerr << "Server socket error\n"; - break; - } - - if (serverPollFds.revents & POLLIN) { - acceptClient_(serverPollFds.fd); + for (size_t idx = 0; idx < numServerSockets_; ++idx) { + struct pollfd const &serverPollFds = pollFds_[idx]; + if (serverPollFds.revents & POLLERR) { + std::cerr << "Server socket error\n"; + break; + } + if (serverPollFds.revents & POLLIN) { + acceptClient_(serverPollFds.fd); + } } - for (size_t idx = 1; idx < pollFds_.size(); ++idx) { + for (size_t idx = numServerSockets_; idx < pollFds_.size(); ++idx) { pollfd &pfd = pollFds_.at(idx); if (pfd.revents & POLLERR) { std::cerr << "Client socket error on fd: " << pfd.fd From d764b539bc4b43ce9c0b41817f5b8acada89e0d5 Mon Sep 17 00:00:00 2001 From: Rapha Date: Fri, 21 Aug 2026 16:46:38 +0200 Subject: [PATCH 028/349] clang-tidy: * cleanup * still open: function run simplification --- srcs/server/IRCServer.cpp | 62 +++++++++++++++------------------------ 1 file changed, 24 insertions(+), 38 deletions(-) diff --git a/srcs/server/IRCServer.cpp b/srcs/server/IRCServer.cpp index 19987d4..eaeb166 100644 --- a/srcs/server/IRCServer.cpp +++ b/srcs/server/IRCServer.cpp @@ -39,9 +39,9 @@ static std::string getDomainStr(int const domain) { } static int sockAddrFamily(int const socketFd) { - struct sockaddr sockAddr; + struct sockaddr sockAddr = {}; socklen_t sockLen = sizeof(sockAddr); - if (getsockname(socketFd, &sockAddr, &sockLen) != 0) + if (getsockname(socketFd, &sockAddr, &sockLen) != 0) // NOLINT throw; return sockAddr.sa_family; } @@ -54,7 +54,7 @@ static void setSocketNonBlock(int const socketFd) { getDomainStr(domain) + " !"); } -static void setSocketAddr(int const socketFd, int const port, +static void setSocketAddr(int const socketFd, int const port, // NOLINT socketAddrX &sockAddrIn46, socklen_t &sockLen) { int domain = sockAddrFamily(socketFd); @@ -73,32 +73,14 @@ static void setSocketAddr(int const socketFd, int const port, } } -static void bindServerSocket(int const socketFd, int const domain, +static void bindServerSocket(int const socketFd, int const domain, // NOLINT int const port) { - // struct sockaddr *serverSockaddr = 0; - // socklen_t socklen = 0; - // struct sockaddr_in serverAddr4 = {}; - // struct sockaddr_in6 serverAddr6 = {}; - // if (domain == AF_INET) { - // serverAddr4.sin_family = AF_INET; - // serverAddr4.sin_addr.s_addr = INADDR_ANY; - // serverAddr4.sin_port = htons(port); - // serverSockaddr = - // reinterpret_cast(&serverAddr4); // NOLINT - // socklen = sizeof(serverAddr4); - // } else if (domain == AF_INET6) { - // serverAddr6.sin6_family = AF_INET; - // serverAddr6.sin6_addr = in6addr_any; - // serverAddr6.sin6_port = htons(port); - // serverSockaddr = - // reinterpret_cast(&serverAddr6); // NOLINT - // socklen = sizeof(serverAddr6); - // } - socketAddrX sockAddrIn46; - socklen_t sockLen; + socketAddrX sockAddrIn46 = {}; + socklen_t sockLen = 0; setSocketAddr(socketFd, port, sockAddrIn46, sockLen); - if (bind(socketFd, reinterpret_cast(&sockAddrIn46), + if (bind(socketFd, + reinterpret_cast(&sockAddrIn46), // NOLINT sockLen) < 0) throw std::runtime_error("Error while binding server socket for " + getDomainStr(domain) + " !"); @@ -159,14 +141,17 @@ int IRCServer::createServerSocket_(int const domain) { void IRCServer::acceptClient_(int const serverFd) { socketAddrX sockAddrIn46 = {}; socklen_t sockLen = 0; + setSocketAddr(serverFd, 0, sockAddrIn46, sockLen); - int const clientFd = - accept(serverFd, reinterpret_cast(&sockAddrIn46), - &sockLen); // NOLINT - if (clientFd == -1) - //throw ?? - ; + int const clientFd = accept( + serverFd, reinterpret_cast(&sockAddrIn46), // NOLINT + &sockLen); + if (clientFd == -1) { + // throw -> no everything else can still run w/o problems + // silent return -> cannot sent anything to client without working socket anyways... + return; + } setSocketNonBlock(clientFd); @@ -178,28 +163,28 @@ void IRCServer::acceptClient_(int const serverFd) { if (sockAddrIn46.sai4.sin_family == AF_INET) { std::cout << "\n AF: " << getDomainStr(sockAddrIn46.sai4.sin_family) << "\n addr:port: "; - uint8_t *addrData = - reinterpret_cast(&sockAddrIn46.sai4.sin_addr.s_addr); + uint8_t *addrData = reinterpret_cast( + &sockAddrIn46.sai4.sin_addr.s_addr); // NOLINT for (size_t idx = 0; idx < sizeof(sockAddrIn46.sai4.sin_addr) / sizeof(uint8_t); ++idx) { if (idx > 0) std::cout << "."; - std::cout << std::dec << static_cast(addrData[idx]); + std::cout << std::dec << static_cast(addrData[idx]); // NOLINT } std::cout << ":" << std::dec << ntohs(sockAddrIn46.sai4.sin_port) << std::endl; } else if (sockAddrIn46.sai6.sin6_family == AF_INET6) { std::cout << "\n AF: " << getDomainStr(sockAddrIn46.sai6.sin6_family) << "\n addr:port: ["; - uint8_t *addrData = - reinterpret_cast(&sockAddrIn46.sai6.sin6_addr.s6_addr); + uint8_t *addrData = reinterpret_cast( + &sockAddrIn46.sai6.sin6_addr.s6_addr); // NOLINT for (size_t idx = 0; idx < sizeof(sockAddrIn46.sai6.sin6_addr) / sizeof(uint8_t); ++idx) { if (idx > 0 && idx % 4 == 0) std::cout << ":"; - std::cout << std::hex << static_cast(addrData[idx]); + std::cout << std::hex << static_cast(addrData[idx]); // NOLINT } std::cout << "]:" << std::dec << ntohs(sockAddrIn46.sai4.sin_port) @@ -254,6 +239,7 @@ void IRCServer::run() { int pollResult = poll(pollFds_.data(), pollFds_.size(), 0); if (pollResult == -1) { + throw; // nice error message and exit the loop // or throw, but then have ressource cleanup after catch break; From aa795903b7fe57c13d85a9199b405ae8ea5019ac Mon Sep 17 00:00:00 2001 From: Rapha Date: Sat, 22 Aug 2026 12:00:39 +0200 Subject: [PATCH 029/349] rfa: outsourcing function printAddrfamilyAddrPort --- srcs/server/IRCServer.cpp | 53 ++++++++++++++++----------------------- 1 file changed, 22 insertions(+), 31 deletions(-) diff --git a/srcs/server/IRCServer.cpp b/srcs/server/IRCServer.cpp index eaeb166..7d500ac 100644 --- a/srcs/server/IRCServer.cpp +++ b/srcs/server/IRCServer.cpp @@ -132,6 +132,27 @@ int IRCServer::createServerSocket_(int const domain) { return socketFd; } +static void printAddrfamilyAddrPort(socketAddrX const &sockAddrIn46) { + char addrBuffer[64]; + socklen_t addrBuffLen = sizeof(addrBuffer) / sizeof(char); + + if (sockAddrIn46.sai4.sin_family == AF_INET) { + std::cout << "\n AF: " << getDomainStr(sockAddrIn46.sai4.sin_family) + << "\n addr:port: " + << inet_ntop(AF_INET, &sockAddrIn46.sai4.sin_addr, addrBuffer, + addrBuffLen) + << ":" << std::dec << ntohs(sockAddrIn46.sai4.sin_port) + << std::endl; + } else if (sockAddrIn46.sai6.sin6_family == AF_INET6) { + std::cout << "\n AF: " << getDomainStr(sockAddrIn46.sai6.sin6_family) + << "\n addr:port: [" + << inet_ntop(AF_INET6, &sockAddrIn46.sai6.sin6_addr, + addrBuffer, addrBuffLen) + << "]:" << std::dec << ntohs(sockAddrIn46.sai4.sin_port) + << std::endl; + } +} + /* Dummy function to get a usable client socket on connect for mockup testing. @@ -159,37 +180,7 @@ void IRCServer::acceptClient_(int const serverFd) { std::cout << "Client connected:\n" << " fd: " << clientFd; - - if (sockAddrIn46.sai4.sin_family == AF_INET) { - std::cout << "\n AF: " << getDomainStr(sockAddrIn46.sai4.sin_family) - << "\n addr:port: "; - uint8_t *addrData = reinterpret_cast( - &sockAddrIn46.sai4.sin_addr.s_addr); // NOLINT - for (size_t idx = 0; - idx < sizeof(sockAddrIn46.sai4.sin_addr) / sizeof(uint8_t); - ++idx) { - if (idx > 0) - std::cout << "."; - std::cout << std::dec << static_cast(addrData[idx]); // NOLINT - } - std::cout << ":" << std::dec << ntohs(sockAddrIn46.sai4.sin_port) - << std::endl; - } else if (sockAddrIn46.sai6.sin6_family == AF_INET6) { - std::cout << "\n AF: " << getDomainStr(sockAddrIn46.sai6.sin6_family) - << "\n addr:port: ["; - uint8_t *addrData = reinterpret_cast( - &sockAddrIn46.sai6.sin6_addr.s6_addr); // NOLINT - for (size_t idx = 0; - idx < sizeof(sockAddrIn46.sai6.sin6_addr) / sizeof(uint8_t); - ++idx) { - if (idx > 0 && idx % 4 == 0) - std::cout << ":"; - std::cout << std::hex << static_cast(addrData[idx]); // NOLINT - } - - std::cout << "]:" << std::dec << ntohs(sockAddrIn46.sai4.sin_port) - << std::endl; - } + printAddrfamilyAddrPort(sockAddrIn46); } /* From e4a8590266e3de2ca1047addc3be63ffee58d134 Mon Sep 17 00:00:00 2001 From: Rapha Date: Sat, 22 Aug 2026 13:01:38 +0200 Subject: [PATCH 030/349] dbg: pass signals to ircserv executable --- .vscode/launch.json | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index de935ed..169f487 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -20,7 +20,17 @@ "description": "Enable pretty-printing for gdb", "text": "-enable-pretty-printing", "ignoreFailures": true - } + }, + { + "description": "Pass SIGINT to program", + "text": "handle SIGINT pass nostop", + "ignoreFailures": true + }, + { + "description": "Pass SIGQUIT to program", + "text": "handle SIGQUIT pass nostop", + "ignoreFailures": true + } ] } ] From ce43b75fd8368ccfc176d144cf4d4e70cf8f2893 Mon Sep 17 00:00:00 2001 From: Rapha Date: Sat, 22 Aug 2026 13:02:16 +0200 Subject: [PATCH 031/349] rfa: putting helper functions in their own namespace/file --- includes/server/IRCServerHelper.hpp | 110 ++++++++++++++++++ srcs/server/IRCServer.cpp | 172 ++++++---------------------- 2 files changed, 148 insertions(+), 134 deletions(-) create mode 100644 includes/server/IRCServerHelper.hpp diff --git a/includes/server/IRCServerHelper.hpp b/includes/server/IRCServerHelper.hpp new file mode 100644 index 0000000..01443a0 --- /dev/null +++ b/includes/server/IRCServerHelper.hpp @@ -0,0 +1,110 @@ +#ifndef IRCSERVERHELPER_HPP +#define IRCSERVERHELPER_HPP + +#include +#include +#include + +namespace srvHelp { + +inline std::string getAdressFamilyStr(int const domain) { + if (domain == AF_INET) + return "IPv4"; + if (domain == AF_INET6) + return "IPv6"; + return "Unknown Address Family id"; +} + +inline int sockAddrFamily(int const socketFd) { + struct sockaddr sockAddr = {}; + socklen_t sockLen = sizeof(sockAddr); + if (getsockname(socketFd, &sockAddr, &sockLen) != 0) // NOLINT + throw; + return sockAddr.sa_family; +} + +inline void setSocketNonBlock(int const socketFd) { + int const domain = sockAddrFamily(socketFd); + if (fcntl(socketFd, F_SETFL, O_NONBLOCK) != 0) // NOLINT + throw std::runtime_error( + "Error while setting socket to NON-BLOCKING for " + + getAdressFamilyStr(domain) + " !"); +} + +inline void setSocketAddr(int const socketFd, int const port, // NOLINT + socketAddrX &sockAddrIn46, socklen_t &sockLen) { + int domain = sockAddrFamily(socketFd); + + if (domain == AF_INET) { + struct sockaddr_in &clientSockAddrIn4 = sockAddrIn46.sai4; + clientSockAddrIn4.sin_family = AF_INET; + clientSockAddrIn4.sin_addr.s_addr = INADDR_ANY; + clientSockAddrIn4.sin_port = htons(port); + sockLen = sizeof(clientSockAddrIn4); + } else if (domain == AF_INET6) { + struct sockaddr_in6 &clientSockAddrIn6 = sockAddrIn46.sai6; + clientSockAddrIn6.sin6_family = AF_INET6; + clientSockAddrIn6.sin6_addr = in6addr_any; + clientSockAddrIn6.sin6_port = htons(port); + sockLen = sizeof(clientSockAddrIn6); + } +} + +inline void bindServerSocket(int const socketFd, int const domain, // NOLINT + int const port) { + socketAddrX sockAddrIn46 = {}; + socklen_t sockLen = 0; + setSocketAddr(socketFd, port, sockAddrIn46, sockLen); + + if (bind(socketFd, + reinterpret_cast(&sockAddrIn46), // NOLINT + sockLen) < 0) + throw std::runtime_error("Error while binding server socket for " + + getAdressFamilyStr(domain) + " !"); +} + +inline void storePollFd(int const socketFd, std::vector &pFds, + std::vector &usrInBuff) { + pollfd pfd = {}; + pfd.fd = socketFd; + pfd.events = POLLIN; // Monitor for incoming connections + + pFds.push_back(pfd); + usrInBuff.push_back(std::string()); +} + +inline bool isSocketListening(int sockfd) { + int optVal = 0; + socklen_t optLen = sizeof(optVal); + if (getsockopt(sockfd, SOL_SOCKET, SO_ACCEPTCONN, &optVal, &optLen) < 0) + throw std::runtime_error("Error getting socket options."); + + return (optVal > 0); +} + +inline void printAddrfamilyAddrPort(socketAddrX const &sockAddrIn46) { + char addrBuffer[64]; + socklen_t addrBuffLen = sizeof(addrBuffer) / sizeof(char); + + if (sockAddrIn46.sai4.sin_family == AF_INET) { + std::cout << "\n AF: " + << getAdressFamilyStr(sockAddrIn46.sai4.sin_family) + << "\n addr:port: " + << inet_ntop(AF_INET, &sockAddrIn46.sai4.sin_addr, addrBuffer, + addrBuffLen) + << ":" << std::dec << ntohs(sockAddrIn46.sai4.sin_port) + << std::endl; + } else if (sockAddrIn46.sai6.sin6_family == AF_INET6) { + std::cout << "\n AF: " + << getAdressFamilyStr(sockAddrIn46.sai6.sin6_family) + << "\n addr:port: [" + << inet_ntop(AF_INET6, &sockAddrIn46.sai6.sin6_addr, + addrBuffer, addrBuffLen) + << "]:" << std::dec << ntohs(sockAddrIn46.sai4.sin_port) + << std::endl; + } +} + +} // namespace srvHelp + +#endif // IRCSERVERHELPER_HPP \ No newline at end of file diff --git a/srcs/server/IRCServer.cpp b/srcs/server/IRCServer.cpp index 7d500ac..5ab47d9 100644 --- a/srcs/server/IRCServer.cpp +++ b/srcs/server/IRCServer.cpp @@ -1,12 +1,9 @@ #include "server/IRCServer.hpp" +#include "server/IRCServerHelper.hpp" #include "server/SignalHandler.hpp" -// #include "server/Socket.hpp" -#include -#include #include -#include #include #include #include @@ -18,84 +15,9 @@ IRCServer::IRCServer(int const port, std::string const &password) : port_(port), password_(password), numServerSockets_(0) {} -// /* -// IRCServer Destructor -// * ... -// */ -// IRCServer::~IRCServer() {} - /* - Dummy function to get a usable server socket - for mockup testing. - Implementation explicitely not exhaustive, - will be replaced by an "official" version anyways. + tbd */ -static std::string getDomainStr(int const domain) { - if (domain == AF_INET) - return "IPv4"; - if (domain == AF_INET6) - return "IPv6"; - return ""; -} - -static int sockAddrFamily(int const socketFd) { - struct sockaddr sockAddr = {}; - socklen_t sockLen = sizeof(sockAddr); - if (getsockname(socketFd, &sockAddr, &sockLen) != 0) // NOLINT - throw; - return sockAddr.sa_family; -} - -static void setSocketNonBlock(int const socketFd) { - int const domain = sockAddrFamily(socketFd); - if (fcntl(socketFd, F_SETFL, O_NONBLOCK) != 0) // NOLINT - throw std::runtime_error( - "Error while setting socket to NON-BLOCKING for " + - getDomainStr(domain) + " !"); -} - -static void setSocketAddr(int const socketFd, int const port, // NOLINT - socketAddrX &sockAddrIn46, socklen_t &sockLen) { - int domain = sockAddrFamily(socketFd); - - if (domain == AF_INET) { - struct sockaddr_in &clientSockAddrIn4 = sockAddrIn46.sai4; - clientSockAddrIn4.sin_family = AF_INET; - clientSockAddrIn4.sin_addr.s_addr = INADDR_ANY; - clientSockAddrIn4.sin_port = htons(port); - sockLen = sizeof(clientSockAddrIn4); - } else if (domain == AF_INET6) { - struct sockaddr_in6 &clientSockAddrIn6 = sockAddrIn46.sai6; - clientSockAddrIn6.sin6_family = AF_INET6; - clientSockAddrIn6.sin6_addr = in6addr_any; - clientSockAddrIn6.sin6_port = htons(port); - sockLen = sizeof(clientSockAddrIn6); - } -} - -static void bindServerSocket(int const socketFd, int const domain, // NOLINT - int const port) { - socketAddrX sockAddrIn46 = {}; - socklen_t sockLen = 0; - setSocketAddr(socketFd, port, sockAddrIn46, sockLen); - - if (bind(socketFd, - reinterpret_cast(&sockAddrIn46), // NOLINT - sockLen) < 0) - throw std::runtime_error("Error while binding server socket for " + - getDomainStr(domain) + " !"); -} - -static void storePollFd(int const socketFd, std::vector &pFds, - std::vector &usrInBuff) { - pollfd pfd = {}; - pfd.fd = socketFd; - pfd.events = POLLIN; // Monitor for incoming connections - - pFds.push_back(pfd); - usrInBuff.push_back(std::string()); -} - int IRCServer::createServerSocket_(int const domain) { if (domain != AF_INET && domain != AF_INET6) throw std::invalid_argument( @@ -104,13 +26,13 @@ int IRCServer::createServerSocket_(int const domain) { int socketFd = socket(domain, SOCK_STREAM, 0); if (socketFd < 0) throw std::runtime_error("Error while opening server socket for " + - getDomainStr(domain) + " !"); + srvHelp::getAdressFamilyStr(domain) + " !"); int optval = 1; - if (setsockopt(socketFd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &optval, - sizeof(optval)) != 0) + if (setsockopt(socketFd, SOL_SOCKET, SO_REUSEADDR /* | SO_REUSEPORT */, + &optval, sizeof(optval)) != 0) throw std::runtime_error("Error while setting socket options for " + - getDomainStr(domain) + " !"); + srvHelp::getAdressFamilyStr(domain) + " !"); if (domain == AF_INET6) { int v6only = 1; @@ -118,52 +40,28 @@ int IRCServer::createServerSocket_(int const domain) { sizeof(v6only)); } - setSocketNonBlock(socketFd); + srvHelp::setSocketNonBlock(socketFd); - bindServerSocket(socketFd, domain, port_); + srvHelp::bindServerSocket(socketFd, domain, port_); if (listen(socketFd, MAX_CONNECTIONS) < 0) throw std::runtime_error("Error while listening on socket for " + - getDomainStr(domain) + " !"); + srvHelp::getAdressFamilyStr(domain) + " !"); - storePollFd(socketFd, pollFds_, clientInputBuffers_); + srvHelp::storePollFd(socketFd, pollFds_, clientInputBuffers_); ++numServerSockets_; return socketFd; } -static void printAddrfamilyAddrPort(socketAddrX const &sockAddrIn46) { - char addrBuffer[64]; - socklen_t addrBuffLen = sizeof(addrBuffer) / sizeof(char); - - if (sockAddrIn46.sai4.sin_family == AF_INET) { - std::cout << "\n AF: " << getDomainStr(sockAddrIn46.sai4.sin_family) - << "\n addr:port: " - << inet_ntop(AF_INET, &sockAddrIn46.sai4.sin_addr, addrBuffer, - addrBuffLen) - << ":" << std::dec << ntohs(sockAddrIn46.sai4.sin_port) - << std::endl; - } else if (sockAddrIn46.sai6.sin6_family == AF_INET6) { - std::cout << "\n AF: " << getDomainStr(sockAddrIn46.sai6.sin6_family) - << "\n addr:port: [" - << inet_ntop(AF_INET6, &sockAddrIn46.sai6.sin6_addr, - addrBuffer, addrBuffLen) - << "]:" << std::dec << ntohs(sockAddrIn46.sai4.sin_port) - << std::endl; - } -} - /* - Dummy function to get a usable client socket - on connect for mockup testing. - Implementation explicitely not exhaustive, - will be replaced by an "official" version anyways. + tbd */ void IRCServer::acceptClient_(int const serverFd) { socketAddrX sockAddrIn46 = {}; socklen_t sockLen = 0; - setSocketAddr(serverFd, 0, sockAddrIn46, sockLen); + srvHelp::setSocketAddr(serverFd, 0, sockAddrIn46, sockLen); int const clientFd = accept( serverFd, reinterpret_cast(&sockAddrIn46), // NOLINT @@ -174,32 +72,28 @@ void IRCServer::acceptClient_(int const serverFd) { return; } - setSocketNonBlock(clientFd); + srvHelp::setSocketNonBlock(clientFd); - storePollFd(clientFd, pollFds_, clientInputBuffers_); + srvHelp::storePollFd(clientFd, pollFds_, clientInputBuffers_); std::cout << "Client connected:\n" << " fd: " << clientFd; - printAddrfamilyAddrPort(sockAddrIn46); + srvHelp::printAddrfamilyAddrPort(sockAddrIn46); } /* - Dummy function to disconnet aclient socket - for mockup testing. - Implementation explicitely not exhaustive, - will be replaced by an "official" version anyways. - There is more to be done than just closing and removing - the client socket. + tbd */ void IRCServer::disconnectClient_(size_t const idx) { pollfd &pfd = pollFds_.at(idx); - if (idx == 0) { - if (pollFds_.size() != 1) + if (idx < numServerSockets_) { + if (pollFds_.size() < numServerSockets_) std::cout << "Close clients before closing server!" << std::endl; std::cout << "Closing server" << std::endl; } else - std::cout << "Closing client #" << idx << " (" << pollFds_.size() - 2 + std::cout << "Closing client #" << idx << " (" + << pollFds_.size() - 1 - numServerSockets_ << " clients remaining)" << std::endl; close(pfd.fd); @@ -214,26 +108,36 @@ void IRCServer::disconnectClient_(size_t const idx) { /* Main server loop - * sets up its listening socket + * sets up its listening socket(s) * runs an infinit loop to listen for connections * handles client connect and disconnect scenarios - * on server stop, disconnects all clients and closes - the server socket + * on server stop signal, disconnects all clients and closes + the server socket(s) */ void IRCServer::run() { createServerSocket_(AF_INET); createServerSocket_(AF_INET6); - //createServerSocket_(AF_INET); while (!SignalHandler::stopLoopSignalReceived()) { usleep(100); int pollResult = poll(pollFds_.data(), pollFds_.size(), 0); if (pollResult == -1) { - throw; - // nice error message and exit the loop - // or throw, but then have ressource cleanup after catch - break; + static size_t loopCounter = 0; + static size_t retries = 42; + if (loopCounter % 1000 == 0) { + retries = 42; + // EINTR — The call was interrupted by a signal. This is the most common retriable error. Retrying is standard practice. + // EAGAIN — Temporary resource unavailability (rare with poll(), but possible in some edge cases). + if (errno == EINTR) + continue; + if (errno == EAGAIN && retries > 1) { + retries--; + usleep(10000); + continue; + } + throw std::runtime_error("poll failed too many times."); + } } for (size_t idx = 0; idx < numServerSockets_; ++idx) { From e5cb0ed0ed99f136e4b9bc54086ee2444712a802 Mon Sep 17 00:00:00 2001 From: Rapha Date: Sat, 22 Aug 2026 12:00:39 +0200 Subject: [PATCH 032/349] rfa: outsourcing function printAddrfamilyAddrPort --- srcs/server/IRCServer.cpp | 53 ++++++++++++++++----------------------- 1 file changed, 22 insertions(+), 31 deletions(-) diff --git a/srcs/server/IRCServer.cpp b/srcs/server/IRCServer.cpp index eaeb166..7d500ac 100644 --- a/srcs/server/IRCServer.cpp +++ b/srcs/server/IRCServer.cpp @@ -132,6 +132,27 @@ int IRCServer::createServerSocket_(int const domain) { return socketFd; } +static void printAddrfamilyAddrPort(socketAddrX const &sockAddrIn46) { + char addrBuffer[64]; + socklen_t addrBuffLen = sizeof(addrBuffer) / sizeof(char); + + if (sockAddrIn46.sai4.sin_family == AF_INET) { + std::cout << "\n AF: " << getDomainStr(sockAddrIn46.sai4.sin_family) + << "\n addr:port: " + << inet_ntop(AF_INET, &sockAddrIn46.sai4.sin_addr, addrBuffer, + addrBuffLen) + << ":" << std::dec << ntohs(sockAddrIn46.sai4.sin_port) + << std::endl; + } else if (sockAddrIn46.sai6.sin6_family == AF_INET6) { + std::cout << "\n AF: " << getDomainStr(sockAddrIn46.sai6.sin6_family) + << "\n addr:port: [" + << inet_ntop(AF_INET6, &sockAddrIn46.sai6.sin6_addr, + addrBuffer, addrBuffLen) + << "]:" << std::dec << ntohs(sockAddrIn46.sai4.sin_port) + << std::endl; + } +} + /* Dummy function to get a usable client socket on connect for mockup testing. @@ -159,37 +180,7 @@ void IRCServer::acceptClient_(int const serverFd) { std::cout << "Client connected:\n" << " fd: " << clientFd; - - if (sockAddrIn46.sai4.sin_family == AF_INET) { - std::cout << "\n AF: " << getDomainStr(sockAddrIn46.sai4.sin_family) - << "\n addr:port: "; - uint8_t *addrData = reinterpret_cast( - &sockAddrIn46.sai4.sin_addr.s_addr); // NOLINT - for (size_t idx = 0; - idx < sizeof(sockAddrIn46.sai4.sin_addr) / sizeof(uint8_t); - ++idx) { - if (idx > 0) - std::cout << "."; - std::cout << std::dec << static_cast(addrData[idx]); // NOLINT - } - std::cout << ":" << std::dec << ntohs(sockAddrIn46.sai4.sin_port) - << std::endl; - } else if (sockAddrIn46.sai6.sin6_family == AF_INET6) { - std::cout << "\n AF: " << getDomainStr(sockAddrIn46.sai6.sin6_family) - << "\n addr:port: ["; - uint8_t *addrData = reinterpret_cast( - &sockAddrIn46.sai6.sin6_addr.s6_addr); // NOLINT - for (size_t idx = 0; - idx < sizeof(sockAddrIn46.sai6.sin6_addr) / sizeof(uint8_t); - ++idx) { - if (idx > 0 && idx % 4 == 0) - std::cout << ":"; - std::cout << std::hex << static_cast(addrData[idx]); // NOLINT - } - - std::cout << "]:" << std::dec << ntohs(sockAddrIn46.sai4.sin_port) - << std::endl; - } + printAddrfamilyAddrPort(sockAddrIn46); } /* From 21d78ef5b8c990e68c70224e2bd60285574b2188 Mon Sep 17 00:00:00 2001 From: Rapha Date: Sat, 22 Aug 2026 13:02:16 +0200 Subject: [PATCH 033/349] rfa: putting helper functions in their own namespace/file --- includes/server/IRCServerHelper.hpp | 110 ++++++++++++++++++ srcs/server/IRCServer.cpp | 172 ++++++---------------------- 2 files changed, 148 insertions(+), 134 deletions(-) create mode 100644 includes/server/IRCServerHelper.hpp diff --git a/includes/server/IRCServerHelper.hpp b/includes/server/IRCServerHelper.hpp new file mode 100644 index 0000000..01443a0 --- /dev/null +++ b/includes/server/IRCServerHelper.hpp @@ -0,0 +1,110 @@ +#ifndef IRCSERVERHELPER_HPP +#define IRCSERVERHELPER_HPP + +#include +#include +#include + +namespace srvHelp { + +inline std::string getAdressFamilyStr(int const domain) { + if (domain == AF_INET) + return "IPv4"; + if (domain == AF_INET6) + return "IPv6"; + return "Unknown Address Family id"; +} + +inline int sockAddrFamily(int const socketFd) { + struct sockaddr sockAddr = {}; + socklen_t sockLen = sizeof(sockAddr); + if (getsockname(socketFd, &sockAddr, &sockLen) != 0) // NOLINT + throw; + return sockAddr.sa_family; +} + +inline void setSocketNonBlock(int const socketFd) { + int const domain = sockAddrFamily(socketFd); + if (fcntl(socketFd, F_SETFL, O_NONBLOCK) != 0) // NOLINT + throw std::runtime_error( + "Error while setting socket to NON-BLOCKING for " + + getAdressFamilyStr(domain) + " !"); +} + +inline void setSocketAddr(int const socketFd, int const port, // NOLINT + socketAddrX &sockAddrIn46, socklen_t &sockLen) { + int domain = sockAddrFamily(socketFd); + + if (domain == AF_INET) { + struct sockaddr_in &clientSockAddrIn4 = sockAddrIn46.sai4; + clientSockAddrIn4.sin_family = AF_INET; + clientSockAddrIn4.sin_addr.s_addr = INADDR_ANY; + clientSockAddrIn4.sin_port = htons(port); + sockLen = sizeof(clientSockAddrIn4); + } else if (domain == AF_INET6) { + struct sockaddr_in6 &clientSockAddrIn6 = sockAddrIn46.sai6; + clientSockAddrIn6.sin6_family = AF_INET6; + clientSockAddrIn6.sin6_addr = in6addr_any; + clientSockAddrIn6.sin6_port = htons(port); + sockLen = sizeof(clientSockAddrIn6); + } +} + +inline void bindServerSocket(int const socketFd, int const domain, // NOLINT + int const port) { + socketAddrX sockAddrIn46 = {}; + socklen_t sockLen = 0; + setSocketAddr(socketFd, port, sockAddrIn46, sockLen); + + if (bind(socketFd, + reinterpret_cast(&sockAddrIn46), // NOLINT + sockLen) < 0) + throw std::runtime_error("Error while binding server socket for " + + getAdressFamilyStr(domain) + " !"); +} + +inline void storePollFd(int const socketFd, std::vector &pFds, + std::vector &usrInBuff) { + pollfd pfd = {}; + pfd.fd = socketFd; + pfd.events = POLLIN; // Monitor for incoming connections + + pFds.push_back(pfd); + usrInBuff.push_back(std::string()); +} + +inline bool isSocketListening(int sockfd) { + int optVal = 0; + socklen_t optLen = sizeof(optVal); + if (getsockopt(sockfd, SOL_SOCKET, SO_ACCEPTCONN, &optVal, &optLen) < 0) + throw std::runtime_error("Error getting socket options."); + + return (optVal > 0); +} + +inline void printAddrfamilyAddrPort(socketAddrX const &sockAddrIn46) { + char addrBuffer[64]; + socklen_t addrBuffLen = sizeof(addrBuffer) / sizeof(char); + + if (sockAddrIn46.sai4.sin_family == AF_INET) { + std::cout << "\n AF: " + << getAdressFamilyStr(sockAddrIn46.sai4.sin_family) + << "\n addr:port: " + << inet_ntop(AF_INET, &sockAddrIn46.sai4.sin_addr, addrBuffer, + addrBuffLen) + << ":" << std::dec << ntohs(sockAddrIn46.sai4.sin_port) + << std::endl; + } else if (sockAddrIn46.sai6.sin6_family == AF_INET6) { + std::cout << "\n AF: " + << getAdressFamilyStr(sockAddrIn46.sai6.sin6_family) + << "\n addr:port: [" + << inet_ntop(AF_INET6, &sockAddrIn46.sai6.sin6_addr, + addrBuffer, addrBuffLen) + << "]:" << std::dec << ntohs(sockAddrIn46.sai4.sin_port) + << std::endl; + } +} + +} // namespace srvHelp + +#endif // IRCSERVERHELPER_HPP \ No newline at end of file diff --git a/srcs/server/IRCServer.cpp b/srcs/server/IRCServer.cpp index 7d500ac..5ab47d9 100644 --- a/srcs/server/IRCServer.cpp +++ b/srcs/server/IRCServer.cpp @@ -1,12 +1,9 @@ #include "server/IRCServer.hpp" +#include "server/IRCServerHelper.hpp" #include "server/SignalHandler.hpp" -// #include "server/Socket.hpp" -#include -#include #include -#include #include #include #include @@ -18,84 +15,9 @@ IRCServer::IRCServer(int const port, std::string const &password) : port_(port), password_(password), numServerSockets_(0) {} -// /* -// IRCServer Destructor -// * ... -// */ -// IRCServer::~IRCServer() {} - /* - Dummy function to get a usable server socket - for mockup testing. - Implementation explicitely not exhaustive, - will be replaced by an "official" version anyways. + tbd */ -static std::string getDomainStr(int const domain) { - if (domain == AF_INET) - return "IPv4"; - if (domain == AF_INET6) - return "IPv6"; - return ""; -} - -static int sockAddrFamily(int const socketFd) { - struct sockaddr sockAddr = {}; - socklen_t sockLen = sizeof(sockAddr); - if (getsockname(socketFd, &sockAddr, &sockLen) != 0) // NOLINT - throw; - return sockAddr.sa_family; -} - -static void setSocketNonBlock(int const socketFd) { - int const domain = sockAddrFamily(socketFd); - if (fcntl(socketFd, F_SETFL, O_NONBLOCK) != 0) // NOLINT - throw std::runtime_error( - "Error while setting socket to NON-BLOCKING for " + - getDomainStr(domain) + " !"); -} - -static void setSocketAddr(int const socketFd, int const port, // NOLINT - socketAddrX &sockAddrIn46, socklen_t &sockLen) { - int domain = sockAddrFamily(socketFd); - - if (domain == AF_INET) { - struct sockaddr_in &clientSockAddrIn4 = sockAddrIn46.sai4; - clientSockAddrIn4.sin_family = AF_INET; - clientSockAddrIn4.sin_addr.s_addr = INADDR_ANY; - clientSockAddrIn4.sin_port = htons(port); - sockLen = sizeof(clientSockAddrIn4); - } else if (domain == AF_INET6) { - struct sockaddr_in6 &clientSockAddrIn6 = sockAddrIn46.sai6; - clientSockAddrIn6.sin6_family = AF_INET6; - clientSockAddrIn6.sin6_addr = in6addr_any; - clientSockAddrIn6.sin6_port = htons(port); - sockLen = sizeof(clientSockAddrIn6); - } -} - -static void bindServerSocket(int const socketFd, int const domain, // NOLINT - int const port) { - socketAddrX sockAddrIn46 = {}; - socklen_t sockLen = 0; - setSocketAddr(socketFd, port, sockAddrIn46, sockLen); - - if (bind(socketFd, - reinterpret_cast(&sockAddrIn46), // NOLINT - sockLen) < 0) - throw std::runtime_error("Error while binding server socket for " + - getDomainStr(domain) + " !"); -} - -static void storePollFd(int const socketFd, std::vector &pFds, - std::vector &usrInBuff) { - pollfd pfd = {}; - pfd.fd = socketFd; - pfd.events = POLLIN; // Monitor for incoming connections - - pFds.push_back(pfd); - usrInBuff.push_back(std::string()); -} - int IRCServer::createServerSocket_(int const domain) { if (domain != AF_INET && domain != AF_INET6) throw std::invalid_argument( @@ -104,13 +26,13 @@ int IRCServer::createServerSocket_(int const domain) { int socketFd = socket(domain, SOCK_STREAM, 0); if (socketFd < 0) throw std::runtime_error("Error while opening server socket for " + - getDomainStr(domain) + " !"); + srvHelp::getAdressFamilyStr(domain) + " !"); int optval = 1; - if (setsockopt(socketFd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &optval, - sizeof(optval)) != 0) + if (setsockopt(socketFd, SOL_SOCKET, SO_REUSEADDR /* | SO_REUSEPORT */, + &optval, sizeof(optval)) != 0) throw std::runtime_error("Error while setting socket options for " + - getDomainStr(domain) + " !"); + srvHelp::getAdressFamilyStr(domain) + " !"); if (domain == AF_INET6) { int v6only = 1; @@ -118,52 +40,28 @@ int IRCServer::createServerSocket_(int const domain) { sizeof(v6only)); } - setSocketNonBlock(socketFd); + srvHelp::setSocketNonBlock(socketFd); - bindServerSocket(socketFd, domain, port_); + srvHelp::bindServerSocket(socketFd, domain, port_); if (listen(socketFd, MAX_CONNECTIONS) < 0) throw std::runtime_error("Error while listening on socket for " + - getDomainStr(domain) + " !"); + srvHelp::getAdressFamilyStr(domain) + " !"); - storePollFd(socketFd, pollFds_, clientInputBuffers_); + srvHelp::storePollFd(socketFd, pollFds_, clientInputBuffers_); ++numServerSockets_; return socketFd; } -static void printAddrfamilyAddrPort(socketAddrX const &sockAddrIn46) { - char addrBuffer[64]; - socklen_t addrBuffLen = sizeof(addrBuffer) / sizeof(char); - - if (sockAddrIn46.sai4.sin_family == AF_INET) { - std::cout << "\n AF: " << getDomainStr(sockAddrIn46.sai4.sin_family) - << "\n addr:port: " - << inet_ntop(AF_INET, &sockAddrIn46.sai4.sin_addr, addrBuffer, - addrBuffLen) - << ":" << std::dec << ntohs(sockAddrIn46.sai4.sin_port) - << std::endl; - } else if (sockAddrIn46.sai6.sin6_family == AF_INET6) { - std::cout << "\n AF: " << getDomainStr(sockAddrIn46.sai6.sin6_family) - << "\n addr:port: [" - << inet_ntop(AF_INET6, &sockAddrIn46.sai6.sin6_addr, - addrBuffer, addrBuffLen) - << "]:" << std::dec << ntohs(sockAddrIn46.sai4.sin_port) - << std::endl; - } -} - /* - Dummy function to get a usable client socket - on connect for mockup testing. - Implementation explicitely not exhaustive, - will be replaced by an "official" version anyways. + tbd */ void IRCServer::acceptClient_(int const serverFd) { socketAddrX sockAddrIn46 = {}; socklen_t sockLen = 0; - setSocketAddr(serverFd, 0, sockAddrIn46, sockLen); + srvHelp::setSocketAddr(serverFd, 0, sockAddrIn46, sockLen); int const clientFd = accept( serverFd, reinterpret_cast(&sockAddrIn46), // NOLINT @@ -174,32 +72,28 @@ void IRCServer::acceptClient_(int const serverFd) { return; } - setSocketNonBlock(clientFd); + srvHelp::setSocketNonBlock(clientFd); - storePollFd(clientFd, pollFds_, clientInputBuffers_); + srvHelp::storePollFd(clientFd, pollFds_, clientInputBuffers_); std::cout << "Client connected:\n" << " fd: " << clientFd; - printAddrfamilyAddrPort(sockAddrIn46); + srvHelp::printAddrfamilyAddrPort(sockAddrIn46); } /* - Dummy function to disconnet aclient socket - for mockup testing. - Implementation explicitely not exhaustive, - will be replaced by an "official" version anyways. - There is more to be done than just closing and removing - the client socket. + tbd */ void IRCServer::disconnectClient_(size_t const idx) { pollfd &pfd = pollFds_.at(idx); - if (idx == 0) { - if (pollFds_.size() != 1) + if (idx < numServerSockets_) { + if (pollFds_.size() < numServerSockets_) std::cout << "Close clients before closing server!" << std::endl; std::cout << "Closing server" << std::endl; } else - std::cout << "Closing client #" << idx << " (" << pollFds_.size() - 2 + std::cout << "Closing client #" << idx << " (" + << pollFds_.size() - 1 - numServerSockets_ << " clients remaining)" << std::endl; close(pfd.fd); @@ -214,26 +108,36 @@ void IRCServer::disconnectClient_(size_t const idx) { /* Main server loop - * sets up its listening socket + * sets up its listening socket(s) * runs an infinit loop to listen for connections * handles client connect and disconnect scenarios - * on server stop, disconnects all clients and closes - the server socket + * on server stop signal, disconnects all clients and closes + the server socket(s) */ void IRCServer::run() { createServerSocket_(AF_INET); createServerSocket_(AF_INET6); - //createServerSocket_(AF_INET); while (!SignalHandler::stopLoopSignalReceived()) { usleep(100); int pollResult = poll(pollFds_.data(), pollFds_.size(), 0); if (pollResult == -1) { - throw; - // nice error message and exit the loop - // or throw, but then have ressource cleanup after catch - break; + static size_t loopCounter = 0; + static size_t retries = 42; + if (loopCounter % 1000 == 0) { + retries = 42; + // EINTR — The call was interrupted by a signal. This is the most common retriable error. Retrying is standard practice. + // EAGAIN — Temporary resource unavailability (rare with poll(), but possible in some edge cases). + if (errno == EINTR) + continue; + if (errno == EAGAIN && retries > 1) { + retries--; + usleep(10000); + continue; + } + throw std::runtime_error("poll failed too many times."); + } } for (size_t idx = 0; idx < numServerSockets_; ++idx) { From ce3441ae1da65a3ec85e4cce0fb61d28484abd2e Mon Sep 17 00:00:00 2001 From: Rapha Date: Mon, 24 Aug 2026 13:01:02 +0200 Subject: [PATCH 034/349] rfa: * rename IRCServer to just Server * add include guards to header files --- Makefile | 2 +- includes/IRC.hpp | 2 +- includes/server/{IRCServer.hpp => Server.hpp} | 15 ++++++++++----- .../{IRCServerHelper.hpp => ServerHelper.hpp} | 6 +++--- includes/server/SignalHandler.hpp | 5 +++++ srcs/main.cpp | 2 +- srcs/server/{IRCServer.cpp => Server.cpp} | 16 ++++++++-------- 7 files changed, 29 insertions(+), 19 deletions(-) rename includes/server/{IRCServer.hpp => Server.hpp} (75%) rename includes/server/{IRCServerHelper.hpp => ServerHelper.hpp} (97%) rename srcs/server/{IRCServer.cpp => Server.cpp} (96%) diff --git a/Makefile b/Makefile index 30124d7..842afed 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,7 @@ DEBUG_BUILD_DIR = build_debug SRCS_ROOT = main -SRCS_SERVER = IRCServer \ +SRCS_SERVER = Server \ SignalHandler # Client \ # Channel diff --git a/includes/IRC.hpp b/includes/IRC.hpp index 859afa8..299b816 100644 --- a/includes/IRC.hpp +++ b/includes/IRC.hpp @@ -1,4 +1,4 @@ -#include "server/IRCServer.hpp" +#include "server/Server.hpp" #include "server/SignalHandler.hpp" #include diff --git a/includes/server/IRCServer.hpp b/includes/server/Server.hpp similarity index 75% rename from includes/server/IRCServer.hpp rename to includes/server/Server.hpp index 17b854b..147d7e4 100644 --- a/includes/server/IRCServer.hpp +++ b/includes/server/Server.hpp @@ -1,3 +1,6 @@ +#ifndef SERVER_HPP +#define SERVER_HPP + #include #include #include @@ -7,7 +10,7 @@ #define MAXMSGLEN 512 #define MAX_CONNECTIONS 512 -class IRCServer { +class Server { private: int port_; std::string password_; @@ -16,16 +19,16 @@ class IRCServer { std::vector pollFds_; std::vector clientInputBuffers_; - // IRCServer(IRCServer const &other); - // IRCServer &operator=(IRCServer const &other); + // Server(Server const &other); + // Server &operator=(Server const &other); int createServerSocket_(int const domain); void acceptClient_(int const serverFd); void disconnectClient_(size_t const idx); public: - IRCServer(int const port, std::string const &password); - // ~IRCServer(); + Server(int const port, std::string const &password); + // ~Server(); void run(); }; @@ -34,3 +37,5 @@ typedef union uSocketAddrX { struct sockaddr_in sai4; struct sockaddr_in6 sai6; } socketAddrX; + +#endif // SERVER_HPP \ No newline at end of file diff --git a/includes/server/IRCServerHelper.hpp b/includes/server/ServerHelper.hpp similarity index 97% rename from includes/server/IRCServerHelper.hpp rename to includes/server/ServerHelper.hpp index 01443a0..febb944 100644 --- a/includes/server/IRCServerHelper.hpp +++ b/includes/server/ServerHelper.hpp @@ -1,5 +1,5 @@ -#ifndef IRCSERVERHELPER_HPP -#define IRCSERVERHELPER_HPP +#ifndef SERVERHELPER_HPP +#define SERVERHELPER_HPP #include #include @@ -107,4 +107,4 @@ inline void printAddrfamilyAddrPort(socketAddrX const &sockAddrIn46) { } // namespace srvHelp -#endif // IRCSERVERHELPER_HPP \ No newline at end of file +#endif // SERVERHELPER_HPP \ No newline at end of file diff --git a/includes/server/SignalHandler.hpp b/includes/server/SignalHandler.hpp index eb33a10..53d9949 100644 --- a/includes/server/SignalHandler.hpp +++ b/includes/server/SignalHandler.hpp @@ -1,3 +1,6 @@ +#ifndef SIGNALHANDLER_HPP +#define SIGNALHANDLER_HPP + #include #include @@ -16,3 +19,5 @@ class SignalHandler { ~SignalHandler(); static bool stopLoopSignalReceived(); }; + +#endif // SIGNALHANDLER_HPP \ No newline at end of file diff --git a/srcs/main.cpp b/srcs/main.cpp index 4fce3a3..925d683 100644 --- a/srcs/main.cpp +++ b/srcs/main.cpp @@ -53,7 +53,7 @@ int main(int argc, char **argv) { checkExtractCmdLnArgs(argc, argv, port, password); SignalHandler sigHandler; - IRCServer srv(port, password); + Server srv(port, password); srv.run(); } catch (std::exception const &e) { std::cerr << e.what() << std::endl; diff --git a/srcs/server/IRCServer.cpp b/srcs/server/Server.cpp similarity index 96% rename from srcs/server/IRCServer.cpp rename to srcs/server/Server.cpp index 5ab47d9..b52146f 100644 --- a/srcs/server/IRCServer.cpp +++ b/srcs/server/Server.cpp @@ -1,6 +1,6 @@ -#include "server/IRCServer.hpp" +#include "server/Server.hpp" -#include "server/IRCServerHelper.hpp" +#include "server/ServerHelper.hpp" #include "server/SignalHandler.hpp" #include @@ -9,16 +9,16 @@ #include /* - IRCServer Constructor + Server Constructor * set port and password from command line args */ -IRCServer::IRCServer(int const port, std::string const &password) +Server::Server(int const port, std::string const &password) : port_(port), password_(password), numServerSockets_(0) {} /* tbd */ -int IRCServer::createServerSocket_(int const domain) { +int Server::createServerSocket_(int const domain) { if (domain != AF_INET && domain != AF_INET6) throw std::invalid_argument( "Internal Error while opening server socket."); @@ -57,7 +57,7 @@ int IRCServer::createServerSocket_(int const domain) { /* tbd */ -void IRCServer::acceptClient_(int const serverFd) { +void Server::acceptClient_(int const serverFd) { socketAddrX sockAddrIn46 = {}; socklen_t sockLen = 0; @@ -84,7 +84,7 @@ void IRCServer::acceptClient_(int const serverFd) { /* tbd */ -void IRCServer::disconnectClient_(size_t const idx) { +void Server::disconnectClient_(size_t const idx) { pollfd &pfd = pollFds_.at(idx); if (idx < numServerSockets_) { @@ -114,7 +114,7 @@ void IRCServer::disconnectClient_(size_t const idx) { * on server stop signal, disconnects all clients and closes the server socket(s) */ -void IRCServer::run() { +void Server::run() { createServerSocket_(AF_INET); createServerSocket_(AF_INET6); From 40e52bf95ddd262b9b4ea25fe1e3e8f2e0aa9fae Mon Sep 17 00:00:00 2001 From: Rapha Date: Mon, 24 Aug 2026 15:36:37 +0200 Subject: [PATCH 035/349] rfa: add more subfunctions feat: update client input handling (msg length, buffer clearance) feat: beautify client connect log msg --- includes/server/Server.hpp | 7 +- includes/server/ServerHelper.hpp | 66 ++++++++---- srcs/server/Server.cpp | 172 +++++++++++++------------------ 3 files changed, 120 insertions(+), 125 deletions(-) diff --git a/includes/server/Server.hpp b/includes/server/Server.hpp index 147d7e4..7d4b41d 100644 --- a/includes/server/Server.hpp +++ b/includes/server/Server.hpp @@ -23,7 +23,9 @@ class Server { // Server &operator=(Server const &other); int createServerSocket_(int const domain); + void processServerSocketData_(); void acceptClient_(int const serverFd); + void processClientSocketData_(); void disconnectClient_(size_t const idx); public: @@ -33,9 +35,4 @@ class Server { void run(); }; -typedef union uSocketAddrX { - struct sockaddr_in sai4; - struct sockaddr_in6 sai6; -} socketAddrX; - #endif // SERVER_HPP \ No newline at end of file diff --git a/includes/server/ServerHelper.hpp b/includes/server/ServerHelper.hpp index febb944..87dd226 100644 --- a/includes/server/ServerHelper.hpp +++ b/includes/server/ServerHelper.hpp @@ -4,6 +4,15 @@ #include #include #include +#include +#include +#include +#include + +typedef union uSocketAddrX { + struct sockaddr_in sai4; + struct sockaddr_in6 sai6; +} socketAddrX; namespace srvHelp { @@ -32,7 +41,8 @@ inline void setSocketNonBlock(int const socketFd) { } inline void setSocketAddr(int const socketFd, int const port, // NOLINT - socketAddrX &sockAddrIn46, socklen_t &sockLen) { + socketAddrX &sockAddrIn46, + socklen_t &sockLen) { // NOLINT int domain = sockAddrFamily(socketFd); if (domain == AF_INET) { @@ -63,8 +73,9 @@ inline void bindServerSocket(int const socketFd, int const domain, // NOLINT getAdressFamilyStr(domain) + " !"); } -inline void storePollFd(int const socketFd, std::vector &pFds, - std::vector &usrInBuff) { +inline void storePollFd(int const socketFd, + std::vector &pFds, // NOLINT + std::vector &usrInBuff) { // NOLINT pollfd pfd = {}; pfd.fd = socketFd; pfd.events = POLLIN; // Monitor for incoming connections @@ -82,29 +93,44 @@ inline bool isSocketListening(int sockfd) { return (optVal > 0); } -inline void printAddrfamilyAddrPort(socketAddrX const &sockAddrIn46) { - char addrBuffer[64]; - socklen_t addrBuffLen = sizeof(addrBuffer) / sizeof(char); +inline std::string printAddrfamilyAddrPort(socketAddrX const &sockAddrIn46) { + char addrBuffer[128]; + socklen_t addrBuffLen = sizeof(addrBuffer) / sizeof(char); + struct sockaddr const &socketAddr = + reinterpret_cast(sockAddrIn46.sai4); // NOLINT + char const *const addrPtr = inet_ntop(socketAddr.sa_family, &sockAddrIn46.sai4.sin_addr, &addrBuffer[0], + addrBuffLen); + std::string addrStr(addrPtr?addrPtr:""); + std::ostringstream oss; if (sockAddrIn46.sai4.sin_family == AF_INET) { - std::cout << "\n AF: " - << getAdressFamilyStr(sockAddrIn46.sai4.sin_family) - << "\n addr:port: " - << inet_ntop(AF_INET, &sockAddrIn46.sai4.sin_addr, addrBuffer, - addrBuffLen) - << ":" << std::dec << ntohs(sockAddrIn46.sai4.sin_port) - << std::endl; + oss << " AF: " << getAdressFamilyStr(sockAddrIn46.sai4.sin_family) + << "\n addr:port: " << inet_ntop(socketAddr.sa_family, &sockAddrIn46.sai4.sin_addr, &addrBuffer[0], + addrBuffLen) << ":" << std::dec + << ntohs(sockAddrIn46.sai4.sin_port); } else if (sockAddrIn46.sai6.sin6_family == AF_INET6) { - std::cout << "\n AF: " - << getAdressFamilyStr(sockAddrIn46.sai6.sin6_family) - << "\n addr:port: [" - << inet_ntop(AF_INET6, &sockAddrIn46.sai6.sin6_addr, - addrBuffer, addrBuffLen) - << "]:" << std::dec << ntohs(sockAddrIn46.sai4.sin_port) - << std::endl; + oss << " AF: " << getAdressFamilyStr(sockAddrIn46.sai6.sin6_family) + << "\n addr:port: [" << inet_ntop(socketAddr.sa_family, &sockAddrIn46.sai6.sin6_addr, &addrBuffer[0], + addrBuffLen) << "]:" << std::dec + << ntohs(sockAddrIn46.sai4.sin_port); } + return oss.str(); } +inline bool checkPollFailOK() { + static size_t loopCounter = 0; + static size_t retries = 42; + if (loopCounter % 1000 == 0) + retries = 42; + if (errno == EINTR) + return true; + if (errno == EAGAIN && retries > 1) { + retries--; + usleep(10000); + return true; + } + return false; +} } // namespace srvHelp #endif // SERVERHELPER_HPP \ No newline at end of file diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index b52146f..c1bad9b 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -54,6 +54,66 @@ int Server::createServerSocket_(int const domain) { return socketFd; } +void Server::processServerSocketData_() { + for (size_t idx = 0; idx < numServerSockets_; ++idx) { + struct pollfd const &serverPollFds = pollFds_[idx]; + if ((serverPollFds.revents & POLLERR) && !srvHelp::checkPollFailOK()) + throw std::runtime_error("Server socket error"); + if (serverPollFds.revents & POLLIN) + acceptClient_(serverPollFds.fd); + } +} + +void Server::processClientSocketData_() { + for (size_t idx = numServerSockets_; idx < pollFds_.size(); ++idx) { + pollfd &pfd = pollFds_.at(idx); + if ((pfd.revents & POLLERR) && !srvHelp::checkPollFailOK()) { + std::cerr << "Client socket error on fd: " << pfd.fd + << "Disconnect client." << std::endl; + disconnectClient_(idx); + } else if (pfd.revents & POLLIN) { + char buffer[BUFFLEN]; + ssize_t bytesRead = recv(pfd.fd, &buffer[0], BUFFLEN, 0); + + if (bytesRead > 0) { + std::string &userInput = clientInputBuffers_.at(idx); + std::string bufferStr(&buffer[0], bytesRead); + + userInput += bufferStr; + size_t lfcrPos = userInput.find("\r\n"); + if (lfcrPos == std::string::npos) { + continue; + } + if (lfcrPos > MAXMSGLEN - 2) { + std::cerr << "Client data error: message too " + "long.\nData sent will be discarded." + << std::endl; + userInput.erase(0, lfcrPos + 2); + continue; + } + // DEBUG + { + std::cout << "UserInput read from client " << idx << ": \n" + << std::string(userInput) /* << std::endl */; + userInput.clear(); + } + } else if (bytesRead <= 0) { + // Comment will be deleted later, kept for reference for now + // this means, the client closed the connection + // POLLHUP isn't useful, because: + // When you kill netcat with Ctrl+C: + // Netcat's OS kernel sent a FIN packet to your server + // Your server's poll() detected this with POLLIN + // Your read() returned 0 (zero bytes) == "I got the FIN, connection closed" + // FIN is the TCP mechanism for orderly shutdown. + // That's why POLLIN + read() return 0—the FIN, + // and the zero read is how the kernel tells you "the peer closed." + disconnectClient_(idx); + } + } + } +} + /* tbd */ @@ -77,8 +137,9 @@ void Server::acceptClient_(int const serverFd) { srvHelp::storePollFd(clientFd, pollFds_, clientInputBuffers_); std::cout << "Client connected:\n" - << " fd: " << clientFd; - srvHelp::printAddrfamilyAddrPort(sockAddrIn46); + << " fd: " << clientFd << "\n" + << srvHelp::printAddrfamilyAddrPort(sockAddrIn46) + << std::endl; } /* @@ -113,6 +174,11 @@ void Server::disconnectClient_(size_t const idx) { * handles client connect and disconnect scenarios * on server stop signal, disconnects all clients and closes the server socket(s) + * if poll returns an error -> exception is thrown, except for two scenarios: + EINTR — The call was interrupted by a signal. This is the most common + retriable error. Retrying is standard practice. + EAGAIN — Temporary resource unavailability (rare with poll(), + but possible in some edge cases). */ void Server::run() { createServerSocket_(AF_INET); @@ -122,105 +188,11 @@ void Server::run() { usleep(100); int pollResult = poll(pollFds_.data(), pollFds_.size(), 0); - if (pollResult == -1) { - static size_t loopCounter = 0; - static size_t retries = 42; - if (loopCounter % 1000 == 0) { - retries = 42; - // EINTR — The call was interrupted by a signal. This is the most common retriable error. Retrying is standard practice. - // EAGAIN — Temporary resource unavailability (rare with poll(), but possible in some edge cases). - if (errno == EINTR) - continue; - if (errno == EAGAIN && retries > 1) { - retries--; - usleep(10000); - continue; - } - throw std::runtime_error("poll failed too many times."); - } - } + if (pollResult == -1 && !srvHelp::checkPollFailOK()) + throw std::runtime_error("poll failed too many times."); - for (size_t idx = 0; idx < numServerSockets_; ++idx) { - struct pollfd const &serverPollFds = pollFds_[idx]; - if (serverPollFds.revents & POLLERR) { - std::cerr << "Server socket error\n"; - break; - } - if (serverPollFds.revents & POLLIN) { - acceptClient_(serverPollFds.fd); - } - } - for (size_t idx = numServerSockets_; idx < pollFds_.size(); ++idx) { - pollfd &pfd = pollFds_.at(idx); - if (pfd.revents & POLLERR) { - std::cerr << "Client socket error on fd: " << pfd.fd - << std::endl; - disconnectClient_(idx); - } else if (pfd.revents & POLLIN) { - char buffer[BUFFLEN]; - ssize_t bytesRead = recv(pfd.fd, &buffer[0], BUFFLEN, 0); - - if (bytesRead > 0) { - std::string &userInput = clientInputBuffers_.at(idx); - std::string bufferStr(&buffer[0], bytesRead); - // bool userInputValid = false; - - // // DEBUG for cont'd messages - // if (!userInput.empty() && *userInput.rbegin() != '\r') { - // *userInput.rbegin() = '&'; - // } - userInput += bufferStr; - size_t lfcrPos = userInput.find("\r\n"); - if (userInput.length() >= MAXMSGLEN) { - if (lfcrPos > MAXMSGLEN - 2) { - std::cerr << "Client data error: message too " - "long.\nData sent will be discarded." - << std::endl; - while (bytesRead > 0) - bytesRead = - recv(pfd.fd, &buffer[0], BUFFLEN, 0); - userInput.clear(); - continue; - } - } else if (lfcrPos == std::string::npos) { - continue; - } else if (lfcrPos < userInput.length() - 2) { - std::cerr << "Client data error: message " - "malformed, data after CR-LF.\nExtraneous " - "data will be discarded." - << std::endl; - userInput.resize(lfcrPos + 2); - } - // if (lfcrPos == userInput.length() - 2) - // userInputValid = true; - // else { - // std::cerr << "What's going on here?" << std::endl; - // // userInput += bufferStr.substr(0, lfcrPos + 2); - // } - // if (userInputValid) - { - std::cout << "UserInput read from client " << idx - << ": \n" - << std::string(userInput) /* << std::endl */; - userInput.clear(); - } - // } - } - if (bytesRead <= 0) { - // Comment will be deleted later, kept for reference for now - // this means, the client closed the connection - // POLLHUP isn't useful, because - // When you kill netcat with Ctrl+C: - // Netcat's OS kernel sent a FIN packet to your server - // Your server's poll() detected this with POLLIN - // Your read() returned 0 (zero bytes) = "I got the FIN, connection closed" - // FIN is the TCP mechanism for orderly shutdown. - // That's why POLLIN + read() return 0—the FIN, - // and the zero read is how the kernel tells you "the peer closed." - disconnectClient_(idx); - } - } - } + processServerSocketData_(); + processClientSocketData_(); } while (!pollFds_.empty()) { disconnectClient_(pollFds_.size() - 1); From 8921266b7592d0d3369d85dfd53cee39ab70a2da Mon Sep 17 00:00:00 2001 From: Rapha Date: Mon, 24 Aug 2026 15:50:46 +0200 Subject: [PATCH 036/349] rfa: cleanup client debug log msg (dysfunctional) --- includes/server/ServerHelper.hpp | 37 +++++++++++++++++--------------- srcs/server/Server.cpp | 5 ++--- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/includes/server/ServerHelper.hpp b/includes/server/ServerHelper.hpp index 87dd226..3f959dd 100644 --- a/includes/server/ServerHelper.hpp +++ b/includes/server/ServerHelper.hpp @@ -94,26 +94,29 @@ inline bool isSocketListening(int sockfd) { } inline std::string printAddrfamilyAddrPort(socketAddrX const &sockAddrIn46) { - char addrBuffer[128]; - socklen_t addrBuffLen = sizeof(addrBuffer) / sizeof(char); - struct sockaddr const &socketAddr = - reinterpret_cast(sockAddrIn46.sai4); // NOLINT - char const *const addrPtr = inet_ntop(socketAddr.sa_family, &sockAddrIn46.sai4.sin_addr, &addrBuffer[0], - addrBuffLen); - std::string addrStr(addrPtr?addrPtr:""); + char addrBuffer[128]; + socklen_t const addrBuffLen = sizeof(addrBuffer) / sizeof(char); + + sa_family_t const saFamily = + reinterpret_cast(sockAddrIn46.sai4) // NOLINT + .sa_family; + void const *addrPtr = 0; + uint16_t portNumber = 0; - std::ostringstream oss; if (sockAddrIn46.sai4.sin_family == AF_INET) { - oss << " AF: " << getAdressFamilyStr(sockAddrIn46.sai4.sin_family) - << "\n addr:port: " << inet_ntop(socketAddr.sa_family, &sockAddrIn46.sai4.sin_addr, &addrBuffer[0], - addrBuffLen) << ":" << std::dec - << ntohs(sockAddrIn46.sai4.sin_port); - } else if (sockAddrIn46.sai6.sin6_family == AF_INET6) { - oss << " AF: " << getAdressFamilyStr(sockAddrIn46.sai6.sin6_family) - << "\n addr:port: [" << inet_ntop(socketAddr.sa_family, &sockAddrIn46.sai6.sin6_addr, &addrBuffer[0], - addrBuffLen) << "]:" << std::dec - << ntohs(sockAddrIn46.sai4.sin_port); + addrPtr = &sockAddrIn46.sai4.sin_addr; + portNumber = ntohs(sockAddrIn46.sai4.sin_port); + } else if (sockAddrIn46.sai4.sin_family == AF_INET) { + addrPtr = &sockAddrIn46.sai4.sin_addr; + portNumber = ntohs(sockAddrIn46.sai6.sin6_port); } + char const *addrStrPtr = + inet_ntop(saFamily, addrPtr, &addrBuffer[0], addrBuffLen); + addrStrPtr = 0; + std::string addrStr(addrStrPtr?addrStrPtr:"nil"); + std::ostringstream oss; + oss << " AF: " << getAdressFamilyStr(saFamily) + << "\n addr:port: " << addrStrPtr << ":" << std::dec << portNumber; return oss.str(); } diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index c1bad9b..3e84eb4 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -138,8 +138,7 @@ void Server::acceptClient_(int const serverFd) { std::cout << "Client connected:\n" << " fd: " << clientFd << "\n" - << srvHelp::printAddrfamilyAddrPort(sockAddrIn46) - << std::endl; + << srvHelp::printAddrfamilyAddrPort(sockAddrIn46) << std::endl; } /* @@ -192,7 +191,7 @@ void Server::run() { throw std::runtime_error("poll failed too many times."); processServerSocketData_(); - processClientSocketData_(); + processClientSocketData_(); } while (!pollFds_.empty()) { disconnectClient_(pollFds_.size() - 1); From a298bd01ed016dd5683c71352da1813ac3d525b2 Mon Sep 17 00:00:00 2001 From: Rapha Date: Mon, 24 Aug 2026 17:08:33 +0200 Subject: [PATCH 037/349] feat: cleanaup and beautify client info print fnctn --- includes/server/ServerHelper.hpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/includes/server/ServerHelper.hpp b/includes/server/ServerHelper.hpp index 3f959dd..683f09b 100644 --- a/includes/server/ServerHelper.hpp +++ b/includes/server/ServerHelper.hpp @@ -103,20 +103,22 @@ inline std::string printAddrfamilyAddrPort(socketAddrX const &sockAddrIn46) { void const *addrPtr = 0; uint16_t portNumber = 0; - if (sockAddrIn46.sai4.sin_family == AF_INET) { + if (saFamily == AF_INET) { addrPtr = &sockAddrIn46.sai4.sin_addr; portNumber = ntohs(sockAddrIn46.sai4.sin_port); - } else if (sockAddrIn46.sai4.sin_family == AF_INET) { - addrPtr = &sockAddrIn46.sai4.sin_addr; + } else if (saFamily == AF_INET6) { + addrPtr = &sockAddrIn46.sai6.sin6_addr; portNumber = ntohs(sockAddrIn46.sai6.sin6_port); } char const *addrStrPtr = inet_ntop(saFamily, addrPtr, &addrBuffer[0], addrBuffLen); - addrStrPtr = 0; - std::string addrStr(addrStrPtr?addrStrPtr:"nil"); + std::string addrStr(addrStrPtr ? addrStrPtr : ""); + if (saFamily == AF_INET6) + addrStr = "[" + addrStr + "]"; + std::ostringstream oss; oss << " AF: " << getAdressFamilyStr(saFamily) - << "\n addr:port: " << addrStrPtr << ":" << std::dec << portNumber; + << "\n addr:port: " << addrStr << ":" << std::dec << portNumber; return oss.str(); } From 7abf59e558a8990066e6197e48c3c538b2101a83 Mon Sep 17 00:00:00 2001 From: Rapha Date: Mon, 24 Aug 2026 17:09:18 +0200 Subject: [PATCH 038/349] feat: add simple sendMsgToClient method for initial testing --- includes/server/Server.hpp | 1 + srcs/server/Server.cpp | 39 +++++++++++++++++++++++++++++++------- 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/includes/server/Server.hpp b/includes/server/Server.hpp index 7d4b41d..41f1437 100644 --- a/includes/server/Server.hpp +++ b/includes/server/Server.hpp @@ -33,6 +33,7 @@ class Server { // ~Server(); void run(); + bool sendMsg(int fd, std::string const &msg); }; #endif // SERVER_HPP \ No newline at end of file diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 3e84eb4..18aea01 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -15,6 +15,23 @@ Server::Server(int const port, std::string const &password) : port_(port), password_(password), numServerSockets_(0) {} +/* + Can anyone send sth outside of the poll loop -> No, right? + Then I'd always know, what fd/client I am currently talking to. + And could just check the sendBuffer and if it's not empty, + send whatever is there + Except for during exception (class Error) handling, when a potential + msg is part of the exception +*/ +bool Server::sendMsg(int fd, std::string const &msg) { + if (send(fd, msg.c_str(), msg.length(), MSG_NOSIGNAL) == -1) { + std::cerr << "Send failed to FD " << fd << std::endl; + return false; + } + std::cout << "Msg sent to FD: " << fd << ": " << msg; + return true; +} + /* tbd */ @@ -93,9 +110,13 @@ void Server::processClientSocketData_() { } // DEBUG { - std::cout << "UserInput read from client " << idx << ": \n" - << std::string(userInput) /* << std::endl */; + std::ostringstream oss; + oss << idx + 1 - numServerSockets_ << ": \n\t" + << std::string(userInput) /* << std::endl */; userInput.clear(); + std::cout << "UserInput read from client #" << oss.str(); + // Pong to client + sendMsg(pfd.fd, "You are client #" + oss.str()); } } else if (bytesRead <= 0) { // Comment will be deleted later, kept for reference for now @@ -148,12 +169,16 @@ void Server::disconnectClient_(size_t const idx) { pollfd &pfd = pollFds_.at(idx); if (idx < numServerSockets_) { - if (pollFds_.size() < numServerSockets_) - std::cout << "Close clients before closing server!" << std::endl; - std::cout << "Closing server" << std::endl; + if (pollFds_.size() != idx + 1) + std::cout << "Close client sockets before closing server!" + << std::endl; + std::cout << "Closing server socket (" + << srvHelp::getAdressFamilyStr( + srvHelp::sockAddrFamily(pfd.fd)) + << ")" << std::endl; } else - std::cout << "Closing client #" << idx << " (" - << pollFds_.size() - 1 - numServerSockets_ + std::cout << "Closing client #" << idx + 1 - numServerSockets_ + << " socket (" << pollFds_.size() - 1 - numServerSockets_ << " clients remaining)" << std::endl; close(pfd.fd); From 3ee38fe059ecefc7b58108765eef135ba59936d0 Mon Sep 17 00:00:00 2001 From: Rapha Date: Mon, 24 Aug 2026 17:08:33 +0200 Subject: [PATCH 039/349] feat: cleanaup and beautify client info print fnctn --- includes/server/ServerHelper.hpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/includes/server/ServerHelper.hpp b/includes/server/ServerHelper.hpp index 3f959dd..683f09b 100644 --- a/includes/server/ServerHelper.hpp +++ b/includes/server/ServerHelper.hpp @@ -103,20 +103,22 @@ inline std::string printAddrfamilyAddrPort(socketAddrX const &sockAddrIn46) { void const *addrPtr = 0; uint16_t portNumber = 0; - if (sockAddrIn46.sai4.sin_family == AF_INET) { + if (saFamily == AF_INET) { addrPtr = &sockAddrIn46.sai4.sin_addr; portNumber = ntohs(sockAddrIn46.sai4.sin_port); - } else if (sockAddrIn46.sai4.sin_family == AF_INET) { - addrPtr = &sockAddrIn46.sai4.sin_addr; + } else if (saFamily == AF_INET6) { + addrPtr = &sockAddrIn46.sai6.sin6_addr; portNumber = ntohs(sockAddrIn46.sai6.sin6_port); } char const *addrStrPtr = inet_ntop(saFamily, addrPtr, &addrBuffer[0], addrBuffLen); - addrStrPtr = 0; - std::string addrStr(addrStrPtr?addrStrPtr:"nil"); + std::string addrStr(addrStrPtr ? addrStrPtr : ""); + if (saFamily == AF_INET6) + addrStr = "[" + addrStr + "]"; + std::ostringstream oss; oss << " AF: " << getAdressFamilyStr(saFamily) - << "\n addr:port: " << addrStrPtr << ":" << std::dec << portNumber; + << "\n addr:port: " << addrStr << ":" << std::dec << portNumber; return oss.str(); } From 39d5848b439a79854bd00358fa83b16c10e811d1 Mon Sep 17 00:00:00 2001 From: Rapha Date: Mon, 24 Aug 2026 17:09:18 +0200 Subject: [PATCH 040/349] feat: add simple sendMsgToClient method for initial testing --- includes/server/Server.hpp | 1 + srcs/server/Server.cpp | 39 +++++++++++++++++++++++++++++++------- 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/includes/server/Server.hpp b/includes/server/Server.hpp index 7d4b41d..41f1437 100644 --- a/includes/server/Server.hpp +++ b/includes/server/Server.hpp @@ -33,6 +33,7 @@ class Server { // ~Server(); void run(); + bool sendMsg(int fd, std::string const &msg); }; #endif // SERVER_HPP \ No newline at end of file diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 3e84eb4..58bd8dc 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -15,6 +15,23 @@ Server::Server(int const port, std::string const &password) : port_(port), password_(password), numServerSockets_(0) {} +/* + Can anyone send sth outside of the poll loop -> No, right? + Then I'd always know, what fd/client I am currently talking to. + And could just check the sendBuffer and if it's not empty, + send whatever is there + Except for during exception (class Error) handling, when a potential + msg is part of the exception +*/ +bool Server::sendMsg(int fd, std::string const &msg) { + if (send(fd, msg.c_str(), msg.length(), MSG_NOSIGNAL) == -1) { + std::cerr << "Send failed to FD " << fd << std::endl; + return false; + } + std::cout << "Msg sent to FD: " << fd << ": " << msg; + return true; +} + /* tbd */ @@ -93,9 +110,13 @@ void Server::processClientSocketData_() { } // DEBUG { - std::cout << "UserInput read from client " << idx << ": \n" - << std::string(userInput) /* << std::endl */; + std::ostringstream oss; + oss << idx + 1 - numServerSockets_ << ": \n\t" + << std::string(userInput) /* << std::endl */; userInput.clear(); + std::cout << "UserInput read from client #" << oss.str(); + // Pong to client + sendMsg(pfd.fd, "You are client #" + oss.str()); } } else if (bytesRead <= 0) { // Comment will be deleted later, kept for reference for now @@ -148,12 +169,16 @@ void Server::disconnectClient_(size_t const idx) { pollfd &pfd = pollFds_.at(idx); if (idx < numServerSockets_) { - if (pollFds_.size() < numServerSockets_) - std::cout << "Close clients before closing server!" << std::endl; - std::cout << "Closing server" << std::endl; + if (pollFds_.size() != idx + 1) + std::cout << "Close client sockets before closing server!" + << std::endl; + std::cout << "Closing server socket (" + << srvHelp::getAdressFamilyStr( + srvHelp::sockAddrFamily(pfd.fd)) + << ")" << std::endl; } else - std::cout << "Closing client #" << idx << " (" - << pollFds_.size() - 1 - numServerSockets_ + std::cout << "Closing client #" << idx + 1 - numServerSockets_ + << " socket (" << pollFds_.size() - 1 - numServerSockets_ << " clients remaining)" << std::endl; close(pfd.fd); From 38d2a8f7dffcf2177e594407d725eae04dd5a361 Mon Sep 17 00:00:00 2001 From: Rapha Date: Mon, 24 Aug 2026 17:19:56 +0200 Subject: [PATCH 041/349] build: * remove unused file IRC.hpp * move all header files from subdirs to includes/ --- includes/IRC.hpp | 5 ----- includes/{server => }/Server.hpp | 2 ++ includes/{server => }/ServerHelper.hpp | 0 includes/{server => }/SignalHandler.hpp | 0 srcs/main.cpp | 3 ++- srcs/server/Server.cpp | 6 +++--- srcs/server/SignalHandler.cpp | 2 +- 7 files changed, 8 insertions(+), 10 deletions(-) delete mode 100644 includes/IRC.hpp rename includes/{server => }/Server.hpp (96%) rename includes/{server => }/ServerHelper.hpp (100%) rename includes/{server => }/SignalHandler.hpp (100%) diff --git a/includes/IRC.hpp b/includes/IRC.hpp deleted file mode 100644 index 299b816..0000000 --- a/includes/IRC.hpp +++ /dev/null @@ -1,5 +0,0 @@ -#include "server/Server.hpp" -#include "server/SignalHandler.hpp" - -#include -#include \ No newline at end of file diff --git a/includes/server/Server.hpp b/includes/Server.hpp similarity index 96% rename from includes/server/Server.hpp rename to includes/Server.hpp index 41f1437..b3dde2c 100644 --- a/includes/server/Server.hpp +++ b/includes/Server.hpp @@ -1,6 +1,8 @@ #ifndef SERVER_HPP #define SERVER_HPP +#include +#include #include #include #include diff --git a/includes/server/ServerHelper.hpp b/includes/ServerHelper.hpp similarity index 100% rename from includes/server/ServerHelper.hpp rename to includes/ServerHelper.hpp diff --git a/includes/server/SignalHandler.hpp b/includes/SignalHandler.hpp similarity index 100% rename from includes/server/SignalHandler.hpp rename to includes/SignalHandler.hpp diff --git a/srcs/main.cpp b/srcs/main.cpp index 925d683..0a8e35d 100644 --- a/srcs/main.cpp +++ b/srcs/main.cpp @@ -1,4 +1,5 @@ -#include "IRC.hpp" +#include "Server.hpp" +#include "SignalHandler.hpp" #include #include diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 58bd8dc..7deb854 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -1,7 +1,7 @@ -#include "server/Server.hpp" +#include "Server.hpp" -#include "server/ServerHelper.hpp" -#include "server/SignalHandler.hpp" +#include "ServerHelper.hpp" +#include "SignalHandler.hpp" #include #include diff --git a/srcs/server/SignalHandler.cpp b/srcs/server/SignalHandler.cpp index d5b9ae4..42c4263 100644 --- a/srcs/server/SignalHandler.cpp +++ b/srcs/server/SignalHandler.cpp @@ -1,4 +1,4 @@ -#include "server/SignalHandler.hpp" +#include "SignalHandler.hpp" #include #include From d125e9e20a03a7af5b926f2dcbbdf5df8172b6d9 Mon Sep 17 00:00:00 2001 From: Rapha Date: Tue, 25 Aug 2026 11:24:14 +0200 Subject: [PATCH 042/349] build: add typedef in header, bc sighandler_t isn't defined on MacOS --- includes/SignalHandler.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/includes/SignalHandler.hpp b/includes/SignalHandler.hpp index 53d9949..eacc770 100644 --- a/includes/SignalHandler.hpp +++ b/includes/SignalHandler.hpp @@ -4,6 +4,8 @@ #include #include +typedef void (*sighandler_t) (int); + class SignalHandler { private: volatile static std::sig_atomic_t stopServerLoop_; From 75041339724cd3cab78f086666b5d245ae356614 Mon Sep 17 00:00:00 2001 From: wenceslao <117309234+numorb@users.noreply.github.com> Date: Tue, 25 Aug 2026 12:53:36 +0200 Subject: [PATCH 043/349] Header missing --- includes/ServerHelper.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/includes/ServerHelper.hpp b/includes/ServerHelper.hpp index 683f09b..928e012 100644 --- a/includes/ServerHelper.hpp +++ b/includes/ServerHelper.hpp @@ -2,6 +2,7 @@ #define SERVERHELPER_HPP #include +#include #include #include #include From dc9407688cb67287f81af4db9946d75c573e5c7e Mon Sep 17 00:00:00 2001 From: Rapha Date: Tue, 25 Aug 2026 12:59:49 +0200 Subject: [PATCH 044/349] fix: remove unused function with forbidden external function --- includes/ServerHelper.hpp | 9 --------- 1 file changed, 9 deletions(-) diff --git a/includes/ServerHelper.hpp b/includes/ServerHelper.hpp index 683f09b..ecc01a6 100644 --- a/includes/ServerHelper.hpp +++ b/includes/ServerHelper.hpp @@ -84,15 +84,6 @@ inline void storePollFd(int const socketFd, usrInBuff.push_back(std::string()); } -inline bool isSocketListening(int sockfd) { - int optVal = 0; - socklen_t optLen = sizeof(optVal); - if (getsockopt(sockfd, SOL_SOCKET, SO_ACCEPTCONN, &optVal, &optLen) < 0) - throw std::runtime_error("Error getting socket options."); - - return (optVal > 0); -} - inline std::string printAddrfamilyAddrPort(socketAddrX const &sockAddrIn46) { char addrBuffer[128]; socklen_t const addrBuffLen = sizeof(addrBuffer) / sizeof(char); From 5856001b673ce2cfa18fd23ecb7086174cfeb4ff Mon Sep 17 00:00:00 2001 From: Rapha Date: Tue, 25 Aug 2026 13:00:31 +0200 Subject: [PATCH 045/349] fix: remove sleep. not necessary with a timeout of -1 in poll --- srcs/server/Server.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 7deb854..1feb249 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -209,9 +209,7 @@ void Server::run() { createServerSocket_(AF_INET6); while (!SignalHandler::stopLoopSignalReceived()) { - usleep(100); - - int pollResult = poll(pollFds_.data(), pollFds_.size(), 0); + int pollResult = poll(pollFds_.data(), pollFds_.size(), -1); if (pollResult == -1 && !srvHelp::checkPollFailOK()) throw std::runtime_error("poll failed too many times."); From c0b10a3e53f28a4376dd1b7c58b20952a2ca94d4 Mon Sep 17 00:00:00 2001 From: Rapha Date: Tue, 25 Aug 2026 14:33:58 +0200 Subject: [PATCH 046/349] rfa: rename checkPoll to checkSocket --- includes/ServerHelper.hpp | 3 ++- srcs/server/Server.cpp | 10 ++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/includes/ServerHelper.hpp b/includes/ServerHelper.hpp index 683f09b..93c2ba8 100644 --- a/includes/ServerHelper.hpp +++ b/includes/ServerHelper.hpp @@ -122,7 +122,8 @@ inline std::string printAddrfamilyAddrPort(socketAddrX const &sockAddrIn46) { return oss.str(); } -inline bool checkPollFailOK() { +inline bool checkSocketFailOK() { + //return SignalHandler::stopLoopSignalReceived(); static size_t loopCounter = 0; static size_t retries = 42; if (loopCounter % 1000 == 0) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 7deb854..c3cc322 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -74,7 +74,7 @@ int Server::createServerSocket_(int const domain) { void Server::processServerSocketData_() { for (size_t idx = 0; idx < numServerSockets_; ++idx) { struct pollfd const &serverPollFds = pollFds_[idx]; - if ((serverPollFds.revents & POLLERR) && !srvHelp::checkPollFailOK()) + if ((serverPollFds.revents & POLLERR) && !srvHelp::checkSocketFailOK()) throw std::runtime_error("Server socket error"); if (serverPollFds.revents & POLLIN) acceptClient_(serverPollFds.fd); @@ -84,7 +84,7 @@ void Server::processServerSocketData_() { void Server::processClientSocketData_() { for (size_t idx = numServerSockets_; idx < pollFds_.size(); ++idx) { pollfd &pfd = pollFds_.at(idx); - if ((pfd.revents & POLLERR) && !srvHelp::checkPollFailOK()) { + if ((pfd.revents & POLLERR) && !srvHelp::checkSocketFailOK()) { std::cerr << "Client socket error on fd: " << pfd.fd << "Disconnect client." << std::endl; disconnectClient_(idx); @@ -209,10 +209,8 @@ void Server::run() { createServerSocket_(AF_INET6); while (!SignalHandler::stopLoopSignalReceived()) { - usleep(100); - - int pollResult = poll(pollFds_.data(), pollFds_.size(), 0); - if (pollResult == -1 && !srvHelp::checkPollFailOK()) + int pollResult = poll(pollFds_.data(), pollFds_.size(), -1); + if (pollResult == -1 && !srvHelp::checkSocketFailOK()) throw std::runtime_error("poll failed too many times."); processServerSocketData_(); From e630a071e41b94b453d44be4aa278dc555996c04 Mon Sep 17 00:00:00 2001 From: Rapha Date: Tue, 25 Aug 2026 14:48:49 +0200 Subject: [PATCH 047/349] fix: resolve remaining merge changes manually rfa: use #pragma once instead of include guards --- includes/Server.hpp | 26 +++++--------------------- includes/ServerHelper.hpp | 7 ++----- 2 files changed, 7 insertions(+), 26 deletions(-) diff --git a/includes/Server.hpp b/includes/Server.hpp index 3cbe815..8096b74 100644 --- a/includes/Server.hpp +++ b/includes/Server.hpp @@ -1,28 +1,9 @@ #pragma once -// draft: to be completed by rhaas - #include "ChannelManager.hpp" #include "ClientManager.hpp" #include "CommandProcessor.hpp" -#define SERVER_NAME "irc.local" - -class Server { - ChannelManager channels_; - ClientManager clients_; - CommandProcessor cmds_; - int port_; - std::string password_; - - public: - Server(int port, const std::string &password); - - bool tryRegister(Client &client); -}; -#ifndef SERVER_HPP -#define SERVER_HPP - #include #include #include @@ -30,12 +11,16 @@ class Server { #include #include +#define SERVER_NAME "irc.local" #define BUFFLEN 516 #define MAXMSGLEN 512 #define MAX_CONNECTIONS 512 class Server { private: + ChannelManager channels_; + ClientManager clients_; + CommandProcessor cmds_; int port_; std::string password_; @@ -58,6 +43,5 @@ class Server { void run(); bool sendMsg(int fd, std::string const &msg); + bool tryRegister(Client &client); }; - -#endif // SERVER_HPP \ No newline at end of file diff --git a/includes/ServerHelper.hpp b/includes/ServerHelper.hpp index 80c7bc9..ed397d5 100644 --- a/includes/ServerHelper.hpp +++ b/includes/ServerHelper.hpp @@ -1,5 +1,4 @@ -#ifndef SERVERHELPER_HPP -#define SERVERHELPER_HPP +#pragma once #include #include @@ -124,7 +123,7 @@ inline std::string printAddrfamilyAddrPort(socketAddrX const &sockAddrIn46) { } inline bool checkSocketFailOK() { - //return SignalHandler::stopLoopSignalReceived(); + //return SignalHandler::stopLoopSignalReceived(); static size_t loopCounter = 0; static size_t retries = 42; if (loopCounter % 1000 == 0) @@ -139,5 +138,3 @@ inline bool checkSocketFailOK() { return false; } } // namespace srvHelp - -#endif // SERVERHELPER_HPP \ No newline at end of file From 82be3b64862f43c966803b1e8ac834d6e23508c4 Mon Sep 17 00:00:00 2001 From: Rapha Date: Tue, 25 Aug 2026 17:22:27 +0200 Subject: [PATCH 048/349] build: add missing srcs from this branch --- Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index ddd7a05..0d53f4f 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,8 @@ SRCS_SERVER = Server \ Client \ ClientManager \ Channel \ - ChannelManager + ChannelManager \ + SignalHandler SRCS_COMMANDS = CommandProcessor \ Handlers From d24f503551c2482e09d8988a23171a6727f0c5a7 Mon Sep 17 00:00:00 2001 From: Rapha Date: Tue, 25 Aug 2026 17:26:20 +0200 Subject: [PATCH 049/349] fix: change to size_t to avoid error when comparing to a std size_t type --- includes/CommandProcessor.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/includes/CommandProcessor.hpp b/includes/CommandProcessor.hpp index 101b0f7..1bcdde8 100644 --- a/includes/CommandProcessor.hpp +++ b/includes/CommandProcessor.hpp @@ -11,9 +11,9 @@ typedef bool (*Handler)(Server &server, Client &client, std::vector ¶ms); class Command { - bool usablePreReg_; - int minParams_; - bool requiresOp_; + bool usablePreReg_; + size_t minParams_; + bool requiresOp_; public: Handler handler; @@ -27,7 +27,7 @@ class Command { return usablePreReg_; } - int getMinParams() const { + size_t getMinParams() const { return minParams_; } From eca651d476e678e1d2b15cda9b778f88759b2d52 Mon Sep 17 00:00:00 2001 From: Rapha Date: Tue, 25 Aug 2026 17:27:14 +0200 Subject: [PATCH 050/349] fix: add (void) for all unused parameters (due to missing implementation), in order to make things compile --- srcs/commands/CommandProcessor.cpp | 3 + srcs/commands/Handlers.cpp | 154 +++++++++++++++++++++++++++++ srcs/server/Channel.cpp | 4 +- srcs/server/Client.cpp | 2 + 4 files changed, 162 insertions(+), 1 deletion(-) diff --git a/srcs/commands/CommandProcessor.cpp b/srcs/commands/CommandProcessor.cpp index 3162547..7e4e8fa 100644 --- a/srcs/commands/CommandProcessor.cpp +++ b/srcs/commands/CommandProcessor.cpp @@ -15,6 +15,9 @@ Command &CommandProcessor::at_(const std::string &name) { // TODO blnunez- bool CommandProcessor::parse(const std::string &line, std::string &cmd, std::vector ¶ms) { + (void)line; + (void)cmd; + (void)params; return false; } diff --git a/srcs/commands/Handlers.cpp b/srcs/commands/Handlers.cpp index 6fa58b0..8fa5f40 100644 --- a/srcs/commands/Handlers.cpp +++ b/srcs/commands/Handlers.cpp @@ -2,3 +2,157 @@ #include "Client.hpp" #include "Server.hpp" + +bool Handlers::cap(Server &server, Client &client, std::vector ¶ms) { + (void)server; + (void)client; + (void)params; + return false; +} + +bool Handlers::invite(Server &server, Client &client, std::vector ¶ms) { + (void)server; + (void)client; + (void)params; + return false; +} + +bool Handlers::join(Server &server, Client &client, std::vector ¶ms) { + (void)server; + (void)client; + (void)params; + return false; +} + +bool Handlers::kick(Server &server, Client &client, std::vector ¶ms) { + (void)server; + (void)client; + (void)params; + return false; +} + +bool Handlers::kill(Server &server, Client &client, std::vector ¶ms) { + (void)server; + (void)client; + (void)params; + return false; +} + +bool Handlers::mode(Server &server, Client &client, std::vector ¶ms) { + (void)server; + (void)client; + (void)params; + return false; +} + +bool Handlers::names(Server &server, Client &client, std::vector ¶ms) { + (void)server; + (void)client; + (void)params; + return false; +} + +bool Handlers::nick(Server &server, Client &client, std::vector ¶ms) { + (void)server; + (void)client; + (void)params; + return false; +} + +bool Handlers::message(Server &server, Client &client, std::vector ¶ms) { + (void)server; + (void)client; + (void)params; + return false; +} + +bool Handlers::oper(Server &server, Client &client, std::vector ¶ms) { + (void)server; + (void)client; + (void)params; + return false; +} + +bool Handlers::part(Server &server, Client &client, std::vector ¶ms) { + (void)server; + (void)client; + (void)params; + return false; +} + +bool Handlers::pass(Server &server, Client &client, std::vector ¶ms) { + (void)server; + (void)client; + (void)params; + return false; +} + +bool Handlers::ping(Server &server, Client &client, std::vector ¶ms) { + (void)server; + (void)client; + (void)params; + return false; +} + +bool Handlers::pong(Server &server, Client &client, std::vector ¶ms) { + (void)server; + (void)client; + (void)params; + return false; +} + +bool Handlers::quit(Server &server, Client &client, std::vector ¶ms) { + (void)server; + (void)client; + (void)params; + return false; +} + +bool Handlers::rehash(Server &server, Client &client, std::vector ¶ms) { + (void)server; + (void)client; + (void)params; + return false; +} + +bool Handlers::rename(Server &server, Client &client, std::vector ¶ms) { + (void)server; + (void)client; + (void)params; + return false; +} + +bool Handlers::topic(Server &server, Client &client, std::vector ¶ms) { + (void)server; + (void)client; + (void)params; + return false; +} + +bool Handlers::user(Server &server, Client &client, std::vector ¶ms) { + (void)server; + (void)client; + (void)params; + return false; +} + +bool Handlers::version(Server &server, Client &client, std::vector ¶ms) { + (void)server; + (void)client; + (void)params; + return false; +} + +bool Handlers::who(Server &server, Client &client, std::vector ¶ms) { + (void)server; + (void)client; + (void)params; + return false; +} + +bool Handlers::whois(Server &server, Client &client, std::vector ¶ms) { + (void)server; + (void)client; + (void)params; + return false; +} diff --git a/srcs/server/Channel.cpp b/srcs/server/Channel.cpp index 70819d9..ad37bc3 100644 --- a/srcs/server/Channel.cpp +++ b/srcs/server/Channel.cpp @@ -10,7 +10,9 @@ Channel::Channel(const std::string &name, const std::string &nameCaseFolded, const std::string &key) : name_(name), nameCaseFolded_(nameCaseFolded), key_(key), userLimit_(-1), - createdTime_(std::time(NULL)), topicSetTime_() {} + createdTime_(std::time(NULL)), topicSetTime_() { + (void)topicSetTime_; +} // Join causes `client` to join the channel named `name`, creating it if needed. void Channel::join(Client *client, const std::string &key) { diff --git a/srcs/server/Client.cpp b/srcs/server/Client.cpp index 4caef79..5008e06 100644 --- a/srcs/server/Client.cpp +++ b/srcs/server/Client.cpp @@ -1,3 +1,5 @@ #include "Client.hpp" // TODO blnunez- + +void Client::send(const std::string &msg) {(void)msg;} From a77601502bb592a68b5d2659d2e98e7bb8babd58 Mon Sep 17 00:00:00 2001 From: Rapha Date: Tue, 25 Aug 2026 17:27:39 +0200 Subject: [PATCH 051/349] feat: new Logger class --- includes/Logger.hpp | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 includes/Logger.hpp diff --git a/includes/Logger.hpp b/includes/Logger.hpp new file mode 100644 index 0000000..aabe315 --- /dev/null +++ b/includes/Logger.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include +#include +#include +#include + +enum logMode { lmInfo, lmError }; + +class Logger { + std::string prefix_; + + std::string nowStr_() const { + time_t now = time(0); + struct tm *timeinfo = localtime(&now); + char buffer[32]; + strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", timeinfo); + return std::string(buffer); + } + + public: + Logger(std::string const &prefix) { + prefix_ = prefix; + } + + void logMsg(std::string const &msg, logMode const lm) const { + if (lm == (logMode)lmInfo) + std::cout << nowStr_() << " " << prefix_ << "[INFO] : " << msg + << std::endl; + if (lm == (logMode)lmError) + std::cerr << nowStr_() << " " << prefix_ << "[ERROR]: " << msg + << std::endl; + } +}; From 8a093f317da3300b25781512969735ce96c63acd Mon Sep 17 00:00:00 2001 From: Rapha Date: Tue, 25 Aug 2026 17:30:19 +0200 Subject: [PATCH 052/349] feat: * partly use new logger * add struct for SocketIOData (buffers, error mngmt) * rework cmd extraction from socket stream * use new interface to hand over an extracted cmd line to the cmd handler --- includes/Server.hpp | 51 ++++++++++++++++++++++++---------- includes/ServerHelper.hpp | 10 ++++--- srcs/server/Server.cpp | 58 +++++++++++++++++++++++++++------------ 3 files changed, 82 insertions(+), 37 deletions(-) diff --git a/includes/Server.hpp b/includes/Server.hpp index 8096b74..88c3151 100644 --- a/includes/Server.hpp +++ b/includes/Server.hpp @@ -3,6 +3,7 @@ #include "ChannelManager.hpp" #include "ClientManager.hpp" #include "CommandProcessor.hpp" +#include "Logger.hpp" #include #include @@ -12,36 +13,56 @@ #include #define SERVER_NAME "irc.local" -#define BUFFLEN 516 +#define RECVBUFFLEN 516 #define MAXMSGLEN 512 #define MAX_CONNECTIONS 512 +struct SocketIOData { + std::string readBuffer; + std::string writeBuffer; + size_t errorCount; + bool discardMode; + + SocketIOData() : errorCount(0), discardMode(false) {} +}; + class Server { private: - ChannelManager channels_; - ClientManager clients_; - CommandProcessor cmds_; - int port_; - std::string password_; + ChannelManager channels_; + ClientManager clients_; + CommandProcessor cmds_; + + int port_; + std::string password_; + std::string serverName_; - size_t numServerSockets_; - std::vector pollFds_; - std::vector clientInputBuffers_; + size_t numServerSockets_; + std::vector pollFds_; + // std::vector clientInputBuffers_; + std::map sockIODataStore_; + + Logger logger_; // Server(Server const &other); // Server &operator=(Server const &other); - int createServerSocket_(int const domain); - void processServerSocketData_(); - void acceptClient_(int const serverFd); - void processClientSocketData_(); - void disconnectClient_(size_t const idx); + int createServerSocket_(int const domain); + void processServerSocketData_(); + void acceptClient_(int const serverFd); + void processClientSocketData_(); + void disconnectClient_(size_t const idx); + void log_(std::string const &msg, logMode const lm); public: Server(int const port, std::string const &password); + // ~Server(); + std::string const &serverName() { + return serverName_; + }; + void run(); bool sendMsg(int fd, std::string const &msg); - bool tryRegister(Client &client); + bool tryRegister(Client &client){(void) client; return false;}; }; diff --git a/includes/ServerHelper.hpp b/includes/ServerHelper.hpp index ed397d5..cc30dcb 100644 --- a/includes/ServerHelper.hpp +++ b/includes/ServerHelper.hpp @@ -73,15 +73,17 @@ inline void bindServerSocket(int const socketFd, int const domain, // NOLINT getAdressFamilyStr(domain) + " !"); } -inline void storePollFd(int const socketFd, - std::vector &pFds, // NOLINT - std::vector &usrInBuff) { // NOLINT +inline void +storePollFd(int const socketFd, + std::vector &pFds, // NOLINT + std::map &sockIODataStore) { // NOLINT pollfd pfd = {}; pfd.fd = socketFd; pfd.events = POLLIN; // Monitor for incoming connections pFds.push_back(pfd); - usrInBuff.push_back(std::string()); + sockIODataStore.insert( + std::make_pair(pfd.fd, SocketIOData())); } inline bool isSocketListening(int sockfd) { diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index c3cc322..3cc6117 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -1,5 +1,6 @@ #include "Server.hpp" +#include "Errors.hpp" #include "ServerHelper.hpp" #include "SignalHandler.hpp" @@ -13,7 +14,8 @@ * set port and password from command line args */ Server::Server(int const port, std::string const &password) - : port_(port), password_(password), numServerSockets_(0) {} + : port_(port), password_(password), serverName_(SERVER_NAME), + numServerSockets_(0), logger_(serverName_ + ": ") {} /* Can anyone send sth outside of the poll loop -> No, right? @@ -65,7 +67,7 @@ int Server::createServerSocket_(int const domain) { throw std::runtime_error("Error while listening on socket for " + srvHelp::getAdressFamilyStr(domain) + " !"); - srvHelp::storePollFd(socketFd, pollFds_, clientInputBuffers_); + srvHelp::storePollFd(socketFd, pollFds_, sockIODataStore_); ++numServerSockets_; return socketFd; @@ -89,23 +91,30 @@ void Server::processClientSocketData_() { << "Disconnect client." << std::endl; disconnectClient_(idx); } else if (pfd.revents & POLLIN) { - char buffer[BUFFLEN]; - ssize_t bytesRead = recv(pfd.fd, &buffer[0], BUFFLEN, 0); + SocketIOData &sockIOData = sockIODataStore_.at(pfd.fd); + char recvBuffer[RECVBUFFLEN]; + ssize_t bytesRead = recv(pfd.fd, &recvBuffer[0], RECVBUFFLEN, 0); if (bytesRead > 0) { - std::string &userInput = clientInputBuffers_.at(idx); - std::string bufferStr(&buffer[0], bytesRead); + std::string &userInput = sockIOData.readBuffer; + userInput += std::string(&recvBuffer[0], bytesRead); - userInput += bufferStr; size_t lfcrPos = userInput.find("\r\n"); - if (lfcrPos == std::string::npos) { - continue; + if ((userInput.length() >= MAXMSGLEN) && + (lfcrPos == std::string::npos || lfcrPos > MAXMSGLEN - 2)) { + sockIOData.discardMode = true; + } + if (sockIOData.discardMode) { + if (lfcrPos != std::string::npos) { + userInput.erase(0, lfcrPos + 2); + sockIOData.discardMode = false; + lfcrPos = userInput.find("\r\n"); + } else if ('\r' == *userInput.rbegin()) + userInput = "\r"; + else + userInput.clear(); } - if (lfcrPos > MAXMSGLEN - 2) { - std::cerr << "Client data error: message too " - "long.\nData sent will be discarded." - << std::endl; - userInput.erase(0, lfcrPos + 2); + if (lfcrPos == std::string::npos || sockIOData.discardMode) { continue; } // DEBUG @@ -118,6 +127,17 @@ void Server::processClientSocketData_() { // Pong to client sendMsg(pfd.fd, "You are client #" + oss.str()); } + try { + cmds_.run(*this, *clients_.at(pfd.fd), userInput); + } catch (Error const &e) { + if (e.hasReply()) { + Client const &c = *clients_.at(pfd.fd); + sendMsg(pfd.fd, Reply::build(serverName(), c.getNick(), + e.reply())); + } else + log_(e.what(), (logMode)lmError); + } + } else if (bytesRead <= 0) { // Comment will be deleted later, kept for reference for now // this means, the client closed the connection @@ -155,7 +175,7 @@ void Server::acceptClient_(int const serverFd) { srvHelp::setSocketNonBlock(clientFd); - srvHelp::storePollFd(clientFd, pollFds_, clientInputBuffers_); + srvHelp::storePollFd(clientFd, pollFds_, sockIODataStore_); std::cout << "Client connected:\n" << " fd: " << clientFd << "\n" @@ -185,9 +205,11 @@ void Server::disconnectClient_(size_t const idx) { std::vector::difference_type dist = static_cast::difference_type>(idx); pollFds_.erase(pollFds_.begin() + dist); - clientInputBuffers_.erase( - clientInputBuffers_.begin() + - static_cast::difference_type>(dist)); + sockIODataStore_.erase(pfd.fd); +} + +void Server::log_(std::string const &msg, logMode const lm) { + logger_.logMsg(msg, lm); } /* From 5a8e8716508a730b32e292df9791fcc96cdefd5d Mon Sep 17 00:00:00 2001 From: Rapha Date: Tue, 25 Aug 2026 17:32:10 +0200 Subject: [PATCH 053/349] rfa: clang-format --- includes/Server.hpp | 6 +++- srcs/commands/Handlers.cpp | 66 +++++++++++++++++++++++++------------- srcs/server/Client.cpp | 4 ++- 3 files changed, 52 insertions(+), 24 deletions(-) diff --git a/includes/Server.hpp b/includes/Server.hpp index 88c3151..356eb5e 100644 --- a/includes/Server.hpp +++ b/includes/Server.hpp @@ -64,5 +64,9 @@ class Server { void run(); bool sendMsg(int fd, std::string const &msg); - bool tryRegister(Client &client){(void) client; return false;}; + + bool tryRegister(Client &client) { + (void)client; + return false; + }; }; diff --git a/srcs/commands/Handlers.cpp b/srcs/commands/Handlers.cpp index 8fa5f40..e8575bb 100644 --- a/srcs/commands/Handlers.cpp +++ b/srcs/commands/Handlers.cpp @@ -3,154 +3,176 @@ #include "Client.hpp" #include "Server.hpp" -bool Handlers::cap(Server &server, Client &client, std::vector ¶ms) { +bool Handlers::cap(Server &server, Client &client, + std::vector ¶ms) { (void)server; (void)client; (void)params; return false; } -bool Handlers::invite(Server &server, Client &client, std::vector ¶ms) { +bool Handlers::invite(Server &server, Client &client, + std::vector ¶ms) { (void)server; (void)client; (void)params; return false; } -bool Handlers::join(Server &server, Client &client, std::vector ¶ms) { +bool Handlers::join(Server &server, Client &client, + std::vector ¶ms) { (void)server; (void)client; (void)params; return false; } -bool Handlers::kick(Server &server, Client &client, std::vector ¶ms) { +bool Handlers::kick(Server &server, Client &client, + std::vector ¶ms) { (void)server; (void)client; (void)params; return false; } -bool Handlers::kill(Server &server, Client &client, std::vector ¶ms) { +bool Handlers::kill(Server &server, Client &client, + std::vector ¶ms) { (void)server; (void)client; (void)params; return false; } -bool Handlers::mode(Server &server, Client &client, std::vector ¶ms) { +bool Handlers::mode(Server &server, Client &client, + std::vector ¶ms) { (void)server; (void)client; (void)params; return false; } -bool Handlers::names(Server &server, Client &client, std::vector ¶ms) { +bool Handlers::names(Server &server, Client &client, + std::vector ¶ms) { (void)server; (void)client; (void)params; return false; } -bool Handlers::nick(Server &server, Client &client, std::vector ¶ms) { +bool Handlers::nick(Server &server, Client &client, + std::vector ¶ms) { (void)server; (void)client; (void)params; return false; } -bool Handlers::message(Server &server, Client &client, std::vector ¶ms) { +bool Handlers::message(Server &server, Client &client, + std::vector ¶ms) { (void)server; (void)client; (void)params; return false; } -bool Handlers::oper(Server &server, Client &client, std::vector ¶ms) { +bool Handlers::oper(Server &server, Client &client, + std::vector ¶ms) { (void)server; (void)client; (void)params; return false; } -bool Handlers::part(Server &server, Client &client, std::vector ¶ms) { +bool Handlers::part(Server &server, Client &client, + std::vector ¶ms) { (void)server; (void)client; (void)params; return false; } -bool Handlers::pass(Server &server, Client &client, std::vector ¶ms) { +bool Handlers::pass(Server &server, Client &client, + std::vector ¶ms) { (void)server; (void)client; (void)params; return false; } -bool Handlers::ping(Server &server, Client &client, std::vector ¶ms) { +bool Handlers::ping(Server &server, Client &client, + std::vector ¶ms) { (void)server; (void)client; (void)params; return false; } -bool Handlers::pong(Server &server, Client &client, std::vector ¶ms) { +bool Handlers::pong(Server &server, Client &client, + std::vector ¶ms) { (void)server; (void)client; (void)params; return false; } -bool Handlers::quit(Server &server, Client &client, std::vector ¶ms) { +bool Handlers::quit(Server &server, Client &client, + std::vector ¶ms) { (void)server; (void)client; (void)params; return false; } -bool Handlers::rehash(Server &server, Client &client, std::vector ¶ms) { +bool Handlers::rehash(Server &server, Client &client, + std::vector ¶ms) { (void)server; (void)client; (void)params; return false; } -bool Handlers::rename(Server &server, Client &client, std::vector ¶ms) { +bool Handlers::rename(Server &server, Client &client, + std::vector ¶ms) { (void)server; (void)client; (void)params; return false; } -bool Handlers::topic(Server &server, Client &client, std::vector ¶ms) { +bool Handlers::topic(Server &server, Client &client, + std::vector ¶ms) { (void)server; (void)client; (void)params; return false; } -bool Handlers::user(Server &server, Client &client, std::vector ¶ms) { +bool Handlers::user(Server &server, Client &client, + std::vector ¶ms) { (void)server; (void)client; (void)params; return false; } -bool Handlers::version(Server &server, Client &client, std::vector ¶ms) { +bool Handlers::version(Server &server, Client &client, + std::vector ¶ms) { (void)server; (void)client; (void)params; return false; } -bool Handlers::who(Server &server, Client &client, std::vector ¶ms) { +bool Handlers::who(Server &server, Client &client, + std::vector ¶ms) { (void)server; (void)client; (void)params; return false; } -bool Handlers::whois(Server &server, Client &client, std::vector ¶ms) { +bool Handlers::whois(Server &server, Client &client, + std::vector ¶ms) { (void)server; (void)client; (void)params; diff --git a/srcs/server/Client.cpp b/srcs/server/Client.cpp index 5008e06..991007b 100644 --- a/srcs/server/Client.cpp +++ b/srcs/server/Client.cpp @@ -2,4 +2,6 @@ // TODO blnunez- -void Client::send(const std::string &msg) {(void)msg;} +void Client::send(const std::string &msg) { + (void)msg; +} From 9314e516497a28b219e32e31ae49d9b90ed279a4 Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 26 Aug 2026 16:38:44 +0200 Subject: [PATCH 054/349] feat: * return IRCv3 error code ERR_INPUTTOOLONG * improve sendMsg with error handling and logging * add colours for logger output * control log verbosity via VERBOSE_LOG * rework disconnectClient: it's more a closeSocket --- Makefile | 4 +- includes/Logger.hpp | 6 +- includes/Reply.hpp | 3 + includes/Server.hpp | 12 +++- includes/colours.hpp | 24 +++++++ srcs/server/Server.cpp | 144 +++++++++++++++++++++++++++-------------- 6 files changed, 137 insertions(+), 56 deletions(-) create mode 100644 includes/colours.hpp diff --git a/Makefile b/Makefile index 0d53f4f..2c5981b 100644 --- a/Makefile +++ b/Makefile @@ -48,9 +48,9 @@ DEPS = $(OBJS:.o=.d) $(DEBUG_OBJS:.o=.d) # ---- Compiler and Flags ------------------------------------------------------ CXX = c++ -CXXFLAGS = -Wall -Wextra -Werror -pedantic -std=c++98 +CXXFLAGS = -Wall -Wextra -Werror -pedantic -std=c++98 -DVERBOSE_LOG=0 IFLAGS = -I$(INC_DIR) -DFLAGS = -DDEBUG -g -fsanitize=address +DFLAGS = -DDEBUG -DVERBOSE_LOG=1 -g -fsanitize=address DEBUG_CXXFLAGS = $(CXXFLAGS) $(DFLAGS) # ---- Rules ------------------------------------------------------------------- diff --git a/includes/Logger.hpp b/includes/Logger.hpp index aabe315..eb4db05 100644 --- a/includes/Logger.hpp +++ b/includes/Logger.hpp @@ -1,5 +1,7 @@ #pragma once +#include "colours.hpp" + #include #include #include @@ -28,7 +30,7 @@ class Logger { std::cout << nowStr_() << " " << prefix_ << "[INFO] : " << msg << std::endl; if (lm == (logMode)lmError) - std::cerr << nowStr_() << " " << prefix_ << "[ERROR]: " << msg - << std::endl; + std::cerr << nowStr_() << " " << prefix_ << CRed + << "[ERROR]: " << msg << CReset << std::endl; } }; diff --git a/includes/Reply.hpp b/includes/Reply.hpp index 386c1de..f58953e 100644 --- a/includes/Reply.hpp +++ b/includes/Reply.hpp @@ -85,6 +85,7 @@ enum Code { ERR_NOORIGIN = 409, // :No origin specified (PING/PONG) ERR_NORECIPIENT = 411, // :No recipient given () (PRIVMSG) ERR_NOTEXTTOSEND = 412, // :No text to send (PRIVMSG) + ERR_INPUTTOOLONG = 417, // :Input line was too long ERR_UNKNOWNCOMMAND = 421, // :Unknown command ERR_NOMOTD = 422, // :MOTD File is missing ERR_NONICKNAMEGIVEN = 431, // :No nickname given (NICK) @@ -139,6 +140,8 @@ inline const char *text(Code code) { return "No origin specified"; case ERR_NOTEXTTOSEND: return "No text to send"; + case ERR_INPUTTOOLONG: + return "Input line was too long"; case ERR_UNKNOWNCOMMAND: return "Unknown command"; case ERR_NOMOTD: diff --git a/includes/Server.hpp b/includes/Server.hpp index 356eb5e..605cedb 100644 --- a/includes/Server.hpp +++ b/includes/Server.hpp @@ -13,9 +13,15 @@ #include #define SERVER_NAME "irc.local" +#define MAX_CONNECTIONS 512 + #define RECVBUFFLEN 516 #define MAXMSGLEN 512 -#define MAX_CONNECTIONS 512 +#define SOCKETERRORLIMIT 16 + +#ifndef VERBOSE_LOG +#define VERBOSE_LOG 1 +#endif struct SocketIOData { std::string readBuffer; @@ -50,7 +56,7 @@ class Server { void processServerSocketData_(); void acceptClient_(int const serverFd); void processClientSocketData_(); - void disconnectClient_(size_t const idx); + void closeSocket_(int const fd); void log_(std::string const &msg, logMode const lm); public: @@ -63,7 +69,7 @@ class Server { }; void run(); - bool sendMsg(int fd, std::string const &msg); + bool sendMsg(int const fd, std::string const &msg); bool tryRegister(Client &client) { (void)client; diff --git a/includes/colours.hpp b/includes/colours.hpp new file mode 100644 index 0000000..f0715a9 --- /dev/null +++ b/includes/colours.hpp @@ -0,0 +1,24 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* colours.hpp :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: raphha +#+ +:+ +#+ */ +/* +#+#+#+#+#+ +#+ */ +/* Created: 2026/03/18 11:26:12 by rhaas #+# #+# */ +/* Updated: 2026/08/26 15:11:20 by raphha ### ########.fr */ +/* */ +/* ************************************************************************** */ + +// Color FG BG +#define CBlack "\x1B[30m" // 40 +#define CRed "\x1B[31m" // 41 +#define CGreen "\x1B[32m" // 42 +#define CYellow "\x1B[33m" // 43 +#define CBlue "\x1B[34m" // 44 +#define CMgnta "\x1B[35m" // 45 +#define CCyan "\x1B[36m" // 46 +#define CWhite "\x1B[37m" // 47 +#define CDeflt "\x1B[39m" // 49 + +#define CReset "\x1B[0m" \ No newline at end of file diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 3cc6117..8e00e1b 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -17,20 +17,51 @@ Server::Server(int const port, std::string const &password) : port_(port), password_(password), serverName_(SERVER_NAME), numServerSockets_(0), logger_(serverName_ + ": ") {} -/* - Can anyone send sth outside of the poll loop -> No, right? - Then I'd always know, what fd/client I am currently talking to. - And could just check the sendBuffer and if it's not empty, - send whatever is there - Except for during exception (class Error) handling, when a potential - msg is part of the exception -*/ -bool Server::sendMsg(int fd, std::string const &msg) { - if (send(fd, msg.c_str(), msg.length(), MSG_NOSIGNAL) == -1) { - std::cerr << "Send failed to FD " << fd << std::endl; - return false; +bool Server::sendMsg(int const fd, std::string const &msg) { + SocketIOData &sIOD = sockIODataStore_.at(fd); + ssize_t bytesSent = -1; + bool success = true; + + if (msg.length() > sIOD.writeBuffer.max_size() - sIOD.writeBuffer.length()) + sIOD.errorCount = SOCKETERRORLIMIT; + else { + sIOD.writeBuffer += msg; + bytesSent = send(fd, sIOD.writeBuffer.c_str(), + sIOD.writeBuffer.length(), MSG_NOSIGNAL); + if (bytesSent <= 0) + ++sIOD.errorCount; } - std::cout << "Msg sent to FD: " << fd << ": " << msg; + if (sIOD.errorCount >= SOCKETERRORLIMIT) { + closeSocket_(fd); + success = false; + } + +#if VERBOSE_LOG + std::ostringstream oss; + oss << "writeBuffer: '" << sIOD.writeBuffer << "'"; + if (!success) { + oss << ": send to FD " << fd << " failed."; + log_(oss.str(), (logMode)lmError); + } else { + if (static_cast(bytesSent) < sIOD.writeBuffer.length()) + oss << " partly (" << bytesSent << "/" << sIOD.writeBuffer.length() + << ": " << sIOD.writeBuffer.substr(0, bytesSent); + else + oss << " successfully"; + oss << " sent to FD: " << fd; + log_(oss.str(), (logMode)lmInfo); + } +#endif // VERBOSE_LOG + + if (!success) + return false; + + if (sIOD.errorCount > 0) + sIOD.errorCount = 0; + if (static_cast(bytesSent) < sIOD.writeBuffer.length()) + sIOD.writeBuffer.erase(0, bytesSent); + else + sIOD.writeBuffer.clear(); return true; } @@ -48,8 +79,8 @@ int Server::createServerSocket_(int const domain) { srvHelp::getAdressFamilyStr(domain) + " !"); int optval = 1; - if (setsockopt(socketFd, SOL_SOCKET, SO_REUSEADDR /* | SO_REUSEPORT */, - &optval, sizeof(optval)) != 0) + if (setsockopt(socketFd, SOL_SOCKET, SO_REUSEADDR, &optval, + sizeof(optval)) != 0) throw std::runtime_error("Error while setting socket options for " + srvHelp::getAdressFamilyStr(domain) + " !"); @@ -89,13 +120,19 @@ void Server::processClientSocketData_() { if ((pfd.revents & POLLERR) && !srvHelp::checkSocketFailOK()) { std::cerr << "Client socket error on fd: " << pfd.fd << "Disconnect client." << std::endl; - disconnectClient_(idx); + closeSocket_(idx); } else if (pfd.revents & POLLIN) { - SocketIOData &sockIOData = sockIODataStore_.at(pfd.fd); - char recvBuffer[RECVBUFFLEN]; + char recvBuffer[RECVBUFFLEN]; ssize_t bytesRead = recv(pfd.fd, &recvBuffer[0], RECVBUFFLEN, 0); if (bytesRead > 0) { + SocketIOData &sockIOData = sockIODataStore_.at(pfd.fd); + Client const *const client = clients_.at(pfd.fd); + + std::string nickName = "*"; + if (client && client->isRegistered()) + nickName = client->getNick(); + std::string &userInput = sockIOData.readBuffer; userInput += std::string(&recvBuffer[0], bytesRead); @@ -103,6 +140,10 @@ void Server::processClientSocketData_() { if ((userInput.length() >= MAXMSGLEN) && (lfcrPos == std::string::npos || lfcrPos > MAXMSGLEN - 2)) { sockIOData.discardMode = true; + sendMsg(pfd.fd, + Reply::build( + serverName(), nickName, + Reply::reply(Reply::ERR_INPUTTOOLONG, ""))); } if (sockIOData.discardMode) { if (lfcrPos != std::string::npos) { @@ -117,16 +158,14 @@ void Server::processClientSocketData_() { if (lfcrPos == std::string::npos || sockIOData.discardMode) { continue; } - // DEBUG - { - std::ostringstream oss; - oss << idx + 1 - numServerSockets_ << ": \n\t" - << std::string(userInput) /* << std::endl */; - userInput.clear(); - std::cout << "UserInput read from client #" << oss.str(); - // Pong to client - sendMsg(pfd.fd, "You are client #" + oss.str()); - } +#if LOG_VERBOSE + std::ostringstream oss; + oss << idx + 1 - numServerSockets_ << ": \n\t" + << std::string(userInput) /* << std::endl */; + std::cout << "UserInput read from client #" << oss.str(); + // Pong to client + sendMsg(pfd.fd, "You are client #" + oss.str()); +#endif // LOG_VERBOSE try { cmds_.run(*this, *clients_.at(pfd.fd), userInput); } catch (Error const &e) { @@ -149,7 +188,7 @@ void Server::processClientSocketData_() { // FIN is the TCP mechanism for orderly shutdown. // That's why POLLIN + read() return 0—the FIN, // and the zero read is how the kernel tells you "the peer closed." - disconnectClient_(idx); + closeSocket_(idx); } } } @@ -164,9 +203,10 @@ void Server::acceptClient_(int const serverFd) { srvHelp::setSocketAddr(serverFd, 0, sockAddrIn46, sockLen); - int const clientFd = accept( - serverFd, reinterpret_cast(&sockAddrIn46), // NOLINT - &sockLen); + int const clientFd = + accept(serverFd, + reinterpret_cast(&sockAddrIn46), // NOLINT + &sockLen); if (clientFd == -1) { // throw -> no everything else can still run w/o problems // silent return -> cannot sent anything to client without working socket anyways... @@ -185,27 +225,33 @@ void Server::acceptClient_(int const serverFd) { /* tbd */ -void Server::disconnectClient_(size_t const idx) { - pollfd &pfd = pollFds_.at(idx); +void Server::closeSocket_(int const fd) { + size_t idx = 0; + for (idx = 0; idx < pollFds_.size(); ++idx) + if (fd == pollFds_.at(idx).fd) + break; + if (idx == pollFds_.size()) + return; + std::ostringstream oss; if (idx < numServerSockets_) { if (pollFds_.size() != idx + 1) - std::cout << "Close client sockets before closing server!" - << std::endl; - std::cout << "Closing server socket (" - << srvHelp::getAdressFamilyStr( - srvHelp::sockAddrFamily(pfd.fd)) - << ")" << std::endl; - } else - std::cout << "Closing client #" << idx + 1 - numServerSockets_ - << " socket (" << pollFds_.size() - 1 - numServerSockets_ - << " clients remaining)" << std::endl; - close(pfd.fd); + return log_("Internal error: Close client sockets before " + "closing server!", + (logMode)lmError); + oss << "Closing server socket (" + << srvHelp::getAdressFamilyStr(srvHelp::sockAddrFamily(fd)) << ")"; + } else { + oss << "Closing client #" << idx + 1 - numServerSockets_ << " socket (" + << pollFds_.size() - 1 - numServerSockets_ << " clients remaining)"; + } + log_(oss.str(), (logMode)lmInfo); + close(fd); std::vector::difference_type dist = static_cast::difference_type>(idx); pollFds_.erase(pollFds_.begin() + dist); - sockIODataStore_.erase(pfd.fd); + sockIODataStore_.erase(fd); } void Server::log_(std::string const &msg, logMode const lm) { @@ -232,13 +278,13 @@ void Server::run() { while (!SignalHandler::stopLoopSignalReceived()) { int pollResult = poll(pollFds_.data(), pollFds_.size(), -1); - if (pollResult == -1 && !srvHelp::checkSocketFailOK()) - throw std::runtime_error("poll failed too many times."); + if (pollResult == -1) + throw std::runtime_error("Fatal error: poll failed."); processServerSocketData_(); processClientSocketData_(); } while (!pollFds_.empty()) { - disconnectClient_(pollFds_.size() - 1); + closeSocket_(pollFds_.size() - 1); } } From abb7cf1832d11b2641adb7d78d1cd4e684ef6cb8 Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 26 Aug 2026 16:38:44 +0200 Subject: [PATCH 055/349] feat: * return IRCv3 error code ERR_INPUTTOOLONG * improve sendMsg with error handling and logging * add colours for logger output * control log verbosity via VERBOSE_LOG * rework disconnectClient: it's more a closeSocket --- Makefile | 2 +- includes/Logger.hpp | 6 +- includes/Reply.hpp | 3 + includes/Server.hpp | 12 +++- includes/colours.hpp | 24 +++++++ srcs/server/Server.cpp | 144 +++++++++++++++++++++++++++-------------- 6 files changed, 136 insertions(+), 55 deletions(-) create mode 100644 includes/colours.hpp diff --git a/Makefile b/Makefile index 0d53f4f..cfed038 100644 --- a/Makefile +++ b/Makefile @@ -50,7 +50,7 @@ DEPS = $(OBJS:.o=.d) $(DEBUG_OBJS:.o=.d) CXX = c++ CXXFLAGS = -Wall -Wextra -Werror -pedantic -std=c++98 IFLAGS = -I$(INC_DIR) -DFLAGS = -DDEBUG -g -fsanitize=address +DFLAGS = -DDEBUG -DVERBOSE_LOG=1 -g -fsanitize=address DEBUG_CXXFLAGS = $(CXXFLAGS) $(DFLAGS) # ---- Rules ------------------------------------------------------------------- diff --git a/includes/Logger.hpp b/includes/Logger.hpp index aabe315..eb4db05 100644 --- a/includes/Logger.hpp +++ b/includes/Logger.hpp @@ -1,5 +1,7 @@ #pragma once +#include "colours.hpp" + #include #include #include @@ -28,7 +30,7 @@ class Logger { std::cout << nowStr_() << " " << prefix_ << "[INFO] : " << msg << std::endl; if (lm == (logMode)lmError) - std::cerr << nowStr_() << " " << prefix_ << "[ERROR]: " << msg - << std::endl; + std::cerr << nowStr_() << " " << prefix_ << CRed + << "[ERROR]: " << msg << CReset << std::endl; } }; diff --git a/includes/Reply.hpp b/includes/Reply.hpp index 386c1de..f58953e 100644 --- a/includes/Reply.hpp +++ b/includes/Reply.hpp @@ -85,6 +85,7 @@ enum Code { ERR_NOORIGIN = 409, // :No origin specified (PING/PONG) ERR_NORECIPIENT = 411, // :No recipient given () (PRIVMSG) ERR_NOTEXTTOSEND = 412, // :No text to send (PRIVMSG) + ERR_INPUTTOOLONG = 417, // :Input line was too long ERR_UNKNOWNCOMMAND = 421, // :Unknown command ERR_NOMOTD = 422, // :MOTD File is missing ERR_NONICKNAMEGIVEN = 431, // :No nickname given (NICK) @@ -139,6 +140,8 @@ inline const char *text(Code code) { return "No origin specified"; case ERR_NOTEXTTOSEND: return "No text to send"; + case ERR_INPUTTOOLONG: + return "Input line was too long"; case ERR_UNKNOWNCOMMAND: return "Unknown command"; case ERR_NOMOTD: diff --git a/includes/Server.hpp b/includes/Server.hpp index 356eb5e..eef5799 100644 --- a/includes/Server.hpp +++ b/includes/Server.hpp @@ -13,9 +13,15 @@ #include #define SERVER_NAME "irc.local" +#define MAX_CONNECTIONS 512 + #define RECVBUFFLEN 516 #define MAXMSGLEN 512 -#define MAX_CONNECTIONS 512 +#define SOCKETERRORLIMIT 16 + +#ifndef VERBOSE_LOG +#define VERBOSE_LOG 0 +#endif struct SocketIOData { std::string readBuffer; @@ -50,7 +56,7 @@ class Server { void processServerSocketData_(); void acceptClient_(int const serverFd); void processClientSocketData_(); - void disconnectClient_(size_t const idx); + void closeSocket_(int const fd); void log_(std::string const &msg, logMode const lm); public: @@ -63,7 +69,7 @@ class Server { }; void run(); - bool sendMsg(int fd, std::string const &msg); + bool sendMsg(int const fd, std::string const &msg); bool tryRegister(Client &client) { (void)client; diff --git a/includes/colours.hpp b/includes/colours.hpp new file mode 100644 index 0000000..f0715a9 --- /dev/null +++ b/includes/colours.hpp @@ -0,0 +1,24 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* colours.hpp :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: raphha +#+ +:+ +#+ */ +/* +#+#+#+#+#+ +#+ */ +/* Created: 2026/03/18 11:26:12 by rhaas #+# #+# */ +/* Updated: 2026/08/26 15:11:20 by raphha ### ########.fr */ +/* */ +/* ************************************************************************** */ + +// Color FG BG +#define CBlack "\x1B[30m" // 40 +#define CRed "\x1B[31m" // 41 +#define CGreen "\x1B[32m" // 42 +#define CYellow "\x1B[33m" // 43 +#define CBlue "\x1B[34m" // 44 +#define CMgnta "\x1B[35m" // 45 +#define CCyan "\x1B[36m" // 46 +#define CWhite "\x1B[37m" // 47 +#define CDeflt "\x1B[39m" // 49 + +#define CReset "\x1B[0m" \ No newline at end of file diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 3cc6117..8e00e1b 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -17,20 +17,51 @@ Server::Server(int const port, std::string const &password) : port_(port), password_(password), serverName_(SERVER_NAME), numServerSockets_(0), logger_(serverName_ + ": ") {} -/* - Can anyone send sth outside of the poll loop -> No, right? - Then I'd always know, what fd/client I am currently talking to. - And could just check the sendBuffer and if it's not empty, - send whatever is there - Except for during exception (class Error) handling, when a potential - msg is part of the exception -*/ -bool Server::sendMsg(int fd, std::string const &msg) { - if (send(fd, msg.c_str(), msg.length(), MSG_NOSIGNAL) == -1) { - std::cerr << "Send failed to FD " << fd << std::endl; - return false; +bool Server::sendMsg(int const fd, std::string const &msg) { + SocketIOData &sIOD = sockIODataStore_.at(fd); + ssize_t bytesSent = -1; + bool success = true; + + if (msg.length() > sIOD.writeBuffer.max_size() - sIOD.writeBuffer.length()) + sIOD.errorCount = SOCKETERRORLIMIT; + else { + sIOD.writeBuffer += msg; + bytesSent = send(fd, sIOD.writeBuffer.c_str(), + sIOD.writeBuffer.length(), MSG_NOSIGNAL); + if (bytesSent <= 0) + ++sIOD.errorCount; } - std::cout << "Msg sent to FD: " << fd << ": " << msg; + if (sIOD.errorCount >= SOCKETERRORLIMIT) { + closeSocket_(fd); + success = false; + } + +#if VERBOSE_LOG + std::ostringstream oss; + oss << "writeBuffer: '" << sIOD.writeBuffer << "'"; + if (!success) { + oss << ": send to FD " << fd << " failed."; + log_(oss.str(), (logMode)lmError); + } else { + if (static_cast(bytesSent) < sIOD.writeBuffer.length()) + oss << " partly (" << bytesSent << "/" << sIOD.writeBuffer.length() + << ": " << sIOD.writeBuffer.substr(0, bytesSent); + else + oss << " successfully"; + oss << " sent to FD: " << fd; + log_(oss.str(), (logMode)lmInfo); + } +#endif // VERBOSE_LOG + + if (!success) + return false; + + if (sIOD.errorCount > 0) + sIOD.errorCount = 0; + if (static_cast(bytesSent) < sIOD.writeBuffer.length()) + sIOD.writeBuffer.erase(0, bytesSent); + else + sIOD.writeBuffer.clear(); return true; } @@ -48,8 +79,8 @@ int Server::createServerSocket_(int const domain) { srvHelp::getAdressFamilyStr(domain) + " !"); int optval = 1; - if (setsockopt(socketFd, SOL_SOCKET, SO_REUSEADDR /* | SO_REUSEPORT */, - &optval, sizeof(optval)) != 0) + if (setsockopt(socketFd, SOL_SOCKET, SO_REUSEADDR, &optval, + sizeof(optval)) != 0) throw std::runtime_error("Error while setting socket options for " + srvHelp::getAdressFamilyStr(domain) + " !"); @@ -89,13 +120,19 @@ void Server::processClientSocketData_() { if ((pfd.revents & POLLERR) && !srvHelp::checkSocketFailOK()) { std::cerr << "Client socket error on fd: " << pfd.fd << "Disconnect client." << std::endl; - disconnectClient_(idx); + closeSocket_(idx); } else if (pfd.revents & POLLIN) { - SocketIOData &sockIOData = sockIODataStore_.at(pfd.fd); - char recvBuffer[RECVBUFFLEN]; + char recvBuffer[RECVBUFFLEN]; ssize_t bytesRead = recv(pfd.fd, &recvBuffer[0], RECVBUFFLEN, 0); if (bytesRead > 0) { + SocketIOData &sockIOData = sockIODataStore_.at(pfd.fd); + Client const *const client = clients_.at(pfd.fd); + + std::string nickName = "*"; + if (client && client->isRegistered()) + nickName = client->getNick(); + std::string &userInput = sockIOData.readBuffer; userInput += std::string(&recvBuffer[0], bytesRead); @@ -103,6 +140,10 @@ void Server::processClientSocketData_() { if ((userInput.length() >= MAXMSGLEN) && (lfcrPos == std::string::npos || lfcrPos > MAXMSGLEN - 2)) { sockIOData.discardMode = true; + sendMsg(pfd.fd, + Reply::build( + serverName(), nickName, + Reply::reply(Reply::ERR_INPUTTOOLONG, ""))); } if (sockIOData.discardMode) { if (lfcrPos != std::string::npos) { @@ -117,16 +158,14 @@ void Server::processClientSocketData_() { if (lfcrPos == std::string::npos || sockIOData.discardMode) { continue; } - // DEBUG - { - std::ostringstream oss; - oss << idx + 1 - numServerSockets_ << ": \n\t" - << std::string(userInput) /* << std::endl */; - userInput.clear(); - std::cout << "UserInput read from client #" << oss.str(); - // Pong to client - sendMsg(pfd.fd, "You are client #" + oss.str()); - } +#if LOG_VERBOSE + std::ostringstream oss; + oss << idx + 1 - numServerSockets_ << ": \n\t" + << std::string(userInput) /* << std::endl */; + std::cout << "UserInput read from client #" << oss.str(); + // Pong to client + sendMsg(pfd.fd, "You are client #" + oss.str()); +#endif // LOG_VERBOSE try { cmds_.run(*this, *clients_.at(pfd.fd), userInput); } catch (Error const &e) { @@ -149,7 +188,7 @@ void Server::processClientSocketData_() { // FIN is the TCP mechanism for orderly shutdown. // That's why POLLIN + read() return 0—the FIN, // and the zero read is how the kernel tells you "the peer closed." - disconnectClient_(idx); + closeSocket_(idx); } } } @@ -164,9 +203,10 @@ void Server::acceptClient_(int const serverFd) { srvHelp::setSocketAddr(serverFd, 0, sockAddrIn46, sockLen); - int const clientFd = accept( - serverFd, reinterpret_cast(&sockAddrIn46), // NOLINT - &sockLen); + int const clientFd = + accept(serverFd, + reinterpret_cast(&sockAddrIn46), // NOLINT + &sockLen); if (clientFd == -1) { // throw -> no everything else can still run w/o problems // silent return -> cannot sent anything to client without working socket anyways... @@ -185,27 +225,33 @@ void Server::acceptClient_(int const serverFd) { /* tbd */ -void Server::disconnectClient_(size_t const idx) { - pollfd &pfd = pollFds_.at(idx); +void Server::closeSocket_(int const fd) { + size_t idx = 0; + for (idx = 0; idx < pollFds_.size(); ++idx) + if (fd == pollFds_.at(idx).fd) + break; + if (idx == pollFds_.size()) + return; + std::ostringstream oss; if (idx < numServerSockets_) { if (pollFds_.size() != idx + 1) - std::cout << "Close client sockets before closing server!" - << std::endl; - std::cout << "Closing server socket (" - << srvHelp::getAdressFamilyStr( - srvHelp::sockAddrFamily(pfd.fd)) - << ")" << std::endl; - } else - std::cout << "Closing client #" << idx + 1 - numServerSockets_ - << " socket (" << pollFds_.size() - 1 - numServerSockets_ - << " clients remaining)" << std::endl; - close(pfd.fd); + return log_("Internal error: Close client sockets before " + "closing server!", + (logMode)lmError); + oss << "Closing server socket (" + << srvHelp::getAdressFamilyStr(srvHelp::sockAddrFamily(fd)) << ")"; + } else { + oss << "Closing client #" << idx + 1 - numServerSockets_ << " socket (" + << pollFds_.size() - 1 - numServerSockets_ << " clients remaining)"; + } + log_(oss.str(), (logMode)lmInfo); + close(fd); std::vector::difference_type dist = static_cast::difference_type>(idx); pollFds_.erase(pollFds_.begin() + dist); - sockIODataStore_.erase(pfd.fd); + sockIODataStore_.erase(fd); } void Server::log_(std::string const &msg, logMode const lm) { @@ -232,13 +278,13 @@ void Server::run() { while (!SignalHandler::stopLoopSignalReceived()) { int pollResult = poll(pollFds_.data(), pollFds_.size(), -1); - if (pollResult == -1 && !srvHelp::checkSocketFailOK()) - throw std::runtime_error("poll failed too many times."); + if (pollResult == -1) + throw std::runtime_error("Fatal error: poll failed."); processServerSocketData_(); processClientSocketData_(); } while (!pollFds_.empty()) { - disconnectClient_(pollFds_.size() - 1); + closeSocket_(pollFds_.size() - 1); } } From c693d5619c4b85c6323f119eaea775a2fafcd0f3 Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 27 Aug 2026 11:28:21 +0200 Subject: [PATCH 056/349] feat: beautify debug output --- includes/ServerHelper.hpp | 2 +- srcs/server/Server.cpp | 29 ++++++++++++++++++++--------- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/includes/ServerHelper.hpp b/includes/ServerHelper.hpp index cc30dcb..6f17a13 100644 --- a/includes/ServerHelper.hpp +++ b/includes/ServerHelper.hpp @@ -83,7 +83,7 @@ storePollFd(int const socketFd, pFds.push_back(pfd); sockIODataStore.insert( - std::make_pair(pfd.fd, SocketIOData())); + std::make_pair(socketFd, SocketIOData())); } inline bool isSocketListening(int sockfd) { diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 8e00e1b..e03a7ca 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -126,10 +126,15 @@ void Server::processClientSocketData_() { ssize_t bytesRead = recv(pfd.fd, &recvBuffer[0], RECVBUFFLEN, 0); if (bytesRead > 0) { - SocketIOData &sockIOData = sockIODataStore_.at(pfd.fd); - Client const *const client = clients_.at(pfd.fd); + SocketIOData &sockIOData = sockIODataStore_.at(pfd.fd); + Client const *client = 0; + try { + client = clients_.at(pfd.fd); + } catch (std::exception const &e) { + std::cerr << "caught exception: " << e.what() << std::endl; + } - std::string nickName = "*"; + std::string nickName = "*"; if (client && client->isRegistered()) nickName = client->getNick(); @@ -158,14 +163,16 @@ void Server::processClientSocketData_() { if (lfcrPos == std::string::npos || sockIOData.discardMode) { continue; } -#if LOG_VERBOSE +#if VERBOSE_LOG std::ostringstream oss; - oss << idx + 1 - numServerSockets_ << ": \n\t" + oss << "You are client #" << idx + 1 - numServerSockets_ + << ": \n" + << "Srv received: " << std::string(userInput) /* << std::endl */; std::cout << "UserInput read from client #" << oss.str(); // Pong to client - sendMsg(pfd.fd, "You are client #" + oss.str()); -#endif // LOG_VERBOSE + sendMsg(pfd.fd, oss.str()); +#endif // VERBOSE_LOG try { cmds_.run(*this, *clients_.at(pfd.fd), userInput); } catch (Error const &e) { @@ -173,9 +180,13 @@ void Server::processClientSocketData_() { Client const &c = *clients_.at(pfd.fd); sendMsg(pfd.fd, Reply::build(serverName(), c.getNick(), e.reply())); - } else - log_(e.what(), (logMode)lmError); + } else { + std::string tmp("Error while trying to send msg:\n"); + tmp.append(e.what()); + log_(tmp.c_str(), (logMode)lmError); + } } + userInput.clear(); } else if (bytesRead <= 0) { // Comment will be deleted later, kept for reference for now From faf47c6e5137b595706a4aa0020660f672d2d6d7 Mon Sep 17 00:00:00 2001 From: rhaas Date: Thu, 27 Aug 2026 17:29:17 +0200 Subject: [PATCH 057/349] fix: missing include guards for colours --- includes/SignalHandler.hpp | 2 +- includes/colours.hpp | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/includes/SignalHandler.hpp b/includes/SignalHandler.hpp index 226dbed..f408d47 100644 --- a/includes/SignalHandler.hpp +++ b/includes/SignalHandler.hpp @@ -22,4 +22,4 @@ class SignalHandler { static bool stopLoopSignalReceived(); }; -#endif // SIGNALHANDLER_HPP \ No newline at end of file +#endif // SIGNALHANDLER_HPP diff --git a/includes/colours.hpp b/includes/colours.hpp index f0715a9..1c51970 100644 --- a/includes/colours.hpp +++ b/includes/colours.hpp @@ -3,13 +3,16 @@ /* ::: :::::::: */ /* colours.hpp :+: :+: :+: */ /* +:+ +:+ +:+ */ -/* By: raphha +#+ +:+ +#+ */ +/* By: rhaas +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2026/03/18 11:26:12 by rhaas #+# #+# */ -/* Updated: 2026/08/26 15:11:20 by raphha ### ########.fr */ +/* Updated: 2026/08/27 17:28:27 by rhaas ### ########.fr */ /* */ /* ************************************************************************** */ +#ifndef COLOURS_HPP +#define COLOURS_HPP + // Color FG BG #define CBlack "\x1B[30m" // 40 #define CRed "\x1B[31m" // 41 @@ -21,4 +24,6 @@ #define CWhite "\x1B[37m" // 47 #define CDeflt "\x1B[39m" // 49 -#define CReset "\x1B[0m" \ No newline at end of file +#define CReset "\x1B[0m" + +#endif // COLOURS_HPP From da8265e28968dac984e97a1d99447f9d7f435b5d Mon Sep 17 00:00:00 2001 From: rhaas Date: Thu, 27 Aug 2026 17:39:10 +0200 Subject: [PATCH 058/349] rfa: * make struct SocketIOData private * make storePollFd a member function, bc it is not just supporting functionality, but also needs access to internal data --- includes/Server.hpp | 19 ++++++++++--------- includes/ServerHelper.hpp | 13 ------------- srcs/server/Server.cpp | 14 ++++++++++++-- 3 files changed, 22 insertions(+), 24 deletions(-) diff --git a/includes/Server.hpp b/includes/Server.hpp index eef5799..52e45ad 100644 --- a/includes/Server.hpp +++ b/includes/Server.hpp @@ -23,17 +23,17 @@ #define VERBOSE_LOG 0 #endif -struct SocketIOData { - std::string readBuffer; - std::string writeBuffer; - size_t errorCount; - bool discardMode; - - SocketIOData() : errorCount(0), discardMode(false) {} -}; - class Server { private: + struct SocketIOData { + std::string readBuffer; + std::string writeBuffer; + size_t errorCount; + bool discardMode; + + SocketIOData() : errorCount(0), discardMode(false) {} + }; + ChannelManager channels_; ClientManager clients_; CommandProcessor cmds_; @@ -55,6 +55,7 @@ class Server { int createServerSocket_(int const domain); void processServerSocketData_(); void acceptClient_(int const serverFd); + void storePollFd_(int const socketFd); void processClientSocketData_(); void closeSocket_(int const fd); void log_(std::string const &msg, logMode const lm); diff --git a/includes/ServerHelper.hpp b/includes/ServerHelper.hpp index 6f17a13..29ca8b1 100644 --- a/includes/ServerHelper.hpp +++ b/includes/ServerHelper.hpp @@ -73,19 +73,6 @@ inline void bindServerSocket(int const socketFd, int const domain, // NOLINT getAdressFamilyStr(domain) + " !"); } -inline void -storePollFd(int const socketFd, - std::vector &pFds, // NOLINT - std::map &sockIODataStore) { // NOLINT - pollfd pfd = {}; - pfd.fd = socketFd; - pfd.events = POLLIN; // Monitor for incoming connections - - pFds.push_back(pfd); - sockIODataStore.insert( - std::make_pair(socketFd, SocketIOData())); -} - inline bool isSocketListening(int sockfd) { int optVal = 0; socklen_t optLen = sizeof(optVal); diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index e03a7ca..66951f9 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -98,7 +98,7 @@ int Server::createServerSocket_(int const domain) { throw std::runtime_error("Error while listening on socket for " + srvHelp::getAdressFamilyStr(domain) + " !"); - srvHelp::storePollFd(socketFd, pollFds_, sockIODataStore_); + storePollFd_(socketFd); ++numServerSockets_; return socketFd; @@ -226,13 +226,23 @@ void Server::acceptClient_(int const serverFd) { srvHelp::setSocketNonBlock(clientFd); - srvHelp::storePollFd(clientFd, pollFds_, sockIODataStore_); + storePollFd_(clientFd); std::cout << "Client connected:\n" << " fd: " << clientFd << "\n" << srvHelp::printAddrfamilyAddrPort(sockAddrIn46) << std::endl; } +void Server::storePollFd_(int const socketFd) { + pollfd pfd = {}; + pfd.fd = socketFd; + pfd.events = POLLIN; // Monitor for incoming connections + + pollFds_.push_back(pfd); + sockIODataStore_.insert( + std::make_pair(socketFd, SocketIOData())); +} + /* tbd */ From 481f22087fc73c4c5537719f0ec05fc11e2d856e Mon Sep 17 00:00:00 2001 From: rhaas Date: Thu, 27 Aug 2026 17:47:04 +0200 Subject: [PATCH 059/349] rfa: move the logMode enum to the Logger class, so it has a sensible namespace --- includes/Logger.hpp | 8 ++++---- includes/Server.hpp | 2 +- srcs/server/Server.cpp | 12 ++++++------ 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/includes/Logger.hpp b/includes/Logger.hpp index eb4db05..47f1d3a 100644 --- a/includes/Logger.hpp +++ b/includes/Logger.hpp @@ -7,8 +7,6 @@ #include #include -enum logMode { lmInfo, lmError }; - class Logger { std::string prefix_; @@ -21,15 +19,17 @@ class Logger { } public: + enum logMode { lmInfo, lmError }; + Logger(std::string const &prefix) { prefix_ = prefix; } void logMsg(std::string const &msg, logMode const lm) const { - if (lm == (logMode)lmInfo) + if (lm == Logger::lmInfo) std::cout << nowStr_() << " " << prefix_ << "[INFO] : " << msg << std::endl; - if (lm == (logMode)lmError) + if (lm == Logger::lmError) std::cerr << nowStr_() << " " << prefix_ << CRed << "[ERROR]: " << msg << CReset << std::endl; } diff --git a/includes/Server.hpp b/includes/Server.hpp index 52e45ad..53f132a 100644 --- a/includes/Server.hpp +++ b/includes/Server.hpp @@ -58,7 +58,7 @@ class Server { void storePollFd_(int const socketFd); void processClientSocketData_(); void closeSocket_(int const fd); - void log_(std::string const &msg, logMode const lm); + void log_(std::string const &msg, Logger::logMode const lm); public: Server(int const port, std::string const &password); diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 66951f9..ce111d0 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -41,7 +41,7 @@ bool Server::sendMsg(int const fd, std::string const &msg) { oss << "writeBuffer: '" << sIOD.writeBuffer << "'"; if (!success) { oss << ": send to FD " << fd << " failed."; - log_(oss.str(), (logMode)lmError); + log_(oss.str(), Logger::lmError); } else { if (static_cast(bytesSent) < sIOD.writeBuffer.length()) oss << " partly (" << bytesSent << "/" << sIOD.writeBuffer.length() @@ -49,7 +49,7 @@ bool Server::sendMsg(int const fd, std::string const &msg) { else oss << " successfully"; oss << " sent to FD: " << fd; - log_(oss.str(), (logMode)lmInfo); + log_(oss.str(), Logger::lmInfo); } #endif // VERBOSE_LOG @@ -183,7 +183,7 @@ void Server::processClientSocketData_() { } else { std::string tmp("Error while trying to send msg:\n"); tmp.append(e.what()); - log_(tmp.c_str(), (logMode)lmError); + log_(tmp.c_str(), Logger::lmError); } } userInput.clear(); @@ -259,14 +259,14 @@ void Server::closeSocket_(int const fd) { if (pollFds_.size() != idx + 1) return log_("Internal error: Close client sockets before " "closing server!", - (logMode)lmError); + Logger::lmError); oss << "Closing server socket (" << srvHelp::getAdressFamilyStr(srvHelp::sockAddrFamily(fd)) << ")"; } else { oss << "Closing client #" << idx + 1 - numServerSockets_ << " socket (" << pollFds_.size() - 1 - numServerSockets_ << " clients remaining)"; } - log_(oss.str(), (logMode)lmInfo); + log_(oss.str(), Logger::lmInfo); close(fd); std::vector::difference_type dist = @@ -275,7 +275,7 @@ void Server::closeSocket_(int const fd) { sockIODataStore_.erase(fd); } -void Server::log_(std::string const &msg, logMode const lm) { +void Server::log_(std::string const &msg, Logger::logMode const lm) { logger_.logMsg(msg, lm); } From 8071ce4bb6834c9d56b5aefc2f7e5d859f999fc8 Mon Sep 17 00:00:00 2001 From: rhaas Date: Thu, 27 Aug 2026 17:49:14 +0200 Subject: [PATCH 060/349] rfa: using pragma once for consistency --- includes/colours.hpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/includes/colours.hpp b/includes/colours.hpp index 1c51970..9fdeabb 100644 --- a/includes/colours.hpp +++ b/includes/colours.hpp @@ -6,12 +6,11 @@ /* By: rhaas +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2026/03/18 11:26:12 by rhaas #+# #+# */ -/* Updated: 2026/08/27 17:28:27 by rhaas ### ########.fr */ +/* Updated: 2026/08/27 17:47:38 by rhaas ### ########.fr */ /* */ /* ************************************************************************** */ -#ifndef COLOURS_HPP -#define COLOURS_HPP +#pragma once // Color FG BG #define CBlack "\x1B[30m" // 40 @@ -25,5 +24,3 @@ #define CDeflt "\x1B[39m" // 49 #define CReset "\x1B[0m" - -#endif // COLOURS_HPP From 810deadf8f08c386f2c7ad1c686aae3c148e8a4e Mon Sep 17 00:00:00 2001 From: rhaas Date: Thu, 27 Aug 2026 17:49:25 +0200 Subject: [PATCH 061/349] rfa: clang-format --- includes/Server.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/Server.hpp b/includes/Server.hpp index 53f132a..3a11189 100644 --- a/includes/Server.hpp +++ b/includes/Server.hpp @@ -58,7 +58,7 @@ class Server { void storePollFd_(int const socketFd); void processClientSocketData_(); void closeSocket_(int const fd); - void log_(std::string const &msg, Logger::logMode const lm); + void log_(std::string const &msg, Logger::logMode const lm); public: Server(int const port, std::string const &password); From c0040355726a3fd089a0622792e80d30876f8b5e Mon Sep 17 00:00:00 2001 From: rhaas Date: Thu, 27 Aug 2026 18:34:26 +0200 Subject: [PATCH 062/349] rfa: cleanup; non-functional --- includes/Server.hpp | 5 +---- includes/ServerHelper.hpp | 1 - 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/includes/Server.hpp b/includes/Server.hpp index 3a11189..73fe122 100644 --- a/includes/Server.hpp +++ b/includes/Server.hpp @@ -5,9 +5,6 @@ #include "CommandProcessor.hpp" #include "Logger.hpp" -#include -#include -#include #include #include #include @@ -74,6 +71,6 @@ class Server { bool tryRegister(Client &client) { (void)client; - return false; + return true; }; }; diff --git a/includes/ServerHelper.hpp b/includes/ServerHelper.hpp index 29ca8b1..debfe94 100644 --- a/includes/ServerHelper.hpp +++ b/includes/ServerHelper.hpp @@ -1,7 +1,6 @@ #pragma once #include -#include #include #include #include From 1a11401de6a810b523d9d013f603d2f67d40800b Mon Sep 17 00:00:00 2001 From: Rapha Date: Fri, 28 Aug 2026 12:39:51 +0200 Subject: [PATCH 063/349] rfa: remove unused function with forbidden getsockopt --- includes/ServerHelper.hpp | 9 --------- 1 file changed, 9 deletions(-) diff --git a/includes/ServerHelper.hpp b/includes/ServerHelper.hpp index debfe94..d89a262 100644 --- a/includes/ServerHelper.hpp +++ b/includes/ServerHelper.hpp @@ -72,15 +72,6 @@ inline void bindServerSocket(int const socketFd, int const domain, // NOLINT getAdressFamilyStr(domain) + " !"); } -inline bool isSocketListening(int sockfd) { - int optVal = 0; - socklen_t optLen = sizeof(optVal); - if (getsockopt(sockfd, SOL_SOCKET, SO_ACCEPTCONN, &optVal, &optLen) < 0) - throw std::runtime_error("Error getting socket options."); - - return (optVal > 0); -} - inline std::string printAddrfamilyAddrPort(socketAddrX const &sockAddrIn46) { char addrBuffer[128]; socklen_t const addrBuffLen = sizeof(addrBuffer) / sizeof(char); From f18c996d688348a0778cbe38c8bd517211eff76c Mon Sep 17 00:00:00 2001 From: Rapha Date: Fri, 28 Aug 2026 12:40:22 +0200 Subject: [PATCH 064/349] feat: * add new log level WARN * add colours for each level --- includes/Logger.hpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/includes/Logger.hpp b/includes/Logger.hpp index 47f1d3a..bed3d0b 100644 --- a/includes/Logger.hpp +++ b/includes/Logger.hpp @@ -19,7 +19,7 @@ class Logger { } public: - enum logMode { lmInfo, lmError }; + enum logMode { lmInfo, lmWarn, lmError }; Logger(std::string const &prefix) { prefix_ = prefix; @@ -27,8 +27,11 @@ class Logger { void logMsg(std::string const &msg, logMode const lm) const { if (lm == Logger::lmInfo) - std::cout << nowStr_() << " " << prefix_ << "[INFO] : " << msg - << std::endl; + std::cout << nowStr_() << " " << prefix_ << CGreen + << " [INFO] : " << msg << CReset << std::endl; + if (lm == Logger::lmWarn) + std::cout << nowStr_() << " " << prefix_ << CYellow + << " [WARN] : " << msg << CReset << std::endl; if (lm == Logger::lmError) std::cerr << nowStr_() << " " << prefix_ << CRed << "[ERROR]: " << msg << CReset << std::endl; From 1c4000f42dadda97f17271a360dc494afae44b6a Mon Sep 17 00:00:00 2001 From: Rapha Date: Fri, 28 Aug 2026 13:12:01 +0200 Subject: [PATCH 065/349] feat: remove unused function checkSocketFailOK --- includes/ServerHelper.hpp | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/includes/ServerHelper.hpp b/includes/ServerHelper.hpp index d89a262..ee9b543 100644 --- a/includes/ServerHelper.hpp +++ b/includes/ServerHelper.hpp @@ -91,29 +91,18 @@ inline std::string printAddrfamilyAddrPort(socketAddrX const &sockAddrIn46) { } char const *addrStrPtr = inet_ntop(saFamily, addrPtr, &addrBuffer[0], addrBuffLen); - std::string addrStr(addrStrPtr ? addrStrPtr : ""); - if (saFamily == AF_INET6) - addrStr = "[" + addrStr + "]"; + std::string addrStr("No addr:port information."); + if (addrStrPtr) { + addrStr.assign(addrStrPtr); + if (saFamily == AF_INET6) + addrStr = "[" + addrStr + "]"; + } std::ostringstream oss; oss << " AF: " << getAdressFamilyStr(saFamily) << "\n addr:port: " << addrStr << ":" << std::dec << portNumber; + return oss.str(); } -inline bool checkSocketFailOK() { - //return SignalHandler::stopLoopSignalReceived(); - static size_t loopCounter = 0; - static size_t retries = 42; - if (loopCounter % 1000 == 0) - retries = 42; - if (errno == EINTR) - return true; - if (errno == EAGAIN && retries > 1) { - retries--; - usleep(10000); - return true; - } - return false; -} } // namespace srvHelp From 2f1f06e98bf8a7c5c2e88cb9389a0f25f0adc603 Mon Sep 17 00:00:00 2001 From: Rapha Date: Fri, 28 Aug 2026 13:14:15 +0200 Subject: [PATCH 066/349] info: update memo regarding POLLHUP --- memo/poll.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/memo/poll.md b/memo/poll.md index 7292e4b..bc1b6ca 100644 --- a/memo/poll.md +++ b/memo/poll.md @@ -77,6 +77,17 @@ A return value of **`0`** specifically indicates a timeout occurred with no even * **Automatic Flags**: The kernel sets `POLLERR`, `POLLHUP`, and `POLLNVAL` in `revents` **even if you did not request them** in `events`. Robust code must always check for these flags to detect connection resets, errors, or invalid descriptors. * **Array Limits**: Passing an `nfds` value larger than the process's `RLIMIT_NOFILE` (often 1024 by default) will cause `poll()` to fail immediately with `EINVAL`. +#### note on how to detect whether client closed the connection on their side + POLLHUP isn't useful, because: + When you kill netcat with Ctrl+C: + Netcat's OS kernel sent a FIN packet to your server + Your server's poll() detected this with POLLIN + Your read() returned 0 (zero bytes) == "I got the FIN, connection closed" + FIN is the TCP mechanism for orderly shutdown. + That's why POLLIN + read() return 0—the FIN, + and the zero read is how the kernel tells you "the peer closed." + + ### Windows Compatibility Standard `poll()` is part of POSIX and is not natively available in the standard Winsock API. Windows provides **`WSAPoll()`** in newer versions (Vista/Server 2008+), but it has historically had bugs and performance issues. Production C++ applications targeting Windows often use `select()`, Overlapped I/O (IOCP), or cross-platform libraries like **`libuv`** or **`Boost.Asio`** to abstract these differences. From 185476dceffea0cf2e8cbacc05e5ef1eb53db251 Mon Sep 17 00:00:00 2001 From: Rapha Date: Fri, 28 Aug 2026 13:38:28 +0200 Subject: [PATCH 067/349] feat: * remove VERBOSE_LOG * add logLevel Exception (and rfa logMsg function) * do not throw on wrong input params * remove checkSocketFailOK function && implement new error handling strategy --- Makefile | 2 +- includes/Logger.hpp | 36 ++++++++++++++++++-------- includes/Server.hpp | 4 --- srcs/main.cpp | 25 +++++++++++------- srcs/server/Server.cpp | 58 ++++++++++++++++++------------------------ 5 files changed, 67 insertions(+), 58 deletions(-) diff --git a/Makefile b/Makefile index cfed038..0d53f4f 100644 --- a/Makefile +++ b/Makefile @@ -50,7 +50,7 @@ DEPS = $(OBJS:.o=.d) $(DEBUG_OBJS:.o=.d) CXX = c++ CXXFLAGS = -Wall -Wextra -Werror -pedantic -std=c++98 IFLAGS = -I$(INC_DIR) -DFLAGS = -DDEBUG -DVERBOSE_LOG=1 -g -fsanitize=address +DFLAGS = -DDEBUG -g -fsanitize=address DEBUG_CXXFLAGS = $(CXXFLAGS) $(DFLAGS) # ---- Rules ------------------------------------------------------------------- diff --git a/includes/Logger.hpp b/includes/Logger.hpp index bed3d0b..6a08787 100644 --- a/includes/Logger.hpp +++ b/includes/Logger.hpp @@ -19,21 +19,35 @@ class Logger { } public: - enum logMode { lmInfo, lmWarn, lmError }; + enum logMode { lmInfo, lmWarn, lmError, lmExcept }; Logger(std::string const &prefix) { prefix_ = prefix; } - void logMsg(std::string const &msg, logMode const lm) const { - if (lm == Logger::lmInfo) - std::cout << nowStr_() << " " << prefix_ << CGreen - << " [INFO] : " << msg << CReset << std::endl; - if (lm == Logger::lmWarn) - std::cout << nowStr_() << " " << prefix_ << CYellow - << " [WARN] : " << msg << CReset << std::endl; - if (lm == Logger::lmError) - std::cerr << nowStr_() << " " << prefix_ << CRed - << "[ERROR]: " << msg << CReset << std::endl; + void logMsg(std::string msg, logMode const lm) const { + std::ostream *os = &std::cout; + if (lm == Logger::lmError || lm == Logger::lmExcept) + os = &std::cerr; + + switch (lm) { + case Logger::lmInfo: + msg.insert(0, " [INFO] : "); + *os << CGreen; + break; + case Logger::lmWarn: + msg.insert(0, " [WARN] : "); + *os << CYellow; + break; + case Logger::lmError: + msg.insert(0, " [ERROR] : "); + *os << CRed; + break; + case Logger::lmExcept: + msg.insert(0, " [EXCEPTION] : "); + *os << CMgnta; + break; + } + *os << nowStr_() << " " << prefix_ << msg << CReset << std::endl; } }; diff --git a/includes/Server.hpp b/includes/Server.hpp index 73fe122..6985e02 100644 --- a/includes/Server.hpp +++ b/includes/Server.hpp @@ -16,10 +16,6 @@ #define MAXMSGLEN 512 #define SOCKETERRORLIMIT 16 -#ifndef VERBOSE_LOG -#define VERBOSE_LOG 0 -#endif - class Server { private: struct SocketIOData { diff --git a/srcs/main.cpp b/srcs/main.cpp index 0a8e35d..075c541 100644 --- a/srcs/main.cpp +++ b/srcs/main.cpp @@ -6,7 +6,7 @@ #include #include -static void checkExtractCmdLnArgs(int const argc, char *const argv[], int &port, +static bool checkExtractCmdLnArgs(int const argc, char *const argv[], int &port, std::string &password); /* @@ -51,26 +51,31 @@ int main(int argc, char **argv) { std::string password; try { - checkExtractCmdLnArgs(argc, argv, port, password); + if (!checkExtractCmdLnArgs(argc, argv, port, password)) + return 1; SignalHandler sigHandler; Server srv(port, password); srv.run(); + } catch (std::exception const &e) { - std::cerr << e.what() << std::endl; + std::cerr << CCyan << " !!! Exception: " << e.what() << CReset + << std::endl; return 1; } - std::cout << "Clean exit." << std::endl; + std::cout << CGreen << "Clean exit." << CReset << std::endl; return 0; } -static void checkExtractCmdLnArgs(int const argc, char *const argv[], int &port, +static bool checkExtractCmdLnArgs(int const argc, char *const argv[], int &port, std::string &password) { std::istringstream iss; std::string remainder; - if (argc != 3) - throw std::out_of_range( - "Usage: ./ircserv "); + if (argc != 3) { + std::cerr << CYellow << "Usage: ./ircserv " + << CReset << std::endl; + return false; + } iss.str(argv[1]); // NOLINT if (!((iss >> port) && (iss >> remainder).eof())) @@ -79,6 +84,8 @@ static void checkExtractCmdLnArgs(int const argc, char *const argv[], int &port, iss.str(argv[2]); // NOLINT if (!(iss >> password).eof()) - throw std::out_of_range( + throw std::runtime_error( "Error while extracting password from commandline parameter."); + + return true; } diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index ce111d0..3ff266a 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -36,22 +36,20 @@ bool Server::sendMsg(int const fd, std::string const &msg) { success = false; } -#if VERBOSE_LOG std::ostringstream oss; - oss << "writeBuffer: '" << sIOD.writeBuffer << "'"; + oss << "SendMsg event:\n" << "WriteBuffer: '" << sIOD.writeBuffer << "':\n"; if (!success) { - oss << ": send to FD " << fd << " failed."; + oss << " send to FD " << fd << " failed."; log_(oss.str(), Logger::lmError); } else { if (static_cast(bytesSent) < sIOD.writeBuffer.length()) oss << " partly (" << bytesSent << "/" << sIOD.writeBuffer.length() - << ": " << sIOD.writeBuffer.substr(0, bytesSent); + << ") " << sIOD.writeBuffer.substr(0, bytesSent); else oss << " successfully"; oss << " sent to FD: " << fd; log_(oss.str(), Logger::lmInfo); } -#endif // VERBOSE_LOG if (!success) return false; @@ -107,8 +105,8 @@ int Server::createServerSocket_(int const domain) { void Server::processServerSocketData_() { for (size_t idx = 0; idx < numServerSockets_; ++idx) { struct pollfd const &serverPollFds = pollFds_[idx]; - if ((serverPollFds.revents & POLLERR) && !srvHelp::checkSocketFailOK()) - throw std::runtime_error("Server socket error"); + if ((serverPollFds.revents & POLLERR)) + throw std::runtime_error("Server socket error."); if (serverPollFds.revents & POLLIN) acceptClient_(serverPollFds.fd); } @@ -116,28 +114,30 @@ void Server::processServerSocketData_() { void Server::processClientSocketData_() { for (size_t idx = numServerSockets_; idx < pollFds_.size(); ++idx) { - pollfd &pfd = pollFds_.at(idx); - if ((pfd.revents & POLLERR) && !srvHelp::checkSocketFailOK()) { - std::cerr << "Client socket error on fd: " << pfd.fd - << "Disconnect client." << std::endl; - closeSocket_(idx); + pollfd &pfd = pollFds_.at(idx); + SocketIOData &sIOD = sockIODataStore_.at(pfd.fd); + + if ((pfd.revents & POLLERR)) { + if (sIOD.errorCount >= SOCKETERRORLIMIT) { + closeSocket_(pfd.fd); + } else + ++sIOD.errorCount; + } else if (pfd.revents & POLLIN) { char recvBuffer[RECVBUFFLEN]; ssize_t bytesRead = recv(pfd.fd, &recvBuffer[0], RECVBUFFLEN, 0); if (bytesRead > 0) { SocketIOData &sockIOData = sockIODataStore_.at(pfd.fd); - Client const *client = 0; + std::string nickName = "*"; try { - client = clients_.at(pfd.fd); + Client const *const client = clients_.at(pfd.fd); + if (client && client->isRegistered()) + nickName = client->getNick(); } catch (std::exception const &e) { - std::cerr << "caught exception: " << e.what() << std::endl; + log_(e.what(), Logger::lmExcept); } - std::string nickName = "*"; - if (client && client->isRegistered()) - nickName = client->getNick(); - std::string &userInput = sockIOData.readBuffer; userInput += std::string(&recvBuffer[0], bytesRead); @@ -163,7 +163,8 @@ void Server::processClientSocketData_() { if (lfcrPos == std::string::npos || sockIOData.discardMode) { continue; } -#if VERBOSE_LOG + +#if 0 // For netcat debug std::ostringstream oss; oss << "You are client #" << idx + 1 - numServerSockets_ << ": \n" @@ -172,7 +173,7 @@ void Server::processClientSocketData_() { std::cout << "UserInput read from client #" << oss.str(); // Pong to client sendMsg(pfd.fd, oss.str()); -#endif // VERBOSE_LOG +#endif try { cmds_.run(*this, *clients_.at(pfd.fd), userInput); } catch (Error const &e) { @@ -189,16 +190,6 @@ void Server::processClientSocketData_() { userInput.clear(); } else if (bytesRead <= 0) { - // Comment will be deleted later, kept for reference for now - // this means, the client closed the connection - // POLLHUP isn't useful, because: - // When you kill netcat with Ctrl+C: - // Netcat's OS kernel sent a FIN packet to your server - // Your server's poll() detected this with POLLIN - // Your read() returned 0 (zero bytes) == "I got the FIN, connection closed" - // FIN is the TCP mechanism for orderly shutdown. - // That's why POLLIN + read() return 0—the FIN, - // and the zero read is how the kernel tells you "the peer closed." closeSocket_(idx); } } @@ -219,8 +210,9 @@ void Server::acceptClient_(int const serverFd) { reinterpret_cast(&sockAddrIn46), // NOLINT &sockLen); if (clientFd == -1) { - // throw -> no everything else can still run w/o problems - // silent return -> cannot sent anything to client without working socket anyways... + log_("Could not accept client:\n" + + srvHelp::printAddrfamilyAddrPort(sockAddrIn46), + Logger::lmWarn); return; } From 0e8d5002ebc44a299305231e54d7be562ea04123 Mon Sep 17 00:00:00 2001 From: Rapha Date: Fri, 28 Aug 2026 15:10:12 +0200 Subject: [PATCH 068/349] rfa: beautify output format --- includes/Logger.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/includes/Logger.hpp b/includes/Logger.hpp index 6a08787..a201c64 100644 --- a/includes/Logger.hpp +++ b/includes/Logger.hpp @@ -32,19 +32,19 @@ class Logger { switch (lm) { case Logger::lmInfo: - msg.insert(0, " [INFO] : "); + msg.insert(0, " [INFO]: "); *os << CGreen; break; case Logger::lmWarn: - msg.insert(0, " [WARN] : "); + msg.insert(0, " [WARN]: "); *os << CYellow; break; case Logger::lmError: - msg.insert(0, " [ERROR] : "); + msg.insert(0, " [ERROR]: "); *os << CRed; break; case Logger::lmExcept: - msg.insert(0, " [EXCEPTION] : "); + msg.insert(0, " [EXCEPTION]: "); *os << CMgnta; break; } From e92a24570713297773d42ddc45c4b716e48fab0c Mon Sep 17 00:00:00 2001 From: Rapha Date: Fri, 28 Aug 2026 15:12:13 +0200 Subject: [PATCH 069/349] feat: separate send and recv error counters fix: use fd instead of index --- includes/Server.hpp | 6 ++-- srcs/server/Server.cpp | 69 ++++++++++++++++++++++-------------------- 2 files changed, 41 insertions(+), 34 deletions(-) diff --git a/includes/Server.hpp b/includes/Server.hpp index 6985e02..4abb003 100644 --- a/includes/Server.hpp +++ b/includes/Server.hpp @@ -21,10 +21,12 @@ class Server { struct SocketIOData { std::string readBuffer; std::string writeBuffer; - size_t errorCount; + size_t recvErrorCount; + size_t sendErrorCount; bool discardMode; - SocketIOData() : errorCount(0), discardMode(false) {} + SocketIOData() + : recvErrorCount(0), sendErrorCount(0), discardMode(false) {} }; ChannelManager channels_; diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 3ff266a..998096c 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -23,21 +23,22 @@ bool Server::sendMsg(int const fd, std::string const &msg) { bool success = true; if (msg.length() > sIOD.writeBuffer.max_size() - sIOD.writeBuffer.length()) - sIOD.errorCount = SOCKETERRORLIMIT; + sIOD.sendErrorCount = SOCKETERRORLIMIT; else { sIOD.writeBuffer += msg; bytesSent = send(fd, sIOD.writeBuffer.c_str(), sIOD.writeBuffer.length(), MSG_NOSIGNAL); if (bytesSent <= 0) - ++sIOD.errorCount; + ++sIOD.sendErrorCount; } - if (sIOD.errorCount >= SOCKETERRORLIMIT) { + if (sIOD.sendErrorCount >= SOCKETERRORLIMIT) { closeSocket_(fd); success = false; } std::ostringstream oss; - oss << "SendMsg event:\n" << "WriteBuffer: '" << sIOD.writeBuffer << "':\n"; + oss << "SendMsg event:\n" + << " WriteBuffer: '" << sIOD.writeBuffer << "':\n"; if (!success) { oss << " send to FD " << fd << " failed."; log_(oss.str(), Logger::lmError); @@ -54,8 +55,8 @@ bool Server::sendMsg(int const fd, std::string const &msg) { if (!success) return false; - if (sIOD.errorCount > 0) - sIOD.errorCount = 0; + if (sIOD.sendErrorCount > 0) + sIOD.sendErrorCount = 0; if (static_cast(bytesSent) < sIOD.writeBuffer.length()) sIOD.writeBuffer.erase(0, bytesSent); else @@ -118,10 +119,10 @@ void Server::processClientSocketData_() { SocketIOData &sIOD = sockIODataStore_.at(pfd.fd); if ((pfd.revents & POLLERR)) { - if (sIOD.errorCount >= SOCKETERRORLIMIT) { + if (sIOD.recvErrorCount >= SOCKETERRORLIMIT) { closeSocket_(pfd.fd); } else - ++sIOD.errorCount; + ++sIOD.recvErrorCount; } else if (pfd.revents & POLLIN) { char recvBuffer[RECVBUFFLEN]; @@ -130,8 +131,9 @@ void Server::processClientSocketData_() { if (bytesRead > 0) { SocketIOData &sockIOData = sockIODataStore_.at(pfd.fd); std::string nickName = "*"; + Client const *client = 0; try { - Client const *const client = clients_.at(pfd.fd); + client = clients_.at(pfd.fd); if (client && client->isRegistered()) nickName = client->getNick(); } catch (std::exception const &e) { @@ -145,12 +147,18 @@ void Server::processClientSocketData_() { if ((userInput.length() >= MAXMSGLEN) && (lfcrPos == std::string::npos || lfcrPos > MAXMSGLEN - 2)) { sockIOData.discardMode = true; + ++sIOD.recvErrorCount; sendMsg(pfd.fd, Reply::build( serverName(), nickName, Reply::reply(Reply::ERR_INPUTTOOLONG, ""))); } if (sockIOData.discardMode) { + if (!client || !client->isRegistered() || + sIOD.recvErrorCount >= SOCKETERRORLIMIT) { + closeSocket_(pfd.fd); + continue; + } if (lfcrPos != std::string::npos) { userInput.erase(0, lfcrPos + 2); sockIOData.discardMode = false; @@ -163,34 +171,35 @@ void Server::processClientSocketData_() { if (lfcrPos == std::string::npos || sockIOData.discardMode) { continue; } - -#if 0 // For netcat debug - std::ostringstream oss; - oss << "You are client #" << idx + 1 - numServerSockets_ - << ": \n" - << "Srv received: " - << std::string(userInput) /* << std::endl */; - std::cout << "UserInput read from client #" << oss.str(); - // Pong to client - sendMsg(pfd.fd, oss.str()); + sIOD.recvErrorCount = 0; + userInput = userInput.substr(0, userInput.length() - 2); +#if 1 // For netcat debug + { + size_t const clientNumber = idx + 1 - numServerSockets_; + std::ostringstream oss; + oss << "You are client #" << clientNumber << ": \n" + << "Srv received: " << std::string(userInput) + << std::endl; + sendMsg(pfd.fd, oss.str()); + } #endif try { cmds_.run(*this, *clients_.at(pfd.fd), userInput); } catch (Error const &e) { if (e.hasReply()) { - Client const &c = *clients_.at(pfd.fd); - sendMsg(pfd.fd, Reply::build(serverName(), c.getNick(), + sendMsg(pfd.fd, Reply::build(serverName(), nickName, e.reply())); } else { - std::string tmp("Error while trying to send msg:\n"); + std::string tmp("Error while trying to run cmd:\n"); + tmp.append(userInput + "\n"); tmp.append(e.what()); - log_(tmp.c_str(), Logger::lmError); + log_(tmp.c_str(), Logger::lmExcept); } } userInput.clear(); } else if (bytesRead <= 0) { - closeSocket_(idx); + closeSocket_(pfd.fd); } } } @@ -255,8 +264,9 @@ void Server::closeSocket_(int const fd) { oss << "Closing server socket (" << srvHelp::getAdressFamilyStr(srvHelp::sockAddrFamily(fd)) << ")"; } else { - oss << "Closing client #" << idx + 1 - numServerSockets_ << " socket (" - << pollFds_.size() - 1 - numServerSockets_ << " clients remaining)"; + oss << "Closing client #" << idx + 1 - numServerSockets_ << ", sockFd " + << fd << "; (" << pollFds_.size() - 1 - numServerSockets_ + << " clients remaining)"; } log_(oss.str(), Logger::lmInfo); @@ -279,11 +289,6 @@ void Server::log_(std::string const &msg, Logger::logMode const lm) { * handles client connect and disconnect scenarios * on server stop signal, disconnects all clients and closes the server socket(s) - * if poll returns an error -> exception is thrown, except for two scenarios: - EINTR — The call was interrupted by a signal. This is the most common - retriable error. Retrying is standard practice. - EAGAIN — Temporary resource unavailability (rare with poll(), - but possible in some edge cases). */ void Server::run() { createServerSocket_(AF_INET); @@ -298,6 +303,6 @@ void Server::run() { processClientSocketData_(); } while (!pollFds_.empty()) { - closeSocket_(pollFds_.size() - 1); + closeSocket_(pollFds_.back().fd); } } From ef42d5b3593dca09298c12d5438e327a405ee9ce Mon Sep 17 00:00:00 2001 From: Rapha Date: Fri, 28 Aug 2026 15:10:12 +0200 Subject: [PATCH 070/349] rfa: beautify output format --- includes/Logger.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/includes/Logger.hpp b/includes/Logger.hpp index 6a08787..a201c64 100644 --- a/includes/Logger.hpp +++ b/includes/Logger.hpp @@ -32,19 +32,19 @@ class Logger { switch (lm) { case Logger::lmInfo: - msg.insert(0, " [INFO] : "); + msg.insert(0, " [INFO]: "); *os << CGreen; break; case Logger::lmWarn: - msg.insert(0, " [WARN] : "); + msg.insert(0, " [WARN]: "); *os << CYellow; break; case Logger::lmError: - msg.insert(0, " [ERROR] : "); + msg.insert(0, " [ERROR]: "); *os << CRed; break; case Logger::lmExcept: - msg.insert(0, " [EXCEPTION] : "); + msg.insert(0, " [EXCEPTION]: "); *os << CMgnta; break; } From 1695a6be57b6f1a2bb8db3d749c5f73b5d651d16 Mon Sep 17 00:00:00 2001 From: Rapha Date: Fri, 28 Aug 2026 15:12:13 +0200 Subject: [PATCH 071/349] feat: separate send and recv error counters fix: use fd instead of index --- includes/Server.hpp | 6 ++-- srcs/server/Server.cpp | 69 ++++++++++++++++++++++-------------------- 2 files changed, 41 insertions(+), 34 deletions(-) diff --git a/includes/Server.hpp b/includes/Server.hpp index 6985e02..4abb003 100644 --- a/includes/Server.hpp +++ b/includes/Server.hpp @@ -21,10 +21,12 @@ class Server { struct SocketIOData { std::string readBuffer; std::string writeBuffer; - size_t errorCount; + size_t recvErrorCount; + size_t sendErrorCount; bool discardMode; - SocketIOData() : errorCount(0), discardMode(false) {} + SocketIOData() + : recvErrorCount(0), sendErrorCount(0), discardMode(false) {} }; ChannelManager channels_; diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 3ff266a..998096c 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -23,21 +23,22 @@ bool Server::sendMsg(int const fd, std::string const &msg) { bool success = true; if (msg.length() > sIOD.writeBuffer.max_size() - sIOD.writeBuffer.length()) - sIOD.errorCount = SOCKETERRORLIMIT; + sIOD.sendErrorCount = SOCKETERRORLIMIT; else { sIOD.writeBuffer += msg; bytesSent = send(fd, sIOD.writeBuffer.c_str(), sIOD.writeBuffer.length(), MSG_NOSIGNAL); if (bytesSent <= 0) - ++sIOD.errorCount; + ++sIOD.sendErrorCount; } - if (sIOD.errorCount >= SOCKETERRORLIMIT) { + if (sIOD.sendErrorCount >= SOCKETERRORLIMIT) { closeSocket_(fd); success = false; } std::ostringstream oss; - oss << "SendMsg event:\n" << "WriteBuffer: '" << sIOD.writeBuffer << "':\n"; + oss << "SendMsg event:\n" + << " WriteBuffer: '" << sIOD.writeBuffer << "':\n"; if (!success) { oss << " send to FD " << fd << " failed."; log_(oss.str(), Logger::lmError); @@ -54,8 +55,8 @@ bool Server::sendMsg(int const fd, std::string const &msg) { if (!success) return false; - if (sIOD.errorCount > 0) - sIOD.errorCount = 0; + if (sIOD.sendErrorCount > 0) + sIOD.sendErrorCount = 0; if (static_cast(bytesSent) < sIOD.writeBuffer.length()) sIOD.writeBuffer.erase(0, bytesSent); else @@ -118,10 +119,10 @@ void Server::processClientSocketData_() { SocketIOData &sIOD = sockIODataStore_.at(pfd.fd); if ((pfd.revents & POLLERR)) { - if (sIOD.errorCount >= SOCKETERRORLIMIT) { + if (sIOD.recvErrorCount >= SOCKETERRORLIMIT) { closeSocket_(pfd.fd); } else - ++sIOD.errorCount; + ++sIOD.recvErrorCount; } else if (pfd.revents & POLLIN) { char recvBuffer[RECVBUFFLEN]; @@ -130,8 +131,9 @@ void Server::processClientSocketData_() { if (bytesRead > 0) { SocketIOData &sockIOData = sockIODataStore_.at(pfd.fd); std::string nickName = "*"; + Client const *client = 0; try { - Client const *const client = clients_.at(pfd.fd); + client = clients_.at(pfd.fd); if (client && client->isRegistered()) nickName = client->getNick(); } catch (std::exception const &e) { @@ -145,12 +147,18 @@ void Server::processClientSocketData_() { if ((userInput.length() >= MAXMSGLEN) && (lfcrPos == std::string::npos || lfcrPos > MAXMSGLEN - 2)) { sockIOData.discardMode = true; + ++sIOD.recvErrorCount; sendMsg(pfd.fd, Reply::build( serverName(), nickName, Reply::reply(Reply::ERR_INPUTTOOLONG, ""))); } if (sockIOData.discardMode) { + if (!client || !client->isRegistered() || + sIOD.recvErrorCount >= SOCKETERRORLIMIT) { + closeSocket_(pfd.fd); + continue; + } if (lfcrPos != std::string::npos) { userInput.erase(0, lfcrPos + 2); sockIOData.discardMode = false; @@ -163,34 +171,35 @@ void Server::processClientSocketData_() { if (lfcrPos == std::string::npos || sockIOData.discardMode) { continue; } - -#if 0 // For netcat debug - std::ostringstream oss; - oss << "You are client #" << idx + 1 - numServerSockets_ - << ": \n" - << "Srv received: " - << std::string(userInput) /* << std::endl */; - std::cout << "UserInput read from client #" << oss.str(); - // Pong to client - sendMsg(pfd.fd, oss.str()); + sIOD.recvErrorCount = 0; + userInput = userInput.substr(0, userInput.length() - 2); +#if 1 // For netcat debug + { + size_t const clientNumber = idx + 1 - numServerSockets_; + std::ostringstream oss; + oss << "You are client #" << clientNumber << ": \n" + << "Srv received: " << std::string(userInput) + << std::endl; + sendMsg(pfd.fd, oss.str()); + } #endif try { cmds_.run(*this, *clients_.at(pfd.fd), userInput); } catch (Error const &e) { if (e.hasReply()) { - Client const &c = *clients_.at(pfd.fd); - sendMsg(pfd.fd, Reply::build(serverName(), c.getNick(), + sendMsg(pfd.fd, Reply::build(serverName(), nickName, e.reply())); } else { - std::string tmp("Error while trying to send msg:\n"); + std::string tmp("Error while trying to run cmd:\n"); + tmp.append(userInput + "\n"); tmp.append(e.what()); - log_(tmp.c_str(), Logger::lmError); + log_(tmp.c_str(), Logger::lmExcept); } } userInput.clear(); } else if (bytesRead <= 0) { - closeSocket_(idx); + closeSocket_(pfd.fd); } } } @@ -255,8 +264,9 @@ void Server::closeSocket_(int const fd) { oss << "Closing server socket (" << srvHelp::getAdressFamilyStr(srvHelp::sockAddrFamily(fd)) << ")"; } else { - oss << "Closing client #" << idx + 1 - numServerSockets_ << " socket (" - << pollFds_.size() - 1 - numServerSockets_ << " clients remaining)"; + oss << "Closing client #" << idx + 1 - numServerSockets_ << ", sockFd " + << fd << "; (" << pollFds_.size() - 1 - numServerSockets_ + << " clients remaining)"; } log_(oss.str(), Logger::lmInfo); @@ -279,11 +289,6 @@ void Server::log_(std::string const &msg, Logger::logMode const lm) { * handles client connect and disconnect scenarios * on server stop signal, disconnects all clients and closes the server socket(s) - * if poll returns an error -> exception is thrown, except for two scenarios: - EINTR — The call was interrupted by a signal. This is the most common - retriable error. Retrying is standard practice. - EAGAIN — Temporary resource unavailability (rare with poll(), - but possible in some edge cases). */ void Server::run() { createServerSocket_(AF_INET); @@ -298,6 +303,6 @@ void Server::run() { processClientSocketData_(); } while (!pollFds_.empty()) { - closeSocket_(pollFds_.size() - 1); + closeSocket_(pollFds_.back().fd); } } From 2ec1378abae75a588ed8de4be505643ef2ad2a74 Mon Sep 17 00:00:00 2001 From: Rapha Date: Sat, 29 Aug 2026 12:17:13 +0200 Subject: [PATCH 072/349] rfa: pragma instead of include guards --- includes/SignalHandler.hpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/includes/SignalHandler.hpp b/includes/SignalHandler.hpp index f408d47..9dd21e5 100644 --- a/includes/SignalHandler.hpp +++ b/includes/SignalHandler.hpp @@ -1,5 +1,4 @@ -#ifndef SIGNALHANDLER_HPP -#define SIGNALHANDLER_HPP +#pragma once #include #include @@ -10,6 +9,7 @@ class SignalHandler { private: volatile static std::sig_atomic_t stopServerLoop_; static void handleStopSignals_(int signal); + std::vector stopSignals_; bool setStopSignals_(sighandler_t handler); @@ -21,5 +21,3 @@ class SignalHandler { ~SignalHandler(); static bool stopLoopSignalReceived(); }; - -#endif // SIGNALHANDLER_HPP From 350a7c48918cf6ee25a2e64162e1771ba9b68194 Mon Sep 17 00:00:00 2001 From: Rapha Date: Sat, 29 Aug 2026 12:18:07 +0200 Subject: [PATCH 073/349] rfa: move (inline) log function from header to cpp file as regular function --- Makefile | 1 + includes/Logger.hpp | 26 +------------------------- srcs/server/Logger.cpp | 31 +++++++++++++++++++++++++++++++ 3 files changed, 33 insertions(+), 25 deletions(-) create mode 100644 srcs/server/Logger.cpp diff --git a/Makefile b/Makefile index 0d53f4f..c6bde47 100644 --- a/Makefile +++ b/Makefile @@ -17,6 +17,7 @@ SRCS_SERVER = Server \ ClientManager \ Channel \ ChannelManager \ + Logger \ SignalHandler SRCS_COMMANDS = CommandProcessor \ diff --git a/includes/Logger.hpp b/includes/Logger.hpp index a201c64..5337f1e 100644 --- a/includes/Logger.hpp +++ b/includes/Logger.hpp @@ -25,29 +25,5 @@ class Logger { prefix_ = prefix; } - void logMsg(std::string msg, logMode const lm) const { - std::ostream *os = &std::cout; - if (lm == Logger::lmError || lm == Logger::lmExcept) - os = &std::cerr; - - switch (lm) { - case Logger::lmInfo: - msg.insert(0, " [INFO]: "); - *os << CGreen; - break; - case Logger::lmWarn: - msg.insert(0, " [WARN]: "); - *os << CYellow; - break; - case Logger::lmError: - msg.insert(0, " [ERROR]: "); - *os << CRed; - break; - case Logger::lmExcept: - msg.insert(0, " [EXCEPTION]: "); - *os << CMgnta; - break; - } - *os << nowStr_() << " " << prefix_ << msg << CReset << std::endl; - } + void logMsg(std::string msg, logMode const lm) const; }; diff --git a/srcs/server/Logger.cpp b/srcs/server/Logger.cpp new file mode 100644 index 0000000..61191de --- /dev/null +++ b/srcs/server/Logger.cpp @@ -0,0 +1,31 @@ +#include "Logger.hpp" + +/* + Log strings in four categories (severity) in different colours + to stdout/stderr respectively. +*/ +void Logger::logMsg(std::string msg, logMode const lm) const { + std::ostream *os = &std::cout; + if (lm == Logger::lmError || lm == Logger::lmExcept) + os = &std::cerr; + + switch (lm) { + case Logger::lmInfo: + msg.insert(0, " [INFO ]: "); + *os << CGreen; + break; + case Logger::lmWarn: + msg.insert(0, " [WARN ]: "); + *os << CYellow; + break; + case Logger::lmError: + msg.insert(0, " [ERROR ]: "); + *os << CRed; + break; + case Logger::lmExcept: + msg.insert(0, " [EXCEPT]: "); + *os << CMgnta; + break; + } + *os << nowStr_() << " " << prefix_ << msg << CReset << std::endl; +} From 1bc66900cfdba77229578d6d5e5315ae0d0913ed Mon Sep 17 00:00:00 2001 From: Rapha Date: Sat, 29 Aug 2026 14:17:58 +0200 Subject: [PATCH 074/349] rfa: tidy && format --- includes/Logger.hpp | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/includes/Logger.hpp b/includes/Logger.hpp index 5337f1e..145c3e6 100644 --- a/includes/Logger.hpp +++ b/includes/Logger.hpp @@ -8,22 +8,20 @@ #include class Logger { - std::string prefix_; + std::string prefix_; - std::string nowStr_() const { + static std::string nowStr_() { time_t now = time(0); struct tm *timeinfo = localtime(&now); char buffer[32]; - strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", timeinfo); - return std::string(buffer); + strftime(&buffer[0], sizeof(buffer), "%Y-%m-%d %H:%M:%S", timeinfo); + return std::string(&buffer[0]); } public: enum logMode { lmInfo, lmWarn, lmError, lmExcept }; - Logger(std::string const &prefix) { - prefix_ = prefix; - } + Logger(std::string const &prefix) : prefix_(prefix) {} void logMsg(std::string msg, logMode const lm) const; }; From e7fd2af0146cff892dab27a2141c586f16b3c461 Mon Sep 17 00:00:00 2001 From: Rapha Date: Sat, 29 Aug 2026 14:19:37 +0200 Subject: [PATCH 075/349] feat: * use Server d'tor to cleanup sockets fix: * do not completely clear client read buffer when composing cmdLine rfa: * split long functions --- includes/Server.hpp | 34 ++++-- srcs/server/Server.cpp | 261 +++++++++++++++++++++++++---------------- 2 files changed, 183 insertions(+), 112 deletions(-) diff --git a/includes/Server.hpp b/includes/Server.hpp index 4abb003..68b61fe 100644 --- a/includes/Server.hpp +++ b/includes/Server.hpp @@ -12,21 +12,25 @@ #define SERVER_NAME "irc.local" #define MAX_CONNECTIONS 512 -#define RECVBUFFLEN 516 +#define RECVBUFFLEN 1024 #define MAXMSGLEN 512 -#define SOCKETERRORLIMIT 16 +#define SOCKETERRORLIMIT 8 + +#define NETCATDBG 1 // For netcat debug class Server { private: struct SocketIOData { std::string readBuffer; std::string writeBuffer; + ssize_t bytesRead; size_t recvErrorCount; size_t sendErrorCount; bool discardMode; SocketIOData() - : recvErrorCount(0), sendErrorCount(0), discardMode(false) {} + : bytesRead(0), recvErrorCount(0), sendErrorCount(0), + discardMode(false) {} }; ChannelManager channels_; @@ -39,28 +43,36 @@ class Server { size_t numServerSockets_; std::vector pollFds_; - // std::vector clientInputBuffers_; std::map sockIODataStore_; Logger logger_; - // Server(Server const &other); - // Server &operator=(Server const &other); - + void setupServerSockets_(); int createServerSocket_(int const domain); void processServerSocketData_(); void acceptClient_(int const serverFd); + void processClientSockets_(); void storePollFd_(int const socketFd); - void processClientSocketData_(); - void closeSocket_(int const fd); + bool checkSocket_(struct pollfd &pfd, SocketIOData &sIOD); + bool processClientSocketData_(struct pollfd &pfd, SocketIOData &sIOD); + void setClientInfo_(int fd, Client const **clntPtr, std::string &nick, + bool &isReg); + bool clientDataSanityCheck_(SocketIOData &sIOD, int fd, + /* Client const *const client, */ + std::string const &nickName, + bool const isRegistered); + void closeSocket_(int const fd); void log_(std::string const &msg, Logger::logMode const lm); + Server(Server const &other); + Server &operator=(Server const &other); + public: Server(int const port, std::string const &password); - // ~Server(); + ~Server(); - std::string const &serverName() { + std::string const &serverName() const { return serverName_; }; diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 998096c..720268b 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -10,13 +10,25 @@ #include /* - Server Constructor - * set port and password from command line args + Server Constructor + * set port and password from command line args + * init the logger prefix (server name) */ Server::Server(int const port, std::string const &password) : port_(port), password_(password), serverName_(SERVER_NAME), numServerSockets_(0), logger_(serverName_ + ": ") {} +Server::~Server() { + while (!pollFds_.empty()) + closeSocket_(pollFds_.back().fd); +} + +/* + sendMsg + * send string msg to a network socket + * uses i/o buffers and error counters in SocketIOData + * closes the socket (== disconnects the client) in case of too many errors +*/ bool Server::sendMsg(int const fd, std::string const &msg) { SocketIOData &sIOD = sockIODataStore_.at(fd); ssize_t bytesSent = -1; @@ -55,8 +67,7 @@ bool Server::sendMsg(int const fd, std::string const &msg) { if (!success) return false; - if (sIOD.sendErrorCount > 0) - sIOD.sendErrorCount = 0; + sIOD.sendErrorCount = 0; if (static_cast(bytesSent) < sIOD.writeBuffer.length()) sIOD.writeBuffer.erase(0, bytesSent); else @@ -64,8 +75,28 @@ bool Server::sendMsg(int const fd, std::string const &msg) { return true; } +void Server::setupServerSockets_() { + int domArr[] = {AF_INET, AF_INET6}; + std::vector domains(domArr, // NOLINT + domArr + // NOLINT + (sizeof(domArr) / sizeof(domArr[0]))); + for (size_t idx = 0; idx < domains.size(); ++idx) { + try { + createServerSocket_(domains[idx]); + } catch (std::exception const &e) { + log_(e.what(), Logger::lmExcept); + } + } + if (numServerSockets_ == 0) + throw std::runtime_error("No server socket could be setup correctly."); +} + /* - tbd + createServerSocket_ + * Setup a listening network socket for the address family/domain specified + * only supports IPv4 and IPv6 + * for IPv6, uses and IPv6only socket, so the IPv4 is kept separate + (not all systems support dual stack handling of IPv4 through IPv6) */ int Server::createServerSocket_(int const domain) { if (domain != AF_INET && domain != AF_INET6) @@ -104,7 +135,9 @@ int Server::createServerSocket_(int const domain) { } void Server::processServerSocketData_() { - for (size_t idx = 0; idx < numServerSockets_; ++idx) { + for (size_t idx = 0; + !SignalHandler::stopLoopSignalReceived() && idx < numServerSockets_; + ++idx) { struct pollfd const &serverPollFds = pollFds_[idx]; if ((serverPollFds.revents & POLLERR)) throw std::runtime_error("Server socket error."); @@ -113,92 +146,118 @@ void Server::processServerSocketData_() { } } -void Server::processClientSocketData_() { - for (size_t idx = numServerSockets_; idx < pollFds_.size(); ++idx) { +bool Server::checkSocket_(struct pollfd &pfd, SocketIOData &sIOD) { + if ((pfd.revents & POLLERR)) { + if (sIOD.recvErrorCount >= SOCKETERRORLIMIT) + closeSocket_(pfd.fd); + else + ++sIOD.recvErrorCount; + return false; + } + return true; +} + +void Server::setClientInfo_(int fd, Client const **clntPtr, std::string &nick, + bool &isReg) { + try { + *clntPtr = clients_.at(fd); + if (*clntPtr && (*clntPtr)->isRegistered()) { + isReg = true; + nick = (*clntPtr)->getNick(); + } + } catch (std::exception const &e) { + log_(e.what(), Logger::lmExcept); + } +} + +bool Server::clientDataSanityCheck_(SocketIOData &sIOD, int fd, /* + Client const *const client, */ + std::string const &nickName, + bool const isRegistered) { + std::string &rdBuffer = sIOD.readBuffer; + size_t lfcrPos = rdBuffer.find("\r\n"); + if ((rdBuffer.length() >= MAXMSGLEN) && + (lfcrPos == std::string::npos || lfcrPos > MAXMSGLEN - 2)) { + sIOD.discardMode = true; + ++sIOD.recvErrorCount; + sendMsg(fd, Reply::build(serverName(), nickName, + Reply::reply(Reply::ERR_INPUTTOOLONG, ""))); + } + if (sIOD.discardMode) { + if (!isRegistered || sIOD.recvErrorCount >= SOCKETERRORLIMIT) { + closeSocket_(fd); + return false; + } + if (lfcrPos != std::string::npos) { + rdBuffer.erase(0, lfcrPos + 2); + sIOD.discardMode = false; + lfcrPos = rdBuffer.find("\r\n"); + } else if ('\r' == *rdBuffer.rbegin()) + rdBuffer = "\r"; + else + rdBuffer.clear(); + } + if (lfcrPos == std::string::npos || sIOD.discardMode) { + return false; + } + return true; +} + +bool Server::processClientSocketData_(struct pollfd &pfd, SocketIOData &sIOD) { + Client const *client = 0; + std::string nickName = "*"; + bool clntIsReg = false; + setClientInfo_(pfd.fd, &client, nickName, clntIsReg); + + if (!clientDataSanityCheck_(sIOD, pfd.fd, nickName, clntIsReg)) + return false; + + sIOD.recvErrorCount = 0; + size_t lfcrPos = sIOD.readBuffer.find("\r\n"); + std::string cmdLine = sIOD.readBuffer.substr(0, lfcrPos); + sIOD.readBuffer = sIOD.readBuffer.substr(lfcrPos + 2); + +#if NETCATDBG + { + // size_t const clientNumber = idx + 1 - numServerSockets_; + std::ostringstream oss; + oss << "You are client '" << nickName << "' (fd " << pfd.fd << "): \n" + << "Srv received: " << std::string(cmdLine) << std::endl; + sendMsg(pfd.fd, oss.str()); + } +#endif + + try { + cmds_.run(*this, *clients_.at(pfd.fd), cmdLine); + } catch (Error const &e) { + if (e.hasReply()) { + sendMsg(pfd.fd, Reply::build(serverName(), nickName, e.reply())); + } else { + std::string tmp("Error while trying to run cmd:\n"); + tmp.append(cmdLine + "\n" + e.what()); + log_(tmp, Logger::lmExcept); + } + } + return true; +} + +void Server::processClientSockets_() { + for (size_t idx = numServerSockets_; + !SignalHandler::stopLoopSignalReceived() && idx < pollFds_.size(); + ++idx) { pollfd &pfd = pollFds_.at(idx); SocketIOData &sIOD = sockIODataStore_.at(pfd.fd); - if ((pfd.revents & POLLERR)) { - if (sIOD.recvErrorCount >= SOCKETERRORLIMIT) { - closeSocket_(pfd.fd); - } else - ++sIOD.recvErrorCount; - - } else if (pfd.revents & POLLIN) { - char recvBuffer[RECVBUFFLEN]; - ssize_t bytesRead = recv(pfd.fd, &recvBuffer[0], RECVBUFFLEN, 0); - - if (bytesRead > 0) { - SocketIOData &sockIOData = sockIODataStore_.at(pfd.fd); - std::string nickName = "*"; - Client const *client = 0; - try { - client = clients_.at(pfd.fd); - if (client && client->isRegistered()) - nickName = client->getNick(); - } catch (std::exception const &e) { - log_(e.what(), Logger::lmExcept); - } - - std::string &userInput = sockIOData.readBuffer; - userInput += std::string(&recvBuffer[0], bytesRead); - - size_t lfcrPos = userInput.find("\r\n"); - if ((userInput.length() >= MAXMSGLEN) && - (lfcrPos == std::string::npos || lfcrPos > MAXMSGLEN - 2)) { - sockIOData.discardMode = true; - ++sIOD.recvErrorCount; - sendMsg(pfd.fd, - Reply::build( - serverName(), nickName, - Reply::reply(Reply::ERR_INPUTTOOLONG, ""))); - } - if (sockIOData.discardMode) { - if (!client || !client->isRegistered() || - sIOD.recvErrorCount >= SOCKETERRORLIMIT) { - closeSocket_(pfd.fd); - continue; - } - if (lfcrPos != std::string::npos) { - userInput.erase(0, lfcrPos + 2); - sockIOData.discardMode = false; - lfcrPos = userInput.find("\r\n"); - } else if ('\r' == *userInput.rbegin()) - userInput = "\r"; - else - userInput.clear(); - } - if (lfcrPos == std::string::npos || sockIOData.discardMode) { - continue; - } - sIOD.recvErrorCount = 0; - userInput = userInput.substr(0, userInput.length() - 2); -#if 1 // For netcat debug - { - size_t const clientNumber = idx + 1 - numServerSockets_; - std::ostringstream oss; - oss << "You are client #" << clientNumber << ": \n" - << "Srv received: " << std::string(userInput) - << std::endl; - sendMsg(pfd.fd, oss.str()); - } -#endif - try { - cmds_.run(*this, *clients_.at(pfd.fd), userInput); - } catch (Error const &e) { - if (e.hasReply()) { - sendMsg(pfd.fd, Reply::build(serverName(), nickName, - e.reply())); - } else { - std::string tmp("Error while trying to run cmd:\n"); - tmp.append(userInput + "\n"); - tmp.append(e.what()); - log_(tmp.c_str(), Logger::lmExcept); - } - } - userInput.clear(); - - } else if (bytesRead <= 0) { + if (!checkSocket_(pfd, sIOD)) + continue; + if (pfd.revents & POLLIN) { + char recvBuffer[RECVBUFFLEN]; + sIOD.bytesRead = recv(pfd.fd, &recvBuffer[0], RECVBUFFLEN, 0); + + if (sIOD.bytesRead > 0) { + sIOD.readBuffer += std::string(&recvBuffer[0], sIOD.bytesRead); + processClientSocketData_(pfd, sIOD); + } else if (sIOD.bytesRead <= 0) { closeSocket_(pfd.fd); } } @@ -257,10 +316,12 @@ void Server::closeSocket_(int const fd) { std::ostringstream oss; if (idx < numServerSockets_) { - if (pollFds_.size() != idx + 1) - return log_("Internal error: Close client sockets before " - "closing server!", - Logger::lmError); + if (pollFds_.size() != idx + 1) { + log_("Internal error: Close client sockets before " + "closing server!", + Logger::lmError); + return; + } oss << "Closing server socket (" << srvHelp::getAdressFamilyStr(srvHelp::sockAddrFamily(fd)) << ")"; } else { @@ -291,18 +352,16 @@ void Server::log_(std::string const &msg, Logger::logMode const lm) { the server socket(s) */ void Server::run() { - createServerSocket_(AF_INET); - createServerSocket_(AF_INET6); + setupServerSockets_(); while (!SignalHandler::stopLoopSignalReceived()) { int pollResult = poll(pollFds_.data(), pollFds_.size(), -1); - if (pollResult == -1) - throw std::runtime_error("Fatal error: poll failed."); + if (pollResult == -1) { + log_("Fatal error: poll failed.", Logger::lmError); + break; + } processServerSocketData_(); - processClientSocketData_(); - } - while (!pollFds_.empty()) { - closeSocket_(pollFds_.back().fd); + processClientSockets_(); } } From 32669f4614ccf85cd4b841715e85c93e84ba3d03 Mon Sep 17 00:00:00 2001 From: Rapha Date: Sat, 29 Aug 2026 14:31:07 +0200 Subject: [PATCH 076/349] rfa: tidy && format --- includes/Server.hpp | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/includes/Server.hpp b/includes/Server.hpp index 68b61fe..de6d0ad 100644 --- a/includes/Server.hpp +++ b/includes/Server.hpp @@ -53,16 +53,15 @@ class Server { void acceptClient_(int const serverFd); void processClientSockets_(); void storePollFd_(int const socketFd); - bool checkSocket_(struct pollfd &pfd, SocketIOData &sIOD); - bool processClientSocketData_(struct pollfd &pfd, SocketIOData &sIOD); - void setClientInfo_(int fd, Client const **clntPtr, std::string &nick, - bool &isReg); - bool clientDataSanityCheck_(SocketIOData &sIOD, int fd, - /* Client const *const client, */ - std::string const &nickName, - bool const isRegistered); - void closeSocket_(int const fd); - void log_(std::string const &msg, Logger::logMode const lm); + bool checkSocket_(struct pollfd &pfd, SocketIOData &sIOD); + bool processClientSocketData_(struct pollfd &pfd, SocketIOData &sIOD); + void setClientInfo_(int fd, Client const **clntPtr, std::string &nick, + bool &isReg); + size_t clientDataSanityCheck_(SocketIOData &sIOD, int fd, + std::string const &nickName, + bool const isRegistered); + void closeSocket_(int const fd); + void log_(std::string const &msg, Logger::logMode const lm); Server(Server const &other); Server &operator=(Server const &other); From de192f10958cb314d9dc4100dace2e000825bb92 Mon Sep 17 00:00:00 2001 From: Rapha Date: Sat, 29 Aug 2026 14:31:46 +0200 Subject: [PATCH 077/349] rfa: avoid recalculation & simplify split function chain --- srcs/server/Server.cpp | 37 +++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 720268b..a9f806a 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -170,10 +170,9 @@ void Server::setClientInfo_(int fd, Client const **clntPtr, std::string &nick, } } -bool Server::clientDataSanityCheck_(SocketIOData &sIOD, int fd, /* - Client const *const client, */ - std::string const &nickName, - bool const isRegistered) { +size_t Server::clientDataSanityCheck_(SocketIOData &sIOD, int fd, + std::string const &nickName, + bool const isRegistered) { std::string &rdBuffer = sIOD.readBuffer; size_t lfcrPos = rdBuffer.find("\r\n"); if ((rdBuffer.length() >= MAXMSGLEN) && @@ -186,21 +185,20 @@ bool Server::clientDataSanityCheck_(SocketIOData &sIOD, int fd, /* if (sIOD.discardMode) { if (!isRegistered || sIOD.recvErrorCount >= SOCKETERRORLIMIT) { closeSocket_(fd); - return false; + return std::string::npos; } if (lfcrPos != std::string::npos) { rdBuffer.erase(0, lfcrPos + 2); sIOD.discardMode = false; lfcrPos = rdBuffer.find("\r\n"); - } else if ('\r' == *rdBuffer.rbegin()) - rdBuffer = "\r"; - else - rdBuffer.clear(); - } - if (lfcrPos == std::string::npos || sIOD.discardMode) { - return false; + } else /* lfcrPos == std::string::npos */ { + if ('\r' == *rdBuffer.rbegin()) + rdBuffer = "\r"; + else + rdBuffer.clear(); + } } - return true; + return lfcrPos; } bool Server::processClientSocketData_(struct pollfd &pfd, SocketIOData &sIOD) { @@ -209,24 +207,23 @@ bool Server::processClientSocketData_(struct pollfd &pfd, SocketIOData &sIOD) { bool clntIsReg = false; setClientInfo_(pfd.fd, &client, nickName, clntIsReg); - if (!clientDataSanityCheck_(sIOD, pfd.fd, nickName, clntIsReg)) + size_t lfcrPos = clientDataSanityCheck_(sIOD, pfd.fd, nickName, clntIsReg); + if (lfcrPos == std::string::npos) return false; - sIOD.recvErrorCount = 0; - size_t lfcrPos = sIOD.readBuffer.find("\r\n"); - std::string cmdLine = sIOD.readBuffer.substr(0, lfcrPos); - sIOD.readBuffer = sIOD.readBuffer.substr(lfcrPos + 2); - #if NETCATDBG { // size_t const clientNumber = idx + 1 - numServerSockets_; std::ostringstream oss; oss << "You are client '" << nickName << "' (fd " << pfd.fd << "): \n" - << "Srv received: " << std::string(cmdLine) << std::endl; + << "Srv received: " << std::string(sIOD.readBuffer) << std::endl; sendMsg(pfd.fd, oss.str()); } #endif + sIOD.recvErrorCount = 0; + std::string cmdLine = sIOD.readBuffer.substr(0, lfcrPos); + sIOD.readBuffer = sIOD.readBuffer.substr(lfcrPos + 2); try { cmds_.run(*this, *clients_.at(pfd.fd), cmdLine); } catch (Error const &e) { From 94c3e4ef204ff31cbd574f4f2eb6393e9e736109 Mon Sep 17 00:00:00 2001 From: Rapha Date: Sat, 29 Aug 2026 14:35:57 +0200 Subject: [PATCH 078/349] feat: log stop signal msg to std::cerr --- srcs/server/SignalHandler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/srcs/server/SignalHandler.cpp b/srcs/server/SignalHandler.cpp index 42c4263..c443815 100644 --- a/srcs/server/SignalHandler.cpp +++ b/srcs/server/SignalHandler.cpp @@ -21,7 +21,7 @@ volatile std::sig_atomic_t SignalHandler::stopServerLoop_ = 0; */ void SignalHandler::handleStopSignals_(int signal) { stopServerLoop_ = signal; - std::cout << "Caught stop signal: " << stopServerLoop_ << "." << std::endl; + std::cerr << "Caught stop signal: " << stopServerLoop_ << "." << std::endl; } /* From 0e829fbc834a18e987c1ecffd55d95467a5577e6 Mon Sep 17 00:00:00 2001 From: Rapha Date: Mon, 31 Aug 2026 12:30:42 +0200 Subject: [PATCH 079/349] rfa: rename function to composeAddrfamAddrPortStr to better describe its purpose --- includes/ServerHelper.hpp | 6 +++--- srcs/server/Server.cpp | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/includes/ServerHelper.hpp b/includes/ServerHelper.hpp index ee9b543..432551c 100644 --- a/includes/ServerHelper.hpp +++ b/includes/ServerHelper.hpp @@ -72,13 +72,13 @@ inline void bindServerSocket(int const socketFd, int const domain, // NOLINT getAdressFamilyStr(domain) + " !"); } -inline std::string printAddrfamilyAddrPort(socketAddrX const &sockAddrIn46) { +inline std::string composeAddrfamAddrPortStr(socketAddrX const &sockAddrIn46) { char addrBuffer[128]; socklen_t const addrBuffLen = sizeof(addrBuffer) / sizeof(char); sa_family_t const saFamily = - reinterpret_cast(sockAddrIn46.sai4) // NOLINT - .sa_family; + // NOLINTNEXTLINE + reinterpret_cast(sockAddrIn46.sai4).sa_family; void const *addrPtr = 0; uint16_t portNumber = 0; diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index a9f806a..c112cff 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -276,7 +276,7 @@ void Server::acceptClient_(int const serverFd) { &sockLen); if (clientFd == -1) { log_("Could not accept client:\n" + - srvHelp::printAddrfamilyAddrPort(sockAddrIn46), + srvHelp::composeAddrfamAddrPortStr(sockAddrIn46), Logger::lmWarn); return; } @@ -287,7 +287,7 @@ void Server::acceptClient_(int const serverFd) { std::cout << "Client connected:\n" << " fd: " << clientFd << "\n" - << srvHelp::printAddrfamilyAddrPort(sockAddrIn46) << std::endl; + << srvHelp::composeAddrfamAddrPortStr(sockAddrIn46) << std::endl; } void Server::storePollFd_(int const socketFd) { From 12cbb99404eb323cb8e0d44b4f48537d394f3cf4 Mon Sep 17 00:00:00 2001 From: Rapha Date: Mon, 31 Aug 2026 13:54:20 +0200 Subject: [PATCH 080/349] rfa: update clang format rules --- .clang-format | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.clang-format b/.clang-format index 45b8729..449ff95 100644 --- a/.clang-format +++ b/.clang-format @@ -7,7 +7,7 @@ IndentWidth: 4 ColumnLimit: 80 IndentCaseLabels: true AllowShortFunctionsOnASingleLine: Empty -AlignConsecutiveDeclarations: AcrossEmptyLinesAndComments +AlignConsecutiveDeclarations: true SeparateDefinitionBlocks: Always ReflowComments: false From 28c64bdde90f6c1a8ef0e32868b62cef8c300dac Mon Sep 17 00:00:00 2001 From: Rapha Date: Mon, 31 Aug 2026 13:56:04 +0200 Subject: [PATCH 081/349] rfa: clang-format --- includes/Channel.hpp | 4 ++-- includes/ChannelManager.hpp | 16 ++++++++-------- includes/Client.hpp | 28 ++++++++++++++-------------- includes/ClientManager.hpp | 6 +++--- includes/Logger.hpp | 2 +- includes/MemberSet.hpp | 8 ++++---- includes/Mode.hpp | 4 ++-- includes/ModeSet.hpp | 8 ++++---- includes/ServerHelper.hpp | 4 ++-- includes/SignalHandler.hpp | 4 ++-- srcs/main.cpp | 2 +- srcs/server/SignalHandler.cpp | 4 ++-- 12 files changed, 45 insertions(+), 45 deletions(-) diff --git a/includes/Channel.hpp b/includes/Channel.hpp index 3ef1aaf..e43cb29 100644 --- a/includes/Channel.hpp +++ b/includes/Channel.hpp @@ -38,8 +38,8 @@ class Channel { Channel(const std::string &name, const std::string &nameCaseFolded, const std::string &key = ""); - void join(Client *client, const std::string &key = ""); - void part(Client *client); + void join(Client *client, const std::string &key = ""); + void part(Client *client); size_t size() const; const std::string &getNameCaseFolded() const; diff --git a/includes/ChannelManager.hpp b/includes/ChannelManager.hpp index 19502f0..5da20f9 100644 --- a/includes/ChannelManager.hpp +++ b/includes/ChannelManager.hpp @@ -17,14 +17,14 @@ class ChannelManager { Channel &at(const std::string &name); - bool hasChannel(const std::string &name) const; - bool isMember(Client *client, const std::string &name) const; + bool hasChannel(const std::string &name) const; + bool isMember(Client *client, const std::string &name) const; - void join(Client *client, const std::string &name, - const std::string &key = ""); - void part(Client *client, const std::string &name); - void removeChannel(const std::string &name); + void join(Client *client, const std::string &name, + const std::string &key = ""); + void part(Client *client, const std::string &name); + void removeChannel(const std::string &name); - void broadcast(const std::string &name, const std::string &msg, - const Client *skip = 0); + void broadcast(const std::string &name, const std::string &msg, + const Client *skip = 0); }; diff --git a/includes/Client.hpp b/includes/Client.hpp index c1b1cdd..0e31524 100644 --- a/includes/Client.hpp +++ b/includes/Client.hpp @@ -31,9 +31,9 @@ class Client { std::string getHostname() const; std::string getRealname() const; - bool isRegistered() const; - bool isOperator() const; - bool hasPassAccepted() const; + bool isRegistered() const; + bool isOperator() const; + bool hasPassAccepted() const; std::string getPrefix() const; bool hasIdentity() const; @@ -41,21 +41,21 @@ class Client { void setUser(const std::string &user, const std::string &real); void setHostname(const std::string &host); - void setRegistered(bool value); - void setPassAccepted(bool value); - void setOperator(bool value); + void setRegistered(bool value); + void setPassAccepted(bool value); + void setOperator(bool value); - bool isInvitedTo(const std::string &name) const; - void addInvite(const std::string &name); - void removeInvite(const std::string &name); + bool isInvitedTo(const std::string &name) const; + void addInvite(const std::string &name); + void removeInvite(const std::string &name); - void send(const std::string &msg); - void appendToRecvBuffer(const std::string &bytes); - bool extractLine(std::string &line); - std::size_t recvBufferSize() const; + void send(const std::string &msg); + void appendToRecvBuffer(const std::string &bytes); + bool extractLine(std::string &line); + std::size_t recvBufferSize() const; const std::string &getSendBuffer() const; void consumeSendBuffer(std::size_t count); void clearSendBuffer(); - bool hasPendingOutput() const; + bool hasPendingOutput() const; }; diff --git a/includes/ClientManager.hpp b/includes/ClientManager.hpp index eede800..ea0c643 100644 --- a/includes/ClientManager.hpp +++ b/includes/ClientManager.hpp @@ -29,7 +29,7 @@ class ClientManager { Client *at(const std::string &name); Client *at(int fd); - bool hasClient(const std::string &name) const; - bool add(Client *client, int fd); - void remove(int fd); + bool hasClient(const std::string &name) const; + bool add(Client *client, int fd); + void remove(int fd); }; diff --git a/includes/Logger.hpp b/includes/Logger.hpp index 145c3e6..5be496b 100644 --- a/includes/Logger.hpp +++ b/includes/Logger.hpp @@ -8,7 +8,7 @@ #include class Logger { - std::string prefix_; + std::string prefix_; static std::string nowStr_() { time_t now = time(0); diff --git a/includes/MemberSet.hpp b/includes/MemberSet.hpp index c82e2a1..b91d42f 100644 --- a/includes/MemberSet.hpp +++ b/includes/MemberSet.hpp @@ -19,13 +19,13 @@ class MemberSet { MemberSet(Client *client); MemberSet(Client *client, const ModeSet &modes); - void add(Client *client, const ModeSet &modes); - void remove(Client *client); - void setModes(Client *client, const ModeSet &modes); + void add(Client *client, const ModeSet &modes); + void remove(Client *client); + void setModes(Client *client, const ModeSet &modes); size_t size() const; bool has(Client *client) const; bool hasAtLeast(Client *client, const ModeSet &modes) const; - void send(const std::string &msg, const Client *skip = 0) const; + void send(const std::string &msg, const Client *skip = 0) const; }; diff --git a/includes/Mode.hpp b/includes/Mode.hpp index 38dfdae..a164f0f 100644 --- a/includes/Mode.hpp +++ b/includes/Mode.hpp @@ -36,8 +36,8 @@ enum UserMode { // ChannelUserModes holds the list of all user modes that can be applied to a // user in a channel in descending order of precedence -static const int channelUserModes[] = {ChannelFounder, ChannelAdmin, - ChannelOperator, Halfop, Voice}; +static const int channelUserModes[] = {ChannelFounder, ChannelAdmin, + ChannelOperator, Halfop, Voice}; inline std::string channelModePrefix(int m) { switch (m) { diff --git a/includes/ModeSet.hpp b/includes/ModeSet.hpp index 01a797f..cd4e4cf 100644 --- a/includes/ModeSet.hpp +++ b/includes/ModeSet.hpp @@ -15,7 +15,7 @@ * Subtract 65 from the mode and use that bit of the uint32_t to represent it. */ class ModeSet { - uint32_t modes_[2]; + uint32_t modes_[2]; static void indexAndMask_(int mode, uint32_t &index, uint32_t &mask) { uint32_t position = (uint32_t)mode - MIN_MODE; @@ -27,10 +27,10 @@ class ModeSet { public: ModeSet(); - bool has(int mode) const; - bool hasAtLeast(const ModeSet &modes) const; + bool has(int mode) const; + bool hasAtLeast(const ModeSet &modes) const; - bool set(int mode, bool on); + bool set(int mode, bool on); ModeSet &operator+=(const std::string &modes); ModeSet &operator-=(const std::string &modes); diff --git a/includes/ServerHelper.hpp b/includes/ServerHelper.hpp index 432551c..8f1f507 100644 --- a/includes/ServerHelper.hpp +++ b/includes/ServerHelper.hpp @@ -73,8 +73,8 @@ inline void bindServerSocket(int const socketFd, int const domain, // NOLINT } inline std::string composeAddrfamAddrPortStr(socketAddrX const &sockAddrIn46) { - char addrBuffer[128]; - socklen_t const addrBuffLen = sizeof(addrBuffer) / sizeof(char); + char addrBuffer[128]; + socklen_t const addrBuffLen = sizeof(addrBuffer) / sizeof(char); sa_family_t const saFamily = // NOLINTNEXTLINE diff --git a/includes/SignalHandler.hpp b/includes/SignalHandler.hpp index 9dd21e5..dbaf7d3 100644 --- a/includes/SignalHandler.hpp +++ b/includes/SignalHandler.hpp @@ -10,8 +10,8 @@ class SignalHandler { volatile static std::sig_atomic_t stopServerLoop_; static void handleStopSignals_(int signal); - std::vector stopSignals_; - bool setStopSignals_(sighandler_t handler); + std::vector stopSignals_; + bool setStopSignals_(sighandler_t handler); SignalHandler(SignalHandler const &other); SignalHandler &operator=(SignalHandler const &other); diff --git a/srcs/main.cpp b/srcs/main.cpp index 075c541..5991465 100644 --- a/srcs/main.cpp +++ b/srcs/main.cpp @@ -46,7 +46,7 @@ static bool checkExtractCmdLnArgs(int const argc, char *const argv[], int &port, equivalent) */ -int main(int argc, char **argv) { +int main(int argc, char **argv) { int port = 0; std::string password; diff --git a/srcs/server/SignalHandler.cpp b/srcs/server/SignalHandler.cpp index c443815..9a4e04f 100644 --- a/srcs/server/SignalHandler.cpp +++ b/srcs/server/SignalHandler.cpp @@ -19,7 +19,7 @@ volatile std::sig_atomic_t SignalHandler::stopServerLoop_ = 0; Will be refactored in a separate class, and with a more explicit stop state request function */ -void SignalHandler::handleStopSignals_(int signal) { +void SignalHandler::handleStopSignals_(int signal) { stopServerLoop_ = signal; std::cerr << "Caught stop signal: " << stopServerLoop_ << "." << std::endl; } @@ -30,7 +30,7 @@ void SignalHandler::handleStopSignals_(int signal) { portable POSIX compliant way to do it. */ bool SignalHandler::setStopSignals_(sighandler_t handler) { - bool success = true; + bool success = true; struct sigaction sigact = {}; sigact.sa_handler = handler; From 4dec74c0e1403883a452aefaee0c4b201dabc736 Mon Sep 17 00:00:00 2001 From: Rapha Date: Mon, 31 Aug 2026 13:57:13 +0200 Subject: [PATCH 082/349] rfa: put socket related functionaliy in its own class --- Makefile | 1 + includes/Server.hpp | 73 +++++++------------ includes/Socket.hpp | 45 ++++++++++++ srcs/server/Server.cpp | 160 +++++------------------------------------ srcs/server/Socket.cpp | 136 +++++++++++++++++++++++++++++++++++ 5 files changed, 226 insertions(+), 189 deletions(-) create mode 100644 includes/Socket.hpp create mode 100644 srcs/server/Socket.cpp diff --git a/Makefile b/Makefile index c6bde47..f457474 100644 --- a/Makefile +++ b/Makefile @@ -13,6 +13,7 @@ DEBUG_BUILD_DIR = build_debug SRCS_ROOT = main SRCS_SERVER = Server \ + Socket \ Client \ ClientManager \ Channel \ diff --git a/includes/Server.hpp b/includes/Server.hpp index de6d0ad..5f1e768 100644 --- a/includes/Server.hpp +++ b/includes/Server.hpp @@ -4,6 +4,7 @@ #include "ClientManager.hpp" #include "CommandProcessor.hpp" #include "Logger.hpp" +#include "Socket.hpp" #include #include @@ -12,56 +13,35 @@ #define SERVER_NAME "irc.local" #define MAX_CONNECTIONS 512 -#define RECVBUFFLEN 1024 -#define MAXMSGLEN 512 -#define SOCKETERRORLIMIT 8 - #define NETCATDBG 1 // For netcat debug class Server { private: - struct SocketIOData { - std::string readBuffer; - std::string writeBuffer; - ssize_t bytesRead; - size_t recvErrorCount; - size_t sendErrorCount; - bool discardMode; - - SocketIOData() - : bytesRead(0), recvErrorCount(0), sendErrorCount(0), - discardMode(false) {} - }; - - ChannelManager channels_; - ClientManager clients_; - CommandProcessor cmds_; - - int port_; - std::string password_; - std::string serverName_; - - size_t numServerSockets_; - std::vector pollFds_; - std::map sockIODataStore_; - - Logger logger_; - - void setupServerSockets_(); - int createServerSocket_(int const domain); - void processServerSocketData_(); - void acceptClient_(int const serverFd); - void processClientSockets_(); - void storePollFd_(int const socketFd); - bool checkSocket_(struct pollfd &pfd, SocketIOData &sIOD); - bool processClientSocketData_(struct pollfd &pfd, SocketIOData &sIOD); - void setClientInfo_(int fd, Client const **clntPtr, std::string &nick, - bool &isReg); - size_t clientDataSanityCheck_(SocketIOData &sIOD, int fd, - std::string const &nickName, - bool const isRegistered); - void closeSocket_(int const fd); - void log_(std::string const &msg, Logger::logMode const lm); + std::string serverName_; + int port_; + std::string password_; + + Logger logger_; + + Socket sockets_; + ChannelManager channels_; + ClientManager clients_; + CommandProcessor cmds_; + + size_t numServerSockets_; + std::vector pollFds_; + + void setupServerSockets_(); + int createServerSocket_(int const domain); + void processServerSocketData_(); + void acceptClient_(int const serverFd); + void processClientSockets_(); + void storePollFd_(int const socketFd); + bool processClientSocketData_(struct pollfd &pfd, + Socket::SocketIOData &sIOD); + void setClientInfo_(int fd, Client const **clntPtr, std::string &nick, + bool &isReg); + void log_(std::string const &msg, Logger::logMode const lm); Server(Server const &other); Server &operator=(Server const &other); @@ -76,7 +56,6 @@ class Server { }; void run(); - bool sendMsg(int const fd, std::string const &msg); bool tryRegister(Client &client) { (void)client; diff --git a/includes/Socket.hpp b/includes/Socket.hpp new file mode 100644 index 0000000..94f550a --- /dev/null +++ b/includes/Socket.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include "Logger.hpp" + +#include +#include +#include + +#define RECVBUFFLEN 1024 +#define MAXMSGLEN 512 +#define SOCKETERRORLIMIT 8 + +class Socket { + public: + struct SocketIOData { + std::string readBuffer; + std::string writeBuffer; + ssize_t bytesRead; + size_t recvErrorCount; + size_t sendErrorCount; + bool discardMode; + bool isSrvSocket; + + SocketIOData() + : bytesRead(0), recvErrorCount(0), sendErrorCount(0), + discardMode(false) {} + }; + + private: + std::map sockIODataStore_; + Logger const &logger_; + + public: + Socket(Logger const &logger) : logger_(logger) {} + + SocketIOData &getSocketIOData_(int const fd); + size_t clientDataSanityCheck_(Socket::SocketIOData &sIOD, int fd, + std::string const &serverName, + std::string const &nickName, + bool const isRegistered); + void addSocket(int const fd); + bool checkSocket_(struct pollfd &pfd); + bool sendMsg(int const fd, std::string const &msg); + void closeSocket_(int const fd); +}; diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index c112cff..b9811bf 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -15,64 +16,14 @@ * init the logger prefix (server name) */ Server::Server(int const port, std::string const &password) - : port_(port), password_(password), serverName_(SERVER_NAME), - numServerSockets_(0), logger_(serverName_ + ": ") {} + : serverName_(SERVER_NAME), port_(port), password_(password), + logger_(serverName_ + ": "), sockets_(logger_), numServerSockets_(0) {} Server::~Server() { - while (!pollFds_.empty()) - closeSocket_(pollFds_.back().fd); -} - -/* - sendMsg - * send string msg to a network socket - * uses i/o buffers and error counters in SocketIOData - * closes the socket (== disconnects the client) in case of too many errors -*/ -bool Server::sendMsg(int const fd, std::string const &msg) { - SocketIOData &sIOD = sockIODataStore_.at(fd); - ssize_t bytesSent = -1; - bool success = true; - - if (msg.length() > sIOD.writeBuffer.max_size() - sIOD.writeBuffer.length()) - sIOD.sendErrorCount = SOCKETERRORLIMIT; - else { - sIOD.writeBuffer += msg; - bytesSent = send(fd, sIOD.writeBuffer.c_str(), - sIOD.writeBuffer.length(), MSG_NOSIGNAL); - if (bytesSent <= 0) - ++sIOD.sendErrorCount; - } - if (sIOD.sendErrorCount >= SOCKETERRORLIMIT) { - closeSocket_(fd); - success = false; + while (!pollFds_.empty()) { + sockets_.closeSocket_(pollFds_.back().fd); + pollFds_.erase(--pollFds_.end()); } - - std::ostringstream oss; - oss << "SendMsg event:\n" - << " WriteBuffer: '" << sIOD.writeBuffer << "':\n"; - if (!success) { - oss << " send to FD " << fd << " failed."; - log_(oss.str(), Logger::lmError); - } else { - if (static_cast(bytesSent) < sIOD.writeBuffer.length()) - oss << " partly (" << bytesSent << "/" << sIOD.writeBuffer.length() - << ") " << sIOD.writeBuffer.substr(0, bytesSent); - else - oss << " successfully"; - oss << " sent to FD: " << fd; - log_(oss.str(), Logger::lmInfo); - } - - if (!success) - return false; - - sIOD.sendErrorCount = 0; - if (static_cast(bytesSent) < sIOD.writeBuffer.length()) - sIOD.writeBuffer.erase(0, bytesSent); - else - sIOD.writeBuffer.clear(); - return true; } void Server::setupServerSockets_() { @@ -146,17 +97,6 @@ void Server::processServerSocketData_() { } } -bool Server::checkSocket_(struct pollfd &pfd, SocketIOData &sIOD) { - if ((pfd.revents & POLLERR)) { - if (sIOD.recvErrorCount >= SOCKETERRORLIMIT) - closeSocket_(pfd.fd); - else - ++sIOD.recvErrorCount; - return false; - } - return true; -} - void Server::setClientInfo_(int fd, Client const **clntPtr, std::string &nick, bool &isReg) { try { @@ -170,44 +110,15 @@ void Server::setClientInfo_(int fd, Client const **clntPtr, std::string &nick, } } -size_t Server::clientDataSanityCheck_(SocketIOData &sIOD, int fd, - std::string const &nickName, - bool const isRegistered) { - std::string &rdBuffer = sIOD.readBuffer; - size_t lfcrPos = rdBuffer.find("\r\n"); - if ((rdBuffer.length() >= MAXMSGLEN) && - (lfcrPos == std::string::npos || lfcrPos > MAXMSGLEN - 2)) { - sIOD.discardMode = true; - ++sIOD.recvErrorCount; - sendMsg(fd, Reply::build(serverName(), nickName, - Reply::reply(Reply::ERR_INPUTTOOLONG, ""))); - } - if (sIOD.discardMode) { - if (!isRegistered || sIOD.recvErrorCount >= SOCKETERRORLIMIT) { - closeSocket_(fd); - return std::string::npos; - } - if (lfcrPos != std::string::npos) { - rdBuffer.erase(0, lfcrPos + 2); - sIOD.discardMode = false; - lfcrPos = rdBuffer.find("\r\n"); - } else /* lfcrPos == std::string::npos */ { - if ('\r' == *rdBuffer.rbegin()) - rdBuffer = "\r"; - else - rdBuffer.clear(); - } - } - return lfcrPos; -} - -bool Server::processClientSocketData_(struct pollfd &pfd, SocketIOData &sIOD) { +bool Server::processClientSocketData_(struct pollfd &pfd, + Socket::SocketIOData &sIOD) { Client const *client = 0; std::string nickName = "*"; bool clntIsReg = false; setClientInfo_(pfd.fd, &client, nickName, clntIsReg); - size_t lfcrPos = clientDataSanityCheck_(sIOD, pfd.fd, nickName, clntIsReg); + size_t lfcrPos = sockets_.clientDataSanityCheck_(sIOD, pfd.fd, serverName(), + nickName, clntIsReg); if (lfcrPos == std::string::npos) return false; @@ -217,7 +128,7 @@ bool Server::processClientSocketData_(struct pollfd &pfd, SocketIOData &sIOD) { std::ostringstream oss; oss << "You are client '" << nickName << "' (fd " << pfd.fd << "): \n" << "Srv received: " << std::string(sIOD.readBuffer) << std::endl; - sendMsg(pfd.fd, oss.str()); + sockets_.sendMsg(pfd.fd, oss.str()); } #endif @@ -228,7 +139,8 @@ bool Server::processClientSocketData_(struct pollfd &pfd, SocketIOData &sIOD) { cmds_.run(*this, *clients_.at(pfd.fd), cmdLine); } catch (Error const &e) { if (e.hasReply()) { - sendMsg(pfd.fd, Reply::build(serverName(), nickName, e.reply())); + sockets_.sendMsg(pfd.fd, + Reply::build(serverName(), nickName, e.reply())); } else { std::string tmp("Error while trying to run cmd:\n"); tmp.append(cmdLine + "\n" + e.what()); @@ -242,10 +154,10 @@ void Server::processClientSockets_() { for (size_t idx = numServerSockets_; !SignalHandler::stopLoopSignalReceived() && idx < pollFds_.size(); ++idx) { - pollfd &pfd = pollFds_.at(idx); - SocketIOData &sIOD = sockIODataStore_.at(pfd.fd); + pollfd &pfd = pollFds_.at(idx); + Socket::SocketIOData &sIOD = sockets_.getSocketIOData_(pfd.fd); - if (!checkSocket_(pfd, sIOD)) + if (!sockets_.checkSocket_(pfd)) continue; if (pfd.revents & POLLIN) { char recvBuffer[RECVBUFFLEN]; @@ -255,7 +167,7 @@ void Server::processClientSockets_() { sIOD.readBuffer += std::string(&recvBuffer[0], sIOD.bytesRead); processClientSocketData_(pfd, sIOD); } else if (sIOD.bytesRead <= 0) { - closeSocket_(pfd.fd); + sockets_.closeSocket_(pfd.fd); } } } @@ -296,43 +208,7 @@ void Server::storePollFd_(int const socketFd) { pfd.events = POLLIN; // Monitor for incoming connections pollFds_.push_back(pfd); - sockIODataStore_.insert( - std::make_pair(socketFd, SocketIOData())); -} - -/* - tbd -*/ -void Server::closeSocket_(int const fd) { - size_t idx = 0; - for (idx = 0; idx < pollFds_.size(); ++idx) - if (fd == pollFds_.at(idx).fd) - break; - if (idx == pollFds_.size()) - return; - - std::ostringstream oss; - if (idx < numServerSockets_) { - if (pollFds_.size() != idx + 1) { - log_("Internal error: Close client sockets before " - "closing server!", - Logger::lmError); - return; - } - oss << "Closing server socket (" - << srvHelp::getAdressFamilyStr(srvHelp::sockAddrFamily(fd)) << ")"; - } else { - oss << "Closing client #" << idx + 1 - numServerSockets_ << ", sockFd " - << fd << "; (" << pollFds_.size() - 1 - numServerSockets_ - << " clients remaining)"; - } - log_(oss.str(), Logger::lmInfo); - - close(fd); - std::vector::difference_type dist = - static_cast::difference_type>(idx); - pollFds_.erase(pollFds_.begin() + dist); - sockIODataStore_.erase(fd); + sockets_.addSocket(socketFd); } void Server::log_(std::string const &msg, Logger::logMode const lm) { diff --git a/srcs/server/Socket.cpp b/srcs/server/Socket.cpp new file mode 100644 index 0000000..0da0511 --- /dev/null +++ b/srcs/server/Socket.cpp @@ -0,0 +1,136 @@ +#include "Socket.hpp" + +#include "Reply.hpp" +#include "ServerHelper.hpp" + +#include +#include +#include +#include + +void Socket::addSocket(int const fd) { + sockIODataStore_.insert( + std::make_pair(fd, Socket::SocketIOData())); +} + +bool Socket::checkSocket_(struct pollfd &pfd) { + SocketIOData &sIOD = sockIODataStore_.at(pfd.fd); + if ((pfd.revents & POLLERR)) { + if (sIOD.recvErrorCount >= SOCKETERRORLIMIT) + closeSocket_(pfd.fd); + else + ++sIOD.recvErrorCount; + return false; + } + return true; +} + +size_t Socket::clientDataSanityCheck_(Socket::SocketIOData &sIOD, int fd, + std::string const &serverName, + std::string const &nickName, + bool const isRegistered) { + std::string &rdBuffer = sIOD.readBuffer; + size_t lfcrPos = rdBuffer.find("\r\n"); + if ((rdBuffer.length() >= MAXMSGLEN) && + (lfcrPos == std::string::npos || lfcrPos > MAXMSGLEN - 2)) { + sIOD.discardMode = true; + ++sIOD.recvErrorCount; + sendMsg(fd, Reply::build(serverName, nickName, + Reply::reply(Reply::ERR_INPUTTOOLONG, ""))); + } + if (sIOD.discardMode) { + if (!isRegistered || sIOD.recvErrorCount >= SOCKETERRORLIMIT) { + closeSocket_(fd); + return std::string::npos; + } + if (lfcrPos != std::string::npos) { + rdBuffer.erase(0, lfcrPos + 2); + sIOD.discardMode = false; + lfcrPos = rdBuffer.find("\r\n"); + } else /* lfcrPos == std::string::npos */ { + if ('\r' == *rdBuffer.rbegin()) + rdBuffer = "\r"; + else + rdBuffer.clear(); + } + } + return lfcrPos; +} + +/* + sendMsg + * send string msg to a network socket + * uses i/o buffers and error counters in SocketIOData + * closes the socket (== disconnects the client) in case of too many errors +*/ +bool Socket::sendMsg(int const fd, std::string const &msg) { + SocketIOData &sIOD = sockIODataStore_.at(fd); + ssize_t bytesSent = -1; + bool success = true; + + if (msg.length() > sIOD.writeBuffer.max_size() - sIOD.writeBuffer.length()) + sIOD.sendErrorCount = SOCKETERRORLIMIT; + else { + sIOD.writeBuffer += msg; + bytesSent = send(fd, sIOD.writeBuffer.c_str(), + sIOD.writeBuffer.length(), MSG_NOSIGNAL); + if (bytesSent <= 0) + ++sIOD.sendErrorCount; + } + if (sIOD.sendErrorCount >= SOCKETERRORLIMIT) { + closeSocket_(fd); + success = false; + } + + std::ostringstream oss; + oss << "SendMsg event:\n" + << " WriteBuffer: '" << sIOD.writeBuffer << "':\n"; + if (!success) { + oss << " send to FD " << fd << " failed."; + logger_.logMsg(oss.str(), Logger::lmError); + } else { + if (static_cast(bytesSent) < sIOD.writeBuffer.length()) + oss << " partly (" << bytesSent << "/" << sIOD.writeBuffer.length() + << ") " << sIOD.writeBuffer.substr(0, bytesSent); + else + oss << " successfully"; + oss << " sent to FD: " << fd; + logger_.logMsg(oss.str(), Logger::lmInfo); + } + + if (!success) + return false; + + sIOD.sendErrorCount = 0; + if (static_cast(bytesSent) < sIOD.writeBuffer.length()) + sIOD.writeBuffer.erase(0, bytesSent); + else + sIOD.writeBuffer.clear(); + return true; +} + +Socket::SocketIOData &Socket::getSocketIOData_(int const fd) { + std::map::iterator sIODit = sockIODataStore_.find(fd); + if (sIODit == sockIODataStore_.end()) { + std::ostringstream oss; + oss << "No SocketIOData for fd " << fd; + throw std::out_of_range(oss.str()); + } + return sIODit->second; +} + +void Socket::closeSocket_(int const fd) { + SocketIOData &sIOD = getSocketIOData_(fd); + + std::ostringstream oss; + if (sIOD.isSrvSocket) { + oss << "Closing server socket (" + << srvHelp::getAdressFamilyStr(srvHelp::sockAddrFamily(fd)) << ")"; + } else { + oss << "Closing client with sockFd " << fd; + } + logger_.logMsg(oss.str(), Logger::lmInfo); + + close(fd); + sockIODataStore_.erase(fd); +} From 70cee9e9eb447b648d9e5da8965d4fcba8dd14a1 Mon Sep 17 00:00:00 2001 From: Rapha Date: Mon, 31 Aug 2026 17:53:05 +0200 Subject: [PATCH 083/349] rfa: move more functionality to Socket class fix: fix cleanup after client socket close --- includes/Server.hpp | 12 +---- includes/Socket.hpp | 28 +++++++----- srcs/server/Server.cpp | 100 ++++++++++++++--------------------------- srcs/server/Socket.cpp | 68 +++++++++++++++++++--------- 4 files changed, 99 insertions(+), 109 deletions(-) diff --git a/includes/Server.hpp b/includes/Server.hpp index 5f1e768..ca7cb86 100644 --- a/includes/Server.hpp +++ b/includes/Server.hpp @@ -13,8 +13,6 @@ #define SERVER_NAME "irc.local" #define MAX_CONNECTIONS 512 -#define NETCATDBG 1 // For netcat debug - class Server { private: std::string serverName_; @@ -36,10 +34,8 @@ class Server { void processServerSocketData_(); void acceptClient_(int const serverFd); void processClientSockets_(); - void storePollFd_(int const socketFd); - bool processClientSocketData_(struct pollfd &pfd, - Socket::SocketIOData &sIOD); - void setClientInfo_(int fd, Client const **clntPtr, std::string &nick, + void storePollFd_(int const socketFd, bool srvSocket); + void setClientInfo_(int fd, Client **clntPtr, std::string &nick, bool &isReg); void log_(std::string const &msg, Logger::logMode const lm); @@ -51,10 +47,6 @@ class Server { ~Server(); - std::string const &serverName() const { - return serverName_; - }; - void run(); bool tryRegister(Client &client) { diff --git a/includes/Socket.hpp b/includes/Socket.hpp index 94f550a..e3f85ff 100644 --- a/includes/Socket.hpp +++ b/includes/Socket.hpp @@ -10,8 +10,11 @@ #define MAXMSGLEN 512 #define SOCKETERRORLIMIT 8 +#define NETCATDBG 1 // For netcat debug + class Socket { public: + private: struct SocketIOData { std::string readBuffer; std::string writeBuffer; @@ -20,10 +23,11 @@ class Socket { size_t sendErrorCount; bool discardMode; bool isSrvSocket; + bool fdClosed; - SocketIOData() + SocketIOData(bool srvSocket) : bytesRead(0), recvErrorCount(0), sendErrorCount(0), - discardMode(false) {} + discardMode(false), isSrvSocket(srvSocket), fdClosed(false) {} }; private: @@ -33,13 +37,15 @@ class Socket { public: Socket(Logger const &logger) : logger_(logger) {} - SocketIOData &getSocketIOData_(int const fd); - size_t clientDataSanityCheck_(Socket::SocketIOData &sIOD, int fd, - std::string const &serverName, - std::string const &nickName, - bool const isRegistered); - void addSocket(int const fd); - bool checkSocket_(struct pollfd &pfd); - bool sendMsg(int const fd, std::string const &msg); - void closeSocket_(int const fd); + void addSocket(int const fd, bool srvSocket); + bool checkSocket_(struct pollfd &pfd); + std::string getCmdLine(int const fd); + size_t + bufferLengthAndSanityCheck_(int const fd /* , bool const isRegistered */); + bool sendMsg(int const fd, std::string const &msg); + void closeSocket_(int const fd); + + bool socketClosed(int const fd) const { + return (sockIODataStore_.find(fd) == sockIODataStore_.end()); + } }; diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index b9811bf..c3f60c3 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -79,7 +79,7 @@ int Server::createServerSocket_(int const domain) { throw std::runtime_error("Error while listening on socket for " + srvHelp::getAdressFamilyStr(domain) + " !"); - storePollFd_(socketFd); + storePollFd_(socketFd, true); ++numServerSockets_; return socketFd; @@ -97,7 +97,7 @@ void Server::processServerSocketData_() { } } -void Server::setClientInfo_(int fd, Client const **clntPtr, std::string &nick, +void Server::setClientInfo_(int fd, Client **clntPtr, std::string &nick, bool &isReg) { try { *clntPtr = clients_.at(fd); @@ -106,76 +106,46 @@ void Server::setClientInfo_(int fd, Client const **clntPtr, std::string &nick, nick = (*clntPtr)->getNick(); } } catch (std::exception const &e) { - log_(e.what(), Logger::lmExcept); + log_(e.what(), Logger::lmWarn); } } -bool Server::processClientSocketData_(struct pollfd &pfd, - Socket::SocketIOData &sIOD) { - Client const *client = 0; - std::string nickName = "*"; - bool clntIsReg = false; - setClientInfo_(pfd.fd, &client, nickName, clntIsReg); - - size_t lfcrPos = sockets_.clientDataSanityCheck_(sIOD, pfd.fd, serverName(), - nickName, clntIsReg); - if (lfcrPos == std::string::npos) - return false; - -#if NETCATDBG - { - // size_t const clientNumber = idx + 1 - numServerSockets_; - std::ostringstream oss; - oss << "You are client '" << nickName << "' (fd " << pfd.fd << "): \n" - << "Srv received: " << std::string(sIOD.readBuffer) << std::endl; - sockets_.sendMsg(pfd.fd, oss.str()); - } -#endif - - sIOD.recvErrorCount = 0; - std::string cmdLine = sIOD.readBuffer.substr(0, lfcrPos); - sIOD.readBuffer = sIOD.readBuffer.substr(lfcrPos + 2); - try { - cmds_.run(*this, *clients_.at(pfd.fd), cmdLine); - } catch (Error const &e) { - if (e.hasReply()) { - sockets_.sendMsg(pfd.fd, - Reply::build(serverName(), nickName, e.reply())); - } else { - std::string tmp("Error while trying to run cmd:\n"); - tmp.append(cmdLine + "\n" + e.what()); - log_(tmp, Logger::lmExcept); - } - } - return true; -} - void Server::processClientSockets_() { for (size_t idx = numServerSockets_; !SignalHandler::stopLoopSignalReceived() && idx < pollFds_.size(); ++idx) { - pollfd &pfd = pollFds_.at(idx); - Socket::SocketIOData &sIOD = sockets_.getSocketIOData_(pfd.fd); - - if (!sockets_.checkSocket_(pfd)) - continue; - if (pfd.revents & POLLIN) { - char recvBuffer[RECVBUFFLEN]; - sIOD.bytesRead = recv(pfd.fd, &recvBuffer[0], RECVBUFFLEN, 0); - - if (sIOD.bytesRead > 0) { - sIOD.readBuffer += std::string(&recvBuffer[0], sIOD.bytesRead); - processClientSocketData_(pfd, sIOD); - } else if (sIOD.bytesRead <= 0) { - sockets_.closeSocket_(pfd.fd); + pollfd &pfd = pollFds_.at(idx); + + if (sockets_.checkSocket_(pfd)) { + if (pfd.revents & POLLIN) { + Client *client = 0; + std::string nickName = "*"; + bool clntIsReg = false; + setClientInfo_(pfd.fd, &client, nickName, clntIsReg); + + std::string cmdLine; + try { + cmdLine = sockets_.getCmdLine(pfd.fd); + cmds_.run(*this, *client, cmdLine); + } catch (Error const &e) { + if (e.hasReply()) { + sockets_.sendMsg( + pfd.fd, + Reply::build(serverName_, nickName, e.reply())); + } else { + std::string tmp( + "Error while trying to get/execute cmd:\n"); + tmp.append("'" + cmdLine + "'\n" + e.what()); + log_(tmp, Logger::lmExcept); + } + } } } + if (sockets_.socketClosed(pfd.fd)) + pollFds_.erase(--pollFds_.end()); } } -/* - tbd -*/ void Server::acceptClient_(int const serverFd) { socketAddrX sockAddrIn46 = {}; socklen_t sockLen = 0; @@ -195,20 +165,20 @@ void Server::acceptClient_(int const serverFd) { srvHelp::setSocketNonBlock(clientFd); - storePollFd_(clientFd); + storePollFd_(clientFd, false); std::cout << "Client connected:\n" << " fd: " << clientFd << "\n" << srvHelp::composeAddrfamAddrPortStr(sockAddrIn46) << std::endl; } -void Server::storePollFd_(int const socketFd) { +void Server::storePollFd_(int const socketFd, bool srvSocket) { pollfd pfd = {}; pfd.fd = socketFd; - pfd.events = POLLIN; // Monitor for incoming connections + pfd.events = POLLIN; pollFds_.push_back(pfd); - sockets_.addSocket(socketFd); + sockets_.addSocket(socketFd, srvSocket); } void Server::log_(std::string const &msg, Logger::logMode const lm) { @@ -221,8 +191,6 @@ void Server::log_(std::string const &msg, Logger::logMode const lm) { * sets up its listening socket(s) * runs an infinit loop to listen for connections * handles client connect and disconnect scenarios - * on server stop signal, disconnects all clients and closes - the server socket(s) */ void Server::run() { setupServerSockets_(); diff --git a/srcs/server/Socket.cpp b/srcs/server/Socket.cpp index 0da0511..a361301 100644 --- a/srcs/server/Socket.cpp +++ b/srcs/server/Socket.cpp @@ -1,5 +1,6 @@ #include "Socket.hpp" +#include "Errors.hpp" #include "Reply.hpp" #include "ServerHelper.hpp" @@ -8,9 +9,9 @@ #include #include -void Socket::addSocket(int const fd) { +void Socket::addSocket(int const fd, bool srvSocket) { sockIODataStore_.insert( - std::make_pair(fd, Socket::SocketIOData())); + std::make_pair(fd, SocketIOData(srvSocket))); } bool Socket::checkSocket_(struct pollfd &pfd) { @@ -25,21 +26,51 @@ bool Socket::checkSocket_(struct pollfd &pfd) { return true; } -size_t Socket::clientDataSanityCheck_(Socket::SocketIOData &sIOD, int fd, - std::string const &serverName, - std::string const &nickName, - bool const isRegistered) { - std::string &rdBuffer = sIOD.readBuffer; - size_t lfcrPos = rdBuffer.find("\r\n"); +std::string Socket::getCmdLine(int const fd) { + Socket::SocketIOData &sIOD = sockIODataStore_.at(fd); + std::string cmdLine; + char recvBuffer[RECVBUFFLEN]; + sIOD.bytesRead = recv(fd, &recvBuffer[0], RECVBUFFLEN, 0); + + if (sIOD.bytesRead > 0) { + sIOD.readBuffer += std::string(&recvBuffer[0], sIOD.bytesRead); + size_t lfcrPos = bufferLengthAndSanityCheck_(fd); + if (lfcrPos != std::string::npos) { + +#if NETCATDBG + { + std::ostringstream oss; + oss << "You are client fd " << fd << ": \n" + << "Srv received: " << std::string(sIOD.readBuffer) + << std::endl; + sendMsg(fd, oss.str()); + } +#endif + sIOD.recvErrorCount = 0; + cmdLine = sIOD.readBuffer.substr(0, lfcrPos); + sIOD.readBuffer = sIOD.readBuffer.substr(lfcrPos + 2); + } + } else if (sIOD.bytesRead <= 0) { + closeSocket_(fd); + } + return cmdLine; +} + +size_t Socket::bufferLengthAndSanityCheck_( + int const fd /* , bool const isRegistered */) { + Socket::SocketIOData &sIOD = sockIODataStore_.at(fd); + std::string &rdBuffer = sIOD.readBuffer; + size_t lfcrPos = rdBuffer.find("\r\n"); + bool msgTooLong = false; + if ((rdBuffer.length() >= MAXMSGLEN) && (lfcrPos == std::string::npos || lfcrPos > MAXMSGLEN - 2)) { sIOD.discardMode = true; ++sIOD.recvErrorCount; - sendMsg(fd, Reply::build(serverName, nickName, - Reply::reply(Reply::ERR_INPUTTOOLONG, ""))); + msgTooLong = true; } if (sIOD.discardMode) { - if (!isRegistered || sIOD.recvErrorCount >= SOCKETERRORLIMIT) { + if (/* !isRegistered || */ sIOD.recvErrorCount >= SOCKETERRORLIMIT) { closeSocket_(fd); return std::string::npos; } @@ -54,6 +85,9 @@ size_t Socket::clientDataSanityCheck_(Socket::SocketIOData &sIOD, int fd, rdBuffer.clear(); } } + if (msgTooLong) + throw Error(Reply::reply(Reply::ERR_INPUTTOOLONG, "")); + return lfcrPos; } @@ -109,18 +143,8 @@ bool Socket::sendMsg(int const fd, std::string const &msg) { return true; } -Socket::SocketIOData &Socket::getSocketIOData_(int const fd) { - std::map::iterator sIODit = sockIODataStore_.find(fd); - if (sIODit == sockIODataStore_.end()) { - std::ostringstream oss; - oss << "No SocketIOData for fd " << fd; - throw std::out_of_range(oss.str()); - } - return sIODit->second; -} - void Socket::closeSocket_(int const fd) { - SocketIOData &sIOD = getSocketIOData_(fd); + SocketIOData &sIOD = sockIODataStore_.at(fd); std::ostringstream oss; if (sIOD.isSrvSocket) { From 7296f2c3903925bbb2059d64c64b5ae4ad44b54f Mon Sep 17 00:00:00 2001 From: Rapha Date: Tue, 1 Sep 2026 13:04:33 +0200 Subject: [PATCH 084/349] fix: applied suggestions from PR review (ArchCipher) --- includes/CommandProcessor.hpp | 2 +- includes/ServerHelper.hpp | 3 ++- srcs/commands/CommandProcessor.cpp | 7 ++----- srcs/main.cpp | 2 ++ srcs/server/Server.cpp | 2 ++ srcs/server/SignalHandler.cpp | 1 - srcs/server/Socket.cpp | 3 +-- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/includes/CommandProcessor.hpp b/includes/CommandProcessor.hpp index 1bcdde8..ec9ae10 100644 --- a/includes/CommandProcessor.hpp +++ b/includes/CommandProcessor.hpp @@ -19,7 +19,7 @@ class Command { Handler handler; Command() {}; - Command(Handler h, bool preReg, int minP, bool op = false) + Command(Handler h, bool preReg, size_t minP, bool op = false) : usablePreReg_(preReg), minParams_(minP), requiresOp_(op), handler(h) {}; diff --git a/includes/ServerHelper.hpp b/includes/ServerHelper.hpp index 8f1f507..3177338 100644 --- a/includes/ServerHelper.hpp +++ b/includes/ServerHelper.hpp @@ -27,7 +27,8 @@ inline int sockAddrFamily(int const socketFd) { struct sockaddr sockAddr = {}; socklen_t sockLen = sizeof(sockAddr); if (getsockname(socketFd, &sockAddr, &sockLen) != 0) // NOLINT - throw; + std::runtime_error( + "Error whil getting address family through getsockname failed"); return sockAddr.sa_family; } diff --git a/srcs/commands/CommandProcessor.cpp b/srcs/commands/CommandProcessor.cpp index 7e4e8fa..a21696e 100644 --- a/srcs/commands/CommandProcessor.cpp +++ b/srcs/commands/CommandProcessor.cpp @@ -3,6 +3,7 @@ #include "Client.hpp" #include "Errors.hpp" #include "Handlers.hpp" +#include "Parser.hpp" #include "Server.hpp" Command &CommandProcessor::at_(const std::string &name) { @@ -12,13 +13,9 @@ Command &CommandProcessor::at_(const std::string &name) { return it->second; } -// TODO blnunez- bool CommandProcessor::parse(const std::string &line, std::string &cmd, std::vector ¶ms) { - (void)line; - (void)cmd; - (void)params; - return false; + return Parser::parse(line, cmd, params); } // Register all commandsexecutable by a client connected to us. diff --git a/srcs/main.cpp b/srcs/main.cpp index 5991465..66dfd59 100644 --- a/srcs/main.cpp +++ b/srcs/main.cpp @@ -81,6 +81,8 @@ static bool checkExtractCmdLnArgs(int const argc, char *const argv[], int &port, if (!((iss >> port) && (iss >> remainder).eof())) throw std::runtime_error( "Error while extracting port number from commandline parameter."); + if (port < 1 || port > 65535) + throw std::runtime_error("Error: port must be in the range 1-65535."); iss.str(argv[2]); // NOLINT if (!(iss >> password).eof()) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index c3f60c3..961d128 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -198,6 +198,8 @@ void Server::run() { while (!SignalHandler::stopLoopSignalReceived()) { int pollResult = poll(pollFds_.data(), pollFds_.size(), -1); if (pollResult == -1) { + if (SignalHandler::stopLoopSignalReceived()) + break; log_("Fatal error: poll failed.", Logger::lmError); break; } diff --git a/srcs/server/SignalHandler.cpp b/srcs/server/SignalHandler.cpp index 9a4e04f..8fd1105 100644 --- a/srcs/server/SignalHandler.cpp +++ b/srcs/server/SignalHandler.cpp @@ -21,7 +21,6 @@ volatile std::sig_atomic_t SignalHandler::stopServerLoop_ = 0; */ void SignalHandler::handleStopSignals_(int signal) { stopServerLoop_ = signal; - std::cerr << "Caught stop signal: " << stopServerLoop_ << "." << std::endl; } /* diff --git a/srcs/server/Socket.cpp b/srcs/server/Socket.cpp index a361301..4e03c8a 100644 --- a/srcs/server/Socket.cpp +++ b/srcs/server/Socket.cpp @@ -10,8 +10,7 @@ #include void Socket::addSocket(int const fd, bool srvSocket) { - sockIODataStore_.insert( - std::make_pair(fd, SocketIOData(srvSocket))); + sockIODataStore_.insert(std::make_pair(fd, SocketIOData(srvSocket))); } bool Socket::checkSocket_(struct pollfd &pfd) { From af99a60145a3d6e39468bf4e31acc85569b64a6a Mon Sep 17 00:00:00 2001 From: Rapha Date: Tue, 1 Sep 2026 14:02:28 +0200 Subject: [PATCH 085/349] dbg: add lldb config --- .vscode/launch.json | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 169f487..da1fa7b 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,11 +1,21 @@ { "version": "0.2.0", "configurations": [ + { + "name": "Debug with LLDB", + "type": "lldb", + "request": "launch", + "args": ["6667", "pass"], + "program": "${workspaceFolder}/ircserv_debug", + "cwd": "${workspaceFolder}", + "stopOnEntry": false, + "preLaunchTask": "42 build with Makefile", + }, { "name": "C/C++ Runner: Debug Session", "type": "cppdbg", "request": "launch", - "args": ["6667", "pass"], + "args": ["6667", "pass"], // "stopAtEntry": true, "externalConsole": false, "cwd": "${workspaceFolder}", From 64c37000b2b61c8a3ebf5c250df15707c94e84c4 Mon Sep 17 00:00:00 2001 From: Rapha Date: Tue, 1 Sep 2026 15:13:39 +0200 Subject: [PATCH 086/349] fix: handle multiple commands in readBuffer --- Makefile | 2 +- includes/Socket.hpp | 4 ++-- srcs/server/Server.cpp | 32 +++++++++++++++++------------- srcs/server/Socket.cpp | 45 ++++++++++++++++++++++-------------------- 4 files changed, 45 insertions(+), 38 deletions(-) diff --git a/Makefile b/Makefile index f457474..72a480c 100644 --- a/Makefile +++ b/Makefile @@ -52,7 +52,7 @@ DEPS = $(OBJS:.o=.d) $(DEBUG_OBJS:.o=.d) CXX = c++ CXXFLAGS = -Wall -Wextra -Werror -pedantic -std=c++98 IFLAGS = -I$(INC_DIR) -DFLAGS = -DDEBUG -g -fsanitize=address +DFLAGS = -DDEBUG -g3 -O0 -fsanitize=address DEBUG_CXXFLAGS = $(CXXFLAGS) $(DFLAGS) # ---- Rules ------------------------------------------------------------------- diff --git a/includes/Socket.hpp b/includes/Socket.hpp index e3f85ff..2bcd6e0 100644 --- a/includes/Socket.hpp +++ b/includes/Socket.hpp @@ -10,7 +10,7 @@ #define MAXMSGLEN 512 #define SOCKETERRORLIMIT 8 -#define NETCATDBG 1 // For netcat debug +#define NETCATDBG 0 // For netcat debug class Socket { public: @@ -39,7 +39,7 @@ class Socket { void addSocket(int const fd, bool srvSocket); bool checkSocket_(struct pollfd &pfd); - std::string getCmdLine(int const fd); + std::string getCmdLine(int const fd, bool &buffHasMoreCmds); size_t bufferLengthAndSanityCheck_(int const fd /* , bool const isRegistered */); bool sendMsg(int const fd, std::string const &msg); diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 961d128..a422b4a 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -124,21 +124,25 @@ void Server::processClientSockets_() { setClientInfo_(pfd.fd, &client, nickName, clntIsReg); std::string cmdLine; - try { - cmdLine = sockets_.getCmdLine(pfd.fd); - cmds_.run(*this, *client, cmdLine); - } catch (Error const &e) { - if (e.hasReply()) { - sockets_.sendMsg( - pfd.fd, - Reply::build(serverName_, nickName, e.reply())); - } else { - std::string tmp( - "Error while trying to get/execute cmd:\n"); - tmp.append("'" + cmdLine + "'\n" + e.what()); - log_(tmp, Logger::lmExcept); + bool buffHasMoreCmds = false; + do { + try { + cmdLine = sockets_.getCmdLine(pfd.fd, buffHasMoreCmds); + if (!cmdLine.empty()) + cmds_.run(*this, *client, cmdLine); + } catch (Error const &e) { + if (e.hasReply()) { + sockets_.sendMsg( + pfd.fd, + Reply::build(serverName_, nickName, e.reply())); + } else { + std::string tmp( + "Error while trying to get/execute cmd:\n"); + tmp.append("'" + cmdLine + "'\n" + e.what()); + log_(tmp, Logger::lmExcept); + } } - } + } while (!buffHasMoreCmds); } } if (sockets_.socketClosed(pfd.fd)) diff --git a/srcs/server/Socket.cpp b/srcs/server/Socket.cpp index 4e03c8a..b4467e3 100644 --- a/srcs/server/Socket.cpp +++ b/srcs/server/Socket.cpp @@ -25,32 +25,35 @@ bool Socket::checkSocket_(struct pollfd &pfd) { return true; } -std::string Socket::getCmdLine(int const fd) { +std::string Socket::getCmdLine(int const fd, bool &buffHasMoreCmds) { Socket::SocketIOData &sIOD = sockIODataStore_.at(fd); std::string cmdLine; - char recvBuffer[RECVBUFFLEN]; - sIOD.bytesRead = recv(fd, &recvBuffer[0], RECVBUFFLEN, 0); - - if (sIOD.bytesRead > 0) { - sIOD.readBuffer += std::string(&recvBuffer[0], sIOD.bytesRead); - size_t lfcrPos = bufferLengthAndSanityCheck_(fd); - if (lfcrPos != std::string::npos) { + if (!buffHasMoreCmds) { + char recvBuffer[RECVBUFFLEN]; + sIOD.bytesRead = recv(fd, &recvBuffer[0], RECVBUFFLEN, 0); + if (sIOD.bytesRead > 0) { + sIOD.readBuffer += std::string(&recvBuffer[0], sIOD.bytesRead); + } else if (sIOD.bytesRead <= 0) { + closeSocket_(fd); + return std::string(); + } + } + size_t lfcrPos = bufferLengthAndSanityCheck_(fd); + if (lfcrPos != std::string::npos) { #if NETCATDBG - { - std::ostringstream oss; - oss << "You are client fd " << fd << ": \n" - << "Srv received: " << std::string(sIOD.readBuffer) - << std::endl; - sendMsg(fd, oss.str()); - } -#endif - sIOD.recvErrorCount = 0; - cmdLine = sIOD.readBuffer.substr(0, lfcrPos); - sIOD.readBuffer = sIOD.readBuffer.substr(lfcrPos + 2); + { + std::ostringstream oss; + oss << "You are client fd " << fd << ": \n" + << "Srv received: " << std::string(sIOD.readBuffer) + << std::endl; + sendMsg(fd, oss.str()); } - } else if (sIOD.bytesRead <= 0) { - closeSocket_(fd); +#endif + sIOD.recvErrorCount = 0; + cmdLine = sIOD.readBuffer.substr(0, lfcrPos); + sIOD.readBuffer = sIOD.readBuffer.substr(lfcrPos + 2); + buffHasMoreCmds = (sIOD.readBuffer.find("\r\n") != std::string::npos); } return cmdLine; } From 18a8a5b246c1d213a47cb090c7fd6370106f9dbd Mon Sep 17 00:00:00 2001 From: Rapha Date: Tue, 1 Sep 2026 16:02:02 +0200 Subject: [PATCH 087/349] dbg: add "pass SIGINT to prog" config for lldb --- .vscode/launch.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index da1fa7b..8fe5d69 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -10,7 +10,10 @@ "cwd": "${workspaceFolder}", "stopOnEntry": false, "preLaunchTask": "42 build with Makefile", - }, + "initCommands": [ + "process handle SIGINT -s false -p true" + ] + }, { "name": "C/C++ Runner: Debug Session", "type": "cppdbg", From eee95330e67ee7021ecb3d24174ea6f819085688 Mon Sep 17 00:00:00 2001 From: Rapha Date: Tue, 1 Sep 2026 16:04:09 +0200 Subject: [PATCH 088/349] fix: * fix logic error for buffHasMoreCmds * erase the correct pollFds_ entry --- srcs/server/Server.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index a422b4a..02689ee 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -142,11 +142,14 @@ void Server::processClientSockets_() { log_(tmp, Logger::lmExcept); } } - } while (!buffHasMoreCmds); + } while (buffHasMoreCmds); } } - if (sockets_.socketClosed(pfd.fd)) - pollFds_.erase(--pollFds_.end()); + if (sockets_.socketClosed(pfd.fd)) { + std::vector::difference_type dist = + static_cast::difference_type>(idx); + pollFds_.erase(pollFds_.begin() + dist); + } } } From 83b6a770b69eb111aac315cded7dca03c81487b6 Mon Sep 17 00:00:00 2001 From: ArchCipher Date: Thu, 30 Jul 2026 18:06:20 +0200 Subject: [PATCH 089/349] clang-format, tidy, makefile, readme edit, includes and srcs folder --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index ab0aa1a..f36d625 100644 --- a/.gitignore +++ b/.gitignore @@ -42,7 +42,6 @@ ircserv ircserv_debug ircserv_bonus ircserv_bonus_debug -ircserv_bonus *.exe *.out *.app From bb52b78a6f86da8d799a708ff676f1dbdc242428 Mon Sep 17 00:00:00 2001 From: Rapha Date: Tue, 4 Aug 2026 17:22:16 +0200 Subject: [PATCH 090/349] feat: server poll loop mockup incl. signal handling --- includes/IRC.hpp | 3 +++ includes/IRCServer.hpp | 25 +++++++++++++++++++++++++ srcs/main.cpp | 8 +++++++- srcs/server/IRCServer.cpp | 19 +++++++++++++++++++ 4 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 includes/IRCServer.hpp create mode 100644 srcs/server/IRCServer.cpp diff --git a/includes/IRC.hpp b/includes/IRC.hpp index e69de29..1e28153 100644 --- a/includes/IRC.hpp +++ b/includes/IRC.hpp @@ -0,0 +1,3 @@ +#include "IRCServer.hpp" + +#include \ No newline at end of file diff --git a/includes/IRCServer.hpp b/includes/IRCServer.hpp new file mode 100644 index 0000000..29914d3 --- /dev/null +++ b/includes/IRCServer.hpp @@ -0,0 +1,25 @@ +#include +#include +#include + +class IRCServer { + private: + volatile static std::sig_atomic_t _stopServerLoop; + static void _handle_sigint(int signal); + + public: + IRCServer() { + _stopServerLoop = 0; + if (std::signal(SIGINT, _handle_sigint) == SIG_ERR) { + throw std::runtime_error("std::signal failed"); + } + } + + ~IRCServer() { + if (std::signal(SIGINT, SIG_DFL) == SIG_ERR) { + // throw std::runtime_error("std::signal failed"); + } + } + + void run(); +}; diff --git a/srcs/main.cpp b/srcs/main.cpp index 42df375..f43c8fd 100644 --- a/srcs/main.cpp +++ b/srcs/main.cpp @@ -1,4 +1,4 @@ -// # include "IRC.hpp" +#include "IRC.hpp" /* NAME: @@ -41,4 +41,10 @@ int main(int argc, char **argv) { (void)argv; if (argc != 3) return (1); + + try { + IRCServer srv; + srv.run(); + } catch (std::exception const &e) { + } } diff --git a/srcs/server/IRCServer.cpp b/srcs/server/IRCServer.cpp new file mode 100644 index 0000000..1343fa5 --- /dev/null +++ b/srcs/server/IRCServer.cpp @@ -0,0 +1,19 @@ +#include "IRCServer.hpp" + +#include +#include +#include + +volatile std::sig_atomic_t IRCServer::_stopServerLoop = 0; + +void IRCServer::_handle_sigint(int signal) { + _stopServerLoop = signal; +} + +void IRCServer::run() { + while (!_stopServerLoop) { + std::cout << "running..." << std::endl; + sleep(1); + } + std::cout << "caught signal: " << _stopServerLoop << "." << std::endl; +} From 73ef665dd2271df8e5d15bf119eb4fde9db19a8d Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 5 Aug 2026 13:03:49 +0200 Subject: [PATCH 091/349] rfa: replace signal with sigaction and put signal handling code in its own functions --- includes/IRCServer.hpp | 23 ++++++++++++++--------- srcs/main.cpp | 1 + srcs/server/IRCServer.cpp | 37 ++++++++++++++++++++++++++++++++----- 3 files changed, 47 insertions(+), 14 deletions(-) diff --git a/includes/IRCServer.hpp b/includes/IRCServer.hpp index 29914d3..7f9ac5d 100644 --- a/includes/IRCServer.hpp +++ b/includes/IRCServer.hpp @@ -1,24 +1,29 @@ #include #include #include +#include class IRCServer { + public: + volatile static std::sig_atomic_t g_stopServerLoop; + static std::vector g_signals; + static void g_handleSignal(int signal); + // static void _initSignalHandling(); + // static void _resetSignalHandling(); private: - volatile static std::sig_atomic_t _stopServerLoop; - static void _handle_sigint(int signal); + void _initSignalHandling(); + void _resetSignalHandling(); + + IRCServer(IRCServer const &other); + IRCServer &operator=(IRCServer const &other); public: IRCServer() { - _stopServerLoop = 0; - if (std::signal(SIGINT, _handle_sigint) == SIG_ERR) { - throw std::runtime_error("std::signal failed"); - } + _initSignalHandling(); } ~IRCServer() { - if (std::signal(SIGINT, SIG_DFL) == SIG_ERR) { - // throw std::runtime_error("std::signal failed"); - } + _resetSignalHandling(); } void run(); diff --git a/srcs/main.cpp b/srcs/main.cpp index f43c8fd..115520a 100644 --- a/srcs/main.cpp +++ b/srcs/main.cpp @@ -47,4 +47,5 @@ int main(int argc, char **argv) { srv.run(); } catch (std::exception const &e) { } + std::cout << "Clean exit." << std::endl; } diff --git a/srcs/server/IRCServer.cpp b/srcs/server/IRCServer.cpp index 1343fa5..68ae9f4 100644 --- a/srcs/server/IRCServer.cpp +++ b/srcs/server/IRCServer.cpp @@ -4,16 +4,43 @@ #include #include -volatile std::sig_atomic_t IRCServer::_stopServerLoop = 0; +volatile std::sig_atomic_t IRCServer::g_stopServerLoop = 0; +std::vector IRCServer::g_signals; -void IRCServer::_handle_sigint(int signal) { - _stopServerLoop = signal; +void IRCServer::g_handleSignal(int signal) { + if (signal == SIGINT || signal == SIGTERM) + g_stopServerLoop = signal; +} + +void IRCServer::_initSignalHandling() { + g_stopServerLoop = 0; + g_signals.push_back(SIGINT); + g_signals.push_back(SIGTERM); + + struct sigaction sigact = {}; + sigact.sa_handler = g_handleSignal; + sigemptyset(&sigact.sa_mask); + sigact.sa_flags = 0; + for (size_t i = 0; i < g_signals.size(); ++i) + if (sigaction(g_signals.at(i), &sigact, NULL) != 0) + throw std::runtime_error("std::signal failed"); +} + +void IRCServer::_resetSignalHandling() { + + struct sigaction sigact = {}; + sigact.sa_handler = SIG_DFL; + sigemptyset(&sigact.sa_mask); + sigact.sa_flags = 0; + + for (size_t i = 0; i < g_signals.size(); ++i) + sigaction(g_signals.at(i), &sigact, NULL); } void IRCServer::run() { - while (!_stopServerLoop) { + while (!g_stopServerLoop) { std::cout << "running..." << std::endl; sleep(1); } - std::cout << "caught signal: " << _stopServerLoop << "." << std::endl; + std::cout << "caught signal: " << g_stopServerLoop << "." << std::endl; } From 91226f38b6f79c8684ee85010beb3ba8ee56081a Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 5 Aug 2026 13:04:51 +0200 Subject: [PATCH 092/349] check: add rules to clang-tidy for private and static class members --- .clang-tidy | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index e35d41a..3a38405 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -15,7 +15,8 @@ Checks: > -cppcoreguidelines-macro-to-enum, -readability-braces-around-statements, -readability-implicit-bool-conversion, - -readability-identifier-length + -readability-identifier-length, + -performance-avoid-endl, WarningsAsErrors: true @@ -38,6 +39,16 @@ CheckOptions: value: camelBack - key: readability-identifier-naming.VariableCase value: camelBack - + - key: readability-identifier-naming.ClassMemberPrefix + value: g_ + - key: readability-identifier-naming.ClassMethodPrefix + value: g_ + - key: readability-identifier-naming.PrivateMemberPrefix + value: _ + - key: readability-identifier-naming.PrivateMethodCase + value: camelBack + - key: readability-identifier-naming.PrivateMethodPrefix + value: _ + - key: readability-identifier-naming.IgnoreMainLikeFunctions value: 1 From beb77c430bec8e7aa4033a74f66e298a22bb7a0d Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 5 Aug 2026 13:24:10 +0200 Subject: [PATCH 093/349] rfa: cleanup code deduplication and more specialised functions --- includes/IRCServer.hpp | 14 ++++++-------- srcs/server/IRCServer.cpp | 31 ++++++++++++++++--------------- 2 files changed, 22 insertions(+), 23 deletions(-) diff --git a/includes/IRCServer.hpp b/includes/IRCServer.hpp index 7f9ac5d..c5fb002 100644 --- a/includes/IRCServer.hpp +++ b/includes/IRCServer.hpp @@ -4,15 +4,13 @@ #include class IRCServer { - public: - volatile static std::sig_atomic_t g_stopServerLoop; - static std::vector g_signals; - static void g_handleSignal(int signal); - // static void _initSignalHandling(); - // static void _resetSignalHandling(); private: - void _initSignalHandling(); - void _resetSignalHandling(); + volatile static std::sig_atomic_t g_stopServerLoop; + static void g_handleStopSignals(int signal); + std::vector _stopSignals; + void _initSignalHandling(); + void _resetSignalHandling(); + bool _setStopSignals(sighandler_t handler); IRCServer(IRCServer const &other); IRCServer &operator=(IRCServer const &other); diff --git a/srcs/server/IRCServer.cpp b/srcs/server/IRCServer.cpp index 68ae9f4..cd2754b 100644 --- a/srcs/server/IRCServer.cpp +++ b/srcs/server/IRCServer.cpp @@ -5,36 +5,37 @@ #include volatile std::sig_atomic_t IRCServer::g_stopServerLoop = 0; -std::vector IRCServer::g_signals; -void IRCServer::g_handleSignal(int signal) { - if (signal == SIGINT || signal == SIGTERM) +void IRCServer::g_handleStopSignals(int signal) { g_stopServerLoop = signal; } void IRCServer::_initSignalHandling() { g_stopServerLoop = 0; - g_signals.push_back(SIGINT); - g_signals.push_back(SIGTERM); + _stopSignals.push_back(SIGINT); + _stopSignals.push_back(SIGTERM); - struct sigaction sigact = {}; - sigact.sa_handler = g_handleSignal; - sigemptyset(&sigact.sa_mask); - sigact.sa_flags = 0; - for (size_t i = 0; i < g_signals.size(); ++i) - if (sigaction(g_signals.at(i), &sigact, NULL) != 0) - throw std::runtime_error("std::signal failed"); + if (!_setStopSignals(g_handleStopSignals)) + throw std::runtime_error("std::signal failed"); } void IRCServer::_resetSignalHandling() { + _setStopSignals(SIG_DFL); +} + +bool IRCServer::_setStopSignals(sighandler_t handler) { + bool success = true; struct sigaction sigact = {}; - sigact.sa_handler = SIG_DFL; + sigact.sa_handler = handler; sigemptyset(&sigact.sa_mask); sigact.sa_flags = 0; - for (size_t i = 0; i < g_signals.size(); ++i) - sigaction(g_signals.at(i), &sigact, NULL); + for (size_t i = 0; i < _stopSignals.size(); ++i) + if (sigaction(_stopSignals.at(i), &sigact, NULL) != 0) + success = false; + + return success; } void IRCServer::run() { From f6c698ee95c2a8ed8c8df9d35dce58922a527afb Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 5 Aug 2026 16:25:56 +0200 Subject: [PATCH 094/349] feat: add SIGQUIT --- srcs/server/IRCServer.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/srcs/server/IRCServer.cpp b/srcs/server/IRCServer.cpp index cd2754b..0b9f6f3 100644 --- a/srcs/server/IRCServer.cpp +++ b/srcs/server/IRCServer.cpp @@ -13,6 +13,7 @@ void IRCServer::g_handleStopSignals(int signal) { void IRCServer::_initSignalHandling() { g_stopServerLoop = 0; _stopSignals.push_back(SIGINT); + _stopSignals.push_back(SIGQUIT); _stopSignals.push_back(SIGTERM); if (!_setStopSignals(g_handleStopSignals)) From 8e092b6d91c83c4332c678039a0871f0d6a15d2c Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 6 Aug 2026 14:22:22 +0200 Subject: [PATCH 095/349] feat: dirty mockup for testing basic functionality of server loop --- includes/IRCServer.hpp | 16 ++++-- srcs/server/IRCServer.cpp | 118 +++++++++++++++++++++++++++++++++++++- 2 files changed, 125 insertions(+), 9 deletions(-) diff --git a/includes/IRCServer.hpp b/includes/IRCServer.hpp index c5fb002..86cc0f4 100644 --- a/includes/IRCServer.hpp +++ b/includes/IRCServer.hpp @@ -1,6 +1,9 @@ #include #include #include +// #include +// #include +// #include #include class IRCServer { @@ -12,17 +15,18 @@ class IRCServer { void _resetSignalHandling(); bool _setStopSignals(sighandler_t handler); + std::vector _pollfds; + IRCServer(IRCServer const &other); IRCServer &operator=(IRCServer const &other); + int _createDummyServerSocket(); + void _acceptClient(); + public: - IRCServer() { - _initSignalHandling(); - } + IRCServer(); - ~IRCServer() { - _resetSignalHandling(); - } + ~IRCServer(); void run(); }; diff --git a/srcs/server/IRCServer.cpp b/srcs/server/IRCServer.cpp index 0b9f6f3..41fd077 100644 --- a/srcs/server/IRCServer.cpp +++ b/srcs/server/IRCServer.cpp @@ -1,19 +1,21 @@ #include "IRCServer.hpp" +#include #include #include #include +#include volatile std::sig_atomic_t IRCServer::g_stopServerLoop = 0; void IRCServer::g_handleStopSignals(int signal) { - g_stopServerLoop = signal; + g_stopServerLoop = signal; } void IRCServer::_initSignalHandling() { g_stopServerLoop = 0; _stopSignals.push_back(SIGINT); - _stopSignals.push_back(SIGQUIT); + _stopSignals.push_back(SIGQUIT); _stopSignals.push_back(SIGTERM); if (!_setStopSignals(g_handleStopSignals)) @@ -39,10 +41,120 @@ bool IRCServer::_setStopSignals(sighandler_t handler) { return success; } +IRCServer::IRCServer() { + _initSignalHandling(); +} + +IRCServer::~IRCServer() { + _resetSignalHandling(); +} + +int IRCServer::_createDummyServerSocket() { + // Create a TCP socket + int serverSocket = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0); + if (serverSocket == -1) { + return -1; + } + + // Bind and listen + struct sockaddr_in serverAddr; + serverAddr.sin_family = AF_INET; + serverAddr.sin_port = htons(8082); + serverAddr.sin_addr.s_addr = INADDR_ANY; + + if (bind(serverSocket, (struct sockaddr *)&serverAddr, + sizeof(serverAddr)) == -1) { + return -1; + } + + listen(serverSocket, 100); + + _pollfds.resize(1); + _pollfds[0].fd = serverSocket; + _pollfds[0].events = POLLIN; // Monitor for incoming connections + + return serverSocket; +} + +void IRCServer::_acceptClient() { + int clientFd; + struct sockaddr_in clientAI; + struct pollfd clientPoll; + socklen_t AISize = sizeof(clientAI); + + // clientFd = accept(_pollfds.front().fd, reinterpret_cast(&clientAI), &AISize); + clientFd = accept4(_pollfds.front().fd, + reinterpret_cast(&clientAI), &AISize, + SOCK_NONBLOCK); + + if (clientFd == -1) { + std::cerr << "[ERROR] Accepting failed." << std::endl; + return; + } + clientPoll.fd = clientFd; + clientPoll.events = POLLIN; + clientPoll.revents = 0; + _pollfds.push_back(clientPoll); + std::cout << "[SERVER] Client connected [FD: " << clientFd << "]" + << std::endl; +} + void IRCServer::run() { + _createDummyServerSocket(); + + // Event loop while (!g_stopServerLoop) { std::cout << "running..." << std::endl; - sleep(1); + sleep(5); + + // poll() timeout in milliseconds (-1 = infinite) + int pollResult = poll(_pollfds.data(), _pollfds.size(), 0); + + if (pollResult == -1) { + // print nice error message and exit the loop + // or throw, but then have ressource cleanup after catch + break; + } else if (pollResult == 0) { + // no data ready to read + continue; + } + + // Check if server socket has activity + struct pollfd &serverPollFds = _pollfds[0]; + if (serverPollFds.revents & POLLERR) { + std::cout << "Socket error\n"; + break; + } else if (serverPollFds.revents & POLLIN) { + _acceptClient(); + } + + // Clients + for (size_t idx = 1; idx < _pollfds.size(); ++idx) { + pollfd &pfd = _pollfds.at(idx); + char buffer[1024] = ""; + int bytesRead = read(pfd.fd, buffer, 1024); + if (bytesRead < 0) { + if (!(errno == EAGAIN || errno == EWOULDBLOCK)) + std::cerr << "[ERROR] Recv failed on FD " << pfd.fd + << std::endl; + } else if (bytesRead == 0) { + // removeClient(fd, pollIndex, "Remote host closed the connection"); + close(pfd.fd); + _pollfds.erase(_pollfds.begin() + idx); + std::cout << "Closed client #" << idx << " ("<<_pollfds.size() -1 << " remaining)"<< std::endl; + } else { + std::cout << "Buffer read from client " << idx << ": \n" + << std::string(buffer) << std::endl; + } + } + } + close(_pollfds[0].fd); + for (size_t idx = 1; idx < _pollfds.size(); ++idx) { + pollfd &pfd = _pollfds.at(idx); + close(pfd.fd); + _pollfds.erase(_pollfds.begin() + idx); + std::cout << "Closed client #" << idx << " ("<<_pollfds.size() - 1 << " remaining)"<< std::endl; } + std::cout << "caught signal: " << g_stopServerLoop << "." << std::endl; } From 5b4da77f521ca43480a5ca07b28a6c07ea57b634 Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 6 Aug 2026 15:10:43 +0200 Subject: [PATCH 096/349] feat: properly shut down clients on client disconnect and server shutdown --- srcs/server/IRCServer.cpp | 60 +++++++++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 21 deletions(-) diff --git a/srcs/server/IRCServer.cpp b/srcs/server/IRCServer.cpp index 41fd077..0743c91 100644 --- a/srcs/server/IRCServer.cpp +++ b/srcs/server/IRCServer.cpp @@ -114,10 +114,10 @@ void IRCServer::run() { // print nice error message and exit the loop // or throw, but then have ressource cleanup after catch break; - } else if (pollResult == 0) { + } /* else if (pollResult == 0) { // no data ready to read continue; - } + } */ // Check if server socket has activity struct pollfd &serverPollFds = _pollfds[0]; @@ -128,33 +128,51 @@ void IRCServer::run() { _acceptClient(); } - // Clients + // Clients for (size_t idx = 1; idx < _pollfds.size(); ++idx) { pollfd &pfd = _pollfds.at(idx); - char buffer[1024] = ""; - int bytesRead = read(pfd.fd, buffer, 1024); - if (bytesRead < 0) { - if (!(errno == EAGAIN || errno == EWOULDBLOCK)) - std::cerr << "[ERROR] Recv failed on FD " << pfd.fd - << std::endl; - } else if (bytesRead == 0) { - // removeClient(fd, pollIndex, "Remote host closed the connection"); - close(pfd.fd); - _pollfds.erase(_pollfds.begin() + idx); - std::cout << "Closed client #" << idx << " ("<<_pollfds.size() -1 << " remaining)"<< std::endl; + std::cout << "Idx: " << idx << std::endl; + if (pfd.revents & POLLERR) { + std::cerr << "[ERROR] Recv failed on FD " << pfd.fd + << std::endl; + // } else if (pfd.revents & POLLHUP) { + // // removeClient(fd, pollIndex, "Remote host closed the connection"); + // close(pfd.fd); + // _pollfds.erase(_pollfds.begin() + idx); + // std::cout << "Closed client #" << idx << " (" + // << _pollfds.size() - 1 << " remaining)" << std::endl; } else { - std::cout << "Buffer read from client " << idx << ": \n" - << std::string(buffer) << std::endl; + std::cout << " revents: " << std::hex << pfd.revents + << std::endl; + } + + if (pfd.revents & POLLIN) { + char buffer[1024] = ""; + int bytesRead = read(pfd.fd, buffer, 1024); + if (bytesRead > 0) { + std::cout << "Buffer read from client " << idx << ": \n" + << std::string(buffer) << std::endl; + } + if (bytesRead == 0) { + // removeClient(fd, pollIndex, "Remote host closed the connection"); + close(pfd.fd); + _pollfds.erase(_pollfds.begin() + idx); + std::cout << "Closed client #" << idx << " (" + << _pollfds.size() - 1 << " remaining)" + << std::endl; + } } } } - close(_pollfds[0].fd); - for (size_t idx = 1; idx < _pollfds.size(); ++idx) { + for (size_t idx = 0; idx < _pollfds.size(); ++idx) { pollfd &pfd = _pollfds.at(idx); close(pfd.fd); - _pollfds.erase(_pollfds.begin() + idx); - std::cout << "Closed client #" << idx << " ("<<_pollfds.size() - 1 << " remaining)"<< std::endl; + if (idx == 0) + std::cout << "Closed server"; + else + std::cout << "Closed client #" << idx; + std::cout << " (" << _pollfds.size() - idx - 1 << " clients remaining)" + << std::endl; } - std::cout << "caught signal: " << g_stopServerLoop << "." << std::endl; } From 1651a6dc202c6dd9c50dfb8411f17a2e5b6eae75 Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 6 Aug 2026 16:29:43 +0200 Subject: [PATCH 097/349] rfa: do (partial) code cleanup and use more dedicated functions --- includes/IRC.hpp | 5 +- includes/{ => server}/IRCServer.hpp | 6 +- srcs/server/IRCServer.cpp | 108 ++++++++++++++-------------- 3 files changed, 57 insertions(+), 62 deletions(-) rename includes/{ => server}/IRCServer.hpp (85%) diff --git a/includes/IRC.hpp b/includes/IRC.hpp index 1e28153..1ea901b 100644 --- a/includes/IRC.hpp +++ b/includes/IRC.hpp @@ -1,3 +1,4 @@ -#include "IRCServer.hpp" +#include "server/IRCServer.hpp" -#include \ No newline at end of file +#include +#include \ No newline at end of file diff --git a/includes/IRCServer.hpp b/includes/server/IRCServer.hpp similarity index 85% rename from includes/IRCServer.hpp rename to includes/server/IRCServer.hpp index 86cc0f4..e0ec330 100644 --- a/includes/IRCServer.hpp +++ b/includes/server/IRCServer.hpp @@ -1,9 +1,4 @@ #include -#include -#include -// #include -// #include -// #include #include class IRCServer { @@ -22,6 +17,7 @@ class IRCServer { int _createDummyServerSocket(); void _acceptClient(); + void _disconnectClient(int const idx); public: IRCServer(); diff --git a/srcs/server/IRCServer.cpp b/srcs/server/IRCServer.cpp index 0743c91..60fce74 100644 --- a/srcs/server/IRCServer.cpp +++ b/srcs/server/IRCServer.cpp @@ -1,4 +1,4 @@ -#include "IRCServer.hpp" +#include "server/IRCServer.hpp" #include #include @@ -55,20 +55,17 @@ int IRCServer::_createDummyServerSocket() { if (serverSocket == -1) { return -1; } - - // Bind and listen + // Bind struct sockaddr_in serverAddr; serverAddr.sin_family = AF_INET; serverAddr.sin_port = htons(8082); serverAddr.sin_addr.s_addr = INADDR_ANY; - - if (bind(serverSocket, (struct sockaddr *)&serverAddr, + if (bind(serverSocket, reinterpret_cast(&serverAddr), sizeof(serverAddr)) == -1) { return -1; } - + // listen listen(serverSocket, 100); - _pollfds.resize(1); _pollfds[0].fd = serverSocket; _pollfds[0].events = POLLIN; // Monitor for incoming connections @@ -78,25 +75,39 @@ int IRCServer::_createDummyServerSocket() { void IRCServer::_acceptClient() { int clientFd; + struct pollfd clientPollfd; struct sockaddr_in clientAI; - struct pollfd clientPoll; socklen_t AISize = sizeof(clientAI); - // clientFd = accept(_pollfds.front().fd, reinterpret_cast(&clientAI), &AISize); + // accept4, so we can directly set SOCK_NONBLOCK + // the subject allows fcntl only on macOS clientFd = accept4(_pollfds.front().fd, reinterpret_cast(&clientAI), &AISize, SOCK_NONBLOCK); if (clientFd == -1) { - std::cerr << "[ERROR] Accepting failed." << std::endl; + std::cerr << "Accept failed." << std::endl; return; } - clientPoll.fd = clientFd; - clientPoll.events = POLLIN; - clientPoll.revents = 0; - _pollfds.push_back(clientPoll); - std::cout << "[SERVER] Client connected [FD: " << clientFd << "]" - << std::endl; + clientPollfd.fd = clientFd; + clientPollfd.events = POLLIN; + clientPollfd.revents = 0; + _pollfds.push_back(clientPollfd); + std::cout << "Client connected, fd: " << clientFd << std::endl; +} + +void IRCServer::_disconnectClient(int const idx) { + pollfd &pfd = _pollfds.at(idx); + + if (idx == 0) { + if (_pollfds.size() != 1) + std::cout << "Close clients before closing server!" << std::endl; + std::cout << "Closing server" << std::endl; + } else + std::cout << "Closing client #" << idx << " (" << _pollfds.size() - 2 + << " clients remaining)" << std::endl; + close(pfd.fd); + _pollfds.erase(_pollfds.begin() + idx); } void IRCServer::run() { @@ -104,25 +115,20 @@ void IRCServer::run() { // Event loop while (!g_stopServerLoop) { - std::cout << "running..." << std::endl; - sleep(5); + // std::cout << "running..." << std::endl; + sleep(1); - // poll() timeout in milliseconds (-1 = infinite) int pollResult = poll(_pollfds.data(), _pollfds.size(), 0); - if (pollResult == -1) { // print nice error message and exit the loop // or throw, but then have ressource cleanup after catch break; - } /* else if (pollResult == 0) { - // no data ready to read - continue; - } */ + } // Check if server socket has activity struct pollfd &serverPollFds = _pollfds[0]; if (serverPollFds.revents & POLLERR) { - std::cout << "Socket error\n"; + std::cerr << "Server socket error\n"; break; } else if (serverPollFds.revents & POLLIN) { _acceptClient(); @@ -131,48 +137,40 @@ void IRCServer::run() { // Clients for (size_t idx = 1; idx < _pollfds.size(); ++idx) { pollfd &pfd = _pollfds.at(idx); - std::cout << "Idx: " << idx << std::endl; + // std::cout << "Idx: " << idx << std::endl; + // std::cout << " revents: " << std::hex << pfd.revents << std::endl; + if (pfd.revents & POLLERR) { - std::cerr << "[ERROR] Recv failed on FD " << pfd.fd + std::cerr << "Client socket error on fd: " << pfd.fd << std::endl; - // } else if (pfd.revents & POLLHUP) { - // // removeClient(fd, pollIndex, "Remote host closed the connection"); - // close(pfd.fd); - // _pollfds.erase(_pollfds.begin() + idx); - // std::cout << "Closed client #" << idx << " (" - // << _pollfds.size() - 1 << " remaining)" << std::endl; - } else { - std::cout << " revents: " << std::hex << pfd.revents - << std::endl; - } - - if (pfd.revents & POLLIN) { + // Do we need to disconnect directly in this case? Maybe not. + _disconnectClient(idx); + } else if (pfd.revents & POLLIN) { char buffer[1024] = ""; int bytesRead = read(pfd.fd, buffer, 1024); if (bytesRead > 0) { std::cout << "Buffer read from client " << idx << ": \n" << std::string(buffer) << std::endl; } - if (bytesRead == 0) { - // removeClient(fd, pollIndex, "Remote host closed the connection"); - close(pfd.fd); - _pollfds.erase(_pollfds.begin() + idx); - std::cout << "Closed client #" << idx << " (" - << _pollfds.size() - 1 << " remaining)" - << std::endl; + if (bytesRead <= 0) { + // this means, the client closed the connection + // POLLHUP isn't useful, because + + // When you kill netcat with Ctrl+C: + // Netcat's OS kernel sent a FIN packet to your server + // Your server's poll() detected this with POLLIN + // Your read() returned 0 (zero bytes) = "I got the FIN, connection closed" + // FIN is the TCP mechanism for orderly shutdown. + // That's why POLLIN + read() return 0—the FIN, + // and the zero read is how the kernel tells you "the peer closed." + _disconnectClient(idx); } } } } - for (size_t idx = 0; idx < _pollfds.size(); ++idx) { - pollfd &pfd = _pollfds.at(idx); - close(pfd.fd); - if (idx == 0) - std::cout << "Closed server"; - else - std::cout << "Closed client #" << idx; - std::cout << " (" << _pollfds.size() - idx - 1 << " clients remaining)" - << std::endl; + while (!_pollfds.empty()) { + // disconnecting clients first + _disconnectClient(_pollfds.size() - 1); } std::cout << "caught signal: " << g_stopServerLoop << "." << std::endl; } From 44f4aaa9a53ee688482a55887cf9e3d331c168ef Mon Sep 17 00:00:00 2001 From: Rapha Date: Fri, 7 Aug 2026 14:28:31 +0200 Subject: [PATCH 098/349] rfa: cleanup for PR --- includes/server/IRCServer.hpp | 3 +- srcs/server/IRCServer.cpp | 82 +++++++++++++++++++++++++++++------ 2 files changed, 70 insertions(+), 15 deletions(-) diff --git a/includes/server/IRCServer.hpp b/includes/server/IRCServer.hpp index e0ec330..6f44771 100644 --- a/includes/server/IRCServer.hpp +++ b/includes/server/IRCServer.hpp @@ -15,13 +15,12 @@ class IRCServer { IRCServer(IRCServer const &other); IRCServer &operator=(IRCServer const &other); - int _createDummyServerSocket(); + int _createServerSocket(); void _acceptClient(); void _disconnectClient(int const idx); public: IRCServer(); - ~IRCServer(); void run(); diff --git a/srcs/server/IRCServer.cpp b/srcs/server/IRCServer.cpp index 60fce74..95570f0 100644 --- a/srcs/server/IRCServer.cpp +++ b/srcs/server/IRCServer.cpp @@ -6,12 +6,27 @@ #include #include +/* + Signal handling + will be refactored in a separate class +*/ volatile std::sig_atomic_t IRCServer::g_stopServerLoop = 0; +/* + Signal callback function + Will set a static class member to the triggering signal, + i.e. != 0, so the state can be used as a bool value directly. + Will be refactored in a separate class, and with a more explicit + stop state request function +*/ void IRCServer::g_handleStopSignals(int signal) { g_stopServerLoop = signal; } +/* + Setup all signal that should trigger the + IRCServer to stop. +*/ void IRCServer::_initSignalHandling() { g_stopServerLoop = 0; _stopSignals.push_back(SIGINT); @@ -22,10 +37,19 @@ void IRCServer::_initSignalHandling() { throw std::runtime_error("std::signal failed"); } +/* + Reset all signal that should trigger the + IRCServer to stop to the default on server exit. +*/ void IRCServer::_resetSignalHandling() { _setStopSignals(SIG_DFL); } +/* + Helper function to actually set the signal behaviour + on system level using sigaction, as this is the most + portable POSIX compliant way to do it. +*/ bool IRCServer::_setStopSignals(sighandler_t handler) { bool success = true; @@ -41,15 +65,31 @@ bool IRCServer::_setStopSignals(sighandler_t handler) { return success; } +/* + IRCServer Constructor + * setup signal handling + * ... +*/ IRCServer::IRCServer() { _initSignalHandling(); } +/* + IRCServer Destructor + * reset signal handling to default + * ... +*/ IRCServer::~IRCServer() { _resetSignalHandling(); } -int IRCServer::_createDummyServerSocket() { +/* + Dummy function to get a usable server socket + for mockup testing. + Implementation explicitely not exhaustive, + will be replaced by an "official" version anyways. +*/ +int IRCServer::_createServerSocket() { // Create a TCP socket int serverSocket = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0); if (serverSocket == -1) { @@ -73,6 +113,12 @@ int IRCServer::_createDummyServerSocket() { return serverSocket; } +/* + Dummy function to get a usable client socket + on connect for mockup testing. + Implementation explicitely not exhaustive, + will be replaced by an "official" version anyways. +*/ void IRCServer::_acceptClient() { int clientFd; struct pollfd clientPollfd; @@ -96,6 +142,14 @@ void IRCServer::_acceptClient() { std::cout << "Client connected, fd: " << clientFd << std::endl; } +/* + Dummy function to disconnet aclient socket + for mockup testing. + Implementation explicitely not exhaustive, + will be replaced by an "official" version anyways. + There is more to be done than just closing and removing + the client socket. +*/ void IRCServer::_disconnectClient(int const idx) { pollfd &pfd = _pollfds.at(idx); @@ -110,22 +164,28 @@ void IRCServer::_disconnectClient(int const idx) { _pollfds.erase(_pollfds.begin() + idx); } +/* + Main server loop + + * sets up its listening socket + * runs an infinit loop to listen for connections + * handles client connect and disconnect scenarios + * on server stop, disconnects all clients and closes + the server socket +*/ void IRCServer::run() { - _createDummyServerSocket(); + _createServerSocket(); - // Event loop while (!g_stopServerLoop) { - // std::cout << "running..." << std::endl; sleep(1); int pollResult = poll(_pollfds.data(), _pollfds.size(), 0); if (pollResult == -1) { - // print nice error message and exit the loop + // nice error message and exit the loop // or throw, but then have ressource cleanup after catch break; } - // Check if server socket has activity struct pollfd &serverPollFds = _pollfds[0]; if (serverPollFds.revents & POLLERR) { std::cerr << "Server socket error\n"; @@ -133,13 +193,8 @@ void IRCServer::run() { } else if (serverPollFds.revents & POLLIN) { _acceptClient(); } - - // Clients for (size_t idx = 1; idx < _pollfds.size(); ++idx) { pollfd &pfd = _pollfds.at(idx); - // std::cout << "Idx: " << idx << std::endl; - // std::cout << " revents: " << std::hex << pfd.revents << std::endl; - if (pfd.revents & POLLERR) { std::cerr << "Client socket error on fd: " << pfd.fd << std::endl; @@ -148,14 +203,16 @@ void IRCServer::run() { } else if (pfd.revents & POLLIN) { char buffer[1024] = ""; int bytesRead = read(pfd.fd, buffer, 1024); + // for mockup only, + // this functionality will go to its own function of course if (bytesRead > 0) { std::cout << "Buffer read from client " << idx << ": \n" << std::string(buffer) << std::endl; } if (bytesRead <= 0) { + // Comment will be deleted later, kept for reference for now // this means, the client closed the connection // POLLHUP isn't useful, because - // When you kill netcat with Ctrl+C: // Netcat's OS kernel sent a FIN packet to your server // Your server's poll() detected this with POLLIN @@ -169,7 +226,6 @@ void IRCServer::run() { } } while (!_pollfds.empty()) { - // disconnecting clients first _disconnectClient(_pollfds.size() - 1); } std::cout << "caught signal: " << g_stopServerLoop << "." << std::endl; From e2ab67e6b15d0e15bc372534152a2900094ef436 Mon Sep 17 00:00:00 2001 From: Rapha Date: Fri, 7 Aug 2026 14:48:02 +0200 Subject: [PATCH 099/349] bug: insert clang-tidy arguments in the right spot --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 4502db9..ddd7a05 100644 --- a/Makefile +++ b/Makefile @@ -74,7 +74,7 @@ tidy: tidy-all: @find srcs includes -type f \( -name "*.cpp" -o -name "*.hpp" \) -print0 \ - | xargs -0 clang-tidy -- $(CXXFLAGS) $(IFLAGS) + | xargs -0 -I {} clang-tidy {} -- $(CXXFLAGS) $(IFLAGS) $(NAME): $(OBJS) $(CXX) $(CXXFLAGS) $(OBJS) -o $@ From 266601273ddd4539a86de080f2219244baabeb38 Mon Sep 17 00:00:00 2001 From: Rapha Date: Fri, 7 Aug 2026 14:48:08 +0200 Subject: [PATCH 100/349] rfa: changes based on clang-tidy hints --- includes/server/IRCServer.hpp | 2 +- srcs/main.cpp | 1 + srcs/server/IRCServer.cpp | 18 ++++++++++-------- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/includes/server/IRCServer.hpp b/includes/server/IRCServer.hpp index 6f44771..f1b204d 100644 --- a/includes/server/IRCServer.hpp +++ b/includes/server/IRCServer.hpp @@ -17,7 +17,7 @@ class IRCServer { int _createServerSocket(); void _acceptClient(); - void _disconnectClient(int const idx); + void _disconnectClient(size_t const idx); public: IRCServer(); diff --git a/srcs/main.cpp b/srcs/main.cpp index 115520a..d934d3b 100644 --- a/srcs/main.cpp +++ b/srcs/main.cpp @@ -46,6 +46,7 @@ int main(int argc, char **argv) { IRCServer srv; srv.run(); } catch (std::exception const &e) { + std::cerr << e.what() << std::endl; } std::cout << "Clean exit." << std::endl; } diff --git a/srcs/server/IRCServer.cpp b/srcs/server/IRCServer.cpp index 95570f0..363ec87 100644 --- a/srcs/server/IRCServer.cpp +++ b/srcs/server/IRCServer.cpp @@ -96,7 +96,7 @@ int IRCServer::_createServerSocket() { return -1; } // Bind - struct sockaddr_in serverAddr; + struct sockaddr_in serverAddr = {}; serverAddr.sin_family = AF_INET; serverAddr.sin_port = htons(8082); serverAddr.sin_addr.s_addr = INADDR_ANY; @@ -120,15 +120,15 @@ int IRCServer::_createServerSocket() { will be replaced by an "official" version anyways. */ void IRCServer::_acceptClient() { - int clientFd; - struct pollfd clientPollfd; - struct sockaddr_in clientAI; - socklen_t AISize = sizeof(clientAI); + int clientFd = 0; + struct pollfd clientPollfd = {}; + struct sockaddr_in clientAI = {}; + socklen_t aiSize = sizeof(clientAI); // accept4, so we can directly set SOCK_NONBLOCK // the subject allows fcntl only on macOS clientFd = accept4(_pollfds.front().fd, - reinterpret_cast(&clientAI), &AISize, + reinterpret_cast(&clientAI), &aiSize, SOCK_NONBLOCK); if (clientFd == -1) { @@ -150,7 +150,7 @@ void IRCServer::_acceptClient() { There is more to be done than just closing and removing the client socket. */ -void IRCServer::_disconnectClient(int const idx) { +void IRCServer::_disconnectClient(size_t const idx) { pollfd &pfd = _pollfds.at(idx); if (idx == 0) { @@ -190,7 +190,9 @@ void IRCServer::run() { if (serverPollFds.revents & POLLERR) { std::cerr << "Server socket error\n"; break; - } else if (serverPollFds.revents & POLLIN) { + } + + if (serverPollFds.revents & POLLIN) { _acceptClient(); } for (size_t idx = 1; idx < _pollfds.size(); ++idx) { From 731770d91c3885869e92c24b67019a9e472fe2b5 Mon Sep 17 00:00:00 2001 From: Rapha Date: Tue, 18 Aug 2026 16:14:58 +0200 Subject: [PATCH 101/349] rfa: handle signals in their own class --- includes/IRC.hpp | 1 + includes/server/IRCServer.hpp | 10 +--- includes/server/SignalHandler.hpp | 20 ++++++++ srcs/main.cpp | 1 + srcs/server/IRCServer.cpp | 83 ++++--------------------------- srcs/server/SignalHandler.cpp | 71 ++++++++++++++++++++++++++ 6 files changed, 104 insertions(+), 82 deletions(-) create mode 100644 includes/server/SignalHandler.hpp create mode 100644 srcs/server/SignalHandler.cpp diff --git a/includes/IRC.hpp b/includes/IRC.hpp index 1ea901b..859afa8 100644 --- a/includes/IRC.hpp +++ b/includes/IRC.hpp @@ -1,4 +1,5 @@ #include "server/IRCServer.hpp" +#include "server/SignalHandler.hpp" #include #include \ No newline at end of file diff --git a/includes/server/IRCServer.hpp b/includes/server/IRCServer.hpp index f1b204d..3208ca4 100644 --- a/includes/server/IRCServer.hpp +++ b/includes/server/IRCServer.hpp @@ -1,15 +1,9 @@ #include #include +#include class IRCServer { private: - volatile static std::sig_atomic_t g_stopServerLoop; - static void g_handleStopSignals(int signal); - std::vector _stopSignals; - void _initSignalHandling(); - void _resetSignalHandling(); - bool _setStopSignals(sighandler_t handler); - std::vector _pollfds; IRCServer(IRCServer const &other); @@ -21,7 +15,7 @@ class IRCServer { public: IRCServer(); - ~IRCServer(); + // ~IRCServer(); void run(); }; diff --git a/includes/server/SignalHandler.hpp b/includes/server/SignalHandler.hpp new file mode 100644 index 0000000..542b58f --- /dev/null +++ b/includes/server/SignalHandler.hpp @@ -0,0 +1,20 @@ +#include +#include + +class SignalHandler { + private: + volatile static std::sig_atomic_t g_stopServerLoop; + static void g_handleStopSignals(int signal); + std::vector _stopSignals; + bool _setStopSignals(sighandler_t handler); + + std::vector _pollfds; + + SignalHandler(SignalHandler const &other); + SignalHandler &operator=(SignalHandler const &other); + + public: + SignalHandler(); + ~SignalHandler(); + static bool stopLoopSignalReceived(); +}; diff --git a/srcs/main.cpp b/srcs/main.cpp index d934d3b..2ef6928 100644 --- a/srcs/main.cpp +++ b/srcs/main.cpp @@ -43,6 +43,7 @@ int main(int argc, char **argv) { return (1); try { + SignalHandler sigHandler; IRCServer srv; srv.run(); } catch (std::exception const &e) { diff --git a/srcs/server/IRCServer.cpp b/srcs/server/IRCServer.cpp index 363ec87..871fb36 100644 --- a/srcs/server/IRCServer.cpp +++ b/srcs/server/IRCServer.cpp @@ -1,4 +1,5 @@ #include "server/IRCServer.hpp" +#include "server/SignalHandler.hpp" #include #include @@ -6,82 +7,17 @@ #include #include -/* - Signal handling - will be refactored in a separate class -*/ -volatile std::sig_atomic_t IRCServer::g_stopServerLoop = 0; - -/* - Signal callback function - Will set a static class member to the triggering signal, - i.e. != 0, so the state can be used as a bool value directly. - Will be refactored in a separate class, and with a more explicit - stop state request function -*/ -void IRCServer::g_handleStopSignals(int signal) { - g_stopServerLoop = signal; -} - -/* - Setup all signal that should trigger the - IRCServer to stop. -*/ -void IRCServer::_initSignalHandling() { - g_stopServerLoop = 0; - _stopSignals.push_back(SIGINT); - _stopSignals.push_back(SIGQUIT); - _stopSignals.push_back(SIGTERM); - - if (!_setStopSignals(g_handleStopSignals)) - throw std::runtime_error("std::signal failed"); -} - -/* - Reset all signal that should trigger the - IRCServer to stop to the default on server exit. -*/ -void IRCServer::_resetSignalHandling() { - _setStopSignals(SIG_DFL); -} - -/* - Helper function to actually set the signal behaviour - on system level using sigaction, as this is the most - portable POSIX compliant way to do it. -*/ -bool IRCServer::_setStopSignals(sighandler_t handler) { - bool success = true; - - struct sigaction sigact = {}; - sigact.sa_handler = handler; - sigemptyset(&sigact.sa_mask); - sigact.sa_flags = 0; - - for (size_t i = 0; i < _stopSignals.size(); ++i) - if (sigaction(_stopSignals.at(i), &sigact, NULL) != 0) - success = false; - - return success; -} - /* IRCServer Constructor - * setup signal handling - * ... + * ... */ -IRCServer::IRCServer() { - _initSignalHandling(); -} +IRCServer::IRCServer() {} -/* - IRCServer Destructor - * reset signal handling to default - * ... -*/ -IRCServer::~IRCServer() { - _resetSignalHandling(); -} +// /* +// IRCServer Destructor +// * ... +// */ +// IRCServer::~IRCServer() {} /* Dummy function to get a usable server socket @@ -176,7 +112,7 @@ void IRCServer::_disconnectClient(size_t const idx) { void IRCServer::run() { _createServerSocket(); - while (!g_stopServerLoop) { + while (!SignalHandler::stopLoopSignalReceived()) { sleep(1); int pollResult = poll(_pollfds.data(), _pollfds.size(), 0); @@ -230,5 +166,4 @@ void IRCServer::run() { while (!_pollfds.empty()) { _disconnectClient(_pollfds.size() - 1); } - std::cout << "caught signal: " << g_stopServerLoop << "." << std::endl; } diff --git a/srcs/server/SignalHandler.cpp b/srcs/server/SignalHandler.cpp new file mode 100644 index 0000000..75dfe0d --- /dev/null +++ b/srcs/server/SignalHandler.cpp @@ -0,0 +1,71 @@ +#include "server/SignalHandler.hpp" + +#include +#include +#include +#include +#include + +/* + Signal handling + will be refactored in a separate class +*/ +volatile std::sig_atomic_t SignalHandler::g_stopServerLoop = 0; + +/* + Signal callback function + Will set a static class member to the triggering signal, + i.e. != 0, so the state can be used as a bool value directly. + Will be refactored in a separate class, and with a more explicit + stop state request function +*/ +void SignalHandler::g_handleStopSignals(int signal) { + std::cout << "Caught stop signal: " << g_stopServerLoop << "." << std::endl; + g_stopServerLoop = signal; +} + +/* + Helper function to actually set the signal behaviour + on system level using sigaction, as this is the most + portable POSIX compliant way to do it. +*/ +bool SignalHandler::_setStopSignals(sighandler_t handler) { + bool success = true; + + struct sigaction sigact = {}; + sigact.sa_handler = handler; + sigemptyset(&sigact.sa_mask); + sigact.sa_flags = 0; + + for (size_t i = 0; success && i < _stopSignals.size(); ++i) + if (sigaction(_stopSignals.at(i), &sigact, NULL) != 0) + success = false; + + return success; +} + +/* + SignalHandler Constructor + * setup signal handling +*/ +SignalHandler::SignalHandler() { + g_stopServerLoop = 0; + _stopSignals.push_back(SIGINT); + _stopSignals.push_back(SIGQUIT); + _stopSignals.push_back(SIGTERM); + + if (!_setStopSignals(g_handleStopSignals)) + throw std::runtime_error("std::signal failed"); +} + +/* + SignalHandler Destructor + * reset signal handling to default +*/ +SignalHandler::~SignalHandler() { + _setStopSignals(SIG_DFL); +} + +bool SignalHandler::stopLoopSignalReceived() { + return (g_stopServerLoop > 0); +} From b3fa8091f19d340bae1c9e3782174d56bf863200 Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 19 Aug 2026 17:36:03 +0200 Subject: [PATCH 102/349] rfa: clang-format dbg: move msg about signal to actually output the received signal number --- includes/server/SignalHandler.hpp | 2 +- srcs/server/SignalHandler.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/includes/server/SignalHandler.hpp b/includes/server/SignalHandler.hpp index 542b58f..cb4759e 100644 --- a/includes/server/SignalHandler.hpp +++ b/includes/server/SignalHandler.hpp @@ -16,5 +16,5 @@ class SignalHandler { public: SignalHandler(); ~SignalHandler(); - static bool stopLoopSignalReceived(); + static bool stopLoopSignalReceived(); }; diff --git a/srcs/server/SignalHandler.cpp b/srcs/server/SignalHandler.cpp index 75dfe0d..6b939dd 100644 --- a/srcs/server/SignalHandler.cpp +++ b/srcs/server/SignalHandler.cpp @@ -20,8 +20,8 @@ volatile std::sig_atomic_t SignalHandler::g_stopServerLoop = 0; stop state request function */ void SignalHandler::g_handleStopSignals(int signal) { - std::cout << "Caught stop signal: " << g_stopServerLoop << "." << std::endl; g_stopServerLoop = signal; + std::cout << "Caught stop signal: " << g_stopServerLoop << "." << std::endl; } /* From 423358baaf0d56d40a42a5874fbd92b7c27f3bf8 Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 19 Aug 2026 17:37:41 +0200 Subject: [PATCH 103/349] fet: * handle chunked messages * respect max message size 512 (see RFC 2812) --- includes/server/IRCServer.hpp | 10 ++++-- srcs/main.cpp | 4 +-- srcs/server/IRCServer.cpp | 59 ++++++++++++++++++++++++++++++----- 3 files changed, 60 insertions(+), 13 deletions(-) diff --git a/includes/server/IRCServer.hpp b/includes/server/IRCServer.hpp index 3208ca4..5a563c7 100644 --- a/includes/server/IRCServer.hpp +++ b/includes/server/IRCServer.hpp @@ -1,10 +1,14 @@ -#include -#include #include +#include +#include + +#define BUFFLEN 516 +#define MAXMSGLEN 512 class IRCServer { private: - std::vector _pollfds; + std::vector _pollfds; + std::vector _userInputBuffer; IRCServer(IRCServer const &other); IRCServer &operator=(IRCServer const &other); diff --git a/srcs/main.cpp b/srcs/main.cpp index 2ef6928..ae8710c 100644 --- a/srcs/main.cpp +++ b/srcs/main.cpp @@ -43,8 +43,8 @@ int main(int argc, char **argv) { return (1); try { - SignalHandler sigHandler; - IRCServer srv; + SignalHandler sigHandler; + IRCServer srv; srv.run(); } catch (std::exception const &e) { std::cerr << e.what() << std::endl; diff --git a/srcs/server/IRCServer.cpp b/srcs/server/IRCServer.cpp index 871fb36..b1e8b5e 100644 --- a/srcs/server/IRCServer.cpp +++ b/srcs/server/IRCServer.cpp @@ -1,4 +1,5 @@ #include "server/IRCServer.hpp" + #include "server/SignalHandler.hpp" #include @@ -43,6 +44,7 @@ int IRCServer::_createServerSocket() { // listen listen(serverSocket, 100); _pollfds.resize(1); + _userInputBuffer.resize(1); _pollfds[0].fd = serverSocket; _pollfds[0].events = POLLIN; // Monitor for incoming connections @@ -75,6 +77,7 @@ void IRCServer::_acceptClient() { clientPollfd.events = POLLIN; clientPollfd.revents = 0; _pollfds.push_back(clientPollfd); + _userInputBuffer.push_back(std::string()); std::cout << "Client connected, fd: " << clientFd << std::endl; } @@ -98,6 +101,7 @@ void IRCServer::_disconnectClient(size_t const idx) { << " clients remaining)" << std::endl; close(pfd.fd); _pollfds.erase(_pollfds.begin() + idx); + _userInputBuffer.erase(_userInputBuffer.begin() + idx); } /* @@ -113,7 +117,7 @@ void IRCServer::run() { _createServerSocket(); while (!SignalHandler::stopLoopSignalReceived()) { - sleep(1); + usleep(100); int pollResult = poll(_pollfds.data(), _pollfds.size(), 0); if (pollResult == -1) { @@ -136,16 +140,55 @@ void IRCServer::run() { if (pfd.revents & POLLERR) { std::cerr << "Client socket error on fd: " << pfd.fd << std::endl; - // Do we need to disconnect directly in this case? Maybe not. _disconnectClient(idx); } else if (pfd.revents & POLLIN) { - char buffer[1024] = ""; - int bytesRead = read(pfd.fd, buffer, 1024); - // for mockup only, - // this functionality will go to its own function of course + char buffer[BUFFLEN]; + int bytesRead = recv(pfd.fd, buffer, BUFFLEN, 0); + if (bytesRead > 0) { - std::cout << "Buffer read from client " << idx << ": \n" - << std::string(buffer) << std::endl; + std::string &userInput = _userInputBuffer.at(idx); + std::string bufferStr(buffer, bytesRead); + // bool userInputValid = false; + + // // DEBUG for cont'd messages + // if (!userInput.empty() && *userInput.rbegin() != '\r') { + // *userInput.rbegin() = '&'; + // } + userInput += bufferStr; + size_t lfcrPos = userInput.find("\r\n"); + if (userInput.length() >= MAXMSGLEN) { + if (lfcrPos > MAXMSGLEN - 2) { + std::cerr << "Client data error: message too " + "long.\nData sent will be discarded." + << std::endl; + while (bytesRead > 0) + bytesRead = recv(pfd.fd, buffer, BUFFLEN, 0); + userInput.clear(); + continue; + } + } else if (lfcrPos == std::string::npos) { + continue; + } else if (lfcrPos < userInput.length() - 2) { + std::cerr << "Client data error: message " + "malformed, data after CR-LF.\nExtraneous " + "data will be discarded." + << std::endl; + userInput.resize(lfcrPos + 2); + } + // if (lfcrPos == userInput.length() - 2) + // userInputValid = true; + // else { + // std::cerr << "What's going on here?" << std::endl; + // // userInput += bufferStr.substr(0, lfcrPos + 2); + // } + // if (userInputValid) + { + std::cout << "UserInput read from client " << idx + << ": \n" + << std::string(userInput) /* << std::endl */; + userInput.clear(); + } + // } } if (bytesRead <= 0) { // Comment will be deleted later, kept for reference for now From f215fa1f99d3b621dee31e53a9d6c440422ebde8 Mon Sep 17 00:00:00 2001 From: Rapha Date: Fri, 21 Aug 2026 12:49:04 +0200 Subject: [PATCH 104/349] clang-tidy: * remove warning about const in declarations * change member name styling to trailing underscore --- .clang-tidy | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 3a38405..58d37a8 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -14,8 +14,9 @@ Checks: > -cppcoreguidelines-pro-bounds-constant-array-index, -cppcoreguidelines-macro-to-enum, -readability-braces-around-statements, - -readability-implicit-bool-conversion, + -readability-avoid-const-params-in-decls, -readability-identifier-length, + -readability-implicit-bool-conversion, -performance-avoid-endl, WarningsAsErrors: true @@ -39,15 +40,13 @@ CheckOptions: value: camelBack - key: readability-identifier-naming.VariableCase value: camelBack - - key: readability-identifier-naming.ClassMemberPrefix - value: g_ - - key: readability-identifier-naming.ClassMethodPrefix - value: g_ - - key: readability-identifier-naming.PrivateMemberPrefix + - key: readability-identifier-naming.ClassMemberSuffix + value: _ + - key: readability-identifier-naming.PrivateMemberSuffix value: _ - key: readability-identifier-naming.PrivateMethodCase value: camelBack - - key: readability-identifier-naming.PrivateMethodPrefix + - key: readability-identifier-naming.PrivateMethodSuffix value: _ - key: readability-identifier-naming.IgnoreMainLikeFunctions From a8cbaa417d4965a3c16c18d6d3093bf95a6195e1 Mon Sep 17 00:00:00 2001 From: Rapha Date: Fri, 21 Aug 2026 12:56:00 +0200 Subject: [PATCH 105/349] rfa: * apply new member name convention * remove explicit d'tor and copy member functions * socket handling: cleanup, restructure, error mgmnt feat: initialise port and password from cla bug: remove _pollfds member from SignalHandler --- includes/server/IRCServer.hpp | 19 ++-- includes/server/SignalHandler.hpp | 10 +- srcs/main.cpp | 40 +++++++- srcs/server/IRCServer.cpp | 164 +++++++++++++++++++++--------- srcs/server/SignalHandler.cpp | 28 ++--- 5 files changed, 178 insertions(+), 83 deletions(-) diff --git a/includes/server/IRCServer.hpp b/includes/server/IRCServer.hpp index 5a563c7..1c14bc2 100644 --- a/includes/server/IRCServer.hpp +++ b/includes/server/IRCServer.hpp @@ -4,21 +4,24 @@ #define BUFFLEN 516 #define MAXMSGLEN 512 +#define MAX_CONNECTIONS 512 class IRCServer { private: - std::vector _pollfds; - std::vector _userInputBuffer; + int port_; + std::string password_; + std::vector pollFds_; + std::vector userInputBuffer_; - IRCServer(IRCServer const &other); - IRCServer &operator=(IRCServer const &other); + // IRCServer(IRCServer const &other); + // IRCServer &operator=(IRCServer const &other); - int _createServerSocket(); - void _acceptClient(); - void _disconnectClient(size_t const idx); + int createServerSocket_(int const domain); + void acceptClient_(); + void disconnectClient_(size_t const idx); public: - IRCServer(); + IRCServer(int const port, std::string const &password); // ~IRCServer(); void run(); diff --git a/includes/server/SignalHandler.hpp b/includes/server/SignalHandler.hpp index cb4759e..eb33a10 100644 --- a/includes/server/SignalHandler.hpp +++ b/includes/server/SignalHandler.hpp @@ -3,12 +3,10 @@ class SignalHandler { private: - volatile static std::sig_atomic_t g_stopServerLoop; - static void g_handleStopSignals(int signal); - std::vector _stopSignals; - bool _setStopSignals(sighandler_t handler); - - std::vector _pollfds; + volatile static std::sig_atomic_t stopServerLoop_; + static void handleStopSignals_(int signal); + std::vector stopSignals_; + bool setStopSignals_(sighandler_t handler); SignalHandler(SignalHandler const &other); SignalHandler &operator=(SignalHandler const &other); diff --git a/srcs/main.cpp b/srcs/main.cpp index ae8710c..4fce3a3 100644 --- a/srcs/main.cpp +++ b/srcs/main.cpp @@ -1,5 +1,13 @@ #include "IRC.hpp" +#include +#include +#include +#include + +static void checkExtractCmdLnArgs(int const argc, char *const argv[], int &port, + std::string &password); + /* NAME: ircserv @@ -37,17 +45,39 @@ equivalent) */ -int main(int argc, char **argv) { - (void)argv; - if (argc != 3) - return (1); +int main(int argc, char **argv) { + int port = 0; + std::string password; try { + checkExtractCmdLnArgs(argc, argv, port, password); + SignalHandler sigHandler; - IRCServer srv; + IRCServer srv(port, password); srv.run(); } catch (std::exception const &e) { std::cerr << e.what() << std::endl; + return 1; } std::cout << "Clean exit." << std::endl; + return 0; +} + +static void checkExtractCmdLnArgs(int const argc, char *const argv[], int &port, + std::string &password) { + std::istringstream iss; + std::string remainder; + if (argc != 3) + throw std::out_of_range( + "Usage: ./ircserv "); + + iss.str(argv[1]); // NOLINT + if (!((iss >> port) && (iss >> remainder).eof())) + throw std::runtime_error( + "Error while extracting port number from commandline parameter."); + + iss.str(argv[2]); // NOLINT + if (!(iss >> password).eof()) + throw std::out_of_range( + "Error while extracting password from commandline parameter."); } diff --git a/srcs/server/IRCServer.cpp b/srcs/server/IRCServer.cpp index b1e8b5e..64243ef 100644 --- a/srcs/server/IRCServer.cpp +++ b/srcs/server/IRCServer.cpp @@ -1,18 +1,23 @@ #include "server/IRCServer.hpp" #include "server/SignalHandler.hpp" +// #include "server/Socket.hpp" #include #include #include +#include #include +// #include +#include #include /* IRCServer Constructor - * ... + * set port and password from command line args */ -IRCServer::IRCServer() {} +IRCServer::IRCServer(int const port, std::string const &password) + : port_(port), password_(password) {} // /* // IRCServer Destructor @@ -26,29 +31,81 @@ IRCServer::IRCServer() {} Implementation explicitely not exhaustive, will be replaced by an "official" version anyways. */ -int IRCServer::_createServerSocket() { - // Create a TCP socket - int serverSocket = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0); - if (serverSocket == -1) { - return -1; - } - // Bind - struct sockaddr_in serverAddr = {}; - serverAddr.sin_family = AF_INET; - serverAddr.sin_port = htons(8082); - serverAddr.sin_addr.s_addr = INADDR_ANY; - if (bind(serverSocket, reinterpret_cast(&serverAddr), - sizeof(serverAddr)) == -1) { - return -1; +static std::string getDomainStr(int const domain) { + if (domain == AF_INET) + return "IPv4"; + if (domain == AF_INET6) + return "IPv6"; + return ""; +} + +static void storeServerSocket(int const sock, std::vector &pFds, + std::vector &usrInBuff) { + pollfd pfd = {}; + pfd.fd = sock; + pfd.events = POLLIN; // Monitor for incoming connections + + pFds.push_back(pfd); + usrInBuff.push_back(std::string()); +} + +static void bindServerSocket(int const socketFd, int const domain, + int const port) { + struct sockaddr *serverSockaddr = 0; + socklen_t socklen = 0; + struct sockaddr_in serverAddr4 = {}; + struct sockaddr_in6 serverAddr6 = {}; + + if (domain == AF_INET) { + serverAddr4.sin_family = AF_INET; + serverAddr4.sin_addr.s_addr = INADDR_ANY; + serverAddr4.sin_port = htons(port); + serverSockaddr = + reinterpret_cast(&serverAddr4); // NOLINT + socklen = sizeof(serverAddr4); + } else if (domain == AF_INET6) { + serverAddr6.sin6_family = AF_INET; + serverAddr6.sin6_addr = in6addr_any; + serverAddr6.sin6_port = htons(port); + serverSockaddr = + reinterpret_cast(&serverAddr6); // NOLINT + socklen = sizeof(serverAddr6); } - // listen - listen(serverSocket, 100); - _pollfds.resize(1); - _userInputBuffer.resize(1); - _pollfds[0].fd = serverSocket; - _pollfds[0].events = POLLIN; // Monitor for incoming connections - - return serverSocket; + if (bind(socketFd, serverSockaddr, socklen) < 0) + throw std::runtime_error("Error while binding server socket for " + + getDomainStr(domain) + " !"); +} + +int IRCServer::createServerSocket_(int const domain) { + if (domain != AF_INET && domain != AF_INET6) + throw std::invalid_argument( + "Internal Error while opening server socket."); + + int socketFd = socket(domain, SOCK_STREAM, 0); + if (socketFd < 0) + throw std::runtime_error("Error while opening server socket for " + + getDomainStr(domain) + " !"); + + int optval = 1; + if (setsockopt(socketFd, SOL_SOCKET, SO_REUSEADDR, &optval, + sizeof(optval)) != 0) + throw std::runtime_error("Error while setting socket options for " + + getDomainStr(domain) + " !"); + + if (fcntl(socketFd, F_SETFL, O_NONBLOCK) != 0) // NOLINT + throw std::runtime_error( + "Error while setting socket to NON-BLOCKING for " + + getDomainStr(domain) + " !"); + + bindServerSocket(socketFd, domain, port_); + + if (listen(socketFd, MAX_CONNECTIONS) < 0) + throw std::runtime_error("Error while listening on socket for " + + getDomainStr(domain) + " !"); + + storeServerSocket(socketFd, pollFds_, userInputBuffer_); + + return socketFd; } /* @@ -57,7 +114,7 @@ int IRCServer::_createServerSocket() { Implementation explicitely not exhaustive, will be replaced by an "official" version anyways. */ -void IRCServer::_acceptClient() { +void IRCServer::acceptClient_() { int clientFd = 0; struct pollfd clientPollfd = {}; struct sockaddr_in clientAI = {}; @@ -65,8 +122,9 @@ void IRCServer::_acceptClient() { // accept4, so we can directly set SOCK_NONBLOCK // the subject allows fcntl only on macOS - clientFd = accept4(_pollfds.front().fd, - reinterpret_cast(&clientAI), &aiSize, + clientFd = accept4(pollFds_.front().fd, + reinterpret_cast(&clientAI), + &aiSize, // NOLINT SOCK_NONBLOCK); if (clientFd == -1) { @@ -76,8 +134,8 @@ void IRCServer::_acceptClient() { clientPollfd.fd = clientFd; clientPollfd.events = POLLIN; clientPollfd.revents = 0; - _pollfds.push_back(clientPollfd); - _userInputBuffer.push_back(std::string()); + pollFds_.push_back(clientPollfd); + userInputBuffer_.push_back(std::string()); std::cout << "Client connected, fd: " << clientFd << std::endl; } @@ -89,19 +147,24 @@ void IRCServer::_acceptClient() { There is more to be done than just closing and removing the client socket. */ -void IRCServer::_disconnectClient(size_t const idx) { - pollfd &pfd = _pollfds.at(idx); +void IRCServer::disconnectClient_(size_t const idx) { + pollfd &pfd = pollFds_.at(idx); if (idx == 0) { - if (_pollfds.size() != 1) + if (pollFds_.size() != 1) std::cout << "Close clients before closing server!" << std::endl; std::cout << "Closing server" << std::endl; } else - std::cout << "Closing client #" << idx << " (" << _pollfds.size() - 2 + std::cout << "Closing client #" << idx << " (" << pollFds_.size() - 2 << " clients remaining)" << std::endl; close(pfd.fd); - _pollfds.erase(_pollfds.begin() + idx); - _userInputBuffer.erase(_userInputBuffer.begin() + idx); + + std::vector::difference_type dist = + static_cast::difference_type>(idx); + pollFds_.erase(pollFds_.begin() + dist); + userInputBuffer_.erase( + userInputBuffer_.begin() + + static_cast::difference_type>(dist)); } /* @@ -114,40 +177,40 @@ void IRCServer::_disconnectClient(size_t const idx) { the server socket */ void IRCServer::run() { - _createServerSocket(); + createServerSocket_(AF_INET); while (!SignalHandler::stopLoopSignalReceived()) { usleep(100); - int pollResult = poll(_pollfds.data(), _pollfds.size(), 0); + int pollResult = poll(pollFds_.data(), pollFds_.size(), 0); if (pollResult == -1) { // nice error message and exit the loop // or throw, but then have ressource cleanup after catch break; } - struct pollfd &serverPollFds = _pollfds[0]; + struct pollfd &serverPollFds = pollFds_[0]; if (serverPollFds.revents & POLLERR) { std::cerr << "Server socket error\n"; break; } if (serverPollFds.revents & POLLIN) { - _acceptClient(); + acceptClient_(); } - for (size_t idx = 1; idx < _pollfds.size(); ++idx) { - pollfd &pfd = _pollfds.at(idx); + for (size_t idx = 1; idx < pollFds_.size(); ++idx) { + pollfd &pfd = pollFds_.at(idx); if (pfd.revents & POLLERR) { std::cerr << "Client socket error on fd: " << pfd.fd << std::endl; - _disconnectClient(idx); + disconnectClient_(idx); } else if (pfd.revents & POLLIN) { - char buffer[BUFFLEN]; - int bytesRead = recv(pfd.fd, buffer, BUFFLEN, 0); + char buffer[BUFFLEN]; + ssize_t bytesRead = recv(pfd.fd, &buffer[0], BUFFLEN, 0); if (bytesRead > 0) { - std::string &userInput = _userInputBuffer.at(idx); - std::string bufferStr(buffer, bytesRead); + std::string &userInput = userInputBuffer_.at(idx); + std::string bufferStr(&buffer[0], bytesRead); // bool userInputValid = false; // // DEBUG for cont'd messages @@ -162,7 +225,8 @@ void IRCServer::run() { "long.\nData sent will be discarded." << std::endl; while (bytesRead > 0) - bytesRead = recv(pfd.fd, buffer, BUFFLEN, 0); + bytesRead = + recv(pfd.fd, &buffer[0], BUFFLEN, 0); userInput.clear(); continue; } @@ -201,12 +265,12 @@ void IRCServer::run() { // FIN is the TCP mechanism for orderly shutdown. // That's why POLLIN + read() return 0—the FIN, // and the zero read is how the kernel tells you "the peer closed." - _disconnectClient(idx); + disconnectClient_(idx); } } } } - while (!_pollfds.empty()) { - _disconnectClient(_pollfds.size() - 1); + while (!pollFds_.empty()) { + disconnectClient_(pollFds_.size() - 1); } } diff --git a/srcs/server/SignalHandler.cpp b/srcs/server/SignalHandler.cpp index 6b939dd..d5b9ae4 100644 --- a/srcs/server/SignalHandler.cpp +++ b/srcs/server/SignalHandler.cpp @@ -10,7 +10,7 @@ Signal handling will be refactored in a separate class */ -volatile std::sig_atomic_t SignalHandler::g_stopServerLoop = 0; +volatile std::sig_atomic_t SignalHandler::stopServerLoop_ = 0; /* Signal callback function @@ -19,9 +19,9 @@ volatile std::sig_atomic_t SignalHandler::g_stopServerLoop = 0; Will be refactored in a separate class, and with a more explicit stop state request function */ -void SignalHandler::g_handleStopSignals(int signal) { - g_stopServerLoop = signal; - std::cout << "Caught stop signal: " << g_stopServerLoop << "." << std::endl; +void SignalHandler::handleStopSignals_(int signal) { + stopServerLoop_ = signal; + std::cout << "Caught stop signal: " << stopServerLoop_ << "." << std::endl; } /* @@ -29,7 +29,7 @@ void SignalHandler::g_handleStopSignals(int signal) { on system level using sigaction, as this is the most portable POSIX compliant way to do it. */ -bool SignalHandler::_setStopSignals(sighandler_t handler) { +bool SignalHandler::setStopSignals_(sighandler_t handler) { bool success = true; struct sigaction sigact = {}; @@ -37,8 +37,8 @@ bool SignalHandler::_setStopSignals(sighandler_t handler) { sigemptyset(&sigact.sa_mask); sigact.sa_flags = 0; - for (size_t i = 0; success && i < _stopSignals.size(); ++i) - if (sigaction(_stopSignals.at(i), &sigact, NULL) != 0) + for (size_t i = 0; success && i < stopSignals_.size(); ++i) + if (sigaction(stopSignals_.at(i), &sigact, NULL) != 0) success = false; return success; @@ -49,12 +49,12 @@ bool SignalHandler::_setStopSignals(sighandler_t handler) { * setup signal handling */ SignalHandler::SignalHandler() { - g_stopServerLoop = 0; - _stopSignals.push_back(SIGINT); - _stopSignals.push_back(SIGQUIT); - _stopSignals.push_back(SIGTERM); + stopServerLoop_ = 0; + stopSignals_.push_back(SIGINT); + stopSignals_.push_back(SIGQUIT); + stopSignals_.push_back(SIGTERM); - if (!_setStopSignals(g_handleStopSignals)) + if (!setStopSignals_(handleStopSignals_)) throw std::runtime_error("std::signal failed"); } @@ -63,9 +63,9 @@ SignalHandler::SignalHandler() { * reset signal handling to default */ SignalHandler::~SignalHandler() { - _setStopSignals(SIG_DFL); + setStopSignals_(SIG_DFL); } bool SignalHandler::stopLoopSignalReceived() { - return (g_stopServerLoop > 0); + return (stopServerLoop_ > 0); } From 41514c63ef35ccd866d7965c010a41fd6653b876 Mon Sep 17 00:00:00 2001 From: Rapha Date: Fri, 21 Aug 2026 14:58:26 +0200 Subject: [PATCH 106/349] feat: accept client implementation (partly) --- includes/server/IRCServer.hpp | 12 ++- srcs/server/IRCServer.cpp | 167 ++++++++++++++++++++++------------ 2 files changed, 120 insertions(+), 59 deletions(-) diff --git a/includes/server/IRCServer.hpp b/includes/server/IRCServer.hpp index 1c14bc2..17b854b 100644 --- a/includes/server/IRCServer.hpp +++ b/includes/server/IRCServer.hpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -10,14 +11,16 @@ class IRCServer { private: int port_; std::string password_; + + size_t numServerSockets_; std::vector pollFds_; - std::vector userInputBuffer_; + std::vector clientInputBuffers_; // IRCServer(IRCServer const &other); // IRCServer &operator=(IRCServer const &other); int createServerSocket_(int const domain); - void acceptClient_(); + void acceptClient_(int const serverFd); void disconnectClient_(size_t const idx); public: @@ -26,3 +29,8 @@ class IRCServer { void run(); }; + +typedef union uSocketAddrX { + struct sockaddr_in sai4; + struct sockaddr_in6 sai6; +} socketAddrX; diff --git a/srcs/server/IRCServer.cpp b/srcs/server/IRCServer.cpp index 64243ef..1abf868 100644 --- a/srcs/server/IRCServer.cpp +++ b/srcs/server/IRCServer.cpp @@ -8,7 +8,6 @@ #include #include #include -// #include #include #include @@ -17,7 +16,7 @@ * set port and password from command line args */ IRCServer::IRCServer(int const port, std::string const &password) - : port_(port), password_(password) {} + : port_(port), password_(password), numServerSockets_(0) {} // /* // IRCServer Destructor @@ -39,43 +38,83 @@ static std::string getDomainStr(int const domain) { return ""; } -static void storeServerSocket(int const sock, std::vector &pFds, - std::vector &usrInBuff) { - pollfd pfd = {}; - pfd.fd = sock; - pfd.events = POLLIN; // Monitor for incoming connections +static int sockAddrFamily(int const socketFd) { + struct sockaddr sockAddr; + socklen_t sockLen = sizeof(sockAddr); + if (getsockname(socketFd, &sockAddr, &sockLen) != 0) + throw; + return sockAddr.sa_family; +} - pFds.push_back(pfd); - usrInBuff.push_back(std::string()); +static void setSocketNonBlock(int const socketFd) { + int const domain = sockAddrFamily(socketFd); + if (fcntl(socketFd, F_SETFL, O_NONBLOCK) != 0) // NOLINT + throw std::runtime_error( + "Error while setting socket to NON-BLOCKING for " + + getDomainStr(domain) + " !"); } -static void bindServerSocket(int const socketFd, int const domain, - int const port) { - struct sockaddr *serverSockaddr = 0; - socklen_t socklen = 0; - struct sockaddr_in serverAddr4 = {}; - struct sockaddr_in6 serverAddr6 = {}; +static void setSocketAddr(int const socketFd, int const port, + socketAddrX &sockAddrIn46, socklen_t &sockLen) { + int domain = sockAddrFamily(socketFd); if (domain == AF_INET) { - serverAddr4.sin_family = AF_INET; - serverAddr4.sin_addr.s_addr = INADDR_ANY; - serverAddr4.sin_port = htons(port); - serverSockaddr = - reinterpret_cast(&serverAddr4); // NOLINT - socklen = sizeof(serverAddr4); + struct sockaddr_in &clientSockAddrIn4 = sockAddrIn46.sai4; + clientSockAddrIn4.sin_family = AF_INET; + clientSockAddrIn4.sin_addr.s_addr = INADDR_ANY; + clientSockAddrIn4.sin_port = htons(port); + sockLen = sizeof(clientSockAddrIn4); } else if (domain == AF_INET6) { - serverAddr6.sin6_family = AF_INET; - serverAddr6.sin6_addr = in6addr_any; - serverAddr6.sin6_port = htons(port); - serverSockaddr = - reinterpret_cast(&serverAddr6); // NOLINT - socklen = sizeof(serverAddr6); + struct sockaddr_in6 &clientSockAddrIn6 = sockAddrIn46.sai6; + clientSockAddrIn6.sin6_family = AF_INET6; + clientSockAddrIn6.sin6_addr = in6addr_any; + clientSockAddrIn6.sin6_port = htons(port); + sockLen = sizeof(clientSockAddrIn6); } - if (bind(socketFd, serverSockaddr, socklen) < 0) +} + +static void bindServerSocket(int const socketFd, int const domain, + int const port) { + // struct sockaddr *serverSockaddr = 0; + // socklen_t socklen = 0; + // struct sockaddr_in serverAddr4 = {}; + // struct sockaddr_in6 serverAddr6 = {}; + // if (domain == AF_INET) { + // serverAddr4.sin_family = AF_INET; + // serverAddr4.sin_addr.s_addr = INADDR_ANY; + // serverAddr4.sin_port = htons(port); + // serverSockaddr = + // reinterpret_cast(&serverAddr4); // NOLINT + // socklen = sizeof(serverAddr4); + // } else if (domain == AF_INET6) { + // serverAddr6.sin6_family = AF_INET; + // serverAddr6.sin6_addr = in6addr_any; + // serverAddr6.sin6_port = htons(port); + // serverSockaddr = + // reinterpret_cast(&serverAddr6); // NOLINT + // socklen = sizeof(serverAddr6); + // } + socketAddrX sockAddrIn46; + socklen_t sockLen; + setSocketAddr(socketFd, port, sockAddrIn46, sockLen); + + if (bind(socketFd, reinterpret_cast(&sockAddrIn46), + sockLen) < 0) throw std::runtime_error("Error while binding server socket for " + getDomainStr(domain) + " !"); } +static void storeServerSocket(int const socketFd, + std::vector &pFds, + std::vector &usrInBuff) { + pollfd pfd = {}; + pfd.fd = socketFd; + pfd.events = POLLIN; // Monitor for incoming connections + + pFds.push_back(pfd); + usrInBuff.push_back(std::string()); +} + int IRCServer::createServerSocket_(int const domain) { if (domain != AF_INET && domain != AF_INET6) throw std::invalid_argument( @@ -92,10 +131,7 @@ int IRCServer::createServerSocket_(int const domain) { throw std::runtime_error("Error while setting socket options for " + getDomainStr(domain) + " !"); - if (fcntl(socketFd, F_SETFL, O_NONBLOCK) != 0) // NOLINT - throw std::runtime_error( - "Error while setting socket to NON-BLOCKING for " + - getDomainStr(domain) + " !"); + setSocketNonBlock(socketFd); bindServerSocket(socketFd, domain, port_); @@ -103,7 +139,8 @@ int IRCServer::createServerSocket_(int const domain) { throw std::runtime_error("Error while listening on socket for " + getDomainStr(domain) + " !"); - storeServerSocket(socketFd, pollFds_, userInputBuffer_); + storeServerSocket(socketFd, pollFds_, clientInputBuffers_); + ++numServerSockets_; return socketFd; } @@ -114,29 +151,44 @@ int IRCServer::createServerSocket_(int const domain) { Implementation explicitely not exhaustive, will be replaced by an "official" version anyways. */ -void IRCServer::acceptClient_() { - int clientFd = 0; - struct pollfd clientPollfd = {}; - struct sockaddr_in clientAI = {}; - socklen_t aiSize = sizeof(clientAI); - - // accept4, so we can directly set SOCK_NONBLOCK - // the subject allows fcntl only on macOS - clientFd = accept4(pollFds_.front().fd, - reinterpret_cast(&clientAI), - &aiSize, // NOLINT - SOCK_NONBLOCK); - - if (clientFd == -1) { - std::cerr << "Accept failed." << std::endl; - return; - } +void IRCServer::acceptClient_(int const serverFd) { + socketAddrX sockAddrIn46; + socklen_t sockLen; + setSocketAddr(serverFd, 0, sockAddrIn46, sockLen); + + int const clientFd = + accept(serverFd, reinterpret_cast(&sockAddrIn46), + &sockLen); // NOLINT + if (clientFd == -1) + //throw ?? + ; + + setSocketNonBlock(clientFd); + + pollfd clientPollfd = {}; clientPollfd.fd = clientFd; clientPollfd.events = POLLIN; - clientPollfd.revents = 0; pollFds_.push_back(clientPollfd); - userInputBuffer_.push_back(std::string()); - std::cout << "Client connected, fd: " << clientFd << std::endl; + clientInputBuffers_.push_back(std::string()); + std::cout << "Client connected:\n" + << " fd: " << clientFd; + if (sockAddrIn46.sai4.sin_family == AF_INET) + std::cout << "\n AF: " << sockAddrIn46.sai4.sin_family + << "\n addr:port: " << sockAddrIn46.sai4.sin_addr.s_addr + << ":" << sockAddrIn46.sai4.sin_port << std::endl; + else if (sockAddrIn46.sai6.sin6_family == AF_INET6) { + std::cout << "\n AF: " << sockAddrIn46.sai6.sin6_family + << "\n addr:port: ["; + for (size_t idx = 0; + idx < sizeof(sockAddrIn46.sai6.sin6_addr) / sizeof(uint8_t); + ++idx) { + if (idx > 0 && idx % 4 == 0) + std::cout << ":"; + std::cout << std::hex << sockAddrIn46.sai6.sin6_addr.s6_addr[idx]; + } + + std::cout << "]:" << sockAddrIn46.sai4.sin_port << std::endl; + } } /* @@ -162,8 +214,8 @@ void IRCServer::disconnectClient_(size_t const idx) { std::vector::difference_type dist = static_cast::difference_type>(idx); pollFds_.erase(pollFds_.begin() + dist); - userInputBuffer_.erase( - userInputBuffer_.begin() + + clientInputBuffers_.erase( + clientInputBuffers_.begin() + static_cast::difference_type>(dist)); } @@ -178,6 +230,7 @@ void IRCServer::disconnectClient_(size_t const idx) { */ void IRCServer::run() { createServerSocket_(AF_INET); + createServerSocket_(AF_INET6); while (!SignalHandler::stopLoopSignalReceived()) { usleep(100); @@ -196,7 +249,7 @@ void IRCServer::run() { } if (serverPollFds.revents & POLLIN) { - acceptClient_(); + acceptClient_(serverPollFds.fd); } for (size_t idx = 1; idx < pollFds_.size(); ++idx) { pollfd &pfd = pollFds_.at(idx); @@ -209,7 +262,7 @@ void IRCServer::run() { ssize_t bytesRead = recv(pfd.fd, &buffer[0], BUFFLEN, 0); if (bytesRead > 0) { - std::string &userInput = userInputBuffer_.at(idx); + std::string &userInput = clientInputBuffers_.at(idx); std::string bufferStr(&buffer[0], bytesRead); // bool userInputValid = false; From 5137b3725fdf22c27972bb4e5a219c8df26d0f59 Mon Sep 17 00:00:00 2001 From: Rapha Date: Fri, 21 Aug 2026 16:30:02 +0200 Subject: [PATCH 107/349] feat: * finish implementation of acceptClient * dual stack ipv4/6 --- srcs/server/IRCServer.cpp | 76 ++++++++++++++++++++++++--------------- 1 file changed, 47 insertions(+), 29 deletions(-) diff --git a/srcs/server/IRCServer.cpp b/srcs/server/IRCServer.cpp index 1abf868..19987d4 100644 --- a/srcs/server/IRCServer.cpp +++ b/srcs/server/IRCServer.cpp @@ -104,9 +104,8 @@ static void bindServerSocket(int const socketFd, int const domain, getDomainStr(domain) + " !"); } -static void storeServerSocket(int const socketFd, - std::vector &pFds, - std::vector &usrInBuff) { +static void storePollFd(int const socketFd, std::vector &pFds, + std::vector &usrInBuff) { pollfd pfd = {}; pfd.fd = socketFd; pfd.events = POLLIN; // Monitor for incoming connections @@ -126,11 +125,17 @@ int IRCServer::createServerSocket_(int const domain) { getDomainStr(domain) + " !"); int optval = 1; - if (setsockopt(socketFd, SOL_SOCKET, SO_REUSEADDR, &optval, + if (setsockopt(socketFd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &optval, sizeof(optval)) != 0) throw std::runtime_error("Error while setting socket options for " + getDomainStr(domain) + " !"); + if (domain == AF_INET6) { + int v6only = 1; + setsockopt(socketFd, IPPROTO_IPV6, IPV6_V6ONLY, &v6only, + sizeof(v6only)); + } + setSocketNonBlock(socketFd); bindServerSocket(socketFd, domain, port_); @@ -139,7 +144,7 @@ int IRCServer::createServerSocket_(int const domain) { throw std::runtime_error("Error while listening on socket for " + getDomainStr(domain) + " !"); - storeServerSocket(socketFd, pollFds_, clientInputBuffers_); + storePollFd(socketFd, pollFds_, clientInputBuffers_); ++numServerSockets_; return socketFd; @@ -152,8 +157,8 @@ int IRCServer::createServerSocket_(int const domain) { will be replaced by an "official" version anyways. */ void IRCServer::acceptClient_(int const serverFd) { - socketAddrX sockAddrIn46; - socklen_t sockLen; + socketAddrX sockAddrIn46 = {}; + socklen_t sockLen = 0; setSocketAddr(serverFd, 0, sockAddrIn46, sockLen); int const clientFd = @@ -165,29 +170,40 @@ void IRCServer::acceptClient_(int const serverFd) { setSocketNonBlock(clientFd); - pollfd clientPollfd = {}; - clientPollfd.fd = clientFd; - clientPollfd.events = POLLIN; - pollFds_.push_back(clientPollfd); - clientInputBuffers_.push_back(std::string()); + storePollFd(clientFd, pollFds_, clientInputBuffers_); + std::cout << "Client connected:\n" << " fd: " << clientFd; - if (sockAddrIn46.sai4.sin_family == AF_INET) - std::cout << "\n AF: " << sockAddrIn46.sai4.sin_family - << "\n addr:port: " << sockAddrIn46.sai4.sin_addr.s_addr - << ":" << sockAddrIn46.sai4.sin_port << std::endl; - else if (sockAddrIn46.sai6.sin6_family == AF_INET6) { - std::cout << "\n AF: " << sockAddrIn46.sai6.sin6_family + + if (sockAddrIn46.sai4.sin_family == AF_INET) { + std::cout << "\n AF: " << getDomainStr(sockAddrIn46.sai4.sin_family) + << "\n addr:port: "; + uint8_t *addrData = + reinterpret_cast(&sockAddrIn46.sai4.sin_addr.s_addr); + for (size_t idx = 0; + idx < sizeof(sockAddrIn46.sai4.sin_addr) / sizeof(uint8_t); + ++idx) { + if (idx > 0) + std::cout << "."; + std::cout << std::dec << static_cast(addrData[idx]); + } + std::cout << ":" << std::dec << ntohs(sockAddrIn46.sai4.sin_port) + << std::endl; + } else if (sockAddrIn46.sai6.sin6_family == AF_INET6) { + std::cout << "\n AF: " << getDomainStr(sockAddrIn46.sai6.sin6_family) << "\n addr:port: ["; + uint8_t *addrData = + reinterpret_cast(&sockAddrIn46.sai6.sin6_addr.s6_addr); for (size_t idx = 0; idx < sizeof(sockAddrIn46.sai6.sin6_addr) / sizeof(uint8_t); ++idx) { if (idx > 0 && idx % 4 == 0) std::cout << ":"; - std::cout << std::hex << sockAddrIn46.sai6.sin6_addr.s6_addr[idx]; + std::cout << std::hex << static_cast(addrData[idx]); } - std::cout << "]:" << sockAddrIn46.sai4.sin_port << std::endl; + std::cout << "]:" << std::dec << ntohs(sockAddrIn46.sai4.sin_port) + << std::endl; } } @@ -231,6 +247,7 @@ void IRCServer::disconnectClient_(size_t const idx) { void IRCServer::run() { createServerSocket_(AF_INET); createServerSocket_(AF_INET6); + //createServerSocket_(AF_INET); while (!SignalHandler::stopLoopSignalReceived()) { usleep(100); @@ -242,16 +259,17 @@ void IRCServer::run() { break; } - struct pollfd &serverPollFds = pollFds_[0]; - if (serverPollFds.revents & POLLERR) { - std::cerr << "Server socket error\n"; - break; - } - - if (serverPollFds.revents & POLLIN) { - acceptClient_(serverPollFds.fd); + for (size_t idx = 0; idx < numServerSockets_; ++idx) { + struct pollfd const &serverPollFds = pollFds_[idx]; + if (serverPollFds.revents & POLLERR) { + std::cerr << "Server socket error\n"; + break; + } + if (serverPollFds.revents & POLLIN) { + acceptClient_(serverPollFds.fd); + } } - for (size_t idx = 1; idx < pollFds_.size(); ++idx) { + for (size_t idx = numServerSockets_; idx < pollFds_.size(); ++idx) { pollfd &pfd = pollFds_.at(idx); if (pfd.revents & POLLERR) { std::cerr << "Client socket error on fd: " << pfd.fd From da46e8f5827bbff67a90e8d7b062de5abc880450 Mon Sep 17 00:00:00 2001 From: Rapha Date: Fri, 21 Aug 2026 16:46:38 +0200 Subject: [PATCH 108/349] clang-tidy: * cleanup * still open: function run simplification --- srcs/server/IRCServer.cpp | 62 +++++++++++++++------------------------ 1 file changed, 24 insertions(+), 38 deletions(-) diff --git a/srcs/server/IRCServer.cpp b/srcs/server/IRCServer.cpp index 19987d4..eaeb166 100644 --- a/srcs/server/IRCServer.cpp +++ b/srcs/server/IRCServer.cpp @@ -39,9 +39,9 @@ static std::string getDomainStr(int const domain) { } static int sockAddrFamily(int const socketFd) { - struct sockaddr sockAddr; + struct sockaddr sockAddr = {}; socklen_t sockLen = sizeof(sockAddr); - if (getsockname(socketFd, &sockAddr, &sockLen) != 0) + if (getsockname(socketFd, &sockAddr, &sockLen) != 0) // NOLINT throw; return sockAddr.sa_family; } @@ -54,7 +54,7 @@ static void setSocketNonBlock(int const socketFd) { getDomainStr(domain) + " !"); } -static void setSocketAddr(int const socketFd, int const port, +static void setSocketAddr(int const socketFd, int const port, // NOLINT socketAddrX &sockAddrIn46, socklen_t &sockLen) { int domain = sockAddrFamily(socketFd); @@ -73,32 +73,14 @@ static void setSocketAddr(int const socketFd, int const port, } } -static void bindServerSocket(int const socketFd, int const domain, +static void bindServerSocket(int const socketFd, int const domain, // NOLINT int const port) { - // struct sockaddr *serverSockaddr = 0; - // socklen_t socklen = 0; - // struct sockaddr_in serverAddr4 = {}; - // struct sockaddr_in6 serverAddr6 = {}; - // if (domain == AF_INET) { - // serverAddr4.sin_family = AF_INET; - // serverAddr4.sin_addr.s_addr = INADDR_ANY; - // serverAddr4.sin_port = htons(port); - // serverSockaddr = - // reinterpret_cast(&serverAddr4); // NOLINT - // socklen = sizeof(serverAddr4); - // } else if (domain == AF_INET6) { - // serverAddr6.sin6_family = AF_INET; - // serverAddr6.sin6_addr = in6addr_any; - // serverAddr6.sin6_port = htons(port); - // serverSockaddr = - // reinterpret_cast(&serverAddr6); // NOLINT - // socklen = sizeof(serverAddr6); - // } - socketAddrX sockAddrIn46; - socklen_t sockLen; + socketAddrX sockAddrIn46 = {}; + socklen_t sockLen = 0; setSocketAddr(socketFd, port, sockAddrIn46, sockLen); - if (bind(socketFd, reinterpret_cast(&sockAddrIn46), + if (bind(socketFd, + reinterpret_cast(&sockAddrIn46), // NOLINT sockLen) < 0) throw std::runtime_error("Error while binding server socket for " + getDomainStr(domain) + " !"); @@ -159,14 +141,17 @@ int IRCServer::createServerSocket_(int const domain) { void IRCServer::acceptClient_(int const serverFd) { socketAddrX sockAddrIn46 = {}; socklen_t sockLen = 0; + setSocketAddr(serverFd, 0, sockAddrIn46, sockLen); - int const clientFd = - accept(serverFd, reinterpret_cast(&sockAddrIn46), - &sockLen); // NOLINT - if (clientFd == -1) - //throw ?? - ; + int const clientFd = accept( + serverFd, reinterpret_cast(&sockAddrIn46), // NOLINT + &sockLen); + if (clientFd == -1) { + // throw -> no everything else can still run w/o problems + // silent return -> cannot sent anything to client without working socket anyways... + return; + } setSocketNonBlock(clientFd); @@ -178,28 +163,28 @@ void IRCServer::acceptClient_(int const serverFd) { if (sockAddrIn46.sai4.sin_family == AF_INET) { std::cout << "\n AF: " << getDomainStr(sockAddrIn46.sai4.sin_family) << "\n addr:port: "; - uint8_t *addrData = - reinterpret_cast(&sockAddrIn46.sai4.sin_addr.s_addr); + uint8_t *addrData = reinterpret_cast( + &sockAddrIn46.sai4.sin_addr.s_addr); // NOLINT for (size_t idx = 0; idx < sizeof(sockAddrIn46.sai4.sin_addr) / sizeof(uint8_t); ++idx) { if (idx > 0) std::cout << "."; - std::cout << std::dec << static_cast(addrData[idx]); + std::cout << std::dec << static_cast(addrData[idx]); // NOLINT } std::cout << ":" << std::dec << ntohs(sockAddrIn46.sai4.sin_port) << std::endl; } else if (sockAddrIn46.sai6.sin6_family == AF_INET6) { std::cout << "\n AF: " << getDomainStr(sockAddrIn46.sai6.sin6_family) << "\n addr:port: ["; - uint8_t *addrData = - reinterpret_cast(&sockAddrIn46.sai6.sin6_addr.s6_addr); + uint8_t *addrData = reinterpret_cast( + &sockAddrIn46.sai6.sin6_addr.s6_addr); // NOLINT for (size_t idx = 0; idx < sizeof(sockAddrIn46.sai6.sin6_addr) / sizeof(uint8_t); ++idx) { if (idx > 0 && idx % 4 == 0) std::cout << ":"; - std::cout << std::hex << static_cast(addrData[idx]); + std::cout << std::hex << static_cast(addrData[idx]); // NOLINT } std::cout << "]:" << std::dec << ntohs(sockAddrIn46.sai4.sin_port) @@ -254,6 +239,7 @@ void IRCServer::run() { int pollResult = poll(pollFds_.data(), pollFds_.size(), 0); if (pollResult == -1) { + throw; // nice error message and exit the loop // or throw, but then have ressource cleanup after catch break; From 760e41826be70079325756f2fdaa8dacc8c8292e Mon Sep 17 00:00:00 2001 From: Rapha Date: Sat, 22 Aug 2026 12:00:39 +0200 Subject: [PATCH 109/349] rfa: outsourcing function printAddrfamilyAddrPort --- srcs/server/IRCServer.cpp | 53 ++++++++++++++++----------------------- 1 file changed, 22 insertions(+), 31 deletions(-) diff --git a/srcs/server/IRCServer.cpp b/srcs/server/IRCServer.cpp index eaeb166..7d500ac 100644 --- a/srcs/server/IRCServer.cpp +++ b/srcs/server/IRCServer.cpp @@ -132,6 +132,27 @@ int IRCServer::createServerSocket_(int const domain) { return socketFd; } +static void printAddrfamilyAddrPort(socketAddrX const &sockAddrIn46) { + char addrBuffer[64]; + socklen_t addrBuffLen = sizeof(addrBuffer) / sizeof(char); + + if (sockAddrIn46.sai4.sin_family == AF_INET) { + std::cout << "\n AF: " << getDomainStr(sockAddrIn46.sai4.sin_family) + << "\n addr:port: " + << inet_ntop(AF_INET, &sockAddrIn46.sai4.sin_addr, addrBuffer, + addrBuffLen) + << ":" << std::dec << ntohs(sockAddrIn46.sai4.sin_port) + << std::endl; + } else if (sockAddrIn46.sai6.sin6_family == AF_INET6) { + std::cout << "\n AF: " << getDomainStr(sockAddrIn46.sai6.sin6_family) + << "\n addr:port: [" + << inet_ntop(AF_INET6, &sockAddrIn46.sai6.sin6_addr, + addrBuffer, addrBuffLen) + << "]:" << std::dec << ntohs(sockAddrIn46.sai4.sin_port) + << std::endl; + } +} + /* Dummy function to get a usable client socket on connect for mockup testing. @@ -159,37 +180,7 @@ void IRCServer::acceptClient_(int const serverFd) { std::cout << "Client connected:\n" << " fd: " << clientFd; - - if (sockAddrIn46.sai4.sin_family == AF_INET) { - std::cout << "\n AF: " << getDomainStr(sockAddrIn46.sai4.sin_family) - << "\n addr:port: "; - uint8_t *addrData = reinterpret_cast( - &sockAddrIn46.sai4.sin_addr.s_addr); // NOLINT - for (size_t idx = 0; - idx < sizeof(sockAddrIn46.sai4.sin_addr) / sizeof(uint8_t); - ++idx) { - if (idx > 0) - std::cout << "."; - std::cout << std::dec << static_cast(addrData[idx]); // NOLINT - } - std::cout << ":" << std::dec << ntohs(sockAddrIn46.sai4.sin_port) - << std::endl; - } else if (sockAddrIn46.sai6.sin6_family == AF_INET6) { - std::cout << "\n AF: " << getDomainStr(sockAddrIn46.sai6.sin6_family) - << "\n addr:port: ["; - uint8_t *addrData = reinterpret_cast( - &sockAddrIn46.sai6.sin6_addr.s6_addr); // NOLINT - for (size_t idx = 0; - idx < sizeof(sockAddrIn46.sai6.sin6_addr) / sizeof(uint8_t); - ++idx) { - if (idx > 0 && idx % 4 == 0) - std::cout << ":"; - std::cout << std::hex << static_cast(addrData[idx]); // NOLINT - } - - std::cout << "]:" << std::dec << ntohs(sockAddrIn46.sai4.sin_port) - << std::endl; - } + printAddrfamilyAddrPort(sockAddrIn46); } /* From 1ce4d1b4ca764418a36a41bac92dc823e8d039d9 Mon Sep 17 00:00:00 2001 From: Rapha Date: Sat, 22 Aug 2026 13:02:16 +0200 Subject: [PATCH 110/349] rfa: putting helper functions in their own namespace/file --- includes/server/IRCServerHelper.hpp | 110 ++++++++++++++++++ srcs/server/IRCServer.cpp | 172 ++++++---------------------- 2 files changed, 148 insertions(+), 134 deletions(-) create mode 100644 includes/server/IRCServerHelper.hpp diff --git a/includes/server/IRCServerHelper.hpp b/includes/server/IRCServerHelper.hpp new file mode 100644 index 0000000..01443a0 --- /dev/null +++ b/includes/server/IRCServerHelper.hpp @@ -0,0 +1,110 @@ +#ifndef IRCSERVERHELPER_HPP +#define IRCSERVERHELPER_HPP + +#include +#include +#include + +namespace srvHelp { + +inline std::string getAdressFamilyStr(int const domain) { + if (domain == AF_INET) + return "IPv4"; + if (domain == AF_INET6) + return "IPv6"; + return "Unknown Address Family id"; +} + +inline int sockAddrFamily(int const socketFd) { + struct sockaddr sockAddr = {}; + socklen_t sockLen = sizeof(sockAddr); + if (getsockname(socketFd, &sockAddr, &sockLen) != 0) // NOLINT + throw; + return sockAddr.sa_family; +} + +inline void setSocketNonBlock(int const socketFd) { + int const domain = sockAddrFamily(socketFd); + if (fcntl(socketFd, F_SETFL, O_NONBLOCK) != 0) // NOLINT + throw std::runtime_error( + "Error while setting socket to NON-BLOCKING for " + + getAdressFamilyStr(domain) + " !"); +} + +inline void setSocketAddr(int const socketFd, int const port, // NOLINT + socketAddrX &sockAddrIn46, socklen_t &sockLen) { + int domain = sockAddrFamily(socketFd); + + if (domain == AF_INET) { + struct sockaddr_in &clientSockAddrIn4 = sockAddrIn46.sai4; + clientSockAddrIn4.sin_family = AF_INET; + clientSockAddrIn4.sin_addr.s_addr = INADDR_ANY; + clientSockAddrIn4.sin_port = htons(port); + sockLen = sizeof(clientSockAddrIn4); + } else if (domain == AF_INET6) { + struct sockaddr_in6 &clientSockAddrIn6 = sockAddrIn46.sai6; + clientSockAddrIn6.sin6_family = AF_INET6; + clientSockAddrIn6.sin6_addr = in6addr_any; + clientSockAddrIn6.sin6_port = htons(port); + sockLen = sizeof(clientSockAddrIn6); + } +} + +inline void bindServerSocket(int const socketFd, int const domain, // NOLINT + int const port) { + socketAddrX sockAddrIn46 = {}; + socklen_t sockLen = 0; + setSocketAddr(socketFd, port, sockAddrIn46, sockLen); + + if (bind(socketFd, + reinterpret_cast(&sockAddrIn46), // NOLINT + sockLen) < 0) + throw std::runtime_error("Error while binding server socket for " + + getAdressFamilyStr(domain) + " !"); +} + +inline void storePollFd(int const socketFd, std::vector &pFds, + std::vector &usrInBuff) { + pollfd pfd = {}; + pfd.fd = socketFd; + pfd.events = POLLIN; // Monitor for incoming connections + + pFds.push_back(pfd); + usrInBuff.push_back(std::string()); +} + +inline bool isSocketListening(int sockfd) { + int optVal = 0; + socklen_t optLen = sizeof(optVal); + if (getsockopt(sockfd, SOL_SOCKET, SO_ACCEPTCONN, &optVal, &optLen) < 0) + throw std::runtime_error("Error getting socket options."); + + return (optVal > 0); +} + +inline void printAddrfamilyAddrPort(socketAddrX const &sockAddrIn46) { + char addrBuffer[64]; + socklen_t addrBuffLen = sizeof(addrBuffer) / sizeof(char); + + if (sockAddrIn46.sai4.sin_family == AF_INET) { + std::cout << "\n AF: " + << getAdressFamilyStr(sockAddrIn46.sai4.sin_family) + << "\n addr:port: " + << inet_ntop(AF_INET, &sockAddrIn46.sai4.sin_addr, addrBuffer, + addrBuffLen) + << ":" << std::dec << ntohs(sockAddrIn46.sai4.sin_port) + << std::endl; + } else if (sockAddrIn46.sai6.sin6_family == AF_INET6) { + std::cout << "\n AF: " + << getAdressFamilyStr(sockAddrIn46.sai6.sin6_family) + << "\n addr:port: [" + << inet_ntop(AF_INET6, &sockAddrIn46.sai6.sin6_addr, + addrBuffer, addrBuffLen) + << "]:" << std::dec << ntohs(sockAddrIn46.sai4.sin_port) + << std::endl; + } +} + +} // namespace srvHelp + +#endif // IRCSERVERHELPER_HPP \ No newline at end of file diff --git a/srcs/server/IRCServer.cpp b/srcs/server/IRCServer.cpp index 7d500ac..5ab47d9 100644 --- a/srcs/server/IRCServer.cpp +++ b/srcs/server/IRCServer.cpp @@ -1,12 +1,9 @@ #include "server/IRCServer.hpp" +#include "server/IRCServerHelper.hpp" #include "server/SignalHandler.hpp" -// #include "server/Socket.hpp" -#include -#include #include -#include #include #include #include @@ -18,84 +15,9 @@ IRCServer::IRCServer(int const port, std::string const &password) : port_(port), password_(password), numServerSockets_(0) {} -// /* -// IRCServer Destructor -// * ... -// */ -// IRCServer::~IRCServer() {} - /* - Dummy function to get a usable server socket - for mockup testing. - Implementation explicitely not exhaustive, - will be replaced by an "official" version anyways. + tbd */ -static std::string getDomainStr(int const domain) { - if (domain == AF_INET) - return "IPv4"; - if (domain == AF_INET6) - return "IPv6"; - return ""; -} - -static int sockAddrFamily(int const socketFd) { - struct sockaddr sockAddr = {}; - socklen_t sockLen = sizeof(sockAddr); - if (getsockname(socketFd, &sockAddr, &sockLen) != 0) // NOLINT - throw; - return sockAddr.sa_family; -} - -static void setSocketNonBlock(int const socketFd) { - int const domain = sockAddrFamily(socketFd); - if (fcntl(socketFd, F_SETFL, O_NONBLOCK) != 0) // NOLINT - throw std::runtime_error( - "Error while setting socket to NON-BLOCKING for " + - getDomainStr(domain) + " !"); -} - -static void setSocketAddr(int const socketFd, int const port, // NOLINT - socketAddrX &sockAddrIn46, socklen_t &sockLen) { - int domain = sockAddrFamily(socketFd); - - if (domain == AF_INET) { - struct sockaddr_in &clientSockAddrIn4 = sockAddrIn46.sai4; - clientSockAddrIn4.sin_family = AF_INET; - clientSockAddrIn4.sin_addr.s_addr = INADDR_ANY; - clientSockAddrIn4.sin_port = htons(port); - sockLen = sizeof(clientSockAddrIn4); - } else if (domain == AF_INET6) { - struct sockaddr_in6 &clientSockAddrIn6 = sockAddrIn46.sai6; - clientSockAddrIn6.sin6_family = AF_INET6; - clientSockAddrIn6.sin6_addr = in6addr_any; - clientSockAddrIn6.sin6_port = htons(port); - sockLen = sizeof(clientSockAddrIn6); - } -} - -static void bindServerSocket(int const socketFd, int const domain, // NOLINT - int const port) { - socketAddrX sockAddrIn46 = {}; - socklen_t sockLen = 0; - setSocketAddr(socketFd, port, sockAddrIn46, sockLen); - - if (bind(socketFd, - reinterpret_cast(&sockAddrIn46), // NOLINT - sockLen) < 0) - throw std::runtime_error("Error while binding server socket for " + - getDomainStr(domain) + " !"); -} - -static void storePollFd(int const socketFd, std::vector &pFds, - std::vector &usrInBuff) { - pollfd pfd = {}; - pfd.fd = socketFd; - pfd.events = POLLIN; // Monitor for incoming connections - - pFds.push_back(pfd); - usrInBuff.push_back(std::string()); -} - int IRCServer::createServerSocket_(int const domain) { if (domain != AF_INET && domain != AF_INET6) throw std::invalid_argument( @@ -104,13 +26,13 @@ int IRCServer::createServerSocket_(int const domain) { int socketFd = socket(domain, SOCK_STREAM, 0); if (socketFd < 0) throw std::runtime_error("Error while opening server socket for " + - getDomainStr(domain) + " !"); + srvHelp::getAdressFamilyStr(domain) + " !"); int optval = 1; - if (setsockopt(socketFd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &optval, - sizeof(optval)) != 0) + if (setsockopt(socketFd, SOL_SOCKET, SO_REUSEADDR /* | SO_REUSEPORT */, + &optval, sizeof(optval)) != 0) throw std::runtime_error("Error while setting socket options for " + - getDomainStr(domain) + " !"); + srvHelp::getAdressFamilyStr(domain) + " !"); if (domain == AF_INET6) { int v6only = 1; @@ -118,52 +40,28 @@ int IRCServer::createServerSocket_(int const domain) { sizeof(v6only)); } - setSocketNonBlock(socketFd); + srvHelp::setSocketNonBlock(socketFd); - bindServerSocket(socketFd, domain, port_); + srvHelp::bindServerSocket(socketFd, domain, port_); if (listen(socketFd, MAX_CONNECTIONS) < 0) throw std::runtime_error("Error while listening on socket for " + - getDomainStr(domain) + " !"); + srvHelp::getAdressFamilyStr(domain) + " !"); - storePollFd(socketFd, pollFds_, clientInputBuffers_); + srvHelp::storePollFd(socketFd, pollFds_, clientInputBuffers_); ++numServerSockets_; return socketFd; } -static void printAddrfamilyAddrPort(socketAddrX const &sockAddrIn46) { - char addrBuffer[64]; - socklen_t addrBuffLen = sizeof(addrBuffer) / sizeof(char); - - if (sockAddrIn46.sai4.sin_family == AF_INET) { - std::cout << "\n AF: " << getDomainStr(sockAddrIn46.sai4.sin_family) - << "\n addr:port: " - << inet_ntop(AF_INET, &sockAddrIn46.sai4.sin_addr, addrBuffer, - addrBuffLen) - << ":" << std::dec << ntohs(sockAddrIn46.sai4.sin_port) - << std::endl; - } else if (sockAddrIn46.sai6.sin6_family == AF_INET6) { - std::cout << "\n AF: " << getDomainStr(sockAddrIn46.sai6.sin6_family) - << "\n addr:port: [" - << inet_ntop(AF_INET6, &sockAddrIn46.sai6.sin6_addr, - addrBuffer, addrBuffLen) - << "]:" << std::dec << ntohs(sockAddrIn46.sai4.sin_port) - << std::endl; - } -} - /* - Dummy function to get a usable client socket - on connect for mockup testing. - Implementation explicitely not exhaustive, - will be replaced by an "official" version anyways. + tbd */ void IRCServer::acceptClient_(int const serverFd) { socketAddrX sockAddrIn46 = {}; socklen_t sockLen = 0; - setSocketAddr(serverFd, 0, sockAddrIn46, sockLen); + srvHelp::setSocketAddr(serverFd, 0, sockAddrIn46, sockLen); int const clientFd = accept( serverFd, reinterpret_cast(&sockAddrIn46), // NOLINT @@ -174,32 +72,28 @@ void IRCServer::acceptClient_(int const serverFd) { return; } - setSocketNonBlock(clientFd); + srvHelp::setSocketNonBlock(clientFd); - storePollFd(clientFd, pollFds_, clientInputBuffers_); + srvHelp::storePollFd(clientFd, pollFds_, clientInputBuffers_); std::cout << "Client connected:\n" << " fd: " << clientFd; - printAddrfamilyAddrPort(sockAddrIn46); + srvHelp::printAddrfamilyAddrPort(sockAddrIn46); } /* - Dummy function to disconnet aclient socket - for mockup testing. - Implementation explicitely not exhaustive, - will be replaced by an "official" version anyways. - There is more to be done than just closing and removing - the client socket. + tbd */ void IRCServer::disconnectClient_(size_t const idx) { pollfd &pfd = pollFds_.at(idx); - if (idx == 0) { - if (pollFds_.size() != 1) + if (idx < numServerSockets_) { + if (pollFds_.size() < numServerSockets_) std::cout << "Close clients before closing server!" << std::endl; std::cout << "Closing server" << std::endl; } else - std::cout << "Closing client #" << idx << " (" << pollFds_.size() - 2 + std::cout << "Closing client #" << idx << " (" + << pollFds_.size() - 1 - numServerSockets_ << " clients remaining)" << std::endl; close(pfd.fd); @@ -214,26 +108,36 @@ void IRCServer::disconnectClient_(size_t const idx) { /* Main server loop - * sets up its listening socket + * sets up its listening socket(s) * runs an infinit loop to listen for connections * handles client connect and disconnect scenarios - * on server stop, disconnects all clients and closes - the server socket + * on server stop signal, disconnects all clients and closes + the server socket(s) */ void IRCServer::run() { createServerSocket_(AF_INET); createServerSocket_(AF_INET6); - //createServerSocket_(AF_INET); while (!SignalHandler::stopLoopSignalReceived()) { usleep(100); int pollResult = poll(pollFds_.data(), pollFds_.size(), 0); if (pollResult == -1) { - throw; - // nice error message and exit the loop - // or throw, but then have ressource cleanup after catch - break; + static size_t loopCounter = 0; + static size_t retries = 42; + if (loopCounter % 1000 == 0) { + retries = 42; + // EINTR — The call was interrupted by a signal. This is the most common retriable error. Retrying is standard practice. + // EAGAIN — Temporary resource unavailability (rare with poll(), but possible in some edge cases). + if (errno == EINTR) + continue; + if (errno == EAGAIN && retries > 1) { + retries--; + usleep(10000); + continue; + } + throw std::runtime_error("poll failed too many times."); + } } for (size_t idx = 0; idx < numServerSockets_; ++idx) { From 64278b51fb4f110139f17568db691b84629098df Mon Sep 17 00:00:00 2001 From: Rapha Date: Mon, 24 Aug 2026 13:01:02 +0200 Subject: [PATCH 111/349] rfa: * rename IRCServer to just Server * add include guards to header files --- includes/IRC.hpp | 2 +- includes/server/{IRCServer.hpp => Server.hpp} | 15 +- .../{IRCServerHelper.hpp => ServerHelper.hpp} | 6 +- includes/server/SignalHandler.hpp | 5 + srcs/main.cpp | 2 +- srcs/server/IRCServer.cpp | 228 ----------------- srcs/server/Server.cpp | 229 +++++++++++++++++- 7 files changed, 247 insertions(+), 240 deletions(-) rename includes/server/{IRCServer.hpp => Server.hpp} (75%) rename includes/server/{IRCServerHelper.hpp => ServerHelper.hpp} (97%) delete mode 100644 srcs/server/IRCServer.cpp diff --git a/includes/IRC.hpp b/includes/IRC.hpp index 859afa8..299b816 100644 --- a/includes/IRC.hpp +++ b/includes/IRC.hpp @@ -1,4 +1,4 @@ -#include "server/IRCServer.hpp" +#include "server/Server.hpp" #include "server/SignalHandler.hpp" #include diff --git a/includes/server/IRCServer.hpp b/includes/server/Server.hpp similarity index 75% rename from includes/server/IRCServer.hpp rename to includes/server/Server.hpp index 17b854b..147d7e4 100644 --- a/includes/server/IRCServer.hpp +++ b/includes/server/Server.hpp @@ -1,3 +1,6 @@ +#ifndef SERVER_HPP +#define SERVER_HPP + #include #include #include @@ -7,7 +10,7 @@ #define MAXMSGLEN 512 #define MAX_CONNECTIONS 512 -class IRCServer { +class Server { private: int port_; std::string password_; @@ -16,16 +19,16 @@ class IRCServer { std::vector pollFds_; std::vector clientInputBuffers_; - // IRCServer(IRCServer const &other); - // IRCServer &operator=(IRCServer const &other); + // Server(Server const &other); + // Server &operator=(Server const &other); int createServerSocket_(int const domain); void acceptClient_(int const serverFd); void disconnectClient_(size_t const idx); public: - IRCServer(int const port, std::string const &password); - // ~IRCServer(); + Server(int const port, std::string const &password); + // ~Server(); void run(); }; @@ -34,3 +37,5 @@ typedef union uSocketAddrX { struct sockaddr_in sai4; struct sockaddr_in6 sai6; } socketAddrX; + +#endif // SERVER_HPP \ No newline at end of file diff --git a/includes/server/IRCServerHelper.hpp b/includes/server/ServerHelper.hpp similarity index 97% rename from includes/server/IRCServerHelper.hpp rename to includes/server/ServerHelper.hpp index 01443a0..febb944 100644 --- a/includes/server/IRCServerHelper.hpp +++ b/includes/server/ServerHelper.hpp @@ -1,5 +1,5 @@ -#ifndef IRCSERVERHELPER_HPP -#define IRCSERVERHELPER_HPP +#ifndef SERVERHELPER_HPP +#define SERVERHELPER_HPP #include #include @@ -107,4 +107,4 @@ inline void printAddrfamilyAddrPort(socketAddrX const &sockAddrIn46) { } // namespace srvHelp -#endif // IRCSERVERHELPER_HPP \ No newline at end of file +#endif // SERVERHELPER_HPP \ No newline at end of file diff --git a/includes/server/SignalHandler.hpp b/includes/server/SignalHandler.hpp index eb33a10..53d9949 100644 --- a/includes/server/SignalHandler.hpp +++ b/includes/server/SignalHandler.hpp @@ -1,3 +1,6 @@ +#ifndef SIGNALHANDLER_HPP +#define SIGNALHANDLER_HPP + #include #include @@ -16,3 +19,5 @@ class SignalHandler { ~SignalHandler(); static bool stopLoopSignalReceived(); }; + +#endif // SIGNALHANDLER_HPP \ No newline at end of file diff --git a/srcs/main.cpp b/srcs/main.cpp index 4fce3a3..925d683 100644 --- a/srcs/main.cpp +++ b/srcs/main.cpp @@ -53,7 +53,7 @@ int main(int argc, char **argv) { checkExtractCmdLnArgs(argc, argv, port, password); SignalHandler sigHandler; - IRCServer srv(port, password); + Server srv(port, password); srv.run(); } catch (std::exception const &e) { std::cerr << e.what() << std::endl; diff --git a/srcs/server/IRCServer.cpp b/srcs/server/IRCServer.cpp deleted file mode 100644 index 5ab47d9..0000000 --- a/srcs/server/IRCServer.cpp +++ /dev/null @@ -1,228 +0,0 @@ -#include "server/IRCServer.hpp" - -#include "server/IRCServerHelper.hpp" -#include "server/SignalHandler.hpp" - -#include -#include -#include -#include - -/* - IRCServer Constructor - * set port and password from command line args -*/ -IRCServer::IRCServer(int const port, std::string const &password) - : port_(port), password_(password), numServerSockets_(0) {} - -/* - tbd -*/ -int IRCServer::createServerSocket_(int const domain) { - if (domain != AF_INET && domain != AF_INET6) - throw std::invalid_argument( - "Internal Error while opening server socket."); - - int socketFd = socket(domain, SOCK_STREAM, 0); - if (socketFd < 0) - throw std::runtime_error("Error while opening server socket for " + - srvHelp::getAdressFamilyStr(domain) + " !"); - - int optval = 1; - if (setsockopt(socketFd, SOL_SOCKET, SO_REUSEADDR /* | SO_REUSEPORT */, - &optval, sizeof(optval)) != 0) - throw std::runtime_error("Error while setting socket options for " + - srvHelp::getAdressFamilyStr(domain) + " !"); - - if (domain == AF_INET6) { - int v6only = 1; - setsockopt(socketFd, IPPROTO_IPV6, IPV6_V6ONLY, &v6only, - sizeof(v6only)); - } - - srvHelp::setSocketNonBlock(socketFd); - - srvHelp::bindServerSocket(socketFd, domain, port_); - - if (listen(socketFd, MAX_CONNECTIONS) < 0) - throw std::runtime_error("Error while listening on socket for " + - srvHelp::getAdressFamilyStr(domain) + " !"); - - srvHelp::storePollFd(socketFd, pollFds_, clientInputBuffers_); - ++numServerSockets_; - - return socketFd; -} - -/* - tbd -*/ -void IRCServer::acceptClient_(int const serverFd) { - socketAddrX sockAddrIn46 = {}; - socklen_t sockLen = 0; - - srvHelp::setSocketAddr(serverFd, 0, sockAddrIn46, sockLen); - - int const clientFd = accept( - serverFd, reinterpret_cast(&sockAddrIn46), // NOLINT - &sockLen); - if (clientFd == -1) { - // throw -> no everything else can still run w/o problems - // silent return -> cannot sent anything to client without working socket anyways... - return; - } - - srvHelp::setSocketNonBlock(clientFd); - - srvHelp::storePollFd(clientFd, pollFds_, clientInputBuffers_); - - std::cout << "Client connected:\n" - << " fd: " << clientFd; - srvHelp::printAddrfamilyAddrPort(sockAddrIn46); -} - -/* - tbd -*/ -void IRCServer::disconnectClient_(size_t const idx) { - pollfd &pfd = pollFds_.at(idx); - - if (idx < numServerSockets_) { - if (pollFds_.size() < numServerSockets_) - std::cout << "Close clients before closing server!" << std::endl; - std::cout << "Closing server" << std::endl; - } else - std::cout << "Closing client #" << idx << " (" - << pollFds_.size() - 1 - numServerSockets_ - << " clients remaining)" << std::endl; - close(pfd.fd); - - std::vector::difference_type dist = - static_cast::difference_type>(idx); - pollFds_.erase(pollFds_.begin() + dist); - clientInputBuffers_.erase( - clientInputBuffers_.begin() + - static_cast::difference_type>(dist)); -} - -/* - Main server loop - - * sets up its listening socket(s) - * runs an infinit loop to listen for connections - * handles client connect and disconnect scenarios - * on server stop signal, disconnects all clients and closes - the server socket(s) -*/ -void IRCServer::run() { - createServerSocket_(AF_INET); - createServerSocket_(AF_INET6); - - while (!SignalHandler::stopLoopSignalReceived()) { - usleep(100); - - int pollResult = poll(pollFds_.data(), pollFds_.size(), 0); - if (pollResult == -1) { - static size_t loopCounter = 0; - static size_t retries = 42; - if (loopCounter % 1000 == 0) { - retries = 42; - // EINTR — The call was interrupted by a signal. This is the most common retriable error. Retrying is standard practice. - // EAGAIN — Temporary resource unavailability (rare with poll(), but possible in some edge cases). - if (errno == EINTR) - continue; - if (errno == EAGAIN && retries > 1) { - retries--; - usleep(10000); - continue; - } - throw std::runtime_error("poll failed too many times."); - } - } - - for (size_t idx = 0; idx < numServerSockets_; ++idx) { - struct pollfd const &serverPollFds = pollFds_[idx]; - if (serverPollFds.revents & POLLERR) { - std::cerr << "Server socket error\n"; - break; - } - if (serverPollFds.revents & POLLIN) { - acceptClient_(serverPollFds.fd); - } - } - for (size_t idx = numServerSockets_; idx < pollFds_.size(); ++idx) { - pollfd &pfd = pollFds_.at(idx); - if (pfd.revents & POLLERR) { - std::cerr << "Client socket error on fd: " << pfd.fd - << std::endl; - disconnectClient_(idx); - } else if (pfd.revents & POLLIN) { - char buffer[BUFFLEN]; - ssize_t bytesRead = recv(pfd.fd, &buffer[0], BUFFLEN, 0); - - if (bytesRead > 0) { - std::string &userInput = clientInputBuffers_.at(idx); - std::string bufferStr(&buffer[0], bytesRead); - // bool userInputValid = false; - - // // DEBUG for cont'd messages - // if (!userInput.empty() && *userInput.rbegin() != '\r') { - // *userInput.rbegin() = '&'; - // } - userInput += bufferStr; - size_t lfcrPos = userInput.find("\r\n"); - if (userInput.length() >= MAXMSGLEN) { - if (lfcrPos > MAXMSGLEN - 2) { - std::cerr << "Client data error: message too " - "long.\nData sent will be discarded." - << std::endl; - while (bytesRead > 0) - bytesRead = - recv(pfd.fd, &buffer[0], BUFFLEN, 0); - userInput.clear(); - continue; - } - } else if (lfcrPos == std::string::npos) { - continue; - } else if (lfcrPos < userInput.length() - 2) { - std::cerr << "Client data error: message " - "malformed, data after CR-LF.\nExtraneous " - "data will be discarded." - << std::endl; - userInput.resize(lfcrPos + 2); - } - // if (lfcrPos == userInput.length() - 2) - // userInputValid = true; - // else { - // std::cerr << "What's going on here?" << std::endl; - // // userInput += bufferStr.substr(0, lfcrPos + 2); - // } - // if (userInputValid) - { - std::cout << "UserInput read from client " << idx - << ": \n" - << std::string(userInput) /* << std::endl */; - userInput.clear(); - } - // } - } - if (bytesRead <= 0) { - // Comment will be deleted later, kept for reference for now - // this means, the client closed the connection - // POLLHUP isn't useful, because - // When you kill netcat with Ctrl+C: - // Netcat's OS kernel sent a FIN packet to your server - // Your server's poll() detected this with POLLIN - // Your read() returned 0 (zero bytes) = "I got the FIN, connection closed" - // FIN is the TCP mechanism for orderly shutdown. - // That's why POLLIN + read() return 0—the FIN, - // and the zero read is how the kernel tells you "the peer closed." - disconnectClient_(idx); - } - } - } - } - while (!pollFds_.empty()) { - disconnectClient_(pollFds_.size() - 1); - } -} diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index c0b38e1..b52146f 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -1,3 +1,228 @@ -#include "Server.hpp" +#include "server/Server.hpp" -// TODO rhaas +#include "server/ServerHelper.hpp" +#include "server/SignalHandler.hpp" + +#include +#include +#include +#include + +/* + Server Constructor + * set port and password from command line args +*/ +Server::Server(int const port, std::string const &password) + : port_(port), password_(password), numServerSockets_(0) {} + +/* + tbd +*/ +int Server::createServerSocket_(int const domain) { + if (domain != AF_INET && domain != AF_INET6) + throw std::invalid_argument( + "Internal Error while opening server socket."); + + int socketFd = socket(domain, SOCK_STREAM, 0); + if (socketFd < 0) + throw std::runtime_error("Error while opening server socket for " + + srvHelp::getAdressFamilyStr(domain) + " !"); + + int optval = 1; + if (setsockopt(socketFd, SOL_SOCKET, SO_REUSEADDR /* | SO_REUSEPORT */, + &optval, sizeof(optval)) != 0) + throw std::runtime_error("Error while setting socket options for " + + srvHelp::getAdressFamilyStr(domain) + " !"); + + if (domain == AF_INET6) { + int v6only = 1; + setsockopt(socketFd, IPPROTO_IPV6, IPV6_V6ONLY, &v6only, + sizeof(v6only)); + } + + srvHelp::setSocketNonBlock(socketFd); + + srvHelp::bindServerSocket(socketFd, domain, port_); + + if (listen(socketFd, MAX_CONNECTIONS) < 0) + throw std::runtime_error("Error while listening on socket for " + + srvHelp::getAdressFamilyStr(domain) + " !"); + + srvHelp::storePollFd(socketFd, pollFds_, clientInputBuffers_); + ++numServerSockets_; + + return socketFd; +} + +/* + tbd +*/ +void Server::acceptClient_(int const serverFd) { + socketAddrX sockAddrIn46 = {}; + socklen_t sockLen = 0; + + srvHelp::setSocketAddr(serverFd, 0, sockAddrIn46, sockLen); + + int const clientFd = accept( + serverFd, reinterpret_cast(&sockAddrIn46), // NOLINT + &sockLen); + if (clientFd == -1) { + // throw -> no everything else can still run w/o problems + // silent return -> cannot sent anything to client without working socket anyways... + return; + } + + srvHelp::setSocketNonBlock(clientFd); + + srvHelp::storePollFd(clientFd, pollFds_, clientInputBuffers_); + + std::cout << "Client connected:\n" + << " fd: " << clientFd; + srvHelp::printAddrfamilyAddrPort(sockAddrIn46); +} + +/* + tbd +*/ +void Server::disconnectClient_(size_t const idx) { + pollfd &pfd = pollFds_.at(idx); + + if (idx < numServerSockets_) { + if (pollFds_.size() < numServerSockets_) + std::cout << "Close clients before closing server!" << std::endl; + std::cout << "Closing server" << std::endl; + } else + std::cout << "Closing client #" << idx << " (" + << pollFds_.size() - 1 - numServerSockets_ + << " clients remaining)" << std::endl; + close(pfd.fd); + + std::vector::difference_type dist = + static_cast::difference_type>(idx); + pollFds_.erase(pollFds_.begin() + dist); + clientInputBuffers_.erase( + clientInputBuffers_.begin() + + static_cast::difference_type>(dist)); +} + +/* + Main server loop + + * sets up its listening socket(s) + * runs an infinit loop to listen for connections + * handles client connect and disconnect scenarios + * on server stop signal, disconnects all clients and closes + the server socket(s) +*/ +void Server::run() { + createServerSocket_(AF_INET); + createServerSocket_(AF_INET6); + + while (!SignalHandler::stopLoopSignalReceived()) { + usleep(100); + + int pollResult = poll(pollFds_.data(), pollFds_.size(), 0); + if (pollResult == -1) { + static size_t loopCounter = 0; + static size_t retries = 42; + if (loopCounter % 1000 == 0) { + retries = 42; + // EINTR — The call was interrupted by a signal. This is the most common retriable error. Retrying is standard practice. + // EAGAIN — Temporary resource unavailability (rare with poll(), but possible in some edge cases). + if (errno == EINTR) + continue; + if (errno == EAGAIN && retries > 1) { + retries--; + usleep(10000); + continue; + } + throw std::runtime_error("poll failed too many times."); + } + } + + for (size_t idx = 0; idx < numServerSockets_; ++idx) { + struct pollfd const &serverPollFds = pollFds_[idx]; + if (serverPollFds.revents & POLLERR) { + std::cerr << "Server socket error\n"; + break; + } + if (serverPollFds.revents & POLLIN) { + acceptClient_(serverPollFds.fd); + } + } + for (size_t idx = numServerSockets_; idx < pollFds_.size(); ++idx) { + pollfd &pfd = pollFds_.at(idx); + if (pfd.revents & POLLERR) { + std::cerr << "Client socket error on fd: " << pfd.fd + << std::endl; + disconnectClient_(idx); + } else if (pfd.revents & POLLIN) { + char buffer[BUFFLEN]; + ssize_t bytesRead = recv(pfd.fd, &buffer[0], BUFFLEN, 0); + + if (bytesRead > 0) { + std::string &userInput = clientInputBuffers_.at(idx); + std::string bufferStr(&buffer[0], bytesRead); + // bool userInputValid = false; + + // // DEBUG for cont'd messages + // if (!userInput.empty() && *userInput.rbegin() != '\r') { + // *userInput.rbegin() = '&'; + // } + userInput += bufferStr; + size_t lfcrPos = userInput.find("\r\n"); + if (userInput.length() >= MAXMSGLEN) { + if (lfcrPos > MAXMSGLEN - 2) { + std::cerr << "Client data error: message too " + "long.\nData sent will be discarded." + << std::endl; + while (bytesRead > 0) + bytesRead = + recv(pfd.fd, &buffer[0], BUFFLEN, 0); + userInput.clear(); + continue; + } + } else if (lfcrPos == std::string::npos) { + continue; + } else if (lfcrPos < userInput.length() - 2) { + std::cerr << "Client data error: message " + "malformed, data after CR-LF.\nExtraneous " + "data will be discarded." + << std::endl; + userInput.resize(lfcrPos + 2); + } + // if (lfcrPos == userInput.length() - 2) + // userInputValid = true; + // else { + // std::cerr << "What's going on here?" << std::endl; + // // userInput += bufferStr.substr(0, lfcrPos + 2); + // } + // if (userInputValid) + { + std::cout << "UserInput read from client " << idx + << ": \n" + << std::string(userInput) /* << std::endl */; + userInput.clear(); + } + // } + } + if (bytesRead <= 0) { + // Comment will be deleted later, kept for reference for now + // this means, the client closed the connection + // POLLHUP isn't useful, because + // When you kill netcat with Ctrl+C: + // Netcat's OS kernel sent a FIN packet to your server + // Your server's poll() detected this with POLLIN + // Your read() returned 0 (zero bytes) = "I got the FIN, connection closed" + // FIN is the TCP mechanism for orderly shutdown. + // That's why POLLIN + read() return 0—the FIN, + // and the zero read is how the kernel tells you "the peer closed." + disconnectClient_(idx); + } + } + } + } + while (!pollFds_.empty()) { + disconnectClient_(pollFds_.size() - 1); + } +} From f88e3154fe7f746485b43cea1f8130ee54cc781f Mon Sep 17 00:00:00 2001 From: Rapha Date: Mon, 24 Aug 2026 15:36:37 +0200 Subject: [PATCH 112/349] rfa: add more subfunctions feat: update client input handling (msg length, buffer clearance) feat: beautify client connect log msg --- includes/server/Server.hpp | 7 +- includes/server/ServerHelper.hpp | 66 ++++++++---- srcs/server/Server.cpp | 172 +++++++++++++------------------ 3 files changed, 120 insertions(+), 125 deletions(-) diff --git a/includes/server/Server.hpp b/includes/server/Server.hpp index 147d7e4..7d4b41d 100644 --- a/includes/server/Server.hpp +++ b/includes/server/Server.hpp @@ -23,7 +23,9 @@ class Server { // Server &operator=(Server const &other); int createServerSocket_(int const domain); + void processServerSocketData_(); void acceptClient_(int const serverFd); + void processClientSocketData_(); void disconnectClient_(size_t const idx); public: @@ -33,9 +35,4 @@ class Server { void run(); }; -typedef union uSocketAddrX { - struct sockaddr_in sai4; - struct sockaddr_in6 sai6; -} socketAddrX; - #endif // SERVER_HPP \ No newline at end of file diff --git a/includes/server/ServerHelper.hpp b/includes/server/ServerHelper.hpp index febb944..87dd226 100644 --- a/includes/server/ServerHelper.hpp +++ b/includes/server/ServerHelper.hpp @@ -4,6 +4,15 @@ #include #include #include +#include +#include +#include +#include + +typedef union uSocketAddrX { + struct sockaddr_in sai4; + struct sockaddr_in6 sai6; +} socketAddrX; namespace srvHelp { @@ -32,7 +41,8 @@ inline void setSocketNonBlock(int const socketFd) { } inline void setSocketAddr(int const socketFd, int const port, // NOLINT - socketAddrX &sockAddrIn46, socklen_t &sockLen) { + socketAddrX &sockAddrIn46, + socklen_t &sockLen) { // NOLINT int domain = sockAddrFamily(socketFd); if (domain == AF_INET) { @@ -63,8 +73,9 @@ inline void bindServerSocket(int const socketFd, int const domain, // NOLINT getAdressFamilyStr(domain) + " !"); } -inline void storePollFd(int const socketFd, std::vector &pFds, - std::vector &usrInBuff) { +inline void storePollFd(int const socketFd, + std::vector &pFds, // NOLINT + std::vector &usrInBuff) { // NOLINT pollfd pfd = {}; pfd.fd = socketFd; pfd.events = POLLIN; // Monitor for incoming connections @@ -82,29 +93,44 @@ inline bool isSocketListening(int sockfd) { return (optVal > 0); } -inline void printAddrfamilyAddrPort(socketAddrX const &sockAddrIn46) { - char addrBuffer[64]; - socklen_t addrBuffLen = sizeof(addrBuffer) / sizeof(char); +inline std::string printAddrfamilyAddrPort(socketAddrX const &sockAddrIn46) { + char addrBuffer[128]; + socklen_t addrBuffLen = sizeof(addrBuffer) / sizeof(char); + struct sockaddr const &socketAddr = + reinterpret_cast(sockAddrIn46.sai4); // NOLINT + char const *const addrPtr = inet_ntop(socketAddr.sa_family, &sockAddrIn46.sai4.sin_addr, &addrBuffer[0], + addrBuffLen); + std::string addrStr(addrPtr?addrPtr:""); + std::ostringstream oss; if (sockAddrIn46.sai4.sin_family == AF_INET) { - std::cout << "\n AF: " - << getAdressFamilyStr(sockAddrIn46.sai4.sin_family) - << "\n addr:port: " - << inet_ntop(AF_INET, &sockAddrIn46.sai4.sin_addr, addrBuffer, - addrBuffLen) - << ":" << std::dec << ntohs(sockAddrIn46.sai4.sin_port) - << std::endl; + oss << " AF: " << getAdressFamilyStr(sockAddrIn46.sai4.sin_family) + << "\n addr:port: " << inet_ntop(socketAddr.sa_family, &sockAddrIn46.sai4.sin_addr, &addrBuffer[0], + addrBuffLen) << ":" << std::dec + << ntohs(sockAddrIn46.sai4.sin_port); } else if (sockAddrIn46.sai6.sin6_family == AF_INET6) { - std::cout << "\n AF: " - << getAdressFamilyStr(sockAddrIn46.sai6.sin6_family) - << "\n addr:port: [" - << inet_ntop(AF_INET6, &sockAddrIn46.sai6.sin6_addr, - addrBuffer, addrBuffLen) - << "]:" << std::dec << ntohs(sockAddrIn46.sai4.sin_port) - << std::endl; + oss << " AF: " << getAdressFamilyStr(sockAddrIn46.sai6.sin6_family) + << "\n addr:port: [" << inet_ntop(socketAddr.sa_family, &sockAddrIn46.sai6.sin6_addr, &addrBuffer[0], + addrBuffLen) << "]:" << std::dec + << ntohs(sockAddrIn46.sai4.sin_port); } + return oss.str(); } +inline bool checkPollFailOK() { + static size_t loopCounter = 0; + static size_t retries = 42; + if (loopCounter % 1000 == 0) + retries = 42; + if (errno == EINTR) + return true; + if (errno == EAGAIN && retries > 1) { + retries--; + usleep(10000); + return true; + } + return false; +} } // namespace srvHelp #endif // SERVERHELPER_HPP \ No newline at end of file diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index b52146f..c1bad9b 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -54,6 +54,66 @@ int Server::createServerSocket_(int const domain) { return socketFd; } +void Server::processServerSocketData_() { + for (size_t idx = 0; idx < numServerSockets_; ++idx) { + struct pollfd const &serverPollFds = pollFds_[idx]; + if ((serverPollFds.revents & POLLERR) && !srvHelp::checkPollFailOK()) + throw std::runtime_error("Server socket error"); + if (serverPollFds.revents & POLLIN) + acceptClient_(serverPollFds.fd); + } +} + +void Server::processClientSocketData_() { + for (size_t idx = numServerSockets_; idx < pollFds_.size(); ++idx) { + pollfd &pfd = pollFds_.at(idx); + if ((pfd.revents & POLLERR) && !srvHelp::checkPollFailOK()) { + std::cerr << "Client socket error on fd: " << pfd.fd + << "Disconnect client." << std::endl; + disconnectClient_(idx); + } else if (pfd.revents & POLLIN) { + char buffer[BUFFLEN]; + ssize_t bytesRead = recv(pfd.fd, &buffer[0], BUFFLEN, 0); + + if (bytesRead > 0) { + std::string &userInput = clientInputBuffers_.at(idx); + std::string bufferStr(&buffer[0], bytesRead); + + userInput += bufferStr; + size_t lfcrPos = userInput.find("\r\n"); + if (lfcrPos == std::string::npos) { + continue; + } + if (lfcrPos > MAXMSGLEN - 2) { + std::cerr << "Client data error: message too " + "long.\nData sent will be discarded." + << std::endl; + userInput.erase(0, lfcrPos + 2); + continue; + } + // DEBUG + { + std::cout << "UserInput read from client " << idx << ": \n" + << std::string(userInput) /* << std::endl */; + userInput.clear(); + } + } else if (bytesRead <= 0) { + // Comment will be deleted later, kept for reference for now + // this means, the client closed the connection + // POLLHUP isn't useful, because: + // When you kill netcat with Ctrl+C: + // Netcat's OS kernel sent a FIN packet to your server + // Your server's poll() detected this with POLLIN + // Your read() returned 0 (zero bytes) == "I got the FIN, connection closed" + // FIN is the TCP mechanism for orderly shutdown. + // That's why POLLIN + read() return 0—the FIN, + // and the zero read is how the kernel tells you "the peer closed." + disconnectClient_(idx); + } + } + } +} + /* tbd */ @@ -77,8 +137,9 @@ void Server::acceptClient_(int const serverFd) { srvHelp::storePollFd(clientFd, pollFds_, clientInputBuffers_); std::cout << "Client connected:\n" - << " fd: " << clientFd; - srvHelp::printAddrfamilyAddrPort(sockAddrIn46); + << " fd: " << clientFd << "\n" + << srvHelp::printAddrfamilyAddrPort(sockAddrIn46) + << std::endl; } /* @@ -113,6 +174,11 @@ void Server::disconnectClient_(size_t const idx) { * handles client connect and disconnect scenarios * on server stop signal, disconnects all clients and closes the server socket(s) + * if poll returns an error -> exception is thrown, except for two scenarios: + EINTR — The call was interrupted by a signal. This is the most common + retriable error. Retrying is standard practice. + EAGAIN — Temporary resource unavailability (rare with poll(), + but possible in some edge cases). */ void Server::run() { createServerSocket_(AF_INET); @@ -122,105 +188,11 @@ void Server::run() { usleep(100); int pollResult = poll(pollFds_.data(), pollFds_.size(), 0); - if (pollResult == -1) { - static size_t loopCounter = 0; - static size_t retries = 42; - if (loopCounter % 1000 == 0) { - retries = 42; - // EINTR — The call was interrupted by a signal. This is the most common retriable error. Retrying is standard practice. - // EAGAIN — Temporary resource unavailability (rare with poll(), but possible in some edge cases). - if (errno == EINTR) - continue; - if (errno == EAGAIN && retries > 1) { - retries--; - usleep(10000); - continue; - } - throw std::runtime_error("poll failed too many times."); - } - } + if (pollResult == -1 && !srvHelp::checkPollFailOK()) + throw std::runtime_error("poll failed too many times."); - for (size_t idx = 0; idx < numServerSockets_; ++idx) { - struct pollfd const &serverPollFds = pollFds_[idx]; - if (serverPollFds.revents & POLLERR) { - std::cerr << "Server socket error\n"; - break; - } - if (serverPollFds.revents & POLLIN) { - acceptClient_(serverPollFds.fd); - } - } - for (size_t idx = numServerSockets_; idx < pollFds_.size(); ++idx) { - pollfd &pfd = pollFds_.at(idx); - if (pfd.revents & POLLERR) { - std::cerr << "Client socket error on fd: " << pfd.fd - << std::endl; - disconnectClient_(idx); - } else if (pfd.revents & POLLIN) { - char buffer[BUFFLEN]; - ssize_t bytesRead = recv(pfd.fd, &buffer[0], BUFFLEN, 0); - - if (bytesRead > 0) { - std::string &userInput = clientInputBuffers_.at(idx); - std::string bufferStr(&buffer[0], bytesRead); - // bool userInputValid = false; - - // // DEBUG for cont'd messages - // if (!userInput.empty() && *userInput.rbegin() != '\r') { - // *userInput.rbegin() = '&'; - // } - userInput += bufferStr; - size_t lfcrPos = userInput.find("\r\n"); - if (userInput.length() >= MAXMSGLEN) { - if (lfcrPos > MAXMSGLEN - 2) { - std::cerr << "Client data error: message too " - "long.\nData sent will be discarded." - << std::endl; - while (bytesRead > 0) - bytesRead = - recv(pfd.fd, &buffer[0], BUFFLEN, 0); - userInput.clear(); - continue; - } - } else if (lfcrPos == std::string::npos) { - continue; - } else if (lfcrPos < userInput.length() - 2) { - std::cerr << "Client data error: message " - "malformed, data after CR-LF.\nExtraneous " - "data will be discarded." - << std::endl; - userInput.resize(lfcrPos + 2); - } - // if (lfcrPos == userInput.length() - 2) - // userInputValid = true; - // else { - // std::cerr << "What's going on here?" << std::endl; - // // userInput += bufferStr.substr(0, lfcrPos + 2); - // } - // if (userInputValid) - { - std::cout << "UserInput read from client " << idx - << ": \n" - << std::string(userInput) /* << std::endl */; - userInput.clear(); - } - // } - } - if (bytesRead <= 0) { - // Comment will be deleted later, kept for reference for now - // this means, the client closed the connection - // POLLHUP isn't useful, because - // When you kill netcat with Ctrl+C: - // Netcat's OS kernel sent a FIN packet to your server - // Your server's poll() detected this with POLLIN - // Your read() returned 0 (zero bytes) = "I got the FIN, connection closed" - // FIN is the TCP mechanism for orderly shutdown. - // That's why POLLIN + read() return 0—the FIN, - // and the zero read is how the kernel tells you "the peer closed." - disconnectClient_(idx); - } - } - } + processServerSocketData_(); + processClientSocketData_(); } while (!pollFds_.empty()) { disconnectClient_(pollFds_.size() - 1); From b2769e77e5ca60f270d893b9673c3b2ab2618268 Mon Sep 17 00:00:00 2001 From: Rapha Date: Mon, 24 Aug 2026 15:50:46 +0200 Subject: [PATCH 113/349] rfa: cleanup client debug log msg (dysfunctional) --- includes/server/ServerHelper.hpp | 37 +++++++++++++++++--------------- srcs/server/Server.cpp | 5 ++--- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/includes/server/ServerHelper.hpp b/includes/server/ServerHelper.hpp index 87dd226..3f959dd 100644 --- a/includes/server/ServerHelper.hpp +++ b/includes/server/ServerHelper.hpp @@ -94,26 +94,29 @@ inline bool isSocketListening(int sockfd) { } inline std::string printAddrfamilyAddrPort(socketAddrX const &sockAddrIn46) { - char addrBuffer[128]; - socklen_t addrBuffLen = sizeof(addrBuffer) / sizeof(char); - struct sockaddr const &socketAddr = - reinterpret_cast(sockAddrIn46.sai4); // NOLINT - char const *const addrPtr = inet_ntop(socketAddr.sa_family, &sockAddrIn46.sai4.sin_addr, &addrBuffer[0], - addrBuffLen); - std::string addrStr(addrPtr?addrPtr:""); + char addrBuffer[128]; + socklen_t const addrBuffLen = sizeof(addrBuffer) / sizeof(char); + + sa_family_t const saFamily = + reinterpret_cast(sockAddrIn46.sai4) // NOLINT + .sa_family; + void const *addrPtr = 0; + uint16_t portNumber = 0; - std::ostringstream oss; if (sockAddrIn46.sai4.sin_family == AF_INET) { - oss << " AF: " << getAdressFamilyStr(sockAddrIn46.sai4.sin_family) - << "\n addr:port: " << inet_ntop(socketAddr.sa_family, &sockAddrIn46.sai4.sin_addr, &addrBuffer[0], - addrBuffLen) << ":" << std::dec - << ntohs(sockAddrIn46.sai4.sin_port); - } else if (sockAddrIn46.sai6.sin6_family == AF_INET6) { - oss << " AF: " << getAdressFamilyStr(sockAddrIn46.sai6.sin6_family) - << "\n addr:port: [" << inet_ntop(socketAddr.sa_family, &sockAddrIn46.sai6.sin6_addr, &addrBuffer[0], - addrBuffLen) << "]:" << std::dec - << ntohs(sockAddrIn46.sai4.sin_port); + addrPtr = &sockAddrIn46.sai4.sin_addr; + portNumber = ntohs(sockAddrIn46.sai4.sin_port); + } else if (sockAddrIn46.sai4.sin_family == AF_INET) { + addrPtr = &sockAddrIn46.sai4.sin_addr; + portNumber = ntohs(sockAddrIn46.sai6.sin6_port); } + char const *addrStrPtr = + inet_ntop(saFamily, addrPtr, &addrBuffer[0], addrBuffLen); + addrStrPtr = 0; + std::string addrStr(addrStrPtr?addrStrPtr:"nil"); + std::ostringstream oss; + oss << " AF: " << getAdressFamilyStr(saFamily) + << "\n addr:port: " << addrStrPtr << ":" << std::dec << portNumber; return oss.str(); } diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index c1bad9b..3e84eb4 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -138,8 +138,7 @@ void Server::acceptClient_(int const serverFd) { std::cout << "Client connected:\n" << " fd: " << clientFd << "\n" - << srvHelp::printAddrfamilyAddrPort(sockAddrIn46) - << std::endl; + << srvHelp::printAddrfamilyAddrPort(sockAddrIn46) << std::endl; } /* @@ -192,7 +191,7 @@ void Server::run() { throw std::runtime_error("poll failed too many times."); processServerSocketData_(); - processClientSocketData_(); + processClientSocketData_(); } while (!pollFds_.empty()) { disconnectClient_(pollFds_.size() - 1); From 1fc2aca519226906b8031bb025951ba9d85da1cb Mon Sep 17 00:00:00 2001 From: Rapha Date: Mon, 24 Aug 2026 17:08:33 +0200 Subject: [PATCH 114/349] feat: cleanaup and beautify client info print fnctn --- includes/server/ServerHelper.hpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/includes/server/ServerHelper.hpp b/includes/server/ServerHelper.hpp index 3f959dd..683f09b 100644 --- a/includes/server/ServerHelper.hpp +++ b/includes/server/ServerHelper.hpp @@ -103,20 +103,22 @@ inline std::string printAddrfamilyAddrPort(socketAddrX const &sockAddrIn46) { void const *addrPtr = 0; uint16_t portNumber = 0; - if (sockAddrIn46.sai4.sin_family == AF_INET) { + if (saFamily == AF_INET) { addrPtr = &sockAddrIn46.sai4.sin_addr; portNumber = ntohs(sockAddrIn46.sai4.sin_port); - } else if (sockAddrIn46.sai4.sin_family == AF_INET) { - addrPtr = &sockAddrIn46.sai4.sin_addr; + } else if (saFamily == AF_INET6) { + addrPtr = &sockAddrIn46.sai6.sin6_addr; portNumber = ntohs(sockAddrIn46.sai6.sin6_port); } char const *addrStrPtr = inet_ntop(saFamily, addrPtr, &addrBuffer[0], addrBuffLen); - addrStrPtr = 0; - std::string addrStr(addrStrPtr?addrStrPtr:"nil"); + std::string addrStr(addrStrPtr ? addrStrPtr : ""); + if (saFamily == AF_INET6) + addrStr = "[" + addrStr + "]"; + std::ostringstream oss; oss << " AF: " << getAdressFamilyStr(saFamily) - << "\n addr:port: " << addrStrPtr << ":" << std::dec << portNumber; + << "\n addr:port: " << addrStr << ":" << std::dec << portNumber; return oss.str(); } From 4c46f35d5b34a25e95b37086d50b60a447a5c7f6 Mon Sep 17 00:00:00 2001 From: Rapha Date: Mon, 24 Aug 2026 17:09:18 +0200 Subject: [PATCH 115/349] feat: add simple sendMsgToClient method for initial testing --- includes/server/Server.hpp | 1 + srcs/server/Server.cpp | 39 +++++++++++++++++++++++++++++++------- 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/includes/server/Server.hpp b/includes/server/Server.hpp index 7d4b41d..41f1437 100644 --- a/includes/server/Server.hpp +++ b/includes/server/Server.hpp @@ -33,6 +33,7 @@ class Server { // ~Server(); void run(); + bool sendMsg(int fd, std::string const &msg); }; #endif // SERVER_HPP \ No newline at end of file diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 3e84eb4..58bd8dc 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -15,6 +15,23 @@ Server::Server(int const port, std::string const &password) : port_(port), password_(password), numServerSockets_(0) {} +/* + Can anyone send sth outside of the poll loop -> No, right? + Then I'd always know, what fd/client I am currently talking to. + And could just check the sendBuffer and if it's not empty, + send whatever is there + Except for during exception (class Error) handling, when a potential + msg is part of the exception +*/ +bool Server::sendMsg(int fd, std::string const &msg) { + if (send(fd, msg.c_str(), msg.length(), MSG_NOSIGNAL) == -1) { + std::cerr << "Send failed to FD " << fd << std::endl; + return false; + } + std::cout << "Msg sent to FD: " << fd << ": " << msg; + return true; +} + /* tbd */ @@ -93,9 +110,13 @@ void Server::processClientSocketData_() { } // DEBUG { - std::cout << "UserInput read from client " << idx << ": \n" - << std::string(userInput) /* << std::endl */; + std::ostringstream oss; + oss << idx + 1 - numServerSockets_ << ": \n\t" + << std::string(userInput) /* << std::endl */; userInput.clear(); + std::cout << "UserInput read from client #" << oss.str(); + // Pong to client + sendMsg(pfd.fd, "You are client #" + oss.str()); } } else if (bytesRead <= 0) { // Comment will be deleted later, kept for reference for now @@ -148,12 +169,16 @@ void Server::disconnectClient_(size_t const idx) { pollfd &pfd = pollFds_.at(idx); if (idx < numServerSockets_) { - if (pollFds_.size() < numServerSockets_) - std::cout << "Close clients before closing server!" << std::endl; - std::cout << "Closing server" << std::endl; + if (pollFds_.size() != idx + 1) + std::cout << "Close client sockets before closing server!" + << std::endl; + std::cout << "Closing server socket (" + << srvHelp::getAdressFamilyStr( + srvHelp::sockAddrFamily(pfd.fd)) + << ")" << std::endl; } else - std::cout << "Closing client #" << idx << " (" - << pollFds_.size() - 1 - numServerSockets_ + std::cout << "Closing client #" << idx + 1 - numServerSockets_ + << " socket (" << pollFds_.size() - 1 - numServerSockets_ << " clients remaining)" << std::endl; close(pfd.fd); From 5deb7d3abd99fbdb106961bcf65c114c0d36ef8e Mon Sep 17 00:00:00 2001 From: Rapha Date: Mon, 24 Aug 2026 17:19:56 +0200 Subject: [PATCH 116/349] build: * remove unused file IRC.hpp * move all header files from subdirs to includes/ --- includes/IRC.hpp | 5 --- includes/Server.hpp | 47 +++++++++++++++++-------- includes/{server => }/ServerHelper.hpp | 0 includes/{server => }/SignalHandler.hpp | 0 includes/server/Server.hpp | 39 -------------------- srcs/main.cpp | 3 +- srcs/server/Server.cpp | 6 ++-- srcs/server/SignalHandler.cpp | 2 +- 8 files changed, 39 insertions(+), 63 deletions(-) delete mode 100644 includes/IRC.hpp rename includes/{server => }/ServerHelper.hpp (100%) rename includes/{server => }/SignalHandler.hpp (100%) delete mode 100644 includes/server/Server.hpp diff --git a/includes/IRC.hpp b/includes/IRC.hpp deleted file mode 100644 index 299b816..0000000 --- a/includes/IRC.hpp +++ /dev/null @@ -1,5 +0,0 @@ -#include "server/Server.hpp" -#include "server/SignalHandler.hpp" - -#include -#include \ No newline at end of file diff --git a/includes/Server.hpp b/includes/Server.hpp index e5cedab..b3dde2c 100644 --- a/includes/Server.hpp +++ b/includes/Server.hpp @@ -1,22 +1,41 @@ -#pragma once +#ifndef SERVER_HPP +#define SERVER_HPP -// draft: to be completed by rhaas +#include +#include +#include +#include +#include +#include -#include "ChannelManager.hpp" -#include "ClientManager.hpp" -#include "CommandProcessor.hpp" - -#define SERVER_NAME "irc.local" +#define BUFFLEN 516 +#define MAXMSGLEN 512 +#define MAX_CONNECTIONS 512 class Server { - ChannelManager channels_; - ClientManager clients_; - CommandProcessor cmds_; - int port_; - std::string password_; + private: + int port_; + std::string password_; + + size_t numServerSockets_; + std::vector pollFds_; + std::vector clientInputBuffers_; + + // Server(Server const &other); + // Server &operator=(Server const &other); + + int createServerSocket_(int const domain); + void processServerSocketData_(); + void acceptClient_(int const serverFd); + void processClientSocketData_(); + void disconnectClient_(size_t const idx); public: - Server(int port, const std::string &password); + Server(int const port, std::string const &password); + // ~Server(); - bool tryRegister(Client &client); + void run(); + bool sendMsg(int fd, std::string const &msg); }; + +#endif // SERVER_HPP \ No newline at end of file diff --git a/includes/server/ServerHelper.hpp b/includes/ServerHelper.hpp similarity index 100% rename from includes/server/ServerHelper.hpp rename to includes/ServerHelper.hpp diff --git a/includes/server/SignalHandler.hpp b/includes/SignalHandler.hpp similarity index 100% rename from includes/server/SignalHandler.hpp rename to includes/SignalHandler.hpp diff --git a/includes/server/Server.hpp b/includes/server/Server.hpp deleted file mode 100644 index 41f1437..0000000 --- a/includes/server/Server.hpp +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef SERVER_HPP -#define SERVER_HPP - -#include -#include -#include -#include - -#define BUFFLEN 516 -#define MAXMSGLEN 512 -#define MAX_CONNECTIONS 512 - -class Server { - private: - int port_; - std::string password_; - - size_t numServerSockets_; - std::vector pollFds_; - std::vector clientInputBuffers_; - - // Server(Server const &other); - // Server &operator=(Server const &other); - - int createServerSocket_(int const domain); - void processServerSocketData_(); - void acceptClient_(int const serverFd); - void processClientSocketData_(); - void disconnectClient_(size_t const idx); - - public: - Server(int const port, std::string const &password); - // ~Server(); - - void run(); - bool sendMsg(int fd, std::string const &msg); -}; - -#endif // SERVER_HPP \ No newline at end of file diff --git a/srcs/main.cpp b/srcs/main.cpp index 925d683..0a8e35d 100644 --- a/srcs/main.cpp +++ b/srcs/main.cpp @@ -1,4 +1,5 @@ -#include "IRC.hpp" +#include "Server.hpp" +#include "SignalHandler.hpp" #include #include diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 58bd8dc..7deb854 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -1,7 +1,7 @@ -#include "server/Server.hpp" +#include "Server.hpp" -#include "server/ServerHelper.hpp" -#include "server/SignalHandler.hpp" +#include "ServerHelper.hpp" +#include "SignalHandler.hpp" #include #include diff --git a/srcs/server/SignalHandler.cpp b/srcs/server/SignalHandler.cpp index d5b9ae4..42c4263 100644 --- a/srcs/server/SignalHandler.cpp +++ b/srcs/server/SignalHandler.cpp @@ -1,4 +1,4 @@ -#include "server/SignalHandler.hpp" +#include "SignalHandler.hpp" #include #include From 6ec029594cf87d680f1c0e2758191650d6e943c9 Mon Sep 17 00:00:00 2001 From: Rapha Date: Tue, 25 Aug 2026 11:24:14 +0200 Subject: [PATCH 117/349] build: add typedef in header, bc sighandler_t isn't defined on MacOS --- includes/SignalHandler.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/includes/SignalHandler.hpp b/includes/SignalHandler.hpp index 53d9949..eacc770 100644 --- a/includes/SignalHandler.hpp +++ b/includes/SignalHandler.hpp @@ -4,6 +4,8 @@ #include #include +typedef void (*sighandler_t) (int); + class SignalHandler { private: volatile static std::sig_atomic_t stopServerLoop_; From 9e605e76fcc05be1c878e422a99feaab236524f8 Mon Sep 17 00:00:00 2001 From: Rapha Date: Tue, 25 Aug 2026 14:33:58 +0200 Subject: [PATCH 118/349] rfa: rename checkPoll to checkSocket --- includes/ServerHelper.hpp | 3 ++- srcs/server/Server.cpp | 10 ++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/includes/ServerHelper.hpp b/includes/ServerHelper.hpp index 683f09b..93c2ba8 100644 --- a/includes/ServerHelper.hpp +++ b/includes/ServerHelper.hpp @@ -122,7 +122,8 @@ inline std::string printAddrfamilyAddrPort(socketAddrX const &sockAddrIn46) { return oss.str(); } -inline bool checkPollFailOK() { +inline bool checkSocketFailOK() { + //return SignalHandler::stopLoopSignalReceived(); static size_t loopCounter = 0; static size_t retries = 42; if (loopCounter % 1000 == 0) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 7deb854..c3cc322 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -74,7 +74,7 @@ int Server::createServerSocket_(int const domain) { void Server::processServerSocketData_() { for (size_t idx = 0; idx < numServerSockets_; ++idx) { struct pollfd const &serverPollFds = pollFds_[idx]; - if ((serverPollFds.revents & POLLERR) && !srvHelp::checkPollFailOK()) + if ((serverPollFds.revents & POLLERR) && !srvHelp::checkSocketFailOK()) throw std::runtime_error("Server socket error"); if (serverPollFds.revents & POLLIN) acceptClient_(serverPollFds.fd); @@ -84,7 +84,7 @@ void Server::processServerSocketData_() { void Server::processClientSocketData_() { for (size_t idx = numServerSockets_; idx < pollFds_.size(); ++idx) { pollfd &pfd = pollFds_.at(idx); - if ((pfd.revents & POLLERR) && !srvHelp::checkPollFailOK()) { + if ((pfd.revents & POLLERR) && !srvHelp::checkSocketFailOK()) { std::cerr << "Client socket error on fd: " << pfd.fd << "Disconnect client." << std::endl; disconnectClient_(idx); @@ -209,10 +209,8 @@ void Server::run() { createServerSocket_(AF_INET6); while (!SignalHandler::stopLoopSignalReceived()) { - usleep(100); - - int pollResult = poll(pollFds_.data(), pollFds_.size(), 0); - if (pollResult == -1 && !srvHelp::checkPollFailOK()) + int pollResult = poll(pollFds_.data(), pollFds_.size(), -1); + if (pollResult == -1 && !srvHelp::checkSocketFailOK()) throw std::runtime_error("poll failed too many times."); processServerSocketData_(); From bd1b2dbc9ceb1eac0b6f39cf8c8db655ccd3a4f9 Mon Sep 17 00:00:00 2001 From: wenceslao <117309234+numorb@users.noreply.github.com> Date: Tue, 25 Aug 2026 12:53:36 +0200 Subject: [PATCH 119/349] Header missing --- includes/ServerHelper.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/includes/ServerHelper.hpp b/includes/ServerHelper.hpp index 93c2ba8..80c7bc9 100644 --- a/includes/ServerHelper.hpp +++ b/includes/ServerHelper.hpp @@ -2,6 +2,7 @@ #define SERVERHELPER_HPP #include +#include #include #include #include From 0c833e0a8d81498f0f5252d623e3b5690f496f08 Mon Sep 17 00:00:00 2001 From: Rapha Date: Tue, 25 Aug 2026 14:48:49 +0200 Subject: [PATCH 120/349] fix: resolve remaining merge changes manually rfa: use #pragma once instead of include guards --- includes/Server.hpp | 14 ++++++++++---- includes/ServerHelper.hpp | 7 ++----- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/includes/Server.hpp b/includes/Server.hpp index b3dde2c..8096b74 100644 --- a/includes/Server.hpp +++ b/includes/Server.hpp @@ -1,5 +1,8 @@ -#ifndef SERVER_HPP -#define SERVER_HPP +#pragma once + +#include "ChannelManager.hpp" +#include "ClientManager.hpp" +#include "CommandProcessor.hpp" #include #include @@ -8,12 +11,16 @@ #include #include +#define SERVER_NAME "irc.local" #define BUFFLEN 516 #define MAXMSGLEN 512 #define MAX_CONNECTIONS 512 class Server { private: + ChannelManager channels_; + ClientManager clients_; + CommandProcessor cmds_; int port_; std::string password_; @@ -36,6 +43,5 @@ class Server { void run(); bool sendMsg(int fd, std::string const &msg); + bool tryRegister(Client &client); }; - -#endif // SERVER_HPP \ No newline at end of file diff --git a/includes/ServerHelper.hpp b/includes/ServerHelper.hpp index 80c7bc9..ed397d5 100644 --- a/includes/ServerHelper.hpp +++ b/includes/ServerHelper.hpp @@ -1,5 +1,4 @@ -#ifndef SERVERHELPER_HPP -#define SERVERHELPER_HPP +#pragma once #include #include @@ -124,7 +123,7 @@ inline std::string printAddrfamilyAddrPort(socketAddrX const &sockAddrIn46) { } inline bool checkSocketFailOK() { - //return SignalHandler::stopLoopSignalReceived(); + //return SignalHandler::stopLoopSignalReceived(); static size_t loopCounter = 0; static size_t retries = 42; if (loopCounter % 1000 == 0) @@ -139,5 +138,3 @@ inline bool checkSocketFailOK() { return false; } } // namespace srvHelp - -#endif // SERVERHELPER_HPP \ No newline at end of file From 2a635fe6b919e03ae651badec2afe9c7170519ec Mon Sep 17 00:00:00 2001 From: Rapha Date: Tue, 25 Aug 2026 17:22:27 +0200 Subject: [PATCH 121/349] build: add missing srcs from this branch --- Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index ddd7a05..0d53f4f 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,8 @@ SRCS_SERVER = Server \ Client \ ClientManager \ Channel \ - ChannelManager + ChannelManager \ + SignalHandler SRCS_COMMANDS = CommandProcessor \ Handlers From b804db3d61c8cfa9751c71d6bbeddb0754ef942e Mon Sep 17 00:00:00 2001 From: Rapha Date: Tue, 25 Aug 2026 17:26:20 +0200 Subject: [PATCH 122/349] fix: change to size_t to avoid error when comparing to a std size_t type --- includes/CommandProcessor.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/includes/CommandProcessor.hpp b/includes/CommandProcessor.hpp index 101b0f7..1bcdde8 100644 --- a/includes/CommandProcessor.hpp +++ b/includes/CommandProcessor.hpp @@ -11,9 +11,9 @@ typedef bool (*Handler)(Server &server, Client &client, std::vector ¶ms); class Command { - bool usablePreReg_; - int minParams_; - bool requiresOp_; + bool usablePreReg_; + size_t minParams_; + bool requiresOp_; public: Handler handler; @@ -27,7 +27,7 @@ class Command { return usablePreReg_; } - int getMinParams() const { + size_t getMinParams() const { return minParams_; } From 3bc39b28e872660ee1cb1f8eec0171acc3f1df83 Mon Sep 17 00:00:00 2001 From: Rapha Date: Tue, 25 Aug 2026 17:27:14 +0200 Subject: [PATCH 123/349] fix: add (void) for all unused parameters (due to missing implementation), in order to make things compile --- srcs/commands/CommandProcessor.cpp | 3 + srcs/commands/Handlers.cpp | 154 +++++++++++++++++++++++++++++ srcs/server/Channel.cpp | 4 +- 3 files changed, 160 insertions(+), 1 deletion(-) diff --git a/srcs/commands/CommandProcessor.cpp b/srcs/commands/CommandProcessor.cpp index 3162547..7e4e8fa 100644 --- a/srcs/commands/CommandProcessor.cpp +++ b/srcs/commands/CommandProcessor.cpp @@ -15,6 +15,9 @@ Command &CommandProcessor::at_(const std::string &name) { // TODO blnunez- bool CommandProcessor::parse(const std::string &line, std::string &cmd, std::vector ¶ms) { + (void)line; + (void)cmd; + (void)params; return false; } diff --git a/srcs/commands/Handlers.cpp b/srcs/commands/Handlers.cpp index 6fa58b0..8fa5f40 100644 --- a/srcs/commands/Handlers.cpp +++ b/srcs/commands/Handlers.cpp @@ -2,3 +2,157 @@ #include "Client.hpp" #include "Server.hpp" + +bool Handlers::cap(Server &server, Client &client, std::vector ¶ms) { + (void)server; + (void)client; + (void)params; + return false; +} + +bool Handlers::invite(Server &server, Client &client, std::vector ¶ms) { + (void)server; + (void)client; + (void)params; + return false; +} + +bool Handlers::join(Server &server, Client &client, std::vector ¶ms) { + (void)server; + (void)client; + (void)params; + return false; +} + +bool Handlers::kick(Server &server, Client &client, std::vector ¶ms) { + (void)server; + (void)client; + (void)params; + return false; +} + +bool Handlers::kill(Server &server, Client &client, std::vector ¶ms) { + (void)server; + (void)client; + (void)params; + return false; +} + +bool Handlers::mode(Server &server, Client &client, std::vector ¶ms) { + (void)server; + (void)client; + (void)params; + return false; +} + +bool Handlers::names(Server &server, Client &client, std::vector ¶ms) { + (void)server; + (void)client; + (void)params; + return false; +} + +bool Handlers::nick(Server &server, Client &client, std::vector ¶ms) { + (void)server; + (void)client; + (void)params; + return false; +} + +bool Handlers::message(Server &server, Client &client, std::vector ¶ms) { + (void)server; + (void)client; + (void)params; + return false; +} + +bool Handlers::oper(Server &server, Client &client, std::vector ¶ms) { + (void)server; + (void)client; + (void)params; + return false; +} + +bool Handlers::part(Server &server, Client &client, std::vector ¶ms) { + (void)server; + (void)client; + (void)params; + return false; +} + +bool Handlers::pass(Server &server, Client &client, std::vector ¶ms) { + (void)server; + (void)client; + (void)params; + return false; +} + +bool Handlers::ping(Server &server, Client &client, std::vector ¶ms) { + (void)server; + (void)client; + (void)params; + return false; +} + +bool Handlers::pong(Server &server, Client &client, std::vector ¶ms) { + (void)server; + (void)client; + (void)params; + return false; +} + +bool Handlers::quit(Server &server, Client &client, std::vector ¶ms) { + (void)server; + (void)client; + (void)params; + return false; +} + +bool Handlers::rehash(Server &server, Client &client, std::vector ¶ms) { + (void)server; + (void)client; + (void)params; + return false; +} + +bool Handlers::rename(Server &server, Client &client, std::vector ¶ms) { + (void)server; + (void)client; + (void)params; + return false; +} + +bool Handlers::topic(Server &server, Client &client, std::vector ¶ms) { + (void)server; + (void)client; + (void)params; + return false; +} + +bool Handlers::user(Server &server, Client &client, std::vector ¶ms) { + (void)server; + (void)client; + (void)params; + return false; +} + +bool Handlers::version(Server &server, Client &client, std::vector ¶ms) { + (void)server; + (void)client; + (void)params; + return false; +} + +bool Handlers::who(Server &server, Client &client, std::vector ¶ms) { + (void)server; + (void)client; + (void)params; + return false; +} + +bool Handlers::whois(Server &server, Client &client, std::vector ¶ms) { + (void)server; + (void)client; + (void)params; + return false; +} diff --git a/srcs/server/Channel.cpp b/srcs/server/Channel.cpp index 70819d9..ad37bc3 100644 --- a/srcs/server/Channel.cpp +++ b/srcs/server/Channel.cpp @@ -10,7 +10,9 @@ Channel::Channel(const std::string &name, const std::string &nameCaseFolded, const std::string &key) : name_(name), nameCaseFolded_(nameCaseFolded), key_(key), userLimit_(-1), - createdTime_(std::time(NULL)), topicSetTime_() {} + createdTime_(std::time(NULL)), topicSetTime_() { + (void)topicSetTime_; +} // Join causes `client` to join the channel named `name`, creating it if needed. void Channel::join(Client *client, const std::string &key) { From 1ae6e906d3f14def32f2e37baffea1968f0214ec Mon Sep 17 00:00:00 2001 From: Rapha Date: Tue, 25 Aug 2026 17:27:39 +0200 Subject: [PATCH 124/349] feat: new Logger class --- includes/Logger.hpp | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 includes/Logger.hpp diff --git a/includes/Logger.hpp b/includes/Logger.hpp new file mode 100644 index 0000000..aabe315 --- /dev/null +++ b/includes/Logger.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include +#include +#include +#include + +enum logMode { lmInfo, lmError }; + +class Logger { + std::string prefix_; + + std::string nowStr_() const { + time_t now = time(0); + struct tm *timeinfo = localtime(&now); + char buffer[32]; + strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", timeinfo); + return std::string(buffer); + } + + public: + Logger(std::string const &prefix) { + prefix_ = prefix; + } + + void logMsg(std::string const &msg, logMode const lm) const { + if (lm == (logMode)lmInfo) + std::cout << nowStr_() << " " << prefix_ << "[INFO] : " << msg + << std::endl; + if (lm == (logMode)lmError) + std::cerr << nowStr_() << " " << prefix_ << "[ERROR]: " << msg + << std::endl; + } +}; From c16db87fd2cf6885f482794c372ff7d655aa2aef Mon Sep 17 00:00:00 2001 From: Rapha Date: Tue, 25 Aug 2026 17:30:19 +0200 Subject: [PATCH 125/349] feat: * partly use new logger * add struct for SocketIOData (buffers, error mngmt) * rework cmd extraction from socket stream * use new interface to hand over an extracted cmd line to the cmd handler --- includes/Server.hpp | 51 ++++++++++++++++++++++++---------- includes/ServerHelper.hpp | 10 ++++--- srcs/server/Server.cpp | 58 +++++++++++++++++++++++++++------------ 3 files changed, 82 insertions(+), 37 deletions(-) diff --git a/includes/Server.hpp b/includes/Server.hpp index 8096b74..88c3151 100644 --- a/includes/Server.hpp +++ b/includes/Server.hpp @@ -3,6 +3,7 @@ #include "ChannelManager.hpp" #include "ClientManager.hpp" #include "CommandProcessor.hpp" +#include "Logger.hpp" #include #include @@ -12,36 +13,56 @@ #include #define SERVER_NAME "irc.local" -#define BUFFLEN 516 +#define RECVBUFFLEN 516 #define MAXMSGLEN 512 #define MAX_CONNECTIONS 512 +struct SocketIOData { + std::string readBuffer; + std::string writeBuffer; + size_t errorCount; + bool discardMode; + + SocketIOData() : errorCount(0), discardMode(false) {} +}; + class Server { private: - ChannelManager channels_; - ClientManager clients_; - CommandProcessor cmds_; - int port_; - std::string password_; + ChannelManager channels_; + ClientManager clients_; + CommandProcessor cmds_; + + int port_; + std::string password_; + std::string serverName_; - size_t numServerSockets_; - std::vector pollFds_; - std::vector clientInputBuffers_; + size_t numServerSockets_; + std::vector pollFds_; + // std::vector clientInputBuffers_; + std::map sockIODataStore_; + + Logger logger_; // Server(Server const &other); // Server &operator=(Server const &other); - int createServerSocket_(int const domain); - void processServerSocketData_(); - void acceptClient_(int const serverFd); - void processClientSocketData_(); - void disconnectClient_(size_t const idx); + int createServerSocket_(int const domain); + void processServerSocketData_(); + void acceptClient_(int const serverFd); + void processClientSocketData_(); + void disconnectClient_(size_t const idx); + void log_(std::string const &msg, logMode const lm); public: Server(int const port, std::string const &password); + // ~Server(); + std::string const &serverName() { + return serverName_; + }; + void run(); bool sendMsg(int fd, std::string const &msg); - bool tryRegister(Client &client); + bool tryRegister(Client &client){(void) client; return false;}; }; diff --git a/includes/ServerHelper.hpp b/includes/ServerHelper.hpp index ed397d5..cc30dcb 100644 --- a/includes/ServerHelper.hpp +++ b/includes/ServerHelper.hpp @@ -73,15 +73,17 @@ inline void bindServerSocket(int const socketFd, int const domain, // NOLINT getAdressFamilyStr(domain) + " !"); } -inline void storePollFd(int const socketFd, - std::vector &pFds, // NOLINT - std::vector &usrInBuff) { // NOLINT +inline void +storePollFd(int const socketFd, + std::vector &pFds, // NOLINT + std::map &sockIODataStore) { // NOLINT pollfd pfd = {}; pfd.fd = socketFd; pfd.events = POLLIN; // Monitor for incoming connections pFds.push_back(pfd); - usrInBuff.push_back(std::string()); + sockIODataStore.insert( + std::make_pair(pfd.fd, SocketIOData())); } inline bool isSocketListening(int sockfd) { diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index c3cc322..3cc6117 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -1,5 +1,6 @@ #include "Server.hpp" +#include "Errors.hpp" #include "ServerHelper.hpp" #include "SignalHandler.hpp" @@ -13,7 +14,8 @@ * set port and password from command line args */ Server::Server(int const port, std::string const &password) - : port_(port), password_(password), numServerSockets_(0) {} + : port_(port), password_(password), serverName_(SERVER_NAME), + numServerSockets_(0), logger_(serverName_ + ": ") {} /* Can anyone send sth outside of the poll loop -> No, right? @@ -65,7 +67,7 @@ int Server::createServerSocket_(int const domain) { throw std::runtime_error("Error while listening on socket for " + srvHelp::getAdressFamilyStr(domain) + " !"); - srvHelp::storePollFd(socketFd, pollFds_, clientInputBuffers_); + srvHelp::storePollFd(socketFd, pollFds_, sockIODataStore_); ++numServerSockets_; return socketFd; @@ -89,23 +91,30 @@ void Server::processClientSocketData_() { << "Disconnect client." << std::endl; disconnectClient_(idx); } else if (pfd.revents & POLLIN) { - char buffer[BUFFLEN]; - ssize_t bytesRead = recv(pfd.fd, &buffer[0], BUFFLEN, 0); + SocketIOData &sockIOData = sockIODataStore_.at(pfd.fd); + char recvBuffer[RECVBUFFLEN]; + ssize_t bytesRead = recv(pfd.fd, &recvBuffer[0], RECVBUFFLEN, 0); if (bytesRead > 0) { - std::string &userInput = clientInputBuffers_.at(idx); - std::string bufferStr(&buffer[0], bytesRead); + std::string &userInput = sockIOData.readBuffer; + userInput += std::string(&recvBuffer[0], bytesRead); - userInput += bufferStr; size_t lfcrPos = userInput.find("\r\n"); - if (lfcrPos == std::string::npos) { - continue; + if ((userInput.length() >= MAXMSGLEN) && + (lfcrPos == std::string::npos || lfcrPos > MAXMSGLEN - 2)) { + sockIOData.discardMode = true; + } + if (sockIOData.discardMode) { + if (lfcrPos != std::string::npos) { + userInput.erase(0, lfcrPos + 2); + sockIOData.discardMode = false; + lfcrPos = userInput.find("\r\n"); + } else if ('\r' == *userInput.rbegin()) + userInput = "\r"; + else + userInput.clear(); } - if (lfcrPos > MAXMSGLEN - 2) { - std::cerr << "Client data error: message too " - "long.\nData sent will be discarded." - << std::endl; - userInput.erase(0, lfcrPos + 2); + if (lfcrPos == std::string::npos || sockIOData.discardMode) { continue; } // DEBUG @@ -118,6 +127,17 @@ void Server::processClientSocketData_() { // Pong to client sendMsg(pfd.fd, "You are client #" + oss.str()); } + try { + cmds_.run(*this, *clients_.at(pfd.fd), userInput); + } catch (Error const &e) { + if (e.hasReply()) { + Client const &c = *clients_.at(pfd.fd); + sendMsg(pfd.fd, Reply::build(serverName(), c.getNick(), + e.reply())); + } else + log_(e.what(), (logMode)lmError); + } + } else if (bytesRead <= 0) { // Comment will be deleted later, kept for reference for now // this means, the client closed the connection @@ -155,7 +175,7 @@ void Server::acceptClient_(int const serverFd) { srvHelp::setSocketNonBlock(clientFd); - srvHelp::storePollFd(clientFd, pollFds_, clientInputBuffers_); + srvHelp::storePollFd(clientFd, pollFds_, sockIODataStore_); std::cout << "Client connected:\n" << " fd: " << clientFd << "\n" @@ -185,9 +205,11 @@ void Server::disconnectClient_(size_t const idx) { std::vector::difference_type dist = static_cast::difference_type>(idx); pollFds_.erase(pollFds_.begin() + dist); - clientInputBuffers_.erase( - clientInputBuffers_.begin() + - static_cast::difference_type>(dist)); + sockIODataStore_.erase(pfd.fd); +} + +void Server::log_(std::string const &msg, logMode const lm) { + logger_.logMsg(msg, lm); } /* From 320c28909b2a141d1263f48a06402f4bd906c0cf Mon Sep 17 00:00:00 2001 From: Rapha Date: Tue, 25 Aug 2026 17:32:10 +0200 Subject: [PATCH 126/349] rfa: clang-format --- includes/Server.hpp | 6 +++- srcs/commands/Handlers.cpp | 66 +++++++++++++++++++++++++------------- 2 files changed, 49 insertions(+), 23 deletions(-) diff --git a/includes/Server.hpp b/includes/Server.hpp index 88c3151..356eb5e 100644 --- a/includes/Server.hpp +++ b/includes/Server.hpp @@ -64,5 +64,9 @@ class Server { void run(); bool sendMsg(int fd, std::string const &msg); - bool tryRegister(Client &client){(void) client; return false;}; + + bool tryRegister(Client &client) { + (void)client; + return false; + }; }; diff --git a/srcs/commands/Handlers.cpp b/srcs/commands/Handlers.cpp index 8fa5f40..e8575bb 100644 --- a/srcs/commands/Handlers.cpp +++ b/srcs/commands/Handlers.cpp @@ -3,154 +3,176 @@ #include "Client.hpp" #include "Server.hpp" -bool Handlers::cap(Server &server, Client &client, std::vector ¶ms) { +bool Handlers::cap(Server &server, Client &client, + std::vector ¶ms) { (void)server; (void)client; (void)params; return false; } -bool Handlers::invite(Server &server, Client &client, std::vector ¶ms) { +bool Handlers::invite(Server &server, Client &client, + std::vector ¶ms) { (void)server; (void)client; (void)params; return false; } -bool Handlers::join(Server &server, Client &client, std::vector ¶ms) { +bool Handlers::join(Server &server, Client &client, + std::vector ¶ms) { (void)server; (void)client; (void)params; return false; } -bool Handlers::kick(Server &server, Client &client, std::vector ¶ms) { +bool Handlers::kick(Server &server, Client &client, + std::vector ¶ms) { (void)server; (void)client; (void)params; return false; } -bool Handlers::kill(Server &server, Client &client, std::vector ¶ms) { +bool Handlers::kill(Server &server, Client &client, + std::vector ¶ms) { (void)server; (void)client; (void)params; return false; } -bool Handlers::mode(Server &server, Client &client, std::vector ¶ms) { +bool Handlers::mode(Server &server, Client &client, + std::vector ¶ms) { (void)server; (void)client; (void)params; return false; } -bool Handlers::names(Server &server, Client &client, std::vector ¶ms) { +bool Handlers::names(Server &server, Client &client, + std::vector ¶ms) { (void)server; (void)client; (void)params; return false; } -bool Handlers::nick(Server &server, Client &client, std::vector ¶ms) { +bool Handlers::nick(Server &server, Client &client, + std::vector ¶ms) { (void)server; (void)client; (void)params; return false; } -bool Handlers::message(Server &server, Client &client, std::vector ¶ms) { +bool Handlers::message(Server &server, Client &client, + std::vector ¶ms) { (void)server; (void)client; (void)params; return false; } -bool Handlers::oper(Server &server, Client &client, std::vector ¶ms) { +bool Handlers::oper(Server &server, Client &client, + std::vector ¶ms) { (void)server; (void)client; (void)params; return false; } -bool Handlers::part(Server &server, Client &client, std::vector ¶ms) { +bool Handlers::part(Server &server, Client &client, + std::vector ¶ms) { (void)server; (void)client; (void)params; return false; } -bool Handlers::pass(Server &server, Client &client, std::vector ¶ms) { +bool Handlers::pass(Server &server, Client &client, + std::vector ¶ms) { (void)server; (void)client; (void)params; return false; } -bool Handlers::ping(Server &server, Client &client, std::vector ¶ms) { +bool Handlers::ping(Server &server, Client &client, + std::vector ¶ms) { (void)server; (void)client; (void)params; return false; } -bool Handlers::pong(Server &server, Client &client, std::vector ¶ms) { +bool Handlers::pong(Server &server, Client &client, + std::vector ¶ms) { (void)server; (void)client; (void)params; return false; } -bool Handlers::quit(Server &server, Client &client, std::vector ¶ms) { +bool Handlers::quit(Server &server, Client &client, + std::vector ¶ms) { (void)server; (void)client; (void)params; return false; } -bool Handlers::rehash(Server &server, Client &client, std::vector ¶ms) { +bool Handlers::rehash(Server &server, Client &client, + std::vector ¶ms) { (void)server; (void)client; (void)params; return false; } -bool Handlers::rename(Server &server, Client &client, std::vector ¶ms) { +bool Handlers::rename(Server &server, Client &client, + std::vector ¶ms) { (void)server; (void)client; (void)params; return false; } -bool Handlers::topic(Server &server, Client &client, std::vector ¶ms) { +bool Handlers::topic(Server &server, Client &client, + std::vector ¶ms) { (void)server; (void)client; (void)params; return false; } -bool Handlers::user(Server &server, Client &client, std::vector ¶ms) { +bool Handlers::user(Server &server, Client &client, + std::vector ¶ms) { (void)server; (void)client; (void)params; return false; } -bool Handlers::version(Server &server, Client &client, std::vector ¶ms) { +bool Handlers::version(Server &server, Client &client, + std::vector ¶ms) { (void)server; (void)client; (void)params; return false; } -bool Handlers::who(Server &server, Client &client, std::vector ¶ms) { +bool Handlers::who(Server &server, Client &client, + std::vector ¶ms) { (void)server; (void)client; (void)params; return false; } -bool Handlers::whois(Server &server, Client &client, std::vector ¶ms) { +bool Handlers::whois(Server &server, Client &client, + std::vector ¶ms) { (void)server; (void)client; (void)params; From 6d5e497a2936df9038272bc7b587795d2786df8d Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 26 Aug 2026 16:38:44 +0200 Subject: [PATCH 127/349] feat: * return IRCv3 error code ERR_INPUTTOOLONG * improve sendMsg with error handling and logging * add colours for logger output * control log verbosity via VERBOSE_LOG * rework disconnectClient: it's more a closeSocket --- Makefile | 2 +- includes/Logger.hpp | 6 +- includes/Server.hpp | 12 +++- includes/colours.hpp | 24 +++++++ srcs/server/Server.cpp | 144 +++++++++++++++++++++++++++-------------- 5 files changed, 133 insertions(+), 55 deletions(-) create mode 100644 includes/colours.hpp diff --git a/Makefile b/Makefile index 0d53f4f..cfed038 100644 --- a/Makefile +++ b/Makefile @@ -50,7 +50,7 @@ DEPS = $(OBJS:.o=.d) $(DEBUG_OBJS:.o=.d) CXX = c++ CXXFLAGS = -Wall -Wextra -Werror -pedantic -std=c++98 IFLAGS = -I$(INC_DIR) -DFLAGS = -DDEBUG -g -fsanitize=address +DFLAGS = -DDEBUG -DVERBOSE_LOG=1 -g -fsanitize=address DEBUG_CXXFLAGS = $(CXXFLAGS) $(DFLAGS) # ---- Rules ------------------------------------------------------------------- diff --git a/includes/Logger.hpp b/includes/Logger.hpp index aabe315..eb4db05 100644 --- a/includes/Logger.hpp +++ b/includes/Logger.hpp @@ -1,5 +1,7 @@ #pragma once +#include "colours.hpp" + #include #include #include @@ -28,7 +30,7 @@ class Logger { std::cout << nowStr_() << " " << prefix_ << "[INFO] : " << msg << std::endl; if (lm == (logMode)lmError) - std::cerr << nowStr_() << " " << prefix_ << "[ERROR]: " << msg - << std::endl; + std::cerr << nowStr_() << " " << prefix_ << CRed + << "[ERROR]: " << msg << CReset << std::endl; } }; diff --git a/includes/Server.hpp b/includes/Server.hpp index 356eb5e..eef5799 100644 --- a/includes/Server.hpp +++ b/includes/Server.hpp @@ -13,9 +13,15 @@ #include #define SERVER_NAME "irc.local" +#define MAX_CONNECTIONS 512 + #define RECVBUFFLEN 516 #define MAXMSGLEN 512 -#define MAX_CONNECTIONS 512 +#define SOCKETERRORLIMIT 16 + +#ifndef VERBOSE_LOG +#define VERBOSE_LOG 0 +#endif struct SocketIOData { std::string readBuffer; @@ -50,7 +56,7 @@ class Server { void processServerSocketData_(); void acceptClient_(int const serverFd); void processClientSocketData_(); - void disconnectClient_(size_t const idx); + void closeSocket_(int const fd); void log_(std::string const &msg, logMode const lm); public: @@ -63,7 +69,7 @@ class Server { }; void run(); - bool sendMsg(int fd, std::string const &msg); + bool sendMsg(int const fd, std::string const &msg); bool tryRegister(Client &client) { (void)client; diff --git a/includes/colours.hpp b/includes/colours.hpp new file mode 100644 index 0000000..f0715a9 --- /dev/null +++ b/includes/colours.hpp @@ -0,0 +1,24 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* colours.hpp :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: raphha +#+ +:+ +#+ */ +/* +#+#+#+#+#+ +#+ */ +/* Created: 2026/03/18 11:26:12 by rhaas #+# #+# */ +/* Updated: 2026/08/26 15:11:20 by raphha ### ########.fr */ +/* */ +/* ************************************************************************** */ + +// Color FG BG +#define CBlack "\x1B[30m" // 40 +#define CRed "\x1B[31m" // 41 +#define CGreen "\x1B[32m" // 42 +#define CYellow "\x1B[33m" // 43 +#define CBlue "\x1B[34m" // 44 +#define CMgnta "\x1B[35m" // 45 +#define CCyan "\x1B[36m" // 46 +#define CWhite "\x1B[37m" // 47 +#define CDeflt "\x1B[39m" // 49 + +#define CReset "\x1B[0m" \ No newline at end of file diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 3cc6117..8e00e1b 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -17,20 +17,51 @@ Server::Server(int const port, std::string const &password) : port_(port), password_(password), serverName_(SERVER_NAME), numServerSockets_(0), logger_(serverName_ + ": ") {} -/* - Can anyone send sth outside of the poll loop -> No, right? - Then I'd always know, what fd/client I am currently talking to. - And could just check the sendBuffer and if it's not empty, - send whatever is there - Except for during exception (class Error) handling, when a potential - msg is part of the exception -*/ -bool Server::sendMsg(int fd, std::string const &msg) { - if (send(fd, msg.c_str(), msg.length(), MSG_NOSIGNAL) == -1) { - std::cerr << "Send failed to FD " << fd << std::endl; - return false; +bool Server::sendMsg(int const fd, std::string const &msg) { + SocketIOData &sIOD = sockIODataStore_.at(fd); + ssize_t bytesSent = -1; + bool success = true; + + if (msg.length() > sIOD.writeBuffer.max_size() - sIOD.writeBuffer.length()) + sIOD.errorCount = SOCKETERRORLIMIT; + else { + sIOD.writeBuffer += msg; + bytesSent = send(fd, sIOD.writeBuffer.c_str(), + sIOD.writeBuffer.length(), MSG_NOSIGNAL); + if (bytesSent <= 0) + ++sIOD.errorCount; } - std::cout << "Msg sent to FD: " << fd << ": " << msg; + if (sIOD.errorCount >= SOCKETERRORLIMIT) { + closeSocket_(fd); + success = false; + } + +#if VERBOSE_LOG + std::ostringstream oss; + oss << "writeBuffer: '" << sIOD.writeBuffer << "'"; + if (!success) { + oss << ": send to FD " << fd << " failed."; + log_(oss.str(), (logMode)lmError); + } else { + if (static_cast(bytesSent) < sIOD.writeBuffer.length()) + oss << " partly (" << bytesSent << "/" << sIOD.writeBuffer.length() + << ": " << sIOD.writeBuffer.substr(0, bytesSent); + else + oss << " successfully"; + oss << " sent to FD: " << fd; + log_(oss.str(), (logMode)lmInfo); + } +#endif // VERBOSE_LOG + + if (!success) + return false; + + if (sIOD.errorCount > 0) + sIOD.errorCount = 0; + if (static_cast(bytesSent) < sIOD.writeBuffer.length()) + sIOD.writeBuffer.erase(0, bytesSent); + else + sIOD.writeBuffer.clear(); return true; } @@ -48,8 +79,8 @@ int Server::createServerSocket_(int const domain) { srvHelp::getAdressFamilyStr(domain) + " !"); int optval = 1; - if (setsockopt(socketFd, SOL_SOCKET, SO_REUSEADDR /* | SO_REUSEPORT */, - &optval, sizeof(optval)) != 0) + if (setsockopt(socketFd, SOL_SOCKET, SO_REUSEADDR, &optval, + sizeof(optval)) != 0) throw std::runtime_error("Error while setting socket options for " + srvHelp::getAdressFamilyStr(domain) + " !"); @@ -89,13 +120,19 @@ void Server::processClientSocketData_() { if ((pfd.revents & POLLERR) && !srvHelp::checkSocketFailOK()) { std::cerr << "Client socket error on fd: " << pfd.fd << "Disconnect client." << std::endl; - disconnectClient_(idx); + closeSocket_(idx); } else if (pfd.revents & POLLIN) { - SocketIOData &sockIOData = sockIODataStore_.at(pfd.fd); - char recvBuffer[RECVBUFFLEN]; + char recvBuffer[RECVBUFFLEN]; ssize_t bytesRead = recv(pfd.fd, &recvBuffer[0], RECVBUFFLEN, 0); if (bytesRead > 0) { + SocketIOData &sockIOData = sockIODataStore_.at(pfd.fd); + Client const *const client = clients_.at(pfd.fd); + + std::string nickName = "*"; + if (client && client->isRegistered()) + nickName = client->getNick(); + std::string &userInput = sockIOData.readBuffer; userInput += std::string(&recvBuffer[0], bytesRead); @@ -103,6 +140,10 @@ void Server::processClientSocketData_() { if ((userInput.length() >= MAXMSGLEN) && (lfcrPos == std::string::npos || lfcrPos > MAXMSGLEN - 2)) { sockIOData.discardMode = true; + sendMsg(pfd.fd, + Reply::build( + serverName(), nickName, + Reply::reply(Reply::ERR_INPUTTOOLONG, ""))); } if (sockIOData.discardMode) { if (lfcrPos != std::string::npos) { @@ -117,16 +158,14 @@ void Server::processClientSocketData_() { if (lfcrPos == std::string::npos || sockIOData.discardMode) { continue; } - // DEBUG - { - std::ostringstream oss; - oss << idx + 1 - numServerSockets_ << ": \n\t" - << std::string(userInput) /* << std::endl */; - userInput.clear(); - std::cout << "UserInput read from client #" << oss.str(); - // Pong to client - sendMsg(pfd.fd, "You are client #" + oss.str()); - } +#if LOG_VERBOSE + std::ostringstream oss; + oss << idx + 1 - numServerSockets_ << ": \n\t" + << std::string(userInput) /* << std::endl */; + std::cout << "UserInput read from client #" << oss.str(); + // Pong to client + sendMsg(pfd.fd, "You are client #" + oss.str()); +#endif // LOG_VERBOSE try { cmds_.run(*this, *clients_.at(pfd.fd), userInput); } catch (Error const &e) { @@ -149,7 +188,7 @@ void Server::processClientSocketData_() { // FIN is the TCP mechanism for orderly shutdown. // That's why POLLIN + read() return 0—the FIN, // and the zero read is how the kernel tells you "the peer closed." - disconnectClient_(idx); + closeSocket_(idx); } } } @@ -164,9 +203,10 @@ void Server::acceptClient_(int const serverFd) { srvHelp::setSocketAddr(serverFd, 0, sockAddrIn46, sockLen); - int const clientFd = accept( - serverFd, reinterpret_cast(&sockAddrIn46), // NOLINT - &sockLen); + int const clientFd = + accept(serverFd, + reinterpret_cast(&sockAddrIn46), // NOLINT + &sockLen); if (clientFd == -1) { // throw -> no everything else can still run w/o problems // silent return -> cannot sent anything to client without working socket anyways... @@ -185,27 +225,33 @@ void Server::acceptClient_(int const serverFd) { /* tbd */ -void Server::disconnectClient_(size_t const idx) { - pollfd &pfd = pollFds_.at(idx); +void Server::closeSocket_(int const fd) { + size_t idx = 0; + for (idx = 0; idx < pollFds_.size(); ++idx) + if (fd == pollFds_.at(idx).fd) + break; + if (idx == pollFds_.size()) + return; + std::ostringstream oss; if (idx < numServerSockets_) { if (pollFds_.size() != idx + 1) - std::cout << "Close client sockets before closing server!" - << std::endl; - std::cout << "Closing server socket (" - << srvHelp::getAdressFamilyStr( - srvHelp::sockAddrFamily(pfd.fd)) - << ")" << std::endl; - } else - std::cout << "Closing client #" << idx + 1 - numServerSockets_ - << " socket (" << pollFds_.size() - 1 - numServerSockets_ - << " clients remaining)" << std::endl; - close(pfd.fd); + return log_("Internal error: Close client sockets before " + "closing server!", + (logMode)lmError); + oss << "Closing server socket (" + << srvHelp::getAdressFamilyStr(srvHelp::sockAddrFamily(fd)) << ")"; + } else { + oss << "Closing client #" << idx + 1 - numServerSockets_ << " socket (" + << pollFds_.size() - 1 - numServerSockets_ << " clients remaining)"; + } + log_(oss.str(), (logMode)lmInfo); + close(fd); std::vector::difference_type dist = static_cast::difference_type>(idx); pollFds_.erase(pollFds_.begin() + dist); - sockIODataStore_.erase(pfd.fd); + sockIODataStore_.erase(fd); } void Server::log_(std::string const &msg, logMode const lm) { @@ -232,13 +278,13 @@ void Server::run() { while (!SignalHandler::stopLoopSignalReceived()) { int pollResult = poll(pollFds_.data(), pollFds_.size(), -1); - if (pollResult == -1 && !srvHelp::checkSocketFailOK()) - throw std::runtime_error("poll failed too many times."); + if (pollResult == -1) + throw std::runtime_error("Fatal error: poll failed."); processServerSocketData_(); processClientSocketData_(); } while (!pollFds_.empty()) { - disconnectClient_(pollFds_.size() - 1); + closeSocket_(pollFds_.size() - 1); } } From e125b5d797418e8e4e27f770dc0bba1e41125d9f Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 27 Aug 2026 11:28:21 +0200 Subject: [PATCH 128/349] feat: beautify debug output --- includes/ServerHelper.hpp | 2 +- srcs/server/Server.cpp | 29 ++++++++++++++++++++--------- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/includes/ServerHelper.hpp b/includes/ServerHelper.hpp index cc30dcb..6f17a13 100644 --- a/includes/ServerHelper.hpp +++ b/includes/ServerHelper.hpp @@ -83,7 +83,7 @@ storePollFd(int const socketFd, pFds.push_back(pfd); sockIODataStore.insert( - std::make_pair(pfd.fd, SocketIOData())); + std::make_pair(socketFd, SocketIOData())); } inline bool isSocketListening(int sockfd) { diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 8e00e1b..e03a7ca 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -126,10 +126,15 @@ void Server::processClientSocketData_() { ssize_t bytesRead = recv(pfd.fd, &recvBuffer[0], RECVBUFFLEN, 0); if (bytesRead > 0) { - SocketIOData &sockIOData = sockIODataStore_.at(pfd.fd); - Client const *const client = clients_.at(pfd.fd); + SocketIOData &sockIOData = sockIODataStore_.at(pfd.fd); + Client const *client = 0; + try { + client = clients_.at(pfd.fd); + } catch (std::exception const &e) { + std::cerr << "caught exception: " << e.what() << std::endl; + } - std::string nickName = "*"; + std::string nickName = "*"; if (client && client->isRegistered()) nickName = client->getNick(); @@ -158,14 +163,16 @@ void Server::processClientSocketData_() { if (lfcrPos == std::string::npos || sockIOData.discardMode) { continue; } -#if LOG_VERBOSE +#if VERBOSE_LOG std::ostringstream oss; - oss << idx + 1 - numServerSockets_ << ": \n\t" + oss << "You are client #" << idx + 1 - numServerSockets_ + << ": \n" + << "Srv received: " << std::string(userInput) /* << std::endl */; std::cout << "UserInput read from client #" << oss.str(); // Pong to client - sendMsg(pfd.fd, "You are client #" + oss.str()); -#endif // LOG_VERBOSE + sendMsg(pfd.fd, oss.str()); +#endif // VERBOSE_LOG try { cmds_.run(*this, *clients_.at(pfd.fd), userInput); } catch (Error const &e) { @@ -173,9 +180,13 @@ void Server::processClientSocketData_() { Client const &c = *clients_.at(pfd.fd); sendMsg(pfd.fd, Reply::build(serverName(), c.getNick(), e.reply())); - } else - log_(e.what(), (logMode)lmError); + } else { + std::string tmp("Error while trying to send msg:\n"); + tmp.append(e.what()); + log_(tmp.c_str(), (logMode)lmError); + } } + userInput.clear(); } else if (bytesRead <= 0) { // Comment will be deleted later, kept for reference for now From 358d8827ab19e798d6005eaf630ece080aa3c8ed Mon Sep 17 00:00:00 2001 From: rhaas Date: Thu, 27 Aug 2026 17:29:17 +0200 Subject: [PATCH 129/349] fix: missing include guards for colours --- includes/SignalHandler.hpp | 2 +- includes/colours.hpp | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/includes/SignalHandler.hpp b/includes/SignalHandler.hpp index eacc770..9fed821 100644 --- a/includes/SignalHandler.hpp +++ b/includes/SignalHandler.hpp @@ -22,4 +22,4 @@ class SignalHandler { static bool stopLoopSignalReceived(); }; -#endif // SIGNALHANDLER_HPP \ No newline at end of file +#endif // SIGNALHANDLER_HPP diff --git a/includes/colours.hpp b/includes/colours.hpp index f0715a9..1c51970 100644 --- a/includes/colours.hpp +++ b/includes/colours.hpp @@ -3,13 +3,16 @@ /* ::: :::::::: */ /* colours.hpp :+: :+: :+: */ /* +:+ +:+ +:+ */ -/* By: raphha +#+ +:+ +#+ */ +/* By: rhaas +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2026/03/18 11:26:12 by rhaas #+# #+# */ -/* Updated: 2026/08/26 15:11:20 by raphha ### ########.fr */ +/* Updated: 2026/08/27 17:28:27 by rhaas ### ########.fr */ /* */ /* ************************************************************************** */ +#ifndef COLOURS_HPP +#define COLOURS_HPP + // Color FG BG #define CBlack "\x1B[30m" // 40 #define CRed "\x1B[31m" // 41 @@ -21,4 +24,6 @@ #define CWhite "\x1B[37m" // 47 #define CDeflt "\x1B[39m" // 49 -#define CReset "\x1B[0m" \ No newline at end of file +#define CReset "\x1B[0m" + +#endif // COLOURS_HPP From af154af3731aed31d51b6389db7e5a8b99046b05 Mon Sep 17 00:00:00 2001 From: rhaas Date: Thu, 27 Aug 2026 17:39:10 +0200 Subject: [PATCH 130/349] rfa: * make struct SocketIOData private * make storePollFd a member function, bc it is not just supporting functionality, but also needs access to internal data --- includes/Server.hpp | 19 ++++++++++--------- includes/ServerHelper.hpp | 13 ------------- srcs/server/Server.cpp | 14 ++++++++++++-- 3 files changed, 22 insertions(+), 24 deletions(-) diff --git a/includes/Server.hpp b/includes/Server.hpp index eef5799..52e45ad 100644 --- a/includes/Server.hpp +++ b/includes/Server.hpp @@ -23,17 +23,17 @@ #define VERBOSE_LOG 0 #endif -struct SocketIOData { - std::string readBuffer; - std::string writeBuffer; - size_t errorCount; - bool discardMode; - - SocketIOData() : errorCount(0), discardMode(false) {} -}; - class Server { private: + struct SocketIOData { + std::string readBuffer; + std::string writeBuffer; + size_t errorCount; + bool discardMode; + + SocketIOData() : errorCount(0), discardMode(false) {} + }; + ChannelManager channels_; ClientManager clients_; CommandProcessor cmds_; @@ -55,6 +55,7 @@ class Server { int createServerSocket_(int const domain); void processServerSocketData_(); void acceptClient_(int const serverFd); + void storePollFd_(int const socketFd); void processClientSocketData_(); void closeSocket_(int const fd); void log_(std::string const &msg, logMode const lm); diff --git a/includes/ServerHelper.hpp b/includes/ServerHelper.hpp index 6f17a13..29ca8b1 100644 --- a/includes/ServerHelper.hpp +++ b/includes/ServerHelper.hpp @@ -73,19 +73,6 @@ inline void bindServerSocket(int const socketFd, int const domain, // NOLINT getAdressFamilyStr(domain) + " !"); } -inline void -storePollFd(int const socketFd, - std::vector &pFds, // NOLINT - std::map &sockIODataStore) { // NOLINT - pollfd pfd = {}; - pfd.fd = socketFd; - pfd.events = POLLIN; // Monitor for incoming connections - - pFds.push_back(pfd); - sockIODataStore.insert( - std::make_pair(socketFd, SocketIOData())); -} - inline bool isSocketListening(int sockfd) { int optVal = 0; socklen_t optLen = sizeof(optVal); diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index e03a7ca..66951f9 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -98,7 +98,7 @@ int Server::createServerSocket_(int const domain) { throw std::runtime_error("Error while listening on socket for " + srvHelp::getAdressFamilyStr(domain) + " !"); - srvHelp::storePollFd(socketFd, pollFds_, sockIODataStore_); + storePollFd_(socketFd); ++numServerSockets_; return socketFd; @@ -226,13 +226,23 @@ void Server::acceptClient_(int const serverFd) { srvHelp::setSocketNonBlock(clientFd); - srvHelp::storePollFd(clientFd, pollFds_, sockIODataStore_); + storePollFd_(clientFd); std::cout << "Client connected:\n" << " fd: " << clientFd << "\n" << srvHelp::printAddrfamilyAddrPort(sockAddrIn46) << std::endl; } +void Server::storePollFd_(int const socketFd) { + pollfd pfd = {}; + pfd.fd = socketFd; + pfd.events = POLLIN; // Monitor for incoming connections + + pollFds_.push_back(pfd); + sockIODataStore_.insert( + std::make_pair(socketFd, SocketIOData())); +} + /* tbd */ From 3b623c4e3f2f9a7a999ccfec793467e08a091816 Mon Sep 17 00:00:00 2001 From: rhaas Date: Thu, 27 Aug 2026 17:47:04 +0200 Subject: [PATCH 131/349] rfa: move the logMode enum to the Logger class, so it has a sensible namespace --- includes/Logger.hpp | 8 ++++---- includes/Server.hpp | 2 +- srcs/server/Server.cpp | 12 ++++++------ 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/includes/Logger.hpp b/includes/Logger.hpp index eb4db05..47f1d3a 100644 --- a/includes/Logger.hpp +++ b/includes/Logger.hpp @@ -7,8 +7,6 @@ #include #include -enum logMode { lmInfo, lmError }; - class Logger { std::string prefix_; @@ -21,15 +19,17 @@ class Logger { } public: + enum logMode { lmInfo, lmError }; + Logger(std::string const &prefix) { prefix_ = prefix; } void logMsg(std::string const &msg, logMode const lm) const { - if (lm == (logMode)lmInfo) + if (lm == Logger::lmInfo) std::cout << nowStr_() << " " << prefix_ << "[INFO] : " << msg << std::endl; - if (lm == (logMode)lmError) + if (lm == Logger::lmError) std::cerr << nowStr_() << " " << prefix_ << CRed << "[ERROR]: " << msg << CReset << std::endl; } diff --git a/includes/Server.hpp b/includes/Server.hpp index 52e45ad..53f132a 100644 --- a/includes/Server.hpp +++ b/includes/Server.hpp @@ -58,7 +58,7 @@ class Server { void storePollFd_(int const socketFd); void processClientSocketData_(); void closeSocket_(int const fd); - void log_(std::string const &msg, logMode const lm); + void log_(std::string const &msg, Logger::logMode const lm); public: Server(int const port, std::string const &password); diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 66951f9..ce111d0 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -41,7 +41,7 @@ bool Server::sendMsg(int const fd, std::string const &msg) { oss << "writeBuffer: '" << sIOD.writeBuffer << "'"; if (!success) { oss << ": send to FD " << fd << " failed."; - log_(oss.str(), (logMode)lmError); + log_(oss.str(), Logger::lmError); } else { if (static_cast(bytesSent) < sIOD.writeBuffer.length()) oss << " partly (" << bytesSent << "/" << sIOD.writeBuffer.length() @@ -49,7 +49,7 @@ bool Server::sendMsg(int const fd, std::string const &msg) { else oss << " successfully"; oss << " sent to FD: " << fd; - log_(oss.str(), (logMode)lmInfo); + log_(oss.str(), Logger::lmInfo); } #endif // VERBOSE_LOG @@ -183,7 +183,7 @@ void Server::processClientSocketData_() { } else { std::string tmp("Error while trying to send msg:\n"); tmp.append(e.what()); - log_(tmp.c_str(), (logMode)lmError); + log_(tmp.c_str(), Logger::lmError); } } userInput.clear(); @@ -259,14 +259,14 @@ void Server::closeSocket_(int const fd) { if (pollFds_.size() != idx + 1) return log_("Internal error: Close client sockets before " "closing server!", - (logMode)lmError); + Logger::lmError); oss << "Closing server socket (" << srvHelp::getAdressFamilyStr(srvHelp::sockAddrFamily(fd)) << ")"; } else { oss << "Closing client #" << idx + 1 - numServerSockets_ << " socket (" << pollFds_.size() - 1 - numServerSockets_ << " clients remaining)"; } - log_(oss.str(), (logMode)lmInfo); + log_(oss.str(), Logger::lmInfo); close(fd); std::vector::difference_type dist = @@ -275,7 +275,7 @@ void Server::closeSocket_(int const fd) { sockIODataStore_.erase(fd); } -void Server::log_(std::string const &msg, logMode const lm) { +void Server::log_(std::string const &msg, Logger::logMode const lm) { logger_.logMsg(msg, lm); } From d2e19cfd4c3bdb01aaa3b23af1797817e2463cab Mon Sep 17 00:00:00 2001 From: rhaas Date: Thu, 27 Aug 2026 17:49:14 +0200 Subject: [PATCH 132/349] rfa: using pragma once for consistency --- includes/colours.hpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/includes/colours.hpp b/includes/colours.hpp index 1c51970..9fdeabb 100644 --- a/includes/colours.hpp +++ b/includes/colours.hpp @@ -6,12 +6,11 @@ /* By: rhaas +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2026/03/18 11:26:12 by rhaas #+# #+# */ -/* Updated: 2026/08/27 17:28:27 by rhaas ### ########.fr */ +/* Updated: 2026/08/27 17:47:38 by rhaas ### ########.fr */ /* */ /* ************************************************************************** */ -#ifndef COLOURS_HPP -#define COLOURS_HPP +#pragma once // Color FG BG #define CBlack "\x1B[30m" // 40 @@ -25,5 +24,3 @@ #define CDeflt "\x1B[39m" // 49 #define CReset "\x1B[0m" - -#endif // COLOURS_HPP From ed4ee1ea9f826ca5e1e489d9d6ecede6eba3a9c7 Mon Sep 17 00:00:00 2001 From: rhaas Date: Thu, 27 Aug 2026 17:49:25 +0200 Subject: [PATCH 133/349] rfa: clang-format --- includes/Server.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/Server.hpp b/includes/Server.hpp index 53f132a..3a11189 100644 --- a/includes/Server.hpp +++ b/includes/Server.hpp @@ -58,7 +58,7 @@ class Server { void storePollFd_(int const socketFd); void processClientSocketData_(); void closeSocket_(int const fd); - void log_(std::string const &msg, Logger::logMode const lm); + void log_(std::string const &msg, Logger::logMode const lm); public: Server(int const port, std::string const &password); From bcb75148e75ab3deeda7314291e0db9eaa5cf3a5 Mon Sep 17 00:00:00 2001 From: rhaas Date: Thu, 27 Aug 2026 18:34:26 +0200 Subject: [PATCH 134/349] rfa: cleanup; non-functional --- includes/Server.hpp | 5 +---- includes/ServerHelper.hpp | 1 - 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/includes/Server.hpp b/includes/Server.hpp index 3a11189..73fe122 100644 --- a/includes/Server.hpp +++ b/includes/Server.hpp @@ -5,9 +5,6 @@ #include "CommandProcessor.hpp" #include "Logger.hpp" -#include -#include -#include #include #include #include @@ -74,6 +71,6 @@ class Server { bool tryRegister(Client &client) { (void)client; - return false; + return true; }; }; diff --git a/includes/ServerHelper.hpp b/includes/ServerHelper.hpp index 29ca8b1..debfe94 100644 --- a/includes/ServerHelper.hpp +++ b/includes/ServerHelper.hpp @@ -1,7 +1,6 @@ #pragma once #include -#include #include #include #include From 6f7e4ac32092138a0bbea7be8f07ad27325c2e63 Mon Sep 17 00:00:00 2001 From: Rapha Date: Fri, 28 Aug 2026 12:39:51 +0200 Subject: [PATCH 135/349] rfa: remove unused function with forbidden getsockopt --- includes/ServerHelper.hpp | 9 --------- 1 file changed, 9 deletions(-) diff --git a/includes/ServerHelper.hpp b/includes/ServerHelper.hpp index debfe94..d89a262 100644 --- a/includes/ServerHelper.hpp +++ b/includes/ServerHelper.hpp @@ -72,15 +72,6 @@ inline void bindServerSocket(int const socketFd, int const domain, // NOLINT getAdressFamilyStr(domain) + " !"); } -inline bool isSocketListening(int sockfd) { - int optVal = 0; - socklen_t optLen = sizeof(optVal); - if (getsockopt(sockfd, SOL_SOCKET, SO_ACCEPTCONN, &optVal, &optLen) < 0) - throw std::runtime_error("Error getting socket options."); - - return (optVal > 0); -} - inline std::string printAddrfamilyAddrPort(socketAddrX const &sockAddrIn46) { char addrBuffer[128]; socklen_t const addrBuffLen = sizeof(addrBuffer) / sizeof(char); From a9f00ac8f44a8067fe2cf2df6459f14991f970e4 Mon Sep 17 00:00:00 2001 From: Rapha Date: Fri, 28 Aug 2026 12:40:22 +0200 Subject: [PATCH 136/349] feat: * add new log level WARN * add colours for each level --- includes/Logger.hpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/includes/Logger.hpp b/includes/Logger.hpp index 47f1d3a..bed3d0b 100644 --- a/includes/Logger.hpp +++ b/includes/Logger.hpp @@ -19,7 +19,7 @@ class Logger { } public: - enum logMode { lmInfo, lmError }; + enum logMode { lmInfo, lmWarn, lmError }; Logger(std::string const &prefix) { prefix_ = prefix; @@ -27,8 +27,11 @@ class Logger { void logMsg(std::string const &msg, logMode const lm) const { if (lm == Logger::lmInfo) - std::cout << nowStr_() << " " << prefix_ << "[INFO] : " << msg - << std::endl; + std::cout << nowStr_() << " " << prefix_ << CGreen + << " [INFO] : " << msg << CReset << std::endl; + if (lm == Logger::lmWarn) + std::cout << nowStr_() << " " << prefix_ << CYellow + << " [WARN] : " << msg << CReset << std::endl; if (lm == Logger::lmError) std::cerr << nowStr_() << " " << prefix_ << CRed << "[ERROR]: " << msg << CReset << std::endl; From 7cbb533cbc6cbfa966ee5450dda7515228fb784a Mon Sep 17 00:00:00 2001 From: Rapha Date: Fri, 28 Aug 2026 13:12:01 +0200 Subject: [PATCH 137/349] feat: remove unused function checkSocketFailOK --- includes/ServerHelper.hpp | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/includes/ServerHelper.hpp b/includes/ServerHelper.hpp index d89a262..ee9b543 100644 --- a/includes/ServerHelper.hpp +++ b/includes/ServerHelper.hpp @@ -91,29 +91,18 @@ inline std::string printAddrfamilyAddrPort(socketAddrX const &sockAddrIn46) { } char const *addrStrPtr = inet_ntop(saFamily, addrPtr, &addrBuffer[0], addrBuffLen); - std::string addrStr(addrStrPtr ? addrStrPtr : ""); - if (saFamily == AF_INET6) - addrStr = "[" + addrStr + "]"; + std::string addrStr("No addr:port information."); + if (addrStrPtr) { + addrStr.assign(addrStrPtr); + if (saFamily == AF_INET6) + addrStr = "[" + addrStr + "]"; + } std::ostringstream oss; oss << " AF: " << getAdressFamilyStr(saFamily) << "\n addr:port: " << addrStr << ":" << std::dec << portNumber; + return oss.str(); } -inline bool checkSocketFailOK() { - //return SignalHandler::stopLoopSignalReceived(); - static size_t loopCounter = 0; - static size_t retries = 42; - if (loopCounter % 1000 == 0) - retries = 42; - if (errno == EINTR) - return true; - if (errno == EAGAIN && retries > 1) { - retries--; - usleep(10000); - return true; - } - return false; -} } // namespace srvHelp From 65aff147fec4030cd78d5b2e1a17eebab9461306 Mon Sep 17 00:00:00 2001 From: Rapha Date: Fri, 28 Aug 2026 13:38:28 +0200 Subject: [PATCH 138/349] feat: * remove VERBOSE_LOG * add logLevel Exception (and rfa logMsg function) * do not throw on wrong input params * remove checkSocketFailOK function && implement new error handling strategy --- Makefile | 2 +- includes/Logger.hpp | 36 ++++++++++++++++++-------- includes/Server.hpp | 4 --- srcs/main.cpp | 25 +++++++++++------- srcs/server/Server.cpp | 58 ++++++++++++++++++------------------------ 5 files changed, 67 insertions(+), 58 deletions(-) diff --git a/Makefile b/Makefile index cfed038..0d53f4f 100644 --- a/Makefile +++ b/Makefile @@ -50,7 +50,7 @@ DEPS = $(OBJS:.o=.d) $(DEBUG_OBJS:.o=.d) CXX = c++ CXXFLAGS = -Wall -Wextra -Werror -pedantic -std=c++98 IFLAGS = -I$(INC_DIR) -DFLAGS = -DDEBUG -DVERBOSE_LOG=1 -g -fsanitize=address +DFLAGS = -DDEBUG -g -fsanitize=address DEBUG_CXXFLAGS = $(CXXFLAGS) $(DFLAGS) # ---- Rules ------------------------------------------------------------------- diff --git a/includes/Logger.hpp b/includes/Logger.hpp index bed3d0b..6a08787 100644 --- a/includes/Logger.hpp +++ b/includes/Logger.hpp @@ -19,21 +19,35 @@ class Logger { } public: - enum logMode { lmInfo, lmWarn, lmError }; + enum logMode { lmInfo, lmWarn, lmError, lmExcept }; Logger(std::string const &prefix) { prefix_ = prefix; } - void logMsg(std::string const &msg, logMode const lm) const { - if (lm == Logger::lmInfo) - std::cout << nowStr_() << " " << prefix_ << CGreen - << " [INFO] : " << msg << CReset << std::endl; - if (lm == Logger::lmWarn) - std::cout << nowStr_() << " " << prefix_ << CYellow - << " [WARN] : " << msg << CReset << std::endl; - if (lm == Logger::lmError) - std::cerr << nowStr_() << " " << prefix_ << CRed - << "[ERROR]: " << msg << CReset << std::endl; + void logMsg(std::string msg, logMode const lm) const { + std::ostream *os = &std::cout; + if (lm == Logger::lmError || lm == Logger::lmExcept) + os = &std::cerr; + + switch (lm) { + case Logger::lmInfo: + msg.insert(0, " [INFO] : "); + *os << CGreen; + break; + case Logger::lmWarn: + msg.insert(0, " [WARN] : "); + *os << CYellow; + break; + case Logger::lmError: + msg.insert(0, " [ERROR] : "); + *os << CRed; + break; + case Logger::lmExcept: + msg.insert(0, " [EXCEPTION] : "); + *os << CMgnta; + break; + } + *os << nowStr_() << " " << prefix_ << msg << CReset << std::endl; } }; diff --git a/includes/Server.hpp b/includes/Server.hpp index 73fe122..6985e02 100644 --- a/includes/Server.hpp +++ b/includes/Server.hpp @@ -16,10 +16,6 @@ #define MAXMSGLEN 512 #define SOCKETERRORLIMIT 16 -#ifndef VERBOSE_LOG -#define VERBOSE_LOG 0 -#endif - class Server { private: struct SocketIOData { diff --git a/srcs/main.cpp b/srcs/main.cpp index 0a8e35d..075c541 100644 --- a/srcs/main.cpp +++ b/srcs/main.cpp @@ -6,7 +6,7 @@ #include #include -static void checkExtractCmdLnArgs(int const argc, char *const argv[], int &port, +static bool checkExtractCmdLnArgs(int const argc, char *const argv[], int &port, std::string &password); /* @@ -51,26 +51,31 @@ int main(int argc, char **argv) { std::string password; try { - checkExtractCmdLnArgs(argc, argv, port, password); + if (!checkExtractCmdLnArgs(argc, argv, port, password)) + return 1; SignalHandler sigHandler; Server srv(port, password); srv.run(); + } catch (std::exception const &e) { - std::cerr << e.what() << std::endl; + std::cerr << CCyan << " !!! Exception: " << e.what() << CReset + << std::endl; return 1; } - std::cout << "Clean exit." << std::endl; + std::cout << CGreen << "Clean exit." << CReset << std::endl; return 0; } -static void checkExtractCmdLnArgs(int const argc, char *const argv[], int &port, +static bool checkExtractCmdLnArgs(int const argc, char *const argv[], int &port, std::string &password) { std::istringstream iss; std::string remainder; - if (argc != 3) - throw std::out_of_range( - "Usage: ./ircserv "); + if (argc != 3) { + std::cerr << CYellow << "Usage: ./ircserv " + << CReset << std::endl; + return false; + } iss.str(argv[1]); // NOLINT if (!((iss >> port) && (iss >> remainder).eof())) @@ -79,6 +84,8 @@ static void checkExtractCmdLnArgs(int const argc, char *const argv[], int &port, iss.str(argv[2]); // NOLINT if (!(iss >> password).eof()) - throw std::out_of_range( + throw std::runtime_error( "Error while extracting password from commandline parameter."); + + return true; } diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index ce111d0..3ff266a 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -36,22 +36,20 @@ bool Server::sendMsg(int const fd, std::string const &msg) { success = false; } -#if VERBOSE_LOG std::ostringstream oss; - oss << "writeBuffer: '" << sIOD.writeBuffer << "'"; + oss << "SendMsg event:\n" << "WriteBuffer: '" << sIOD.writeBuffer << "':\n"; if (!success) { - oss << ": send to FD " << fd << " failed."; + oss << " send to FD " << fd << " failed."; log_(oss.str(), Logger::lmError); } else { if (static_cast(bytesSent) < sIOD.writeBuffer.length()) oss << " partly (" << bytesSent << "/" << sIOD.writeBuffer.length() - << ": " << sIOD.writeBuffer.substr(0, bytesSent); + << ") " << sIOD.writeBuffer.substr(0, bytesSent); else oss << " successfully"; oss << " sent to FD: " << fd; log_(oss.str(), Logger::lmInfo); } -#endif // VERBOSE_LOG if (!success) return false; @@ -107,8 +105,8 @@ int Server::createServerSocket_(int const domain) { void Server::processServerSocketData_() { for (size_t idx = 0; idx < numServerSockets_; ++idx) { struct pollfd const &serverPollFds = pollFds_[idx]; - if ((serverPollFds.revents & POLLERR) && !srvHelp::checkSocketFailOK()) - throw std::runtime_error("Server socket error"); + if ((serverPollFds.revents & POLLERR)) + throw std::runtime_error("Server socket error."); if (serverPollFds.revents & POLLIN) acceptClient_(serverPollFds.fd); } @@ -116,28 +114,30 @@ void Server::processServerSocketData_() { void Server::processClientSocketData_() { for (size_t idx = numServerSockets_; idx < pollFds_.size(); ++idx) { - pollfd &pfd = pollFds_.at(idx); - if ((pfd.revents & POLLERR) && !srvHelp::checkSocketFailOK()) { - std::cerr << "Client socket error on fd: " << pfd.fd - << "Disconnect client." << std::endl; - closeSocket_(idx); + pollfd &pfd = pollFds_.at(idx); + SocketIOData &sIOD = sockIODataStore_.at(pfd.fd); + + if ((pfd.revents & POLLERR)) { + if (sIOD.errorCount >= SOCKETERRORLIMIT) { + closeSocket_(pfd.fd); + } else + ++sIOD.errorCount; + } else if (pfd.revents & POLLIN) { char recvBuffer[RECVBUFFLEN]; ssize_t bytesRead = recv(pfd.fd, &recvBuffer[0], RECVBUFFLEN, 0); if (bytesRead > 0) { SocketIOData &sockIOData = sockIODataStore_.at(pfd.fd); - Client const *client = 0; + std::string nickName = "*"; try { - client = clients_.at(pfd.fd); + Client const *const client = clients_.at(pfd.fd); + if (client && client->isRegistered()) + nickName = client->getNick(); } catch (std::exception const &e) { - std::cerr << "caught exception: " << e.what() << std::endl; + log_(e.what(), Logger::lmExcept); } - std::string nickName = "*"; - if (client && client->isRegistered()) - nickName = client->getNick(); - std::string &userInput = sockIOData.readBuffer; userInput += std::string(&recvBuffer[0], bytesRead); @@ -163,7 +163,8 @@ void Server::processClientSocketData_() { if (lfcrPos == std::string::npos || sockIOData.discardMode) { continue; } -#if VERBOSE_LOG + +#if 0 // For netcat debug std::ostringstream oss; oss << "You are client #" << idx + 1 - numServerSockets_ << ": \n" @@ -172,7 +173,7 @@ void Server::processClientSocketData_() { std::cout << "UserInput read from client #" << oss.str(); // Pong to client sendMsg(pfd.fd, oss.str()); -#endif // VERBOSE_LOG +#endif try { cmds_.run(*this, *clients_.at(pfd.fd), userInput); } catch (Error const &e) { @@ -189,16 +190,6 @@ void Server::processClientSocketData_() { userInput.clear(); } else if (bytesRead <= 0) { - // Comment will be deleted later, kept for reference for now - // this means, the client closed the connection - // POLLHUP isn't useful, because: - // When you kill netcat with Ctrl+C: - // Netcat's OS kernel sent a FIN packet to your server - // Your server's poll() detected this with POLLIN - // Your read() returned 0 (zero bytes) == "I got the FIN, connection closed" - // FIN is the TCP mechanism for orderly shutdown. - // That's why POLLIN + read() return 0—the FIN, - // and the zero read is how the kernel tells you "the peer closed." closeSocket_(idx); } } @@ -219,8 +210,9 @@ void Server::acceptClient_(int const serverFd) { reinterpret_cast(&sockAddrIn46), // NOLINT &sockLen); if (clientFd == -1) { - // throw -> no everything else can still run w/o problems - // silent return -> cannot sent anything to client without working socket anyways... + log_("Could not accept client:\n" + + srvHelp::printAddrfamilyAddrPort(sockAddrIn46), + Logger::lmWarn); return; } From 8648e73acf4a40f549f312245c6205444593a95a Mon Sep 17 00:00:00 2001 From: Rapha Date: Fri, 28 Aug 2026 15:10:12 +0200 Subject: [PATCH 139/349] rfa: beautify output format --- includes/Logger.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/includes/Logger.hpp b/includes/Logger.hpp index 6a08787..a201c64 100644 --- a/includes/Logger.hpp +++ b/includes/Logger.hpp @@ -32,19 +32,19 @@ class Logger { switch (lm) { case Logger::lmInfo: - msg.insert(0, " [INFO] : "); + msg.insert(0, " [INFO]: "); *os << CGreen; break; case Logger::lmWarn: - msg.insert(0, " [WARN] : "); + msg.insert(0, " [WARN]: "); *os << CYellow; break; case Logger::lmError: - msg.insert(0, " [ERROR] : "); + msg.insert(0, " [ERROR]: "); *os << CRed; break; case Logger::lmExcept: - msg.insert(0, " [EXCEPTION] : "); + msg.insert(0, " [EXCEPTION]: "); *os << CMgnta; break; } From efb35db1094abc914501ca1b3bcbfb55e2f464a8 Mon Sep 17 00:00:00 2001 From: Rapha Date: Fri, 28 Aug 2026 15:12:13 +0200 Subject: [PATCH 140/349] feat: separate send and recv error counters fix: use fd instead of index --- includes/Server.hpp | 6 ++-- srcs/server/Server.cpp | 69 ++++++++++++++++++++++-------------------- 2 files changed, 41 insertions(+), 34 deletions(-) diff --git a/includes/Server.hpp b/includes/Server.hpp index 6985e02..4abb003 100644 --- a/includes/Server.hpp +++ b/includes/Server.hpp @@ -21,10 +21,12 @@ class Server { struct SocketIOData { std::string readBuffer; std::string writeBuffer; - size_t errorCount; + size_t recvErrorCount; + size_t sendErrorCount; bool discardMode; - SocketIOData() : errorCount(0), discardMode(false) {} + SocketIOData() + : recvErrorCount(0), sendErrorCount(0), discardMode(false) {} }; ChannelManager channels_; diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 3ff266a..998096c 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -23,21 +23,22 @@ bool Server::sendMsg(int const fd, std::string const &msg) { bool success = true; if (msg.length() > sIOD.writeBuffer.max_size() - sIOD.writeBuffer.length()) - sIOD.errorCount = SOCKETERRORLIMIT; + sIOD.sendErrorCount = SOCKETERRORLIMIT; else { sIOD.writeBuffer += msg; bytesSent = send(fd, sIOD.writeBuffer.c_str(), sIOD.writeBuffer.length(), MSG_NOSIGNAL); if (bytesSent <= 0) - ++sIOD.errorCount; + ++sIOD.sendErrorCount; } - if (sIOD.errorCount >= SOCKETERRORLIMIT) { + if (sIOD.sendErrorCount >= SOCKETERRORLIMIT) { closeSocket_(fd); success = false; } std::ostringstream oss; - oss << "SendMsg event:\n" << "WriteBuffer: '" << sIOD.writeBuffer << "':\n"; + oss << "SendMsg event:\n" + << " WriteBuffer: '" << sIOD.writeBuffer << "':\n"; if (!success) { oss << " send to FD " << fd << " failed."; log_(oss.str(), Logger::lmError); @@ -54,8 +55,8 @@ bool Server::sendMsg(int const fd, std::string const &msg) { if (!success) return false; - if (sIOD.errorCount > 0) - sIOD.errorCount = 0; + if (sIOD.sendErrorCount > 0) + sIOD.sendErrorCount = 0; if (static_cast(bytesSent) < sIOD.writeBuffer.length()) sIOD.writeBuffer.erase(0, bytesSent); else @@ -118,10 +119,10 @@ void Server::processClientSocketData_() { SocketIOData &sIOD = sockIODataStore_.at(pfd.fd); if ((pfd.revents & POLLERR)) { - if (sIOD.errorCount >= SOCKETERRORLIMIT) { + if (sIOD.recvErrorCount >= SOCKETERRORLIMIT) { closeSocket_(pfd.fd); } else - ++sIOD.errorCount; + ++sIOD.recvErrorCount; } else if (pfd.revents & POLLIN) { char recvBuffer[RECVBUFFLEN]; @@ -130,8 +131,9 @@ void Server::processClientSocketData_() { if (bytesRead > 0) { SocketIOData &sockIOData = sockIODataStore_.at(pfd.fd); std::string nickName = "*"; + Client const *client = 0; try { - Client const *const client = clients_.at(pfd.fd); + client = clients_.at(pfd.fd); if (client && client->isRegistered()) nickName = client->getNick(); } catch (std::exception const &e) { @@ -145,12 +147,18 @@ void Server::processClientSocketData_() { if ((userInput.length() >= MAXMSGLEN) && (lfcrPos == std::string::npos || lfcrPos > MAXMSGLEN - 2)) { sockIOData.discardMode = true; + ++sIOD.recvErrorCount; sendMsg(pfd.fd, Reply::build( serverName(), nickName, Reply::reply(Reply::ERR_INPUTTOOLONG, ""))); } if (sockIOData.discardMode) { + if (!client || !client->isRegistered() || + sIOD.recvErrorCount >= SOCKETERRORLIMIT) { + closeSocket_(pfd.fd); + continue; + } if (lfcrPos != std::string::npos) { userInput.erase(0, lfcrPos + 2); sockIOData.discardMode = false; @@ -163,34 +171,35 @@ void Server::processClientSocketData_() { if (lfcrPos == std::string::npos || sockIOData.discardMode) { continue; } - -#if 0 // For netcat debug - std::ostringstream oss; - oss << "You are client #" << idx + 1 - numServerSockets_ - << ": \n" - << "Srv received: " - << std::string(userInput) /* << std::endl */; - std::cout << "UserInput read from client #" << oss.str(); - // Pong to client - sendMsg(pfd.fd, oss.str()); + sIOD.recvErrorCount = 0; + userInput = userInput.substr(0, userInput.length() - 2); +#if 1 // For netcat debug + { + size_t const clientNumber = idx + 1 - numServerSockets_; + std::ostringstream oss; + oss << "You are client #" << clientNumber << ": \n" + << "Srv received: " << std::string(userInput) + << std::endl; + sendMsg(pfd.fd, oss.str()); + } #endif try { cmds_.run(*this, *clients_.at(pfd.fd), userInput); } catch (Error const &e) { if (e.hasReply()) { - Client const &c = *clients_.at(pfd.fd); - sendMsg(pfd.fd, Reply::build(serverName(), c.getNick(), + sendMsg(pfd.fd, Reply::build(serverName(), nickName, e.reply())); } else { - std::string tmp("Error while trying to send msg:\n"); + std::string tmp("Error while trying to run cmd:\n"); + tmp.append(userInput + "\n"); tmp.append(e.what()); - log_(tmp.c_str(), Logger::lmError); + log_(tmp.c_str(), Logger::lmExcept); } } userInput.clear(); } else if (bytesRead <= 0) { - closeSocket_(idx); + closeSocket_(pfd.fd); } } } @@ -255,8 +264,9 @@ void Server::closeSocket_(int const fd) { oss << "Closing server socket (" << srvHelp::getAdressFamilyStr(srvHelp::sockAddrFamily(fd)) << ")"; } else { - oss << "Closing client #" << idx + 1 - numServerSockets_ << " socket (" - << pollFds_.size() - 1 - numServerSockets_ << " clients remaining)"; + oss << "Closing client #" << idx + 1 - numServerSockets_ << ", sockFd " + << fd << "; (" << pollFds_.size() - 1 - numServerSockets_ + << " clients remaining)"; } log_(oss.str(), Logger::lmInfo); @@ -279,11 +289,6 @@ void Server::log_(std::string const &msg, Logger::logMode const lm) { * handles client connect and disconnect scenarios * on server stop signal, disconnects all clients and closes the server socket(s) - * if poll returns an error -> exception is thrown, except for two scenarios: - EINTR — The call was interrupted by a signal. This is the most common - retriable error. Retrying is standard practice. - EAGAIN — Temporary resource unavailability (rare with poll(), - but possible in some edge cases). */ void Server::run() { createServerSocket_(AF_INET); @@ -298,6 +303,6 @@ void Server::run() { processClientSocketData_(); } while (!pollFds_.empty()) { - closeSocket_(pollFds_.size() - 1); + closeSocket_(pollFds_.back().fd); } } From 6c663f5d7ec7e680e431a572ad65ea38db022b01 Mon Sep 17 00:00:00 2001 From: Rapha Date: Sat, 29 Aug 2026 12:17:13 +0200 Subject: [PATCH 141/349] rfa: pragma instead of include guards --- includes/SignalHandler.hpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/includes/SignalHandler.hpp b/includes/SignalHandler.hpp index 9fed821..ca264ae 100644 --- a/includes/SignalHandler.hpp +++ b/includes/SignalHandler.hpp @@ -1,5 +1,4 @@ -#ifndef SIGNALHANDLER_HPP -#define SIGNALHANDLER_HPP +#pragma once #include #include @@ -10,6 +9,7 @@ class SignalHandler { private: volatile static std::sig_atomic_t stopServerLoop_; static void handleStopSignals_(int signal); + std::vector stopSignals_; bool setStopSignals_(sighandler_t handler); @@ -21,5 +21,3 @@ class SignalHandler { ~SignalHandler(); static bool stopLoopSignalReceived(); }; - -#endif // SIGNALHANDLER_HPP From 4a2f741a0ca06339cb1195bf4483c354d6cd5fed Mon Sep 17 00:00:00 2001 From: Rapha Date: Sat, 29 Aug 2026 12:18:07 +0200 Subject: [PATCH 142/349] rfa: move (inline) log function from header to cpp file as regular function --- Makefile | 1 + includes/Logger.hpp | 26 +------------------------- srcs/server/Logger.cpp | 31 +++++++++++++++++++++++++++++++ 3 files changed, 33 insertions(+), 25 deletions(-) create mode 100644 srcs/server/Logger.cpp diff --git a/Makefile b/Makefile index 0d53f4f..c6bde47 100644 --- a/Makefile +++ b/Makefile @@ -17,6 +17,7 @@ SRCS_SERVER = Server \ ClientManager \ Channel \ ChannelManager \ + Logger \ SignalHandler SRCS_COMMANDS = CommandProcessor \ diff --git a/includes/Logger.hpp b/includes/Logger.hpp index a201c64..5337f1e 100644 --- a/includes/Logger.hpp +++ b/includes/Logger.hpp @@ -25,29 +25,5 @@ class Logger { prefix_ = prefix; } - void logMsg(std::string msg, logMode const lm) const { - std::ostream *os = &std::cout; - if (lm == Logger::lmError || lm == Logger::lmExcept) - os = &std::cerr; - - switch (lm) { - case Logger::lmInfo: - msg.insert(0, " [INFO]: "); - *os << CGreen; - break; - case Logger::lmWarn: - msg.insert(0, " [WARN]: "); - *os << CYellow; - break; - case Logger::lmError: - msg.insert(0, " [ERROR]: "); - *os << CRed; - break; - case Logger::lmExcept: - msg.insert(0, " [EXCEPTION]: "); - *os << CMgnta; - break; - } - *os << nowStr_() << " " << prefix_ << msg << CReset << std::endl; - } + void logMsg(std::string msg, logMode const lm) const; }; diff --git a/srcs/server/Logger.cpp b/srcs/server/Logger.cpp new file mode 100644 index 0000000..61191de --- /dev/null +++ b/srcs/server/Logger.cpp @@ -0,0 +1,31 @@ +#include "Logger.hpp" + +/* + Log strings in four categories (severity) in different colours + to stdout/stderr respectively. +*/ +void Logger::logMsg(std::string msg, logMode const lm) const { + std::ostream *os = &std::cout; + if (lm == Logger::lmError || lm == Logger::lmExcept) + os = &std::cerr; + + switch (lm) { + case Logger::lmInfo: + msg.insert(0, " [INFO ]: "); + *os << CGreen; + break; + case Logger::lmWarn: + msg.insert(0, " [WARN ]: "); + *os << CYellow; + break; + case Logger::lmError: + msg.insert(0, " [ERROR ]: "); + *os << CRed; + break; + case Logger::lmExcept: + msg.insert(0, " [EXCEPT]: "); + *os << CMgnta; + break; + } + *os << nowStr_() << " " << prefix_ << msg << CReset << std::endl; +} From aaa785388f2ba4936f20e4e5f6b6fbc743506088 Mon Sep 17 00:00:00 2001 From: Rapha Date: Sat, 29 Aug 2026 14:17:58 +0200 Subject: [PATCH 143/349] rfa: tidy && format --- includes/Logger.hpp | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/includes/Logger.hpp b/includes/Logger.hpp index 5337f1e..145c3e6 100644 --- a/includes/Logger.hpp +++ b/includes/Logger.hpp @@ -8,22 +8,20 @@ #include class Logger { - std::string prefix_; + std::string prefix_; - std::string nowStr_() const { + static std::string nowStr_() { time_t now = time(0); struct tm *timeinfo = localtime(&now); char buffer[32]; - strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", timeinfo); - return std::string(buffer); + strftime(&buffer[0], sizeof(buffer), "%Y-%m-%d %H:%M:%S", timeinfo); + return std::string(&buffer[0]); } public: enum logMode { lmInfo, lmWarn, lmError, lmExcept }; - Logger(std::string const &prefix) { - prefix_ = prefix; - } + Logger(std::string const &prefix) : prefix_(prefix) {} void logMsg(std::string msg, logMode const lm) const; }; From a11b4303a6bbcfe5801c225af75b01adecedd551 Mon Sep 17 00:00:00 2001 From: Rapha Date: Sat, 29 Aug 2026 14:19:37 +0200 Subject: [PATCH 144/349] feat: * use Server d'tor to cleanup sockets fix: * do not completely clear client read buffer when composing cmdLine rfa: * split long functions --- includes/Server.hpp | 34 ++++-- srcs/server/Server.cpp | 261 +++++++++++++++++++++++++---------------- 2 files changed, 183 insertions(+), 112 deletions(-) diff --git a/includes/Server.hpp b/includes/Server.hpp index 4abb003..68b61fe 100644 --- a/includes/Server.hpp +++ b/includes/Server.hpp @@ -12,21 +12,25 @@ #define SERVER_NAME "irc.local" #define MAX_CONNECTIONS 512 -#define RECVBUFFLEN 516 +#define RECVBUFFLEN 1024 #define MAXMSGLEN 512 -#define SOCKETERRORLIMIT 16 +#define SOCKETERRORLIMIT 8 + +#define NETCATDBG 1 // For netcat debug class Server { private: struct SocketIOData { std::string readBuffer; std::string writeBuffer; + ssize_t bytesRead; size_t recvErrorCount; size_t sendErrorCount; bool discardMode; SocketIOData() - : recvErrorCount(0), sendErrorCount(0), discardMode(false) {} + : bytesRead(0), recvErrorCount(0), sendErrorCount(0), + discardMode(false) {} }; ChannelManager channels_; @@ -39,28 +43,36 @@ class Server { size_t numServerSockets_; std::vector pollFds_; - // std::vector clientInputBuffers_; std::map sockIODataStore_; Logger logger_; - // Server(Server const &other); - // Server &operator=(Server const &other); - + void setupServerSockets_(); int createServerSocket_(int const domain); void processServerSocketData_(); void acceptClient_(int const serverFd); + void processClientSockets_(); void storePollFd_(int const socketFd); - void processClientSocketData_(); - void closeSocket_(int const fd); + bool checkSocket_(struct pollfd &pfd, SocketIOData &sIOD); + bool processClientSocketData_(struct pollfd &pfd, SocketIOData &sIOD); + void setClientInfo_(int fd, Client const **clntPtr, std::string &nick, + bool &isReg); + bool clientDataSanityCheck_(SocketIOData &sIOD, int fd, + /* Client const *const client, */ + std::string const &nickName, + bool const isRegistered); + void closeSocket_(int const fd); void log_(std::string const &msg, Logger::logMode const lm); + Server(Server const &other); + Server &operator=(Server const &other); + public: Server(int const port, std::string const &password); - // ~Server(); + ~Server(); - std::string const &serverName() { + std::string const &serverName() const { return serverName_; }; diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 998096c..720268b 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -10,13 +10,25 @@ #include /* - Server Constructor - * set port and password from command line args + Server Constructor + * set port and password from command line args + * init the logger prefix (server name) */ Server::Server(int const port, std::string const &password) : port_(port), password_(password), serverName_(SERVER_NAME), numServerSockets_(0), logger_(serverName_ + ": ") {} +Server::~Server() { + while (!pollFds_.empty()) + closeSocket_(pollFds_.back().fd); +} + +/* + sendMsg + * send string msg to a network socket + * uses i/o buffers and error counters in SocketIOData + * closes the socket (== disconnects the client) in case of too many errors +*/ bool Server::sendMsg(int const fd, std::string const &msg) { SocketIOData &sIOD = sockIODataStore_.at(fd); ssize_t bytesSent = -1; @@ -55,8 +67,7 @@ bool Server::sendMsg(int const fd, std::string const &msg) { if (!success) return false; - if (sIOD.sendErrorCount > 0) - sIOD.sendErrorCount = 0; + sIOD.sendErrorCount = 0; if (static_cast(bytesSent) < sIOD.writeBuffer.length()) sIOD.writeBuffer.erase(0, bytesSent); else @@ -64,8 +75,28 @@ bool Server::sendMsg(int const fd, std::string const &msg) { return true; } +void Server::setupServerSockets_() { + int domArr[] = {AF_INET, AF_INET6}; + std::vector domains(domArr, // NOLINT + domArr + // NOLINT + (sizeof(domArr) / sizeof(domArr[0]))); + for (size_t idx = 0; idx < domains.size(); ++idx) { + try { + createServerSocket_(domains[idx]); + } catch (std::exception const &e) { + log_(e.what(), Logger::lmExcept); + } + } + if (numServerSockets_ == 0) + throw std::runtime_error("No server socket could be setup correctly."); +} + /* - tbd + createServerSocket_ + * Setup a listening network socket for the address family/domain specified + * only supports IPv4 and IPv6 + * for IPv6, uses and IPv6only socket, so the IPv4 is kept separate + (not all systems support dual stack handling of IPv4 through IPv6) */ int Server::createServerSocket_(int const domain) { if (domain != AF_INET && domain != AF_INET6) @@ -104,7 +135,9 @@ int Server::createServerSocket_(int const domain) { } void Server::processServerSocketData_() { - for (size_t idx = 0; idx < numServerSockets_; ++idx) { + for (size_t idx = 0; + !SignalHandler::stopLoopSignalReceived() && idx < numServerSockets_; + ++idx) { struct pollfd const &serverPollFds = pollFds_[idx]; if ((serverPollFds.revents & POLLERR)) throw std::runtime_error("Server socket error."); @@ -113,92 +146,118 @@ void Server::processServerSocketData_() { } } -void Server::processClientSocketData_() { - for (size_t idx = numServerSockets_; idx < pollFds_.size(); ++idx) { +bool Server::checkSocket_(struct pollfd &pfd, SocketIOData &sIOD) { + if ((pfd.revents & POLLERR)) { + if (sIOD.recvErrorCount >= SOCKETERRORLIMIT) + closeSocket_(pfd.fd); + else + ++sIOD.recvErrorCount; + return false; + } + return true; +} + +void Server::setClientInfo_(int fd, Client const **clntPtr, std::string &nick, + bool &isReg) { + try { + *clntPtr = clients_.at(fd); + if (*clntPtr && (*clntPtr)->isRegistered()) { + isReg = true; + nick = (*clntPtr)->getNick(); + } + } catch (std::exception const &e) { + log_(e.what(), Logger::lmExcept); + } +} + +bool Server::clientDataSanityCheck_(SocketIOData &sIOD, int fd, /* + Client const *const client, */ + std::string const &nickName, + bool const isRegistered) { + std::string &rdBuffer = sIOD.readBuffer; + size_t lfcrPos = rdBuffer.find("\r\n"); + if ((rdBuffer.length() >= MAXMSGLEN) && + (lfcrPos == std::string::npos || lfcrPos > MAXMSGLEN - 2)) { + sIOD.discardMode = true; + ++sIOD.recvErrorCount; + sendMsg(fd, Reply::build(serverName(), nickName, + Reply::reply(Reply::ERR_INPUTTOOLONG, ""))); + } + if (sIOD.discardMode) { + if (!isRegistered || sIOD.recvErrorCount >= SOCKETERRORLIMIT) { + closeSocket_(fd); + return false; + } + if (lfcrPos != std::string::npos) { + rdBuffer.erase(0, lfcrPos + 2); + sIOD.discardMode = false; + lfcrPos = rdBuffer.find("\r\n"); + } else if ('\r' == *rdBuffer.rbegin()) + rdBuffer = "\r"; + else + rdBuffer.clear(); + } + if (lfcrPos == std::string::npos || sIOD.discardMode) { + return false; + } + return true; +} + +bool Server::processClientSocketData_(struct pollfd &pfd, SocketIOData &sIOD) { + Client const *client = 0; + std::string nickName = "*"; + bool clntIsReg = false; + setClientInfo_(pfd.fd, &client, nickName, clntIsReg); + + if (!clientDataSanityCheck_(sIOD, pfd.fd, nickName, clntIsReg)) + return false; + + sIOD.recvErrorCount = 0; + size_t lfcrPos = sIOD.readBuffer.find("\r\n"); + std::string cmdLine = sIOD.readBuffer.substr(0, lfcrPos); + sIOD.readBuffer = sIOD.readBuffer.substr(lfcrPos + 2); + +#if NETCATDBG + { + // size_t const clientNumber = idx + 1 - numServerSockets_; + std::ostringstream oss; + oss << "You are client '" << nickName << "' (fd " << pfd.fd << "): \n" + << "Srv received: " << std::string(cmdLine) << std::endl; + sendMsg(pfd.fd, oss.str()); + } +#endif + + try { + cmds_.run(*this, *clients_.at(pfd.fd), cmdLine); + } catch (Error const &e) { + if (e.hasReply()) { + sendMsg(pfd.fd, Reply::build(serverName(), nickName, e.reply())); + } else { + std::string tmp("Error while trying to run cmd:\n"); + tmp.append(cmdLine + "\n" + e.what()); + log_(tmp, Logger::lmExcept); + } + } + return true; +} + +void Server::processClientSockets_() { + for (size_t idx = numServerSockets_; + !SignalHandler::stopLoopSignalReceived() && idx < pollFds_.size(); + ++idx) { pollfd &pfd = pollFds_.at(idx); SocketIOData &sIOD = sockIODataStore_.at(pfd.fd); - if ((pfd.revents & POLLERR)) { - if (sIOD.recvErrorCount >= SOCKETERRORLIMIT) { - closeSocket_(pfd.fd); - } else - ++sIOD.recvErrorCount; - - } else if (pfd.revents & POLLIN) { - char recvBuffer[RECVBUFFLEN]; - ssize_t bytesRead = recv(pfd.fd, &recvBuffer[0], RECVBUFFLEN, 0); - - if (bytesRead > 0) { - SocketIOData &sockIOData = sockIODataStore_.at(pfd.fd); - std::string nickName = "*"; - Client const *client = 0; - try { - client = clients_.at(pfd.fd); - if (client && client->isRegistered()) - nickName = client->getNick(); - } catch (std::exception const &e) { - log_(e.what(), Logger::lmExcept); - } - - std::string &userInput = sockIOData.readBuffer; - userInput += std::string(&recvBuffer[0], bytesRead); - - size_t lfcrPos = userInput.find("\r\n"); - if ((userInput.length() >= MAXMSGLEN) && - (lfcrPos == std::string::npos || lfcrPos > MAXMSGLEN - 2)) { - sockIOData.discardMode = true; - ++sIOD.recvErrorCount; - sendMsg(pfd.fd, - Reply::build( - serverName(), nickName, - Reply::reply(Reply::ERR_INPUTTOOLONG, ""))); - } - if (sockIOData.discardMode) { - if (!client || !client->isRegistered() || - sIOD.recvErrorCount >= SOCKETERRORLIMIT) { - closeSocket_(pfd.fd); - continue; - } - if (lfcrPos != std::string::npos) { - userInput.erase(0, lfcrPos + 2); - sockIOData.discardMode = false; - lfcrPos = userInput.find("\r\n"); - } else if ('\r' == *userInput.rbegin()) - userInput = "\r"; - else - userInput.clear(); - } - if (lfcrPos == std::string::npos || sockIOData.discardMode) { - continue; - } - sIOD.recvErrorCount = 0; - userInput = userInput.substr(0, userInput.length() - 2); -#if 1 // For netcat debug - { - size_t const clientNumber = idx + 1 - numServerSockets_; - std::ostringstream oss; - oss << "You are client #" << clientNumber << ": \n" - << "Srv received: " << std::string(userInput) - << std::endl; - sendMsg(pfd.fd, oss.str()); - } -#endif - try { - cmds_.run(*this, *clients_.at(pfd.fd), userInput); - } catch (Error const &e) { - if (e.hasReply()) { - sendMsg(pfd.fd, Reply::build(serverName(), nickName, - e.reply())); - } else { - std::string tmp("Error while trying to run cmd:\n"); - tmp.append(userInput + "\n"); - tmp.append(e.what()); - log_(tmp.c_str(), Logger::lmExcept); - } - } - userInput.clear(); - - } else if (bytesRead <= 0) { + if (!checkSocket_(pfd, sIOD)) + continue; + if (pfd.revents & POLLIN) { + char recvBuffer[RECVBUFFLEN]; + sIOD.bytesRead = recv(pfd.fd, &recvBuffer[0], RECVBUFFLEN, 0); + + if (sIOD.bytesRead > 0) { + sIOD.readBuffer += std::string(&recvBuffer[0], sIOD.bytesRead); + processClientSocketData_(pfd, sIOD); + } else if (sIOD.bytesRead <= 0) { closeSocket_(pfd.fd); } } @@ -257,10 +316,12 @@ void Server::closeSocket_(int const fd) { std::ostringstream oss; if (idx < numServerSockets_) { - if (pollFds_.size() != idx + 1) - return log_("Internal error: Close client sockets before " - "closing server!", - Logger::lmError); + if (pollFds_.size() != idx + 1) { + log_("Internal error: Close client sockets before " + "closing server!", + Logger::lmError); + return; + } oss << "Closing server socket (" << srvHelp::getAdressFamilyStr(srvHelp::sockAddrFamily(fd)) << ")"; } else { @@ -291,18 +352,16 @@ void Server::log_(std::string const &msg, Logger::logMode const lm) { the server socket(s) */ void Server::run() { - createServerSocket_(AF_INET); - createServerSocket_(AF_INET6); + setupServerSockets_(); while (!SignalHandler::stopLoopSignalReceived()) { int pollResult = poll(pollFds_.data(), pollFds_.size(), -1); - if (pollResult == -1) - throw std::runtime_error("Fatal error: poll failed."); + if (pollResult == -1) { + log_("Fatal error: poll failed.", Logger::lmError); + break; + } processServerSocketData_(); - processClientSocketData_(); - } - while (!pollFds_.empty()) { - closeSocket_(pollFds_.back().fd); + processClientSockets_(); } } From 8a4a8eb0986cdee626e74457dcae898cb0d4a04e Mon Sep 17 00:00:00 2001 From: Rapha Date: Sat, 29 Aug 2026 14:31:07 +0200 Subject: [PATCH 145/349] rfa: tidy && format --- includes/Server.hpp | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/includes/Server.hpp b/includes/Server.hpp index 68b61fe..de6d0ad 100644 --- a/includes/Server.hpp +++ b/includes/Server.hpp @@ -53,16 +53,15 @@ class Server { void acceptClient_(int const serverFd); void processClientSockets_(); void storePollFd_(int const socketFd); - bool checkSocket_(struct pollfd &pfd, SocketIOData &sIOD); - bool processClientSocketData_(struct pollfd &pfd, SocketIOData &sIOD); - void setClientInfo_(int fd, Client const **clntPtr, std::string &nick, - bool &isReg); - bool clientDataSanityCheck_(SocketIOData &sIOD, int fd, - /* Client const *const client, */ - std::string const &nickName, - bool const isRegistered); - void closeSocket_(int const fd); - void log_(std::string const &msg, Logger::logMode const lm); + bool checkSocket_(struct pollfd &pfd, SocketIOData &sIOD); + bool processClientSocketData_(struct pollfd &pfd, SocketIOData &sIOD); + void setClientInfo_(int fd, Client const **clntPtr, std::string &nick, + bool &isReg); + size_t clientDataSanityCheck_(SocketIOData &sIOD, int fd, + std::string const &nickName, + bool const isRegistered); + void closeSocket_(int const fd); + void log_(std::string const &msg, Logger::logMode const lm); Server(Server const &other); Server &operator=(Server const &other); From d6549935b007479e3c3aa264a4f56b9412c479b9 Mon Sep 17 00:00:00 2001 From: Rapha Date: Sat, 29 Aug 2026 14:31:46 +0200 Subject: [PATCH 146/349] rfa: avoid recalculation & simplify split function chain --- srcs/server/Server.cpp | 37 +++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 720268b..a9f806a 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -170,10 +170,9 @@ void Server::setClientInfo_(int fd, Client const **clntPtr, std::string &nick, } } -bool Server::clientDataSanityCheck_(SocketIOData &sIOD, int fd, /* - Client const *const client, */ - std::string const &nickName, - bool const isRegistered) { +size_t Server::clientDataSanityCheck_(SocketIOData &sIOD, int fd, + std::string const &nickName, + bool const isRegistered) { std::string &rdBuffer = sIOD.readBuffer; size_t lfcrPos = rdBuffer.find("\r\n"); if ((rdBuffer.length() >= MAXMSGLEN) && @@ -186,21 +185,20 @@ bool Server::clientDataSanityCheck_(SocketIOData &sIOD, int fd, /* if (sIOD.discardMode) { if (!isRegistered || sIOD.recvErrorCount >= SOCKETERRORLIMIT) { closeSocket_(fd); - return false; + return std::string::npos; } if (lfcrPos != std::string::npos) { rdBuffer.erase(0, lfcrPos + 2); sIOD.discardMode = false; lfcrPos = rdBuffer.find("\r\n"); - } else if ('\r' == *rdBuffer.rbegin()) - rdBuffer = "\r"; - else - rdBuffer.clear(); - } - if (lfcrPos == std::string::npos || sIOD.discardMode) { - return false; + } else /* lfcrPos == std::string::npos */ { + if ('\r' == *rdBuffer.rbegin()) + rdBuffer = "\r"; + else + rdBuffer.clear(); + } } - return true; + return lfcrPos; } bool Server::processClientSocketData_(struct pollfd &pfd, SocketIOData &sIOD) { @@ -209,24 +207,23 @@ bool Server::processClientSocketData_(struct pollfd &pfd, SocketIOData &sIOD) { bool clntIsReg = false; setClientInfo_(pfd.fd, &client, nickName, clntIsReg); - if (!clientDataSanityCheck_(sIOD, pfd.fd, nickName, clntIsReg)) + size_t lfcrPos = clientDataSanityCheck_(sIOD, pfd.fd, nickName, clntIsReg); + if (lfcrPos == std::string::npos) return false; - sIOD.recvErrorCount = 0; - size_t lfcrPos = sIOD.readBuffer.find("\r\n"); - std::string cmdLine = sIOD.readBuffer.substr(0, lfcrPos); - sIOD.readBuffer = sIOD.readBuffer.substr(lfcrPos + 2); - #if NETCATDBG { // size_t const clientNumber = idx + 1 - numServerSockets_; std::ostringstream oss; oss << "You are client '" << nickName << "' (fd " << pfd.fd << "): \n" - << "Srv received: " << std::string(cmdLine) << std::endl; + << "Srv received: " << std::string(sIOD.readBuffer) << std::endl; sendMsg(pfd.fd, oss.str()); } #endif + sIOD.recvErrorCount = 0; + std::string cmdLine = sIOD.readBuffer.substr(0, lfcrPos); + sIOD.readBuffer = sIOD.readBuffer.substr(lfcrPos + 2); try { cmds_.run(*this, *clients_.at(pfd.fd), cmdLine); } catch (Error const &e) { From 2cfbc97264f9e6b5c0db6147587acff6416713aa Mon Sep 17 00:00:00 2001 From: Rapha Date: Sat, 29 Aug 2026 14:35:57 +0200 Subject: [PATCH 147/349] feat: log stop signal msg to std::cerr --- srcs/server/SignalHandler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/srcs/server/SignalHandler.cpp b/srcs/server/SignalHandler.cpp index 42c4263..c443815 100644 --- a/srcs/server/SignalHandler.cpp +++ b/srcs/server/SignalHandler.cpp @@ -21,7 +21,7 @@ volatile std::sig_atomic_t SignalHandler::stopServerLoop_ = 0; */ void SignalHandler::handleStopSignals_(int signal) { stopServerLoop_ = signal; - std::cout << "Caught stop signal: " << stopServerLoop_ << "." << std::endl; + std::cerr << "Caught stop signal: " << stopServerLoop_ << "." << std::endl; } /* From 301fe77e0fb0a35ace1e675e7942eb7fe0fcf632 Mon Sep 17 00:00:00 2001 From: Rapha Date: Mon, 31 Aug 2026 12:30:42 +0200 Subject: [PATCH 148/349] rfa: rename function to composeAddrfamAddrPortStr to better describe its purpose --- includes/ServerHelper.hpp | 6 +++--- srcs/server/Server.cpp | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/includes/ServerHelper.hpp b/includes/ServerHelper.hpp index ee9b543..432551c 100644 --- a/includes/ServerHelper.hpp +++ b/includes/ServerHelper.hpp @@ -72,13 +72,13 @@ inline void bindServerSocket(int const socketFd, int const domain, // NOLINT getAdressFamilyStr(domain) + " !"); } -inline std::string printAddrfamilyAddrPort(socketAddrX const &sockAddrIn46) { +inline std::string composeAddrfamAddrPortStr(socketAddrX const &sockAddrIn46) { char addrBuffer[128]; socklen_t const addrBuffLen = sizeof(addrBuffer) / sizeof(char); sa_family_t const saFamily = - reinterpret_cast(sockAddrIn46.sai4) // NOLINT - .sa_family; + // NOLINTNEXTLINE + reinterpret_cast(sockAddrIn46.sai4).sa_family; void const *addrPtr = 0; uint16_t portNumber = 0; diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index a9f806a..c112cff 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -276,7 +276,7 @@ void Server::acceptClient_(int const serverFd) { &sockLen); if (clientFd == -1) { log_("Could not accept client:\n" + - srvHelp::printAddrfamilyAddrPort(sockAddrIn46), + srvHelp::composeAddrfamAddrPortStr(sockAddrIn46), Logger::lmWarn); return; } @@ -287,7 +287,7 @@ void Server::acceptClient_(int const serverFd) { std::cout << "Client connected:\n" << " fd: " << clientFd << "\n" - << srvHelp::printAddrfamilyAddrPort(sockAddrIn46) << std::endl; + << srvHelp::composeAddrfamAddrPortStr(sockAddrIn46) << std::endl; } void Server::storePollFd_(int const socketFd) { From 9232d7606f39e736e766e045447ca011a6b03c3c Mon Sep 17 00:00:00 2001 From: Rapha Date: Mon, 31 Aug 2026 13:54:20 +0200 Subject: [PATCH 149/349] rfa: update clang format rules --- .clang-format | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.clang-format b/.clang-format index 45b8729..449ff95 100644 --- a/.clang-format +++ b/.clang-format @@ -7,7 +7,7 @@ IndentWidth: 4 ColumnLimit: 80 IndentCaseLabels: true AllowShortFunctionsOnASingleLine: Empty -AlignConsecutiveDeclarations: AcrossEmptyLinesAndComments +AlignConsecutiveDeclarations: true SeparateDefinitionBlocks: Always ReflowComments: false From 7a42144b81adb20e6c17b8760be490457869c6ec Mon Sep 17 00:00:00 2001 From: Rapha Date: Mon, 31 Aug 2026 13:56:04 +0200 Subject: [PATCH 150/349] rfa: clang-format --- includes/Channel.hpp | 4 ++-- includes/ChannelManager.hpp | 16 ++++++++-------- includes/Client.hpp | 28 ++++++++++++++-------------- includes/ClientManager.hpp | 6 +++--- includes/Logger.hpp | 2 +- includes/MemberSet.hpp | 8 ++++---- includes/Mode.hpp | 4 ++-- includes/ModeSet.hpp | 8 ++++---- includes/ServerHelper.hpp | 4 ++-- includes/SignalHandler.hpp | 4 ++-- srcs/main.cpp | 2 +- srcs/server/SignalHandler.cpp | 4 ++-- 12 files changed, 45 insertions(+), 45 deletions(-) diff --git a/includes/Channel.hpp b/includes/Channel.hpp index 3ef1aaf..e43cb29 100644 --- a/includes/Channel.hpp +++ b/includes/Channel.hpp @@ -38,8 +38,8 @@ class Channel { Channel(const std::string &name, const std::string &nameCaseFolded, const std::string &key = ""); - void join(Client *client, const std::string &key = ""); - void part(Client *client); + void join(Client *client, const std::string &key = ""); + void part(Client *client); size_t size() const; const std::string &getNameCaseFolded() const; diff --git a/includes/ChannelManager.hpp b/includes/ChannelManager.hpp index 19502f0..5da20f9 100644 --- a/includes/ChannelManager.hpp +++ b/includes/ChannelManager.hpp @@ -17,14 +17,14 @@ class ChannelManager { Channel &at(const std::string &name); - bool hasChannel(const std::string &name) const; - bool isMember(Client *client, const std::string &name) const; + bool hasChannel(const std::string &name) const; + bool isMember(Client *client, const std::string &name) const; - void join(Client *client, const std::string &name, - const std::string &key = ""); - void part(Client *client, const std::string &name); - void removeChannel(const std::string &name); + void join(Client *client, const std::string &name, + const std::string &key = ""); + void part(Client *client, const std::string &name); + void removeChannel(const std::string &name); - void broadcast(const std::string &name, const std::string &msg, - const Client *skip = 0); + void broadcast(const std::string &name, const std::string &msg, + const Client *skip = 0); }; diff --git a/includes/Client.hpp b/includes/Client.hpp index c1b1cdd..0e31524 100644 --- a/includes/Client.hpp +++ b/includes/Client.hpp @@ -31,9 +31,9 @@ class Client { std::string getHostname() const; std::string getRealname() const; - bool isRegistered() const; - bool isOperator() const; - bool hasPassAccepted() const; + bool isRegistered() const; + bool isOperator() const; + bool hasPassAccepted() const; std::string getPrefix() const; bool hasIdentity() const; @@ -41,21 +41,21 @@ class Client { void setUser(const std::string &user, const std::string &real); void setHostname(const std::string &host); - void setRegistered(bool value); - void setPassAccepted(bool value); - void setOperator(bool value); + void setRegistered(bool value); + void setPassAccepted(bool value); + void setOperator(bool value); - bool isInvitedTo(const std::string &name) const; - void addInvite(const std::string &name); - void removeInvite(const std::string &name); + bool isInvitedTo(const std::string &name) const; + void addInvite(const std::string &name); + void removeInvite(const std::string &name); - void send(const std::string &msg); - void appendToRecvBuffer(const std::string &bytes); - bool extractLine(std::string &line); - std::size_t recvBufferSize() const; + void send(const std::string &msg); + void appendToRecvBuffer(const std::string &bytes); + bool extractLine(std::string &line); + std::size_t recvBufferSize() const; const std::string &getSendBuffer() const; void consumeSendBuffer(std::size_t count); void clearSendBuffer(); - bool hasPendingOutput() const; + bool hasPendingOutput() const; }; diff --git a/includes/ClientManager.hpp b/includes/ClientManager.hpp index eede800..ea0c643 100644 --- a/includes/ClientManager.hpp +++ b/includes/ClientManager.hpp @@ -29,7 +29,7 @@ class ClientManager { Client *at(const std::string &name); Client *at(int fd); - bool hasClient(const std::string &name) const; - bool add(Client *client, int fd); - void remove(int fd); + bool hasClient(const std::string &name) const; + bool add(Client *client, int fd); + void remove(int fd); }; diff --git a/includes/Logger.hpp b/includes/Logger.hpp index 145c3e6..5be496b 100644 --- a/includes/Logger.hpp +++ b/includes/Logger.hpp @@ -8,7 +8,7 @@ #include class Logger { - std::string prefix_; + std::string prefix_; static std::string nowStr_() { time_t now = time(0); diff --git a/includes/MemberSet.hpp b/includes/MemberSet.hpp index c82e2a1..b91d42f 100644 --- a/includes/MemberSet.hpp +++ b/includes/MemberSet.hpp @@ -19,13 +19,13 @@ class MemberSet { MemberSet(Client *client); MemberSet(Client *client, const ModeSet &modes); - void add(Client *client, const ModeSet &modes); - void remove(Client *client); - void setModes(Client *client, const ModeSet &modes); + void add(Client *client, const ModeSet &modes); + void remove(Client *client); + void setModes(Client *client, const ModeSet &modes); size_t size() const; bool has(Client *client) const; bool hasAtLeast(Client *client, const ModeSet &modes) const; - void send(const std::string &msg, const Client *skip = 0) const; + void send(const std::string &msg, const Client *skip = 0) const; }; diff --git a/includes/Mode.hpp b/includes/Mode.hpp index 38dfdae..a164f0f 100644 --- a/includes/Mode.hpp +++ b/includes/Mode.hpp @@ -36,8 +36,8 @@ enum UserMode { // ChannelUserModes holds the list of all user modes that can be applied to a // user in a channel in descending order of precedence -static const int channelUserModes[] = {ChannelFounder, ChannelAdmin, - ChannelOperator, Halfop, Voice}; +static const int channelUserModes[] = {ChannelFounder, ChannelAdmin, + ChannelOperator, Halfop, Voice}; inline std::string channelModePrefix(int m) { switch (m) { diff --git a/includes/ModeSet.hpp b/includes/ModeSet.hpp index 01a797f..cd4e4cf 100644 --- a/includes/ModeSet.hpp +++ b/includes/ModeSet.hpp @@ -15,7 +15,7 @@ * Subtract 65 from the mode and use that bit of the uint32_t to represent it. */ class ModeSet { - uint32_t modes_[2]; + uint32_t modes_[2]; static void indexAndMask_(int mode, uint32_t &index, uint32_t &mask) { uint32_t position = (uint32_t)mode - MIN_MODE; @@ -27,10 +27,10 @@ class ModeSet { public: ModeSet(); - bool has(int mode) const; - bool hasAtLeast(const ModeSet &modes) const; + bool has(int mode) const; + bool hasAtLeast(const ModeSet &modes) const; - bool set(int mode, bool on); + bool set(int mode, bool on); ModeSet &operator+=(const std::string &modes); ModeSet &operator-=(const std::string &modes); diff --git a/includes/ServerHelper.hpp b/includes/ServerHelper.hpp index 432551c..8f1f507 100644 --- a/includes/ServerHelper.hpp +++ b/includes/ServerHelper.hpp @@ -73,8 +73,8 @@ inline void bindServerSocket(int const socketFd, int const domain, // NOLINT } inline std::string composeAddrfamAddrPortStr(socketAddrX const &sockAddrIn46) { - char addrBuffer[128]; - socklen_t const addrBuffLen = sizeof(addrBuffer) / sizeof(char); + char addrBuffer[128]; + socklen_t const addrBuffLen = sizeof(addrBuffer) / sizeof(char); sa_family_t const saFamily = // NOLINTNEXTLINE diff --git a/includes/SignalHandler.hpp b/includes/SignalHandler.hpp index ca264ae..ba1940d 100644 --- a/includes/SignalHandler.hpp +++ b/includes/SignalHandler.hpp @@ -10,8 +10,8 @@ class SignalHandler { volatile static std::sig_atomic_t stopServerLoop_; static void handleStopSignals_(int signal); - std::vector stopSignals_; - bool setStopSignals_(sighandler_t handler); + std::vector stopSignals_; + bool setStopSignals_(sighandler_t handler); SignalHandler(SignalHandler const &other); SignalHandler &operator=(SignalHandler const &other); diff --git a/srcs/main.cpp b/srcs/main.cpp index 075c541..5991465 100644 --- a/srcs/main.cpp +++ b/srcs/main.cpp @@ -46,7 +46,7 @@ static bool checkExtractCmdLnArgs(int const argc, char *const argv[], int &port, equivalent) */ -int main(int argc, char **argv) { +int main(int argc, char **argv) { int port = 0; std::string password; diff --git a/srcs/server/SignalHandler.cpp b/srcs/server/SignalHandler.cpp index c443815..9a4e04f 100644 --- a/srcs/server/SignalHandler.cpp +++ b/srcs/server/SignalHandler.cpp @@ -19,7 +19,7 @@ volatile std::sig_atomic_t SignalHandler::stopServerLoop_ = 0; Will be refactored in a separate class, and with a more explicit stop state request function */ -void SignalHandler::handleStopSignals_(int signal) { +void SignalHandler::handleStopSignals_(int signal) { stopServerLoop_ = signal; std::cerr << "Caught stop signal: " << stopServerLoop_ << "." << std::endl; } @@ -30,7 +30,7 @@ void SignalHandler::handleStopSignals_(int signal) { portable POSIX compliant way to do it. */ bool SignalHandler::setStopSignals_(sighandler_t handler) { - bool success = true; + bool success = true; struct sigaction sigact = {}; sigact.sa_handler = handler; From a081a94e16c610e01e937843c7ec4cec184913c8 Mon Sep 17 00:00:00 2001 From: Rapha Date: Mon, 31 Aug 2026 13:57:13 +0200 Subject: [PATCH 151/349] rfa: put socket related functionaliy in its own class --- Makefile | 1 + includes/Server.hpp | 73 +++++++------------ includes/Socket.hpp | 45 ++++++++++++ srcs/server/Server.cpp | 160 +++++------------------------------------ srcs/server/Socket.cpp | 136 +++++++++++++++++++++++++++++++++++ 5 files changed, 226 insertions(+), 189 deletions(-) create mode 100644 includes/Socket.hpp create mode 100644 srcs/server/Socket.cpp diff --git a/Makefile b/Makefile index c6bde47..f457474 100644 --- a/Makefile +++ b/Makefile @@ -13,6 +13,7 @@ DEBUG_BUILD_DIR = build_debug SRCS_ROOT = main SRCS_SERVER = Server \ + Socket \ Client \ ClientManager \ Channel \ diff --git a/includes/Server.hpp b/includes/Server.hpp index de6d0ad..5f1e768 100644 --- a/includes/Server.hpp +++ b/includes/Server.hpp @@ -4,6 +4,7 @@ #include "ClientManager.hpp" #include "CommandProcessor.hpp" #include "Logger.hpp" +#include "Socket.hpp" #include #include @@ -12,56 +13,35 @@ #define SERVER_NAME "irc.local" #define MAX_CONNECTIONS 512 -#define RECVBUFFLEN 1024 -#define MAXMSGLEN 512 -#define SOCKETERRORLIMIT 8 - #define NETCATDBG 1 // For netcat debug class Server { private: - struct SocketIOData { - std::string readBuffer; - std::string writeBuffer; - ssize_t bytesRead; - size_t recvErrorCount; - size_t sendErrorCount; - bool discardMode; - - SocketIOData() - : bytesRead(0), recvErrorCount(0), sendErrorCount(0), - discardMode(false) {} - }; - - ChannelManager channels_; - ClientManager clients_; - CommandProcessor cmds_; - - int port_; - std::string password_; - std::string serverName_; - - size_t numServerSockets_; - std::vector pollFds_; - std::map sockIODataStore_; - - Logger logger_; - - void setupServerSockets_(); - int createServerSocket_(int const domain); - void processServerSocketData_(); - void acceptClient_(int const serverFd); - void processClientSockets_(); - void storePollFd_(int const socketFd); - bool checkSocket_(struct pollfd &pfd, SocketIOData &sIOD); - bool processClientSocketData_(struct pollfd &pfd, SocketIOData &sIOD); - void setClientInfo_(int fd, Client const **clntPtr, std::string &nick, - bool &isReg); - size_t clientDataSanityCheck_(SocketIOData &sIOD, int fd, - std::string const &nickName, - bool const isRegistered); - void closeSocket_(int const fd); - void log_(std::string const &msg, Logger::logMode const lm); + std::string serverName_; + int port_; + std::string password_; + + Logger logger_; + + Socket sockets_; + ChannelManager channels_; + ClientManager clients_; + CommandProcessor cmds_; + + size_t numServerSockets_; + std::vector pollFds_; + + void setupServerSockets_(); + int createServerSocket_(int const domain); + void processServerSocketData_(); + void acceptClient_(int const serverFd); + void processClientSockets_(); + void storePollFd_(int const socketFd); + bool processClientSocketData_(struct pollfd &pfd, + Socket::SocketIOData &sIOD); + void setClientInfo_(int fd, Client const **clntPtr, std::string &nick, + bool &isReg); + void log_(std::string const &msg, Logger::logMode const lm); Server(Server const &other); Server &operator=(Server const &other); @@ -76,7 +56,6 @@ class Server { }; void run(); - bool sendMsg(int const fd, std::string const &msg); bool tryRegister(Client &client) { (void)client; diff --git a/includes/Socket.hpp b/includes/Socket.hpp new file mode 100644 index 0000000..94f550a --- /dev/null +++ b/includes/Socket.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include "Logger.hpp" + +#include +#include +#include + +#define RECVBUFFLEN 1024 +#define MAXMSGLEN 512 +#define SOCKETERRORLIMIT 8 + +class Socket { + public: + struct SocketIOData { + std::string readBuffer; + std::string writeBuffer; + ssize_t bytesRead; + size_t recvErrorCount; + size_t sendErrorCount; + bool discardMode; + bool isSrvSocket; + + SocketIOData() + : bytesRead(0), recvErrorCount(0), sendErrorCount(0), + discardMode(false) {} + }; + + private: + std::map sockIODataStore_; + Logger const &logger_; + + public: + Socket(Logger const &logger) : logger_(logger) {} + + SocketIOData &getSocketIOData_(int const fd); + size_t clientDataSanityCheck_(Socket::SocketIOData &sIOD, int fd, + std::string const &serverName, + std::string const &nickName, + bool const isRegistered); + void addSocket(int const fd); + bool checkSocket_(struct pollfd &pfd); + bool sendMsg(int const fd, std::string const &msg); + void closeSocket_(int const fd); +}; diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index c112cff..b9811bf 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -15,64 +16,14 @@ * init the logger prefix (server name) */ Server::Server(int const port, std::string const &password) - : port_(port), password_(password), serverName_(SERVER_NAME), - numServerSockets_(0), logger_(serverName_ + ": ") {} + : serverName_(SERVER_NAME), port_(port), password_(password), + logger_(serverName_ + ": "), sockets_(logger_), numServerSockets_(0) {} Server::~Server() { - while (!pollFds_.empty()) - closeSocket_(pollFds_.back().fd); -} - -/* - sendMsg - * send string msg to a network socket - * uses i/o buffers and error counters in SocketIOData - * closes the socket (== disconnects the client) in case of too many errors -*/ -bool Server::sendMsg(int const fd, std::string const &msg) { - SocketIOData &sIOD = sockIODataStore_.at(fd); - ssize_t bytesSent = -1; - bool success = true; - - if (msg.length() > sIOD.writeBuffer.max_size() - sIOD.writeBuffer.length()) - sIOD.sendErrorCount = SOCKETERRORLIMIT; - else { - sIOD.writeBuffer += msg; - bytesSent = send(fd, sIOD.writeBuffer.c_str(), - sIOD.writeBuffer.length(), MSG_NOSIGNAL); - if (bytesSent <= 0) - ++sIOD.sendErrorCount; - } - if (sIOD.sendErrorCount >= SOCKETERRORLIMIT) { - closeSocket_(fd); - success = false; + while (!pollFds_.empty()) { + sockets_.closeSocket_(pollFds_.back().fd); + pollFds_.erase(--pollFds_.end()); } - - std::ostringstream oss; - oss << "SendMsg event:\n" - << " WriteBuffer: '" << sIOD.writeBuffer << "':\n"; - if (!success) { - oss << " send to FD " << fd << " failed."; - log_(oss.str(), Logger::lmError); - } else { - if (static_cast(bytesSent) < sIOD.writeBuffer.length()) - oss << " partly (" << bytesSent << "/" << sIOD.writeBuffer.length() - << ") " << sIOD.writeBuffer.substr(0, bytesSent); - else - oss << " successfully"; - oss << " sent to FD: " << fd; - log_(oss.str(), Logger::lmInfo); - } - - if (!success) - return false; - - sIOD.sendErrorCount = 0; - if (static_cast(bytesSent) < sIOD.writeBuffer.length()) - sIOD.writeBuffer.erase(0, bytesSent); - else - sIOD.writeBuffer.clear(); - return true; } void Server::setupServerSockets_() { @@ -146,17 +97,6 @@ void Server::processServerSocketData_() { } } -bool Server::checkSocket_(struct pollfd &pfd, SocketIOData &sIOD) { - if ((pfd.revents & POLLERR)) { - if (sIOD.recvErrorCount >= SOCKETERRORLIMIT) - closeSocket_(pfd.fd); - else - ++sIOD.recvErrorCount; - return false; - } - return true; -} - void Server::setClientInfo_(int fd, Client const **clntPtr, std::string &nick, bool &isReg) { try { @@ -170,44 +110,15 @@ void Server::setClientInfo_(int fd, Client const **clntPtr, std::string &nick, } } -size_t Server::clientDataSanityCheck_(SocketIOData &sIOD, int fd, - std::string const &nickName, - bool const isRegistered) { - std::string &rdBuffer = sIOD.readBuffer; - size_t lfcrPos = rdBuffer.find("\r\n"); - if ((rdBuffer.length() >= MAXMSGLEN) && - (lfcrPos == std::string::npos || lfcrPos > MAXMSGLEN - 2)) { - sIOD.discardMode = true; - ++sIOD.recvErrorCount; - sendMsg(fd, Reply::build(serverName(), nickName, - Reply::reply(Reply::ERR_INPUTTOOLONG, ""))); - } - if (sIOD.discardMode) { - if (!isRegistered || sIOD.recvErrorCount >= SOCKETERRORLIMIT) { - closeSocket_(fd); - return std::string::npos; - } - if (lfcrPos != std::string::npos) { - rdBuffer.erase(0, lfcrPos + 2); - sIOD.discardMode = false; - lfcrPos = rdBuffer.find("\r\n"); - } else /* lfcrPos == std::string::npos */ { - if ('\r' == *rdBuffer.rbegin()) - rdBuffer = "\r"; - else - rdBuffer.clear(); - } - } - return lfcrPos; -} - -bool Server::processClientSocketData_(struct pollfd &pfd, SocketIOData &sIOD) { +bool Server::processClientSocketData_(struct pollfd &pfd, + Socket::SocketIOData &sIOD) { Client const *client = 0; std::string nickName = "*"; bool clntIsReg = false; setClientInfo_(pfd.fd, &client, nickName, clntIsReg); - size_t lfcrPos = clientDataSanityCheck_(sIOD, pfd.fd, nickName, clntIsReg); + size_t lfcrPos = sockets_.clientDataSanityCheck_(sIOD, pfd.fd, serverName(), + nickName, clntIsReg); if (lfcrPos == std::string::npos) return false; @@ -217,7 +128,7 @@ bool Server::processClientSocketData_(struct pollfd &pfd, SocketIOData &sIOD) { std::ostringstream oss; oss << "You are client '" << nickName << "' (fd " << pfd.fd << "): \n" << "Srv received: " << std::string(sIOD.readBuffer) << std::endl; - sendMsg(pfd.fd, oss.str()); + sockets_.sendMsg(pfd.fd, oss.str()); } #endif @@ -228,7 +139,8 @@ bool Server::processClientSocketData_(struct pollfd &pfd, SocketIOData &sIOD) { cmds_.run(*this, *clients_.at(pfd.fd), cmdLine); } catch (Error const &e) { if (e.hasReply()) { - sendMsg(pfd.fd, Reply::build(serverName(), nickName, e.reply())); + sockets_.sendMsg(pfd.fd, + Reply::build(serverName(), nickName, e.reply())); } else { std::string tmp("Error while trying to run cmd:\n"); tmp.append(cmdLine + "\n" + e.what()); @@ -242,10 +154,10 @@ void Server::processClientSockets_() { for (size_t idx = numServerSockets_; !SignalHandler::stopLoopSignalReceived() && idx < pollFds_.size(); ++idx) { - pollfd &pfd = pollFds_.at(idx); - SocketIOData &sIOD = sockIODataStore_.at(pfd.fd); + pollfd &pfd = pollFds_.at(idx); + Socket::SocketIOData &sIOD = sockets_.getSocketIOData_(pfd.fd); - if (!checkSocket_(pfd, sIOD)) + if (!sockets_.checkSocket_(pfd)) continue; if (pfd.revents & POLLIN) { char recvBuffer[RECVBUFFLEN]; @@ -255,7 +167,7 @@ void Server::processClientSockets_() { sIOD.readBuffer += std::string(&recvBuffer[0], sIOD.bytesRead); processClientSocketData_(pfd, sIOD); } else if (sIOD.bytesRead <= 0) { - closeSocket_(pfd.fd); + sockets_.closeSocket_(pfd.fd); } } } @@ -296,43 +208,7 @@ void Server::storePollFd_(int const socketFd) { pfd.events = POLLIN; // Monitor for incoming connections pollFds_.push_back(pfd); - sockIODataStore_.insert( - std::make_pair(socketFd, SocketIOData())); -} - -/* - tbd -*/ -void Server::closeSocket_(int const fd) { - size_t idx = 0; - for (idx = 0; idx < pollFds_.size(); ++idx) - if (fd == pollFds_.at(idx).fd) - break; - if (idx == pollFds_.size()) - return; - - std::ostringstream oss; - if (idx < numServerSockets_) { - if (pollFds_.size() != idx + 1) { - log_("Internal error: Close client sockets before " - "closing server!", - Logger::lmError); - return; - } - oss << "Closing server socket (" - << srvHelp::getAdressFamilyStr(srvHelp::sockAddrFamily(fd)) << ")"; - } else { - oss << "Closing client #" << idx + 1 - numServerSockets_ << ", sockFd " - << fd << "; (" << pollFds_.size() - 1 - numServerSockets_ - << " clients remaining)"; - } - log_(oss.str(), Logger::lmInfo); - - close(fd); - std::vector::difference_type dist = - static_cast::difference_type>(idx); - pollFds_.erase(pollFds_.begin() + dist); - sockIODataStore_.erase(fd); + sockets_.addSocket(socketFd); } void Server::log_(std::string const &msg, Logger::logMode const lm) { diff --git a/srcs/server/Socket.cpp b/srcs/server/Socket.cpp new file mode 100644 index 0000000..0da0511 --- /dev/null +++ b/srcs/server/Socket.cpp @@ -0,0 +1,136 @@ +#include "Socket.hpp" + +#include "Reply.hpp" +#include "ServerHelper.hpp" + +#include +#include +#include +#include + +void Socket::addSocket(int const fd) { + sockIODataStore_.insert( + std::make_pair(fd, Socket::SocketIOData())); +} + +bool Socket::checkSocket_(struct pollfd &pfd) { + SocketIOData &sIOD = sockIODataStore_.at(pfd.fd); + if ((pfd.revents & POLLERR)) { + if (sIOD.recvErrorCount >= SOCKETERRORLIMIT) + closeSocket_(pfd.fd); + else + ++sIOD.recvErrorCount; + return false; + } + return true; +} + +size_t Socket::clientDataSanityCheck_(Socket::SocketIOData &sIOD, int fd, + std::string const &serverName, + std::string const &nickName, + bool const isRegistered) { + std::string &rdBuffer = sIOD.readBuffer; + size_t lfcrPos = rdBuffer.find("\r\n"); + if ((rdBuffer.length() >= MAXMSGLEN) && + (lfcrPos == std::string::npos || lfcrPos > MAXMSGLEN - 2)) { + sIOD.discardMode = true; + ++sIOD.recvErrorCount; + sendMsg(fd, Reply::build(serverName, nickName, + Reply::reply(Reply::ERR_INPUTTOOLONG, ""))); + } + if (sIOD.discardMode) { + if (!isRegistered || sIOD.recvErrorCount >= SOCKETERRORLIMIT) { + closeSocket_(fd); + return std::string::npos; + } + if (lfcrPos != std::string::npos) { + rdBuffer.erase(0, lfcrPos + 2); + sIOD.discardMode = false; + lfcrPos = rdBuffer.find("\r\n"); + } else /* lfcrPos == std::string::npos */ { + if ('\r' == *rdBuffer.rbegin()) + rdBuffer = "\r"; + else + rdBuffer.clear(); + } + } + return lfcrPos; +} + +/* + sendMsg + * send string msg to a network socket + * uses i/o buffers and error counters in SocketIOData + * closes the socket (== disconnects the client) in case of too many errors +*/ +bool Socket::sendMsg(int const fd, std::string const &msg) { + SocketIOData &sIOD = sockIODataStore_.at(fd); + ssize_t bytesSent = -1; + bool success = true; + + if (msg.length() > sIOD.writeBuffer.max_size() - sIOD.writeBuffer.length()) + sIOD.sendErrorCount = SOCKETERRORLIMIT; + else { + sIOD.writeBuffer += msg; + bytesSent = send(fd, sIOD.writeBuffer.c_str(), + sIOD.writeBuffer.length(), MSG_NOSIGNAL); + if (bytesSent <= 0) + ++sIOD.sendErrorCount; + } + if (sIOD.sendErrorCount >= SOCKETERRORLIMIT) { + closeSocket_(fd); + success = false; + } + + std::ostringstream oss; + oss << "SendMsg event:\n" + << " WriteBuffer: '" << sIOD.writeBuffer << "':\n"; + if (!success) { + oss << " send to FD " << fd << " failed."; + logger_.logMsg(oss.str(), Logger::lmError); + } else { + if (static_cast(bytesSent) < sIOD.writeBuffer.length()) + oss << " partly (" << bytesSent << "/" << sIOD.writeBuffer.length() + << ") " << sIOD.writeBuffer.substr(0, bytesSent); + else + oss << " successfully"; + oss << " sent to FD: " << fd; + logger_.logMsg(oss.str(), Logger::lmInfo); + } + + if (!success) + return false; + + sIOD.sendErrorCount = 0; + if (static_cast(bytesSent) < sIOD.writeBuffer.length()) + sIOD.writeBuffer.erase(0, bytesSent); + else + sIOD.writeBuffer.clear(); + return true; +} + +Socket::SocketIOData &Socket::getSocketIOData_(int const fd) { + std::map::iterator sIODit = sockIODataStore_.find(fd); + if (sIODit == sockIODataStore_.end()) { + std::ostringstream oss; + oss << "No SocketIOData for fd " << fd; + throw std::out_of_range(oss.str()); + } + return sIODit->second; +} + +void Socket::closeSocket_(int const fd) { + SocketIOData &sIOD = getSocketIOData_(fd); + + std::ostringstream oss; + if (sIOD.isSrvSocket) { + oss << "Closing server socket (" + << srvHelp::getAdressFamilyStr(srvHelp::sockAddrFamily(fd)) << ")"; + } else { + oss << "Closing client with sockFd " << fd; + } + logger_.logMsg(oss.str(), Logger::lmInfo); + + close(fd); + sockIODataStore_.erase(fd); +} From 6b08786662d0bdd5f92604456dbc1e12bae0b965 Mon Sep 17 00:00:00 2001 From: Rapha Date: Mon, 31 Aug 2026 17:53:05 +0200 Subject: [PATCH 152/349] rfa: move more functionality to Socket class fix: fix cleanup after client socket close --- includes/Server.hpp | 12 +---- includes/Socket.hpp | 28 +++++++----- srcs/server/Server.cpp | 100 ++++++++++++++--------------------------- srcs/server/Socket.cpp | 68 +++++++++++++++++++--------- 4 files changed, 99 insertions(+), 109 deletions(-) diff --git a/includes/Server.hpp b/includes/Server.hpp index 5f1e768..ca7cb86 100644 --- a/includes/Server.hpp +++ b/includes/Server.hpp @@ -13,8 +13,6 @@ #define SERVER_NAME "irc.local" #define MAX_CONNECTIONS 512 -#define NETCATDBG 1 // For netcat debug - class Server { private: std::string serverName_; @@ -36,10 +34,8 @@ class Server { void processServerSocketData_(); void acceptClient_(int const serverFd); void processClientSockets_(); - void storePollFd_(int const socketFd); - bool processClientSocketData_(struct pollfd &pfd, - Socket::SocketIOData &sIOD); - void setClientInfo_(int fd, Client const **clntPtr, std::string &nick, + void storePollFd_(int const socketFd, bool srvSocket); + void setClientInfo_(int fd, Client **clntPtr, std::string &nick, bool &isReg); void log_(std::string const &msg, Logger::logMode const lm); @@ -51,10 +47,6 @@ class Server { ~Server(); - std::string const &serverName() const { - return serverName_; - }; - void run(); bool tryRegister(Client &client) { diff --git a/includes/Socket.hpp b/includes/Socket.hpp index 94f550a..e3f85ff 100644 --- a/includes/Socket.hpp +++ b/includes/Socket.hpp @@ -10,8 +10,11 @@ #define MAXMSGLEN 512 #define SOCKETERRORLIMIT 8 +#define NETCATDBG 1 // For netcat debug + class Socket { public: + private: struct SocketIOData { std::string readBuffer; std::string writeBuffer; @@ -20,10 +23,11 @@ class Socket { size_t sendErrorCount; bool discardMode; bool isSrvSocket; + bool fdClosed; - SocketIOData() + SocketIOData(bool srvSocket) : bytesRead(0), recvErrorCount(0), sendErrorCount(0), - discardMode(false) {} + discardMode(false), isSrvSocket(srvSocket), fdClosed(false) {} }; private: @@ -33,13 +37,15 @@ class Socket { public: Socket(Logger const &logger) : logger_(logger) {} - SocketIOData &getSocketIOData_(int const fd); - size_t clientDataSanityCheck_(Socket::SocketIOData &sIOD, int fd, - std::string const &serverName, - std::string const &nickName, - bool const isRegistered); - void addSocket(int const fd); - bool checkSocket_(struct pollfd &pfd); - bool sendMsg(int const fd, std::string const &msg); - void closeSocket_(int const fd); + void addSocket(int const fd, bool srvSocket); + bool checkSocket_(struct pollfd &pfd); + std::string getCmdLine(int const fd); + size_t + bufferLengthAndSanityCheck_(int const fd /* , bool const isRegistered */); + bool sendMsg(int const fd, std::string const &msg); + void closeSocket_(int const fd); + + bool socketClosed(int const fd) const { + return (sockIODataStore_.find(fd) == sockIODataStore_.end()); + } }; diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index b9811bf..c3f60c3 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -79,7 +79,7 @@ int Server::createServerSocket_(int const domain) { throw std::runtime_error("Error while listening on socket for " + srvHelp::getAdressFamilyStr(domain) + " !"); - storePollFd_(socketFd); + storePollFd_(socketFd, true); ++numServerSockets_; return socketFd; @@ -97,7 +97,7 @@ void Server::processServerSocketData_() { } } -void Server::setClientInfo_(int fd, Client const **clntPtr, std::string &nick, +void Server::setClientInfo_(int fd, Client **clntPtr, std::string &nick, bool &isReg) { try { *clntPtr = clients_.at(fd); @@ -106,76 +106,46 @@ void Server::setClientInfo_(int fd, Client const **clntPtr, std::string &nick, nick = (*clntPtr)->getNick(); } } catch (std::exception const &e) { - log_(e.what(), Logger::lmExcept); + log_(e.what(), Logger::lmWarn); } } -bool Server::processClientSocketData_(struct pollfd &pfd, - Socket::SocketIOData &sIOD) { - Client const *client = 0; - std::string nickName = "*"; - bool clntIsReg = false; - setClientInfo_(pfd.fd, &client, nickName, clntIsReg); - - size_t lfcrPos = sockets_.clientDataSanityCheck_(sIOD, pfd.fd, serverName(), - nickName, clntIsReg); - if (lfcrPos == std::string::npos) - return false; - -#if NETCATDBG - { - // size_t const clientNumber = idx + 1 - numServerSockets_; - std::ostringstream oss; - oss << "You are client '" << nickName << "' (fd " << pfd.fd << "): \n" - << "Srv received: " << std::string(sIOD.readBuffer) << std::endl; - sockets_.sendMsg(pfd.fd, oss.str()); - } -#endif - - sIOD.recvErrorCount = 0; - std::string cmdLine = sIOD.readBuffer.substr(0, lfcrPos); - sIOD.readBuffer = sIOD.readBuffer.substr(lfcrPos + 2); - try { - cmds_.run(*this, *clients_.at(pfd.fd), cmdLine); - } catch (Error const &e) { - if (e.hasReply()) { - sockets_.sendMsg(pfd.fd, - Reply::build(serverName(), nickName, e.reply())); - } else { - std::string tmp("Error while trying to run cmd:\n"); - tmp.append(cmdLine + "\n" + e.what()); - log_(tmp, Logger::lmExcept); - } - } - return true; -} - void Server::processClientSockets_() { for (size_t idx = numServerSockets_; !SignalHandler::stopLoopSignalReceived() && idx < pollFds_.size(); ++idx) { - pollfd &pfd = pollFds_.at(idx); - Socket::SocketIOData &sIOD = sockets_.getSocketIOData_(pfd.fd); - - if (!sockets_.checkSocket_(pfd)) - continue; - if (pfd.revents & POLLIN) { - char recvBuffer[RECVBUFFLEN]; - sIOD.bytesRead = recv(pfd.fd, &recvBuffer[0], RECVBUFFLEN, 0); - - if (sIOD.bytesRead > 0) { - sIOD.readBuffer += std::string(&recvBuffer[0], sIOD.bytesRead); - processClientSocketData_(pfd, sIOD); - } else if (sIOD.bytesRead <= 0) { - sockets_.closeSocket_(pfd.fd); + pollfd &pfd = pollFds_.at(idx); + + if (sockets_.checkSocket_(pfd)) { + if (pfd.revents & POLLIN) { + Client *client = 0; + std::string nickName = "*"; + bool clntIsReg = false; + setClientInfo_(pfd.fd, &client, nickName, clntIsReg); + + std::string cmdLine; + try { + cmdLine = sockets_.getCmdLine(pfd.fd); + cmds_.run(*this, *client, cmdLine); + } catch (Error const &e) { + if (e.hasReply()) { + sockets_.sendMsg( + pfd.fd, + Reply::build(serverName_, nickName, e.reply())); + } else { + std::string tmp( + "Error while trying to get/execute cmd:\n"); + tmp.append("'" + cmdLine + "'\n" + e.what()); + log_(tmp, Logger::lmExcept); + } + } } } + if (sockets_.socketClosed(pfd.fd)) + pollFds_.erase(--pollFds_.end()); } } -/* - tbd -*/ void Server::acceptClient_(int const serverFd) { socketAddrX sockAddrIn46 = {}; socklen_t sockLen = 0; @@ -195,20 +165,20 @@ void Server::acceptClient_(int const serverFd) { srvHelp::setSocketNonBlock(clientFd); - storePollFd_(clientFd); + storePollFd_(clientFd, false); std::cout << "Client connected:\n" << " fd: " << clientFd << "\n" << srvHelp::composeAddrfamAddrPortStr(sockAddrIn46) << std::endl; } -void Server::storePollFd_(int const socketFd) { +void Server::storePollFd_(int const socketFd, bool srvSocket) { pollfd pfd = {}; pfd.fd = socketFd; - pfd.events = POLLIN; // Monitor for incoming connections + pfd.events = POLLIN; pollFds_.push_back(pfd); - sockets_.addSocket(socketFd); + sockets_.addSocket(socketFd, srvSocket); } void Server::log_(std::string const &msg, Logger::logMode const lm) { @@ -221,8 +191,6 @@ void Server::log_(std::string const &msg, Logger::logMode const lm) { * sets up its listening socket(s) * runs an infinit loop to listen for connections * handles client connect and disconnect scenarios - * on server stop signal, disconnects all clients and closes - the server socket(s) */ void Server::run() { setupServerSockets_(); diff --git a/srcs/server/Socket.cpp b/srcs/server/Socket.cpp index 0da0511..a361301 100644 --- a/srcs/server/Socket.cpp +++ b/srcs/server/Socket.cpp @@ -1,5 +1,6 @@ #include "Socket.hpp" +#include "Errors.hpp" #include "Reply.hpp" #include "ServerHelper.hpp" @@ -8,9 +9,9 @@ #include #include -void Socket::addSocket(int const fd) { +void Socket::addSocket(int const fd, bool srvSocket) { sockIODataStore_.insert( - std::make_pair(fd, Socket::SocketIOData())); + std::make_pair(fd, SocketIOData(srvSocket))); } bool Socket::checkSocket_(struct pollfd &pfd) { @@ -25,21 +26,51 @@ bool Socket::checkSocket_(struct pollfd &pfd) { return true; } -size_t Socket::clientDataSanityCheck_(Socket::SocketIOData &sIOD, int fd, - std::string const &serverName, - std::string const &nickName, - bool const isRegistered) { - std::string &rdBuffer = sIOD.readBuffer; - size_t lfcrPos = rdBuffer.find("\r\n"); +std::string Socket::getCmdLine(int const fd) { + Socket::SocketIOData &sIOD = sockIODataStore_.at(fd); + std::string cmdLine; + char recvBuffer[RECVBUFFLEN]; + sIOD.bytesRead = recv(fd, &recvBuffer[0], RECVBUFFLEN, 0); + + if (sIOD.bytesRead > 0) { + sIOD.readBuffer += std::string(&recvBuffer[0], sIOD.bytesRead); + size_t lfcrPos = bufferLengthAndSanityCheck_(fd); + if (lfcrPos != std::string::npos) { + +#if NETCATDBG + { + std::ostringstream oss; + oss << "You are client fd " << fd << ": \n" + << "Srv received: " << std::string(sIOD.readBuffer) + << std::endl; + sendMsg(fd, oss.str()); + } +#endif + sIOD.recvErrorCount = 0; + cmdLine = sIOD.readBuffer.substr(0, lfcrPos); + sIOD.readBuffer = sIOD.readBuffer.substr(lfcrPos + 2); + } + } else if (sIOD.bytesRead <= 0) { + closeSocket_(fd); + } + return cmdLine; +} + +size_t Socket::bufferLengthAndSanityCheck_( + int const fd /* , bool const isRegistered */) { + Socket::SocketIOData &sIOD = sockIODataStore_.at(fd); + std::string &rdBuffer = sIOD.readBuffer; + size_t lfcrPos = rdBuffer.find("\r\n"); + bool msgTooLong = false; + if ((rdBuffer.length() >= MAXMSGLEN) && (lfcrPos == std::string::npos || lfcrPos > MAXMSGLEN - 2)) { sIOD.discardMode = true; ++sIOD.recvErrorCount; - sendMsg(fd, Reply::build(serverName, nickName, - Reply::reply(Reply::ERR_INPUTTOOLONG, ""))); + msgTooLong = true; } if (sIOD.discardMode) { - if (!isRegistered || sIOD.recvErrorCount >= SOCKETERRORLIMIT) { + if (/* !isRegistered || */ sIOD.recvErrorCount >= SOCKETERRORLIMIT) { closeSocket_(fd); return std::string::npos; } @@ -54,6 +85,9 @@ size_t Socket::clientDataSanityCheck_(Socket::SocketIOData &sIOD, int fd, rdBuffer.clear(); } } + if (msgTooLong) + throw Error(Reply::reply(Reply::ERR_INPUTTOOLONG, "")); + return lfcrPos; } @@ -109,18 +143,8 @@ bool Socket::sendMsg(int const fd, std::string const &msg) { return true; } -Socket::SocketIOData &Socket::getSocketIOData_(int const fd) { - std::map::iterator sIODit = sockIODataStore_.find(fd); - if (sIODit == sockIODataStore_.end()) { - std::ostringstream oss; - oss << "No SocketIOData for fd " << fd; - throw std::out_of_range(oss.str()); - } - return sIODit->second; -} - void Socket::closeSocket_(int const fd) { - SocketIOData &sIOD = getSocketIOData_(fd); + SocketIOData &sIOD = sockIODataStore_.at(fd); std::ostringstream oss; if (sIOD.isSrvSocket) { From 9fb454811f614643ecf40afcc512a80b2e3dde8e Mon Sep 17 00:00:00 2001 From: Rapha Date: Tue, 1 Sep 2026 13:04:33 +0200 Subject: [PATCH 153/349] fix: applied suggestions from PR review (ArchCipher) --- includes/CommandProcessor.hpp | 2 +- includes/ServerHelper.hpp | 3 ++- srcs/commands/CommandProcessor.cpp | 7 ++----- srcs/main.cpp | 2 ++ srcs/server/Server.cpp | 2 ++ srcs/server/SignalHandler.cpp | 1 - srcs/server/Socket.cpp | 3 +-- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/includes/CommandProcessor.hpp b/includes/CommandProcessor.hpp index 1bcdde8..ec9ae10 100644 --- a/includes/CommandProcessor.hpp +++ b/includes/CommandProcessor.hpp @@ -19,7 +19,7 @@ class Command { Handler handler; Command() {}; - Command(Handler h, bool preReg, int minP, bool op = false) + Command(Handler h, bool preReg, size_t minP, bool op = false) : usablePreReg_(preReg), minParams_(minP), requiresOp_(op), handler(h) {}; diff --git a/includes/ServerHelper.hpp b/includes/ServerHelper.hpp index 8f1f507..3177338 100644 --- a/includes/ServerHelper.hpp +++ b/includes/ServerHelper.hpp @@ -27,7 +27,8 @@ inline int sockAddrFamily(int const socketFd) { struct sockaddr sockAddr = {}; socklen_t sockLen = sizeof(sockAddr); if (getsockname(socketFd, &sockAddr, &sockLen) != 0) // NOLINT - throw; + std::runtime_error( + "Error whil getting address family through getsockname failed"); return sockAddr.sa_family; } diff --git a/srcs/commands/CommandProcessor.cpp b/srcs/commands/CommandProcessor.cpp index 7e4e8fa..a21696e 100644 --- a/srcs/commands/CommandProcessor.cpp +++ b/srcs/commands/CommandProcessor.cpp @@ -3,6 +3,7 @@ #include "Client.hpp" #include "Errors.hpp" #include "Handlers.hpp" +#include "Parser.hpp" #include "Server.hpp" Command &CommandProcessor::at_(const std::string &name) { @@ -12,13 +13,9 @@ Command &CommandProcessor::at_(const std::string &name) { return it->second; } -// TODO blnunez- bool CommandProcessor::parse(const std::string &line, std::string &cmd, std::vector ¶ms) { - (void)line; - (void)cmd; - (void)params; - return false; + return Parser::parse(line, cmd, params); } // Register all commandsexecutable by a client connected to us. diff --git a/srcs/main.cpp b/srcs/main.cpp index 5991465..66dfd59 100644 --- a/srcs/main.cpp +++ b/srcs/main.cpp @@ -81,6 +81,8 @@ static bool checkExtractCmdLnArgs(int const argc, char *const argv[], int &port, if (!((iss >> port) && (iss >> remainder).eof())) throw std::runtime_error( "Error while extracting port number from commandline parameter."); + if (port < 1 || port > 65535) + throw std::runtime_error("Error: port must be in the range 1-65535."); iss.str(argv[2]); // NOLINT if (!(iss >> password).eof()) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index c3f60c3..961d128 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -198,6 +198,8 @@ void Server::run() { while (!SignalHandler::stopLoopSignalReceived()) { int pollResult = poll(pollFds_.data(), pollFds_.size(), -1); if (pollResult == -1) { + if (SignalHandler::stopLoopSignalReceived()) + break; log_("Fatal error: poll failed.", Logger::lmError); break; } diff --git a/srcs/server/SignalHandler.cpp b/srcs/server/SignalHandler.cpp index 9a4e04f..8fd1105 100644 --- a/srcs/server/SignalHandler.cpp +++ b/srcs/server/SignalHandler.cpp @@ -21,7 +21,6 @@ volatile std::sig_atomic_t SignalHandler::stopServerLoop_ = 0; */ void SignalHandler::handleStopSignals_(int signal) { stopServerLoop_ = signal; - std::cerr << "Caught stop signal: " << stopServerLoop_ << "." << std::endl; } /* diff --git a/srcs/server/Socket.cpp b/srcs/server/Socket.cpp index a361301..4e03c8a 100644 --- a/srcs/server/Socket.cpp +++ b/srcs/server/Socket.cpp @@ -10,8 +10,7 @@ #include void Socket::addSocket(int const fd, bool srvSocket) { - sockIODataStore_.insert( - std::make_pair(fd, SocketIOData(srvSocket))); + sockIODataStore_.insert(std::make_pair(fd, SocketIOData(srvSocket))); } bool Socket::checkSocket_(struct pollfd &pfd) { From dbc1f82287a4b1043a6c4a9915eeba59b47c0b91 Mon Sep 17 00:00:00 2001 From: Rapha Date: Tue, 1 Sep 2026 15:13:39 +0200 Subject: [PATCH 154/349] fix: handle multiple commands in readBuffer --- Makefile | 2 +- includes/Socket.hpp | 4 ++-- srcs/server/Server.cpp | 32 +++++++++++++++++------------- srcs/server/Socket.cpp | 45 ++++++++++++++++++++++-------------------- 4 files changed, 45 insertions(+), 38 deletions(-) diff --git a/Makefile b/Makefile index f457474..72a480c 100644 --- a/Makefile +++ b/Makefile @@ -52,7 +52,7 @@ DEPS = $(OBJS:.o=.d) $(DEBUG_OBJS:.o=.d) CXX = c++ CXXFLAGS = -Wall -Wextra -Werror -pedantic -std=c++98 IFLAGS = -I$(INC_DIR) -DFLAGS = -DDEBUG -g -fsanitize=address +DFLAGS = -DDEBUG -g3 -O0 -fsanitize=address DEBUG_CXXFLAGS = $(CXXFLAGS) $(DFLAGS) # ---- Rules ------------------------------------------------------------------- diff --git a/includes/Socket.hpp b/includes/Socket.hpp index e3f85ff..2bcd6e0 100644 --- a/includes/Socket.hpp +++ b/includes/Socket.hpp @@ -10,7 +10,7 @@ #define MAXMSGLEN 512 #define SOCKETERRORLIMIT 8 -#define NETCATDBG 1 // For netcat debug +#define NETCATDBG 0 // For netcat debug class Socket { public: @@ -39,7 +39,7 @@ class Socket { void addSocket(int const fd, bool srvSocket); bool checkSocket_(struct pollfd &pfd); - std::string getCmdLine(int const fd); + std::string getCmdLine(int const fd, bool &buffHasMoreCmds); size_t bufferLengthAndSanityCheck_(int const fd /* , bool const isRegistered */); bool sendMsg(int const fd, std::string const &msg); diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 961d128..a422b4a 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -124,21 +124,25 @@ void Server::processClientSockets_() { setClientInfo_(pfd.fd, &client, nickName, clntIsReg); std::string cmdLine; - try { - cmdLine = sockets_.getCmdLine(pfd.fd); - cmds_.run(*this, *client, cmdLine); - } catch (Error const &e) { - if (e.hasReply()) { - sockets_.sendMsg( - pfd.fd, - Reply::build(serverName_, nickName, e.reply())); - } else { - std::string tmp( - "Error while trying to get/execute cmd:\n"); - tmp.append("'" + cmdLine + "'\n" + e.what()); - log_(tmp, Logger::lmExcept); + bool buffHasMoreCmds = false; + do { + try { + cmdLine = sockets_.getCmdLine(pfd.fd, buffHasMoreCmds); + if (!cmdLine.empty()) + cmds_.run(*this, *client, cmdLine); + } catch (Error const &e) { + if (e.hasReply()) { + sockets_.sendMsg( + pfd.fd, + Reply::build(serverName_, nickName, e.reply())); + } else { + std::string tmp( + "Error while trying to get/execute cmd:\n"); + tmp.append("'" + cmdLine + "'\n" + e.what()); + log_(tmp, Logger::lmExcept); + } } - } + } while (!buffHasMoreCmds); } } if (sockets_.socketClosed(pfd.fd)) diff --git a/srcs/server/Socket.cpp b/srcs/server/Socket.cpp index 4e03c8a..b4467e3 100644 --- a/srcs/server/Socket.cpp +++ b/srcs/server/Socket.cpp @@ -25,32 +25,35 @@ bool Socket::checkSocket_(struct pollfd &pfd) { return true; } -std::string Socket::getCmdLine(int const fd) { +std::string Socket::getCmdLine(int const fd, bool &buffHasMoreCmds) { Socket::SocketIOData &sIOD = sockIODataStore_.at(fd); std::string cmdLine; - char recvBuffer[RECVBUFFLEN]; - sIOD.bytesRead = recv(fd, &recvBuffer[0], RECVBUFFLEN, 0); - - if (sIOD.bytesRead > 0) { - sIOD.readBuffer += std::string(&recvBuffer[0], sIOD.bytesRead); - size_t lfcrPos = bufferLengthAndSanityCheck_(fd); - if (lfcrPos != std::string::npos) { + if (!buffHasMoreCmds) { + char recvBuffer[RECVBUFFLEN]; + sIOD.bytesRead = recv(fd, &recvBuffer[0], RECVBUFFLEN, 0); + if (sIOD.bytesRead > 0) { + sIOD.readBuffer += std::string(&recvBuffer[0], sIOD.bytesRead); + } else if (sIOD.bytesRead <= 0) { + closeSocket_(fd); + return std::string(); + } + } + size_t lfcrPos = bufferLengthAndSanityCheck_(fd); + if (lfcrPos != std::string::npos) { #if NETCATDBG - { - std::ostringstream oss; - oss << "You are client fd " << fd << ": \n" - << "Srv received: " << std::string(sIOD.readBuffer) - << std::endl; - sendMsg(fd, oss.str()); - } -#endif - sIOD.recvErrorCount = 0; - cmdLine = sIOD.readBuffer.substr(0, lfcrPos); - sIOD.readBuffer = sIOD.readBuffer.substr(lfcrPos + 2); + { + std::ostringstream oss; + oss << "You are client fd " << fd << ": \n" + << "Srv received: " << std::string(sIOD.readBuffer) + << std::endl; + sendMsg(fd, oss.str()); } - } else if (sIOD.bytesRead <= 0) { - closeSocket_(fd); +#endif + sIOD.recvErrorCount = 0; + cmdLine = sIOD.readBuffer.substr(0, lfcrPos); + sIOD.readBuffer = sIOD.readBuffer.substr(lfcrPos + 2); + buffHasMoreCmds = (sIOD.readBuffer.find("\r\n") != std::string::npos); } return cmdLine; } From 407862762adc5c5dcf02a3051de9036fff661768 Mon Sep 17 00:00:00 2001 From: Rapha Date: Tue, 1 Sep 2026 16:04:09 +0200 Subject: [PATCH 155/349] fix: * fix logic error for buffHasMoreCmds * erase the correct pollFds_ entry --- srcs/server/Server.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index a422b4a..02689ee 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -142,11 +142,14 @@ void Server::processClientSockets_() { log_(tmp, Logger::lmExcept); } } - } while (!buffHasMoreCmds); + } while (buffHasMoreCmds); } } - if (sockets_.socketClosed(pfd.fd)) - pollFds_.erase(--pollFds_.end()); + if (sockets_.socketClosed(pfd.fd)) { + std::vector::difference_type dist = + static_cast::difference_type>(idx); + pollFds_.erase(pollFds_.begin() + dist); + } } } From 72e33c1e8b9ceafb8297a7b5acd480887f75f575 Mon Sep 17 00:00:00 2001 From: Rapha Date: Tue, 1 Sep 2026 16:22:39 +0200 Subject: [PATCH 156/349] rfa: clang-format --- includes/SignalHandler.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/SignalHandler.hpp b/includes/SignalHandler.hpp index ba1940d..dbaf7d3 100644 --- a/includes/SignalHandler.hpp +++ b/includes/SignalHandler.hpp @@ -3,7 +3,7 @@ #include #include -typedef void (*sighandler_t) (int); +typedef void (*sighandler_t)(int); class SignalHandler { private: From e6e5f50f1072cf7ac38624cb76c8cdcf09428fa3 Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 09:16:25 +0200 Subject: [PATCH 157/349] fix: applied suggestions from latest PR review (ArchCipher) --- includes/Server.hpp | 1 + includes/ServerHelper.hpp | 4 +-- includes/Socket.hpp | 14 ++++---- includes/colours.hpp | 12 ------- srcs/server/Server.cpp | 68 ++++++++++++++++++++------------------- srcs/server/Socket.cpp | 35 +++++++++++--------- 6 files changed, 65 insertions(+), 69 deletions(-) diff --git a/includes/Server.hpp b/includes/Server.hpp index ca7cb86..a9989d8 100644 --- a/includes/Server.hpp +++ b/includes/Server.hpp @@ -35,6 +35,7 @@ class Server { void acceptClient_(int const serverFd); void processClientSockets_(); void storePollFd_(int const socketFd, bool srvSocket); + void removePollFd_(int const idx); void setClientInfo_(int fd, Client **clntPtr, std::string &nick, bool &isReg); void log_(std::string const &msg, Logger::logMode const lm); diff --git a/includes/ServerHelper.hpp b/includes/ServerHelper.hpp index 3177338..7e9d387 100644 --- a/includes/ServerHelper.hpp +++ b/includes/ServerHelper.hpp @@ -27,8 +27,8 @@ inline int sockAddrFamily(int const socketFd) { struct sockaddr sockAddr = {}; socklen_t sockLen = sizeof(sockAddr); if (getsockname(socketFd, &sockAddr, &sockLen) != 0) // NOLINT - std::runtime_error( - "Error whil getting address family through getsockname failed"); + throw std::runtime_error( + "Error while getting address family through getsockname failed"); return sockAddr.sa_family; } diff --git a/includes/Socket.hpp b/includes/Socket.hpp index 2bcd6e0..02beb0c 100644 --- a/includes/Socket.hpp +++ b/includes/Socket.hpp @@ -13,7 +13,6 @@ #define NETCATDBG 0 // For netcat debug class Socket { - public: private: struct SocketIOData { std::string readBuffer; @@ -30,20 +29,21 @@ class Socket { discardMode(false), isSrvSocket(srvSocket), fdClosed(false) {} }; - private: std::map sockIODataStore_; Logger const &logger_; + bool updateBuffer_(int const fd); + size_t + bufferLengthAndSanityCheck_(int const fd /* , bool const isRegistered */); + public: Socket(Logger const &logger) : logger_(logger) {} void addSocket(int const fd, bool srvSocket); - bool checkSocket_(struct pollfd &pfd); + bool checkSocket(struct pollfd &pfd); // NOLINT std::string getCmdLine(int const fd, bool &buffHasMoreCmds); - size_t - bufferLengthAndSanityCheck_(int const fd /* , bool const isRegistered */); - bool sendMsg(int const fd, std::string const &msg); - void closeSocket_(int const fd); + bool sendMsg(int const fd, std::string const &msg); + void closeSocket(int const fd); bool socketClosed(int const fd) const { return (sockIODataStore_.find(fd) == sockIODataStore_.end()); diff --git a/includes/colours.hpp b/includes/colours.hpp index 9fdeabb..8eb9880 100644 --- a/includes/colours.hpp +++ b/includes/colours.hpp @@ -1,15 +1,3 @@ -/* ************************************************************************** */ -/* */ -/* ::: :::::::: */ -/* colours.hpp :+: :+: :+: */ -/* +:+ +:+ +:+ */ -/* By: rhaas +#+ +:+ +#+ */ -/* +#+#+#+#+#+ +#+ */ -/* Created: 2026/03/18 11:26:12 by rhaas #+# #+# */ -/* Updated: 2026/08/27 17:47:38 by rhaas ### ########.fr */ -/* */ -/* ************************************************************************** */ - #pragma once // Color FG BG diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 02689ee..a48b358 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -21,7 +21,7 @@ Server::Server(int const port, std::string const &password) Server::~Server() { while (!pollFds_.empty()) { - sockets_.closeSocket_(pollFds_.back().fd); + sockets_.closeSocket(pollFds_.back().fd); pollFds_.erase(--pollFds_.end()); } } @@ -116,40 +116,36 @@ void Server::processClientSockets_() { ++idx) { pollfd &pfd = pollFds_.at(idx); - if (sockets_.checkSocket_(pfd)) { - if (pfd.revents & POLLIN) { - Client *client = 0; - std::string nickName = "*"; - bool clntIsReg = false; - setClientInfo_(pfd.fd, &client, nickName, clntIsReg); - - std::string cmdLine; - bool buffHasMoreCmds = false; - do { - try { - cmdLine = sockets_.getCmdLine(pfd.fd, buffHasMoreCmds); - if (!cmdLine.empty()) - cmds_.run(*this, *client, cmdLine); - } catch (Error const &e) { - if (e.hasReply()) { - sockets_.sendMsg( - pfd.fd, - Reply::build(serverName_, nickName, e.reply())); - } else { - std::string tmp( - "Error while trying to get/execute cmd:\n"); - tmp.append("'" + cmdLine + "'\n" + e.what()); - log_(tmp, Logger::lmExcept); - } + if (sockets_.checkSocket(pfd) && (pfd.revents & POLLIN)) { + Client *client = 0; + std::string nickName = "*"; + bool clntIsReg = false; + setClientInfo_(pfd.fd, &client, nickName, clntIsReg); + + std::string cmdLine; + bool buffHasMoreCmds = false; + do { + try { + cmdLine = sockets_.getCmdLine(pfd.fd, buffHasMoreCmds); + if (!cmdLine.empty()) + cmds_.run(*this, *client, cmdLine); + } catch (Error const &e) { + if (e.hasReply()) { + sockets_.sendMsg( + pfd.fd, + Reply::build(serverName_, nickName, e.reply())); + } else { + std::string tmp( + "Error while trying to get/execute cmd:\n"); + tmp.append("'" + cmdLine + "'\n" + e.what()); + log_(tmp, Logger::lmExcept); } - } while (buffHasMoreCmds); - } - } - if (sockets_.socketClosed(pfd.fd)) { - std::vector::difference_type dist = - static_cast::difference_type>(idx); - pollFds_.erase(pollFds_.begin() + dist); + } + } while (buffHasMoreCmds); } + + if (sockets_.socketClosed(pfd.fd)) + removePollFd_(pfd.fd); } } @@ -188,6 +184,12 @@ void Server::storePollFd_(int const socketFd, bool srvSocket) { sockets_.addSocket(socketFd, srvSocket); } +void Server::removePollFd_(int const idx) { + std::vector::difference_type dist = + static_cast::difference_type>(idx); + pollFds_.erase(pollFds_.begin() + dist); +} + void Server::log_(std::string const &msg, Logger::logMode const lm) { logger_.logMsg(msg, lm); } diff --git a/srcs/server/Socket.cpp b/srcs/server/Socket.cpp index b4467e3..71294d2 100644 --- a/srcs/server/Socket.cpp +++ b/srcs/server/Socket.cpp @@ -13,11 +13,11 @@ void Socket::addSocket(int const fd, bool srvSocket) { sockIODataStore_.insert(std::make_pair(fd, SocketIOData(srvSocket))); } -bool Socket::checkSocket_(struct pollfd &pfd) { +bool Socket::checkSocket(struct pollfd &pfd) { SocketIOData &sIOD = sockIODataStore_.at(pfd.fd); if ((pfd.revents & POLLERR)) { if (sIOD.recvErrorCount >= SOCKETERRORLIMIT) - closeSocket_(pfd.fd); + closeSocket(pfd.fd); else ++sIOD.recvErrorCount; return false; @@ -25,20 +25,25 @@ bool Socket::checkSocket_(struct pollfd &pfd) { return true; } +bool Socket::updateBuffer_(int const fd) { + Socket::SocketIOData &sIOD = sockIODataStore_.at(fd); + char recvBuffer[RECVBUFFLEN]; + sIOD.bytesRead = recv(fd, &recvBuffer[0], RECVBUFFLEN, 0); + if (sIOD.bytesRead <= 0) { + closeSocket(fd); + return false; + } + sIOD.readBuffer += std::string(&recvBuffer[0], sIOD.bytesRead); + return true; +} + std::string Socket::getCmdLine(int const fd, bool &buffHasMoreCmds) { Socket::SocketIOData &sIOD = sockIODataStore_.at(fd); std::string cmdLine; - if (!buffHasMoreCmds) { - char recvBuffer[RECVBUFFLEN]; - sIOD.bytesRead = recv(fd, &recvBuffer[0], RECVBUFFLEN, 0); - if (sIOD.bytesRead > 0) { - sIOD.readBuffer += std::string(&recvBuffer[0], sIOD.bytesRead); - } else if (sIOD.bytesRead <= 0) { - closeSocket_(fd); - return std::string(); - } - } + if (!buffHasMoreCmds) + updateBuffer_(fd); + size_t lfcrPos = bufferLengthAndSanityCheck_(fd); if (lfcrPos != std::string::npos) { #if NETCATDBG @@ -73,7 +78,7 @@ size_t Socket::bufferLengthAndSanityCheck_( } if (sIOD.discardMode) { if (/* !isRegistered || */ sIOD.recvErrorCount >= SOCKETERRORLIMIT) { - closeSocket_(fd); + closeSocket(fd); return std::string::npos; } if (lfcrPos != std::string::npos) { @@ -114,7 +119,7 @@ bool Socket::sendMsg(int const fd, std::string const &msg) { ++sIOD.sendErrorCount; } if (sIOD.sendErrorCount >= SOCKETERRORLIMIT) { - closeSocket_(fd); + closeSocket(fd); success = false; } @@ -145,7 +150,7 @@ bool Socket::sendMsg(int const fd, std::string const &msg) { return true; } -void Socket::closeSocket_(int const fd) { +void Socket::closeSocket(int const fd) { SocketIOData &sIOD = sockIODataStore_.at(fd); std::ostringstream oss; From 6471e071356f0651567edc7e199bd0adea5aeaa0 Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 13:19:52 +0200 Subject: [PATCH 158/349] feat: add handling of negative error code. Reason: Server sending shutdown error msg has no error code, so setting it to a negative value should be possible and should suppress sending a code. --- includes/Reply.hpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/includes/Reply.hpp b/includes/Reply.hpp index dd8d333..7377279 100644 --- a/includes/Reply.hpp +++ b/includes/Reply.hpp @@ -249,8 +249,10 @@ Use "*" for `nick` until the client has registered (pre-001). inline std::string build(const std::string &server, const std::string &nick, const Message &msg) { std::ostringstream ss; - ss << ":" << server << " " << std::setw(3) << std::setfill('0') << msg.code - << " " << nick; + ss << ":" << server; + if (msg.code > 0) + ss << " " << std::setw(3) << std::setfill('0') << msg.code; + ss << " " << nick; if (!msg.params.empty()) ss << " " << msg.params; if (msg.has_trailing) From cbb95e3ead175f11c6d72440c3025367478a196b Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 13:20:31 +0200 Subject: [PATCH 159/349] feat: add public interface for sending messages --- includes/Server.hpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/includes/Server.hpp b/includes/Server.hpp index a9989d8..2af766a 100644 --- a/includes/Server.hpp +++ b/includes/Server.hpp @@ -45,11 +45,15 @@ class Server { public: Server(int const port, std::string const &password); - ~Server(); void run(); + // Pass-through interfaces + void sendMsg(int const fd, std::string const &msg) { + sockets_.sendMsg(fd, msg); + } + bool tryRegister(Client &client) { (void)client; return true; From 651025888b22d36a62948912e24c73c6110c81b9 Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 13:21:00 +0200 Subject: [PATCH 160/349] fun: set lower error limit --- includes/Socket.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/Socket.hpp b/includes/Socket.hpp index 02beb0c..613e451 100644 --- a/includes/Socket.hpp +++ b/includes/Socket.hpp @@ -8,7 +8,7 @@ #define RECVBUFFLEN 1024 #define MAXMSGLEN 512 -#define SOCKETERRORLIMIT 8 +#define SOCKETERRORLIMIT 3 #define NETCATDBG 0 // For netcat debug From 7b3cf1411812a9ea6a334986f94bf180f3e10584 Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 13:22:16 +0200 Subject: [PATCH 161/349] feat: send goodby error message to clients when server shuts down --- srcs/server/Server.cpp | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index a48b358..d2a809b 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -19,9 +19,18 @@ Server::Server(int const port, std::string const &password) : serverName_(SERVER_NAME), port_(port), password_(password), logger_(serverName_ + ": "), sockets_(logger_), numServerSockets_(0) {} +/* + Cleanup + * going backwards through pollFds to ensure server sockets are closed last + */ Server::~Server() { + std::string nickName /*= ...get from Manager */; while (!pollFds_.empty()) { - sockets_.closeSocket(pollFds_.back().fd); + int const fd = pollFds_.back().fd; + sockets_.sendMsg( + fd, Reply::build(serverName_, nickName, + Reply::Message(-1, "Server shutdown - goodbye."))); + sockets_.closeSocket(fd); pollFds_.erase(--pollFds_.end()); } } @@ -117,10 +126,13 @@ void Server::processClientSockets_() { pollfd &pfd = pollFds_.at(idx); if (sockets_.checkSocket(pfd) && (pfd.revents & POLLIN)) { + + // to be replaced by a request to some sort of manager Client *client = 0; std::string nickName = "*"; bool clntIsReg = false; setClientInfo_(pfd.fd, &client, nickName, clntIsReg); + // end: to be replaced by a request to some sort of manager std::string cmdLine; bool buffHasMoreCmds = false; @@ -144,8 +156,10 @@ void Server::processClientSockets_() { } while (buffHasMoreCmds); } - if (sockets_.socketClosed(pfd.fd)) + if (sockets_.socketClosed(pfd.fd)) { removePollFd_(pfd.fd); + // inform Account Manager of connection abort + } } } From ee4498f37b9fed70a0d9ce0c59b36ec426093256 Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 13:22:56 +0200 Subject: [PATCH 162/349] feat: check for errors (not errno) after recv and close socket conditionally --- srcs/server/Socket.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/srcs/server/Socket.cpp b/srcs/server/Socket.cpp index 71294d2..84a0528 100644 --- a/srcs/server/Socket.cpp +++ b/srcs/server/Socket.cpp @@ -30,7 +30,9 @@ bool Socket::updateBuffer_(int const fd) { char recvBuffer[RECVBUFFLEN]; sIOD.bytesRead = recv(fd, &recvBuffer[0], RECVBUFFLEN, 0); if (sIOD.bytesRead <= 0) { - closeSocket(fd); + ++sIOD.recvErrorCount; + if (sIOD.bytesRead == 0 || sIOD.recvErrorCount >= SOCKETERRORLIMIT) + closeSocket(fd); return false; } sIOD.readBuffer += std::string(&recvBuffer[0], sIOD.bytesRead); From 4587e15d307013b048edd4637161a191e1c31f55 Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 13:32:27 +0200 Subject: [PATCH 163/349] fix: update msg content, including "ERROR", add nickname and hint to inform clientManager --- srcs/server/Server.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index d2a809b..b36a658 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -24,14 +24,16 @@ Server::Server(int const port, std::string const &password) * going backwards through pollFds to ensure server sockets are closed last */ Server::~Server() { - std::string nickName /*= ...get from Manager */; while (!pollFds_.empty()) { - int const fd = pollFds_.back().fd; + int const fd = pollFds_.back().fd; + std::string nickName = clients_.at(fd)->getNick(); sockets_.sendMsg( - fd, Reply::build(serverName_, nickName, - Reply::Message(-1, "Server shutdown - goodbye."))); + fd, Reply::build( + serverName_, nickName, + Reply::Message(-1, "ERROR", "Server shutdown - goodbye."))); sockets_.closeSocket(fd); pollFds_.erase(--pollFds_.end()); + // inform Account Manager of connection abort } } From 6dc5eee1851c7ed1c38663c8375e2fa1d0d64750 Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 13:34:29 +0200 Subject: [PATCH 164/349] fix: remove (active) client if corresponding socket is closed --- srcs/server/Server.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index b36a658..1b16ea2 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -33,7 +33,7 @@ Server::~Server() { Reply::Message(-1, "ERROR", "Server shutdown - goodbye."))); sockets_.closeSocket(fd); pollFds_.erase(--pollFds_.end()); - // inform Account Manager of connection abort + clients_.remove(fd); } } @@ -160,7 +160,7 @@ void Server::processClientSockets_() { if (sockets_.socketClosed(pfd.fd)) { removePollFd_(pfd.fd); - // inform Account Manager of connection abort + clients_.remove(pfd.fd); } } } From e799b0781f73d70316e8f1a510e0cd3ba36091ca Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 14:00:47 +0200 Subject: [PATCH 165/349] feat: * use add and remove from client manager * set default nick to "*" in client c'tor --- includes/Client.hpp | 3 ++- srcs/server/Server.cpp | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/includes/Client.hpp b/includes/Client.hpp index 0e31524..dc8d888 100644 --- a/includes/Client.hpp +++ b/includes/Client.hpp @@ -23,7 +23,8 @@ class Client { std::set invitedTo_; public: - Client() : registered_(false), passAccepted_(false), oper_(false) {} + Client() + : nick_("*"), registered_(false), passAccepted_(false), oper_(false) {} std::string getNick() const; std::string getNickCaseFolded() const; diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 1b16ea2..620b123 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -33,7 +33,7 @@ Server::~Server() { Reply::Message(-1, "ERROR", "Server shutdown - goodbye."))); sockets_.closeSocket(fd); pollFds_.erase(--pollFds_.end()); - clients_.remove(fd); + clients_.remove(fd); } } @@ -185,6 +185,7 @@ void Server::acceptClient_(int const serverFd) { srvHelp::setSocketNonBlock(clientFd); storePollFd_(clientFd, false); + clients_.add(new Client(), clientFd); std::cout << "Client connected:\n" << " fd: " << clientFd << "\n" From d5577483a02a5aa8e157f718009f1d5dc4cf39fc Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 15:13:12 +0200 Subject: [PATCH 166/349] fix: differentiate btw serv and client socket --- includes/Socket.hpp | 6 ++++++ srcs/server/Server.cpp | 16 +++++++++------- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/includes/Socket.hpp b/includes/Socket.hpp index 613e451..c0970b9 100644 --- a/includes/Socket.hpp +++ b/includes/Socket.hpp @@ -48,4 +48,10 @@ class Socket { bool socketClosed(int const fd) const { return (sockIODataStore_.find(fd) == sockIODataStore_.end()); } + + bool isServerSocket(int const fd) const { + if (!socketClosed(fd)) + return sockIODataStore_.find(fd)->second.isSrvSocket; + return false; + }; }; diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 620b123..7c3ddd6 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -25,15 +25,17 @@ Server::Server(int const port, std::string const &password) */ Server::~Server() { while (!pollFds_.empty()) { - int const fd = pollFds_.back().fd; - std::string nickName = clients_.at(fd)->getNick(); - sockets_.sendMsg( - fd, Reply::build( - serverName_, nickName, - Reply::Message(-1, "ERROR", "Server shutdown - goodbye."))); + int const fd = pollFds_.back().fd; + if (!sockets_.isServerSocket(fd)) { + std::string nickName = clients_.at(fd)->getNick(); + sockets_.sendMsg( + fd, Reply::build(serverName_, nickName, + Reply::Message(-1, "ERROR", + "Server shutdown - goodbye."))); + clients_.remove(fd); + } sockets_.closeSocket(fd); pollFds_.erase(--pollFds_.end()); - clients_.remove(fd); } } From 33897d7fe5a83448bfdbb986412f1fada03e99a8 Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 13:19:52 +0200 Subject: [PATCH 167/349] feat: add handling of negative error code. Reason: Server sending shutdown error msg has no error code, so setting it to a negative value should be possible and should suppress sending a code. --- includes/Reply.hpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/includes/Reply.hpp b/includes/Reply.hpp index dd8d333..7377279 100644 --- a/includes/Reply.hpp +++ b/includes/Reply.hpp @@ -249,8 +249,10 @@ Use "*" for `nick` until the client has registered (pre-001). inline std::string build(const std::string &server, const std::string &nick, const Message &msg) { std::ostringstream ss; - ss << ":" << server << " " << std::setw(3) << std::setfill('0') << msg.code - << " " << nick; + ss << ":" << server; + if (msg.code > 0) + ss << " " << std::setw(3) << std::setfill('0') << msg.code; + ss << " " << nick; if (!msg.params.empty()) ss << " " << msg.params; if (msg.has_trailing) From edf92b70ed8f01a9cf34bad5051d6be23ef57edb Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 13:20:31 +0200 Subject: [PATCH 168/349] feat: add public interface for sending messages --- includes/Server.hpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/includes/Server.hpp b/includes/Server.hpp index a9989d8..2af766a 100644 --- a/includes/Server.hpp +++ b/includes/Server.hpp @@ -45,11 +45,15 @@ class Server { public: Server(int const port, std::string const &password); - ~Server(); void run(); + // Pass-through interfaces + void sendMsg(int const fd, std::string const &msg) { + sockets_.sendMsg(fd, msg); + } + bool tryRegister(Client &client) { (void)client; return true; From bec1a31cf7039332178819068f3451688b6b33fb Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 13:21:00 +0200 Subject: [PATCH 169/349] fun: set lower error limit --- includes/Socket.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/Socket.hpp b/includes/Socket.hpp index 02beb0c..613e451 100644 --- a/includes/Socket.hpp +++ b/includes/Socket.hpp @@ -8,7 +8,7 @@ #define RECVBUFFLEN 1024 #define MAXMSGLEN 512 -#define SOCKETERRORLIMIT 8 +#define SOCKETERRORLIMIT 3 #define NETCATDBG 0 // For netcat debug From 2ab1ec15ace1db441545ac147648aa0105646174 Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 13:22:16 +0200 Subject: [PATCH 170/349] feat: send goodby error message to clients when server shuts down --- srcs/server/Server.cpp | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index a48b358..d2a809b 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -19,9 +19,18 @@ Server::Server(int const port, std::string const &password) : serverName_(SERVER_NAME), port_(port), password_(password), logger_(serverName_ + ": "), sockets_(logger_), numServerSockets_(0) {} +/* + Cleanup + * going backwards through pollFds to ensure server sockets are closed last + */ Server::~Server() { + std::string nickName /*= ...get from Manager */; while (!pollFds_.empty()) { - sockets_.closeSocket(pollFds_.back().fd); + int const fd = pollFds_.back().fd; + sockets_.sendMsg( + fd, Reply::build(serverName_, nickName, + Reply::Message(-1, "Server shutdown - goodbye."))); + sockets_.closeSocket(fd); pollFds_.erase(--pollFds_.end()); } } @@ -117,10 +126,13 @@ void Server::processClientSockets_() { pollfd &pfd = pollFds_.at(idx); if (sockets_.checkSocket(pfd) && (pfd.revents & POLLIN)) { + + // to be replaced by a request to some sort of manager Client *client = 0; std::string nickName = "*"; bool clntIsReg = false; setClientInfo_(pfd.fd, &client, nickName, clntIsReg); + // end: to be replaced by a request to some sort of manager std::string cmdLine; bool buffHasMoreCmds = false; @@ -144,8 +156,10 @@ void Server::processClientSockets_() { } while (buffHasMoreCmds); } - if (sockets_.socketClosed(pfd.fd)) + if (sockets_.socketClosed(pfd.fd)) { removePollFd_(pfd.fd); + // inform Account Manager of connection abort + } } } From 9824f200e7fc086a99fc60adbf89479e06c85ec6 Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 13:22:56 +0200 Subject: [PATCH 171/349] feat: check for errors (not errno) after recv and close socket conditionally --- srcs/server/Socket.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/srcs/server/Socket.cpp b/srcs/server/Socket.cpp index 71294d2..84a0528 100644 --- a/srcs/server/Socket.cpp +++ b/srcs/server/Socket.cpp @@ -30,7 +30,9 @@ bool Socket::updateBuffer_(int const fd) { char recvBuffer[RECVBUFFLEN]; sIOD.bytesRead = recv(fd, &recvBuffer[0], RECVBUFFLEN, 0); if (sIOD.bytesRead <= 0) { - closeSocket(fd); + ++sIOD.recvErrorCount; + if (sIOD.bytesRead == 0 || sIOD.recvErrorCount >= SOCKETERRORLIMIT) + closeSocket(fd); return false; } sIOD.readBuffer += std::string(&recvBuffer[0], sIOD.bytesRead); From d6686fd35f8675368a0bf5c280c53acaefc005ea Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 13:32:27 +0200 Subject: [PATCH 172/349] fix: update msg content, including "ERROR", add nickname and hint to inform clientManager --- srcs/server/Server.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index d2a809b..b36a658 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -24,14 +24,16 @@ Server::Server(int const port, std::string const &password) * going backwards through pollFds to ensure server sockets are closed last */ Server::~Server() { - std::string nickName /*= ...get from Manager */; while (!pollFds_.empty()) { - int const fd = pollFds_.back().fd; + int const fd = pollFds_.back().fd; + std::string nickName = clients_.at(fd)->getNick(); sockets_.sendMsg( - fd, Reply::build(serverName_, nickName, - Reply::Message(-1, "Server shutdown - goodbye."))); + fd, Reply::build( + serverName_, nickName, + Reply::Message(-1, "ERROR", "Server shutdown - goodbye."))); sockets_.closeSocket(fd); pollFds_.erase(--pollFds_.end()); + // inform Account Manager of connection abort } } From 0f00f795a93b36eddc8132d115b51bcd0764f973 Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 13:34:29 +0200 Subject: [PATCH 173/349] fix: remove (active) client if corresponding socket is closed --- srcs/server/Server.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index b36a658..1b16ea2 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -33,7 +33,7 @@ Server::~Server() { Reply::Message(-1, "ERROR", "Server shutdown - goodbye."))); sockets_.closeSocket(fd); pollFds_.erase(--pollFds_.end()); - // inform Account Manager of connection abort + clients_.remove(fd); } } @@ -160,7 +160,7 @@ void Server::processClientSockets_() { if (sockets_.socketClosed(pfd.fd)) { removePollFd_(pfd.fd); - // inform Account Manager of connection abort + clients_.remove(pfd.fd); } } } From a1e4708b5235dfb34398ee93382eb6177db0abc1 Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 14:00:47 +0200 Subject: [PATCH 174/349] feat: * use add and remove from client manager * set default nick to "*" in client c'tor --- includes/Client.hpp | 3 ++- srcs/server/Server.cpp | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/includes/Client.hpp b/includes/Client.hpp index 0e31524..dc8d888 100644 --- a/includes/Client.hpp +++ b/includes/Client.hpp @@ -23,7 +23,8 @@ class Client { std::set invitedTo_; public: - Client() : registered_(false), passAccepted_(false), oper_(false) {} + Client() + : nick_("*"), registered_(false), passAccepted_(false), oper_(false) {} std::string getNick() const; std::string getNickCaseFolded() const; diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 1b16ea2..620b123 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -33,7 +33,7 @@ Server::~Server() { Reply::Message(-1, "ERROR", "Server shutdown - goodbye."))); sockets_.closeSocket(fd); pollFds_.erase(--pollFds_.end()); - clients_.remove(fd); + clients_.remove(fd); } } @@ -185,6 +185,7 @@ void Server::acceptClient_(int const serverFd) { srvHelp::setSocketNonBlock(clientFd); storePollFd_(clientFd, false); + clients_.add(new Client(), clientFd); std::cout << "Client connected:\n" << " fd: " << clientFd << "\n" From d0ff8065940eed1a8ed7af9cb89104ee036ffb03 Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 15:13:12 +0200 Subject: [PATCH 175/349] fix: differentiate btw serv and client socket --- includes/Socket.hpp | 6 ++++++ srcs/server/Server.cpp | 16 +++++++++------- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/includes/Socket.hpp b/includes/Socket.hpp index 613e451..c0970b9 100644 --- a/includes/Socket.hpp +++ b/includes/Socket.hpp @@ -48,4 +48,10 @@ class Socket { bool socketClosed(int const fd) const { return (sockIODataStore_.find(fd) == sockIODataStore_.end()); } + + bool isServerSocket(int const fd) const { + if (!socketClosed(fd)) + return sockIODataStore_.find(fd)->second.isSrvSocket; + return false; + }; }; diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 620b123..7c3ddd6 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -25,15 +25,17 @@ Server::Server(int const port, std::string const &password) */ Server::~Server() { while (!pollFds_.empty()) { - int const fd = pollFds_.back().fd; - std::string nickName = clients_.at(fd)->getNick(); - sockets_.sendMsg( - fd, Reply::build( - serverName_, nickName, - Reply::Message(-1, "ERROR", "Server shutdown - goodbye."))); + int const fd = pollFds_.back().fd; + if (!sockets_.isServerSocket(fd)) { + std::string nickName = clients_.at(fd)->getNick(); + sockets_.sendMsg( + fd, Reply::build(serverName_, nickName, + Reply::Message(-1, "ERROR", + "Server shutdown - goodbye."))); + clients_.remove(fd); + } sockets_.closeSocket(fd); pollFds_.erase(--pollFds_.end()); - clients_.remove(fd); } } From c866fe6b2b75f8136f40aa890cc638c60d679daa Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 17:15:03 +0200 Subject: [PATCH 176/349] rfa: remove sendMsg interface in Server, everybody will use the sockets._sendMsg directly --- includes/Server.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/includes/Server.hpp b/includes/Server.hpp index 2af766a..fb289b8 100644 --- a/includes/Server.hpp +++ b/includes/Server.hpp @@ -50,9 +50,9 @@ class Server { void run(); // Pass-through interfaces - void sendMsg(int const fd, std::string const &msg) { - sockets_.sendMsg(fd, msg); - } + // void sendMsg(int const fd, std::string const &msg) { + // sockets_.sendMsg(fd, msg); + // } bool tryRegister(Client &client) { (void)client; From b165021ff22e7cc22d1b55be00f7f04806fef115 Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 17:15:53 +0200 Subject: [PATCH 177/349] rfa: * use static const where possible * use .at() for element access instead of [] --- srcs/server/Server.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 7c3ddd6..7d4eb7f 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -32,7 +32,7 @@ Server::~Server() { fd, Reply::build(serverName_, nickName, Reply::Message(-1, "ERROR", "Server shutdown - goodbye."))); - clients_.remove(fd); + clients_.remove(fd); } sockets_.closeSocket(fd); pollFds_.erase(--pollFds_.end()); @@ -40,13 +40,13 @@ Server::~Server() { } void Server::setupServerSockets_() { - int domArr[] = {AF_INET, AF_INET6}; - std::vector domains(domArr, // NOLINT - domArr + // NOLINT - (sizeof(domArr) / sizeof(domArr[0]))); + static int const domArr[] = {AF_INET, AF_INET6}; + static std::vector const domains( + // NOLINTNEXTLINE + domArr, domArr + (sizeof(domArr) / sizeof(domArr[0]))); for (size_t idx = 0; idx < domains.size(); ++idx) { try { - createServerSocket_(domains[idx]); + createServerSocket_(domains.at(idx)); } catch (std::exception const &e) { log_(e.what(), Logger::lmExcept); } @@ -102,7 +102,7 @@ void Server::processServerSocketData_() { for (size_t idx = 0; !SignalHandler::stopLoopSignalReceived() && idx < numServerSockets_; ++idx) { - struct pollfd const &serverPollFds = pollFds_[idx]; + struct pollfd const &serverPollFds = pollFds_.at(idx); if ((serverPollFds.revents & POLLERR)) throw std::runtime_error("Server socket error."); if (serverPollFds.revents & POLLIN) From 917a9bc4a9f71b537bef8382c7507bd8d7426014 Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 17:17:00 +0200 Subject: [PATCH 178/349] fix: deactivate debug output depending on NETCATDBG --- srcs/server/Socket.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/srcs/server/Socket.cpp b/srcs/server/Socket.cpp index 84a0528..7d32cb7 100644 --- a/srcs/server/Socket.cpp +++ b/srcs/server/Socket.cpp @@ -125,6 +125,7 @@ bool Socket::sendMsg(int const fd, std::string const &msg) { success = false; } +#if NETCATDBG std::ostringstream oss; oss << "SendMsg event:\n" << " WriteBuffer: '" << sIOD.writeBuffer << "':\n"; @@ -140,6 +141,7 @@ bool Socket::sendMsg(int const fd, std::string const &msg) { oss << " sent to FD: " << fd; logger_.logMsg(oss.str(), Logger::lmInfo); } +#endif if (!success) return false; From 9334d4c0e75e47fb64d024d4a0777b6e59e7628a Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 17:40:46 +0200 Subject: [PATCH 179/349] fix: return immediately on errors during buffer update --- srcs/server/Socket.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/srcs/server/Socket.cpp b/srcs/server/Socket.cpp index 7d32cb7..74b9933 100644 --- a/srcs/server/Socket.cpp +++ b/srcs/server/Socket.cpp @@ -44,7 +44,8 @@ std::string Socket::getCmdLine(int const fd, bool &buffHasMoreCmds) { std::string cmdLine; if (!buffHasMoreCmds) - updateBuffer_(fd); + if (!updateBuffer_(fd)) + return cmdLine; size_t lfcrPos = bufferLengthAndSanityCheck_(fd); if (lfcrPos != std::string::npos) { From fb4bc79899bec9f469b43e01138893ab8b58908a Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 13:19:52 +0200 Subject: [PATCH 180/349] feat: add handling of negative error code. Reason: Server sending shutdown error msg has no error code, so setting it to a negative value should be possible and should suppress sending a code. --- includes/Reply.hpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/includes/Reply.hpp b/includes/Reply.hpp index 9c37c30..3e69062 100644 --- a/includes/Reply.hpp +++ b/includes/Reply.hpp @@ -267,8 +267,10 @@ Use "*" for `nick` until the client has registered (pre-001). inline std::string build(const std::string &server, const std::string &nick, const Message &msg) { std::ostringstream ss; - ss << ":" << server << " " << std::setw(3) << std::setfill('0') << msg.code - << " " << nick; + ss << ":" << server; + if (msg.code > 0) + ss << " " << std::setw(3) << std::setfill('0') << msg.code; + ss << " " << nick; if (!msg.params.empty()) ss << " " << msg.params; if (msg.hasTrailing) From ead9b3c1d5f7d7866c092ed9a14ec8366b3493ab Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 13:20:31 +0200 Subject: [PATCH 181/349] feat: add public interface for sending messages --- includes/Server.hpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/includes/Server.hpp b/includes/Server.hpp index 68a7d32..2acf8c0 100644 --- a/includes/Server.hpp +++ b/includes/Server.hpp @@ -45,7 +45,6 @@ class Server { public: Server(int const port, std::string const &password); - ~Server(); void run(); @@ -54,6 +53,11 @@ class Server { ChannelManager &getChannelManager(); ClientManager &getClientManager(); + // Pass-through interfaces + void sendMsg(int const fd, std::string const &msg) { + sockets_.sendMsg(fd, msg); + } + bool tryRegister(Client &client) { (void)client; return true; From 59de1638b5a43678a6d3caeb6b7e4d6480d0fabc Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 13:21:00 +0200 Subject: [PATCH 182/349] fun: set lower error limit --- includes/Socket.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/Socket.hpp b/includes/Socket.hpp index 02beb0c..613e451 100644 --- a/includes/Socket.hpp +++ b/includes/Socket.hpp @@ -8,7 +8,7 @@ #define RECVBUFFLEN 1024 #define MAXMSGLEN 512 -#define SOCKETERRORLIMIT 8 +#define SOCKETERRORLIMIT 3 #define NETCATDBG 0 // For netcat debug From 37d67956fee07782eec5a09208afe7e1f2665ca3 Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 13:22:16 +0200 Subject: [PATCH 183/349] feat: send goodby error message to clients when server shuts down --- srcs/server/Server.cpp | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 0cf528a..51f2b53 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -19,9 +19,18 @@ Server::Server(int const port, std::string const &password) : serverName_(SERVER_NAME), port_(port), password_(password), logger_(serverName_ + ": "), sockets_(logger_), numServerSockets_(0) {} +/* + Cleanup + * going backwards through pollFds to ensure server sockets are closed last + */ Server::~Server() { + std::string nickName /*= ...get from Manager */; while (!pollFds_.empty()) { - sockets_.closeSocket(pollFds_.back().fd); + int const fd = pollFds_.back().fd; + sockets_.sendMsg( + fd, Reply::build(serverName_, nickName, + Reply::Message(-1, "Server shutdown - goodbye."))); + sockets_.closeSocket(fd); pollFds_.erase(--pollFds_.end()); } } @@ -117,10 +126,13 @@ void Server::processClientSockets_() { pollfd &pfd = pollFds_.at(idx); if (sockets_.checkSocket(pfd) && (pfd.revents & POLLIN)) { + + // to be replaced by a request to some sort of manager Client *client = 0; std::string nickName = "*"; bool clntIsReg = false; setClientInfo_(pfd.fd, &client, nickName, clntIsReg); + // end: to be replaced by a request to some sort of manager std::string cmdLine; bool buffHasMoreCmds = false; @@ -144,8 +156,10 @@ void Server::processClientSockets_() { } while (buffHasMoreCmds); } - if (sockets_.socketClosed(pfd.fd)) + if (sockets_.socketClosed(pfd.fd)) { removePollFd_(pfd.fd); + // inform Account Manager of connection abort + } } } From d72f708c48ff878421284b4194e68f347b0f5e57 Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 13:22:56 +0200 Subject: [PATCH 184/349] feat: check for errors (not errno) after recv and close socket conditionally --- srcs/server/Socket.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/srcs/server/Socket.cpp b/srcs/server/Socket.cpp index 71294d2..84a0528 100644 --- a/srcs/server/Socket.cpp +++ b/srcs/server/Socket.cpp @@ -30,7 +30,9 @@ bool Socket::updateBuffer_(int const fd) { char recvBuffer[RECVBUFFLEN]; sIOD.bytesRead = recv(fd, &recvBuffer[0], RECVBUFFLEN, 0); if (sIOD.bytesRead <= 0) { - closeSocket(fd); + ++sIOD.recvErrorCount; + if (sIOD.bytesRead == 0 || sIOD.recvErrorCount >= SOCKETERRORLIMIT) + closeSocket(fd); return false; } sIOD.readBuffer += std::string(&recvBuffer[0], sIOD.bytesRead); From 6e995532571d33f0364ba2caf0e287f2e7ef59b2 Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 13:32:27 +0200 Subject: [PATCH 185/349] fix: update msg content, including "ERROR", add nickname and hint to inform clientManager --- srcs/server/Server.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 51f2b53..96b66cc 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -24,14 +24,16 @@ Server::Server(int const port, std::string const &password) * going backwards through pollFds to ensure server sockets are closed last */ Server::~Server() { - std::string nickName /*= ...get from Manager */; while (!pollFds_.empty()) { - int const fd = pollFds_.back().fd; + int const fd = pollFds_.back().fd; + std::string nickName = clients_.at(fd)->getNick(); sockets_.sendMsg( - fd, Reply::build(serverName_, nickName, - Reply::Message(-1, "Server shutdown - goodbye."))); + fd, Reply::build( + serverName_, nickName, + Reply::Message(-1, "ERROR", "Server shutdown - goodbye."))); sockets_.closeSocket(fd); pollFds_.erase(--pollFds_.end()); + // inform Account Manager of connection abort } } From ecdebb4548a7b57dcd9a35d6f4d5f1787c5da1e5 Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 13:34:29 +0200 Subject: [PATCH 186/349] fix: remove (active) client if corresponding socket is closed --- srcs/server/Server.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 96b66cc..95eb501 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -33,7 +33,7 @@ Server::~Server() { Reply::Message(-1, "ERROR", "Server shutdown - goodbye."))); sockets_.closeSocket(fd); pollFds_.erase(--pollFds_.end()); - // inform Account Manager of connection abort + clients_.remove(fd); } } @@ -160,7 +160,7 @@ void Server::processClientSockets_() { if (sockets_.socketClosed(pfd.fd)) { removePollFd_(pfd.fd); - // inform Account Manager of connection abort + clients_.remove(pfd.fd); } } } From f82b0dc8de57a1213f9b3a63eff02de6371fc756 Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 14:00:47 +0200 Subject: [PATCH 187/349] feat: * use add and remove from client manager * set default nick to "*" in client c'tor --- includes/Client.hpp | 3 ++- srcs/server/Server.cpp | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/includes/Client.hpp b/includes/Client.hpp index e46229a..c8dff0e 100644 --- a/includes/Client.hpp +++ b/includes/Client.hpp @@ -25,7 +25,8 @@ class Client { std::set invitedTo_; public: - Client() : registered_(false), passAccepted_(false), oper_(false) {} + Client() + : nick_("*"), registered_(false), passAccepted_(false), oper_(false) {} std::string getNick() const; std::string getNickCaseFolded() const; diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 95eb501..5c1cdaf 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -33,7 +33,7 @@ Server::~Server() { Reply::Message(-1, "ERROR", "Server shutdown - goodbye."))); sockets_.closeSocket(fd); pollFds_.erase(--pollFds_.end()); - clients_.remove(fd); + clients_.remove(fd); } } @@ -185,6 +185,7 @@ void Server::acceptClient_(int const serverFd) { srvHelp::setSocketNonBlock(clientFd); storePollFd_(clientFd, false); + clients_.add(new Client(), clientFd); std::cout << "Client connected:\n" << " fd: " << clientFd << "\n" From a4d10049d5f0de094f3348656af88d4883b061fc Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 15:13:12 +0200 Subject: [PATCH 188/349] fix: differentiate btw serv and client socket --- includes/Socket.hpp | 6 ++++++ srcs/server/Server.cpp | 16 +++++++++------- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/includes/Socket.hpp b/includes/Socket.hpp index 613e451..c0970b9 100644 --- a/includes/Socket.hpp +++ b/includes/Socket.hpp @@ -48,4 +48,10 @@ class Socket { bool socketClosed(int const fd) const { return (sockIODataStore_.find(fd) == sockIODataStore_.end()); } + + bool isServerSocket(int const fd) const { + if (!socketClosed(fd)) + return sockIODataStore_.find(fd)->second.isSrvSocket; + return false; + }; }; diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 5c1cdaf..9f0ff34 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -25,15 +25,17 @@ Server::Server(int const port, std::string const &password) */ Server::~Server() { while (!pollFds_.empty()) { - int const fd = pollFds_.back().fd; - std::string nickName = clients_.at(fd)->getNick(); - sockets_.sendMsg( - fd, Reply::build( - serverName_, nickName, - Reply::Message(-1, "ERROR", "Server shutdown - goodbye."))); + int const fd = pollFds_.back().fd; + if (!sockets_.isServerSocket(fd)) { + std::string nickName = clients_.at(fd)->getNick(); + sockets_.sendMsg( + fd, Reply::build(serverName_, nickName, + Reply::Message(-1, "ERROR", + "Server shutdown - goodbye."))); + clients_.remove(fd); + } sockets_.closeSocket(fd); pollFds_.erase(--pollFds_.end()); - clients_.remove(fd); } } From e4c69ad6de0999d35a57a21c675e05806e806d8e Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 17:15:03 +0200 Subject: [PATCH 189/349] rfa: remove sendMsg interface in Server, everybody will use the sockets._sendMsg directly --- includes/Server.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/includes/Server.hpp b/includes/Server.hpp index 2acf8c0..879ccd6 100644 --- a/includes/Server.hpp +++ b/includes/Server.hpp @@ -54,9 +54,9 @@ class Server { ClientManager &getClientManager(); // Pass-through interfaces - void sendMsg(int const fd, std::string const &msg) { - sockets_.sendMsg(fd, msg); - } + // void sendMsg(int const fd, std::string const &msg) { + // sockets_.sendMsg(fd, msg); + // } bool tryRegister(Client &client) { (void)client; From c8477587cc12212d74041241bd57ae26b0541fad Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 17:15:53 +0200 Subject: [PATCH 190/349] rfa: * use static const where possible * use .at() for element access instead of [] --- srcs/server/Server.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 9f0ff34..dea7b20 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -32,7 +32,7 @@ Server::~Server() { fd, Reply::build(serverName_, nickName, Reply::Message(-1, "ERROR", "Server shutdown - goodbye."))); - clients_.remove(fd); + clients_.remove(fd); } sockets_.closeSocket(fd); pollFds_.erase(--pollFds_.end()); @@ -40,13 +40,13 @@ Server::~Server() { } void Server::setupServerSockets_() { - int domArr[] = {AF_INET, AF_INET6}; - std::vector domains(domArr, // NOLINT - domArr + // NOLINT - (sizeof(domArr) / sizeof(domArr[0]))); + static int const domArr[] = {AF_INET, AF_INET6}; + static std::vector const domains( + // NOLINTNEXTLINE + domArr, domArr + (sizeof(domArr) / sizeof(domArr[0]))); for (size_t idx = 0; idx < domains.size(); ++idx) { try { - createServerSocket_(domains[idx]); + createServerSocket_(domains.at(idx)); } catch (std::exception const &e) { log_(e.what(), Logger::lmExcept); } @@ -102,7 +102,7 @@ void Server::processServerSocketData_() { for (size_t idx = 0; !SignalHandler::stopLoopSignalReceived() && idx < numServerSockets_; ++idx) { - struct pollfd const &serverPollFds = pollFds_[idx]; + struct pollfd const &serverPollFds = pollFds_.at(idx); if ((serverPollFds.revents & POLLERR)) throw std::runtime_error("Server socket error."); if (serverPollFds.revents & POLLIN) From 4e9973c5bfa714c80d36e3778cae1c4e95f78493 Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 17:17:00 +0200 Subject: [PATCH 191/349] fix: deactivate debug output depending on NETCATDBG --- srcs/server/Socket.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/srcs/server/Socket.cpp b/srcs/server/Socket.cpp index 84a0528..7d32cb7 100644 --- a/srcs/server/Socket.cpp +++ b/srcs/server/Socket.cpp @@ -125,6 +125,7 @@ bool Socket::sendMsg(int const fd, std::string const &msg) { success = false; } +#if NETCATDBG std::ostringstream oss; oss << "SendMsg event:\n" << " WriteBuffer: '" << sIOD.writeBuffer << "':\n"; @@ -140,6 +141,7 @@ bool Socket::sendMsg(int const fd, std::string const &msg) { oss << " sent to FD: " << fd; logger_.logMsg(oss.str(), Logger::lmInfo); } +#endif if (!success) return false; From 2cf1a8b70355f48d29f70898d1e6c1cc957b2d81 Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 17:40:46 +0200 Subject: [PATCH 192/349] fix: return immediately on errors during buffer update --- srcs/server/Socket.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/srcs/server/Socket.cpp b/srcs/server/Socket.cpp index 7d32cb7..74b9933 100644 --- a/srcs/server/Socket.cpp +++ b/srcs/server/Socket.cpp @@ -44,7 +44,8 @@ std::string Socket::getCmdLine(int const fd, bool &buffHasMoreCmds) { std::string cmdLine; if (!buffHasMoreCmds) - updateBuffer_(fd); + if (!updateBuffer_(fd)) + return cmdLine; size_t lfcrPos = bufferLengthAndSanityCheck_(fd); if (lfcrPos != std::string::npos) { From 6edbd2b57e0741948b6790092dde4cf19fc02729 Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 3 Sep 2026 12:40:41 +0200 Subject: [PATCH 193/349] fix: create the ERROR message from scratch, as Reply::Message doesn't allow non-Code generation --- srcs/server/Server.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index dea7b20..50ad431 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -27,12 +27,12 @@ Server::~Server() { while (!pollFds_.empty()) { int const fd = pollFds_.back().fd; if (!sockets_.isServerSocket(fd)) { - std::string nickName = clients_.at(fd)->getNick(); - sockets_.sendMsg( - fd, Reply::build(serverName_, nickName, - Reply::Message(-1, "ERROR", - "Server shutdown - goodbye."))); - clients_.remove(fd); + std::string nickName = clients_.at(fd).getNick(); + std::ostringstream oss; + oss << "ERROR :" << serverName_ << " " << nickName + << " :Server shutdown - goodbye."; + sockets_.sendMsg(fd, oss.str()); + clients_.remove(fd, channels_); } sockets_.closeSocket(fd); pollFds_.erase(--pollFds_.end()); @@ -162,7 +162,7 @@ void Server::processClientSockets_() { if (sockets_.socketClosed(pfd.fd)) { removePollFd_(pfd.fd); - clients_.remove(pfd.fd); + clients_.remove(pfd.fd, channels_); } } } @@ -187,7 +187,7 @@ void Server::acceptClient_(int const serverFd) { srvHelp::setSocketNonBlock(clientFd); storePollFd_(clientFd, false); - clients_.add(new Client(), clientFd); + clients_.add(clientFd); std::cout << "Client connected:\n" << " fd: " << clientFd << "\n" From 2b5b9c8c38c90459584240ab7c519253de2ab836 Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 3 Sep 2026 15:27:03 +0200 Subject: [PATCH 194/349] fix: adapt to new function interface --- srcs/server/Server.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 50ad431..b82717d 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -187,7 +187,7 @@ void Server::acceptClient_(int const serverFd) { srvHelp::setSocketNonBlock(clientFd); storePollFd_(clientFd, false); - clients_.add(clientFd); + clients_.add(*(new Client()), clientFd); std::cout << "Client connected:\n" << " fd: " << clientFd << "\n" From 6a73ea9cccbecdc14fda11b016e04dcfc731576d Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 13:20:31 +0200 Subject: [PATCH 195/349] feat: add public interface for sending messages --- includes/Server.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/includes/Server.hpp b/includes/Server.hpp index 879ccd6..2acf8c0 100644 --- a/includes/Server.hpp +++ b/includes/Server.hpp @@ -54,9 +54,9 @@ class Server { ClientManager &getClientManager(); // Pass-through interfaces - // void sendMsg(int const fd, std::string const &msg) { - // sockets_.sendMsg(fd, msg); - // } + void sendMsg(int const fd, std::string const &msg) { + sockets_.sendMsg(fd, msg); + } bool tryRegister(Client &client) { (void)client; From 4761572975a020e7c9596852a40f558032b1f00c Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 13:22:16 +0200 Subject: [PATCH 196/349] feat: send goodby error message to clients when server shuts down --- srcs/server/Server.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index dea7b20..827ee51 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -24,6 +24,7 @@ Server::Server(int const port, std::string const &password) * going backwards through pollFds to ensure server sockets are closed last */ Server::~Server() { + std::string nickName /*= ...get from Manager */; while (!pollFds_.empty()) { int const fd = pollFds_.back().fd; if (!sockets_.isServerSocket(fd)) { From af5f5a231fd804c3ea1dce9ad6f95db12ca679b0 Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 13:32:27 +0200 Subject: [PATCH 197/349] fix: update msg content, including "ERROR", add nickname and hint to inform clientManager --- srcs/server/Server.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 827ee51..12c0e14 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -24,7 +24,6 @@ Server::Server(int const port, std::string const &password) * going backwards through pollFds to ensure server sockets are closed last */ Server::~Server() { - std::string nickName /*= ...get from Manager */; while (!pollFds_.empty()) { int const fd = pollFds_.back().fd; if (!sockets_.isServerSocket(fd)) { @@ -37,6 +36,7 @@ Server::~Server() { } sockets_.closeSocket(fd); pollFds_.erase(--pollFds_.end()); + // inform Account Manager of connection abort } } From 716b83a0171ea168853945f91ff29cb7ba5ce3d0 Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 13:34:29 +0200 Subject: [PATCH 198/349] fix: remove (active) client if corresponding socket is closed --- srcs/server/Server.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 12c0e14..a01e870 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -36,7 +36,7 @@ Server::~Server() { } sockets_.closeSocket(fd); pollFds_.erase(--pollFds_.end()); - // inform Account Manager of connection abort + clients_.remove(fd); } } From 1623e262018398537e55cac6931632c0e20127ed Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 14:00:47 +0200 Subject: [PATCH 199/349] feat: * use add and remove from client manager * set default nick to "*" in client c'tor --- srcs/server/Server.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index a01e870..110ea34 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -36,7 +36,7 @@ Server::~Server() { } sockets_.closeSocket(fd); pollFds_.erase(--pollFds_.end()); - clients_.remove(fd); + clients_.remove(fd); } } From bdb72eb5d2ebf2a9333e269cd86945f51d6cfe24 Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 15:13:12 +0200 Subject: [PATCH 200/349] fix: differentiate btw serv and client socket --- srcs/server/Server.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 110ea34..dea7b20 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -36,7 +36,6 @@ Server::~Server() { } sockets_.closeSocket(fd); pollFds_.erase(--pollFds_.end()); - clients_.remove(fd); } } From a3346aa4d38ed9cd3e32e54199ea62337305934c Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 17:15:03 +0200 Subject: [PATCH 201/349] rfa: remove sendMsg interface in Server, everybody will use the sockets._sendMsg directly --- includes/Server.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/includes/Server.hpp b/includes/Server.hpp index 2acf8c0..879ccd6 100644 --- a/includes/Server.hpp +++ b/includes/Server.hpp @@ -54,9 +54,9 @@ class Server { ClientManager &getClientManager(); // Pass-through interfaces - void sendMsg(int const fd, std::string const &msg) { - sockets_.sendMsg(fd, msg); - } + // void sendMsg(int const fd, std::string const &msg) { + // sockets_.sendMsg(fd, msg); + // } bool tryRegister(Client &client) { (void)client; From 98c5a25c98c200376f69dfd85ec955ea9cf241eb Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 3 Sep 2026 12:40:41 +0200 Subject: [PATCH 202/349] fix: create the ERROR message from scratch, as Reply::Message doesn't allow non-Code generation --- srcs/server/Server.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index dea7b20..50ad431 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -27,12 +27,12 @@ Server::~Server() { while (!pollFds_.empty()) { int const fd = pollFds_.back().fd; if (!sockets_.isServerSocket(fd)) { - std::string nickName = clients_.at(fd)->getNick(); - sockets_.sendMsg( - fd, Reply::build(serverName_, nickName, - Reply::Message(-1, "ERROR", - "Server shutdown - goodbye."))); - clients_.remove(fd); + std::string nickName = clients_.at(fd).getNick(); + std::ostringstream oss; + oss << "ERROR :" << serverName_ << " " << nickName + << " :Server shutdown - goodbye."; + sockets_.sendMsg(fd, oss.str()); + clients_.remove(fd, channels_); } sockets_.closeSocket(fd); pollFds_.erase(--pollFds_.end()); @@ -162,7 +162,7 @@ void Server::processClientSockets_() { if (sockets_.socketClosed(pfd.fd)) { removePollFd_(pfd.fd); - clients_.remove(pfd.fd); + clients_.remove(pfd.fd, channels_); } } } @@ -187,7 +187,7 @@ void Server::acceptClient_(int const serverFd) { srvHelp::setSocketNonBlock(clientFd); storePollFd_(clientFd, false); - clients_.add(new Client(), clientFd); + clients_.add(clientFd); std::cout << "Client connected:\n" << " fd: " << clientFd << "\n" From d9908dc37da37e5f1dcda239b30c21708ae71edc Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 3 Sep 2026 15:27:03 +0200 Subject: [PATCH 203/349] fix: adapt to new function interface --- srcs/server/Server.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 50ad431..b82717d 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -187,7 +187,7 @@ void Server::acceptClient_(int const serverFd) { srvHelp::setSocketNonBlock(clientFd); storePollFd_(clientFd, false); - clients_.add(clientFd); + clients_.add(*(new Client()), clientFd); std::cout << "Client connected:\n" << " fd: " << clientFd << "\n" From dfef9b2b0e94dd66e3c0d76fa94fbb87456d7eed Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 3 Sep 2026 16:12:55 +0200 Subject: [PATCH 204/349] dbg: add info msg in Server d'tor about unexpectedly non-existing client object --- srcs/server/Server.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index b82717d..7703349 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -27,7 +27,19 @@ Server::~Server() { while (!pollFds_.empty()) { int const fd = pollFds_.back().fd; if (!sockets_.isServerSocket(fd)) { - std::string nickName = clients_.at(fd).getNick(); + std::string nickName; + try { + nickName = clients_.at(fd).getNick(); + } catch (std::exception const &e) { + logger_.logMsg("\n Cannot get client object:" + "\n starting from a second client, the object " + "isn't correctly created" + "\n in function bool ClientManager::add(Client " + "&client, int fd)" + "\n Exception: " + + std::string(e.what()), + Logger::lmError); + } std::ostringstream oss; oss << "ERROR :" << serverName_ << " " << nickName << " :Server shutdown - goodbye."; From d9213b4c83a9d79d37b4c017baae846c925cc8eb Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 3 Sep 2026 16:18:33 +0200 Subject: [PATCH 205/349] rfa: rename function to reflect action more precisely --- includes/Socket.hpp | 2 +- srcs/server/Server.cpp | 3 ++- srcs/server/Socket.cpp | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/includes/Socket.hpp b/includes/Socket.hpp index c0970b9..5799091 100644 --- a/includes/Socket.hpp +++ b/includes/Socket.hpp @@ -41,7 +41,7 @@ class Socket { void addSocket(int const fd, bool srvSocket); bool checkSocket(struct pollfd &pfd); // NOLINT - std::string getCmdLine(int const fd, bool &buffHasMoreCmds); + std::string getCRLFLineFromBuffer(int const fd, bool &buffHasMoreCmds); bool sendMsg(int const fd, std::string const &msg); void closeSocket(int const fd); diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 7703349..7f21069 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -154,7 +154,8 @@ void Server::processClientSockets_() { bool buffHasMoreCmds = false; do { try { - cmdLine = sockets_.getCmdLine(pfd.fd, buffHasMoreCmds); + cmdLine = + sockets_.getCRLFLineFromBuffer(pfd.fd, buffHasMoreCmds); if (!cmdLine.empty()) cmds_.run(*this, *client, cmdLine); } catch (Error const &e) { diff --git a/srcs/server/Socket.cpp b/srcs/server/Socket.cpp index 74b9933..de1f722 100644 --- a/srcs/server/Socket.cpp +++ b/srcs/server/Socket.cpp @@ -39,7 +39,7 @@ bool Socket::updateBuffer_(int const fd) { return true; } -std::string Socket::getCmdLine(int const fd, bool &buffHasMoreCmds) { +std::string Socket::getCRLFLineFromBuffer(int const fd, bool &buffHasMoreCmds) { Socket::SocketIOData &sIOD = sockIODataStore_.at(fd); std::string cmdLine; From bb0e973277b8899ba2d88054dcc81e98114cfd81 Mon Sep 17 00:00:00 2001 From: Rapha Date: Sun, 6 Sep 2026 11:38:47 +0200 Subject: [PATCH 206/349] feat: revert additions that could potentially have been used for a disconnect message. --- includes/Reply.hpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/includes/Reply.hpp b/includes/Reply.hpp index 3e69062..9c37c30 100644 --- a/includes/Reply.hpp +++ b/includes/Reply.hpp @@ -267,10 +267,8 @@ Use "*" for `nick` until the client has registered (pre-001). inline std::string build(const std::string &server, const std::string &nick, const Message &msg) { std::ostringstream ss; - ss << ":" << server; - if (msg.code > 0) - ss << " " << std::setw(3) << std::setfill('0') << msg.code; - ss << " " << nick; + ss << ":" << server << " " << std::setw(3) << std::setfill('0') << msg.code + << " " << nick; if (!msg.params.empty()) ss << " " << msg.params; if (msg.hasTrailing) From 3745b5e874db22fdbe43804cc9e594e7e9abfa56 Mon Sep 17 00:00:00 2001 From: Rapha Date: Sun, 6 Sep 2026 12:46:36 +0200 Subject: [PATCH 207/349] rfa: update includes using tool iwyu --- Makefile | 3 +++ includes/Logger.hpp | 8 ++------ includes/Server.hpp | 18 ++++++++++-------- includes/Socket.hpp | 10 ++++++---- srcs/main.cpp | 4 +++- srcs/server/Logger.cpp | 4 ++++ srcs/server/Server.cpp | 22 +++++++++++++--------- srcs/server/SignalHandler.cpp | 8 +++----- srcs/server/Socket.cpp | 16 ++++++++-------- 9 files changed, 52 insertions(+), 41 deletions(-) diff --git a/Makefile b/Makefile index 72a480c..2f502fb 100644 --- a/Makefile +++ b/Makefile @@ -79,6 +79,9 @@ tidy-all: @find srcs includes -type f \( -name "*.cpp" -o -name "*.hpp" \) -print0 \ | xargs -0 -I {} clang-tidy {} -- $(CXXFLAGS) $(IFLAGS) +iwyu: fclean + make -k CXX=include-what-you-use CXXFLAGS="-Xiwyu --error_always" + $(NAME): $(OBJS) $(CXX) $(CXXFLAGS) $(OBJS) -o $@ diff --git a/includes/Logger.hpp b/includes/Logger.hpp index 5be496b..9cee6ef 100644 --- a/includes/Logger.hpp +++ b/includes/Logger.hpp @@ -1,11 +1,7 @@ #pragma once -#include "colours.hpp" - -#include -#include -#include -#include +#include // for localtime, strftime, time, time_t +#include // for basic_string, string class Logger { std::string prefix_; diff --git a/includes/Server.hpp b/includes/Server.hpp index 879ccd6..fd33629 100644 --- a/includes/Server.hpp +++ b/includes/Server.hpp @@ -1,14 +1,16 @@ #pragma once -#include "ChannelManager.hpp" -#include "ClientManager.hpp" -#include "CommandProcessor.hpp" -#include "Logger.hpp" -#include "Socket.hpp" +#include "ChannelManager.hpp" // for ChannelManager +#include "ClientManager.hpp" // for ClientManager +#include "CommandProcessor.hpp" // for CommandProcessor +#include "Logger.hpp" // for Logger +#include "Socket.hpp" // for Socket -#include -#include -#include +#include // for pollfd +#include // for size_t +#include // for string, basic_string +#include // for vector +class Client; #define SERVER_NAME "irc.local" #define MAX_CONNECTIONS 512 diff --git a/includes/Socket.hpp b/includes/Socket.hpp index 5799091..6371993 100644 --- a/includes/Socket.hpp +++ b/includes/Socket.hpp @@ -1,10 +1,12 @@ #pragma once -#include "Logger.hpp" +#include "Logger.hpp" // for Logger -#include -#include -#include +#include // for map, operator== +#include // for size_t +#include // for string, basic_string +#include // for ssize_t +#include // for pair #define RECVBUFFLEN 1024 #define MAXMSGLEN 512 diff --git a/srcs/main.cpp b/srcs/main.cpp index 66dfd59..2788ea2 100644 --- a/srcs/main.cpp +++ b/srcs/main.cpp @@ -1,9 +1,11 @@ #include "Server.hpp" #include "SignalHandler.hpp" +#include "colours.hpp" // for CReset, CCyan, CGreen, CYellow -#include #include +#include // for cerr, cout #include +#include // for runtime_error #include static bool checkExtractCmdLnArgs(int const argc, char *const argv[], int &port, diff --git a/srcs/server/Logger.cpp b/srcs/server/Logger.cpp index 61191de..16190d2 100644 --- a/srcs/server/Logger.cpp +++ b/srcs/server/Logger.cpp @@ -1,5 +1,9 @@ #include "Logger.hpp" +#include "colours.hpp" // for CGreen, CMgnta, CRed, CReset, CYellow + +#include // for basic_ostream, operator<<, basic_ios, cerr, cout + /* Log strings in four categories (severity) in different colours to stdout/stderr respectively. diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 7f21069..affac0d 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -1,14 +1,18 @@ #include "Server.hpp" -#include "Errors.hpp" -#include "ServerHelper.hpp" -#include "SignalHandler.hpp" - -#include -#include -#include -#include -#include +#include "Client.hpp" // for Client +#include "Errors.hpp" // for Error +#include "Reply.hpp" // for build +#include "ServerHelper.hpp" // for getAdressFamilyStr, composeAddrfamAddrP... +#include "SignalHandler.hpp" // for SignalHandler + +#include // for exception +#include // for basic_ostream, operator<<, basic_ostrea... +#include // for IPPROTO_IPV6, IPV6_V6ONLY +#include // for basic_ostringstream +#include // for runtime_error, invalid_argument +#include // for allocator, char_traits, basic_string +#include // for setsockopt, AF_INET6, AF_INET, accept /* Server Constructor diff --git a/srcs/server/SignalHandler.cpp b/srcs/server/SignalHandler.cpp index 8fd1105..415275b 100644 --- a/srcs/server/SignalHandler.cpp +++ b/srcs/server/SignalHandler.cpp @@ -1,10 +1,8 @@ #include "SignalHandler.hpp" -#include -#include -#include -#include -#include +#include // for sigaction, sig_atomic_t, sigemptyset, SIGINT +#include // for NULL +#include // for runtime_error /* Signal handling diff --git a/srcs/server/Socket.cpp b/srcs/server/Socket.cpp index de1f722..fefd917 100644 --- a/srcs/server/Socket.cpp +++ b/srcs/server/Socket.cpp @@ -1,13 +1,13 @@ #include "Socket.hpp" -#include "Errors.hpp" -#include "Reply.hpp" -#include "ServerHelper.hpp" - -#include -#include -#include -#include +#include "Errors.hpp" // for Error +#include "Reply.hpp" // for reply, Code +#include "ServerHelper.hpp" // for getAdressFamilyStr, sockAddrFamily + +#include // for pollfd, POLLERR +#include // for basic_ostringstream, basic_ostream, oper... +#include // for recv, send, MSG_NOSIGNAL +#include // for close void Socket::addSocket(int const fd, bool srvSocket) { sockIODataStore_.insert(std::make_pair(fd, SocketIOData(srvSocket))); From acbed29470b8c75abbf5e3ec2e48adda43b1a91e Mon Sep 17 00:00:00 2001 From: Rapha Date: Mon, 7 Sep 2026 13:09:49 +0200 Subject: [PATCH 208/349] fix: set nick_ later --- includes/Client.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/Client.hpp b/includes/Client.hpp index c8dff0e..a9d73ec 100644 --- a/includes/Client.hpp +++ b/includes/Client.hpp @@ -26,7 +26,7 @@ class Client { public: Client() - : nick_("*"), registered_(false), passAccepted_(false), oper_(false) {} + : registered_(false), passAccepted_(false), oper_(false) {} std::string getNick() const; std::string getNickCaseFolded() const; From cceb3a8e621de180472e8a321a5c500f4f2caf16 Mon Sep 17 00:00:00 2001 From: Rapha Date: Mon, 7 Sep 2026 13:10:48 +0200 Subject: [PATCH 209/349] fix: * use idx instead of fd. * add --idx bc of removed element to avaoid skip --- srcs/server/Server.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index affac0d..3bd7761 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -178,8 +178,9 @@ void Server::processClientSockets_() { } if (sockets_.socketClosed(pfd.fd)) { - removePollFd_(pfd.fd); clients_.remove(pfd.fd, channels_); + removePollFd_(idx); + --idx; } } } From ae2fb442de83329607fd438e7ecbd3fbfd33516c Mon Sep 17 00:00:00 2001 From: Rapha Date: Mon, 7 Sep 2026 13:13:22 +0200 Subject: [PATCH 210/349] rfa: clang-format --- includes/Client.hpp | 3 +-- srcs/server/Server.cpp | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/includes/Client.hpp b/includes/Client.hpp index a9d73ec..e46229a 100644 --- a/includes/Client.hpp +++ b/includes/Client.hpp @@ -25,8 +25,7 @@ class Client { std::set invitedTo_; public: - Client() - : registered_(false), passAccepted_(false), oper_(false) {} + Client() : registered_(false), passAccepted_(false), oper_(false) {} std::string getNick() const; std::string getNickCaseFolded() const; diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 3bd7761..69d25c9 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -179,8 +179,8 @@ void Server::processClientSockets_() { if (sockets_.socketClosed(pfd.fd)) { clients_.remove(pfd.fd, channels_); - removePollFd_(idx); - --idx; + removePollFd_(idx); + --idx; } } } From 5307c1a2c1fbb60044f519a70c4d74a7d98a20fa Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 10 Sep 2026 11:27:20 +0200 Subject: [PATCH 211/349] feat: * separate removeClosedSockets - needs to be done in one go at the end of a poll-loop * use fd in removePollFd (add functor pfdHasFd for use in find_if (lambdas not available in c++98) * add msgTooLong to bufferLength... function to notify that situation * eliminate use of socketClosed in isServerSocket - too much overhead * prepare Server d'tor for use of soon to be added function sendClosingMsg * remove try/catch, this block won't throw after rfa in other PR * disable output (only active for NETCATDBG) * add new passthrough Server::sendMsg * change exception msg text in SignalHnadler * add socketClosed check in checkSocket4Recv - it may habe been closed somewhere else * implement new msgTooLong functionality and reorder var definitions * disable error count for msgTooLong, this should be handled by cmds_.run (deprecated, will be reverted) rfa: * rename checkSocket for more clarity dbg: * add snippet to force crash for dbg purpose --- includes/Server.hpp | 8 ++- includes/ServerHelper.hpp | 12 +++++ includes/Socket.hpp | 13 ++--- reviewNotes.txt | 27 ++++++++++ srcs/server/Server.cpp | 98 +++++++++++++++++++++++++---------- srcs/server/SignalHandler.cpp | 2 +- srcs/server/Socket.cpp | 96 ++++++++++++++++++---------------- 7 files changed, 170 insertions(+), 86 deletions(-) create mode 100644 reviewNotes.txt diff --git a/includes/Server.hpp b/includes/Server.hpp index fd33629..4447524 100644 --- a/includes/Server.hpp +++ b/includes/Server.hpp @@ -36,8 +36,9 @@ class Server { void processServerSocketData_(); void acceptClient_(int const serverFd); void processClientSockets_(); + void removeClosedSockets_(); void storePollFd_(int const socketFd, bool srvSocket); - void removePollFd_(int const idx); + void removePollFd_(int const fd); void setClientInfo_(int fd, Client **clntPtr, std::string &nick, bool &isReg); void log_(std::string const &msg, Logger::logMode const lm); @@ -50,16 +51,13 @@ class Server { ~Server(); void run(); + void sendMsg(int const fd, std::string const &msg); const std::string &getServerName() const; ChannelManager &getChannelManager(); ClientManager &getClientManager(); // Pass-through interfaces - // void sendMsg(int const fd, std::string const &msg) { - // sockets_.sendMsg(fd, msg); - // } - bool tryRegister(Client &client) { (void)client; return true; diff --git a/includes/ServerHelper.hpp b/includes/ServerHelper.hpp index 7e9d387..015ea06 100644 --- a/includes/ServerHelper.hpp +++ b/includes/ServerHelper.hpp @@ -106,4 +106,16 @@ inline std::string composeAddrfamAddrPortStr(socketAddrX const &sockAddrIn46) { return oss.str(); } +class pfdHasFd { + private: + int const fd_; + + public: + pfdHasFd(int fd) : fd_(fd) {} + + bool operator()(pollfd const pfd) const { + return (pfd.fd == fd_); + } +}; + } // namespace srvHelp diff --git a/includes/Socket.hpp b/includes/Socket.hpp index 6371993..181fe03 100644 --- a/includes/Socket.hpp +++ b/includes/Socket.hpp @@ -34,15 +34,14 @@ class Socket { std::map sockIODataStore_; Logger const &logger_; - bool updateBuffer_(int const fd); - size_t - bufferLengthAndSanityCheck_(int const fd /* , bool const isRegistered */); + bool updateBuffer_(int const fd); + size_t bufferLengthAndSanityCheck_(int const fd, bool &msgTooLong); public: Socket(Logger const &logger) : logger_(logger) {} void addSocket(int const fd, bool srvSocket); - bool checkSocket(struct pollfd &pfd); // NOLINT + bool checkSocket4Recv(struct pollfd &pfd); // NOLINT std::string getCRLFLineFromBuffer(int const fd, bool &buffHasMoreCmds); bool sendMsg(int const fd, std::string const &msg); void closeSocket(int const fd); @@ -52,8 +51,10 @@ class Socket { } bool isServerSocket(int const fd) const { - if (!socketClosed(fd)) - return sockIODataStore_.find(fd)->second.isSrvSocket; + std::map::const_iterator it = + sockIODataStore_.find(fd); + if (it != sockIODataStore_.end()) + return it->second.isSrvSocket; return false; }; }; diff --git a/reviewNotes.txt b/reviewNotes.txt new file mode 100644 index 0000000..6613ea9 --- /dev/null +++ b/reviewNotes.txt @@ -0,0 +1,27 @@ +was macht Client::remove genau + * wo wird das aufgerufen + * wie wird sichergestellt, dass gleichzeitig auf pollfds gelöscht wird? + * sollte remove vllt nur von Server:: aufgerufen werden, wenn auch pollfds entfernt wird? + +Usage of exceptions to send replies from clients -> sendMsg +I'll take out the try catch, will have to see about sending the msg too long error. +Maybe this can be passed on to the cmds_.run, how to indicate? + +poll loop has to be reworked, clients cannot be closed/removed in a running poll loop with sending msgs, because maybe a set of clients was gathered elsewhere and this would result in invalid pointers. + +clients_.add( -> creation of Client should be moved inside this function. +* https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#i11-never-transfer-ownership-by-a-raw-pointer-t-or-reference-t + +multiple "*" vs nick_.empty -> maybe as a MACRO + +call setHostname in acceptClient_ (rhaas) +no: instead of passing a new Client pass serverName +instead of passing client reference to cmds.run, give it the fileDescr + + +move msgTooLong to cmds_.run -> yes. +no error counter for line too long, maybe moves to the cmd processor + +comd handler won't throw + +make separate PR for Server::sendMsg diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 69d25c9..36b27da 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -9,6 +9,7 @@ #include // for exception #include // for basic_ostream, operator<<, basic_ostrea... #include // for IPPROTO_IPV6, IPV6_V6ONLY +#include #include // for basic_ostringstream #include // for runtime_error, invalid_argument #include // for allocator, char_traits, basic_string @@ -31,6 +32,10 @@ Server::~Server() { while (!pollFds_.empty()) { int const fd = pollFds_.back().fd; if (!sockets_.isServerSocket(fd)) { + // Update after Call client member function for shutdown notice message! + // after PR feat/cmds2: implement QUIT, PING, PONG and NOTICE - #38# +//#define REMOVE_ME +#ifndef REMOVE_ME std::string nickName; try { nickName = clients_.at(fd).getNick(); @@ -48,10 +53,15 @@ Server::~Server() { oss << "ERROR :" << serverName_ << " " << nickName << " :Server shutdown - goodbye."; sockets_.sendMsg(fd, oss.str()); +#else + Client &client = clients_.at(fd); + client.sendClosingMsg("Server shutdown - goodbye"); +#endif clients_.remove(fd, channels_); } sockets_.closeSocket(fd); - pollFds_.erase(--pollFds_.end()); + this->removePollFd_(fd); + // pollFds_.erase(--pollFds_.end()); } } @@ -124,6 +134,11 @@ void Server::processServerSocketData_() { if (serverPollFds.revents & POLLIN) acceptClient_(serverPollFds.fd); } + + // DEBUG check if cleanup works correctly + static size_t crash = 0; + if (++crash > 5) + throw std::runtime_error("Server socket error."); } void Server::setClientInfo_(int fd, Client **clntPtr, std::string &nick, @@ -145,41 +160,56 @@ void Server::processClientSockets_() { ++idx) { pollfd &pfd = pollFds_.at(idx); - if (sockets_.checkSocket(pfd) && (pfd.revents & POLLIN)) { + if (sockets_.checkSocket4Recv(pfd) && (pfd.revents & POLLIN)) { - // to be replaced by a request to some sort of manager - Client *client = 0; - std::string nickName = "*"; - bool clntIsReg = false; - setClientInfo_(pfd.fd, &client, nickName, clntIsReg); - // end: to be replaced by a request to some sort of manager + // // to be replaced by a request to some sort of manager + // Client *client = 0; + // std::string nickName = "*"; + // bool clntIsReg = false; + // setClientInfo_(pfd.fd, &client, nickName, clntIsReg); + // // end: to be replaced by a request to some sort of manager std::string cmdLine; bool buffHasMoreCmds = false; do { - try { - cmdLine = - sockets_.getCRLFLineFromBuffer(pfd.fd, buffHasMoreCmds); - if (!cmdLine.empty()) - cmds_.run(*this, *client, cmdLine); - } catch (Error const &e) { - if (e.hasReply()) { - sockets_.sendMsg( - pfd.fd, - Reply::build(serverName_, nickName, e.reply())); - } else { - std::string tmp( - "Error while trying to get/execute cmd:\n"); - tmp.append("'" + cmdLine + "'\n" + e.what()); - log_(tmp, Logger::lmExcept); + // try { + cmdLine = + sockets_.getCRLFLineFromBuffer(pfd.fd, buffHasMoreCmds); + if (!cmdLine.empty()) { + // returns true if connection is to be closed + // only happens after a QUIT cmd or maybe after too many ERR_INPUTTOOLONG + if (cmds_.run( + *this, + /* should soon be: pfd.fd */ clients_.at(pfd.fd), + cmdLine)) { + sockets_.closeSocket(pfd.fd); + break; } } + // } catch (Error const &e) { + // if (e.hasReply()) { + // sockets_.sendMsg( + // pfd.fd, + // Reply::build(serverName_, nickName, e.reply())); + // } else { + // std::string tmp( + // "Error while trying to get/execute cmd:\n"); + // tmp.append("'" + cmdLine + "'\n" + e.what()); + // log_(tmp, Logger::lmExcept); + // } + // } } while (buffHasMoreCmds); } + } + removeClosedSockets_(); +} +void Server::removeClosedSockets_() { + for (size_t idx = numServerSockets_; idx < pollFds_.size(); ++idx) { + pollfd &pfd = pollFds_.at(idx); if (sockets_.socketClosed(pfd.fd)) { clients_.remove(pfd.fd, channels_); - removePollFd_(idx); + removePollFd_(pfd.fd); --idx; } } @@ -207,9 +237,11 @@ void Server::acceptClient_(int const serverFd) { storePollFd_(clientFd, false); clients_.add(*(new Client()), clientFd); +#if NETCATDBG std::cout << "Client connected:\n" << " fd: " << clientFd << "\n" << srvHelp::composeAddrfamAddrPortStr(sockAddrIn46) << std::endl; +#endif } void Server::storePollFd_(int const socketFd, bool srvSocket) { @@ -221,10 +253,11 @@ void Server::storePollFd_(int const socketFd, bool srvSocket) { sockets_.addSocket(socketFd, srvSocket); } -void Server::removePollFd_(int const idx) { - std::vector::difference_type dist = - static_cast::difference_type>(idx); - pollFds_.erase(pollFds_.begin() + dist); +void Server::removePollFd_(int const fd) { + std::vector::iterator it = + std::find_if(pollFds_.begin(), pollFds_.end(), srvHelp::pfdHasFd(fd)); + if (it != pollFds_.end()) + pollFds_.erase(it); } void Server::log_(std::string const &msg, Logger::logMode const lm) { @@ -266,3 +299,12 @@ void Server::run() { processClientSockets_(); } } + +void Server::sendMsg(int const fd, std::string const &msg) { + if (!sockets_.sendMsg(fd, msg)) { + // will be done at the end of a poll loop + + // clients_.remove(fd, channels_); + // removePollFd_(fd); + } +} \ No newline at end of file diff --git a/srcs/server/SignalHandler.cpp b/srcs/server/SignalHandler.cpp index 415275b..852bd52 100644 --- a/srcs/server/SignalHandler.cpp +++ b/srcs/server/SignalHandler.cpp @@ -52,7 +52,7 @@ SignalHandler::SignalHandler() { stopSignals_.push_back(SIGTERM); if (!setStopSignals_(handleStopSignals_)) - throw std::runtime_error("std::signal failed"); + throw std::runtime_error("Setting stop signals failed"); } /* diff --git a/srcs/server/Socket.cpp b/srcs/server/Socket.cpp index fefd917..1dfa0c6 100644 --- a/srcs/server/Socket.cpp +++ b/srcs/server/Socket.cpp @@ -13,7 +13,10 @@ void Socket::addSocket(int const fd, bool srvSocket) { sockIODataStore_.insert(std::make_pair(fd, SocketIOData(srvSocket))); } -bool Socket::checkSocket(struct pollfd &pfd) { +bool Socket::checkSocket4Recv(struct pollfd &pfd) { + if (socketClosed(pfd.fd)) + return false; + SocketIOData &sIOD = sockIODataStore_.at(pfd.fd); if ((pfd.revents & POLLERR)) { if (sIOD.recvErrorCount >= SOCKETERRORLIMIT) @@ -40,50 +43,51 @@ bool Socket::updateBuffer_(int const fd) { } std::string Socket::getCRLFLineFromBuffer(int const fd, bool &buffHasMoreCmds) { - Socket::SocketIOData &sIOD = sockIODataStore_.at(fd); - std::string cmdLine; - - if (!buffHasMoreCmds) + if (!buffHasMoreCmds) { if (!updateBuffer_(fd)) - return cmdLine; + return ""; + } + + bool msgTooLong = false; + size_t lfcrPos = bufferLengthAndSanityCheck_(fd, msgTooLong); + if (msgTooLong) + return std::string(MAXMSGLEN + 1, '\0'); + if (lfcrPos == std::string::npos) + return ""; - size_t lfcrPos = bufferLengthAndSanityCheck_(fd); - if (lfcrPos != std::string::npos) { + Socket::SocketIOData &sIOD = sockIODataStore_.at(fd); #if NETCATDBG - { - std::ostringstream oss; - oss << "You are client fd " << fd << ": \n" - << "Srv received: " << std::string(sIOD.readBuffer) - << std::endl; - sendMsg(fd, oss.str()); - } -#endif - sIOD.recvErrorCount = 0; - cmdLine = sIOD.readBuffer.substr(0, lfcrPos); - sIOD.readBuffer = sIOD.readBuffer.substr(lfcrPos + 2); - buffHasMoreCmds = (sIOD.readBuffer.find("\r\n") != std::string::npos); + { + std::ostringstream oss; + oss << "You are client fd " << fd << ": \n" + << "Srv received: " << std::string(sIOD.readBuffer) << std::endl; + sendMsg(fd, oss.str()); } +#endif + std::string cmdLine; + cmdLine = sIOD.readBuffer.substr(0, lfcrPos); + sIOD.recvErrorCount = 0; + sIOD.readBuffer = sIOD.readBuffer.substr(lfcrPos + 2); + buffHasMoreCmds = (sIOD.readBuffer.find("\r\n") != std::string::npos); return cmdLine; } -size_t Socket::bufferLengthAndSanityCheck_( - int const fd /* , bool const isRegistered */) { +size_t Socket::bufferLengthAndSanityCheck_(int const fd, bool &msgTooLong) { Socket::SocketIOData &sIOD = sockIODataStore_.at(fd); std::string &rdBuffer = sIOD.readBuffer; size_t lfcrPos = rdBuffer.find("\r\n"); - bool msgTooLong = false; - if ((rdBuffer.length() >= MAXMSGLEN) && + if ((!sIOD.discardMode) && (rdBuffer.length() >= MAXMSGLEN) && (lfcrPos == std::string::npos || lfcrPos > MAXMSGLEN - 2)) { sIOD.discardMode = true; - ++sIOD.recvErrorCount; msgTooLong = true; + // ++sIOD.recvErrorCount; } if (sIOD.discardMode) { - if (/* !isRegistered || */ sIOD.recvErrorCount >= SOCKETERRORLIMIT) { - closeSocket(fd); - return std::string::npos; - } + // if (sIOD.recvErrorCount >= SOCKETERRORLIMIT) { + // closeSocket(fd); + // return std::string::npos; + // } if (lfcrPos != std::string::npos) { rdBuffer.erase(0, lfcrPos + 2); sIOD.discardMode = false; @@ -95,8 +99,8 @@ size_t Socket::bufferLengthAndSanityCheck_( rdBuffer.clear(); } } - if (msgTooLong) - throw Error(Reply::reply(Reply::ERR_INPUTTOOLONG, "")); + // if (msgTooLong) + // throw Error(Reply::reply(Reply::ERR_INPUTTOOLONG, "")); return lfcrPos; } @@ -108,6 +112,9 @@ size_t Socket::bufferLengthAndSanityCheck_( * closes the socket (== disconnects the client) in case of too many errors */ bool Socket::sendMsg(int const fd, std::string const &msg) { + if (socketClosed(fd)) + return false; + SocketIOData &sIOD = sockIODataStore_.at(fd); ssize_t bytesSent = -1; bool success = true; @@ -126,23 +133,20 @@ bool Socket::sendMsg(int const fd, std::string const &msg) { success = false; } -#if NETCATDBG - std::ostringstream oss; - oss << "SendMsg event:\n" - << " WriteBuffer: '" << sIOD.writeBuffer << "':\n"; - if (!success) { - oss << " send to FD " << fd << " failed."; - logger_.logMsg(oss.str(), Logger::lmError); - } else { - if (static_cast(bytesSent) < sIOD.writeBuffer.length()) + { + std::ostringstream oss; + oss << "SendMsg event:\n" + << " WriteBuffer: '" << sIOD.writeBuffer << "':\n"; + if (!success) { + oss << " send to FD " << fd << " failed."; + logger_.logMsg(oss.str(), Logger::lmError); + } else if (static_cast(bytesSent) < sIOD.writeBuffer.length()) { oss << " partly (" << bytesSent << "/" << sIOD.writeBuffer.length() - << ") " << sIOD.writeBuffer.substr(0, bytesSent); - else - oss << " successfully"; - oss << " sent to FD: " << fd; - logger_.logMsg(oss.str(), Logger::lmInfo); + << ") " << sIOD.writeBuffer.substr(0, bytesSent) + << " sent to FD: " << fd; + logger_.logMsg(oss.str(), Logger::lmInfo); + } } -#endif if (!success) return false; From 004659b4fd60c210712c73204bdcb2bb57859d45 Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 10 Sep 2026 11:48:51 +0200 Subject: [PATCH 212/349] feat: * reactivate error count for msgTooLong --- srcs/server/Socket.cpp | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/srcs/server/Socket.cpp b/srcs/server/Socket.cpp index 1dfa0c6..198aa90 100644 --- a/srcs/server/Socket.cpp +++ b/srcs/server/Socket.cpp @@ -81,16 +81,16 @@ size_t Socket::bufferLengthAndSanityCheck_(int const fd, bool &msgTooLong) { (lfcrPos == std::string::npos || lfcrPos > MAXMSGLEN - 2)) { sIOD.discardMode = true; msgTooLong = true; - // ++sIOD.recvErrorCount; } if (sIOD.discardMode) { - // if (sIOD.recvErrorCount >= SOCKETERRORLIMIT) { - // closeSocket(fd); - // return std::string::npos; - // } + if (++sIOD.recvErrorCount >= SOCKETERRORLIMIT) { + closeSocket(fd); + return std::string::npos; + } if (lfcrPos != std::string::npos) { rdBuffer.erase(0, lfcrPos + 2); sIOD.discardMode = false; + sIOD.recvErrorCount = 0; lfcrPos = rdBuffer.find("\r\n"); } else /* lfcrPos == std::string::npos */ { if ('\r' == *rdBuffer.rbegin()) @@ -99,9 +99,6 @@ size_t Socket::bufferLengthAndSanityCheck_(int const fd, bool &msgTooLong) { rdBuffer.clear(); } } - // if (msgTooLong) - // throw Error(Reply::reply(Reply::ERR_INPUTTOOLONG, "")); - return lfcrPos; } From 9dc562421dfd632146f834ac1f370b7ac904e219 Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 10 Sep 2026 12:00:33 +0200 Subject: [PATCH 213/349] rfa: clang-format --- includes/Socket.hpp | 6 ------ srcs/server/Server.cpp | 5 ----- srcs/server/Socket.cpp | 8 -------- 3 files changed, 19 deletions(-) diff --git a/includes/Socket.hpp b/includes/Socket.hpp index f871ad1..181fe03 100644 --- a/includes/Socket.hpp +++ b/includes/Socket.hpp @@ -1,13 +1,7 @@ #pragma once -#include "Logger.hpp" // for Logger #include "Logger.hpp" // for Logger -#include // for map, operator== -#include // for size_t -#include // for string, basic_string -#include // for ssize_t -#include // for pair #include // for map, operator== #include // for size_t #include // for string, basic_string diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 8e77b96..5e802e1 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -1,10 +1,5 @@ #include "Server.hpp" -#include "Client.hpp" // for Client -#include "Errors.hpp" // for Error -#include "Reply.hpp" // for build -#include "ServerHelper.hpp" // for getAdressFamilyStr, composeAddrfamAddrP... -#include "SignalHandler.hpp" // for SignalHandler #include "Client.hpp" // for Client #include "Errors.hpp" // for Error #include "Reply.hpp" // for build diff --git a/srcs/server/Socket.cpp b/srcs/server/Socket.cpp index 55cbae4..198aa90 100644 --- a/srcs/server/Socket.cpp +++ b/srcs/server/Socket.cpp @@ -1,16 +1,9 @@ #include "Socket.hpp" -#include "Errors.hpp" // for Error -#include "Reply.hpp" // for reply, Code -#include "ServerHelper.hpp" // for getAdressFamilyStr, sockAddrFamily #include "Errors.hpp" // for Error #include "Reply.hpp" // for reply, Code #include "ServerHelper.hpp" // for getAdressFamilyStr, sockAddrFamily -#include // for pollfd, POLLERR -#include // for basic_ostringstream, basic_ostream, oper... -#include // for recv, send, MSG_NOSIGNAL -#include // for close #include // for pollfd, POLLERR #include // for basic_ostringstream, basic_ostream, oper... #include // for recv, send, MSG_NOSIGNAL @@ -151,7 +144,6 @@ bool Socket::sendMsg(int const fd, std::string const &msg) { logger_.logMsg(oss.str(), Logger::lmInfo); } } -#endif if (!success) return false; From ad262f4efa5ccb51f7c1ebb39e8235886431ed83 Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 10 Sep 2026 12:05:04 +0200 Subject: [PATCH 214/349] feat: * rework ClientManager::add to only take a fd; the Client * can be created if necessary there, doesn't hvae to be done in Server --- includes/ClientManager.hpp | 6 +++--- srcs/server/ClientManager.cpp | 5 +++-- srcs/server/Server.cpp | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/includes/ClientManager.hpp b/includes/ClientManager.hpp index 97cc01e..3e50e96 100644 --- a/includes/ClientManager.hpp +++ b/includes/ClientManager.hpp @@ -49,9 +49,9 @@ class ClientManager { bool hasNick(const std::string &name) const; std::size_t size() const; - // add takes ownership of `client`; returns false (and does not take - // ownership) if `client` is NULL or `fd` is already present. - bool add(int fd, Client *client); + // add takes a file descriptor of a newly created socket + // returns false if `fd` is already present. + bool add(int fd); // setNick changes `client`'s nick and keeps the nick index in sync. // The caller validates the nick and checks hasNick() first. void setNick(Client &client, const std::string &nick); diff --git a/srcs/server/ClientManager.cpp b/srcs/server/ClientManager.cpp index 7cc7a1c..f1f9ce5 100644 --- a/srcs/server/ClientManager.cpp +++ b/srcs/server/ClientManager.cpp @@ -46,9 +46,10 @@ std::size_t ClientManager::size() const { return byFd_.size(); } -bool ClientManager::add(int fd, Client *client) { - if (!client || hasClient(fd)) +bool ClientManager::add(int fd) { + if (hasClient(fd)) return false; + Client *client = new Client(fd); byFd_.insert(std::make_pair(fd, client)); if (!client->getNick().empty()) byNick_.insert(std::make_pair(client->getNickCaseFolded(), fd)); diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 5e802e1..ef374b0 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -242,7 +242,7 @@ void Server::acceptClient_(int const serverFd) { srvHelp::setSocketNonBlock(clientFd); storePollFd_(clientFd, false); - clients_.add(clientFd, new Client(clientFd)); + clients_.add(clientFd); #if NETCATDBG std::cout << "Client connected:\n" From 6dee2ccdab717661bd7677626017e3aa9d0f7012 Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 10 Sep 2026 12:07:38 +0200 Subject: [PATCH 215/349] feat: * init clients hostname with servername --- includes/Client.hpp | 5 +++-- includes/ClientManager.hpp | 4 ++-- srcs/server/ClientManager.cpp | 4 ++-- srcs/server/Server.cpp | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/includes/Client.hpp b/includes/Client.hpp index a7fea9f..41ddbdc 100644 --- a/includes/Client.hpp +++ b/includes/Client.hpp @@ -29,8 +29,9 @@ class Client { Client() : fd_(-1), registered_(false), passAccepted_(false), oper_(false) {} - explicit Client(int fd) - : fd_(fd), registered_(false), passAccepted_(false), oper_(false) {} + explicit Client(int fd, std::string const &serverName) + : fd_(fd), hostname_(serverName), registered_(false), + passAccepted_(false), oper_(false) {} int getFd() const; std::string getNick() const; diff --git a/includes/ClientManager.hpp b/includes/ClientManager.hpp index 3e50e96..dbb0d44 100644 --- a/includes/ClientManager.hpp +++ b/includes/ClientManager.hpp @@ -50,8 +50,8 @@ class ClientManager { std::size_t size() const; // add takes a file descriptor of a newly created socket - // returns false if `fd` is already present. - bool add(int fd); + // returns false if `fd` is already present. + bool add(int fd, std::string const &serverName); // setNick changes `client`'s nick and keeps the nick index in sync. // The caller validates the nick and checks hasNick() first. void setNick(Client &client, const std::string &nick); diff --git a/srcs/server/ClientManager.cpp b/srcs/server/ClientManager.cpp index f1f9ce5..2cf0fa2 100644 --- a/srcs/server/ClientManager.cpp +++ b/srcs/server/ClientManager.cpp @@ -46,10 +46,10 @@ std::size_t ClientManager::size() const { return byFd_.size(); } -bool ClientManager::add(int fd) { +bool ClientManager::add(int fd, std::string const &serverName) { if (hasClient(fd)) return false; - Client *client = new Client(fd); + Client *client = new Client(fd, serverName); byFd_.insert(std::make_pair(fd, client)); if (!client->getNick().empty()) byNick_.insert(std::make_pair(client->getNickCaseFolded(), fd)); diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index ef374b0..6718a38 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -242,7 +242,7 @@ void Server::acceptClient_(int const serverFd) { srvHelp::setSocketNonBlock(clientFd); storePollFd_(clientFd, false); - clients_.add(clientFd); + clients_.add(clientFd, serverName_); #if NETCATDBG std::cout << "Client connected:\n" From 49a62ff6c253f76c94f67d2159650a89ece891c6 Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 10 Sep 2026 12:12:01 +0200 Subject: [PATCH 216/349] git: ignore personal review file --- .gitignore | 1 + reviewNotes.txt | 27 --------------------------- 2 files changed, 1 insertion(+), 27 deletions(-) delete mode 100644 reviewNotes.txt diff --git a/.gitignore b/.gitignore index f36d625..c183ee7 100644 --- a/.gitignore +++ b/.gitignore @@ -74,3 +74,4 @@ vcpkg_installed/ # test output & cache Testing/ .cache/ +reviewNotes.txt diff --git a/reviewNotes.txt b/reviewNotes.txt deleted file mode 100644 index 6613ea9..0000000 --- a/reviewNotes.txt +++ /dev/null @@ -1,27 +0,0 @@ -was macht Client::remove genau - * wo wird das aufgerufen - * wie wird sichergestellt, dass gleichzeitig auf pollfds gelöscht wird? - * sollte remove vllt nur von Server:: aufgerufen werden, wenn auch pollfds entfernt wird? - -Usage of exceptions to send replies from clients -> sendMsg -I'll take out the try catch, will have to see about sending the msg too long error. -Maybe this can be passed on to the cmds_.run, how to indicate? - -poll loop has to be reworked, clients cannot be closed/removed in a running poll loop with sending msgs, because maybe a set of clients was gathered elsewhere and this would result in invalid pointers. - -clients_.add( -> creation of Client should be moved inside this function. -* https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#i11-never-transfer-ownership-by-a-raw-pointer-t-or-reference-t - -multiple "*" vs nick_.empty -> maybe as a MACRO - -call setHostname in acceptClient_ (rhaas) -no: instead of passing a new Client pass serverName -instead of passing client reference to cmds.run, give it the fileDescr - - -move msgTooLong to cmds_.run -> yes. -no error counter for line too long, maybe moves to the cmd processor - -comd handler won't throw - -make separate PR for Server::sendMsg From d454b34e7c7ee50e3daf14816d16c9178a77e1c0 Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 10 Sep 2026 17:11:52 +0200 Subject: [PATCH 217/349] feat: * register commands in CommandProcessor c'tor * remove setClientInfo (unused, deprecated) * log client connect msg instead of just showing it in dbg mode rfa: shorten Client connect log msg --- includes/CommandProcessor.hpp | 4 ++++ includes/Server.hpp | 2 -- includes/ServerHelper.hpp | 4 ++-- srcs/server/Server.cpp | 37 ++++++++--------------------------- 4 files changed, 14 insertions(+), 33 deletions(-) diff --git a/includes/CommandProcessor.hpp b/includes/CommandProcessor.hpp index c124c65..2b1304c 100644 --- a/includes/CommandProcessor.hpp +++ b/includes/CommandProcessor.hpp @@ -40,6 +40,10 @@ class CommandProcessor { Command &at_(const std::string &name); public: + CommandProcessor() { + registerCommands(); + } + void registerCommands(); // parser TODO blnunez- bool parse(const std::string &line, std::string &name, diff --git a/includes/Server.hpp b/includes/Server.hpp index c759045..38b3e7b 100644 --- a/includes/Server.hpp +++ b/includes/Server.hpp @@ -53,8 +53,6 @@ class Server { void removeClosedSockets_(); void storePollFd_(int const socketFd, bool srvSocket); void removePollFd_(int const fd); - void setClientInfo_(int fd, Client **clntPtr, std::string &nick, - bool &isReg); void log_(std::string const &msg, Logger::logMode const lm); Server(Server const &other); diff --git a/includes/ServerHelper.hpp b/includes/ServerHelper.hpp index 015ea06..6c43a78 100644 --- a/includes/ServerHelper.hpp +++ b/includes/ServerHelper.hpp @@ -100,8 +100,8 @@ inline std::string composeAddrfamAddrPortStr(socketAddrX const &sockAddrIn46) { addrStr = "[" + addrStr + "]"; } std::ostringstream oss; - oss << " AF: " << getAdressFamilyStr(saFamily) - << "\n addr:port: " << addrStr << ":" << std::dec << portNumber; + oss << " AF:" << getAdressFamilyStr(saFamily) + << "addr:port: " << addrStr << ":" << std::dec << portNumber; return oss.str(); } diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 6718a38..590f404 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -143,22 +143,9 @@ void Server::processServerSocketData_() { } // DEBUG check if cleanup works correctly - static size_t crash = 0; - if (++crash > 5) - throw std::runtime_error("Server socket error."); -} - -void Server::setClientInfo_(int fd, Client **clntPtr, std::string &nick, - bool &isReg) { - try { - *clntPtr = &clients_.at(fd); - if (*clntPtr && (*clntPtr)->isRegistered()) { - isReg = true; - nick = (*clntPtr)->getNick(); - } - } catch (std::exception const &e) { - log_(e.what(), Logger::lmWarn); - } + // static size_t crash = 0; + // if (++crash > 5) + // throw std::runtime_error("DBG: Server socket error."); } void Server::processClientSockets_() { @@ -168,14 +155,6 @@ void Server::processClientSockets_() { pollfd &pfd = pollFds_.at(idx); if (sockets_.checkSocket4Recv(pfd) && (pfd.revents & POLLIN)) { - - // // to be replaced by a request to some sort of manager - // Client *client = 0; - // std::string nickName = "*"; - // bool clntIsReg = false; - // setClientInfo_(pfd.fd, &client, nickName, clntIsReg); - // // end: to be replaced by a request to some sort of manager - std::string cmdLine; bool buffHasMoreCmds = false; do { @@ -244,11 +223,11 @@ void Server::acceptClient_(int const serverFd) { storePollFd_(clientFd, false); clients_.add(clientFd, serverName_); -#if NETCATDBG - std::cout << "Client connected:\n" - << " fd: " << clientFd << "\n" - << srvHelp::composeAddrfamAddrPortStr(sockAddrIn46) << std::endl; -#endif + std::ostringstream oss; + oss << "Client connected:" + << " fd:" << clientFd + << srvHelp::composeAddrfamAddrPortStr(sockAddrIn46) << std::endl; + log_(oss.str(), Logger::lmInfo); } void Server::storePollFd_(int const socketFd, bool srvSocket) { From 773a773cac818f10b8c9daeed1b61a0e461c2bd9 Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 10 Sep 2026 17:13:26 +0200 Subject: [PATCH 218/349] feat: * remove throw and replace by a log entry and appropriate prog flow; except for when a system function fails --- includes/Server.hpp | 2 +- includes/ServerHelper.hpp | 4 ++-- srcs/main.cpp | 36 ++++++++++++++++++++++-------------- srcs/server/Server.cpp | 17 ++++++++--------- 4 files changed, 33 insertions(+), 26 deletions(-) diff --git a/includes/Server.hpp b/includes/Server.hpp index 38b3e7b..9c7ad67 100644 --- a/includes/Server.hpp +++ b/includes/Server.hpp @@ -45,7 +45,7 @@ class Server { size_t numServerSockets_; std::vector pollFds_; - void setupServerSockets_(); + bool setupServerSockets_(); int createServerSocket_(int const domain); void processServerSocketData_(); void acceptClient_(int const serverFd); diff --git a/includes/ServerHelper.hpp b/includes/ServerHelper.hpp index 6c43a78..ae96eac 100644 --- a/includes/ServerHelper.hpp +++ b/includes/ServerHelper.hpp @@ -100,8 +100,8 @@ inline std::string composeAddrfamAddrPortStr(socketAddrX const &sockAddrIn46) { addrStr = "[" + addrStr + "]"; } std::ostringstream oss; - oss << " AF:" << getAdressFamilyStr(saFamily) - << "addr:port: " << addrStr << ":" << std::dec << portNumber; + oss << " AF:" << getAdressFamilyStr(saFamily) << "addr:port: " << addrStr + << ":" << std::dec << portNumber; return oss.str(); } diff --git a/srcs/main.cpp b/srcs/main.cpp index 2788ea2..8d7e09b 100644 --- a/srcs/main.cpp +++ b/srcs/main.cpp @@ -69,27 +69,35 @@ int main(int argc, char **argv) { return 0; } +static void toCerr(std::string const &msg, std::string const &colour) { + std::cerr << colour << msg << CReset << std::endl; +} + static bool checkExtractCmdLnArgs(int const argc, char *const argv[], int &port, std::string &password) { - std::istringstream iss; - std::string remainder; if (argc != 3) { - std::cerr << CYellow << "Usage: ./ircserv " - << CReset << std::endl; + toCerr("Usage: ./ircserv ", CYellow); return false; } - iss.str(argv[1]); // NOLINT - if (!((iss >> port) && (iss >> remainder).eof())) - throw std::runtime_error( - "Error while extracting port number from commandline parameter."); - if (port < 1 || port > 65535) - throw std::runtime_error("Error: port must be in the range 1-65535."); + std::istringstream issPort(argv[1]); // NOLINT + std::string remainder; + if (!((issPort >> port) && (issPort >> remainder).eof())) { + toCerr("Error while extracting port number from commandline parameter.", + CRed); + return false; + } + if (port < 1 || port > 65535) { + toCerr("Error: port must be in the range 1-65535.", CRed); + return false; + } - iss.str(argv[2]); // NOLINT - if (!(iss >> password).eof()) - throw std::runtime_error( - "Error while extracting password from commandline parameter."); + std::istringstream issPass(argv[2]); // NOLINT + if (!(issPass >> password).eof()) { + toCerr("Error while extracting password from commandline parameter.", + CRed); + return false; + } return true; } diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 590f404..e599c0a 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -72,7 +72,7 @@ Server::~Server() { } } -void Server::setupServerSockets_() { +bool Server::setupServerSockets_() { static int const domArr[] = {AF_INET, AF_INET6}; static std::vector const domains( // NOLINTNEXTLINE @@ -84,8 +84,11 @@ void Server::setupServerSockets_() { log_(e.what(), Logger::lmExcept); } } - if (numServerSockets_ == 0) - throw std::runtime_error("No server socket could be setup correctly."); + if (numServerSockets_ == 0) { + log_("No server socket could be setup correctly.", Logger::lmError); + return false; + } + return true; } /* @@ -141,11 +144,6 @@ void Server::processServerSocketData_() { if (serverPollFds.revents & POLLIN) acceptClient_(serverPollFds.fd); } - - // DEBUG check if cleanup works correctly - // static size_t crash = 0; - // if (++crash > 5) - // throw std::runtime_error("DBG: Server socket error."); } void Server::processClientSockets_() { @@ -282,7 +280,8 @@ ClientManager &Server::getClientManager() { * handles client connect and disconnect scenarios */ void Server::run() { - setupServerSockets_(); + if (!setupServerSockets_()) + return; while (!SignalHandler::stopLoopSignalReceived()) { int pollResult = poll(pollFds_.data(), pollFds_.size(), -1); From cc940c52e97f49c8fd2a38cb0618d431f0e2d967 Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 10 Sep 2026 17:17:44 +0200 Subject: [PATCH 219/349] rfa: log format --- includes/ServerHelper.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/ServerHelper.hpp b/includes/ServerHelper.hpp index ae96eac..468da80 100644 --- a/includes/ServerHelper.hpp +++ b/includes/ServerHelper.hpp @@ -100,7 +100,7 @@ inline std::string composeAddrfamAddrPortStr(socketAddrX const &sockAddrIn46) { addrStr = "[" + addrStr + "]"; } std::ostringstream oss; - oss << " AF:" << getAdressFamilyStr(saFamily) << "addr:port: " << addrStr + oss << " AF:" << getAdressFamilyStr(saFamily) << " addr:port: " << addrStr << ":" << std::dec << portNumber; return oss.str(); From c98abe4b61805b49399cac1e3fc71a844619dc29 Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 10 Sep 2026 18:21:54 +0200 Subject: [PATCH 220/349] rfa: split composeAddrfamAddrPortStr to make subfunctionality usable elsewhere --- includes/ServerHelper.hpp | 52 ++++++++++++++++++++++++++++++--------- 1 file changed, 40 insertions(+), 12 deletions(-) diff --git a/includes/ServerHelper.hpp b/includes/ServerHelper.hpp index 468da80..2740562 100644 --- a/includes/ServerHelper.hpp +++ b/includes/ServerHelper.hpp @@ -73,16 +73,10 @@ inline void bindServerSocket(int const socketFd, int const domain, // NOLINT getAdressFamilyStr(domain) + " !"); } -inline std::string composeAddrfamAddrPortStr(socketAddrX const &sockAddrIn46) { - char addrBuffer[128]; - socklen_t const addrBuffLen = sizeof(addrBuffer) / sizeof(char); - - sa_family_t const saFamily = - // NOLINTNEXTLINE - reinterpret_cast(sockAddrIn46.sai4).sa_family; +inline void getIPandPort(socketAddrX const &sockAddrIn46, + sa_family_t const saFamily, uint16_t &portNumber, + std::string &addrStr) { void const *addrPtr = 0; - uint16_t portNumber = 0; - if (saFamily == AF_INET) { addrPtr = &sockAddrIn46.sai4.sin_addr; portNumber = ntohs(sockAddrIn46.sai4.sin_port); @@ -90,15 +84,49 @@ inline std::string composeAddrfamAddrPortStr(socketAddrX const &sockAddrIn46) { addrPtr = &sockAddrIn46.sai6.sin6_addr; portNumber = ntohs(sockAddrIn46.sai6.sin6_port); } - char const *addrStrPtr = + + char addrBuffer[128]; + socklen_t const addrBuffLen = sizeof(addrBuffer) / sizeof(char); + char const *addrStrPtr = inet_ntop(saFamily, addrPtr, &addrBuffer[0], addrBuffLen); - std::string addrStr("No addr:port information."); if (addrStrPtr) { addrStr.assign(addrStrPtr); if (saFamily == AF_INET6) addrStr = "[" + addrStr + "]"; - } + } else + addrStr.assign("No addr:port information."); +} + +inline sa_family_t getSaFamily(socketAddrX const &sockAddrIn46) { + // NOLINTNEXTLINE + return reinterpret_cast(sockAddrIn46.sai4) + .sa_family; +} + +inline std::string composeAddrfamAddrPortStr(socketAddrX const &sockAddrIn46) { + sa_family_t const saFamily = getSaFamily(sockAddrIn46); + + uint16_t portNumber = 0; + std::string addrStr("No addr:port information."); + getIPandPort(sockAddrIn46, saFamily, portNumber, addrStr); + + // if (saFamily == AF_INET) { + // addrPtr = &sockAddrIn46.sai4.sin_addr; + // portNumber = ntohs(sockAddrIn46.sai4.sin_port); + // } else if (saFamily == AF_INET6) { + // addrPtr = &sockAddrIn46.sai6.sin6_addr; + // portNumber = ntohs(sockAddrIn46.sai6.sin6_port); + // } + // char const *addrStrPtr = + // inet_ntop(saFamily, addrPtr, &addrBuffer[0], addrBuffLen); + + // std::string addrStr("No addr:port information."); + // if (addrStrPtr) { + // addrStr.assign(addrStrPtr); + // if (saFamily == AF_INET6) + // addrStr = "[" + addrStr + "]"; + // } std::ostringstream oss; oss << " AF:" << getAdressFamilyStr(saFamily) << " addr:port: " << addrStr << ":" << std::dec << portNumber; From 3b3cacee52d8cef294b721d4d3cb5b5da1fc6f1a Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 10 Sep 2026 18:24:04 +0200 Subject: [PATCH 221/349] feat: * use client.sendClosingMsg as it is available now * keep try/catch in poll loop active until client error msgs have been moved from exception to regular replies * set client hostname to socket IP --- srcs/server/Server.cpp | 65 ++++++++++++++++++++++++------------------ 1 file changed, 38 insertions(+), 27 deletions(-) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index e599c0a..d885578 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -41,7 +41,7 @@ Server::~Server() { if (!sockets_.isServerSocket(fd)) { // Update after Call client member function for shutdown notice message! // after PR feat/cmds2: implement QUIT, PING, PONG and NOTICE - #38# -//#define REMOVE_ME +#define REMOVE_ME #ifndef REMOVE_ME std::string nickName; try { @@ -68,7 +68,6 @@ Server::~Server() { } sockets_.closeSocket(fd); this->removePollFd_(fd); - // pollFds_.erase(--pollFds_.end()); } } @@ -156,32 +155,39 @@ void Server::processClientSockets_() { std::string cmdLine; bool buffHasMoreCmds = false; do { - // try { - cmdLine = - sockets_.getCRLFLineFromBuffer(pfd.fd, buffHasMoreCmds); - if (!cmdLine.empty()) { - // returns true if connection is to be closed - // only happens after a QUIT cmd or maybe after too many ERR_INPUTTOOLONG - if (cmds_.run( - *this, - /* should soon be: pfd.fd */ clients_.at(pfd.fd), - cmdLine)) { - sockets_.closeSocket(pfd.fd); - break; +#define ERRMSG2CLIENTBYEXCEPT +#ifdef ERRMSG2CLIENTBYEXCEPT + try { +#endif + cmdLine = + sockets_.getCRLFLineFromBuffer(pfd.fd, buffHasMoreCmds); + if (!cmdLine.empty()) { + // returns true if connection is to be closed + // only happens after a QUIT cmd or maybe after too many ERR_INPUTTOOLONG + if (cmds_.run(*this, + /* should soon be: pfd.fd */ + clients_.at(pfd.fd), cmdLine)) { + sockets_.closeSocket(pfd.fd); + break; + } + } +#ifdef ERRMSG2CLIENTBYEXCEPT + } catch (Error const &e) { + if (e.hasReply()) { + Client *client = clients_.find(pfd.fd); + std::string nickName = + (client ? client->getNick() : "dbgDummyNick"); + sockets_.sendMsg( + pfd.fd, + Reply::build(serverName_, nickName, e.reply())); + } else { + std::string tmp( + "Error while trying to get/execute cmd:\n"); + tmp.append("'" + cmdLine + "'\n" + e.what()); + log_(tmp, Logger::lmExcept); } } - // } catch (Error const &e) { - // if (e.hasReply()) { - // sockets_.sendMsg( - // pfd.fd, - // Reply::build(serverName_, nickName, e.reply())); - // } else { - // std::string tmp( - // "Error while trying to get/execute cmd:\n"); - // tmp.append("'" + cmdLine + "'\n" + e.what()); - // log_(tmp, Logger::lmExcept); - // } - // } +#endif } while (buffHasMoreCmds); } } @@ -219,7 +225,12 @@ void Server::acceptClient_(int const serverFd) { srvHelp::setSocketNonBlock(clientFd); storePollFd_(clientFd, false); - clients_.add(clientFd, serverName_); + + std::string clientIP; + uint16_t port; + srvHelp::getIPandPort(sockAddrIn46, srvHelp::getSaFamily(sockAddrIn46), + port, clientIP); + clients_.add(clientFd, clientIP); std::ostringstream oss; oss << "Client connected:" From a1e91346821dda57a0ea1f3958d9b6e16384d72f Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 10 Sep 2026 18:24:41 +0200 Subject: [PATCH 222/349] feat: log more info about client (dis)connect --- srcs/server/Socket.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/srcs/server/Socket.cpp b/srcs/server/Socket.cpp index 198aa90..1295616 100644 --- a/srcs/server/Socket.cpp +++ b/srcs/server/Socket.cpp @@ -34,8 +34,16 @@ bool Socket::updateBuffer_(int const fd) { sIOD.bytesRead = recv(fd, &recvBuffer[0], RECVBUFFLEN, 0); if (sIOD.bytesRead <= 0) { ++sIOD.recvErrorCount; - if (sIOD.bytesRead == 0 || sIOD.recvErrorCount >= SOCKETERRORLIMIT) + if (sIOD.bytesRead == 0 || sIOD.recvErrorCount >= SOCKETERRORLIMIT) { + std::ostringstream oss; + oss << "Client sockFd:" << fd; + if (sIOD.recvErrorCount >= SOCKETERRORLIMIT) + oss << " exceeded error limit (" << SOCKETERRORLIMIT << ")"; + else if (sIOD.bytesRead == 0) + oss << " closed connection on their side"; + logger_.logMsg(oss.str(), Logger::lmInfo); closeSocket(fd); + } return false; } sIOD.readBuffer += std::string(&recvBuffer[0], sIOD.bytesRead); @@ -164,7 +172,7 @@ void Socket::closeSocket(int const fd) { oss << "Closing server socket (" << srvHelp::getAdressFamilyStr(srvHelp::sockAddrFamily(fd)) << ")"; } else { - oss << "Closing client with sockFd " << fd; + oss << "Closing client sockFd:" << fd; } logger_.logMsg(oss.str(), Logger::lmInfo); From 250bd513a3a0b11c7bb8bd2d7fc003e7e4331957 Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 10 Sep 2026 19:02:49 +0200 Subject: [PATCH 223/349] feat: use one global static function to get a string for "now" --- includes/Logger.hpp | 4 ++-- srcs/server/Logger.cpp | 2 +- srcs/server/Server.cpp | 12 +++--------- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/includes/Logger.hpp b/includes/Logger.hpp index 9cee6ef..060738e 100644 --- a/includes/Logger.hpp +++ b/includes/Logger.hpp @@ -6,7 +6,8 @@ class Logger { std::string prefix_; - static std::string nowStr_() { + public: + static std::string nowStr() { time_t now = time(0); struct tm *timeinfo = localtime(&now); char buffer[32]; @@ -14,7 +15,6 @@ class Logger { return std::string(&buffer[0]); } - public: enum logMode { lmInfo, lmWarn, lmError, lmExcept }; Logger(std::string const &prefix) : prefix_(prefix) {} diff --git a/srcs/server/Logger.cpp b/srcs/server/Logger.cpp index 16190d2..cd3ed79 100644 --- a/srcs/server/Logger.cpp +++ b/srcs/server/Logger.cpp @@ -31,5 +31,5 @@ void Logger::logMsg(std::string msg, logMode const lm) const { *os << CMgnta; break; } - *os << nowStr_() << " " << prefix_ << msg << CReset << std::endl; + *os << nowStr() << " " << prefix_ << msg << CReset << std::endl; } diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index d885578..dfd0e34 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -21,15 +21,9 @@ * init the logger prefix (server name) */ Server::Server(int const port, std::string const &password) - : serverName_(SERVER_NAME), serverVersion_(SERVER_VERSION), port_(port), - password_(password), logger_(serverName_ + ": "), sockets_(logger_), - numServerSockets_(0) { - std::time_t now = std::time(0); - char buf[64]; - std::strftime(buf, sizeof(buf), "%a %b %d %H:%M:%S %Y", - std::localtime(&now)); - createdAt_ = buf; -} + : serverName_(SERVER_NAME), serverVersion_(SERVER_VERSION), + createdAt_(Logger::nowStr()), port_(port), password_(password), + logger_(serverName_ + ": "), sockets_(logger_), numServerSockets_(0) {} /* Cleanup From 5ab07653eedd37e2a17f6400acecd3a21ef4de49 Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 10 Sep 2026 19:05:53 +0200 Subject: [PATCH 224/349] feat: add @kmurugan's hack to send msgs to clients. Should work at least for single client communication --- srcs/server/Server.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index dfd0e34..8ae6b8b 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -183,6 +183,13 @@ void Server::processClientSockets_() { } #endif } while (buffHasMoreCmds); + + Client *client = clients_.find(pfd.fd); + if (client && !client->getSendBuffer().empty()) { + const std::string &out = client->getSendBuffer(); + sockets_.sendMsg(pfd.fd, out); + client->consumeSendBuffer(out.size()); + } } } removeClosedSockets_(); From cc3a245b4bcb468fe18cd5f8cd581dfa289062fa Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 10 Sep 2026 19:09:27 +0200 Subject: [PATCH 225/349] feat: remove trainling newline of Client connect msg --- srcs/server/Server.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 8ae6b8b..5d88775 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -236,7 +236,7 @@ void Server::acceptClient_(int const serverFd) { std::ostringstream oss; oss << "Client connected:" << " fd:" << clientFd - << srvHelp::composeAddrfamAddrPortStr(sockAddrIn46) << std::endl; + << srvHelp::composeAddrfamAddrPortStr(sockAddrIn46); log_(oss.str(), Logger::lmInfo); } From ac8b8f86bdfa14487bac1fe1147efb0f9335b73d Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 10 Sep 2026 19:09:27 +0200 Subject: [PATCH 226/349] feat: remove trainling newline of Client connect msg --- srcs/server/Server.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 8ae6b8b..0697f6e 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -57,6 +57,12 @@ Server::~Server() { #else Client &client = clients_.at(fd); client.sendClosingMsg("Server shutdown - goodbye"); + + // REMOVE ME ---> + const std::string &out = client.getSendBuffer(); + sockets_.sendMsg(fd, out); + client.consumeSendBuffer(out.size()); + // <--- REMOVE ME #endif clients_.remove(fd, channels_); } @@ -184,12 +190,14 @@ void Server::processClientSockets_() { #endif } while (buffHasMoreCmds); + // REMOVE ME ---> Client *client = clients_.find(pfd.fd); if (client && !client->getSendBuffer().empty()) { const std::string &out = client->getSendBuffer(); sockets_.sendMsg(pfd.fd, out); client->consumeSendBuffer(out.size()); } + // <--- REMOVE ME } } removeClosedSockets_(); @@ -236,7 +244,7 @@ void Server::acceptClient_(int const serverFd) { std::ostringstream oss; oss << "Client connected:" << " fd:" << clientFd - << srvHelp::composeAddrfamAddrPortStr(sockAddrIn46) << std::endl; + << srvHelp::composeAddrfamAddrPortStr(sockAddrIn46); log_(oss.str(), Logger::lmInfo); } From 100b16bd1f6430d10bd1bb3f31c8263194b8c741 Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 10 Sep 2026 19:26:10 +0200 Subject: [PATCH 227/349] rfa: remove redundant header includes --- includes/Server.hpp | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/includes/Server.hpp b/includes/Server.hpp index 9c7ad67..120acec 100644 --- a/includes/Server.hpp +++ b/includes/Server.hpp @@ -6,17 +6,6 @@ #include "Logger.hpp" // for Logger #include "Socket.hpp" // for Socket -#include // for pollfd -#include // for size_t -#include // for string, basic_string -#include // for vector -class Client; -#include "ChannelManager.hpp" // for ChannelManager -#include "ClientManager.hpp" // for ClientManager -#include "CommandProcessor.hpp" // for CommandProcessor -#include "Logger.hpp" // for Logger -#include "Socket.hpp" // for Socket - #include // for pollfd #include // for size_t #include // for string, basic_string From 113766a9434c7b7455514fde730491863e63b227 Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 10 Sep 2026 19:39:54 +0200 Subject: [PATCH 228/349] feat: add more explicit error message about failed password extraction --- srcs/main.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/srcs/main.cpp b/srcs/main.cpp index 8d7e09b..a70943e 100644 --- a/srcs/main.cpp +++ b/srcs/main.cpp @@ -94,7 +94,10 @@ static bool checkExtractCmdLnArgs(int const argc, char *const argv[], int &port, std::istringstream issPass(argv[2]); // NOLINT if (!(issPass >> password).eof()) { - toCerr("Error while extracting password from commandline parameter.", + toCerr("Error while extracting password from commandline parameter '" + + issPass.str() + + "'\n Make sure it consist of alphanumeric characters " + "without spaces", CRed); return false; } From 42b4a65568cebf389fa50e6009bf6e00647d64d5 Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 13:19:52 +0200 Subject: [PATCH 229/349] feat: add handling of negative error code. Reason: Server sending shutdown error msg has no error code, so setting it to a negative value should be possible and should suppress sending a code. --- includes/Reply.hpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/includes/Reply.hpp b/includes/Reply.hpp index 63088e8..3f66b33 100644 --- a/includes/Reply.hpp +++ b/includes/Reply.hpp @@ -272,8 +272,10 @@ Use "*" for `nick` until the client has registered (pre-001). inline std::string build(const std::string &server, const std::string &nick, const Message &msg) { std::ostringstream ss; - ss << ":" << server << " " << std::setw(3) << std::setfill('0') << msg.code - << " " << nick; + ss << ":" << server; + if (msg.code > 0) + ss << " " << std::setw(3) << std::setfill('0') << msg.code; + ss << " " << nick; if (!msg.params.empty()) ss << " " << msg.params; if (msg.hasTrailing) From 38c43ce9e69e454b7a97322f8ead9ed10c675903 Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 13:20:31 +0200 Subject: [PATCH 230/349] feat: add public interface for sending messages --- includes/Server.hpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/includes/Server.hpp b/includes/Server.hpp index 996f13c..aa2e199 100644 --- a/includes/Server.hpp +++ b/includes/Server.hpp @@ -62,7 +62,12 @@ class Server { ClientManager &getClientManager(); // Pass-through interfaces - // void sendMsg(int const fd, std::string const &msg) { - // sockets_.sendMsg(fd, msg); - // } + void sendMsg(int const fd, std::string const &msg) { + sockets_.sendMsg(fd, msg); + } + + // Pass-through interfaces + void sendMsg(int const fd, std::string const &msg) { + sockets_.sendMsg(fd, msg); + } }; From b9da295a54ac16b8a0394fa84caf8febbab566ca Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 13:22:16 +0200 Subject: [PATCH 231/349] feat: send goodby error message to clients when server shuts down --- srcs/server/Server.cpp | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 2594809..e759da3 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -20,21 +20,15 @@ * init the logger prefix (server name) */ Server::Server(int const port, std::string const &password) - : serverName_(SERVER_NAME), serverVersion_(SERVER_VERSION), port_(port), - password_(password), logger_(serverName_ + ": "), sockets_(logger_), - numServerSockets_(0) { - std::time_t now = std::time(0); - char buf[64]; - std::strftime(buf, sizeof(buf), "%a %b %d %H:%M:%S %Y", - std::localtime(&now)); - createdAt_ = buf; -} + : serverName_(SERVER_NAME), port_(port), password_(password), + logger_(serverName_ + ": "), sockets_(logger_), numServerSockets_(0) {} /* Cleanup * going backwards through pollFds to ensure server sockets are closed last */ Server::~Server() { + std::string nickName /*= ...get from Manager */; while (!pollFds_.empty()) { int const fd = pollFds_.back().fd; if (!sockets_.isServerSocket(fd)) { From fb2d8bdb9033b2d88950ed38dd51e0512687e2c1 Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 13:32:27 +0200 Subject: [PATCH 232/349] fix: update msg content, including "ERROR", add nickname and hint to inform clientManager --- srcs/server/Server.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index e759da3..5656305 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -28,7 +28,6 @@ Server::Server(int const port, std::string const &password) * going backwards through pollFds to ensure server sockets are closed last */ Server::~Server() { - std::string nickName /*= ...get from Manager */; while (!pollFds_.empty()) { int const fd = pollFds_.back().fd; if (!sockets_.isServerSocket(fd)) { @@ -53,6 +52,7 @@ Server::~Server() { } sockets_.closeSocket(fd); pollFds_.erase(--pollFds_.end()); + // inform Account Manager of connection abort } } From fbf74225df6b5c8ff5994163ecf772bb07e9f8f4 Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 13:34:29 +0200 Subject: [PATCH 233/349] fix: remove (active) client if corresponding socket is closed --- srcs/server/Server.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 5656305..06cdb36 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -52,7 +52,7 @@ Server::~Server() { } sockets_.closeSocket(fd); pollFds_.erase(--pollFds_.end()); - // inform Account Manager of connection abort + clients_.remove(fd); } } From d3c7234c7f98e863ff2309d1be46676f1d0f4e2f Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 14:00:47 +0200 Subject: [PATCH 234/349] feat: * use add and remove from client manager * set default nick to "*" in client c'tor --- srcs/server/Server.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 06cdb36..146f4bb 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -52,7 +52,7 @@ Server::~Server() { } sockets_.closeSocket(fd); pollFds_.erase(--pollFds_.end()); - clients_.remove(fd); + clients_.remove(fd); } } From fb1899818a8a730c286cf89ca83533b12391aff1 Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 15:13:12 +0200 Subject: [PATCH 235/349] fix: differentiate btw serv and client socket --- srcs/server/Server.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 146f4bb..6093e74 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -52,7 +52,6 @@ Server::~Server() { } sockets_.closeSocket(fd); pollFds_.erase(--pollFds_.end()); - clients_.remove(fd); } } From 0bb0b3308c6632a15099f7bafc94cb9772f12ee1 Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 17:15:03 +0200 Subject: [PATCH 236/349] rfa: remove sendMsg interface in Server, everybody will use the sockets._sendMsg directly --- includes/Server.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/includes/Server.hpp b/includes/Server.hpp index aa2e199..2e459df 100644 --- a/includes/Server.hpp +++ b/includes/Server.hpp @@ -62,9 +62,9 @@ class Server { ClientManager &getClientManager(); // Pass-through interfaces - void sendMsg(int const fd, std::string const &msg) { - sockets_.sendMsg(fd, msg); - } + // void sendMsg(int const fd, std::string const &msg) { + // sockets_.sendMsg(fd, msg); + // } // Pass-through interfaces void sendMsg(int const fd, std::string const &msg) { From 632c426202a9ba989a0123b624e3777178760da2 Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 13:20:31 +0200 Subject: [PATCH 237/349] feat: add public interface for sending messages --- includes/Server.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/includes/Server.hpp b/includes/Server.hpp index 2e459df..aa2e199 100644 --- a/includes/Server.hpp +++ b/includes/Server.hpp @@ -62,9 +62,9 @@ class Server { ClientManager &getClientManager(); // Pass-through interfaces - // void sendMsg(int const fd, std::string const &msg) { - // sockets_.sendMsg(fd, msg); - // } + void sendMsg(int const fd, std::string const &msg) { + sockets_.sendMsg(fd, msg); + } // Pass-through interfaces void sendMsg(int const fd, std::string const &msg) { From 961cab0159e25e36e99dbc777dd2b3a19e8f351b Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 13:22:16 +0200 Subject: [PATCH 238/349] feat: send goodby error message to clients when server shuts down --- srcs/server/Server.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 6093e74..e759da3 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -28,6 +28,7 @@ Server::Server(int const port, std::string const &password) * going backwards through pollFds to ensure server sockets are closed last */ Server::~Server() { + std::string nickName /*= ...get from Manager */; while (!pollFds_.empty()) { int const fd = pollFds_.back().fd; if (!sockets_.isServerSocket(fd)) { From 174229c1f265c97b6645b8982281f44ef21a9151 Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 13:32:27 +0200 Subject: [PATCH 239/349] fix: update msg content, including "ERROR", add nickname and hint to inform clientManager --- srcs/server/Server.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index e759da3..5656305 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -28,7 +28,6 @@ Server::Server(int const port, std::string const &password) * going backwards through pollFds to ensure server sockets are closed last */ Server::~Server() { - std::string nickName /*= ...get from Manager */; while (!pollFds_.empty()) { int const fd = pollFds_.back().fd; if (!sockets_.isServerSocket(fd)) { @@ -53,6 +52,7 @@ Server::~Server() { } sockets_.closeSocket(fd); pollFds_.erase(--pollFds_.end()); + // inform Account Manager of connection abort } } From b8bc6bf7ca021447afd31318e0227de0baecbee8 Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 13:34:29 +0200 Subject: [PATCH 240/349] fix: remove (active) client if corresponding socket is closed --- srcs/server/Server.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 5656305..06cdb36 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -52,7 +52,7 @@ Server::~Server() { } sockets_.closeSocket(fd); pollFds_.erase(--pollFds_.end()); - // inform Account Manager of connection abort + clients_.remove(fd); } } From c924108869d0d3111fafaa8c87095b04852cf792 Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 14:00:47 +0200 Subject: [PATCH 241/349] feat: * use add and remove from client manager * set default nick to "*" in client c'tor --- srcs/server/Server.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 06cdb36..146f4bb 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -52,7 +52,7 @@ Server::~Server() { } sockets_.closeSocket(fd); pollFds_.erase(--pollFds_.end()); - clients_.remove(fd); + clients_.remove(fd); } } From 1f01c15c18d51230a6e8e3f8d51ae30f9a3f2eb0 Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 15:13:12 +0200 Subject: [PATCH 242/349] fix: differentiate btw serv and client socket --- srcs/server/Server.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 146f4bb..6093e74 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -52,7 +52,6 @@ Server::~Server() { } sockets_.closeSocket(fd); pollFds_.erase(--pollFds_.end()); - clients_.remove(fd); } } From e53d0f7cc08f6a55bd8d2fbc44a90771085e9ff7 Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 17:15:03 +0200 Subject: [PATCH 243/349] rfa: remove sendMsg interface in Server, everybody will use the sockets._sendMsg directly --- includes/Server.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/includes/Server.hpp b/includes/Server.hpp index aa2e199..2e459df 100644 --- a/includes/Server.hpp +++ b/includes/Server.hpp @@ -62,9 +62,9 @@ class Server { ClientManager &getClientManager(); // Pass-through interfaces - void sendMsg(int const fd, std::string const &msg) { - sockets_.sendMsg(fd, msg); - } + // void sendMsg(int const fd, std::string const &msg) { + // sockets_.sendMsg(fd, msg); + // } // Pass-through interfaces void sendMsg(int const fd, std::string const &msg) { From 5e01354a46b693e839ef282dcd76405c74757f8a Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 3 Sep 2026 12:40:41 +0200 Subject: [PATCH 244/349] fix: create the ERROR message from scratch, as Reply::Message doesn't allow non-Code generation --- srcs/server/Server.cpp | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 6093e74..d50b142 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -31,19 +31,7 @@ Server::~Server() { while (!pollFds_.empty()) { int const fd = pollFds_.back().fd; if (!sockets_.isServerSocket(fd)) { - std::string nickName; - try { - nickName = clients_.at(fd).getNick(); - } catch (std::exception const &e) { - logger_.logMsg("\n Cannot get client object:" - "\n starting from a second client, the object " - "isn't correctly created" - "\n in function bool ClientManager::add(Client " - "&client, int fd)" - "\n Exception: " + - std::string(e.what()), - Logger::lmError); - } + std::string nickName = clients_.at(fd).getNick(); std::ostringstream oss; oss << "ERROR :" << serverName_ << " " << nickName << " :Server shutdown - goodbye."; @@ -178,9 +166,8 @@ void Server::processClientSockets_() { } if (sockets_.socketClosed(pfd.fd)) { + removePollFd_(pfd.fd); clients_.remove(pfd.fd, channels_); - removePollFd_(idx); - --idx; } } } From d91f7298c5e375a520992933306d8e7d916f7cf6 Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 13:20:31 +0200 Subject: [PATCH 245/349] feat: add public interface for sending messages --- includes/Server.hpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/includes/Server.hpp b/includes/Server.hpp index 2e459df..7b05e8a 100644 --- a/includes/Server.hpp +++ b/includes/Server.hpp @@ -61,11 +61,6 @@ class Server { ChannelManager &getChannelManager(); ClientManager &getClientManager(); - // Pass-through interfaces - // void sendMsg(int const fd, std::string const &msg) { - // sockets_.sendMsg(fd, msg); - // } - // Pass-through interfaces void sendMsg(int const fd, std::string const &msg) { sockets_.sendMsg(fd, msg); From eb9de35a77fc23753e4edb16ac67b9e6ca388d0d Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 13:22:16 +0200 Subject: [PATCH 246/349] feat: send goodby error message to clients when server shuts down --- srcs/server/Server.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index d50b142..c3d38df 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -23,21 +23,21 @@ Server::Server(int const port, std::string const &password) : serverName_(SERVER_NAME), port_(port), password_(password), logger_(serverName_ + ": "), sockets_(logger_), numServerSockets_(0) {} +/* + Cleanup + * going backwards through pollFds to ensure server sockets are closed last + */ /* Cleanup * going backwards through pollFds to ensure server sockets are closed last */ Server::~Server() { + std::string nickName /*= ...get from Manager */; while (!pollFds_.empty()) { int const fd = pollFds_.back().fd; - if (!sockets_.isServerSocket(fd)) { - std::string nickName = clients_.at(fd).getNick(); - std::ostringstream oss; - oss << "ERROR :" << serverName_ << " " << nickName - << " :Server shutdown - goodbye."; - sockets_.sendMsg(fd, oss.str()); - clients_.remove(fd, channels_); - } + sockets_.sendMsg( + fd, Reply::build(serverName_, nickName, + Reply::Message(-1, "Server shutdown - goodbye."))); sockets_.closeSocket(fd); pollFds_.erase(--pollFds_.end()); } From 608c4a30b30eec96c80370cd5b400cd27f436379 Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 13:32:27 +0200 Subject: [PATCH 247/349] fix: update msg content, including "ERROR", add nickname and hint to inform clientManager --- srcs/server/Server.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index c3d38df..d258a4a 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -32,14 +32,16 @@ Server::Server(int const port, std::string const &password) * going backwards through pollFds to ensure server sockets are closed last */ Server::~Server() { - std::string nickName /*= ...get from Manager */; while (!pollFds_.empty()) { - int const fd = pollFds_.back().fd; + int const fd = pollFds_.back().fd; + std::string nickName = clients_.at(fd)->getNick(); sockets_.sendMsg( - fd, Reply::build(serverName_, nickName, - Reply::Message(-1, "Server shutdown - goodbye."))); + fd, Reply::build( + serverName_, nickName, + Reply::Message(-1, "ERROR", "Server shutdown - goodbye."))); sockets_.closeSocket(fd); pollFds_.erase(--pollFds_.end()); + // inform Account Manager of connection abort } } From 3af3f4619aa7597172aca8011e5eea34864dba2c Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 13:34:29 +0200 Subject: [PATCH 248/349] fix: remove (active) client if corresponding socket is closed --- srcs/server/Server.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index d258a4a..891b115 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -41,7 +41,7 @@ Server::~Server() { Reply::Message(-1, "ERROR", "Server shutdown - goodbye."))); sockets_.closeSocket(fd); pollFds_.erase(--pollFds_.end()); - // inform Account Manager of connection abort + clients_.remove(fd); } } From 4f6f04dd83b92f75f4bd2075756feb9dae4f6a86 Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 14:00:47 +0200 Subject: [PATCH 249/349] feat: * use add and remove from client manager * set default nick to "*" in client c'tor --- srcs/server/Server.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 891b115..ef0962b 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -41,7 +41,7 @@ Server::~Server() { Reply::Message(-1, "ERROR", "Server shutdown - goodbye."))); sockets_.closeSocket(fd); pollFds_.erase(--pollFds_.end()); - clients_.remove(fd); + clients_.remove(fd); } } From ec827b2f14d1112b9ae2a0f2725848fe88fbef22 Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 15:13:12 +0200 Subject: [PATCH 250/349] fix: differentiate btw serv and client socket --- srcs/server/Server.cpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index ef0962b..cff5518 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -33,15 +33,17 @@ Server::Server(int const port, std::string const &password) */ Server::~Server() { while (!pollFds_.empty()) { - int const fd = pollFds_.back().fd; - std::string nickName = clients_.at(fd)->getNick(); - sockets_.sendMsg( - fd, Reply::build( - serverName_, nickName, - Reply::Message(-1, "ERROR", "Server shutdown - goodbye."))); + int const fd = pollFds_.back().fd; + if (!sockets_.isServerSocket(fd)) { + std::string nickName = clients_.at(fd)->getNick(); + sockets_.sendMsg( + fd, Reply::build(serverName_, nickName, + Reply::Message(-1, "ERROR", + "Server shutdown - goodbye."))); + clients_.remove(fd); + } sockets_.closeSocket(fd); pollFds_.erase(--pollFds_.end()); - clients_.remove(fd); } } From ea0f71a53f40ea525c1a120fb5088050d8527a09 Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 17:15:53 +0200 Subject: [PATCH 251/349] rfa: * use static const where possible * use .at() for element access instead of [] --- srcs/server/Server.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index cff5518..5923800 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -40,7 +40,7 @@ Server::~Server() { fd, Reply::build(serverName_, nickName, Reply::Message(-1, "ERROR", "Server shutdown - goodbye."))); - clients_.remove(fd); + clients_.remove(fd); } sockets_.closeSocket(fd); pollFds_.erase(--pollFds_.end()); From f16156cb6ee856653d968511695eaea2d51bf71a Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 3 Sep 2026 12:40:41 +0200 Subject: [PATCH 252/349] fix: create the ERROR message from scratch, as Reply::Message doesn't allow non-Code generation --- srcs/server/Server.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 5923800..60f94ba 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -35,12 +35,12 @@ Server::~Server() { while (!pollFds_.empty()) { int const fd = pollFds_.back().fd; if (!sockets_.isServerSocket(fd)) { - std::string nickName = clients_.at(fd)->getNick(); - sockets_.sendMsg( - fd, Reply::build(serverName_, nickName, - Reply::Message(-1, "ERROR", - "Server shutdown - goodbye."))); - clients_.remove(fd); + std::string nickName = clients_.at(fd).getNick(); + std::ostringstream oss; + oss << "ERROR :" << serverName_ << " " << nickName + << " :Server shutdown - goodbye."; + sockets_.sendMsg(fd, oss.str()); + clients_.remove(fd, channels_); } sockets_.closeSocket(fd); pollFds_.erase(--pollFds_.end()); @@ -172,6 +172,7 @@ void Server::processClientSockets_() { if (sockets_.socketClosed(pfd.fd)) { removePollFd_(pfd.fd); clients_.remove(pfd.fd, channels_); + clients_.remove(pfd.fd, channels_); } } } @@ -196,7 +197,7 @@ void Server::acceptClient_(int const serverFd) { srvHelp::setSocketNonBlock(clientFd); storePollFd_(clientFd, false); - clients_.add(clientFd, new Client(clientFd)); + clients_.add(clientFd); std::cout << "Client connected:\n" << " fd: " << clientFd << "\n" From ca7fc343f92b28c72227848c5c3670588c6ff18c Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 3 Sep 2026 15:27:03 +0200 Subject: [PATCH 253/349] fix: adapt to new function interface --- srcs/server/Server.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 60f94ba..3397a6a 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -197,7 +197,7 @@ void Server::acceptClient_(int const serverFd) { srvHelp::setSocketNonBlock(clientFd); storePollFd_(clientFd, false); - clients_.add(clientFd); + clients_.add(*(new Client()), clientFd); std::cout << "Client connected:\n" << " fd: " << clientFd << "\n" From 791b141501936e786c3a0a1257428c2273d53976 Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 3 Sep 2026 16:12:55 +0200 Subject: [PATCH 254/349] dbg: add info msg in Server d'tor about unexpectedly non-existing client object --- srcs/server/Server.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 3397a6a..97701d0 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -35,7 +35,19 @@ Server::~Server() { while (!pollFds_.empty()) { int const fd = pollFds_.back().fd; if (!sockets_.isServerSocket(fd)) { - std::string nickName = clients_.at(fd).getNick(); + std::string nickName; + try { + nickName = clients_.at(fd).getNick(); + } catch (std::exception const &e) { + logger_.logMsg("\n Cannot get client object:" + "\n starting from a second client, the object " + "isn't correctly created" + "\n in function bool ClientManager::add(Client " + "&client, int fd)" + "\n Exception: " + + std::string(e.what()), + Logger::lmError); + } std::ostringstream oss; oss << "ERROR :" << serverName_ << " " << nickName << " :Server shutdown - goodbye."; From b674655ea4f104641acb7b146352617ef189dffc Mon Sep 17 00:00:00 2001 From: Rapha Date: Sun, 6 Sep 2026 11:38:47 +0200 Subject: [PATCH 255/349] feat: revert additions that could potentially have been used for a disconnect message. --- includes/Reply.hpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/includes/Reply.hpp b/includes/Reply.hpp index 3f66b33..63088e8 100644 --- a/includes/Reply.hpp +++ b/includes/Reply.hpp @@ -272,10 +272,8 @@ Use "*" for `nick` until the client has registered (pre-001). inline std::string build(const std::string &server, const std::string &nick, const Message &msg) { std::ostringstream ss; - ss << ":" << server; - if (msg.code > 0) - ss << " " << std::setw(3) << std::setfill('0') << msg.code; - ss << " " << nick; + ss << ":" << server << " " << std::setw(3) << std::setfill('0') << msg.code + << " " << nick; if (!msg.params.empty()) ss << " " << msg.params; if (msg.hasTrailing) From 1736fea42b22d394b359576fdd518b9f1f0c607e Mon Sep 17 00:00:00 2001 From: Rapha Date: Mon, 7 Sep 2026 13:10:48 +0200 Subject: [PATCH 256/349] fix: * use idx instead of fd. * add --idx bc of removed element to avaoid skip --- srcs/server/Server.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 97701d0..574c4d7 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -182,9 +182,9 @@ void Server::processClientSockets_() { } if (sockets_.socketClosed(pfd.fd)) { - removePollFd_(pfd.fd); - clients_.remove(pfd.fd, channels_); clients_.remove(pfd.fd, channels_); + removePollFd_(idx); + --idx; } } } From fbb36a7d271e14a23ff8e57b248f1c435e699897 Mon Sep 17 00:00:00 2001 From: Rapha Date: Mon, 7 Sep 2026 13:13:22 +0200 Subject: [PATCH 257/349] rfa: clang-format --- srcs/server/Server.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 574c4d7..362e5b1 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -183,8 +183,8 @@ void Server::processClientSockets_() { if (sockets_.socketClosed(pfd.fd)) { clients_.remove(pfd.fd, channels_); - removePollFd_(idx); - --idx; + removePollFd_(idx); + --idx; } } } From 90e874c2fa3c6397e47e7f9501927ff22bbc9d38 Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 10 Sep 2026 11:27:20 +0200 Subject: [PATCH 258/349] feat: * separate removeClosedSockets - needs to be done in one go at the end of a poll-loop * use fd in removePollFd (add functor pfdHasFd for use in find_if (lambdas not available in c++98) * add msgTooLong to bufferLength... function to notify that situation * eliminate use of socketClosed in isServerSocket - too much overhead * prepare Server d'tor for use of soon to be added function sendClosingMsg * remove try/catch, this block won't throw after rfa in other PR * disable output (only active for NETCATDBG) * add new passthrough Server::sendMsg * change exception msg text in SignalHnadler * add socketClosed check in checkSocket4Recv - it may habe been closed somewhere else * implement new msgTooLong functionality and reorder var definitions * disable error count for msgTooLong, this should be handled by cmds_.run (deprecated, will be reverted) rfa: * rename checkSocket for more clarity dbg: * add snippet to force crash for dbg purpose --- includes/Server.hpp | 4 +- includes/ServerHelper.hpp | 12 +++++ includes/Socket.hpp | 13 ++--- reviewNotes.txt | 27 ++++++++++ srcs/server/Server.cpp | 98 +++++++++++++++++++++++++---------- srcs/server/SignalHandler.cpp | 2 +- srcs/server/Socket.cpp | 96 ++++++++++++++++++---------------- 7 files changed, 170 insertions(+), 82 deletions(-) create mode 100644 reviewNotes.txt diff --git a/includes/Server.hpp b/includes/Server.hpp index 7b05e8a..214c441 100644 --- a/includes/Server.hpp +++ b/includes/Server.hpp @@ -39,8 +39,9 @@ class Server { void processServerSocketData_(); void acceptClient_(int const serverFd); void processClientSockets_(); + void removeClosedSockets_(); void storePollFd_(int const socketFd, bool srvSocket); - void removePollFd_(int const idx); + void removePollFd_(int const fd); void setClientInfo_(int fd, Client **clntPtr, std::string &nick, bool &isReg); void log_(std::string const &msg, Logger::logMode const lm); @@ -53,6 +54,7 @@ class Server { ~Server(); void run(); + void sendMsg(int const fd, std::string const &msg); const std::string &getServerName() const; const std::string &getServerVersion() const; diff --git a/includes/ServerHelper.hpp b/includes/ServerHelper.hpp index 7e9d387..015ea06 100644 --- a/includes/ServerHelper.hpp +++ b/includes/ServerHelper.hpp @@ -106,4 +106,16 @@ inline std::string composeAddrfamAddrPortStr(socketAddrX const &sockAddrIn46) { return oss.str(); } +class pfdHasFd { + private: + int const fd_; + + public: + pfdHasFd(int fd) : fd_(fd) {} + + bool operator()(pollfd const pfd) const { + return (pfd.fd == fd_); + } +}; + } // namespace srvHelp diff --git a/includes/Socket.hpp b/includes/Socket.hpp index 6371993..181fe03 100644 --- a/includes/Socket.hpp +++ b/includes/Socket.hpp @@ -34,15 +34,14 @@ class Socket { std::map sockIODataStore_; Logger const &logger_; - bool updateBuffer_(int const fd); - size_t - bufferLengthAndSanityCheck_(int const fd /* , bool const isRegistered */); + bool updateBuffer_(int const fd); + size_t bufferLengthAndSanityCheck_(int const fd, bool &msgTooLong); public: Socket(Logger const &logger) : logger_(logger) {} void addSocket(int const fd, bool srvSocket); - bool checkSocket(struct pollfd &pfd); // NOLINT + bool checkSocket4Recv(struct pollfd &pfd); // NOLINT std::string getCRLFLineFromBuffer(int const fd, bool &buffHasMoreCmds); bool sendMsg(int const fd, std::string const &msg); void closeSocket(int const fd); @@ -52,8 +51,10 @@ class Socket { } bool isServerSocket(int const fd) const { - if (!socketClosed(fd)) - return sockIODataStore_.find(fd)->second.isSrvSocket; + std::map::const_iterator it = + sockIODataStore_.find(fd); + if (it != sockIODataStore_.end()) + return it->second.isSrvSocket; return false; }; }; diff --git a/reviewNotes.txt b/reviewNotes.txt new file mode 100644 index 0000000..6613ea9 --- /dev/null +++ b/reviewNotes.txt @@ -0,0 +1,27 @@ +was macht Client::remove genau + * wo wird das aufgerufen + * wie wird sichergestellt, dass gleichzeitig auf pollfds gelöscht wird? + * sollte remove vllt nur von Server:: aufgerufen werden, wenn auch pollfds entfernt wird? + +Usage of exceptions to send replies from clients -> sendMsg +I'll take out the try catch, will have to see about sending the msg too long error. +Maybe this can be passed on to the cmds_.run, how to indicate? + +poll loop has to be reworked, clients cannot be closed/removed in a running poll loop with sending msgs, because maybe a set of clients was gathered elsewhere and this would result in invalid pointers. + +clients_.add( -> creation of Client should be moved inside this function. +* https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#i11-never-transfer-ownership-by-a-raw-pointer-t-or-reference-t + +multiple "*" vs nick_.empty -> maybe as a MACRO + +call setHostname in acceptClient_ (rhaas) +no: instead of passing a new Client pass serverName +instead of passing client reference to cmds.run, give it the fileDescr + + +move msgTooLong to cmds_.run -> yes. +no error counter for line too long, maybe moves to the cmd processor + +comd handler won't throw + +make separate PR for Server::sendMsg diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 362e5b1..73ab558 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -9,6 +9,7 @@ #include // for exception #include // for basic_ostream, operator<<, basic_ostrea... #include // for IPPROTO_IPV6, IPV6_V6ONLY +#include #include // for basic_ostringstream #include // for runtime_error, invalid_argument #include // for allocator, char_traits, basic_string @@ -35,6 +36,10 @@ Server::~Server() { while (!pollFds_.empty()) { int const fd = pollFds_.back().fd; if (!sockets_.isServerSocket(fd)) { + // Update after Call client member function for shutdown notice message! + // after PR feat/cmds2: implement QUIT, PING, PONG and NOTICE - #38# +//#define REMOVE_ME +#ifndef REMOVE_ME std::string nickName; try { nickName = clients_.at(fd).getNick(); @@ -52,10 +57,15 @@ Server::~Server() { oss << "ERROR :" << serverName_ << " " << nickName << " :Server shutdown - goodbye."; sockets_.sendMsg(fd, oss.str()); +#else + Client &client = clients_.at(fd); + client.sendClosingMsg("Server shutdown - goodbye"); +#endif clients_.remove(fd, channels_); } sockets_.closeSocket(fd); - pollFds_.erase(--pollFds_.end()); + this->removePollFd_(fd); + // pollFds_.erase(--pollFds_.end()); } } @@ -128,6 +138,11 @@ void Server::processServerSocketData_() { if (serverPollFds.revents & POLLIN) acceptClient_(serverPollFds.fd); } + + // DEBUG check if cleanup works correctly + static size_t crash = 0; + if (++crash > 5) + throw std::runtime_error("Server socket error."); } void Server::setClientInfo_(int fd, Client **clntPtr, std::string &nick, @@ -149,41 +164,56 @@ void Server::processClientSockets_() { ++idx) { pollfd &pfd = pollFds_.at(idx); - if (sockets_.checkSocket(pfd) && (pfd.revents & POLLIN)) { + if (sockets_.checkSocket4Recv(pfd) && (pfd.revents & POLLIN)) { - // to be replaced by a request to some sort of manager - Client *client = 0; - std::string nickName = "*"; - bool clntIsReg = false; - setClientInfo_(pfd.fd, &client, nickName, clntIsReg); - // end: to be replaced by a request to some sort of manager + // // to be replaced by a request to some sort of manager + // Client *client = 0; + // std::string nickName = "*"; + // bool clntIsReg = false; + // setClientInfo_(pfd.fd, &client, nickName, clntIsReg); + // // end: to be replaced by a request to some sort of manager std::string cmdLine; bool buffHasMoreCmds = false; do { - try { - cmdLine = - sockets_.getCRLFLineFromBuffer(pfd.fd, buffHasMoreCmds); - if (!cmdLine.empty()) - cmds_.run(*this, *client, cmdLine); - } catch (Error const &e) { - if (e.hasReply()) { - sockets_.sendMsg( - pfd.fd, - Reply::build(serverName_, nickName, e.reply())); - } else { - std::string tmp( - "Error while trying to get/execute cmd:\n"); - tmp.append("'" + cmdLine + "'\n" + e.what()); - log_(tmp, Logger::lmExcept); + // try { + cmdLine = + sockets_.getCRLFLineFromBuffer(pfd.fd, buffHasMoreCmds); + if (!cmdLine.empty()) { + // returns true if connection is to be closed + // only happens after a QUIT cmd or maybe after too many ERR_INPUTTOOLONG + if (cmds_.run( + *this, + /* should soon be: pfd.fd */ clients_.at(pfd.fd), + cmdLine)) { + sockets_.closeSocket(pfd.fd); + break; } } + // } catch (Error const &e) { + // if (e.hasReply()) { + // sockets_.sendMsg( + // pfd.fd, + // Reply::build(serverName_, nickName, e.reply())); + // } else { + // std::string tmp( + // "Error while trying to get/execute cmd:\n"); + // tmp.append("'" + cmdLine + "'\n" + e.what()); + // log_(tmp, Logger::lmExcept); + // } + // } } while (buffHasMoreCmds); } + } + removeClosedSockets_(); +} +void Server::removeClosedSockets_() { + for (size_t idx = numServerSockets_; idx < pollFds_.size(); ++idx) { + pollfd &pfd = pollFds_.at(idx); if (sockets_.socketClosed(pfd.fd)) { clients_.remove(pfd.fd, channels_); - removePollFd_(idx); + removePollFd_(pfd.fd); --idx; } } @@ -211,9 +241,11 @@ void Server::acceptClient_(int const serverFd) { storePollFd_(clientFd, false); clients_.add(*(new Client()), clientFd); +#if NETCATDBG std::cout << "Client connected:\n" << " fd: " << clientFd << "\n" << srvHelp::composeAddrfamAddrPortStr(sockAddrIn46) << std::endl; +#endif } void Server::storePollFd_(int const socketFd, bool srvSocket) { @@ -225,10 +257,11 @@ void Server::storePollFd_(int const socketFd, bool srvSocket) { sockets_.addSocket(socketFd, srvSocket); } -void Server::removePollFd_(int const idx) { - std::vector::difference_type dist = - static_cast::difference_type>(idx); - pollFds_.erase(pollFds_.begin() + dist); +void Server::removePollFd_(int const fd) { + std::vector::iterator it = + std::find_if(pollFds_.begin(), pollFds_.end(), srvHelp::pfdHasFd(fd)); + if (it != pollFds_.end()) + pollFds_.erase(it); } void Server::log_(std::string const &msg, Logger::logMode const lm) { @@ -282,3 +315,12 @@ void Server::run() { processClientSockets_(); } } + +void Server::sendMsg(int const fd, std::string const &msg) { + if (!sockets_.sendMsg(fd, msg)) { + // will be done at the end of a poll loop + + // clients_.remove(fd, channels_); + // removePollFd_(fd); + } +} \ No newline at end of file diff --git a/srcs/server/SignalHandler.cpp b/srcs/server/SignalHandler.cpp index 415275b..852bd52 100644 --- a/srcs/server/SignalHandler.cpp +++ b/srcs/server/SignalHandler.cpp @@ -52,7 +52,7 @@ SignalHandler::SignalHandler() { stopSignals_.push_back(SIGTERM); if (!setStopSignals_(handleStopSignals_)) - throw std::runtime_error("std::signal failed"); + throw std::runtime_error("Setting stop signals failed"); } /* diff --git a/srcs/server/Socket.cpp b/srcs/server/Socket.cpp index fefd917..1dfa0c6 100644 --- a/srcs/server/Socket.cpp +++ b/srcs/server/Socket.cpp @@ -13,7 +13,10 @@ void Socket::addSocket(int const fd, bool srvSocket) { sockIODataStore_.insert(std::make_pair(fd, SocketIOData(srvSocket))); } -bool Socket::checkSocket(struct pollfd &pfd) { +bool Socket::checkSocket4Recv(struct pollfd &pfd) { + if (socketClosed(pfd.fd)) + return false; + SocketIOData &sIOD = sockIODataStore_.at(pfd.fd); if ((pfd.revents & POLLERR)) { if (sIOD.recvErrorCount >= SOCKETERRORLIMIT) @@ -40,50 +43,51 @@ bool Socket::updateBuffer_(int const fd) { } std::string Socket::getCRLFLineFromBuffer(int const fd, bool &buffHasMoreCmds) { - Socket::SocketIOData &sIOD = sockIODataStore_.at(fd); - std::string cmdLine; - - if (!buffHasMoreCmds) + if (!buffHasMoreCmds) { if (!updateBuffer_(fd)) - return cmdLine; + return ""; + } + + bool msgTooLong = false; + size_t lfcrPos = bufferLengthAndSanityCheck_(fd, msgTooLong); + if (msgTooLong) + return std::string(MAXMSGLEN + 1, '\0'); + if (lfcrPos == std::string::npos) + return ""; - size_t lfcrPos = bufferLengthAndSanityCheck_(fd); - if (lfcrPos != std::string::npos) { + Socket::SocketIOData &sIOD = sockIODataStore_.at(fd); #if NETCATDBG - { - std::ostringstream oss; - oss << "You are client fd " << fd << ": \n" - << "Srv received: " << std::string(sIOD.readBuffer) - << std::endl; - sendMsg(fd, oss.str()); - } -#endif - sIOD.recvErrorCount = 0; - cmdLine = sIOD.readBuffer.substr(0, lfcrPos); - sIOD.readBuffer = sIOD.readBuffer.substr(lfcrPos + 2); - buffHasMoreCmds = (sIOD.readBuffer.find("\r\n") != std::string::npos); + { + std::ostringstream oss; + oss << "You are client fd " << fd << ": \n" + << "Srv received: " << std::string(sIOD.readBuffer) << std::endl; + sendMsg(fd, oss.str()); } +#endif + std::string cmdLine; + cmdLine = sIOD.readBuffer.substr(0, lfcrPos); + sIOD.recvErrorCount = 0; + sIOD.readBuffer = sIOD.readBuffer.substr(lfcrPos + 2); + buffHasMoreCmds = (sIOD.readBuffer.find("\r\n") != std::string::npos); return cmdLine; } -size_t Socket::bufferLengthAndSanityCheck_( - int const fd /* , bool const isRegistered */) { +size_t Socket::bufferLengthAndSanityCheck_(int const fd, bool &msgTooLong) { Socket::SocketIOData &sIOD = sockIODataStore_.at(fd); std::string &rdBuffer = sIOD.readBuffer; size_t lfcrPos = rdBuffer.find("\r\n"); - bool msgTooLong = false; - if ((rdBuffer.length() >= MAXMSGLEN) && + if ((!sIOD.discardMode) && (rdBuffer.length() >= MAXMSGLEN) && (lfcrPos == std::string::npos || lfcrPos > MAXMSGLEN - 2)) { sIOD.discardMode = true; - ++sIOD.recvErrorCount; msgTooLong = true; + // ++sIOD.recvErrorCount; } if (sIOD.discardMode) { - if (/* !isRegistered || */ sIOD.recvErrorCount >= SOCKETERRORLIMIT) { - closeSocket(fd); - return std::string::npos; - } + // if (sIOD.recvErrorCount >= SOCKETERRORLIMIT) { + // closeSocket(fd); + // return std::string::npos; + // } if (lfcrPos != std::string::npos) { rdBuffer.erase(0, lfcrPos + 2); sIOD.discardMode = false; @@ -95,8 +99,8 @@ size_t Socket::bufferLengthAndSanityCheck_( rdBuffer.clear(); } } - if (msgTooLong) - throw Error(Reply::reply(Reply::ERR_INPUTTOOLONG, "")); + // if (msgTooLong) + // throw Error(Reply::reply(Reply::ERR_INPUTTOOLONG, "")); return lfcrPos; } @@ -108,6 +112,9 @@ size_t Socket::bufferLengthAndSanityCheck_( * closes the socket (== disconnects the client) in case of too many errors */ bool Socket::sendMsg(int const fd, std::string const &msg) { + if (socketClosed(fd)) + return false; + SocketIOData &sIOD = sockIODataStore_.at(fd); ssize_t bytesSent = -1; bool success = true; @@ -126,23 +133,20 @@ bool Socket::sendMsg(int const fd, std::string const &msg) { success = false; } -#if NETCATDBG - std::ostringstream oss; - oss << "SendMsg event:\n" - << " WriteBuffer: '" << sIOD.writeBuffer << "':\n"; - if (!success) { - oss << " send to FD " << fd << " failed."; - logger_.logMsg(oss.str(), Logger::lmError); - } else { - if (static_cast(bytesSent) < sIOD.writeBuffer.length()) + { + std::ostringstream oss; + oss << "SendMsg event:\n" + << " WriteBuffer: '" << sIOD.writeBuffer << "':\n"; + if (!success) { + oss << " send to FD " << fd << " failed."; + logger_.logMsg(oss.str(), Logger::lmError); + } else if (static_cast(bytesSent) < sIOD.writeBuffer.length()) { oss << " partly (" << bytesSent << "/" << sIOD.writeBuffer.length() - << ") " << sIOD.writeBuffer.substr(0, bytesSent); - else - oss << " successfully"; - oss << " sent to FD: " << fd; - logger_.logMsg(oss.str(), Logger::lmInfo); + << ") " << sIOD.writeBuffer.substr(0, bytesSent) + << " sent to FD: " << fd; + logger_.logMsg(oss.str(), Logger::lmInfo); + } } -#endif if (!success) return false; From f12be36dca0b5a4967863fe48ae11e190050632a Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 10 Sep 2026 11:48:51 +0200 Subject: [PATCH 259/349] feat: * reactivate error count for msgTooLong --- srcs/server/Socket.cpp | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/srcs/server/Socket.cpp b/srcs/server/Socket.cpp index 1dfa0c6..198aa90 100644 --- a/srcs/server/Socket.cpp +++ b/srcs/server/Socket.cpp @@ -81,16 +81,16 @@ size_t Socket::bufferLengthAndSanityCheck_(int const fd, bool &msgTooLong) { (lfcrPos == std::string::npos || lfcrPos > MAXMSGLEN - 2)) { sIOD.discardMode = true; msgTooLong = true; - // ++sIOD.recvErrorCount; } if (sIOD.discardMode) { - // if (sIOD.recvErrorCount >= SOCKETERRORLIMIT) { - // closeSocket(fd); - // return std::string::npos; - // } + if (++sIOD.recvErrorCount >= SOCKETERRORLIMIT) { + closeSocket(fd); + return std::string::npos; + } if (lfcrPos != std::string::npos) { rdBuffer.erase(0, lfcrPos + 2); sIOD.discardMode = false; + sIOD.recvErrorCount = 0; lfcrPos = rdBuffer.find("\r\n"); } else /* lfcrPos == std::string::npos */ { if ('\r' == *rdBuffer.rbegin()) @@ -99,9 +99,6 @@ size_t Socket::bufferLengthAndSanityCheck_(int const fd, bool &msgTooLong) { rdBuffer.clear(); } } - // if (msgTooLong) - // throw Error(Reply::reply(Reply::ERR_INPUTTOOLONG, "")); - return lfcrPos; } From b1db1958ebcdf69e8e9d56ab46904c301f15ff91 Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 10 Sep 2026 12:05:04 +0200 Subject: [PATCH 260/349] feat: * rework ClientManager::add to only take a fd; the Client * can be created if necessary there, doesn't hvae to be done in Server --- includes/ClientManager.hpp | 6 +++--- srcs/server/ClientManager.cpp | 5 +++-- srcs/server/Server.cpp | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/includes/ClientManager.hpp b/includes/ClientManager.hpp index 97cc01e..3e50e96 100644 --- a/includes/ClientManager.hpp +++ b/includes/ClientManager.hpp @@ -49,9 +49,9 @@ class ClientManager { bool hasNick(const std::string &name) const; std::size_t size() const; - // add takes ownership of `client`; returns false (and does not take - // ownership) if `client` is NULL or `fd` is already present. - bool add(int fd, Client *client); + // add takes a file descriptor of a newly created socket + // returns false if `fd` is already present. + bool add(int fd); // setNick changes `client`'s nick and keeps the nick index in sync. // The caller validates the nick and checks hasNick() first. void setNick(Client &client, const std::string &nick); diff --git a/srcs/server/ClientManager.cpp b/srcs/server/ClientManager.cpp index 7cc7a1c..f1f9ce5 100644 --- a/srcs/server/ClientManager.cpp +++ b/srcs/server/ClientManager.cpp @@ -46,9 +46,10 @@ std::size_t ClientManager::size() const { return byFd_.size(); } -bool ClientManager::add(int fd, Client *client) { - if (!client || hasClient(fd)) +bool ClientManager::add(int fd) { + if (hasClient(fd)) return false; + Client *client = new Client(fd); byFd_.insert(std::make_pair(fd, client)); if (!client->getNick().empty()) byNick_.insert(std::make_pair(client->getNickCaseFolded(), fd)); diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 73ab558..f6f5242 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -239,7 +239,7 @@ void Server::acceptClient_(int const serverFd) { srvHelp::setSocketNonBlock(clientFd); storePollFd_(clientFd, false); - clients_.add(*(new Client()), clientFd); + clients_.add(clientFd); #if NETCATDBG std::cout << "Client connected:\n" From 053c5294c259eaf75a9a0a759f2cfb610fb09edd Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 10 Sep 2026 12:07:38 +0200 Subject: [PATCH 261/349] feat: * init clients hostname with servername --- includes/Client.hpp | 5 +++-- includes/ClientManager.hpp | 4 ++-- srcs/server/ClientManager.cpp | 4 ++-- srcs/server/Server.cpp | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/includes/Client.hpp b/includes/Client.hpp index 8e1914e..9dd0df0 100644 --- a/includes/Client.hpp +++ b/includes/Client.hpp @@ -29,8 +29,9 @@ class Client { Client() : fd_(-1), registered_(false), passAccepted_(false), oper_(false) {} - explicit Client(int fd) - : fd_(fd), registered_(false), passAccepted_(false), oper_(false) {} + explicit Client(int fd, std::string const &serverName) + : fd_(fd), hostname_(serverName), registered_(false), + passAccepted_(false), oper_(false) {} int getFd() const; std::string getNick() const; diff --git a/includes/ClientManager.hpp b/includes/ClientManager.hpp index 3e50e96..dbb0d44 100644 --- a/includes/ClientManager.hpp +++ b/includes/ClientManager.hpp @@ -50,8 +50,8 @@ class ClientManager { std::size_t size() const; // add takes a file descriptor of a newly created socket - // returns false if `fd` is already present. - bool add(int fd); + // returns false if `fd` is already present. + bool add(int fd, std::string const &serverName); // setNick changes `client`'s nick and keeps the nick index in sync. // The caller validates the nick and checks hasNick() first. void setNick(Client &client, const std::string &nick); diff --git a/srcs/server/ClientManager.cpp b/srcs/server/ClientManager.cpp index f1f9ce5..2cf0fa2 100644 --- a/srcs/server/ClientManager.cpp +++ b/srcs/server/ClientManager.cpp @@ -46,10 +46,10 @@ std::size_t ClientManager::size() const { return byFd_.size(); } -bool ClientManager::add(int fd) { +bool ClientManager::add(int fd, std::string const &serverName) { if (hasClient(fd)) return false; - Client *client = new Client(fd); + Client *client = new Client(fd, serverName); byFd_.insert(std::make_pair(fd, client)); if (!client->getNick().empty()) byNick_.insert(std::make_pair(client->getNickCaseFolded(), fd)); diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index f6f5242..afbf59c 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -239,7 +239,7 @@ void Server::acceptClient_(int const serverFd) { srvHelp::setSocketNonBlock(clientFd); storePollFd_(clientFd, false); - clients_.add(clientFd); + clients_.add(clientFd, serverName_); #if NETCATDBG std::cout << "Client connected:\n" From afa828df6b5e719fb7b47979138ae4070b377933 Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 10 Sep 2026 12:12:01 +0200 Subject: [PATCH 262/349] git: ignore personal review file --- .gitignore | 1 + reviewNotes.txt | 27 --------------------------- 2 files changed, 1 insertion(+), 27 deletions(-) delete mode 100644 reviewNotes.txt diff --git a/.gitignore b/.gitignore index f36d625..c183ee7 100644 --- a/.gitignore +++ b/.gitignore @@ -74,3 +74,4 @@ vcpkg_installed/ # test output & cache Testing/ .cache/ +reviewNotes.txt diff --git a/reviewNotes.txt b/reviewNotes.txt deleted file mode 100644 index 6613ea9..0000000 --- a/reviewNotes.txt +++ /dev/null @@ -1,27 +0,0 @@ -was macht Client::remove genau - * wo wird das aufgerufen - * wie wird sichergestellt, dass gleichzeitig auf pollfds gelöscht wird? - * sollte remove vllt nur von Server:: aufgerufen werden, wenn auch pollfds entfernt wird? - -Usage of exceptions to send replies from clients -> sendMsg -I'll take out the try catch, will have to see about sending the msg too long error. -Maybe this can be passed on to the cmds_.run, how to indicate? - -poll loop has to be reworked, clients cannot be closed/removed in a running poll loop with sending msgs, because maybe a set of clients was gathered elsewhere and this would result in invalid pointers. - -clients_.add( -> creation of Client should be moved inside this function. -* https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#i11-never-transfer-ownership-by-a-raw-pointer-t-or-reference-t - -multiple "*" vs nick_.empty -> maybe as a MACRO - -call setHostname in acceptClient_ (rhaas) -no: instead of passing a new Client pass serverName -instead of passing client reference to cmds.run, give it the fileDescr - - -move msgTooLong to cmds_.run -> yes. -no error counter for line too long, maybe moves to the cmd processor - -comd handler won't throw - -make separate PR for Server::sendMsg From 0840afdfb8b6c5ac9794b19f7ada48df4fc994fe Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 10 Sep 2026 17:11:52 +0200 Subject: [PATCH 263/349] feat: * register commands in CommandProcessor c'tor * remove setClientInfo (unused, deprecated) * log client connect msg instead of just showing it in dbg mode rfa: shorten Client connect log msg --- includes/CommandProcessor.hpp | 4 ++++ includes/Server.hpp | 2 -- includes/ServerHelper.hpp | 4 ++-- srcs/server/Server.cpp | 37 ++++++++--------------------------- 4 files changed, 14 insertions(+), 33 deletions(-) diff --git a/includes/CommandProcessor.hpp b/includes/CommandProcessor.hpp index c124c65..2b1304c 100644 --- a/includes/CommandProcessor.hpp +++ b/includes/CommandProcessor.hpp @@ -40,6 +40,10 @@ class CommandProcessor { Command &at_(const std::string &name); public: + CommandProcessor() { + registerCommands(); + } + void registerCommands(); // parser TODO blnunez- bool parse(const std::string &line, std::string &name, diff --git a/includes/Server.hpp b/includes/Server.hpp index 214c441..dd2a2ed 100644 --- a/includes/Server.hpp +++ b/includes/Server.hpp @@ -42,8 +42,6 @@ class Server { void removeClosedSockets_(); void storePollFd_(int const socketFd, bool srvSocket); void removePollFd_(int const fd); - void setClientInfo_(int fd, Client **clntPtr, std::string &nick, - bool &isReg); void log_(std::string const &msg, Logger::logMode const lm); Server(Server const &other); diff --git a/includes/ServerHelper.hpp b/includes/ServerHelper.hpp index 015ea06..6c43a78 100644 --- a/includes/ServerHelper.hpp +++ b/includes/ServerHelper.hpp @@ -100,8 +100,8 @@ inline std::string composeAddrfamAddrPortStr(socketAddrX const &sockAddrIn46) { addrStr = "[" + addrStr + "]"; } std::ostringstream oss; - oss << " AF: " << getAdressFamilyStr(saFamily) - << "\n addr:port: " << addrStr << ":" << std::dec << portNumber; + oss << " AF:" << getAdressFamilyStr(saFamily) + << "addr:port: " << addrStr << ":" << std::dec << portNumber; return oss.str(); } diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index afbf59c..4149d17 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -140,22 +140,9 @@ void Server::processServerSocketData_() { } // DEBUG check if cleanup works correctly - static size_t crash = 0; - if (++crash > 5) - throw std::runtime_error("Server socket error."); -} - -void Server::setClientInfo_(int fd, Client **clntPtr, std::string &nick, - bool &isReg) { - try { - *clntPtr = &clients_.at(fd); - if (*clntPtr && (*clntPtr)->isRegistered()) { - isReg = true; - nick = (*clntPtr)->getNick(); - } - } catch (std::exception const &e) { - log_(e.what(), Logger::lmWarn); - } + // static size_t crash = 0; + // if (++crash > 5) + // throw std::runtime_error("DBG: Server socket error."); } void Server::processClientSockets_() { @@ -165,14 +152,6 @@ void Server::processClientSockets_() { pollfd &pfd = pollFds_.at(idx); if (sockets_.checkSocket4Recv(pfd) && (pfd.revents & POLLIN)) { - - // // to be replaced by a request to some sort of manager - // Client *client = 0; - // std::string nickName = "*"; - // bool clntIsReg = false; - // setClientInfo_(pfd.fd, &client, nickName, clntIsReg); - // // end: to be replaced by a request to some sort of manager - std::string cmdLine; bool buffHasMoreCmds = false; do { @@ -241,11 +220,11 @@ void Server::acceptClient_(int const serverFd) { storePollFd_(clientFd, false); clients_.add(clientFd, serverName_); -#if NETCATDBG - std::cout << "Client connected:\n" - << " fd: " << clientFd << "\n" - << srvHelp::composeAddrfamAddrPortStr(sockAddrIn46) << std::endl; -#endif + std::ostringstream oss; + oss << "Client connected:" + << " fd:" << clientFd + << srvHelp::composeAddrfamAddrPortStr(sockAddrIn46) << std::endl; + log_(oss.str(), Logger::lmInfo); } void Server::storePollFd_(int const socketFd, bool srvSocket) { From eddc79501429f3421d2e66bfbe03dd8890730ef1 Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 10 Sep 2026 17:13:26 +0200 Subject: [PATCH 264/349] feat: * remove throw and replace by a log entry and appropriate prog flow; except for when a system function fails --- includes/Server.hpp | 2 +- includes/ServerHelper.hpp | 4 ++-- srcs/main.cpp | 36 ++++++++++++++++++++++-------------- srcs/server/Server.cpp | 17 ++++++++--------- 4 files changed, 33 insertions(+), 26 deletions(-) diff --git a/includes/Server.hpp b/includes/Server.hpp index dd2a2ed..8921ed9 100644 --- a/includes/Server.hpp +++ b/includes/Server.hpp @@ -34,7 +34,7 @@ class Server { size_t numServerSockets_; std::vector pollFds_; - void setupServerSockets_(); + bool setupServerSockets_(); int createServerSocket_(int const domain); void processServerSocketData_(); void acceptClient_(int const serverFd); diff --git a/includes/ServerHelper.hpp b/includes/ServerHelper.hpp index 6c43a78..ae96eac 100644 --- a/includes/ServerHelper.hpp +++ b/includes/ServerHelper.hpp @@ -100,8 +100,8 @@ inline std::string composeAddrfamAddrPortStr(socketAddrX const &sockAddrIn46) { addrStr = "[" + addrStr + "]"; } std::ostringstream oss; - oss << " AF:" << getAdressFamilyStr(saFamily) - << "addr:port: " << addrStr << ":" << std::dec << portNumber; + oss << " AF:" << getAdressFamilyStr(saFamily) << "addr:port: " << addrStr + << ":" << std::dec << portNumber; return oss.str(); } diff --git a/srcs/main.cpp b/srcs/main.cpp index 2788ea2..8d7e09b 100644 --- a/srcs/main.cpp +++ b/srcs/main.cpp @@ -69,27 +69,35 @@ int main(int argc, char **argv) { return 0; } +static void toCerr(std::string const &msg, std::string const &colour) { + std::cerr << colour << msg << CReset << std::endl; +} + static bool checkExtractCmdLnArgs(int const argc, char *const argv[], int &port, std::string &password) { - std::istringstream iss; - std::string remainder; if (argc != 3) { - std::cerr << CYellow << "Usage: ./ircserv " - << CReset << std::endl; + toCerr("Usage: ./ircserv ", CYellow); return false; } - iss.str(argv[1]); // NOLINT - if (!((iss >> port) && (iss >> remainder).eof())) - throw std::runtime_error( - "Error while extracting port number from commandline parameter."); - if (port < 1 || port > 65535) - throw std::runtime_error("Error: port must be in the range 1-65535."); + std::istringstream issPort(argv[1]); // NOLINT + std::string remainder; + if (!((issPort >> port) && (issPort >> remainder).eof())) { + toCerr("Error while extracting port number from commandline parameter.", + CRed); + return false; + } + if (port < 1 || port > 65535) { + toCerr("Error: port must be in the range 1-65535.", CRed); + return false; + } - iss.str(argv[2]); // NOLINT - if (!(iss >> password).eof()) - throw std::runtime_error( - "Error while extracting password from commandline parameter."); + std::istringstream issPass(argv[2]); // NOLINT + if (!(issPass >> password).eof()) { + toCerr("Error while extracting password from commandline parameter.", + CRed); + return false; + } return true; } diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 4149d17..a89a7da 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -69,7 +69,7 @@ Server::~Server() { } } -void Server::setupServerSockets_() { +bool Server::setupServerSockets_() { static int const domArr[] = {AF_INET, AF_INET6}; static std::vector const domains( // NOLINTNEXTLINE @@ -81,8 +81,11 @@ void Server::setupServerSockets_() { log_(e.what(), Logger::lmExcept); } } - if (numServerSockets_ == 0) - throw std::runtime_error("No server socket could be setup correctly."); + if (numServerSockets_ == 0) { + log_("No server socket could be setup correctly.", Logger::lmError); + return false; + } + return true; } /* @@ -138,11 +141,6 @@ void Server::processServerSocketData_() { if (serverPollFds.revents & POLLIN) acceptClient_(serverPollFds.fd); } - - // DEBUG check if cleanup works correctly - // static size_t crash = 0; - // if (++crash > 5) - // throw std::runtime_error("DBG: Server socket error."); } void Server::processClientSockets_() { @@ -279,7 +277,8 @@ ClientManager &Server::getClientManager() { * handles client connect and disconnect scenarios */ void Server::run() { - setupServerSockets_(); + if (!setupServerSockets_()) + return; while (!SignalHandler::stopLoopSignalReceived()) { int pollResult = poll(pollFds_.data(), pollFds_.size(), -1); From 5bfc028f03a6fd65c4782e3111b0babc9c153248 Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 10 Sep 2026 17:17:44 +0200 Subject: [PATCH 265/349] rfa: log format --- includes/ServerHelper.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/ServerHelper.hpp b/includes/ServerHelper.hpp index ae96eac..468da80 100644 --- a/includes/ServerHelper.hpp +++ b/includes/ServerHelper.hpp @@ -100,7 +100,7 @@ inline std::string composeAddrfamAddrPortStr(socketAddrX const &sockAddrIn46) { addrStr = "[" + addrStr + "]"; } std::ostringstream oss; - oss << " AF:" << getAdressFamilyStr(saFamily) << "addr:port: " << addrStr + oss << " AF:" << getAdressFamilyStr(saFamily) << " addr:port: " << addrStr << ":" << std::dec << portNumber; return oss.str(); From 60ff38c2a29ba8a8f0f225027089859db3623c93 Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 10 Sep 2026 18:21:54 +0200 Subject: [PATCH 266/349] rfa: split composeAddrfamAddrPortStr to make subfunctionality usable elsewhere --- includes/ServerHelper.hpp | 52 ++++++++++++++++++++++++++++++--------- 1 file changed, 40 insertions(+), 12 deletions(-) diff --git a/includes/ServerHelper.hpp b/includes/ServerHelper.hpp index 468da80..2740562 100644 --- a/includes/ServerHelper.hpp +++ b/includes/ServerHelper.hpp @@ -73,16 +73,10 @@ inline void bindServerSocket(int const socketFd, int const domain, // NOLINT getAdressFamilyStr(domain) + " !"); } -inline std::string composeAddrfamAddrPortStr(socketAddrX const &sockAddrIn46) { - char addrBuffer[128]; - socklen_t const addrBuffLen = sizeof(addrBuffer) / sizeof(char); - - sa_family_t const saFamily = - // NOLINTNEXTLINE - reinterpret_cast(sockAddrIn46.sai4).sa_family; +inline void getIPandPort(socketAddrX const &sockAddrIn46, + sa_family_t const saFamily, uint16_t &portNumber, + std::string &addrStr) { void const *addrPtr = 0; - uint16_t portNumber = 0; - if (saFamily == AF_INET) { addrPtr = &sockAddrIn46.sai4.sin_addr; portNumber = ntohs(sockAddrIn46.sai4.sin_port); @@ -90,15 +84,49 @@ inline std::string composeAddrfamAddrPortStr(socketAddrX const &sockAddrIn46) { addrPtr = &sockAddrIn46.sai6.sin6_addr; portNumber = ntohs(sockAddrIn46.sai6.sin6_port); } - char const *addrStrPtr = + + char addrBuffer[128]; + socklen_t const addrBuffLen = sizeof(addrBuffer) / sizeof(char); + char const *addrStrPtr = inet_ntop(saFamily, addrPtr, &addrBuffer[0], addrBuffLen); - std::string addrStr("No addr:port information."); if (addrStrPtr) { addrStr.assign(addrStrPtr); if (saFamily == AF_INET6) addrStr = "[" + addrStr + "]"; - } + } else + addrStr.assign("No addr:port information."); +} + +inline sa_family_t getSaFamily(socketAddrX const &sockAddrIn46) { + // NOLINTNEXTLINE + return reinterpret_cast(sockAddrIn46.sai4) + .sa_family; +} + +inline std::string composeAddrfamAddrPortStr(socketAddrX const &sockAddrIn46) { + sa_family_t const saFamily = getSaFamily(sockAddrIn46); + + uint16_t portNumber = 0; + std::string addrStr("No addr:port information."); + getIPandPort(sockAddrIn46, saFamily, portNumber, addrStr); + + // if (saFamily == AF_INET) { + // addrPtr = &sockAddrIn46.sai4.sin_addr; + // portNumber = ntohs(sockAddrIn46.sai4.sin_port); + // } else if (saFamily == AF_INET6) { + // addrPtr = &sockAddrIn46.sai6.sin6_addr; + // portNumber = ntohs(sockAddrIn46.sai6.sin6_port); + // } + // char const *addrStrPtr = + // inet_ntop(saFamily, addrPtr, &addrBuffer[0], addrBuffLen); + + // std::string addrStr("No addr:port information."); + // if (addrStrPtr) { + // addrStr.assign(addrStrPtr); + // if (saFamily == AF_INET6) + // addrStr = "[" + addrStr + "]"; + // } std::ostringstream oss; oss << " AF:" << getAdressFamilyStr(saFamily) << " addr:port: " << addrStr << ":" << std::dec << portNumber; From ae6f4f0ff2af884fc72b60833094c9a4558e9386 Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 10 Sep 2026 18:24:04 +0200 Subject: [PATCH 267/349] feat: * use client.sendClosingMsg as it is available now * keep try/catch in poll loop active until client error msgs have been moved from exception to regular replies * set client hostname to socket IP --- srcs/server/Server.cpp | 65 ++++++++++++++++++++++++------------------ 1 file changed, 38 insertions(+), 27 deletions(-) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index a89a7da..3f55c75 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -38,7 +38,7 @@ Server::~Server() { if (!sockets_.isServerSocket(fd)) { // Update after Call client member function for shutdown notice message! // after PR feat/cmds2: implement QUIT, PING, PONG and NOTICE - #38# -//#define REMOVE_ME +#define REMOVE_ME #ifndef REMOVE_ME std::string nickName; try { @@ -65,7 +65,6 @@ Server::~Server() { } sockets_.closeSocket(fd); this->removePollFd_(fd); - // pollFds_.erase(--pollFds_.end()); } } @@ -153,32 +152,39 @@ void Server::processClientSockets_() { std::string cmdLine; bool buffHasMoreCmds = false; do { - // try { - cmdLine = - sockets_.getCRLFLineFromBuffer(pfd.fd, buffHasMoreCmds); - if (!cmdLine.empty()) { - // returns true if connection is to be closed - // only happens after a QUIT cmd or maybe after too many ERR_INPUTTOOLONG - if (cmds_.run( - *this, - /* should soon be: pfd.fd */ clients_.at(pfd.fd), - cmdLine)) { - sockets_.closeSocket(pfd.fd); - break; +#define ERRMSG2CLIENTBYEXCEPT +#ifdef ERRMSG2CLIENTBYEXCEPT + try { +#endif + cmdLine = + sockets_.getCRLFLineFromBuffer(pfd.fd, buffHasMoreCmds); + if (!cmdLine.empty()) { + // returns true if connection is to be closed + // only happens after a QUIT cmd or maybe after too many ERR_INPUTTOOLONG + if (cmds_.run(*this, + /* should soon be: pfd.fd */ + clients_.at(pfd.fd), cmdLine)) { + sockets_.closeSocket(pfd.fd); + break; + } + } +#ifdef ERRMSG2CLIENTBYEXCEPT + } catch (Error const &e) { + if (e.hasReply()) { + Client *client = clients_.find(pfd.fd); + std::string nickName = + (client ? client->getNick() : "dbgDummyNick"); + sockets_.sendMsg( + pfd.fd, + Reply::build(serverName_, nickName, e.reply())); + } else { + std::string tmp( + "Error while trying to get/execute cmd:\n"); + tmp.append("'" + cmdLine + "'\n" + e.what()); + log_(tmp, Logger::lmExcept); } } - // } catch (Error const &e) { - // if (e.hasReply()) { - // sockets_.sendMsg( - // pfd.fd, - // Reply::build(serverName_, nickName, e.reply())); - // } else { - // std::string tmp( - // "Error while trying to get/execute cmd:\n"); - // tmp.append("'" + cmdLine + "'\n" + e.what()); - // log_(tmp, Logger::lmExcept); - // } - // } +#endif } while (buffHasMoreCmds); } } @@ -216,7 +222,12 @@ void Server::acceptClient_(int const serverFd) { srvHelp::setSocketNonBlock(clientFd); storePollFd_(clientFd, false); - clients_.add(clientFd, serverName_); + + std::string clientIP; + uint16_t port; + srvHelp::getIPandPort(sockAddrIn46, srvHelp::getSaFamily(sockAddrIn46), + port, clientIP); + clients_.add(clientFd, clientIP); std::ostringstream oss; oss << "Client connected:" From e30114949622582a5cca123a48c6070874c114fd Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 10 Sep 2026 18:24:41 +0200 Subject: [PATCH 268/349] feat: log more info about client (dis)connect --- srcs/server/Socket.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/srcs/server/Socket.cpp b/srcs/server/Socket.cpp index 198aa90..1295616 100644 --- a/srcs/server/Socket.cpp +++ b/srcs/server/Socket.cpp @@ -34,8 +34,16 @@ bool Socket::updateBuffer_(int const fd) { sIOD.bytesRead = recv(fd, &recvBuffer[0], RECVBUFFLEN, 0); if (sIOD.bytesRead <= 0) { ++sIOD.recvErrorCount; - if (sIOD.bytesRead == 0 || sIOD.recvErrorCount >= SOCKETERRORLIMIT) + if (sIOD.bytesRead == 0 || sIOD.recvErrorCount >= SOCKETERRORLIMIT) { + std::ostringstream oss; + oss << "Client sockFd:" << fd; + if (sIOD.recvErrorCount >= SOCKETERRORLIMIT) + oss << " exceeded error limit (" << SOCKETERRORLIMIT << ")"; + else if (sIOD.bytesRead == 0) + oss << " closed connection on their side"; + logger_.logMsg(oss.str(), Logger::lmInfo); closeSocket(fd); + } return false; } sIOD.readBuffer += std::string(&recvBuffer[0], sIOD.bytesRead); @@ -164,7 +172,7 @@ void Socket::closeSocket(int const fd) { oss << "Closing server socket (" << srvHelp::getAdressFamilyStr(srvHelp::sockAddrFamily(fd)) << ")"; } else { - oss << "Closing client with sockFd " << fd; + oss << "Closing client sockFd:" << fd; } logger_.logMsg(oss.str(), Logger::lmInfo); From 7a6250b883ee1b2b6947c9a490bf6d974fd5638e Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 10 Sep 2026 19:02:49 +0200 Subject: [PATCH 269/349] feat: use one global static function to get a string for "now" --- includes/Logger.hpp | 4 ++-- srcs/server/Logger.cpp | 2 +- srcs/server/Server.cpp | 7 ++----- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/includes/Logger.hpp b/includes/Logger.hpp index 9cee6ef..060738e 100644 --- a/includes/Logger.hpp +++ b/includes/Logger.hpp @@ -6,7 +6,8 @@ class Logger { std::string prefix_; - static std::string nowStr_() { + public: + static std::string nowStr() { time_t now = time(0); struct tm *timeinfo = localtime(&now); char buffer[32]; @@ -14,7 +15,6 @@ class Logger { return std::string(&buffer[0]); } - public: enum logMode { lmInfo, lmWarn, lmError, lmExcept }; Logger(std::string const &prefix) : prefix_(prefix) {} diff --git a/srcs/server/Logger.cpp b/srcs/server/Logger.cpp index 16190d2..cd3ed79 100644 --- a/srcs/server/Logger.cpp +++ b/srcs/server/Logger.cpp @@ -31,5 +31,5 @@ void Logger::logMsg(std::string msg, logMode const lm) const { *os << CMgnta; break; } - *os << nowStr_() << " " << prefix_ << msg << CReset << std::endl; + *os << nowStr() << " " << prefix_ << msg << CReset << std::endl; } diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 3f55c75..dfd0e34 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -21,13 +21,10 @@ * init the logger prefix (server name) */ Server::Server(int const port, std::string const &password) - : serverName_(SERVER_NAME), port_(port), password_(password), + : serverName_(SERVER_NAME), serverVersion_(SERVER_VERSION), + createdAt_(Logger::nowStr()), port_(port), password_(password), logger_(serverName_ + ": "), sockets_(logger_), numServerSockets_(0) {} -/* - Cleanup - * going backwards through pollFds to ensure server sockets are closed last - */ /* Cleanup * going backwards through pollFds to ensure server sockets are closed last From 96364b6f9aaa69134b9e03f2e90798130288aa96 Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 10 Sep 2026 19:05:53 +0200 Subject: [PATCH 270/349] feat: add @kmurugan's hack to send msgs to clients. Should work at least for single client communication --- srcs/server/Server.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index dfd0e34..8ae6b8b 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -183,6 +183,13 @@ void Server::processClientSockets_() { } #endif } while (buffHasMoreCmds); + + Client *client = clients_.find(pfd.fd); + if (client && !client->getSendBuffer().empty()) { + const std::string &out = client->getSendBuffer(); + sockets_.sendMsg(pfd.fd, out); + client->consumeSendBuffer(out.size()); + } } } removeClosedSockets_(); From 9cca906b50911bf28b9817f25a8a248d9075d497 Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 10 Sep 2026 19:09:27 +0200 Subject: [PATCH 271/349] feat: remove trainling newline of Client connect msg --- srcs/server/Server.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 8ae6b8b..0697f6e 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -57,6 +57,12 @@ Server::~Server() { #else Client &client = clients_.at(fd); client.sendClosingMsg("Server shutdown - goodbye"); + + // REMOVE ME ---> + const std::string &out = client.getSendBuffer(); + sockets_.sendMsg(fd, out); + client.consumeSendBuffer(out.size()); + // <--- REMOVE ME #endif clients_.remove(fd, channels_); } @@ -184,12 +190,14 @@ void Server::processClientSockets_() { #endif } while (buffHasMoreCmds); + // REMOVE ME ---> Client *client = clients_.find(pfd.fd); if (client && !client->getSendBuffer().empty()) { const std::string &out = client->getSendBuffer(); sockets_.sendMsg(pfd.fd, out); client->consumeSendBuffer(out.size()); } + // <--- REMOVE ME } } removeClosedSockets_(); @@ -236,7 +244,7 @@ void Server::acceptClient_(int const serverFd) { std::ostringstream oss; oss << "Client connected:" << " fd:" << clientFd - << srvHelp::composeAddrfamAddrPortStr(sockAddrIn46) << std::endl; + << srvHelp::composeAddrfamAddrPortStr(sockAddrIn46); log_(oss.str(), Logger::lmInfo); } From 4042ab47251e510ba00e5ff35c62bd037872da2f Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 10 Sep 2026 19:39:54 +0200 Subject: [PATCH 272/349] feat: add more explicit error message about failed password extraction --- srcs/main.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/srcs/main.cpp b/srcs/main.cpp index 8d7e09b..a70943e 100644 --- a/srcs/main.cpp +++ b/srcs/main.cpp @@ -94,7 +94,10 @@ static bool checkExtractCmdLnArgs(int const argc, char *const argv[], int &port, std::istringstream issPass(argv[2]); // NOLINT if (!(issPass >> password).eof()) { - toCerr("Error while extracting password from commandline parameter.", + toCerr("Error while extracting password from commandline parameter '" + + issPass.str() + + "'\n Make sure it consist of alphanumeric characters " + "without spaces", CRed); return false; } From b284de23d7ac9c903c0d19bfc9568f391b26c8a9 Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 10 Sep 2026 19:44:02 +0200 Subject: [PATCH 273/349] rfa: clang-tidy --- includes/Logger.hpp | 2 +- includes/ServerHelper.hpp | 6 +++--- srcs/server/Server.cpp | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/includes/Logger.hpp b/includes/Logger.hpp index 060738e..a62eaad 100644 --- a/includes/Logger.hpp +++ b/includes/Logger.hpp @@ -10,7 +10,7 @@ class Logger { static std::string nowStr() { time_t now = time(0); struct tm *timeinfo = localtime(&now); - char buffer[32]; + char buffer[32]; // NOLINT strftime(&buffer[0], sizeof(buffer), "%Y-%m-%d %H:%M:%S", timeinfo); return std::string(&buffer[0]); } diff --git a/includes/ServerHelper.hpp b/includes/ServerHelper.hpp index 2740562..8661dbf 100644 --- a/includes/ServerHelper.hpp +++ b/includes/ServerHelper.hpp @@ -85,7 +85,7 @@ inline void getIPandPort(socketAddrX const &sockAddrIn46, portNumber = ntohs(sockAddrIn46.sai6.sin6_port); } - char addrBuffer[128]; + char addrBuffer[128]; // NOLINT socklen_t const addrBuffLen = sizeof(addrBuffer) / sizeof(char); char const *addrStrPtr = inet_ntop(saFamily, addrPtr, &addrBuffer[0], addrBuffLen); @@ -134,12 +134,12 @@ inline std::string composeAddrfamAddrPortStr(socketAddrX const &sockAddrIn46) { return oss.str(); } -class pfdHasFd { +class PfdHasFd { private: int const fd_; public: - pfdHasFd(int fd) : fd_(fd) {} + PfdHasFd(int fd) : fd_(fd) {} bool operator()(pollfd const pfd) const { return (pfd.fd == fd_); diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 0697f6e..1d259a3 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -236,7 +236,7 @@ void Server::acceptClient_(int const serverFd) { storePollFd_(clientFd, false); std::string clientIP; - uint16_t port; + uint16_t port = 0; srvHelp::getIPandPort(sockAddrIn46, srvHelp::getSaFamily(sockAddrIn46), port, clientIP); clients_.add(clientFd, clientIP); @@ -259,7 +259,7 @@ void Server::storePollFd_(int const socketFd, bool srvSocket) { void Server::removePollFd_(int const fd) { std::vector::iterator it = - std::find_if(pollFds_.begin(), pollFds_.end(), srvHelp::pfdHasFd(fd)); + std::find_if(pollFds_.begin(), pollFds_.end(), srvHelp::PfdHasFd(fd)); if (it != pollFds_.end()) pollFds_.erase(it); } From 2a689fed4b8ac48fd61d836dabff6e219c53b041 Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 10 Sep 2026 19:59:38 +0200 Subject: [PATCH 274/349] rfa: remove Server::sendMsg multiple definitions --- includes/Server.hpp | 1 - srcs/server/Server.cpp | 9 --------- 2 files changed, 10 deletions(-) diff --git a/includes/Server.hpp b/includes/Server.hpp index 8921ed9..db137bf 100644 --- a/includes/Server.hpp +++ b/includes/Server.hpp @@ -52,7 +52,6 @@ class Server { ~Server(); void run(); - void sendMsg(int const fd, std::string const &msg); const std::string &getServerName() const; const std::string &getServerVersion() const; diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 1d259a3..dfc65b9 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -316,12 +316,3 @@ void Server::run() { processClientSockets_(); } } - -void Server::sendMsg(int const fd, std::string const &msg) { - if (!sockets_.sendMsg(fd, msg)) { - // will be done at the end of a poll loop - - // clients_.remove(fd, channels_); - // removePollFd_(fd); - } -} \ No newline at end of file From 05f4bad2160cb5fb1ef201f26b641ecf326f1893 Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 10 Sep 2026 20:08:36 +0200 Subject: [PATCH 275/349] rfa: beautify here and there, remove comments and unused deprecated code --- includes/Client.hpp | 4 ++-- includes/ServerHelper.hpp | 22 +++++----------------- includes/Socket.hpp | 2 -- srcs/server/Server.cpp | 29 ++--------------------------- srcs/server/SignalHandler.cpp | 4 ++-- srcs/server/Socket.cpp | 8 -------- 6 files changed, 11 insertions(+), 58 deletions(-) diff --git a/includes/Client.hpp b/includes/Client.hpp index 9dd0df0..f0062ac 100644 --- a/includes/Client.hpp +++ b/includes/Client.hpp @@ -29,8 +29,8 @@ class Client { Client() : fd_(-1), registered_(false), passAccepted_(false), oper_(false) {} - explicit Client(int fd, std::string const &serverName) - : fd_(fd), hostname_(serverName), registered_(false), + explicit Client(int fd, std::string const &clientHost) + : fd_(fd), hostname_(clientHost), registered_(false), passAccepted_(false), oper_(false) {} int getFd() const; diff --git a/includes/ServerHelper.hpp b/includes/ServerHelper.hpp index 8661dbf..ab286f8 100644 --- a/includes/ServerHelper.hpp +++ b/includes/ServerHelper.hpp @@ -108,25 +108,9 @@ inline std::string composeAddrfamAddrPortStr(socketAddrX const &sockAddrIn46) { sa_family_t const saFamily = getSaFamily(sockAddrIn46); uint16_t portNumber = 0; - std::string addrStr("No addr:port information."); + std::string addrStr; getIPandPort(sockAddrIn46, saFamily, portNumber, addrStr); - // if (saFamily == AF_INET) { - // addrPtr = &sockAddrIn46.sai4.sin_addr; - // portNumber = ntohs(sockAddrIn46.sai4.sin_port); - // } else if (saFamily == AF_INET6) { - // addrPtr = &sockAddrIn46.sai6.sin6_addr; - // portNumber = ntohs(sockAddrIn46.sai6.sin6_port); - // } - // char const *addrStrPtr = - // inet_ntop(saFamily, addrPtr, &addrBuffer[0], addrBuffLen); - - // std::string addrStr("No addr:port information."); - // if (addrStrPtr) { - // addrStr.assign(addrStrPtr); - // if (saFamily == AF_INET6) - // addrStr = "[" + addrStr + "]"; - // } std::ostringstream oss; oss << " AF:" << getAdressFamilyStr(saFamily) << " addr:port: " << addrStr << ":" << std::dec << portNumber; @@ -134,6 +118,10 @@ inline std::string composeAddrfamAddrPortStr(socketAddrX const &sockAddrIn46) { return oss.str(); } +/* + * Functor that can be used in std::find_if + * workaround since we are stuck with c++98 without lambdas + */ class PfdHasFd { private: int const fd_; diff --git a/includes/Socket.hpp b/includes/Socket.hpp index 181fe03..2045344 100644 --- a/includes/Socket.hpp +++ b/includes/Socket.hpp @@ -12,8 +12,6 @@ #define MAXMSGLEN 512 #define SOCKETERRORLIMIT 3 -#define NETCATDBG 0 // For netcat debug - class Socket { private: struct SocketIOData { diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index dfc65b9..c5f6940 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -33,28 +33,6 @@ Server::~Server() { while (!pollFds_.empty()) { int const fd = pollFds_.back().fd; if (!sockets_.isServerSocket(fd)) { - // Update after Call client member function for shutdown notice message! - // after PR feat/cmds2: implement QUIT, PING, PONG and NOTICE - #38# -#define REMOVE_ME -#ifndef REMOVE_ME - std::string nickName; - try { - nickName = clients_.at(fd).getNick(); - } catch (std::exception const &e) { - logger_.logMsg("\n Cannot get client object:" - "\n starting from a second client, the object " - "isn't correctly created" - "\n in function bool ClientManager::add(Client " - "&client, int fd)" - "\n Exception: " + - std::string(e.what()), - Logger::lmError); - } - std::ostringstream oss; - oss << "ERROR :" << serverName_ << " " << nickName - << " :Server shutdown - goodbye."; - sockets_.sendMsg(fd, oss.str()); -#else Client &client = clients_.at(fd); client.sendClosingMsg("Server shutdown - goodbye"); @@ -63,7 +41,7 @@ Server::~Server() { sockets_.sendMsg(fd, out); client.consumeSendBuffer(out.size()); // <--- REMOVE ME -#endif + clients_.remove(fd, channels_); } sockets_.closeSocket(fd); @@ -163,10 +141,7 @@ void Server::processClientSockets_() { sockets_.getCRLFLineFromBuffer(pfd.fd, buffHasMoreCmds); if (!cmdLine.empty()) { // returns true if connection is to be closed - // only happens after a QUIT cmd or maybe after too many ERR_INPUTTOOLONG - if (cmds_.run(*this, - /* should soon be: pfd.fd */ - clients_.at(pfd.fd), cmdLine)) { + if (cmds_.run(*this, clients_.at(pfd.fd), cmdLine)) { sockets_.closeSocket(pfd.fd); break; } diff --git a/srcs/server/SignalHandler.cpp b/srcs/server/SignalHandler.cpp index 852bd52..e273705 100644 --- a/srcs/server/SignalHandler.cpp +++ b/srcs/server/SignalHandler.cpp @@ -56,8 +56,8 @@ SignalHandler::SignalHandler() { } /* - SignalHandler Destructor - * reset signal handling to default + * SignalHandler Destructor + * reset signal handling to default */ SignalHandler::~SignalHandler() { setStopSignals_(SIG_DFL); diff --git a/srcs/server/Socket.cpp b/srcs/server/Socket.cpp index 1295616..f263fb9 100644 --- a/srcs/server/Socket.cpp +++ b/srcs/server/Socket.cpp @@ -64,14 +64,6 @@ std::string Socket::getCRLFLineFromBuffer(int const fd, bool &buffHasMoreCmds) { return ""; Socket::SocketIOData &sIOD = sockIODataStore_.at(fd); -#if NETCATDBG - { - std::ostringstream oss; - oss << "You are client fd " << fd << ": \n" - << "Srv received: " << std::string(sIOD.readBuffer) << std::endl; - sendMsg(fd, oss.str()); - } -#endif std::string cmdLine; cmdLine = sIOD.readBuffer.substr(0, lfcrPos); sIOD.recvErrorCount = 0; From a7b4acb1b75406be75a3a63b9adeb84e8c48445d Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 10 Sep 2026 20:13:18 +0200 Subject: [PATCH 276/349] rfa: cleanup after merge --- includes/Server.hpp | 1 - srcs/server/Server.cpp | 9 --------- srcs/server/Socket.cpp | 4 ---- 3 files changed, 14 deletions(-) diff --git a/includes/Server.hpp b/includes/Server.hpp index 8921ed9..db137bf 100644 --- a/includes/Server.hpp +++ b/includes/Server.hpp @@ -52,7 +52,6 @@ class Server { ~Server(); void run(); - void sendMsg(int const fd, std::string const &msg); const std::string &getServerName() const; const std::string &getServerVersion() const; diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index fd16a0b..c5f6940 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -291,12 +291,3 @@ void Server::run() { processClientSockets_(); } } - -void Server::sendMsg(int const fd, std::string const &msg) { - if (!sockets_.sendMsg(fd, msg)) { - // will be done at the end of a poll loop - - // clients_.remove(fd, channels_); - // removePollFd_(fd); - } -} \ No newline at end of file diff --git a/srcs/server/Socket.cpp b/srcs/server/Socket.cpp index 51e2f4c..f263fb9 100644 --- a/srcs/server/Socket.cpp +++ b/srcs/server/Socket.cpp @@ -13,10 +13,6 @@ void Socket::addSocket(int const fd, bool srvSocket) { sockIODataStore_.insert(std::make_pair(fd, SocketIOData(srvSocket))); } -bool Socket::checkSocket4Recv(struct pollfd &pfd) { - if (socketClosed(pfd.fd)) - return false; - bool Socket::checkSocket4Recv(struct pollfd &pfd) { if (socketClosed(pfd.fd)) return false; From 01274ec3f5a545026ff7218a362d21fa530478ab Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 10 Sep 2026 20:31:58 +0200 Subject: [PATCH 277/349] rfa: iwyu --- includes/Server.hpp | 1 - includes/Socket.hpp | 2 +- srcs/main.cpp | 15 +++++++-------- srcs/server/Server.cpp | 8 ++++---- srcs/server/Socket.cpp | 4 +--- 5 files changed, 13 insertions(+), 17 deletions(-) diff --git a/includes/Server.hpp b/includes/Server.hpp index db137bf..4f49f79 100644 --- a/includes/Server.hpp +++ b/includes/Server.hpp @@ -10,7 +10,6 @@ #include // for size_t #include // for string, basic_string #include // for vector -class Client; #define SERVER_NAME "irc.local" #define SERVER_VERSION "1.0" diff --git a/includes/Socket.hpp b/includes/Socket.hpp index 2045344..1c46b4d 100644 --- a/includes/Socket.hpp +++ b/includes/Socket.hpp @@ -2,7 +2,7 @@ #include "Logger.hpp" // for Logger -#include // for map, operator== +#include // for map, operator!=, operator== #include // for size_t #include // for string, basic_string #include // for ssize_t diff --git a/srcs/main.cpp b/srcs/main.cpp index a70943e..29dd240 100644 --- a/srcs/main.cpp +++ b/srcs/main.cpp @@ -1,12 +1,11 @@ -#include "Server.hpp" -#include "SignalHandler.hpp" -#include "colours.hpp" // for CReset, CCyan, CGreen, CYellow +#include "Server.hpp" // for Server +#include "SignalHandler.hpp" // for SignalHandler +#include "colours.hpp" // for CRed, CReset, CCyan, CGreen, CYellow -#include -#include // for cerr, cout -#include -#include // for runtime_error -#include +#include // for exception +#include // for basic_ostream, operator<<, basic_istream +#include // for basic_istringstream +#include // for char_traits, allocator, basic_string static bool checkExtractCmdLnArgs(int const argc, char *const argv[], int &port, std::string &password); diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index c5f6940..bcbe31a 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -6,13 +6,13 @@ #include "ServerHelper.hpp" // for getAdressFamilyStr, composeAddrfamAddrP... #include "SignalHandler.hpp" // for SignalHandler +#include // for find_if #include // for exception -#include // for basic_ostream, operator<<, basic_ostrea... #include // for IPPROTO_IPV6, IPV6_V6ONLY -#include -#include // for basic_ostringstream +#include // for basic_ostringstream, basic_ostream, ope... #include // for runtime_error, invalid_argument -#include // for allocator, char_traits, basic_string +#include // for uint16_t +#include // for allocator, basic_string, char_traits #include // for setsockopt, AF_INET6, AF_INET, accept /* diff --git a/srcs/server/Socket.cpp b/srcs/server/Socket.cpp index f263fb9..c34b97a 100644 --- a/srcs/server/Socket.cpp +++ b/srcs/server/Socket.cpp @@ -1,7 +1,5 @@ #include "Socket.hpp" -#include "Errors.hpp" // for Error -#include "Reply.hpp" // for reply, Code #include "ServerHelper.hpp" // for getAdressFamilyStr, sockAddrFamily #include // for pollfd, POLLERR @@ -64,7 +62,7 @@ std::string Socket::getCRLFLineFromBuffer(int const fd, bool &buffHasMoreCmds) { return ""; Socket::SocketIOData &sIOD = sockIODataStore_.at(fd); - std::string cmdLine; + std::string cmdLine; cmdLine = sIOD.readBuffer.substr(0, lfcrPos); sIOD.recvErrorCount = 0; sIOD.readBuffer = sIOD.readBuffer.substr(lfcrPos + 2); From 6948169654791d0bc2a7d102fd90547405a05cd7 Mon Sep 17 00:00:00 2001 From: ArchCipher Date: Fri, 11 Sep 2026 13:08:14 +0200 Subject: [PATCH 278/349] fix: sendmsg --- includes/Server.hpp | 5 ++- srcs/server/Socket.cpp | 75 ++++++++++++++++++++---------------------- 2 files changed, 39 insertions(+), 41 deletions(-) diff --git a/includes/Server.hpp b/includes/Server.hpp index 4f49f79..48cbac0 100644 --- a/includes/Server.hpp +++ b/includes/Server.hpp @@ -61,6 +61,9 @@ class Server { // Pass-through interfaces void sendMsg(int const fd, std::string const &msg) { - sockets_.sendMsg(fd, msg); + if (sockets_.socketClosed(fd)) + return; + if (!sockets_.sendMsg(fd, msg)) + sockets_.closeSocket(fd); } }; diff --git a/srcs/server/Socket.cpp b/srcs/server/Socket.cpp index c34b97a..388afdb 100644 --- a/srcs/server/Socket.cpp +++ b/srcs/server/Socket.cpp @@ -75,28 +75,29 @@ size_t Socket::bufferLengthAndSanityCheck_(int const fd, bool &msgTooLong) { std::string &rdBuffer = sIOD.readBuffer; size_t lfcrPos = rdBuffer.find("\r\n"); - if ((!sIOD.discardMode) && (rdBuffer.length() >= MAXMSGLEN) && - (lfcrPos == std::string::npos || lfcrPos > MAXMSGLEN - 2)) { - sIOD.discardMode = true; - msgTooLong = true; + if (!sIOD.discardMode) { + if ((rdBuffer.length() < MAXMSGLEN) || + (lfcrPos != std::string::npos && lfcrPos <= MAXMSGLEN - 2)) + return lfcrPos; + sIOD.discardMode = true; + msgTooLong = true; } - if (sIOD.discardMode) { - if (++sIOD.recvErrorCount >= SOCKETERRORLIMIT) { - closeSocket(fd); - return std::string::npos; - } - if (lfcrPos != std::string::npos) { - rdBuffer.erase(0, lfcrPos + 2); - sIOD.discardMode = false; - sIOD.recvErrorCount = 0; - lfcrPos = rdBuffer.find("\r\n"); - } else /* lfcrPos == std::string::npos */ { - if ('\r' == *rdBuffer.rbegin()) - rdBuffer = "\r"; - else - rdBuffer.clear(); - } + // discard mode + if (++sIOD.recvErrorCount >= SOCKETERRORLIMIT) { + closeSocket(fd); + return std::string::npos; } + if (lfcrPos != std::string::npos) { + rdBuffer.erase(0, lfcrPos + 2); + sIOD.discardMode = false; + sIOD.recvErrorCount = 0; + return rdBuffer.find("\r\n"); + } + // lfcrPos == std::string::npos + if (!rdBuffer.empty() && '\r' == *rdBuffer.rbegin()) + rdBuffer = "\r"; + else + rdBuffer.clear(); return lfcrPos; } @@ -105,29 +106,25 @@ size_t Socket::bufferLengthAndSanityCheck_(int const fd, bool &msgTooLong) { * send string msg to a network socket * uses i/o buffers and error counters in SocketIOData * closes the socket (== disconnects the client) in case of too many errors + + NOTE: MUST be called ony through Socket::sendMsg */ bool Socket::sendMsg(int const fd, std::string const &msg) { - if (socketClosed(fd)) - return false; - - SocketIOData &sIOD = sockIODataStore_.at(fd); - ssize_t bytesSent = -1; - bool success = true; + SocketIOData &sIOD = sockIODataStore_.at(fd); if (msg.length() > sIOD.writeBuffer.max_size() - sIOD.writeBuffer.length()) - sIOD.sendErrorCount = SOCKETERRORLIMIT; - else { - sIOD.writeBuffer += msg; - bytesSent = send(fd, sIOD.writeBuffer.c_str(), - sIOD.writeBuffer.length(), MSG_NOSIGNAL); - if (bytesSent <= 0) - ++sIOD.sendErrorCount; - } - if (sIOD.sendErrorCount >= SOCKETERRORLIMIT) { - closeSocket(fd); - success = false; + return false; + sIOD.writeBuffer += msg; + ssize_t bytesSent = send(fd, sIOD.writeBuffer.c_str(), + sIOD.writeBuffer.length(), MSG_NOSIGNAL); + if (bytesSent == -1) { + ++sIOD.sendErrorCount; + if (sIOD.sendErrorCount >= SOCKETERRORLIMIT) + return false; + return true; } +#if NETCATDBG { std::ostringstream oss; oss << "SendMsg event:\n" @@ -142,9 +139,7 @@ bool Socket::sendMsg(int const fd, std::string const &msg) { logger_.logMsg(oss.str(), Logger::lmInfo); } } - - if (!success) - return false; +#endif sIOD.sendErrorCount = 0; if (static_cast(bytesSent) < sIOD.writeBuffer.length()) From 566f1bc2f82697967b41eca339e4110faadff4ff Mon Sep 17 00:00:00 2001 From: ArchCipher Date: Fri, 11 Sep 2026 13:47:37 +0200 Subject: [PATCH 279/349] fix: use Server::sendMsg() --- includes/Client.hpp | 36 ++++++++--------- includes/ClientManager.hpp | 2 +- srcs/server/Client.cpp | 76 +++++++++++------------------------ srcs/server/ClientManager.cpp | 4 +- srcs/server/Server.cpp | 18 +-------- srcs/server/Socket.cpp | 19 ++++++--- 6 files changed, 56 insertions(+), 99 deletions(-) diff --git a/includes/Client.hpp b/includes/Client.hpp index f0062ac..4db8fc6 100644 --- a/includes/Client.hpp +++ b/includes/Client.hpp @@ -11,7 +11,10 @@ #include #include +class Server; + class Client { + Server *server_; int fd_; std::string nick_; std::string nickCaseFolded_; @@ -19,18 +22,17 @@ class Client { std::string hostname_; std::string realname_; bool registered_; - std::string recvBuffer_; - std::string sendBuffer_; bool passAccepted_; bool oper_; std::set invitedTo_; public: Client() - : fd_(-1), registered_(false), passAccepted_(false), oper_(false) {} + : server_(0), fd_(-1), registered_(false), passAccepted_(false), + oper_(false) {} - explicit Client(int fd, std::string const &clientHost) - : fd_(fd), hostname_(clientHost), registered_(false), + explicit Client(int fd, std::string const &hostname, Server *server) + : server_(server), fd_(fd), hostname_(hostname), registered_(false), passAccepted_(false), oper_(false) {} int getFd() const; @@ -47,13 +49,11 @@ class Client { std::string getPrefix() const; // Nick the numerics are addressed to: "*" until the client has one. std::string getReplyTarget() const; - // "ERROR :Closing link: (nick!user@host) [reason]", the line a client gets - // right before its connection is closed (QUIT, wrong PASS, shutdown). - void sendClosingMsg(const std::string &reason); - bool hasIdentity() const; - void setNick(const std::string &name); - void setUser(const std::string &user, const std::string &real); - void setHostname(const std::string &host); + bool hasIdentity() const; + void setNick(const std::string &name); + void setUser(const std::string &user, const std::string &real); + void setHostname(const std::string &host); + void setServer(Server *server); void setRegistered(bool value); void setPassAccepted(bool value); @@ -63,14 +63,10 @@ class Client { void addInvite(const std::string &name); void removeInvite(const std::string &name); + // all send routed to `Server::sendMsg()` void send(const std::string &msg); void sendReply(const std::string &serverName, const Reply::Message &msg); - void appendToRecvBuffer(const std::string &bytes); - bool extractLine(std::string &line); - std::size_t recvBufferSize() const; - const std::string &getSendBuffer() const; - void consumeSendBuffer(std::size_t count); - void clearSendBuffer(); - - bool hasPendingOutput() const; + // "ERROR :Closing link: (nick!user@host) [reason]", the line a client gets + // right before its connection is closed (QUIT, wrong PASS, shutdown). + void sendClosingMsg(const std::string &reason); }; diff --git a/includes/ClientManager.hpp b/includes/ClientManager.hpp index dbb0d44..9eec4f4 100644 --- a/includes/ClientManager.hpp +++ b/includes/ClientManager.hpp @@ -51,7 +51,7 @@ class ClientManager { // add takes a file descriptor of a newly created socket // returns false if `fd` is already present. - bool add(int fd, std::string const &serverName); + bool add(int fd, std::string const &hostname, Server &server); // setNick changes `client`'s nick and keeps the nick index in sync. // The caller validates the nick and checks hasNick() first. void setNick(Client &client, const std::string &nick); diff --git a/srcs/server/Client.cpp b/srcs/server/Client.cpp index d384af2..b205433 100644 --- a/srcs/server/Client.cpp +++ b/srcs/server/Client.cpp @@ -1,5 +1,6 @@ #include "Client.hpp" +#include "Server.hpp" #include "Utils.hpp" int Client::getFd() const { @@ -38,13 +39,6 @@ bool Client::hasPassAccepted() const { return passAccepted_; } -// "nick!user@host", or "*" until NICK and USER are both known. The host is -// set by the server from the accepted socket address; until then (or if it -// never is) "localhost" keeps the prefix well-formed for clients. -void Client::sendClosingMsg(const std::string &reason) { - send("ERROR :Closing link: (" + getPrefix() + ") [" + reason + "]\r\n"); -} - std::string Client::getPrefix() const { if (!hasIdentity()) return "*"; @@ -52,6 +46,12 @@ std::string Client::getPrefix() const { (hostname_.empty() ? std::string("localhost") : hostname_); } +// 431/432/433/464 all arrive before registration completes, so the nick is +// used as soon as there is one. +std::string Client::getReplyTarget() const { + return nick_.empty() ? "*" : nick_; +} + bool Client::hasIdentity() const { return !nick_.empty() && !username_.empty(); } @@ -70,6 +70,10 @@ void Client::setHostname(const std::string &host) { hostname_ = host; } +void Client::setServer(Server *server) { + server_ = server; +} + void Client::setRegistered(bool value) { registered_ = value; } @@ -94,57 +98,23 @@ void Client::removeInvite(const std::string &name) { invitedTo_.erase(Utils::foldCase(name)); } +// ---- Send message ----------------------------------------------------------- + void Client::send(const std::string &msg) { - sendBuffer_ += msg; + if (server_) + server_->sendMsg(fd_, msg); } void Client::sendReply(const std::string &serverName, const Reply::Message &msg) { - sendBuffer_ += Reply::build(serverName, getReplyTarget(), msg); + const std::string &text = Reply::build(serverName, getReplyTarget(), msg); + if (server_) + server_->sendMsg(fd_, text); } -// 431/432/433/464 all arrive before registration completes, so the nick is -// used as soon as there is one. -std::string Client::getReplyTarget() const { - return nick_.empty() ? "*" : nick_; -} - -void Client::appendToRecvBuffer(const std::string &bytes) { - recvBuffer_ += bytes; -} - -bool Client::extractLine(std::string &line) { - const std::string::size_type pos = recvBuffer_.find('\n'); - if (pos == std::string::npos) - return false; - - line = recvBuffer_.substr(0, pos); - if (!line.empty() && line[line.size() - 1] == '\r') - line.erase(line.size() - 1); - - recvBuffer_.erase(0, pos + 1); - return true; -} - -std::size_t Client::recvBufferSize() const { - return recvBuffer_.size(); -} - -const std::string &Client::getSendBuffer() const { - return sendBuffer_; -} - -void Client::consumeSendBuffer(std::size_t count) { - if (count >= sendBuffer_.size()) - sendBuffer_.clear(); - else - sendBuffer_.erase(0, count); -} - -void Client::clearSendBuffer() { - sendBuffer_.clear(); -} - -bool Client::hasPendingOutput() const { - return !sendBuffer_.empty(); +// "nick!user@host", or "*" until NICK and USER are both known. The host is +// set by the server from the accepted socket address; until then (or if it +// never is) "localhost" keeps the prefix well-formed for clients. +void Client::sendClosingMsg(const std::string &reason) { + send("ERROR :Closing link: (" + getPrefix() + ") [" + reason + "]\r\n"); } diff --git a/srcs/server/ClientManager.cpp b/srcs/server/ClientManager.cpp index 2cf0fa2..3e039c4 100644 --- a/srcs/server/ClientManager.cpp +++ b/srcs/server/ClientManager.cpp @@ -46,10 +46,10 @@ std::size_t ClientManager::size() const { return byFd_.size(); } -bool ClientManager::add(int fd, std::string const &serverName) { +bool ClientManager::add(int fd, std::string const &hostname, Server &server) { if (hasClient(fd)) return false; - Client *client = new Client(fd, serverName); + Client *client = new Client(fd, hostname, &server); byFd_.insert(std::make_pair(fd, client)); if (!client->getNick().empty()) byNick_.insert(std::make_pair(client->getNickCaseFolded(), fd)); diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index bcbe31a..f6ebf73 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -35,13 +35,6 @@ Server::~Server() { if (!sockets_.isServerSocket(fd)) { Client &client = clients_.at(fd); client.sendClosingMsg("Server shutdown - goodbye"); - - // REMOVE ME ---> - const std::string &out = client.getSendBuffer(); - sockets_.sendMsg(fd, out); - client.consumeSendBuffer(out.size()); - // <--- REMOVE ME - clients_.remove(fd, channels_); } sockets_.closeSocket(fd); @@ -164,15 +157,6 @@ void Server::processClientSockets_() { } #endif } while (buffHasMoreCmds); - - // REMOVE ME ---> - Client *client = clients_.find(pfd.fd); - if (client && !client->getSendBuffer().empty()) { - const std::string &out = client->getSendBuffer(); - sockets_.sendMsg(pfd.fd, out); - client->consumeSendBuffer(out.size()); - } - // <--- REMOVE ME } } removeClosedSockets_(); @@ -214,7 +198,7 @@ void Server::acceptClient_(int const serverFd) { uint16_t port = 0; srvHelp::getIPandPort(sockAddrIn46, srvHelp::getSaFamily(sockAddrIn46), port, clientIP); - clients_.add(clientFd, clientIP); + clients_.add(clientFd, clientIP, *this); std::ostringstream oss; oss << "Client connected:" diff --git a/srcs/server/Socket.cpp b/srcs/server/Socket.cpp index 388afdb..d650938 100644 --- a/srcs/server/Socket.cpp +++ b/srcs/server/Socket.cpp @@ -78,11 +78,11 @@ size_t Socket::bufferLengthAndSanityCheck_(int const fd, bool &msgTooLong) { if (!sIOD.discardMode) { if ((rdBuffer.length() < MAXMSGLEN) || (lfcrPos != std::string::npos && lfcrPos <= MAXMSGLEN - 2)) - return lfcrPos; - sIOD.discardMode = true; - msgTooLong = true; + return lfcrPos; + sIOD.discardMode = true; + msgTooLong = true; } - // discard mode + // discard mode if (++sIOD.recvErrorCount >= SOCKETERRORLIMIT) { closeSocket(fd); return std::string::npos; @@ -110,7 +110,7 @@ size_t Socket::bufferLengthAndSanityCheck_(int const fd, bool &msgTooLong) { NOTE: MUST be called ony through Socket::sendMsg */ bool Socket::sendMsg(int const fd, std::string const &msg) { - SocketIOData &sIOD = sockIODataStore_.at(fd); + SocketIOData &sIOD = sockIODataStore_.at(fd); if (msg.length() > sIOD.writeBuffer.max_size() - sIOD.writeBuffer.length()) return false; @@ -149,8 +149,15 @@ bool Socket::sendMsg(int const fd, std::string const &msg) { return true; } +/* +Performance note: 2 searches of sockIODataStore. +Wrap in try catch to remove this redundancy +or make socketClosed return a pointer instead +*/ void Socket::closeSocket(int const fd) { - SocketIOData &sIOD = sockIODataStore_.at(fd); + if (socketClosed(fd)) + return; + SocketIOData &sIOD = sockIODataStore_.at(fd); // throws std::ostringstream oss; if (sIOD.isSrvSocket) { From c8f0d875a1e802c65f9e4508215a95e29a7afa89 Mon Sep 17 00:00:00 2001 From: ArchCipher Date: Fri, 11 Sep 2026 14:26:04 +0200 Subject: [PATCH 280/349] chore: align netcat debug line to changes --- includes/Server.hpp | 8 +------- srcs/server/Server.cpp | 19 ++++++++++++++++--- srcs/server/Socket.cpp | 28 +++++++++++----------------- 3 files changed, 28 insertions(+), 27 deletions(-) diff --git a/includes/Server.hpp b/includes/Server.hpp index 48cbac0..ce66f81 100644 --- a/includes/Server.hpp +++ b/includes/Server.hpp @@ -59,11 +59,5 @@ class Server { ChannelManager &getChannelManager(); ClientManager &getClientManager(); - // Pass-through interfaces - void sendMsg(int const fd, std::string const &msg) { - if (sockets_.socketClosed(fd)) - return; - if (!sockets_.sendMsg(fd, msg)) - sockets_.closeSocket(fd); - } + void sendMsg(int const fd, std::string const &msg); }; diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index f6ebf73..4a74559 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -145,9 +145,8 @@ void Server::processClientSockets_() { Client *client = clients_.find(pfd.fd); std::string nickName = (client ? client->getNick() : "dbgDummyNick"); - sockets_.sendMsg( - pfd.fd, - Reply::build(serverName_, nickName, e.reply())); + sendMsg(pfd.fd, + Reply::build(serverName_, nickName, e.reply())); } else { std::string tmp( "Error while trying to get/execute cmd:\n"); @@ -275,3 +274,17 @@ void Server::run() { processClientSockets_(); } } + +/* +NOTE: all send should go though server::sendMsg +*/ +void Server::sendMsg(int const fd, std::string const &msg) { + if (!sockets_.sendMsg(fd, msg)) { + sockets_.closeSocket(fd); +#if NETCATDBG + std::ostringstream oss; + oss << "SendMsg event:\nSendto FD " << fd << " failed."; + logger_.logMsg(oss.str(), Logger::lmError); +#endif + } +} diff --git a/srcs/server/Socket.cpp b/srcs/server/Socket.cpp index d650938..143e243 100644 --- a/srcs/server/Socket.cpp +++ b/srcs/server/Socket.cpp @@ -105,11 +105,13 @@ size_t Socket::bufferLengthAndSanityCheck_(int const fd, bool &msgTooLong) { sendMsg * send string msg to a network socket * uses i/o buffers and error counters in SocketIOData - * closes the socket (== disconnects the client) in case of too many errors + * returns false in case of too many errors - caller must close socket - NOTE: MUST be called ony through Socket::sendMsg + NOTE: caller MUST close socket when sendMsg returns false */ bool Socket::sendMsg(int const fd, std::string const &msg) { + if (socketClosed(fd)) + return true; SocketIOData &sIOD = sockIODataStore_.at(fd); if (msg.length() > sIOD.writeBuffer.max_size() - sIOD.writeBuffer.length()) @@ -124,27 +126,19 @@ bool Socket::sendMsg(int const fd, std::string const &msg) { return true; } + sIOD.sendErrorCount = 0; + if (static_cast(bytesSent) < sIOD.writeBuffer.length()) { #if NETCATDBG - { std::ostringstream oss; oss << "SendMsg event:\n" << " WriteBuffer: '" << sIOD.writeBuffer << "':\n"; - if (!success) { - oss << " send to FD " << fd << " failed."; - logger_.logMsg(oss.str(), Logger::lmError); - } else if (static_cast(bytesSent) < sIOD.writeBuffer.length()) { - oss << " partly (" << bytesSent << "/" << sIOD.writeBuffer.length() - << ") " << sIOD.writeBuffer.substr(0, bytesSent) - << " sent to FD: " << fd; - logger_.logMsg(oss.str(), Logger::lmInfo); - } - } + oss << " partly (" << bytesSent << "/" << sIOD.writeBuffer.length() + << ") " << sIOD.writeBuffer.substr(0, bytesSent) + << " sent to FD: " << fd; + logger_.logMsg(oss.str(), Logger::lmInfo); #endif - - sIOD.sendErrorCount = 0; - if (static_cast(bytesSent) < sIOD.writeBuffer.length()) sIOD.writeBuffer.erase(0, bytesSent); - else + } else sIOD.writeBuffer.clear(); return true; } From da226f6f0a0aed3c582970871cff4b3b822c2483 Mon Sep 17 00:00:00 2001 From: ArchCipher Date: Fri, 11 Sep 2026 14:46:55 +0200 Subject: [PATCH 281/349] chore: format --- srcs/server/Socket.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/srcs/server/Socket.cpp b/srcs/server/Socket.cpp index 143e243..198558c 100644 --- a/srcs/server/Socket.cpp +++ b/srcs/server/Socket.cpp @@ -110,7 +110,7 @@ size_t Socket::bufferLengthAndSanityCheck_(int const fd, bool &msgTooLong) { NOTE: caller MUST close socket when sendMsg returns false */ bool Socket::sendMsg(int const fd, std::string const &msg) { - if (socketClosed(fd)) + if (socketClosed(fd)) return true; SocketIOData &sIOD = sockIODataStore_.at(fd); From c47653f0d301458d04d29d8f0b1da8758e76f8ab Mon Sep 17 00:00:00 2001 From: ArchCipher Date: Fri, 11 Sep 2026 15:13:25 +0200 Subject: [PATCH 282/349] fix: remove servername from Client::sendReply --- includes/Channel.hpp | 14 ++++---- includes/Client.hpp | 2 +- srcs/commands/Handlers.cpp | 49 +++++++++++---------------- srcs/commands/Messaging.cpp | 9 ++--- srcs/commands/Registration.cpp | 26 +++++++-------- srcs/server/Channel.cpp | 60 ++++++++++++++-------------------- srcs/server/Client.cpp | 11 ++++--- 7 files changed, 71 insertions(+), 100 deletions(-) diff --git a/includes/Channel.hpp b/includes/Channel.hpp index a706763..61c1b7b 100644 --- a/includes/Channel.hpp +++ b/includes/Channel.hpp @@ -39,12 +39,10 @@ class Channel { // Modes (MODE command) bool applyModeToMember_(Server &server, Client &client, const Mode::ModeChange &change); - void applyKeyMode_(const std::string &serverName, Client &client, - const Mode::ModeChange &change, - Mode::ModeChanges &applied); - void applyUserLimitMode_(const std::string &serverName, Client &client, - const Mode::ModeChange &change, - Mode::ModeChanges &applied); + void applyKeyMode_(Client &client, const Mode::ModeChange &change, + Mode::ModeChanges &applied); + void applyUserLimitMode_(Client &client, const Mode::ModeChange &change, + Mode::ModeChanges &applied); static bool validateChannelKey_(std::string key); public: @@ -77,10 +75,10 @@ class Channel { // Topic (TOPIC command) void setTopic(Client &client, const std::string &topic); - void sendTopic(const std::string &serverName, Client &client) const; + void sendTopic(Client &client) const; // Names / Invite (NAMES, INVITE) - void sendNames(const std::string &serverName, Client &client) const; + void sendNames(Client &client) const; void checkInvite(Client &client, Client &target) const; // Privilege checks diff --git a/includes/Client.hpp b/includes/Client.hpp index 4db8fc6..0c14da1 100644 --- a/includes/Client.hpp +++ b/includes/Client.hpp @@ -65,7 +65,7 @@ class Client { // all send routed to `Server::sendMsg()` void send(const std::string &msg); - void sendReply(const std::string &serverName, const Reply::Message &msg); + void sendReply(const Reply::Message &msg); // "ERROR :Closing link: (nick!user@host) [reason]", the line a client gets // right before its connection is closed (QUIT, wrong PASS, shutdown). void sendClosingMsg(const std::string &reason); diff --git a/srcs/commands/Handlers.cpp b/srcs/commands/Handlers.cpp index a8f27cc..b7ab948 100644 --- a/srcs/commands/Handlers.cpp +++ b/srcs/commands/Handlers.cpp @@ -33,14 +33,13 @@ bool Handlers::invite(Server &server, Client &client, chan.checkInvite(client, target); } target.addInvite(params[1]); - client.sendReply(server.getServerName(), - Reply::reply(Reply::RPL_INVITING, + client.sendReply(Reply::reply(Reply::RPL_INVITING, params[1] + " " + target.getNick())); target.send(Reply::fromClient(client.getPrefix(), "INVITE", target.getNick() + " " + params[1])); } catch (const Error &e) { if (e.hasReply()) - client.sendReply(server.getServerName(), e.reply()); + client.sendReply(e.reply()); } return false; } @@ -75,7 +74,6 @@ bool Handlers::join(Server &server, Client &client, std::vector keys = params.size() > 1 ? Utils::split(params[1], ',') : std::vector(); - const std::string &serverName = server.getServerName(); for (size_t i = 0; i < channels.size(); i++) { if (channels[i].empty()) continue; @@ -85,11 +83,11 @@ bool Handlers::join(Server &server, Client &client, Channel &ch = cm.at(channels[i]); ch.broadcast( Reply::fromClient(client.getPrefix(), "JOIN", channels[i])); - ch.sendTopic(serverName, client); - ch.sendNames(serverName, client); + ch.sendTopic(client); + ch.sendNames(client); } catch (const Error &e) { if (e.hasReply()) - client.sendReply(serverName, e.reply()); + client.sendReply(e.reply()); } } return false; @@ -110,8 +108,7 @@ bool Handlers::kick(Server &server, Client &client, std::vector channels = Utils::split(params[0], ','); std::vector users = Utils::split(params[1], ','); if (channels.size() != users.size() && channels.size() != 1) { - client.sendReply(server.getServerName(), - Reply::reply(Reply::ERR_NEEDMOREPARAMS, "KICK")); + client.sendReply(Reply::reply(Reply::ERR_NEEDMOREPARAMS, "KICK")); return false; } ChannelManager &cm = server.getChannelManager(); @@ -131,7 +128,7 @@ bool Handlers::kick(Server &server, Client &client, cm.kick(client, chan, user, comment); } catch (const Error &e) { if (e.hasReply()) - client.sendReply(server.getServerName(), e.reply()); + client.sendReply(e.reply()); } } return false; @@ -164,13 +161,11 @@ client (userhost) modes. */ bool Handlers::mode(Server &server, Client &client, const std::vector ¶ms) { - const std::string &serverName = server.getServerName(); try { Channel &chan = server.getChannelManager().at(params[0]); if (params.size() < 2) { const std::string modes = chan.modeString(client); - client.sendReply(serverName, - Reply::reply(Reply::RPL_CHANNELMODEIS, + client.sendReply(Reply::reply(Reply::RPL_CHANNELMODEIS, chan.getName() + " " + modes)); return false; } @@ -180,11 +175,9 @@ bool Handlers::mode(Server &server, Client &client, Mode::parseChannelModeChanges(modeParams, unknown); std::string::const_iterator it = unknown.begin(); for (; it != unknown.end(); it++) { - client.sendReply(serverName, - Reply::reply(Reply::ERR_UNKNOWNMODE, - Mode::toString(*it), - "is unknown mode char to me for " + - chan.getName())); + client.sendReply(Reply::reply( + Reply::ERR_UNKNOWNMODE, Mode::toString(*it), + "is unknown mode char to me for " + chan.getName())); } if (!changes.size()) return false; @@ -193,7 +186,7 @@ bool Handlers::mode(Server &server, Client &client, chan.broadcastCmodeChanges(client, applied); } catch (const Error &e) { if (e.hasReply()) - client.sendReply(serverName, e.reply()); + client.sendReply(e.reply()); } return false; } @@ -219,19 +212,17 @@ are listed as being on `channel' "*". */ bool Handlers::names(Server &server, Client &client, const std::vector ¶ms) { - const std::string &serverName = server.getServerName(); if (params.empty()) { - client.sendReply(serverName, Reply::reply(Reply::RPL_ENDOFNAMES, "*")); + client.sendReply(Reply::reply(Reply::RPL_ENDOFNAMES, "*")); return false; } std::vector chans = Utils::split(params[0], ','); for (size_t i = 0; i < chans.size(); i++) { try { Channel &ch = server.getChannelManager().at(chans[i]); - ch.sendNames(serverName, client); + ch.sendNames(client); } catch (const Error &) { - client.sendReply(serverName, - Reply::reply(Reply::RPL_ENDOFNAMES, chans[i])); + client.sendReply(Reply::reply(Reply::RPL_ENDOFNAMES, chans[i])); } } return false; @@ -258,7 +249,6 @@ bool Handlers::part(Server &server, Client &client, std::vector channels = Utils::split(params[0], ','); std::string reason = (params.size() > 1) ? params[1] : client.getNick(); ChannelManager &cm = server.getChannelManager(); - const std::string &serverName = server.getServerName(); for (size_t i = 0; i < channels.size(); i++) { if (channels[i].empty()) continue; @@ -266,7 +256,7 @@ bool Handlers::part(Server &server, Client &client, cm.part(client, channels[i], reason); } catch (const Error &e) { if (e.hasReply()) - client.sendReply(serverName, e.reply()); + client.sendReply(e.reply()); } } return false; @@ -305,10 +295,10 @@ bool Handlers::topic(Server &server, Client &client, if (params.size() > 1) ch.setTopic(client, params[1]); else - ch.sendTopic(server.getServerName(), client); + ch.sendTopic(client); } catch (const Error &e) { if (e.hasReply()) - client.sendReply(server.getServerName(), e.reply()); + client.sendReply(e.reply()); } return false; } @@ -326,8 +316,7 @@ bool Handlers::version(Server &server, Client &client, (void)params; const std::string &serverName = server.getServerName(); // . : - client.sendReply(serverName, - Reply::reply(Reply::RPL_VERSION, + client.sendReply(Reply::reply(Reply::RPL_VERSION, server.getServerVersion() + " " + serverName, "ft_irc")); return false; diff --git a/srcs/commands/Messaging.cpp b/srcs/commands/Messaging.cpp index c396701..d9630d2 100644 --- a/srcs/commands/Messaging.cpp +++ b/srcs/commands/Messaging.cpp @@ -49,18 +49,15 @@ void deliverTo(Server &server, Client &from, const std::string &target, bool deliver(Server &server, Client &client, const std::vector ¶ms, const std::string &cmd, bool silent) { - const std::string &serverName = server.getServerName(); if (params.empty() || params[0].empty()) { if (!silent) - client.sendReply(serverName, - Reply::reply(Reply::ERR_NORECIPIENT, "", + client.sendReply(Reply::reply(Reply::ERR_NORECIPIENT, "", "No recipient given (" + cmd + ")")); return false; } if (params.size() < 2 || params[1].empty()) { if (!silent) - client.sendReply(serverName, - Reply::reply(Reply::ERR_NOTEXTTOSEND, "")); + client.sendReply(Reply::reply(Reply::ERR_NOTEXTTOSEND, "")); return false; } const std::vector targets = Utils::split(params[0], ','); @@ -73,7 +70,7 @@ bool deliver(Server &server, Client &client, deliverTo(server, client, targets[i], line); } catch (const Error &e) { if (!silent && e.hasReply()) - client.sendReply(serverName, e.reply()); + client.sendReply(e.reply()); } } return false; diff --git a/srcs/commands/Registration.cpp b/srcs/commands/Registration.cpp index 6103e3a..8a9d436 100644 --- a/srcs/commands/Registration.cpp +++ b/srcs/commands/Registration.cpp @@ -72,29 +72,26 @@ bool isValidNick(const std::string &nick) { void sendWelcome(Server &server, Client &client) { const std::string name = server.getServerName(); - client.sendReply(name, - Reply::reply(Reply::RPL_WELCOME, "", + const std::string version = server.getServerVersion(); + client.sendReply(Reply::reply(Reply::RPL_WELCOME, "", "Welcome to the Internet Relay Network " + client.getPrefix())); - client.sendReply(name, Reply::reply(Reply::RPL_YOURHOST, "", - "Your host is " + name + - ", running version " + - server.getServerVersion())); client.sendReply( - name, Reply::reply(Reply::RPL_CREATED, "", - "This server was created " + server.getCreatedAt())); + Reply::reply(Reply::RPL_YOURHOST, "", + "Your host is " + name + ", running version " + version)); + client.sendReply( + Reply::reply(Reply::RPL_CREATED, "", + "This server was created " + server.getCreatedAt())); // client.sendReply( - name, Reply::reply(Reply::RPL_MYINFO, - name + " " + server.getServerVersion() + " itkol")); - client.sendReply(name, - Reply::reply(Reply::RPL_ISUPPORT, + Reply::reply(Reply::RPL_MYINFO, name + " " + version + " itkol")); + client.sendReply(Reply::reply(Reply::RPL_ISUPPORT, "CHANTYPES=&#+! NICKLEN=30 CHANMODES=,k,l,it " "PREFIX=(o)@ CASEMAPPING=rfc1459", "are supported by this server")); // No MOTD: irssi (and most clients) consider registration finished only // after RPL_ENDOFMOTD (376) or ERR_NOMOTD (422). - client.sendReply(name, Reply::reply(Reply::ERR_NOMOTD, "")); + client.sendReply(Reply::reply(Reply::ERR_NOMOTD, "")); } } // namespace @@ -173,8 +170,7 @@ bool Registration::tryRegister(Server &server, Client &client) { if (client.isRegistered() || !client.hasIdentity()) return false; if (!client.hasPassAccepted()) { - client.sendReply(server.getServerName(), - Reply::reply(Reply::ERR_PASSWDMISMATCH, "")); + client.sendReply(Reply::reply(Reply::ERR_PASSWDMISMATCH, "")); client.sendClosingMsg("Password incorrect"); return true; // event loop: flush, then close the connection } diff --git a/srcs/server/Channel.cpp b/srcs/server/Channel.cpp index eda4652..39e312b 100644 --- a/srcs/server/Channel.cpp +++ b/srcs/server/Channel.cpp @@ -142,7 +142,6 @@ Note: ERR_INVALIDMODEPARAM is IRCv3 extension Mode::ModeChanges Channel::applyChannelModeChanges(Server &server, Client &client, Mode::ModeChanges changes) { - const std::string &serverName = server.getServerName(); Mode::ModeChanges applied; Mode::ModeChanges::const_iterator it = changes.begin(); for (; it != changes.end(); it++) { @@ -150,16 +149,15 @@ Mode::ModeChanges Channel::applyChannelModeChanges(Server &server, continue; if (!Mode::hasPrivs(client.getNickCaseFolded(), members_.highestUserMode(client), *it)) { - client.sendReply(serverName, - Reply::reply(Reply::ERR_CHANOPRIVSNEEDED, name_)); + client.sendReply(Reply::reply(Reply::ERR_CHANOPRIVSNEEDED, name_)); continue; } switch (it->mode) { case Mode::Key: - applyKeyMode_(serverName, client, *it, applied); + applyKeyMode_(client, *it, applied); break; case Mode::UserLimit: - applyUserLimitMode_(serverName, client, *it, applied); + applyUserLimitMode_(client, *it, applied); break; case Mode::ChannelFounder: case Mode::ChannelAdmin: @@ -168,7 +166,6 @@ Mode::ModeChanges Channel::applyChannelModeChanges(Server &server, case Mode::Voice: if (it->arg.empty()) { client.sendReply( - serverName, Reply::reply(Reply::ERR_NEEDMOREPARAMS, "MODE")); continue; } @@ -183,32 +180,29 @@ Mode::ModeChanges Channel::applyChannelModeChanges(Server &server, return applied; } -void Channel::applyKeyMode_(const std::string &serverName, Client &client, - const Mode::ModeChange &change, - Mode::ModeChanges &applied) { +void Channel::applyKeyMode_(Client &client, const Mode::ModeChange &change, + Mode::ModeChanges &applied) { if (change.op == Mode::Remove) { setKey(""); applied.push_back(change); return; } if (!key_.empty()) { - client.sendReply(serverName, Reply::reply(Reply::ERR_KEYSET, name_)); + client.sendReply(Reply::reply(Reply::ERR_KEYSET, name_)); return; } if (!validateChannelKey_(change.arg)) { - client.sendReply( - serverName, - Reply::reply( - Reply::ERR_INVALIDMODEPARAM, - Utils::join3(name_, Mode::toString(change.mode), change.arg), - "Invalid mode parameter")); + client.sendReply(Reply::reply( + Reply::ERR_INVALIDMODEPARAM, + Utils::join3(name_, Mode::toString(change.mode), change.arg), + "Invalid mode parameter")); return; } setKey(change.arg); applied.push_back(change); } -void Channel::applyUserLimitMode_(const std::string &serverName, Client &client, +void Channel::applyUserLimitMode_(Client &client, const Mode::ModeChange &change, Mode::ModeChanges &applied) { static const std::string &userLimitError = @@ -221,12 +215,10 @@ void Channel::applyUserLimitMode_(const std::string &serverName, Client &client, } int val = Utils::toPosInt(change.arg); if (val <= 0) { - client.sendReply( - serverName, - Reply::reply( - Reply::ERR_INVALIDMODEPARAM, - Utils::join3(name_, Mode::toString(change.mode), change.arg), - userLimitError)); + client.sendReply(Reply::reply( + Reply::ERR_INVALIDMODEPARAM, + Utils::join3(name_, Mode::toString(change.mode), change.arg), + userLimitError)); return; } setUserLimit(val); @@ -260,11 +252,10 @@ bool Channel::applyModeToMember_(Server &server, Client &client, Client &target = server.getClientManager().at(change.arg); if (members_.setMode(target, change.mode, change.op == Mode::Add)) return true; - client.sendReply(server.getServerName(), - Reply::reply(Reply::ERR_USERNOTINCHANNEL, + client.sendReply(Reply::reply(Reply::ERR_USERNOTINCHANNEL, change.arg + " " + name_)); } catch (Error &e) { - client.sendReply(server.getServerName(), e.reply()); + client.sendReply(e.reply()); } return false; } @@ -307,18 +298,17 @@ topic is set. Note: RPL_TOPICWHOTIME is IRCv3 extension */ -void Channel::sendTopic(const std::string &serverName, Client &client) const { +void Channel::sendTopic(Client &client) const { if (!hasMember(client)) throw Error::reply(Reply::ERR_NOTONCHANNEL, name_); if (topic_.empty()) { - client.sendReply(serverName, Reply::reply(Reply::RPL_NOTOPIC, name_)); + client.sendReply(Reply::reply(Reply::RPL_NOTOPIC, name_)); return; } - client.sendReply(serverName, Reply::reply(Reply::RPL_TOPIC, name_, topic_)); + client.sendReply(Reply::reply(Reply::RPL_TOPIC, name_, topic_)); std::ostringstream oss; oss << static_cast(topicSetTime_); // convert to unix epoch in seconds - client.sendReply(serverName, - Reply::reply(Reply::RPL_TOPICWHOTIME, + client.sendReply(Reply::reply(Reply::RPL_TOPICWHOTIME, name_ + " " + topicSetBy_ + " " + oss.str())); } @@ -327,10 +317,10 @@ void Channel::sendTopic(const std::string &serverName, Client &client) const { // send list of users in the channel to the given client // Note: If MODE `s` secret channel flag is implemented: check is required so // non-members receive non-existent channel instead of a leak in client names -void Channel::sendNames(const std::string &serverName, Client &client) const { - client.sendReply(serverName, Reply::reply(Reply::RPL_NAMREPLY, "= " + name_, - members_.namesList())); - client.sendReply(serverName, Reply::reply(Reply::RPL_ENDOFNAMES, name_)); +void Channel::sendNames(Client &client) const { + client.sendReply( + Reply::reply(Reply::RPL_NAMREPLY, "= " + name_, members_.namesList())); + client.sendReply(Reply::reply(Reply::RPL_ENDOFNAMES, name_)); } // throws error when client cannot invite target to the channel diff --git a/srcs/server/Client.cpp b/srcs/server/Client.cpp index b205433..94bce11 100644 --- a/srcs/server/Client.cpp +++ b/srcs/server/Client.cpp @@ -105,11 +105,12 @@ void Client::send(const std::string &msg) { server_->sendMsg(fd_, msg); } -void Client::sendReply(const std::string &serverName, - const Reply::Message &msg) { - const std::string &text = Reply::build(serverName, getReplyTarget(), msg); - if (server_) - server_->sendMsg(fd_, text); +void Client::sendReply(const Reply::Message &msg) { + if (!server_) + return; + const std::string &text = + Reply::build(server_->getServerName(), getReplyTarget(), msg); + server_->sendMsg(fd_, text); } // "nick!user@host", or "*" until NICK and USER are both known. The host is From 6501394aa52e6a7a4965772650d81ccf9bafb8ba Mon Sep 17 00:00:00 2001 From: ArchCipher Date: Fri, 11 Sep 2026 16:31:09 +0200 Subject: [PATCH 283/349] fix: Remove throw from CommandProcessor --- includes/CommandProcessor.hpp | 11 +++++---- includes/Errors.hpp | 2 +- includes/Reply.hpp | 2 +- srcs/commands/CommandProcessor.cpp | 26 ++++++++++++-------- srcs/commands/Connection.cpp | 6 +++-- srcs/commands/Messaging.cpp | 2 +- srcs/commands/Registration.cpp | 38 +++++++++++++++++------------- 7 files changed, 51 insertions(+), 36 deletions(-) diff --git a/includes/CommandProcessor.hpp b/includes/CommandProcessor.hpp index 2b1304c..e56c3e7 100644 --- a/includes/CommandProcessor.hpp +++ b/includes/CommandProcessor.hpp @@ -1,5 +1,7 @@ #pragma once +#include "Reply.hpp" + #include #include #include @@ -37,15 +39,14 @@ class Command { class CommandProcessor { std::map commands_; - Command &at_(const std::string &name); + + bool sendError_(Client &client, Reply::Code code, + const std::string ¶ms = ""); public: - CommandProcessor() { - registerCommands(); - } + CommandProcessor(); void registerCommands(); - // parser TODO blnunez- bool parse(const std::string &line, std::string &name, std::vector ¶ms); bool run(Server &server, Client &client, const std::string &line); diff --git a/includes/Errors.hpp b/includes/Errors.hpp index 67bdf1d..fd728aa 100644 --- a/includes/Errors.hpp +++ b/includes/Errors.hpp @@ -49,7 +49,7 @@ class Error : public std::exception { return reply_; } - static Error reply(Reply::Code code, const std::string ¶ms) { + static Error reply(Reply::Code code, const std::string ¶ms = "") { return Error(Reply::reply(code, params)); } diff --git a/includes/Reply.hpp b/includes/Reply.hpp index 63088e8..251d8e4 100644 --- a/includes/Reply.hpp +++ b/includes/Reply.hpp @@ -228,7 +228,7 @@ struct Message { // reply() with default text from text(). // reply(ERR_NOSUCHNICK, "bob") -> code=401, body="bob :No such nick/channel" // reply(RPL_MYINFO, "irc.local 1.0 itkol") -> code=4, body="irc.local 1.0 itkol" -inline Message reply(Code code, const std::string ¶ms) { +inline Message reply(Code code, const std::string ¶ms = "") { const char *t = text(code); if (*t != '\0') return Message(code, params, t); diff --git a/srcs/commands/CommandProcessor.cpp b/srcs/commands/CommandProcessor.cpp index 31555c2..cba41e3 100644 --- a/srcs/commands/CommandProcessor.cpp +++ b/srcs/commands/CommandProcessor.cpp @@ -9,11 +9,8 @@ #include -Command &CommandProcessor::at_(const std::string &name) { - std::map::iterator it = commands_.find(name); - if (it == commands_.end()) - throw Error::reply(Reply::ERR_UNKNOWNCOMMAND, name); - return it->second; +CommandProcessor::CommandProcessor() { + registerCommands(); } bool CommandProcessor::parse(const std::string &line, std::string &cmd, @@ -48,6 +45,12 @@ void CommandProcessor::registerCommands() { add("WHOIS", Command(Handlers::whois, false, 1)); } +bool CommandProcessor::sendError_(Client &client, Reply::Code code, + const std::string ¶ms) { + client.sendReply(Reply::reply(code, params)); + return false; +} + // Run runs this command with the given client/message. bool CommandProcessor::run(Server &server, Client &client, const std::string &line) { @@ -56,16 +59,19 @@ bool CommandProcessor::run(Server &server, Client &client, if (!parse(line, name, params)) return false; - Command &cmd = at_(name); + std::map::iterator it = commands_.find(name); + if (it == commands_.end()) + return sendError_(client, Reply::ERR_UNKNOWNCOMMAND, name); + Command &cmd = it->second; if (!client.isRegistered() && !cmd.isUsablePreReg()) - throw Error::reply(Reply::ERR_NOTREGISTERED, ""); + return sendError_(client, Reply::ERR_NOTREGISTERED); if (client.isRegistered() && (cmd.handler == Handlers::pass || cmd.handler == Handlers::user)) - throw Error::reply(Reply::ERR_ALREADYREGISTRED, ""); + return sendError_(client, Reply::ERR_ALREADYREGISTRED); if (cmd.getRequiresOp() && !client.isOperator()) - throw Error::reply(Reply::ERR_NOPRIVILEGES, ""); + return sendError_(client, Reply::ERR_NOPRIVILEGES); if (params.size() < cmd.getMinParams()) - throw Error::reply(Reply::ERR_NEEDMOREPARAMS, name); + return sendError_(client, Reply::ERR_NEEDMOREPARAMS, name); bool exiting = cmd.handler(server, client, params); // after each command, see if we can send registration to the client diff --git a/srcs/commands/Connection.cpp b/srcs/commands/Connection.cpp index 27da08c..39f81aa 100644 --- a/srcs/commands/Connection.cpp +++ b/srcs/commands/Connection.cpp @@ -42,8 +42,10 @@ bool Handlers::quit(Server &server, Client &client, bool Handlers::ping(Server &server, Client &client, const std::vector ¶ms) { - if (params.empty() || params[0].empty()) - throw Error::reply(Reply::ERR_NOORIGIN, ""); + if (params.empty() || params[0].empty()) { + client.sendReply(Reply::reply(Reply::ERR_NOORIGIN)); + return false; + } client.send(Reply::fromClient(server.getServerName(), "PONG", server.getServerName(), params[0])); return false; diff --git a/srcs/commands/Messaging.cpp b/srcs/commands/Messaging.cpp index d9630d2..cd1a4ba 100644 --- a/srcs/commands/Messaging.cpp +++ b/srcs/commands/Messaging.cpp @@ -57,7 +57,7 @@ bool deliver(Server &server, Client &client, } if (params.size() < 2 || params[1].empty()) { if (!silent) - client.sendReply(Reply::reply(Reply::ERR_NOTEXTTOSEND, "")); + client.sendReply(Reply::reply(Reply::ERR_NOTEXTTOSEND)); return false; } const std::vector targets = Utils::split(params[0], ','); diff --git a/srcs/commands/Registration.cpp b/srcs/commands/Registration.cpp index 8a9d436..5dd4d4a 100644 --- a/srcs/commands/Registration.cpp +++ b/srcs/commands/Registration.cpp @@ -91,7 +91,7 @@ void sendWelcome(Server &server, Client &client) { "are supported by this server")); // No MOTD: irssi (and most clients) consider registration finished only // after RPL_ENDOFMOTD (376) or ERR_NOMOTD (422). - client.sendReply(Reply::reply(Reply::ERR_NOMOTD, "")); + client.sendReply(Reply::reply(Reply::ERR_NOMOTD)); } } // namespace @@ -117,10 +117,10 @@ bool Handlers::cap(Server &server, Client &client, // PASS bool Handlers::pass(Server &server, Client &client, const std::vector ¶ms) { - if (client.isRegistered()) - throw Error::reply(Reply::ERR_ALREADYREGISTRED, ""); - if (params[0].empty()) - throw Error::reply(Reply::ERR_NEEDMOREPARAMS, "PASS"); + if (params[0].empty()) { + client.sendReply(Reply::reply(Reply::ERR_NEEDMOREPARAMS, "PASS")); + return false; + } // A wrong password is not reported here: it is reported (464) and the // link closed when NICK+USER complete, see tryRegister(). client.setPassAccepted(params[0] == server.getPassword()); @@ -130,16 +130,22 @@ bool Handlers::pass(Server &server, Client &client, // NICK bool Handlers::nick(Server &server, Client &client, const std::vector ¶ms) { - if (params.empty() || params[0].empty()) - throw Error::reply(Reply::ERR_NONICKNAMEGIVEN, ""); + if (params.empty() || params[0].empty()) { + client.sendReply(Reply::reply(Reply::ERR_NONICKNAMEGIVEN)); + return false; + } const std::string &nick = params[0]; - if (!isValidNick(nick)) - throw Error::reply(Reply::ERR_ERRONEUSNICKNAME, nick); + if (!isValidNick(nick)) { + client.sendReply(Reply::reply(Reply::ERR_ERRONEUSNICKNAME, nick)); + return false; + } ClientManager &clients = server.getClientManager(); const bool sameClient = Utils::foldCase(nick) == client.getNickCaseFolded(); - if (!sameClient && clients.hasNick(nick)) - throw Error::reply(Reply::ERR_NICKNAMEINUSE, nick); + if (!sameClient && clients.hasNick(nick)) { + client.sendReply(Reply::reply(Reply::ERR_NICKNAMEINUSE, nick)); + return false; + } if (client.isRegistered()) { // Registered client renaming itself: everyone who can see it - itself @@ -158,10 +164,10 @@ bool Handlers::nick(Server &server, Client &client, bool Handlers::user(Server &server, Client &client, const std::vector ¶ms) { (void)server; - if (client.isRegistered()) - throw Error::reply(Reply::ERR_ALREADYREGISTRED, ""); - if (params[0].empty() || params[3].empty()) - throw Error::reply(Reply::ERR_NEEDMOREPARAMS, "USER"); + if (params[0].empty() || params[3].empty()) { + client.sendReply(Reply::reply(Reply::ERR_NEEDMOREPARAMS, "USER")); + return false; + } client.setUser(params[0], params[3]); return false; } @@ -170,7 +176,7 @@ bool Registration::tryRegister(Server &server, Client &client) { if (client.isRegistered() || !client.hasIdentity()) return false; if (!client.hasPassAccepted()) { - client.sendReply(Reply::reply(Reply::ERR_PASSWDMISMATCH, "")); + client.sendReply(Reply::reply(Reply::ERR_PASSWDMISMATCH)); client.sendClosingMsg("Password incorrect"); return true; // event loop: flush, then close the connection } From ccb2f267b480810b9bf1aebc8e0c2aaf229168dc Mon Sep 17 00:00:00 2001 From: ArchCipher Date: Fri, 11 Sep 2026 16:48:24 +0200 Subject: [PATCH 284/349] chore: remove try except in Scoket::processClientSockets_ --- srcs/commands/CommandProcessor.cpp | 1 - srcs/commands/Connection.cpp | 1 - srcs/commands/Registration.cpp | 1 - srcs/server/Server.cpp | 34 ++++++------------------------ 4 files changed, 7 insertions(+), 30 deletions(-) diff --git a/srcs/commands/CommandProcessor.cpp b/srcs/commands/CommandProcessor.cpp index cba41e3..48e7fa9 100644 --- a/srcs/commands/CommandProcessor.cpp +++ b/srcs/commands/CommandProcessor.cpp @@ -1,7 +1,6 @@ #include "CommandProcessor.hpp" #include "Client.hpp" -#include "Errors.hpp" #include "Handlers.hpp" #include "Parser.hpp" #include "Registration.hpp" diff --git a/srcs/commands/Connection.cpp b/srcs/commands/Connection.cpp index 39f81aa..3ee37bf 100644 --- a/srcs/commands/Connection.cpp +++ b/srcs/commands/Connection.cpp @@ -21,7 +21,6 @@ */ #include "Client.hpp" -#include "Errors.hpp" #include "Handlers.hpp" #include "Reply.hpp" #include "Server.hpp" diff --git a/srcs/commands/Registration.cpp b/srcs/commands/Registration.cpp index 5dd4d4a..e4216b2 100644 --- a/srcs/commands/Registration.cpp +++ b/srcs/commands/Registration.cpp @@ -32,7 +32,6 @@ #include "Registration.hpp" #include "Client.hpp" -#include "Errors.hpp" #include "Handlers.hpp" #include "Parser.hpp" #include "Reply.hpp" diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 4a74559..b297225 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -126,35 +126,15 @@ void Server::processClientSockets_() { std::string cmdLine; bool buffHasMoreCmds = false; do { -#define ERRMSG2CLIENTBYEXCEPT -#ifdef ERRMSG2CLIENTBYEXCEPT - try { -#endif - cmdLine = - sockets_.getCRLFLineFromBuffer(pfd.fd, buffHasMoreCmds); - if (!cmdLine.empty()) { - // returns true if connection is to be closed - if (cmds_.run(*this, clients_.at(pfd.fd), cmdLine)) { - sockets_.closeSocket(pfd.fd); - break; - } - } -#ifdef ERRMSG2CLIENTBYEXCEPT - } catch (Error const &e) { - if (e.hasReply()) { - Client *client = clients_.find(pfd.fd); - std::string nickName = - (client ? client->getNick() : "dbgDummyNick"); - sendMsg(pfd.fd, - Reply::build(serverName_, nickName, e.reply())); - } else { - std::string tmp( - "Error while trying to get/execute cmd:\n"); - tmp.append("'" + cmdLine + "'\n" + e.what()); - log_(tmp, Logger::lmExcept); + cmdLine = + sockets_.getCRLFLineFromBuffer(pfd.fd, buffHasMoreCmds); + if (!cmdLine.empty()) { + // returns true if connection is to be closed + if (cmds_.run(*this, clients_.at(pfd.fd), cmdLine)) { + sockets_.closeSocket(pfd.fd); + break; } } -#endif } while (buffHasMoreCmds); } } From 4c91b69d0cfa146cc93bd45af70ac669cb91036e Mon Sep 17 00:00:00 2001 From: ArchCipher Date: Fri, 11 Sep 2026 17:10:07 +0200 Subject: [PATCH 285/349] fix: use client::find instead of at + flatten processClientSockets_ --- srcs/server/Server.cpp | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index b297225..3822857 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -1,8 +1,6 @@ #include "Server.hpp" #include "Client.hpp" // for Client -#include "Errors.hpp" // for Error -#include "Reply.hpp" // for build #include "ServerHelper.hpp" // for getAdressFamilyStr, composeAddrfamAddrP... #include "SignalHandler.hpp" // for SignalHandler @@ -122,21 +120,23 @@ void Server::processClientSockets_() { ++idx) { pollfd &pfd = pollFds_.at(idx); - if (sockets_.checkSocket4Recv(pfd) && (pfd.revents & POLLIN)) { - std::string cmdLine; - bool buffHasMoreCmds = false; - do { - cmdLine = - sockets_.getCRLFLineFromBuffer(pfd.fd, buffHasMoreCmds); - if (!cmdLine.empty()) { - // returns true if connection is to be closed - if (cmds_.run(*this, clients_.at(pfd.fd), cmdLine)) { - sockets_.closeSocket(pfd.fd); - break; - } - } - } while (buffHasMoreCmds); - } + if (!(sockets_.checkSocket4Recv(pfd) && (pfd.revents & POLLIN))) + continue; + bool buffHasMoreCmds = false; + do { + std::string cmdLine = + sockets_.getCRLFLineFromBuffer(pfd.fd, buffHasMoreCmds); + if (cmdLine.empty()) + continue; + Client *client = clients_.find(pfd.fd); + if (!client) + break; + // returns true if connection is to be closed + if (cmds_.run(*this, *client, cmdLine)) { + sockets_.closeSocket(pfd.fd); + break; + } + } while (buffHasMoreCmds); } removeClosedSockets_(); } From 735275f4500fea0c7765561dc84ff737904dfd19 Mon Sep 17 00:00:00 2001 From: Rapha Date: Sun, 13 Sep 2026 13:51:00 +0200 Subject: [PATCH 286/349] rfa: use find & check for nullptr instead of at(fd) that might throw --- srcs/server/Server.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index bcbe31a..d7782b0 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -33,13 +33,14 @@ Server::~Server() { while (!pollFds_.empty()) { int const fd = pollFds_.back().fd; if (!sockets_.isServerSocket(fd)) { - Client &client = clients_.at(fd); - client.sendClosingMsg("Server shutdown - goodbye"); + Client *client = clients_.find(fd); + if (client) + client->sendClosingMsg("Server shutdown - goodbye"); // REMOVE ME ---> - const std::string &out = client.getSendBuffer(); + const std::string &out = client->getSendBuffer(); sockets_.sendMsg(fd, out); - client.consumeSendBuffer(out.size()); + client->consumeSendBuffer(out.size()); // <--- REMOVE ME clients_.remove(fd, channels_); From 39e2926fdee94299b1ef9c5aa6264364a77e5767 Mon Sep 17 00:00:00 2001 From: ArchCipher Date: Tue, 15 Sep 2026 10:12:16 +0200 Subject: [PATCH 287/349] bugfix: parsing -o --- includes/Mode.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/Mode.hpp b/includes/Mode.hpp index 308f401..1057260 100644 --- a/includes/Mode.hpp +++ b/includes/Mode.hpp @@ -191,11 +191,11 @@ parseChannelModeChanges(const std::vector ¶ms, changes.push_back(ModeChange(*it, op, "")); break; case UserLimit: - case ChannelOperator: if (op == Remove) { changes.push_back(ModeChange(*it, op, "")); continue; } + case ChannelOperator: case Key: if (idx >= params.size()) changes.push_back(ModeChange(*it, op, "")); From 1af5ddae24a31a61035e1c704702a17a11b04809 Mon Sep 17 00:00:00 2001 From: ArchCipher Date: Tue, 15 Sep 2026 12:20:09 +0200 Subject: [PATCH 288/349] Merge branch 'main' of github.com:gitraphha/ft_irc into feat/sendMsg --- srcs/server/Server.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 4a74559..3f26fd5 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -33,8 +33,9 @@ Server::~Server() { while (!pollFds_.empty()) { int const fd = pollFds_.back().fd; if (!sockets_.isServerSocket(fd)) { - Client &client = clients_.at(fd); - client.sendClosingMsg("Server shutdown - goodbye"); + Client *client = clients_.find(fd); + if (client) + client->sendClosingMsg("Server shutdown - goodbye"); clients_.remove(fd, channels_); } sockets_.closeSocket(fd); From 0203df20aa67a749f06dcef47423d128ead36f05 Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 13:19:52 +0200 Subject: [PATCH 289/349] feat: add handling of negative error code. Reason: Server sending shutdown error msg has no error code, so setting it to a negative value should be possible and should suppress sending a code. --- includes/Reply.hpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/includes/Reply.hpp b/includes/Reply.hpp index 63088e8..3f66b33 100644 --- a/includes/Reply.hpp +++ b/includes/Reply.hpp @@ -272,8 +272,10 @@ Use "*" for `nick` until the client has registered (pre-001). inline std::string build(const std::string &server, const std::string &nick, const Message &msg) { std::ostringstream ss; - ss << ":" << server << " " << std::setw(3) << std::setfill('0') << msg.code - << " " << nick; + ss << ":" << server; + if (msg.code > 0) + ss << " " << std::setw(3) << std::setfill('0') << msg.code; + ss << " " << nick; if (!msg.params.empty()) ss << " " << msg.params; if (msg.hasTrailing) From 04623222ce8ed6776ba85638f8559411f825bfe9 Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 13:22:16 +0200 Subject: [PATCH 290/349] feat: send goodby error message to clients when server shuts down --- srcs/server/Server.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 3f26fd5..fde60dc 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -30,6 +30,7 @@ Server::Server(int const port, std::string const &password) * going backwards through pollFds to ensure server sockets are closed last */ Server::~Server() { + std::string nickName /*= ...get from Manager */; while (!pollFds_.empty()) { int const fd = pollFds_.back().fd; if (!sockets_.isServerSocket(fd)) { From 7e134ea510af20ba81f2afc555e4f3eb943abad9 Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 13:32:27 +0200 Subject: [PATCH 291/349] fix: update msg content, including "ERROR", add nickname and hint to inform clientManager --- srcs/server/Server.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index fde60dc..3f26fd5 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -30,7 +30,6 @@ Server::Server(int const port, std::string const &password) * going backwards through pollFds to ensure server sockets are closed last */ Server::~Server() { - std::string nickName /*= ...get from Manager */; while (!pollFds_.empty()) { int const fd = pollFds_.back().fd; if (!sockets_.isServerSocket(fd)) { From 6e0486262cf40b8f9475e7ff1c52f1526df6e5bd Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 13:22:16 +0200 Subject: [PATCH 292/349] feat: send goodby error message to clients when server shuts down --- srcs/server/Server.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 3f26fd5..fde60dc 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -30,6 +30,7 @@ Server::Server(int const port, std::string const &password) * going backwards through pollFds to ensure server sockets are closed last */ Server::~Server() { + std::string nickName /*= ...get from Manager */; while (!pollFds_.empty()) { int const fd = pollFds_.back().fd; if (!sockets_.isServerSocket(fd)) { From e9e53575d7c7c01c21255a910701386f1269bce1 Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 13:32:27 +0200 Subject: [PATCH 293/349] fix: update msg content, including "ERROR", add nickname and hint to inform clientManager --- srcs/server/Server.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index fde60dc..3f26fd5 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -30,7 +30,6 @@ Server::Server(int const port, std::string const &password) * going backwards through pollFds to ensure server sockets are closed last */ Server::~Server() { - std::string nickName /*= ...get from Manager */; while (!pollFds_.empty()) { int const fd = pollFds_.back().fd; if (!sockets_.isServerSocket(fd)) { From 17cb9c0f52459d451b5df6d192f641a60800bbf0 Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 3 Sep 2026 12:40:41 +0200 Subject: [PATCH 294/349] fix: create the ERROR message from scratch, as Reply::Message doesn't allow non-Code generation --- srcs/server/Server.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 3f26fd5..e920224 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -166,6 +166,7 @@ void Server::removeClosedSockets_() { for (size_t idx = numServerSockets_; idx < pollFds_.size(); ++idx) { pollfd &pfd = pollFds_.at(idx); if (sockets_.socketClosed(pfd.fd)) { + removePollFd_(pfd.fd); clients_.remove(pfd.fd, channels_); removePollFd_(pfd.fd); --idx; From 40985d8e536a3804df277cec3bfbcb6900740057 Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 13:22:16 +0200 Subject: [PATCH 295/349] feat: send goodby error message to clients when server shuts down --- srcs/server/Server.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index e920224..e250dd9 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -25,11 +25,16 @@ Server::Server(int const port, std::string const &password) createdAt_(Logger::nowStr()), port_(port), password_(password), logger_(serverName_ + ": "), sockets_(logger_), numServerSockets_(0) {} +/* + Cleanup + * going backwards through pollFds to ensure server sockets are closed last + */ /* Cleanup * going backwards through pollFds to ensure server sockets are closed last */ Server::~Server() { + std::string nickName /*= ...get from Manager */; while (!pollFds_.empty()) { int const fd = pollFds_.back().fd; if (!sockets_.isServerSocket(fd)) { From 5d610f4afb1aef88edb94467ccd7d7d2e83e6ada Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 2 Sep 2026 13:32:27 +0200 Subject: [PATCH 296/349] fix: update msg content, including "ERROR", add nickname and hint to inform clientManager --- srcs/server/Server.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index e250dd9..8ede875 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -34,7 +34,6 @@ Server::Server(int const port, std::string const &password) * going backwards through pollFds to ensure server sockets are closed last */ Server::~Server() { - std::string nickName /*= ...get from Manager */; while (!pollFds_.empty()) { int const fd = pollFds_.back().fd; if (!sockets_.isServerSocket(fd)) { From 4d8401ec214a5fef8539f4e7d93f40cbcb21d605 Mon Sep 17 00:00:00 2001 From: Rapha Date: Sun, 6 Sep 2026 11:38:47 +0200 Subject: [PATCH 297/349] feat: revert additions that could potentially have been used for a disconnect message. --- includes/Reply.hpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/includes/Reply.hpp b/includes/Reply.hpp index 3f66b33..63088e8 100644 --- a/includes/Reply.hpp +++ b/includes/Reply.hpp @@ -272,10 +272,8 @@ Use "*" for `nick` until the client has registered (pre-001). inline std::string build(const std::string &server, const std::string &nick, const Message &msg) { std::ostringstream ss; - ss << ":" << server; - if (msg.code > 0) - ss << " " << std::setw(3) << std::setfill('0') << msg.code; - ss << " " << nick; + ss << ":" << server << " " << std::setw(3) << std::setfill('0') << msg.code + << " " << nick; if (!msg.params.empty()) ss << " " << msg.params; if (msg.hasTrailing) From 9c1fd8580442b119950a06ee43701fd0df0e1c55 Mon Sep 17 00:00:00 2001 From: Rapha Date: Mon, 7 Sep 2026 13:10:48 +0200 Subject: [PATCH 298/349] fix: * use idx instead of fd. * add --idx bc of removed element to avaoid skip --- srcs/server/Server.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 8ede875..8034355 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -170,7 +170,6 @@ void Server::removeClosedSockets_() { for (size_t idx = numServerSockets_; idx < pollFds_.size(); ++idx) { pollfd &pfd = pollFds_.at(idx); if (sockets_.socketClosed(pfd.fd)) { - removePollFd_(pfd.fd); clients_.remove(pfd.fd, channels_); removePollFd_(pfd.fd); --idx; From deba0e1f8c9dc99218c7518cda42bc22b124026b Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 10 Sep 2026 11:27:20 +0200 Subject: [PATCH 299/349] feat: * separate removeClosedSockets - needs to be done in one go at the end of a poll-loop * use fd in removePollFd (add functor pfdHasFd for use in find_if (lambdas not available in c++98) * add msgTooLong to bufferLength... function to notify that situation * eliminate use of socketClosed in isServerSocket - too much overhead * prepare Server d'tor for use of soon to be added function sendClosingMsg * remove try/catch, this block won't throw after rfa in other PR * disable output (only active for NETCATDBG) * add new passthrough Server::sendMsg * change exception msg text in SignalHnadler * add socketClosed check in checkSocket4Recv - it may habe been closed somewhere else * implement new msgTooLong functionality and reorder var definitions * disable error count for msgTooLong, this should be handled by cmds_.run (deprecated, will be reverted) rfa: * rename checkSocket for more clarity dbg: * add snippet to force crash for dbg purpose --- includes/Server.hpp | 1 + reviewNotes.txt | 27 +++++++++++++++++++++++++++ srcs/server/Socket.cpp | 1 + 3 files changed, 29 insertions(+) create mode 100644 reviewNotes.txt diff --git a/includes/Server.hpp b/includes/Server.hpp index ce66f81..c72eb41 100644 --- a/includes/Server.hpp +++ b/includes/Server.hpp @@ -51,6 +51,7 @@ class Server { ~Server(); void run(); + void sendMsg(int const fd, std::string const &msg); const std::string &getServerName() const; const std::string &getServerVersion() const; diff --git a/reviewNotes.txt b/reviewNotes.txt new file mode 100644 index 0000000..6613ea9 --- /dev/null +++ b/reviewNotes.txt @@ -0,0 +1,27 @@ +was macht Client::remove genau + * wo wird das aufgerufen + * wie wird sichergestellt, dass gleichzeitig auf pollfds gelöscht wird? + * sollte remove vllt nur von Server:: aufgerufen werden, wenn auch pollfds entfernt wird? + +Usage of exceptions to send replies from clients -> sendMsg +I'll take out the try catch, will have to see about sending the msg too long error. +Maybe this can be passed on to the cmds_.run, how to indicate? + +poll loop has to be reworked, clients cannot be closed/removed in a running poll loop with sending msgs, because maybe a set of clients was gathered elsewhere and this would result in invalid pointers. + +clients_.add( -> creation of Client should be moved inside this function. +* https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#i11-never-transfer-ownership-by-a-raw-pointer-t-or-reference-t + +multiple "*" vs nick_.empty -> maybe as a MACRO + +call setHostname in acceptClient_ (rhaas) +no: instead of passing a new Client pass serverName +instead of passing client reference to cmds.run, give it the fileDescr + + +move msgTooLong to cmds_.run -> yes. +no error counter for line too long, maybe moves to the cmd processor + +comd handler won't throw + +make separate PR for Server::sendMsg diff --git a/srcs/server/Socket.cpp b/srcs/server/Socket.cpp index 198558c..9178c9a 100644 --- a/srcs/server/Socket.cpp +++ b/srcs/server/Socket.cpp @@ -81,6 +81,7 @@ size_t Socket::bufferLengthAndSanityCheck_(int const fd, bool &msgTooLong) { return lfcrPos; sIOD.discardMode = true; msgTooLong = true; + // ++sIOD.recvErrorCount; } // discard mode if (++sIOD.recvErrorCount >= SOCKETERRORLIMIT) { From 6fc229c632bbf1d7e404d309c55324ab21e549d4 Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 10 Sep 2026 11:48:51 +0200 Subject: [PATCH 300/349] feat: * reactivate error count for msgTooLong --- srcs/server/Socket.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/srcs/server/Socket.cpp b/srcs/server/Socket.cpp index 9178c9a..198558c 100644 --- a/srcs/server/Socket.cpp +++ b/srcs/server/Socket.cpp @@ -81,7 +81,6 @@ size_t Socket::bufferLengthAndSanityCheck_(int const fd, bool &msgTooLong) { return lfcrPos; sIOD.discardMode = true; msgTooLong = true; - // ++sIOD.recvErrorCount; } // discard mode if (++sIOD.recvErrorCount >= SOCKETERRORLIMIT) { From 3d89b6eab06eb082f15496299aca0c676b1bdd34 Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 10 Sep 2026 12:12:01 +0200 Subject: [PATCH 301/349] git: ignore personal review file --- reviewNotes.txt | 27 --------------------------- 1 file changed, 27 deletions(-) delete mode 100644 reviewNotes.txt diff --git a/reviewNotes.txt b/reviewNotes.txt deleted file mode 100644 index 6613ea9..0000000 --- a/reviewNotes.txt +++ /dev/null @@ -1,27 +0,0 @@ -was macht Client::remove genau - * wo wird das aufgerufen - * wie wird sichergestellt, dass gleichzeitig auf pollfds gelöscht wird? - * sollte remove vllt nur von Server:: aufgerufen werden, wenn auch pollfds entfernt wird? - -Usage of exceptions to send replies from clients -> sendMsg -I'll take out the try catch, will have to see about sending the msg too long error. -Maybe this can be passed on to the cmds_.run, how to indicate? - -poll loop has to be reworked, clients cannot be closed/removed in a running poll loop with sending msgs, because maybe a set of clients was gathered elsewhere and this would result in invalid pointers. - -clients_.add( -> creation of Client should be moved inside this function. -* https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#i11-never-transfer-ownership-by-a-raw-pointer-t-or-reference-t - -multiple "*" vs nick_.empty -> maybe as a MACRO - -call setHostname in acceptClient_ (rhaas) -no: instead of passing a new Client pass serverName -instead of passing client reference to cmds.run, give it the fileDescr - - -move msgTooLong to cmds_.run -> yes. -no error counter for line too long, maybe moves to the cmd processor - -comd handler won't throw - -make separate PR for Server::sendMsg From 9629d639002bad6413d70e9cadf1bbb561c96725 Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 10 Sep 2026 19:02:49 +0200 Subject: [PATCH 302/349] feat: use one global static function to get a string for "now" --- srcs/server/Server.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 8034355..3f26fd5 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -25,10 +25,6 @@ Server::Server(int const port, std::string const &password) createdAt_(Logger::nowStr()), port_(port), password_(password), logger_(serverName_ + ": "), sockets_(logger_), numServerSockets_(0) {} -/* - Cleanup - * going backwards through pollFds to ensure server sockets are closed last - */ /* Cleanup * going backwards through pollFds to ensure server sockets are closed last From d2d1e1c8c6fc9094d6f91bdc112cabee04efad92 Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 10 Sep 2026 19:59:38 +0200 Subject: [PATCH 303/349] rfa: remove Server::sendMsg multiple definitions --- includes/Server.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/includes/Server.hpp b/includes/Server.hpp index c72eb41..ce66f81 100644 --- a/includes/Server.hpp +++ b/includes/Server.hpp @@ -51,7 +51,6 @@ class Server { ~Server(); void run(); - void sendMsg(int const fd, std::string const &msg); const std::string &getServerName() const; const std::string &getServerVersion() const; From 959b694593ef38a8e641b24915408e91ec5f9e77 Mon Sep 17 00:00:00 2001 From: Rapha Date: Sun, 13 Sep 2026 15:31:55 +0200 Subject: [PATCH 304/349] feat: implement stub, just so it compiles, tbd in another (prior) commit --- includes/CommandProcessor.hpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/includes/CommandProcessor.hpp b/includes/CommandProcessor.hpp index 2b1304c..65349c4 100644 --- a/includes/CommandProcessor.hpp +++ b/includes/CommandProcessor.hpp @@ -50,4 +50,9 @@ class CommandProcessor { std::vector ¶ms); bool run(Server &server, Client &client, const std::string &line); void add(const std::string &name, const Command &cmd); + void inputTooLong(Server &srv, int const fd){ + (void)srv; + (void)fd; + // tbd. + }; }; From 67f009d4c4c126b587cba9bb4ac6ef7ccfaa9e28 Mon Sep 17 00:00:00 2001 From: Rapha Date: Sun, 13 Sep 2026 15:34:01 +0200 Subject: [PATCH 305/349] feat: re-implement the msgTooLong logic, based on added functionality elsewhere rfa: avoid redundant lookups --- includes/Socket.hpp | 20 ++++++++++++-------- srcs/server/Server.cpp | 24 +++++++++++++++++++----- srcs/server/Socket.cpp | 42 +++++++++++++++++++++++------------------- 3 files changed, 54 insertions(+), 32 deletions(-) diff --git a/includes/Socket.hpp b/includes/Socket.hpp index 1c46b4d..f164ac7 100644 --- a/includes/Socket.hpp +++ b/includes/Socket.hpp @@ -32,7 +32,7 @@ class Socket { std::map sockIODataStore_; Logger const &logger_; - bool updateBuffer_(int const fd); + bool updateBuffer_(int const fd, SocketIOData &sIOD); size_t bufferLengthAndSanityCheck_(int const fd, bool &msgTooLong); public: @@ -40,19 +40,23 @@ class Socket { void addSocket(int const fd, bool srvSocket); bool checkSocket4Recv(struct pollfd &pfd); // NOLINT - std::string getCRLFLineFromBuffer(int const fd, bool &buffHasMoreCmds); + std::string getCRLFLineFromBuffer(int const fd, bool &buffHasMoreCmds, + bool &msgTooLong); bool sendMsg(int const fd, std::string const &msg); void closeSocket(int const fd); - bool socketClosed(int const fd) const { - return (sockIODataStore_.find(fd) == sockIODataStore_.end()); + SocketIOData *getSockIOData(int const fd) { + std::map::iterator it = sockIODataStore_.find(fd); + if (it == sockIODataStore_.end()) + return NULL; + return &(it->second); } bool isServerSocket(int const fd) const { - std::map::const_iterator it = - sockIODataStore_.find(fd); - if (it != sockIODataStore_.end()) - return it->second.isSrvSocket; + SocketIOData const *const sIODptr = + const_cast(this)->getSockIOData(fd); + if (sIODptr) + return sIODptr->isSrvSocket; return false; }; }; diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 3f26fd5..fbf4b3e 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -131,11 +131,25 @@ void Server::processClientSockets_() { #ifdef ERRMSG2CLIENTBYEXCEPT try { #endif - cmdLine = - sockets_.getCRLFLineFromBuffer(pfd.fd, buffHasMoreCmds); - if (!cmdLine.empty()) { + bool msgTooLong = false; + cmdLine = sockets_.getCRLFLineFromBuffer( + pfd.fd, buffHasMoreCmds, msgTooLong); + if (msgTooLong) + cmds_.inputTooLong(*this, pfd.fd); + else if (!cmdLine.empty()) { // returns true if connection is to be closed - if (cmds_.run(*this, clients_.at(pfd.fd), cmdLine)) { + Client *client = clients_.find(pfd.fd); + if (!client) { + std::ostringstream oss("Internal error: No client data found for fd:"); + oss << pfd.fd; + log_(oss.str(), Logger::lmError); + // close socket alltogether here?"No client data for fd" + // There's not much chance the client data will reappear later... + // Can't even send Closing message without client ptr + // sockets_.closeSocket(pfd.fd); + break; + } + if (cmds_.run(*this, *client, cmdLine)) { sockets_.closeSocket(pfd.fd); break; } @@ -165,7 +179,7 @@ void Server::processClientSockets_() { void Server::removeClosedSockets_() { for (size_t idx = numServerSockets_; idx < pollFds_.size(); ++idx) { pollfd &pfd = pollFds_.at(idx); - if (sockets_.socketClosed(pfd.fd)) { + if (sockets_.getSockIOData(pfd.fd) == NULL) { clients_.remove(pfd.fd, channels_); removePollFd_(pfd.fd); --idx; diff --git a/srcs/server/Socket.cpp b/srcs/server/Socket.cpp index 198558c..fafba0d 100644 --- a/srcs/server/Socket.cpp +++ b/srcs/server/Socket.cpp @@ -12,23 +12,22 @@ void Socket::addSocket(int const fd, bool srvSocket) { } bool Socket::checkSocket4Recv(struct pollfd &pfd) { - if (socketClosed(pfd.fd)) + SocketIOData *sIODptr = getSockIOData(pfd.fd); + if (!sIODptr) return false; - SocketIOData &sIOD = sockIODataStore_.at(pfd.fd); if ((pfd.revents & POLLERR)) { - if (sIOD.recvErrorCount >= SOCKETERRORLIMIT) + if (sIODptr->recvErrorCount >= SOCKETERRORLIMIT) closeSocket(pfd.fd); else - ++sIOD.recvErrorCount; + ++sIODptr->recvErrorCount; return false; } return true; } -bool Socket::updateBuffer_(int const fd) { - Socket::SocketIOData &sIOD = sockIODataStore_.at(fd); - char recvBuffer[RECVBUFFLEN]; +bool Socket::updateBuffer_(int const fd, SocketIOData &sIOD) { + char recvBuffer[RECVBUFFLEN]; sIOD.bytesRead = recv(fd, &recvBuffer[0], RECVBUFFLEN, 0); if (sIOD.bytesRead <= 0) { ++sIOD.recvErrorCount; @@ -48,21 +47,22 @@ bool Socket::updateBuffer_(int const fd) { return true; } -std::string Socket::getCRLFLineFromBuffer(int const fd, bool &buffHasMoreCmds) { +std::string Socket::getCRLFLineFromBuffer(int const fd, bool &buffHasMoreCmds, + bool &msgTooLong) { + SocketIOData *sIODptr = getSockIOData(fd); + if (!sIODptr) + return ""; + + SocketIOData &sIOD = *sIODptr; if (!buffHasMoreCmds) { - if (!updateBuffer_(fd)) + if (!updateBuffer_(fd, sIOD)) return ""; } - - bool msgTooLong = false; size_t lfcrPos = bufferLengthAndSanityCheck_(fd, msgTooLong); - if (msgTooLong) - return std::string(MAXMSGLEN + 1, '\0'); - if (lfcrPos == std::string::npos) + if (msgTooLong || lfcrPos == std::string::npos) return ""; - Socket::SocketIOData &sIOD = sockIODataStore_.at(fd); - std::string cmdLine; + std::string cmdLine; cmdLine = sIOD.readBuffer.substr(0, lfcrPos); sIOD.recvErrorCount = 0; sIOD.readBuffer = sIOD.readBuffer.substr(lfcrPos + 2); @@ -110,9 +110,13 @@ size_t Socket::bufferLengthAndSanityCheck_(int const fd, bool &msgTooLong) { NOTE: caller MUST close socket when sendMsg returns false */ bool Socket::sendMsg(int const fd, std::string const &msg) { - if (socketClosed(fd)) - return true; - SocketIOData &sIOD = sockIODataStore_.at(fd); + SocketIOData *sIODptr = getSockIOData(fd); + if (!sIODptr) + return false; + + SocketIOData &sIOD = *sIODptr; + ssize_t bytesSent = -1; + bool success = true; if (msg.length() > sIOD.writeBuffer.max_size() - sIOD.writeBuffer.length()) return false; From e44c13eff5d1ffccee38e7b04e5c57384d4f116d Mon Sep 17 00:00:00 2001 From: Rapha Date: Sun, 13 Sep 2026 15:39:12 +0200 Subject: [PATCH 306/349] rfa: clang-format --- includes/CommandProcessor.hpp | 11 ++++++----- srcs/server/Server.cpp | 23 ++++++++++++----------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/includes/CommandProcessor.hpp b/includes/CommandProcessor.hpp index 65349c4..2c05067 100644 --- a/includes/CommandProcessor.hpp +++ b/includes/CommandProcessor.hpp @@ -50,9 +50,10 @@ class CommandProcessor { std::vector ¶ms); bool run(Server &server, Client &client, const std::string &line); void add(const std::string &name, const Command &cmd); - void inputTooLong(Server &srv, int const fd){ - (void)srv; - (void)fd; - // tbd. - }; + + void inputTooLong(Server &srv, int const fd) { + (void)srv; + (void)fd; + // tbd. + }; }; diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index fbf4b3e..4b5b2f2 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -138,17 +138,18 @@ void Server::processClientSockets_() { cmds_.inputTooLong(*this, pfd.fd); else if (!cmdLine.empty()) { // returns true if connection is to be closed - Client *client = clients_.find(pfd.fd); - if (!client) { - std::ostringstream oss("Internal error: No client data found for fd:"); - oss << pfd.fd; - log_(oss.str(), Logger::lmError); - // close socket alltogether here?"No client data for fd" - // There's not much chance the client data will reappear later... - // Can't even send Closing message without client ptr - // sockets_.closeSocket(pfd.fd); - break; - } + Client *client = clients_.find(pfd.fd); + if (!client) { + std::ostringstream oss( + "Internal error: No client data found for fd:"); + oss << pfd.fd; + log_(oss.str(), Logger::lmError); + // close socket alltogether here?"No client data for fd" + // There's not much chance the client data will reappear later... + // Can't even send Closing message without client ptr + // sockets_.closeSocket(pfd.fd); + break; + } if (cmds_.run(*this, *client, cmdLine)) { sockets_.closeSocket(pfd.fd); break; From d84c4cdccc1e70a09ce3eefdc33206d1d17425bb Mon Sep 17 00:00:00 2001 From: Rapha Date: Sun, 13 Sep 2026 16:53:19 +0200 Subject: [PATCH 307/349] fix: log send system fctn error (bytesSent < 0) as failed; also avoids the otherwise wrong static_cast --- srcs/server/Socket.cpp | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/srcs/server/Socket.cpp b/srcs/server/Socket.cpp index fafba0d..1b68c31 100644 --- a/srcs/server/Socket.cpp +++ b/srcs/server/Socket.cpp @@ -136,11 +136,22 @@ bool Socket::sendMsg(int const fd, std::string const &msg) { std::ostringstream oss; oss << "SendMsg event:\n" << " WriteBuffer: '" << sIOD.writeBuffer << "':\n"; - oss << " partly (" << bytesSent << "/" << sIOD.writeBuffer.length() - << ") " << sIOD.writeBuffer.substr(0, bytesSent) - << " sent to FD: " << fd; - logger_.logMsg(oss.str(), Logger::lmInfo); -#endif + if (!success || bytesSent < 0) { + oss << " send to FD " << fd << " failed."; + logger_.logMsg(oss.str(), Logger::lmError); + } else if (static_cast(bytesSent) < sIOD.writeBuffer.length()) { + oss << " partly (" << bytesSent << "/" << sIOD.writeBuffer.length() + << ") " << sIOD.writeBuffer.substr(0, bytesSent) + << " sent to FD: " << fd; + logger_.logMsg(oss.str(), Logger::lmInfo); + } + } + + if (!success) + return false; + + sIOD.sendErrorCount = 0; + if (static_cast(bytesSent) < sIOD.writeBuffer.length()) sIOD.writeBuffer.erase(0, bytesSent); } else sIOD.writeBuffer.clear(); From d6a0ff789652d2bd94cd15b0284a9e4cce6446a0 Mon Sep 17 00:00:00 2001 From: Rapha Date: Tue, 15 Sep 2026 20:33:42 +0200 Subject: [PATCH 308/349] feat: * log all incoming and outgoing raw messages (using two new logModes * use getSockIOData in all places to check the ptr before doing anything else, e.g. initialising a reference --- includes/Logger.hpp | 2 +- srcs/server/Logger.cpp | 8 ++++++++ srcs/server/Server.cpp | 18 +++++++++++++----- srcs/server/Socket.cpp | 15 ++++++++++----- 4 files changed, 32 insertions(+), 11 deletions(-) diff --git a/includes/Logger.hpp b/includes/Logger.hpp index a62eaad..4844721 100644 --- a/includes/Logger.hpp +++ b/includes/Logger.hpp @@ -15,7 +15,7 @@ class Logger { return std::string(&buffer[0]); } - enum logMode { lmInfo, lmWarn, lmError, lmExcept }; + enum logMode { lmInfo, lmWarn, lmError, lmExcept, lmIn, lmOut }; Logger(std::string const &prefix) : prefix_(prefix) {} diff --git a/srcs/server/Logger.cpp b/srcs/server/Logger.cpp index cd3ed79..20c6d09 100644 --- a/srcs/server/Logger.cpp +++ b/srcs/server/Logger.cpp @@ -30,6 +30,14 @@ void Logger::logMsg(std::string msg, logMode const lm) const { msg.insert(0, " [EXCEPT]: "); *os << CMgnta; break; + case Logger::lmIn: + msg.insert(0, " [INPUT ]: "); + *os << CWhite; + break; + case Logger::lmOut: + msg.insert(0, " [OUTPUT]: "); + *os << CBlue; + break; } *os << nowStr() << " " << prefix_ << msg << CReset << std::endl; } diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 4b5b2f2..2dfa0c4 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -134,6 +134,13 @@ void Server::processClientSockets_() { bool msgTooLong = false; cmdLine = sockets_.getCRLFLineFromBuffer( pfd.fd, buffHasMoreCmds, msgTooLong); + + { + std::ostringstream oss; + oss << "RecvMsg event:\nFrom FD " << pfd.fd << ": " + << cmdLine; + logger_.logMsg(oss.str(), Logger::lmIn); + } if (msgTooLong) cmds_.inputTooLong(*this, pfd.fd); else if (!cmdLine.empty()) { @@ -295,12 +302,13 @@ void Server::run() { NOTE: all send should go though server::sendMsg */ void Server::sendMsg(int const fd, std::string const &msg) { + { + std::ostringstream oss; + oss << "SendMsg event:\nSendto FD " << fd << ": " + << msg.substr(0, msg.length() - 2); + logger_.logMsg(oss.str(), Logger::lmOut); + } if (!sockets_.sendMsg(fd, msg)) { sockets_.closeSocket(fd); -#if NETCATDBG - std::ostringstream oss; - oss << "SendMsg event:\nSendto FD " << fd << " failed."; - logger_.logMsg(oss.str(), Logger::lmError); -#endif } } diff --git a/srcs/server/Socket.cpp b/srcs/server/Socket.cpp index 1b68c31..31b8cc0 100644 --- a/srcs/server/Socket.cpp +++ b/srcs/server/Socket.cpp @@ -71,9 +71,13 @@ std::string Socket::getCRLFLineFromBuffer(int const fd, bool &buffHasMoreCmds, } size_t Socket::bufferLengthAndSanityCheck_(int const fd, bool &msgTooLong) { - Socket::SocketIOData &sIOD = sockIODataStore_.at(fd); - std::string &rdBuffer = sIOD.readBuffer; - size_t lfcrPos = rdBuffer.find("\r\n"); + SocketIOData *sIODptr = getSockIOData(fd); + if (!sIODptr) + return std::string::npos; + + SocketIOData &sIOD = *sIODptr; + std::string &rdBuffer = sIOD.readBuffer; + size_t lfcrPos = rdBuffer.find("\r\n"); if (!sIOD.discardMode) { if ((rdBuffer.length() < MAXMSGLEN) || @@ -164,10 +168,11 @@ Wrap in try catch to remove this redundancy or make socketClosed return a pointer instead */ void Socket::closeSocket(int const fd) { - if (socketClosed(fd)) + SocketIOData *sIODptr = getSockIOData(fd); + if (!sIODptr) return; - SocketIOData &sIOD = sockIODataStore_.at(fd); // throws + SocketIOData &sIOD = *sIODptr; std::ostringstream oss; if (sIOD.isSrvSocket) { oss << "Closing server socket (" From b664ab85de086de754f7babdb63d9654dbe667af Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 16 Sep 2026 13:45:55 +0200 Subject: [PATCH 309/349] feat: apply suggestions from PR review --- includes/Socket.hpp | 16 ++-------------- srcs/server/Server.cpp | 21 +++++++++++---------- srcs/server/Socket.cpp | 17 +++++++++++++++-- 3 files changed, 28 insertions(+), 26 deletions(-) diff --git a/includes/Socket.hpp b/includes/Socket.hpp index f164ac7..8a7258d 100644 --- a/includes/Socket.hpp +++ b/includes/Socket.hpp @@ -45,18 +45,6 @@ class Socket { bool sendMsg(int const fd, std::string const &msg); void closeSocket(int const fd); - SocketIOData *getSockIOData(int const fd) { - std::map::iterator it = sockIODataStore_.find(fd); - if (it == sockIODataStore_.end()) - return NULL; - return &(it->second); - } - - bool isServerSocket(int const fd) const { - SocketIOData const *const sIODptr = - const_cast(this)->getSockIOData(fd); - if (sIODptr) - return sIODptr->isSrvSocket; - return false; - }; + SocketIOData *getSockIOData(int const fd); + bool isServerSocket(int const fd); }; diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 2dfa0c4..f6805f5 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -144,20 +144,21 @@ void Server::processClientSockets_() { if (msgTooLong) cmds_.inputTooLong(*this, pfd.fd); else if (!cmdLine.empty()) { - // returns true if connection is to be closed + bool closeConnection = false; Client *client = clients_.find(pfd.fd); if (!client) { - std::ostringstream oss( - "Internal error: No client data found for fd:"); - oss << pfd.fd; + std::ostringstream oss; + oss << "Internal error: No client data found for " + "fd:" + << pfd.fd; log_(oss.str(), Logger::lmError); - // close socket alltogether here?"No client data for fd" - // There's not much chance the client data will reappear later... - // Can't even send Closing message without client ptr - // sockets_.closeSocket(pfd.fd); - break; + closeConnection = true; + } else { + // returns true if connection is to be closed + closeConnection = + cmds_.run(*this, *client, cmdLine); } - if (cmds_.run(*this, *client, cmdLine)) { + if (closeConnection) { sockets_.closeSocket(pfd.fd); break; } diff --git a/srcs/server/Socket.cpp b/srcs/server/Socket.cpp index 31b8cc0..d03236e 100644 --- a/srcs/server/Socket.cpp +++ b/srcs/server/Socket.cpp @@ -62,8 +62,7 @@ std::string Socket::getCRLFLineFromBuffer(int const fd, bool &buffHasMoreCmds, if (msgTooLong || lfcrPos == std::string::npos) return ""; - std::string cmdLine; - cmdLine = sIOD.readBuffer.substr(0, lfcrPos); + std::string cmdLine = sIOD.readBuffer.substr(0, lfcrPos); sIOD.recvErrorCount = 0; sIOD.readBuffer = sIOD.readBuffer.substr(lfcrPos + 2); buffHasMoreCmds = (sIOD.readBuffer.find("\r\n") != std::string::npos); @@ -185,3 +184,17 @@ void Socket::closeSocket(int const fd) { close(fd); sockIODataStore_.erase(fd); } + +Socket::SocketIOData *Socket::getSockIOData(int const fd) { + std::map::iterator it = sockIODataStore_.find(fd); + if (it == sockIODataStore_.end()) + return NULL; + return &(it->second); +} + +bool Socket::isServerSocket(int const fd) { + SocketIOData const *const sIODptr = getSockIOData(fd); + if (sIODptr) + return sIODptr->isSrvSocket; + return false; +} \ No newline at end of file From 9d0284cd5dad078a0feb02d848c116dcea5cd1c9 Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 16 Sep 2026 13:57:13 +0200 Subject: [PATCH 310/349] fix: make it compile --- srcs/server/Socket.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/srcs/server/Socket.cpp b/srcs/server/Socket.cpp index d03236e..328836d 100644 --- a/srcs/server/Socket.cpp +++ b/srcs/server/Socket.cpp @@ -118,7 +118,6 @@ bool Socket::sendMsg(int const fd, std::string const &msg) { return false; SocketIOData &sIOD = *sIODptr; - ssize_t bytesSent = -1; bool success = true; if (msg.length() > sIOD.writeBuffer.max_size() - sIOD.writeBuffer.length()) @@ -135,7 +134,6 @@ bool Socket::sendMsg(int const fd, std::string const &msg) { sIOD.sendErrorCount = 0; if (static_cast(bytesSent) < sIOD.writeBuffer.length()) { -#if NETCATDBG std::ostringstream oss; oss << "SendMsg event:\n" << " WriteBuffer: '" << sIOD.writeBuffer << "':\n"; @@ -156,9 +154,9 @@ bool Socket::sendMsg(int const fd, std::string const &msg) { sIOD.sendErrorCount = 0; if (static_cast(bytesSent) < sIOD.writeBuffer.length()) sIOD.writeBuffer.erase(0, bytesSent); - } else - sIOD.writeBuffer.clear(); - return true; + +else sIOD.writeBuffer.clear(); +return true; } /* From f56a43555dd93712a22dbe694284b2d4e308ceb5 Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 16 Sep 2026 15:09:18 +0200 Subject: [PATCH 311/349] feat: add preproc define switch for dbg logging --- includes/Logger.hpp | 2 ++ srcs/server/Logger.cpp | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/includes/Logger.hpp b/includes/Logger.hpp index 4844721..32b6c75 100644 --- a/includes/Logger.hpp +++ b/includes/Logger.hpp @@ -3,6 +3,8 @@ #include // for localtime, strftime, time, time_t #include // for basic_string, string +#define DBGLOG + class Logger { std::string prefix_; diff --git a/srcs/server/Logger.cpp b/srcs/server/Logger.cpp index 20c6d09..1c2b879 100644 --- a/srcs/server/Logger.cpp +++ b/srcs/server/Logger.cpp @@ -9,6 +9,11 @@ to stdout/stderr respectively. */ void Logger::logMsg(std::string msg, logMode const lm) const { +#ifdef DBGLOG + if (lm == Logger::lmIn || lm == Logger::lmOut) + return; +#endif + std::ostream *os = &std::cout; if (lm == Logger::lmError || lm == Logger::lmExcept) os = &std::cerr; From 6a2e26bcded21ef800e37db7f0786181a1e81128 Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 16 Sep 2026 15:09:47 +0200 Subject: [PATCH 312/349] rfa: rename to MAX_CONNECTIONQUEUE for more clarity --- includes/Server.hpp | 2 +- srcs/server/Server.cpp | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/includes/Server.hpp b/includes/Server.hpp index ce66f81..824cdbd 100644 --- a/includes/Server.hpp +++ b/includes/Server.hpp @@ -13,7 +13,7 @@ #define SERVER_NAME "irc.local" #define SERVER_VERSION "1.0" -#define MAX_CONNECTIONS 512 +#define MAX_CONNECTIONQUEUE 512 class Server { private: diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index f6805f5..b6ac076 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -95,7 +95,7 @@ int Server::createServerSocket_(int const domain) { srvHelp::bindServerSocket(socketFd, domain, port_); - if (listen(socketFd, MAX_CONNECTIONS) < 0) + if (listen(socketFd, MAX_CONNECTIONQUEUE) < 0) throw std::runtime_error("Error while listening on socket for " + srvHelp::getAdressFamilyStr(domain) + " !"); @@ -300,7 +300,7 @@ void Server::run() { } /* -NOTE: all send should go though server::sendMsg + NOTE: all send should go though server::sendMsg */ void Server::sendMsg(int const fd, std::string const &msg) { { @@ -309,7 +309,6 @@ void Server::sendMsg(int const fd, std::string const &msg) { << msg.substr(0, msg.length() - 2); logger_.logMsg(oss.str(), Logger::lmOut); } - if (!sockets_.sendMsg(fd, msg)) { + if (!sockets_.sendMsg(fd, msg)) sockets_.closeSocket(fd); - } } From 4890bd21f9b0509aa0949a598b4d1c7287437f62 Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 16 Sep 2026 15:11:35 +0200 Subject: [PATCH 313/349] rfa: * apply/cleanup changes from PR * remove details logging, use DBGLOG for detailed insight --- srcs/server/Socket.cpp | 34 ++++------------------------------ 1 file changed, 4 insertions(+), 30 deletions(-) diff --git a/srcs/server/Socket.cpp b/srcs/server/Socket.cpp index 328836d..7cc5a37 100644 --- a/srcs/server/Socket.cpp +++ b/srcs/server/Socket.cpp @@ -118,52 +118,26 @@ bool Socket::sendMsg(int const fd, std::string const &msg) { return false; SocketIOData &sIOD = *sIODptr; - bool success = true; - if (msg.length() > sIOD.writeBuffer.max_size() - sIOD.writeBuffer.length()) return false; sIOD.writeBuffer += msg; ssize_t bytesSent = send(fd, sIOD.writeBuffer.c_str(), sIOD.writeBuffer.length(), MSG_NOSIGNAL); - if (bytesSent == -1) { + if (bytesSent <= 0) { ++sIOD.sendErrorCount; if (sIOD.sendErrorCount >= SOCKETERRORLIMIT) return false; return true; } - sIOD.sendErrorCount = 0; - if (static_cast(bytesSent) < sIOD.writeBuffer.length()) { - std::ostringstream oss; - oss << "SendMsg event:\n" - << " WriteBuffer: '" << sIOD.writeBuffer << "':\n"; - if (!success || bytesSent < 0) { - oss << " send to FD " << fd << " failed."; - logger_.logMsg(oss.str(), Logger::lmError); - } else if (static_cast(bytesSent) < sIOD.writeBuffer.length()) { - oss << " partly (" << bytesSent << "/" << sIOD.writeBuffer.length() - << ") " << sIOD.writeBuffer.substr(0, bytesSent) - << " sent to FD: " << fd; - logger_.logMsg(oss.str(), Logger::lmInfo); - } - } - - if (!success) - return false; - sIOD.sendErrorCount = 0; if (static_cast(bytesSent) < sIOD.writeBuffer.length()) sIOD.writeBuffer.erase(0, bytesSent); - -else sIOD.writeBuffer.clear(); -return true; + else + sIOD.writeBuffer.clear(); + return true; } -/* -Performance note: 2 searches of sockIODataStore. -Wrap in try catch to remove this redundancy -or make socketClosed return a pointer instead -*/ void Socket::closeSocket(int const fd) { SocketIOData *sIODptr = getSockIOData(fd); if (!sIODptr) From fbb0be11f97ee5d7d33bd8f64e5ec08061d33ec3 Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 16 Sep 2026 15:29:08 +0200 Subject: [PATCH 314/349] fix: include updates from latest poll-loop4 --- srcs/server/Server.cpp | 35 +++++++++++++++++++++++++++++------ srcs/server/Socket.cpp | 11 +---------- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index a3dcabf..32737e8 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -125,18 +125,41 @@ void Server::processClientSockets_() { continue; bool buffHasMoreCmds = false; do { - std::string cmdLine = - sockets_.getCRLFLineFromBuffer(pfd.fd, buffHasMoreCmds); + + bool msgTooLong = false; + std::string cmdLine = sockets_.getCRLFLineFromBuffer( + pfd.fd, buffHasMoreCmds, msgTooLong); + + { + std::ostringstream oss; + oss << "RecvMsg event:\nFrom FD " << pfd.fd << ": " << cmdLine; + logger_.logMsg(oss.str(), Logger::lmIn); + } + if (msgTooLong) { + cmds_.inputTooLong(*this, pfd.fd); + continue; + } if (cmdLine.empty()) continue; + Client *client = clients_.find(pfd.fd); - if (!client) - break; - // returns true if connection is to be closed - if (cmds_.run(*this, *client, cmdLine)) { + bool closeConnection = false; + if (!client) { + std::ostringstream oss; + oss << "Internal error: No client data found for " + "fd:" + << pfd.fd; + log_(oss.str(), Logger::lmError); + closeConnection = true; + } else { + // returns true if connection is to be closed + closeConnection = cmds_.run(*this, *client, cmdLine); + } + if (closeConnection) { sockets_.closeSocket(pfd.fd); break; } + } while (buffHasMoreCmds); } removeClosedSockets_(); diff --git a/srcs/server/Socket.cpp b/srcs/server/Socket.cpp index 398d8b2..7cc5a37 100644 --- a/srcs/server/Socket.cpp +++ b/srcs/server/Socket.cpp @@ -78,10 +78,6 @@ size_t Socket::bufferLengthAndSanityCheck_(int const fd, bool &msgTooLong) { std::string &rdBuffer = sIOD.readBuffer; size_t lfcrPos = rdBuffer.find("\r\n"); - if (!sIOD.discardMode) { - if ((rdBuffer.length() < MAXMSGLEN) || - (lfcrPos != std::string::npos && lfcrPos <= MAXMSGLEN - 2)) - return lfcrPos; if (!sIOD.discardMode) { if ((rdBuffer.length() < MAXMSGLEN) || (lfcrPos != std::string::npos && lfcrPos <= MAXMSGLEN - 2)) @@ -137,16 +133,11 @@ bool Socket::sendMsg(int const fd, std::string const &msg) { sIOD.sendErrorCount = 0; if (static_cast(bytesSent) < sIOD.writeBuffer.length()) sIOD.writeBuffer.erase(0, bytesSent); - } else + else sIOD.writeBuffer.clear(); return true; } -/* -Performance note: 2 searches of sockIODataStore. -Wrap in try catch to remove this redundancy -or make socketClosed return a pointer instead -*/ void Socket::closeSocket(int const fd) { SocketIOData *sIODptr = getSockIOData(fd); if (!sIODptr) From d4c7808cc252c99a14d1f6e19ba742040e2292ff Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 16 Sep 2026 15:36:08 +0200 Subject: [PATCH 315/349] feat: remove try catch --- srcs/server/Server.cpp | 87 +++++++++++++++++------------------------- 1 file changed, 34 insertions(+), 53 deletions(-) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index b6ac076..65804e4 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -124,61 +124,42 @@ void Server::processClientSockets_() { pollfd &pfd = pollFds_.at(idx); if (sockets_.checkSocket4Recv(pfd) && (pfd.revents & POLLIN)) { - std::string cmdLine; - bool buffHasMoreCmds = false; + bool buffHasMoreCmds = false; do { -#define ERRMSG2CLIENTBYEXCEPT -#ifdef ERRMSG2CLIENTBYEXCEPT - try { -#endif - bool msgTooLong = false; - cmdLine = sockets_.getCRLFLineFromBuffer( - pfd.fd, buffHasMoreCmds, msgTooLong); - - { - std::ostringstream oss; - oss << "RecvMsg event:\nFrom FD " << pfd.fd << ": " - << cmdLine; - logger_.logMsg(oss.str(), Logger::lmIn); - } - if (msgTooLong) - cmds_.inputTooLong(*this, pfd.fd); - else if (!cmdLine.empty()) { - bool closeConnection = false; - Client *client = clients_.find(pfd.fd); - if (!client) { - std::ostringstream oss; - oss << "Internal error: No client data found for " - "fd:" - << pfd.fd; - log_(oss.str(), Logger::lmError); - closeConnection = true; - } else { - // returns true if connection is to be closed - closeConnection = - cmds_.run(*this, *client, cmdLine); - } - if (closeConnection) { - sockets_.closeSocket(pfd.fd); - break; - } - } -#ifdef ERRMSG2CLIENTBYEXCEPT - } catch (Error const &e) { - if (e.hasReply()) { - Client *client = clients_.find(pfd.fd); - std::string nickName = - (client ? client->getNick() : "dbgDummyNick"); - sendMsg(pfd.fd, - Reply::build(serverName_, nickName, e.reply())); - } else { - std::string tmp( - "Error while trying to get/execute cmd:\n"); - tmp.append("'" + cmdLine + "'\n" + e.what()); - log_(tmp, Logger::lmExcept); - } + bool msgTooLong = false; + std::string cmdLine = sockets_.getCRLFLineFromBuffer( + pfd.fd, buffHasMoreCmds, msgTooLong); + { + std::ostringstream oss; + oss << "RecvMsg event:\nFrom FD " << pfd.fd << ": " + << cmdLine; + logger_.logMsg(oss.str(), Logger::lmIn); + } + + if (msgTooLong) { + cmds_.inputTooLong(*this, pfd.fd); + continue; + } + if (cmdLine.empty()) + continue; + + bool closeConnection = false; + Client *client = clients_.find(pfd.fd); + if (!client) { + std::ostringstream oss; + oss << "Internal error: No client data found for " + "fd:" + << pfd.fd; + log_(oss.str(), Logger::lmError); + closeConnection = true; + } else { + // returns true if connection is to be closed + closeConnection = cmds_.run(*this, *client, cmdLine); + } + if (closeConnection) { + sockets_.closeSocket(pfd.fd); + break; } -#endif } while (buffHasMoreCmds); } } From 010f1466a5d070bc336446a8c2eec2a39ca66ae6 Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 16 Sep 2026 15:42:47 +0200 Subject: [PATCH 316/349] fix: fix dbglog wrong logic --- srcs/server/Logger.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/srcs/server/Logger.cpp b/srcs/server/Logger.cpp index 1c2b879..f381767 100644 --- a/srcs/server/Logger.cpp +++ b/srcs/server/Logger.cpp @@ -9,7 +9,7 @@ to stdout/stderr respectively. */ void Logger::logMsg(std::string msg, logMode const lm) const { -#ifdef DBGLOG +#ifndef DBGLOG if (lm == Logger::lmIn || lm == Logger::lmOut) return; #endif From 53afb0b3189b0cd12e25745d71536f0218f337ab Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 16 Sep 2026 15:43:19 +0200 Subject: [PATCH 317/349] fix: DBGLOG wrong logic --- srcs/server/Logger.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/srcs/server/Logger.cpp b/srcs/server/Logger.cpp index 1c2b879..f381767 100644 --- a/srcs/server/Logger.cpp +++ b/srcs/server/Logger.cpp @@ -9,7 +9,7 @@ to stdout/stderr respectively. */ void Logger::logMsg(std::string msg, logMode const lm) const { -#ifdef DBGLOG +#ifndef DBGLOG if (lm == Logger::lmIn || lm == Logger::lmOut) return; #endif From fb916eda16bbc74f77d677b218c0ce8a1d40f671 Mon Sep 17 00:00:00 2001 From: Rapha Date: Wed, 16 Sep 2026 15:52:52 +0200 Subject: [PATCH 318/349] fix: include proposed changes from PR --- srcs/server/Server.cpp | 79 +++++++++++++++++++++--------------------- 1 file changed, 40 insertions(+), 39 deletions(-) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 65804e4..4143231 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -123,45 +123,46 @@ void Server::processClientSockets_() { ++idx) { pollfd &pfd = pollFds_.at(idx); - if (sockets_.checkSocket4Recv(pfd) && (pfd.revents & POLLIN)) { - bool buffHasMoreCmds = false; - do { - bool msgTooLong = false; - std::string cmdLine = sockets_.getCRLFLineFromBuffer( - pfd.fd, buffHasMoreCmds, msgTooLong); - { - std::ostringstream oss; - oss << "RecvMsg event:\nFrom FD " << pfd.fd << ": " - << cmdLine; - logger_.logMsg(oss.str(), Logger::lmIn); - } - - if (msgTooLong) { - cmds_.inputTooLong(*this, pfd.fd); - continue; - } - if (cmdLine.empty()) - continue; - - bool closeConnection = false; - Client *client = clients_.find(pfd.fd); - if (!client) { - std::ostringstream oss; - oss << "Internal error: No client data found for " - "fd:" - << pfd.fd; - log_(oss.str(), Logger::lmError); - closeConnection = true; - } else { - // returns true if connection is to be closed - closeConnection = cmds_.run(*this, *client, cmdLine); - } - if (closeConnection) { - sockets_.closeSocket(pfd.fd); - break; - } - } while (buffHasMoreCmds); - } + if (!(sockets_.checkSocket4Recv(pfd) && (pfd.revents & POLLIN))) + continue; + bool buffHasMoreCmds = false; + do { + + bool msgTooLong = false; + std::string cmdLine = sockets_.getCRLFLineFromBuffer( + pfd.fd, buffHasMoreCmds, msgTooLong); + + { + std::ostringstream oss; + oss << "RecvMsg event:\nFrom FD " << pfd.fd << ": " << cmdLine; + logger_.logMsg(oss.str(), Logger::lmIn); + } + + if (msgTooLong) { + cmds_.inputTooLong(*this, pfd.fd); + continue; + } + if (cmdLine.empty()) + continue; + + Client *client = clients_.find(pfd.fd); + bool closeConnection = false; + if (!client) { + std::ostringstream oss; + oss << "Internal error: No client data found for " + "fd:" + << pfd.fd; + log_(oss.str(), Logger::lmError); + closeConnection = true; + } else { + // returns true if connection is to be closed + closeConnection = cmds_.run(*this, *client, cmdLine); + } + if (closeConnection) { + sockets_.closeSocket(pfd.fd); + break; + } + } while (buffHasMoreCmds); } removeClosedSockets_(); } From a91f7c1a0901fb4351dff7789d0e7be325a4dbf9 Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 17 Sep 2026 12:00:53 +0200 Subject: [PATCH 319/349] fix: update based on PR review: buffer would not have been consumed correctly in some cases --- includes/CommandProcessor.hpp | 4 ++-- srcs/server/Server.cpp | 12 ++++-------- srcs/server/Socket.cpp | 16 +++++++++++----- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/includes/CommandProcessor.hpp b/includes/CommandProcessor.hpp index 2c05067..1f32674 100644 --- a/includes/CommandProcessor.hpp +++ b/includes/CommandProcessor.hpp @@ -51,9 +51,9 @@ class CommandProcessor { bool run(Server &server, Client &client, const std::string &line); void add(const std::string &name, const Command &cmd); - void inputTooLong(Server &srv, int const fd) { + void inputTooLong(Server &srv, Client &client) { (void)srv; - (void)fd; + (void)client; // tbd. }; }; diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 4143231..7459f69 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -127,7 +127,6 @@ void Server::processClientSockets_() { continue; bool buffHasMoreCmds = false; do { - bool msgTooLong = false; std::string cmdLine = sockets_.getCRLFLineFromBuffer( pfd.fd, buffHasMoreCmds, msgTooLong); @@ -138,13 +137,6 @@ void Server::processClientSockets_() { logger_.logMsg(oss.str(), Logger::lmIn); } - if (msgTooLong) { - cmds_.inputTooLong(*this, pfd.fd); - continue; - } - if (cmdLine.empty()) - continue; - Client *client = clients_.find(pfd.fd); bool closeConnection = false; if (!client) { @@ -155,6 +147,10 @@ void Server::processClientSockets_() { log_(oss.str(), Logger::lmError); closeConnection = true; } else { + if (msgTooLong) + cmds_.inputTooLong(*this, *client); + if (cmdLine.empty()) + continue; // returns true if connection is to be closed closeConnection = cmds_.run(*this, *client, cmdLine); } diff --git a/srcs/server/Socket.cpp b/srcs/server/Socket.cpp index 7cc5a37..93d48f5 100644 --- a/srcs/server/Socket.cpp +++ b/srcs/server/Socket.cpp @@ -59,7 +59,7 @@ std::string Socket::getCRLFLineFromBuffer(int const fd, bool &buffHasMoreCmds, return ""; } size_t lfcrPos = bufferLengthAndSanityCheck_(fd, msgTooLong); - if (msgTooLong || lfcrPos == std::string::npos) + if (lfcrPos == std::string::npos) return ""; std::string cmdLine = sIOD.readBuffer.substr(0, lfcrPos); @@ -84,24 +84,30 @@ size_t Socket::bufferLengthAndSanityCheck_(int const fd, bool &msgTooLong) { return lfcrPos; sIOD.discardMode = true; msgTooLong = true; + ++sIOD.recvErrorCount; } + // discard mode - if (++sIOD.recvErrorCount >= SOCKETERRORLIMIT) { + while (lfcrPos != std::string::npos && lfcrPos > MAXMSGLEN - 2) { + ++sIOD.recvErrorCount; + rdBuffer.erase(0, lfcrPos + 2); + lfcrPos = rdBuffer.find("\r\n"); + } + if (sIOD.recvErrorCount >= SOCKETERRORLIMIT) { closeSocket(fd); return std::string::npos; } if (lfcrPos != std::string::npos) { - rdBuffer.erase(0, lfcrPos + 2); sIOD.discardMode = false; sIOD.recvErrorCount = 0; - return rdBuffer.find("\r\n"); + return lfcrPos; } // lfcrPos == std::string::npos if (!rdBuffer.empty() && '\r' == *rdBuffer.rbegin()) rdBuffer = "\r"; else rdBuffer.clear(); - return lfcrPos; + return std::string::npos; } /* From 11425f8513651c294f6cf8bf9b4a625cba02a27d Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 17 Sep 2026 12:06:03 +0200 Subject: [PATCH 320/349] feat: allign with feat/pl4 --- srcs/server/Server.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/srcs/server/Server.cpp b/srcs/server/Server.cpp index 5885763..95e1c45 100644 --- a/srcs/server/Server.cpp +++ b/srcs/server/Server.cpp @@ -1,9 +1,12 @@ #include "Server.hpp" #include "Client.hpp" // for Client +#include "Errors.hpp" // for Error +#include "Reply.hpp" // for build #include "ServerHelper.hpp" // for getAdressFamilyStr, composeAddrfamAddrP... #include "SignalHandler.hpp" // for SignalHandler + #include // for find_if #include // for exception #include // for IPPROTO_IPV6, IPV6_V6ONLY From 689311e899ccd42682b7cfb8377e774b498838de Mon Sep 17 00:00:00 2001 From: Rapha Date: Thu, 17 Sep 2026 17:07:14 +0200 Subject: [PATCH 321/349] feat: dummy implementation of inputTooLong Msg for testing --- includes/CommandProcessor.hpp | 6 +----- srcs/commands/CommandProcessor.cpp | 8 ++++++++ 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/includes/CommandProcessor.hpp b/includes/CommandProcessor.hpp index 1f32674..84f2768 100644 --- a/includes/CommandProcessor.hpp +++ b/includes/CommandProcessor.hpp @@ -51,9 +51,5 @@ class CommandProcessor { bool run(Server &server, Client &client, const std::string &line); void add(const std::string &name, const Command &cmd); - void inputTooLong(Server &srv, Client &client) { - (void)srv; - (void)client; - // tbd. - }; + void inputTooLong(Server &srv, Client &client); }; diff --git a/srcs/commands/CommandProcessor.cpp b/srcs/commands/CommandProcessor.cpp index 31555c2..f7c8126 100644 --- a/srcs/commands/CommandProcessor.cpp +++ b/srcs/commands/CommandProcessor.cpp @@ -77,3 +77,11 @@ bool CommandProcessor::run(Server &server, Client &client, void CommandProcessor::add(const std::string &name, const Command &cmd) { commands_.insert(std::make_pair(name, cmd)); } + +void CommandProcessor::inputTooLong(Server &srv, Client &client) { + (void)srv; + std::ostringstream oss; + oss << "Hello fd:" << client.getFd() << " : msg too long.\r\n"; + client.send(oss.str()); + // tbd. +} \ No newline at end of file From a3610e8fc932db83d0ff4cea66193a642032f0fa Mon Sep 17 00:00:00 2001 From: ArchCipher Date: Sat, 5 Sep 2026 21:48:20 +0200 Subject: [PATCH 322/349] fill readme --- README.md | 113 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 85 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 07d21ed..c33f3db 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,21 @@ *This project has been created as part of the 42 curriculum by blnunez-, kmurugan, rhaas.* -# ft_irc - IRC server +#