Selaa lähdekoodia

ftp: the client kinda works, needs lots of love still

Sergey Alirzaev 4 vuotta sitten
vanhempi
commit
e5ec28805b

+ 1 - 0
config/board_bt6711.h

@@ -81,6 +81,7 @@ XSETTING( TELNET_t, sTelnet, SETTINGS_SetTelnetDef, PART_DEFAULT_1 ) \
 XSETTING( WHITE_LIST_t, sWhiteList[MAX_WHITE_LIST], SETTINGS_SetWhiteListDef, ALL_DEFAULT ) \
 XSETTING( WHITE_LIST_t, sWhiteListTemp[MAX_WHITE_LIST], SETTINGS_SetWhiteListDef, ALL_DEFAULT ) \
 XSETTING( uint8_t, sFlagNotification[ALL_TRAPS], SETTINGS_SetFlagNotificationDef, ALL_DEFAULT ) \
+XSETTING( FTP_Update_t,	sFTPUpdate, SETTINGS_SetFTPUpdateDef, ALL_DEFAULT ) \
 
 #define SNMP_DEV_ROOT_OID       11
 

+ 1 - 0
config/common_config.h

@@ -65,6 +65,7 @@
 #define USER_FLASH_LAST_PAGE_ADDRESS  0x08060000
 #define USER_FLASH_END_ADDRESS        0x0807FFFF
 #define USER_FLASH_CRC_ADDRESS        0x0807FFFC
+#define MAIN_FW_SIZE    USER_FLASH_END_ADDRESS - USER_FLASH_FIRST_PAGE_ADDRESS + 1
 
 #define IAP_FLASH_FIRST_PAGE_ADDRESS  0x08000000 /* Only as example see comment */
 #define IAP_FLASH_CRC_ADDRESS         0x0801FFFC

+ 0 - 1
iap/Modules/Ethernet/httpserver.c

@@ -62,7 +62,6 @@
 /* Max HTTP Etag field length */
 #define MAX_ETAG_LEN        48
 
-#define MAIN_FW_SIZE    USER_FLASH_END_ADDRESS - USER_FLASH_FIRST_PAGE_ADDRESS + 1
 #define DB_FW_SIZE      DB_CPU_FLASH_END_ADDRESS - DB_CPU_FLASH_FIRST_PAGE_ADDRESS + 1
 
 #define HTTP_RET_UPLOAD_ERROR() do {\

+ 80 - 3
modules/HTTP_Server/http_server.c

@@ -19,6 +19,7 @@
 #include "bt6710_fs/fsdata.c"
 #elif HARDWARE_BT6711
 #include "bt6711_fs/fsdata.c"
+#include "ftp.h"
 #endif
 #include "settings_api.h"
 #include "netconf.h"
@@ -70,6 +71,7 @@ static uint32_t Parse_Content_Length(char *data, uint32_t len);
 char *send_file(char *filename, char *pnonmatch,  struct fs_file *file, uint16_t *Len);
 static uint32_t Parse_Header(char *data, uint32_t len, const char *field, uint32_t flen, char *value);
 bool GetFileName(char *inStr, char *fileName, uint8_t *fileNameLen);
+static char *HTTP_FTPFWUpdate(uint32_t reqNum, char *bufIn, char *bufOut, uint16_t lenBufIn, uint16_t *lenBufOut);
 
 #define NUM_LOCKS 3
 static void *locks[NUM_LOCKS];
@@ -162,6 +164,8 @@ typedef struct {
     char *(*handler)(uint32_t reqNum, char *bufIn, char *bufOut, uint16_t lenBufIn, uint16_t *lenBufOut);
 } web_func_handler_t;
 
+#define X(x) (x), sizeof(x) - 1
+// TODO Xify all the list like ftp_fw_update!
 web_func_handler_t process_web_funcs[] = {
 #ifdef HTTP_AUTH_ENABLE
     { "POST /login.cgi",                15,   COMMON_ANSWER,        ALL_ACCESS,   HTTP_LoginPage },
@@ -186,10 +190,14 @@ web_func_handler_t process_web_funcs[] = {
     { "GET /reboot.cgi",                15,   COMMON_ANSWER,        TIME_ACCESS,  HTTP_Reboot },
     { "GET /confirm.cgi",               16,   COMMON_ANSWER,        TIME_ACCESS,  HTTP_Confirm },
     { "GET /fw_update.cgi",             18,   COMMON_ANSWER,        TIME_ACCESS,  HTTP_ConfirmBootPwd },
+#ifdef HARDWARE_BT6711
+    { X("POST /ftp_fw_update.cgi"),           COMMON_ANSWER,        TIME_ACCESS,  HTTP_FTPFWUpdate },
+#endif // HARDWARE_BT6711
     { "GET",                            3,    COMMON_ANSWER,        ALL_ACCESS,   HTTP_GetRequest },
     { "",                               0,    COMMON_ANSWER,        ALL_ACCESS,   HTTP_NoFound },
     { "", 0, 0, 0, NULL }
 };
+#undef X
 
 static bool lock_buf(struct http_state *hs, void *buf)
 {
@@ -1381,6 +1389,57 @@ char *HTTP_ConfirmBootPwd(uint32_t reqNum, char *bufIn, char *bufOut, uint16_t l
     return bufOut;
 }
 
+#ifdef HARDWARE_BT6711
+// Download the firmware via FTP, put it on the SPI flash, verify it and reboot the controller
+static char *HTTP_FTPFWUpdate(uint32_t reqNum, char *bufIn, char *bufOut, uint16_t lenBufIn, uint16_t *lenBufOut)
+{
+    (void)bufIn;
+    (void)lenBufIn;
+    (void)reqNum;
+
+    // check the logged user's permission level
+    if (seclevel == USER) {
+      return 0;
+    }
+
+    static lwftp_session_t ftpcfg;
+    FTP_Update_t *settings = &sSettings.sFTPUpdate;
+    ftpcfg.settings = settings;
+
+    char value[30];
+
+    // FIXME GetParamValue doesn't check for buffer overflow
+    // ebalbekova told we don't need any verification against malicious actors
+    GetParamValue0(bufIn, "ftp_server", value, NULL);
+    if (!ipaddr_aton(value, &settings->server_ip)) {
+        IP4_ADDR(&settings->server_ip, 192,168,0,253);
+    }
+
+    GetParamValue0(bufIn, "ftp_port", value, NULL);
+    settings->server_port = atoi(value);
+    settings->server_port = settings->server_port == 0 ? 21 : settings->server_port;
+
+    GetParamValue0(bufIn, "ftp_path", settings->remote_path, NULL);
+    GetParamValue0(bufIn, "ftp_login", settings->user, NULL);
+    if (settings->user[0] == 0) {
+        strcpy(settings->user, "anonymous");
+    }
+    GetParamValue0(bufIn, "ftp_password", settings->pass, NULL);
+    if (settings->pass[0] == 0) {
+        strcpy(settings->pass, "guest");
+    }
+
+    start_ftp_client(&ftpcfg);
+    // TODO write the settings to NVRAM
+    // TODO wait for the client to connect to the server and check whether the file exists
+
+    strcpy(bufOut, HTTP_200_OK);
+    *lenBufOut = strlen(bufOut);
+
+    return bufOut;
+}
+#endif // HARDWARE_BT6711
+
 #ifdef HTTP_AUTH_ENABLE
 
 void LoginTimerCallback(TimerHandle_t pxTimer)
@@ -1936,9 +1995,20 @@ static uint32_t Parse_Content_Length(char *data, uint32_t len)
     return size;
 }
 
+/**
+  * @brief get the value of an URL parameter supplied in a HTTP request, zero-terminated
+  */
+uint8_t GetParamValue0(char *inStr, char *paramName, char *paramValue, uint8_t *paramLen)
+{
+    uint8_t tmplen;
+    uint8_t *len = paramLen ? paramLen : &tmplen;
+    uint8_t rv = GetParamValue(inStr, paramName, paramValue, len);
+    paramValue[*len] = 0;
+    return rv;
+}
 
 /**
-  * @brief
+  * @brief get the value of an URL parameter supplied in a HTTP request
   * @retval None
   */
 uint8_t GetParamValue(char *inStr, char *paramName, char *paramValue, uint8_t *paramLen)
@@ -1974,14 +2044,21 @@ uint8_t GetParamValue(char *inStr, char *paramName, char *paramValue, uint8_t *p
         if (endValue == 0) {
             endValue = strpbrk(strPtr, " ");
         }
+        if (endValue == 0) {
+            endValue = strPtr + strlen(strPtr);
+        }
         len = endValue - beginValue - 1;
         strncpy(paramValue, beginValue + 1, len);
         *endValue = '0';
         *beginValue = '0';
-        *paramLen = len;
+        if (paramLen) {
+            *paramLen = len;
+        }
         return 1;
     } else {
-        *paramLen = 0;
+        if (paramLen) {
+            *paramLen = 0;
+        }
         return 0;
     }
 }

+ 1 - 0
modules/HTTP_Server/http_server.h

@@ -173,6 +173,7 @@ char* HTTP_NoFound(uint32_t reqNum, char *bufIn, char *bufOut, uint16_t lenBufIn
   * @brief  
   */
 uint8_t GetParamValue(char *inStr, char *paramName, char *paramValue, uint8_t *paramLen);
+uint8_t GetParamValue0(char *inStr, char *paramName, char *paramValue, uint8_t *paramLen);
 
 uint8_t GetCookieValue(char *inStr, char *paramName, char *paramValue, uint8_t *paramLen);
 

+ 2 - 2
modules/Makefile

@@ -214,7 +214,7 @@ ifeq ($(HARDWARE), bt6711)
 BUILDDIR = ../build/bt6711/$(TARGET)
 endif
 
-FW_FLASH_START = $(shell awk '/USER_FLASH_FIRST_PAGE_ADDRESS/{print $$3}' ../config/common_config.h )
+FW_FLASH_START = $(shell awk '/USER_FLASH_FIRST_PAGE_ADDRESS/{print $$3;exit}' ../config/common_config.h )
 FW_FLASH_CRC = $(shell awk '/USER_FLASH_CRC_ADDRESS/{print $$3}' ../config/common_config.h )
 
 ifeq ($(HARDWARE), bt6703)
@@ -303,7 +303,7 @@ postbuild: $(BUILDDIR)/$(TARGET).bin
 	@echo "FW CRC address: $(FW_FLASH_CRC)"
 	@echo "Copying to: $(OUTPUTDIR)/$(FW_NAME).bin"
 	@cp $(BUILDDIR)/$(TARGET).bin $(OUTPUTDIR)/$(FW_NAME).bin
-	@$(BUILDDIR)/../../tools/cortex_crc $(OUTPUTDIR)/$(FW_NAME).bin $(FW_FLASH_START) $(FW_FLASH_CRC)
+	$(BUILDDIR)/../../tools/cortex_crc $(OUTPUTDIR)/$(FW_NAME).bin $(FW_FLASH_START) $(FW_FLASH_CRC)
 	
 release:
 	@echo "FW version: $(RELEASE_VERSION)"

+ 684 - 0
modules/ftp.c

@@ -0,0 +1,684 @@
+/*
+ * 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 <string.h>
+#include <stdbool.h>
+#include "ftp.h"
+#include "lwip/tcp.h"
+#include "stm32f4xx.h"
+#include "common_config.h"
+#include "spi_flash.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))
+
+
+/** 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 {
+    // NULL pbuf shall lead to close the pcb. Close is postponed after
+    // the session state machine updates. No need to close right here.
+    // Instead we kindly tell data sink we are done
+    if (s->data_sink) {
+      s->data_sink(s->data_handle, NULL, 0);
+    }
+  }
+  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->data_pcb) {
+    lwftp_pcb_close(s->data_pcb);
+    s->data_pcb = NULL;
+  }
+  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);
+  }
+}
+
+/** 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->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->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->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->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->control_state = LWFTP_QUIT;
+        }
+      }
+      break;
+    case LWFTP_RETR_SENT:
+      if (response>0) {
+        if (response==150) {
+          s->control_state = LWFTP_XFERING;
+        } else if (response==550) {
+            s->control_state = LWFTP_QUIT;
+            //result = LWFTP_RESULT_ERR_FILENAME;
+            LWIP_DEBUGF(LWFTP_WARNING, ("lwftp: Failed to open file '%s'\n", s->settings->remote_path));
+        }
+        else {
+          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 ) {
+      LWIP_DEBUGF(LWFTP_WARNING, ("lwftp:failed to connect to server (%s)\n",lwip_strerr(err)));
+      result = LWFTP_RESULT_ERR_CONNECT;
+    } else {
+      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;
+  }
+  // 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->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;
+  }
+  // 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;
+}
+
+//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(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()
+{
+  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;
+  static unsigned offset = 0;
+
+  if (ptr) {
+    // we received some data from the FTP server
+    if (offset == 0) {
+      // we need to erase the flash before we can write it
+      erase_spif_firmware();
+    }
+
+    if (offset + len > MAIN_FW_SIZE) {
+      // the firmware won't fit into the MCU flash
+      return 0;
+    }
+    spi_flash_write(SPI_FLASH_SECTOR_SIZE * FIRMWARE_UPDATE_SECTOR_OFFSET + offset, ptr, len, 0);
+    offset += len;
+  } else {
+    // all the data is in
+    uint32_t fw_size = offset;
+    // next time it comes, overwrite the existing SPI contents
+    offset = 0;
+
+    bool good_firmware = validate_spif_firmware(fw_size);
+    if (good_firmware) {
+      // TODO reboot
+    } 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)
+{
+  s->data_sink = data_sink;
+  //s->done_fn = ftp_retr_callback;
+  lwftp_retr(s);
+  // FTP session will continue with the connection callback
+}

+ 86 - 0
modules/ftp.h

@@ -0,0 +1,86 @@
+/*
+ * lwftp.h : 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>
+ *
+ */
+
+#ifndef __IAP_FTP_H__
+#define __IAP_FTP_H__
+
+#include "lwip/opt.h"
+#include "lwip/ip.h"
+#include "settings_api.h"
+
+enum lwftp_results {
+  LWFTP_RESULT_OK=0,
+  LWFTP_RESULT_ERR_UNKNOWN,   /** Unknown error */
+  LWFTP_RESULT_ERR_CONNECT,   /** Connection to server failed */
+  LWFTP_RESULT_ERR_HOSTNAME,  /** Failed to resolve server hostname */
+  LWFTP_RESULT_ERR_CLOSED,    /** Connection unexpectedly closed by remote server */
+  LWFTP_RESULT_ERR_TIMEOUT,   /** Connection timed out (server didn't respond in time) */
+  LWFTP_RESULT_ERR_SRVR_RESP  /** Server responded with an unknown response code */
+};
+
+/** LWFTP control connection state */
+typedef enum  {
+  LWFTP_CLOSED=0,
+  LWFTP_CONNECTED,
+  LWFTP_USER_SENT,
+  LWFTP_PASS_SENT,
+  LWFTP_TYPE_SENT,
+  LWFTP_PASV_SENT,
+  LWFTP_RETR_SENT,
+  LWFTP_STOR_SENT,
+  LWFTP_XFERING,
+  LWFTP_QUIT,
+  LWFTP_QUIT_SENT,
+  LWFTP_CLOSING,
+} lwftp_state_t;
+
+/** LWFTP session structure */
+typedef struct {
+  // User interface
+  FTP_Update_t *settings;
+  void          *data_handle;
+  unsigned      (*data_source)(void*, const char**, unsigned);
+  unsigned      (*data_sink)(void*, const char*, unsigned);
+  void          (*done_fn)(void*, int);
+  // Internal data
+  lwftp_state_t   control_state;
+  lwftp_state_t   target_state;
+  lwftp_state_t   data_state;
+  struct tcp_pcb  *control_pcb;
+  struct tcp_pcb  *data_pcb;
+} lwftp_session_t;
+
+// LWFTP API
+err_t lwftp_store(lwftp_session_t *s);
+void start_ftp_client(lwftp_session_t *s);
+
+#endif

+ 2 - 6
modules/log/log.c

@@ -20,10 +20,6 @@ static bool fLogInit = false;  // Флаг инициализации журна
 
 #define LOG_TIME	1000*60*10
 
-#define LOG_FLASH_SECTOR_OFFSET	4
-
-#define ALARM_LOG_FLASH_SECTOR_OFFSET	258
-
 static int op_sector_erase(struct ringfs_flash_partition *flash, int address) {
 	(void)flash;
 	int ret;
@@ -106,7 +102,7 @@ void log_init(bool format) {
 	if (!spi_flash_desc.present)
 		return;
 	ringfs_flash.sector_size = spi_flash_desc.sector_size;
-	ringfs_flash.sector_count = spi_flash_desc.sector_count/2 - LOG_FLASH_SECTOR_OFFSET;
+	ringfs_flash.sector_count = SECTOR_COUNT;
 
 	ringfs_init(&fs, &ringfs_flash, LOG_ENTRY_VERSION, sizeof(log_entry_t));
 	if (format || ringfs_scan(&fs) != 0){
@@ -115,7 +111,7 @@ void log_init(bool format) {
 	}
 	DBG printf("FAT1 true\r\n");
 	ringfs_flash2.sector_size = spi_flash_desc.sector_size;
-	ringfs_flash2.sector_count = spi_flash_desc.sector_count/2 - LOG_FLASH_SECTOR_OFFSET;
+	ringfs_flash2.sector_count = SECTOR_COUNT;
 
 	ringfs_init(&fs2, &ringfs_flash2, LOG_ENTRY_VERSION, sizeof(log_entry_t));
 	if (format || ringfs_scan(&fs2) != 0){

+ 12 - 0
modules/settings_api.h

@@ -18,6 +18,8 @@
 #include <stdbool.h>
 #include "d_inouts.h"
 #include "common_config.h"
+#include "lwip/opt.h"
+#include "lwip/ip.h"
 
 /* Предельные границы настроек */
 #define MAX_VOLT_CELL_RANGE				3.0
@@ -321,6 +323,14 @@ typedef struct
   uint32_t serial;
 } UPS_Setting_t;
 
+typedef struct {
+	ip_addr_t server_ip;
+	u16_t server_port;
+	char remote_path[100];
+	char user[30];
+	char pass[30];
+} FTP_Update_t;
+
 /**
   * @brief  Настройки реле.
   */ 
@@ -559,6 +569,8 @@ void SETTINGS_SetTempControlDef(void);
   */
 void SETTINGS_SetFlagNotificationDef(void);
 
+void SETTINGS_SetFTPUpdateDef(void);
+
 /**
   * @brief  Установить значение сервисных настроек по умолчанию
   */

+ 9 - 0
modules/settings_api_bt6711.c

@@ -261,6 +261,15 @@ void SETTINGS_SetFlagNotificationDef(void)
     }
 }
 
+void SETTINGS_SetFTPUpdateDef(void)
+{
+  IP4_ADDR(&sSettings.sFTPUpdate.server_ip, 192,168,0,253);
+  sSettings.sFTPUpdate.server_port = 21;
+  strcpy(&sSettings.sFTPUpdate.remote_path, "firmware.bin");
+  strcpy(&sSettings.sFTPUpdate.user, "anonymous");
+  strcpy(&sSettings.sFTPUpdate.pass, "guest");
+}
+
 /**
   * @brief  Установить значение настроек прозрачного порта по умолчанию
   */

+ 13 - 0
peripheral_modules/inc/spi_flash.h

@@ -18,6 +18,19 @@
 #define SPI_FLASH_BLOCK_SIZE                16//4
 #define SPI_FLASH_BLOCK_NUMBER		        32
 
+// SPI flash partitioning layout
+#define LOG_FLASH_SECTOR_OFFSET	4
+#ifdef HARDWARE_BT6711
+// assuming 512 sectors
+#define ALARM_LOG_FLASH_SECTOR_OFFSET	174
+#define SECTOR_COUNT 170
+#define FIRMWARE_UPDATE_SECTOR_OFFSET	344
+#define FIRMWARE_UPDATE_SECTOR_COUNT	(512 - FIRMWARE_UPDATE_SECTOR_OFFSET)
+#else // HARDWARE_BT6711
+#define ALARM_LOG_FLASH_SECTOR_OFFSET	258
+#define SECTOR_COUNT (spi_flash_desc.sector_count/2 - LOG_FLASH_SECTOR_OFFSET)
+#endif // HARDWARE_BT6711
+
 #if defined ( __ICCARM__ )
 typedef int ssize_t;
 #endif

+ 1 - 1
service_hw/Makefile

@@ -122,7 +122,7 @@ CFLAGS += -DUSE_STDPERIPH_DRIVER -DSTM32F40_41xxx -DLOG_ENABLE -DMBEDTLS_CONFIG_
 #-DPOLARSSL_CERTS_C
 BUILDDIR = ../build/bt6702_service/$(TARGET)
 
-FW_FLASH_START = $(shell awk '/USER_FLASH_FIRST_PAGE_ADDRESS/{print $$3}' ../config/common_config.h )
+FW_FLASH_START = $(shell awk '/USER_FLASH_FIRST_PAGE_ADDRESS/{print $$3;exit}' ../config/common_config.h )
 FW_FLASH_CRC = $(shell awk '/USER_FLASH_CRC_ADDRESS/{print $$3}' ../config/common_config.h )
 
 FW_NAME = BT_6703xx_service