/**
 * 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_wfd.cpp
 * @author
 * @date
 * @brief This file contains the functions to do 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 "log.h"
#include <android/log.h>

#include <pthread.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>

int conditionMet = 0;
bool repeater_worker_thread_alive = false;
pthread_cond_t cond  = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex_cond = PTHREAD_MUTEX_INITIALIZER;

/**
 * @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
#define TRY_COUNT	1024
#define HDCP2_CONNECTION_ISSUE

void HDCP2_Close_Socket(HDCP2_Ctx *hdcp, const char *type)
{
	if (hdcp == NULL || type == NULL) {
		HDCP2_Log("Invalid input in HDCP2_Close_Socket");
		return;
	}

	if (strncmp(type, "sd", 2) == 0 && hdcp->sd > 0) {
		if (close(hdcp->sd) < 0) {
			HDCP2_Log("Close listening socket failure. %s", strerror(errno));
		} else {
			hdcp->sd = -1;
			HDCP2_Log("Close listening socket completely");
		}
	} else if (strncmp(type, "csd", 3) == 0 && hdcp->csd > 0) {
		if (close(hdcp->csd) < 0) {
			HDCP2_Log("Close data socket failure. %s", strerror(errno));
		} else {
			hdcp->csd = -1;
			HDCP2_Log("Close data socket completely");
		}
	}
}

/**
 * @fn static int check_dir_exist(const char* path)
 * @brief This function checks whether the directory mentioned in the path exists
 * @param path - pointer to the path that is to be checked
 * @return int - HDCP2_Ok in case of success, else -1
 */
static int check_dir_exist(const char* path)
{
	if (access(path, R_OK) == 0)
		return HDCP2_OK;

	HDCP2_Log("Fail to access %s. %s(%d)", path, strerror(errno), errno);

	return -1;
}

static int check_ReceiveMsg_id(const uint8_t MsgId, uint8_t *RxId)
{
	int ret = HDCP2_OK;

	if (MsgId != *RxId) {
		HDCP2_Log("[ERROR] Expected msg ID: %d, but received msg ID: %d", MsgId, *RxId);
		ret = HDCP2_NETWORK_ERROR;
	}

	//HDCP2_Log("Expected msg ID: %d, and received msg ID: %d", MsgId, *RxId);
	return ret;
}

/**
 * @fn static int make_directory(const char* path)
 * @brief This function creates directory mentioned in the path
 * @param path - pointer to the path where directory is to be created
 * @return int - HDCP2_Ok in case of success, else -1
 */
static int make_directory(const char* path)
{
	int ret = HDCP2_OK;

	if (check_dir_exist(path) != HDCP2_OK) {
		HDCP2_DEBUG("create dir [%s]", path);
		if (mkdir(path, 0744) != HDCP2_OK) //only owner
		{
			HDCP2_Log("create dir failed... [%s]. %s(%d)", path, strerror(errno), errno);
			ret = -1;
		}
	}

	return ret;
}

/**
 * @fn static void GetTimeStamp(timestamp *ts)
 * @brief This function obtains the current timestamp of the system
 * @param ts - pointer to be populated in the function
 * @return void
 */
void GetTimeStamp(timestamp *ts)
{
	gettimeofday((struct timeval *) ts, NULL);
}

/**
 * @fn tatic long EstimateTime(timestamp *start, timestamp *end)
 * @brief This function calculates the time elapsed between the two instances
 * @param start - pointer to the timestamp at the beginning
 * @param end - pointer to the timestamp at the end
 * @return long - time elapsed
 */
static long EstimateTime(timestamp *start, timestamp *end)
{
	return (end->tv_sec - start->tv_sec) * 1000 + (end->tv_usec - start->tv_usec) / 1000;
}

/**
 * @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 int HDCP2_NotifyRepeater(HDCP2_Ctx *hdcp, int retcode)
 * @brief This function notifies the repeater about the retcodes for server_repeater function
 * @param hdcp - pointer to HDCP context
 * @param retcode - the status to be notified to the repeater
 * @return int - HDCP2_OK in case of success, else error code corresponding to the error
 */
int HDCP2_NotifyRepeater(HDCP2_Ctx *hdcp, int retcode)
{
	msg error_msg;
	ext1 error_ext1;

	if (!hdcp || !hdcp->observerNotify)
		return HDCP2_ERR_INVALID_INPUT;

	switch (retcode) {
	case HDCP2_OK:
		error_msg = HDCP2_AUTHENTICATION_SUCCESS;
		error_ext1 = HDCP2_ERROR_NONE;
		HDCP2_Log("HDCP 2.x Repeater Connected");
		break;
	case HDCP2_NETWORK_ERROR:
		error_msg = HDCP2_UPSTREAM_SHUTDOWN;
		error_ext1 = HDCP2_ERROR_NETWORK;
		break;
	case HDCP2_TZ_NULL_REQUEST:
		error_msg = HDCP2_UPSTREAM_SHUTDOWN;
		error_ext1 = HDCP2_ERROR_NULL_REQUEST;
		break;
	case HDCP2_TZ_NULL_RESPONSE:
		error_msg = HDCP2_UPSTREAM_SHUTDOWN;
		error_ext1 = HDCP2_ERROR_NULL_RESPONSE;
		break;
	case HDCP2_ERR_INVALID_STATE:
		error_msg = HDCP2_INVALID_STATE;
		error_ext1 = HDCP2_ERROR_INVALID_STATE;
		break;
	case HDCP2_ERR_BKSV_READ_FAIL:
		error_msg = HDCP2_DOWNSTREAM_HDMI_ERROR;
		error_ext1= HDCP2_ERROR_HDMI_CABLE;
		break;
	case HDCP2_TYPE1_CONTENT:
		error_msg = HDCP2_DOWNSTREAM_TYPE_CHANGE;
		error_ext1 = HDCP2_DOWNSTREAM_TYPE1_CONTENT;
		break;
	case HDCP2_TYPE0_CONTENT:
		error_msg = HDCP2_DOWNSTREAM_TYPE_CHANGE;
		error_ext1 = HDCP2_DOWNSTREAM_TYPE0_CONTENT;
		break;
	default:
		error_msg = HDCP2_DOWNSTREAM_SHUTDOWN;
		error_ext1 = HDCP2_ERROR_CRYPTO;
	}

	if (retcode != HDCP2_OK)
		HDCP2_Log("Notify (%d, %d, %d)", retcode, error_msg, error_ext1);

	hdcp->observerNotify((void*) hdcp->wfd_ctx, error_msg, error_ext1, 0);

	return HDCP2_OK;
}

/**
 * @fn void server_repeater(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
 */
/*In an infinite loop, it reads characters from sockfd and writes them
 to the same socket.
 */
void server_repeater(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;
	int option_value = 1;
	timestamp ts_s, ts_e;
	long duration = 0;
	ts_s.tv_sec=0;
	ts_s.tv_usec=0;
	ts_e.tv_sec=0;
	ts_e.tv_usec=0;

	if (!hdcp || csd <0 ) {
		HDCP2_Log("Invalid argument in server_repeater()");
		ret = HDCP2_ERR_INVALID_STATE;
		if (HDCP2_NotifyRepeater(hdcp, ret) != HDCP2_OK)
			HDCP2_Log("Notify failed in server_repeater()");
		return;
	}

#ifndef USE_QUALCOMM
	if (!hdcp->tlc_opened ) {
		HDCP2_Log("Invalid argument in server_repeater()");
		ret = HDCP2_ERR_INVALID_STATE;
		if (HDCP2_NotifyRepeater(hdcp, ret) != HDCP2_OK)
			HDCP2_Log("Notify failed in server_repeater()");

		return;
	}
#endif /* USE_QUALCOMM */

	/* disable nagle algorithm */
	if (setsockopt(csd, IPPROTO_TCP, TCP_NODELAY, (char *) &option_value, sizeof(option_value)) < 0) {
		HDCP2_Log("setsockopt failure. %s", strerror(errno));
	}

	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) {
			if (hdcp->sd > 0) // Whether close Allshare cast or not
				HDCP2_Log("No response from client...");
			ret = HDCP2_NETWORK_ERROR;
			break;
		}

		HDCP2_DEBUG("Received 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()
			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:
			if (hdcp->version == HDCP2_VERSION_2_3) {
				RECEIVE(SKE_SEND_EKS_VER23)
			} else {
				RECEIVE(SKE_SEND_EKS)
			}
			RX_CMD(SKE_Send_Eks_R)
			if (hdcp->entity == HDCP2_RECEIVER) {
				quit = 1;
				ret = HDCP2_OK;
				break;
			}
			TX_CMD(RepeaterAuth_Send_ReceiverId_List_Rep)
			SEND();
			GetTimeStamp(&ts_s);
			if (hdcp->version == HDCP2_VERSION_2_0) {
				quit = 1;
				ret = HDCP2_OK;
				goto err;
			}
			break;
		case CMD_REPEATERAUTH_SEND_ACK:
			RECEIVE(REPEATERAUTH_SEND_ACK)
			GetTimeStamp(&ts_e);
			duration = EstimateTime(&ts_s, &ts_e);
			if (duration > HDCP2_VALID_TIME_1SEC) {
				HDCP2_Log("Timeout: REPEATERAUTH_SEND_ACK %d ms", duration);
				ret = HDCP2_ERR_REPEATER_AUTH_SEND_ACK_FAILED;
				//break;//TBD:send REAUTH_REQ=true if RepeaterAuth_Send_Ack is not received within one second or
				//mismatch occurs between least significant 128 bits of V and V'.
			}
			RX_CMD(RepeaterAuth_Send_Ack_Rep)
			TX_CMD(Receiver_AuthStatus_Rep)
			SEND()
			break;
		case CMD_REPEATERAUTH_STREAM_MANAGE:
			RECEIVE(REPEATERAUTH_STREAM_MANAGE)
			RX_CMD(RepeaterAuth_Stream_Manage_Rep)
			TX_CMD(RepeaterAuth_Stream_Ready_Rep)
			SEND()
			// temporary enabled to quit AKE
			// we should not quit AKE session due to the dynamic down prop.
			ret = HDCP2_OK;
			quit = 1;
			goto err;
		default:
			ret = HDCP2_NETWORK_ERROR;
			HDCP2_Log(">>> Unsupported Command %d", rxbuffer[0]);
			break;
		}

err:
		if(ret <= 0) {
			if (ret != HDCP2_NETWORK_ERROR) {
				if (HDCP2_NotifyRepeater(hdcp, ret) != HDCP2_OK){
					HDCP2_Log("Notify failed");
					// hdcp->running_ake reset will help to close the worker thread.
					hdcp->running_ake = 0;
					break;
				} else {
					if (quit == 1 && conditionMet == 0) {
						if (pthread_mutex_lock(&mutex_cond) != 0) {
							HDCP2_Log("pthread_mutex_lock failed. %s", strerror(errno));
							return;
						}

						conditionMet = 1;

						if (pthread_cond_broadcast(&cond) != 0) {
							HDCP2_Log("pthread_cond_broadcast failed. %s", strerror(errno));
							if(pthread_mutex_unlock(&mutex_cond) != 0){
								   HDCP2_Log("pthread_mutex_unlock failed. %s", strerror(errno));
							}
							return;
						}

						if (pthread_mutex_unlock(&mutex_cond) != 0) {
							HDCP2_Log("pthread_mutex_unlock failed. %s", strerror(errno));
							return;
						}
					}
					hdcp->running_ake = 0;
				}
			} else { // HDCP2_NETWORK_ERROR
			// need to retry it
				hdcp->running_ake = 1;
				quit = 1;
			}
		}
	}
	return;
}

bool isSocketInListeningMode = false;

/**
 * @fn void repeater_worker(void * arg)
 * @brief This function accepts the connection from client socket and then calls server_repeater
 * @param void
 * @return void
 */
void repeater_worker(void * arg)
{
	struct sockaddr_in cad;
	int alen = sizeof(cad);
	HDCP2_Ctx *hdcp = (HDCP2_Ctx *) arg;

	if (!hdcp) {
		HDCP2_Log("Invalid input in repeater_worker()");
		goto exit;
	}

	/* 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. %s", strerror(errno));
			goto exit;
		}

		HDCP2_Log("Waiting for the client to connect ");
		isSocketInListeningMode = true;

		if ((hdcp->csd = accept(hdcp->sd, (struct sockaddr *) &cad, (socklen_t *) &alen)) < 0) {
			if (hdcp->running_ake != 0 )
				HDCP2_Log("accept failed. %s", strerror(errno));

			if (pthread_mutex_unlock(&mutex)) {
				HDCP2_Log("pthread_mutex_unlock failed. %s", strerror(errno));
			}
			goto exit;

		}

		isSocketInListeningMode = false;
		if (pthread_mutex_unlock(&mutex)) {
			HDCP2_Log("pthread_mutex_unlock failed. %s", strerror(errno));
			goto exit;
		}

		if(hdcp->running_ake == 0) {
			if (hdcp->csd >= 0)
				HDCP2_Close_Socket(hdcp, "csd");
			HDCP2_Log("Listening THREAD exit(1)");
			goto exit;
		}

		server_repeater(hdcp, hdcp->csd);

		if (hdcp->csd > 0) {
			HDCP2_Close_Socket(hdcp, "csd");
		}

	}
	HDCP2_Log("Listening THREAD exit(2)");

exit:
	HDCP2_Log("Listening THREAD exit");
	if (conditionMet == 0) {
		if (pthread_mutex_lock(&mutex_cond) != 0)
			HDCP2_Log("pthread_mutex_lock failed. %s", strerror(errno));

		conditionMet = 1;

		if (pthread_cond_broadcast(&cond) != 0)
			HDCP2_Log("pthread_cond_broadcast failed. %s", strerror(errno));

		 if (pthread_mutex_unlock(&mutex_cond) != 0)
			 HDCP2_Log("pthread_mutex_unlock failed. %s", strerror(errno));
	}
	pthread_exit(NULL);
}

/**
 * @fn int HDCP2_Start_Repeater(HDCP2_Ctx *hdcp, const int port, HDCP2Observer observerNotify, void* wfd_ctx)
 * @brief This function calls HDCP2_Start_WFD_Receiver function which creates a socket and also creates threads of repeater worker type
 * @param hdcp - pointer to HDCP context
 * @param observerNotify - Callback function which is to be defined in WFD for error handling
 * @param wfd_ctx - pointer to WFD context
 * @return int - HDCP2_OK in case of success, else error code corresponding to the error
 */
int HDCP2_Start_Repeater(HDCP2_Ctx *hdcp, const int port, HDCP2Observer observerNotify, void* wfd_ctx)
{
	return HDCP2_Start_WFD_Receiver(hdcp, port, observerNotify, wfd_ctx);
}

/**
 * @fn int HDCP2_Start_WFD_Receiver(HDCP2_Ctx *hdcp, const int port, HDCP2Observer observerNotify, void* wfd_ctx)
 * @brief This function  which creates a socket and also creates threads of repeater worker type
 * @param hdcp - pointer to HDCP context
 * @param port - port on which socket will be created
 * @param observerNotify - Callback function which is to be defined in WFD for error handling
 * @return int - HDCP2_OK in case of success, else error code corresponding to the error.
 */
int HDCP2_Start_WFD_Receiver(HDCP2_Ctx *hdcp, const int port, HDCP2Observer observerNotify, void* wfd_ctx)
{
	struct sockaddr_in sad;/* structure to hold server's address*/
	int option_value = 1; /* needed for setsockopt */
	pthread_t tid; /* two thread ids */
	pthread_attr_t attr;
	struct sched_param param;
	int ret = HDCP2_OK;
	cond = (pthread_cond_t) PTHREAD_COND_INITIALIZER;
	mutex_cond = (pthread_mutex_t) PTHREAD_MUTEX_INITIALIZER;
	memset(&param, 0x0, sizeof(param));
	conditionMet = 0;

	if (!hdcp)
		return HDCP2_ERR_INVALID_INPUT;

	hdcp->observerNotify = observerNotify;
	hdcp->wfd_ctx = wfd_ctx;

	pthread_attr_init(&attr);
	pthread_attr_setschedpolicy(&attr, SCHED_FIFO);
	pthread_attr_setschedparam(&attr, &param);

	/* 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 ((hdcp->sd = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
		HDCP2_Log("socket creation failed. %s", strerror(errno));
		return HDCP2_NETWORK_ERROR;
	}

	/* Make listening socket's port reusable - must appear before bind */
	if (setsockopt(hdcp->sd, SOL_SOCKET, SO_REUSEADDR, &option_value, sizeof(option_value)) < 0) {
		HDCP2_Log("setsockopt failure. %s", strerror(errno));
		HDCP2_Close_Socket(hdcp, "sd");
		return HDCP2_NETWORK_ERROR;
	}

	/* Bind a local address to the socket */
	if (bind(hdcp->sd, (struct sockaddr *) &sad, sizeof(sad)) < 0) {
		HDCP2_Log("bind failed. %s", strerror(errno));
		HDCP2_Close_Socket(hdcp, "sd");
		return HDCP2_NETWORK_ERROR;
	}

	/* Specify size of request queue */
	if (listen(hdcp->sd, QLEN) < 0) {
		HDCP2_Log("listen failed. %s", strerror(errno));
		HDCP2_Close_Socket(hdcp, "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. %s", strerror(errno));
	}

	/* initialize mutex */
	if (pthread_mutex_init(&mutex, NULL)) {
		HDCP2_Log("pthread_mutex_init. %s", strerror(errno));
		return HDCP2_ERR_THREAD;
	}

	/* 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->running_ake = 1;
	if (pthread_create(&tid, &attr, (void* (*)(void *)) repeater_worker, hdcp) != 0) {
		HDCP2_Log("pthread_create. %s", strerror(errno));
		return HDCP2_ERR_THREAD;
	}

	// Wait for succees s of AKE
	if (pthread_mutex_lock(&mutex_cond) != 0) {
		HDCP2_Log("pthread_mutex_lock failed. %s", strerror(errno));
		return HDCP2_ERR_THREAD;
	}

	while (!conditionMet) {
		//printf("Thread blocked\n");
		if (pthread_cond_wait(&cond, &mutex_cond) != 0) {
			HDCP2_Log("pthread_cond_wait failed. %s", strerror(errno));
			ret = HDCP2_ERR_THREAD;
			break;
		}
	}

	if (pthread_mutex_unlock(&mutex_cond) != 0) {
		HDCP2_Log("pthread_mutex_unlock failed. %s", strerror(errno));
		ret = HDCP2_ERR_THREAD;
	}

	pthread_cond_destroy(&cond);
	pthread_mutex_destroy(&mutex_cond);

	HDCP2_Log("Return HDCP2_Start_Repeater()");
	return ret;
}

/**
 * @fn int HDCP2_Stop_Repeater(HDCP2_Ctx *hdcp)
 * @brief This function closes the socket connection
 * @param hdcp - pointer to HDCP context
 * @return int - HDCP2_OK in case of success, else error code corresponding to the error
 */
int HDCP2_Stop_Repeater(HDCP2_Ctx *hdcp)
{
	HDCP2_Log("HDCP2_Stop_Repeater called");

	if (!hdcp) {
		HDCP2_Log("HDCP2_Stop_Repeater Left");
		return HDCP2_ERR_INVALID_INPUT;
	}
	hdcp->running_ake = 0;

	HDCP2_Pause(hdcp);

	if (hdcp->sd > 0) {
		shutdown(hdcp->csd, SHUT_RD);
		shutdown(hdcp->sd, SHUT_RD);
	}

	conditionMet = 0;

	return HDCP2_OK;
}

/**
 * @fn int HDCP2_Stop_WFD_Receiver(HDCP2_Ctx *hdcp)
 * @brief This function closes the socket connection by calling HDCP2_Stop_Repeater function
 * @param hdcp - pointer to HDCP context
 * @return int - HDCP2_OK in case of success, else error code corresponding to the error
 */
int HDCP2_Stop_WFD_Receiver(HDCP2_Ctx *hdcp)
{
	return HDCP2_Stop_Repeater(hdcp);
}

/**
 * @fn int HDCP2_Connect(HDCP2_Ctx *hdcp, const char *address, const int port)
 * @brief It starts the hdcp connection
 * @param hdcp - pointer to HDCP context
 * @param address - pointer to IP address string
 * @param port - Port on which hdcp connection is to be made
 * @return int - HDCP2_OK in case of success, else error code corresponding to the error
 */
int HDCP2_Connect(HDCP2_Ctx *hdcp, const char *address, const int port)
{
	unsigned char payload[4096] = { 0 };
	unsigned char rxbuffer[4096] = { 0 };
	struct sockaddr_in serv_addr;
	int ret = 0;
	int res = 0;
	int write_sz=0;
	struct timeval tv_time_7ms = { 0, 7000 }; /* 7ms */
	struct timeval tv_time_50ms = { 0, 50000 }; /* 50ms */
	struct timeval tv_time_100ms = { 0, 100000 }; /* 100ms */
	struct timeval tv_time_150ms = { 0, 150000 }; /* 150ms */
	struct timeval tv_time_200ms = { 0, 200000 }; /* 200ms */
	struct timeval tv_time_300ms = { 0, 300000 }; /* 300ms */
	struct timeval tv_time_400ms = { 0, 400000 }; /* 400ms */
	struct timeval tv_time_1sec = {1, 0 }; /* 1 sec */
	struct timeval tv_time_3sec = { 3, 0 }; /* 3 sec */
	struct timeval tv_time_20ms = {0, 0};

	fd_set write_fdset;

	int num_try=TRY_COUNT;
	int tries = 0, lctries = 0;
	long duration = 0;
	int option_value = 1; /* needed for setsockopt */
	int option_value2 = 0;
	Downstream_Params params ;
	int flags = 0;
	socklen_t length;

	/*
	 *  Pre 0. Check conditions
	 */
	if (!hdcp || !address || port <= 0) {
		HDCP2_Log("Invalid input: %x, %s, %d", hdcp, address, port);
		usleep(300000);
		return HDCP2_ERR_INVALID_INPUT;
	}
	if (HDCP2_IsClosed(hdcp)) {
		usleep(300000);
		return HDCP2_ERR_CLOSED;
	}

	bzero((char *) &serv_addr, sizeof(serv_addr));
	serv_addr.sin_family = PF_INET;
	serv_addr.sin_addr.s_addr = inet_addr(address);
	serv_addr.sin_port = htons(port);

	GetTimeStamp(&hdcp->time_ake_start);

	/*
	 * Pre1. Make Dir
	 */
#if defined(USE_MOBICORE) || defined(USE_TEEGRIS) || !defined(USE_QSEE_WRAP_WITH_SFS)
	if (make_directory(hdcp->pair_info_dir) < 0)
		return HDCP2_ERR_MAKE_PAIRING_DIR;
#endif /* USE_MOBICORE || USE_TEEGRIS || USE_QSEE_WRAP_WITH_SFS */

	HDCP2_Log(" ------------------------------------------");
	/*
	 * Pre2. Open Socket
	 */
	// connect to  the server
	while (num_try > 0) {
		if (hdcp->sd > 0) {
			HDCP2_Close_Socket(hdcp, "sd");
		}
		if ((hdcp->sd = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
			HDCP2_Log("socket creation error. %s", strerror(errno));
			ret = HDCP2_NETWORK_ERROR;
			goto err;
		}

		// Get the file descriptor flag
		flags = fcntl(hdcp->sd, F_GETFL, 0);
		if (fcntl(hdcp->sd, F_SETFL, flags | O_NONBLOCK) < 0) {
			HDCP2_Log("Failed to set non-block, %s", strerror(errno));
			 ret = HDCP2_NETWORK_ERROR;
			goto err;
		}

		HDCP2_DEBUG("Socket opened successfully [%s:%d]", address, port);

		if ((ret = connect(hdcp->sd, (struct sockaddr *) &serv_addr, sizeof(serv_addr))) < 0) {
			num_try--;

#ifdef HDCP2_CONNECTION_ISSUE
			if (num_try<(TRY_COUNT/2)) {
				HDCP2_Log("before setsockopt: num_try is less than TRY_COUNT/2");

				if (setsockopt(hdcp->sd,SOL_SOCKET,SO_RCVTIMEO, &tv_time_100ms, sizeof(tv_time_100ms))<0) {
					HDCP2_Log("Error in setsockopt(). %s\n", strerror(errno));
				}
			}
#endif /* HDCP2_CONNECTION_ISSUE */

			ret = HDCP2_NETWORK_ERROR;

			if (errno == EINPROGRESS) {
				//HDCP2_Log("EINPROGRESS in connect() - selecting");
				FD_ZERO(&write_fdset);
				FD_SET(hdcp->sd, &write_fdset);

				tv_time_20ms.tv_sec = 0;
				tv_time_20ms.tv_usec = 20000;

				//Wait for writefds since socket is processing "connect".
				res = select(hdcp->sd+1, NULL/*readfds*/, &write_fdset/*writefds*/, NULL/*exceptfds*/, &tv_time_1sec);

				if (res < 0 && errno == EINTR) {
					HDCP2_Log("Interrupted. Retry");
					continue;
				} else if (res > 0) {
				// Successful
					if (FD_ISSET(hdcp->sd, &write_fdset)) {
						// Socket selected for write
						length = sizeof(int);
						if (getsockopt(hdcp->sd, SOL_SOCKET, SO_ERROR, (void*)(&option_value2), &length) < 0) {
							HDCP2_Log("Error in getsockopt(). %s", strerror(errno));
							continue;
						}
						// Check the value returned
						if (option_value2) {
							HDCP2_Log("Error in delayed connection. %s(%d)", strerror(option_value2), option_value2);
							continue;
						}
						ret = HDCP2_OK;
						break;
					}
					continue;
				} else { // res == 0 if it meets timeout (10000usec). res < 0 means other error such as nomem, wrong fd etc.
					// This usually happens when socket is disconnected or network is too slow. Previously, we used Block mode connection, so when disconnected, connect function used to wait for 10 sec.
					// With current code, it will close the socket, and re-try connection for maximum 30 times.
					HDCP2_Log("Error connecting2. %s", strerror(errno));
					ret = HDCP2_NETWORK_UNREACHABLE;
					continue;
				}
			} else {
				HDCP2_Log("Error connecting3. %s", strerror(errno));
				continue;
			}
		}
	}

	if (ret == HDCP2_NETWORK_UNREACHABLE) {
		HDCP2_Log("Can't connect to the server %s:%d. %s", address, port, strerror(errno));
		goto err;
	}

	// Set to Block mode. Otherwise All the other communicates will fail.
	if (fcntl(hdcp->sd, F_SETFL, flags & ~(O_NONBLOCK)) < 0) {
		HDCP2_Log("Failed to set block, %s", strerror(errno));
		ret = HDCP2_NETWORK_ERROR;
		goto err;
	}
	/* disable nagle algorithm*/
	if (setsockopt(hdcp->sd, IPPROTO_TCP, TCP_NODELAY, (char *) &option_value, sizeof(option_value)) < 0) {
		HDCP2_Log("setsockopt failure. %s", strerror(errno));
		return HDCP2_NETWORK_ERROR;
	}

	/*
	 * HDCP 2.1 AKE
	 */
	for (tries = 0; tries < hdcp->ake_retry_count; tries++) {
		if (hdcp->ake_retry_count > 1) {
			if (tries > 0)
				HDCP2_Log(" ===========  AKE(%.3d)  ==========", tries);

			HDCP2_Log("HDCP version is: %d.%d", hdcp->version / 10, hdcp->version % 10);
		}

		if (tries) {
			if (setsockopt(hdcp->sd, SOL_SOCKET, SO_RCVTIMEO, &tv_time_400ms, sizeof(tv_time_400ms)) == -1) {
				HDCP2_Log("setsockopt failure. %s", strerror(errno));
				ret = HDCP2_NETWORK_ERROR;
				goto err;
			}

			if ((ret = read(hdcp->sd, rxbuffer, sizeof(rxbuffer))) > 0) {
				HDCP2_Log("Clear remained 1 %d bytes", ret);

				if (setsockopt(hdcp->sd, SOL_SOCKET, SO_RCVTIMEO, &tv_time_50ms, sizeof(tv_time_50ms)) == -1) {
					HDCP2_Log("setsockopt failure. %s", strerror(errno));
					ret = HDCP2_NETWORK_ERROR;
					goto err;
				}

				if ((ret = read(hdcp->sd, rxbuffer, sizeof(rxbuffer))) > 0)
					HDCP2_Log("Clear remained 2 %d bytes", ret);
			}
		}

		/*
		 * 1. Send AKE_Init
		 */
		if ((ret = AKE_Init_T(hdcp, payload)) < 0)
			continue;

		if ((write_sz=write(hdcp->sd, payload, ret)) != ret) {
			HDCP2_Log("FAILED sends AKE_Init. %s ,", strerror(errno));
			HDCP2_Log("bytes written successfully=%d ,", write_sz);
			HDCP2_LogHex("akeinit... data written is : ", payload, write_sz );
			// if there is Broken Pipe error, try one more connection.
			if (errno == EPIPE) {
				ret = HDCP2_ERR_UNKNOWN_PIPE;
				goto err;
			} else {
				ret = HDCP2_NETWORK_ERROR;
			}

			continue;
		}

		/*
		 * 2. Send AKE_Tramsmitter_Info (>= HDCP 2.1)
		 */
		if (hdcp->version >= HDCP2_VERSION_2_1) {
			if ((ret = AKE_Tramsmitter_Info_T(hdcp, payload)) < 0)
				continue;

			if ((write_sz=write(hdcp->sd, payload, ret)) != ret) {
				HDCP2_Log("FAILED sends AKE_Transmitter_Info. %s", strerror(errno));
				HDCP2_Log("bytes written successfully=%d ,",write_sz);
				HDCP2_LogHex("ake_transmitter... data written is : ",payload,write_sz );
				ret = HDCP2_NETWORK_ERROR;
				continue;
			}

			/* Make Time Out */
			if (setsockopt(hdcp->sd, SOL_SOCKET, SO_RCVTIMEO, &tv_time_100ms, sizeof(tv_time_100ms)) == -1) {
				HDCP2_Log("setsockopt failure. %s", strerror(errno));
				ret = HDCP2_NETWORK_ERROR;
				goto err;
			}
		}

		/*
		 * 3. Receive AKE_Send_Cert
		 */
		if ((ret = read(hdcp->sd, rxbuffer, HDCP2_SIZE_AKE_SEND_CERT)) != HDCP2_SIZE_AKE_SEND_CERT) {
			HDCP2_Log("bytes read successfully=%d ",ret);
			HDCP2_LogHex("data:",rxbuffer, ret);
			if (errno == EAGAIN || errno == ECONNRESET) {
				if (tries + 1 == (hdcp->ake_retry_count - 5) && hdcp->version > HDCP2_VERSION_2_1) {
					hdcp->version = HDCP2_VERSION_2_1;
					ret = HDCP2_SetVersion(hdcp);
					tries = 0;
					HDCP2_Log("HDCP version changed to: %d.%d", hdcp->version / 10, hdcp->version % 10);
					continue;
				} else {
					HDCP2_Log("Timeout. Next message should be received by 100 ms");
				}
			// 2013 TV doesn't reply for 2.3 transmitter information
			} else if (errno == EINPROGRESS && hdcp->version > HDCP2_VERSION_2_0) {
				hdcp->version = HDCP2_VERSION_2_2;
				ret = HDCP2_SetVersion(hdcp);
				HDCP2_Log("[Need to check Rcv. side] HDCP version changed to: %d.%d", hdcp->version / 10, hdcp->version % 10);
			} else {
				HDCP2_Log("FAILED receives AKE_Send_Cert. %s", strerror(errno));
			}
			continue;
		} else if ((ret = check_ReceiveMsg_id(CMD_AKE_SEND_CERT, rxbuffer)) < 0) {
			continue;
		}

		if ((ret = AKE_Send_Cert_T(hdcp, payload, rxbuffer)) < 0)
			continue;

		/*
		 * 4. Receive AKE_Receiver_Info (>= HDCP 2.1)
		 */
		if (hdcp->version >= HDCP2_VERSION_2_1) {
			if ((ret = read(hdcp->sd, rxbuffer, HDCP2_SIZE_AKE_RECEIVER_INFO)) != HDCP2_SIZE_AKE_RECEIVER_INFO) {
				HDCP2_Log("bytes read successfully=%d ",ret);
				HDCP2_LogHex("data:",rxbuffer, ret);
				if (errno == EAGAIN || errno == ECONNRESET) {
					if (tries + 1 == (hdcp->ake_retry_count - 5) && hdcp->version > HDCP2_VERSION_2_0) {
						hdcp->version = HDCP2_VERSION_2_0;
						ret = HDCP2_SetVersion(hdcp);
						tries = 0;
						HDCP2_Log("HDCP version changed to: %d.%d", hdcp->version / 10, hdcp->version % 10);
						continue;
					} else {
						HDCP2_Log("Timeout. Next message should be received by 100 ms");
					}
				} else {
					HDCP2_Log("FAILED receives AKE_Receiver_Info. %s", strerror(errno));
				}

				ret=HDCP2_NETWORK_ERROR;
				continue;
			} else if ((ret = check_ReceiveMsg_id(CMD_AKE_RECEIVER_INFO, rxbuffer)) < 0) {
				continue;
			}

			if ((ret = AKE_Receiver_Info_T(hdcp, rxbuffer)) < 0) {
				continue;
			}

			if (HDCP2_VERSION_2_0 + rxbuffer[3] < hdcp->version) {
				hdcp->version = HDCP2_VERSION_2_0 + rxbuffer[3];
				tries = 0;
				HDCP2_Log("HDCP version changed to: %d.%d", hdcp->version / 10, hdcp->version % 10);
			}

			if (hdcp->Tx_LC_Precompute && ((rxbuffer[5] & LOCALITY_PRECOMPUTE_SUPPORT) != 0)) {
				 HDCP2_Log("Tx LC precompute AND Rx LC precompute are enabled. So Locality Check is with LC Precompute");
			} else {
				HDCP2_Log("Tx LC precompute OR Rx LC precompute is disabled. So Locality Check is without LC Precompute");
			}
		 }

		/*
		 * 5. Send AKE_No_Stored_km_T / AKE_Stored_km
		 */
		if (hdcp->is_paired) {
			if ((ret = AKE_Stored_km_T(hdcp, payload)) < 0)
				continue;

			if ((write_sz=write(hdcp->sd, payload, ret)) != ret) {
				HDCP2_Log("FAILED sends AKE_Stored_km. %s", strerror(errno));
				HDCP2_Log("bytes written successfully=%d ,",write_sz);
				HDCP2_LogHex("ake_stored... data written is:",payload,write_sz );
				ret = HDCP2_NETWORK_ERROR;
				continue;
			}

			/* Make Time Out */
			if (setsockopt(hdcp->sd, SOL_SOCKET, SO_RCVTIMEO, &tv_time_200ms, sizeof(tv_time_200ms)) == -1) {
				HDCP2_Log("setsockopt failure. %s", strerror(errno));
				ret = HDCP2_NETWORK_ERROR;
				goto err;
			}
			//HDCP2_LogHex(">>>AKE_Stored_km", payload, netlen);
		} else {
			if ((ret = AKE_No_Stored_km_T(hdcp, payload)) < 0) {
				if (ret == HDCP2_ERR_DCP_VERIFICATION) {
					HDCP2_Log("Invalid DLP-LLC's signature on receiver's cert");
					goto err;
				}
				continue;
			}

			if ((write_sz=write(hdcp->sd, payload, ret)) != ret) {
				HDCP2_Log("FAILED sends AKE_No_Stored_km. %s", strerror(errno));
				HDCP2_Log("bytes written successfully=%d ,",write_sz);
				HDCP2_LogHex("ake_no_stored... data written is:",payload,write_sz );
				ret = HDCP2_NETWORK_ERROR;
				continue;
			}

			if (setsockopt(hdcp->sd, SOL_SOCKET, SO_RCVTIMEO, &tv_time_1sec, sizeof(tv_time_1sec)) == -1) {
				HDCP2_Log("setsockopt failure. %s", strerror(errno));
				ret = HDCP2_NETWORK_ERROR;
				goto err;
			}
		}

		/*
		 * 6. Receive AKE_Send_rrx
		 */
		if ((ret = read(hdcp->sd, rxbuffer, HDCP2_SIZE_AKE_SEND_RRX)) != HDCP2_SIZE_AKE_SEND_RRX) {
			HDCP2_Log("bytes read successfully=%d ",ret);
			HDCP2_LogHex("data:",rxbuffer, ret);
			if (errno == EAGAIN) {
				if (hdcp->is_paired)
					HDCP2_Log("Timeout. Next message should be received by 200ms");
				else
					HDCP2_Log("Timeout. Next message should be received by 1sec");
			} else {
				HDCP2_Log("FAILED receives AKE_Send_rrx. %s", strerror(errno));
			}

			continue;
		} else if ((ret = check_ReceiveMsg_id(CMD_AKE_SEND_RRX, rxbuffer)) < 0) {
			continue;
		}

		if ((ret = AKE_Send_rrx_T(hdcp, rxbuffer)) < 0)
			continue;

		/*
		 * 7. Receive AKE_Send_H_prime
		 */
		if ((ret = read(hdcp->sd, rxbuffer, HDCP2_SIZE_AKE_SEND_H_PRIME)) != HDCP2_SIZE_AKE_SEND_H_PRIME) {
			HDCP2_Log("bytes read successfully=%d ",ret);
			HDCP2_LogHex("data:",rxbuffer, ret);
			if (errno == EAGAIN) {
				if(hdcp->is_paired)
					HDCP2_Log("Timeout. Next message should be received by 200ms");
				else
					HDCP2_Log("Timeout. Next message should be received by 1sec");
			} else
				HDCP2_Log("FAILED receives AKE_Send_H_prime. %s", strerror(errno));

			continue;
		} else if ((ret = check_ReceiveMsg_id(CMD_AKE_SEND_H_PRIME, rxbuffer)) < 0) {
			continue;
		}

		ret = AKE_Send_H_prime_T(hdcp, rxbuffer);
		if (ret < 0) {
			if (ret == HDCP2_ERR_INVALID_H)
			{
				HDCP2_Log("Invalid H prime received, restart authentication.");
			}
			continue;
		}

		/*
		 * 8. Receive AKE_Send_Pairing_Info
		 */
		if (!hdcp->is_paired) {
			/* Make Time Out */
			if (setsockopt(hdcp->sd, SOL_SOCKET, SO_RCVTIMEO, &tv_time_200ms, sizeof(tv_time_200ms)) == -1) {
				HDCP2_Log("setsockopt failure. %s", strerror(errno));
				ret = HDCP2_NETWORK_ERROR;
				goto err;
			}

			if ((ret = read(hdcp->sd, rxbuffer, HDCP2_SIZE_AKE_SEND_PAIRING_INFO)) != HDCP2_SIZE_AKE_SEND_PAIRING_INFO) {
				HDCP2_Log("bytes read successfully=%d ",ret);
				HDCP2_LogHex("data:",rxbuffer, ret);
				if (errno == EAGAIN)
					HDCP2_Log("Timeout. Next message should be received by 200ms");
				else
					HDCP2_Log("FAILED receives AKE_Send_Pairing_Info. %s", strerror(errno));

				continue;
			} else if ((ret = check_ReceiveMsg_id(CMD_AKE_SEND_PAIRING_INFO, rxbuffer)) < 0) {
				continue;
			}

			/*
			 * 8.c AKE_Send_Pairing_Info_T
			 */
			if ((ret = AKE_Send_Pairing_Info_T(hdcp, rxbuffer)) < 0)
				continue;
		}

		/*
		 * Locality Check
		 */
		ret = HDCP2_ERR_TIME_LC;
		for (lctries = 0; lctries < hdcp->lc_retry_count; lctries++) {
			if (hdcp->sd == 0) {
				HDCP2_Log("Control path was closed");
				ret = HDCP2_NETWORK_ERROR;
				goto err;
			}

			if (lctries > 0) {
				if (setsockopt(hdcp->sd, SOL_SOCKET, SO_RCVTIMEO, &tv_time_1sec, sizeof(tv_time_1sec)) == -1) {
					HDCP2_Log("setsockopt failure. %s", strerror(errno));
					ret = HDCP2_NETWORK_ERROR;
					goto err;
				}

				if ((ret = read(hdcp->sd, rxbuffer, sizeof(rxbuffer))) > 0) {
					HDCP2_Log("Clear remained %d bytes.", ret);
					HDCP2_LogHex("data:",rxbuffer, ret);
				}
			}

			if (hdcp->Tx_LC_Precompute && hdcp->Rx_LC_Precompute && hdcp->version >= HDCP2_VERSION_2_1) {
				/*
				 * 9. Send LC_Init
				 */
				if ((ret = LC_Init_T(hdcp, payload)) < 0)
					continue;

				if ((write_sz=write(hdcp->sd, payload, ret)) != ret) {
					HDCP2_Log("FAILED sends LC_Init. %s", strerror(errno));
					HDCP2_Log("bytes written successfully=%d ,",write_sz);
					HDCP2_LogHex("LC_Init... data written is:",payload,write_sz );
					ret = HDCP2_NETWORK_ERROR;
					continue;
				}

				/*
				 * 9.a. Receive RTT_READY
				 */
				if ((ret = read(hdcp->sd, rxbuffer, HDCP2_SIZE_RTT_READY)) != HDCP2_SIZE_RTT_READY) {
					HDCP2_Log("FAILED receives RTT_Ready. %s", strerror(errno));
					HDCP2_Log("bytes read successfully=%d ",ret);
					HDCP2_LogHex("data:",rxbuffer, ret);
					ret = HDCP2_NETWORK_ERROR;
					continue;
				} else if ((ret = check_ReceiveMsg_id(CMD_RTT_READY, rxbuffer)) < 0) {
					continue;
				}

				if ((ret = RTT_Ready_T(hdcp, rxbuffer)) != HDCP2_OK) {
					HDCP2_Log("RTT Ready failure");
					continue;
				}

				/*
				 * 9.b Send RTT_Challenge
				 */
				if ((ret = RTT_Challenge_T(hdcp, payload)) < 0) {
					HDCP2_Log("RTT_Challenge_T");
					continue;
				}

				if ((write_sz=write(hdcp->sd, payload, ret)) != ret) {
					HDCP2_Log("FAILED sends RTT_Challenge. %s", strerror(errno));
					HDCP2_Log("bytes written successfully=%d ,",write_sz);
					HDCP2_LogHex("RTT_Challenge... data written is:",payload,write_sz );
					ret = HDCP2_NETWORK_ERROR;
					continue;
				}

				if (setsockopt(hdcp->sd, SOL_SOCKET, SO_RCVTIMEO, &tv_time_7ms, sizeof(tv_time_7ms)) == -1) {
					HDCP2_Log("setsockopt failure. %s", strerror(errno));
					ret = HDCP2_NETWORK_ERROR;
					goto err;
				}

				/*
				 * 10. Receive LC_Send_L_prime
				 */
				if ((ret = read(hdcp->sd, rxbuffer, HDCP2_SIZE_LC_SEND_L_PRIME_PC)) != HDCP2_SIZE_LC_SEND_L_PRIME_PC) {
					HDCP2_Log("bytes read successfully=%d ",ret);
					HDCP2_LogHex("data:",rxbuffer, ret);
					if (errno == EAGAIN)
						HDCP2_Log("Timeout. Next message should be received by 7ms");
					else
						HDCP2_Log("FAILED receives LC_Send_L_Prime. %s", strerror(errno));

					continue;
				} else if ((ret = check_ReceiveMsg_id(CMD_LC_SEND_L_PRIME, rxbuffer)) < 0) {
					continue;
				}

				/*
				 * 10.b LC_Send_L_prime
				 */
				if ((ret = LC_Send_L_prime_T(hdcp, rxbuffer)) != HDCP2_OK)
					continue;
				else
					break;
			} else {
				/*
				 * 9. Send LC_Init
				 */
				if ((ret = LC_Init_T(hdcp, payload)) < 0)
					continue;

				if ((write_sz=write(hdcp->sd, payload, ret)) != ret) {
					HDCP2_Log("FAILED sends LC_Init. %s", strerror(errno));
					HDCP2_Log("bytes written successfully=%d ,",write_sz);
					HDCP2_LogHex("LC_Init... data written is:",payload,write_sz );
					ret = HDCP2_NETWORK_ERROR;
					continue;
				}

				if (setsockopt(hdcp->sd, SOL_SOCKET, SO_RCVTIMEO, &tv_time_7ms, sizeof(tv_time_7ms)) == -1) {
					HDCP2_Log("setsockopt failure. %s", strerror(errno));
					ret = HDCP2_NETWORK_ERROR;
					goto err;
				}

				/*
				 * 10. Receive LC_Send_L_prime
				 */
				if ((ret = read(hdcp->sd, rxbuffer,HDCP2_SIZE_LC_SEND_L_PRIME)) != HDCP2_SIZE_LC_SEND_L_PRIME) {
					HDCP2_Log("bytes read successfully=%d ",ret);
					HDCP2_LogHex("data:",rxbuffer, ret);
					if (errno == EAGAIN)
						HDCP2_Log("Timeout. Next message should be received by 7ms");
					else
						HDCP2_Log("FAILED receives LC_Send_L_Prime. %s(%d)", strerror(errno), errno);

					ret=HDCP2_NETWORK_ERROR;
					continue;
				} else if ((ret = check_ReceiveMsg_id(CMD_LC_SEND_L_PRIME, rxbuffer)) < 0) {
					continue;
				}

				/*
				 * 10.b LC_Send_L_prime
				 */
				if ((ret = LC_Send_L_prime_T(hdcp, rxbuffer)) != HDCP2_OK)
					continue;
				else
					break;
			}
		}

		if (ret != HDCP2_OK)
			continue;

		/*
		 * Session Key Exchange
		 */
		/* 11. Send SKE_Send_Eks
		 */
		if ((ret = SKE_Send_Eks_T(hdcp, payload)) < 0)
			continue;

		if ((write_sz=write(hdcp->sd, payload, ret)) != ret) {
			HDCP2_Log("FAILED sends SKE_Send_Eks. %s", strerror(errno));
			HDCP2_Log("bytes written successfully=%d ,",write_sz);
			HDCP2_LogHex("SKE_Send_Eks... data written is:",payload,write_sz );
			ret = HDCP2_NETWORK_ERROR;
			continue;
		}

		/*
		 * Repeater Support
		 */
		//Tx-Repeater Support
		if (hdcp->REPEATER) {
			if (setsockopt(hdcp->sd, SOL_SOCKET, SO_RCVTIMEO, &tv_time_3sec, sizeof(tv_time_3sec)) == -1) {
				HDCP2_Log("setsockopt failure. %s", strerror(errno));
				ret = HDCP2_NETWORK_ERROR;
				goto err;
			}

			int ret1 = 0;
			/*
			 * 12. Receive  RepeaterAuth_Send_ReceiverId_List
			 */
			if (hdcp->version >= HDCP2_VERSION_2_1) {
				ret1 = read(hdcp->sd, rxbuffer,HDCP_SIZE_REPEATERAUTH_SEND_RECEIVERID_LIST21);

				if ((ret1%(RECEIVER_ID_SIZE) != 1) || (ret1 < (HDCP_FIXED_SIZE_REPEATERAUTH_SEND_RECEIVERID_LIST21 + RECEIVER_ID_SIZE))) {
					HDCP2_Log("FAILED receives RepeaterAuth_Send_ReceiverID_List21. %s", strerror(errno));
					HDCP2_Log("bytes read successfully=%d ",ret1);
					HDCP2_LogHex("data:",rxbuffer, ret1);
					ret = HDCP2_NETWORK_ERROR;
					continue;
				} else if ((ret = check_ReceiveMsg_id(CMD_REPEATERAUTH_SEND_RECEIVER_ID_LIST, rxbuffer)) < 0) {
					continue;
				}
			} else {
				ret1 = read(hdcp->sd, rxbuffer,HDCP_SIZE_REPEATERAUTH_SEND_RECEIVERID_LIST20);

				if ((ret1%(RECEIVER_ID_SIZE) != 2)||(ret1 < (HDCP_FIXED_SIZE_REPEATERAUTH_SEND_RECEIVERID_LIST20 + RECEIVER_ID_SIZE))) {
					HDCP2_Log("FAILED receives RepeaterAuth_Send_ReceiverID_List20. %s", strerror(errno));
					HDCP2_Log("bytes read successfully=%d ",ret1);
					HDCP2_LogHex("data:",rxbuffer, ret1);
					ret = HDCP2_NETWORK_ERROR;
					continue;
				} else if ((ret = check_ReceiveMsg_id(CMD_REPEATERAUTH_SEND_RECEIVER_ID_LIST, rxbuffer)) < 0) {
					continue;
				}
			}

			if (setsockopt(hdcp->sd, SOL_SOCKET, SO_RCVTIMEO, &tv_time_300ms, sizeof(tv_time_300ms)) == -1) {
				HDCP2_Log("setsockopt failure. %s", strerror(errno));
				ret = HDCP2_NETWORK_ERROR;
				goto err;
			}

			if ((ret = RepeaterAuth_Send_ReceiverId_List_T(hdcp, rxbuffer)) < 0)
				continue;

			// these changes required for HDCP 21 and HDCP 22
			if (hdcp->version >= HDCP2_VERSION_2_1) {
				/*
				 * 13. Send RepeaterAuth_Send_Ack
				 */
				if ((ret = RepeaterAuth_Send_Ack_T(hdcp, payload)) < 0)
					continue;

				if ((write_sz=write(hdcp->sd, payload, ret)) != ret) {
					HDCP2_Log("FAILED sends RepeaterAuth_Send_Ack. %s", strerror(errno));
					HDCP2_Log("bytes written successfully=%d ,",write_sz);
					HDCP2_LogHex("RepeaterAuth_Send_Ack... data written is:",payload,write_sz );
					ret = HDCP2_NETWORK_ERROR;
					continue;
				}

				if (setsockopt(hdcp->sd, SOL_SOCKET, SO_RCVTIMEO, &tv_time_1sec, sizeof(tv_time_1sec)) == -1) {
					HDCP2_Log("setsockopt failure. %s", strerror(errno));
					ret = HDCP2_NETWORK_ERROR;
					goto err;
				}

				/*
				 * 14. Receive Receiver_AuthStatus
				 */
				 if ((ret = read(hdcp->sd, rxbuffer, HDCP_SIZE_RECEIVERAUTH_STATUS))!= HDCP_SIZE_RECEIVERAUTH_STATUS) {
					HDCP2_Log("FAILED receives Receiver_AutStatus. %s", strerror(errno));
					HDCP2_Log("bytes read successfully=%d ",ret);
					HDCP2_LogHex("data:",rxbuffer, ret);
					ret = HDCP2_NETWORK_ERROR;
					continue;
				} else if ((ret = check_ReceiveMsg_id(CMD_RECEIVER_AUTHSTATUS, rxbuffer)) < 0) {
					continue;
				}

				if (setsockopt(hdcp->sd, SOL_SOCKET, SO_RCVTIMEO, &tv_time_300ms, sizeof(tv_time_300ms)) == -1) {
					HDCP2_Log("setsockopt failure. %s", strerror(errno));
					ret = HDCP2_NETWORK_ERROR;
					goto err;
				}

				ret = Receiver_AuthStatus_T(hdcp, rxbuffer);
				if (ret == HDCP2_ERR_REAUTH_REQ) {
					HDCP2_Log("Repeater Asks for Reauthentication\n ");
					continue;
				} else if (ret < 0) {
					continue;
				}
			}

			// downstream content management moved as a separate API HDCP2_DownstreamPropagation
			/* Default Downstream Content management Call */
			if (hdcp->version >= HDCP2_VERSION_2_1) {
				params.streamCtrj[0]=0x00;
				params.streamCtrj[1]=0x00;
				params.streamCtrj[2]=0x00;
				params.streamCtrj[3]=0x00;
				params.ContentStreamIDj[0]=0x10;
				params.ContentStreamIDj[1]=0x11;
				params.Type = 0x00;
				if ((ret = HDCP2_DownstreamPropagation(hdcp, &params, 1)) < 0)
					continue;
			}
		}

		// Success AKE
		ret = HDCP2_OK;
		break;
	}

	if (ret != HDCP2_OK)
		goto err;

	GetTimeStamp(&hdcp->time_ske_done);
	HDCP2_Log("Transmitter AKE time = %ld ms",
			EstimateTime(&hdcp->time_ake_start, &hdcp->time_ske_done));

#ifdef USE_TEEGRIS
	// This function is to prevent memory leak for blowfish 
	HDCP2_TZ_Release_Resmem();
#endif /* USE_TEEGRIS */

	HDCP2_Log("HDCP2 Connected");
	return HDCP2_OK;

err:
	if (ret != HDCP2_ERR_DCP_VERIFICATION)
		HDCP2_Log("Timeout...");
	
	if (hdcp->sd > 0) {
		HDCP2_Close_Socket(hdcp, "sd");
		usleep(500*1000);
	}

	return ret;
}

/**
 * @fn int HDCP2_DownstreamPropagation(HDCP2_Ctx *hdcp, Downstream_Params *params, int k)
 * @brief This function sends RepeaterAuth stream manage massage to transmitter, sets timeout for RepeaterAuth_Stream_Ready, receives RepeaterAuth stream ready  message and checks timeout for RepeaterAuth_Stream_Ready
 * @param hdcp - pointer to HDCP context.
 * @param params - pointer to structure Downstream_Params.
 * @param int k - maximum number of streams possible.
 * @return int - HDCP2_OK in case of success, else error code corresponding to the error.
 */
int HDCP2_DownstreamPropagation(HDCP2_Ctx *hdcp, Downstream_Params *params, int k)
{
	timestamp ts_s, ts_e;
	long duration = 0;
	int ret = 0;
	int write_sz=0;
	unsigned char rxbuffer[4096] = { 0 };

	if (!hdcp) {
		HDCP2_Log("HDCP2_DownstreamPropagation: Invalid input: %x", hdcp);
		return HDCP2_ERR_INVALID_INPUT;
	}
	if (HDCP2_IsClosed(hdcp)) {
		return HDCP2_ERR_CLOSED;
	}
	if (k > MAX_NUM_STEAM_K) {
		HDCP2_Log("HDCP2_DownstreamPropagation: Max stream exceeded: %d", k);
		return HDCP2_ERR_MAX_STREAM_EXCEEDED;
	}

	hdcp->no_of_streams[1] = k; //only LSB is taken since max allowed streams are 16.

	/*
	 * Send RepeaterAuth stream manage massage
	 */
	if (hdcp->version >= HDCP2_VERSION_2_1) {
		unsigned char payload[4096] = { 0 };
		if ((ret = RepeaterAuth_Stream_Manage_T(hdcp, payload, params)) < 0)
			goto err;
		if ((write_sz=write(hdcp->sd, payload, ret)) != ret) {
			HDCP2_Log("FAILED sends ReapeaterAuth. %s", strerror(errno));
			HDCP2_Log("bytes written successfully=%d ,",write_sz);
			HDCP2_LogHex("Sent ReapeaterAuth... data written is:",payload,write_sz );
			ret = HDCP2_NETWORK_ERROR;
			goto err;
		}
	}
	/*
	 * Set timeout for RepeaterAuth_Stream_Ready
	 */
	GetTimeStamp(&ts_s);

	/*
	 * Receive RepeaterAuth stream ready  massage
	 */
	if ((ret = read(hdcp->sd, rxbuffer, HDCP_SIZE_RECEIVERAUTH_READY))!= HDCP_SIZE_RECEIVERAUTH_READY)  {
		HDCP2_Log("FAILED receives ReceiverAuth. %s", strerror(errno));
		HDCP2_Log("bytes read successfully=%d ",ret);
		HDCP2_LogHex("data:",rxbuffer, ret);
		ret = HDCP2_NETWORK_ERROR;
		goto err;
	}

	/*
	 * Check timeout for RepeaterAuth_Stream_Ready
	 */
	GetTimeStamp(&ts_e);
	duration = EstimateTime(&ts_s, &ts_e);
	if (duration > HDCP2_VALID_TIME_100MS) {
		HDCP2_Log("Timeout: RepeaterAuth_Stream_Ready: %dms", duration);
		ret = HDCP2_ERR_TIME_SKE;
		goto err;
	}

	if ((ret = RepeaterAuth_Stream_Ready_T(hdcp, rxbuffer)) < 0)
		goto err;

	return HDCP2_OK;

	err:
	HDCP2_Log("HDCP2_DownstreamPropagation failed...");

	return ret;
}
