Friday, December 25, 2009

Implementing a Diameter Peer State Machine using Test Driven Development (Part 3).

Just a brief riepilogue of what are the missing points :

- When a peer is in "Closed" state and receives a START signal it must
  1. take a I-Snd-Conn-Req action : this is currently a missing point;
  2. change its state to Wait-Conn-Ack : we did that in the last episode :)
- When a peer is in "Closed" state and receives a R-Conn-CER signal it must :
  1. take the following 3 actions : R-Accept, Process-CER and R-Snd-CEA.
  2. change its state to R-Open.
So, concerning this episode, we are going to add the behaviour requested by the R-Conn-CER signal, leaving out for the moment all what is concerning actions. That means we will concentrate only on state transistion.

Following the same approach we used for the "Start" signal, we can write another test method :

public class PeerStateMachineTestCase
{
....@Test
....public void rConnCerSignal()
...{
......Peer peer = new Peer();
......peer.signal(PeerEventType.R_CONN_CER);

......assertTrue(peer.currentStateIs(peer.R_OPEN);
...}
}

PeerEventType.R_CONN_CER is not giving any compiler warning because we already added it in the first part.
What is really missing(from a compiler perspective) is a R_OPEN member instance on Peer class:

public class Peer
{
....final State CLOSED = new State(){};
....final State WAIT_CONN_ACK = new State(){};
....final State R_OPEN = new State(){};
...
...
}

Now our test compiles fine but it gives us a red bar: after PeerEventType.R_CONN_CER signal has been received peer is not (as expected) in R_OPEN state but in WAIT_CONN_ACK :(
This is reasonable if we remember the Peer.signal(...) implementation we did last time :

public void signal(PeerEventType type)
{
....currentState = WAIT_CONN_ACK;
}

With the new R_CONN_CER signal this implementation is not working anymore so we need to rewrite the method in order to let the peer properly react to a given signal. The most obvious solution could be :

public void signal(PeerEventType type)
{
....if (currentState == CLOSED)
....{........if (type == START)
........{............currentState = WAIT_CONN_ACK;
........}
........else if (type == R_CONN_CER)
........{............currentState = R_OPEN;
........}
....}
}

Without any doubt it's working but
  1. If you have a look at the state machine table defined on RFC 3588 you'll quickly realize that this approach will result in a looot of "if / else if" statements;
  2. personally I believe that the transition state is something related with the state itself, while in the implementation above we made that part of the Peer logic.
In addition, each time a new state needs to be added you must :
  1. Add a new state member instance;
  2. Modify the signal method.
What I'm trying to suggest is the following : if
  1. we already have something that tell us what is the current state;
  2. that thing is an object (an instance of State)
  3. obviously that object could have its own state and methods;
Why don't we put the state transition logic within the each State? With this approach we need to modify a little bit the State interface introduced in part one adding a new method and a new exception :

(Interface State)
void signal(PeerEventType eventType) throws WrongEventException;

Now each instance of State must implements this method which is supposed to
  1. Change the state of the hosting Peer instance (remember that our State instances are inner classes);
  2. Raise an exception if a wrong (not allowed) signal is received;
So for example, the inner CLOSED State instance will do something like that :

....final State CLOSED = new State()
....{
........public void signal(PeerEventType eventType) throws WrongEventException
........{............switch (eventType)
............{................case START :
................{....................currentState = WAIT_CONN_ACK;
....................break;
................}
................case R_CONN_CER :
................{....................currentState = R_OPEN;
....................break;
................}
................default :
................{....................throw new WrongEventException("Some useful message");
................}
............}
........}....};

Of course, in order to get things working as expected, we need to modify the Peer.signal method :

public void signal(PeerEventType type) throws WrongEventTypeException
{
....currentState.signal(type);
}

Now our 2 test methods should correctly work.

See you soon for the last part :)

Ciao
Andrea