/********************************************************************************/
/*                                                                              */
/*                              TPM Host IO                                     */
/*                           Written by Ken Goldman                             */
/*                     IBM Thomas J. Watson Research Center                     */
/*            $Id: tpm_io.c 4564 2011-04-13 19:33:38Z stefanb $                */
/*                                                                              */
/* (c) Copyright IBM Corporation 2006, 2010, 2019				*/
/*										*/
/* All rights reserved.								*/
/* 										*/
/* Redistribution and use in source and binary forms, with or without		*/
/* modification, are permitted provided that the following conditions are	*/
/* met:										*/
/* 										*/
/* Redistributions of source code must retain the above copyright notice,	*/
/* this list of conditions and the following disclaimer.			*/
/* 										*/
/* Redistributions in binary form must reproduce the above copyright		*/
/* notice, this list of conditions and the following disclaimer in the		*/
/* documentation and/or other materials provided with the distribution.		*/
/* 										*/
/* Neither the names of the IBM Corporation nor the names of its		*/
/* contributors may be used to endorse or promote products derived from		*/
/* this software without specific prior written permission.			*/
/* 										*/
/* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS		*/
/* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT		*/
/* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR	*/
/* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT		*/
/* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,	*/
/* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT		*/
/* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,	*/
/* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY	*/
/* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT		*/
/* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE	*/
/* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.		*/
/********************************************************************************/

/* These are platform specific.  This version uses a TCP/IP socket interface.

   Environment variables are:

           TPM_PORT - the client and server socket port number
*/

#include <config.h>

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <stdbool.h>
#include <errno.h>
#include <limits.h>
#include <arpa/inet.h>
#include <netinet/in.h> /* BSD: sockaddr_in */
#include <sys/socket.h> /* BSD: accept() */
#include <sys/select.h> /* BSD: select() */
#include <sys/uio.h>

#include <libtpms/tpm_error.h>
#include <libtpms/tpm_error.h>
#include <libtpms/tpm_types.h>

#include "logging.h"
#include "swtpm_debug.h"
#include "swtpm_io.h"
#include "tpmlib.h"
#include "utils.h"

/*
  global variables
*/

/* platform dependent */

static int      sock_fd = -1;


/* SWTPM_IO_Read() reads a TPM command packet from the host

   Puts the result in 'buffer' up to 'bufferSize' bytes.

   On success, the number of bytes in the buffer is equal to 'bufferLength' bytes

   This function is intended to be platform independent.
*/

TPM_RESULT SWTPM_IO_Read(TPM_CONNECTION_FD *connection_fd,   /* read/write file descriptor */
                         unsigned char *buffer,   /* output: command stream */
                         uint32_t *bufferLength,  /* output: command stream length */
                         size_t bufferSize)       /* input: max size of output buffer */
{
    ssize_t             n;
    size_t              offset = 0;

    if (connection_fd->fd < 0) {
        TPM_DEBUG("SWTPM_IO_Read: Passed file descriptor is invalid\n");
        return TPM_IOERROR;
    }

    if (bufferSize < sizeof(struct tpm_req_header)) {
        TPM_DEBUG("SWTPM_IO_Read: Passed buffer is too small\n");
        return TPM_IOERROR;
    }

    while (true) {
        n = read(connection_fd->fd, &buffer[offset], bufferSize - offset);
        if (n < 0 && errno == EINTR)
            continue;
        if (n > 0) {
            offset += n;
            if (offset < sizeof(struct tpm_req_header))
                continue;
            break;
        } else {
           return TPM_IOERROR;
        }
    }

    *bufferLength = offset;
    SWTPM_PrintAll(" SWTPM_IO_Read:", " ", buffer, *bufferLength);

    return 0;
}


/* SWTPM_IO_SetSocketFD tells the IO layer that it's not necessary to open
   a server socket.
 */
TPM_RESULT SWTPM_IO_SetSocketFD(int fd)
{
    sock_fd = fd;
    return 0;
}

int SWTPM_IO_GetSocketFD(void)
{
    return sock_fd;
}


/* SWTPM_IO_Init initializes the TPM to host interface.

   This is the Unix platform dependent socket version.
*/

TPM_RESULT SWTPM_IO_Init(void)
{
    TPM_DEBUG(" SWTPM_IO_Init:\n");

    return 0;
}


/* SWTPM_IO_Connect() establishes a connection between the TPM server and the host client

   This is the Unix platform dependent socket version.
*/

TPM_RESULT SWTPM_IO_Connect(TPM_CONNECTION_FD *connection_fd,     /* read/write file descriptor */
                            int notify_fd)
{
    TPM_RESULT          rc = 0;
    socklen_t           cli_len;
    struct sockaddr_in  cli_addr;       /* Internet version of sockaddr */
    int                 max_fd = -1;
    fd_set              readfds;
    int                 n;

    while (rc == 0) {
        FD_ZERO(&readfds);

        FD_SET(sock_fd, &readfds);
        max_fd = sock_fd;

        FD_SET(notify_fd, &readfds);
        max_fd = (notify_fd > max_fd) ? notify_fd : max_fd;

        TPM_DEBUG("SWTPM_IO_Connect: Waiting for connections\n");

        n = select(max_fd + 1, &readfds, NULL, NULL, NULL);

        if (n > 0 && FD_ISSET(notify_fd, &readfds)) {
            rc = TPM_IOERROR;
            break;
        }

        if (n > 0 && FD_ISSET(sock_fd, &readfds)) {
            cli_len = sizeof(cli_addr);
            /* block until connection from client */
            TPM_DEBUG("\n SWTPM_IO_Connect: Accepting connection ...\n");
            connection_fd->fd = accept(sock_fd, (struct sockaddr *)&cli_addr, &cli_len);
            if (connection_fd->fd < 0) {
                logprintf(STDERR_FILENO,
                          "SWTPM_IO_Connect: Error, accept() %d %s\n",
                          errno, strerror(errno));
                rc = TPM_IOERROR;
            }
            break;
        }
    }

    return rc;
}

/* SWTPM_IO_Write() writes 'buffer_length' bytes to the host.

   This is the Unix platform dependent socket version.
*/

TPM_RESULT SWTPM_IO_Write(TPM_CONNECTION_FD *connection_fd,       /* read/write file descriptor */
                          const struct iovec *iovec,
                          int iovcnt)
{
    ssize_t     nwritten = 0;
    size_t      totlen = 0;
    int         i;

    SWTPM_PrintAll(" SWTPM_IO_Write:", " ",
                   iovec[1].iov_base, iovec[1].iov_len);

    /* test that connection is open to write */
    if (connection_fd->fd < 0) {
        logprintf(STDERR_FILENO,
                  "SWTPM_IO_Write: Error, connection not open, fd %d\n",
                  connection_fd->fd);
        return TPM_IOERROR;
    }

    for (i = 0; i < iovcnt; i++)
        totlen += iovec[i].iov_len;

    nwritten = writev_full(connection_fd->fd, iovec, iovcnt);
    if (nwritten < 0) {
        logprintf(STDERR_FILENO, "SWTPM_IO_Write: Error, writev() %d %s\n",
                  errno, strerror(errno));
        return TPM_IOERROR;
    }
    if ((size_t)nwritten < totlen) {
        logprintf(STDERR_FILENO,
                  "SWTPM_IO_Write: Failed to write all bytes %zu != %zu\n",
                  nwritten, totlen);
        return TPM_IOERROR;
    }
    return 0;
}

/* SWTPM_IO_Disconnect() breaks the connection between the TPM server and the host client

   This is the Unix platform dependent socket version.
*/

TPM_RESULT SWTPM_IO_Disconnect(TPM_CONNECTION_FD *connection_fd)
{
    /* close the connection to the client */
    if (connection_fd->fd >= 0) {
        close(connection_fd->fd);
        connection_fd->fd = -1;     /* mark the connection closed */
    }

    return 0;
}
