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.
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
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.
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
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.
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() | ^----^
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
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() | ^--------^