Custom decoding¶
Custom decoding allows blueSPY users to implement their own higher-layers of packet decoding on top of the existing ones. A user writes a C/C++ library which provides a function that blueSPY will call when it encounters the specified events. This function can then add higher level events to represent the user defined protocol data from one or more built-in events.
Custom decoders should be used only in the case where you want to create new events to be shown in blueSPY. If you want to perform actions external to blueSPY such as logging or network requests based on blueSPY events, this is possible with the standard blueSPY API.
Writing a custom decoder¶
An example custom decoder is included in the blueSPY release inside the example_custom_decoder directory.
All custom decoders must include a function called init which is called when the decoder library is loaded.
When compiling your custom decoder, ensure that this function is visible in the shared section of the resulting shared library so that blueSPY can call it.
You will want to define one or more callback functions and then register them in the init function with bluespy_register_event_callback(event_type, callback).
The event type specifies which events the callback should be called on. You can register more than one callback for a single event type and can register one callback to many event types.
Callback¶
A callback function should have the following format:
Within the callback, you can use the bluespy_add_event function to add events representing your higher layer protocol.
A custom event is specified by the following struct.
typedef struct bluespy_custom_event {
bluespy_event_id* children;
unsigned int n_children;
bluespy_query_value (*query)(const struct bluespy_custom_event* self, const char* query_str,
bool prefer_string);
} bluespy_custom_event;
bluespy_add_event you should not change it.
If you wish to add such an event with multiple children, wait until you have the full list of children before submitting it.
In order to store information about your events you can subclass the custom event (in C++), or have it as the first member of another struct (C-style inheritance), then pass that to bluespy_add_event, and later cast the self pointer to your custom type in your query function.
Children¶
blueSPY combines events from the bottom up through the Baseband/Logical/Transaction/Application aggregation layers you can find in the GUI. One or more events at the Baseband layer (the children) can be combined into one Logical (e.g. L2CAP) packet (the parent). If you submit an event that has baseband children, it will appear in the 'Logical' level or above. You can add further custom events that have your own custom events as children to combine into higher layers. All events must have at least one child. All events can have at most one parent, so if you submit 2 events with the same children array, the first one you submitted will be replaced. If you submit a new parent of an event that blueSPY has interpreted as having a different type of parent, then it will replace blueSPY's interpretation.
Queries¶
The query member is a pointer to a function that is used to determine features of your event.
There are many standard queries in blueSPY, e.g. the text in the Summary column in the GUI will use the "summary" query on your custom event, and the Raw view will use "payload".
Several queries such as "duration" and "rssi" will have default values based on the children if you do not customise them.
You can also add queries of your own; these will not show in the blueSPY GUI but can be used by within your custom decoders.
Custom query names must consist of alphanumeric characters and underscores, and start with a letter (the name should match the regex [A-Za-z][A-Za-z0-9_]*).
You may return different values on later calls to query based on new packets.
For example you may wish to update information about a connection in the details view.
query will be called in a different thread to bluespy_add_event, so you should be careful when looking at state that may be changed by bluespy_add_event. (bluespy_add_event itself is only ever called in one thread).
Details¶
One of the queries you should implement is "details".
This query is used to show the event as a tree structure in the Details tab.
You must return json in the expected format in order for blueSPY to parse it and show it correctly.
[
{"name": "Name", "value": "Value"},
{
"name": "Name2",
"value": [
{"name": "Name3", "value": 1},
{"name": "Name4", "value": 2}
]
}
]
The expected JSON structure is shown above. It must be an array of objects. Each object represents a field that will be shown in the UI. A field object must include two keys: "name" which corresponds to a string value and "value" which corresponds to a string, number, boolean or array which recursively follows these rules.
Fields can also include the keys "bitpos" and "bitwidth". These are used to indicate which bits in the packet this field corresponds to so that this can be highlighted in the Raw tab.
Allocation¶
Event objects must exist for as long as the capture file is open.
To aid with this, there is a bluespy_allocate (and in C++ bluespy::allocate) function that automatically frees the objects when they are no longer needed.
You should not call bluespy_allocate from each query, as this will cause ever increasing memory usage.
Due to the multithreaded nature of blueSPY if you open a new capture the cleanup for the old capture's events may occur concurrently with the new capture's callbacks.
Loading Custom decoders¶
You can have blueSPY load custom decoders in one of two ways:
-
You can add the compiled decoders to the blueSPY data directory. This is
~/.local/share/RFcreations/blueSPY/custom_decoderson Linux,~/Library/Application Support/RFcreations/blueSPY/custom_decoderson MacOS andC:\Users\<UserName>\AppData\Roaming\RFcreations\blueSPY\custom_decoderson Windows. This will ensure that the custom decoders are loaded every time you open blueSPY. -
You can set the
BLUESPY_CUSTOM_PROTOCOL_DIRenvironment variable to be the path of a directory containing the compiled decoders. This is useful for testing your decoder during development or for decoders you don't want to be using most of the time.