Implementing callee side

Unpadded provides tools for receiving and processing incoming packet from the caller. Depending on your needs, there are several options available, especially for embedded platforms.

The following sections will assume these functions to be defined by the callee device:

void write_byte_to_master(upd::byte_t);
upd::byte_t read_byte_from_master();

Basics : fulfiling incoming request with dispatchers

Processing incoming invocation requests and sending back the result of this invocation to the caller is done with the dispatcher classes. There are four of them:

  • dispatcher, the most basic one, which provide no internal buffer;

  • buffered_dispatcher, which use user-provided buffers;

  • single_buffered_dispatcher, which use a single internal buffer for both receiving and sending;

  • double_buffered_dispatcher, which use two separate internal buffers: one for receiving and one for sending;

Receiving a packet and sending back a response packet is pretty straightforward:

// We assume that a `keyring` object has been defined and
// both the caller and the callee use this keyring.

// We create a dispatcher which can handle request for
// the invocables managed by `keyring`. The second
// argument will be discussed later.
static upd::dispatcher dispatcher{keyring, upd::policy::any_callback};

// Let's assume this function is invoked when the input
// buffer contains a full request.
void processing_request() {
  // And, that's it ! With this single line, the dispatcher
  // will automatically extract the bytes from the input
  // buffer and write the response to the output buffer.
  dispatcher(read_byte_from_master, write_byte_to_master);
}

Buffered dispatchers vs. non-buffered dispatchers

Both kind of dispatchers use callbacks to read and write the bytes of the packets. However:

  • Non-buffered dispatchers will immediately read all the bytes needed for a full packet, regardless of whether these bytes have actually all been receive at this point. In other words, the provided invocable which is used to get the bytes one by one will be called reapetedly, and it is up to you to implement the correct behavior. For example, you may block until at least one byte is available. The most sensible solution would be to call the dispatcher only when enough bytes have been received or to use synchronization primitives in order to wait for new bytes to come without blocking the device.

  • Buffered dispatchers use buffers which allow you to store incomplete packets. That means that you may receive a part of the packet, then do something else to finally receive the rest later.

Non-buffered dispatchers may be a viable option when you can afford to block the execuction of your program or can use an RTOS. It may be also be useful if you have specific needs and want to implement your own dispatcher, since it is the most basic form of dispatcher.

Buffered dispatchers are best used when you can’t afford to waste machine cycles. Most often, communication peripherals of microcontrollers can only receive incoming data in a fixed-width FIFO and same goes with sending. In that case, buffered dispatchers are suitable, since they provide a buffer big enough to receive an action request or response.

Single buffered dispatchers vs. double buffered dispatchers

Single buffered dispatchers only have a single internal buffer, which is used to store both incoming packets and packets to send back. However, you can’t receive any packet as long as you have a packet to send in the buffer.

Double buffered dispatchers, on the other hand, use separate buffers for input and output. That means that you can receive an incoming request and sending a response packet at the same time. Only one packet can fit in both buffers.

You can implement your owm kind of buffered dispatcher using the buffered_dispatcher class. On the other hand, single_buffered_dispatcher and double_buffered_dispatcher come with their own internal buffers, whose sizes are optimized to handle packets no larger than what you should receive or send. These sizes are deduced at compile-time with CTAD (or template argument deduction is you are using make_single_buffered_dispatcher or make_double_buffered_dispatcher) using the provided keyring.

Example

In the follwing example, we assume that on_byte_received is invoked whenever a byte has been received from callee (which can be retrieved using read_byte_from_callee) and on_byte_sent is invoked whenever a byte has been fully sent to callee with the function write_byte_to_callee.

motion_keyring.hpp
#pragma once

#include <cstdint>

#include <upd/format.hpp>
#include <upd/keyring.hpp>
#include <upd/typelist.hpp>

void set_forward_speed(std::uint32_t);
void set_left_steering_speed(std::uint32_t);
void set_right_steering_speed(std::uint32_t);

constexpr upd::keyring motion_keyring{upd::flist<set_forward_speed, set_left_steering_speed, set_right_steering_speed>,
                                      upd::little_endian,
                                      upd::twos_complement};

Hot swapping callbacks

You can replace what callback will invoked after your dispatcher has been created with the replace member function. However, it must be invocable in the same way (ie. it must be invocable on the same parameter as the replaced callback, and it must return a value of the same type). Depending on the storage policy, you can even replace the callbacks with callback of any storage duration.

Storage policy

Depending on what you can afford to do, you will want your invocables to be stored differently. There is two available policies at the moment:

  • Allowing any kind of callback to be stored with the any_action policy. It means that the dispatcher is responsible for the life cycle of the callbacks, and it therefore uses dynamic allocation.

  • Allowing callback with static storage duration only with the weak_reference policy. In that case, the dispatcher doesn’t need to manage the life cycle of the callbacks because it restricts the callbacks to be allocated statically. In that case, the dispatcher merely refers to the callbacks without keeping it alive, hence the name of the policy. This policy works well with plain functions, since they exist: in program memory which is usually not modified. It can also work with function objects, but in that case, the object cannot live on the heap or on the stack. It must be alive during the whole execution of the program.

The latter policy is more appropriate for microcontrollers.

API References

dispatcher

template<typename Keyring, action_features Action_Features>
class dispatcher : public upd::detail::immediate_process<dispatcher<Keyring, Action_Features>, Keyring::index_t>

Action container able to accept and process action requests.

A dispatcher is constructed from a keyring instance and is able to unserialize a payload serialized by a key created from the same keyring. When it happens, the dispatcher calls the callback associated with the key, forwards the arguments from the payload to the callback. The functions are internally held as action instances.

Template Parameters:
  • Keyring – Keyring describing the actions to manage

  • Action_Features – Restriction on stored actions

Public Types

using signatures_t = typename Keyring::signatures_t

Typelist containing the signatures of the callbacks.

using index_t = typename Keyring::index_t

Type of the index prepended to the payload when sending a packet to the callee.

Public Functions

inline explicit dispatcher(Keyring, action_features_h<Action_Features>)

Construct the object from the provided keyring.

inline dispatcher()

Construct the object from the provided keyring.

template<typename Src, typename Dest>
inline index_t operator()(Src &&src, Dest &&dest)

Extract an index from a byte sequence then invoke the action with that index.

The parameters for the action call are extracted from src and the return value is inserted into dest.

The following member functions are also defined through CRTP.

auto operator()(Input &&, Output &&);
auto operator()(Input &&, Output &&) const;
All these functions work the same way as operator()(Src &&, Dest &&).

Template Parameters:
  • Input – Input byte stream type of any kind

  • Output – Output byte stream type of any kind

Parameters:
  • src – Byte getter

  • dest – Byte putter

Returns:

the index of the called action

template<typename Src>
inline action_t *get_action(Src &&src)

Extract an index from a byte sequence and get the action with that index.

Parameters:

src – Byte getter

Returns:

Either a reference to the action if it exists or nullptr

template<typename Src>
inline index_t get_index(Src &&src) const

Extract an index from a byte sequence.

Parameters:

src – Byte getter

Returns:

The extracted index

template<index_t Index, typename F, F Ftor>
inline void replace(unevaluated<F, Ftor>)

Replace with a free function or a callback with static storage duration.

Template Parameters:
  • Index – Index of the action to replace

  • Ftor – Free function or callback with static storage duration

template<index_t Index, auto &Ftor>
inline void replace()

(C++17) Replace with a free function or a callback with static storage duration

Template Parameters:
  • Index – Index of the action to replace

  • Ftor – Free function or callback with static storage duration

template<index_t Index, typename F>
inline void replace(F &&ftor)

Replace with a callback of any kind.

Template Parameters:

Index – Index of the action to replace

Parameters:

ftor – Callback of any kind

inline action_t &operator[](index_t index)

Get one of the stored actions.

Warning

No bound check is performed.

Parameters:

index – Index of an action

Returns:

the action associated with that index

inline const action_t &operator[](index_t index) const

Get one of the stored actions.

Warning

No bound check is performed.

Parameters:

index – Index of an action

Returns:

the action associated with that index

Public Static Attributes

static constexpr auto size = Keyring::size

Number of managed callbacks.

static constexpr auto endianess = Keyring::endianess

Endianess of the data in the packets built by the keys.

static constexpr auto signed_mode = Keyring::signed_mode

Signed number representation of the data in the packets built by the keys.

template<typename Keyring, action_features Action_Features>
dispatcher<Keyring, Action_Features> make_dispatcher(Keyring, action_features_h<Action_Features>)

Make a dispatcher.

buffered_dispatcher

template<typename D, typename Dispatcher>
class buffered_dispatcher : public upd::detail::immediate_reader<buffered_dispatcher<D, Dispatcher>, packet_status>, public upd::detail::immediate_writer<buffered_dispatcher<D, Dispatcher>>, public upd::detail::immediate_process<buffered_dispatcher<D, Dispatcher>, packet_status>

Dispatcher with input / output storage.

Instances of this class may store input and output byte streams while they are received or sent. This allows the user to load and unload the dispatcher byte after byte, whereas plain dispatchers cannot buffer their input and output. A buffered dispatcher goes through the following states:

  1. The input buffer is empty, ready to accept an action request.

  2. Once a full action request has been received, it is immediately fulfilled and the result is written to the output buffer. The input buffer is reset, thus it may receive a new request while the output buffer is unloaded.

  3. Once the output buffer is empty, it may be written again.

This class is not self-sufficient and must be derived from according to the CRTP idiom.

Note

It is possible to use a single buffer as input and output as long as the reading and the writing does not occur at the same time. For that purpose, is_loaded() will indicate whether the output buffer is empty or not.

Template Parameters:
  • D – Derived class

  • Dispatcher – Type of the underlying dispatcher

Public Types

using index_t = typename Dispatcher::index_t

Type of the index prepended to the payload when sending a packet to the callee.

Public Functions

template<typename Keyring, action_features Action_Features>
inline explicit buffered_dispatcher(Keyring, action_features_h<Action_Features>)

Initialize the underlying plain dispatcher with a keyring.

Template Parameters:
  • Keyring – keyring template instance

  • Action_Features – Allowed action features for the managed actions

inline buffered_dispatcher()

Initialize the underlying plain dispatcher with a keyring.

Template Parameters:
  • Keyring – keyring template instance

  • Action_Features – Allowed action features for the managed actions

inline bool is_loaded() const

Indicates whether the output buffer contains data to send.

Returns:

true if and only if the next call to put() or write_to() will have a visible effect

template<typename Src>
inline packet_status read_from(Src &&src)

Put bytes into the input buffer until a full action request is stored.

The following member functions are also defined through CRTP.

R operator<<(Src &&);
R operator<<(Src &&) const;
R read_from(It);
R read_from(It) const;
R operator<<(It);
R operator<<(It) const;
All these functions work the same way as read_from(Src &&).

Template Parameters:
  • Src – Byte getter type

  • It – Input iterator type

Parameters:

src – Byte getter

Returns:

one of the following :

  • packet_status::DROPPED_PACKET: The received index was invalid and the input buffer content was therefore discarded.

  • packet_status::RESOLVED_PACKET: The packet was fully loaded and the associated action has been called (the input buffer is empty and the output buffer contains the result of the action invocation).

inline packet_status put(byte_t byte)

Put one byte into the input buffer.

Parameters:

byte – Byte to put

Returns:

one of the following :

  • packet_status::LOADING_PACKET: The packet is not yet fully loaded.

  • packet_status::DROPPED_PACKET: The received index was invalid and the input buffer content was therefore discarded.

  • packet_status::RESOLVED_PACKET: The packet was fully loaded and the associated action has been called (the input buffer is empty and the output buffer contains the result of the action invocation).

template<typename Dest>
inline void write_to(Dest &&dest)

Completely output the output buffer content.

The following member functions are also defined through CRTP.

void operator>>(Dest &&);
void operator>>(Dest &&) const;
void write_to(It);
void write_to(It) const;
void operator>>(It);
void operator>>(It) const;
All these functions work the same way as write_to(Dest &&).

Template Parameters:
  • Dest – Byte putter type

  • It – Output iterator type

Parameters:

dest – Byte putter

inline byte_t get()

Output one byte from the output buffer.

If the output buffer is empty, the function will return an arbitrary value.

Returns:

the next byte in the output buffer (if it is not empty) or an arbitrary value

template<index_t Index, typename F, F Ftor>
inline void replace(unevaluated<F, Ftor>)

Replace with a free function or a callback with static storage duration.

Template Parameters:
  • Index – Index of the action to replace

  • Ftor – Free function or callback with static storage duration

template<typename Src, typename Dest>
inline packet_status operator()(Src &&src, Dest &&dest)

Call read_from() then write_to()

write_to() is called if and only the output buffer has been populated by read_from().

The following member functions are also defined through CRTP.

auto operator()(Input &&, Output &&);
auto operator()(Input &&, Output &&) const;
All these functions work the same way as operator()(Src &&, Dest &&).

Template Parameters:
  • Input – Input byte stream type of any kind

  • Output – Output byte stream type of any kind

Parameters:
  • src – Byte getter

  • dest – Byte putter

Returns:

the enumerator instance resulting from the read_from() call

template<index_t Index, auto &Ftor>
inline void replace()

(C++17) Replace with a free function or a callback with static storage duration

Template Parameters:
  • Index – Index of the action to replace

  • Ftor – Free function or callback with static storage duration

template<index_t Index, typename F>
inline void replace(F &&ftor)

Replace with a callback of any kind.

Template Parameters:

Index – Index of the action to replace

Parameters:

ftor – Callback of any kind

template<typename Output, typename Key>
inline bool reply(Output &&output, Key k)

Forward the content of the output buffer to another dispatcher as an action request.

This function send an action request to the other dispatcher, which is supposed to process directly the content of the output buffer. The requested action must accept a single byte buffer as sole argument (the size of the buffer can be choosen at compile time). The content of the received byte sequence is the same as if it was written by write_to(), so it can be deserialized inside the requested action by using the key that was used to populate the output buffer.

This function can only be used if the data in the output buffer is complete (i.e. if no call to get() has been made since the last packet resolution) and if the requested action buffer is large enough to hold all the data to send. When this function has finished executing, the output buffer is empty.

Parameters:
  • output – Output byte stream of any kind

  • k – Key of the action to request on the other dispatcher

Returns:

true if and only if the content of the output buffer has been written to the output byte stream

inline action_t &operator[](index_t index)

Get one of the stored actions.

Warning

No bound check is performed.

Parameters:

index – Index of an action

Returns:

the action associated with that index

inline const action_t &operator[](index_t index) const

Get one of the stored actions.

Warning

No bound check is performed.

Parameters:

index – Index of an action

Returns:

the action associated with that index

single_buffered_dispatcher

template<typename Dispatcher>
class single_buffered_dispatcher : public upd::buffered_dispatcher<single_buffered_dispatcher<Dispatcher>, Dispatcher>

Implements a dispatcher using a single buffer for input and output.

The buffer is allocated statically as a plain array. Its size is as small as possible for holding any action request and any action response.

Warning

It is not possible to read a request and write a response at the same time. If you need to do that, use double_buffered_dispatcher instead.

Template Parameters:

Dispatcher – Underlying dispatcher type

Public Functions

template<typename Keyring, action_features Action_Features>
inline explicit single_buffered_dispatcher(Keyring, action_features_h<Action_Features>)

Initialize the underlying dispatcher.

Template Parameters:
  • Keyring – Keyring which holds the actions to be managed by the dispatcher

  • Action_Features – Features of the actions managed by the dispatcher

single_buffered_dispatcher() = default

Initialize the underlying dispatcher.

inline bool is_loaded() const

Indicates whether the output buffer contains data to send.

Returns:

true if and only if the next call to put() or write_to() will have a visible effect

inline packet_status read_from(Src &&src)

Put bytes into the input buffer until a full action request is stored.

The following member functions are also defined through CRTP.

R operator<<(Src &&);
R operator<<(Src &&) const;
R read_from(It);
R read_from(It) const;
R operator<<(It);
R operator<<(It) const;
All these functions work the same way as read_from(Src &&).

Template Parameters:
  • Src – Byte getter type

  • It – Input iterator type

Parameters:

src – Byte getter

Returns:

one of the following :

  • packet_status::DROPPED_PACKET: The received index was invalid and the input buffer content was therefore discarded.

  • packet_status::RESOLVED_PACKET: The packet was fully loaded and the associated action has been called (the input buffer is empty and the output buffer contains the result of the action invocation).

inline packet_status put(byte_t byte)

Put one byte into the input buffer.

Parameters:

byte – Byte to put

Returns:

one of the following :

  • packet_status::LOADING_PACKET: The packet is not yet fully loaded.

  • packet_status::DROPPED_PACKET: The received index was invalid and the input buffer content was therefore discarded.

  • packet_status::RESOLVED_PACKET: The packet was fully loaded and the associated action has been called (the input buffer is empty and the output buffer contains the result of the action invocation).

inline void write_to(Dest &&dest)

Completely output the output buffer content.

The following member functions are also defined through CRTP.

void operator>>(Dest &&);
void operator>>(Dest &&) const;
void write_to(It);
void write_to(It) const;
void operator>>(It);
void operator>>(It) const;
All these functions work the same way as write_to(Dest &&).

Template Parameters:
  • Dest – Byte putter type

  • It – Output iterator type

Parameters:

dest – Byte putter

inline byte_t get()

Output one byte from the output buffer.

If the output buffer is empty, the function will return an arbitrary value.

Returns:

the next byte in the output buffer (if it is not empty) or an arbitrary value

inline packet_status operator()(Src &&src, Dest &&dest)

Call read_from() then write_to()

write_to() is called if and only the output buffer has been populated by read_from().

The following member functions are also defined through CRTP.

auto operator()(Input &&, Output &&);
auto operator()(Input &&, Output &&) const;
All these functions work the same way as operator()(Src &&, Dest &&).

Template Parameters:
  • Input – Input byte stream type of any kind

  • Output – Output byte stream type of any kind

Parameters:
  • src – Byte getter

  • dest – Byte putter

Returns:

the enumerator instance resulting from the read_from() call

inline bool reply(Output &&output, Key k)

Forward the content of the output buffer to another dispatcher as an action request.

This function send an action request to the other dispatcher, which is supposed to process directly the content of the output buffer. The requested action must accept a single byte buffer as sole argument (the size of the buffer can be choosen at compile time). The content of the received byte sequence is the same as if it was written by write_to(), so it can be deserialized inside the requested action by using the key that was used to populate the output buffer.

This function can only be used if the data in the output buffer is complete (i.e. if no call to get() has been made since the last packet resolution) and if the requested action buffer is large enough to hold all the data to send. When this function has finished executing, the output buffer is empty.

Parameters:
  • output – Output byte stream of any kind

  • k – Key of the action to request on the other dispatcher

Returns:

true if and only if the content of the output buffer has been written to the output byte stream

Public Static Attributes

static constexpr auto buffer_size = detail::max_p<detail::needed_input_buffer_size<keyring_t>, detail::needed_output_buffer_size<keyring_t>>::value

Equals the size of the buffer.

template<typename Keyring, action_features Action_Features>
auto make_single_buffered_dispatcher(Keyring, action_features_h<Action_Features>)

Make a single buffered dispatcher.

double_buffered_dispatcher

template<typename Dispatcher>
class double_buffered_dispatcher : public upd::buffered_dispatcher<double_buffered_dispatcher<Dispatcher>, Dispatcher>

Implements a dispatcher using separate buffers for input and output.

The buffers are allocated statically as plain arrays. Their sizes are as small as possible for holding any action request and any action response.

Note

If you would rather having a single buffer and do not mind reading and writing at different moment, consider using single_buffered_dispatcher instead.

Template Parameters:

Dispatcher – Underlying dispatcher type

Public Functions

template<typename Keyring, action_features Action_Features>
inline explicit double_buffered_dispatcher(Keyring, action_features_h<Action_Features>)

Initialize the underlying dispatcher.

Template Parameters:
  • Keyring – Keyring which holds the actions to be managed by the dispatcher

  • Action_Features – Features of the actions managed by the dispatcher

double_buffered_dispatcher() = default

Initialize the underlying dispatcher.

inline bool is_loaded() const

Indicates whether the output buffer contains data to send.

Returns:

true if and only if the next call to put() or write_to() will have a visible effect

inline packet_status read_from(Src &&src)

Put bytes into the input buffer until a full action request is stored.

The following member functions are also defined through CRTP.

R operator<<(Src &&);
R operator<<(Src &&) const;
R read_from(It);
R read_from(It) const;
R operator<<(It);
R operator<<(It) const;
All these functions work the same way as read_from(Src &&).

Template Parameters:
  • Src – Byte getter type

  • It – Input iterator type

Parameters:

src – Byte getter

Returns:

one of the following :

  • packet_status::DROPPED_PACKET: The received index was invalid and the input buffer content was therefore discarded.

  • packet_status::RESOLVED_PACKET: The packet was fully loaded and the associated action has been called (the input buffer is empty and the output buffer contains the result of the action invocation).

inline packet_status put(byte_t byte)

Put one byte into the input buffer.

Parameters:

byte – Byte to put

Returns:

one of the following :

  • packet_status::LOADING_PACKET: The packet is not yet fully loaded.

  • packet_status::DROPPED_PACKET: The received index was invalid and the input buffer content was therefore discarded.

  • packet_status::RESOLVED_PACKET: The packet was fully loaded and the associated action has been called (the input buffer is empty and the output buffer contains the result of the action invocation).

inline void write_to(Dest &&dest)

Completely output the output buffer content.

The following member functions are also defined through CRTP.

void operator>>(Dest &&);
void operator>>(Dest &&) const;
void write_to(It);
void write_to(It) const;
void operator>>(It);
void operator>>(It) const;
All these functions work the same way as write_to(Dest &&).

Template Parameters:
  • Dest – Byte putter type

  • It – Output iterator type

Parameters:

dest – Byte putter

inline byte_t get()

Output one byte from the output buffer.

If the output buffer is empty, the function will return an arbitrary value.

Returns:

the next byte in the output buffer (if it is not empty) or an arbitrary value

inline packet_status operator()(Src &&src, Dest &&dest)

Call read_from() then write_to()

write_to() is called if and only the output buffer has been populated by read_from().

The following member functions are also defined through CRTP.

auto operator()(Input &&, Output &&);
auto operator()(Input &&, Output &&) const;
All these functions work the same way as operator()(Src &&, Dest &&).

Template Parameters:
  • Input – Input byte stream type of any kind

  • Output – Output byte stream type of any kind

Parameters:
  • src – Byte getter

  • dest – Byte putter

Returns:

the enumerator instance resulting from the read_from() call

inline bool reply(Output &&output, Key k)

Forward the content of the output buffer to another dispatcher as an action request.

This function send an action request to the other dispatcher, which is supposed to process directly the content of the output buffer. The requested action must accept a single byte buffer as sole argument (the size of the buffer can be choosen at compile time). The content of the received byte sequence is the same as if it was written by write_to(), so it can be deserialized inside the requested action by using the key that was used to populate the output buffer.

This function can only be used if the data in the output buffer is complete (i.e. if no call to get() has been made since the last packet resolution) and if the requested action buffer is large enough to hold all the data to send. When this function has finished executing, the output buffer is empty.

Parameters:
  • output – Output byte stream of any kind

  • k – Key of the action to request on the other dispatcher

Returns:

true if and only if the content of the output buffer has been written to the output byte stream

Public Static Attributes

static constexpr auto input_buffer_size = detail::needed_input_buffer_size<keyring_t>::value

Equals the size of the input buffer.

static constexpr auto output_buffer_size = detail::needed_output_buffer_size<keyring_t>::value

Equals the size of the output buffer.

template<typename Keyring, action_features Action_Features>
auto make_double_buffered_dispatcher(Keyring, action_features_h<Action_Features>)

Make a double buffered dispatcher.

packet_status

enum class upd::packet_status

Enumerates the possible status of a loading packet.

  • LOADING_PACKET: The packet is currently being loaded and is not yet complete

  • DROPPED_PACKET: The packet loading has been canceled before completion

  • RESOLVED_PACKET: The packet loading has been completed and the corresponding action has been called

Values:

enumerator LOADING_PACKET
enumerator DROPPED_PACKET
enumerator RESOLVED_PACKET

Policies

Warning

doxygenvariable: Cannot find variable “upd::policy::any_action” in doxygen xml output for project “Unpadded” from directory: /home/runner/work/Unpadded/Unpadded/build/doc/xml

Warning

doxygenvariable: Cannot find variable “upd::policy::static_storage_duration_only” in doxygen xml output for project “Unpadded” from directory: /home/runner/work/Unpadded/Unpadded/build/doc/xml