Acv 77 Java Users Guide
Acv 77 Java Users Guide
Version 7.7
Objective Systems, Inc.
April 2023
ASN1C: ASN.1 Compiler User's Guide for Java
Copyright © 1997-2023 Objective Systems, Inc.
License. The software described in this document is furnished under a license agreement and may be used only in
accordance with the terms of this agreement. This document may be distributed in any form, electronic or otherwise,
provided that it is distributed in its entirety with the copyright and this notice intact.
Author's Contact Information. Comments, suggestions, and inquiries regarding ASN1C or this document may
be sent by electronic mail to <[email protected]>.
Table of Contents
1. Overview of ASN1C for Java ........................................................................................................... 1
2. Using the Compiler ......................................................................................................................... 2
Running ASN1C ........................................................................................................................ 2
ASN1C Java Command Line Options ............................................................................................ 2
Compiler Configuration File ......................................................................................................... 9
ASN.1 Standard Revisions ......................................................................................................... 18
Compiler Error Reporting ........................................................................................................... 19
3. ASN1C GUI Users Guide ............................................................................................................... 20
Quick Start .............................................................................................................................. 20
Activating a License Key ................................................................................................... 20
Creating a New Project ...................................................................................................... 22
Creating a Project ..................................................................................................................... 24
Creating a New Project ...................................................................................................... 24
Editing a Project ............................................................................................................... 25
Opening a Project ............................................................................................................. 25
Saving a Project ............................................................................................................... 25
Editing Schemas ....................................................................................................................... 25
Creating a New Schema File .............................................................................................. 26
Editing a Schema File ....................................................................................................... 26
Deleting a Schema File ...................................................................................................... 27
Compiling ............................................................................................................................... 27
Interface .................................................................................................................................. 27
Editor ............................................................................................................................. 28
Project Window ................................................................................................................ 29
ASN.1 Tree Window ......................................................................................................... 32
Error Log Window ............................................................................................................ 32
Project Settings ................................................................................................................ 33
4. Generated Java Source Code Overview ............................................................................................. 46
General Form of a Generated Java Source File ............................................................................... 46
Package Specification ................................................................................................................ 47
Class Declaration ...................................................................................................................... 47
Tag Constant ............................................................................................................................ 48
Public Member Variables ........................................................................................................... 48
Constructors ............................................................................................................................. 48
Decode Method ........................................................................................................................ 49
Encode Method ........................................................................................................................ 50
Other Methods ......................................................................................................................... 51
Inner Classes ............................................................................................................................ 51
Error Handling ......................................................................................................................... 52
5. ASN.1 Type to Java Class Mappings ................................................................................................ 53
BOOLEAN .............................................................................................................................. 53
INTEGER ............................................................................................................................... 53
Large Integer Support ........................................................................................................ 54
BIT STRING ........................................................................................................................... 55
Named Bits ..................................................................................................................... 55
OCTET STRING ...................................................................................................................... 56
TBCD and BCD Strings .................................................................................................... 57
PLMNidentity .................................................................................................................. 57
Character String Types .............................................................................................................. 57
ENUMERATED ....................................................................................................................... 58
NULL ..................................................................................................................................... 59
iii
ASN1C
iv
ASN1C
v
ASN1C
vi
Chapter 1. Overview of ASN1C for Java
The ASN1C code generation tool translates an Abstract Syntax Notation 1 (ASN.1) or XML Schema Definitions (XSD)
source file into computer language source files that allow typed data to be encoded/decoded. This release of ASN1C
includes options to generate code in the following languages: C, C++, C#, Java, Python, or Go. This manual discusses
the Java code generation capabilities. The following manuals discuss the other language code generation capabilities:
Each module or namespace that is encountered in an ASN.1 or XSD source file results in the generation of a series
of Java source files. A separate Java file is generated for each production (type or global element) in the source file.
Additional files are generated for compiler-generated productions and to hold value specification constants.
There is also a set of classes that form the run-time component of the Java package. These classes provide the primitive
component building blocks that are assembled by the compiler to encode/decode complex structures. They also provide
support for managing message buffers that hold the encoded message components.
ASN1C works with the version of ASN.1 specified in ITU-T international standards X.680 through X.683. It generates
code for encoding/decoding data in accordance with the following encoding rules:
• Basic Encoding Rules (BER), Distinguished Encoding Rules (DER), and Canonical Encoding Rules (CER) as pub-
lished in the ITU-T X.690 and ISO/IEC 8825-1 standards.
• Packed Encoding Rules (PER) as published in the ITU-T X.691 and ISO/IEC 8825-2 standards. Both aligned and
unaligned variants are supported.
• XML Encoding Rules (XER) as published in the ITU-T X.693 and ISO/IEC 8825-3 standards.
• Octet Encoding Rules (OER) as published in the ITU-T X.696 and ISO/IEC 8825-7:2014 standards.
• JSON Encoding Rules (JER) as published in the ITU-T X.697 and ISO/IEC 8825-8:2018 standards.
The compiler is capable of parsing all ASN.1 syntax as defined in the standards. It is capable of parsing advanced
syntax including Information Object Specifications as defined in the ITU-T X.681 standard as well as Parameterized
Types as defined in ITU-T X.683.
Note that XER support does not include support for the EXTENDED-XER syntax. This is accomplished through direct
compilation of XSD files. An internal translation of XSD to ASN.1 based on the rules in the X.694 standard is done
within the compiler and the resulting ASN.1 syntax is compiled into Java classes.
This release of the compiler contains a special compiler option (-asnstd x208) that is backward compatible with dep-
recated features from the older X.208 and X.209 standards. These include the ANY data type and unnamed fields in
SEQUENCE, SET, and CHOICE types. This version can also parse type syntax from common macro definitions such
as ROSE and SNMP.
1
Chapter 2. Using the Compiler
Running ASN1C
The ASN1C compiler distribution contains command-line compiler executables as well as a graphical user interface
(GUI) wizard that can aid in the specification of compiler options. Please refer to the ASN1C C/C++ Compiler User's
Manual for instructions on how to run the compiler. The remaining sections describe options and configuration items
specific to the Java version.
2
Using the Compiler
3
Using the Compiler
4
Using the Compiler
5
Using the Compiler
6
Using the Compiler
7
Using the Compiler
8
Using the Compiler
A simple form of the Extended Markup Language (XML) is used to format items in the file. This language was chosen
because it is fairly well known and provides a natural interface for representing hierarchical data such as the structure
of ASN.1 modules and productions. The use of an external configuration file was chosen over embedding directives
within the ASN.1 source itself due to the fact that ASN.1 source versions tend to change frequently. An external
configuration file can be reused with a new version of an ASN.1 module, but internal directives would have to be
reapplied to the new version of the ASN.1 code.
At the outer level of the markup is the <asn1config> </asn1config> tag pair. Within this tag pair, the specification of
global items and modules can be made. Global items are applied to all items in all modules. An example would be the
<storage> qualifier. A storage class such as dynamic can be specified and applied to all productions in all modules.
This will cause dynamic storage (pointers) to be used to any embedded structures within all of the generated code to
reduce memory consumption demands.
The specification of a module is done using the <module></module> tag pair. This tag pair can only be nested within
the top-level <asn1config> section. The module is identified by using the required <name></name> tag pair or by
specifying the name as an attribute (for example, <module name="MyModule">). Other attributes specified within the
<module> section apply only to that module and not to other modules specified within the specification. A complete
list of all module attributes is provided in the table at the end of this section.
The specification of an individual production is done using the <production></production> tag pair. This tag pair can
only be nested within a <module> section. The production is identified by using the required <name></name> tag pair
or by specifying the name as an attribute (for example, <production name="MyProd">). Other attributes within the
production section apply only to the referenced production and nothing else. A complete list of attributes that can be
applied to individual productions is provided in the table at the end of this section.
When an attribute is specified in more than one section, the most specific application is always used. For example,
assume a <typePrefix> qualifier is used within a module specification to specify a prefix for all generated types in
the module and another one is used to specify a prefix for a single production. The production with the type prefix
will be generated with the type prefix assigned to it and all other generated types will contain the type prefix assigned
at the module level.
Values in the different sections can be specified in one of the following ways:
1. Using the <name>value</name> form. This assigns the given value to the given name. For example, the following
would be used to specify the name of the "H323-MESSAGES" module in a module section:
<name>H323-MESSAGES</name>
2. Flag variables that turn some attribute on or off would be specified using a single <name/> entry. For example, to
specify a given production is a PDU, the following would be specified in a production section:
<isPDU/>
9
Using the Compiler
3. An attribute list can be associated with some items. This is normally used as a shorthand form for specifying lists
of names. For example, to specify a list of type names to be included in the generated code for a particular module,
the following would be used:
<include types="TypeName1,TypeName2,TypeName3"/>
<asn1config><storage>dynamic</storage></asn1config>
This specification indicates dynamic storage should be used in all places where its use would result in significant
memory usage savings within all modules in the specified source file.
<asn1config>
<module>
<name>H323-MESSAGES</name>
<sourceFile>h225.asn</sourceFile>
<typePrefix>H225</typePrefix>
</module>
...
</asn1config>
This specification applies to module 'H323-MESSAGES' in the source file being processed. For IMPORT statements
involving this module, it indicates that the source file 'h225.asn' should be searched for specifications. It also indicates
that when C or C++ types are generated, they should be prefixed with the 'H225'. This can help prevent name clashes
if one or more modules are involved and they contain productions with common names.
The following tables specify the list of attributes that can be applied at all of the different levels: global, module, and
individual production:
Global Level
There are no attributes that are specific to Java that can be specified at the global level.
Module Level
These attributes can be applied at the module level by including them within a <module> section:
10
Using the Compiler
Production Level
These attributes can be applied at the production level by including them within a <production> section:
11
Using the Compiler
12
Using the Compiler
Element Level
These attributes can be applied at the element level by including them within a <element> section:
13
Using the Compiler
14
Using the Compiler
15
Using the Compiler
16
Using the Compiler
17
Using the Compiler
-asnstd x680
• This is the default option.
• This option indicates ASN1C should follow the latest revision of the X.680 and X.690 series that is supported by
the tool.
-asnstd x680-2021
• Follows the 2021 revision of the X.680/X.690 series.
• PER encoding of BIT STRINGs with contents constraints: The BIT STRING is padded to a multiple of 8 bits for
aligned PER only.
• JER encoding of BIT STRING and OCTET STRING with contents constraints: an encoder's option using a JSON
object with a 'containing' key was added in this revision. The encoder will use this encoder's option and the decoder
will decode values encoded using either option.
-asnstd x680-2015
• Follows the 2015 revision of the X.680/X.690 series.
• PER encoding of BIT STRINGs with contents constraints: There is no padding of the BIT STRING value.
-asnstd x680-2008
• Follows the 2008 revision of the X.680/X.690 series.
• PER encoding of BIT STRINGs with contents constraints: The BIT STRING is padded to a multiple of 8 bits for
both aligned and unaligned PER.
-asnstd mixed
• Used when source files contain modules with both X.208- and X.680-based syntax.
-asnstd x208
• This option supports the deprecated X.208 and X.209 standards.
• Allows unnamed fields in SEQUENCE, SET, and CHOICE constructs (not allowed by X.680).
• Allows use of ROSE OPERATION and ERROR macros and SNMP OBJECTTYPE macros (macros are not a
feature of X.680).
18
Using the Compiler
Syntax errors are errors in the ASN.1 source specification itself. These occur when the rules specified in the ASN.1
grammar are not followed. ASN1C will flag these types of errors with the error message 'Syntax Error' and abort
compilation on the source file. The offending line number will be provided. The user can re-run the compilation with
the '-l' flag specified to see the lines listed as they are parsed. This can be quite helpful in tracking down a syntax error.
• Invalid case on identifiers: module name must begin with an uppercase letter, productions (types) must begin with an
uppercase letter, and element names within constructors (SEQUENCE, SET, CHOICE) must begin with lowercase
letters.
• Elements within constructors not properly delimited with commas: either a comma is omitted at the end of an
element declaration, or an extra comma is added at the end of an element declaration before the closing brace.
• Invalid special characters: only letters, numbers, and the hyphen (-) character are allowed. Programmers tend to
like to use the underscore character (_) in identifiers. This is not allowed in ASN.1. Conversely, C or C# does not
allow hyphens in identifiers. To get around this problem, ASN1C converts all hyphens in an ASN.1 specification
to underscore characters in the generated code.
Semantics errors occur on the compiler back-end as the code is being generated. In this case, parsing was successful,
but the compiler does not know how to generate the code. These errors are flagged by embedding error messages
directly in the generated code. The error messages always begin with an identifier with the prefix '%ASN-',. A search
can be done for this string in order to find the locations of the errors. A single error message is output to stderr after
compilation on the unit is complete to indicate error conditions exist.
19
Chapter 3. ASN1C GUI Users Guide
Quick Start
This section demonstrates running ACGUI, activating a license key, creating a new ASN.1 schema, and compiling it
to C for BER data. The process is similar for other languages.
If the Enter License Key window is displayed and it is not showing a current license key, right-click in the text box
and paste the accurate license key. Then click Activate to unlock ASN1C.
20
ASN1C GUI Users Guide
In some cases, the Enter License Key window is displayed and showing a current license key. In these cases, it is likely
that the key being shown is expired. First deactivate the current key by clicking Deactivate. Then, right-click in the
text box and paste the current license key, and click Activate to unlock ASN1C.
If an osyslic.txt license file is being used instead of a key, and the osyslic.txt is in a location where the GUI does not
look, click Import to find the file and use it to unlock ASN1C.
If Skip is clicked within the Enter License Key window, the features of the GUI can be explored, but code cannot
be generated.
If a new license key must be activated and the current one is still valid (for example, if ASN1C is purchased before
the evaluation key expires), the existing license must be deactivated first. This deactivation can be done from the GUI
by navigating to Tools > Options and then selecting the License tab. The ASN.1 Editor Settings window is displayed.
Click Deactivate to deactivate the existing license. Then click OK. Next, navigate to Tools > Options > License tab
again to activate the new key.
21
ASN1C GUI Users Guide
The "Check-in license on exit" check box is used to indicate that the license should be immediately returned to the
license pool upon exit making it available for other users on different machines. If not checked, the machine on which
it is being used will continue to hold it until it times out (typically in 24 hours). If you will only be using ASN1C on
a single machine most of the time, it is better to keep it unchecked as it will lead to faster startups since the Internet
check will not need to be done each time.
The HTTP PROXY box can be used if you are using ASN1C on a machine that requires Internet requests go through
a proxy server.
The Output tab is displayed by default. In the Application Language Type section, select C. In the Encoding Rules
section, select BER. Finally, in the Output Directory text box, enter or browse to the location where the generated files
should be saved. When finished defining the project settings, click OK. These settings can be changed at any time by
selecting Project > Project Settings from the menu.
Next, a new schema file is created for the project. Click New in the toolbar or navigate to File > New Schema File. A
dialog box is displayed to define a name for the new file. Once entered, the file is added to the project window under
the "Schema/ASN.1 files" heading and its empty contents are shown in the editor.
22
ASN1C GUI Users Guide
The schema is then written between the "DEFINITIONS ::= BEGIN" and "END" statements in the file. For this
example, the following can be entered:
Once the schema is created, click Validate to perform a check for errors.
Note
If the new schema file has not yet been saved, ACGUI asks if it should be. Once saved, ACGUI validates
the file.
If the schema has errors, they are displayed in the log at the bottom of the ACGUI window.
Once the project has been configured, click Compile to generate code according to the project settings. If compliation
is successful, a list of generated files, sorted according to the selected language, is displayed under the Generated Items
heading in the Project pane. If compilation is not successful, any errors are displayed in the Error Log.
23
ASN1C GUI Users Guide
At this time, project settings can be changed and schema files can be edited as needed.
Creating a Project
Since there are a large number of options available in the code generation process, ACGUI allows settings to be saved
in project files for reuse. Project files can be created, opened, and saved from the Project menu. If no project file is
explicitly used, a dummy project is implicitly created and can be saved to a file at a later time.
The ASN1C Settings window contains standard tabs for Output, Function Generation, Constraints and Debugging,
and Code Modifications. Additional tabs are loaded once an Application Language Type is selected on the Output tab.
Note
Details regarding the tabs and contents within the ASN1C Settings window can be found in the Interface
topic of this guide.
24
ASN1C GUI Users Guide
Editing a Project
A project's settings can be changed at any time by navigating to Project > Project Settings.
Opening a Project
To open an existing project, navigate to Project > Open Project. Use the File Explorer window to navigate to the desired
project and click Open. Recent projects can be accessed by navigating to Project > Recent projects and selecting the
desired project from the list.
Once opened, project assets, such as ASN.1 schemas and generated source files, are visible in the Project pane.
Saving a Project
To save a project, navigate to Project > Save Project or Project > Save Project As.
Editing Schemas
The central area of the ACGUI window is dedicated to editing ASN.1 schema definition files.
Note
Additional information on the schema Editor can be found in the Interface topic of this guide.
25
ASN1C GUI Users Guide
Define the schema by copy/pasting text or by manually entering text between the "DEFINITIONS ::= BEGIN" and
"END" statements in the file. When finished, click Save file in the toolbar or navigate to File > Save file or File >
Save file as. Click Validate to check for proper syntax and to confirm that no errors are present. Upon validation, a
success or failure message is returned in the Error Log.
At any point during editing, the schema can be saved and validated.
26
ASN1C GUI Users Guide
Compiling
Once a project is created and schemas are added, the schemas may be compiled to generate source code and related files.
Note
A target language must be selected for the project prior to compiling. To set the target language, navigate to
Project > Project Settings > Output tab, and make a selection from the Application Language Type section.
Click Compile from the toolbar or navigate to Tools > Compile. Upon compilation, a success or failure message is
returned in the Error Log.
Note
Upon clicking Compile, if any files have unsaved changes, a dialog box is displayed to prompt the user to
save the files.
After compilation, changes can continue to be made to the schema and to the project settings. Recompilation can be
done as needed.
Interface
The ACGUI interface is comprised of five parts: Editor, Project window, ASN.1 Tree window, Error Log window,
and Project Settings.
27
ASN1C GUI Users Guide
Editor
The central part of the ACGUI window is the schema Editor. From here, schema files can be viewed and edited.
To begin editing an ASN.1 schema, create or open a schema file. A new schema file can be opened by clicking New in
the toolbar or by navigating to File > New Schema File. An existing schema file can be accessed via the Open button
in the toolbar or navigating to File > Open File. The file is added to the current project and shown in the Editor.
28
ASN1C GUI Users Guide
The Editor window is also used to display a schema browser for navigating within a validated schema. To display the
browser after validating a schema, click on an item in the ASN.1 Tree window. The browser displays a hyperlinked
version of the schema, centered on the definition of the selected item. Click the names of other defined types in the
browser to show their definitions.
By default, schema documents are displayed as tabs in the Editor. The Text and Browser tabs at the bottom of the
window are for schema editing and hyperlinked schema browsing, respectively. Schema files currently open for edit
are displayed as tabs at the top of the Text tab.
Project Window
The Project window allows the user to interact with project assets, and contains the following components: Schema/
ASN.1 files, Include Directories, Configuration files and Generated Items.
29
ASN1C GUI Users Guide
Schema/ASN.1 files This section lists the files that contain the current project's ASN.1 schema definitions.
Include Directories This section lists the directories containing auxiliary ASN.1 schema files. The current
project's schema may import definitions from modules defined in an included directory.
Configuration file This section lists the ASN1C compiler configuration file currently in use.
Generated Items This section lists the files generated by the compiler, separated by target language.
Click on a schema or configuration file in the Project window to open that file in the Editor. Right-click on any schema
file, include directory, or configuration file to easily add another or remove that particular asset from the project.
Right-clicking on the Schema/ASN.1 files heading also displays options for 'Find in Files' and 'Replace in Files.'
Selecting 'Find in Files' displays the Find in Files window, which provides users with the ability to enter any text to be
located in either all schemas in the project, or only the schemas currently open in the editor. Once the Find parameters
have been defined, click Find All to be presented with a list of files that contain the search text.
Click an entry within the results list to display the text in the editor.
30
ASN1C GUI Users Guide
Selecting 'Replace in Files' displays the Replace in Files window, which provides users with the ability to enter any
text to be located and replaced in either all schemas in the project, or only the schemas currently open in the editor.
Once the Find and Replace parameters have been defined, click Replace All to be presented with a list of files that
include the word to be changed.
Click an entry within the results list to display the text in the editor, and if desired, deselect any check box next to
an entry in the results list to prevent the replacement from happening on that result. Click Replace to perform the
word replacement.
31
ASN1C GUI Users Guide
At the top level of the tree, the modules of the schema are shown. Each of these modules can be expanded to reveal
branches for the types, values, information objects, etc. defined within. Click on any node of the tree to show the
relevant ASN.1 definition in a built-in browser in the Editor window.
32
ASN1C GUI Users Guide
In many cases, an error is associated with a particular portion of the schema being compiled. Click on an error to
open the schema editor at the location in which the error occurred. If more than one error is reported, clicking Next
Error and Previous Error in the toolbar moves the Editor window to the part of the schema where the next or previous
error occurred.
When the reported errors are no longer needed, click Clear in the Error Log window to remove them from display.
Project Settings
The Project Settings window is where details regarding the project, such as encoding rules, target language, and code
features to generate are modified. The window contains the following tabs: Output, Function Generation, Constraints
and Debugging, Code Modifications, language-specific code modifications, and Build Options.
Output tab
The Output tab contains options for selecting a target language, encoding rules, and output directory.
33
ASN1C GUI Users Guide
Application Language Type This section provides users with the ability to define the target language for the
project. A target language must be selected in order to compile a schema.
For C or C++ target languages, the C/C++ Output Options section controls how
generated code is distributed across source files.
For C#, the C# Code Organization section controls how generated code is dis-
tributed across source files and how files are organized into directories.
For Java, the Java Code Organization section controls how generated code
should be organized into directories based on the ASN.1 module for which they
were generated. Alternatively, generated files are placed directly into the output
directory.
ASN.1 Standard This section provides users with the ability to apply current or previously-de-
fined ASN.1 standards to the generated code.
Additional Translations This section provides users with the ability to define the options for generating
transformed versions of the input schema, such as HTML or pretty-printed.
Encoding Rules This section provides users with the ability to define one or more encoding rule
sets to be selected for the generated code.
34
ASN1C GUI Users Guide
Input Options This section provides users with the ability to define how strict the compiler is
when parsing ASN.1 schema.
Generated Function Types This section provides users with the ability to define granular control of which
functions to generate. The printing functions allow for various printing schemes
to be generated, such as print-to-string and print-to-standard-output, and how
the printed data should be formatted.
35
ASN1C GUI Users Guide
Specify PDU Types This section provides users with the ability to define which productions to select
as PDUs.
Sample Program Generation This section provides users with the ability to define the generation of simple
encoding and decoding programs, which demonstrate using the generated code.
Additionally, the sample writer program can optionally encode randomly-gen-
erated test data.
Language-specific Functions Depending on the target language selected, additional options may be displayed
within this section of the Function Generation tab.
For C and C++, additional functions for memory management and macros for
dealing with named bits in BIT STRINGs can be generated. Initialization func-
tions are generated by default, but may be turned off.
For Java, get and set methods can be generated for members of generated class-
es. It is also possible to generate methods that can fetch certain types of meta-
data (for example, if an element is optional). A similar option exists for C#.
36
ASN1C GUI Users Guide
Constraints This section provides users with the ability to add or remove various types of
restriction checks from the generated code.
Debugging and Event Handling This section provides users with the ability to add debug tracing and event
hooks. In addition to enabling event callbacks, generation of type structures
can also be disabled, in which case generated decode functionality simply calls
user-created event handlers and does not perform its own decoding operation.
37
ASN1C GUI Users Guide
Space Optimizations This section provides users with the ability to remove unwanted or unneeded function-
ality and shorten the names of generated types.
Other Options This section provides users with the ability to define several miscellaneous settings,
including the option to generate code for types that have been imported into the current
schema.
Language-specific tab
Additional code modification options that are language-specific are shown in a separate tab next to the Code Modifi-
cations tab. The label and contents of this tab changes based on the language selected within the Output tab.
38
ASN1C GUI Users Guide
For C/C++, the tab is displayed as follows, and includes several settings for adjusting how ASN.1 types are mapped
to native C/C++ types:
For C#, the tab is displayed as follows, and includes settings to allow for manipulating the namespace into which
code is generated:
Note
The Java tab contains similar options.
39
ASN1C GUI Users Guide
40
ASN1C GUI Users Guide
A makefile can be generated in either Windows or GNU format. For Windows, a Visual Studio project can also
be generated. Under the Build Libraries section, which generates the build script to build a library rather than an
executable, the desired variety of library can be selected.
The C/C++ Compile Optimization section allows for defining whether Space or Time optimization qualifiers should
be added to the C compilation command-line in the makefile.
41
ASN1C GUI Users Guide
For C#, a makefile or Visual Studio project can be created, optionally including a *.mk file listing the files generated.
An option to specify a strongly named key file is also available.
42
ASN1C GUI Users Guide
Like C#, Java can also provide a *.mk generated file list, as well as an Ant build script and a batch or shell script.
43
ASN1C GUI Users Guide
For Python, ASN1C can create a batch file (Windows) or shell script (non-Windows) that generates the Python code
as set up by the GUI settings.
44
ASN1C GUI Users Guide
The Go code generator can create a makefile to generate and build Go code. The generator can also create a JSON
file with random test data. Additionally, an option for the generator not to create a main.go file (for instance, if there
already is one that has been modified) is available.
45
Chapter 4. Generated Java Source Code
Overview
A separate Java source file with extension '.java' is generated for each production encountered within an ASN.1 source
file. Every ASN.1 type is mapped to a Java class. This is true even at the lowest levels – types such as BOOLEAN,
INTEGER, and NULL all have wrapper classes.
• Package specification
• Import statements
• Class declaration
• Constructors
• Other methods
Additional specialized items may be present as well depending on the base type of the target production. These spe-
cialized items are discussed in the sections on ASN.1 to Java mappings for the various ASN.1 types.
A complete generated Java source file for the 'EmployeeNumber' production within the production within the ASN.1
sample file 'employee.asn' can be found on the following page. The ASN.1 production from which this file was gen-
erated is as follows:
package sample_ber.Employee;
import com.objsys.asn1j.runtime.*;
import java.io.*;
import java.util.*;
public EmployeeNumber() {
super();
}
46
Generated Java Source Code Overview
if (explicit) {
aal += buffer.encodeTagAndLength (TAG, aal);
}
return (aal);
}
}
Package Specification
The package specification is the first item in the file and is declared using the 'package' keyword. By default, this is
set to the name of the ASN.1 module that is being compiled. However, this can be modified by using the –pkgpfx
and – pkgname command line options. The –pkgpfx option adds the specified prefix before the module name. For
example, if an ASN.1 module named 'Employee' is being compiled and '-pkgpfx test.' is specified on the command
line, the package name in the generated source files would be 'test.Employee'. The –pkgname switch takes this a step
further. It allows specification of the full package name. In the sample specification above, '-pkgpfx sample_ber.' was
specified on the compiler command line.
Standard import statements are added for the ASN1C Java run-time classes and Java utility classes. Import statements
may also be added for items imported from other ASN.1 modules if they don't exist within the package being generated.
Class Declaration
Next comes the class declaration. It is of the following form:
<ProdName> is the name of the production in the ASN.1 source file. <BaseClass> is a class from which the type is
derived. This can either be a standard run-time or compiler-generated class. In our example, the EmployeeNumber
47
Generated Java Source Code Overview
is an INTEGER, so we can directly extend the Asn1Integer run-time base class. If we had a declaration such as the
following:
Our EmployeeSSNumber class would be derived from the compiler-generated EmployeeNumber class as follows:
Note: the preceding example is not true if –compact is specified. In that case, all intermediate classes would be removed
so EmployeeSSNumber would extend Asn1Integer as in the first case.
Tag Constant
The next item in the generated source file is a tag constant. This is only generated if the production is tagged. The
runtime class Asn1Tag is used for this constant. This class contains methods for operating on ASN.1 tag values. In
the sample above, the [APPLICATION 2] tag that is present in the ASN.1 production definition is represented by the
generated tag constant.
Constructed types will contain public member variables to represent the elements that make up the type. For example,
the following SEQUENCE production:
will result in the following public member variables being added to the generated class:
Note that the member variables are public. They were declared this way to make access easier. A trade-off existed be-
tween ease-of-use and secure encapsulation. The ease-of-use approach was chosen because it was felt that the repeated
use of get/set methods within deeply nested structures would be too clumsy and bulky in most applications. Therefore,
the variables were made public to make the encapsulated values easier to set and retrieve. Consistency checks have
been added in some methods to make sure values of the correct types are specified for these elements. These checks
are discussed in the sections on the specific constructed types.
This behavior can be overridden by using the -getset command-line option. This will cause the member variables to
be declared as protected variables and accessor/mutator methods (i.e. get/set) methods added to access the variables.
Constructors
Constructors are generated to allow an object to be initialized in a number of different ways. All productions have a
default constructor with no parameters. This creates an empty object that can be populated at a later time. Constructors
48
Generated Java Source Code Overview
are also created that take a parameter of the base type value to allow direct population upon creation of an object. In
our example code, two constructors were generated:
public EmployeeNumber () {
super();
}
More complex constructed ASN.1 types such as a SEQUENCE would have a constructor that would have an argument
for each defined element. A CHOICE on the other hand would have a unique constructor for each of the possible choice
items. See the sections on specific ASN.1 types to find out exactly what constructors are generated for a given type.
Decode Method
The generated decode method for BER/DER has the following general form:
Users of the C and C++ version of the product might recognize this form. It is very similar to the C function prototype.
A reference to an Asn1BerDecodeBuffer object is passed that specifies the message being decoded. This is similar to
the context variable in the C version of the product.
The explicit and implicitLength arguments should be of no concern to the average user. The explicit argument should be
set to true and the implicitLength argument set to zero. These arguments are only used in internal calls generated by
the compiler when implicit tagging is used. In this case, the decoder will at times only be concerned with decoding the
contents of a field and not the tag information. At the outer levels, it will always be necessary to decode a tag and length.
The Java decode method reports errors by throwing exceptions. This is a change from the C/C++ version that returned
a status value. The method signature includes the following throws clause:
The Asn1Exception class is the base class for all exceptions defined for ASN1C. A complete list of these exceptions
can be found in the ASN1C Exceptions section.
In this case, the explicit and implicitLength arguments are not required since PER has no tagging. The only required
argument is a reference to a decode buffer object.
For XER or XML, the default behavior is to generate code using the org.xmlpull.v1.XmlPullParser class
to parse the XML. Other parsers can be specified using -stax or -sax. When the XmlPull API is used, the following
methods are generated:
49
Generated Java Source Code Overview
If -sax is specified on the command line, the SAX API is used and two overloaded decode methods are generated:
These take as arguments an XML reader object reference and a reference to an input source object. The XML reader
object is a standard class within an XML parser that reads and parses an XML document. The input source can either
be a URI (this can be a local filename) or an in-memory byte stream.
Encode Method
The generated encode method for BER/DER has the following general form:
The Asn1BerEncodeBuffer argument specifies the buffer into which the message will be encoded. The explicit argu-
ment is primarily for use by the compiler for generating internal calls to handle implicitly tagged elements in con-
structed types. Users should always set this argument to true.
The encode method returns the length of the encoded component. Unlike the C /C++ version, this return value does
no double as a status value as well. Any errors that occur in the encode process are reported by throwing an ASN1C
exception. A complete list of these exceptions can be found in the ASN1C Exceptions section.
In this case, the explicit argument is not required since PER has no tagging. The only required argument is a reference
to an encode buffer object. Also note that the return value is void instead of int. No intermediate lengths are returned
during the encoding of a PER message. Any errors that occur are reported as an exception; hence there I no need for
a return value.
In this case, the buffer reference is to an XER encoder object and an element name argument is added. The
Asn1XerEncoder reference is to an interface that allows either a message buffer or output stream object to be passed
into the method. In the case of XML, this object reference would be to an Asn1XmlEncoder interface.
50
Generated Java Source Code Overview
The element name is the name of the element that is to bracket the XML encoded value (i.e. <elemName>value</
elemName>).
The method return type is void because errors are reported through the exception mechanism.
Other Methods
Other generated methods include the following; these are documented more fully in separate sections:
• Get and set methods are generated when the -getset command-line swith is set. These methods allow bean-style
access to each element in a container type (SEQUENCE, SET, and CHOICE).
• Print methods are generated when the -print or -prtToStr command-line switches are specified. Users can
call these methods to write a formatted representation of the object to a print writer, a print stream, or a string.
• Public set_<element> methods are generated for all CHOICE types. These allow users to select the desired
element to be encoded in a CHOICE. If the -getset switch is specified, these methods are replaced by set methods
as described above.
• If -copy is specified on the command-line, ASN1C generates overrides to the standard clone method.
• ASN1C generates equals and hashCode methods when -compare is specified on the command line.
• Specifying -genmetadata on the command line causes ASN1C to generate methods that allow users access to
some of the meta data for containers, like whether elements are required to be present or the value range specified
for an element.
Inner Classes
The generation of code for XER or XML may cause the following inner class definition to be generated:
SaxHandler() {
<code ..>
}
51
Generated Java Source Code Overview
<code ..>
}
This is an implementation of a standard SAX content handler class. As the XML parser software parses messages,
the methods within this class are invoked with the parsed content. The startElement method is invoked after a start
element tag (<tag>) is parsed. The characters method is invoked one or more times to pass the content between tags
into the application. The endElement method is invoked when an end element tag (</tag>) is encountered.
The ASN1C compiler generates custom code for each ASN.1 type within a given specification to parse the XML
contents and fill in the generated Java objects.
Error Handling
As noted elsewhere in this manual, the asn1c runtime and generated code will throw exceptions that are, or derive
from, Asn1Exception. The exception tells you what was wrong and provides a stack trace. If you need additional
information you may try the following:
• Identify the problem element using element name tracking. The procedure to do this is simple:
1. Add "-events" to your asn1c command line. This is necessary for element name tracking to work.
4. Get the name of the problem element by invoking Asn1Context.getCurrentElement() inside your exception han-
dling code.
Element name tracking is demonstrated in the sample Writer in sample_ber/EventHandler.
52
Chapter 5. ASN.1 Type to Java Class
Mappings
The following sections discuss the specific mappings of ASN.1 and XSD types to Java classes.
BOOLEAN
The ASN.1 BOOLEAN type is converted to a Java class that extends the Asn1Boolean run-time class. This base class
encapsulates the following public member variable:
This is where the Boolean value to be encoded is stored. It also contains the result of a decode operation. Since it is
public, it can be accessed directly to get or set the value. The generated constructors can also be used to set the value.
The following shows the basic mapping from ASN.1 type to Java class definition:
ASN.1 Production
XSD Type
<xsd:boolean>
This definition assumes a simple assignment of the form "<name> ::= BOOLEAN" (i.e., no tagging or subtypes have
been added to the BOOLEAN declaration). In this case, no specific encode or decode methods are generated – calls
to these methods pass through to the generic calls defined in the base class. This is true of all other primitive type
declarations as well unless otherwise noted.
INTEGER
The ASN.1 INTEGER type is converted to a Java class that extends the Asn1Integer run-time class. This base class
encapsulates the following public member variable:
This is where the integer value to be encoded is stored. It also contains the result of a decode operation. Since it is
public, it can be accessed directly to get or set the value. The generated constructors can also be used to set the value.
53
ASN.1 Type to Java Class Mappings
The following shows the basic mapping from ASN.1 type to Java class definition:
ASN.1 Production
XSD Types
This shows the class generated for a simple INTEGER assignment. If a tagged or constrained type is specified, specific
encode and decode methods will be generated as well.
For example, the following INTEGER type might be declared in the ASN.1 source file:
Then, in a configuration file used with the ASN.1 definition above, the following declaration can be made:
<production>
<name>SecurityKeyType</name>
<isBigInteger/>
</production>
This will cause the compiler to generate the following class header:
The value field is populated by creating a Java BigInteger object and either passing it in through the constructor or
using it to directly populate the public member variable named value declared in the base class.
54
ASN.1 Type to Java Class Mappings
BIT STRING
The ASN.1 BIT STRING type is converted to a Java class that extends the Asn1BitString run-time class. This base
class encapsulates the following two public member variables:
The following shows the basic mapping from ASN.1 type to Java class definition:
ASN.1 Production
This shows the class generated for a simple BIT STRING assignment. If a tagged or constrained type is specified,
specific encode and decode methods will be generated as well.
The constructors generated for this type provide additional options for populating the member variables in the base
class. In addition to passing the string using the numbits and data arguments to specify a bit string in native format,
the string can be specified as an array of boolean values or as a string. The string form expects the string to be passed
in the ASN.1 value notation format for either a binary string (i.e., 'xxxx'B) or a hexadecimal string (i.e., 'xxxx'H).
Named Bits
In the ASN.1 standard, it is possible to define an enumerated bit string that specifies named constants for different
bit positions. ASN1C provides support for this type of construct by generating symbolic constants that can be used
to set, clear, or test these named bits. These symbolic constants are simply the bit names and values in the following
general form:
The base class contains the following methods for using these generated constants:
55
ASN.1 Type to Java Class Mappings
• set : This method can be used to set a bit in the bit string to be set. There is also an overloaded version that takes
a boolean value argument that can be used to set the bit to the given boolean value.
• clear : This method can be used to clear the named bit in the bit string.
• isSet : This method can be used to test if the named bit is set or clear.
See the Asn1BitString class description in the run-time section for more details on these methods.
OCTET STRING
The ASN.1 OCTET STRING type is converted to a Java class that extends the Asn1OctetString run-time class. This
base class encapsulates the following public member variable:
The number of octets to be encoded or that were decoded is specified in the built-in length component of the array
object (i.e., value.length).
The following shows the basic mapping from ASN.1 type to Java class definition:
ASN.1 Production
XSD Types
<xsd:hexBinary>, <xsd:base64Binary>
This shows the class generated for a simple OCTET STRING assignment. If a tagged or constrained type is specified,
specific encode and decode methods will be generated as well.
56
ASN.1 Type to Java Class Mappings
The constructors generated for this type provide additional options for populating the member variables in the base
class. In addition to passing the string directly using the data argument, the string form can be used. The string is
passed in ASN.1 value notation format for either a binary string (i.e., 'xxxx'B), hexadecimal string (i.e., 'xxxx'H), or
a character string (i.e., 'xxxx'). A constructor also exists that allows a portion of a byte array starting at a given offset
and consisting of a given number of bytes to be used to populate the variable.
• TBCD-STRING
• TBCDSTRING
• TBCDString
• BCDString
Additionally, you can use a configuration file and the isTBCDString element to apply this special treatment to
other types.
This special treatment of BCD and TBCD strings can be disabled using the -noTBCD or -noBCD command-line
option; these two options are equivalent and either option disables special treatement in both cases. Use of this option
is necessary for certain specifications, which use the above type names for types that are not strictly TBCD strings.
PLMNidentity
Some specifications (e.g., RANAP) contain a production named PLMNidentity. In most of these specifications the
production is declared to be of type TBCD-STRING, although this declaration is not correct, as a filler character can
appear in the middle of the string, which is not permitted in genuine TBCD strings.
The default behavior of ASN1C is to recognize a production named PLMNidentity (any case) and treat that production
as an OCTET STRING, regardless of what the ASN.1 might specify. This special treatment of PLMNidentity can be
disabled with the -noPLMN qualifier to the asn1c command. There is also an <isPLMNidentity> configuration file item
at production level to declare that a production is a PLMNidentity and will therefore be treated as an OCTET STRING.
All character string types are derived from the Asn1CharString base class (except the UniversalString). This class
contains the following public member variable that holds the character string contents:
Each of the specific ASN.1 character string types except UniversalString has an associated Java class that is derived
from the Asn1CharString base class. The general form of the Java class name for each of the ASN.1 string types is
57
ASN.1 Type to Java Class Mappings
Asn1 followed by the ASN.1 string type name. For example, IA5String is represented by the Asn1IA5String class,
NumericString by the Asn1NumericString, etc.
The UniversalString associated Java class is derived from Asn1Type and it contains the following public member that
holds the character string contents:
The following shows the basic mapping from ASN.1 type to Java class definition:
ASN.1 Production
XSD Types
<xsd:string> and all related types including date/time types and duration.
ENUMERATED
The ASN.1 ENUMERATED type is converted into a Java class that extends the Asn1Enumerated run-time class. In
version 6.1, the generated code was changed to conform to Joshua Bloch's static enumeration pattern (as explained in
Effective Java). Enumerated values are created as singletons to allow for lazy initialization. A specially named object,
dec, is created to hold decoded values. In combination, these changes improve application performance, since only a
fixed number of objects are allocated for any execution of the application.
The following shows the basic mapping from ASN.1 type to Java class definition:
ASN.1 Production
XSD Types
58
ASN.1 Type to Java Class Mappings
return <e1>;
}
...
Note
1. The ... notation used in the ASN.1 definition above does not represent the ASN.1 extensibility notation. It
is used to show a continuation of the enumerated sequence of values.
2. The <e1>, <e2>, etc. items denote enumerated constants. These can be in identifier only format or
identifier(value) format. The <v1>, <v2>, etc. items denote the enumerated values. These are sequential
numbers starting at zero if no values are provided. Otherwise, the actual enumerated values are used.
3. The public methods that are generated are shown without arguments or function bodies for brevity.
In the case of the enumerated type, encode/decode methods are always generated. These verify that the given value
is within the defined set. An Asn1InvalidEnumException is thrown if the value is not in the defined set unless the
enumeration is extensible. In this case, no exception is thrown.
If an extensibility marker (...) is present in the ASN.1 definition, it will not affect the generated constants. A constant
will be generated for all options – both root and extended. However, in the ValueOf method, an "undefined" constant
will be returned to indicate that the value is not in the original specification.
NULL
The ASN.1 NULL type is converted into to a Java class that extends the Asn1Null run-time class. This base class does
not contain a public member variable for a value because the NULL type has no associated value.
The following shows the basic mapping from ASN.1 type to Java class definition:
ASN.1 Production
59
ASN.1 Type to Java Class Mappings
super();
}
}
This shows the class generated for a simple NULL assignment. If a tagged type is specified, specific encode and decode
methods will be generated as well.
OBJECT IDENTIFIER
The ASN.1 OBJECT IDENTIFIER type is converted to a Java class that extends the Asn1ObjectIdentifier run-time
class. This base class encapsulates the following public member variable:
The number of subidentifiers to be encoded or that were decoded is specified in the built-in length component of the
array object (i.e., value.length).
The following shows the basic mapping from ASN.1 type to Java class definition:
ASN.1 Production
This shows the class generated for a simple OBJECT IDENTIFIER assignment. If a tagged or constrained type is
specified, specific encode and decode methods will be generated as well.
RELATIVE-OID
The ASN.1 RELATIVE-OID type is converted to a Java class that extends the Asn1RelativeOID run-time class. This
class extends the Asn1ObjectIdentifier class defined above. The storage of the relative OID value is the same as de-
scribed for OBJECT IDENTIFIER. The only difference is the extended class defines different implementations of the
encode/decode methods that apply the rules associated with the RELATIVE-OID type.
REAL
The ASN.1 REAL type is converted to a Java class that extends the Asn1Real run-time class.
The Asn1Real base class is used for standard ASN.1 REAL specifications or XSD float or double types. This class
encapsulates the following public member variable:
60
ASN.1 Type to Java Class Mappings
The following shows the basic mapping from ASN.1 type to Java class definition:
ASN.1 Production
XSD Types
<xsd:float>, <xsd:double>
This shows the class generated for a simple REAL assignment. If a tagged or constrained type is specified, specific
encode and decode methods will be generated as well.
The ASN.1 Base 10 REAL type is converted to a Java class that extends the Asn1Real10 run-time class. A base 10
real is specified in ASN.1 using a WITH COMPONENTS clause such as the following:
REAL(WITH COMPONENTS {
...,
base (10)
})
In this case, the real number is stored as a Java character string in the character string base class:
ASN.1 Production:
XSD Types
<xsd:decimal>
61
ASN.1 Type to Java Class Mappings
}
}
SEQUENCE
The ASN.1 SEQUENCE type is converted to a Java class that extends the Asn1Type run-time base class. Public
member variables are generated for each of the elements defined in the SEQUENCE. Each of these member variables
represents an object reference since all of the ASN.1 types are mapped to Java objects.
The following shows the basic mapping from ASN.1 type to Java class definition:
ASN.1 Production
XSD Types
<xsd:sequence>, <xsd:all>
public <name> () {
super();
}
62
ASN.1 Type to Java Class Mappings
Note
1. The ... notation used in the ASN.1 definition above does not represent the ASN.1 extensibility notation.
It is used to show a continuation of the sequence elements.
2. The <type1>, <type2>, etc. items denote the equivalent Java types generated from the ASN.1 <ele-
ment-type1>, <element-type2>, etc. definitions.
3. The public and private methods that are generated are shown without arguments or function bodies for
brevity.
The compiler first generates a public member variable for each of the elements defined in the SEQUENCE. The
decision was made to make these variables public to make them easier to populate for encoding. The alternative was to
use protected or private variables with get/set methods for setting or examining the values. It was felt that this approach
would be too cumbersome for setting values in deeply nested constructed types.
A default constructor is then generated followed by overloaded constructors for setting the element values. The first
form is simply a direct mapping of each of the element types to a constructor argument. The second form only contains
arguments for the required types in the SEQUENCE (i.e. OPTIONAL and DEFAULT elements are omitted). The third
form uses the base type of each of the elements as the type for each argument. This makes it possible to construct a
SEQUENCE or SET using literal variables instead of always having to create an object. Finally, another variant of
this constructor with primitive types is generated for required elements only. It is possible that you will not see all of
these variations in a given generated class. It depends on a) whether or not the SEQUENCE or SET contains optional
items and b) whether or not it contains primitive data items.
For example, the following shows how a variable of a generated class containing two IA5String elements could be
constructed:
Without this second form of constructor, the following would need to be done:
Also note that since all member variables are public, it is not necessary to use any of the argument-based constructors
at all. A variable can be created using the default constructor and each of the elements populated directly.
A ::= SEQUENCE {
x SEQUENCE {
a1 INTEGER,
a2 BOOLEAN
},
y OCTET STRING SIZE (10)
}
In this example, the production has two elements: x and y. The nested SEQUENCE x has two additional elements:
a1 and a2.
The ASN1C compiler first recursively pulls all of the embedded constructed elements out of the SEQUENCE and
forms new temporary types. The names of the temporary types are of the form <name>_<element-name1>_<element-
63
ASN.1 Type to Java Class Mappings
name2>_ ... <element-nameN>. Using this algorithm, the ASN.1 type defined above would be reduced to the following
equivalent ASN.1 types:
A ::= SEQUENCE {
x A-x,
y OCTET STRING SIZE (10)
}
The mapping of the ASN.1 types to Java classes would then be done.
In the case of nesting levels greater than two, all of the intermediate element names are used to form the final name.
For example, consider the following type definition that contains three nesting levels:
X ::= SEQUENCE {
a SEQUENCE {
aa SEQUENCE { x INTEGER, y BOOLEAN },
bb INTEGER
}
}
In this case, the generation of temporary types results in the following equivalent type definitions:
Note that the name for the aa element type is X-a-aa. It contains both the name for a (at level 1) and aa (at level 2).
This is a change from v5.1x and lower where only the production name and last element name would be used (i.e., X-
aa). The change was made to ensure uniqueness of the generated names when multiple nesting levels are used.
OPTIONAL keyword
Elements within a sequence can be declared to be optional using the OPTIONAL keyword. This indicates that the
element is not required in the encoded message.
Optional elements are accounted for in the Java version of the compiler by simply using null object references to
denote the absence of an element. Remember that even the simplest primitive ASN.1 type definitions are wrapped in
a Java class definition. Therefore an object must be created for any type defined as an element within a SEQUENCE.
To populate a SEQUENCE object for encoding that contains optional elements, the special constructor(s) for required
elements only can be used. The default constructor also can be used followed by the manual creation and setting of
the individual element values. The default constructor will initialize all element object references to null, so only the
items to be encoded need be populated.
DEFAULT keyword
The DEFAULT keyword allows a default value to be specified for elements within the SEQUENCE. ASN1C will
parse this specification and treat it as it does an optional element. Note that the value specification is only parsed in
64
ASN.1 Type to Java Class Mappings
simple cases for primitive values. It is up to the programmer to provide the value in complex cases. For BER encoding,
a value must be specified be it the default or other value.
For DER or PER, it is a requirement that no value be present in the encoding for the default value. For integer and
boolean default values, the compiler automatically generates code to handle this requirement based on the value in the
structure. For other values, the default value is handled the same as an optional element (i.e., a null object reference
indicates that nothing should be transmitted). The programmer must set the element object reference to null on the
encode side to specify default value selected. If this is done, a value is not encoded into the message. On the decode
side, the developer must test for a null object reference. If this is the case, the default value specified in the ASN.1
specification is used.
Extension Elements
If the SEQUENCE type contains an open extension field (i.e., a ... at the end of the specification or a ..., ... in the
middle), a special element will be inserted to capture encoded extension elements for inclusion in the final encoded
message. This element will be of type ASN1OpenExt and have the name extElem1. This field will contain the complete
encoding of any extension elements that may have been present in a message when it is decoded. On subsequent encode
of the type, the extension fields will be copied into the new message.
If the SEQUENCE type contains an extension marker and extension elements, then the open extension type field will
not be added. Instead, the actual extension elements will be present. These elements will be treated as optional elements
whether they were declared that way or not. The reason is because a version 1 message could be received that does
not contain the elements.
An example of how this is used might be a gateway application that read XML data and then translated to binary
form for transmission over a low bandwidth network. When received on the other end, the receiving application would
transcode back from binary to XML. Suppose the item being transmitted was described using an xsd:all type that
had three elements: a, b, and c. When the original XML document was received by the sending application, suppose
the elements were received in the order c, b, a. The order array would record this fact and it would be included in
the binary serialization. When the receiver decoded the message on the other end, the order information would be
available along with the element data. The receiver could then reconstruct the XML document with the items in the
same order as received.
SET
The ASN.1 SET type is converted into a Java class that is identical to that for SEQUENCE as described in the previous
section. The only difference between SEQUENCE and SET is that elements may be transmitted in any order in a SET
whereas they must be in the defined order in a SEQUENCE. The only impact this has on ASN1C is in the generated
decoder for a SET type.
The decoder must take into account the possibility of out-of-order elements. This is handled by using a loop to parse
each element in the message. Each time an item is parsed, an internal mask bit within the decoder is set to indicate
the element was received. The complete set of received elements is then checked after the loop is completed to verify
all required elements were received.
65
ASN.1 Type to Java Class Mappings
SEQUENCE OF
The ASN.1 SEQUENCE OF type is converted to a Java class that extends the Asn1Type run-time base class. An array
public member variable named elements is generated to hold the elements of the defined type.
The following shows the basic mapping from ASN.1 type to Java class definition:
ASN.1 Production
XSD Types
Elements or content group definitions containing the minOccurs and/or maxOccurs facets. Also, <xsd:list> types use
this model.
public <type> () {
elements = null;
}
The compiler first generates a public member variable to hold the SEQUENCE OF elements. The decision was made
to make the variable public to make it easier to populate for encoding. The alternative was to use protected or private
variables with get/set methods for setting or examining the values. It was felt that this approach would be too cumber-
some for setting values in deeply nested constructed types.
Two constructors are generated: a default constructor and a constructor that takes a number of elements argument.
The default constructor will set the elements variable to null. The second constructor will allocate space for the given
number of elements. The recommended way to populate a variable of this type for encoding is to use the second form
of the constructor to allocate the required number of elements and then directly set the element object values. For
example, to populate the following construct:
Note that each of the integer element values is wrapped in an Asn1Integer wrapper class.
66
ASN.1 Type to Java Class Mappings
When a constructed type is referenced, a temporary type is generated for use in the final production. The format of
this temporary type name is as follows:
<prodName>_element
In this definition, <prodName> refers to the name of the production containing the SEQUENCE OF type.
For example, a simple (and very common) single level nested SEQUENCE OF construct might be as follows:
In this case, a temporary type is generated for the element of the SEQUENCE OF construct. This results in the following
two equivalent ASN.1 types:
These types are then converted into the equivalent Java classes using the standard mapping that was previously de-
scribed.
Normally, this would result in the addresses element being pulled out and used to create a temporary type with a name
equal to "SomePDU-addresses" as follows:
However, when the SEQUENCE OF element references a simple defined type as above with no additional tagging
or constraint information, an optimization is done to cut down on the size of the generated code. This optimization
is to generate a common name for the new temporary type that can be used for other similar references. The form of
this common name is as follows:
_SeqOf<elementProdName>
67
ASN.1 Type to Java Class Mappings
So instead of this:
The advantage is that the new type can now be easily reused if "SEQUENCE OF AliasAddress" is used in any other
element declarations. Note the (illegal) use of an underscore in the first position. This is to ensure that no name colli-
sions occur with other ASN.1 productions defined within the specification.
An example of the savings of this optimization can be found in H.225. The above element reference is repeated 25
different times in different places. The result is the generation of one new temporary type that is referenced in 25
different places. Without this optimization, 25 unique types with the same definition would have been generated.
SET OF
The ASN.1 SET OF type is converted into a Java class that is identical to that for SEQUENCE OF as described in
the previous section.
CHOICE
The ASN.1 CHOICE type is converted to a Java class that extends the Asn1Choice run-time base class. This base class
contains protected member variables to hold the choice element object and a selector value to specify which item in
the CHOICE was chosen. Methods are generated to get and set the base class members.
The following shows the basic mapping from ASN.1 type to Java class definition:
ASN.1 Production
XSD Types
<xsd:choice>, <xsd:union>
68
ASN.1 Type to Java Class Mappings
...
public void decode () { ... }
public int encode () { ... }
public void print () { ... }
}
Note
1. The ... notation used in the ASN.1 definition above does not represent the ASN.1 extensibility notation.
It is used to show a continuation of the sequence elements.
2. The public and private methods that are generated are shown without arguments or function bodies for
brevity.
The compiler generates sequential identification constants for each of the defined elements in the CHOICE construct.
The format used is the element names converted to all uppercase characters and preceded by an underscore. The
constants represent the values returned by the base class getChoiceID method can therefore be used to determine what
type of choice element was received in a decode operation.
The getElemName method is generated by the compiler and returns the name of the selected element.
A series of set_<element> methods are generated for setting the element value. In these declarations, <element> would
be replaced with the actual element names. This is the only way an element value can be set for encoding; these
methods ensure a consistent setting of both the element identifier and object reference values.
To access the value of a generated CHOICE object, the getChoiceID and getElement methods within the base class
are used. This is generally done with an if or switch statement as follows:
Asn1BMPString element;
if (aliasAddress.getChoiceID() == AliasAddress._H323_ID) {
element = (Asn1BMPString) aliasAddress.getElement();
}
In this case, getChoiceID is invoked and the result tested to see if the expected value was received. If it was, the element
is assigned using getElement with a cast operation.
69
ASN.1 Type to Java Class Mappings
c TestChoice-c
}
In this case, the embedded constructed element for option c was pulled out to form the TestChoice-c production and
then this new production is referenced in the original definition.
The following demonstrates setting a variable of the TestChoice structure defined above to use the first option:
1. getChoiceID – this returns an identifier equal to one the generated choice identifier constants, and
2. getElement – this returns a reference to the decoded element object. It is of type Asn1Type but it can be upcast to
the correct element type using information from the getChoiceID call.
In addition, the compiler generates a getElemName method that can be used to get the textual name of the decoded
element.
The <xsd:union> type is handled in a similar fashion to a choice type. The main difference is that the items in a union
are not tagged. As per X.694, special element names are generated for these items for use in an ASN.1 CHOICE type.
These names are based on the base name alt and progress with sequential digits added for each addional union item
(alt-1, alt- 2, etc.). XML decoding is accomplished by attempting to decode the content of each alternative in the union
and setting the value to the first alternative that can be decoded successfully.
Open Type
Note: The X.680 Open Type replaces the X.208 ANY or ANY DEFINED BY constructs. An ANY or ANY DEFINED
BY encountered within an ASN.1 module will result in the generation of code corresponding to the Open Type de-
scribed below.
The ASN.1 Open Type is converted into a Java class that extends the Asn1OpenType class. This class in turn extends the
Asn1OctetString class and provides the following public member variable for storing the encoded message component:
The number of octets to be encoded or that were decoded is specified in the built-in length component of the array
object (i.e., value.length).
The following shows the basic mapping from ASN.1 type to Java class definition:
70
ASN.1 Type to Java Class Mappings
ASN.1 Production
The <openType> placeholder is to be replaced with any type of open type specification. It could be the ANY or
ANY DEFINED BY keywords from the X.208 specification or an open type from X.681 (for example, TYPEIDEN-
TIFIER.& Type).
The last form of the constructor shown above is for an optimized form of Open Type encoding. When encoding is
done using BER, an open type header can be directly added to the beginning of an encoded message component. By
using this form of the constructor, you are indicating to the run-time encoder that the encoded message component
onto which a header is to be added is already present in the message buffer. The advantage is that binary copies of
the encoded message components are avoided both from the encode buffer to the open type object and from the open
type object back to the encode buffer.
For XER, a new class derived from the Asn1OpenType class was created. This is the Asn1XerOpenType class and this
must be used whenever an open type is required for XER. The reason for creating a special derived class is because
of dependencies on XML parser classes defined within this class. If these were added directly to the Asn1OpenType
class, a user would need to always have XML parser .jar files included in their classpath – even if working with BER,
DER, or PER only.
If the –tables command line option is selected and the ASN.1 type definition references a table constraint, the code
generated is different. In this case, Asn1OpenType above is replaced with Asn1Type. This the base class for all ASN.1
types. This allows a value of any ASN.1 type to be specified. On the encoding side, a user can assign an object of any
ASN.1 type to this variable and the encoding routine will call the appropriate encoder according to the table index
value. If the variable type is not present in the table and the Object Set is extensible, than it can be encoded as an
open type. Otherwise an exception will be thrown. On the decoding side, the appropriate variable type is populated
from the table based on the decoded index parameters. The user can determine the variable type from the table index
value. If the variable type is not present in table, then it will be decoded as an open type if the Object Set is extensible;
otherwise and exception will be thrown.
<xsd:any> Handling
71
ASN.1 Type to Java Class Mappings
The XSD any wildcard item is similar to an ASN.1 open type in semantics in that it allows any valid content to be
present in that position in an XML document. However, an ASN.1 open type is not used to model an <xsd:any>.
Instead, a character string variable is used. This stores the full XML text of the field in native XML form (i.e. angle
brackets and the like are not escaped). Note that the XML text is not converted to different form when using binary
encoding rules - it is maintained as XML text.
External Type
The ASN.1 EXTERNAL type is a useful type used to include non-ASN.1 or other data within an ASN.1 encoded
message. The type is described using the following ASN.1 SEQUENCE:
The ASN.1 compiler is used to create a meta-definition for this structure. The definition is stored in the file
Asn1External.java (or Asn1XerExternal.java for XER). An object created from the resulting Java class is populated
just like any other compiler-generated structure for working with ASN.1 data.
EmbeddedPDV Type
The ASN.1 EMBEDDED PDV type is a useful type used to include non-ASN.1 or other data within an ASN.1 encoded
message. It was introduced in 1994 to replace EXTERNAL by removing unneeded fields and adding a few new ones
to hold information that was missing. This type is described using the following ASN.1 SEQUENCE:
The ASN.1 compiler is used to create a meta-definition for this structure. The definition is stored in the file
Asn1EmbeddedPDV.java (or Asn1XerEmbeddedPDV.java for XER). An object created from the resulting Java class
is populated just like any other compiler-generated structure for working with ASN.1 data.
72
ASN.1 Type to Java Class Mappings
Parameterized Types
The ASN1C compiler can parse parameterized type definitions and references as specified in the X.683 standard.
These types allow dummy parameters to be declared that will be replaced with actual parameters when the type is
referenced. This is similar to templates in C++.
A simple and common example of the use of parameterized types is for the declaration of an upper bound on a sized
type as follows:
In this definition, 'ub' would be replaced with an actual value when the type is referenced. For example, a sized octet
string with an upper bound of 32 would be declared as follows:
The compiler would handle this in the same way as if the original type was declared to be an octet string of size 1 to
32. In the case of Java, this would result in size constraint checks being added to the generated encode and decode
methods for the type.
Another common example of parameterization is the substitution of a given type inside a common container type. For
example, security specifications frequently contain a 'signed' parameterized type that allows a digital signature to be
applied to other types. An example of this would be as follows:
where 'Name' would be another type defined elsewhere within the module.
ASN1C performs the substitution to create the proper Java class definition for SignedName:
When processing parameterized type definitions, ASN1C will first look to see if the parameters are actually used
in the final generated code. If not, they will simply be discarded and the parameterized type converted to a normal
type reference. For example, when used with information objects, parameterized types are frequently used to pass
information object set definitions to impose table constraints on the final type. Since table constraints do not affect
the code that is generated by the compiler, the parameterized type definition is reduced to a normal type definition
and references to it are handled in the same way as defined type references. This can lead to a significant reduction in
generated code in cases where a parameterized type is referenced over and over again.
For example, consider the following often-repeated pattern from the UMTS 3GPP specs:
73
ASN.1 Type to Java Class Mappings
In this case, IEsSetParam refers to an information object set specification that constrains the values that are passed
for any given instance of a type referencing a ProtocolIE-Field. The compiler does not add any extra code to check
for these values, so the parameter can be discarded. After processing the Information Object Class references within
the construct (refer to the Information Objects section for information on how this is done), the reduced definition for
ProtocolIE-Field becomes the following:
References to the field are simply replaced with a reference to the generated ProtocolID-Field class.
Value Specifications
The ASN1C compiler can parse any type of ASN.1 value specification, however, the basic version will only generate
code for the following types of value specifications:
• BOOLEAN
• INTEGER
• ENUMERATED
• Binary String
• Hexadecimal String
• Character String
• OBJECT IDENTIFER
The Pro version of the compiler will generate code for the following remaining types of value specifications:
• Enumerated
• Real
• Sequence
• Set
• Sequence Of
• Set Of
• Choice
If any of the above types of value specifications are detected in an ASN.1 module, the compiler will generate a Java
source file with a special class to hold the values. The name of the source file and class is of the following format:
74
ASN.1 Type to Java Class Mappings
_<ModuleName>Values
In this definition, <ModuleName> would be replaced with the name of the ASN.1 module in which the values are
defined.
The following sections provide details on the Java constants generated for the various types of ASN.1 value specifi-
cations.
ASN.1 production:
ASN.1 production:
ASN.1 production:
In the ASN.1 production definition, the lowercase 'b's above represent binary digits (1's or 0's). The generated code
contains a numbits constant set to the number of bits (binary digits) in the string. The data constant specifies the binary
data using hexadecimal byte values.
ASN.1 production:
75
ASN.1 Type to Java Class Mappings
In the ASN.1 production definition, the lowercase 'h's above represent hexadecimal digits (0-9, a-f, or A-F). The
generated constant specifies the binary data using hexadecimal byte values.
ASN.1 production:
In the ASN.1 production definition, <StringType> would be replaced with one of the ASN.1 character string types
(for example, IA5String). The lowercase 'c's represent string characters. The generated constant is simply the string
in Java form.
ASN.1 production:
ASN.1 production:
76
ASN.1 Type to Java Class Mappings
enumvalue will be the sequential integer value corresponding to the enumitem in enumtype.
ASN.1 production:
ASN.1 production:
This would result in the following Java constant being generated for value:
ASN.1 production:
77
ASN.1 Type to Java Class Mappings
This would result in the following Java constant being generated for value:
ASN.1 production:
78
Chapter 6. Generated BER/DER/CER
Encode Methods
Two different types of BER (Basic Encoding Rules) encode methods may be generated using the ASN1C compiler:
For DER (Distinguished Encoding Rules), only the first option is available because a requirement of DER is that all
lengths must be in definite form. For CER (Canonical Encoding Rules), only the second option is available because all
constructed element lengths must be in indefinite length form. Each of these methods are described in the following
sections.
An encode method is only generated if it is required to alter the encoding of the base class method. The Java model is
built on inheritance from a set of common run-time base classes. These run-time classes contain default implementa-
tions of encode/decode methods that handle the encoding/decoding of the basic types. These default implementations
include support for adding the universal tags associated with the types as defined in the X.680 standard.
So for simple assignments, the generation of an encode method is not necessary. For example, the following production
will not result in the generation of an encode method:
X ::= INTEGER
In this case, the generated Java class extends the Asn1Integer base class and the default encode method within this
class is sufficient to encode a value of the generated type.
However, if the type is altered to contain a tag or constraint, then a custom encode method would be generated:
Some types will always cause encode methods to be generated. At the primitive level, this is true for the ENUMER-
ATED type. This type will always contain a custom set of enumerated values. All constructed types (SEQUENCE,
SET, SEQUENCE/SET OF, and CHOICE) will cause encode methods to be added to the generated classes.
The buffer argument is a reference of an Asn1BerEncodeBuffer object that describes the buffer into which a message
is being encoded. This must be created and initialized before calling any encode method. See the description of this
class in the Java Run-Time Classes section for details on how this class is used.
79
Generated BER/DER/CER Encode Methods
The return value is the length in octets of the encoded message component. Unlike the C/C++ version, a negative
value is never returned to indicate an encoding failure. That is handled by the exception mechanism. All ASN1C
Java exceptions are derived from the Asn1Exception base class. See the section on exceptions for a complete list and
description of the various exceptions that can be thrown.
Constructors are provided for most generated types to allow direct population of the encapsulated member variable(s)
on initialization. The exception is the classes generated for SEQUENCE OF or SET OF. These only allow the size of
an array to be specified – population of the array elements must be done manually.
All of the base run-time classes except Asn1Null contain public member variables. In practically all cases there is a
single variable called value that is of the base type that needs to be populated. For example, the Asn1Integer base class
contains the following item:
Therefore, population of any class variable derived from INTEGER can be done by adding.value to the end of the
lefthand side of the assignment and an integer value on the right. So for the following assignment:
X ::= INTEGER
A variable of the type can either be populated using the constructor with the following statement:
X x = new X (25);
X x = new X ();
x.value = 25;
The only primitive type that does not have a single member called value to represent its value is BIT STRING. In this
case, the Asn1BitString class contains a second variable called numbits to specify the number of bits in the string.
1. Create an encode message buffer object into which the value will be encoded.
3. Invoke encode message buffer methods to access the encoded message component.
The first step is the creation of an encode message buffer object. Unlike the C/C++ version of the product, there is no
choice to be made between a static or dynamic encode buffer. In Java, everything is dynamic. There are two forms of
the constructor: a default constructor and one that allows specification of a message buffer size increment. The size
increment will determine how often the buffer will need to be resized to hold large messages. If you know that you will
be encoding large messages, then this object should be constructed with a large value for the increment. If you know
that you will be encoding small messages in a constrained environment, then this value can be set very low. The default
constructor sets the value to a reasonable mid-range value (see SIZE_INCREMENT in Asn1EncodeBuffer.java, as of
this writing the value was set to 1024).
80
Generated BER/DER/CER Encode Methods
The second step is the invocation of the encode method. The calling arguments were described earlier. As per the Java
standard, this method must be invoked from within a try/catch block to catch the possible Asn1Exception that may be
thrown. Alternatively, the method from which the encode method is called can declare that it throws an Asn1Exception
leaving it to be dealt with at a higher level.
Finally, encode buffer methods can be called to access the encoded message component. The encode method itself
returns the length of the component, so this item is already known (however, there is a getMsgLength method available
if you want to access this length from a different location). Unlike C or C++, a pointer to where the message starts in
the encode buffer cannot be returned (recall that BER encoding is done from back to front, so a message rarely starts
at the beginning of a buffer). However, the Java API provides an object called a ByteArrayInputStream that provides
a way to look at the encoded component as a stream. The encode buffer object therefore provides a method called
getByteArrayInputStream which is the preferred way to access the encoded component.
In addition to getByteArrayInputStream there is a getMsgCopy function that will retrieve a copy of the generated
message into a byte array object. This is somewhat slower because a copy needs to be done. The encode buffer class
also contains other methods for operating directly on the encoded component (for example, the write method can be
used to write it to a file or other medium). And of course, one could derive their own special encode buffer class from
this class to add more functionality. See the description of the Asn1BerEncodeBuffer class in the run-time section for
a full description of the available methods.
try {
personnelRecord.encode (encodeBuffer, true);
if (trace) {
System.out.println ("Encoding was successful");
System.out.println ("Hex dump of encoded record:");
encodeBuffer.hexDump ();
System.out.println ("Binary dump:");
encodeBuffer.binDump ();
}
encodeBuffer.hexDump
81
Generated BER/DER/CER Encode Methods
You would not want to recreate the data holder and message buffer objects on each pass of the loop. This would have
an adverse effect on the performance of the application. What you would want to do is only create the objects a single
time and then reuse them to encode each message instance.
It turns out that this is an easy thing to do. The public member variable access to the data holder object makes it easy to
change the variables on each given pass. And the encode buffer object contains a reset method for resetting the encode
buffer for subsequent encodings. The use of this method has the advantage of not releasing any of the memory that
had been accumulated to this point for previous encodings.
To show an example of object reuse, suppose we were going to encode a series of names. The ASN.1 type for the
names would be as follows:
The generated Java class would contain public member variables for each of the string objects:
The most efficient way to repopulate these variables within a loop would be simply to assign each of the new strings to
be encoded directly to the public value member variables contained within the Asn1IA5String objects (i.e., the Name
or Asn1IA5String objects should not be reconstructed each time).
Name name = new Name ("", "", ""); // creates empty string objects
Asn1BerEncodeBuffer encodeBuffer = new Asn1BerEncodeBuffer ();
for (;;) {
...
82
Generated BER/DER/CER Encode Methods
name.givenName.value = string1;
name.initial.value = string2;
name.familyName.value = string3;
// encode
try {
len = name.encode (encodeBuffer, true);
...
encodeBuffer.reset ();
}
catch (Asn1Exception e) {
// handle error ..
}
}
The basic principles of the generation of the encode methods are the same as for ordinary BER/DER encode methods.
Stream-oriented BER encoding starts from the beginning of the message until the message is complete. This is some-
times referred to as "forward encoding". This differs from regular BER where encoding that is done from back-tofront.
Indefinite lengths are used for all constructed elements in the message. Also, there is no permanent buffer for stream-
oriented encoding, all octets are written directly to the output stream.
The out argument is a reference of an Asn1BerOutputStream object that describes the output stream into which a
message is being encoded. This must be created and initialized before calling any encode method. See the description
of this class in the Java Run-Time Classes section for details on how this class is used
The explicit argument specifies whether or not an explicit tag should be applied to the encoded contents. The average
user will almost always want to set this argument to true. The only time it would not be set to true is if a user wanted
83
Generated BER/DER/CER Encode Methods
to just encode a contents field with no tag. This argument is used primarily by the compiler when generating internal
calls to properly handle implicit and explicit tagging.
Unlike the C/C++ version, a negative value is never returned form encode methods to indicate an encoding failure.
That is handled by the exception mechanism. All ASN1C Java exceptions are derived from the Asn1Exception base
class. See the section on exceptions for a complete list and description of the various exceptions that can be thrown.
If I/O error occurs then the java.io.IOException is thrown.
1. Create an output stream object into which the value will be encoded
The first step is the creation of an output stream object. There are two forms of the constructor: a constructor with one
parameter (OutputStream reference) and one that allows specification of an internal buffer size. A larger internal buffer
size generally provides better performance at the expense of increased memory consumption. The first constructor sets
the value to a reasonable mid-range value.
The second step is the invocation of the encode method. The calling arguments were described earlier. As per the
Java standard, this method must be invoked from within a try/catch block to catch the possible Asn1Exception and
java.io.IOException, which may be thrown. Alternatively, the method from which the encode method is called can
declare that it throws Asn1Exception and java.io.IOException leaving it to be dealt with at a higher level.
try {
// Step 1: Create an output stream object. This object uses the
// default size increment for buffer expansion..
if (trace) {
System.out.println ("Encoding was successful");
System.out.println ("Hex dump of encoded record:");
encodeBuffer.hexDump ();
84
Generated BER/DER/CER Encode Methods
try {
if (out != null)
out.close ();
}
catch (Exception e) {}
}
If you compare this example with the BER encoding example in Figure 2, you will see the encoding procedure is
almost identical. This makes it very easy to switch encoding methods should the need arise. All you need to do is
change Asn1BerEncodeBuffer to Asn1BerOutputStream and remove the explicit code that writes the messages into the
stream. Also closing of the stream should be added.
85
Chapter 7. Generated BER/DER/CER
Decode Methods
For each ASN.1 production defined in the ASN.1 source file, a Java decode method may be generated. This method
will decode an ASN.1 message into public member variables within the Java object.
As was the case for encode methods, a decode method is only generated if it is required to alter the default method
in the base class. The Java model is built on inheritance from a set of common run-time base classes. These run-
time classes contain default implementations of encode/decode methods that handle the encoding/decoding of the
basic types. These default implementations include support for handling the universal tags associated with the types
as defined in the X.680 standard.
The buffer argument is a reference of an Asn1BerDecodeBuffer object that describes the message that is being decoded.
This must be created and initialized before calling any decode method. See the description of this class in the Java
Run- Time Classes section for details on how this class is used.
The explicit and implicitLength arguments specify whether or not an explicit tag should be parsed from the encoded
contents. The average user will almost always want to set explicit to true and implicitLength to zero. The only time
these arguments would not be set this way is if a user wanted to directly decode contents with no tag/length information.
These arguments are used primarily by the compiler when generating internal calls to properly handle implicit and
explicit tagging.
The decode method returns no result. Unlike the C/C++ version, a negative status value is not returned to indicate a
failure. That is handled by the exception mechanism. All ASN1C Java exceptions are derived from the Asn1Exception
base class. See the section on exceptions for a complete list and description of the various ASN.1 exceptions that can
be thrown. The java.io.Exception that can be thrown is in the read method within the decode buffer base class. This
method attempts to read data from an input stream using the methods in the java.io package.
The first step is the creation of a decode message buffer object. The Asn1BerDecodeBuffer object contains constructors
that can either accept a message as a byte array or as an I/O input stream. The input stream option makes it possible
86
Generated BER/DER/CER Decode Methods
to decode messages directly from other mediums other than a memory buffer (for example, a message can be decoded
directly from a file).
The Asn1BerDecodeBuffer object contains a method called peekTag that can be used to determine the outer-level tag
on a message. This can be used to determine the type of message received in applications that must deal with multiple
message types.
The generated decode method can then be invoked to decode the message. The calling arguments were described
earlier. As per the Java standard, this method must be invoked from within a try/catch block to catch the possible
exceptions that may be thrown. Alternatively, the method from which the decode method is called can declare that it
throws the exceptions leaving them to be dealt with at a higher level.
The final step is to process the data. All data is contained within public member variables so access is quite easy. And
of course Java has the distinct advantage of not requiring any clean-up once you are done with the data. The garbage
collector will collect the unused memory when it is no longer referenced.
try {
if (trace) {
System.out.println ("Decode was successful");
personnelRecord.print (System.out, "personnelRecord", 0);
}
}
catch (Exception e) {
System.out.println (e.getMessage());
e.printStackTrace();
return;
}
87
Generated BER/DER/CER Decode Methods
the most efficient way to decode the messages. Objects should be reused where possible to avoid the overhead of
excessive memory allocations and garbage collection.
A single decode buffer object can be used to process a stream of messages. If the decode message buffer is created
using an input stream object that contains a series of messages (for example, a file containing multiple records or a
communications device), all that needs to be done is the continuous invocation of the BER decode method for the
given message type.
Nothing special needs to be done to reuse the generated type object for decoding. The decoder will automatically all
the internal init() method before decoding to make sure all items are reset to their starting state.
In the example above, all that would need to be done to decode a series of personnel records is the inclusion of a loop
after the PersonnelRecord object was created in step 2:
for (;;) {
personnelRecord.decode (decodeBuffer);
if (trace) {
System.out.println ("Decode was successful");
personnelRecord.print (System.out, "personnelRecord", 0);
}
}
Deferred decoding can be done on elements defined within a SEQUENCE, SET or CHOICE construct. It is done by
designating an element to be an open type by using the <isOpenType/> configuration setting. This setting causes the
ASN1C compiler to insert an Asn1OpenType placeholder in place of the type that would have normally been used
for the element. The data in its original encoded form will be stored in the open type container when the message is
decoded. The data within the open type container can be fully decoded later by using the normally-generated decode
function generated by the ASN1C compiler. (This stands in contrast to C and C++ code generation, which requires
a special decode function for this purpose.)
Partial decoding is similar to deferred decoding, except that where deferred decoding captures the encoded data for
later decoding, partial decoding skips over it and discards the encoded data. Since the encoded data is not retained,
partial decoding uses less memory. Partial decoding is done by designating an element to be skipped using the <skip/>
configuration setting. This setting causes the ASN1C compiler to generate decoders that simply skip over that element.
The following configuration file is required to indicate the element id is to be processed as an open type (i.e. that it
will be decoded later) and that element details should be skipped:
<asn1config>
<module>
88
Generated BER/DER/CER Decode Methods
<name>modulename</name>
<production>
<name>Identifier</name>
<element>
<name>id</name>
<isOpenType/>
<element/>
<element>
<name>details</name>
<skip/>
<element/>
<production/>
<module/>
<asn1config/>
In the generated code, the element id type will be replaced with an open type (Asn1OpenType), and the type will be
decoded as such. When the top-level decoding has finished, the element may be decoded by taking the open type value
(id.value) and using it as a source for a new Asn1BerDecodeBuffer. The element can then be decoded by creating a
new object for the element (in this case, a new instance of Asn1ObjectIdentifier) and calling its decode method. The
element details will simply be skipped.
89
Chapter 8. Generated PER Encode Methods
The generation of methods to encode data in accordance with the Packed Encoding Rules (PER) is similar to how
methods were generated in the BER/DER case discussed previously. For each ASN.1 production defined in the ASN.1
source file, a Java encode method may be generated. This function will convert a populated variable of the given type
into an encoded ASN.1 message.
An encode method is only generated if it is required to alter the encoding of the base class method. The Java model is
built on inheritance from a set of common run-time base classes. These run-time classes contain default implementa-
tions of encode/decode methods that handle the encoding/decoding of the basic types.
For simple assignments, the generation of an encode method is not necessary. For example, the following production
will not result in the generation of an encode method:
X ::= INTEGER
In this case, the generated Java class extends the Asn1Integer base class and the default encode method within this
class is sufficient to encode a value of the generated type.
In the case of BER/DER, a custom encode method was generated if a) the type was tagged, or b) it contained a testable
constraint. In the case of PER, only the latter condition will cause a custom method to be generated. The reason is
because PER basically ignores the tags on tagged types and they therefore have no effect on the final decoded message
component.
For example, the following declaration will cause a custom encode method to be generated because the value range
constraint is a PER-visible that will alter the encoding:
In this case, special logic is necessary to apply the value range constraint.
Some types will always cause encode methods to be generated. At the primitive level, this is true for the ENUMER-
ATED type. This type will always contain a custom set of enumerated values. All constructed types (SEQUENCE,
SET, SEQUENCE/SET OF, and CHOICE) will cause encode methods to be added to the generated classes.
The buffer argument is a reference of an Asn1PerEncodeBuffer object that describes the buffer into which a message
is to be encoded. This must be created and initialized before calling any encode method. See the description of this
class in the Java Run-Time Classes section for details on how this class is used.
The PER encode methods do not return a value. This is different than the C/C++ version that returns a negative
status value to indicate an encoding failure. For Java, errors are reported via the exception mechanism. All ASN1C
Java exceptions are derived from the Asn1Exception base class. See the section on exceptions for a complete list and
description of the various exceptions that can be thrown.
90
Generated PER Encode Methods
Once an object's member variables have been populated, the object's encode method can be invoked to encode the
value. The general procedure to do this involves the following three steps:
1. Create an encode message buffer object into which the value will be encoded
3. Invoke encode message buffer methods to access the encoded message component
The first step is the creation of an encode message buffer object. For PER encoding, this is an object of the
Asn1PerEncodeBuffer class. The following constructors are available for creating a PER encode buffer object:
The first argument indicates whether PER aligned or unaligned encoding should be done. The second form of the
constructor contains a size increment argument. This argument will determine how often the buffer will need to be
resized to hold large messages. If you know that you will be encoding large messages, then this object should be
constructed with a large value for the increment. If you know that you will be encoding small messages in a constrained
environment, then this value can be set very low. The default constructor sets the value to a reasonable mid-range
value (see SIZE_INCREMENT in Asn1EncodeBuffer.java, as of this writing the value was set to 1024).
The second step is the invocation of the encode method. The calling arguments were described earlier. As per the
Java standard, this method must be invoked from within a try/catch block to catch the possible exceptions that may be
thrown. Alternatively, the method from which the encode method is called can declare that it throws an Asn1Exception
leaving it to be dealt with at a higher level.
Finally, encode buffer methods can be called to access the encoded message component. The Java API provides an
object called a ByteArrayInputStream that provides a way to look at the encoded component as a stream. The encode
buffer object provides a method called getInputStream that returns a byte array input stream representing the message
component. This is the preferred way to access the encoded component.
In addition to getInputStream there is a getMsgCopy function that will retrieve a copy of the generated message into
a byte array object. This is somewhat slower because a copy needs to be done. Another option that is only available
when doing PER encoding is the getBuffer method. This returns a reference to the actual message buffer into which
the message was encoded. Since a PER message is encoded front-to-back (unlike the back-to-front used in BER/DER
encoding), the buffer reference returned will point to the start of the encoded message. The getMsgByteCnt method
can then be used to get the message length in bytes or the getMsgBitCnt method can be called to get the length in bits.
The encode buffer class also contains other methods for operating directly on the encoded component (for example,
the write method can be used to write it to a file or other medium). And of course, one could derive their own special
encode buffer class from this class to add more functionality. See the description of the Asn1PerEncodeBuffer class
in the runtime section for a full description of the available methods.
91
Generated PER Encode Methods
try {
personnelRecord.encode (encodeBuffer);
if (trace) {
System.out.println ("Encoding was successful");
System.out.println ("Hex dump of encoded record:");
encodeBuffer.hexDump ();
System.out.println ("Binary dump:");
encodeBuffer.binDump ("personnelRecord");
}
encodeBuffer.hexDump
(new PrintStream (new FileOutputStream ("message.dmp")));
If you compare this example with the BER encoding example in Figure 2, you will see the encoding procedure is almost
identical. This makes it very easy to switch encoding methods should the need arise. All you need to do is change
Asn1BerEncodeBuffer to Asn1PerEncodeBuffer and remove the explicit argument from the encode method call.
92
Generated PER Encode Methods
to the ASN.1 data type to be encoded outside of the processing loop. These objects can then be reused to encode
each instance of the messages to be sent. After each message is encoded, the PER buffer must be reset for the next
message by using the reset method. See the section on reuse of objects in the BER encoding section for a more thorough
discussion and sample code on using this capability.
93
Chapter 9. Generated PER Decode Methods
For each ASN.1 production defined in the ASN.1 source file, a Java decode method may be generated. This method
will decode an ASN.1 message into public member variables within the Java object.
As was the case for encode methods, a decode method is only generated if it is required to alter the default method in the
base class. The Java model is built on inheritance from a set of common run-time base classes. These run-time classes
contain default implementations of encode/decode methods that handle the encoding/decoding of the basic types.
For primitive types, a custom PER decode method is only generated if one or more of the following is true:
The exception to this rule is the ENUMERATED primitive type (or likewise, INTEGER type with a named number
list) that will always cause a decode method to be generated.
Constructed types will always cause custom PER decode methods to be generated.
The buffer argument is a reference of an Asn1PerDecodeBuffer object that describes the message that is being decoded.
This must be created and initialized before calling any decode method. See the description of this class in the Java
Run- Time Classes section for details on how this class is used.
The decode method returns no result. Unlike the C/C++ version, a negative status value is not returned to indicate a
failure. That is handled by the exception mechanism. All ASN1C Java exceptions are derived from the Asn1Exception
base class. See the section on exceptions for a complete list and description of the various ASN.1 exceptions that can
be thrown. The java.io.Exception that can be thrown is in the read method within the decode buffer base class. This
method attempts to read data from an input stream using the methods in the java.io package.
The first step is the creation of a decode message buffer object. The Asn1PerDecodeBuffer object contains constructors
that can either accept a message as a byte array or as an I/O input stream. The input stream option makes it possible
94
Generated PER Decode Methods
to decode messages directly from other mediums other than a memory buffer (for example, a message can be decoded
directly from a file or a socket).
Unlike BER or DER, no mechanism exists in PER to peek at an outer level tag or identifier to identify the message
type. This type must be known beforehand. Most protocols that employ PER have a specific outer level type know
as a "Protocol Data Unit" (PDU) that encompasses all of the different message types that might be received. This is
typically a CHOICE construct with each option representing a different type of message.
The generated decode method for the PDU is invoked to decode the message. The calling arguments were described
earlier. As per the Java standard, this method must be invoked from within a try/catch block to catch the possible
exceptions that may be thrown. Alternatively, the method from which the decode method is called can declare that it
throws the exceptions leaving them to be dealt with at a higher level.
The final step is to process the data. All data is contained within public member variables so access is quite easy. All
of the primitive data type classes contain a public member variable called value that contains decoded data. This can
be accessed in nested structures by prefixing value with each of the element names from the top down. For example,
the given name element in the Name type shown earlier would be accessed as follows: name.givenName.value (this
assumes an instance of the Name class was created using the variable name name).
try {
if (trace) {
System.out.println ("Decode was successful");
personnelRecord.print (System.out, "personnelRecord", 0);
}
}
catch (Exception e) {
System.out.println (e.getMessage());
e.printStackTrace();
return;
}
95
Generated PER Decode Methods
96
Chapter 10. Generated XML Methods
Overview
X.693 specifies XER ("XML Encoding Rules"). There are three variants of XER given: BASIC-XER (often just XER
for short), canonical XER, and EXTENDED-XER. Into this mix, Objective Systems has added its own encoding rules
which we'll call OSys-XER. OSys-XER is very similar to XER, but has a few variations that are meant to produce
XML documents more closely aligned with what you might get if you were using XML Schema to specify your
abstract syntax. Generally, OSys-XER produces fewer tags. The differences between these two sets of encoding rules
are discussed in more detail below.
ASN1C supports BASIC-XER, canonical XER, and OSys-XER. It has for some time supported EXTENDED-XER
via direct compilation of XSD. In version 6.5.0, we have begun to add direct support for EXTENDED-XER by adding
support for some of the XER encoding instructions. Nonetheless, EXTENDED-XER is most fully support today via
direct compilation of XSD. By compiling XSD, you can obtain behavior much the same as with OSys-XER, and more.
• The "XER" runtime. This is used for basic and canonical XER.
• The "XML" runtime. This is used for OSys-XER and for EXTENDED-XER (whether compiling XSD or compiling
ASN.1 with XER encoding instructions).
Because these two runtimes are so similar, they are discussed in this chapter together. As you read this chapter, it is
important to keep in mind when each of these runtimes is used so that you know which cases apply to you.
Note that you may use the -xsd switch when generating XML encoders and decoders. The XML schema produced from
the ASN.1 specification using the -xsd switch can be used to validate the XML messages generated using the XML
encode functions. Similarly, an XML instance can be validated using the generated XML schema prior to decoding.
97
Generated XML Methods
• OSys-XER uses an XSD list (a space-delimited list of strings) to represent a SEQUENCE OF X, where X is any of the
following types: BOOLEAN, BIT STRING without named bits, ENUMERATED, GeneralizedTime, INTEGER,
OBJECT IDENTIFIER, OCTET STRING, REAL, RELATIVE-OID, or UTCTime. Similarly, a BIT STRING with
named bits is encoded as an XSD list (consisting of the named bit identifiers).
For example, the ASN.1 specification “A ::= SEQUENCE OF INTEGER” with value “{ 1 2 3 }” would produce
the following encoding in XER:
<A><INTEGER>1</INTEGER><INTEGER>2</INTEGER><INTEGER>3</INTEGER></A>
<A>1 2 3</A>
• The OSys-XER encoding for a SEQUENCE OF CHOICE wraps each repetition in an element. In XER, each oc-
currence of the CHOICE is an XML element and it is not further wrapped.
For example, given MyType ::= SEQUENCE OF CHOICE { a A, b B }, XER might produce a sequence
of <a> and <b> elements, while OSys-XER will produce a series of <CHOICE> elements, or, if the CHOICE type
were named MyChoice, a series of <MyChoice> elements.
• The values of the BOOLEAN data type are expressed as the lower case words “true” or “false” with no delimiters.
In XER, the values are <true/> and <false/>.
• Enumerated token values are expressed as the identifiers themselves instead of as empty XML elements (i.e. ele-
ments wrapped in ‘< />’). For example, a value of the ASN.1 type “Colors ::= ENUMERATED { red, blue, green }”
equal to “red” would simply be “<color>red</color>” instead of “<color><red/></color>”.
• The special REAL values <NOT-A-NUMBER/>, <PLUS-INFINITY/> and <MINUS-INFINITY/> are represented
as NaN, INF and -INF, respectively.
• GeneralizedTime and UTCTime values are transformed into the XSD representation for dateTime (YYYY-MMD-
DTHH: MM:SS[.SSSS][(Z|(+|-)HH:MM)]) when encoded to XML. When an XML document is decoded, the time
format is transformed into the ASN.1 format.
• When encoding/decoding a type as the root element of an XML document, OSys-XER will typically give the root
element name a lowercase first letter. By contrast, XER uses the NonParameterizedTypeName, which will have an
uppercase first letter, for the root element.
EXTENDED-XER
EXTENDED-XER (specified in X.693) allows you to vary the XML encoding of ASN.1 by using XER encoding
instructions. ASN1C supports EXTENDED-XER in two different ways: by compiling XSD and by compiling ASN.1
with XER encoding instructions. Support for XER encoding instructions in ASN.1 is limited.
This section relates to our support for XER encoding instructions. If some features you need are not supported, you
might consider using direct compilation of XSD.
98
Generated XML Methods
• BASE64: This instruction causes octet strings to be encoded in a base64 representation, rather than a hexadecimal
one.
Limitations
The following are limitations related to EXTENDED-XER:
• For BASE64: ASN1C only supports BASE64 on octet strings. Using BASE64 with octet stings having contents
constraints, open types, or restricted character strings is not supported.
• For encoder's options: ASN1C decoders do not support the following encoder's options allowed by EXTEND-
ED-XER:
• Enforcement of Encoding Instruction Restrictions: ASN1C does not check that you are using encoding instructions
properly. Misapplication of encoding instructions has undefined results. For example, X.693 does not generally
allow ATTRIBUTE to be applied to a sequence type (there are a few cases where it can be); such an application
produces malformed XML.
In particular, when applying ATTRIBUTE to a restricted character string type, the type should be restricted to
exclude the control characters listed in X.680 15.15.5, since these control characters are encoded as empty elements.
(Another solution would be to use ATTRIBUTE and BASE64 together, except that ASN1C does not currently
support BASE64 for restricted character strings.) ASN1C will not enforce this rule, but you will get malformed
XML if you try to encode a string having control characters as an attribute.
• XSD Generation: The -xsd switch does not currently generate XSD that can be used to validate EXTENDED-XER
encodings. (Actually, in the worst cases, it is not possible to produce XSD that validates precisely the set of valid
EXTENDED-XER encodings; the closest approximations would either fail to reject some invalid encodings or fail
to accept some valid encodings. This is a result of the encoder's options, which can produce mixed content models
and XML Schema's limited abilities to constrain mixed content models.)
• You will supply the name of the element when encoding a value. Typically, this will be the name of the ASN.1
PDU type.
Finally, there is a sample reader and writer program in java/sample_xer/EmployeeEXER, should you need
to see an example.
99
Generated XML Methods
An encode method is only generated if it is required to alter the encoding of the base class method. The Java model is
built on inheritance from a set of common run-time base classes. These run-time classes contain default implementa-
tions of encode/decode methods that handle the encoding/decoding of the basic types.
For simple assignments, the generation of an encode method is not necessary. For example, the following production
will not result in the generation of an encode method:
X ::= INTEGER
In this case, the generated Java class extends the Asn1Integer base class and the default encode method within this
class is sufficient to encode a value of the generated type.
1. The ASN.1 type is constructed (SEQUENCE, SET, SEQUENCE OF, SET OF, or CHOICE).
2. The ASN.1 type contains a testable constraint (for example, INTEGER (1..100))
3. The ASN.1 type is enumerated. This includes an INTEGER type with named numbers, a BIT STRING with named
bit constants, or the ENUMERATED built-in type.
Note
Two variations are discussed here: "XER" and "XML". The "XML" variant applies to EXTENDED-XER.
See the Overview section above for more information.
The buffer argument is a reference to an Asn1XerEncoder or Asn1XmlEncoder derived object that describes the
buffer or output stream into which a message is to be encoded. Asn1XerEncoder is a base interface for the
Asn1XerEncodeBuffer and Asn1XerOutputStream classes. Similarly, Asn1XmlEncoder is an interface to a pure XML
version of these base classes. There is no difference which encode method is used: output stream or message buffer.
The generated logic is the same, the difference is only in the first parameter of the encode method. This must be created
and initialized before calling any encode method. See the description of this class in the Java Run-Time Classes section
for details on how this class is used.
The elemName argument is a reference to a string containing the element name text. This text is used to form the
standard XML angle-bracketed wrapper that is applied to each element in a message. Note the name passed must not
contain the angle-brackets (i.e. the < > characters). These will be added by the encode method.
100
Generated XML Methods
The elemName can be passed in different ways to control how the name is applied. The normal way is to pass a name
that is applied as the element name of the element. If null is passed, then the default element name for the referenced
ASN.1 built-in type is used. For example, <BOOLEAN> is the default element name for the ASN.1 BOOLEAN type.
The complete list of default element names can be found in the X.693 standard. If an empty string is passed (i.e. ""),
this tells the encode method to omit the element name string all together and just encode the value (this is similar to
implicit tagging in the BER case).
The XER or XML encode methods do not return a value. This is different than the C/C++ version that returns a negative
status value to indicate an encoding failure. For Java, errors are reported via the exception mechanism. All ASN1C
Java exceptions are derived from the Asn1Exception base class. See the section on exceptions for a complete list and
description of the various exceptions that can be thrown. If I/O error occurs then the java.io.IOException is thrown.
Once an object's member variables have been populated, the object's encode method can be invoked to encode the
value. The general procedure to do this involves the following three steps:
1. Create an encode message buffer or output stream object into which the value will be encoded
2. Invoke encode methods. These include the encodeStartDocument and encodeEndDocument methods from the
Asn1XerEncodeBuffer class and the encode method from the ASN1C generated class.
3. If the encode message buffer is used: invoke encode message buffer methods to access the encoded message com-
ponent. If the output stream is used: close the stream.
The first step is the creation of an encode message buffer object. For XER encoding, this is an object of the
Asn1XerEncodeBuffer class. The following constructors are available for creating an XER encode buffer object:
The default constructor sets all internal buffer control variables to default values. Canonical XER is set to false and
size increment is set to 1024. The other forms of the constructor allow these variables to be changed. Canonical XER
specifies that the canonical form of XER encoding (CXER as specified in X.693) should be used. Size increment
specifies the amount by which the dynamic encode buffer should be expanded when it fills up. This should be set
lower for small, memory-constrained environments and higher if large messages are being encoded.
If the output stream method is used then the first step is the creation of an output stream. For XER encoding, this is
an object of the Asn1XerOutputStream class. The following constructors are available for creating an XER encode
buffer object:
The first constructor creates a buffered XER output stream with default size of an internal buffer. Canonical XER is
set to false. The other form of the constructor allows these variables to be changed. Canonical XER specifies that the
canonical form of XER encoding (CXER as specified in X.693) should be used. The buffer size argument specifies
the size of the internal buffer of the stream. Larger buffer sizes typically provide better performance at the expense
of increased memory consumption.
101
Generated XML Methods
public Asn1XmlEncodeBuffer ()
public Asn1XmlEncodeBuffer (int sizeIncrement)
public Asn1XmlOutputStream (OutputStream os)
public Asn1XmlOutputStream (OutputStream os, int bufSize)
The main difference is the XML classes to not have a canonical XML option; therefore, there is not cxer or canonical
boolean argument.
The second step is the invocation of the encode methods. The calling arguments were described earlier. As per the
Java standard, this method must be invoked from within a try/catch block to catch the possible Asn1Exception or
java.io.IOException that may be thrown. Alternatively, the method from which the encode method is called can declare
that it throws Asn1Exception and java.io.IOException leaving it to be dealt with at a higher level.
Finally, if a message buffer is used, encode buffer methods can be called to access the encoded message component.
The Java API provides an object called a ByteArrayInputStream that provides a way to look at the encoded component
as a stream. The encode buffer object provides a method called getInputStream that returns a byte array input stream
representing the message component. This is the preferred way to access the encoded component.
In addition to getInputStream, there is a getMsgCopy method that will retrieve a copy of the generated message into
a byte array object. This is somewhat slower because a copy needs to be done. Another option that is available when
doing XER encoding is the getBuffer method. This returns a reference to the actual message buffer into which the
message was encoded. Since an XER message is encoded front-to-back (unlike the back-to-front used in BER/DER
encoding), the buffer reference returned will point to the start of the encoded message. The getMsgLength method can
then be used to get the message length (in bytes). Note that the byte count may not correspond to the actual character
count as UTF-8 encoding is used and some characters may be multiple bytes in length.
If an output stream is used, the stream should be closed when encoding is complete to ensure all buffered data is
flushed to the output device.
The Asn1XerEncodeBuffer encode buffer class also contains other methods for operating directly on the encoded
component (for example, the write method can be used to write it to a file or other medium). A user could also
derive their own special encode buffer class from this class to add more functionality. See the description of the
Asn1XerEncodeBuffer class in the run-time section for a full description of the available methods.
try {
encodeBuffer.encodeStartDocument ();
102
Generated XML Methods
encodeBuffer.encodeEndDocument ();
if (trace) {
System.out.println ("Encoding was successful");
encodeBuffer.write (System.out);
}
try {
out.encodeStartDocument ();
out.encodeEndDocument ();
if (trace) {
System.out.println ("Encoding was successful");
103
Generated XML Methods
encodeBuffer.write (System.out);
}
}
catch (Exception e) {
System.out.println (e.getMessage());
e.printStackTrace();
return;
}
finally {
// Step 3: Close the stream.
try {
if (out != null)
out.close ();
}
catch (Exception e) {}
}
If you compare these examples with the other encoding examples, you will see the procedures are similar. This makes
it very easy to switch encoding methods should the need arise.
In the case of XML encode, the procedure is very similar. The only difference is that it is not necessary to call the
encodeStartDocument and encodeEndDocument methods. The are built into the generated encode method for PDU
data types.
The resulting XML document from running the program above is as follows:
104
Generated XML Methods
<familyName>Jones</familyName>
</name>
<dateOfBirth>19590717</dateOfBirth>
</ChildInformation>
</children>
</PersonnelRecord>
The decodeDocument method should be used when the parser is positioned at the start of an XML document,
making it necessary to move to the root element before reaching the data to be decoded. This is the method you will
most likely use.
The decode method should be used when the parser is already positioned on the element to be decoded. If the current
element contains the content to be decoded, invoke decode with asGroup = false. If, however, the current
element is itself part of the content to be decoded (and, in some cases, its siblings also), then invoke decode with
asGroup = true. Most likely, you will pass false.
To illustrate, suppose that we have a SEQUENCE type that defines elem1 and elem2. If we have an XML snippet
to decode, <root><elem1/><elem2/></root>, and we are positioned on the root element, we would use
asGroup = false, because root contains the content to be decoded and is not a part of it. If, however, we were
positioned on the elem1 element, we would use group = true, because elem1 is a part of the content to be
decoded; it is the beginning of the content itself.
1. Create an XmlPullParserFactory.
3. Configure the new parser by setting it to process namespaces and giving it the input stream to be parsed.
6. Access the decoded data through the public members of the generated class.
105
Generated XML Methods
try {
// Create an XML reader object
org.xmlpull.v1.XmlPullParserFactory xmlInputFactory =
org.xmlpull.v1.XmlPullParserFactory.newInstance();
org.xmlpull.v1.XmlPullParser reader =
xmlInputFactory.newPullParser();
reader.setFeature(
org.xmlpull.v1.XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
reader.setInput(inputFile, null);
106
Generated XML Methods
The decodeDocument method should be used when the parser is positioned at the start of an XML document,
making it necessary to move to the root element before reaching the data to be decoded. This is the method you will
most likely use.
The decode method should be used when the parser is already positioned on the element to be decoded. If the current
element contains the content to be decoded, invoke decode with asGroup = false. If, however, the current
element is itself part of the content to be decoded (and, in some cases, its siblings also), then invoke decode with
asGroup = true. Most likely, you will pass false.
To illustrate, suppose that we have a SEQUENCE type that defines elem1 and elem2. If we have an XML snippet
to decode, <root><elem1/><elem2/></root>, and we are positioned on the root element, we would use
asGroup = false, because root contains the content to be decoded and is not a part of it. If, however, we were
positioned on the elem1 element, we would use group = true, because elem1 is a part of the content to be
decoded; it is the beginning of the content itself.
1. Create an XMLInputFactory.
6. Access the decoded data through the public members of the generated class.
try {
// Create an XML reader object
javax.xml.stream.XMLInputFactory xmlInputFactory =
javax.xml.stream.XMLInputFactory.newInstance();
xmlInputFactory.setProperty(
javax.xml.stream.XMLInputFactory.IS_COALESCING, Boolean.TRUE);
javax.xml.stream.XMLStreamReader reader =
xmlInputFactory.createXMLStreamReader(inputFile);
107
Generated XML Methods
The default SAX parser used is the XERCES parser developed by the Apache Software Foundation ( http://
xml.apache.org ). Since SAX is a de-facto standard, it should be relatively straightforward to use the generated han-
dlers with any other implementation of SAX parser.
A diagram showing the components used in the XML decode process is as follows:
108
Generated XML Methods
ASN1C generates code to implement the following methods defined in SAX content handler interface:
startElement
characters
endElement
The interface defines other methods that can be implemented as well, but these are sufficient to decode XER encoded
data. These methods are added to an inner SAX handler class generated for each ASN.1 production.
The procedure to invoke the generated decode method is similar to that for the other encoding rules. It is as follows:
1. Instantiate an XMLReader object. The XML parser interface should provide a factory method for creating an object
of this type for any vendor-specific XML parser implementation.
2. Instantiate a generated Java <ProdName> object to hold the decoded message data.
3. Invoke the <ProdName> object decode method passing the reader created in step 1 and the URI of the XML doc-
ument to be parsed. This method initiates and invokes the XML parser's parse method to parse the document. This,
in turn, invokes the generated SAX handler methods.
4. Methods within the <ProdName> object can now be used to access the decoded data. The member variables that
were declared to be public can be accessed directly.
try {
// Create an XML reader object
XMLReader reader =
XMLReaderFactory.createXMLReader (vendorParserClass);
109
Generated XML Methods
110
Chapter 11. Generated OER Encode
Methods
The generation of methods to encode data in accordance with the Octet Encoding Rules (OER) is similar to how
methods were generated in the BER/DER case discussed previously. For each ASN.1 production defined in the ASN.1
source file, a Java encode method may be generated. This function will convert a populated variable of the given type
into an encoded ASN.1 message.
An encode method is only generated if it is required to alter the encoding of the base class method. The Java model is
built on inheritance from a set of common run-time base classes. These run-time classes contain default implementa-
tions of encode/decode methods that handle the encoding/decoding of the basic types.
For simple assignments, the generation of an encode method is not necessary. For example, the following production
will not result in the generation of an encode method:
X ::= INTEGER
In this case, the generated Java class extends the Asn1Integer base class and the default encode method within this
class is sufficient to encode a value of the generated type.
For example, the following declaration will cause a custom encode method to be generated because the value range
constraint is OER-visible and alters the encoding:
In this case, special logic is necessary to apply the value range constraint.
Constructed types (SEQUENCE, SET, SEQUENCE/SET OF, and CHOICE) will always have a generated encode
method.
The buffer argument is a reference of an Asn1OerEncodeBuffer object that describes the buffer into which a message
is to be encoded. This must be created and initialized before calling any encode method. See the description of this
class in the Java Run-Time Classes section for details on how this class is used.
The OER encode methods do not return a value. Errors are reported via the exception mechanism. All ASN1C Java
exceptions are derived from the Asn1Exception base class. See the section on exceptions for a complete list and de-
scription of the various exceptions that can be thrown.
111
Generated OER Encode Methods
Once an object's member variables have been populated, the object's encode method can be invoked to encode the
value. The general procedure to do this involves the following three steps:
1. Create an encode message buffer object into which the value will be encoded
3. Invoke encode message buffer methods to access the encoded message component
The first step is the creation of an encode message buffer object. For OER encoding, this is an object of the
Asn1OerEncodeBuffer class. The following constructors are available for creating an OER encode buffer object:
The second form of the constructor includes a size argument for the initial size of the buffer. The default constructor
sets the value to a reasonable mid-range value (see INITIAL_SIZE in Asn1EncodeBuffer.java, as of this writing the
value was set to 1024).
The second step is the invocation of the encode method, described earlier. As the method throws java.io.IOException,
your code must either handle that exception in a try/catch block or else declare that it throws it.
Finally, encode buffer methods can be called to access the encoded message component. The Java API provides an
object called a ByteArrayInputStream that provides a way to look at the encoded component as a stream. The encode
buffer object provides a method called getInputStream that returns a byte array input stream representing the message
component. This is the preferred way to access the encoded component.
In addition to getInputStream there is a getMsgCopy function that will retrieve a copy of the generated message into a
byte array object. This is somewhat slower because a copy needs to be done. Another option is the getBuffer method.
This returns a reference to the actual message buffer into which the message was encoded. The getMsgByteCnt method
can then be used to get the message length in bytes or the getMsgBitCnt method can be called to get the length in bits.
The encode buffer class also contains other methods for operating directly on the encoded component (for example,
the write method can be used to write it to a file or other medium). And of course, one could derive their own special
encode buffer class from this class to add more functionality. See the description of the Asn1OerEncodeBuffer class
in the runtime section for a full description of the available methods.
112
Generated OER Encode Methods
try {
personnelRecord.encode (encodeBuffer);
if (trace) {
System.out.println ("Encoding was successful");
System.out.println ("Hex dump of encoded record:");
encodeBuffer.hexDump ();
}
encodeBuffer.hexDump
(new PrintStream (new FileOutputStream ("message.dmp")));
If you compare this example with the BER encoding example in Figure 2, you will see the encoding procedure is
almost identical. This makes it very easy to switch encoding methods should the need arise. All you need to do is
change the buffer class and slightly alter the invocation of the encode method.
Canonical OER
If you want to produce a Canonical-OER encoding, you can choose to do so either at code generation time by specifying
the -coer switch or at runtime by invoking setCanonicalMode(true) on the encode buffer.
113
Chapter 12. Generated OER Decode
Methods
For each ASN.1 production defined in the ASN.1 source file, a Java decode method may be generated. This method
will decode an ASN.1 message into public member variables within the Java object.
As was the case for encode methods, a decode method is only generated if it is required to alter the default method in the
base class. The Java model is built on inheritance from a set of common run-time base classes. These run-time classes
contain default implementations of encode/decode methods that handle the encoding/decoding of the basic types.
For primitive types, a custom OER decode method is only generated if one or more of the following is true:
The exception to this rule is the ENUMERATED primitive type (or likewise, INTEGER type with a named number
list) that will always cause a decode method to be generated.
Constructed types will always cause custom OER decode methods to be generated.
The buffer argument is a reference of an Asn1OerDecodeBuffer object that describes the message that is being decoded.
This must be created and initialized before calling any decode method. See the description of this class in the Java
Run- Time Classes section for details on how this class is used.
The decode method returns no result. Failure is handled by the exception mechanism. All ASN1C Java exceptions are
derived from the Asn1Exception base class. See the section on exceptions for a complete list and description of the
various ASN.1 exceptions that can be thrown.
The first step is the creation of a decode message buffer object. The Asn1OerDecodeBuffer object contains constructors
that can either accept a message as a byte array or as an I/O input stream. The input stream option makes it possible
to decode messages directly from other mediums other than a memory buffer (for example, a message can be decoded
directly from a file or a socket).
114
Generated OER Decode Methods
Unlike some other encoding rules, OER encodings do not indicate the ASN.1 type that was encoded. The ASN.1 type
must be known prior to decoding in order to successfully decode it. A common approach in specifications is to have a
specific outer level type known as a "Protocol Data Unit" (PDU) that encompasses all of the different message types
that might be received. This is typically a CHOICE construct with each option representing a different type of message.
Whatever the message type is, the generated decode method for that message is invoked to decode the message. The
calling arguments were described earlier. As the method throws java.io.IOException, your code must either handle
that exception in a try/catch block or else declare that it throws it.
The final step is to process the data. All data is contained within public member variables so access is quite easy. All
of the primitive data type classes contain a public member variable called value that contains decoded data. This can
be accessed in nested structures by prefixing value with each of the element names from the top down. For example,
the given name element in the Name type shown earlier would be accessed as follows: name.givenName.value (this
assumes an instance of the Name class was created using the variable name name).
try {
if (trace) {
System.out.println ("Decode was successful");
personnelRecord.print (System.out, "personnelRecord", 0);
}
}
catch (Exception e) {
System.out.println (e.getMessage());
e.printStackTrace();
return;
}
Canonical OER
Many of the rules for Canonical-OER can be enforced during decoding, such that exceptions are thrown if the decoder
detects that the encoding was not canonical. By default, this behavior is disabled. You can enable this behavior by
either generating the code with the -coer switch or by invoking setCanonicalMode(true) on the decode buffer.
115
Generated OER Decode Methods
116
Chapter 13. Generated JSON Methods
Overview
This chapter discusses the code generated for encoding and decoding JSON data.
Note
BACKWARD COMPATIBILITY: Prior to 7.3 (and starting with asn1c 6.6), asn1c used proprietary en-
coding rules for JSON, as this work predated the development of ITU-T X.697. If you need to work with
our proprietary encoding rules, you must use the command-line arguments -compat 729 (or an older version
number). We urge you to upgrade to using X.697 JER.
Our proprietary rules, and differences with X.697, are available on our website [http://www.obj-sys.com/
docs/JSONEncodingRules.pdf].
An encode method is only generated if it is required to alter the behavior of the base class. The Java model is built on
inheritance from a set of common run-time base classes. These run-time classes contain default implementations of
encode/decode methods that handle the encoding/decoding of the basic types. The generated classes for all constructed
types (SEQUENCE, SEQUENCE OF, and CHOICE) will include generated encode methods.
The buffer argument is an Asn1JsonOutputStream object which receives the encoded message. This must be created
and initialized before calling any encode method. See the description of this class in the Java Run-Time Classes section
for details on how this class is used. Since JSON is a character-based encoding, Asn1JsonOutputStream are constructed
on java.io.Writer instances. There is a subclass, Asn1JsonOutputBuffer, that can be used to output to a byte array.
As you can see, the encode methods return void; an exception is thrown if an error occurs. All ASN1C Java exceptions
are derived from Asn1Exception. See the section on exceptions for a complete list and description of the various
exceptions that can be thrown.
117
Generated JSON Methods
Constructors are provided for most generated types to allow direct population of the encapsulated member variable(s)
on initialization. The exception to this is for classes generated for SEQUENCE OF types. In that case, the constructors
only allow the size of an array to be specified – population of the array elements must be done manually.
All of the base run-time classes except Asn1Null contain public member variables. In practically all cases there is a
single variable called value that is of the base type that needs to be populated. For example, the Asn1Integer base class
contains the following item:
X ::= INTEGER(0..255)
you may populate a variable of this type either using the constructor:
X x = new X (25);
X x = new X ();
x.value = 25;
The only primitive type that does not have a single member called value to represent its value is BIT STRING. The
Asn1BitString class also contains a second variable called numbits to specify the number of bits in the string.
1. Create an encode output stream into which the value will be encoded.
The first step is the creation of an encode output stream, an Asn1JsonOutputStream. There is a single constructor
which accepts a java.io.Writer. As usual, you may use a buffered writer, file writer, char array writer etc., or some
combination thereof.
The second step is the invocation of the encode method. The calling arguments were described earlier.
try {
// Step 1: Create an encode output stream, with UTF-8 character encoding
encodeStream = new Asn1JsonOutputStream (
new java.io.OutputStreamWriter( new java.io.FileOutputStream(filename), "UTF-8") );
118
Generated JSON Methods
personnelRecord.encode (encodeStream);
if (trace) {
System.out.println ("Encoding was successful");
}
}
catch (Exception e) {
System.out.println(e.getMessage());
e.printStackTrace();
}
finally {
try {
if (encodeStream != null) encodeStream.close ();
}
catch (Exception e) {}
}
In such cases, you can avoid some object creation and garbage collection by reusing objects you have already created.
The generated classes and the ASN1C runtime classes can often be viewed as reusable containers into which you can
simply assign new data.
To show an example of object reuse, suppose we were going to encode a series of octet strings. The ASN.1 type for
our data might be:
The generated Java class would contain public member variables for each of the octet strings:
The most efficient way to repopulate these variables within a loop would be simply to assign the data to be encoded
to the public value field of the Asn1OctetString objects. You do not need to create new Asn1OctetString or Data
objects each time.
Data data = new Data(null, null, null); // creates empty octet string objects
Asn1JsonOutputStream encodeStream = new Asn1JsonOutputStream (yourWriter);
for (;;) {
119
Generated JSON Methods
...
// populate octet strings (assume first, second, third are byte arrays
// populated by the above logic)
data.first.value = first;
data.second.value = second;
data.third.value = third;
// encode
try {
name.encode (encodeStream);
As was the case for encode methods, a decode method is only generated if it is required to alter the behavior of the
base class. The Java model is built on inheritance from a set of common run-time base classes. These run-time classes
contain default implementations of encode/decode methods that handle the encoding/decoding of the basic types.
The buffer argument is an Asn1JsonDecodeBuffer object which provides the message to be decoded. This must be
created and initialized before calling any decode method. See the description of this class in the Java Run-Time Classes
section for details on how this class is used.
As you can see, the decode method returns void; the data is decoded into the instance on which decode is invoked and
an exception is thrown if an error occurs. All ASN1C exceptions are derived from Asn1Exception. See the section on
exceptions for a complete list and description of the various ASN1C exceptions that can be thrown.
120
Generated JSON Methods
The first step is the creation of a decode buffer. An Asn1JsonDecodeBuffer can be constructed on either a java.io.Reader
or a java.io.InputStream. In the latter case, the character encoding can be detected (except on Java ME, where it is
assumed to be UTF-8). Thus, messages can easily be sourced from streams, files, or arrays.
The second step is to invoke the generated decode method. The calling arguments were described earlier.
The final step is to apply your application-specific processing to the data. All data is contained within public member
variables so access is quite easy.
try {
// Step 1: create a decode buffer for the message to be decoded.
// This example will use a file input stream to decode a message
// in a binary file.
A single decode buffer can be used to process a stream of messages (assuming all messages are using the same character
encoding). If the decode buffer is created using an input stream that contains a series of messages (for example, a file
containing multiple records, or a communications device), you can repeatedly invoke the JSON decode method on
the given message type.
Note that you can also use the same instance of your message type for repeated decoding, rather than creating a new
object and leaving the old one to be garbage collected. Nothing special needs to be done to do this. The generated
121
Generated JSON Methods
decode method will automatically call the internal init() method before decoding to make sure all items are reset to
their starting state.
In the example above, all that would need to be done to decode a series of personnel records is the inclusion of a loop
after the PersonnelRecord object was created in step 2:
for (;;) {
personnelRecord.decode (decodeBuffer);
if (trace) {
System.out.println ("Decode was successful");
personnelRecord.print (System.out, "personnelRecord", 0);
}
}
122
Chapter 14. Generated MDER Encode
Methods
Unlike what is done for other encoding rules, for MDER we provide stream-based encoding only. In order to encode
to a memory buffer, you simply use a java.io.ByteArrayOutputStream.
For each ASN.1 production defined in an ASN.1 source file, an encode method may be generated. This function will
convert a populated variable of the given type into an encoded ASN.1 message.
An encode method is only generated if it is required to alter the behavior of the base class. The Java model is built
on inheritance from a set of common run-time base classes. MDER supports only a few primitive types, and most of
these are subsets of ASN.1 built-in types. Therefore, in most cases, there is not an applicable MDER encode method
in the common run-time base class and so an encode method will be generated.
The generated classes for all constructed types (SEQUENCE, SEQUENCE OF, and CHOICE) will include generated
encode methods.
The buffer argument is an Asn1MderOutputStream object which receives the encoded message. This must be created
and initialized before calling any encode method. See the description of this class in the Java Run-Time Classes section
for details on how this class is used.
The useCachedLength argument indicates whether the encode method can rely on cached length information. In some
cases, MDER requires pre-calculation of the length of nested structures and this piece of information is needed twice
during encoding (once when encoding the containing structure and once when encoding the nested structure). The
generated types cache this information during encoding. User code should normally pass false for this argument.
The generated encoding methods will pass true to other encoding methods when appropriate.
As you can see, the encode methods return void; an exception is thrown if an error occurs. All ASN1C Java exceptions
are derived from Asn1Exception. See the section on exceptions for a complete list and description of the various
exceptions that can be thrown.
Constructors are provided for most generated types to allow direct population of the encapsulated member variable(s)
on initialization. The exception to this is for classes generated for SEQUENCE OF types. In that case, the constructors
only allow the size of an array to be specified – population of the array elements must be done manually.
123
Generated MDER Encode Methods
All of the base run-time classes except Asn1Null contain public member variables. In practically all cases there is a
single variable called value that is of the base type that needs to be populated. For example, the Asn1Integer base class
contains the following item:
X ::= INTEGER(0..255)
you may populate a variable of this type either using the constructor:
X x = new X (25);
X x = new X ();
x.value = 25;
The only primitive type that does not have a single member called value to represent its value is BIT STRING. The
Asn1BitString class also contains a second variable called numbits to specify the number of bits in the string.
1. Create an encode output stream into which the value will be encoded.
The first step is the creation of an encode output stream, an Asn1MderOutputStream. There is a single constructor
which accepts a java.io.OutputStream. As usual, you may use a buffered output stream, file output stream, byte array
output stream, etc., or some combination thereof.
The second step is the invocation of the encode method. The calling arguments were described earlier.
try {
// Step 1: Create an encode output stream.
encodeStream = new Asn1MderOutputStream (new FileOutputStream (filename));
124
Generated MDER Encode Methods
if (trace) {
System.out.println ("Encoding was successful");
}
}
catch (Exception e) {
System.out.println(e.getMessage());
e.printStackTrace();
}
finally {
try {
if (encodeStream != null) encodeStream.close ();
}
catch (Exception e) {}
}
In such cases, you can avoid some object creation and garbage collection by reusing objects you have already created.
The generated classes and the ASN1C runtime classes can often be viewed as reusable containers into which you can
simply assign new data.
To show an example of object reuse, suppose we were going to encode a series of octet strings. The ASN.1 type for
our data might be:
The generated Java class would contain public member variables for each of the octet strings:
The most efficient way to repopulate these variables within a loop would be simply to assign the data to be encoded
to the public value field of the Asn1OctetString objects. You do not need to create new Asn1OctetString or Data
objects each time.
Data data = new Data(null, null, null); // creates empty octet string objects
Asn1MderOutputStream encodeStream = new Asn1MderOutputStream (outputStream);
for (;;) {
125
Generated MDER Encode Methods
...
// populate octet strings (assume first, second, third are byte arrays
// populated by the above logic)
data.first.value = first;
data.second.value = second;
data.third.value = third;
// encode
try {
name.encode (encodeStream, /*useCachedLength=*/false);
126
Chapter 15. Generated MDER Decode
Methods
For each ASN.1 production defined in the ASN.1 source file, a decode method may be generated. This method will
decode an ASN.1 message into public member variables within the Java object.
As was the case for encode methods, a decode method is only generated if it is required to alter the behavior of the
base class. The Java model is built on inheritance from a set of common run-time base classes. MDER supports only
a few primitive types, and most of these are subsets of ASN.1 built-in types. Therefore, in most cases, there is not an
applicable MDER decode method in the common run-time base class and so a decode method will be generated.
The buffer argument is an Asn1MderDecodeBuffer object which provides the message to be decoded. This must be
created and initialized before calling any decode method. See the description of this class in the Java Run-Time Classes
section for details on how this class is used.
As you can see, the decode method returns void; the data is decoded into the instance on which decode is invoked and
an exception is thrown if an error occurs. All ASN1C exceptions are derived from Asn1Exception. See the section on
exceptions for a complete list and description of the various ASN1C exceptions that can be thrown.
The first step is the creation of a decode buffer. Asn1MderDecodeBuffer has a constructor that accepts messages stored
in a byte array. It also has a constructor that accepts a java.io.InputStream so that messages may be streamed from
various sources, such as from a file.
The second step is to invoke the generated decode method. The calling arguments were described earlier.
The final step is to apply your application-specific processing to the data. All data is contained within public member
variables so access is quite easy.
try {
127
Generated MDER Decode Methods
A single decode buffer can be used to process a stream of messages. If the decode buffer is created using an input
stream that contains a series of messages (for example, a file containing multiple records, or a communications device),
you can repeatedly invoke the MDER decode method on the given message type.
Note that you can also use the same instance of your message type for repeated decoding, rather than creating a new
object and leaving the old one to be garbage collected. Nothing special needs to be done to do this. The generated
decode method will automatically call the internal init() method before decoding to make sure all items are reset to
their starting state.
In the example above, all that would need to be done to decode a series of personnel records is the inclusion of a loop
after the PersonnelRecord object was created in step 2:
for (;;) {
personnelRecord.decode (decodeBuffer);
if (trace) {
System.out.println ("Decode was successful");
personnelRecord.print (System.out, "personnelRecord", 0);
}
}
128
Chapter 16. Table Constraint Processing
The ASN1C Java code generator can generate code to process ASN.1 table constraints as specified in the X.681 and
X.682 ASN.1 standards. This code is generated through the use of the -tables option. This instructs the compiler
to generate additional methods and tables to allow multi-level message types specified using table constraints to be
encoded or decoded with a single method call.
Special code is generated for the CLASS, Information Object, and Information Object Set items to create the table
necessary to for table constraint processing. Then additional encode and decode methods are generated that use these
tables to branch to the multiple message levels.
CLASS specification
NOTE: Class code generation is done only when -tables is specified.
This additional code is generated to support the processing required to verify table constraints, which is intended for
use only in compiler-generated code. Therefore, it is not necessary for the average user to understand the mappings
in order to use the product. The information presented here is informative only to provide a better understanding of
how the compiler handles table constraints.
The Java class generated to model an ASN.1 class contains member variables for each of the fields within the class.
To create an instance of this class, an information object is required to populate these variables with the values defined
in the ASN.1 information object specification.
Java code will be generated for each ASN.1 CLASS definition in a separate Java source file containing a Java class
corresponding to the ASN.1 CLASS definition. The name of the source file and class is of the following format:
<ClassName>.java
In this definition, <ClassName> would be replaced with the name of the ASN.1 CLASS for which this file is generated.
where:
<FieldName> is replaced with the name of the field.
129
Table Constraint Processing
<TypeName> is replaced with the generated runtime Java classname for the ASN.1 Type.
<ClassName> is replaced with the name of the information object class.
For a type field definition, an element with type Asn1Type is generated which is the base class for all types in the Java
runtime package. A type field can hold a value of any type.
Example
public ATTRIBUTE() {
Type = null;
id = null;
}
public ATTRIBUTE(
Asn1Type Type_,
Asn1ObjectIdentifier id_
) {
Type = Type_;
id = id_;
}
}
NOTE: If the ASN.1 type name is same as the ASN.1 class name (ignoring case) in a single module definition, then
the ASN.1 class name will be changed to following:
<ClassName>_CLASS
In this definition, <ClassName> would be replaced with the name of the ASN.1 CLASS and the literal token "_CLASS"
would be appended.
For example:
130
Table Constraint Processing
ASN1C will change the ATTRIBUTE class name to ATTRIBUTE_CLASS to avoid conflicts with the Attribute type.
This automated feature will help users to successfully compile the generated code without having to manually change
the name via a configuration file setting.
Additional Java classes are generated to create types for fields within the class definitions as follows:
1. New type assignments are created for TypeField type definitions as follows:
Here ClassName is replaced with name of the Class Assignment and FieldName is replaced with name of the field.
Type is the type definition in the ASN.1 CLASS's TypeField.
This type is used as a defined type in the information object definition for absent values of the TypeField. It is also
useful for the user to generate a value for a related OpenType definition in a table constraint.
2. New type assignments are created for ValueField or ValueSetField type definitions if the type is with a constraint
definition and/or the type is Sequence / Set / Choice / Sequenceof / SetOf definition.
Here ClassName is replaced with name of the Class Assignment and FieldName is replaced with name of the
ValueField or ValueSetField. Type is the type definition in The ASN.1 CLASS's ValueField or ValueSetField. This
type will appear as a defined type in the ASN.1 CLASS's ValueField or ValueSetField.
This new type assignment is used for compiler internal code generation purpose. It is not designed for use by the
end user.
3. New value assignments are created for ValueField default value definitions as follows:
Here ClassName is replaced with name of the Class Assignment and FieldName is replaced with name of the
ValueField. Value is the default value in the ASN.1 CLASS's ValueField & Type is the type in the ASN.1 CLASS's
ValueField.
This value is used as a defined value in the information object definition for an absent value of the field. This new
value assignment is used for compiler internal code generation purpose. It is not designed for use by the end user.
ABSTRACT-SYNTAX
The ASN.1 ABSTRACT-SYNTAX class is a useful class definition used to declare the top-level protocol data units
(PDU's) defined within a specification. The class is described using the following ASN.1 definition:
ASN1C is used to create a meta-definition for this structure. The definition is stored in the file Asn1AbstractSyntax.java
(or Asn1XerAbstractSyntax.java for XER). An object created from the resulting Java class is populated just like any
other compiler-generated structure for working with ASN.1 data.
131
Table Constraint Processing
TYPE-IDENTIFIER
The ASN.1 TYPE-IDENTIFIER class is a useful class definition for uniquely identifying typed data at runtime. The
class is described using the following ASN.1 definition:
The ASN.1 compiler is used to create a meta-definition for this structure. The definition is stored in the file
Asn1TypeIdentifier.java (or Asn1XerTypeIdentifier.java for XER). An object created from the resulting Java class is
populated just like any other compiler-generated structure for working with ASN.1 data.
Information Object
NOTE: Information Object code generation is only done when the -tables option is selected.
This additional code is generated to support the processing required to verify table constraints, which is intended for
use only in compiler-generated code. Therefore, it is not necessary for the average user to understand the mappings
in order to use the product. The information presented here is informative only to provide a better understanding of
how the compiler handles table constraints.
Information Object code will be generated in a Java source file with a special class to hold the values. The name of
the source file and class is of the following format:
_<ModuleName>Values.java
In this definition, <ModuleName> would be replaced with the name of the ASN.1 module in which the values are
defined.
For each Information Object defined within a specification, a Java constant is generated which is an instance of the
ASN.1 CLASS definition for the object. Each Information Object constant calls the Class constructor with the field
value specified in the ASN.1 information object definition.
If the ASN.1 CLASS field is optional and the field value is absent in the Information Object definition, then its cor-
responding member variable will be initialized to "null". If the ASN.1 CLASS field has a default value and its field
value is absent in the Information Object, then the generated code for the Information Object will set the Class field's
value to the default value.
ASN.1 definition:
For example, consider the following Information Object declaration for the above ATTRIBUTE class:
132
Table Constraint Processing
NOTE: The following new Type Assignment is created for each TypeField's type definition if the type is one of the
following ASN.1 built-in types: Sequence / Set / SequenceOf / SetOf / Choice / Constrained Type / Enumerated Type /
NamedInteger Type / NamedBitList Type / ParameterizedType:
Here ObjectName is replaced with name of the Object Assignment. If Object is defined in ObjectSet, then ObjectName
is replaced with the name of the ObjectSet Assignment. FieldName is replaced with name of this type field. Type is
the type definition in Object's typefield.
This type is used as Defined Type in the information object definition for type field. It is also useful for the user to
generate value for related OpenType definition in table constraint.
This additional code is generated to support the processing required to verify table constraints which is intended for
use only in compiler-generated code. Therefore, it is not necessary for the average user to understand the mappings
in order to use the product. The information presented here is informative only to provide a better understanding of
how the compiler handles table constraints.
Information Object code will be generated in a Java source file with a special class to hold the values. The name of
the source file and class is of the following format:
_<ModuleName>Values.java
In this definition, <ModuleName> would be replaced with the name of the ASN.1 module in which the Information
Object Sets are defined.
Each Information Object Set specification causes a Java constant to be generated containing an array of Information
Object values. Each object in the array is an instance of the equivalent Java class representing the corresponding ASN.1
information object
As of this writing, a static array is used to hold the objects, but this could be changed to something like a linked list
or hash.
ASN.1 definition:
For example, consider the following Information Object Set declaration for above ATTRIBUTE definition:
133
Table Constraint Processing
The ASN1C compiler is capable of generating code in one of two forms for information in an object specification:
1. Simple form: in this form, references to variable type fields within standard types are simply treated as open types
and an open type placeholder is inserted.
2. Table form: in this form, all of the classes, objects, and object sets within a specification result in the generation
of code for parsing and formatting the information field references within standard type structures.
The second form is selected by specifying the –tables command line option.
To better understand the support in this area, the individual components of Information Object specifications are
examined. We begin with the "CLASS" specification that provides a schema for Information Object definitions. A
sample class specification is as follows:
Users familiar with ASN.1 will recognize this as a simplified definition of the ROSE OPERATION MACRO using
the Information Object format. When a class specification such as this is parsed, information on its fields is maintained
in memory for later reference. In the simple form of code generation, the class definition itself does not result in the
generation of any corresponding Java code. It is only an abstract template that will be used to define new items later
on in the specification. In the table form, a Java container class is generated to hold the Information Object instances
of the ASN.1 CLASS.
Fields from within the class can be referenced in standard ASN.1 types. It is these types of references that the compiler
is mainly concerned with. These are typically "header" types that are used to add a common header to a variety of other
message body types. An example would be the following ASN.1 type definition for a ROSE invoke message header:
134
Table Constraint Processing
This is a very simple case that purposely omits a lot of additional information such as Information Object Set constraints
that are typically part of definitions such as this. The reason this information is not present is because we are just
interested in showing the items that the compiler is concerned with. We will use this type to demonstrate the simple
form of code generation. We will then add table constraints and discuss what changes when the –tables command
line options is used.
The opcode field within this definition is an example of a fixed type field reference. It is known as this because if you
go back to the original class specification, you will see that operationCode is defined to be of a specific type (namely
a choice between a local and global value). The generated typedef for this field will contain a reference to the type
from the class definition.
The argument field is an example of a variable type field. In this case, if you refer back to the class definition, you will
see that no type is provided. This means that this field can contain an instance of any encoded type (note: in practice,
table constraints can be used with Information Object Sets to limit the message types that can be placed in this field).
The generated typedef for this field contains an "open type" (Java Asn1OpenType class) reference to hold a previously
encoded component to be specified in the final message.
...
}
The following would be the procedure to add the Invoke header type to an ASN.1 message body:
3. Plug the bytes into the "data" argument of the open type constructor in the Invoke type variable.
In this case, the amount of code generated to support the information object references is minimal. The amount of
coding required by a user to encode or decode the variable type field elements, however, can be rather large. This
is a trade-off that exists between using the compiler generated table constraints solution (as we will see below) and
using the simple form.
135
Table Constraint Processing
The "{My-ops}" constraint on the opcode element specifies an information object set (not shown) that constrains the
element value to one of the values in the object set. The {My-ops}{@opcode} constraint on the argument element
goes a step further – it ties the type of the field to the type specified in the row that matches the given opcode value.
ASN1C generates an in-memory table for each of the items in the information object sets defined in a specification. In
the example above, a table would be generated for the My-ops information object set. The code generated for the type
would then use this table to verify that the given items in a structure that reference this table match the constraints.
The Java type generated for the SEQUENCE above when –tables is specified would be as follows:
...
}
This is almost identical to the type generated in the simple case. The difference is that ASN1Type is used instead for the
argument element instead of ASN1OpenType. This type is defined as the base class for all the generated ASN.1 types.
It holds the value to be encoded or decoded. The way a user Would use this to encode a value of this type is as follows:
1. Populate a variable of the type to be used as the argument to the invoke type.
Note that in this case, the intermediate type does not need to be manually encoded by the user. The generated encoder
has logic built-in to encode the complete message using the information in the generated tables.
Additional equals() method will be generated for Sequence, Set, Sequence Of, Set Of or Choice types if required
for table constraint processing. This method will be an implementation of Asn1Type.equals() virtual method. These
methods are used by the generated code to verify that data in a generated structure to be encoded (or data that has just
been decoded) matches the table constraint values.
An additional table constraint check method is also generated for each type that contains table constraints. These
functions have the following prototypes:
BER/DER:
PER:
The decode argument is used to decide if this method is to used for encoding or decoding. The aligned argument is
for PER and specified whether aligned or unaligned encoding/decoding is in effect.
136
Table Constraint Processing
The purpose of these methods is to verify that the fixed values within the table constraints are what they should be and
to encode or decode the open type fields using the encoder or decoder methods from the Asn1Type objects assigned to
the given table row. Calls to these functions are automatically built into the standard encode or decode functions for
the given type. They should be considered hidden functions not for use within an application that uses the API.
The checkTC method will have different logic for relative and simple table constraints. The logic to invoke this method
is as follows:
1. The table constraint key is searched in the object set array to find the class object for the data in the populated type
variable to be encoded.
2. If the key element value is NOT found and the table constraint object set is extensible, the checkTC method will
do no further processing (i.e. a value field match will not be performed). The user will have had to populate the
type field using an Asn1OpenType object in order for it to be decoded because the generated table contains no
information on how to encode the value.
3. If the key element value is found, the method will verify all fixed type values match what is defined in the key row
of the object set and will also verify that the type of any variable type fields matches the expected type.
4. If the key element value is not found in the table (or object set) and the objectset is NOT extensible, then a table
constraint violation exception will be thrown.
1. The checkTC method will verify that all of the fixed type values match what is defined in the table constraint object
set. If the element value does not exist in the table (or object set) and the object set is NOT extensible, then a table
constraint violation exception will be thrown.
After the checkTC method call, the normal encode logic is performed.
The normal decode logic is performed first to populate the standard and open type fields in the generated structure.
After that, the checkTC method is invoked to perform following table constraint checks:
1. The table constraint key is searched in the object set array to find the class object for the data in the populated type
variable to be encoded.
2. If the key element value is NOT found and the table constraint object set is extensible, the checkTC method will
do no further processing (i.e. a value field match will not be performed) and the variable type fields will be stored
as open types (i.e. as instances of Java Asn1OpenType classes). The user will be responsible for further decoding
of the open type value.
3. If the key element value is found, the checkTC method will verify all fixed type values match what is defined in the
key row of the object set and will fully decode all type fields according to the key row type and store the resulting
decoded type in the ASN1Type fields.
4. If the key element value is NOT found in the table (or object set) and the object set is NOT extensible, then a table
constraint violation exception will be thrown.
137
Table Constraint Processing
1. This function will verify all the fixed type values match what is defined in the table constraint object set. If an
element value does not exist in the table (or object set) and the object set is NOT extensible, then a table constraint
violation exception will be thrown.
2. Populate the value for this type and assign it to the open type member variable.
A complete example showing how to assign open type values when table constraint code is generated is as follows:
In the above example, the Invoke type contains a relative table constraint. Its element opcode refers to the ATTRIBUTE
class's id field and the argument element refers to ATTRIBUTE class's Type field. The opcode element is the index
element into the {SupportedAttributes} information object set. The argument element is an open type but its type must
match that specified at the location in the {SupportedAttributes} information object set indexed by opcode.
In this example, opcode can have only two possible values { 0 1 1 } or { 0 1 2 }. If the opcode value is { 0 1 1} then
argument must be a value of type VisibleString. If the opcode value is { 0 1 2 } then argument will have an INTEGER
value. Any other value of the opcode element will be a violation of the Table Constraint.
If the SupportedAttributes object set was extensible (in this example, it is not), then the argument element can be a
value of any type. In this case, if the user is using an index element value outside the object set, then the user will have
to encode the argument element as an Asn1OpenType.
138
Table Constraint Processing
The important thing to note is that not much changes from the normal procedure. The only significant difference is that
now the argument field can be directly populated with an instance of its target type. Without table constraint checking
logic, this value would have to have been first encoded and then placed in an Asn1OpenType container object.
139
Chapter 17. Generated Print Methods
The -print option causes print methods to be generated. These functions can be used to print the contents of variables
of generated types. A print method is generated in each of the generated Java source files.
The -prtToStr option causes ASN1C to generate print methods whose primary argument is a StringBuilder object. This
provides a somewhat higher-performance means for users to write data to a string (instead of using StringWriter or
another stream-based method).
and
The fundamental difference between them is the out argument. In the first method, out is a PrintWriter object, and in
the second method, it is a PrintStream object. In both cases, out specifies a stream to which output should be written.
For text output (such as this method performs), PrintWriter is preferred over PrintStream and will usually perform
better. However, the PrintStream form is provided for backwards compatibility and for writing to standard output (i.e.
System.out).
The varName argument is used to specify the top-level variable name of the item being printed. Normally, this would
be set to the same name as the variable declared in your program that holds the object being printed. For example,
if you declared a variable called personnelRecord to hold a PersonnelRecord object, the varName object would be
set to "personnelRecord".
The level argument is used to specify the indentation level for printing nested types. The user would always want to
set this to zero at the outer-level.
For example, the call to print the personnelRecord from the previous examples would be as follows:
personnelRecord {
name {
givenName = 'John'
initial = 'P'
familyName = 'Smith'
}
number = 51
title = 'Director'
dateOfHire = '19710917'
nameOfSpouse {
givenName = 'Mary'
initial = 'T'
familyName = 'Smith'
140
Generated Print Methods
}
children[0] {
name {
givenName = 'Ralph'
initial = 'T'
familyName = 'Smith'
}
dateOfBirth = '19571111'
}
children[1] {
name {
givenName = 'Susan'
initial = 'B'
familyName = 'Jones'
}
dateOfBirth = '19590717'
}
}
The method calls are made in much the same way as the calls detailed in the previous section, and users are recom-
mended to read them if questions persist. The output format is exactly the same as well.
Users should note that StringBuilder is unsynchronized, and so, too, are the generated print-to-string methods. Access
to the StringBuilder should be locked properly (or one StringBuilder per thread should be allocated) to ensure that
output is in order.
141
Chapter 18. Generated Copy Methods
When -copy is specified on the command line, ASN1C will generate overridden clone methods in Java code. These
methods perform a deep copy.
It is noted by many that writing a good clone() method is non-trivial, and we found this to be true. In gener-
al, users can expect clone to act as it is specified in the Java documentation. The sole exception to this is that
Asn1Enumerated instances are immutable and will return themselves rather than a new instance.
@Override
public Object clone()
throws CloneNotSupportedException
{
/* ... */
}
When users generate code compatible with the CLDC or Java Micro Edition, the @Override annotation is removed.
142
Chapter 19. Generated Compare Methods
The -compare command line option causes an equals method to be added to each generated class. The signature of
this method is as follows:
where ClassName is the name of the generated class to which the member function belongs. The method returns a
boolean result of true if the object instances are equal and false if not.
Note that for classes extended from the Asn1Choice class, no equals member function is generated. This is because
the Asn1Choice class already has an equals member function, which is inherited by classes extending the Asn1Choice
class.
143
Chapter 20. Generated Metadata Methods
In version 6.8, ASN1C adds the ability to generate methods to inspect the elements in a SEQUENCE or SET. Using
the -genmetadata option, users can generate the following methods:
These methods provide access to syntactic information present in the schema used during code generation: whether
the elements in a SEQUENCE or SET are required or optional, and what, if any, value range may be applied to the
elements. The following sections describe these methods in detail.
isRequired
The isRequired method has the following signature:
The method returns true when the element given by elemName is required to be present in the given SEQUENCE
or SET and false when it is not. In the event that the named element is not a member of the structure, the
Asn1InvalidElemException is thrown.
The following ASN.1, taken from a slightly-modified version of the Employee sample program, provides a good
example:
There are seven total elements: one is optional and the others are required. The generated code is straightforward:
144
Generated Metadata Methods
}
else if (elemName.equals("dateOfHire")) {
return true;
}
else if (elemName.equals("nameOfSpouse")) {
return true;
}
else if (elemName.equals("children")) {
return true;
}
getValueRange
The getValueRange method has the following signature:
The method returns true when the value range range has been set by the method and false when it has not been
set. No exceptions are thrown by this method.
The following ASN.1, taken from a slightly-modified version of the Employee sample program, provides a good
example:
145
Chapter 21. Generated Sample Programs
The –writer and -reader options cause writer and reader sample programs to be generated.
The writer program contains sample code to populate and encode an instance of ASN.1 data. The main purpose is to
provide a code template to users for writing code to populate objects. This is quite useful to users because generated
classes can become very complex as the ASN.1 schemas become more complex. The writer code also shows users
how to instantiate an encode buffer object and how to use encode functions. The writer program writes the encoded
data to a file. If the writer program is generated by using both -writer and -getset options, then the generated writer
program uses getset functions to populate data.
The reader program on the other hand reads the encoded data from a file. It shows users how to use a decode buffer
object to decode data and populate the corresponding class object. On successful decode, it prints the decoded data
to standard output.
146
Chapter 22. Build Scripts
Generating Command Line or Shell Scripts
The -genbuild option causes a build script to be generated. This script can be used to Java compile the generated
source files.
For Windows, the -w32 command line option should be specified along with -genbuild to generate a DOS batch file
(.bat). This file is named build.bat.
For Linux/UNIX, a shell script is generated. The name of this file is build.sh.
When a build script is generated, it is assumed that the ASN1C project exists within the ASN1C installation directory
tree. The generation logic tries to determine the root directory of the installation by traversing upward from the project
directory in an attempt to locate the java subdirectory which is assumed to be the installation root directory. If the
project is located outside of the ASN1C hierarchy, the user can set the OSROOTDIR environment variable to point
at the root directory.
If the root directory is located successfully, the generated build script will use that directory; however, if the compiler
fails to find the installation root directory, it will use @ROOT_DIR@ instead and print an error message. Users will
have to manually replace @ROOT_DIR@ with the actual compiler installation root directory. Also, for the -xer or -
xml option along with -genbuild, an XML parser is required. The compiler will try to locate a parser and use it if found.
However, if a parser is not found, then the compiler will use @XERCES_ROOT@ instead of the parser root directory.
An error message will be printed and the user will update the file accordingly.
It is necessary to add the asn1c runtime (asn1rt.jar) to some Maven repository. You can add asn1rt.jar to a local Maven
repository using the following command:
The generated pom.xml will include a dependency with the groupId and artifactId shown above. The version will be
based on the version number built into asn1c. You will need to ensure the version number you use to add the JAR to
the repository is compatible with the version number specified in the pom.xml dependency.
You should follow Maven's standard directory layout. To do this, you'll likely want to run asn1c using options similar
to the following:
-pkgpfx com.mycompany.abc
-o src\main\java\com\mycompany\abc -dirs -genmaven
147
Build Scripts
This ensures that package is rooted in the folder Maven expects and creates subfolders for each of the parts of the
package name.
If you do not follow the standard directly layout, you should use the -objdir option to specify a separate folder for the
location of your .class files. For example, you might use these options:
-pkgpfx com.mycompany.abc
-o src\com\mycompany\abc -objdir bin -dirs -genmaven
If you generate a writer or reader sample program, the generated pom.xml file will include executions for the sample
programs. They will be configured to run during the test phase. You can run the sample programs separately using
the following commands:
mvn exec:java@writer
mvn exec:java@reader
Only use -objdir with asn1c if your version of Gradle supports the java.outputDir property for a SourceSet in the Java
plugin. As of this writing, java.outputDir is incubating.
The generated file uses the Java Library plugin. This was done with the thought that asn1c-generated code would likely
be incorporated into a Java library.
A task, cleanGenSource, is generated for deleting the generated code. Task "clean" is made dependent upon this task.
Because of this, you will want to ensure that source code is generated into a separate folder from your own, handwritten
code. Otherwise, you will need to edit the task's configuration to exclude your handwritten code.
A task, asn1c, is generated for running asn1c. Task "compileJava" is made dependent on this task. If the output folder
(where code is generated) contains handwritten code, Gradle may see output files for this task as perpetually out-of-
date and actually execute this task for every build. In this case, you can edit the configuration of the task's output.files
to exclude your handwritten files.
If you opt to generate sample writer and reader programs, tasks for running these will be generated. Gradle will au-
tomatically make these tasks depend upon task "compileJava", since that task produces output to the classpath used
for the writer and reader programs.
148
Chapter 23. Event Handler and Exception
Handler Interfaces
The –events command line switch causes hooks for user-defined event and exception handlers to be inserted into the
generated Java decode methods. Event handlers fire when key message-processing events occur during the course of
parsing an ASN.1 message. What the event handler does is up to you. Event handlers are similar in functionality to
the Simple API for XML (SAX) that was described earlier for parsing XML messages.
The exception handler is invoked when the decoder would otherwise throw certain exceptions. You can choose to
ignore the exception or throw a different exception.
Event Handlers
How Event Handlers Work
Users of XML parsers are probably already quite familiar with the concepts of SAX. Significant events are defined
that occur during the parsing of a message. As a parser works through a message, these events are 'fired' as they occur
by invoking user defined callback functions. These callback functions are also known as event handler functions. A
diagram illustrating this parsing process is as follows:
The events are defined to be significant actions that occur during the parsing process. We will define the following
events that will be passed to the user when an ASN.1 message is parsed:
1. startElement – This event occurs when the parser moves into a new element. For example, if we have a SE-
QUENCE { a, b, c } construct (type names omitted), this event will fire when we begin parsing a, b, and c. The
name of the element is passed to the event handling callback function.
2. endElement – This event occurs when the parser leaves a given element space. Using the example above, these
would occur after the parsing of a, b, and c are complete. The name of the element is once again passed to the
event handling callback function.
3. characters method – This method is defined to pass all of the different types of primitive values that are encoun-
tered when parsing a message. The primitive values are passed out in a stringified form.
149
Event Handler and Exception Handler Interfaces
The start and end element methods are invoked when an element is parsed within a constructed type. The start method
is invoked as soon as the tag/length is parsed in a BER or DER message. The end method is invoked after the contents
of the field are processed. The signature of these methods is as follows:
The name argument is used pass the element name. The index argument is used for SEQUENCE OF/SET OF constructs
only. It is used to pass the index of the item in the array. This argument is set to –1 for all other constructs.
The characters method is used to pass out ASN.1 primitive data. This is a departure from the C++ event handler
methodology in which separate methods are defined for all of the different data types. This implementation is more
closely aligned with the standard SAX implementation for XML. The reason it is done this way in Java and not C+
+ is because it is much easier to stringify values. Since memory management is built-in to Java, it is easy to create a
string and pass it out. This is a problem in C++ because it becomes a performance issue if too many malloc's are done
and it also places a burden on the user to free the memory for the allocated strings.
The svalue argument contains the stringified value. The format of this value is ASN.1 value notation for the value
as defined in the X.680 standard. The typeCode argument contains an identifier that specifies the ASN.1 type of the
value. The identifier corresponds to the universal identifier values (the ID number in the universal tags) for each of
the primitive data types. The only exception to this rule is that the identifier 99 was added to represent an Open Type
construct. Constants for all of the identifier values are provided in the Asn1Type class. See the javadoc documentation
for this class for a list of the constants.
2. Objects of these classes must be created and registered prior to calling the generated decode method for a particular
type.
The best way to illustrate this procedure is through examples. We will first show a simple event handler application to
provide a customized formatted printout of the fields in a BER message. Then we will show a simple XML converter
class that will convert the data in a BER message to XML.
The format for the printout will be simple. Each element name will be printed followed by an equal sign (=) and an
open brace ({) and newline. The value will then be printed followed by another newline. Finally, a closing brace (})
followed by another newline will terminate the printing of the element. An indentation count will be maintained to
allow for a properly indented printout.
150
Event Handler and Exception Handler Interfaces
We will first create a class called PrintHandler that implements the Asn1NamedEventHandler interface and handles the
formatted printing of the data. The rule for the implementation of interfaces is that you must provide an implementation
for each of the methods listed. That is it. You can add as many additional methods, member variables, etc., that you like.
In this definition, we chose to add the mVarName and mIndentSpaces member variables to keep track of these items.
The user is free to add any type of member variables he or she wants. The only firm requirement in defining this class
is the implementation of the methods defined in the interface.
151
Event Handler and Exception Handler Interfaces
In this simplified implementation, we simply indent (this is another private method within the class) and print out the
name, equal sign, and opening brace. We then increment the indent level. Logic is also present to check the index
value to see if it is zero or greater. If it is, an array subscript is added to the element name.
The characters method simply indents and prints the stringified value:
Next, we need to create an object of the class and register it prior to invoking the decode method. In the Reader.java
program, the following lines do this:
The addEventHandler method defined in the Asn1DecodeBuffer base class is the mechanism used to do this. Note
that event handler objects can be stacked. Several can be registered before invoking the decode function. When this is
done, the entire list of event handler objects is iterated through and the appropriate event handling callback function
invoked whenever a defined event is encountered.
The implementation is now complete. The program can now be compiled and run. When this is done, the resulting
output is as follows:
employee = {
name = {
givenName = {
"John"
}
initial = {
"P"
}
familyName = {
"Smith"
}
}
...
This can certainly be improved. For one thing it can be changed to print primitive values out in a "name = value"
format (i.e., without the braces). But this should provide the general idea of how it is done.
152
Event Handler and Exception Handler Interfaces
It turns out that with event handlers, this conversion is fairly easy. As the handler events fire, all of the required
symbolic data is passed out to generate an XML document. The programmer is free to massage this data any way he
or she wants to comply with whatever DTD or XML Schema is in use.
The ToXML sample program demonstrates the conversion of ASN.1 data to XML using event handlers. The sample
is not intended to be a robust implementation – it is merely designed to provide guidance in how one would go about
doing this transformation.
The sample program can be found in the java/sample_ber/ToXML subdirectory within the ASN1C installation. The
complete class definition for the XMLHandler class is as follows:
153
Event Handler and Exception Handler Interfaces
}
}
This is very similar to the PrintHandler class defined earlier. The startElement method simply opens an XML element
block:
This illustrates the use of the typeCode argument for obtaining information on the ASN.1 type of the data. Note that
this is a simplified version of an XER formatting method. A true implementation would need to do some massaging
of the stringified data to fit the XER rules which, in general, do not follow the ASN.1 value formatting rules. The
implementation would also need some logic to check if the type wrapper should be output or not; it is not always
done in certain cases.
Finally note the constructor and finished method. The constructor prints out the outer-level wrapper tag. Since Java
does not have destructors, a finished method is defined to terminate this tag. This method must be called manually from
within the application program after the Java decode method. See the Reader.java program to see how this is done.
Object registration is done as before in the PrintHandler example. The only difference is that an object of the XML-
Handler class is created instead of the PrintHandler class.
When compiled and executed, the output from the Reader program looks like this:
<PersonnelRecord>
<name>
<givenName>
<IA5String>'John'</IA5String>
</givenName>
<initial>
<IA5String>'P'</IA5String>
</initial>
<familyName>
<IA5String>'Smith'</IA5String>
154
Event Handler and Exception Handler Interfaces
</familyName>
</name>
<number>
<INTEGER>51</INTEGER>
</number>
<title>
<IA5String>'Director'</IA5String>
</title>
<dateOfHire>
<IA5String>'19710917'</IA5String>
</dateOfHire>
<nameOfSpouse>
<givenName>
<IA5String>'Mary'</IA5String>
</givenName>
<initial>
<IA5String>'T'</IA5String>
</initial>
<familyName>
<IA5String>'Smith'</IA5String>
</familyName>
</nameOfSpouse>
<children>
<element>
<name>
<givenName>
<IA5String>'Ralph'</IA5String>
</givenName>
<initial>
<IA5String>'T'</IA5String>
</initial>
<familyName>
<IA5String>'Smith'</IA5String>
</familyName>
</name>
<dateOfBirth>
<IA5String>'19571111'</IA5String>
</dateOfBirth>
</element>
<element>
<name>
<givenName>
<IA5String>'Susan'</IA5String>
</givenName>
<initial>
<IA5String>'B'</IA5String>
</initial>
<familyName>
<IA5String>'Jones'</IA5String>
</familyName>
</name>
<dateOfBirth>
<IA5String>'19590717'</IA5String>
</dateOfBirth>
155
Event Handler and Exception Handler Interfaces
</element>
</children>
</PersonnelRecord>
Add an XML document header and you should be able to display this data in XML-enabled browser.
Exception Handlers
Exception handlers are only supported for BER decoding. BER is more amenable to error recovery than some other
encoding rules.
• Implement the Asn1BerExceptionHandler interface. In the example below, you can see the signature of the
only method in this interface.
• Instantiate an instance of your exception handler class and set it as the exception handler, using
Asn1BerDecodeBuffer.setExceptionHandler().
The following example is taken from the reader program of the java/sample_ber/ErrorHandlersample. In
that sample, we illustrate ignoring all of the exceptions that can be ignored. You can see that some exceptions requ-
uire skipping a TLV (tag-length-value), while others don't require any special action. The runtime documentation for
Asn1BerExceptionHandler documents what is required to handle each type of exception. Exceptions that are
not ignored are simply returned by the handler; if we had wanted to, the handler could have constructed some other
exception and returned that, instead.
156
Event Handler and Exception Handler Interfaces
return e;
}
}
decodeBuffer.setExceptionHandler(new Handler());
157
Chapter 24. IMPORT/EXPORT of Types
ASN1C allows productions to be shared between different modules through the ASN.1 IMPORT/EXPORT mecha-
nism. The compiler parses but ignores the EXPORTS declaration within a module. As far as it is concerned, any type
defined within a module is available for import by another module.
When ASN1C sees an IMPORT statement, it first checks its list of loaded modules to see if the module has already
been loaded into memory. If not, it will attempt to find and parse another source file containing the module. The logic
for locating the source file is as follows:
1. The configuration file (if specified) is checked for a <sourceFile> element containing the name of the source file
for the module.
2. If this element is not present, the compiler looks for a file with the name <ModuleName>.asn where module name
is the name of the module specified in the IMPORT statement.
In both cases, the –I command line option can be used to tell the compiler where to look for the files.
The other way of specifying multiple modules is to include them all within a single ASN.1 source file. It is possible to
have an ASN.1 source file containing multiple module definitions in which modules IMPORT definitions from other
modules. An example of this would be the following:
A ::= B
END
B ::= INTEGER
END
This entire fragment of code would be present in a single ASN.1 source file.
158
Chapter 25. Compact Code Generation
The -compact command line switch can be used to reduce the amount of source code generated for a given ASN.1
specification. This is done by generating the code for simple definitions inline within structured type definitions instead
of creating separate classes.
In normal mode, the compiler would generate three classes for these productions: one corresponding to X, Y, and Z
respectively. But in compact mode, it is recognized that a user would normally not be interested in encoding or decoding
X and Y on their own. They would primarily be interested in encoding or decoding the more complex structured
types (i.e. the PDU's) that make up fully formed messages. Taking this into account, when –compact is specified, the
compiler will not generate separate classes for X and Y in the above definition. Instead, it will include only the base
types for X and Y in the generated code for the SEQUENCE Z. All logic to handle the tags and constraints will be
built directly into the Z encode and decode methods.
So the result will be only a single class generated (Z) that will contain an Asn1Integer object to represent X and an
Asn1OctetString object to represent Y. The logic to process the application tags and the size constraint on the octet
string will be generated inline in the encode and decode methods in Z.
159
Chapter 26. ROSE and SNMP Macro
Support
The ASN1C compiler has a special processing mode that contains extensions to handle items in the older 1990 version
of ASN.1 (i.e. the now deprecated X.208 and X.209 standards). This mode is activated by using the -asnstd x208
commandline option.
Although the X.208 and X.209 standards are no longer supported by the ITU-T, they are still in use today. This version
of ASN1C contains logic to parse some common MACRO definitions that are still in widespread use despite the fact
that MACRO syntax was retired with this version of the standard. The types of MACRO definitions that are supported
are ROSE OPERATION and ERROR and SNMP OBJECT-TYPE.
The definition of the ROSE OPERATION MACRO that is built into the ASN1C90 version of the compiler is as
follows:
END
This MACRO does not need to be defined in the ASN.1 specification to be parsed. In fact, any attempt to redefine this
MACRO will be ignored. Its definition is hard-coded into the compiler.
What the compiler does with this definition is uses it to parse types and values out of OPERATION definitions. An
example of an OPERATION definition is as follows:
login OPERATION
160
ROSE and SNMP Macro Support
In this case, there are two embedded types (an ARGUMENT type and a RESULT type) and an integer value (1) that
identifies the OPERATION. There are also error definitions.
The ASN1C compiler generates two types of items for the OPERATION:
1. It extracts the type definitions from within the OPERATION definitions and generates equivalent Java classes and
encoders/decoders, and
2. It generates value constants for the value associated with the OPERATION (i.e., the value to the right of the '::='
in the definition).
The compiler does not generate any structures or code related to the OPERATION itself (for example, code to encode
the body and header in a single step). The reason is because of the multi-layered nature of the protocol. It is assumed
that the user of such a protocol would be most interested in doing the processing in multiple stages, hence no single
function or structure is generated.
Therefore, to encode the login example the user would do the following:
1. At the application layer, the Login_ARGUMENT structure would be populated with the username and password
to be encoded.
2. The encode function for Login_ARGUMENT would be called and the resulting message pointer and length would
be passed down to the next layer (the ROSE layer).
3. At the ROSE layer, the Invoke structure would be populated with the OPERATION value, invoke identifier, and
other header parameters. The open type object used to hold the encoded parameter value from step 2 is populated
by creating an Asn1OpenType object using the length of the encoded component.
4. The encode function for Invoke would be called resulting in a fully encoded ROSE Invoke message ready for
transfer across the communications link.
On the decode side, the process would be reversed with the message flowing up the stack:
1. At the ROSE layer, the header would be decoded producing information on the OPERATION type (based on the
MACRO definition) and message type (Invoke, Result, etc..). The invoke identifier would also be available for use
in session management. In our example, we would know at this point that we got a login invoke request.
161
ROSE and SNMP Macro Support
2. Based on the information from step 1, the ROSE layer would know that the Open Type field contains a pointer
and length to an encoded Login_ARGUMENT component. It would then route this information to the appropriate
processor within the Application Layer for handling this type of message.
3. The Application Layer would call the specific decoder associated with the Login_ARGUMENT. It would then have
available to it the username/password the user is logging in with. It could then do whatever application-specific
processing is required with this information (database lookup, etc.).
4. Finally, the Application Layer would begin the encoding process again in order to send back a Result or Error
message to the Login Request.
The login OPERATION also contains references to ERROR definitions. These are defined using a separate MACRO
that is built into the compiler. The definition of this MACRO is as follows:
END
In this definition, an error is assigned an identifying number as well as on optional parameter type to hold parameters
associated with the error. An example of a reference to this MACRO for the authenticationFailure error in the login
operation defined earlier would be as follows:
applicationError ERROR
PARAMETER SEQUENCE {
errorText IA5String
}
::= 1
The ASN1C90 compiler will generate a type definition for the error parameter and a value constant for the error value.
The format of the name of the type generated will be "<name>_PARAMETER" where <name> is the ERROR name
162
ROSE and SNMP Macro Support
(applicationError in this case) with the first letter set to uppercase. The name of the value will simply be the ERROR
name.
SNMP OBJECT-TYPE
The SNMP OBJECT-TYPE MACRO is one of several MACROs used in Management Information Base (MIB) de-
finitions. It is the only MACRO of interest to ASN1C because it is the one that specifies the object identifiers and
data that are contained in the MIB.
The version of the MACRO currently supported by this version of ASN1C can be found in the SMI Version 2 RFC
(RFC 2578). The compiler generates code for two of the items specified in this MACRO definition:
1. The ASN.1 type that is specified using the SYNTAX command, and
For an example of the generated code, we can look at the following definition from the UDP MIB:
udpInDatagrams OBJECT-TYPE
SYNTAX Counter32
MAX-ACCESS read-only
STATUS current
DESCRIPTION
"The total number of UDP datagrams delivered to UDP users."
::= { udp 1 }
In this case, a type definition is generated for the SYNTAX element and an Object Identifier value is generated for
the entire item. The name used for the type definition is "<name>_SYNTAX" where <name> would be replaced
with the OBJECT-TYPE name (i.e., udpInDatagrams). The name used for the Object Identifier value constant is the
OBJECTTYPE name. So for the above definitions, the following two Java items would be generated:
1. A "udpInDatagrams_SYNTAX.java" file. This would contain the udpInDatagrams_SYNTAX class definition, and
163
Chapter 27. Other Java-based Platforms
Java Micro Edition Support
If your ASN1C license purchase includes support for BER or PER encoding, it may also include support for Java
Micro Edition. You may find the following additional JAR files in your installation's java folder:
• asn1rt-jme-px.jar: ASN1C Java ME runtime with support for PER and XER/XML
When using ASN1C generated code on a Java Micro Edition platform, observe the following:
• Use option -cldc instead of -java. The generated code should be compatible with CLDC 1.1.
• Do NOT use any of the following ASN1C compiler options: -print -events -trace. These are not supported with
CLDC 1.1.
• For XER or XML, the optional package for JSR-280 is required. This provides the SAX or StAX XML parser used
by the generated code.
• For XER or XML the following tasks are more limited or not supported in the Java Micro Edition environment:
• Working with real numbers (e.g., 3.14). For example, the encoding of most real values is not supported because
the Java infrastructure needed to do such encodings simply isn't present in the Java Micro Edition environment.
• Your client code may use either one of two approaches for referring to classes such as ArrayList, which are not
present in CLDC 1.1:
1. explicitly import objsys.asn1j.runtime.ArrayList. Your code will only be compatible with our Java ME support
jar.
2. import java.util.* and objsys.asn1j.runtime.*. You code may be used in Java SE or Java ME environment. You
must make sure that only one class named ArrayList is available on your classpath when compiling. If compiling
for Java SE, do not include the ASN1C Java ME runtime JAR. If compiling for Java ME, use the CLDC 1.1 JAR
(from the Java ME SDK) in your bootclasspath and include the ASN1C Java ME runtime JAR in your classpath.
• Using the same generated code on both Java SE and Java ME CLDC 1.1 is not supported. Compile your ASN.1
specification once for Java SE and once for Java ME.
• Note that we have not tested using Java ME Connected Device Configuration (CDC) rather than CLDC.
Android
ASN1C supports the Android platform. The ASN1C runtime must be deployed on Android just as it must be deployed
in a normal JSE environment. However, the runtime's Java bytecode must first be converted into Android (Dalvik VM)
164
Other Java-based Platforms
bytecode. The easiest method is to use the Android ADT Eclipse Plug-in and reference the ASN1C Java runtime in
the same way as you would for a normal Java application. The Android developer tools provide the ability to convert
the Java bytecode that is located in any referenced JAR files. The ADT plug-in handles this automatically for you. If
you choose not to use the plug-in, Android's command-line tools can provide the same functionality.
For use with Android, the ASN1C Java runtime should be either an unexpired, evaluation runtime or else an unlimited
runtime.
Your installation contains a sample Android project in java/sample_ber/AndroidSample. Note that while this sample
is located in the sample_ber folder, it is really a catch-all project and it is not strictly a BER-only sample.
Our Java runtime is obfuscated, which means it can have some unusual, but legal, bytecode produced by the obfuscator.
Sometimes, a particular JVM (or, in this case, the Dalvik VM) doesn’t like bytecode that an obfuscator produces. We
first found this was a problem with our unlimited runtimes and so we changed our obfuscation settings. If you have
version 6.4.2 (or later) of ASN1C, and are using the unlimited runtime, you should not encounter verification errors.
Using the unlimited runtime would be the typical case for use with Android (per-host-licensing not really being an
option). If, however, you are working with the evaluation runtime, you should be sure to have ASN1C version 6.4.3
(or later). Note that we just discovered that the verification errors persisted in our evaluation runtime while in the
midst of packaging and releasing our 6.4.3 kits. As a result, we repackaged those kits. If you have an ASN1C 6.4.3
evaluation kit, you should not encounter verification errors on Android. If you do, and you were an early adopter of
6.4.3, you may just need to re-download the evaluation kit. This is likely the case if your runtime JAR file is dated
before August 12, 2011.
165