/*
 *
 *
 *    unixsocket.cc
 *
 *    C++ source code for class my::unixsocket 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++/unixsocket.h>


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

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

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

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

my::unixsocket::unixsocket (const std::string &s) : sd(-1) {
	check_sd();
	sockaddr_un bind_addr;
	unixsocket::address(s).to_sockaddr(&bind_addr);

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

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

	// build the sockaddr_un structure to pass to bind
	sockaddr_un 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_un)) < 0)
		throw unixsocket::exception(this, errno);
}

void my::unixsocket::bind (const std::string &a) {
	check_sd();
	const unixsocket::address addr(a);

	// build the sockaddr_un structure to pass to bind
	sockaddr_un 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_un)) < 0)
		throw unixsocket::exception(this, errno);
}

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

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

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

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

ssize_t my::unixsocket::write_vp (const void *b, size_t len) {
	return ::send(sd, b, len,
		#ifdef LINUX
		MSG_NOSIGNAL
		#else
		0
		#endif /* LINUX */
	);
}

void my::unixsocket::connect (const socket::address &a) {
	// make sure we have a live socket
	check_sd();

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

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

	// build the sockaddr_un structure to pass to connect
	sockaddr_un 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_un)) < 0)
		throw unixsocket::exception(this, errno);
}

void my::unixsocket::connect (const std::string &a) {
	// make sure we have a live socket
	check_sd();

	// if the address wasn't valid, throw an exception
	if (!a.size())
		throw unixsocket::exception(this, EINVAL);

	// build the sockaddr_un structure to pass to connect
	const unixsocket::address addr(a);
	sockaddr_un 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_un)) < 0)
		throw unixsocket::exception(this, errno);
}

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

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

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

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

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

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

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

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

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

void my::unixsocket::disconnect (void) {
	if (sd >= 0)
		close();
}

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

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

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

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

void my::unixsocket::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 unixsocket::exception(this, errno);
	}
}
*/
