ProtoCore v0.0.1
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
udp.h
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file udp.h
6 * @brief Layer 4 (Transport) - connectionless UDP datagram service.
7 *
8 * The transport layer's UDP counterpart to the TCP connection pool (see
9 * tcp.h / listener.h). It owns *all* lwIP UDP plumbing (`udp_pcb`,
10 * `pbuf`); higher layers (application services such as SNMP and the captive
11 * portal's DNS responder) bind a port and exchange datagrams through this API
12 * without ever touching lwIP. This keeps the OSI layering honest: transport
13 * concerns stay in the transport layer.
14 *
15 * Datagram delivery is callback-driven from the lwIP thread (no per-loop
16 * polling). The handler receives a contiguous payload and an opaque peer token
17 * valid only for the duration of the call; reply synchronously with
18 * pc_udp_send(). On non-Arduino (host) builds the functions are no-op stubs so
19 * the services that use them stay host-compilable.
20 *
21 * @author Douglas Quigg (dstroy0)
22 * @date 2026
23 */
24
25#ifndef PROTOCORE_UDP_H
26#define PROTOCORE_UDP_H
27
28#include "protocore_config.h"
29#include <stddef.h>
30#include <stdint.h>
31
32/**
33 * @brief Opaque sender of a received datagram.
34 *
35 * Wraps the lwIP source address / port / PCB. Valid only inside the
36 * pc_udp_handler call; pass it to pc_udp_send() to reply. The concrete layout
37 * lives in udp.cpp so no lwIP type escapes the transport layer.
38 */
39struct pc_udp_peer;
40
41/**
42 * @brief Datagram handler invoked for each received UDP packet.
43 *
44 * @param data contiguous datagram payload (transport-owned scratch).
45 * @param len payload length in bytes.
46 * @param peer reply token (valid only during this call).
47 * @param ctx the opaque context passed to pc_udp_listen().
48 */
49typedef void (*pc_udp_handler)(const uint8_t *data, size_t len, const struct pc_udp_peer *peer, void *ctx);
50
51/**
52 * @brief Bind a UDP port and route incoming datagrams to @p handler.
53 *
54 * @param port UDP port to bind (e.g. 161 for SNMP, 53 for captive DNS).
55 * @param handler callback for each datagram.
56 * @param ctx opaque pointer forwarded to @p handler.
57 * @return true on success; false if no listener slot is free, the bind fails,
58 * or UDP is unavailable (host builds). ESP32 only; a host build returns false.
59 */
60bool pc_udp_listen(uint16_t port, pc_udp_handler handler, void *ctx);
61
62/**
63 * @brief Bind a UDP port, join an IPv4 multicast group, and route group datagrams to @p handler.
64 *
65 * The multicast counterpart to pc_udp_listen(): as well as binding the port it joins @p group_ip
66 * via IGMP on every interface, so the stack accepts datagrams addressed to the group rather than to
67 * this host. That is what lets a service *observe* a shared protocol - counting mDNS announcements
68 * on 224.0.0.251:5353, watching SSDP on 239.255.255.250:1900 - instead of only its own traffic.
69 *
70 * The socket is bound with SO_REUSEADDR, because a well-known multicast port is normally already
71 * bound by whoever implements that protocol (the ESP-IDF `mdns` component holds 5353). Without it
72 * the second bind fails and the observer never sees a packet.
73 *
74 * Observation only: joining a group does not make this device a responder for it. Reply with
75 * pc_udp_send() only if the protocol expects it.
76 *
77 * @param group_ip IPv4 multicast group, dotted-quad (224.0.0.0/4).
78 * @param port UDP port to bind (e.g. 5353 for mDNS).
79 * @param handler callback for each datagram received on the group.
80 * @param ctx opaque pointer forwarded to @p handler.
81 * @return true if the port bound and the group was joined; false if the pool is full, @p group_ip
82 * is not a multicast address, IGMP is unavailable, or on a host build.
83 */
84bool pc_udp_listen_multicast(const char *group_ip, uint16_t port, pc_udp_handler handler, void *ctx);
85
86/**
87 * @brief Leave the multicast group joined on @p port and release its listener slot.
88 * @return true if a matching multicast listener was found and torn down.
89 */
90bool pc_udp_leave_multicast(uint16_t port);
91
92/**
93 * @brief Send a datagram back to the peer captured during the handler call.
94 *
95 * @param peer the token handed to the pc_udp_handler.
96 * @param data payload bytes.
97 * @param len payload length.
98 * @return true if the datagram was queued for transmission.
99 */
100bool pc_udp_send(const struct pc_udp_peer *peer, const uint8_t *data, size_t len);
101
102/**
103 * @brief Send a UDP datagram to an arbitrary destination (outbound client).
104 *
105 * Unlike pc_udp_send() - which replies to the peer of a received datagram -
106 * this sends to a host given as a dotted-quad IPv4 string and port, using a
107 * single shared outbound PCB. Fire-and-forget; for clients such as the syslog
108 * sender. ESP32 only; a host build returns false.
109 *
110 * @param dst_ip destination IPv4 address (e.g. "192.168.1.10").
111 * @param dst_port destination UDP port.
112 * @param data payload bytes.
113 * @param len payload length.
114 * @return true if the datagram was queued; false on a bad address, allocation
115 * failure, or a host build.
116 */
117bool pc_udp_sendto(const char *dst_ip, uint16_t dst_port, const uint8_t *data, size_t len);
118
119/**
120 * @brief Copy a received peer's source IPv4 address (dotted-quad) and port out.
121 *
122 * The pc_udp_peer token is valid only inside the handler; a service that wants to
123 * message the peer later (e.g. CoAP Observe notifications) captures its address
124 * here and sends with pc_udp_listener_sendto(). @return false on a host build or
125 * a too-small buffer.
126 */
127bool pc_udp_peer_addr(const struct pc_udp_peer *peer, char *ip_out, size_t ip_cap, uint16_t *port_out);
128
129/**
130 * @brief Send a datagram from the listener bound on @p listen_port to an address.
131 *
132 * Unlike pc_udp_sendto() (a shared ephemeral source port), this uses the bound
133 * listener's PCB, so the datagram's source is @p listen_port - required when a
134 * peer matches replies by the server endpoint (CoAP Observe notifications come
135 * from :5683). @return false if no listener is bound on @p listen_port.
136 */
137bool pc_udp_listener_sendto(uint16_t listen_port, const char *dst_ip, uint16_t dst_port, const uint8_t *data,
138 size_t len);
139
140#if !defined(ARDUINO)
141// Host-only test hooks: capture the last datagram passed to pc_udp_sendto() so a
142// unit test can inspect what an outbound UDP service (SNMP trap/inform, syslog,
143// telemetry) actually built. Off by default; enable per test. Mirrors the
144// tcp_capture() seam in the lwIP mock.
145void pc_udp_capture_enable();
146void pc_udp_capture_reset();
147const uint8_t *pc_udp_captured(); ///< bytes of the last captured datagram (nullptr if none)
148size_t pc_udp_captured_len(); ///< length of the last captured datagram
149
150// Deliver a datagram from (src_ip, src_port) to the handler bound to listen_port, driving the
151// receive path of a UDP service (CoAP Observe, DNS, SNMP) as if a peer had sent it.
152void pc_udp_inject(uint16_t listen_port, const char *src_ip, uint16_t src_port, const uint8_t *data, size_t len);
153void pc_udp_set_listener_sendto_result(bool ok); ///< force pc_udp_listener_sendto() to fail (unreachable peer)
154void pc_udp_reset_listeners(); ///< clear all bound listeners (per-test isolation)
155
156/** @brief The multicast group joined on @p port, or nullptr if that port has no multicast listener. */
157const char *pc_udp_joined_group(uint16_t port);
158#endif
159
160#endif // PROTOCORE_UDP_H
User-facing configuration for ProtoCore.
u16_t port
Definition udp.cpp:54
bool pc_udp_sendto(const char *dst_ip, uint16_t dst_port, const uint8_t *data, size_t len)
Send a UDP datagram to an arbitrary destination (outbound client).
Definition udp.cpp:358
void(* pc_udp_handler)(const uint8_t *data, size_t len, const struct pc_udp_peer *peer, void *ctx)
Datagram handler invoked for each received UDP packet.
Definition udp.h:49
bool pc_udp_listener_sendto(uint16_t listen_port, const char *dst_ip, uint16_t dst_port, const uint8_t *data, size_t len)
Send a datagram from the listener bound on listen_port to an address.
Definition udp.cpp:407
bool pc_udp_listen(uint16_t port, pc_udp_handler handler, void *ctx)
Bind a UDP port and route incoming datagrams to handler.
Definition udp.cpp:226
bool pc_udp_send(const struct pc_udp_peer *peer, const uint8_t *data, size_t len)
Send a datagram back to the peer captured during the handler call.
Definition udp.cpp:336
bool pc_udp_listen_multicast(const char *group_ip, uint16_t port, pc_udp_handler handler, void *ctx)
Bind a UDP port, join an IPv4 multicast group, and route group datagrams to handler.
Definition udp.cpp:255
bool pc_udp_leave_multicast(uint16_t port)
Leave the multicast group joined on port and release its listener slot.
Definition udp.cpp:309
bool pc_udp_peer_addr(const struct pc_udp_peer *peer, char *ip_out, size_t ip_cap, uint16_t *port_out)
Copy a received peer's source IPv4 address (dotted-quad) and port out.
Definition udp.cpp:393