Added AsyncUdpListener

This commit is contained in:
2021-04-15 23:17:22 +02:00
parent 66437a5318
commit 49b2d31678
4 changed files with 450 additions and 0 deletions

21
CMakeLists.txt Normal file
View File

@ -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)

39
Kconfig.projbuild Normal file
View File

@ -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

259
src/asyncudplistener.cpp Normal file
View File

@ -0,0 +1,259 @@
#include "asyncudplistener.h"
#include "sdkconfig.h"
#define LOG_LOCAL_LEVEL CONFIG_LOG_LOCAL_LEVEL_ASYNC_UDP_LISTENER
// system includes
#include <cassert>
// esp-idf includes
#include <lwip/priv/tcpip_priv.h>
#include <lwip/prot/ethernet.h>
#include <esp_log.h>
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<const udp_hdr*>(_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; i<TCPIP_ADAPTER_IF_MAX; i++)
{
tcpip_adapter_get_netif((tcpip_adapter_if_t)i, &nif);
struct netif *netif = (struct netif *)nif;
if (netif && netif == ntif)
{
_if = (tcpip_adapter_if_t)i;
break;
}
}
}
bool AsyncUdpListener::listen(const ip_addr_t *addr, uint16_t port)
{
if (!_udp_queue.constructed())
{
_udp_queue.construct(UBaseType_t{32}, sizeof(lwip_event_packet_t *));
if (!_udp_queue->handle)
{
_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<void(const UdpPacketWrapper &packet)> &&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<AsyncUdpListener*>(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;
}
}

131
src/asyncudplistener.h Normal file
View File

@ -0,0 +1,131 @@
#pragma once
// system includes
#include <functional>
#include <cstring>
#include <optional>
#include <array>
// esp-idf includes
#include <lwip/ip_addr.h>
#include <lwip/udp.h>
#include <lwip/pbuf.h>
#include <esp_netif.h>
// 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<u32_t> localIP() const
{
if (_localIp.type != IPADDR_TYPE_V4)
return std::nullopt;
return _localIp.u_addr.ip4.addr;
}
std::optional<std::array<u32_t, 4>> localIPv6() const
{
if (_localIp.type != IPADDR_TYPE_V6)
return std::nullopt;
return *reinterpret_cast<const std::array<u32_t, 4>*>(_localIp.u_addr.ip6.addr);
}
uint16_t localPort() const { return _localPort; }
std::optional<u32_t> remoteIP() const
{
if (_remoteIp.type != IPADDR_TYPE_V4)
return std::nullopt;
return _remoteIp.u_addr.ip4.addr;
}
std::optional<std::array<u32_t, 4>> remoteIPv6() const
{
if (_remoteIp.type != IPADDR_TYPE_V6)
return std::nullopt;
return *reinterpret_cast<const std::array<u32_t, 4>*>(_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<void(const UdpPacketWrapper &packet)> &&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<espcpputils::queue> _udp_queue;
udp_pcb *_pcb{};
bool _connected{};
};