/*
 *
 *
 *    tcpsocket.cc
 *
 *    C++ source code for class my::tcpsocket member functions and/or static
 *    member data.
 *
 *    =========================================================================
 *
 *    Copyright (C) 1999-2003 by Matt Roberts, KK5JY.
 *
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Lesser General Public
 *    License as published by the Free Software Foundation; either
 *    version 2.1 of the License, or (at your option) any later version.
 *
 *    This library 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
 *    Lesser General Public License for more details.
 *
 *    You should have received a copy of the GNU Lesser General Public
 *    License along with this library; if not, write to the Free Software
 *    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 *    =========================================================================
 *
 *
 */


#include <my++/tcpsocket.h>


void my::tcpsocket::check_sd (void) {
	// if the socket is not allocated
	if (sd < 0) {
		// request a new socket from the kernel
		sd = ::socket (PF_INET, SOCK_STREAM, 0);
		if (sd < 0)
			throw (tcpsocket::exception(0, errno));
	}
}

my::tcpsocket::tcpsocket (void) {
	// just initialize the socket descriptor
	sd = (-1);
}

my::tcpsocket::tcpsocket (const inet_address &a, int p) : sd(-1) {
	// there has to be a better way to do this without manually...
	// ...building a sockaddr_in
	check_sd();
	sockaddr_in a2;
	tcpsocket::address(a,p).to_sockaddr(&a2);

	// try to bind; if that fails, throw an exception
	if (::bind(sd, (sockaddr*)(&a2), sizeof(sockaddr_in)) < 0)
		throw tcpsocket::exception(0, errno);
}

my::tcpsocket::tcpsocket (const tcpsocket::address &a) : sd(-1) {
	check_sd();
	sockaddr_in bind_addr;
	a.to_sockaddr(&bind_addr);

	// try to bind; if that fails, throw an exception
	if (::bind(sd, (sockaddr*)(&bind_addr), sizeof(sockaddr_in)) < 0)
		throw tcpsocket::exception(0, errno);
}

my::tcpsocket::tcpsocket (const std::string &s, int p) : sd(-1) {
	check_sd();
	sockaddr_in bind_addr;
	tcpsocket::address(s,p).to_sockaddr(&bind_addr);

	// try to bind; if that fails, throw an exception
	if (::bind(sd, (sockaddr*)(&bind_addr), sizeof(sockaddr_in)) < 0)
		throw tcpsocket::exception(0, errno);
}

void my::tcpsocket::bind (const my::socket::address &a) {
	check_sd();
	const tcpsocket::address &addr = dynamic_cast<const tcpsocket::address&>(a);

	// build the sockaddr_in structure to pass to bind
	sockaddr_in bind_addr;
	addr.to_sockaddr(&bind_addr);

	// try to bind the address; if that fails, throw an exception
	if (::bind(sd, (sockaddr*)(&bind_addr), sizeof(sockaddr_in)) < 0)
		throw tcpsocket::exception(this, errno);
}

/*
void my::tcpsocket::bind (const std::string &a) {
	// implement me -- FIXME
	// make 'a' into a URL, and bind
}
*/

void my::tcpsocket::ishutdown (void) {
	if (::shutdown (sd, SHUT_RD) < 0)
		throw tcpsocket::exception(this, errno);
}

void my::tcpsocket::oshutdown (void) {
	if (::shutdown (sd, SHUT_WR) < 0)
		throw tcpsocket::exception(this, errno);
}

void my::tcpsocket::ioshutdown (void) {
	if (::shutdown (sd, SHUT_RDWR) < 0)
		throw tcpsocket::exception(this, errno);
}

ssize_t my::tcpsocket::read_vp (void *buf, size_t len) {
	return ::recv(sd, buf, len,
		#ifdef LINUX
		MSG_NOSIGNAL
		#else /* i.e., SOLARIS */
		0
		#endif /* ifdef LINUX */
	);
}

ssize_t my::tcpsocket::write_vp (const void *buf, size_t len) {
	return ::send(sd, buf, len,
		#ifdef LINUX
		MSG_NOSIGNAL
		#else /* i.e., SOLARIS */
		0
		#endif /* ifdef LINUX */
	);
}

void my::tcpsocket::disconnect (void) {
	close();
}

void my::tcpsocket::connect (const socket::address &a) {
	check_sd();

	// check to see that the object was a my::tcpsocket object
	const tcpsocket::address *addr = dynamic_cast<const tcpsocket::address*>(&a);

	// if the address wasn't an 'tcpsocket::address' structure, throw an exception
	if (!addr)
		throw tcpsocket::exception(this, EINVAL);

	// build the sockaddr_in structure to pass to connect
	sockaddr_in rmt_addr;
	addr->to_sockaddr(&rmt_addr);

	// try to bind the address; if that fails, throw an exception
	if (::connect(sd, (sockaddr*)(&rmt_addr), sizeof(sockaddr_in)) < 0)
		throw tcpsocket::exception(this, errno);
}

void my::tcpsocket::connect (const std::string &a) {
	// implement me
	// treat 'a' as a URL, and connect
}

void my::tcpsocket::listen (int backlog) {
	if (::listen(sd, backlog) < 0)
		throw tcpsocket::exception (this, errno);
}

void my::tcpsocket::listen (void) {
	if (::listen(sd, DEFAULT_BACKLOG) < 0)
		throw tcpsocket::exception (this, errno);
}

void my::tcpsocket::accept (my::socket &s, my::socket::address &a) {
	// cast the provided args into the correct derived type
	tcpsocket::address &a2 = dynamic_cast<tcpsocket::address&>(a);
	tcpsocket &s2 = dynamic_cast<tcpsocket&>(s);

	sockaddr_in recd_addr;
	socklen_t recd_len = sizeof(recd_addr);
	int new_sd = ::accept (sd, (sockaddr*)(&recd_addr), &recd_len);
	if (new_sd < 0)
		throw tcpsocket::exception (this, errno);

	// assign the accepted data into the args above
	a2 = recd_addr;

	// assign the descriptor to the new socket
	s2.sd = new_sd;
}

void my::tcpsocket::accept (socket &s, std::string &a) {
	tcpsocket::address a2;
	tcpsocket &s2 = dynamic_cast<tcpsocket&>(s);

	accept(s2, a2);
	a2.to_string(a);
}

void my::tcpsocket::accept (socket &s) {
	tcpsocket::address dummy;
	accept (s, dummy);
}

void my::tcpsocket::close (void) throw() {
	if (sd >= 0) {
		::close(sd);
		sd = (-1);
	}
}

void my::tcpsocket::name (socket::address &addr) {
	if (sd >= 0) {
		sockaddr_in bound_addr;
		socklen_t addr_len = sizeof(bound_addr);
		int rtn = ::getsockname(sd, (sockaddr*)(&bound_addr), &addr_len);
		if (rtn < 0)
			throw tcpsocket::exception(this, errno);
		tcpsocket::address &a2 = dynamic_cast<tcpsocket::address&>(addr);
		a2 = bound_addr;
	}
}

void my::tcpsocket::peer (socket::address &addr) {
	if (sd >= 0) {
		sockaddr_in bound_addr;
		socklen_t addr_len = sizeof(bound_addr);
		int rtn = ::getpeername(sd, (sockaddr*)(&bound_addr), &addr_len);
		if (rtn < 0)
			throw tcpsocket::exception(this, errno);
		tcpsocket::address &a2 = dynamic_cast<tcpsocket::address&>(addr);
		a2 = bound_addr;
	}
}

/*
my::tcpsocket &my::tcpsocket::operator = (const int new_sd) {
	assign(new_sd);
	return (*this);
}

void my::tcpsocket::assign (int new_sd) {
	if (sd >= 0)
		::close (sd);

	// duplicate the open descriptor for our new object
	sd = dup(new_sd);

	if (sd < 0) {
		sd = -1;
		throw tcpsocket::exception(this, errno);
	}
}
*/
