TinyBER is a very small, limited ASN.1 BER codec and code generator meant for use on embedded devices (or anywhere code size is restricted). The generated code uses fixed-size structures and makes no calls to malloc or free.
$ sudo python setup.py install
TinyBER can be used for ad-hoc encoding and decoding of data, but it also comes with a limited code generator.
A simple buf_t
structure is used for both input and output::
typedef struct {
uint8_t * buffer;
unsigned int pos;
unsigned int size;
} buf_t;
Encoding is a little unusual. In the interest of efficiency, data can be encoded directly into an output buffer - backwards. Because asn.1 structures tend to accumulate in reverse (the Type and Length precede the Value in the stream), the most efficient way to encode them is to do so in reverse.
For example, to encode a SEQUENCE of objects there are three steps::
int mark0 = obuf.pos;
CHECK (encode_OCTET_STRING (&obuf, (uint8_t *) "ghi", 3));
CHECK (encode_OCTET_STRING (&obuf, (uint8_t *) "def", 3));
CHECK (encode_OCTET_STRING (&obuf, (uint8_t *) "abc", 3));
CHECK (encode_TLV (&obuf, mark0, TAG_SEQUENCE));
- Remember the stream's position (record the value of obuf.pos).
- Encode each item of the SEQUENCE in reverse.
- Emit the type and length for the entire sequence.
Note that the buf_t
object is used in a 'predecrement' mode. When
you initialize a buffer for output, its pos
field points to the
end of the buffer. As data is written, pos
moves backward.
When decoding an object, first call decode_TLV()
to get the type,
length, and value pointers to the object::
buf_t src;
init_ibuf (&src, data, length);
asn1raw dst;
int r = decode_TLV (&dst, &src);
Now examine the type tag - if it is the expected type, then you may further decode the value. If the value itself makes up a more complex structure, continue the procedure recursively.
A simple utility structure, asn1raw
is used to represent a TLV::
typedef struct {
uint8_t type;
int length;
uint8_t * value;
} asn1raw;
To decode a 'structured' element (i.e., a SEQUENCE or SET), create an
array of asn1raw
objects, and pass it to decode_structured()
::
asn1raw subs[50];
int n = 50;
int i;
CHECK (decode_structured (ob, &subs[0], &n));
In this example we allow up to 50 sub-elements. If more are present
in the stream an error will be returned. If there are less than 50
the actual number will be set in n
(i.e., n
is an in-out
param).
Now that you have the metadata for each sub-element, you may recursively continue decoding each one in turn. (This could be viewed as a form of recursive-descent parser).
This is not a full BER codec by any stretch: for example it supports only definite-length (i.e., actual length is always prepended), and as such it can be used for DER encoding as long as care is taken to follow the rules.
It does not support INTEGERs larger than a machine int (int64_t by default).
Still missing are direct support for SET, APPLICATION, BITSTRING, OIDs, etc... though if you are familiar with BER they can be implemented with relative ease.
Because tinyber requires fixed-sized elements for all structures (to avoid malloc & free), using recursive (or mutually recursive) types is impossible::
List ::= SEQUENCE {
car INTEGER,
cdr List OPTIONAL
}
Tinyber can't make a fixed-sized structure that might hold a potentially infinite list, so it cannot handle this kind of construction.
Included is a code generator, tinyber_gen.py
, which can generate
type definitions and BER encoders/decoders for a limited subset of the
ASN.1 specification language (X.680) in C and Python.
usage: tinyber_gen [-h] [-o OUTDIR] [-l LANG] [-ns] FILE
tinyber ASN.1 BER/DER code generator.
positional arguments:
FILE asn.1 spec
optional arguments:
-h, --help show this help message and exit
-o OUTDIR, --outdir OUTDIR
output directory (defaults to location of input file)
-l LANG, --lang LANG output language ('c' or 'python')
-ns, --no-standalone [python only] do not insert codec.py into output file.
For example::
beast:tinyber rushing$ python tinyber_gen.py -l c thing.asn1
beast:tinyber rushing$ ls -l thing.[ch]
-rw-r--r-- 1 rushing staff 20240 Jan 20 13:08 thing.c
-rw-r--r-- 1 rushing staff 4939 Jan 20 13:08 thing.h
beast:tinyber rushing$
The code generator requires the
asn1ate package to be installed.
asn1ate
is a parser for X.680 designed for use by code generators.
If your goal is to keep your codec as small as possible, a good approach is to segregate your packet types into 'server' and 'client' groups. Otherwise the outermost CHOICE PDU will force the inclusion of both server and client encoders and decoders on both sides. If you use two different PDU's, you will get only the encoders and decoders needed for each side. For example::
ThingModule DEFINITIONS ::= BEGIN
ThingClientMessage ::= CHOICE {
login-request [0] LoginRequest,
status-request [1] StatusRequest,
}
ThingServerMessage ::= CHOICE {
login-reply [0] LoginReply,
status-reply [1] StatusReply
}
This software is licensed under the Apache 2 license. However, the output, which is included into other projects, is not encumbered with any license restrictions. See the LICENSE.txt for more details.