/*
 * Copyright (c) 2000-2001 Apple Computer, Inc. All Rights Reserved.
 * 
 * The contents of this file constitute Original Code as defined in and are
 * subject to the Apple Public Source License Version 1.2 (the 'License').
 * You may not use this file except in compliance with the License. Please obtain
 * a copy of the License at http://www.apple.com/publicsource and read it before
 * using this file.
 * 
 * This Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS
 * OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT
 * LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
 * PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see the License for the
 * specific language governing rights and limitations under the License.
 */


//
// ip++ - C++ layer for IP socket and address management
//
// Key to comments:
//	HBO = host byte order, NBO = network byte order
//
// Rules for byte ordering: C++ objects store addresses and ports in NBO.
// Struct in_addr arguments are in NBO. Integer type arguments are in HBO.
// Stick with the conversion methods and you win. Cast around and you lose.
//
// @@@ Which namespace should we be in?
//
#ifndef _H_IPPLUSPLUS
#define _H_IPPLUSPLUS

#include "unix++.h"
#include "timeflow.h"
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <cstdio>
#include <cstdarg>
#include <map>

using namespace UnixPlusPlus;


namespace Security {
namespace IPPlusPlus {

class Host;


//
// For now, ports are simply a short unsigned integer type, in HBO.
//
typedef uint16 IPPort;


//
// An IP host address.
//
class IPAddress : public in_addr {
public:
    IPAddress()						{ s_addr = htonl(INADDR_ANY); }
    IPAddress(const struct in_addr &addr) { s_addr = addr.s_addr; }
    explicit IPAddress(uint32 addr)	{ s_addr = htonl(addr); }
    IPAddress(const char *s);		// ONLY dotted-quad form - use hosts.h for name resolution
    
    operator uint32 () const		{ return ntohl(s_addr); }
    operator string () const;		// "n.n.n.n" (no name resolution)
    
public:
    bool operator == (const IPAddress &other) const	{ return s_addr == other.s_addr; }
    bool operator != (const IPAddress &other) const	{ return s_addr != other.s_addr; }
    bool operator < (const IPAddress &other) const	{ return s_addr < other.s_addr; }
    
    operator bool () const			{ return s_addr != htonl(INADDR_ANY); }
    bool operator ! () const		{ return s_addr == htonl(INADDR_ANY); }
    
public:
    static const IPAddress &any;
};


//
// An IP "socket address", i.e. a combined host address and port.
//
class IPSockAddress : public sockaddr_in {
public:
    IPSockAddress();
    IPSockAddress(const struct sockaddr_in &sockaddr)	{ *(sockaddr_in *)this = sockaddr; }
    IPSockAddress(const IPAddress &addr, IPPort port);
    
    IPAddress address() const		{ return sin_addr; }
    void address(IPAddress addr)	{ sin_addr = addr; }
    IPPort port() const				{ return ntohs(sin_port); }
    void port(IPPort p)				{ sin_port = htons(p); }
    
    operator string () const;		// "n.n.n.n:p" (no name resolution)

    // automatically convert to struct sockaddr * for use in system calls
    operator struct sockaddr * ()
    { return reinterpret_cast<struct sockaddr *>(this); }
    operator const struct sockaddr * () const
    { return reinterpret_cast<const struct sockaddr *>(this); }
    
    // conveniences
    IPSockAddress defaults(const IPSockAddress &defaultAddr) const;
    IPSockAddress defaults(const IPAddress &defaultAddr, IPPort defaultPort = 0) const;
    IPSockAddress defaults(IPPort defaultPort) const;
};


//
// An IP socket.
// This inherits all functionality of a FileDesc, so I/O is fun and easy.
// Socket is "passive"; it doesn't own any resources and does nothing on destruction.
// On the upside, you can assign Sockets freely.
// If you want self-managing sockets that clean up after themselves,
// use the subclasses below.
//
class Socket : public FileDesc {
public:
    Socket() { }
    explicit Socket(int type, int protocol = 0);
    
    Socket &operator = (int fd)				{ setFd(fd); return *this; }
    
    // basic open (socket system call)
    void open(int type, int protocol = 0);
    
    // standard socket operations
    void bind(const IPSockAddress &addr);	// to this socket address
    void bind(const IPAddress &addr = IPAddress::any, IPPort port = 0);
    void listen(int backlog = 1);
    void accept(Socket &s);
    void accept(Socket &s, IPSockAddress &peer);
    bool connect(const IPSockAddress &peer);
    bool connect(const IPAddress &addr, IPPort port);
    void connect(const Host &host, IPPort port);	// any address of this host
    void shutdown(int type);
    enum { shutdownRead = 0, shutdownWrite = 1, shutdownBoth = 2 };
    
    // get endpoint addresses
    IPSockAddress localAddress() const;
    IPSockAddress peerAddress() const;
    
    // socket options
    void setOption(const void *value, int length, int name, int level = SOL_SOCKET) const;
    void getOption(void *value, int &length, int name, int level = SOL_SOCKET) const;
    
    template <class T> void setOption(const T &value, int name, int level = SOL_SOCKET) const
    { setOption(&value, sizeof(value), name, level); }
    
    template <class T> T getOption(int name, int level = SOL_SOCKET) const
    {
        T value; int length = sizeof(value);
        getOption(&value, length, name, level);
        assert(length == sizeof(value));
        return value;
    }
    
    // some specific useful options
    int type() const		{ return getOption<int>(SO_TYPE); }
    int error() const		{ return getOption<int>(SO_ERROR); }
    
public:
#if defined(SOMAXCONN)
    static const int listenMaxQueue = SOMAXCONN;
#else
    static const int listenMaxQueue = 5;	// the traditional BSD UNIX value
#endif

protected:
    void prepare(int fdFlags, int type, int protocol = 0);
};


//
// A TCPClientSocket is a self-connecting TCP socket that connects (actively) to a server.
// Since TCP, once established, is symmetric, it can also be used for the server side
// of a TCP pipe. You can think of it as the least complex embodiment of a TCP connection.
//
class TCPClientSocket : public Socket {
    NOCOPY(TCPClientSocket)
public:
    TCPClientSocket() { }
    ~TCPClientSocket();	// closes connection
    
#if BUG_GCC
    void open(int type, int protocol = 0)	{ Socket::open(type, protocol); }
#else
    using Socket::open;
#endif
    
    void open(const IPSockAddress &peer, int fdFlags = 0);
    void open(const IPAddress &addr, IPPort port, int fdFlags = 0);
    void open(const Host &host, IPPort port, int fdFlags = 0);

    TCPClientSocket(const IPSockAddress &peer, int fdFlags = 0)
    { open(peer, fdFlags); }
    TCPClientSocket(const IPAddress &addr, IPPort port, int fdFlags = 0)
    { open(addr, port, fdFlags); }
    TCPClientSocket(const Host &host, IPPort port, int fdFlags = 0)
    { open(host, port, fdFlags); }
    
protected:	// for serverSocket/clientSocket footsy play
    void setFd(int fd)			{ Socket::setFd(fd); }
    
private:
    TCPClientSocket(int sockfd);
};


//
// A TCPServerSocket is a self-initializing listener socket for incoming TCP requests
// (usually to a server). Its function operator yields the next incoming connection request
// as a TCPClientSocket (see above). For one-shot receivers, the receive() method will
// create the client and close the listener atomically (which is sometimes faster).
//
class TCPServerSocket : public Socket {
    NOCOPY(TCPServerSocket)
public:
    TCPServerSocket() { }
    ~TCPServerSocket();	// closes listener; existing connections unaffected
    
    void open(const IPSockAddress &local, int depth = 1);
    void open(IPPort port = 0, int depth = 1)
    { open(IPSockAddress(IPAddress::any, port), depth); }

    TCPServerSocket(const IPSockAddress &local, int depth = 1)	{ open(local, depth); }
    TCPServerSocket(IPPort port, int depth = 1)					{ open(port, depth); }
    
    void operator () (TCPClientSocket &newClient);	// retrieve next connection
    void receive(TCPClientSocket &client);			// accept once, then close listener
};


}	// end namespace IPPlusPlus
}	// end namespace Security


#endif //_H_IPPLUSPLUS
