// This file is part of eMule 
//
// Written by moosetea, enjoy
//
//This program is free software; you can redistribute it and/or
//modify it under the terms of the GNU General Public License
//as published by the Free Software Foundation; either
//version 2 of the License, or (at your option) any later version.
//
//This program is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU General Public License for more details.
//
//You should have received a copy of the GNU General Public License
//along with this program; if not, write to the Free Software
//Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//
// This allows lan clients to add each other as peers, it could be
// very buggy and needs alot more work.
//
// Thanks to codeproject for help with the multicast code, 
// which works but is a bit of a shambles.

// moosetea: LanCast

// Base includes needed by this class (and every class)
#include "StdAfx.h"
#include "emule.h"
#include "Preferences.h"
#include "LanCast.h"
#include "packets.h"
#include "ED2KLink.h"
#include "otherfunctions.h"
#include "KnownFile.h"
#include "UpDownClient.h"
#include "DownloadQueue.h"
#include "SharedFileList.h"
#include "SafeFile.h"
#include "Log.h" //added by sivka [for AddLogLine()]
#include "Exceptions.h" //added by sivka [for CATCH_DFLT_EXCEPTIONS]
#include "Optimizer\memboost.h" //modified by sivka [eMulePlus: -Optimizer-]

// This is the multicast port and must be the same for all lancast using clients on the same LAN
//modified by sivka - use variable ports
//#define LANCAST_PORT	5000

CLanCast::CLanCast()
{
	bStarted = false;
	if( thePrefs.bLanCast ) //modified by sivka
		Start();
}

bool CLanCast::IsLanIP(UINT dwUserIP)
{
	if( thePrefs.bLanCast ) //modified by sivka
		Start();
	else
		Stop();

	if (!bStarted)
		return false;

	for (POSITION pos = interface_list.GetHeadPosition();pos;) {
		multicast_interface mcif = interface_list.GetNext(pos);
		if ((mcif.address & mcif.subnet) == (dwUserIP & mcif.subnet))
			return true;
	}

	return false;
}

bool CLanCast::SendPacket(Packet* packet)
{
	char* pcSendBuffer = new char[packet->size+2];

	
	//modified by sivka [eMulePlus: -Optimizer-]
	memcpy_optimized(pcSendBuffer,packet->GetUDPHeader(),2); // Standard UDP Header
	memcpy_optimized(pcSendBuffer+2,packet->pBuffer,packet->size); // The packet Data

	// Send the LanCast packet
	if(m_SendSocket.SendTo(pcSendBuffer, packet->size+2, (SOCKADDR*)&m_saHostGroup, sizeof(m_saHostGroup), 0) == SOCKET_ERROR)
	{
		delete[] pcSendBuffer;
		return FALSE;
	}
	else
	{
		delete[] pcSendBuffer;
		return TRUE;
	}
}

void CLanCast::BroadcastHash(CKnownFile* cur_file)
{
	if( thePrefs.bLanCast ) //modified by sivka
		Start();
	else
		Stop();

	if (bStarted){
		CSafeMemFile data_out(4+2+16);  //mofified by sivka
	
		for (POSITION pos = interface_list.GetHeadPosition();pos;)
		{
			data_out.WriteUInt32(interface_list.GetNext(pos).address); //mofified by sivka
			data_out.WriteUInt16(thePrefs.GetPort()); //mofified by sivka
			data_out.WriteHash16(cur_file->GetFileHash()); //mofified by sivka

			Packet* packet = new Packet(&data_out,OP_LANCASTPROT);
			packet->opcode = OP_HASH;

			SendPacket(packet);
			delete packet;
		}
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void CLanCast::ReceiveHash(char* pachPacket, UINT nSize)
{
	try { //added by sivka
		CSafeMemFile offeredfiles((BYTE*)pachPacket,nSize);
		
		// Read Hash Data
		UINT dwID = offeredfiles.ReadUInt32(); //modified by sivka
		uint16 nPort = offeredfiles.ReadUInt16(); //modified by sivka
		uchar filehash[16];
		offeredfiles.ReadHash16(filehash); //modified by sivka
	
		 // Debug Info for Devs - The following lines should be removed in release
		CString buffer = EncodeBase16(filehash, 16);
		if( thePrefs.GetVerbose() )
			AddDebugLogLine(false,_T("LANCAST: Just Received %s from %s:%d"),buffer,inet_ntoa(*(in_addr*)&dwID),nPort); //modified by sivka
	
		CPartFile* file = theApp.downloadqueue->GetFileByID((uchar*)filehash);
		if (file) 
		{
			// Create a new Updown client for this source (with no server info), userid is the LAN ip address
			CUpDownClient* newsource = new CUpDownClient(file,nPort,dwID,0,0,true);
	
			// Add this LAN client to the download queue, this also will check for duplicate sockets and "merge" them
			theApp.downloadqueue->CheckAndAddSource(file,newsource);
		}
	}
	catch(CFileException* error) //added by sivka
	{
		error->Delete();
		if (thePrefs.GetVerbose())
			AddDebugLogLine(false, _T("INVALID PACKET ==> CLanCast::ReceiveHash()"));
	}
	catch(CMemoryException* error) //added by sivka
	{
		error->Delete();
		if (thePrefs.GetVerbose())
			AddDebugLogLine(false, _T("MEMORY EXCEPTION ==> CLanCast::ReceiveHash()"));
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
CLanCast::~CLanCast()
{
	Stop();
}

void CLanCast::OnReceive(int nErrorCode)
{
	if(nErrorCode!=0) return;
	
	char buffer[5000];
	CString addressbuffer;
	UINT port;

	UINT length = ReceiveFrom(buffer,5000,addressbuffer,port);
	if(length == SOCKET_ERROR)
		return;

	if( !thePrefs.bLanCast ) //modified by sivka
		return;

	if((uint8)buffer[0] != OP_LANCASTPROT)
		return;

	// If this isnt a packet from ourselves
	UINT addr = inet_addr(CStringA(addressbuffer));
	for(POSITION pos=interface_list.GetHeadPosition();pos!=NULL;)
		if(interface_list.GetNext(pos).address==addr)
			return;

	ProcessPacket(buffer+2,length-2,buffer[1]);
}

bool CLanCast::ProcessPacket(char* packet, UINT size, uint8 opcode){
	try{
		switch(opcode){
			case OP_HASH :{	
				ReceiveHash(packet,size);
				break;
			}
			default:
				AddLogLine(false,GetResString(IDS_LANCAST_UNK_OPCODE)); //modified by sivka
				return false;
		}

		return true;
	}
	catch(...){
		AddLogLine(false,GetResString(IDS_LANCAST_ERR_MULITC)); //modified by sivka
		return false;
	}
}

//modified by sivka - improved
void CALLBACK CLanCast::UDPTimerProc(HWND/* hwnd*/,UINT/* uMsg*/,UINT_PTR/* idEvent*/,DWORD/* dwTime*/)
{
	// NOTE: Always handle all type of MFC exceptions in TimerProcs - otherwise we'll get mem leaks
	try
	{
		int count=theApp.sharedfiles->GetCount();
		static int current = 0;
		if(!count) return;
		for(int i=0;i<32;i++){
			current = (current+1) % count;
			CKnownFile *file = theApp.sharedfiles->GetFileByIndex(i);
			if(file) theApp.lancast->BroadcastHash(file);
		}
	}
	CATCH_DFLT_EXCEPTIONS(_T("CLanCast::UDPTimerProc"))
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////
// CLanCast Private member functions - MultiCast Network Code

BOOL CLanCast::CreateReceivingSocket(LPCTSTR strGroupIP, UINT nGroupPort)
{
	// Create socket for receiving packets from multicast group
	if(!Create(nGroupPort, SOCK_DGRAM, FD_READ))
		return FALSE;
	BOOL bMultipleApps = TRUE;		// allow reuse of local port if needed
	SetSockOpt(SO_REUSEADDR, (void*)&bMultipleApps, sizeof(BOOL), SOL_SOCKET);

	// Fill m_saHostGroup_in for sending datagrams
	//modified by sivka [eMulePlus: -Optimizer-]
	memzero_optimized(&m_saHostGroup, sizeof(m_saHostGroup));
	m_saHostGroup.sin_family = AF_INET;
	m_saHostGroup.sin_addr.s_addr = inet_addr(CStringA(strGroupIP));
	m_saHostGroup.sin_port = htons((USHORT)nGroupPort);

	// Join the multicast group on all interfaces
	ip_mreq mrMReq;
	mrMReq.imr_multiaddr.s_addr = inet_addr(CStringA(strGroupIP));	// group addr
	for (POSITION pos = interface_list.GetHeadPosition();pos;) {
		POSITION old_pos = pos;
		mrMReq.imr_interface.s_addr = interface_list.GetNext(pos).address;	// current interface
		if (SetSockOpt(IP_ADD_MEMBERSHIP, (char FAR *)&mrMReq, sizeof(mrMReq), IPPROTO_IP) == 0)
			interface_list.RemoveAt(old_pos);
	}

	return TRUE;
}

BOOL CLanCast::CreateSendingSocket(UINT nTTL, BOOL bLoopBack)
{
	if(!m_SendSocket.Create(0, SOCK_DGRAM, 0))		// Create an unconnected UDP socket
		return FALSE;

	if(m_SendSocket.SetSockOpt(IP_MULTICAST_TTL, &nTTL, sizeof(int), IPPROTO_IP) == 0)
		AddLogLine(false,GetResString(IDS_LANCAST_ERR_TTL)); //modified by sivka

	SetLoopBack(bLoopBack);

	return TRUE;
}

void CLanCast::Start()
{
	if (!bStarted){
		// Try and join the multicast group - 224.0.0.1, using the prefs port and no loopback
		if( JoinGroup(_T("224.0.0.1"), thePrefs.uiLanCastPort, 50, false) ) //modified by sivka
			AddLogLine(false,GetResString(IDS_LANCAST_JOIN_SUCCEEDED), thePrefs.uiLanCastPort); //modified by sivka
		else
			AddLogLine(false,GetResString(IDS_LANCAST_JOIN_FAILED), thePrefs.uiLanCastPort); //modified by sivka
	}
}

void CLanCast::Stop()
{
	if (bStarted){
		if( LeaveGroup() ) //modified by sivka
			AddLogLine(false,GetResString(IDS_LANCAST_LEAVE_SUCCEEDED), thePrefs.uiLanCastPort); //modified by sivka
		else
			AddLogLine(false,GetResString(IDS_LANCAST_LEAVE_FAILED), thePrefs.uiLanCastPort); //modified by sivka
	}
}


BOOL CLanCast::JoinGroup(CString GroupIP, UINT nGroupPort, UINT nTTL, BOOL bLoopback)
{
	bStarted = false;

	// enumerate all interfaces
	PMIB_IPADDRTABLE pIPAddrTable;
	DWORD dwSize = 0;

	// get address table size
	if (GetIpAddrTable(NULL, &dwSize, 0) != ERROR_INSUFFICIENT_BUFFER)
		return FALSE;

	// get address table
	pIPAddrTable = (PMIB_IPADDRTABLE)new char[dwSize];
	if (GetIpAddrTable(pIPAddrTable, &dwSize, 0) != NO_ERROR) {
		delete pIPAddrTable;
		return FALSE;
	}

	UINT localhost = inet_addr("127.0.0.1");
	UINT publicsubnet = inet_addr("255.255.255.255");
	UINT emptysubnet = inet_addr("0.0.0.0");

	// add interfaces
	interface_list.RemoveAll();
	for (DWORD i = 0; i < pIPAddrTable->dwNumEntries; i++) {
		multicast_interface mcif;
		if ((mcif.address = pIPAddrTable->table[i].dwAddr) == localhost)
			continue;
		if ((mcif.subnet = pIPAddrTable->table[i].dwMask) == publicsubnet)
			continue;
		if (mcif.subnet == emptysubnet)
			continue;
		interface_list.AddTail(mcif);
		AddLogLine(false,_T("IP=%s <-> SUBNET=%s"), ipstr(mcif.address), ipstr(mcif.subnet)); //added by sivka
	}

	delete pIPAddrTable;

	if(!CreateReceivingSocket(GroupIP, nGroupPort))		// Create Socket for receiving and join the host group
		return FALSE;
	if(!CreateSendingSocket(nTTL, bLoopback))			// Create Socket for sending
		return FALSE;

	udp_timer = ::SetTimer(NULL, NULL, 15000, CLanCast::UDPTimerProc);
	bStarted = true;

	return TRUE;
}

BOOL CLanCast::LeaveGroup()
{
	bStarted = false;
	::KillTimer(NULL, udp_timer);

	// Close receving socket
	ip_mreq mrMReq;
	mrMReq.imr_multiaddr = m_saHostGroup.sin_addr;	// group addr
	for (POSITION pos = interface_list.GetHeadPosition();pos;) {
		mrMReq.imr_interface.s_addr = interface_list.GetNext(pos).address;	// current interface
		SetSockOpt(IP_DROP_MEMBERSHIP, (char FAR *)&mrMReq, sizeof(mrMReq), IPPROTO_IP);
	}
	Close();

	// Close sending socket
	m_SendSocket.Close();

	return TRUE;
}

void CLanCast::SetLoopBack(BOOL bLoop)
{
	// Set LOOPBACK option to TRUE OR FALSE according to IsLoop parameter
	int nLoopBack = (int)bLoop;

	// Try to manually set the loopback, ie tell dozer to ignlore packets from itself (if setloopback is false)
	m_SendSocket.SetSockOpt(IP_MULTICAST_LOOP, &nLoopBack, sizeof(int), IPPROTO_IP);
	SetSockOpt(IP_MULTICAST_LOOP, &nLoopBack, sizeof(int), IPPROTO_IP);
}
