diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..cd6e87c --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,21 @@ +set(headers + src/asyncudplistener.h +) + +set(sources + src/asyncudplistener.cpp +) + +idf_component_register(INCLUDE_DIRS + src + SRCS + ${headers} + ${sources} + REQUIRES + cpputils + espchrono + espcpputils + espwifistack +) + +target_compile_options(${COMPONENT_TARGET} PRIVATE -Wno-unused-function -Wno-deprecated-declarations -Wno-missing-field-initializers) diff --git a/Kconfig.projbuild b/Kconfig.projbuild new file mode 100644 index 0000000..f9fef3d --- /dev/null +++ b/Kconfig.projbuild @@ -0,0 +1,39 @@ +menu "Simple Async UDP Listener" + +choice LOG_LOCAL_LEVEL_ASYNC_UDP_LISTENER + bool "ASYNC_UDP_LISTENER log verbosity" + default LOG_LOCAL_LEVEL_ASYNC_UDP_LISTENER_INFO + help + Specify how much output to compile into the binary. + You can set lower verbosity level at runtime using + esp_log_level_set function. + + Note that this setting limits which log statements + are compiled into the program. So setting this to, + say, "Warning" would mean that changing log level + to "Debug" at runtime will not be possible. + + config LOG_LOCAL_LEVEL_ASYNC_UDP_LISTENER_NONE + bool "No output" + config LOG_LOCAL_LEVEL_ASYNC_UDP_LISTENER_ERROR + bool "Error" + config LOG_LOCAL_LEVEL_ASYNC_UDP_LISTENER_WARN + bool "Warning" + config LOG_LOCAL_LEVEL_ASYNC_UDP_LISTENER_INFO + bool "Info" + config LOG_LOCAL_LEVEL_ASYNC_UDP_LISTENER_DEBUG + bool "Debug" + config LOG_LOCAL_LEVEL_ASYNC_UDP_LISTENER_VERBOSE + bool "Verbose" +endchoice + +config LOG_LOCAL_LEVEL_ASYNC_UDP_LISTENER + int + default 0 if LOG_LOCAL_LEVEL_ASYNC_UDP_LISTENER_NONE + default 1 if LOG_LOCAL_LEVEL_ASYNC_UDP_LISTENER_ERROR + default 2 if LOG_LOCAL_LEVEL_ASYNC_UDP_LISTENER_WARN + default 3 if LOG_LOCAL_LEVEL_ASYNC_UDP_LISTENER_INFO + default 4 if LOG_LOCAL_LEVEL_ASYNC_UDP_LISTENER_DEBUG + default 5 if LOG_LOCAL_LEVEL_ASYNC_UDP_LISTENER_VERBOSE + +endmenu diff --git a/src/asyncudplistener.cpp b/src/asyncudplistener.cpp new file mode 100644 index 0000000..dc9c8b4 --- /dev/null +++ b/src/asyncudplistener.cpp @@ -0,0 +1,259 @@ +#include "asyncudplistener.h" + +#include "sdkconfig.h" +#define LOG_LOCAL_LEVEL CONFIG_LOG_LOCAL_LEVEL_ASYNC_UDP_LISTENER + +// system includes +#include + +// esp-idf includes +#include +#include +#include + +namespace { +constexpr const char * const TAG = "ASYNC_UDP_LISTENER"; + +typedef struct +{ + void *arg; + udp_pcb *pcb; + pbuf *pb; + const ip_addr_t *addr; + uint16_t port; + struct netif *netif; +} lwip_event_packet_t; + +typedef struct +{ + struct tcpip_api_call_data call; + udp_pcb * pcb; + const ip_addr_t *addr; + uint16_t port; + struct pbuf *pb; + struct netif *netif; + err_t err; +} udp_api_call_t; + +err_t _udp_bind_api(struct tcpip_api_call_data *api_call_msg) +{ + udp_api_call_t *msg = (udp_api_call_t *)api_call_msg; + msg->err = udp_bind(msg->pcb, msg->addr, msg->port); + return msg->err; +} + +err_t _udp_bind(struct udp_pcb *pcb, const ip_addr_t *addr, u16_t port) +{ + udp_api_call_t msg; + msg.pcb = pcb; + msg.addr = addr; + msg.port = port; + tcpip_api_call(_udp_bind_api, (struct tcpip_api_call_data*)&msg); + return msg.err; +} + +err_t _udp_disconnect_api(struct tcpip_api_call_data *api_call_msg) +{ + udp_api_call_t *msg = (udp_api_call_t *)api_call_msg; + msg->err = 0; + udp_disconnect(msg->pcb); + return msg->err; +} + +void _udp_disconnect(struct udp_pcb *pcb) +{ + udp_api_call_t msg; + msg.pcb = pcb; + tcpip_api_call(_udp_disconnect_api, (struct tcpip_api_call_data*)&msg); +} + +void _udp_recv(void *arg, udp_pcb *pcb, pbuf *pb, const ip_addr_t *addr, uint16_t port) +{ + while (pb != NULL) + { + pbuf *this_pb = pb; + pb = pb->next; + this_pb->next = NULL; + + if (!AsyncUdpListener::_udp_task_post(arg, pcb, this_pb, addr, port, ip_current_input_netif())) + pbuf_free(this_pb); + } +} +} // namespace + +UdpPacketWrapper::UdpPacketWrapper(pbuf *pb, const ip_addr_t *raddr, uint16_t rport, struct netif *ntif) +{ + _pb = pb; + _if = TCPIP_ADAPTER_IF_MAX; + _data = (uint8_t*)(pb->payload); + _len = pb->len; + _index = 0; + + //memcpy(&_remoteIp, raddr, sizeof(ip_addr_t)); + _remoteIp.type = raddr->type; + _localIp.type = _remoteIp.type; + + eth_hdr* eth = NULL; + const udp_hdr* udphdr = reinterpret_cast(_data - UDP_HLEN); + _localPort = ntohs(udphdr->dest); + _remotePort = ntohs(udphdr->src); + + if (_remoteIp.type == IPADDR_TYPE_V4) + { + eth = (eth_hdr *)(((uint8_t *)(pb->payload)) - UDP_HLEN - IP_HLEN - SIZEOF_ETH_HDR); + struct ip_hdr * iphdr = (struct ip_hdr *)(((uint8_t *)(pb->payload)) - UDP_HLEN - IP_HLEN); + _localIp.u_addr.ip4.addr = iphdr->dest.addr; + _remoteIp.u_addr.ip4.addr = iphdr->src.addr; + } + else + { + eth = (eth_hdr *)(((uint8_t *)(pb->payload)) - UDP_HLEN - IP6_HLEN - SIZEOF_ETH_HDR); + struct ip6_hdr * ip6hdr = (struct ip6_hdr *)(((uint8_t *)(pb->payload)) - UDP_HLEN - IP6_HLEN); + std::memcpy(&_localIp.u_addr.ip6.addr, (uint8_t *)ip6hdr->dest.addr, 16); + std::memcpy(&_remoteIp.u_addr.ip6.addr, (uint8_t *)ip6hdr->src.addr, 16); + } + _remoteMac = wifi_stack::mac_t{eth->src.addr}; + + void *nif{NULL}; + for (int i=0; ihandle) + { + _udp_queue.destruct(); + ESP_LOGE(TAG, "xQueueCreate failed"); + return false; + } + } + + if (!_init()) + { + ESP_LOGE(TAG, "failed to init"); + return false; + } + + close(); + + if (addr) + { + IP_SET_TYPE_VAL(_pcb->local_ip, addr->type); + IP_SET_TYPE_VAL(_pcb->remote_ip, addr->type); + } + + if (_udp_bind(_pcb, addr, port) != ERR_OK) + { + ESP_LOGE(TAG, "failed to bind"); + return false; + } + + _connected = true; + + return true; +} + +void AsyncUdpListener::poll(std::function &&callback, TickType_t xTicksToWait) +{ + if (!_udp_queue.constructed()) + return; + + lwip_event_packet_t *e{NULL}; + while (_udp_queue->receive(&e, xTicksToWait) == pdTRUE) + { + if (!e->pb) + { + free((void*)(e)); + continue; + } + + //udp_pcb *upcb = e->pcb; + pbuf *pb = e->pb; + const ip_addr_t *addr = e->addr; + uint16_t port = e->port; + struct netif *netif = e->netif; + + while (pb != NULL) + { + pbuf *this_pb = pb; + pb = pb->next; + this_pb->next = NULL; + + if (callback) + callback(UdpPacketWrapper{this_pb, addr, port, netif}); + + pbuf_free(this_pb); + } + + free((void*)(e)); + } +} + +bool AsyncUdpListener::_udp_task_post(void *arg, udp_pcb *pcb, pbuf *pb, const ip_addr_t *addr, uint16_t port, struct netif *netif) +{ + if (!arg) + return false; + + auto &queue = reinterpret_cast(arg)->_udp_queue; + if (!queue.constructed()) + return false; + + lwip_event_packet_t *e = (lwip_event_packet_t *)malloc(sizeof(lwip_event_packet_t)); + if (!e) + return false; + + e->arg = arg; + e->pcb = pcb; + e->pb = pb; + e->addr = addr; + e->port = port; + e->netif = netif; + + if (queue->send(&e, portMAX_DELAY) != pdPASS) + { + free((void*)(e)); + return false; + } + + return true; +} + +bool AsyncUdpListener::_init() +{ + if (_pcb) + return true; + + _pcb = udp_new(); + if (!_pcb) + { + ESP_LOGE(TAG, "udp_new() failed"); + return false; + } + + udp_recv(_pcb, &_udp_recv, (void *)this); + + return true; +} + +void AsyncUdpListener::close() +{ + if (_pcb) + { + if (_connected) + _udp_disconnect(_pcb); + + _connected = false; + } +} diff --git a/src/asyncudplistener.h b/src/asyncudplistener.h new file mode 100644 index 0000000..ab83495 --- /dev/null +++ b/src/asyncudplistener.h @@ -0,0 +1,131 @@ +#pragma once + +// system includes +#include +#include +#include +#include + +// esp-idf includes +#include +#include +#include +#include + +// local includes +#include "cppmacros.h" +#include "delayedconstruction.h" +#include "espwifiutils.h" +#include "wrappers/queue.h" + +class UdpPacketWrapper +{ + CPP_DISABLE_COPY_MOVE(UdpPacketWrapper) + +public: + UdpPacketWrapper(pbuf *pb, const ip_addr_t *addr, uint16_t port, struct netif * netif); + + const uint8_t *data() const { return _data; } + size_t length() const { return _len; } + bool isBroadcast() const + { + if (_localIp.type == IPADDR_TYPE_V6) + return false; + uint32_t ip = _localIp.u_addr.ip4.addr; + return ip == 0xFFFFFFFF || ip == 0 || (ip & 0xFF000000) == 0xFF000000; + } + bool isMulticast() const { return ip_addr_ismulticast(&(_localIp)); } + bool isIPv6() const { return _localIp.type == IPADDR_TYPE_V6; } + + tcpip_adapter_if_t interface() const { return _if; } + + std::optional localIP() const + { + if (_localIp.type != IPADDR_TYPE_V4) + return std::nullopt; + return _localIp.u_addr.ip4.addr; + } + + std::optional> localIPv6() const + { + if (_localIp.type != IPADDR_TYPE_V6) + return std::nullopt; + return *reinterpret_cast*>(_localIp.u_addr.ip6.addr); + } + + uint16_t localPort() const { return _localPort; } + + std::optional remoteIP() const + { + if (_remoteIp.type != IPADDR_TYPE_V4) + return std::nullopt; + return _remoteIp.u_addr.ip4.addr; + } + + std::optional> remoteIPv6() const + { + if (_remoteIp.type != IPADDR_TYPE_V6) + return std::nullopt; + return *reinterpret_cast*>(_remoteIp.u_addr.ip6.addr); + } + + uint16_t remotePort() const { return _remotePort; } + + wifi_stack::mac_t remoteMac() const { return _remoteMac; } + +private: + const pbuf *_pb; + tcpip_adapter_if_t _if; + ip_addr_t _localIp; + uint16_t _localPort; + ip_addr_t _remoteIp; + uint16_t _remotePort; + wifi_stack::mac_t _remoteMac; + const uint8_t *_data; + size_t _len; + size_t _index; +}; + +class AsyncUdpListener +{ + CPP_DISABLE_COPY_MOVE(AsyncUdpListener) + +public: + AsyncUdpListener() = default; + + bool listen(const ip_addr_t *addr, uint16_t port); + +// bool listen(const IPAddress addr, uint16_t port) +// { +// ip_addr_t laddr; +// laddr.type = IPADDR_TYPE_V4; +// laddr.u_addr.ip4.addr = addr; +// return listen(&laddr, port); +// } + +// bool listen(const IPv6Address addr, uint16_t port) +// { +// ip_addr_t laddr; +// laddr.type = IPADDR_TYPE_V6; +// memcpy((uint8_t*)(laddr.u_addr.ip6.addr), (const uint8_t*)addr, 16); +// return listen(&laddr, port); +// } + + bool listen(uint16_t port) + { + return listen(IP_ANY_TYPE, port); + } + + void poll(std::function &&callback, TickType_t xTicksToWait = 0); + + static bool _udp_task_post(void *arg, udp_pcb *pcb, pbuf *pb, const ip_addr_t *addr, uint16_t port, struct netif *netif); + +private: + bool _init(); + void close(); + +private: + cpputils::DelayedConstruction _udp_queue; + udp_pcb *_pcb{}; + bool _connected{}; +};