/*
 *	tsock.c
 *	rotinas de comunicacoes usando sockets
 *
 *  97.03.07
 *
 */

/*
============================== C O P Y R I G H T ==============================

(C) 1995-97 Paulo Sousa

This software was written by Paulo Sousa, Researcher of Instituto Superior de 
Engenharia do Porto (ISEP/IPP), Portugal. Use of this software, for any 
purpose, is granted subject to the following conditions: 

1.	This copyright notice must be included with the software or any software
	derived from it;

2.	The risk of using this software is accepted by the user. No warranty as to
	its usefulness or functionality is provided or implied. ISEP provides no 
	support;

3.	You may redistribute this and other files or routines as long as you are
	not compensated for it. You may not use the released source codes in anyway
	commercial without permission.

It'd be nice if you share your modifications with me and everyone else.
email:	psousa@dei.isep.ipp.pt
WWW:	http://www.dei.isep.ipp.pt/~psousa

===============================================================================
*/

/*
 * antes de compilar copiar macros apropriadas
 *
#define TP_OS_WINDOWS
#define TP_OSVER_WIN32
#define TP_OS_OS400
#define TP_HWD_ALPHA
 *
 */

/* compilacao para Win95/NT em maquina Intel */
#define TP_OS_WINDOWS
#define TP_OSVER_WIN32


#include "tc_sock.h"



#if defined(__GNUC__) || defined(__GNUG__)
#define STATIC_INLINE		static __inline__
#elif defined(_MSC_VER)
#define STATIC_INLINE	static inline
#else
#define STATIC_INLINE	static 
#endif

/************************
 *						*
 * Variaveis globais	*
 *						*
 ************************/

#define TS_CHAR_1	'*'

/************************************
 *									*
 * Funcoes privadas deste modulo	*
 *									*
 ************************************/

/*****************************************************************************
 FUNCAO:
 RETORNO:
 DATA:		97.03.12
 AUTOR:		Paulo Sousa
 REVISOES:
 -----------+-------------------+---------------------------------------------
	DATA	|	   QUEM			|	   O QUE
 -----------+-------------------+---------------------------------------------
 *****************************************************************************/
STATIC_INLINE
int _my_pow10(int exp		/*(I)*/
			  )
{
register int i;
register int res = 10;

	if (exp == 0)
	{
		return 0;
	}

	for (i = 1; i < exp; i++)
	{
		res *= 10;
	}

	return res;
}

/*****************************************************************************
 FUNCAO:
 RETORNO:
 DATA:		97.03.12
 AUTOR:		Paulo Sousa
 REVISOES:
 -----------+-------------------+---------------------------------------------
	DATA	|	   QUEM			|	   O QUE
 -----------+-------------------+---------------------------------------------
 *****************************************************************************/
STATIC_INLINE
short _port_num(LPCSTR lpcszValue		/*(I)*/
				)
{
register int i;
short iVal = 0;
int len = strlen(lpcszValue);

	for (i = 0; i < len; i++)
	{
		if (!isdigit(lpcszValue[i]))
		{
			return -1;
		}

		iVal += _my_pow10(len - i + 1)*(lpcszValue[i]-'0');
	}

	return htons(iVal);
}


/****************************************
 *										*
 * Funcoes exportadas por este modulo	*
 *										*
 ****************************************/

/*****************************************************************************
 FUNCAO:	modificar parametro TTL (Time To Live) do IP
 RETORNO:	0 sucesso ou SOCKET_ERROR
 DATA:		97.04.09
 AUTOR:		Paulo Sousa
 REVISOES:
 -----------+-------------------+---------------------------------------------
	DATA	|	   QUEM			|	   O QUE
 -----------+-------------------+---------------------------------------------
 *****************************************************************************/
int APIENTRY sock_set_ttl(SOCKET s,			/*(IO)*/
						  int nTimeToLive	/*(I)*/
						  ) 
{
int rc;

	rc = setsockopt(s, IPPROTO_IP, IP_TTL, (LPSTR)&nTimeToLive, sizeof(int));

	return rc;
}

/*
Regarding timeouts on sockets:

Yes, this is possible with 32-bit Winsock.  Suppose you wish to
timeout if nothing is received on a blocking socket after thirty
seconds:
*/
/*****************************************************************************
 FUNCAO:	modificar o tempo de timeout para operacoes de recv() e send()
 RETORNO:	0 sucesso ou SOCKET_ERROR
 EXEMPLO:
			...	   // assume we've connected, etc.
			sock_set_timeout(s, 30000, SO_RECVTIMO);
			int nResult = recv(s,pRecvBuf,...   // recv will wait 30ms for data to start
			if(nResult == SOCKET_ERROR) {
				if(WSAGetLastError() == WSAECONNABORTED) {
					// timeout happened
				}
				else {
					// some other error
				}
			}
 DATA:		97.04.09
 AUTOR:		Paulo Sousa
 REVISOES:
 -----------+-------------------+---------------------------------------------
	DATA	|	   QUEM			|	   O QUE
 -----------+-------------------+---------------------------------------------
 *****************************************************************************/
int APIENTRY sock_set_timeout(SOCKET s,			/*(IO)*/
							  int nTimeOut,		/*(I) timout in milisec */
							  int opt			/*(I) SO_RCVTIMEO ou SO_SNDTIMEO */
							  )
{
int rc;

	switch (opt)
	{
	case SO_RCVTIMEO:
		rc = setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (LPSTR)&nTimeOut, sizeof(int));
		break;
	case SO_SNDTIMEO:
		rc = setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, (LPSTR)&nTimeOut, sizeof(int));
		break;
	default:
		rc = SOCKET_ERROR;
	}

	return rc;
}

/*****************************************************************************
 FUNCAO:	
 RETORNO:	0 sucesso ou SOCKET_ERROR
 DATA:		97.08.07
 AUTOR:		Paulo Sousa
 REVISOES:
 -----------+-------------------+---------------------------------------------
	DATA	|	   QUEM			|	   O QUE
 -----------+-------------------+---------------------------------------------
 *****************************************************************************/
int APIENTRY sock_set_broadcast(SOCKET s		/*(IO)*/
								) 
{
int rc;
BOOL b = TRUE;

	rc = setsockopt(s, SOL_SOCKET, SO_BROADCAST, (LPSTR)&b, sizeof(BOOL));

	return rc;
}

/*****************************************************************************
 FUNCAO:
 RETORNO:
 DATA:		97.03.12
 AUTOR:		** retirada de WinSockX **
 REVISOES:
 -----------+-------------------+---------------------------------------------
	DATA	|	   QUEM			|	   O QUE
 -----------+-------------------+---------------------------------------------
 *****************************************************************************/
short APIENTRY sock_close(SOCKET sock	/*(I)*/
						  )
{
int nRet=0;
BYTE buffer[500];
 
	if (sock != INVALID_SOCKET) 
	{
#if 0
		/* disable asynchronous notification if window handle provided */
		if (hWnd != NULL) 
		{
			nRet = WSAAsyncSelect(hSock, hWnd, 0, 0);
			if (nRet == SOCKET_ERROR)
			{
				WSAperror (WSAGetLastError(), "CloseConn() WSAAsyncSelect()", 0);
			}
		}
#endif

		/* Half-close the connection to close neatly (NOTE: we ignore the
		 * return from this function because some BSD-based WinSock 
		 * implementations fail shutdown() with WSAEINVAL if a TCP reset 
		 * has been recieved.  In any case, if it fails it means the 
		 * connection is already closed anyway, so it doesn't matter.) 
		 */
		nRet = shutdown(sock, 1);

		/* Read remaining data (until EOF or error) */
		while (recv(sock, buffer, sizeof(buffer), 0) > 0)
		{
		}

		/* close the socket, and ignore any error (since we can't do much about them anyway) */
		closesocket(sock);
	}

	return nRet;
}

/*****************************************************************************
 FUNCAO:
 RETORNO:	devolve o numero de porta do servico ja' em "network byte order",
			ou -1 em caso de erro
 DATA:		97.01.08
 AUTOR:		Paulo Sousa
 REVISOES:
 -----------+-------------------+---------------------------------------------
	DATA	|	   QUEM			|	   O QUE
 -----------+-------------------+---------------------------------------------
 *****************************************************************************/
short APIENTRY sock_get_service(LPCSTR lpcszServico,		/*(I) nome do servico ou numero de porta ou SERVICE_ANY */
								LPCSTR lpcszProtocolo		/*(I)*/
								)
{
short iPort = -1;

	/* any */
	if (lpcszServico[0] == TS_CHAR_1 && strcmp(lpcszServico, SERVICE_ANY) == 0)
	{
		return htons(0);
	}

	/* numero de porta */
	if (isdigit(lpcszServico[0]))
	{
		iPort = _port_num(lpcszServico);
	}

	/* nome de servico */
	if (iPort == -1)
	{
	LPSERVENT srv_info;

#ifdef TP_OS_OS400
		srv_info = getservbyname( (LPSTR)lpcszServico, (LPSTR)lpcszProtocolo);
#else
		srv_info = getservbyname(lpcszServico, lpcszProtocolo);
#endif
		if (srv_info == NULL)
			return -1;

		iPort = srv_info->s_port;
	}

	return iPort;
}

/*****************************************************************************
 FUNCAO:	Get the Local IP address using the following algorithm:
			- get local hostname with gethostname()
			- attempt to resolve local hostname with gethostbyname()
			if that fails:
			- get a UDP socket
			- connect UDP socket to arbitrary address and port
			- use getsockname() to get local address
 RETORNO:
 DATA:		97.03.12
 AUTOR:		** retirada de WinSockX **
 REVISOES:
 -----------+-------------------+---------------------------------------------
	DATA	|	   QUEM			|	   O QUE
 -----------+-------------------+---------------------------------------------
 *****************************************************************************/
u_long APIENTRY sock_get_local_ip(void) 
{
char szLclHost[MAXHOSTNAME];
LPHOSTENT lpstHostent;
struct sockaddr_in stLclAddr;
struct sockaddr_in stRmtAddr;
int nAddrSize = sizeof(SOCKADDR);
SOCKET hSock;
int nRet;
    
    /* Init local address (to zero) */
    stLclAddr.sin_addr.s_addr = INADDR_ANY;
    
    /* Get the local hostname */
    nRet = gethostname(szLclHost, MAXHOSTNAME); 
    if (nRet != SOCKET_ERROR) 
	{
		/* Resolve hostname for local address */
		lpstHostent = gethostbyname((LPSTR)szLclHost);
		if (lpstHostent != NULL)
		{
			stLclAddr.sin_addr.s_addr = *((u_long FAR*) (lpstHostent->h_addr));
		}
    } 
    
    /* If still not resolved, then try second strategy */
    if (stLclAddr.sin_addr.s_addr == INADDR_ANY) 
	{
		/* Get a UDP socket */
		hSock = socket(AF_INET, SOCK_DGRAM, 0);
		if (hSock != INVALID_SOCKET)  
		{
			/* Connect to arbitrary port and address (NOT loopback) */
			stRmtAddr.sin_family = AF_INET;
			stRmtAddr.sin_port   = htons(IPPORT_ECHO);
			stRmtAddr.sin_addr.s_addr = inet_addr("128.127.50.1");
			nRet = connect(hSock, (LPSOCKADDR)&stRmtAddr, sizeof(struct sockaddr_in));
			if (nRet != SOCKET_ERROR) 
			{
				/* Get local address */
				getsockname(hSock, (LPSOCKADDR)&stLclAddr, (int FAR*)&nAddrSize);
			}
			closesocket(hSock);   /* we're done with the socket */
		}
    }

    return (stLclAddr.sin_addr.s_addr);
}

/*****************************************************************************
 FUNCAO:
 RETORNO:
 DATA:		97.03.12
 AUTOR:		Paulo Sousa
 REVISOES:
 -----------+-------------------+---------------------------------------------
	DATA	|	   QUEM			|	   O QUE
 -----------+-------------------+---------------------------------------------
 *****************************************************************************/
u_long APIENTRY sock_get_host_ip(LPCSTR lpcszHost		/*(I) nome da maquina ou HOST_BROADCAST ou HOST_LOCAL */
								 )
{
u_long lHost = INADDR_NONE;

	assert(lpcszHost != NULL);

	/* enderecos especiais */
	if (lpcszHost[0] == TS_CHAR_1)
	{
		/* local */
		if (strcmp(lpcszHost, HOST_LOCAL) == 0)
		{
			return htonl(INADDR_ANY);
		}

		/* broadcast */
		if (strcmp(lpcszHost, HOST_BROADCAST) == 0)
		{
			return INADDR_BROADCAST;
		}
	}

	/* endereco IP numerico do tipo a.b.c.d */
	if (isdigit(lpcszHost[0]))
	{
#ifdef TP_OS_OS400
		lHost = inet_addr( (LPSTR)lpcszHost );
#else
		lHost = inet_addr(lpcszHost);
#endif
		/*
		** nao faz return, porque um nome DNS pode comecar por um digito
		** ex: 156abc.xpto.com
		*/
	}

	/* nome de maquina */
	if (lHost == INADDR_NONE)
	{
	LPHOSTENT host_info;

#ifdef TP_OS_OS400
		host_info = gethostbyname( (LPSTR)lpcszHost);
#else
		host_info = gethostbyname(lpcszHost);
#endif
		if (host_info == NULL)
			return INADDR_NONE;

		/* lHost = *((u_long FAR *) (lpstHost->h_addr)); */
		lHost = (*((struct in_addr FAR*)(host_info->h_addr))).s_addr;
	}

	return lHost;
}

/*****************************************************************************
 FUNCAO:	criar endereco IP
 RETORNO:
 DATA:		97.01.08
 AUTOR:		Paulo Sousa
 REVISOES:
 -----------+-------------------+---------------------------------------------
	DATA	|	   QUEM			|	   O QUE
 -----------+-------------------+---------------------------------------------
 *****************************************************************************/
BOOL APIENTRY sock_mkaddr(struct sockaddr_in FAR* lpsrv_addr,	/*(O)*/
						  LPCSTR lpcszHost,						/*(I) nome da maquina destino ou HOST_BROADCAST ou HOST_LOCAL */
						  LPCSTR lpcszServico,					/*(I) numero da porta ou nome do servico ou SERVICE_ANY */
						  LPCSTR lpcszProtocolo					/*(I)*/
						  )
{
short iPort;
unsigned long lHost;

	assert(lpcszHost != NULL && lpcszServico != NULL);

	/* endereco IP da maquina */
	lHost = sock_get_host_ip(lpcszHost);
	if (lHost == INADDR_NONE)
	{
		return FALSE;
	}
	
	/* servico */
	iPort = sock_get_service(lpcszServico, lpcszProtocolo);
	if (iPort == -1)
	{
		return FALSE;
	}

	/* construir endereco */
	memset(lpsrv_addr, 0, sizeof(struct sockaddr_in));
	lpsrv_addr->sin_family = AF_INET;
	lpsrv_addr->sin_addr.s_addr = lHost;
	lpsrv_addr->sin_port = iPort;

	return TRUE;
}

/*****************************************************************************
 FUNCAO:
 RETORNO:
 DATA:		97.03.07
 AUTOR:		Paulo Sousa
 REVISOES:
 -----------+-------------------+---------------------------------------------
	DATA	|	   QUEM			|	   O QUE
 -----------+-------------------+---------------------------------------------
 *****************************************************************************/
SOCKET APIENTRY sock_create(LPCSTR lpcszService,	/*(I) numero de porta ou nome do servico */
							LPCSTR lpcszProtocolo,	/*(I) protolo do servico */
							int tipo,				/*(I) tipo de socket: SOCK_DGRAM, SOCK_STREAM, ... */
							int proto				/*(I) protocolo: IPPROTO_TCP, ... */
							)
{
struct sockaddr_in sock_addr;
int rc;
SOCKET sock;

	/* endereco local */
	if (sock_mkaddr(&sock_addr, HOST_LOCAL, lpcszService, lpcszProtocolo) == FALSE)
		return INVALID_SOCKET;

	/* criar socket */
	sock = socket(AF_INET, tipo, proto);
	if (sock == INVALID_SOCKET)
		return INVALID_SOCKET;


	/* ligar o socket a' porta local */
	rc = bind(sock, (LPSOCKADDR)&sock_addr, sizeof(struct sockaddr_in)); 
	if (rc == SOCKET_ERROR)
	{
		sock_close(sock);
		return INVALID_SOCKET;
	}

	return sock;
}

/*****************************************************************************
 FUNCAO:
 RETORNO:
 DATA:		96.05.10
 AUTOR:		Paulo Sousa
 REVISOES:
 -----------+-------------------+---------------------------------------------
	DATA	|	   QUEM			|	   O QUE
 -----------+-------------------+---------------------------------------------
 *****************************************************************************/
SOCKET APIENTRY sock_listen_accept(SOCKET srv_sock,				/*(I)*/
								   struct sockaddr FAR* addr, 	/*(O)*/
								   int FAR* addrlen				/*(O)*/
								   )
{
SOCKET client_sock;

	/* listen */
	if (listen(srv_sock, 5) == SOCKET_ERROR)
		return INVALID_SOCKET;

	/* accept */
	client_sock = accept(srv_sock, addr, addrlen);
	if (client_sock == INVALID_SOCKET)
		return INVALID_SOCKET;

	return client_sock;
}

/*****************************************************************************
 FUNCAO:
 RETORNO:
 DATA:		96.06.13
 AUTOR:		Paulo Sousa
 REVISOES:
 -----------+-------------------+---------------------------------------------
	DATA	|	   QUEM			|	   O QUE
 -----------+-------------------+---------------------------------------------
	97.01.08	Paulo Sousa			modifiquei para usar sock_mkraddr()
 *****************************************************************************/
BOOL APIENTRY sock_connect(SOCKET sock,				/*(I)*/
						   LPCSTR lpcszHost,		/*(I) nome da maquina ou HOST_BROADCAST */
						   LPCSTR lpcszServico,		/*(I)*/
						   LPCSTR lpcszProtocolo	/*(I)*/
						   )
{
struct sockaddr_in srv_addr;

	/* servico & endereco remotos */
	if (sock_mkaddr(&srv_addr, lpcszHost, lpcszServico, lpcszProtocolo) == FALSE)
	{
		return FALSE;
	}

	/* broadcast */
	if (srv_addr.sin_addr.s_addr == INADDR_BROADCAST)
	{
		sock_set_broadcast(sock);
	}
	else
	{
		/* ligar 'a porta remota */
		if (connect(sock, (LPSOCKADDR)&srv_addr, sizeof(struct sockaddr_in)) == SOCKET_ERROR)
			return FALSE;
	}

	return TRUE;
}

/*****************************************************************************
 FUNCAO:
 RETORNO:
 DATA:		97.03.07
 AUTOR:		Paulo Sousa
 REVISOES:
 -----------+-------------------+---------------------------------------------
	DATA	|	   QUEM			|	   O QUE
 -----------+-------------------+---------------------------------------------
 *****************************************************************************/
SOCKET APIENTRY sock_open(LPCSTR lpcszHost,			/*(I) nome da maquina ou HOST_BROADCAST */
						  LPCSTR lpcszService,		/*(I) numero de porta ou nome do servico */
						  LPCSTR lpcszProtocolo,	/*(I) protocolo do servico: "tcp", "udp", ... */
						  int tipo,					/*(I) tipo de socket: SOCK_STREAM, ... */
						  int proto					/*(I) protocolo: IPPROTO_TCP, ... */
						  )
{
SOCKET sock;

	/* criar socket lado cliente */
	sock = sock_create(SERVICE_ANY, NULL, tipo, proto);
	if (sock == INVALID_SOCKET)
	{
		return INVALID_SOCKET;
	}

	/* conectar ao lado servidor */
	if (sock_connect(sock, lpcszHost, lpcszService, lpcszProtocolo) == FALSE)
	{
		sock_close(sock);

		return INVALID_SOCKET;
	}

	return sock;
}

/*****************************************************************************
 FUNCAO:
 RETORNO:	
 DATA:		97.03.12
 AUTOR:		Paulo Sousa
 REVISOES:
 -----------+-------------------+---------------------------------------------
	DATA	|	   QUEM			|	   O QUE
 -----------+-------------------+---------------------------------------------
 *****************************************************************************/
DWORD APIENTRY sock_get_queued_bytes(SOCKET fd		/*(I)*/
									 )
{
DWORD nReady;

	/* Query the system how many bytes are ready to be read */
	ioctlsocket(fd, FIONREAD, &nReady);

	return nReady;
}

/*****************************************************************************
 FUNCAO:	Calculate Internet checksum for data buffer and length (one’s 
			complement sum of 16-bit words).  Used in IP, ICMP, UDP, IGMP.
 RETORNO:
 DATA:		97.03.12
 AUTOR:
 REVISOES:
 -----------+-------------------+---------------------------------------------
	DATA	|	   QUEM			|	   O QUE
 -----------+-------------------+---------------------------------------------
 *****************************************************************************/
WORD APIENTRY sock_cksum(LPCBYTE lpBuffer,	/*(I)*/
						 size_t nLen		/*(I)*/
						 ) 
{	
register long lSum = 0L;
LPWORD lp = (LPWORD)lpBuffer;
		
	/* 
	 * note: to handle odd number of bytes, last (even) byte in 
	 * buffer have a value of 0 (we assume that it does) 
	 */
	while (nLen > 0) 
	{
		lSum += *(lp++);	/* add word value to sum */
		nLen -= 2;          /* decrement byte count by 2 */
	}

	/* put 32-bit sum into 16-bits */
	lSum = (lSum & 0xffff) + (lSum>>16);
	lSum += (lSum >> 16);

	/* one-complement */
	lSum = ~lSum;

	return (WORD)lSum; 
}

/*****************************************************************************
 FUNCAO:
 RETORNO:
 DATA:		97.03.12
 AUTOR:		Paulo Sousa
 REVISOES:
 -----------+-------------------+---------------------------------------------
	DATA	|	   QUEM			|	   O QUE
 -----------+-------------------+---------------------------------------------
 *****************************************************************************/
BOOL APIENTRY sock_startup(void)
{
#ifdef TP_OS_WINDOWS
WSADATA wsa;

	if (WSAStartup(MAKEWORD(1,1), &wsa) != 0)
	{
		return FALSE;
	}
#endif

	return TRUE;
}

/*****************************************************************************
 FUNCAO:
 RETORNO:
 DATA:		97.03.12
 AUTOR:		Paulo Sousa
 REVISOES:
 -----------+-------------------+---------------------------------------------
	DATA	|	   QUEM			|	   O QUE
 -----------+-------------------+---------------------------------------------
 *****************************************************************************/
void APIENTRY sock_cleanup(void)
{
#ifdef TP_OS_WINDOWS
	WSACleanup();
#endif
}

/*****************************************************************************
 FUNCAO:	enviar completamento o buffer
 RETORNO:	
 DATA:		96.08.22
 AUTOR:		Paulo Sousa
 REVISOES:
 -----------+-------------------+---------------------------------------------
	DATA	|	   QUEM			|	   O QUE
 -----------+-------------------+---------------------------------------------
 *****************************************************************************/
int APIENTRY sock_send_n(SOCKET sock,				/*(I)*/
						 const LPBYTE buffer_snd,	/*(I)*/
						 int buf_len				/*(I)*/
						 )
{
int iQtSnd;
int iQtBuf = buf_len;
LPBYTE buffer_ptr = buffer_snd;

	do {
		iQtSnd = send(sock, buffer_ptr, iQtBuf, 0);
		if (iQtSnd == SOCKET_ERROR)
			return SOCKET_ERROR;

		buffer_ptr += iQtSnd;
		iQtBuf -= iQtSnd;
	} while (iQtBuf != 0);
	
	return buf_len;
}

/*****************************************************************************
 FUNCAO:	efectuar recv()
 RETORNO:	
 DATA:		96.08.22
 AUTOR:		Paulo Sousa
 REVISOES:
 -----------+-------------------+---------------------------------------------
	DATA	|	   QUEM			|	   O QUE
 -----------+-------------------+---------------------------------------------
 *****************************************************************************/
int APIENTRY sock_recv_n(SOCKET sock,			/*(I)*/
						 LPBYTE buffer_rcv,		/*(O)*/
						 int buf_len			/*(I)*/
						 )
{
LPBYTE lp_buf = buffer_rcv;
int rc_len;
size_t max_len = buf_len;

	do {
		rc_len = recv(sock, lp_buf, max_len, 0);
		if (rc_len == 0)
		{
			/* o parceiro fechou a ligacao */
			return 0;
		}
		else if (rc_len == SOCKET_ERROR)
		{
			return SOCKET_ERROR;
		}

		lp_buf += rc_len;
		max_len -= rc_len;
	} while (max_len > 0);

	return buf_len;
}

/*****************************************************************************
 FUNCAO:
 RETORNO:
 DATA:
 AUTOR:
 REVISOES:
 -----------+-------------------+---------------------------------------------
	DATA	|	   QUEM			|	   O QUE
 -----------+-------------------+---------------------------------------------
 *****************************************************************************/

