Explorar el Código

【重构】TFTP 使用 socket 编写,支持支持客户端

tangyuxin hace 6 años
padre
commit
210562df72
Se han modificado 14 ficheros con 1655 adiciones y 665 borrados
  1. BIN
      images/tftp_server.png
  2. BIN
      images/tftpd_cfg.png
  3. BIN
      images/tftpd_get.png
  4. BIN
      images/tftpd_put.png
  5. 59 49
      tftp/README.md
  6. 8 5
      tftp/SConscript
  7. 54 0
      tftp/tftp.h
  8. 346 0
      tftp/tftp_client.c
  9. 0 107
      tftp/tftp_opts.h
  10. 211 48
      tftp/tftp_port.c
  11. 533 362
      tftp/tftp_server.c
  12. 0 94
      tftp/tftp_server.h
  13. 377 0
      tftp/tftp_xfer.c
  14. 67 0
      tftp/tftp_xfer.h

BIN
images/tftp_server.png


BIN
images/tftpd_cfg.png


BIN
images/tftpd_get.png


BIN
images/tftpd_put.png


+ 59 - 49
tftp/README.md

@@ -4,51 +4,47 @@
 
 [TFTP](https://baike.baidu.com/item/TFTP) (Trivial File Transfer Protocol, 简单文件传输协议)是 TCP/IP 协议族中的一个用来在客户机与服务器之间进行简单文件传输的协议,提供不复杂、开销不大的文件传输服务,端口号为 **69** ,比传统的 FTP 协议要轻量级很多,适用于小型的嵌入式产品上。
 
-RT-Thread 目前支持的是 TFTP 服务器。
+RT-Thread 目前支持 TFTP 服务器和 TFTP 客户端
 
-## 2、使用
+## 2 TFTP 服务器
 
-### 2.1 安装 TFTP 客户端
+下面将介绍设备端启动 TFTP 服务器,PC 端作为客户端从设备上读写文件的流程。
 
-安装文件位于 `/tools/Tftpd64-4.60-setup.exe` ,使用 TFTP 前,请先安装该软件。
+### 2.1 启动 TFTP 服务器
 
-### 2.2 启动 TFTP 服务器
+在传输文件前,需要在 MSH 终端上使用 `tftp -s` 命令来启动 TFTP 服务器,大致效果如下:
 
-在传输文件前,需要在 RT-Thread 上使用 Finsh/MSH 命令来启动 TFTP 服务器,大致效果如下:
-
-```
-msh />tftp_server
-TFTP server start successfully.
-msh />
+```shell
+msh />tftp -s
+msh />tftp server start!
 ```
 
-### 2.3 传输文件
+### 2.2 客户端配置
 
-打开刚安装的 `Tftpd64` 软件,按如下操作进行配置:
+PC 端打开 `Tftpd64` 软件,按如下操作进行客户端的配置:
 
 - 1、选择 `Tftp Client` ;
-- 2、在 `Server interfaces` 下拉框中,务必选择好与 RT-Thread 处于同一网段的网卡;
-- 3、填写 TFTP 服务器的 IP 地址。可以在 RT-Thread 的 MSH 下使用 `ifconfig` 命令查看;
-- 4、填写 TFTP 服务器端口号,默认: `69` 
+- 2、填写 TFTP 服务器的 IP 地址。可以在 RT-Thread 的 MSH 下使用 `ifconfig` 命令查看;
+- 3、填写 TFTP 服务器端口号,默认: `69` 
 
 ![tftpd_cfg](../images/tftpd_cfg.png)
 
-#### 2.3.1 发送文件到 RT-Thread
+#### 2.2.1 发送文件到设备端
 
-- 1、在 `Tftpd64` 软件中,选择好要发送文件
-- 2、`Remote File` 是服务器端保存文件的路径(包括文件名),选项支持相对路径和绝对路径。由于 RT-Thread 默认开启 `DFS_USING_WORKDIR` 选项,此时相对路径是基于 Finsh/MSH 当前进入的目录。所以,使用相对路径时,务必提前切换好目录;
-- 3、点击 `Put` 按钮即可。
+- 1、Local File 是 PC 端发送到设备端的文件路径
+- 2、`Remote File` 是服务器端保存文件的路径(包括文件名),服务端支持相对路径和绝对路径。由于 RT-Thread 默认开启 `DFS_USING_WORKDIR` 选项,此时相对路径是基于 Finsh/MSH 当前进入的目录。所以,使用相对路径时,务必提前切换好目录;
+- 3、点击 `Put` 按钮即可发送文件到设备端
 
-如下图所示,将文件发送至 Finsh/MSH 当前进入的目录下,这里使用的是 **相对路径** :
+如下图所示,将文件发送至当前目录下,这里使用的是 **相对路径** :
 
 ![tftpd_put](../images/tftpd_put.png)
 
 > 注意:如果 `DFS_USING_WORKDIR` 未开启,同时 `Remote File` 为空,文件会将保存至根路径下。
 
-#### 2.3.2 从 RT-Thread 接收文件
+#### 2.2.2 从设备端接收文件
 
-- 1、在 `Tftpd64` 软件中,填写好要接收保存的文件路径(包含文件名);
-- 2、`Remote File` 是服务器端待接收回来的文件路径(包括文件名),选项支持相对路径和绝对路径。由于 RT-Thread 默认开启 `DFS_USING_WORKDIR` 选项,此时相对路径是基于 Finsh/MSH 当前进入的目录。所以,使用相对路径时,务必提前切换好目录;
+- 1、Local File 是 PC 端接收文件的存放路径(包含文件名);
+- 2、`Remote File` 是服务器发送文件的路径(包括文件名),选项支持相对路径和绝对路径。由于 RT-Thread 默认开启 `DFS_USING_WORKDIR` 选项,此时相对路径是基于 Finsh/MSH 当前进入的目录。所以,使用相对路径时,务必提前切换好目录;
 - 3、点击 `Get` 按钮即可。
 
 如下所示,将 `/web_root/image.jpg` 保存到本地,这里使用的是 **绝对路径** :
@@ -62,35 +58,49 @@ msh /web_root>
 
 ![tftpd_get](../images/tftpd_get.png)
 
-## 3、常见问题
+## 2.3 TFTP 客户端
 
-### 3.1 Put 文件到单片机时,程序会进入 hardfault :
+下面将介绍设备端作为 TFTP 客户端,从 PC 上读写文件的流程。
 
-hardfault 时日志类似如下:
+### 2.3.1 服务端配置
 
-```
-msh />psr: 0x61000000
- pc: 0x08009030
- lr: 0x08009031
-r12: 0x00000000
-r03: 0x20000208
-r02: 0x20000208
-r01: 0x2000fa44
-r00: 0x00000000
-hard fault on thread: tcpip
-thread pri  status      sp     stack size max used left tick  error
------- ---  ------- ---------- ----------  ------  ---------- ---
-tshell  20  suspend 0x00000140 0x00001000    07%   0x00000006 000
-tcpip   10  close   0xfffffd0f 0x0000004b    100%   0x2000f9bf 000
-etx     12  ready   0x00000098 0x00000400    14%   0x00000010 000
-erx     12  ready   0x000000e0 0x00000400    53%   0x00000010 000
-tidle   31  ready   0x00000048 0x00000100    34%   0x0000001c 000
-```
+打开刚安装的 `Tftpd64` 软件,按如下操作进行配置:
+
+- 1、选择 `Tftp Server` ;
+- 2、配置服务器文件夹路径
+- 3、在 `Server interfaces` 下拉框中,务必选择好与 RT-Thread 处于同一网段的网卡;
+
+![tftpd_cfg](../images/tftp_server.png)
+
+### 2.3.1 TFTP 命令详解
+
+TFTP 客户端读取文件命令详解
+
+tftp -r/-w ip_addr file_name [-p]
+
+- tftp      : 第一个参数固定 `tftp`
+- -w        : 往服务器写文件
+- -r        : 从服务器读文件
+- ip_addr   : 服务器 IP 地址
+- file_name : 文件名字
+- -p        : 服务器端口号
+
+### 2.3.2 TFTP 读文件
+
+tftp -r ip_addr file_name
+
+使用上述命令将 tftp 服务器上的一个文件下载到本地。命令示例如下:
+
+eg: tftp -r 192.168.1.13 text.txt
+
+从 192.168.1.13 这个服务器上下载 text.txt 文件到本地根路径下
+
+### 2.3.2 TFTP 写文件
 
-可以看出 tcpip 线程的 max used 指标为 100%,说明其堆栈已经溢出。还有种可能,此时的 tcpip 线程那行信息出现了乱码,同样也是其线程堆栈溢出所致。
+tftp -w ip_addr file_name
 
-在 lwIP 的 tcpip 默认配置位于:
+使用上述命令将本地的一个文件上传到 tftp 服务器。命令示例如下:
 
-`RT-Thread Components` → `Network stack` → `light weight TCP/IP stack` → ` (1024) the stack size of lwIP thread`
+eg: tftp -w 192.168.1.13 text.txt
 
-该选项默认为 `1024` ,建议调整至 `2048` 即可
+把本地根路径下 text.txt 文件上传到 192.168.1.13 服务器上

+ 8 - 5
tftp/SConscript

@@ -1,10 +1,13 @@
+Import('RTT_ROOT')
+Import('rtconfig')
 from building import *
 
-cwd = GetCurrentDir()
-src = Glob('*.c')
-
-CPPPATH = [cwd]
+cwd     = GetCurrentDir()
+src     = Glob('*.c') + Glob('*.cpp')
+CPPPATH = [cwd, str(Dir('#'))]
+list = os.listdir(cwd)
+objs = []
 
 group = DefineGroup('NetUtils', src, depend = ['PKG_NETUTILS_TFTP'], CPPPATH = CPPPATH)
 
-Return('group')
+Return('group')

+ 54 - 0
tftp/tftp.h

@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2006-2019, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date           Author       Notes
+ * 2019-02-26     tyx          first implementation
+ */
+
+#ifndef __TFTP_H__
+#define __TFTP_H__
+
+#define TFTP_OK         (0)
+#define TFTP_ETIMEOUT   (2)
+#define TFTP_EMEM       (3)
+#define TFTP_ESYS       (4)
+#define TFTP_EACK       (5)
+#define TFTP_EBLK       (6)
+#define TFTP_EDATA      (7)
+#define TFTP_EFILE      (8)
+#define TFTP_ECMD       (9)
+#define TFTP_EINVAL     (10)
+#define TFTP_EXFER      (11)
+#define TFTP_EOTHER     (10000)
+
+#define TFTP_MAX_RETRY (3)
+#define TFTP_SERVER_CONNECT_MAX (5)
+
+#define tftp_printf printf
+
+struct tftp_client
+{
+    int max_retry;
+    void *_private;
+};
+
+struct tftp_server
+{
+    int is_stop;
+    int is_write;
+    char *root_name;
+    void *_private;
+};
+
+struct tftp_client *tftp_client_create(const char *ip_addr, int port);
+void tftp_client_destroy(struct tftp_client *client);
+int tftp_client_push(struct tftp_client *client, const char *local_name, const char *remote_name);
+int tftp_client_pull(struct tftp_client *client, const char *remote_name, const char *local_name);
+struct tftp_server *tftp_server_create(const char *root_name, int port);
+void tftp_server_run(struct tftp_server *server);
+void tftp_server_destroy(struct tftp_server *server);
+void tftp_server_write_set(struct tftp_server *server, int is_write);
+#endif

+ 346 - 0
tftp/tftp_client.c

@@ -0,0 +1,346 @@
+/*
+ * Copyright (c) 2006-2019, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date           Author       Notes
+ * 2019-02-26     tyx          first implementation
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <sys/select.h>
+#include "tftp_xfer.h"
+#include "tftp.h"
+
+struct tftp_client_private
+{
+    struct tftp_xfer *xfer;
+    fd_set fdr;
+    struct timeval timeout;
+};
+
+extern void *tftp_file_open(const char *fname, const char *mode, int is_write);
+extern int tftp_file_write(void *handle, int pos, void *buff, int len);
+extern int tftp_file_read(void *handle, int pos, void *buff, int len);
+extern void tftp_file_close(void *handle);
+extern int tftp_thread_create(void **task, void (*entry)(void *param), void *param);
+
+static int tftp_client_select(struct tftp_client_private *_private)
+{
+    int ret;
+    FD_ZERO(&_private->fdr);
+    FD_SET(_private->xfer->sock, &_private->fdr);
+    _private->timeout.tv_sec = 5;
+    _private->timeout.tv_usec = 0;
+    ret = select(_private->xfer->sock + 1, &_private->fdr, NULL, NULL, (void *)&_private->timeout);
+    if (ret == 0)
+    {
+        return -TFTP_ETIMEOUT;
+    }
+    else if (ret < 0)
+    {
+        return -TFTP_ESYS;
+    }
+    return ret;
+}
+
+struct tftp_client *tftp_client_create(const char *ip_addr, int port)
+{
+    struct tftp_client_private *_private;
+    struct tftp_client *client;
+
+    /* malloc client mem */
+    client = malloc(sizeof(struct tftp_client) + sizeof(struct tftp_client_private));
+    if (client == NULL)
+    {
+        tftp_printf("create client failed!! exit \n");
+        return NULL;
+    }
+    /* Creating Private Data */
+    _private = (struct tftp_client_private *)&client[1];
+    /* Create a client connection */
+    _private->xfer = tftp_xfer_create(ip_addr, port);
+    if (_private->xfer == NULL)
+    {
+        tftp_printf("tftp xfer create failed!! exit\n");
+        free(client);
+        return NULL;
+    }
+    /* Number of Initial Retries */
+    client->max_retry = TFTP_MAX_RETRY;
+    /* Binding Private Data */
+    client->_private = _private;
+    return client;
+}
+
+void tftp_client_destroy(struct tftp_client *client)
+{
+    struct tftp_client_private *_private;
+
+    _private = client->_private;
+    /* Release connection objects */
+    tftp_xfer_destroy(_private->xfer);
+    /* Free memory */
+    free(client);
+}
+
+int tftp_client_push(struct tftp_client *client, const char *local_name, const char *remote_name)
+{
+    struct tftp_client_private *_private;
+    void *fp;
+    struct tftp_packet *pack;
+    int send_size, r_size;
+    int file_size = 0;
+    int res;
+    int max_retry;
+
+    _private = client->_private;
+    max_retry = client->max_retry;
+    while (max_retry)
+    {
+        /* Send Write Request */
+        res = tftp_send_request(_private->xfer, TFTP_CMD_WRQ, remote_name);
+        if (res != TFTP_OK)
+        {
+            tftp_printf("tftp send request failed !! retry:%d. exit\n", client->max_retry - max_retry);
+            max_retry = 0;
+            break;
+        }
+        /* Waiting for server response */
+        res = tftp_client_select(_private);
+        if (res > 0 && FD_ISSET(_private->xfer->sock, &_private->fdr))
+        {
+            /* Receive the server response */
+            break;
+        }
+        else if (res == -TFTP_ETIMEOUT)
+        {
+            tftp_printf("tftp selct timeout. retry\n");
+            max_retry --;
+            continue;
+        }
+        else
+        {
+            /* Waiting for Response Error */
+            tftp_printf("tftp selct err:%d. exit\n", res);
+            max_retry = 0;
+            break;
+        }
+    }
+
+    if (max_retry == 0)
+    {
+        return res;
+    }
+    /* Receiving ACK */
+    res = tftp_wait_ack(_private->xfer);
+    if (res != TFTP_OK)
+    {
+        tftp_printf("wait ack failed!! exit\n");
+        return res;
+    }
+    /* Open file */
+    fp = tftp_file_open(local_name, _private->xfer->mode, 1);
+    if (fp == NULL)
+    {
+        tftp_printf("open file \"%s\" error.\n", local_name);
+        return -TFTP_EFILE;
+    }
+    pack = malloc(sizeof(struct tftp_packet));
+    if (pack == NULL)
+    {
+        tftp_transfer_err(_private->xfer, 0, "malloc pack failed!");
+        tftp_file_close(fp);
+        return -TFTP_EMEM;
+    }
+    while (1)
+    {
+        /* read file */
+        r_size = tftp_file_read(fp, file_size, &pack->data, _private->xfer->blksize);
+        if (r_size < 0)
+        {
+            max_retry = 0;
+            break;
+        }
+        max_retry = client->max_retry;
+        while (max_retry)
+        {
+            /* Send data to server */
+            send_size = tftp_write_data(_private->xfer, pack, r_size + 4);
+            if (send_size != (r_size + 4))
+            {
+                tftp_transfer_err(_private->xfer, 0, "send file err!");
+                max_retry = 0;
+                break;
+            }
+            /* Wait server ACK */
+            res = tftp_client_select(_private);
+            if (res > 0 && FD_ISSET(_private->xfer->sock, &_private->fdr))
+            {
+                /* Receive a server ACK */
+                break;
+            }
+            else if (res == -TFTP_ETIMEOUT)
+            {
+                tftp_printf("tftp selct timeout. retry\n");
+                max_retry --;
+                continue;
+            }
+            else
+            {
+                tftp_printf("tftp selct err:%d. exit\n", res);
+                max_retry = 0;
+                break;
+            }
+        }
+
+        if (max_retry == 0)
+        {
+            break;
+        }
+        /* Receiving ACK */
+        if (tftp_wait_ack(_private->xfer) != TFTP_OK)
+        {
+            tftp_printf("wait ack failed!! exit\n");
+            break;
+        }
+        file_size += r_size;
+        if (r_size < _private->xfer->blksize)
+        {
+            break;
+        }
+    }
+    /* close file */
+    tftp_file_close(fp);
+    free(pack);
+    return file_size;
+}
+
+int tftp_client_pull(struct tftp_client *client, const char *remote_name, const char *local_name)
+{
+    struct tftp_client_private *_private;
+    void *fp;
+    struct tftp_packet *pack;
+    int recv_size, w_size;
+    int file_size = 0;
+    int res;
+    int max_retry;
+
+    _private = client->_private;
+    max_retry = client->max_retry;
+    while (max_retry)
+    {
+        /* Send Read File Request */
+        res = tftp_send_request(_private->xfer, TFTP_CMD_RRQ, remote_name);
+        if (res != TFTP_OK)
+        {
+            tftp_printf("tftp send request failed !! retry:%d. exit\n", max_retry);
+            max_retry = 0;
+            break;
+        }
+        /* Waiting for the server to respond to the request */
+        res = tftp_client_select(_private);
+        if (res > 0 && FD_ISSET(_private->xfer->sock, &_private->fdr))
+        {
+            /* Receive the server response */
+            break;
+        }
+        else if (res == -TFTP_ETIMEOUT)
+        {
+            tftp_printf("tftp selct timeout. retry\n");
+            max_retry --;
+            continue;
+        }
+        else
+        {
+            tftp_printf("tftp selct err:%d. exit\n", res);
+            max_retry = 0;
+            break;
+        }        
+    }
+
+    /* More than the maximum number of retries. exit */
+    if (max_retry == 0)
+    {
+        return res;
+    }
+
+    /* Request successful. open file */
+    fp = tftp_file_open(local_name, _private->xfer->mode, 1);
+    if (fp == NULL)
+    {
+        tftp_printf("open file \"%s\" error.\n", local_name);
+        return -TFTP_EFILE;
+    }
+    pack = malloc(sizeof(struct tftp_packet));
+    if (pack == NULL)
+    {
+        /* malloc failed. send err msg and exit */
+        tftp_transfer_err(_private->xfer, 0, "malloc pack failed!");
+        tftp_file_close(fp);
+        return -TFTP_EMEM;
+    }
+    while (1)
+    {
+        /* Receiving data from server */
+        recv_size = tftp_read_data(_private->xfer, pack, \
+            (int)((uint8_t *)&pack->data - (uint8_t *)pack) + _private->xfer->blksize);
+        if (recv_size < 0)
+        {
+            tftp_printf("read data err[%d]! exit\n", recv_size);
+            break;
+        }
+        /* Write data to file */
+        w_size = tftp_file_write(fp, file_size, &pack->data, recv_size);
+        if (w_size != recv_size)
+        {
+            tftp_printf("write file err! exit\n");
+            tftp_transfer_err(_private->xfer, 0, "write file err!");
+            break;
+        }
+        file_size += recv_size;
+        /* Data less than one package. Completion of reception */
+        if (recv_size < _private->xfer->blksize)
+        {
+            tftp_resp_ack(_private->xfer);
+            break;
+        }
+        max_retry = client->max_retry;
+        while (max_retry)
+        {
+            /* Send a response signal */
+            tftp_resp_ack(_private->xfer);
+            /* Waiting for the server to send data */
+            res = tftp_client_select(_private);
+            if (res > 0 && FD_ISSET(_private->xfer->sock, &_private->fdr))
+            {
+                break;
+            }
+            else if (res == -TFTP_ETIMEOUT)
+            {
+                tftp_printf("tftp selct timeout. retry\n");
+                max_retry --;
+            }
+            else
+            {
+                tftp_printf("tftp selct err:%d. exit\n", res);
+                max_retry = 0;
+                break;
+            }
+        }
+        if (max_retry == 0)
+        {
+            break;
+        }
+    }
+    /* close file */
+    tftp_file_close(fp);
+    free(pack);
+    return file_size;
+}

+ 0 - 107
tftp/tftp_opts.h

@@ -1,107 +0,0 @@
-/****************************************************************//**
- *
- * @file tftp_opts.h
- *
- * @author   Logan Gunthorpe <logang@deltatee.com>
- *
- * @brief    Trivial File Transfer Protocol (RFC 1350) implementation options
- *
- * Copyright (c) Deltatee Enterprises Ltd. 2013
- * 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: Logan Gunthorpe <logang@deltatee.com>
- *
- */
-
-#ifndef LWIP_HDR_APPS_TFTP_OPTS_H
-#define LWIP_HDR_APPS_TFTP_OPTS_H
-
-#include <rtthread.h>
-
-/**
- * @defgroup tftp_opts Options
- * @ingroup tftp
- * @{
- */
-
-/**
- * Enable TFTP debug messages
- */
-#if !defined TFTP_DEBUG
-#define TFTP_DEBUG            LWIP_DBG_ON
-#endif
-
-/**
- * TFTP server port
- */
-#if defined NETUTILS_TFTP_PORT
-#define TFTP_PORT             NETUTILS_TFTP_PORT
-#elif !defined TFTP_PORT
-#define TFTP_PORT             69
-#endif
-
-/**
- * TFTP timeout
- */
-#if !defined TFTP_TIMEOUT_MSECS
-#define TFTP_TIMEOUT_MSECS    10000
-#endif
-
-/**
- * Max. number of retries when a file is read from server
- */
-#if !defined TFTP_MAX_RETRIES
-#define TFTP_MAX_RETRIES      5
-#endif
-
-/**
- * TFTP timer cyclic interval
- */
-#if !defined TFTP_TIMER_MSECS
-#define TFTP_TIMER_MSECS      50
-#endif
-
-/**
- * Max. length of TFTP filename
- */
-#if !defined TFTP_MAX_FILENAME_LEN
-#define TFTP_MAX_FILENAME_LEN 20
-#endif
-
-/**
- * Max. length of TFTP mode
- */
-#if !defined TFTP_MAX_MODE_LEN
-#define TFTP_MAX_MODE_LEN     7
-#endif
-
-/**
- * @}
- */
-
-#endif /* LWIP_HDR_APPS_TFTP_OPTS_H */

+ 211 - 48
tftp/tftp_port.c

@@ -1,44 +1,27 @@
 /*
- * File      : tftp_port.c
- * This file is part of RT-Thread RTOS
- * COPYRIGHT (C) 2006 - 2017, RT-Thread Development Team
+ * Copyright (c) 2006-2019, RT-Thread Development Team
  *
- *  This program is free software; you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation; either version 2 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License along
- *  with this program; if not, write to the Free Software Foundation, Inc.,
- *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ * SPDX-License-Identifier: Apache-2.0
  *
  * Change Logs:
  * Date           Author       Notes
- * 2017-08-17     armink       first version.
+ * 2019-02-26     tyx          first implementation
  */
 
 #include <rtthread.h>
 #include <dfs_posix.h>
-#include <lwip/apps/tftp_server.h>
-
-#if defined(RT_USING_LWIP) && (RT_LWIP_TCPTHREAD_STACKSIZE < 1408)
-#error The lwIP tcpip thread stack size(RT_LWIP_TCPTHREAD_STACKSIZE) must more than 1408
-#endif
-
-static struct tftp_context ctx;
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "tftp.h"
 
-static void* tftp_open(const char* fname, const char* mode, u8_t write)
+RT_WEAK void *tftp_file_open(const char *fname, const char *mode, int is_write)
 {
-    int fd = -1;
+    int fd = 0;
 
     if (!rt_strcmp(mode, "octet"))
     {
-        if (write)
+        if (is_write)
         {
             fd = open(fname, O_WRONLY | O_CREAT, 0);
         }
@@ -52,39 +35,219 @@ static void* tftp_open(const char* fname, const char* mode, u8_t write)
         rt_kprintf("tftp: No support this mode(%s).", mode);
     }
 
-    return (void *) fd;
+    return (void *)fd;
+}
+
+RT_WEAK int tftp_file_write(void *handle, int pos, void *buff, int len)
+{
+    int fd = (int)handle;
+
+    return write(fd, buff, len);
 }
 
-static int tftp_write(void* handle, struct pbuf* p)
+RT_WEAK int tftp_file_read(void *handle, int pos, void *buff, int len)
 {
-    int fd = (int) handle;
+    int fd = (int)handle;
 
-    return write(fd, p->payload, p->len);
+    return read(fd, buff, len);
 }
 
-#if defined(RT_USING_FINSH)
-#include <finsh.h>
+RT_WEAK void tftp_file_close(void *handle)
+{
+    close((int)handle);
+}
+
+static struct tftp_server *server;
+
+static void tftp_server_thread(void *param)
+{
+    tftp_server_run((struct tftp_server *)param);
+    server = RT_NULL;
+}
 
-static void tftp_server(uint8_t argc, char **argv)
+#define _S_MODE_CMD        (100)
+#define _CW_MODE_CMD       (101)
+#define _CR_MODE_CMD       (102)
+#define _IP_MODE_CMD       (103)
+#define _P_MODE_CMD        (104)
+#define _STOP_MODE_CMD     (107)
+#define _UNKNOWN_MODE_CMD  (0)
+
+struct _tftp_cmd
 {
-    ctx.open = tftp_open;
-    ctx.close = (void (*)(void *)) close;
-    ctx.read = (int (*)(void *, void *, int)) read;
-    ctx.write = tftp_write;
+    const char *cmd_str;
+    const char *help_info;
+    int cmd;
+};
 
-    if (tftp_init(&ctx) == ERR_OK)
+static const struct _tftp_cmd _cmd_tab[] = 
+{
+    {"-s", "begin tftp server", _S_MODE_CMD},
+    {"-w", "client write file to server", _CW_MODE_CMD},
+    {"-r", "client read file from server", _CR_MODE_CMD},
+    {"-p", "server port to listen on/connect to", _P_MODE_CMD},
+    {"--stop", "stop tftp server", _STOP_MODE_CMD},
+};
+
+static void _tftp_help(void)
+{
+    int i;
+    printf("Usage: tftp [-s|-w|-r host] [path]\n");
+    printf("       tftp [-h|--stop]\n\n");
+    for (i = 0; i < sizeof(_cmd_tab) / sizeof(_cmd_tab[0]); i++)
     {
-        rt_kprintf("TFTP server start successfully.\n");
+        printf("       %-6.6s:  %s\n", _cmd_tab[i].cmd_str, _cmd_tab[i].help_info);
     }
-    else
+    printf("\neg: \n");
+    printf("    open server: tftp -s /\n");
+    printf("    read  file : tftp -r 192.168.1.1 test.data\n");
+    printf("    wriet file : tftp -w 192.168.1.1 test.data\n");
+}
+
+static int _tftp_msh(int argc, char *argv[])
+{
+    int i, j, cmd;
+    int tftp_mode = 0;
+    char *ip = RT_NULL;
+    char *path[2] = {0};
+    int port = 0, stop = 0;
+
+    if (argc == 1)
     {
-        rt_kprintf("TFTP server start failed.\n");
+        goto _help;
     }
-}
-FINSH_FUNCTION_EXPORT(tftp_server, start tftp server.);
+    for (i = 1; i < argc; i++)
+    {
+        cmd = _UNKNOWN_MODE_CMD;
+        for (j = 0; j < sizeof(_cmd_tab) / sizeof(_cmd_tab[0]); j++)
+        {
+            if (strcmp(_cmd_tab[j].cmd_str, argv[i]) == 0)
+            {
+                cmd = _cmd_tab[j].cmd;
+                break;
+            }
+        }
+        switch (cmd)
+        {
+        case _S_MODE_CMD:
+            if (tftp_mode != 0)
+            {
+                goto _help;
+            }
+            tftp_mode = _S_MODE_CMD;
+            break;
+        case _CW_MODE_CMD:
+        case _CR_MODE_CMD:
+            if (tftp_mode != 0)
+            {
+                goto _help;
+            }
+            tftp_mode = cmd;
+            if ((i + 1) < argc)
+            {
+                ip = argv[i + 1];
+                i ++;
+            }
+            break;
+        case _P_MODE_CMD:
+            if (port != 0)
+            {
+                goto _help;
+            }
+            if ((i + 1) < argc)
+            {
+                port = atoi(argv[i + 1]);
+                i ++;
+            }
+            break;
+        case _STOP_MODE_CMD:
+            if (argc != 2)
+            {
+                goto _help;
+            }
+            stop = 1;
+            break;
+        default:
+        {
+            if (path[0] == RT_NULL)
+            {
+                path[0] = argv[i];
+            }
+            else if (path[1] == RT_NULL)
+            {
+                path[1] = argv[i];
+            }
+            else
+            {
+                goto _help;
+            }
+        }
+        }
+    }
+
+    if (stop && server)
+    {
+        tftp_server_destroy(server);
+        return 0;
+    }
+
+    if (port == 0)
+    {
+        port = 69;
+    }
+    if (tftp_mode == _S_MODE_CMD)
+    {
+        rt_thread_t tid;
+        if (server != RT_NULL)
+        {
+            printf("tftp server is run");
+            return 0;
+        }
+        if (path[0] == RT_NULL)
+        {
+            path[0] = "/";
+        }
+        server = tftp_server_create(path[0], port);
+        tftp_server_write_set(server, 1);
+        tid = rt_thread_create("tftps", tftp_server_thread, server, 2048, 18, 20);
+        if (tid == NULL)
+        {
+            return -1;
+        }
+        rt_thread_startup(tid);
+        return 0;
+    }
+    else if ((tftp_mode == _CW_MODE_CMD) || (tftp_mode == _CR_MODE_CMD))
+    {
+        struct tftp_client *client;
 
-#if defined(FINSH_USING_MSH)
-MSH_CMD_EXPORT(tftp_server, start tftp server.);
-#endif /* defined(FINSH_USING_MSH) */
+        if (ip == RT_NULL || path[0] == RT_NULL)
+        {
+            goto _help;
+        }
+        if (path[1] == RT_NULL)
+        {
+            path[1] = path[0];
+        }
+        client = tftp_client_create(ip, port);
+        if (tftp_mode == _CR_MODE_CMD)
+        {
+            printf("file size:%d\n", tftp_client_pull(client, path[0], path[1]));
+        }
+        else
+        {
+            printf("file size:%d\n", tftp_client_push(client, path[0], path[1]));
+        }
+        tftp_client_destroy(client);
+        return 0;
+    }
+    else
+    {
+        goto _help;
+    }
 
-#endif /* defined(RT_USING_FINSH) */
+_help:
+    _tftp_help();
+    return -1;
+}
+FINSH_FUNCTION_EXPORT_ALIAS(_tftp_msh, __cmd_tftp, tftp.);

+ 533 - 362
tftp/tftp_server.c

@@ -1,417 +1,588 @@
-/****************************************************************//**
+/*
+ * Copyright (c) 2006-2019, RT-Thread Development Team
  *
- * @file tftp_server.c
- *
- * @author   Logan Gunthorpe <logang@deltatee.com>
- *           Dirk Ziegelmeier <dziegel@gmx.de>
- *
- * @brief    Trivial File Transfer Protocol (RFC 1350)
- *
- * Copyright (c) Deltatee Enterprises Ltd. 2013
- * 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: Logan Gunthorpe <logang@deltatee.com>
- *         Dirk Ziegelmeier <dziegel@gmx.de>
+ * SPDX-License-Identifier: Apache-2.0
  *
+ * Change Logs:
+ * Date           Author       Notes
+ * 2019-02-26     tyx          first implementation
  */
 
-/**
- * @defgroup tftp TFTP server
- * @ingroup apps
- *
- * This is simple TFTP server for the lwIP raw API.
- */
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <sys/select.h>
+#include "tftp_xfer.h"
+#include "tftp.h"
 
-#include "lwip/apps/tftp_server.h"
+#define TFTP_SERVER_EVENT_CONNECT   (0x1 << 0)
+#define TFTP_SERVER_EVENT_DATA      (0x1 << 1)
+#define TFTP_SERVER_EVENT_TIMEOUT   (0x1 << 2)
 
-#if LWIP_UDP
+#define TFTP_SERVER_FILE_NAME_MAX   (512)
 
-#include "lwip/udp.h"
-#include "lwip/timeouts.h"
-#include "lwip/debug.h"
+#define TFTP_SERVER_REQ_READ    (0x0)
+#define TFTP_SERVER_REQ_WRITE   (0x1)
 
-#define TFTP_MAX_PAYLOAD_SIZE 512
-#define TFTP_HEADER_LENGTH    4
+extern void *tftp_file_open(const char *fname, const char *mode, int is_write);
+extern int tftp_file_write(void *handle, int pos, void *buff, int len);
+extern int tftp_file_read(void *handle, int pos, void *buff, int len);
+extern void tftp_file_close(void *handle);
 
-#define TFTP_RRQ   1
-#define TFTP_WRQ   2
-#define TFTP_DATA  3
-#define TFTP_ACK   4
-#define TFTP_ERROR 5
+struct tftp_client_xfer
+{
+    struct tftp_xfer *xfer;
+    int16_t w_r;
+    int16_t retry;
+    int pos;
+    int last_read;
+    void *fd;
+};
 
-enum tftp_error {
-  TFTP_ERROR_FILE_NOT_FOUND    = 1,
-  TFTP_ERROR_ACCESS_VIOLATION  = 2,
-  TFTP_ERROR_DISK_FULL         = 3,
-  TFTP_ERROR_ILLEGAL_OPERATION = 4,
-  TFTP_ERROR_UNKNOWN_TRFR_ID   = 5,
-  TFTP_ERROR_FILE_EXISTS       = 6,
-  TFTP_ERROR_NO_SUCH_USER      = 7
+struct tftp_server_private
+{
+    struct tftp_xfer *server_xfer;
+    struct tftp_client_xfer *client_table;
+    int table_num;
+    fd_set fdr;
+    struct timeval timeout;
 };
 
-#include <string.h>
+static int tftp_server_select(struct tftp_server *server)
+{
+    struct tftp_server_private *_private;
+    int max_sock, i;
+    int ret;
+
+    _private = server->_private;
+    FD_ZERO(&_private->fdr);
+    /* Select server */
+    FD_SET(_private->server_xfer->sock, &_private->fdr);
+    max_sock = _private->server_xfer->sock;
+    /* Select all client connections */
+    for (i = 0; i < _private->table_num; i++)
+    {
+        if (_private->client_table[i].xfer != NULL)
+        {
+            FD_SET(_private->client_table[i].xfer->sock, &_private->fdr);
+            if (max_sock < _private->client_table[i].xfer->sock)
+            {
+                max_sock = _private->client_table[i].xfer->sock;
+            }
+        }
+    }
+    /* Setting timeout time */
+    _private->timeout.tv_sec = 5;
+    _private->timeout.tv_usec = 0;
+    ret = select(max_sock + 1, &_private->fdr, NULL, NULL, (void *)&_private->timeout);
+    if (ret == 0)
+    {
+        return -TFTP_ETIMEOUT;
+    }
+    else if (ret < 0)
+    {
+        return -TFTP_ESYS;
+    }
 
-struct tftp_state {
-  const struct tftp_context *ctx;
-  void *handle;
-  struct pbuf *last_data;
-  struct udp_pcb *upcb;
-  ip_addr_t addr;
-  u16_t port;
-  int timer;
-  int last_pkt;
-  u16_t blknum;
-  u8_t retries;
-  u8_t mode_write;
-};
+    return ret;
+}
 
-static struct tftp_state tftp_state;
+static struct tftp_client_xfer *tftp_client_xfer_get(struct tftp_server *server, int index)
+{
+    struct tftp_server_private *_private;
 
-static void tftp_tmr(void* arg);
+    _private = server->_private;
+    if (_private->table_num > index)
+    {
+        return &_private->client_table[index];
+    }
+    return NULL;
+}
 
-static void
-close_handle(void)
+static struct tftp_client_xfer *tftp_client_xfer_add(struct tftp_server *server, struct tftp_xfer *xfer)
 {
-  tftp_state.port = 0;
-  ip_addr_set_any(0, &tftp_state.addr);
-
-  if(tftp_state.last_data != NULL) {
-    pbuf_free(tftp_state.last_data);
-    tftp_state.last_data = NULL;
-  }
-
-  sys_untimeout(tftp_tmr, NULL);
-  
-  if (tftp_state.handle) {
-    tftp_state.ctx->close(tftp_state.handle);
-    tftp_state.handle = NULL;
-    LWIP_DEBUGF(TFTP_DEBUG | LWIP_DBG_STATE, ("tftp: closing\n"));
-  }
+    struct tftp_server_private *_private;
+    int i;
+
+    _private = server->_private;
+    /* View space and add */
+    for (i = 0; i < _private->table_num; i++)
+    {
+        if (_private->client_table[i].xfer == NULL)
+        {
+            memset(&_private->client_table[i], 0, sizeof(struct tftp_client_xfer));
+            _private->client_table[i].xfer = xfer;
+            return &_private->client_table[i];
+        }
+    }
+    return NULL;
 }
 
-static void
-send_error(const ip_addr_t *addr, u16_t port, enum tftp_error code, const char *str)
+static void tftp_client_xfer_delete(struct tftp_server *server, struct tftp_xfer *xfer)
 {
-  int str_length = strlen(str);
-  struct pbuf* p;
-  u16_t* payload;
-  
-  p = pbuf_alloc(PBUF_TRANSPORT, (u16_t)(TFTP_HEADER_LENGTH + str_length + 1), PBUF_RAM);
-  if(p == NULL) {
-    return;
-  }
-
-  payload = (u16_t*) p->payload;
-  payload[0] = PP_HTONS(TFTP_ERROR);
-  payload[1] = lwip_htons(code);
-  MEMCPY(&payload[2], str, str_length + 1);
-
-  udp_sendto(tftp_state.upcb, p, addr, port);
-  pbuf_free(p);
+    struct tftp_server_private *_private;
+    int i;
+
+    _private = server->_private;
+    /* Find a clinet xfer and remove */
+    for (i = 0; i < _private->table_num; i++)
+    {
+        if (_private->client_table[i].xfer == xfer)
+        {
+            _private->client_table[i].xfer = NULL;
+            break;
+        }
+    }
 }
 
-static void
-send_ack(u16_t blknum)
+static void tftp_client_xfer_destroy(struct tftp_server *server, struct tftp_client_xfer *client)
 {
-  struct pbuf* p;
-  u16_t* payload;
-  
-  p = pbuf_alloc(PBUF_TRANSPORT, TFTP_HEADER_LENGTH, PBUF_RAM);
-  if(p == NULL) {
-    return;
-  }
-  payload = (u16_t*) p->payload;
-  
-  payload[0] = PP_HTONS(TFTP_ACK);
-  payload[1] = lwip_htons(blknum);
-  udp_sendto(tftp_state.upcb, p, &tftp_state.addr, tftp_state.port);
-  pbuf_free(p);
+    /* Close client connection */
+    tftp_xfer_destroy(client->xfer);
+    /* close file */
+    tftp_file_close(client->fd);
+    /* Delete client */
+    tftp_client_xfer_delete(server, client->xfer);
 }
 
-static void
-resend_data(void)
+static void tftp_server_send_file(struct tftp_server *server, struct tftp_client_xfer *client, struct tftp_packet *packet, bool resend)
 {
-  struct pbuf *p = pbuf_alloc(PBUF_TRANSPORT, tftp_state.last_data->len, PBUF_RAM);
-  if(p == NULL) {
-    return;
-  }
-
-  if(pbuf_copy(p, tftp_state.last_data) != ERR_OK) {
-    pbuf_free(p);
-    return;
-  }
-    
-  udp_sendto(tftp_state.upcb, p, &tftp_state.addr, tftp_state.port);
-  pbuf_free(p);
+    int r_size, s_size;
+    int retry = TFTP_MAX_RETRY;
+
+    if (resend == false)
+    {
+        client->pos += client->last_read;
+    }
+    /* read file */
+    r_size = tftp_file_read(client->fd, client->pos, &packet->data, client->xfer->blksize);
+    if (r_size < 0)
+    {
+        r_size = 0;
+    }
+    while (1)
+    {
+        /* Send data to client */
+        s_size = tftp_write_data(client->xfer, packet, r_size + 4);
+        if (r_size == (s_size - 4))
+        {
+            break;
+        }
+        /* Failed to send data. retry */
+        if (retry-- == 0)
+        {
+            break;
+        }
+    }
+    /* Maximum number of retries */
+    if (retry == 0)
+    {
+        /* Destroy client connection */
+        tftp_client_xfer_destroy(server, client);
+    }
+    else
+    {
+        client->last_read = r_size;
+    }
 }
 
-static void
-send_data(void)
+static void tftp_server_send_ack(struct tftp_server *server, struct tftp_client_xfer *client)
 {
-  u16_t *payload;
-  int ret;
-
-  if(tftp_state.last_data != NULL) {
-    pbuf_free(tftp_state.last_data);
-  }
-  
-  tftp_state.last_data = pbuf_alloc(PBUF_TRANSPORT, TFTP_HEADER_LENGTH + TFTP_MAX_PAYLOAD_SIZE, PBUF_RAM);
-  if(tftp_state.last_data == NULL) {
-    return;
-  }
-
-  payload = (u16_t *) tftp_state.last_data->payload;
-  payload[0] = PP_HTONS(TFTP_DATA);
-  payload[1] = lwip_htons(tftp_state.blknum);
-
-  ret = tftp_state.ctx->read(tftp_state.handle, &payload[2], TFTP_MAX_PAYLOAD_SIZE);
-  if (ret < 0) {
-    send_error(&tftp_state.addr, tftp_state.port, TFTP_ERROR_ACCESS_VIOLATION, "Error occured while reading the file.");
-    close_handle();
-    return;
-  }
-
-  pbuf_realloc(tftp_state.last_data, (u16_t)(TFTP_HEADER_LENGTH + ret));
-  resend_data();
+    int retry = TFTP_MAX_RETRY;
+    bool res;
+
+    while (1)
+    {
+        /* send ack */
+        res = tftp_resp_ack(client->xfer);
+        if (res == TFTP_OK)
+        {
+            break;
+        }
+        /* send failed. retry */
+        if (retry-- == 0)
+        {
+            break;
+        }
+    }
+    if (retry == 0)
+    {
+        /* Maximum number of retries */
+        tftp_client_xfer_destroy(server, client);
+    }
 }
 
-static void
-recv(void *arg, struct udp_pcb *upcb, struct pbuf *p, const ip_addr_t *addr, u16_t port)
+static void tftp_server_transf_handle(struct tftp_server *server, struct tftp_client_xfer *client, int event, struct tftp_packet *packet)
 {
-  u16_t *sbuf = (u16_t *) p->payload;
-  int opcode;
-
-  LWIP_UNUSED_ARG(arg);
-  LWIP_UNUSED_ARG(upcb);
-  
-  if (((tftp_state.port != 0) && (port != tftp_state.port)) ||
-      (!ip_addr_isany_val(tftp_state.addr) && !ip_addr_cmp(&tftp_state.addr, addr))) {
-    send_error(addr, port, TFTP_ERROR_ACCESS_VIOLATION, "Only one connection at a time is supported");
-    pbuf_free(p);
-    return;
-  }
-
-  opcode = sbuf[0];
-
-  tftp_state.last_pkt = tftp_state.timer;
-  tftp_state.retries = 0;
-
-  switch (opcode) {
-    case PP_HTONS(TFTP_RRQ): /* fall through */
-    case PP_HTONS(TFTP_WRQ):
-    {
-      const char tftp_null = 0;
-      char filename[TFTP_MAX_FILENAME_LEN + 1] = { 0 };
-      char mode[TFTP_MAX_MODE_LEN] = { 0 };
-      u16_t filename_end_offset;
-      u16_t mode_end_offset;
-
-      if(tftp_state.handle != NULL) {
-        send_error(addr, port, TFTP_ERROR_ACCESS_VIOLATION, "Only one connection at a time is supported");
+    switch (event)
+    {
+    case TFTP_SERVER_EVENT_CONNECT:
+        if (client->w_r == TFTP_SERVER_REQ_READ)
+        {
+            /* Read the file request and return the file data */
+            tftp_server_send_file(server, client, packet, false);
+        }
+        else
+        {
+            /* Write file request, return ACK */
+            tftp_server_send_ack(server, client);
+        }
         break;
-      }
-      
-      sys_timeout(TFTP_TIMER_MSECS, tftp_tmr, NULL);
-
-      /* find \0 in pbuf -> end of filename string */
-      filename_end_offset = pbuf_memfind(p, &tftp_null, sizeof(tftp_null), 2);
-      if((u16_t)(filename_end_offset-2) > sizeof(filename)) {
-        send_error(addr, port, TFTP_ERROR_ACCESS_VIOLATION, "Filename too long/not NULL terminated");
+    case TFTP_SERVER_EVENT_DATA:
+        /* Receive data from client */
+        if (client->w_r == TFTP_SERVER_REQ_READ)
+        {
+            /* If reques is read. Receive ACK */
+            if (tftp_wait_ack(client->xfer) == TFTP_OK)
+            {
+                /* Receive ACK success. If it's the last package of data, close client */
+                if (client->last_read < client->xfer->blksize)
+                {
+                    tftp_client_xfer_destroy(server, client);
+                    break;
+                }
+                /* Receive ACK success. Continue sending data */
+                tftp_server_send_file(server, client, packet, false);
+                client->retry = TFTP_MAX_RETRY;
+            }
+            else
+            {
+                /* Receive ACK failed. close client */
+                tftp_transfer_err(client->xfer, 0, "err ack!");
+                tftp_client_xfer_destroy(server, client);
+            }
+        }
+        else
+        {
+            /* Write File Request handle */
+            int recv_size, w_size;
+            /* Receiving File Data from Client */
+            recv_size = tftp_read_data(client->xfer, packet,
+                                       (int)((uint8_t *)&packet->data - (uint8_t *)packet) + client->xfer->blksize);
+            if (recv_size < 0)
+            {
+                /* Receiving failed. */
+                tftp_printf("server read data err! disconnect client\n");
+                tftp_client_xfer_destroy(server, client);
+            }
+            else
+            {
+                /* write file */
+                w_size = tftp_file_write(client->fd, client->pos, &packet->data, recv_size);
+                if (w_size != recv_size)
+                {
+                    /* Write file error, close connection */
+                    tftp_printf("server write file err! disconnect client\n");
+                    tftp_transfer_err(client->xfer, 0, "write file err!");
+                    tftp_client_xfer_destroy(server, client);
+                    break;
+                }
+                /* Reply ack */
+                tftp_server_send_ack(server, client);
+                client->pos += recv_size;
+                /* Receive the last packet of data. close client */
+                if (recv_size < client->xfer->blksize)
+                {
+                    tftp_client_xfer_destroy(server, client);
+                }
+            }
+        }
         break;
-      }
-      pbuf_copy_partial(p, filename, filename_end_offset-2, 2);
-
-      /* find \0 in pbuf -> end of mode string */
-      mode_end_offset = pbuf_memfind(p, &tftp_null, sizeof(tftp_null), filename_end_offset+1);
-      if((u16_t)(mode_end_offset-filename_end_offset) > sizeof(mode)) {
-        send_error(addr, port, TFTP_ERROR_ACCESS_VIOLATION, "Mode too long/not NULL terminated");
+    case TFTP_SERVER_EVENT_TIMEOUT:
+        /* Timeout handle */
+        if (client->w_r == TFTP_SERVER_REQ_READ)
+        {
+            /* resend file */
+            if (client->retry > 0)
+            {
+                tftp_server_send_file(server, client, packet, true);
+                client->retry--;
+            }
+            else
+            {
+                /* Maximum number of retransmissions */
+                tftp_client_xfer_destroy(server, client);
+            }
+        }
+        else
+        {
+            if (client->retry > 0)
+            {
+                /* resend ack */
+                tftp_server_send_ack(server, client);
+                client->retry--;
+            }
+            else
+            {
+                tftp_client_xfer_destroy(server, client);
+            }
+        }
         break;
-      }
-      pbuf_copy_partial(p, mode, mode_end_offset-filename_end_offset, filename_end_offset+1);
- 
-      tftp_state.handle = tftp_state.ctx->open(filename, mode, opcode == PP_HTONS(TFTP_WRQ));
-      tftp_state.blknum = 1;
-
-      if (!tftp_state.handle) {
-        send_error(addr, port, TFTP_ERROR_FILE_NOT_FOUND, "Unable to open requested file.");
+    default:
+        tftp_printf("warr!! unknown event:%d\n", event);
         break;
-      }
-
-      LWIP_DEBUGF(TFTP_DEBUG | LWIP_DBG_STATE, ("tftp: %s request from ", (opcode == PP_HTONS(TFTP_WRQ)) ? "write" : "read"));
-      ip_addr_debug_print(TFTP_DEBUG | LWIP_DBG_STATE, addr);
-      LWIP_DEBUGF(TFTP_DEBUG | LWIP_DBG_STATE, (" for '%s' mode '%s'\n", filename, mode));
-
-      ip_addr_copy(tftp_state.addr, *addr);
-      tftp_state.port = port;
-
-      if (opcode == PP_HTONS(TFTP_WRQ)) {
-        tftp_state.mode_write = 1;
-        send_ack(0);
-      } else {
-        tftp_state.mode_write = 0;
-        send_data();
-      }
+    }
+}
 
-      break;
+static struct tftp_client_xfer *tftp_server_request_handle(struct tftp_server *server, struct tftp_packet *packet)
+{
+    struct tftp_xfer *xfer;
+    struct tftp_server_private *_private;
+    char *path, *full_path;
+    int name_len;
+    struct tftp_client_xfer *client_xfer;
+    void *fd = NULL;
+    char *mode;
+    char *blksize_str;
+    int blocksize;
+
+    _private = server->_private;
+    /* Receiving client requests */
+    memset(packet, 0, sizeof(struct tftp_packet));
+    xfer = tftp_recv_request(_private->server_xfer, packet);
+    if (xfer == NULL)
+    {
+        return NULL;
     }
-    
-    case PP_HTONS(TFTP_DATA):
+    /* Can write ? */
+    if (ntohs(packet->cmd) == TFTP_CMD_WRQ && (!server->is_write))
     {
-      int ret;
-      u16_t blknum;
-      
-      if (tftp_state.handle == NULL) {
-        send_error(addr, port, TFTP_ERROR_ACCESS_VIOLATION, "No connection");
-        break;
-      }
-
-      if (tftp_state.mode_write != 1) {
-        send_error(addr, port, TFTP_ERROR_ACCESS_VIOLATION, "Not a write connection");
-        break;
-      }
-
-      blknum = lwip_ntohs(sbuf[1]);
-      pbuf_header(p, -TFTP_HEADER_LENGTH);
-
-      ret = tftp_state.ctx->write(tftp_state.handle, p);
-      if (ret < 0) {
-        send_error(addr, port, TFTP_ERROR_ACCESS_VIOLATION, "error writing file");
-        close_handle();
-      } else {
-        send_ack(blknum);
-      }
-
-      if (p->tot_len < TFTP_MAX_PAYLOAD_SIZE) {
-        close_handle();
-      }
-      break;
+        tftp_printf("server read only!\n");
+        tftp_transfer_err(xfer, 0, "server read only!");
+        tftp_xfer_destroy(xfer);
+        return NULL;
     }
 
-    case PP_HTONS(TFTP_ACK):
+    /* Get file path */
+    path = packet->info.filename;
+    /* Get transfer mode */
+    mode = path + strlen(path) + 1;
+    tftp_xfer_mode_set(xfer, mode);
+    /* Get block size */
+    blksize_str = mode + strlen(mode) + 1;
+    if (strcmp(blksize_str, "blksize") == 0)
     {
-      u16_t blknum;
-      int lastpkt;
-
-      if (tftp_state.handle == NULL) {
-        send_error(addr, port, TFTP_ERROR_ACCESS_VIOLATION, "No connection");
-        break;
-      }
-
-      if (tftp_state.mode_write != 0) {
-        send_error(addr, port, TFTP_ERROR_ACCESS_VIOLATION, "Not a read connection");
-        break;
-      }
-
-      blknum = lwip_ntohs(sbuf[1]);
-      if (blknum != tftp_state.blknum) {
-        send_error(addr, port, TFTP_ERROR_UNKNOWN_TRFR_ID, "Wrong block number");
-        break;
-      }
+        blocksize = atoi(blksize_str + strlen(blksize_str) + 1);
+        if (tftp_xfer_blksize_set(xfer, blocksize) != TFTP_OK)
+        {
+            tftp_printf("set block size err:%d\n", blocksize);
+            tftp_transfer_err(xfer, 0, "block size err!");
+            tftp_xfer_destroy(xfer);
+            return NULL;
+        }
+    }
+    /* Get full file path */
+    name_len = strlen(path) + strlen(server->root_name);
+    if (name_len >= TFTP_SERVER_FILE_NAME_MAX)
+    {
+        tftp_printf("file name is to long!!\n");
+        tftp_transfer_err(xfer, 0, "file name to long!");
+        tftp_xfer_destroy(xfer);
+        return NULL;
+    }
+    full_path = malloc(name_len);
+    if (full_path == NULL)
+    {
+        tftp_printf("mallo full path failed!\n");
+        tftp_transfer_err(xfer, 0, "server err!");
+        tftp_xfer_destroy(xfer);
+        return NULL;
+    }
 
-      lastpkt = 0;
+    strcpy(full_path, server->root_name);
+    if (path[0] != '/')
+    {
+        strcat(full_path, "/");
+    }
+    strcat(full_path, path);
 
-      if (tftp_state.last_data != NULL) {
-        lastpkt = tftp_state.last_data->tot_len != (TFTP_MAX_PAYLOAD_SIZE + TFTP_HEADER_LENGTH);
-      }
+    /* open file */
+    if (ntohs(packet->cmd) == TFTP_CMD_RRQ)
+    {
+        fd = tftp_file_open(full_path, TFTP_XFER_OCTET, 0);
+    }
+    else if (ntohs(packet->cmd) == TFTP_CMD_WRQ)
+    {
+        fd = tftp_file_open(full_path, TFTP_XFER_OCTET, 1);
+    }
+    free(full_path);
+    if (fd == NULL)
+    {
+        tftp_printf("open file failed!\n");
+        tftp_transfer_err(xfer, 0, "file err");
+        tftp_xfer_destroy(xfer);
+        return NULL;
+    }
+    /* push client to queue */
+    client_xfer = tftp_client_xfer_add(server, xfer);
+    if (client_xfer == NULL)
+    {
+        tftp_printf("too many connections!!");
+        tftp_transfer_err(xfer, 0, "too many connections!");
+        tftp_xfer_destroy(xfer);
+        tftp_file_close(fd);
+    }
+    else
+    {
+        client_xfer->w_r = ntohs(packet->cmd) == TFTP_CMD_RRQ ? \
+            TFTP_SERVER_REQ_READ : TFTP_SERVER_REQ_WRITE;
+        client_xfer->retry = TFTP_MAX_RETRY;
+        client_xfer->fd = fd;
+        client_xfer->pos = 0;
+        client_xfer->last_read = 0;
+    }
+    return client_xfer;
+}
 
-      if (!lastpkt) {
-        tftp_state.blknum++;
-        send_data();
-      } else {
-        close_handle();
-      }
+void tftp_server_run(struct tftp_server *server)
+{
+    struct tftp_xfer *xfer;
+    struct tftp_packet *packet;
+    struct tftp_server_private *_private;
+    int res, i;
+    struct tftp_client_xfer *client_xfer;
 
-      break;
+    if (server == NULL)
+    {
+        return;
     }
-    
-    default:
-      send_error(addr, port, TFTP_ERROR_ILLEGAL_OPERATION, "Unknown operation");
-      break;
-  }
-
-  pbuf_free(p);
+    _private = server->_private;
+    /* malloc transport packet */
+    packet = malloc(sizeof(struct tftp_packet));
+    if (packet == NULL)
+    {
+        return;
+    }
+    /* Create connect */
+    xfer = tftp_xfer_create("0.0.0.0", 69);
+    if (xfer == NULL)
+    {
+        free(packet);
+        return;
+    }
+    /* Set connection type to server */
+    if (tftp_xfer_type_set(xfer, TFTP_XFER_TYPE_SERVER) != TFTP_OK)
+    {
+        free(packet);
+        tftp_xfer_destroy(xfer);
+        return;
+    }
+    _private->server_xfer = xfer;
+    tftp_printf("tftp server start!\n");
+    /* run server */
+    while (!server->is_stop)
+    {
+        /* Waiting client data */
+        res = tftp_server_select(server);
+        if (res == -TFTP_ETIMEOUT)
+        {
+            /* Waiting for data timeout */
+            for (i = 0; i < _private->table_num; i++)
+            {
+                if (_private->client_table[i].xfer != NULL)
+                {
+                    client_xfer = tftp_client_xfer_get(server, i);
+                    tftp_server_transf_handle(server, client_xfer, TFTP_SERVER_EVENT_TIMEOUT, packet);
+                }
+            }
+            continue;
+        }
+        else if (res < 0)
+        {
+            break;
+        }
+        else
+        {
+            /* Connection request handle */
+            if (FD_ISSET(_private->server_xfer->sock, &_private->fdr))
+            {
+                client_xfer = tftp_server_request_handle(server, packet);
+                if (client_xfer != NULL)
+                {
+                    tftp_server_transf_handle(server, client_xfer, TFTP_SERVER_EVENT_CONNECT, packet);
+                }
+            }
+            /* Client data handle */
+            for (i = 0; i < _private->table_num; i++)
+            {
+                if (_private->client_table[i].xfer != NULL &&
+                    FD_ISSET(_private->client_table[i].xfer->sock, &_private->fdr))
+                {
+                    client_xfer = tftp_client_xfer_get(server, i);
+                    tftp_server_transf_handle(server, client_xfer, TFTP_SERVER_EVENT_DATA, packet);
+                }
+            }
+        }
+    }
+    /* exit. destroy all client */
+    for (i = 0; i < _private->table_num; i++)
+    {
+        if (_private->client_table[i].xfer != NULL)
+        {
+            client_xfer = tftp_client_xfer_get(server, i);
+            tftp_client_xfer_destroy(server, client_xfer);
+        }
+    }
+    /* free server */
+    tftp_xfer_destroy(_private->server_xfer);
+    free(_private->client_table);
+    free(server->root_name);
+    free(server);
+    free(packet);
+    tftp_printf("tftp server stop!\n");
 }
 
-static void
-tftp_tmr(void* arg)
+struct tftp_server *tftp_server_create(const char *root_name, int port)
 {
-  LWIP_UNUSED_ARG(arg);
-  
-  tftp_state.timer++;
-
-  if (tftp_state.handle == NULL) {
-    return;
-  }
-
-  sys_timeout(TFTP_TIMER_MSECS, tftp_tmr, NULL);
-
-  if ((tftp_state.timer - tftp_state.last_pkt) > (TFTP_TIMEOUT_MSECS / TFTP_TIMER_MSECS)) {
-    if ((tftp_state.last_data != NULL) && (tftp_state.retries < TFTP_MAX_RETRIES)) {
-      LWIP_DEBUGF(TFTP_DEBUG | LWIP_DBG_STATE, ("tftp: timeout, retrying\n"));
-      resend_data();
-      tftp_state.retries++;
-    } else {
-      LWIP_DEBUGF(TFTP_DEBUG | LWIP_DBG_STATE, ("tftp: timeout\n"));
-      close_handle();
-    }
-  }
+    struct tftp_server_private *_private;
+    struct tftp_server *server;
+    int mem_len;
+
+    /* new server object */
+    mem_len = sizeof(struct tftp_server_private) + sizeof(struct tftp_server);
+    server = malloc(mem_len);
+    if (server == NULL)
+    {
+        return NULL;
+    }
+    /* init server object */
+    memset(server, 0, mem_len);
+    server->root_name = strdup(root_name);
+    if (server->root_name == NULL)
+    {
+        free(server);
+        return NULL;
+    }
+    _private = (struct tftp_server_private *)&server[1];
+    server->_private = _private;
+    mem_len = sizeof(struct tftp_client_xfer) * TFTP_SERVER_CONNECT_MAX;
+    /* malloc client queue */
+    _private->client_table = malloc(mem_len);
+    if (_private->client_table == NULL)
+    {
+        free(server);
+        return NULL;
+    }
+    memset(_private->client_table, 0, mem_len);
+    _private->table_num = TFTP_SERVER_CONNECT_MAX;
+    return server;
 }
 
-/** @ingroup tftp
- * Initialize TFTP server.
- * @param ctx TFTP callback struct
- */
-err_t 
-tftp_init(const struct tftp_context *ctx)
+void tftp_server_write_set(struct tftp_server *server, int is_write)
 {
-  err_t ret;
-
-  struct udp_pcb *pcb = udp_new_ip_type(IPADDR_TYPE_ANY);
-  if (pcb == NULL) {
-    return ERR_MEM;
-  }
-
-  ret = udp_bind(pcb, IP_ANY_TYPE, TFTP_PORT);
-  if (ret != ERR_OK) {
-    udp_remove(pcb);
-    return ret;
-  }
-
-  tftp_state.handle    = NULL;
-  tftp_state.port      = 0;
-  tftp_state.ctx       = ctx;
-  tftp_state.timer     = 0;
-  tftp_state.last_data = NULL;
-  tftp_state.upcb      = pcb;
-
-  udp_recv(pcb, recv, NULL);
-
-  return ERR_OK;
+    if (server != NULL)
+    {
+        server->is_write = is_write;
+    }
 }
 
-#endif /* LWIP_UDP */
+void tftp_server_destroy(struct tftp_server *server)
+{
+    if (server != NULL)
+    {
+        server->is_stop = 1;
+    }
+}

+ 0 - 94
tftp/tftp_server.h

@@ -1,94 +0,0 @@
-/****************************************************************//**
- *
- * @file tftp_server.h
- *
- * @author   Logan Gunthorpe <logang@deltatee.com>
- *
- * @brief    Trivial File Transfer Protocol (RFC 1350)
- *
- * Copyright (c) Deltatee Enterprises Ltd. 2013
- * 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: Logan Gunthorpe <logang@deltatee.com>
- *
- */
-
-#ifndef LWIP_HDR_APPS_TFTP_SERVER_H
-#define LWIP_HDR_APPS_TFTP_SERVER_H
-
-#include "tftp_opts.h"
-#include "lwip/err.h"
-#include "lwip/pbuf.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/** @ingroup tftp
- * TFTP context containing callback functions for TFTP transfers
- */
-struct tftp_context {
-  /**
-   * Open file for read/write.
-   * @param fname Filename
-   * @param mode Mode string from TFTP RFC 1350 (netascii, octet, mail)
-   * @param write Flag indicating read (0) or write (!= 0) access
-   * @returns File handle supplied to other functions
-   */
-  void* (*open)(const char* fname, const char* mode, u8_t write);
-  /**
-   * Close file handle
-   * @param handle File handle returned by open()
-   */
-  void (*close)(void* handle);
-  /**
-   * Read from file 
-   * @param handle File handle returned by open()
-   * @param buf Target buffer to copy read data to
-   * @param bytes Number of bytes to copy to buf
-   * @returns &gt;= 0: Success; &lt; 0: Error
-   */
-  int (*read)(void* handle, void* buf, int bytes);
-  /**
-   * Write to file
-   * @param handle File handle returned by open()
-   * @param pbuf PBUF adjusted such that payload pointer points
-   *             to the beginning of write data. In other words,
-   *             TFTP headers are stripped off.
-   * @returns &gt;= 0: Success; &lt; 0: Error
-   */
-  int (*write)(void* handle, struct pbuf* p);
-};
-
-err_t tftp_init(const struct tftp_context* ctx);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif /* LWIP_HDR_APPS_TFTP_SERVER_H */

+ 377 - 0
tftp/tftp_xfer.c

@@ -0,0 +1,377 @@
+/*
+ * Copyright (c) 2006-2019, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date           Author       Notes
+ * 2019-02-26     tyx          first implementation
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <sys/select.h>
+#include "tftp_xfer.h"
+#include "tftp.h"
+
+struct tftp_xfer_private
+{
+    struct sockaddr_in server;
+    struct sockaddr_in sender;
+    char *ip_addr;
+    uint16_t port;
+    uint16_t block;
+};
+
+static int tftp_recv_raw_data(struct tftp_xfer *xfer, void *buff, int len)
+{
+    struct tftp_xfer_private *_private;
+    int sender_len = sizeof(struct sockaddr_in);
+    int r_size = 0;
+
+    /* udp receive data */
+    _private = xfer->_private;
+    r_size = recvfrom(xfer->sock, buff, len, 0, (struct sockaddr *)&_private->sender, (socklen_t *)&sender_len);
+    if (r_size < 0)
+    {
+        return -TFTP_EXFER;
+    }
+    return r_size;
+}
+
+void tftp_transfer_err(struct tftp_xfer *xfer, uint16_t err_no, const char *err_msg)
+{
+    uint16_t *snd_packet;
+    struct tftp_xfer_private *_private;
+    int str_len;
+
+    /* Calculate error message length */
+    str_len = strlen(err_msg);
+    if (str_len > 512)
+    {
+        str_len = 512;
+    }
+    /* maloc mem */
+    snd_packet = malloc(str_len + 4);
+    if (snd_packet == NULL)
+    {
+        return;
+    }
+    _private = xfer->_private;
+    // Send err msg.
+    snd_packet[0] = htons(TFTP_CMD_ERROR);
+    snd_packet[1] = htons(err_no);
+    strncpy((char *)&snd_packet[2], err_msg, str_len);
+    sendto(xfer->sock, snd_packet, str_len + 4, 0, (struct sockaddr *)&_private->sender, sizeof(struct sockaddr_in));
+    free(snd_packet);
+}
+
+int tftp_resp_ack(struct tftp_xfer *xfer)
+{
+    uint16_t snd_packet[2];
+    struct tftp_xfer_private *_private;
+    int size;
+
+    _private = xfer->_private;
+    // Send ACK.
+    snd_packet[0] = htons(TFTP_CMD_ACK);
+    snd_packet[1] = htons(_private->block);
+    size = sendto(xfer->sock, snd_packet, sizeof(snd_packet), 0, (struct sockaddr *)&_private->sender, sizeof(struct sockaddr_in));
+    if (size != sizeof(snd_packet))
+    {
+        return -TFTP_EXFER;
+    }
+    return TFTP_OK;
+}
+
+int tftp_wait_ack(struct tftp_xfer *xfer)
+{
+    int r_size;
+    struct tftp_xfer_private *_private;
+    uint16_t recv_buff[4];
+
+    _private = xfer->_private;
+    /* Receiving raw data */
+    r_size = tftp_recv_raw_data(xfer, recv_buff, sizeof(recv_buff));
+    /* check is ack */
+    if (r_size >= 4 && ntohs(recv_buff[0]) == TFTP_CMD_ACK && ntohs(recv_buff[1]) == _private->block)
+    {
+        _private->block++;
+        return TFTP_OK;
+    }
+    if (r_size < 0)
+    {
+        return r_size;
+    }
+    else if (r_size < 4)
+    {
+        return -TFTP_EDATA;
+    }
+    else if (ntohs(recv_buff[0]) != TFTP_CMD_ACK)
+    {
+        return -TFTP_EACK;
+    }
+    else if (ntohs(recv_buff[1]) != _private->block)
+    {
+        return -TFTP_EBLK;
+    }
+    return -TFTP_EOTHER;
+}
+
+int tftp_read_data(struct tftp_xfer *xfer, struct tftp_packet *pack, int len)
+{
+    int r_size;
+    struct tftp_xfer_private *_private;
+
+    _private = xfer->_private;
+    /* Receiving raw data */
+    r_size = tftp_recv_raw_data(xfer, pack, len);
+    /* Check that the data is correct  */
+    if (r_size >= 4 && ntohs(pack->cmd) == TFTP_CMD_DATA && ntohs(pack->info.block) == (uint16_t)(_private->block + 1))
+    {
+        _private->block = ntohs(pack->info.block);
+        /* Return data length */
+        return r_size - 4;
+    }
+    if (r_size < 0)
+    {
+        return r_size;
+    }
+    else if (r_size < 4)
+    {
+        tftp_printf("Bad packet: r_size=%d\n", r_size);
+        return -TFTP_EDATA;
+    }
+    else if (ntohs(pack->cmd) == TFTP_CMD_ERROR)
+    {
+        tftp_printf("err[%d] msg:%s code:%d\n", ntohs(pack->info.code), pack->data, ntohs(pack->info.code));
+        return -TFTP_ECMD;
+    }
+    else if ((_private->block + 1) != pack->info.block)
+    {
+        tftp_printf("Bad block recv:%d != check:%d\n", _private->block + 1, pack->info.block);
+        return -TFTP_EBLK;
+    }
+
+    return -TFTP_EOTHER;
+}
+
+int tftp_write_data(struct tftp_xfer *xfer, struct tftp_packet *pack, int len)
+{
+    struct tftp_xfer_private *_private;
+    int size;
+
+    _private = xfer->_private;
+    /* Packing header */
+    pack->cmd = htons(TFTP_CMD_DATA);
+    pack->info.block = htons(_private->block);
+    /* Send data */
+    size = sendto(xfer->sock, pack, len, 0, (struct sockaddr *)&_private->sender, sizeof(struct sockaddr_in));
+    if (size != len)
+    {
+        return -TFTP_EXFER;
+    }
+    return size;
+}
+
+int tftp_send_request(struct tftp_xfer *xfer, uint16_t cmd, const char *remote_file)
+{
+    struct tftp_packet *send_packet;
+    struct tftp_xfer_private *_private;
+    int size, r_size;
+    int res;
+
+    _private = xfer->_private;
+    /* Check connection type */
+    if (xfer->type != TFTP_XFER_TYPE_CLIENT)
+    {
+        res = tftp_xfer_type_set(xfer, TFTP_XFER_TYPE_CLIENT);
+        if (res != TFTP_OK)
+        {
+            return res;
+        }
+    }
+    /* malloc mem */
+    send_packet = malloc(sizeof(struct tftp_packet));
+    if (send_packet == NULL)
+    {
+        return -TFTP_EMEM;
+    }
+    /* Packing request packet header */
+    send_packet->cmd = htons(cmd);
+    size = sprintf(send_packet->info.filename, "%s%c%s%c%d%c", remote_file, 0, xfer->mode, 0, xfer->blksize, 0) + 3;
+    /* send data */
+    r_size = sendto(xfer->sock, send_packet, size, 0, (struct sockaddr *)&_private->server, sizeof(struct sockaddr_in));
+    free(send_packet);
+    if (size != r_size)
+    {
+        return -TFTP_EXFER;
+    }
+    return TFTP_OK;
+}
+
+struct tftp_xfer *tftp_recv_request(struct tftp_xfer *xfer, struct tftp_packet *packet)
+{
+    struct tftp_xfer_private *_private;
+    int size, mem_size;
+    struct tftp_xfer *client_xfer = NULL;
+
+    _private = xfer->_private;
+    /* Check connection type */
+    if (xfer->type != TFTP_XFER_TYPE_SERVER)
+    {
+        if (tftp_xfer_type_set(xfer, TFTP_XFER_TYPE_SERVER) != true)
+        {
+            return NULL;
+        }
+    }
+
+    /* get packet size */
+    mem_size = sizeof(struct tftp_packet);
+    memset(packet, 0, mem_size);
+    /* Receiving raw data */
+    size = tftp_recv_raw_data(xfer, packet, mem_size);
+    if (size > 0)
+    {
+        /* Determine the type of request */
+        if (ntohs(packet->cmd) == TFTP_CMD_RRQ || ntohs(packet->cmd) == TFTP_CMD_WRQ)
+        {
+            /* Create connection */
+            client_xfer = tftp_xfer_create(inet_ntoa(_private->sender.sin_addr),
+                                           ntohs(_private->sender.sin_port));
+            /* Copy connection information */
+            if (client_xfer != NULL)
+            {
+                struct tftp_xfer_private *_client_private = client_xfer->_private;
+                memcpy(&_client_private->sender, &_private->sender, sizeof(struct sockaddr_in));
+            }
+            if (ntohs(packet->cmd) == TFTP_CMD_RRQ)
+            {
+                ((struct tftp_xfer_private *)client_xfer->_private)->block = 1;
+            }
+        }
+    }
+
+    return client_xfer;
+}
+
+int tftp_xfer_type_set(struct tftp_xfer *xfer, int type)
+{
+    struct tftp_xfer_private *_private;
+
+    _private = xfer->_private;
+    /* Setting connection type for client */
+    if (type == TFTP_XFER_TYPE_CLIENT)
+    {
+        /* Check whether the type is set */
+        if (xfer->type != TFTP_XFER_TYPE_CLIENT)
+        {
+            /* Server type has been set. return */
+            if (xfer->type == TFTP_XFER_TYPE_SERVER)
+            {
+                return -TFTP_EINVAL;
+            }
+            /* Initialize client connection */
+            _private->server.sin_family = PF_INET;
+            _private->server.sin_port = htons(_private->port);
+            _private->server.sin_addr.s_addr = inet_addr(_private->ip_addr);
+            xfer->type = TFTP_XFER_TYPE_CLIENT;
+            return TFTP_OK;
+        }
+    }
+    else if (type == TFTP_XFER_TYPE_SERVER)
+    {
+        if (xfer->type != TFTP_XFER_TYPE_SERVER)
+        {
+            if (xfer->type == TFTP_XFER_TYPE_CLIENT)
+            {
+                return -TFTP_EINVAL;
+            }
+            _private->server.sin_family = AF_INET;
+            _private->server.sin_addr.s_addr = INADDR_ANY;
+            _private->server.sin_port = htons(_private->port);
+            /* Binding port */
+            if (bind(xfer->sock, (struct sockaddr *)&_private->server, sizeof(struct sockaddr_in)) < 0)
+            {
+                tftp_printf("tftp server bind failed!! exit\n");
+                return -TFTP_ESYS;
+            }
+            xfer->type = TFTP_XFER_TYPE_SERVER;
+            return TFTP_OK;
+        }
+    }
+    return -TFTP_EINVAL;
+}
+
+void tftp_xfer_mode_set(struct tftp_xfer *xfer, const char *mode)
+{
+    if (xfer->mode)
+    {
+        free(xfer->mode);
+    }
+    xfer->mode = strdup(mode);
+}
+
+int tftp_xfer_blksize_set(struct tftp_xfer *xfer, int blksize)
+{
+    if ((blksize < 8) || (blksize > XFER_DATA_SIZE_MAX))
+    {
+        return -TFTP_EINVAL;
+    }
+    xfer->blksize = blksize;
+
+    return TFTP_OK;
+}
+
+struct tftp_xfer *tftp_xfer_create(const char *ip_addr, int port)
+{
+    int sock;
+    struct tftp_xfer *xfer;
+    struct tftp_xfer_private *_private;
+    int mem_len;
+
+    /* malloc connect object */
+    mem_len = sizeof(struct tftp_xfer) + sizeof(struct tftp_xfer_private);
+    xfer = malloc(mem_len);
+    if (xfer == NULL)
+    {
+        tftp_printf("can't create tftp transfer!! exit\n");
+        return NULL;
+    }
+    memset(xfer, 0, mem_len);
+    _private = (struct tftp_xfer_private *)&xfer[1];
+
+    /* create socket */
+    sock = socket(PF_INET, SOCK_DGRAM, 0);
+    if (sock < 0)
+    {
+        tftp_printf("can't create socket!! exit\n");
+        free(xfer);
+        return NULL;
+    }
+
+    /* Initialize private data */
+    _private->ip_addr = strdup(ip_addr);
+    _private->port = port;
+    _private->block = 0;
+    xfer->sock = sock;
+    xfer->mode = strdup(TFTP_XFER_OCTET);
+    xfer->blksize = XFER_DATA_SIZE_MAX;
+    xfer->_private = _private;
+    return xfer;
+}
+
+void tftp_xfer_destroy(struct tftp_xfer *xfer)
+{
+    struct tftp_xfer_private *_private;
+
+    /* free all mem */
+    _private = xfer->_private;
+    closesocket(xfer->sock);
+    free(_private->ip_addr);
+    free(xfer->mode);
+    free(xfer);
+}

+ 67 - 0
tftp/tftp_xfer.h

@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2006-2019, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date           Author       Notes
+ * 2019-02-26     tyx          first implementation
+ */
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#ifndef __TFTP_XFER_H__
+#define __TFTP_XFER_H__
+
+#define TFTP_CMD_RRQ        (1) /*Read request (RRQ)*/
+#define TFTP_CMD_WRQ        (2) /*Write request (WRQ) */
+#define TFTP_CMD_DATA       (3) /*Data (DATA)*/
+#define TFTP_CMD_ACK        (4) /*Acknowledgment (ACK)*/
+#define TFTP_CMD_ERROR      (5) /*Error (ERROR)*/
+
+#define TFTP_XFER_OCTET ("octet")
+#define TFTP_XFER_ASCII ("ascii")
+
+#define TFTP_XFER_TYPE_CLIENT (0x01)
+#define TFTP_XFER_TYPE_SERVER (0x02)
+
+#define XFER_DATA_SIZE_MAX (512)
+
+union file_info 
+{
+    uint16_t code;
+    uint16_t block;
+    char filename[2];
+};
+
+struct tftp_packet
+{
+    uint16_t cmd;
+    union file_info info;
+    char data[XFER_DATA_SIZE_MAX];
+};
+
+struct tftp_xfer
+{
+    int sock;
+    int type;
+    int blksize;
+    char *mode;
+    void *_private;
+};
+
+struct tftp_xfer *tftp_xfer_create(const char *ip_addr, int port);
+void tftp_xfer_destroy(struct tftp_xfer *xfer);
+int tftp_send_request(struct tftp_xfer *xfer, uint16_t cmd, const char *remote_file);
+struct tftp_xfer *tftp_recv_request(struct tftp_xfer *xfer, struct tftp_packet *packet);
+void tftp_xfer_mode_set(struct tftp_xfer *xfer, const char *mode);
+int tftp_xfer_blksize_set(struct tftp_xfer *xfer, int blksize);
+int tftp_xfer_type_set(struct tftp_xfer *xfer, int type);
+int tftp_read_data(struct tftp_xfer *xfer, struct tftp_packet *pack, int size);
+void tftp_transfer_err(struct tftp_xfer *xfer, uint16_t err_no, const char *err_msg);
+int tftp_wait_ack(struct tftp_xfer *xfer);
+int tftp_write_data(struct tftp_xfer *xfer, struct tftp_packet *pack, int len);
+int tftp_resp_ack(struct tftp_xfer *xfer);
+
+#endif