OpenTTCN/Training/2008-04-15

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


Training blackboard, April 15-17, 2008



Contents

Training blackboard

int main(...)
{

    // tries to find openttcnd daemon on 127.0.0.1, port 5500:

    int rv = otInitAdapter(0); // 127.0.0.1, 5500

    if (rv) { /* report error */ }

}

include/isl/TTCN3.h: otInitAdapter()

Other way to initialize adapter:

// openttcnd is on 192.168.1.1:
otInitAdapter("192.168.1.1:5500");
// openttcnd is on 192.168.1.1, port 5600:
otInitAdapter("192.168.1.1:5600");

Way to change the openttcnd startup port

C:\Program Files\OpenTTCN\Tester3\etc\OpenTTCN.ini:

//change this line to change port name:

port = 5500

More complex init function

int otResolveInitialReferences(const char* id, const char** args);

(documentation for it is available in include\isl\TTCN3.h)

Way to register adapter to the session

int otRegisterAdapter(const char* sessionName, const char* id);

(specified in include\tri\tri_ext.h)

The simplest adapter config

otRegisterAdapter("MySessionName", "TRI");

Two adapters

// Adapter A:

otRegisterAdapter("MySessionName", "TRI");

// Adapter B:

otRegisterAdapter("MySessionName", "TRI:tsiP2");

Standalone coding and decoding

otRegisterAdapter("MySessionName", "TCI_CD");

Multipurpose adapters

otRegisterAdapter("MySessionName", "TRI:tsiP1");
otRegisterAdapter("MySessionName", "TRI:tsiP2");
otRegisterAdapter("MySessionName", "TCI_CD");
otRegisterAdapter("MySessionName", "TRI_PA:ext_F1");
otRegisterAdapter("MySessionName", "TRI_PA:ext_F2");

Full list of adapter roles (documentation)

Comments for:

int otRegisterAdapter(const char* sessionName, const char* id);

in C:\Program Files\OpenTTCN\SDK\include\tri\tri_ext.h

How to programmatically control test execution options (TCI-TM)

In C:\Program Files\OpenTTCN\SDK\include\tci\tci_ext.h:

Set of options starting with OT_TM_LOG

(use otSetTMFlags(), otResetTMFlags() to set or reset these options)

Options need to be set before the commencement of the test campaign (i.e. before you run tciStartTestCase() or tciStartControl()).

How to get a list of test cases programmatically in TCI-TM

TciTestCaseIdListType tciGetTestCases();

(specified in tci\tci.h)

(you need to call otSelectSession() beforehand to select active session)

How to start openttcnd

ot start (command-line)

C:\Program Files\OpenTTCN\SDK\include\isl\TTCN3.h:

int otStartSystemServices(const char* installPath,
    const char* port, int nowait,
    otStatusIndicationListener listener);

Create session

cmd-line:

session create MySession

API:

C:\Program Files\OpenTTCN\SDK\include\isl\TTCN3.h:

int otCreateSession(const char* sessionName);

Communication: adapter - test component

  • CORBA-based (TRI-PA, TRI-SA, TCI-CD, TCI-TM) (SDK for C, SDK for Java, SDK for C#)
    • tri.h
  • TRI-UDP (TRI-PA, TRI-SA) (you need to have CORBA-based TCI-CD)
    • tri_udp.h

APIs

  • C:\Program Files\OpenTTCN\SDK\include\tci - C
  • C:\Program Files\OpenTTCN\SDK\include\tri - C
  • C:\Program Files\OpenTTCN\SDK\include\isl - C++

Timer example

C:\Program Files\OpenTTCN\SDK\examples\timer

TCI-TM: Logging

Callback (tci/tci.h):

void tciLog(String message);

triSend()

C:\Program Files\OpenTTCN\SDK\include\tri\tri.h:

TriStatus triSend
(const TriComponentId* componentId,
 const TriPortId* tsiPortId,
 const TriAddress* sutAddress,
 const TriMessage* sendMessage);

DOC

typedef BinaryString TriAddress;
typedef BinaryString TriMessage;

tciEncode()

DOC

Enabling adapter debug output

tri/tri_ext.h

otSetSAFlags(OT_SA_FLAG_VERBOSE);

Other ways to debug the adapter

tester run:

    --log-encoded-as-text, -t    log encoded messages in text format
    --log-encoded-as-hex, -h     log encoded messages in hex format

TCI-TM:

tci/tci_ext.h:

#define OT_TM_LOG_ENCODED_AS_HEX     0x00000020L
#define OT_TM_LOG_ENQUEUED_EVENTS    0x00000040L
...

int otSetTMFlags(unsigned long flags);

Other ways to debug the test suite

tester run:

   --log-src-line-numbers, -l   enable logging of source file line numbers
   --log-src-file-name, -f      enable logging of source file name

   --print-stack-trace (added recently)

   --log-enqueued-events, -e    log events that arrived to the port queue
   --log-mismatch-events, -m    log mismatching communication statements
   --smart-mismatch, -M         log mismatch that preceded non-pass verdict

More info: tester help more

Also in TCI-TM.

odb (debugger)

Validation of decoder result

tci/tci_ext.h:

#define OT_CD_FLAG_VALIDATE_DECODED_VALUE 0x00000004L

Enabled by default.

To disable (for performance reasons): int otResetCDFlags(unsigned long flags);

triEnqueueMsg

tri/tri.h

void triEnqueueMsg
(const TriPortId* tsiPortId,
 const TriAddress* sutAddress,
 const TriComponentId* componentId,
 const TriMessage* receivedMessage);

tciDecode

DOC

Procedure-based communication

void triEnqueueCall()

void triEnqueueReply()

void triEnqueueException()

Multicast, broadcast

(Rarely used currently.)

  • triSendMC() - multicast
  • triSendBC() - broadcast
  • same for call, reply, exception
  • for more details, see: tri\tri.h

Session

session create dsrc
importer2 load dsrc MySuite.mp

isl/TTCN3.h:

otCreateSession();
otDeleteSession();
otListSessions();

otInitAdapter

DOC

otRegisterAdapter

tri/tri_ext.h:

DOC

  • in case there is a conflict related to multiple registrations, a best match rule is used, and the best matching adapter is selected to serve a particular request

Persistent, non-persistent data

(persistent repository data is physically stored in Tester3\var directory)

persistent:

  • precompiled test suites
  • module parameters (TTCN-2: test suite parameters)
  • sessions created

non-persistent data:

  • adapter registrations

Stoppping an adapter

session stop

isl/TTCN3.h:

otStopAdapter()

Stopping openttcnd

ot stop

isl/TTCN3.h:

otStopSystemServices();

Message not received - what to do?

  • enable adapter debug output (see above)
  • use -e option in 'tester run' (this will show the messages as they arrive from the adapter)
    • this probably would not work if the component port and TSI port are not mapped
  • make sure that the message is decoded OK (tciDecode())
  • make sure that the TSI port name specified in triEnqueueMsg() is correct and port index is (typically) -1
  • make sure that test component port was mapped to TSI port
  • use -m to see if there is some mismatch

How do I report an error that happened inside an adapter?

int otReportError(const char* errorReason);

(defined in isl\TTCN3.h - see comments in the header for more detail)

Structure of header files in the SDK

  • isl - general-purpose functionality (TTCN3.h is the major header file)
  • tri - TRI: TRI-SA (network interface code), TRI-PA (external functions, timers)
    • proprietary extensions are in tri_ext.h
  • tci - TCI: TCI-CD (coding, decoding), TCI-TM (test management)
    • proprietary extensions are in tci_ext.h

Structure of header files in the SDK

signature SimpleProc(
    in integer p1,
    out bitstring p2,
    inout octetstring p3)
return integer
exception(
    BS_0_1024,
    RecordType,
    EquivalentRecordType,
    integer,
    octetstring);

Developing adapter example

  • start Visual C++ 9.0 Express Edition
  • File|New|Project
  • Win32 console application
  • Example project location: C:\courses\wsp6
  • Name: simple
  • no precompiled header

After the project is created:

  • remove all unnecessary files generated by MS project wizard
  • create empty main.cxx and add it to the project
  • add the following content to main.cxx:
/* for Sleep() call */
#include <windows.h>

#include <tri/tri.h>
#include <isl/TTCN3.h>

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
    int status = otInitAdapter(0);

    if (status)
    {
        printf("main(): otInitAdapter() failed.\n");
        exit(status);
    }

    /* Register the adapter to OpenTTCN server */

    status = otRegisterAdapter("simple", "TRI");

    if (status)
    {
        printf("main(): otRegisterAdapter() failed.\n");
        exit(status);
    }

    /* Enable printout of library diagnostic information */
    otSetSAFlags(OT_SA_FLAG_VERBOSE);

    printf("Init OK!\n");

    /* Enter the infinite loop */
    while (1) Sleep(100000);

    /* Avoid compiler complaints */
    return 0;
}

Using Release mode only

Build | Configuration Manager | Active solution configuration: Release

Configuring Visual C++ project

  • assumption that OpenTTCN SDK is installed to:
C:\Program Files\OpenTTCN\SDK

If you have another installation location, then project settings are different

Right-click on simple, then Properties, then:

  • Configuration properties|C/C++|General|Additional include directories:
    • Add C:\Program Files\OpenTTCN\SDK\include
  • Configuration properties|C/C++|Code generation|Runtime library:
    • Change /MD (default) to /MT (SDK libraries use this runtime library)
  • Configuration properties|Linker|General|Additional library directories:
    • Add C:\Program Files\OpenTTCN\SDK\lib
  • Configuration properties|Linker|Input|Additional dependencies:
    • Add omniDynamic4.lib omniORB4.lib omnithread.lib pthreads-2.7.0-mt.lib openttcn-tcd-mt.lib openttcn-tsa-mt.lib openttcn-isl-mt.lib openttcn-msg-mt.lib ws2_32.lib

Adding default content to SA_impl.cxx, PA_impl.cxx

  • Add two more empty source files to the project: PA_impl.cxx and SA_impl.cxx

Content of SA_impl.cxx:

#include <tri/tri.h>

/***************************************************************************
 * Implementation of TRI interface (SA, user-provided part).
 */

TriStatus triSend
(const TriComponentId* componentId,
 const TriPortId* tsiPortId,
 const TriAddress* sutAddress,
 const TriMessage* sendMessage)
{
    return TRI_ERROR;
}

TriStatus triSAReset()
{
    return TRI_OK;
}

TriStatus triExecuteTestCase
(const TriTestCaseId* testCaseId,
 const TriPortIdList* tsiPortList)
{
    return TRI_OK;
}

TriStatus triMap
(const TriPortId* compPortId,
 const TriPortId* tsiPortId)
{
    return TRI_OK;
}

TriStatus triUnmap
(const TriPortId* compPortId,
 const TriPortId* tsiPortId)
{
    return TRI_OK;
}

TriStatus triCall
(const TriComponentId* componentId,
 const TriPortId* tsiPortId,
 const TriAddress* sutAddress,
 const TriSignatureId* signatureId,
 const TriParameterList* parameterList)
{
    return TRI_ERROR;
}

TriStatus triReply
(const TriComponentId* componentId,
 const TriPortId* tsiPortId,
 const TriAddress* sutAddress,
 const TriSignatureId* signatureId,
 const TriParameterList* parameterList,
 const TriParameter* returnValue)
{
    return TRI_ERROR;
}

TriStatus triRaise
(const TriComponentId* componentId,
 const TriPortId* tsiPortId,
 const TriAddress* sutAddress,
 const TriSignatureId* signatureId,
 const TriException* exception)
{
    return TRI_ERROR;
}

TriStatus triSUTActionInformal
(const char* description)
{
    return TRI_ERROR;
}

TriStatus triSUTActionTemplate
(const TriActionTemplate* templateValue)
{
    return TRI_ERROR;
}

Content of PA_impl.cxx:

#include <tri/tri.h>

#include <isl/TTCN3.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/***************************************************************************
 * Implementation of TRI interface (PA, user-provided part).
 */

TriStatus triPAReset()
{
    return TRI_OK;
}

TriStatus triExternalFunction
(const TriFunctionId* functionId, /* in parameter */
 TriParameterList* parameterList, /* inout parameter */
 TriParameter* returnValue /* out parameter */)
{
    // Example:

    if (!strcmp(functionId->objectName, "MyFunct1"))
    {
        // implement MyFunct1
    }
    else if (!strcmp(functionId->objectName, "MyFunct2"))
    {
        // implement MyFunct2
    }
    // ...
    else
    {
        otReportError("Unrecognized external function");
    }

    return TRI_ERROR;
}

TriStatus triStartTimer
(const TriTimerId* timerId,
 TriTimerDuration timerDuration)
{
    return TRI_ERROR;
}

TriStatus triStopTimer
(const TriTimerId* timerId)
{
    return TRI_ERROR;
}

TriStatus triReadTimer
(const TriTimerId* timerId,
 TriTimerDuration* elapsedTime)
{
    return TRI_ERROR;
}

TriStatus triTimerRunning
(const TriTimerId* timerId,
 unsigned char* running)
{
    return TRI_ERROR;
}

ot start, session create

Start openttcnd daemon:

ot start

Create a session named "simple":

session create simple

Adding CD

Add CD_impl.cxx to the project with the following content:

#include <tci/tci.h>

#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <memory.h>
#include <string.h>

/***************************************************************************
 * Implementation of TCI interface (CD, user-provided encoding part).
 */

BinaryString tciEncode(TciValue value)
{
    BinaryString result;
    memset(&result, 0, sizeof(result));
    return result;
}

/***************************************************************************
 * Implementation of TCI interface (CD, user-provided decoding part).
 */

TciValue tciDecode(BinaryString message, TciType decHypothesis)
{
    TciValue result = 0;
    return result;
}


Add the following before adapter registration (main.cxx):

otSetEncoder(0, &tciEncode);
otSetDecoder(0, &tciDecode);

Add the following include to main.cxx:

#include <tci/tci.h>

(Documentation for otSetEncoder() is here)

Background info

  • SUT protocol definition: DOC

Example test case

Added C:\courses\wsp6\simple\ttcn3\Main.ttcn file with the following content:

module Main
{

/*******************************************************************
 * Message type definitions
 */

type integer uint8(0..255);
type integer uint16(0..65535);

type record Message
{
    uint8 typeCode,
    Payload payload optional
}

type record Payload
{
    uint16 len,
    charstring content
}

type octetstring Raw;

/*******************************************************************
 * Other definitions
 */

// Address type used for UDP packets.
type record address
{
    charstring host,
    integer portField
}

modulepar
{
    // IP address and UDP port number of the SUT.
    charstring PX_SUT_IP_ADDR := "127.0.0.1";
    integer PX_SUT_PORT := 7431;
}

type port PortType mixed
{
    inout all;
}

type component ComponentType
{
    port PortType p;
    timer T_GUARD := 10.0;

    var address sut_addr :=
    {
        host := PX_SUT_IP_ADDR,
        portField := PX_SUT_PORT
    };
}

type component SystemInterfaceType
{
    port PortType tsiPort;
}

template Message GreetingRequest
(in template charstring phrase) :=
{
    typeCode := 1,
    payload :=
    {
        len := lengthof(phrase),
        content := phrase
    }
}

template Message GreetingResponse
(in template charstring phrase)
modifies GreetingRequest :=
{
    typeCode := 2
}

/* the above is equivalent to the following:

{
    typeCode := 2,
    payload :=
    {
        len := lengthof(phrase),
        content := phrase
    }
}

*/

altstep DefaultAltstep() runs on ComponentType
{
    [] any port.check
    {
        setverdict(fail);
        stop;
    }
    [] T_GUARD.timeout
    {
        setverdict(fail);
        stop;
    }
}

testcase TC_R2D2_001()
    runs on ComponentType
    system SystemInterfaceType
{
    activate(DefaultAltstep());
    T_GUARD.start;
    map(mtc:p, system:tsiPort);

    p.send(GreetingRequest("Hello, world!")) to sut_addr;
    p.receive(GreetingResponse("Hello, mankind!")) from sut_addr;

    p.send(GreetingRequest("Hello, gentlemen!")) to sut_addr;
    p.receive(GreetingResponse("Reformulate")) from sut_addr;

    setverdict(pass);
}

}

Compile test suite

importer3 load simple Main.ttcn

Run test case

  • start the adapter (Ctrl+F5 in VC++)
  • tester run simple TC_R2D2_001

Adding encoder

Add the following to CD_impl.cxx (in place of current tciEncode()):

/* for otReportError() */
#include <isl/TTCN3.h>

#define MAX_BUFFER_SIZE 4096

BinaryString tciEncode(TciValue value)
{
    int len = 0;
    BinaryString result;

    TciType valType = tciGetType(value);
    String typeName = tciGetName(valType);
    TciTypeClassType typeClass = tciGetTypeClass(valType);

    memset(&result, 0, sizeof(result));

    if (typeClass == TCI_ADDRESS_TYPE)
    {
        len = sizeof(TciValue);
        result.data = (unsigned char *) malloc(len);
        memcpy(result.data, &value, len);
        result.bits = len << 3;
    }
    else if (!strcmp(typeName, "Message"))
    {
        long typeCode = 0, length = 0;
        unsigned char* buffer = 0;

        TciValue payload = 0;

        char* str = 0;
        long str_len = 0;

        buffer = (unsigned char *) malloc(MAX_BUFFER_SIZE);
        memset(buffer, 0, MAX_BUFFER_SIZE);

        len = 0;

        typeCode = tciValueToLong(tciGetRecFieldValue(value, "typeCode"));

        buffer[len++] = (unsigned char) typeCode;

        payload = tciGetRecFieldValue(value, "payload");

        /* 'payload' field is optional, so it may be omitted */
        if (!tciNotPresent(payload))
        {
            long length = tciValueToLong(tciGetRecFieldValue(payload, "len"));

            /* most significant byte first */
            buffer[len++] = (unsigned char) (length >> 8);
            buffer[len++] = (unsigned char) (length >> 0);

            tciValueToCharstring(&str, &str_len,
                tciGetRecFieldValue(payload, "content"));

            memcpy(buffer + len, str, str_len);
            len += str_len;

            free(str);
        }

        result.data = buffer;
        result.bits = len << 3;
    }
    else
    {
        otReportError("tciEncode(): Unrecognized value type.");
    }

    return result;
}

To use this code, you will need to #include "Utilities.h" to CD_impl.cxx and add files Utilities.h and Utilities.c to your Visual Studio project.

You will need to copy the code of Utilities.h and Utilities.c files referred to in the links of the previous paragraph into your Visual C++ project. Click on the links of the previous paragraph to see the content of both files. Note that these are not the same as in the examples\util directory shipped with the C SDK.

Ways to implement coding and decoding

  • simple: using tciGetType(), tciGetName(myType)
  • more complex (more intelligent): using 'encode' and 'variant' attributes
  • example of attribute definition in TTCN-3 test suite:
type record MyMessage
{
    integer f1,
    integer f2
}
with { encode "MyPropName1=MyPropValue1;MyPropName2=MyPropValue2",
       variant "MyPropName4=MyPropValue4;MyPropName3=MyPropValue3" }
  • how to retrieve encoding, variant attributes using TCI API:
String tciGetTypeEncoding(TciType inst);
/* OpenTTCN Specific: MM4RV is as defined in [RM1]. */
String tciGetTypeEncodingVariant(TciType inst);

Full list of TCI type classes

Search for "TciTypeClassType" in tci\tci.h

Adding triSend handler

Replace triSend() implementation in SA_impl.cxx with the following fragment:

#include "Utilities.h"

TriStatus triSend
(const TriComponentId* componentId,
 const TriPortId* tsiPortId,
 const TriAddress* sutAddress,
 const TriMessage* sendMessage)
{
    TciValue addrValue = *((TciValue *) sutAddress->data);

    unsigned long ipAddr = 0;
    unsigned short portNumber = 0;

    int result =
        extractHostAndPortFromAddress(
            &ipAddr, &portNumber, addrValue);

    if (result) return TRI_ERROR;

    result = sendDatagramPacket(
        ipAddr,
        portNumber,
        (char*) sendMessage->data,
        sendMessage->bits >> 3);

    return (!result) ? TRI_OK : TRI_ERROR;
}

SUT implementation

SUT implementation can be downloaded here.

Way to start SUT binary (in the zip package): start_sut.bat:

start Release\SystemUnderTest.exe 7431
  • SUT is configured to listen on UDP port 7431

Theory track (continued)

TTCN-2 execution to TTCN-3 adaptation mapping

DOC

  • triMap(), triUnmap() are in fact called automatically

Procedure-based communication

Signatures

Example of signature definition for procedure-based communication:

signature Signature(
    in integer par1,
    out bitstring par2,
    MyRecordType par3,
    inout OS_0_256 par4)
return integer
exception(
    BS_0_1024,
    RecordType);
signature ASP_Type1(
    in integer par1,
    in bitstring par2,
    in MyRecordType par3,
    in OS_0_256 par4);

template ASP_Type1 t1 :=
{
par1 := 1,
par2 := '1101'B,
par3 := { f1 := 1, f2 := 2 },
par4 := 'AF'O
}

...
p.call(t1);

triCall

TriStatus triCall
(const TriComponentId* componentId,
 const TriPortId* tsiPortId,
 const TriAddress* sutAddress,
 const TriSignatureId* signatureId,
 const TriParameterList* parameterList);

triEnqueueReply

void triEnqueueReply
(const TriPortId* tsiPortId,
 const TriAddress* sutAddress,
 const TriComponentId* componentId,
 const TriSignatureId* signatureId,
 const TriParameterList* parameterList,
 const TriParameter* returnValue);

triEnqueueException

void triEnqueueException
(const TriPortId* tsiPortId,
 const TriAddress* sutAddress,
 const TriComponentId* componentId,
 const TriSignatureId* signatureId,
 const TriException* exception);

When tciEncode() is called

Message-based communication:

  • called to encode a message and optionally address prior to triSend() call

Procedure-based communication:

  • called to encode in, inout parameters and optionally address prior to triCall() call
  • called to encode out, inout parameters and optionally return value and optionally address prior to triReply() call
  • called to encode exception and optionally address prior to triRaise() call

External functions:

  • called to encode in, inout parameters of external function prior to triExternalFunction() call

SUT actions:

  • called to encode template prior to triSUTActionTemplate()

When tciDecode() is called

Message-based communication:

  • called to decode a message and optionally address when triEnqueueMsg() call is being processed

Procedure-based communication:

  • called to decode in, inout parameters and optionally address when triEnqueueCall() call is being processed
  • called to decode out, inout parameters and optionally return value and optionally address when triEnqueueReply() call is being processed
  • called to decode exception and optionally address when triEnqueueException() call is being processed

External functions:

  • called to decode out, inout parameters of external function and optionally external function return value after triExternalFunction() callback function returns

When decoding hypothesis is provided

It is accurately provided when decoding:

  • out, inout parameters and return value when triEnqueueReply() call is being processed
  • in, inout parameters when triEnqueueCall() call is being processed
  • out, inout parameters and return value after triExternalFunction() callback function returns

Decoding hypothesis may be accurately provided when decoding address.

Decoding hypothesis by default is not provided for the following cases:

  • decoding of a message in message-based communication (when triEnqueueMsg() call is being processed)
  • decoding an exception in procedure-based communication (when triEnqueueException() call is being processed)

Decoding hypothesis for messages

  • none by default
  • see comment for OT_CD_FLAG_MESSAGES_WITH_HYPOTHESIS flag in tci\tci_ext.h for more details
  • this flag can be set using:
int otSetCDFlags(unsigned long flags);

(found in tci/tci_ext.h)

  • more information on exact algorithm for constructing list of expected types during phase 1 (types A, B, C in the drawing) can be found in documentation for OT_CD_FLAG_MESSAGES_WITH_HYPOTHESIS flag in tci\tci_ext.h

Decoding hypothesis for exceptions

  • none by default
  • see comment for OT_CD_FLAG_EXCEPTIONS_WITH_HYPOTHESIS flag in tci\tci_ext.h for more details
  • bitwise combination of flags is possible:
otSetCDFlags(OT_CD_FLAG_MESSAGES_WITH_HYPOTHESIS |
             OT_CD_FLAG_EXCEPTIONS_WITH_HYPOTHESIS);

Preparing TriParameterList for triEnqueueReply

  • consult how many parameters the relevant signature definition in TTCN-3 has
    • e.g. four (see Signature definition above)
TriParameterList pl;
pl.parList = (TriParameter **) malloc(sizeof(TriParameter*) * 4);
pl.length = 4;

TriParameter** parList = pl.parList;

parList[0] = (TriParameter*) malloc(sizeof(TriParameter));
memset(&(parList[0]->par), 0, sizeof(BinaryString));
parList[0]->mode = TRI_IN;

parList[1] = (TriParameter*) malloc(sizeof(TriParameter));
memset(&(parList[1]->par), 0, sizeof(BinaryString));
/* Add code to set the value of parList[1]->par which is a
   BinaryString and which should contain encoding of the
   parameter in binary form */
parList[1]->mode = TRI_OUT;

/* Do similar thing for the rest of IN parameters as
   we did for parList[0].

   Do similar thing for the rest of INOUT, OUT parameters as
   we did for parList[1].
 */

  • use malloc() to allocate data holder for encoding for e.g. parList[1]->par.data
  • use malloc() to allocate data holder for encoding for signature return value:
TriParameter rv;
rv.par.data = (...) malloc(...);
rv.mode = TRI_IN;

Preparing TriParameterList for triEnqueueCall

  • consult how many parameters the relevant signature definition in TTCN-3 has
    • e.g. four (see Signature definition above)
TriParameterList pl;
pl.parList = (TriParameter **) malloc(sizeof(TriParameter*) * 4);
pl.length = 4;

TriParameter** parList = pl.parList;

parList[0] = (TriParameter*) malloc(sizeof(TriParameter));
memset(&(parList[0]->par), 0, sizeof(BinaryString));
/* Add code to set the value of parList[0]->par which is a
   BinaryString and which should contain encoding of the
   parameter in binary form */
parList[0]->mode = TRI_IN;

parList[1] = (TriParameter*) malloc(sizeof(TriParameter));
memset(&(parList[1]->par), 0, sizeof(BinaryString));
parList[1]->mode = TRI_OUT;

/* Do similar thing for the rest of IN, INOUT parameters as
   we did for parList[0].

   Do similar thing for the rest of OUT parameters as
   we did for parList[1].
 */

triExternalFunction

TriStatus triExternalFunction
(const TriFunctionId* functionId,
 TriParameterList* parameterList, /* inout parameter */
 TriParameter* returnValue); /* out parameter */

In TTCN-3:

external function F1(
    in integer p1,
    inout RecType p2,
    out boolean p3)
return charstring;

function MyFunct()
{
    ...
    var RecType v1;
    var boolean v2;

    var charstring v3 := F1(2 + 5, v1, v2);

    // After call to F1(), v1 may and v2 should contain value assigned by the adapter.
}

Typical structure of tciEncode()

BinaryString tciEncode(TciValue value)
{
    TciType valType = tciGetType(value);
    String typeName = tciGetName(valType);

    BinaryString result;
    memset(&result, 0, sizeof(BinaryString));

    if (!strcmp(typeName, "T1"))
    {
        // Encoding of T1 value
    }
    else if (!strcmp(typeName, "T2"))
    {
        // Encoding of T2 value
    }
    ...
    else if (!strcmp(typeName, "T30"))
    {
        // Encoding of T30 value
    }
    else
    {
        otReportError("tciEncode(): Cannot recognize typeName");
    }

    return result;
}

otReportError() is defined in isl/TTCN3.h (see comments there for more details):

int otReportError(const char* errorReason);

Structure of tciEncode() that uses variant attribute

BinaryString tciEncode(TciValue value)
{
    TciType valType = tciGetType(value);
    String typeVariantAttr = tciGetTypeEncodingVariant(valType);

    BinaryString result;
    memset(&result, 0, sizeof(BinaryString));

    if (!strcmp(typeVariantAttr, "my variant attr val 1"))
    {
        // Encoding of value with type having 'val 1' variant attribute
    }
    else if (!strcmp(typeVariantAttr, "my variant attr val 2"))
    {
        ...
    }
    ...
    else if (!strcmp(typeVariantAttr, "my variant attr val 3"))
    {
        ...
    }
    else
    {
        otReportError("tciEncode(): Cannot recognize variant attribute");
    }

    return result;
}
  • use STL map to speedup type and variant lookup
  • or use OpenTTCN-supplied solution to speedup correct decoder resolution

otSetEncoder - Setting specific encoder (for performance reasons)

DOC

BinaryString myTciEncode(TciValue value)
{
    // Default (generic) encoder responsible for encoding of values of all types
    // except T1 and MyModule.T2.
    ...
}

BinaryString myTciEncode2(TciValue value)
{
    // This function is responsible solely for encoding of values of type T1
    ...
}

BinaryString myTciEncode3(TciValue value)
{
    // This function is responsible solely for encoding of values of type MyModule.T2
    ...
}

int main()
{
    ...
    otSetEncoder(0, &myTciEncode);
    ...
}

// Problem: tciGetTypeForName() calls
void F()
{
    otSetEncoder(tciGetTypeForName("T1"), &myTciEncode2);
    otSetEncoder(tciGetTypeForName("MyModule.T2"), &myTciEncode3);
}

  • create MTC (TCI-CD request server) (tci/tci_ext.h):
// Creates MTC service TCI-CD query request as a side effect
int otTMSelectSession(const char* sessionName);
  • or put F() into triExecuteTestcase()
  • or check documentation for otRegisterSetCodecFunction() in tci/tci_ext.h

SUT action (implicit send in TTCN-2)

In TTCN-3:

action("Please dial the number +1234567");

Callback in TRI:

TriStatus triSUTActionInformal
(const char* description);

In TTCN-3:

action(myTmplRef);

Callback in TRI:

TriStatus triSUTActionTemplate
(const TriActionTemplate* templateValue);

Memory management rules for TRI interface

  • discussed in the preamble for tci/tci.h
  • for TRI callbacks ('provided functions' is the standard term used throughout TCI specification) like triSend(), the rules are quite simple:
  • you (the adapter developer) do not own pointers and their content supplied to you by the SDK in the callback functions like triSend(), triCall(), triExecuteTestcase() etc.
  • supplied parameters are thread-safe and they are safe to use for the duration of the user-implemented callback handling
  • it is unsafe to use a stored shallow copy of a pointer supplied to the callback like triSend() by the framework after the callback finishes and returns control back to the SDK, because there is no guarantee that the pointer still exists after the callback returns (SDK can get rid of it at any moment of time as it sees fit)
  • if you want to use some parameter after callback returns, you need to make your own deep private copy if the parameter in question
  • a deep copy can be made for almost any TRI parameter using OpenTTCN-supplied otDuplicate...() family of functions defined in tri/tri_ext.h
  • don't forget to deallocate your own private copy after you no longer use it to avoid a memory leak
  • this could be done using otRelease...() family of OpenTTCN-supplied functions found in tri/tri_ext.h (they do a deep deallocation of relevant data structures)
  • for TRI SDK-supplied (or TE-supplied) API functions ('required functions' is the standard term used throughout TCI specification) memory management rules are as folows:
  • you (the adapter developer) own data that you pass to API function in place of function formal parameters
  • memory management rules for return value of 'provided' and 'required' TRI operations are trivial and they do not need to be defined, because all of them return exclusively either void or TriStatus which is defined as long int (primitive non-pointer data type)

Memory management rules for TCI-CD interface

  • tciEncode()
  • you (the adapter developer) do not own TciValue passed as a parameter
  • from the perspective of adapter developer TciValue passed as a parameter is immutable and shall not be changed (so you can only introspect it)
  • you do not own returned BinaryString; in particular, you do not own the 'data' field after you return it back to the caller
  • you are still responsible for allocating the 'data' field using malloc(), but it is not you but the SDK that deallocates the 'data' field using free() at some convenient time
  • the 'aux' field is not interpreted by the SDK in any way
  • the SDK will not try to deallocate data referred to by the 'aux' field by itself
  • the SDK will pass the content of the 'aux' field unmodified to the other entity (e.g. triSend()), unless standalone codec is applicable, in which case content of 'aux' field will be lost
  • tciDecode()
  • you do not own message and decHypothesis parameters
  • it is a rule of thumb that the SDK will always own any data structure of type TciType (stored in the internal cache buffers)
  • you stop owning TciValue at the moment you return it back to the caller of tciDecode()

Multiplication by eight is equal to shift left by three

int lenInOctets = 5;
BinaryString bs;
memset(&bs, 0, sizeof(BinaryString));
bs.data = (unsigned char *) malloc(lenInOctets);
bs.bits = lenInOctets << 3; // equivalent to lenInOctets * 8

Encode and variant attributes

Example of typedef with encode and variant attributes:

type RecType record
{
    integer f1,
    integer f2
}
with
{
   encode "ENC:123",
   variant "VAR:123
A:1,
B:2,
bits=3"
}

Retrieval of encode, variant attributes for typedefs:

In tci/tci.h:

/* OpenTTCN Specific: MM4RV is as defined in [RM1]. */
String tciGetTypeEncoding(TciType inst);

/* OpenTTCN Specific: MM4RV is as defined in [RM1]. */
String tciGetTypeEncodingVariant(TciType inst);

Example of value with encode and variant attributes:

template RecType t1 := { f1 := 1, f2 := 2}
// with { encode "ABC", variant "DEF" }
...
p.send(t1);

-> tciEncode(t1); (invocation inside the SDK for C code)

Retrieval of encode, variant attributes for values:

In tci/tci.h:

String tciGetValueEncoding(TciValue inst);

String tciGetValueEncodingVariant(TciValue inst);

Example of adapter code:

// String is defined as char*
String myEnc = tciGetTypeEncoding(tciGetTypeForName("MyTestSuiteType"));

if (!strcmp(myEnc, "MyEncodingValue"))
{
  ....
}

// When the function is exited, you do not need to
// deallocate memory occupied by myEnc.

Encode and variant attributes: example of use

TTCN-3 code:

type integer INT_16
with { variant "type=integer;length-in-bits=16;octet-order=msb-first" }

type integer INT_32
with { variant "type=integer;length-in-bits=32;octet-order=msb-first" }

type record Message
{
    INT_16 f1,
    INT_32 f2
}
with { variant "type=record-container" }

Conventional approach to tciEncode():

BinaryString tciEncode(TciValue value)
{
    TciType valType = tciGetType(value);
    String typeName = tciGetName(valType);

    BinaryString result;
    memset(&result, 0, sizeof(BinaryString));

    if (!strcmp(typeName, "Message"))
    {
        // Encode field f1 using hard-coded knowledge that it is 16 bit
        // Encode field f2 using hard-coded knowledge that it is 32 bit
    }
    else
    {
        otReportError("tciEncode(): Cannot recognize typeName");
    }

    return result;
}

Approach to tciEncode() using variant info (generic encoder):

BinaryString tciEncode(TciValue value)
{
    TciType valType = tciGetType(value);
    String variant = tciGetTypeEncodingVariant(TciType inst);

    map<string, string> variantItems = parseVariant(variant);

    BinaryString result;
    memset(&result, 0, sizeof(BinaryString));

    if (variantItems["type"] == "record-container")
    {
        // Call tciEncode() recursively for all fields of the record
        // container and concatenate result of encoding into single
        // result buffer.

        ...
    }
    else if (variantItems["type"] == "integer")
    {
        int lenInBits = stringToLong(variantItems["length-in-bits"]);
        string octetOrder = variantItems["octet-order"];

        if (octetOrder == "msb-first")
        {
            // Encode integer value into number of bits specified in lenInBits,
            // most significant byte first

            ...
        }
        else if (octetOrder == "lsb-first")
        {
            // Encode integer value into number of bits specified in lenInBits,
            // least significant byte first

            ...
        }
    }
    else
    {
        otReportError("tciEncode(): Cannot recognize variant type");
    }

    return result;
}

Developing example adapter (continued)

SUT binary and source code

  • Binary and source: here
  • how to start the adapter: from the command line: start SystemUnderTest.exe 7431
  • recovering from the previous day (starting openttcnd daemon):
ot start
  • restarting the adapter:
    • start VC++
    • open "simple" solution from yesterday
    • Ctrl+F5 - start the adapter
  • run the test case:
tester run simple @all

(@all is used for fast execution of a test case if it is the only one in the test suite; otherwise it runs all test cases defined in the test suite)

  • SUT window should show that it received a request and has replied with a valid response

Adding socket listener thread

Add SocketListener.cxx to the project with the following content:

#include "Utilities.h"

#include <tri/tri.h>
#include <tci/tci.h>

#include <isl/TTCN3.h>

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <assert.h>
#include <pthread.h>

#ifndef _WIN32
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#else
#include <winsock2.h>
typedef int socklen_t;
#endif

#define MAX_BUFFER_SIZE 65536

/* UDP port listener thread implementation */
void *runSocketListener(void *p_)
{
    int rval;
    unsigned char buff[MAX_BUFFER_SIZE];

    int len;
    int errorCode = 0;

    struct sockaddr_in src_addr;
    socklen_t addrlen = sizeof src_addr;

    char* host = 0;
    unsigned short port = 0;

    TciValue sutAddrHandle = 0;
    TciValue hostField = 0;
    TciValue portField = 0;

    TriAddress sutAddr;
    TriMessage recvMsg;
    TriPortId tsiPort;

    memset(&tsiPort, 0, sizeof(TriPortId));

    if (!_socketDescInitialized)
    {
        rval = bindSocketToAnyPort(&_socketDesc);

        if (rval)
        {
            otReportError("runSocketListener(): Cannot bind socket.");
            return 0;
        }

        _socketDescInitialized = 1;
    }

    /* Loop forever */
    while (1)
    {
        /* Receive a packet from the SUT */

        memset(&src_addr, 0, sizeof src_addr);

        rval = recvfrom(
            _socketDesc,
#ifndef _WIN32
            buff, 
#else
            (char *) buff, 
#endif
            sizeof buff, 
            0, 
            (struct sockaddr *) &src_addr, 
            &addrlen);

        /* We will proceed further only if the packet has been received OK */

        if (rval == -1)
        {
#ifndef _WIN32
            printf("runSocketListener(): "
                "recvfrom(): Error: %s\n", strerror(errno));
#else
            errorCode = WSAGetLastError();

            printf("runSocketListener(): "
                "recvfrom(): Error: error code = %i\n", errorCode);
#endif
            continue;
        }

        /* Put SUT address information directly into TciValue */

        host = inet_ntoa(src_addr.sin_addr);
        hostField = charstringToTciValue(host);

        port = ntohs(src_addr.sin_port);
        portField = longToTciValue(port);

        sutAddrHandle = tciNewInstance(tciGetTypeForName("address"));

        tciSetRecFieldValue(sutAddrHandle, "host", hostField);
        tciSetRecFieldValue(sutAddrHandle, "portField", portField);

        memset(&sutAddr, 0, sizeof(sutAddr));

        len = sizeof(TciValue);
        sutAddr.data = (unsigned char *) malloc(len);
        memcpy(sutAddr.data, &sutAddrHandle, len);
        sutAddr.bits = len << 3;

        /* Put raw encoded frame received from the SUT "as is" to recvMsg */

        recvMsg.data = buff;
        recvMsg.bits = rval << 3;

        buff[rval] = 0;

        tsiPort.portName = "tsiPort"; /* hard-coded */
        tsiPort.portIndex = -1;

        /* Append the message to the port queue on the test execution side */

        triEnqueueMsg(&tsiPort, &sutAddr, 0, &recvMsg);

        free(sutAddr.data);
    }

    /* Unreachable code, added to avoid compiler complaints */
    return 0;
}

(Adding pretest function)

session set property simple PRETEST_FUNCTION=Func_F1

Function Func_F1 needs to be defined in TTCN-3, e.g.:

function Func_F1()
{
    // Custom code here.
}
session set property simple POSTTEST_FUNCTION=Func_F2

More documentation is in ReleaseNodes.pdf, search for PRETEST.

(Controlling test component process priority)

  • TC_PRIORITY=(IDLE, NORMAL, HIGH, REALTIME)

E.g.:

session set property simple TC_PRIORITY=HIGH

Creating socket listener thread

Add this section before "printf("Init OK!\n");" in main.cxx:

/* Create UDP listener thread */

{
    pthread_t thread_ptr;

    extern void *runSocketListener(void *p_);

    if (pthread_create(&thread_ptr, 0, &runSocketListener, 0) != 0)
    {
        printf("main(): Unable to start a new thread.\n");
        exit(1);
    }
}
  • do not forget to #include <pthread.h> in main.cxx

Adding decoder for the received message

  • replace the default implementation of tciDecode() in CD_impl.cxx with the following fragment of code:
TciValue tciDecode(BinaryString message, TciType decHypothesis)
{
    int len;
    TciValue result = 0;

    TciValue typeCode = 0;
    TciValue lenField = 0;
    TciValue contentField = 0;
    TciValue payload = 0;

    long length = 0;

    String typeName = "";

    if (decHypothesis)
    {
        typeName = tciGetName(decHypothesis);
    }

    if (!strcmp(typeName, "address"))
    {
        return *((TciValue *) message.data);
    }
    else if (!decHypothesis)
    {
        /* Assuming decoding of value of Message type */

        char* buffer = (char *) message.data;
        len = message.bits >> 3;

        if (!len)
        {
            otReportError("tciDecode(): Error: Message content is too short.");
            return 0;
        }

        if (len == 2)
        {
            otReportError("tciDecode(): Error: Message content is malformed.");
            return 0;
        }

        typeCode = longToTciValue((unsigned char) buffer[0]);

        result = tciNewInstance(tciGetTypeForName("Message"));

        tciSetRecFieldValue(result, "typeCode", typeCode);

        if (len > 2)
        {
            length =
                (((unsigned long) (unsigned char) buffer[1]) << 8) |
                (((unsigned long) (unsigned char) buffer[2]) << 0);

            lenField = tciNewInstance(tciGetTypeForName("uint16"));
            assignLongToTciValue(lenField, length);

            contentField = sizedCharstringToTciValue(buffer + 3, len - 3);

            payload = tciNewInstance(tciGetTypeForName("Payload"));

            tciSetRecFieldValue(payload, "len", lenField);
            tciSetRecFieldValue(payload, "content", contentField);

            tciSetRecFieldValue(result, "payload", payload);
        }
    }
    else
    {
        printf("tciDecode(): Error: Unrecognized type '%s'.\n", typeName);
    }

    return result;
}

(Memory management for TCI containers)

  • when you return from tciDecode() you stop owning the return value
  • when you establish a container-contained relationship between two TciValue's, e.g. using:
  • tciSetRecFieldValue()
  • tciSetRecOfFieldValue()
  • tciAppendRecOfFieldValue()
  • tciSetUnionVariant()

... then you transfer ownership of the newly contained TciValue from yourself to the container

  • with this system you almost never need to deallocate TciValue by yourself
  • if in some case you need to manually deallocate TciValue, use otReleaseTciValue() from tci/tci_ext.h
  • typically you might need to call otReleaseTciValue() only in case of erroneous input frame, when the decoding failed half-way
  • never try to reuse contained elements in more than one container-contained relationship, as this may lead to a segmentation fault when the corresponding container is deallocated. Example (don't do this):
TciValue lenField = tciNewInstance(tciGetTypeForName("uint16"));
TciValue contentField = tciNewInstance(tciGetTypeForName("MyContent"));

TciValue payload = tciNewInstance(tciGetTypeForName("Payload"));

tciSetRecFieldValue(payload, "len", lenField);
tciSetRecFieldValue(payload, "content", contentField);

// The following will lead to segmentation fault:
tciSetRecFieldValue(payload, "alt_content", contentField);

Theory track (continued)

Parameter provisioning in TCI-TM

  • by default, through callback (defined in tci/tci.h):
TciValue tciGetModulePar
(TciModuleParameterIdType parameterId);
  • the callback is invoked implicitly multiple times, once per each module parameter, by the SDK when you start a test case or control part
  • explicit triggering of callback call sequence (see documentation in tci/tci_ext.h):
int otTMProvisionModuleParameters();
  • if you want to use standard OpenTTCN scheme for parameter provisioning, thus bypassing TTCN-3 tciGetModulePar() callback mechanism:
    • search for documentation on OT_TM_FLAG_USE_PARAMETERS_FROM_REPOSITORY flag in tci/tci_ext.h - this tells how to do this
  • conventional approach is through 'importer3 parameterize SESSION FILE' command, where file is an ordinary TTCN-3 module containing parameter declarations, e.g.:
module parameters
{

modulepar charstring PX_VAL_1 := "Supplied value of parameter #1.";
modulepar integer PX_PAR_2 := 5500;

}

Overview of TCI-TM interface

  • selector functions (proprietary):
  • int otSelectSession(); (defined in tci/tci_ext.h)
  • needs to be called prior to use of any other function from the list below
  • the most important side effect of session selection is that main test component is allocated
  • this is approximately equivalent to 'tester ini' or 'tester initialize' from the command-line toolset
  • same as otTMSelectSession()
  • int otDeselectSession();
  • the most important side effect of session deselection is that main test component is deallocated
  • this is approximately equivalent to 'tester rel' or 'tester release' from the command-line toolset
  • introspection (browsing) functions: they allow to retrieve e.g. list of test cases, test case interface declarations etc.
  • TciTestCaseIdListType tciGetTestCases() - returns list of test cases defined in the test suite
  • tciGetTestCaseParameters() - returns list of formal parameter definitions of the specified test case
  • tciGetModuleParameters() - returns list of formal parameter definitions (name + optional default module parameter value)
  • controlling functions: functions that start and stop execution of test cases and test campaigns:
  • tciStartTestCase()
  • you need to specify test case name and optionally list of test case actual parameters
  • tciStopTestCase()
  • tciStartControl()
  • tciStopControl()
  • callback and notification functions:
  • tciTestCaseStarted()
  • tciTestCaseTerminated()
  • tciControlTerminated()
  • tciGetModulePar() // used in parameter provisioning prior to commencement of test case or control part execution
  • tciLog() // callback used to implement logging capability - can be used also in "TCI_TM" adapter - not to be confused with TCI-TM entity

Way to stop adapter from the command line

From the command line:

session stop SESSION ADAPTER

Stops and unregisters running adapter. Example:

session stop simple TRI

Programmatically (using isl/TTCN3.h):

int otStopAdapter(const char* sessionName, const char* adapterName);

Example:

otStopAdapter("simple", "TRI");

Way to insert versioning info to the log (start of test case)

  • otSetProperty()
  • defined in isl/TTCN3.h
  • needs to be tried in practice

TCI-TM extensions

  • way to programmatically start openttcnd (isl/TTCN3.h) (same as ot start):
int otStartSystemServices(const char* installPath,
    const char* port, int nowait,
    otStatusIndicationListener listener);
module MyModule
{
const charstring MyConst := "1.2.3";
}

Example of adapter using procedure-based communication

Updating Main.ttcn

We will use example of adapter and test suite that we were developing during Day 1 and Day 2.

Add the following fragment of code to the end of Main.ttcn:

//////////////////////////////////////////////////////////////
// Day 3: signature example, procedure-based communication

type integer byte (0..255);
type boolean bool;

type record Overflow { }
type record DivisionByZero { }

// After the signature invocation returns:

// arg1_div_arg2 contains arg1 / arg2
// return value of signature contains arg1 * arg2
// xor_par contains (original xor_par xor true)
signature MultiplyAndDivide(
    in byte arg1,
    in byte arg2,
    out byte arg1_div_arg2,
    inout bool xor_par)
return byte
exception(
    Overflow,
    DivisionByZero);

template MultiplyAndDivide t_MultiplyAndDivide(
    in template byte par1,
    in template byte par2,
    in template byte par3,
    in template bool par4) :=
{
    arg1 := par1,
    arg2 := par2,
    arg1_div_arg2 := par3,
    xor_par := par4
}

testcase TC_proc_example()
    runs on ComponentType
    system SystemInterfaceType
{
    activate(DefaultAltstep());
    T_GUARD.start;
    map(mtc:p, system:tsiPort);

    var byte byte_var;

    p.call(t_MultiplyAndDivide(11, 5, omit, true), 5.0)
    {
        [] p.getreply(t_MultiplyAndDivide(omit, omit, 2, false)) -> value byte_var
        {
            log(byte_var);
            if (byte_var == 55)
            { setverdict(pass); } else { setverdict(fail); stop; }
        }
        [] p.catch // should also catch 5.0 timeout
        {
            setverdict(fail);
            stop;
        }
    }
}

Updating adapter code

  • to suppress MS VC++ deprecation warnings about standard C library functions, _CRT_SECURE_NO_WARNINGS symbol was added to:

Properties | Configuration Properties | C/C++ | Preprocessor | Preprocessor Definitions

  • the following fragment of code was added to tciEncode() before "else { otReportError ... " part:
    else if (!strcmp(typeName, "byte"))
    {
        long val = tciValueToLong(value);
        unsigned char casted_val = (unsigned char) val;

        len = 1;
        result.data = (unsigned char *) malloc(len);

        memcpy(result.data, &casted_val, len);
        // the above is the same as:
        // result.data[0] = casted_val
        // or even:
        // result.data[0] = (unsigned char) val;
        // or even:
        // result.data[0] = (unsigned char) tciValueToLong(value);

        result.bits = len << 3;
    }
    else if (!strcmp(typeName, "bool"))
    {
        len = 1;
        result.data = (unsigned char *) malloc(len);
        result.data[0] = tciValueToBoolean(value) ? 1 : 0;
        result.bits = len << 3;
    }
  • Utilities.h was updated as follows:
int tciValueToBoolean(TciValue value);
  • Utilities.c was updated as follows:
int tciValueToBoolean(TciValue value)
{
    return tciGetBooleanValue(value) ? 1 : 0;
}

Adding triCall()

Add the following in place of current triCall() in SA_impl.cxx:

TriStatus triCall
(const TriComponentId* componentId,
 const TriPortId* tsiPortId,
 const TriAddress* sutAddress,
 const TriSignatureId* signatureId,
 const TriParameterList* parameterList)
{
    if (!strcmp(signatureId->objectName, "MultiplyAndDivide"))
    {
        // The following section of code simulates a real SUT
        // servant. It should have been split into two threads,
        // one sending a request to the SUT and another one
        // listening for a response, but for simplicity we do
        // all in one single-threaded section of code.

        // Calculate business logic of the signature:

        unsigned char p1 = parameterList->parList[0]->par.data[0];
        unsigned char p2 = parameterList->parList[1]->par.data[0];

        if (!p2)
        {
            // TBD: throw DivisionByZero exception

            return TRI_ERROR;
        }

        unsigned char p1_div_p2 = p1 / p2;

        long p1_mul_p2 = p1 * p2;

        if (p1_mul_p2 > 255)
        {
            // TBD: throw Overflow exception

            return TRI_ERROR;
        }

        unsigned char casted_p1_mul_p2 = (unsigned char) p1_mul_p2;

        unsigned char p4 = parameterList->parList[3]->par.data[0];
        unsigned char p4_xor_true = p4 ^ 1;

        ///////////////////////////////////////////////////
        // Send a reply back to TE:

        BinaryString dummyEncoding;
        memset(&dummyEncoding, 0, sizeof(BinaryString));

        // prepare parameter list:

        TriParameterList dstParList;
        dstParList.length = 4;
        dstParList.parList = (TriParameter **) malloc(sizeof(TriParameter*) * 4);

        TriParameter dstParList_p1;
        dstParList_p1.mode = TRI_IN;
        dstParList_p1.par = dummyEncoding;
        dstParList.parList[0] = &dstParList_p1;

        TriParameter dstParList_p2;
        dstParList_p2.mode = TRI_IN;
        dstParList_p2.par = dummyEncoding;
        dstParList.parList[1] = &dstParList_p2;

        TriParameter dstParList_p3;
        dstParList_p3.mode = TRI_OUT;
        dstParList_p3.par = dummyEncoding;
        dstParList_p3.par.data = &p1_div_p2;
        dstParList_p3.par.bits = 1 << 3; // same as 8
        dstParList.parList[2] = &dstParList_p3;

        TriParameter dstParList_p4;
        dstParList_p4.mode = TRI_INOUT;
        dstParList_p4.par = dummyEncoding;
        dstParList_p4.par.data = &p4_xor_true;
        dstParList_p4.par.bits = 1 << 3; // same as 8
        dstParList.parList[3] = &dstParList_p4;

        // prepare return value:

        TriParameter retValPar;
        retValPar.mode = TRI_IN;
        retValPar.par = dummyEncoding;
        retValPar.par.data = &casted_p1_mul_p2;
        retValPar.par.bits = 1 << 3; // same as 8

        // prepare signature id:

        TriSignatureId dstSigId;
        memset(&dstSigId, 0, sizeof(TriSignatureId));
        dstSigId.objectName = "MultiplyAndDivide";

        // prepare TSI port id:

        TriPortId dstPortId;
        memset(&dstPortId, 0, sizeof(TriPortId));

        dstPortId.portName = tsiPortId->portName;
        dstPortId.portIndex = -1; // could be tsiPortId->portIndex
                                  // to support also port arrays

        triEnqueueReply(
            &dstPortId /* const TriPortId* tsiPortId */,
            0 /* const TriAddress* sutAddress */,
            0 /* const TriComponentId* componentId */,
            &dstSigId /* const TriSignatureId* signatureId */,
            &dstParList /* const TriParameterList* parameterList */,
            &retValPar /* const TriParameter* returnValue */);

        return TRI_OK;
    }
    else
    {
        otReportError("triCall(): Unrecognized signature name.");
        return TRI_ERROR;
    }

    return TRI_ERROR;
}

Do not forget to include several header files in the beginning of SA_impl.cxx:

#include <stdlib.h>
#include <string.h>
#include <memory.h>

#include <isl/TTCN3.h>

Improving tciDecode() error reporting

Add the following to the end of tciDecode():

otReportError("tciDecode(): Unrecognized value type.");

after:

printf("tciDecode(): Error: Unrecognized type '%s'.\n", typeName);

Adding decoders for bool and byte

Add the following code to tciDecode() before "else if (!decHypothesis) { ...":

    else if (!strcmp(typeName, "byte"))
    {
        return longToTciValue(message.data[0]);
    }
    else if (!strcmp(typeName, "bool"))
    {
        return booleanToTciValue(message.data[0]);
    }

Add this to Utilities.h:

TciValue booleanToTciValue(int value);

Add this to Utilities.c:

TciValue booleanToTciValue(int value)
{
    TciValue result = tciNewInstance(tciGetBooleanType());
    tciSetBooleanValue(result, value ? 1 : 0);
    return result;
}

Improving adapter quality

The following is a fix for the adapter code above. It introduces more strict type compliancy with the decoding hypothesis. Loose type compatibility in the previous code has led to a "silent" type name mismatch in getreply for the return value (TTCN-3 was expecting 'byte', while adapter generated 'integer').

Replaced previous update of CD_impl.cxx (tciEncode()) with the following fragment of code:

    else if (!strcmp(typeName, "byte"))
    {
        TciValue result = tciNewInstance(tciGetTypeForName("byte"));
        assignLongToTciValue(result, message.data[0]);
        return result;

        // return longToTciValue(message.data[0]);
    }
    else if (!strcmp(typeName, "bool"))
    {
        TciValue result = tciNewInstance(tciGetTypeForName("bool"));
        assignBooleanToTciValue(result, message.data[0]);
        return result;

        // return booleanToTciValue(message.data[0]);
    }

Added the following to Utilities.h:

void assignBooleanToTciValue(TciValue dst, int value);

Added the following to Utilities.c:

void assignBooleanToTciValue(TciValue dst, int value)
{
    tciSetBooleanValue(dst, value ? 1 : 0);
}

With these fixes we can now properly perform full matching of the return value in our Main.ttcn:

[] p.getreply(t_MultiplyAndDivide(omit, omit, 2, false) value 55) -> value byte_var

(Was:)

[] p.getreply(t_MultiplyAndDivide(omit, omit, 2, false)) -> value byte_var

Exception handling in procedure-based communication (triEnqueueException())

Updating Main.ttcn

Add the following code to Main.ttcn to the end of TC_proc_example:

    p.call(t_MultiplyAndDivide(11, 0, omit, true), 5.0)
    {
        [] p.catch(MultiplyAndDivide, DivisionByZero : { })
        {
            setverdict(pass);
        }
        [] p.getreply
        {
            setverdict(fail);
            stop;
        }
        [] p.catch // should also catch 5.0 timeout
        {
            setverdict(fail);
            stop;
        }
    }

Updating adapter

Add the following section of code in place of "if (!p2) { // TBD: throw DivisionByZero exception ..." in SA_impl.cxx:

        if (!p2)
        {
            // throw DivisionByZero exception

            // prepare exception data:

            BinaryString exc;
            memset(&exc, 0, sizeof(BinaryString));

            unsigned char MAGIC_DIV_BY_0_CODE = 0x3E;

            exc.data = &MAGIC_DIV_BY_0_CODE;
            exc.bits = 8;

            // prepare signature id:

            TriSignatureId dstSigId;
            memset(&dstSigId, 0, sizeof(TriSignatureId));
            dstSigId.objectName = "MultiplyAndDivide";

            // prepare TSI port id:

            TriPortId dstPortId;
            memset(&dstPortId, 0, sizeof(TriPortId));

            dstPortId.portName = tsiPortId->portName;
            dstPortId.portIndex = -1;

            triEnqueueException(
                &dstPortId /* const TriPortId* tsiPortId */,
                0 /*const TriAddress* sutAddress */,
                0 /*const TriComponentId* componentId */,
                &dstSigId /*const TriSignatureId* signatureId */,
                &exc /* const TriException* exception */);

            return TRI_OK;
        }

Updating CD_impl.cxx

Add the following to tciDecode():

        unsigned char MAGIC_DIV_BY_0_CODE = 0x3E;

        if (((unsigned char) buffer[0]) == MAGIC_DIV_BY_0_CODE)
        {
            return tciNewInstance(tciGetTypeForName("DivisionByZero"));
        }

after:

       typeCode = longToTciValue((unsigned char) buffer[0]);

and before:

       result = tciNewInstance(tciGetTypeForName("Message"));

One more exception handling example

Updating Main.ttcn

Add the following code to Main.ttcn to the end of TC_proc_example:

    p.call(t_MultiplyAndDivide(16, 17, omit, true), 5.0)
    {
        [] p.catch(MultiplyAndDivide, Overflow : { })
        {
            setverdict(pass);
        }
        [] p.getreply
        {
            setverdict(fail);
            stop;
        }
        [] p.catch // should also catch 5.0 timeout
        {
            setverdict(fail);
            stop;
        }
    }

Updating adapter

Add the following section of code in place of "if (p1_mul_p2 > 255) { // TBD: throw Overflow exception ..." in SA_impl.cxx:

        if (p1_mul_p2 > 255)
        {
            // throw Overflow exception

            // prepare exception data:

            BinaryString exc;
            memset(&exc, 0, sizeof(BinaryString));

            unsigned char MAGIC_OVERFLOW_CODE = 0x3F;

            exc.data = &MAGIC_OVERFLOW_CODE;
            exc.bits = 8;

            // prepare signature id:

            TriSignatureId dstSigId;
            memset(&dstSigId, 0, sizeof(TriSignatureId));
            dstSigId.objectName = "MultiplyAndDivide";

            // prepare TSI port id:

            TriPortId dstPortId;
            memset(&dstPortId, 0, sizeof(TriPortId));

            dstPortId.portName = tsiPortId->portName;
            dstPortId.portIndex = -1;

            triEnqueueException(
                &dstPortId /* const TriPortId* tsiPortId */,
                0 /*const TriAddress* sutAddress */,
                0 /*const TriComponentId* componentId */,
                &dstSigId /*const TriSignatureId* signatureId */,
                &exc /* const TriException* exception */);

            return TRI_OK;
        }

Updating CD_impl.cxx

Add the following to tciDecode():

        unsigned char MAGIC_OVERFLOW_CODE = 0x3F;

        if (((unsigned char) buffer[0]) == MAGIC_OVERFLOW_CODE)
        {
            return tciNewInstance(tciGetTypeForName("Overflow"));
        }

before:

       result = tciNewInstance(tciGetTypeForName("Message"));

Syntax for redirecting signature reply return value and parameters to variable

   var byte a1, a2, a1da2;
   var bool xp;

   p.call(t_MultiplyAndDivide(11, 5, omit, true), 5.0)
   {
       [] p.getreply(t_MultiplyAndDivide(omit, omit, 2, false) value 55)
            -> value byte_var param ( /* a1 := arg1, a2 := arg2, */
                                      a1da2 := arg1_div_arg2, xp := xor_par )
       {
           log(byte_var);
           log(a1da2);
           log(xp);

           if (byte_var == 55)
           { setverdict(pass); } else { setverdict(fail); stop; }
       }
       [] p.catch // should also catch 5.0 timeout
       {
           setverdict(fail);
           stop;
       }
   }

External function example

Updating Main.ttcn

Add the following to the end of Main.ttcn:

/////////////////////////////////////////////////////////////////////////////
// External function example

// arg1_div_arg2 contains arg1 / arg2
// return value of signature contains arg1 * arg2
// xor_par contains (original xor_par xor true)
//
// Upon return, exception_indicator is:
// 0 - upon success
// 1 - in case of division by zero
// 2 - in case of overflow
//
external function ext_f_MultiplyAndDivide(
    in byte arg1,
    in byte arg2,
    out byte arg1_div_arg2,
    inout bool xor_par,
    out byte exception_indicator)
return byte;

testcase TC_ext_f_example()
    runs on ComponentType
    system SystemInterfaceType
{
    activate(DefaultAltstep());
    T_GUARD.start;
    map(mtc:p, system:tsiPort);

    var byte a1_div_a2;
    var bool xor_arg := true;
    var byte exc_ind;

    var byte rv := ext_f_MultiplyAndDivide(11, 5, a1_div_a2, xor_arg, exc_ind);

    log("a1_div_a2:");
    log(a1_div_a2);

    log("xor_arg:");
    log(xor_arg);

    log("exc_ind:");
    log(exc_ind);

    log("rv:");
    log(rv);

    if (a1_div_a2 != 2) { setverdict(fail); stop; }
    if (xor_arg != false) { setverdict(fail); stop; }
    if (exc_ind != 0) { setverdict(fail); stop; }
    if (rv != 55) { setverdict(fail); stop; }

    setverdict(pass);
}

Updating PA_impl.cxx

Add the following in place of triExternalFunction() implementation:

TriStatus triExternalFunction
(const TriFunctionId* functionId, /* in parameter */
 TriParameterList* parameterList, /* inout parameter */
 TriParameter* returnValue /* out parameter */)
{
    // Example:

    if (!strcmp(functionId->objectName, "ext_f_MultiplyAndDivide"))
    {
        unsigned char p1 = parameterList->parList[0]->par.data[0];
        unsigned char p2 = parameterList->parList[1]->par.data[0];

        if (!p2)
        {
            // TBD: fill in DivisionByZero indicator

            return TRI_ERROR;
        }

        unsigned char p1_div_p2 = p1 / p2;

        long p1_mul_p2 = p1 * p2;

        if (p1_mul_p2 > 255)
        {
            // TBD: fill in Overflow indicator

            return TRI_ERROR;
        }

        unsigned char casted_p1_mul_p2 = (unsigned char) p1_mul_p2;

        unsigned char p4 = parameterList->parList[3]->par.data[0];
        unsigned char p4_xor_true = p4 ^ 1;

        // Fill in inout parameters with changed values,
        // assign out parameters:

        // Allocate memory for out parameter arg1_div_arg2. When you
        // return from triExternalFunction(), you transfer ownership
        // of this memory to the caller.

        parameterList->parList[2]->par.data =
            (unsigned char *) malloc(1);

        parameterList->parList[2]->par.data[0] = p1_div_p2;

        parameterList->parList[2]->par.bits = 8;

        // Change inout parameter xor_par if its original value
        // has changed. Otherwise, you don't have to touch it.

        // reallocate memory for storing inout parameter encoding
        // (free + malloc):

        free(parameterList->parList[3]->par.data);

        parameterList->parList[3]->par.data =
            (unsigned char *) malloc(1);

        parameterList->parList[3]->par.data[0] = p4_xor_true;

        parameterList->parList[3]->par.bits = 8;

        // Allocate memory for out parameter exception_indicator.

        parameterList->parList[4]->par.data =
            (unsigned char *) malloc(1);

        parameterList->parList[4]->par.data[0] = 0 /* SUCCESS */;

        parameterList->parList[4]->par.bits = 8;

        // Allocate memory for return value:

        returnValue->par.data =
            (unsigned char *) malloc(1);

        returnValue->par.data[0] = casted_p1_mul_p2;

        returnValue->par.bits = 8;

        return TRI_OK;
    }
    else
    {
        otReportError("Unrecognized external function");
        return TRI_ERROR;
    }

    return TRI_ERROR;
}

Filling in DivisionByZero indicator, Overflow indicator in external function

  • left as a material for self-study
  • hint: see how the indicator is filled in with zero (SUCCESS) (parameterList->parList[4]->par.data[0])

Example of adapter distribution

Modifications allowing adapter to register to session with another role

Change adapter self-registration code in main.cxx to the following:

    /* Register the adapter to OpenTTCN server */

    status = otRegisterAdapter(
        "simple", (argc > 1) ? argv[1] : "TRI");

Distributed config

  • run one adapter either using Ctrl+F5 from VC++, or simply from the command line without parameters. This will register the adapter to the session with the TRI role
  • run another copy of adapter binary using this command line:
adapter.exe TRI_PA

This will run second instance of adapter binary with TRI_PA role.

You will now observe that all communication except external functions is redirected to adapter with TRI role, while requests to calculate external functions are dispatched to the adapter with TRI_PA role.

  • run session status simple to see the list of available adapters (should be TRI, TRI_PA)
  • stop TRI_PA using this command:
session stop simple TRI_PA

Way to specify local network interface in case of multiple interfaces

Before calling:

   int status = otInitAdapter(0 /* same as "127.0.0.1:5500" */);

Put the following code to initialize adapter in main.cxx:

// OmniORB specific. Listen on local port 4343, local loopback (127.0.0.1):
const char* args[] = { "-ORBendPoint", "giop:tcp:127.0.0.1:4343", NULL };

int rv = otResolveInitialReferences("ORB", args);
  • documentation is available in isl/TTCN3.h in otResolveInitialReferences()
  • OmniORB manual gives more info about ORB-specific options
  • other possibilities:
    • "giop:tcp:127.0.0.1:" - any port (picks any available port randomly)
    • "giop:tcp::" - all local interfaces and any port (picked randomly for each interface)
  • OmniORB documentation:

http://omniorb.sourceforge.net/omni40/omniORB/

(we use 4.0.7)

  • way to increase ORB verbosity level (trace level):
const char* args[] = { "-ORBtraceLevel", "40", NULL };

int rv = otResolveInitialReferences("ORB", args);
  • ORB options can be combined:
const char* args[] = { "-ORBendPoint", "giop:tcp:127.0.0.1:4343",
"-ORBtraceLevel", "40", NULL };

int rv = otResolveInitialReferences("ORB", args);

PTC example

  • test scenario is on the 'physical' blackboard

Updating test suite (Main.ttcn)

Add the following to the end of Main.ttcn:

/////////////////////////////////////////////////////////////////////////////
// Parallel demonstration

type record Token { }

type record Token2 { }

type record Token3 { }

type component ExtendedComponentType
{
    port PortType p, p2, pp1, pp2;
    timer T_GUARD := 10.0;
}

type component Extended_TSI
{
    port PortType tsiPort1, tsiPort2;
}

function TC_parallel_PTC_1() runs on ExtendedComponentType
{
    activate(DefaultAltstep());
    T_GUARD.start;

    // step (1)

    p.receive(Token : { });

    // step (2)

    pp1.send(Token : { });
}

function TC_parallel_PTC_2() runs on ExtendedComponentType
{
    activate(DefaultAltstep());
    T_GUARD.start;

    // step (5)

    pp2.receive(Token2 : { });

    // step (6)

    p2.send(Token3 : { });
}

testcase TC_parallel()
    runs on ExtendedComponentType
    system Extended_TSI
{
    activate(DefaultAltstep());
    T_GUARD.start;

    var ExtendedComponentType ptcRef1, ptcRef2;

    ptcRef1 := ExtendedComponentType.create;
    ptcRef2 := ExtendedComponentType.create;

    connect(mtc:p, ptcRef1:p);
    connect(mtc:p2, ptcRef2:p2);

    map(ptcRef1:pp1, system:tsiPort1);
    map(ptcRef2:pp2, system:tsiPort2);

    ptcRef1.start(TC_parallel_PTC_1());
    ptcRef2.start(TC_parallel_PTC_2());

    // step (1):

    p.send(Token : { });

    // step (6)

    p2.receive(Token3 : { });

    setverdict(pass);
}

Updating adapter

Add the following code to tciEncode(), closer to the end, before "else { otReportError ...":

    else if (!strcmp(typeName, "Token"))
    {
        len = 1;
        result.data = (unsigned char *) malloc(len);
        result.data[0] = 0x41; // MAGIC CODE
        result.bits = len << 3;
    }

Add the following code to triSend(), to the very beginning of the function body, before this line:

   TciValue addrValue = *((TciValue *) sutAddress->data);

the code to be added:

    if ((sendMessage->bits == 8) &&
        (sendMessage->data[0] == 0x41 /* MAGIC for Token */) &&
        (!strcmp(tsiPortId->portName, "tsiPort1")))
    {
        // Forward Token2 to tsiPort2 according to spec on the
        // blackboard (steps 3, 4):

        // prepare message:

         BinaryString msg;
         memset(&msg, 0, sizeof(BinaryString));

         unsigned char MAGIC_TOKEN2_CODE = 0x42;

         msg.data = &MAGIC_TOKEN2_CODE;
         msg.bits = 8;

        // prepare TSI port id:

        TriPortId dstPortId;
        memset(&dstPortId, 0, sizeof(TriPortId));

        dstPortId.portName = "tsiPort2";
        dstPortId.portIndex = -1;

        // send Token2 to tsiPort2:

        triEnqueueMsg(&dstPortId, 0, 0, &msg);

        return TRI_OK;
    }

Add the following code to tciDecode(), before this line:

       result = tciNewInstance(tciGetTypeForName("Message"));

code to be added:

        unsigned char MAGIC_TOKEN2_CODE = 0x42;

        if (((unsigned char) buffer[0]) == MAGIC_TOKEN2_CODE)
        {
            return tciNewInstance(tciGetTypeForName("Token2"));
        }

Parallel config, several adapters

  • run one adapter as normally (with TRI role), run another one like this:
adapter.exe TRI:tsiPort1

You will see that communication goes through the second adapter.

Sending the token in the opposite direction

  • left as an exercise for self-study

Some more notes on memory management in TCI

Important comment in tci.h:

/**
 * OpenTTCN Specific: Unless otherwise specified, memory management model
 * for the return value in the Value interface and its subinterfaces,
 * including IntegerValue, CharstringValue etc., is as defined in [RM1].
 */
  • Similar comment governing default memory management rules for TCI-TM can be found in the beginning of the "TCI-TM required" section in tci.h.
  • Similar comment governing default memory management rules for TCI-CD can be found in the beginning of the "TCI-CD required" section in tci.h.

Developing TCI-TM example

  • open new instance of VC++ IDE
  • create new project, call it tcitm
  • in our example we will be using C:\courses\wsp6\tcitm folder for the example
  • Win32 console application
  • use these instructions as a base to configure your Visual C++ project
  • add openttcn-ttm-mt.lib in addition to the list above to add support for TCI-TM (Linker|Input|Additional dependencies)
  • add empty main.cxx to tcitm project so that VC++ knows that this is C++ project, so C++ project properties can appear
  • add skeletons for PA_impl.cxx SA_impl.cxx (dummy ones) according to instruction here

main.cxx

Add the following code to main.cxx:

/* 
    Copyright 2004-2006 OpenTTCN Oy
    All Rights Reserved

    OPENTTCN OY
    Web: http://www.openttcn.com Email: info@oes.fi
    P.O.BOX 6, FIN-53851 LAPPEENRANTA, FINLAND

*/

#ifdef _WIN32
#include <windows.h>
#else
#include <unistd.h>
#endif

#include "Utilities.h"

#include <tci/tci.h>
#include <isl/TTCN3.h>

#include <string>
#include <vector>
#include <iostream>

using namespace std;

#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <string.h>

#ifndef _WIN32
#define SLEEP_MILLIS(x) usleep((x) * 1000)
#else
#define SLEEP_MILLIS(x) Sleep(x)
#endif

static bool volatile _tc_running = false;
static bool volatile _ctrl_running = false;

static vector<TciVerdictValue> _verdicts;

static string _tcName;

static bool PC_run_tcitm_demo_from_control = false;
static bool PX_long_sleep_in_TC_tcitm_demo = false;
static bool PC_sleep_in_control = false;

static const char* line_sep =
"*****************************************************************************";

static const char* alt_line_sep =
"/////////////////////////////////////////////////////////////////////////////";

/////////////////////////////////////////////////////////////////////////////
// Helper operations.

inline void waitUntilTestcaseExecutionComplete()
{
    while (_tc_running)
    {
        SLEEP_MILLIS(200);
    }
}

inline void waitUntilControlExecutionComplete()
{
    while (_ctrl_running)
    {
        SLEEP_MILLIS(200);
    }
}

inline void runTestCase(TciParameterListType parList)
{
    _tc_running = true;

    // Note that tciStartTestCase() has non-blocking semantics, i.e. it does
    // not block until test case execution is complete:
    tciStartTestCase((char *) _tcName.c_str(), parList);

    waitUntilTestcaseExecutionComplete();
}

inline void runControl()
{
    _ctrl_running = true;

    // Note that tciStartControl() has non-blocking semantics, i.e. it does
    // not block until control part execution is complete:
    tciStartControl();

    waitUntilControlExecutionComplete();
}

inline void reportStartOfControlPart(int no_)
{
    cout << endl;
    cout << line_sep << endl;
    cout << "*** RUNNING CONTROL PART " << no_ << endl;
    cout << endl;
}

/////////////////////////////////////////////////////////////////////////////
// TCI-TM, user-provided part.

extern "C" void tciTestCaseStarted
(TciTestCaseIdType testCaseId,
 TciParameterListType parameterList,
 double timer)
{
    cout << endl;
    cout << line_sep << endl;
    cout << "*** RUNNING TEST CASE " << testCaseId;

    if (timer >= 0.0)
    {
        cout << " (MAX. DURATION: " << doubleToStr(timer) << ")";
    }

    cout << endl;

    if (parameterList.length)
    {
        cout << "*** " << stringifyParameterList(parameterList) << endl;
    }

    cout << endl;

    _tc_running = true;
}

extern "C" void tciTestCaseTerminated
(TciVerdictValue verdict,
 TciParameterListType parameterList)
{
    string v;

    if (verdict == TCI_VERDICT_PASS)
    {
        v = "PASS";
    }
    else if (verdict == TCI_VERDICT_FAIL)
    {
        v = "FAIL";
    }
    else if (verdict == TCI_VERDICT_INCONC)
    {
        v = "INCONC";
    }
    else if (verdict == TCI_VERDICT_NONE)
    {
        v = "NONE";
    }
    else
    {
        v = "ERROR";
    }

    cout << endl;
    cout << alt_line_sep << endl;
    cout << "/// TEST CASE EXECUTION COMPLETE ";
    cout << "(VERDICT: " << v << ")" << endl;

    if (parameterList.length)
    {
        cout << "/// " << stringifyParameterList(parameterList) << endl;
    }

    cout << endl;

    _verdicts.push_back(verdict);
    _tc_running = false;
}

extern "C" void tciControlTerminated()
{
    _ctrl_running = false;

    cout << endl;
    cout << alt_line_sep << endl;
    cout << "/// CONTROL PART EXECUTION COMPLETE" << endl;
    cout << endl;
}

extern "C" void tciLog(String message)
{
    cout << message << endl;
}

TciValue tciGetModulePar
(TciModuleParameterIdType parameterId)
{
    TciValue result = 0;
    string parId = parameterId;

    if (parId == "PX_SUT_IP_ADDR")
    {
        result = charstringToTciValue("127.0.0.1");
    }
    else if (parId == "PX_SUT_PORT")
    {
        result = longToTciValue(6543);
    }
    else if (parId == "PX_par_2")
    {
        result = charstringToTciValue("Supplied value of parameter #2");
    }
    else if (parId == "PX_status_OK")
    {
        result = tciNewInstance(tciGetTypeForName("inquiry_status"));
        tciSetRecFieldValue(result, "status", longToTciValue(27));
        tciSetRecFieldValue(result, "extended", octetstringToTciValue("780E"));
    }
    else if (parId == "PC_run_tcitm_demo_from_control")
    {
        result = booleanToTciValue(PC_run_tcitm_demo_from_control);
    }
    else if (parId == "PX_long_sleep_in_TC_tcitm_demo")
    {
        result = booleanToTciValue(PX_long_sleep_in_TC_tcitm_demo);
    }
    else if (parId == "PC_sleep_in_control")
    {
        result = booleanToTciValue(PC_sleep_in_control);
    }

    return result;
}

/////////////////////////////////////////////////////////////////////////////
// Main function.

int main (int argc, char *argv[])
{
    int i, len, j, jlen;

    /////////////////////////////////////////////////////////////////////////
    // Initialization part:

    int status = otInitAdapter
        (0 /* can also be for example "", "127.0.0.1", or "127.0.0.1:5500" */);

    if (status)
    {
        cout << "main(): otInitAdapter() failed." << endl;
        exit(status);
    }

    string sessionName = (argc == 2) ? argv[1] : "simple";

    status = otTMSelectSession(sessionName.c_str());

    if (status)
    {
        cout << "main(): otTMSelectSession() failed." << endl;
        exit(status);
    }

    /////////////////////////////////////////////////////////////////////////
    // Explore the list of parameters:

    cout << endl;
    cout << "Module parameters" << endl;
    cout << "-----------------" << endl;
    cout << endl;

    TciModuleIdType moduleName;
    memset(&moduleName, 0, sizeof(TciModuleIdType));

    TciModuleParameterListType params = tciGetModuleParameters(moduleName);

    len = params.length;
    for (i = 0; i < len; i++)
    {
        TciModuleParameterType* elem = params.modParList + i;
        TciValue v = elem->defaultValue;

        cout << elem->parName;

        if (v)
        {
            cout << ":[" << tciGetName(tciGetType(v)) << "]";

            cout << endl;
            char* ptr = otStringifyTciValue(v, 0);
            cout << ptr;
            free(ptr);

/*
            cout << endl;
            ptr = otStringifyTciValue(v, OT_CD_STRINGIFY_INDENTED);
            cout << ptr;
            free(ptr);
*/
        }

        cout << endl;
        cout << endl;
    }

    /////////////////////////////////////////////////////////////////////////
    // Explore the list of test cases:

    cout << "Test cases" << endl;
    cout << "----------" << endl;
    cout << endl;

    TciTestCaseIdListType cases = tciGetTestCases();

    len = cases.length;
    for (i = 0; i < len; i++)
    {
        QualifiedName* elem = cases.idList + i;
        cout << elem->objectName;

        // Introspect test case formal parameters:

        TciParameterTypeListType formalPars =
            tciGetTestCaseParameters(elem->objectName);

        jlen = formalPars.length;

        cout << "(";

        for (j = 0; j < jlen; j++)
        {
            if (j) cout << ", ";
            cout << tciGetName(formalPars.parList[j]);
            cout << " p" << (j + 1);
        }

        cout << ")";

        cout << endl;
    }

    cout << endl;

    /////////////////////////////////////////////////////////////////////////
    // Print an exemplary configuration, i.e. TSI port list, for the
    // first three test cases:

    cout << "Test cases configuration" << endl;
    cout << "------------------------" << endl;
    cout << endl;

    int cfgLen = len;
    if (cfgLen > 3) cfgLen = 3;

    for (i = 0; i < cfgLen; i++)
    {
        QualifiedName* elem = cases.idList + i;

        TriPortIdList portList = tciGetTestCaseTSI(elem->objectName);
        jlen = portList.length;

        cout << "CONFIGURATION: " << elem->objectName;
        cout << ": " << jlen << " port(s)" << endl;
        cout << endl;

        for (j = 0; j < jlen; j++)
        {
            TriPortId* portId = portList.portIdList[j];

            cout << stringifyTriPortId(portId) << endl;
            cout << endl;
        }
    }

    // Go no further if the session name is not hello:
    if (sessionName != "simple") exit(0);

/*
    /////////////////////////////////////////////////////////////////////////
    // Run exemplary test cases (direct execution of test cases through
    // TCI-TM):

    cout << "Test cases execution" << endl;
    cout << "--------------------" << endl;

    TciParameterListType parList;
    TciParameterType* par = 0;
    memset(&parList, 0, sizeof(TciParameterListType));

    // Execute TC_proc_example:

    _tcName = "TC_proc_example";
    runTestCase(parList);

    // Execute TC_ext_f_example:

    _tcName = "TC_ext_f_example";
    runTestCase(parList);
*/

    exit(0);
    return 0;
}

Utilities.h

Add the following code to a newly-created Utilities.h:

/* 
    Copyright 2004-2006 OpenTTCN Oy
    All Rights Reserved

    OPENTTCN OY
    Web: http://www.openttcn.com Email: info@oes.fi
    P.O.BOX 6, FIN-53851 LAPPEENRANTA, FINLAND

*/

#ifndef __OT_SDK_SAMPLES_UTIL_UTILITIES_H__
#define __OT_SDK_SAMPLES_UTIL_UTILITIES_H__

#include <tci/tci.h>
#include <isl/Mutex.h>

#include <vector>
#include <string>

////////////////////////////////////////////////////////////////////////////
// Utility operations for synhronization of a printout.

// Mutex to synchronize on printing of messages to the terminal window
// from different threads. Without this synchronization, printout may
// become incomprehensive if threads are trying to print something at
// the same time.
OpenTTCN::ISL::Mutex& getPrintingMutex();

////////////////////////////////////////////////////////////////////////////
// Utility operations for binding a socket and sending data to a UDP port.

class PortMappingDescriptor
{

public:

    // TTCN-3 TSI port name, applicable for SUT adapters only,
    // N/A for an exemplary SUT (System Under Test).
    std::string portName;

    // IP address of the local interface, as returned by inet_addr()
    // function, or containing INADDR_ANY if any local IP address can
    // be used.
    unsigned long ipAddr;

    // Local port through which messages are sent and received.
    int portNumber;

    // Socket descriptor that was a result of the socket() and bind()
    // operations performed in bindSocketToPort().
    int socketDesc;

    // Creates a socket bound to the UDP port specified by portNumber
    // of the local interface specified by ipAddr. The result of this
    // operation is assigned to the socket descriptor socketDesc that
    // identifies a newly bound socket.
    void bindSocketToPort();

    // Sends a UDP packet to the specified host and port. Local socket
    // should be bound by the time of invoking this operation using
    // bindSocketToPort(). The length_ parameter indicates size of the
    // payload in octets.
    void sendDatagramPacket(unsigned long host_,
        unsigned short port_, const char* data_, long length_);
};

void registerPortMappingDescriptor(const PortMappingDescriptor& desc);

std::vector<PortMappingDescriptor>& getPortMappingDescriptors();

// Returns 0 pointer if no such port mapping descriptor with the
// specified port name has been found.
PortMappingDescriptor* getPortMappingDescriptor(const std::string& portName);

////////////////////////////////////////////////////////////////////////////
// TCI-CD-related utility functions.

long tciValueToLong(TciValue value);
void assignLongToTciValue(TciValue* dst, long value);
TciValue longToTciValue(long value);

std::string tciValueToCharstring(TciValue value);
void assignCharstringToTciValue(TciValue* dst, const std::string& value);
TciValue charstringToTciValue(const std::string& value);

TciValue booleanToTciValue(bool value);

std::string tciValueToOctetstring(TciValue value);
void assignOctetstringToTciValue(TciValue* dst, const std::string& value);
TciValue octetstringToTciValue(const std::string& value);

////////////////////////////////////////////////////////////////////////////
// TCI-TM-related utility functions.

void printVerdictsInfo(const std::vector<TciVerdictValue>& verdicts);

////////////////////////////////////////////////////////////////////////////
// Value conversion utility functions.

std::string intToStr(int value);
std::string longToStr(long value);
std::string doubleToStr(double value);

////////////////////////////////////////////////////////////////////////////
// Utility functions providing stringified representation of data.

std::string stringifyQualifiedName(QualifiedName* v);
std::string stringifyTriComponentId(TriComponentId* v);
std::string stringifyTriPortId(TriPortId* v);
std::string stringifyParameterList(TciParameterListType parameterList);

////////////////////////////////////////////////////////////////////////////
// Miscellaneous utility functions.

std::string formatHexMessage(const std::string& message);

// For value == "aa:b::c" and separator == ':', the result would be
// a vector of { "aa", "b", "c" }.
std::vector<std::string> split(const std::string& value, char separator);

#endif

Utilities.cxx

Add the following code to a newly-created Utilities.cxx:

/* 
    Copyright 2004-2006 OpenTTCN Oy
    All Rights Reserved

    OPENTTCN OY
    Web: http://www.openttcn.com Email: info@oes.fi
    P.O.BOX 6, FIN-53851 LAPPEENRANTA, FINLAND

*/

#include "Utilities.h"

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <assert.h>
#include <ctype.h>

#ifndef _WIN32
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#else
#include <winsock2.h>
typedef int socklen_t;
#endif

#include <iostream>
using namespace std;

#define MAX_BUFFER_SIZE 8192

static OpenTTCN::ISL::Mutex _printing_mutex;

static vector<PortMappingDescriptor> _portMappingDescriptors;

static const char* line_sep =
"*****************************************************************************";

OpenTTCN::ISL::Mutex& getPrintingMutex()
{
    return _printing_mutex;
}

void PortMappingDescriptor :: bindSocketToPort()
{
    int rval;
    static struct sockaddr_in myaddr;

    socketDesc = socket(PF_INET, SOCK_DGRAM, 0);

    memset (&myaddr, 0, sizeof myaddr);
    myaddr.sin_addr.s_addr = ipAddr;

#ifdef _WIN32
    myaddr.sin_family = AF_INET;
#endif

    myaddr.sin_port = htons((unsigned short) portNumber);
    rval = bind(socketDesc, (struct sockaddr *) &myaddr, sizeof myaddr);

    if (rval < 0)
    {

#ifndef _WIN32
        printf("PortMappingDescriptor::bindSocketToPort(): "
            "Fatal Error: bind() returned: %s\n", strerror(errno));
#else
        int errorCode = WSAGetLastError();

        printf("PortMappingDescriptor::bindSocketToPort(): "
            "Fatal Error: bind() returned error code %i\n", errorCode);
#endif

        exit(-1);
    }
}

void PortMappingDescriptor :: sendDatagramPacket(unsigned long host_,
    unsigned short port_, const char* data_, long length_)
{
    int rval;

    struct sockaddr_in saddr;
    memset (&saddr, 0, sizeof saddr);

    saddr.sin_family = AF_INET;

    saddr.sin_addr.s_addr = host_;
    saddr.sin_port = port_;

    while (1)
    {
        rval = sendto (
            socketDesc, 
            data_, 
            length_, 
            0,
            (struct sockaddr *) &saddr, 
            sizeof saddr);

        if (-1 == rval)
        {
#ifndef _WIN32
            printf("sendDatagramPacket(): send error = %s\n", strerror(errno));
#else
            int errorCode = WSAGetLastError();
            printf("sendDatagramPacket(): send error code = %i\n", errorCode);
#endif

        }

        if ((-1 == rval) && ((errno == EAGAIN) || (errno == EINTR)))
        {
            printf("sendDatagramPacket(): resending\n");
        }
        else if (-1 == rval)
        {
            printf("sendDatagramPacket(): failed to send packet\n");
            break;
        }
        else
        {
            // Send operation succeeded.
            break;
        }
    }
}

void registerPortMappingDescriptor(const PortMappingDescriptor& desc)
{
    _portMappingDescriptors.push_back(desc);
}

vector<PortMappingDescriptor>& getPortMappingDescriptors()
{
    return _portMappingDescriptors;
}

PortMappingDescriptor* getPortMappingDescriptor(const string& portName)
{
    int i, len;

    len = _portMappingDescriptors.size();
    for (i = 0; i < len; i++)
    {
        PortMappingDescriptor& desc = _portMappingDescriptors[i];
        if (portName == desc.portName) return &desc;
    }

    return 0;
}

long tciValueToLong(TciValue value)
{
    String valNumberAbs;
    Boolean valNumberSign;

    char* valNumber;
    long val;

    valNumberAbs = tciGetIntAbs(value);
    valNumberSign = tciGetIntSign(value);

    valNumber = (char *) malloc(strlen(valNumberAbs) + 2);
    valNumber[0] = valNumberSign ? '+' : '-';
    strcpy(valNumber + 1, valNumberAbs);

    val = atol(valNumber);
    free(valNumber);

    return val;
}

void assignLongToTciValue(TciValue* dst, long value)
{
    char valAbsValue[MAX_BUFFER_SIZE];
    Boolean valSignValue;

    valSignValue = (value >= 0) ? 1 : 0;
    if (value < 0) value = -value;

    sprintf(valAbsValue, "%li", value);

    tciSetIntAbs(*dst, valAbsValue);
    tciSetIntSign(*dst, valSignValue);
}

TciValue longToTciValue(long value)
{
    TciValue result = tciNewInstance(tciGetIntegerType());
    assignLongToTciValue(&result, value);
    return result;
}

string tciValueToCharstring(TciValue value)
{
    TciCharStringValue cStringValue = tciGetCStringValue(value);
    return string (cStringValue.string + 1, cStringValue.length);
}

void assignCharstringToTciValue(TciValue* dst, const string& value)
{
    TciCharStringValue cStringValue;

    string rawValue = string("\"") + value + "\"";
    cStringValue.length = value.size();
    cStringValue.string = (char *) rawValue.c_str();

    tciSetCStringValue(*dst, cStringValue);
}

TciValue charstringToTciValue(const string& value)
{
    TciValue result = tciNewInstance(tciGetTciCharstringType());
    assignCharstringToTciValue(&result, value);
    return result;
}

TciValue booleanToTciValue(bool value)
{
    TciValue result = tciNewInstance(tciGetBooleanType());
    tciSetBooleanValue(result, value ? 1 : 0);
    return result;
}

string tciValueToOctetstring(TciValue value)
{
    string result;
    String val = tciGetOStringValue(value);

    if (val)
    {
        result = string(val);
        result = result.substr(1, result.size() - 3);
    }

    return result;
}

void assignOctetstringToTciValue(TciValue* dst, const string& value)
{
    string rawValue = string("'") + value + "'O";
    tciSetOStringValue(*dst, (char *) rawValue.c_str());
}

TciValue octetstringToTciValue(const string& value)
{
    TciValue result = tciNewInstance(tciGetOctetstringType());
    assignOctetstringToTciValue(&result, value);
    return result;
}

inline string spc(int num)
{
    string result;
    while ((num--) > 0) result += " ";
    return result;
}

void printVerdictsInfo(const vector<TciVerdictValue>& verdicts)
{
    int i;

    int pass = 0;
    int fail = 0;
    int inconc = 0;
    int none = 0;
    int error = 0;

    int total = verdicts.size();
    for (i = 0; i < total; i++)
    {
        TciVerdictValue verdict = verdicts[i];

        if (verdict == TCI_VERDICT_PASS) pass++;
        else if (verdict == TCI_VERDICT_FAIL) fail++;
        else if (verdict == TCI_VERDICT_INCONC) inconc++;
        else if (verdict == TCI_VERDICT_NONE) none++;
        else error++;

    }

    string passStr      = intToStr(pass);
    string failStr      = intToStr(fail);
    string inconcStr    = intToStr(inconc);
    string noneStr      = intToStr(none);
    string errorStr     = intToStr(error);
    string totalStr     = intToStr(total);

    cout << line_sep << endl;
    cout << "*** TEST EXECUTION SUMMARY" << endl << endl;

    cout << "Pass    Fail    Inconc    None    Error    Total" << endl;

    cout << passStr     << spc( 8 - passStr.size());
    cout << failStr     << spc( 8 - failStr.size());
    cout << inconcStr   << spc(10 - inconcStr.size());
    cout << noneStr     << spc( 8 - noneStr.size());
    cout << errorStr    << spc( 9 - errorStr.size());
    cout << totalStr    << "";

    cout << endl;
}

string intToStr(int value)
{
    char buffer[MAX_BUFFER_SIZE];
    sprintf(buffer, "%i", value);
    string str = buffer;
    return str;
}

string longToStr(long value)
{
    char buffer[MAX_BUFFER_SIZE];
    sprintf(buffer, "%li", value);
    string str = buffer;
    return str;
}

string doubleToStr(double value)
{
    char buffer[MAX_BUFFER_SIZE];
    sprintf(buffer, "%G", value);
    string str = buffer;

    if ((split(str, '.').size() == 1) &&
        (split(str, 'E').size() == 1))
    {
        str += ".0";
    }

    return str;
}

string stringifyQualifiedName(QualifiedName* v)
{
    return string() + "\"" + v->objectName + "\"";
}

string stringifyTriComponentId(TriComponentId* v)
{
    return string() + "{ compInst = \"" + (char *) v->compInst.data + 
        "\" compType = " + stringifyQualifiedName(&(v->compType)) + " }";
}

string stringifyTriPortId(TriPortId* v)
{
    char buffer[256];
    sprintf(buffer, "%li", v->portIndex);
    string str = buffer;

    return string() + "{ " +
        "compInst = " + stringifyTriComponentId(&(v->compInst)) + " " +
        "portName = \"" + v->portName + "\" portIndex = " + str +
        " portType = " + stringifyQualifiedName(&(v->portType)) + " }";
}

string stringifyParameterList
(TciParameterListType parameterList)
{
    int i, len;
    string result;

    result += "(";

    len = parameterList.length;
    for (i = 0; i < len; i++)
    {
        TciParameterType* par = parameterList.parList + i;

        if (i) result += ", ";

        TciParameterPassingModeType parPassMode = par->parPassMode;

        if (parPassMode == TCI_IN_PAR)
        {
            result += "in";
        }
        else if (parPassMode == TCI_INOUT_PAR)
        {
            result += "inout";
        }
        else if (parPassMode == TCI_OUT_PAR)
        {
            result += "out";
        }

        if (par->parValue)
        {
            result += string() + " " + tciGetName(tciGetType(par->parValue));
        }

        result += string() + " p" + intToStr(i + 1) + " := ";

        string parValue = "-";

        if (par->parValue)
        {
            char* buff = otStringifyTciValue(par->parValue, 0);
            parValue = buff;
            free(buff);
        }

        result += parValue;
    }

    result += ")";
    return result;
}

string formatHexMessage(const string& message)
{
    int i, len;
    const int MAX_COLUMN = 72;

    int column = 0;
    string result;

    len = message.size();
    for (i = 0; i < len; i++)
    {
        result += message[i];
        column++;

        if ((i % 2))
        {
            if (column >= MAX_COLUMN)
            {
                if (i != len - 1)
                {
                    result += '\n';
                    column = 0;
                }
            }
            else
            {
                result += ' ';
                column++;
            }
        }
    }

    return result;
}

vector<string> split(const string& value, char separator)
{
    vector<string> result;

    int size = value.size();
    const char* buffer = value.c_str();

    if (!size) return result;

    string line;

    char ch;
    int index = 0;
    
    while (index < size)
    {
        ch = buffer[index];

        if (ch == separator)
        {
            if (line != "") result.push_back(line);
            line = "";
        }
        else line += ch;

        index++;
    }

    if (line != "") result.push_back(line);
    return result;
}

Distributed infrastructure

  • more information and documentation on DI is available from OpenTTCN on demand

SDK for Java

  • SDK for Java contains an example that can be used as a base for development


Thank you!
Views
Personal tools