#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <arpa/inet.h>
#include <net/ethernet.h>
#include <netinet/if_ether.h>
#include <linux/if_packet.h>
#include <errno.h>
#include "../common.h"
#include "raw_sock.h"
#include "api.h"


int eth_header_init(char *fbuf, char *dmac_hex, char *smac_hex, short ptype);
/****************************************************************************
 * Function   : 	set_socket_nonblocking
 * Description: 	将套接字设置为非阻塞模式
 * Input      : 	socket    : 套接字文件描述符
 * Output     : 	N/A
 * Return     :	成功返回OK，出错返回ERROR
****************************************************************************/
int set_socket_nonblocking(int socket)
{
	int val = 0;

	val = fcntl(socket, F_GETFL);
	val |= O_NONBLOCK;

	if (fcntl(socket, F_SETFL, val) < 0)
	{
		perror("fcntl");
		return ERROR;
	}

	return OK;
}


/****************************************************************************
 * Function   : 	set_socket_timeout_val
 * Description: 	将套接字设置为非阻塞模式
 * Input      : 	sock : 套接字文件描述符
 				type : 类型(发送超时或者接收超时)
 				sec  : 超时时间(秒)
 				usec : 超时时间(微秒)
 * Output     : 	N/A
 * Return     :	成功返回OK，出错返回ERROR
****************************************************************************/
int set_socket_timeout_val(int sock, int type, int sec, int usec)
{
	struct timeval tv;

	int ret = ERROR;

	if ((sock < 0) || (sec < 0) || (usec < 0)
		|| (type != SO_RCVTIMEO && type != SO_SNDTIMEO))
	{
		return ERROR;
	}

	tv.tv_sec = sec;
	tv.tv_usec = usec;

	ret = setsockopt(sock, SOL_SOCKET, type, &tv, sizeof(struct timeval));
	if (ret < 0)
	{
		proto_syslog(LOG_ERR,"set sock timeout error, socket: %d, type: %s, sec: %d, usec: %d\n",
			sock, (type == SO_RCVTIMEO) ? "SO_RCVTIMEO" : "SO_SNDTIMEO", sec, usec);
		return ERROR;
	}

	return OK;
}


/****************************************************************************
 * Function   : 	bind_to_device
 * Description: 	将套接字绑定到指定设备(仅用于协议族PF_PACKET)
 * Input      : 	sock : 套接字文件描述符
				prot : 协议类型
 				dev  : 设备名称
 * Output     : 	N/A
 * Return     :	成功返回OK，出错返回ERROR
****************************************************************************/
int bind_to_device(int sock, int prot, char dev[])
{
	struct ifreq ifstruct;
	struct sockaddr_ll sa;

	memset(&sa, 0, sizeof(sa));
	memset(&ifstruct, 0, sizeof(ifstruct));

	strlcpy(ifstruct.ifr_name, dev, sizeof(ifstruct.ifr_name));
	if (ioctl(sock, SIOCGIFINDEX, &ifstruct) < 0)
	{
		perror("ioctl SIOCGIFINDEX");
		return ERROR;
	}

	sa.sll_family = PF_PACKET;
	sa.sll_protocol = htons(prot);
	sa.sll_ifindex = ifstruct.ifr_ifindex;

	/* 将socket与设备绑定 */
	if (bind(sock, (struct sockaddr  *)&sa, sizeof(struct sockaddr_ll)) < 0)
	{
		perror("bind");
		return ERROR;
	}

	return OK;
}


/****************************************************************************
 * Function   : 	raw_socket_init
 * Description: 	创建原始套接字，并设置为非阻塞模式，并绑定到指定设备
 				仅用于协议族PF_PACKET
 * Input      : 	prot : 协议类型
 				dev  : 设备名称
 * Output     : 	N/A
 * Return     :	成功返回socket文件描述符，出错返回ERROR
****************************************************************************/
int raw_socket_init(int prot, char *dev)
{
	int rawsock = -1;
	int ret = ERROR;

	if (!dev)
	{
		return ERROR;
	}

	/* 创建原始套接字 */
	if ((rawsock = socket(PF_PACKET, SOCK_RAW, htons(prot))) < 0)
	{
		perror("socket error\n");
		return ERROR;
	}

	ret = set_socket_timeout_val(rawsock, SO_RCVTIMEO, 1, 0);
	if (ret == ERROR)
	{
		proto_syslog(LOG_ERR,"set socket timeout val failed\n");
		close(rawsock);
		return ERROR;
	}
	/*设置为非阻塞*/
	
	// ret= set_socket_nonblocking(rawsock);
	// if (ret == ERROR)
	// {
	// 	printf("set socket nonblocking failed\n");
	// 	close(rawsock);
	// 	return ERROR;
	// }

	/* 将套接字与设备绑定 */
	ret = bind_to_device(rawsock, prot, dev);
	if (ret == ERROR)
	{
		proto_syslog(LOG_ERR,"set socket nonblocking failed\n");
		close(rawsock);
		return ERROR;
	}

	return rawsock;
}

RAW_SOCK *raw_config_init(char *ifname,char *channel_name)
{
	proto_syslog(LOG_INFO,"raw config init!!\n");
	char smac_str[STR_MAC_LEN] = {0};
	int ret = OK;
	// int err = 0;
	RAW_SOCK *raw = calloc(1,sizeof(RAW_SOCK));
	if ((!raw) ||(!ifname)||(!channel_name))
	{
		proto_syslog(LOG_ERR,"some data null\n");
		goto ERR;
	}
	raw->ifname = strdup(ifname);
	raw->channel_name = strdup(channel_name);
	raw->len = 0;
	ret = get_local_macaddr(raw->ifname, smac_str);
	if (ret != OK)
	{
		proto_syslog(LOG_ERR,"%s get local macaddr failed\n", ifname);
		goto ERR;
	}

	mac_string2mac_hex(smac_str, raw->smac_hex);
	mac_string2mac_hex(BROADCAST_MAC_ADDR , raw->dmac_hex);

	ret = eth_header_init(raw->send_buffer, raw->dmac_hex,  raw->smac_hex, ETH_P_TP);
	if (ret != OK)
	{
		proto_syslog(LOG_ERR,"eth header init failed\n");
		goto ERR;
	}
	ret =  raw_socket_init(ETH_P_TP,raw->ifname);
	if (ret == ERROR)
	{
		proto_syslog(LOG_ERR,"raw socket init failed\n");
		goto ERR;
	}
	raw->sock = ret;
	recv(raw->sock, raw->rcv_buffer, RAW_PACKET_LEN_MAX, 0);
	return raw;
ERR:
	if (raw!=NULL)
	{
		if (raw->ifname!=NULL)
		{
			free(raw->ifname);
		}
		if (raw->channel_name!=NULL)
		{
			free(raw->channel_name);
		}
		free(raw);
		raw = NULL;
	}
	return NULL;
	
}
/****************************************************************************
 * Function   : 	raw_data_send
 * Description: 	用原始套接字将数据发送出去
 				约定数据长度不得大于1514字节，不得小于14字节
 * Input      : 	raw : 原始套接字结构
 * Output     : 	N/A
 * Return     :	成功返回OK，出错返回ERROR
****************************************************************************/
int raw_data_send(RAW_SOCK *raw)
{
	int ret = 0;
	int send_byte = 0;
	int retry_count = 0;

	if (!raw ||raw->len < RAW_PACKET_LEN_MIN || raw->len > RAW_PACKET_LEN_MAX)
	{
		perror("invalid argument\n");
		return ERROR;
	}

send_again:
	ret = send(raw->sock, raw->send_buffer + send_byte, raw->len, 0);
	if (ret > 0)
	{
		send_byte += ret;
		if (send_byte != raw->len)
		{
			proto_syslog(LOG_ERR,"raw data send incomplete, ret = %d\n", ret);
			if (retry_count < 5)
			{
				retry_count++;
				usleep(10000);
				goto send_again;
			}
			else
			{
				proto_syslog(LOG_ERR,"raw data send retry_count=%d\n", retry_count);
				return ERROR;
			}
		}
		else
		{
			return OK;
		}
	}

	if (errno == EAGAIN && retry_count < 5)
	{
		retry_count++;
		usleep(10000);
		goto send_again;
	}

	proto_syslog(LOG_ERR,"raw data send error ret = %d, errno=%d strerror(errno)=%s raw->sock=%d\n", ret, errno, strerror(errno), raw->sock);
	return ERROR;
}


/****************************************************************************
 * Function   : 	raw_data_recv
 * Description: 	从指定套接字中读取数据.由于待读取数据长度未知，所以缓存
 				空间由此函数分配，由调用者释放
 * Input      : 	raw : 原始套接字结构
 * Output     : 	N/A
 * Return     :	成功返回OK，出错返回ERROR
****************************************************************************/
int raw_data_recv(RAW_SOCK *raw)
{
	int ret;
	if (!raw)
	{
		proto_syslog(LOG_ERR,"invalid argument : rawdata is NULL\n");
		return ERROR;
	}
	raw->len = 0;
	ret = recv(raw->sock, raw->rcv_buffer, RAW_PACKET_LEN_MAX, 0);
	if (ret < 0)
	{
		/* 所有数据已接收完成 */
		if (errno == EAGAIN)
		{
			return ERROR;
		}
		else
		{
			proto_syslog(LOG_ERR,"Failed to recv. errno:%s",strerror(errno));
			return ERROR;
		}
	}
	else if (ret == 0)
	{
		proto_syslog(LOG_ERR,"socket %d disconnected\n", raw->sock);
		return ERROR;
	}

	raw->len = ret;

	return OK;
}


/****************************************************************************
 * Function   : 	eth_header_init
 * Description: 	以太网头部初始化
 * Input      : 	fbuf     : 帧缓存
 				dmac_hex : 目的MAC，十六进制形式
 				smac_hex : 源MAC，十六进制形式
 				ptype    : 协议类型
 * Output     : 	N/A
 * Return     :	成功返回OK，出错返回ERROR
****************************************************************************/
int eth_header_init(char *fbuf, char *dmac_hex, char *smac_hex, short ptype)
{
	ETH_HEADER *eth = NULL;

	if (!fbuf || !dmac_hex || !smac_hex)
	{
		return ERROR;
	}

	/* 填充以太网头部 */
	eth = (ETH_HEADER *)fbuf;

	memcpy(eth->ether_dhost, dmac_hex, MAC_HEX_LEN);
	memcpy(eth->ether_shost, smac_hex, MAC_HEX_LEN);

	eth->ether_type = htons(ptype);

	return OK;
}


/****************************************************************************
 * Function   : 	send_discovery_frame
 * Description: 	向局域网内发送设备发现报文
 * Input      : 	raw : 套接字结构
 * Output     : 	N/A
 * Return     :	成功返回OK，出错返回ERROR
****************************************************************************/
int send_data_frame(RAW_SOCK *raw,char *data,int len)
{
	int ret = OK;
	memcpy(raw->send_buffer+sizeof(ETH_HEADER),data,len);
	raw->len = len+sizeof(ETH_HEADER);
	ret = raw_data_send(raw);
	if (ret != OK)
	{
		proto_syslog(LOG_ERR,"raw data send error\n");
		return ERROR;
	}

	return OK;
}


int raw_config_deinit(RAW_SOCK *raw)
{
	free(raw->channel_name);
	free(raw->ifname);
	close(raw->sock);
	free(raw);
	return 0;
}


