/*
 * Smnet.cpp - Network SIP listener for Short Messages (SMS's) for OpenBTS.
 * Written by John Gilmore, July 2009.
 *
 * Copyright 2009 Free Software Foundation, Inc.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * See the COPYING file in the main directory for details.
 */

#include <time.h>
#include <osipparser2/osip_message.h>	/* from osipparser2 */
#include <iostream>
#include <fstream>
#include "poll.h"
#include <sys/types.h>			// for sys/socket.h
#include <sys/socket.h>			// recvfrom
#include <netdb.h>			// getaddrinfo
#include <errno.h>
#include <fcntl.h>
#include <cstdlib>			// l64a
#include <arpa/inet.h>			// inet_ntop

#include "smnet.h"
#include "smqueue.h"

using namespace std;

namespace SMqueue {

// Because the abort function isn't always accessible in C--?
void
SMnet::abfuckingort()
{
	*((char *)0) = -1;
}


/*
 * Send a datagram on a handy socket
 * FIXME:  Make the source host/port match the one in the SIP dgram!
 *	   Currently we just send on the first socket with right addr length!
 * Result is true if sent OK; false if not (and errno is set if false).
 */
bool
SMnet::send_dgram(char *buffer, size_t buffsize, char *toaddr,
		size_t toaddrsize)
{
	ssize_t i;
	int flags = MSG_DONTWAIT;

	if (!sockets || numsockets == 0) {
		errno = EBADF;
		return false;
	}

	nfds_t j;
	for (j = 0; j < numsockets; j++) {
		if (
		    sockinfo[j].socktype == SOCK_DGRAM &&
		    sockinfo[j].addrlen == toaddrsize) {
			i = sendto (sockets[j].fd, buffer, buffsize,
				flags, (struct sockaddr *)toaddr, toaddrsize);
			if (i < 0)
				continue;
			if (i != (ssize_t) buffsize) {
				errno = EPIPE;		// We could do better...
				return false;
			}
			return true;		// Success!
		}
	} // else try another socket

	errno = EBADF;
	return false;		// No socket worked.
}


/* Talk to the GSM engine... */
bool
SMnet::deliver_msg_datagram(SMqueue::short_msg_pending *smp)
{
	char *scheme, *host, *port;
	int s, i;

	// Make sure the text is valid before writing it for debug,
	// or delivering it to a handset.
	smp->make_text_valid();

	// FIXME, kludge it.
	// Write to first file descriptor...
	// we should chk for matching address family, etc...
	LOG(DEBUG) << endl << "--Deliver message:";
	LOG(DEBUG) << smp->text;

	// We need to have at least ONE socket open (for sending on).
	if (!sockets || numsockets == 0)
		return false;

	/* Get the addressing info (and the SIP msg itself) out of the msg */
	if (!smp->parse()) return false;
	scheme = smp->parsed->req_uri->scheme;
	host   = smp->parsed->req_uri->host;
	port   = smp->parsed->req_uri->port;
	
	struct addrinfo myhints;
	struct addrinfo *myaddrs, *ap;

	memset(&myhints, 0, sizeof(myhints));
	myhints.ai_family = AF_UNSPEC;		// Any address family eg v4/6
	myhints.ai_socktype = SOCK_DGRAM;	// Datagrams for now FIXME
#ifdef AI_IDN
	myhints.ai_flags = AI_IDN;		// Int'l dom names OK.
#endif
	if (!scheme)
		scheme = (char * const)"sip";
	if (!port)
		port = scheme;	// More specific port is better

	s = getaddrinfo(host, port, &myhints, &myaddrs);
	if (s != 0) {
		LOG(ERROR) << "deliver_msg_datagram() can't lookup addr/port: " 
		     << host << ":" << port << ", error " << s;
		return false;
	}
	
	// Now deliver to some working address we got back.
	// For each address, find an open socket that has the right
	// kind of protocol and return address...
	// FIXME, do we need to match the bound address with the packet's
	// "return address"?  Maybe...
	for (ap = myaddrs; ap != NULL; ap = ap->ai_next) {
		int flags = MSG_DONTWAIT;
		nfds_t j;
		i = 0;
		for (j = 0; j < numsockets; j++) {
			if (sockinfo[j].addrfam == ap->ai_family &&
			    sockinfo[j].socktype == ap->ai_socktype &&
			    sockinfo[j].protofam == ap->ai_protocol &&
			    sockinfo[j].addrlen == ap->ai_addrlen) {
				i = sendto (sockets[j].fd, 
					smp->text, smp->text_length,
					flags, ap->ai_addr, ap->ai_addrlen);
				if (i < 0) {
					break;	// Try another address
				}
			}
		}
		if (i < 0) continue;		// Errored, try another address
		if (i == 0) continue;		// No match, try another address.
		
		break;				// sent -- stop trying!
	}
	freeaddrinfo(myaddrs);		// Don't leak memory.
	if (ap == NULL) {		// If we walked off bottom of list,
		LOG(ERROR) << "Couldn't send datagram to " << host << ":" 
		     << port << " on any socket";
		return false;		// we failed to send
	}
	return true;			// Hey, we sent the message onward!
}


/* 
 * Network listener for SIP Short Messages.
 * Timeout in milliseconds (negative means infinity).
 *
 * Result is <0 if error; 
 * Result is >0 if buffer contains a received packet.
 * (Packet's source address is saved away for future access by
 * our caller.)
 * Result == 0 if caller was interested in writing, there's no received
 * packet yet, and it's OK to write now.  OR if we timed out.
 */
int
SMnet::get_next_dgram (char *buffer, size_t bufferlen, int mstimeout)
{
	int i, fd, flags;
	nfds_t j;
	short revents;
	socklen_t addrlen; 
	size_t recvlength;

	i = poll_sockets(mstimeout);
	if (i < 0) 	// error
		return i;
	if (i == 0)	// timeout
		return 0;

	for (j = 0; j < numsockets; j++) {	// Walk the sockets.
		fd = sockets[j].fd;
		revents = sockets[j].revents;

#ifdef POLLRDHUP
		if (revents & (POLLIN|POLLPRI|POLLRDHUP))
#else
		if (revents & (POLLIN|POLLPRI))
#endif
		{					// input OK
			// FIXME, TCP and files aren't supported yet
			addrlen = sizeof(src_addr);
			flags = MSG_DONTWAIT|MSG_TRUNC;
			recvlength = recvfrom(fd, buffer, bufferlen, flags,
					(sockaddr *)&src_addr, &addrlen);
			if (recvlength < 0) {
				// Error on receive.
				LOG(ERROR) << "Error " << strerror(errno)
				     << "on recvfrom";
				// We shouldn't loop -- or the error might make
				// an endless loop.  Return.
				return -1;
			}
			if (addrlen > sizeof(src_addr)) {
				// Received address truncated.  BUG in program!
				LOG(ERROR) << "recvfrom received address truncated!";
				// FIXME, print src_addr too
				abfuckingort();
			}
			recvaddrlen = addrlen;		// Save for later
			if (recvlength > bufferlen) {
				// Received packet itself truncated.
				LOG(ERROR) << "recvfrom data packet truncated, "
				        "buffer has " 
				     << bufferlen << " bytes, packet of " 
				     << recvlength << "!";
				// FIXME, print src_addr too
				return -1;
			}
			
			//
			// OK, we got a full packet from a particular address.
			// Pass it upstairs for further processing.
			//
			return recvlength;
		}

		if (revents & (POLLOUT)) {	// output OK
			return 0;		// Tell caller to retry write
		}

		if (revents & (POLLERR|POLLHUP|POLLNVAL)) {	// errors
			LOG(ERROR) << "Poll error " << strerror(errno)
			     << "huh!";
			return -1;
		}
		// If no bits set on this FD, loop to the next one.
	}

	LOG(ERROR) << "Poll() returned " << i << " without any pending I/O";
	return -1;
}


/*
 * Initialize short-message handling. 
 * Make one or more sockets and set up to listen on them.
 * This function is xxx ought-to-be ipv6-agnostic, and is even
 * almost UDP/TCP-agnostic.
 */
bool
SMnet::listen_on_port(std::string port)
{
	int s;
	int gotasocket = 0;
	struct addrinfo myhints;
	struct addrinfo *myaddrs, *ap;

	memset(&myhints, 0, sizeof(myhints));
	myhints.ai_family = AF_UNSPEC;		// Any address family eg v4/6
	myhints.ai_socktype = SOCK_DGRAM;	// Datagrams for now FIXME
	myhints.ai_flags = AI_PASSIVE;		// From anybody

	s = getaddrinfo(NULL, (port.c_str()), &myhints, &myaddrs);
	if (s != 0) {
		LOG(ERROR) << "listen_on_port(" << port 
		     << ") can't get addr/port to listen on";
		return -1;
	}
	
	for (ap = myaddrs; ap != NULL; ap = ap->ai_next) {
	
		int fd, i;

		fd = socket(ap->ai_family, ap->ai_socktype, ap->ai_protocol);
		if (fd < 0)
			continue;		// Try another
		// Set our port number & address.
		i = bind(fd, ap->ai_addr, ap->ai_addrlen);
		if (i < 0) {
			LOG(ERROR) << "listen_on_port(" << port
			     << ") can't bind to addr '"
			     << string_addr (ap, true) << "': " 
			     << strerror(errno);
			close(fd);		// Don't leave it dangling
			continue;		// Try another
		}
		
		(void) fcntl(fd, F_SETFL, O_NONBLOCK);	// Non-blocking I/O
#ifdef O_CLOEXEC
		(void) fcntl(fd, F_SETFD, O_CLOEXEC);	// Close on exec child
#endif

		// Now set up our class to poll on, and use, this socket.
		add_socket (fd, POLLIN|POLLPRI, ap->ai_family,
			ap->ai_socktype, ap->ai_protocol, ap->ai_addr,
			ap->ai_addrlen);
		// Be slightly verbose here.
		LOG(INFO) << "Listening at address '"
		     << string_addr (ap, true) << "'.";
		gotasocket++;
		// And keep looping to make several sockets if we can!
	}
	freeaddrinfo(myaddrs);		// Don't leak memory.

	if (!gotasocket)
		return false;
	return true;
}

/*
 * My hostname as known to the global network.
 *
 * FIXME, there is no kernel interface for finding this out.  Our return
 * address depends what interface our packets go out.  NAT and such make
 * it even more complicated.  This would need to be a config-file setting.
 */
char *
SMnet::myhostname()
{
	size_t plen = INET6_ADDRSTRLEN;
	struct sockaddr_storage sockadd;
	struct sockaddr *sp;
	char *p = 0;
	int i;
	socklen_t socklen;
	int flags;
	
	// Cache it once, return it forever.
	if (my_network_hostname)
		return my_network_hostname;

	// Get "my address" on some socket.
	sp = (struct sockaddr *)&sockadd;
	socklen = sizeof (sockadd);
	i = getsockname (sockets[0].fd, sp, (socklen_t *)&socklen);

	p = new char[plen];
	p[0] = 'x'; p[1] = '\0';	// In case of failure
	flags = NI_DGRAM|NI_NUMERICHOST|NI_NUMERICSERV;
	i = getnameinfo(sp, socklen, p, plen, (char *)0, 0, flags);
	if (i != 0) {
		LOG(ERROR) << "myhostname() can't find our name: error "
		     << gai_strerror(i);
	}

	my_network_hostname = p;
	return my_network_hostname;
}

/* Make printable IP address */
std::string
SMnet::string_addr (struct addrinfo *myaddr, bool withport)
{
	return string_addr (myaddr->ai_addr, myaddr->ai_addrlen, withport);
}

/* Make printable IP address */
std::string
SMnet::string_addr (struct sockaddr *sa, socklen_t len, bool withport)
{
	size_t plen = NI_MAXHOST;
	size_t portlen = NI_MAXSERV;
	char *p = 0, *port = 0;
	int i;
	string ret;
	int flags;

	p = new char[plen];
	p[0] = '0'; p[1] = '\0';	// In case of failure
	if (withport) {
		port = new char[portlen];
		port[0]='0'; port[1] = '\0';
	}
	if (len != 0) {
		flags = NI_DGRAM|NI_NUMERICHOST|NI_NUMERICSERV;
		i = getnameinfo(sa, len, p, plen, port, portlen, flags);
		if (i != 0) {
			LOG(ERROR) << "string_addr() can't print IP address: error "
			     << gai_strerror(i);
			if (0 == inet_ntop(sa->sa_family, sa->sa_data, p, plen)) {
				LOG(ERROR) << "string_addr() can't print IP address: inet_ntop error "
				     << strerror(errno);
			}
			//FIXME snprintf(port, portlen, "%d", sa->port);
		}
	}

	if (withport) 
		ret = string(p) + ":" + string(port);
	else
		ret = string(p);

	delete [] p;
	delete [] port;
	return ret;
}

/* Parse printable IP address and port */
bool
SMnet::parse_addr (const char *str, char *sockad, socklen_t maxlen, socklen_t *len)
{
	const char *p = strchr(str, ':');
	char *host;
	const char *port;

	if (p == NULL)
		return false;

	// Empty address
	if (!strcmp("0:0", str)) {
		*len = 0;
		return true;
	}

	host = new char[1 + p - str];
	strncpy(host, str, p-str);
	host[p-str] = '\0';
	port = p+1;

	struct addrinfo myhints;
	struct addrinfo *myaddrs, *ap;
	int s;

	memset(&myhints, 0, sizeof(myhints));
	myhints.ai_family = AF_UNSPEC;		// Any address family eg v4/6
	myhints.ai_socktype = SOCK_DGRAM;	// Datagrams for now FIXME

	s = getaddrinfo(host, port, &myhints, &myaddrs);
	if (s != 0) {
		LOG(ERROR) << "parse_addr() can't lookup addr/port: " 
		     << host << ":" << port << ", error " << s;
		return false;
	}
	
	int seenone = false;
	// Now pick some working address we got back.
	for (ap = myaddrs; ap != NULL; ap = ap->ai_next) {
		if (ap->ai_addrlen > maxlen)
			continue;
		if (ap->ai_socktype != SOCK_DGRAM)
			continue;

		if (seenone) return false;
		seenone = true;

		// We found it!
		memcpy(sockad, ap->ai_addr, ap->ai_addrlen);
		*len = ap->ai_addrlen;
	}
	freeaddrinfo(myaddrs);		// Don't leak memory.
	return seenone;
}
		

/*
 * Return a different random integer each time we are called.
 * They are all guaranteed to be positive numbers.
 */
unsigned int
SMnet::new_random_number()
{
#define	RAND_DEVICE	"/dev/urandom"
	union {
		char randbuf[4];
		int randnum;
	} randy;
	static int fallback;
	int i;

	if (random_fd <= 0) {
		random_fd = open (RAND_DEVICE, O_RDONLY);
		if (random_fd < 0) {
			LOG(ERROR) << "Can't open " << RAND_DEVICE;
		}
	}

	do {
		if (random_fd > 0) {
			i = read(random_fd, &randy.randbuf, sizeof(randy.randbuf));
			if (i != sizeof(randy.randbuf)) {
				LOG(ERROR) << "Can't read from " << RAND_DEVICE;
				randy.randnum = ++fallback;
			}
		} else {
			randy.randnum = ++fallback;
		}
		
		if (randy.randnum < 0)
			randy.randnum = -randy.randnum;

	// Avoid passing either 0 or -infinity to a64l
	// (MacOS is particularly finicky about that, but it really is undef.)
	} while (randy.randnum <= 0);
	
	return (unsigned int)randy.randnum;
}

/*
 * Return a different random string (a "call number" for a Call-ID in
 * SIP, RFC 3261) each time we are called.  That string is good until the
 * next call, but may need to be copied if needed beyond that.
 */
char *
SMnet::new_call_number()
{
	char *p;
	long randnum;

	randnum = new_random_number();
	
	p = l64a (randnum);
	if (!random_string) {
		random_string = new char[6+1];
	}
	strncpy(random_string, p, 6);
	random_string[6] = '\0';
	return random_string;
}


} // namespace SMlistener
