/*
 * Copyright (c) 2005, Swedish Institute of Computer Science
 * 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.
 *
 */

/**
 *
 * \file
 * Code propagation and storage.
 * \author
 * Adam Dunkels <adam@sics.se>
 *
 * This file implements a simple form of code propagation, which
 * allows a binary program to be downloaded and propagated throughout
 * a network of devices.
 *
 * Features:
 *
 *    Commands: load code, start code
 *    Point-to-point download over TCP
 *    Point-to-multipoint delivery over UDP broadcasts
 *    Versioning of code modules
 *
 * Procedure:
 *
 *    1. Receive code over TCP
 *    2. Send code packets over UDP
 *
 *    When a code packet is deemed to be missed, a NACK is sent. If a
 *    NACK is received, the sending restarts at the point in the
 *    binary where the NACK pointed to. (This is *not* very efficient,
 *    but simple to implement...)
 *
 * States:
 *
 *  Receiving code header -> receiving code -> sending code
 *
 */

#include "contiki.h"
#include "sys/clock.h"

#include "loader/elfloader.h"
#include "net/ip/tcpip.h"

#include "dev/eeprom.h"
#include "dev/leds.h"

#include "lib/random.h"

#include "codeprop.h"

void codeprop_set_rate(clock_time_t time);

/*static int random_rand(void) { return 1; }*/

#if 0
#define PRINTF(x) printf x
#else
#define PRINTF(x)
#endif

#define START_TIMEOUT 12 * CLOCK_SECOND
#define MISS_NACK_TIMEOUT (CLOCK_SECOND / 8) * (random_rand() % 8)
#define HIT_NACK_TIMEOUT (CLOCK_SECOND / 8) * (8 + random_rand() % 16)
#define NACK_REXMIT_TIMEOUT CLOCK_SECOND * (4 + random_rand() % 4)

#define WAITING_TIME CLOCK_SECOND * 10

#define NUM_SEND_DUPLICATES 2

#define UDPHEADERSIZE 8
#define UDPDATASIZE   32

struct codeprop_udphdr {
  uint16_t id;
  uint16_t type;
#define TYPE_DATA 0x0001
#define TYPE_NACK 0x0002
  uint16_t addr;
  uint16_t len;
  uint8_t data[UDPDATASIZE];
};

static void uipcall(void *state);

PROCESS(codeprop_process, "Code propagator");

struct codeprop_state {
  uint8_t state;
#define STATE_NONE              0
#define STATE_RECEIVING_TCPDATA 1
#define STATE_RECEIVING_UDPDATA 2
#define STATE_SENDING_UDPDATA   3
  uint16_t count;
  uint16_t addr;
  uint16_t len;
  uint16_t id;
  struct etimer sendtimer;
  struct timer nacktimer, timer, starttimer;
  uint8_t received;
  uint8_t send_counter;
  struct pt tcpthread_pt;
  struct pt udpthread_pt;
  struct pt recv_udpthread_pt;
};

static struct uip_udp_conn *udp_conn;

static struct codeprop_state s;

process_event_t codeprop_event_quit;

void system_log(char *msg);

static clock_time_t send_time;

#define CONNECTION_TIMEOUT (30 * CLOCK_SECOND)


enum {
  EVENT_START_PROGRAM
};
/*---------------------------------------------------------------------*/
void
codeprop_set_rate(clock_time_t time)
{
  send_time = time;
}
/*---------------------------------------------------------------------*/
PROCESS_THREAD(codeprop_process, ev, data)
{
  PROCESS_BEGIN();
  
  s.id = 0/*random_rand()*/;
  
  send_time = CLOCK_SECOND/4;
  
  PT_INIT(&s.udpthread_pt);
  PT_INIT(&s.recv_udpthread_pt);
  
  tcp_listen(UIP_HTONS(CODEPROP_DATA_PORT));
  
  udp_conn = udp_broadcast_new(UIP_HTONS(CODEPROP_DATA_PORT), NULL);
  
  codeprop_event_quit = process_alloc_event();
  
  s.state = STATE_NONE;
  s.received = 0;
  s.addr = 0;
  s.len = 0;

  while(1) {

    PROCESS_YIELD();
  
    if(ev == EVENT_START_PROGRAM) {
      /* First kill old program. */
      elfloader_unload();
      elfloader_load(EEPROMFS_ADDR_CODEPROP);
    } else if(ev == tcpip_event) {
      uipcall(data);
    } else if(ev == PROCESS_EVENT_TIMER) {
      tcpip_poll_udp(udp_conn);
    }
  }

  PROCESS_END();
}
/*---------------------------------------------------------------------*/
static uint16_t
send_udpdata(struct codeprop_udphdr *uh)
{
  uint16_t len;
  
  uh->type = UIP_HTONS(TYPE_DATA);
  uh->addr = uip_htons(s.addr);
  uh->id = uip_htons(s.id);

  if(s.len - s.addr > UDPDATASIZE) {
    len = UDPDATASIZE;
  } else {
    len = s.len - s.addr;
  }

  eeprom_read(EEPROMFS_ADDR_CODEPROP + s.addr,
	      &uh->data[0], len);
  
  uh->len = uip_htons(s.len);

  PRINTF(("codeprop: sending packet from address 0x%04x\n", s.addr));
  uip_udp_send(len + UDPHEADERSIZE);

  return len;
}
/*---------------------------------------------------------------------*/
static
PT_THREAD(send_udpthread(struct pt *pt))
{
  int len;
  struct codeprop_udphdr *uh = (struct codeprop_udphdr *)uip_appdata;


  PT_BEGIN(pt);

  while(1) {
    PT_WAIT_UNTIL(pt, s.state == STATE_SENDING_UDPDATA);

    for(s.addr = 0; s.addr < s.len; ) {
      len = send_udpdata(uh);
      s.addr += len;
      
      etimer_set(&s.sendtimer, CLOCK_SECOND/4);
      do {
	PT_WAIT_UNTIL(pt, uip_newdata() || etimer_expired(&s.sendtimer));
	
	if(uip_newdata()) {
	  if(uh->type == UIP_HTONS(TYPE_NACK)) {
	    PRINTF(("send_udpthread: got NACK for address 0x%x (now 0x%x)\n",
		    uip_htons(uh->addr), s.addr));
	    /* Only accept a NACK if it points to a lower byte. */
	    if(uip_htons(uh->addr) <= s.addr) {
	      s.addr = uip_htons(uh->addr);
	    }
	  }
	  PT_YIELD(pt);
	}
      } while(!etimer_expired(&s.sendtimer));
    }
    
    s.state = STATE_NONE;

    process_post(PROCESS_BROADCAST, codeprop_event_quit, (process_data_t)NULL);
  }
  PT_END(pt);
}
/*---------------------------------------------------------------------*/
static void
send_nack(struct codeprop_udphdr *uh, unsigned short addr)
{
  uh->type = UIP_HTONS(TYPE_NACK);
  uh->addr = uip_htons(addr);
  uip_udp_send(UDPHEADERSIZE);
}
/*---------------------------------------------------------------------*/
static
PT_THREAD(recv_udpthread(struct pt *pt))
{
  int len;
  struct codeprop_udphdr *uh = (struct codeprop_udphdr *)uip_appdata;
  
  /*  if(uip_newdata()) {
    PRINTF(("recv_udpthread: id %d uh->id %d\n", s.id, uip_htons(uh->id)));
    }*/
  
  PT_BEGIN(pt);
  
  while(1) {

    do {
      PT_WAIT_UNTIL(pt, uip_newdata() &&
		    uh->type == UIP_HTONS(TYPE_DATA) &&
		    uip_htons(uh->id) > s.id);
      
      if(uip_htons(uh->addr) != 0) {
	s.addr = 0;
	send_nack(uh, 0);
      }
      
    } while(uip_htons(uh->addr) != 0);

    leds_on(LEDS_YELLOW);
    
    s.addr = 0;
    s.id = uip_htons(uh->id);
    s.len = uip_htons(uh->len);
    
    timer_set(&s.timer, CONNECTION_TIMEOUT);
    process_post(PROCESS_BROADCAST, codeprop_event_quit, (process_data_t)NULL);

    while(s.addr < s.len) {
            
      if(uip_htons(uh->addr) == s.addr) {
	leds_blink();
	len = uip_datalen() - UDPHEADERSIZE;
	if(len > 0) {
	  eeprom_write(EEPROMFS_ADDR_CODEPROP + s.addr,
		       &uh->data[0], len);
	  PRINTF(("Saved %d bytes at address %d, %d bytes left\n",
		  uip_datalen() - UDPHEADERSIZE, s.addr,
		  s.len - s.addr));
	  
	  s.addr += len;
	}
	
      } else if(uip_htons(uh->addr) > s.addr) {
	PRINTF(("sending nack since 0x%x != 0x%x\n", uip_htons(uh->addr), s.addr));
	send_nack(uh, s.addr);
      }

      if(s.addr < s.len) {

	/*	timer_set(&s.nacktimer, NACK_TIMEOUT);*/

	do {
	  timer_set(&s.nacktimer, HIT_NACK_TIMEOUT);
	  PT_YIELD_UNTIL(pt, timer_expired(&s.nacktimer) ||
			 (uip_newdata() &&
			  uh->type == UIP_HTONS(TYPE_DATA) &&
			  uip_htons(uh->id) == s.id));
	  if(timer_expired(&s.nacktimer)) {
	    send_nack(uh, s.addr);
	  }
	} while(timer_expired(&s.nacktimer));
      }
      
    }

    leds_off(LEDS_YELLOW);
    /*    printf("Received entire bunary over udr\n");*/
    process_post(PROCESS_BROADCAST, codeprop_event_quit, (process_data_t)NULL);
    process_post(&codeprop_process, EVENT_START_PROGRAM, NULL);
    PT_EXIT(pt);
  }
  
  PT_END(pt);
}
/*---------------------------------------------------------------------*/
static
PT_THREAD(recv_tcpthread(struct pt *pt))
{
  uint8_t *dataptr;
  struct codeprop_tcphdr *th;
  int datalen = uip_datalen();
  
  PT_BEGIN(pt);

  while(1) {
    
    PT_WAIT_UNTIL(pt, uip_connected());
    
    s.state = STATE_RECEIVING_TCPDATA;
    
    s.addr = 0;
    s.count = 0;
    process_post(PROCESS_BROADCAST, codeprop_event_quit, (process_data_t)NULL);


    /* Read the header. */
    PT_WAIT_UNTIL(pt, uip_newdata() && uip_datalen() > 0);
    dataptr = uip_appdata;

    if(uip_datalen() < sizeof(struct codeprop_tcphdr)) {
      PRINTF(("codeprop: header not found in first tcp segment\n"));
      uip_abort();
    }
    th = (struct codeprop_tcphdr *)uip_appdata;
    s.len = uip_htons(th->len);
    s.addr = 0;
    uip_appdata += sizeof(struct codeprop_tcphdr);
    datalen = uip_datalen() - sizeof(struct codeprop_tcphdr);

    /* Read the rest of the data. */
    do {
      if(datalen > 0) {
	/*	printf("Got %d bytes\n", uip_len);*/
	eeprom_write(EEPROMFS_ADDR_CODEPROP + s.addr,
		     uip_appdata,
		     datalen);
	s.addr += datalen;
      }
      if(s.addr < s.len) {
	PT_YIELD_UNTIL(pt, uip_newdata());
      }
    } while(s.addr < s.len);

    /* Print out the "ok" message. */
    do {
      uip_send("ok\r\n", 4);
      PT_WAIT_UNTIL(pt, uip_acked() || uip_rexmit() || uip_closed());
    } while(uip_rexmit());

    /* Close the connection. */
    uip_close();
    
    ++s.id;
    s.state = STATE_SENDING_UDPDATA;
    tcpip_poll_udp(udp_conn);

    codeprop_start_program();
    
    PT_WAIT_UNTIL(pt, s.state != STATE_SENDING_UDPDATA);
    /*    printf("recv_tcpthread: unblocked\n");*/
  }
  
  PT_END(pt);
}
/*---------------------------------------------------------------------*/
void
codeprop_start_broadcast(unsigned int len)
{
  s.addr = 0;
  s.len = len;
  ++s.id;
  s.state = STATE_SENDING_UDPDATA;
  tcpip_poll_udp(udp_conn);
}
/*---------------------------------------------------------------------*/
void
codeprop_start_program(void)
{
  process_post(PROCESS_BROADCAST, codeprop_event_quit, (process_data_t)NULL);
  process_post(&codeprop_process, EVENT_START_PROGRAM, NULL);
}
/*---------------------------------------------------------------------*/
static void
uipcall(void *state)
{
  if(uip_udpconnection()) {
    recv_udpthread(&s.recv_udpthread_pt);
    send_udpthread(&s.udpthread_pt);
  } else {
    if(uip_conn->lport == UIP_HTONS(CODEPROP_DATA_PORT)) {
      if(uip_connected()) {

	if(state == NULL) {
	  s.addr = 0;
	  s.count = 0;
	  PT_INIT(&s.tcpthread_pt);
	  process_poll(&codeprop_process);
	  tcp_markconn(uip_conn, &s);
	  process_post(PROCESS_BROADCAST, codeprop_event_quit,
		       (process_data_t)NULL);
	} else {
	  PRINTF(("codeprop: uip_connected() and state != NULL\n"));
	  uip_abort();
	}
      }
      recv_tcpthread(&s.tcpthread_pt);


      if(uip_closed() || uip_aborted() || uip_timedout()) {
	PRINTF(("codeprop: connection down\n"));
	tcp_markconn(uip_conn, NULL);
      }
    }
  }
}
/*---------------------------------------------------------------------*/