RFC 5444 handler library Copyright (c) 2010 Henning Rogge The rfc 5444 library is a combined parser and generator for the 'Generalized Mobile Ad Hoc Network (MANET) Packet/Message Format'. The library is split into a separate reader and writer part, both of them use a generic avl-tree and double-linked list implementation. The reader and writer allow multiple independent code blocks to read overlapping parts of a RFC 5444 data stream and assemble packets and messages together. This allow API users to separate different sub- protocol in their codebase (e.g. NHDP and OLSRv2). ======================= RFC 5444 reader ======================= 1) general information 1.1) using the reader 1.2) rfc5444_reader_tlvblock_context 1.3) rfc5444_reader_tlvblock_entry 1.4) order of callbacks while parsing a rfc5444 stream 2) block callback 2.1) setting up a Block Callback 2.2) implementing the block callback 3) stream callbacks 4) message forwarding 1) General information ********************** The basic blocks of information of rfc5444 are addresses of 1 to 16 bytes and TLVs. TLV is a shortcut for type-length-value. Each piece of data which is not included in packet, message or address header has a type and a length field, which allows a parser which does not know this TLV to skip to the next TLV without being forced to stop parsing the data stream. The RFC 5444 reader is based on the concept of tlv-block consumers as the basic entities for reading RFC 5444. TLV-blocks can exist on three different levels of hierarchy in RFC 5444: * packet TLVs * message TLVs * address TLVs Each consumer registers on one of this three levels and can use two different kinds of callbacks to learn about the relevant TLV-block and its context. Both group of callbacks can be used in the same consumer. The first callback is the block_callback. It allows the consumer to define a set of TLVs it's interested in. The parser collects all TLV data defined in the set and calls the block_callback. Hence the processing is block-oriented. The other callbacks allow a more stream oriented handling of rfc5444 data: * start_callback * tlv_callback * end_callback For each consumer context (packet, message or address) the parser calls the start_callback first, then it calls the tlv_callback for each TLV in the context and finally the end_callback. Each consumer has a priority, which defines the order in which the parser calls the consumers. Early consumers can tell the parser to stop processing the current context or the whole packet, which allows creating of security or semantic checking consumers. Both block and stream callbacks return two kind of data structures, rfc5444_reader_tlvblock_context and rfc5444_reader_tlvblock_entry. The block callback initializes a pointer to the second one for each rfc5444_reader_tlvblock_consumer_entry it finds in the rfc5444 stream. 1,1) Using the reader ********************* The rfc5444 reader is very simple to use. First the user has to allocate a rfc5444 reader context with rfc5444_reader_init(). All functions of the reader require a pointer to this context as a parameter. If you don't need the reader anymore, call rfc5444_reader_cleanup() to free all allocated memory of the reader including the reader context itself. After allocating the context you can register and unregister all necessary callbacks to the reader, as long as you do this outside of the reader callbacks itself! To parse binary rfc5444, just call rfc5444_reader_handle_packet(). Example: (...) struct rfc5444_reader *context; context = rfc5444_reader_init(); assert(context); (...) 1.2) rfc5444_reader_tlvblock_context ******************************** There is a second context: the tlvblock context tells the callback about the enclosing packet header, message header and address entry. It contains all relevant fields of the parsed headers. The first field "type" of the context is an enum that defines the scope of the context. A type CONTEXT_PACKET applies to callbacks for packet scope and packet tlvs, which means that one the fields relevant for packets are initialized. This context will contain the packet version, it's flags field and (if available) the packet sequence number. A type CONTEXT_MESSAGE applies to callbacks for message scope and tlvs. It contains all the data of the packet context in addition to the relevant fields of the message header (msg type, flags, address length and the four optional fields hopcount, hoplimit, originator address and sequence number). The last type CONTEXT_ADDRESS applies to an address and it's tlvs. It contains all data of packet and message context plus the address and its prefix length. The whole struct is read-only, DO NOT MODIFY the fields inside a callback. 1.3) rfc5444_reader_tlvblock_entry ****************************** The tlvblock entry contains the data about a single tlv. It contains the type of the tlv (including the extension type), the raw flags field, the length of the value, a pointer to the binary value (NULL if length is zero) and the index fields for address block TLVs. The whole struct is read-only, DO NOT MODIFY the fields inside a callback. 1.4) Order of callbacks while parsing a rfc5444 stream ******************************************************* The total order of callbacks can be described as: * packet consumer "order 1" - start_callback - tlv_callback (tlv_1) - ... - tlv_callback (tlv_n) - block_callback (tlvs) * packet_consumer "order 2" - start_callback - tlv_callback (tlv_1) - ... - tlv_callback (tlv_n) - block_callback (tlvs) * message_consumer "order 1" - start_callback - tlv_callback (tlv_1) - ... - tlv_callback (tlv_n) - block_callback (tlvs) * address_consumer "order 1" * address addr_1 - start_callback (addr_1) - tlv_callback (addr_1, tlv_1) - ... - tlv_callback (addr_1, tlv_n) - block_callback (tlvs) - end_callback (addr_1) * address addr_2 - start_callback (addr_2) - tlv_callback (addr_2, tlv_1) - ... - tlv_callback (addr_2, tlv_n) - block_callback (tlvs) - end_callback (addr_2) * message_consumer "order 1" - end_callback * message_consumer "order 2" - start_callback - tlv_callback (tlv_1) - ... - tlv_callback (tlv_n) - block_callback (tlvs) - end_callback * address_consumer "order 3" * address addr_1 - start_callback (addr_1) - tlv_callback (addr_1, tlv_1) - ... - tlv_callback (addr_1, tlv_n) - block_callback (tlvs) - end_callback (addr_1) * address addr_2 - start_callback (addr_2) - tlv_callback (addr_2, tlv_1) - ... - tlv_callback (addr_2, tlv_n) - block_callback (tlvs) - end_callback (addr_2) * packet_consumer "order 2" - end_callback * packet_consumer "order 1" - end_callback 2.) Block callback ****************** The block callback allows you to define a filter for a specific context (packet, message or address) and get data about a fixed number of TLVs your callback needs to know about. This makes writing multiple TLV callbacks just to collect the different TLVs from a message or address just to store them in a temporary data structure unnecessary. 2.1) Setting up a Block Callback ******************************** The first option to parse rfc5444 data is the block_callback. The block_callback is initialized by an array of rfc5444_reader_tlvblock_consumer_entry objects. struct rfc5444_reader_tlvblock_consumer_entry consumer[] = { { .type = 4 }, { .type = 1, .match_type_ext = true, .type_ext = 1 }, { .type = 2, .mandatory = true }, { .type = 6, .copy_value = &value, .copy_value_maxlen = sizeof(value) } }; ... struct rfc5444_reader *context; struct rfc5444_reader_tlvblock_consumer *consumer; .... consumer = rfc5444_reader_add_message_consumer(context, consumer, ARRAYSIZE(consumer), 2, /* msg type */ 1 /* order */); consumer->block_callback = my_block_callback; Each entry of the array represents a single TLV type of the context. There are three different optional settings in addition to the tlv type, which can be combined: * mandatory (boolean) * match_type_ext (boolean) and type_ext (uint8_t) * copy_value (void *) and copy_value_maxlen (size_t) By setting mandatory to 'true' in an entry of the rfc5444_reader_tlvblock_consumer_entry array the consumer tells the parser that the TLV is mandatory. The parser keeps track if all mandatory TLVs are available and tells the consumer this by a callback parameter. Setting match_type_ext to 'true' let the consumer specify which extended type of the TLV is relevant for this entry. If not set the type_ext variable is ignored and the first TLV with a matching type (and any kind of extended type) will be used for the block callback. Copy_value tells the parser to copy the value of the TLV to a private buffer of the consumer. The copy_value_maxlen variable has to be set to the maximum number of bytes available in the private buffer to prevent a buffer overflow. 2.2) Implementing the block callback *********************************** The block callback has three parameters. The first is a pointer to the tlvblock consumer object returned by the register function. The second one is a pointer to a rfc5444_reader_tlvblock_context object, which contains the packet sequence number, the decoded content of the message header (for message and address consumers) and the current address (only for address consumers). The last parameter mandatory_missing is true if one of the tlv entries marked as mandatory is missing. enum rfc5444_result callback(struct rfc5444_reader_tlvblock_consumer *consumer, struct rfc5444_reader_tlvblock_context *context, bool mandatory_missing) { ... return RFC5444_OKAY; } For each tlv in the block with a corresponding array entry, the parser initializes a rfc5444_reader_tlvblock_entry pointer in the entry which contains all data about the original tlv, including type(ext), value and/or indices. The default return value of the block callback is RFC5444_OKAY, which tells the parser that everything is right and the next callback should be called. See README.dropcontext for other return values. 3) Stream callbacks ******************* The stream callbacks are a group of three callbacks that are similar to a SAX XML parser. The consumer gets one callback when its context (the packet, message or address) starts, one callback for each TLV inside the context (with a pointer to a rfc5444_reader_tlvblock_context and a rfc5444_reader_tlvblock_entry object) and one callback when the context ends. enum rfc5444_result my_start_callback(struct rfc5444_reader_tlvblock_consumer *, struct rfc5444_reader_tlvblock_context *context) { ... return RFC5444_OKAY; } enum rfc5444_result my_tlv_callback(struct rfc5444_reader_tlvblock_entry *, struct rfc5444_reader_tlvblock_context *context) { ... return RFC5444_OKAY; } enum rfc5444_result my_end_callback(struct rfc5444_reader_tlvblock_consumer *, struct rfc5444_reader_tlvblock_context *context, bool dropped); ... return RFC5444_OKAY; } ... struct rfc5444_reader *context; struct rfc5444_reader_tlvblock_consumer *consumer; .... consumer = rfc5444_reader_add_message_consumer(context, consumer, ARRAYSIZE(consumer), 2, /* msg type */ 1 /* order */); consumer->start_callback = my_start_callback; consumer->tlv_callback = my_tlv_callback; consumer->end_callback = my_end_callback; These three callbacks allow the consumer to analyze the whole tlv-block stream in a linear order without missing duplicate TLVs or unknown one, but are more complex to use for most protocol implementations. The end callback has an additional boolean parameter called drop, which tells the callback that the context has been dropped by an earlier callback. The default return value of this callbacks is RFC5444_OKAY, which tells the parser that everything is right and the next callback should be called. See README.dropcontext for other return values. 4) message forwarding ********************* Each time after a message has been parsed by all callbacks the parser checks if it should be forwarded to other nodes (hoplimit must be present in the header for this). If yes the message will be forwarded by calling the forward_message() callback of the reader context.