Saturday, May 30, 2009

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

RFC3588 (Diameter Base Protocol), specifically par. 5.6, contains specification about a finite state machine that MUST be observed by all Diameter implementations.

In this article we will see how to implement a peer state machine using Test Driven Development. Note that this is only the first part of the whole article.
The following is an extract (just the first entry) of the RFC table (you can found that on the previously mentioned paragraph) that illustrates the peer state machine :

I choose that entry because "Closed" is the peer initial state.
As you can see when the peer is in that state it can receive only two kind of events:

1) Start : A diameter application has signaled that a connection should be initiated with the peer;
2) R-Conn-CER : An acknowledgement is received stating that the transport connection has been established, and the associated Capability Exchange Request (CER) has arrived.

The first event must be associated with the following action on the peer :

1) I-Snd-Conn-Req (Initiator Sends Connection Request) : A transport connection is initiated with the peer;

After the peer successfully processed the action its new state MUST be "Wait-Conn-Ack".

The second event, R-Conn-CER, is firing the following actions on the peer :

1) R-Accept : The incoming connection associated with the R-Conn-CER is accepted as the responder connection;
2) Process-CER : The CER associated with the R-Conn-CER is processed.
3) R-Snd-CEA (Receiver Sends CEA)

After those three actions, the peer must be in "R-Open" state.

Well, of course if you have a look at the whole table there are a lot of states / actions / events but for simplicity we can assume that we are handling only with the "Closed" state. The whole article can be applied to other table entries.

First implicit requirement is that we should have a Peer class, it must have an internal state and the initial value of that state should be "Closed".
So, for a first implementation of our test case I'd like to write something like this :

package com.gazzax.diameter.peer;

public class PeerStateMachineTestCase
{
@Test
public void initialState()
{
Peer peer = new Peer();
assertTrue(peer.currentStateIs(peer.CLOSED));
}
}

Obviously, the test doesn't compile for several reasons; First of all, we need a Peer class :) So let's create it!

package com.gazzax.diameter.peer;

public class Peer
{
}

One little step ahead! But it is not sufficient because the TestCase still doesn't compile :( There's no a currentStateIs(...) method on the Peer class!
Note that that method is accepting a parameter that represents a peer state. Peer's states are typed instance members of peer class and they have a default visibility. Why? Because in that way we can see / use them on our test case without breaking encapsulation (PeerStateMachineTestCase and Peer are in the same package even if they usually reside in different source folders)

NOTE : the current state of the peer IS NOT exposed (it is a private member). You can only have an indirect / read-only access through the currentStateIs(...) method.
This is done because we don't want break enapsulation and therefore peer state invariants.

Anyway, the state machine table suggests that

- a peer, regardless its state, should be able to receive a signal;
- the concrete handling of that signal (fired actions and next state) depends on the current state of the peer;

So, this suggests me that a peer state itself shuold be able to receive a signal. Here is the peer state interface:

package com.gazzax.diameter.peer;

public interface State
{
void signal(PeerEventType event) throws WrongEventException;
}

Where

- PeerEventType is an enumeration of all defined event types; for the current example it will look like this :

package com.gazzax.diameter.peer;

public enum PeerEventType
{
START,
R_CONN_CER
}

- WrongEventException : that will be thrown when the received event type is violating the rules defined on the peer state machine table. I mean, when the peer will receive a wrong event according with its current state. I mean, when the current state of the peer doesn't recognize the received event type as valid.

We left the TestCase with compilation errors. We need a currentStateIs(State peerState) method. Let's write it with a dummy implementation!

public class Peer
{
private final State CLOSED;

boolean currentStateIs(State state)
{
return false;
}
}

Now code should compile :)
Run again the test case and you will see the red bar! Bad? Absolutely not, that means "Progress"! Another little step ahead!

What is the minimal thing / adjustment that we could do in order to get a green bar?

public class Peer
{
private final State CLOSED;

boolean currentStateIs(State state)
{
return true;
}
}

Run again the test and...et voilĂ ! We got a green bar! Another little step ahead!

No comments: