forked from osmocom/wireshark
219 lines
8 KiB
Text
219 lines
8 KiB
Text
$Id$
|
|
|
|
1. Introduction
|
|
|
|
Binary trees is a well known and popular device in computer science to handle
|
|
storage of object based on a search key or identity.
|
|
One particular class of binary trees are Red/Black trees which have nice
|
|
properties such as being self-balanced.
|
|
Such trees are often very fast for accessing data, and may average O(log(n))
|
|
for lookups, compared to linked lists that are of order O(n).
|
|
|
|
Benefits of using binary trees are that they are incredibly fast for
|
|
accessing data and they scale very well with good characteristics even to
|
|
very large number of objects.
|
|
|
|
Wireshark provides its own version of red black binary trees designed in
|
|
particular to be easy to use and to eliminate most of the memory management
|
|
often associated with such trees.
|
|
|
|
The trees supported by wireshark are currently all created using SEasonal
|
|
storage which means that when you load a new trace into wireshark, the SEasonal
|
|
memory management will automatically release every single byte of data
|
|
associated with the tree.
|
|
|
|
Please see README.malloc for a description of EmPhereal and SEasonal storage.
|
|
|
|
|
|
2. Basic Usage
|
|
For most users it will be sufficiant to only know and use three functions
|
|
emem_tree_t *se_tree_create(int type, char *name);
|
|
void se_tree_insert32(emem_tree_t *se_tree, guint32 key, void *data);
|
|
void *se_tree_lookup32(emem_tree_t *se_tree, guint32 key);
|
|
|
|
|
|
2.1 se_tree_create(int type, char *name);
|
|
se_tree_create() is used to initialize a tree that will be automatically
|
|
cleared and reset everytime wireshark is resetting all SEasonal storage,
|
|
that is every time you load a new capture file into wireshark or when
|
|
you rescan the entire capture file from scratch.
|
|
|
|
Name is just a literal text string and serves no other purpose than making
|
|
debugging of the trees easier. Specify a name here that uniquely identifies
|
|
both the protocol you create the tree for and its purpose.
|
|
|
|
|
|
This function is most likely called once from your
|
|
proto_register_<yourprotocol>() function.
|
|
|
|
Example (from packet-tcp.c):
|
|
#include <epan/emem.h>
|
|
...
|
|
static emem_tree_t *tcp_pdu_time_table = NULL;
|
|
...
|
|
void proto_register_...(void) {
|
|
...
|
|
tcp_pdu_time_table=se_tree_create(EMEM_TREE_TYPE_RED_BLACK, "PROTO_my_tree");
|
|
...
|
|
}
|
|
|
|
That is how easy it is to create a binary tree. You only need to create it once
|
|
when wireshark starts and the tree will remain there until you exit wireshark.
|
|
Everytime a new capture is loaded, all nodes allocated to the tree is
|
|
automatically and the tree is reset without you having to do anything at all.
|
|
|
|
|
|
2.2 se_tree_insert32(emem_tree_t *se_tree, guint32 key, void *data);
|
|
This function is used to add a new node into the tree.
|
|
This function is used for such trees where you always use a guint32 as the
|
|
identifier/key for the node. Most trees you want to use are likely in this
|
|
category.
|
|
|
|
As data you should specify a pointer to the data structure you want to be
|
|
able to retreive later when you look for that same key.
|
|
|
|
NOTE: If you insert a node to a key that already exists in the tree
|
|
this function will allow you to do that. It will just drop the old pointer
|
|
to data and replace it with the new one you just provided.
|
|
This should not be a problem as long as the old and the new data blobs
|
|
are se_allocated() since you cant free() such memory explicitely anyway
|
|
and the old one will be release automatically anyway when the SEasonal
|
|
system reclaims all the SE data.
|
|
|
|
NOTE: It is a good idea to only provide data that point to blobs allocated
|
|
bu se_alloc(). By doing that you will have a tree where the tree and all
|
|
the data pointed to will be automatically released by the system at the same
|
|
time.
|
|
This is very neat and makes real difficult to have memory leaks in your code.
|
|
|
|
NOTE: When you insert items in the tree, it is very likely that you only
|
|
want to add any data to the tree during the very first time you process
|
|
a particular packet.
|
|
Wireshark may reprocess the same packet multiple times afterwards by the user
|
|
clicking on the packet or for other reasons.
|
|
You probably DO want to protect the insert call within an if statement such
|
|
as
|
|
|
|
Example:
|
|
/* only TRUE first time we process this packet*/
|
|
if(!pinfo->fd->flags.visited){
|
|
...
|
|
data=se_alloc(...);
|
|
data->...
|
|
...
|
|
se_tree_insert32(se_tree, key, data);
|
|
...
|
|
}
|
|
|
|
Please do think about how and when you want to add items to the tree.
|
|
If you dont think you should not use the if statement to protect the insert
|
|
please reconsider and think it through one extra time.
|
|
|
|
|
|
2.3 se_tree_lookup32(emem_tree_t *se_tree, guint32 key);
|
|
This function is used to read back from the tree the data value that is
|
|
associated with this key.
|
|
If no such node was found the function will return NULL.
|
|
|
|
Example:
|
|
...
|
|
data_structure = se_tree_lookup32(se_tree, key);
|
|
if(data_structure){
|
|
...
|
|
}
|
|
|
|
Dont forget to check that the returned pointer is non-NULL before you
|
|
dereference it, please.
|
|
|
|
|
|
Simple as that, can it be easier?
|
|
|
|
|
|
|
|
|
|
3. Advanced Usage
|
|
This will list some of the more unconventional and hopefully rarely used
|
|
functions.
|
|
|
|
3.1 se_tree_create_non_persistent(int type, char *name);
|
|
Sometimes you dont want a tree that is automatically reset to become an empty
|
|
tree. You might want a tree that is completely destroyed once the next
|
|
capture file is read and even the pointer to the tree itself becomes invalid.
|
|
|
|
This would most likely be the case when you do NOT want a global tree
|
|
but instead a tree that is held inside some other SE allocated structure.
|
|
So that when that encapsulating structure is released the entire tree will
|
|
dissapear completely as well.
|
|
|
|
Maybe you have a normal tree to track all conversations for your protocol
|
|
and for each conversation you se_alloc() a structure to maintain some
|
|
data about that conversation. Assume you want to add to that structure
|
|
another binary tree a binary tree that is private to that structure/
|
|
conversation to hold some other data.
|
|
In that case, this is the function you want.
|
|
|
|
|
|
3.2 se_tree_insert32_array / se_tree_lookup32_array
|
|
typedef struct _emem_tree_key_t {
|
|
guint32 length; /*length in guint32 words */
|
|
guint32 *key;
|
|
} emem_tree_key_t;
|
|
void se_tree_insert32_array(emem_tree_t *se_tree, emem_tree_key_t *key, void *data);
|
|
void *se_tree_lookup32_array(emem_tree_t *se_tree, emem_tree_key_t *key);
|
|
|
|
NOTE: the *key parameter taken by these functions WILL be modified by
|
|
these functions so if you want to reuse the key for a second call
|
|
you will have to reinitialize key.
|
|
|
|
|
|
These functions are used to insert and lookup a tree where nodes are NOT
|
|
indexed by a single guint32 but more like an array of guint32 elements.
|
|
|
|
These functions take as key an array of guint32 vectors : emem_tree_key_t.
|
|
The fucntions will use vector by vector to search further down the tree
|
|
until an array element where length==0 is found indicating the end of the
|
|
array.
|
|
|
|
NOTE: you MUST terminate the emem_tree_key_t array by {0, NULL}
|
|
If you forget to do this wireshark will immediately crash.
|
|
|
|
NOTE: length indicates the number of guint32 values in the vector, not number
|
|
of bytes.
|
|
|
|
If your keys are always of exactly the same length, always, and you are willing
|
|
to bet that there can never ever be a key of a different length you can
|
|
get away with a simple :
|
|
emem_tree_key_t key[2];
|
|
key[0].length= LEN;
|
|
key[0].key= KEY;
|
|
key[1].length=0;
|
|
key[1].key=NULL;
|
|
se_tree_insert32_array(se_tree, &key[0], data);
|
|
But IF you would accidentally pass a key with a different number of guint32s
|
|
in its vectors to this same se_tree you will crash.
|
|
Dont use key like this. Please.
|
|
|
|
|
|
Instead use this simple workaround to make it all safe :
|
|
Specify the first index as 1 guint32 holding the total number of guints in the
|
|
rest of the key.
|
|
See NFS that does this to handle filehandes that may be of different lengths
|
|
in the same tree :
|
|
emem_tree_key_t fhkey[3];
|
|
guint32 tmplen;
|
|
|
|
tmplen = <length of filehandle/4>;
|
|
fhkey[0].length = 1;
|
|
fhkey[0].key = &tmplen;
|
|
fhkey[1].length = tmplen;
|
|
fhkey[1].key = <filehandle>;
|
|
fhkey[2].length = 0;
|
|
fhkey[2].key = NULL;
|
|
|
|
( /4 since we specify the length here in number of guint32 not number of bytes)
|
|
|
|
|
|
3.3 se_tree_insert_string / se_tree_lookup_string
|
|
to be added...
|
|
|