/* -------------------------------------------------------------------- */
/* SMS Client, send messages to mobile phones and pagers		*/
/*									*/
/* tap.c								*/
/*									*/
/*  Copyright (C) 1997,1998,1999 Angelo Masci				*/
/*									*/
/*  This library is free software; you can redistribute it and/or	*/
/*  modify it under the terms of the GNU Library General Public		*/
/*  License as published by the Free Software Foundation; either	*/
/*  version 2 of the License, or (at your option) any later version.	*/
/*									*/
/*  This library 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	*/
/*  Library General Public License for more details.			*/
/*									*/
/*  You should have received a copy of the GNU Library General Public	*/
/*  License along with this library; if not, write to the Free		*/
/*  Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.	*/
/*									*/
/*  You can contact the author at this e-mail address:			*/
/*									*/
/*  angelo@styx.demon.co.uk						*/
/*									*/
/* -------------------------------------------------------------------- */
/* $Id: tap.c,v 5.1 1998/02/01 07:10:39 root Exp root $
   -------------------------------------------------------------------- */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

#include "common/common.h"
#include "logfile/logfile.h"
#include "driver.h"
#include "error.h"
#include "comms/comms.h"
#include "resource/resource.h"

#include "ascii.h"

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

static struct tap_env
{
	DRIVER_DEFAULT_ENV def;

	/* Place any extended driver	*/ 
	/* variables here 		*/

	long 	max_tries,
	 	login_timeout,
		init_login_sleep,	
		login_sleep;

	char	*select,
		*response,
		*disconnect;

} driver_env;

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

static 	RESOURCE resource_list[] = 
	{
		{ RESOURCE_STRING,  "SMS_comms_params", 	0, 1, NULL, 0,  "8N1",  0, 	 &(driver_env.def.comms_params)  	},
		{ RESOURCE_STRING,  "SMS_centre_number", 	0, 1, NULL, 0,  NULL,   0, 	 &(driver_env.def.centre_number)  	},
		{ RESOURCE_NUMERIC, "SMS_baud", 		0, 1, NULL, 0,  NULL,   9600, 	 &(driver_env.def.baud)  		},
		{ RESOURCE_NUMERIC, "SMS_deliver_timeout", 	0, 0, NULL, 0,  NULL,   30, 	 &(driver_env.def.deliver_timeout)  	},
		{ RESOURCE_NUMERIC, "SMS_timeout", 		0, 0, NULL, 0,  NULL,   10, 	 &(driver_env.def.timeout)  		},
		{ RESOURCE_NUMERIC, "SMS_login_timeout", 	0, 0, NULL, 0,  NULL,   10, 	 &(driver_env.login_timeout)  		},
		{ RESOURCE_NUMERIC, "SMS_login_sleep", 		0, 0, NULL, 0,  NULL,   2000000, &(driver_env.login_sleep)  		},
		{ RESOURCE_NUMERIC, "SMS_init_login_sleep", 	0, 0, NULL, 0,  NULL,   1500000, &(driver_env.init_login_sleep)  	},
		{ RESOURCE_NUMERIC, "SMS_write_timeout", 	0, 0, NULL, 0,  NULL,   10, 	 &(driver_env.def.write_timeout)  	},
		{ RESOURCE_NUMERIC, "SMS_max_tries", 		0, 0, NULL, 0,  NULL,   5, 	 &(driver_env.max_tries)  		},
		{ RESOURCE_NUMERIC, "SMS_max_deliver", 		0, 0, NULL, 0,  NULL,   0, 	 &(driver_env.def.max_deliver) 	        },

		{ RESOURCE_STRING,  "SMS_tap_select", 		0, 0, NULL, 0,  "PG1",  0, 	 &(driver_env.select) 	                },
		{ RESOURCE_STRING,  "SMS_tap_response", 	0, 0, NULL, 0,  "TRUE", 0, 	 &(driver_env.response) 	        },
		{ RESOURCE_STRING,  "SMS_tap_disconnect", 	0, 0, NULL, 0,  "TRUE", 0, 	 &(driver_env.disconnect) 	        },
		{ RESOURCE_NULL,     NULL, 			0, 1, NULL, 0,  NULL,   0, 	 NULL  					}
	};

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

#define DELIVERTIMEOUT 		(driver_env.def.deliver_timeout)
#define TIMEOUT 		(driver_env.def.timeout)
#define LOGINTIMEOUT 		(driver_env.login_timeout)
#define LOGINSLEEP 		(driver_env.login_sleep)
#define INITLOGINSLEEP 		(driver_env.init_login_sleep)
#define WRITETIMEOUT 		(driver_env.def.write_timeout)

#define TAP_SELECT              (driver_env.select)
#define TAP_RESPONSE		(strcmp(driver_env.response, "TRUE") == 0)
#define TAP_DISCONNECT		(strcmp(driver_env.disconnect, "TRUE") == 0)

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

#define FD			(driver_env.def.fd)

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

#define MAX_TRIES		(driver_env.max_tries)

#define MAX_TAPMESSAGE	150
#define MAX_ENVELOPE 	64
#define MAX_NUMBER	20
#define MAX_MOBILENUM	20
#define MAX_MESSAGE	512

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

static char TAP_charset[] =
	{
		0x20 /* 0x00    */, 0x20 /* 0x01    */, 0x20 /* 0x02    */,
		0x20 /* 0x03    */, 0x20 /* 0x04    */, 0x20 /* 0x05    */,
		0x20 /* 0x06    */, 0x20 /* 0x07    */, 0x20 /* 0x08    */,
		0x20 /* 0x09    */, 0x0A /* 0x0A LF */, 0x20 /* 0x0B    */,
		0x20 /* 0x0C    */, 0x0A /* 0x0D LF */, 0x20 /* 0x0E    */,
		0x20 /* 0x0F    */, 0x20 /* 0x10    */, 0x20 /* 0x11    */,
		0x20 /* 0x12    */, 0x20 /* 0x13    */, 0x20 /* 0x14    */,
		0x20 /* 0x15    */, 0x20 /* 0x16    */, 0x20 /* 0x17    */,
		0x20 /* 0x18    */, 0x20 /* 0x19    */, 0x20 /* 0x1A    */,
		0x20 /* 0x1B    */, 0x20 /* 0x1C    */, 0x20 /* 0x1D    */,
		0x20 /* 0x1E    */, 0x20 /* 0x1F    */, 0x20 /* 0x20    */,
		0x21 /* 0x21 !  */, 0x22 /* 0x22 "  */, 0x23 /* 0x23 #  */,
		0x24 /* 0x24 $  */, 0x25 /* 0x25 %  */, 0x26 /* 0x26 &  */,
		0x27 /* 0x27 '  */, 0x28 /* 0x28 (  */, 0x29 /* 0x29 )  */,
		0x2A /* 0x2A *  */, 0x2B /* 0x2B +  */, 0x2C /* 0x2C ,  */,
		0x2D /* 0x2D -  */, 0x2E /* 0x2E .  */, 0x2F /* 0x2F /  */,
		0x30 /* 0x30 0  */, 0x31 /* 0x31 1  */, 0x32 /* 0x32 2  */,
		0x33 /* 0x33 3  */, 0x34 /* 0x34 4  */, 0x35 /* 0x35 5  */,
		0x36 /* 0x36 6  */, 0x37 /* 0x37 7  */, 0x38 /* 0x38 8  */,
		0x39 /* 0x39 9  */, 0x3A /* 0x3A :  */, 0x3B /* 0x3B ;  */,
		0x3C /* 0x3C <  */, 0x3D /* 0x3D =  */, 0x3E /* 0x3E >  */,
		0x3F /* 0x3F ?  */, 0x40 /* 0x40 @  */, 0x41 /* 0x41 A  */,
		0x42 /* 0x42 B  */, 0x43 /* 0x43 C  */, 0x44 /* 0x44 D  */,
		0x45 /* 0x45 E  */, 0x46 /* 0x46 F  */, 0x47 /* 0x47 G  */,
		0x48 /* 0x48 H  */, 0x49 /* 0x49 I  */, 0x4A /* 0x4A J  */,
		0x4B /* 0x4B K  */, 0x4C /* 0x4C L  */, 0x4D /* 0x4D M  */,
		0x4E /* 0x4E N  */, 0x4F /* 0x4F O  */, 0x50 /* 0x50 P  */,
		0x51 /* 0x51 Q  */, 0x52 /* 0x52 R  */, 0x53 /* 0x53 S  */,
		0x54 /* 0x54 T  */, 0x55 /* 0x55 U  */, 0x56 /* 0x56 V  */,
		0x57 /* 0x57 W  */, 0x58 /* 0x58 X  */, 0x59 /* 0x59 Y  */,
		0x5A /* 0x5A Z  */, 0x20 /* 0x5B    */, 0x20 /* 0x5C    */,
		0x20 /* 0x5D    */, 0x20 /* 0x5E    */, 0x20 /* 0x5F    */,
		0x20 /* 0x60    */, 0x61 /* 0x61 a  */, 0x62 /* 0x62 b  */,
		0x63 /* 0x63 c  */, 0x64 /* 0x64 d  */, 0x65 /* 0x65 e  */,
		0x66 /* 0x66 f  */, 0x67 /* 0x67 g  */, 0x68 /* 0x68 h  */,
		0x69 /* 0x69 i  */, 0x6A /* 0x6A j  */, 0x6B /* 0x6B k  */,
		0x6C /* 0x6C l  */, 0x6D /* 0x6D m  */, 0x6E /* 0x6E n  */,
		0x6F /* 0x6F o  */, 0x70 /* 0x70 p  */, 0x71 /* 0x71 q  */,
		0x72 /* 0x72 r  */, 0x73 /* 0x73 s  */, 0x74 /* 0x74 t  */,
		0x75 /* 0x75 u  */, 0x76 /* 0x76 v  */, 0x77 /* 0x77 w  */,
		0x78 /* 0x78 x  */, 0x79 /* 0x79 y  */, 0x7A /* 0x7A z  */,
		0x20 /* 0x7B    */, 0x20 /* 0x7C    */, 0x20 /* 0x7D    */,
		0x20 /* 0x7E    */, 0x20 /* 0x7F    */, 0x20 /* 0x80    */,
		0x20 /* 0x81    */, 0x20 /* 0x82    */, 0x20 /* 0x83    */,
		0x20 /* 0x84    */, 0x20 /* 0x85    */, 0x20 /* 0x86    */,
		0x20 /* 0x87    */, 0x20 /* 0x88    */, 0x20 /* 0x89    */,
		0x20 /* 0x8A    */, 0x20 /* 0x8B    */, 0x20 /* 0x8C    */,
		0x20 /* 0x8D    */, 0x20 /* 0x8E    */, 0x20 /* 0x8F    */,
		0x20 /* 0x90    */, 0x20 /* 0x91    */, 0x20 /* 0x92    */,
		0x20 /* 0x93    */, 0x20 /* 0x94    */, 0x20 /* 0x95    */,
		0x20 /* 0x96    */, 0x20 /* 0x97    */, 0x20 /* 0x98    */,
		0x20 /* 0x99    */, 0x20 /* 0x9A    */, 0x20 /* 0x9B    */,
		0x20 /* 0x9C    */, 0x20 /* 0x9D    */, 0x20 /* 0x9E    */,
		0x20 /* 0x9F    */, 0x20 /* 0xA0    */, 0x20 /* 0xA1    */,
		0x20 /* 0xA2    */, 0x20 /* 0xA3    */, 0x20 /* 0xA4    */,
		0x20 /* 0xA5    */, 0x20 /* 0xA6    */, 0x20 /* 0xA7    */,
		0x20 /* 0xA8    */, 0x20 /* 0xA9    */, 0x20 /* 0xAA    */,
		0x20 /* 0xAB    */, 0x20 /* 0xAC    */, 0x20 /* 0xAD    */,
		0x20 /* 0xAE    */, 0x20 /* 0xAF    */, 0x20 /* 0xB0    */,
		0x20 /* 0xB1    */, 0x20 /* 0xB2    */, 0x20 /* 0xB3    */,
		0x20 /* 0xB4    */, 0x20 /* 0xB5    */, 0x20 /* 0xB6    */,
		0x20 /* 0xB7    */, 0x20 /* 0xB8    */, 0x20 /* 0xB9    */,
		0x20 /* 0xBA    */, 0x20 /* 0xBB    */, 0x20 /* 0xBC    */,
		0x20 /* 0xBD    */, 0x20 /* 0xBE    */, 0x20 /* 0xBF    */,
		0x20 /* 0xC0    */, 0x20 /* 0xC1    */, 0x20 /* 0xC2    */,
		0x20 /* 0xC3    */, 0x5B /* 0xC4    */, 0x20 /* 0xC5    */,
		0x20 /* 0xC6    */, 0x20 /* 0xC7    */, 0x20 /* 0xC8    */,
		0x20 /* 0xC9    */, 0x20 /* 0xCA    */, 0x20 /* 0xCB    */,
		0x20 /* 0xCC    */, 0x20 /* 0xCD    */, 0x20 /* 0xCE    */,
		0x20 /* 0xCF    */, 0x20 /* 0xD0    */, 0x20 /* 0xD1    */,
		0x20 /* 0xD2    */, 0x20 /* 0xD3    */, 0x20 /* 0xD4    */,
		0x20 /* 0xD5    */, 0x5C /* 0xD6    */, 0x20 /* 0xD7    */,
		0x20 /* 0xD8    */, 0x20 /* 0xD9    */, 0x20 /* 0xDA    */,
		0x20 /* 0xDB    */, 0x5D /* 0xDC    */, 0x20 /* 0xDD    */,
		0x20 /* 0xDE    */, 0x1E /* 0xDF    */, 0x7E /* 0xE0    */,
		0x20 /* 0xE1    */, 0x20 /* 0xE2    */, 0x20 /* 0xE3    */,
		0x7B /* 0xE4    */, 0x20 /* 0xE5    */, 0x20 /* 0xE6    */,
		0x20 /* 0xE7    */, 0x20 /* 0xE8    */, 0x20 /* 0xE9    */,
		0x20 /* 0xEA    */, 0x20 /* 0xEB    */, 0x20 /* 0xEC    */,
		0x20 /* 0xED    */, 0x20 /* 0xEE    */, 0x20 /* 0xEF    */,
		0x20 /* 0xF0    */, 0x20 /* 0xF1    */, 0x20 /* 0xF2    */,
		0x20 /* 0xF3    */, 0x20 /* 0xF4    */, 0x20 /* 0xF5    */,
		0x7C /* 0xF6    */, 0x20 /* 0xF7    */, 0x20 /* 0xF8    */,
		0x20 /* 0xF9    */, 0x20 /* 0xFA    */, 0x20 /* 0xFB    */,
		0x7D /* 0xFC    */, 0x20 /* 0xFD    */, 0x20 /* 0xFE    */,
		0x20 /* 0xFF    */
	};


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

static char ACK1[] = { 'I', 'D', '=', '\0' };
static char ACK2[] = { S_ACK, S_CR, '\0' };
static char ACK3[] = { S_ESC, '[',  'p',  S_CR, '\0' };
static char ACK4[] = { S_CR, '\0' };
static char ACK5[] = { S_ACK, S_CR, '\0' };
static char ACK6[] = { S_NAK, S_CR, '\0' };
static char ACK7[] = { S_RS, S_CR, '\0' };
static char ACK8[] = { S_ESC, S_EOT, S_CR, '\0' };

static char REQ1[]  = { S_CR, '\0' };
static char REQ2a[] = { S_ESC, '\0' };
static char REQ2b[] = { S_CR, '\0' };
static char REQ3[]  = { S_STX, '\0' };
static char REQ4[]  = { S_CR, '\0' };
static char REQ5[]  = { S_CR, S_ETX, '\0' };
static char REQ6[]  = { S_CR, '\0' };
static char REQ7[]  = { S_EOT, S_CR, '\0' };

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

static char buf[MAX_RESPONSE_BUFSIZE];

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

static void TAP_checksum(char *);
static void TAP_hangup(void);
static void TAP_buildmessage(char *, char *, char *);

static int TAP_send_disconnect(void);
static int TAP_login(void);
static int TAP_sendmessage(char *, char *);

/* -------------------------------------------------------------------- */
/* -------------------------------------------------------------------- */
static void TAP_checksum(char *message)
{
	int 	i,
		checksum;

		
	checksum  = 0;
	for (i=0; i<strlen(message); i++)
	{
#if 1
		checksum += message[i];
#else
		/* Should we cast each character to		*/
		/* unsigned char to generate the correct	*/
		/* checksum for messages including 		*/
		/* characters with values >128			*/

		checksum += (unsigned char)message[i];
#endif
	}

	checksum = (checksum & 0x0FFF);	/* Mask low 12Bits */

	message[i]   = (((checksum & 0x0F00) >> 8) | 0x30);
	message[i+1] = (((checksum & 0x00F0) >> 4) | 0x30);
	message[i+2] = (((checksum & 0x000F)     ) | 0x30);
	message[i+3] = '\0';
}


/* -------------------------------------------------------------------- */
/* -------------------------------------------------------------------- */
static void TAP_hangup(void)
{	default_hangup((DRIVER_DEFAULT_ENV *)(&driver_env));
}

/* -------------------------------------------------------------------- */
/* -------------------------------------------------------------------- */
static int TAP_send_disconnect(void)
{
	lprintf(LOG_STANDARD, "Disconnect...\n");
	
	if (TAP_DISCONNECT)
	{	
		twrite(FD, REQ7, strlen(REQ7), WRITETIMEOUT);
		if (expstr(FD, buf, ACK8, MAX_RESPONSE_BUFSIZE, TIMEOUT) == 0)
		{
			lprintf(LOG_STANDARD, "Disconnected from SMSC\n");
		}
		else
		{	lprintf(LOG_STANDARD, "Failed to disconnect from SMSC\n");
		
			TAP_hangup();
			return ETAP_NODISCONNECT;
		}
	}

	return 0;
}


/* -------------------------------------------------------------------- */
/* -------------------------------------------------------------------- */
static int TAP_login(void)
{
	int 	i;


	lprintf(LOG_STANDARD, "Login...\n");
	

	/* Resend the S_CR every 2 seconds	*/
	/* until ID= received, only try		*/
	/* 5 times then give up.		*/

	sms_usleep(INITLOGINSLEEP);
	for (i=0; i < MAX_TRIES; i++)
	{
		twrite(FD, REQ1, strlen(REQ1), WRITETIMEOUT);

		if (expstr(FD, buf, ACK1, MAX_RESPONSE_BUFSIZE, LOGINTIMEOUT) == 0)
		{	
			lprintf(LOG_STANDARD, "SMSC Acknowledgment received\n");	
			break;
		}

		lprintf(LOG_STANDARD, "SMSC Retry for Acknowledgment\n");	
		sms_usleep(LOGINSLEEP);
	}

	if (i == MAX_TRIES)
	{
		lprintf(LOG_STANDARD, "Failed Acknowledgment %ld retries attempted\n", MAX_TRIES);
		
		TAP_hangup();
		return ETAP_NOACK;
	}

	
	/* Send S_ESC <TAP_SELECT> S_CR and get back	*/
	/* the acknowledgement S_ACK S_CR	*/

	twrite(FD, REQ2a,      strlen(REQ2a),      WRITETIMEOUT);
	twrite(FD, TAP_SELECT, strlen(TAP_SELECT), WRITETIMEOUT);
	twrite(FD, REQ2b,      strlen(REQ2b),      WRITETIMEOUT);

	if (expstr(FD, buf, ACK2, MAX_RESPONSE_BUFSIZE, TIMEOUT) == 0)
	{
		lprintf(LOG_STANDARD, "Login successful\n");
	}
	else
	{	lprintf(LOG_STANDARD, "Login failed\n");
	
		TAP_hangup();
		return ETAP_NOLOGIN;
	}

	if (expstr(FD, buf, ACK3, MAX_RESPONSE_BUFSIZE, TIMEOUT) == 0)
	{
		lprintf(LOG_STANDARD, "Ready to receive message\n");
	}
	else
	{	lprintf(LOG_STANDARD, "SMSC NOT Ready to receive message\n");
		
		TAP_hangup();
		return ETAP_NOREADY;
	}

	return 0;
}


/* -------------------------------------------------------------------- */
/* -------------------------------------------------------------------- */
static void TAP_buildmessage(char *tap_message, char *msisdn, char *message)
{
	int 	i;
	char	*ptr;


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

	tap_message[0] = '\0';
	
	strcat(tap_message, REQ3);
	strcat(tap_message, msisdn);
	strcat(tap_message, REQ4);

	
	/* Convert message so that it contains	*/
	/* only TAP valid characters		*/
	/* any invalid characters will be	*/
	/* mapped to a SPACE			*/
	/* Add to tap_message			*/

	ptr = tap_message;
	while(*ptr != '\0')
	{	ptr++;
	}

	for (i=0; i<strlen(message) +1; i++)
	{	*ptr = TAP_charset[(unsigned char)message[i]];
		ptr++;
	}

	*ptr = '\0';

	strcat(tap_message, REQ5);


	/* Generate TAP checksum. Consisting	*/
	/* of 3 characters. Append this to end	*/
	/* of tap_message.			*/

	TAP_checksum(tap_message);

	strcat(tap_message, REQ6);
}


/* -------------------------------------------------------------------- */
/* -------------------------------------------------------------------- */
static int TAP_sendmessage(char *msisdn, char *message)
{
	char 	tap_message[MAX_MESSAGE + MAX_ENVELOPE];


	TAP_buildmessage(tap_message, msisdn, message);
	
	twrite(FD, tap_message, strlen(tap_message), WRITETIMEOUT);
	if (TAP_RESPONSE)
	{	
		if (expstr(FD, buf, ACK4, MAX_RESPONSE_BUFSIZE, DELIVERTIMEOUT) == 0)
		{
			lprintf(LOG_STANDARD, "Received Message Response: %s\n", buf);
		}
		else
		{	lprintf(LOG_STANDARD, "No Message Response\n");
			
			TAP_hangup();
			return ETAP_NOMESSAGE;
		}
	}

	if (expstr(FD, buf, ACK5, MAX_RESPONSE_BUFSIZE, TIMEOUT) == 0)
	{
		lprintf(LOG_STANDARD, "Successful message submission\n");
	}
	else
	{ 	if (strncmp(&buf[strlen(buf) - strlen(ACK6)], ACK6, strlen(ACK6)) == 0)
		{
			lprintf(LOG_STANDARD, "Message submission failed - Transmission incomprehensible\n");
		}
		else if (strncmp(&buf[strlen(buf) - strlen(ACK7)], ACK7, strlen(ACK7)) == 0)
		{
			lprintf(LOG_STANDARD, "Message submission failed - Problem with message\n");
		}
		else
		{	lprintf(LOG_STANDARD, "Message submission failed - Reason Unknown\n");
		}
		
		TAP_hangup();
		return ETAP_NODELIVERY;
	}

	return 0;
}

/* -------------------------------------------------------------------- */
/* -------------------------------------------------------------------- */
DEVICE_ENTRY tap_device = {

	"TAP",
	"1.1",
	resource_list,
	(DRIVER_DEFAULT_ENV *)(&driver_env),

	default_init,
	default_main,
	default_validate_numeric_id,
	default_dial,
	default_hangup,
	TAP_send_disconnect,
	default_multiple_counted_deliver,
	TAP_sendmessage,
	TAP_login
};
