esp32-s3_fota_test_wifi/components/ftp_manager/ftp_manager.c

198 lines
5.3 KiB
C

#include "ftp_manager.h"
#include "esp_log.h"
#include "lwip/sockets.h"
#include "lwip/netdb.h"
#include <string.h>
#include <stdio.h>
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;
}