summaryrefslogtreecommitdiff
path: root/protocols/Discord/src/gateway.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'protocols/Discord/src/gateway.cpp')
-rw-r--r--protocols/Discord/src/gateway.cpp193
1 files changed, 147 insertions, 46 deletions
diff --git a/protocols/Discord/src/gateway.cpp b/protocols/Discord/src/gateway.cpp
index a7352d4120..7454383878 100644
--- a/protocols/Discord/src/gateway.cpp
+++ b/protocols/Discord/src/gateway.cpp
@@ -17,30 +17,20 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "stdafx.h"
-void CDiscordProto::OnReceiveGateway(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest*)
-{
- if (pReply->resultCode != 200) {
- ShutdownSession();
- return;
- }
-
- JSONNode root = JSONNode::parse(pReply->pData);
- if (!root) {
- ShutdownSession();
- return;
- }
+//////////////////////////////////////////////////////////////////////////////////////
+// sends a piece of JSON to a server via a websocket, masked
- m_szGateway = root["url"].as_mstring();
- ForkThread(&CDiscordProto::GatewayThread, NULL);
-}
-
-void CDiscordProto::GatewaySend(int opCode, const char *szBuf)
+void CDiscordProto::GatewaySend(const JSONNode &pRoot, int opCode)
{
if (m_hGatewayConnection == NULL)
return;
+ json_string szText = pRoot.write();
+
BYTE header[20];
- size_t datalen, strLen = mir_strlen(szBuf);
+ size_t datalen;
+ uint64_t strLen = szText.length();
+
header[0] = 0x80 + (opCode & 0x7F);
if (strLen < 126) {
header[1] = (strLen & 0xFF);
@@ -65,15 +55,36 @@ void CDiscordProto::GatewaySend(int opCode, const char *szBuf)
datalen = 10;
}
+ union {
+ uLong dwMask;
+ Bytef arMask[4];
+ };
+ dwMask = crc32(rand(), (Bytef*)szText.c_str(), (uInt)szText.length());
+ memcpy(header + datalen, arMask, _countof(arMask));
+ datalen += _countof(arMask);
+ header[1] |= 0x80;
+
ptrA sendBuf((char*)mir_alloc(strLen + datalen));
memcpy(sendBuf, header, datalen);
- if (strLen)
- memcpy(sendBuf.get() + datalen, szBuf, strLen);
+ if (strLen) {
+ memcpy(sendBuf.get() + datalen, szText.c_str(), strLen);
+ for (size_t i = 0; i < strLen; i++)
+ sendBuf[i + datalen] ^= arMask[i & 3];
+ }
Netlib_Send(m_hGatewayConnection, sendBuf, int(strLen + datalen), 0);
}
+//////////////////////////////////////////////////////////////////////////////////////
+// gateway worker thread
+
void CDiscordProto::GatewayThread(void*)
{
+ GatewayThreadWorker();
+ ShutdownSession();
+}
+
+void CDiscordProto::GatewayThreadWorker()
+{
// connect to the gateway server
if (!mir_strncmp(m_szGateway, "wss://", 6))
m_szGateway.Delete(0, 6);
@@ -94,13 +105,11 @@ void CDiscordProto::GatewayThread(void*)
m_hGatewayConnection = (HANDLE)CallService(MS_NETLIB_OPENCONNECTION, (WPARAM)m_hGatewayNetlibUser, (LPARAM)&conn);
if (m_hGatewayConnection == NULL) {
debugLogA("Gateway connection failed to connect to %s:%d, exiting", m_szGateway.c_str(), conn.wPort);
- LBL_Fatal:
- ShutdownSession();
return;
}
{
CMStringA szBuf;
- szBuf.AppendFormat("GET https://%s/?encoding=etf&v=6 HTTP/1.1\r\n", m_szGateway.c_str());
+ szBuf.AppendFormat("GET https://%s/?encoding=json&v=6 HTTP/1.1\r\n", m_szGateway.c_str());
szBuf.AppendFormat("Host: %s\r\n", m_szGateway.c_str());
szBuf.AppendFormat("Upgrade: websocket\r\n");
szBuf.AppendFormat("Pragma: no-cache\r\n");
@@ -112,7 +121,7 @@ void CDiscordProto::GatewayThread(void*)
szBuf.AppendFormat("\r\n");
if (Netlib_Send(m_hGatewayConnection, szBuf, szBuf.GetLength(), MSG_DUMPASTEXT) == SOCKET_ERROR) {
debugLogA("Error establishing gateway connection to %s:%d, send failed", m_szGateway.c_str(), conn.wPort);
- goto LBL_Fatal;
+ return;
}
}
{
@@ -120,13 +129,13 @@ void CDiscordProto::GatewayThread(void*)
int bufSize = Netlib_Recv(m_hGatewayConnection, buf, _countof(buf), MSG_DUMPASTEXT);
if (bufSize <= 0) {
debugLogA("Error establishing gateway connection to %s:%d, read failed", m_szGateway.c_str(), conn.wPort);
- goto LBL_Fatal;
+ return;
}
int status = 0;
if (sscanf(buf, "HTTP/1.1 %d", &status) != 1 || status != 101) {
debugLogA("Error establishing gateway connection to %s:%d, status %d", m_szGateway.c_str(), conn.wPort, status);
- goto LBL_Fatal;
+ return;
}
}
@@ -134,22 +143,16 @@ void CDiscordProto::GatewayThread(void*)
bool bExit = false;
int offset = 0;
- unsigned char *dataBuf = NULL;
+ char *dataBuf = NULL;
size_t dataBufSize = 0;
+ bool bDataBufAllocated = false;
while (!bExit) {
if (m_bTerminated)
break;
- NETLIBSELECT sel = {};
- sel.cbSize = sizeof(sel);
- sel.dwTimeout = 1000;
- sel.hReadConns[0] = m_hGatewayConnection;
- if (CallService(MS_NETLIB_SELECT, 0, (LPARAM)&sel) == 0) // timeout, send a hartbeat packet
- GatewaySend(3, "{ \"op\":1, \"d\":(null) }");
-
unsigned char buf[2048];
- int bufSize = Netlib_Recv(m_hGatewayConnection, (char*)buf+offset, _countof(buf) - offset, 0);
+ int bufSize = Netlib_Recv(m_hGatewayConnection, (char*)buf + offset, _countof(buf) - offset, 0);
if (bufSize == 0) {
debugLogA("Gateway connection gracefully closed");
break;
@@ -201,16 +204,37 @@ void CDiscordProto::GatewayThread(void*)
debugLogA("Got packet: buffer = %d, opcode = %d, headerSize = %d, final = %d, masked = %d", bufSize, opCode, headerSize, bIsFinal, bIsMasked);
+ // we have some additional data, not only opcode
if (bufSize > headerSize) {
- size_t newSize = dataBufSize + bufSize - headerSize;
- dataBuf = (unsigned char*)mir_realloc(dataBuf, newSize);
- memcpy(dataBuf + dataBufSize, buf + headerSize, bufSize - headerSize);
- dataBufSize = newSize;
- debugLogA("data buffer reallocated to %d bytes", dataBufSize);
- }
+ if (bIsFinal && payloadSize < _countof(buf)) { // it fits, no need to reallocate a buffer
+ bDataBufAllocated = false;
+ dataBuf = (char*)buf + headerSize;
+ dataBufSize = bufSize - headerSize;
+ }
+ else {
+ bDataBufAllocated = true;
+ size_t newSize = dataBufSize + payloadSize;
+ size_t currPacketSize = bufSize - headerSize;
+ dataBuf = (char*)mir_realloc(dataBuf, newSize+1);
+ memcpy(dataBuf + dataBufSize, buf + headerSize, currPacketSize);
+ while (currPacketSize < payloadSize) {
+ int result = Netlib_Recv(m_hGatewayConnection, dataBuf + dataBufSize + currPacketSize, int(payloadSize - currPacketSize), 0);
+ if (result == 0) {
+ debugLogA("Gateway connection gracefully closed");
+ break;
+ }
+ if (result < 0) {
+ debugLogA("Gateway connection error, exiting");
+ break;
+ }
+ currPacketSize += result;
+ }
- if (dataBufSize < payloadSize)
- continue;
+ dataBufSize = newSize;
+ debugLogA("data buffer reallocated to %d bytes", dataBufSize);
+ }
+ dataBuf[dataBufSize] = 0;
+ }
switch (opCode){
case 0: // text packet
@@ -218,8 +242,9 @@ void CDiscordProto::GatewayThread(void*)
case 2: // continuation
if (bIsFinal) {
// process a packet here
- mir_free(dataBuf); dataBuf = NULL;
- dataBufSize = 0;
+ JSONNode root = JSONNode::parse(dataBuf);
+ if (root)
+ GatewayProcess(root);
}
break;
@@ -233,9 +258,85 @@ void CDiscordProto::GatewayThread(void*)
Netlib_Send(m_hGatewayConnection, (char*)buf + headerSize, bufSize - headerSize, 0);
break;
}
+
+ if (bIsFinal) {
+ if (bDataBufAllocated)
+ mir_free(dataBuf);
+ dataBuf = NULL;
+ dataBufSize = 0;
+ }
}
Netlib_CloseHandle(m_hGatewayConnection);
m_hGatewayConnection = NULL;
- ShutdownSession();
+}
+
+//////////////////////////////////////////////////////////////////////////////////////
+// handles server commands
+
+void CDiscordProto::GatewayProcess(const JSONNode &pRoot)
+{
+ int opCode = pRoot["op"].as_int();
+ switch (opCode) {
+ case 0: // process incoming command
+ {
+ int iSeq = pRoot["s"].as_int();
+ if (iSeq != 0)
+ m_iGatewaySeq = iSeq;
+
+ CMStringW wszCommand = pRoot["t"].as_mstring();
+ debugLogA("got a server command to dispatch: %S", wszCommand.c_str());
+
+ GatewayHandlerFunc pFunc = GetHandler(wszCommand);
+ if (pFunc)
+ (this->*pFunc)(pRoot["d"]);
+ }
+ break;
+
+ case 10: // hello
+ m_iHartbeatInterval = pRoot["d"]["heartbeat_interval"].as_int();
+
+ GatewaySendIdentify();
+ break;
+
+ case 11: // heartbeat ack
+ break;
+
+ default:
+ debugLogA("ACHTUNG! Unknown opcode: %d, report it to developer", opCode);
+ }
+}
+
+//////////////////////////////////////////////////////////////////////////////////////
+// requests to be sent to a gateway
+
+void CDiscordProto::GatewaySendHeartbeat()
+{
+ // we don't send heartbeat packets until we get logged in
+ if (!m_iHartbeatInterval || !m_iGatewaySeq)
+ return;
+
+ JSONNode root;
+ root << INT_PARAM("op", 1) << INT_PARAM("d", m_iGatewaySeq);
+ GatewaySend(root);
+}
+
+void CDiscordProto::GatewaySendIdentify()
+{
+ wchar_t wszOs[256];
+ GetOSDisplayString(wszOs, _countof(wszOs));
+
+ char szVersion[256];
+ Miranda_GetVersionText(szVersion, _countof(szVersion));
+
+ JSONNode props; props.set_name("properties");
+ props << WCHAR_PARAM("os", wszOs) << CHAR_PARAM("browser", "Chrome") << CHAR_PARAM("device", szVersion)
+ << CHAR_PARAM("referrer", "http://miranda-ng.org") << CHAR_PARAM("referring_domain", "miranda-ng.org");
+
+ JSONNode payload; payload.set_name("d");
+ payload << CHAR_PARAM("token", m_szAccessToken) << props << BOOL_PARAM("compress", false) << INT_PARAM("large_threshold", 250);
+
+ JSONNode root;
+ root << INT_PARAM("op", 2) << payload;
+ GatewaySend(root);
}