/*
Copyright (C) 2012-24 Miranda NG team (https://miranda-ng.org)
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 version 2
of the License.
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 .
*/
#include "stdafx.h"
void __cdecl CSteamProto::ServerThread(void *)
{
	// load web socket servers first if needed
	int iTimeDiff = db_get_dw(0, STEAM_MODULE, DBKEY_HOSTS_DATE);
	int iHostCount = db_get_dw(0, STEAM_MODULE, DBKEY_HOSTS_COUNT);
	if (!iHostCount || time(0) - iTimeDiff > 3600 * 24 * 7) { // once a week
		if (!SendRequest(new GetHostsRequest(), &CSteamProto::OnGotHosts)) {
			LoginFailed();
			return;
		}
	}
	srand(time(0));
	m_ws = nullptr;
	CMStringA szHost;
	do {
		szHost.Format("Host%d", rand() % iHostCount);
		szHost = db_get_sm(0, STEAM_MODULE, szHost);
		szHost.Insert(0, "wss://");
		szHost += "/cmsocket/";
	}
	while (ServerThreadStub(szHost));
}
bool CSteamProto::ServerThreadStub(const char *szHost)
{
	WebSocket ws(this);
	NLHR_PTR pReply(ws.connect(m_hNetlibUser, szHost));
	if (pReply == nullptr) {
		debugLogA("websocket connection failed");
		return false;
	}
	if (pReply->resultCode != 101) {
		debugLogA("websocket connection failed: %d", pReply->resultCode);
		return false;
	}
	m_ws = &ws;
	debugLogA("Websocket connection succeeded");
	// Send init packets
	Login();
	ws.run();
	m_ws = nullptr;
	return false;
}
/////////////////////////////////////////////////////////////////////////////////////////
void WebSocket::process(const uint8_t *buf, size_t cbLen)
{
	uint32_t dwSign = *(uint32_t *)buf;
	EMsg msgType = (EMsg)(dwSign & ~STEAM_PROTOCOL_MASK);
	// now process the body
	if (msgType == EMsg::Multi) {
		buf += 8; cbLen -= 8;
		p->ProcessMulti(buf, cbLen);
	}
	else p->ProcessMessage(buf, cbLen);
}
void CSteamProto::ProcessMulti(const uint8_t *buf, size_t cbLen)
{
	proto::MsgMulti pMulti(buf, cbLen);
	if (pMulti == nullptr) {
		debugLogA("Unable to decode multi message, exiting");
		return;
	}
	debugLogA("processing %s multi message of size %d", (pMulti->size_unzipped) ? "zipped" : "normal", pMulti->message_body.len);
	
	ptrA tmp;
	if (pMulti->size_unzipped) {
		tmp = (char *)mir_alloc(pMulti->size_unzipped + 1);
		cbLen = FreeImage_ZLibGUnzip((uint8_t*)tmp.get(), pMulti->size_unzipped, pMulti->message_body.data, (unsigned)pMulti->message_body.len);
		if (!cbLen) {
			debugLogA("Unable to unzip multi message, exiting");
			return;
		}
		buf = (const uint8_t *)tmp.get();
	}
	else {
		buf = pMulti->message_body.data;
		cbLen = pMulti->message_body.len;
	}
	while ((int)cbLen > 0) {
		uint32_t cbPacketLen = *(uint32_t *)buf; buf += sizeof(uint32_t); cbLen -= sizeof(uint32_t);
		ProcessMessage(buf, cbPacketLen);
		buf += cbPacketLen; cbLen -= cbPacketLen;
	}
}
void CSteamProto::ProcessMessage(const uint8_t *buf, size_t cbLen)
{
	uint32_t dwSign = *(uint32_t *)buf; buf += sizeof(uint32_t); cbLen -= sizeof(uint32_t);
	EMsg msgType = (EMsg)(dwSign & ~STEAM_PROTOCOL_MASK);
	bool bIsProto = (dwSign & STEAM_PROTOCOL_MASK) != 0;
	CMsgProtoBufHeader hdr;
	if (msgType == EMsg::ChannelEncryptRequest || msgType == EMsg::ChannelEncryptResult) {
		hdr.has_jobid_source = hdr.has_jobid_target = true;
		hdr.jobid_source = *(int64_t *)buf; buf += sizeof(int64_t);
		hdr.jobid_target = *(int64_t *)buf; buf += sizeof(int64_t);
	}
	else if (bIsProto) {
		uint32_t hdrLen = *(uint32_t *)buf; buf += sizeof(uint32_t); cbLen -= sizeof(uint32_t);
		proto::MsgProtoBufHeader tmpHeader(buf, hdrLen);
		if (tmpHeader == nullptr) {
			debugLogA("Unable to decode message header, exiting");
			return;
		}
		memcpy(&hdr, tmpHeader, sizeof(hdr));
		buf += hdrLen; cbLen -= hdrLen;
	}
	else {
		debugLogA("Got unknown header, exiting");
		return;
	}
	MsgCallback pCallback = 0;
	{
		mir_cslock lck(m_csRequests);
		if (auto *pReq = m_arRequests.find((ProtoRequest *)&hdr.jobid_target)) {
			pCallback = pReq->pCallback;
			m_arRequests.remove(pReq);
		}
	}
	if (pCallback) {
		(this->*pCallback)(buf, cbLen);
		return;
	}
	// persistent callbacks
	switch (msgType) {
	case EMsg::ClientLogOnResponse:
		OnLoggedOn(buf, cbLen);
		break;
	}
}