Lev Walkin <vlm@lionet.info>
Download the PDF version
The purpose of the ASN.1 compiler, of which this document is part, is to convert the specifications in ASN.1 notation into some other language. At this moment, only C and C++ target languages are supported, the latter is in upward compatibility mode.
The compiler reads the specification and emits a series of target language structures (C's structs, unions, enums) describing the corresponding ASN.1 types. The compiler also creates the code which allows automatic serialization and deserialization of these structures using several standardized encoding rules (BER, DER, XER).
For example, suppose the following ASN.1 module is given1.1:
The compiler would read this ASN.1 definition and produce the following C type1.2:RectangleTest DEFINITIONS ::= BEGIN Rectangle ::= SEQUENCE { height INTEGER, -- Height of the rectangle width INTEGER -- Width of the rectangle } END
It would also create the code for converting this structure into platform-independent wire representation (a serializer API) and the decoder of such wire representation back into local, machine-specific type (a deserializer API).typedef struct Rectangle_s { int height; int width; } Rectangle_t;
After building and installing the compiler, the asn1c1.3 command may be used to compile the ASN.1 module1.4:
If several ASN.1 modules contain interdependencies, all of the files must be specified altogether:asn1c <module.asn1>
The compiler -E and -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 -F is involved) ASN.1 specification as it was "understood" by the compiler. It might be useful to check whether a particular syntactic construction is properly supported by the compiler.asn1c <module1.asn1> <module2.asn1> ...
The -P option is used to dump the compiled output on the screen instead of creating a bunch of .c and .h files on disk in the current directory. You would probably want to start with -P option instead of creating a mess in your current directory. Another option, -R, asks compiler to only generate the files which need to be generated, and supress linking in the numerous support files.asn1c -EF <module-to-test.asn1>
Print the compiled output instead of creating multiple source files:
asn1c -P <module-to-compile-and-print.asn1>
After compiling, the following entities will be created in your current directory:
In other words, after compiling the Rectangle module, you have the following set of files: { Makefile.am.sample, Rectangle.c, Rectangle.h, ... }, where ''...'' stands for the set of additional ''helper'' files created by the compiler. If you add a simple file with the int main() routine, it would even be possible to compile everything with the single instruction:
Refer to the Chapter cha:Step-by-step-examples for a sample int main() routine.cc -I. -o rectangle.exe *.c # It could be that simple
The Table 1 summarizes various options affecting the compiler's behavior.
|
First of all, you should include one or more header files into your application. Typically, it is enough to include the header file of the main PDU type. For our Rectangle module, including the Rectangle.h file is sufficient:
The header files defines the C structure corresponding to the ASN.1 definition of a rectangle and the declaration of the ASN.1 type descriptor, which is used as an argument to most of the functions provided by the ASN.1 module. For example, here is the code which frees the Rectangle_t structure:#include <Rectangle.h>
This code defines a rect pointer which points to the Rectangle_t structure which needs to be freed. The second line invokes the generic free_struct() routine created specifically for this Rectangle_t structure. The asn_DEF_Rectangle is the type descriptor, which holds a collection of routines to deal with the Rectangle_t structure.Rectangle_t *rect = ...; asn_DEF_Rectangle.free_struct(&asn_DEF_Rectangle, rect, 0);
The following member functions of the asn_DEF_Rectangle type descriptor are of interest:
The Basic Encoding Rules describe the most widely used (by the ASN.1 community) way to encode and decode a given structure in a machine-independent way. Several other encoding rules (CER, DER) define a more restrictive versions of BER, so the generic BER parser is also capable of decoding the data encoded by CER and DER encoders. The opposite is not true.
The ASN.1 compiler provides the generic BER decoder which is implicitly capable of decoding BER, CER and DER encoded data.
The decoder is restartable (stream-oriented), which means that in case the buffer has less data than it is expected, the decoder will process whatever there is available and ask for more data to be provided. Please note that the decoder may actually process less data than it was given in the buffer, which means that you must be able to make the next buffer contain the unprocessed part of the previous buffer.
Suppose, you have two buffers of encoded data: 100 bytes and 200 bytes.
Here is the simplest example of BER decoding.
The code above defines a function, simple_deserializer, which takes a buffer and its length and is expected to return a pointer to the Rectangle_t structure. Inside, it tries to convert the bytes passed into the target structure (rect) using the BER decoder and returns the rect pointer afterwards. If the structure cannot be deserialized, it frees the memory which might be left allocated by the unfinished ber_decoder routine and returns 0 (no data). (This freeing is necessary because the ber_decoder is a restartable procedure, and may fail just because there is more data needs to be provided before decoding could be finalized). The code above obviously does not take into account the way the ber_decoder() failed, so the freeing is necessary because the part of the buffer may already be decoded into the structure by the time something goes wrong.Rectangle_t * simple_deserializer(const void *buffer, size_t buf_size) { Rectangle_t *rect = 0; /* Note this 0! */ asn_dec_rval_t rval; rval = asn_DEF_Rectangle.ber_decoder(0, &asn_DEF_Rectangle, (void **)&rect, buffer, buf_size, 0); if(rval.code == RC_OK) { return rect; /* Decoding succeeded */ } else { /* Free partially decoded rect */ asn_DEF_Rectangle.free_struct( &asn_DEF_Rectangle, rect, 0); return 0; } }
A little less wordy would be to invoke a globally available ber_decode() function instead of dereferencing the asn_DEF_Rectangle type descriptor:
Note that the initial (asn_DEF_Rectangle.ber_decoder) reference is gone, and also the last argument (0) is no longer necessary.rval = ber_decode(0, &asn_DEF_Rectangle, (void **)&rect, buffer, buf_size);
These two ways of BER decoder invocations are fully equivalent.
The BER decoder may fail because of (the following RC_... codes are defined in ber_decoder.h):
Please look into ber_decoder.h for the precise definition of ber_decode() and related types.
The Distinguished Encoding Rules is the canonical variant of BER encoding rules. The DER is best suited to encode the structures where all the lengths are known beforehand. This is probably exactly how you want to encode: either after a BER decoding or after a manual fill-up, the target structure contains the data which size is implicitly known before encoding. Among other uses, the DER encoding is used to encode X.509 certificates.
As with BER decoder, the DER encoder may be invoked either directly from the ASN.1 type descriptor (asn_DEF_Rectangle) or from the stand-alone function, which is somewhat simpler:
As you see, the DER encoder does not write into some sort of buffer or something. It just invokes the custom function (possible, multiple times) which would save the data into appropriate storage. The optional argument app_key is opaque for the DER encoder code and just used by _write_stream() as the pointer to the appropriate output stream to be used./* * This is the serializer itself, * it supplies the DER encoder with the * pointer to the custom output function. */ ssize_t simple_serializer(FILE *ostream, Rectangle_t *rect) { asn_enc_rval_t er; /* Encoder return value */ er = der_encode(&asn_DEF_Rect, rect, write_stream, ostream); if(er.encoded == -1) { /* * Failed to encode the rectangle data. */ fprintf(stderr, ''Cannot encode %s: %s\n'', er.failed_type->name, strerror(errno)); return -1; } else { /* Return the number of bytes */ return er.encoded; } }
If the custom write function is not given (passed as 0), then the DER encoder will essentially do the same thing (i.e., encode the data) but no callbacks will be invoked (so the data goes nowhere). It may prove useful to determine the size of the structure's encoding before actually doing the encoding2.2.
Please look into der_encoder.h for the precise definition of der_encode() and related types.
The XER stands for XML Encoding Rules, where XML, in turn, is eXtensible Markup Language, a text-based format for information exchange. The encoder routine API comes in two flavors: stdio-based and callback-based. With the callback-based encoder, the encoding process is very similar to the DER one, described in Section Encoding DER. The following example uses the definition of write_stream() from up there.
Please look into xer_encoder.h for the precise definition of xer_encode() and related types./* * This procedure generates the XML document * by invoking the XER encoder. * NOTE: Do not copy this code verbatim! * If the stdio output is necessary, * use the xer_fprint() procedure instead. * See Section Printing the target. */ int print_as_XML(FILE *ostream, Rectangle_t *rect) { asn_enc_rval_t er; /* Encoder return value */ er = xer_encode(&asn_DEF_Rectangle, rect, XER_F_BASIC, /* BASIC-XER or CANONICAL-XER */ write_stream, ostream); return (er.encoded == -1) ? -1 : 0; }
See Section [Printing the target] for the example of stdio-based XML encoder and other pretty-printing suggestions.
The data encoded using the XER rules can be subsequently decoded using the xer_decode() API call:
The decoder takes both BASIC-XER and CANONICAL-XER encodings.Rectangle_t * XML_to_Rectangle(const void *buffer, size_t buf_size) { Rectangle_t *rect = 0; /* Note this 0! */ asn_dec_rval_t rval; rval = xer_decode(0, &asn_DEF_Rectangle, (void **)&rect, buffer, buf_size); if(rval.code == RC_OK) { return rect; /* Decoding succeeded */ } else { /* Free partially decoded rect */ asn_DEF_Rectangle.free_struct( &asn_DEF_Rectangle, rect, 0); return 0; } }
The decoder shares its data consumption properties with BER decoder; please read the Section Decoding BER to know more.
Please look into xer_decoder.h for the precise definition of xer_decode() and related types.
Sometimes the target structure needs to be validated. For example, if the structure was created by the application (as opposed to being decoded from some external source), some important information required by the ASN.1 specification might be missing. On the other hand, the successful decoding of the data from some external source does not necessarily mean that the data is fully valid either. It might well be the case that the specification describes some subtype constraints that were not taken into account during decoding, and it would actually be useful to perform the last check when the data is ready to be encoded or when the data has just been decoded to ensure its validity according to some stricter rules.
The asn_check_constraints() function checks the type for various implicit and explicit constraints. It is recommended to use asn_check_constraints() function after each decoding and before each encoding.
Please look into constraints.h for the precise definition of asn_check_constraints() and related types.
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:
Please look into constr_TYPE.h for the precise definition of asn_fprint() and related types.asn_fprint(stdout, &asn_DEF_Rectangle, rect);
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:
See Section Encoding XER for XML-related details.xer_fprint(stdout, &asn_DEF_Rectangle, rect);
Freeing the structure is slightly more complex than it may seem to. When the ASN.1 structure is freed, all the members of the structure and their submembers etc etc are recursively freed too. But it might not be feasible to free the structure itself. Consider the following case:
In this example, the application programmer defined a custom structure with one ASN.1-derived member (rect). This member is not a reference to the Rectangle_t, but an in-place inclusion of the Rectangle_t structure. If the freeing is necessary, the usual procedure of freeing everything must not be applied to the &rect pointer itself, because it does not point to the memory block directly allocated by the memory allocation routine, but instead lies within a block allocated for the my_figure structure.struct my_figure { /* The custom structure */ int flags; /* <some custom member> */ /* The type is generated by the ASN.1 compiler */ Rectangle_t rect; /* other members of the structure */ };
To solve this problem, the free_struct routine has the additional argument (besides the obvious type descriptor and target structure pointers), which is the flag specifying whether the outer pointer itself must be freed (0, default) or it should be left intact (non-zero value).
It is safe to invoke the free_struct function with the target structure pointer set to 0 (NULL), the function will do nothing./* 1. Rectangle_t is defined within my_figure */ struct my_figure { Rectangle_t rect; } *mf = ...; /* * Freeing the Rectangle_t * without freeing the mf->rect area */ asn_DEF_Rectangle.free_struct( &asn_DEF_Rectangle, &mf->rect, 1 /* !free */); /* 2. Rectangle_t is a stand-alone pointer */ Rectangle_t *rect = ...; /* * Freeing the Rectangle_t * and freeing the rect pointer */ asn_DEF_Rectangle.free_struct( &asn_DEF_Rectangle, rect, 0 /* free the pointer too */);
This example will help you to create a simple BER and XER encoder of a ''Rectangle'' type used throughout this document.
RectangleModule1 DEFINITIONS ::= BEGIN Rectangle ::= SEQUENCE { height INTEGER, width INTEGER } END
asn1c -fnative-types rectangle.asn1
#include <stdio.h> #include <sys/types.h> #include <Rectangle.h> /* Rectangle ASN.1 type */ /* * This is a custom function which writes the * encoded output into some FILE stream. */ static int write_out(const void *buffer, size_t size, void *app_key) { FILE *out_fp = app_key; size_t wrote; wrote = fwrite(buffer, 1, size, out_fp); return (wrote == size) ? 0 : -1; } int main(int ac, char **av) { Rectangle_t *rectangle; /* Type to encode */ asn_enc_rval_t ec; /* Encoder return value */ /* Allocate the Rectangle_t */ rectangle = calloc(1, sizeof(Rectangle_t)); /* not malloc! */ if(!rectangle) { perror(''calloc() failed''); exit(71); /* better, EX_OSERR */ } /* Initialize the Rectangle members */ rectangle->height = 42; /* any random value */ rectangle->width = 23; /* any random value */ /* 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(71); /* better, EX_OSERR */ } /* Encode the Rectangle type as BER (DER) */ ec = der_encode(&asn_DEF_Rectangle, rectangle, write_out, fp); fclose(fp); if(ec.encoded == -1) { fprintf(stderr, ''Could not encode Rectangle (at %s)\n'', ec.failed_type ? ec.failed_type->name : ''unknown''); exit(65); /* better, EX_DATAERR */ } else { fprintf(stderr, ''Created %s with BER encoded Rectangle\n'', filename); } } /* Also print the constructed Rectangle XER encoded (XML) */ xer_fprint(stdout, &asn_DEF_Rectangle, rectangle); return 0; /* Encoding finished successfully */ }
cc -I. -o rencode *.c
This example will help you to create a simple BER decoder of a simple ''Rectangle'' type used throughout this document.
RectangleModule1 DEFINITIONS ::= BEGIN Rectangle ::= SEQUENCE { height INTEGER, width INTEGER } END
asn1c -fnative-types rectangle.asn1
#include <stdio.h> #include <sys/types.h> #include <Rectangle.h> /* Rectangle ASN.1 type */ int main(int ac, char **av) { char buf[1024]; /* Temporary buffer */ Rectangle_t *rectangle = 0; /* Type to decode */ asn_dec_rval_t rval; /* Decoder return value */ FILE *fp; /* Input file handler */ size_t size; /* Number of bytes read */ char *filename; /* Input file name */ /* Require a single filename argument */ if(ac != 2) { fprintf(stderr, ''Usage: %s <file.ber>\n'', av[0]); exit(64); /* better, EX_USAGE */ } else { filename = av[1]; } /* Open input file as read-only binary */ fp = fopen(filename, ''rb''); if(!fp) { perror(filename); exit(66); /* better, EX_NOINPUT */ } /* 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); exit(65); /* better, EX_DATAERR */ } /* Decode the input buffer as Rectangle type */ rval = ber_decode(0, &asn_DEF_Rectangle, (void **)&rectangle, buf, size); if(rval.code != RC_OK) { fprintf(stderr, ''%s: Broken Rectangle encoding at byte %ld\n'', filename, (long)rval.consumed); exit(65); /* better, EX_DATAERR */ } /* Print the decoded Rectangle type as XML */ xer_fprint(stdout, &asn_DEF_Rectangle, rectangle); return 0; /* Decoding finished successfully */ }
cc -I. -o rdecode *.c
This chapter shows how to define ASN.1 constraints and use the generated validation code.
This example shows how to add basic constraints to the ASN.1 specification and how to invoke the constraints validation code in your application.
RectangleModuleWithConstraints DEFINITIONS ::= BEGIN Rectangle ::= SEQUENCE { height INTEGER (0..100), -- Value range constraint width INTEGER (0..MAX) -- Makes width non-negative } END
int ret; /* Return value */ char errbuf[128]; /* Buffer for error message */ size_t errlen = sizeof(errbuf); /* Size of the buffer */ /* ... here may go Rectangle decoding code ... */ ret = asn_check_constraints(&asn_DEF_Rectangle, rectangle, errbuf, &errlen); /* assert(errlen < sizeof(errbuf)); // you may rely on that */ if(ret) { fprintf(stderr, ''Constraint validation failed: %s\n'', errbuf /* errbuf is properly nul-terminated */ ); /* exit(...); // Replace with appropriate action */ } /* ... here may go Rectangle encoding code ... */
This chapter defines some basic ASN.1 concepts and describes several most widely used types. It is by no means an authoritative or complete reference. For more complete ASN.1 description, please refer to Olivier Dubuisson's book [Dub00] or the ASN.1 body of standards itself [ITU-T/ASN.1].
The Abstract Syntax Notation One is used to formally describe the semantics of data transmitted across the network. Two communicating parties may have different formats of their native data types (i.e. number of bits in the integer type), thus it is important to have a way to describe the data in a manner which is independent from the particular machine's representation. The ASN.1 specifications are used to achieve the following:
This ASN.1 specification describes a constructed type, Rectangle, containing two integer fields. This specification may tell the reader that there exists this kind of data structure and that some entity may be prepared to send or receive it. The question on how that entity is going to send or receive the encoded data is outside the scope of ASN.1. For example, this data structure may be encoded according to some encoding rules and sent to the destination using the TCP protocol. The ASN.1 specifies several ways of encoding (or ''serializing'', or ''marshaling'') the data: BER, PER, XER and others, including CER and DER derivatives from BER.Rectangle ::= SEQUENCE { height INTEGER, width INTEGER }
The complete specification must be wrapped in a module, which looks like this:
The module header consists of module name (RectangleModule1), the module object identifier ({...}), a keyword ''DEFINITIONS'', a set of module flags (AUTOMATIC TAGS) and ''::= BEGIN''. The module ends with an ''END'' statement.RectangleModule1 { iso org(3) dod(6) internet(1) private(4) enterprise(1) spelio(9363) software(1) asn1c(5) docs(2) rectangle(1) 1 } DEFINITIONS AUTOMATIC TAGS ::= BEGIN -- This is a comment which describes nothing. Rectangle ::= SEQUENCE { height INTEGER, -- Height of the rectangle width INTEGER -- Width of the rectangle } END
The BOOLEAN type models the simple binary TRUE/FALSE, YES/NO, ON/OFF or a similar kind of two-way choice.
The INTEGER type is a signed natural number type without any restrictions on its size. If the automatic checking on INTEGER value bounds are necessary, the subtype constraints must be used.
SimpleInteger ::= INTEGER -- An integer with a very limited range SmallPositiveInt ::= INTEGER (0..127) -- Integer, negative NegativeInt ::= INTEGER (MIN..0)
The ENUMERATED type is semantically equivalent to the INTEGER type with some integer values explicitly named.
FruitId ::= ENUMERATED { apple(1), orange(2) } -- The numbers in braces are optional, -- the enumeration can be performed -- automatically by the compiler ComputerOSType ::= ENUMERATED { FreeBSD, -- acquires value 0 Windows, -- acquires value 1 Solaris(5), -- remains 5 Linux, -- becomes 6 MacOS -- becomes 7 }
This type models the sequence of 8-bit bytes. This may be used to transmit some opaque data or data serialized by other types of encoders (i.e. video file, photo picture, etc).
The OBJECT IDENTIFIER is used to represent the unique identifier of any object, starting from the very root of the registration tree. If your organization needs to uniquely identify something (a router, a room, a person, a standard, or whatever), you are encouraged to get your own identification subtree at http://www.iana.org/protocols/forms.htm.
For example, the very first ASN.1 module in this Chapter (RectangleModule1) has the following OBJECT IDENTIFIER: 1 3 6 1 4 1 9363 1 5 2 1 1.
As you see, names are optional.ExampleOID ::= OBJECT IDENTIFIER rectangleModule1-oid ExampleOID ::= { 1 3 6 1 4 1 9363 1 5 2 1 1 } -- An identifier of the Internet. internet-id OBJECT IDENTIFIER ::= { iso(1) identified-organization(3) dod(6) internet(1) }
The RELATIVE-OID type has the semantics of a subtree of an OBJECT IDENTIFIER. There may be no need to repeat the whole sequence of numbers from the root of the registration tree where the only thing of interest is some of the tree's subsequence.
this-document RELATIVE-OID ::= { docs(2) usage(1) } this-example RELATIVE-OID ::= { this-document assorted-examples(0) this-example(1) }
This is essentially the ASCII, with 128 character codes available (7 lower bits of an 8-bit byte).
This is the character string which encodes the full Unicode range (4 bytes) using multibyte character sequences.
This type represents the character string with the alphabet consisting of numbers (''0'' to ''9'') and a space.
The character string with the following alphabet: space, ''''' (single quote), ''('', '')'', ''+'', '','' (comma), ''-'', ''.'', ''/'', digits (''0'' to ''9''), '':'', ''='', ''?'', upper-case and lower-case letters (''A'' to ''Z'' and ''a'' to ''z'').
The character string with the alphabet which is more or less a subset of ASCII between the space and the ''~'' symbol (tilde).
Alternatively, the alphabet may be described as the PrintableString alphabet presented earlier, plus the following characters: ''!'', '''''', ''#'', ''$'', ''%'', ''&'', ''*'', '';'', ''<'', ''>'', ''['', ''\'', '']'', ''^'', ''_'', ''`'' (single left quote), ''{'', ''|'', ''}'', ''~''.
This is an ordered collection of other simple or constructed types. The SEQUENCE constructed type resembles the C ''struct'' statement.
Address ::= SEQUENCE { -- The apartment number may be omitted apartmentNumber NumericString OPTIONAL, streetName PrintableString, cityName PrintableString, stateName PrintableString, -- This one may be omitted too zipNo NumericString OPTIONAL }
This is a collection of other simple or constructed types. Ordering is not important. The data may arrive in the order which is different from the order of specification. Data is encoded in the order not necessarily corresponding to the order of specification.
This type is just a choice between the subtypes specified in it. The CHOICE type contains at most one of the subtypes specified, and it is always implicitly known which choice is being decoded or encoded. This one resembles the C ''union'' statement.
The following type defines a response code, which may be either an integer code or a boolean ''true''/''false'' code.
ResponseCode ::= CHOICE { intCode INTEGER, boolCode BOOLEAN }
This one is the list (array) of simple or constructed types:
-- Example 1 ManyIntegers ::= SEQUENCE OF INTEGER -- Example 2 ManyRectangles ::= SEQUENCE OF Rectangle -- More complex example: -- an array of structures defined in place. ManyCircles ::= SEQUENCE OF SEQUENCE { radius INTEGER }
The SET OF type models the bag of structures. It resembles the SEQUENCE OF type, but the order is not important: i.e. the elements may arrive in the order which is not necessarily the same as the in-memory order on the remote machines.
-- A set of structures defined elsewhere SetOfApples :: SET OF Apple -- Set of integers encoding the kind of a fruit FruitBag ::= SET OF ENUMERATED { apple, orange }
Placing the constraint checking code after decoding, but before any further action depending on the decoded data, helps to make sure the application got the valid contents before making use of it.