The FriendFace Protocol allows client programs to communicate with the FriendFace Server. It describes the format that requests from the client program should take, and the format of the responses from the Server to the client program. There are six kinds of request that a client program can make: each of these requests is made by the client program sending a number of non-empty lines to the server; each request is met with a response by the server, as detailed below. The response “on” indicates success; the response “xn” indicates failure; and the response “?n” indicates that the client hasn’t followed the protocol, i.e., the client’s request wasn’t syntactically correct.
Register a new member. In order to register a new member, the client sends
a line (i.e., a string ending with a newline “n”) beginning with ‘0’
a non-empty line containing the desired name of the new member
For example, to register a new member with name “Moss”, the client should send “0nMossn”; to register a new member with name “Jennywenny”, the client should send “0nJennywennyn”. The server responds with:
“on” if the request is successful; i.e., the given member name does not already exist in FriendFace, and is now added as a new member;
“xn” if the request is unsuccessful because there is already a member with that name in FriendFace;
“?n” if the client has not supplied a non-empty line for the desired name.
For example, if the FriendFace member list is (in the notation of the Maude spec)
[“Moss”, null] ~> null
(just one member, called “Moss”, with no friends), then the request “0nJennywennyn” will be met with the response “on” (success), and the member list will become
[“Jennywenny”, null] ~> [“Moss”, null] ~> null
(the order of members in the list is not important); the request “0nMossn” will be met with the response “xn” as Moss is already a member, and the member list would remain unchanged; and the requests “0nDelina” and “0nn would both be met with the response “?n”, as neither conform to the protocol (no second line in the first case; empty second line in the second case), and the member list remains unchanged.
Request a list of all friendship requests for a given member. In order to request a list of all the members who have asked to be friends with a given member, the client sends:
a line beginning with “1”; and
a line containing the name of the given member.
For example, “1nRoyn” would be the request to find the names of all members who have asked to be friends with Roy. The server responds with “onn” if there are no friendship requests for the given member (this includes the case that the given member is not in fact a registered member); if there are requests for the given member, the server responds with “on” followed by the names of all members who have requested to be friends with the given member. For example, if Moss and Jennywenny have both asked to be friends with Roy, the response would be “onMossnJennywennyn”. If the request is not properly formatted, the server responds with “?n”. For example, “1nRoy” would be met with “?n”.
Submit a friendship request. In order to submit a friendship request, the client sends three lines:
a line beginning with “2”
a line containing the name of the member requesting friendship
a line containing the name of the member they want to be friends with.
For example, if the client wants to send a request from Moss to be friends with Roy, it would send “2nMossnRoyn”. The server responds with “on” if the request is successfully added, i.e., Moss and Roy are both members of FriendFace, and the request does not already exist; otherwise, if the request already exists, or if at least one of Moss and Roy are not registered members, the server responds with “xn”. If the request is not properly formatted, the server responds with “?n”. For example, “2nMossnRoy” and “2nMossnn” would both be met with “?n”, as neither provide three non-empty lines.
Accept a friendship request. In order to accept a friendship request, a client sends:
a line beginning with “3”;
a line containing the name of the member who requested the friendship;
a line containing the name of the member they want to be friends with.
For example, “3nMossnRoyn” is a request to accept Moss’s request to be friends with Roy. If a request for the two given members exists in FriendFace, the server removes the request from the system, adds each of the two members to the other’s list of friends, and responds with “on”. If there is no such request in FriendFace, the server responds with “xn”. If the request is not properly formatted, the server responds with “?n”. For example, “3nnn” would be met with “?n”.
Refuse a friendship request. In order to refuse a friendship request, a client sends:
a line beginning with “4”;
a line containing the name of the member who requested the friendship;
a line containing the name of the member they want to be friends with.
For example, “4nMossnRoy” is a request to refuse Moss’s request to be friends with Roy. If a request for the two given members exists in FriendFace, the server removes the request from the system, and responds with “on”. If there is no such request in FriendFace, the server responds with “xn”. If the request is not properly formatted, the server responds with “?n”. For example, “4nnRoyn” would be met with “?n”.
Request a list of all the friends of a given member’s friends. In order to request a list of all the members who are friends of friends of a given member, the client sends:
a line beginning with “5”; and
a line containing the name of the given member.
For example, “5nRoyn” would be the request to find the names of all members who are friends with some friend of Roy’s, not including Roy himself (who is obviously a friend of each of his friends). The server responds with “onn” if there are no such members (e.g., if Roy has no friends, or if all of his friends are friends only with him). If there are friends of friends of the given member, the server responds with “on” followed by the names of all members who are friends of friends of the given member. For example, if Roy, Moss and Jennywenny are all friends with each other, and only with each other, then the request “5nRoyn” would be met with “onMossnJennywennyn”. If the request is not properly formatted, the server responds with “?n”. For example, “5n” would be met with “?n”.
Task 1: Specify the FriendFace server
Any request a client makes to the server is just a string. When the server receives such a string, two things happen:
the state of FriendFace is updated
the server sends a string to the client.
We’ll call the string that the client program sends the “input string”, and we’ll call the string that the server sends to the client in response the “output string”. How the state of the FriendFace is changed depends upon the request made by the client program. If the client registers a new member, the state will be updated by having one new member added to the list of members, assuming the requested name isn’t already taken. Or if the request is to accept a friendship request between two members, the state will change by updating the lists of friends of the two members. And so on. In order to specify the behaviour of the FriendFace server, we need to say, for any given input string received in any given FriendFace state:
what the resulting FriendFace state is, and
what the output string sent back to the client program is.
The functionality of the FriendFace server therefore takes two inputs: the input string sent by the client program, and the current Friendface state. The output is a pair consisting of the resulting FriendFace state and the output sent back to the client program. For example, if the input string is “0nMossn” and the current FriendFace state is { null, null }, then the result should be the pair consisting of the FriendFace state { [“Moss”, null] ~> null, null } and the string “on”. Similarly, if the input is the string “0nRoyn” and that last FriendFace state, the output should be the FriendFace state { [“Roy”, null] ~> [“Moss”, null] ~> null, null } and the string “on”. And if the input string is “2nRoynMossn” and the FriendFace state is the last one (with members Roy and Moss), then the output would be { [“Roy”, null] ~> [“Moss”, null] ~> null, [“Roy”, “Moss”] ~> null } and the string “on”. As a final example, if the input string is the same, i.e., “2nRoynMossn”, but the FriendFace state is the one with just Moss as member, i.e., { [“Moss”, null] ~> null, null }, then the output should consist of the same state (i.e., no change to the state) and the output string “xn”.
The file friendFaceServer.maude contains a partial specification of the FriendFace protocol. This specification uses utilities.maude that contains some useful operations for working with strings. Equations are given that specify how the server should respond to requests to register a new member. For example,
var S : String .
var FF : FriendFace .
cq S > FF = register(secondLine(S), FF) > okToken + “n”
if S beginsWith registerToken and
S has 2 lines and
not isIn(secondLine(S), getMembers(FF)) .
describes successfully responding to a request to register a new member. For example,
“0nMossn” > { null, null }
simplifies to
{ [ “Moss”, null ] ~> null, null } > “on” .
You should add equations to this file to specify how the server responds to the other requests that clients can make.
The operation
op _>>_ : StreamList FriendFace -> FriendFaceAndOutputs .
extends _>_ to take a list of input streams and produce a list of output streams. For example, “0nMossn” “0nRoyn” “0Moss” >> { null, null } represents three client connections to the new FriendFace server, the first two of which are register-requests and the third of which doesn’t conform to protocol. The expected result would be
{ [“Moss”, null] ~> [“Roy”, null] ~> null, null } >> “on” “on” “?n”
with two successful registrations and one protocol error message. Finally, operation getOutputs takes a list of input requests (strings), sends them to the new FriendFace state, and returns the list of responses from the server. For example, getOutputs “0nMossn” “0nRoyn” “0Moss” gives result “on” “on” “?n”. Your tests (see Task 3 below) should use getOutputs in this way.
Task 2: Implement the FriendFace server
Implement the FriendFace server in Java by writing a class FriendFaceServer in a file FriendFaceServer.java.
The server should be multi-threaded and for each client there should be a time-out on the socket connection so that the connection is closed if there is no input from the client program over a period of 5 seconds.
Your server should use an ExecutorService to execute threads, and you should include some means of shutting the server down cleanly. If there are still some threads running when the server is shut down, you should allow up to five seconds for these threads to complete before exiting the Java interpreter (see the APIs for how to do this).
Your server should be thread-safe. You should make it thread-safe without modifying FriendFace.java, and you should include in your in-line comments some indication as to why your implementation is thread-safe.
Task 3: Test the FriendFace server
The file friendFaceServer.maude contains some test reductions that show the expected outputs from four client connections. You should add more tests to this file, and include a main method in a file FriendFaceServerTest.java with corresponding tests. These should give the same results as your Maude tests.
While you are developing your code, it may be useful to test your server using telnet, or this example GUI (save the file, then run with java -jar MetaClient.jar).