Unit testing server protocols in C


I've been porting a number of utilities in pibox to provide library support for common functions.  This includes a network configuration library – known as libpiboxnet – and a higher level library for PiBox specific functions.  The code for the networking library was originally duplicated in two different apps with nearly identical functions.  One of those is the PiBox daemon.  This daemon accepts network packets for performing a variety of functions.  Currently all packets are required to come from the localhost but later they will permit connections from remote apps like the new piboid android app.

One of the most important tasks when writing a server library is unit testing the inbound message handling.  So how can you do this?  There are many testing infrastructures, but it's actually pretty easy to do without one.  For PiBox's piboxd daemon I wanted to automate testing using ordinary BASH scripts.  Before getting to the shell scripts let's define the protocol that will be tested.

Binary protocol format

Before explaining the test method I should explain what I'm testing.  There are three components to the server that need testing:

  1. The inbound connection handler that validates the message
  2. The queue handler that is used to asynchronously handle the message
  3. The message specific functions

The server doesn't get a lot of connections so it's not implemented to be robust (a bug on its own).  It handles each connection and attempts to read in the complete message.  If it fails, it drops the connection and waits for the next message.  If the message is complete it queues it for a separate thread to handle.

The incoming message is a self defined message.  This means it has a small header with a payload size and the payload.  The header is defined by the message protocol and contains a type and action.  If the payload (or any other part of the message) does not arrive in time then the connection times out.

The header has to be validated for the protocol.  If it fails the connection drops without reading the payload.  The payload depends on the message type and action.  The queue processor passes the message to its handler.  If necessary, the inbound connection is kept open and passed along to the message handler who is responsible for cleaning up the connection.

The payload is shown with an alternate format that is used for specific commands where a tag and a filename are used.  The use of the tag depends on the header where specific types require the tag and filename.

As you can see there isn't much to this protocol.  It's pretty simple though there are a number of points of potential failure.  Much of the protocol is handled in the header to perform various network setup.   Configuration data is in the payload.  Unknown types and actions are ignored.

Now that the design is defined it's time to look at unit testing the inbound, queue and message handling.

Send a packet with netcat

There are probably other ways to do this (it's software, there are 1000 solutions with at least half being sufficient) but I use netcat.  The command line for all tests is simple:

The -4 option means use IPv4.  Doing the tests on localhost is easier if I limit the protocol to IPv4.  I'm not testing network connectivity, just the application layer protocol.  The port depends on how I run the piboxd daemon.  The redirection from stdin is used to pass the data to transfer to piboxd.  It's the protocol packet we're testing.

Build a packet with xxd

The secret to protocol testing with netcat is building the packet to feed to it.  The packet defined above uses single byte binary fields which are not easy to generate with ordinary shell scripts.   Fortunately they are very easy to generate with xxd using a three part command.

printf "0: %s" $header | sed -E 's/0: (..)(..)(..)(..)/0: \4\3\2\1/' | xxd -r > servertest.dat

The header value is a string representing the four bytes of the header in the protocol packet.  The string is represented in LSB first, as in

00000201

This makes it easier to map to the protocol diagram.  But on an intel box the ordering needs to be reversed.  This is accomplished with the sed command, which translates the string to this.

01020000

But this is a still a string.  In order to pass it with netcat we need to make this a binary value.  That's where xxd comes in.  Normally xxd is used to convert binary data into a human readable string.  Using the -r option we can convert the string back into binary and send it to a temporary file, the one we'll pass to netcat to run our test.

This is just an example of generating the header but the same process is used to generate the payload size and payload fields, appending them to the temporary file.

Scripting the whole thing

To script the tests I used two functions:  genMsg and sendIt.  The former is used to generate the temporary file used to run a test.  The latter is used to send the message from the temporary file.  Simple enough.  In practice these functions used state variables to modify the message content in order to apply invalid formats.  But a simple test might look like the following.

genMsg 00000201 webcam
NCOUT=1
sendIt
unset NCOUT

The genMsg function is handed the header and a payload.  The NCOUT variable is used to save output from netcat to check for error messages.  As the script expands many such variables are created to vary the tests.

Extensions

As more features were added to piboxd more unit tests were added to the script.  This includes the ability to launch an netcat-based web server to receive replies from piboxd as well as sending and receiving multicast messages using socat (an alternative to netcat).  I was even able to run piboxd with valgrind to check for leaks.

The scripts for testing piboxd are in the tests directory but need to be run from the top of the source tree in order to find required data files.  Feel free to check them out and let me know if you have additional ideas!

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.