diff options
Diffstat (limited to 'protocols/Discord/src/gateway.cpp')
-rw-r--r-- | protocols/Discord/src/gateway.cpp | 193 |
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); } |