From d2bb37b2a87e0c30b943805f4721afbd4a5ab175 Mon Sep 17 00:00:00 2001 From: Alexander Entinger Date: Tue, 15 Oct 2024 10:01:38 +0200 Subject: [PATCH 1/5] Implement packet-based UDP API. --- examples/UDP_Client/UDP_Client.ino | 8 ++- examples/UDP_Server/UDP_Server.ino | 7 ++- src/Arduino_10BASE_T1S_UDP.cpp | 83 +++++++++++++++++++----------- src/Arduino_10BASE_T1S_UDP.h | 68 ++++++++++++++++++++++-- 4 files changed, 131 insertions(+), 35 deletions(-) diff --git a/examples/UDP_Client/UDP_Client.ino b/examples/UDP_Client/UDP_Client.ino index 4fea78b..9c83e23 100644 --- a/examples/UDP_Client/UDP_Client.ino +++ b/examples/UDP_Client/UDP_Client.ino @@ -143,7 +143,7 @@ void loop() int const rx_packet_size = udp_client.parsePacket(); if (rx_packet_size) { - /* Receive incoming UDP packets. */ + /* Print some metadata from received UDP packet. */ Serial.print("Received "); Serial.print(rx_packet_size); Serial.print(" bytes from "); @@ -152,15 +152,21 @@ void loop() Serial.print(udp_client.remotePort()); Serial.println(); + /* Read from received UDP packet. */ int const bytes_read = udp_client.read(udp_rx_msg_buf, sizeof(udp_rx_msg_buf)); if (bytes_read > 0) { udp_rx_msg_buf[bytes_read] = 0; } + + /* Finish reading the current packet. */ + udp_client.flush(); + Serial.print("["); Serial.print(millis()); Serial.print("] UDP_Client received packet content: \""); Serial.print(reinterpret_cast(udp_rx_msg_buf)); Serial.println("\""); + } } diff --git a/examples/UDP_Server/UDP_Server.ino b/examples/UDP_Server/UDP_Server.ino index 701c81c..2ddb501 100644 --- a/examples/UDP_Server/UDP_Server.ino +++ b/examples/UDP_Server/UDP_Server.ino @@ -122,7 +122,7 @@ void loop() int const packet_size = udp_server.parsePacket(); if (packet_size) { - /* Receive incoming UDP packets. */ + /* Print some metadata from received UDP packet. */ Serial.print("Received "); Serial.print(packet_size); Serial.print(" bytes from "); @@ -131,10 +131,15 @@ void loop() Serial.print(udp_server.remotePort()); Serial.println(); + /* Read from received UDP packet. */ int const bytes_read = udp_server.read(udp_rx_msg_buf, sizeof(udp_rx_msg_buf)); if (bytes_read > 0) { udp_rx_msg_buf[bytes_read] = 0; } + + /* Finish reading the current packet. */ + udp_server.flush(); + Serial.print("UDP_Server received packet content: \""); Serial.print(reinterpret_cast(udp_rx_msg_buf)); Serial.println("\""); diff --git a/src/Arduino_10BASE_T1S_UDP.cpp b/src/Arduino_10BASE_T1S_UDP.cpp index 23b77ab..ba22041 100644 --- a/src/Arduino_10BASE_T1S_UDP.cpp +++ b/src/Arduino_10BASE_T1S_UDP.cpp @@ -26,8 +26,6 @@ static void lwIp_udp_raw_recv(void *arg, struct udp_pcb *pcb, struct pbuf *p, co Arduino_10BASE_T1S_UDP::Arduino_10BASE_T1S_UDP() : _udp_pcb{nullptr} -, _remote_ip{0,0,0,0} -, _remote_port{0} , _send_to_ip{0,0,0,0} , _send_to_port{0} { @@ -138,70 +136,97 @@ size_t Arduino_10BASE_T1S_UDP::write(const uint8_t * buffer, size_t size) int Arduino_10BASE_T1S_UDP::parsePacket() { - return available(); + if (_rx_pkt_list.size()) + return _rx_pkt_list.front()->totalSize(); + else + return 0; } int Arduino_10BASE_T1S_UDP::available() { - return _rx_data.size(); + if (_rx_pkt_list.size()) + return _rx_pkt_list.front()->available(); + else + return 0; } int Arduino_10BASE_T1S_UDP::read() { - uint8_t const data = _rx_data.front(); - _rx_data.pop_front(); - return data; + if (_rx_pkt_list.size()) + return _rx_pkt_list.front()->read(); + else + return -1; } int Arduino_10BASE_T1S_UDP::read(unsigned char* buffer, size_t len) { - size_t bytes_read = 0; - for (; bytes_read < len && !_rx_data.empty(); bytes_read++) - { - buffer[bytes_read] = _rx_data.front(); - _rx_data.pop_front(); - } - return bytes_read; + if (_rx_pkt_list.size()) + return _rx_pkt_list.front()->read(buffer, len); + else + return -1; } int Arduino_10BASE_T1S_UDP::read(char* buffer, size_t len) { - return read((unsigned char*)buffer, len); + if (_rx_pkt_list.size()) + return _rx_pkt_list.front()->read(buffer, len); + else + return -1; } int Arduino_10BASE_T1S_UDP::peek() { - return _rx_data.front(); + if (_rx_pkt_list.size()) + return _rx_pkt_list.front()->peek(); + else + return -1; } void Arduino_10BASE_T1S_UDP::flush() { - /* Nothing to be done. */ + /* Drop packet from receive buffer. */ + if (_rx_pkt_list.size()) + { + auto const rx_pkt = _rx_pkt_list.front(); + _rx_pkt_list.pop_front(); + delete rx_pkt; + } } IPAddress Arduino_10BASE_T1S_UDP::remoteIP() { - return _remote_ip; + if (_rx_pkt_list.size()) + return _rx_pkt_list.front()->remoteIP(); + else + return IPAddress(); } uint16_t Arduino_10BASE_T1S_UDP::remotePort() { - return _remote_port; + if (_rx_pkt_list.size()) + return _rx_pkt_list.front()->remotePort(); + else + return 0; } void Arduino_10BASE_T1S_UDP::onUdpRawRecv(struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *addr, uint16_t port) { /* Obtain remote port and remote IP. */ - _remote_ip = IPAddress(ip4_addr1(addr), - ip4_addr2(addr), - ip4_addr3(addr), - ip4_addr4(addr)); - _remote_port = port; - - /* Copy data into buffer. */ - std::copy((uint8_t *)p->payload, - (uint8_t *)p->payload + p->len, - std::back_inserter(_rx_data)); + auto const remote_ip = IPAddress( + ip4_addr1(addr), + ip4_addr2(addr), + ip4_addr3(addr), + ip4_addr4(addr)); + auto const remote_port = port; + + /* Create UDP object. */ + auto const rx_pkt = new UdpRxPacket( + remote_ip, + remote_port, + (uint8_t *)p->payload, + p->len); + + _rx_pkt_list.push_back(rx_pkt); /* Free pbuf */ pbuf_free(p); diff --git a/src/Arduino_10BASE_T1S_UDP.h b/src/Arduino_10BASE_T1S_UDP.h index 60f775a..6ca412b 100644 --- a/src/Arduino_10BASE_T1S_UDP.h +++ b/src/Arduino_10BASE_T1S_UDP.h @@ -14,6 +14,7 @@ * INCLUDE **************************************************************************************/ +#include #include #include @@ -67,13 +68,72 @@ class Arduino_10BASE_T1S_UDP : public UDP private: + /* LWIP */ struct udp_pcb * _udp_pcb; - IPAddress _remote_ip; - uint16_t _remote_port; - std::deque _rx_data; - + /* UDP TRANSMISSION */ IPAddress _send_to_ip; uint16_t _send_to_port; std::vector _tx_data; + + /* UDP RECEPTION */ + class UdpRxPacket + { + private: + IPAddress const _remote_ip; + uint16_t const _remote_port; + size_t const _rx_data_len; + std::deque _rx_data; + + public: + UdpRxPacket( + IPAddress const remote_ip, + uint16_t const remote_port, + uint8_t const * p_data, + size_t const data_len) + : _remote_ip(remote_ip) + , _remote_port(remote_port) + , _rx_data_len(data_len) + { + std::copy(p_data, p_data + data_len, std::back_inserter(_rx_data)); + } + + IPAddress remoteIP() const { return _remote_ip; } + uint16_t remotePort() const { return _remote_port; } + size_t totalSize() const { return _rx_data_len; } + + int available() + { + return _rx_data.size(); + } + + int read() + { + uint8_t const data = _rx_data.front(); + _rx_data.pop_front(); + return data; + } + + int read(unsigned char* buffer, size_t len) + { + size_t bytes_read = 0; + for (; bytes_read < len && !_rx_data.empty(); bytes_read++) + { + buffer[bytes_read] = _rx_data.front(); + _rx_data.pop_front(); + } + return bytes_read; + } + + int read(char* buffer, size_t len) + { + return read((unsigned char*)buffer, len); + } + + int peek() + { + return _rx_data.front(); + } + }; + std::list _rx_pkt_list; }; From 738944ee0cee88fa5117f85c06c9954828b46001 Mon Sep 17 00:00:00 2001 From: Alexander Entinger Date: Tue, 15 Oct 2024 10:08:35 +0200 Subject: [PATCH 2/5] By using a smart pointer (std::shared_ptr) we can eliminate all headache concerning memory leaks. --- src/Arduino_10BASE_T1S_UDP.cpp | 6 +----- src/Arduino_10BASE_T1S_UDP.h | 5 ++++- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Arduino_10BASE_T1S_UDP.cpp b/src/Arduino_10BASE_T1S_UDP.cpp index ba22041..a238169 100644 --- a/src/Arduino_10BASE_T1S_UDP.cpp +++ b/src/Arduino_10BASE_T1S_UDP.cpp @@ -186,11 +186,7 @@ void Arduino_10BASE_T1S_UDP::flush() { /* Drop packet from receive buffer. */ if (_rx_pkt_list.size()) - { - auto const rx_pkt = _rx_pkt_list.front(); _rx_pkt_list.pop_front(); - delete rx_pkt; - } } IPAddress Arduino_10BASE_T1S_UDP::remoteIP() @@ -220,7 +216,7 @@ void Arduino_10BASE_T1S_UDP::onUdpRawRecv(struct udp_pcb *pcb, struct pbuf *p, c auto const remote_port = port; /* Create UDP object. */ - auto const rx_pkt = new UdpRxPacket( + auto const rx_pkt = std::make_shared( remote_ip, remote_port, (uint8_t *)p->payload, diff --git a/src/Arduino_10BASE_T1S_UDP.h b/src/Arduino_10BASE_T1S_UDP.h index 6ca412b..e0010b0 100644 --- a/src/Arduino_10BASE_T1S_UDP.h +++ b/src/Arduino_10BASE_T1S_UDP.h @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -98,6 +99,8 @@ class Arduino_10BASE_T1S_UDP : public UDP std::copy(p_data, p_data + data_len, std::back_inserter(_rx_data)); } + typedef std::shared_ptr SharedPtr; + IPAddress remoteIP() const { return _remote_ip; } uint16_t remotePort() const { return _remote_port; } size_t totalSize() const { return _rx_data_len; } @@ -135,5 +138,5 @@ class Arduino_10BASE_T1S_UDP : public UDP return _rx_data.front(); } }; - std::list _rx_pkt_list; + std::list _rx_pkt_list; }; From 17d84b5490e35f6dece6931fdf44b5e9b960d151 Mon Sep 17 00:00:00 2001 From: Alexander Entinger Date: Tue, 15 Oct 2024 10:27:17 +0200 Subject: [PATCH 3/5] By choosing a receive buffer smaller than the total UDP payload size (in our examples) we can test the functionality of reading a UDP packet until exhaustion. --- examples/UDP_Client/UDP_Client.ino | 28 +++++++++++--------- examples/UDP_Server/UDP_Server.ino | 41 ++++++++++++++++++++---------- 2 files changed, 44 insertions(+), 25 deletions(-) diff --git a/examples/UDP_Client/UDP_Client.ino b/examples/UDP_Client/UDP_Client.ino index 9c83e23..99280fc 100644 --- a/examples/UDP_Client/UDP_Client.ino +++ b/examples/UDP_Client/UDP_Client.ino @@ -29,8 +29,6 @@ static T1SMacSettings const t1s_default_mac_settings; static IPAddress const UDP_SERVER_IP_ADDR = {192, 168, 42, 100 + 0}; static uint16_t const UDP_CLIENT_PORT = 8889; static uint16_t const UDP_SERVER_PORT = 8888; -static uint8_t udp_tx_msg_buf[256] = {0}; -static uint8_t udp_rx_msg_buf[256] = {0}; /************************************************************************************** * GLOBAL VARIABLES @@ -152,21 +150,27 @@ void loop() Serial.print(udp_client.remotePort()); Serial.println(); - /* Read from received UDP packet. */ - int const bytes_read = udp_client.read(udp_rx_msg_buf, sizeof(udp_rx_msg_buf)); - if (bytes_read > 0) { - udp_rx_msg_buf[bytes_read] = 0; - } - - /* Finish reading the current packet. */ - udp_client.flush(); - Serial.print("["); Serial.print(millis()); Serial.print("] UDP_Client received packet content: \""); - Serial.print(reinterpret_cast(udp_rx_msg_buf)); + + /* Read from received UDP packet. */ + size_t const UDP_RX_MSG_BUF_SIZE = 16 + 1; /* Reserve the last byte for the '\0' termination. */ + uint8_t udp_rx_msg_buf[UDP_RX_MSG_BUF_SIZE] = {0}; + int bytes_read = udp_client.read(udp_rx_msg_buf, UDP_RX_MSG_BUF_SIZE - 1); + while(bytes_read != 0) + { + /* Print received data to Serial. */ + udp_rx_msg_buf[bytes_read] = '\0'; /* Terminate buffer so that we can print it as a C-string. */ + Serial.print(reinterpret_cast(udp_rx_msg_buf)); + + /* Continue reading. */ + bytes_read = udp_client.read(udp_rx_msg_buf, UDP_RX_MSG_BUF_SIZE - 1); + } Serial.println("\""); + /* Finish reading the current packet. */ + udp_client.flush(); } } diff --git a/examples/UDP_Server/UDP_Server.ino b/examples/UDP_Server/UDP_Server.ino index 2ddb501..7821dd3 100644 --- a/examples/UDP_Server/UDP_Server.ino +++ b/examples/UDP_Server/UDP_Server.ino @@ -33,7 +33,6 @@ static T1SPlcaSettings const t1s_plca_settings{T1S_PLCA_NODE_ID}; static T1SMacSettings const t1s_default_mac_settings; static uint16_t const UDP_SERVER_LOCAL_PORT = 8888; -static uint8_t udp_rx_msg_buf[256] = {0}; /************************************************************************************** * GLOBAL VARIABLES @@ -119,34 +118,50 @@ void loop() } /* Check for incoming UDP packets. */ - int const packet_size = udp_server.parsePacket(); - if (packet_size) + int const rx_packet_size = udp_server.parsePacket(); + if (rx_packet_size) { + std::vector udp_tx_buf(rx_packet_size); + IPAddress const destination_ip = udp_server.remoteIP(); + uint16_t const destination_port = udp_server.remotePort(); + /* Print some metadata from received UDP packet. */ Serial.print("Received "); - Serial.print(packet_size); + Serial.print(rx_packet_size); Serial.print(" bytes from "); Serial.print(udp_server.remoteIP()); Serial.print(" port "); Serial.print(udp_server.remotePort()); Serial.println(); + Serial.print("["); + Serial.print(millis()); + Serial.print("] UDP_Client received packet content: \""); + /* Read from received UDP packet. */ - int const bytes_read = udp_server.read(udp_rx_msg_buf, sizeof(udp_rx_msg_buf)); - if (bytes_read > 0) { - udp_rx_msg_buf[bytes_read] = 0; + size_t const UDP_RX_MSG_BUF_SIZE = 16 + 1; /* Reserve the last byte for the '\0' termination. */ + uint8_t udp_rx_msg_buf[UDP_RX_MSG_BUF_SIZE] = {0}; + int bytes_read = udp_server.read(udp_rx_msg_buf, UDP_RX_MSG_BUF_SIZE - 1); + while(bytes_read != 0) + { + /* Copy received data into transmit buffer for echo functionality. */ + std::copy(udp_rx_msg_buf, udp_rx_msg_buf + bytes_read, std::back_inserter(udp_tx_buf)); + + /* Print received data to Serial. */ + udp_rx_msg_buf[bytes_read] = '\0'; /* Terminate buffer so that we can print it as a C-string. */ + Serial.print(reinterpret_cast(udp_rx_msg_buf)); + + /* Continue reading. */ + bytes_read = udp_server.read(udp_rx_msg_buf, UDP_RX_MSG_BUF_SIZE - 1); } + Serial.println("\""); /* Finish reading the current packet. */ udp_server.flush(); - Serial.print("UDP_Server received packet content: \""); - Serial.print(reinterpret_cast(udp_rx_msg_buf)); - Serial.println("\""); - /* Send back a reply, to the IP address and port we got the packet from. */ - udp_server.beginPacket(udp_server.remoteIP(), udp_server.remotePort()); - udp_server.write((const uint8_t *)udp_rx_msg_buf, packet_size); + udp_server.beginPacket(destination_ip, destination_port); + udp_server.write(udp_tx_buf.data(), udp_tx_buf.size()); udp_server.endPacket(); } } From 475f64951ec85b45ce93210c57577ff9401295f8 Mon Sep 17 00:00:00 2001 From: Alexander Entinger Date: Tue, 15 Oct 2024 10:37:56 +0200 Subject: [PATCH 4/5] Fix: std::back_inserter already adds new elements, no need to reserve buffer before using std::back_inserter. Doing so doubles the memory requirements. --- examples/UDP_Client/UDP_Client.ino | 1 + examples/UDP_Server/UDP_Server.ino | 2 +- src/Arduino_10BASE_T1S_UDP.cpp | 1 - src/Arduino_10BASE_T1S_UDP.h | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/UDP_Client/UDP_Client.ino b/examples/UDP_Client/UDP_Client.ino index 99280fc..55929ef 100644 --- a/examples/UDP_Client/UDP_Client.ino +++ b/examples/UDP_Client/UDP_Client.ino @@ -121,6 +121,7 @@ void loop() prev_udp_packet_sent = now; /* Prepare UDP packet. */ + uint8_t udp_tx_msg_buf[256] = {0}; int const tx_packet_size = snprintf((char *)udp_tx_msg_buf, sizeof(udp_tx_msg_buf), "Single-Pair Ethernet / 10BASE-T1S: packet cnt = %d", tx_packet_cnt); /* Send a UDP packet to the UDP server. */ diff --git a/examples/UDP_Server/UDP_Server.ino b/examples/UDP_Server/UDP_Server.ino index 7821dd3..a6b446c 100644 --- a/examples/UDP_Server/UDP_Server.ino +++ b/examples/UDP_Server/UDP_Server.ino @@ -121,7 +121,7 @@ void loop() int const rx_packet_size = udp_server.parsePacket(); if (rx_packet_size) { - std::vector udp_tx_buf(rx_packet_size); + std::vector udp_tx_buf; IPAddress const destination_ip = udp_server.remoteIP(); uint16_t const destination_port = udp_server.remotePort(); diff --git a/src/Arduino_10BASE_T1S_UDP.cpp b/src/Arduino_10BASE_T1S_UDP.cpp index a238169..9696083 100644 --- a/src/Arduino_10BASE_T1S_UDP.cpp +++ b/src/Arduino_10BASE_T1S_UDP.cpp @@ -129,7 +129,6 @@ size_t Arduino_10BASE_T1S_UDP::write(uint8_t data) size_t Arduino_10BASE_T1S_UDP::write(const uint8_t * buffer, size_t size) { - _tx_data.reserve(_tx_data.size() + size); std::copy(buffer, buffer + size, std::back_inserter(_tx_data)); return size; } diff --git a/src/Arduino_10BASE_T1S_UDP.h b/src/Arduino_10BASE_T1S_UDP.h index e0010b0..71eaa6c 100644 --- a/src/Arduino_10BASE_T1S_UDP.h +++ b/src/Arduino_10BASE_T1S_UDP.h @@ -95,8 +95,8 @@ class Arduino_10BASE_T1S_UDP : public UDP : _remote_ip(remote_ip) , _remote_port(remote_port) , _rx_data_len(data_len) + , _rx_data(p_data, p_data + data_len) { - std::copy(p_data, p_data + data_len, std::back_inserter(_rx_data)); } typedef std::shared_ptr SharedPtr; From b4121867cb29f539a6051e3fffeea12b7ef36e38 Mon Sep 17 00:00:00 2001 From: Alexander Entinger Date: Tue, 15 Oct 2024 10:42:31 +0200 Subject: [PATCH 5/5] Improvement: perform a const cast (the outcome is the same, but its cleaner). --- src/Arduino_10BASE_T1S_UDP.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Arduino_10BASE_T1S_UDP.cpp b/src/Arduino_10BASE_T1S_UDP.cpp index 9696083..0e46c00 100644 --- a/src/Arduino_10BASE_T1S_UDP.cpp +++ b/src/Arduino_10BASE_T1S_UDP.cpp @@ -218,7 +218,7 @@ void Arduino_10BASE_T1S_UDP::onUdpRawRecv(struct udp_pcb *pcb, struct pbuf *p, c auto const rx_pkt = std::make_shared( remote_ip, remote_port, - (uint8_t *)p->payload, + (uint8_t const *)p->payload, p->len); _rx_pkt_list.push_back(rx_pkt);