One of the most common need is to create some sort of analysis tool
for the existing ASN.1 data files. Let's build a converter for existing
Rectangle binary files between BER, OER, PER, and XER (XML).
Let's take the following ASN.1 example%
\footnote{Part \ref{par:ASN.1-Basics} provides a quick reference
on the ASN.1 notation.}:
\begin{enumerate}
\item Create a file named \textbf{rectangle.asn} with the following contents:
\begin{asn}
RectangleModule DEFINITIONS ::= BEGIN
Rectangle ::= SEQUENCE {
height INTEGER, -- Height of the rectangle
width INTEGER -- Width of the rectangle
height INTEGER,
width INTEGER
}
END
\end{asn}
The asn1c compiler reads this ASN.1 definition and produce the following
C type:
\begin{codesample}
typedef struct Rectangle_s {
long height;
long width;
} Rectangle_t;
\end{codesample}
The asn1c compiler also creates the code for converting this structure into
platform-independent wire representation and the decoder
of such wire representation back into local, machine-specific type.
These encoders and decoders are also called serializers and deserializers,
marshallers and unmarshallers, or codecs.
\section{Quick start with asn1c}
\item Compile it into the set of .c and .h files using \cmd{asn1c} compiler:
After building and installing the compiler, the \cmd{asn1c}
may be used to compile the ASN.1 modules%
\footnote{This is probably \textbf{not} what you want to try out right now. Read through the rest of this chapter and check the Section~\ref{sec:Command-line-options}
to find out about \textbf{-P} and \textbf{-R} options.%
}:
\begin{bash}
asn1c %\emph{<modules.asn>}%
\end{bash}
If several ASN.1 modules contain interdependencies, all of the files
must be specified altogether:
\begin{bash}
asn1c %\emph{<module1.asn> <module2.asn> ...}%
\end{bash}
The compiler \textbf{-E} and \textbf{-EF} options are used for testing
the parser and the semantic fixer, respectively. These options will
instruct the compiler to dump out the parsed (and fixed, if \textbf{-F}
is involved) ASN.1 specification as it was understood
by the compiler. It might be useful to check whether a particular
syntactic construct is properly supported by the compiler.
The \cmd{asn1c} compiler produces a number of files:
\begin{itemize}
\item A set of .c and .h files for each type defined
in the ASN.1 specification. These files will be named similarly to
the ASN.1 types (\textbf{Rectangle.c} and \textbf{Rectangle.h} for the
RectangleModule ASN.1 module defined in the beginning of this document).
\item A set of helper .c and .h files which contain the generic encoders,
decoders and other useful routines.
Sometimes they are referred to by the term \emph{skeletons}.
There will be quite a few of them, some
of them are not even always necessary, but the overall amount of code
after compilation will be rather small anyway.
\item A \textbf{Makefile.am.libasncodecs} file which explicitly lists all the
generated files.
This makefile can be used on its own to build the just the codec library.
\item A \textbf{converter-example.c} file containing the \emph{int main()} function with a fully functioning encoder and data format converter. It can convert a given PDU between BER, XER and possibly OER and PER (if \cmd{-gen-OER} or \cmd{-gen-PER} options to \cmd{asn1c} were in effect). At some point you will want to replace this file with your own file containing the \emph{int main()} function.
\item A \textbf{Makefile.am.example} file which binds together
\textbf{Makefile.am.libasncodecs} and \textbf{converter-example.c}
to build a versatile converter and debugger for your data formats.
\end{itemize}
It is possible to compile everything with just a couple of instructions:
\begin{bash}
asn1c -pdu=%\emph{Rectangle}% *.asn
make -f Makefile.am.example # If you use `make`
\end{bash}
or
\begin{bash}
asn1c *.asn
cc -I. -DPDU=%\emph{Rectangle}% -o rectangle.exe *.c # ... or like this
./converter-example -h
\end{bash}
Refer to the Chapter \ref{cha:Step-by-step-examples} for a sample
\emph{int main()} function if you want some custom logic and not satisfied
with the supplied \emph{converter-example.c}.
\end{enumerate}
\clearpage{}
\section{\label{sec:Command-line-options}Command line options}
\section{A “Rectangle” Encoder}
The following table summarizes the \cmd{asn1c} command line options.
This example will help you create a simple BER and XER encoder of
a ``Rectangle'' type used throughout this document.
\begin{enumerate}
\item Create a file named \textbf{rectangle.asn} with the following contents:
{\ttfamily -E}&{\small Stop after the parsing stage and print the reconstructed ASN.1
specification code to the standard output.}\\
{\ttfamily -F}&{\small Used together with \texttt{-E}, instructs the compiler to stop after
the ASN.1 syntax tree fixing stage and dump the reconstructed ASN.1
specification to the standard output.}\\
{\ttfamily -P}&{\small Dump the compiled output to the standard output instead of
creating the target language files on disk.}\\
{\ttfamily -R}&{\small Restrict the compiler to generate only the ASN.1 tables, omitting the usual support code.}\\
{\ttfamily -S~\emph{<directory>}}&{\small Use the specified directory with ASN.1 skeleton files.}\\
{\ttfamily -X}&{\small Generate the XML DTD for the specified ASN.1 modules.}\\\\
\textbf{Warning Options}&\textbf{Description}\\
\midrule
{\ttfamily -Werror}&{\small Treat warnings as errors; abort if any warning is produced.}\\
{\ttfamily -Wdebug-parser}&{\small Enable the parser debugging during the ASN.1 parsing stage.}\\
{\ttfamily -Wdebug-lexer}&{\small Enable the lexer debugging during the ASN.1 parsing stage.}\\
{\ttfamily -Wdebug-fixer}&{\small Enable the ASN.1 syntax tree fixer debugging during the fixing stage.}\\
{\ttfamily -Wdebug-compiler}&{\small Enable debugging during the actual compile time.}\\\\
\textbf{Language Options}&\textbf{Description}\\
\midrule
{\ttfamily -fbless-SIZE}&{\small Allow SIZE() constraint for INTEGER, ENUMERATED, and other types for which this constraint is normally prohibited by the standard.
This is a violation of an ASN.1 standard and compiler may fail to produce the meaningful code.}\\
{\ttfamily -fcompound-names}&{\small Use complex names for C structures. Using complex names prevents
name clashes in case the module reuses the same identifiers in multiple
contexts.}\\
{\ttfamily -findirect-choice}&{\small When generating code for a CHOICE type, compile the CHOICE
members as indirect pointers instead of declaring them inline. Consider
using this option together with \texttt{-fno-include-deps}
to prevent circular references.}\\
{\ttfamily -fincludes-quoted}&{\small Generate \#include lines in "double" instead of <angle> quotes.}\\
{\ttfamily -fknown-extern-type=\emph{<name>}}&{\small Pretend the specified type is known. The compiler will assume
the target language source files for the given type have been provided
manually. }\\
{\ttfamily -fline-refs}&{\small Include ASN.1 module's line numbers in generated code comments.}\\
{\ttfamily -fno-constraints}&{\small Do not generate ASN.1 subtype constraint checking code. This
may produce a shorter executable.}\\
{\ttfamily -fno-include-deps}&{\small Do not generate courtesy \#include lines for non-critical dependencies.}\\
{\ttfamily -funnamed-unions}&{\small Enable unnamed unions in the definitions of target language's structures.}\\
{\ttfamily -fwide-types}&{\small Use the wide integer types (INTEGER\_t, REAL\_t) instead of machine's native data types (long, double). }\\\\
{\ttfamily -gen-OER}&{\small Generate the Octet Encoding Rules (OER) support code.}\\
{\ttfamily -gen-PER}&{\small Generate the Packed Encoding Rules (PER) support code.}\\
{\ttfamily -pdu=\{\textbf{all}|\textbf{auto}|\emph{Type}\}}&{\small Create a PDU table for specified types, or discover the Protocol Data Units automatically.
In case of \texttt{-pdu=\textbf{all}}, all ASN.1 types defined in all modules wil form a PDU table. In case of \texttt{-pdu=\textbf{auto}}, all types not referenced by any other type will form a PDU table. If \texttt{\emph{Type}} is an ASN.1 type identifier, it is added to a PDU table. The last form may be specified multiple times.}\\\\
\textbf{Output Options}&\textbf{Description}\\
\midrule
{\ttfamily -print-class-matrix}&{\small When \texttt{-EF} options are given, this option instructs the compiler to print out the collected Information Object Class matrix.}\\
{\ttfamily -print-constraints}&{\small With \texttt{-EF}, this option instructs the compiler
to explain its internal understanding of subtype constraints.}\\
This code defines a \emph{rect} pointer which points to the Rectangle\_t
structure which needs to be freed. The second line uses a generic
\code{ASN\_STRUCT\_FREE()} macro which invokes the memory deallocation routine
created specifically for this Rectangle\_t structure.
The \emph{asn\_DEF\_Rectangle} is the type descriptor which holds
a collection of routines and operations defined for the Rectangle\_t structure.
/* Allocate the Rectangle_t */
rectangle = calloc(1, sizeof(Rectangle_t)); /* not malloc! */
if(!rectangle) {
perror("calloc() failed");
exit(1);
}
The following member functions of the asn\_DEF\_Rectangle type descriptor
are of interest:
\begin{description}
\item [{ber\_decoder}] This is the generic \emph{restartable}%
\footnote{Restartability mean that if the decoder encounters the end of the buffer it may be invoked again with the rest of the
buffer to continue decoding.}
BER decoder (Basic Encoding Rules). This decoder would create and/or
fill the target structure for you. See Section~\ref{sub:Decoding-BER}.
\item [{der\_encoder}] This is the generic DER encoder (Distinguished Encoding
Rules). This encoder will take the target structure and encode it
into a series of bytes. See Section~\ref{sub:Encoding-DER}. NOTE:
DER encoding is a subset of BER. Any BER decoder should be able to
handle any DER input.
\item [{oer\_decoder}] This is the OER (Octet Encoding Rules) decoder.
\item [{oer\_encoder}] This is the Canonical OER encoder. This encoder
will take the target structure and encode it into a series of bytes compatible
with all BASIC-OER and CANONICAL-OER decoders.
\item [{uper\_decoder}] This is the Unaligned PER decoder.
\item [{uper\_encoder}] This is the Unaligned Basic PER encoder. This encoder
will take the target structure and encode it into a series of bytes.
\item [{xer\_decoder}] This is the generic XER decoder. It takes both BASIC-XER
or CANONICAL-XER encodings and deserializes the data into a local,
machine-dependent representation. See Section~\ref{sub:Decoding-XER}.
\item [{xer\_encoder}] This is the XER encoder (XML Encoding Rules). This
encoder will take the target structure and represent it as an XML
(text) document using either BASIC-XER or CANONICAL-XER encoding rules.
See Section~\ref{sub:Encoding-XER}.
\item [{check\_constraints}] Check that the contents of the target structure
are semantically valid and constrained to appropriate implicit or
explicit subtype constraints. See Section~\ref{sub:Validating-the-target}.
\item [{print\_struct}] This function convert the contents of the passed
target structure into human readable form. This form is not formal
and cannot be converted back into the structure, but it may turn out
to be useful for debugging or quick-n-dirty printing. See Section~\ref{sub:Printing-the-target}.
\item [{free\_struct}] This is a generic disposal which frees the target
structure. See Section~\ref{sub:Freeing-the-target}.
\end{description}
Each of the above function takes the type descriptor (\emph{asn\_DEF\_\ldots{}})
and the target structure (\emph{rect}, as in the example above).
\subsection{\label{sub:Generic-Encoding}Generic encoders and decoders}
/* Initialize the Rectangle members */
rectangle->height = 42; /* any random value */
rectangle->width = 23; /* any random value */
Before we start describing specific encoders and decoders, let's step back
a little and check out a simple high level way.
/* BER encode the data if filename is given */
if(ac < 2) {
fprintf(stderr, "Specify filename for BER output\n");
} else {
const char *filename = av[1];
FILE *fp = fopen(filename, "wb"); /* for BER output */
if(!fp) {
perror(filename);
exit(1);
}
The asn1c runtime supplies (see \emph{asn\_application.h}) two sets of high level functions, \emph{asn\_encode*} and \emph{asn\_decode*}, which take a transfer syntax selector as the argument. The transfer syntax selector is defined as this:
Rectangle_t *%$\underbracket{\textrm{\listingfont rect = 0}}$%; /* %\textbf{\color{red}Note this 0\footnote{Forgetting to properly initialize the pointer to a destination structure is a major source of support requests.}!}% */
Rectangle_t *%$\underbracket{\textrm{\listingfont rectangle = 0}}$%; /* Type to decode. %\textbf{\color{red}Note this 0\footnote{Forgetting to properly initialize the pointer to a destination structure is a major source of support requests.}!}% */
Rectangle_t *%$\underbracket{\textrm{\listingfont rect = 0}}$%; /* %\textbf{\color{red}Note this 0\footnote{Forgetting to properly initialize the pointer to a destination structure is a major source of support requests.}!}% */
\begin{bash}
cc -I. -o %\textbf{\emph{rdecode}} \emph{*.c}%
\end{bash}
\item Done. You have just created the BER decoder of a Rectangle type,
Rectangle_t *%$\underbracket{\textrm{\listingfont rect = 0}}$%; /* %\textbf{\color{red}Note this 0\footnote{Forgetting to properly initialize the pointer to a destination structure is a major source of support requests.}!}% */
\subsection{\label{sub:Printing-the-target}Printing the target structure}
The \cmd{asn1c} compiler produces a number of files:
\begin{itemize}
\item A set of .c and .h files for each type defined
in the ASN.1 specification. These files will be named similarly to
the ASN.1 types (\textbf{Rectangle.c} and \textbf{Rectangle.h} for the
RectangleModule ASN.1 module defined in the beginning of this document).
\item A set of helper .c and .h files which contain the generic encoders,
decoders and other useful routines.
Sometimes they are referred to by the term \emph{skeletons}.
There will be quite a few of them, some
of them are not even always necessary, but the overall amount of code
after compilation will be rather small anyway.
\item A \textbf{Makefile.am.libasncodecs} file which explicitly lists all the
generated files.
This makefile can be used on its own to build the just the codec library.
\item A \textbf{converter-example.c} file containing the \emph{int main()} function with a fully functioning encoder and data format converter. It can convert a given PDU between BER, XER and possibly OER and PER (if \cmd{-gen-OER} or \cmd{-gen-PER} options to \cmd{asn1c} were in effect). At some point you will want to replace this file with your own file containing the \emph{int main()} function.
\item A \textbf{Makefile.am.example} file which binds together
\textbf{Makefile.am.libasncodecs} and \textbf{converter-example.c}
to build a versatile converter and debugger for your data formats.
\end{itemize}
It is possible to compile everything with just a couple of instructions:
\begin{bash}
asn1c -pdu=%\emph{Rectangle}% *.asn
make -f Makefile.am.example # If you use `make`
\end{bash}
or
\begin{bash}
asn1c *.asn
cc -I. -DPDU=%\emph{Rectangle}% -o rectangle.exe *.c # ... or like this
\end{bash}
Refer to the \fref{chap:Quick-start-examples} for a sample
\emph{int main()} function if you want some custom logic and not satisfied
with the supplied \emph{converter-example.c}.
There are two ways to print the target structure: either invoke the
print\_struct member of the ASN.1 type descriptor, or using the asn\_fprint()
function, which is a simpler wrapper of the former:
\begin{codesample}
asn_fprint(stdout, &asn_DEF_Rectangle, rect);
\end{codesample}
Look into constr\_TYPE.h for the precise definition of asn\_fprint()
and related types.
\clearpage{}
\section{\label{sec:Command-line-options}Command line options}
Another practical alternative to this custom format printing would
be to invoke XER encoder. The default BASIC-XER encoder performs reasonable
formatting for the output to be useful and human readable. To invoke
the XER decoder in a manner similar to asn\_fprint(), use the xer\_fprint()
call:
\begin{codesample}
xer_fprint(stdout, &asn_DEF_Rectangle, rect);
\end{codesample}
See Section~\ref{sub:Encoding-XER} for XML-related details.
The following table summarizes the \cmd{asn1c} command line options.
{\ttfamily -E}&{\small Stop after the parsing stage and print the reconstructed ASN.1
specification code to the standard output.}\\
{\ttfamily -F}&{\small Used together with \texttt{-E}, instructs the compiler to stop after
the ASN.1 syntax tree fixing stage and dump the reconstructed ASN.1
specification to the standard output.}\\
{\ttfamily -P}&{\small Dump the compiled output to the standard output instead of
creating the target language files on disk.}\\
{\ttfamily -R}&{\small Restrict the compiler to generate only the ASN.1 tables, omitting the usual support code.}\\
{\ttfamily -S~\emph{<directory>}}&{\small Use the specified directory with ASN.1 skeleton files.}\\
{\ttfamily -X}&{\small Generate the XML DTD for the specified ASN.1 modules.}\\\\
\textbf{Warning Options}&\textbf{Description}\\
\midrule
{\ttfamily -Werror}&{\small Treat warnings as errors; abort if any warning is produced.}\\
{\ttfamily -Wdebug-parser}&{\small Enable the parser debugging during the ASN.1 parsing stage.}\\
{\ttfamily -Wdebug-lexer}&{\small Enable the lexer debugging during the ASN.1 parsing stage.}\\
{\ttfamily -Wdebug-fixer}&{\small Enable the ASN.1 syntax tree fixer debugging during the fixing stage.}\\
{\ttfamily -Wdebug-compiler}&{\small Enable debugging during the actual compile time.}\\\\
\textbf{Language Options}&\textbf{Description}\\
\midrule
{\ttfamily -fbless-SIZE}&{\small Allow SIZE() constraint for INTEGER, ENUMERATED, and other types for which this constraint is normally prohibited by the standard.
This is a violation of an ASN.1 standard and compiler may fail to produce the meaningful code.}\\
{\ttfamily -fcompound-names}&{\small Use complex names for C structures. Using complex names prevents
name clashes in case the module reuses the same identifiers in multiple
contexts.}\\
{\ttfamily -findirect-choice}&{\small When generating code for a CHOICE type, compile the CHOICE
members as indirect pointers instead of declaring them inline. Consider
using this option together with \texttt{-fno-include-deps}
to prevent circular references.}\\
{\ttfamily -fincludes-quoted}&{\small Generate \#include lines in "double" instead of <angle> quotes.}\\
{\ttfamily -fknown-extern-type=\emph{<name>}}&{\small Pretend the specified type is known. The compiler will assume
the target language source files for the given type have been provided
manually. }\\
{\ttfamily -fline-refs}&{\small Include ASN.1 module's line numbers in generated code comments.}\\
{\ttfamily -fno-constraints}&{\small Do not generate ASN.1 subtype constraint checking code. This
may produce a shorter executable.}\\
{\ttfamily -fno-include-deps}&{\small Do not generate courtesy \#include lines for non-critical dependencies.}\\
{\ttfamily -funnamed-unions}&{\small Enable unnamed unions in the definitions of target language's structures.}\\
{\ttfamily -fwide-types}&{\small Use the wide integer types (INTEGER\_t, REAL\_t) instead of machine's native data types (long, double). }\\\\
{\ttfamily -gen-OER}&{\small Generate the Octet Encoding Rules (OER) support code.}\\
{\ttfamily -gen-PER}&{\small Generate the Packed Encoding Rules (PER) support code.}\\
{\ttfamily -pdu=\{\textbf{all}|\textbf{auto}|\emph{Type}\}}&{\small Create a PDU table for specified types, or discover the Protocol Data Units automatically.
In case of \texttt{-pdu=\textbf{all}}, all ASN.1 types defined in all modules wil form a PDU table. In case of \texttt{-pdu=\textbf{auto}}, all types not referenced by any other type will form a PDU table. If \texttt{\emph{Type}} is an ASN.1 type identifier, it is added to a PDU table. The last form may be specified multiple times.}\\\\
\textbf{Output Options}&\textbf{Description}\\
\midrule
{\ttfamily -print-class-matrix}&{\small When \texttt{-EF} options are given, this option instructs the compiler to print out the collected Information Object Class matrix.}\\
{\ttfamily -print-constraints}&{\small With \texttt{-EF}, this option instructs the compiler
to explain its internal understanding of subtype constraints.}\\
This code defines a \emph{rect} pointer which points to the Rectangle\_t
structure which needs to be freed. The second line uses a generic
\code{ASN\_STRUCT\_FREE()} macro which invokes the memory deallocation routine
created specifically for this Rectangle\_t structure.
The \emph{asn\_DEF\_Rectangle} is the type descriptor which holds
a collection of routines and operations defined for the Rectangle\_t structure.
\section{\label{sec:Generic-Encoding}Generic encoders and decoders}
Before we start describing specific encoders and decoders, let's step back
a little and check out a simple high level way.
The asn1c runtime supplies (see \emph{asn\_application.h}) two sets of high level functions, \emph{asn\_encode*} and \emph{asn\_decode*}, which take a transfer syntax selector as the argument. The transfer syntax selector is defined as this:
Rectangle_t *%$\underbracket{\textrm{\listingfont rect = 0}}$%; /* %\textbf{\color{red}Note this 0\footnote{Forgetting to properly initialize the pointer to a destination structure is a major source of support requests.}!}% */
Rectangle_t *%$\underbracket{\textrm{\listingfont rect = 0}}$%; /* %\textbf{\color{red}Note this 0\footnote{Forgetting to properly initialize the pointer to a destination structure is a major source of support requests.}!}% */
See \fref{sec:Printing-the-target} for the example of stdio-based
XML encoder and other pretty-printing suggestions.
int main(int ac, char **av) {
char buf[1024]; /* Temporary buffer */
asn_dec_rval_t rval; /* Decoder return value */
Rectangle_t *%$\underbracket{\textrm{\listingfont rectangle = 0}}$%; /* Type to decode. %\textbf{\color{red}Note this 0\footnote{Forgetting to properly initialize the pointer to a destination structure is a major source of support requests.}!}% */
Rectangle_t *%$\underbracket{\textrm{\listingfont rect = 0}}$%; /* %\textbf{\color{red}Note this 0\footnote{Forgetting to properly initialize the pointer to a destination structure is a major source of support requests.}!}% */
/* Read up to the buffer size */
size = fread(buf, 1, sizeof(buf), fp);
fclose(fp);
if(!size) {
fprintf(stderr, "%\%%s: Empty or broken\n", filename);