/*
 * net_tele.c - telegrams for cleint/server communication
 *
 * $Id: net_tele.c,v 1.11 2006/02/09 21:21:24 fzago Exp $
 *
 * Program XBLAST
 * (C) by Oliver Vogel (e-mail: m.vogel@ndh.net)
 *
 * 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; or (at your option)
 * any later version
 *
 * This program is distributed in the hope that it will be entertaining,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILTY 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.
 * 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "xblast.h"

/*
 * local macros
 */
#define MAX_HEADER_SIZE 5
#define MAX_DATA_SIZE   255
#define MAX_TAIL_SIZE   1
#define MAX_TOTAL_SIZE (MAX_HEADER_SIZE + MAX_DATA_SIZE + MAX_TAIL_SIZE)

#define START_BYTE      0x68
#define STOP_BYTE       0x16

/*
 * local types
 */
typedef int (*WriteFunc) (const XBSocket *, const void *, size_t);
typedef int (*ReadFunc) (const XBSocket *, void *, size_t);

/* details the parse state of a telegram */
typedef enum
{
	PS_Start,
	PS_Len,
	PS_COT,
	PS_ID,
	PS_IOB,
	PS_Data,
	PS_Stop
} ParseState;

/* extracted telegram information */
struct _xb_telegram
{
	XBTeleCOT cot;
	XBTeleID id;
	unsigned char iob;
	void *data;
	size_t len;
	XBTelegram *next;
};

/* telegram queue */
typedef struct _xb_any_queue
{
	XBTelegram *first;
	XBTelegram *last;
} XBAnyQueue;

/* for sending */
struct _xb_snd_queue
{
	XBAnyQueue any;
	size_t wIndex;				/* next byte to write */
	size_t wLen;				/* bytes to write */
	char wBuf[MAX_TOTAL_SIZE];	/* data to write */
};

/* for receiving */
struct _xb_rcv_queue
{
	XBAnyQueue any;
	size_t rIndex;				/* next byte to read */
	size_t rLen;				/* length to be read */
	XBBool rFlag;				/* flag to read more data */
	ParseState rState;			/* current state of reading */
	size_t dCount;				/* length of read data block */
	char rBuf[MAX_TOTAL_SIZE];	/* buffer read from socket */
	char tBuf[MAX_TOTAL_SIZE];	/* extracted telegram */
};

/*------------------------------------------------------------------------*
 *
 * Debugoutput
 *
 *------------------------------------------------------------------------*/

#ifdef DEBUG_TELE

/*
 * translate COT codes
 */
const char *
StringCOT (XBTeleCOT cot)
{
	switch (cot) {
	case XBT_COT_Activate:
		return "act";
	case XBT_COT_Spontaneous:
		return "spont";
	case XBT_COT_SendData:
		return "snd_dat";
	case XBT_COT_RequestData:
		return "req_dat";
	case XBT_COT_DataNotAvailable:
		return "dat_not";
	case XBT_COT_DataAvailable:
		return "dat_ava";
	default:
		return "???";
	}
}								/* StringCOT */

/*
 * translate ID codes
 */
const char *
StringID (XBTeleID id)
{
	switch (id) {
	case XBT_ID_GameConfig:
		return "gam_cfg";
	case XBT_ID_PlayerConfig:
		return "plr_cfg";
	case XBT_ID_RequestDisconnect:
		return "req_dis";
	case XBT_ID_HostDisconnected:
		return "hst_dis";
	case XBT_ID_StartGame:
		return "sta_gam";
	case XBT_ID_RandomSeed:
		return "rnd_sed";
	case XBT_ID_LevelConfig:
		return "lvl_cfg";
	case XBT_ID_DgramPort:
		return "dgm_prt";
	case XBT_ID_Sync:
		return "sync";
	case XBT_ID_HostIsIn:
		return "hst_in";
	case XBT_ID_HostIsOut:
		return "hst_out";
	case XBT_ID_TeamChange:
		return "tm_cng";
	case XBT_ID_GameStat:
		return "gm_stat";
	case XBT_ID_PID:
		, return "pid";
	case XBT_ID_WinnerTeam:
		, return "win_tm";
	case XBT_ID_Async:
		return "async";
	case XBT_ID_Chat:
		return "chat";
	case XBT_ID_HostChange:
		return "hst_chg";
	case XBT_ID_HostChangeReq:
		return "hst_req";
	case XBT_ID_TeamChangeReq:
		return "tm_req";
	default:
		return "???";
	}
}								/* StringID */

/*
 * output telegram
 */
static void
DebugTelegram (FILE * fout, const XBTelegram * tele)
{
	fprintf (fout, "%s %s %03u (%03u Bytes)",
			 StringID (tele->id), StringCOT (tele->cot), (unsigned)tele->iob, (unsigned)tele->len);
	if (tele->len > 0) {
		size_t i;
		const char *s = tele->data;
		fputs (" \"", fout);
		for (i = 0; i < tele->len; i++) {
			if (isprint (s[i])) {
				fputc (s[i], fout);
			}
			else {
				fprintf (fout, "\\%03o", (unsigned)s[i]);
			}
		}
		fputc ('\"', fout);
	}
}								/* DebugTelegram */

#endif

/*------------------------------------------------------------------------*
 *
 * XBTelegram
 *
 *------------------------------------------------------------------------*/

/*
 * create a new telegram from given data
 */
XBTelegram *
Net_CreateTelegram (XBTeleCOT cot, XBTeleID id, XBTeleIOB iob, const void *buf, size_t len)
{
	XBTelegram *tele;
	assert (len < MAX_DATA_SIZE);
	tele = calloc (1, sizeof (XBTelegram));
	assert (tele != NULL);
	tele->cot = cot;
	tele->id = id;
	tele->iob = iob;
	/* copy buffer if needed */
	if (buf != NULL && len != 0) {
		tele->len = len;
		tele->data = malloc (len);
		assert (tele->data != NULL);
		memcpy (tele->data, buf, len);
	}
	return tele;
}								/* Net_CreateTelegram */

/*
 * delete a telegram
 */
void
Net_DeleteTelegram (XBTelegram * tele)
{
	assert (tele != NULL);
	if (NULL != tele->data) {
		free (tele->data);
	}
	free (tele);
}								/* Net_DeleteTelegram */

/*
 * write telegram to a given buffer, return size
 */
static size_t
WriteTelegram (const XBTelegram * tele, char *buf)
{
	char *ptr = buf;
	/* write header */
	*ptr++ = START_BYTE;
	*ptr++ = tele->len;
	*ptr++ = (char)tele->cot;
	*ptr++ = (char)tele->id;
	*ptr++ = (char)tele->iob;
	/* write data */
	assert (tele->len < MAX_DATA_SIZE);
	if (tele->len > 0) {
		assert (tele->data != NULL);
		memcpy (ptr, tele->data, tele->len);
		ptr += tele->len;
	}
	/* write tail */
	*ptr++ = STOP_BYTE;
	/* return len */
	return ptr - buf;
}								/* WriteTelegram */

/*------------------------------------------------------------------------*
 *
 * XBAnyQueue
 *
 *------------------------------------------------------------------------*/

/*
 * delete a queue completely
 */
static void
DeleteAnyQueue (XBAnyQueue * list)
{
	XBTelegram *teleNext;
	for (; list->first != NULL; list->first = teleNext) {
		teleNext = list->first->next;
		Net_DeleteTelegram (list->first);
	}
}								/* DeleteAnyQueue */

/*
 * add telegram to to a queue
 */
static void
AddTelegram (XBAnyQueue * list, XBTelegram * tele)
{
	assert (list != NULL);
	assert (tele != NULL);
	if (NULL == list->last) {
		list->first = tele;
	}
	else {
		list->last->next = tele;
	}
	list->last = tele;
#ifdef DEBUG_TELE
	fputs ("add tele:", stderr);
	DebugTelegram (stderr, tele);
	fputc ('\n', stderr);
#endif
}								/* AddTelegram */

/*
 * get first telegram from queue
 */
static XBTelegram *
GetTelegram (XBAnyQueue * list)
{
	XBTelegram *tele;
	assert (list != NULL);
	/* first element from list */
	tele = list->first;
	/* update list if needed */
	if (list->first != NULL) {
		list->first = list->first->next;
	}
	if (list->first == NULL) {
		list->last = NULL;
	}
	return tele;
}								/* GetTelegram */

/*------------------------------------------------------------------------*
 *
 * XBSndQueue
 *
 *------------------------------------------------------------------------*/

/*
 * create a snd queue
 */
XBSndQueue *
Net_CreateSndQueue (XBBool server)
{
	XBSndQueue *list = calloc (1, sizeof (XBSndQueue));
	assert (list != NULL);
	return list;
}								/* Net_CreateSndQueue */

/*
 * delete a sndqueue
 */
void
Net_DeleteSndQueue (XBSndQueue * list)
{
	DeleteAnyQueue (&list->any);
	free (list);
}								/* Net_DeleteSndQueue */

/*
 * write a first telegram fro queue to socket
 */
XBTeleResult
Net_Send (XBSndQueue * list, const XBSocket * pSocket)
{
	int result;
	XBTelegram *tele;
	assert (list != NULL);
	/* check if new telegram must be written to buffer */
	if (0 == list->wLen && NULL != (tele = GetTelegram (&list->any))) {
#ifdef DEBUG_TELE
		Dbg_Out ("wrt tele %d (%03u bytes)\n", Socket_Fd (pSocket), tele->len);
#endif
		list->wLen = WriteTelegram (tele, list->wBuf);
		list->wIndex = 0;
		Net_DeleteTelegram (tele);
	}
	/* check if any unwritten bytes are left in the buffer */
	if (list->wIndex < list->wLen) {
		result = Socket_Send (pSocket, list->wBuf + list->wIndex, list->wLen - list->wIndex);
		switch (result) {
		case XB_SOCKET_ERROR:
			Dbg_Out ("ERROR while writing\n");
			return XBT_R_IOError;
		case XB_SOCKET_END_OF_FILE:
			Dbg_Out ("END_OF_FILE while writing\n");
			return XBT_R_IOError;
		case XB_SOCKET_WOULD_BLOCK:
			return XBT_R_Continue;
		default:
			break;
		}
		list->wIndex += result;
		/* mark as complete send */
		if (list->wIndex == list->wLen) {
			list->wLen = list->wIndex = 0;
		}
	}
	/* check if we need to call write again */
	if (list->wLen > 0 || list->any.first != NULL) {
		return XBT_R_Continue;
	}
	else {
		return XBT_R_Complete;
	}
}								/* Net_Send */

/*
 * add a telegram to the send queue
 */
void
Net_SendTelegram (XBSndQueue * list, XBTelegram * tele)
{
#ifdef DEBUG_TELE
	Dbg_Out (" > snd ");
#endif
	AddTelegram (&list->any, tele);
}								/* Net_SendTelegram */

/*------------------------------------------------------------------------*
 *
 * XBRcvQueue
 *
 *------------------------------------------------------------------------*/

/*
 * create rcv queue
 */
XBRcvQueue *
Net_CreateRcvQueue (XBBool server)
{
	XBRcvQueue *list = calloc (1, sizeof (XBRcvQueue));
	assert (list != NULL);
	list->rState = PS_Start;
	list->rFlag = XBTrue;
	return list;
}								/* Net_CreateRcvQueue */

/*
 * delete rcv queue
 */
void
Net_DeleteRcvQueue (XBRcvQueue * list)
{
	DeleteAnyQueue (&list->any);
	free (list);
}								/* Net_DeleteRcvQueue */

/*
 * fill read buffer from socket
 */
static XBTeleResult
ReadBuffer (XBRcvQueue * list, const XBSocket * pSocket)
{
#ifdef W32
	long result;
#else
	ssize_t result;
#endif
	result = Socket_Receive (pSocket, list->rBuf, MAX_TOTAL_SIZE);
	switch (result) {
	case XB_SOCKET_ERROR:
		return XBT_R_IOError;
	case XB_SOCKET_END_OF_FILE:
		return XBT_R_EndOfFile;
	case XB_SOCKET_WOULD_BLOCK:
		result = 0;
		break;
	}
	list->rLen = result;
	list->rIndex = 0;
	return XBT_R_Continue;
}								/* ReadBuffer */

/*
 * fetch byte from internal read buffer
 */
static XBBool
GetByte (XBRcvQueue * list, size_t tIndex)
{
	if (list->rIndex < list->rLen) {
		list->tBuf[tIndex] = list->rBuf[list->rIndex++];
		return XBTrue;
	}
	else {
		return XBFalse;
	}
}								/* GetByte */

/*
 * read telegram data from socket
 */
XBTeleResult
Net_Receive (XBRcvQueue * list, const XBSocket * pSocket)
{
	size_t i, len;
	XBTelegram *tele;
	assert (list != NULL);
	/* check if more input is needed */
	if (list->rFlag) {
		XBTeleResult result;
		/* try tp read from socket into buffer */
		if (XBT_R_Continue != (result = ReadBuffer (list, pSocket))) {
			return result;
		}
		/* assume we will complete the telegram */
		list->rFlag = XBFalse;
	}
	/* parse read buffer until empty or parse error */
	while (1) {
		switch (list->rState) {
		case PS_Start:			/* we need a start byte */

			do {
				/* try to get first start byte in buffer */
				if (!GetByte (list, 0)) {
					/* failed, mark for input and return */
					list->rFlag = XBTrue;
					return XBT_R_Continue;
				}
#ifdef DEBUG_TELE
				fputc ('{', stderr);
#endif
			} while (list->tBuf[0] != START_BYTE);
			/* restart loop needing length byte */
			list->rState = PS_Len;
			break;
		case PS_Len:			/* we need a length byte */
			/* try to get len byte */
			if (!GetByte (list, 1)) {
				/* failed, mark for more input and return */
				list->rFlag = XBTrue;
				return XBT_R_Continue;
			}
#ifdef DEBUG_TELE
			fputc ('L', stderr);
#endif
			/* restart loop needing COT */
			list->rState = PS_COT;
			break;
		case PS_COT:			/* we need COT */
			/* try to read COT */
			if (!GetByte (list, 2)) {
				/* failed, mark for more input and return */
				list->rFlag = XBTrue;
				return XBT_R_Continue;
			}
#ifdef DEBUG_TELE
			fputc ('C', stderr);
#endif
			/* need signal id now */
			list->rState = PS_ID;
			break;
		case PS_ID:			/* need signal id */
			/* try to read id byte */
			if (!GetByte (list, 3)) {
				/* failed, mark for more input and return */
				list->rFlag = XBTrue;
				return XBT_R_Continue;
			}
#ifdef DEBUG_TELE
			fputc ('I', stderr);
#endif
			/* need IOB now */
			list->rState = PS_IOB;
			break;
		case PS_IOB:			/* need IOB */
			/* try to get IOB byte */
			if (!GetByte (list, 4)) {
				/* failed, mark for more input and return */
				list->rFlag = XBTrue;
				return XBT_R_Continue;
			}
#ifdef DEBUG_TELE
			fputc ('i', stderr);
#endif
			/* need data now, no data so far */
			list->rState = PS_Data;
			list->dCount = 0;
			break;
		case PS_Data:			/* need data */
			/* data length needed */
			len = list->tBuf[1];
			/* try to read data byte by byte */
			for (i = list->dCount; i < len; i++) {
				/* try to get it */
				if (!GetByte (list, MAX_HEADER_SIZE + i)) {
					/* failed, mark for more input, update data count so far and return */
					list->rFlag = XBTrue;
					list->dCount = i;
					return XBT_R_Continue;
				}
#ifdef DEBUG_TELE
				fputc ('.', stderr);
#endif
			}
#ifdef DEBUG_TELE
			fputc ('D', stderr);
#endif
			/* need stop now */
			list->rState = PS_Stop;
			break;
		case PS_Stop:			/* need stop */
			/* try to read stop byte */
			len = list->tBuf[1];
			if (!GetByte (list, MAX_HEADER_SIZE + len)) {
				/* failed, mark for more input and return */
				list->rFlag = XBTrue;
				return XBT_R_Continue;
			}
			/* we will need start byte next, in any case */
			list->rState = PS_Start;
			/* check stop byte */
			if (STOP_BYTE != list->tBuf[MAX_HEADER_SIZE + len]) {
				/* wrong byte, return parse error */
				return XBT_R_TeleError;
			}
#ifdef DEBUG_TELE
			Dbg_Out ("}\n");
#endif
			/* telegram is now complete, create the structurre */
			tele =
				Net_CreateTelegram (list->tBuf[2], list->tBuf[3], list->tBuf[4],
									list->tBuf + MAX_HEADER_SIZE, len);
			assert (NULL != tele);
#ifdef DEBUG_TELE
			Dbg_Out (" < rcv ");
#endif
			/* add it to the queue */
			AddTelegram (&list->any, tele);
			/* continue to parse buffer for more telegrams */
		}
	}
	return XBT_R_TeleError;
}								/* Net_Receive */

/*
 * get first telegram in receive queue
 */
XBTelegram *
Net_ReceiveTelegram (XBRcvQueue * list)
{
	return GetTelegram (&list->any);
}								/* Net_ReceiveTelegram */

/*
 * get cause of telegram transmission
 */
XBTeleCOT
Net_TeleCOT (const XBTelegram * tele)
{
	assert (tele != NULL);
	return tele->cot;
}								/* Net_TeleCOT */

/*
 * get id of telegram
 */
XBTeleID
Net_TeleID (const XBTelegram * tele)
{
	assert (tele != NULL);
	return tele->id;
}								/* Net_TeleID */

/*
 * get iob of telegram
 */
XBTeleIOB
Net_TeleIOB (const XBTelegram * tele)
{
	assert (tele != NULL);
	return tele->iob;
}								/* Net_TeleIOB */

/*
 * get telegram data
 */
const void *
Net_TeleData (const XBTelegram * tele, size_t * len)
{
	assert (tele != NULL);
	assert (len != NULL);
	*len = tele->len;
	return tele->data;
}								/* Net_TeleData */

/*
 * end of file net_tele.c
 */
