Common Field Types

The majority of communication protocols use relatively small set of various field types. However, the number of various ways used to serialise these fields, as well as handle them in different parts of the code, may be significantly bigger.

It would be impractical to create a separate class for each and every variant of the same type fields. That's why there is a need to use template parameters when defining a frequently used field type. The basic example would be implementing numeric integral value fields. Different fields of such type may have different serialisation lengths.

template <typename TValueType>
class IntValueField
{
public:
    using ValueType = TValueType;

    // Accessed stored value
    ValueType& value() { return m_value; }
    const ValueType& value() const { return m_value; }

    template <typename TIter>
    ErrorStatus read(TIter& iter, std::size_t len) {... /* read m_value */ }

    template <typename TIter>
    ErrorStatus write(TIter& iter, std::size_t len) const {... /* write m_value */ }

private:
    ValueType m_value = 0;    
};

Below is a description of most common fields used by majority of binary communication protocols with the list of possible variations, that can influence how the field is serialised and/or handled.

The Generic Library chapter will concentrate on how to generalise development of any communication protocol by creating a generic library and reusing it in independent implementations of various protocols. It will also explain how to create generic field classes for the types listed below.

Numeric Integral Values

Used to operate with simple numeric integer values.

  • May have different serialisation length: 1, 2, 3, 4, 5, ... bytes. Having

    basic types of std::uint8_t, std::uint16_t, std::uint32_t, ... may be

    not enough. Some extra work may be required to support lengths, such as 3, 5, 6,

    7 bytes.

  • May be signed and unsigned. Some protocols require different serialisation rules

    for signed values, such as adding some predefined offset prior

    to serialisation to make sure that the value being serialised is non-negative. When

    value deserialised, the same offset must be subtracted to get the actual value.

  • May have variable serialisation length, based on the value being serialised,

    such as having Base-128

    encoding.

Enumeration Values

Similar to Numeric Integral Values, but storing the value as enumeration type for easier access.

Bitmask Values

Similar to Numeric Integral Values, but with unsigned internal storage type and with each bit having separate meaning. The class definition should support having different serialisation lengths as well as provide a convenient interface to inquire about and update various bits' values.

Strings

Some protocols serialise strings by prefixing the string itself with its size, others have '\0' suffix to mark the end of the string. Some strings may be allocated a fixed size and require '\0' padding if its actual length is shorter.

Consider how the internal string value is stored. Usually std::string is used. However, what about the bare-metal embedded systems, that disallow usage of dynamic memory allocation and/or exceptions? There needs to be a way to substitute underlying std::string with a custom implementation of some StaticString that exposes similar interface, but receives a maximum storage size as a template parameter.

Lists

There may be lists of raw bytes, list of other fields, or even a group of fields. Similar to Strings, the serialisation of lists may differ. Lists of variable size may require a prefix with their size information. Other lists may have fixed (predefined) size and will not require any additional size information.

The internal storage consideration is applicable here as well. For most systems std::vector will do the job, but for bare-metal ones something else may be required. For example some custom implementation of StaticVector that exposes the same public interface, but receives a maximum storage size as a template parameter. There must be an easy way to substitute one with another.

Bundles

The group of fields sometimes needs to be bundled into a single entity and be treated as a single field. The good example would be having a list of complex structures (bundles).

Bitfields

Similar to Bundles, where every field member takes only limited number of bits instead of bytes. Usually the members of the bitfields are Numeric Integral Values, Enumeration Values, and Bitmask Values

Common Variations

All the fields stated above may require an ability to:

  • set custom default value when the field object is created.

  • have custom value validation logic.

  • fail the read operation on invalid value.

  • ignore the incoming invalid value, i.e. not to fail the read operation, but

    preserve the existing value if the value being read is invalid.

Last updated