/*
 * Copyright (c) 2004, Adam Dunkels.
 * 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. Neither the name of the Institute nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``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 INSTITUTE OR CONTRIBUTORS 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.
 *
 * This file is part of the Contiki operating system.
 *
 * Author: Adam Dunkels <adam@sics.se>
 *
 * $Id: ftpc.c,v 1.2 2006/08/21 21:37:37 oliverschmidt Exp $
 */
#include "contiki.h"
#include "ftpc.h"

#include <string.h>
#include <stdio.h>

#define ISO_nl 0x0a
#define ISO_cr 0x0d

#define DATAPORT 6510

#define MAX_FILENAMELEN 32

struct ftp_dataconn {
  unsigned char type;
  unsigned char conntype;
#define CONNTYPE_LIST 0
#define CONNTYPE_FILE 1

  u16_t port;
  
  unsigned char filenameptr;
  char filename[MAX_FILENAMELEN];

  
};

struct ftp_connection {
  unsigned char type;
#define TYPE_CONTROL 1
#define TYPE_DATA    2
#define TYPE_ABORT   3
#define TYPE_CLOSE   4

  unsigned char state;
#define STATE_NONE         0
#define STATE_INITIAL      1
#define STATE_SEND_USER    2
#define STATE_USER_SENT    3
#define STATE_SEND_PASS    4
#define STATE_PASS_SENT    5
#define STATE_SEND_PORT    6
#define STATE_PORT_SENT    7
#define STATE_SEND_OPTIONS 8
#define STATE_OPTION_SENT  9
#define STATE_CONNECTED   10
#define STATE_SEND_NLST   11
#define STATE_NLST_SENT   12
#define STATE_SEND_RETR   13
#define STATE_RETR_SENT   14
  
#define STATE_SEND_CWD    15
#define STATE_CWD_SENT    16

#define STATE_SEND_CDUP   17
#define STATE_CDUP_SENT   18

#define STATE_SEND_QUIT   19
#define STATE_QUIT_SENT   20
  
  unsigned char connected_confirmed;
  
  struct ftp_dataconn dataconn;
  
  char code[3];
  unsigned char codeptr;

  unsigned char optionsptr;

  char filename[MAX_FILENAMELEN];

};

#define NUM_OPTIONS 1
static const struct {
  unsigned char num;
  char *commands[NUM_OPTIONS];
} options = {
  NUM_OPTIONS,
  {"TYPE I\r\n"}
};

static struct ftp_connection *waiting_for_dataconn;

MEMB(connections, struct ftp_connection, 1);

/*---------------------------------------------------------------------------*/
void
ftpc_init(void)
{
  memb_init(&connections);
  /*  tcp_listen(HTONS(DATAPORT));*/
}
/*---------------------------------------------------------------------------*/
void *
ftpc_connect(u16_t *ipaddr, u16_t port)
{
  struct ftp_connection *c;

  c = (struct ftp_connection *)memb_alloc(&connections);
  if(c == NULL) {
    return NULL;
  }
  c->type = TYPE_CONTROL;
  c->state = STATE_INITIAL;
  c->connected_confirmed = 0;
  c->codeptr = 0;
  c->dataconn.type = TYPE_DATA;
  c->dataconn.port = DATAPORT;
  tcp_listen(HTONS(DATAPORT));

  if(tcp_connect((uip_ipaddr_t *)ipaddr, port, c) == NULL) {
    memb_free(&connections, c);
    return NULL;
  }

  return c;
}
/*---------------------------------------------------------------------------*/
static void
handle_input(struct ftp_connection *c)
{
  int code;

  code = (c->code[0] - '0') * 100 +
    (c->code[1] - '0') * 10 +
    (c->code[2] - '0');
  /*  printf("Handle input code %d state %d\n", code, c->state);*/

  if(c->state == STATE_INITIAL) {
    if(code == 220) {
      c->state = STATE_SEND_USER;
    }
  } else if(c->state == STATE_USER_SENT) {
    if(code == 331) {
      c->state = STATE_SEND_PASS;
    }
  } else if(c->state == STATE_PASS_SENT) {
    if(code == 230) {
      c->state = STATE_SEND_OPTIONS;
      c->optionsptr = 0;
    }
  } else if(c->state == STATE_PORT_SENT) {
    c->state = STATE_CONNECTED;
    if(c->connected_confirmed == 0) {
      ftpc_connected(c);
      c->connected_confirmed = 1;
    }
  } else if(c->state == STATE_OPTION_SENT) {
    if(c->optionsptr >= options.num) {
      c->state = STATE_SEND_PORT;
    } else {
      c->state = STATE_SEND_OPTIONS;
    }
  } else if((c->state == STATE_NLST_SENT ||
	     c->state == STATE_RETR_SENT ||
	     c->state == STATE_CONNECTED)) {
    if(code == 226 || code == 550) {
      tcp_unlisten(htons(c->dataconn.port));
      ++c->dataconn.port;
      tcp_listen(htons(c->dataconn.port));
      c->state = STATE_SEND_PORT;
    }

    if(code == 550) {
      ftpc_list_file(NULL);
    }
  } else if(c->state == STATE_CWD_SENT ||
	    c->state == STATE_CDUP_SENT) {
    c->state = STATE_CONNECTED;
    ftpc_cwd_done(code);
    /*  } else if(c->state == STATE_) {
	c->state = STATE_CONNECTED;*/
  }
}
/*---------------------------------------------------------------------------*/
static void
newdata(struct ftp_connection *c)
{
  u16_t i;
  u8_t d;
  
  for(i = 0; i < uip_datalen(); ++i) {
    d = ((char *)uip_appdata)[i];
    if(c->codeptr < sizeof(c->code)) {
      c->code[c->codeptr] = d;
      ++c->codeptr;
    }

    if(d == ISO_nl) {
      handle_input(c);
      c->codeptr = 0;
    }
  }
}
/*---------------------------------------------------------------------------*/
static void
acked(struct ftp_connection *c)
{
  switch(c->state) {
  case STATE_SEND_USER:
    c->state = STATE_USER_SENT;
    break;
  case STATE_SEND_PASS:
    c->state = STATE_PASS_SENT;
    break;
  case STATE_SEND_PORT:
    c->state = STATE_PORT_SENT;
    break;
  case STATE_SEND_OPTIONS:
    ++c->optionsptr;
    c->state = STATE_OPTION_SENT;
    break;
  case STATE_SEND_NLST:
    c->state = STATE_NLST_SENT;
    break;
  case STATE_SEND_RETR:
    c->state = STATE_RETR_SENT;
    break;
  case STATE_SEND_CWD:
    c->state = STATE_CWD_SENT;
    break;
  case STATE_SEND_CDUP:
    c->state = STATE_CDUP_SENT;
    break;
  case STATE_SEND_QUIT:
    c->state = STATE_QUIT_SENT;
    uip_close();
    break;
  }
}
/*---------------------------------------------------------------------------*/
static void
senddata(struct ftp_connection *c)
{
  u16_t len;
  
  switch(c->state) {
  case STATE_SEND_USER:
    strcpy(uip_appdata, "USER ");
    strncpy((char *)uip_appdata + 5, ftpc_username(), uip_mss() - 7);
    len = (u16_t)strlen(ftpc_username());
    strcpy((char *)uip_appdata + 5 + len, "\r\n");
    uip_send(uip_appdata, len + 2 + 5);
    break;
  case STATE_SEND_PASS:
    strcpy(uip_appdata, "PASS ");
    strncpy((char *)uip_appdata + 5, ftpc_password(), uip_mss() - 7);
    len = (u16_t)strlen(ftpc_password());
    strcpy((char *)uip_appdata + 5 + len, "\r\n");
    uip_send(uip_appdata, len + 2 + 5);
    break;
  case STATE_SEND_PORT:
    len = sprintf(uip_appdata, "PORT %d,%d,%d,%d,%d,%d\n",
		  uip_ipaddr1(&uip_hostaddr),
		  uip_ipaddr2(&uip_hostaddr),
		  uip_ipaddr3(&uip_hostaddr),
		  uip_ipaddr4(&uip_hostaddr),
		  (c->dataconn.port) >> 8,
		  (c->dataconn.port) & 0xff);
    uip_send(uip_appdata, len);
    break;
  case STATE_SEND_OPTIONS:
    len = (u16_t)strlen(options.commands[c->optionsptr]);
    uip_send(options.commands[c->optionsptr], len);
    break;
  case STATE_SEND_NLST:
    uip_send("NLST\r\n", 6);
    break;
  case STATE_SEND_RETR:
    len = sprintf(uip_appdata, "RETR %s\r\n", c->filename);
    uip_send(uip_appdata, len);
    break;
  case STATE_SEND_CWD:
    len = sprintf(uip_appdata, "CWD %s\r\n", c->filename);
    uip_send(uip_appdata, len);
    break;
  case STATE_SEND_CDUP:
    uip_send("CDUP\r\n", 6);
    break;
  case STATE_SEND_QUIT:
    uip_send("QUIT\r\n", 6);
    break;
  }
}
/*---------------------------------------------------------------------------*/
void
ftpc_appcall(void *state)
{
  int i, t;
  struct ftp_connection *c = (struct ftp_connection *)state;
  struct ftp_dataconn *d = (struct ftp_dataconn *)state;
    
  if(uip_connected()) {
    if(state == NULL) {
      if(waiting_for_dataconn != NULL) {
	d = &waiting_for_dataconn->dataconn;
	waiting_for_dataconn = NULL;
	tcp_markconn(uip_conn, d);
	d->filenameptr = 0;
	
      } else {
	uip_abort();
      }
    } else {
      /*      tcp_listen(uip_conn->lport);*/
      senddata(c);
    }
    return;
  }

  if(c->type == TYPE_ABORT) {
    uip_abort();
    return;
  }

  if(c->type == TYPE_CLOSE) {
    uip_close();
    c->type = TYPE_CONTROL;
    return;
  }

  if(c->type == TYPE_CONTROL) {
    if(uip_closed()) {
      c->dataconn.type = TYPE_ABORT;
      ftpc_closed();
      memb_free(&connections, c);
    }
    if(uip_aborted()) {
      c->dataconn.type = TYPE_ABORT;
      ftpc_aborted();
      memb_free(&connections, c);
    }
    if(uip_timedout()) {
      c->dataconn.type = TYPE_ABORT;
      ftpc_timedout();
      memb_free(&connections, c);
    }


    if(uip_acked()) {
      acked(c);
    }
    if(uip_newdata()) {
      newdata(c);
    }
    if(uip_rexmit() ||
       uip_newdata() ||
     uip_acked()) {
      senddata(c);
    } else if(uip_poll()) {
      senddata(c);
    }
  } else {
    if(d->conntype == CONNTYPE_LIST) {
      if(uip_newdata()) {
	for(i = 0; i < uip_datalen(); ++i) {
	  t = ((char *)uip_appdata)[i];
	  
	  if(d->filenameptr < sizeof(d->filename) - 1 &&
	     t != ISO_cr &&
	     t != ISO_nl) {
	    d->filename[d->filenameptr] = t;
	    ++d->filenameptr;
	  }
	  
	  if(t == ISO_nl) {
	    d->filename[d->filenameptr] = 0;
	    ftpc_list_file(d->filename);
	    d->filenameptr = 0;
	  }

	}
      }
      if(uip_closed()) {
	ftpc_list_file(NULL);
      }
    } else {
      if(uip_newdata()) {
	ftpc_data(uip_appdata, uip_datalen());
	/*	printf("Received %d data bytes: '%s'\n",
		uip_datalen(), uip_appdata);*/
      } else if(uip_closed() || uip_timedout() || uip_aborted()) {
	ftpc_data(NULL, 0);
      }
    }
  }
}
/*---------------------------------------------------------------------------*/
char
ftpc_list(void *conn)
{
  struct ftp_connection *c;

  c = conn;
  
  if(c == NULL ||
     c->state != STATE_CONNECTED) {
    return 0;
  }

  c->state = STATE_SEND_NLST;
  c->dataconn.conntype = CONNTYPE_LIST;
  waiting_for_dataconn = c;
  return 1;
}
/*---------------------------------------------------------------------------*/
char
ftpc_get(void *conn, char *filename)
{
  struct ftp_connection *c;

  c = conn;
  
  if(c == NULL ||
     c->state != STATE_CONNECTED) {
    return 0;
  }

  strncpy(c->filename, filename, sizeof(c->filename));

  c->state = STATE_SEND_RETR;
  c->dataconn.conntype = CONNTYPE_FILE;
  waiting_for_dataconn = c;
  return 1;
}
/*---------------------------------------------------------------------------*/
void
ftpc_close(void *conn)
{
  struct ftp_connection *c;
  
  c = conn;
  
  if(c == NULL) {
    return;
  }

  c->type = TYPE_CLOSE;
}
/*---------------------------------------------------------------------------*/
void
ftpc_cwd(void *conn, char *dirname)
{
  struct ftp_connection *c;
  
  c = conn;
  
  if(c == NULL ||
     c->state != STATE_CONNECTED) {
    return;
  }

  strncpy(c->filename, dirname, sizeof(c->filename));
  c->state = STATE_SEND_CWD;
}
/*---------------------------------------------------------------------------*/
void
ftpc_cdup(void *conn)
{
  struct ftp_connection *c;
  
  c = conn;
  
  if(c == NULL ||
     c->state != STATE_CONNECTED) {
    return;
  }
  
  c->state = STATE_SEND_CDUP;
}
/*---------------------------------------------------------------------------*/
