/*
 * PtokaX - hub server for Direct Connect peer to peer network.

 * Copyright (C) 2002-2005  Ptaczek, Ptaczek at PtokaX dot org
 * Copyright (C) 2004-2022  Petr Kozelka, PPK at PtokaX dot org

 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3
 * as published by the Free Software Foundation.

 * 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, see <http://www.gnu.org/licenses/>.
 */

//---------------------------------------------------------------------------
#include "stdinc.h"
//---------------------------------------------------------------------------
#include "UdpDebug.h"
//---------------------------------------------------------------------------
#include "LanguageManager.h"
#include "ServerManager.h"
#include "SettingManager.h"
#include "User.h"
#include "utility.h"
//---------------------------------------------------------------------------
#ifdef _WIN32
#pragma hdrstop
#else
#include <string>
#include <syslog.h>
#endif
//---------------------------------------------------------------------------
UdpDebug * UdpDebug::m_Ptr = nullptr;
bool g_isUseSyslog = false;
bool g_isUseLog = false;
//---------------------------------------------------------------------------

UdpDebug::UdpDbgItem::UdpDbgItem() : m_pPrev(NULL), m_pNext(NULL),
#ifdef _WIN32
	s(INVALID_SOCKET),
#else
	s(-1),
#endif
	sas_len(0), ui32Hash(0), bIsScript(false), bAllData(true)
{
	memset(&sas_to, 0, sizeof(sockaddr_storage));
}
//---------------------------------------------------------------------------

UdpDebug::UdpDbgItem::~UdpDbgItem()
{
	safe_closesocket(s);
}
//---------------------------------------------------------------------------

UdpDebug::UdpDebug() : sDebugBuffer(NULL), sDebugHead(NULL), pDbgItemList(NULL)
{
	// ...
}
//---------------------------------------------------------------------------

UdpDebug::~UdpDebug()
{
	free(sDebugBuffer);

	UdpDbgItem * cur = NULL,
	             * next = pDbgItemList;

	while (next != NULL)
	{
		cur = next;
		next = cur->m_pNext;

		delete cur;
	}
}
//---------------------------------------------------------------------------

void UdpDebug::Broadcast(const char * msg, const size_t szMsgLen) const
{
#ifndef _WIN32
	if (g_isUseSyslog)
	{
		std::string l_str(msg, szMsgLen);
		//printf("%s\r", l_str.c_str());
		syslog(LOG_NOTICE, "%s", l_str.c_str());
	}
#endif

	if (pDbgItemList == NULL)
	{
		return;
	}

	((uint16_t *)sDebugBuffer)[1] = (uint16_t)szMsgLen;
	memcpy(sDebugHead, msg, szMsgLen);
	size_t szLen = (sDebugHead - sDebugBuffer) + szMsgLen;

	UdpDbgItem * pCur = NULL,
	             * m_pNext = pDbgItemList;

	while (m_pNext != NULL && m_pNext->bAllData == true)
	{
		pCur = m_pNext;
		m_pNext = pCur->m_pNext;
#ifdef _WIN32
		sendto(pCur->s, sDebugBuffer, (int)szLen, 0, (struct sockaddr *)&pCur->sas_to, pCur->sas_len);
#else
		sendto(pCur->s, sDebugBuffer, szLen, 0, (struct sockaddr *)&pCur->sas_to, pCur->sas_len);
#endif
		ServerManager::m_ui64BytesSent += szLen;
	}
}
//---------------------------------------------------------------------------

void UdpDebug::BroadcastFormat(const char * sFormatMsg, ...) const
{

#ifndef _WIN32
	if (g_isUseSyslog)
	{
		std::string l_str;
		l_str.resize(65535);
		va_list vlArgs;
		va_start(vlArgs, sFormatMsg);
		vsnprintf(&l_str[0], l_str.size(), sFormatMsg, vlArgs);
		va_end(vlArgs);
		syslog(LOG_NOTICE, "%s", l_str.c_str());
	}
#endif

	if (pDbgItemList == NULL)
	{
		return;
	}
	va_list vlArgs;
	va_start(vlArgs, sFormatMsg);

	const int iRet = vsprintf(sDebugHead, sFormatMsg, vlArgs);

	va_end(vlArgs);

	if (iRet < 0 || iRet >= 65535)
	{
		AppendDebugLogFormat("[ERR] vsprintf wrong value %d in UdpDebug::Broadcast\n", iRet);

		return;
	}

	((uint16_t *)sDebugBuffer)[1] = (uint16_t)iRet;
	size_t szLen = (sDebugHead - sDebugBuffer) + iRet;

	UdpDbgItem * pCur = NULL,
	             * m_pNext = pDbgItemList;

	while (m_pNext != NULL && m_pNext->bAllData == true)
	{
		pCur = m_pNext;
		m_pNext = pCur->m_pNext;
#ifdef _WIN32
		sendto(pCur->s, sDebugBuffer, (int)szLen, 0, (struct sockaddr *)&pCur->sas_to, pCur->sas_len);
#else
		sendto(pCur->s, sDebugBuffer, szLen, 0, (struct sockaddr *)&pCur->sas_to, pCur->sas_len);
#endif
		ServerManager::m_ui64BytesSent += szLen;
	}
}
//---------------------------------------------------------------------------

void UdpDebug::CreateBuffer()
{
	if (sDebugBuffer != NULL)
	{
		return;
	}

	sDebugBuffer = (char *)malloc(4 + 256 + 65535);
	if (sDebugBuffer == NULL)
	{
		AppendDebugLog("%s - [MEM] Cannot allocate 4+256+65535 bytes for sDebugBuffer in UdpDebug::CreateBuffer\n");

		exit(EXIT_FAILURE);
	}

	UpdateHubName();
}
//---------------------------------------------------------------------------

bool UdpDebug::New(User * pUser, const uint16_t ui16Port)
{
	UdpDbgItem * pNewDbg = new (std::nothrow) UdpDbgItem();
	if (pNewDbg == NULL)
	{
		AppendDebugLog("%s - [MEM] Cannot allocate pNewDbg in UdpDebug::New\n");
		return false;
	}
	if (pUser->m_sNick)
	{
		pNewDbg->m_sNick = pUser->m_sNick;
	}

	pNewDbg->ui32Hash = pUser->m_ui32NickHash;

	struct in6_addr i6addr;
	memcpy(&i6addr, &pUser->m_ui128IpHash, 16);

	bool bIPv6 = (IN6_IS_ADDR_V4MAPPED(&i6addr) == 0);

	if (bIPv6 == true)
	{
		((struct sockaddr_in6 *)&pNewDbg->sas_to)->sin6_family = AF_INET6;
		((struct sockaddr_in6 *)&pNewDbg->sas_to)->sin6_port = htons(ui16Port);
		memcpy(((struct sockaddr_in6 *)&pNewDbg->sas_to)->sin6_addr.s6_addr, pUser->m_ui128IpHash, 16);
		pNewDbg->sas_len = sizeof(struct sockaddr_in6);
	}
	else
	{
		((struct sockaddr_in *)&pNewDbg->sas_to)->sin_family = AF_INET;
		((struct sockaddr_in *)&pNewDbg->sas_to)->sin_port = htons(ui16Port);
		((struct sockaddr_in *)&pNewDbg->sas_to)->sin_addr.s_addr = inet_addr(pUser->m_sIP);
		pNewDbg->sas_len = sizeof(struct sockaddr_in);
	}

	pNewDbg->s = socket((bIPv6 == true ? AF_INET6 : AF_INET), SOCK_DGRAM, IPPROTO_UDP);
#ifdef _WIN32
	if (pNewDbg->s == INVALID_SOCKET)
	{
		int iErr = WSAGetLastError();
#else
	if (pNewDbg->s == -1)
	{
#endif
		pUser->SendFormat("UdpDebug::New1", true, "*** [ERR] %s: %s (%d).|", LanguageManager::m_Ptr->m_sTexts[LAN_UDP_SCK_CREATE_ERR],
#ifdef _WIN32
		                  WSErrorStr(iErr), iErr);
#else
		                  ErrnoStr(errno), errno);
#endif
		delete pNewDbg;
		return false;
	}

	// set non-blocking
#ifdef _WIN32
	uint32_t block = 1;
	if (SOCKET_ERROR == ioctlsocket(pNewDbg->s, FIONBIO, (unsigned long *)&block))
	{
		int iErr = WSAGetLastError();
#else
	int oldFlag = fcntl(pNewDbg->s, F_GETFL, 0);
	if (fcntl(pNewDbg->s, F_SETFL, oldFlag | O_NONBLOCK) == -1)
	{
#endif
		pUser->SendFormat("UdpDebug::New2", true, "*** [ERR] %s: %s (%d).|", LanguageManager::m_Ptr->m_sTexts[LAN_UDP_NON_BLOCK_FAIL],
#ifdef _WIN32
		                  WSErrorStr(iErr), iErr);
#else
		                  ErrnoStr(errno), errno);
#endif
		delete pNewDbg;
		return false;
	}

	pNewDbg->m_pPrev = nullptr;
	pNewDbg->m_pNext = nullptr;

	if (pDbgItemList == NULL)
	{
		CreateBuffer();
		pDbgItemList = pNewDbg;
	}
	else
	{
		pDbgItemList->m_pPrev = pNewDbg;
		pNewDbg->m_pNext = pDbgItemList;
		pDbgItemList = pNewDbg;
	}

	pNewDbg->bIsScript = false;

	int iLen = sprintf(sDebugHead, "[HUB] Subscribed, users online: %u", ServerManager::m_ui32Logged);
	if (iLen < 0 || iLen > 65535)
	{
		AppendDebugLogFormat("[ERR] sprintf wrong value %d in UdpDebug::New\n", iLen);

		return true;
	}

	// create packet
	((uint16_t *)sDebugBuffer)[1] = (uint16_t)iLen;
	size_t szLen = (sDebugHead - sDebugBuffer) + iLen;
#ifdef _WIN32
	sendto(pNewDbg->s, sDebugBuffer, (int)szLen, 0, (struct sockaddr *)&pNewDbg->sas_to, pNewDbg->sas_len);
#else
	sendto(pNewDbg->s, sDebugBuffer, szLen, 0, (struct sockaddr *)&pNewDbg->sas_to, pNewDbg->sas_len);
#endif
	ServerManager::m_ui64BytesSent += szLen;

	return true;
}
//---------------------------------------------------------------------------

bool UdpDebug::New(const char * sIP, const uint16_t ui16Port, const bool bAllData, const char * sScriptName)
{
	UdpDbgItem * pNewDbg = new (std::nothrow) UdpDbgItem();
	if (pNewDbg == NULL)
	{
		AppendDebugLog("%s - [MEM] Cannot allocate pNewDbg in UdpDebug::New\n");
		return false;
	}

	// initialize dbg item
	pNewDbg->m_sNick = sScriptName;

	pNewDbg->ui32Hash = 0;

	uint8_t ui128IP[16];
	HashIP(sIP, ui128IP);

	struct in6_addr i6addr;
	memcpy(&i6addr, &ui128IP, 16);

	bool bIPv6 = (IN6_IS_ADDR_V4MAPPED(&i6addr) == 0);

	if (bIPv6 == true)
	{
		((struct sockaddr_in6 *)&pNewDbg->sas_to)->sin6_family = AF_INET6;
		((struct sockaddr_in6 *)&pNewDbg->sas_to)->sin6_port = htons(ui16Port);
		memcpy(((struct sockaddr_in6 *)&pNewDbg->sas_to)->sin6_addr.s6_addr, ui128IP, 16);
		pNewDbg->sas_len = sizeof(struct sockaddr_in6);
	}
	else
	{
		((struct sockaddr_in *)&pNewDbg->sas_to)->sin_family = AF_INET;
		((struct sockaddr_in *)&pNewDbg->sas_to)->sin_port = htons(ui16Port);
		memcpy(&((struct sockaddr_in *)&pNewDbg->sas_to)->sin_addr.s_addr, ui128IP + 12, 4);
		pNewDbg->sas_len = sizeof(struct sockaddr_in);
	}

	pNewDbg->s = socket((bIPv6 == true ? AF_INET6 : AF_INET), SOCK_DGRAM, IPPROTO_UDP);

#ifdef _WIN32
	if (pNewDbg->s == INVALID_SOCKET)
	{
#else
	if (pNewDbg->s == -1)
	{
#endif
		delete pNewDbg;
		return false;
	}

	// set non-blocking
#ifdef _WIN32
	uint32_t block = 1;
	if (SOCKET_ERROR == ioctlsocket(pNewDbg->s, FIONBIO, (unsigned long *)&block))
	{
#else
	int oldFlag = fcntl(pNewDbg->s, F_GETFL, 0);
	if (fcntl(pNewDbg->s, F_SETFL, oldFlag | O_NONBLOCK) == -1)
	{
#endif
		delete pNewDbg;
		return false;
	}

	pNewDbg->m_pPrev = nullptr;
	pNewDbg->m_pNext = nullptr;

	if (pDbgItemList == NULL)
	{
		CreateBuffer();
		pDbgItemList = pNewDbg;
	}
	else
	{
		pDbgItemList->m_pPrev = pNewDbg;
		pNewDbg->m_pNext = pDbgItemList;
		pDbgItemList = pNewDbg;
	}

	pNewDbg->bIsScript = true;
	pNewDbg->bAllData = bAllData;

	int iLen = sprintf(sDebugHead, "[HUB] Subscribed, users online: %u", ServerManager::m_ui32Logged);
	if (iLen < 0 || iLen > 65535)
	{
		AppendDebugLogFormat("[ERR] sprintf wrong value %d in UdpDebug::New2\n", iLen);

		return true;
	}

	// create packet
	((uint16_t *)sDebugBuffer)[1] = (uint16_t)iLen;
	size_t szLen = (sDebugHead - sDebugBuffer) + iLen;
#ifdef _WIN32
	sendto(pNewDbg->s, sDebugBuffer, (int)szLen, 0, (struct sockaddr *)&pNewDbg->sas_to, pNewDbg->sas_len);
#else
	sendto(pNewDbg->s, sDebugBuffer, szLen, 0, (struct sockaddr *)&pNewDbg->sas_to, pNewDbg->sas_len);
#endif
	ServerManager::m_ui64BytesSent += szLen;

	return true;
}
//---------------------------------------------------------------------------

void UdpDebug::DeleteBuffer()
{
	safe_free(sDebugBuffer);
	sDebugHead = nullptr;
}
//---------------------------------------------------------------------------

bool UdpDebug::Remove(User * pUser)
{
	UdpDbgItem * pCur = NULL,
	             * m_pNext = pDbgItemList;

	while (m_pNext != NULL)
	{
		pCur = m_pNext;
		m_pNext = pCur->m_pNext;

		if (pCur->bIsScript == false && pCur->ui32Hash == pUser->m_ui32NickHash && strcasecmp(pCur->m_sNick.c_str(), pUser->m_sNick) == 0)
		{
			if (pCur->m_pPrev == NULL)
			{
				if (pCur->m_pNext == NULL)
				{
					pDbgItemList = nullptr;
					DeleteBuffer();
				}
				else
				{
					pCur->m_pNext->m_pPrev = nullptr;
					pDbgItemList = pCur->m_pNext;
				}
			}
			else if (pCur->m_pNext == NULL)
			{
				pCur->m_pPrev->m_pNext = nullptr;
			}
			else
			{
				pCur->m_pPrev->m_pNext = pCur->m_pNext;
				pCur->m_pNext->m_pPrev = pCur->m_pPrev;
			}

			delete pCur;
			return true;
		}
	}
	return false;
}
//---------------------------------------------------------------------------

void UdpDebug::Remove(const char * sScriptName)
{
	UdpDbgItem * pCur = NULL,
	             * m_pNext = pDbgItemList;

	while (m_pNext != NULL)
	{
		pCur = m_pNext;
		m_pNext = pCur->m_pNext;

		if (pCur->bIsScript == true && strcasecmp(pCur->m_sNick.c_str(), sScriptName) == 0)
		{
			if (pCur->m_pPrev == NULL)
			{
				if (pCur->m_pNext == NULL)
				{
					pDbgItemList = nullptr;
					DeleteBuffer();
				}
				else
				{
					pCur->m_pNext->m_pPrev = nullptr;
					pDbgItemList = pCur->m_pNext;
				}
			}
			else if (pCur->m_pNext == NULL)
			{
				pCur->m_pPrev->m_pNext = nullptr;
			}
			else
			{
				pCur->m_pPrev->m_pNext = pCur->m_pNext;
				pCur->m_pNext->m_pPrev = pCur->m_pPrev;
			}

			delete pCur;
			return;
		}
	}
}
//---------------------------------------------------------------------------

bool UdpDebug::CheckUdpSub(User * pUser, bool bSndMess/* = false*/) const
{
	UdpDbgItem * pCur = NULL,
	             * m_pNext = pDbgItemList;

	while (m_pNext != NULL)
	{
		pCur = m_pNext;
		m_pNext = pCur->m_pNext;

		if (pCur->bIsScript == false && pCur->ui32Hash == pUser->m_ui32NickHash && strcasecmp(pCur->m_sNick.c_str(), pUser->m_sNick) == 0)
		{
			if (bSndMess == true)
			{
				pUser->SendFormat("UdpDebug::CheckUdpSub", true, "<%s> *** %s %hu. %s.|", SettingManager::m_Ptr->m_sPreTexts[SettingManager::SETPRETXT_HUB_SEC], LanguageManager::m_Ptr->m_sTexts[LAN_YOU_SUBSCRIBED_UDP_DBG],
				                  ntohs(pCur->sas_to.ss_family == AF_INET6 ? ((struct sockaddr_in6 *)&pCur->sas_to)->sin6_port : ((struct sockaddr_in *)&pCur->sas_to)->sin_port), LanguageManager::m_Ptr->m_sTexts[LAN_TO_UNSUB_UDP_DBG]);
			}

			return true;
		}
	}
	return false;
}
//---------------------------------------------------------------------------

void UdpDebug::Send(const char * sScriptName, const char * sMessage, const size_t szMsgLen) const
{
	if (pDbgItemList == NULL)
	{
		return;
	}

	UdpDbgItem * pCur = NULL,
	             * m_pNext = pDbgItemList;

	while (m_pNext != NULL)
	{
		pCur = m_pNext;
		m_pNext = pCur->m_pNext;

		if (pCur->bIsScript == true && strcasecmp(pCur->m_sNick.c_str(), sScriptName) == 0)
		{
			// create packet
			((uint16_t *)sDebugBuffer)[1] = (uint16_t)szMsgLen;
			memcpy(sDebugHead, sMessage, szMsgLen);
			size_t szLen = (sDebugHead - sDebugBuffer) + szMsgLen;

#ifdef _WIN32
			sendto(pCur->s, sDebugBuffer, (int)szLen, 0, (struct sockaddr *)&pCur->sas_to, pCur->sas_len);
#else
			sendto(pCur->s, sDebugBuffer, szLen, 0, (struct sockaddr *)&pCur->sas_to, pCur->sas_len);
#endif
			ServerManager::m_ui64BytesSent += szLen;

			return;
		}
	}
}
//---------------------------------------------------------------------------

void UdpDebug::Cleanup()
{
	UdpDbgItem * pCur = NULL,
	             * m_pNext = pDbgItemList;

	while (m_pNext != NULL)
	{
		pCur = m_pNext;
		m_pNext = pCur->m_pNext;

		delete pCur;
	}

	pDbgItemList = nullptr;
}
//---------------------------------------------------------------------------

void UdpDebug::UpdateHubName()
{
	if (sDebugBuffer == NULL)
	{
		return;
	}

	((uint16_t *)sDebugBuffer)[0] = (uint16_t)SettingManager::m_Ptr->m_ui16TextsLens[SETTXT_HUB_NAME];
	memcpy(sDebugBuffer + 4, SettingManager::m_Ptr->m_sTexts[SETTXT_HUB_NAME], SettingManager::m_Ptr->m_ui16TextsLens[SETTXT_HUB_NAME]);

	sDebugHead = sDebugBuffer + 4 + SettingManager::m_Ptr->m_ui16TextsLens[SETTXT_HUB_NAME];
}
//---------------------------------------------------------------------------

V1042 This file is marked with copyleft license, which requires you to open the derived source code.

V641 The size of the '& pNewDbg->sas_to' buffer is not a multiple of the element size of the type 'struct sockaddr_in6'.

V641 The size of the '& pNewDbg->sas_to' buffer is not a multiple of the element size of the type 'struct sockaddr_in6'.

V641 The size of the '& pCur->sas_to' buffer is not a multiple of the element size of the type 'struct sockaddr_in6'.