
/*
 * Licensed Materials - Property of IBM
 *
 * trousers - An open source TCG Software Stack
 *
 * (C) Copyright International Business Machines Corp. 2004-2006
 *
 */

#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <limits.h>

#include "trousers/tss.h"
#include "trousers/trousers.h"
#include "trousers_types.h"
#include "spi_utils.h"
#include "capabilities.h"
#include "tsplog.h"
#include "hosttable.h"
#include "tcsd_wrap.h"
#include "obj.h"
#include "rpc_tcstp_tsp.h"
#include "tsp_tcsi_param.h"


void
initData(struct tcsd_comm_data *comm, int parm_count)
{
	/* min packet size should be the size of the header */
	__tspi_memset(&comm->hdr, 0, sizeof(struct tcsd_packet_hdr));
	comm->hdr.packet_size = sizeof(struct tcsd_packet_hdr);
	comm->hdr.type_offset = sizeof(struct tcsd_packet_hdr);
	comm->hdr.parm_offset = comm->hdr.type_offset + (sizeof(TCSD_PACKET_TYPE) * parm_count);
	comm->hdr.packet_size = comm->hdr.parm_offset;

	__tspi_memset(comm->buf, 0, comm->buf_size);
}

int
loadData(UINT64 *offset, TCSD_PACKET_TYPE data_type, void *data, int data_size, BYTE *blob)
{
	switch (data_type) {
		case TCSD_PACKET_TYPE_BYTE:
			Trspi_LoadBlob_BYTE(offset, *((BYTE *) (data)), blob);
			break;
		case TCSD_PACKET_TYPE_BOOL:
			Trspi_LoadBlob_BOOL(offset, *((TSS_BOOL *) (data)), blob);
			break;
		case TCSD_PACKET_TYPE_UINT16:
			Trspi_LoadBlob_UINT16(offset, *((UINT16 *) (data)), blob);
			break;
		case TCSD_PACKET_TYPE_UINT32:
			Trspi_LoadBlob_UINT32(offset, *((UINT32 *) (data)), blob);
			break;
		case TCSD_PACKET_TYPE_PBYTE:
			Trspi_LoadBlob(offset, data_size, blob, (BYTE *)data);
			break;
		case TCSD_PACKET_TYPE_NONCE:
			Trspi_LoadBlob(offset, 20, blob, ((TCPA_NONCE *)data)->nonce);
			break;
		case TCSD_PACKET_TYPE_DIGEST:
			Trspi_LoadBlob(offset, 20, blob, ((TCPA_DIGEST *)data)->digest);
			break;
		case TCSD_PACKET_TYPE_AUTH:
			LoadBlob_AUTH(offset, blob, ((TPM_AUTH *)data));
			break;
		case TCSD_PACKET_TYPE_UUID:
			Trspi_LoadBlob_UUID(offset, blob, *((TSS_UUID *)data));
			break;
		case TCSD_PACKET_TYPE_ENCAUTH:
			Trspi_LoadBlob(offset, 20, blob, ((TCPA_ENCAUTH *)data)->authdata);
			break;
		case TCSD_PACKET_TYPE_VERSION:
			Trspi_LoadBlob_TCPA_VERSION(offset, blob, *((TCPA_VERSION *)data));
			break;
#ifdef TSS_BUILD_PS
		case TCSD_PACKET_TYPE_LOADKEY_INFO:
			LoadBlob_LOADKEY_INFO(offset, blob, ((TCS_LOADKEY_INFO *)data));
			break;
#endif
		case TCSD_PACKET_TYPE_PCR_EVENT:
			Trspi_LoadBlob_PCR_EVENT(offset, blob, ((TSS_PCR_EVENT *)data));
			break;
		case TCSD_PACKET_TYPE_COUNTER_VALUE:
			Trspi_LoadBlob_COUNTER_VALUE(offset, blob, ((TPM_COUNTER_VALUE *)data));
			break;
		case TCSD_PACKET_TYPE_SECRET:
			Trspi_LoadBlob(offset, 20, blob, ((TCPA_SECRET *)data)->authdata);
			break;
		default:
			LogError("TCSD packet type unknown! (0x%x)", data_type & 0xff);
			return TSPERR(TSS_E_INTERNAL_ERROR);
	}

	return TSS_SUCCESS;
}

int
setData(TCSD_PACKET_TYPE dataType,
	int index,
	void *theData,
	int theDataSize,
	struct tcsd_comm_data *comm)
{
        UINT64 old_offset, offset;
        TSS_RESULT result;
        TCSD_PACKET_TYPE *type;

        /* Calculate the size of the area needed (use NULL for blob address) */
        offset = 0;
        if ((result = loadData(&offset, dataType, theData, theDataSize, NULL)))
                return result;
        if ((comm->hdr.packet_size + offset) > TSS_TPM_TXBLOB_SIZE) {
                LogError("Too much data to be transmitted!");
                return TSPERR(TSS_E_INTERNAL_ERROR);
        }
        if ((comm->hdr.packet_size + offset) > comm->buf_size) {
                /* reallocate the buffer */
                BYTE *buffer;
                int buffer_size = comm->hdr.packet_size + offset;

                LogDebug("Increasing communication buffer to %d bytes.", buffer_size);
                buffer = realloc(comm->buf, buffer_size);
                if (buffer == NULL) {
                        LogError("realloc of %d bytes failed.", buffer_size);
                        return TSPERR(TSS_E_INTERNAL_ERROR);
                }
                comm->buf_size = buffer_size;
                comm->buf = buffer;
        }

        offset = old_offset = comm->hdr.parm_offset + comm->hdr.parm_size;
        if ((result = loadData(&offset, dataType, theData, theDataSize, comm->buf)))
                return result;
        type = (TCSD_PACKET_TYPE *)(comm->buf + comm->hdr.type_offset) + index;
        *type = dataType;
        comm->hdr.type_size += sizeof(TCSD_PACKET_TYPE);
        comm->hdr.parm_size += (offset - old_offset);

        comm->hdr.packet_size = offset;
        comm->hdr.num_parms++;

        return TSS_SUCCESS;
}

UINT32
getData(TCSD_PACKET_TYPE dataType,
	int index,
	void *theData,
	int theDataSize,
	struct tcsd_comm_data *comm)
{
	TSS_RESULT result;
	UINT64 old_offset, offset;
	TCSD_PACKET_TYPE *type = (TCSD_PACKET_TYPE *)(comm->buf + comm->hdr.type_offset) + index;

	if ((UINT32)index >= comm->hdr.num_parms || dataType != *type) {
		LogDebug("Data type of TCS packet element %d doesn't match.", index);
		return TSS_TCP_RPC_BAD_PACKET_TYPE;
	}
	old_offset = offset = comm->hdr.parm_offset;
	switch (dataType) {
		case TCSD_PACKET_TYPE_BYTE:
			Trspi_UnloadBlob_BYTE(&offset, (BYTE *)theData, comm->buf);
			break;
		case TCSD_PACKET_TYPE_BOOL:
			Trspi_UnloadBlob_BOOL(&offset, (TSS_BOOL *)theData, comm->buf);
			break;
		case TCSD_PACKET_TYPE_UINT16:
			Trspi_UnloadBlob_UINT16(&offset, (UINT16 *)theData, comm->buf);
			break;
		case TCSD_PACKET_TYPE_UINT32:
			Trspi_UnloadBlob_UINT32(&offset, (UINT32 *)theData, comm->buf);
			break;
		case TCSD_PACKET_TYPE_UINT64:
			Trspi_UnloadBlob_UINT64(&offset, (UINT64 *)theData, comm->buf);
			break;
		case TCSD_PACKET_TYPE_PBYTE:
			Trspi_UnloadBlob(&offset, theDataSize, comm->buf, (BYTE *)theData);
			break;
		case TCSD_PACKET_TYPE_NONCE:
			Trspi_UnloadBlob_NONCE(&offset, comm->buf, (TPM_NONCE *)theData);
			break;
		case TCSD_PACKET_TYPE_DIGEST:
			Trspi_UnloadBlob(&offset, sizeof(TCPA_DIGEST), comm->buf,
					 ((TCPA_DIGEST *)theData)->digest);
			break;
		case TCSD_PACKET_TYPE_AUTH:
			UnloadBlob_AUTH(&offset, comm->buf, ((TPM_AUTH *)theData));
			break;
		case TCSD_PACKET_TYPE_UUID:
			Trspi_UnloadBlob_UUID(&offset, comm->buf, ((TSS_UUID *)theData));
			break;
		case TCSD_PACKET_TYPE_ENCAUTH:
			Trspi_UnloadBlob(&offset, sizeof(TCPA_ENCAUTH), comm->buf,
					 ((TCPA_ENCAUTH *)theData)->authdata);
			break;
		case TCSD_PACKET_TYPE_VERSION:
			Trspi_UnloadBlob_TCPA_VERSION(&offset, comm->buf,
						      ((TCPA_VERSION *)theData));
			break;
		case TCSD_PACKET_TYPE_KM_KEYINFO:
			if ((result = Trspi_UnloadBlob_KM_KEYINFO(&offset, comm->buf,
								  ((TSS_KM_KEYINFO *)theData))))
				return result;
			break;
		case TCSD_PACKET_TYPE_KM_KEYINFO2:
			if ((result = Trspi_UnloadBlob_KM_KEYINFO2(&offset, comm->buf,
								   ((TSS_KM_KEYINFO2 *)theData))))
				return result;
			break;
#ifdef TSS_BUILD_PS
		case TCSD_PACKET_TYPE_LOADKEY_INFO:
			UnloadBlob_LOADKEY_INFO(&offset, comm->buf, ((TCS_LOADKEY_INFO *)theData));
			break;
#endif
		case TCSD_PACKET_TYPE_PCR_EVENT:
			if ((result = Trspi_UnloadBlob_PCR_EVENT(&offset, comm->buf,
								 ((TSS_PCR_EVENT *)theData))))
				return result;
			break;
		case TCSD_PACKET_TYPE_COUNTER_VALUE:
			Trspi_UnloadBlob_COUNTER_VALUE(&offset, comm->buf,
						       ((TPM_COUNTER_VALUE *)theData));
			break;
		case TCSD_PACKET_TYPE_SECRET:
			Trspi_UnloadBlob(&offset, sizeof(TCPA_SECRET), comm->buf,
					 ((TCPA_SECRET *)theData)->authdata);
			break;
		default:
			LogError("unknown data type (%d) in TCSD packet!", dataType);
			return -1;
	}
	comm->hdr.parm_offset = offset;
	comm->hdr.parm_size -= (offset - old_offset);

	return TSS_SUCCESS;
}

TSS_RESULT
sendTCSDPacket(struct host_table_entry *hte)
{
	TSS_RESULT rc;
	UINT64 offset = 0;

	Trspi_LoadBlob_UINT32(&offset, hte->comm.hdr.packet_size, hte->comm.buf);
	Trspi_LoadBlob_UINT32(&offset, hte->comm.hdr.u.ordinal, hte->comm.buf);
	Trspi_LoadBlob_UINT32(&offset, hte->comm.hdr.num_parms, hte->comm.buf);
	Trspi_LoadBlob_UINT32(&offset, hte->comm.hdr.type_size, hte->comm.buf);
	Trspi_LoadBlob_UINT32(&offset, hte->comm.hdr.type_offset, hte->comm.buf);
	Trspi_LoadBlob_UINT32(&offset, hte->comm.hdr.parm_size, hte->comm.buf);
	Trspi_LoadBlob_UINT32(&offset, hte->comm.hdr.parm_offset, hte->comm.buf);

#if 0
	/* ---  Send it */
	printBuffer(hte->comm.buf, hte->comm.hdr.packet_size);
	LogInfo("Sending Packet with TCSD ordinal 0x%X", hte->comm.hdr.u.ordinal);
#endif
	/* if the ordinal is open context, there are some host table entry
	 * manipulations that must be done, so call _init
	 */
	if (hte->comm.hdr.u.ordinal == TCSD_ORD_OPENCONTEXT) {
		if ((rc = send_init(hte))) {
			LogError("Failed to send packet");
			return rc;
		}
	} else {
		if ((rc = tcs_sendit(hte))) {
			LogError("Failed to send packet");
			return rc;
		}
	}

	/* create a platform version of the tcsd header */
	offset = 0;
	Trspi_UnloadBlob_UINT32(&offset, &hte->comm.hdr.packet_size, hte->comm.buf);
	Trspi_UnloadBlob_UINT32(&offset, &hte->comm.hdr.u.result, hte->comm.buf);
	Trspi_UnloadBlob_UINT32(&offset, &hte->comm.hdr.num_parms, hte->comm.buf);
	Trspi_UnloadBlob_UINT32(&offset, &hte->comm.hdr.type_size, hte->comm.buf);
	Trspi_UnloadBlob_UINT32(&offset, &hte->comm.hdr.type_offset, hte->comm.buf);
	Trspi_UnloadBlob_UINT32(&offset, &hte->comm.hdr.parm_size, hte->comm.buf);
	Trspi_UnloadBlob_UINT32(&offset, &hte->comm.hdr.parm_offset, hte->comm.buf);

	return TSS_SUCCESS;
}

int
recv_from_socket(int sock, void *buffer, int size)
{
	int recv_size = 0, recv_total = 0;

	while (recv_total < size) {
		errno = 0;
		if ((recv_size = recv(sock, buffer+recv_total, size-recv_total, 0)) <= 0) {
			if (recv_size < 0) {
				if (errno == EINTR)
					continue;
				LogError("Socket receive connection error: %s.", strerror(errno));
			} else {
				LogDebug("Socket connection closed.");
			}

			return -1;
		}
		recv_total += recv_size;
	}

	return recv_total;
}

int
send_to_socket(int sock, void *buffer, int size)
{
	int send_size = 0, send_total = 0;

	while (send_total < size) {
		if ((send_size = send(sock, buffer+send_total, size-send_total, 0)) < 0) {
			LogError("Socket send connection error: %s.", strerror(errno));
			return -1;
		}
		send_total += send_size;
	}

	return send_total;
}

TSS_RESULT
send_init(struct host_table_entry *hte)
{
	int sd;
	int recv_size;
	BYTE *buffer;
	TSS_RESULT result;

	result = get_socket(hte, &sd);
	if (result != TSS_SUCCESS)
		goto err_exit;

	if (send_to_socket(sd, hte->comm.buf, hte->comm.hdr.packet_size) < 0) {
		result = TSPERR(TSS_E_COMM_FAILURE);
		goto err_exit;
	}

	buffer = hte->comm.buf;
	recv_size = sizeof(struct tcsd_packet_hdr);
	if (recv_from_socket(sd, buffer, recv_size) < 0) {
		result = TSPERR(TSS_E_COMM_FAILURE);
		goto err_exit;
	}
	buffer += sizeof(struct tcsd_packet_hdr);       /* increment the receive buffer pointer */

	/* check the packet size */
	recv_size = Decode_UINT32(hte->comm.buf);
	if (recv_size < (int)sizeof(struct tcsd_packet_hdr)) {
		LogError("Packet to receive from socket %d is too small (%d bytes)",
				sd, recv_size);
		result = TSPERR(TSS_E_COMM_FAILURE);
		goto err_exit;
	}

	if (recv_size > (int) hte->comm.buf_size ) {
		BYTE *new_buffer;

		LogDebug("Increasing communication buffer to %d bytes.", recv_size);
		new_buffer = realloc(hte->comm.buf, recv_size);
		if (new_buffer == NULL) {
			LogError("realloc of %d bytes failed.", recv_size);
			result = TSPERR(TSS_E_OUTOFMEMORY);
			goto err_exit;
		}
		buffer = new_buffer + sizeof(struct tcsd_packet_hdr);
		hte->comm.buf_size = recv_size;
		hte->comm.buf = new_buffer;
	}

	/* get the rest of the packet */
	recv_size -= sizeof(struct tcsd_packet_hdr);    /* already received the header */
	if (recv_from_socket(sd, buffer, recv_size) < 0) {
		result = TSPERR(TSS_E_COMM_FAILURE);
		goto err_exit;
	}

	hte->socket = sd;

	return TSS_SUCCESS;

err_exit:
	close(sd);
	return result;
}

TSS_RESULT
tcs_sendit(struct host_table_entry *hte)
{
	int recv_size;
	BYTE *buffer;
	TSS_RESULT result;

	if (send_to_socket(hte->socket, hte->comm.buf, hte->comm.hdr.packet_size) < 0) {
		result = TSPERR(TSS_E_COMM_FAILURE);
		goto err_exit;
	}

	buffer = hte->comm.buf;
	recv_size = sizeof(struct tcsd_packet_hdr);
	if ((recv_size = recv_from_socket(hte->socket, buffer, recv_size)) < 0) {
		result = TSPERR(TSS_E_COMM_FAILURE);
		goto err_exit;
	}
	buffer += recv_size;            /* increment the receive buffer pointer */

	/* check the packet size */
	recv_size = Decode_UINT32(hte->comm.buf);
	if (recv_size < (int)sizeof(struct tcsd_packet_hdr)) {
		LogError("Packet to receive from socket %d is too small (%d bytes)",
				hte->socket, recv_size);
		result = TSPERR(TSS_E_COMM_FAILURE);
		goto err_exit;
	}

	if (recv_size > (int) hte->comm.buf_size ) {
		BYTE *new_buffer;

		LogDebug("Increasing communication buffer to %d bytes.", recv_size);
		new_buffer = realloc(hte->comm.buf, recv_size);
		if (new_buffer == NULL) {
			LogError("realloc of %d bytes failed.", recv_size);
			result = TSPERR(TSS_E_OUTOFMEMORY);
			goto err_exit;
		}
		buffer = new_buffer + sizeof(struct tcsd_packet_hdr);
		hte->comm.buf_size = recv_size;
		hte->comm.buf = new_buffer;
	}

	/* get the rest of the packet */
	recv_size -= sizeof(struct tcsd_packet_hdr);    /* already received the header */
	if ((recv_size = recv_from_socket(hte->socket, buffer, recv_size)) < 0) {
		result = TSPERR(TSS_E_COMM_FAILURE);
		goto err_exit;
	}

	return TSS_SUCCESS;

err_exit:
	return result;
}

/* TODO: Future work - remove socket creation/manipulation from RPC-specific file */
TSS_RESULT
get_socket(struct host_table_entry *hte, int *sd)
{
	char port_str[TCP_PORT_STR_MAX_LEN]; // To accomodate string 65535
	struct addrinfo hints, *p, *res=NULL;
	int rv;
	TSS_RESULT result = TSS_SUCCESS;

	__tspi_memset(&hints, 0, sizeof(hints));
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_flags = AI_NUMERICSERV;

	__tspi_memset(&port_str, 0, sizeof(port_str));

	if (get_tcsd_port(port_str) != TSS_SUCCESS) {
		LogError("Could not retrieve TCP port information.");
		goto exit;
	}

	LogDebug("Retrieving address information from host: %s", (char *)hte->hostname);
	rv = getaddrinfo((char *)hte->hostname, port_str,
			&hints, &res);
	if (rv != 0) {
		LogError("hostname %s does not resolve to a valid address.", hte->hostname);
		result = TSPERR(TSS_E_CONNECTION_FAILED);
		res = NULL;
		goto exit;
	}

	LogWarn("Got a list of valid IPs");

	for (p = res; p != NULL; p = p->ai_next) {

		*sd = socket(p->ai_family, SOCK_STREAM, 0);
		if (*sd == -1)
			continue;

		if (connect(*sd, p->ai_addr, p->ai_addrlen) != -1)
			break; // Got a connection

		LogWarn("Could not connect to machine: %s", (char*)hte->hostname);

		close(*sd);
	}

	if (p == NULL) {
		LogError("Could not connect to any machine in the list.");
		result = TSPERR(TSS_E_COMM_FAILURE);
		goto exit;
	}

exit:
	if (res != NULL)
		freeaddrinfo(res);

	return result;
}
