From 6f8955cdc2e8bcdeaa15a06d0f1badf13d14cb5c Mon Sep 17 00:00:00 2001 From: George Hazan Date: Tue, 8 Oct 2019 20:37:59 +0300 Subject: first version of WA that connects --- protocols/WhatsAppWeb/src/server.cpp | 254 +++++++++++++++++++++++++++++++++-- 1 file changed, 245 insertions(+), 9 deletions(-) (limited to 'protocols/WhatsAppWeb/src/server.cpp') diff --git a/protocols/WhatsAppWeb/src/server.cpp b/protocols/WhatsAppWeb/src/server.cpp index 8b94cb675c..8e9419ad47 100644 --- a/protocols/WhatsAppWeb/src/server.cpp +++ b/protocols/WhatsAppWeb/src/server.cpp @@ -97,7 +97,7 @@ static int random_func(uint8_t *pData, size_t size, void *) return 0; } -bool WhatsAppProto::ShowQrCode() +bool WhatsAppProto::ShowQrCode(const CMStringA &ref) { CMStringA szPubKey(getMStringA(DBKEY_PUBKEY)); if (szPubKey.IsEmpty()) { @@ -135,17 +135,253 @@ bool WhatsAppProto::ShowQrCode() return true; } -///////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////// +// sends a piece of JSON to a server via a websocket, masked + +int WhatsAppProto::WSSend(const CMStringA &str, WA_PKT_HANDLER pHandler) +{ + if (m_hServerConn == nullptr) + return -1; + + int pktId = ++m_iPktNumber; + + CMStringA buf; + buf.Format("%d.--%d,", (int)m_iLoginTime, pktId); + if (!str.IsEmpty()) { + buf.AppendChar(','); + buf += str; + } + + if (pHandler != nullptr) { + auto *pReq = new WARequest; + pReq->issued = time(0); + pReq->pHandler = pHandler; + pReq->pktId = pktId; + + mir_cslock lck(m_csPacketQueue); + m_arPacketQueue.insert(pReq); + } + + WebSocket_Send(m_hServerConn, buf.c_str(), buf.GetLength()); + return pktId; +} + +void WhatsAppProto::OnLoggedIn() +{ + debugLogA("CDiscordProto::OnLoggedIn"); + m_bOnline = true; + // SetServerStatus(m_iDesiredStatus); +} + +void WhatsAppProto::OnLoggedOut(void) +{ + debugLogA("CDiscordProto::OnLoggedOut"); + m_bOnline = false; + m_bTerminated = true; + m_iPktNumber = 0; + + ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)m_iStatus, ID_STATUS_OFFLINE); + m_iStatus = m_iDesiredStatus = ID_STATUS_OFFLINE; + + setAllContactStatuses(ID_STATUS_OFFLINE, false); +} + +void WhatsAppProto::RestoreSession() +{ +} + +void WhatsAppProto::ShutdownSession() +{ + if (m_bTerminated) + return; + + debugLogA("WhatsAppProto::ShutdownSession"); + + // shutdown all resources + if (m_hServerConn) + Netlib_Shutdown(m_hServerConn); + + OnLoggedOut(); +} + +void WhatsAppProto::StartSession() +{ + WORD v[4]; + Miranda_GetFileVersion(&v); + + CMStringA payload(FORMAT, "[\"admin\",\"init\",[%d,%d,%d],[\"Windows\",\"Miranda\",\"10\"],%s,true]", + v[1], v[2], v[3], m_szClientId.c_str()); + WSSend(payload, &WhatsAppProto::OnStartSession); +} + +void WhatsAppProto::OnStartSession(const JSONNode &root) +{ + int status = root["status"].as_int(); + if (status != 200) { + debugLogA("Session start failed with error %d", status); + ShutdownSession(); + return; + } + + CMStringA ref = root["ref"].as_mstring(); + ShowQrCode(ref); +} + +////////////////////////////////////////////////////////////////////////////////////// +// gateway worker thread void WhatsAppProto::ServerThread(void *) { - m_pConn = new WAConnection(); + while (ServerThreadWorker()) + ; + ShutdownSession(); +} + +bool WhatsAppProto::ServerThreadWorker() +{ + NETLIBHTTPHEADER hdrs[] = + { + { "Origin", "https://web.whatsapp.com" }, + { "Sec-WebSocket-Key", "k/hwJLznKpk3p2hxyYGzWA==" }, + { 0, 0 } + }; + + m_hServerConn = WebSocket_Connect(m_hNetlibUser, "web.whatsapp.com/ws", hdrs); + if (m_hServerConn == nullptr) { + debugLogA("Server connection failed, exiting"); + return false; + } + + debugLogA("Server connection succeeded"); + + m_szClientToken = getMStringA(DBKEY_CLIENT_SECRET); + if (m_szClientToken.IsEmpty()) + StartSession(); + else + RestoreSession(); + + bool bExit = false; + int offset = 0; + MBinBuffer netbuf; + + while (!bExit) { + if (m_bTerminated) + break; - ptrA szClientToken(getStringA(DBKEY_CLIENT_SECRET)); - if (szClientToken == nullptr) { - if (!ShowQrCode()) { - delete m_pConn; - return; + unsigned char buf[2048]; + int bufSize = Netlib_Recv(m_hServerConn, (char *)buf + offset, _countof(buf) - offset, MSG_NODUMP); + if (bufSize == 0) { + debugLogA("Gateway connection gracefully closed"); + bExit = !m_bTerminated; + break; + } + if (bufSize < 0) { + debugLogA("Gateway connection error, exiting"); + break; + } + + WSHeader hdr; + if (!WebSocket_InitHeader(hdr, buf, bufSize)) { + offset += bufSize; + continue; + } + offset = 0; + + debugLogA("Got packet: buffer = %d, opcode = %d, headerSize = %d, final = %d, masked = %d", bufSize, hdr.opCode, hdr.headerSize, hdr.bIsFinal, hdr.bIsMasked); + + // we have some additional data, not only opcode + if ((size_t)bufSize > hdr.headerSize) { + size_t currPacketSize = bufSize - hdr.headerSize; + netbuf.append(buf, bufSize); + while (currPacketSize < hdr.payloadSize) { + int result = Netlib_Recv(m_hServerConn, (char *)buf, _countof(buf), MSG_NODUMP); + if (result == 0) { + debugLogA("Gateway connection gracefully closed"); + bExit = !m_bTerminated; + break; + } + if (result < 0) { + debugLogA("Gateway connection error, exiting"); + break; + } + currPacketSize += result; + netbuf.append(buf, result); + } + } + + // read all payloads from the current buffer, one by one + size_t prevSize = 0; + while (true) { + switch (hdr.opCode) { + case 0: // text packet + case 1: // binary packet + case 2: // continuation + if (hdr.bIsFinal) { + // process a packet here + CMStringA szJson(netbuf.data() + hdr.headerSize, (int)hdr.payloadSize); + debugLogA("JSON received:\n%s", szJson.c_str()); + + int pos = szJson.Find(','); + if (pos != -1) { + CMStringA prefix = szJson.Left(pos); + szJson.Delete(0, pos); + + JSONNode root = JSONNode::parse(szJson); + if (root) { + int sessId, pktId; + if (sscanf(szJson, "%d--.%d,", &sessId, &pktId) == 2) { + auto *pReq = m_arPacketQueue.find((WARequest *)&pktId); + if (pReq != nullptr) { + (this->*pReq->pHandler)(root); + } + } + else ProcessPacket(root); + } + } + } + break; + + case 8: // close + debugLogA("server required to exit"); + bExit = true; // simply reconnect, don't exit + break; + + case 9: // ping + debugLogA("ping received"); + Netlib_Send(m_hServerConn, (char *)buf + hdr.headerSize, bufSize - int(hdr.headerSize), 0); + break; + } + + if (hdr.bIsFinal) + netbuf.remove(hdr.headerSize + hdr.payloadSize); + + if (netbuf.length() == 0) + break; + + // if we have not enough data for header, continue reading + if (!WebSocket_InitHeader(hdr, netbuf.data(), netbuf.length())) + break; + + // if we have not enough data for data, continue reading + if (hdr.headerSize + hdr.payloadSize > netbuf.length()) + break; + + debugLogA("Got inner packet: buffer = %d, opcode = %d, headerSize = %d, payloadSize = %d, final = %d, masked = %d", netbuf.length(), hdr.opCode, hdr.headerSize, hdr.payloadSize, hdr.bIsFinal, hdr.bIsMasked); + if (prevSize == netbuf.length()) { + netbuf.remove(prevSize); + debugLogA("dropping current packet, exiting"); + break; + } + + prevSize = netbuf.length(); } } -} \ No newline at end of file + + Netlib_CloseHandle(m_hServerConn); + m_hServerConn = nullptr; + return bExit; +} + +void WhatsAppProto::ProcessPacket(const JSONNode &root) +{ +} -- cgit v1.2.3