CS2030 Practical Assessment #1: Handshake

Back to homepage

Problem Description

A three-way handshake protocol between a terminal (or endpoint) device and a host comprises a series of three handshake exchanges:

Many terminals can be connected to a single host. The host can then proceed to broadcast to all connected terminals.

As an example, a food (or guest) paging system comprises of a transmitter (host) and a number of pagers (terminals). Each pager begins by initiating the handshake with the transmitter. Once a connection is established, the pager can be handed to a patron so that he or she can be notified when his/her food order is ready. A broadcast can also be done to beep all pagers, e.g. to find misplaced pagers.

Task

Your task is to establish the three-way handshake between a terminal (e.g. pager) and a host (e.g. transmitter) via the three corresponding methods snd, rcv and ack as shown below:

jshell> new Pager("pager1").snd(new Transmitter("transmit1"))
$.. ==> pager1 >--snd--> transmit1

jshell> new Pager("pager1").snd(new Transmitter("transmit1")).rcv()
$.. ==> pager1 >--snd--> transmit1 >--rcv--> pager1

jshell> new Pager("pager1").snd(new Transmitter("transmit1")).rcv().ack()
$.. ==> pager1 >--snd--> transmit1 >--rcv--> pager1 >--ack--> transmit1

Having established a connection between a host and a terminal, the host can proceed to broadcast a beep.

jshell> new Pager("pager1").snd(new Transmitter("transmit1")).rcv().ack().broadcast()
pager1:beep

Take note!

There are altogether five levels. You are required to complete ALL levels.

You should keep to the constructs and programming discipline instilled throughout the module.

The following levels demonstrate the handshake between a pager and a transmitter. In general, any terminal is allowed to connect to any host.

Level 1

Write a Pager class with a constructor that takes in a unique string identifier so as to create a pager as a terminal (Term).

$ javac your_java_files
$ jshell your_java_files_in_bottom-up_dependency_order
jshell> Pager p1 = new Pager("pager1")
p1 ==> pager1

jshell> Term t1 = p1
t1 ==> pager1

jshell> new Term()
|  Error:
|  Term is abstract; cannot be instantiated
|  new Term()
|  ^--------^

jshell> p1 instanceof Pager
$.. ==> true

jshell> p1 instanceof Term
$.. ==> true

In addition, define an overriding equals method that returns true if any two terminals (not necessarily only pagers; can even be a mix of different types of terminals) have the same identifier, or false otherwise.

jshell> t1.equals(p1)
$.. ==> true

jshell> p1.equals(t1)
$.. ==> true

jshell> p1.equals(new Pager("pager1"))
$.. ==> true

jshell> t1.equals(new Pager("pager1"))
$.. ==> true

jshell> p1.equals("pager1")
$.. ==> false

Level 2

A transmitter is the host that food pagers communicate with. Write a class Transmitter with a constructor that takes in a unique string identifier and creates the transmitter as a host (Host).

Next, include a snd method to enable a terminal (e.g. a pager) to initiate the handshake with a host (e.g. a transmitter).

new Pager("pager1").snd(new Transmitter("transmit1"))

Note that the above returns a host.

Lastly, define an overriding equals method that returns true if two hosts (not necessarily only transmitters; can even be a mix of different types of hosts) have the same identifier, or false otherwise.

$ javac your_java_files
$ jshell your_java_files_in_bottom-up_dependency_order
jshell> Pager p1 = new Pager("pager1")
p1 ==> pager1

jshell> Transmitter r1 = new Transmitter("transmit1")
r1 ==> transmit1

jshell> p1.snd(r1)
$.. ==> pager1 >--snd--> transmit1

jshell> p1.snd(r1).equals(r1)
$.. ==> true

jshell> Host h1 = r1
h1 ==> transmit1

jshell> new Host()
|  Error:
|  Host is abstract; cannot be instantiated
|  new Host()
|  ^--------^

jshell> p1.snd(r1).equals(h1)
$.. ==> true

jshell> r1.equals(h1)
$.. ==> true

jshell> h1.equals(p1)
$.. ==> false

Reminder: ensure that your classes are NOT cyclic dependent throughout all levels.

Level 3

Let us continue with the next phase of the handshake. After a host obtains snd from a terminal, it can proceed to send rcv back to the terminal.

new Pager("pager1").snd(new Transmitter("transmit1")).rcv()

Note that the above will return a terminal.

$ javac your_java_files
$ jshell your_java_files_in_bottom-up_dependency_order
jshell> Pager p1 = new Pager("pager1")
p1 ==> pager1

jshell> Transmitter r1 = new Transmitter("transmit1")
r1 ==> transmit1

jshell> p1.snd(r1).rcv()
$.. ==> pager1 >--snd--> transmit1 >--rcv--> pager1

jshell> p1.snd(r1).rcv().equals(p1)
$.. ==> true

jshell> p1.snd(r1).equals(r1)
$.. ==> true

From the last test case above, the host transmitter that is returned from p1.snd(r1) is the same as r1 (as deemed by the equals method). However, the rcv method cannot be invoked from r1 straightaway since it does not follow from snd.

jshell> r1.rcv()
|  Error:
|  cannot find symbol
|    symbol:   method rcv()
|  r1.rcv()
|  ^----^

Level 4

We are now ready to complete the handshake by including the ack method.

new Pager("pager1").snd(new Transmitter("transmit1")).rcv().ack()

Note that the above will return a host with a connection established to a terminal.

$ javac your_java_files
$ jshell your_java_files_in_bottom-up_dependency_order
jshell> Pager p1 = new Pager("pager1")
p1 ==> pager1

jshell> Transmitter r1 = new Transmitter("transmit1")
r1 ==> transmit1

jshell> p1.snd(r1).rcv().ack()
$.. ==> pager1 >--snd--> transmit1 >--rcv--> pager1 >--ack--> transmit1

jshell> p1.snd(r1).rcv().equals(p1)
$.. ==> true

jshell> p1.ack()
|  Error:
|  cannot find symbol
|    symbol:   method ack()
|  p1.ack()
|  ^----^

From the above, we observe that the pager terminal is the same throughout the handshake (as deemed by the equals method). However, the ack method cannot be invoked from p1 before handshaking commences.

The following shows that the host transmitter also remains the same throughout the handshake.

jshell> p1.snd(r1).rcv().ack().equals(r1)
$.. ==> true

jshell> p1.snd(r1).rcv().ack().equals(p1.snd(r1))
$.. ==> true

Level 5

We now add more terminals to the same host. Suppose a transmitter r1 has established a connection to pager p1. The following demonstrates how another pager p2 can be included. You may assume that a terminal that has already established communication with a host will not establish the connection again.

jshell> Pager p1 = new Pager("pager1")
p1 ==> pager1

jshell> Transmitter r1 = new Transmitter("transmit1")
r1 ==> transmit1

jshell> p1.snd(r1)
$.. ==> pager1 >--snd--> transmit1

jshell> p1.snd(r1).rcv().ack()
$.. ==> pager1 >--snd--> transmit1 >--rcv--> pager1 >--ack--> transmit1

jshell> Pager p2 = new Pager("pager2")
p2 ==> pager2

jshell> Host h1 = p2.snd(p1.snd(r1).rcv().ack()).rcv().ack()
h1 ==> pager2 >--snd--> transmit1 >--rcv--> pager2 >--ack--> transmit1

jshell> Host h2 = p2.snd(r1).rcv().ack()
h2 ==> pager2 >--snd--> transmit1 >--rcv--> pager2 >--ack--> transmit1

Although the last two test cases "look" similar, h1 establishes connections to p1 and then p2, while h2 establishes connection to only p2. To see this, define a broadcast method in the host that broadcasts a beep to the connected terminals in the order they are added. Note that output arises from the respective terminals (not the host).

jshell> h1.broadcast()
pager1:beep
pager2:beep

jshell> h2.broadcast()
pager2:beep

The following shows how broadcast behaves at different parts of the handshake sequence.

jshell> p1.snd(r1).broadcast() // p1 has yet to establish connection

jshell> p1.snd(r1).rcv().ack().broadcast() // p1 has established connection
pager1:beep

jshell> p2.snd(p1.snd(r1).rcv().ack()).broadcast() // p2 has yet to establish connection after p1
pager1:beep

Lastly, to ensure that handshakes from different terminals do not interfere with one another, incomplete handshakes cannot be passed to snd. The following will give a compilation error.

jshell> p2.snd(p1.snd(r1)).rcv().ack()
|  Error:
|  incompatible types: ...
|  p2.snd(p1.snd(r1)).rcv().ack()
|         ^--------^