#include <cnet.h>
#include <stdlib.h>

#define	MAXHOPS		4

/*
    This is an implementation of the simplest (naive) flooding algorithm in
    cnet. Whenever a new network layer packet requires delivery, it is
    transmitted on *all* physical links. To limit the combinatoric
    explosion in the number of data frames in the whole network, data
    frames are disgarded after they have travelled MAXHOPS hops.

    The purpose of this example is to demonstrate the flooding process
    itself, and for this reason only a minimal datalink layer protocol is
    provided to deliver the frames. As this flooding protocol assumes a
    reliable datalink layer protocol, CNET_write_physical_reliable() is
    called directly. Notice, however, the separation of the network layer
    and the datalink layer code.

    This naive flooding algorithm exhibits a very poor efficiency.
    With the 7 nodes in the AUSTRALIA.MAP file, the efficiency is typically
    only about 2%.

    Execute this protocol with the command:	cnet FLOODING1
 */


typedef enum    	{ NL_DATA, NL_ACK }   NL_PACKETKIND;

typedef struct {
    CnetAddr		src;
    CnetAddr		dest;
    NL_PACKETKIND	kind;      	/* only ever NL_DATA or NL_ACK */
    int			seqno;		/* 0, 1, 2, ... */
    int			hopsleft;	/* time to live */
    int			length;       	/* the length of the msg portion only */
    char		msg[MAX_MESSAGE_SIZE];
} NL_PACKET;

#define PACKET_HEADER_SIZE  \
		    (2*sizeof(CnetAddr)+sizeof(NL_PACKETKIND)+3*sizeof(int))
#define PACKET_SIZE(p)	    (PACKET_HEADER_SIZE + p.length)



/* -------- A SIMPLE NETWORK LAYER TABLE AS AN ABSTRACT DATA TYPE -------- */


typedef struct {
    CnetAddr	address;
    int		ackexpected;
    int		nextframetosend;
    int		frameexpected;
} NL_TABLE;

static	NL_TABLE	*table;
static	int		table_size;


static void init_NL()
{
    table	= (NL_TABLE *)malloc(sizeof(NL_TABLE));
    table_size	= 0;
}


static int find_address(CnetAddr address)
{
    int	t;

    for(t=0 ; t<table_size ; ++t)
	if(table[t].address == address)
	    return(t);

    table	= (NL_TABLE *)realloc((char *)table,
					(table_size+1)*sizeof(NL_TABLE));

    table[table_size].address		= address;
    table[table_size].ackexpected	= 0;
    table[table_size].nextframetosend	= 0;
    table[table_size].frameexpected	= 0;
    return(table_size++);
}

static int ackexpected(CnetAddr address) {
    return(table[ find_address(address) ].ackexpected);
}

static void inc_ackexpected(CnetAddr address) {
    table[ find_address(address) ].ackexpected++;
}

static int nextframetosend(CnetAddr address) {
    return(table[ find_address(address) ].nextframetosend++);
}

static int frameexpected(CnetAddr address) {
    return(table[ find_address(address) ].frameexpected);
}

static void inc_frameexpected(CnetAddr address) {
    table[ find_address(address) ].frameexpected++;
}

static void show_table(CnetEvent ev, CnetTimer timer, CnetData data)
{
    int	t;

    CNET_clear();
    printf("\n%16s %16s %16s %16s\n",
	    "destination", "ackexpected", "nextframetosend", "frameexpected");
    for(t=0 ; t<table_size ; ++t)
	printf("%16d %16d %16d %16d\n",
		(int)table[t].address, table[t].ackexpected,
		table[t].nextframetosend, table[t].frameexpected);
}


/* ----------------------------------------------------------------------- */

static int down_to_datalink(int link, char *packet, int length);

static void flood(char *packet, int length)
{
    int	   link;

    for(link=1 ; link<=nodeinfo.nlinks ; ++link)
	CHECK(down_to_datalink(link, packet, length));
}

static void down_to_network(CnetEvent ev, CnetTimer timer, CnetData data)
{
    NL_PACKET	p;

    p.length	= sizeof(p.msg);
    CHECK(CNET_read_application(&p.dest, p.msg, &p.length));
    CNET_disable_application(p.dest);

    p.src	= nodeinfo.address;
    p.kind	= NL_DATA;
    p.hopsleft	= MAXHOPS;
    p.seqno	= nextframetosend(p.dest);

    flood((char *)&p, PACKET_SIZE(p));
}


static int up_to_network(char *packet, int length)
{
    NL_PACKET	*p;
    CnetAddr	addr;

    p	= (NL_PACKET *)packet;

    if(p->dest == nodeinfo.address) {		/*  THIS PACKET IS FOR ME */
	if(p->kind == NL_DATA && p->seqno == frameexpected(p->src)) {
	    length	= p->length;
	    CHECK(CNET_write_application(p->msg, &length));
	    inc_frameexpected(p->src);

	    addr	= p->src;	/* transform NL_DATA into NL_ACK */
	    p->src	= p->dest;
	    p->dest	= addr;

	    p->kind	= NL_ACK;
	    p->hopsleft	= MAXHOPS;
	    p->length	= 0;
	    flood(packet, PACKET_HEADER_SIZE);	/* just ACK with a header */
	}
	else if(p->kind == NL_ACK && p->seqno == ackexpected(p->src)) {
	    CNET_enable_application(p->src);
	    inc_ackexpected(p->src);
	}
    }
    else {				/* THIS PACKET IF FOR SOMEONE ELSE */
	if(--p->hopsleft > 0)		/* send it back out again */
	    flood(packet, length);
    }
    return(0);
}


/* ------------- A MINIMAL RELIABLE DATALINK LAYER FOLLOWS -------------- */


typedef struct {
    /* as we use a reliable datalink, we don't need any other fields */
    char        packet[sizeof(NL_PACKET)];
} DLL_FRAME;


static int down_to_datalink(int link, char *packet, int length)
{
    CHECK(CNET_write_physical_reliable(link, (char *)packet, &length));
    return(0);
}

static void up_to_datalink(CnetEvent ev, CnetTimer timer, CnetData data)
{
    DLL_FRAME	f;
    int		link, length;

    length	= sizeof(DLL_FRAME);
    CHECK(CNET_read_physical(&link, (char *)&f, &length));

    CHECK(up_to_network(f.packet, length));
}

static void init_DLL()
{
    /* nothing to do! */
}


/* ----------------------------------------------------------------------- */

void reboot_node(CnetEvent ev, CnetTimer timer, CnetData data)
{
    CHECK(CNET_set_handler(EV_APPLICATIONREADY, down_to_network, 0));
    CHECK(CNET_set_handler(EV_PHYSICALREADY,    up_to_datalink,  0));
    CHECK(CNET_set_handler(EV_DEBUG1,           show_table,      0));

    CHECK(CNET_set_debug_string( EV_DEBUG1, "NL info"));

    init_DLL();
    init_NL();

    CNET_enable_application(ALLNODES);
}
