/*====================================================================*
	Copyright (c) 2020 Qualcomm Technologies, Inc.
	All Rights Reserved.
	Confidential and Proprietary - Qualcomm Technologies, Inc.
	******************************************************************
	2013 Qualcomm Atheros, Inc.
*====================================================================*/

/*====================================================================*
 *
 *   CPLChannel.cpp - CPLChannel class definition;
 *
 *   Ethernet I/O channel managment for powerline applications;
 *
 *   Contributor(s):
 *	    Charles Maier <charles.maier@intellon.com>
 *
 *--------------------------------------------------------------------*/

#ifndef CPLCHANNEL_SOURCE
#define CPLCHANNEL_SOURCE

#define SLOTS 6
#define CARRIERS 1155

/*====================================================================*
 *   system header files;
 *--------------------------------------------------------------------*/

#include <unistd.h>
#include <iostream>
#include <cstring>
#include <cstdlib>

/*====================================================================*
 *   system header files;
 *--------------------------------------------------------------------*/

#if defined (__linux__)
#	include <sys/socket.h>
#	include <sys/ioctl.h>
#	include <sys/poll.h>
#	include <linux/if_packet.h>
#	include <net/ethernet.h>
#	include <net/if_arp.h>
#	include <net/if.h>
#elif defined (__APPLE__)
#	include <sys/types.h>
#	include <sys/socket.h>
#	include <sys/ioctl.h>
#	include <netinet/if_ether.h>
#	include <net/if_dl.h>
#	include <net/bpf.h>
#	include <fcntl.h>
#elif defined (__OpenBSD__)
#	include <sys/types.h>
#	include <sys/socket.h>
#	include <sys/ioctl.h>
#	include <net/bpf.h>
#	include <unistd.h>
#	include <fcntl.h>
#elif defined (WINPCAP) 
#elif defined (LIBPCAP)
#else
#error "Unknown Environment"
#endif

/*====================================================================*
 *   custom header files;
 *--------------------------------------------------------------------*/

#include "../classes/CPLChannel.hpp"
#include "../classes/ohomeplug.hpp"
#include "../classes/ointellon.hpp"
#include "../classes/omemory.hpp"
#include "../classes/oerror.hpp"

/*====================================================================*
 *
 *   signed Descriptor () const;
 *
 *   return the channel socket file descriptor;
 *
 *--------------------------------------------------------------------*/

signed CPLChannel::Descriptor () const 

{
	return (this->mfd);
}

/*====================================================================*
 *
 *   signed Bridges (void * memory, size_t extent);
 *
 *   encode memory with a consecutive list of bridge device hardware
 *   addresses; return the number of addresses encoded; return -1 on
 *   memory overflow;
 *
 *   this is the start point for device discovery; each bridge could
 *   be the gateway to a separate powerline network;
 *
 *--------------------------------------------------------------------*/

signed CPLChannel::Bridges (void * memory, size_t extent) 

{
	ointellon intellon;
	byte * origin = (byte *)(memory);
	byte * offset = (byte *)(memory);
	byte message [ETHER_MAX_LEN];
	std::memset (memory, 0, extent);
	std::memset (message, 0, sizeof (message));
	intellon.ImportHostAddress (this->HostAddress ());
	intellon.ExportHeader (message);
	if (this->SendMessage (message, ETHER_MIN_LEN) > 0) 
	{
		while (this->ReadMessage (message, sizeof (message)) > 0) 
		{
			if (extent < ETHER_ADDR_LEN) 
			{
				oerror::error (0, EOVERFLOW, "Bridge address lost");
				continue;
			}
			intellon.ImportHeader (message);
			if (intellon.IsMessageType (0, VS_SW_VER| MMTYPE_CNF)) 
			{
				intellon.ExportHostAddress (offset);
				offset += ETHER_ADDR_LEN;
				extent -= ETHER_ADDR_LEN;
			}
		}
	}
	return ((signed)(offset - origin) / ETHER_ADDR_LEN);
}

/*====================================================================*
 *
 *   signed Neighbors (void * memory, size_t extent);
 *
 *   return a list powerline network device addresses on a powerline
 *   network; the list consists of a known device plus all others on
 *   the same powerline network; the device is defined by the channel
 *   peer address and appears first in the list;
 *
 *   the device address must be explicit; it cannot be the emptycast,
 *   broadcast or localcast address;
 *
 *   the first (known) device is omitted here as an expriment despite
 *   what was said above; - charlie maier
 *
 *--------------------------------------------------------------------*/

signed CPLChannel::Neighbors (void * memory, size_t extent) 

{
	ointellon intellon;
	byte * origin = (byte *)(memory);
	byte * offset = (byte *)(memory);
	byte message [ETHER_MAX_LEN];

#ifndef __GNUC__
#pragma pack (push,1)
#endif

	struct __packed station 
	{
		uint8_t DA [ETHER_ADDR_LEN];
		uint8_t TEI;
		uint8_t BDA [ETHER_ADDR_LEN];
		uint8_t AVGTX;
		uint8_t AVGRX;
	}
	* station;
	struct __packed network 
	{
		uint8_t NID [7];
		uint8_t SNID;
		uint8_t TEI;
		uint8_t ROLE;
		uint8_t CCO_MACADDR [ETHER_ADDR_LEN];
		uint8_t CCO_TEI;
		uint8_t NUMSTAS;
		struct station station [1];
	}
	* network;
	struct __packed networks 
	{
		uint8_t NUMAVLNS;
		struct network network [1];
	}
	* networks;

#ifndef __GNUC__
#pragma pack (pop)
#endif

	std::memset (memory, 0, extent);
	if (!std::memcmp (this->PeerAddress (), oethernet::EmptycastAddress, ETHER_ADDR_LEN)) 
	{
		oerror::error (0, ECANCELED, "Emptycast address used to explore network");
		return (0);
	}
	if (!std::memcmp (this->PeerAddress (), oethernet::BroadcastAddress, ETHER_ADDR_LEN)) 
	{
		oerror::error (0, ECANCELED, "Broadcast address used to explore network");
		return (0);
	}
	if (!std::memcmp (this->PeerAddress (), ointellon::LocalcastAddress, ETHER_ADDR_LEN)) 
	{
		oerror::error (0, ECANCELED, "Localcast address used to explore network");
		return (0);
	}
	std::memset (message, 0, sizeof (message));
	intellon.ImportPeerAddress (this->PeerAddress ());
	intellon.ImportHostAddress (this->HostAddress ());
	intellon.SetMessageType (VS_NW_INFO | MMTYPE_REQ);
	networks = (struct networks *)(intellon.ExportHeader (message));
	if (this->SendMessage (message, ETHER_MIN_LEN) <= 0) 
	{
		oerror::error (0, errno, CPLCHANNEL_CANTSEND);
		return (0);
	}
	if (this->ReadMessage (message, sizeof (message)) <= 0) 
	{
		oerror::error (0, errno, CPLCHANNEL_CANTREAD);
		return (0);
	}
	network = (struct network *)(&networks->network);
	while (networks->NUMAVLNS-- > 0) 
	{
		if (extent < ETHER_ADDR_LEN) 
		{
			oerror::error (0, EOVERFLOW, "Bridge address lost");
			return (-1);
		}

#if 0

		intellon.ImportHeader (message);
		intellon.ExportHostAddress (offset);
		offset += ETHER_ADDR_LEN;
		extent -= ETHER_ADDR_LEN;

#endif

		station = (struct station *)(&network->station);
		while (network->NUMSTAS-- > 0) 
		{
			if (extent < ETHER_ADDR_LEN) 
			{
				oerror::error (0, EOVERFLOW, "Device address lost");
				return (-1);
			}
			if (std::memcmp (station->DA, oethernet::BroadcastAddress, ETHER_ADDR_LEN)) 
			{
				std::memcpy (offset, station->DA, sizeof (station->DA));
				offset += ETHER_ADDR_LEN;
				extent -= ETHER_ADDR_LEN;
			}
			station++;
		}
		network = (struct network *)(station);
	}
	return ((signed)(offset - origin) / ETHER_ADDR_LEN);
}

/*====================================================================*
 *
 *   signed SendMessage (void const * message, signed length);
 *
 *--------------------------------------------------------------------*/

signed CPLChannel::SendMessage (void const * memory, signed extent) 

{
	this->dump (memory, extent);

#if defined (__linux__)

	extent = sendto (this->mfd, memory, extent, 0, (struct sockaddr *) (0), (socklen_t) (0));

#elif defined (__APPLE__) || defined (__OpenBSD__)

	extent = write (this->mfd, memory, extent);

#elif defined (WINPCAP) || defined (LIBPCAP)

	if (pcap_sendpacket (this->msocket, (const u_char *)(memory), extent)) 
	{
		extent = -1;
	}

#else
#error "Unknown Environment"
#endif

	return (extent);
}

/*====================================================================*
 *
 *   signed ReadMessage (void * memory, signed extent);
 *
 *   encode external memory with an incoming Ethernet frame; return
 *   frame length on success, 0 on timeout or -1 on error;
 *
 *   on linux/osx, this method returns as soon as a frame arrives or
 *   once the timeout has expired; consequently, long timeout values
 *   do not affect performance;
 *
 *   on winpcap this method does not return until timeout expires;
 *   consequenty, long timeouts affect performance;
 *
 *--------------------------------------------------------------------*/

signed CPLChannel::ReadMessage (void * memory, signed extent) 

{

#if defined (__linux__)

	struct pollfd pollfd = 
	{
		this->mfd,
		POLLIN,
		0
	};
	int status = poll (&pollfd, 1, this->mtimeout);
	std::memset (memory, 0, extent);
	if (status < 0) 
	{
		oerror::error (0, errno, "poll");
		return (-1);
	}
	if (status > 0) 
	{
		extent = recvfrom (this->mfd, memory, extent, 0, (struct sockaddr *) (0), (socklen_t *)(0));
		if (extent == -1) 
		{
			oerror::error (0, errno, "recvfrom");
			return (-1);
		}
		this->dump (memory, extent);
		return (extent);
	}

#elif defined (__APPLE__) || defined (__OpenBSD__)

	byte buffer [this->bpf_length];
	struct bpf_hdr * bpf_hdr = (struct bpf_hdr *)(buffer);
	std::memset (memory, 0, extent);
	std::memset (buffer, 0, sizeof (buffer));
	extent = read (this->mfd, buffer, sizeof (buffer));
	if (extent < 0) 
	{
		oerror::error (0, errno, "bpf");
		return (-1);
	}
	if (extent > 0) 
	{
		extent = bpf_hdr->bh_caplen;
		std::memcpy (memory, buffer + bpf_hdr->bh_hdrlen, bpf_hdr->bh_caplen);
		this->dump (memory, extent);
		return (extent);
	}

#elif defined (WINPCAP) || defined (LIBPCAP)

	struct pcap_pkthdr * header;
	const byte * data;
	signed status = pcap_next_ex (this->msocket, &header, &data);
	std::memset (memory, 0, extent);
	if (status < 0) 
	{
		oerror::error (0, errno, "pcap_next_ex");
		return (-1);
	}
	if (status > 0) 
	{
		extent = header->caplen;
		std::memcpy (memory, data, header->caplen);
		this->dump (memory, extent);
		return (extent);
	}

#else
#error "Unknown Environment"
#endif

	return (0);
}

/*====================================================================*
 *
 *   CPLChannel & dump (void const * memory, signed extent);
 *
 *   print Ethernet frames in hex dump format on stderr when verbose
 *   flag is set; use this for testing and debugging purposes;
 *
 *--------------------------------------------------------------------*/

CPLChannel & CPLChannel::dump (void const * memory, size_t extent) 

{
	if (oflagword::anyset (CPLCHANNEL_FLAG_VERBOSE)) 
	{
		omemory::hexdump (memory, 0, extent, &std::cerr);
		std::cerr << std::endl;
	}
	return (*this);
}

/*====================================================================*
 *
 *   CPLChannel & open ()
 *
 *   open a raw ethernet socket on the designated interface and apply
 *   a packet filter; the filter accepts HomePlug AV frames addressed
 *   to either this host or the ethernet broadcast address; set the
 *   channel host address to the interface hardware address;
 *
 *   if you don't understand this code then you probably have a life;
 *
 *--------------------------------------------------------------------*/

CPLChannel & CPLChannel::open () 

{

#if defined (__linux__)

	struct ifreq ifreq;
	struct sockaddr_ll sockaddr_ll = 
	{
		PF_PACKET,
		0x0000,
		0x0000,
		ARPHRD_ETHER,
		PACKET_OTHERHOST,
		ETHER_ADDR_LEN,
		{
			0x00,
			0x00,
			0x00,
			0x00,
			0x00,
			0x00,
			0x00,
			0x00
		}
	};
	std::memset (&ifreq, 0, sizeof (ifreq));
	oethernet::ExportProtocol (&sockaddr_ll.sll_protocol);
	if ((this->mfd = socket (sockaddr_ll.sll_family, SOCK_RAW, sockaddr_ll.sll_protocol)) == -1) 
	{
		oerror::error (1, errno, "%s", ifreq.ifr_name);
	}
	std::memcpy (ifreq.ifr_name, this->Name (), sizeof (ifreq.ifr_name));
	if (ioctl (this->mfd, SIOCGIFINDEX, &ifreq) == -1) 
	{
		oerror::error (0, errno, "%s", ifreq.ifr_name);
	}
	sockaddr_ll.sll_ifindex = ifreq.ifr_ifindex;
	if (ioctl (this->mfd, SIOCGIFHWADDR, &ifreq) == -1) 
	{
		oerror::error (0, errno, "%s", ifreq.ifr_name);
	}
	std::memcpy (sockaddr_ll.sll_addr, ifreq.ifr_ifru.ifru_hwaddr.sa_data, sizeof (sockaddr_ll.sll_addr));
	if (bind (this->mfd, (struct sockaddr *) (&sockaddr_ll), sizeof (sockaddr_ll)) == -1) 
	{
		oerror::error (0, errno, "%s", ifreq.ifr_name);
	}
	if (ioctl (this->mfd, SIOCGIFFLAGS, &ifreq) == -1) 
	{
		oerror::error (0, errno, "%s", ifreq.ifr_name);
	}
	ifreq.ifr_flags |= (IFF_UP | IFF_BROADCAST | IFF_MULTICAST);
	ifreq.ifr_flags &= ~(IFF_ALLMULTI | IFF_PROMISC);
	if (ioctl (this->mfd, SIOCSIFFLAGS, &ifreq) == -1) 
	{
		oerror::error (0, errno, "%s", ifreq.ifr_name);
	}

#else

	struct bpf_program bpf_program;
	static struct bpf_insn bpf_insn [] = 
	{
		{
			BPF_LD + BPF_H + BPF_ABS,
			0,
			0,
			12
		},
		{
			BPF_JMP + BPF_JEQ + BPF_K,
			0,
			18,
			0
		},
		{
			BPF_LD + BPF_B + BPF_ABS,
			0,
			0,
			0
		},
		{
			BPF_JMP + BPF_JEQ + BPF_K,
			0,
			10,
			0
		},
		{
			BPF_LD + BPF_B + BPF_ABS,
			0,
			0,
			1
		},
		{
			BPF_JMP + BPF_JEQ + BPF_K,
			0,
			8,
			0
		},
		{
			BPF_LD + BPF_B + BPF_ABS,
			0,
			0,
			2
		},
		{
			BPF_JMP + BPF_JEQ + BPF_K,
			0,
			6,
			0
		},
		{
			BPF_LD + BPF_B + BPF_ABS,
			0,
			0,
			3
		},
		{
			BPF_JMP + BPF_JEQ + BPF_K,
			0,
			4,
			0
		},
		{
			BPF_LD + BPF_B + BPF_ABS,
			0,
			0,
			4
		},
		{
			BPF_JMP + BPF_JEQ + BPF_K,
			0,
			2,
			0
		},
		{
			BPF_LD + BPF_B + BPF_ABS,
			0,
			0,
			5
		},
		{
			BPF_JMP + BPF_JEQ + BPF_K,
			4,
			0,
			0
		},
		{
			BPF_LD + BPF_W + BPF_ABS,
			0,
			0,
			0
		},
		{
			BPF_JMP + BPF_JEQ + BPF_K,
			0,
			4,
			0xFFFFFFFF
		},
		{
			BPF_LD + BPF_H + BPF_ABS,
			0,
			0,
			4
		},
		{
			BPF_JMP + BPF_JEQ + BPF_K,
			0,
			2,
			0xFFFF
		},
		{
			BPF_LD + BPF_W + BPF_LEN,
			0,
			0,
			0
		},
		{
			BPF_RET + BPF_A,
			0,
			0,
			0
		},
		{
			BPF_RET + BPF_K,
			0,
			0,
			0
		}
	};

#if defined (__APPLE__) || defined (__OpenBSD__)

	struct ifreq ifreq;
	struct timeval timer = 
	{
		0,
		0
	};
	const byte * hwaddr = this->HardwareAddress ();
	char filename [FILENAME_MAX];
	unsigned count;
	unsigned state;
	for (count = 0; count < 100; count++) 
	{
		std::snprintf (filename, sizeof (filename), CPLCHANNEL_BPFDEVICE, count);
		if ((this->mfd =::open (filename, O_RDWR)) != -1) 
		{
			break;
		}
	}
	if (this->mfd == -1) 
	{
		oerror::error (1, ECANCELED, "No bpf devices available");
	}
	std::memcpy (ifreq.ifr_name, ointerface::Name (), sizeof (ifreq.ifr_name));
	if (ioctl (this->mfd, BIOCSETIF, &ifreq) == -1) 
	{
		oerror::error (0, errno, "1 %s", ifreq.ifr_name);
		return (*this);
	}
	if (ioctl (this->mfd, BIOCGBLEN, &this->bpf_length) == -1) 
	{
		oerror::error (0, errno, "Can't determine buffer length");
	}
	state = true;
	if (ioctl (this->mfd, BIOCIMMEDIATE, &state) == -1) 
	{
		oerror::error (0, errno, "Can't activate immediate mode");
	}

#if defined (__APPLE__)

	state = false;
	if (ioctl (this->mfd, BIOCSSEESENT, &state) == -1) 
	{
		oerror::error (0, errno, "Can't hide outgoing frames");
	}

#elif defined (__OpenBSD__)

	state = BPF_DIRECTION_OUT;
	if (ioctl (this->mfd, BIOCSDIRFILT, &state) == -1) 
	{
		oerror::error (0, errno, "Can't hide outgoing frames");
	}

#else
#error "Abandon all hope"
#endif

#if defined (__MAC_10_6)

/*
 *	accommodate know bug in BPF on MAC OS X 10.6; shorter times may cause socket
 *	read operations to block indefinitely when no frames are available;
 */

	timer.tv_sec = 1;

#else

	timer.tv_usec = this->mtimeout * 1000;

#endif

	if (ioctl (this->mfd, BIOCSRTIMEOUT, &timer) == -1) 
	{
		oerror::error (0, errno, "Can't set timeout");
	}
	bpf_program.bf_len = sizeof (bpf_insn)/sizeof (struct bpf_insn);
	bpf_program.bf_insns = bpf_insn;
	bpf_insn [1].k = oethernet::Protocol ();
	bpf_insn [3].k = hwaddr [0];
	bpf_insn [5].k = hwaddr [1];
	bpf_insn [7].k = hwaddr [2];
	bpf_insn [9].k = hwaddr [3];
	bpf_insn [11].k = hwaddr [4];
	bpf_insn [13].k = hwaddr [5];
	if (ioctl (this->mfd, BIOCSETF, &bpf_program) == -1) 
	{
		oerror::error (0, errno, "Can't use filter");
	}

#elif defined (WINPCAP) || defined (LIBPCAP)

	const byte * hostaddr = this->HardwareAddress ();
	this->msocket = pcap_open_live (this->Name (), 65536, 0, this->mtimeout, this->merrbuf);
	if (!this->msocket) 
	{
		oerror::error (1, errno, "No such adapter: %s", ointerface::Name ());
	}
	bpf_program.bf_len = sizeof (bpf_insn)/sizeof (struct bpf_insn);
	bpf_program.bf_insns = bpf_insn;
	bpf_insn [1].k = oethernet::Protocol ();
	bpf_insn [3].k = hostaddr [0];
	bpf_insn [5].k = hostaddr [1];
	bpf_insn [7].k = hostaddr [2];
	bpf_insn [9].k = hostaddr [3];
	bpf_insn [11].k = hostaddr [4];
	bpf_insn [13].k = hostaddr [5];
	if (pcap_setfilter (this->msocket, &bpf_program) < 0) 
	{
		oerror::error (0, errno, "Can't use filter: %s", ointerface::Name ());
	}
	if (pcap_setmintocopy (this->msocket, ETHER_MIN_LEN)) 
	{
		oerror::error (0, errno, "Can't open socket: %s", ointerface::Name ());
	}

#else
#error "Unknown Environment"
#endif
#endif

	oethernet::ImportHostAddress (this->HardwareAddress ());
	return (*this);
}

/*====================================================================*
 *
 *   CPLChannel & link ()
 *
 *   find any available powerline bridge and set the channel peer
 *   address; read all responses because this is a local broadcast;
 *   the last response read will be the lucky device;
 *
 *--------------------------------------------------------------------*/

CPLChannel & CPLChannel::link () 

{
	ointellon intellon;
	byte message [ETHER_MAX_LEN];
	std::memset (message, 0, sizeof (message));
	intellon.ImportHostAddress (this->HostAddress ());
	intellon.ExportHeader (message);
	if (this->SendMessage (message, ETHER_MIN_LEN) > 0) 
	{
		while (this->ReadMessage (message, sizeof (message)) > 0) 
		{
			intellon.ImportHeader (message);
			if (intellon.IsMessageType (0, VS_SW_VER|MMTYPE_CNF)) 
			{
				this->ImportPeerAddress (intellon.HostAddress ());
			}
		}
	}
	return (*this);
}

/*====================================================================*
 *
 *   CPLChannel & init (unsigned timeout)
 *
 *   initialize class members; set the channel Ethernet header to
 *   the default Intellon header;
 *
 *--------------------------------------------------------------------*/

CPLChannel & CPLChannel::init (unsigned timeout) 

{
	ointellon intellon;
	this->mfd = -1;
	this->mtimeout = timeout;
	this->ImportPeerAddress (intellon.PeerAddress ());
	this->ImportHostAddress (intellon.HostAddress ());
	this->SetProtocol (intellon.Protocol ());
	return (*this);
}

/*====================================================================*
 *
 *   CPLChannel (unsigned ifindex, unsigned timeout)
 *
 *--------------------------------------------------------------------*/

CPLChannel::CPLChannel (unsigned ifindex, unsigned timeout): ointerface (ifindex) 

{
	this->init (timeout);
	this->open ();
	this->link ();
	return;
}

/*====================================================================*
 *
 *   CPLChannel (char const * ifname, unsigned vTimeout)
 *
 *--------------------------------------------------------------------*/

CPLChannel::CPLChannel (char const * ifname, unsigned vTimeout): ointerface (ifname) 

{
	this->init (vTimeout);
	this->open ();
	this->link ();
	return;
}

/*====================================================================*
 *
 *   ~CPLChannel ()
 *
 *   free sockets and descriptors;
 *
 *--------------------------------------------------------------------*/

CPLChannel::~CPLChannel () 

{

#if defined (__linux__)

	::close (this->mfd);

#elif defined (__APPLE__) || defined (__OpenBSD__)

	::close (this->mfd);

#elif defined (WINPCAP) || defined (LIBPCAP)

	pcap_close (this->msocket);

#else
#error "Unknown Environment"
#endif

	return;
}

/*====================================================================*
 *   end definition;
 *--------------------------------------------------------------------*/

#endif