/*=============================================================================|
|  PROJECT SNAP7                                                         1.3.0 |
|==============================================================================|
|  Copyright (C) 2013, 2015 Davide Nardella                                    |
|  All rights reserved.                                                        |
|==============================================================================|
|  SNAP7 is free software: you can redistribute it and/or modify               |
|  it under the terms of the Lesser GNU General Public License as published by |
|  the Free Software Foundation, either version 3 of the License, or           |
|  (at your option) any later version.                                         |
|                                                                              |
|  It means that you can distribute your commercial software linked with       |
|  SNAP7 without the requirement to distribute the source code of your         |
|  application and without the requirement that your application be itself     |
|  distributed under LGPL.                                                     |
|                                                                              |
|  SNAP7 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               |
|  Lesser GNU General Public License for more details.                         |
|                                                                              |
|  You should have received a copy of the GNU General Public License and a     |
|  copy of Lesser GNU General Public License along with Snap7.                 |
|  If not, see  http://www.gnu.org/licenses/                                   |
|=============================================================================*/
#ifndef snap_msgsock_h
#define snap_msgsock_h
//---------------------------------------------------------------------------
#include "snap_platform.h"
#include "snap_sysutils.h"
//----------------------------------------------------------------------------
#if defined(OS_WINDOWS) || defined (OS_SOLARIS) || defined(OS_OSX)
# define MSG_NOSIGNAL    0
#endif
//----------------------------------------------------------------------------
// Non blocking connection to avoid root priviledges under UNIX 
// i.e. raw socket pinger is not more used.
// Thanks to Rolf Stalder that made it ;)
//----------------------------------------------------------------------------
#ifdef PLATFORM_UNIX
    #define NON_BLOCKING_CONNECT
#endif
#ifdef NON_BLOCKING_CONNECT
    #include <fcntl.h>
#endif
//----------------------------------------------------------------------------
/*
  In Windows sizeof socket varies depending of the platform :
    win32 -> sizeof(SOCKET) = 4
    win64 -> sizeof(SOCKET) = 8

  Even though sizeof(SOCKET) is 8, should be safe to cast it to int, because
  the value constitutes an index in per-process table of limited size
  and not a real pointer.

  Other Os define the socket as int regardless of the processor.

  We want to sleep peacefully, so it's better to define a portable socket.
*/

#ifdef OS_WINDOWS
typedef SOCKET socket_t;
#else
typedef int socket_t;
#endif

//----------------------------------------------------------------------------
#define  SD_RECEIVE      0x00
#define  SD_SEND         0x01
#define  SD_BOTH         0x02
#define  MaxPacketSize   65536

//----------------------------------------------------------------------------
// For other platform we need to re-define next constants
#if defined(PLATFORM_UNIX) || defined(OS_OSX)

#define INVALID_SOCKET (socket_t)(~0)
#define SOCKET_ERROR             (-1)

#define WSAEINTR             EINTR
#define WSAEBADF             EBADF
#define WSAEACCES            EACCES
#define WSAEFAULT            EFAULT
#define WSAEINVAL            EINVAL
#define WSAEMFILE            EMFILE
#define WSAEWOULDBLOCK       EWOULDBLOCK
#define WSAEINPROGRESS       EINPROGRESS
#define WSAEALREADY          EALREADY
#define WSAENOTSOCK          ENOTSOCK
#define WSAEDESTADDRREQ      EDESTADDRREQ
#define WSAEMSGSIZE          EMSGSIZE
#define WSAEPROTOTYPE        EPROTOTYPE
#define WSAENOPROTOOPT       ENOPROTOOPT
#define WSAEPROTONOSUPPORT   EPROTONOSUPPORT
#define WSAESOCKTNOSUPPORT   ESOCKTNOSUPPORT
#define WSAEOPNOTSUPP        EOPNOTSUPP
#define WSAEPFNOSUPPORT      EPFNOSUPPORT
#define WSAEAFNOSUPPORT      EAFNOSUPPORT
#define WSAEADDRINUSE        EADDRINUSE
#define WSAEADDRNOTAVAIL     EADDRNOTAVAIL
#define WSAENETDOWN          ENETDOWN
#define WSAENETUNREACH       ENETUNREACH
#define WSAENETRESET         ENETRESET
#define WSAECONNABORTED      ECONNABORTED
#define WSAECONNRESET        ECONNRESET
#define WSAENOBUFS           ENOBUFS
#define WSAEISCONN           EISCONN
#define WSAENOTCONN          ENOTCONN
#define WSAESHUTDOWN         ESHUTDOWN
#define WSAETOOMANYREFS      ETOOMANYREFS
#define WSAETIMEDOUT         ETIMEDOUT
#define WSAECONNREFUSED      ECONNREFUSED
#define WSAELOOP             ELOOP
#define WSAENAMETOOLONG      ENAMETOOLONG
#define WSAEHOSTDOWN         EHOSTDOWN
#define WSAEHOSTUNREACH      EHOSTUNREACH
#define WSAENOTEMPTY         ENOTEMPTY
#define WSAEUSERS            EUSERS
#define WSAEDQUOT            EDQUOT
#define WSAESTALE            ESTALE
#define WSAEREMOTE           EREMOTE
#endif

#define WSAEINVALIDADDRESS   12001

#define ICmpBufferSize 4096
typedef byte TIcmpBuffer[ICmpBufferSize];

// Ping result
#define PR_CANNOT_PERFORM -1  // cannot ping :
                              //   unix    : no root rights or SUID flag set to
                              //             open raw sockets
                              //   windows : neither helper DLL found nor raw
                              //             sockets can be opened (no administrator rights)
                              //  In this case the execution continues whitout
                              //  the benefit of the smart-connect.

#define PR_SUCCESS         0  // Host found
#define PR_ERROR           1  // Ping Error, Ping was performed but ...
                              //   - host didn't replied (not found)
                              //   - routing error
                              //   - TTL expired
                              //   - ... all other icmp error that we don't need
                              //      to know.

// Ping Kind
#define pkCannotPing  1  // see PR_CANNOT_PERFORM comments
#define pkWinHelper   2  // use iphlpapi.dll (only windows)
#define pkRawSocket   3  // use raw sockets (unix/windows)

const byte ICMP_ECHORP  = 0;  // ECHO Reply
const byte ICMP_ECHORQ  = 8;  // ECHO Request
//---------------------------------------------------------------------------
// RAW SOCKET PING STRUCTS
//---------------------------------------------------------------------------
#pragma pack(1)
typedef struct{
    byte ip_hl_v;
    byte ip_tos;
    word ip_len;
    word ip_id ;
    word ip_off;
    byte ip_ttl;
    byte ip_p;
    word ip_sum;
    longword ip_src;
    longword ip_dst;
}TIPHeader;

typedef struct{
    byte ic_type;  // Type of message
    byte ic_code;  // Code
    word ic_cksum; // 16 bit checksum
    word ic_id;    // ID (ic1 : ipv4)
    word ic_seq;   // Sequence
}TIcmpHeader;

typedef struct{
    TIcmpHeader Header;
    byte Data[32]; // use the well known default
}TIcmpPacket, *PIcmpPacket;

typedef struct{
    TIPHeader IPH;
    TIcmpPacket ICmpReply;
}TIcmpReply, *PIcmpReply;
#pragma pack()

//---------------------------------------------------------------------------
class TRawSocketPinger
{
private:
    socket_t FSocket;
    PIcmpPacket SendPacket;
    TIcmpBuffer IcmpBuffer;
    word FId, FSeq;
    void InitPacket();
    word PacketChecksum();
    bool CanRead(int Timeout);
public:
    bool Ping(longword ip_addr, int Timeout);
    TRawSocketPinger();
    ~TRawSocketPinger();
};
typedef TRawSocketPinger *PRawSocketPinger;
//---------------------------------------------------------------------------
class TPinger
{
private:
    PRawSocketPinger RawPinger;
    bool RawAvail;
#ifdef OS_WINDOWS
	bool WinPing(longword ip_addr, int Timeout);
#endif
	bool RawPing(longword ip_addr, int Timeout);
public:
    TPinger();
    ~TPinger();
	bool Ping(char *Host, int Timeout);
	bool Ping(longword ip_addr, int Timeout);
};
typedef TPinger *PPinger;
//---------------------------------------------------------------------------
class TSnapBase // base class endian-aware
{
private:
		bool LittleEndian;
protected:
		longword SwapDWord(longword Value);
		word SwapWord(word Value);
public:
		TSnapBase();
};
//---------------------------------------------------------------------------
class TMsgSocket : public TSnapBase
{
private:
        PPinger Pinger;
        int GetLastSocketError();
        int SockCheck(int SockResult);
        void DestroySocket();
        void SetSocketOptions();
        bool CanWrite(int Timeout);
        void GetLocal();
        void GetRemote();
        void SetSin(sockaddr_in &sin, char *Address, u_short Port);
        void GetSin(sockaddr_in sin, char *Address, u_short &Port);
protected:
        socket_t FSocket;
        sockaddr_in LocalSin;
        sockaddr_in RemoteSin;
        //--------------------------------------------------------------------------
        // low level socket
        void CreateSocket();
        // Called when a socket is assigned externally
        void GotSocket();
        // Returns how many bytes are ready to be read in the winsock buffer
        int WaitingData();
        // Waits until there at least "size" bytes ready to be read or until receive timeout occurs
        int WaitForData(int Size, int Timeout);
        // Clear socket input buffer
        void Purge();
public:
        longword ClientHandle;
        longword LocalBind;
        // Coordinates Address:Port
        char LocalAddress[16];
        char RemoteAddress[16];
        word LocalPort;
        word RemotePort;
        // "speed" of the socket listener (used server-side)
        int WorkInterval;
        // Timeouts : 3 different values for fine tuning.
        // Send timeout should be small since with work with small packets and TCP_NO_DELAY
        //   option, so we don't expect "time to wait".
        // Recv timeout depends of equipment's processing time : we send a packet, the equipment
        //   processes the message, finally it sends the answer. In any case Recv timeout > Send Timeout.
        // PingTimeout is the maximum time interval during which we expect that the PLC answers.
        //   By default is 750 ms, increase it if there are many switch/repeaters.
        int PingTimeout;
        int RecvTimeout;
        int SendTimeout;
        //int ConnTimeout;
        // Output : Last operation error
        int LastTcpError;
        // Output : Connected to the remote Host/Peer/Client
        bool Connected;
        //--------------------------------------------------------------------------
        TMsgSocket();
        virtual ~TMsgSocket();
        // Returns true if "something" can be read during the Timeout interval..
        bool CanRead(int Timeout);
        // Connects to a peer (using RemoteAddress and RemotePort)
        int SckConnect(); // (client-side)
        // Disconnects from a peer (gracefully)
        void SckDisconnect();
        // Disconnects RAW
        void ForceClose();
        // Binds to a local adapter (using LocalAddress and LocalPort) (server-side)
        int SckBind();
        // Listens for an incoming connection (server-side)
        int SckListen();
        // Set an external socket reference (tipically from a listener)
        void SetSocket(socket_t s);
        // Accepts an incoming connection returning a socket descriptor (server-side)
        socket_t SckAccept();
        // Pings the peer before connecting
        bool Ping(char *Host);
        bool Ping(sockaddr_in Addr);
        // Sends a packet
        int SendPacket(void *Data,  int Size);
        // Returns true if a Packet at least of "Size" bytes is ready to be read
        bool PacketReady(int Size);
        // Receives everything
        int Receive(void *Data, int BufSize, int & SizeRecvd);
        // Receives a packet of size specified.
        int RecvPacket(void *Data, int Size);
        // Peeks a packet of size specified without extract it from the socket queue
        int PeekPacket(void *Data, int Size);
        virtual bool Execute();
};

typedef TMsgSocket *PMsgSocket;
//---------------------------------------------------------------------------
void Msg_CloseSocket(socket_t FSocket);
longword Msg_GetSockAddr(socket_t FSocket);
//---------------------------------------------------------------------------
class SocketsLayer
{
private:
#ifdef OS_WINDOWS
    WSADATA wsaData;
#endif
public:
    SocketsLayer();
    ~SocketsLayer();
};

#endif // snap_msgsock_h
