OpenTTCN/Knowledge base/Creating Adapters with Adapter Framework

From OpenTTCN

Jump to: navigation, search

  OpenTTCN DocZone

  Home | Developer's corner | Knowledge base | Working documents | Documentation | OpenTTCN IDE | Tutorials | Training | How do I | Frequently asked questions | Technical support


Creating Adapters with Adapter Framework


This article describes work in progress. The OpenTTCN Adapter Framework discussed here, and the included codecs and adapters, are still a work-in-progress prototype. API changes are still likely to happen, so exercise suitable caution. Information on this page can change as the implementation evolves.

The code presented in this article requires OpenTTCN Tester 2012 4.2.0beta6.0 or newer, OpenTTCN SDK for C# and OpenTTCN Adapter Framework. If you want to be posted on the most recent news on this topic, please contact us at support@openttcn.fi

This article explains the APIs available in the OpenTTCN Adapter Framework for instantiating adapters for different protocols. Most prominently, adapters for protocols implemented over TCP are demonstrated, although other transports will likely be added in the future. OpenTTCN Adapter Framework is developed on top of OpenTTCN SDK for C# to provide ready made components for simplified adapter development.

Usually for higher level protocols a stack of codecs is required. For example, SOAP based protocols use a stack of TCP, HTTP, XML, SOAP with the target protocol on top of SOAP. You could go lower with IP and MAC protocols as well, but the lowest transport layers are usually tested separately. The OpenTTCN Adapter Framework lets you instantiate a codec for such a protocol with very little to no code written. All you need to provide is how the existing components are connected together.

For a working adapter, at minimum two components are required

  • TCI-CD Codec
    Possibly an OpenTTCN Codec Component, this component encodes and decodes the messages from/to TTCN-3 data types.
  • TRI-SA Adapter
    Implements the transport layer, this delivers messages from/to the implementation to the codec for decoding.

As you might have noticed, the TRI-SA is required to deliver complete messages for coding and decoding. Because TCP is a stream based protocol, a TCP adapter also requires a third component to bind together the SUT Adapter and the Codec. This is required for encapsulation, as TTCN-3 requires the SA to pass on message as suitable pieces for the Codec to decode. For stream protocols, a Chunking Mode is required to perform this task. Chunking Modes examine and buffer received stream data and extract complete messages from the stream for passing on to the codec.

Contents

Chunking Modes

A Chunking Mode is always required for a stream based adapter. The task of the Chunking Mode is to detect message boundaries from a received stream and to split the stream of bytes to one or multiple messages that each can be separately handled by TTCN-3 decoder as a complete message. Sometimes this task requires buffering until more data has been received from a stream. Sometimes this task requires splitting of data received from the stream to two or more messages.

Some Chunking Modes are already provided in the framework, but for more complex protocols you may have to implement your own. Some of the included Chunking Modes are

  • StreamChunkingMode
    Simply forward data received from the OS straight to the codec. Good for simple protocols, where you can do further processing in TTCN-3. Not very practical chunking mode, as TCP makes no guarantees how messages are cut for transport and delivery. No buffering.
  • SeparatorChunkingMode
    Splits the stream based on some byte sequence. Most common use is with text based protocols, where the line terminator can be used as the separator for messages, with each line decoded separately. Buffering.
  • LengthTagChunkingMode
    This is for length tag protocols. Assumes there is a length tag with fixed number of bytes at the beginning of the message (eg. 32-bit/4 byte unsigned integer in Network Byte Order). Buffering. Planned addition is an offset parameter for protocols where the length is in a header at a fixed location (eg. UDP/TCP protocols themselves).
  • HttpChunkingMode
    This is included with the HTTP Codec. HTTP 1.1 is an example of a protocol requiring complex processing to split messages, as there are multiple headers that decide whether message length is decided by connection closure, a length value in the header or by chunked transfer encoding. There is no generic way to split this into messages, so a custom ChunkingMode had to be made.

Additionally there are two classes for implementing your own ChunkingMode:

  • ChunkingMode
    Defines the interface for Chunking Modes. Inherit this class and implement handleReceivedData(byte[],int), copy() and reset() methods to create your own Chunking Mode.
    User's handleReceivedData receives data when new data is available in the stream port to which this Chunking mode is associated. User may add, buffer, change, or discard the data. User implementation makes callback to provide data back to the stream port to which this Chunking mode is associated.
  • BufferingChunkingMode
    Base class implementing buffering of messages, intended mostly for simplifying development of your ChunkingMode. This class implements the ChunkingMode interface, and instead requires you to implement a function findMessageEnd, which is given the current buffered data and should return an index to the end of the first message, or -1 if there is yet insufficient data for a message.
    User's findMessageEnd receives call when there is new data available in the stream port to which this Chunking mode is associated or when there is still old data in the internal buffer and the last call to FindMessageEnd did not return -1. User may change the data. User may not add, buffer, or discard the data. User implementation returns -1 if there given buffer does not contain a complete message and new data is required, alternatively implementation returns information where the end of complete message resides.
    Also usable as is for client protocols with a single request/response pair per connection, as the received message will be passed forward on connection termination (eg. HTTP 1.0)

Simple TCP Adapter

Simplest possible TCP Adapter:

Image:language_mapping_c_sharp.png

static void Main(string[] args)
{
    StartHere.Initialize();
    TcpPort myPort = new TcpPort("mysession","myTSIPortId");
    Console.ReadLine();
}

This instantiates a TCP client port, which uses the StreamChunkingMode to split messages (ie. sends in whatever pieces TCP recv() returns) and uses TcpCodecComponent http://www.openttcn.com/Codecs/2011/TCP for coding and decoding. TcpCodecComponent is the base TCP codec, which will simply pass forward messages as an Octetstring derived type, and additionally handles the TCP control messages. This Codec Component must always be used with the TCP TRI-SA component.

Of course, for a real protocol, handling the raw bytes in TTCN-3 is not efficient. This is where the protocol stack comes into play.

Protocol Stack

The TCP Adapter provides a way to provide your own coding / decoding for TCP messages. This is done by supplying TcpCodecComponent with another Codec Component that will be used to decode TCP messages. The TcpCodecComponent is still required as an intermediary as it handles the tcp connection management messages (like connection closure notification).

A solid example is registering the HttpCodecComponent (http://www.openttcn.com/Codecs/2011/TCP) for decoding the TCP stream. You must also register the HttpChunkingMode with the TcpPort, to provide proper message chunking for the HTTP Codec.

With these simple additions, you now have an HTTP Adapter. You might also want to set some of the properties in the TcpPort class to provide for smoother usage, eg. setting ConnectAutomatically = true, NotifyOnAutoConnect = false, to rid yourself of connection establishment related messages, as tracking connections is usually not interesting when testing protocols built above HTTP.

In the same way, you can register the XML Codec Component into the HTTP Codec Component, to decode HTTP payloads as XML documents. If your testing session contains the SOAP XSD definitions, you can then use your adapter as a SOAP Adapter.

You can also implement your own Codec Component and plug it in at any part of the stack.

Adding Codec for Custom Protocol

If your protocol is more complex, the generic approach might be insufficient, and you may have to implement your own ChunkingMode and Codec Component.

As an example, lets imagine a protocol with three message types. Each message is preceded with a 1-byte tag. There are three types of messages:

  1. 1-byte tag only
  2. tag indicating a pascal string (1 byte tag + 1 byte length field + N bytes of string data)
  3. tag indicating a XML envelope, followed by 4 byte unsigned length tag in Network Byte Order, followed by XML data of specified length.

To handle this protocol, you would first implement a chunking mode that will correctly cut the stream to messages. The CustomProtocolChunkingMode would inherit from BufferingChunkingMode and implement findMessageEnd(...) so that it reads the 1 byte tag, then:

  • If its the base tag, indicate theres a message end at 1 byte.
  • If its the pascal string, read the length byte, and if there's sufficient data in the buffer, return the end of the message based on the length.
  • If its the XML tag, read the 4 byte length and decode it, then if there sufficient data in the buffer, return the end of the message based on the length.

After this, you need Coding and Decoding. You would need to implement a CustomProtocolCodecComponent, which decodes the previously chunked messages. This Codec Component can delegate some of the work to other codecs, for example the XML message portion of message type 3 could be passed on to the XML Codec Component for encoding and decoding.

Example - NETCONF

To make all this concrete, lets make an adapter for a real world protocol. We'll be making an adapter for testing NETCONF protocol over SOAP. For the TTCN-3 side of things, like how to use the XML Schema Definitions with the XML Translator and XML Codec Component to write your test suite, refer to the article Creating NETCONF Over SOAP XML Codec.

To have a working test suite, we'll develop a mirror test suite where two tester instances are running in separate sessions, one working as a NETCONF server, another as NETCONF client.

If you are new to creating OpenTTCN Adapters, its also recommended you familiarize yourself with the basics of creating adapters using C#.

Let's examine what's needed for NETCONF over SOAP. Its a SOAP protocol, which is XML. The XML Codec will handle the SOAP and NETCONF types. Next we need the transport, which is HTTP running over TCP. So our protocol stack will look like:

  • NETCONF
  • SOAP
  • HTTP
  • TCP

NETCONF Adapter Code

Let's look at our main function:

Image:language_mapping_c_sharp.png

First some argument parsing and the standard initialization code.

static void Main(string[] args)
{
    Console.WriteLine(_programName + " " + _version + " Copyright 2012 OpenTTCN Ltd");
    Console.WriteLine();
    Console.WriteLine("Created with OpenTTCN SDK for C# " + StartHere.Version);
    Console.WriteLine();

    // simple command line parsing: netconf.exe server|client sessionName [portName portNumber]
    if (!(args.Length == 2 || args.Length == 4)) {
        Console.WriteLine("Invalid commandline, usage:\nnetconf.exe server|client sessionName [portName portNumber]");
        Console.WriteLine();
        return;
    }
    bool server = (args[0] == "server"); //otherwise assume "client"
    string session = args[1];
    string portName = "tsiPort";
    int portNumber = 1111;
    if (args.Length == 4) {
        portName = args[2];
        portNumber = int.Parse(args[3]);
    }

    Console.WriteLine(String.Format(
        "Session {0}: Starting NETCONF {1} port '{2}'\nListening on 127.0.0.1:{3}",
        session,
        (server?"server":"client"),
        portName,
        portNumber));
    Console.WriteLine();

    // Initialize OpenTTCN SDK
    StartHere.Initialize();

Then we instantiate a client or server port based on the command line arguments. We're using the hardcoded address 127.0.0.1:1111 for both client and server (since they'll be connecting to each other).

    // Instantiate TCP Port
    TcpPort myPort;
    if (server)
    {
        myPort = new TcpPort(session, portName, "127.0.0.1", portNumber);
    }
    else
    {
        myPort = new TcpPort(session, portName);
        myPort.ConnectAutomatically = true;
        myPort.ConnectAddress = "127.0.0.1:" + portNumber.ToString(); // connect to HTTP server on localhost
    }

Next we need to get references to our codec components and configure them

    // Obtain Codec Components
    ICodecComponent ccHttp = StartHereCD.GetCodecComponent("http://www.openttcn.com/Codecs/2011/HTTP");
    ICodecComponent ccXml = StartHereCD.GetCodecComponent("http://www.openttcn.com/Codecs/2011/XML");

    // Configure XML Codec Component to use <?xml ... ?> header
    ccXml.SetProperty("ENABLE-XML-DECLARATION", true);

Now we need to put it all together: Associate the HTTP Codec Component with the TCP Codec so that HTTP messages are encoded and decoded with it, and then tell the HTTP Codec Component that the HTTP request/response body should be coded with the XML Codec Component (which can handle NETCONF).

    // Connect the codecs together XML <-> HTTP <-> TCP
    ccHttp.SetProperty("innerCodec", ccXml);
    myPort.Codec.SetProperty("innerCodec", ccHttp);

Finally, setup the HttpChunkingMode to handle chunking of messages:

    // Set HTTP Chunking Mode as required by HTTP codec
    myPort.setChunkingMode(new HttpChunkingMode());

    Utils.WaitForKey("Press ENTER to exit...");

    StartHere.UnregisterAllAdapters(session);
}

And that's all that's required. It's that simple to make an adapter for an SOAP-based protocol.

NETCONF Mirror Test Suite

Once you have the adapter running, you'll need to import the test suite. Download the package from Source code section. The test suite is in the archive in testsuite/netconf and testsuite/netconf_server directories. The two projects can be imported directly into Tester IDE, and provide launch configurations. Importing is done by right-clicking inside the File Browser then selecting Import... and using the Import Existing Projects wizard. The two test suites are simply copies of each other, to provide separate testing session for the client and server.

To launch the tests, first setup everything in Tester and make sure compilation was successful (Answer yes to the question about enabling XML Schema Compilation). Setup required TTCN-3 libraries by adding -lAdapterLib option to additional import arguments via right-clicking the project and adding said value to OpenTTCN -> OpenTTCN Compiler.

Next, build the NETCONF example adapter, and launch two copies of it, with following arguments:

  • otnetconf.exe client netconf
  • otnetconf.exe server netconf_server

After this, step into the IDE and launch the "netconf server" launch configuration - it will execute the testcase TC_Netconf_Server() in the "netconf_server" project. Once you have that started, you can launch the "netconf client" launch configuration, which will run the TC_Netconf_Client() testcase from the "netconf" project. The messages will be sent and received, and you'll have two test campaign logs in the "Text log" view showing PASS verdicts.

If something went wrong, you should check the test campaign logs for reason of the error, and the adapter console windows for any exceptions to find out what the problem is.

Example - Custom Chunking Mode

Some protocols require a custom Chunking Mode. For demonstation purposes, we'll go through one of the chunking modes included with the adapter framework, SeparatorChunkingMode:

Image:language_mapping_c_sharp.png

To start off, we inherit from BufferingChunkingMode, as that handles the nitty-gritty of buffering data and removing pieces from the buffer to send them forward.

public class SeparatorChunkingMode : BufferingChunkingMode
{
    private byte[] _separator;

Constructors are pretty simple. We add one argument: the byte[] separator to split on.

    public SeparatorChunkingMode(byte[] separator)
        : base()
    {
        _separator = new byte[separator.Length];
        Array.Copy(separator, _separator, _separator.Length);
    }

    public SeparatorChunkingMode(
        HandleReceivedMessageDelegate handler,
        byte[] separator) : base(handler) {
        _separator = new byte[separator.Length];
        Array.Copy(separator, _separator, _separator.Length);
    }

Next we implement findMessageEnd from BufferingChunkingMode. We search the buffer, starting from offset and see if we can find the separator byte sequence. If we find a match, we return the index to the end of it (including the separator). Otherwise we return -1.

    protected override long findMessageEnd(byte[] buffer, long offset, long length)
    {
        long index = -1;
        for (long i = offset;
            i < length - (_separator.Length - 1); i++)
        {
            bool matches = true;
            for (int j = 0; j < _separator.Length; j++)
            {
                if (buffer[i + j] != _separator[j])
                {
                    matches = false;
                    break;
                }
            }
            if (matches)
            {
                index = i + _separator.Length;
                break;
            }
        }
        return index;
    }

Finally it is necessary to override the factory method to produce objects of this kind with clean state but same configuration, which in this case means simply returning a new object.

    public override ChunkingMode copy()
    {
        return new SeparatorChunkingMode(_separator);
    }
}

We didn't have to override the reset() method, as we do not remember any state. If there was remembered state, we would have to implement it and remember to call the base class reset method as well, to ensure the state reset propagates correctly. Example from HttpChunkingMode:

Image:language_mapping_c_sharp.png

public override void reset()
{
    base.reset();
    _state = new FindState();
}

Codec Properties

The following table enumerates some properties supported by the codec components.

Table 1: Properties supported by Codec Components (C#)
Property name Property value type
(property default value)
Effect Example

innerCodec

ICodecComponent
(null)

Codec Component instance to use for coding / decoding the payload contents for transport protocols.

cc.SetProperty("innerCodec", ccXml);

Source code

You can download the full NETCONF example source code developed in this article by following this link.

Views
Personal tools