From a3610e8fc932db83d0ff4cea66193a642032f0fa Mon Sep 17 00:00:00 2001 From: ArchCipher Date: Sat, 5 Sep 2026 21:48:20 +0200 Subject: [PATCH 01/13] 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 4c2224a32992275638a16753a05d95f2a3e7fa40 Mon Sep 17 00:00:00 2001 From: ArchCipher Date: Thu, 17 Sep 2026 18:27:30 +0200 Subject: [PATCH 02/13] 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 2bd3389e3bd0a42c42e875fc5857227cd1f19aa8 Mon Sep 17 00:00:00 2001 From: ArchCipher Date: Sat, 5 Sep 2026 21:48:20 +0200 Subject: [PATCH 03/13] 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 04/13] 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 05/13] 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 d1b2715acf281b9c9e9b75e164e39f42256ddbc2 Mon Sep 17 00:00:00 2001 From: gitraphha <38692288+gitraphha@users.noreply.github.com> Date: Fri, 18 Sep 2026 14:57:16 +0200 Subject: [PATCH 06/13] Update README.md Co-authored-by: wenceslao <117309234+numorb@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 61c76c2..b217810 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 From e3371c8514f7921248ef163b648653bc50f5202e Mon Sep 17 00:00:00 2001 From: gitraphha <38692288+gitraphha@users.noreply.github.com> Date: Fri, 18 Sep 2026 14:57:27 +0200 Subject: [PATCH 07/13] Update README.md Co-authored-by: wenceslao <117309234+numorb@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b217810..ae0d876 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ Please note that you may need system admin permissions to use privileged ports ( ### Registration - `PASS ` — authenticate during registration. - `NICK ` — set/change the nick. -- `USER ` — complete registration credentials. +- `USER ` — complete registration credentials. ### Channels - `JOIN <#channel> []` — join (or create) a channel, optional key for `+k`. From 2aa758dc47ff83943e9d9c82b4d82629f922cc0b Mon Sep 17 00:00:00 2001 From: wenceslao <117309234+numorb@users.noreply.github.com> Date: Fri, 18 Sep 2026 15:00:16 +0200 Subject: [PATCH 08/13] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ae0d876..c02082d 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ make bonus && ./ircserv_bonus ```sh # connect with netcat and register -nc +nc -C # -C sends CRLF line endings, which the server requires (on macOS use -c) PASS NICK USER 0 * : From c934b4bda075883a819a7a520bfecbdeee6f5637 Mon Sep 17 00:00:00 2001 From: Rapha Date: Fri, 18 Sep 2026 15:02:40 +0200 Subject: [PATCH 09/13] feat: remove --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c02082d..61fe04f 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ make bonus && ./ircserv_bonus ```sh # connect with netcat and register -nc -C # -C sends CRLF line endings, which the server requires (on macOS use -c) +nc -C # -C sends CRLF line endings (on macOS use -c) PASS NICK USER 0 * : From c0af8e31d4bd55d5275d738d8898ada26d3c0e6e Mon Sep 17 00:00:00 2001 From: ArchCipher Date: Fri, 18 Sep 2026 16:05:31 +0200 Subject: [PATCH 10/13] add challenges + fix kick --- README.md | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 61fe04f..af9d13a 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ Please note that you may need system admin permissions to use privileged ports ( ### 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). +- `KICK <#channel> []` — remove a member (the issuer must outrank the target). - `INVITE <#channel>` — invite a user (may require operator rank). - `TOPIC <#channel> []` — view/set the topic (may require operator rank). - `NAMES <#channel>` — list current members. @@ -137,7 +137,8 @@ NOTE: Review clang-tidy results and fix only what makes sense for this project. **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. +**Solution:** A `Reply` module maps each numeric code to its required parameter layout and trailing text, and an `Error` carries a pre-built `Reply::Message`.`CommandProcessor::run` validates the command name, registration state, +operator requirement and parameter count up front. On the first bad condition it sends the error to the offending client via `sendError_` and stops that command line without throwing. Lower-level helpers (`Channel`, `ChannelManager`, `ClientManager`) still throw `Error`, and each handler wraps its calls in a small local `try/catch (Error)` that forwards `e.reply()` to the client. Every failure therefore produces exactly one well-formed `:server :\r\n`, and no exception escapes into the event loop. ### Usage of `poll` and buffers @@ -145,12 +146,24 @@ NOTE: Review clang-tidy results and fix only what makes sense for this project. **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. +### Single-Author Event Loop + +**Challenge:** One person owned the whole loop, so every change touched it all at once and nothing could be changed in isolation. `Server` and `Socket` were not separated into distinct jobs, and the work arrived as one PR with 30-50 commits, making every review unsafe. + +**Solution:** If redone: separate `Socket` (fd, buffer, limits) from `Server` (pollfd vector, close decisions) from day one, and ship changes as small, one-behaviour PRs. + ### 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. +### Over-Length Input Handling + +**Challenge:** Not disconnecting on the first over-length message forced a discard-mode state machine, a per-client error counter and deferred-close markers into the loop — where the hard-to-find bugs lived. + +**Solution:** If redone: close on the first over-length line. That deletes the discard state, the error counter and the close markers. The loop becomes just "lines in, close on error". + ### 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. From 67643bbd5dd7e2d663e26be6cda10467f3c61959 Mon Sep 17 00:00:00 2001 From: ArchCipher Date: Sat, 19 Sep 2026 10:51:06 +0200 Subject: [PATCH 11/13] feat: add detailed dcc send commands --- README.md | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index af9d13a..921765f 100644 --- a/README.md +++ b/README.md @@ -89,8 +89,8 @@ Built with `make bonus` → `./ircserv_bonus `. | Abbr.| Meaning | |------|---------------------------| -| CTCP | Client-to-client protocol | -| DDC | Direct Client-to-Client | +| CTCP | Client-To-Client Protocol | +| DCC | 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`. @@ -107,8 +107,59 @@ CTCP queries (`\x01VERSION\x01`, `\x01PING \x01`) sent to the bot via `PR ### File transfer (DCC SEND) +#### Using irssi + +Sender: +```sh +/dcc send +``` + +Receiver receives: + +`DCC SEND from [ port ]: ` + +Receiver: +```sh +# set filepath +/set dcc_download_path + +# receive file +/dcc get +``` + +#### Using nc + +Sender: +```sh +PRIVMSG :\x01DCC SEND \x01 +``` + +Receiver receives: + +`:sender!sender@127.0.0.1 PRIVMSG receiver :\x01DCC SEND \x01` + +Receiver in another terminal must open nc to receive: +```sh +nc > +``` + +#### Using the BOT + `!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. +Sender: +```sh +/msg bot !send +``` + +Receiver: +```sh +/msg bot !accept + +# Receiver in another terminal must open nc to receive: +nc 127.0.0.1 > +``` + --- ## Clang tool commands From 916f9b7bc67dd2e4b40651d110f37a9c68d3e548 Mon Sep 17 00:00:00 2001 From: ArchCipher Date: Sat, 19 Sep 2026 15:24:16 +0200 Subject: [PATCH 12/13] feat: architecture mermaid --- README.md | 184 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 184 insertions(+) diff --git a/README.md b/README.md index 921765f..1071e55 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,190 @@ Please note that you may need system admin permissions to use privileged ports ( --- +## Architecture + +### Overview + +```mermaid +flowchart LR + subgraph CLIENT["client connection"] + IRC["irc client"] + DCCP["DCC peer"] + end + + subgraph CONN["connection"] + LISTENER["IRC listener"] + SOCKET["Socket"] + end + + subgraph CORE["server core"] + SERVER["Server"] + CMD["CommandProcessor"] + CM["ClientManager"] + CHM["ChannelManager"] + end + + IRC --> LISTENER --> SOCKET --> SERVER + SERVER --> CMD + SERVER --> CM + SERVER --> CHM + CM --> CHM + DCCP -. "direct peer transfer" .-> SERVER + + class IRC,DCCP,LISTENER,SOCKET,SERVER,CMD,CM,CHM node; +``` + +### Core Server + +```mermaid +flowchart TB + subgraph SRV["Server"] + direction TB + LSTN["IRC listeners\n (bind + listen)"] + ACCEPT["Accept client"] + LOOP["Event loop\n (poll(), non-blocking)"] + SOCK["Socket\n (buffered reads, overlong-line discard)"] + SIG["SignalHandler"] + LOG["Logger"] + end + + subgraph DRIVES["owned and driven by the loop"] + direction TB + CMD["CommandProcessor"] + CM["ClientManager"] + CHM["ChannelManager"] + TM["TransferManager (bonus)"] + end + + LSTN --> ACCEPT --> SOCK --> LOOP + LOOP --> CMD + LOOP --> CM + LOOP --> CHM + LOOP --> TM + + class LSTN,ACCEPT,LOOP,SOCK,SIG,LOG node; + class CMD,CM,CHM,TM node; +``` + +### Client System +```mermaid +flowchart TB + subgraph CORE["server core"] + SRV["Server"] + CMD["CommandProcessor"] + end + + subgraph CLISYS["client system"] + CLI["Client"] + CLM["ClientManager\n (lookup by nick)"] + REG["Registration\n (PASS NICK USER)"] + QUIT["QUIT/disconnect\n (part all channels)"] + end + + SRV -->|"accepted connection"| CLM --> CLI + CMD -->|"commands by nick"| CLM + CLM --> QUIT -. "leave all channels" .-> CHM["ChannelManager"] + CLI --> REG -->|"send welcome message"| CLIENT["client"] + + class SRV,CMD,CLIENT node; + class CLISYS,CLI,CLM,REG,QUIT node; +``` + +### Channel System + +```mermaid +flowchart TB + subgraph CORE["server core"] + SRV["Server"] + CM["ClientManager"] + end + + subgraph CHSYS["channel system"] + CHM["ChannelManager"] + CH["Channel"] + MEM["MemberSet\n (Client pointers, per-member ModeSet)"] + MS["ModeSet"] + MOD["Mode"] + end + + SRV --> CHM --> CH + CM --> |"provides client objects"| CH + CH -->|contains| MEM + CH -->|has| MS + CH -->|uses| MOD + MEM -->|stores per-member| MS + MS -->|uses| MOD + + class SRV,CM node; + class CHSYS,CHM,CH,MEM,MS,MOD node; +``` + +### Command Processing + +```mermaid +flowchart TB + + CI["client input"] + SOCKIN["socket"] + RB["response buffer"] + SO["socket output"] + + subgraph CP["Command Processing"] + PA["command parser"] + VA["command validation"] + H["command handler"] + INV["invalid"] + end + + CI --> SOCKIN --> PA + PA --> VA + VA --> H + VA -.-> INV + H --> RB + INV --> RB + RB --> SO + + class CI,SOCKIN,RB,SO node; + class CP,PA,VA,H,INV node; +``` + +### Bonus + +```mermaid +flowchart TB + PRIVMSG["PRIVMSG"] + NORMAL["Client"] + BOT["DCCBot"] + + subgraph FT["file transfer"] + VAL["Validate filename in files/"] + LSN["Open listener - OS-picked port"] + BUILD["Build DCC SEND CTCP offer"] + OFFER["Send offer to target client"] + ACC["!accept - only the target"] + STREAM["connect + stream - 1 KiB chunks"] + DONE["transfer complete"] + EXPIRE["rejected or 60 s expiry"] + end + + PARSE["Parse CTCP (VERSION / PING)"] + REPLY["NOTICE reply to client"] + OTHER["!help !version !whoami !accept !reject"] + + PRIVMSG -->|"target is bot"| BOT + PRIVMSG -->|"target is client"| NORMAL + BOT -->|"CTCP"| PARSE --> REPLY + BOT -->|"other command"| OTHER + BOT -->|"!send"| VAL --> LSN --> BUILD --> OFFER + OFFER -->|"client accepts"| ACC --> STREAM --> DONE + OFFER -->|"!reject / timeout"| EXPIRE + + class PRIVMSG,NORMAL,BOT,REPLY,OTHER node; + class FT,VAL,LSN,BUILD,OFFER,ACC,STREAM,DONE,EXPIRE node; +``` + +--- + ## IRC Command & Message Reference ### Registration From b86788bf34df5d0709c50066ea8d4b4b20e4c3f4 Mon Sep 17 00:00:00 2001 From: wenceslao Date: Tue, 22 Sep 2026 08:34:24 +0200 Subject: [PATCH 13/13] update readme.md --- README.md | 44 +++++++++++++++++++++++++++++++++----------- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 1071e55..4623dac 100644 --- a/README.md +++ b/README.md @@ -37,9 +37,20 @@ 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 (no spaces) must be supplied.\ Please note that you may need system admin permissions to use privileged ports (usually 1–1023). +### Tests + +```sh +make -C tests # the fixed checks, then the dynamic tester; totals and the report link at the end +make -C tests/checks # one group, or one test by name: make -C tests/checks socket +make -C tests lab # interactive lab: client windows in one terminal, server log, walkthrough +make -C tests/dynamic_tester attach # live monitor of the server, in a second terminal while the tester runs +``` + +The dynamic tester writes `tests/dynamic_tester/artifacts/report.html`. Every command, with its options, is in `tests/HOWTO.txt`. + --- ## Architecture @@ -236,11 +247,11 @@ flowchart TB ### 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 (the issuer must outrank the target). +- `KICK <#channel> []` — remove a member (requires operator rank). - `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. +- `MODE <#channel> [modes]` — view/set channel modes. ### Messaging - `PRIVMSG ` — send a message. The bonus bot answers PRIVMSG addressed to it. @@ -261,7 +272,10 @@ flowchart TB 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). ### Other -- `PING`/`PONG` — keepalive and latency check. +- `QUIT []` — leave the server, members of shared channels see the QUIT and the client gets ERROR :Closing link. +- `PING`/`PONG` — the server answers `PING`, `PONG` is accepted silently (the server sends no `PING`). +- Also implemented: `CAP` (empty capability list, for irssi), `VERSION`. +- Recognised but without effect: `WHO`, `WHOIS`, `OPER`, `RENAME`, and `KILL`/`REHASH` (481, no one can become IRC operator). --- @@ -329,7 +343,7 @@ nc > #### Using the BOT -`!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. +`!send` opens a listener on an OS-picked port and hands the target a CTCP `DCC SEND 127.0.0.1 ` line, so the receiver must be on the same machine as the server. 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). Offers expire after 60 seconds without activity. Offered files must live in a `files/` directory next to the server; filenames containing `/` or `..` are rejected. Sender: ```sh @@ -372,7 +386,7 @@ NOTE: Review clang-tidy results and fix only what makes sense for this project. **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, and an `Error` carries a pre-built `Reply::Message`.`CommandProcessor::run` validates the command name, registration state, +**Solution:** A `Reply` module maps each numeric code to its required parameter layout and trailing text, and an `Error` carries a pre-built `Reply::Message`. `CommandProcessor::run` validates the command name, registration state, operator requirement and parameter count up front. On the first bad condition it sends the error to the offending client via `sendError_` and stops that command line without throwing. Lower-level helpers (`Channel`, `ChannelManager`, `ClientManager`) still throw `Error`, and each handler wraps its calls in a small local `try/catch (Error)` that forwards `e.reply()` to the client. Every failure therefore produces exactly one well-formed `:server :\r\n`, and no exception escapes into the event loop. ### Usage of `poll` and buffers @@ -381,9 +395,17 @@ operator requirement and parameter count up front. On the first bad condition it **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. -### Single-Author Event Loop +On the write side nothing is sent directly: every reply is appended to the client's write buffer and flushed only when `poll` reports `POLLOUT` for that socket. A client that stops reading is therefore never blocking the loop and is not dropped for being slow; its data waits in the buffer until it reads again. + +### One `poll` for everything, including the bonus transfer + +**Challenge:** The subject allows a single `poll` call. The DCC file transfer adds two more sockets (the listener the receiver connects to and the data connection), and the first version polled them with a second `poll` inside the transfer code. + +**Solution:** Before each `poll` the transfer sockets are added to the same `pollfd` vector as the clients (and removed again once the offer is closed), so the main loop reports their events and the transfer code only reads the results. `accept` on the transfer listener and `send` on the data socket now happen only after `poll` said so. + +### Event Loop and Socket Layer Not Split Early -**Challenge:** One person owned the whole loop, so every change touched it all at once and nothing could be changed in isolation. `Server` and `Socket` were not separated into distinct jobs, and the work arrived as one PR with 30-50 commits, making every review unsafe. +**Challenge:** The loop and the socket handling grew together in one place, so every change touched it all at once and nothing could be changed in isolation. `Server` and `Socket` were not separated into distinct jobs, and the work arrived as one PR with 30-50 commits, making every review unsafe. **Solution:** If redone: separate `Socket` (fd, buffer, limits) from `Server` (pollfd vector, close decisions) from day one, and ship changes as small, one-behaviour PRs. @@ -401,7 +423,7 @@ operator requirement and parameter count up front. On the first bad condition it ### 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. +**Challenge:** The CTCP mechanic used by DCC is barely documented anywhere. Between two clients a CTCP is just a `PRIVMSG` whose text is wrapped in `\x01`, so the server forwards it untouched. 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. @@ -415,7 +437,7 @@ operator requirement and parameter count up front. On the first bad condition it - [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) +- [Modern IRC client protocol](https://modern.ircdocs.horse) ### rfc reading tips - [Tips for Reading RFCs](https://www.rfc-editor.org/series/rfc-tips/) @@ -439,6 +461,6 @@ operator requirement and parameter count up front. On the first bad condition it - 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. +- Testing and debugging: Generating protocol-level test scenarios and reviewing edge cases; the tests under `tests/checks/` and the tester and live monitor under `tests/dynamic_tester/` were written with AI assistance and reviewed by the team. ---