In this chapter, we discuss our mapping from CSN.1 to ASN.1. There is no standard procedure for this mapping; what we present here is our own invention. The purpose of this mapping is ultimately to be able to generate data-binding code from the CSN.1 - code that includes structures that mirror the data defined in the CSN.1, along with code for encoding those structures into a bit string according to the CSN.1 definition, and decoding in the reverse direction.
For a description of CSN.1, along with the grammar we use to describe CSN.1, please refer to the previous chapter.
The purpose of this chapter is to help you understand how we generate code from CSN.1 by explaining the intermediate step that our compiler internally performs (the CSN.1 to ASN.1 conversion). This chapter is not required reading; you might skip it for now and return to it if needed. Note that you can see the resulting ASN.1 that is generated from CSN.1 by adding the -asn1 <filename>
option to the compiler command line; this tells the compiler to output the ASN.1 to a file.
The challenge in defining the mapping is that CSN.1 is a concrete syntax. To a certain degree, by naming (labeling) parts of the notation, semantics are conveyed, but the notation is still a concrete notation, not an abstract notation like ASN.1. In order to generate data structures from CSN.1 in a way similar to what asn1c does for ASN.1, we need to recognize patterns in the CSN.1 and infer the semantics. That's what our CSN.1 to ASN.1 mapping does. Once we internally map the CSN.1 into ASN.1, we can generate code for it, just as we do for ASN.1.
On the surface, the mapping procedure might seem simple, but a semi-formal description of the mapping is complex, for a variety of reasons. First, there are multiple possible mappings, so there are choices to be made. Also, not everything in the CSN.1 represents application data (some bits are auxiliary - they are purely a part of the encoding) so some parts of the CSN.1 probably shouldn't be mapped into ASN.1 at all, but yet must be accounted for, somehow.
Just to illustrate, consider the following simple example as a way to highlight the difference between CSN.1 (being concrete) and ASN.1 (being abstract). This CSN.1 calls for a single bit (either a 0 or 1) in the encoding: <value: bit>
. First, note that we have chosen to name the bit "value" - naming is not required in CSN.1. That itself presents a challenge. Next, observe that this CSN.1 could correspond to either of the following components in ASN.1 (and arguably other less obvious possibilities), and nothing in the CSN.1 tells us which is intended:
value BOOLEAN value INTEGER (0..1)
As another example, you might think CSN.1 alternation maps directly to ASN.1 CHOICE
. But, consider this example:
{ 0 | 1 <foobar : <foo><bar>> }
Here you have something called "foobar" that may or may not be present in the encoding, depending on whether a one or zero bit preceedes it. That sounds more like an optional component of a SEQUENCE than two alternatives in a CHOICE!
It also happens that sometimes the context affects how something is mapped. This is why the mapping procedure presented here is algorithmic and does not specify how each CSN.1 operation (concatenation, alternation, …) is mapped into ASN.1.
The mapping process must also specify encoding rules. If you map CSN.1 to ASN.1, that ASN.1 can't produce bit strings that are valid according to the CSN.1 unless you specify some encoding rules. That's a challenge, however, because none of the standard ASN.1 encoding rules fit the bill, and the only standardized notation for defining custom ASN.1 encoding rules (ECN) is extremely complicated and, we believe, insufficient for this task. For this reason, the mapping presented here will use prose to describe how the ASN.1 types that are produced should be encoded so as to correspond to the CSN.1 from which they were derived.