From 62a442b8573f049325044485723ce76dc9535fdd Mon Sep 17 00:00:00 2001 From: vjan-nie Date: Sat, 1 Aug 2026 16:23:30 +0200 Subject: [PATCH] docs: manual testing updated with bonus feature testing examples, and USER-GUIDE has been created --- USER-GUIDE.md | 454 ++++++++++++++++++++++++++++++++++++++++ tests/MANUAL-TESTING.md | 200 ++++++++++++++++++ 2 files changed, 654 insertions(+) create mode 100644 USER-GUIDE.md diff --git a/USER-GUIDE.md b/USER-GUIDE.md new file mode 100644 index 0000000..3933972 --- /dev/null +++ b/USER-GUIDE.md @@ -0,0 +1,454 @@ +# ft_irc — User Guide + +A practical guide to using this IRC server, feature by feature. Each section +explains *what* the feature does, *why* it behaves the way it does, and shows +the fastest way to try it — sometimes with a raw `nc` session (when seeing the +exact protocol lines matters), sometimes with HexChat (when the point is the +user experience). + +The server implements a subset of RFC 2812: enough that a real client like +HexChat behaves as it would against any public IRC network, but deliberately +scoped to the project's requirements (no server-to-server, no services layer). + +--- + +## Running the server + +```bash +make # full build (mandatory + bonus + optional extras) +# or +make mandatory # RFC core only — this is what the grade is decided on +make bonus # mandatory + Bot + FILE transfer + +./ircserv +./ircserv 6667 test123 +``` + +- **port** — the TCP port the server listens on. +- **password** — the connection password every client must send before + registering. This is the server password (IRC's `PASS`), not a per-user + account password. + +The server is single-threaded and event-driven (one `epoll` loop, non-blocking +sockets). It never spawns a thread or a process per client — every connection +is multiplexed through that one loop. + +--- + +## Two ways to connect + +Throughout this guide, examples use whichever client makes the point clearest. + +**`nc` (netcat)** shows you the raw protocol — every byte in, every byte out. +Use it when the interesting thing is the exact wire format. Register by typing +the three lines yourself: + +```bash +nc -C 127.0.0.1 6667 +``` +``` +PASS test123 +NICK alice +USER alice 0 * :Alice Liddell +``` + +`nc -C` sends CRLF line endings, which IRC requires. Without `-C` some +netcat builds send bare `\n`; this server tolerates that, but real IRC is +CRLF, so `-C` keeps you honest. + +**HexChat** builds those lines for you and renders the results as a chat UI. +Use it when the point is what a real user sees. Configure a server at +`127.0.0.1/6667`, set the server password to `test123`, and connect. To watch +the raw protocol underneath HexChat, open **Window → Raw Log**. + +> **Why registration is three commands.** `PASS`, `NICK`, and `USER` together +> form the handshake. The server won't treat you as a real user until all three +> arrive and the password matches. Before that, only `CAP`, `PASS`, `NICK`, +> `USER`, `QUIT`, and `PONG` are accepted — anything else gets +> `451 :You have not registered`. + +--- + +## Registration and identity + +### Setting your password (PASS) + +The password must be correct, or the server rejects you before you can do +anything. A wrong password is reported and then the connection closes: + +``` +PASS wrongpass +NICK alice +USER alice 0 * :Alice +``` +``` +:ft_irc 464 alice :Password incorrect +``` + +The `464` reaches you *before* the socket closes — you get told why, rather +than seeing a silent drop. That matters with a real client: HexChat shows you +"Password incorrect" instead of an unexplained disconnect. + +### Choosing a nickname (NICK) + +Your nickname is your identity on the network. It must be unique +(case-insensitively — `Alice` and `alice` are the same nick) and at most 9 +characters. + +``` +NICK alice +``` + +**Over-long nicks are truncated, not rejected.** If you send a nick longer +than 9 characters, the server shortens it to 9 rather than refusing it — +exactly as real ircds do: + +``` +NICK abcdefghijklmnop +``` +``` +:abcdefghi!alice@127.0.0.1 NICK :abcdefghi +``` + +> **Why truncate instead of reject?** RFC 2812 defines 9 as the maximum nick +> length, but it asks *clients* to tolerate longer strings and doesn't tell the +> server to reject them — real servers truncate to the length they advertise +> (`NICKLEN=9`). Rejecting would also break clients like HexChat: its +> reconnect-retry appends suffixes that make the nick *longer*, so a rejected +> over-long nick would fail every retry and never connect. Truncation is both +> more correct and more compatible. + +Invalid *characters* are still rejected (this is a different problem from +length): + +``` +NICK ab#cd +``` +``` +:ft_irc 432 * ab#cd :Erroneous nickname +``` + +A nick that's already taken — including one that collides only after +truncation — gets `433`: + +``` +NICK alice +``` +``` +:ft_irc 433 * alice :Nickname is already in use +``` + +### Completing registration (USER) + +``` +USER alice 0 * :Alice Liddell +``` + +The last parameter (after the colon) is your "real name" and may contain +spaces; the earlier fields are mostly legacy. Once `USER` completes a valid +`PASS`+`NICK`+`USER` set, the server sends the welcome burst (`001`–`005`) and +you're registered. + +--- + +## Channels + +### Joining (JOIN) + +``` +JOIN #general +``` + +Joining a channel that doesn't exist creates it, and the creator becomes its +operator. You'll see the topic (or that there is none), the member list, and +the channel modes: + +``` +:alice!alice@127.0.0.1 JOIN #general +:ft_irc 331 alice #general :No topic is set +:ft_irc 353 alice = #general :@alice +:ft_irc 366 alice #general :End of /NAMES list +``` + +The `@` before your nick in the `353` list means you're a channel operator. + +In HexChat, `/join #general` does the same and opens a channel tab. + +### Talking (PRIVMSG and NOTICE) + +`PRIVMSG` sends a message — to a channel or to another user: + +``` +PRIVMSG #general :hello everyone +PRIVMSG bob :hey, got a minute? +``` + +> **The space before the colon is mandatory.** The colon marks the start of the +> "trailing" parameter (the message text, which may contain spaces). From +> HexChat you never see this — it builds the line for you. From `nc`, forgetting +> the space changes the meaning: +> ``` +> PRIVMSG bob:hello -> 412 :No text to send (no space -> no text parameter) +> PRIVMSG #bob :hi -> 403 :No such channel (the # makes it a channel) +> ``` + +`NOTICE` is identical in form but signals "don't auto-reply to this" — it's for +automated messages. Clients won't bounce error replies off a `NOTICE`, which is +why bots and servers use it. + +Messaging errors are specific: a message to a nick that doesn't exist gets +`401 :No such nick/channel`; to a channel you're not in, `404 :Cannot send to +channel`; with no text at all, `412`. + +### Leaving (PART) and topics (TOPIC) + +``` +PART #general :goodbye +TOPIC #general :Project planning — read the pinned notes +``` + +`TOPIC` with no text *reads* the current topic; with text, it *sets* it. In a +`+t` channel (see modes below), only operators can set it. + +--- + +## Channel operators and modes (MODE) + +Channel operators control the channel. The creator starts as operator; +operators can grant the status to others. + +``` +MODE #general +o bob # make bob an operator +MODE #general -o bob # remove it +``` + +The mandatory channel modes are `+i`, `+t`, `+k`, `+o`, `+l`: + +| Mode | Effect | Example | +|------|--------|---------| +| `+i` | Invite-only: nobody joins without an invite | `MODE #general +i` | +| `+t` | Only operators can change the topic | `MODE #general +t` | +| `+k` | Sets a channel key (password) | `MODE #general +k secret` | +| `+o` | Grants/removes operator status | `MODE #general +o bob` | +| `+l` | Limits the number of members | `MODE #general +l 20` | + +> **Why these five.** They're the RFC channel modes the project requires — the +> ones a moderator actually needs: control who joins (`+i`, `+k`, `+l`), who +> can change the topic (`+t`), and who has authority (`+o`). + +### Invite-only in practice (+i and INVITE) + +``` +MODE #general +i +``` + +Now an uninvited user is refused: + +``` +JOIN #general +``` +``` +:ft_irc 473 #general :Cannot join channel (+i) +``` + +An operator invites them, and then they can join: + +``` +INVITE bob #general +``` +Only members of the channel it grants access to can send an `INVITE`, and only +operators may do so in a `+i` channel — a non-operator trying to invite gets +`482 :You're not channel operator`. + +### Keyed channels (+k) + +``` +MODE #general +k secret +``` + +Now joining requires the key: + +``` +JOIN #general +``` +``` +:ft_irc 475 #general :Cannot join channel (+k) +``` +``` +JOIN #general secret +``` +succeeds. A wrong key gets the same `475`. + +### Kicking (KICK) + +``` +KICK #general troublemaker :please read the rules +``` + +Only operators can kick. The kicked user is removed and everyone in the channel +sees it. + +--- + +## Keeping the connection alive (PING/PONG) + +The server periodically sends a `PING` to check you're still there; your client +must answer with a matching `PONG`. HexChat does this automatically. From `nc` +you'd answer by hand: + +``` +PING :ft_irc +``` +``` +PONG :ft_irc +``` + +If a client stops answering, the server eventually drops it with a +`Ping timeout`. Conversely, the server answers *your* `PING`: + +``` +PING :keepalive +``` +``` +:ft_irc PONG ft_irc :keepalive +``` + +> **Why this exists.** TCP can keep a socket "open" long after the peer is +> actually gone (a frozen machine, a pulled cable with no RST). PING/PONG is how +> the server detects and reclaims those dead connections instead of leaking +> them. + +--- + +## Querying (WHO, WHOIS, USERHOST) + +``` +WHO #general # who's in the channel +WHOIS bob # details about a user +USERHOST bob # bob's user@host +``` + +HexChat issues these under the hood when you open a channel or hover a nick; +from `nc` you can run them directly to see the `352`/`315` (WHO) and +`311`/`318` (WHOIS) replies. + +--- + +## Quitting (QUIT) + +``` +QUIT :heading out +``` + +The server tells everyone who shared a channel with you that you've left — +once, no matter how many channels you had in common — and cleans you out of all +of them. Closing the socket abruptly (Ctrl+C in `nc`) is handled the same way, +with a `Connection closed` reason. + +--- + +# Bonus features + +These require `make bonus`. Confirm you're running the bonus build before +testing them — a `!help` to the bot that comes back `401 :No such +nick/channel` means you're on the mandatory binary. + +## The Bot (ircbot) + +`ircbot` is a **virtual user inside the server** — not a separate process, not a +connected client. It only reacts to private messages addressed to its nick. +Typing `!help` into a channel does nothing; it's just channel text. + +``` +/msg ircbot !help +``` +(from `nc`: `PRIVMSG ircbot :!help`) + +Commands: + +``` +/msg ircbot !time # current server time +/msg ircbot !info # server info +/msg ircbot !info #general # that channel's member count and modes +/msg ircbot !joke # a joke (rotates, doesn't repeat one string) +``` + +An unknown command gets a helpful reply rather than silence: + +``` +/msg ircbot !nonsense +``` +``` +:ircbot PRIVMSG you :Unknown command. Type !help for available commands. +``` + +The bot's nick is reserved — you can't take it (`433`), so nobody can +impersonate it. + +## FILE transfer + +A server-mediated file transfer over a small `FILE` protocol. The server +relays base64-encoded chunks between two clients; **it never decodes the +payload and never touches disk** — it's pure relay. The proof of correctness is +that what the receiver gets is byte-for-byte what the sender sent. + +The happy path, with `hello world!` (12 bytes, base64 `aGVsbG8gd29ybGQh`): + +``` +# alice (sender) # bob (receiver, already registered) +FILE SEND bob hello.txt 12 + -> alice: NOTICE :FILE 1 offered to bob + -> bob: FILE OFFER 1 hello.txt 12 + FILE ACCEPT 1 + -> alice: FILE OK 1 +FILE DATA 1 aGVsbG8gd29ybGQh + -> bob: FILE DATA 1 aGVsbG8gd29ybGQh +FILE END 1 + -> bob: FILE END 1 12 +``` + +Decode what bob received to confirm it survived intact — in a *separate shell*, +not inside `nc` (anything typed into `nc` goes to the server as an IRC line): + +```bash +echo 'aGVsbG8gd29ybGQh' | base64 -d # -> hello world! +``` + +The transfer id (`1` above) increments per offer — use whatever id the server +actually gave you. + +**Rejection is reported, not silent.** If bob sends `FILE REJECT 1`, alice gets +`FILE NO 1`. And data sent after a rejection goes nowhere: alice sending +`FILE DATA` for a rejected/unknown id gets `NOTICE :FILE: no transfer with id +N`, and bob receives nothing. A transfer to a nonexistent user is refused up +front: `NOTICE :FILE: no such nick `. + +> **Why FILE errors are NOTICEs, not numerics.** `FILE` isn't an RFC command, so +> no standard numeric applies to it. The server reports its errors as +> `NOTICE`s instead. + +## DCC passthrough + +Separate from the `FILE` protocol above. HexChat's own file-transfer uses DCC, +which rides inside a CTCP-wrapped `PRIVMSG` (the payload is bracketed by `\x01` +bytes). The server implements **no DCC logic** — it just relays that +`\x01`-wrapped payload untouched, which is all HexChat needs to pop up its +transfer dialog. + +From HexChat this "just works" (initiate a DCC send between two connected +clients). The key property is that the server passes the `\x01` control bytes +through intact rather than stripping them — if it stripped them, HexChat's DCC +would be dead. + +--- + +## A note for graders / defense + +Every feature above has been verified against HexChat, with Libera Chat +(running Solanum) as the reference for "how a real server behaves." The design +goal throughout: using HexChat against this server should feel like using it +against any official IRC network, within the mandatory + bonus feature set. + +A few deliberate, documented divergences from Solanum remain — all cosmetic, +none affecting a HexChat session (e.g. minor differences in a couple of +numeric-reply strings). They're catalogued in the project's testing notes. diff --git a/tests/MANUAL-TESTING.md b/tests/MANUAL-TESTING.md index 324d6b6..70a5ec4 100644 --- a/tests/MANUAL-TESTING.md +++ b/tests/MANUAL-TESTING.md @@ -620,6 +620,206 @@ swallows or rewrites input, and a reply to a command you did not send proves not | 5 | Default `KICK` reason uses the kicker's nick; real servers use the kicked user's | Cosmetic | 3.5 | | 6 | No `~` prefix on the username when there is no ident response | Cosmetic | 1.1 | +Everything else in this document has been verified as behaving correctly — including the +idle timeout, which was reported as broken for a while and turned out to be a measurement +artifact of using `nc` as the test client. + +--- + +## 10. Bonus features + +These require the bonus build. Everything below was verified against it. + +```bash +make fclean && make bonus +./ircserv 6667 test123 +``` + +### A note on syntax when using `nc` + +HexChat builds protocol lines for you: `/msg ircbot !help` becomes +`PRIVMSG ircbot :!help`. From `nc` you type the whole line yourself, and **the space before +the colon is mandatory** — it separates the target from the message text. + +Two ways to get it wrong, both of which the server rejects correctly: + +``` +PRIVMSG #ircbot :!help -> 403 :No such channel (the # makes it a channel name) +PRIVMSG ircbot:!help -> 412 :No text to send (no space, so no text parameter) +``` + +### 10.1 Is the bonus build actually running? + +*Do this first — everything else in this section depends on it.* + +``` +/msg ircbot !help +``` + +A reply means the bonus tier is linked. `401 ircbot :No such nick/channel` means you are +running the mandatory binary. + +`ircbot` is **not** a separate process and **not** a connected client. It is a virtual user +inside the server that only claims private messages addressed to its nickname. Typing +`!help` into a channel does nothing — it is just channel text. + +### 10.2 Bot commands + +``` +/msg ircbot !time +/msg ircbot !info +/msg ircbot !info #test +/msg ircbot !joke +``` + +`!info #test` should report the member count and modes of that channel. Run `!joke` several +times to confirm it rotates rather than repeating one hardcoded string. + +### 10.3 The bot's nickname is reserved + +``` +/quote NICK ircbot +``` + +Expect `433 :Nickname is already in use`. If a client can take this nickname, it can +impersonate the bot. + +### 10.4 Unknown bot command + +``` +/msg ircbot !nonsense +``` + +Expect a reply, not silence: +`:ircbot PRIVMSG you :Unknown command. Type !help for available commands.` + +### 10.5 FILE transfer — happy path + +This is the server-mediated `FILE` protocol (base64 relay). The server never decodes the +payload and never touches disk. + +Two terminals running `nc -C 127.0.0.1 6667`. Use a tiny file so it fits in one chunk — +`hello world!` is 12 bytes and its base64 is `aGVsbG8gd29ybGQh`. + +**Receiver (bob)** — register first: +``` +PASS test123 +NICK bob +USER bob 0 * :Bob +``` + +**Sender (alice)**: +``` +PASS test123 +NICK alice +USER alice 0 * :Alice +FILE SEND bob hello.txt 12 +``` + +Expected exchange: +``` +alice -> FILE SEND bob hello.txt 12 +alice <- :ft_irc NOTICE alice :FILE 1 offered to bob + bob <- :alice!alice@127.0.0.1 FILE OFFER 1 hello.txt 12 + bob -> FILE ACCEPT 1 +alice <- :bob!bob@127.0.0.1 FILE OK 1 +alice -> FILE DATA 1 aGVsbG8gd29ybGQh + bob <- :alice!alice@127.0.0.1 FILE DATA 1 aGVsbG8gd29ybGQh +alice -> FILE END 1 + bob <- :alice!alice@127.0.0.1 FILE END 1 12 +``` + +**The proof is that the payload bob receives is character-for-character identical to what +alice sent.** To see it in plain text, decode it — but in a *separate shell*, not inside +`nc`. Anything typed into `nc` is sent to the server as an IRC line, which is why shell +commands come back as `421 ECHO :Unknown command`. + +```bash +echo 'aGVsbG8gd29ybGQh' | base64 -d # -> hello world! +``` + +Note the transfer id (`1` here) increments per offer; use the id the server actually gave +you. + +### 10.6 FILE transfer — rejection + +``` +alice -> FILE SEND bob otro.txt 12 + bob <- :alice!alice@127.0.0.1 FILE OFFER 2 otro.txt 12 + bob -> FILE REJECT 2 +alice <- :bob!bob@127.0.0.1 FILE NO 2 +``` + +The sender must be told, not left waiting. + +### 10.7 Sending data after a rejection + +*The one case with real potential to bite: if data still flows after a refusal, `REJECT` +means nothing.* + +From alice, after the rejection above: +``` +FILE DATA 2 aGVsbG8gd29ybGQh +``` + +Expect `:ft_irc NOTICE alice :FILE: no transfer with id 2`, and **check the receiver's +terminal**: bob must receive nothing at all. + +### 10.8 FILE transfer to a non-existent user + +``` +FILE SEND nadie x.txt 12 +``` + +Expect `:ft_irc NOTICE you :FILE: no such nick nadie` — an explicit error, not silence. + +`FILE` errors are reported as server `NOTICE`s rather than numerics. That is deliberate: +`FILE` is not an RFC command, so no standard numeric applies to it. + +### 10.9 DCC relay (CTCP passthrough) + +Separate from 10.5–10.8. The server implements no DCC logic at all; it just relays the +`\x01`-wrapped CTCP payload untouched, which is all HexChat needs to show its file transfer +dialog. + +This one cannot be typed by hand — you need the literal `\x01` bytes. Two scripted +terminals. + +**Receiver** (`cat -v` makes the control bytes visible): +```bash +{ printf 'PASS test123\r\nNICK dan\r\nUSER dan 0 * :D\r\n'; sleep 20; } \ + | nc 127.0.0.1 6667 | cat -v +``` + +**Sender**, once `dan` is registered: +```bash +{ printf 'PASS test123\r\nNICK eve\r\nUSER eve 0 * :E\r\n'; sleep 2; \ + printf 'PRIVMSG dan :\001DCC SEND test.txt 2130706433 12345 100\001\r\n'; sleep 3; } \ + | nc 127.0.0.1 6667 +``` + +Expected on the receiver: +``` +:eve!eve@127.0.0.1 PRIVMSG dan :^ADCC SEND test.txt 2130706433 12345 100^A^M +``` + +The `^A` markers are the `\x01` bytes arriving intact. The trailing `^M` is the CR of the +line ending, which is normal. If the server stripped the `\x01` bytes, HexChat would never +show a transfer dialog and DCC would be dead. + +--- + +## Known issues at time of writing + +| # | Issue | Severity | Test | +|---|---|---|---| +| 1 | `324`/`329` sent twice on join (HexChat de-duplicates them on screen) | Low | 3.1 | +| 2 | `CHANMODES` puts `l` in the wrong group; should be `,k,l,it` | Cosmetic | 1.1 | +| 3 | Channel name capitalisation differs between the `JOIN` echo and the numerics | Cosmetic | 3.2 | +| 4 | Default `KICK` reason uses the kicker's nick; real servers use the kicked user's | Cosmetic | 3.5 | +| 5 | No `~` prefix on the username when there is no ident response | Cosmetic | 1.1 | +| 6 | The bot uses a bare-nick prefix (`:ircbot PRIVMSG …`) where real services use `nick!user@host` | Cosmetic | 10.2 | + Everything else in this document has been verified as behaving correctly — including the idle timeout, which was reported as broken for a while and turned out to be a measurement artifact of using `nc` as the test client. \ No newline at end of file