/*****************************************************************************/
/* Copyright (c) 2009 NXP B.V.                                               */
/*                                                                           */
/* This library is free software; you can redistribute it and/or             */
/* modify it under the terms of the GNU Lesser General Public                */
/* License as published by the Free Software Foundation, using               */
/* version 2.1 of the License.                                               */
/*                                                                           */
/* This library 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          */
/* Lesser General Public License for more details.                           */
/*                                                                           */
/* You should have received a copy of the GNU Lesser General Public          */
/* License along with this library; if not, write to the Free Software       */
/* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA   */
/*                                                                           */
/*****************************************************************************/
/**
    @file   phStbIpc.c
    @brief  Implementation of phStbIpc.

@b  Component:  phStbIpc

*//*

Rev Date       Author      Comments
--------------------------------------------------------------------------------
  1 20060928   batelaan    Initial
  2 20061010   batelaan    Crosscheck fixes.
  3 20061213   A.West      Fixes for KERNEL build
  4 20061221   batelaan    Protocol fix.
  5 20070102   batelaan    Make work in kernel mode.
  6 20070111   A.West      Add CALL_UNPROTECTED around socket calls
  7 20070209   batelaan    Correct debug print text.
  8 20070212   batelaan    Don't use 'userspace' sys_* socket functions.
  9 20070214   raiyat      Fix read problems.
 10 20070215   batelaan    Crosscheck fixes.
 11 20070228   raiyat      reduce dbg warnings
 12 20070326   batelaan    Set TCP_NODELAY for tcp sockets.
 13 20071210   hutchinson  Merged in EB's 20071128 release.
 14 20080205   batelaan    Update file copyright/license text
 15 20080221   batelaan    Prevent spinning in Read/WriteExactly.
 16 20080410   batelaan    Use O_SYNC in attempt to flush data.
 17 20080603   batelaan    Use phStbNetDev_GetSubNet()
 18 20080604   batelaan    Fix compilation warning
 19 20090121   batelaan    Update license header
--------------------------------------------------------------------------------
    For consistency and standardisation retain the Section Separators.
*/

// TODO: split in CE and Linux specific files

/***********************************************
* INCLUDE FILES                                *
************************************************/

#include <phStbDbg.h>
#include <phStbIpc.h>
#include <phStbNetDev.h>
#include <prjconfig.h>

#include <string.h>

#if TMFL_OS_IS_CE
  #include "compat_nxp/nxp_kernel.h"
  #include "winsock2.h"
  //#include "compat_sock.h"
  #include "compat_fs.h"
#else
#if TMFL_MODE_KERNEL
  #include <linux/unistd.h>
  #include <linux/kernel.h>
  #include <linux/module.h>
  #include <linux/net.h>
  #include <linux/socket.h>
  #include <linux/in.h>
  #ifdef PH_STBIPC_TCPIP_ENABLED
    #include <linux/tcp.h>
  #endif
  #include <asm/fcntl.h>
#else
  #include <stdio.h>
  #include <errno.h>
  #include <unistd.h>
  #include <stdlib.h>
  #include <sys/socket.h>
  #ifdef PH_STBIPC_TCPIP_ENABLED
    #include <netinet/in.h>
    #include <netinet/tcp.h>
  #endif
#endif /* #if TMFL_MODE_KERNEL */
#endif /* #if TMFL_OS_IS_CE */

#ifdef PH_STBIPC_TIPC_ENABLED
  #include <net/tipc/tipc.h>
#endif

/***********************************************
* LOCAL MACROS                                 *
* recommendation only: <MODULE><_WORD>+        *
************************************************/

/** Macro chosing between userspace or kernelspace implementations. */
#if TMFL_OS_IS_LINUX
#  if TMFL_MODE_KERNEL
#    define IMPL_SWITCH(function) StbIpc_Sock_##function
#  else
#    define IMPL_SWITCH(function) function
#  endif
#  define SOCKET_CLOSE      IMPL_SWITCH(close)
#  define SOCKET_READ       IMPL_SWITCH(read)
#  define SOCKET_WRITE      IMPL_SWITCH(write)
#else
#    define IMPL_SWITCH(function) function
#    define SOCKET_CLOSE      IMPL_SWITCH(closesocket)
#    define SOCKET_READ       IMPL_SWITCH(recv)
#    define SOCKET_WRITE      IMPL_SWITCH(send)
#endif

#define SOCKET_CREATE     IMPL_SWITCH(socket)
#define SOCKET_BIND       IMPL_SWITCH(bind)
#define SOCKET_LISTEN     IMPL_SWITCH(listen)
#define SOCKET_ACCEPT     IMPL_SWITCH(accept)
#define SOCKET_CONNECT    IMPL_SWITCH(connect)
#define SOCKET_GETSOCKOPT IMPL_SWITCH(getsockopt)
#define SOCKET_SETSOCKOPT IMPL_SWITCH(setsockopt)

/** Number of connections that a server can 'queue',
    i.e. not yet handled by the accept() call.
    @see phStbIpc_ServerCreate()
*/
#define MAX_CONNECTION_BACKLOG 10

#define PH_STBIPC_HANDSHAKE_CLIENT_PREFIX "client: "
#define PH_STBIPC_HANDSHAKE_SERVER_PREFIX "server: "
#define PH_STBIPC_HANDSHAKE_PREFIX_LEN 8U

#if TMFL_MODE_KERNEL && TMFL_OS_IS_LINUX
  #define THIS_MODULE_DESCRIPTION "Client/Server IPC API"
  MODULE_LICENSE("GPL v2");
  MODULE_DESCRIPTION(THIS_MODULE_DESCRIPTION);
  MODULE_AUTHOR("NXP B.V.");
#else
  #define EXPORT_SYMBOL(symbol)
#endif

/******************************************************************
* LOCAL TYPEDEFS                                                  *
* recommendation only:                [p]phStbIpc_<Word>+_t       *
*******************************************************************/

typedef union phStbIpc_SockAddr {
  #ifdef PH_STBIPC_TCPIP_ENABLED
    struct sockaddr_in   inetAddr;
  #endif
  #ifdef PH_STBIPC_TIPC_ENABLED
    struct sockaddr_tipc tipcAddr;
  #endif
} phStbIpc_SockAddr_t;

typedef struct phStbIpc_AddrData {
    int domain;
    int type;
    int protocol;
    unsigned int port;
    uint32_t nnn[4];
    phStbIpc_SockAddr_t sockAddr;
    int sockAddrLen;
} phStbIpc_AddrData_t;

/******************************************************************
* STATIC FUNCTION PROTOTYPES                  <Module>_<Word>+    *
*******************************************************************/

/******************************************************************
* STATIC DATA                  g[k|p|kp|pk|kpk]<Module>_<Word>+   *
*******************************************************************/

DBG_UNIT(gphStbIpc_DbgUnit);
static bool gStbIpc_Initialised = false;

static const phStbIpc_Endianness_t gkStbIpc_Endianness
#if TMFL_ENDIAN == TMFL_ENDIAN_BIG
    = phStbIpc_Endianness_Big;
#else
    = phStbIpc_Endianness_Little;
#endif

/**************************************************************************
* EXPORTED DATA                     g[k|p|kp|pk|kpk]phStbIpc_<Word>+      *
***************************************************************************/

/*******************************************************************************
* FUNCTION IMPLEMENTATION  <Module>[_<Word>+] for static functions             *
*                          ph[<layer>]<Module>[_<Word>+] for exported functions*
********************************************************************************/

/** Initialise component. Automatically called by phStbIpc_ServerCreate()
    and phStbIpc_ClientCreate().
*/
static tmErrorCode_t StbIpc_Init(void)
{
    if (!gStbIpc_Initialised)
    {
#if TMFL_OS_IS_CE
        WORD wVersionRequested;
        WSADATA wsaData;
        int err;

        wVersionRequested = MAKEWORD( 2, 2 );

        err = WSAStartup( wVersionRequested, &wsaData );
        if ( err != 0 ) {
            /* Tell the user that we could not find a usable */
            /* WinSock DLL.                                  */
            return -1;
        }

        /* Confirm that the WinSock DLL supports 2.2.*/
        /* Note that if the DLL supports versions greater    */
        /* than 2.2 in addition to 2.2, it will still return */
        /* 2.2 in wVersion since that is the version we      */
        /* requested.                                        */

        if ( LOBYTE( wsaData.wVersion ) != 2 ||
                HIBYTE( wsaData.wVersion ) != 2 ) {
            /* Tell the user that we could not find a usable */
            /* WinSock DLL.                                  */
            WSACleanup( );
            return -1;
        }

        /* The WinSock DLL is acceptable. Proceed. */
#endif

        gStbIpc_Initialised = true;
        DBG_REGISTER_MODULE(gphStbIpc_DbgUnit, "StbIpc");
        DBG_ENABLE_FLAGS(gphStbIpc_DbgUnit, DBG_WARNING);
        // DBG_ENABLE_FLAGS(gphStbIpc_DbgUnit, DBG_ALL_FLAGS);
    }
    return TM_OK;
}

/******************************************************************************/

/** Handles system call error return value 'intelligently'.
    Prints errors.
    In Linux kernel mode it uses the return value of the system call, negated, which is equivalent
    to the errno value after the system call returns in user space.
    In Linux user mode it uses errno directly.
    The errno value is then left-shifted by #PH_STBIPC_SYSTEM_CALL_RETURN_VALUE_SHIFT,
    and bitwise or-ed with #PH_ERR_STBIPC_SYSTEM_CALL.
    @param st @b IN System call return value.
    @param msg @b IN Indication of system call that was performed.
    @return Error code #PH_ERR_STBIPC_SYSTEM_CALL, with the system error
    code shifted by #PH_STBIPC_SYSTEM_CALL_RETURN_VALUE_SHIFT bitwise or'ed.
    @see PH_STBIPC_SYSTEM_CALL_ERR
*/
static tmErrorCode_t StbIpc_ReturnCodeToErrorCode(
    int st,
    char *msg
)
{
    tmErrorCode_t err;

#if TMFL_OS_IS_CE
    err = PH_STBIPC_SYSTEM_CALL_ERR (WSAGetLastError ());
    DBG_PRINT3(gphStbIpc_DbgUnit, DBG_LEVEL_1, "phStbIpc %s => %d err=0x%08x",
               msg, st, err);
#else

#if TMFL_MODE_KERNEL
    err = PH_STBIPC_SYSTEM_CALL_ERR(-st);
    DBG_PRINT3(gphStbIpc_DbgUnit, DBG_LEVEL_1, "phStbIpc %s => %d err=0x%08x",
               msg, st, err);
#else
    {
        int savedErrno = errno;
        char *errnoMsg = strerror(errno);
        perror(msg);
        err = PH_STBIPC_SYSTEM_CALL_ERR(savedErrno);
        DBG_PRINT5(gphStbIpc_DbgUnit, DBG_LEVEL_1, "phStbIpc %s => %d err=0x%08x errno=%d %s",
                   msg, st, err, savedErrno, errnoMsg);

    }
#endif

#endif // TMFL_OS_IS_CE
    return err;
}

/******************************************************************************/

/** Parse address string into a format easily usable by the socket API.
    @param address @b IN Address specification.
    @param pAddrData @b OUT Address data obtained from the address specification.
    @return One of:
    - #PH_ERR_STBIPC_PROTOCOL_NOT_ENABLED
    - #PH_ERR_STBTIPC_PROTOCOL_NOT_ENABLED
    - #PH_ERR_STBIPC_ADDRESS_FORMAT
*/
static tmErrorCode_t StbIpc_ParseAddr(
    char *address,
    phStbIpc_AddrData_t *pAddrData
)
{
    tmErrorCode_t err = TM_OK;

    memset(pAddrData, 0, sizeof(*pAddrData));
    pAddrData->type = SOCK_STREAM;
    /* If we use
       pAddrData->type = SOCK_DGRAM;
       we get a "listen(): Operation not supported" error. */

    if (strncmp(address, PH_STBIPC_TCPIP_ADDRESS_PREFIX, sizeof(PH_STBIPC_TCPIP_ADDRESS_PREFIX) - 1) == 0)
    {
  #ifndef PH_STBIPC_TCPIP_ENABLED
        err = PH_ERR_STBIPC_PROTOCOL_NOT_ENABLED;
        goto RETURN;
  #else
        int ret;
        unsigned int mipsID;
        /* Check if mips1/mips2 type IP address is specified. */
        ret = sscanf(address + sizeof(PH_STBIPC_TCPIP_ADDRESS_PREFIX) - 1,
                     "mips%u:%u",
                     &mipsID,
                     &pAddrData->port);
        if (ret == 2)
        {
            char subNet[PH_STBNETDEV_SUBNET_LEN + 1];
            if ((mipsID < 1) || (mipsID > 2))
            {
                err = PH_ERR_STBIPC_ADDRESS_FORMAT;
                goto RETURN;
            }
            err = phStbNetDev_GetSubNet(subNet);
            if (err != TM_OK)
            {
                err = PH_ERR_STBIPC_ADDRESS_LOOKUP;
                goto RETURN;
            }
            pAddrData->nnn[3] = mipsID;
            ret = sscanf(subNet,
                         "%u.%u.%u",
                         &pAddrData->nnn[0],
                         &pAddrData->nnn[1],
                         &pAddrData->nnn[2]);
            if (ret != 3)
            {
                err = PH_ERR_STBIPC_ADDRESS_LOOKUP;
                goto RETURN;
            }
        }
        else
        {
            /* Check if aaa.bbb.ccc.ddd type IP address is specified. */
            ret = sscanf(address + sizeof(PH_STBIPC_TCPIP_ADDRESS_PREFIX) - 1,
                         "%u.%u.%u.%u:%u",
                         &pAddrData->nnn[0],
                         &pAddrData->nnn[1],
                         &pAddrData->nnn[2],
                         &pAddrData->nnn[3],
                         &pAddrData->port);
            if (ret != 5)
            {
                err = PH_ERR_STBIPC_ADDRESS_FORMAT;
                goto RETURN;
            }
        }
        pAddrData->domain = PF_INET;
        pAddrData->sockAddr.inetAddr.sin_family = AF_INET;
        pAddrData->sockAddr.inetAddr.sin_port   = htons(pAddrData->port);
        pAddrData->sockAddr.inetAddr.sin_addr.s_addr   = htonl(
            (pAddrData->nnn[0] << IN_CLASSA_NSHIFT) |
            (pAddrData->nnn[1] << IN_CLASSB_NSHIFT) |
            (pAddrData->nnn[2] << IN_CLASSC_NSHIFT) |
            pAddrData->nnn[3]);
        //printf("address = 0x%08x port=%d\n", pAddrData->sockAddr.inetAddr.sin_addr.s_addr, pAddrData->sockAddr.inetAddr.sin_port);
        pAddrData->sockAddrLen = sizeof(struct sockaddr_in);
        /* possibly in future: IPPROTO_UDP for efficiency? */
        goto RETURN;
  #endif
    }

    if (strncmp(address, PH_STBIPC_TIPC_ADDRESS_PREFIX, sizeof(PH_STBIPC_TIPC_ADDRESS_PREFIX) - 1) == 0)
    {
  #ifndef PH_STBIPC_TIPC_ENABLED
        err = PH_ERR_STBIPC_PROTOCOL_NOT_ENABLED;
        goto RETURN;
  #else
#error Not supported anymore.
        int ret;
        ret = sscanf(address + sizeof(PH_STBIPC_TIPC_ADDRESS_PREFIX) - 1,
                     "%u.%u.%u:%u",
                     &pAddrData->nnn[0],
                     &pAddrData->nnn[1],
                     &pAddrData->nnn[2],
                     &pAddrData->port);
        if (ret != 4)
        {
            err = PH_ERR_STBIPC_ADDRESS_FORMAT;
            goto RETURN;
        }
        pAddrData->domain = AF_TIPC;
        /* already 0: pAddrData->protocol = 0; */
        pAddrData->sockAddr.tipcAddr.family = AF_TIPC;
        pAddrData->sockAddr.tipcAddr.addrtype = TIPC_ADDR_ID;
        pAddrData->sockAddr.tipcAddr.scope = TIPC_NODE_SCOPE;
        pAddrData->sockAddr.tipcAddr.addr.id.node =
            tipc_addr(
                pAddrData->nnn[0],
                pAddrData->nnn[1],
                pAddrData->nnn[2]);
        //printf("node = 0x%08x\n", pAddrData->sockAddr.tipcAddr.addr.id.node);
        pAddrData->sockAddrLen = sizeof(struct sockaddr_tipc);
        goto RETURN;
  #endif
    }

    err = PH_ERR_STBIPC_ADDRESS_FORMAT;
  RETURN:
    DBG_PRINT2(gphStbIpc_DbgUnit,
               (err == TM_OK) ? DBG_INTERFACE_LEAVE : (DBG_WARNING|DBG_INTERFACE_LEAVE),
               "StbIpc_ParseAddr addr='%s' => 0x%08x",
               address, err);
    return err;
}

/******************************************************************************/

#if TMFL_MODE_KERNEL && TMFL_OS_IS_LINUX
static int StbIpc_Sock_socket(
    int domain,
    int type,
    int protocol)
{
    struct socket *sock;
    int st;

    st = sock_create(domain, type, protocol, &sock);

    DBG_PRINT2(gphStbIpc_DbgUnit,
               DBG_INTERFACE_LEAVE,
               "StbIpc_Sock_socket => sock=0x%p st=%d", sock, st);
    /* Pointers in kernel space are 0x80000000 and higher, so negative int's.
       Negative int's indicate error values, so ensure return value is positive
       on success. */
    return (st == 0) ? -(int)sock : st;
}

#if 0
/* Unfortunately sock_getsockopt is not exported by the kernel! */
static int StbIpc_Sock_getsockopt(
   int fd,
   int level,
   int optname,
   void *optval,
   int *optlen)
{
    struct socket *sock = (struct socket*)-fd;
    int st;

    if (level == SOL_SOCKET)
    {
        st = sock_getsockopt(sock, level, optname, optval, optlen);
    }
    else
    {
        st = sock->ops->getsockopt(sock, level, optname, optval, optlen);
    }

    DBG_PRINT1(gphStbIpc_DbgUnit,
               DBG_INTERFACE_LEAVE,
               "StbIpc_Sock_getsockopt => %d", st);
    return st;
}
#endif

static int StbIpc_Sock_setsockopt(
   int fd,
   int level,
   int optname,
   void *optval,
   int optlen)
{
    struct socket *sock = (struct socket*)-fd;
    int st;

    if (level == SOL_SOCKET)
    {
        st = sock_setsockopt(sock, level, optname, optval, optlen);
    }
    else
    {
        st = sock->ops->setsockopt(sock, level, optname, optval, optlen);
    }

    DBG_PRINT1(gphStbIpc_DbgUnit,
               DBG_INTERFACE_LEAVE,
               "StbIpc_Sock_setsockopt => %d", st);
    return st;
}
#endif

/** Creates a socket.
    For TCP sockets sets TCP_NODELAY option.
    @param @b IN serverAddress Server address specification, passed to StbIpc_ParseAddr().
    Only used to determine address domain, type, and protocol (e.g. AF_INET/AF_TIPC).
    @param @b IN pAddrData Address data. Output from StbIpc_ParseAddr().
    @param @b OUT pSock Socket that was created.
 */
static tmErrorCode_t StbIpc_SocketCreate(
    char *serverAddress,
    phStbIpc_AddrData_t *pAddrData,
    int *pSock
)
{
    tmErrorCode_t err;
    int sock;

    err = StbIpc_ParseAddr(serverAddress, pAddrData);
    if (err != TM_OK)
    {
        goto RETURN;
    }

    sock = SOCKET_CREATE(
        pAddrData->domain,
        pAddrData->type,
        pAddrData->protocol);
    if (sock < 0)
    {
        err = StbIpc_ReturnCodeToErrorCode(sock, "socket()");
        goto RETURN;
    }

    if (pAddrData->type == SOCK_STREAM)
    {
        int flag = 1;
        int st;
        st = SOCKET_SETSOCKOPT(
            sock,            /* socket affected */
            IPPROTO_TCP,     /* set option at TCP level */
            TCP_NODELAY,     /* name of option */
            (void*)&flag,
            sizeof(int));    /* length of option value */
        if (st < 0)
        {
            err = StbIpc_ReturnCodeToErrorCode(sock, "setsockopt()");
            goto RETURN;
        }
    }

    err = TM_OK;
    *pSock = sock;

  RETURN:
    return err;
}

/******************************************************************************/
/******************************************************************************/
/*                               S E R V E R                                  */
/******************************************************************************/
/******************************************************************************/

#if TMFL_MODE_KERNEL && TMFL_OS_IS_LINUX
static int StbIpc_Sock_bind(
    int fd,
    struct sockaddr *addr,
    int addrlen
)
{
    struct socket *sock = (struct socket*)-fd;
    int st;

    st = kernel_bind(sock, addr, addrlen);

    DBG_PRINT1(gphStbIpc_DbgUnit,
               DBG_INTERFACE_LEAVE,
               "StbIpc_Sock_bind => st=%d", st);
    return st;
}

static int StbIpc_Sock_listen(
    int fd,
    int backlog
)
{
    struct socket *sock = (struct socket*)-fd;
    int st;

    st = kernel_listen(sock, backlog);

    DBG_PRINT1(gphStbIpc_DbgUnit,
               DBG_INTERFACE_LEAVE,
               "StbIpc_Sock_listen => st=%d", st);
    return st;
}
#endif

tmErrorCode_t phStbIpc_ServerCreate(
    char *serverAddress,
    pphStbIpc_Server_t *pServer
)
{
    tmErrorCode_t err;
    int sock = -1;
    int ret;
    phStbIpc_AddrData_t addressData;

    StbIpc_Init();

    DBG_PRINT1(gphStbIpc_DbgUnit, DBG_INTERFACE_ENTER, "phStbIpc_ServerCreate addr='%s'",
               serverAddress);

    err = StbIpc_SocketCreate(serverAddress, &addressData, &sock);
    if (err != TM_OK)
    {
        goto RETURN;
    }

#if 0
	// Why on earth would we want to bind to every interface when
	// specifically told not to?
    addressData.sockAddr.inetAddr.sin_addr.s_addr = INADDR_ANY;
#endif

    ret = SOCKET_BIND(sock, (struct sockaddr *)&addressData.sockAddr, addressData.sockAddrLen);

    if (ret != 0)
    {
        err = StbIpc_ReturnCodeToErrorCode(ret, "bind()");
        goto RETURN;
    }

    ret = SOCKET_LISTEN(sock, MAX_CONNECTION_BACKLOG);
    if (ret != 0)
    {
        err = StbIpc_ReturnCodeToErrorCode(ret, "listen()");
        goto RETURN;
    }

    err = TM_OK;
    *pServer = (pphStbIpc_Server_t)sock;

  RETURN:
    DBG_PRINT3(gphStbIpc_DbgUnit, DBG_INTERFACE_LEAVE, "phStbIpc_ServerCreate addr='%s' => server=0x%x err=0x%08x",
               serverAddress, sock, err);
    return err;
}
EXPORT_SYMBOL(phStbIpc_ServerCreate);

/******************************************************************************/

#if TMFL_MODE_KERNEL && TMFL_OS_IS_LINUX
static int StbIpc_Sock_close(
    int fd
)
{
    struct socket *sock = (struct socket*)-fd;

    if (fd < 0) return -1; // TODO
    sock_release(sock);

    DBG_PRINT0(gphStbIpc_DbgUnit,
               DBG_INTERFACE_LEAVE,
               "StbIpc_Sock_close");
    return 0;
}
#endif

tmErrorCode_t phStbIpc_ServerDestroy(
    pphStbIpc_Server_t server
)
{
    tmErrorCode_t err;
    int ret;
    int sock = (int)server;

    DBG_PRINT1(gphStbIpc_DbgUnit, DBG_INTERFACE_ENTER, "phStbIpc_ServerDestroy server=%p",
               server);
    ret = SOCKET_CLOSE(sock);
    if (ret < 0)
    {
        err = StbIpc_ReturnCodeToErrorCode(ret, "close()");
        goto RETURN;
    }
    err = TM_OK;

  RETURN:
    DBG_PRINT2(gphStbIpc_DbgUnit, DBG_INTERFACE_LEAVE, "phStbIpc_ServerDestroy server=%p => 0x%08x",
               server, err);
    return err;
}
EXPORT_SYMBOL(phStbIpc_ServerDestroy);

/******************************************************************************/

#if TMFL_MODE_KERNEL && TMFL_OS_IS_LINUX
static int StbIpc_Sock_accept(
    int fd,
    struct sockaddr *addr,
    int *addrlen
)
{
    struct socket *sock = (struct socket*)-fd;
    struct socket *newsock;
    int flags = 0;
    int st;

    st = kernel_accept(sock, &newsock, flags);

    DBG_PRINT2(gphStbIpc_DbgUnit,
               DBG_INTERFACE_LEAVE,
               "StbIpc_Sock_accept => newsock=%p st=%d", newsock, st);
    /* Pointers in kernel space are 0x80000000 and higher, so negative int's.
       Negative int's indicate error values, so ensure return value is positive
       on success. */
    return (st == 0) ? -(int)newsock : st;
}
#endif

tmErrorCode_t phStbIpc_ServerAccept(
    pphStbIpc_Server_t server,
    pphStbIpc_Connection_t *pConnection
)
{
    tmErrorCode_t err;
    int serverSock = (int)server;
    phStbIpc_SockAddr_t sockAddr;
    uint sockAddrLen = sizeof (sockAddr);
    int sock;
    int nAttempts = 5;

    DBG_PRINT1(gphStbIpc_DbgUnit, DBG_INTERFACE_ENTER, "phStbIpc_ServerAccept server=%p",
               server);

    /* There seems to be a problem with linux on the PNX8335, where accept() return -EINVAL.
       This can be circumvented by just retrying. */
    do
    {
        sock = SOCKET_ACCEPT(serverSock, (struct sockaddr*)&sockAddr, &sockAddrLen);

        if (sock >= 0)
        {
            break;
        }
        else
        {
            err = StbIpc_ReturnCodeToErrorCode(sock, "accept()");
            if ((nAttempts == 1) || (sock != -EINVAL))
            {
                goto RETURN;
            }
            DBG_PRINT2(gphStbIpc_DbgUnit, DBG_WARNING, "phStbIpc_ServerAccept server=%p => err=0x%08x",
                       server, err);
        }
        nAttempts--;
    }
    while (nAttempts > 0);

    *pConnection = (pphStbIpc_Connection_t)sock;
    err = TM_OK;

  RETURN:
    DBG_PRINT3(gphStbIpc_DbgUnit, DBG_INTERFACE_LEAVE, "phStbIpc_ServerAccept server=%p => conn=%p err=0x%08x",
               server, *pConnection, err);
    return err;
}
EXPORT_SYMBOL(phStbIpc_ServerAccept);

/******************************************************************************/
/******************************************************************************/
/*                               C L I E N T                                  */
/******************************************************************************/
/******************************************************************************/

#if TMFL_MODE_KERNEL && TMFL_OS_IS_LINUX
static int StbIpc_Sock_connect(
    int fd,
    struct sockaddr *addr,
    int addrlen
)
{
    struct socket *sock = (struct socket*)-fd;
    int flags = O_RDWR|O_SYNC;
    int st;

    st = kernel_connect(sock, addr, addrlen, flags);

    DBG_PRINT1(gphStbIpc_DbgUnit,
               DBG_INTERFACE_LEAVE,
               "StbIpc_Sock_connect => st=%d", st);
    return st;
}
#endif

tmErrorCode_t phStbIpc_ClientCreate(
    char *serverAddress,
    pphStbIpc_Connection_t *pConnection
)
{
    tmErrorCode_t err;
    int sock;
    int ret;
    phStbIpc_AddrData_t addressData;

    StbIpc_Init();

    DBG_PRINT1(gphStbIpc_DbgUnit, DBG_INTERFACE_ENTER, "phStbIpc_ClientCreate addr='%s'",
               serverAddress);
    err = StbIpc_SocketCreate(serverAddress, &addressData, &sock);
    if (err != TM_OK)
    {
        goto RETURN;
    }

    ret = SOCKET_CONNECT(sock, (struct sockaddr*)&addressData.sockAddr, addressData.sockAddrLen);

    if (ret < 0)
    {
        err = StbIpc_ReturnCodeToErrorCode(ret, "connect()");
        goto RETURN;
    }
    *pConnection = (pphStbIpc_Connection_t)sock;
    err = TM_OK;

  RETURN:
    DBG_PRINT3(gphStbIpc_DbgUnit, DBG_INTERFACE_LEAVE, "phStbIpc_ClientCreate addr='%s' => conn=%p err=0x%08x",
               serverAddress, *pConnection, err);
    return err;
}
EXPORT_SYMBOL(phStbIpc_ClientCreate);

/******************************************************************************/
/******************************************************************************/
/*                               C O M M O N                                  */
/******************************************************************************/
/******************************************************************************/

#if TMFL_MODE_KERNEL && TMFL_OS_IS_LINUX
static int StbIpc_Sock_read(
    int fd,
    uint8_t *pBuffer,
    size_t size
)
{
    struct socket *sock = (struct socket*)-fd;
    int st;
    struct iovec iov;
    struct msghdr msg;
    int flags = 0;

    msg.msg_control = NULL;
    msg.msg_controllen = 0;
    msg.msg_iovlen = 1;
    msg.msg_iov = &iov;
    msg.msg_flags = 0;
    iov.iov_len = size;
    iov.iov_base = pBuffer;
    msg.msg_name = NULL;
    msg.msg_namelen = 0;

    st = sock_recvmsg(sock, &msg, size, flags);

    DBG_PRINT1(gphStbIpc_DbgUnit,
               DBG_INTERFACE_LEAVE,
               "StbIpc_Sock_read => %d", st);
    return st;
}
#endif

tmErrorCode_t phStbIpc_Read(
    pphStbIpc_Connection_t connection,
    size_t len,
    uint8_t *pBuffer,
    size_t *pLenRead
)
{
    tmErrorCode_t err;
    int sock = (int)connection;
    int ret;

    DBG_PRINT2(gphStbIpc_DbgUnit, DBG_INTERFACE_ENTER, "phStbIpc_Read conn=%p len=%u",
               connection, len);

#if TMFL_OS_IS_LINUX
    ret = SOCKET_READ(sock, pBuffer, len);
#else
    ret = SOCKET_READ(sock, pBuffer, len, 0);
#endif

    if (ret < 0)
    {
        *pLenRead = 0;
        err = StbIpc_ReturnCodeToErrorCode(ret, "read()");
        goto RETURN;
    }
    *pLenRead = ret;
    err = TM_OK;

  RETURN:
    DBG_PRINT4(gphStbIpc_DbgUnit, DBG_INTERFACE_LEAVE,
        "phStbIpc_Read conn=%p len=%u => 0x%x (read=%u)",
        connection, len, err, *pLenRead);
    return err;
}
EXPORT_SYMBOL(phStbIpc_Read);

/******************************************************************************/

tmErrorCode_t phStbIpc_ReadExactly(
    pphStbIpc_Connection_t connection,
    uint8_t *pBuffer,
    size_t len,
    size_t *pLenRead
)
{
    tmErrorCode_t err;
    size_t lenReadTotal = 0;
    size_t lenRead;
    do
    {
        err = phStbIpc_Read(connection, len - lenReadTotal, pBuffer + lenReadTotal, &lenRead);
        lenReadTotal += lenRead;
    }
    while ((lenReadTotal < len) && (err == TM_OK) && (lenRead > 0));
    *pLenRead = lenReadTotal;
    if ((err == TM_OK) && (lenReadTotal != len))
    {
        err = PH_ERR_STBIPC_IO;
    }
    return err;
}
EXPORT_SYMBOL(phStbIpc_ReadExactly);

/******************************************************************************/

#if TMFL_MODE_KERNEL && TMFL_OS_IS_LINUX
static int StbIpc_Sock_write(
    int fd,
    uint8_t *pBuffer,
    size_t size
)
{
    struct socket *sock = (struct socket*)-fd;
    int st;
    struct iovec iov;
    struct msghdr msg;

    msg.msg_name = NULL;
    msg.msg_namelen = 0;
    msg.msg_control = NULL;
    msg.msg_controllen = 0;
    msg.msg_iovlen = 1;
    msg.msg_iov = &iov;
    msg.msg_flags = 0;
    iov.iov_len = size;
    iov.iov_base = pBuffer;

    st = sock_sendmsg(sock, &msg, size);

    DBG_PRINT1(gphStbIpc_DbgUnit,
               DBG_INTERFACE_LEAVE,
               "StbIpc_Sock_write => %d", st);
    return st;
}
#endif

tmErrorCode_t phStbIpc_Write(
    pphStbIpc_Connection_t connection,
    uint8_t *pBuffer,
    size_t len,
    size_t *pLenWritten
)
{
    tmErrorCode_t err;
    int sock = (int)connection;
    int ret;

    DBG_PRINT2(gphStbIpc_DbgUnit, DBG_INTERFACE_ENTER, "phStbIpc_Write conn=%p len=%u",
               connection, len);

#if TMFL_OS_IS_LINUX
	ret = SOCKET_WRITE(sock, pBuffer, len);
#else
	ret = SOCKET_WRITE(sock, pBuffer, len, 0);
#endif

    if (ret < 0)
    {
        *pLenWritten = 0;
        err = StbIpc_ReturnCodeToErrorCode(ret, "write()");
        goto RETURN;
    }
    *pLenWritten = ret;
    err = TM_OK;

  RETURN:
    DBG_PRINT4(gphStbIpc_DbgUnit, DBG_INTERFACE_LEAVE,
        "phStbIpc_Write conn=%p len=%u => 0x%x (written=%u)",
        connection, len, err, *pLenWritten);
    return err;
}
EXPORT_SYMBOL(phStbIpc_Write);

/******************************************************************************/

tmErrorCode_t phStbIpc_WriteExactly(
    pphStbIpc_Connection_t connection,
    uint8_t *pBuffer,
    size_t len,
    size_t *pLenWritten
)
{
    tmErrorCode_t err;
    size_t lenWrittenTotal = 0;
    size_t lenWritten;
    do
    {
        err = phStbIpc_Write(connection, pBuffer + lenWrittenTotal, len - lenWrittenTotal, &lenWritten);
        lenWrittenTotal += lenWritten;
    }
    while ((lenWrittenTotal < len) && (err == TM_OK) && (lenWritten > 0));
    *pLenWritten = lenWrittenTotal;
    if ((err == TM_OK) && (lenWrittenTotal != len))
    {
        err = PH_ERR_STBIPC_IO;
    }
    return err;
}
EXPORT_SYMBOL(phStbIpc_WriteExactly);

/******************************************************************************/

tmErrorCode_t phStbIpc_Close(
    pphStbIpc_Connection_t connection
)
{
    tmErrorCode_t err;
    int sock = (int)connection;
    int ret;

    DBG_PRINT1(gphStbIpc_DbgUnit, DBG_INTERFACE_ENTER, "phStbIpc_Close conn=%p",
               connection);

    ret = SOCKET_CLOSE(sock);
    if (ret < 0)
    {
        err = StbIpc_ReturnCodeToErrorCode(ret, "close()");
        goto RETURN;
    }
    err = TM_OK;

  RETURN:
    DBG_PRINT2(gphStbIpc_DbgUnit, DBG_INTERFACE_LEAVE, "phStbIpc_Close conn=%p => 0x%08x",
               connection, err);
    return err;
}
EXPORT_SYMBOL(phStbIpc_Close);

/******************************************************************************/

tmErrorCode_t phStbIpc_ServerHandShake(
    pphStbIpc_Server_t server,
    pphStbIpc_Connection_t connection,
    char *message,
    phStbIpc_Endianness_t *pEndianness
)
{
    tmErrorCode_t err;
    size_t lenRead;
    size_t lenWritten;
    char buf[PH_STBIPC_HANDSHAKE_MAX_MSG + PH_STBIPC_HANDSHAKE_PREFIX_LEN + 1];
    bool closeConnection = false;
    phStbIpc_Endianness_t endianness;
    size_t messageLen;
    size_t totalMessageLen;

    DBG_PRINT2(gphStbIpc_DbgUnit, DBG_INTERFACE_ENTER, "phStbIpc_ServerHandShake server=%p message='%s'",
               server, message);

    messageLen = strlen(message);
    if (messageLen > PH_STBIPC_HANDSHAKE_MAX_MSG)
    {
        err = PH_ERR_STBIPC_HANDSHAKE_LEN;
        goto RETURN;
    }
    totalMessageLen = 1 + PH_STBIPC_HANDSHAKE_PREFIX_LEN + messageLen;

    if (connection == NULL)
    {
        err = phStbIpc_ServerAccept(server, &connection);
        if (err != TM_OK)
        {
            goto RETURN;
        }
        closeConnection = true;
    }

    err = phStbIpc_ReadExactly(connection,
                               (uint8_t*)buf,
                               totalMessageLen,
                               &lenRead);
    if (err != TM_OK)
    {
        goto RETURN;
    }

    /* Terminate message read: */
    buf[lenRead] = '\0';
    DBG_PRINT1(gphStbIpc_DbgUnit, DBG_LEVEL_2, "phStbIpc_ServerHandShake: from client '%s'", buf);

    endianness = (phStbIpc_Endianness_t)buf[0];
    if (((endianness != phStbIpc_Endianness_Big) && (endianness != phStbIpc_Endianness_Little)) ||
        (strncmp(buf + 1, PH_STBIPC_HANDSHAKE_CLIENT_PREFIX, PH_STBIPC_HANDSHAKE_PREFIX_LEN) != 0) ||
        (strcmp(buf + 1 + PH_STBIPC_HANDSHAKE_PREFIX_LEN, message) != 0))
    {
        err = PH_ERR_STBIPC_HANDSHAKE_SERVER;
        goto RETURN;
    }
    *pEndianness = endianness;

    /* Setup our response. */
    buf[0] = gkStbIpc_Endianness;
    strncpy(buf + 1, PH_STBIPC_HANDSHAKE_SERVER_PREFIX, PH_STBIPC_HANDSHAKE_PREFIX_LEN);
    err = phStbIpc_WriteExactly(connection,
                                (uint8_t*)buf,
                                totalMessageLen,
                                &lenWritten);
  RETURN:
    if (closeConnection)
    {
        tmErrorCode_t err1;
        err1 = phStbIpc_Close(connection);
        if (err == TM_OK)
        {
            err = err1;
        }
    }
    DBG_PRINT4(gphStbIpc_DbgUnit, DBG_INTERFACE_LEAVE,
        "phStbIpc_ServerHandShake server=%p conn=%p message='%s' => 0x%08x",
        server, connection, message, err);
    return err;
}
EXPORT_SYMBOL(phStbIpc_ServerHandShake);

/******************************************************************************/

tmErrorCode_t phStbIpc_ClientHandShake(
    char *pServerAddress,
    pphStbIpc_Connection_t connection,
    char *message,
    phStbIpc_Endianness_t *pEndianness
)
{
    tmErrorCode_t err;
    size_t lenRead;
    size_t lenWritten;
    char buf[PH_STBIPC_HANDSHAKE_MAX_MSG + PH_STBIPC_HANDSHAKE_PREFIX_LEN + 1];
    bool closeConnection = false;
    phStbIpc_Endianness_t endianness;
    size_t messageLen;
    size_t totalMessageLen;

    if ((pServerAddress == NULL) && (connection != NULL))
    {
        pServerAddress = "<unspecified>";
    }

    DBG_PRINT3(gphStbIpc_DbgUnit, DBG_INTERFACE_ENTER,
         "phStbIpc_ClientHandShake server='%s' conn=%p message='%s'",
         pServerAddress, connection, message);

    messageLen = strlen(message);
    if (messageLen > PH_STBIPC_HANDSHAKE_MAX_MSG)
    {
        err = PH_ERR_STBIPC_HANDSHAKE_LEN;
        goto RETURN;
    }

    totalMessageLen = 1 + PH_STBIPC_HANDSHAKE_PREFIX_LEN + messageLen;

    if (connection == NULL)
    {
        err = phStbIpc_ClientCreate(pServerAddress, &connection);
        if (err != TM_OK)
        {
            goto RETURN;
        }
        closeConnection = true;
    }

    sprintf(buf, "%c%s%s", gkStbIpc_Endianness, PH_STBIPC_HANDSHAKE_CLIENT_PREFIX, message);
    DBG_ASSERT(strlen(buf) == totalMessageLen);
    err = phStbIpc_WriteExactly(connection,
                                (uint8_t*)buf,
                                totalMessageLen,
                                &lenWritten);
    if (err != TM_OK)
    {
        goto RETURN;
    }

    err = phStbIpc_ReadExactly(connection,
                               (uint8_t*)buf,
                               totalMessageLen,
                               &lenRead);
    if (err != TM_OK)
    {
        goto RETURN;
    }
    /* Terminate message read: */
    buf[lenRead] = '\0';

    DBG_PRINT1(gphStbIpc_DbgUnit, DBG_LEVEL_2, "phStbIpc_ClientHandShake: from server '%s'", buf);

    endianness = (phStbIpc_Endianness_t)buf[0];
    if (((endianness != phStbIpc_Endianness_Big) && (endianness != phStbIpc_Endianness_Little)) ||
        (strncmp(buf + 1, PH_STBIPC_HANDSHAKE_SERVER_PREFIX, PH_STBIPC_HANDSHAKE_PREFIX_LEN) != 0) ||
        (strcmp(buf + 1 + PH_STBIPC_HANDSHAKE_PREFIX_LEN, message) != 0))
    {
        err = PH_ERR_STBIPC_HANDSHAKE_CLIENT;
        goto RETURN;
    }
    *pEndianness = endianness;

  RETURN:
    if (closeConnection)
    {
        tmErrorCode_t err1;
        err1 = phStbIpc_Close(connection);
        if (err == TM_OK)
        {
            err = err1;
        }
    }
    DBG_PRINT4(gphStbIpc_DbgUnit, DBG_INTERFACE_LEAVE,
        "phStbIpc_ClientHandShake server='%s' conn=%p message='%s' => 0x%08x",
        pServerAddress, connection, message, err);
    return err;
}
EXPORT_SYMBOL(phStbIpc_ClientHandShake);

/******************************************************************************/
