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 -trace syntax.asn

This command line tells the TCE-C 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 one C source file asntable.c and of two C include files asn and asntype.

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 all definitions generated by the ASN.1 compiler */
/* (includes AbstractSyntax and MyType declarations) */
#include "asn"
int main()
{ casnContext context; /* thread context */
asnMAXSINT rc; /* return code */
casnStream traceStream; /* file stream where to trace (optional) */
asnbyte * buffer = (asnbyte*)0; /* buffer for the encoded message */
asnMAXUINT encodingLength; /* number of encoded bytes */
/* Context initialization */
/* initialize the thread context and set the abstract syntax to use */
rc = casnInitContext(&context);
if (rc != asnSUCCESS) return rc;
casnSetAbstractSyntax(&context,&AbstractSyntax);
/* initialize a file stream where to trace */
rc = casnInitOutputTxtFileStream(&context,&traceStream,"traces.txt");
if (rc != asnSUCCESS) return rc;
/* activate some traces (optional) */
casnSetTraceStream(&context,traceStream);
casnSetEncodingTraceWhileEncoding(&context,asnTRUE);
casnSetValueTraceWhileDecoding(&context,asnTRUE);
/* Encoding process */
{ casnStream outputStream; /* memory stream where to encode into */
MyType myValue; /* the value to encode */
/* initialize an auto-sized output memory stream where to encode */
rc = casnInitOutputAutoMemoryStream(&context,&outputStream);
if (rc != asnSUCCESS) return rc;
/* build the value to encode */
casnInitValue(&context,&myValue,asnSizeof(MyType));
myValue.firstComponent = 10;
myValue.secondComponent = asnTRUE;
/* encode myValue into the output stream */
rc = casnPERencode(
&context, /* the thread context */
casnTMyType, /* the identifier of the type */
&myValue, /* the value to encode */
outputStream, /* where to encode */
&encodingLength); /* the number of encoded bytes */
if (rc != asnSUCCESS) return rc;
/* take the buffer from output stream */
buffer = casnDetachMemoryStream(&context,outputStream);
/* here buffer[0 to encodingLength-1] contains the encoded message */
/* cleaning */
asnDestroyStream(&context,outputStream);
casnDestroyValue(&context,casnTMyType,&myValue);
}
/* Decoding process */
{
casnStream inputStream; /* memory stream where to decode from */
MyType myValue; /* the decoded value */
asnMAXUINT decodingLength; /* number of decoded bytes */
/*here buffer[0 to encodingLength-1] contains the message to decode*/
/* initialize an input memory stream where to decode from */
rc = casnInitInputMemoryStream(
&context,
&inputStream,
&buffer[0],
encodingLength);
if (rc != asnSUCCESS) return rc;
/* decode into myValue from the input stream */
rc = casnPERdecode(
&context, /* the thread context */
casnTMyType, /* the identifier of the type */
&myValue, /* the value to fill */
inputStream, /* where to decode from */
&decodingLength); /* the number of decoded bytes */
if (rc != asnSUCCESS) return rc;
/* here myValue contains the decoded value */
printf("value of firstComponent is %ld\n",myValue.firstComponent);
printf("value of secondComponent is %s\n",
(myValue.secondComponent==asnFALSE ? "false" : "true"));

/* cleaning */
casnDestroyValue(&context,casnTMyType,&myValue);
casnDestroyStream(&context,inputStream);
}
/* Overall cleaning */
asnCtxFree(&context,&buffer[0]);
casnDestroyStream(&context,traceStream);
casnDestroyContext(&context);
}

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 that contains all the #include directives necessary to use the C API.

/* include all definitions generated by the ASN.1 compiler */
/* (includes AbstractSyntax and MyType declarations) */
#include "asn"

Some variables are necessary to perform the encoding and the decoding processes:

casnContext context; /* thread context */
asnMAXSINT rc; /* return code */
casnStream traceStream; /* file stream where to trace (optional) */
asnbyte * buffer = (asnbyte*)0; /* buffer for the encoded message */
asnMAXUINT encodingLength; /* number of encoded bytes */

The variable context is necessary in all functions of the C API. The variable traceStream can be used to get some traces about the encoding and decoding processes.

Context initialization

Each encoding and decoding function needs a context as a parameter. A context is a variable of type casnContext. 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. The context is initialized with these instructions:

rc = casnInitContext(&context);
if (rc != asnSUCCESS) return rc;
casnSetAbstractSyntax(&context,&AbstractSyntax);

A field in the context has to be set with a descriptor of the abstract syntax in which the types to encode or decode are defined. This descriptor is defined in the asntype file, usually with the name AbstractSyntax (the name of this descriptor can be changed with the -idpfx compiler option). For debugging purposes, it can be useful to trace the results of the encoding and decoding processes in some file. This requires the definition of a stream. Streams are the abstractions used by the TCE-C runtime as target for traces, as output for the encoding process and as input for the decoding process. The following instructions create an output text file stream to the file named traces.txt and check the return code:

rc = casnInitOutputTxtFileStream(&context,&traceStream,"traces.txt");
if (rc != asnSUCCESS) return rc;

The newly created stream has to be defined as the trace stream in the context:

casnSetTraceStream(&context,traceStream);

Then, the trace mechanism is activated at the encoding and at the decoding:

casnSetEncodingTraceWhileEncoding(&context,asnTRUE);
casnSetValueTraceWhileDecoding(&context,asnTRUE);

Encoding process

The encoding process converts a value defined on the ASNSDK TCE-C API into a message encoded in ASN.1 PER.

For the encoded message, it is necessary to define a stream:

casnStream outputStream; /* memory stream where to encode into */

For the value, it is necessary to define a variable of the appropriate C type. The file asntype generated by the ASN.1 compiler contains a C type definition for each ASN.1 type. The name of the C type is derived from the name of the ASN.1 type. The C type corresponding to the MyType ASN.1 type is simply MyType:

MyType myValue; /* the value to encode */

It is necessary to define the kind of stream used to contain the result of the encoding process. In this example, an automatic output memory stream is used. Such a stream hides a memory buffer, automatically allocated during the encoding process with the exact size of the encoded message. The following instructions initialize the stream:

rc = casnInitOutputAutoMemoryStream(&context,&outputStream);
if (rc != asnSUCCESS) return rc;

It is necessary to initialize myValue before encoding. The first step is always to initialize the value globally. This requires calling the casnInitvalue function giving the context, the address and the size of the value to initialize:

casnInitValue(&context,&myValue,asnSizeof(MyType));
myValue.firstComponent = 10;
myValue.secondComponent = asnTRUE;

To encode this value using the ASN.1 aligned packed encoding rules, the user application must just call the casnPERencode function with the appropriate parameters:

rc = casnPERencode(
&context, /* the thread context */
casnTMyType, /* the identifier of the type */
&myValue, /* the value to encode */
outputStream, /* where to encode */
&encodingLength); /* the number of encoded bytes */
if (rc != asnSUCCESS) return rc;

The second parameter is the identifier of the type to encode. It is a constant defined in the asntype file. The name of this identifier is the name of the C type prefixed by casnT, here casnTMyType. The fourth parameter is the stream where the PER encoding engine will write the ASN.1 encoded message corresponding to the value. The last parameter returns the length in bytes of the encoded message.

Depending on the settings in the context, this function can do automatically some additional work. Here it writes a trace of the encoded message in the trace stream (the traces.txt file).

If the encoding is successful, the output stream contains the encoded message. Here the output stream is an automatic memory stream. For such a stream, it is possible to detach the automatically allocated buffer from the stream:

buffer = casnDetachMemoryStream(&context,outputStream);

The user application accesses the encoded message using the buffer and length variables. The buffer will be released later using the asnCtxFree function.

The output stream and the value to encode are no longer necessary. For proper operation, it is required to release the memory used by these variables:

casnDestroyStream(&context,outputStream);
casnDestroyValue(&context,casnTMyType,&myValue);

It is a good practice to always do this cleaning, even if, as in this very simple example, the cleaning of myValue does nothing.

Decoding process

The decoding process converts a message encoded in ASN.1 into a value defined on the ASNSDK TCE-C API. To decode the message, it is necessary to define a stream that will contain the encoded message:

casnStream inputStream; /* memory stream where to decode from */

For storing the decoded value, it is necessary to define a variable of the appropriate C type. The file asntype generated by the ASN.1 compiler contains a C type definition for each ASN.1 type. The name of the C type is derived from the name of the ASN.1 type. The C type corresponding to the MyType ASN.1 type is simply MyType:

MyType myValue; /* the decoded value */

The message to decode is contained in a memory buffer but the ASN.1 runtime requires an input stream to decode from. It is necessary to build an input memory stream over the memory buffer:

rc = casnInitInputMemoryStream(&context,
&inputStream,
&buffer[0],
encodingLength);
if (rc != asnSUCCESS) return rc;

To decode the message using the ASN.1 aligned PER, the user application must just call the casnPERdecode function with the appropriate parameters:

rc = casnPERdecode( &context, /* the thread context */
casnTMyType, /* the identifier of the type */
&myValue, /* the value to fill */
inputStream, /* where to decode from */
&decodingLength); /* the number of decoded bytes */
if (rc != asnSUCCESS) return rc;

These parameters are almost identical to the parameters of the casnPERencode function. The second parameter is the identifier of the type to decode. It is a constant defined in the asntype file. The name of this identifier is the name of the C type prefixed by casnT, here casnTMyType. The third parameter is the address of the value where to decode into, here myValue of type MyType.

Depending on the settings in the context, this function can do automatically some additional work. Here it writes a trace of the decoded value in the trace stream (the traces.txt file).

If the decoding is successful, the myValue variable contains the ASN.1 message in the format of the C API. The user application can access the fields of this variable in a way depending on the ASN.1 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",myValue.firstComponent);
printf("value of secondComponent is %s\n",
(myValue.secondComponent==asnFALSE ? "false" : "true"));

The input stream and the decoded value are no longer necessary. For proper operation, it is necessary to release the memory used by these variables:

casnDestroyValue(&context,casnTMyType,&myValue);
casnDestroyStream(&context,inputStream);

Overall cleaning

The buffer obtained from the encoding process, and used as input for the decoding process, is no longer necessary. It is released with the asnCtxFree function:

asnCtxFree(&context,&buffer[0]);

For proper operation, it is also necessary to release the memory used by the trace stream and the context:

casnDestroyStream(&context,traceStream);
casnDestroyContext(&context);

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>