/********************************************************************************/
/*										*/
/*			     				*/
/*			     Written by Ken Goldman				*/
/*		       IBM Thomas J. Watson Research Center			*/
/*            $Id: TcpServer.c 1048 2017-07-20 20:28:26Z kgoldman $		*/
/*										*/
/*  Licenses and Notices							*/
/*										*/
/*  1. Copyright Licenses:							*/
/*										*/
/*  - Trusted Computing Group (TCG) grants to the user of the source code in	*/
/*    this specification (the "Source Code") a worldwide, irrevocable, 		*/
/*    nonexclusive, royalty free, copyright license to reproduce, create 	*/
/*    derivative works, distribute, display and perform the Source Code and	*/
/*    derivative works thereof, and to grant others the rights granted herein.	*/
/*										*/
/*  - The TCG grants to the user of the other parts of the specification 	*/
/*    (other than the Source Code) the rights to reproduce, distribute, 	*/
/*    display, and perform the specification solely for the purpose of 		*/
/*    developing products based on such documents.				*/
/*										*/
/*  2. Source Code Distribution Conditions:					*/
/*										*/
/*  - Redistributions of Source Code must retain the above copyright licenses, 	*/
/*    this list of conditions and the following disclaimers.			*/
/*										*/
/*  - Redistributions in binary form must reproduce the above copyright 	*/
/*    licenses, this list of conditions	and the following disclaimers in the 	*/
/*    documentation and/or other materials provided with the distribution.	*/
/*										*/
/*  3. Disclaimers:								*/
/*										*/
/*  - THE COPYRIGHT LICENSES SET FORTH ABOVE DO NOT REPRESENT ANY FORM OF	*/
/*  LICENSE OR WAIVER, EXPRESS OR IMPLIED, BY ESTOPPEL OR OTHERWISE, WITH	*/
/*  RESPECT TO PATENT RIGHTS HELD BY TCG MEMBERS (OR OTHER THIRD PARTIES)	*/
/*  THAT MAY BE NECESSARY TO IMPLEMENT THIS SPECIFICATION OR OTHERWISE.		*/
/*  Contact TCG Administration (admin@trustedcomputinggroup.org) for 		*/
/*  information on specification licensing rights available through TCG 	*/
/*  membership agreements.							*/
/*										*/
/*  - THIS SPECIFICATION IS PROVIDED "AS IS" WITH NO EXPRESS OR IMPLIED 	*/
/*    WARRANTIES WHATSOEVER, INCLUDING ANY WARRANTY OF MERCHANTABILITY OR 	*/
/*    FITNESS FOR A PARTICULAR PURPOSE, ACCURACY, COMPLETENESS, OR 		*/
/*    NONINFRINGEMENT OF INTELLECTUAL PROPERTY RIGHTS, OR ANY WARRANTY 		*/
/*    OTHERWISE ARISING OUT OF ANY PROPOSAL, SPECIFICATION OR SAMPLE.		*/
/*										*/
/*  - Without limitation, TCG and its members and licensors disclaim all 	*/
/*    liability, including liability for infringement of any proprietary 	*/
/*    rights, relating to use of information in this specification and to the	*/
/*    implementation of this specification, and TCG disclaims all liability for	*/
/*    cost of procurement of substitute goods or services, lost profits, loss 	*/
/*    of use, loss of data or any incidental, consequential, direct, indirect, 	*/
/*    or special damages, whether under contract, tort, warranty or otherwise, 	*/
/*    arising in any way out of use or reliance upon this specification or any 	*/
/*    information herein.							*/
/*										*/
/*  (c) Copyright IBM Corp. and others, 2016, 2017				*/
/*										*/
/********************************************************************************/

/* D.3 TcpServer.c */
/* D.3.1. Description */
/* This file contains the socket interface to a TPM simulator. */
/* D.3.2. Includes, Locals, Defines and Function Prototypes */
#include "TpmBuildSwitches.h"
#include <stdio.h>

#ifdef TPM_WINDOWS
#include <windows.h>
#include <winsock.h>
#else
typedef int SOCKET
#endif
#include <string.h>
#include <stdlib.h>
#include <stdint.h>

#include "Implementation.h"	/* kgold */
#include "TpmTcpProtocol.h"
#include "Manufacture_fp.h"
#include "Simulator_fp.h"
#include "TcpServer_fp.h"

/*     To access key cache control in TPM */
void RsaKeyCacheControl(int state);
#ifndef __IGNORE_STATE__
static uint32_t ServerVersion = 1;
#define MAX_BUFFER 1048576
char InputBuffer[MAX_BUFFER];       //The input data buffer for the simulator.
char OutputBuffer[MAX_BUFFER];      //The output data buffer for the simulator.
struct
{
    uint32_t    largestCommandSize;
    uint32_t    largestCommand;
    uint32_t    largestResponseSize;
    uint32_t    largestResponse;
} CommandResponseSizes = {0};
#endif // __IGNORE_STATE___
/* D.3.3. Functions */
/* D.3.3.1. CreateSocket() */
/* This function creates a socket listening on PortNumber. */
static int
CreateSocket(
	     int                  PortNumber,
	     SOCKET              *listenSocket
	     )
{
    WSADATA              wsaData;
    struct               sockaddr_in MyAddress;
    int res;
    // Initialize Winsock
    res = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if(res != 0)
	{
	    printf("WSAStartup failed with error: %d\n", res);
	    return -1;
	}
    // create listening socket
    *listenSocket = socket(PF_INET, SOCK_STREAM, 0);
    if(INVALID_SOCKET == *listenSocket)
	{
	    printf("Cannot create server listen socket.  Error is 0x%x\n",
		   WSAGetLastError());
	    return -1;
	}
    // bind the listening socket to the specified port
    ZeroMemory(&MyAddress, sizeof(MyAddress));
    MyAddress.sin_port = htons((short)PortNumber);
    MyAddress.sin_family = AF_INET;
    res = bind(*listenSocket, (struct sockaddr*) &MyAddress, sizeof(MyAddress));
    if(res == SOCKET_ERROR)
	{
	    printf("Bind error.  Error is 0x%x\n", WSAGetLastError());
	    return -1;
	};
    // listen/wait for server connections
    res = listen(*listenSocket, 3);
    if(res == SOCKET_ERROR)
	{
	    printf("Listen error.  Error is 0x%x\n", WSAGetLastError());
	    return -1;
	};
    return 0;
}
/* D.3.3.2. PlatformServer() */
/* This function processes incoming platform requests. */
BOOL
PlatformServer(
	       SOCKET           s
	       )
{
    BOOL                 OK = TRUE;
    uint32_t             Command;
    for(;;)
	{
	    OK = ReadBytes(s, (char*)&Command, 4);
	    // client disconnected (or other error).  We stop processing this client
	    // and return to our caller who can stop the server or listen for another
	    // connection.
	    if(!OK) return TRUE;
	    Command = ntohl(Command);
	    switch(Command)
		{
		  case TPM_SIGNAL_POWER_ON:
		    _rpc__Signal_PowerOn(FALSE);
		    break;
		  case TPM_SIGNAL_POWER_OFF:
		    _rpc__Signal_PowerOff();
		    break;
		  case TPM_SIGNAL_RESET:
		    _rpc__Signal_PowerOn(TRUE);
		    break;
		  case TPM_SIGNAL_RESTART:
		    _rpc__Signal_Restart();
		    break;
		  case TPM_SIGNAL_PHYS_PRES_ON:
		    _rpc__Signal_PhysicalPresenceOn();
		    break;
		  case TPM_SIGNAL_PHYS_PRES_OFF:
		    _rpc__Signal_PhysicalPresenceOff();
		    break;
		  case TPM_SIGNAL_CANCEL_ON:
		    _rpc__Signal_CancelOn();
		    break;
		  case TPM_SIGNAL_CANCEL_OFF:
		    _rpc__Signal_CancelOff();
		    break;
		  case TPM_SIGNAL_NV_ON:
		    _rpc__Signal_NvOn();
		    break;
		  case TPM_SIGNAL_NV_OFF:
		    _rpc__Signal_NvOff();
		    break;
		  case TPM_SIGNAL_KEY_CACHE_ON:
		    _rpc__RsaKeyCacheControl(TRUE);
		    break;
		  case TPM_SIGNAL_KEY_CACHE_OFF:
		    _rpc__RsaKeyCacheControl(FALSE);
		    break;
		  case TPM_SESSION_END:
		    // Client signaled end-of-session
		    TpmEndSimulation();
		    return TRUE;
		  case TPM_STOP:
		    // Client requested the simulator to exit
		    return FALSE;
		  case TPM_TEST_FAILURE_MODE:
		    _rpc__ForceFailureMode();
		    break;
		  case TPM_GET_COMMAND_RESPONSE_SIZES:
		    OK = WriteVarBytes(s, (char *)&CommandResponseSizes,
				       sizeof(CommandResponseSizes));
		    memset(&CommandResponseSizes, 0, sizeof(CommandResponseSizes));
		    if(!OK)
			return TRUE;
		    break;
		  default:
		    printf("Unrecognized platform interface command %d\n",
			   (int)Command);
		    WriteUINT32(s, 1);
		    return TRUE;
		}
	    WriteUINT32(s, 0);
	}
    return FALSE;
}
/* D.3.3.3. PlatformSvcRoutine() */
/* This function is called to set up the socket interfaces to listen for commands. */
DWORD WINAPI
PlatformSvcRoutine(
		   LPVOID           port
		   )
{
    int                  PortNumber = (int)(INT_PTR)port;
    SOCKET               listenSocket, serverSocket;
    struct               sockaddr_in HerAddress;
    int                  res;
    int                  length;
    BOOL                 continueServing;
    res = CreateSocket(PortNumber, &listenSocket);
    if(res != 0)
	{
	    printf("Create platform service socket fail\n");
	    return res;
	}
    // Loop accepting connections one-by-one until we are killed or asked to stop
    // Note the platform service is single-threaded so we don't listen for a new
    // connection until the prior connection drops.
    do
	{
	    printf("Platform server listening on port %d\n", PortNumber);
	    // blocking accept
	    length = sizeof(HerAddress);
	    serverSocket = accept(listenSocket,
				  (struct sockaddr*) &HerAddress,
				  &length);
	    if(serverSocket == SOCKET_ERROR)
		{
		    printf("Accept error.  Error is 0x%x\n", WSAGetLastError());
		    return -1;
		};
	    printf("Client accepted\n");
	    // normal behavior on client disconnection is to wait for a new client
	    // to connect
	    continueServing = PlatformServer(serverSocket);
	    closesocket(serverSocket);
	} while(continueServing);
    return 0;
}
/* D.3.3.4. PlatformSignalService() */
/* This function starts a new thread waiting for platform signals. Platform signals are processed
   one at a time in the order in which they are received. */
int
PlatformSignalService(
		      int              PortNumberPlatform
		      )
{
    HANDLE               hPlatformSvc;
    int                  ThreadId;
    int                  port = PortNumberPlatform;
    // Create service thread for platform signals
    hPlatformSvc = CreateThread(NULL, 0,
				(LPTHREAD_START_ROUTINE)PlatformSvcRoutine,
				(LPVOID)(INT_PTR)port, 0, (LPDWORD)&ThreadId);
    if(hPlatformSvc == NULL)
	{
	    printf("Thread Creation failed\n");
	    return -1;
	}
    return 0;
}
/* D.3.3.5. RegularCommandService() */
/* This function services regular commands. */
int
RegularCommandService(
		      int              PortNumber
		      )
{
    SOCKET               listenSocket;
    SOCKET               serverSocket;
    struct               sockaddr_in HerAddress;
    int res, length;
    BOOL continueServing;
    res = CreateSocket(PortNumber, &listenSocket);
    if(res != 0)
	{
	    printf("Create platform service socket fail\n");
	    return res;
	}
    // Loop accepting connections one-by-one until we are killed or asked to stop
    // Note the TPM command service is single-threaded so we don't listen for
    // a new connection until the prior connection drops.
    do
	{
	    printf("TPM command server listening on port %d\n", PortNumber);
	    // blocking accept
	    length = sizeof(HerAddress);
	    serverSocket = accept(listenSocket,
				  (struct sockaddr*) &HerAddress,
				  &length);
	    if(serverSocket == SOCKET_ERROR)
		{
		    printf("Accept error.  Error is 0x%x\n", WSAGetLastError());
		    return -1;
		};
	    printf("Client accepted\n");
	    // normal behavior on client disconnection is to wait for a new client
	    // to connect
	    continueServing = TpmServer(serverSocket);
	    closesocket(serverSocket);
	} while(continueServing);
    return 0;
}
/* D.3.3.6. StartTcpServer() */
/* Main entry-point to the TCP server.  The server listens on port specified. Note that there is no
   way to specify the network interface in this implementation. */
int
StartTcpServer(
	       int              *PortNumber,
	       int              *PortNumberPlatform
	       )
{
    int                  res;
    // Start Platform Signal Processing Service
    res = PlatformSignalService(*PortNumberPlatform);
    if(res != 0)
	{
	    printf("PlatformSignalService failed\n");
	    return res;
	}
    // Start Regular/DRTM TPM command service
    res = RegularCommandService(*PortNumber);
    if(res != 0)
	{
	    printf("RegularCommandService failed\n");
	    return res;
	}
    return 0;
}
/* D.3.3.7. ReadBytes() */
/* This function reads the indicated number of bytes (NumBytes) into buffer from the indicated
   socket. */
BOOL
ReadBytes(
	  SOCKET           s,
	  char            *buffer,
	  int              NumBytes
	  )
{
    int                  res;
    int                  numGot = 0;
    while(numGot < NumBytes)
	{
	    res = recv(s, buffer + numGot, NumBytes - numGot, 0);
	    if(res == -1)
		{
		    printf("Receive error.  Error is 0x%x\n", WSAGetLastError());
		    return FALSE;
		}
	    if(res == 0)
		{
		    return FALSE;
		}
	    numGot += res;
	}
    return TRUE;
}
/* D.3.3.8. WriteBytes() */
/* This function will send the indicated number of bytes (NumBytes) to the indicated socket */
BOOL
WriteBytes(
	   SOCKET           s,
	   char            *buffer,
	   int              NumBytes
	   )
{
    int                  res;
    int                  numSent = 0;
    while(numSent < NumBytes)
	{
	    res = send(s, buffer + numSent, NumBytes - numSent, 0);
	    if(res == -1)
		{
		    if(WSAGetLastError() == 0x2745)
			{
			    printf("Client disconnected\n");
			}
		    else
			{
			    printf("Send error.  Error is 0x%x\n", WSAGetLastError());
			}
		    return FALSE;
		}
	    numSent += res;
	}
    return TRUE;
}
/* D.3.3.9. WriteUINT32() */
/* Send 4 bytes containing hton(1) */
BOOL
WriteUINT32(
	    SOCKET           s,
	    uint32_t         val
	    )
{
    UINT32 netVal = htonl(val);
    return WriteBytes(s, (char*)&netVal, 4);
}
/* D.3.3.10. ReadVarBytes() */
/* Get a UINT32-length-prepended binary array.  Note that the 4-byte length is in network byte order
   (big-endian). */
BOOL
ReadVarBytes(
	     SOCKET           s,
	     char            *buffer,
	     uint32_t        *BytesReceived,
	     int              MaxLen
	     )
{
    int                  length;
    BOOL                 res;
    res = ReadBytes(s, (char*)&length, 4);
    if(!res) return res;
    length = ntohl(length);
    *BytesReceived = length;
    if(length > MaxLen)
	{
	    printf("Buffer too big.  Client says %d\n", length);
	    return FALSE;
	}
    if(length == 0) return TRUE;
    res = ReadBytes(s, buffer, length);
    if(!res) return res;
    return TRUE;
}
/* D.3.3.11. WriteVarBytes() */
/* Send a UINT32-length-prepended binary array.  Note that the 4-byte length is in network byte
   order (big-endian). */
BOOL
WriteVarBytes(
	      SOCKET           s,
	      char            *buffer,
	      int              BytesToSend
	      )
{
    uint32_t             netLength = htonl(BytesToSend);
    BOOL res;
    res = WriteBytes(s, (char*)&netLength, 4);
    if(!res) return res;
    res = WriteBytes(s, buffer, BytesToSend);
    if(!res) return res;
    return TRUE;
}
/* D.3.3.12. TpmServer() */
/* Processing incoming TPM command requests using the protocol / interface defined above. */
BOOL
TpmServer(
	  SOCKET           s
	  )
{
    uint32_t             length;
    uint32_t             Command;
    BYTE                 locality;
    BOOL                 OK;
    int                  result;
    int                  clientVersion;
    _IN_BUFFER           InBuffer;
    _OUT_BUFFER          OutBuffer;
    for(;;)
	{
	    OK = ReadBytes(s, (char*)&Command, 4);
	    // client disconnected (or other error).  We stop processing this client
	    // and return to our caller who can stop the server or listen for another
	    // connection.
	    if(!OK)
		return TRUE;
	    Command = ntohl(Command);
	    switch(Command)
		{
		  case TPM_SIGNAL_HASH_START:
		    _rpc__Signal_Hash_Start();
		    break;
		  case TPM_SIGNAL_HASH_END:
		    _rpc__Signal_HashEnd();
		    break;
		  case TPM_SIGNAL_HASH_DATA:
		    OK = ReadVarBytes(s, InputBuffer, &length, MAX_BUFFER);
		    if(!OK) return TRUE;
		    InBuffer.Buffer = (BYTE*)InputBuffer;
		    InBuffer.BufferSize = length;
		    _rpc__Signal_Hash_Data(InBuffer);
		    break;
		  case TPM_SEND_COMMAND:
		    OK = ReadBytes(s, (char*)&locality, 1);
		    if(!OK)
			return TRUE;
		    OK = ReadVarBytes(s, InputBuffer, &length, MAX_BUFFER);
		    if(!OK)
			return TRUE;
		    InBuffer.Buffer = (BYTE*)InputBuffer;
		    InBuffer.BufferSize = length;
		    OutBuffer.BufferSize = MAX_BUFFER;
		    OutBuffer.Buffer = (_OUTPUT_BUFFER)OutputBuffer;
		    // record the number of bytes in the command if it is the largest
		    // we have seen so far.
		    if(InBuffer.BufferSize > CommandResponseSizes.largestCommandSize)
			{
			    CommandResponseSizes.largestCommandSize = InBuffer.BufferSize;
			    memcpy(&CommandResponseSizes.largestCommand,
				   &InputBuffer[6], sizeof(UINT32));
			}
		    _rpc__Send_Command(locality, InBuffer, &OutBuffer);
		    // record the number of bytes in the response if it is the largest
		    // we have seen so far.
		    if(OutBuffer.BufferSize > CommandResponseSizes.largestResponseSize)
			{
			    CommandResponseSizes.largestResponseSize
				= OutBuffer.BufferSize;
			    memcpy(&CommandResponseSizes.largestResponse,
				   &OutputBuffer[6], sizeof(UINT32));
			}
		    OK = WriteVarBytes(s,
				       (char*)OutBuffer.Buffer,
				       OutBuffer.BufferSize);
		    if(!OK)
			return TRUE;
		    break;
		  case TPM_REMOTE_HANDSHAKE:
		    OK = ReadBytes(s, (char*)&clientVersion, 4);
		    if(!OK)
			return TRUE;
		    if(clientVersion == 0)
			{
			    printf("Unsupported client version (0).\n");
			    return TRUE;
			}
		    OK &= WriteUINT32(s, ServerVersion);
		    OK &= WriteUINT32(s, tpmInRawMode
				      | tpmPlatformAvailable | tpmSupportsPP);
		    break;
		  case TPM_SET_ALTERNATIVE_RESULT:
		    OK = ReadBytes(s, (char*)&result, 4);
		    if(!OK)
			return TRUE;
		    // Alternative result is not applicable to the simulator.
		    break;
		  case TPM_SESSION_END:
		    // Client signaled end-of-session
		    return TRUE;
		  case TPM_STOP:
		    // Client requested the simulator to exit
		    return FALSE;
		  default:
		    printf("Unrecognized TPM interface command %d\n", (int)Command);
		    return TRUE;
		}
	    OK = WriteUINT32(s, 0);
	    if(!OK)
		return TRUE;
	}
    return FALSE;
}
