// Copyright (C) 1999-2005 Open Source Telecom Corporation.
// Copyright (C) 2006-2014 David Sugar, Tycho Softworks.
// Copyright (C) 2015 Cherokees of Idaho.
//
// 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 2 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 Lesser General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
// As a special exception, you may use this file as part of a free software
// library without restriction.  Specifically, if other files instantiate
// templates or use macros or inline functions from this file, or you compile
// this file and link it with other files to produce an executable, this
// file does not by itself cause the resulting executable to be covered by
// the GNU General Public License.  This exception does not however
// invalidate any other reasons why the executable file might be covered by
// the GNU General Public License.
//
// This exception applies only to the code released under the name GNU
// Common C++.  If you copy code from other releases into a copy of GNU
// Common C++, as the General Public License permits, the exception does
// not apply to the code that you add in this way.  To avoid misleading
// anyone as to the status of such modified files, you must delete
// this exception notice from them.
//
// If you write modifications of your own for GNU Common C++, it is your choice
// whether to permit this exception to apply to your modifications.
// If you do not wish that, delete this exception notice.
//

/**
 * @file commoncpp/udp.h
 * @short udp derived socket classes.
 **/

#ifndef COMMONCPP_UDP_H_
#define COMMONCPP_UDP_H_

#include <cstdio>

#ifndef COMMONCPP_CONFIG_H_
#include <commoncpp/config.h>
#endif

#ifndef COMMONCPP_STRING_H_
#include <commoncpp/string.h>
#endif

#ifndef COMMONCPP_ADDRESS_H_
#include <commoncpp/address.h>
#endif

#ifndef COMMONCPP_SOCKET_H_
#include <commoncpp/socket.h>
#endif

namespace ost {

/**
 * UDP sockets implement the TCP SOCK_DGRAM UDP protocol.  They can be
 * used to pass unverified messages between hosts, or to broadcast a
 * specific message to an entire subnet.  Please note that Streaming of
 * realtime data commonly use UDPDuplex related classes rather than
 * UDPSocket.
 *
 * In addition to connected TCP sessions, Common C++ supports UDP sockets and
 * these also cover a range of functionality.  Like a TCPSocket, A UDPSocket
 * can be created bound to a specific network interface and/or port address,
 * though this is not required.  UDP sockets also are usually either
 * connected or otherwise "associated" with a specific "peer" UDP socket.
 * Since UDP sockets operate through discreet packets, there are no streaming
 * operators used with UDP sockets.
 *
 * In addition to the UDP "socket" class, there is a "UDPBroadcast" class.
 * The UDPBroadcast is a socket that is set to send messages to a subnet as a
 * whole rather than to an individual peer socket that it may be associated
 * with.
 *
 * UDP sockets are often used for building "realtime" media  streaming
 * protocols and full duplex messaging services.  When used in this manner,
 * typically a pair of UDP sockets are used together; one socket is used to
 * send and the other to receive data with an associated pair of UDP sockets
 * on a "peer" host.  This concept is represented through the Common C++
 * UDPDuplex object, which is a pair of sockets that communicate with another
 * UDPDuplex pair.
 *
 *
 * @author David Sugar <dyfet@ostel.com>
 * @short Unreliable Datagram Protocol sockets.
 */
class __EXPORT UDPSocket : public Socket
{
private:
    inline Error setKeepAlive(bool enable)
        {return Socket::setKeepAlive(enable);}

    __DELETE_COPY(UDPSocket);

protected:
    Socket::address peer;

    Family family;

public:
    /**
     * Create an unbound UDP socket, mostly for internal use.
     */
    UDPSocket(Family family = IPV4);

    /**
     * Create a UDP socket bound by a service name.
     */
    UDPSocket(const char *name, Family family = IPV4);

    /**
     * Create a UDP socket and bind it to a specific interface
     * and port address so that other UDP sockets on remote
     * machines (or the same host) may find and send UDP messages
     * to it.  On failure to bind, an exception is thrown.
     *
     * @param bind address to bind this socket to.
     * @param port number to bind this socket to.
     */
    UDPSocket(const ucommon::Socket::address &bind);
    UDPSocket(const IPV4Address &bind, tpport_t port);
#ifdef  CCXX_IPV6
    UDPSocket(const IPV6Address &bind, tpport_t port);
#endif

    /**
     * Destroy a UDP socket as a socket.
     */
    virtual ~UDPSocket();

    /**
     * Set the loopback.
     */
    inline Error setLoopback(bool enable)
        {return Socket::setLoopbackByFamily(enable, family);}

    /**
     * Set the multicast.
     */
    inline Error setMulticast(bool enable)
        {return Socket::setMulticastByFamily(enable, family);}

    /**
     * Set time to live.
     */
    inline Error setTimeToLive(char ttl)
        {return Socket::setTimeToLiveByFamily(ttl, family);}

    /**
     * set the peer address to send message packets to.  This can be
     * set before every send() call if nessisary.
     *
     * @param host address to send packets to.
     * @param port number to deliver packets to.
     */
    void setPeer(const ucommon::Socket::address &host);
    void connect(const ucommon::Socket::address &host);

    void setPeer(const IPV4Host &host, tpport_t port);
    void connect(const IPV4Host &host, tpport_t port);
#ifdef  CCXX_IPV6
    void setPeer(const IPV6Host &host, tpport_t port);
    void connect(const IPV6Host &host, tpport_t port);
#endif

    /**
     * get the interface index for a named network device
     *
     * @param ethX is device name, like "eth0" or "eth1"
     * @param InterfaceIndex is the index value returned by os
     * @todo Win32 and ipv6 specific implementation.
     */
    Socket::Error getInterfaceIndex(const char *ethX,int& InterfaceIndex);

    /**
     * join a multicast group on a particular interface
     *
     * @param ia is the multicast address to use
     * @param InterfaceIndex is the index value returned by
     * getInterfaceIndex
     * @todo Win32 and ipv6 specific implementation.
     */
    Socket::Error join(const ucommon::Socket::address &ia, int InterfaceIndex=0);
    Socket::Error join(const IPV4Multicast &ia,int InterfaceIndex);

    /**
     * Send a message packet to a peer host.
     *
     * @param buf pointer to packet buffer to send.
     * @param len of packet buffer to send.
     * @return number of bytes sent.
     */
    ssize_t send(const void *buf, size_t len);

    /**
     * Receive a message from any host.
     *
     * @param buf pointer to packet buffer to receive.
     * @param len of packet buffer to receive.
     * @param reply save sender address for reply if true.
     * @return number of bytes received.
     */
    ssize_t receive(void *buf, size_t len, bool reply = false);

    /**
     * Examine address of sender of next waiting packet.  This also
     * sets "peer" address to the sender so that the next "send"
     * message acts as a "reply".  This additional behavior overides
     * the standard socket getSender behavior.
     *
     * @param port pointer to hold port number.
     */
    ucommon::Socket::address getPeer();

    IPV4Host getIPV4Peer(tpport_t *port = NULL);
    inline IPV4Host getPeer(tpport_t *port)
        {return getIPV4Peer(port);}

#ifdef  CCXX_IPV6
    IPV6Host getIPV6Peer(tpport_t *port = NULL);
#endif

    /**
     * Examine contents of next waiting packet.
     *
     * @param buf pointer to packet buffer for contents.
     * @param len of packet buffer.
     * @return number of bytes examined.
     */
    inline ssize_t peek(void *buf, size_t len)
        {return ::recv(so, (char *)buf, (socksize_t)len, MSG_PEEK);}

    /**
     * Associate socket with a named connection
     */
    void setPeer(const char *service);
    void connect(const char *service);

    /**
     * Disassociate this socket from any host connection.  No data
     * should be read or written until a connection is established.
     */
    Error disconnect(void);
};

/**
 * Representing a UDP socket used for subnet broadcasts, this class
 * provides an alternate binding and setPeer() capability for UDP
 * sockets.
 *
 * @author David Sugar <dyfet@ostel.com>
 * @short Unreliable Datagram for subnet broadcasts.
 */
class __EXPORT UDPBroadcast : public UDPSocket
{
private:
    void setPeer(const IPV4Host &ia, tpport_t port);

    Error setBroadcast(bool enable)
        {return Socket::setBroadcast(enable);}

    __DELETE_COPY(UDPBroadcast);

public:
    /**
     * Create and bind a subnet broadcast socket.
     *
     * @param ia address to bind socket under locally.
     * @param port to bind socket under locally.
     */
    UDPBroadcast(const IPV4Address &ia, tpport_t port);

    /**
     * Set peer by subnet rather than specific host.
     *
     * @param subnet of peer hosts to send to.
     * @param port number to use.
     */
    void setPeer(const IPV4Broadcast &subnet, tpport_t port);
};

/**
 * Representing half of a two-way UDP connection, the UDP transmitter
 * can broadcast data to another selected peer host or to an entire
 * subnet.
 *
 * @author David Sugar <dyfet@ostel.com>
 * @short Unreliable Datagram Peer Associations.
 */
class __EXPORT UDPTransmit : protected UDPSocket
{
private:
    /**
     * Common code for diferent flavours of Connect (host, broadcast,
     * multicast).
     *
     * @param ia network address to associate with
     * @param port port number to associate with
     */
    Error cConnect(const IPV4Address &ia, tpport_t port);

    __DELETE_COPY(UDPTransmit);

protected:
    /**
     * Create a UDP transmitter.
     */
    UDPTransmit(Family family = IPV4);

    /**
     * Create a UDP transmitter, bind it to a specific interface
     * and port address so that other UDP sockets on remote
     * machines (or the same host) may find and send UDP messages
     * to it, and associate it with a given port on a peer host.
     * On failure to bind, an exception is thrown.  This class is
     * only used to build the UDP Duplex.
     *
     * @param bind address to bind this socket to.
     * @param port number to bind this socket to. If 0 or not specified,
     *        the bind socket address port is used.
     */
    UDPTransmit(const ucommon::Socket::address &bind);

    UDPTransmit(const IPV4Address &bind, tpport_t port = 5005);
#ifdef  CCXX_IPV6
    UDPTransmit(const IPV6Address &bind, tpport_t port = 5005);
#endif

    /**
     * Associate this socket with a specified peer host.  The port
     * number from the constructor will be used.  All UDP packets
     * will be sent to and received from the specified host.
     *
     * @return 0 on success, -1 on error.
     * @param host address to connect socket to.
     * @param port to connect socket to.
     */
    Error connect(const ucommon::Socket::address &host);

    Error connect(const IPV4Host &host, tpport_t port);
#ifdef  CCXX_IPV6
    Error connect(const IPV6Address &host, tpport_t port);
#endif

    /**
     * Associate this socket with a subnet of peer hosts for
     * subnet broadcasting.  The server must be able to assert
     * broadcast permission for the socket.
     *
     * @return 0 on success, -1 on error.
     * @param subnet subnet address to broadcast into.
     * @param port transport port to broadcast into.
     */
    Error connect(const IPV4Broadcast &subnet, tpport_t port);

    /**
     * Associate this socket with a multicast group.
     *
     * @return 0 on success, -1 on error.
     * @param mgroup address of the multicast group to send to.
     * @param port port number
     */
    Error connect(const IPV4Multicast &mgroup, tpport_t port);
#ifdef  CCXX_IPV6
    Error connect(const IPV6Multicast &mgroup, tpport_t port);
#endif

    /**
     * Transmit "send" to use "connected" send rather than sendto.
     *
     * @return number of bytes sent.
     * @param buf address of buffer to send.
     * @param len of bytes to send.
     */
    inline ssize_t send(const void *buf, size_t len)
        {return ::send(so, (const char *)buf, (socksize_t)len, MSG_NOSIGNAL);}

    /**
     * Stop transmitter.
     */
    inline void endTransmitter(void)
        {Socket::endSocket();}

    /*
     * Get transmitter socket.
     *
     * @return transmitter.
     */
    inline SOCKET getTransmitter(void)
        {return so;};

    inline Error setMulticast(bool enable)
        {return Socket::setMulticastByFamily(enable, family);}

    inline Error setTimeToLive(uint8_t ttl)
        {return Socket::setTimeToLiveByFamily(ttl, family);}

public:
    /**
     * Transmit "send" to use "connected" send rather than sendto.
     *
     * @note Windows does not support MSG_DONTWAIT, so it is defined
     *   as 0 on that platform.
     * @return number of bytes sent.
     * @param buffer address of buffer to send.
     * @param len of bytes to send.
     */
    inline ssize_t transmit(const char *buffer, size_t len)
        {return ::send(so, buffer, (socksize_t)len, MSG_DONTWAIT|MSG_NOSIGNAL);}

    /**
     * See if output queue is empty for sending more packets.
     *
     * @return true if output available.
     * @param timeout in milliseconds to wait.
     */
    inline bool isOutputReady(unsigned long timeout = 0l) {
        return Socket::isPending(Socket::pendingOutput, timeout);
    }


    inline Error setRouting(bool enable)
        {return Socket::setRouting(enable);}

    inline Error setTypeOfService(Tos tos)
        {return Socket::setTypeOfService(tos);}

    inline Error setBroadcast(bool enable)
        {return Socket::setBroadcast(enable);}
};

/**
 * Representing half of a two-way UDP connection, the UDP receiver
 * can receive data from another peer host or subnet.  This class is
 * used exclusivily to derive the UDPDuplex.
 *
 * @author David Sugar <dyfet@ostel.com>
 * @short Unreliable Datagram Peer Associations.
 */
class __EXPORT UDPReceive : protected UDPSocket
{
private:
    __DELETE_COPY(UDPReceive);

protected:
    /**
     * Create a UDP receiver, bind it to a specific interface
     * and port address so that other UDP sockets on remote
     * machines (or the same host) may find and send UDP messages
     * to it, and associate it with a given port on a peer host.
     * On failure to bind, an exception is thrown.
     *
     * @param bind address to bind this socket to.
     */
    UDPReceive(const ucommon::Socket::address &bind);
    UDPReceive(const IPV4Address &bind, tpport_t port);
#ifdef  CCXX_IPV6
    UDPReceive(const IPV6Address &bind, tpport_t port);
#endif

    /**
     * Associate this socket with a specified peer host.  The port
     * number from the constructor will be used.  All UDP packets
     * will be sent received from the specified host.
     *
     * @return 0 on success, -1 on error.
     * @param host host network address to connect socket to.
     * @param port host transport port to connect socket to.
     */
    Error connect(const ucommon::Socket::address &host);
    Error connect(const IPV4Host &host, tpport_t port);
#ifdef  CCXX_IPV6
    Error connect(const IPV6Host &host, tpport_t port);
#endif

    /**
     * Check for pending data.
     *
     * @return true if data is waiting.
     * @param timeout in milliseconds.
     */
    bool isPendingReceive(timeout_t timeout) {
        return Socket::isPending(Socket::pendingInput, timeout);
    }

    /**
     * End receiver.
     */
    inline void endReceiver(void)
        {Socket::endSocket();}

    inline SOCKET getReceiver(void) const
        {return so;}

    inline Error setRouting(bool enable)
        {return Socket::setRouting(enable);}

    inline Error setMulticast(bool enable)
        {return Socket::setMulticastByFamily(enable, family);}

    inline Error join(const ucommon::Socket::address &ia)
            {return Socket::join(ia);}

    inline Error join(const IPV4Multicast &ia)
        {return Socket::join(ia);}

#ifdef  CCXX_IPV6
    inline Error join(const IPV6Multicast &ia)
        {return Socket::join(ia);}
#endif

    inline Error drop(const IPV4Multicast &ia)
        {return Socket::drop(ia);}

#ifdef  CCXX_IPV6
    inline Error drop(const IPV6Multicast &ia)
        {return Socket::drop(ia);}
#endif

public:
    /**
     * Receive a data packet from the connected peer host.
     *
     * @return num of bytes actually received.
     * @param buf address of data receive buffer.
     * @param len size of data receive buffer.
     */
    inline ssize_t receive(void *buf, size_t len)
        {return ::recv(so, (char *)buf, (socksize_t)len, 0);}

    /**
     * See if input queue has data packets available.
     *
     * @return true if data packets available.
     * @param timeout in milliseconds.
     */
    inline bool isInputReady(timeout_t timeout = TIMEOUT_INF) {
        return Socket::isPending(Socket::pendingInput, timeout);
    }
};

/**
 * UDP duplex connections impliment a bi-directional point-to-point UDP
 * session between two peer hosts.  Two UDP sockets are typically used
 * on alternating port addresses to assure that sender and receiver
 * data does not collide or echo back.  A UDP Duplex is commonly used
 * for full duplex real-time streaming of UDP data between hosts.
 *
 * @author David Sugar <dyfet@ostel.com>
 * @short Unreliable Datagram Peer Associations.
 */
class __EXPORT UDPDuplex : public UDPTransmit, public UDPReceive
{
private:
    __DELETE_COPY(UDPDuplex);

public:
    /**
     * Create a UDP duplex as a pair of UDP simplex objects
     * bound to alternating and interconnected port addresses.
     *
     * @param bind address to bind this socket to.
     * @param port number to bind sender.
     */
    UDPDuplex(const ucommon::Socket::address &bind);
    UDPDuplex(const IPV4Address &bind, tpport_t port);
#ifdef  CCXX_IPV6
    UDPDuplex(const IPV6Address &bind, tpport_t port);
#endif

    /**
     * Associate the duplex with a specified peer host. Both
     * the sender and receiver will be interconnected with
     * the remote host.
     *
     * @return 0 on success, error code on error.
     * @param host address to connect socket to.
     * @param port number to connect socket to.
     */
    Error connect(const ucommon::Socket::address &host);
    Error connect(const IPV4Host &host, tpport_t port);
#ifdef  CCXX_IPV6
    Error connect(const IPV6Host &host, tpport_t port);
#endif

    /**
     * Disassociate this duplex from any host connection.  No data
     * should be read or written until a connection is established.
     *
     * @return 0 on success, error code on error.
     */
    Error disconnect(void);
};

} // namespace ost

#endif
