/*******************************************************************************
 *	BSocket.cc	BSocket Classes
 *			T.Barnaby,	BEAM Ltd,	1/4/05
 *******************************************************************************
 */
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <net/if.h>
#include "BSocket.h"

#if HAVE_GETIFADDRS
#include <ifaddrs.h>
#endif

#ifndef IP_MTU
#define IP_MTU	14
#endif


BSocketAddress::BSocketAddress(){
	olen = 0;
	oaddress = 0;
}

BSocketAddress::BSocketAddress(const BSocketAddress& add){
	olen = 0;
	oaddress = 0;
	set(add.oaddress, add.olen);
}

BSocketAddress::BSocketAddress(SockAddr* address, int len){
	olen = 0;
	oaddress = 0;
	set(address, len);
}

BSocketAddress::~BSocketAddress(){
	free(oaddress);
	oaddress = 0;
	olen = 0;;
}

BError BSocketAddress::set(SockAddr* address, int len){
	BError	err;
	
	if(olen != len){
		olen = len;
		if(oaddress)
			free(oaddress);
		oaddress = (SockAddr*)malloc(olen);
	}
	memcpy(oaddress, address, olen);
	return err;
}

const BSocketAddress::SockAddr* BSocketAddress::raw() const {
	return oaddress;
}

int BSocketAddress::len() const{
	return olen;
}

BSocketAddress& BSocketAddress::operator=(const BSocketAddress& add){
	if(this != &add){
		free(oaddress);

		olen = add.olen;
		oaddress = (SockAddr*)malloc(olen);
		memcpy(oaddress, add.oaddress, olen);
	}
	return *this;
}

int BSocketAddress::operator==(const BSocketAddress& add) const {
	return !memcmp(oaddress, add.oaddress, olen);
}

int BSocketAddress::operator!=(const BSocketAddress& add) const {
	return ! (*this == add);
}



BError BSocketAddressINET::set(BString hostName, uint32_t port){
	BError			err;
	struct hostent*         host;
	SockAddrIP		add;

	/* Clear out address structure */
	memset(&add, 0, sizeof(add));
	add.sin_family	= AF_INET;
	add.sin_port = htons(port);

	if(hostName.len()){
		if((host = gethostbyname(hostName)) && host->h_addr_list[0]){
			memcpy(&(add.sin_addr.s_addr), host->h_addr_list[0], sizeof(add.sin_addr.s_addr));
		}
		else {
			err.set(-h_errno, hstrerror(h_errno));
		}
	}
	if(!err)
		err = BSocketAddress::set((SockAddr*)&add, sizeof(add));
	
	return err;
}

BError BSocketAddressINET::set(uint32_t address, uint32_t port){
	BError			err;
	SockAddrIP		add;

	/* Clear out address structure */
	memset(&add, 0, sizeof(add));
	add.sin_family	= AF_INET;
	add.sin_port = htons(port);
	add.sin_addr.s_addr = htonl(address);
	err = BSocketAddress::set((SockAddr*)&add, sizeof(add));
	
	return err;
}

BError BSocketAddressINET::set(BString hostName, BString service, BString type){
	BError		err;
	struct servent*	ser;

	if(ser = getservbyname(service.retStr(), type.retStr())){
		err = set(hostName, htons(ser->s_port));
	}
	else {
		err.set(-1, "Getservbyname error");
	}
	return err;
}

void BSocketAddressINET::setPort(uint32_t port){
	SockAddrIP		add;

	/* Clear out address structure */
	memset(&add, 0, sizeof(add));
	if(len())
		memcpy(&add, raw(), sizeof(add));
	add.sin_port = htons(port);
	BSocketAddress::set((SockAddr*)&add, sizeof(add));
}

uint32_t BSocketAddressINET::address(){
	uint32_t	ret = 0;
	
	if(len()){
		ret = ntohl(((SockAddrIP*)raw())->sin_addr.s_addr);
	}
	return ret;
}

uint32_t BSocketAddressINET::port(){
	uint32_t	ret = 0;
	
	if(len()){
		ret = ntohs(((SockAddrIP*)raw())->sin_port);
	}
	return ret;
}

BString BSocketAddressINET::getString(){
	BString			ret;
	char			buf[32];
	SockAddrIP*		add = (SockAddrIP*)raw();
	

	buf[0] = '\0';
	if(len()){
		inet_ntop(add->sin_family, &add->sin_addr, buf, sizeof(buf));
		ret = BString(buf) + ":" + ntohs(add->sin_port);
	}

	return ret;
}


// Some usefull functions
BString BSocketAddressINET::getHostName(){
	char	s[256];
	
	gethostname(s, sizeof(s));
	s[sizeof(s) - 1] = '\0';
	return s;
}

BList<uint32_t> BSocketAddressINET::getIpAddresses(){
	struct hostent*         host;
	int			n;
	BList<uint32_t>		l;
	
	if(host = gethostbyname(getHostName())){
		for(n = 0; host->h_addr_list[n]; n++){
			l.append(ntohl(*((int*)host->h_addr_list[n])));
		}
	}
	return l;
}

BList<BString> BSocketAddressINET::getIpAddressList(){
	BList<BString>		l;
	char			buf[256] = "";
	struct hostent*         host;
	int			n;
	
	if(host = gethostbyname(getHostName())){
		for(n = 0; host->h_addr_list[n]; n++){
			if(inet_ntop(AF_INET, host->h_addr_list[n], buf, sizeof(buf))){
				l.append(buf);
			}
		}
	}

	return l;
}

BList<BString> BSocketAddressINET::getIpAddressListAll(){
	BList<BString>		l;
	char			buf[256] = "";

#if HAVE_GETIFADDRS
	struct ifaddrs*		addrs = NULL;
	struct ifaddrs*		addr = NULL;
	struct sockaddr_in*	ia;

	if(getifaddrs(&addrs) == 0){
		for(addr = addrs; addr; addr = addr->ifa_next){
			if(addr->ifa_addr && (addr->ifa_addr->sa_family == PF_INET)){
				ia = (struct sockaddr_in*)addr->ifa_addr;
				if(inet_ntop(AF_INET, &ia->sin_addr, buf, sizeof(buf))){
					l.append(buf);
				}
			}
		}
	}
#else
	struct hostent*         host;
	int			n;
	
	if(host = gethostbyname(getHostName())){
		for(n = 0; host->h_addr_list[n]; n++){
			if(inet_ntop(AF_INET, host->h_addr_list[n], buf, sizeof(buf))){
				l.append(buf);
			}
		}
	}
#endif
	return l;
}

/*******************************************************************************
 *	BSocket class
 *******************************************************************************
 */

BSocket::BSocket(){
	osocket = -1;
}

BSocket::BSocket(int fd){
	osocket = fd;
}

BSocket::BSocket(NType type){
	init(type);
	if(osocket < 0)
		fprintf(stderr, "BSocket::BSocket: Socket creation error: %s\n", strerror(errno));
}

BSocket::~BSocket(){
	if(osocket >= 0)
		::close(osocket);
	osocket = -1;
}

BError BSocket::init(NType type){
	BError	err;
	
	switch(type){
	case STREAM:	osocket = socket(AF_INET, SOCK_STREAM, 0);	break;
	case DGRAM:	osocket = socket(AF_INET, SOCK_DGRAM, 0);	break;
	}
	
	if(osocket < 0)
		err.set(-errno, strerror(errno));
	return err;
}

int BSocket::getFd(){
	return osocket;
}

BError BSocket::bind(const BSocketAddress& address){
	BError		err;

	if(::bind(osocket, address, address.len()) < 0)
		err.set(-errno, strerror(errno));

	return err;
}

BError BSocket::connect(const BSocketAddress& address){
	BError		err;

	if(::connect(osocket, address, address.len()) < 0)
		err.set(-errno, strerror(errno));

	return err;
}

BError BSocket::shutdown(int how){
	BError		err;

	if(::shutdown(osocket, how) < 0)
		err.set(-errno, strerror(errno));

	return err;
}

BError BSocket::close(){
	BError		err;
	
	if(osocket >= 0)
		::close(osocket);
	osocket = -1;
	
	return err;
}

BError BSocket::listen(int backlog){
	BError	err;
	
	if(::listen(osocket, 5) < 0)
		err.set(-errno, strerror(errno));
	return err;
}

BError BSocket::accept(int& fd){
	BError				err;
	int				f;

	if((f = ::accept(osocket, 0, 0)) < 0){
		err.set(-errno, strerror(errno));
	}
	else {
		fd = f;
	}
	return err;
}

BError BSocket::accept(int& fd, BSocketAddress& address){
	BError				err;
	BSocketAddress::SockAddr	add;
	socklen_t			len = sizeof(add);
	int				f;

	if((f = ::accept(osocket, &add, &len)) < 0){
		err.set(-errno, strerror(errno));
	}
	else {
		fd = f;
		address.set(&add, len);
	}
	return err;
}

BError BSocket::send(const void* buf, BSize nbytes, BSize& nbytesSent, int flags){
	BError	err;
	int	r;
	
	if((r = ::send(osocket, buf, nbytes, flags)) < 0){
		err.set(-errno, strerror(errno));
	}
	else {
		nbytesSent = r;
	}
	return err;
}

BError BSocket::sendTo(const BSocketAddress& address, const void* buf, BSize nbytes, BSize& nbytesSent, int flags){
	BError	err;
	int	r;
	
	if((r = ::sendto(osocket, buf, nbytes, flags, address, address.len())) < 0){
		err.set(-errno, strerror(errno));
	}
	else {
		nbytesSent = r;
	}
	return err;
}

BError BSocket::recv(void* buf, BSize maxbytes, BSize& nbytesRecv, int flags){
	BError	err;
	int	r;

	if((r = ::recv(osocket, buf, maxbytes, flags)) < 0){
		err.set(-errno, strerror(errno));
	}
	else {
		if(r == 0)
			err.set(-EPIPE, "Connection Closed by Peer");
		nbytesRecv = r;
	}
	return err;
}

BError BSocket::recvWithTimeout(void* buf, BSize maxbytes, BSize& nbytesRecv, int timeout, int flags){
	BError		err;
	int		r;
	fd_set		set;
	struct timeval	t;
	
	if(timeout >= 0){
		t.tv_sec = 0;
		t.tv_usec = timeout;
		FD_ZERO(&set);
		FD_SET(osocket, &set);
		
		r = select(osocket + 1, &set, 0, 0, &t);
		if(r == 0)
			return err.set(-ETIMEDOUT, "Connection timed out");
	}

	return recv(buf, maxbytes, nbytesRecv, flags);
}

BError BSocket::recvFrom(BSocketAddress& address, void* buf, BSize maxbytes, BSize& nbytesRecv, int flags){
	BError				err;
	int				r;
	BSocketAddress::SockAddr	add;
	socklen_t			len = sizeof(add);

	if((r = ::recvfrom(osocket, buf, maxbytes, flags, &add, &len)) < 0){
		err.set(-errno, strerror(errno));
	}
	else {
		address.set(&add, len);
	}
	return err;
}

BError BSocket::recvFromWithTimeout(BSocketAddress& address, void* buf, BSize maxbytes, BSize& nbytesRecv, int timeout, int flags){
	BError		err;
	int		r;
	fd_set		set;
	struct timeval	t;
	
	if(timeout >= 0){
		t.tv_sec = 0;
		t.tv_usec = timeout;
		FD_ZERO(&set);
		FD_SET(osocket, &set);
		
		r = select(osocket + 1, &set, 0, 0, &t);
		if(r == 0)
			return err.set(-ETIMEDOUT, "Connection timed out");
	}
	
	return recvFrom(address, buf, maxbytes, nbytesRecv, flags);
}


BError BSocket::setSockOpt(int level, int optname, void* optval, unsigned int optlen){
	BError	err;
	
	if(setsockopt(osocket, level, optname, optval, optlen) < 0)
		err.set(-errno, strerror(errno));
	return err;	
}

BError BSocket::getSockOpt(int level, int optname, void* optval, unsigned int* optlen){
	BError	err;

	if(getsockopt(osocket, level, optname, optval, optlen) < 0)
		err.set(-errno, strerror(errno));
	return err;	
}

BError BSocket::setReuseAddress(int on){
	return setSockOpt(SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
}


BError BSocket::setBroadCast(int on){
	return setSockOpt(SOL_SOCKET, SO_BROADCAST, &on, sizeof(on));
}

BError BSocket::setPriority(Priority priority){
	unsigned int	level;
	
	switch(priority){
	case PriorityLow:	level = 2;	break;
	case PriorityNormal:	level = 0;	break;
	case PriorityHigh:	level = 6;	break;
	default:		level = 0;	break;
	}
	return setSockOpt(SOL_SOCKET, SO_PRIORITY, &level, sizeof(level));
}

BError BSocket::getMTU(uint32_t& mtu){
	socklen_t	mtulen = sizeof(mtu);
	
	return getSockOpt(SOL_IP, IP_MTU, &mtu, &mtulen);
}

BError BSocket::getAddress(BSocketAddress& address){
	BError				err;
	BSocketAddress::SockAddr	add;
	socklen_t			len = sizeof(add);
	
	if(getsockname(osocket, &add, &len) < 0){
		err.set(-errno, strerror(errno));
	}
	else {
		address.set(&add, len);
	}
	return err;
}
