/*
 * 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"

/** 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("\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("\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\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\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("\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", 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\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) {
      printf("ftp: the firmware is too big! aborting the download\r\n");
      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 {
      // 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
