Chapter 6
Examples

This tutorial introduces the bases of T3DevKit through two examples.

The first one (DNSTester) is taken from the book An Introduction to TTCN-3 [1], it implements a simple DNS client for resolving an IP address. While the book focuses only on the abstract side (TTCN-3 sources) we cover the full process to make the test executable. This especially includes: writing the system adapter and the codec.

The second example (DNSTester2) is advanced version of the dns client. The TTCN-3 type definitions have been rewritten so as to provide a more accurate description of DNS messages and demonstrate the capabilities of the codec generator.

Sources files for both examples are provided in the T3DevKit package.

 6.1 DNSTester
  6.1.1 Implementing the codec
  6.1.2 Implementing the system adapter
 6.2 DNSTester2

6.1 DNSTester

DNSTester is described in details in Chapter 2 of An Introduction of TTCN-3[1]. This example is fully functional, however you will not be able to execute it if you do not provide a codec (for representing abstract TTCN-3 values in “real world” data, i.e. bits) and a system adapter (for communicating with the DNS server).

6.1.1 Implementing the codec

The syntax used for representing DNS messages is the following:

 
type integer Identification (0..65535); 
type enumerated MessageKind {e_Question, e_Answer}
type charstring Question; 
type charstring Answer; 
 
type record DNSMessage { 
  Identification        identification, 
  MessageKind           messageKind, 
  Question              question, 
  Answer                answer optional 
}

Examples of encoded DNS query and DNS answer are shown in Figures 6.1 and 6.2. They map to the following two messages used in the example.

 
template DNSMessage a_DNSQuestion := 
{ 
  identification      := 12345, 
  messageKind        := e_Question, 
  question_          := www.research.nokia.com
  answer            := omit 
} 
 
template DNSMessage a_DNSAnswer := 
{ 
  identification      := 12345, 
  messageKind        := e_Answer, 
  question_          := www.research.nokia.com
  answer            := 74.52.83.98 
}


PIC

Figure 6.1: Example of DNS query



PIC

Figure 6.2: Example of DNS answer


As we can see, transforming a DNS message between our TTCN-3 model and the “real” encoded message is not a so trivial task. The DNSMessage record is a very simplified representation of the message: several fields were removed (eg. flags, authority records...) and also the remote server may perform compression of DNS names.

The task is not impossible however since all important information is available. Implementing the CoDec will mostly consist of adding extra fields when encoding and remove them when decoding.

Let us start with the definition of DNS messages given in Rfc 1035[2]. The record DNSMessage has to be mapped to a message containing 5 sections.

[ 4.1. Format ]  
 
    +---------------------+  
    |        Header       |  
    +---------------------+  
    |       Question      | the question for the name server  
    +---------------------+  
    |        Answer       | RRs answering the question  
    +---------------------+  
    |      Authority      | RRs pointing toward an authority  
    +---------------------+  
    |      Additional     | RRs holding additional information  
    +---------------------+

The first section is the header. It contains two fields that can directly be mapped to the fields of DNSMessage. ID is mapped to Identification and the QR flag is mapped to messageKind. Every other field has to be filled on the fly by the codec so as to produce a valid message when encoding. The same applies for when decoding these fields must be processed.

[ 4.1.1. Header section format ]  
 
      0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5  
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+  
    |                      ID                       |  
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+  
    |QR|   Opcode  |AA|TC|RD|RA|   Z    |   RCODE   |  
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+  
    |                    QDCOUNT                    |  
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+  
    |                    ANCOUNT                    |  
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+  
    |                    NSCOUNT                    |  
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+  
    |                    ARCOUNT                    |  
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

Once the codec generator is lauched, the record DNSMessage is mapped to the following class:

 
namespace t3devlib { 
  namespace gen { 
 
    class DNSMessage : public t3devlib::Record 
    { 
    public
        // constructor 
        DNSMessage(); 
 
        // field identifiers 
        static const int id_identification = 0; 
        static const int id_messageKind = 1; 
        static const int id_question = 2; 
        static const int id_answer = 3; 
 
        // field accessors 
        Identification& Get_identification(); 
        MessageKind& Get_messageKind(); 
        Question& Get_question(); 
        Answer& Get_answer(); 
 
        // encoder and decoder 
        void Encode (t3devlib::Buffer& buffer) throw (t3devlib::EncodeError); 
        void Decode (t3devlib::Buffer& buffer) throw (t3devlib::DecodeError); 
 
 
        // helper functions for the decoder 
        static void SetHypLength (int length); 
        static int  GetHypLength (); 
 
        static void SetHypFieldLength (int id, int length); 
        static int  GetHypFieldLength (int id); 
 
        static void SetHypFieldIsPresent (int id, int is_present); 
        static int  GetHypFieldIsPresent (int id); 
    }
 
  } 
}

This class...

The default behaviour of the encoder is to call successively the Encode() function of each field. In our case we will need to do some additional processing for inserting missing fields in the header (Opcode, AA, TC, RD, RA, RCODE, ...). These fields have to be inserted after the second field (messageKind).

This operation is done by defining a PostEncodeField() member function. This function is called by the decoder after having decoded any field.

This function takes two parameters: the identifier of the field that has just been encoded and a reference to the buffer we are working with.

 
void DNSMessage::PostEncodeField (int id, Buffer& buffer) throw (EncodeError) 
{ 
        switch (id) { 
        case id_messageKind: { 
                // padding 
                Unsigned(15, 0x0100).Encode(buffer); 
 
                // number of questions 
                Unsigned(16, 1).Encode(buffer); 
 
                // number of answers 
                if (IsPresent (id_answer)) { 
                        Unsigned(16, 1).Encode(buffer); 
                } else { 
                        Unsigned(16, 0).Encode(buffer); 
                } 
 
                // number of authority records 
                Unsigned (16, 0).Encode(buffer); 
 
                // number of additional records 
                Unsigned (16, 0).Encode(buffer); 
 
                break
        } 
        } 
}

For each field that is to be added a Unsigned class is instantiated. The first parameter is the size of the integer in bits and the second is the initial value. Then the Encode() method is called to encode the integer into the buffer.

There is a similar work to be done in the decoder, but it is a bit more complex since the number of questions and answers is not known in advance.

 
static int dns_message_nb_queries; 
static int dns_message_nb_answers; 
 
void DNSMessage::PostDecodeField (int id, Buffer& buffer) throw (DecodeError) 
{ 
        switch (id) { 
        case id_messageKind: { 
                // padding: ignore the next 15 bits 
                Unsigned(15).Decode (buffer); 
 
                // number of queries 
                Unsigned un(16); 
                un.Decode (buffer); 
                dns_message_nb_queries = un.GetValue(); 
 
                // number of answers 
                un.Decode (buffer); 
                dns_message_nb_answers = un.GetValue(); 
 
                // number of authority records 
                un.Decode (buffer); 
 
                // number of additional records 
                un.Decode (buffer); 
 
                // tell if the optional answer field is present or not 
                if (dns_message_nb_answers == 0) { 
                        SetHypFieldIsPresent (id_answer, 0); 
                } else { 
                        SetHypFieldIsPresent (id_answer, 1); 
                } 
                break
        } 
        case id_question: { 
                // skip additional questions 
                for (int i = dns_message_nb_queries-1 ; i>0 ; i--{ 
                        Question().Decode(buffer); 
                } 
                break
        } 
        } 
}

In addition to processing the missing header fields this functions performs three operations:

Unfortunately the definition of a codec for the four other types present in the test (Identification, MessageKind, Question and Answer) cannot be done automatically by codec generator since primitive types are not yet supported. The class declarations have to be done manually.

The integer subtype Identification in declared with the macro INTEGER_DEFINITION().

 
INTEGER_DEFINITION(DNSTester, Identification, Unsigned, 16)

DNSTester is the defining module, Unsigned is the parent class (the other valid value is Signed) and 16 is the size of the integer in bits.

The enumeration MessageKind is defined as follows:

 
class MessageKind : public Enum { 
private
        static const char* fields_[]; 
public
        MessageKind() : Enum (fields_) {} 
 
        const char* GetModuleName() const { return DNSTester} 
        const char* GetTypeName() const { return MessageKind} 
 
        void Encode (Buffer& buffer) throw (EncodeError); 
        void Decode (Buffer& buffer) throw (DecodeError); 
}
 
const char* MessageKind::fields_[] = { e_Questione_Answer };

This field is mapped to the QR flag (one bit) in the DNS Header. It can be encoded and decoded with a Boolean variable.

 
void MessageKind::Encode (Buffer& buffer) throw (EncodeError) 
{ 
        Boolean mk (GetValue() == GetValueId(e_Answer)); 
 
        mk.Encode (buffer); 
} 
 
void MessageKind::Decode (Buffer& buffer) throw (DecodeError) 
{ 
        Boolean mk; 
 
        mk.Decode (buffer); 
 
        if (mk.GetValue() == false{ 
                SetValueString (e_Question); 
        } else { 
                SetValueString (e_Answer); 
        } 
}

The second section contains question records which are mapped to the question field. There may be several records while only one character string is present in DNSMessage, others will be ignored. The field QNAME is not encoded directly as a string, the name is split into labels which are encoded separately (possibly with compression).

[ 4.1.2. Question section format ]  
 
                                    1  1  1  1  1  1  
      0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5  
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+  
    |                                               |  
    /                     QNAME                     /  
    /                                               /  
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+  
    |                     QTYPE                     |  
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+  
    |                     QCLASS                    |  
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+  

The section is mapped to the following class.

 
class Question : public t3devlib::Charstring { 
public
        Question() : Charstring() {} 
 
        const char* GetModuleName() const { return DNSTester} 
        const char* GetTypeName() const   { return Question} 
 
        void Encode (Buffer& buffer) throw (EncodeError); 
        void Decode (Buffer& buffer) throw (DecodeError); 
};

Encoding and decoding DNS names is a bit too complicated to be presented in this document. For this purpose we wrote two separate functions: encode_dns_name() and decode_dns_name(). When performing compression these functions need to know where is the starting position of the DNS message in the buffer, so a third function (set_dns_message_origin()) has been written for this purpose.

 
void set_dns_message_origin (const t3devlib::Buffer& buffer); 
 
void encode_dns_name (const Charstring& name, Buffer& buffer) throw (EncodeError); 
void decode_dns_name (Charstring& name, Buffer& buffer) throw (DecodeError);

set_dns_message_origin() is called before encoding and before decoding the DNS message.

 
void DNSMessage::PreEncode (Buffer& buffer) throw (EncodeError) 
{ 
        set_dns_message_origin (buffer); 
} 
 
void DNSMessage::PreDecode (Buffer& buffer) throw (DecodeError) 
{ 
        set_dns_message_origin (buffer); 
}

The two other functions are used in Encode() and Decode().

 
void Question::Encode (Buffer& buffer) throw (EncodeError) 
{ 
        encode_dns_name(*this, buffer); 
 
        // type A 
        Unsigned(16, 1).Encode(buffer); 
 
        // class IN 
        Unsigned(16, 1).Encode(buffer); 
} 
 
void Question::Decode (Buffer& buffer) throw (DecodeError) { 
        decode_dns_name(*this, buffer); 
 
        // read (and ignore) type & class 
        Unsigned(32).Decode(buffer); 
}

The third section contains resource records describing the answers. Whereas we generate queries with only one question we can get several answers in the reply, typically when the name queried is an alias (CNAME). The codec has to process every resource record and select the one that contains the final answer, i.e. the IP address of the host.

The IP address is binary encoded on 32-bit, it has to be converted into the dot notation to be presented to the test.

Also we may get answers of an unknown type. In that case the record must be skipped, knowing that the length of the resource data is given by the field RDLENGTH.

[ 4.1.3. Resource record format ]  
 
      0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5  
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+  
    |                                               |  
    /                                               /  
    /                      NAME                     /  
    |                                               |  
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+  
    |                      TYPE                     |  
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+  
    |                     CLASS                     |  
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+  
    |                      TTL                      |  
    |                                               |  
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+  
    |                   RDLENGTH                    |  
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|  
    /                     RDATA                     /  
    /                                               /  
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

Class Answer is similar to Question. Also two functions were written for converting the IP address from and into the dot notation.

 
class Answer : public t3devlib::Charstring { 
public
        Answer() : Charstring() {} 
 
        const char* GetModuleName() const { return DNSTester} 
        const char* GetTypeName() const   { return Answer} 
 
        void Encode (Buffer& buffer) throw (EncodeError); 
        void Decode (Buffer& buffer) throw (DecodeError); 
}
 
void encode_ip_address (const Charstring& ip_charstring, Buffer& buffer) throw (EncodeError); 
void decode_ip_address (Charstring& ip_charstring, Buffer& buffer) throw (DecodeError);

To encode the answer we need to get the dns name of the query which is present in the question. This is done by accessing the parent which is supposed to be a DNSMessage.

 
void Answer::Encode (Buffer& buffer) throw (EncodeError) { 
 
        Variable* parent = GetParent(); 
        DNSMessage* msg; 
        if (parent && (msg = dynamic_cast<DNSMessage*> (parent))) { 
                encode_dns_name (msg->Get_question(), buffer); 
        } else { 
                cerr << Answer::Encode(): warning: there is no question field << endl; 
                Unsigned(8).Encode(buffer); // empty string 
        } 
 
        Unsigned u16(16), u32(32); 
 
        // type -> A 
        u16.SetValue (1); 
        u16.Encode (buffer); 
 
        // Class -> IN 
        u16.SetValue (1); 
        u16.Encode (buffer); 
 
        // TTL -> 1 day 
        u32.SetValue (86400); 
        u32.Encode (buffer); 
 
        // length -> 4 bytes 
        u16.SetValue (4); 
        u16.Encode (buffer); 
 
        // Resource data -> ip address 
        encode_ip_address(*this, buffer); 
}

Decoding the answer is less straightforward. Several answers may be present and we have to select the right one (whose class is IN and type is A).

 
void Answer::Decode (Buffer& buffer) throw (DecodeError) { 
        Charstring cs; 
        Unsigned clas(16), type(16), ttl(32), length(16), ip(32); 
 
        for (int i = dns_message_nb_answers ; i>0 ; i--{ 
                // read the name 
                decode_dns_name (cs, buffer); 
 
                // read type & class 
                type.Decode (buffer); 
                clas.Decode (buffer); 
                ttl.Decode (buffer); 
                length.Decode (buffer); 
                if ((clas.GetValue() == 1) &&   // IN class 
                    (type.GetValue() == 1) &&   // A record 
                    (length.GetValue() == 4)) { // correct size 
                        decode_ip_address (*this, buffer); 
 
                        // move to the end of the buffer 
                        buffer.SetPosition (buffer.GetEndMarker()); 
                        break
                } else { 
                        // move forward to the next answer 
                        buffer.SetPosition (buffer.GetPosition() + length.GetValue()*8); 
                } 
        } 
}

6.1.2 Implementing the system adapter

The system adapter is needed for exchanging messages between the test and the system under test by implementing the communication ports.

In TTCN-3 the port for interfacing with the recursive DNS server is defined as follows:

 
type port DNSPort message { 
  inout DNSMessage 
}

TODO

6.2 DNSTester2

TODO