ProtoCore v0.0.1
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
udp.cpp
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.cpp
6 * @brief Layer 4 UDP datagram service - the only place lwIP UDP is touched.
7 */
8
10
11#include <string.h> // memcpy (both the lwIP and host builds)
12
13#if defined(ARDUINO)
14
15#include "diffserv.h" // DiffServ DSCP marking for outbound datagrams (compiles out when off)
16#include "lwip/igmp.h" // igmp_joingroup / _leavegroup - multicast group membership
17#include "lwip/pbuf.h"
18#include "lwip/priv/tcpip_priv.h" // tcpip_api_call - marshal raw udp_* onto tcpip_thread
19#include "lwip/udp.h"
20
21// A small fixed pool of bound UDP ports (e.g. SNMP :161 + captive DNS :53). No
22// heap: the pool and the shared receive scratch live in BSS.
24{
25 struct udp_pcb *pcb;
27 void *ctx;
28 ip_addr_t group; // multicast group joined by this listener (mcast only)
29 bool mcast; // true if this slot joined a group and must leave on teardown
30 bool used;
31};
32
33// All UDP transport state, owned by one instance (internal linkage): the listener table, the
34// shared single-datagram rx buffer, the shared outbound PCB, and the tcpip-thread reentrancy
35// flag. One named owner, unreachable from any other translation unit.
36//
37// in_tcpip_thread is true while a udp_recv trampoline (or a marshaled op) is running, i.e. while
38// we are already inside tcpip_thread. A handler replying from the trampoline then sends directly
39// instead of re-marshaling (which would deadlock on the tcpip mailbox) - the UDP mirror of
40// tcp.cpp's TransportCtx::in_tcpip_thread.
41struct UdpCtx
42{
44 uint8_t rx[PC_UDP_RX_BUF_SIZE]; // shared: lwIP delivers one datagram at a time
45 struct udp_pcb *out = nullptr; // one shared outbound PCB for pc_udp_sendto()
46 volatile bool in_tcpip_thread = false;
47};
48static UdpCtx s_udp;
49
50// Concrete peer: lwIP source address/port plus the receiving PCB to reply on.
52{
53 const ip_addr_t *addr;
54 u16_t port;
55 struct udp_pcb *pcb;
56};
57
58// Raw send (alloc + copy + sendto + free). Only ever called in tcpip_thread.
59static bool udp_pbuf_send(struct udp_pcb *pcb, const ip_addr_t *addr, u16_t port, const uint8_t *data, size_t len)
60{
61 struct pbuf *p = pbuf_alloc(PBUF_TRANSPORT, (u16_t)len, PBUF_RAM);
62 if (!p)
63 {
64 return false;
65 }
66 memcpy(p->payload, data, len);
67 err_t e = udp_sendto(pcb, p, addr, port);
68 pbuf_free(p);
69 return e == ERR_OK;
70}
71
72// lwIP udp_recv trampoline: copy the (possibly chained) pbuf into a contiguous
73// scratch buffer and hand it to the registered handler. Runs in tcpip_thread, so a
74// reply the handler sends is already in-thread (flagged for the send helpers).
75static void udp_trampoline(void *arg, struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *addr, u16_t port)
76{
77 UdpListener *l = (UdpListener *)arg;
78 if (!p)
79 {
80 return;
81 }
82 u16_t n = (p->tot_len < sizeof(s_udp.rx)) ? p->tot_len : (u16_t)sizeof(s_udp.rx);
83 pbuf_copy_partial(p, s_udp.rx, n, 0);
84 pbuf_free(p);
85 if (l && l->handler)
86 {
87 pc_udp_peer peer = {addr, port, pcb};
88 bool prev = s_udp.in_tcpip_thread;
89 s_udp.in_tcpip_thread = true;
90 l->handler(s_udp.rx, n, &peer, l->ctx);
91 s_udp.in_tcpip_thread = prev;
92 }
93}
94
95// Raw lwIP UDP must run in tcpip_thread: with lwIP core-locking (arduino-esp32 3.x /
96// IDF 5.x) a udp_new/bind/recv/sendto from any other task asserts ("Required to lock
97// TCPIP core functionality"), and without it, it races the stack. pc_udp_* therefore
98// marshal these ops via tcpip_api_call(), the same as the TCP transport.
99enum class pc_udp_op : uint8_t
100{
101 UDP_OP_LISTEN, // udp_new + bind + arm recv on s_udp.listeners[slot]
102 UDP_OP_LISTEN_MCAST, // as LISTEN, plus SO_REUSEADDR + igmp_joingroup
103 UDP_OP_LEAVE_MCAST, // igmp_leavegroup + udp_remove
104 UDP_OP_SEND, // send to addr:port on an existing pcb
105 UDP_OP_SEND_OUT // send to addr:port on the shared lazy outbound pcb
106};
107
109{
110 struct tcpip_api_call_data base;
112 int slot; // LISTEN: index into s_udp.listeners
113 struct udp_pcb *pcb; // SEND: target pcb
114 ip_addr_t addr; // SEND / SEND_OUT: destination (by value - caller's may be transient)
115 u16_t port; // LISTEN: bind port; SEND / SEND_OUT: destination port
116 const uint8_t *data; // SEND / SEND_OUT
117 size_t len; // SEND / SEND_OUT
118 bool result;
119};
120
121// Stamp a UDP pcb with the configured default DSCP (DiffServ) so its outbound datagrams carry the class.
122// No-op when marking is off or the DSCP is 0 (best-effort). Applied per outbound send so a live
123// pc_udp_set_dscp() change takes effect on the next datagram - useful for network testing.
124static inline void apply_udp_dscp(struct udp_pcb *pcb)
125{
126#if PC_ENABLE_DIFFSERV
127 uint8_t dscp = pc_diffserv_udp_dscp();
128 if (pcb && dscp)
129 {
130 pcb->tos = pc_dscp_to_tos(dscp);
131 }
132#else
133 (void)pcb;
134#endif
135}
136
137// Runs in tcpip_thread via tcpip_api_call.
138static err_t udp_do(struct tcpip_api_call_data *c)
139{
140 pc_udp_call *k = (pc_udp_call *)c;
141 bool prev = s_udp.in_tcpip_thread;
142 s_udp.in_tcpip_thread = true;
143 k->result = false;
144 switch (k->op)
145 {
147 struct udp_pcb *pcb = udp_new();
148 if (pcb)
149 {
150 if (udp_bind(pcb, IP_ANY_TYPE, k->port) == ERR_OK)
151 {
152 s_udp.listeners[k->slot].pcb = pcb;
153 apply_udp_dscp(pcb); // reply datagrams from this listener carry the configured DSCP
154 udp_recv(pcb, udp_trampoline, &s_udp.listeners[k->slot]);
155 k->result = true;
156 }
157 else
158 {
159 udp_remove(pcb);
160 }
161 }
162 break;
163 }
165#if LWIP_IGMP
166 struct udp_pcb *pcb = udp_new();
167 if (pcb)
168 {
169 // A well-known multicast port is usually already bound by whoever implements that
170 // protocol (the ESP-IDF mdns component holds 5353), so co-bind rather than fail.
171 // Bind IPv4-only, NOT IP_ANY_TYPE: a dual-stack ANY pcb also matches the IPv4
172 // datagrams the other responder's own socket is waiting on, and lwIP hands each
173 // datagram to the first matching pcb - which silently stops that responder answering
174 // queries even though its announcements still go out.
175 ip_set_option(pcb, SOF_REUSEADDR);
176 if (udp_bind(pcb, IP4_ADDR_ANY, k->port) == ERR_OK &&
177 igmp_joingroup(IP4_ADDR_ANY4, ip_2_ip4(&k->addr)) == ERR_OK)
178 {
179 s_udp.listeners[k->slot].pcb = pcb;
180 s_udp.listeners[k->slot].group = k->addr;
181 s_udp.listeners[k->slot].mcast = true;
182 udp_recv(pcb, udp_trampoline, &s_udp.listeners[k->slot]);
183 k->result = true;
184 }
185 else
186 {
187 udp_remove(pcb);
188 }
189 }
190#endif
191 break;
192 }
194#if LWIP_IGMP
195 UdpListener *l = &s_udp.listeners[k->slot];
196 igmp_leavegroup(IP4_ADDR_ANY4, ip_2_ip4(&l->group));
197 if (l->pcb)
198 {
199 udp_remove(l->pcb);
200 }
201 l->pcb = nullptr;
202 l->mcast = false;
203 k->result = true;
204#endif
205 break;
206 }
208 k->result = udp_pbuf_send(k->pcb, &k->addr, k->port, k->data, k->len);
209 break;
211 if (!s_udp.out)
212 {
213 s_udp.out = udp_new();
214 }
215 if (s_udp.out)
216 {
217 apply_udp_dscp(s_udp.out); // per-send: a live pc_udp_set_dscp() change takes effect immediately
218 k->result = udp_pbuf_send(s_udp.out, &k->addr, k->port, k->data, k->len);
219 }
220 break;
221 }
222 s_udp.in_tcpip_thread = prev;
223 return ERR_OK;
224}
225
226bool pc_udp_listen(uint16_t port, pc_udp_handler handler, void *ctx)
227{
228 for (int i = 0; i < PC_MAX_UDP_LISTENERS; i++)
229 {
230 if (s_udp.listeners[i].used)
231 {
232 continue;
233 }
234 // The trampoline reads handler/ctx once recv is armed, so set them first.
235 s_udp.listeners[i].handler = handler;
236 s_udp.listeners[i].ctx = ctx;
237 s_udp.listeners[i].pcb = nullptr;
238 pc_udp_call k;
239 memset(&k, 0, sizeof(k));
241 k.slot = i;
242 k.port = port;
243 tcpip_api_call(udp_do, &k.base); // always called off tcpip_thread (service begin())
244 if (!k.result)
245 {
246 s_udp.listeners[i].handler = nullptr;
247 return false;
248 }
249 s_udp.listeners[i].used = true;
250 return true;
251 }
252 return false; // pool exhausted
253}
254
255bool pc_udp_listen_multicast(const char *group_ip, uint16_t port, pc_udp_handler handler, void *ctx)
256{
257#if LWIP_IGMP
258 if (!group_ip)
259 {
260 return false;
261 }
262 ip_addr_t group;
263 if (!ipaddr_aton(group_ip, &group) || !IP_IS_V4(&group))
264 {
265 return false;
266 }
267 // 224.0.0.0/4 - joining a unicast address would silently never deliver.
268 if (!ip4_addr_ismulticast(ip_2_ip4(&group)))
269 {
270 return false;
271 }
272
273 for (int i = 0; i < PC_MAX_UDP_LISTENERS; i++)
274 {
275 if (s_udp.listeners[i].used)
276 {
277 continue;
278 }
279 // The trampoline reads handler/ctx once recv is armed, so set them first.
280 s_udp.listeners[i].handler = handler;
281 s_udp.listeners[i].ctx = ctx;
282 s_udp.listeners[i].pcb = nullptr;
283 s_udp.listeners[i].mcast = false;
284 pc_udp_call k;
285 memset(&k, 0, sizeof(k));
287 k.slot = i;
288 k.port = port;
289 k.addr = group;
290 tcpip_api_call(udp_do, &k.base); // always called off tcpip_thread (service begin())
291 if (!k.result)
292 {
293 s_udp.listeners[i].handler = nullptr;
294 return false;
295 }
296 s_udp.listeners[i].used = true;
297 return true;
298 }
299 return false; // pool exhausted
300#else
301 (void)group_ip;
302 (void)port;
303 (void)handler;
304 (void)ctx;
305 return false; // lwIP built without IGMP
306#endif
307}
308
309bool pc_udp_leave_multicast(uint16_t port)
310{
311#if LWIP_IGMP
312 for (int i = 0; i < PC_MAX_UDP_LISTENERS; i++)
313 {
314 if (!s_udp.listeners[i].used || !s_udp.listeners[i].mcast || !s_udp.listeners[i].pcb ||
315 s_udp.listeners[i].pcb->local_port != port)
316 {
317 continue;
318 }
319 pc_udp_call k;
320 memset(&k, 0, sizeof(k));
322 k.slot = i;
323 tcpip_api_call(udp_do, &k.base);
324 s_udp.listeners[i].used = false;
325 s_udp.listeners[i].handler = nullptr;
326 s_udp.listeners[i].ctx = nullptr;
327 return k.result;
328 }
329 return false;
330#else
331 (void)port;
332 return false;
333#endif
334}
335
336bool pc_udp_send(const struct pc_udp_peer *peer, const uint8_t *data, size_t len)
337{
338 if (!peer || !peer->pcb || !peer->addr || !data || len == 0)
339 {
340 return false;
341 }
342 if (s_udp.in_tcpip_thread) // replying from a handler (already in tcpip_thread)
343 {
344 return udp_pbuf_send(peer->pcb, peer->addr, peer->port, data, len);
345 }
346 pc_udp_call k;
347 memset(&k, 0, sizeof(k));
349 k.pcb = peer->pcb;
350 k.addr = *peer->addr;
351 k.port = peer->port;
352 k.data = data;
353 k.len = len;
354 tcpip_api_call(udp_do, &k.base);
355 return k.result;
356}
357
358bool pc_udp_sendto(const char *dst_ip, uint16_t dst_port, const uint8_t *data, size_t len)
359{
360 if (!dst_ip || !data || len == 0)
361 {
362 return false;
363 }
364 ip_addr_t dst;
365 if (!ipaddr_aton(dst_ip, &dst))
366 {
367 return false;
368 }
369 if (s_udp.in_tcpip_thread)
370 {
371 if (!s_udp.out)
372 {
373 s_udp.out = udp_new();
374 if (!s_udp.out)
375 {
376 return false;
377 }
378 }
379 apply_udp_dscp(s_udp.out);
380 return udp_pbuf_send(s_udp.out, &dst, dst_port, data, len);
381 }
382 pc_udp_call k;
383 memset(&k, 0, sizeof(k));
385 k.addr = dst;
386 k.port = dst_port;
387 k.data = data;
388 k.len = len;
389 tcpip_api_call(udp_do, &k.base);
390 return k.result;
391}
392
393bool pc_udp_peer_addr(const struct pc_udp_peer *peer, char *ip_out, size_t ip_cap, uint16_t *port_out)
394{
395 if (!peer || !peer->addr || !ip_out || ip_cap < 8)
396 {
397 return false;
398 }
399 ipaddr_ntoa_r(peer->addr, ip_out, (int)ip_cap);
400 if (port_out)
401 {
402 *port_out = peer->port;
403 }
404 return true;
405}
406
407bool pc_udp_listener_sendto(uint16_t listen_port, const char *dst_ip, uint16_t dst_port, const uint8_t *data,
408 size_t len)
409{
410 if (!dst_ip || !data || len == 0)
411 {
412 return false;
413 }
414 ip_addr_t dst;
415 if (!ipaddr_aton(dst_ip, &dst))
416 {
417 return false;
418 }
419 struct udp_pcb *pcb = nullptr;
420 for (int i = 0; i < PC_MAX_UDP_LISTENERS; i++)
421 {
422 if (s_udp.listeners[i].used && s_udp.listeners[i].pcb && s_udp.listeners[i].pcb->local_port == listen_port)
423 {
424 pcb = s_udp.listeners[i].pcb;
425 break;
426 }
427 }
428 if (!pcb)
429 {
430 return false;
431 }
432 if (s_udp.in_tcpip_thread)
433 {
434 return udp_pbuf_send(pcb, &dst, dst_port, data, len);
435 }
436 pc_udp_call k;
437 memset(&k, 0, sizeof(k));
439 k.pcb = pcb;
440 k.addr = dst;
441 k.port = dst_port;
442 k.data = data;
443 k.len = len;
444 tcpip_api_call(udp_do, &k.base);
445 return k.result;
446}
447
448#else // host build: no lwIP. A test-injectable UDP mock keeps UDP-using services host-testable.
449
450// Concrete host peer: the source address/port of an injected datagram, which a service's handler
451// reads back via pc_udp_peer_addr() to reply (or, for CoAP Observe, to key a registration).
452struct pc_udp_peer
453{
454 char ip[16];
455 uint16_t port;
456};
457
458// Host UDP mock state, owned by one instance (internal linkage): the bound listeners, a capture of
459// the last datagram sent (shared by pc_udp_send/sendto/listener_sendto), and the listener_sendto
460// result knob. A test drives the receive path with pc_udp_inject() and reads replies via
461// pc_udp_captured(). One named owner, unreachable from any other translation unit.
462namespace
463{
464struct HostUdpListener
465{
466 uint16_t port;
467 pc_udp_handler handler;
468 void *ctx;
469 char group[16]; // multicast group joined on this port ("" for a plain listener)
470 bool mcast;
471 bool used;
472};
473struct HostUdpCtx
474{
475 HostUdpListener lst[PC_MAX_UDP_LISTENERS];
476 bool cap_on;
477 uint8_t cap_buf[2048];
478 size_t cap_len;
479 bool listener_sendto_ok;
480};
481HostUdpCtx s_udp = {{}, false, {}, 0, true};
482
483// Capture one outbound datagram if capture is enabled; return whether it was captured.
484bool host_capture(const uint8_t *data, size_t len)
485{
486 if (s_udp.cap_on && data && len && len <= sizeof(s_udp.cap_buf))
487 {
488 memcpy(s_udp.cap_buf, data, len);
489 s_udp.cap_len = len;
490 return true;
491 }
492 return false;
493}
494} // namespace
495
496bool pc_udp_listen(uint16_t port, pc_udp_handler handler, void *ctx)
497{
498 // Rebind an existing port, else take a free slot, else evict slot 0 (host tests only).
499 HostUdpListener *slot = nullptr;
500 for (int i = 0; i < PC_MAX_UDP_LISTENERS; i++)
501 {
502 if (s_udp.lst[i].used && s_udp.lst[i].port == port)
503 {
504 slot = &s_udp.lst[i];
505 }
506 }
507 for (int i = 0; i < PC_MAX_UDP_LISTENERS && !slot; i++)
508 {
509 if (!s_udp.lst[i].used)
510 {
511 slot = &s_udp.lst[i];
512 }
513 }
514 if (!slot)
515 {
516 slot = &s_udp.lst[0];
517 }
518 slot->port = port;
519 slot->handler = handler;
520 slot->ctx = ctx;
521 slot->used = true;
522 return true;
523}
524
525// Validate a dotted-quad IPv4 multicast group (224.0.0.0/4) and report its length.
526//
527// Mirrors the device-side validation so a host test catches a bad group before hardware does.
528// Returning the length is what lets the caller copy it with a bound it just proved, instead of
529// trusting a scan that happened several branches earlier.
530static bool parse_mcast_group(const char *ip, size_t *len_out)
531{
532 if (!ip)
533 {
534 return false;
535 }
536 uint32_t octet = 0;
537 uint32_t first = 0;
538 int count = 0;
539 bool have_digit = false;
540 const char *p = ip;
541 for (;; p++)
542 {
543 if (*p >= '0' && *p <= '9')
544 {
545 octet = octet * 10 + (uint32_t)(*p - '0');
546 have_digit = true;
547 if (octet > 255)
548 {
549 return false;
550 }
551 }
552 else if (*p == '.' || *p == '\0')
553 {
554 if (!have_digit || count == 4)
555 {
556 return false;
557 }
558 if (count == 0)
559 {
560 first = octet;
561 }
562 count++;
563 octet = 0;
564 have_digit = false;
565 if (*p == '\0')
566 {
567 break;
568 }
569 }
570 else
571 {
572 return false;
573 }
574 }
575 if (count != 4 || first < 224 || first > 239) // 224.0.0.0/4
576 {
577 return false;
578 }
579 *len_out = (size_t)(p - ip);
580 return true;
581}
582
583bool pc_udp_listen_multicast(const char *group_ip, uint16_t port, pc_udp_handler handler, void *ctx)
584{
585 size_t glen = 0;
586 if (!parse_mcast_group(group_ip, &glen))
587 {
588 return false;
589 }
590 // A normal dotted quad is at most 15 chars, but parse_mcast_group() only bounds each octet's
591 // VALUE (<=255), not its digit count - a run of leading zeros ("224.000000000000000.0.0") keeps
592 // an octet at 0 indefinitely while still growing glen past this buffer (see
593 // test_multicast_group_too_long_for_buffer_rejected), so this check is genuinely reachable, not
594 // just defensive. Failing here before claiming a listener slot means a truncated group leaks
595 // nothing.
596 if (glen >= sizeof(s_udp.lst[0].group))
597 {
598 return false;
599 }
600
601 // pc_udp_listen()'s host stub (this file, #else branch above) always returns true - rebind,
602 // free-slot, or evict-slot-0 all succeed unconditionally, with no failure path of its own - so
603 // this guard, and the "not found" fallthrough below, only exist for the real ESP32
604 // implementation (which can fail: no free pcb, a bind conflict). Neither is reachable on native.
605 if (!pc_udp_listen(port, handler, ctx)) // GCOVR_EXCL_BR_LINE - pc_udp_listen() host stub never fails
606 {
607 return false; // GCOVR_EXCL_LINE - unreachable: see above
608 }
609 for (int i = 0; i < PC_MAX_UDP_LISTENERS; i++) // GCOVR_EXCL_BR_LINE - loop always finds the slot pc_udp_listen()
610 // just claimed (lowest-free-first, so every lower index is
611 // already used); natural exhaustion is unreachable
612 {
613 if (s_udp.lst[i].used && s_udp.lst[i].port == port)
614 {
615 memcpy(s_udp.lst[i].group, group_ip, glen + 1); // + NUL; length bounded above
616 s_udp.lst[i].mcast = true;
617 return true;
618 }
619 }
620 return false; // GCOVR_EXCL_LINE - unreachable: the loop above always finds the just-bound slot
621}
622
623bool pc_udp_leave_multicast(uint16_t port)
624{
625 for (int i = 0; i < PC_MAX_UDP_LISTENERS; i++)
626 {
627 if (s_udp.lst[i].used && s_udp.lst[i].mcast && s_udp.lst[i].port == port)
628 {
629 s_udp.lst[i] = {};
630 return true;
631 }
632 }
633 return false;
634}
635
636const char *pc_udp_joined_group(uint16_t port)
637{
638 for (int i = 0; i < PC_MAX_UDP_LISTENERS; i++)
639 {
640 if (s_udp.lst[i].used && s_udp.lst[i].mcast && s_udp.lst[i].port == port)
641 {
642 return s_udp.lst[i].group;
643 }
644 }
645 return nullptr;
646}
647
648void pc_udp_inject(uint16_t listen_port, const char *src_ip, uint16_t src_port, const uint8_t *data, size_t len)
649{
650 for (int i = 0; i < PC_MAX_UDP_LISTENERS; i++)
651 {
652 if (s_udp.lst[i].used && s_udp.lst[i].port == listen_port && s_udp.lst[i].handler)
653 {
654 pc_udp_peer peer;
655 strncpy(peer.ip, src_ip ? src_ip : "", sizeof(peer.ip) - 1);
656 peer.ip[sizeof(peer.ip) - 1] = '\0';
657 peer.port = src_port;
658 s_udp.lst[i].handler(data, len, &peer, s_udp.lst[i].ctx);
659 return;
660 }
661 }
662}
663
664void pc_udp_set_listener_sendto_result(bool ok)
665{
666 s_udp.listener_sendto_ok = ok;
667}
668
669void pc_udp_reset_listeners()
670{
671 for (int i = 0; i < PC_MAX_UDP_LISTENERS; i++)
672 {
673 s_udp.lst[i] = {};
674 }
675 s_udp.listener_sendto_ok = true;
676}
677
678bool pc_udp_send(const struct pc_udp_peer *peer, const uint8_t *data, size_t len)
679{
680 (void)peer;
681 return host_capture(data, len);
682}
683
684void pc_udp_capture_enable()
685{
686 s_udp.cap_on = true;
687 s_udp.cap_len = 0;
688}
689void pc_udp_capture_reset()
690{
691 s_udp.cap_len = 0;
692}
693const uint8_t *pc_udp_captured()
694{
695 return s_udp.cap_len ? s_udp.cap_buf : nullptr;
696}
697size_t pc_udp_captured_len()
698{
699 return s_udp.cap_len;
700}
701
702bool pc_udp_sendto(const char *dst_ip, uint16_t dst_port, const uint8_t *data, size_t len)
703{
704 (void)dst_ip;
705 (void)dst_port;
706 return host_capture(data, len); // captured "send" succeeds so the caller's success path runs
707}
708
709bool pc_udp_peer_addr(const struct pc_udp_peer *peer, char *ip_out, size_t ip_cap, uint16_t *port_out)
710{
711 if (!peer)
712 {
713 return false;
714 }
715 if (ip_out && ip_cap)
716 {
717 strncpy(ip_out, peer->ip, ip_cap - 1);
718 ip_out[ip_cap - 1] = '\0';
719 }
720 if (port_out)
721 {
722 *port_out = peer->port;
723 }
724 return true;
725}
726
727bool pc_udp_listener_sendto(uint16_t listen_port, const char *dst_ip, uint16_t dst_port, const uint8_t *data,
728 size_t len)
729{
730 (void)listen_port;
731 (void)dst_ip;
732 (void)dst_port;
733 if (!s_udp.listener_sendto_ok)
734 {
735 return false; // test knob: model an unreachable peer (drops the observer)
736 }
737 host_capture(data, len);
738 return true;
739}
740
741#endif // ARDUINO
Layer 4 (Transport) - DiffServ QoS marking (RFC 2474) for outbound traffic.
#define PC_UDP_RX_BUF_SIZE
Shared receive-scratch size for the transport-layer UDP service.
#define PC_MAX_UDP_LISTENERS
Maximum simultaneously bound UDP ports (transport-layer UDP service).
Definition udp.cpp:42
struct udp_pcb * out
Definition udp.cpp:45
UdpListener listeners[PC_MAX_UDP_LISTENERS]
Definition udp.cpp:43
uint8_t rx[PC_UDP_RX_BUF_SIZE]
Definition udp.cpp:44
volatile bool in_tcpip_thread
Definition udp.cpp:46
struct udp_pcb * pcb
Definition udp.cpp:25
pc_udp_handler handler
Definition udp.cpp:26
ip_addr_t group
Definition udp.cpp:28
bool mcast
Definition udp.cpp:29
bool used
Definition udp.cpp:30
void * ctx
Definition udp.cpp:27
size_t len
Definition udp.cpp:117
struct udp_pcb * pcb
Definition udp.cpp:113
bool result
Definition udp.cpp:118
const uint8_t * data
Definition udp.cpp:116
int slot
Definition udp.cpp:112
pc_udp_op op
Definition udp.cpp:111
u16_t port
Definition udp.cpp:115
struct tcpip_api_call_data base
Definition udp.cpp:110
ip_addr_t addr
Definition udp.cpp:114
struct udp_pcb * pcb
Definition udp.cpp:55
const ip_addr_t * addr
Definition udp.cpp:53
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
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
pc_udp_op
Definition udp.cpp:100
@ UDP_OP_LEAVE_MCAST
@ UDP_OP_LISTEN_MCAST
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
Layer 4 (Transport) - connectionless UDP datagram service.
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