Assignment 2

Exercise:
Implementing a Rust IRC Server

Implementation of a Rust IRC Server (IRIS)

Change Log

  • Assignment Released
  • Clarified USER and NICK commands; and added info about replies; fixed "client" which should say "server"; added and clarified design. (2022-11-04 09:30)
  • Clarified all the comments made in this forum post (2022-11-10 09:30)
  • Made a small patch to the starter code to fix some messages not working correctly. (2022-11-10 09:30)
  • Fixed broken starter code (2022-11-12 22:30)
  • Username should be called nickname (2022-11-14 13:30)
  • Submission instructions clarified at the bottom of the page (2022-11-18 12:30)

In this second assignment we will be building an IRC Server in Rust. IRC is the "Internet Relay Chat" protocol. It is the ancient predecessor of services like Discord and Messenger; and is even still used today. You will only be implementing a server, and do not need to implement a client.

There are three parts to this assignment:

  • Part 1 is setting up your server. No knowledge of concurrency is required for this step.
  • Part 2 is supporting inter-user messaging and IRC channels, which will require concurrency.
  • Part 3 is supporting plugins.

The goals of this assignment are:

  1. To experience concurrent programming in rust.
  2. To practice designing and structuring a larger rust program.
  3. To focus on skills and design patterns that would actually be used when solving modern programming problems (i.e. writing a pull request, writing re-usable code, etc.)
  4. To have fun making a useful application.

We want to also be explicit about what the goals aren’t:

  1. To teach, or assess, any content related to networking. You do not need any knowledge of TCP, IP, or any other networking concepts. If something seems related to that, and you think you need to understand it, please ask on the forums.
  2. To implement a fully compliant IRC server -- we've asked you to implement a minimum number of commands to get something functional.
  3. To understand the full IRC protocol. You will not need to read anything from the IRC protocol directly -- we've quoted any relevant sections here.
  4. To assess your ability to write large amounts of useless cruft solely for us to mark you on. Where you're writing code, we have a reason for it. Where you're writing text, it's because we want to genuinely understand your thinking.

What you will submit

At the end of this assignment, you will submit a tarfile containing:
  • One crate, containing an IRC server implementation.
  • Any other crates required for your implementation.
  • A completed copy of mark_request.txt containing a list of features that you've completed; how to run your code; and good/bad things about your code. This is basically a pull-request on GitHub; but in a text file.

Part 0: Getting Started

Starter Code

We have provided starter code for this assignment; which you can access by downloading this tar file. Particularly, the file types.rs contains structs to represent every message your IRC server will need to send or receive.

There are some unit tests in types.rs that you can run to help understand the types. You are not expected to write your own unit tests for this assignment, except for design excellence.

You can also run the following command to fetch the starter code:

  6991 fetch assignment 02

A note on a bugs in the starter code

Prior to this message, there were some small bugs in the starter code. They are now fixed, but if you'd like to fix them in existing code; here is the diff:

--- a/activities/iris/starter/iris/src/lib/types.rs
+++ b/activities/iris/starter/iris/src/lib/types.rs
@@ -249,7 +249,8 @@ impl TryFrom> for PrivMsg {
     fn try_from(value: Vec) -> Result {
         Ok(PrivMsg {
             target: Target::from(value.get(1).ok_or(ErrorType::NoRecipient)?.to_string()),
-            message: value.into_iter().last().ok_or(ErrorType::NoTextToSend)?,
+            // skip(2) here skips the PRIVMSG instruction and target.
+            message: value.into_iter().skip(2).last().ok_or(ErrorType::NoTextToSend)?,
         })
     }
 }
@@ -266,7 +267,8 @@ impl TryFrom> for QuitMsg {

     fn try_from(value: Vec) -> Result {
         Ok(QuitMsg {
-            message: value.into_iter().last(),
+            // skip(1) here skips the QUIT instruction.
+            message: value.into_iter().skip(1).last(),
         })
     }
 }
@@ -307,7 +309,8 @@ impl<'a> TryFrom> for ParsedMessage {

         let message = match command[0].as_str() {
             "PING" => Ok(Message::Ping(
-                command.last().ok_or(ErrorType::NoOrigin)?.to_string(),
+                // Skip here ignores the "PING".
+                command.iter().skip(1).last().ok_or(ErrorType::NoOrigin)?.to_string(),
             )),
             "PRIVMSG" => Ok(Message::PrivMsg(PrivMsg::try_from(command)?)),
             "USER" => Ok(Message::User(UserMsg::try_from(command)?)),

    

Networking Terminology

This assignment does not require any knowledge of networking, but just to ensure everyone is on the same page with basic terminology:

  • Client and Server: the two sides of any connection. Servers handle the logic of communicating between many clients. Clients only ever interact directly with a server. For example, your browser is a "client" that sends a request to the 6991 webpage "server", which gives you a response (i.e. - this page!).
  • Address: a web address. Often in this course, it will be localhost, which indicates the web-address of the current computer.
  • Port: a number between 1000 and 65535. Since computers can have multiple programs running at once, a port number helps identify which program should receive a message. IRIS servers will normally be on port 6991. IRC clients could have any port number.

About IRC

IRC is specified by multiple internet standards. In this course, we will only implement a subset of functionality described in RFC 1459. That said, you should not consult that document. If any details are unclear from this assignment, please message the forum and we will clarify.

The IRC protocol is a text-based protocol, with the simplest client being any socket program capable of connecting to the server.

The IRC protocol consists of Clients and Servers. In a real IRC setup, servers connect to other servers to relay messages between clients (hence the Relay in IRC), but our system will only support a single server.

Each client is distinguished from other clients by a unique nickname having a maximum length of nine (9) characters. In addition to the nickname, all servers must know the real name of the client who has connected.
Servers and clients send each other messages which may or may not generate a reply. If the message contains a valid command, as described in later sections, the client should expect a reply as specified but it is not advised to wait forever for the reply; client to server and server to server communication is essentially asynchronous in nature.
Each IRC message may consist of up to three main parts: the prefix (optional), the command, and the command parameters (of which there may be up to 15). The prefix, command, and all parameters are separated by one (or more) ASCII space character(s) (0x20).
The presence of a prefix is indicated with a single leading ASCII colon character (':', 0x3b), which must be the first character of the message itself. There must be no gap (whitespace) between the colon and the prefix. The prefix is used by servers to indicate the origin of the message.
The command must either be a valid IRC command or a three (3) digit number represented in ASCII text.
IRC messages are always lines of characters terminated with a CR-LF (Carriage Return - Line Feed, or \r\n) pair, and these messages shall not exceed 512 characters in length, counting all characters including the trailing CR-LF. Thus, there are 510 characters maximum allowed for the command and its parameters.
Most of the messages sent to the server generate a reply of some sort. The most common reply is the numeric reply, used for both errors and normal replies. The numeric reply must be sent as one message consisting of the sender prefix, the three digit numeric, and the target of the reply. A numeric reply is just like a normal message, except that the keyword is made up of 3 numeric digits rather than a string of letters.

Client

Because you are building a server, clearly you will need a Client. We are happy to provide two clients for you to use.

The first is a small rust program (ircat) which can be used to send arbitrary messages to a server. It doesn't have any concept of the IRC protocol, but it will let you control exactly what gets sent to your server. You should use this for debugging, and sending the example messages shown below. To access this program, run:

6991 ircat [server_address] [port]

We strongly encourage you to use ircat as inspiration for how to develop your server. You can access the source code for ircat here.

The second is sic (the suckless IRC client). This is a C program which is a small, but useful IRC client. It makes it easier to "simulate" a real connection, since you don't need to think about getting the messages exactly right.

6991 sic -h [server_address] -p [port] -n [nickname]

sic is written in C, so its code will not be useful to you. That said, you can access its code here. To run sic, use the options below:

sic [-h host] [-p port] [-n nick]

Sic expects most lines of text you send will start with a colon. The lines below describe what lines of text you can write:

:j #channel
Join a channel
:l #channel
Leave a channel
:m #channel/user message
Send a message to channel or user
:s #channel/user
Set default channel or user
:command
Any line beginning with : is sent as a command to the server.
text
Throws an error, unless a default channel has been set, in which case it sends the text as a message to the default channel.

You are more than welcome to use your own, preferred IRC client. Since they are built to the same standard, they should function very similarly. However, you should ensure that your server does in fact function with sic as well.

Reference Solution

The reference IRIS server is available at 6991 iris. Please note that IRIS servers are open to anyone on CSE's systems. We do not take responsibility for servers students run themselves.

Part 1: Single-Client Server (25% performance)

In this part you will build the simplest possible IRC server, which only sends and receives commands from a single client. You will implement the following commands:

  • USER/NICK
  • PING/PONG
  • 001 (RPL_WELCOME)
  • QUIT
  • PRIVMSG
Each of the above dotpoints is worth 5% of your functional correctness mark.

Connection Registration

To begin a connection, the NICK and USER commands must be sent, by the client, in order. Should messages not be sent in that order, messages should be ignored. Due to a bug in the spec, if you instead choose to accept messages in either order, you will not be penalised (but the implementation in the spec is actually simpler.)

Future parts of the assignment will involve storing values from the NICK and USER commands. How you do that is up to you. There’s no 'requirement' to store things in a particular way, as long as it lets you solve the parts of the assignment. You are also guaranteed that once connections have been established, you will not receive any more NICK or USER messages.

NICK

Command
NICK
Parameters
NICK nickname
Errors
Examples
NICK wiz
The NICK message is used to give user a nickname. Nicknames must be at most 9 characters long, not begin with a number, and can only include ascii letters or numbers. If the server receives an identical NICK from a client which is directly connected, it shall issue an ERR_NICKCOLLISION to the local client, and ignore the NICK command.

USER

Command
USER
Parameters
USER ignored ignored ignored :realname
Errors
Examples
USER ignored ignored ignored :Ronnie Reagan
The USER message is used at the beginning of connection to specify the real name of a new user. It must be noted that realname parameter must be the last parameter, because it may contain space characters and must be prefixed with a colon (':') to make sure this is recognised as such. This command is only implemented in IRIS because all IRC clients send this message. The ignored fields are fields that a real IRC server would expect for the USER command, but in our implementation, they will not be used, and have been replaced with ignored.

PING/PONG

Once a connection has been established, your server should reply to "PING" messages with a "PONG".

PING

Command
PING
Parameters
PING :hostname
Errors
Examples
PING :clientname
The PING command is used to test the presence of an active server at the other end of the connection. When a PING message is received, the appropriate PONG message MUST be sent as reply to the server.

PONG (server reply only)

Command
PONG
Parameters
PONG :hostname
Errors
Examples
PONG :clientname
PONG message is a reply to PING message. It should contain the same parameters as the PING message that caused it. This is only a reply from a server, users cannot use a PONG command.

Greetings

Once a connection has been established, your server should also send a welcome message to the client.

RPL_WELCOME (server reply only)

Command
001
Parameters
:hostname 001 nickname :Greeting Message...
Errors
Examples
:iris-server 001 tfpk :Hi Thomas Kunc, welcome to IRC
Upon success, the client will receive an RPL_WELCOME message indicating that the connection is now registered and known the to the entire IRC network. The reply message MUST contain the full name of the client. This is only a reply from the server, a user cannot use the 001 command.

Quitting

QUIT

Command
QUIT
Parameters
QUIT :message for quitting
Errors
Examples
QUIT :Dinner-time!
QUIT is used to leave the connection. When the server receives this message, it should close the connection to the client. The client will not receive any further messages from the server. As described in the JOIN message below, if the client was connected to a channel; all other users connected to that channel should be sent the QUIT message of the quitting user, including a message. If no message is provided by the user, you should send the user's nickname as the message. You can assume that a QUIT message will always be sent before a connection is closed.

Messaging

Heads up! Over part 1 and 2, you'll be implementing the PRIVMSG command. What follows is a description of the PRIVMSG command, with notes on what you need to implement in part 1.

PRIVMSG (sent to server)

Command
PRIVMSG
Parameters
PRIVMSG target :MSG
Errors
Examples
PRIVMSG Zac :Lets give everyone full marks.
PRIVMSG Tom :Agreed, full marks for everyone!
PRIVMSG Zac :Want to grab ice cream before the workshop?
PRIVMSG is used to send private messages back to yourself, between users, as well as to send messages to channels. "target" is used to figure out who to send messages to. There are two options for targets.

If a nickname is the target, a message will just be sent to that person. If the target starts with a hash (#), then it will be sent to anyone in that channel.

Successful PRIVMSG messages are never acknowledged. The user who sent it must assume the message was sent successfully, unless their nick is also listed as the target, in which case they should receive the message.

If no recipient is specified, send back a "NORECIPIENT". If a recipient is specified, but there is no text, send back "NOTEXTOTSEND". If the user targets a channel, but the channel does not exist or is invalid, reply "NOSUCHCHANNEL". Otherwise if the user's target does not exist or is invalid, reply "NOSUCHNICK".

In part 1, you only need to implement the ability to send messages to yourself. In sic (the IRC client), this will appear as two identical messages.

PRIVMSG (sent to client)

Command
PRIVMSG
Parameters
:nickname PRIVMSG target :message
Errors
Examples
:tom PRIVMSG Zac :Lets give everyone full marks.
:zac PRIVMSG Tom :Agreed, full marks for everyone!
:shrey PRIVMSG Zac :Want to grab ice cream before the workshop?
The server should forward a PRIVMSG to it's intended target, adding a prefix to indicate the sender.

Part 2: Multi-Client Server (50% performance)

In this part, you will make modifications to your server to support multiple users. You will implement:

  • More than one user joining the server.
  • PRIVMSG to other users.
  • JOIN
  • PART
  • PRIVMSG to channels
Each of the above dotpoints is worth 10% of your functional correctness mark.

A very important design constraint applies in this assignment: Your server must not store user data any longer than necessary. This means you may not store messages indefinitely; nor may you store nicknames after they have left the server. Failure to follow this constraint will lead to a significant mark penalty.

Multiple Users

Your program should support more than one user connecting to the same server.

PRIVMSG should support sending messages to other users on the channel.

Channels

JOIN

Command
JOIN
Parameters
Errors
Examples
JOIN #haku
:tfpk JOIN #haku // This is sent to everyone else who is already in the channel.
The JOIN command is used by a user to request to start listening to the specific channel. Once a user has joined a channel, they should receive information about all commands the server receives affecting the channel. This includes JOIN, PART, QUIT, and of course PRIVMSG. This allows channel members to keep track of the other channel members.

PART

Command
PART
Parameters
PART #channel
Errors
Examples
PART #haku
:tfpk PART #haku // This is sent to everyone else who is already in the channel.
The PART message causes the client sending the message to be removed from the list of active users of a channel.

Part 3: Plugins (25% performance)

In this last main part of the assignment; you will refactor your program to support "plugins". You can choose to design plugins however you would like, but your guiding design principle should be: your plugin system should be easy to use for someone who knows basic terminal commands; has cargo installed on their system; but does not know any Rust. It should also be easy for someone familiar with Rust to implement their own plugin; without modifying your code.

You will need to build two plugins:

  • A reminder system -- a user tells the plugin that they want to remind a user about something in a specified number of seconds. Worth 15% of your final mark.
  • An example plugin -- a well documented, minimal example that a developer could use to make their own plugin. This might be as simple as a plugin which replies to a specific message with a specified reply.

But your system should be extensible to other commands.

Part 4: Submit a Mark Request

When building modern software, you are expected to send in a pull request, explaining what you've done. In COMP6991, you must submit a "mark request". This is contained in mark_request.txt.

The mark request helps us mark you -- we will use it to help us find out what to mark. It is not meant to take more than 10 minutes to write. No marks are awarded for the mark request specifically; but if it is very inaccurate, we reserve the right to either request you resubmit it with a penalty; or mark using it as a basis (and therefore not mark things you've done).

Error Codes and Error Handling

No Such Nickname

A nickname cannot be found.

No Such Channel

A channel's name is invalid.

No Origin

a PING command does not contain an argument.

No Recipient

A PRIVMSG command did not include the destination.

No Text To Send

A PRIVMSG command did not include a message.

Unknown Command

A command was not recognised.

No Nickname Given

A nickname parameter expected for a command and isn't found.

Erroneous Nickname

A NICK message which is not valid (i.e. bad length or character set).

Need More Parameters

Returned by the server by numerous commands to indicate to the client that it didn't supply enough parameters. Should only be used if no other error better explains the problem.

Other Information

Submission

See the instructions down the bottom of the page.

Using Other Crates

We are happy for you to use any crate that has been published on crates.io under four conditions:

      You may not use any crates which were designed to work with IRC clients or servers.
      The crate must not have been authored by anyone else in the course.
      The crate must have at least 1000 downloads, excluding the last 30 days.
      The crate must not impose license restrictions which require you to share your own code.

If you are in doubt (or think these restrictions unfairly constrain you from using a reasonable crate), ask on the course forum.

Marking Scheme

There are 3 things on which you will be marked:

  • Mechanical Style (10%)
  • Functional Correctness (40%)
  • Idiomatic Design (50%)

You can see how the marks break down here:

1. Mechanical Style (10%):

We will look at your crates, and make sure they:

  • Compile, with no warnings or errors.
  • Raise no issues with 6991 cargo clippy
  • Are formatted with rustfmt
  • Have any tests written for them pass

If they do all of the above, you get full. Otherwise, we will award partial marks. This is meant to be the "easy marks" of programming.

2. Functional Correctness (40%):

Your code should do what we describe. We will run the code through some obvious tests to make sure it behaves as we expect. For example, we will likely do the following:

  • Trying to start your program.
  • Testing out the commands listed above.
  • Testing out some simple error cases of the above commands (messages without targets, for example).

The marks available of each section of the assignment is shown next to the title of the section above.

Your functional correctness mark will be capped at 25% if only one client can connect at a time.

Your functional correctness mark will be capped at 75% if you do not implement support for plugins.

Your functional correctness mark will be capped at 100% if you implement support for plugins, and multiple clients can connect at a time.

3. Idiomatic Design (50%):

Your code should be well designed. This is where we will spend most of our time when marking. To help you, we have provided "design excellence" suggestions, which are ideas to make your design really excellent. You don't have to do them, but they would be good ways of getting a great design.

The following list of properties will be marked in your program:

  • Code is abstracted appropriately.
  • Types are used appropriately to express data in the program.
  • The design does not impose unnecessary constraints on either the caller or callee through borrowing, lifetimes or ownership.
  • Uses traits sensibly to add expressiveness and avoid unnecessary code.
  • Data structures used are appropriate to store data.
  • Functions perform error handling; cases that are expected do not panic.
  • Code is sensibly organised, and split into appropriate modules.
  • Concurrency primitives should be used appropriately. Locks should not be held too long, but assumptions should not be made about the state of shared resources outside a locked section.
  • Documentation, where provided, is correct and readable.
  • (for plugins) appropriate documentation of how to create a plugin is provided.
  • (optional) Uses external crates effectively to achieve the above goals.
  • (optional) Where code is designed in a sub-optimal way, comments about how to improve it are made under "Design Limitations".

If you have only completed part 1, then only 25% of the marks for this section will be available. If you have only completed part 2, then only 75% of the marks for this section will be available.

Your mark will be calculated based on the feedback you have received:

100% of available marks Very little negative feedback is given on the above criteria; and at least one design excellence suggestions are implemented (or your own ideas which we consider equivalently excellent).
85% of available marks Some minor comments are made about some of the above criteria. Above this mark, at least one design excellence feature was implemented (or your own idea is considered equivalently excellent).
75% of available marks Major comments are made about one or two criteria, with multiple small comments in different areas.
65% of available marks Major comments are made about three or more criteria.
50% of available marks Many areas have major comments made.
below 50% of available marks Assignments in this category are likely written as "translations from C", and ignore many Rust features and design patterns.

Note that the following penalties apply to your total mark for plagiarism:

0 for the assignment Knowingly providing your work to anyone and it is subsequently submitted (by anyone).
0 for the assignment Submitting any other persons work. This includes joint work.
0 FL for COMP6991 Paying another person to complete work. Submitting another persons work without their consent.

Formal Stuff

Assignment Conditions

  • Joint work is not permitted on this assignment.

    This is an individual assignment.

    The work you submit must be entirely your own work. Submission of any work even partly written by any other person is not permitted.

    The only exception being if you use small amounts (< 10 lines) of general purpose code (not specific to the assignment) obtained from a site such as Stack Overflow or other publicly available resources. You should attribute the source of this code clearly in an accompanying comment.

    Assignment submissions will be examined, both automatically and manually for work written by others.

    Do not request help from anyone other than the teaching staff of COMP6991.

    Do not post your assignment code to the course forum.

    Rationale: this assignment is an individual piece of work. It is designed to develop the skills needed to produce an entire working program. Using code written by or taken from other people will stop you learning these skills.

  • The use of code-synthesis tools is permitted on this assignment, however beware -- the code it creates can be subtly broken or introduce design flaws. It is your job to figure out what code is good.

    Rationale: this assignment is intended to mimic the real world. These tools are available in the real world.

  • Sharing, publishing, distributing your assignment work is not permitted.

    Do not provide or show your assignment work to any other person, other than the teaching staff of COMP6991. For example, do not share your work with friends.

    Do not publish your assignment code via the internet. For example, do not place your assignment in a public GitHub repository. You can publish Workshops or Labs (after they are due), but assignments are large investments for the course and worth a significant amount; so publishing them makes it harder for us and tempts future students.

    Rationale: by publishing or sharing your work you are facilitating other students to use your work, which is not permitted. If they submit your work, you may become involved in an academic integrity investigation.

  • Sharing, publishing, distributing your assignment work after the completion of COMP6991 is not permitted.

    For example, do not place your assignment in a public GitHub repository after COMP6991 is over.

    Rationale:COMP6991 sometimes reuses assignment themes, using similar concepts and content. If students in future terms can find your code and use it, which is not permitted, you may become involved in an academic integrity investigation.

Violation of the above conditions may result in an academic integrity investigation with possible penalties, up to and including a mark of 0 in COMP6991 and exclusion from UNSW.

Relevant scholarship authorities will be informed if students holding scholarships are involved in an incident of plagiarism or other misconduct. If you knowingly provide or show your assignment work to another person for any reason, and work derived from it is submitted - you may be penalised, even if the work was submitted without your knowledge or consent. This may apply even if your work is submitted by a third party unknown to you.

If you have not shared your assignment, you will not be penalised if your work is taken without your consent or knowledge.

For more information, read the UNSW Student Code , or contact the course account.

When you are finished working on this exercise, you must submit your work by running give:

This exercise regrettably cannot be submitted with 6991 give. Instead, please package your crate(s) (along with all other files intended for submission) up into a tar file named crate.tar.

For example:
tar cvf crate.tar <path1> <path2> <path3> ...
e.g.:
tar cvf crate.tar ./my_crate1/ ./my_crate2/

Finally, submit with the following command:

give cs6991 assign02_iris crate.tar

You must run give before Week 10 Friday 17:00:00 to obtain the marks for this exercise. Note that this is an individual exercise; the work you submit with give must be entirely your own.