Translate

Monday, July 16, 2018

Android - proprietary ASN.1 encoder

The purpose of the ASN.1 coding specification described here is to have a way to create ASN.1 data sequence in a program without having an ASN.1 compiler capable to read a formal ASN.1 notation.
There are a lot of libraries out there with many different ways to handle the coding/decoding of an ASN.1 message. I needed something simple, cheap and fast so I created this system.
I originally created this years ago in C and now ported in Java for Android.


To use this method, the developer has to create an intermediate structure called ‘guideline’ derived from the formal ASN.1 notation and deploy it in the code.
A function/object will be called in order to process the guideline and create the coded ASN.1 sequence to be sent out.
The guideline is needed in order to know what data to convert and how to put them together when creating the ASN.1 message, so basically the guideline is used to “code” the ASN.1 message.
The guideline DOES NOT contains data, these are acquired in the program, saved in an array and transformed in ASN.1 sequence when the guideline is deployed.

How to generate the guideline


The idea is to create a set of “instructions” to be followed when the ASN.1 message is prepared.
An ASN.1 message is a series of “blocks” of three elements :
  • Tag 
  • Length 
  • Data 
i.e. ANY data is coded based on the structure above. An ASN.1 message is a sequence of blocks.

The tag indicate the type of data, the length indicate how many data are present and the data … are the data coded depending the type of tag.
Each element of the block is assumed to be an array of unsigned 8 bit length variables.


The guideline then has to be able to build an ASN.1 sequence following the BER rules.

Guideline blocks

Also the guideline is based on a series of “blocks”.
Each block describes how to build a variable and it has three elements :

  • Tag 
  • Level 
  • How to read 

A guideline is thus a collection of blocks. The guideline is read sequentially.

Tag

The tag is the tag to be sent in the ASN.1 message and is used of course to know how to code the data.
So it is important to stress out that the “guideline tag” IS in the end the ASN.1 tag element !

The tag has 3 elements and is 1 byte wide:

  • Class (bit 7 and 6) 
    • 00 UNIVERSAL 
    • 01 APPLICATION 
    • 10 CONTEXT_SPECIFICATION 
    • 11 PRIVATE 
  • Type (bit 5) 
    • 0 PRIMITIVE 
    • 1 CONSTRUCTED 
  • Data type (bit 4 to 0) 

Depends by the class - for UNIVERSAL is :

Type
Tag number
(decimal)
Tag number
(hexadecimal)
INTEGER
2
02
BIT STRING
3
03
OCTET STRING
4
04
NULL
5
05
OBJECT IDENTIFIER
6
06
SEQUENCE and SEQUENCE OF
16
10
SET and SET OF
17
11
PrintableString
19
13
T61String
20
14
IA5String
22
16
UTCTime
23
17

Note that there are more types than in this table - see ITU X680 for details

Level

The Level indicate the position of the data in the structure.
In ASN.1 there are two main type of data (see the tag type id - bit 5) :

  • Primitive 
  • Constructed 

A primitive type is a “basic” data, like integer, boolean, enum, etc.
A constructed type is basically a container for primitive types, a kind of C struct.

Every time a constructed type is used the level increase by one and when the constructed type ends the level goes back by one.
So the level is used to translate in the guideline a constructed type.

How to read


The How to read parameter tells how to read the container of data in order to build the message.
More details in specific examples. The field contains enums that define what to do.

  • NO_DATA_TO_READ
    No data are read from the array
  • ONLY_ONE_DATA_READ
    Only ONE element of the array is read.
    Typical used with BOOLEAN and INTEGER
  • READ_UNTIL_NULL
    Read from the data array until a null is found.
    Typical used for STRINGS
  • FIRST_DATA_LENGTH
    The first data is the length.
    Alternative method used for STRINGS
  • READ_3_DATA
    Read 3 data from the data array.
    Typical used for OBJID
  • READ_4_DATA
    Read 4 data from the data array.
    Typical used for OBJID
  • READ_5_DATA
    Read 5 data from the data array
    Typical used for OBJID
  • READ_6_DATA
    Read 6 data from the data array
    Typical used for OBJID
  • READ_7_DATA
    Read 7 data from the data array
    Typical used for OBJID
  • READ_8_DATA
    Read 8 data from the data array
    Typical used for OBJID
  • READ_9_DATA
    Read 9 data from the data array
    Typical used for OBJID
  • READ_10_DATA
    Read 10 data from the data array
    Typical used for OBJID
  • GENERATE_NULL_CODE
    Generate the NULL coding
  • GENERATE_INDEF_FORM
    Generate indefinite form
    Typical used for objects with variable length
All these way to read from the array are needed for different types of ASN.1 messages.

How to create a guideline

Starting from the formal ASN.1 notation, identify the main message.
For example, from this formal notation :


DefaultActivity DEFINITIONS ::=


BEGIN


Param1 ::= INTEGER(0..2),
Param2 ::= INTEGER(0..255)
Param3 ::= INTEGER(0..2)
Param4 ::= BOOLEAN
Param5 ::= BOOLEAN


Param6 ::= SEQUENCE {
param2 Param2,
param3 Param3
}

MainData ::= SEQUENCE {
param1  Param1, param6 Param6, param4 Param4, param5 Param5 }


END


It can be helpful to translate the notation in a pseudo C code, a structure, something like this :
                                   <-- level 0
Struct MainData        <-- level 1
{
   Int param1;            <-- level 1
   Struct param6        <-- level 2
   {
      Int param2;         <-- level 2
      Int param3;         <-- level 2
   }

   Boolean param4;   <-- level 1
   Boolean param5;   <-- level 1
}


The MainData is the message to be sent, it contains all the other definitions.

So we start from there.
The first block defines a “sequence”, i.e. a “container”.
We can use the universal coding for that, so the first element of the guideline block to create is the tag.
Because the sequence is a constructed type it means that the level change.
The level starts to 0, so anything contained in the sequence will be at the level 1.

Since the sequence contains other variables, it has no direct presence in the data array, i.e. NO data need to be read from the Data array.

Here the first block guideline for the sequence :
  • Tag = UNIVERSAL, CONSTRUCTED, SEQUENCE
  • Level = 1
  • How to read = NO_DATA_READ
The second element is a variable.
Param1 is defined as Integer.
The variable is at the level 1 and we need to read only 1 element from the Data array.
The data array contains integers values.

  • Tag = UNIVERSAL, PRIMITIVE, INTEGER
  • Level = 1
  • How to read = ONLY_ONE_READ
The third element is another sequence.
The variable pupilsBehavior is a type Pupil variable, defined above.
Because a sequence we are ending up increasing the level.

  • Tag = UNIVERSAL, CONSTRUCTED, SEQUENCE
  • Level = 2
  • How to read = NO_DATA_READ
Then we start to code inside the Param6.
The first element is param2, i.e. an Integer.

The variable is at the level 2 and we need to read only 1 element from the Data array.
The data array contains integers values.

  • Tag = UNIVERSAL, PRIMITIVE, INTEGER
  • Level = 2
  • How to read = ONLY_ONE_READ
Same for the param3.
The variable is at the level 2 and we need to read only 1 element from the Data array.
The data array contains integers values.

  • Tag = UNIVERSAL, PRIMITIVE, INTEGER
  • Level = 2
  • How to read = ONLY_ONE_READ
At this point we are ended the Param6 sequence, so continuing to create the guideline we’ll go back to one level.
The next variable to code is param4 defined as a Boolean type.

The variable is at the level 1 and we need to read only 1 element from the Data array.
The data array contains integers values so basically we consider the value false if zero and true if different from zero.

  • Tag = UNIVERSAL, PRIMITIVE, BOOLEAN
  • Level = 1
  • How to read = ONLY_ONE_READ
And the last variable is param5, similar to the one above, thus Boolean.
  • Tag = UNIVERSAL, PRIMITIVE, BOOLEAN
  • Level = 1
  • How to read = ONLY_ONE_READ

To conclude the guideline we create a closing block.

  • Tag = END_GUIDELINE
  • Level = 0
  • How to read = NO_DATA_READ


Coding

In order to create the ASN.1 message is necessary to have another element, the data to encode.
In a separate array or collection, we keep the data to be coded.
So after preparing the guideline, it is necessary to prepare the data to be coded.
From the guideline is possible to know what data are needed.

For example, from the example above, we need basically three integer and two boolean.

All of these are stored in an array of integers.

Currently there are some limits :
  • Integer values will be unsigned 
  • Integer values will be limited to 65535 
  • Boolean values will be stored as integer, 0 for false, &gt;0 for true (typically 1)


If other coding will be added, these limits could change.
Once we have our data array populated (be sure to store in it the number of data expected !) then is possible to code, i.e. prepare the ASN.1 message coded.
A function will read the guideline, block after block and fill up an array with the result.

Counters

A special attention is needed to correct count and assign the length of each ASN.1 block.

Each ASN.1 block has two mandatory elements plus an optional one.
The first one is the tag, the second one the length and the third one the data (if any).
For example, CONSTRUCTED TYPES doesn’t have the data, because everything following on the same level ARE the data of the type.

On the other ends, PRIMITIVES type of data have the three blocks, i.e. tag, length and data.



The length of a block indicates the number of data following the tag and length fields.
So for example, a BOOLEAN type is code with the data equal to 0 for false and 255 (0xFF) for true. The length of the data will be always 1.
A coded block for a boolean “true” will be thus : 0x01, 0x01, 0xFF

A Sequence (constructed type) containing a Boolean will be :

0x30, 0x03, 0x01, 0x01, 0xFF


Better represented in this way :

0x30, 0x03,
    0x01, 0x01, 0xFF


The first two bytes (0x30, 0x03) represent the tag and length of the Sequence and the length of the sequence is the entire Boolean coding (3 bytes).
And so on.
This imply that every time we have a constructed type, we need a counter for the block, plus a global counter in order to have the ASN.1 packet length.
And we need to know when and how to stop a specific counter.
When a constructed type is completed, the counter for that block must be stopped and the value saved on the correct position in the packet.

So basically, when a constructed type is found, create a counter and keep it update every time another block is added.
When the level goes back, stop the counter and write in the correct position in the ASN.1 packet the counted value.

In the end of the coding, all the open counters will be stopped and the length assigned to the right position.


Code structure

The ASN.1 encoder is based on 4 classes.

  • main class
  • guideline class
  • counter class
  • common defines

Main class - asn1obj


This is the main class, the one that contains the main functionalities. Basically the class contains methods to :
  • build/manage the guideline (collection of guideline blocks)
  • build/manage the counters for constructed types (collection of counter blocks)
  • manage the data to be encoded
  • encode the ASN.1 message

Guideline class - guideline

This class contains method capable to handle the main guideline functionalities.
The class define a guideline block.


Counter class - counter


This class contains methods capable to handle the counters functionalities.


Common defines


This class defines the main ASN.1 parameters.

More to come ...

No comments:

Post a Comment