The dictionary effectively defines four tables, the spaces, vendors,
attributes/fields and values tables.
Each table entry contains a number, a name, and a few variables that
define the entry's properties, except for those in the vendors and
values tables, which are simple name/number mappings, nothing more.
A space is something that enables the packet decoder (and encoder, but
decoding is easier to think about) to decode a block of data. In order
to do so, a space has the following properties:
atr_ofs and atr_size: define the place and size of an attribute nr.
field to be used for finding items to apply, if any;
vnd_ofs and vnd_size: idem for a vendor field.
If a space's atr_size is 0, then the space has no numbered attributes/
fields/dictionary items (all used interchangably in the dictionary and
its code) and provides no way to walk the block, other than to apply
each field defined in that space one after another.
Once the decoder finds a dictionary item (an attribute/field) to apply,
either by applying all defined in the space in sequence, or by looking
one up based on the vendor and attribute numbers obtained from the block
at the given places, it proceeds by decoding the block at the current
offset using the dictionary item found.
A dictionary item has the following properties to that end:
len_ofs, len_size and len_adj: define the place, size and adjustment
for a length field in the A/V pair or field, if any, relative to the
enclosing block;
val_ofs, val_size and val_type: define the place, size and type of
the value of the attribute, relative to the enclosing block.
The relation between the size of the original data block, len_adj and
val_size is as follows.
Take the value of the length field, as given by len_ofs and len_size.
If there is no length field (len_size is 0), take the size of the
enclosing block.
Calculate the 'skip length' for the attribute, that is, the offset of
the next attribute relative to the current one, using len_adj: if
len_adj is positive, then that's the skip length, disregarding step 1
(although if the skip length takes the decoder past the enclosing block,
you get a decode error); if len_adj is negative or zero, then subtract
len_adj from the length obtained in step 1 to get the skip length.
If val_size is positive, then that's the length of the value as it will
be in the decoded A/V pair list; if val_size is negative or zero, then
subtract val_size from skiplen (obtained in step 2) to get the value
size.
If 'nodec' is not 0, then a new A/V pair is created on the request list.
If 'subspace' is defined, then the value is decoded by applying the
given space to the block given by the value.
Encoding goes the other way around, based on the same information. There
are some subtleties involving catch all attributes and some other
things, but if you grasp the above, you should be able to create working
dictionaries for anything that even remotely resembles RADIUS attributes
using the stuff that's already there.