#include "ftp_manager.h" #include "esp_log.h" #include "lwip/sockets.h" #include "lwip/netdb.h" #include #include static const char *TAG = "FTP_MANAGER"; /* Receive one line terminated by \r\n into buffer (null-terminated) */ static int ftp_recv_line(int sock, char *buf, size_t len) { size_t pos = 0; while (pos < len - 1) { char c; int r = recv(sock, &c, 1, 0); if (r <= 0) { break; } buf[pos++] = c; if (pos >= 2 && buf[pos-2] == '\r' && buf[pos-1] == '\n') { break; } } buf[pos] = '\0'; return (int)pos; } /* Send a command followed by \r\n */ static int ftp_send_cmd(int sock, const char *cmd) { char line[256]; int len = snprintf(line, sizeof(line), "%s\r\n", cmd); if (len <= 0) return -1; return send(sock, line, len, 0); } /* Resolve hostname or dotted IP and connect */ static int ftp_connect_control(const char *host, int port) { char port_str[8]; snprintf(port_str, sizeof(port_str), "%d", port); struct addrinfo hints = {0}; hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; struct addrinfo *res = NULL; int err = getaddrinfo(host, port_str, &hints, &res); if (err != 0 || res == NULL) { ESP_LOGE(TAG, "getaddrinfo failed for host '%s': %d", host, err); return -1; } int sock = -1; struct addrinfo *p; for (p = res; p != NULL; p = p->ai_next) { sock = socket(p->ai_family, p->ai_socktype, 0); if (sock < 0) continue; if (connect(sock, p->ai_addr, p->ai_addrlen) == 0) { break; // success } ESP_LOGW(TAG, "connect() failed to one addr, retrying next"); close(sock); sock = -1; } freeaddrinfo(res); if (sock < 0) { ESP_LOGE(TAG, "Could not connect to FTP server %s:%d", host, port); return -1; } ESP_LOGI(TAG, "Connected to FTP server %s:%d", host, port); return sock; } ftp_err_t ftp_manager_download(const ftp_config_t *cfg, ftp_progress_cb_t cb) { if (!cfg || !cfg->server || !cfg->remote_path || !cfg->local_path) { ESP_LOGE(TAG, "Invalid FTP config"); return FTP_ERR_CONNECT; } ESP_LOGI(TAG, "FTP download start: server=%s port=%d remote=%s local=%s", cfg->server, cfg->port, cfg->remote_path, cfg->local_path); int ctrl_sock = ftp_connect_control(cfg->server, cfg->port); if (ctrl_sock < 0) { return FTP_ERR_CONNECT; } char line[256]; /* Read welcome line */ ftp_recv_line(ctrl_sock, line, sizeof(line)); ESP_LOGI(TAG, "FTP: %s", line); /* USER */ char cmd[128]; snprintf(cmd, sizeof(cmd), "USER %s", cfg->username ? cfg->username : "anonymous"); ftp_send_cmd(ctrl_sock, cmd); ftp_recv_line(ctrl_sock, line, sizeof(line)); ESP_LOGI(TAG, "FTP: %s", line); /* PASS */ snprintf(cmd, sizeof(cmd), "PASS %s", cfg->password ? cfg->password : "anonymous"); ftp_send_cmd(ctrl_sock, cmd); ftp_recv_line(ctrl_sock, line, sizeof(line)); ESP_LOGI(TAG, "FTP: %s", line); /* VERY SIMPLE IMPLEMENTATION: use passive mode for data connection */ /* PASV */ ftp_send_cmd(ctrl_sock, "PASV"); ftp_recv_line(ctrl_sock, line, sizeof(line)); ESP_LOGI(TAG, "FTP: %s", line); /* Parse "(h1,h2,h3,h4,p1,p2)" from 227 response */ int h1,h2,h3,h4,p1,p2; char *p = strchr(line, '('); if (!p || sscanf(p+1, "%d,%d,%d,%d,%d,%d", &h1,&h2,&h3,&h4,&p1,&p2) != 6) { ESP_LOGE(TAG, "Failed to parse PASV response"); close(ctrl_sock); return FTP_ERR_DOWNLOAD; } char data_host[32]; snprintf(data_host, sizeof(data_host), "%d.%d.%d.%d", h1,h2,h3,h4); int data_port = p1*256 + p2; ESP_LOGI(TAG, "FTP data connection to %s:%d", data_host, data_port); int data_sock = ftp_connect_control(data_host, data_port); if (data_sock < 0) { close(ctrl_sock); return FTP_ERR_CONNECT; } /* RETR */ snprintf(cmd, sizeof(cmd), "RETR %s", cfg->remote_path); ftp_send_cmd(ctrl_sock, cmd); ftp_recv_line(ctrl_sock, line, sizeof(line)); ESP_LOGI(TAG, "FTP: %s", line); /* Now receive file data on data_sock */ FILE *fp = fopen(cfg->local_path, "wb"); if (!fp) { ESP_LOGE(TAG, "Failed to open local file %s", cfg->local_path); close(data_sock); close(ctrl_sock); return FTP_ERR_FILE; } uint8_t buf[2048]; size_t total = 0; while (1) { int r = recv(data_sock, buf, sizeof(buf), 0); if (r < 0) { ESP_LOGE(TAG, "Error reading data socket"); fclose(fp); close(data_sock); close(ctrl_sock); return FTP_ERR_DOWNLOAD; } else if (r == 0) { break; // done } fwrite(buf, 1, r, fp); total += r; if (cb) cb(total, 0); } fclose(fp); close(data_sock); /* Read final transfer response */ ftp_recv_line(ctrl_sock, line, sizeof(line)); ESP_LOGI(TAG, "FTP: %s", line); /* QUIT */ ftp_send_cmd(ctrl_sock, "QUIT"); ftp_recv_line(ctrl_sock, line, sizeof(line)); ESP_LOGI(TAG, "FTP: %s", line); close(ctrl_sock); ESP_LOGI(TAG, "FTP download complete, total bytes=%u", (unsigned)total); return FTP_OK; }