Browse Source

Very first connection and successful data transfer to a local server

datahandler
Laurent GONZALEZ 11 years ago
parent
commit
61b4940281
  1. 56
      README.md
  2. 425
      lwftp.c
  3. 65
      lwftp.h

56
README.md

@ -2,3 +2,59 @@ lwftp
=====
A lightweight FTP client using raw API of LWIP
This client is designed with a very low level interface, and can be
used as a library to build smarter clients with more features.
Only STOR operation is supported, and server must accept anonymous,
binary, passive connexions.
There is no storage back-end. The requester provides a callback to
source data.
* The callback is called once with a non null pointer to (char*), to be
used by the actual storage backend to write the location of data. The
return value is the length of available data, limited to the value of
argument maxlen.
* When the callback is called with a NULL pointer to (char*), the maxlen
argument is the number of bytes successfully sent since last call. This
shall be used by the storage backend as an acknowledge.
As an example, the LWFTP client can be called the following way:
static uint file_source(const char** pptr, uint maxlen)
{
static const uint mylen = 12345;
static const char * const mydata = (char*)0x20000000;
static uint offset = 0;
uint len = 0;
if (pptr) {
len = mylen - offset;
if ( len > maxlen ) len = maxlen;
*pptr = mydata + offset;
} else {
offset += maxlen;
if ( offset > mylen ) offset = mylen;
}
return len;
}
static void ftp_test(void)
{
lwftp_session_t s;
err_t error;
// Initialize session data
memset(&s, 0, sizeof(s));
IP4_ADDR(&s.server_ip, 192,168,0,31);
s.server_port = 21;
s.data_source = file_source;
s.remote_path = "/data.bin";
// Store this file
error = lwftp_store(&s);
if ( error != ERR_OK ) {
LOG_ERROR("lwftp_store returned %s", lwip_strerr(error));
}
}

425
lwftp.c

@ -0,0 +1,425 @@
/*
* 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>
*
*/
#include <string.h>
#include "lwftp.h"
#include "lwip/tcp.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)
/** 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, ("lwtcp: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(&data, s->data_pcb->mss);
if (len) {
LWIP_DEBUGF(LWFTP_TRACE, ("lwtcp:sending %d bytes of data\n",len));
error = tcp_write(s->data_pcb, data, len, 0);
if (error!=ERR_OK) {
LWIP_DEBUGF(LWFTP_SEVERE, ("lwtcp:write failure (%s), not implemented\n",lwip_strerr(error)));
}
}
}
if (!len) {
LWIP_DEBUGF(LWFTP_TRACE, ("lwtcp: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)
{
LWIP_DEBUGF(LWFTP_SEVERE, ("lwtcp:nothing implemented (line %d)\n",__LINE__));
return ERR_ABRT;
}
/** 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)
{
lwftp_session_t *s = (lwftp_session_t*)arg;
if ( s->data_source ) {
s->data_source(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_DEBUGF(LWFTP_SEVERE, ("lwtcp:nothing implemented (line %d)\n",__LINE__));
}
/** 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)
{
lwftp_session_t *s = (lwftp_session_t*)arg;
if ( err == ERR_OK ) {
LWIP_DEBUGF(LWFTP_TRACE, ("lwtcp:connected for data to server\n"));
s->data_state = LWFTP_CONNECTED;
} else {
LWIP_DEBUGF(LWFTP_WARNING, ("lwtcp:failed to connect for data to server (%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 data_port;
// Find server connection parameter
ptr = strchr(p->payload, '(');
if (!ptr) return ERR_BUF;
ip4_addr1(&data_server) = strtoul(ptr+1,&ptr,10);
ip4_addr2(&data_server) = strtoul(ptr+1,&ptr,10);
ip4_addr3(&data_server) = strtoul(ptr+1,&ptr,10);
ip4_addr4(&data_server) = strtoul(ptr+1,&ptr,10);
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, char* msg, size_t len)
{
err_t error;
LWIP_DEBUGF(LWFTP_TRACE,("lwtcp:sending %s",msg));
error = tcp_write(s->control_pcb, msg, len, 0);
if ( error != ERR_OK ) {
LWIP_DEBUGF(LWFTP_WARNING, ("lwtcp:cannot write (%s)\n",lwip_strerr(error)));
}
return error;
}
/** Close control connection
* @param pointer to lwftp session data
*/
static void lwftp_control_close(lwftp_session_t *s)
{
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;
}
/** 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)
{
uint response = 0;
// Try to get response number
if (p) {
response = strtoul(p->payload, NULL, 10);
LWIP_DEBUGF(LWFTP_TRACE, ("lwtcp:got response %d\n",response));
}
switch (s->control_state) {
case LWFTP_CONNECTED:
if (response>0) {
if (response==220) {
lwftp_send_msg(s, PTRNLEN("USER anonymous\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 none@nowhere.net\n"));
s->control_state = LWFTP_PASS_SENT;
} else {
s->control_state = LWFTP_QUIT;
}
}
break;
case LWFTP_PASS_SENT:
if (response>0) {
if (response==230) {
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);
lwftp_send_msg(s, PTRNLEN("STOR "));
lwftp_send_msg(s, s->remote_path, strlen(s->remote_path));
lwftp_send_msg(s, PTRNLEN("\n"));
s->control_state = LWFTP_STOR_SENT;
} else {
s->control_state = LWFTP_QUIT;
}
}
break;
case LWFTP_STOR_SENT:
if (response>0) {
if (response==150) {
s->control_state = LWFTP_STORING;
lwftp_data_sent(s,NULL,0);
} else {
s->control_state = LWFTP_QUIT;
}
}
break;
case LWFTP_STORING:
if (response>0) {
if (response!=226) {
LWIP_DEBUGF(LWFTP_WARNING, ("lwftp:expected 226, received %d\n",response));
}
s->control_state = LWFTP_QUIT;
}
break;
case LWFTP_QUIT_SENT:
if (response>0) {
if (response!=221) {
LWIP_DEBUGF(LWFTP_WARNING, ("lwftp:expected 221, received %d\n",response));
}
lwftp_control_close(s);
}
break;
default:
LWIP_DEBUGF(LWFTP_SEVERE, ("lwtcp:unhandled state (%d)\n",s->control_state));
}
// Quit when required to do so
if ( s->control_state == LWFTP_QUIT ) {
lwftp_send_msg(s, PTRNLEN("QUIT\n"));
s->control_state = LWFTP_QUIT_SENT;
}
}
/** 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, ("lwtcp:connection closed by remote host\n"));
lwftp_control_close(s);
}
} else {
LWIP_DEBUGF(LWFTP_SERIOUS, ("lwtcp:failed to receive (%s)\n",lwip_strerr(err)));
lwftp_control_close(s);
}
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)
{
LWIP_DEBUGF(LWFTP_TRACE, ("lwtcp: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_DEBUGF(LWFTP_SEVERE, ("lwtcp:nothing implemented (line %d)\n",__LINE__));
}
/** 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)
{
lwftp_session_t *s = (lwftp_session_t*)arg;
if ( err == ERR_OK ) {
LWIP_DEBUGF(LWFTP_TRACE, ("lwtcp:connected to server\n"));
s->control_state = LWFTP_CONNECTED;
} else {
LWIP_DEBUGF(LWFTP_WARNING, ("lwtcp:failed to connect to server (%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->remote_path || s->control_pcb || s->data_pcb) {
LWIP_DEBUGF(LWFTP_WARNING, ("lwtcp:invalid session data\n"));
return ERR_ARG;
}
// Get sessions pcb
s->control_pcb = tcp_new();
if (!s->control_pcb) {
LWIP_DEBUGF(LWFTP_SERIOUS, ("lwtcp: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, ("lwtcp: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->server_ip, s->server_port, lwftp_control_connected);
if ( error == ERR_OK ) goto exit;
LWIP_DEBUGF(LWFTP_SERIOUS, ("lwtcp:cannot connect control_pcb (%s)\n", lwip_strerr(error)));
close_pcb:
// Release pcbs in case of failure
lwftp_control_close(s);
exit:
return error;
}

65
lwftp.h

@ -0,0 +1,65 @@
/*
* 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>
*
*/
#include "lwip/opt.h"
#include "lwip/ip.h"
/** LWFTP control connection state */
typedef enum {
LWFTP_CLOSED,
LWFTP_CONNECTED,
LWFTP_USER_SENT,
LWFTP_PASS_SENT,
LWFTP_TYPE_SENT,
LWFTP_PASV_SENT,
LWFTP_STOR_SENT,
LWFTP_STORING,
LWFTP_QUIT,
LWFTP_QUIT_SENT,
} lwtcp_state_t;
/** LWFTP session structure */
typedef struct {
// User interface
ip_addr_t server_ip;
u16_t server_port;
char *remote_path;
uint (*data_source)(const char**, uint);
// Internal data
lwtcp_state_t control_state;
lwtcp_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);
Loading…
Cancel
Save