// ------------------------------------------------
// File : sys.cpp
// Date: 4-apr-2002
// Author: giles
// Desc: 
//		Sys is a base class for all things systemy, like starting threads, creating sockets etc..
//		Lock is a very basic cross platform CriticalSection class		
//
// (c) 2002 peercast.org
// ------------------------------------------------
// 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 2 of the License, 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.
// ------------------------------------------------

#include "common.h"
#include "sys.h"
#include "socket.h"
#include "gnutella.h"
#include <stdlib.h>
#include <time.h>

// -----------------------------------
const char *LogBuffer::logTypes[]=
{
	"",
	"DBUG",
	"EROR",
	"GNET",
	"CHAN",
};

// -----------------------------------
// base64 encode/decode taken from ices2 source.. 
static char base64table[64] = {
    'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P',
    'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f',
    'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v',
    'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/'
};
#if 0
// -----------------------------------
static char *util_base64_encode(char *data)
{
    int len = strlen(data);
    char *out = malloc(len*4/3 + 4);
    char *result = out;
    int chunk;

    while(len > 0) {
        chunk = (len >3)?3:len;
        *out++ = base64table[(*data & 0xFC)>>2];
        *out++ = base64table[((*data & 0x03)<<4) | ((*(data+1) & 0xF0) >> 4)];
        switch(chunk) {
            case 3:
                *out++ = base64table[((*(data+1) & 0x0F)<<2) | ((*(data+2) & 0xC0)>>6)];
                *out++ = base64table[(*(data+2)) & 0x3F];
                break;
            case 2:
                *out++ = base64table[((*(data+1) & 0x0F)<<2)];
                *out++ = '=';
                break;
            case 1:
                *out++ = '=';
                *out++ = '=';
                break;
        }
        data += chunk;
        len -= chunk;
    }

    return result;
}
#endif

// -----------------------------------
static int base64chartoval(char input)
{
    if(input >= 'A' && input <= 'Z')
        return input - 'A';
    else if(input >= 'a' && input <= 'z')
        return input - 'a' + 26;
    else if(input >= '0' && input <= '9')
        return input - '0' + 52;
    else if(input == '+')
        return 62;
    else if(input == '/')
        return 63;
    else if(input == '=')
        return -1;
    else
        return -2;
}

// -----------------------------------
static char *util_base64_decode(char *input)
{
	return NULL;
}





// ------------------------------------------
Sys::Sys()
{
	idleSleepTime = 10;
	logBuf = new LogBuffer(1000,100);
}

// ------------------------------------------
void Sys::sleepIdle()
{
	sleep(idleSleepTime);
}

// ------------------------------------------
void Host::toResolvedName(char *str)
{
	char hostName[128];

	if (ClientSocket::getHostname(hostName,ip))
		sprintf(str,"%s:%d",hostName,port);
	else
		toStr(str);
}

// ------------------------------------------
bool Host::isLocalhost()
{
	return loopbackIP(); 
}
// ------------------------------------------
void Host::fromStrName(const char *str, int p)
{
	if (!strlen(str))
	{
		port = 0;
		ip = 0;
		return;
	}

	char name[128];
	strcpy(name,str);
	port = p;
	char *pp = strstr(name,":");
	if (pp)
	{
		port = atoi(pp+1);
		pp[0] = 0;
	}

	ip = ClientSocket::getIP(name);
}
// ------------------------------------------
void Host::fromStrIP(const char *str, int p)
{
	unsigned int ipb[4];
	unsigned int ipp;


	if (strstr(str,":"))
	{
		if (sscanf(str,"%03d.%03d.%03d.%03d:%d",&ipb[0],&ipb[1],&ipb[2],&ipb[3],&ipp) == 5)
		{
			ip = ((ipb[0]&0xff) << 24) | ((ipb[1]&0xff) << 16) | ((ipb[2]&0xff) << 8) | ((ipb[3]&0xff));
			port = ipp;
		}else
		{
			ip = 0;
			port = 0;
		}
	}else{
		port = p;
		if (sscanf(str,"%03d.%03d.%03d.%03d",&ipb[0],&ipb[1],&ipb[2],&ipb[3]) == 4)
			ip = ((ipb[0]&0xff) << 24) | ((ipb[1]&0xff) << 16) | ((ipb[2]&0xff) << 8) | ((ipb[3]&0xff));
		else
			ip = 0;
	}
}
// -----------------------------------
bool Host::isMemberOf(Host &h)
{
	if (h.ip==0)
		return false;

    if( h.ip0() != 255 && ip0() != h.ip0() )
        return false;
    if( h.ip1() != 255 && ip1() != h.ip1() )
        return false;
    if( h.ip2() != 255 && ip2() != h.ip2() )
        return false;
    if( h.ip3() != 255 && ip3() != h.ip3() )
        return false;

/* removed for endieness compatibility
	for(int i=0; i<4; i++)
		if (h.ipByte[i] != 255)
			if (ipByte[i] != h.ipByte[i])
				return false;
*/
	return true;
}

// -----------------------------------
char *trimstr(char *s1)
{
	while (*s1)
	{
		if ((*s1 == ' ') || (*s1 == '\t'))
			s1++;
		else
			break;

	}

	char *s = s1;

	s1 = s1+strlen(s1);

	while (*--s1)
		if ((*s1 != ' ') && (*s1 != '\t'))
			break;

	s1[1] = 0;

	return s;
}

// -----------------------------------
char *stristr(const char *s1, const char *s2)
{
	while (*s1)
	{
		if (TOUPPER(*s1) == TOUPPER(*s2))
		{
			const char *c1 = s1;
			const char *c2 = s2;

			while (*c1 && *c2)
			{
				if (TOUPPER(*c1) != TOUPPER(*c2))
					break;
				c1++;
				c2++;
			}
			if (*c2==0)
				return (char *)s1;
		}

		s1++;
	}
	return NULL;
}
// -----------------------------------
bool String::isValidURL()
{
	return (strnicmp(data,"http://",7)==0) || (strnicmp(data,"mailto:",7)==0);
}

// -----------------------------------
void String::setFromTime(unsigned int t)
{
	char *p = ctime((time_t*)&t);
	if (p)
		strcpy(data,p);
	else
		strcpy(data,"-");
	type = T_ASCII;
}
// -----------------------------------
void String::setFromStopwatch(unsigned int t)
{
	unsigned int sec,min,hour,day;

	sec = t%60;
	min = (t/60)%60;
	hour = (t/3600)%24;
	day = (t/86400);

	if (day)
		sprintf(data,"%d day, %d hour",day,hour);
	else if (hour)
		sprintf(data,"%d hour, %d min",hour,min);
	else if (min)
		sprintf(data,"%d min, %d sec",min,sec);
	else if (sec)
		sprintf(data,"%d sec",sec);
	else
		sprintf(data,"-");

	type = T_ASCII;
}
// -----------------------------------
void String::setFromString(const char *str, TYPE t)
{
	int cnt=0;
	bool quote=false;
	while (*str)
	{
		bool add=true;
		if (*str == '\"')
		{
			if (quote) 
				break;
			else 
				quote = true;
			add = false;
		}else if (*str == ' ')
		{
			if (!quote)
			{
				if (cnt)
					break;
				else
					add = false;
			}
		}

		if (add)
		{
			data[cnt++] = *str++;
			if (cnt >= (MAX_LEN-1))
				break;
		}else
			str++;
	}
	data[cnt] = 0;
	type = t;
}

// -----------------------------------
int String::base64WordToChars(char *out,const char *input)
{
	char *start = out;
	signed char vals[4];

    vals[0] = base64chartoval(*input++);
    vals[1] = base64chartoval(*input++);
    vals[2] = base64chartoval(*input++);
    vals[3] = base64chartoval(*input++);

    if(vals[0] < 0 || vals[1] < 0 || vals[2] < -1 || vals[3] < -1) 
		return 0;

    *out++ = vals[0]<<2 | vals[1]>>4;
    if(vals[2] >= 0)
        *out++ = ((vals[1]&0x0F)<<4) | (vals[2]>>2);
    else
        *out++ = 0;

    if(vals[3] >= 0)
        *out++ = ((vals[2]&0x03)<<6) | (vals[3]);
    else
        *out++ = 0;

	return out-start;
}

// -----------------------------------
void String::BASE642ASCII(const char *input)
{
	char *out = data;
    int len = strlen(input);

    while(len >= 4) 
	{
		out += base64WordToChars(out,input);
		input += 4;
        len -= 4;
    }
    *out = 0;
}

// -----------------------------------
//---------------------
// Japanese Patch
#define isSJIS(a) ((unsigned char)a >= 0x81 && (unsigned char)a <= 0x9f || (unsigned char)a >= 0xe0 && (unsigned char)a<=0xfc)
#define isSJIS2(a) ((unsigned char)a >= 0x40 && (unsigned char)a <= 0x7e || (unsigned char)a >= 0x80 && (unsigned char)a<=0xfc)
void String::ASCII2HTML(const char *in)
{
	char *op = data;
	char *oe = data+MAX_LEN-10;
	unsigned char c;
	const char *p = in;
	while (c = *p++)
	{

		if(isSJIS(c) && isSJIS2(*p))
		{
			unsigned char d = *p++;
			if(d)
			{
				*op++ = c;
				*op++ = d;
			}
			else
			{
				sprintf(op,"&#x%02X;",(int)c);
				op+=6;
				break;
			}
		}
		else if (((c >= '0') && (c <= '9')) || ((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z')))
		{
			*op++ = c;
		}
		else
		{
			sprintf(op,"&#x%02X;",(int)c);
			op+=6;
		}


		if (op >= oe)
			break;
	}
	*op = 0;
}
/*
void String::ASCII2HTML(const char *in)
{
	char *op = data;
	char *oe = data+MAX_LEN-10;
	unsigned char c;
	const char *p = in;
	while (c = *p++)
	{
		
		if (((c >= '0') && (c <= '9')) || ((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z')))
		{
			*op++ = c;
		}else
		{
			sprintf(op,"&#x%02X;",(int)c);
			op+=6;
		}
		if (op >= oe)
			break;
	}
	*op = 0;
}
*/
//---------------------
// -----------------------------------
void String::ASCII2ESC(const char *in, bool safe)
{
	char *op = data;
	char *oe = data+MAX_LEN-10;
	const char *p = in;
	unsigned char c;
	while (c = *p++)
	{
		if (((c >= '0') && (c <= '9')) || ((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z')))
			*op++ = c;
		else
		{
			*op++ = '%';
			if (safe)
				*op++ = '%';
			*op=0;
			sprintf(op,"%02X",(int)c);
			op+=2;
		}
		if (op >= oe)
			break;
	}
	*op=0;
}
// -----------------------------------
void String::HTML2ASCII(const char *in)
{
	unsigned char c;
	char *o = data;
	char *oe = data+MAX_LEN-10;
	const char *p = in;
	while (c = *p++)
	{
		if ((c == '&') && (p[0] == '#'))
		{
			p++;
			char code[8];
			char *cp = code;
			char ec = *p++;		// hex/dec
			while (c=*p++) 
			{
				if (c!=';')
					*cp++ = c;
				else
					break;
			}
			*cp = 0;
			c = (unsigned char)strtoul(code,NULL,ec=='x'?16:10);
		}
		*o++ = c;
		if (o >= oe)
			break;
	}

	*o=0;
}

// -----------------------------------
void String::ESC2ASCII(const char *in)
{
	unsigned char c;
	char *o = data;
	char *oe = data+MAX_LEN-10;
	const char *p = in;
	while (c = *p++)
	{
		if (c == '+')
			c = ' ';
		else if (c == '%')
		{
			if (p[0] == '%')
				p++;

			char hi = TOUPPER(p[0]);
			char lo = TOUPPER(p[1]);
			c = (TONIBBLE(hi)<<4) | TONIBBLE(lo);
			p+=2;
		}
		*o++ = c;
		if (o >= oe)
			break;
	}

	*o=0;
}
// -----------------------------------
void String::ASCII2META(const char *in, bool safe)
{
	char *op = data;
	char *oe = data+MAX_LEN-10;
	const char *p = in;
	unsigned char c;
	while (c = *p++)
	{
		switch (c)
		{
			case '%':
				if (safe)
					*op++='%';
				break;
			case ';':
				c = ':';
				break;
		}

		*op++=c;
		if (op >= oe)
			break;
	}
	*op=0;
}
// -----------------------------------
void String::convertTo(TYPE t)
{
	if (t != type)
	{
		String tmp = *this;

		// convert to ASCII
		switch (type)
		{
			case T_NONE:
			case T_ASCII:
				break;
			case T_HTML:
				tmp.HTML2ASCII(data);
				break;
			case T_ESC:
			case T_ESCSAFE:
				tmp.ESC2ASCII(data);
				break;
			case T_META:
			case T_METASAFE:
				//tmp.META2ASCII(tmp.data,data);
				break;
			case T_BASE64:
				tmp.BASE642ASCII(data);
				break;
		}

		// convert to new format
		switch (t)
		{
			case T_NONE:
			case T_ASCII:
				strcpy(data,tmp.data);
				break;
			case T_HTML:
				ASCII2HTML(tmp.data);
				break;
			case T_ESC:
				ASCII2ESC(tmp.data,false);
				break;
			case T_ESCSAFE:
				ASCII2ESC(tmp.data,true);
				break;
			case T_META:
				ASCII2META(tmp.data,false);
				break;
			case T_METASAFE:
				ASCII2META(tmp.data,true);
				break;
		}

		type = t;
	}
}
// -----------------------------------
void LogBuffer::write(const char *str, TYPE t)
{
	lock.on();

	unsigned int len = strlen(str);
	int cnt=0;
	while (len)
	{
		unsigned int rlen = len;
		if (rlen > (lineLen-1))
			rlen = lineLen-1;

		int i = currLine % maxLines;
		int bp = i*lineLen;
		strncpy(&buf[bp],str,rlen);
		buf[bp+rlen] = 0;
		if (cnt==0)
		{
			times[i] = sys->getTime();
			types[i] = t;
		}else
		{
			times[i] = 0;
			types[i] = T_NONE;
		}
		currLine++;

		str += rlen;
		len -= rlen;
		cnt++;
	}

	lock.off();
}

// -----------------------------------
char *getCGIarg(char *str, char *arg)
{
	if (!str)
		return NULL;

	char *s = strstr(str,arg);

	if (!s)
		return NULL;

	s += strlen(arg);

	return s;
}

// -----------------------------------
bool cmpCGIarg(char *str, char *arg, char *value)
{
	if ((!str) || (!strlen(value)))
		return false;

	char *s = strstr(str,arg);

	if (!s)
		return false;

	s += strlen(arg);

	return strncmp(s,value,strlen(value))==0;
}
// -----------------------------------
bool hasCGIarg(char *str, char *arg)
{
	if (!str)
		return false;

	char *s = strstr(str,arg);

	if (!s)
		return false;

	return true;
}


// ---------------------------
void GnuID::encode(Host *h, const char *salt1, const char *salt2, unsigned char salt3)
{
	int s1=0,s2=0;
	for(int i=0; i<16; i++)
	{
		unsigned char ipb = id[i];

		// encode with IP address 
		if (h)
			ipb ^= ((unsigned char *)&h->ip)[i&3];

		// add a bit of salt 
		if (salt1)
		{
			if (salt1[s1])
				ipb ^= salt1[s1++];
			else
				s1=0;
		}

		// and some more
		if (salt2)
		{
			if (salt2[s2])
				ipb ^= salt2[s2++];
			else
				s2=0;
		}

		// plus some pepper
		ipb ^= salt3;

		id[i] = ipb;
	}

}
// ---------------------------
void GnuID::toStr(char *str)
{

	str[0] = 0;
	for(int i=0; i<16; i++)
	{
		char tmp[8];
		unsigned char ipb = id[i];

		sprintf(tmp,"%02X",ipb);
		strcat(str,tmp);
	}

}
// ---------------------------
void GnuID::fromStr(const char *str)
{
	clear();

	if (strlen(str) < 32)
		return;

	char buf[8];

	buf[2] = 0;

	for(int i=0; i<16; i++)
	{
		buf[0] = str[i*2];
		buf[1] = str[i*2+1];
		id[i] = (unsigned char)strtoul(buf,NULL,16);
	}

}

// ---------------------------
void GnuID::generate()
{
	clear();

	for(int i=0; i<16; i++)
		id[i] = sys->rnd();

	// its a nasty hack, but it just might work jim.
	// insert a 6 digit code in the bottom of the ID string
	// so that we can tell what versions are out there.
	// also used to detect version changes/fixes in packets.

	MemoryStream mem(id,16);
	mem.skip(12);
	mem.writeLong(PEERCAST_PACKETID);
}

// ---------------------------
int GnuID::getVersion()
{
	MemoryStream mem(id,16);
	mem.skip(12);
	int pid = mem.readLong();

	int ver = pid&0xfffff;	
	return ver;
}

// ---------------------------
void GnuIDList::init(int max)
{
	ids = new GnuID[max];
	maxID = max;
	for(int i=0; i<maxID; i++)
		ids[i].clear();
}
// ---------------------------
bool GnuIDList::contains(GnuID &id)
{
	for(int i=0; i<maxID; i++)
		if (ids[i].isSame(id))
			return true;
	return false;
}
// ---------------------------
int GnuIDList::numUsed()
{
	int cnt=0;
	for(int i=0; i<maxID; i++)
		if (ids[i].storeTime)
			cnt++;
	return cnt;
}
// ---------------------------
unsigned int GnuIDList::getOldest()
{
	unsigned int t=(unsigned int)-1;
	for(int i=0; i<maxID; i++)
		if (ids[i].storeTime)
			if (ids[i].storeTime < t)
				t = ids[i].storeTime;
	return t;
}
// ---------------------------
void GnuIDList::addGnuID(GnuID &id)
{
	unsigned int minTime = (unsigned int) -1;
	int minIndex = 0;

	// find oldest
	for(int i=0; i<maxID; i++)
		if (ids[i].storeTime <= minTime)
		{
			minTime = ids[i].storeTime;
			minIndex = i;
		}

	ids[minIndex] = id;
	ids[minIndex].storeTime = sys->getTime();
	return;
}
// ---------------------------
void GnuIDList::clearAll()
{
	for(int i=0; i<maxID; i++)
		ids[i].clear();
}
	
