/*
 *   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
	0.3	removed dependency of X		OZ6BL
		added writing of log 		OZ6BL
		added renaming of upload file	OZ6BL
	0.4	fixed an error in rename to .pul OZ6BL
		flushes the log file after every
		write to it			OZ6BL
		fixed an error in close of the
		file to upload			OZ6BL
	0.5	Made to compile with Debian 2.0 (glibc2)
		Removed some type inconsistemcies OZ6BL
*/

#define RCS_ID $Id: upload.c,v 1.9 2001/01/20 19:10:40 oz6bl Exp $

#include <stdio.h>
#include <stdlib.h>
#include <errno.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 <netax25/ax25.h>
#include <netrose/rose.h>
#include <signal.h>
#include <ctype.h>

#include <netax25/axconfig.h>
#include <netax25/axlib.h>
#include "header.h"
#include "ftl0.h"

extern char Satellite[];
extern char myCall[];
extern FILE *logfp ;

#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 oldName [80] ;
char newName [80] ;

char msg[256];

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)
{
	struct tm *tm ;
	time_t t ;

	t = time (NULL) ;
	tm = gmtime (&t) ;
	fprintf(logfp, "%02d/%02d/%02d %02d:%02d:%02d - %s\n",
			tm->tm_mday, tm->tm_mon + 1, tm->tm_year,
			tm->tm_hour, tm->tm_min, tm->tm_sec, msg);
	fflush(logfp) ;
	writen(fd[1], msg, strlen(msg) + 1);
}

void error(char *msg, int returnCode) {
	/* errorCode == 0: file done (either success or abandoned)
		     == 1: problems with connection */
	update_status(msg);
	close(s);
	fclose(fp);
	sleep(1);
	exit(returnCode);
}

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;
	}
        /* abandon the file: rename it to *.xul */
        sprintf(oldName, "%lx.pul", uploadFileId);
        sprintf(newName, "%lx.xul", uploadFileId);
        rename(oldName, newName);
	error(text, 0);
}

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

	switch (err_code) {
		case EPERM:
			sprintf(text, "%s: operation not permitted", msg);
			break;
		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 timed out", 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, 1);
}

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, 0); }
			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: 
					if (buffer[2] == ER_NO_SUCH_FILE_NUMBER) {
						/* The slot for the file in the
						   server has expired - delete the
						   file ID in the header */
						fseek (fp, 5L, SEEK_SET) ;
						uploadFileId = 0L ;
						fwrite ((char *)&uploadFileId, 1, 4, fp) ; }
						/* the file will be picked up
						   again next time around */
					ftl0_error("Received UL_ERROR_RESP", buffer[2]);
				default:
					sprintf(msg, "Received unexpected GO_RESP: %d", type);
					error(msg, 0);
			}

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

                	/* rename the file to <uploadFileId>.pul */
                	sprintf(newName, "%lx.pul", uploadFileId);
                	rename(oldName, newName);

			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, (char *)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, 0);
			}

		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, 0);
			}

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

	                /* rename the file to *.ul and close it */
	                sprintf(oldName, "%lx.pul", uploadFileId);
	                sprintf(newName, "%lx.ul", uploadFileId);
	                rename(oldName, newName);
			fclose (fp) ;

			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, (char *)buffer, bytes + 2) != (bytes + 2))
			socket_error("write failed", errno);

		offset += bytes;
		return;
	}

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

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

	if (writen(s, (char *)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", 0);

	/* 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;
	free((char *)pHeader);

	if ((s = socket(AF_AX25, SOCK_SEQPACKET, PF_AX25)) < 0)
		socket_error("Socket open 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 = ax25_aton(myCall, &addr);

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

	addr_len = ax25_aton(Satellite, &addr);

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

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

	strcpy (oldName, fileName) ; /* for possible renames */

	/* 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);
	}
}
