/*
 * SPDX-License-Identifier: Apache-2.0
 *
 * Copyright 2017 Cisco Systems, Inc.
 */


#include "CUnit.h"
#include "Basic.h"
#include "Automated.h"
//#include "CUnit/Console.h"

#include "nfapi_interface.h"
#include "nfapi_vnf_interface.h"
#include "nfapi.h"
#include <stdio.h>  // for printf
#include <pthread.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netinet/sctp.h>
#include <arpa/inet.h>
#include "debug.h"
#include <unistd.h>
#include <stdlib.h>

/* Test Suite setup and cleanup functions: */

int init_suite(void) { return 0; }
int clean_suite(void) { return 0; }

#define MAX_PACKED_MESSAGE_SIZE	8192

#define SFNSF2SFN(_sfnsf) ((_sfnsf) >> 4)
#define SFNSF2SF(_sfnsf) ((_sfnsf) & 0xF)

typedef struct
{
	uint8_t enabled;
	uint8_t started;
	uint16_t phy_id;

	int p7_rx_port;
	char* p7_rx_addr;

	struct sockaddr_in p7_rx_sockaddr;
	int p7_rx_sock;

	struct sockaddr_in p7_tx_sockaddr;
	int p7_tx_sock;
} pnf_test_config_phy_t;

typedef struct
{
	uint8_t enabled;
	
	char* vnf_p5_addr;
	int vnf_p5_port;

	struct sockaddr_in p5_tx_sockaddr;
	int p5_sock;

	int p7_rx_port_base;

	pnf_test_config_phy_t phys[4];

} pnf_test_config_t;

pnf_test_config_t pnf_test_config[5];

typedef struct
{
	uint8_t enabled;
	uint8_t state;
	uint16_t p5_idx;
	uint16_t phy_id;

	uint8_t vnf_idx;
	//struct sockaddr_in p7_rx_sockaddr;
	struct sockaddr_in p7_tx_sockaddr;

} vnf_test_config_phy_t;

typedef struct 
{
	uint8_t enabled;
	pthread_t thread;

	char* vnf_p7_addr;
	int vnf_p7_port;

	struct sockaddr_in p7_rx_sockaddr;

	nfapi_vnf_p7_config_t* config;

	uint8_t max_phys;
	uint8_t phy_count;
	//uint8_t phy_ids[4];
	
} vnf_test_config_vnf_t;

typedef struct  
{
	char* p5_addr;
	int p5_port;

	pthread_t thread;

	vnf_test_config_vnf_t vnfs[2];

	uint8_t phy_count;
	vnf_test_config_phy_t phys[5];

	nfapi_vnf_config_t* p5_vnf_config;

} vnf_test_config_t;

vnf_test_config_t vnf_test_config[5];

void reset_test_configs()
{
	memset(&pnf_test_config, 0, sizeof(pnf_test_config));
	memset(&vnf_test_config, 0, sizeof(vnf_test_config));
}

void pnf_create_p5_sock(pnf_test_config_t* config)
{
	config->p5_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP);
	
	config->p5_tx_sockaddr.sin_family = AF_INET;
	config->p5_tx_sockaddr.sin_port = htons(config->vnf_p5_port);
	config->p5_tx_sockaddr.sin_addr.s_addr = inet_addr(config->vnf_p5_addr);
}

void pnf_p5_connect(pnf_test_config_t* config)
{
	int connect_result = connect(config->p5_sock, (struct sockaddr *)&config->p5_tx_sockaddr, sizeof(config->p5_tx_sockaddr) );

	printf("connect_result %d %d\n", connect_result, errno);
}

pnf_test_config_phy_t* find_pnf_phy_config(pnf_test_config_t* config, uint16_t phy_id)
{
	int i = 0;
	for(i = 0; i < 4; ++i)
	{
		if(config->phys[i].phy_id == phy_id)
			return &(config->phys[i]);
	}

	return 0;
}

vnf_test_config_phy_t* find_vnf_phy_config(vnf_test_config_t* config, uint16_t phy_id)
{
	int i = 0;
	for(i = 0; i < 4; ++i)
	{
		if(config->phys[i].phy_id == phy_id)
			return &(config->phys[i]);
	}

	return 0;
}




/************* Test case functions ****************/


void vnf_test_start_no_config(void) 
{
	int result = nfapi_vnf_start(0);
	CU_ASSERT_EQUAL(result, -1);
}

void* vnf_test_start_thread(void* ptr)
{
	int result = nfapi_vnf_start((nfapi_vnf_config_t*)ptr);
	return (void*)(intptr_t)result;
}

int pnf_connection_indication_called = 0;
int pnf_connection_indication(nfapi_vnf_config_t* config, int p5_idx)
{
	printf("[VNF] pnf_connection_indication p5_idx:%d\n", p5_idx);
	pnf_connection_indication_called++;

	nfapi_pnf_param_request_t req;
	memset(&req, 0, sizeof(req));
	req.header.message_id = NFAPI_PNF_PARAM_REQUEST;
	req.header.message_length = 0;

	nfapi_vnf_pnf_param_req(config, p5_idx, &req);
	
	return 1;
}

int pnf_disconnect_indication(nfapi_vnf_config_t* config, int p5_idx)
{
	printf("[VNF] pnf_disconnect_indication p5_idx:%d\n", p5_idx);
	pnf_connection_indication_called++;

	vnf_test_config_t* test_config = (vnf_test_config_t*)(config->user_data);
	printf("[VNF] pnf_disconnect_indication user_data %p\n", config->user_data);

	int i = 0;
	for(i = 0; i < test_config->phy_count; ++i)
	{
		vnf_test_config_phy_t* phy = &test_config->phys[i];
		vnf_test_config_vnf_t* vnf = &test_config->vnfs[phy->p5_idx];

		// need to send stop request/response

		nfapi_vnf_p7_del_pnf((vnf->config), phy->phy_id);
		vnf->phy_count--;
	}


	for(i = 0; i < 2; ++i)
	{
		vnf_test_config_vnf_t* vnf = &test_config->vnfs[i];
	printf("[VNF] pnf_disconnect_indication phy_count:%d\n", vnf->phy_count);
		if(vnf->enabled == 1)
		{
			nfapi_vnf_p7_stop(vnf->config);
			//vnf->config->terminate = 1;

			int* result;
	printf("[VNF] waiting for vnf p7 thread to exit\n");
			pthread_join((vnf->thread), (void**)&result);
			CU_ASSERT_EQUAL(result, 0);

			vnf->enabled = 0;
		}
	}
	
	return 1;
}

int send_p5_message(int p5Sock, nfapi_p4_p5_message_header_t* msg, unsigned msg_size, struct sockaddr_in* addr, socklen_t addr_size)
{
	char buffer[256];
	int encoded_size = nfapi_p5_message_pack(msg, msg_size, buffer, sizeof(buffer), 0);
	int result = sendto(p5Sock, buffer, encoded_size, 0, (struct sockaddr*)addr, addr_size);
	(void)result;
	return 0;
}

int recv_p5_message(int p5Sock, nfapi_p4_p5_message_header_t* msg, unsigned msg_size)
{
	struct sockaddr_in addr2;
	socklen_t addr2len;
	char buffer2[256];
	int result2 = recvfrom(p5Sock, buffer2, sizeof(buffer2), 0, (struct sockaddr*)&addr2, &addr2len); 
		
	int unpack_result = nfapi_p5_message_unpack(buffer2, result2, msg, msg_size, 0); //, sizeof(resp));
	(void)unpack_result;
	return 0;
}

int send_pnf_param_response(pnf_test_config_t* config) //int p5Sock, struct sockaddr_in* addr, socklen_t addr_size)
{
	nfapi_pnf_param_response_t resp;
	memset(&resp, 0, sizeof(resp));
	resp.header.message_id = NFAPI_PNF_PARAM_RESPONSE;

	resp.header.message_length = 0;
	resp.error_code = NFAPI_MSG_OK;

	//send_p5_message(p5Sock, &resp, sizeof(resp), addr, addr_size);
	send_p5_message(config->p5_sock, &resp.header, sizeof(resp), &(config->p5_tx_sockaddr), sizeof(config->p5_tx_sockaddr));
	return 0;
}

int create_p7_rx_socket(const char* addr, int port)
{
	int fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

	struct sockaddr_in sock_addr;
	sock_addr.sin_family = AF_INET;
	sock_addr.sin_port = htons(port);

	if(addr == 0)
		sock_addr.sin_addr.s_addr = INADDR_ANY;
	else
		sock_addr.sin_addr.s_addr = inet_addr(addr);

	int bind_result = bind(fd, (struct sockaddr*)&sock_addr, sizeof(sock_addr));
	if(bind_result != 0)
	{
		printf("Failed to bind p7 rx socket %d(%d) to %s:%d\n", bind_result, errno, addr, port);
	}

	
	return fd;
}

int create_p7_tx_socket()
{
	int fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	return fd;
}

int send_param_response(pnf_test_config_t* config, uint16_t phy_id)
{
	nfapi_param_response_t resp;
	memset(&resp, 0, sizeof(resp));
	resp.header.message_id = NFAPI_PARAM_RESPONSE;
	resp.header.phy_id = phy_id;

	pnf_test_config_phy_t* phy = find_pnf_phy_config(config, phy_id);

	resp.error_code = NFAPI_MSG_OK;

	resp.nfapi_config.p7_pnf_port.tl.tag = NFAPI_NFAPI_P7_PNF_PORT_TAG;
	resp.nfapi_config.p7_pnf_port.value = phy->p7_rx_port;

	struct sockaddr_in pnf_p7_sockaddr;
	pnf_p7_sockaddr.sin_addr.s_addr = inet_addr(phy->p7_rx_addr);

	resp.nfapi_config.p7_pnf_address_ipv4.tl.tag = NFAPI_NFAPI_P7_PNF_ADDRESS_IPV4_TAG;
	memcpy(&resp.nfapi_config.p7_pnf_address_ipv4.address[0], &pnf_p7_sockaddr.sin_addr.s_addr, 4);

	send_p5_message(config->p5_sock, &resp.header, sizeof(resp), &(config->p5_tx_sockaddr), sizeof(config->p5_tx_sockaddr));
	return 0;
}

int receive_pnf_param_request(pnf_test_config_t* config)
{
	nfapi_pnf_param_request_t req;
	recv_p5_message(config->p5_sock, &req.header, sizeof(req));

	CU_ASSERT_EQUAL(req.header.message_id, NFAPI_PNF_PARAM_REQUEST);
	if(req.header.message_id == NFAPI_PNF_PARAM_REQUEST)
	{
		printf("[PNF] nfapi_pnf_param_request\n");
	}
	return 0;
}

int receive_param_request(pnf_test_config_t* config)
{
	nfapi_param_request_t req;
	recv_p5_message(config->p5_sock, &req.header, sizeof(req));

	CU_ASSERT_EQUAL(req.header.message_id, NFAPI_PARAM_REQUEST);
	if(req.header.message_id == NFAPI_PARAM_REQUEST)
	{
		printf("[PNF] nfapi_param_request phy_id:%d\n", req.header.phy_id);
	}
	else
	{
		printf("[PNF] ERROR, unexpected message %d phy_id:%d\n", req.header.message_id, req.header.phy_id);
	}
	return req.header.phy_id;
}

int send_pnf_config_response(pnf_test_config_t* config)
{
	nfapi_pnf_config_response_t resp;
	memset(&resp, 0, sizeof(resp));
	resp.header.message_id = NFAPI_PNF_CONFIG_RESPONSE;
	resp.header.message_length = 0;
	resp.error_code = NFAPI_MSG_OK;

	send_p5_message(config->p5_sock, &resp.header, sizeof(resp), &(config->p5_tx_sockaddr), sizeof(config->p5_tx_sockaddr));
	return 0;
}

int send_config_response(pnf_test_config_t* config, uint16_t phy_id)
{
	nfapi_config_response_t resp;
	memset(&resp, 0, sizeof(resp));
	resp.header.message_id = NFAPI_CONFIG_RESPONSE;
	resp.header.message_length = 0;
	resp.header.phy_id = phy_id;
	resp.error_code = NFAPI_MSG_OK;

	send_p5_message(config->p5_sock, &resp.header, sizeof(resp), &(config->p5_tx_sockaddr), sizeof(config->p5_tx_sockaddr));
	return 0;
}


int receive_pnf_config_request(pnf_test_config_t* config)
{
	nfapi_pnf_config_request_t req;
	recv_p5_message(config->p5_sock, &req.header, sizeof(req));

	CU_ASSERT_EQUAL(req.header.message_id, NFAPI_PNF_CONFIG_REQUEST);
	if(req.header.message_id == NFAPI_PNF_CONFIG_REQUEST)
	{
		printf("decoded nfapi_pnf_config_request\n");

		int i = 0;
		for(i = 0; i < req.pnf_phy_rf_config.number_phy_rf_config_info; ++i)
		{
			nfapi_phy_rf_config_info_t* info = &(req.pnf_phy_rf_config.phy_rf_config[i]);
			printf("adding pnf phy %d id %d\n",  i, info->phy_id);

			config->phys[i].enabled = 1;
			config->phys[i].phy_id = info->phy_id;
		}
	}
	return 0;
}

int receive_config_request(pnf_test_config_t* config)
{
	nfapi_config_request_t req;
	recv_p5_message(config->p5_sock, &req.header, sizeof(req));

	CU_ASSERT_EQUAL(req.header.message_id, NFAPI_CONFIG_REQUEST);
	if(req.header.message_id == NFAPI_CONFIG_REQUEST)
	{
		printf("[PNF] nfapi_config_request phy_id:%d\n", req.header.phy_id);

		pnf_test_config_phy_t* phy = find_pnf_phy_config(config, req.header.phy_id);

		// todo verify that this is the same as a vnf config?
		phy->p7_tx_sockaddr.sin_family = AF_INET;
		phy->p7_tx_sockaddr.sin_port = htons(req.nfapi_config.p7_vnf_port.value);
		memcpy(&phy->p7_tx_sockaddr.sin_addr.s_addr, &req.nfapi_config.p7_vnf_address_ipv4.address, 4);
		phy->p7_tx_sock = create_p7_tx_socket();


		printf("[PNF] vnf p7 address %s:%d\n", inet_ntoa(phy->p7_tx_sockaddr.sin_addr), ntohs(phy->p7_tx_sockaddr.sin_port));

		return req.header.phy_id;
	}

	return NFAPI_PHY_ID_NA;
}

int send_pnf_start_response(pnf_test_config_t* config)
{
	nfapi_pnf_start_response_t resp;
	memset(&resp, 0, sizeof(resp));
	resp.header.message_id = NFAPI_PNF_START_RESPONSE;
	resp.header.message_length = 0;
	resp.error_code = NFAPI_MSG_OK;

	send_p5_message(config->p5_sock, &resp.header, sizeof(resp), &(config->p5_tx_sockaddr), sizeof(config->p5_tx_sockaddr));
	return 0;
}

int send_start_response(pnf_test_config_t* config, uint16_t phy_id)
{
	nfapi_start_response_t resp;
	memset(&resp, 0, sizeof(resp));
	resp.header.message_id = NFAPI_START_RESPONSE;
	resp.header.message_length = 0;
	resp.header.phy_id  = phy_id;
	resp.error_code = NFAPI_MSG_OK;

	send_p5_message(config->p5_sock, &resp.header, sizeof(resp), &(config->p5_tx_sockaddr), sizeof(config->p5_tx_sockaddr));
	return 0;
}

int receive_pnf_start_request(pnf_test_config_t* config)
{
	nfapi_pnf_start_request_t req;
	recv_p5_message(config->p5_sock, &req.header, sizeof(req));

	CU_ASSERT_EQUAL(req.header.message_id, NFAPI_PNF_START_REQUEST);
	if(req.header.message_id == NFAPI_PNF_START_REQUEST)
	{
		printf("decoded nfapi_pnf_start_request\n");

		int phy_idx = 0;
		for(phy_idx = 0; phy_idx < 4; ++phy_idx)
		{
			if(config->phys[phy_idx].enabled)
			{
				pnf_test_config_phy_t* phy = &(config->phys[phy_idx]);
				phy->p7_rx_port = config->p7_rx_port_base + phy_idx;
				phy->p7_rx_addr = "127.0.0.1"; 
			}
		}
	}
	return 0;
}

int receive_start_request(pnf_test_config_t* config)
{
	nfapi_start_request_t req;
	recv_p5_message(config->p5_sock, &req.header, sizeof(req));

	CU_ASSERT_EQUAL(req.header.message_id, NFAPI_START_REQUEST);
	if(req.header.message_id == NFAPI_START_REQUEST)
	{
		printf("[PNF] nfapi_start_request\n");

		pnf_test_config_phy_t* phy = find_pnf_phy_config(config, req.header.phy_id);
		printf("[PNF] creating p7 rx socket %s:%d\n", phy->p7_rx_addr, phy->p7_rx_port);
		phy->p7_rx_sock = create_p7_rx_socket(phy->p7_rx_addr, phy->p7_rx_port);
		phy->started = 1;

	}

	return req.header.phy_id;
}

int send_pnf_stop_response(pnf_test_config_t* config, uint16_t phy_id)
{
	printf("pnf_stop_response\n");
	nfapi_pnf_stop_response_t resp;
	memset(&resp, 0, sizeof(resp));
	resp.header.message_id = NFAPI_PNF_STOP_RESPONSE;
	resp.header.message_length = 0;
	resp.error_code = NFAPI_MSG_OK;

	send_p5_message(config->p5_sock, &resp.header, sizeof(resp), &(config->p5_tx_sockaddr), sizeof(config->p5_tx_sockaddr));
	return 0;
}

int receive_pnf_stop_request(pnf_test_config_t* config)
{
	printf("pnf_stop_request\n");
	nfapi_pnf_stop_request_t req;
	recv_p5_message(config->p5_sock, &req.header, sizeof(req));

	CU_ASSERT_EQUAL(req.header.message_id, NFAPI_PNF_STOP_REQUEST);
	if(req.header.message_id == NFAPI_PNF_STOP_REQUEST)
	{
		printf("decoded nfapi_pnf_stop_request\n");
	}
	return 0;
}
int pnf_param_response(nfapi_vnf_config_t* config, int p5_idx,nfapi_pnf_param_response_t* response)
{
	printf("[VNF] pnf_param_response p5_idx:%d\n", p5_idx);
	CU_ASSERT_EQUAL(response->error_code, NFAPI_MSG_OK);

	uint16_t phy_id;
	nfapi_vnf_allocate_phy(config, p5_idx, &phy_id);

	nfapi_pnf_config_request_t req;
	memset(&req, 0, sizeof(req));
	req.header.message_id = NFAPI_PNF_CONFIG_REQUEST;
	req.header.message_length = 0;
	req.header.phy_id = NFAPI_PHY_ID_NA;

	req.pnf_phy_rf_config.tl.tag = NFAPI_PNF_PHY_RF_TAG;
	req.pnf_phy_rf_config.number_phy_rf_config_info = 2;
	nfapi_vnf_allocate_phy(config, p5_idx, &req.pnf_phy_rf_config.phy_rf_config[0].phy_id);
	nfapi_vnf_allocate_phy(config, p5_idx, &req.pnf_phy_rf_config.phy_rf_config[1].phy_id);
	
	uint16_t i = vnf_test_config[0].phy_count;

	vnf_test_config[0].phys[i].enabled = 1;
	vnf_test_config[0].phys[i].p5_idx = p5_idx;
	vnf_test_config[0].phys[i].phy_id = req.pnf_phy_rf_config.phy_rf_config[0].phy_id;

	i++;

	vnf_test_config[0].phys[i].enabled = 1;
	vnf_test_config[0].phys[i].p5_idx = p5_idx;
	vnf_test_config[0].phys[i].phy_id = req.pnf_phy_rf_config.phy_rf_config[1].phy_id;

	i++;
	
	vnf_test_config[0].phy_count = i;

	nfapi_vnf_pnf_config_req(config, p5_idx, &req);

	return 0;
}

int pnf_config_response(nfapi_vnf_config_t* config, int p5_idx, nfapi_pnf_config_response_t* response)
{
	printf("pnf_config_response\n");
	CU_ASSERT_EQUAL(response->error_code, NFAPI_MSG_OK);

	nfapi_pnf_start_request_t req;
	memset(&req, 0, sizeof(req));
	req.header.message_id = NFAPI_PNF_START_REQUEST;
	req.header.message_length = 0;
	req.header.phy_id = response->header.phy_id;

	nfapi_vnf_pnf_start_req(config, p5_idx, &req);

	return 0;
}


void* vnf_test_start_p7_thread(void* ptr)
{
	int result = nfapi_vnf_p7_start((nfapi_vnf_p7_config_t*)ptr);
	(void)result;
	return 0;
}

int test_subframe_indication(nfapi_vnf_p7_config_t* config, uint16_t phy_id, uint16_t sfn_sf)
{
	//printf("[VNF:%d] (%d:%d) SUBFRAME_IND\n", phy_id, SFNSF2SFN(sfn_sf), SFNSF2SF(sfn_sf));

	nfapi_dl_config_request_t dl_config_req;
	memset(&dl_config_req, 0, sizeof(dl_config_req));
	dl_config_req.header.message_id = NFAPI_DL_CONFIG_REQUEST;
	dl_config_req.header.phy_id = phy_id;
	dl_config_req.sfn_sf = sfn_sf;
	nfapi_vnf_p7_dl_config_req(config, &dl_config_req);
	
	nfapi_ul_config_request_t ul_config_req;
	memset(&ul_config_req, 0, sizeof(ul_config_req));
	ul_config_req.header.message_id = NFAPI_UL_CONFIG_REQUEST;
	ul_config_req.header.phy_id = phy_id;
	ul_config_req.sfn_sf = sfn_sf;
	nfapi_vnf_p7_ul_config_req(config, &ul_config_req);

	nfapi_hi_dci0_request_t hi_dci0_req;
	memset(&hi_dci0_req, 0, sizeof(hi_dci0_req));
	hi_dci0_req.header.message_id = NFAPI_HI_DCI0_REQUEST;
	hi_dci0_req.header.phy_id = phy_id;
	hi_dci0_req.sfn_sf = sfn_sf;
	nfapi_vnf_p7_hi_dci0_req(config, &hi_dci0_req);


	nfapi_tx_request_t tx_req;
	memset(&tx_req, 0, sizeof(tx_req));
	tx_req.header.message_id = NFAPI_TX_REQUEST;
	tx_req.header.phy_id = phy_id;
	tx_req.sfn_sf = sfn_sf;
	nfapi_vnf_p7_tx_req(config, &tx_req);
	return 0;
}

int test_harq_indication(nfapi_vnf_p7_config_t* config, nfapi_harq_indication_t* ind)
{
	//printf("[VNF:%d] (%d:%d) HARQ_IND\n", ind->header.phy_id, SFNSF2SFN(ind->sfn_sf), SFNSF2SF(ind->sfn_sf));
	return 0;
}

int test_nb_harq_indication(nfapi_vnf_p7_config_t* config, nfapi_nb_harq_indication_t* ind)
{
	//printf("[VNF:%d] (%d:%d) HARQ_IND\n", ind->header.phy_id, SFNSF2SFN(ind->sfn_sf), SFNSF2SF(ind->sfn_sf));
	return 0;
}


int test_crc_indication(nfapi_vnf_p7_config_t* config, nfapi_crc_indication_t* ind)
{
	//printf("[VNF:%d] (%d:%d) CRC_IND\n", ind->header.phy_id, SFNSF2SFN(ind->sfn_sf), SFNSF2SF(ind->sfn_sf));
	return 0;
}
int test_rx_indication(nfapi_vnf_p7_config_t* config, nfapi_rx_indication_t* ind)
{
	//printf("[VNF:%d] (%d:%d) RX_IND\n", ind->header.phy_id, SFNSF2SFN(ind->sfn_sf), SFNSF2SF(ind->sfn_sf));
	return 0;
}
int test_rach_indication(nfapi_vnf_p7_config_t* config, nfapi_rach_indication_t* ind)
{
	//printf("[VNF:%d] (%d:%d) RACH_IND\n", ind->header.phy_id, SFNSF2SFN(ind->sfn_sf), SFNSF2SF(ind->sfn_sf));
	return 0;
}
int test_nrach_indication(nfapi_vnf_p7_config_t* config, nfapi_nrach_indication_t* ind)
{
	//printf("[VNF:%d] (%d:%d) RACH_IND\n", ind->header.phy_id, SFNSF2SFN(ind->sfn_sf), SFNSF2SF(ind->sfn_sf));
	return 0;
}
int test_srs_indication(nfapi_vnf_p7_config_t* config, nfapi_srs_indication_t* ind)
{
	//printf("[VNF:%d] (%d:%d) SRS_IND\n", ind->header.phy_id, SFNSF2SFN(ind->sfn_sf), SFNSF2SF(ind->sfn_sf));
	return 0;
}
int test_sr_indication(nfapi_vnf_p7_config_t* config, nfapi_sr_indication_t* ind)
{
	//printf("[VNF:%d] (%d:%d) SR_IND\n", ind->header.phy_id, SFNSF2SFN(ind->sfn_sf), SFNSF2SF(ind->sfn_sf));
	return 0;
}
int test_cqi_indication(nfapi_vnf_p7_config_t* config, nfapi_cqi_indication_t* ind)
{
	//printf("[VNF:%d] (%d:%d) CQI_IND\n", ind->header.phy_id, SFNSF2SFN(ind->sfn_sf), SFNSF2SF(ind->sfn_sf));
	return 0;
}

int pnf_start_response(nfapi_vnf_config_t* config, int p5_idx, nfapi_pnf_start_response_t* response)
{
	printf("[VNF] pnf_start_response p5_idx:%d\n", p5_idx);
	CU_ASSERT_EQUAL(response->error_code, NFAPI_MSG_OK);

	return 0;
}

int pnf_stop_response(nfapi_vnf_config_t* config, int p5_idx, nfapi_pnf_stop_response_t* response)
{
	printf("pnf_stop_response\n");
	CU_ASSERT_EQUAL(response->error_code, NFAPI_MSG_OK);

	nfapi_vnf_p7_stop(vnf_test_config[0].vnfs[0].config);
	return 0;
}

int param_response(nfapi_vnf_config_t* config, int p5_idx, nfapi_param_response_t* response)
{
	printf("[VNF] param_response p5_idx:%d phy_id:%d\n", p5_idx, response->header.phy_id);
	CU_ASSERT_EQUAL(response->error_code, NFAPI_MSG_OK);

	struct sockaddr_in addr;
	memcpy(&addr.sin_addr.s_addr, &response->nfapi_config.p7_pnf_address_ipv4.address, 4);
	printf("[VNF] param_response pnf p7 %s:%d\n", inet_ntoa(addr.sin_addr), response->nfapi_config.p7_pnf_port.value);

	// find the vnf phy configuration
	vnf_test_config_phy_t* phy = find_vnf_phy_config(&(vnf_test_config[0]), response->header.phy_id);
	
	// save the pnf p7 connection information
	phy->p7_tx_sockaddr.sin_port = response->nfapi_config.p7_pnf_port.value;
	memcpy(&phy->p7_tx_sockaddr.sin_addr.s_addr, &response->nfapi_config.p7_pnf_address_ipv4.address, 4);

	nfapi_config_request_t req;
	memset(&req, 0, sizeof(req));
	req.header.message_id = NFAPI_CONFIG_REQUEST;
	req.header.phy_id = response->header.phy_id;

	vnf_test_config_vnf_t* vnf = &(vnf_test_config[0].vnfs[phy->vnf_idx]);
	req.nfapi_config.p7_vnf_address_ipv4.tl.tag = NFAPI_NFAPI_P7_VNF_ADDRESS_IPV4_TAG;
	memcpy(&req.nfapi_config.p7_vnf_address_ipv4.address, &vnf->p7_rx_sockaddr.sin_addr.s_addr, 4);

	req.nfapi_config.p7_vnf_port.tl.tag = NFAPI_NFAPI_P7_VNF_PORT_TAG;
	req.nfapi_config.p7_vnf_port.value = vnf->p7_rx_sockaddr.sin_port;

	nfapi_vnf_config_req(config, p5_idx, &req);
	return 0;
}

int config_response(nfapi_vnf_config_t* config, int p5_idx, nfapi_config_response_t* response)
{
	printf("[VNF] config_response p5_idx:%d phy_id:%d\n", p5_idx, response->header.phy_id);
	return 0;
	CU_ASSERT_EQUAL(response->error_code, NFAPI_MSG_OK);

}

int send_vnf_start_req(vnf_test_config_t* config, vnf_test_config_phy_t* phy)
{
	nfapi_start_request_t req;
	memset(&req, 0, sizeof(req));
	req.header.message_id = NFAPI_START_REQUEST;
	req.header.phy_id = phy->phy_id;

	nfapi_vnf_start_req(config->p5_vnf_config, phy->p5_idx, &req);
	return 0;
}

int start_response(nfapi_vnf_config_t* config, int p5_idx, nfapi_start_response_t* response)
{
	printf("[VNF] start_response p5_idx:%d phy_id:%d\n", p5_idx, response->header.phy_id);
	CU_ASSERT_EQUAL(response->error_code, NFAPI_MSG_OK);

	sleep(1);
	
	vnf_test_config_phy_t* phy = find_vnf_phy_config(&(vnf_test_config[0]), response->header.phy_id);

	char* addr = inet_ntoa(phy->p7_tx_sockaddr.sin_addr);
	int port = phy->p7_tx_sockaddr.sin_port;

	vnf_test_config_vnf_t* vnf = &(vnf_test_config[0].vnfs[phy->vnf_idx]);

	nfapi_vnf_p7_add_pnf(vnf->config, addr, port, response->header.phy_id, 0);
	return 0;
}

int stop_response(nfapi_vnf_config_t* config, int p5_idx, nfapi_stop_response_t* response)
{
	printf("stop_response p5_idx:%d phy_id:%d\n", p5_idx, response->header.phy_id);
	CU_ASSERT_EQUAL(response->error_code, NFAPI_MSG_OK);
	return 0;
}

void  start_vnf_p5(vnf_test_config_t* test_config)
{
	test_config->p5_vnf_config = nfapi_vnf_config_create();

	test_config->p5_vnf_config->vnf_p5_port = test_config->p5_port;
	test_config->p5_vnf_config->vnf_ipv4 = 1;
	test_config->p5_vnf_config->pnf_connection_indication = &pnf_connection_indication;
	test_config->p5_vnf_config->pnf_disconnect_indication = &pnf_disconnect_indication;
	test_config->p5_vnf_config->pnf_param_resp = &pnf_param_response;
	test_config->p5_vnf_config->pnf_config_resp = &pnf_config_response;
	test_config->p5_vnf_config->pnf_start_resp = &pnf_start_response;
	test_config->p5_vnf_config->pnf_stop_resp = &pnf_stop_response;
	test_config->p5_vnf_config->param_resp = &param_response;
	test_config->p5_vnf_config->config_resp = &config_response;
	test_config->p5_vnf_config->start_resp = &start_response;
	test_config->p5_vnf_config->stop_resp = &stop_response;

	test_config->p5_vnf_config->user_data = test_config;

	pthread_create(&test_config->thread, NULL, &vnf_test_start_thread, test_config->p5_vnf_config);

	sleep(1);

}

void start_vnf_p7(vnf_test_config_vnf_t* vnf)
{
	
	// todo : select which vnf to use for these phy's
	vnf->enabled = 1;

	vnf->p7_rx_sockaddr.sin_addr.s_addr = inet_addr(vnf->vnf_p7_addr);
	vnf->p7_rx_sockaddr.sin_port = vnf->vnf_p7_port;


	vnf->config = nfapi_vnf_p7_config_create();
	vnf->config->checksum_enabled = 0;
	vnf->config->port = vnf->vnf_p7_port;
	vnf->config->subframe_indication = &test_subframe_indication;
	vnf->config->harq_indication = &test_harq_indication;
	vnf->config->crc_indication = &test_crc_indication;
	vnf->config->rx_indication = &test_rx_indication;
	vnf->config->rach_indication = &test_rach_indication;
	vnf->config->srs_indication = &test_srs_indication;
	vnf->config->sr_indication = &test_sr_indication;
	vnf->config->cqi_indication = &test_cqi_indication;
	
	vnf->config->nb_harq_indication = &test_nb_harq_indication;
	vnf->config->nrach_indication = &test_nrach_indication;
	

	vnf->config->segment_size = 1400;
	vnf->config->max_num_segments = 6;

	pthread_create(&(vnf->thread), NULL, &vnf_test_start_p7_thread, vnf->config);

	vnf->enabled = 1;

	//vnf->phy_count++;

	sleep(1);
}

void send_p7_segmented_msg(int sock, char* msg, int len, int segment_size, struct sockaddr* addr,  socklen_t addr_len)
{
	static uint8_t sequence_num = 0;
	if(len < segment_size)
	{
		msg[7] = sequence_num;
		sendto(sock, msg, len, 0, addr, addr_len);
	}
	else
	{
		int msg_body_len = len - 12 ; 
		int seg_body_len = segment_size - 12 ; 
		int segments = (msg_body_len / (seg_body_len)) + ((msg_body_len % seg_body_len) ? 1 : 0); 

		//printf("sending segmented message len:%d seg_size:%d count:%d\n", len, segment_size, segments);

		int segment = 0;
		int offset = 12;
		for(segment = 0; segment < segments; ++segment)
		{
			uint8_t last = 0;
			uint16_t size = segment_size - 12;
			if(segment + 1 == segments)
			{
				last = 1;
				size = (msg_body_len) - (seg_body_len * segment);
			}

			char buffer[segment_size];

			memcpy(&buffer[0], msg, 12);
			buffer[6] = ((!last) << 7) + segment;
			buffer[7] = sequence_num;

			// msg length
			uint8_t* p = (uint8_t*)&buffer[4];
			push16(size + 12, &p, (uint8_t*)&buffer[size + 12]);

			memcpy(&buffer[12], msg + offset, size);
			offset += size;
		
			sendto(sock, &buffer[0], size + 12, 0, addr, addr_len);

		}


	}

	sequence_num++;
}

void start_phy(vnf_test_config_phy_t* phy)
{

	nfapi_param_request_t req;
	memset(&req, 0, sizeof(req));
	req.header.message_id = NFAPI_PARAM_REQUEST;
	req.header.phy_id = phy->phy_id;

	nfapi_vnf_config_t* p5_vnf_config = (vnf_test_config[0].p5_vnf_config);
	

	nfapi_vnf_param_req(p5_vnf_config, phy->p5_idx, &req);

}


void vnf_test_start_connect(void)
{
	reset_test_configs();

	int segment_size = 256;

	vnf_test_config[0].p5_addr = "127.0.0.1";
	vnf_test_config[0].p5_port = 4242;

	vnf_test_config[0].vnfs[0].enabled = 0;
	vnf_test_config[0].vnfs[0].vnf_p7_addr = "127.0.0.1";
	vnf_test_config[0].vnfs[0].vnf_p7_port = 7878;
	vnf_test_config[0].vnfs[0].max_phys = 1;

	vnf_test_config[0].vnfs[1].enabled = 0;
	vnf_test_config[0].vnfs[1].vnf_p7_addr = "127.0.0.1";
	vnf_test_config[0].vnfs[1].vnf_p7_port = 7879;
	vnf_test_config[0].vnfs[1].max_phys = 1;


	// this is the action that p9 would take to set the vnf p5 address/port
	pnf_test_config[0].enabled = 1;
	pnf_test_config[0].vnf_p5_port = vnf_test_config[0].p5_port;
	pnf_test_config[0].vnf_p5_addr = vnf_test_config[0].p5_addr;
	pnf_test_config[0].p7_rx_port_base = 8000;

	pnf_test_config[1].enabled = 1;
	pnf_test_config[1].vnf_p5_port = vnf_test_config[0].p5_port;
	pnf_test_config[1].vnf_p5_addr = vnf_test_config[0].p5_addr;
	pnf_test_config[1].p7_rx_port_base = 8100;

	pnf_connection_indication_called = 0;

	start_vnf_p5(&vnf_test_config[0]);


	pnf_create_p5_sock(&pnf_test_config[0]);
	pnf_p5_connect(&pnf_test_config[0]);

	receive_pnf_param_request(&pnf_test_config[0]);
	send_pnf_param_response(&pnf_test_config[0]);

	receive_pnf_config_request(&pnf_test_config[0]);
	send_pnf_config_response(&pnf_test_config[0]);

	receive_pnf_start_request(&pnf_test_config[0]);
	send_pnf_start_response(&pnf_test_config[0]);

	pnf_create_p5_sock(&pnf_test_config[1]);

	pnf_p5_connect(&pnf_test_config[1]);

	receive_pnf_param_request(&pnf_test_config[1]);
	send_pnf_param_response(&pnf_test_config[1]);

	receive_pnf_config_request(&pnf_test_config[1]);
	send_pnf_config_response(&pnf_test_config[1]);

	receive_pnf_start_request(&pnf_test_config[1]);
	send_pnf_start_response(&pnf_test_config[1]);

	// start the vnf_p7 thread and assign by phys to that instance

	start_vnf_p7(&vnf_test_config[0].vnfs[0]);
	//start_vnf_p7(&vnf_test_config[0].vnfs[1]);
	
	printf("---- configuring phy %d -----\n", vnf_test_config[0].phys[0].phy_id);
	vnf_test_config[0].phys[0].vnf_idx = 0;
	start_phy(&vnf_test_config[0].phys[0]);

	int phy_id = receive_param_request(&pnf_test_config[0]);
	send_param_response(&pnf_test_config[0], phy_id); 

	phy_id = receive_config_request(&pnf_test_config[0]);
	send_config_response(&pnf_test_config[0], phy_id);

	printf("---- configuring phy %d -----\n", vnf_test_config[0].phys[2].phy_id);
	vnf_test_config[0].phys[2].vnf_idx = 0; 
	start_phy(&vnf_test_config[0].phys[2]);

	phy_id = receive_param_request(&pnf_test_config[1]);
	send_param_response(&pnf_test_config[1], phy_id); 

	phy_id = receive_config_request(&pnf_test_config[1]);
	send_config_response(&pnf_test_config[1], phy_id);

	printf("---- configuring phy %d -----\n", vnf_test_config[0].phys[1].phy_id);

	vnf_test_config[0].phys[1].vnf_idx = 0;
	start_phy(&vnf_test_config[0].phys[1]);

	phy_id = receive_param_request(&pnf_test_config[0]);
	send_param_response(&pnf_test_config[0], phy_id); 

	phy_id = receive_config_request(&pnf_test_config[0]);
	send_config_response(&pnf_test_config[0], phy_id);

	//-------------
	printf("---- starting phy %d -----\n", vnf_test_config[0].phys[0].phy_id);

	send_vnf_start_req(&vnf_test_config[0], &vnf_test_config[0].phys[0]);
	phy_id = receive_start_request(&pnf_test_config[0]);
	send_start_response(&pnf_test_config[0], phy_id);
/*
	printf("---- starting phy %d -----\n", vnf_test_config[0].phys[2].phy_id);

	send_vnf_start_req(&vnf_test_config[0], &vnf_test_config[0].phys[2]);
	phy_id = receive_start_request(&pnf_test_config[1]);
	send_start_response(&pnf_test_config[1], phy_id);

	printf("---- starting phy %d -----\n", vnf_test_config[0].phys[1].phy_id);

	send_vnf_start_req(&vnf_test_config[0], &vnf_test_config[0].phys[1]);
	phy_id = receive_start_request(&pnf_test_config[0]);
	send_start_response(&pnf_test_config[0], phy_id);
*/
	//close(pnf_test_config[0].p5_sock);

	char buffer[2048];
	int buffer_size = sizeof(buffer);

	
	fd_set rdfs;
	int exit = 0;
	while(exit != 1)
	{
		int pnf_idx, phy_idx; 
		int max_fd = 0;
		FD_ZERO(&rdfs);

		for(pnf_idx = 0; pnf_idx < 4; ++pnf_idx)
		{
			if(pnf_test_config[pnf_idx].enabled)
			{
				FD_SET(pnf_test_config[pnf_idx].p5_sock, &rdfs);
				if(pnf_test_config[pnf_idx].p5_sock > max_fd)
					max_fd = pnf_test_config[pnf_idx].p5_sock;
			}

			for(phy_idx = 0; phy_idx < 4; ++phy_idx)
			{
				pnf_test_config_phy_t* phy_info = &(pnf_test_config[pnf_idx].phys[phy_idx]);

				if(phy_info->started == 1 && phy_info->enabled == 1)
				{
					//////////////////printf("adding %d/%d %d %d\n", pnf_idx, phy_idx, phy_info->phy_id, phy_info->p7_rx_sock);
					FD_SET(phy_info->p7_rx_sock, &rdfs);
					if(phy_info->p7_rx_sock > max_fd)
						max_fd = phy_info->p7_rx_sock;
				}
			}
		}

		
		// changed to select,
		int select_result = select(max_fd + 1, &rdfs, NULL, NULL, NULL);
		(void)select_result;

		for(pnf_idx = 0; pnf_idx < 4; ++pnf_idx)
		{
			if(pnf_test_config[pnf_idx].enabled)
			{
				//pnf_test_config_t* pnf_config = &(pnf_test_config[pnf_idx]);
			}

			for(phy_idx = 0; phy_idx < 4; ++phy_idx)
			{
				pnf_test_config_phy_t* phy_info = &(pnf_test_config[pnf_idx].phys[phy_idx]);

				if(phy_info->enabled)
				{
					if(FD_ISSET(phy_info->p7_rx_sock, &rdfs))
					{
						//struct sockaddr_in recv_addr;
						//memset(&recv_addr, 0, sizeof(recv_addr));
						//socklen_t recv_addr_size;
						int len = recvfrom(phy_info->p7_rx_sock, &buffer[0], buffer_size, 0, 0, 0);// (struct sockaddr*)&recv_addr, &recv_addr_size);

						if(len == -1)
						{
							printf("recvfrom %d failed %d\n", phy_info->p7_rx_sock, errno);
							continue;
						}

						nfapi_p7_message_header_t header;
						nfapi_p7_message_header_unpack(buffer, len, &header, sizeof(header), 0);

						switch(header.message_id)
						{
							case NFAPI_DL_NODE_SYNC:
								{
									nfapi_dl_node_sync_t msg;
									nfapi_p7_message_unpack(buffer, len, &msg, sizeof(msg), 0);
									printf("[PNF:%d] NFAPI_DL_NODE_SYNC t1:%d\n", msg.header.phy_id, msg.t1);

									nfapi_ul_node_sync_t resp;
									memset(&resp, 0, sizeof(resp));
									resp.header.message_id = NFAPI_UL_NODE_SYNC;
									resp.header.phy_id = msg.header.phy_id;
									resp.t1 = msg.t1;
									usleep(50);
									resp.t2 = msg.t1 + 50; // 50 us rx latency
									resp.t3 = msg.t1 + 10; // 10 us pnf proc

									usleep(50);

									len = nfapi_p7_message_pack(&resp, buffer, buffer_size, 0);
									// send ul node sycn
									send_p7_segmented_msg(phy_info->p7_tx_sock, &buffer[0], len, segment_size, (struct sockaddr*)&phy_info->p7_tx_sockaddr, sizeof(phy_info->p7_tx_sockaddr));
								}
								break;
							case NFAPI_DL_CONFIG_REQUEST:
								{
									nfapi_dl_config_request_t msg;
									nfapi_p7_message_unpack(buffer, len, &msg, sizeof(msg), 0);
									//printf("[PNF:%d] (%d/%d) NFAPI_DL_CONFIG_REQUEST\n", header.phy_id, SFNSF2SFN(msg.sfn_sf), SFNSF2SF(msg.sfn_sf));

									if(SFNSF2SFN(msg.sfn_sf) == 500)
										exit = 1;
									// simulate the uplink messages
									
									nfapi_harq_indication_t harq_ind;
									memset(&harq_ind, 0, sizeof(harq_ind));
									harq_ind.header.message_id = NFAPI_HARQ_INDICATION;
									harq_ind.header.phy_id = msg.header.phy_id;
									harq_ind.sfn_sf = msg.sfn_sf;
									harq_ind.harq_indication_body.tl.tag = NFAPI_HARQ_INDICATION_BODY_TAG;
									harq_ind.harq_indication_body.number_of_harqs = 2;

									harq_ind.harq_indication_body.harq_pdu_list = (nfapi_harq_indication_pdu_t*)(malloc(sizeof(nfapi_harq_indication_pdu_t) * harq_ind.harq_indication_body.number_of_harqs));

									int i = 0;
									for(i = 0; i < harq_ind.harq_indication_body.number_of_harqs; ++i)
									{
										harq_ind.harq_indication_body.harq_pdu_list[i].rx_ue_information.tl.tag = NFAPI_RX_UE_INFORMATION_TAG;
									}
									len = nfapi_p7_message_pack(&harq_ind, buffer, buffer_size, 0);
									//sendto(phy_info->p7_tx_sock, &buffer[0], len, 0, (struct sockaddr*)&phy_info->p7_tx_sockaddr, sizeof(phy_info->p7_tx_sockaddr));
									send_p7_segmented_msg(phy_info->p7_tx_sock, &buffer[0], len, segment_size, (struct sockaddr*)&phy_info->p7_tx_sockaddr, sizeof(phy_info->p7_tx_sockaddr));
									


									//sendto(phy_info->p7_tx_sock, &buffer[0], len, 0, (struct sockaddr*)&phy_info->p7_tx_sockaddr, sizeof(phy_info->p7_tx_sockaddr));

									free(harq_ind.harq_indication_body.harq_pdu_list);

									nfapi_crc_indication_t crc_ind;
									memset(&crc_ind, 0, sizeof(crc_ind));
									crc_ind.header.message_id = NFAPI_CRC_INDICATION;
									crc_ind.header.phy_id = msg.header.phy_id;
									crc_ind.sfn_sf = msg.sfn_sf;
									len = nfapi_p7_message_pack(&crc_ind, buffer, buffer_size, 0);
									send_p7_segmented_msg(phy_info->p7_tx_sock, &buffer[0], len, segment_size, (struct sockaddr*)&phy_info->p7_tx_sockaddr, sizeof(phy_info->p7_tx_sockaddr));

									nfapi_sr_indication_t sr_ind;
									memset(&sr_ind, 0, sizeof(sr_ind));
									sr_ind.header.message_id = NFAPI_RX_SR_INDICATION;
									sr_ind.header.phy_id = msg.header.phy_id;
									sr_ind.sfn_sf = msg.sfn_sf;
									len = nfapi_p7_message_pack(&sr_ind, buffer, buffer_size, 0);
									send_p7_segmented_msg(phy_info->p7_tx_sock, &buffer[0], len, segment_size, (struct sockaddr*)&phy_info->p7_tx_sockaddr, sizeof(phy_info->p7_tx_sockaddr));

									nfapi_cqi_indication_t cqi_ind;
									memset(&cqi_ind, 0, sizeof(cqi_ind));
									cqi_ind.header.message_id = NFAPI_RX_CQI_INDICATION;
									cqi_ind.header.phy_id = msg.header.phy_id;
									cqi_ind.sfn_sf = msg.sfn_sf;
									len = nfapi_p7_message_pack(&cqi_ind, buffer, buffer_size, 0);
									send_p7_segmented_msg(phy_info->p7_tx_sock, &buffer[0], len, segment_size, (struct sockaddr*)&phy_info->p7_tx_sockaddr, sizeof(phy_info->p7_tx_sockaddr));

									nfapi_rach_indication_t rach_ind;
									memset(&rach_ind, 0, sizeof(rach_ind));
									rach_ind.header.message_id = NFAPI_RACH_INDICATION;
									rach_ind.header.phy_id = msg.header.phy_id;
									rach_ind.sfn_sf = msg.sfn_sf;
									len = nfapi_p7_message_pack(&rach_ind, buffer, buffer_size, 0);
									send_p7_segmented_msg(phy_info->p7_tx_sock, &buffer[0], len, segment_size, (struct sockaddr*)&phy_info->p7_tx_sockaddr, sizeof(phy_info->p7_tx_sockaddr));

									nfapi_srs_indication_t srs_ind;
									memset(&srs_ind, 0, sizeof(srs_ind));
									srs_ind.header.message_id = NFAPI_SRS_INDICATION;
									srs_ind.header.phy_id = msg.header.phy_id;
									srs_ind.sfn_sf = msg.sfn_sf;
									len = nfapi_p7_message_pack(&srs_ind, buffer, buffer_size, 0);
									send_p7_segmented_msg(phy_info->p7_tx_sock, &buffer[0], len, segment_size, (struct sockaddr*)&phy_info->p7_tx_sockaddr, sizeof(phy_info->p7_tx_sockaddr));

									nfapi_rx_indication_t rx_ind;
									memset(&rx_ind, 0, sizeof(rx_ind));
									rx_ind.header.message_id = NFAPI_RX_ULSCH_INDICATION;
									rx_ind.header.phy_id = msg.header.phy_id;
									rx_ind.sfn_sf = msg.sfn_sf;
									len = nfapi_p7_message_pack(&rx_ind, buffer, buffer_size, 0);
									send_p7_segmented_msg(phy_info->p7_tx_sock, &buffer[0], len, segment_size, (struct sockaddr*)&phy_info->p7_tx_sockaddr, sizeof(phy_info->p7_tx_sockaddr));

									if(msg.sfn_sf % 5 == 0)
									{
										// periodically send the timing info
										nfapi_timing_info_t timing_info;
										memset(&timing_info, 0, sizeof(timing_info));
										timing_info.header.message_id = NFAPI_TIMING_INFO;
										timing_info.header.phy_id = msg.header.phy_id;
										len = nfapi_p7_message_pack(&timing_info, buffer, buffer_size, 0);
										send_p7_segmented_msg(phy_info->p7_tx_sock, &buffer[0], len, segment_size, (struct sockaddr*)&phy_info->p7_tx_sockaddr, sizeof(phy_info->p7_tx_sockaddr));
									}
									
									nfapi_nb_harq_indication_t nb_harq_ind;
									memset(&nb_harq_ind, 0, sizeof(nb_harq_ind));
									nb_harq_ind.header.message_id = NFAPI_NB_HARQ_INDICATION;
									nb_harq_ind.header.phy_id = msg.header.phy_id;
									nb_harq_ind.sfn_sf = msg.sfn_sf;
									len = nfapi_p7_message_pack(&nb_harq_ind, buffer, buffer_size, 0);
									send_p7_segmented_msg(phy_info->p7_tx_sock, &buffer[0], len, segment_size, (struct sockaddr*)&phy_info->p7_tx_sockaddr, sizeof(phy_info->p7_tx_sockaddr));

									nfapi_nrach_indication_t nrach_ind;
									memset(&nrach_ind, 0, sizeof(nrach_ind));
									nrach_ind.header.message_id = NFAPI_NRACH_INDICATION;
									nrach_ind.header.phy_id = msg.header.phy_id;
									nrach_ind.sfn_sf = msg.sfn_sf;
									len = nfapi_p7_message_pack(&nrach_ind, buffer, buffer_size, 0);
									send_p7_segmented_msg(phy_info->p7_tx_sock, &buffer[0], len, segment_size, (struct sockaddr*)&phy_info->p7_tx_sockaddr, sizeof(phy_info->p7_tx_sockaddr));

								}
								break;
							case NFAPI_UL_CONFIG_REQUEST:
								{
									nfapi_ul_config_request_t msg;
									nfapi_p7_message_unpack(buffer, len, &msg, sizeof(msg), 0);
									//printf("[PNF:%d] (%d/%d) NFAPI_UL_CONFIG_REQUEST\n", header.phy_id, SFNSF2SFN(msg.sfn_sf), SFNSF2SF(msg.sfn_sf));
								}
								break;
							case NFAPI_HI_DCI0_REQUEST:
								{
									nfapi_hi_dci0_request_t msg;
									nfapi_p7_message_unpack(buffer, len, &msg, sizeof(msg), 0);
									//printf("[PNF:%d] (%d/%d) NFAPI_HI_DCI0_REQUEST\n", header.phy_id, SFNSF2SFN(msg.sfn_sf), SFNSF2SF(msg.sfn_sf));
								}
								break;
							case NFAPI_TX_REQUEST:
								{
									nfapi_tx_request_t msg;
									nfapi_p7_message_unpack(buffer, len, &msg, sizeof(msg), 0);
									//printf("[PNF:%d] (%d/%d) NFAPI_TX_REQUEST\n", header.phy_id, SFNSF2SFN(msg.sfn_sf), SFNSF2SF(msg.sfn_sf));
								}
								break;
						}
					
					}
				}
			}
		}
	}

	printf("Triggering p5 shutdown\n");

	// vnf p5 trigger shutdown
	nfapi_pnf_stop_request_t stop_req;
	memset(&stop_req, 0, sizeof(stop_req));
	stop_req.header.message_id = NFAPI_PNF_STOP_REQUEST;
	nfapi_vnf_pnf_stop_req(vnf_test_config[0].p5_vnf_config, 0, &stop_req);

	phy_id = receive_pnf_stop_request(&pnf_test_config[0]);
	send_pnf_stop_response(&pnf_test_config[0], phy_id);


	nfapi_vnf_stop(vnf_test_config[0].p5_vnf_config);


	
	int* result;
	pthread_join((vnf_test_config[0].thread), (void**)&result);
	CU_ASSERT_EQUAL(result, 0);
	//CU_ASSERT_EQUAL(pnf_connection_indication_called, 1);


	close(pnf_test_config[0].p5_sock);
}

void vnf_test_start_connect_ipv6(void)
{
	char* vnf_addr = "::1";
	int vnf_port = 4242;
	pnf_connection_indication_called = 0;

	nfapi_vnf_config_t* config = nfapi_vnf_config_create();
	config->vnf_p5_port = vnf_port;
	config->vnf_ipv4 = 0;
	config->vnf_ipv6 = 1;
	config->pnf_connection_indication = &pnf_connection_indication;
	config->pnf_disconnect_indication = &pnf_disconnect_indication;
	config->pnf_param_resp = &pnf_param_response;
	config->pnf_config_resp = &pnf_config_response;
	config->pnf_start_resp = &pnf_start_response;

	pthread_t thread;
	pthread_create(&thread, NULL, &vnf_test_start_thread, config);

	sleep(1);

	
	int p5Sock = socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP);
	
	struct sockaddr_in6 addr;
	addr.sin6_family = AF_INET6;
	addr.sin6_port = htons(vnf_port);
	//addr.sin6_addr = inet_addr(vnf_addr);
	inet_pton(AF_INET6, vnf_addr, &addr.sin6_addr);


	/*
	int connect_result = connect(p5Sock, (struct sockaddr *)&addr, sizeof(addr) );

	printf("connect_result %d %d\n", connect_result, errno);

	receive_pnf_param_request(p5Sock);
	send_pnf_param_response(p5Sock, &addr, sizeof(addr));

	receive_pnf_config_request(p5Sock);
	send_pnf_config_response(p5Sock, &addr, sizeof(addr));

	receive_pnf_start_request(p5Sock);
	send_pnf_start_response(p5Sock, &addr, sizeof(addr));
	*/
	sleep(4);
	nfapi_vnf_stop(config);
	
	int* result;
	pthread_join(thread, (void**)&result);
	CU_ASSERT_EQUAL(result, 0);
	CU_ASSERT_EQUAL(pnf_connection_indication_called, 1);


	close(p5Sock);
}
void vnf_test_start_connect_2(void)
{
	char* vnf_addr = "127.0.0.1";
	int vnf_port = 4242;
	pnf_connection_indication_called = 0;

	nfapi_vnf_config_t * config = nfapi_vnf_config_create();
	config->vnf_p5_port = vnf_port;
	config->pnf_connection_indication = &pnf_connection_indication;

	pthread_t thread;
	pthread_create(&thread, NULL, &vnf_test_start_thread, config);

	sleep(1);

	
	
	struct sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_port = htons(vnf_port);
	addr.sin_addr.s_addr = inet_addr(vnf_addr);

	
	int p5Sock1 = socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP);

	int connect_result_1 = connect(p5Sock1, (struct sockaddr *)&addr, sizeof(addr) );

	printf("connect_result_1 %d %d\n", connect_result_1, errno);

	int p5Sock2 = socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP);

	int connect_result_2 = connect(p5Sock2, (struct sockaddr *)&addr, sizeof(addr) );

	printf("connect_result_2 %d %d\n", connect_result_2, errno);

	sleep(1);

	close(p5Sock1);

	sleep(1);

	close(p5Sock2);

	sleep(1);

	nfapi_vnf_stop(config);
	
	int* result;
	pthread_join(thread, (void**)&result);
	CU_ASSERT_EQUAL(result, 0);
	CU_ASSERT_EQUAL(pnf_connection_indication_called, 2);


	close(p5Sock1);
	close(p5Sock2);
}

void vnf_test_p7_segmentation_test1(void)
{

}


/************* Test Runner Code goes here **************/

int main ( void )
{
   CU_pSuite pSuite = NULL;

   /* initialize the CUnit test registry */
   if ( CUE_SUCCESS != CU_initialize_registry() )
      return CU_get_error();

   /* add a suite to the registry */
   pSuite = CU_add_suite( "vnf_test_suite", init_suite, clean_suite );
   if ( NULL == pSuite ) 
   {
      CU_cleanup_registry();
      return CU_get_error();
   }

        //(NULL == CU_add_test(pSuite, "vnf_test_start_connect_2", vnf_test_start_connect_2)) 
   /* add the tests to the suite */
   if ( (NULL == CU_add_test(pSuite, "vnf_test_start_no_config", vnf_test_start_no_config)) ||
        (NULL == CU_add_test(pSuite, "vnf_test_start_connect", vnf_test_start_connect)) ||
        (NULL == CU_add_test(pSuite, "vnf_test_p7_segmentation_test1", vnf_test_p7_segmentation_test1))
      )
   {
      CU_cleanup_registry();
      return CU_get_error();
   }

   // Run all tests using the basic interface
   CU_basic_set_mode(CU_BRM_VERBOSE);
   CU_set_output_filename("vnf_unit_test_results.xml");
   CU_basic_run_tests();

	CU_pSuite s = CU_get_registry()->pSuite;
	int count = 0;
	while(s)
	{
		CU_pTest t = s->pTest;
		while(t)
		{
			count++;
			t = t->pNext;
		}
		s = s->pNext;
	}

	printf("%d..%d\n", 1, count);



	s = CU_get_registry()->pSuite;
	count = 1;
	while(s)
	{
		CU_pTest t = s->pTest;
		while(t)
		{
			int pass = 1;
			CU_FailureRecord* failures = CU_get_failure_list();
			while(failures)
			{
				if(strcmp(failures->pSuite->pName, s->pName) == 0 &&
				   strcmp(failures->pTest->pName, t->pName) == 0)
				{
					pass = 0;
					failures = 0;
				}
				else
				{
					failures = failures->pNext;
				}
			}

			if(pass)
				printf("ok %d - %s:%s\n", count, s->pName, t->pName);
			else 
				printf("not ok %d - %s:%s\n", count, s->pName, t->pName);

			count++;
			t = t->pNext;
		}
		s = s->pNext;
	}

   CU_cleanup_registry();
   return CU_get_error();

}
