| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752 | /* * lwftp.c : a lightweight FTP client using raw API of LWIP * * Copyright (c) 2014 GEZEDO * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, *    this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, *    this list of conditions and the following disclaimer in the documentation *    and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products *    derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. * * Author: Laurent GONZALEZ <lwip@gezedo.com> * */#pragma GCC diagnostic error "-Wall"#pragma GCC diagnostic error "-Wextra"#include "common_config.h"#ifdef FTP_ENABLE#include <string.h>#include <stdbool.h>#include "ftp.h"#include "lwip/tcp.h"#include "sockets.h"#include "stm32f4xx.h"#include "spi_flash.h"#include "web_params_api.h"#include "FreeRTOS.h"#include "task.h"#include "hal.h"#include "syslog.h"#include "log.h"/** Enable debugging for LWFTP */#ifndef LWFTP_DEBUG#define LWFTP_DEBUG   LWIP_DBG_ON#endif#define LWFTP_TRACE   (LWFTP_DEBUG|LWIP_DBG_TRACE)#define LWFTP_WARNING (LWFTP_DEBUG|LWIP_DBG_LEVEL_WARNING)#define LWFTP_SERIOUS (LWFTP_DEBUG|LWIP_DBG_LEVEL_SERIOUS)#define LWFTP_SEVERE  (LWFTP_DEBUG|LWIP_DBG_LEVEL_SEVERE)#define PTRNLEN(s)  s,(sizeof(s)-1)#define min(x,y) ((x)<(y)?(x):(y))lwftp_session_t ftpcfg;static unsigned received_bytes_count = 0;static char **error_ptr;/** Close control or data pcb * @param pointer to lwftp session data */static err_t lwftp_pcb_close(struct tcp_pcb *tpcb){  err_t error;  tcp_err(tpcb, NULL);  tcp_recv(tpcb, NULL);  tcp_sent(tpcb, NULL);  error = tcp_close(tpcb);  if ( error != ERR_OK ) {    LWIP_DEBUGF(LWFTP_SEVERE, ("lwftp:pcb close failure, not implemented\n"));  }  return ERR_OK;}/** Send data * @param pointer to lwftp session data * @param pointer to PCB * @param number of bytes sent */static err_t lwftp_send_next_data(lwftp_session_t *s){  const char *data;  int len = 0;  err_t error = ERR_OK;  if (s->data_source) {    len = s->data_source(s->data_handle, &data, s->data_pcb->mss);    if (len) {      LWIP_DEBUGF(LWFTP_TRACE, ("lwftp:sending %d bytes of data\n",len));      error = tcp_write(s->data_pcb, data, len, 0);      if (error!=ERR_OK) {        LWIP_DEBUGF(LWFTP_SEVERE, ("lwftp:write failure (%s), not implemented\n",lwip_strerr(error)));      }    }  }  if (!len) {    LWIP_DEBUGF(LWFTP_TRACE, ("lwftp:end of file\n"));    lwftp_pcb_close(s->data_pcb);    s->data_pcb = NULL;  }  return ERR_OK;}/** Handle data connection incoming data * @param pointer to lwftp session data * @param pointer to PCB * @param pointer to incoming pbuf * @param state of incoming process */static err_t lwftp_data_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err){  (void)err;  lwftp_session_t *s = (lwftp_session_t*)arg;  if (p) {    if (s->data_sink) {      struct pbuf *q;      for (q=p; q; q=q->next) {        s->data_sink(s->data_handle, q->payload, q->len);      }    } else {      LWIP_DEBUGF(LWFTP_SEVERE, ("lwftp: sinking %d bytes\n",p->tot_len));    }    tcp_recved(tpcb, p->tot_len);    pbuf_free(p);  } else {    if (s->data_sink) {      s->data_sink(s->data_handle, NULL, 0);    }    // NULL pbuf shall lead to close the pcb.    if (s->data_pcb) {      lwftp_pcb_close(s->data_pcb);      s->data_pcb = NULL;    }  }  return ERR_OK;}/** Handle data connection acknowledge of sent data * @param pointer to lwftp session data * @param pointer to PCB * @param number of bytes sent */static err_t lwftp_data_sent(void *arg, struct tcp_pcb *tpcb, u16_t len){  (void)tpcb;  lwftp_session_t *s = (lwftp_session_t*)arg;  if ( s->data_source ) {    s->data_source(s->data_handle, NULL, len);  }  return lwftp_send_next_data(s);}/** Handle data connection error * @param pointer to lwftp session data * @param state of connection */static void lwftp_data_err(void *arg, err_t err){  LWIP_UNUSED_ARG(err);  if (arg != NULL) {    lwftp_session_t *s = (lwftp_session_t*)arg;    LWIP_DEBUGF(LWFTP_WARNING, ("lwftp:failed/error connecting for data to server (%s)\n",lwip_strerr(err)));    s->control_state = LWFTP_QUIT;  // gracefully exit on data error    s->data_pcb = NULL; // No need to de-allocate PCB  }}/** Process newly connected PCB * @param pointer to lwftp session data * @param pointer to PCB * @param state of connection */static err_t lwftp_data_connected(void *arg, struct tcp_pcb *tpcb, err_t err){  (void)tpcb;  lwftp_session_t *s = (lwftp_session_t*)arg;  if ( err == ERR_OK ) {    LWIP_DEBUGF(LWFTP_TRACE, ("lwftp:connected for data to server\n"));    s->data_state = LWFTP_CONNECTED;  } else {    LWIP_DEBUGF(LWFTP_WARNING, ("lwftp:err in data_connected (%s)\n",lwip_strerr(err)));  }  return err;}/** Open data connection for passive transfer * @param pointer to lwftp session data * @param pointer to incoming PASV response */static err_t lwftp_data_open(lwftp_session_t *s, struct pbuf *p){  err_t error;  char *ptr;  ip_addr_t data_server;  u16_t data_port;  // Find server connection parameter  ptr = strchr(p->payload, '(');  if (!ptr) return ERR_BUF;  do {    unsigned int a = strtoul(ptr+1,&ptr,10);    unsigned int b = strtoul(ptr+1,&ptr,10);    unsigned int c = strtoul(ptr+1,&ptr,10);    unsigned int d = strtoul(ptr+1,&ptr,10);    IP4_ADDR(&data_server,a,b,c,d);  } while(0);  data_port  = strtoul(ptr+1,&ptr,10) << 8;  data_port |= strtoul(ptr+1,&ptr,10) & 255;  if (*ptr!=')') return ERR_BUF;  // Open data session  tcp_arg(s->data_pcb, s);  tcp_err(s->data_pcb, lwftp_data_err);  tcp_recv(s->data_pcb, lwftp_data_recv);  tcp_sent(s->data_pcb, lwftp_data_sent);  error = tcp_connect(s->data_pcb, &data_server, data_port, lwftp_data_connected);  return error;}/** Send a message to control connection * @param pointer to lwftp session data * @param pointer to message string */static err_t lwftp_send_msg(lwftp_session_t *s, const char* msg, size_t len){  err_t error;  LWIP_DEBUGF(LWFTP_TRACE,("lwftp:sending %s",msg));  error = tcp_write(s->control_pcb, msg, len, 0);  if ( error != ERR_OK ) {      LWIP_DEBUGF(LWFTP_WARNING, ("lwftp:cannot write (%s)\n",lwip_strerr(error)));  }  return error;}/** Close control connection * @param pointer to lwftp session data * @param result to pass to callback fn (if called) */static void lwftp_control_close(lwftp_session_t *s, int result){  if (s->control_pcb) {    lwftp_pcb_close(s->control_pcb);    s->control_pcb = NULL;  }  s->control_state = LWFTP_CLOSED;  if ( (result >= 0) && s->done_fn ) {    s->done_fn(s->data_handle, result);  }}static void set_timeout(struct tcp_pcb *tpcb, unsigned seconds){  struct timeval timeout;  timeout.tv_sec = seconds;  timeout.tv_usec = 0;  setsockopt(tpcb, SOL_SOCKET, SO_RCVTIMEO, (const char *)&timeout, sizeof(timeout));}/** Main client state machine * @param pointer to lwftp session data * @param pointer to PCB * @param pointer to incoming data */static void lwftp_control_process(lwftp_session_t *s, struct tcp_pcb *tpcb, struct pbuf *p){  (void)tpcb;  unsigned response = 0;  int result = LWFTP_RESULT_ERR_SRVR_RESP;  // Try to get response number  if (p) {    response = strtoul(p->payload, NULL, 10);    LWIP_DEBUGF(LWFTP_TRACE, ("lwftp:got response %d\n",response));  }  switch (s->control_state) {    case LWFTP_CONNECTED:      if (response>0) {        if (response==220) {          lwftp_send_msg(s, PTRNLEN("USER "));          lwftp_send_msg(s, s->settings->user, strlen(s->settings->user));          lwftp_send_msg(s, PTRNLEN("\r\n"));          s->control_state = LWFTP_USER_SENT;        } else {          s->error = "The server doesn't like us";          s->control_state = LWFTP_QUIT;        }      } else {        s->error = "The server doesn't greet us";        s->control_state = LWFTP_QUIT;      }      break;    case LWFTP_USER_SENT:      if (response>0) {        if (response==331) {          lwftp_send_msg(s, PTRNLEN("PASS "));          lwftp_send_msg(s, s->settings->pass, strlen(s->settings->pass));          lwftp_send_msg(s, PTRNLEN("\r\n"));          s->control_state = LWFTP_PASS_SENT;        } else if (response==230) {          goto anonymous;        } else {          s->error = "Wrong user name";          s->control_state = LWFTP_QUIT;        }      }      break;    case LWFTP_PASS_SENT:      if (response>0) {        if (response==230) {anonymous:          lwftp_send_msg(s, PTRNLEN("TYPE I\r\n"));          s->control_state = LWFTP_TYPE_SENT;        } else {          s->error = "Wrong password";          s->control_state = LWFTP_QUIT;        }      }      break;    case LWFTP_TYPE_SENT:      if (response>0) {        if (response==200) {          lwftp_send_msg(s, PTRNLEN("PASV\r\n"));          s->control_state = LWFTP_PASV_SENT;        } else {          s->error = "The server doesn't support binary files";          s->control_state = LWFTP_QUIT;        }      }      break;    case LWFTP_PASV_SENT:      if (response>0) {        if (response==227) {          lwftp_data_open(s,p);          switch (s->target_state) {            case LWFTP_STOR_SENT:              lwftp_send_msg(s, PTRNLEN("STOR "));              break;            case LWFTP_RETR_SENT:              lwftp_send_msg(s, PTRNLEN("RETR "));              break;            default:              //LOG_ERROR("Unexpected internal state");              s->target_state = LWFTP_QUIT;            }          lwftp_send_msg(s, s->settings->remote_path, strlen(s->settings->remote_path));          lwftp_send_msg(s, PTRNLEN("\r\n"));          s->control_state = s->target_state;        } else {          s->error = "The server doesn't support PASV";          s->control_state = LWFTP_QUIT;        }      }      break;    case LWFTP_RETR_SENT:      if (response>0) {        if (response==150) {          s->control_state = LWFTP_XFERING;          set_timeout(s->control_pcb, 0);        } else if (response==550) {            s->control_state = LWFTP_QUIT;            s->error = "Failed to open file";            LWIP_DEBUGF(LWFTP_WARNING, ("lwftp: %s '%s'\n", s->error, s->settings->remote_path));        }        else {          s->error = "The server doesn't start sending the file";          s->control_state = LWFTP_QUIT;          LWIP_DEBUGF(LWFTP_WARNING, ("lwftp:expected 150, received %d\n",response));        }      }      break;    case LWFTP_STOR_SENT:      if (response>0) {        if (response==150) {          s->control_state = LWFTP_XFERING;          lwftp_data_sent(s,NULL,0);        } else {          s->control_state = LWFTP_QUIT;        }      }      break;    case LWFTP_XFERING:      if (response>0) {        if (response==226) {          s->data_state = LWFTP_XFERING;  // signal transfer OK        } else {          LWIP_DEBUGF(LWFTP_WARNING, ("lwftp:expected 226, received %d\n",response));        }        // Quit anyway after any message received during STOR        s->control_state = LWFTP_QUIT;      }      break;    case LWFTP_QUIT_SENT:      if (response>0) {        if (response==221) {          if (s->data_state == LWFTP_XFERING){ // check for transfer OK            result = LWFTP_RESULT_OK;          }        } else {          LWIP_DEBUGF(LWFTP_WARNING, ("lwftp:expected 221, received %d\n",response));        }        s->control_state = LWFTP_CLOSING;      }      break;    default:      LWIP_DEBUGF(LWFTP_SEVERE, ("lwftp:unhandled state (%d)\n",s->control_state));  }  // Free receiving pbuf if any  if (p) {    pbuf_free(p);  }  // Handle second step in state machine  switch ( s->control_state ) {    case LWFTP_QUIT:      lwftp_send_msg(s, PTRNLEN("QUIT\r\n"));      s->control_state = LWFTP_QUIT_SENT;      break;    case LWFTP_CLOSING:      // this function frees s, no use of s is allowed after      return lwftp_control_close(s, result);    default:;  }}/** Handle control connection incoming data * @param pointer to lwftp session data * @param pointer to PCB * @param pointer to incoming pbuf * @param state of incoming process */static err_t lwftp_control_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err){  lwftp_session_t *s = (lwftp_session_t*)arg;  if ( err == ERR_OK ) {    if (p) {      tcp_recved(tpcb, p->tot_len);      lwftp_control_process(s, tpcb, p);    } else {      LWIP_DEBUGF(LWFTP_WARNING, ("lwftp:connection closed by remote host\n"));      lwftp_control_close(s, LWFTP_RESULT_ERR_CLOSED);    }  } else {    LWIP_DEBUGF(LWFTP_SERIOUS, ("lwftp:failed to receive (%s)\n",lwip_strerr(err)));    lwftp_control_close(s, LWFTP_RESULT_ERR_UNKNOWN);  }  return err;}/** Handle control connection acknowledge of sent data * @param pointer to lwftp session data * @param pointer to PCB * @param number of bytes sent */static err_t lwftp_control_sent(void *arg, struct tcp_pcb *tpcb, u16_t len){  (void)arg;  (void)tpcb;  (void)len;  LWIP_DEBUGF(LWFTP_TRACE, ("lwftp:successfully sent %d bytes\n",len));  return ERR_OK;}/** Handle control connection error * @param pointer to lwftp session data * @param state of connection */static void lwftp_control_err(void *arg, err_t err){  LWIP_UNUSED_ARG(err);  if (arg != NULL) {    lwftp_session_t *s = (lwftp_session_t*)arg;    int result;    if( s->control_state == LWFTP_CLOSED ) {      s->error = "failed to connect to server";      LWIP_DEBUGF(LWFTP_WARNING, ("lwftp: %s (%s)\n",lwip_strerr(err)));      result = LWFTP_RESULT_ERR_CONNECT;    } else {      s->error = "connection closed by remote host";      LWIP_DEBUGF(LWFTP_WARNING, ("lwftp:connection closed by remote host\n"));      result = LWFTP_RESULT_ERR_CLOSED;    }    s->control_pcb = NULL; // No need to de-allocate PCB    lwftp_control_close(s, result);  }}/** Process newly connected PCB * @param pointer to lwftp session data * @param pointer to PCB * @param state of connection */static err_t lwftp_control_connected(void *arg, struct tcp_pcb *tpcb, err_t err){  (void)tpcb;  lwftp_session_t *s = (lwftp_session_t*)arg;  if ( err == ERR_OK ) {    LWIP_DEBUGF(LWFTP_TRACE, ("lwftp:connected to server\n"));      s->control_state = LWFTP_CONNECTED;  } else {    LWIP_DEBUGF(LWFTP_WARNING, ("lwftp:err in control_connected (%s)\n",lwip_strerr(err)));  }  return err;}/** Store data to a remote file * @param Session structure */err_t lwftp_store(lwftp_session_t *s){  err_t error;  // Check user supplied data  if ( (s->control_state!=LWFTP_CLOSED) ||       !s->settings->remote_path ||       s->control_pcb ||       s->data_pcb ||       !s->settings->user ||       !s->settings->pass )  {    LWIP_DEBUGF(LWFTP_WARNING, ("lwftp:invalid session data\n"));    return ERR_ARG;  }  // Get sessions pcb  s->control_pcb = tcp_new();  if (!s->control_pcb) {    LWIP_DEBUGF(LWFTP_SERIOUS, ("lwftp:cannot alloc control_pcb (low memory?)\n"));    error = ERR_MEM;    goto exit;  }  s->data_pcb = tcp_new();  if (!s->data_pcb) {    LWIP_DEBUGF(LWFTP_SERIOUS, ("lwftp:cannot alloc data_pcb (low memory?)\n"));    error = ERR_MEM;    goto close_pcb;  }  set_timeout(s->control_pcb, 5);  set_timeout(s->data_pcb, 5);  // Open control session  tcp_arg(s->control_pcb, s);  tcp_err(s->control_pcb, lwftp_control_err);  tcp_recv(s->control_pcb, lwftp_control_recv);  tcp_sent(s->control_pcb, lwftp_control_sent);  error = tcp_connect(s->control_pcb, &s->settings->server_ip, s->settings->server_port, lwftp_control_connected);  if ( error == ERR_OK ) goto exit;  LWIP_DEBUGF(LWFTP_SERIOUS, ("lwftp:cannot connect control_pcb (%s)\n", lwip_strerr(error)));close_pcb:  // Release pcbs in case of failure  lwftp_control_close(s, -1);exit:  return error;}err_t lwftp_retr(lwftp_session_t *s){  s->target_state = LWFTP_RETR_SENT;  err_t error;  // Check user supplied data  if (!s->settings->remote_path) {    s->error = "empty remote path";    return ERR_ARG;  }  if (!s->settings->user) {    s->error = "empty user name";    return ERR_ARG;  }  if (!s->settings->pass) {    s->error = "empty password";    return ERR_ARG;  }  if (s->control_state != LWFTP_CLOSED || s->control_pcb) {    // previous connection is incomplete    lwftp_control_close(s, -1);  }  if (s->data_pcb) {    lwftp_pcb_close(s->data_pcb);  }  // Get sessions pcb  s->control_pcb = tcp_new();  if (!s->control_pcb) {    s->error = "cannot alloc control_pcb (low memory?)";    error = ERR_MEM;    goto exit;  }  s->data_pcb = tcp_new();  if (!s->data_pcb) {    s->error = "cannot alloc data_pcb (low memory?)";    error = ERR_MEM;    goto close_pcb;  }  // Open control session  tcp_arg(s->control_pcb, s);  tcp_err(s->control_pcb, lwftp_control_err);  tcp_recv(s->control_pcb, lwftp_control_recv);  tcp_sent(s->control_pcb, lwftp_control_sent);  error = tcp_connect(s->control_pcb, &s->settings->server_ip, s->settings->server_port, lwftp_control_connected);  if ( error == ERR_OK ) goto exit;  LWIP_DEBUGF(LWFTP_SERIOUS, ("lwftp:cannot connect control_pcb (%s)\n", lwip_strerr(error)));  s->error = "cannot connet control_pcb";close_pcb:  // Release pcbs in case of failure  lwftp_control_close(s, -1);exit:  return error;}//static void ftp_retr_callback(void *arg, int result)//{//  lwftp_session_t *s = (lwftp_session_t *)arg;////  if (result != LWFTP_RESULT_OK) {//      //LOG_ERROR("retr failed (%d)", result);//      return lwftp_close(s);//  }//  lwftp_close(s);//}static bool validate_spif_firmware(uint32_t fw_len){  const uint32_t spif_firmware_offset = SPI_FLASH_SECTOR_SIZE * FIRMWARE_UPDATE_SECTOR_OFFSET;  // check the firmware revision id  char rev[HW_REV_LEN];  spi_flash_read(spif_firmware_offset + HW_REV_OFFSET, &rev, sizeof(rev), 0);  if (strncmp(rev, HW_REV, sizeof(rev))) {    printf("HW revision mismatch: expected %s, found %s\r\n", HW_REV, rev);    return false;  }  // check if the firmware length is correct  if (fw_len != MAIN_FW_SIZE) {    printf("ftp: firmware size mismatch: expected %d, got %ld\r\n", MAIN_FW_SIZE, fw_len);    return false;  }  uint32_t expected_crc;  const uint32_t crc_offset = USER_FLASH_CRC_ADDRESS - USER_FLASH_FIRST_PAGE_ADDRESS;  spi_flash_read(spif_firmware_offset + crc_offset, &expected_crc, sizeof(expected_crc), 0);  // calculate CRC  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_CRC, ENABLE);  CRC->CR = 1;  for (uint32_t offset = 0; offset < crc_offset; offset += 4) {    uint32_t data;    spi_flash_read(spif_firmware_offset + offset, &data, sizeof(data), 0);    CRC->DR = data;  }  uint32_t calculated_crc = CRC->DR;  if (expected_crc != calculated_crc) {    printf("ftp: calculated CRC (%lx) doesn't match the expected CRC (%lx)!\r\n", calculated_crc, expected_crc);    return false;  }  return true;}static void erase_spif_firmware(){  printf("ftp: erasing SPI firmware partition...\r\n");  for (int sec = 0; sec < FIRMWARE_UPDATE_SECTOR_COUNT; ++sec) {    spi_flash_erase_sector(SPI_FLASH_SECTOR_SIZE * (FIRMWARE_UPDATE_SECTOR_OFFSET + sec), 0);  }}static unsigned data_sink(void *arg, const char* ptr, unsigned len){  (void)arg;  if (ptr && len) {    // we received some data from the FTP server    if (received_bytes_count == 0) {      // we need to erase the flash before we can write it      erase_spif_firmware();    }    if (received_bytes_count + len > MAIN_FW_SIZE) {      syslog_str(SYSLOG_ERROR, "Некорректный размер файла ПО");      log_event_data(LOG_UPDATE_SOFT, "Некорр. размер файла");      return 0;    }    spi_flash_write(SPI_FLASH_SECTOR_SIZE * FIRMWARE_UPDATE_SECTOR_OFFSET + received_bytes_count, ptr, len, 0);    received_bytes_count += len;  } else {    printf("ftp: the firmware is downloaded, verifying...\r\n");    uint32_t fw_size = received_bytes_count;    bool good_firmware = validate_spif_firmware(fw_size);    if (good_firmware) {      printf("ftp: the firmware is valid, rebooting...\r\n");      set_act_source(FTP_ACT);      HTTP_StartResetTask(true);    } else {      syslog_str(SYSLOG_ERROR, "Некорректный файл ПО");      log_event_data(LOG_UPDATE_SOFT, "Некорректный файл");      // erase it so the bootloader won't try to verify it every time      erase_spif_firmware();    }  }  return len;}void start_ftp_client(lwftp_session_t *s){  received_bytes_count = 0;  s->error = NULL;  error_ptr = &s->error;  s->data_sink = data_sink;  //s->done_fn = ftp_retr_callback;  lwftp_retr(s);  // FTP session will continue with the connection callback}char *get_ftp_progress(){  if (*error_ptr) {    return *error_ptr;  } else {    unsigned progress = received_bytes_count * 100 / MAIN_FW_SIZE;    static char progress_str_buf[4];    snprintf(progress_str_buf, sizeof(progress_str_buf), "%u", progress);    return progress_str_buf;  }}#endif // FTP_ENABLE
 |