2#if defined(USE_OTA) && defined(ESPHOME_OTA_UDP)
9#ifdef ESPHOME_OTA_UDP_LWIP
12#include "lwip/tcpip.h"
34static constexpr uint8_t UDP_MSG_DATA = 0x01;
35static constexpr uint8_t UDP_FLAG_COMMITTED = 0x80;
36static constexpr uint8_t UDP_MSG_ACK = 0x40;
37static constexpr size_t UDP_HEADER_SIZE = 9;
38static constexpr size_t UDP_ACK_HEADER_SIZE = 12;
40static constexpr uint32_t UDP_COMMIT_TIMEOUT = 80000;
42static constexpr uint32_t UDP_READ_WAIT_MS = 2000;
44static constexpr uint32_t UDP_COMMIT_POLL_MS = 100;
45static constexpr uint32_t UDP_LINGER_MS = 3000;
47#ifdef ESPHOME_OTA_UDP_LWIP
51 UdpLock() { LOCK_TCPIP_CORE(); }
52 ~UdpLock() { UNLOCK_TCPIP_CORE(); }
57static uint16_t slot_len(
struct pbuf *p) {
return p->tot_len; }
58static uint16_t slot_copy(
struct pbuf *p,
void *dst, uint16_t
len, uint16_t offset) {
59 return pbuf_copy_partial(p, dst,
len, offset);
61static void slot_free(
struct pbuf *p) { pbuf_free(p); }
69struct OtaUdpDatagram {
73static uint16_t slot_len(OtaUdpDatagram *p) {
return p->len; }
74static uint16_t slot_copy(OtaUdpDatagram *p,
void *dst, uint16_t
len, uint16_t offset) {
75 const uint16_t n = std::min<uint16_t>(
len, p->len - offset);
76 memcpy(dst, p->data + offset, n);
79static void slot_free(OtaUdpDatagram *p) {
delete p; }
82static void put_u16(uint8_t *at, uint16_t value) {
91#ifdef ESPHOME_OTA_UDP_LWIP
92 if (this->pcb !=
nullptr)
93 udp_remove(this->pcb);
98 for (
auto &p : this->queue) {
106 const uint16_t
len = UDP_ACK_HEADER_SIZE + link.
out_len;
107#ifdef ESPHOME_OTA_UDP_LWIP
108 struct pbuf *p = pbuf_alloc(PBUF_TRANSPORT,
len, PBUF_RAM);
111 auto *
ack =
static_cast<uint8_t *
>(p->payload);
115 ack[0] = UDP_MSG_ACK;
118 put_u16(
ack + 7, prompted_by);
128#ifdef ESPHOME_OTA_UDP_LWIP
143 uint8_t hdr[UDP_HEADER_SIZE];
144 if (slot_copy(p, hdr, UDP_HEADER_SIZE, 0) != UDP_HEADER_SIZE || memcmp(hdr + 1, link.
reply, 4) != 0) {
150 link.
committed |= (hdr[0] & UDP_FLAG_COMMITTED) != 0;
153 if (acked != 0 && acked <= link.
out_len) {
160 if ((hdr[0] & ~UDP_FLAG_COMMITTED) == UDP_MSG_DATA && slot_len(p) > UDP_HEADER_SIZE &&
172#ifdef ESPHOME_OTA_UDP_LWIP
176 auto &link = *
static_cast<UdpLink *
>(arg);
177 if (!ip_addr_cmp(addr, &link.peer_ip)) {
189 return v4_a && v4_b && ipv4_a == ipv4_b;
190 return a.
ss_family == AF_INET6 && b.ss_family == AF_INET6 &&
191 memcmp(&
reinterpret_cast<const sockaddr_in6 *
>(&a)->sin6_addr,
192 &
reinterpret_cast<const sockaddr_in6 *
>(&b)->sin6_addr,
sizeof(in6_addr)) == 0;
196 UdpLink &link = *this->
udp_;
198 uint8_t buf[
sizeof(OtaUdpDatagram::data)];
201 const ssize_t len = link.sock->recvfrom(buf,
sizeof(buf),
reinterpret_cast<struct
sockaddr *
>(&from), &from_len);
204 if (!same_ip(from, link.peer))
206 auto *datagram =
new OtaUdpDatagram;
208 memcpy(datagram->data, buf,
len);
209 const uint16_t port = from.ss_family == AF_INET6 ? ntohs(
reinterpret_cast<sockaddr_in6 *
>(&from)->sin6_port)
210 : ntohs(reinterpret_cast<
sockaddr_in *>(&from)->sin_port);
219 if (this->
client_->getpeername(
reinterpret_cast<struct
sockaddr *
>(&peer), &peer_len) != 0)
224#ifdef ESPHOME_OTA_UDP_LWIP
227 ip_addr_set_ip4_u32(&link.
peer_ip, ipv4);
229#if USE_NETWORK_IPV6 && LWIP_IPV6
238 link.
pcb = udp_new_ip_type(IPADDR_TYPE_ANY);
240 link.
pcb = udp_new();
243 if (link.
pcb ==
nullptr || udp_bind(link.
pcb, IP_ANY_TYPE, this->port_) != ERR_OK)
254 if (link.
sock ==
nullptr || local_len == 0 ||
255 link.
sock->bind(
reinterpret_cast<struct
sockaddr *
>(&local), local_len) != 0 ||
256 link.
sock->setblocking(
false) != 0)
260 token[0] = (token[0] & 0x7F) | 0x01;
261 memcpy(link.
reply, token,
sizeof(token));
267 0, UDP_COMMIT_POLL_MS * 1000
269 this->
client_->setsockopt(SOL_SOCKET, SO_RCVTIMEO, &tv,
sizeof(tv));
271 while (
millis() - start < UDP_COMMIT_TIMEOUT) {
289 this->
udp_ =
nullptr;
290 tv = {UDP_READ_WAIT_MS / 1000, 0};
291 this->
client_->setsockopt(SOL_SOCKET, SO_RCVTIMEO, &tv,
sizeof(tv));
319 if (
millis() - start >= UDP_READ_WAIT_MS)
325 const uint16_t offset = UDP_HEADER_SIZE + link.
read_offset;
326 const uint16_t n = slot_copy(p, buf + got, std::min<size_t>(
len - got, slot_len(p) - offset), offset);
329 if (UDP_HEADER_SIZE + link.
read_offset >= slot_len(p)) {
360 if (this->
udp_ ==
nullptr)
363 while (
millis() - start < UDP_LINGER_MS) {
void feed_wdt()
Feed the task watchdog.
bool would_block_(int error_code) const
bool udp_send_(const uint8_t *data, size_t len)
void yield_and_feed_watchdog_()
static void udp_recv_cb(void *arg, struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *addr, uint16_t port)
ssize_t udp_read_(uint8_t *buf, size_t len)
static constexpr uint8_t UDP_WINDOW
static void udp_on_datagram(UdpLink &link, UdpSlot p, uint16_t port)
static constexpr size_t UDP_OUT_SIZE
std::unique_ptr< socket::Socket > client_
static void udp_send_ack(UdpLink &link, uint16_t prompted_by)
void udp_open_(UdpLink &link)
Helper class to lock the lwIP TCPIP core when making lwIP API calls from non-TCPIP threads.
ESPHOME_LOG_TAG(TAG, CRASH_LOG_TAG)
std::unique_ptr< Socket > socket_ip(int type, int protocol)
Create a socket in the newest available IP domain (IPv6 or IPv4) of the given type and protocol.
bool sockaddr_to_ipv4(const struct sockaddr *addr, uint32_t *out)
Network order IPv4 of an AF_INET or v4 mapped AF_INET6 peer; false for anything else.
socklen_t set_sockaddr_any(struct sockaddr *addr, socklen_t addrlen, uint16_t port)
Set a sockaddr to the any address and specified port for the IP version used by socket_ip().
bool random_bytes(uint8_t *data, size_t len)
Generate len random bytes using the platform's secure RNG (hardware RNG or OS CSPRNG).
constexpr uint16_t encode_uint16(uint8_t msb, uint8_t lsb)
Encode a 16-bit value given the most and least significant byte.
uint32_t IRAM_ATTR HOT millis()
Application App
Global storage of Application pointer - only one Application can exist.
UdpSlot queue[UDP_WINDOW]
struct sockaddr_storage peer
std::unique_ptr< socket::Socket > sock
uint8_t out[UDP_OUT_SIZE]
struct in6_addr sin6_addr