ASNSDK TCE-Java
Application Example

The following application example describes how to use the ASNSDK TCE-Java 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 BER 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 stored in a src/spe_start.asn file:

MyModule DEFINITIONS AUTOMATIC TAGS ::= BEGIN MyType ::= SEQUENCE { firstComponent INTEGER DEFAULT defaultValue, secondComponent BOOLEAN } defaultValue INTEGER ::= 1 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-Java ASN.1 compiler is called with the following command line:

asnc -java -pkgpfx my.company.asnccode -o src/my/company/asnccode -ber -trace src/spe_start.asn

This command line tells the TCE-Java ASN.1 compiler to generate:

  • a Java API in the package my.company.asnccode,
  • stored in the directory src/my/company/asnccode,
  • with the feature "encoding and decoding in ASN.1 BER",
  • with the feature "value trace",
  • using the ASN.1 abstract syntax contained in the src/spe_start.asn file.

The Java API is generated in the directory my/company/asnccode.

After a successful ASN.1 compilation, the src/my/company/asnccode directory contains:

  • a binary file: asntable.dat,
  • three Java source files: RuntimeConfiguration.java, Values.java and ValueFactory.java,
  • an HTML file for the Javadoc tool : package.html
  • the subdirectory MyModule with the following files:
    • a Java source file MyType.java,
    • an HTML file for the Javadoc tool: package.html.

The five files in the src/my/company/asnccode directory are always generated by the ASN.1 compiler.

The others files and subdirectories depend on the compiled ASN.1 abstract syntax:

  • each ASN.1 module is mapped to a Java package,
  • each type assignment is mapped to a Java class.

Writing the user application

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

/* Declarations */
package my.company.mycode;
// import the Java API base classes
import fr.marben.asnsdk.japi.*;
import fr.marben.asnsdk.japi.spe.*;
// import the classes generated for the abstract syntax
import my.company.asnccode.MyModule.*;
import my.company.asnccode.*;
/**
* Encoding and decoding of the "getting started" example described in the
* runtime programmer's guide with use of the ASNSDK TCE-JAVA API.
*/
public class Spe_start
{
/**
* Java entry point.
*/
public static void main(String[] args)
{
/* Parameters */
if (args.length != 3)
{
System.out.println("usage: Spe_start asntable.dat encoding log");
return;
}
try
{
/* Initialization */
// instanciate a writer to the log file
java.io.PrintWriter logWriter = new java.io.PrintWriter(
new java.io.OutputStreamWriter(
new java.io.FileOutputStream(args[2]),
"UTF-8"),
true);
// configure the runtime for the optional features
RuntimeConfiguration.initialize();
// instanciate the thread context
Context context = new Context();
// load and define the compiled abstract syntax
context.setAbstractSyntax(
Loader.load(context, new java.io.FileInputStream(args[0])));
// define the value factory
context.setValueFactory(new my.company.asnccode.ValueFactory());
// configuration of the traces
context.setTraceWriter(logWriter);
context.setEncodingTraceWhileEncoding(true);
context.setEncodingTraceWhileDecoding(true);
context.setValueTraceWhileEncoding(true);
context.setValueTraceWhileDecoding(true);
context.setIndentedXerValueTrace(true);
/* Encoding process */
logWriter.println("\n*** BER ENCODING BEGIN ***\n");
try
{
// instanciate an empty value of type MyType
MyType valueToEncode = new MyType();
// give the default value to the field firstComponent
valueToEncode.firstComponent =
new IntIntegerValue(Values.MyModule__defaultValue);
// give the value true to the field secondComponent
valueToEncode.secondComponent = new BooleanValue(true);
// instanciate an output stream to the encoding file
java.io.OutputStream outputStream =
new java.io.BufferedOutputStream(
new java.io.FileOutputStream(args[1]));
// encode into the output stream
valueToEncode.berEncode(
context,
outputStream);
// close the output stream
outputStream.close();
logWriter.println("*** BER ENCODING OK ***");
}
catch (Exception e)
{
logWriter.println("\n*** BER ENCODING KO ***");
e.printStackTrace(logWriter);
}
/* Decoding process */
logWriter.println("\n*** BER DECODING BEGIN ***\n");
try
{
// instanciate an input stream from the encoding file
java.io.InputStream inputStream =
new java.io.BufferedInputStream(
new java.io.FileInputStream(args[1]));
// instanciate an empty value of type MyType
MyType decodedValue = new MyType();
// decode the value from an input stream
decodedValue.berDecode(
context,
inputStream);
// display the value of the first component (optional)
if (decodedValue.firstComponent != null)
{
logWriter.println("firstComponent="+
decodedValue.firstComponent.getValue());
}
else
{
logWriter.println("firstComponent absent");
}
// display the value of the second component (mandatory)
logWriter.println("secondComponent="+
decodedValue.secondComponent.getValue());
logWriter.println("*** BER DECODING OK ***");
}
catch (Exception e)
{
logWriter.println("\n*** BER DECODING KO ***");
e.printStackTrace(logWriter);
}
}
catch (Exception e)
{
System.out.println("Init failure");
e.printStackTrace();
}
} // end of main method
}

Declarations

In this example, the user code is contained in the main method of a my.company.mycode.Spe_start class:

package my.company.mycode;
// imports
public class Spe_start
{
public static void main(String[] args)
{
// user code
}
}

To avoid the use of long package prefixes in the source code, all classes of four packages are imported:

// import the Java API base classes
import fr.marben.asnsdk.japi.*;
import fr.marben.asnsdk.japi.spe.*;
// import the classes generated for the abstract syntax
import my.company.MyModule.*;
import my.company.asnccode.*;

The two first instructions import the base classes of the ASNSDK TCE-Java runtime. The classes imported from these packages are Context, Loader, IntIntegerValue and BooleanValue. The next instruction imports the Java classes generated by the ASN.1 compiler for the types of the ASN.1 MyModule module. The last instruction imports the Java classes generated by the ASN.1 compiler.

Parameters

The entry point of the sample application requires three parameters:

if (args.length != 3)
{
System.out.println("usage: Spe_start asntable.dat encoding log");
return;
}

The first parameter is the path of the asntable.dat file generated by the ASN.1 compiler. This file contains the ASN.1 abstract syntax in a compiled format. The content of this file is necessary to the operation of the ASNSDK TCE-Java runtime.

The second parameter is the path of the file where the encoded message produced in this example will be stored.

The third parameter is the path of the file where the traces produced in this example will be stored. These traces are optional features of the ASNSDK TCE-Java runtime that are helpful for the debugging of the user application.

Note that the ASNSDK TCE-JAVA runtime is not restricted to the use of files. Any java.io.Stream could be used.

Initialization

The initializations are made in four steps:

  • Instantiation of a java.io.Writer for the traces,
  • Static initialization of the ASNSDK TCE-Java runtime,
  • Initialization of the thread context,
  • Configuration of the traces.

This initialization code is enclosed in a try catch block to catch the exceptions that can be thrown.

  1. Instantiation of a java.io.Writer for the traces

    // instantiate a writer to the log file
    java.io.PrintWriter logWriter = new java.io.PrintWriter(
    new java.io.OutputStreamWriter(
    new java.io.FileOutputStream(args[2]),
    "UTF-8"),
    true);

    Here an auto-flushed java.io.PrintWriter with an UTF-8 encoding is created, but any java.io.Writer could be created.

  2. Static initialization of the runtime

    The ASNSDK TCE-Java runtime has several optional features called services. These services have to be activated before their first use. The initialize method of the class RuntimeConfiguration generated by the ASN.1 compiler contains all the necessary stuff:

    // configure the runtime for the optional features
    RuntimeConfiguration.initialize();

    This method needs to be called only one time per Java Virtual Machine.

  3. Initialization of the thread context

    All services provided by the ASNSDK TCE-Java runtime use a thread context. This thread context contains all the dynamic settings of the ASNSDK TCE-Java runtime. These settings control the way encoding and decoding are performed. Note that in a multi-threaded environment, each thread must use a different thread context.

    // instantiate the thread context
    Context context = new Context();

    Two settings are mandatory:

    • The setting of the compiled abstract syntax,
    • The setting of the value factory.

    The compiled abstract syntax has been generated by the ASN.1 compiler in the asntable.dat file. The content of this file is necessary to the ASNSDK TCE-Java runtime to perform its work. The Loader.load method loads the compiled abstract syntax into memory.

    Note that the content of the asntable.dat file can be stored anywhere. The ASNSDK TCE-Java runtime requires only that it can be obtained using a java.io.InputStream. Note also that the compiled abstract syntax can be shared among several threads. A compiled abstract syntax is set into the thread context using the setAbstractSyntax method:

    // load and define the compiled abstract syntax
    context.setAbstractSyntax(
    Loader.load(context, new java.io.FileInputStream(args[0])));

    The value factory has been generated by the ASN.1 compiler in the ValueFactory.java file. The value factory is necessary to the ASNSDK TCE-Java runtime to instantiate the decoded values. It is set into the thread context using the setValueFactory method:

    // define the value factory
    context.setValueFactory(new my.company.asnccode.ValueFactory());

  4. Configuration of the traces

    For debugging purposes, it can be useful to perform some traces.

    // configuration of the traces
    context.setTraceWriter(logWriter);

    The ASNSDK TCE-JAVA runtime provides several trace capabilities. This example requests the trace of the value before encoding and after encoding, and also the trace of the value before decoding and after decoding. The values in decoded format are indented.

    context.setEncodingTraceWhileEncoding(true);
    context.setEncodingTraceWhileDecoding(true);
    context.setValueTraceWhileEncoding(true);
    context.setValueTraceWhileDecoding(true);
    context.setIndentedXerValueTrace(true);

Encoding process

To encode a value, it is necessary to:

  • instantiate the value on the ASNSDK TCE-Java API,
  • request the encoding of the value into a stream.

  1. Value instantiation

    The ASN.1 modules and types have been mapped by the ASN.1 compiler to Java packages and classes. These classes always have a default constructor, setter and getter methods. Simple types also have an initialization constructor.

    It is recommended to generate the Java documentation (Javadoc) of the classes generated by the ASN.1 compiler before starting to write the user application Java code and to use an HTML browser to walk through this documentation while writing Java code using the ASNSDK TCE-Java API.

    In this example, we want to encode a value of the ASN.1 type MyType defined in the ASN.1 module MyModule. The corresponding Java class is my.company.asnccode.MyType. The my.company.asnccode package prefix comes from the -pkgpfx my.company.asnccode option used during the ASN.1 compilation.

    The default constructor of this class instantiates a value with all fields absent:

    // instantiate an empty value of type MyType
    MyType valueToEncode = new MyType();

    The MyType type is a SEQUENCE type with two fields:

    • A optional field firstComponent of type INTEGER with a default value of defaultValue,
    • A mandatory field secondComponent of type BOOLEAN.

    The ASN.1 BOOLEAN type is always mapped to the BooleanValue Java class in the package fr.marben.asnsdk.japi.spe. To give the value TRUE to the field secondComponent, simply write:

    // give the value true to the field secondComponent
    valueToEncode.secondComponent = new BooleanValue(true);

    The ASN.1 INTEGER type is mapped here to the IntIntegerValue Java class in the package fr.marben.asnsdk.japi.spe. To give the value defaultValue to the field firstComponent, simply write:

    // give the default value to the field firstComponent
    valueToEncode.firstComponent =
    new IntIntegerValue(Values.MyModule__defaultValue);

    The value defaultValue has been generated by the ASN.1 compiler in the my.company.asnccode.Values class with the name MyModule_defaultValue. This single class contains all generated values. Note that it is necessary to provide a value for all mandatory fields. Optional fields can remain absent (the value of the field remains null in this case).

  2. Encoding of the Value

    It is necessary to instantiate the java.io.OutputStream where to store the encoded value:

    // instantiate an output stream to the encoding file
    java.io.OutputStream outputStream =
    new java.io.BufferedOutputStream(
    new java.io.FileOutputStream(args[1]));

    In this example, the encoded value is stored in the file whose path is given by the second parameter of the program. Note the use of a java.io.BufferedOutputStream to improve the I/O performances. A simple call to the berEncode method of the value allows encoding the value in BER:

    // encode into the output stream
    valueToEncode.berEncode(
    context,
    outputStream);

    This simple call does not only encode the value in BER but also performs the traces as requested by the settings in the thread context:

    • The value is traced into the traceWriter,
    • The value is encoded into the output stream,
    • The encoding is traced into the traceWriter.

    To finish, the output stream is closed:

    // close the output stream
    outputStream.close();

Encoding Results

The value in BER format is available in the file corresponding to the output stream. A hexadecimal dump of this file is listed below:

30 06 80 01 01 81 01 FF

The file corresponding to the trace writer contains the trace of the value before the encoding process (in XML format) and after the encoding process (in hexadecimal format and in ASCII format). See the content of this file below:

<MyType>
<firstComponent>1</firstComponent>
<secondComponent>
<true/>
</secondComponent>
</MyType>
<encoding>
30068001 018101FF <!--0.......-->
</encoding>

Decoding process

It is necessary to instantiate the java.io.InputStream where to read the encoded value from:

// instanciate an input stream from the encoding file
java.io.InputStream inputStream =
new java.io.BufferedInputStream(
new java.io.FileInputStream(args[1]));

In this example, the encoded value is stored in the file whose path is given by the second parameter of the program. Note the use of a java.io.BufferedInputStream to improve the I/O performances. Before being able to decode, it is necessary to instantiate an object of the class to decode. The default constructor of this class instantiates a value with all fields absent:

// instantiate an empty value of type MyType
MyType decodedValue = new MyType();

A simple call to the berDecode method of the value allows decoding the value using BER:

// decode the value from an input stream
decodedValue.berDecode
context,
inputStream);

This simple call does not only decode the value in BER but also performs the traces as requested by the settings in the thread context:

  • the value is decoded from the input stream,
  • the encoding is traced into the traceWriter,
  • the value is traced into the traceWriter.

In this example, the decoded values are simply written in the trace file.

The value of the field secondComponent is simply obtained through the getValue method of the secondComponent attribute of the decoded value. The boolean value returned is written using the logWriter:

// display the value of the second component (mandatory)
logWriter.println("secondComponent="+
decodedValue.secondComponent.getValue());

The field firstComponent is optional. If it is absent, the value of the firstComponent attribute of the decoded value is null. If the field is present, the getValue method allows getting its value. The int value returned is written using the logWriter:

// display the value of the first component (optional)
if (decodedValue.firstComponent != null)
{
logWriter.println("firstComponent="+
decodedValue.firstComponent.getValue());
}
else
{
logWriter.println("firstComponent absent");
}

Decoding Results

The file corresponding to the trace writer contains the trace of the value before the decoding process (in hexadecimal format and in ASCII format) and after the decoding process (in XML format). See the content of this file below:

<encoding>
30068001 018101FF <!--0.......-->
</encoding>
<MyType>
<firstComponent>1</firstComponent>
<secondComponent>
<true/>
</secondComponent>
</MyType>