TSN.1 Compiler Tutorial

A practical guide for programmers


In this tutorial, we take a real world wireless standard as an example and show you how to use the TSN.1 Compiler to develop your message parser. It is a simple three step process. Step 1 is straightforward. Step 2 is automated. Step 3 is what you need to do regardless of the parser you use. Here is an outline of the development process:

  1. Write the Message Definition in TSN.1
  2. Generate the Message Parser
  3. Encode/Decode the Message

1. Write the Message Definition in TSN.1

You start off by taking the informal description of a message in the standard document and formalizing it using the syntax of TSN.1. For most standards, this translation is fairly straightforward. It is just a matter of copy and paste plus some editing. We will use the MAC ARQ Feedback Information Element message in the IEEE 802.16-2004 (WiMAX) as an example for the remainder of this tutorial. Here is the TSN.1 definition of the ARQFeedbackIE:

01 wimax_mac_ARQFeedbackIE() ::=
02 {
03    CID         16;
04    LAST        1;
05    ACKType     2;
06    BSN         11;
07    NumACKMaps  2;
08
09    if(ACKType != 1)
10    {
11       ACKMaps [ NumACKMaps + 1 ]  : // Array of ACKMaps
12       {
13          if(ACKType != 3)
14          {
15             SelectiveACKMap  16;
16          }
17          else
18          {
19             SequenceFormat  1;
20
21             if(SequenceFormat == 0)
22             {
23                SequenceACKMap   2;
24                Sequence1Length  6;
25                Sequence2Length  6;
26                reserve          1;
27             }
28             else
29             {
30                SequenceACKMap   3;
31                Sequence1Length  4;
32                Sequence2Length  4;
33                Sequenc32Length  4;
34             }
35          }
36       }
37    }
38
39    if(LAST != 1)
40    {
41       Next  : wimax_mac_ARQFeedbackIE; // Recursion
42    }
43 }

As you can see, the TSN.1 definition of this IE is quite simple. It looks almost the same as the way it appears in the WiMAX standard. Each parameter in the IE is defined as a bit field with a field name followed by the number of bits. This definition illustrates several syntax features of the TSN.1 notation. We see examples of the if else conditionals. There is an example of an array (line 11), which is defined using a for loop in the WiMAX standard. The size of the array is NumACKMaps + 1. We also see an example of a recursion (line 41). In WiMAX, a MAC ARQ_FEEDBACK PDU can consist of a number of ARQFeedbackIEs. The LAST bit (line 4) indicates if the current IE is the last one. If LAST is not set to 1, then there is another ARQFeedbackIE after the current one.

2. Generate the Message Parser

Once you have the message defined in TSN.1, your work is pretty much done. The TSN.1 Compiler will automatically generate the message encoding/decoding code for you. Assume the above definition is in a file called wimax_mac.tsn, issue the following command at the prompt to generate the encoding/decoding code in C:

tsnc -pack -unpack wimax_mac.tsn
     

To generate it in C++:

tsnc -c++ -pack -unpack wimax_mac.tsn
     

If no error is detected, the Compiler generates two files wimax_mac.h and wimax_mac.c (wimax_mac.cxx for C++) in the current directory. You can change the output directory by using the "-d" option. You can also change the generated file extensions using the "-hext" and "-cext" options. The generated .c/.cxx files are used exclusively by the Compiler Runtime Library. You should never need to look at them. The generated header file contains the type definition for the message. Here is what the generated C header file looks like:

/***************************************************************************
 * DO NOT EDIT!!! DO NOT EDIT!!! DO NOT EDIT!!!
 *
 * This file is generated by the TSN.1 Compiler.
 * Visit http://www.protomatics.com for more information.
 **************************************************************************/

#ifndef WIMAX_MAC_H
#define WIMAX_MAC_H

/***************************************************************************
 * Includes
 **************************************************************************/

#include "tsnc_msg.h"



/***************************************************************************
 * Definitions
 **************************************************************************/

/*
 * Message: wimax_mac_ARQFeedbackIE_ACKMaps
 */
extern tsnc_msg_Descriptor wimax_mac_ARQFeedbackIE_ACKMaps_descriptor;

typedef struct _wimax_mac_ARQFeedbackIE_ACKMaps
{
   /* Common Message Fields */
   tsnc_Message _tsnc_msg_;

   /* Fields */
   void   *_msg0;
   uint16   SelectiveACKMap;
   uint8    SequenceFormat;
   uint8    SequenceACKMap;
   uint8    Sequence1Length;
   uint8    Sequence2Length;
   uint8    Sequenc32Length;

} wimax_mac_ARQFeedbackIE_ACKMaps;


/*
 * Message: wimax_mac_ARQFeedbackIE
 */
extern tsnc_msg_Descriptor wimax_mac_ARQFeedbackIE_descriptor;

typedef struct _wimax_mac_ARQFeedbackIE
{
   /* Common Message Fields */
   tsnc_Message _tsnc_msg_;

   /* Fields */
   uint16   CID;
   uint8    LAST;
   uint8    ACKType;
   uint16   BSN;
   uint8    NumACKMaps;
   wimax_mac_ARQFeedbackIE_ACKMaps   *ACKMaps[4];
   struct _wimax_mac_ARQFeedbackIE   *Next;

} wimax_mac_ARQFeedbackIE;

Note that two C structures are generated. This is because each element of the ACKMaps array is also a message. It is an inline nested message (line 12 - 36) and the corresponding C structure is named wimax_mac_ARQFeedbackIE_ACKMaps. Each bit field in the message is mapped into its appropriate integer type in C. The CID field is 16 bit and it is mapped into a uint16; LAST is 1 bit and it is mapped into a uint8, and so on.

The generated C++ header file looks very similar to the C version. Instead of struct, each message is defined as a class with a default constructor and destructor.

3. Encode/Decode the Message

Now the parser is "written". Let's take a look at how your application can encode/decode messages using the Compiler Runtime Library. First, we'll look at a decode example. Here we assume that you have received a MAC PDU that contains an ARQ_FEEDBACK message. We need to decode it by unpacking it into the generated C structure. Once it's unpacked, we can then process the feedbacks more efficiently using this structure. Functions with the "tsnc_msg" prefix are the functions defined in the Runtime Library.

void wimax_mac_process_ARQ_FEEDBACK
   (
      const uint8 *pdu_buf, /* Buffer containing the raw bytes of the MAC PDU */
      uint32       pdu_len  /* Size of the PDU in bytes */
   )
{
   wimax_mac_ARQFeedbackIE  ie;     /* The variable holding the unpacked message */
   wimax_mac_ARQFeedbackIE *p;      /* The pointer iterating over each ARQFeedbackIE */
   tsnc_Status              status; /* Unpack status */
   uint32                   i;

   /* Initialize the message */
   tsnc_msg_initialize(&ie, wimax_mac_ARQFeedbackIE);

   /* Unpack the message */
   status = tsnc_msg_unpack(&ie, pdu_buf, 0, pdu_len * 8, NULL);
   if(status != TSNC_STATUS_OK)
   {
      /* Report error */
   }

   /* Message is decoded. Process it using the unpacked structure. */
   for(p = &ie; p != NULL; p = p->Next)
   {
      /* Process the IE */
   }

   /* Finalize the message. This frees the internal message pointers recursively. */
   tsnc_msg_finalize(&ie);
}

Or in C++:

void wimax_mac_process_ARQ_FEEDBACK
   (
      const uint8 *pdu_buf, /* Buffer containing the raw bytes of the MAC PDU */
      uint32       pdu_len  /* Size of the PDU in bytes */
   )
{
   wimax_mac_ARQFeedbackIE  ie;     /* The variable holding the unpacked message */
   wimax_mac_ARQFeedbackIE *p;      /* The pointer iterating over each ARQFeedbackIE */
   tsnc_Status              status; /* Unpack status */
   uint32                   i;

   /* Unpack the message */
   status = ie.Unpack(pdu_buf, 0, pdu_len * 8, NULL);
   if(status != TSNC_STATUS_OK)
   {
      /* Report error */
   }

   /* Message is decoded. Process it using the unpacked structure. */
   for(p = &ie; p != NULL; p = p->Next)
   {
      /* Process the IE */
   }
}

In the C++ version, we no longer need to explicitly initialize or finalize a message. It is automatic through the constructor and destructor.

Next let's look at an encode example. In this case, we need to generate a ARQ_FEEDBACK message. We start by populating the fields of the structure and then call the pack function to pack it into a stream of bytes.

void wimax_mac_generate_ARQ_FEEDBACK
   (
      uint8  *pdu_buf,     /* Buffer to store the encoded MAC PDU */
      uint32  pdu_max_len  /* Maximum size of the buffer in bytes */
      uint32 *pdu_len      /* Output parameter returning the actual number of byte encoded */
   )
{
   wimax_mac_ARQFeedbackIE  ie;     /* The variable holding the message to be packed */
   tsnc_Status              status; /* Pack status */

   /* Initialize the message */
   tsnc_msg_initialize(&ie, wimax_mac_ARQFeedbackIE);

   /* Populate the message structure */
   ie.CID  = cid;
   ie.LAST = 0;
   ...

   ie.Next = tsnc_msg_new(wimax_mac_ARQFeedbackIE);
   ie.Next->CID  = next_cid;
   ie.Next->LAST = 1;
   ...

   /* Pack the message */
   status = tsnc_msg_pack(&ie, pdu_buf, 0, pdu_max_len * 8, pdu_len);
   if(status != TSNC_STATUS_OK)
   {
      /* Report error */
   }

   /* Convert from number of bit to number of byte */
   *pdu_len >>= 3;

   /* Finalize the message. This frees the internal message pointers recursively. */
   tsnc_msg_finalize(&ie);
}

Or in C++:

void wimax_mac_generate_ARQ_FEEDBACK
   (
      uint8  *pdu_buf,     /* Buffer to store the encoded MAC PDU */
      uint32  pdu_max_len  /* Maximum size of the buffer in bytes */
      uint32 *pdu_len      /* Output parameter returning the actual number of byte encoded */
   )
{
   wimax_mac_ARQFeedbackIE  ie;     /* The variable holding the message to be packed */
   tsnc_Status              status; /* Pack status */

   /* Populate the message structure */
   ie.CID  = cid;
   ie.LAST = 0;
   ...

   ie.Next = new wimax_mac_ARQFeedbackIE();
   ie.Next->CID  = next_cid;
   ie.Next->LAST = 1;
   ...

   /* Pack the message */
   status = ie.Pack(pdu_buf, 0, pdu_max_len * 8, pdu_len);
   if(status != TSNC_STATUS_OK)
   {
      /* Report error */
   }

   /* Convert from number of bit to number of byte */
   *pdu_len >>= 3;
}

Pack and unpack are not the only functions supported by the Runtime Library. There are 6 more functions:

4. Conclusion

With the TSN.1 Compiler, writing message parsers become quick and easy. Imagine how much more code you would have to write if you were to implement the pack and unpack functions by hand, and this is just one message!

Once you get comfortable with this process, you can really apply it to other part of your protocol stack development. For example, you can use TSN.1 to define not only over-the-air messages, but also the protocol stack internal messages. Many standards refer to them as primitives. This can really streamline your development because it makes tracing and debugging these primitives effortless. If you need to trace on a particular primitive, simply call the pack function to pack it into a buffer and send this stream of bytes over your trace interface. Your test tool picks it up from the other end, unpacks and displays it.