CS courses that are very useful:
The problem: Protocols need to be parsed by both the client and the server.
Example protocol:
Simple Post Office Protocol (SPOP)
Command have the same functionality as POP but are limited to the following:
moria% telnet rohan 110
Trying 130.191.143.100...
Connected to rohan.sdsu.edu.
Escape character is '^]'.
+OK QUALCOMM Pop server derived from UCB (version 2.1.4-R3) at rohan
starting.
USER turtle
+OK Password required for turtle.
PASS *******
+OK turtle has 1 message(s) (354 octets).
LIST
+OK 1 messages (354 octets)
.
QUIT
+OK Pop server at rohan signing off.
Connection closed by foreign host.
moria%
The authentication consists of two commands which have to come in sequence:
The other commands (except for QUIT) cannot be executed before the user
has been authenticated.
What does this mean for the client and server?
The first step in parsing a protocol is to extract the commands from the input
stream.
Since POP (and therefore SPOP) is a line oriented protocol, the logical steps
to get commands is:
In Java we can use readLine() to get a line of text and a StringTokenizer to
break the line into tokens.
Why not use ASCIIInputStream.readWord()?
Simple approach: (Excuse my pseudo code here...)
start: get command if command is "USER" then username = argument get command if command is "PASS" then password = argument if valid(username,password) then while true get command if command is "LIST" then performLIST elsif command is "RETR" then performRETR(argument) elsif command is "QUIT" then return end if end while end if end if end if
Major problems with this algorithm:
Also note that this is implemented in terms of functions. No OOP was used.
A better way of looking at all of this is using a picture.
[ Silence while Andrew draws a weird picture ]
int state = 0; while (true) { command = next command; switch (state) { case 0: if (command.equals("USER")) { username = argument; state = 1; } else if (command.equals("QUIT")) state = 4; else error("Unknown: " + command); break; case 1: if (command.equals("PASS")) { if (valid(username, argument)) state = 2; else { error("Unauthorized"); state = 3; } } else error("Unknown: " + command); break; ...
Major problems with this algorithm:
Advantages:
0 | 1 | 2 | 3 | 4 | |
USER | 1 | 3 | 3 | 4 | 4 |
PASS | 3 | 2 | 3 | 4 | 4 |
LIST | 3 | 3 | 2 | 4 | 4 |
RETR | 3 | 3 | 2 | 4 | 4 |
QUIT | 4 | 4 | 4 | 4 | 4 |
Usable? Not really, we need actions associated with state transitions.
Problem here is that Java doesn't have function pointers.
Also the PASS command can succeed or fail.
There is also lots of redundency in the table.
We can fix the problem with the two outcomes of the PASS command by making each
outcome its own command:
0 | 1 | 2 | 3 | 4 | |
USER | 1 | 3 | 3 | 4 | 4 |
PASSvalid | 3 | 2 | 3 | 4 | 4 |
PASSbogus | 3 | 3 | 3 | 4 | 4 |
LIST | 3 | 3 | 2 | 4 | 4 |
RETR | 3 | 3 | 2 | 4 | 4 |
QUIT | 4 | 4 | 4 | 4 | 4 |
Also, actions could be objects that implement a certain SPOP state
interface.
In C/C++ we can define the following:
struct { int currentState; char *command; int stateIfSucceed; int stateIfFailed; int (*action)(char **); } actionTable[] = { {0, "USER", 1, 3, actionUser}, {0, "QUIT", 4, 4, actionQuit}, {1, "PASS", 2, 3, actionPass}, {1, "QUIT", 4, 4, actionQuit}, {2, "LIST", 2, 2, actionList}, {2, "RETR", 2, 2, actionList}, {2, "QUIT", 4, 4, actionList}, {0, 0, 0, 0, 0} };
Advantages:
We will use the following names for the states:
0 | NoAuth |
1 | HaveUser |
2 | Process |
3 | Invalid |
4 | Quit |
The idea here is to make each state an object.
A state object can then be called upon to process a request and return the new
state.
There should be only one object for each state for efficiency reasons.
This is done by declaring a static member of each state class to contain an
object of that class.
Example:
public class SingleObjClass { private static SingleObjClass obj = new SingleObjClass(); private SingleObjClass() { } public static SingleObjClass getInstance() { return obj; } }
Two ways to proceed from here:
We'll use the second method in an example.
public class SPOPState { public SPOPState USER(String args[]) { return Invalid.getInstance(); } public SPOPState PASS(String args[]) { return Invalid.getInstance(); } public SPOPState LIST(String args[]) { return Invalid.getInstance(); } public SPOPState RETR(String args[]) { return Invalid.getInstance(); } public SPOPState QUIT(String args[]) { return Quit.getInstance(); } }
public class NoAuth extends SPOPState { private static NoAuth obj = new NoAuth(); private SPOPInitial() { } public static NoAuth getInstance() { return obj; } public SPOPState USER(String args[]) { Validator.setUsername(args[0]); return HaveUser.getInstance(); } }
public class HaveUser extends SPOPState { private static HaveUser obj = new HaveUser(); private SPOPInitial() { } public static HaveUser getInstance() { return obj; } public SPOPState PASS(String args[]) { if (Validator.valid(args[0])) { return Process.getInstance(); } else { return Invalid.getInstance(); } } }
The previous example was meant to show the structure, it is by no means
complete.
Things missing:
Other Issues: