Writing a Packet Template

In this tutorial, we will show you how to define your own packet templates that you can later conveniently fill using the property grid.

Simple Packet

Inside the Transmit pane, switch to the Binary Tab, and click the Use packet template button:

_images/use-packet-template-button.png

The property grid and the packet template selector will appear:

_images/empty-packet-template-list.png

The packet template selector is empty, because there are no packet templates defined yet. Click the Edit packet template button, and the Packet Template Editor dialog will appear. Here you can describe the templates for your packets by defining C-like struct-s.

Copy paste the following code:

alignment(1);

struct MyPacket
{
    uint8_t m_byte;
    uint16_t m_word;
    uint32_t m_dword;
    uint64_t m_qword;
}

This will yield the following result:

_images/simple-packet-template.png

You can experiment with filling the fields both in the property grid and in the hex-editor and observe how they synchronize automatically.

Note

When filling in a field vie the property grid, you can use both decimal and hexadecimal (prefixed with 0x) notations.

Pay attentrion to the alignment declaration. Most times, you want to start your packet template script with the following line:

alignment(1);

This is to prevent the Jancy compiler from inserting implicit paddings to try and align fields on the default 8-byte boundaries. By default, Jancy aligns fields just like most modern C-compilers do (they use the 8-byte alignment by default). The detailed description of what alignment is for and how it works is beyond the scope of this document. Just remember that when you notice some unexpected offsets for your fields, it most likely means that you’ve forgotten about the alignemnt(1) directive.

Big-endian Fields

More often than not, network protocols are using the big-endian format for integers. It means, the most-significant byte is transmitted first, and the least-signinficant byte is transmitted last. Because this is such a common thing in network protocols, this byte order is even called the network byte order!

At the same time, on Intel archictures that dominate the PC market, integers are natively representned using the little-endian format – that is, when an integer is stored in memory, the least-significant byte occupies the lower memory address, and the most-significant byte goes into the higher address.

What that means, is that byte order conversions are simply ubiquitos in network programming, and because they are used in so many places, it’s super-easy to forget about it in once or twice – and that all it takes for your program to malfunction!

Jancy, being the language that targets the IO programming, has the native support for big-endians. All you have to do is to declare a field as bigendian, and all the necessary byte-order conversions will happen automatically behind the scene!

Try changing your packet struct as such:

struct MyPacket
{
    uint8_t m_byte;
    bigendian uint16_t m_word;
    bigendian uint32_t m_dword;
    bigendian uint64_t m_qword;
}

Then observe the effects of this change by modifying values of the last three fields.

Enumerated Fields

Sometimes, a field does not carry an arbitrary number, but rather can only hold a value from some predefined enumeration. For example, the EtherType field of the Ethernet II header typically only holds one of the values defined here: https://en.wikipedia.org/wiki/EtherType

When you fill your packet using the property grid, it’s desireable to have such fields represented by combo-boxes with a fixed set of drop-down values.

In order to achieve this, define an enumeration as such:

enum MyCommand: uint8_t
{
    Handshake,
    Play,
    Pause,
    Stop,
}

Then add a field of this type to your packet template:

struct MyPacket
{
    MyCommand m_command;
    ...
}
_images/packet-template-enum.png

You can even use bigendian integers when definining the enumeration:

enum MyCommand: bigendian uint16_t
{
    Handshake = 0x0123,
    Play      = 0x4567,
    Pause     = 0x89ab,
    Stop      = 0xcdef,
}

Custom Actions

Filling packet fields with the property grid is both very convenient and much less error-prone than writing it by hand in the hex-editor – now, after you’ve tried that for yourself, you will most certainly agree.

However, certain things are still hard to fill even with the property grid – the checksum field would be the first thing that comes to mind here. In many protocols, after you’ve filled all the fields of a packet, you need to calculate the checksum and add it to the packet, as well – otherwise, the receiving party will simply dismiss your packet as an invalid one.

To save the day, here come the custom actions.

Besides fields, your packet template can also define user-invokable methods which can be programmatically modify the packet as necessary.

To mark a method as user-invokable, add the [ packetTemplateAction ] attribute as such:

struct MyPacket
{
    MyCommand m_command;
    uint8_t m_byte;
    bigendian uint16_t m_word;
    bigendian uint32_t m_dword;
    bigendian uint64_t m_qword;
    bigendian uint64_t m_checksum;

    [ packetTemplateAction ]
    void calcChecksum()
    {
        m_checksum = m_command + m_byte + m_word + m_dword + m_qword;
    }
}

After that you will see a new clickable hyperlink called calcChecksum, which will update the m_checksum field appropriately. The end result can be seen below:

_images/packet-template-action.png