ASNSDK TCE-C++
Application Example

The following application example describes how to use the ASNSDK TCE-C++ tools to encode and decode ASN.1 data. It presents the main concepts and features of the runtime.

Based on a very simple ASN.1 description, this application example encodes a ASN.1 value in PER encoding rules, then decodes it. Tracing facilities are activated during the encoding and the decoding.

Compiling the ASN.1 abstract syntax

Let's consider the following abstract syntax, written in a syntax.asn file:

MyModule DEFINITIONS AUTOMATIC TAGS ::=
BEGIN
MyType ::=SEQUENCE {
firstComponent INTEGER,
secondComponent BOOLEAN
}
END

The first step, to build a user application, which can encode or decode ASN.1 values of the MyType ASN.1 type, is to compile the above abstract syntax. To do so, the TCE-C++ ASN.1 compiler is called with the command line:
asnc -c++ -per -hsfx h -trace syntax.asn

This command line tells the ASN.1 compiler to generate a C++ API corresponding to the abstract syntax contained in the syntax.asn file, using the Aligned PER encoding rules.

The generated C++ API is composed of the C++ source files:
asnctor.cc, asndtor.cc, asntable.cc
and of the C++ include files:
asn.h, asntype.h

Writing the user application

The second step is to write the application example in C++ source code. The application example is fully detailed in the remaining of this section.

/* Declarations */
#include "asn.h"
int main()
{
/* Context initialization */

// build thread context
asnContext context;

// activate Trace Management and create a traceStream (optional)
context.setEncodingTraceWhileEncoding(asnTRUE);
context.setValueTraceWhileDecoding(asnTRUE);
asnFileStream traceStream("traces.txt", // file name
asnFSTREAM_TXT_WRITE );// write text stream
context.setTraceStream(&traceStream);

/* Encoding process */

// create an empty encoding stream
asnMemoryStream encoding(asnFSTREAM_WRITE); // write binary stream

// instanciate the ASN.1 value to encode
MyType myValue;
myValue.firstComponent = 10;
myValue.secondComponent = asnTRUE;

// encode myValue into the encoding stream
asnMAXUINT len = myValue.PERencode(&context, &encoding);

// get the buffer from encoding stream
asnbytep buffer = encoding.detach();

// use the buffer to send the message
/* Decoding process */

// a buffer contains the message to decode

// build a decoding stream over the buffer
asnMemoryStream decoding(buffer, // memory area
len, // area length
asnFSTREAM_READ); // read binary stream

// decode the decoding stream into myDecodedValue
MyType myDecodedValue;
myDecodedValue.PERdecode(&context, &decoding);

/*here myDecodedValue contains the decoded value */
printf("value of firstComponent is %ld\n",
(long)myDecodedValue.firstComponent);
printf("value of secondComponent is %s\n",
(myDecodedValue.secondComponent==asnFALSE ? "false":"true"));
/* Memory cleanup */

// free the buffer
asnfree(buffer);
return 0;
}

Declarations

To use the specific C++ API, the user application must first include the asn.h file generated by the TCE-C++ ASN.1 Compiler :

#include "asn.h"

This file contains all the #include directives necessary to use the specific C++ API.

Context initialization

Each encoding and decoding method needs a "context" as a parameter. A context is an instance of the C++ class asnContext. In a multi-threaded environment, each thread must use a different context. Furthermore, a context contains settings, which control the way encoding and decoding are performed. In the above example, the context controls the way traces are generated while encoding and decoding. The context is created by the instruction:

asnContext context;

It's highly recommended to create the context before the first memory allocation and destroy it after the last memory release to be able to use all the memory management functionalities offered by the ASNSDK runtime.

The trace of the encoding result is activated by the instruction:

context.setEncodingTraceWhileEncoding( asnTRUE );

The trace of the decoded value is activated by the instruction:

context.setValueTraceWhileDecoding( asnTRUE );

The trace output is a "write text stream". A stream is an instance of one of the following C++ classes: asnMemoryStream or asnFileStream. A stream is a data encapsulation giving a unidirectional access (read or write) to a contiguous memory buffer, or to a file.

A file text stream is build by the instruction:

asnFileStream traceStream( "traces.txt", // file name
asnFSTREAM_TXT_WRITE );// write text stream

This file stream is used as trace output by the instruction:

context.setTraceStream( &traceStream );

Encoding process

"Write streams" are also used as encoding output. The user application example uses an automatic memory stream, which encapsulates the access to a memory area allocated during the encoding. The following instruction builds the memory stream:

asnMemoryStream encoding(asnFSTREAM_WRITE ); // write binary stream

The ASN.1 compiler has generated the MyType C++ class.

To encode a value of the ASN.1 type: MyType, the user application must first create an instance of the MyType C++ class and instantiate the attributes:

MyType myValue;
myValue.firstComponent = 10;
myValue.secondComponent = asnTRUE;

To encode this value, the user application must just call the PERencode method of the MyType C++ class. The thread context and the output stream are used as parameters:

asnMAXUINT len = myValue.PERencode( &context, &encoding );

This method puts the encoding result in a newly created memory area and returns the result length (in bytes). As required by the context settings, the PERencode method also generates the trace of the encoding in the traces.txt file. The user can get the encoded data by detaching the created memory area from the encoding stream. This memory area will not anymore be managed by the encoding stream.

asnbytep buffer = encoding.detach();

Decoding process

"Read streams" are used as decoding input. The user application example uses a memory stream which encapsulates the memory area created during the encoding.

The following instruction builds the memory stream:

asnMemoryStream decoding(buffer, // memory area
len, // area length
asnFSTREAM_READ ); // read binary stream

To decode the message, the user application must create a new instance of the MyType C++ class:

MyType myDecodedValue;

The thread context and the input stream are used as PERdecode method parameters:

myDecodedValue.PERdecode( &context, &decoding );

This method puts the decoding result in the instance of the MyType C++ class. As required by the context settings, it also generates the XML trace of the decoded value in the traces.txt file.

Note that it is not possible to use the same instance to decode several times without destroying the instance between each decoding (the decoder assumes that the instance is not initialized ; thus, it could create memory leaks if the instance is initialized).

If the decoding is successful, the instance of the MyType C++ class (myDecodedValue) contains the PER message. The user application can access the fields of this object in a way depending on the type of each field. Here, the user application simply displays the value of the two fields on the standard output:

printf("value of firstComponent is %ld\n",
(long)myDecodedValue.firstComponent);
printf("value of secondComponent is %s\n",
(myDecodedValue.secondComponent==asnFALSE ? "false" : "true"));

Memory cleanup

To avoid memory leak, the previously allocated C++ instances must be deleted. Here they will be automatically deleted at the closing bracket. The memory buffer detached by the user now belongs to him, he also has to delete it :

asnfree(buffer);

Running the user application

Running the application example generates the traces.txt file, which contains in sequence the XML trace of the encoding, then the XML trace of the decoded value:

<encoding>
010A80
</encoding>
<MyType>
<firstComponent>10</firstComponent>
<secondComponent>
<true/>
</secondComponent>
</MyType>