Chapter 4
Implementing Adapters

Adapters are essential parts for building an Executable Test Suite. Their role is to make the link between the TTCN-3 Abstract Test Suite and the platform executing the test.

The TTCN-3 environment provides one standardised interface: the TRI (TTCN-3 Runtime Interface) for implementing the adapters. It is divided into two components:

T3DevLib provides an implementation of the TRI and a set of C++ classes for easing the development of the adapters.

This section starts with a description of classes used for representing and manipulating data from the TRI, then presents how to implement communication ports and SUT actions (for the System Adapter) and external functions and timers (for the Platform Adapter).

 4.1 Manipulating TRI structures
 4.2 Communication Ports
  4.2.1 Basics
  4.2.2 Map & Unmap operations
  4.2.3 Sending and Receiving messages
  4.2.4 Registering ports
  4.2.5 Summary
 4.3 SUT Actions
 4.4 External Functions
  4.4.1 Basics
  4.4.2 Registering external functions
  4.4.3 Accessing parameters
  4.4.4 Returning values
  4.4.5 Summary
 4.5 Timers
  4.5.1 Simulated Time
 4.6 Reusing existing Adapters

4.1 Manipulating TRI structures

The TTCN-3 Runtime Interface defines several structures for exchanging data and referencing components and ports: QualifiedName, BinaryString, TriComponentId, TriPortId, TriTimerId and TriParameterList.

Any TRI implementation is expected to handle them, which implies tedious operations (manipulating pointers, allocating and deallocating memory, parsing and comparing the contents).

To ease this work, T3DevLib provides a set of C++ classes for embedding these structures, accessing their content and performing most of the work safely and transparently. The mapping is shown in Table 4.1.




TRI structure T3DevLib equivalent
class


BinaryString Bitstring
QualifiedName QName
TriComponentId ComponentId
TriPortId PortId
TriTimerId TimerId
TriParameterList ParameterList



Table 4.1: Mapping of TRI structures

Generally the user does not have to instantiate any of these classes, except Bitstring.

PortId, ComponentId and TimerId are used for referencing TTCN-3 ports, components and timers. T3DevLib manages its own pool for these objects, they are created on the fly and destroyed at the end of the testcase. It will never create two instances of the same class representing the same port or component. Thus the address of a PortId, ComponentId or TimerId object is sufficient for identifying uniquely a port, component or timer during a testcase.

4.2 Communication Ports

T3DevLib provides a smart way of implementing communication ports. Each port type declared in TTCN-3 is mapped to one C++ class derived from the root class t3devlib::Port. Essential functions are provided by the library : multiplexing API calls, handling and storing informations about connected ports.

An example of port implementation is described in section 6.1.2.

4.2.1 Basics

For each port definition a C++ class has to be written.

For example with the following TTCN-3 code a type of port named EchoPort. The purpose of this port will be to send back (echo) all the messages that are sent to it.

 
module MyModule { 
  type port EchoPort message { 
    inout all 
  } 
}

The minimal c++ class that must be defined to implement the port is the following one.

 
class EchoPort : public Port 
{ 
public
        EchoPort (PortId& id); 
 
protected
        bool Map (const PortId& port_id); 
        bool Unmap (const PortId& port_id); 
        bool Send (const ComponentId& from, const Bitstring& msg); 
};

The constructor is public and takes one parameter, which identifies the new port in the current testcase. This id must be forwarded to the parent constructor.

 
EchoPort::EchoPort (PortId& id) : 
        Port (id) 
 
{ 
}

Other methods are used for implementing port actions. They all return a boolean value, true means that the action was performed successfully.

4.2.2 Map & Unmap operations

Map() and Unmap() are called by T3DevLib when a system port is mapped or unmapped to a port of the testcase (using TTCN-3 instructions map and unmap).

They take one parameter which is the identifier of the testcase port that is being mapped to this port.

These functions can be used for initialising and releasing the underlying hardware, open and close a socket, start a listening thread...

For the echo port, there is no special action to do, just return true:

 
bool EchoPort::Map (const PortId& connected_port_id) 
{ 
  return true
} 
 
bool EchoPort::Unmap (const PortId& connected_port_id) 
{ 
  return true
}

Once a port is successfully mapped, it appears in the list of connected ports accessible with GetMappedPortIdList().

4.2.3 Sending and Receiving messages

When a message is sent to a system port by the testcase, T3DevLib calls the Send() member function of the corresponding port in C++.

It takes two parameters: the identifier of the component, that sent the message and the message itself.

Received message can be transferred to the testcase by calling Port::EnqueueMsg(). It also takes two parameters: the identifier of the component, the message is sent to and the message itself.

For the case of the Echo port, the message is sent back in Send().

 
bool EchoPort::Send (const ComponentId& from,const Bitstring& msg) 
  return EnqueueMsg (from, msg); 
}

4.2.4 Registering ports

Before executing a testcase it is necessary to register each port type. This is needed for instantiating correctly the port at the beginning of the testcase.

This is done at System Adapter initialisation (in SAInit()) with the static function Port::Register(). The function takes three parameters: the class implementing the port (as a template parameter), the name of the defining module and the name of the port.

 
void SAInit() 
{ 
  Port::RegisterType<EchoPort> (MyModuleEchoPort); 
}

4.2.5 Summary

An overview of the typical operation of a System Adapter implementation is shown in Figure 4.1. It features the implementation of two different ports MyPortType1 and MyPortType2.

This scenario contains several stages:

  1. Initialisation: the SA registers its two port implementations
  2. Testcase start: T3DevLib instantiates system ports needed for the testcase
  3. Testcase body:
    1. ports are mapped to the test system ports using Map()
    2. messages are exchanged using Send() and EnqueueMsg()
    3. ports are unmapped using Unmap()
  4. Testcase termination: ports are deleted and the SA is reinitialised for the next testcase.


PIC

Figure 4.1: Typical MSC for a System Adapter implementation


4.3 SUT Actions

T3DevKit provides a simple mechanism for handling informal actions to be performed on the System Under Test with the TTCN-3 action instruction.

Actions shall be implemented as a function and registered using SUTAction::Register() during the initialisation of the System Adapter. Then, when the action instruction is invoked in the TTCN-3 code the corresponding function will be executed.

These functions take one parameter (the textual description of the action) and return a boolean: true if successful, false otherwise.

Several actions may share the same handler function for several actions, in that case they can be distinguished with the parameter.

At runtime if the requested action is not registered, then T3DevKit will print the raw text to the standard output and will ask for user confirmation to proceed.

The following example show how to implement and register three actions named “Action1”, “Action2” and “Action3”. “Action1” has its own handler and the two others share the same handler.

 
bool Action1 (const char* action) 
{ 
  cout << Action1: Executing Action 1 << endl; 
  return true
} 
 
bool Action2or3 (const char* action) 
{ 
  cout << Action2or3: Executing  << action << endl; 
  return true
} 
 
namespace t3devlib 
{ 
  void SAInit () 
  { 
    // register Action 1” using Action1() as handler 
    SUTAction::Register (Action 1, &Action1); 
 
    // register Action 2” and Action 3” using Action2or3() as handler 
    SUTAction::Register (Action 2, &Action2or3); 
    SUTAction::Register (Action 3, &Action2or3); 
  } 
}

These three actions can be invoked in TTCN-3 as follows:

 
testcase ... 
{ 
  ... 
 
 action (Action 1); // --> will output Action1: Executing Action 1” 
 
 action (Action 2); // --> will output Action2or3: Executing Action 2” 
 
 action (Action 3); // --> will output Action2or3: Executing Action 3” 
 
 action (Action 4); // --> unregistered action will output a special message: 
                      //     ‘‘Informal action requested: Action4’’ 
                      //     and will wait the user to press Enter 
  ... 
}

4.4 External Functions

For handling external functions, T3DevLib provides:

4.4.1 Basics

For each external function definition a C++ function has to be written.

For example with the following TTCN-3 code an external function named EchoFunction. The purpose of this function is to return (echo) the parameter that is given.

 
module MyModule 
{ 
  external function EchoFunction (in octetstring val) return octetstring
}

It is implemented in C++ as follows.

 
bool FrameLength (ParameterList& param_list, Bitstring& return_value) 
{ 
  return_value.SetValueBin (param_list[0]); 
 
  return true
}

The function takes two parameters: a reference to the list of parameters used to call the function and a reference to the variable that stores the result of the function.

It returns true when the function was successfully executed. Returning false will trigger an error verdict for the current testcase.

4.4.2 Registering external functions

Like ports, external functions must be registered before executing a testcase. This is needed for calling the correct function, when triExternalFunction() is called.

This is done at Platform Adapter initialisation (in PAInit()) with the static function ExternalFunction::Register(). The function takes three parameters: the name of the defining module, the name of the function and a pointer to the function implemented in C++.

 
void PAInit() 
{ 
  Port::RegisterType(MyModuleEchoFunction, &EchoFunction); 
}

4.4.3 Accessing parameters

Parameters are handled with the ParameterList class. This class is a wrapper for the standard TRI structure TriParameterList. It allows easy and safe access to the values without manipulating pointers.

The number of parameters is known with GetSize() and each parameter can be accessed with GetParam[] or with the operator [], which both return a reference.

The type of every parameter and return value is Bitstring. Parameters are encoded before calling the function and return values are decoded after returning.

It is a good practise to check the consistency of the parameters with assertions before executing a function, especially because errors can occur in the codec before the function call.

This is demonstrated in the next example, which implements a function for performing a bitwise AND on two 8-bit integers.

Definition in TTCN-3:

 
module MyModule 
{ 
  type integer UInt8 (0 .. 255); 
 
  external function UInt8BitwiseAnd (in UInt8 a, in UInt8 b) return UInt8; 
}

Implementation in C++:

 
bool UInt8BitwiseAnd (ParameterList& param_list, Bitstring& return_value) 
{ 
  // check the consistency of parameters 
  assert (param_list.GetSize() == 2); 
  assert (param_list[0].GetLength() == 8); 
  assert (param_list[1].GetLength() == 8); 
 
  unsigned char result; 
 
  // perform the operation 
  result = *(param_list[0].GetValueBin()) & *(param_list[1].GetValueBin()); 
 
  // set the returned value 
  return_value.SetValueBin (&result, 8); 
 
  // return successful status 
  return true
}

4.4.4 Returning values

There are two possible ways of returning values to the test from an external function:

The first method is described in the previous sections and is pretty simple. A reference to an empty Bitstring variable is provided to the function. The function just has to store the result in the variable.

The second method is less straightforward because of an issue with memory allocation. out and inout variables need to be instantiated during the function call and freed afterwards. However there is no way of knowing when a variable is no longer used by the Test Executable.

The best practise we found is to use static variables. This is safe since two function calls cannot occur simultaneously.

The parameters can be returned with ParameterList::SetParam() which takes and keeps a reference to a memory buffer or a Bitstring.

The example given in the previous section can be rewritten as follows.

Definition in TTCN-3:

 
module MyModule 
{ 
  type integer UInt8 (0 .. 255); 
 
  external function UInt8BitwiseAnd (in UInt8 a, in UInt8 b, out UInt8 result); 
}

Implementation in C++:

 
bool UInt8BitwiseAnd (ParameterList& param_list, Bitstring& return_value) 
{ 
  // check the consistency of parameters 
  assert (param_list.GetSize() == 3); 
  assert (param_list[0].GetLength() == 8); 
  assert (param_list[1].GetLength() == 8); 
 
  // static variable for storing the result 
  static unsigned char result; 
 
  // perform the operation 
  result = *(param_list[0].GetValueBin()) & *(param_list[1].GetValueBin()); 
 
  // set the returned parameter 
  param_list.SetParam(2, &result, 8); 
 
  // return successful status 
  return true
}

4.4.5 Summary

An overview of the typical operation of a System Adapter implementation is shown in Figure 4.2. It features the implementation of two different functions MyFunc1 and MyFunc2.

This scenario contains several stages:

  1. Initialisation: the PA registers its two port implementations
  2. Reset: PAReset() is called before executing each testcase
  3. Testcase body: function are called directly by T3DevLib


PIC

Figure 4.2: Typical MSC for External Functions implementation


4.5 Timers

T3DevLib provides and implementation of TTCN-3 timers based on the POSIX Threads API. It is activated by default and should fit the needs of most users. Except in special cases (like need of a high precision timer or simulated time) this section can be skipped.

A custom timer is implemented with a class derived from the abstract base class Timer. Several timer implementations may coexist, but only one can be active at the same time. The active timer implementation is selected during Platform Adapter Reset before the beginning of the testcase. If no implementation is chosen, then T3DevLib uses its own internal timers.

The selection is performed with Timer::SetImplementation(). It takes one parameter: a pointer to a function that instantiate a new timer (using instruction new) and return a pointer to the newly created timer. This function should be generated with template createTimer<>.

 
// custom timer definition 
class MyTimer : public t3devlib::Timer 
{ 
  ... 
}
 
namespace t3devlib 
{ 
  // Platform Adapter Reset 
  void PAReset() 
  { 
    Timer::SetImplementation (&createTimer<MyTimer>); 
  } 
}

A timer implementation provides a constructor and four primitives. The constructor takes exactly one parameter: the runtime identifier (TimerId) of the timer. The four primitives are Start(), Stop(), Read() and IsRunning(), they map directly to the corresponding TTCN-3 instructions: start, stop, read and running. When a timer expires, the Timeout() method must be called.

Each of these functions must return immediately. Therefore monitoring active timers and reporting timeouts should be performed in a separate thread.

A typical implementation of a timer should look as follows:

 
// custom timer definition 
class MyTimer : public t3devlib::Timer 
{ 
  // Constructor 
  MyTimer (TimerId& timer_id) 
    : Timer (timer_id) 
  { 
    // some initialisation 
    ... 
  } 
 
  void Start (double duration) 
  { 
    // start the timer 
    ... 
  } 
 
  void Stop() 
  { 
    // stop the timer 
    ... 
  } 
 
  double Read() 
  { 
   // return the number of seconds elapsed since the timer was started 
   // or zero if the timer is inactive 
    ... 
  } 
 
  bool IsRunning() 
  { 
    // return true if the timer is running 
    ... 
  } 
 
 
  static void InitTimerThread () 
  { 
    // start a thread for monitoring timers and reporting timeout to the Test Executable 
    ... 
  } 
 
}
 
 
namespace t3devlib 
{ 
  // Platform Adapter Initialisation (called before the first testcase) 
  void PAInit() 
  { 
    MyTimer::InitTimerThread(); 
  } 
 
  // Platform Adapter Reset (called before every testcase) 
  void PAReset() 
  { 
    Timer::SetImplementation (&createTimer<MyTimer>); 
  } 
}

Examples of timer operation are shown in Figure 4.3 (basic operation with one timer) and Figure 4.4 (concurrency with two timers).


PIC

Figure 4.3: Typical MSC for a Timer implementation



PIC

Figure 4.4: Typical MSC with multiple timers


Further information can be found in the API documentation for class Timer and ultimately the DefaultTimer implementation in T3DevLib sources shows an example of implementation.

4.5.1 Simulated Time

T3DevKit provides an alternative implementation of timers implementing simulated time. This may be useful when running tests on pre-recorded traces (eg. a tcpdump capture file). In such cases there are no needs to run the tests in real time, so it is preferable to run them in simulated time in order to speed up the execution.

Initialisation

Simulated time is provided by the VirtualTimer class. In order to use it, you need to select at the beginning of a testcase when the Platform Adapter is reset.

 
namespace t3devlib 
{ 
  void PAReset() 
  { 
    Timer::SetImplementation (&createTimer<VirtualTimer>); 
  } 
}

This virtual timer class implements an event queue and a virtual clock. The clock it not initialised when the testcase starts and the event queue is empty (in other words there is no notion of time yet).

Once the testcase is started, the implementation will wait for the first events to be scheduled (eg. timers, messages) and then it will initialise the clock and start dispatching the events.

Note: the clock is initialised to the time of the first event with an absolute time (usually the first received message).

Scheduling events

When running in simulated time, it is necessary to synchronise the external events (eg. received messages) with the simulated clock. For example if a system port reads it messages from a capture file, the System Adapter should ensure that the messages are dispatched according to their timestamp (so that they are seen at the right time in the testcase).

The VirtualTimer class provides two functions for scheduling and cancelling events: NewEvent() and ClearEvent().

 
class VirtualTimer 
{ 
public
    static void NewEvent (const void* tag, const boost::xtime& absolute_time, const boost::function<void()>& handler); 
 
    static void ClearEvent (const void* tag); 
 
    [...] 
};

The NewEvent() function takes three parameters:

ClearEvent() is used to cancel all upcoming events with a given tag.

Examples:

 
//////////////////////////////////////////////////////////////////////// 
// Basic example with a callback to an ordinary function 
// 
 
// callback function 
void my_handler(); 
 
 
[...] 
 
 
 
// schedule an event at epoch+10000.2 seconds which will trigger a call to my_handler() 
boost::xtime timestamp = {10000, 200000000}
VirtualTimer::NewEvent (this, timestamp, &my_handler); 
 
 
 
 
//////////////////////////////////////////////////////////////////////// 
// A little more elaborate example using a callback to a member function 
// 
 
class MyPortImplementation : public t3devlib::Port 
{ 
private
  // callback member function 
  void MyHandler(); 
 
 
public
  // schedule an event in 10 seconds which will trigger a call to this->MyHandler() 
  void ScheduleAnEventIn10Seconds() 
  { 
    boost::xtime timestamp = VirtualTimer::GetCurrentTime(); 
    timestamp.sec += 10; 
 
    VirtualTimer::NewEvent (this, timestamp, boost::bind (&MyPortImplementation::MyHandler, this)); 
  } 
 
};

4.6 Reusing existing Adapters

This feature was removed in T3DevKit v0.10