diff options
author | George Hazan <ghazan@miranda.im> | 2017-01-04 00:34:25 +0300 |
---|---|---|
committer | George Hazan <ghazan@miranda.im> | 2017-01-04 00:34:25 +0300 |
commit | ac1d88965de3022be2f484c2f58185ae75b4fe00 (patch) | |
tree | ce2e5ddf89bb9acf1f13a698d4d4a9e55d8a4520 /protocols/Discord | |
parent | f82e226c107565f8dac8bc327b945648377c0eba (diff) |
gateway support: beginning
Diffstat (limited to 'protocols/Discord')
-rw-r--r-- | protocols/Discord/src/connection.cpp | 5 | ||||
-rw-r--r-- | protocols/Discord/src/gateway.cpp | 241 | ||||
-rw-r--r-- | protocols/Discord/src/proto.cpp | 17 | ||||
-rw-r--r-- | protocols/Discord/src/proto.h | 12 |
4 files changed, 272 insertions, 3 deletions
diff --git a/protocols/Discord/src/connection.cpp b/protocols/Discord/src/connection.cpp index db3d667761..bc48d402e4 100644 --- a/protocols/Discord/src/connection.cpp +++ b/protocols/Discord/src/connection.cpp @@ -67,6 +67,11 @@ void CDiscordProto::OnLoggedIn() Push(new AsyncHttpRequest(this, REQUEST_GET, "/users/@me/guilds", &CDiscordProto::OnReceiveGuilds)); Push(new AsyncHttpRequest(this, REQUEST_GET, "/users/@me/channels", &CDiscordProto::OnReceiveChannels)); Push(new AsyncHttpRequest(this, REQUEST_GET, "/users/@me/relationships", &CDiscordProto::OnReceiveFriends)); + + if (m_szGateway.IsEmpty()) + Push(new AsyncHttpRequest(this, REQUEST_GET, "/gateway", &CDiscordProto::OnReceiveGateway)); + else + ForkThread(&CDiscordProto::GatewayThread, NULL); } void CDiscordProto::OnLoggedOut() diff --git a/protocols/Discord/src/gateway.cpp b/protocols/Discord/src/gateway.cpp new file mode 100644 index 0000000000..a7352d4120 --- /dev/null +++ b/protocols/Discord/src/gateway.cpp @@ -0,0 +1,241 @@ +/* +Copyright © 2016-17 Miranda NG team + +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, 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; + } + + m_szGateway = root["url"].as_mstring(); + ForkThread(&CDiscordProto::GatewayThread, NULL); +} + +void CDiscordProto::GatewaySend(int opCode, const char *szBuf) +{ + if (m_hGatewayConnection == NULL) + return; + + BYTE header[20]; + size_t datalen, strLen = mir_strlen(szBuf); + header[0] = 0x80 + (opCode & 0x7F); + if (strLen < 126) { + header[1] = (strLen & 0xFF); + datalen = 2; + } + else if (strLen < 65536) { + header[1] = 0x7E; + header[2] = (strLen >> 8) & 0xFF; + header[3] = strLen & 0xFF; + datalen = 4; + } + else { + header[1] = 0x7F; + header[2] = (strLen >> 56) & 0xff; + header[3] = (strLen >> 48) & 0xff; + header[4] = (strLen >> 40) & 0xff; + header[5] = (strLen >> 32) & 0xff; + header[6] = (strLen >> 24) & 0xff; + header[7] = (strLen >> 16) & 0xff; + header[8] = (strLen >> 8) & 0xff; + header[9] = strLen & 0xff; + datalen = 10; + } + + ptrA sendBuf((char*)mir_alloc(strLen + datalen)); + memcpy(sendBuf, header, datalen); + if (strLen) + memcpy(sendBuf.get() + datalen, szBuf, strLen); + Netlib_Send(m_hGatewayConnection, sendBuf, int(strLen + datalen), 0); +} + +void CDiscordProto::GatewayThread(void*) +{ + // connect to the gateway server + if (!mir_strncmp(m_szGateway, "wss://", 6)) + m_szGateway.Delete(0, 6); + + NETLIBOPENCONNECTION conn = { 0 }; + conn.cbSize = sizeof(conn); + conn.szHost = m_szGateway; + conn.flags = NLOCF_V2 | NLOCF_SSL; + conn.timeout = 5; + + int pos = m_szGateway.Find(':'); + if (pos != -1) { + conn.wPort = atoi(m_szGateway.GetBuffer() + pos + 1); + m_szGateway.Truncate(pos); + } + else conn.wPort = 443; + + 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("Host: %s\r\n", m_szGateway.c_str()); + szBuf.AppendFormat("Upgrade: websocket\r\n"); + szBuf.AppendFormat("Pragma: no-cache\r\n"); + szBuf.AppendFormat("Cache-Control: no-cache\r\n"); + szBuf.AppendFormat("Connection: Upgrade\r\n"); + szBuf.AppendFormat("Sec-WebSocket-Key: KFShSwLlp4E6C7JZc5h4sg==\r\n"); + szBuf.AppendFormat("Sec-WebSocket-Version: 13\r\n"); + szBuf.AppendFormat("Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits\r\n"); + 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; + } + } + { + char buf[1024]; + 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; + } + + 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; + } + } + + debugLogA("Gateway connection succeeded"); + + bool bExit = false; + int offset = 0; + unsigned char *dataBuf = NULL; + size_t dataBufSize = 0; + + 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); + if (bufSize == 0) { + debugLogA("Gateway connection gracefully closed"); + break; + } + if (bufSize < 0) { + debugLogA("Gateway connection error, exiting"); + break; + } + if (bufSize < 2) { + offset = bufSize; + continue; + } + offset = 0; + + bool bIsFinal = (buf[0] & 0x80) != 0; + bool bIsMasked = (buf[1] & 0x80) != 0; + int opCode = buf[0] & 0x0F, firstByte = buf[1] & 0x7F; + int headerSize = 2 + (firstByte == 0x7E ? 2 : 0) + (firstByte == 0x7F ? 8 : 0) + (bIsMasked ? 4 : 0); + if (bufSize < headerSize) { + offset = bufSize; + continue; + } + + int dataShift; + uint64_t payloadSize = 0; + switch (firstByte) { + case 0x7F: + payloadSize += ((uint64_t)buf[2]) << 56; + payloadSize += ((uint64_t)buf[3]) << 48; + payloadSize += ((uint64_t)buf[4]) << 40; + payloadSize += ((uint64_t)buf[5]) << 32; + payloadSize += ((uint64_t)buf[6]) << 24; + payloadSize += ((uint64_t)buf[7]) << 16; + payloadSize += ((uint64_t)buf[8]) << 8; + payloadSize += ((uint64_t)buf[9]); + dataShift = 10; + break; + + case 0x7E: + payloadSize += ((uint64_t)buf[2]) << 8; + payloadSize += ((uint64_t)buf[3]); + dataShift = 4; + break; + + default: + payloadSize = firstByte; + dataShift = 2; + } + + debugLogA("Got packet: buffer = %d, opcode = %d, headerSize = %d, final = %d, masked = %d", bufSize, opCode, headerSize, bIsFinal, bIsMasked); + + 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 (dataBufSize < payloadSize) + continue; + + switch (opCode){ + case 0: // text packet + case 1: // binary packet + case 2: // continuation + if (bIsFinal) { + // process a packet here + mir_free(dataBuf); dataBuf = NULL; + dataBufSize = 0; + } + break; + + case 8: // close + debugLogA("server required to exit", dataBufSize); + bExit = true; + break; + + case 9: // ping + debugLogA("ping received", dataBufSize); + Netlib_Send(m_hGatewayConnection, (char*)buf + headerSize, bufSize - headerSize, 0); + break; + } + } + + Netlib_CloseHandle(m_hGatewayConnection); + m_hGatewayConnection = NULL; + ShutdownSession(); +} diff --git a/protocols/Discord/src/proto.cpp b/protocols/Discord/src/proto.cpp index 41179a5a2d..f85df56fe3 100644 --- a/protocols/Discord/src/proto.cpp +++ b/protocols/Discord/src/proto.cpp @@ -57,13 +57,21 @@ CDiscordProto::CDiscordProto(const char *proto_name, const wchar_t *username) : } // Network initialization - CMStringW descr(FORMAT, TranslateT("%s server connection"), m_tszUserName); - + CMStringW descr; NETLIBUSER nlu = { sizeof(nlu) }; - nlu.flags = NUF_INCOMING | NUF_OUTGOING | NUF_HTTPCONNS | NUF_UNICODE; + nlu.szSettingsModule = m_szModuleName; + nlu.flags = NUF_INCOMING | NUF_OUTGOING | NUF_HTTPCONNS | NUF_UNICODE; + descr.Format(TranslateT("%s server connection"), m_tszUserName); nlu.ptszDescriptiveName = descr.GetBuffer(); m_hNetlibUser = (HANDLE)CallService(MS_NETLIB_REGISTERUSER, 0, (LPARAM)&nlu); + + CMStringA module(FORMAT, "%s.Gateway", m_szModuleName); + nlu.szSettingsModule = module.GetBuffer(); + nlu.flags = NUF_INCOMING | NUF_OUTGOING | NUF_UNICODE; + descr.Format(TranslateT("%s gateway connection"), m_tszUserName); + nlu.ptszDescriptiveName = descr.GetBuffer(); + m_hGatewayNetlibUser = (HANDLE)CallService(MS_NETLIB_REGISTERUSER, 0, (LPARAM)&nlu); } CDiscordProto::~CDiscordProto() @@ -263,6 +271,9 @@ int CDiscordProto::OnPreShutdown(WPARAM, LPARAM) m_bTerminated = true; SetEvent(m_evRequestsQueue); + + if (m_hGatewayConnection) + Netlib_Shutdown(m_hGatewayConnection); return 0; } diff --git a/protocols/Discord/src/proto.h b/protocols/Discord/src/proto.h index 8d0e6c24fb..b822bbf323 100644 --- a/protocols/Discord/src/proto.h +++ b/protocols/Discord/src/proto.h @@ -117,6 +117,18 @@ class CDiscordProto : public PROTO<CDiscordProto> m_bTerminated; // Miranda's going down ////////////////////////////////////////////////////////////////////////////////////// + // gateway + + CMStringA m_szGateway; + HANDLE + m_hGatewayNetlibUser, // the separate netlib user handle for gateways + m_hGatewayConnection; // gateway connection + void __cdecl GatewayThread(void*); + void GatewaySend(int opCode, const char*); + + void OnReceiveGateway(NETLIBHTTPREQUEST*, AsyncHttpRequest*); + + ////////////////////////////////////////////////////////////////////////////////////// // options CMOption<wchar_t*> m_wszEmail; // my own email |