This section discusses the mapping of an ASN.1 SEQUENCE type to C. The C++ mapping is similar but there are some differences. These are discussed in the C++ Mapping of SEQUENCE subsection at the end of this section.
An ASN.1 SEQUENCE is a constructed type consisting of a series of element definitions. These elements can be of any ASN.1 type including other constructed types. For example, it is possible to nest a SEQUENCE definition within another SEQUENCE definition as follows:
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 internal types. The names of these types are of the
form <name>_<element-name1>_<elementname2>_ ...
<element-nameN>
. For example, in the definition above, two temporary types
would be generated: A_x
and A_y
(A_y
is generated
because a static OCTET STRING maps to a C++ struct type).
The general form is as follows:
ASN.1 production:
<name> ::= SEQUENCE { <element1-name> <element1-type>, <element2-name> <element2-type>, ... }
Generated C code:
typedef struct { <type1> <element1-name>; <type2> <element2-name>; ... } <name>;
- or -
typedef struct { ... } <tempName1> typedef struct { ... } <tempName2> typedef struct { <tempName1> <element1-name>; <tempName2> <element2-name>; ... } <name>;
The <type1>
and <type2>
placeholders represent the
equivalent C types for the ASN.1 types <element1-type>
and
<element2-type>
respectively. This form of the structure will be
generated if the internal types are primitive. <tempName1>
and
<tempName2>
are formed using the algorithm described above for
pulling structured types out of the definition. This form is used for constructed
elements and elements that map to structured C types.
The example above would result in the following generated C typedefs:
typedef struct A_x { OSINT32 a1; OSBOOL a2; } A_x; typedef struct A_y { OSUINT32 numocts; OSOCTET data[10]; } A_y; typedef struct A { A_x x; A_y y; } A;
In this case, elements x
and y
map to structured C types,
so temporary typedefs are generated.
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:
X-a-aa ::= SEQUENCE { x INTEGER, y BOOLEAN } X-a ::= SEQUENCE { aa X-a-aa, bb INTEGER } X ::= SEQUENCE { X-a a }
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).
The concatanation of all of the intermdeiate element names can lead to very long names
in some cases. To get around the problem, the -shortnames command-line option can be used to form shorter names. In
this case, only the type name and the last element name are used. In the example above,
this would lead to an element name of X-aa
. The disadvantage of this is
that the names may not always be unique. If using this option results in non-unique
names, an _n
suffix is added where n
is a sequential number to
make the names unique.
Note that although the compiler can handle embedded constructed types within productions, it is generally not considered good style to define productions this way. It is much better to manually define the constructed types for use in the final production definition. For example, the production defined at the start of this section can be rewritten as the following set of productions:
X ::= SEQUENCE { a1 INTEGER, a2 BOOLEAN } Y ::= OCTET STRING A ::= SEQUENCE { X x, Y y }
This makes the generated code easier to understand for the end user.
As of X.680, unnamed elements are not allowed: elements must be named. ASN1C still provides backward compatibility support for this syntax however.
In an ASN.1 SEQUENCE definition, the <element-name> tokens at the beginning of element declarations are optional. It is possible to include only a type name without a field identifier to define an element. This is normally done with defined type elements, but can be done with built-in types as well. An example of a SEQUENCE with unnamed elements would be as follows:
AnInt ::= [PRIVATE 1] INTEGER Aseq ::= [PRIVATE 2] SEQUENCE { x INTEGER, AnInt }
In this case, the first element (x) is named and the second element is unnamed.
ASN1C handles this by generating an element name using the type name with the first character set to lower case. For built-in types, a constant element name is used for each type (for example, aInt is used for INTEGER). There is one caveat, however. ASN1C cannot handle multiple unnamed elements in a SEQUENCE or SET with the same type names. Element names must be used in this case to distinguish the elements.
So, for the example above, the generated code would be as follows:
typedef OSINT32 AnInt; typedef struct Aseq { OSINT32 x; AnInt anInt; } Aseq;
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. An additional construct is added to the generated code to indicate whether an optional element is present in the message or not. This construct is a bit structure placed at the beginning of the generated sequence structure. This structure always has variable name 'm' and contains single-bit elements of the form '<element-name>Present' as follows:
struct { unsigned <element-name1>Present : 1, unsigned <element-name2>Present : 1, ... } m;
In this case, the elements included in this construct correspond to only those elements marked as OPTIONAL within the production. If a production contains no optional elements, the entire construct is omitted.
For example, the production in the previous example can be changed to make both elements optional:
Aseq ::= [PRIVATE 2] SEQUENCE { x INTEGER OPTIONAL, AnInt OPTIONAL }
In this case, the following C typedef is generated:
typedef struct Aseq { struct { unsigned xPresent : 1, unsigned anIntPresent : 1 } m; OSINT32 x; AnInt anInt; } Aseq;
When this structure is populated for encoding, the developer must set the xPresent and anIntPresent flags accordingly to indicate whether the elements are to be included in the encoded message or not. Conversely, when a message is decoded into this structure, the developer must test the flags to determine if the element was provided in the message or not.
The generated C++ structure will contain a constructor if OPTIONAL elements are present. This constructor will set all optional bits to zero when a variable of the structured type is declared. The programmer therefore does not have to be worried about clearing bits for elements that are not used; only with setting bits for the elements that are to be encoded.
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 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, an optional present flag bit is generated. The programmer must set this bit to false 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 present bit not set. If this is the case, the default value specified in the ASN.1 specification must be used and the value in the structure ignored.
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 OSRTDList and have the name extElem1. This is a linked list of open type fields. Each entry in the list is of type ASN1OpenType. The fields will contain complete encodings 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.
The -noOpenExt command line option can be used to alter this default behavior. If this option is specified, the extElem1 element is not included in the generated code and extension data that may be present in a decoded message is simply dropped.
If the SEQUENCE type contains an extension marker and extension elements, then the actual extension elements will be present in addition to the extElem1 element. 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.
Additional bits will be generated in the bit mask if version brackets are present. These are groupings of extended elements that typically correspond to a particular version of a protocol. An example would be as follows:
TestSequence ::= SEQUENCE { item-code INTEGER (0..254), item-name IA5String (SIZE (3..10)) OPTIONAL, ... ! 1, urgency ENUMERATED { normal, high } DEFAULT normal, [[ alternate-item-code INTEGER (0..254), alternate-item-name IA5String (SIZE (3..10)) OPTIONAL ]] }
In this case, a special bit flag will be added to the mask structure to indicate the presence or absence of the entire element block. This will be of the form "_v#ExtPresent" where # would be replaced by the sequential version number. In the example above, this number would be three (two would be the version extension number of the urgency field). Therefore, the generated bit mask would be as follows:
struct { unsigned item_namePresent : 1; unsigned urgencyPresent : 1; unsigned _v3ExtPresent : 1; unsigned alternate_item_namePresent : 1; } m;
In this case, the setting of the _v3ExtPresent flag would indicate the presence or absence of the entire version block. Note that it is also possible to have optional items within the block (alternate-item-name).
The C++ mapping of an ASN.1 SEQUENCE type is very similar to the C mapping. However, there are some important differences:
As with all C++ types, the prefix ASN1T_ is added before the typename to distinguish the data class from the control class (the control class contains an ASN1C_ prefix).
A default constructor is generated to initialize the structure elements. This constructor will initialize all elements and set any simple default values that may have been specified in the ASN.1 definition.
If the -genCopy command line switch was specified, a copy constructor will be generated to allow an instance of the data contained within a PDU control class object to be copied.
Also if -genCopy was specified, a destructor is generated if the type contains dynamic fields. This destructor will free all memory held by the type when the object is deleted or goes out of scope.