#include <stdio.h>
#include <stdarg.h>
#include <time.h>
#ifdef WIN32
#define vsnprintf _vsnprintf
#define snprintf _snprintf
#endif

#include "gsmlib.h"
#define CTRLZ 26

/*DEBUGGING */
static FILE *debugfp;

/* Internal functions used */
int gsm_haveline(Gsm *gsm, char *bf, int bfsz, int read_raw);
int gsm_process_message_info(GsmMsg *msg, char *info);
int gsm_process_message_notify(GsmMsg *msg, char *info);
char *windows_errmsg(void);
char *sms_error(int err_code);

/* Change to just lib.h when placed into sam directory */
#include "..\sam\lprintf.h"
#include "..\sam\lib.h"

static void debugfp_write(char *format, ...) {
	char temp[GSM_BFSZ];
	va_list args;
	time_t t;
	char *tmpstr[BFSZ];
	struct tm atm;
	
	time(&t);
	va_start(args,format);
	vsnprintf(temp,GSM_BFSZ-1,format,args);	
	fprintf(debugfp,"%.20s %s\n",safe_asctime(safe_localtime(&t,&atm),tmpstr)+4,temp);
	fflush(debugfp);
}

Gsm *gsm_new(void (*fn)(GsmMsg *)) {
	Gsm *gsm = calloc(1, sizeof(Gsm));
	gsm->gsm = INVALID_HANDLE_VALUE;
	gsm->callbk = fn;

	debugfp = fopen("\\gsm.log","a");
	return gsm;
}

int gsm_open(Gsm *gsm, int comport) {
	COMMTIMEOUTS timeouts;
	char bf[GSM_BFSZ];

	sprintf(bf,"COM%d",comport);
	gsm->gsm = CreateFile(bf,GENERIC_READ|GENERIC_WRITE,0,0,OPEN_EXISTING,FILE_FLAG_OVERLAPPED,0);
	if (gsm->gsm==INVALID_HANDLE_VALUE) {
		strcpy(gsm->error,windows_errmsg());
		return FALSE;
	}
	/* Clear comm buffers pending reads/writes etc - not reqd here.
	if (!PurgeComm(gsm->gsm,PURGE_TXABORT|PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR)) {
		strcpy(gsm->error,windows_errmsg());
		return FALSE;
	}
	*/
	// Get old timeout values
	if (!GetCommTimeouts(gsm->gsm,&gsm->timeouts)) {
		strcpy(gsm->error,windows_errmsg());
		return FALSE;
	}
	// Set new timeout values
	memcpy(&timeouts,&gsm->timeouts,sizeof(COMMTIMEOUTS));
	timeouts.ReadIntervalTimeout = 10;
	timeouts.ReadTotalTimeoutMultiplier = 0;
	timeouts.ReadTotalTimeoutConstant = 0;
	timeouts.WriteTotalTimeoutMultiplier = 0;
	timeouts.WriteTotalTimeoutConstant = 5000;
	if (!SetCommTimeouts(gsm->gsm,&timeouts)) {
		strcpy(gsm->error,windows_errmsg());
		return FALSE;
	}

	//Setup com properties
	memset(&gsm->dcb,0,sizeof(DCB));
	gsm->dcb.DCBlength = sizeof(gsm->dcb);
	gsm->dcb.fBinary = TRUE;
	gsm->dcb.fParity = FALSE;
	gsm->dcb.BaudRate = CBR_9600;
	gsm->dcb.StopBits = ONESTOPBIT;
	gsm->dcb.ByteSize = 8;
	gsm->dcb.Parity = NOPARITY;	
	gsm->dcb.fDtrControl = DTR_CONTROL_ENABLE;
	gsm->dcb.fRtsControl =  RTS_CONTROL_ENABLE;
	gsm->dcb.fTXContinueOnXoff = TRUE;
	gsm->dcb.fAbortOnError = TRUE;
	gsm->dcb.XonLim = 2048;
	gsm->dcb.XoffLim = 512;
	gsm->dcb.XonChar = 17;
	gsm->dcb.XoffChar = 19;
	if (!SetCommState(gsm->gsm,&gsm->dcb)) {
		strcpy(gsm->error,windows_errmsg());
		return FALSE;
	}

	if (!gsm_write(gsm,"AT+CBST=7,0,0\r\n")) return FALSE;
	if (!gsm_readline(gsm,bf,GSM_BFSZ,INFINITE)) return FALSE;
	if (strcmp(bf,"AT+CBST=7,0,0")==0) if (!gsm_readline(gsm,bf,GSM_BFSZ,INFINITE)) return FALSE;
	if (gsm_iserror(gsm,bf)) return FALSE;

	/* Turn Echo off */
	if (!gsm_write(gsm,"ATE0\r\n")) return FALSE;
	if (!gsm_readline(gsm,bf,GSM_BFSZ,INFINITE)) return FALSE;
	if (strcmp(bf,"ATE0\r")==0) if (!gsm_readline(gsm,bf,GSM_BFSZ,INFINITE)) return FALSE;
	if (strcmp(bf,"OK")!=0) {
		lprintf(gsm->error,GSM_BFSZ-1,"ATE0 not OK [%s]",bf);
		return FALSE;
	}

	/* Set error codes ON */
	if (!gsm_write(gsm,"AT+CMEE=1\r\n")) return FALSE;
	if (!gsm_readline(gsm,bf,GSM_BFSZ,INFINITE)) return FALSE;
	if (gsm_iserror(gsm,bf)) return FALSE;

try_again:
	/* Check if it can handle SMS */
	if (!gsm_write(gsm,"AT+CSMS=0\r\n")) return FALSE;
	if (!gsm_readline(gsm,bf,GSM_BFSZ,INFINITE)) return FALSE;
	if (gsm_iserror(gsm,bf)) {
		/* Still initialising? */
		if (gsm_errcode(bf)==515) {
			lib_msleep(500);
			goto try_again;
		}
		return FALSE;
	}
	if (!gsm_readline(gsm,bf,GSM_BFSZ,INFINITE)) return FALSE;

	/* Set text mode */
	if (!gsm_write(gsm,"AT+CMGF=1\r\n")) return FALSE;
	if (!gsm_readline(gsm,bf,GSM_BFSZ,INFINITE)) return FALSE;
	if (gsm_iserror(gsm,bf)) return FALSE;

	/* Enable notification of new messages */
	if (!gsm_write(gsm,"AT+CNMI=1,1,0,0,0\r\n")) return FALSE;
	if (!gsm_readline(gsm,bf,GSM_BFSZ,INFINITE)) return FALSE;
	if (gsm_iserror(gsm,bf)) return FALSE;

	return TRUE;
}

int gsm_close(Gsm *gsm) {
	if (gsm->gsm==INVALID_HANDLE_VALUE) return TRUE;
	/* Re-set old timeout values */
	if (SetCommTimeouts(gsm->gsm,&gsm->timeouts)) {
		strcpy(gsm->error,windows_errmsg());
		return FALSE;
	}
	CloseHandle(gsm->gsm);
	gsm->gsm = INVALID_HANDLE_VALUE;
	return TRUE;
}

void gsm_free(Gsm **gsm) {
	if (*gsm==NULL) return;
	if ((*gsm)->gsm!=INVALID_HANDLE_VALUE) gsm_close(*gsm);
	if (debugfp) fclose(debugfp);
}

int gsm_write(Gsm *gsm, char *string, ...) {
	OVERLAPPED WriteDone = {0};
	char bf[GSM_BFSZ];
	va_list arg;
	int bytes;	

	if (gsm->gsm == INVALID_HANDLE_VALUE) {
		strcpy(gsm->error,"Invalid GSM handle");
		return 0;
	}
	va_start(arg,string);
	vsnprintf(bf,GSM_BFSZ,string,arg);
	va_end(arg);

	WriteDone.hEvent = CreateEvent(NULL,TRUE,FALSE,NULL);
	if (WriteDone.hEvent==NULL) {
		strcpy(gsm->error,windows_errmsg());
		return 0;
	}

	debugfp_write("WRITE %d[%s]\n\n",strlen(bf),bf);
	/*
	fprintf(debugfp,"WRITE %d[%s]\n\n",strlen(bf),bf);
	fflush(debugfp);
	*/
	if (!WriteFile(gsm->gsm,bf,strlen(bf),&bytes,&WriteDone)) {
		if (GetLastError() != ERROR_IO_PENDING) { 
			strcpy(gsm->error,windows_errmsg());
			bytes = 0;
		}
		else {
			if (!GetOverlappedResult(gsm->gsm,&WriteDone,&bytes,TRUE)) {
				strcpy(gsm->error,windows_errmsg());
				bytes = 0;
			}
		}
	}
	CloseHandle(WriteDone.hEvent);
	return bytes;
}

int gsm_readline_raw(Gsm *gsm, char *bf, int bfsz, int milli, int read_raw) {
	static BOOL waiting;
	int bytes;

read_again:
	while (!waiting) {
		if (gsm_haveline(gsm,bf,bfsz,read_raw)) return TRUE;

		memset(&gsm->sync,0,sizeof(OVERLAPPED));
		gsm->sync.hEvent = CreateEvent(NULL,TRUE,FALSE,NULL);
		if (ReadFile(gsm->gsm,&gsm->readbf[gsm->inlen],GSM_BFSZ-gsm->inlen,&bytes,&gsm->sync)!=0) {
			CloseHandle(gsm->sync.hEvent);
			continue;
		}

		if (GetLastError()!=ERROR_IO_PENDING) {
			strcpy(gsm->error,windows_errmsg());
			CloseHandle(gsm->sync.hEvent);
			return FALSE;
		}

		waiting = TRUE;
	}

	if (waiting) {
		switch(WaitForSingleObject(gsm->sync.hEvent,milli)) {
		case WAIT_OBJECT_0:
			waiting = FALSE;
			if (!GetOverlappedResult(gsm->gsm,&gsm->sync,&bytes,FALSE)) {
				strcpy(gsm->error,windows_errmsg());
				CloseHandle(gsm->sync.hEvent);
				return FALSE;
			}
			CloseHandle(gsm->sync.hEvent);
			gsm->inlen += bytes;
			gsm->readbf[gsm->inlen] = '\0';
			debugfp_write("READ +%d = %d[%s]\n\n",bytes,gsm->inlen,gsm->readbf);
			/*
			fprintf(debugfp,"READ +%d = %d[%s]\n\n",bytes,gsm->inlen,gsm->readbf);
			fflush(debugfp);
			*/
			goto read_again;
		case WAIT_TIMEOUT:
			break;
		default:
			waiting = FALSE;
			CloseHandle(gsm->sync.hEvent);
			strcpy(gsm->error,windows_errmsg());				
			return FALSE;
		};
	}
	return -1;
}

int gsm_poll(Gsm *gsm, int milli) {
	char bf[GSM_BFSZ];
	GsmMsg *msg;
	int status;	
	
	status = gsm_readline_raw(gsm,bf,GSM_BFSZ,milli,TRUE);
	if (status == FALSE) return FALSE;
	if (status == -TRUE) return TRUE;
	if ((msg = gsm_isincoming(bf))) {
		if (gsm->callbk) gsm->callbk(msg);
		else gsm_msg_free(&msg);
	}
	else if (gsm_isring(bf)) gsm_clear_ring(gsm);
	else {
		strcpy(gsm->error,bf);
		return -TRUE;
	}
	return TRUE;
}

int gsm_text_notify(Gsm *gsm, int mode, void (*fn)(GsmMsg *)) {
	char bf[GSM_BFSZ];

	if (!gsm_write(gsm,"AT+CNMI=1,%d,0,0,0\r\n"),(mode==FALSE)?0:1) return FALSE;
	if (!gsm_readline(gsm,bf,GSM_BFSZ,INFINITE)) return FALSE;
	if (gsm_iserror(gsm,bf)) return FALSE;
	if (fn) gsm->callbk = fn;
	return TRUE;
}

int gsm_text_recv(Gsm *gsm, GsmMsg *msg) {
	char bf[GSM_BFSZ];

	if (!gsm_write(gsm,"AT+CMGR=%d\r\n",msg->index)) return FALSE;
	if (!gsm_readline(gsm,bf,GSM_BFSZ,INFINITE)) return FALSE;
	if (gsm_iserror(gsm,bf)) return FALSE;
	if (!gsm_process_message_info(msg,&bf[7])) {
		lprintf(gsm->error,GSM_BFSZ-1,"Message info string incorrect{%s}",&bf[7]);
		return FALSE;
	}
	if (!gsm_readline(gsm,bf,GSM_BFSZ,INFINITE)) return FALSE;
	msg->message[0] = '\0';
	while (TRUE) {
		ncat(msg->message,bf,GSM_BFSZ-1);
		if (!gsm_readline(gsm,bf,GSM_BFSZ,INFINITE)) return FALSE;
		if (strcmp(bf,"OK")==0) break;
		ncat(msg->message,"\r\n",GSM_BFSZ-1);
	}
	return TRUE;
}

int gsm_text_stored(Gsm *gsm, GsmMsg *stored[], int length, int *read) {
	char bf[GSM_BFSZ], *ptr;
	int index;

	if (read) *read = 0;

	/* Ask for all messages */
	if (!gsm_write(gsm,"AT+CMGL=\"ALL\"\r\n")) return FALSE;
	if (!gsm_readline(gsm,bf,GSM_BFSZ,INFINITE)) return FALSE;
	if (gsm_iserror(gsm,bf)) return FALSE;

	for ( index=0; index<length; index++ ) {
		/* break on OK line */
		if (strcmp(bf,"OK")==0) break;

		/* create msg struct */
		stored[index] = calloc(1,sizeof(GsmMsg));
		stored[index]->index = atoi(&bf[7]);

		/* read msg info */
		for ( ptr=&bf[7]; *ptr && *ptr!=','; ptr++ );
		if (!gsm_process_message_info(stored[index],ptr+1)) {
			lprintf(gsm->error,GSM_BFSZ-1,"Message info string incorrect{%s}",ptr+1);
			return FALSE;
		}

		/* read message text */
		if (!gsm_readline(gsm,bf,GSM_BFSZ,INFINITE)) return FALSE;
		stored[index]->message[0] = '\0';
		while (TRUE) {
			ncat(stored[index]->message,bf,GSM_BFSZ-1);
			if (!gsm_readline(gsm,bf,GSM_BFSZ,INFINITE)) return FALSE;
			if (strcmp(bf,"OK")==0 || strncmp(bf,"+CMGL:",6)==0) break;
			ncat(stored[index]->message,"\r\n",GSM_BFSZ-1);
		}

		if (read) (*read)++;
	}	
	return TRUE;
}

int gsm_text_delete(Gsm *gsm, GsmMsg *msg) {
	char bf[GSM_BFSZ];

	if (!gsm_write(gsm,"AT+CMGD=%d\r\n",msg->index)) return FALSE;
	if (!gsm_readline(gsm,bf,GSM_BFSZ,INFINITE)) return FALSE;
	if (gsm_iserror(gsm,bf)) return FALSE;
	return TRUE;
}

int gsm_text_send(Gsm *gsm, char *phone, char *message) {
	char bf[GSM_BFSZ];

	if (!gsm_write(gsm,"AT+CMGS=\"%s\"\r\n",phone)) return FALSE;
	if (!gsm_readline(gsm,bf,GSM_BFSZ,INFINITE)) return FALSE;
	if (!gsm_write(gsm,"%.159s%c",message,CTRLZ)) return FALSE;
	if (!gsm_readline(gsm,bf,GSM_BFSZ,INFINITE)) return FALSE;
	if (gsm_iserror(gsm,bf)) return FALSE;
	if (!gsm_readline(gsm,bf,GSM_BFSZ,INFINITE)) return FALSE;
	return TRUE;
}

int gsm_clear_ring(Gsm *gsm) {
	char bf[GSM_BFSZ];

	if (!gsm_write(gsm,"ATA\r\n")) return FALSE;
	if (!gsm_readline(gsm,bf,GSM_BFSZ,INFINITE)) return FALSE;
	if (!gsm_write(gsm,"ATH0\r\n")) return FALSE;
	if (!gsm_readline(gsm,bf,GSM_BFSZ,INFINITE)) return FALSE;
	return TRUE;
}

char *gsm_error(Gsm *gsm) {
	return gsm->error;
}

void gsm_clear_error(Gsm *gsm) {
	gsm->error[0] = '\0';
}

/* Here be 'Internal fns' */
int gsm_haveline(Gsm *gsm, char *bf, int bfsz, int read_raw) {
	char *start, *end;
	GsmMsg *msg;

	bf[0] = '\0';
	while (TRUE) {
		if ( gsm->inlen == 0 ) return 0;

		for ( start=gsm->readbf; *start=='\r' || *start=='\n'; start++ ); 
		end = strchr(start,'\n');

		if (end==NULL) {
			if (strcmp(start,"> ")!=0) return 0;
			end = start+1;
		}

		*end = '\0';
		if(end[-1]=='\r') end[-1] = '\0';
		ncpy(bf,start,bfsz-1);
		gsm->inlen -= (end+1-gsm->readbf);
		if (gsm->inlen > 0) memmove(gsm->readbf,end+1,gsm->inlen+1);
		gsm->readbf[gsm->inlen] = '\0';
		
		if (read_raw) break;

		if ( gsm_isring(bf) ) {
			gsm_clear_ring(gsm);
			continue;
		}

		msg = gsm_isincoming(bf);
		if (!msg) break;
		if (gsm->callbk) gsm->callbk(msg);
		else gsm_msg_free(&msg);
	}
	return strlen(bf);
}

/* gsm_process_message_info
Expects "<stat>","<oa>",["<alpha>"],"<scts>"[,<tooa>,<fo>,<pid>,<dcs>,"<sca>",<tosca>,<length>] 
with NO \r\n
*/
int gsm_process_message_info(GsmMsg *msg, char *info) {
	int index;
	char *ptr;
	
	for ( ptr=&info[1],index=0; *ptr && *ptr!='\"' && index<GSM_BFSZ; ptr++,index++ )
		msg->status[index] = *ptr;
	msg->status[index] = '\0';
	
	if (*ptr=='\0') return FALSE;

	for ( ptr+=3,index=0; *ptr && *ptr!='\"' && index<GSM_BFSZ; ptr++,index++ )
		msg->phone[index] = *ptr;
	msg->phone[index] = '\0';

	if (*ptr=='\0') return FALSE;
	
	ptr++;
	if (*(ptr+1)!=',') {
		for ( ptr+=2,index=0; *ptr && *ptr!='\"' && index<GSM_BFSZ; ptr++,index++ )
			msg->alpha[index] = *ptr;
		msg->alpha[index] = '\0';
	}
	else msg->alpha[0] = '\0';

	if (*ptr=='\0') return FALSE;
	
	for ( ptr+=3,index=0; *ptr && *ptr!='\"' && index<GSM_BFSZ; ptr++,index++ )
		msg->timestamp[index] = *ptr;
	msg->timestamp[index] = '\0';	
	ptr++;

	if (*ptr=='\0') return TRUE;

	ptr++;
	if (*ptr && *ptr!=',') {
		msg->type_of_originating_address = atoi(ptr);
		for ( ptr++; *ptr && *ptr!=','; ptr++ );
	}
	if (*ptr=='\0') return TRUE;
	
	ptr++;
	if (*ptr && *ptr!=',') {
		msg->first_octet_of_sms = atoi(ptr);
		for ( ptr++; *ptr && *ptr!=','; ptr++ );
	}
	if (*ptr=='\0') return TRUE;

	ptr++;
	if (*ptr && *ptr!=',') {
		msg->protocal_identifier = atoi(ptr);
		for ( ptr++; *ptr && *ptr!=','; ptr++ );
	}
	if (*ptr=='\0') return TRUE;

	ptr++;
	if (*ptr && *ptr!=',') {
		msg->data_coding_sceme = atoi(ptr);
		for ( ptr++; *ptr && *ptr!=','; ptr++ );
	}
	if (*ptr=='\0') return TRUE;

	ptr++;
	if (*ptr=='\"') {
		for ( index=0; *ptr && *ptr!='\"' && index<GSM_BFSZ; index++,ptr++ )
			msg->service_center[index] = *ptr;
		msg->service_center[index] = '\0';
		if (*ptr=='\"') ptr++;
	}
	if (*ptr=='\0') return TRUE;

	ptr++;
	if (*ptr && *ptr!=',') {
		msg->type_of_service_center = atoi(ptr);
		for ( ptr++; *ptr && *ptr!=','; ptr++ );
	}
	if (*ptr=='\0') return TRUE;

	ptr++;
	if (*ptr && *ptr!=',') {
		msg->length = atoi(ptr);
		for ( ptr++; *ptr && *ptr!=','; ptr++ );
	}
	if (*ptr=='\0') return TRUE;

	return TRUE;
}

/* gsm_process_message_notify
Expects "<mem>",<index>
with NO \r\n
*/
int gsm_process_message_notify(GsmMsg *msg, char *info) {
	int index;
	char *ptr;

	for ( ptr=&info[1],index=0; *ptr && *ptr!='\"' && index < GSM_BFSZ; ptr++,index++ )
		msg->memory[index] = *ptr;
	
	if (*ptr=='\0') return FALSE;

	msg->index = atoi(ptr+2);

	return TRUE;
}

int gsm_isring(char *bf) {
	if (stricmp(bf,"RING")==0) return TRUE;
	return FALSE;
}

GsmMsg *gsm_isincoming(char *bf) {
	if (strncmp(bf,"+CMTI:",6)==0) {
		GsmMsg *msg = calloc(1,sizeof(GsmMsg));

		if (gsm_process_message_notify(msg,&bf[7])) return msg;
		gsm_msg_free(&msg);
	}
	return NULL;
}

int gsm_iserror(Gsm *gsm, char *bf) {
	if (bf) {
		if (strncmp(bf,"+CMS ERROR:",11)==0) {
			strcpy(gsm->error,sms_error(atoi(&bf[12])));
			return TRUE;
		}
		if (strcmp(bf,"ERROR")==0) {
			strcpy(gsm->error,"NO ERROR CODE GIVEN");
			return TRUE;
		}
	}
	return FALSE;
}

int gsm_errcode(char *bf) {
	if (bf) {
		if (strncmp(bf,"+CMS ERROR:",11)==0) return atoi(&bf[12]);
		if (strcmp(bf,"ERROR")==0) return -1;
	}
	return -2;
}

char *windows_errmsg(void) {
	static char errmsg[GSM_BFSZ];

	if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM,NULL,GetLastError(),MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT),(LPTSTR)errmsg,GSM_BFSZ,NULL)==0)
		sprintf(errmsg,"[%d] Failed to generate error message",GetLastError());
	return errmsg;
}

char *sms_error(int err_code) {
	static char errmsg[GSM_BFSZ];

	if (err_code>=0 && err_code<=127) {
		/*0...127 GSM 04.11 Annex E-2 values*/
		if (err_code==0) sprintf(errmsg,"[%d] unspecified GSM radio link layer error",err_code);
		else if (err_code==1) sprintf(errmsg,"[%d] unassigned number",err_code);
		else if (err_code==2) sprintf(errmsg,"[%d] operator determined barring",err_code);
		else if (err_code==3) sprintf(errmsg,"[%d] call barred",err_code);
		else if (err_code==4) sprintf(errmsg,"[%d] CUG reject",err_code);
		else if (err_code==5) sprintf(errmsg,"[%d] transfer rejected",err_code);
		else if (err_code==6) sprintf(errmsg,"[%d] destination out of service",err_code);
		else if (err_code==7) sprintf(errmsg,"[%d] unidentified subscriber",err_code);
		else if (err_code==8) sprintf(errmsg,"[%d] facility rejected",err_code);
		else if (err_code==9) sprintf(errmsg,"[%d] unknown subscriber",err_code);
		else if (err_code==10) sprintf(errmsg,"[%d] network out of order",err_code);
		else if (err_code==11) sprintf(errmsg,"[%d] temporary failure",err_code);
		else if (err_code==12) sprintf(errmsg,"[%d] congestion",err_code);
		else if (err_code==13) sprintf(errmsg,"[%d] resources unavailable, unspecified",err_code);
		else if (err_code==14) sprintf(errmsg,"[%d] requested facility not implemented",err_code);
		else if (err_code==15) sprintf(errmsg,"[%d] invalid SM transfer reference value",err_code);
		else if (err_code==16) sprintf(errmsg,"[%d] invalid message, unspecified",err_code);
		else if (err_code==17) sprintf(errmsg,"[%d] mandatory information element missing",err_code);
		else if (err_code==18) sprintf(errmsg,"[%d] message type non-existent or not implemented ",err_code);
		else if (err_code==19) sprintf(errmsg,"[%d] SM not compatible with SM transfer state or message type non-existent or not implemented",err_code);
		else if (err_code==20) sprintf(errmsg,"[%d] information element non-existent or not implemented",err_code);
		else if (err_code==21) sprintf(errmsg,"[%d] invalid information element contents",err_code);
		else if (err_code==22) sprintf(errmsg,"[%d] SM not compatible with SM transfer state",err_code);
		else if (err_code==23) sprintf(errmsg,"[%d] protocol error, unspecified",err_code);
		else if (err_code==24) sprintf(errmsg,"[%d] interworking failure, unspecified",err_code);
		else if (err_code>=25 && err_code<=49) sprintf(errmsg,"[%d] Reserved",err_code); /* Guess */
		else if (err_code==50) sprintf(errmsg,"[%d] unspecified GSM transport layer error",err_code);
		else if (err_code==51) sprintf(errmsg,"[%d] telematic interworking not supported",err_code);
		else if (err_code==52) sprintf(errmsg,"[%d] short message Type 0 not supported",err_code);
		else if (err_code==53) sprintf(errmsg,"[%d] cannot replace short message",err_code);
		else if (err_code>=54 && err_code<=65) sprintf(errmsg,"[%d] Reserved",err_code); /* Guess */
		else if (err_code==66) sprintf(errmsg,"[%d] unspecified TP-PID error",err_code);
		else if (err_code==67) sprintf(errmsg,"[%d] data coding scheme not supported",err_code);
		else if (err_code==68) sprintf(errmsg,"[%d] message class not supported",err_code);
		else if (err_code>=69 && err_code<=81) sprintf(errmsg,"[%d] Reserved",err_code); /* Guess */
		else if (err_code==82) sprintf(errmsg,"[%d] unspecified TP-DCS error",err_code);
		else if (err_code==83) sprintf(errmsg,"[%d] command cannot be actioned",err_code);
		else if (err_code>=84 && err_code<=97) sprintf(errmsg,"[%d] Reserved",err_code); /* Guess */
		else if (err_code==98) sprintf(errmsg,"[%d] unspecified TP-Command error",err_code);
		else if (err_code==99) sprintf(errmsg,"[%d] TPDU not supported",err_code);
		else if (err_code>=100 && err_code<=114) sprintf(errmsg,"[%d] Reserved",err_code); /* Guess */
		else if (err_code==115) sprintf(errmsg,"[%d] SMSC busy",err_code);
		else if (err_code==116) sprintf(errmsg,"[%d] no SMSC subscription",err_code);
		else if (err_code==117) sprintf(errmsg,"[%d] SMSC system failure",err_code);
		else if (err_code==118) sprintf(errmsg,"[%d] invalid SME address",err_code);
		else if (err_code==119) sprintf(errmsg,"[%d] destination SME barred",err_code);
		else if (err_code>=120 && err_code<=126) sprintf(errmsg,"[%d] Reserved",err_code); /* Guess */
		else if (err_code==127) sprintf(errmsg,"[%d] memory capacity exceeded",err_code);
	}
	else if (err_code>=128 && err_code<=255) {
		/* 128...255 GSM 03.40 subclause 9.2.3.22 values */

		/* TP-PID errors */
		if (err_code==128) sprintf(errmsg,"[%d] Telematic interworking not supported",err_code);
		else if (err_code==129) sprintf(errmsg,"[%d] Short message type 0 not supported",err_code);
		else if (err_code==130) sprintf(errmsg,"[%d] Cannot replace short message",err_code);
		else if (err_code>=131 && err_code<=142) sprintf(errmsg,"[%d] Reserved", err_code);
		else if (err_code==143) sprintf(errmsg,"[%d] Unspecified TP-PID error",err_code);

		/* TP-DCS errors */
		else if (err_code==144) sprintf(errmsg,"[%d] Data coding sceme (alphabet) not supported",err_code);
		else if (err_code==145) sprintf(errmsg,"[%d] Message class not supported",err_code);
		else if (err_code>=146 && err_code<=158) sprintf(errmsg,"[%d] Reserved",err_code);
		else if (err_code==159) sprintf(errmsg,"[%d] Unspecified TP-DCS error",err_code);

		/* TP-Command errors */
		else if (err_code==160) sprintf(errmsg,"[%d] Command cannot be actioned",err_code);
		else if (err_code==161) sprintf(errmsg,"[%d] Command unsupported",err_code);
		else if (err_code>=162 && err_code<=174) sprintf(errmsg,"[%d] Reserved",err_code);
		else if (err_code==175) sprintf(errmsg,"[%d] Unspecified TP-Command error",err_code);
		else if (err_code==176) sprintf(errmsg,"[%d] TPDU not supported",err_code);
		else if (err_code>=177 && err_code <=191) sprintf(errmsg,"[%d] Reserved",err_code);
		else if (err_code==192) sprintf(errmsg,"[%d] SC busy",err_code);
		else if (err_code==193) sprintf(errmsg,"[%d] No SC subscription",err_code);
		else if (err_code==194) sprintf(errmsg,"[%d] SC system failure",err_code);
		else if (err_code==195) sprintf(errmsg,"[%d] Invalid SME address",err_code);
		else if (err_code==196) sprintf(errmsg,"[%d] Destination SME barred",err_code);
		else if (err_code==197) sprintf(errmsg,"[%d] SM Rejected-Duplicate SM",err_code);
		else if (err_code>=198 && err_code<=207) sprintf(errmsg,"[%d] Reserved",err_code);
		else if (err_code==208) sprintf(errmsg,"[%d] SIM SMS storage full",err_code);
		else if (err_code==209) sprintf(errmsg,"[%d] No SMS storage capability in SIM",err_code);
		else if (err_code==210) sprintf(errmsg,"[%d] Error in MS",err_code);
		else if (err_code==211) sprintf(errmsg,"[%d] Memory Capacity Exceeded",err_code);
		else if (err_code>=212 && err_code<=223) sprintf(errmsg,"[%d] Reserved",err_code);
		else if (err_code>=224 && err_code<=254) sprintf(errmsg,"[%d] Values specific to an aplication",err_code);
		else if (err_code==255) sprintf(errmsg,"[%d] Unspecified error cause",err_code);
	}
	else if (err_code==300) sprintf(errmsg,"[%d] 300 ME failure",err_code);
	else if (err_code==301) sprintf(errmsg,"[%d] SMS service of ME reserved",err_code);
	else if (err_code==302) sprintf(errmsg,"[%d] operation not allowed",err_code);
	else if (err_code==303) sprintf(errmsg,"[%d] operation not supported",err_code);
	else if (err_code==304) sprintf(errmsg,"[%d] invalid PDU mode parameter",err_code);
	else if (err_code==305) sprintf(errmsg,"[%d] invalid text mode parameter",err_code);
	else if (err_code==310) sprintf(errmsg,"[%d] SIM not inserted",err_code);
	else if (err_code==311) sprintf(errmsg,"[%d] SIM PIN required",err_code);
	else if (err_code==312) sprintf(errmsg,"[%d] PH-SIM PIN required",err_code);
	else if (err_code==313) sprintf(errmsg,"[%d] SIM failure",err_code);
	else if (err_code==314) sprintf(errmsg,"[%d] SIM busy",err_code);
	else if (err_code==315) sprintf(errmsg,"[%d] SIM wrong",err_code);
	else if (err_code==316) sprintf(errmsg,"[%d] SIM PUK required",err_code);
	else if (err_code==317) sprintf(errmsg,"[%d] SIM PIN2 required",err_code);
	else if (err_code==318) sprintf(errmsg,"[%d] SIM PUK2 required",err_code);
	else if (err_code==320) sprintf(errmsg,"[%d] memory failure",err_code);
	else if (err_code==321) sprintf(errmsg,"[%d] invalid memory index",err_code);
	else if (err_code==322) sprintf(errmsg,"[%d] memory full",err_code);
	else if (err_code==330) sprintf(errmsg,"[%d] SMSC address unknown",err_code);
	else if (err_code==331) sprintf(errmsg,"[%d] no network service",err_code);
	else if (err_code==332) sprintf(errmsg,"[%d] network timeout",err_code);
	else if (err_code==340) sprintf(errmsg,"[%d] no +CNMA acknowledgement expected",err_code);
	else if (err_code==500) sprintf(errmsg,"[%d] unknown error",err_code);
	/* Next line MUST come after block above */
	else if (err_code>=256 && err_code<=511) sprintf(errmsg,"[%d] Reserved",err_code);
	else if (err_code>=512) sprintf(errmsg,"[%d] Manufacturer specific",err_code);
	else sprintf(errmsg,"[%d] UNKNOWN ERROR CODE",err_code);
	return errmsg;
}

void gsm_msg_free(GsmMsg **msg) {
	GsmMsg *ptr, *next;

	for ( ptr=*msg; ptr; ptr=next ) {
		next = ptr->next;
		free(ptr);
	}
	*msg = NULL;
}
