OpenTTCN/Developer corner/Implementing external functions

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


Implementing external functions



In this article we implement TTCN-3 external functions in a platform adapter using ETSI TRI-PA interface as defined in ETSI ES 201 873-5 V4.2.1 (2010-07): Methods for Testing and Specification (MTS); The Testing and Test Control Notation version 3; Part 5: TTCN-3 Runtime Interface (TRI).


We use OpenTTCN Tester 2011 4.1.2 for this purpose. We show how to do this for:

  • C language mapping of TRI-PA using OpenTTCN SDK for C
  • C# language mapping of TRI-PA using OpenTTCN SDK for C#
  • C++ language mapping of TRI-PA using OpenTTCN SDK for C++
  • Java language mapping of TRI-PA using OpenTTCN SDK for Java


Contents

Creating adapter skeleton

Here we develop some template code and set up the infrastructure necessary to run the adapter. Please refer to other articles in this wiki and to OpenTTCN documentation for details about adapter programming fundamentals explaining structure of the template code below.

Create an adapter programming project in the relevant development environment, and configure it accordingly. Please refer to Chapter 4 Linking OpenTTCN Libraries of OpenTTCN SDK User Guide and articles in this wiki for details on adapter project configuration.

Add the following skeleton files to the adapter project:


Image:language_mapping_ansi_c.png

Note that although the code above is written in C++, it uses C API of OpenTTCN SDK for C, and C language mapping of TRI-PA specified by ETSI.

In the above we define triExternalFunction() skeleton in PA_impl.cxx and register this implementation to the SDK in main.cxx using otSetTriPACallbackFunctionPointers() call.

We register this adapter as platform adapter to the Test Execution (TE) using otRegisterAdapter() call in main.cxx by specifying adapter role as "TRI_PA" (meaning platform adapter).

Image:language_mapping_c_sharp.png

Image:language_mapping_cpp.png

Image:language_mapping_java.png


The TE will forward external function evaluation requests to the registered adapter, and the SDK library will invoke proper registered callback handler to serve the request.

Defining TTCN-3 external functions

Now we need to have definitions of TTCN-3 external functions to be implemented in our adapter.

All external functions are defined in one TTCN-3 module, ef_example_module.ttcn:

module ef_example_module
{

///////////////////////////////////////////////////////////////////
// External function definitions

// returns a1 + a2
external function ef_calc_sum(integer a1, integer a2) return integer;

// returns a1 - a2
external function ef_calc_sub(integer a1, integer a2) return integer;

// returns a1 * a2
external function ef_calc_mul(integer a1, integer a2) return integer;

// returns a1 / a2
external function ef_calc_div(integer a1, integer a2) return integer;

///////////////////////////////////////////////////////////////////
// External functions testing

type component MTC { }

testcase TC_test_ef() runs on MTC
{
    var integer result;

    result := ef_calc_sum(5, 10);
    log("5 + 10 = " & int2str(result));
    if (result != 15) { setverdict(fail); stop; }

    result := ef_calc_sub(5, 10);
    log("5 - 10 = " & int2str(result));
    if (result != -5) { setverdict(fail); stop; }

    result := ef_calc_mul(5, 10);
    log("5 * 10 = " & int2str(result));
    if (result != 50) { setverdict(fail); stop; }

    result := ef_calc_div(5, 10);
    log("5 / 10 = " & int2str(result));
    if (result != 0) { setverdict(fail); stop; }

    setverdict(pass);
}

}

Although all external functions above are so simple that they could be more conveniently implemented using TTCN-3 builtin arithmetic operators (as regular, non-external functions), we chose to implement them externally for educational purposes.

In more complex cases TTCN-3 alone may not be enough, and the benefit of having implemented them externally may become more obvious. This is especially the case with computationally heavy algorithms like checksum calculation or encryption, or with functionality not readily available in TTCN-3, such as database or filesystem access.

You need to compile the above ef_example_module.ttcn module into the ext_func_example session/project. Preferred way to do this is using OpenTTCN IDE. From the command line this can be done as follows:

importer3 load ext_func_example ef_example_module.ttcn

Now if you try to run the TC_test_ef testcase after you compile and start the platform adapter, you will see the following error message in the campaign log:

mtc : {17:17:09.159} : // VERDICT ef_example_module.TC_test_ef TEST CASE ERROR: E4205: triExternalFunction() return status is not TRI_OK.

This is because our skeleton triExternalFunction() implementation in PA_impl currently does not do anything useful, and it returns TRI_ERROR to indicate this fact.

Implementing TTCN-3 external functions

To implement the specified TTCN-3 external functions we need to add proper code to the triExternalFunction() callback handler.

The triExternalFunction() callback handler receives all in and inout parameters of external function in encoded form, and it needs to return all inout and out parameters and return value of the external function also in the encoded form.

We provided generic encoder and decoder in CD_impl when we were creating the adapter skeleton. You may want to study the CD_impl code to see what encoding it produces and what coded streams it accepts.

Replace the existing content of PA_impl with the following code:


Image:language_mapping_ansi_c.png

Image:language_mapping_c_sharp.png

Image:language_mapping_cpp.png

Image:language_mapping_java.png


As you can see, we organized our collection of external functions as a map which is looked up in the triExternalFunction() function. This is done to speed up selection of proper external function implementation, what especially makes sense if there are tens or hundreds of them.

The tests in the TC_test_ef testcase should now pass, producing the following log output:

...
mtc : {12:38:50.968} : log("5 + 10 = 15");
mtc : {12:38:50.994} : log("5 - 10 = -5");
mtc : {12:38:51.023} : log("5 * 10 = 50");
mtc : {12:38:51.049} : log("5 / 10 = 0");
...

Handling inout and out parameters

In addition to in parameter passing mode which is the default, it is possible to specify out and inout parameters for TTCN-3 external functions, much like it is possible to do so for regular TTCN-3 functions. The in parameter passing mode means 'pass by value', inout means 'pass by reference', and out means 'alternative to return value'.

A rule of thumb is that you need to assign values to all out parameters and assign new values to those inout parameters that have changed.

In case of C mapping, there are additional rules governing memory management. For all i th parameters in the parameter list that are out parameters you need to do malloc() for the parameterList->parList[i]->par.data field, and for all inout i th parameters in the parameter list that have changed you need to free() and then malloc() the parameterList->parList[i]->par.data field prior to assigning a new encoded value to the field. You don't need to touch in parameters and those inout parameters that have not changed as a result of external function evaluation.


Add the following external function definition to ef_example_module.ttcn:

/**
 * Replaces all occurences of search_substr in str with new_substr.
 *
 * Both search_substr and new_substr must be of the same length.
 *
 * Returns the resulting string in the same str parameter.
 *
 * Upon return occurences_num contains number of actual replacements
 * made, what is duplicated in the external function return value.
 */
external function ef_find_and_replace(
    inout charstring str,
    in charstring search_substr,
    in charstring new_substr,
    out integer occurences_num)
return integer;


Add the TC_test_ef_find_and_replace testcase to ef_example_module.ttcn to test our planned implementation of the ef_find_and_replace() external function:

testcase TC_test_ef_find_and_replace() runs on MTC
{
    var integer result, num_of_occurences;

    var charstring s := "In the town he stayed for the purpose.";

    log("original s = " & s);

    result := ef_find_and_replace(s, "the", "thy", num_of_occurences);

    log("modified s = " & s);
    log("num_of_occurences = " & int2str(num_of_occurences));
    log("result = " & int2str(result));

    if (s != "In thy town he stayed for thy purpose.") { setverdict(fail); stop; }
    if (num_of_occurences != 2) { setverdict(fail); stop; }
    if (result != 2) { setverdict(fail); stop; }

    setverdict(pass);
}

Adding ef_find_and_replace implementation

Implement the ef_find_and_replace() external function in the platform adapter by updating PA_impl as follows:


Image:language_mapping_ansi_c.png

Add the following function signature to the Forward declarations section of PA_impl:

TriStatus ef_find_and_replace(const TriFunctionId* functionId,
    TriParameterList* parameterList, TriParameter* returnValue);

Add the following line to initializePA():

    _extFuncMap["ef_find_and_replace"] = &ef_find_and_replace;

Add the following code to the Helpers section of PA_impl:

std::string triParameterToString(TriParameter* src)
{
    long str_len =
        (((unsigned long) src->par.data[0]) << 8) |
        (((unsigned long) src->par.data[1]) << 0);

    return std::string((const char *) (src->par.data + 2), str_len);
}

void assignStringToTriParameter(TriParameter* dst, const std::string& src)
{
    long str_len = (long) src.size();

    dst->par.data = (unsigned char *) malloc(str_len + 2);
    dst->par.bits = (str_len + 2) << 3;

    // String length, most significant byte first:

    dst->par.data[0] = (unsigned char) (str_len >> 8);
    dst->par.data[1] = (unsigned char) (str_len >> 0);

    // String data:

    memcpy(dst->par.data + 2, src.c_str(), str_len);
}

Add the following code to the External function implementations section of PA_impl:

void ef_find_and_replace_helper(
    std::string& str /* inout */,
    const std::string& search_substr /* in */,
    const std::string& new_substr /* in */,
    long& occurences_num /* out */)
{
    int i, len;
    int substr_size = (int) search_substr.size();

    if (substr_size != new_substr.size())
    {
        otReportError("Strings size mismatch in ef_find_and_replace().");
        return;
    }

    if (!substr_size)
    {
        otReportError("Search string size is zero in ef_find_and_replace().");
        return;
    }

    occurences_num = 0;
    if (substr_size > (int) str.size())
    {
        // No way bigger string can be a substring of the smaller one:
        return; 
    }

    len = ((int) str.size()) - substr_size;
    for (i = 0; i <= len; i++)
    {
        if (str.substr(i, substr_size) == search_substr)
        {
            str.replace(i, substr_size, new_substr);
            occurences_num++;
            i += substr_size - 1;
        }
    }
}

TriStatus ef_find_and_replace(const TriFunctionId* functionId,
    TriParameterList* parameterList, TriParameter* returnValue)
{
    TriParameter** pars = parameterList->parList;

    std::string str = triParameterToString(pars[0]);
    std::string search_substr = triParameterToString(pars[1]);
    std::string new_substr = triParameterToString(pars[2]);
    long occurences_num = -1;

    ef_find_and_replace_helper(
        str, search_substr, new_substr, occurences_num);

    // It is safe to assume that inout str always changes:

    free(pars[0]->par.data);
    assignStringToTriParameter(pars[0], str);

    // Perform mandatory assignment of occurences_num out parameter:

    assignLongToTriParameter(pars[3], occurences_num);

    // Duplicate occurences_num in the return value:

    assignLongToTriParameter(returnValue, occurences_num);

    return TRI_OK;
}

Image:language_mapping_c_sharp.png

Add the following line to static PA_impl() (static constructor of PA_impl):

            _extFuncMap.Add("ef_find_and_replace", ef_find_and_replace);

Add the following code to the Helpers section of PA_impl:

        public static string TriParameterToString(ITriParameter src)
        {
            CD_impl cd = new CD_impl();
            ITciCDRequired req = StartHereCD.TciCDRequired;

            ITciCharstringValue v = (ITciCharstringValue)
                cd.Decode(new TriMessage(src.EncodedParameter), req.GetCharstring());

            return v.StringValue;
        }

        public static void AssignStringToTriParameter(ITriParameter dst, string src)
        {
            CD_impl cd = new CD_impl();
            ITciCDRequired req = StartHereCD.TciCDRequired;

            ITciCharstringValue v = (ITciCharstringValue) req.GetCharstring().NewInstance();
            v.StringValue = src;
            ITriMessage msg = cd.Encode(v);
            dst.EncodedParameter = msg.EncodedMessage;
        }

Add the following code to the External function implementations section of PA_impl:

        private static void ef_find_and_replace_helper(
            ref string str /* inout */,
            string search_substr /* in */,
            string new_substr /* in */,
            out long occurences_num /* out */)
        {
            int i, len;
            int substr_size = (int) search_substr.Length;

            if (substr_size != new_substr.Length)
            {
                StartHere.ReportError("Strings size mismatch in ef_find_and_replace().");
                occurences_num = -1;
                return;
            }

            if (substr_size == 0)
            {
                StartHere.ReportError("Search string size is zero in ef_find_and_replace().");
                occurences_num = -1;
                return;
            }

            occurences_num = 0;
            if (substr_size > str.Length)
            {
                // No way bigger string can be a substring of the smaller one:
                return; 
            }

            System.Text.StringBuilder sb = new System.Text.StringBuilder();

            len = str.Length - substr_size;
            for (i = 0; i <= len; i++)
            {
                if (str.Substring(i, substr_size) == search_substr)
                {
                    sb.Append(new_substr);
                    occurences_num++;
                    i += substr_size - 1;
                }
                else
                {
                    sb.Append(str[i]);
                }
            }

            if (i < str.Length) sb.Append(str.Substring(i, str.Length - i));

            str = sb.ToString();
        }

        public static TriStatus ef_find_and_replace(
            ITriFunctionId functionId,
            ITriParameterList parameterList,
            ITriParameter returnValue)
        {
            string str = TriParameterToString(parameterList[0]);
            string search_substr = TriParameterToString(parameterList[1]);
            string new_substr = TriParameterToString(parameterList[2]);
            long occurences_num;

            ef_find_and_replace_helper(
                ref str, search_substr, new_substr, out occurences_num);

            // It is safe to assume that inout str always changes:

            AssignStringToTriParameter(parameterList[0], str);

            // Perform mandatory assignment of occurences_num out parameter:

            AssignLongToTriParameter(parameterList[3], occurences_num);

            // Duplicate occurences_num in the return value:

            AssignLongToTriParameter(returnValue, occurences_num);

            return TriStatus.TriOk;
        }

Image:language_mapping_cpp.png

Add the following method signature to the private section of PA_impl in PA_impl.h:

    static TriStatus ef_find_and_replace(
        const TriFunctionId *functionId,
        TriParameterList *parameterList,
        TriParameter *returnValue);

Add the following line to PA_impl :: PA_impl() (constructor of PA_impl):

        _extFuncMap["ef_find_and_replace"] = &PA_impl::ef_find_and_replace;

Add the following code to the Helpers section of PA_impl.cpp:

static std::string triParameterToString(TriParameter* src)
{
    const unsigned char* src_par_data = src->getEncodedParameter();

    long str_len =
        (((unsigned long) src_par_data[0]) << 8) |
        (((unsigned long) src_par_data[1]) << 0);

    return std::string((const char *) (src_par_data + 2), str_len);
}

static void assignStringToTriParameter(TriParameter* dst, const std::string& src)
{
    long str_len = (long) src.size();

    unsigned char* dst_par_data = (unsigned char *) malloc(str_len + 2);
    long dst_par_bits = (str_len + 2) << 3;

    // String length, most significant byte first:

    dst_par_data[0] = (unsigned char) (str_len >> 8);
    dst_par_data[1] = (unsigned char) (str_len >> 0);

    // String data:

    memcpy(dst_par_data + 2, src.c_str(), str_len);

    dst->setEncodedParameter(dst_par_data, dst_par_bits);

    free(dst_par_data);
}

Add the following code to the External function implementations section of PA_impl.cpp:

static void ef_find_and_replace_helper(
    std::string& str /* inout */,
    const std::string& search_substr /* in */,
    const std::string& new_substr /* in */,
    long& occurences_num /* out */)
{
    int i, len;
    int substr_size = (int) search_substr.size();

    if (substr_size != new_substr.size())
    {
        StartHere::reportError("Strings size mismatch in ef_find_and_replace().");
        return;
    }

    if (!substr_size)
    {
        StartHere::reportError("Search string size is zero in ef_find_and_replace().");
        return;
    }

    occurences_num = 0;
    if (substr_size > (int) str.size())
    {
        // No way bigger string can be a substring of the smaller one:
        return; 
    }

    len = ((int) str.size()) - substr_size;
    for (i = 0; i <= len; i++)
    {
        if (str.substr(i, substr_size) == search_substr)
        {
            str.replace(i, substr_size, new_substr);
            occurences_num++;
            i += substr_size - 1;
        }
    }
}

TriStatus PA_impl :: ef_find_and_replace(const TriFunctionId* functionId,
    TriParameterList* parameterList, TriParameter* returnValue)
{
    TriParameterList* pars = parameterList;

    std::string str = triParameterToString(&pars->get(0));
    std::string search_substr = triParameterToString(&pars->get(1));
    std::string new_substr = triParameterToString(&pars->get(2));
    long occurences_num = -1;

    ef_find_and_replace_helper(
        str, search_substr, new_substr, occurences_num);

    // It is safe to assume that inout str always changes:

    assignStringToTriParameter(&pars->get(0), str);

    // Perform mandatory assignment of occurences_num out parameter:

    assignLongToTriParameter(&pars->get(3), occurences_num);

    // Duplicate occurences_num in the return value:

    assignLongToTriParameter(returnValue, occurences_num);

    return TRI_OK;
}

Image:language_mapping_java.png

Add the following statement to the static constructor of PA_impl:

        _extFuncMap.put("ef_find_and_replace", new ExtFuncPtr() {

            public TriStatus refer(TriFunctionId functionId,
                TriParameterList parameterList, TriParameter returnValue) {

                return PA_impl.ef_find_and_replace(functionId, parameterList, returnValue);
            }
        });

Add the following code to the Helpers section of PA_impl:

    public static String triParameterToString(TriParameter src) {

        CD_impl cd = new CD_impl();
        TciCDRequired req = StartHereCD.getTciCDRequired();

        CharstringValue v = (CharstringValue)
            cd.decode(TriFactory.createTriMessage(src.getEncodedParameter()), req.getCharstring());

        return ((com.openttcn.sdk.CharstringValue) v).getValue();
    }

    public static void assignStringToTriParameter(TriParameter dst, String src) {

        CD_impl cd = new CD_impl();
        TciCDRequired req = StartHereCD.getTciCDRequired();

        CharstringValue v = (CharstringValue) req.getCharstring().newInstance();
        ((com.openttcn.sdk.CharstringValue) v).setValue(src);
        
        TriMessage msg = cd.encode(v);

        dst.setEncodedParameter(msg.getEncodedMessage());
        dst.setNumberOfBits(msg.getNumberOfBits());
    }

Add the following code to the External function implementations section of PA_impl:

    private static void ef_find_and_replace_helper(
        org.omg.CORBA.StringHolder str /* inout */,
        String search_substr /* in */,
        String new_substr /* in */,
        org.omg.CORBA.LongHolder occurences_num /* out */) {

    	int i, len;
        int substr_size = search_substr.length();

        if (substr_size != new_substr.length()) {

            StartHere.reportError("Strings size mismatch in ef_find_and_replace().");
            occurences_num.value = -1;
            return;
        }

        if (substr_size == 0) {

            StartHere.reportError("Search string size is zero in ef_find_and_replace().");
            occurences_num.value = -1;
            return;
        }

        occurences_num.value = 0;
        if (substr_size > str.value.length()) {

            // No way bigger string can be a substring of the smaller one:
            return; 
        }

        StringBuilder sb = new StringBuilder();

        len = str.value.length() - substr_size;
        for (i = 0; i <= len; i++) {

            if (str.value.substring(i, substr_size + i).equals(search_substr)) {

                sb.append(new_substr);
                occurences_num.value++;
                i += substr_size - 1;
            }
            else {

                sb.append(str.value.charAt(i));
            }
        }

        if (i < str.value.length()) sb.append(str.value.substring(i, str.value.length()));

        str.value = sb.toString();
    }

    public static TriStatus ef_find_and_replace(
        TriFunctionId functionId,
        TriParameterList parameterList,
        TriParameter returnValue) {

    	org.omg.CORBA.StringHolder str = new org.omg.CORBA.StringHolder();
        str.value = triParameterToString(parameterList.get(0));
        String search_substr = triParameterToString(parameterList.get(1));
        String new_substr = triParameterToString(parameterList.get(2));
        org.omg.CORBA.LongHolder occurences_num = new org.omg.CORBA.LongHolder();

        ef_find_and_replace_helper(
            str, search_substr, new_substr, occurences_num);

        // It is safe to assume that inout str always changes:

        assignStringToTriParameter(parameterList.get(0), str.value);

        // Perform mandatory assignment of occurences_num out parameter:

        assignLongToTriParameter(parameterList.get(3), occurences_num.value);

        // Duplicate occurences_num in the return value:

        assignLongToTriParameter(returnValue, occurences_num.value);

        return TriFactory.createTriStatus(TriStatus.TRI_OK);
    }


The above implementation of the ef_find_and_replace() external function demonstrates handling of inout parameter str, and out parameter occurences_num.

The tests in the TC_test_ef_find_and_replace testcase should now pass, producing the following log output:

...
mtc : {15:00:18.223} : log("original s = In the town he stayed for the purpose.");
mtc : {15:00:18.281} : log("modified s = In thy town he stayed for thy purpose.");
mtc : {15:00:18.281} : log("num_of_occurences = 2");
mtc : {15:00:18.281} : log("result = 2");
...

Final versions of modified files

Here you can donwload final versions of modified source files obtained after applying all modifications made in this article:


Image:language_mapping_ansi_c.png

Image:language_mapping_c_sharp.png

Image:language_mapping_cpp.png

Image:language_mapping_java.png

Views
Personal tools