//
//   Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012
//   Free Software Foundation, Inc
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
//xs

#ifdef HAVE_CONFIG_H
#include "gnashconfig.h"
#endif

#include <mutex>
#include <vector>

#include "utility.h"
#include "log.h"
#include "network.h"

#include <sys/types.h>
#include <cstring>
#include <cstdio>
#include <cerrno>
#include <fcntl.h>
#if defined(HAVE_WINSOCK_H) && !defined(__OS2__)
# include <winsock2.h>
# include <windows.h>
# include <sys/stat.h>
# include <io.h>
# include <ws2tcpip.h>
#else
# include <sys/ioctl.h>
# include <sys/time.h>
# include <unistd.h>
# include <sys/select.h>
# include <netinet/in.h>
# include <arpa/inet.h>
# include <sys/socket.h>
# include <sys/un.h>
# include <netdb.h>
# include <sys/param.h>
# include <sys/select.h>
#include <csignal>

// This is for non-standard signal functions such as sigemptyset.
#include <signal.h>

#ifdef HAVE_POLL_H
# include <poll.h>
#else 
# ifdef HAVE_EPOLL_H
#  include <epoll.h>
# endif
#endif
#endif

#include "buffer.h"
#include "GnashException.h"

#ifndef MAXHOSTNAMELEN
#define MAXHOSTNAMELEN 256
#endif

#ifndef FIONREAD
#define FIONREAD 0
#endif

using std::string;
using std::vector;

/// \namespace gnash
///	This is the main namespace for Gnash and it's libraries.
namespace gnash {

static const short DEFAULTPORT  = RTMP_PORT;

#ifndef INADDR_NONE
#define INADDR_NONE  0xffffffff
#endif

static void cntrlc_handler(int sig);
// this is set when we get a signal during a pselect() or ppoll()
static int  sig_number = 0;

Network::Network()
	:
	_ipaddr(INADDR_ANY),
	_sockfd(0),
	_listenfd(0),
	_port(0),
	_connected(false),
	_debug(true),
	_timeout(0)
{
//    GNASH_REPORT_FUNCTION;
#if defined(HAVE_WINSOCK_H) && !defined(__OS2__)
    WORD wVersionRequested;
    WSADATA wsaData;
    wVersionRequested = MAKEWORD(1, 1);		// Windows Sockets 1.1
    if (WSAStartup( wVersionRequested, &wsaData ) != 0) {
        log_error(_("Could not find a usable WinSock DLL"));
        exit(EXIT_FAILURE);
    }
#endif

}

Network::~Network()
{
//    GNASH_REPORT_FUNCTION;
#if defined(HAVE_WINSOCK_H) && !defined(__OS2__)
    WSACleanup();
#else
    closeNet();
#endif
}

// Description: Create a tcp/ip network server. This creates a server
//              that listens for incoming socket connections. This
//              supports IP aliasing on the host, and will sequntially
//              look for IP address to bind this port to.
int
Network::createServer(void)
{
//    GNASH_REPORT_FUNCTION;

    short port;

    if (_port) {
	port = _port;
    } else {
	port = DEFAULTPORT;
    }
    return createServer(port);
}

int
Network::createServer(short port)
{
//    GNASH_REPORT_FUNCTION;
    return createServer("localhost.localdomain", port);
}

int
Network::createServer(std::string hostname, short port)
{
//    GNASH_REPORT_FUNCTION;

    int             on;
    int             retries = 0;

    if (_listenfd >= 2) {
	log_debug(_("already connected to port %hd"), port);
	return _listenfd;
    }

    int code = 0;
    struct addrinfo req, *ans;
    std::memset(&req, 0, sizeof(struct addrinfo));
    // For wildcard IP address
    req.ai_flags    = AI_PASSIVE;
    req.ai_family   = AF_UNSPEC; // Allow IPv4 or IPv6
    req.ai_socktype = SOCK_STREAM;
    req.ai_protocol = 0;        // default is tcp

    std::stringstream portstr;
    portstr << port;
    if ((code = getaddrinfo(hostname.c_str(), portstr.str().c_str(),
                            &req, &ans)) != 0) {
        log_error(_("getaddrinfo() failed with code: #%d - %s\n"),
                  code, gai_strerror(code));
        freeaddrinfo(ans);          // free the response data
        return false;
    }

    // display all the IP numbers
    struct addrinfo *ot = ans;
    while (ot) {
        char clienthost   [NI_MAXHOST];
        std::memset(&clienthost, 0, NI_MAXHOST);
        char clientservice[NI_MAXSERV];
        std::memset(&clientservice, 0, NI_MAXSERV);
        getnameinfo(ot->ai_addr, ot->ai_addrlen,
                    clienthost, sizeof(clienthost),
                    clientservice, sizeof(clientservice),
                    NI_NUMERICHOST);
        
        std::shared_ptr<char> straddr = getIPString(ot);
        
        if (ot->ai_family == AF_INET6) {
            log_debug("%s has IPV6 address of: %s", hostname, straddr.get());
        } else if (ot->ai_family == AF_INET) {
            log_debug("%s has IPV4 address of: %s", hostname, straddr.get());
        } else {
            log_error("%s has no IP address!", hostname);
        }
        
        ot = ot->ai_next;
    }

    struct addrinfo *it = ans;
    while (it) {
        // Get a file descriptor for this socket connection
        _listenfd = socket(it->ai_family, it->ai_socktype, it->ai_protocol);
        
        if (_listenfd < 0) {
            log_debug("unable to create socket: %s"), strerror(errno);
            // Try the next IP number
            it = it->ai_next;
        } else {
            std::shared_ptr<char> straddr = getIPString(it);
            log_debug("Socket created for %s", straddr); 
            break;
        }
    }

    // error, wasn't able to create a socket
    if (it == nullptr) {
        log_error(_("unable to create socket: %s"), strerror(errno));
        return -1;
    }
    
    on = 1;
    if (setsockopt(_listenfd, SOL_SOCKET, SO_REUSEADDR,
                   (char *)&on, sizeof(on)) < 0) {
        log_error(_("setsockopt SO_REUSEADDR failed"));
        freeaddrinfo(ans);          // free the response data
        return -1;
    }
    
    retries = 0;
    while (retries < 5) {
        if (bind(_listenfd, it->ai_addr, it->ai_addrlen)) {
            log_error(_("unable to bind to port %hd: %s"),
                    port, strerror(errno));
//                    inet_ntoa(sock_in.sin_addr), strerror(errno));
            retries++;
        }
        
        if (listen(_listenfd, 5) < 0) {
            log_error(_("unable to listen on port: %hd: %s "),
                port, strerror(errno));
            break;
        }
        
	// We have a socket created
        _port = port;
        
        return _listenfd;
    }
    
    freeaddrinfo(ans);          // free the response data
    return -1;
}

// Description: Accept a new network connection for the port we have
//              created a server for.
// The default is to block.
int
Network::newConnection(void)
{
//    GNASH_REPORT_FUNCTION;

    return newConnection(true, _listenfd);
}

int
Network::newConnection(int fd)
{
//    GNASH_REPORT_FUNCTION;

    return newConnection(true, fd);
}

int
Network::newConnection(bool block)
{
//    GNASH_REPORT_FUNCTION;

    return newConnection(block, _listenfd);
}

int
Network::newConnection(bool block, int fd)
{
//    GNASH_REPORT_FUNCTION;

    struct sockaddr	newfsin;
    socklen_t		alen;
    int			ret;
    fd_set                fdset;
    int                   retries = 3;

    alen = sizeof(struct sockaddr_in);

    if (fd <= 2) {
        return -1;
    }
    if (_debug) {
	log_debug(_("Waiting to accept net traffic on fd #%d for port %d"), fd, _port);
    }

#ifdef HAVE_PSELECT
	struct timespec tval;
	sigset_t sigset, blockset, pending;
	sigemptyset(&blockset);        
//        sigaddset(&blockset, SIGINT); /* Block SIGINT */
        sigaddset(&blockset, SIGPIPE); /* Block SIGPIPE */
	sigprocmask(SIG_BLOCK, &blockset, &sigset);
#else
	struct timeval tval;
#endif
	
    while (retries--) {
        // We use select to wait for the read file descriptor to be
        // active, which means there is a client waiting to connect.
        FD_ZERO(&fdset);
        // also return on any input from stdin
//         if (_console) {
//             FD_SET(fileno(stdin), &fdset);
//         }
        FD_SET(fd, &fdset);

        // Reset the timeout value, since select modifies it on return. To
        // block, set the timeout to zero.
#ifdef HAVE_PSELECT
	tval.tv_sec = _timeout;
	tval.tv_nsec = 0;
        if (block) {
	    ret = pselect(fd+1, &fdset, nullptr, nullptr, nullptr, &blockset);
	} else {
	    ret = pselect(fd+1, &fdset, nullptr, nullptr, &tval, &blockset);
	}
	if (sig_number) {
	    log_debug("Have a SIGINT interrupt waiting!");
	}
	sigpending(&pending);
	if (sigismember(&pending, SIGINT)) {
	    log_debug("Have a pending SIGINT interrupt waiting!");
	    int sig;
	    sigwait(&blockset, &sig);
	}
	if (sigismember(&pending, SIGPIPE)) {
	    log_debug("Have a pending SIGPIPE interrupt waiting!");
	    int sig;
	    sigwait(&blockset, &sig);
	}
#else
        tval.tv_sec = 1;
        tval.tv_usec = 0;
        if (block) {
            ret = select(fd+1, &fdset, NULL, NULL, NULL);
        } else {
            ret = select(fd+1, &fdset, NULL, NULL, &tval);
        }
#endif

        if (FD_ISSET(0, &fdset)) {
	    if (_debug) {
		log_debug(_("There is a new network connection request."));
	    }
            return 1;
        }
	
        // If interrupted by a system call, try again
        if (ret == -1 && errno == EINTR) {
            log_debug(_("The accept() socket for fd #%d was interrupted by a system call"), fd);
        }

        if (ret == -1) {
            log_debug("The accept() socket for fd #%d never was available", fd);
            return -1;
        }

        if (ret == 0) {
            if (_debug) {
                log_debug("The accept() socket for fd #%d timed out"
                    " waitingfor data", fd);
		return 0;
            }
        }
	
    }

#ifndef HAVE_WINSOCK_H
    fcntl(_listenfd, F_SETFL, O_NONBLOCK); // Don't let accept() block
#endif
    _sockfd = accept(fd, &newfsin, &alen);

    if (_sockfd < 0) {
        log_error(_("unable to accept: %s"), strerror(errno));
        return -1;
    }

    if (_debug) {
	log_debug(_("Accepting TCP/IP connection on fd #%d for port %d"), _sockfd, _port);
    }

    return _sockfd;
}

#if defined(_WIN32) || defined(__amigaos4__)
/* from sys/socket.h */
typedef unsigned short      sa_family_t;

/* from sys/un.h */
#define UNIX_PATH_MAX   108

struct sockaddr_un {
    sa_family_t sun_family; /* AF_UNIX */
    char sun_path[UNIX_PATH_MAX];   /* pathname */
};

#endif /* _WIN32 */

// Connect to a named pipe
bool
Network::connectSocket(const string &sockname)
{
//    GNASH_REPORT_FUNCTION;

    struct sockaddr_un  addr;
    fd_set              fdset;
    struct timeval      tval;
    int                 ret;
    int                 retries;

    addr.sun_family = AF_UNIX;
    // socket names must be 108 bytes or less as specifiec in sys/un.h.
    strncpy(addr.sun_path, sockname.c_str(), 100);

    _sockfd = ::socket(AF_UNIX, SOCK_STREAM, 0);
    if (_sockfd < 0) {
	log_error(_("unable to create socket: %s"), strerror(errno));
	_sockfd = -1;
	return false;
    }
    
    retries = 2;
    while (retries-- > 0) {
        // We use select to wait for the read file descriptor to be
        // active, which means there is a client waiting to connect.
        FD_ZERO(&fdset);
        FD_SET(_sockfd, &fdset);

        // Reset the timeout value, since select modifies it on return. To
        // block, set the timeout to zero.
        tval.tv_sec = 5;
        tval.tv_usec = 0;

        ret = ::select(_sockfd+1, &fdset, nullptr, nullptr, &tval);

        // If interrupted by a system call, try again
        if (ret == -1 && errno == EINTR) {
	    log_debug(_("The connect() socket for fd %d was interrupted by a system call"),
		      _sockfd);
	    continue;
	}

        if (ret == -1) {
	    log_debug(_("The connect() socket for fd %d never was available for writing"),
		      _sockfd);
#ifdef HAVE_WINSOCK_H
	    ::shutdown(_sockfd, 0); // FIXME: was SHUT_BOTH
#else
	    ::shutdown(_sockfd, SHUT_RDWR);
#endif
	    _sockfd = -1;
	    return false;
	}
        if (ret == 0) {
            log_error(_("The connect() socket for fd %d timed out waiting to write"),
                      _sockfd);
            continue;
        }

        if (ret > 0) {
            ret = ::connect(_sockfd, reinterpret_cast<struct sockaddr *>(&addr), sizeof(addr));
            if (ret == 0) {
                log_debug(_("\tsocket name %s for fd %d"), sockname, _sockfd);
                _connected = true;
                assert(_sockfd > 0);
                return true;
            }
            if (ret == -1) {
                log_error(_("The connect() socket for fd %d never was available for writing"),
                        _sockfd);
                _sockfd = -1;
                assert(!_connected);
                return false;
            }
        }
    }
    

#ifndef HAVE_WINSOCK_H
    fcntl(_sockfd, F_SETFL, O_NONBLOCK);
#endif

    _connected = true;
    assert(_sockfd > 0);
    return true;    
}

// Create a client connection to a tcp/ip based service
bool
Network::createClient(void)
{
//    GNASH_REPORT_FUNCTION;

    short port;

    if (_port) {
	port = _port;
    } else {
	port = RTMP_PORT;
    }
    return createClient("localhost", port);
}
bool
Network::createClient(short /* port */)
{
//    GNASH_REPORT_FUNCTION;

    return false;
}

bool
Network::createClient(const string &hostname)
{
//    GNASH_REPORT_FUNCTION;

    short port;

    if (_port) {
	port = _port;
    } else {
	port = RTMP_PORT;
    }
    return createClient(hostname, port);
}

bool
Network::createClient(const string &hostname, short port)
{
//    GNASH_REPORT_FUNCTION;

    fd_set              fdset;
    struct timeval      tval;
    int                 ret;
    int                 retries;

//    assert( ! connected() );
    if (connected()) {
        return true;
    }

    _port = port;    
    log_debug(_("%s: to host %s at port %d"), __FUNCTION__, hostname, port);

    int code = 0;
    struct addrinfo req, *ans;
    std::memset(&req, 0, sizeof(struct addrinfo));
    req.ai_family = AF_UNSPEC;  // Allow IPv4 or IPv6
    req.ai_socktype = SOCK_STREAM;
    
    if ((code = getaddrinfo(hostname.c_str(), nullptr, &req, &ans)) != 0) {
        log_error(_("getaddrinfo() failed with code: #%d - %s\n"),
                  code, gai_strerror(code));
        return false;
    }
    
    // display all the IP numbers
    struct addrinfo *ot = ans;
    while (ot) {
        // // We only want the SOCK_STREAM type
        // if (ot->ai_socktype == SOCK_DGRAM) {
        //     // log_debug("SockType is SOCK_DGRAM");
        //     ot = ot->ai_next;
        //     continue;
        // }
        char clienthost   [NI_MAXHOST];
        std::memset(&clienthost, 0, NI_MAXHOST);
        char clientservice[NI_MAXSERV];
        std::memset(&clientservice, 0, NI_MAXSERV);
        getnameinfo(ot->ai_addr, ot->ai_addrlen,
                    clienthost, sizeof(clienthost),
                    clientservice, sizeof(clientservice),
                    NI_NUMERICHOST);
        
        std::shared_ptr<char> straddr = getIPString(ot);
        
        if (ot->ai_family == AF_INET6) {
            log_debug("%s has IPV6 address of: %s", hostname, straddr.get());
        } else if (ot->ai_family == AF_INET) {
            log_debug("%s has IPV4 address of: %s", hostname, straddr.get());
        } else {
            log_error("%s has no IP address!", hostname);
        }
        
        ot = ot->ai_next;
    }

    // Multiple IPV$ and IPV6 numbers may be returned, so we try them all if
    // required
    struct addrinfo *it = ans;
    while (it) {
        _sockfd = ::socket(it->ai_family, it->ai_socktype, it->ai_protocol);
        if (_sockfd < 0) {
            const int err = errno;
            log_error(_("Socket creation failed: %s"), std::strerror(err));
            _sockfd = 0;
            // Try the next IP number
            it = it->ai_next;
        } else {
            break;
        }
    }
    
    // cache the data we need later
    struct sockaddr_in6 *addr6 = reinterpret_cast<struct sockaddr_in6 *>(it->ai_addr);
    // When NULL is passed to getaddrinfo(), the port isn't set in
    // the returned data, so we do it here.
    addr6->sin6_port = htons(port);
    // This is used for ::connect()
    struct sockaddr *saddr = it->ai_addr;

    const int addrlen = it->ai_addrlen;
    std::shared_ptr<char> straddr = getIPString(it);

    freeaddrinfo(ans);          // free the response data
    
    retries = 2;
    while (retries-- > 0) {
        // We use select to wait for the read file descriptor to be
        // active, which means there is a client waiting to connect.
        FD_ZERO(&fdset);
        FD_SET(_sockfd, &fdset);

        // Reset the timeout value, since select modifies it on return. To
        // block, set the timeout to zero.
        tval.tv_sec = 5;
        tval.tv_usec = 0;

        ret = ::select(_sockfd+1, &fdset, nullptr, nullptr, &tval);

        // If interrupted by a system call, try again
        if (ret == -1 && errno == EINTR) {
            log_debug(_("The connect() socket for fd %d was interrupted "
                        "by a system call"), _sockfd);
            continue;
        }

        if (ret == -1) {
            log_debug(_("The connect() socket for fd %d never was "
                        "available for writing"), _sockfd);
#ifdef HAVE_WINSOCK_H
            ::shutdown(_sockfd, 0); // FIXME: was SHUT_BOTH
#else
            ::shutdown(_sockfd, SHUT_RDWR);
#endif
	    ::close(_sockfd);
            _sockfd = -1;
            return false;
        }

        if (ret == 0) {
#ifdef HAVE_WINSOCK_H
            ::shutdown(_sockfd, 0); // FIXME: was SHUT_BOTH
#else
            ::shutdown(_sockfd, SHUT_RDWR);
#endif
            log_error(_("The connect() socket for fd %d timed out waiting "
                        "to write"), _sockfd);
	    ::close(_sockfd);
            continue;
        }

        if (ret > 0) {
            ret = ::connect(_sockfd, saddr, addrlen);

            if (ret == 0) {
                log_debug(_("\tport %d at IP %s for fd %d"), port,
                          straddr, _sockfd);
                _connected = true;
                assert(_sockfd > 0);
                return true;
            }
            if (ret == -1) {
                log_error(_("The connect() socket for fd %d never was "
                            "available for writing"), _sockfd);
#ifdef HAVE_WINSOCK_H
		::shutdown(_sockfd, 0); // FIXME: was SHUT_BOTH
#else
		::shutdown(_sockfd, SHUT_RDWR);
#endif
		::close(_sockfd);
                _sockfd = -1;
                assert(!_connected);
                return false;
            }
        }
    }
    // printf("\tConnected at port %d on IP %s for fd #%d", port,
    //        ::inet_ntoa(sock_in.sin_addr), _sockfd);

#ifndef HAVE_WINSOCK_H
    fcntl(_sockfd, F_SETFL, O_NONBLOCK);
#endif

    _connected = true;
    _port = port;
    assert(_sockfd > 0);

    return true;
}

bool
Network::closeNet()
{
//    GNASH_REPORT_FUNCTION;

    if ((_sockfd > 0) && (_connected)) {
        closeNet(_sockfd);
        _sockfd = 0;
        _connected = false;
    }

    return false;
}

bool
Network::closeNet(int sockfd)
{
//    GNASH_REPORT_FUNCTION;

    int retries = 0;

    // If we can't close the socket, other processes must be
    // locked on it, so we wait a second, and try again. After a
    // few tries, we give up, cause there must be something
    // wrong.

    if (sockfd <= 0) {
        return true;
    }

    while (retries < 3) {
        if (sockfd) {
            // Shutdown the socket connection
#if 0
            if (shutdown(sockfd, SHUT_RDWR) < 0) {
                if (errno != ENOTCONN) {
                    cerr << "WARNING: Unable to shutdown socket for fd #"
                         << sockfd << strerror(errno) << endl;
                } else {
                    cerr << "The socket using fd #" << sockfd
                         << " has been shut down successfully." << endl;
                    return true;
                }
            }
#endif
            if (::close(sockfd) < 0) {
		// If we have a bad file descriptor, it's because
		// this got closed already, usually by another
		// thread being paranoid.
		if (errno != EBADF) {
		    log_error(_("Unable to close the socket for fd #%d: %s"),
			      sockfd, strerror(errno));
		}
#ifndef HAVE_WINSOCK_H
                sleep(1);
#endif
                retries++;
            } else {
		log_debug(_("Closed the socket on fd #%d"), sockfd);
                return true;
            }
        }
    }
    return false;
}
// Description: Close an open socket connection.
bool
Network::closeConnection(void)
{
//    GNASH_REPORT_FUNCTION;

    closeConnection(_sockfd);
    _sockfd = 0;
    closeConnection(_listenfd);
    _listenfd = 0;
    _connected = false;

    return false;
}

bool
Network::closeConnection(int fd)
{
//    GNASH_REPORT_FUNCTION;

    if (fd > 0) {
        ::close(fd);
	log_debug(_("%s: Closed fd #%d"), __FUNCTION__, fd);
//        closeNet(fd);
    }

    return false;
}

std::shared_ptr<cygnal::Buffer>
Network::readNet()
{
//    GNASH_REPORT_FUNCTION;
    std::shared_ptr<cygnal::Buffer> buffer(new cygnal::Buffer);
    int ret = readNet(*buffer);
    if (ret > 0) {
	buffer->resize(ret);
    }
    return buffer;
}

// Read from the connection
int
Network::readNet(int fd, cygnal::Buffer &buffer)
{
//    GNASH_REPORT_FUNCTION;
    int ret = readNet(fd, buffer.reference(), buffer.size(), _timeout);
    if (ret > 0) {
	buffer.resize(ret);
    }

    return ret;
}

int
Network::readNet(int fd, cygnal::Buffer *buffer)
{
//    GNASH_REPORT_FUNCTION;
    int ret = readNet(fd, buffer->reference(), buffer->size(), _timeout);
    if (ret > 0) {
	buffer->setSeekPointer(buffer->reference() + ret);
    }

    return ret;
}

int
Network::readNet(cygnal::Buffer &buffer)
{
//    GNASH_REPORT_FUNCTION;
    int ret = readNet(_sockfd, buffer, _timeout);

    return ret;
}

int
Network::readNet(cygnal::Buffer &buffer, int timeout)
{
//    GNASH_REPORT_FUNCTION;
    int ret = readNet(_sockfd, buffer.reference(), buffer.size(), timeout);
    if (ret > 0) {
	buffer.resize(ret);	// FIXME: why does this corrupt
    }

    return ret;
}

int
Network::readNet(int fd, cygnal::Buffer &buffer, int timeout)
{
    // GNASH_REPORT_FUNCTION;
    int ret = readNet(fd, buffer.reference(), buffer.size(), timeout);
    buffer.setSeekPointer(ret);
#if 0
    if (ret > 0) {
	buffer.resize(ret);	// FIXME: why does this corrupt
    }
#endif
    
    return ret;
}

int
Network::readNet(byte_t *data, int nbytes)
{
//    GNASH_REPORT_FUNCTION;
    return readNet(_sockfd, data, nbytes, _timeout);
}

int
Network::readNet(byte_t *data, int nbytes, int timeout)
{
//    GNASH_REPORT_FUNCTION;
    return readNet(_sockfd, data, nbytes, timeout);
}

int
Network::readNet(int fd, byte_t *data, int nbytes)
{
//    GNASH_REPORT_FUNCTION;
    return readNet(fd, data, nbytes, _timeout);
}

int
Network::readNet(int fd, byte_t *buffer, int nbytes, int timeout)
{
//     GNASH_REPORT_FUNCTION;

    fd_set              fdset;
    int                 ret = -1;

//     std::lock_guard<std::mutex> lock(_net_mutex);

    if (_debug) {
	log_debug(_("Trying to read %d bytes from fd #%d"), nbytes, fd);
    }
#ifdef NET_TIMING
    if (_timing_debug)
    {
        gettimeofday(&tp, NULL);
        read_start_time = static_cast<double>(tp.tv_sec)
            + static_cast<double>(tp.tv_usec*1e-6);
    }
#endif
    if (fd > 2) {
        FD_ZERO(&fdset);
        FD_SET(fd, &fdset);

#ifdef HAVE_PSELECT
	struct timespec tval;
	sigset_t pending, blockset;
	sigemptyset(&blockset);        
        // sigaddset(&blockset, SIGINT); /* Block SIGINT */
//        sigaddset(&blockset, SIGPIPE); /* Block SIGPIPE */
        sigprocmask(SIG_BLOCK, &blockset, nullptr);

	// Trap ^C (SIGINT) so we can kill all the threads
// 	struct sigaction  act;
// 	act.sa_handler = cntrlc_handler;
// 	act.sa_flags = 0;
// 	sigemptyset(&act.sa_mask);
// 	sigaction (SIGINT, &act, NULL);
#else
	struct timeval tval;
#endif
        if (timeout == 0) {
#ifdef HAVE_PSELECT
	    ret = pselect(fd+1, &fdset, nullptr, nullptr, nullptr, &blockset);
#else
	    ret = select(fd+1, &fdset, NULL, NULL, NULL);
#endif
	} else {	
#ifdef HAVE_PSELECT
	    tval.tv_sec = timeout;
	    tval.tv_nsec = 0;
	    ret = pselect(fd+1, &fdset, nullptr, nullptr, &tval, &blockset);
	    sigpending(&pending);
	    if (sigismember(&pending, SIGINT)) {
		log_debug("Have a pending SIGINT interrupt waiting!");
		int sig;
		sigwait(&blockset, &sig);
		cntrlc_handler(SIGINT);
	    }
	    if (sigismember(&pending, SIGPIPE)) {
		log_debug("Have a pending SIGPIPE interrupt waiting!");
		int sig;
		sigwait(&blockset, &sig);
		cntrlc_handler(SIGINT);
	    }
#else
	    tval.tv_sec = timeout;
	    tval.tv_usec = 0;
	    ret = select(fd+1, &fdset, NULL, NULL, &tval);
#endif
	}

        // If interrupted by a system call, try again
        if (ret == -1 && errno == EINTR) {
            log_error(_("The socket for fd #%d was interrupted by a system call"), fd);
        }

        if (ret == -1) {
            log_error(_("The socket for fd #%d was never available for reading"), fd);
            return -1;
        }

        if (ret == 0) {
	    if (_debug) {
		log_debug(_("The socket for #fd %d timed out waiting to read"), fd);
	    }
            return 0;
        }

#ifdef USE_SSL
	if (_ssl) {
	    ret = _ssl->sslRead(buffer, nbytes);
	} else {
	    ret = read(fd, buffer, nbytes);
	}
#else
	ret = read(fd, buffer, nbytes);
#endif

	// If we read zero bytes, the network may be closed, as we returned from the select()
        if (ret == -1) {
            log_error(_("The socket for fd #%d was never available for reading data"), fd);
            return -1;
        }

        if (ret == 0) {
	    if (_debug) {
		log_debug (_("The socket for #fd %d timed out waiting to read data"), fd);
	    }
            return 0;
        }
	
	if (_debug) {
	    log_debug (_("read %d bytes from fd #%d from port %d"), ret, fd, _port);
	}
#if 0
	if (ret) {
	    log_debug (_("%s: Read packet data from fd #%d (%d bytes): \n%s"),
		       __FUNCTION__, fd, ret, hexify(buffer, ret, true));
	}
#endif    
    }

    return ret;

}

// Write to the connection
int
Network::writeNet(cygnal::Buffer *buffer)
{
//     GNASH_REPORT_FUNCTION;
    return writeNet(buffer->reference(), buffer->allocated());
}

int
Network::writeNet(int fd, cygnal::Buffer *buffer)
{
//     GNASH_REPORT_FUNCTION;
    return writeNet(fd, buffer->reference(), buffer->allocated());
}

// Write to the connection
int
Network::writeNet(cygnal::Buffer &buffer)
{
//     GNASH_REPORT_FUNCTION;
    return writeNet(buffer.reference(), buffer.allocated());
}

// Write to the connection
int
Network::writeNet(int fd, cygnal::Buffer &buffer)
{
//     GNASH_REPORT_FUNCTION;
    return writeNet(fd, buffer.reference(), buffer.allocated());
}

int
Network::writeNet(const std::string& data)
{
//     GNASH_REPORT_FUNCTION;
    return writeNet(reinterpret_cast<const byte_t *>(data.c_str()), data.size());
}

int
Network::writeNet(const byte_t *data, int nbytes)
{
//     GNASH_REPORT_FUNCTION;
    return writeNet(_sockfd, data, nbytes, _timeout);
}

// int
// Network::writeNet(const byte_t *buffer, int nbytes)
// {
//     return writeNet(_sockfd, buffer, nbytes, _timeout);
// }

// int
// Network::writeNet(int fd, const byte_t *buffer)
// {
//     return writeNet(fd, buffer, strlen(buffer), _timeout);
// }

int
Network::writeNet(int fd, const byte_t *data, int nbytes)
{
//     GNASH_REPORT_FUNCTION;
    return writeNet(fd, data, nbytes, _timeout);
}

int
Network::writeNet(int fd, const byte_t *buffer, int nbytes, int timeout)
{
//     GNASH_REPORT_FUNCTION;

    fd_set              fdset;
    int                 ret = -1;

    std::lock_guard<std::mutex> lock(_net_mutex);
    
    // We need a writable, and not const point for byte arithmetic.
    byte_t *bufptr = const_cast<byte_t *>(buffer);

#ifdef NET_TIMING
    // If we are debugging the tcp/ip timings, get the initial time.
    if (_timing_debug)
    {
        gettimeofday(&starttime, 0);
    }
#endif
    if (fd > 2) {
        FD_ZERO(&fdset);
        FD_SET(fd, &fdset);

#ifdef HAVE_PSELECT
	struct timespec tval;
	sigset_t pending, blockset; //, emptyset;
	sigemptyset(&blockset);        
        // sigaddset(&blockset, SIGINT); /* Block SIGINT */
        sigaddset(&blockset, SIGPIPE);
        sigprocmask(SIG_BLOCK, &blockset, nullptr);
#else
	struct timeval tval;
#endif
        // Reset the timeout value, since select modifies it on return
        if (timeout <= 0) {
            timeout = 5;
        }
#ifdef HAVE_PSELECT
	tval.tv_sec = timeout;
	tval.tv_nsec = 0;
	ret = pselect(fd+1, nullptr, &fdset, nullptr, &tval, &blockset);
	sigpending(&pending);
	if (sigismember(&pending, SIGINT)) {
	    log_debug("Have a pending SIGINT interrupt waiting!");
	    int sig;
	    sigwait(&blockset, &sig);
	    cntrlc_handler(SIGINT);
	}
#else
	tval.tv_sec = timeout;
        tval.tv_usec = 0;
        ret = select(fd+1, NULL, &fdset, NULL, &tval);
#endif
	
        // If interrupted by a system call, try again
        if (ret == -1 && errno == EINTR) {
            log_error (_("The socket for fd #%d was interrupted by a system call"), fd);
        }

        if (ret == -1) {
            log_error (_("The socket for fd #%d was never available for writing"), fd);
        }

        if (ret == 0) {
            log_debug (_("The socket for fd #%d timed out waiting to write"), fd);
	    return 0;
        }

#ifdef USE_SSL
	if (_ssl) {
	    ret = _ssl->sslWrite(buffer, nbytes);
	} else {
	    ret = write(fd, bufptr, nbytes);
	}
#else
	ret = write(fd, bufptr, nbytes);
#endif
        if (ret == 0) {
            log_error (_("Wrote zero out of %d bytes to fd #%d: %s"), 
		nbytes, fd, strerror(errno));
            return ret;
        }
        if (ret < 0) {
            log_error (_("Couldn't write %d bytes to fd #%d: %s"), 
		nbytes, fd, strerror(errno));
            return ret;
        }
        if (ret > 0) {
            bufptr += ret;
            if (ret != nbytes) {
		if (_debug) {
		    log_debug (_("wrote %d bytes to fd #%d, expected %d"),
			       ret, fd, nbytes);
		}
            } else {
		if (_debug) {
		    log_debug (_("wrote %d bytes to fd #%d for port %d"),
			       ret, fd, _port);
		}
//                return ret;
            }
        }
#if 0
	if (ret) {
	    log_debug (_("%s: Wrote packet data to fd #%d: \n%s"),
		       __FUNCTION__, fd, hexify(buffer, ret, true));
	}
#endif    
    }

#ifdef NET_TIMING
    if (_timing_debug)
    {
        gettimeofday(&endtime, 0);

        if ((endtime.tv_sec - starttime.tv_sec) &&
            endtime.tv_usec - starttime.tv_usec)
        {
            log_debug (_("took %d usec to write (%d bytes)"),
		endtime.tv_usec - starttime.tv_usec, bytes_written);
        }
    }
#endif


    return ret;
}

void
Network::addPollFD(struct pollfd &fd, Network::entry_t *func)
{
//    GNASH_REPORT_FUNCTION;

    log_debug(_("%s: adding fd #%d to pollfds"), __PRETTY_FUNCTION__, fd.fd);
    std::lock_guard<std::mutex> lock(_poll_mutex);
    _handlers[fd.fd] = func;
     _pollfds.push_back(fd);
//     notify();
}

void
Network::addPollFD(struct pollfd &fd)
{
//    GNASH_REPORT_FUNCTION;
    log_debug(_("%s: adding fd #%d to pollfds"), __PRETTY_FUNCTION__, fd.fd);
    std::lock_guard<std::mutex> lock(_poll_mutex);
     _pollfds.push_back(fd);
//     notify();
}

struct pollfd
&Network::getPollFD(int index)
{
//    GNASH_REPORT_FUNCTION;
    std::lock_guard<std::mutex> lock(_poll_mutex);
    return _pollfds[index];
}

struct pollfd *
Network::getPollFDPtr()
{
//    GNASH_REPORT_FUNCTION;
    std::lock_guard<std::mutex> lock(_poll_mutex);
    return &_pollfds[0];
}

void
Network::erasePollFD(int fd)
{
//    GNASH_REPORT_FUNCTION;
    log_debug(_("%s: erasing fd #%d from pollfds"), __PRETTY_FUNCTION__, fd);
    std::lock_guard<std::mutex> lock(_poll_mutex);
    if (_pollfds.size() > 0) {
	vector<struct pollfd>::iterator it;
	for (it=_pollfds.begin(); it<_pollfds.end(); ++it) {
	    if ((*it).fd == fd) {
		_pollfds.erase(it);
		break;
	    }
	}
    }
}

void
Network::erasePollFD(vector<struct pollfd>::iterator &itt)
{
//    GNASH_REPORT_FUNCTION;
    std::lock_guard<std::mutex> lock(_poll_mutex);
    if (_pollfds.size() == 1) {
 	_pollfds.clear();
     } else {
	_pollfds.erase(itt);
    }
}

void
Network::addEntry(int fd, Network::entry_t *func)
{
//    GNASH_REPORT_FUNCTION;
    std::lock_guard<std::mutex> lock(_poll_mutex);
    _handlers[fd] = func;
}

Network::entry_t *
Network::getEntry(int fd)
{
//    GNASH_REPORT_FUNCTION;
    std::lock_guard<std::mutex> lock(_poll_mutex);
    return _handlers[fd];
}

std::shared_ptr<std::vector<struct pollfd> >
Network::waitForNetData(int limit, struct pollfd *fds)
{
//    GNASH_REPORT_FUNCTION;

    std::shared_ptr<vector<struct pollfd> > hits(new vector<struct pollfd>);

    log_debug(_("%s: waiting for %d fds"), __FUNCTION__, limit);

    if ((fds == nullptr) || (limit == 0)) {
	return hits;
    }
    
    int timeout = _timeout;
    if (timeout <= 0) {
	timeout = 5;
    }
#ifdef HAVE_PPOLL
	struct timespec tval;
	sigset_t pending, blockset;
	sigemptyset(&blockset);         /* Block SIGINT */
//        sigaddset(&blockset, SIGINT);
//        sigaddset(&blockset, SIGPIPE);
        sigprocmask(SIG_BLOCK, &blockset, nullptr);

	tval.tv_sec = 5; // FIXME: was _timeout;
	tval.tv_nsec = 0;
	int ret = ppoll(fds, limit, &tval, &blockset);
	sigpending(&pending);
	if (sigismember(&pending, SIGINT)) {
	    log_debug("Have a pending SIGINT interrupt waiting!");
	    int sig;
	    sigwait(&blockset, &sig);
	}
#else
#ifdef HAVE_POLL_H
	int ret = poll(fds, limit, _timeout);
#else
	fd_set fdset;
	struct timeval        tval;
	tval.tv_sec = timeout;
	tval.tv_usec = 0;
	int ret = select(limit+1, &fdset, NULL, NULL, &tval);
#endif
#endif

	log_debug(_("Poll returned: %d, timeout is: %d"), ret, _timeout);

    while (ret--) {
	for (int i = 0; i<limit; i++) {
	    // If we get this event, the other end of the connection has been shut down
#if 0
	    if (fds[i].revents &POLLPRI ) {
		log_debug("%s: Revents has a POLLPRI  set 0x%x for fd #%d",
			  __FUNCTION__, fds[i].revents, fds[i].fd);
	    }
	    if (fds[i].revents & POLLRDNORM) {
		log_debug("%s: Revents has a POLLRDNORM set 0x%x for fd #%d",
			  __FUNCTION__,  fds[i].revents, fds[i].fd);
	    }
	    if (fds[i].revents & POLLHUP) {
		log_debug("%s: Revents has a POLLHUP set 0x%x for fd #%d",
			  __FUNCTION__,  fds[i].revents, fds[i].fd);
	    }


	    if (fds[i].revents & POLLERR) {
		log_debug("%s: Revents has a POLLERR set 0x%x for fd #%d",
			  __FUNCTION__,  fds[i].revents, fds[i].fd);
	    }
	    if (fds[i].revents & POLLHUP) {
		log_debug("%s: Revents has a POLLHUP set 0x%x for fd #%d",
			  __FUNCTION__,  fds[i].revents, fds[i].fd);
	    }
	    if (fds[i].revents & POLLNVAL) {
		log_debug("%s: Revents has a POLLNVAL set 0x%x for fd #%d",
			  __FUNCTION__,  fds[i].revents, fds[i].fd);
//		throw GnashException("Polling an invalid file descritor");
	    }
	    if (fds[i].revents & POLLIN) {
		log_debug("%s: Revents has a POLLIN set 0x%x for fd #%d",
			  __FUNCTION__,  fds[i].revents, fds[i].fd);
	    }
	    if (fds[i].revents & POLLMSG) {
		log_debug("%s: Revents has a POLLMSG set 0x%x for fd #%d",
			  __FUNCTION__,  fds[i].revents, fds[i].fd);
	    }
	    if (fds[i].revents & POLLREMOVE) {
		log_debug("%s: Revents has a POLLREMOVE set 0x%x for fd #%d",
			  __FUNCTION__,  fds[i].revents, fds[i].fd);
	    }
	    if (fds[i].revents & POLLRDHUP) {
		log_debug("%s: Revents has a POLLRDHUP set 0x%x for fd #%d",
			  __FUNCTION__,  fds[i].revents, fds[i].fd);
//		throw GnashException("Connection dropped from client side.");
	    }
#endif    
// 	    if ((fds[i].revents & POLLIN) || (fds[i].revents & POLLRDHUP))  {
		hits->push_back(fds[i]);
// 		// If we got as many matches as were seen by poll(), then
// 		// stop searching the rest of the items in the array.
// 		if (hits->size() == ret) {
// 		    break;
// 		}
// 	    } else {
// 		log_debug("No data on fd #%d, revents is 0x%x", fds[i].fd, fds[i].revents);
// 	    }
	}
    }
    
    return hits;
}

fd_set
Network::waitForNetData(vector<int> &data)
{
    // GNASH_REPORT_FUNCTION;

    fd_set fdset;
    FD_ZERO(&fdset);
	
    if (data.size()) {
	int max = 0;
	
	for (size_t i = 0; i<data.size(); i++) {
	    FD_SET(data[i], &fdset);
	    if (data[i] > max) {
		max = data[i];
	    }
	}
	return waitForNetData(max+1, fdset);
    }

    return fdset;
}

fd_set
Network::waitForNetData(int limit, fd_set files)
{
    // GNASH_REPORT_FUNCTION;

    // select modifies the set of file descriptors, and we don't
    // want to modify the one passed as an argument, so we make a copy.
    fd_set fdset = files;

    // Reset the timeout value, since select modifies it on return
    int timeout = _timeout;
    if (timeout <= 0) {
	timeout = 30;
    }
#ifdef HAVE_PSELECT_XX
    struct timespec tval;
    sigset_t pending, sigmask;
    sigprocmask(SIG_BLOCK, &sigmask, NULL);

    tval.tv_sec = 0;
    tval.tv_nsec = timeout * 1000000000;
    int ret = pselect(limit+1, &fdset, NULL, NULL, &tval, &sigmask);
    sigpending(&pending);
    if (sigismember(&pending, SIGINT)) {
	log_debug("Have a pending SIGINT interrupt waiting!");
	int sig;
	sigwait(&sigmask, &sig);
    }
    if (sigismember(&pending, SIGPIPE)) {
	log_debug("Have a pending SIGPIPE interrupt waiting!");
	int sig;
	sigwait(&sigmask, &sig);
    }
#else
    struct timeval tval;
    tval.tv_sec = 0;
    tval.tv_usec = timeout * 1000; // was 1000000
    int ret = select(limit+1, &fdset, nullptr, nullptr, &tval);
#endif
    // If interrupted by a system call, try again
    if (ret == -1 && errno == EINTR) {
	log_error (_("Waiting for data was interrupted by a system call"));
    }
    
    if (ret == -1) {
	log_error (_("Waiting for data for fdset, was never available for reading"));
	FD_ZERO(&fdset);
	FD_SET(0, &fdset);
	return fdset;
    }
    
    if (ret == 0) {
	// log_debug (_("Waiting for data for fdset, timed out waiting for data"));
	FD_ZERO(&fdset);
	FD_SET(0, &fdset);
	return fdset;
    }

    if (ret < 0) {
	log_error(_("select() got an error: %s."), strerror(errno));
	FD_ZERO(&fdset);
	FD_SET(0, &fdset);
    } else {
	log_network(_("select() saw activity on %d file descriptors."), ret);
    }

    return fdset;
}

Network &
Network::operator = (Network &net)
{
    GNASH_REPORT_FUNCTION;
    
    // the file descriptor used for reading and writing
    _sockfd= net.getFileFd();
    // the file descriptor used to listen for new connections
    _listenfd = net.getListenFd();
    _port = net.getPort();
    _portstr = net.getPortStr();
    _url = net.getURL();
    _protocol = net.getProtocol();
    _host = net.getHost();
    _path = net.getPath();
    _connected = net.connected();
    _debug = net.netDebug();
    _timeout = net.getTimeout();
    return *this;
}

void
Network::toggleDebug(bool val)
{
    // Turn on our own debugging
    _debug = val;

    // Turn on debugging for the utility methods
    // recursive on all control paths,
    // toggleDebug(true);
}

#ifdef USE_SSL
bool
Network::initSSL(std::string &hostname)
{
    GNASH_REPORT_FUNCTION;
    string nothing;
    initSSL(hostname, nothing);
}

bool
Network::initSSL(std::string &hostname, std::string &password)
{
    GNASH_REPORT_FUNCTION;
    string nothing;

    initSSL(hostname, password, nothing, nothing, nothing, true);
}

bool
Network::initSSL(std::string &hostname, std::string &password, bool auth)
{
    GNASH_REPORT_FUNCTION;

    string nothing;
    initSSL(hostname, password, nothing, nothing, nothing, auth);
}

bool
Network::initSSL(std::string &hostname, std::string &passwd, 
		 std::string &keyfile, std::string &calist,
		 std::string &rootpath, bool auth)
{
    GNASH_REPORT_FUNCTION;

    // FIXME: make sure we have a connection

    if (_sockfd == 0) {
	if ((_sockfd = createClient(hostname, SSL_PORT) == false)) {
	    log_error(_("Can't connect to server %s"), hostname);
	    return false;
	}
    }

    if (!_ssl) {
	_ssl.reset(new SSLClient);
    }

    if (!hostname.empty()) {
	_ssl->setHostname(hostname);
    } else {
	log_debug(_("Using default hostname: \"%s\""), _host);
    }
    if (!keyfile.empty()) {
	_ssl->setKeyfile(keyfile);
    } else {
	log_debug(_("Using default keyfile: \"%s\""), _ssl->getKeyfile());
    }
    if (!calist.empty()) {
	_ssl->setCAlist(calist);
    } else {
	log_debug(_("Using default CA List: \"%s\""), _ssl->getCAlist());
    }

    if (!passwd.empty()) {
	_ssl->setPassword(passwd);
    } else {
	log_debug(_("Using default Password: \"%s\""), _ssl->getPassword());
    }
    if (!rootpath.empty()) {
	_ssl->setRootPath(rootpath);
    } else {
	log_debug(_("Using default Root Path to PEM files: \"%s\""),
		  _ssl->getRootPath());
    }

    if (_ssl->sslConnect(_sockfd)) {
        log_debug(_("Connected to SSL server"));
    } else {
        log_error(_("Couldn't connect to SSL server"));
	return false;
    }

    // If we got this far, everthing worked
    return true;
}
#endif

// Use an ioctl() to see how many bytes are in the network buffers.
size_t
Network::sniffBytesReady(int fd)
{
    // GNASH_REPORT_FUNCTION;

    int bytes = 0;
    fd_set fdset;

    FD_SET(fd, &fdset);

    struct timeval tval;
    tval.tv_sec = 0;
    tval.tv_usec = 10;
    if (select(fd+1, &fdset, nullptr, nullptr, &tval)) {
 	if (FD_ISSET(fd, &fdset)) {
#ifndef _WIN32
	    ioctl(fd, FIONREAD, &bytes);
#else
	    ioctlSocket(fd, FIONREAD, &bytes);
#endif
	}
    }

    log_network(_("#%d bytes waiting in kernel network buffer."), bytes);
    
    return bytes;
}

// Return the string representation of the IPV4 or IPV6 number
std::shared_ptr<char>
Network::getIPString(struct addrinfo *ai)
{
    std::shared_ptr<char> straddr(new char[INET6_ADDRSTRLEN]);
    std::memset(straddr.get(), 0, INET6_ADDRSTRLEN);    
    if (ai->ai_family == AF_INET6) {
        struct sockaddr_in6 *sock6 = reinterpret_cast<struct sockaddr_in6 *>(ai->ai_addr);
        struct in6_addr sin6_addr = sock6->sin6_addr;
        ::inet_ntop(AF_INET6, &sin6_addr, straddr.get(), INET6_ADDRSTRLEN);
//        log_debug("IPV6 address: %s", straddr.get());
    } else if (ai->ai_family == AF_INET) {
        struct sockaddr_in *sock = reinterpret_cast<struct sockaddr_in *>(ai->ai_addr);
        struct in_addr sin_addr = sock->sin_addr;
        ::inet_ntop(AF_INET, &sin_addr, straddr.get(), INET_ADDRSTRLEN);
//        log_debug("IPV4 address: %s", straddr);
    } else {
        log_error(_("no IP address in addrinfo!"));
    }
    
    return straddr;
}

// Trap Control-C so we can cleanly exit
static void
cntrlc_handler (int sig)
{
    GNASH_REPORT_FUNCTION;
    sig_number = sig;
    log_debug(_("Got an %d interrupt while blocked on pselect()"), sig);
    exit(EXIT_FAILURE);
}

} // end of gnash namespace

// Local Variables:
// mode: C++
// indent-tabs-mode: nil
// End:
