[PATCH v2 5/5] libtest: Add packet processor

Chris Johns chrisj at rtems.org
Thu Feb 1 23:14:57 UTC 2024


Hi,

Thanks for the updated documentation, protocol and use cases. It has helped. Now
I understand some of the context I have raised further questions about it I feel
we should resolve. Without a protocol version number being exchanged it limits
how we can grow and develop this protocol beyond where it is.

On 26/1/2024 6:23 am, Sebastian Huber wrote:
> The RTEMS Test Framework packet processor provides a simple mechanism to
> exchange reliable and in-order data through transmitting and receiving
> one character at a time.
> 
> The packet processor does not buffer data.  The processor uses a
> stop-and-wait automatic repeat request method. There is at most one packet
> in transmission.  The data transfer is done using a single character input
> and output method.  The protocol uses 12-bit sequence numbers, so a host
> could use a sliding window method to increase throughput.  All integers and
> data are base64url encoded.  A 24-bit CRC is used to ensure the data
> integrity.  The '{' character starts a packet.  The '}' character terminates
> a packet.  The '#' character prefixes a 24-bit CRC value.  The ':' character
> separates fields.  The '+' character prefixes data fields.

Is the line encoding a subset of the ASCII character set? Is the line disciple raw?

I have reviewed the protocol as it is and I have some suggestions. The data link
layer messages are mixed with the pay load messages. If the messages followed:

 {<12-bit seq><12-bit ack>:<packet>[:[data]]]#<24-bit CRC>}

where:

<packet> is `H`, `A`, `R` or `P` are data link packets and `P` for payload can
contain `J`, `L`, `S` etc for the test and chain loader protocol. For example:

 {<12-bit seq><12-bit ack>:P:J:<64-bit address>#<24-bit CRC>}

Note, `P` could be a protocol id and so `T` could denote "test and chain loader".

[:] denotes this is optional depending on pay load data being present or it
could mean data link vs payload packets, what ever works here.

>  The following packets are defined:
> 
> * hello: {<12-bit seq><12-bit ack>:H#<24-bit CRC>}

Are you able to have the `H` be a query and return data? A hello that contains
protocol versioning data is always useful for long lived protocols. Version
numbering should be 1, 2, 3, 4 etc and 0 is reserved.
> * acknowledge: {<12-bit seq><12-bit ack>:A#<24-bit CRC>}

Does this ack all seq numbers up to this number?

> * reject: {<12-bit seq><12-bit ack>
>   :R:<12-bit seq of rejected packet>#<24-bit CRC>}

Are the `<>` fields binary and so base64 encoded?

> * jump: {<12-bit seq><12-bit ack>:J:<64-bit address>#<24-bit CRC>}
> 
> * load: {<12-bit seq><12-bit ack>:L:<64-bit load address>:
>   <64-bit load size in bytes>#<24-bit CRC>+<data to load>#<24-bit CRC>}
> 
> * signal: {<12-bit seq><12-bit ack>:S:<64-bit signal number>:
>   <64-bit signal value>#<24-bit CRC>}

Are the signals defined? If so maybe a reference to the enum or whatever?

> * channel: {<12-bit seq><12-bit ack>:C:<64-bit channel number>:
>   <64-bit data size in bytes>#<24-bit CRC>+<channel data>#<24-bit CRC>}

How would I add a packet type to the protocol? For example a 'D' packet could
transport GDB remote protocol for use on targets with limited resources and the
need to share this channel.

The data link and framwwork is useful for our project beyond the test use case
so it would be good to see if something more useful is within reach. :)

> The intended use case are boot loaders and test runners.  For example, test
> runners may interface with an external test server performing equipment
> handling on request using the packet processor.
> 
> Use T_packet_initialize() to initialize the packet processor.  Use
> T_packet_process() to drive the packet processing.  You can enqueue
> packets for transmission with T_packet_enqueue().  You can reliably send
> signals with T_packet_send().  You can reliably transmit and receive
> channel data with T_packet_channel_push() and
> T_packet_channel_exchange().
> 
> A simple boot loader could be implemented like this:

If this is RTEMS running the loader and then jumping to something loaded would
be a chain loader and not a boot loader? The use of the term boot loader
confused me. Chain loading is tricky because you need to handle the address
spaces to avoid clashing or you need to have a way to relocate the loaded data.
How is this handled?

A chain loader package in libmisc (or where ever) would be a very nice addition.

Given this I question the prefixing with T_ for the protocol. Again I see it as
more useful than just testing. :) I am sure testing has been the leading use
case for you but the T_ fingerprint assumes it is just for testing. :)

>   #include <bsp.h>
>   #include <rtems/bspIo.h>
>   #include <rtems/counter.h>
>   #include <rtems/test-packet.h>
> 
>   static void output_char(T_packet_control* self, uint8_t ch) {
>     (void)self;
>     rtems_putc(ch);
>   }
> 
>   static T_packet_status event_handler(T_packet_control* self,
>                                        T_packet_event_control* ctrl,
>                                        T_packet_event evt) {
>     (void)ctrl;
> 
>     switch (evt) {
>       case T_PACKET_EVENT_JUMP:
>         T_packet_output_acknowledge(self);
>         bsp_restart(T_packet_get_jump_address(self));
>         break;
>       case T_PACKET_EVENT_HELLO:
>       case T_PACKET_EVENT_LOAD_END:
>         T_packet_output_acknowledge(self);
>         break;
>       case T_PACKET_EVENT_OUTPUT_END:
>         rtems_putc('\n');
>         break;
>       default:
>         break;
>     }
> 
>     return T_PACKET_SUCCESSFUL;
>   }
> 
>   static uint32_t clock_monotonic(T_packet_control* self) {
>     (void)self;
>     return rtems_counter_read();
>   }
> 
>   static void Init(rtems_task_argument arg) {
>     (void)arg;
> 
>     T_packet_control self;
>     T_packet_initialize(&self, 0, NULL, output_char, clock_monotonic);
> 
>     T_packet_event_control event;
>     T_packet_prepend_event_handler(&self, &event, event_handler);
> 
>     while (true) {
>       T_packet_process(&self, getchark());
>     }
>   }
> ---
>  cpukit/include/rtems/test-packet.h            | 810 +++++++++++++++++
>  cpukit/libtest/t-test-packet.c                | 839 ++++++++++++++++++
>  spec/build/cpukit/librtemstest.yml            |   2 +
>  .../build/testsuites/unit/unit-no-clock-0.yml |   1 +
>  testsuites/unit/tc-test-packet.c              | 604 +++++++++++++
>  5 files changed, 2256 insertions(+)
>  create mode 100644 cpukit/include/rtems/test-packet.h
>  create mode 100644 cpukit/libtest/t-test-packet.c
>  create mode 100644 testsuites/unit/tc-test-packet.c
> 
> diff --git a/cpukit/include/rtems/test-packet.h b/cpukit/include/rtems/test-packet.h
> new file mode 100644
> index 0000000000..22bba59bba
> --- /dev/null
> +++ b/cpukit/include/rtems/test-packet.h
> @@ -0,0 +1,810 @@
> +/* SPDX-License-Identifier: BSD-2-Clause */
> +
> +/**
> + * @file
> + *
> + * @ingroup RTEMSTestFrameworkPacket
> + *
> + * @brief This header file provides the interfaces of the
> + *   @ref RTEMSTestFrameworkPacket.
> + */
> +
> +/*
> + * Copyright (C) 2024 embedded brains GmbH & Co. KG
> + *
> + * Redistribution and use in source and binary forms, with or without
> + * modification, are permitted provided that the following conditions
> + * are met:
> + * 1. Redistributions of source code must retain the above copyright
> + *    notice, this list of conditions and the following disclaimer.
> + * 2. Redistributions in binary form must reproduce the above copyright
> + *    notice, this list of conditions and the following disclaimer in the
> + *    documentation and/or other materials provided with the distribution.
> + *
> + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
> + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
> + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
> + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
> + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
> + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
> + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
> + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
> + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
> + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
> + * POSSIBILITY OF SUCH DAMAGE.
> + */
> +
> +#ifndef _RTEMS_TEST_PACKET_H
> +#define _RTEMS_TEST_PACKET_H
> +
> +#include <rtems/base64.h>
> +
> +#ifdef __cplusplus
> +extern "C" {
> +#endif /* __cplusplus */
> +
> +/**
> + * @defgroup RTEMSTestFrameworkPacket RTEMS Test Framework - Packet Processor
> + *
> + * @ingroup RTEMSTestFramework
> + *
> + * @brief The RTEMS Test Framework packet processor provides a simple mechanism
> + *   to exchange reliable and in-order data through transmitting and receiving
> + *   one character at a time.
> + *
> + * The packet processor does not buffer data.  The processor uses a
> + * stop-and-wait automatic repeat request method. There is at most one packet
> + * in transmission.  The data transfer is done using a single character input
> + * and output method.  The protocol uses 12-bit sequence numbers, so a host
> + * could use a sliding window method to increase throughput.  All integers and
> + * data are base64url encoded.  A 24-bit CRC is used to ensure the data
> + * integrity.  The '{' character starts a packet.  The '}' character terminates
> + * a packet.  The '#' character prefixes a 24-bit CRC value.  The ':' character
> + * separates fields.  The '+' character prefixes data fields.  The following
> + * packets are defined:
> + *
> + * * hello: {<12-bit seq><12-bit ack>:H#<24-bit CRC>}
> + *
> + * * acknowledge: {<12-bit seq><12-bit ack>:A#<24-bit CRC>}
> + *
> + * * reject: {<12-bit seq><12-bit ack>
> + *   :R:<12-bit seq of rejected packet>#<24-bit CRC>}
> + *
> + * * jump: {<12-bit seq><12-bit ack>:J:<64-bit address>#<24-bit CRC>}
> + *
> + * * load: {<12-bit seq><12-bit ack>:L:<64-bit load address>:
> + *   <64-bit load size in bytes>#<24-bit CRC>+<data to load>#<24-bit CRC>}
> + *
> + * * signal: {<12-bit seq><12-bit ack>:S:<64-bit signal number>:
> + *   <64-bit signal value>#<24-bit CRC>}
> + *
> + * * channel: {<12-bit seq><12-bit ack>:C:<64-bit channel number>:
> + *   <64-bit data size in bytes>#<24-bit CRC>+<channel data>#<24-bit CRC>}
> + *
> + * The intended use case are boot loaders and test runners.  For example, test
> + * runners may interface with an external test server performing equipment
> + * handling on request using the packet processor.
> + *
> + * Use T_packet_initialize() to initialize the packet processor.  Use
> + * T_packet_process() to drive the packet processing.  You can enqueue
> + * packets for transmission with T_packet_enqueue().  You can reliably send
> + * signals with T_packet_send().  You can reliably transmit and receive
> + * channel data with T_packet_channel_push() and
> + * T_packet_channel_exchange().
> + *
> + * A simple boot loader for test runs could be implemented like this:
> + *
> + * @code
> + * #include <bsp.h>
> + * #include <rtems/bspIo.h>
> + * #include <rtems/counter.h>
> + * #include <rtems/test-packet.h>
> + *
> + * static void output_char(T_packet_control* self, uint8_t ch) {
> + *   (void)self;
> + *   rtems_putc(ch);
> + * }
> + *
> + * static T_packet_status event_handler(T_packet_control* self,
> + *                                      T_packet_event_control* ctrl,
> + *                                      T_packet_event evt) {
> + *   (void)ctrl;
> + *
> + *   switch (evt) {
> + *     case T_PACKET_EVENT_JUMP:
> + *       T_packet_output_acknowledge(self);
> + *       bsp_restart(T_packet_get_jump_address(self));
> + *       break;
> + *     case T_PACKET_EVENT_HELLO:
> + *     case T_PACKET_EVENT_LOAD_END:
> + *       T_packet_output_acknowledge(self);
> + *       break;
> + *     case T_PACKET_EVENT_OUTPUT_END:
> + *       rtems_putc('\n');
> + *       break;
> + *     default:
> + *       break;
> + *   }
> + *
> + *   return T_PACKET_SUCCESSFUL;
> + * }
> + *
> + * static uint32_t clock_monotonic(T_packet_control* self) {
> + *   (void)self;
> + *   return rtems_counter_read();
> + * }
> + *
> + * static void Init(rtems_task_argument arg) {
> + *   (void)arg;
> + *
> + *   T_packet_control self;
> + *   T_packet_initialize(&self, 0, NULL, output_char, clock_monotonic);
> + *
> + *   T_packet_event_control event;
> + *   T_packet_prepend_event_handler(&self, &event, event_handler);
> + *
> + *   while (true) {
> + *     T_packet_process(&self, getchark());
> + *   }
> + * }
> + * @endcode
> + *
> + * @{
> + */
> +
> +/**
> + * @brief These enumerators represent packet processing states.
> + */
> +typedef enum {
> +  T_PACKET_STATE_START,
> +  T_PACKET_STATE_COLON,
> +  T_PACKET_STATE_CRC,
> +  T_PACKET_STATE_DECODE_DATA,
> +  T_PACKET_STATE_HASH,
> +  T_PACKET_STATE_REJECT,
> +  T_PACKET_STATE_SEQ_ACK,
> +  T_PACKET_STATE_TYPE,
> +  T_PACKET_STATE_VALUE
> +} T_packet_state;
> +
> +/**
> + * @brief These enumerators represent packet events.
> + */
> +typedef enum {
> +  /**
> +   * @brief This event happens when an acknowledge package was received.
> +   */
> +  T_PACKET_EVENT_ACKNOWLEDGE,
> +
> +  /**
> +   * @brief This event happens when channel data may get received.
> +   *
> +   * Call T_packet_set_channel_target() to set a target for the received
> +   * channel data, otherwise the packet is rejected.
> +   *
> +   * @see T_packet_get_channel_number() and T_packet_get_channel_size().
> +   */
> +  T_PACKET_EVENT_CHANNEL_BEGIN,
> +
> +  /**
> +   * @brief This event happens when channel data was received successfully.
> +   *
> +   * Call T_packet_output_acknowledge() to acknowledge the channel data,
> +   * otherwise the packet is rejected.
> +   *
> +   * @see T_packet_get_channel_number() and T_packet_get_channel_size().
> +   */
> +  T_PACKET_EVENT_CHANNEL_END,
> +
> +  /**
> +   * @brief This event happens when an duplicate packet was received.
> +   */
> +  T_PACKET_EVENT_DUPLICATE,
> +
> +  /**
> +   * @brief This event happens when a garbage character was received.
> +   */
> +  T_PACKET_EVENT_GARBAGE,
> +
> +  /**
> +   * @brief This event happens when a hello package was received.
> +   *
> +   * Call T_packet_output_acknowledge() to acknowledge the hello, otherwise
> +   * the packet is rejected.
> +   */
> +  T_PACKET_EVENT_HELLO,
> +
> +  /**
> +   * @brief This event happens when a jump should take place.
> +   *
> +   * Since a jump usually does not return, you should acknowledge the packet
> +   * explicitly through a call to T_packet_output_acknowledge().
> +   *
> +   * @see T_packet_get_jump_address().
> +   */
> +  T_PACKET_EVENT_JUMP,
> +
> +  /**
> +   * @brief This event happens when data load may should take place.
> +   *
> +   * Some actions may be performed to enable loading through updated MMU/MPU
> +   * configurations.  To reject loading of data, call
> +   * T_packet_output_reject() for this event.
> +   *
> +   * @see T_packet_get_load_address() and T_packet_get_load_size().
> +   */
> +  T_PACKET_EVENT_LOAD_BEGIN,
> +
> +  /**
> +   * @brief This event happens when data was successfully load.
> +   *
> +   * Some actions may be performed to flush/invalidate caches or restore
> +   * MMU/MPU settings.
> +   *
> +   * @see T_packet_get_load_address() and T_packet_get_load_size().
> +   */
> +  T_PACKET_EVENT_LOAD_END,
> +
> +  /**
> +   * @brief This event happens when a not acknowledge packet is transmitted.
> +   */
> +  T_PACKET_EVENT_NACK,
> +
> +  /**
> +   * @brief This event happens when idle processing is performed.
> +   */
> +  T_PACKET_EVENT_NOTHING,
> +
> +  /**
> +   * @brief This event happens when a packet output starts.
> +   */
> +  T_PACKET_EVENT_OUTPUT_BEGIN,
> +
> +  /**
> +   * @brief This event happens when a packet output ends.
> +   */
> +  T_PACKET_EVENT_OUTPUT_END,
> +
> +  /**
> +   * @brief This event happens when a packet is rejected.
> +   */
> +  T_PACKET_EVENT_REJECT,
> +
> +  /**
> +   * @brief This event happens when a packet is transmitted again.
> +   */
> +  T_PACKET_EVENT_SEND_AGAIN,
> +
> +  /**
> +   * @brief This event happens when a packet is dequeued for transmission.
> +   */
> +  T_PACKET_EVENT_SEND_DEQUEUE,
> +
> +  /**
> +   * @brief This event happens when a transmitted packet was acknowledged.
> +   */
> +  T_PACKET_EVENT_SEND_DONE,
> +
> +  /**
> +   * @brief This event happens when a signal was received.
> +   *
> +   * Call T_packet_output_acknowledge() to acknowledge the signal, otherwise
> +   * the packet is rejected.
> +   *
> +   * @see T_packet_get_signal_number() and T_packet_get_signal_value().
> +   */
> +  T_PACKET_EVENT_SIGNAL
> +} T_packet_event;
> +
> +/**
> + * @brief These enumerators represent the status of packet requests.
> + */
> +typedef enum {
> +  /**
> +   * @brief Indicates a successful operation.
> +   */
> +  T_PACKET_SUCCESSFUL,
> +
> +  /**
> +   * @brief Indicates that the event handling should continue.
> +   */
> +  T_PACKET_CONTINUE,
> +
> +  /**
> +   * @brief Indicates that the request timed out.
> +   */
> +  T_PACKET_TIMEOUT,
> +
> +  /**
> +   * @brief Indicates that a data buffer is not large enough.
> +   */
> +  T_PACKET_OVERFLOW,
> +
> +  /**
> +   * @brief Indicates that no input character handler was available.
> +   */
> +  T_PACKET_NO_INPUT_CHAR_HANDLER
> +} T_packet_status;
> +
> +typedef struct T_packet_packet T_packet_packet;
> +
> +typedef struct T_packet_control T_packet_control;
> +
> +typedef struct T_packet_event_control T_packet_event_control;
> +
> +typedef T_packet_status (*T_packet_event_handler)(T_packet_control*,
> +                                                  T_packet_event_control*,
> +                                                  T_packet_event);
> +
> +/**
> + * @brief This structure represents a packet event handler.
> + */
> +struct T_packet_event_control {
> +  T_packet_event_control* next;
> +  T_packet_event_handler handler;
> +};
> +
> +/**
> + * @brief This structure contains a packet to send.
> + */
> +struct T_packet_packet {
> +  T_packet_packet* next;
> +  void (*output)(T_packet_control*, T_packet_packet*);
> +  void (*done)(T_packet_control*, T_packet_packet*);
> +};
> +
> +/**
> + * @brief This structure contains a signal packet to send.
> + */
> +typedef struct {
> +  T_packet_packet base;
> +  uint64_t signal_number;
> +  uint64_t signal_value;
> +} T_packet_signal_packet;
> +
> +/**
> + * @brief This structure contains a channel packet to send.
> + */
> +typedef struct {
> +  T_packet_packet base;
> +  uint64_t channel_number;
> +  const void* data_begin;
> +  size_t data_size;
> +} T_packet_channel_packet;
> +
> +/**
> + * @brief This structure represents a packet transfer.
> + */
> +typedef struct {
> +  union {
> +    T_packet_packet base;
> +    T_packet_channel_packet channel;
> +    T_packet_signal_packet signal;
> +  };
> +  T_packet_status status;
> +  T_packet_event_control event;
> +} T_packet_packet_transfer;
> +
> +/**
> + * @brief This structure represents a packet processor.
> + */
> +struct T_packet_control {
> +  T_packet_state state;
> +  T_packet_event_control* event_head;
> +  T_packet_event packet_done_event;
> +  uint32_t my_seq;
> +  uint32_t other_seq;
> +  size_t seq_ack_idx;
> +  uint32_t seq_ack;
> +  uint8_t packet_type;
> +  uint32_t crc_calculated;
> +  uint32_t crc_received;
> +  size_t crc_idx;
> +  size_t value_idx;
> +  uint64_t values[2];
> +  Base64_Decode_control b64_decode;
> +  uint32_t snd_timeout;
> +  T_packet_packet* snd_pending;
> +  T_packet_packet* snd_head;
> +  T_packet_packet** snd_tail;
> +  T_packet_packet hello;
> +  void (*output_char)(T_packet_control*, uint8_t);
> +  uint32_t (*clock_monotonic)(T_packet_control*);
> +  int (*input_char)(T_packet_control*);
> +};
> +
> +/**
> + * @brief Initializes the packet processor.
> + *
> + * @param[out] self is the packet processor to initialize.
> + *
> + * @param seq is the initial sequence and acknowledge number.
> + *
> + * @param input_char_handler is the handler to get characters.  This handler is
> + *   optional and may be NULL.
> + *
> + * @param output_char_handler is the handler to output characters.
> + *
> + * @param clock_monotonic_handler is the handler to get the current value of a
> + *   monotonic clock.
> + */
> +void T_packet_initialize(
> +    T_packet_control* self,
> +    uint32_t seq,
> +    int (*input_char_handler)(T_packet_control*),
> +    void (*output_char_handler)(T_packet_control*, uint8_t),
> +    uint32_t (*clock_monotonic_handler)(T_packet_control*));
> +
> +/**
> + * @brief Appends the event handler to the event handler list of the packet
> + *   processor.
> + *
> + * @param[in, out] self is the packet processor.
> + *
> + * @param[out] ctrl is the event control for the handler.
> + *
> + * @param handler is the event handler.
> + */
> +void T_packet_append_event_handler(T_packet_control* self,
> +                                   T_packet_event_control* ctrl,
> +                                   T_packet_event_handler handler);
> +
> +/**
> + * @brief Prepends the event handler to the event handler list of the packet
> + *   processor.
> + *
> + * @param[in, out] self is the packet processor.
> + *
> + * @param[out] ctrl is the event control for the handler.
> + *
> + * @param handler is the event handler.
> + */
> +void T_packet_prepend_event_handler(T_packet_control* self,
> +                                    T_packet_event_control* ctrl,
> +                                    T_packet_event_handler handler);
> +
> +/**
> + * @brief Removes the event handler from the event handler list of the I/O
> + *   packet processor.
> + *
> + * @param[in, out] self is the packet processor.
> + *
> + * @param[in] ctrl is the event control to remove.
> + */
> +void T_packet_remove_event_handler(T_packet_control* self,
> +                                   T_packet_event_control* ctrl);
> +
> +/**
> + * @brief Processes the character or performs the idle processing.
> + *
> + * It may output at most one packet per call using the output character
> + * handler.
> + *
> + * @param[in, out] self is the packet processor.
> + *
> + * @param ch is the character to process or -1 to perform the idle processing.
> + */
> +void T_packet_process(T_packet_control* self, int ch);
> +
> +/**
> + * @brief Sends the packet and waits for a non-continuation status.
> + *
> + * The status change shall be done by the provided packet send done handler or
> + * the event handler.
> + *
> + * @param[in, out] self is the packet processor.
> + *
> + * @param[in, out] transfer is the packet transfer.
> + *
> + * @param timeout is the optional request timeout.  When the value is zero, the
> + *   function waits forever for the non-continuation status.  Otherwise, the
> + * function returns with a timeout status if the non-continuation status was not
> + *   established within the specified time frame.  The timeout value is with
> + *   respect to the used monotonic clock handler.
> + */
> +T_packet_status T_packet_send(T_packet_control* self,
> +                              T_packet_packet_transfer* transfer,
> +                              uint32_t timeout);
> +
> +/**
> + * @brief Sends the signal and waits for the acknowledge.
> + *
> + * @param[in, out] self is the packet processor.
> + *
> + * @param signal_number is the signal number.
> + *
> + * @param signal_value is the signal value.
> + *
> + * @param timeout is the optional request timeout.  When the value is zero, the
> + *   function waits forever for the acknowledge.  Otherwise, the function
> + *   returns with a timeout status if no acknowledge was received within the
> + *   specified time frame.  The timeout value is with respect to the used
> + *   monotonic clock handler.
> + */
> +T_packet_status T_packet_signal(T_packet_control* self,
> +                                uint64_t signal_number,
> +                                uint64_t signal_value,
> +                                uint32_t timeout);
> +
> +/**
> + * @brief Pushes the data to the channel and waits for the acknowledge.
> + *
> + * @param[in, out] self is the packet processor.
> + *
> + * @param channel_number is the channel number.
> + *
> + * @param data_begin is the begin address of the data area to push.
> + *
> + * @param data_size is the size in bytes of the data area to push.
> + *
> + * @param timeout is the optional request timeout.  When the value is zero, the
> + *   function waits forever for the acknowledge.  Otherwise, the function
> + *   returns with a timeout status if no acknowledge was received within the
> + *   specified time frame.  The timeout value is with respect to the used
> + *   monotonic clock handler.
> + */
> +T_packet_status T_packet_channel_push(T_packet_control* self,
> +                                      uint64_t channel_number,
> +                                      const void* data_begin,
> +                                      size_t data_size,
> +                                      uint32_t timeout);
> +
> +/**
> + * @brief Pushes the data to the channel and waits for an
> + *   acknowledge with response data.
> + *
> + * @param[in, out] self is the packet processor.
> + *
> + * @param channel_number is the channel number.
> + *
> + * @param transmit_begin is the begin address of the data area to transmit.
> + *
> + * @param transmit_size is the size in bytes of the data area to transmit.
> + *
> + * @param[out] receive_begin is the begin address of the data area to receive
> + * data.
> + *
> + * @param[in, out] receive_size is the pointer to a buffer size object.  On
> + *   entry, the value of this object is the size in bytes of the data area to
> + *   receive data.  On return, the value of this object is the size in bytes of
> + *   the data received.
> + *
> + * @param timeout is the optional request timeout.  When the value is zero, the
> + *   function waits forever for the acknowledge and received data.  Otherwise,
> + *   the function returns with a timeout status if no acknowledge or data was
> + *   received within the specified time frame.  The timeout value is with
> + *   respect to the used monotonic clock handler.
> + */
> +T_packet_status T_packet_channel_exchange(T_packet_control* self,
> +                                          uint64_t channel_number,
> +                                          const void* transmit_begin,
> +                                          size_t transmit_size,
> +                                          void* receive_begin,
> +                                          size_t* receive_size,
> +                                          uint32_t timeout);
> +
> +/**
> + * @brief Gets the jump address.
> + *
> + * It may be used in ::T_PACKET_EVENT_JUMP events.
> + *
> + * @return Returns the jump address.
> + */
> +static inline void* T_packet_get_jump_address(const T_packet_control* self) {
> +  return (void*)(uintptr_t)self->values[0];
> +}
> +
> +/**
> + * @brief Gets the load address.
> + *
> + * It may be used in ::T_PACKET_EVENT_LOAD_BEGIN and
> + * ::T_PACKET_EVENT_LOAD_END events.
> + *
> + * @return Returns the load address.
> + */
> +static inline void* T_packet_get_load_address(const T_packet_control* self) {
> +  return (void*)(uintptr_t)self->values[0];
> +}
> +
> +/**
> + * @brief Gets the load size.
> + *
> + * It may be used in ::T_PACKET_EVENT_LOAD_BEGIN and
> + * ::T_PACKET_EVENT_LOAD_END events.
> + *
> + * @return Returns the load size.
> + */
> +static inline size_t T_packet_get_load_size(const T_packet_control* self) {
> +  return (size_t)self->values[1];
> +}
> +
> +/**
> + * @brief Gets the signal number.
> + *
> + * It may be used in ::T_PACKET_EVENT_SIGNAL events.
> + *
> + * @return Returns the signal number.
> + */
> +static inline uint64_t T_packet_get_signal_number(
> +    const T_packet_control* self) {
> +  return self->values[0];
> +}
> +
> +/**
> + * @brief Gets the signal value.
> + *
> + * It may be used in ::T_PACKET_EVENT_SIGNAL events.
> + *
> + * @return Returns the signal value.
> + */
> +static inline uint64_t T_packet_get_signal_value(const T_packet_control* self) {
> +  return self->values[1];
> +}
> +
> +/**
> + * @brief Gets the channel number.
> + *
> + * It may be used in ::T_PACKET_EVENT_CHANNEL_BEGIN and
> + * ::T_PACKET_EVENT_CHANNEL_END events.
> + *
> + * @return Returns the channel number.
> + */
> +static inline uint64_t T_packet_get_channel_number(
> +    const T_packet_control* self) {
> +  return self->values[0];
> +}
> +
> +/**
> + * @brief Gets the channel data size in bytes.
> + *
> + * It may be used in ::T_PACKET_EVENT_CHANNEL_BEGIN and
> + * ::T_PACKET_EVENT_CHANNEL_END events.
> + *
> + * @return Returns the channel data size in bytes.
> + */
> +static inline size_t T_packet_get_channel_size(const T_packet_control* self) {
> +  return (size_t)self->values[1];
> +}
> +
> +/**
> + * @brief Sets the channel data target address.
> + *
> + * It may be used in ::T_PACKET_EVENT_CHANNEL_BEGIN events.
> + *
> + * @param[out] target is the channel data target address.  The target area
> + *   shall be large enough to receive T_packet_get_channel_size() bytes.
> + */
> +static inline void T_packet_set_channel_target(T_packet_control* self,
> +                                               void* target) {
> +  _Base64_Decode_initialize(&self->b64_decode, target,
> +                            T_packet_get_channel_size(self));
> +}
> +
> +/**
> + * @brief Outputs an acknowledge packet.
> + *
> + * @param[in, out] self is the packet processor.
> + */
> +void T_packet_output_acknowledge(T_packet_control* self);
> +
> +/**
> + * @brief Outputs a reject packet.
> + *
> + * @param[in, out] self is the packet processor.
> + */
> +void T_packet_output_reject(T_packet_control* self);
> +
> +/**
> + * @brief Does nothing.
> + *
> + * @param[in] self is the packet processor.
> + *
> + * @param[in] pkt is the successfully transmitted packet.
> + */
> +void T_packet_done_default(T_packet_control* self, T_packet_packet* pkt);
> +
> +/**
> + * @brief Outputs a hello packet.
> + *
> + * @param[in, out] self is the packet processor.
> + *
> + * @param[in, out] pkt is the packet to output.
> + */
> +void T_packet_output_hello(T_packet_control* self, T_packet_packet* pkt);
> +
> +/**
> + * @brief Outputs a signal packet.
> + *
> + * @param[in, out] self is the packet processor.
> + *
> + * @param[in, out] pkt is the packet to output.
> + */
> +void T_packet_output_signal(T_packet_control* self, T_packet_packet* pkt);
> +
> +/**
> + * @brief Outputs a channel packet.
> + *
> + * @param[in, out] self is the packet processor.
> + *
> + * @param[in, out] pkt is the packet to output.
> + */
> +void T_packet_output_channel(T_packet_control* self, T_packet_packet* pkt);
> +
> +/**
> + * @brief Enqueues an initialized packet for transmission.
> + *
> + * @param[in, out] self is the packet processor.
> + *
> + * @param[in, out] pkt is the packet to enqueue.
> + */
> +static inline void T_packet_enqueue(T_packet_control* self,
> +                                    T_packet_packet* pkt) {
> +  pkt->next = NULL;
> +  *self->snd_tail = pkt;
> +  self->snd_tail = &pkt->next;
> +}
> +
> +/**
> + * @brief Removes the packet from the send queue.
> + *
> + * @param[in, out] self is the packet processor.
> + *
> + * @param[in, out] pkt is the packet to cancel.
> + */
> +void T_packet_cancel(T_packet_control* self, T_packet_packet* pkt);
> +
> +/**
> + * @brief Initializes the signal packet.
> + *
> + * @param[out] pkt is the packet to enqueue.
> + *
> + * @param signal_number is the signal number.
> + *
> + * @param signal_value is the signal value.
> + *
> + * @param done is the transfer done handler.
> + */
> +static inline void T_packet_initialize_signal(T_packet_signal_packet* pkt,
> +                                              uint64_t signal_number,
> +                                              uint64_t signal_value,
> +                                              void (*done)(T_packet_control*,
> +                                                           T_packet_packet*)) {
> +  pkt->base.output = T_packet_output_signal;
> +  pkt->base.done = done;
> +  pkt->signal_number = signal_number;
> +  pkt->signal_value = signal_value;
> +}
> +
> +/**
> + * @brief Initializes the channel packet.
> + *
> + * @param[out] pkt is the packet to enqueue.
> + *
> + * @param channel_number is the channel number.
> + *
> + * @param data_begin is the begin address of the data area.
> + *
> + * @param data_size is the size in bytes of the data area.
> + *
> + * @param done is the transfer done handler.
> + */
> +static inline void T_packet_initialize_channel(T_packet_channel_packet* pkt,
> +                                               uint64_t channel_number,
> +                                               const void* data_begin,
> +                                               size_t data_size,
> +                                               void (*done)(T_packet_control*,
> +                                                            T_packet_packet*)) {
> +  pkt->base.output = T_packet_output_channel;
> +  pkt->base.done = done;
> +  pkt->channel_number = channel_number;
> +  pkt->data_begin = data_begin;
> +  pkt->data_size = data_size;
> +}
> +
> +/** @} */
> +
> +#ifdef __cplusplus
> +}
> +#endif /* __cplusplus */
> +
> +#endif /* _RTEMS_TEST_PACKET_H */
> diff --git a/cpukit/libtest/t-test-packet.c b/cpukit/libtest/t-test-packet.c
> new file mode 100644
> index 0000000000..036e356e7e
> --- /dev/null
> +++ b/cpukit/libtest/t-test-packet.c
> @@ -0,0 +1,839 @@
> +/* SPDX-License-Identifier: BSD-2-Clause */
> +
> +/*
> + * Copyright (C) 2023 embedded brains GmbH & Co. KG
> + *
> + * Redistribution and use in source and binary forms, with or without
> + * modification, are permitted provided that the following conditions
> + * are met:
> + * 1. Redistributions of source code must retain the above copyright
> + *    notice, this list of conditions and the following disclaimer.
> + * 2. Redistributions in binary form must reproduce the above copyright
> + *    notice, this list of conditions and the following disclaimer in the
> + *    documentation and/or other materials provided with the distribution.
> + *
> + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
> + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
> + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
> + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
> + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
> + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
> + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
> + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
> + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
> + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
> + * POSSIBILITY OF SUCH DAMAGE.
> + */
> +
> +#include <rtems/test-packet.h>
> +
> +#include <limits.h>
> +#include <string.h>
> +
> +#include <rtems/crc.h>
> +
> +#define SEQ_SHIFT 12
> +#define SEQ_MASK UINT32_C(0xfff)
> +#define ACK_VALID UINT32_C(0x1000000)
> +#define SEQ_VALID UINT32_C(0x2000000)
> +
> +void T_packet_append_event_handler(T_packet_control* self,
> +                                   T_packet_event_control* ctrl,
> +                                   T_packet_event_handler handler) {
> +  ctrl->handler = handler;
> +  ctrl->next = NULL;
> +  T_packet_event_control* tail = self->event_head;
> +  T_packet_event_control** next = &self->event_head;
> +
> +  while (true) {
> +    if (tail == NULL) {
> +      *next = ctrl;
> +      return;
> +    }
> +
> +    next = &tail->next;
> +    tail = tail->next;
> +  }
> +}
> +
> +void T_packet_prepend_event_handler(T_packet_control* self,
> +                                    T_packet_event_control* ctrl,
> +                                    T_packet_event_handler handler) {
> +  ctrl->handler = handler;
> +  ctrl->next = self->event_head;
> +  self->event_head = ctrl;
> +}
> +
> +void T_packet_remove_event_handler(T_packet_control* self,
> +                                   T_packet_event_control* pkt) {
> +  T_packet_event_control* other = self->event_head;
> +  T_packet_event_control** prev = &self->event_head;
> +
> +  while (other != NULL) {
> +    if (pkt == other) {
> +      *prev = pkt->next;
> +      return;
> +    }
> +
> +    prev = &other->next;
> +    other = other->next;
> +  }
> +}
> +
> +static void event(T_packet_control* self, T_packet_event event) {
> +  T_packet_event_control* ctrl = self->event_head;
> +
> +  while (ctrl != NULL) {
> +    T_packet_event_control* next = ctrl->next;
> +    T_packet_status status = (*ctrl->handler)(self, ctrl, event);
> +
> +    if (status != T_PACKET_CONTINUE) {
> +      return;
> +    }
> +
> +    ctrl = next;
> +  }
> +}
> +
> +static void output_char(T_packet_control* self, uint8_t ch) {
> +  (*self->output_char)(self, ch);
> +}
> +
> +static uint32_t output_start(T_packet_control* self, uint8_t packet_type) {
> +  event(self, T_PACKET_EVENT_OUTPUT_BEGIN);
> +  output_char(self, '{');
> +  uint32_t crc = CRC24Q_SEED;
> +  uint32_t seq_ack = (self->my_seq << SEQ_SHIFT) | self->other_seq;
> +
> +  for (int i = 18; i >= 0; i -= 6) {
> +    uint8_t ch = _Base64url_Encoding[(seq_ack >> i) & 0x3f];
> +    output_char(self, ch);
> +    crc = _CRC24Q_Update(crc, ch);
> +  }
> +
> +  output_char(self, ':');
> +  crc = _CRC24Q_Update(crc, ':');
> +  output_char(self, packet_type);
> +  crc = _CRC24Q_Update(crc, packet_type);
> +
> +  return crc;
> +}
> +
> +static uint32_t output_value(T_packet_control* self,
> +                             uint32_t crc,
> +                             uint64_t value) {
> +  output_char(self, ':');
> +  crc = _CRC24Q_Update(crc, ':');
> +
> +  int i = 60;
> +
> +  /* Skip leading zeros */
> +  while (i >= 6) {
> +    if (((value >> i) & 0x3f) != 0) {
> +      break;
> +    }
> +
> +    i -= 6;
> +  }
> +
> +  while (i >= 0) {
> +    uint8_t ch = _Base64url_Encoding[(value >> i) & 0x3f];
> +    output_char(self, ch);
> +    crc = _CRC24Q_Update(crc, ch);
> +    i -= 6;
> +  }
> +
> +  return crc;
> +}
> +
> +static void output_crc(T_packet_control* self, uint32_t crc) {
> +  output_char(self, '#');
> +
> +  for (int i = 18; i >= 0; i -= 6) {
> +    uint8_t ch = _Base64url_Encoding[(crc >> i) & 0x3f];
> +    output_char(self, ch);
> +  }
> +}
> +
> +static void output_end(T_packet_control* self) {
> +  output_char(self, '}');
> +  event(self, T_PACKET_EVENT_OUTPUT_END);
> +}
> +
> +static void inc_my_seq(T_packet_control* self) {
> +  self->my_seq = (self->my_seq + UINT32_C(1)) & SEQ_MASK;
> +}
> +
> +static void make_pending(T_packet_control* self, T_packet_packet* pkt) {
> +  inc_my_seq(self);
> +  self->snd_pending = pkt;
> +  event(self, T_PACKET_EVENT_SEND_DEQUEUE);
> +}
> +
> +static void output_packet(T_packet_control* self,
> +                          T_packet_packet* pkt,
> +                          uint32_t t0) {
> +  (*pkt->output)(self, pkt);
> +  uint32_t t1 = (*self->clock_monotonic)(self);
> +  self->snd_timeout = t0 + 4 * (t1 - t0);
> +}
> +
> +static void output_simple_packet(T_packet_control* self, uint8_t packet_type) {
> +  uint32_t crc = output_start(self, packet_type);
> +  output_crc(self, crc);
> +  output_end(self);
> +}
> +
> +static void send_done(T_packet_control* self) {
> +  T_packet_packet* pending = self->snd_pending;
> +
> +  if (pending != NULL) {
> +    T_packet_packet* next = pending->next;
> +    self->snd_pending = NULL;
> +    self->snd_head = next;
> +
> +    if (next == NULL) {
> +      self->snd_tail = &self->snd_head;
> +    }
> +
> +    event(self, T_PACKET_EVENT_SEND_DONE);
> +    (*pending->done)(self, pending);
> +  }
> +}
> +
> +static void output_response(T_packet_control* self, uint8_t packet_type) {
> +  uint32_t seq_ack = self->seq_ack;
> +
> +  if ((seq_ack & ACK_VALID) != 0 && (seq_ack & SEQ_MASK) == self->my_seq) {
> +    send_done(self);
> +  }
> +
> +  if ((seq_ack & SEQ_VALID) != 0) {
> +    self->other_seq = (seq_ack >> SEQ_SHIFT) & SEQ_MASK;
> +  }
> +
> +  self->state = T_PACKET_STATE_START;
> +
> +  if (packet_type == 'R' && self->snd_pending == NULL) {
> +    inc_my_seq(self);
> +    uint32_t crc = output_start(self, packet_type);
> +    crc = output_value(self, crc, (seq_ack >> SEQ_SHIFT) & SEQ_MASK);
> +    output_crc(self, crc);
> +    output_end(self);
> +    return;
> +  }
> +
> +  T_packet_packet* head = self->snd_head;
> +
> +  if (head == NULL) {
> +    if (self->packet_type != 'A') {
> +      inc_my_seq(self);
> +      output_simple_packet(self, packet_type);
> +    }
> +
> +    return;
> +  }
> +
> +  if (self->snd_pending != NULL) {
> +    event(self, T_PACKET_EVENT_SEND_AGAIN);
> +  } else {
> +    make_pending(self, head);
> +  }
> +
> +  output_packet(self, head, (*self->clock_monotonic)(self));
> +}
> +
> +static void output_nack(T_packet_control* self) {
> +  event(self, T_PACKET_EVENT_NACK);
> +  output_response(self, 'A');
> +}
> +
> +void T_packet_output_acknowledge(T_packet_control* self) {
> +  output_response(self, 'A');
> +}
> +
> +void T_packet_output_reject(T_packet_control* self) {
> +  output_response(self, 'R');
> +}
> +
> +void T_packet_initialize(
> +    T_packet_control* self,
> +    uint32_t seq,
> +    int (*input_char_handler)(T_packet_control*),
> +    void (*output_char_handler)(T_packet_control*, uint8_t),
> +    uint32_t (*clock_monotonic_handler)(T_packet_control*)) {
> +  self = memset(self, 0, sizeof(*self));
> +  seq &= SEQ_MASK;
> +  self->my_seq = seq;
> +  self->other_seq = seq;
> +  self->hello.output = T_packet_output_hello;
> +  self->hello.done = T_packet_done_default;
> +  self->snd_head = &self->hello;
> +  self->snd_tail = &self->hello.next;
> +  self->input_char = input_char_handler;
> +  self->output_char = output_char_handler;
> +  self->clock_monotonic = clock_monotonic_handler;
> +}
> +
> +static void begin(T_packet_control* self) {
> +  self->state = T_PACKET_STATE_SEQ_ACK;
> +  self->crc_calculated = CRC24Q_SEED;
> +  self->seq_ack_idx = 0;
> +  self->seq_ack = 0;
> +}
> +
> +static void receive_crc(T_packet_control* self) {
> +  self->crc_received = 0;
> +  self->crc_idx = 0;
> +  self->state = T_PACKET_STATE_CRC;
> +}
> +
> +static void update_crc(T_packet_control* self, uint8_t ch) {
> +  self->crc_calculated = _CRC24Q_Update(self->crc_calculated, ch);
> +}
> +
> +static void do_seq_ack(T_packet_control* self, uint8_t ch) {
> +  update_crc(self, ch);
> +  size_t seq_ack_idx = self->seq_ack_idx;
> +
> +  if (seq_ack_idx < 4) {
> +    if (ch < RTEMS_ARRAY_SIZE(_Base64_Decoding)) {
> +      uint8_t decoded_ch = _Base64_Decoding[ch];
> +
> +      if (decoded_ch <= 63) {
> +        self->seq_ack = (self->seq_ack << 6) | decoded_ch;
> +        self->seq_ack_idx = seq_ack_idx + 1;
> +      } else {
> +        output_nack(self);
> +      }
> +    } else {
> +      output_nack(self);
> +    }
> +  } else {
> +    switch (ch) {
> +      case ':':
> +        self->state = T_PACKET_STATE_TYPE;
> +        break;
> +      default:
> +        output_nack(self);
> +        break;
> +    }
> +  }
> +}
> +
> +static void do_type(T_packet_control* self, uint8_t ch) {
> +  update_crc(self, ch);
> +  self->packet_type = ch;
> +
> +  switch (ch) {
> +    case 'C':
> +      self->packet_done_event = T_PACKET_EVENT_CHANNEL_END;
> +      self->state = T_PACKET_STATE_COLON;
> +      break;
> +    case 'S':
> +      self->packet_done_event = T_PACKET_EVENT_SIGNAL;
> +      self->state = T_PACKET_STATE_COLON;
> +      break;
> +    case 'L':
> +      self->packet_done_event = T_PACKET_EVENT_LOAD_END;
> +      self->state = T_PACKET_STATE_COLON;
> +      break;
> +    case 'J':
> +      self->packet_done_event = T_PACKET_EVENT_JUMP;
> +      self->state = T_PACKET_STATE_COLON;
> +      break;
> +    case 'H':
> +      self->packet_done_event = T_PACKET_EVENT_HELLO;
> +      self->state = T_PACKET_STATE_HASH;
> +      break;
> +    case 'A':
> +      self->packet_done_event = T_PACKET_EVENT_ACKNOWLEDGE;
> +      self->state = T_PACKET_STATE_HASH;
> +      break;
> +    default:
> +      self->packet_done_event = T_PACKET_EVENT_REJECT;
> +      self->state = T_PACKET_STATE_REJECT;
> +      break;
> +  }
> +}

You have handlers for the time, ie clock_monotonic, so why not one to handle the
payload? My quick review would indicate the code is clean enough to handle this
but I am not sure.

> +
> +static void do_colon(T_packet_control* self, uint8_t ch) {
> +  switch (ch) {
> +    case ':':
> +      update_crc(self, ch);
> +      self->value_idx = 0;
> +      self->values[0] = 0;
> +      self->state = T_PACKET_STATE_VALUE;
> +      break;
> +    default:
> +      output_nack(self);
> +      break;
> +  }
> +}
> +
> +static void update_value(T_packet_control* self, uint8_t ch) {
> +  if (ch >= RTEMS_ARRAY_SIZE(_Base64_Decoding)) {
> +    output_nack(self);
> +    return;
> +  }
> +
> +  uint8_t decoded_ch = _Base64_Decoding[ch];
> +
> +  if (decoded_ch > 63) {
> +    output_nack(self);
> +    return;
> +  }
> +
> +  uint64_t value = self->values[self->value_idx];
> +
> +  if ((value & UINT64_C(0xfc00000000000000)) != 0) {
> +    output_nack(self);
> +    return;
> +  }
> +
> +  self->values[self->value_idx] = (value << 6) | decoded_ch;
> +  update_crc(self, ch);
> +}
> +
> +static void do_value(T_packet_control* self, uint8_t ch) {
> +  switch (ch) {
> +    case '#':
> +      ++self->value_idx;
> +      receive_crc(self);
> +      break;
> +    case ':':
> +      if ((self->packet_type == 'C' || self->packet_type == 'L' ||
> +           self->packet_type == 'S') &&
> +          self->value_idx == 0) {
> +        update_crc(self, ch);
> +        self->value_idx = 1;
> +        self->values[1] = 0;
> +      } else {
> +        output_nack(self);
> +      }
> +
> +      break;
> +    default:
> +      update_value(self, ch);
> +      break;
> +  }
> +}
> +
> +static void do_hash(T_packet_control* self, uint8_t ch) {
> +  switch (ch) {
> +    case '#':
> +      receive_crc(self);
> +      break;
> +    default:
> +      output_nack(self);
> +      break;
> +  }
> +}
> +
> +static void do_reject(T_packet_control* self, uint8_t ch) {
> +  switch (ch) {
> +    case '#':
> +      receive_crc(self);
> +      break;
> +    default:
> +      update_crc(self, ch);
> +      break;
> +  }
> +}
> +
> +static void next_component(T_packet_control* self) {
> +  if (self->value_idx != 2) {
> +    output_nack(self);
> +    return;
> +  }
> +
> +  self->crc_calculated = CRC24Q_SEED;
> +
> +  switch (self->packet_type) {
> +    case 'C': {
> +      self->b64_decode.target = NULL;
> +      self->state = T_PACKET_STATE_DECODE_DATA;
> +      event(self, T_PACKET_EVENT_CHANNEL_BEGIN);
> +
> +      if (self->b64_decode.target == NULL) {
> +        T_packet_output_reject(self);
> +      }
> +
> +      break;
> +    }
> +    case 'L':
> +      _Base64_Decode_initialize(&self->b64_decode,
> +                                (uint8_t*)(uintptr_t)self->values[0],
> +                                self->values[1]);
> +      self->state = T_PACKET_STATE_DECODE_DATA;
> +      event(self, T_PACKET_EVENT_LOAD_BEGIN);
> +      break;
> +    default:
> +      output_nack(self);
> +      break;
> +  }
> +}
> +
> +static void check_crc(T_packet_control* self, uint8_t ch) {
> +  if ((self->crc_calculated & CRC24Q_MASK) != self->crc_received) {
> +    output_nack(self);
> +    return;
> +  }
> +
> +  uint32_t seq_ack = self->seq_ack | ACK_VALID;
> +  uint32_t other_seq = (seq_ack >> SEQ_SHIFT) & SEQ_MASK;
> +
> +  if (((self->other_seq - other_seq) & SEQ_MASK) < SEQ_MASK / 2) {
> +    event(self, T_PACKET_EVENT_DUPLICATE);
> +    T_packet_output_acknowledge(self);
> +    return;
> +  }
> +
> +  if (self->packet_done_event == T_PACKET_EVENT_REJECT) {
> +    self->seq_ack = seq_ack;
> +    event(self, T_PACKET_EVENT_REJECT);
> +    T_packet_output_reject(self);
> +    return;
> +  }
> +
> +  switch (ch) {
> +    case '}':
> +      self->seq_ack = seq_ack | SEQ_VALID;
> +      event(self, self->packet_done_event);
> +
> +      if (self->state != T_PACKET_STATE_START) {
> +        if (self->packet_type == 'A') {
> +          output_response(self, 'A');
> +        } else {
> +          self->seq_ack = seq_ack;
> +          T_packet_output_reject(self);
> +        }
> +      }
> +
> +      break;
> +    case '+':
> +      self->seq_ack = seq_ack;
> +      next_component(self);
> +      break;
> +    default:
> +      output_nack(self);
> +      break;
> +  }
> +}
> +
> +static void do_crc(T_packet_control* self, uint8_t ch) {
> +  size_t crc_idx = self->crc_idx;
> +
> +  if (crc_idx < 4) {
> +    if (ch < RTEMS_ARRAY_SIZE(_Base64_Decoding)) {
> +      uint8_t decoded_ch = _Base64_Decoding[ch];
> +
> +      if (decoded_ch <= 63) {
> +        self->crc_received = (self->crc_received << 6) | decoded_ch;
> +        self->crc_idx = crc_idx + 1;
> +      } else {
> +        output_nack(self);
> +      }
> +    } else {
> +      output_nack(self);
> +    }
> +  } else {
> +    check_crc(self, ch);
> +  }
> +}
> +
> +static void do_decode_data(T_packet_control* self, uint8_t ch) {
> +  switch (ch) {
> +    case '#':
> +      if (self->b64_decode.target == self->b64_decode.target_end) {
> +        receive_crc(self);
> +      } else {
> +        output_nack(self);
> +      }
> +      break;
> +    default: {
> +      Base64_Decode_status status = _Base64_Decode(&self->b64_decode, ch);
> +
> +      if (status == BASE64_DECODE_SUCCESS) {
> +        update_crc(self, ch);
> +      } else {
> +        output_nack(self);
> +      }
> +
> +      break;
> +    }
> +  }
> +}
> +
> +static void idle_processing(T_packet_control* self) {
> +  event(self, T_PACKET_EVENT_NOTHING);
> +
> +  if (self->state != T_PACKET_STATE_START) {
> +    return;
> +  }
> +
> +  T_packet_packet* head = self->snd_head;
> +
> +  if (head == NULL) {
> +    return;
> +  }
> +
> +  uint32_t now = (*self->clock_monotonic)(self);
> +
> +  if (self->snd_pending != NULL) {
> +    if ((self->snd_timeout - now) <= UINT32_MAX / 2) {
> +      return;
> +    }

The now value is 32bits so not nano-seconds and UINT32_MAX / 2 seems like a long
time? There are no commants about this timeout handling and I would have to
guess what is happening here. I would prefer having comments to guide me.

Chris

> +
> +    event(self, T_PACKET_EVENT_SEND_AGAIN);
> +  } else {
> +    make_pending(self, head);
> +  }
> +
> +  output_packet(self, head, now);
> +}
> +
> +void T_packet_process(T_packet_control* self, int ch_or_nothing) {
> +  if (ch_or_nothing == -1) {
> +    idle_processing(self);
> +    return;
> +  }
> +
> +  uint8_t ch = (uint8_t)ch_or_nothing;
> +
> +  if (ch == '{') {
> +    begin(self);
> +    return;
> +  }
> +
> +  switch (self->state) {
> +    case T_PACKET_STATE_SEQ_ACK:
> +      do_seq_ack(self, ch);
> +      break;
> +    case T_PACKET_STATE_TYPE:
> +      do_type(self, ch);
> +      break;
> +    case T_PACKET_STATE_COLON:
> +      do_colon(self, ch);
> +      break;
> +    case T_PACKET_STATE_VALUE:
> +      do_value(self, ch);
> +      break;
> +    case T_PACKET_STATE_DECODE_DATA:
> +      do_decode_data(self, ch);
> +      break;
> +    case T_PACKET_STATE_HASH:
> +      do_hash(self, ch);
> +      break;
> +    case T_PACKET_STATE_CRC:
> +      do_crc(self, ch);
> +      break;
> +    case T_PACKET_STATE_REJECT:
> +      do_reject(self, ch);
> +      break;
> +    default:
> +      /* Wait for packet start */
> +      event(self, T_PACKET_EVENT_GARBAGE);
> +      break;
> +  }
> +}
> +
> +void T_packet_done_default(T_packet_control* self, T_packet_packet* pkt) {
> +  (void)self;
> +  (void)pkt;
> +}
> +
> +void T_packet_output_hello(T_packet_control* self, T_packet_packet* pkt) {
> +  (void)pkt;
> +  output_simple_packet(self, 'H');
> +}
> +
> +void T_packet_output_signal(T_packet_control* self, T_packet_packet* pkt) {
> +  uint32_t crc = output_start(self, 'S');
> +  T_packet_signal_packet* signal_pkt = (T_packet_signal_packet*)pkt;
> +  crc = output_value(self, crc, signal_pkt->signal_number);
> +  crc = output_value(self, crc, signal_pkt->signal_value);
> +  output_crc(self, crc);
> +  output_end(self);
> +}
> +
> +static void b64_output_char(int c, void* arg) {
> +  T_packet_control* self = arg;
> +  output_char(self, (uint8_t)c);
> +  self->crc_calculated = _CRC24Q_Update(self->crc_calculated, (uint8_t)c);
> +}
> +
> +void T_packet_output_channel(T_packet_control* self, T_packet_packet* pkt) {
> +  uint32_t crc = output_start(self, 'C');
> +  T_packet_channel_packet* channel_pkt = (T_packet_channel_packet*)pkt;
> +  crc = output_value(self, crc, channel_pkt->channel_number);
> +  crc = output_value(self, crc, channel_pkt->data_size);
> +  output_crc(self, crc);
> +  output_char(self, '+');
> +  self->crc_calculated = CRC24Q_SEED;
> +  _Base64url_Encode(b64_output_char, self, channel_pkt->data_begin,
> +                    channel_pkt->data_size, NULL, INT_MAX);
> +  output_crc(self, self->crc_calculated);
> +  output_end(self);
> +}
> +
> +void T_packet_cancel(T_packet_control* self, T_packet_packet* pkt) {
> +  T_packet_packet* enq_pkt = self->snd_head;
> +  T_packet_packet** prev = &self->snd_head;
> +
> +  if (self->snd_pending == pkt) {
> +    self->snd_pending = NULL;
> +  }
> +
> +  while (enq_pkt != NULL) {
> +    if (pkt == enq_pkt) {
> +      *prev = pkt->next;
> +
> +      if (self->snd_tail == &pkt->next) {
> +        self->snd_tail = prev;
> +      }
> +
> +      return;
> +    }
> +
> +    prev = &enq_pkt->next;
> +    enq_pkt = enq_pkt->next;
> +  }
> +}
> +
> +static void done_success(T_packet_control* self, T_packet_packet* pkt) {
> +  (void)self;
> +  T_packet_packet_transfer* transfer = (T_packet_packet_transfer*)pkt;
> +  transfer->status = T_PACKET_SUCCESSFUL;
> +}
> +
> +T_packet_status T_packet_send(T_packet_control* self,
> +                              T_packet_packet_transfer* transfer,
> +                              uint32_t timeout) {
> +  if (self->input_char == NULL) {
> +    T_packet_remove_event_handler(self, &transfer->event);
> +    return T_PACKET_NO_INPUT_CHAR_HANDLER;
> +  }
> +
> +  transfer->status = T_PACKET_CONTINUE;
> +  T_packet_enqueue(self, &transfer->base);
> +  T_packet_process(self, -1);
> +
> +  bool check_for_timeout = (timeout != 0);
> +  uint32_t t0 = 0;
> +
> +  if (check_for_timeout) {
> +    t0 = (*self->clock_monotonic)(self);
> +  }
> +
> +  while (true) {
> +    T_packet_process(self, (*self->input_char)(self));
> +
> +    if (transfer->status != T_PACKET_CONTINUE) {
> +      return transfer->status;
> +    }
> +
> +    if (check_for_timeout) {
> +      uint32_t t1 = (*self->clock_monotonic)(self);
> +      uint32_t new_timeout = timeout - (t1 - t0);
> +
> +      if (new_timeout > timeout) {
> +        T_packet_cancel(self, &transfer->base);
> +        T_packet_remove_event_handler(self, &transfer->event);
> +        return T_PACKET_TIMEOUT;
> +      }
> +
> +      timeout = new_timeout;
> +      t0 = t1;
> +    }
> +  }
> +}
> +
> +T_packet_status T_packet_signal(T_packet_control* self,
> +                                uint64_t signal_number,
> +                                uint64_t signal_value,
> +                                uint32_t timeout) {
> +  T_packet_packet_transfer transfer;
> +  T_packet_initialize_signal(&transfer.signal, signal_number, signal_value,
> +                             done_success);
> +  return T_packet_send(self, &transfer, timeout);
> +}
> +
> +T_packet_status T_packet_channel_push(T_packet_control* self,
> +                                      uint64_t channel_number,
> +                                      const void* data_begin,
> +                                      size_t data_size,
> +                                      uint32_t timeout) {
> +  T_packet_packet_transfer transfer;
> +  T_packet_initialize_channel(&transfer.channel, channel_number, data_begin,
> +                              data_size, done_success);
> +  return T_packet_send(self, &transfer, timeout);
> +}
> +
> +typedef struct {
> +  T_packet_packet_transfer base;
> +  void* receive_begin;
> +  size_t receive_size_max;
> +  size_t receive_size_return;
> +} channel_transfer;
> +
> +static T_packet_status channel_exchange_event(T_packet_control* self,
> +                                              T_packet_event_control* ctrl,
> +                                              T_packet_event evt) {
> +  channel_transfer* transfer =
> +      RTEMS_CONTAINER_OF(ctrl, channel_transfer, base.event);
> +
> +  if (evt == T_PACKET_EVENT_CHANNEL_BEGIN) {
> +    if (T_packet_get_channel_number(self) !=
> +        transfer->base.channel.channel_number) {
> +      return T_PACKET_CONTINUE;
> +    }
> +
> +    size_t size = T_packet_get_channel_size(self);
> +
> +    if (size <= transfer->receive_size_max) {
> +      transfer->receive_size_return = size;
> +      T_packet_set_channel_target(self, transfer->receive_begin);
> +    } else {
> +      transfer->base.status = T_PACKET_OVERFLOW;
> +      T_packet_remove_event_handler(self, ctrl);
> +    }
> +
> +    return T_PACKET_SUCCESSFUL;
> +  }
> +
> +  if (evt == T_PACKET_EVENT_CHANNEL_END) {
> +    if (T_packet_get_channel_number(self) !=
> +        transfer->base.channel.channel_number) {
> +      return T_PACKET_CONTINUE;
> +    }
> +
> +    transfer->base.status = T_PACKET_SUCCESSFUL;
> +    T_packet_remove_event_handler(self, ctrl);
> +    T_packet_output_acknowledge(self);
> +    return T_PACKET_SUCCESSFUL;
> +  }
> +
> +  return T_PACKET_CONTINUE;
> +}
> +
> +T_packet_status T_packet_channel_exchange(T_packet_control* self,
> +                                          uint64_t channel_number,
> +                                          const void* transmit_begin,
> +                                          size_t transmit_size,
> +                                          void* receive_begin,
> +                                          size_t* receive_size,
> +                                          uint32_t timeout) {
> +  channel_transfer transfer;
> +  T_packet_initialize_channel(&transfer.base.channel, channel_number,
> +                              transmit_begin, transmit_size,
> +                              T_packet_done_default);
> +  transfer.receive_begin = receive_begin;
> +  transfer.receive_size_max = *receive_size;
> +  transfer.receive_size_return = 0;
> +  T_packet_prepend_event_handler(self, &transfer.base.event,
> +                                 channel_exchange_event);
> +  T_packet_status status = T_packet_send(self, &transfer.base, timeout);
> +  *receive_size = transfer.receive_size_return;
> +  return status;
> +}
> diff --git a/spec/build/cpukit/librtemstest.yml b/spec/build/cpukit/librtemstest.yml
> index fc268f8971..8389962c16 100644
> --- a/spec/build/cpukit/librtemstest.yml
> +++ b/spec/build/cpukit/librtemstest.yml
> @@ -13,6 +13,7 @@ install:
>    - cpukit/include/rtems/test.h
>    - cpukit/include/rtems/test-info.h
>    - cpukit/include/rtems/test-gcov.h
> +  - cpukit/include/rtems/test-packet.h
>    - cpukit/include/rtems/test-printer.h
>    - cpukit/include/rtems/test-scheduler.h
>  install-path: ${BSP_LIBDIR}
> @@ -28,6 +29,7 @@ source:
>  - cpukit/libtest/t-test-checks.c
>  - cpukit/libtest/t-test-hash-sha256.c
>  - cpukit/libtest/t-test-interrupt.c
> +- cpukit/libtest/t-test-packet.c
>  - cpukit/libtest/t-test-rtems-context.c
>  - cpukit/libtest/t-test-rtems-fds.c
>  - cpukit/libtest/t-test-rtems-heap.c
> diff --git a/spec/build/testsuites/unit/unit-no-clock-0.yml b/spec/build/testsuites/unit/unit-no-clock-0.yml
> index 825e7619cd..2cbf505ab5 100644
> --- a/spec/build/testsuites/unit/unit-no-clock-0.yml
> +++ b/spec/build/testsuites/unit/unit-no-clock-0.yml
> @@ -18,6 +18,7 @@ source:
>  - testsuites/unit/tc-misaligned-builtin-memcpy.c
>  - testsuites/unit/tc-score-msgq.c
>  - testsuites/unit/tc-score-rbtree.c
> +- testsuites/unit/tc-test-packet.c
>  - testsuites/unit/ts-unit-no-clock-0.c
>  stlib: []
>  target: testsuites/unit/ts-unit-no-clock-0.exe
> diff --git a/testsuites/unit/tc-test-packet.c b/testsuites/unit/tc-test-packet.c
> new file mode 100644
> index 0000000000..a56ad74fea
> --- /dev/null
> +++ b/testsuites/unit/tc-test-packet.c
> @@ -0,0 +1,604 @@
> +/* SPDX-License-Identifier: BSD-2-Clause */
> +
> +/*
> + * Copyright (C) 2024 embedded brains GmbH & Co. KG
> + *
> + * Redistribution and use in source and binary forms, with or without
> + * modification, are permitted provided that the following conditions
> + * are met:
> + * 1. Redistributions of source code must retain the above copyright
> + *    notice, this list of conditions and the following disclaimer.
> + * 2. Redistributions in binary form must reproduce the above copyright
> + *    notice, this list of conditions and the following disclaimer in the
> + *    documentation and/or other materials provided with the distribution.
> + *
> + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
> + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
> + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
> + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
> + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
> + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
> + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
> + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
> + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
> + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
> + * POSSIBILITY OF SUCH DAMAGE.
> + */
> +
> +#include <rtems/test-packet.h>
> +
> +#include <inttypes.h>
> +#include <limits.h>
> +#include <string.h>
> +#include <sys/endian.h>
> +
> +#include <rtems/crc.h>
> +
> +#include <rtems/test.h>
> +
> +typedef struct {
> +  T_packet_control base;
> +  T_packet_event_control event;
> +  T_packet_event_control event_success;
> +  T_packet_event_control event_continue;
> +  size_t response_idx;
> +  char response_buf[256];
> +  char load_buf[32];
> +  uint32_t counter;
> +  T_packet_signal_packet signal_pkt;
> +  T_packet_channel_packet channel_pkt;
> +  const char* input;
> +  uint32_t crc;
> +} test_control;
> +
> +typedef struct {
> +  const char* input;
> +  const char* response;
> +  const char* load;
> +} test_case;
> +
> +static const test_case test_cases[] = {
> +    {"@X at X@X at X@X", "@X at Q@R{1211:H#Mos3}@r at X@X at X@X at T@R{1211:H#Mos3}@r", ""},
> +    {"{a{B at XBCC:H#h4zx}x{{BBCC:H#h4zx}",
> +     "@X at H@Q at R{12BB:H#EmCT}@r at G@D at T@R{12BB:H#EmCT}@r", ""},
> +    {"{123456789", "@N at Q@R{1211:H#Mos3}@r at G@G at G@G", ""},
> +    {"{1234:HX", "@N at Q@R{1211:H#Mos3}@r", ""},
> +    {"{1234:H#\xe2\x82\xac", "@N at Q@R{1211:H#Mos3}@r at G@G", ""},
> +    {"{1234:H#|", "@N at Q@R{1211:H#Mos3}@r", ""},
> +    {"{1234:H#abcd|", "@N at Q@R{1211:H#Mos3}@r", ""},
> +    {"{12345", "@N at Q@R{1211:H#Mos3}@r", ""},
> +    {"{|{\xe2\x82\xac", "@N at Q@R{1211:H#Mos3}@r at N@T at R{1211:H#Mos3}@r at G@G", ""},
> +    {"{1234:X:#gOys}{1312:X:#N4Ge}",
> +     "@E at R{1211:R:12#TQxe}@r at E@R{1311:R:13#PksK}@r", ""},
> +    {"@X{1211:Y#cYlF}{1312:Y#qyFL}",
> +     "@X at Q@R{1211:H#Mos3}@r at E@T at R{1211:H#Mos3}@r at E@o at R{1311:R:13#PksK}@r", ""},
> +    {"{1234:JX", "@N at Q@R{1211:H#Mos3}@r", ""},
> +    {"{1234:J:\xe2\x82\xac", "@N at Q@R{1211:H#Mos3}@r at G@G", ""},
> +    {"{1234:J:|", "@N at Q@R{1211:H#Mos3}@r", ""},
> +    {"{1234:J:123456789a_", "@N at Q@R{1211:H#Mos3}@r", ""},
> +    {"{1234:J::", "@N at Q@R{1211:H#Mos3}@r", ""},
> +    {"{1234:J:0#BzfY|", "@N at Q@R{1211:H#Mos3}@r", ""},
> +    {"{1234:J:0#BzfY+", "@N at Q@R{1211:H#Mos3}@r", ""},
> +    {"{1234:J:0#abcd}", "@N at Q@R{1211:H#Mos3}@r", ""},
> +    {"{1234:J:AAAAAAAAAAAAAAAAAAAAADerb7v#hu_C}", "@J at Q@R{1212:H#Md21}@r", ""},
> +    {"{1234:J:Derb7v#r2GB}", "@J at Q@R{1212:H#Md21}@r", ""},
> +    {"{1234:L:0:0:", "@N at Q@R{1211:H#Mos3}@r", ""},
> +    {"{1234:L:0#AZrc+", "@N at Q@R{1211:H#Mos3}@r", ""},
> +    {"{1234:L:0#uykR+", "@N at Q@R{1211:H#Mos3}@r", ""},
> +    {"@X{1212:L:A:A#EnK5+#AAAA}",
> +     "@X at Q@R{1211:H#Mos3}@r at L@o at R{1311:R:12#uAfx}@r at G@G at G@G at G@G", ""},
> +    {"{@L+SGVsbG8sIHdvcmxkI#", "@L at N@Q at R{1211:H#Mos3}@r", "Hello, world "},
> +    {"{@L+SGVsbG8sIHdvcmxkI|", "@L at N@Q at R{1211:H#Mos3}@r", "Hello, world "},
> +    {"{@L+SGVsbG8sIHdvcmxkIQ==#b1BU}", "@L at l@Q at R{1212:H#Md21}@r",
> +     "Hello, world!"},
> +    {"{1234:S:BI0VniHZUMh:A#8aeE}", "@S at Q@R{1212:H#Md21}@r", ""},
> +    {"{1234:S:BI0VniHZUMh:A#8aeE+", "@N at Q@R{1211:H#Mos3}@r", ""},
> +    {"{1234:S:BI0VniHZUMh:AKvN76vN76vN#c-zW}", "@S at R{1211:R:12#TQxe}@r", ""},
> +    {"{1234:C:BI0VniHZUMh:A#gl_H+", "@C at R{1211:R:12#TQxe}@r", ""},
> +    {"{1234:C:BI0VniHZUMh:N#vKHp+SGVsbG8sIHdvcmxkIQ==#b1BU}",
> +     "@C at c@Q at R{1212:H#Md21}@r", "Hello, world!"},
> +    {"@X{121 at S@X2:A#FhB3}@X at X@X at X@X{1313:A#TOcs}@X{1413:A#y8uF}",
> +     "@X at Q@R{1211:H#Mos3}@r at X@A at o@Q at R{1312:S:A:Ej#5Yfj}@r at X@X at X@X at T@R{1312:S:"
> +     "A:"
> +     "Ej#5Yfj}@r at X@A at o@s at X@A",
> +     ""},
> +    {"@X{1212:A at C#FhB3}@X",
> +     "@X at Q@R{1211:H#Mos3}@r at A@o at Q@R{1312:C:RW:G#sdj6+BlubBlub#1V-z}@r at X", ""}};
> +
> +static void process(test_control* self, const char* input) {
> +  uint32_t crc = self->crc;
> +
> +  while (*input != '\0') {
> +    crc = _CRC24Q_Update(crc, (uint8_t)*input);
> +    T_packet_process(&self->base, (uint8_t)*input);
> +    ++input;
> +  }
> +
> +  self->crc = crc;
> +}
> +
> +static void process_char(int c, void* arg) {
> +  test_control* self = (test_control*)arg;
> +  self->crc = _CRC24Q_Update(self->crc, (uint8_t)c);
> +  T_packet_process(&self->base, (uint8_t)c);
> +}
> +
> +static void output_char(T_packet_control* base, uint8_t ch) {
> +  test_control* self = (test_control*)base;
> +  size_t idx = self->response_idx;
> +  self->response_idx = idx + 1;
> +
> +  if (idx < sizeof(self->response_buf) - 1) {
> +    self->response_buf[idx] = (char)ch;
> +  }
> +}
> +
> +static int input_char(T_packet_control* base) {
> +  test_control* self = (test_control*)base;
> +  const char* input = self->input;
> +  uint8_t ch = (uint8_t)*input;
> +
> +  if (ch == '\0') {
> +    return -1;
> +  }
> +
> +  self->input = input + 1;
> +  return ch;
> +}
> +
> +static T_packet_status event(T_packet_control* base,
> +                             T_packet_event_control* ctrl,
> +                             T_packet_event evt) {
> +  test_control* self = (test_control*)base;
> +  T_eq_ptr(ctrl, &self->event);
> +  output_char(base, '@');
> +
> +  switch (evt) {
> +    case T_PACKET_EVENT_ACKNOWLEDGE:
> +      output_char(base, 'A');
> +      break;
> +    case T_PACKET_EVENT_CHANNEL_BEGIN: {
> +      output_char(base, 'C');
> +      T_eq_u64(T_packet_get_channel_number(base), UINT64_C(0x1234567887654321));
> +      size_t size = T_packet_get_channel_size(base);
> +
> +      if (size != 0) {
> +        T_packet_set_channel_target(base, &self->load_buf[0]);
> +        T_eq_sz(size, 0xd);
> +      }
> +
> +      break;
> +    }
> +    case T_PACKET_EVENT_CHANNEL_END:
> +      output_char(base, 'c');
> +      T_packet_output_acknowledge(base);
> +      break;
> +    case T_PACKET_EVENT_DUPLICATE:
> +      output_char(base, 'D');
> +      break;
> +    case T_PACKET_EVENT_GARBAGE:
> +      output_char(base, 'G');
> +      break;
> +    case T_PACKET_EVENT_HELLO:
> +      output_char(base, 'H');
> +      T_packet_output_acknowledge(base);
> +      break;
> +    case T_PACKET_EVENT_JUMP:
> +      output_char(base, 'J');
> +      T_packet_output_acknowledge(base);
> +      T_eq_uptr((uintptr_t)T_packet_get_jump_address(base),
> +                (uintptr_t)0xdeadbeef);
> +      break;
> +    case T_PACKET_EVENT_NOTHING:
> +      output_char(base, 'X');
> +      break;
> +    case T_PACKET_EVENT_LOAD_BEGIN:
> +      output_char(base, 'L');
> +
> +      if (T_packet_get_load_address(base) == NULL) {
> +        T_packet_output_reject(base);
> +      } else {
> +        T_eq_ptr(T_packet_get_load_address(base), &self->load_buf[0]);
> +        T_eq_sz(T_packet_get_load_size(base), 13);
> +      }
> +
> +      break;
> +    case T_PACKET_EVENT_LOAD_END:
> +      output_char(base, 'l');
> +      T_packet_output_acknowledge(base);
> +      T_eq_ptr(T_packet_get_load_address(base), &self->load_buf[0]);
> +      T_eq_sz(T_packet_get_load_size(base), 13);
> +      break;
> +    case T_PACKET_EVENT_OUTPUT_BEGIN:
> +      output_char(base, 'R');
> +      break;
> +    case T_PACKET_EVENT_OUTPUT_END:
> +      output_char(base, 'r');
> +      break;
> +    case T_PACKET_EVENT_SEND_DONE:
> +      output_char(base, 'o');
> +      break;
> +    case T_PACKET_EVENT_REJECT:
> +      output_char(base, 'E');
> +      break;
> +    case T_PACKET_EVENT_NACK:
> +      output_char(base, 'N');
> +      break;
> +    case T_PACKET_EVENT_SEND_DEQUEUE:
> +      output_char(base, 'Q');
> +      break;
> +    case T_PACKET_EVENT_SEND_AGAIN:
> +      output_char(base, 'T');
> +      break;
> +    case T_PACKET_EVENT_SIGNAL: {
> +      output_char(base, 'S');
> +      T_eq_u64(T_packet_get_signal_number(base), UINT64_C(0x1234567887654321));
> +      uint64_t value = T_packet_get_signal_value(base);
> +
> +      if (value == 0) {
> +        T_packet_output_acknowledge(base);
> +      } else {
> +        T_eq_u64(value, UINT64_C(0xabcdefabcdefabcd));
> +      }
> +
> +      break;
> +    }
> +    default:
> +      output_char(base, 'U');
> +      break;
> +  }
> +
> +  return T_PACKET_SUCCESSFUL;
> +}
> +
> +static T_packet_status event_success(T_packet_control* base,
> +                                     T_packet_event_control* ctrl,
> +                                     T_packet_event evt) {
> +  (void)evt;
> +  test_control* self = (test_control*)base;
> +  T_eq_ptr(ctrl, &self->event_success);
> +  output_char(base, '@');
> +  output_char(base, 'Z');
> +  return T_PACKET_SUCCESSFUL;
> +}
> +
> +static T_packet_status event_continue(T_packet_control* base,
> +                                      T_packet_event_control* ctrl,
> +                                      T_packet_event evt) {
> +  (void)evt;
> +  test_control* self = (test_control*)base;
> +  T_eq_ptr(ctrl, &self->event_continue);
> +  output_char(base, '@');
> +  output_char(base, 'Y');
> +  return T_PACKET_CONTINUE;
> +}
> +
> +static T_packet_status event_channel_load(T_packet_control* base,
> +                                          T_packet_event_control* ctrl,
> +                                          T_packet_event evt) {
> +  test_control* self = (test_control*)base;
> +  T_eq_ptr(ctrl, &self->event);
> +
> +  if (evt == T_PACKET_EVENT_CHANNEL_BEGIN) {
> +    if (T_packet_get_channel_number(base) != 3) {
> +      return T_PACKET_CONTINUE;
> +    }
> +
> +    T_lt_sz(T_packet_get_channel_size(base), sizeof(self->load_buf));
> +    T_packet_set_channel_target(base, &self->load_buf[0]);
> +    return T_PACKET_SUCCESSFUL;
> +  }
> +
> +  if (evt == T_PACKET_EVENT_CHANNEL_END) {
> +    if (T_packet_get_channel_number(base) != 3) {
> +      return T_PACKET_CONTINUE;
> +    }
> +
> +    T_packet_output_acknowledge(base);
> +    return T_PACKET_SUCCESSFUL;
> +  }
> +
> +  return T_PACKET_CONTINUE;
> +}
> +
> +static void output_load(test_control* self) {
> +  T_packet_process(&self->base, '{');
> +  self->crc = CRC24Q_SEED;
> +  process(self, "1234:L:");
> +
> +  uint8_t addr[9];
> +  addr[0] = 0;
> +  be64enc(&addr[1], (uint64_t)(uintptr_t)&self->load_buf[0]);
> +  _Base64url_Encode(process_char, self, &addr[0], sizeof(addr), NULL, INT_MAX);
> +
> +  /* The 'N' is the length of "Hello, world!" */
> +  process(self, ":N");
> +
> +  T_packet_process(&self->base, '#');
> +
> +  for (int i = 18; i >= 0; i -= 6) {
> +    uint8_t ch = _Base64url_Encoding[(self->crc >> i) & 0x3f];
> +    T_packet_process(&self->base, ch);
> +  }
> +}
> +
> +static void signal_done(T_packet_control* base, T_packet_packet* pkt) {
> +  test_control* self = (test_control*)base;
> +  output_char(base, '@');
> +  output_char(base, 's');
> +  T_eq_ptr(pkt, &self->signal_pkt);
> +}
> +
> +static void channel_done(T_packet_control* base, T_packet_packet* pkt) {
> +  test_control* self = (test_control*)base;
> +  output_char(base, '@');
> +  output_char(base, 'e');
> +  T_eq_ptr(pkt, &self->channel_pkt);
> +}
> +
> +static uint32_t now(T_packet_control* base) {
> +  test_control* self = (test_control*)base;
> +  uint32_t counter = self->counter;
> +  self->counter = counter + 1;
> +  return self->counter;
> +}
> +
> +static const char channel_data[] = {0x06, 0x5b, 0x9b, 0x06, 0x5b, 0x9b};
> +
> +static void clear_response(test_control* self) {
> +  self->response_idx = 0;
> +  memset(&self->response_buf[0], 0, sizeof(self->response_buf));
> +}
> +
> +static void initialize_test_control(test_control* self) {
> +  self->input = "";
> +  memset(&self->base, 0xff, sizeof(self->base));
> +  memset(&self->load_buf[0], 0, sizeof(self->load_buf));
> +  clear_response(self);
> +  T_packet_initialize(&self->base, 3445, NULL, output_char, now);
> +  T_packet_prepend_event_handler(&self->base, &self->event, event);
> +}
> +
> +T_TEST_CASE(TPacket) {
> +  test_control self;
> +
> +  for (size_t i = 0; i < RTEMS_ARRAY_SIZE(test_cases); ++i) {
> +    initialize_test_control(&self);
> +    const test_case* tc = &test_cases[i];
> +    const char* ch = tc->input;
> +
> +    while (*ch != '\0') {
> +      if (*ch == '@') {
> +        ++ch;
> +        switch (*ch) {
> +          case 'X':
> +            T_packet_process(&self.base, -1);
> +            break;
> +          case 'L':
> +            output_load(&self);
> +            break;
> +          case 'S':
> +            T_packet_initialize_signal(&self.signal_pkt, 0, 0x123, signal_done);
> +            T_packet_enqueue(&self.base, &self.signal_pkt.base);
> +            break;
> +          case 'C':
> +            T_packet_initialize_channel(&self.channel_pkt, 0x456,
> +                                        &channel_data[0], sizeof(channel_data),
> +                                        channel_done);
> +            T_packet_enqueue(&self.base, &self.channel_pkt.base);
> +            break;
> +          default:
> +            T_unreachable();
> +            break;
> +        }
> +      } else {
> +        T_packet_process(&self.base, (uint8_t)*ch);
> +      }
> +
> +      ++ch;
> +    }
> +
> +    T_eq_str(&self.response_buf[0], tc->response);
> +    T_eq_str(&self.load_buf[0], tc->load);
> +  }
> +}
> +
> +T_TEST_CASE(TPacketCancel) {
> +  test_control self;
> +  initialize_test_control(&self);
> +  T_null(self.base.snd_pending);
> +  T_eq_ptr(self.base.snd_head, &self.base.hello);
> +  T_eq_ptr(self.base.snd_tail, &self.base.hello.next);
> +
> +  T_packet_process(&self.base, -1);
> +  T_eq_ptr(self.base.snd_pending, &self.base.hello);
> +  T_eq_ptr(self.base.snd_head, &self.base.hello);
> +  T_eq_ptr(self.base.snd_tail, &self.base.hello.next);
> +
> +  T_packet_packet pkt;
> +  memset(&pkt, 0xff, sizeof(pkt));
> +  T_packet_enqueue(&self.base, &pkt);
> +  T_eq_ptr(self.base.snd_head, &self.base.hello);
> +  T_eq_ptr(self.base.snd_tail, &pkt.next);
> +
> +  T_packet_packet pkt_2;
> +  memset(&pkt_2, 0xff, sizeof(pkt_2));
> +  T_packet_enqueue(&self.base, &pkt_2);
> +  T_eq_ptr(self.base.snd_head, &self.base.hello);
> +  T_eq_ptr(self.base.snd_tail, &pkt_2.next);
> +
> +  T_packet_cancel(&self.base, &self.base.hello);
> +  T_null(self.base.snd_pending);
> +  T_eq_ptr(self.base.snd_head, &pkt);
> +  T_eq_ptr(self.base.snd_tail, &pkt_2.next);
> +
> +  T_packet_cancel(&self.base, &pkt_2);
> +  T_null(self.base.snd_pending);
> +  T_eq_ptr(self.base.snd_head, &pkt);
> +  T_eq_ptr(self.base.snd_tail, &pkt.next);
> +
> +  /* Double cancel has no effects */
> +  T_packet_cancel(&self.base, &pkt_2);
> +  T_null(self.base.snd_pending);
> +  T_eq_ptr(self.base.snd_head, &pkt);
> +  T_eq_ptr(self.base.snd_tail, &pkt.next);
> +
> +  T_packet_cancel(&self.base, &pkt);
> +  T_null(self.base.snd_pending);
> +  T_null(self.base.snd_head);
> +  T_eq_ptr(self.base.snd_tail, &self.base.snd_head);
> +}
> +
> +T_TEST_CASE(TPacketSignal) {
> +  test_control self;
> +  initialize_test_control(&self);
> +  T_packet_status status = T_packet_signal(&self.base, 1, 2, 0);
> +  T_eq_int(status, T_PACKET_NO_INPUT_CHAR_HANDLER);
> +  T_eq_str(&self.response_buf[0], "");
> +
> +  self.base.input_char = input_char;
> +  self.input = "{1212:A#FhB3}{1313:A#TOcs}";
> +  status = T_packet_signal(&self.base, 3, 4, 0);
> +  T_eq_int(status, T_PACKET_SUCCESSFUL);
> +  T_null(self.base.snd_pending);
> +  T_null(self.base.snd_head);
> +  T_eq_str(&self.response_buf[0],
> +           "@X at Q@R{1211:H#Mos3}@r at A@o at Q@R{1312:S:D:E#AzkU}@r at A@o");
> +
> +  clear_response(&self);
> +  status = T_packet_signal(&self.base, 5, 6, 2);
> +  T_eq_int(status, T_PACKET_TIMEOUT);
> +  T_null(self.base.snd_pending);
> +  T_null(self.base.snd_head);
> +  T_eq_str(&self.response_buf[0],
> +           "@X at Q@R{1413:S:F:G#So3p}@r at X@X at T@R{1413:S:F:G#So3p}@r");
> +}
> +
> +T_TEST_CASE(TPacketChannelPush) {
> +  test_control self;
> +  initialize_test_control(&self);
> +  T_packet_status status = T_packet_channel_push(
> +      &self.base, 1, &channel_data[0], sizeof(channel_data), 0);
> +  T_eq_int(status, T_PACKET_NO_INPUT_CHAR_HANDLER);
> +  T_eq_str(&self.response_buf[0], "");
> +
> +  self.base.input_char = input_char;
> +  self.input = "{1212:A#FhB3}{1313:A#TOcs}";
> +  status = T_packet_channel_push(&self.base, 2, &channel_data[0],
> +                                 sizeof(channel_data), 0);
> +  T_eq_int(status, T_PACKET_SUCCESSFUL);
> +  T_null(self.base.snd_pending);
> +  T_null(self.base.snd_head);
> +  T_eq_str(
> +      &self.response_buf[0],
> +      "@X at Q@R{1211:H#Mos3}@r at A@o at Q@R{1312:C:C:G#J4sp+BlubBlub#1V-z}@r at A@o");
> +
> +  clear_response(&self);
> +  status = T_packet_channel_push(&self.base, 3, &channel_data[0],
> +                                 sizeof(channel_data), 2);
> +  T_eq_int(status, T_PACKET_TIMEOUT);
> +  T_null(self.base.snd_pending);
> +  T_null(self.base.snd_head);
> +  T_eq_str(&self.response_buf[0],
> +           "@X at Q@R{1413:C:D:G#4RFf+BlubBlub#1V-z}@r at X@X at T@R{1413:C:D:G#4RFf+"
> +           "BlubBlub#1V-z}@r");
> +}
> +
> +T_TEST_CASE(TPacketChannelExchange) {
> +  test_control self;
> +  initialize_test_control(&self);
> +  T_packet_remove_event_handler(&self.base, &self.event);
> +
> +  char receive_buf[32];
> +  size_t receive_size = sizeof(receive_buf);
> +  memset(&receive_buf[0], 0, sizeof(receive_buf));
> +  T_packet_status status = T_packet_channel_exchange(
> +      &self.base, 1, &channel_data[0], sizeof(channel_data), &receive_buf[0],
> +      &receive_size, 0);
> +  T_eq_int(status, T_PACKET_NO_INPUT_CHAR_HANDLER);
> +  T_eq_str(&self.response_buf[0], "");
> +  T_eq_sz(receive_size, 0);
> +
> +  self.base.input_char = input_char;
> +  self.input = "{1212:A#FhB3}{1313:C:B:I#J6Gn+Zm9vIGJhcgA=#oHjZ}";
> +  receive_size = sizeof(receive_buf);
> +  memset(&receive_buf[0], 0, sizeof(receive_buf));
> +  status = T_packet_channel_exchange(&self.base, 1, &channel_data[0],
> +                                     sizeof(channel_data), &receive_buf[0],
> +                                     &receive_size, 0);
> +  T_eq_int(status, T_PACKET_SUCCESSFUL);
> +  T_null(self.base.snd_pending);
> +  T_null(self.base.snd_head);
> +  T_eq_str(&self.response_buf[0],
> +           "{1211:H#Mos3}{1312:C:B:G#pIL-+BlubBlub#1V-z}{1413:A#y8uF}");
> +  T_eq_sz(receive_size, 8);
> +  T_eq_str(&receive_buf[0], "foo bar");
> +
> +  clear_response(&self);
> +  T_packet_append_event_handler(&self.base, &self.event, event_channel_load);
> +  self.input =
> +      "{1415:C:D:F#_jiM+b29wcwA=#xfkE}{1515:C:C:I#uBx9+Zm9vIGJhcgA=#oHjZ}";
> +  receive_size = 7;
> +  memset(&receive_buf[0], 0, sizeof(receive_buf));
> +  status = T_packet_channel_exchange(&self.base, 2, &channel_data[0],
> +                                     sizeof(channel_data), &receive_buf[0],
> +                                     &receive_size, 0);
> +  T_eq_int(status, T_PACKET_OVERFLOW);
> +  T_null(self.base.snd_pending);
> +  T_null(self.base.snd_head);
> +  T_eq_str(&self.response_buf[0],
> +           "{1513:C:C:G#mcuA+BlubBlub#1V-z}{1614:A#e961}{1714:R:15#Eohl}");
> +  T_eq_sz(receive_size, 0);
> +  T_eq_str(&receive_buf[0], "");
> +  T_eq_str(&self.load_buf[0], "oops");
> +  T_packet_remove_event_handler(&self.base, &self.event);
> +}
> +
> +T_TEST_CASE(TPacketEventHandler) {
> +  test_control self;
> +  initialize_test_control(&self);
> +  T_eq_ptr(self.base.event_head, &self.event);
> +
> +  T_packet_process(&self.base, -1);
> +  process(&self, "{1212:A#FhB3}{1313:A#TOcs}");
> +  T_eq_str(&self.response_buf[0], "@X at Q@R{1211:H#Mos3}@r at A@o at A");
> +
> +  clear_response(&self);
> +  T_packet_remove_event_handler(&self.base, &self.event);
> +  T_null(self.base.event_head);
> +  T_packet_process(&self.base, -1);
> +  T_eq_str(&self.response_buf[0], "");
> +
> +  clear_response(&self);
> +  T_packet_append_event_handler(&self.base, &self.event_success, event_success);
> +  T_packet_append_event_handler(&self.base, &self.event_continue,
> +                                event_continue);
> +  T_eq_ptr(self.base.event_head, &self.event_success);
> +  T_packet_process(&self.base, -1);
> +  T_eq_str(&self.response_buf[0], "@Z");
> +
> +  T_packet_remove_event_handler(&self.base, &self.event_success);
> +  T_eq_ptr(self.base.event_head, &self.event_continue);
> +
> +  T_packet_remove_event_handler(&self.base, &self.event_continue);
> +  T_null(self.base.event_head);
> +
> +  clear_response(&self);
> +  T_packet_prepend_event_handler(&self.base, &self.event_success,
> +                                 event_success);
> +  T_packet_prepend_event_handler(&self.base, &self.event_continue,
> +                                 event_continue);
> +  T_eq_ptr(self.base.event_head, &self.event_continue);
> +  T_packet_process(&self.base, -1);
> +  T_eq_str(&self.response_buf[0], "@Y at Z");
> +
> +  T_packet_remove_event_handler(&self.base, &self.event_success);
> +  T_eq_ptr(self.base.event_head, &self.event_continue);
> +
> +  /* Double remove has no effects */
> +  T_packet_remove_event_handler(&self.base, &self.event_success);
> +  T_eq_ptr(self.base.event_head, &self.event_continue);
> +
> +  T_packet_remove_event_handler(&self.base, &self.event_continue);
> +  T_null(self.base.event_head);
> +}


More information about the devel mailing list