#include "common.h"
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <signal.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <netdb.h>
#include <sys/select.h>
#include <errno.h>
#include <stdio.h>
#include <ctype.h>

#define MAXSTRLEN (1024)

static void
common_Log(FILE *pLog, const char *csFmtStr, va_list va_args);
static void
common_DoExit(int iExitCode);
static void
common_SigpipeHandle(int x);

/*function to wait for a specific FILE to be ready for reading*/
ERROR_STATUS
COMMON_ReadWaitFile(FILE *phF)
{
	int iFDNum=fileno(phF);
	return COMMON_ReadWaitFD(iFDNum);
}
/*function to wait for a specific File Descriptor (file, socket, pipe) to be ready for reading*/
ERROR_STATUS
COMMON_ReadWaitFD(int iFD)
{
	int iStatus;
	fd_set fds_r,fds_w,fds_ex;

	FD_ZERO(&fds_r);
	FD_ZERO(&fds_w);
	FD_ZERO(&fds_ex);
	FD_SET(iFD,&fds_r);

	iStatus=select(iFD+1,&fds_r,&fds_w,&fds_ex,NULL);

	if(iStatus>0) {
		/*more one file ready (i.e. should only be one)*/
		iStatus=SUCCESS;
	} else {
		/*something gone wrond or we are interrupted*/
		iStatus=ERROR_INTERRUPT;
	}
	return iStatus;
}


// Function to read a proxy list from file and populate array of proxies
// Each proxy should be a separate line containing the url only
// e.g.:
//127.0.0.1:8423
//127.0.0.1:8424
//127.0.0.1:4433
ERROR_STATUS
COMMON_ReadProxyListFile(SSL *ptSSL,
		const char *sFilename){
	FILE *phF;					// pointer to file
	unsigned int uiCount = 0;	// simple counters
	char acLine [128];			// maximum size of line to read from
	SPP_PROXY *patNewProxiesList=NULL;

	if(ptSSL==NULL || sFilename==NULL) {
		return INVALID_POINTER;
	}

	// Open file for reading
	phF = fopen(sFilename,"r");

	// Check for errors while opening file
	if( phF == NULL ){
		return FILE_NOT_READABLE;
	}

	uiCount=0;
	// Read file line-by-line
	while ( fgets ( acLine, sizeof(acLine), phF ) != NULL ) {

		// Remove trailing newline (NOTE: strtoK is not thread safe)
		strtok(acLine, "\n");
		if(strlen(acLine)<=2) continue; //ignore blank lines

		COMMON_AppendProxy(ptSSL,acLine);

		/*TODO - log the proxy creation properly*/
		COMMON_Log(stdout,"Proxy(%2u): %s\n",uiCount,acLine);
	}

	// Close file
	fclose(phF);
	return SUCCESS;
}

ERROR_STATUS
COMMON_InitProxySet(SSL *ptSSL)
{
	while(ptSSL->proxies_len>0) {
		ptSSL->proxies_len--;
		OPENSSL_free(ptSSL->proxies[ptSSL->proxies_len]);
	}
	ptSSL->proxies_len=0;
	memset(ptSSL->proxies,0,sizeof(ptSSL->proxies));
	return SUCCESS;
}
ERROR_STATUS
COMMON_InitMulticontextSet(SSL *ptSSL)
{
	while(ptSSL->slices_len>0) {
		ptSSL->slices_len--;
		OPENSSL_free(ptSSL->slices[ptSSL->slices_len]);
	}
	ptSSL->slices_len=0;
	memset(ptSSL->slices,0,sizeof(ptSSL->slices));
	return SUCCESS;
}

ERROR_STATUS
COMMON_AppendProxy(SSL *ptSSL,
		const char *psProxyURL)
{
	char *psProxyUrlCpy=NULL;

	if(ptSSL->proxies_len>=(sizeof(ptSSL->proxies)/sizeof(ptSSL->proxies[0])))
		return ARRAY_OVERFLOW;

	/*SPP_generate_proxy will generate something with the string provide
	 * but there is no guarantee that it will be sensible if
	 * invalid data is passed in*/
	psProxyUrlCpy=strdup(psProxyURL);
	ptSSL->proxies[ptSSL->proxies_len] = SPP_generate_proxy(ptSSL, psProxyUrlCpy);
	(ptSSL->proxies_len)++;
	return SUCCESS;
}
ERROR_STATUS
COMMON_SetServer(SSL *ptSSL,
		const char *psURL)
{
	char *psUrlCpy=NULL;

	psUrlCpy=strdup(psURL);
	ptSSL->spp_server_address = psUrlCpy;
	return SUCCESS;
}
ERROR_STATUS
COMMON_AppendContext(SSL *ptSSL,
		const char *psContextDesc)
{
	char *psContextDescCpy=NULL;

	if(ptSSL->slices_len>=(sizeof(ptSSL->slices)/sizeof(ptSSL->slices[0])))
		return ARRAY_OVERFLOW;
	psContextDescCpy=strdup(psContextDesc);
	ptSSL->slices[ptSSL->slices_len] = SPP_generate_slice(ptSSL, psContextDescCpy);
	ptSSL->slices_len++;
	return SUCCESS;

}

ERROR_STATUS
COMMON_SetProxyAccessPermissionByID(SSL *ptSSL, int iSliceID, int iMiddleboxNum,
							int bGrantRead, int bGrantWrite)
{
	int *read_slices; size_t *read_slice_len;
	int *write_slices; size_t *write_slice_len;
	int reads_insert_index, write_insert_index;
	int x,y,z;

	if(ptSSL->proxies_len<=iMiddleboxNum)
		return ARRAY_OVERFLOW;
	read_slices=ptSSL->proxies[iMiddleboxNum]->read_slice_ids;
	read_slice_len=&(ptSSL->proxies[iMiddleboxNum]->read_slice_ids_len);
	write_slices=ptSSL->proxies[iMiddleboxNum]->write_slice_ids;
	write_slice_len=&(ptSSL->proxies[iMiddleboxNum]->write_slice_ids_len);

	/*handle empty case specially*/
	if(*read_slice_len==0 && bGrantRead) {
		read_slices[0]=iSliceID;
		(*read_slice_len)++;
	}
	if(*write_slice_len==0 && bGrantWrite) {
		write_slices[0]=iSliceID;
		(*write_slice_len)++;
	}

	/*if we have just inserted the first record, this loop will
	 * terminate on first iteration*/
	x=0;y=*read_slice_len;
	while(y>x+1) {
		z=(x+y)/2;
		if(read_slices[z]>iSliceID) y=z;
		else x=z;
	}
	if(bGrantRead) {
		if(read_slices[x]!=iSliceID) {
			if(*read_slice_len>y) { /*we have higher number slices to move up*/
				memmove(&read_slices[x+1],&read_slices[x],(*read_slice_len-y)*sizeof(*read_slices));
			}
			read_slices[x+1]=iSliceID;
			(*read_slice_len)++;
		} /*else we are re-granted an already granted access - NOP*/
	} else { /*we are removing the permission if it is granted*/
		if(read_slices[x]==iSliceID) {
			if(*read_slice_len>y) { /*we have higher number slices to move up*/
				memmove(&read_slices[x],&read_slices[x+1],(*read_slice_len-y)*sizeof(*read_slices));
			}
			(*read_slice_len)--;
		} /*else we removing a non-existent permission - NOP*/
	}


	x=0;y=*write_slice_len;
	while(y>x+1) {
		z=(x+y)/2;
		if(write_slices[z]>iSliceID) y=z;
		else x=z;
	}
	if(bGrantWrite) {
		if(write_slices[x]!=iSliceID) {
			if(*write_slice_len>y) { /*we have higher number slices to move up*/
				memmove(&write_slices[x+1],&write_slices[x],(*write_slice_len-y)*sizeof(*write_slices));
			}
			write_slices[x+1]=iSliceID;
			(*write_slice_len)++;
		} /*else we are re-granted an already granted access - NOP*/
	} else { /*we are removing the permission if it is granted*/
		if(write_slices[x]==iSliceID) {
			if(*write_slice_len>y) { /*we have higher number slices to move up*/
				memmove(&write_slices[x],&write_slices[x+1],(*write_slice_len-y)*sizeof(*write_slices));
			}
			(*write_slice_len)--;
		} /*else we removing a non-existent permission - NOP*/
	}
	return SUCCESS;
}

ERROR_STATUS
COMMON_TcpConnect(int *piSocket, const char *sHost, int iPort){

	struct hostent *hp;
	struct sockaddr_in addr;
	int iOne=1;

	// Resolve host
	if(!(hp = gethostbyname(sHost)))
		return NETWORK_CONNECT_FAIL;

	memset(&addr, 0, sizeof(addr));
	addr.sin_addr = *(struct in_addr*)
	hp->h_addr_list[0];
	addr.sin_family = AF_INET;
	addr.sin_port = htons(iPort);

	if((*piSocket=socket(AF_INET,SOCK_STREAM, IPPROTO_TCP))<0){
		return NETWORK_CONNECT_FAIL;
	}
	/*I'm copying this option below*/
	//set_nagle(*piSocket, 1);
	setsockopt(*piSocket, IPPROTO_TCP, TCP_NODELAY, (char*)&iOne, sizeof(int));

	if(connect(*piSocket,(struct sockaddr *)&addr, sizeof(addr))<0){
		return NETWORK_CONNECT_FAIL;
	}

	return SUCCESS;
}


void
COMMON_LogErrorAndExit(int iExitCode, FILE *pLog, const char *csFmtStr, ...)
{
	va_list va_args;
	va_start(va_args,csFmtStr);
	common_Log(pLog, csFmtStr,va_args);
	va_end(va_args);
	common_DoExit(iExitCode);

}
void
COMMON_CheckLogErrorAndExit(int iExitCode, FILE *pLog, const char *csFmtStr, ...)
{
	va_list va_args;
	if(iExitCode!=0) {
		va_start(va_args,csFmtStr);
		common_Log(pLog, csFmtStr,va_args);
		va_end(va_args);
		common_DoExit(iExitCode);
	}
}
void
COMMON_Log(FILE *pLog, const char *csFmtStr, ...)
{
	va_list va_args;
	va_start(va_args,csFmtStr);
	common_Log(pLog, csFmtStr,va_args);
	va_end(va_args);

}
void
COMMON_CheckLog(int iExitCode, FILE *pLog, const char *csFmtStr, ...)
{
	va_list va_args;
	if(iExitCode!=0) {
		va_start(va_args,csFmtStr);
		common_Log(pLog, csFmtStr,va_args);
		va_end(va_args);
	}
}

char *gsPassword=NULL;

static int
password_cb(char *buf,int num, int rwflag,void *userdata)
{
	if(gsPassword==NULL)
		return 0;

	if(num<strlen(gsPassword)+1)
		return(0);

	strcpy(buf,gsPassword);
	return(strlen(gsPassword));

}

ERROR_STATUS
COMMON_InitializeSSLCtx(SSL_CTX **pptCtx,
		const char *sMyKeyfile, const char *sMyPassword,
		const char *sCAKeysFile,
		unsigned int iID) /*todo - check the name of this in spec - the byte that identifies the middlebox number, client or server*/
{
	const SSL_METHOD *meth;
	SSL_CTX *ctx;

	/* Global system initialization*/
	SSL_library_init();
	SSL_load_error_strings();


	/* Set up a SIGPIPE handler */
	signal(SIGPIPE,common_SigpipeHandle);

	if(iID==ID_CLIENT || iID==ID_SERVER) {
		meth = SPP_method();
	} else if(iID >=ID_MIDDLEBOX_MIN && iID<=ID_MIDDLEBOX_MAX) {
		meth = SPP_proxy_method();
	} else {
		return INTEGER_OUT_OF_RANGE;
	}

	ctx = SSL_CTX_new(meth);

	/* Specify the cipher suites that may be used. */
	if (!SSL_CTX_set_cipher_list(ctx, "DHE-RSA-AES128-SHA256")) {
		return ERROR_IN_LIBRARY_CALL;
	}


	/* Load the CAs*/
	if(!(SSL_CTX_load_verify_locations(ctx,
			sCAKeysFile,NULL)))
		return FILE_NOT_READABLE;

	/* Load our certificate*/
	if(!(SSL_CTX_use_certificate_chain_file(ctx,
			sMyKeyfile)))
		return FILE_NOT_READABLE;

	/*set the routine to handle passing in the passowrd for our private key*/
	if(gsPassword!=NULL) {
		free(gsPassword); gsPassword=NULL;
	}
	if(sMyPassword!=NULL){
		gsPassword=strdup(sMyPassword);
	}

	SSL_CTX_set_default_passwd_cb(ctx,
			password_cb);

	/*load our private key*/
	if(!(SSL_CTX_use_PrivateKey_file(ctx,
			sMyKeyfile,SSL_FILETYPE_PEM)))
		return FILE_NOT_READABLE;

	*pptCtx=ctx;
	return SUCCESS;
}

void
COMMON_DestroyCtx(SSL_CTX *ptCtx)
{
	if(gsPassword!=NULL) {
		free(gsPassword); gsPassword=NULL;
		SSL_CTX_free(ptCtx);
	}
}



static void
common_Log(FILE *pLog, const char *csFmtStr, va_list va_args)
{
	assert(pLog!=NULL);
	assert(csFmtStr!=NULL);
	assert(strnlen(csFmtStr,MAXSTRLEN)<MAXSTRLEN);
	vfprintf(pLog,csFmtStr,va_args);
	fflush(pLog);
}

static void
common_DoExit(int iExitCode)
{
	exit(iExitCode);
}

static void
common_SigpipeHandle(int x)
{
	UNUSED(x);
}

char* COMMON_MakeNullTerminatedCopy (const char* buf, const unsigned int length)
{
	char* retBuf = malloc(length + 1);
	memcpy(retBuf, buf, length);
	retBuf[length] = 0;
	return retBuf;
}

long getMicrotime()
{
	struct timeval currentTime;
	gettimeofday(&currentTime, NULL);
	return currentTime.tv_sec * (int)1e6 + currentTime.tv_usec;
}

char* COMMON_WriteJSONFile (cJSON* data, char* source)
{
	char fileNameBuf[128];
	sprintf(fileNameBuf, "%s_%lu.json", source, getMicrotime());

	FILE* f = fopen (fileNameBuf, "w");
	char* jsonString = cJSON_Print(data);
	fwrite(jsonString, 1, strlen(jsonString), f);

	fclose(f);
	free(jsonString);

	return strdup(fileNameBuf);
}

cJSON* COMMON_ReadJSONFile (char* filename)
{
	FILE * f = fopen (filename, "r");
	fseek (f, 0, SEEK_END);
	int length = ftell (f);
	fseek (f, 0, SEEK_SET);
	char* buffer = malloc (length);
	if (buffer)
	{
		fread (buffer, 1, length, f);
	}
	fclose (f);

	cJSON* json = cJSON_Parse(buffer);

	free(buffer);

	return json;
}

int COMMON_Base64Encode(const unsigned char* buffer, size_t length, char** b64text) 
{ 
	BIO *bio, *b64;
	BUF_MEM *bufferPtr;

	b64 = BIO_new(BIO_f_base64());
	bio = BIO_new(BIO_s_mem());
	bio = BIO_push(b64, bio);

	BIO_set_flags(bio, 0); // BIO_FLAGS_BASE64_NO_NL); //Ignore newlines - write everything in one line
	BIO_write(bio, buffer, length);
	BIO_flush(bio);
	BIO_get_mem_ptr(bio, &bufferPtr);
	BIO_set_close(bio, BIO_NOCLOSE);
	BIO_free_all(bio);

	*b64text=(*bufferPtr).data;

	return (0); //success
}

size_t common_calcDecodeLength(const char* b64input) 
{ //Calculates the length of a decoded string
	size_t len = strlen(b64input),
		padding = 0;

	if (b64input[len-1] == '=' && b64input[len-2] == '=') //last two chars are =
		padding = 2;
	else if (b64input[len-1] == '=') //last char is =
		padding = 1;

	return (len*3)/4 - padding;
}


int COMMON_Base64Decode(char* b64message, unsigned char** buffer, size_t* length) 
{
	BIO *bio, *b64;

	int decodeLen = common_calcDecodeLength(b64message);
	*buffer = (unsigned char*)malloc(decodeLen + 1);
	(*buffer)[decodeLen] = '\0';

	bio = BIO_new_mem_buf(b64message, -1);
	b64 = BIO_new(BIO_f_base64());
	bio = BIO_push(b64, bio);

	BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL); //Do not use newlines to flush buffer
	*length = BIO_read(bio, *buffer, strlen(b64message));
	assert(*length == decodeLen); //length should equal decodeLen, else something went horribly wrong
	BIO_free_all(bio);

	return (0); //success
}

char* COMMON_CallExternalProcess(const char* commandString, unsigned int* length)
{
    FILE* p = popen(commandString, "r");
    
    // Ugh. Do this better.
	const unsigned int bufSize = 65536;
    char* responseBuf = malloc(bufSize);
    memset(responseBuf, 0, bufSize);

    char* ptr = responseBuf;
    int ch;
	unsigned int count = 0;

    while( (ch=fgetc(p)) != EOF)
    {
        *ptr++ = ch;
		count++;
    }

    int returnCode = WEXITSTATUS(pclose(p));
	*length = count;
	return responseBuf;
}

#define MAX_CERT_FIELD_LENGTH 1024
#define SHA1LEN 20

void hex_encode(unsigned char* readbuf, void *writebuf, size_t len)
{
  for(size_t i=0; i < len; i++) {
    char *l = (char*) (2*i + ((intptr_t) writebuf));
    sprintf(l, "%02x", readbuf[i]);
  }
}

void COMMON_PrintCertificateDetails (X509* cert)
{
  if (NULL == cert) {
    printf ("\tNo certificate\n");
    return;
  }
  
  char subj[MAX_CERT_FIELD_LENGTH+1];
  char issuer[MAX_CERT_FIELD_LENGTH+1];
  X509_NAME_oneline(X509_get_subject_name(cert), subj, MAX_CERT_FIELD_LENGTH);
  X509_NAME_oneline(X509_get_issuer_name(cert), issuer, MAX_CERT_FIELD_LENGTH);
  printf("\tSubject: %s\n", subj);
  printf("\tIssuer: %s\n", issuer);       
  
  char buf[SHA1LEN];
  
  const EVP_MD *digest = EVP_sha1();
  unsigned len;
  
  int rc = X509_digest(cert, digest, (unsigned char*) buf, &len);
  if (rc == 0 || len != SHA1LEN) {
    printf ("\tFailed to get SHA1 fingerprint\n");
  }

  char strbuf[2*SHA1LEN+1];
  hex_encode(buf, strbuf, SHA1LEN);
  printf ("\tFingerprint: %s\n", strbuf);
}

