/**
 * Copyright (C) 2011 Samsung Electronics Co., Ltd. All rights reserved.
 *
 * Mobile Communication Division,
 * Digital Media & Communications Business, Samsung Electronics Co., Ltd.
 *
 * This software and its documentation are confidential and proprietary
 * information of Samsung Electronics Co., Ltd.  No part of the software and
 * documents may be copied, reproduced, transmitted, translated, or reduced to
 * any electronic medium or machine-readable form without the prior written
 * consent of Samsung Electronics.
 *
 * Samsung Electronics makes no representations with respect to the contents,
 * and assumes no responsibility for any errors that might appear in the
 * software and documents. This publication and the contents hereof are subject
 * to change without notice.
 *
 */
/**
 * @file hdcp2_smartview.cpp
 * @author
 * @date
 * @brief This file contains the functions to establish the connection between hdcp transmitter and receiver .
 */

#include <hdcp2.h>
#include <time.h>
#include <stdio.h>
#include <memory.h>
#include <sys/time.h>
#include <unistd.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <signal.h>
#include <pthread.h>
#include <stdarg.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/tcp.h>
#include <android/log.h>
#include "log.h"

/**
 * @def BUF_RESET()
 * This macro is assigned a value of
 *	  memset(rxbuffer, 0, sizeof(rxbuffer));
 * to be maintained everywhere.
 */
#define BUF_RESET() memset(rxbuffer, 0, sizeof(rxbuffer));

/**
 * @def RECEIVE(x)
 * This macro is assigned a value of
 * netlen = recv(csd, rxbuffer+1, (sizeof(x))-1, 0);\
 * netlen++;
 * to be maintained everywhere.
 */
#define RECEIVE(x)	netlen = recv(csd, rxbuffer+1, (sizeof(x))-1, 0);\
		netlen++;

/**
 * @def RX_CMD(x)
 * This macro is assigned a value of
 *   {
 *    if ((ret = (x)(hdcp,rxbuffer)) < 0)
 *     {
 *      ake_reset = 1;
 *      HDCP2_Log("Rx(%d)", ret);
 *      break;
 *     }
 *    }
 * to be maintained everywhere.
 */
#define RX_CMD(x) {if ((ret = (x)(hdcp,rxbuffer)) < 0) {ake_reset = 1; HDCP2_Log("Rx(%d)", ret); break;}}

/**
 * @def TX_CMD(x)
 * This macro is assigned a value of
 *   {
 *    if ((ret = (x)(hdcp,payload)) < 0)
 *     {
 *      ake_reset = 1;
 *      HDCP2_Log("Tx(%d)", ret);
 *      break;
 *     }
 *    }
 * to be maintained everywhere.
 */
#define TX_CMD(x) {if ((ret = (x)(hdcp,payload)) < 0) {ake_reset = 1; HDCP2_Log("Tx(%d)", ret); break;}}

/**
 * @def SEND()
 *
 * This macro is assigned value of
 *
 *  {if ((netlen = write(csd, payload, ret)) != ret) { \
 *  HDCP2_Log("SEND(ret=%d, netlen=%d)", ret, netlen); \
 *   ret = HDCP2_NETWORK_ERROR; \
 *   goto err; \
 *   }}
 *
 *  to be maintained everywhere.
 */
#define SEND() {if ((netlen = write(csd, payload, ret)) != ret) { \
		HDCP2_Log("SEND(ret=%d, netlen=%d)", ret, netlen); \
		ret = HDCP2_NETWORK_ERROR; \
		goto err; \
		}}

pthread_mutex_t mutex_;

 /**
  * @def QLEN
  *
  * This macro is assigned value of 6 to be maintained everywhere.
  */
#define QLEN	6

 /**
  * @fn void sig_pipe(int n)
  * @brief This function indicates some error with the pipe
  * @param n specifies the error code
  * @return void
  */
void sig_pipe(int n)
{
	fprintf(stderr, "Broken pipe signal\n");
}

/**
 * @fn void server(HDCP2_Ctx *hdcp, int csd)
 * @brief In an infinite loop, it reads characters from sockfd and writes them to the same socket.
 * @param hdcp - pointer to HDCP context
 * @param csd - socket descriptor
 * @return int - HDCP2_OK in case of success, else error code corresponding to the error
 */
void server(HDCP2_Ctx *hdcp, int csd)
{
	unsigned char payload[4096] = { 0 };
	unsigned char rxbuffer[4096] = { 0 };
	int ret = 0, netlen = 1;
	int quit = 0;
	struct timeval tv_time_300ms = { 0, 300000 }; /* 300ms */
	int ake_reset = 0;

	if (!hdcp)
		return;

	/* Make Time Out */
	if (setsockopt(csd, SOL_SOCKET, SO_RCVTIMEO, &tv_time_300ms, sizeof(tv_time_300ms)) == -1) {
		HDCP2_Log("setsockopt failure");
	}

	while (!quit) {
		memset(rxbuffer, 0, sizeof(rxbuffer));

		// clear RX queue for reset
		if (ake_reset) {
			HDCP2_Log("Reset = %d, Ret = %d", ake_reset, ret);
			if ((ret = read(csd, rxbuffer, sizeof(rxbuffer))) > 0)
				HDCP2_Log("Clear remained %d bytes", ret);
			ake_reset = 0;
		}

		/*read the command string */
		netlen = recv(csd, rxbuffer, 1, 0);
		if (netlen != 1) {
			HDCP2_Log("No response from client...");
			ret = HDCP2_NETWORK_ERROR;
			goto err;
		}
		//HDCP2_Log("\nReceived command %d, netlen=%d", rxbuffer[0], netlen);

		switch (rxbuffer[0]) {
		case CMD_AKE_INIT:
			RECEIVE(AKE_INIT)
			RX_CMD(AKE_Init_R)
			TX_CMD(AKE_Send_Cert_R)
			SEND()
			//HDCP2_Log("CMD_AKE_INIT Send()");
			break;
		case CMD_AKE_TRANSMITTER_INFO:
			RECEIVE(AKE_TRANSMITTER_INFO)
			RX_CMD(AKE_Tramsmitter_Info_R)
			if (hdcp->version == HDCP2_VERSION_2_0) {
				HDCP2_Log("HDCP Version is %d. Drop AKE_Transmitter_Info message", HDCP2_VERSION_2_0);
				ret = 1;
				goto err;
			}
			TX_CMD(AKE_Receiver_Info_R)
			SEND()
			break;
		case CMD_AKE_NO_STORED_KM:
			RECEIVE(AKE_NO_STORED_KM)
			RX_CMD(AKE_No_Stored_km_R)
			TX_CMD(AKE_Send_rrx_R)
			SEND()
			TX_CMD(AKE_Send_H_prime_R)
			SEND()
			TX_CMD(AKE_Send_Pairing_Info_R)
			SEND()
			break;
		case CMD_AKE_STORED_KM:
			RECEIVE(AKE_STORED_KM)
			RX_CMD(AKE_Stored_km_R)
			TX_CMD(AKE_Send_rrx_R)
			SEND()
			TX_CMD(AKE_Send_H_prime_R)
			SEND()
			break;
		case CMD_LC_INIT:
			RECEIVE(LC_INIT)
			RX_CMD(LC_Init_R)
			if (hdcp->Tx_LC_Precompute && hdcp->Rx_LC_Precompute && hdcp->version >= HDCP2_VERSION_2_1) {
				TX_CMD(RTT_Ready_R)
			} else {
				TX_CMD(LC_Send_L_prime_R)
			}
			SEND();
			break;
		case CMD_RTT_CHALLENGE:
			RECEIVE(RTT_CHALLENGE)
			RX_CMD(RTT_Challenge_R)
			TX_CMD(LC_Send_L_prime_R)
			SEND();
			break;
		case CMD_SKE_SEND_EKS:
			RECEIVE(SKE_SEND_EKS)
			RX_CMD(SKE_Send_Eks_R)
			quit = 1;
			break;
		default:
			ret = HDCP2_NETWORK_ERROR;
			HDCP2_Log(">>> Unsupported Command %d", rxbuffer[0]);
			goto err;
		}
	}

	HDCP2_Pause(hdcp);
	HDCP2_Log("HDCP 2.x AKE Finished (%d)", ret);

	return;

err:
	HDCP2_Log("HDCP 2.x AKE Failed with %d", ret);
	return;
}

/**
 * @fn void worker(void * arg)
 * @brief This function accepts the connection from client socket and then calls server_repeater
 * @param void
 * @return void
 */
void worker(void * arg)
{
	struct sockaddr_in cad;
	int alen = sizeof(cad);
	int sd2 = -1;
	HDCP2_Ctx *hdcp = (HDCP2_Ctx *) arg;

	if (!hdcp)
		return;

	/* Main server loop - accept and handle requests */
	while (hdcp->running_ake) {
		/* Obtain a connection sd2 from listening socket *sd */
		if (pthread_mutex_lock(&mutex_)) {
			HDCP2_Log("pthread_mutex_lock failed");
			return;
		}

		if ((sd2 = accept(hdcp->csd, (struct sockaddr *) &cad, (socklen_t *) &alen)) < 0) {
			HDCP2_Log("accept failed");

			if (pthread_mutex_unlock(&mutex_)) {
				HDCP2_Log("pthread_mutex_unlock failed");
			}
			return;
		}

		HDCP2_Log("accepted");
		if (pthread_mutex_unlock(&mutex_)) {
			HDCP2_Log("pthread_mutex_unlock failed");
			if (sd2 >= 0)
				close(sd2);
			return;
		}

		server(hdcp, sd2);
		if (sd2 >= 0)
			close(sd2);
	}

	if (hdcp->csd >= 0)
		close(hdcp->csd);
}

/**
 * @fn int HDCP2_Pause(HDCP2_Ctx *hdcp)
 * @brief This function resets the trustzone
 * @param hdcp - pointer to HDCP context
 * @return int - HDCP2_OK in case of success, else error code corresponding to the error
 */
int HDCP2_Pause(HDCP2_Ctx *hdcp)
{
	HDCP2_Log("Pause");

#ifdef USE_MOBICORE
	return HDCP2_TZ_Reset(hdcp);
#else
	return HDCP2_OK;
#endif /* USE_MOBICORE */
}

/**
 * @fn int HDCP2_Start_Receiver(HDCP2_Ctx *hdcp, const int port, int threadwait);
 * @brief starts the worker thread. Worker thread handles authentication
 * @param hdcp - pointer to HDCP context
 * @param port - indicates the port to be used in establishing socket connection
 * @param threadwait If threadwait is enabled, hdcp2_start_receiver will wait for the worker thread to exit
 * @return int - HDCP2_OK in case of success, else error code corresponding to the error
 */
int HDCP2_Start_Receiver(HDCP2_Ctx *hdcp, const int port, int threadwait)
{
	struct sockaddr_in sad;/* structure to hold server's address*/
	int sd; /* listening socket descriptor */
	int option_value = 1; /* needed for setsockopt */
	pthread_t tid; /* two thread ids */

	if (!hdcp)
		return HDCP2_ERR_INVALID_INPUT;

	/* clear and initialize server's sockaddr structure */
	memset((char *) &sad, 0, sizeof(sad));
	sad.sin_family = AF_INET; /* set family to Internet */
	sad.sin_addr.s_addr = INADDR_ANY; /* set the local IP address */
	sad.sin_port = htons((u_short) port); /* Set port */

	/* Create socket */
	if ((sd = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
		HDCP2_Log("socket creation failed");
		return HDCP2_NETWORK_ERROR;
	}

	/* Make listening socket's port reusable - must appear before bind */
	if (setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, (char *) &option_value, sizeof(option_value)) < 0) {
		HDCP2_Log("setsockopt failure");
		if (sd >= 0)
			close(sd);
		return HDCP2_NETWORK_ERROR;
	}

	/* disable nagle algorithm */
	if (setsockopt(sd, IPPROTO_TCP, TCP_NODELAY, (char *) &option_value, sizeof(option_value)) < 0) {
		HDCP2_Log("setsockopt failure");
		if (sd >= 0)
			close(sd);
		return HDCP2_NETWORK_ERROR;
	}

	/* Bind a local address to the socket */
	if (bind(sd, (struct sockaddr *) &sad, sizeof(sad)) < 0) {
		HDCP2_Log("bind failed");
		if (sd >= 0)
			close(sd);
		return HDCP2_NETWORK_ERROR;
	}

	/* Specify size of request queue */
	if (listen(sd, QLEN) < 0) {
		HDCP2_Log("listen failed");
		if (sd >= 0)
			close(sd);
		return HDCP2_NETWORK_ERROR;
	}

	/* Establish handling of the SIGPIPE signal */
	if (signal(SIGPIPE, sig_pipe) == SIG_ERR) {
		HDCP2_Log("Unable to set up signal handler");
	}

	/* initialize mutex_ */
	if (pthread_mutex_init(&mutex_, NULL)) {
		HDCP2_Log("pthread_mutex_init\n");
		if (sd >= 0)
			close(sd);
		return HDCP2_NETWORK_ERROR;
	}

	/* create worker threads. Each worker thread will compete on mutex_ to accept a connection.
	 Once a worker thread has a connection, it releases the mutex_ so that another thread can
	 get the connection. */
	hdcp->csd = sd;
	hdcp->running_ake = 1;
	if (pthread_create(&tid, NULL, (void* (*)(void*)) worker, hdcp) != 0) {
		HDCP2_Log("pthread_create");
		return HDCP2_NETWORK_ERROR;
	}

	/* wait for threads to terminate - they shouldn't */
	if (threadwait)
		pthread_join(tid, NULL);

	return HDCP2_OK;
}

/**
 * @fn int HDCP2_Stop_Receiver(HDCP2_Ctx *hdcp)
 * @brief calls This function releases all the memory and signals the worker thread to exit
 * @param hdcp - pointer to HDCP context
 * @return int - HDCP2_OK in case of success, else error code corresponding to the error
 */
int HDCP2_Stop_Receiver(HDCP2_Ctx *hdcp)
{
	if(!hdcp)
		return HDCP2_ERR_INVALID_INPUT;

	hdcp->running_ake = 0;
	HDCP2_Pause(hdcp);
	HDCP2_Log("Stop");

	return HDCP2_OK;
}
