Homework 8: PennPals Specification

Table of Contents

Additional Links

Server Concepts

Client-Server Architecture

The chat program has a client-server architecture, where every user runs an instance of the client program on his or her own computer. The client establishes a connection with an instance of the server program over the network. There are many clients and one server, and the server is responsible for coordinating communication between the connected clients. Clients chat with other clients by sending messages to the server, which are then passed along (relayed) to other clients. Communication between a client and the server occurs via command strings, which are formatted text strings that indicate some action that the client wishes to take (e.g. post a message to other users). When the client wants to take an action, the client generates a command string that corresponds to the desired action, and sends that string to the server. The server, upon receiving a command string, parses it and determines the appropriate response.

Channels

The server does not serve as one giant chat room for all connected users. Instead, this protocol is modeled around the idea of channels, or groupings of users on the server. The server has multiple channels. Any user may opt to join the discussion in a channel, in which case they will receive all messages and commands that are directed to that channel after they join. At any point a user can also leave a channel, and will then stop receiving messages. Any user can also create a new channel, which they will automatically be made the owner of. The owner of a channel has special privileges (detailed later) that allows the owner to control users and messages in that channel.

Nicknames

Each user of the chat service (running a client) can choose a nickname that serves as his or her identity. Nicknames are represented as strings, and they must consist of one or more alphanumeric characters (see ServerUtil.isValidNickname for determining if a nickname is valid). When a user wishes to refer to another user in a command, they use their nickname. Users may change their nicknames at any point by sending NICK command to the server, and any other users in some channel with that user will be notified.

Owners

In every channel, the user that actually created that channel is designated as the channel's owner. The owner in a channel is special in that he or she is allowed to kick other users from the channel. In the client application, the owner of a channel is indicated by having a @ character before their nickname in the payload of the NAMES command.

Example

Suppose we have two clients and one server.

Command Parsing Specification

Each command string sent from the client to the server is a String that can be parsed into a Command. A Command consists of three parts: a command type; zero, one or two parameters; and an optional payload.

Commands are sent to the server using a formatted string which follows one of the following patterns, depending on whether the parameters and payload are present:

  1. TYPE
  2. TYPE PARAM0
  3. TYPE PARAM0 :PAYLOAD
  4. TYPE PARAM0 PARAM1
  5. TYPE PARAM0 PARAM1 :PAYLOAD

The command TYPE should be one of the types listed in the table below. The two parameters (PARAM0 and PARAM1) are separated from the command type and from each other by spaces. The start of the payload is marked by a colon character, and the PAYLOAD itself runs until the end of the command; it may be the empty string. Note that the payload does NOT include the leading colon.

This format means that parameters cannot contain spaces or colons, but may contain other sorts of punctuation. On the other hand, the payload has no such limitations (i.e., it can contain spaces and colons).

As an example, consider the following string:

MESG foo :Hello, world!

The string above matches the third pattern, TYPE PARAM0 :PAYLOAD. So the command type is MESG, the first (and only) parameter is "foo", and the payload is "Hello, world!"

Upon parsing a command string, the parser should return a Command object storing the parsed command type, parameters, and payload. It is a good idea to read through the Command class and its documentation to understand how this class can be constructed.

The parser should parse any command in the protocol, even if that command represents an unsupported operation by the server (e.g. QUIT, NAMES, CONNECT). It is the responsibility of the server model, not the parser, to identify when the server has received an unsupported command and broadcast an error in response. This decision to have the model detect invalid commands, rather than having the parser detect them, is an example of separation of concerns; the parser's only job is to parse commands, without regard for whether or not they are semantically valid. This is discussed in more detail in the style guide.

Server Model Specification

The server model (defined in ServerModel.java) is responsible for maintaining the internal state of the server; the model keeps track of information such as what users are connected and what channels they are in, and updates this information in response to commands received from one of the connected clients.

Server Model Methods

The server model is created by the ServerBackend, which is responsible for managing the network connections between the server and its clients, when the server is first started. While the server is running, the backend interacts with the server model via the following three methods:

The registerUser method is used by the backend to notify the model that a new client has connected to the server. The backend uses an integer user ID to uniquely identify every user that is connected to the server, which is passed to the model when registering a user. The model must keep track of this user ID, as it must be used when instructing the backend to send a message to a particular client. Even if that client's nickname in the model changes, the same user ID must be used to refer to them when communicating with the backend.

As part of registering a user, the model must assign an initial nickname for that user in the model. The server should assign a nickname of "UserX", where X is the first integer starting from 0 that results in a unique nickname for that user. For example, the first user to register with the server should be given the username "User0". If another user then registers, they should be given the name "User1". If "User0" changes his or her nickname to "cis120", then the next user that registers with the server should be given the nickname "User0" (since "User0" is no longer in use by any existing users). We have given you several test cases for this behavior in ClientConnectionTest.java.

The deregisterUser method is used by the backend to notify the model that a particular client (identified via their user ID) has disconnected from the server. The model should remove that user from its internal state and send a QUIT response to all users in a channel with the deregistered user. If that user is the owner of any channels, the server should also delete those channels from its internal state. In this case, the server just needs to send the QUIT response to everyone in these channels as normal; it is the job of the client to recognize that the quitting user was the channel owner and update its state accordingly.

The handle method is used by the backend to notify the model that a command has been received from a client. In this case, the model is given the user ID of the sender, and a Command object representing the command that was received from the sender. The model should verify that the command is valid, and update its state accordingly. The precise definition of what it means for a command to be valid is described in the commands specification below. Because handle has a large amount of specific detail, this method is described in greater depth in its own section below.

Broadcasting Server Actions

In addition to updating the internal server state, each of these methods may also require informing some subset of the connected clients that the server state has been updated. This is done by sending a ServerResponse to the relevant users. A ServerResponse is similar to a Command in that it has a command type, parameters, and payload. In addition, a ServerResponse also stores the sender of the command that triggered the ServerResponse. The command specification below has more information about what information should be contained in the ServerResponses for each Command.

To simplify the communication of ServerResponses to connected clients, each of the three methods of the model returns a Broadcast object. This is a data structure that collects together all of the ServerResponses that should be sent to clients as a result of the method call. A Broadcast can be thought of as a mapping from user IDs to lists of ServerResponses; the list associated with each user ID represents all of the ServerResponses that should be sent to the client with that user ID, in the order they should be sent. After each method, the Broadcast returned should contain the following:

In addition to the three methods defined above, the server model also has the following methods:

The above methods are not used by the server backend during normal operation of the server. Instead, they are used in our unit tests to check that the internal server state is correct after some sequence of commands. In cases where the specified channel doesn't exist in getUsers and getOwner, an empty Collection should be returned.

Additional Documentation

The documentation for ServerModel.java contains more information about what exactly should be done for each of the methods above. Broadcast.java would also be valuable to look at, as we have provided several methods which may be useful when constructing a Broadcast. Finally, you should look back at Command.java to understand what methods you have to extract information out of a Command.

Handle Specification

As the name suggests, commands determine the actions taken by the server. Here, we discuss the concepts that relate to how the server should respond when any Command is processed in handle. The specific behavior of the server on each command will be discussed later.

Updating Internal State

The simplest behavior of the server model when handle is called is to update its internal state. The internal state updates that should occur depend on how the server model is actually implemented, but should model things such as users leaving channels, users joining channels, etc.

Relaying Responses

When the server receives a Command from a client, in addition to updating its internal state, it must provide information about the change to other clients. This happens through a process called relaying, in which information is forwarded from one individual to others by way of an intermediate server.

For example, an email relay server is used to coordinate email exchange between multiple clients. Rather than sending information directly between computers (with the possibility of those computers getting out of sync or going offline), every client connects to a centralized relay server. When a user wants to send an email, their computer first sends the message to the relay server, and then the relay server delivers that message to each of its recipients, along with confirmation to the sender that the message was sent successfully. The server also informs the sender if one of the email addresses they specified was invalid, or there was some other error.

Similarly, in the case of our chat client, there are two different groups of people who should recieve relayed information once the server state changes:

  1. Response to the client who sent the Command. The server must confirm to the client that their command was received and properly handled, or else that it was an invalid command. This type of confirmation is relayed back to the sender of the original command in every case.
  2. Other clients currently connected to the server. Depending on the function of a particular command (as laid out in the protocol specification further down), there may be effects that other users need to be informed of. For example, if a user leaves a channel, the remaining participants in that channel should be told that this person has left.

Because the protocol serves as an unambiguous way for clients and server to communicate about chat actions, it woud be silly to define a new way for the server to inform clients of changes to its state. Instead, we will choose to relay the Command itself to other recipients. The ServerResponse class serves as a wrapper for a Command, but also attaches information about the name of the sender who originally sent the command to the server. By including the name of the sender in the ServerResponse, we can ensure that non-sender clients receiving a ServerResponse are informed of which user initiated the action.

There may also be cases in which the server originates a ServerResponse but does not have a Command object to attach to it. These cases are the responses of type CONNECT, ERROR, NAMES, and QUIT. Further information can be found below.

Error Conditions

In some cases, the server cannot process a command. This can happen because the server does not recognize the command, or because the requested action is inconsistent with the server state (e.g. a user kicking another user out of a channel when they are not the owner). In these cases, the server should send back a ServerResponse with the ERROR command type.

For each of the commands in the protocol, there is a specified set of error codes that could be generated by that command. The ERROR command generated by the server in these cases should take the numerical error code of the error as a parameter. The different error codes, as well as the getCode method for getting a numerical error code, can be found in ServerErrorCode.java. The payload for an error is optional, and can be anything you would like, though we would suggest some helpful message for debugging.

In cases where multiple error codes may be relevant (e.g. if a user doesn't exist, they necessarily aren't in any channel), your code may generate any of the relevant errors; we will only be checking that your code returns one of the potential error codes specified for a given command. Note that the server should never receive any of the command types designated as only being used for ServerResponses below. These should always send back a COMMAND_UNSUPPORTED_BY_SERVER error message.

Certain situations (e.g. joining a channel one is already in, inviting someone to a channel to which they are already invited, etc.) are seemingly redundant actions. In this case, the specification below states that these actions should be relayed as normal, and should not corrupt the server state. This means that you do not need to explicitly check for them, so long as you are sure that receiving such redundant commands will not mess up your internal server state.

While a client may receive multiple commands during a particular call to handle, they should never receive duplicate commands in the same Broadcast. This invariant is enforced by the Broadcast class.

Invite-Only Channels Specification

Our specification allows for the feature of invite-only channels, which are channels that users may only join after they have been invited. The guidelines for invite-only channels are as follows...

PennPals Commands

Here, we detail all of the commands in the protocol, and their intended behaviors. Remember the following key points from above while reading this...

In the specification below, the terms sender and target are used to refer to users referenced in a command. Sender, as the name implies, refers to the sender of the given command. Target refers to the user referred to in the parameter of a command, for commands that take a user as a parameter.

Command Type (Number of) Parameters Payload Behavior Relayed To Error Conditions
NICK (1) new nickname none Changes the sender's nick to the one indicated. The sender nick in the ServerResponse is the sender's OLD nick. If the new nick is already in use, including by the sender, this should trigger a NICK_ALREADY_IN_USE error. If the new nick is invalid (i.e non alpanumeric characters or empty), the parameter should be considered invalid. Everyone who can see the sending client (everyone in every channel the sender is in).
  • NICK_ALREADY_IN_USE
  • INVALID_PARAMETER
CREATE (2) channel name, invite-only flag none Creates the indicated channel on the server; the sender is automatically added to the channel and made the owner of the channel. If the invite-only flag is 1, the channel should be invite-only, and if the invite-only flag is 0, the channel should not be invite-only. If the invite-only flag is anything else, the parameter should be considered invalid. Only the client that sent the command.
  • CHANNEL_ALREADY_EXISTS
  • INVALID_PARAMETER
JOIN (1) channel name none Joins the indicated channel. The sender must have an invite to join an invite-only channel. On successfully joining a channel, the user should be relayed the JOIN command and then sent a NAMES server response. If the sender is already in the channel, the command should be relayed as normal and should not corrupt the internal server state. Everyone in the channel.
  • CHANNEL_DOES_NOT_EXIST
  • SENDER_NOT_INVITED
MESG (1) channel name required: message Sends a message to everyone in the channel. Everyone in the channel.
  • CHANNEL_DOES_NOT_EXIST
  • SENDER_NOT_IN_CHANNEL
LEAVE (1) channel name optional: leave message Sender is removed from the channel, optionally with a parting message. If the sender is the owner of the channel, then the channel is also destroyed. Everyone in the channel, including the sender.
  • CHANNEL_DOES_NOT_EXIST
  • SENDER_NOT_IN_CHANNEL
KICK (2) channel name, user name optional: kick message Kicks the target from the indicated channel, optionally with a kick message. Only allowed if the sender is the channel owner. Kicking oneself is permitted; KICK is relayed with message, but otherwise the behavior is as if the owner sent LEAVE. Everyone in the channel, including the target.
  • CHANNEL_DOES_NOT_EXIST
  • TARGET_NOT_IN_CHANNEL
  • TARGET_DOES_NOT_EXIST
  • SENDER_NOT_IN_CHANNEL
  • SENDER_NOT_OWNER
INVITE (2) channel name, user name none Invites the target to the indicated channel. Invite is valid until the target leaves the channel (voluntarily or not), after which they must be re-invited to join again. If an INVITE is sent to a target who is already invited to the channel or who is already in the channel, it should be relayed as normal without corrupting the internal server state. Attempting to issue an invite to a channel that is not invite-only should cause an INVITE_TO_PUBLIC_CHANNEL for the sender. Everyone in the indicated channel and the indicated user.
  • INVITE_TO_PUBLIC_CHANNEL
  • CHANNEL_DOES_NOT_EXIST
  • TARGET_DOES_NOT_EXIST
  • SENDER_NOT_IN_CHANNEL
  • SENDER_NOT_OWNER

PennPals Server Responses

Any of the commands listed above will also be sent by the server back to the client to acknowledge success, as well as be relayed to the appropriate parties. In addition, there are four additional command types that the server may send to a client, but the client may not send to the server. In other words, these command types are only valid as part of a ServerResponse. If any of these commands are sent by the client to the server, it should send back a COMMAND_UNSUPPORTED_BY_SERVER ERROR response.

Command Type (Number of) Parameters Payload Description
CONNECT (0) none Sent by the server to the client after the client is successfully registered with the server. The sender nick in the response is the initial nickname assigned by the server.
ERROR (1) error code optional: error message Sent by the server to indicate to the client that a previously received command was erroneous. The sender nick in the response is the nickname of the user that triggered the error.
NAMES (1) channel name A space-separated list of names in the channel in alphabetical order. The name of the channel owner is preceded with a @. Sent by the server on receipt of a successful JOIN to inform the sender of the other users in the channel. This list should include the name of the sender of the JOIN that resulted in the NAMES response. The payload should not have any leading or trailing whitespace. The sender nick for the response should be the username of the user that triggered the NAMES response.
QUIT (0) none Sent by the server when a user deregisters to all users in channels with that user. This should not be relayed back to the user who was deregistered, since they are no longer connected to the server. Any channels the user owned should be destroyed. The sender nick should be the nickname of the user that quit.

Glossary

Last modified: Wed Apr 1 09:42:28 EDT 2015