#include "common.h" void WhatsAppProto::ChangeStatus(void*) { if (m_iDesiredStatus != ID_STATUS_OFFLINE && m_iStatus == ID_STATUS_OFFLINE) { ResetEvent(update_loop_lock_); ForkThread(&WhatsAppProto::sentinelLoop, this); ForkThread(&WhatsAppProto::stayConnectedLoop, this); } else if (m_iStatus == ID_STATUS_INVISIBLE && m_iDesiredStatus == ID_STATUS_ONLINE) { if (this->connection != NULL) { this->connection->sendAvailableForChat(); m_iStatus = ID_STATUS_ONLINE; ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE) m_iStatus, ID_STATUS_INVISIBLE); } } else if (m_iStatus == ID_STATUS_ONLINE && m_iDesiredStatus == ID_STATUS_INVISIBLE) { if (this->connection != NULL) { this->connection->sendClose(); m_iStatus = ID_STATUS_INVISIBLE; SetAllContactStatuses( ID_STATUS_OFFLINE, true ); ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE) m_iStatus, ID_STATUS_ONLINE); } } else if (m_iDesiredStatus == ID_STATUS_OFFLINE) { if (this->conn != NULL) { SetEvent(update_loop_lock_); this->conn->forceShutdown(); LOG("Forced shutdown"); } } } void WhatsAppProto::stayConnectedLoop(void*) { bool error = true; std::string cc, in, pass; DBVARIANT dbv = {0}; if ( !getString(WHATSAPP_KEY_CC, &dbv)) { cc = dbv.pszVal; db_free(&dbv); error = cc.empty(); } if (error) { NotifyEvent(m_tszUserName,TranslateT("Please enter a country-code."),NULL,WHATSAPP_EVENT_CLIENT); return; } error = true; if ( !getString(WHATSAPP_KEY_LOGIN, &dbv)) { in = dbv.pszVal; db_free(&dbv); error = in.empty(); } if (error) { NotifyEvent(m_tszUserName,TranslateT("Please enter a phone-number without country code."),NULL,WHATSAPP_EVENT_CLIENT); return; } this->phoneNumber = cc + in; this->jid = this->phoneNumber + "@s.whatsapp.net"; error = true; if ( !getString(WHATSAPP_KEY_NICK, &dbv)) { this->nick = dbv.pszVal; db_free(&dbv); error = nick.empty(); } if (error) { NotifyEvent(m_tszUserName, TranslateT("Please enter a nickname."), NULL, WHATSAPP_EVENT_CLIENT); return; } error = true; if ( !getString(WHATSAPP_KEY_PASS, &dbv)) { CallService(MS_DB_CRYPT_DECODESTRING,strlen(dbv.pszVal)+1, reinterpret_cast(dbv.pszVal)); pass = dbv.pszVal; db_free(&dbv); error = pass.empty(); } if (error) { NotifyEvent(m_tszUserName,TranslateT("Please enter a password."),NULL,WHATSAPP_EVENT_CLIENT); return; } // ----------------------------- Mutex writerMutex; WALogin* login = NULL; int desiredStatus; this->conn = NULL; while (true) { if (connection != NULL) { if (connection->getLogin() == NULL && login != NULL) { delete login; login = NULL; } delete connection; connection = NULL; } if (this->conn != NULL) { delete this->conn; this->conn = NULL; } desiredStatus = this->m_iDesiredStatus; if (desiredStatus == ID_STATUS_OFFLINE || error) { LOG("Set status to offline"); SetAllContactStatuses( ID_STATUS_OFFLINE, true ); this->ToggleStatusMenuItems(false); int prevStatus = this->m_iStatus; this->m_iStatus = ID_STATUS_OFFLINE; ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE) m_iStatus, prevStatus); break; } LOG("Connecting..."); this->m_iStatus = ID_STATUS_CONNECTING; ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE) ID_STATUS_OFFLINE, m_iStatus); CODE_BLOCK_TRY unsigned passLen; ptrA passBin((char*)mir_base64_decode(pass.c_str(), &passLen)); std::string password(passBin, passLen), resource = ACCOUNT_RESOURCE; int portNumber; if (getByte(WHATSAPP_KEY_SSL, 0)) portNumber = 443, resource += "-443"; else portNumber = 5222, resource += "-5222"; this->conn = new WASocketConnection("c.whatsapp.net", portNumber); connection = new WAConnection(&this->connMutex, this, this); login = new WALogin(connection, new BinTreeNodeReader(connection, conn, WAConnection::dictionary, WAConnection::DICTIONARY_LEN), new BinTreeNodeWriter(connection, conn, WAConnection::dictionary, WAConnection::DICTIONARY_LEN, &writerMutex), "s.whatsapp.net", this->phoneNumber, resource, password, nick); std::vector* nextChallenge = login->login(*this->challenge); delete this->challenge; this->challenge = nextChallenge; connection->setLogin(login); connection->setVerboseId(true); // ? if (desiredStatus != ID_STATUS_INVISIBLE) { connection->sendAvailableForChat(); } LOG("Set status to online"); this->m_iStatus = desiredStatus; ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE )m_iStatus, ID_STATUS_CONNECTING); this->ToggleStatusMenuItems(true); ForkThread(&WhatsAppProto::ProcessBuddyList, this); // #TODO Move out of try block. Exception is expected on disconnect bool cont = true; while (cont == true) { this->lastPongTime = time(NULL); cont = connection->read(); } LOG("Exit from read-loop"); CODE_BLOCK_CATCH(WAException) error = true; CODE_BLOCK_CATCH(exception) error = true; CODE_BLOCK_CATCH_UNKNOWN error = true; CODE_BLOCK_END } LOG("Break out from loop"); } void WhatsAppProto::sentinelLoop(void*) { int delay = MAX_SILENT_INTERVAL; int quietInterval; while (WaitForSingleObjectEx(update_loop_lock_, delay * 1000, true) == WAIT_TIMEOUT) { if (this->m_iStatus != ID_STATUS_OFFLINE && this->connection != NULL && this->m_iDesiredStatus == this->m_iStatus) { // #TODO Quiet after pong or tree read? quietInterval = difftime(time(NULL), this->lastPongTime); if (quietInterval >= MAX_SILENT_INTERVAL) { CODE_BLOCK_TRY LOG("send ping"); this->lastPongTime = time(NULL); this->connection->sendPing(); CODE_BLOCK_CATCH(exception) CODE_BLOCK_END } else { delay = MAX_SILENT_INTERVAL - quietInterval; } } else { delay = MAX_SILENT_INTERVAL; } } ResetEvent(update_loop_lock_); LOG("Exiting sentinel loop"); } void WhatsAppProto::onPing(const std::string& id) { if (this->isOnline()) { CODE_BLOCK_TRY LOG("Sending pong with id %s", id.c_str()); this->connection->sendPong(id); CODE_BLOCK_CATCH_ALL } }