Logo Pico-Framework A web-first embedded framework for C++
Loading...
Searching...
No Matches
Tcp.cpp
Go to the documentation of this file.
1#include "network/Tcp.h"
2
3#include <lwip/dns.h>
4#include <lwip/api.h>
5#include <lwip/sockets.h>
6#include <lwip/inet.h>
7#include <cstring>
8#include <cstdio>
9#include <FreeRTOS.h>
10#include <task.h>
11#include <pico/stdlib.h>
12#include "utility/utility.h"
14
15#if PICO_TCP_ENABLE_TLS
16#include <lwip/altcp.h>
17#include <lwip/altcp_tls.h>
18#endif
19
20#include "framework_config.h"
21#include "DebugTrace.h"
23
25 : sockfd(-1), connected(false), use_tls(false)
26#if PICO_TCP_ENABLE_TLS
27 ,
28 tls_config(nullptr), server_tls_config(nullptr),
29 tls_pcb(nullptr)
30#endif
31{
32}
33
34Tcp::Tcp(int fd)
35 : sockfd(fd), connected(true), use_tls(false)
36#if PICO_TCP_ENABLE_TLS
37 ,
38 tls_config(nullptr), server_tls_config(nullptr),
39 tls_pcb(nullptr)
40#endif
41{
42}
43
45{
46 close();
47}
48
49Tcp::Tcp(Tcp &&other) noexcept
50{
51 *this = std::move(other);
52}
53
54Tcp &Tcp::operator=(Tcp &&other) noexcept
55{
56 if (this != &other)
57 {
58 sockfd = other.sockfd;
59 #if PICO_TCP_ENABLE_TLS
60 tls_pcb = other.tls_pcb;
61 tls_config = other.tls_config;
62 server_tls_config = other.server_tls_config;
63 #endif
64 connected = other.connected;
65 use_tls = other.use_tls;
66 recv_buffer = other.recv_buffer;
67
68 other.sockfd = -1;
69 #if PICO_TCP_ENABLE_TLS
70 other.tls_pcb = nullptr;
71 other.tls_config = nullptr;
72 other.server_tls_config = nullptr;
73 #endif
74 other.recv_buffer = nullptr;
75 }
76 return *this;
77}
78
79std::string Tcp::getPeerIp() const
80{
81 sockaddr_in addr;
82 socklen_t len = sizeof(addr);
83 if (lwip_getpeername(sockfd, reinterpret_cast<sockaddr *>(&addr), &len) == 0)
84 {
85 ip_addr_t ip;
86 ip.addr = addr.sin_addr.s_addr;
87 return std::string(ipaddr_ntoa(&ip));
88 }
89 return "0.0.0.0";
90}
91
92#if PICO_TCP_ENABLE_TLS
93void Tcp::setRootCACertificate(const std::string &pem)
94{
95 root_ca_cert = pem;
96}
97
98
99void Tcp::setServerTlsConfig(const std::string &cert, const std::string &key)
100{
101 server_tls_cert = cert;
102 server_tls_key = key;
103
104 server_tls_config = altcp_tls_create_config_server_privkey_cert(
105 reinterpret_cast<const uint8_t *>(server_tls_key.c_str()), server_tls_key.size(),
106 reinterpret_cast<const uint8_t *>(server_tls_cert.c_str()), server_tls_cert.size(),
107 nullptr, 0 // no passphrase
108 );
110 {
111 printf("[Tcp] Failed to create TLS server config\n");
112 }
113}
114#endif
115
116bool Tcp::connect(const char *host, int port, bool use_tls)
117{
118 strncpy(hostname, host, sizeof(hostname) - 1);
119 this->use_tls = use_tls;
120 ip_addr_t ip;
121 if (!resolveHostnameBlocking(host, &ip))
122 {
123 printf("[Tcp] DNS resolution failed for %s\n", host);
124 return false;
125 }
126#if PICO_TCP_ENABLE_TLS
127 return use_tls ? connectTls(ip, port) : connectPlain(ip, port);
128#else
129 return connectPlain(ip, port);
130#endif
131}
132
133bool Tcp::connectPlain(const ip_addr_t &ip, int port)
134{
135 struct sockaddr_in addr = {};
136 addr.sin_family = AF_INET;
137 addr.sin_port = lwip_htons(port);
138 addr.sin_addr.s_addr = ip.addr;
139
140 sockfd = lwip_socket(AF_INET, SOCK_STREAM, 0);
141 if (sockfd < 0)
142 {
143 printf("[Tcp] Failed to create socket\n");
144 return false;
145 }
146
147 if (lwip_connect(sockfd, reinterpret_cast<struct sockaddr *>(&addr), sizeof(addr)) < 0)
148 {
149 printf("[Tcp] Failed to connect to server\n");
150 lwip_close(sockfd);
151 sockfd = -1;
152 return false;
153 }
154 TRACE("[Tcp] Connected to server (plain)\n");
155 connected = true;
156 use_tls = false;
157 return true;
158}
159
160#if PICO_TCP_ENABLE_TLS
161bool Tcp::connectTls(const char *host, int port)
162{
163 ip_addr_t ip;
164 if (!resolveHostnameBlocking(host, &ip))
165 {
166 printf("[Tcp] DNS resolution failed for %s\n", host);
167 return false;
168 }
169 return connectTls(ip, port);
170}
171
172err_t Tcp::onConnected(void *arg, struct altcp_pcb *conn, err_t err)
173{
174 TRACE("[Tcp] onConnected callback\n");
175 if (err != ERR_OK)
176 {
177 printf("[Tcp] Connection failed: %d\n", err);
178
179 return err;
180 }
181 // Set the context for the callbacks
182 auto *self = static_cast<Tcp *>(arg);
183
184 self->connectResult = err;
185
186 if (self->connectingTask)
187 {
188 TRACE("TLS Connection State in onConnect: %d\n", conn->state);
189 xTaskNotifyGiveIndexed(self->connectingTask, NotifyConnect);
190 }
191
192 return ERR_OK;
193}
194
195void Tcp::onError(void *arg, err_t err)
196{
197 auto *self = static_cast<Tcp *>(arg);
198 printf("[Tcp] altcp error: %d\n", err);
199
200 self->connectResult = err;
201
202 // Notify the waiting task (e.g. to break ulTaskNotifyTakeIndexed)
203 if (self->connectingTask)
204 {
205 xTaskNotifyGiveIndexed(self->connectingTask, NotifyConnect);
206 }
207
208 self->tls_pcb = nullptr;
209}
210
211bool Tcp::connectTls(const ip_addr_t &ip, int port)
212{
213 if (!tls_config)
214 {
215 TRACE("[Tcp] RootCA Certificate is: %s\n", root_ca_cert.c_str());
216 tls_config = altcp_tls_create_config_client(
217 reinterpret_cast<const uint8_t *>(root_ca_cert.c_str()),
218 root_ca_cert.size() + 1); // +1 for null terminator
219 // tls_config = altcp_tls_create_config_client(nullptr, 0);
220 if (!tls_config)
221 {
222 printf("[Tcp] TLS config creation failed %d\n", tls_config);
223 return false;
224 }
225 TRACE("[Tcp] TLS config created\n");
226 }
227 tls_pcb = altcp_tls_new(tls_config, IPADDR_TYPE_ANY);
228 // tls_pcb = altcp_tls_new(tls_config, IP_GET_TYPE(&ip));
229 if (!tls_pcb)
230 {
231 printf("[Tcp] Failed to create TLS connection\n");
232 return false;
233 }
234 TRACE("Setting tls hostname: %s\n", hostname);
235
236 // Because altcp_tls_context() returns void*, we must cast to correct type in C++
237 auto *ssl_ctx = static_cast<mbedtls_ssl_context *>(altcp_tls_context(tls_pcb));
238 mbedtls_ssl_set_hostname(ssl_ctx, hostname);
239
240 // Register recv callback *before* connect
241 altcp_arg(tls_pcb, this); // Set the context for the callbacks
242 TRACE("[Tcp] Registering TLS recv callback\n");
243 altcp_recv(tls_pcb, tlsRecvCallback); // Register callback
244
245 this->connectResult = ERR_OK;
246 this->connectingTask = xTaskGetCurrentTaskHandle();
247 altcp_err(tls_pcb, &Tcp::onError);
248 TRACE("[Tcp] TLS Connecting to %s:%d\n", ipaddr_ntoa(&ip), port);
249 err_t err = altcp_connect(tls_pcb, &ip, port, &Tcp::onConnected);
250 if (err != ERR_OK)
251 {
252 printf("[Tcp] altcp_connect failed: %d\n", err);
253 altcp_close(tls_pcb);
254 tls_pcb = nullptr;
255 return false;
256 }
257
258 // Wait briefly for the handshake — altcp doesn't expose state.
259 // Wait for notification that TLS handshake completed
260 ulTaskNotifyTakeIndexed(NotifyConnect, pdTRUE, pdMS_TO_TICKS(portMAX_DELAY));
261 TRACE("[Tcp] TLS handshake completed\n");
262 if (connectResult == ERR_OK)
263 {
264 printf("[Tcp] TLS connection established\n");
265 connected = true;
266 }
267 else
268 {
269 printf("[Tcp] TLS connection failed %d\n", connectResult);
270 altcp_close(tls_pcb);
271 tls_pcb = nullptr;
272 return false;
273 }
274 use_tls = true;
275 return true;
276}
277#endif
278
279int Tcp::send(const char *buffer, size_t size)
280{
281 TRACE("[Tcp] Sending %zu bytes\n", size);
282 #if PICO_TCP_ENABLE_TLS
283 if ((!buffer && size > 0) || !connected || (use_tls && !tls_pcb))
284#else
285 if ((!buffer && size > 0) || !connected)
286#endif
287 {
288 printf("[Tcp] Invalid buffer, size, or connection\n");
289 return -1;
290 }
291
292 constexpr size_t chunkSize = HTTP_BUFFER_SIZE;
293 size_t totalSent = 0;
294
295 // absolute_time_t startTime = get_absolute_time(); // <-- your timing starts here
296
297 while (totalSent < size)
298 {
299 size_t toSend = (size - totalSent > chunkSize) ? chunkSize : (size - totalSent);
300 #if PICO_TCP_ENABLE_TLS
301 if (use_tls && tls_pcb)
302 {
303
304 if (tls_pcb->state == nullptr)
305 {
306 printf("[Tcp] TLS connection is not established\n");
307 return -1;
308 }
309
310 err_t err = altcp_write(tls_pcb, buffer + totalSent, toSend, TCP_WRITE_FLAG_COPY);
311 if (err != ERR_OK)
312 {
313 printf("[Tcp] altcp_write failed: %d\n", err);
314 return -1;
315 }
316 if (altcp_output(tls_pcb) != ERR_OK)
317 {
318 printf("[Tcp] altcp_output failed\n");
319 return -1;
320 }
321 }
322
323 else
324 #endif
325 if (sockfd >= 0)
326 {
327 int ret = lwip_send(sockfd, buffer + totalSent, toSend, 0);
328 if (ret <= 0)
329 {
330 warning("[Tcp] lwip_send failed: ", ret);
331 return -1;
332 }
333 }
334 else
335 {
336 printf("[Tcp] No valid socket or TLS connection\n");
337 return -1;
338 }
339
340 vTaskDelay(pdMS_TO_TICKS(STREAM_SEND_DELAY_MS)); // Throttle per chunk
341 totalSent += toSend;
342 }
343
344 // After sending ALL chunks: yield and delay for TCP flush
345 taskYIELD();
346 vTaskDelay(pdMS_TO_TICKS(20)); // Give lwIP time to transmit the data
347
348 return static_cast<int>(size); // Report success
349}
350
351err_t Tcp::tlsRecvCallback(void *arg, struct altcp_pcb *conn, struct pbuf *p, err_t err)
352{
353 auto *self = static_cast<Tcp *>(arg);
354 if (!self)
355 return ERR_VAL;
356
357 if (!p)
358 {
359 // Remote closed connection
360 if (self->recv_buffer)
361 {
362 pbuf_free(self->recv_buffer);
363 self->recv_buffer = nullptr;
364 }
365 self->recv_offset = 0;
366 return ERR_OK;
367 }
368
369 // Drop if already have a pbuf (single-buffer policy for now)
370 if (self->recv_buffer)
371 {
372 pbuf_free(p); // Drop additional pbuf to prevent overflow
373 return ERR_OK;
374 }
375
376 self->recv_buffer = p;
377 self->recv_offset = 0;
378
379 // Notify any task waiting in recv()
380 if (self->waiting_task)
381 {
382 xTaskNotifyGiveIndexed(self->waiting_task, NotifyRecv);
383 self->waiting_task = nullptr;
384 }
385
386 return ERR_OK;
387}
388
389int Tcp::recv(char *buffer, size_t size, uint32_t timeout_ms)
390{
391 if (!use_tls)
392 {
393 TRACE("Plain recv: %zu bytes\n", size);
394 int received = lwip_recv(sockfd, buffer, size, 0);
395 if (received < 0)
396 {
397 TRACE("Plain recv error: %d, %s\n", errno, strerror(errno));
398 return received;
399 }
400 return received;
401 }
402
403#if PICO_TCP_ENABLE_TLS
404 if (!tls_pcb || !buffer || size == 0)
405 {
406 return -1;
407 }
408
409 // If no data available yet, block and wait for notify
410 if (!recv_buffer)
411 {
412 waiting_task = xTaskGetCurrentTaskHandle();
413 BaseType_t result = ulTaskNotifyTakeIndexed(NotifyRecv, pdTRUE, pdMS_TO_TICKS(timeout_ms));
414 if (result == 0 || !recv_buffer)
415 return 0; // timeout or nothing delivered
416 }
417
418 if (!recv_buffer)
419 {
420 return 0; // Nothing was delivered even after wakeup
421 }
422
423 // Copy as much as we can from the pbuf to the buffer
424 size_t available = recv_buffer->tot_len - recv_offset;
425 size_t to_copy = (size < available) ? size : available;
426 pbuf_copy_partial(recv_buffer, buffer, to_copy, recv_offset);
427 recv_offset += to_copy;
428
429 // If we've consumed the full pbuf, release it
430 if (recv_offset >= recv_buffer->tot_len)
431 {
432 pbuf_free(recv_buffer);
433 recv_buffer = nullptr;
434 recv_offset = 0;
435 }
436 return static_cast<int>(to_copy);
437#else
438 printf("[Tcp] TLS not enabled in this build\n");
439 return -1;
440#endif
441}
442
444{
445 int result = 0;
446 #if PICO_TCP_ENABLE_TLS
447 if (use_tls && tls_pcb)
448 {
449 altcp_close(tls_pcb);
450 tls_pcb = nullptr;
451 }
452 else
453 #endif
454 if (sockfd >= 0)
455 {
456 result = lwip_close(sockfd);
457 sockfd = -1;
458 }
459 recv_offset = 0;
460 waiting_task = nullptr;
461 if (recv_buffer)
462 {
463 pbuf_free(recv_buffer);
464 recv_buffer = nullptr;
465 }
466
467 connected = false;
468 return result;
469}
470
471err_t Tcp::acceptCallback(void *arg, struct altcp_pcb *new_conn, err_t err)
472{
473 auto *self = static_cast<Tcp *>(arg);
474
475 if (err != ERR_OK || !new_conn || !self)
476 {
477 return ERR_VAL;
478 }
479
480 self->pending_client = new_conn;
481
482 if (self->waiting_task)
483 {
484 xTaskNotifyGiveIndexed(self->waiting_task, NotifyAccept); // Notify waiting task
485 self->waiting_task = nullptr;
486 }
487
488 return ERR_OK;
489}
490
492{
493 if (use_tls)
494 {
495 #if PICO_TCP_ENABLE_TLS
496 pending_client = nullptr;
497 waiting_task = xTaskGetCurrentTaskHandle();
498 ulTaskNotifyTakeIndexed(NotifyAccept, pdTRUE, portMAX_DELAY);
499 waiting_task = nullptr;
500
501 if (pending_client)
502 {
503 Tcp *client = new Tcp();
504 client->tls_pcb = pending_client;
505 client->use_tls = true;
506 client->connected = true;
507 pending_client = nullptr;
508 return client;
509 }
510 return nullptr;
511 #else
512 printf("[Tcp] TLS not enabled in this build\n");
513 return nullptr;
514 #endif
515 }
516
517 // For plain TCP, use lwIP accept directly
518 struct sockaddr_in client_addr{};
519 socklen_t addr_len = sizeof(client_addr);
520
521 int client_fd = lwip_accept(sockfd, reinterpret_cast<struct sockaddr *>(&client_addr), &addr_len);
522 TRACE("[Tcp] Accept returned new socket: %d\n", client_fd);
523
524 if (client_fd >= 0)
525 {
526 // Set receive timeout
527 struct timeval recv_timeout = {.tv_sec = 0, .tv_usec = 100000};
528 lwip_setsockopt(client_fd, SOL_SOCKET, SO_RCVTIMEO, &recv_timeout, sizeof(recv_timeout));
529
530 // Optional: Force-close on disconnect (RST on close)
531 struct linger so_linger = {.l_onoff = 1, .l_linger = 0};
532 lwip_setsockopt(client_fd, SOL_SOCKET, SO_LINGER, &so_linger, sizeof(so_linger));
533
534 Tcp *client = new Tcp(client_fd);
535 return client;
536 }
537
538 printf("[Tcp] Accept timeout, no client.\n");
539 return nullptr;
540}
541
542bool Tcp::bindAndListen(int port)
543{
544 #if PICO_TCP_ENABLE_TLS
546 #else
547 return bindAndListenPlain(port);
548 #endif
549}
550
551#include <fcntl.h> // needed for O_NONBLOCK
553{
554 sockfd = lwip_socket(AF_INET, SOCK_STREAM, 0);
555 if (sockfd < 0)
556 {
557 printf("[Tcp] Failed to create socket\n");
558 return false;
559 }
560
561 int opt = 1;
562 int flags = fcntl(sockfd, F_GETFL, 0);
563 fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
564
565 struct sockaddr_in addr = {};
566 addr.sin_family = AF_INET;
567 addr.sin_port = lwip_htons(port);
568 addr.sin_addr.s_addr = PP_HTONL(INADDR_ANY);
569
570 if (lwip_bind(sockfd, reinterpret_cast<struct sockaddr *>(&addr), sizeof(addr)) < 0)
571 {
572 printf("[Tcp] Failed to bind socket\n");
573 lwip_close(sockfd);
574 sockfd = -1;
575 return false;
576 }
577
578 if (lwip_listen(sockfd, 10) < 0)
579 {
580 printf("[Tcp] Failed to listen on socket\n");
581 lwip_close(sockfd);
582 sockfd = -1;
583 return false;
584 }
585
586 TRACE("[Tcp] Listening on port: %d, (socket: %d)\n", port, sockfd);
587
588 use_tls = false;
589 connected = true;
590 return true;
591}
592#if PICO_TCP_ENABLE_TLS
593bool Tcp::bindAndListenTls(int port)
594{
596 {
597 printf("[Tcp] TLS config not set for server\n");
598 return false;
599 }
600
601 tls_pcb = altcp_tls_new(server_tls_config, IPADDR_TYPE_V4);
602 if (!tls_pcb)
603 {
604 printf("[Tcp] Failed to create TLS PCB\n");
605 return false;
606 }
607
608 struct sockaddr_in addr = {};
609 addr.sin_family = AF_INET;
610 addr.sin_port = lwip_htons(port);
611 addr.sin_addr.s_addr = PP_HTONL(INADDR_ANY);
612
613 err_t err = altcp_bind(tls_pcb, reinterpret_cast<ip_addr_t *>(&addr.sin_addr), port);
614 if (err != ERR_OK)
615 {
616 printf("[Tcp] altcp_bind failed: %d\n", err);
617 altcp_close(tls_pcb);
618 tls_pcb = nullptr;
619 return false;
620 }
621
622 altcp_accept(tls_pcb, Tcp::acceptCallback);
623 use_tls = true;
624 connected = true;
625 return true;
626}
627#endif
Macro-based debug trace system with optional SD file logging.
#define TRACE_INIT(MODULE_NAME)
Declare trace usage in a source file for a given module.
Definition DebugTrace.h:170
#define TRACE(...)
Default trace (INFO level).
Definition DebugTrace.h:187
General-purpose TCP socket abstraction with optional TLS support for both client and server use.
@ NotifyAccept
Definition Tcp.h:32
@ NotifyConnect
Definition Tcp.h:33
@ NotifyRecv
Definition Tcp.h:31
General-purpose TCP socket wrapper with optional TLS support via mbedTLS (altcp).
Definition Tcp.h:39
int connectResult
Definition Tcp.h:146
Tcp()
Definition Tcp.cpp:24
bool connected
Definition Tcp.h:143
Tcp * accept()
Accept a new incoming connection (for server use).
Definition Tcp.cpp:491
int sockfd
Definition Tcp.h:142
bool connect(const char *host, int port, bool useTls=false)
Connect to a remote host.
Definition Tcp.cpp:116
static err_t tlsRecvCallback(void *arg, struct altcp_pcb *conn, struct pbuf *p, err_t err)
Definition Tcp.cpp:351
~Tcp()
Definition Tcp.cpp:44
bool bindAndListenTls(int port)
char hostname[64]
Hostname for TLS connections.
Definition Tcp.h:170
TaskHandle_t waiting_task
Task handle for async operations.
Definition Tcp.h:167
bool use_tls
Definition Tcp.h:144
void setRootCACertificate(const std::string &pem)
Set the Root CA certificate to be used for client TLS connections (PEM format).
std::string root_ca_cert
Definition Tcp.h:148
std::string getPeerIp() const
Definition Tcp.cpp:79
static err_t acceptCallback(void *arg, struct altcp_pcb *new_conn, err_t err)
Definition Tcp.cpp:471
struct altcp_tls_config * server_tls_config
Definition Tcp.h:161
bool connectTls(const ip_addr_t &ip, int port)
struct pbuf * recv_buffer
Buffer for TLS receive.
Definition Tcp.h:155
bool connectPlain(const ip_addr_t &ip, int port)
Definition Tcp.cpp:133
size_t recv_offset
Definition Tcp.h:156
int send(const char *buffer, size_t size)
Send data over the connection.
Definition Tcp.cpp:279
Tcp & operator=(const Tcp &)=delete
void setServerTlsConfig(const std::string &certPem, const std::string &keyPem)
Set the certificate and key to use for server-side TLS (PEM format).
bool bindAndListenPlain(int port)
Definition Tcp.cpp:552
TaskHandle_t connectingTask
Task handle for async operations.
Definition Tcp.h:166
int close()
Close the connection and free resources.
Definition Tcp.cpp:443
bool bindAndListen(int port)
Bind and listen on a port for incoming connections (for server use).
Definition Tcp.cpp:542
int recv(char *buffer, size_t size, uint32_t timeout_ms)
Receive data from the connection.
Definition Tcp.cpp:389
std::string server_tls_cert
Definition Tcp.h:159
altcp_pcb * pending_client
For TLS: set by acceptCallback.
Definition Tcp.h:168
std::string server_tls_key
Definition Tcp.h:160
Delegates to user or system configuration.
#define HTTP_BUFFER_SIZE
Size of the HTTP buffer for request/response data.
#define STREAM_SEND_DELAY_MS
bool resolveHostnameBlocking(const char *hostname, ip_addr_t *result, uint32_t timeout_ms=5000)
Blocking DNS resolution using lwIP.
void warning(const std::string &msg)
Definition utility.cpp:215
System utilities for diagnostics, memory, stack usage, and tracing.