/*
 *   Copyright 1992, 1993, 1994 John Melton (G0ORX/N6LYT)
 *              All Rights Reserved
 *
 *   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 1, or (at your option)
 *   any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

/*
	upload.c

	Pacsat Upload for Linux and X-Windows

	This program has been run using the 1.0 version of the
	Linux kernel with the patches from Alan Cox to provide AX.25
	encapsulation in the SLIP protocol(verion 0.12).

	The TNC must be setup for KISS protocol.

	John Melton
	G0ORX, N6LYT

	4 Charlwoods Close
	Copthorne
	West Sussex
	RH10 3QZ
	England

	INTERNET:	g0orx@amsat.org
			n6lyt@amsat.org
			john@images.demon.co.uk
			J.D.Melton@slh0613.icl.wins.co.uk

	History:

	0.1	cloned from (x)upload.c		G4KLX
	0.2	handle short writes		G4KLX
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <dirent.h>
#include <sys/socket.h>
#include <linux/ax25.h>
#include <signal.h>
#include <ctype.h>

#include <X11/Intrinsic.h>

#include "header.h"
#include "ftl0.h"

extern char satellite[];
extern char myCall[];

#define IDLE			0
#define LOGGING_IN		1
#define REQUESTING_UPLOAD	2
#define UPLOADING		3
#define SENT_END_DATA		4

int state = IDLE;

int fd[];
int s;
FILE *fp;
unsigned long fileId;
long offset;
int fileLength;
UPLOAD_REQUEST *ul_request;
UPLOAD_RESPONSE *ul_response;
unsigned long uploadFileId;
unsigned char buffer[1024];
extern int maxFrame;
extern int T1;
	
#define MAXHEADER 1024
unsigned char header[MAXHEADER];
int nHeader;

char msg[256];

int convert_call(char *, struct full_sockaddr_ax25 *);

int writen(int fd, char *ptr, int n)
{
	int nleft, nwritten;

	nleft = n;

	while (nleft > 0)
	{
		if ((nwritten = write(fd, ptr, nleft)) <= 0)
			return nwritten;

		nleft -= nwritten;
		ptr   += nwritten;
	}

	return n;
}

void update_status(char *msg)
{
	writen(fd[1], msg, strlen(msg) + 1);
}

void error(char *msg)
{
	update_status(msg);

	close(s);
	fclose(fp);

	sleep(1);
	exit(1);
}

void ftl0_error(char *msg, int err_code)
{
	char text[128];

	switch (err_code)
	{
		case ER_ILL_FORMED_CMD:
			sprintf(text, "%s: ILL_FORMED_CMD", msg);
			break;
		case ER_BAD_CONTINUE:
			sprintf(text, "%s: BAD_CONTINUE", msg);
			break;
		case ER_SERVER_FSYS:
			sprintf(text, "%s: SERVER_FSYS", msg);
			break;
		case ER_NO_SUCH_FILE_NUMBER:
			sprintf(text, "%s: NO_SUCH_FILE_NUMBER", msg);
			break;
		case ER_FILE_COMPLETE:
			sprintf(text, "%s: FILE_COMPLETE", msg);
			break;
		case ER_NO_ROOM:
			sprintf(text, "%s: NO_ROOM", msg);
			break;
		case ER_BAD_HEADER:
			sprintf(text, "%s: BAD_HEADER", msg);
			break;
		case ER_HEADER_CHECK:
			sprintf(text, "%s: HEADER_CHECK", msg);
			break;
		case ER_BODY_CHECK:
			sprintf(text, "%s: BODY_CHECK", msg);
			break;
		default:
			sprintf(text, "%s: ftl0 error %d", msg, err_code);
			break;
	}

	error(text);
}

void socket_error(char *msg, int err_code)
{
	char text[128];

	switch (err_code)
	{
		case ECONNRESET:
			sprintf(text, "%s: connection reset", msg);
			break;
		case EISCONN:
			sprintf(text, "%s: already connected", msg);
			break;
		case ENOTCONN:
			sprintf(text, "%s: not connected", msg);
			break;
		case ESHUTDOWN:
			sprintf(text, "%s: connection shutdown", msg);
			break;
		case ETIMEDOUT:
			sprintf(text, "%s: connection timedout", msg);
			break;
		case ECONNREFUSED:
			sprintf(text, "%s: connection refused", msg);
			break;
		case EHOSTDOWN:
			sprintf(text, "%s: no route to host", msg);
			break;
		default:
			sprintf(text, "%s: socket error %d", msg, err_code);
			break;
	}

	error(text);
}

void ReceivedFrame(FILE *fp, char *fileName)
{
	int bytes;
	int type;
	int length;

	if ((bytes = read(s, buffer, 512)) < 0)
		socket_error("read failed", errno);

	length = buffer[0] + ((buffer[1] << 3) & 0xFF00);
	type   = buffer[1] & 0x1F;

	switch (state)
	{
		case LOGGING_IN:
			if (type != LOGIN_RESP)
			{
				sprintf(msg, "Expected LOGIN_RESP got %d", type);
				error(msg);
			}
			
			update_status("Status: Logged in");

			/* request an upload */
			sprintf(msg, "Status: Requesting upload: file:%lx length:%d",
					fileId, fileLength);
			update_status(msg);

			buffer[0] = sizeof(UPLOAD_REQUEST);
			buffer[1] = UPLOAD_CMD;
			
			ul_request         = (UPLOAD_REQUEST *)&buffer[2];
			ul_request->file   = fileId;
			ul_request->length = fileLength;
	
			if (writen(s, (char *)buffer, sizeof(UPLOAD_REQUEST) + 2) != (sizeof(UPLOAD_REQUEST) + 2))
				socket_error("write failed", errno);

			state = REQUESTING_UPLOAD;
			break;

		case REQUESTING_UPLOAD:
			switch (type)
			{
				case UL_GO_RESP:
					break;
				case UL_ERROR_RESP:
					ftl0_error("Received UL_ERROR_RESP", buffer[2]);
				default:
					sprintf(msg, "Received unexpected GO_RESP: %d", type);
					error(msg);
			}

			ul_response = (UPLOAD_RESPONSE *)&buffer[2];
			offset       = ul_response->offset;
			uploadFileId = ul_response->file;

			sprintf(msg, "Status: Received GO_RESP: file:%lx offset:%ld",
				uploadFileId, offset);
			update_status(msg);

			/* update the file id in the header */
			/* incase it does not all make it */
			fseek(fp, 5L, SEEK_SET);
			fwrite((char *)&uploadFileId, 1, 4, fp);

			/* now seek to the start of the data and start uploading */
			fseek(fp, offset, SEEK_SET);
			state = UPLOADING;
			break;

		case UPLOADING:
			switch (type)
			{
				case UL_NAK_RESP:
					/* send a data end packet */
					buffer[0] = 0;
					buffer[1] = DATA_END;
					if (writen(s, buffer, 2) != 2)
						socket_error("write failed", errno);
					ftl0_error("Received UL_NAK_RESP", buffer[2]);
				default:
					sprintf(msg, "Received unexpected response: %d", type);
					error(msg);
			}

		case SENT_END_DATA:
			switch (type)
			{
				case UL_ACK_RESP:
					break;
				case UL_NAK_RESP:
					ftl0_error("Received UL_NAK_RESP", buffer[2]);
				default:
					sprintf(msg, "Received unexpected response: %d", type);
					error(msg);
			}

			sprintf(msg, "Status: File %s uploaded as message %lx", fileName, uploadFileId);
			update_status(msg);

			unlink(fileName);

			close(s);
			sleep(1);

			exit(0);
	}
}

void SendFrame(FILE *fp, char *fileName)
{
	int bytes;

	if ((bytes = fread(buffer + 2, 1, maxFrame, fp)) > 0)
	{
		buffer[0] = bytes;
		buffer[1] = DATA;

		/* always send fileId of 0 in header */
		if (offset == 0L)
		{
			buffer[7]  = 0;
			buffer[8]  = 0;
			buffer[9]  = 0;
			buffer[10] = 0;
		}

		if (writen(s, buffer, bytes + 2) != (bytes + 2))
			socket_error("write failed", errno);

		offset += bytes;
		return;
	}

	if (bytes < 0) socket_error("read failed", errno);

	fclose(fp);

	/* send a data end packet */
	buffer[0] = 0;
	buffer[1] = DATA_END;

	if (writen(s, buffer, 2) != 2)
		socket_error("write failed", errno);

	state = SENT_END_DATA;
}

void upload(char *fileName)
{
	HEADER *pHeader;
	int opt;
	int addr_len;
	struct full_sockaddr_ax25 addr;
	int bytes;
	fd_set rx, tx;

	sprintf(msg, "Status: Uploading: %s", fileName);
	update_status(msg);

	if ((fp = fopen(fileName, "r+")) == NULL)
		error("File open failed");

	/* get the file length */
	fseek(fp, 0L, SEEK_END);
	fileLength = ftell(fp);

	/* extracting the header */
	fseek(fp, 0L, SEEK_SET);
	bytes = fread(buffer, 1, 1024, fp);

	fseek(fp, 0L, SEEK_SET);
	pHeader = ExtractHeader(buffer, bytes, &bytes);
	fileId = pHeader->fileId;
	XtFree((char *)pHeader);

	if ((s = socket(AF_AX25, SOCK_SEQPACKET, PF_AX25)) < 0)
		socket_error("Socket open failed", errno);

	opt = 1;

	if (setsockopt(s, SOL_AX25, AX25_WINDOW, &opt, sizeof(opt)) == -1)
		socket_error("setsockopt (AX25_WINDOW) failed", errno);

	if (setsockopt(s, SOL_AX25, AX25_T1, &T1, sizeof(opt)) == -1)
		socket_error("setsockopt (AX25_T1) failed", errno);

#ifdef DEBUG
	opt = 1;

	if (setsockopt(s, SOL_SOCKET, SO_DEBUG, &opt, sizeof(opt)) == -1)
		socket_error("setsockopt (SO_DEBUG) failed", errno);
#endif

	addr_len = convert_call(myCall, &addr);

	if (bind(s, (struct sockaddr *)&addr, addr_len) == -1)
		socket_error("bind failed", errno);

	addr_len = convert_call(satellite, &addr);

	update_status("Status: Connecting...");

	if (connect(s, (struct sockaddr *)&addr, addr_len) != 0)
		socket_error("connect failed", errno);

	update_status("Status: Connected");

	/* setup state */
	state = LOGGING_IN;

	for (;;)
	{
		FD_ZERO(&rx);
		FD_ZERO(&tx);

		FD_SET(s, &rx);

		if (state == UPLOADING)
		{
			FD_SET(s, &tx);	
			select(s + 1, &rx, &tx, NULL, NULL);
		}
		else
		{
			select(s + 1, &rx, NULL, NULL, NULL);
		}
				
		if (FD_ISSET(s, &rx)) ReceivedFrame(fp, fileName);
		
		if (FD_ISSET(s, &tx)) SendFrame(fp, fileName);
	}
}

/*
 *	Library routine for callsign conversion.
 *
 */
 
void convert_call_entry(char *name,unsigned char *buf)
{
	int ct = 0;
	int ssid = 0;
	char *p = name;

	while (ct < 6)
	{
		if (*p == '-' || *p == '\0') break;

		if (islower(*p)) *p = toupper(*p);

		if (!isalnum(*p))
		{
			sprintf(msg, "Invalid symbol in callsign - %c\n", *p);
			error(msg);
		}
		
		buf[ct] = (*p << 1);
		p++;
		ct++;
	}

	while (ct < 6)
	{
		buf[ct] = ' ' << 1;
		ct++;
	}

	if (*p == '-')
	{
		p++;

		if (sscanf(p, "%d", &ssid) != 1 || ssid < 0 || ssid > 15)
		{
			sprintf(msg, "Invalid SSID - %s - %s\n", name, p);
			error(msg);
		}
	}

	buf[6] = ((ssid + '0') << 1) & 0x1E;
}

int convert_call(char *call, struct full_sockaddr_ax25 *sax)
{

	int len = 0;
	unsigned char *bp, *np;
	char *addrp;
	int n = 0;
	unsigned char *tmp = strdup(call);
	
	if (tmp == NULL) return -1;
		
	bp = tmp;
	
	addrp = sax->fsa_ax25.sax25_call.ax25_call;

	do
	{
		/* Fetch one callsign token */
		while (*bp && isspace(*bp)) bp++;
		np = bp;
		while (*np && !isspace(*np)) np++;
		if (*np) *np++ = 0;
	
		/* Check for the optional 'via' syntax */
		if (n == 1 && (strcasecmp(bp, "V") == 0 || strcasecmp(bp, "VIA") == 0))
		{
			bp = np;
			continue;
		}
		
		/* Process the token */
		convert_call_entry(bp, addrp);
			
		/* Move along */
		bp = np;
		n++;

		if (n == 1)
		{
			addrp = sax->fsa_digipeater[0].ax25_call;	/* First digipeater address */
			len  += sizeof(struct sockaddr_ax25);
		}
		else
		{
			addrp += sizeof(ax25_address);
			len   += sizeof(ax25_address);
		}
	}
	while (n < AX25_MAX_DIGIS && *bp);

	free(tmp);

	/* Tidy up */
	sax->fsa_ax25.sax25_ndigis = n - 1;
	sax->fsa_ax25.sax25_family = AF_AX25;	

	return len;
}

                                                                                                                                