#ifndef UDPS_CLIENT_H #define UDPS_CLIENT_H /** * @file udps_client.h * @brief Standalone UDPS (UDPStreamer) receiver library — C99, no MARTe2. * * Depends only on libc and BSD sockets, so it can be dropped into any C or C++ * program that needs to consume a UDPStreamer / DebugService stream. The wire * format is specified in Docs/Protocol.md; the library reference (and a worked * example) is Docs/UDPS-C-Client.md. * * Usage in one paragraph: fill a udps_client_config_t, create a client, install * callbacks, then call udps_client_poll() in a loop. The client owns the * connection state machine — it sends CONNECT, reassembles fragmented packets, * decodes CONFIG and DATA, sends keepalives, and reconnects when the server * goes silent. Nothing is done behind your back: no threads are created and * every callback runs inside your call to udps_client_poll(). * * Threading: a udps_client_t must be used from one thread at a time. */ #include #include #ifdef __cplusplus extern "C" { #endif /*---------------------------------------------------------------------------*/ /* Protocol constants */ /*---------------------------------------------------------------------------*/ /** Magic number: ASCII 'UDPS' stored little-endian. */ #define UDPS_MAGIC 0x53504455u /** Size of the packed packet header on the wire. */ #define UDPS_HEADER_SIZE 17u /** Size of one serialised signal descriptor in a CONFIG payload. */ #define UDPS_SIGNAL_DESC_SIZE 136u /** Value of udps_signal_t::time_signal_idx when the signal has no time reference. */ #define UDPS_NO_TIME_SIGNAL 0xFFFFFFFFu /** Upper bound on elements per signal; larger descriptors are rejected. */ #define UDPS_MAX_ELEMENTS (1u << 20) /** Packet types (udps_header_t::type). */ enum { UDPS_PKT_DATA = 0, /**< Server -> client: signal samples. */ UDPS_PKT_CONFIG = 1, /**< Server -> client: signal metadata. */ UDPS_PKT_ACK = 2, /**< Client -> server: keepalive. */ UDPS_PKT_CONNECT = 3, /**< Client -> server: open a session. */ UDPS_PKT_DISCONNECT = 4 /**< Either direction: close a session. */ }; /** Sample type codes (udps_signal_t::type_code). */ enum { UDPS_T_UINT8 = 0, UDPS_T_INT8 = 1, UDPS_T_UINT16 = 2, UDPS_T_INT16 = 3, UDPS_T_UINT32 = 4, UDPS_T_INT32 = 5, UDPS_T_UINT64 = 6, UDPS_T_INT64 = 7, UDPS_T_FLOAT32 = 8, UDPS_T_FLOAT64 = 9, UDPS_T_UNKNOWN = 255 }; /** Quantisation codes (udps_signal_t::quant_type). */ enum { UDPS_QUANT_NONE = 0, /**< Raw values in the signal's own type. */ UDPS_QUANT_UINT8 = 1, /**< [range_min, range_max] mapped onto uint8. */ UDPS_QUANT_INT8 = 2, UDPS_QUANT_UINT16 = 3, UDPS_QUANT_INT16 = 4 }; /** Time-reference modes (udps_signal_t::time_mode). */ enum { UDPS_TIME_PACKET = 0, /**< No per-element time; use packet arrival. */ UDPS_TIME_FULL_ARRAY = 1, /**< The time signal carries one stamp per element. */ UDPS_TIME_FIRST_SAMPLE = 2, /**< Time signal (scalar) stamps element 0. */ UDPS_TIME_LAST_SAMPLE = 3 /**< Time signal (scalar) stamps element N-1. */ }; /** Publishing modes (udps_frame_t::publish_mode). */ enum { UDPS_PUBLISH_STRICT = 0, /**< One packet per RT cycle. */ UDPS_PUBLISH_ACCUMULATE = 1, /**< A batch of cycles per packet. */ UDPS_PUBLISH_DECIMATE = 2 /**< One packet every N cycles. */ }; /*---------------------------------------------------------------------------*/ /* Data model */ /*---------------------------------------------------------------------------*/ /** Decoded 17-byte packet header. */ typedef struct { uint32_t magic; uint8_t type; uint32_t counter; /**< Same for every fragment of one update. */ uint16_t fragment_idx; uint16_t total_fragments; /**< 1 when the update fits in one datagram. */ uint32_t payload_bytes; } udps_header_t; /** Metadata for one streamed signal, as carried by the CONFIG payload. */ typedef struct { char name[65]; /**< NUL-terminated. */ uint8_t type_code; /**< UDPS_T_*. */ uint8_t quant_type; /**< UDPS_QUANT_*. */ uint8_t num_dimensions; /**< 0 scalar, 1 vector, 2 matrix. */ uint32_t num_rows; uint32_t num_cols; double range_min; /**< Physical range, used to dequantise. */ double range_max; uint8_t time_mode; /**< UDPS_TIME_*. */ double sampling_rate; /**< Hz; 0 when unknown. */ uint32_t time_signal_idx;/**< Index into the signal list, or UDPS_NO_TIME_SIGNAL. */ char unit[33]; /**< NUL-terminated. */ } udps_signal_t; /** * @brief Decoded values of one signal within a frame. * * Values are always physical doubles: quantised signals are already expanded * back onto [range_min, range_max]. @c count is @c num_samples for a scalar * signal in Accumulate mode (one value per batched cycle) and the signal's * element count in every other case. */ typedef struct { const double *values; uint32_t count; } udps_signal_values_t; /** One fully decoded DATA packet. */ typedef struct { uint32_t counter; /**< Packet counter; gaps mean lost datagrams. */ uint64_t hrt; /**< Producer's high-resolution timer at send. */ double recv_time; /**< Wall-clock seconds (CLOCK_REALTIME) at arrival. */ uint8_t publish_mode; /**< UDPS_PUBLISH_*. */ uint32_t num_samples; /**< Batched cycles; 1 unless Accumulate. */ uint32_t num_signals; const udps_signal_t *signals; /**< num_signals entries, CONFIG order. */ const udps_signal_values_t *values; /**< num_signals entries, same order. */ } udps_frame_t; /** Connection lifecycle events reported through udps_event_cb. */ typedef enum { UDPS_EVENT_CONNECTED, /**< Sockets are up and CONNECT was sent. */ UDPS_EVENT_DISCONNECTED, /**< Session dropped; the client will retry. */ UDPS_EVENT_ERROR /**< Recoverable problem; detail says what. */ } udps_event_t; /** Cumulative counters, never reset. */ typedef struct { uint64_t packets_received; /**< Datagrams (and TCP frames) accepted. */ uint64_t bytes_received; uint64_t frames_delivered; /**< DATA packets decoded and handed to you. */ uint64_t config_updates; uint64_t fragments_dropped; /**< Duplicate, stale or unplaceable fragments. */ uint64_t counter_gaps; /**< DATA packets missing from the sequence. */ uint64_t reconnects; } udps_stats_t; /*---------------------------------------------------------------------------*/ /* Client */ /*---------------------------------------------------------------------------*/ typedef struct udps_client udps_client_t; /** Called whenever a CONFIG packet redefines the signal set. */ typedef void (*udps_config_cb)(const udps_signal_t *signals, uint32_t num_signals, uint8_t publish_mode, void *user); /** * @brief Called for every decoded DATA packet. * * The frame and everything it points at are owned by the client and are only * valid until the callback returns — copy anything you need to keep. */ typedef void (*udps_data_cb)(const udps_frame_t *frame, void *user); /** Called on connection state changes and on recoverable errors. */ typedef void (*udps_event_cb)(udps_event_t event, const char *detail, void *user); /** * @brief Transport configuration. * * Zero-initialise with udps_client_config_init(), then override what you need. * Set @c multicast_group to switch from unicast to multicast: in unicast the * client sends CONNECT over UDP and receives everything on its ephemeral port; * in multicast it joins the group for DATA and opens a TCP control connection * to @c server_port for CONNECT and CONFIG. */ typedef struct { const char *server_addr; /**< IPv4 dotted quad. Required. */ uint16_t server_port; /**< UDP port (unicast) or TCP port (multicast). Required. */ const char *multicast_group;/**< IPv4 group; NULL selects unicast. */ const char *interface_addr; /**< Local IPv4 of the interface to join on. NULL = default route. */ uint16_t data_port; /**< Multicast data port; 0 means server_port + 1. */ double silence_timeout_s; /**< Reconnect after this long without data. 0 disables. */ double reconnect_delay_s; /**< Wait between reconnect attempts. */ double keepalive_interval_s;/**< Unicast ACK period. 0 disables. */ uint32_t recv_buffer_bytes; /**< SO_RCVBUF; large bursts need a large value. */ uint32_t max_packet_bytes; /**< Ceiling on one reassembled payload. */ } udps_client_config_t; /** Fills @p cfg with the defaults documented in Docs/UDPS-C-Client.md. */ void udps_client_config_init(udps_client_config_t *cfg); /** * @brief Creates a client. No socket is opened until the first poll. * @return NULL if @p cfg is invalid or memory ran out. */ udps_client_t *udps_client_create(const udps_client_config_t *cfg); /** Closes the session (sending DISCONNECT if connected) and frees the client. */ void udps_client_destroy(udps_client_t *client); /** Installs the callbacks. Any of them may be NULL. */ void udps_client_set_callbacks(udps_client_t *client, udps_config_cb on_config, udps_data_cb on_data, udps_event_cb on_event, void *user); /** * @brief Drives the client: connects if needed, then waits for and processes * packets for at most @p timeout_ms milliseconds. * * Callbacks fire from inside this call. A negative @p timeout_ms blocks until * something happens. Call it in a loop; it is the only function that does work. * * @return the number of packets processed (0 on timeout), or -1 if the session * broke. -1 is not fatal: the next call retries after reconnect_delay_s. */ int udps_client_poll(udps_client_t *client, int timeout_ms); /** Non-zero once the sockets are up (which does not yet imply CONFIG arrived). */ int udps_client_is_connected(const udps_client_t *client); /** * @brief The current signal set, or NULL before the first CONFIG. * @param num_signals Out; may be NULL. */ const udps_signal_t *udps_client_signals(const udps_client_t *client, uint32_t *num_signals); /** The publishing mode from the last CONFIG (UDPS_PUBLISH_*). */ uint8_t udps_client_publish_mode(const udps_client_t *client); /** Copies the counters into @p out. */ void udps_client_stats(const udps_client_t *client, udps_stats_t *out); /** Human-readable description of the last failure. Never NULL. */ const char *udps_client_last_error(const udps_client_t *client); /*---------------------------------------------------------------------------*/ /* Stateless helpers */ /*---------------------------------------------------------------------------*/ /** Elements in one sample of @p signal (rows x cols, at least 1). */ uint32_t udps_signal_num_elements(const udps_signal_t *signal); /** Short name of a type code, e.g. "float32". Never NULL. */ const char *udps_type_name(uint8_t type_code); /** * @brief Decodes a packet header. * @return 0 on success, -1 if @p len is too small or the magic is wrong. */ int udps_parse_header(const void *buf, size_t len, udps_header_t *out); /** * @brief Decodes a reassembled CONFIG payload. * @param signals Out array of at most @p max_signals entries. * @param num_signals Out; the number actually written. * @param publish_mode Out; may be NULL. * @return 0 on success, -1 if the payload is malformed or does not fit. */ int udps_parse_config(const void *payload, size_t len, udps_signal_t *signals, uint32_t max_signals, uint32_t *num_signals, uint8_t *publish_mode); /** * @brief One value out of a frame. * @param sample Accumulate batch slot; ignored for non-scalar signals. * @param elem Element within the sample; ignored for accumulated scalars. * @return the value, or 0.0 if any index is out of range. */ double udps_frame_value(const udps_frame_t *frame, uint32_t signal_idx, uint32_t sample, uint32_t elem); /** * @brief Arrival-anchored estimate of the wall-clock time of one element. * * Exact only for streams that declare a sampling rate: the packet is assumed to * have arrived as its last element was produced, and earlier elements are dated * backwards by 1/sampling_rate. Signals with UDPS_TIME_PACKET, or without a * sampling rate, all report the arrival time. When the stream carries a time * signal (time_signal_idx != UDPS_NO_TIME_SIGNAL) that signal is the accurate * source — read it like any other signal instead of using this helper. */ double udps_frame_element_time(const udps_frame_t *frame, uint32_t signal_idx, uint32_t elem); #ifdef __cplusplus } /* extern "C" */ #endif #endif /* UDPS_CLIENT_H */