summaryrefslogtreecommitdiff
path: root/protocols/WhatsApp
diff options
context:
space:
mode:
authordartraiden <wowemuh@gmail.com>2023-01-02 21:10:29 +0300
committerdartraiden <wowemuh@gmail.com>2023-01-02 21:10:29 +0300
commit1979fd80424d16b2e489f9b57d01d9c7811d25a2 (patch)
tree960d42c5fe4a51f0fe2850bea91256e226bce221 /protocols/WhatsApp
parentadfbbb217d4f4a05acf198755f219a5223d31c27 (diff)
Update copyrights
Diffstat (limited to 'protocols/WhatsApp')
-rw-r--r--protocols/WhatsApp/src/appsync.cpp2
-rw-r--r--protocols/WhatsApp/src/avatars.cpp338
-rw-r--r--protocols/WhatsApp/src/chats.cpp2
-rw-r--r--protocols/WhatsApp/src/crypt.cpp354
-rw-r--r--protocols/WhatsApp/src/db.h86
-rw-r--r--protocols/WhatsApp/src/dicts.h388
-rw-r--r--protocols/WhatsApp/src/iq.cpp1140
-rw-r--r--protocols/WhatsApp/src/main.cpp142
-rw-r--r--protocols/WhatsApp/src/message.cpp1004
-rw-r--r--protocols/WhatsApp/src/noise.cpp402
-rw-r--r--protocols/WhatsApp/src/options.cpp190
-rw-r--r--protocols/WhatsApp/src/proto.cpp2
-rw-r--r--protocols/WhatsApp/src/proto.h2
-rw-r--r--protocols/WhatsApp/src/qrcode.cpp274
-rw-r--r--protocols/WhatsApp/src/server.cpp2
-rw-r--r--protocols/WhatsApp/src/signal.cpp1576
-rw-r--r--protocols/WhatsApp/src/stdafx.cxx16
-rw-r--r--protocols/WhatsApp/src/stdafx.h144
-rw-r--r--protocols/WhatsApp/src/utils.cpp1178
-rw-r--r--protocols/WhatsApp/src/utils.h534
-rw-r--r--protocols/WhatsApp/src/version.h2
-rw-r--r--protocols/WhatsApp/src/wanode.cpp1216
22 files changed, 4497 insertions, 4497 deletions
diff --git a/protocols/WhatsApp/src/appsync.cpp b/protocols/WhatsApp/src/appsync.cpp
index 42e911a3b7..d298f880d2 100644
--- a/protocols/WhatsApp/src/appsync.cpp
+++ b/protocols/WhatsApp/src/appsync.cpp
@@ -1,7 +1,7 @@
/*
WhatsApp plugin for Miranda NG
-Copyright © 2019-22 George Hazan
+Copyright © 2019-23 George Hazan
*/
diff --git a/protocols/WhatsApp/src/avatars.cpp b/protocols/WhatsApp/src/avatars.cpp
index d2eda4014a..d648bb3801 100644
--- a/protocols/WhatsApp/src/avatars.cpp
+++ b/protocols/WhatsApp/src/avatars.cpp
@@ -1,169 +1,169 @@
-/*
-
-WhatsApp plugin for Miranda NG
-Copyright © 2019-22 George Hazan
-
-*/
-
-#include "stdafx.h"
-
-void WhatsAppProto::OnIqGetAvatar(const WANode &node)
-{
- auto *pUser = FindUser(node.getAttr("from"));
- if (pUser == nullptr)
- return;
-
- PROTO_AVATAR_INFORMATION ai = {};
- ai.hContact = pUser->hContact;
- ai.format = PA_FORMAT_JPEG;
- wcsncpy_s(ai.filename, GetAvatarFileName(pUser->hContact), _TRUNCATE);
-
- auto *pNode = node.getChild("picture");
-
- DWORD dwLastChangeTime = pNode->getAttrInt("id");
-
- CMStringA szUrl(pNode->getAttr("url"));
- if (szUrl.IsEmpty()) {
- setDword(pUser->hContact, DBKEY_AVATAR_TAG, 0); // avatar doesn't exist, don't check it later
-
-LBL_Error:
- ProtoBroadcastAck(pUser->hContact, ACKTYPE_AVATAR, ACKRESULT_FAILED, HANDLE(&ai));
- return;
- }
-
- // if avatar was changed or not present at all, download it
- if (dwLastChangeTime > getDword(pUser->hContact, DBKEY_AVATAR_TAG)) {
- if (!g_plugin.SaveFile(szUrl, ai))
- goto LBL_Error;
-
- // set timestamp of avatar being saved
- setDword(pUser->hContact, DBKEY_AVATAR_TAG, dwLastChangeTime);
- }
- ProtoBroadcastAck(pUser->hContact, ACKTYPE_AVATAR, ACKRESULT_SUCCESS, HANDLE(&ai));
-}
-
-INT_PTR WhatsAppProto::GetAvatarInfo(WPARAM wParam, LPARAM lParam)
-{
- PROTO_AVATAR_INFORMATION *pai = (PROTO_AVATAR_INFORMATION*)lParam;
-
- ptrA jid(getStringA(pai->hContact, isChatRoom(pai->hContact) ? "ChatRoomID" : DBKEY_ID));
- if (jid == NULL)
- return GAIR_NOAVATAR;
-
- CMStringW tszFileName(GetAvatarFileName(pai->hContact));
- wcsncpy_s(pai->filename, tszFileName.c_str(), _TRUNCATE);
- pai->format = PA_FORMAT_JPEG;
-
- DWORD dwTag = getDword(pai->hContact, DBKEY_AVATAR_TAG, -1);
- if (dwTag == -1 || (wParam & GAIF_FORCE) != 0)
- if (pai->hContact != NULL && isOnline()) {
- ServerFetchAvatar(jid);
- return GAIR_WAITFOR;
- }
-
- debugLogA("No avatar");
- return GAIR_NOAVATAR;
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-INT_PTR WhatsAppProto::GetAvatarCaps(WPARAM wParam, LPARAM lParam)
-{
- switch (wParam) {
- case AF_PROPORTION:
- return PIP_SQUARE;
-
- case AF_FORMATSUPPORTED: // Jabber supports avatars of virtually all formats
- return PA_FORMAT_JPEG;
-
- case AF_ENABLED:
- return TRUE;
-
- case AF_MAXSIZE:
- POINT *size = (POINT*)lParam;
- if (size)
- size->x = size->y = 640;
- return 0;
- }
- return -1;
-}
-
-CMStringW WhatsAppProto::GetAvatarFileName(MCONTACT hContact)
-{
- CMStringW result = m_tszAvatarFolder + L"\\";
-
- CMStringA jid;
- if (hContact != NULL) {
- ptrA szId(getStringA(hContact, isChatRoom(hContact) ? "ChatRoomID" : DBKEY_ID));
- if (szId == NULL)
- return L"";
-
- jid = szId;
- }
- else jid = m_szJid;
-
- return result + _A2T(jid.c_str()) + L".jpg";
-}
-
-INT_PTR WhatsAppProto::GetMyAvatar(WPARAM wParam, LPARAM lParam)
-{
- std::wstring tszOwnAvatar(m_tszAvatarFolder + L"\\myavatar.jpg");
- wcsncpy_s((wchar_t*)wParam, lParam, tszOwnAvatar.c_str(), _TRUNCATE);
- return 0;
-}
-
-INT_PTR WhatsAppProto::SetMyAvatar(WPARAM, LPARAM)
-{
- return 0;
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-void WhatsAppProto::ServerFetchAvatar(const char *jid)
-{
- WANodeIq iq(IQ::GET, "w:profile:picture", jid);
- *iq.addChild("picture") << CHAR_PARAM("type", "preview") << CHAR_PARAM("query", "url");
- WSSendNode(iq, &WhatsAppProto::OnIqGetAvatar);
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-bool CMPlugin::SaveFile(const char *pszUrl, PROTO_AVATAR_INFORMATION &ai)
-{
- NETLIBHTTPREQUEST req = {};
- req.cbSize = sizeof(req);
- req.flags = NLHRF_NODUMP | NLHRF_PERSISTENT | NLHRF_SSL | NLHRF_HTTP11 | NLHRF_REDIRECT;
- req.requestType = REQUEST_GET;
- req.szUrl = (char*)pszUrl;
- req.nlc = hAvatarConn;
-
- NETLIBHTTPREQUEST *pReply = Netlib_HttpTransaction(hAvatarUser, &req);
- if (pReply == nullptr) {
- hAvatarConn = nullptr;
- debugLogA("Failed to retrieve avatar from url: %s", pszUrl);
- return false;
- }
-
- hAvatarConn = pReply->nlc;
-
- bool bSuccess = false;
- if (pReply->resultCode == 200 && pReply->pData && pReply->dataLength) {
- if (auto *pszHdr = Netlib_GetHeader(pReply, "Content-Type"))
- ai.format = ProtoGetAvatarFormatByMimeType(pszHdr);
-
- if (ai.format != PA_FORMAT_UNKNOWN) {
- FILE *fout = _wfopen(ai.filename, L"wb");
- if (fout) {
- fwrite(pReply->pData, 1, pReply->dataLength, fout);
- fclose(fout);
- bSuccess = true;
- }
- else debugLogA("Error saving avatar to file %S", ai.filename);
- }
- else debugLogA("unknown avatar mime type");
- }
- else debugLogA("Error %d reading avatar from url: %s", pReply->resultCode, pszUrl);
-
- Netlib_FreeHttpRequest(pReply);
- return bSuccess;
-}
+/*
+
+WhatsApp plugin for Miranda NG
+Copyright © 2019-23 George Hazan
+
+*/
+
+#include "stdafx.h"
+
+void WhatsAppProto::OnIqGetAvatar(const WANode &node)
+{
+ auto *pUser = FindUser(node.getAttr("from"));
+ if (pUser == nullptr)
+ return;
+
+ PROTO_AVATAR_INFORMATION ai = {};
+ ai.hContact = pUser->hContact;
+ ai.format = PA_FORMAT_JPEG;
+ wcsncpy_s(ai.filename, GetAvatarFileName(pUser->hContact), _TRUNCATE);
+
+ auto *pNode = node.getChild("picture");
+
+ DWORD dwLastChangeTime = pNode->getAttrInt("id");
+
+ CMStringA szUrl(pNode->getAttr("url"));
+ if (szUrl.IsEmpty()) {
+ setDword(pUser->hContact, DBKEY_AVATAR_TAG, 0); // avatar doesn't exist, don't check it later
+
+LBL_Error:
+ ProtoBroadcastAck(pUser->hContact, ACKTYPE_AVATAR, ACKRESULT_FAILED, HANDLE(&ai));
+ return;
+ }
+
+ // if avatar was changed or not present at all, download it
+ if (dwLastChangeTime > getDword(pUser->hContact, DBKEY_AVATAR_TAG)) {
+ if (!g_plugin.SaveFile(szUrl, ai))
+ goto LBL_Error;
+
+ // set timestamp of avatar being saved
+ setDword(pUser->hContact, DBKEY_AVATAR_TAG, dwLastChangeTime);
+ }
+ ProtoBroadcastAck(pUser->hContact, ACKTYPE_AVATAR, ACKRESULT_SUCCESS, HANDLE(&ai));
+}
+
+INT_PTR WhatsAppProto::GetAvatarInfo(WPARAM wParam, LPARAM lParam)
+{
+ PROTO_AVATAR_INFORMATION *pai = (PROTO_AVATAR_INFORMATION*)lParam;
+
+ ptrA jid(getStringA(pai->hContact, isChatRoom(pai->hContact) ? "ChatRoomID" : DBKEY_ID));
+ if (jid == NULL)
+ return GAIR_NOAVATAR;
+
+ CMStringW tszFileName(GetAvatarFileName(pai->hContact));
+ wcsncpy_s(pai->filename, tszFileName.c_str(), _TRUNCATE);
+ pai->format = PA_FORMAT_JPEG;
+
+ DWORD dwTag = getDword(pai->hContact, DBKEY_AVATAR_TAG, -1);
+ if (dwTag == -1 || (wParam & GAIF_FORCE) != 0)
+ if (pai->hContact != NULL && isOnline()) {
+ ServerFetchAvatar(jid);
+ return GAIR_WAITFOR;
+ }
+
+ debugLogA("No avatar");
+ return GAIR_NOAVATAR;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+INT_PTR WhatsAppProto::GetAvatarCaps(WPARAM wParam, LPARAM lParam)
+{
+ switch (wParam) {
+ case AF_PROPORTION:
+ return PIP_SQUARE;
+
+ case AF_FORMATSUPPORTED: // Jabber supports avatars of virtually all formats
+ return PA_FORMAT_JPEG;
+
+ case AF_ENABLED:
+ return TRUE;
+
+ case AF_MAXSIZE:
+ POINT *size = (POINT*)lParam;
+ if (size)
+ size->x = size->y = 640;
+ return 0;
+ }
+ return -1;
+}
+
+CMStringW WhatsAppProto::GetAvatarFileName(MCONTACT hContact)
+{
+ CMStringW result = m_tszAvatarFolder + L"\\";
+
+ CMStringA jid;
+ if (hContact != NULL) {
+ ptrA szId(getStringA(hContact, isChatRoom(hContact) ? "ChatRoomID" : DBKEY_ID));
+ if (szId == NULL)
+ return L"";
+
+ jid = szId;
+ }
+ else jid = m_szJid;
+
+ return result + _A2T(jid.c_str()) + L".jpg";
+}
+
+INT_PTR WhatsAppProto::GetMyAvatar(WPARAM wParam, LPARAM lParam)
+{
+ std::wstring tszOwnAvatar(m_tszAvatarFolder + L"\\myavatar.jpg");
+ wcsncpy_s((wchar_t*)wParam, lParam, tszOwnAvatar.c_str(), _TRUNCATE);
+ return 0;
+}
+
+INT_PTR WhatsAppProto::SetMyAvatar(WPARAM, LPARAM)
+{
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void WhatsAppProto::ServerFetchAvatar(const char *jid)
+{
+ WANodeIq iq(IQ::GET, "w:profile:picture", jid);
+ *iq.addChild("picture") << CHAR_PARAM("type", "preview") << CHAR_PARAM("query", "url");
+ WSSendNode(iq, &WhatsAppProto::OnIqGetAvatar);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+bool CMPlugin::SaveFile(const char *pszUrl, PROTO_AVATAR_INFORMATION &ai)
+{
+ NETLIBHTTPREQUEST req = {};
+ req.cbSize = sizeof(req);
+ req.flags = NLHRF_NODUMP | NLHRF_PERSISTENT | NLHRF_SSL | NLHRF_HTTP11 | NLHRF_REDIRECT;
+ req.requestType = REQUEST_GET;
+ req.szUrl = (char*)pszUrl;
+ req.nlc = hAvatarConn;
+
+ NETLIBHTTPREQUEST *pReply = Netlib_HttpTransaction(hAvatarUser, &req);
+ if (pReply == nullptr) {
+ hAvatarConn = nullptr;
+ debugLogA("Failed to retrieve avatar from url: %s", pszUrl);
+ return false;
+ }
+
+ hAvatarConn = pReply->nlc;
+
+ bool bSuccess = false;
+ if (pReply->resultCode == 200 && pReply->pData && pReply->dataLength) {
+ if (auto *pszHdr = Netlib_GetHeader(pReply, "Content-Type"))
+ ai.format = ProtoGetAvatarFormatByMimeType(pszHdr);
+
+ if (ai.format != PA_FORMAT_UNKNOWN) {
+ FILE *fout = _wfopen(ai.filename, L"wb");
+ if (fout) {
+ fwrite(pReply->pData, 1, pReply->dataLength, fout);
+ fclose(fout);
+ bSuccess = true;
+ }
+ else debugLogA("Error saving avatar to file %S", ai.filename);
+ }
+ else debugLogA("unknown avatar mime type");
+ }
+ else debugLogA("Error %d reading avatar from url: %s", pReply->resultCode, pszUrl);
+
+ Netlib_FreeHttpRequest(pReply);
+ return bSuccess;
+}
diff --git a/protocols/WhatsApp/src/chats.cpp b/protocols/WhatsApp/src/chats.cpp
index 5fbe0cf1ef..59f98d4548 100644
--- a/protocols/WhatsApp/src/chats.cpp
+++ b/protocols/WhatsApp/src/chats.cpp
@@ -1,7 +1,7 @@
/*
WhatsApp plugin for Miranda NG
-Copyright © 2019-22 George Hazan
+Copyright © 2019-23 George Hazan
*/
diff --git a/protocols/WhatsApp/src/crypt.cpp b/protocols/WhatsApp/src/crypt.cpp
index 20e8ddd1bd..049a75844c 100644
--- a/protocols/WhatsApp/src/crypt.cpp
+++ b/protocols/WhatsApp/src/crypt.cpp
@@ -1,177 +1,177 @@
-/*
-
-WhatsApp plugin for Miranda NG
-Copyright © 2019-22 George Hazan
-
-*/
-
-#include "stdafx.h"
-
-MBinBuffer aesDecrypt(
- const EVP_CIPHER *cipher,
- const uint8_t *key,
- const uint8_t *iv,
- const void *data, size_t dataLen,
- const void *additionalData, size_t additionalLen)
-{
- int tag_len = 0, dec_len = 0, final_len = 0;
- EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
- EVP_DecryptInit_ex(ctx, cipher, NULL, key, iv);
-
- if (additionalLen)
- EVP_DecryptUpdate(ctx, nullptr, &tag_len, (uint8_t *)additionalData, (int)additionalLen);
-
- if (cipher == EVP_aes_256_gcm()) {
- dataLen -= 16;
- EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16, (uint8_t *)data + dataLen);
- }
-
- MBinBuffer ret;
- uint8_t outbuf[2000];
- for (size_t len = 0; len < dataLen; len += 1024) {
- size_t portionSize = dataLen - len;
- EVP_DecryptUpdate(ctx, outbuf, &dec_len, (uint8_t *)data + len, (int)min(portionSize, 1024));
- ret.append(outbuf, dec_len);
- }
-
- EVP_DecryptFinal_ex(ctx, outbuf, &final_len);
- if (final_len)
- ret.append(outbuf, final_len);
-
- EVP_CIPHER_CTX_free(ctx);
- return ret;
-}
-
-MBinBuffer aesEncrypt(
- const EVP_CIPHER *cipher,
- const uint8_t *key,
- const uint8_t *iv,
- const void *data, size_t dataLen,
- const void *additionalData, size_t additionalLen)
-{
- int tag_len = 0, dec_len = 0, final_len = 0;
- EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
- EVP_EncryptInit_ex(ctx, cipher, NULL, key, iv);
-
- if (additionalLen)
- EVP_EncryptUpdate(ctx, nullptr, &tag_len, (uint8_t *)additionalData, (int)additionalLen);
-
- if (cipher == EVP_aes_256_gcm()) {
- dataLen -= 16;
- EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16, (uint8_t *)data + dataLen);
- }
-
- MBinBuffer ret;
- uint8_t outbuf[2000];
- for (size_t len = 0; len < dataLen; len += 1024) {
- size_t portionSize = dataLen - len;
- EVP_EncryptUpdate(ctx, outbuf, &dec_len, (uint8_t *)data + len, (int)min(portionSize, 1024));
- ret.append(outbuf, dec_len);
- }
-
- EVP_EncryptFinal_ex(ctx, outbuf, &final_len);
- if (final_len)
- ret.append(outbuf, final_len);
-
- EVP_CIPHER_CTX_free(ctx);
- return ret;
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-static unsigned char *HKDF_Extract(const EVP_MD *evp_md,
- const unsigned char *salt, size_t salt_len,
- const unsigned char *key, size_t key_len,
- unsigned char *prk, size_t *prk_len)
-{
- unsigned int tmp_len;
-
- if (!HMAC(evp_md, salt, (int)salt_len, key, (int)key_len, prk, &tmp_len))
- return NULL;
-
- *prk_len = tmp_len;
- return prk;
-}
-
-static unsigned char *HKDF_Expand(const EVP_MD *evp_md,
- const unsigned char *prk, size_t prk_len,
- const unsigned char *info, size_t info_len,
- unsigned char *okm, size_t okm_len)
-{
- HMAC_CTX *hmac;
- unsigned char *ret = NULL;
-
- unsigned int i;
-
- unsigned char prev[EVP_MAX_MD_SIZE];
-
- size_t done_len = 0, dig_len = EVP_MD_size(evp_md);
-
- size_t n = okm_len / dig_len;
- if (okm_len % dig_len)
- n++;
-
- if (n > 255 || okm == NULL)
- return NULL;
-
- if ((hmac = HMAC_CTX_new()) == NULL)
- return NULL;
-
- if (!HMAC_Init_ex(hmac, prk, (int)prk_len, evp_md, NULL))
- goto err;
-
- for (i = 1; i <= n; i++) {
- size_t copy_len;
- const unsigned char ctr = i;
-
- if (i > 1) {
- if (!HMAC_Init_ex(hmac, NULL, 0, NULL, NULL))
- goto err;
-
- if (!HMAC_Update(hmac, prev, dig_len))
- goto err;
- }
-
- if (!HMAC_Update(hmac, info, info_len))
- goto err;
-
- if (!HMAC_Update(hmac, &ctr, 1))
- goto err;
-
- if (!HMAC_Final(hmac, prev, NULL))
- goto err;
-
- copy_len = (done_len + dig_len > okm_len) ?
- okm_len - done_len :
- dig_len;
-
- memcpy(okm + done_len, prev, copy_len);
-
- done_len += copy_len;
- }
- ret = okm;
-
-err:
- OPENSSL_cleanse(prev, sizeof(prev));
- HMAC_CTX_free(hmac);
- return ret;
-}
-
-unsigned char *HKDF(const EVP_MD *evp_md,
- const unsigned char *salt, size_t salt_len,
- const unsigned char *key, size_t key_len,
- const unsigned char *info, size_t info_len,
- unsigned char *okm, size_t okm_len)
-{
- unsigned char prk[EVP_MAX_MD_SIZE];
- unsigned char *ret;
- size_t prk_len;
-
- if (!HKDF_Extract(evp_md, salt, salt_len, key, key_len, prk, &prk_len))
- return NULL;
-
- ret = HKDF_Expand(evp_md, prk, prk_len, info, info_len, okm, okm_len);
- OPENSSL_cleanse(prk, sizeof(prk));
-
- return ret;
-}
+/*
+
+WhatsApp plugin for Miranda NG
+Copyright © 2019-23 George Hazan
+
+*/
+
+#include "stdafx.h"
+
+MBinBuffer aesDecrypt(
+ const EVP_CIPHER *cipher,
+ const uint8_t *key,
+ const uint8_t *iv,
+ const void *data, size_t dataLen,
+ const void *additionalData, size_t additionalLen)
+{
+ int tag_len = 0, dec_len = 0, final_len = 0;
+ EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
+ EVP_DecryptInit_ex(ctx, cipher, NULL, key, iv);
+
+ if (additionalLen)
+ EVP_DecryptUpdate(ctx, nullptr, &tag_len, (uint8_t *)additionalData, (int)additionalLen);
+
+ if (cipher == EVP_aes_256_gcm()) {
+ dataLen -= 16;
+ EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16, (uint8_t *)data + dataLen);
+ }
+
+ MBinBuffer ret;
+ uint8_t outbuf[2000];
+ for (size_t len = 0; len < dataLen; len += 1024) {
+ size_t portionSize = dataLen - len;
+ EVP_DecryptUpdate(ctx, outbuf, &dec_len, (uint8_t *)data + len, (int)min(portionSize, 1024));
+ ret.append(outbuf, dec_len);
+ }
+
+ EVP_DecryptFinal_ex(ctx, outbuf, &final_len);
+ if (final_len)
+ ret.append(outbuf, final_len);
+
+ EVP_CIPHER_CTX_free(ctx);
+ return ret;
+}
+
+MBinBuffer aesEncrypt(
+ const EVP_CIPHER *cipher,
+ const uint8_t *key,
+ const uint8_t *iv,
+ const void *data, size_t dataLen,
+ const void *additionalData, size_t additionalLen)
+{
+ int tag_len = 0, dec_len = 0, final_len = 0;
+ EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
+ EVP_EncryptInit_ex(ctx, cipher, NULL, key, iv);
+
+ if (additionalLen)
+ EVP_EncryptUpdate(ctx, nullptr, &tag_len, (uint8_t *)additionalData, (int)additionalLen);
+
+ if (cipher == EVP_aes_256_gcm()) {
+ dataLen -= 16;
+ EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16, (uint8_t *)data + dataLen);
+ }
+
+ MBinBuffer ret;
+ uint8_t outbuf[2000];
+ for (size_t len = 0; len < dataLen; len += 1024) {
+ size_t portionSize = dataLen - len;
+ EVP_EncryptUpdate(ctx, outbuf, &dec_len, (uint8_t *)data + len, (int)min(portionSize, 1024));
+ ret.append(outbuf, dec_len);
+ }
+
+ EVP_EncryptFinal_ex(ctx, outbuf, &final_len);
+ if (final_len)
+ ret.append(outbuf, final_len);
+
+ EVP_CIPHER_CTX_free(ctx);
+ return ret;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static unsigned char *HKDF_Extract(const EVP_MD *evp_md,
+ const unsigned char *salt, size_t salt_len,
+ const unsigned char *key, size_t key_len,
+ unsigned char *prk, size_t *prk_len)
+{
+ unsigned int tmp_len;
+
+ if (!HMAC(evp_md, salt, (int)salt_len, key, (int)key_len, prk, &tmp_len))
+ return NULL;
+
+ *prk_len = tmp_len;
+ return prk;
+}
+
+static unsigned char *HKDF_Expand(const EVP_MD *evp_md,
+ const unsigned char *prk, size_t prk_len,
+ const unsigned char *info, size_t info_len,
+ unsigned char *okm, size_t okm_len)
+{
+ HMAC_CTX *hmac;
+ unsigned char *ret = NULL;
+
+ unsigned int i;
+
+ unsigned char prev[EVP_MAX_MD_SIZE];
+
+ size_t done_len = 0, dig_len = EVP_MD_size(evp_md);
+
+ size_t n = okm_len / dig_len;
+ if (okm_len % dig_len)
+ n++;
+
+ if (n > 255 || okm == NULL)
+ return NULL;
+
+ if ((hmac = HMAC_CTX_new()) == NULL)
+ return NULL;
+
+ if (!HMAC_Init_ex(hmac, prk, (int)prk_len, evp_md, NULL))
+ goto err;
+
+ for (i = 1; i <= n; i++) {
+ size_t copy_len;
+ const unsigned char ctr = i;
+
+ if (i > 1) {
+ if (!HMAC_Init_ex(hmac, NULL, 0, NULL, NULL))
+ goto err;
+
+ if (!HMAC_Update(hmac, prev, dig_len))
+ goto err;
+ }
+
+ if (!HMAC_Update(hmac, info, info_len))
+ goto err;
+
+ if (!HMAC_Update(hmac, &ctr, 1))
+ goto err;
+
+ if (!HMAC_Final(hmac, prev, NULL))
+ goto err;
+
+ copy_len = (done_len + dig_len > okm_len) ?
+ okm_len - done_len :
+ dig_len;
+
+ memcpy(okm + done_len, prev, copy_len);
+
+ done_len += copy_len;
+ }
+ ret = okm;
+
+err:
+ OPENSSL_cleanse(prev, sizeof(prev));
+ HMAC_CTX_free(hmac);
+ return ret;
+}
+
+unsigned char *HKDF(const EVP_MD *evp_md,
+ const unsigned char *salt, size_t salt_len,
+ const unsigned char *key, size_t key_len,
+ const unsigned char *info, size_t info_len,
+ unsigned char *okm, size_t okm_len)
+{
+ unsigned char prk[EVP_MAX_MD_SIZE];
+ unsigned char *ret;
+ size_t prk_len;
+
+ if (!HKDF_Extract(evp_md, salt, salt_len, key, key_len, prk, &prk_len))
+ return NULL;
+
+ ret = HKDF_Expand(evp_md, prk, prk_len, info, info_len, okm, okm_len);
+ OPENSSL_cleanse(prk, sizeof(prk));
+
+ return ret;
+}
diff --git a/protocols/WhatsApp/src/db.h b/protocols/WhatsApp/src/db.h
index cb562715f4..1e4bebc6a8 100644
--- a/protocols/WhatsApp/src/db.h
+++ b/protocols/WhatsApp/src/db.h
@@ -1,43 +1,43 @@
-/*
-
-WhatsApp plugin for Miranda NG
-Copyright © 2019-22 George Hazan
-
-*/
-
-#define MODULENAME "WhatsApp"
-
-// DB keys
-#define DBKEY_ID "ID"
-#define DBKEY_DEVICE_ID "DeviceId"
-#define DBKEY_EPHEMERAL_TS "EphemeralTS"
-#define DBKEY_EPHEMERAL_EXPIRE "EphemeralExpire"
-
-#define DBKEY_NOISE_PUB "NoisePublicKey"
-#define DBKEY_NOISE_PRIV "NoisePrivateKey"
-
-#define DBKEY_SIGNED_IDENTITY_PUB "SignedIdentityPublicKey"
-#define DBKEY_SIGNED_IDENTITY_PRIV "SignedIdentityPrivateKey"
-
-#define DBKEY_PREKEY "SignedPreKey1"
-#define DBKEY_PREKEY_NEXT_ID "PrekeyNextId"
-#define DBKEY_PREKEY_UPLOAD_ID "PrekeyUploadId"
-
-#define DBKEY_REG_ID "RegistrationId"
-#define DBKEY_SECRET_KEY "AdvSecretKey"
-
-#define DBKEY_NICK "Nick"
-#define DBKEY_DEF_GROUP "DefaultGroup"
-#define DBKEY_AUTORUNCHATS "AutoRunChats"
-#define DBKEY_AVATAR_TAG "AvatarTag"
-
-#define DBKEY_EVENT_CLIENT_COLBACK "PopupClientColorBack"
-#define DBKEY_EVENT_CLIENT_COLTEXT "PopupClientColorText"
-#define DBKEY_EVENT_CLIENT_TIMEOUT "PopupClientTimeout"
-#define DBKEY_EVENT_CLIENT_DEFAULT "PopupClientColorDefault"
-
-#define DBKEY_EVENT_OTHER_COLBACK "PopupOtherColorBack"
-#define DBKEY_EVENT_OTHER_COLTEXT "PopupOtherColorText"
-#define DBKEY_EVENT_OTHER_TIMEOUT "PopupOtherTimeout"
-#define DBKEY_EVENT_OTHER_DEFAULT "PopupOtherColorDefault"
-
+/*
+
+WhatsApp plugin for Miranda NG
+Copyright © 2019-23 George Hazan
+
+*/
+
+#define MODULENAME "WhatsApp"
+
+// DB keys
+#define DBKEY_ID "ID"
+#define DBKEY_DEVICE_ID "DeviceId"
+#define DBKEY_EPHEMERAL_TS "EphemeralTS"
+#define DBKEY_EPHEMERAL_EXPIRE "EphemeralExpire"
+
+#define DBKEY_NOISE_PUB "NoisePublicKey"
+#define DBKEY_NOISE_PRIV "NoisePrivateKey"
+
+#define DBKEY_SIGNED_IDENTITY_PUB "SignedIdentityPublicKey"
+#define DBKEY_SIGNED_IDENTITY_PRIV "SignedIdentityPrivateKey"
+
+#define DBKEY_PREKEY "SignedPreKey1"
+#define DBKEY_PREKEY_NEXT_ID "PrekeyNextId"
+#define DBKEY_PREKEY_UPLOAD_ID "PrekeyUploadId"
+
+#define DBKEY_REG_ID "RegistrationId"
+#define DBKEY_SECRET_KEY "AdvSecretKey"
+
+#define DBKEY_NICK "Nick"
+#define DBKEY_DEF_GROUP "DefaultGroup"
+#define DBKEY_AUTORUNCHATS "AutoRunChats"
+#define DBKEY_AVATAR_TAG "AvatarTag"
+
+#define DBKEY_EVENT_CLIENT_COLBACK "PopupClientColorBack"
+#define DBKEY_EVENT_CLIENT_COLTEXT "PopupClientColorText"
+#define DBKEY_EVENT_CLIENT_TIMEOUT "PopupClientTimeout"
+#define DBKEY_EVENT_CLIENT_DEFAULT "PopupClientColorDefault"
+
+#define DBKEY_EVENT_OTHER_COLBACK "PopupOtherColorBack"
+#define DBKEY_EVENT_OTHER_COLTEXT "PopupOtherColorText"
+#define DBKEY_EVENT_OTHER_TIMEOUT "PopupOtherTimeout"
+#define DBKEY_EVENT_OTHER_DEFAULT "PopupOtherColorDefault"
+
diff --git a/protocols/WhatsApp/src/dicts.h b/protocols/WhatsApp/src/dicts.h
index 7b84487d14..ab37f10abb 100644
--- a/protocols/WhatsApp/src/dicts.h
+++ b/protocols/WhatsApp/src/dicts.h
@@ -1,194 +1,194 @@
-/*
-
-WhatsApp plugin for Miranda NG
-Copyright © 2019-22 George Hazan
-
-*/
-
-static char *SingleByteTokens[] = {
- "", "xmlstreamstart", "xmlstreamend", "s.whatsapp.net", "type", "participant", "from", "receipt", "id", "broadcast", "status", "message", "notification", "notify", "to", "jid", "user",
- "class", "offline", "g.us", "result", "mediatype", "enc", "skmsg", "off_cnt", "xmlns", "presence", "participants", "ack", "t", "iq", "device_hash", "read", "value", "media", "picture",
- "chatstate", "unavailable", "text", "urn:xmpp:whatsapp:push", "devices", "verified_name", "contact", "composing", "edge_routing", "routing_info", "item", "image", "verified_level",
- "get", "fallback_hostname", "2", "media_conn", "1", "v", "handshake", "fallback_class", "count", "config", "offline_preview", "download_buckets", "w:profile:picture", "set",
- "creation", "location", "fallback_ip4", "msg", "urn:xmpp:ping", "fallback_ip6", "call-creator", "relaylatency", "success", "subscribe", "video", "business_hours_config",
- "platform", "hostname", "version", "unknown", "0", "ping", "hash", "edit", "subject", "max_buckets", "download", "delivery", "props", "sticker", "name", "last", "contacts",
- "business", "primary", "preview", "w:p", "pkmsg", "call-id", "retry", "prop", "call", "auth_ttl", "available", "relay_id", "last_id", "day_of_week", "w", "host", "seen",
- "bits", "list", "atn", "upload", "is_new", "w:stats", "key", "paused", "specific_hours", "multicast", "stream:error", "mmg.whatsapp.net", "code", "deny", "played", "profile",
- "fna", "device-list", "close_time", "latency", "gcm", "pop", "audio", "26", "w:web", "open_time", "error", "auth", "ip4", "update", "profile_options", "config_value", "category",
- "catalog_not_created", "00", "config_code", "mode", "catalog_status", "ip6", "blocklist", "registration", "7", "web", "fail", "w:m", "cart_enabled", "ttl", "gif", "300",
- "device_orientation", "identity", "query", "401", "media-gig2-1.cdn.whatsapp.net", "in", "3", "te2", "add", "fallback", "categories", "ptt", "encrypt", "notice",
- "thumbnail-document", "item-not-found", "12", "thumbnail-image", "stage", "thumbnail-link", "usync", "out", "thumbnail-video", "8", "01", "context", "sidelist",
- "thumbnail-gif", "terminate", "not-authorized", "orientation", "dhash", "capability", "side_list", "md-app-state", "description", "serial", "readreceipts", "te",
- "business_hours", "md-msg-hist", "tag", "attribute_padding", "document", "open_24h", "delete", "expiration", "active", "prev_v_id", "true", "passive", "index", "4",
- "conflict", "remove", "w:gp2", "config_expo_key", "screen_height", "replaced", "02", "screen_width", "uploadfieldstat", "2:47DEQpj8", "media-bog1-1.cdn.whatsapp.net",
- "encopt", "url", "catalog_exists", "keygen", "rate", "offer", "opus", "media-mia3-1.cdn.whatsapp.net", "privacy", "media-mia3-2.cdn.whatsapp.net", "signature",
- "preaccept", "token_id", "media-eze1-1.cdn.whatsapp.net"
-};
-
-static char *dict0[] = {
- "media-for1-1.cdn.whatsapp.net", "relay", "media-gru2-2.cdn.whatsapp.net", "uncompressed", "medium", "voip_settings", "device", "reason",
- "media-lim1-1.cdn.whatsapp.net", "media-qro1-2.cdn.whatsapp.net", "media-gru1-2.cdn.whatsapp.net", "action", "features", "media-gru2-1.cdn.whatsapp.net",
- "media-gru1-1.cdn.whatsapp.net", "media-otp1-1.cdn.whatsapp.net", "kyc-id", "priority", "phash", "mute", "token", "100", "media-qro1-1.cdn.whatsapp.net",
- "none", "media-mrs2-2.cdn.whatsapp.net", "sign_credential", "03", "media-mrs2-1.cdn.whatsapp.net", "protocol", "timezone", "transport", "eph_setting", "1080",
- "original_dimensions", "media-frx5-1.cdn.whatsapp.net", "background", "disable", "original_image_url", "5", "transaction-id", "direct_path", "103", "appointment_only",
- "request_image_url", "peer_pid", "address", "105", "104", "102", "media-cdt1-1.cdn.whatsapp.net", "101", "109", "110", "106", "background_location", "v_id", "sync",
- "status-old", "111", "107", "ppic", "media-scl2-1.cdn.whatsapp.net", "business_profile", "108", "invite", "04", "audio_duration", "media-mct1-1.cdn.whatsapp.net",
- "media-cdg2-1.cdn.whatsapp.net", "media-los2-1.cdn.whatsapp.net", "invis", "net", "voip_payload_type", "status-revoke-delay", "404", "state", "use_correct_order_for_hmac_sha1",
- "ver", "media-mad1-1.cdn.whatsapp.net", "order", "540", "skey", "blinded_credential", "android", "contact_remove", "enable_downlink_relay_latency_only", "duration",
- "enable_vid_one_way_codec_nego", "6", "media-sof1-1.cdn.whatsapp.net", "accept", "all", "signed_credential", "media-atl3-1.cdn.whatsapp.net", "media-lhr8-1.cdn.whatsapp.net",
- "website", "05", "latitude", "media-dfw5-1.cdn.whatsapp.net", "forbidden", "enable_audio_piggyback_network_mtu_fix", "media-dfw5-2.cdn.whatsapp.net", "note.m4r",
- "media-atl3-2.cdn.whatsapp.net", "jb_nack_discard_count_fix", "longitude", "Opening.m4r", "media-arn2-1.cdn.whatsapp.net", "email", "timestamp", "admin",
- "media-pmo1-1.cdn.whatsapp.net", "America/Sao_Paulo", "contact_add", "media-sin6-1.cdn.whatsapp.net", "interactive", "8000", "acs_public_key",
- "sigquit_anr_detector_release_rollover_percent", "media.fmed1-2.fna.whatsapp.net", "groupadd", "enabled_for_video_upgrade", "latency_update_threshold",
- "media-frt3-2.cdn.whatsapp.net", "calls_row_constraint_layout", "media.fgbb2-1.fna.whatsapp.net", "mms4_media_retry_notification_encryption_enabled", "timeout",
- "media-sin6-3.cdn.whatsapp.net", "audio_nack_jitter_multiplier", "jb_discard_count_adjust_pct_rc", "audio_reserve_bps", "delta", "account_sync", "default",
- "media.fjed4-6.fna.whatsapp.net", "06", "lock_video_orientation", "media-frt3-1.cdn.whatsapp.net", "w:g2", "media-sin6-2.cdn.whatsapp.net", "audio_nack_algo_mask",
- "media.fgbb2-2.fna.whatsapp.net", "media.fmed1-1.fna.whatsapp.net", "cond_range_target_bitrate", "mms4_server_error_receipt_encryption_enabled", "vid_rc_dyn", "fri",
- "cart_v1_1_order_message_changes_enabled", "reg_push", "jb_hist_deposit_value", "privatestats", "media.fist7-2.fna.whatsapp.net", "thu", "jb_discard_count_adjust_pct",
- "mon", "group_call_video_maximization_enabled", "mms_cat_v1_forward_hot_override_enabled", "audio_nack_new_rtt", "media.fsub2-3.fna.whatsapp.net",
- "media_upload_aggressive_retry_exponential_backoff_enabled", "tue", "wed", "media.fruh4-2.fna.whatsapp.net", "audio_nack_max_seq_req", "max_rtp_audio_packet_resends",
- "jb_hist_max_cdf_value", "07", "audio_nack_max_jb_delay", "mms_forward_partially_downloaded_video", "media-lcy1-1.cdn.whatsapp.net", "resume", "jb_inband_fec_aware",
- "new_commerce_entry_point_enabled", "480", "payments_upi_generate_qr_amount_limit", "sigquit_anr_detector_rollover_percent", "media.fsdu2-1.fna.whatsapp.net", "fbns",
- "aud_pkt_reorder_pct", "dec", "stop_probing_before_accept_send", "media_upload_max_aggressive_retries", "edit_business_profile_new_mode_enabled",
- "media.fhex4-1.fna.whatsapp.net", "media.fjed4-3.fna.whatsapp.net", "sigquit_anr_detector_64bit_rollover_percent", "cond_range_ema_jb_last_delay",
- "watls_enable_early_data_http_get", "media.fsdu2-2.fna.whatsapp.net", "message_qr_disambiguation_enabled", "media-mxp1-1.cdn.whatsapp.net", "sat", "vertical",
- "media.fruh4-5.fna.whatsapp.net", "200", "media-sof1-2.cdn.whatsapp.net", "-1", "height", "product_catalog_hide_show_items_enabled", "deep_copy_frm_last",
- "tsoffline", "vp8/h.264", "media.fgye5-3.fna.whatsapp.net", "media.ftuc1-2.fna.whatsapp.net", "smb_upsell_chat_banner_enabled", "canonical", "08", "9", ".",
- "media.fgyd4-4.fna.whatsapp.net", "media.fsti4-1.fna.whatsapp.net", "mms_vcache_aggregation_enabled", "mms_hot_content_timespan_in_seconds", "nse_ver", "rte",
- "third_party_sticker_web_sync", "cond_range_target_total_bitrate", "media_upload_aggressive_retry_enabled", "instrument_spam_report_enabled", "disable_reconnect_tone",
- "move_media_folder_from_sister_app", "one_tap_calling_in_group_chat_size", "10", "storage_mgmt_banner_threshold_mb", "enable_backup_passive_mode", "sharechat_inline_player_enabled",
- "media.fcnq2-1.fna.whatsapp.net", "media.fhex4-2.fna.whatsapp.net", "media.fist6-3.fna.whatsapp.net", "ephemeral_drop_column_stage", "reconnecting_after_network_change_threshold_ms",
- "media-lhr8-2.cdn.whatsapp.net", "cond_jb_last_delay_ema_alpha", "entry_point_block_logging_enabled", "critical_event_upload_log_config", "respect_initial_bitrate_estimate",
- "smaller_image_thumbs_status_enabled", "media.fbtz1-4.fna.whatsapp.net", "media.fjed4-1.fna.whatsapp.net", "width", "720", "enable_frame_dropper", "enable_one_side_mode",
- "urn:xmpp:whatsapp:dirty", "new_sticker_animation_behavior_v2", "media.flim3-2.fna.whatsapp.net", "media.fuio6-2.fna.whatsapp.net", "skip_forced_signaling", "dleq_proof",
- "status_video_max_bitrate", "lazy_send_probing_req", "enhanced_storage_management", "android_privatestats_endpoint_dit_enabled", "media.fscl13-2.fna.whatsapp.net", "video_duration"
-};
-
-static char *dict1[] = {
- "group_call_discoverability_enabled", "media.faep9-2.fna.whatsapp.net", "msgr", "bloks_loggedin_access_app_id", "db_status_migration_step", "watls_prefer_ip6",
- "jabber:iq:privacy", "68", "media.fsaw1-11.fna.whatsapp.net", "mms4_media_conn_persist_enabled", "animated_stickers_thread_clean_up", "media.fcgk3-2.fna.whatsapp.net",
- "media.fcgk4-6.fna.whatsapp.net", "media.fgye5-2.fna.whatsapp.net", "media.flpb1-1.fna.whatsapp.net", "media.fsub2-1.fna.whatsapp.net", "media.fuio6-3.fna.whatsapp.net",
- "not-allowed", "partial_pjpeg_bw_threshold", "cap_estimated_bitrate", "mms_chatd_resume_check_over_thrift", "smb_upsell_business_profile_enabled", "product_catalog_webclient",
- "groups", "sigquit_anr_detector_release_updated_rollout", "syncd_key_rotation_enabled", "media.fdmm2-1.fna.whatsapp.net", "media-hou1-1.cdn.whatsapp.net",
- "remove_old_chat_notifications", "smb_biztools_deeplink_enabled", "use_downloadable_filters_int", "group_qr_codes_enabled", "max_receipt_processing_time",
- "optimistic_image_processing_enabled", "smaller_video_thumbs_status_enabled", "watls_early_data", "reconnecting_before_relay_failover_threshold_ms", "cond_range_packet_loss_pct",
- "groups_privacy_blacklist", "status-revoke-drop", "stickers_animated_thumbnail_download", "dedupe_transcode_shared_images", "dedupe_transcode_shared_videos",
- "media.fcnq2-2.fna.whatsapp.net", "media.fgyd4-1.fna.whatsapp.net", "media.fist7-1.fna.whatsapp.net", "media.flim3-3.fna.whatsapp.net", "add_contact_by_qr_enabled",
- "https://faq.whatsapp.com/payments", "multicast_limit_global", "sticker_notification_preview", "smb_better_catalog_list_adapters_enabled", "bloks_use_minscript_android",
- "pen_smoothing_enabled", "media.fcgk4-5.fna.whatsapp.net", "media.fevn1-3.fna.whatsapp.net", "media.fpoj7-1.fna.whatsapp.net", "media-arn2-2.cdn.whatsapp.net",
- "reconnecting_before_network_change_threshold_ms", "android_media_use_fresco_for_gifs", "cond_in_congestion", "status_image_max_edge", "sticker_search_enabled",
- "starred_stickers_web_sync", "db_blank_me_jid_migration_step", "media.fist6-2.fna.whatsapp.net", "media.ftuc1-1.fna.whatsapp.net", "09", "anr_fast_logs_upload_rollout",
- "camera_core_integration_enabled", "11", "third_party_sticker_caching", "thread_dump_contact_support", "wam_privatestats_enabled", "vcard_as_document_size_kb", "maxfpp",
- "fbip", "ephemeral_allow_group_members", "media-bom1-2.cdn.whatsapp.net", "media-xsp1-1.cdn.whatsapp.net", "disable_prewarm", "frequently_forwarded_max",
- "media.fbtz1-5.fna.whatsapp.net", "media.fevn7-1.fna.whatsapp.net", "media.fgyd4-2.fna.whatsapp.net", "sticker_tray_animation_fully_visible_items",
- "green_alert_banner_duration", "reconnecting_after_p2p_failover_threshold_ms", "connected", "share_biz_vcard_enabled", "stickers_animation", "0a", "1200", "WhatsApp",
- "group_description_length", "p_v_id", "payments_upi_intent_transaction_limit", "frequently_forwarded_messages", "media-xsp1-2.cdn.whatsapp.net",
- "media.faep8-1.fna.whatsapp.net", "media.faep8-2.fna.whatsapp.net", "media.faep9-1.fna.whatsapp.net", "media.fdmm2-2.fna.whatsapp.net", "media.fgzt3-1.fna.whatsapp.net",
- "media.flim4-2.fna.whatsapp.net", "media.frao1-1.fna.whatsapp.net", "media.fscl9-2.fna.whatsapp.net", "media.fsub2-2.fna.whatsapp.net", "superadmin",
- "media.fbog10-1.fna.whatsapp.net", "media.fcgh28-1.fna.whatsapp.net", "media.fjdo10-1.fna.whatsapp.net", "third_party_animated_sticker_import", "delay_fec",
- "attachment_picker_refresh", "android_linked_devices_re_auth_enabled", "rc_dyn", "green_alert_block_jitter", "add_contact_logging_enabled", "biz_message_logging_enabled",
- "conversation_media_preview_v2", "media-jnb1-1.cdn.whatsapp.net", "ab_key", "media.fcgk4-2.fna.whatsapp.net", "media.fevn1-1.fna.whatsapp.net", "media.fist6-1.fna.whatsapp.net",
- "media.fruh4-4.fna.whatsapp.net", "media.fsti4-2.fna.whatsapp.net", "mms_vcard_autodownload_size_kb", "watls_enabled", "notif_ch_override_off", "media.fsaw1-14.fna.whatsapp.net",
- "media.fscl13-1.fna.whatsapp.net", "db_group_participant_migration_step", "1020", "cond_range_sterm_rtt", "invites_logging_enabled", "triggered_block_enabled",
- "group_call_max_participants", "media-iad3-1.cdn.whatsapp.net", "product_catalog_open_deeplink", "shops_required_tos_version", "image_max_kbytes",
- "cond_low_quality_vid_mode", "db_receipt_migration_step", "jb_early_prob_hist_shrink", "media.fdmm2-3.fna.whatsapp.net", "media.fdmm2-4.fna.whatsapp.net",
- "media.fruh4-1.fna.whatsapp.net", "media.fsaw2-2.fna.whatsapp.net", "remove_geolocation_videos", "new_animation_behavior", "fieldstats_beacon_chance", "403",
- "authkey_reset_on_ban", "continuous_ptt_playback", "reconnecting_after_relay_failover_threshold_ms", "false", "group", "sun", "conversation_swipe_to_reply",
- "ephemeral_messages_setting", "smaller_video_thumbs_enabled", "md_device_sync_enabled", "bloks_shops_pdp_url_regex", "lasso_integration_enabled",
- "media-bom1-1.cdn.whatsapp.net", "new_backup_format_enabled", "256", "media.faep6-1.fna.whatsapp.net", "media.fasr1-1.fna.whatsapp.net", "media.fbtz1-7.fna.whatsapp.net",
- "media.fesb4-1.fna.whatsapp.net", "media.fjdo1-2.fna.whatsapp.net", "media.frba2-1.fna.whatsapp.net", "watls_no_dns", "600", "db_broadcast_me_jid_migration_step",
- "new_wam_runtime_enabled", "group_update", "enhanced_block_enabled", "sync_wifi_threshold_kb", "mms_download_nc_cat", "bloks_minification_enabled",
- "ephemeral_messages_enabled", "reject", "voip_outgoing_xml_signaling", "creator", "dl_bw", "payments_request_messages", "target_bitrate", "bloks_rendercore_enabled",
- "media-hbe1-1.cdn.whatsapp.net", "media-hel3-1.cdn.whatsapp.net", "media-kut2-2.cdn.whatsapp.net", "media-lax3-1.cdn.whatsapp.net", "media-lax3-2.cdn.whatsapp.net",
- "sticker_pack_deeplink_enabled", "hq_image_bw_threshold", "status_info", "voip", "dedupe_transcode_videos", "grp_uii_cleanup", "linked_device_max_count",
- "media.flim1-1.fna.whatsapp.net", "media.fsaw2-1.fna.whatsapp.net", "reconnecting_after_call_active_threshold_ms", "1140", "catalog_pdp_new_design",
- "media.fbtz1-10.fna.whatsapp.net", "media.fsaw1-15.fna.whatsapp.net", "0b", "consumer_rc_provider", "mms_async_fast_forward_ttl", "jb_eff_size_fix",
- "voip_incoming_xml_signaling", "media_provider_share_by_uuid", "suspicious_links", "dedupe_transcode_images", "green_alert_modal_start", "media-cgk1-1.cdn.whatsapp.net",
- "media-lga3-1.cdn.whatsapp.net", "template_doc_mime_types", "important_messages", "user_add", "vcard_max_size_kb", "media.fada2-1.fna.whatsapp.net",
- "media.fbog2-5.fna.whatsapp.net", "media.fbtz1-3.fna.whatsapp.net", "media.fcgk3-1.fna.whatsapp.net", "media.fcgk7-1.fna.whatsapp.net", "media.flim1-3.fna.whatsapp.net",
- "media.fscl9-1.fna.whatsapp.net", "ctwa_context_enterprise_enabled", "media.fsaw1-13.fna.whatsapp.net", "media.fuio11-2.fna.whatsapp.net", "status_collapse_muted",
- "db_migration_level_force", "recent_stickers_web_sync", "bloks_session_state", "bloks_shops_enabled", "green_alert_setting_deep_links_enabled", "restrict_groups",
- "battery", "green_alert_block_start", "refresh", "ctwa_context_enabled", "md_messaging_enabled", "status_image_quality", "md_blocklist_v2_server",
- "media-del1-1.cdn.whatsapp.net", "13", "userrate", "a_v_id", "cond_rtt_ema_alpha", "invalid"
-};
-
-static char *dict2[] = {
- "media.fada1-1.fna.whatsapp.net", "media.fadb3-2.fna.whatsapp.net", "media.fbhz2-1.fna.whatsapp.net", "media.fcor2-1.fna.whatsapp.net", "media.fjed4-2.fna.whatsapp.net",
- "media.flhe4-1.fna.whatsapp.net", "media.frak1-2.fna.whatsapp.net", "media.fsub6-3.fna.whatsapp.net", "media.fsub6-7.fna.whatsapp.net", "media.fvvi1-1.fna.whatsapp.net",
- "search_v5_eligible", "wam_real_time_enabled", "report_disk_event", "max_tx_rott_based_bitrate", "product", "media.fjdo10-2.fna.whatsapp.net", "video_frame_crc_sample_interval",
- "media_max_autodownload", "15", "h.264", "wam_privatestats_buffer_count", "md_phash_v2_enabled", "account_transfer_enabled", "business_product_catalog",
- "enable_non_dyn_codec_param_fix", "is_user_under_epd_jurisdiction", "media.fbog2-4.fna.whatsapp.net", "media.fbtz1-2.fna.whatsapp.net", "media.fcfc1-1.fna.whatsapp.net",
- "media.fjed4-5.fna.whatsapp.net", "media.flhe4-2.fna.whatsapp.net", "media.flim1-2.fna.whatsapp.net", "media.flos5-1.fna.whatsapp.net", "android_key_store_auth_ver",
- "010", "anr_process_monitor", "delete_old_auth_key", "media.fcor10-3.fna.whatsapp.net", "storage_usage_enabled", "android_camera2_support_level", "dirty",
- "consumer_content_provider", "status_video_max_duration", "0c", "bloks_cache_enabled", "media.fadb2-2.fna.whatsapp.net", "media.fbko1-1.fna.whatsapp.net",
- "media.fbtz1-9.fna.whatsapp.net", "media.fcgk4-4.fna.whatsapp.net", "media.fesb4-2.fna.whatsapp.net", "media.fevn1-2.fna.whatsapp.net", "media.fist2-4.fna.whatsapp.net",
- "media.fjdo1-1.fna.whatsapp.net", "media.fruh4-6.fna.whatsapp.net", "media.fsrg5-1.fna.whatsapp.net", "media.fsub6-6.fna.whatsapp.net", "minfpp", "5000", "locales",
- "video_max_bitrate", "use_new_auth_key", "bloks_http_enabled", "heartbeat_interval", "media.fbog11-1.fna.whatsapp.net", "ephemeral_group_query_ts", "fec_nack",
- "search_in_storage_usage", "c", "media-amt2-1.cdn.whatsapp.net", "linked_devices_ui_enabled", "14", "async_data_load_on_startup", "voip_incoming_xml_ack", "16",
- "db_migration_step", "init_bwe", "max_participants", "wam_buffer_count", "media.fada2-2.fna.whatsapp.net", "media.fadb3-1.fna.whatsapp.net", "media.fcor2-2.fna.whatsapp.net",
- "media.fdiy1-2.fna.whatsapp.net", "media.frba3-2.fna.whatsapp.net", "media.fsaw2-3.fna.whatsapp.net", "1280", "status_grid_enabled", "w:biz", "product_catalog_deeplink",
- "media.fgye10-2.fna.whatsapp.net", "media.fuio11-1.fna.whatsapp.net", "optimistic_upload", "work_manager_init", "lc", "catalog_message", "cond_net_medium",
- "enable_periodical_aud_rr_processing", "cond_range_ema_rtt", "media-tir2-1.cdn.whatsapp.net", "frame_ms", "group_invite_sending", "payments_web_enabled",
- "wallpapers_v2", "0d", "browser", "hq_image_max_edge", "image_edit_zoom", "linked_devices_re_auth_enabled", "media.faly3-2.fna.whatsapp.net",
- "media.fdoh5-3.fna.whatsapp.net", "media.fesb3-1.fna.whatsapp.net", "media.fknu1-1.fna.whatsapp.net", "media.fmex3-1.fna.whatsapp.net", "media.fruh4-3.fna.whatsapp.net",
- "255", "web_upgrade_to_md_modal", "audio_piggyback_timeout_msec", "enable_audio_oob_fec_feature", "from_ip", "image_max_edge", "message_qr_enabled", "powersave",
- "receipt_pre_acking", "video_max_edge", "full", "011", "012", "enable_audio_oob_fec_for_sender", "md_voip_enabled", "enable_privatestats", "max_fec_ratio",
- "payments_cs_faq_url", "media-xsp1-3.cdn.whatsapp.net", "hq_image_quality", "media.fasr1-2.fna.whatsapp.net", "media.fbog3-1.fna.whatsapp.net",
- "media.ffjr1-6.fna.whatsapp.net", "media.fist2-3.fna.whatsapp.net", "media.flim4-3.fna.whatsapp.net", "media.fpbc2-4.fna.whatsapp.net", "media.fpku1-1.fna.whatsapp.net",
- "media.frba1-1.fna.whatsapp.net", "media.fudi1-1.fna.whatsapp.net", "media.fvvi1-2.fna.whatsapp.net", "gcm_fg_service", "enable_dec_ltr_size_check", "clear", "lg",
- "media.fgru11-1.fna.whatsapp.net", "18", "media-lga3-2.cdn.whatsapp.net", "pkey", "0e", "max_subject", "cond_range_lterm_rtt", "announcement_groups", "biz_profile_options",
- "s_t", "media.fabv2-1.fna.whatsapp.net", "media.fcai3-1.fna.whatsapp.net", "media.fcgh1-1.fna.whatsapp.net", "media.fctg1-4.fna.whatsapp.net", "media.fdiy1-1.fna.whatsapp.net",
- "media.fisb4-1.fna.whatsapp.net", "media.fpku1-2.fna.whatsapp.net", "media.fros9-1.fna.whatsapp.net", "status_v3_text", "usync_sidelist", "17", "announcement", "...",
- "md_group_notification", "0f", "animated_pack_in_store", "013", "America/Mexico_City", "1260", "media-ams4-1.cdn.whatsapp.net", "media-cgk1-2.cdn.whatsapp.net",
- "media-cpt1-1.cdn.whatsapp.net", "media-maa2-1.cdn.whatsapp.net", "media.fgye10-1.fna.whatsapp.net", "e", "catalog_cart", "hfm_string_changes", "init_bitrate",
- "packless_hsm", "group_info", "America/Belem", "50", "960", "cond_range_bwe", "decode", "encode", "media.fada1-8.fna.whatsapp.net", "media.fadb1-2.fna.whatsapp.net",
- "media.fasu6-1.fna.whatsapp.net", "media.fbog4-1.fna.whatsapp.net", "media.fcgk9-2.fna.whatsapp.net", "media.fdoh5-2.fna.whatsapp.net", "media.ffjr1-2.fna.whatsapp.net",
- "media.fgua1-1.fna.whatsapp.net", "media.fgye1-1.fna.whatsapp.net", "media.fist1-4.fna.whatsapp.net", "media.fpbc2-2.fna.whatsapp.net", "media.fres2-1.fna.whatsapp.net",
- "media.fsdq1-2.fna.whatsapp.net", "media.fsub6-5.fna.whatsapp.net", "profilo_enabled", "template_hsm", "use_disorder_prefetching_timer", "video_codec_priority",
- "vpx_max_qp", "ptt_reduce_recording_delay", "25", "iphone", "Windows", "s_o", "Africa/Lagos", "abt", "media-kut2-1.cdn.whatsapp.net", "media-mba1-1.cdn.whatsapp.net",
- "media-mxp1-2.cdn.whatsapp.net", "md_blocklist_v2", "url_text", "enable_short_offset", "group_join_permissions", "enable_audio_piggyback_feature", "image_quality",
- "media.fcgk7-2.fna.whatsapp.net", "media.fcgk8-2.fna.whatsapp.net", "media.fclo7-1.fna.whatsapp.net", "media.fcmn1-1.fna.whatsapp.net", "media.feoh1-1.fna.whatsapp.net",
- "media.fgyd4-3.fna.whatsapp.net", "media.fjed4-4.fna.whatsapp.net", "media.flim1-4.fna.whatsapp.net", "media.flim2-4.fna.whatsapp.net", "media.fplu6-1.fna.whatsapp.net",
- "media.frak1-1.fna.whatsapp.net", "media.fsdq1-1.fna.whatsapp.net", "to_ip", "015", "vp8", "19", "21", "1320", "auth_key_ver", "message_processing_dedup", "server-error",
- "wap4_enabled", "420", "014", "cond_range_rtt", "ptt_fast_lock_enabled", "media-ort2-1.cdn.whatsapp.net", "fwd_ui_start_ts"
-};
-
-static char *dict3[] = {
- "contact_blacklist", "Asia/Jakarta", "media.fepa10-1.fna.whatsapp.net", "media.fmex10-3.fna.whatsapp.net", "disorder_prefetching_start_when_empty", "America/Bogota",
- "use_local_probing_rx_bitrate", "America/Argentina/Buenos_Aires", "cross_post", "media.fabb1-1.fna.whatsapp.net", "media.fbog4-2.fna.whatsapp.net", "media.fcgk9-1.fna.whatsapp.net",
- "media.fcmn2-1.fna.whatsapp.net", "media.fdel3-1.fna.whatsapp.net", "media.ffjr1-1.fna.whatsapp.net", "media.fgdl5-1.fna.whatsapp.net", "media.flpb1-2.fna.whatsapp.net",
- "media.fmex2-1.fna.whatsapp.net", "media.frba2-2.fna.whatsapp.net", "media.fros2-2.fna.whatsapp.net", "media.fruh2-1.fna.whatsapp.net", "media.fybz2-2.fna.whatsapp.net",
- "options", "20", "a", "017", "018", "mute_always", "user_notice", "Asia/Kolkata", "gif_provider", "locked", "media-gua1-1.cdn.whatsapp.net", "piggyback_exclude_force_flush",
- "24", "media.frec39-1.fna.whatsapp.net", "user_remove", "file_max_size", "cond_packet_loss_pct_ema_alpha", "media.facc1-1.fna.whatsapp.net", "media.fadb2-1.fna.whatsapp.net",
- "media.faly3-1.fna.whatsapp.net", "media.fbdo6-2.fna.whatsapp.net", "media.fcmn2-2.fna.whatsapp.net", "media.fctg1-3.fna.whatsapp.net", "media.ffez1-2.fna.whatsapp.net",
- "media.fist1-3.fna.whatsapp.net", "media.fist2-2.fna.whatsapp.net", "media.flim2-2.fna.whatsapp.net", "media.fmct2-3.fna.whatsapp.net", "media.fpei3-1.fna.whatsapp.net",
- "media.frba3-1.fna.whatsapp.net", "media.fsdu8-2.fna.whatsapp.net", "media.fstu2-1.fna.whatsapp.net", "media_type", "receipt_agg", "016", "enable_pli_for_crc_mismatch",
- "live", "enc_rekey", "frskmsg", "d", "media.fdel11-2.fna.whatsapp.net", "proto", "2250", "audio_piggyback_enable_cache", "skip_nack_if_ltrp_sent", "mark_dtx_jb_frames",
- "web_service_delay", "7282", "catalog_send_all", "outgoing", "360", "30", "LIMITED", "019", "audio_picker", "bpv2_phase", "media.fada1-7.fna.whatsapp.net",
- "media.faep7-1.fna.whatsapp.net", "media.fbko1-2.fna.whatsapp.net", "media.fbni1-2.fna.whatsapp.net", "media.fbtz1-1.fna.whatsapp.net", "media.fbtz1-8.fna.whatsapp.net",
- "media.fcjs3-1.fna.whatsapp.net", "media.fesb3-2.fna.whatsapp.net", "media.fgdl5-4.fna.whatsapp.net", "media.fist2-1.fna.whatsapp.net", "media.flhe2-2.fna.whatsapp.net",
- "media.flim2-1.fna.whatsapp.net", "media.fmex1-1.fna.whatsapp.net", "media.fpat3-2.fna.whatsapp.net", "media.fpat3-3.fna.whatsapp.net", "media.fros2-1.fna.whatsapp.net",
- "media.fsdu8-1.fna.whatsapp.net", "media.fsub3-2.fna.whatsapp.net", "payments_chat_plugin", "cond_congestion_no_rtcp_thr", "green_alert", "not-a-biz", "..",
- "shops_pdp_urls_config", "source", "media-dus1-1.cdn.whatsapp.net", "mute_video", "01b", "currency", "max_keys", "resume_check", "contact_array", "qr_scanning", "23",
- "b", "media.fbfh15-1.fna.whatsapp.net", "media.flim22-1.fna.whatsapp.net", "media.fsdu11-1.fna.whatsapp.net", "media.fsdu15-1.fna.whatsapp.net", "Chrome", "fts_version",
- "60", "media.fada1-6.fna.whatsapp.net", "media.faep4-2.fna.whatsapp.net", "media.fbaq5-1.fna.whatsapp.net", "media.fbni1-1.fna.whatsapp.net", "media.fcai3-2.fna.whatsapp.net",
- "media.fdel3-2.fna.whatsapp.net", "media.fdmm3-2.fna.whatsapp.net", "media.fhex3-1.fna.whatsapp.net", "media.fisb4-2.fna.whatsapp.net", "media.fkhi5-2.fna.whatsapp.net",
- "media.flos2-1.fna.whatsapp.net", "media.fmct2-1.fna.whatsapp.net", "media.fntr7-1.fna.whatsapp.net", "media.frak3-1.fna.whatsapp.net", "media.fruh5-2.fna.whatsapp.net",
- "media.fsub6-1.fna.whatsapp.net", "media.fuab1-2.fna.whatsapp.net", "media.fuio1-1.fna.whatsapp.net", "media.fver1-1.fna.whatsapp.net", "media.fymy1-1.fna.whatsapp.net",
- "product_catalog", "1380", "audio_oob_fec_max_pkts", "22", "254", "media-ort2-2.cdn.whatsapp.net", "media-sjc3-1.cdn.whatsapp.net", "1600", "01a", "01c", "405",
- "key_frame_interval", "body", "media.fcgh20-1.fna.whatsapp.net", "media.fesb10-2.fna.whatsapp.net", "125", "2000", "media.fbsb1-1.fna.whatsapp.net",
- "media.fcmn3-2.fna.whatsapp.net", "media.fcpq1-1.fna.whatsapp.net", "media.fdel1-2.fna.whatsapp.net", "media.ffor2-1.fna.whatsapp.net", "media.fgdl1-4.fna.whatsapp.net",
- "media.fhex2-1.fna.whatsapp.net", "media.fist1-2.fna.whatsapp.net", "media.fjed5-2.fna.whatsapp.net", "media.flim6-4.fna.whatsapp.net", "media.flos2-2.fna.whatsapp.net",
- "media.fntr6-2.fna.whatsapp.net", "media.fpku3-2.fna.whatsapp.net", "media.fros8-1.fna.whatsapp.net", "media.fymy1-2.fna.whatsapp.net", "ul_bw", "ltrp_qp_offset",
- "request", "nack", "dtx_delay_state_reset", "timeoffline", "28", "01f", "32", "enable_ltr_pool", "wa_msys_crypto", "01d", "58", "dtx_freeze_hg_update",
- "nack_if_rpsi_throttled", "253", "840", "media.famd15-1.fna.whatsapp.net", "media.fbog17-2.fna.whatsapp.net", "media.fcai19-2.fna.whatsapp.net", "media.fcai21-4.fna.whatsapp.net",
- "media.fesb10-4.fna.whatsapp.net", "media.fesb10-5.fna.whatsapp.net", "media.fmaa12-1.fna.whatsapp.net", "media.fmex11-3.fna.whatsapp.net", "media.fpoa33-1.fna.whatsapp.net",
- "1050", "021", "clean", "cond_range_ema_packet_loss_pct", "media.fadb6-5.fna.whatsapp.net", "media.faqp4-1.fna.whatsapp.net", "media.fbaq3-1.fna.whatsapp.net",
- "media.fbel2-1.fna.whatsapp.net", "media.fblr4-2.fna.whatsapp.net", "media.fclo8-1.fna.whatsapp.net", "media.fcoo1-2.fna.whatsapp.net", "media.ffjr1-4.fna.whatsapp.net",
- "media.ffor9-1.fna.whatsapp.net", "media.fisb3-1.fna.whatsapp.net", "media.fkhi2-2.fna.whatsapp.net", "media.fkhi4-1.fna.whatsapp.net", "media.fpbc1-2.fna.whatsapp.net",
- "media.fruh2-2.fna.whatsapp.net", "media.fruh5-1.fna.whatsapp.net", "media.fsub3-1.fna.whatsapp.net", "payments_transaction_limit", "252", "27", "29", "tintagel", "01e",
- "237", "780", "callee_updated_payload", "020", "257", "price", "025", "239", "payments_cs_phone_number", "mediaretry", "w:auth:backup:token", "Glass.caf", "max_bitrate",
- "240", "251", "660", "media.fbog16-1.fna.whatsapp.net", "media.fcgh21-1.fna.whatsapp.net", "media.fkul19-2.fna.whatsapp.net", "media.flim21-2.fna.whatsapp.net",
- "media.fmex10-4.fna.whatsapp.net", "64", "33", "34", "35", "interruption", "media.fabv3-1.fna.whatsapp.net", "media.fadb6-1.fna.whatsapp.net", "media.fagr1-1.fna.whatsapp.net",
- "media.famd1-1.fna.whatsapp.net", "media.famm6-1.fna.whatsapp.net", "media.faqp2-3.fna.whatsapp.net"
-};
+/*
+
+WhatsApp plugin for Miranda NG
+Copyright © 2019-23 George Hazan
+
+*/
+
+static char *SingleByteTokens[] = {
+ "", "xmlstreamstart", "xmlstreamend", "s.whatsapp.net", "type", "participant", "from", "receipt", "id", "broadcast", "status", "message", "notification", "notify", "to", "jid", "user",
+ "class", "offline", "g.us", "result", "mediatype", "enc", "skmsg", "off_cnt", "xmlns", "presence", "participants", "ack", "t", "iq", "device_hash", "read", "value", "media", "picture",
+ "chatstate", "unavailable", "text", "urn:xmpp:whatsapp:push", "devices", "verified_name", "contact", "composing", "edge_routing", "routing_info", "item", "image", "verified_level",
+ "get", "fallback_hostname", "2", "media_conn", "1", "v", "handshake", "fallback_class", "count", "config", "offline_preview", "download_buckets", "w:profile:picture", "set",
+ "creation", "location", "fallback_ip4", "msg", "urn:xmpp:ping", "fallback_ip6", "call-creator", "relaylatency", "success", "subscribe", "video", "business_hours_config",
+ "platform", "hostname", "version", "unknown", "0", "ping", "hash", "edit", "subject", "max_buckets", "download", "delivery", "props", "sticker", "name", "last", "contacts",
+ "business", "primary", "preview", "w:p", "pkmsg", "call-id", "retry", "prop", "call", "auth_ttl", "available", "relay_id", "last_id", "day_of_week", "w", "host", "seen",
+ "bits", "list", "atn", "upload", "is_new", "w:stats", "key", "paused", "specific_hours", "multicast", "stream:error", "mmg.whatsapp.net", "code", "deny", "played", "profile",
+ "fna", "device-list", "close_time", "latency", "gcm", "pop", "audio", "26", "w:web", "open_time", "error", "auth", "ip4", "update", "profile_options", "config_value", "category",
+ "catalog_not_created", "00", "config_code", "mode", "catalog_status", "ip6", "blocklist", "registration", "7", "web", "fail", "w:m", "cart_enabled", "ttl", "gif", "300",
+ "device_orientation", "identity", "query", "401", "media-gig2-1.cdn.whatsapp.net", "in", "3", "te2", "add", "fallback", "categories", "ptt", "encrypt", "notice",
+ "thumbnail-document", "item-not-found", "12", "thumbnail-image", "stage", "thumbnail-link", "usync", "out", "thumbnail-video", "8", "01", "context", "sidelist",
+ "thumbnail-gif", "terminate", "not-authorized", "orientation", "dhash", "capability", "side_list", "md-app-state", "description", "serial", "readreceipts", "te",
+ "business_hours", "md-msg-hist", "tag", "attribute_padding", "document", "open_24h", "delete", "expiration", "active", "prev_v_id", "true", "passive", "index", "4",
+ "conflict", "remove", "w:gp2", "config_expo_key", "screen_height", "replaced", "02", "screen_width", "uploadfieldstat", "2:47DEQpj8", "media-bog1-1.cdn.whatsapp.net",
+ "encopt", "url", "catalog_exists", "keygen", "rate", "offer", "opus", "media-mia3-1.cdn.whatsapp.net", "privacy", "media-mia3-2.cdn.whatsapp.net", "signature",
+ "preaccept", "token_id", "media-eze1-1.cdn.whatsapp.net"
+};
+
+static char *dict0[] = {
+ "media-for1-1.cdn.whatsapp.net", "relay", "media-gru2-2.cdn.whatsapp.net", "uncompressed", "medium", "voip_settings", "device", "reason",
+ "media-lim1-1.cdn.whatsapp.net", "media-qro1-2.cdn.whatsapp.net", "media-gru1-2.cdn.whatsapp.net", "action", "features", "media-gru2-1.cdn.whatsapp.net",
+ "media-gru1-1.cdn.whatsapp.net", "media-otp1-1.cdn.whatsapp.net", "kyc-id", "priority", "phash", "mute", "token", "100", "media-qro1-1.cdn.whatsapp.net",
+ "none", "media-mrs2-2.cdn.whatsapp.net", "sign_credential", "03", "media-mrs2-1.cdn.whatsapp.net", "protocol", "timezone", "transport", "eph_setting", "1080",
+ "original_dimensions", "media-frx5-1.cdn.whatsapp.net", "background", "disable", "original_image_url", "5", "transaction-id", "direct_path", "103", "appointment_only",
+ "request_image_url", "peer_pid", "address", "105", "104", "102", "media-cdt1-1.cdn.whatsapp.net", "101", "109", "110", "106", "background_location", "v_id", "sync",
+ "status-old", "111", "107", "ppic", "media-scl2-1.cdn.whatsapp.net", "business_profile", "108", "invite", "04", "audio_duration", "media-mct1-1.cdn.whatsapp.net",
+ "media-cdg2-1.cdn.whatsapp.net", "media-los2-1.cdn.whatsapp.net", "invis", "net", "voip_payload_type", "status-revoke-delay", "404", "state", "use_correct_order_for_hmac_sha1",
+ "ver", "media-mad1-1.cdn.whatsapp.net", "order", "540", "skey", "blinded_credential", "android", "contact_remove", "enable_downlink_relay_latency_only", "duration",
+ "enable_vid_one_way_codec_nego", "6", "media-sof1-1.cdn.whatsapp.net", "accept", "all", "signed_credential", "media-atl3-1.cdn.whatsapp.net", "media-lhr8-1.cdn.whatsapp.net",
+ "website", "05", "latitude", "media-dfw5-1.cdn.whatsapp.net", "forbidden", "enable_audio_piggyback_network_mtu_fix", "media-dfw5-2.cdn.whatsapp.net", "note.m4r",
+ "media-atl3-2.cdn.whatsapp.net", "jb_nack_discard_count_fix", "longitude", "Opening.m4r", "media-arn2-1.cdn.whatsapp.net", "email", "timestamp", "admin",
+ "media-pmo1-1.cdn.whatsapp.net", "America/Sao_Paulo", "contact_add", "media-sin6-1.cdn.whatsapp.net", "interactive", "8000", "acs_public_key",
+ "sigquit_anr_detector_release_rollover_percent", "media.fmed1-2.fna.whatsapp.net", "groupadd", "enabled_for_video_upgrade", "latency_update_threshold",
+ "media-frt3-2.cdn.whatsapp.net", "calls_row_constraint_layout", "media.fgbb2-1.fna.whatsapp.net", "mms4_media_retry_notification_encryption_enabled", "timeout",
+ "media-sin6-3.cdn.whatsapp.net", "audio_nack_jitter_multiplier", "jb_discard_count_adjust_pct_rc", "audio_reserve_bps", "delta", "account_sync", "default",
+ "media.fjed4-6.fna.whatsapp.net", "06", "lock_video_orientation", "media-frt3-1.cdn.whatsapp.net", "w:g2", "media-sin6-2.cdn.whatsapp.net", "audio_nack_algo_mask",
+ "media.fgbb2-2.fna.whatsapp.net", "media.fmed1-1.fna.whatsapp.net", "cond_range_target_bitrate", "mms4_server_error_receipt_encryption_enabled", "vid_rc_dyn", "fri",
+ "cart_v1_1_order_message_changes_enabled", "reg_push", "jb_hist_deposit_value", "privatestats", "media.fist7-2.fna.whatsapp.net", "thu", "jb_discard_count_adjust_pct",
+ "mon", "group_call_video_maximization_enabled", "mms_cat_v1_forward_hot_override_enabled", "audio_nack_new_rtt", "media.fsub2-3.fna.whatsapp.net",
+ "media_upload_aggressive_retry_exponential_backoff_enabled", "tue", "wed", "media.fruh4-2.fna.whatsapp.net", "audio_nack_max_seq_req", "max_rtp_audio_packet_resends",
+ "jb_hist_max_cdf_value", "07", "audio_nack_max_jb_delay", "mms_forward_partially_downloaded_video", "media-lcy1-1.cdn.whatsapp.net", "resume", "jb_inband_fec_aware",
+ "new_commerce_entry_point_enabled", "480", "payments_upi_generate_qr_amount_limit", "sigquit_anr_detector_rollover_percent", "media.fsdu2-1.fna.whatsapp.net", "fbns",
+ "aud_pkt_reorder_pct", "dec", "stop_probing_before_accept_send", "media_upload_max_aggressive_retries", "edit_business_profile_new_mode_enabled",
+ "media.fhex4-1.fna.whatsapp.net", "media.fjed4-3.fna.whatsapp.net", "sigquit_anr_detector_64bit_rollover_percent", "cond_range_ema_jb_last_delay",
+ "watls_enable_early_data_http_get", "media.fsdu2-2.fna.whatsapp.net", "message_qr_disambiguation_enabled", "media-mxp1-1.cdn.whatsapp.net", "sat", "vertical",
+ "media.fruh4-5.fna.whatsapp.net", "200", "media-sof1-2.cdn.whatsapp.net", "-1", "height", "product_catalog_hide_show_items_enabled", "deep_copy_frm_last",
+ "tsoffline", "vp8/h.264", "media.fgye5-3.fna.whatsapp.net", "media.ftuc1-2.fna.whatsapp.net", "smb_upsell_chat_banner_enabled", "canonical", "08", "9", ".",
+ "media.fgyd4-4.fna.whatsapp.net", "media.fsti4-1.fna.whatsapp.net", "mms_vcache_aggregation_enabled", "mms_hot_content_timespan_in_seconds", "nse_ver", "rte",
+ "third_party_sticker_web_sync", "cond_range_target_total_bitrate", "media_upload_aggressive_retry_enabled", "instrument_spam_report_enabled", "disable_reconnect_tone",
+ "move_media_folder_from_sister_app", "one_tap_calling_in_group_chat_size", "10", "storage_mgmt_banner_threshold_mb", "enable_backup_passive_mode", "sharechat_inline_player_enabled",
+ "media.fcnq2-1.fna.whatsapp.net", "media.fhex4-2.fna.whatsapp.net", "media.fist6-3.fna.whatsapp.net", "ephemeral_drop_column_stage", "reconnecting_after_network_change_threshold_ms",
+ "media-lhr8-2.cdn.whatsapp.net", "cond_jb_last_delay_ema_alpha", "entry_point_block_logging_enabled", "critical_event_upload_log_config", "respect_initial_bitrate_estimate",
+ "smaller_image_thumbs_status_enabled", "media.fbtz1-4.fna.whatsapp.net", "media.fjed4-1.fna.whatsapp.net", "width", "720", "enable_frame_dropper", "enable_one_side_mode",
+ "urn:xmpp:whatsapp:dirty", "new_sticker_animation_behavior_v2", "media.flim3-2.fna.whatsapp.net", "media.fuio6-2.fna.whatsapp.net", "skip_forced_signaling", "dleq_proof",
+ "status_video_max_bitrate", "lazy_send_probing_req", "enhanced_storage_management", "android_privatestats_endpoint_dit_enabled", "media.fscl13-2.fna.whatsapp.net", "video_duration"
+};
+
+static char *dict1[] = {
+ "group_call_discoverability_enabled", "media.faep9-2.fna.whatsapp.net", "msgr", "bloks_loggedin_access_app_id", "db_status_migration_step", "watls_prefer_ip6",
+ "jabber:iq:privacy", "68", "media.fsaw1-11.fna.whatsapp.net", "mms4_media_conn_persist_enabled", "animated_stickers_thread_clean_up", "media.fcgk3-2.fna.whatsapp.net",
+ "media.fcgk4-6.fna.whatsapp.net", "media.fgye5-2.fna.whatsapp.net", "media.flpb1-1.fna.whatsapp.net", "media.fsub2-1.fna.whatsapp.net", "media.fuio6-3.fna.whatsapp.net",
+ "not-allowed", "partial_pjpeg_bw_threshold", "cap_estimated_bitrate", "mms_chatd_resume_check_over_thrift", "smb_upsell_business_profile_enabled", "product_catalog_webclient",
+ "groups", "sigquit_anr_detector_release_updated_rollout", "syncd_key_rotation_enabled", "media.fdmm2-1.fna.whatsapp.net", "media-hou1-1.cdn.whatsapp.net",
+ "remove_old_chat_notifications", "smb_biztools_deeplink_enabled", "use_downloadable_filters_int", "group_qr_codes_enabled", "max_receipt_processing_time",
+ "optimistic_image_processing_enabled", "smaller_video_thumbs_status_enabled", "watls_early_data", "reconnecting_before_relay_failover_threshold_ms", "cond_range_packet_loss_pct",
+ "groups_privacy_blacklist", "status-revoke-drop", "stickers_animated_thumbnail_download", "dedupe_transcode_shared_images", "dedupe_transcode_shared_videos",
+ "media.fcnq2-2.fna.whatsapp.net", "media.fgyd4-1.fna.whatsapp.net", "media.fist7-1.fna.whatsapp.net", "media.flim3-3.fna.whatsapp.net", "add_contact_by_qr_enabled",
+ "https://faq.whatsapp.com/payments", "multicast_limit_global", "sticker_notification_preview", "smb_better_catalog_list_adapters_enabled", "bloks_use_minscript_android",
+ "pen_smoothing_enabled", "media.fcgk4-5.fna.whatsapp.net", "media.fevn1-3.fna.whatsapp.net", "media.fpoj7-1.fna.whatsapp.net", "media-arn2-2.cdn.whatsapp.net",
+ "reconnecting_before_network_change_threshold_ms", "android_media_use_fresco_for_gifs", "cond_in_congestion", "status_image_max_edge", "sticker_search_enabled",
+ "starred_stickers_web_sync", "db_blank_me_jid_migration_step", "media.fist6-2.fna.whatsapp.net", "media.ftuc1-1.fna.whatsapp.net", "09", "anr_fast_logs_upload_rollout",
+ "camera_core_integration_enabled", "11", "third_party_sticker_caching", "thread_dump_contact_support", "wam_privatestats_enabled", "vcard_as_document_size_kb", "maxfpp",
+ "fbip", "ephemeral_allow_group_members", "media-bom1-2.cdn.whatsapp.net", "media-xsp1-1.cdn.whatsapp.net", "disable_prewarm", "frequently_forwarded_max",
+ "media.fbtz1-5.fna.whatsapp.net", "media.fevn7-1.fna.whatsapp.net", "media.fgyd4-2.fna.whatsapp.net", "sticker_tray_animation_fully_visible_items",
+ "green_alert_banner_duration", "reconnecting_after_p2p_failover_threshold_ms", "connected", "share_biz_vcard_enabled", "stickers_animation", "0a", "1200", "WhatsApp",
+ "group_description_length", "p_v_id", "payments_upi_intent_transaction_limit", "frequently_forwarded_messages", "media-xsp1-2.cdn.whatsapp.net",
+ "media.faep8-1.fna.whatsapp.net", "media.faep8-2.fna.whatsapp.net", "media.faep9-1.fna.whatsapp.net", "media.fdmm2-2.fna.whatsapp.net", "media.fgzt3-1.fna.whatsapp.net",
+ "media.flim4-2.fna.whatsapp.net", "media.frao1-1.fna.whatsapp.net", "media.fscl9-2.fna.whatsapp.net", "media.fsub2-2.fna.whatsapp.net", "superadmin",
+ "media.fbog10-1.fna.whatsapp.net", "media.fcgh28-1.fna.whatsapp.net", "media.fjdo10-1.fna.whatsapp.net", "third_party_animated_sticker_import", "delay_fec",
+ "attachment_picker_refresh", "android_linked_devices_re_auth_enabled", "rc_dyn", "green_alert_block_jitter", "add_contact_logging_enabled", "biz_message_logging_enabled",
+ "conversation_media_preview_v2", "media-jnb1-1.cdn.whatsapp.net", "ab_key", "media.fcgk4-2.fna.whatsapp.net", "media.fevn1-1.fna.whatsapp.net", "media.fist6-1.fna.whatsapp.net",
+ "media.fruh4-4.fna.whatsapp.net", "media.fsti4-2.fna.whatsapp.net", "mms_vcard_autodownload_size_kb", "watls_enabled", "notif_ch_override_off", "media.fsaw1-14.fna.whatsapp.net",
+ "media.fscl13-1.fna.whatsapp.net", "db_group_participant_migration_step", "1020", "cond_range_sterm_rtt", "invites_logging_enabled", "triggered_block_enabled",
+ "group_call_max_participants", "media-iad3-1.cdn.whatsapp.net", "product_catalog_open_deeplink", "shops_required_tos_version", "image_max_kbytes",
+ "cond_low_quality_vid_mode", "db_receipt_migration_step", "jb_early_prob_hist_shrink", "media.fdmm2-3.fna.whatsapp.net", "media.fdmm2-4.fna.whatsapp.net",
+ "media.fruh4-1.fna.whatsapp.net", "media.fsaw2-2.fna.whatsapp.net", "remove_geolocation_videos", "new_animation_behavior", "fieldstats_beacon_chance", "403",
+ "authkey_reset_on_ban", "continuous_ptt_playback", "reconnecting_after_relay_failover_threshold_ms", "false", "group", "sun", "conversation_swipe_to_reply",
+ "ephemeral_messages_setting", "smaller_video_thumbs_enabled", "md_device_sync_enabled", "bloks_shops_pdp_url_regex", "lasso_integration_enabled",
+ "media-bom1-1.cdn.whatsapp.net", "new_backup_format_enabled", "256", "media.faep6-1.fna.whatsapp.net", "media.fasr1-1.fna.whatsapp.net", "media.fbtz1-7.fna.whatsapp.net",
+ "media.fesb4-1.fna.whatsapp.net", "media.fjdo1-2.fna.whatsapp.net", "media.frba2-1.fna.whatsapp.net", "watls_no_dns", "600", "db_broadcast_me_jid_migration_step",
+ "new_wam_runtime_enabled", "group_update", "enhanced_block_enabled", "sync_wifi_threshold_kb", "mms_download_nc_cat", "bloks_minification_enabled",
+ "ephemeral_messages_enabled", "reject", "voip_outgoing_xml_signaling", "creator", "dl_bw", "payments_request_messages", "target_bitrate", "bloks_rendercore_enabled",
+ "media-hbe1-1.cdn.whatsapp.net", "media-hel3-1.cdn.whatsapp.net", "media-kut2-2.cdn.whatsapp.net", "media-lax3-1.cdn.whatsapp.net", "media-lax3-2.cdn.whatsapp.net",
+ "sticker_pack_deeplink_enabled", "hq_image_bw_threshold", "status_info", "voip", "dedupe_transcode_videos", "grp_uii_cleanup", "linked_device_max_count",
+ "media.flim1-1.fna.whatsapp.net", "media.fsaw2-1.fna.whatsapp.net", "reconnecting_after_call_active_threshold_ms", "1140", "catalog_pdp_new_design",
+ "media.fbtz1-10.fna.whatsapp.net", "media.fsaw1-15.fna.whatsapp.net", "0b", "consumer_rc_provider", "mms_async_fast_forward_ttl", "jb_eff_size_fix",
+ "voip_incoming_xml_signaling", "media_provider_share_by_uuid", "suspicious_links", "dedupe_transcode_images", "green_alert_modal_start", "media-cgk1-1.cdn.whatsapp.net",
+ "media-lga3-1.cdn.whatsapp.net", "template_doc_mime_types", "important_messages", "user_add", "vcard_max_size_kb", "media.fada2-1.fna.whatsapp.net",
+ "media.fbog2-5.fna.whatsapp.net", "media.fbtz1-3.fna.whatsapp.net", "media.fcgk3-1.fna.whatsapp.net", "media.fcgk7-1.fna.whatsapp.net", "media.flim1-3.fna.whatsapp.net",
+ "media.fscl9-1.fna.whatsapp.net", "ctwa_context_enterprise_enabled", "media.fsaw1-13.fna.whatsapp.net", "media.fuio11-2.fna.whatsapp.net", "status_collapse_muted",
+ "db_migration_level_force", "recent_stickers_web_sync", "bloks_session_state", "bloks_shops_enabled", "green_alert_setting_deep_links_enabled", "restrict_groups",
+ "battery", "green_alert_block_start", "refresh", "ctwa_context_enabled", "md_messaging_enabled", "status_image_quality", "md_blocklist_v2_server",
+ "media-del1-1.cdn.whatsapp.net", "13", "userrate", "a_v_id", "cond_rtt_ema_alpha", "invalid"
+};
+
+static char *dict2[] = {
+ "media.fada1-1.fna.whatsapp.net", "media.fadb3-2.fna.whatsapp.net", "media.fbhz2-1.fna.whatsapp.net", "media.fcor2-1.fna.whatsapp.net", "media.fjed4-2.fna.whatsapp.net",
+ "media.flhe4-1.fna.whatsapp.net", "media.frak1-2.fna.whatsapp.net", "media.fsub6-3.fna.whatsapp.net", "media.fsub6-7.fna.whatsapp.net", "media.fvvi1-1.fna.whatsapp.net",
+ "search_v5_eligible", "wam_real_time_enabled", "report_disk_event", "max_tx_rott_based_bitrate", "product", "media.fjdo10-2.fna.whatsapp.net", "video_frame_crc_sample_interval",
+ "media_max_autodownload", "15", "h.264", "wam_privatestats_buffer_count", "md_phash_v2_enabled", "account_transfer_enabled", "business_product_catalog",
+ "enable_non_dyn_codec_param_fix", "is_user_under_epd_jurisdiction", "media.fbog2-4.fna.whatsapp.net", "media.fbtz1-2.fna.whatsapp.net", "media.fcfc1-1.fna.whatsapp.net",
+ "media.fjed4-5.fna.whatsapp.net", "media.flhe4-2.fna.whatsapp.net", "media.flim1-2.fna.whatsapp.net", "media.flos5-1.fna.whatsapp.net", "android_key_store_auth_ver",
+ "010", "anr_process_monitor", "delete_old_auth_key", "media.fcor10-3.fna.whatsapp.net", "storage_usage_enabled", "android_camera2_support_level", "dirty",
+ "consumer_content_provider", "status_video_max_duration", "0c", "bloks_cache_enabled", "media.fadb2-2.fna.whatsapp.net", "media.fbko1-1.fna.whatsapp.net",
+ "media.fbtz1-9.fna.whatsapp.net", "media.fcgk4-4.fna.whatsapp.net", "media.fesb4-2.fna.whatsapp.net", "media.fevn1-2.fna.whatsapp.net", "media.fist2-4.fna.whatsapp.net",
+ "media.fjdo1-1.fna.whatsapp.net", "media.fruh4-6.fna.whatsapp.net", "media.fsrg5-1.fna.whatsapp.net", "media.fsub6-6.fna.whatsapp.net", "minfpp", "5000", "locales",
+ "video_max_bitrate", "use_new_auth_key", "bloks_http_enabled", "heartbeat_interval", "media.fbog11-1.fna.whatsapp.net", "ephemeral_group_query_ts", "fec_nack",
+ "search_in_storage_usage", "c", "media-amt2-1.cdn.whatsapp.net", "linked_devices_ui_enabled", "14", "async_data_load_on_startup", "voip_incoming_xml_ack", "16",
+ "db_migration_step", "init_bwe", "max_participants", "wam_buffer_count", "media.fada2-2.fna.whatsapp.net", "media.fadb3-1.fna.whatsapp.net", "media.fcor2-2.fna.whatsapp.net",
+ "media.fdiy1-2.fna.whatsapp.net", "media.frba3-2.fna.whatsapp.net", "media.fsaw2-3.fna.whatsapp.net", "1280", "status_grid_enabled", "w:biz", "product_catalog_deeplink",
+ "media.fgye10-2.fna.whatsapp.net", "media.fuio11-1.fna.whatsapp.net", "optimistic_upload", "work_manager_init", "lc", "catalog_message", "cond_net_medium",
+ "enable_periodical_aud_rr_processing", "cond_range_ema_rtt", "media-tir2-1.cdn.whatsapp.net", "frame_ms", "group_invite_sending", "payments_web_enabled",
+ "wallpapers_v2", "0d", "browser", "hq_image_max_edge", "image_edit_zoom", "linked_devices_re_auth_enabled", "media.faly3-2.fna.whatsapp.net",
+ "media.fdoh5-3.fna.whatsapp.net", "media.fesb3-1.fna.whatsapp.net", "media.fknu1-1.fna.whatsapp.net", "media.fmex3-1.fna.whatsapp.net", "media.fruh4-3.fna.whatsapp.net",
+ "255", "web_upgrade_to_md_modal", "audio_piggyback_timeout_msec", "enable_audio_oob_fec_feature", "from_ip", "image_max_edge", "message_qr_enabled", "powersave",
+ "receipt_pre_acking", "video_max_edge", "full", "011", "012", "enable_audio_oob_fec_for_sender", "md_voip_enabled", "enable_privatestats", "max_fec_ratio",
+ "payments_cs_faq_url", "media-xsp1-3.cdn.whatsapp.net", "hq_image_quality", "media.fasr1-2.fna.whatsapp.net", "media.fbog3-1.fna.whatsapp.net",
+ "media.ffjr1-6.fna.whatsapp.net", "media.fist2-3.fna.whatsapp.net", "media.flim4-3.fna.whatsapp.net", "media.fpbc2-4.fna.whatsapp.net", "media.fpku1-1.fna.whatsapp.net",
+ "media.frba1-1.fna.whatsapp.net", "media.fudi1-1.fna.whatsapp.net", "media.fvvi1-2.fna.whatsapp.net", "gcm_fg_service", "enable_dec_ltr_size_check", "clear", "lg",
+ "media.fgru11-1.fna.whatsapp.net", "18", "media-lga3-2.cdn.whatsapp.net", "pkey", "0e", "max_subject", "cond_range_lterm_rtt", "announcement_groups", "biz_profile_options",
+ "s_t", "media.fabv2-1.fna.whatsapp.net", "media.fcai3-1.fna.whatsapp.net", "media.fcgh1-1.fna.whatsapp.net", "media.fctg1-4.fna.whatsapp.net", "media.fdiy1-1.fna.whatsapp.net",
+ "media.fisb4-1.fna.whatsapp.net", "media.fpku1-2.fna.whatsapp.net", "media.fros9-1.fna.whatsapp.net", "status_v3_text", "usync_sidelist", "17", "announcement", "...",
+ "md_group_notification", "0f", "animated_pack_in_store", "013", "America/Mexico_City", "1260", "media-ams4-1.cdn.whatsapp.net", "media-cgk1-2.cdn.whatsapp.net",
+ "media-cpt1-1.cdn.whatsapp.net", "media-maa2-1.cdn.whatsapp.net", "media.fgye10-1.fna.whatsapp.net", "e", "catalog_cart", "hfm_string_changes", "init_bitrate",
+ "packless_hsm", "group_info", "America/Belem", "50", "960", "cond_range_bwe", "decode", "encode", "media.fada1-8.fna.whatsapp.net", "media.fadb1-2.fna.whatsapp.net",
+ "media.fasu6-1.fna.whatsapp.net", "media.fbog4-1.fna.whatsapp.net", "media.fcgk9-2.fna.whatsapp.net", "media.fdoh5-2.fna.whatsapp.net", "media.ffjr1-2.fna.whatsapp.net",
+ "media.fgua1-1.fna.whatsapp.net", "media.fgye1-1.fna.whatsapp.net", "media.fist1-4.fna.whatsapp.net", "media.fpbc2-2.fna.whatsapp.net", "media.fres2-1.fna.whatsapp.net",
+ "media.fsdq1-2.fna.whatsapp.net", "media.fsub6-5.fna.whatsapp.net", "profilo_enabled", "template_hsm", "use_disorder_prefetching_timer", "video_codec_priority",
+ "vpx_max_qp", "ptt_reduce_recording_delay", "25", "iphone", "Windows", "s_o", "Africa/Lagos", "abt", "media-kut2-1.cdn.whatsapp.net", "media-mba1-1.cdn.whatsapp.net",
+ "media-mxp1-2.cdn.whatsapp.net", "md_blocklist_v2", "url_text", "enable_short_offset", "group_join_permissions", "enable_audio_piggyback_feature", "image_quality",
+ "media.fcgk7-2.fna.whatsapp.net", "media.fcgk8-2.fna.whatsapp.net", "media.fclo7-1.fna.whatsapp.net", "media.fcmn1-1.fna.whatsapp.net", "media.feoh1-1.fna.whatsapp.net",
+ "media.fgyd4-3.fna.whatsapp.net", "media.fjed4-4.fna.whatsapp.net", "media.flim1-4.fna.whatsapp.net", "media.flim2-4.fna.whatsapp.net", "media.fplu6-1.fna.whatsapp.net",
+ "media.frak1-1.fna.whatsapp.net", "media.fsdq1-1.fna.whatsapp.net", "to_ip", "015", "vp8", "19", "21", "1320", "auth_key_ver", "message_processing_dedup", "server-error",
+ "wap4_enabled", "420", "014", "cond_range_rtt", "ptt_fast_lock_enabled", "media-ort2-1.cdn.whatsapp.net", "fwd_ui_start_ts"
+};
+
+static char *dict3[] = {
+ "contact_blacklist", "Asia/Jakarta", "media.fepa10-1.fna.whatsapp.net", "media.fmex10-3.fna.whatsapp.net", "disorder_prefetching_start_when_empty", "America/Bogota",
+ "use_local_probing_rx_bitrate", "America/Argentina/Buenos_Aires", "cross_post", "media.fabb1-1.fna.whatsapp.net", "media.fbog4-2.fna.whatsapp.net", "media.fcgk9-1.fna.whatsapp.net",
+ "media.fcmn2-1.fna.whatsapp.net", "media.fdel3-1.fna.whatsapp.net", "media.ffjr1-1.fna.whatsapp.net", "media.fgdl5-1.fna.whatsapp.net", "media.flpb1-2.fna.whatsapp.net",
+ "media.fmex2-1.fna.whatsapp.net", "media.frba2-2.fna.whatsapp.net", "media.fros2-2.fna.whatsapp.net", "media.fruh2-1.fna.whatsapp.net", "media.fybz2-2.fna.whatsapp.net",
+ "options", "20", "a", "017", "018", "mute_always", "user_notice", "Asia/Kolkata", "gif_provider", "locked", "media-gua1-1.cdn.whatsapp.net", "piggyback_exclude_force_flush",
+ "24", "media.frec39-1.fna.whatsapp.net", "user_remove", "file_max_size", "cond_packet_loss_pct_ema_alpha", "media.facc1-1.fna.whatsapp.net", "media.fadb2-1.fna.whatsapp.net",
+ "media.faly3-1.fna.whatsapp.net", "media.fbdo6-2.fna.whatsapp.net", "media.fcmn2-2.fna.whatsapp.net", "media.fctg1-3.fna.whatsapp.net", "media.ffez1-2.fna.whatsapp.net",
+ "media.fist1-3.fna.whatsapp.net", "media.fist2-2.fna.whatsapp.net", "media.flim2-2.fna.whatsapp.net", "media.fmct2-3.fna.whatsapp.net", "media.fpei3-1.fna.whatsapp.net",
+ "media.frba3-1.fna.whatsapp.net", "media.fsdu8-2.fna.whatsapp.net", "media.fstu2-1.fna.whatsapp.net", "media_type", "receipt_agg", "016", "enable_pli_for_crc_mismatch",
+ "live", "enc_rekey", "frskmsg", "d", "media.fdel11-2.fna.whatsapp.net", "proto", "2250", "audio_piggyback_enable_cache", "skip_nack_if_ltrp_sent", "mark_dtx_jb_frames",
+ "web_service_delay", "7282", "catalog_send_all", "outgoing", "360", "30", "LIMITED", "019", "audio_picker", "bpv2_phase", "media.fada1-7.fna.whatsapp.net",
+ "media.faep7-1.fna.whatsapp.net", "media.fbko1-2.fna.whatsapp.net", "media.fbni1-2.fna.whatsapp.net", "media.fbtz1-1.fna.whatsapp.net", "media.fbtz1-8.fna.whatsapp.net",
+ "media.fcjs3-1.fna.whatsapp.net", "media.fesb3-2.fna.whatsapp.net", "media.fgdl5-4.fna.whatsapp.net", "media.fist2-1.fna.whatsapp.net", "media.flhe2-2.fna.whatsapp.net",
+ "media.flim2-1.fna.whatsapp.net", "media.fmex1-1.fna.whatsapp.net", "media.fpat3-2.fna.whatsapp.net", "media.fpat3-3.fna.whatsapp.net", "media.fros2-1.fna.whatsapp.net",
+ "media.fsdu8-1.fna.whatsapp.net", "media.fsub3-2.fna.whatsapp.net", "payments_chat_plugin", "cond_congestion_no_rtcp_thr", "green_alert", "not-a-biz", "..",
+ "shops_pdp_urls_config", "source", "media-dus1-1.cdn.whatsapp.net", "mute_video", "01b", "currency", "max_keys", "resume_check", "contact_array", "qr_scanning", "23",
+ "b", "media.fbfh15-1.fna.whatsapp.net", "media.flim22-1.fna.whatsapp.net", "media.fsdu11-1.fna.whatsapp.net", "media.fsdu15-1.fna.whatsapp.net", "Chrome", "fts_version",
+ "60", "media.fada1-6.fna.whatsapp.net", "media.faep4-2.fna.whatsapp.net", "media.fbaq5-1.fna.whatsapp.net", "media.fbni1-1.fna.whatsapp.net", "media.fcai3-2.fna.whatsapp.net",
+ "media.fdel3-2.fna.whatsapp.net", "media.fdmm3-2.fna.whatsapp.net", "media.fhex3-1.fna.whatsapp.net", "media.fisb4-2.fna.whatsapp.net", "media.fkhi5-2.fna.whatsapp.net",
+ "media.flos2-1.fna.whatsapp.net", "media.fmct2-1.fna.whatsapp.net", "media.fntr7-1.fna.whatsapp.net", "media.frak3-1.fna.whatsapp.net", "media.fruh5-2.fna.whatsapp.net",
+ "media.fsub6-1.fna.whatsapp.net", "media.fuab1-2.fna.whatsapp.net", "media.fuio1-1.fna.whatsapp.net", "media.fver1-1.fna.whatsapp.net", "media.fymy1-1.fna.whatsapp.net",
+ "product_catalog", "1380", "audio_oob_fec_max_pkts", "22", "254", "media-ort2-2.cdn.whatsapp.net", "media-sjc3-1.cdn.whatsapp.net", "1600", "01a", "01c", "405",
+ "key_frame_interval", "body", "media.fcgh20-1.fna.whatsapp.net", "media.fesb10-2.fna.whatsapp.net", "125", "2000", "media.fbsb1-1.fna.whatsapp.net",
+ "media.fcmn3-2.fna.whatsapp.net", "media.fcpq1-1.fna.whatsapp.net", "media.fdel1-2.fna.whatsapp.net", "media.ffor2-1.fna.whatsapp.net", "media.fgdl1-4.fna.whatsapp.net",
+ "media.fhex2-1.fna.whatsapp.net", "media.fist1-2.fna.whatsapp.net", "media.fjed5-2.fna.whatsapp.net", "media.flim6-4.fna.whatsapp.net", "media.flos2-2.fna.whatsapp.net",
+ "media.fntr6-2.fna.whatsapp.net", "media.fpku3-2.fna.whatsapp.net", "media.fros8-1.fna.whatsapp.net", "media.fymy1-2.fna.whatsapp.net", "ul_bw", "ltrp_qp_offset",
+ "request", "nack", "dtx_delay_state_reset", "timeoffline", "28", "01f", "32", "enable_ltr_pool", "wa_msys_crypto", "01d", "58", "dtx_freeze_hg_update",
+ "nack_if_rpsi_throttled", "253", "840", "media.famd15-1.fna.whatsapp.net", "media.fbog17-2.fna.whatsapp.net", "media.fcai19-2.fna.whatsapp.net", "media.fcai21-4.fna.whatsapp.net",
+ "media.fesb10-4.fna.whatsapp.net", "media.fesb10-5.fna.whatsapp.net", "media.fmaa12-1.fna.whatsapp.net", "media.fmex11-3.fna.whatsapp.net", "media.fpoa33-1.fna.whatsapp.net",
+ "1050", "021", "clean", "cond_range_ema_packet_loss_pct", "media.fadb6-5.fna.whatsapp.net", "media.faqp4-1.fna.whatsapp.net", "media.fbaq3-1.fna.whatsapp.net",
+ "media.fbel2-1.fna.whatsapp.net", "media.fblr4-2.fna.whatsapp.net", "media.fclo8-1.fna.whatsapp.net", "media.fcoo1-2.fna.whatsapp.net", "media.ffjr1-4.fna.whatsapp.net",
+ "media.ffor9-1.fna.whatsapp.net", "media.fisb3-1.fna.whatsapp.net", "media.fkhi2-2.fna.whatsapp.net", "media.fkhi4-1.fna.whatsapp.net", "media.fpbc1-2.fna.whatsapp.net",
+ "media.fruh2-2.fna.whatsapp.net", "media.fruh5-1.fna.whatsapp.net", "media.fsub3-1.fna.whatsapp.net", "payments_transaction_limit", "252", "27", "29", "tintagel", "01e",
+ "237", "780", "callee_updated_payload", "020", "257", "price", "025", "239", "payments_cs_phone_number", "mediaretry", "w:auth:backup:token", "Glass.caf", "max_bitrate",
+ "240", "251", "660", "media.fbog16-1.fna.whatsapp.net", "media.fcgh21-1.fna.whatsapp.net", "media.fkul19-2.fna.whatsapp.net", "media.flim21-2.fna.whatsapp.net",
+ "media.fmex10-4.fna.whatsapp.net", "64", "33", "34", "35", "interruption", "media.fabv3-1.fna.whatsapp.net", "media.fadb6-1.fna.whatsapp.net", "media.fagr1-1.fna.whatsapp.net",
+ "media.famd1-1.fna.whatsapp.net", "media.famm6-1.fna.whatsapp.net", "media.faqp2-3.fna.whatsapp.net"
+};
diff --git a/protocols/WhatsApp/src/iq.cpp b/protocols/WhatsApp/src/iq.cpp
index b9cf5bd7d2..24513dd06b 100644
--- a/protocols/WhatsApp/src/iq.cpp
+++ b/protocols/WhatsApp/src/iq.cpp
@@ -1,570 +1,570 @@
-/*
-
-WhatsApp plugin for Miranda NG
-Copyright © 2019-22 George Hazan
-
-*/
-
-#include "stdafx.h"
-
-void WhatsAppProto::OnAccountSync(const WANode &node)
-{
- if (auto *pList = node.getChild("devices")) {
- auto *pUser = FindUser(m_szJid);
- pUser->arDevices.destroy();
-
- for (auto &it : pList->getChildren())
- if (it->title == "device")
- pUser->arDevices.insert(new WAJid(it->getAttr("jid"), it->getAttrInt("key-index")));
- }
-
- if (auto *pList = node.getChild("blocklist"))
- for (auto &it : pList->getChildren())
- if (it->title == "item") {
- auto *pUser = AddUser(it->getAttr("jid"), false);
- Contact::Hide(pUser->hContact, 0 == mir_strcmp(it->getAttr("action"), "block"));
- }
-
- SendAck(node);
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-void WhatsAppProto::OnIqBlockList(const WANode &node)
-{
- for (auto &it : node.getChild("list")->getChildren()) {
- auto *pUser = AddUser(it->getAttr("jid"), false);
- Contact::Hide(pUser->hContact);
- }
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-void WhatsAppProto::OnIqCountPrekeys(const WANode &node)
-{
- m_bUpdatedPrekeys = true;
-
- int iCount = node.getChild("count")->getAttrInt("value");
- if (iCount < 5)
- UploadMorePrekeys();
-}
-
-void WhatsAppProto::UploadMorePrekeys()
-{
- WANodeIq iq(IQ::SET, "encrypt");
-
- auto regId = encodeBigEndian(getDword(DBKEY_REG_ID));
- iq.addChild("registration")->content.append(regId.c_str(), regId.size());
-
- iq.addChild("type")->content.append(KEY_BUNDLE_TYPE, 1);
- iq.addChild("identity")->content.append(m_signalStore.signedIdentity.pub);
-
- const int PORTION = 10;
- m_signalStore.generatePrekeys(PORTION);
-
- int iStart = getDword(DBKEY_PREKEY_UPLOAD_ID, 1);
- auto *n = iq.addChild("list");
- for (int i = 0; i < PORTION; i++) {
- auto *nKey = n->addChild("key");
-
- int keyId = iStart + i;
- auto encId = encodeBigEndian(keyId, 3);
- nKey->addChild("id")->content.append(encId.c_str(), encId.size());
- nKey->addChild("value")->content.append(getBlob(CMStringA(FORMAT, "PreKey%dPublic", keyId)));
- }
- setDword(DBKEY_PREKEY_UPLOAD_ID, iStart + PORTION);
-
- auto *skey = iq.addChild("skey");
-
- auto encId = encodeBigEndian(m_signalStore.preKey.keyid, 3);
- skey->addChild("id")->content.append(encId.c_str(), encId.size());
- skey->addChild("value")->content.append(m_signalStore.preKey.pub);
- skey->addChild("signature")->content.append(m_signalStore.preKey.signature);
-
- WSSendNode(iq, &WhatsAppProto::OnIqDoNothing);
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-void WhatsAppProto::OnIqDoNothing(const WANode&)
-{
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-void WhatsAppProto::OnIqGetKeys(const WANode &node, void *pUserInfo)
-{
- for (auto &it : node.getChild("list")->getChildren())
- if (it->title == "user")
- m_signalStore.injectSession(it->getAttr("jid"), it, it);
-
- // don't forget to send delayed message when all keys are retrieved
- if (pUserInfo)
- FinishTask((WASendTask *)pUserInfo);
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-void WhatsAppProto::OnIqGetUsync(const WANode &node, void *pUserInfo)
-{
- WANodeIq iq(IQ::GET, "encrypt");
- auto *pKey = iq.addChild("key");
-
- for (auto *nUser : node.getChild("usync")->getChild("list")->getChildren()) {
- auto *pszJid = nUser->getAttr("jid");
-
- auto *pUser = AddUser(pszJid, false);
- pUser->bDeviceInit = true;
- pUser->arDevices.destroy();
-
- if (auto *pList = nUser->getChild("devices")->getChild("device-list"))
- for (auto &it : pList->getChildren())
- if (it->title == "device")
- pUser->arDevices.insert(new WAJid(pszJid, it->getAttrInt("id")));
-
- for (auto &it : pUser->arDevices) {
- auto blob = getBlob(MSignalSession(it->user, it->device).getSetting());
- if (blob.isEmpty())
- pKey->addChild("user")->addAttr("jid", it->toString());
- }
- }
-
- if (pKey->getChildren().getCount() > 0)
- WSSendNode(iq, &WhatsAppProto::OnIqGetKeys, pUserInfo);
- else if (pUserInfo)
- FinishTask((WASendTask *)pUserInfo);
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-void WhatsAppProto::OnIqPairDevice(const WANode &node)
-{
- WSSendNode(WANodeIq(IQ::RESULT) << CHAR_PARAM("id", node.getAttr("id")));
-
- if (auto *pRef = node.getChild("pair-device")->getChild("ref")) {
- ShowQrCode(pRef->getBody());
- }
- else {
- debugLogA("OnIqPairDevice: got reply without ref, exiting");
- ShutdownSession();
- }
-}
-
-void WhatsAppProto::OnIqPairSuccess(const WANode &node)
-{
- CloseQrDialog();
-
- auto *pRoot = node.getChild("pair-success");
-
- try {
- if (auto *pPlatform = pRoot->getChild("platform"))
- debugLogA("Got response from platform: %s", pPlatform->getBody().c_str());
-
- if (auto *pBiz = pRoot->getChild("biz"))
- if (auto *pszName = pBiz->getAttr("name"))
- setUString("Nick", pszName);
-
- if (auto *pDevice = pRoot->getChild("device")) {
- if (auto *pszJid = pDevice->getAttr("jid")) {
- WAJid jid(pszJid);
- m_szJid = jid.user + "@" + jid.server;
- m_arUsers.insert(new WAUser(0, m_szJid, false));
-
- setUString(DBKEY_ID, m_szJid);
- setDword(DBKEY_DEVICE_ID, jid.device);
- }
- }
- else throw "OnIqPairSuccess: got reply without device info, exiting";
-
- if (auto *pIdentity = pRoot->getChild("device-identity")) {
- proto::ADVSignedDeviceIdentityHMAC payload(pIdentity->content);
-
- auto &hmac = payload->hmac;
- auto &details = payload->details;
- {
- // check details signature using HMAC
- uint8_t signature[32];
- unsigned int out_len = sizeof(signature);
- MBinBuffer secret(getBlob(DBKEY_SECRET_KEY));
- HMAC(EVP_sha256(), secret.data(), (int)secret.length(), details.data, (int)details.len, signature, &out_len);
- if (memcmp(hmac.data, signature, sizeof(signature)))
- throw "OnIqPairSuccess: got reply with invalid details signature, exiting";
- }
-
- proto::ADVSignedDeviceIdentity account(details);
-
- auto &deviceDetails = account->details;
- auto &accountSignature = account->accountsignature;
- auto &accountSignatureKey = account->accountsignaturekey;
- {
- MBinBuffer buf;
- buf.append("\x06\x00", 2);
- buf.append(deviceDetails.data, deviceDetails.len);
- buf.append(m_signalStore.signedIdentity.pub);
-
- ec_public_key key = {};
- memcpy(key.data, accountSignatureKey.data, sizeof(key.data));
- if (1 != curve_verify_signature(&key, buf.data(), buf.length(), accountSignature.data, accountSignature.len))
- throw "OnIqPairSuccess: got reply with invalid account signature, exiting";
- }
- debugLogA("Received valid account signature");
- {
- MBinBuffer buf;
- buf.append("\x06\x01", 2);
- buf.append(deviceDetails.data, deviceDetails.len);
- buf.append(m_signalStore.signedIdentity.pub);
- buf.append(accountSignatureKey.data, accountSignatureKey.len);
-
- signal_buffer *result;
- ec_private_key key = {};
- memcpy(key.data, m_signalStore.signedIdentity.priv.data(), m_signalStore.signedIdentity.priv.length());
- if (curve_calculate_signature(m_signalStore.CTX(), &result, &key, buf.data(), buf.length()) != 0)
- throw "OnIqPairSuccess: cannot calculate account signature, exiting";
-
- account->devicesignature = proto::SetBinary(result->data, result->len);
- account->has_devicesignature = true;
- signal_buffer_free(result);
- }
-
- {
- MBinBuffer key;
- if (accountSignatureKey.len == 32)
- key.append(KEY_BUNDLE_TYPE, 1);
- key.append(accountSignatureKey.data, accountSignatureKey.len);
- db_set_blob(0, m_szModuleName, "SignalIdentifierKey", key.data(), (int)key.length());
- }
-
- proto::CleanBinary(account->accountsignaturekey); account->has_accountsignaturekey = false;
- MBinBuffer accountEnc(proto::Serialize(account));
- db_set_blob(0, m_szModuleName, "WAAccount", accountEnc.data(), (int)accountEnc.length());
-
- proto::ADVDeviceIdentity deviceIdentity(deviceDetails);
-
- WANodeIq reply(IQ::RESULT); reply << CHAR_PARAM("id", node.getAttr("id"));
-
- WANode *nodePair = reply.addChild("pair-device-sign");
-
- WANode *nodeDeviceIdentity = nodePair->addChild("device-identity");
- nodeDeviceIdentity->addAttr("key-index", deviceIdentity->keyindex);
- nodeDeviceIdentity->content.append(accountEnc.data(), accountEnc.length());
- WSSendNode(reply);
- }
- else throw "OnIqPairSuccess: got reply without identity, exiting";
- }
- catch (const char *pErrMsg) {
- debugLogA(pErrMsg);
- ShutdownSession();
- }
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-void WhatsAppProto::OnIqResult(const WANode &node)
-{
- if (auto *pszId = node.getAttr("id")) {
- for (auto &it : m_arPacketQueue) {
- if (it->szPacketId == pszId) {
- it->Execute(this, node);
- m_arPacketQueue.remove(it);
- break;
- }
- }
- }
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-void WhatsAppProto::OnNotifyAny(const WANode &node)
-{
- SendAck(node);
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-void WhatsAppProto::OnReceiveChatState(const WANode &node)
-{
- if (auto *pUser = FindUser(node.getAttr("from"))) {
- if (node.getChild("composing")) {
- pUser->m_timer1 = time(0);
- pUser->m_timer2 = 0;
- setWord(pUser->hContact, "Status", ID_STATUS_ONLINE);
-
- CallService(MS_PROTO_CONTACTISTYPING, pUser->hContact, 60);
- }
- else if (node.getChild("paused"))
- CallService(MS_PROTO_CONTACTISTYPING, pUser->hContact, PROTOTYPE_CONTACTTYPING_OFF);
- }
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-void WhatsAppProto::OnNotifyDevices(const WANode &node)
-{
- if (!mir_strcmp(node.getAttr("jid"), m_szJid))
- debugLogA("received list of my own devices");
- SendAck(node);
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-void WhatsAppProto::OnNotifyEncrypt(const WANode &node)
-{
- if (!mir_strcmp(node.getAttr("from"), S_WHATSAPP_NET))
- OnIqCountPrekeys(node);
- SendAck(node);
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-void WhatsAppProto::OnNotifyPicture(const WANode &node)
-{
- if (auto *pszFrom = node.getAttr("from"))
- if (m_szJid != pszFrom)
- if (auto *pszUser = FindUser(pszFrom))
- ServerFetchAvatar(pszFrom);
-
- SendAck(node);
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-void WhatsAppProto::OnProcessHandshake(const uint8_t *pData, int cbLen)
-{
- proto::HandshakeMessage msg(pData, cbLen);
- if (!msg) {
- debugLogA("Error parsing data, exiting");
-
-LBL_Error:
- ShutdownSession();
- return;
- }
-
- auto &static_ = msg->serverhello->static_;
- auto &payload_ = msg->serverhello->payload;
- auto &ephemeral_ = msg->serverhello->ephemeral;
-
- m_noise->updateHash(ephemeral_.data, ephemeral_.len);
- m_noise->mixIntoKey(m_noise->ephemeral.priv.data(), ephemeral_.data);
-
- MBinBuffer decryptedStatic = m_noise->decrypt(static_.data, static_.len);
- m_noise->mixIntoKey(m_noise->ephemeral.priv.data(), decryptedStatic.data());
-
- proto::CertChain cert(m_noise->decrypt(payload_.data, payload_.len));
- proto::CertChain__NoiseCertificate__Details details(cert->intermediate->details);
- if (details->issuerserial != 0) {
- debugLogA("Invalid certificate serial number, exiting");
- goto LBL_Error;
- }
-
- MBinBuffer encryptedPub = m_noise->encrypt(m_noise->noiseKeys.pub.data(), m_noise->noiseKeys.pub.length());
- m_noise->mixIntoKey(m_noise->noiseKeys.priv.data(), ephemeral_.data);
-
- // create reply
- Wa__ClientPayload node;
- Wa__ClientPayload__DevicePairingRegistrationData pairingData;
- Wa__DeviceProps companion;
- Wa__DeviceProps__AppVersion appVersion;
- T2Utf devName(m_wszDeviceName);
-
- MFileVersion v;
- Miranda_GetFileVersion(&v);
-
- // not received our jid from server? generate registration packet then
- if (m_szJid.IsEmpty()) {
- uint8_t buildHash[16];
- mir_md5_hash((BYTE *)APP_VERSION, sizeof(APP_VERSION) - 1, buildHash);
-
- appVersion.primary = v[0]; appVersion.has_primary = true;
- appVersion.secondary = v[1]; appVersion.has_secondary = true;
- appVersion.tertiary = v[2]; appVersion.has_tertiary = true;
- appVersion.quaternary = v[3]; appVersion.has_quaternary = true;
-
- companion.os = devName.get();
- companion.version = &appVersion;
- companion.platformtype = WA__DEVICE_PROPS__PLATFORM_TYPE__DESKTOP; companion.has_platformtype = true;
- companion.requirefullsync = false; companion.has_requirefullsync = true;
-
- MBinBuffer buf(proto::Serialize(&companion));
- auto szRegId(encodeBigEndian(getDword(DBKEY_REG_ID)));
- auto szKeyId(encodeBigEndian(m_signalStore.preKey.keyid));
-
- pairingData.deviceprops = proto::SetBinary(buf.data(), buf.length()); pairingData.has_deviceprops = true;
- pairingData.buildhash = proto::SetBinary(buildHash, sizeof(buildHash)); pairingData.has_buildhash = true;
- pairingData.eregid = proto::SetBinary(szRegId.c_str(), szRegId.size()); pairingData.has_eregid = true;
- pairingData.ekeytype = proto::SetBinary(KEY_BUNDLE_TYPE, 1); pairingData.has_ekeytype = true;
- pairingData.eident = proto::SetBinary(m_signalStore.signedIdentity.pub.data(), m_signalStore.signedIdentity.pub.length()); pairingData.has_eident = true;
- pairingData.eskeyid = proto::SetBinary(szKeyId.c_str(), szKeyId.size()); pairingData.has_eskeyid = true;
- pairingData.eskeyval = proto::SetBinary(m_signalStore.preKey.pub.data(), m_signalStore.preKey.pub.length()); pairingData.has_eskeyval = true;
- pairingData.eskeysig = proto::SetBinary(m_signalStore.preKey.signature.data(), m_signalStore.preKey.signature.length()); pairingData.has_eskeysig = true;
- node.devicepairingdata = &pairingData;
-
- node.passive = false; node.has_passive = true;
- }
- // generate login packet
- else {
- WAJid jid(m_szJid);
- node.username = _atoi64(jid.user); node.has_username = true;
- node.device = getDword(DBKEY_DEVICE_ID); node.has_device = true;
- node.passive = true; node.has_passive = true;
- }
-
- Wa__ClientPayload__UserAgent__AppVersion userVersion;
- userVersion.primary = 2; userVersion.has_primary = true;
- userVersion.secondary = 2230; userVersion.has_secondary = true;
- userVersion.tertiary = 15; userVersion.has_tertiary = true;
-
- Wa__ClientPayload__UserAgent userAgent;
- userAgent.appversion = &userVersion;
- userAgent.platform = WA__CLIENT_PAYLOAD__USER_AGENT__PLATFORM__WEB; userAgent.has_platform = true;
- userAgent.releasechannel = WA__CLIENT_PAYLOAD__USER_AGENT__RELEASE_CHANNEL__RELEASE; userAgent.has_releasechannel = true;
- userAgent.mcc = "000";
- userAgent.mnc = "000";
- userAgent.osversion = "0.1";
- userAgent.osbuildnumber = "0.1";
- userAgent.manufacturer = "";
- userAgent.device = "Desktop";
- userAgent.localelanguageiso6391 = "en";
- userAgent.localecountryiso31661alpha2 = "US";
-
- Wa__ClientPayload__WebInfo webInfo;
- webInfo.websubplatform = WA__CLIENT_PAYLOAD__WEB_INFO__WEB_SUB_PLATFORM__WEB_BROWSER; webInfo.has_websubplatform = true;
-
- node.connecttype = WA__CLIENT_PAYLOAD__CONNECT_TYPE__WIFI_UNKNOWN; node.has_connecttype = true;
- node.connectreason = WA__CLIENT_PAYLOAD__CONNECT_REASON__USER_ACTIVATED; node.has_connectreason = true;
- node.useragent = &userAgent;
- node.webinfo = &webInfo;
-
- MBinBuffer payload(proto::Serialize(&node));
- MBinBuffer payloadEnc = m_noise->encrypt(payload.data(), payload.length());
-
- Wa__HandshakeMessage__ClientFinish finish;
- finish.payload = {payloadEnc.length(), payloadEnc.data()}; finish.has_payload = true;
- finish.static_ = {encryptedPub.length(), encryptedPub.data()}; finish.has_static_ = true;
-
- Wa__HandshakeMessage handshake;
- handshake.clientfinish = &finish;
- WSSend(handshake);
-
- m_noise->finish();
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-void WhatsAppProto::OnReceiveFailure(const WANode &node)
-{
- m_bTerminated = true;
-
- ProcessFailure(node.getAttrInt("reason"));
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-void WhatsAppProto::OnReceiveInfo(const WANode &node)
-{
- if (auto *pChild = node.getFirstChild()) {
- if (pChild->title == "offline") {
- debugLogA("Processed %d offline events", pChild->getAttrInt("count"));
-
- // retrieve loaded prekeys count
- if (!m_bUpdatedPrekeys)
- WSSendNode(WANodeIq(IQ::GET, "encrypt") << XCHILD("count"), &WhatsAppProto::OnIqCountPrekeys);
-
- auto *pUser = FindUser(m_szJid);
- if (pUser->arDevices.getCount() == 0) {
- LIST<char> jids(1);
- jids.insert(m_szJid.GetBuffer());
- SendUsync(jids, nullptr);
- }
-
- for (auto &it : m_arCollections) {
- if (it->version == 0) {
- m_impl.m_resyncApp.Stop();
- m_impl.m_resyncApp.Start(1000);
- break;
- }
- }
- }
- }
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-void WhatsAppProto::ProcessReceipt(MCONTACT hContact, const char *msgId, bool bRead)
-{
- MEVENT hEvent = db_event_getById(m_szModuleName, msgId);
- if (hEvent == 0)
- return;
-
- if (g_plugin.bHasMessageState)
- CallService(MS_MESSAGESTATE_UPDATE, hContact, bRead ? MRD_TYPE_READ : MRD_TYPE_DELIVERED);
-
- if (bRead)
- db_event_markRead(hContact, hEvent);
-}
-
-void WhatsAppProto::OnReceiveReceipt(const WANode &node)
-{
- if (!mir_strcmp(node.getAttr("type"), "retry")) {
- for (auto &it : node.getChildren())
- if (it->title == "keys")
- m_signalStore.injectSession(node.getAttr("from"), &node, it);
- return;
- }
-
- if (auto *pUser = FindUser(node.getAttr("from"))) {
- bool bRead = mir_strcmp(node.getAttr("type"), "read") == 0;
- ProcessReceipt(pUser->hContact, node.getAttr("id"), bRead);
-
- if (auto *pList = node.getChild("list"))
- for (auto &it : pList->getChildren())
- if (it->title == "item")
- ProcessReceipt(pUser->hContact, it->getAttr("id"), bRead);
- }
-
- SendAck(node);
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-void WhatsAppProto::OnStreamError(const WANode &node)
-{
- m_bTerminated = true;
-
- if (auto *pszCode = node.getAttr("code"))
- ProcessFailure(atoi(pszCode));
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-void WhatsAppProto::OnSuccess(const WANode &)
-{
- OnLoggedIn();
-
- WSSendNode(WANodeIq(IQ::SET, "passive") << XCHILD("active"), &WhatsAppProto::OnIqDoNothing);
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-void WhatsAppProto::InitPersistentHandlers()
-{
- m_arPersistent.insert(new WAPersistentHandler("iq", "set", "md", "pair-device", &WhatsAppProto::OnIqPairDevice));
- m_arPersistent.insert(new WAPersistentHandler("iq", "set", "md", "pair-success", &WhatsAppProto::OnIqPairSuccess));
-
- m_arPersistent.insert(new WAPersistentHandler("notification", "devices", 0, 0, &WhatsAppProto::OnNotifyDevices));
- m_arPersistent.insert(new WAPersistentHandler("notification", "encrypt", 0, 0, &WhatsAppProto::OnNotifyEncrypt));
- m_arPersistent.insert(new WAPersistentHandler("notification", "picture", 0, 0, &WhatsAppProto::OnNotifyPicture));
- m_arPersistent.insert(new WAPersistentHandler("notification", "account_sync", 0, 0, &WhatsAppProto::OnAccountSync));
- m_arPersistent.insert(new WAPersistentHandler("notification", "server_sync", 0, 0, &WhatsAppProto::OnServerSync));
- m_arPersistent.insert(new WAPersistentHandler("notification", 0, 0, 0, &WhatsAppProto::OnNotifyAny));
-
- m_arPersistent.insert(new WAPersistentHandler("ack", 0, 0, 0, &WhatsAppProto::OnReceiveAck));
- m_arPersistent.insert(new WAPersistentHandler("ib", 0, 0, 0, &WhatsAppProto::OnReceiveInfo));
- m_arPersistent.insert(new WAPersistentHandler("failure", 0, 0, 0, &WhatsAppProto::OnReceiveFailure));
- m_arPersistent.insert(new WAPersistentHandler("message", 0, 0, 0, &WhatsAppProto::OnReceiveMessage));
- m_arPersistent.insert(new WAPersistentHandler("receipt", 0, 0, 0, &WhatsAppProto::OnReceiveReceipt));
- m_arPersistent.insert(new WAPersistentHandler("chatstates", 0, 0, 0, &WhatsAppProto::OnReceiveChatState));
- m_arPersistent.insert(new WAPersistentHandler("stream:error", 0, 0, 0, &WhatsAppProto::OnStreamError));
- m_arPersistent.insert(new WAPersistentHandler("success", 0, 0, 0, &WhatsAppProto::OnSuccess));
-
- m_arPersistent.insert(new WAPersistentHandler(0, "result", 0, 0, &WhatsAppProto::OnIqResult));
-}
+/*
+
+WhatsApp plugin for Miranda NG
+Copyright © 2019-23 George Hazan
+
+*/
+
+#include "stdafx.h"
+
+void WhatsAppProto::OnAccountSync(const WANode &node)
+{
+ if (auto *pList = node.getChild("devices")) {
+ auto *pUser = FindUser(m_szJid);
+ pUser->arDevices.destroy();
+
+ for (auto &it : pList->getChildren())
+ if (it->title == "device")
+ pUser->arDevices.insert(new WAJid(it->getAttr("jid"), it->getAttrInt("key-index")));
+ }
+
+ if (auto *pList = node.getChild("blocklist"))
+ for (auto &it : pList->getChildren())
+ if (it->title == "item") {
+ auto *pUser = AddUser(it->getAttr("jid"), false);
+ Contact::Hide(pUser->hContact, 0 == mir_strcmp(it->getAttr("action"), "block"));
+ }
+
+ SendAck(node);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void WhatsAppProto::OnIqBlockList(const WANode &node)
+{
+ for (auto &it : node.getChild("list")->getChildren()) {
+ auto *pUser = AddUser(it->getAttr("jid"), false);
+ Contact::Hide(pUser->hContact);
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void WhatsAppProto::OnIqCountPrekeys(const WANode &node)
+{
+ m_bUpdatedPrekeys = true;
+
+ int iCount = node.getChild("count")->getAttrInt("value");
+ if (iCount < 5)
+ UploadMorePrekeys();
+}
+
+void WhatsAppProto::UploadMorePrekeys()
+{
+ WANodeIq iq(IQ::SET, "encrypt");
+
+ auto regId = encodeBigEndian(getDword(DBKEY_REG_ID));
+ iq.addChild("registration")->content.append(regId.c_str(), regId.size());
+
+ iq.addChild("type")->content.append(KEY_BUNDLE_TYPE, 1);
+ iq.addChild("identity")->content.append(m_signalStore.signedIdentity.pub);
+
+ const int PORTION = 10;
+ m_signalStore.generatePrekeys(PORTION);
+
+ int iStart = getDword(DBKEY_PREKEY_UPLOAD_ID, 1);
+ auto *n = iq.addChild("list");
+ for (int i = 0; i < PORTION; i++) {
+ auto *nKey = n->addChild("key");
+
+ int keyId = iStart + i;
+ auto encId = encodeBigEndian(keyId, 3);
+ nKey->addChild("id")->content.append(encId.c_str(), encId.size());
+ nKey->addChild("value")->content.append(getBlob(CMStringA(FORMAT, "PreKey%dPublic", keyId)));
+ }
+ setDword(DBKEY_PREKEY_UPLOAD_ID, iStart + PORTION);
+
+ auto *skey = iq.addChild("skey");
+
+ auto encId = encodeBigEndian(m_signalStore.preKey.keyid, 3);
+ skey->addChild("id")->content.append(encId.c_str(), encId.size());
+ skey->addChild("value")->content.append(m_signalStore.preKey.pub);
+ skey->addChild("signature")->content.append(m_signalStore.preKey.signature);
+
+ WSSendNode(iq, &WhatsAppProto::OnIqDoNothing);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void WhatsAppProto::OnIqDoNothing(const WANode&)
+{
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void WhatsAppProto::OnIqGetKeys(const WANode &node, void *pUserInfo)
+{
+ for (auto &it : node.getChild("list")->getChildren())
+ if (it->title == "user")
+ m_signalStore.injectSession(it->getAttr("jid"), it, it);
+
+ // don't forget to send delayed message when all keys are retrieved
+ if (pUserInfo)
+ FinishTask((WASendTask *)pUserInfo);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void WhatsAppProto::OnIqGetUsync(const WANode &node, void *pUserInfo)
+{
+ WANodeIq iq(IQ::GET, "encrypt");
+ auto *pKey = iq.addChild("key");
+
+ for (auto *nUser : node.getChild("usync")->getChild("list")->getChildren()) {
+ auto *pszJid = nUser->getAttr("jid");
+
+ auto *pUser = AddUser(pszJid, false);
+ pUser->bDeviceInit = true;
+ pUser->arDevices.destroy();
+
+ if (auto *pList = nUser->getChild("devices")->getChild("device-list"))
+ for (auto &it : pList->getChildren())
+ if (it->title == "device")
+ pUser->arDevices.insert(new WAJid(pszJid, it->getAttrInt("id")));
+
+ for (auto &it : pUser->arDevices) {
+ auto blob = getBlob(MSignalSession(it->user, it->device).getSetting());
+ if (blob.isEmpty())
+ pKey->addChild("user")->addAttr("jid", it->toString());
+ }
+ }
+
+ if (pKey->getChildren().getCount() > 0)
+ WSSendNode(iq, &WhatsAppProto::OnIqGetKeys, pUserInfo);
+ else if (pUserInfo)
+ FinishTask((WASendTask *)pUserInfo);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void WhatsAppProto::OnIqPairDevice(const WANode &node)
+{
+ WSSendNode(WANodeIq(IQ::RESULT) << CHAR_PARAM("id", node.getAttr("id")));
+
+ if (auto *pRef = node.getChild("pair-device")->getChild("ref")) {
+ ShowQrCode(pRef->getBody());
+ }
+ else {
+ debugLogA("OnIqPairDevice: got reply without ref, exiting");
+ ShutdownSession();
+ }
+}
+
+void WhatsAppProto::OnIqPairSuccess(const WANode &node)
+{
+ CloseQrDialog();
+
+ auto *pRoot = node.getChild("pair-success");
+
+ try {
+ if (auto *pPlatform = pRoot->getChild("platform"))
+ debugLogA("Got response from platform: %s", pPlatform->getBody().c_str());
+
+ if (auto *pBiz = pRoot->getChild("biz"))
+ if (auto *pszName = pBiz->getAttr("name"))
+ setUString("Nick", pszName);
+
+ if (auto *pDevice = pRoot->getChild("device")) {
+ if (auto *pszJid = pDevice->getAttr("jid")) {
+ WAJid jid(pszJid);
+ m_szJid = jid.user + "@" + jid.server;
+ m_arUsers.insert(new WAUser(0, m_szJid, false));
+
+ setUString(DBKEY_ID, m_szJid);
+ setDword(DBKEY_DEVICE_ID, jid.device);
+ }
+ }
+ else throw "OnIqPairSuccess: got reply without device info, exiting";
+
+ if (auto *pIdentity = pRoot->getChild("device-identity")) {
+ proto::ADVSignedDeviceIdentityHMAC payload(pIdentity->content);
+
+ auto &hmac = payload->hmac;
+ auto &details = payload->details;
+ {
+ // check details signature using HMAC
+ uint8_t signature[32];
+ unsigned int out_len = sizeof(signature);
+ MBinBuffer secret(getBlob(DBKEY_SECRET_KEY));
+ HMAC(EVP_sha256(), secret.data(), (int)secret.length(), details.data, (int)details.len, signature, &out_len);
+ if (memcmp(hmac.data, signature, sizeof(signature)))
+ throw "OnIqPairSuccess: got reply with invalid details signature, exiting";
+ }
+
+ proto::ADVSignedDeviceIdentity account(details);
+
+ auto &deviceDetails = account->details;
+ auto &accountSignature = account->accountsignature;
+ auto &accountSignatureKey = account->accountsignaturekey;
+ {
+ MBinBuffer buf;
+ buf.append("\x06\x00", 2);
+ buf.append(deviceDetails.data, deviceDetails.len);
+ buf.append(m_signalStore.signedIdentity.pub);
+
+ ec_public_key key = {};
+ memcpy(key.data, accountSignatureKey.data, sizeof(key.data));
+ if (1 != curve_verify_signature(&key, buf.data(), buf.length(), accountSignature.data, accountSignature.len))
+ throw "OnIqPairSuccess: got reply with invalid account signature, exiting";
+ }
+ debugLogA("Received valid account signature");
+ {
+ MBinBuffer buf;
+ buf.append("\x06\x01", 2);
+ buf.append(deviceDetails.data, deviceDetails.len);
+ buf.append(m_signalStore.signedIdentity.pub);
+ buf.append(accountSignatureKey.data, accountSignatureKey.len);
+
+ signal_buffer *result;
+ ec_private_key key = {};
+ memcpy(key.data, m_signalStore.signedIdentity.priv.data(), m_signalStore.signedIdentity.priv.length());
+ if (curve_calculate_signature(m_signalStore.CTX(), &result, &key, buf.data(), buf.length()) != 0)
+ throw "OnIqPairSuccess: cannot calculate account signature, exiting";
+
+ account->devicesignature = proto::SetBinary(result->data, result->len);
+ account->has_devicesignature = true;
+ signal_buffer_free(result);
+ }
+
+ {
+ MBinBuffer key;
+ if (accountSignatureKey.len == 32)
+ key.append(KEY_BUNDLE_TYPE, 1);
+ key.append(accountSignatureKey.data, accountSignatureKey.len);
+ db_set_blob(0, m_szModuleName, "SignalIdentifierKey", key.data(), (int)key.length());
+ }
+
+ proto::CleanBinary(account->accountsignaturekey); account->has_accountsignaturekey = false;
+ MBinBuffer accountEnc(proto::Serialize(account));
+ db_set_blob(0, m_szModuleName, "WAAccount", accountEnc.data(), (int)accountEnc.length());
+
+ proto::ADVDeviceIdentity deviceIdentity(deviceDetails);
+
+ WANodeIq reply(IQ::RESULT); reply << CHAR_PARAM("id", node.getAttr("id"));
+
+ WANode *nodePair = reply.addChild("pair-device-sign");
+
+ WANode *nodeDeviceIdentity = nodePair->addChild("device-identity");
+ nodeDeviceIdentity->addAttr("key-index", deviceIdentity->keyindex);
+ nodeDeviceIdentity->content.append(accountEnc.data(), accountEnc.length());
+ WSSendNode(reply);
+ }
+ else throw "OnIqPairSuccess: got reply without identity, exiting";
+ }
+ catch (const char *pErrMsg) {
+ debugLogA(pErrMsg);
+ ShutdownSession();
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void WhatsAppProto::OnIqResult(const WANode &node)
+{
+ if (auto *pszId = node.getAttr("id")) {
+ for (auto &it : m_arPacketQueue) {
+ if (it->szPacketId == pszId) {
+ it->Execute(this, node);
+ m_arPacketQueue.remove(it);
+ break;
+ }
+ }
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void WhatsAppProto::OnNotifyAny(const WANode &node)
+{
+ SendAck(node);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void WhatsAppProto::OnReceiveChatState(const WANode &node)
+{
+ if (auto *pUser = FindUser(node.getAttr("from"))) {
+ if (node.getChild("composing")) {
+ pUser->m_timer1 = time(0);
+ pUser->m_timer2 = 0;
+ setWord(pUser->hContact, "Status", ID_STATUS_ONLINE);
+
+ CallService(MS_PROTO_CONTACTISTYPING, pUser->hContact, 60);
+ }
+ else if (node.getChild("paused"))
+ CallService(MS_PROTO_CONTACTISTYPING, pUser->hContact, PROTOTYPE_CONTACTTYPING_OFF);
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void WhatsAppProto::OnNotifyDevices(const WANode &node)
+{
+ if (!mir_strcmp(node.getAttr("jid"), m_szJid))
+ debugLogA("received list of my own devices");
+ SendAck(node);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void WhatsAppProto::OnNotifyEncrypt(const WANode &node)
+{
+ if (!mir_strcmp(node.getAttr("from"), S_WHATSAPP_NET))
+ OnIqCountPrekeys(node);
+ SendAck(node);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void WhatsAppProto::OnNotifyPicture(const WANode &node)
+{
+ if (auto *pszFrom = node.getAttr("from"))
+ if (m_szJid != pszFrom)
+ if (auto *pszUser = FindUser(pszFrom))
+ ServerFetchAvatar(pszFrom);
+
+ SendAck(node);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void WhatsAppProto::OnProcessHandshake(const uint8_t *pData, int cbLen)
+{
+ proto::HandshakeMessage msg(pData, cbLen);
+ if (!msg) {
+ debugLogA("Error parsing data, exiting");
+
+LBL_Error:
+ ShutdownSession();
+ return;
+ }
+
+ auto &static_ = msg->serverhello->static_;
+ auto &payload_ = msg->serverhello->payload;
+ auto &ephemeral_ = msg->serverhello->ephemeral;
+
+ m_noise->updateHash(ephemeral_.data, ephemeral_.len);
+ m_noise->mixIntoKey(m_noise->ephemeral.priv.data(), ephemeral_.data);
+
+ MBinBuffer decryptedStatic = m_noise->decrypt(static_.data, static_.len);
+ m_noise->mixIntoKey(m_noise->ephemeral.priv.data(), decryptedStatic.data());
+
+ proto::CertChain cert(m_noise->decrypt(payload_.data, payload_.len));
+ proto::CertChain__NoiseCertificate__Details details(cert->intermediate->details);
+ if (details->issuerserial != 0) {
+ debugLogA("Invalid certificate serial number, exiting");
+ goto LBL_Error;
+ }
+
+ MBinBuffer encryptedPub = m_noise->encrypt(m_noise->noiseKeys.pub.data(), m_noise->noiseKeys.pub.length());
+ m_noise->mixIntoKey(m_noise->noiseKeys.priv.data(), ephemeral_.data);
+
+ // create reply
+ Wa__ClientPayload node;
+ Wa__ClientPayload__DevicePairingRegistrationData pairingData;
+ Wa__DeviceProps companion;
+ Wa__DeviceProps__AppVersion appVersion;
+ T2Utf devName(m_wszDeviceName);
+
+ MFileVersion v;
+ Miranda_GetFileVersion(&v);
+
+ // not received our jid from server? generate registration packet then
+ if (m_szJid.IsEmpty()) {
+ uint8_t buildHash[16];
+ mir_md5_hash((BYTE *)APP_VERSION, sizeof(APP_VERSION) - 1, buildHash);
+
+ appVersion.primary = v[0]; appVersion.has_primary = true;
+ appVersion.secondary = v[1]; appVersion.has_secondary = true;
+ appVersion.tertiary = v[2]; appVersion.has_tertiary = true;
+ appVersion.quaternary = v[3]; appVersion.has_quaternary = true;
+
+ companion.os = devName.get();
+ companion.version = &appVersion;
+ companion.platformtype = WA__DEVICE_PROPS__PLATFORM_TYPE__DESKTOP; companion.has_platformtype = true;
+ companion.requirefullsync = false; companion.has_requirefullsync = true;
+
+ MBinBuffer buf(proto::Serialize(&companion));
+ auto szRegId(encodeBigEndian(getDword(DBKEY_REG_ID)));
+ auto szKeyId(encodeBigEndian(m_signalStore.preKey.keyid));
+
+ pairingData.deviceprops = proto::SetBinary(buf.data(), buf.length()); pairingData.has_deviceprops = true;
+ pairingData.buildhash = proto::SetBinary(buildHash, sizeof(buildHash)); pairingData.has_buildhash = true;
+ pairingData.eregid = proto::SetBinary(szRegId.c_str(), szRegId.size()); pairingData.has_eregid = true;
+ pairingData.ekeytype = proto::SetBinary(KEY_BUNDLE_TYPE, 1); pairingData.has_ekeytype = true;
+ pairingData.eident = proto::SetBinary(m_signalStore.signedIdentity.pub.data(), m_signalStore.signedIdentity.pub.length()); pairingData.has_eident = true;
+ pairingData.eskeyid = proto::SetBinary(szKeyId.c_str(), szKeyId.size()); pairingData.has_eskeyid = true;
+ pairingData.eskeyval = proto::SetBinary(m_signalStore.preKey.pub.data(), m_signalStore.preKey.pub.length()); pairingData.has_eskeyval = true;
+ pairingData.eskeysig = proto::SetBinary(m_signalStore.preKey.signature.data(), m_signalStore.preKey.signature.length()); pairingData.has_eskeysig = true;
+ node.devicepairingdata = &pairingData;
+
+ node.passive = false; node.has_passive = true;
+ }
+ // generate login packet
+ else {
+ WAJid jid(m_szJid);
+ node.username = _atoi64(jid.user); node.has_username = true;
+ node.device = getDword(DBKEY_DEVICE_ID); node.has_device = true;
+ node.passive = true; node.has_passive = true;
+ }
+
+ Wa__ClientPayload__UserAgent__AppVersion userVersion;
+ userVersion.primary = 2; userVersion.has_primary = true;
+ userVersion.secondary = 2230; userVersion.has_secondary = true;
+ userVersion.tertiary = 15; userVersion.has_tertiary = true;
+
+ Wa__ClientPayload__UserAgent userAgent;
+ userAgent.appversion = &userVersion;
+ userAgent.platform = WA__CLIENT_PAYLOAD__USER_AGENT__PLATFORM__WEB; userAgent.has_platform = true;
+ userAgent.releasechannel = WA__CLIENT_PAYLOAD__USER_AGENT__RELEASE_CHANNEL__RELEASE; userAgent.has_releasechannel = true;
+ userAgent.mcc = "000";
+ userAgent.mnc = "000";
+ userAgent.osversion = "0.1";
+ userAgent.osbuildnumber = "0.1";
+ userAgent.manufacturer = "";
+ userAgent.device = "Desktop";
+ userAgent.localelanguageiso6391 = "en";
+ userAgent.localecountryiso31661alpha2 = "US";
+
+ Wa__ClientPayload__WebInfo webInfo;
+ webInfo.websubplatform = WA__CLIENT_PAYLOAD__WEB_INFO__WEB_SUB_PLATFORM__WEB_BROWSER; webInfo.has_websubplatform = true;
+
+ node.connecttype = WA__CLIENT_PAYLOAD__CONNECT_TYPE__WIFI_UNKNOWN; node.has_connecttype = true;
+ node.connectreason = WA__CLIENT_PAYLOAD__CONNECT_REASON__USER_ACTIVATED; node.has_connectreason = true;
+ node.useragent = &userAgent;
+ node.webinfo = &webInfo;
+
+ MBinBuffer payload(proto::Serialize(&node));
+ MBinBuffer payloadEnc = m_noise->encrypt(payload.data(), payload.length());
+
+ Wa__HandshakeMessage__ClientFinish finish;
+ finish.payload = {payloadEnc.length(), payloadEnc.data()}; finish.has_payload = true;
+ finish.static_ = {encryptedPub.length(), encryptedPub.data()}; finish.has_static_ = true;
+
+ Wa__HandshakeMessage handshake;
+ handshake.clientfinish = &finish;
+ WSSend(handshake);
+
+ m_noise->finish();
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void WhatsAppProto::OnReceiveFailure(const WANode &node)
+{
+ m_bTerminated = true;
+
+ ProcessFailure(node.getAttrInt("reason"));
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void WhatsAppProto::OnReceiveInfo(const WANode &node)
+{
+ if (auto *pChild = node.getFirstChild()) {
+ if (pChild->title == "offline") {
+ debugLogA("Processed %d offline events", pChild->getAttrInt("count"));
+
+ // retrieve loaded prekeys count
+ if (!m_bUpdatedPrekeys)
+ WSSendNode(WANodeIq(IQ::GET, "encrypt") << XCHILD("count"), &WhatsAppProto::OnIqCountPrekeys);
+
+ auto *pUser = FindUser(m_szJid);
+ if (pUser->arDevices.getCount() == 0) {
+ LIST<char> jids(1);
+ jids.insert(m_szJid.GetBuffer());
+ SendUsync(jids, nullptr);
+ }
+
+ for (auto &it : m_arCollections) {
+ if (it->version == 0) {
+ m_impl.m_resyncApp.Stop();
+ m_impl.m_resyncApp.Start(1000);
+ break;
+ }
+ }
+ }
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void WhatsAppProto::ProcessReceipt(MCONTACT hContact, const char *msgId, bool bRead)
+{
+ MEVENT hEvent = db_event_getById(m_szModuleName, msgId);
+ if (hEvent == 0)
+ return;
+
+ if (g_plugin.bHasMessageState)
+ CallService(MS_MESSAGESTATE_UPDATE, hContact, bRead ? MRD_TYPE_READ : MRD_TYPE_DELIVERED);
+
+ if (bRead)
+ db_event_markRead(hContact, hEvent);
+}
+
+void WhatsAppProto::OnReceiveReceipt(const WANode &node)
+{
+ if (!mir_strcmp(node.getAttr("type"), "retry")) {
+ for (auto &it : node.getChildren())
+ if (it->title == "keys")
+ m_signalStore.injectSession(node.getAttr("from"), &node, it);
+ return;
+ }
+
+ if (auto *pUser = FindUser(node.getAttr("from"))) {
+ bool bRead = mir_strcmp(node.getAttr("type"), "read") == 0;
+ ProcessReceipt(pUser->hContact, node.getAttr("id"), bRead);
+
+ if (auto *pList = node.getChild("list"))
+ for (auto &it : pList->getChildren())
+ if (it->title == "item")
+ ProcessReceipt(pUser->hContact, it->getAttr("id"), bRead);
+ }
+
+ SendAck(node);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void WhatsAppProto::OnStreamError(const WANode &node)
+{
+ m_bTerminated = true;
+
+ if (auto *pszCode = node.getAttr("code"))
+ ProcessFailure(atoi(pszCode));
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void WhatsAppProto::OnSuccess(const WANode &)
+{
+ OnLoggedIn();
+
+ WSSendNode(WANodeIq(IQ::SET, "passive") << XCHILD("active"), &WhatsAppProto::OnIqDoNothing);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void WhatsAppProto::InitPersistentHandlers()
+{
+ m_arPersistent.insert(new WAPersistentHandler("iq", "set", "md", "pair-device", &WhatsAppProto::OnIqPairDevice));
+ m_arPersistent.insert(new WAPersistentHandler("iq", "set", "md", "pair-success", &WhatsAppProto::OnIqPairSuccess));
+
+ m_arPersistent.insert(new WAPersistentHandler("notification", "devices", 0, 0, &WhatsAppProto::OnNotifyDevices));
+ m_arPersistent.insert(new WAPersistentHandler("notification", "encrypt", 0, 0, &WhatsAppProto::OnNotifyEncrypt));
+ m_arPersistent.insert(new WAPersistentHandler("notification", "picture", 0, 0, &WhatsAppProto::OnNotifyPicture));
+ m_arPersistent.insert(new WAPersistentHandler("notification", "account_sync", 0, 0, &WhatsAppProto::OnAccountSync));
+ m_arPersistent.insert(new WAPersistentHandler("notification", "server_sync", 0, 0, &WhatsAppProto::OnServerSync));
+ m_arPersistent.insert(new WAPersistentHandler("notification", 0, 0, 0, &WhatsAppProto::OnNotifyAny));
+
+ m_arPersistent.insert(new WAPersistentHandler("ack", 0, 0, 0, &WhatsAppProto::OnReceiveAck));
+ m_arPersistent.insert(new WAPersistentHandler("ib", 0, 0, 0, &WhatsAppProto::OnReceiveInfo));
+ m_arPersistent.insert(new WAPersistentHandler("failure", 0, 0, 0, &WhatsAppProto::OnReceiveFailure));
+ m_arPersistent.insert(new WAPersistentHandler("message", 0, 0, 0, &WhatsAppProto::OnReceiveMessage));
+ m_arPersistent.insert(new WAPersistentHandler("receipt", 0, 0, 0, &WhatsAppProto::OnReceiveReceipt));
+ m_arPersistent.insert(new WAPersistentHandler("chatstates", 0, 0, 0, &WhatsAppProto::OnReceiveChatState));
+ m_arPersistent.insert(new WAPersistentHandler("stream:error", 0, 0, 0, &WhatsAppProto::OnStreamError));
+ m_arPersistent.insert(new WAPersistentHandler("success", 0, 0, 0, &WhatsAppProto::OnSuccess));
+
+ m_arPersistent.insert(new WAPersistentHandler(0, "result", 0, 0, &WhatsAppProto::OnIqResult));
+}
diff --git a/protocols/WhatsApp/src/main.cpp b/protocols/WhatsApp/src/main.cpp
index dc4c252927..318a027b76 100644
--- a/protocols/WhatsApp/src/main.cpp
+++ b/protocols/WhatsApp/src/main.cpp
@@ -1,71 +1,71 @@
-/*
-
-WhatsApp plugin for Miranda NG
-Copyright © 2019-22 George Hazan
-
-*/
-
-#include "stdafx.h"
-#include "version.h"
-
-CMPlugin g_plugin;
-
-PLUGININFOEX pluginInfo = {
- sizeof(PLUGININFOEX),
- __PLUGIN_NAME,
- PLUGIN_MAKE_VERSION(__MAJOR_VERSION, __MINOR_VERSION, __RELEASE_NUM, __BUILD_NUM),
- __DESCRIPTION,
- __AUTHOR,
- __COPYRIGHT,
- __AUTHORWEB,
- UNICODE_AWARE, //not transient
- // {008B9CE1-154B-44E4-9823-97C1AAB00C3C}
- { 0x8b9ce1, 0x154b, 0x44e4, { 0x98, 0x23, 0x97, 0xc1, 0xaa, 0xb0, 0xc, 0x3c }}
-};
-
-/////////////////////////////////////////////////////////////////////////////////////////
-// Interface information
-
-extern "C" __declspec(dllexport) const MUUID MirandaInterfaces[] = { MIID_PROTOCOL, MIID_LAST };
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-CMPlugin::CMPlugin() :
- ACCPROTOPLUGIN<WhatsAppProto>(MODULENAME, pluginInfo)
-{
- SetUniqueId(DBKEY_ID);
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-// Load
-
-static int OnPluginLoaded(WPARAM, LPARAM)
-{
- g_plugin.bHasMessageState = ServiceExists(MS_MESSAGESTATE_UPDATE);
- return 0;
-}
-
-int CMPlugin::Load()
-{
- HookEvent(ME_SYSTEM_MODULELOAD, OnPluginLoaded);
- HookEvent(ME_SYSTEM_MODULEUNLOAD, OnPluginLoaded);
- OnPluginLoaded(0, 0);
-
- // special netlib user for reading avatars, blobs etc via HTTP protocol
- NETLIBUSER nlu = {};
- nlu.flags = NUF_INCOMING | NUF_OUTGOING | NUF_HTTPCONNS | NUF_UNICODE;
- nlu.szSettingsModule = "WhatsApp";
- nlu.szDescriptiveName.w = TranslateT("WhatsApp (HTTP)");
- hAvatarUser = Netlib_RegisterUser(&nlu);
- return 0;
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-// Unload
-
-int CMPlugin::Unload()
-{
- Netlib_CloseHandle(hAvatarConn);
- Netlib_CloseHandle(hAvatarUser);
- return 0;
-}
+/*
+
+WhatsApp plugin for Miranda NG
+Copyright © 2019-23 George Hazan
+
+*/
+
+#include "stdafx.h"
+#include "version.h"
+
+CMPlugin g_plugin;
+
+PLUGININFOEX pluginInfo = {
+ sizeof(PLUGININFOEX),
+ __PLUGIN_NAME,
+ PLUGIN_MAKE_VERSION(__MAJOR_VERSION, __MINOR_VERSION, __RELEASE_NUM, __BUILD_NUM),
+ __DESCRIPTION,
+ __AUTHOR,
+ __COPYRIGHT,
+ __AUTHORWEB,
+ UNICODE_AWARE, //not transient
+ // {008B9CE1-154B-44E4-9823-97C1AAB00C3C}
+ { 0x8b9ce1, 0x154b, 0x44e4, { 0x98, 0x23, 0x97, 0xc1, 0xaa, 0xb0, 0xc, 0x3c }}
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Interface information
+
+extern "C" __declspec(dllexport) const MUUID MirandaInterfaces[] = { MIID_PROTOCOL, MIID_LAST };
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+CMPlugin::CMPlugin() :
+ ACCPROTOPLUGIN<WhatsAppProto>(MODULENAME, pluginInfo)
+{
+ SetUniqueId(DBKEY_ID);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Load
+
+static int OnPluginLoaded(WPARAM, LPARAM)
+{
+ g_plugin.bHasMessageState = ServiceExists(MS_MESSAGESTATE_UPDATE);
+ return 0;
+}
+
+int CMPlugin::Load()
+{
+ HookEvent(ME_SYSTEM_MODULELOAD, OnPluginLoaded);
+ HookEvent(ME_SYSTEM_MODULEUNLOAD, OnPluginLoaded);
+ OnPluginLoaded(0, 0);
+
+ // special netlib user for reading avatars, blobs etc via HTTP protocol
+ NETLIBUSER nlu = {};
+ nlu.flags = NUF_INCOMING | NUF_OUTGOING | NUF_HTTPCONNS | NUF_UNICODE;
+ nlu.szSettingsModule = "WhatsApp";
+ nlu.szDescriptiveName.w = TranslateT("WhatsApp (HTTP)");
+ hAvatarUser = Netlib_RegisterUser(&nlu);
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Unload
+
+int CMPlugin::Unload()
+{
+ Netlib_CloseHandle(hAvatarConn);
+ Netlib_CloseHandle(hAvatarUser);
+ return 0;
+}
diff --git a/protocols/WhatsApp/src/message.cpp b/protocols/WhatsApp/src/message.cpp
index e8d2b775ec..250a63e4d1 100644
--- a/protocols/WhatsApp/src/message.cpp
+++ b/protocols/WhatsApp/src/message.cpp
@@ -1,502 +1,502 @@
-/*
-
-WhatsApp plugin for Miranda NG
-Copyright © 2019-22 George Hazan
-
-*/
-
-#include "stdafx.h"
-
-void WhatsAppProto::OnReceiveMessage(const WANode &node)
-{
- auto *msgId = node.getAttr("id");
- auto *msgType = node.getAttr("type");
- auto *msgFrom = node.getAttr("from");
- auto *category = node.getAttr("category");
- auto *recipient = node.getAttr("recipient");
- auto *participant = node.getAttr("participant");
-
- if (msgType == nullptr || msgFrom == nullptr || msgId == nullptr) {
- debugLogA("bad message received: <%s> <%s> <%s>", msgType, msgFrom, msgId);
- return;
- }
-
- SendAck(node);
-
- MEVENT hEvent = db_event_getById(m_szModuleName, msgId);
- if (hEvent) {
- debugLogA("this message is already processed: %s", msgId);
- return;
- }
-
- WAMSG type;
- WAJid jid(msgFrom);
- CMStringA szAuthor, szChatId;
-
- if (node.getAttr("offline"))
- type.bOffline = true;
-
- // message from one user to another
- if (jid.isUser()) {
- if (recipient) {
- if (m_szJid != msgFrom) {
- debugLogA("strange message: with recipient, but not from me");
- return;
- }
- szChatId = recipient;
- }
- else szChatId = msgFrom;
-
- type.bPrivateChat = true;
- szAuthor = msgFrom;
- }
- else if (jid.isGroup()) {
- if (!participant) {
- debugLogA("strange message: from group, but without participant");
- return;
- }
-
- type.bGroupChat = true;
- szAuthor = participant;
- szChatId = msgFrom;
- }
- else if (jid.isBroadcast()) {
- if (!participant) {
- debugLogA("strange message: from group, but without participant");
- return;
- }
-
- bool bIsMe = m_szJid == participant;
- if (jid.isStatusBroadcast()) {
- if (bIsMe)
- type.bDirectStatus = true;
- else
- type.bOtherStatus = true;
- }
- else {
- if (bIsMe)
- type.bPeerBroadcast = true;
- else
- type.bOtherBroadcast = true;
- }
- szChatId = msgFrom;
- szAuthor = participant;
- }
- else {
- debugLogA("invalid message type");
- return;
- }
-
- CMStringA szSender = (type.bPrivateChat) ? szAuthor : szChatId;
- bool bFromMe = (m_szJid == msgFrom);
- if (!bFromMe && participant)
- bFromMe = m_szJid == participant;
-
- Wa__MessageKey key;
- key.remotejid = szChatId.GetBuffer();
- key.id = (char*)msgId;
- key.fromme = bFromMe; key.has_fromme = true;
- if (participant)
- key.participant = (char*)participant;
-
- Wa__WebMessageInfo msg;
- msg.key = &key;
- msg.messagetimestamp = _atoi64(node.getAttr("t")); msg.has_messagetimestamp = true;
- msg.pushname = (char*)node.getAttr("notify");
- if (bFromMe)
- msg.status = WA__WEB_MESSAGE_INFO__STATUS__SERVER_ACK, msg.has_status = true;
-
- int iDecryptable = 0;
-
- for (auto &it : node.getChildren()) {
- if (it->title != "enc" || it->content.length() == 0)
- continue;
-
- MBinBuffer msgBody;
- auto *pszType = it->getAttr("type");
- try {
- if (!mir_strcmp(pszType, "pkmsg") || !mir_strcmp(pszType, "msg")) {
- CMStringA szUser = (WAJid(szSender).isUser()) ? szSender : szAuthor;
- msgBody = m_signalStore.decryptSignalProto(szUser, pszType, it->content);
- }
- else if (!mir_strcmp(pszType, "skmsg")) {
- msgBody = m_signalStore.decryptGroupSignalProto(szSender, szAuthor, it->content);
- }
- else throw "Invalid e2e type";
-
- if (msgBody.isEmpty())
- throw "Invalid e2e message";
-
- iDecryptable++;
-
- proto::Message encMsg(unpadBuffer16(msgBody));
- if (!encMsg)
- throw "Invalid decoded message";
-
- if (encMsg->devicesentmessage)
- msg.message = encMsg->devicesentmessage->message;
- else
- msg.message = encMsg;
-
- if (encMsg->senderkeydistributionmessage)
- m_signalStore.processSenderKeyMessage(szAuthor, encMsg->senderkeydistributionmessage);
-
- ProcessMessage(type, msg);
-
- // send receipt
- const char *pszReceiptType = nullptr, *pszReceiptTo = participant;
- if (!mir_strcmp(category, "peer"))
- pszReceiptType = "peer_msg";
- else if (bFromMe) {
- // message was sent by me from a different device
- pszReceiptType = "sender";
- if (WAJid(szChatId).isUser())
- pszReceiptTo = szAuthor;
- }
- else if (!m_hServerConn)
- pszReceiptType = "inactive";
-
- SendReceipt(szChatId, pszReceiptTo, msgId, pszReceiptType);
- }
- catch (const char *pszError) {
- debugLogA("Message decryption failed with error: %s", pszError);
- }
-
- if (!iDecryptable) {
- debugLogA("Nothing to decrypt");
- return;
- }
- }
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-static const Wa__Message* getBody(const Wa__Message *message)
-{
- if (message->ephemeralmessage) {
- auto *pMsg = message->ephemeralmessage->message;
- return (pMsg->viewoncemessage) ? pMsg->viewoncemessage->message : pMsg;
- }
-
- if (message->viewoncemessage)
- return message->viewoncemessage->message;
-
- return message;
-}
-
-void WhatsAppProto::ProcessMessage(WAMSG type, const Wa__WebMessageInfo &msg)
-{
- auto *key = msg.key;
- auto *body = getBody(msg.message);
-
- debugLogA("Got a message: %s", protobuf_c_text_to_string(&msg).c_str());
-
- uint32_t timestamp = msg.messagetimestamp;
- char *participant = key->participant, *chatId;
- auto *msgId = key->id;
-
- if (type.bPrivateChat || type.bGroupChat)
- chatId = key->remotejid;
- else
- chatId = (participant) ? participant : key->remotejid;
-
- WAJid jidFrom(chatId); jidFrom.device = 0;
- WAUser *pUser = AddUser(jidFrom.toString(), false);
-
- if (!key->fromme && msg.pushname && pUser && !pUser->bIsGroupChat)
- setUString(pUser->hContact, "Nick", msg.pushname);
-
- // try to extract some text
- if (pUser) {
- CMStringA szMessageText(GetMessageText(body));
- if (!szMessageText.IsEmpty()) {
- // for chats & group chats store message in profile
- if (type.bPrivateChat || type.bGroupChat) {
- PROTORECVEVENT pre = {};
- pre.timestamp = timestamp;
- pre.szMessage = szMessageText.GetBuffer();
- pre.szMsgId = msgId;
- if (type.bOffline)
- pre.flags |= PREF_CREATEREAD;
- if (key->fromme)
- pre.flags |= PREF_SENT;
- ProtoChainRecvMsg(pUser->hContact, &pre);
-
- if (pUser->bIsGroupChat) {
- GCEVENT gce = {m_szModuleName, 0, GC_EVENT_MESSAGE};
- gce.dwFlags = GCEF_UTF8;
- gce.pszID.a = pUser->szId;
- gce.pszUID.a = participant;
- gce.bIsMe = key->fromme;
- gce.pszText.a = szMessageText.GetBuffer();
- gce.time = timestamp;
- Chat_Event(&gce);
- }
- }
- // translate statuses into status messages
- else if (type.bOtherStatus || type.bDirectStatus || type.bPeerBroadcast || type.bOtherBroadcast) {
- setUString(pUser->hContact, "StatusMsg", szMessageText);
- }
- }
- }
-
- if (body->protocolmessage) {
- auto *protoMsg = body->protocolmessage;
- switch (protoMsg->type) {
- case WA__MESSAGE__PROTOCOL_MESSAGE__TYPE__APP_STATE_SYNC_KEY_SHARE:
- for (int i = 0; i < protoMsg->appstatesynckeyshare->n_keys; i++) {
- auto *it = protoMsg->appstatesynckeyshare->keys[i];
- auto &keyid = it->keyid->keyid;
- auto &keydata = it->keydata->keydata;
-
- CMStringA szSetting(FORMAT, "AppSyncKey%d", decodeBigEndian(keyid));
- db_set_blob(0, m_szModuleName, szSetting, keydata.data, (unsigned)keydata.len);
- }
- break;
-
- case WA__MESSAGE__PROTOCOL_MESSAGE__TYPE__APP_STATE_FATAL_EXCEPTION_NOTIFICATION:
- m_impl.m_resyncApp.Stop();
- m_impl.m_resyncApp.Start(10000);
- break;
-
- case WA__MESSAGE__PROTOCOL_MESSAGE__TYPE__HISTORY_SYNC_NOTIFICATION:
- debugLogA("History sync notification");
- if (auto *pHist = protoMsg->historysyncnotification) {
- MBinBuffer buf(DownloadEncryptedFile(directPath2url(pHist->directpath), pHist->mediakey, "History"));
- if (!buf.isEmpty()) {
- MBinBuffer inflate(unzip(unpadBuffer16(buf)));
-
- proto::HistorySync sync(inflate);
- if (sync)
- ProcessHistorySync(sync);
- }
- }
- break;
-
- case WA__MESSAGE__PROTOCOL_MESSAGE__TYPE__REVOKE:
- break;
-
- case WA__MESSAGE__PROTOCOL_MESSAGE__TYPE__EPHEMERAL_SETTING:
- if (pUser) {
- setDword(pUser->hContact, DBKEY_EPHEMERAL_TS, timestamp);
- setDword(pUser->hContact, DBKEY_EPHEMERAL_EXPIRE, protoMsg->ephemeralexpiration);
- }
- break;
- }
- }
- else if (body->reactionmessage) {
- debugLogA("Got a reaction to a message");
- }
- else if (msg.has_messagestubtype) {
- switch (msg.messagestubtype) {
- case WA__WEB_MESSAGE_INFO__STUB_TYPE__GROUP_PARTICIPANT_LEAVE:
- case WA__WEB_MESSAGE_INFO__STUB_TYPE__GROUP_PARTICIPANT_REMOVE:
- debugLogA("Participant %s removed from chat", participant);
- break;
-
- case WA__WEB_MESSAGE_INFO__STUB_TYPE__GROUP_PARTICIPANT_ADD:
- case WA__WEB_MESSAGE_INFO__STUB_TYPE__GROUP_PARTICIPANT_INVITE:
- case WA__WEB_MESSAGE_INFO__STUB_TYPE__GROUP_PARTICIPANT_ADD_REQUEST_JOIN:
- debugLogA("Participant %s added to chat", participant);
- break;
-
- case WA__WEB_MESSAGE_INFO__STUB_TYPE__GROUP_PARTICIPANT_DEMOTE:
- debugLogA("Participant %s demoted", participant);
- break;
-
- case WA__WEB_MESSAGE_INFO__STUB_TYPE__GROUP_PARTICIPANT_PROMOTE:
- debugLogA("Participant %s promoted", participant);
- break;
-
- case WA__WEB_MESSAGE_INFO__STUB_TYPE__GROUP_CHANGE_ANNOUNCE:
- debugLogA("Groupchat announce", participant);
- break;
-
- case WA__WEB_MESSAGE_INFO__STUB_TYPE__GROUP_CHANGE_RESTRICT:
- debugLogA("Groupchat restriction", participant);
- break;
-
- case WA__WEB_MESSAGE_INFO__STUB_TYPE__GROUP_CHANGE_SUBJECT:
- debugLogA("Groupchat subject was changed", participant);
- break;
- }
- }
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-void WhatsAppProto::OnReceiveAck(const WANode &node)
-{
- auto *pUser = FindUser(node.getAttr("from"));
- if (pUser == nullptr)
- return;
-
- if (!mir_strcmp(node.getAttr("class"), "message")) {
- WAOwnMessage tmp(0, 0, node.getAttr("id"));
- {
- mir_cslock lck(m_csOwnMessages);
- if (auto *pOwn = m_arOwnMsgs.find(&tmp)) {
- tmp.pktId = pOwn->pktId;
- m_arOwnMsgs.remove(pOwn);
- }
- else return;
- }
- ProtoBroadcastAck(pUser->hContact, ACKTYPE_MESSAGE, ACKRESULT_SUCCESS, (HANDLE)tmp.pktId, (LPARAM)tmp.szMessageId.c_str());
- }
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-bool WhatsAppProto::CreateMsgParticipant(WANode *pParticipants, const WAJid &jid, const MBinBuffer &orig)
-{
- int type = 0;
-
- try {
- MBinBuffer pBuffer(m_signalStore.encryptSignalProto(jid, orig, type));
-
- auto *pNode = pParticipants->addChild("to");
- pNode->addAttr("jid", jid.toString());
-
- auto *pEnc = pNode->addChild("enc");
- *pEnc << CHAR_PARAM("v", "2") << CHAR_PARAM("type", (type == 3) ? "pkmsg" : "msg");
- pEnc->content.assign(pBuffer.data(), pBuffer.length());
- }
- catch (const char *) {
- }
-
- return type == 3;
-}
-
-int WhatsAppProto::SendTextMessage(const char *jid, const char *pszMsg)
-{
- WAJid toJid(jid);
-
- // send task creation
- auto *pTask = new WASendTask(jid);
-
- // basic message
- Wa__Message__ExtendedTextMessage extMessage;
- extMessage.text = (char *)pszMsg;
-
- Wa__Message body;
- body.extendedtextmessage = &extMessage;
-
- LIST<char> arCheckList(1);
- if (toJid.isGroup()) {
- MBinBuffer encodedMsg(proto::Serialize(&body));
- padBuffer16(encodedMsg);
-
- MBinBuffer skmsgKey;
- MBinBuffer cipherText(m_signalStore.encryptSenderKey(toJid, m_szJid, encodedMsg, skmsgKey));
-
- auto *pEnc = pTask->payLoad.addChild("enc");
- *pEnc << CHAR_PARAM("v", "2") << CHAR_PARAM("type", "skmsg");
- pEnc->content.append(cipherText.data(), cipherText.length());
-
- Wa__Message__SenderKeyDistributionMessage sentBody;
- sentBody.axolotlsenderkeydistributionmessage.data = skmsgKey.data();
- sentBody.axolotlsenderkeydistributionmessage.len = skmsgKey.length();
- sentBody.has_axolotlsenderkeydistributionmessage = true;
- sentBody.groupid = (char*)jid;
-
- Wa__Message msg;
- msg.senderkeydistributionmessage = &sentBody;
-
- pTask->content.append(proto::Serialize(&msg));
-
- if (auto *pChatUser = FindUser(jid)) {
- if (pChatUser->si) {
- for (auto &it : pChatUser->si->arUsers) {
- T2Utf userJid(it->pszUID);
- auto *pUser = FindUser(jid);
- if (pUser == nullptr)
- m_arUsers.insert(pUser = new WAUser(INVALID_CONTACT_ID, userJid, false));
- if (pUser->bDeviceInit) {
- for (auto &jt : pUser->arDevices)
- pTask->arDest.insert(new WAJid(*jt));
- }
- else arCheckList.insert(mir_strdup(userJid));
- }
- }
- }
- }
- else {
- Wa__Message__DeviceSentMessage sentBody;
- sentBody.message = &body;
- sentBody.destinationjid = (char*)jid;
-
- Wa__Message msg;
- msg.devicesentmessage = &sentBody;
-
- pTask->content.append(proto::Serialize(&msg));
-
- if (auto *pUser = FindUser(jid)) {
- if (pUser->bDeviceInit) {
- for (auto &it : pUser->arDevices)
- pTask->arDest.insert(new WAJid(*it));
- }
- else arCheckList.insert(mir_strdup(jid));;
- }
- }
-
- padBuffer16(pTask->content);
-
- auto *pOwnUser = FindUser(m_szJid);
- for (auto &it : pOwnUser->arDevices)
- if (it->device != (int)getDword(DBKEY_DEVICE_ID))
- pTask->arDest.insert(new WAJid(*it));
-
- // generate & reserve packet id
- int pktId;
- {
- mir_cslock lck(m_csOwnMessages);
- pktId = m_iPacketId++;
- m_arOwnMsgs.insert(new WAOwnMessage(pktId, jid, pTask->szMsgId));
- }
-
- // if some keys are missing, schedule task for execution & retrieve keys
- if (arCheckList.getCount()) {
- SendUsync(arCheckList, pTask);
- for (auto &it : arCheckList)
- mir_free(it);
- }
- else // otherwise simply execute the task
- SendTask(pTask);
-
- return pktId;
-}
-
-void WhatsAppProto::FinishTask(WASendTask *pTask)
-{
- if (auto *pUser = FindUser(pTask->payLoad.getAttr("to"))) {
- if (pUser->bIsGroupChat) {
- for (auto &it : pUser->si->getUserList())
- if (auto *pChatUser = FindUser(T2Utf(it->pszUID)))
- for (auto &cc: pChatUser->arDevices)
- pTask->arDest.insert(new WAJid(*cc));
- }
- else for (auto &it : pUser->arDevices)
- pTask->arDest.insert(new WAJid(*it));
- }
-
- SendTask(pTask);
-}
-
-void WhatsAppProto::SendTask(WASendTask *pTask)
-{
- // pack all data and send the whole payload
- bool shouldIncludeIdentity = false;
- auto *pParticipants = pTask->payLoad.addChild("participants");
-
- for (auto &it : pTask->arDest)
- shouldIncludeIdentity |= CreateMsgParticipant(pParticipants, *it, pTask->content);
-
- if (shouldIncludeIdentity) {
- MBinBuffer encIdentity(m_signalStore.encodeSignedIdentity(true));
- auto *pNode = pTask->payLoad.addChild("device-identity");
- pNode->content.assign(encIdentity.data(), encIdentity.length());
- }
-
- WSSendNode(pTask->payLoad);
- delete pTask;
-}
+/*
+
+WhatsApp plugin for Miranda NG
+Copyright © 2019-23 George Hazan
+
+*/
+
+#include "stdafx.h"
+
+void WhatsAppProto::OnReceiveMessage(const WANode &node)
+{
+ auto *msgId = node.getAttr("id");
+ auto *msgType = node.getAttr("type");
+ auto *msgFrom = node.getAttr("from");
+ auto *category = node.getAttr("category");
+ auto *recipient = node.getAttr("recipient");
+ auto *participant = node.getAttr("participant");
+
+ if (msgType == nullptr || msgFrom == nullptr || msgId == nullptr) {
+ debugLogA("bad message received: <%s> <%s> <%s>", msgType, msgFrom, msgId);
+ return;
+ }
+
+ SendAck(node);
+
+ MEVENT hEvent = db_event_getById(m_szModuleName, msgId);
+ if (hEvent) {
+ debugLogA("this message is already processed: %s", msgId);
+ return;
+ }
+
+ WAMSG type;
+ WAJid jid(msgFrom);
+ CMStringA szAuthor, szChatId;
+
+ if (node.getAttr("offline"))
+ type.bOffline = true;
+
+ // message from one user to another
+ if (jid.isUser()) {
+ if (recipient) {
+ if (m_szJid != msgFrom) {
+ debugLogA("strange message: with recipient, but not from me");
+ return;
+ }
+ szChatId = recipient;
+ }
+ else szChatId = msgFrom;
+
+ type.bPrivateChat = true;
+ szAuthor = msgFrom;
+ }
+ else if (jid.isGroup()) {
+ if (!participant) {
+ debugLogA("strange message: from group, but without participant");
+ return;
+ }
+
+ type.bGroupChat = true;
+ szAuthor = participant;
+ szChatId = msgFrom;
+ }
+ else if (jid.isBroadcast()) {
+ if (!participant) {
+ debugLogA("strange message: from group, but without participant");
+ return;
+ }
+
+ bool bIsMe = m_szJid == participant;
+ if (jid.isStatusBroadcast()) {
+ if (bIsMe)
+ type.bDirectStatus = true;
+ else
+ type.bOtherStatus = true;
+ }
+ else {
+ if (bIsMe)
+ type.bPeerBroadcast = true;
+ else
+ type.bOtherBroadcast = true;
+ }
+ szChatId = msgFrom;
+ szAuthor = participant;
+ }
+ else {
+ debugLogA("invalid message type");
+ return;
+ }
+
+ CMStringA szSender = (type.bPrivateChat) ? szAuthor : szChatId;
+ bool bFromMe = (m_szJid == msgFrom);
+ if (!bFromMe && participant)
+ bFromMe = m_szJid == participant;
+
+ Wa__MessageKey key;
+ key.remotejid = szChatId.GetBuffer();
+ key.id = (char*)msgId;
+ key.fromme = bFromMe; key.has_fromme = true;
+ if (participant)
+ key.participant = (char*)participant;
+
+ Wa__WebMessageInfo msg;
+ msg.key = &key;
+ msg.messagetimestamp = _atoi64(node.getAttr("t")); msg.has_messagetimestamp = true;
+ msg.pushname = (char*)node.getAttr("notify");
+ if (bFromMe)
+ msg.status = WA__WEB_MESSAGE_INFO__STATUS__SERVER_ACK, msg.has_status = true;
+
+ int iDecryptable = 0;
+
+ for (auto &it : node.getChildren()) {
+ if (it->title != "enc" || it->content.length() == 0)
+ continue;
+
+ MBinBuffer msgBody;
+ auto *pszType = it->getAttr("type");
+ try {
+ if (!mir_strcmp(pszType, "pkmsg") || !mir_strcmp(pszType, "msg")) {
+ CMStringA szUser = (WAJid(szSender).isUser()) ? szSender : szAuthor;
+ msgBody = m_signalStore.decryptSignalProto(szUser, pszType, it->content);
+ }
+ else if (!mir_strcmp(pszType, "skmsg")) {
+ msgBody = m_signalStore.decryptGroupSignalProto(szSender, szAuthor, it->content);
+ }
+ else throw "Invalid e2e type";
+
+ if (msgBody.isEmpty())
+ throw "Invalid e2e message";
+
+ iDecryptable++;
+
+ proto::Message encMsg(unpadBuffer16(msgBody));
+ if (!encMsg)
+ throw "Invalid decoded message";
+
+ if (encMsg->devicesentmessage)
+ msg.message = encMsg->devicesentmessage->message;
+ else
+ msg.message = encMsg;
+
+ if (encMsg->senderkeydistributionmessage)
+ m_signalStore.processSenderKeyMessage(szAuthor, encMsg->senderkeydistributionmessage);
+
+ ProcessMessage(type, msg);
+
+ // send receipt
+ const char *pszReceiptType = nullptr, *pszReceiptTo = participant;
+ if (!mir_strcmp(category, "peer"))
+ pszReceiptType = "peer_msg";
+ else if (bFromMe) {
+ // message was sent by me from a different device
+ pszReceiptType = "sender";
+ if (WAJid(szChatId).isUser())
+ pszReceiptTo = szAuthor;
+ }
+ else if (!m_hServerConn)
+ pszReceiptType = "inactive";
+
+ SendReceipt(szChatId, pszReceiptTo, msgId, pszReceiptType);
+ }
+ catch (const char *pszError) {
+ debugLogA("Message decryption failed with error: %s", pszError);
+ }
+
+ if (!iDecryptable) {
+ debugLogA("Nothing to decrypt");
+ return;
+ }
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static const Wa__Message* getBody(const Wa__Message *message)
+{
+ if (message->ephemeralmessage) {
+ auto *pMsg = message->ephemeralmessage->message;
+ return (pMsg->viewoncemessage) ? pMsg->viewoncemessage->message : pMsg;
+ }
+
+ if (message->viewoncemessage)
+ return message->viewoncemessage->message;
+
+ return message;
+}
+
+void WhatsAppProto::ProcessMessage(WAMSG type, const Wa__WebMessageInfo &msg)
+{
+ auto *key = msg.key;
+ auto *body = getBody(msg.message);
+
+ debugLogA("Got a message: %s", protobuf_c_text_to_string(&msg).c_str());
+
+ uint32_t timestamp = msg.messagetimestamp;
+ char *participant = key->participant, *chatId;
+ auto *msgId = key->id;
+
+ if (type.bPrivateChat || type.bGroupChat)
+ chatId = key->remotejid;
+ else
+ chatId = (participant) ? participant : key->remotejid;
+
+ WAJid jidFrom(chatId); jidFrom.device = 0;
+ WAUser *pUser = AddUser(jidFrom.toString(), false);
+
+ if (!key->fromme && msg.pushname && pUser && !pUser->bIsGroupChat)
+ setUString(pUser->hContact, "Nick", msg.pushname);
+
+ // try to extract some text
+ if (pUser) {
+ CMStringA szMessageText(GetMessageText(body));
+ if (!szMessageText.IsEmpty()) {
+ // for chats & group chats store message in profile
+ if (type.bPrivateChat || type.bGroupChat) {
+ PROTORECVEVENT pre = {};
+ pre.timestamp = timestamp;
+ pre.szMessage = szMessageText.GetBuffer();
+ pre.szMsgId = msgId;
+ if (type.bOffline)
+ pre.flags |= PREF_CREATEREAD;
+ if (key->fromme)
+ pre.flags |= PREF_SENT;
+ ProtoChainRecvMsg(pUser->hContact, &pre);
+
+ if (pUser->bIsGroupChat) {
+ GCEVENT gce = {m_szModuleName, 0, GC_EVENT_MESSAGE};
+ gce.dwFlags = GCEF_UTF8;
+ gce.pszID.a = pUser->szId;
+ gce.pszUID.a = participant;
+ gce.bIsMe = key->fromme;
+ gce.pszText.a = szMessageText.GetBuffer();
+ gce.time = timestamp;
+ Chat_Event(&gce);
+ }
+ }
+ // translate statuses into status messages
+ else if (type.bOtherStatus || type.bDirectStatus || type.bPeerBroadcast || type.bOtherBroadcast) {
+ setUString(pUser->hContact, "StatusMsg", szMessageText);
+ }
+ }
+ }
+
+ if (body->protocolmessage) {
+ auto *protoMsg = body->protocolmessage;
+ switch (protoMsg->type) {
+ case WA__MESSAGE__PROTOCOL_MESSAGE__TYPE__APP_STATE_SYNC_KEY_SHARE:
+ for (int i = 0; i < protoMsg->appstatesynckeyshare->n_keys; i++) {
+ auto *it = protoMsg->appstatesynckeyshare->keys[i];
+ auto &keyid = it->keyid->keyid;
+ auto &keydata = it->keydata->keydata;
+
+ CMStringA szSetting(FORMAT, "AppSyncKey%d", decodeBigEndian(keyid));
+ db_set_blob(0, m_szModuleName, szSetting, keydata.data, (unsigned)keydata.len);
+ }
+ break;
+
+ case WA__MESSAGE__PROTOCOL_MESSAGE__TYPE__APP_STATE_FATAL_EXCEPTION_NOTIFICATION:
+ m_impl.m_resyncApp.Stop();
+ m_impl.m_resyncApp.Start(10000);
+ break;
+
+ case WA__MESSAGE__PROTOCOL_MESSAGE__TYPE__HISTORY_SYNC_NOTIFICATION:
+ debugLogA("History sync notification");
+ if (auto *pHist = protoMsg->historysyncnotification) {
+ MBinBuffer buf(DownloadEncryptedFile(directPath2url(pHist->directpath), pHist->mediakey, "History"));
+ if (!buf.isEmpty()) {
+ MBinBuffer inflate(unzip(unpadBuffer16(buf)));
+
+ proto::HistorySync sync(inflate);
+ if (sync)
+ ProcessHistorySync(sync);
+ }
+ }
+ break;
+
+ case WA__MESSAGE__PROTOCOL_MESSAGE__TYPE__REVOKE:
+ break;
+
+ case WA__MESSAGE__PROTOCOL_MESSAGE__TYPE__EPHEMERAL_SETTING:
+ if (pUser) {
+ setDword(pUser->hContact, DBKEY_EPHEMERAL_TS, timestamp);
+ setDword(pUser->hContact, DBKEY_EPHEMERAL_EXPIRE, protoMsg->ephemeralexpiration);
+ }
+ break;
+ }
+ }
+ else if (body->reactionmessage) {
+ debugLogA("Got a reaction to a message");
+ }
+ else if (msg.has_messagestubtype) {
+ switch (msg.messagestubtype) {
+ case WA__WEB_MESSAGE_INFO__STUB_TYPE__GROUP_PARTICIPANT_LEAVE:
+ case WA__WEB_MESSAGE_INFO__STUB_TYPE__GROUP_PARTICIPANT_REMOVE:
+ debugLogA("Participant %s removed from chat", participant);
+ break;
+
+ case WA__WEB_MESSAGE_INFO__STUB_TYPE__GROUP_PARTICIPANT_ADD:
+ case WA__WEB_MESSAGE_INFO__STUB_TYPE__GROUP_PARTICIPANT_INVITE:
+ case WA__WEB_MESSAGE_INFO__STUB_TYPE__GROUP_PARTICIPANT_ADD_REQUEST_JOIN:
+ debugLogA("Participant %s added to chat", participant);
+ break;
+
+ case WA__WEB_MESSAGE_INFO__STUB_TYPE__GROUP_PARTICIPANT_DEMOTE:
+ debugLogA("Participant %s demoted", participant);
+ break;
+
+ case WA__WEB_MESSAGE_INFO__STUB_TYPE__GROUP_PARTICIPANT_PROMOTE:
+ debugLogA("Participant %s promoted", participant);
+ break;
+
+ case WA__WEB_MESSAGE_INFO__STUB_TYPE__GROUP_CHANGE_ANNOUNCE:
+ debugLogA("Groupchat announce", participant);
+ break;
+
+ case WA__WEB_MESSAGE_INFO__STUB_TYPE__GROUP_CHANGE_RESTRICT:
+ debugLogA("Groupchat restriction", participant);
+ break;
+
+ case WA__WEB_MESSAGE_INFO__STUB_TYPE__GROUP_CHANGE_SUBJECT:
+ debugLogA("Groupchat subject was changed", participant);
+ break;
+ }
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void WhatsAppProto::OnReceiveAck(const WANode &node)
+{
+ auto *pUser = FindUser(node.getAttr("from"));
+ if (pUser == nullptr)
+ return;
+
+ if (!mir_strcmp(node.getAttr("class"), "message")) {
+ WAOwnMessage tmp(0, 0, node.getAttr("id"));
+ {
+ mir_cslock lck(m_csOwnMessages);
+ if (auto *pOwn = m_arOwnMsgs.find(&tmp)) {
+ tmp.pktId = pOwn->pktId;
+ m_arOwnMsgs.remove(pOwn);
+ }
+ else return;
+ }
+ ProtoBroadcastAck(pUser->hContact, ACKTYPE_MESSAGE, ACKRESULT_SUCCESS, (HANDLE)tmp.pktId, (LPARAM)tmp.szMessageId.c_str());
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+bool WhatsAppProto::CreateMsgParticipant(WANode *pParticipants, const WAJid &jid, const MBinBuffer &orig)
+{
+ int type = 0;
+
+ try {
+ MBinBuffer pBuffer(m_signalStore.encryptSignalProto(jid, orig, type));
+
+ auto *pNode = pParticipants->addChild("to");
+ pNode->addAttr("jid", jid.toString());
+
+ auto *pEnc = pNode->addChild("enc");
+ *pEnc << CHAR_PARAM("v", "2") << CHAR_PARAM("type", (type == 3) ? "pkmsg" : "msg");
+ pEnc->content.assign(pBuffer.data(), pBuffer.length());
+ }
+ catch (const char *) {
+ }
+
+ return type == 3;
+}
+
+int WhatsAppProto::SendTextMessage(const char *jid, const char *pszMsg)
+{
+ WAJid toJid(jid);
+
+ // send task creation
+ auto *pTask = new WASendTask(jid);
+
+ // basic message
+ Wa__Message__ExtendedTextMessage extMessage;
+ extMessage.text = (char *)pszMsg;
+
+ Wa__Message body;
+ body.extendedtextmessage = &extMessage;
+
+ LIST<char> arCheckList(1);
+ if (toJid.isGroup()) {
+ MBinBuffer encodedMsg(proto::Serialize(&body));
+ padBuffer16(encodedMsg);
+
+ MBinBuffer skmsgKey;
+ MBinBuffer cipherText(m_signalStore.encryptSenderKey(toJid, m_szJid, encodedMsg, skmsgKey));
+
+ auto *pEnc = pTask->payLoad.addChild("enc");
+ *pEnc << CHAR_PARAM("v", "2") << CHAR_PARAM("type", "skmsg");
+ pEnc->content.append(cipherText.data(), cipherText.length());
+
+ Wa__Message__SenderKeyDistributionMessage sentBody;
+ sentBody.axolotlsenderkeydistributionmessage.data = skmsgKey.data();
+ sentBody.axolotlsenderkeydistributionmessage.len = skmsgKey.length();
+ sentBody.has_axolotlsenderkeydistributionmessage = true;
+ sentBody.groupid = (char*)jid;
+
+ Wa__Message msg;
+ msg.senderkeydistributionmessage = &sentBody;
+
+ pTask->content.append(proto::Serialize(&msg));
+
+ if (auto *pChatUser = FindUser(jid)) {
+ if (pChatUser->si) {
+ for (auto &it : pChatUser->si->arUsers) {
+ T2Utf userJid(it->pszUID);
+ auto *pUser = FindUser(jid);
+ if (pUser == nullptr)
+ m_arUsers.insert(pUser = new WAUser(INVALID_CONTACT_ID, userJid, false));
+ if (pUser->bDeviceInit) {
+ for (auto &jt : pUser->arDevices)
+ pTask->arDest.insert(new WAJid(*jt));
+ }
+ else arCheckList.insert(mir_strdup(userJid));
+ }
+ }
+ }
+ }
+ else {
+ Wa__Message__DeviceSentMessage sentBody;
+ sentBody.message = &body;
+ sentBody.destinationjid = (char*)jid;
+
+ Wa__Message msg;
+ msg.devicesentmessage = &sentBody;
+
+ pTask->content.append(proto::Serialize(&msg));
+
+ if (auto *pUser = FindUser(jid)) {
+ if (pUser->bDeviceInit) {
+ for (auto &it : pUser->arDevices)
+ pTask->arDest.insert(new WAJid(*it));
+ }
+ else arCheckList.insert(mir_strdup(jid));;
+ }
+ }
+
+ padBuffer16(pTask->content);
+
+ auto *pOwnUser = FindUser(m_szJid);
+ for (auto &it : pOwnUser->arDevices)
+ if (it->device != (int)getDword(DBKEY_DEVICE_ID))
+ pTask->arDest.insert(new WAJid(*it));
+
+ // generate & reserve packet id
+ int pktId;
+ {
+ mir_cslock lck(m_csOwnMessages);
+ pktId = m_iPacketId++;
+ m_arOwnMsgs.insert(new WAOwnMessage(pktId, jid, pTask->szMsgId));
+ }
+
+ // if some keys are missing, schedule task for execution & retrieve keys
+ if (arCheckList.getCount()) {
+ SendUsync(arCheckList, pTask);
+ for (auto &it : arCheckList)
+ mir_free(it);
+ }
+ else // otherwise simply execute the task
+ SendTask(pTask);
+
+ return pktId;
+}
+
+void WhatsAppProto::FinishTask(WASendTask *pTask)
+{
+ if (auto *pUser = FindUser(pTask->payLoad.getAttr("to"))) {
+ if (pUser->bIsGroupChat) {
+ for (auto &it : pUser->si->getUserList())
+ if (auto *pChatUser = FindUser(T2Utf(it->pszUID)))
+ for (auto &cc: pChatUser->arDevices)
+ pTask->arDest.insert(new WAJid(*cc));
+ }
+ else for (auto &it : pUser->arDevices)
+ pTask->arDest.insert(new WAJid(*it));
+ }
+
+ SendTask(pTask);
+}
+
+void WhatsAppProto::SendTask(WASendTask *pTask)
+{
+ // pack all data and send the whole payload
+ bool shouldIncludeIdentity = false;
+ auto *pParticipants = pTask->payLoad.addChild("participants");
+
+ for (auto &it : pTask->arDest)
+ shouldIncludeIdentity |= CreateMsgParticipant(pParticipants, *it, pTask->content);
+
+ if (shouldIncludeIdentity) {
+ MBinBuffer encIdentity(m_signalStore.encodeSignedIdentity(true));
+ auto *pNode = pTask->payLoad.addChild("device-identity");
+ pNode->content.assign(encIdentity.data(), encIdentity.length());
+ }
+
+ WSSendNode(pTask->payLoad);
+ delete pTask;
+}
diff --git a/protocols/WhatsApp/src/noise.cpp b/protocols/WhatsApp/src/noise.cpp
index 004b71cdef..18ae206519 100644
--- a/protocols/WhatsApp/src/noise.cpp
+++ b/protocols/WhatsApp/src/noise.cpp
@@ -1,201 +1,201 @@
-/*
-
-WhatsApp plugin for Miranda NG
-Copyright © 2019-22 George Hazan
-
-WANoise class implementation
-
-*/
-
-#include "stdafx.h"
-
-static uint8_t intro_header[] = {87, 65, 6, DICT_VERSION};
-static uint8_t noise_init[] = "Noise_XX_25519_AESGCM_SHA256\0\0\0\0";
-
-WANoise::WANoise(WhatsAppProto *_ppro) :
- ppro(_ppro)
-{
- salt.assign(noise_init, 32);
- encKey.assign(noise_init, 32);
- decKey.assign(noise_init, 32);
-
- // generate ephemeral keys: public & private
- ec_key_pair *pKeys;
- curve_generate_key_pair(ppro->m_signalStore.CTX(), &pKeys);
-
- auto *pPubKey = ec_key_pair_get_public(pKeys);
- ephemeral.pub.assign(pPubKey->data, sizeof(pPubKey->data));
-
- auto *pPrivKey = ec_key_pair_get_private(pKeys);
- ephemeral.priv.assign(pPrivKey->data, sizeof(pPrivKey->data));
- ec_key_pair_destroy((signal_type_base*)pKeys);
-
- // prepare hash
- memcpy(hash, noise_init, 32);
- updateHash(intro_header, 4);
- updateHash(ephemeral.pub.data(), ephemeral.pub.length());
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-// libsignal data initialization
-
-void WANoise::init()
-{
- // no data? generate them
- if (ppro->getDword(DBKEY_REG_ID, 0xFFFF) == 0xFFFF) {
- // generate registration id
- uint32_t regId;
- Utils_GetRandom(&regId, sizeof(regId));
- ppro->setDword(DBKEY_REG_ID, regId & 0x3FFF);
-
- // generate secret key
- uint8_t secretKey[32];
- Utils_GetRandom(secretKey, sizeof(secretKey));
- db_set_blob(0, ppro->m_szModuleName, DBKEY_SECRET_KEY, secretKey, sizeof(secretKey));
-
- // generate noise keys (private & public)
- ec_key_pair *pKeys;
- curve_generate_key_pair(ppro->m_signalStore.CTX(), &pKeys);
-
- auto *pPubKey = ec_key_pair_get_public(pKeys);
- db_set_blob(0, ppro->m_szModuleName, DBKEY_NOISE_PUB, pPubKey->data, sizeof(pPubKey->data));
-
- auto *pPrivKey = ec_key_pair_get_private(pKeys);
- db_set_blob(0, ppro->m_szModuleName, DBKEY_NOISE_PRIV, pPrivKey->data, sizeof(pPrivKey->data));
- ec_key_pair_destroy((signal_type_base *)pKeys);
- }
-
- noiseKeys.pub = ppro->getBlob(DBKEY_NOISE_PUB);
- noiseKeys.priv = ppro->getBlob(DBKEY_NOISE_PRIV);
-}
-
-void WANoise::finish()
-{
- deriveKey("", 0, encKey, decKey);
- readCounter = writeCounter = 0;
- memset(hash, 0, sizeof(hash));
- bInitFinished = true;
-}
-
-void WANoise::deriveKey(const void *pData, size_t cbLen, MBinBuffer &write, MBinBuffer &read)
-{
- size_t outlen = 64;
- uint8_t out[64];
- HKDF(EVP_sha256(), salt.data(), (int)salt.length(), (BYTE *)pData, (int)cbLen, (BYTE *)"", 0, out, outlen);
-
- write.assign(out, 32);
- read.assign(out + 32, 32);
-}
-
-void WANoise::mixIntoKey(const void *n, const void *p)
-{
- uint8_t tmp[32];
- curve25519_donna((unsigned char *)tmp, (const unsigned char *)n, (const unsigned char *)p);
-
- deriveKey(tmp, sizeof(tmp), salt, encKey);
- decKey.assign(encKey.data(), encKey.length());
- readCounter = writeCounter = 0;
-}
-
-MBinBuffer WANoise::decrypt(const void *pData, size_t cbLen)
-{
- uint8_t iv[12];
- generateIV(iv, (bInitFinished) ? readCounter : writeCounter);
-
- MBinBuffer res;
- if (!bInitFinished)
- res = aesDecrypt(EVP_aes_256_gcm(), decKey.data(), iv, pData, cbLen, hash, sizeof(hash));
- else
- res = aesDecrypt(EVP_aes_256_gcm(), decKey.data(), iv, pData, cbLen);
-
- updateHash(pData, cbLen);
- return res;
-}
-
-size_t WANoise::decodeFrame(const uint8_t *&p, size_t &cbLen)
-{
- if (cbLen < 3)
- return 0;
-
- size_t payloadLen = 0;
- for (int i = 0; i < 3; i++) {
- payloadLen <<= 8;
- payloadLen += p[i];
- }
-
- // ppro->debugLogA("got payload of size %d", payloadLen);
-
- cbLen -= 3;
- if (payloadLen > cbLen) {
- ppro->debugLogA("payload length %d exceeds capacity %d", payloadLen, cbLen);
- return 0;
- }
-
- p += 3;
- return payloadLen;
-}
-
-MBinBuffer WANoise::encodeFrame(const void *pData, size_t cbLen)
-{
- MBinBuffer res;
- if (!bSendIntro) {
- bSendIntro = true;
- res.append(intro_header, 4);
- }
-
- uint8_t buf[3];
- size_t foo = cbLen;
- for (int i = 0; i < 3; i++) {
- buf[2 - i] = foo & 0xFF;
- foo >>= 8;
- }
- res.append(buf, 3);
- res.append(pData, cbLen);
- return res;
-}
-
-MBinBuffer WANoise::encrypt(const void *pData, size_t cbLen)
-{
- uint8_t iv[12];
- generateIV(iv, writeCounter);
-
- MBinBuffer res;
- uint8_t outbuf[1024 + 64];
-
- int enc_len = 0, final_len = 0;
- EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
- EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, encKey.data(), iv);
-
- if (!bInitFinished)
- EVP_EncryptUpdate(ctx, NULL, &enc_len, hash, sizeof(hash));
-
- for (size_t len = 0; len < cbLen; len += 1024) {
- size_t portionSize = cbLen - len;
- EVP_EncryptUpdate(ctx, outbuf, &enc_len, (BYTE *)pData + len, (int)min(portionSize, 1024));
- res.append(outbuf, enc_len);
- }
- EVP_EncryptFinal_ex(ctx, outbuf, &final_len);
- if (final_len)
- res.append(outbuf, final_len);
-
- uint8_t tag[16];
- EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, sizeof(tag), tag);
- res.append(tag, sizeof(tag));
-
- EVP_CIPHER_CTX_free(ctx);
-
- updateHash(res.data(), res.length());
- return res;
-}
-
-void WANoise::updateHash(const void *pData, size_t cbLen)
-{
- if (bInitFinished)
- return;
-
- SHA256_CTX ctx;
- SHA256_Init(&ctx);
- SHA256_Update(&ctx, hash, sizeof(hash));
- SHA256_Update(&ctx, pData, cbLen);
- SHA256_Final(hash, &ctx);
-}
+/*
+
+WhatsApp plugin for Miranda NG
+Copyright © 2019-23 George Hazan
+
+WANoise class implementation
+
+*/
+
+#include "stdafx.h"
+
+static uint8_t intro_header[] = {87, 65, 6, DICT_VERSION};
+static uint8_t noise_init[] = "Noise_XX_25519_AESGCM_SHA256\0\0\0\0";
+
+WANoise::WANoise(WhatsAppProto *_ppro) :
+ ppro(_ppro)
+{
+ salt.assign(noise_init, 32);
+ encKey.assign(noise_init, 32);
+ decKey.assign(noise_init, 32);
+
+ // generate ephemeral keys: public & private
+ ec_key_pair *pKeys;
+ curve_generate_key_pair(ppro->m_signalStore.CTX(), &pKeys);
+
+ auto *pPubKey = ec_key_pair_get_public(pKeys);
+ ephemeral.pub.assign(pPubKey->data, sizeof(pPubKey->data));
+
+ auto *pPrivKey = ec_key_pair_get_private(pKeys);
+ ephemeral.priv.assign(pPrivKey->data, sizeof(pPrivKey->data));
+ ec_key_pair_destroy((signal_type_base*)pKeys);
+
+ // prepare hash
+ memcpy(hash, noise_init, 32);
+ updateHash(intro_header, 4);
+ updateHash(ephemeral.pub.data(), ephemeral.pub.length());
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// libsignal data initialization
+
+void WANoise::init()
+{
+ // no data? generate them
+ if (ppro->getDword(DBKEY_REG_ID, 0xFFFF) == 0xFFFF) {
+ // generate registration id
+ uint32_t regId;
+ Utils_GetRandom(&regId, sizeof(regId));
+ ppro->setDword(DBKEY_REG_ID, regId & 0x3FFF);
+
+ // generate secret key
+ uint8_t secretKey[32];
+ Utils_GetRandom(secretKey, sizeof(secretKey));
+ db_set_blob(0, ppro->m_szModuleName, DBKEY_SECRET_KEY, secretKey, sizeof(secretKey));
+
+ // generate noise keys (private & public)
+ ec_key_pair *pKeys;
+ curve_generate_key_pair(ppro->m_signalStore.CTX(), &pKeys);
+
+ auto *pPubKey = ec_key_pair_get_public(pKeys);
+ db_set_blob(0, ppro->m_szModuleName, DBKEY_NOISE_PUB, pPubKey->data, sizeof(pPubKey->data));
+
+ auto *pPrivKey = ec_key_pair_get_private(pKeys);
+ db_set_blob(0, ppro->m_szModuleName, DBKEY_NOISE_PRIV, pPrivKey->data, sizeof(pPrivKey->data));
+ ec_key_pair_destroy((signal_type_base *)pKeys);
+ }
+
+ noiseKeys.pub = ppro->getBlob(DBKEY_NOISE_PUB);
+ noiseKeys.priv = ppro->getBlob(DBKEY_NOISE_PRIV);
+}
+
+void WANoise::finish()
+{
+ deriveKey("", 0, encKey, decKey);
+ readCounter = writeCounter = 0;
+ memset(hash, 0, sizeof(hash));
+ bInitFinished = true;
+}
+
+void WANoise::deriveKey(const void *pData, size_t cbLen, MBinBuffer &write, MBinBuffer &read)
+{
+ size_t outlen = 64;
+ uint8_t out[64];
+ HKDF(EVP_sha256(), salt.data(), (int)salt.length(), (BYTE *)pData, (int)cbLen, (BYTE *)"", 0, out, outlen);
+
+ write.assign(out, 32);
+ read.assign(out + 32, 32);
+}
+
+void WANoise::mixIntoKey(const void *n, const void *p)
+{
+ uint8_t tmp[32];
+ curve25519_donna((unsigned char *)tmp, (const unsigned char *)n, (const unsigned char *)p);
+
+ deriveKey(tmp, sizeof(tmp), salt, encKey);
+ decKey.assign(encKey.data(), encKey.length());
+ readCounter = writeCounter = 0;
+}
+
+MBinBuffer WANoise::decrypt(const void *pData, size_t cbLen)
+{
+ uint8_t iv[12];
+ generateIV(iv, (bInitFinished) ? readCounter : writeCounter);
+
+ MBinBuffer res;
+ if (!bInitFinished)
+ res = aesDecrypt(EVP_aes_256_gcm(), decKey.data(), iv, pData, cbLen, hash, sizeof(hash));
+ else
+ res = aesDecrypt(EVP_aes_256_gcm(), decKey.data(), iv, pData, cbLen);
+
+ updateHash(pData, cbLen);
+ return res;
+}
+
+size_t WANoise::decodeFrame(const uint8_t *&p, size_t &cbLen)
+{
+ if (cbLen < 3)
+ return 0;
+
+ size_t payloadLen = 0;
+ for (int i = 0; i < 3; i++) {
+ payloadLen <<= 8;
+ payloadLen += p[i];
+ }
+
+ // ppro->debugLogA("got payload of size %d", payloadLen);
+
+ cbLen -= 3;
+ if (payloadLen > cbLen) {
+ ppro->debugLogA("payload length %d exceeds capacity %d", payloadLen, cbLen);
+ return 0;
+ }
+
+ p += 3;
+ return payloadLen;
+}
+
+MBinBuffer WANoise::encodeFrame(const void *pData, size_t cbLen)
+{
+ MBinBuffer res;
+ if (!bSendIntro) {
+ bSendIntro = true;
+ res.append(intro_header, 4);
+ }
+
+ uint8_t buf[3];
+ size_t foo = cbLen;
+ for (int i = 0; i < 3; i++) {
+ buf[2 - i] = foo & 0xFF;
+ foo >>= 8;
+ }
+ res.append(buf, 3);
+ res.append(pData, cbLen);
+ return res;
+}
+
+MBinBuffer WANoise::encrypt(const void *pData, size_t cbLen)
+{
+ uint8_t iv[12];
+ generateIV(iv, writeCounter);
+
+ MBinBuffer res;
+ uint8_t outbuf[1024 + 64];
+
+ int enc_len = 0, final_len = 0;
+ EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
+ EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, encKey.data(), iv);
+
+ if (!bInitFinished)
+ EVP_EncryptUpdate(ctx, NULL, &enc_len, hash, sizeof(hash));
+
+ for (size_t len = 0; len < cbLen; len += 1024) {
+ size_t portionSize = cbLen - len;
+ EVP_EncryptUpdate(ctx, outbuf, &enc_len, (BYTE *)pData + len, (int)min(portionSize, 1024));
+ res.append(outbuf, enc_len);
+ }
+ EVP_EncryptFinal_ex(ctx, outbuf, &final_len);
+ if (final_len)
+ res.append(outbuf, final_len);
+
+ uint8_t tag[16];
+ EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, sizeof(tag), tag);
+ res.append(tag, sizeof(tag));
+
+ EVP_CIPHER_CTX_free(ctx);
+
+ updateHash(res.data(), res.length());
+ return res;
+}
+
+void WANoise::updateHash(const void *pData, size_t cbLen)
+{
+ if (bInitFinished)
+ return;
+
+ SHA256_CTX ctx;
+ SHA256_Init(&ctx);
+ SHA256_Update(&ctx, hash, sizeof(hash));
+ SHA256_Update(&ctx, pData, cbLen);
+ SHA256_Final(hash, &ctx);
+}
diff --git a/protocols/WhatsApp/src/options.cpp b/protocols/WhatsApp/src/options.cpp
index 3df8e69343..9945da04e5 100644
--- a/protocols/WhatsApp/src/options.cpp
+++ b/protocols/WhatsApp/src/options.cpp
@@ -1,95 +1,95 @@
-/*
-
-WhatsApp plugin for Miranda NG
-Copyright © 2019-22 George Hazan
-
-*/
-
-#include "stdafx.h"
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-class COptionsDlg : public CProtoDlgBase<WhatsAppProto>
-{
- CCtrlCheck chkHideChats, chkBbcodes;
- CCtrlEdit edtGroup, edtNick, edtDevName;
- CCtrlButton btnUnregister;
- ptrW m_wszOldGroup;
-
-public:
- COptionsDlg(WhatsAppProto *ppro, int iDlgID, bool bFullDlg) :
- CProtoDlgBase<WhatsAppProto>(ppro, iDlgID),
- chkBbcodes(this, IDC_USEBBCODES),
- chkHideChats(this, IDC_HIDECHATS),
- edtNick(this, IDC_NICK),
- edtGroup(this, IDC_DEFGROUP),
- edtDevName(this, IDC_DEVICE_NAME),
- btnUnregister(this, IDC_UNREGISTER),
- m_wszOldGroup(mir_wstrdup(ppro->m_wszDefaultGroup))
- {
- CreateLink(edtNick, ppro->m_wszNick);
- CreateLink(edtGroup, ppro->m_wszDefaultGroup);
- CreateLink(edtDevName, ppro->m_wszDeviceName);
-
- if (bFullDlg) {
- CreateLink(chkHideChats, ppro->m_bHideGroupchats);
- CreateLink(chkBbcodes, ppro->m_bUseBbcodes);
- }
-
- btnUnregister.OnClick = Callback(this, &COptionsDlg::onClick_Unregister);
- }
-
- bool OnInitDialog() override
- {
- if (!m_proto->getMStringA(DBKEY_ID).IsEmpty())
- edtDevName.Disable();
- return true;
- }
-
- bool OnApply() override
- {
- if (mir_wstrlen(m_proto->m_wszNick)) {
- SetFocus(edtNick.GetHwnd());
- return false;
- }
-
- if (mir_wstrcmp(m_proto->m_wszDefaultGroup, m_wszOldGroup))
- Clist_GroupCreate(0, m_proto->m_wszDefaultGroup);
- return true;
- }
-
- void onClick_Unregister(CCtrlButton *)
- {
- if (IDYES != MessageBoxW(0, TranslateT("Do you really want to unregister Miranda?"), m_proto->m_tszUserName, MB_ICONQUESTION | MB_YESNO))
- return;
-
- if (m_proto->isOnline())
- m_proto->SendUnregister();
- else
- m_proto->OnErase();
- }
-};
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-INT_PTR WhatsAppProto::SvcCreateAccMgrUI(WPARAM, LPARAM hwndParent)
-{
- auto *pDlg = new COptionsDlg(this, IDD_ACCMGRUI, false);
- pDlg->SetParent((HWND)hwndParent);
- pDlg->Create();
- return (INT_PTR)pDlg->GetHwnd();
-}
-
-int WhatsAppProto::OnOptionsInit(WPARAM wParam, LPARAM)
-{
- OPTIONSDIALOGPAGE odp = {};
- odp.szTitle.w = m_tszUserName;
- odp.flags = ODPF_UNICODE;
- odp.szGroup.w = LPGENW("Network");
-
- odp.position = 1;
- odp.szTab.w = LPGENW("Account");
- odp.pDialog = new COptionsDlg(this, IDD_OPTIONS, true);
- g_plugin.addOptions(wParam, &odp);
- return 0;
-}
+/*
+
+WhatsApp plugin for Miranda NG
+Copyright © 2019-23 George Hazan
+
+*/
+
+#include "stdafx.h"
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+class COptionsDlg : public CProtoDlgBase<WhatsAppProto>
+{
+ CCtrlCheck chkHideChats, chkBbcodes;
+ CCtrlEdit edtGroup, edtNick, edtDevName;
+ CCtrlButton btnUnregister;
+ ptrW m_wszOldGroup;
+
+public:
+ COptionsDlg(WhatsAppProto *ppro, int iDlgID, bool bFullDlg) :
+ CProtoDlgBase<WhatsAppProto>(ppro, iDlgID),
+ chkBbcodes(this, IDC_USEBBCODES),
+ chkHideChats(this, IDC_HIDECHATS),
+ edtNick(this, IDC_NICK),
+ edtGroup(this, IDC_DEFGROUP),
+ edtDevName(this, IDC_DEVICE_NAME),
+ btnUnregister(this, IDC_UNREGISTER),
+ m_wszOldGroup(mir_wstrdup(ppro->m_wszDefaultGroup))
+ {
+ CreateLink(edtNick, ppro->m_wszNick);
+ CreateLink(edtGroup, ppro->m_wszDefaultGroup);
+ CreateLink(edtDevName, ppro->m_wszDeviceName);
+
+ if (bFullDlg) {
+ CreateLink(chkHideChats, ppro->m_bHideGroupchats);
+ CreateLink(chkBbcodes, ppro->m_bUseBbcodes);
+ }
+
+ btnUnregister.OnClick = Callback(this, &COptionsDlg::onClick_Unregister);
+ }
+
+ bool OnInitDialog() override
+ {
+ if (!m_proto->getMStringA(DBKEY_ID).IsEmpty())
+ edtDevName.Disable();
+ return true;
+ }
+
+ bool OnApply() override
+ {
+ if (mir_wstrlen(m_proto->m_wszNick)) {
+ SetFocus(edtNick.GetHwnd());
+ return false;
+ }
+
+ if (mir_wstrcmp(m_proto->m_wszDefaultGroup, m_wszOldGroup))
+ Clist_GroupCreate(0, m_proto->m_wszDefaultGroup);
+ return true;
+ }
+
+ void onClick_Unregister(CCtrlButton *)
+ {
+ if (IDYES != MessageBoxW(0, TranslateT("Do you really want to unregister Miranda?"), m_proto->m_tszUserName, MB_ICONQUESTION | MB_YESNO))
+ return;
+
+ if (m_proto->isOnline())
+ m_proto->SendUnregister();
+ else
+ m_proto->OnErase();
+ }
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+INT_PTR WhatsAppProto::SvcCreateAccMgrUI(WPARAM, LPARAM hwndParent)
+{
+ auto *pDlg = new COptionsDlg(this, IDD_ACCMGRUI, false);
+ pDlg->SetParent((HWND)hwndParent);
+ pDlg->Create();
+ return (INT_PTR)pDlg->GetHwnd();
+}
+
+int WhatsAppProto::OnOptionsInit(WPARAM wParam, LPARAM)
+{
+ OPTIONSDIALOGPAGE odp = {};
+ odp.szTitle.w = m_tszUserName;
+ odp.flags = ODPF_UNICODE;
+ odp.szGroup.w = LPGENW("Network");
+
+ odp.position = 1;
+ odp.szTab.w = LPGENW("Account");
+ odp.pDialog = new COptionsDlg(this, IDD_OPTIONS, true);
+ g_plugin.addOptions(wParam, &odp);
+ return 0;
+}
diff --git a/protocols/WhatsApp/src/proto.cpp b/protocols/WhatsApp/src/proto.cpp
index f97aa989e8..006f78dfc0 100644
--- a/protocols/WhatsApp/src/proto.cpp
+++ b/protocols/WhatsApp/src/proto.cpp
@@ -1,7 +1,7 @@
/*
WhatsApp plugin for Miranda NG
-Copyright © 2019-22 George Hazan
+Copyright © 2019-23 George Hazan
*/
diff --git a/protocols/WhatsApp/src/proto.h b/protocols/WhatsApp/src/proto.h
index e6088cd4b8..dbcf57b597 100644
--- a/protocols/WhatsApp/src/proto.h
+++ b/protocols/WhatsApp/src/proto.h
@@ -1,7 +1,7 @@
/*
WhatsApp plugin for Miranda NG
-Copyright © 2019-22 George Hazan
+Copyright © 2019-23 George Hazan
*/
diff --git a/protocols/WhatsApp/src/qrcode.cpp b/protocols/WhatsApp/src/qrcode.cpp
index 18edfc3e91..22c9e1da95 100644
--- a/protocols/WhatsApp/src/qrcode.cpp
+++ b/protocols/WhatsApp/src/qrcode.cpp
@@ -1,137 +1,137 @@
-/*
-
-WhatsApp plugin for Miranda NG
-Copyright © 2019-22 George Hazan
-
-*/
-
-#include "stdafx.h"
-
-class CWhatsAppQRDlg : public CProtoDlgBase<WhatsAppProto>
-{
-public:
- CWhatsAppQRDlg(WhatsAppProto *ppro) :
- CProtoDlgBase<WhatsAppProto>(ppro, IDD_SHOWQR)
- {}
-
- void OnDestroy() override
- {
- m_proto->m_pQRDlg = nullptr;
-
- if (!m_bSucceeded)
- m_proto->ShutdownSession();
- }
-
- void SetSuccess()
- {
- m_bSucceeded = true;
- }
-
- void SetData(const CMStringA &str)
- {
- auto *pQR = QRcode_encodeString(str, 0, QR_ECLEVEL_L, QR_MODE_8, 1);
-
- HWND hwndRc = GetDlgItem(m_hwnd, IDC_QRPIC);
- RECT rc;
- GetClientRect(hwndRc, &rc);
-
- ::SetForegroundWindow(m_hwnd);
-
- int scale = 8; // (rc.bottom - rc.top) / pQR->width;
- int rowLen = pQR->width * scale * 3;
- if (rowLen % 4)
- rowLen = (rowLen / 4 + 1) * 4;
- int dataLen = rowLen * pQR->width * scale;
-
- mir_ptr<BYTE> pData((BYTE *)mir_alloc(dataLen));
- if (pData == nullptr) {
- QRcode_free(pQR);
- return;
- }
-
- memset(pData, 0xFF, dataLen); // white background by default
-
- const BYTE *s = pQR->data;
- for (int y = 0; y < pQR->width; y++) {
- BYTE *d = pData.get() + rowLen * y * scale;
- for (int x = 0; x < pQR->width; x++) {
- if (*s & 1)
- for (int i = 0; i < scale; i++)
- for (int j = 0; j < scale; j++) {
- d[j * 3 + i * rowLen] = 0;
- d[1 + j * 3 + i * rowLen] = 0;
- d[2 + j * 3 + i * rowLen] = 0;
- }
-
- d += scale * 3;
- s++;
- }
- }
-
- BITMAPFILEHEADER fih = {};
- fih.bfType = 0x4d42; // "BM"
- fih.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + dataLen;
- fih.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
-
- BITMAPINFOHEADER bih = {};
- bih.biSize = sizeof(BITMAPINFOHEADER);
- bih.biWidth = pQR->width * scale;
- bih.biHeight = -bih.biWidth;
- bih.biPlanes = 1;
- bih.biBitCount = 24;
- bih.biCompression = BI_RGB;
-
- wchar_t wszTempPath[MAX_PATH], wszTempFile[MAX_PATH];
- GetTempPathW(_countof(wszTempPath), wszTempPath);
- GetTempFileNameW(wszTempPath, L"wa_", TRUE, wszTempFile);
- FILE *f = _wfopen(wszTempFile, L"wb");
- fwrite(&fih, sizeof(BITMAPFILEHEADER), 1, f);
- fwrite(&bih, sizeof(BITMAPINFOHEADER), 1, f);
- fwrite(pData, sizeof(unsigned char), dataLen, f);
- fclose(f);
-
- SendMessage(hwndRc, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)Image_Load(wszTempFile));
-
- DeleteFileW(wszTempFile);
- QRcode_free(pQR);
- }
-};
-
-static INT_PTR __stdcall sttShowDialog(void *param)
-{
- WhatsAppProto *ppro = (WhatsAppProto *)param;
-
- if (ppro->m_pQRDlg == nullptr) {
- ppro->m_pQRDlg = new CWhatsAppQRDlg(ppro);
- ppro->m_pQRDlg->Show();
- }
- else {
- SetForegroundWindow(ppro->m_pQRDlg->GetHwnd());
- SetActiveWindow(ppro->m_pQRDlg->GetHwnd());
- }
- return 0;
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-void WhatsAppProto::CloseQrDialog()
-{
- if (m_pQRDlg) {
- m_pQRDlg->SetSuccess();
- m_pQRDlg->Close();
- }
-}
-
-bool WhatsAppProto::ShowQrCode(const CMStringA &ref)
-{
- CallFunctionSync(sttShowDialog, this);
-
- MBinBuffer secret(getBlob(DBKEY_SECRET_KEY));
-
- ptrA s1(mir_base64_encode(m_noise->noiseKeys.pub));
- ptrA s2(mir_base64_encode(m_signalStore.signedIdentity.pub));
- ptrA s3(mir_base64_encode(secret));
- CMStringA szQrData(FORMAT, "%s,%s,%s,%s", ref.c_str(), s1.get(), s2.get(), s3.get());
- m_pQRDlg->SetData(szQrData);
- return true;
-}
+/*
+
+WhatsApp plugin for Miranda NG
+Copyright © 2019-23 George Hazan
+
+*/
+
+#include "stdafx.h"
+
+class CWhatsAppQRDlg : public CProtoDlgBase<WhatsAppProto>
+{
+public:
+ CWhatsAppQRDlg(WhatsAppProto *ppro) :
+ CProtoDlgBase<WhatsAppProto>(ppro, IDD_SHOWQR)
+ {}
+
+ void OnDestroy() override
+ {
+ m_proto->m_pQRDlg = nullptr;
+
+ if (!m_bSucceeded)
+ m_proto->ShutdownSession();
+ }
+
+ void SetSuccess()
+ {
+ m_bSucceeded = true;
+ }
+
+ void SetData(const CMStringA &str)
+ {
+ auto *pQR = QRcode_encodeString(str, 0, QR_ECLEVEL_L, QR_MODE_8, 1);
+
+ HWND hwndRc = GetDlgItem(m_hwnd, IDC_QRPIC);
+ RECT rc;
+ GetClientRect(hwndRc, &rc);
+
+ ::SetForegroundWindow(m_hwnd);
+
+ int scale = 8; // (rc.bottom - rc.top) / pQR->width;
+ int rowLen = pQR->width * scale * 3;
+ if (rowLen % 4)
+ rowLen = (rowLen / 4 + 1) * 4;
+ int dataLen = rowLen * pQR->width * scale;
+
+ mir_ptr<BYTE> pData((BYTE *)mir_alloc(dataLen));
+ if (pData == nullptr) {
+ QRcode_free(pQR);
+ return;
+ }
+
+ memset(pData, 0xFF, dataLen); // white background by default
+
+ const BYTE *s = pQR->data;
+ for (int y = 0; y < pQR->width; y++) {
+ BYTE *d = pData.get() + rowLen * y * scale;
+ for (int x = 0; x < pQR->width; x++) {
+ if (*s & 1)
+ for (int i = 0; i < scale; i++)
+ for (int j = 0; j < scale; j++) {
+ d[j * 3 + i * rowLen] = 0;
+ d[1 + j * 3 + i * rowLen] = 0;
+ d[2 + j * 3 + i * rowLen] = 0;
+ }
+
+ d += scale * 3;
+ s++;
+ }
+ }
+
+ BITMAPFILEHEADER fih = {};
+ fih.bfType = 0x4d42; // "BM"
+ fih.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + dataLen;
+ fih.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
+
+ BITMAPINFOHEADER bih = {};
+ bih.biSize = sizeof(BITMAPINFOHEADER);
+ bih.biWidth = pQR->width * scale;
+ bih.biHeight = -bih.biWidth;
+ bih.biPlanes = 1;
+ bih.biBitCount = 24;
+ bih.biCompression = BI_RGB;
+
+ wchar_t wszTempPath[MAX_PATH], wszTempFile[MAX_PATH];
+ GetTempPathW(_countof(wszTempPath), wszTempPath);
+ GetTempFileNameW(wszTempPath, L"wa_", TRUE, wszTempFile);
+ FILE *f = _wfopen(wszTempFile, L"wb");
+ fwrite(&fih, sizeof(BITMAPFILEHEADER), 1, f);
+ fwrite(&bih, sizeof(BITMAPINFOHEADER), 1, f);
+ fwrite(pData, sizeof(unsigned char), dataLen, f);
+ fclose(f);
+
+ SendMessage(hwndRc, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)Image_Load(wszTempFile));
+
+ DeleteFileW(wszTempFile);
+ QRcode_free(pQR);
+ }
+};
+
+static INT_PTR __stdcall sttShowDialog(void *param)
+{
+ WhatsAppProto *ppro = (WhatsAppProto *)param;
+
+ if (ppro->m_pQRDlg == nullptr) {
+ ppro->m_pQRDlg = new CWhatsAppQRDlg(ppro);
+ ppro->m_pQRDlg->Show();
+ }
+ else {
+ SetForegroundWindow(ppro->m_pQRDlg->GetHwnd());
+ SetActiveWindow(ppro->m_pQRDlg->GetHwnd());
+ }
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void WhatsAppProto::CloseQrDialog()
+{
+ if (m_pQRDlg) {
+ m_pQRDlg->SetSuccess();
+ m_pQRDlg->Close();
+ }
+}
+
+bool WhatsAppProto::ShowQrCode(const CMStringA &ref)
+{
+ CallFunctionSync(sttShowDialog, this);
+
+ MBinBuffer secret(getBlob(DBKEY_SECRET_KEY));
+
+ ptrA s1(mir_base64_encode(m_noise->noiseKeys.pub));
+ ptrA s2(mir_base64_encode(m_signalStore.signedIdentity.pub));
+ ptrA s3(mir_base64_encode(secret));
+ CMStringA szQrData(FORMAT, "%s,%s,%s,%s", ref.c_str(), s1.get(), s2.get(), s3.get());
+ m_pQRDlg->SetData(szQrData);
+ return true;
+}
diff --git a/protocols/WhatsApp/src/server.cpp b/protocols/WhatsApp/src/server.cpp
index 0afe96662d..7512b05caf 100644
--- a/protocols/WhatsApp/src/server.cpp
+++ b/protocols/WhatsApp/src/server.cpp
@@ -1,7 +1,7 @@
/*
WhatsApp plugin for Miranda NG
-Copyright © 2019-22 George Hazan
+Copyright © 2019-23 George Hazan
*/
diff --git a/protocols/WhatsApp/src/signal.cpp b/protocols/WhatsApp/src/signal.cpp
index b94a3452e4..d8a57b0222 100644
--- a/protocols/WhatsApp/src/signal.cpp
+++ b/protocols/WhatsApp/src/signal.cpp
@@ -1,788 +1,788 @@
-/*
-
-WhatsApp plugin for Miranda NG
-Copyright © 2019-22 George Hazan
-
-*/
-
-#include "stdafx.h"
-
-/////////////////////////////////////////////////////////////////////////////////////////
-// MSignalStore members
-
-static int CompareSessions(const MSignalSession *p1, const MSignalSession *p2)
-{
- if (int ret = mir_strcmp(p1->szName, p2->szName))
- return ret;
-
- return p1->getDeviceId() - p2->getDeviceId();
-}
-
-MSignalStore::MSignalStore(PROTO_INTERFACE *_1, const char *_2) :
- pProto(_1),
- prefix(_2),
- arSessions(1, &CompareSessions)
-{
- init();
-}
-
-MSignalStore::~MSignalStore()
-{
- signal_protocol_store_context_destroy(m_pStore);
- signal_context_destroy(m_pContext);
-}
-
-void MSignalStore::logError(int err, const char *pszMessage)
-{
- if (err < 0) {
- pProto->debugLogA("libsignal error %d", err);
- throw pszMessage;
- }
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-static void log_func(int level, const char *pmsg, size_t /*msgLen*/, void *pUserData)
-{
- auto *pStore = (MSignalStore *)pUserData;
- pStore->pProto->debugLogA("libsignal {%d}: %s", level, pmsg);
-}
-
-static int hmac_sha256_init(void **hmac_context, const uint8_t *key, size_t key_len, void *)
-{
- HMAC_CTX *ctx = HMAC_CTX_new();
- *hmac_context = ctx;
- HMAC_Init(ctx, key, (int)key_len, EVP_sha256());
- return 0;
-}
-
-static int hmac_sha256_update(void *hmac_context, const uint8_t *data, size_t data_len, void *)
-{
- return HMAC_Update((HMAC_CTX *)hmac_context, data, data_len);
-}
-
-static int hmac_sha256_final(void *hmac_context, signal_buffer **output, void *)
-{
- BYTE data[200];
- unsigned len = 0;
- if (!HMAC_Final((HMAC_CTX *)hmac_context, data, &len))
- return 1;
-
- *output = signal_buffer_create(data, len);
- return 0;
-}
-
-static void hmac_sha256_cleanup(void *hmac_context, void *)
-{
- HMAC_CTX_free((HMAC_CTX *)hmac_context);
-}
-
-static int random_func(uint8_t *pData, size_t size, void *)
-{
- Utils_GetRandom(pData, size);
- return 0;
-}
-
-static int decrypt_func(signal_buffer **output,
- int /*cipher*/,
- const uint8_t *key, size_t /*key_len*/,
- const uint8_t *iv, size_t /*iv_len*/,
- const uint8_t *ciphertext, size_t ciphertext_len,
- void * /*user_data*/)
-{
- MBinBuffer res = aesDecrypt(EVP_aes_256_cbc(), key, iv, ciphertext, ciphertext_len);
- *output = signal_buffer_create(res.data(), res.length());
- return SG_SUCCESS;
-}
-
-static int encrypt_func(signal_buffer **output,
- int /*cipher*/,
- const uint8_t *key, size_t /*key_len*/,
- const uint8_t *iv, size_t /*iv_len*/,
- const uint8_t *ciphertext, size_t ciphertext_len,
- void * /*user_data*/)
-{
- MBinBuffer res = aesEncrypt(EVP_aes_256_cbc(), key, iv, ciphertext, ciphertext_len);
- *output = signal_buffer_create(res.data(), res.length());
- return SG_SUCCESS;
-}
-
-static int contains_session_func(const signal_protocol_address *address, void *user_data)
-{
- auto *pStore = (MSignalStore *)user_data;
- auto *pSession = pStore->getSession(address);
- return pSession != nullptr;
-}
-
-static int delete_all_sessions_func(const char *name, size_t name_len, void *user_data)
-{
- auto *pStore = (MSignalStore *)user_data;
- auto &pList = pStore->arSessions;
-
- int count = 0;
- for (auto &it : pList.rev_iter()) {
- if (it->hasAddress(name, name_len)) {
- pStore->pProto->delSetting(it->getSetting());
- pList.remove(pList.indexOf(&it));
- count++;
- }
- }
- return count;
-}
-
-int delete_session_func(const signal_protocol_address *address, void *user_data)
-{
- auto *pStore = (MSignalStore *)user_data;
- auto &pList = pStore->arSessions;
-
- MSignalSession tmp(CMStringA(address->name, (int)address->name_len), address->device_id);
- int idx = pList.getIndex(&tmp);
- if (idx != -1) {
- pStore->pProto->delSetting(tmp.getSetting());
- pList.remove(idx);
- }
- return 0;
-}
-
-static int get_sub_device_sessions_func(signal_int_list **sessions, const char *name, size_t name_len, void *user_data)
-{
- auto *pStore = (MSignalStore *)user_data;
- CMStringA szName(name, (int)name_len);
-
- signal_int_list *l = signal_int_list_alloc();
- unsigned int array_size = 0;
-
- for (auto &it : pStore->arSessions)
- if (it->szName == szName) {
- array_size++;
- signal_int_list_push_back(l, it->getDeviceId());
- }
-
- *sessions = l;
- return array_size;
-}
-
-static void destroy_func(void *)
-{}
-
-int load_session_func(signal_buffer **record, signal_buffer **user_data_storage, const signal_protocol_address *address, void *user_data)
-{
- auto *pStore = (MSignalStore *)user_data;
-
- auto *pSession = pStore->getSession(address);
- if (pSession == nullptr)
- return 0;
-
- *record = signal_buffer_create(pSession->sessionData.data(), pSession->sessionData.length());
- *user_data_storage = 0;
- return 1;
-}
-
-static int store_session_func(const signal_protocol_address *address, uint8_t *record, size_t record_len, uint8_t *, size_t, void *user_data)
-{
- auto *pStore = (MSignalStore *)user_data;
-
- MSignalSession tmp(CMStringA(address->name, (int)address->name_len), address->device_id);
- auto *pSession = pStore->arSessions.find(&tmp);
- if (pSession == nullptr) {
- pSession = new MSignalSession(tmp);
- pStore->arSessions.insert(pSession);
- }
-
- pSession->sessionData.assign(record, record_len);
- db_set_blob(0, pStore->pProto->m_szModuleName, pSession->getSetting(), record, (unsigned)record_len);
- return 0;
-}
-
-static int contains_pre_key(uint32_t pre_key_id, void *user_data)
-{
- auto *pStore = (MSignalStore *)user_data;
-
- CMStringA szSetting(FORMAT, "%s%d", "PreKey", pre_key_id);
- MBinBuffer blob(pStore->pProto->getBlob(szSetting));
- return (blob.data() != 0);
-}
-
-static int load_pre_key(signal_buffer **record, uint32_t pre_key_id, void *user_data)
-{
- auto *pStore = (MSignalStore *)user_data;
-
- CMStringA szSetting(FORMAT, "%s%d", "PreKey", pre_key_id);
- MBinBuffer blob(pStore->pProto->getBlob(szSetting));
- if (blob.isEmpty()) {
- pStore->pProto->debugLogA("Prekey #%d not found", pre_key_id);
- return SG_ERR_INVALID_KEY_ID;
- }
-
- *record = signal_buffer_create(blob.data(), blob.length());
- return SG_SUCCESS; //key exists and succesfully loaded
-}
-
-static int remove_pre_key(uint32_t pre_key_id, void *user_data)
-{
- auto *pStore = (MSignalStore *)user_data;
- pStore->pProto->debugLogA("Request to remove prekey #%d", pre_key_id);
-
- /*
- CMStringA szSetting(FORMAT, "%s%d", "PreKey", pre_key_id);
- pStore->pProto->delSetting(szSetting);
-
- szSetting.Format("PreKey%uPublic", pre_key_id);
- pStore->pProto->delSetting(szSetting);
-
- szSetting.Format("PreKey%uPrivate", pre_key_id);
- pStore->pProto->delSetting(szSetting);
- */
- return 0;
-}
-
-static int store_pre_key(uint32_t pre_key_id, uint8_t *record, size_t record_len, void *user_data)
-{
- auto *pStore = (MSignalStore *)user_data;
-
- CMStringA szSetting(FORMAT, "%s%d", "PreKey", pre_key_id);
- db_set_blob(0, pStore->pProto->m_szModuleName, szSetting, record, (unsigned int)record_len);
-
- session_pre_key *prekey = nullptr;
- session_pre_key_deserialize(&prekey, record, record_len, pStore->CTX()); //TODO: handle error
- if (prekey) {
- ec_key_pair *pre_key_pair = session_pre_key_get_key_pair(prekey);
-
- SignalBuffer key_buf(ec_key_pair_get_public(pre_key_pair));
- szSetting.Format("PreKey%uPublic", pre_key_id);
- db_set_blob(0, pStore->pProto->m_szModuleName, szSetting, key_buf.data(), key_buf.len());
- }
-
- return 0;
-}
-
-static int contains_signed_pre_key(uint32_t signed_pre_key_id, void *user_data)
-{
- auto *pStore = (MSignalStore *)user_data;
-
- CMStringA szSetting(FORMAT, "%s%d", "SignedPreKey", signed_pre_key_id);
- MBinBuffer blob(pStore->pProto->getBlob(szSetting));
- return blob.data() != 0;
-}
-
-static int load_signed_pre_key(signal_buffer **record, uint32_t signed_pre_key_id, void *user_data)
-{
- auto *pStore = (MSignalStore *)user_data;
-
- if (signed_pre_key_id == 0)
- signed_pre_key_id = 1;
-
- CMStringA szSetting(FORMAT, "%s%d", "SignedPreKey", signed_pre_key_id);
- MBinBuffer blob(pStore->pProto->getBlob(szSetting));
- if (blob.isEmpty()) {
- pStore->pProto->debugLogA("Signed prekey #%d not found", signed_pre_key_id);
- return SG_ERR_INVALID_KEY_ID;
- }
-
- *record = signal_buffer_create(blob.data(), blob.length());
- return SG_SUCCESS; //key exist and succesfully loaded
-}
-
-static int store_signed_pre_key(uint32_t signed_pre_key_id, uint8_t *record, size_t record_len, void *user_data)
-{
- auto *pStore = (MSignalStore *)user_data;
-
- CMStringA szSetting(FORMAT, "%s%d", "SignedPreKey", signed_pre_key_id);
- db_set_blob(0, pStore->pProto->m_szModuleName, szSetting, record, (unsigned int)record_len);
- return 0;
-}
-
-static int remove_signed_pre_key(uint32_t signed_pre_key_id, void *user_data)
-{
- auto *pStore = (MSignalStore *)user_data;
-
- CMStringA szSetting(FORMAT, "%s%d", "SignedPreKey", signed_pre_key_id);
- pStore->pProto->delSetting(szSetting);
- return 0;
-}
-
-static int get_identity_key_pair(signal_buffer **public_data, signal_buffer **private_data, void *user_data)
-{
- auto *pStore = (MSignalStore *)user_data;
-
- MBinBuffer buf;
- buf.append(KEY_BUNDLE_TYPE, 1);
- buf.append(pStore->signedIdentity.pub);
- *public_data = signal_buffer_create(buf.data(), (int)buf.length());
-
- *private_data = signal_buffer_create(pStore->signedIdentity.priv.data(), (int)pStore->signedIdentity.priv.length());
- return 0;
-}
-
-static int get_local_registration_id(void *user_data, uint32_t *registration_id)
-{
- auto *pStore = (MSignalStore *)user_data;
- *registration_id = pStore->pProto->getDword(DBKEY_REG_ID);
- return 0;
-}
-
-static int save_identity(const signal_protocol_address *address, uint8_t *key_data, size_t key_len, void *user_data)
-{
- auto *pStore = (MSignalStore *)user_data;
-
- CMStringA szSetting(FORMAT, "%s_%s_%d", "SignalIdentity", CMStringA(address->name, (int)address->name_len).c_str(), address->device_id);
- if (key_data != nullptr)
- db_set_blob(0, pStore->pProto->m_szModuleName, szSetting, key_data, (unsigned int)key_len); //TODO: check return value
- else
- pStore->pProto->delSetting(szSetting);
- return 0;
-}
-
-static int is_trusted_identity(const signal_protocol_address * /*address*/, uint8_t * /*key_data*/, size_t /*key_len*/, void * /*user_data*/)
-{
- return 1;
-}
-
-static CMStringA get_sender_setting(const signal_protocol_sender_key_name *skn)
-{
- WAJid jid(CMStringA(skn->sender.name, (int)skn->sender.name_len));
- return CMStringA(FORMAT, "SenderKey_%*s_%s_%d", (unsigned)skn->group_id_len, skn->group_id, jid.user.c_str(), skn->sender.device_id);
-}
-
-static int load_sender_key(signal_buffer **record, signal_buffer **, const signal_protocol_sender_key_name *skn, void *user_data)
-{
- auto *pStore = (MSignalStore *)user_data;
-
- CMStringA szSetting(get_sender_setting(skn));
- MBinBuffer blob(pStore->pProto->getBlob(szSetting));
- if (blob.isEmpty())
- return 0;
-
- *record = signal_buffer_create(blob.data(), blob.length());
- return 1;
-}
-
-static int store_sender_key(const signal_protocol_sender_key_name *skn, uint8_t *record, size_t record_len, uint8_t*, size_t, void *user_data)
-{
- auto *pStore = (MSignalStore *)user_data;
-
- CMStringA szSetting(get_sender_setting(skn));
- db_set_blob(0, pStore->pProto->m_szModuleName, szSetting, record, (unsigned)record_len);
- return 0;
-}
-
-void MSignalStore::init()
-{
- signal_context_create(&m_pContext, this);
- signal_context_set_log_function(m_pContext, log_func);
-
- signal_crypto_provider prov;
- memset(&prov, 0xFF, sizeof(prov));
- prov.hmac_sha256_init_func = hmac_sha256_init;
- prov.hmac_sha256_final_func = hmac_sha256_final;
- prov.hmac_sha256_update_func = hmac_sha256_update;
- prov.hmac_sha256_cleanup_func = hmac_sha256_cleanup;
- prov.random_func = random_func;
- prov.decrypt_func = decrypt_func;
- prov.encrypt_func = encrypt_func;
- signal_context_set_crypto_provider(m_pContext, &prov);
-
- // read resident data from database
- MBinBuffer blob(pProto->getBlob(DBKEY_PREKEY));
- if (blob.isEmpty()) {
- // nothing? generate signed identity keys (private & public)
- ratchet_identity_key_pair *keyPair;
- signal_protocol_key_helper_generate_identity_key_pair(&keyPair, m_pContext);
-
- auto *pPubKey = ratchet_identity_key_pair_get_public(keyPair);
- db_set_blob(0, pProto->m_szModuleName, DBKEY_SIGNED_IDENTITY_PUB, pPubKey->data, sizeof(pPubKey->data));
-
- auto *pPrivKey = ratchet_identity_key_pair_get_private(keyPair);
- db_set_blob(0, pProto->m_szModuleName, DBKEY_SIGNED_IDENTITY_PRIV, pPrivKey->data, sizeof(pPrivKey->data));
-
- session_signed_pre_key *signed_pre_key;
- signal_protocol_key_helper_generate_signed_pre_key(&signed_pre_key, keyPair, 1, time(0), m_pContext);
-
- SignalBuffer prekeyBuf(signed_pre_key);
- db_set_blob(0, pProto->m_szModuleName, DBKEY_PREKEY, prekeyBuf.data(), prekeyBuf.len());
- blob.assign(prekeyBuf.data(), prekeyBuf.len());
-
- SIGNAL_UNREF(signed_pre_key);
- SIGNAL_UNREF(keyPair);
- }
-
- session_signed_pre_key *signed_pre_key;
- session_signed_pre_key_deserialize(&signed_pre_key, blob.data(), blob.length(), m_pContext);
-
- ec_key_pair *pKeys = session_signed_pre_key_get_key_pair(signed_pre_key);
- auto *pPubKey = ec_key_pair_get_public(pKeys);
- preKey.pub.assign(pPubKey->data, sizeof(pPubKey->data));
-
- auto *pPrivKey = ec_key_pair_get_private(pKeys);
- preKey.priv.assign(pPrivKey->data, sizeof(pPrivKey->data));
-
- preKey.signature.assign(session_signed_pre_key_get_signature(signed_pre_key), session_signed_pre_key_get_signature_len(signed_pre_key));
- preKey.keyid = session_signed_pre_key_get_id(signed_pre_key);
- SIGNAL_UNREF(signed_pre_key);
-
- signedIdentity.pub = pProto->getBlob(DBKEY_SIGNED_IDENTITY_PUB);
- signedIdentity.priv = pProto->getBlob(DBKEY_SIGNED_IDENTITY_PRIV);
-
- // create store with callbacks
- signal_protocol_store_context_create(&m_pStore, m_pContext);
-
- signal_protocol_session_store ss;
- ss.contains_session_func = &contains_session_func;
- ss.delete_all_sessions_func = &delete_all_sessions_func;
- ss.delete_session_func = &delete_session_func;
- ss.destroy_func = &destroy_func;
- ss.get_sub_device_sessions_func = &get_sub_device_sessions_func;
- ss.load_session_func = &load_session_func;
- ss.store_session_func = &store_session_func;
- ss.user_data = this;
- signal_protocol_store_context_set_session_store(m_pStore, &ss);
-
- signal_protocol_pre_key_store sp;
- sp.contains_pre_key = &contains_pre_key;
- sp.destroy_func = &destroy_func;
- sp.load_pre_key = &load_pre_key;
- sp.remove_pre_key = &remove_pre_key;
- sp.store_pre_key = &store_pre_key;
- sp.user_data = this;
- signal_protocol_store_context_set_pre_key_store(m_pStore, &sp);
-
- signal_protocol_sender_key_store sk;
- sk.destroy_func = destroy_func;
- sk.load_sender_key = load_sender_key;
- sk.store_sender_key = store_sender_key;
- sk.user_data = this;
- signal_protocol_store_context_set_sender_key_store(m_pStore, &sk);
-
- signal_protocol_signed_pre_key_store ssp;
- ssp.contains_signed_pre_key = &contains_signed_pre_key;
- ssp.destroy_func = &destroy_func;
- ssp.load_signed_pre_key = &load_signed_pre_key;
- ssp.remove_signed_pre_key = &remove_signed_pre_key;
- ssp.store_signed_pre_key = &store_signed_pre_key;
- ssp.user_data = this;
- signal_protocol_store_context_set_signed_pre_key_store(m_pStore, &ssp);
-
- signal_protocol_identity_key_store sip;
- sip.destroy_func = &destroy_func;
- sip.get_identity_key_pair = &get_identity_key_pair;
- sip.get_local_registration_id = &get_local_registration_id;
- sip.is_trusted_identity = &is_trusted_identity;
- sip.save_identity = &save_identity;
- sip.user_data = this;
- signal_protocol_store_context_set_identity_key_store(m_pStore, &sip);
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-// MSignalSession members
-
-MSignalSession::MSignalSession(const CMStringA &_1, int _2) :
- szName(_1)
-{
- address.name = szName.GetBuffer();
- address.name_len = szName.GetLength();
- address.device_id = _2;
-}
-
-MSignalSession::~MSignalSession()
-{
- session_cipher_free(cipher);
-}
-
-bool MSignalSession::hasAddress(const char *name, size_t name_len) const
-{
- if (address.name_len != name_len)
- return false;
- return memcmp(address.name, name, name_len) == 0;
-}
-
-CMStringA MSignalSession::getSetting() const
-{
- return CMStringA(FORMAT, "SignalSession_%s_%d", szName.c_str(), getDeviceId());
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-MSignalSession* MSignalStore::createSession(const CMStringA &szName, int deviceId)
-{
- signal_protocol_address tmp = {szName.c_str(), (unsigned)szName.GetLength(), deviceId};
- auto *pSession = getSession(&tmp);
- if (pSession == nullptr) {
- pSession = new MSignalSession(szName, deviceId);
- arSessions.insert(pSession);
- }
-
- if (pSession->cipher == nullptr)
- logError(
- session_cipher_create(&pSession->cipher, m_pStore, &pSession->address, m_pContext),
- "session_cipher_create failure");
-
- return pSession;
-}
-
-MSignalSession* MSignalStore::getSession(const signal_protocol_address *address)
-{
- MSignalSession tmp(CMStringA(address->name, (int)address->name_len), address->device_id);
- auto *pSession = arSessions.find(&tmp);
- if (pSession == nullptr) {
- MBinBuffer blob(pProto->getBlob(tmp.getSetting()));
- if (blob.isEmpty())
- return nullptr;
-
- pSession = new MSignalSession(tmp);
- pSession->sessionData.assign(blob.data(), blob.length());
- arSessions.insert(pSession);
- }
-
- return pSession;
-}
-
-void MSignalStore::importPublicKey(ec_public_key **result, MBinBuffer &buf)
-{
- buf.appendBefore("\x05", 1);
- curve_decode_point(result, buf.data(), buf.length(), m_pContext);
-}
-
-void MSignalStore::injectSession(const char *szJid, const WANode *pNode, const WANode *pKey)
-{
- WAJid jid(szJid);
- auto *signedKey = pKey->getChild("skey");
- auto *key = pKey->getChild("key");
- auto *identity = pKey->getChild("identity");
- auto *registration = pNode->getChild("registration");
- if (!signedKey || !key || !identity || !registration) {
- pProto->debugLogA("Bad key data for %s", jid.toString().c_str());
- return;
- }
-
- signal_protocol_address address = {jid.user.c_str(), (unsigned)jid.user.GetLength(), jid.device};
-
- session_builder *builder;
- logError(
- session_builder_create(&builder, m_pStore, &address, m_pContext),
- "unable to create session cipher");
-
- int regId = decodeBigEndian(registration->content);
- int preKeyId = decodeBigEndian(key->getChild("id")->content);
- int signedPreKeyId = decodeBigEndian(signedKey->getChild("id")->content);
-
- ec_public_key *preKeyPub, *signedPreKeyPub, *identityKey;
- importPublicKey(&preKeyPub, key->getChild("value")->content);
- importPublicKey(&identityKey, identity->content);
- importPublicKey(&signedPreKeyPub, signedKey->getChild("value")->content);
-
- auto &sign = signedKey->getChild("signature")->content;
-
- session_pre_key_bundle *bundle;
- logError(
- session_pre_key_bundle_create(&bundle, regId, jid.device, preKeyId, preKeyPub, signedPreKeyId, signedPreKeyPub, sign.data(), sign.length(), identityKey),
- "unable to create pre key bundle");
-
- logError(
- session_builder_process_pre_key_bundle(builder, bundle),
- "unable to process pre key bundle");
-
- session_pre_key_bundle_destroy((signal_type_base*)bundle);
- session_builder_free(builder);
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-MBinBuffer MSignalStore::decryptSignalProto(const CMStringA &from, const char *pszType, const MBinBuffer &encrypted)
-{
- WAJid jid(from);
- auto *pSession = createSession(jid.user, jid.device);
-
- signal_buffer *result = nullptr;
- if (!mir_strcmp(pszType, "pkmsg")) {
- pre_key_signal_message *pMsg;
- logError(
- pre_key_signal_message_deserialize(&pMsg, encrypted.data(), encrypted.length(), m_pContext),
- "unable to deserialize prekey message");
-
- logError(
- session_cipher_decrypt_pre_key_signal_message(pSession->getCipher(), pMsg, this, &result),
- "unable to decrypt prekey message");
-
- pre_key_signal_message_destroy((signal_type_base*)pMsg);
- }
- else {
- signal_message *pMsg;
- logError(
- signal_message_deserialize(&pMsg, encrypted.data(), encrypted.length(), m_pContext),
- "unable to deserialize signal message");
-
- logError(
- session_cipher_decrypt_signal_message(pSession->getCipher(), pMsg, this, &result),
- "unable to decrypt signal message");
-
- signal_message_destroy((signal_type_base *)pMsg);
- }
-
- MBinBuffer res;
- res.assign(result->data, result->len);
- signal_buffer_free(result);
- return res;
-}
-
-MBinBuffer MSignalStore::decryptGroupSignalProto(const CMStringA &group, const CMStringA &sender, const MBinBuffer &encrypted)
-{
- WAJid jid(sender);
- signal_protocol_sender_key_name senderKeyName;
- senderKeyName.group_id = group.c_str();
- senderKeyName.group_id_len = group.GetLength();
- senderKeyName.sender.device_id = 0;
- senderKeyName.sender.name = jid.user.c_str();
- senderKeyName.sender.name_len = jid.user.GetLength();
-
- group_cipher *cipher;
- logError(
- group_cipher_create(&cipher, m_pStore, &senderKeyName, m_pContext),
- "unable to create group cipher");
-
- sender_key_message *skmsg;
- logError(
- sender_key_message_deserialize(&skmsg, encrypted.data(), encrypted.length(), m_pContext),
- "unable to deserialize skmsg");
-
- signal_buffer *result = nullptr;
- logError(
- group_cipher_decrypt(cipher, skmsg, this, &result),
- "unable to decrypt skmsg");
-
- sender_key_message_destroy((signal_type_base *)skmsg);
- group_cipher_free(cipher);
-
- MBinBuffer res;
- res.assign(result->data, result->len);
- signal_buffer_free(result);
- return res;
-}
-
-void MSignalStore::processSenderKeyMessage(const CMStringA &author, const Wa__Message__SenderKeyDistributionMessage *msg)
-{
- WAJid jid(author);
- signal_protocol_sender_key_name senderKeyName;
- senderKeyName.group_id = msg->groupid;
- senderKeyName.group_id_len = mir_strlen(msg->groupid);
- senderKeyName.sender.device_id = 0;
- senderKeyName.sender.name = jid.user.c_str();
- senderKeyName.sender.name_len = jid.user.GetLength();
-
- group_session_builder *builder;
- logError(
- group_session_builder_create(&builder, m_pStore, m_pContext),
- "unable to create session builder");
-
- sender_key_distribution_message *skmsg;
- logError(
- sender_key_distribution_message_deserialize(&skmsg, msg->axolotlsenderkeydistributionmessage.data, msg->axolotlsenderkeydistributionmessage.len, m_pContext),
- "unable to decode skd message");
-
- logError(
- group_session_builder_process_session(builder, &senderKeyName, skmsg),
- "unable to process skd message");
-
- sender_key_distribution_message_destroy((signal_type_base *)skmsg);
- group_session_builder_free(builder);
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-// encryption
-
-MBinBuffer MSignalStore::encryptSenderKey(const WAJid &to, const CMStringA &from, const MBinBuffer &buf, MBinBuffer &skmsgKey)
-{
- signal_protocol_sender_key_name senderKeyName;
- senderKeyName.group_id = to.user.c_str();
- senderKeyName.group_id_len = to.user.GetLength();
- senderKeyName.sender.device_id = 0;
- senderKeyName.sender.name = from.c_str();
- senderKeyName.sender.name_len = from.GetLength();
-
- group_session_builder *builder;
- logError(
- group_session_builder_create(&builder, m_pStore, m_pContext),
- "unable to create session builder");
-
- sender_key_distribution_message *skmsg;
- logError(
- group_session_builder_create_session(builder, &skmsg, &senderKeyName),
- "unable to create session");
-
- group_cipher *cipher;
- logError(
- group_cipher_create(&cipher, m_pStore, &senderKeyName, m_pContext),
- "unable to create group cipher");
-
- ciphertext_message *encMessage;
- logError(
- group_cipher_encrypt(cipher, buf.data(), buf.length(), &encMessage),
- "unable to encrypt group message");
-
- MBinBuffer res;
- auto *cipherText = ciphertext_message_get_serialized(encMessage);
- res.assign(cipherText->data, cipherText->len);
-
- auto *pKey = sender_key_distribution_message_get_signature_key(skmsg);
- skmsgKey.assign(pKey->data, sizeof(pKey->data));
-
- sender_key_distribution_message_destroy((signal_type_base*)skmsg);
- group_cipher_free(cipher);
- group_session_builder_free(builder);
- return res;
-}
-
-MBinBuffer MSignalStore::encryptSignalProto(const WAJid &to, const MBinBuffer &buf, int &type)
-{
- auto *pSession = createSession(to.user, to.device);
-
- ciphertext_message *pEncrypted;
- logError(
- session_cipher_encrypt(pSession->getCipher(), buf.data(), buf.length(), &pEncrypted),
- "unable to encrypt signal message");
-
- type = ciphertext_message_get_type(pEncrypted);
-
- MBinBuffer res;
- auto *encBuf = ciphertext_message_get_serialized(pEncrypted);
- res.assign(encBuf->data, encBuf->len);
- SIGNAL_UNREF(pEncrypted);
- return res;
-}
-
-MBinBuffer MSignalStore::encodeSignedIdentity(bool bIncludeSignatureKey)
-{
- proto::ADVSignedDeviceIdentity identity(pProto->getBlob("WAAccount"));
-
- if (!bIncludeSignatureKey)
- proto::CleanBinary(identity->accountsignaturekey), identity->has_accountsignaturekey = false;
-
- return proto::Serialize(identity);
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-// generate and save pre keys set
-
-void MSignalStore::generatePrekeys(int count)
-{
- int iNextKeyId = pProto->getDword(DBKEY_PREKEY_NEXT_ID, 1);
-
- CMStringA szSetting;
- signal_protocol_key_helper_pre_key_list_node *keys_root;
- signal_protocol_key_helper_generate_pre_keys(&keys_root, iNextKeyId, count, m_pContext);
- for (auto *it = keys_root; it; it = signal_protocol_key_helper_key_list_next(it)) {
- session_pre_key *pre_key = signal_protocol_key_helper_key_list_element(it);
- uint32_t pre_key_id = session_pre_key_get_id(pre_key);
-
- SignalBuffer buf(pre_key);
- szSetting.Format("PreKey%d", pre_key_id);
- db_set_blob(0, pProto->m_szModuleName, szSetting, buf.data(), buf.len());
-
- ec_key_pair *pre_key_pair = session_pre_key_get_key_pair(pre_key);
- auto *pPubKey = ec_key_pair_get_public(pre_key_pair);
- szSetting.Format("PreKey%dPublic", pre_key_id);
- db_set_blob(0, pProto->m_szModuleName, szSetting, pPubKey->data, sizeof(pPubKey->data));
- }
- signal_protocol_key_helper_key_list_free(keys_root);
-
- pProto->setDword(DBKEY_PREKEY_NEXT_ID, iNextKeyId + count);
-}
+/*
+
+WhatsApp plugin for Miranda NG
+Copyright © 2019-23 George Hazan
+
+*/
+
+#include "stdafx.h"
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MSignalStore members
+
+static int CompareSessions(const MSignalSession *p1, const MSignalSession *p2)
+{
+ if (int ret = mir_strcmp(p1->szName, p2->szName))
+ return ret;
+
+ return p1->getDeviceId() - p2->getDeviceId();
+}
+
+MSignalStore::MSignalStore(PROTO_INTERFACE *_1, const char *_2) :
+ pProto(_1),
+ prefix(_2),
+ arSessions(1, &CompareSessions)
+{
+ init();
+}
+
+MSignalStore::~MSignalStore()
+{
+ signal_protocol_store_context_destroy(m_pStore);
+ signal_context_destroy(m_pContext);
+}
+
+void MSignalStore::logError(int err, const char *pszMessage)
+{
+ if (err < 0) {
+ pProto->debugLogA("libsignal error %d", err);
+ throw pszMessage;
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static void log_func(int level, const char *pmsg, size_t /*msgLen*/, void *pUserData)
+{
+ auto *pStore = (MSignalStore *)pUserData;
+ pStore->pProto->debugLogA("libsignal {%d}: %s", level, pmsg);
+}
+
+static int hmac_sha256_init(void **hmac_context, const uint8_t *key, size_t key_len, void *)
+{
+ HMAC_CTX *ctx = HMAC_CTX_new();
+ *hmac_context = ctx;
+ HMAC_Init(ctx, key, (int)key_len, EVP_sha256());
+ return 0;
+}
+
+static int hmac_sha256_update(void *hmac_context, const uint8_t *data, size_t data_len, void *)
+{
+ return HMAC_Update((HMAC_CTX *)hmac_context, data, data_len);
+}
+
+static int hmac_sha256_final(void *hmac_context, signal_buffer **output, void *)
+{
+ BYTE data[200];
+ unsigned len = 0;
+ if (!HMAC_Final((HMAC_CTX *)hmac_context, data, &len))
+ return 1;
+
+ *output = signal_buffer_create(data, len);
+ return 0;
+}
+
+static void hmac_sha256_cleanup(void *hmac_context, void *)
+{
+ HMAC_CTX_free((HMAC_CTX *)hmac_context);
+}
+
+static int random_func(uint8_t *pData, size_t size, void *)
+{
+ Utils_GetRandom(pData, size);
+ return 0;
+}
+
+static int decrypt_func(signal_buffer **output,
+ int /*cipher*/,
+ const uint8_t *key, size_t /*key_len*/,
+ const uint8_t *iv, size_t /*iv_len*/,
+ const uint8_t *ciphertext, size_t ciphertext_len,
+ void * /*user_data*/)
+{
+ MBinBuffer res = aesDecrypt(EVP_aes_256_cbc(), key, iv, ciphertext, ciphertext_len);
+ *output = signal_buffer_create(res.data(), res.length());
+ return SG_SUCCESS;
+}
+
+static int encrypt_func(signal_buffer **output,
+ int /*cipher*/,
+ const uint8_t *key, size_t /*key_len*/,
+ const uint8_t *iv, size_t /*iv_len*/,
+ const uint8_t *ciphertext, size_t ciphertext_len,
+ void * /*user_data*/)
+{
+ MBinBuffer res = aesEncrypt(EVP_aes_256_cbc(), key, iv, ciphertext, ciphertext_len);
+ *output = signal_buffer_create(res.data(), res.length());
+ return SG_SUCCESS;
+}
+
+static int contains_session_func(const signal_protocol_address *address, void *user_data)
+{
+ auto *pStore = (MSignalStore *)user_data;
+ auto *pSession = pStore->getSession(address);
+ return pSession != nullptr;
+}
+
+static int delete_all_sessions_func(const char *name, size_t name_len, void *user_data)
+{
+ auto *pStore = (MSignalStore *)user_data;
+ auto &pList = pStore->arSessions;
+
+ int count = 0;
+ for (auto &it : pList.rev_iter()) {
+ if (it->hasAddress(name, name_len)) {
+ pStore->pProto->delSetting(it->getSetting());
+ pList.remove(pList.indexOf(&it));
+ count++;
+ }
+ }
+ return count;
+}
+
+int delete_session_func(const signal_protocol_address *address, void *user_data)
+{
+ auto *pStore = (MSignalStore *)user_data;
+ auto &pList = pStore->arSessions;
+
+ MSignalSession tmp(CMStringA(address->name, (int)address->name_len), address->device_id);
+ int idx = pList.getIndex(&tmp);
+ if (idx != -1) {
+ pStore->pProto->delSetting(tmp.getSetting());
+ pList.remove(idx);
+ }
+ return 0;
+}
+
+static int get_sub_device_sessions_func(signal_int_list **sessions, const char *name, size_t name_len, void *user_data)
+{
+ auto *pStore = (MSignalStore *)user_data;
+ CMStringA szName(name, (int)name_len);
+
+ signal_int_list *l = signal_int_list_alloc();
+ unsigned int array_size = 0;
+
+ for (auto &it : pStore->arSessions)
+ if (it->szName == szName) {
+ array_size++;
+ signal_int_list_push_back(l, it->getDeviceId());
+ }
+
+ *sessions = l;
+ return array_size;
+}
+
+static void destroy_func(void *)
+{}
+
+int load_session_func(signal_buffer **record, signal_buffer **user_data_storage, const signal_protocol_address *address, void *user_data)
+{
+ auto *pStore = (MSignalStore *)user_data;
+
+ auto *pSession = pStore->getSession(address);
+ if (pSession == nullptr)
+ return 0;
+
+ *record = signal_buffer_create(pSession->sessionData.data(), pSession->sessionData.length());
+ *user_data_storage = 0;
+ return 1;
+}
+
+static int store_session_func(const signal_protocol_address *address, uint8_t *record, size_t record_len, uint8_t *, size_t, void *user_data)
+{
+ auto *pStore = (MSignalStore *)user_data;
+
+ MSignalSession tmp(CMStringA(address->name, (int)address->name_len), address->device_id);
+ auto *pSession = pStore->arSessions.find(&tmp);
+ if (pSession == nullptr) {
+ pSession = new MSignalSession(tmp);
+ pStore->arSessions.insert(pSession);
+ }
+
+ pSession->sessionData.assign(record, record_len);
+ db_set_blob(0, pStore->pProto->m_szModuleName, pSession->getSetting(), record, (unsigned)record_len);
+ return 0;
+}
+
+static int contains_pre_key(uint32_t pre_key_id, void *user_data)
+{
+ auto *pStore = (MSignalStore *)user_data;
+
+ CMStringA szSetting(FORMAT, "%s%d", "PreKey", pre_key_id);
+ MBinBuffer blob(pStore->pProto->getBlob(szSetting));
+ return (blob.data() != 0);
+}
+
+static int load_pre_key(signal_buffer **record, uint32_t pre_key_id, void *user_data)
+{
+ auto *pStore = (MSignalStore *)user_data;
+
+ CMStringA szSetting(FORMAT, "%s%d", "PreKey", pre_key_id);
+ MBinBuffer blob(pStore->pProto->getBlob(szSetting));
+ if (blob.isEmpty()) {
+ pStore->pProto->debugLogA("Prekey #%d not found", pre_key_id);
+ return SG_ERR_INVALID_KEY_ID;
+ }
+
+ *record = signal_buffer_create(blob.data(), blob.length());
+ return SG_SUCCESS; //key exists and succesfully loaded
+}
+
+static int remove_pre_key(uint32_t pre_key_id, void *user_data)
+{
+ auto *pStore = (MSignalStore *)user_data;
+ pStore->pProto->debugLogA("Request to remove prekey #%d", pre_key_id);
+
+ /*
+ CMStringA szSetting(FORMAT, "%s%d", "PreKey", pre_key_id);
+ pStore->pProto->delSetting(szSetting);
+
+ szSetting.Format("PreKey%uPublic", pre_key_id);
+ pStore->pProto->delSetting(szSetting);
+
+ szSetting.Format("PreKey%uPrivate", pre_key_id);
+ pStore->pProto->delSetting(szSetting);
+ */
+ return 0;
+}
+
+static int store_pre_key(uint32_t pre_key_id, uint8_t *record, size_t record_len, void *user_data)
+{
+ auto *pStore = (MSignalStore *)user_data;
+
+ CMStringA szSetting(FORMAT, "%s%d", "PreKey", pre_key_id);
+ db_set_blob(0, pStore->pProto->m_szModuleName, szSetting, record, (unsigned int)record_len);
+
+ session_pre_key *prekey = nullptr;
+ session_pre_key_deserialize(&prekey, record, record_len, pStore->CTX()); //TODO: handle error
+ if (prekey) {
+ ec_key_pair *pre_key_pair = session_pre_key_get_key_pair(prekey);
+
+ SignalBuffer key_buf(ec_key_pair_get_public(pre_key_pair));
+ szSetting.Format("PreKey%uPublic", pre_key_id);
+ db_set_blob(0, pStore->pProto->m_szModuleName, szSetting, key_buf.data(), key_buf.len());
+ }
+
+ return 0;
+}
+
+static int contains_signed_pre_key(uint32_t signed_pre_key_id, void *user_data)
+{
+ auto *pStore = (MSignalStore *)user_data;
+
+ CMStringA szSetting(FORMAT, "%s%d", "SignedPreKey", signed_pre_key_id);
+ MBinBuffer blob(pStore->pProto->getBlob(szSetting));
+ return blob.data() != 0;
+}
+
+static int load_signed_pre_key(signal_buffer **record, uint32_t signed_pre_key_id, void *user_data)
+{
+ auto *pStore = (MSignalStore *)user_data;
+
+ if (signed_pre_key_id == 0)
+ signed_pre_key_id = 1;
+
+ CMStringA szSetting(FORMAT, "%s%d", "SignedPreKey", signed_pre_key_id);
+ MBinBuffer blob(pStore->pProto->getBlob(szSetting));
+ if (blob.isEmpty()) {
+ pStore->pProto->debugLogA("Signed prekey #%d not found", signed_pre_key_id);
+ return SG_ERR_INVALID_KEY_ID;
+ }
+
+ *record = signal_buffer_create(blob.data(), blob.length());
+ return SG_SUCCESS; //key exist and succesfully loaded
+}
+
+static int store_signed_pre_key(uint32_t signed_pre_key_id, uint8_t *record, size_t record_len, void *user_data)
+{
+ auto *pStore = (MSignalStore *)user_data;
+
+ CMStringA szSetting(FORMAT, "%s%d", "SignedPreKey", signed_pre_key_id);
+ db_set_blob(0, pStore->pProto->m_szModuleName, szSetting, record, (unsigned int)record_len);
+ return 0;
+}
+
+static int remove_signed_pre_key(uint32_t signed_pre_key_id, void *user_data)
+{
+ auto *pStore = (MSignalStore *)user_data;
+
+ CMStringA szSetting(FORMAT, "%s%d", "SignedPreKey", signed_pre_key_id);
+ pStore->pProto->delSetting(szSetting);
+ return 0;
+}
+
+static int get_identity_key_pair(signal_buffer **public_data, signal_buffer **private_data, void *user_data)
+{
+ auto *pStore = (MSignalStore *)user_data;
+
+ MBinBuffer buf;
+ buf.append(KEY_BUNDLE_TYPE, 1);
+ buf.append(pStore->signedIdentity.pub);
+ *public_data = signal_buffer_create(buf.data(), (int)buf.length());
+
+ *private_data = signal_buffer_create(pStore->signedIdentity.priv.data(), (int)pStore->signedIdentity.priv.length());
+ return 0;
+}
+
+static int get_local_registration_id(void *user_data, uint32_t *registration_id)
+{
+ auto *pStore = (MSignalStore *)user_data;
+ *registration_id = pStore->pProto->getDword(DBKEY_REG_ID);
+ return 0;
+}
+
+static int save_identity(const signal_protocol_address *address, uint8_t *key_data, size_t key_len, void *user_data)
+{
+ auto *pStore = (MSignalStore *)user_data;
+
+ CMStringA szSetting(FORMAT, "%s_%s_%d", "SignalIdentity", CMStringA(address->name, (int)address->name_len).c_str(), address->device_id);
+ if (key_data != nullptr)
+ db_set_blob(0, pStore->pProto->m_szModuleName, szSetting, key_data, (unsigned int)key_len); //TODO: check return value
+ else
+ pStore->pProto->delSetting(szSetting);
+ return 0;
+}
+
+static int is_trusted_identity(const signal_protocol_address * /*address*/, uint8_t * /*key_data*/, size_t /*key_len*/, void * /*user_data*/)
+{
+ return 1;
+}
+
+static CMStringA get_sender_setting(const signal_protocol_sender_key_name *skn)
+{
+ WAJid jid(CMStringA(skn->sender.name, (int)skn->sender.name_len));
+ return CMStringA(FORMAT, "SenderKey_%*s_%s_%d", (unsigned)skn->group_id_len, skn->group_id, jid.user.c_str(), skn->sender.device_id);
+}
+
+static int load_sender_key(signal_buffer **record, signal_buffer **, const signal_protocol_sender_key_name *skn, void *user_data)
+{
+ auto *pStore = (MSignalStore *)user_data;
+
+ CMStringA szSetting(get_sender_setting(skn));
+ MBinBuffer blob(pStore->pProto->getBlob(szSetting));
+ if (blob.isEmpty())
+ return 0;
+
+ *record = signal_buffer_create(blob.data(), blob.length());
+ return 1;
+}
+
+static int store_sender_key(const signal_protocol_sender_key_name *skn, uint8_t *record, size_t record_len, uint8_t*, size_t, void *user_data)
+{
+ auto *pStore = (MSignalStore *)user_data;
+
+ CMStringA szSetting(get_sender_setting(skn));
+ db_set_blob(0, pStore->pProto->m_szModuleName, szSetting, record, (unsigned)record_len);
+ return 0;
+}
+
+void MSignalStore::init()
+{
+ signal_context_create(&m_pContext, this);
+ signal_context_set_log_function(m_pContext, log_func);
+
+ signal_crypto_provider prov;
+ memset(&prov, 0xFF, sizeof(prov));
+ prov.hmac_sha256_init_func = hmac_sha256_init;
+ prov.hmac_sha256_final_func = hmac_sha256_final;
+ prov.hmac_sha256_update_func = hmac_sha256_update;
+ prov.hmac_sha256_cleanup_func = hmac_sha256_cleanup;
+ prov.random_func = random_func;
+ prov.decrypt_func = decrypt_func;
+ prov.encrypt_func = encrypt_func;
+ signal_context_set_crypto_provider(m_pContext, &prov);
+
+ // read resident data from database
+ MBinBuffer blob(pProto->getBlob(DBKEY_PREKEY));
+ if (blob.isEmpty()) {
+ // nothing? generate signed identity keys (private & public)
+ ratchet_identity_key_pair *keyPair;
+ signal_protocol_key_helper_generate_identity_key_pair(&keyPair, m_pContext);
+
+ auto *pPubKey = ratchet_identity_key_pair_get_public(keyPair);
+ db_set_blob(0, pProto->m_szModuleName, DBKEY_SIGNED_IDENTITY_PUB, pPubKey->data, sizeof(pPubKey->data));
+
+ auto *pPrivKey = ratchet_identity_key_pair_get_private(keyPair);
+ db_set_blob(0, pProto->m_szModuleName, DBKEY_SIGNED_IDENTITY_PRIV, pPrivKey->data, sizeof(pPrivKey->data));
+
+ session_signed_pre_key *signed_pre_key;
+ signal_protocol_key_helper_generate_signed_pre_key(&signed_pre_key, keyPair, 1, time(0), m_pContext);
+
+ SignalBuffer prekeyBuf(signed_pre_key);
+ db_set_blob(0, pProto->m_szModuleName, DBKEY_PREKEY, prekeyBuf.data(), prekeyBuf.len());
+ blob.assign(prekeyBuf.data(), prekeyBuf.len());
+
+ SIGNAL_UNREF(signed_pre_key);
+ SIGNAL_UNREF(keyPair);
+ }
+
+ session_signed_pre_key *signed_pre_key;
+ session_signed_pre_key_deserialize(&signed_pre_key, blob.data(), blob.length(), m_pContext);
+
+ ec_key_pair *pKeys = session_signed_pre_key_get_key_pair(signed_pre_key);
+ auto *pPubKey = ec_key_pair_get_public(pKeys);
+ preKey.pub.assign(pPubKey->data, sizeof(pPubKey->data));
+
+ auto *pPrivKey = ec_key_pair_get_private(pKeys);
+ preKey.priv.assign(pPrivKey->data, sizeof(pPrivKey->data));
+
+ preKey.signature.assign(session_signed_pre_key_get_signature(signed_pre_key), session_signed_pre_key_get_signature_len(signed_pre_key));
+ preKey.keyid = session_signed_pre_key_get_id(signed_pre_key);
+ SIGNAL_UNREF(signed_pre_key);
+
+ signedIdentity.pub = pProto->getBlob(DBKEY_SIGNED_IDENTITY_PUB);
+ signedIdentity.priv = pProto->getBlob(DBKEY_SIGNED_IDENTITY_PRIV);
+
+ // create store with callbacks
+ signal_protocol_store_context_create(&m_pStore, m_pContext);
+
+ signal_protocol_session_store ss;
+ ss.contains_session_func = &contains_session_func;
+ ss.delete_all_sessions_func = &delete_all_sessions_func;
+ ss.delete_session_func = &delete_session_func;
+ ss.destroy_func = &destroy_func;
+ ss.get_sub_device_sessions_func = &get_sub_device_sessions_func;
+ ss.load_session_func = &load_session_func;
+ ss.store_session_func = &store_session_func;
+ ss.user_data = this;
+ signal_protocol_store_context_set_session_store(m_pStore, &ss);
+
+ signal_protocol_pre_key_store sp;
+ sp.contains_pre_key = &contains_pre_key;
+ sp.destroy_func = &destroy_func;
+ sp.load_pre_key = &load_pre_key;
+ sp.remove_pre_key = &remove_pre_key;
+ sp.store_pre_key = &store_pre_key;
+ sp.user_data = this;
+ signal_protocol_store_context_set_pre_key_store(m_pStore, &sp);
+
+ signal_protocol_sender_key_store sk;
+ sk.destroy_func = destroy_func;
+ sk.load_sender_key = load_sender_key;
+ sk.store_sender_key = store_sender_key;
+ sk.user_data = this;
+ signal_protocol_store_context_set_sender_key_store(m_pStore, &sk);
+
+ signal_protocol_signed_pre_key_store ssp;
+ ssp.contains_signed_pre_key = &contains_signed_pre_key;
+ ssp.destroy_func = &destroy_func;
+ ssp.load_signed_pre_key = &load_signed_pre_key;
+ ssp.remove_signed_pre_key = &remove_signed_pre_key;
+ ssp.store_signed_pre_key = &store_signed_pre_key;
+ ssp.user_data = this;
+ signal_protocol_store_context_set_signed_pre_key_store(m_pStore, &ssp);
+
+ signal_protocol_identity_key_store sip;
+ sip.destroy_func = &destroy_func;
+ sip.get_identity_key_pair = &get_identity_key_pair;
+ sip.get_local_registration_id = &get_local_registration_id;
+ sip.is_trusted_identity = &is_trusted_identity;
+ sip.save_identity = &save_identity;
+ sip.user_data = this;
+ signal_protocol_store_context_set_identity_key_store(m_pStore, &sip);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MSignalSession members
+
+MSignalSession::MSignalSession(const CMStringA &_1, int _2) :
+ szName(_1)
+{
+ address.name = szName.GetBuffer();
+ address.name_len = szName.GetLength();
+ address.device_id = _2;
+}
+
+MSignalSession::~MSignalSession()
+{
+ session_cipher_free(cipher);
+}
+
+bool MSignalSession::hasAddress(const char *name, size_t name_len) const
+{
+ if (address.name_len != name_len)
+ return false;
+ return memcmp(address.name, name, name_len) == 0;
+}
+
+CMStringA MSignalSession::getSetting() const
+{
+ return CMStringA(FORMAT, "SignalSession_%s_%d", szName.c_str(), getDeviceId());
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+MSignalSession* MSignalStore::createSession(const CMStringA &szName, int deviceId)
+{
+ signal_protocol_address tmp = {szName.c_str(), (unsigned)szName.GetLength(), deviceId};
+ auto *pSession = getSession(&tmp);
+ if (pSession == nullptr) {
+ pSession = new MSignalSession(szName, deviceId);
+ arSessions.insert(pSession);
+ }
+
+ if (pSession->cipher == nullptr)
+ logError(
+ session_cipher_create(&pSession->cipher, m_pStore, &pSession->address, m_pContext),
+ "session_cipher_create failure");
+
+ return pSession;
+}
+
+MSignalSession* MSignalStore::getSession(const signal_protocol_address *address)
+{
+ MSignalSession tmp(CMStringA(address->name, (int)address->name_len), address->device_id);
+ auto *pSession = arSessions.find(&tmp);
+ if (pSession == nullptr) {
+ MBinBuffer blob(pProto->getBlob(tmp.getSetting()));
+ if (blob.isEmpty())
+ return nullptr;
+
+ pSession = new MSignalSession(tmp);
+ pSession->sessionData.assign(blob.data(), blob.length());
+ arSessions.insert(pSession);
+ }
+
+ return pSession;
+}
+
+void MSignalStore::importPublicKey(ec_public_key **result, MBinBuffer &buf)
+{
+ buf.appendBefore("\x05", 1);
+ curve_decode_point(result, buf.data(), buf.length(), m_pContext);
+}
+
+void MSignalStore::injectSession(const char *szJid, const WANode *pNode, const WANode *pKey)
+{
+ WAJid jid(szJid);
+ auto *signedKey = pKey->getChild("skey");
+ auto *key = pKey->getChild("key");
+ auto *identity = pKey->getChild("identity");
+ auto *registration = pNode->getChild("registration");
+ if (!signedKey || !key || !identity || !registration) {
+ pProto->debugLogA("Bad key data for %s", jid.toString().c_str());
+ return;
+ }
+
+ signal_protocol_address address = {jid.user.c_str(), (unsigned)jid.user.GetLength(), jid.device};
+
+ session_builder *builder;
+ logError(
+ session_builder_create(&builder, m_pStore, &address, m_pContext),
+ "unable to create session cipher");
+
+ int regId = decodeBigEndian(registration->content);
+ int preKeyId = decodeBigEndian(key->getChild("id")->content);
+ int signedPreKeyId = decodeBigEndian(signedKey->getChild("id")->content);
+
+ ec_public_key *preKeyPub, *signedPreKeyPub, *identityKey;
+ importPublicKey(&preKeyPub, key->getChild("value")->content);
+ importPublicKey(&identityKey, identity->content);
+ importPublicKey(&signedPreKeyPub, signedKey->getChild("value")->content);
+
+ auto &sign = signedKey->getChild("signature")->content;
+
+ session_pre_key_bundle *bundle;
+ logError(
+ session_pre_key_bundle_create(&bundle, regId, jid.device, preKeyId, preKeyPub, signedPreKeyId, signedPreKeyPub, sign.data(), sign.length(), identityKey),
+ "unable to create pre key bundle");
+
+ logError(
+ session_builder_process_pre_key_bundle(builder, bundle),
+ "unable to process pre key bundle");
+
+ session_pre_key_bundle_destroy((signal_type_base*)bundle);
+ session_builder_free(builder);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+MBinBuffer MSignalStore::decryptSignalProto(const CMStringA &from, const char *pszType, const MBinBuffer &encrypted)
+{
+ WAJid jid(from);
+ auto *pSession = createSession(jid.user, jid.device);
+
+ signal_buffer *result = nullptr;
+ if (!mir_strcmp(pszType, "pkmsg")) {
+ pre_key_signal_message *pMsg;
+ logError(
+ pre_key_signal_message_deserialize(&pMsg, encrypted.data(), encrypted.length(), m_pContext),
+ "unable to deserialize prekey message");
+
+ logError(
+ session_cipher_decrypt_pre_key_signal_message(pSession->getCipher(), pMsg, this, &result),
+ "unable to decrypt prekey message");
+
+ pre_key_signal_message_destroy((signal_type_base*)pMsg);
+ }
+ else {
+ signal_message *pMsg;
+ logError(
+ signal_message_deserialize(&pMsg, encrypted.data(), encrypted.length(), m_pContext),
+ "unable to deserialize signal message");
+
+ logError(
+ session_cipher_decrypt_signal_message(pSession->getCipher(), pMsg, this, &result),
+ "unable to decrypt signal message");
+
+ signal_message_destroy((signal_type_base *)pMsg);
+ }
+
+ MBinBuffer res;
+ res.assign(result->data, result->len);
+ signal_buffer_free(result);
+ return res;
+}
+
+MBinBuffer MSignalStore::decryptGroupSignalProto(const CMStringA &group, const CMStringA &sender, const MBinBuffer &encrypted)
+{
+ WAJid jid(sender);
+ signal_protocol_sender_key_name senderKeyName;
+ senderKeyName.group_id = group.c_str();
+ senderKeyName.group_id_len = group.GetLength();
+ senderKeyName.sender.device_id = 0;
+ senderKeyName.sender.name = jid.user.c_str();
+ senderKeyName.sender.name_len = jid.user.GetLength();
+
+ group_cipher *cipher;
+ logError(
+ group_cipher_create(&cipher, m_pStore, &senderKeyName, m_pContext),
+ "unable to create group cipher");
+
+ sender_key_message *skmsg;
+ logError(
+ sender_key_message_deserialize(&skmsg, encrypted.data(), encrypted.length(), m_pContext),
+ "unable to deserialize skmsg");
+
+ signal_buffer *result = nullptr;
+ logError(
+ group_cipher_decrypt(cipher, skmsg, this, &result),
+ "unable to decrypt skmsg");
+
+ sender_key_message_destroy((signal_type_base *)skmsg);
+ group_cipher_free(cipher);
+
+ MBinBuffer res;
+ res.assign(result->data, result->len);
+ signal_buffer_free(result);
+ return res;
+}
+
+void MSignalStore::processSenderKeyMessage(const CMStringA &author, const Wa__Message__SenderKeyDistributionMessage *msg)
+{
+ WAJid jid(author);
+ signal_protocol_sender_key_name senderKeyName;
+ senderKeyName.group_id = msg->groupid;
+ senderKeyName.group_id_len = mir_strlen(msg->groupid);
+ senderKeyName.sender.device_id = 0;
+ senderKeyName.sender.name = jid.user.c_str();
+ senderKeyName.sender.name_len = jid.user.GetLength();
+
+ group_session_builder *builder;
+ logError(
+ group_session_builder_create(&builder, m_pStore, m_pContext),
+ "unable to create session builder");
+
+ sender_key_distribution_message *skmsg;
+ logError(
+ sender_key_distribution_message_deserialize(&skmsg, msg->axolotlsenderkeydistributionmessage.data, msg->axolotlsenderkeydistributionmessage.len, m_pContext),
+ "unable to decode skd message");
+
+ logError(
+ group_session_builder_process_session(builder, &senderKeyName, skmsg),
+ "unable to process skd message");
+
+ sender_key_distribution_message_destroy((signal_type_base *)skmsg);
+ group_session_builder_free(builder);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// encryption
+
+MBinBuffer MSignalStore::encryptSenderKey(const WAJid &to, const CMStringA &from, const MBinBuffer &buf, MBinBuffer &skmsgKey)
+{
+ signal_protocol_sender_key_name senderKeyName;
+ senderKeyName.group_id = to.user.c_str();
+ senderKeyName.group_id_len = to.user.GetLength();
+ senderKeyName.sender.device_id = 0;
+ senderKeyName.sender.name = from.c_str();
+ senderKeyName.sender.name_len = from.GetLength();
+
+ group_session_builder *builder;
+ logError(
+ group_session_builder_create(&builder, m_pStore, m_pContext),
+ "unable to create session builder");
+
+ sender_key_distribution_message *skmsg;
+ logError(
+ group_session_builder_create_session(builder, &skmsg, &senderKeyName),
+ "unable to create session");
+
+ group_cipher *cipher;
+ logError(
+ group_cipher_create(&cipher, m_pStore, &senderKeyName, m_pContext),
+ "unable to create group cipher");
+
+ ciphertext_message *encMessage;
+ logError(
+ group_cipher_encrypt(cipher, buf.data(), buf.length(), &encMessage),
+ "unable to encrypt group message");
+
+ MBinBuffer res;
+ auto *cipherText = ciphertext_message_get_serialized(encMessage);
+ res.assign(cipherText->data, cipherText->len);
+
+ auto *pKey = sender_key_distribution_message_get_signature_key(skmsg);
+ skmsgKey.assign(pKey->data, sizeof(pKey->data));
+
+ sender_key_distribution_message_destroy((signal_type_base*)skmsg);
+ group_cipher_free(cipher);
+ group_session_builder_free(builder);
+ return res;
+}
+
+MBinBuffer MSignalStore::encryptSignalProto(const WAJid &to, const MBinBuffer &buf, int &type)
+{
+ auto *pSession = createSession(to.user, to.device);
+
+ ciphertext_message *pEncrypted;
+ logError(
+ session_cipher_encrypt(pSession->getCipher(), buf.data(), buf.length(), &pEncrypted),
+ "unable to encrypt signal message");
+
+ type = ciphertext_message_get_type(pEncrypted);
+
+ MBinBuffer res;
+ auto *encBuf = ciphertext_message_get_serialized(pEncrypted);
+ res.assign(encBuf->data, encBuf->len);
+ SIGNAL_UNREF(pEncrypted);
+ return res;
+}
+
+MBinBuffer MSignalStore::encodeSignedIdentity(bool bIncludeSignatureKey)
+{
+ proto::ADVSignedDeviceIdentity identity(pProto->getBlob("WAAccount"));
+
+ if (!bIncludeSignatureKey)
+ proto::CleanBinary(identity->accountsignaturekey), identity->has_accountsignaturekey = false;
+
+ return proto::Serialize(identity);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// generate and save pre keys set
+
+void MSignalStore::generatePrekeys(int count)
+{
+ int iNextKeyId = pProto->getDword(DBKEY_PREKEY_NEXT_ID, 1);
+
+ CMStringA szSetting;
+ signal_protocol_key_helper_pre_key_list_node *keys_root;
+ signal_protocol_key_helper_generate_pre_keys(&keys_root, iNextKeyId, count, m_pContext);
+ for (auto *it = keys_root; it; it = signal_protocol_key_helper_key_list_next(it)) {
+ session_pre_key *pre_key = signal_protocol_key_helper_key_list_element(it);
+ uint32_t pre_key_id = session_pre_key_get_id(pre_key);
+
+ SignalBuffer buf(pre_key);
+ szSetting.Format("PreKey%d", pre_key_id);
+ db_set_blob(0, pProto->m_szModuleName, szSetting, buf.data(), buf.len());
+
+ ec_key_pair *pre_key_pair = session_pre_key_get_key_pair(pre_key);
+ auto *pPubKey = ec_key_pair_get_public(pre_key_pair);
+ szSetting.Format("PreKey%dPublic", pre_key_id);
+ db_set_blob(0, pProto->m_szModuleName, szSetting, pPubKey->data, sizeof(pPubKey->data));
+ }
+ signal_protocol_key_helper_key_list_free(keys_root);
+
+ pProto->setDword(DBKEY_PREKEY_NEXT_ID, iNextKeyId + count);
+}
diff --git a/protocols/WhatsApp/src/stdafx.cxx b/protocols/WhatsApp/src/stdafx.cxx
index 7942e82b45..5146fe9a0a 100644
--- a/protocols/WhatsApp/src/stdafx.cxx
+++ b/protocols/WhatsApp/src/stdafx.cxx
@@ -1,8 +1,8 @@
-/*
-
-WhatsApp plugin for Miranda NG
-Copyright © 2019-22 George Hazan
-
-*/
-
-#include "stdafx.h"
+/*
+
+WhatsApp plugin for Miranda NG
+Copyright © 2019-23 George Hazan
+
+*/
+
+#include "stdafx.h"
diff --git a/protocols/WhatsApp/src/stdafx.h b/protocols/WhatsApp/src/stdafx.h
index 945e385af2..3b1326e277 100644
--- a/protocols/WhatsApp/src/stdafx.h
+++ b/protocols/WhatsApp/src/stdafx.h
@@ -1,72 +1,72 @@
-/*
-
-WhatsApp plugin for Miranda NG
-Copyright © 2019-22 George Hazan
-
-*/
-
-#pragma once
-#pragma warning(disable:4996 4290 4200 4239 4324)
-
-#include <fcntl.h>
-#include <malloc.h>
-#include <io.h>
-#include <time.h>
-#include <windows.h>
-
-#include <list>
-#include <map>
-#include <memory>
-#include <string>
-
-#include <newpluginapi.h>
-#include <m_avatars.h>
-#include <m_chat_int.h>
-#include <m_clist.h>
-#include <m_contacts.h>
-#include <m_database.h>
-#include <m_history.h>
-#include <m_imgsrvc.h>
-#include <m_ignore.h>
-#include <m_json.h>
-#include <m_langpack.h>
-#include <m_message.h>
-#include <m_netlib.h>
-#include <m_options.h>
-#include <m_popup.h>
-#include <m_protocols.h>
-#include <m_protosvc.h>
-#include <m_protoint.h>
-#include <m_skin.h>
-#include <m_string.h>
-#include <statusmodes.h>
-#include <m_userinfo.h>
-#include <m_icolib.h>
-#include <m_utils.h>
-#include <m_xml.h>
-#include <m_hotkeys.h>
-#include <m_folders.h>
-#include <m_json.h>
-#include <m_gui.h>
-#include <m_messagestate.h>
-
-#include <openssl/evp.h>
-#include <openssl/hmac.h>
-#include <openssl/sha.h>
-#include <openssl/kdf.h>
-
-#include "../../libs/libqrencode/src/qrencode.h"
-#include "../../libs/zlib/src/zlib.h"
-
-#include "../../utils/mir_signal.h"
-
-#include "pmsg.proto.h"
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-#include "db.h"
-#include "utils.h"
-#include "proto.h"
-#include "resource.h"
-
-#pragma comment(lib, "libcrypto.lib")
+/*
+
+WhatsApp plugin for Miranda NG
+Copyright © 2019-23 George Hazan
+
+*/
+
+#pragma once
+#pragma warning(disable:4996 4290 4200 4239 4324)
+
+#include <fcntl.h>
+#include <malloc.h>
+#include <io.h>
+#include <time.h>
+#include <windows.h>
+
+#include <list>
+#include <map>
+#include <memory>
+#include <string>
+
+#include <newpluginapi.h>
+#include <m_avatars.h>
+#include <m_chat_int.h>
+#include <m_clist.h>
+#include <m_contacts.h>
+#include <m_database.h>
+#include <m_history.h>
+#include <m_imgsrvc.h>
+#include <m_ignore.h>
+#include <m_json.h>
+#include <m_langpack.h>
+#include <m_message.h>
+#include <m_netlib.h>
+#include <m_options.h>
+#include <m_popup.h>
+#include <m_protocols.h>
+#include <m_protosvc.h>
+#include <m_protoint.h>
+#include <m_skin.h>
+#include <m_string.h>
+#include <statusmodes.h>
+#include <m_userinfo.h>
+#include <m_icolib.h>
+#include <m_utils.h>
+#include <m_xml.h>
+#include <m_hotkeys.h>
+#include <m_folders.h>
+#include <m_json.h>
+#include <m_gui.h>
+#include <m_messagestate.h>
+
+#include <openssl/evp.h>
+#include <openssl/hmac.h>
+#include <openssl/sha.h>
+#include <openssl/kdf.h>
+
+#include "../../libs/libqrencode/src/qrencode.h"
+#include "../../libs/zlib/src/zlib.h"
+
+#include "../../utils/mir_signal.h"
+
+#include "pmsg.proto.h"
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+#include "db.h"
+#include "utils.h"
+#include "proto.h"
+#include "resource.h"
+
+#pragma comment(lib, "libcrypto.lib")
diff --git a/protocols/WhatsApp/src/utils.cpp b/protocols/WhatsApp/src/utils.cpp
index 05e9d2c3bc..1e2d39f553 100644
--- a/protocols/WhatsApp/src/utils.cpp
+++ b/protocols/WhatsApp/src/utils.cpp
@@ -1,589 +1,589 @@
-/*
-
-WhatsApp plugin for Miranda NG
-Copyright © 2019-22 George Hazan
-
-*/
-
-#include "stdafx.h"
-
-WAJid::WAJid(const char *pszUser, const char *pszServer, int iDevice, int iAgent) :
- user(pszUser ? pszUser : ""),
- server(pszServer ? pszServer : ""),
- device(iDevice),
- agent(iAgent)
-{}
-
-WAJid::WAJid(const char *pszJid, int _id)
-{
- if (pszJid == nullptr)
- pszJid = "";
-
- auto *tmp = NEWSTR_ALLOCA(pszJid);
- auto *p = strrchr(tmp, '@');
- if (p) {
- *p = 0;
- server = p + 1;
- }
-
- if (p = strrchr(tmp, ':')) {
- *p = 0;
- device = atoi(p + 1);
- }
- else device = _id;
-
- if (p = strrchr(tmp, '_')) {
- *p = 0;
- agent = atoi(p + 1);
- }
- else agent = 0;
-
- user = tmp;
-}
-
-bool WAJid::isUser() const
-{ return server == "s.whatsapp.net";
-}
-
-bool WAJid::isGroup() const
-{ return server == "g.us";
-}
-
-bool WAJid::isBroadcast() const
-{
- return server == "broadcast";
-}
-
-bool WAJid::isStatusBroadcast() const
-{
- return isBroadcast() && user == "status";
-}
-
-CMStringA WAJid::toString() const
-{
- CMStringA ret(user);
- if (agent > 0)
- ret.AppendFormat("_%d", agent);
- if (device > 0)
- ret.AppendFormat(":%d", device);
- ret.AppendFormat("@%s", server.c_str());
- return ret;
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-static uint8_t sttLtHashInfo[] = "WhatsApp Patch Integrity";
-
-void LT_HASH::add(const void *pData, size_t len)
-{
- uint16_t tmp[_countof(hash)];
- HKDF(EVP_sha256(), (BYTE *)"", 0, (BYTE*)pData, len, sttLtHashInfo, sizeof(sttLtHashInfo) - 1, (BYTE *)tmp, sizeof(tmp));
-
- for (int i = 0; i < _countof(hash); i++)
- hash[i] += tmp[i];
-}
-
-void LT_HASH::sub(const void *pData, size_t len)
-{
- uint16_t tmp[_countof(hash)];
- HKDF(EVP_sha256(), (BYTE *)"", 0, (BYTE *)pData, len, sttLtHashInfo, sizeof(sttLtHashInfo) - 1, (BYTE *)tmp, sizeof(tmp));
-
- for (int i = 0; i < _countof(hash); i++)
- hash[i] -= tmp[i];
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-WAUser* WhatsAppProto::FindUser(const char *szId)
-{
- if (szId == nullptr)
- return nullptr;
-
- mir_cslock lck(m_csUsers);
- auto *tmp = (WAUser *)_alloca(sizeof(WAUser));
- tmp->szId = (char*)szId;
- return m_arUsers.find(tmp);
-}
-
-WAUser* WhatsAppProto::AddUser(const char *szId, bool bTemporary)
-{
- auto *pUser = FindUser(szId);
- if (pUser != nullptr)
- return pUser;
-
- MCONTACT hContact = db_add_contact();
- Proto_AddToContact(hContact, m_szModuleName);
-
- pUser = new WAUser(hContact, mir_strdup(szId));
- pUser->bIsGroupChat = WAJid(szId).isGroup();
-
- if (pUser->bIsGroupChat) {
- setByte(hContact, "ChatRoom", 1);
- setString(hContact, "ChatRoomID", szId);
- }
- else {
- setString(hContact, DBKEY_ID, szId);
- if (m_wszDefaultGroup)
- Clist_SetGroup(hContact, m_wszDefaultGroup);
- }
-
- if (bTemporary)
- Contact::RemoveFromList(hContact);
-
- mir_cslock lck(m_csUsers);
- m_arUsers.insert(pUser);
- return pUser;
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-WA_PKT_HANDLER WhatsAppProto::FindPersistentHandler(const WANode &pNode)
-{
- auto *pChild = pNode.getFirstChild();
- CMStringA szChild = (pChild) ? pChild->title : "";
- CMStringA szTitle = pNode.title;
- CMStringA szType = pNode.getAttr("type");
- CMStringA szXmlns = pNode.getAttr("xmlns");
-
- for (auto &it : m_arPersistent) {
- if (it->pszTitle && szTitle != it->pszTitle)
- continue;
- if (it->pszType && szType != it->pszType)
- continue;
- if (it->pszXmlns && szXmlns != it->pszXmlns)
- continue;
- if (it->pszChild && szChild != it->pszChild)
- continue;
- return it->pHandler;
- }
-
- return nullptr;
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-CMStringA WhatsAppProto::GenerateMessageId()
-{
- return CMStringA(FORMAT, "%d.%d-%d", m_wMsgPrefix[0], m_wMsgPrefix[1], m_iPacketId++);
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-// sends a piece of JSON to a server via a websocket, masked
-
-int WhatsAppProto::WSSend(const ProtobufCMessage &msg)
-{
- if (m_hServerConn == nullptr)
- return -1;
-
- MBinBuffer buf(proto::Serialize(&msg));
- Netlib_Dump(m_hServerConn, buf.data(), buf.length(), true, 0);
-
- MBinBuffer payload = m_noise->encodeFrame(buf.data(), buf.length());
- WebSocket_SendBinary(m_hServerConn, payload.data(), payload.length());
- return 0;
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-int WhatsAppProto::WSSendNode(WANode &node)
-{
- if (m_hServerConn == nullptr)
- return 0;
-
- CMStringA szText;
- node.print(szText);
- debugLogA("Sending binary node:\n%s", szText.c_str());
-
- WAWriter writer;
- writer.writeNode(&node);
-
- MBinBuffer encData = m_noise->encrypt(writer.body.data(), writer.body.length());
- MBinBuffer payload = m_noise->encodeFrame(encData.data(), encData.length());
- WebSocket_SendBinary(m_hServerConn, payload.data(), payload.length());
- return 1;
-}
-
-int WhatsAppProto::WSSendNode(WANode &node, WA_PKT_HANDLER pHandler)
-{
- if (m_hServerConn == nullptr)
- return 0;
-
- CMStringA id(GenerateMessageId());
- node.addAttr("id", id);
- {
- mir_cslock lck(m_csPacketQueue);
- m_arPacketQueue.insert(new WARequestSimple(id, pHandler));
- }
-
- return WSSendNode(node);
-}
-
-int WhatsAppProto::WSSendNode(WANode &node, WA_PKT_HANDLER_FULL pHandler, void *pUserInfo)
-{
- if (m_hServerConn == nullptr)
- return 0;
-
- CMStringA id(GenerateMessageId());
- node.addAttr("id", id);
- {
- mir_cslock lck(m_csPacketQueue);
- m_arPacketQueue.insert(new WARequestParam(id, pHandler, pUserInfo));
- }
-
- return WSSendNode(node);
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-std::string decodeBinStr(const std::string &buf)
-{
- size_t cbLen;
- void *pData = mir_base64_decode(buf.c_str(), &cbLen);
- if (pData == nullptr)
- return "";
-
- std::string res((char *)pData, cbLen);
- mir_free(pData);
- return res;
-}
-
-MBinBuffer decodeBufStr(const std::string &buf)
-{
- MBinBuffer res;
- size_t cbLen;
- void *pData = mir_base64_decode(buf.c_str(), &cbLen);
- if (pData == nullptr)
- return res;
-
- res.assign(pData, cbLen);
- mir_free(pData);
- return res;
-}
-
-uint32_t decodeBigEndian(const uint8_t *buf, size_t len)
-{
- uint32_t ret = 0;
- for (int i = 0; i < len; i++) {
- ret <<= 8;
- ret += buf[i];
- }
-
- return ret;
-}
-
-std::string encodeBigEndian(uint32_t num, size_t len)
-{
- std::string res;
- for (int i = 0; i < len; i++) {
- char c = num & 0xFF;
- res = c + res;
- num >>= 8;
- }
- return res;
-}
-
-void generateIV(uint8_t *iv, uint32_t &pVar)
-{
- auto counter = encodeBigEndian(pVar);
- memset(iv, 0, 8);
- memcpy(iv + 8, counter.c_str(), sizeof(uint32_t));
-
- pVar++;
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-// Padding
-
-void padBuffer16(MBinBuffer &buf)
-{
- uint8_t c = 16 - buf.length() % 16;
-
- for (uint8_t i = 0; i < c; i++)
- buf.append(&c, 1);
-}
-
-MBinBuffer unpadBuffer16(const MBinBuffer &buf)
-{
- size_t len = buf.length();
- auto p = buf.data() + len - 1;
- if (*p <= 0x10) {
- MBinBuffer res;
- res.assign(buf.data(), len - *p);
- return res;
- }
-
- return buf;
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-// Popups
-
-void WhatsAppProto::InitPopups(void)
-{
- g_plugin.addPopupOption(CMStringW(FORMAT, TranslateT("%s error notifications"), m_tszUserName), m_bUsePopups);
-
- char name[256];
- mir_snprintf(name, "%s_%s", m_szModuleName, "Error");
-
- wchar_t desc[256];
- mir_snwprintf(desc, L"%s/%s", m_tszUserName, TranslateT("Errors"));
-
- POPUPCLASS ppc = {};
- ppc.flags = PCF_UNICODE;
- ppc.pszName = name;
- ppc.pszDescription.w = desc;
- ppc.hIcon = IcoLib_GetIconByHandle(m_hProtoIcon);
- ppc.colorBack = RGB(191, 0, 0); //Red
- ppc.colorText = RGB(255, 245, 225); //Yellow
- ppc.iSeconds = 60;
- m_hPopupClass = Popup_RegisterClass(&ppc);
-
- IcoLib_ReleaseIcon(ppc.hIcon);
-}
-
-void WhatsAppProto::Popup(MCONTACT hContact, const wchar_t *szMsg, const wchar_t *szTitle)
-{
- if (!m_bUsePopups)
- return;
-
- char name[256];
- mir_snprintf(name, "%s_%s", m_szModuleName, "Error");
-
- CMStringW wszTitle(szTitle);
- if (hContact == 0) {
- wszTitle.Insert(0, L": ");
- wszTitle.Insert(0, m_tszUserName);
- }
-
- POPUPDATACLASS ppd = {};
- ppd.szTitle.w = wszTitle;
- ppd.szText.w = szMsg;
- ppd.pszClassName = name;
- ppd.hContact = hContact;
- Popup_AddClass(&ppd);
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-MBinBuffer WhatsAppProto::unzip(const MBinBuffer &src)
-{
- z_stream strm = {};
- inflateInit(&strm);
-
- strm.avail_in = (uInt)src.length();
- strm.next_in = (Bytef *)src.data();
-
- MBinBuffer res;
- Bytef buf[2048];
-
- while (strm.avail_in > 0) {
- strm.avail_out = sizeof(buf);
- strm.next_out = buf;
-
- int ret = inflate(&strm, Z_NO_FLUSH);
- switch (ret) {
- case Z_NEED_DICT:
- ret = Z_DATA_ERROR;
- __fallthrough;
-
- case Z_DATA_ERROR:
- case Z_MEM_ERROR:
- inflateEnd(&strm);
- return res;
- }
-
- res.append(buf, sizeof(buf) - strm.avail_out);
- if (ret == Z_STREAM_END)
- break;
- }
-
- inflateEnd(&strm);
- return res;
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-void bin2file(const MBinBuffer &buf, const wchar_t *pwszFileName)
-{
- int fileId = _wopen(pwszFileName, _O_WRONLY | _O_TRUNC | _O_BINARY | _O_CREAT, _S_IREAD | _S_IWRITE);
- if (fileId != -1) {
- write(fileId, buf.data(), (unsigned)buf.length());
- close(fileId);
- }
-}
-
-void string2file(const std::string &str, const wchar_t *pwszFileName)
-{
- int fileId = _wopen(pwszFileName, _O_WRONLY | _O_TRUNC | _O_BINARY | _O_CREAT, _S_IREAD | _S_IWRITE);
- if (fileId != -1) {
- write(fileId, str.c_str(), (unsigned)str.size());
- close(fileId);
- }
-}
-
-CMStringA file2string(const wchar_t *pwszFileName)
-{
- CMStringA res;
-
- int fileId = _wopen(pwszFileName, _O_RDONLY | _O_BINARY, _S_IREAD | _S_IWRITE);
- if (fileId != -1) {
- res.Truncate(filelength(fileId));
- read(fileId, res.GetBuffer(), res.GetLength());
- close(fileId);
- }
- return res;
-}
-
-void WhatsAppProto::GetMessageContent(
- CMStringA &txt,
- const char *szType,
- const char *szMimeType,
- const char *szUrl,
- const char *szDirectPath,
- const ProtobufCBinaryData &pMediaKey,
- const char *szCaption)
-{
- if (szCaption) {
- if (m_bUseBbcodes)
- txt.Append("[b]");
- txt.Append(szCaption);
- if (m_bUseBbcodes)
- txt.Append("[/b]");
- txt.Append("\n");
- }
-
- CMStringA url = szUrl;
- int idx = url.ReverseFind('/');
- if (idx != -1)
- url.Delete(0, idx+1);
- idx = url.ReverseFind('.');
- if (idx != -1)
- url.Truncate(idx);
- if (szMimeType)
- url.Append(_T2A(ProtoGetAvatarExtension(ProtoGetAvatarFormatByMimeType(szMimeType))));
-
- char *szMediaType = NEWSTR_ALLOCA(szType);
- szMediaType[0] = toupper(szMediaType[0]);
-
- MBinBuffer buf = DownloadEncryptedFile(directPath2url(szDirectPath), pMediaKey, szMediaType);
- if (!buf.isEmpty()) {
- CMStringW pwszFileName(GetTmpFileName(szType, url));
- bin2file(buf, pwszFileName);
-
- pwszFileName.Replace(L"\\", L"/");
- txt.AppendFormat("file://%s", T2Utf(pwszFileName).get());
- }
-}
-
-CMStringA WhatsAppProto::GetMessageText(const Wa__Message *pMessage)
-{
- CMStringA szMessageText;
-
- if (pMessage) {
- if (auto *pExt = pMessage->extendedtextmessage) {
- if (pExt->title) {
- if (m_bUseBbcodes)
- szMessageText.Append("[b]");
- szMessageText.Append(pExt->title);
- if (m_bUseBbcodes)
- szMessageText.Append("[/b]");
- szMessageText.Append("\n");
- }
-
- if (pExt->contextinfo && pExt->contextinfo->quotedmessage)
- szMessageText.AppendFormat("> %s\n\n", pExt->contextinfo->quotedmessage->conversation);
-
- if (pExt->text)
- szMessageText.Append(pExt->text);
- }
- else if (auto *pAudio = pMessage->audiomessage) {
- GetMessageContent(szMessageText, "audio", pAudio->url, pAudio->directpath, pAudio->mimetype, pAudio->mediakey);
- }
- else if (auto *pVideo = pMessage->videomessage) {
- GetMessageContent(szMessageText, "video", pVideo->url, pVideo->directpath, pVideo->mimetype, pVideo->mediakey, pVideo->caption);
- }
- else if (auto *pImage = pMessage->imagemessage) {
- GetMessageContent(szMessageText, "image", pImage->url, pImage->directpath, pImage->mimetype, pImage->mediakey, pImage->caption);
- }
- else if (mir_strlen(pMessage->conversation))
- szMessageText = pMessage->conversation;
- }
-
- return szMessageText;
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-void proto::CleanBinary(ProtobufCBinaryData &field)
-{
- if (field.data) {
- free(field.data);
- field.data = nullptr;
- }
- field.len = 0;
-}
-
-ProtobufCBinaryData proto::SetBinary(const void *pData, size_t len)
-{
- ProtobufCBinaryData res;
- if (pData == nullptr) {
- res.data = nullptr;
- res.len = 0;
- }
- else {
- res.data = (uint8_t *)malloc(res.len = len);
- memcpy(res.data, pData, len);
- }
- return res;
-}
-
-MBinBuffer proto::Serialize(const ProtobufCMessage *msg)
-{
- MBinBuffer res(protobuf_c_message_get_packed_size(msg));
- protobuf_c_message_pack(msg, res.data());
- return res;
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-CMStringA directPath2url(const char *pszDirectPath)
-{
- return CMStringA("https://mmg.whatsapp.net") + pszDirectPath;
-}
-
-WAMediaKeys::WAMediaKeys(const uint8_t *pKey, size_t keyLen, const char *pszMediaType)
-{
- CMStringA pszHkdfString(FORMAT, "WhatsApp %s Keys", pszMediaType);
-
- HKDF(EVP_sha256(), (BYTE *)"", 0, pKey, (int)keyLen, (BYTE *)pszHkdfString.c_str(), pszHkdfString.GetLength(), (BYTE *)this, sizeof(*this));
-}
-
-MBinBuffer WhatsAppProto::DownloadEncryptedFile(const char *url, const ProtobufCBinaryData &mediaKeys, const char *pszMediaType)
-{
- NETLIBHTTPHEADER headers[1] = {{"Origin", "https://web.whatsapp.com"}};
-
- NETLIBHTTPREQUEST req = {};
- req.cbSize = sizeof(req);
- req.requestType = REQUEST_GET;
- req.szUrl = (char*)url;
- req.headersCount = _countof(headers);
- req.headers = headers;
-
- MBinBuffer ret;
- auto *pResp = Netlib_HttpTransaction(m_hNetlibUser, &req);
- if (pResp) {
- if (pResp->resultCode == 200) {
- WAMediaKeys out(mediaKeys.data, mediaKeys.len, pszMediaType);
- ret = aesDecrypt(EVP_aes_256_cbc(), out.cipherKey, out.iv, pResp->pData, pResp->dataLength);
- }
- }
-
- return ret;
-}
-
-CMStringW WhatsAppProto::GetTmpFileName(const char *pszClass, const char *pszAddition)
-{
- CMStringW ret(VARSW(L"%miranda_userdata%"));
- ret.AppendFormat(L"/%S/%S_%S", m_szModuleName, pszClass, pszAddition);
- return ret;
-}
+/*
+
+WhatsApp plugin for Miranda NG
+Copyright © 2019-23 George Hazan
+
+*/
+
+#include "stdafx.h"
+
+WAJid::WAJid(const char *pszUser, const char *pszServer, int iDevice, int iAgent) :
+ user(pszUser ? pszUser : ""),
+ server(pszServer ? pszServer : ""),
+ device(iDevice),
+ agent(iAgent)
+{}
+
+WAJid::WAJid(const char *pszJid, int _id)
+{
+ if (pszJid == nullptr)
+ pszJid = "";
+
+ auto *tmp = NEWSTR_ALLOCA(pszJid);
+ auto *p = strrchr(tmp, '@');
+ if (p) {
+ *p = 0;
+ server = p + 1;
+ }
+
+ if (p = strrchr(tmp, ':')) {
+ *p = 0;
+ device = atoi(p + 1);
+ }
+ else device = _id;
+
+ if (p = strrchr(tmp, '_')) {
+ *p = 0;
+ agent = atoi(p + 1);
+ }
+ else agent = 0;
+
+ user = tmp;
+}
+
+bool WAJid::isUser() const
+{ return server == "s.whatsapp.net";
+}
+
+bool WAJid::isGroup() const
+{ return server == "g.us";
+}
+
+bool WAJid::isBroadcast() const
+{
+ return server == "broadcast";
+}
+
+bool WAJid::isStatusBroadcast() const
+{
+ return isBroadcast() && user == "status";
+}
+
+CMStringA WAJid::toString() const
+{
+ CMStringA ret(user);
+ if (agent > 0)
+ ret.AppendFormat("_%d", agent);
+ if (device > 0)
+ ret.AppendFormat(":%d", device);
+ ret.AppendFormat("@%s", server.c_str());
+ return ret;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static uint8_t sttLtHashInfo[] = "WhatsApp Patch Integrity";
+
+void LT_HASH::add(const void *pData, size_t len)
+{
+ uint16_t tmp[_countof(hash)];
+ HKDF(EVP_sha256(), (BYTE *)"", 0, (BYTE*)pData, len, sttLtHashInfo, sizeof(sttLtHashInfo) - 1, (BYTE *)tmp, sizeof(tmp));
+
+ for (int i = 0; i < _countof(hash); i++)
+ hash[i] += tmp[i];
+}
+
+void LT_HASH::sub(const void *pData, size_t len)
+{
+ uint16_t tmp[_countof(hash)];
+ HKDF(EVP_sha256(), (BYTE *)"", 0, (BYTE *)pData, len, sttLtHashInfo, sizeof(sttLtHashInfo) - 1, (BYTE *)tmp, sizeof(tmp));
+
+ for (int i = 0; i < _countof(hash); i++)
+ hash[i] -= tmp[i];
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+WAUser* WhatsAppProto::FindUser(const char *szId)
+{
+ if (szId == nullptr)
+ return nullptr;
+
+ mir_cslock lck(m_csUsers);
+ auto *tmp = (WAUser *)_alloca(sizeof(WAUser));
+ tmp->szId = (char*)szId;
+ return m_arUsers.find(tmp);
+}
+
+WAUser* WhatsAppProto::AddUser(const char *szId, bool bTemporary)
+{
+ auto *pUser = FindUser(szId);
+ if (pUser != nullptr)
+ return pUser;
+
+ MCONTACT hContact = db_add_contact();
+ Proto_AddToContact(hContact, m_szModuleName);
+
+ pUser = new WAUser(hContact, mir_strdup(szId));
+ pUser->bIsGroupChat = WAJid(szId).isGroup();
+
+ if (pUser->bIsGroupChat) {
+ setByte(hContact, "ChatRoom", 1);
+ setString(hContact, "ChatRoomID", szId);
+ }
+ else {
+ setString(hContact, DBKEY_ID, szId);
+ if (m_wszDefaultGroup)
+ Clist_SetGroup(hContact, m_wszDefaultGroup);
+ }
+
+ if (bTemporary)
+ Contact::RemoveFromList(hContact);
+
+ mir_cslock lck(m_csUsers);
+ m_arUsers.insert(pUser);
+ return pUser;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+WA_PKT_HANDLER WhatsAppProto::FindPersistentHandler(const WANode &pNode)
+{
+ auto *pChild = pNode.getFirstChild();
+ CMStringA szChild = (pChild) ? pChild->title : "";
+ CMStringA szTitle = pNode.title;
+ CMStringA szType = pNode.getAttr("type");
+ CMStringA szXmlns = pNode.getAttr("xmlns");
+
+ for (auto &it : m_arPersistent) {
+ if (it->pszTitle && szTitle != it->pszTitle)
+ continue;
+ if (it->pszType && szType != it->pszType)
+ continue;
+ if (it->pszXmlns && szXmlns != it->pszXmlns)
+ continue;
+ if (it->pszChild && szChild != it->pszChild)
+ continue;
+ return it->pHandler;
+ }
+
+ return nullptr;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+CMStringA WhatsAppProto::GenerateMessageId()
+{
+ return CMStringA(FORMAT, "%d.%d-%d", m_wMsgPrefix[0], m_wMsgPrefix[1], m_iPacketId++);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// sends a piece of JSON to a server via a websocket, masked
+
+int WhatsAppProto::WSSend(const ProtobufCMessage &msg)
+{
+ if (m_hServerConn == nullptr)
+ return -1;
+
+ MBinBuffer buf(proto::Serialize(&msg));
+ Netlib_Dump(m_hServerConn, buf.data(), buf.length(), true, 0);
+
+ MBinBuffer payload = m_noise->encodeFrame(buf.data(), buf.length());
+ WebSocket_SendBinary(m_hServerConn, payload.data(), payload.length());
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+int WhatsAppProto::WSSendNode(WANode &node)
+{
+ if (m_hServerConn == nullptr)
+ return 0;
+
+ CMStringA szText;
+ node.print(szText);
+ debugLogA("Sending binary node:\n%s", szText.c_str());
+
+ WAWriter writer;
+ writer.writeNode(&node);
+
+ MBinBuffer encData = m_noise->encrypt(writer.body.data(), writer.body.length());
+ MBinBuffer payload = m_noise->encodeFrame(encData.data(), encData.length());
+ WebSocket_SendBinary(m_hServerConn, payload.data(), payload.length());
+ return 1;
+}
+
+int WhatsAppProto::WSSendNode(WANode &node, WA_PKT_HANDLER pHandler)
+{
+ if (m_hServerConn == nullptr)
+ return 0;
+
+ CMStringA id(GenerateMessageId());
+ node.addAttr("id", id);
+ {
+ mir_cslock lck(m_csPacketQueue);
+ m_arPacketQueue.insert(new WARequestSimple(id, pHandler));
+ }
+
+ return WSSendNode(node);
+}
+
+int WhatsAppProto::WSSendNode(WANode &node, WA_PKT_HANDLER_FULL pHandler, void *pUserInfo)
+{
+ if (m_hServerConn == nullptr)
+ return 0;
+
+ CMStringA id(GenerateMessageId());
+ node.addAttr("id", id);
+ {
+ mir_cslock lck(m_csPacketQueue);
+ m_arPacketQueue.insert(new WARequestParam(id, pHandler, pUserInfo));
+ }
+
+ return WSSendNode(node);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+std::string decodeBinStr(const std::string &buf)
+{
+ size_t cbLen;
+ void *pData = mir_base64_decode(buf.c_str(), &cbLen);
+ if (pData == nullptr)
+ return "";
+
+ std::string res((char *)pData, cbLen);
+ mir_free(pData);
+ return res;
+}
+
+MBinBuffer decodeBufStr(const std::string &buf)
+{
+ MBinBuffer res;
+ size_t cbLen;
+ void *pData = mir_base64_decode(buf.c_str(), &cbLen);
+ if (pData == nullptr)
+ return res;
+
+ res.assign(pData, cbLen);
+ mir_free(pData);
+ return res;
+}
+
+uint32_t decodeBigEndian(const uint8_t *buf, size_t len)
+{
+ uint32_t ret = 0;
+ for (int i = 0; i < len; i++) {
+ ret <<= 8;
+ ret += buf[i];
+ }
+
+ return ret;
+}
+
+std::string encodeBigEndian(uint32_t num, size_t len)
+{
+ std::string res;
+ for (int i = 0; i < len; i++) {
+ char c = num & 0xFF;
+ res = c + res;
+ num >>= 8;
+ }
+ return res;
+}
+
+void generateIV(uint8_t *iv, uint32_t &pVar)
+{
+ auto counter = encodeBigEndian(pVar);
+ memset(iv, 0, 8);
+ memcpy(iv + 8, counter.c_str(), sizeof(uint32_t));
+
+ pVar++;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Padding
+
+void padBuffer16(MBinBuffer &buf)
+{
+ uint8_t c = 16 - buf.length() % 16;
+
+ for (uint8_t i = 0; i < c; i++)
+ buf.append(&c, 1);
+}
+
+MBinBuffer unpadBuffer16(const MBinBuffer &buf)
+{
+ size_t len = buf.length();
+ auto p = buf.data() + len - 1;
+ if (*p <= 0x10) {
+ MBinBuffer res;
+ res.assign(buf.data(), len - *p);
+ return res;
+ }
+
+ return buf;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Popups
+
+void WhatsAppProto::InitPopups(void)
+{
+ g_plugin.addPopupOption(CMStringW(FORMAT, TranslateT("%s error notifications"), m_tszUserName), m_bUsePopups);
+
+ char name[256];
+ mir_snprintf(name, "%s_%s", m_szModuleName, "Error");
+
+ wchar_t desc[256];
+ mir_snwprintf(desc, L"%s/%s", m_tszUserName, TranslateT("Errors"));
+
+ POPUPCLASS ppc = {};
+ ppc.flags = PCF_UNICODE;
+ ppc.pszName = name;
+ ppc.pszDescription.w = desc;
+ ppc.hIcon = IcoLib_GetIconByHandle(m_hProtoIcon);
+ ppc.colorBack = RGB(191, 0, 0); //Red
+ ppc.colorText = RGB(255, 245, 225); //Yellow
+ ppc.iSeconds = 60;
+ m_hPopupClass = Popup_RegisterClass(&ppc);
+
+ IcoLib_ReleaseIcon(ppc.hIcon);
+}
+
+void WhatsAppProto::Popup(MCONTACT hContact, const wchar_t *szMsg, const wchar_t *szTitle)
+{
+ if (!m_bUsePopups)
+ return;
+
+ char name[256];
+ mir_snprintf(name, "%s_%s", m_szModuleName, "Error");
+
+ CMStringW wszTitle(szTitle);
+ if (hContact == 0) {
+ wszTitle.Insert(0, L": ");
+ wszTitle.Insert(0, m_tszUserName);
+ }
+
+ POPUPDATACLASS ppd = {};
+ ppd.szTitle.w = wszTitle;
+ ppd.szText.w = szMsg;
+ ppd.pszClassName = name;
+ ppd.hContact = hContact;
+ Popup_AddClass(&ppd);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+MBinBuffer WhatsAppProto::unzip(const MBinBuffer &src)
+{
+ z_stream strm = {};
+ inflateInit(&strm);
+
+ strm.avail_in = (uInt)src.length();
+ strm.next_in = (Bytef *)src.data();
+
+ MBinBuffer res;
+ Bytef buf[2048];
+
+ while (strm.avail_in > 0) {
+ strm.avail_out = sizeof(buf);
+ strm.next_out = buf;
+
+ int ret = inflate(&strm, Z_NO_FLUSH);
+ switch (ret) {
+ case Z_NEED_DICT:
+ ret = Z_DATA_ERROR;
+ __fallthrough;
+
+ case Z_DATA_ERROR:
+ case Z_MEM_ERROR:
+ inflateEnd(&strm);
+ return res;
+ }
+
+ res.append(buf, sizeof(buf) - strm.avail_out);
+ if (ret == Z_STREAM_END)
+ break;
+ }
+
+ inflateEnd(&strm);
+ return res;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void bin2file(const MBinBuffer &buf, const wchar_t *pwszFileName)
+{
+ int fileId = _wopen(pwszFileName, _O_WRONLY | _O_TRUNC | _O_BINARY | _O_CREAT, _S_IREAD | _S_IWRITE);
+ if (fileId != -1) {
+ write(fileId, buf.data(), (unsigned)buf.length());
+ close(fileId);
+ }
+}
+
+void string2file(const std::string &str, const wchar_t *pwszFileName)
+{
+ int fileId = _wopen(pwszFileName, _O_WRONLY | _O_TRUNC | _O_BINARY | _O_CREAT, _S_IREAD | _S_IWRITE);
+ if (fileId != -1) {
+ write(fileId, str.c_str(), (unsigned)str.size());
+ close(fileId);
+ }
+}
+
+CMStringA file2string(const wchar_t *pwszFileName)
+{
+ CMStringA res;
+
+ int fileId = _wopen(pwszFileName, _O_RDONLY | _O_BINARY, _S_IREAD | _S_IWRITE);
+ if (fileId != -1) {
+ res.Truncate(filelength(fileId));
+ read(fileId, res.GetBuffer(), res.GetLength());
+ close(fileId);
+ }
+ return res;
+}
+
+void WhatsAppProto::GetMessageContent(
+ CMStringA &txt,
+ const char *szType,
+ const char *szMimeType,
+ const char *szUrl,
+ const char *szDirectPath,
+ const ProtobufCBinaryData &pMediaKey,
+ const char *szCaption)
+{
+ if (szCaption) {
+ if (m_bUseBbcodes)
+ txt.Append("[b]");
+ txt.Append(szCaption);
+ if (m_bUseBbcodes)
+ txt.Append("[/b]");
+ txt.Append("\n");
+ }
+
+ CMStringA url = szUrl;
+ int idx = url.ReverseFind('/');
+ if (idx != -1)
+ url.Delete(0, idx+1);
+ idx = url.ReverseFind('.');
+ if (idx != -1)
+ url.Truncate(idx);
+ if (szMimeType)
+ url.Append(_T2A(ProtoGetAvatarExtension(ProtoGetAvatarFormatByMimeType(szMimeType))));
+
+ char *szMediaType = NEWSTR_ALLOCA(szType);
+ szMediaType[0] = toupper(szMediaType[0]);
+
+ MBinBuffer buf = DownloadEncryptedFile(directPath2url(szDirectPath), pMediaKey, szMediaType);
+ if (!buf.isEmpty()) {
+ CMStringW pwszFileName(GetTmpFileName(szType, url));
+ bin2file(buf, pwszFileName);
+
+ pwszFileName.Replace(L"\\", L"/");
+ txt.AppendFormat("file://%s", T2Utf(pwszFileName).get());
+ }
+}
+
+CMStringA WhatsAppProto::GetMessageText(const Wa__Message *pMessage)
+{
+ CMStringA szMessageText;
+
+ if (pMessage) {
+ if (auto *pExt = pMessage->extendedtextmessage) {
+ if (pExt->title) {
+ if (m_bUseBbcodes)
+ szMessageText.Append("[b]");
+ szMessageText.Append(pExt->title);
+ if (m_bUseBbcodes)
+ szMessageText.Append("[/b]");
+ szMessageText.Append("\n");
+ }
+
+ if (pExt->contextinfo && pExt->contextinfo->quotedmessage)
+ szMessageText.AppendFormat("> %s\n\n", pExt->contextinfo->quotedmessage->conversation);
+
+ if (pExt->text)
+ szMessageText.Append(pExt->text);
+ }
+ else if (auto *pAudio = pMessage->audiomessage) {
+ GetMessageContent(szMessageText, "audio", pAudio->url, pAudio->directpath, pAudio->mimetype, pAudio->mediakey);
+ }
+ else if (auto *pVideo = pMessage->videomessage) {
+ GetMessageContent(szMessageText, "video", pVideo->url, pVideo->directpath, pVideo->mimetype, pVideo->mediakey, pVideo->caption);
+ }
+ else if (auto *pImage = pMessage->imagemessage) {
+ GetMessageContent(szMessageText, "image", pImage->url, pImage->directpath, pImage->mimetype, pImage->mediakey, pImage->caption);
+ }
+ else if (mir_strlen(pMessage->conversation))
+ szMessageText = pMessage->conversation;
+ }
+
+ return szMessageText;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void proto::CleanBinary(ProtobufCBinaryData &field)
+{
+ if (field.data) {
+ free(field.data);
+ field.data = nullptr;
+ }
+ field.len = 0;
+}
+
+ProtobufCBinaryData proto::SetBinary(const void *pData, size_t len)
+{
+ ProtobufCBinaryData res;
+ if (pData == nullptr) {
+ res.data = nullptr;
+ res.len = 0;
+ }
+ else {
+ res.data = (uint8_t *)malloc(res.len = len);
+ memcpy(res.data, pData, len);
+ }
+ return res;
+}
+
+MBinBuffer proto::Serialize(const ProtobufCMessage *msg)
+{
+ MBinBuffer res(protobuf_c_message_get_packed_size(msg));
+ protobuf_c_message_pack(msg, res.data());
+ return res;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+CMStringA directPath2url(const char *pszDirectPath)
+{
+ return CMStringA("https://mmg.whatsapp.net") + pszDirectPath;
+}
+
+WAMediaKeys::WAMediaKeys(const uint8_t *pKey, size_t keyLen, const char *pszMediaType)
+{
+ CMStringA pszHkdfString(FORMAT, "WhatsApp %s Keys", pszMediaType);
+
+ HKDF(EVP_sha256(), (BYTE *)"", 0, pKey, (int)keyLen, (BYTE *)pszHkdfString.c_str(), pszHkdfString.GetLength(), (BYTE *)this, sizeof(*this));
+}
+
+MBinBuffer WhatsAppProto::DownloadEncryptedFile(const char *url, const ProtobufCBinaryData &mediaKeys, const char *pszMediaType)
+{
+ NETLIBHTTPHEADER headers[1] = {{"Origin", "https://web.whatsapp.com"}};
+
+ NETLIBHTTPREQUEST req = {};
+ req.cbSize = sizeof(req);
+ req.requestType = REQUEST_GET;
+ req.szUrl = (char*)url;
+ req.headersCount = _countof(headers);
+ req.headers = headers;
+
+ MBinBuffer ret;
+ auto *pResp = Netlib_HttpTransaction(m_hNetlibUser, &req);
+ if (pResp) {
+ if (pResp->resultCode == 200) {
+ WAMediaKeys out(mediaKeys.data, mediaKeys.len, pszMediaType);
+ ret = aesDecrypt(EVP_aes_256_cbc(), out.cipherKey, out.iv, pResp->pData, pResp->dataLength);
+ }
+ }
+
+ return ret;
+}
+
+CMStringW WhatsAppProto::GetTmpFileName(const char *pszClass, const char *pszAddition)
+{
+ CMStringW ret(VARSW(L"%miranda_userdata%"));
+ ret.AppendFormat(L"/%S/%S_%S", m_szModuleName, pszClass, pszAddition);
+ return ret;
+}
diff --git a/protocols/WhatsApp/src/utils.h b/protocols/WhatsApp/src/utils.h
index 02882f1c17..af02695818 100644
--- a/protocols/WhatsApp/src/utils.h
+++ b/protocols/WhatsApp/src/utils.h
@@ -1,267 +1,267 @@
-/*
-
-WhatsApp plugin for Miranda NG
-Copyright © 2019-22 George Hazan
-
-*/
-
-#define DICT_VERSION 2
-
-#define LIST_EMPTY 0
-#define STREAM_END 2
-#define DICTIONARY_0 236
-#define DICTIONARY_1 237
-#define DICTIONARY_2 238
-#define DICTIONARY_3 239
-#define AD_JID 247
-#define LIST_8 248
-#define LIST_16 249
-#define JID_PAIR 250
-#define HEX_8 251
-#define BINARY_8 252
-#define BINARY_20 253
-#define BINARY_32 254
-#define NIBBLE_8 255
-
-class WANode // kinda XML
-{
- friend class WAReader;
- friend class WAWriter;
-
- WANode *pParent = nullptr;
- OBJLIST<struct Attr> attrs;
- OBJLIST<WANode> children;
-
-public:
- WANode();
- WANode(const char *pszTitle);
- ~WANode();
-
- void addAttr(const char *pszName, const char *pszValue);
- void addAttr(const char *pszName, int iValue);
- int getAttrInt(const char *pszName) const;
- const char *getAttr(const char *pszName) const;
-
- CMStringA getBody() const;
-
- WANode *addChild(const char *pszName);
- WANode *getChild(const char *pszName) const;
- WANode *getFirstChild(void) const;
- const OBJLIST<WANode> &getChildren(void) const
- { return children;
- }
-
- void print(CMStringA &dest, int level = 0) const;
-
- CMStringA title;
- MBinBuffer content;
-};
-
-__forceinline WANode &operator<<(WANode &node, const CHAR_PARAM &param)
-{
- node.addAttr(param.szName, param.szValue);
- return node;
-}
-
-__forceinline WANode &operator<<(WANode &node, const INT_PARAM &param)
-{
- node.addAttr(param.szName, param.iValue);
- return node;
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-namespace IQ
-{
- enum Type { GET, SET, RESULT };
-};
-
-struct WANodeIq : public WANode
-{
- WANodeIq(IQ::Type type, const char *pszXmlns = nullptr, const char *pszTo = nullptr);
-};
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-struct XCHILD
-{
- const char *name, *value;
-
- __forceinline XCHILD(const char *_name) :
- name(_name)
- {}
-};
-
-__forceinline WANode& operator<<(WANode &node, const XCHILD &child)
-{
- node.addChild(child.name);
- return node;
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-// WAReader
-
-class WAReader
-{
- const BYTE *m_buf, *m_limit;
-
- uint32_t readIntN(int i);
- CMStringA readStringFromChars(int size);
-
- bool readAttributes(WANode *node, int count);
- uint32_t readInt20();
- bool readList(WANode *pParent, int tag);
- int readListSize(int tag);
- CMStringA readPacked(int tag);
- CMStringA readString(int tag);
-
-public:
- WAReader(const void *buf, size_t cbLen) :
- m_buf((BYTE*)buf),
- m_limit((BYTE*)buf + cbLen)
- {}
-
- WANode* readNode();
-
- __forceinline uint32_t readInt8() { return readIntN(1); }
- __forceinline uint32_t readInt16() { return readIntN(2); }
- __forceinline uint32_t readInt32() { return readIntN(4); }
-};
-
-/////////////////////////////////////////////////////////////////////////////////////////
-// WAWriter
-
-class WAWriter
-{
- __forceinline void writeInt8(int value) { writeIntN(value, 1); }
- __forceinline void writeInt16(int value) { writeIntN(value, 2); }
- __forceinline void writeInt32(int value) { writeIntN(value, 4); }
-
- void writeByte(uint8_t b);
- void writeIntN(int value, int i);
- void writeInt20(int value);
- void writeLength(int value);
- void writeListSize(int tag);
- void writePacked(const CMStringA &str, int tag);
- void writeString(const char *str);
- bool writeToken(const char *str);
-
-public:
- void writeNode(const WANode *pNode);
-
- MBinBuffer body;
-};
-
-/////////////////////////////////////////////////////////////////////////////////////////
-// WAJid
-
-struct WAJid
-{
- int device, agent;
- CMStringA user, server;
-
- WAJid(const char *pszJid, int device = 0);
- WAJid(const char *pszUser, const char *pszServer, int device = 0, int agent = 0);
-
- CMStringA toString() const;
-
- bool isUser() const;
- bool isGroup() const;
- bool isBroadcast() const;
- bool isStatusBroadcast() const;
-};
-
-/////////////////////////////////////////////////////////////////////////////////////////
-// WASendTask
-
-struct WASendTask
-{
- WASendTask(const char *jid) :
- payLoad("message"),
- arDest(1)
- {
- uint8_t msgId[8];
- Utils_GetRandom(&msgId, sizeof(msgId));
- bin2hex(msgId, sizeof(msgId), szMsgId);
- strupr(szMsgId);
-
- payLoad << CHAR_PARAM("id", szMsgId) << CHAR_PARAM("type", "text") << CHAR_PARAM("to", jid);
- }
-
- char szMsgId[40];
- WANode payLoad;
- OBJLIST<WAJid> arDest;
- MBinBuffer content;
-};
-
-/////////////////////////////////////////////////////////////////////////////////////////
-// LT_HASH
-
-struct LT_HASH
-{
- LT_HASH()
- {
- init();
- };
-
- uint16_t hash[64];
-
- void add(const void *pData, size_t len);
- void sub(const void *pData, size_t len);
-
- void init()
- {
- SecureZeroMemory(hash, sizeof(hash));
- }
-};
-
-/////////////////////////////////////////////////////////////////////////////////////////
-// functions
-
-void bin2file(const MBinBuffer &buf, const wchar_t *pwszFileName);
-
-void string2file(const std::string &str, const wchar_t *pwszFileName);
-CMStringA file2string(const wchar_t *pwszFileName);
-
-CMStringA directPath2url(const char *pszDirectPath);
-
-std::string decodeBinStr(const std::string &buf);
-MBinBuffer decodeBufStr(const std::string &buf);
-
-void padBuffer16(MBinBuffer &buf);
-MBinBuffer unpadBuffer16(const MBinBuffer &buf);
-
-CMStringA protobuf_c_text_to_string(const ProtobufCMessage *m);
-
-MBinBuffer aesDecrypt(
- const EVP_CIPHER *cipher,
- const uint8_t *key,
- const uint8_t *iv,
- const void *data, size_t dataLen,
- const void *additionalData = 0, size_t additionalLen = 0);
-
-MBinBuffer aesEncrypt(
- const EVP_CIPHER *cipher,
- const uint8_t *key,
- const uint8_t *iv,
- const void *data, size_t dataLen,
- const void *additionalData = 0, size_t additionalLen = 0);
-
-uint32_t decodeBigEndian(const uint8_t *pData, size_t len);
-
-__forceinline uint32_t decodeBigEndian(const ProtobufCBinaryData &buf) {
- return decodeBigEndian(buf.data, buf.len);
-}
-__forceinline uint32_t decodeBigEndian(const MBinBuffer &buf) {
- return decodeBigEndian(buf.data(), buf.length());
-}
-
-std::string encodeBigEndian(uint32_t num, size_t len = sizeof(uint32_t));
-
-void generateIV(uint8_t *iv, uint32_t &pVar);
-
-unsigned char* HKDF(const EVP_MD *evp_md,
- const unsigned char *salt, size_t salt_len,
- const unsigned char *key, size_t key_len,
- const unsigned char *info, size_t info_len,
- unsigned char *okm, size_t okm_len);
+/*
+
+WhatsApp plugin for Miranda NG
+Copyright © 2019-23 George Hazan
+
+*/
+
+#define DICT_VERSION 2
+
+#define LIST_EMPTY 0
+#define STREAM_END 2
+#define DICTIONARY_0 236
+#define DICTIONARY_1 237
+#define DICTIONARY_2 238
+#define DICTIONARY_3 239
+#define AD_JID 247
+#define LIST_8 248
+#define LIST_16 249
+#define JID_PAIR 250
+#define HEX_8 251
+#define BINARY_8 252
+#define BINARY_20 253
+#define BINARY_32 254
+#define NIBBLE_8 255
+
+class WANode // kinda XML
+{
+ friend class WAReader;
+ friend class WAWriter;
+
+ WANode *pParent = nullptr;
+ OBJLIST<struct Attr> attrs;
+ OBJLIST<WANode> children;
+
+public:
+ WANode();
+ WANode(const char *pszTitle);
+ ~WANode();
+
+ void addAttr(const char *pszName, const char *pszValue);
+ void addAttr(const char *pszName, int iValue);
+ int getAttrInt(const char *pszName) const;
+ const char *getAttr(const char *pszName) const;
+
+ CMStringA getBody() const;
+
+ WANode *addChild(const char *pszName);
+ WANode *getChild(const char *pszName) const;
+ WANode *getFirstChild(void) const;
+ const OBJLIST<WANode> &getChildren(void) const
+ { return children;
+ }
+
+ void print(CMStringA &dest, int level = 0) const;
+
+ CMStringA title;
+ MBinBuffer content;
+};
+
+__forceinline WANode &operator<<(WANode &node, const CHAR_PARAM &param)
+{
+ node.addAttr(param.szName, param.szValue);
+ return node;
+}
+
+__forceinline WANode &operator<<(WANode &node, const INT_PARAM &param)
+{
+ node.addAttr(param.szName, param.iValue);
+ return node;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+namespace IQ
+{
+ enum Type { GET, SET, RESULT };
+};
+
+struct WANodeIq : public WANode
+{
+ WANodeIq(IQ::Type type, const char *pszXmlns = nullptr, const char *pszTo = nullptr);
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+struct XCHILD
+{
+ const char *name, *value;
+
+ __forceinline XCHILD(const char *_name) :
+ name(_name)
+ {}
+};
+
+__forceinline WANode& operator<<(WANode &node, const XCHILD &child)
+{
+ node.addChild(child.name);
+ return node;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// WAReader
+
+class WAReader
+{
+ const BYTE *m_buf, *m_limit;
+
+ uint32_t readIntN(int i);
+ CMStringA readStringFromChars(int size);
+
+ bool readAttributes(WANode *node, int count);
+ uint32_t readInt20();
+ bool readList(WANode *pParent, int tag);
+ int readListSize(int tag);
+ CMStringA readPacked(int tag);
+ CMStringA readString(int tag);
+
+public:
+ WAReader(const void *buf, size_t cbLen) :
+ m_buf((BYTE*)buf),
+ m_limit((BYTE*)buf + cbLen)
+ {}
+
+ WANode* readNode();
+
+ __forceinline uint32_t readInt8() { return readIntN(1); }
+ __forceinline uint32_t readInt16() { return readIntN(2); }
+ __forceinline uint32_t readInt32() { return readIntN(4); }
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// WAWriter
+
+class WAWriter
+{
+ __forceinline void writeInt8(int value) { writeIntN(value, 1); }
+ __forceinline void writeInt16(int value) { writeIntN(value, 2); }
+ __forceinline void writeInt32(int value) { writeIntN(value, 4); }
+
+ void writeByte(uint8_t b);
+ void writeIntN(int value, int i);
+ void writeInt20(int value);
+ void writeLength(int value);
+ void writeListSize(int tag);
+ void writePacked(const CMStringA &str, int tag);
+ void writeString(const char *str);
+ bool writeToken(const char *str);
+
+public:
+ void writeNode(const WANode *pNode);
+
+ MBinBuffer body;
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// WAJid
+
+struct WAJid
+{
+ int device, agent;
+ CMStringA user, server;
+
+ WAJid(const char *pszJid, int device = 0);
+ WAJid(const char *pszUser, const char *pszServer, int device = 0, int agent = 0);
+
+ CMStringA toString() const;
+
+ bool isUser() const;
+ bool isGroup() const;
+ bool isBroadcast() const;
+ bool isStatusBroadcast() const;
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// WASendTask
+
+struct WASendTask
+{
+ WASendTask(const char *jid) :
+ payLoad("message"),
+ arDest(1)
+ {
+ uint8_t msgId[8];
+ Utils_GetRandom(&msgId, sizeof(msgId));
+ bin2hex(msgId, sizeof(msgId), szMsgId);
+ strupr(szMsgId);
+
+ payLoad << CHAR_PARAM("id", szMsgId) << CHAR_PARAM("type", "text") << CHAR_PARAM("to", jid);
+ }
+
+ char szMsgId[40];
+ WANode payLoad;
+ OBJLIST<WAJid> arDest;
+ MBinBuffer content;
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// LT_HASH
+
+struct LT_HASH
+{
+ LT_HASH()
+ {
+ init();
+ };
+
+ uint16_t hash[64];
+
+ void add(const void *pData, size_t len);
+ void sub(const void *pData, size_t len);
+
+ void init()
+ {
+ SecureZeroMemory(hash, sizeof(hash));
+ }
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// functions
+
+void bin2file(const MBinBuffer &buf, const wchar_t *pwszFileName);
+
+void string2file(const std::string &str, const wchar_t *pwszFileName);
+CMStringA file2string(const wchar_t *pwszFileName);
+
+CMStringA directPath2url(const char *pszDirectPath);
+
+std::string decodeBinStr(const std::string &buf);
+MBinBuffer decodeBufStr(const std::string &buf);
+
+void padBuffer16(MBinBuffer &buf);
+MBinBuffer unpadBuffer16(const MBinBuffer &buf);
+
+CMStringA protobuf_c_text_to_string(const ProtobufCMessage *m);
+
+MBinBuffer aesDecrypt(
+ const EVP_CIPHER *cipher,
+ const uint8_t *key,
+ const uint8_t *iv,
+ const void *data, size_t dataLen,
+ const void *additionalData = 0, size_t additionalLen = 0);
+
+MBinBuffer aesEncrypt(
+ const EVP_CIPHER *cipher,
+ const uint8_t *key,
+ const uint8_t *iv,
+ const void *data, size_t dataLen,
+ const void *additionalData = 0, size_t additionalLen = 0);
+
+uint32_t decodeBigEndian(const uint8_t *pData, size_t len);
+
+__forceinline uint32_t decodeBigEndian(const ProtobufCBinaryData &buf) {
+ return decodeBigEndian(buf.data, buf.len);
+}
+__forceinline uint32_t decodeBigEndian(const MBinBuffer &buf) {
+ return decodeBigEndian(buf.data(), buf.length());
+}
+
+std::string encodeBigEndian(uint32_t num, size_t len = sizeof(uint32_t));
+
+void generateIV(uint8_t *iv, uint32_t &pVar);
+
+unsigned char* HKDF(const EVP_MD *evp_md,
+ const unsigned char *salt, size_t salt_len,
+ const unsigned char *key, size_t key_len,
+ const unsigned char *info, size_t info_len,
+ unsigned char *okm, size_t okm_len);
diff --git a/protocols/WhatsApp/src/version.h b/protocols/WhatsApp/src/version.h
index 3a160f44ec..caa1a7ea52 100644
--- a/protocols/WhatsApp/src/version.h
+++ b/protocols/WhatsApp/src/version.h
@@ -10,4 +10,4 @@
#define __DESCRIPTION "WhatsApp protocol support for Miranda NG."
#define __AUTHOR "George Hazan"
#define __AUTHORWEB "https://miranda-ng.org/p/WhatsApp"
-#define __COPYRIGHT "© 2019-22 Miranda NG team"
+#define __COPYRIGHT "© 2019-23 Miranda NG team"
diff --git a/protocols/WhatsApp/src/wanode.cpp b/protocols/WhatsApp/src/wanode.cpp
index 0c3fe15d03..9c40912f28 100644
--- a/protocols/WhatsApp/src/wanode.cpp
+++ b/protocols/WhatsApp/src/wanode.cpp
@@ -1,608 +1,608 @@
-/*
-
-WhatsApp plugin for Miranda NG
-Copyright © 2019-22 George Hazan
-
-*/
-
-#include "stdafx.h"
-#include "dicts.h"
-
-struct Attr
-{
- Attr(const char *pszName, const char *pszValue) :
- name(pszName),
- value(pszValue)
- {}
-
- Attr(const char *pszName, int iValue) :
- name(pszName),
- value(FORMAT, "%d", iValue)
- {}
-
- CMStringA name, value;
-};
-
-/////////////////////////////////////////////////////////////////////////////////////////
-// WANodeIq members
-
-WANodeIq::WANodeIq(IQ::Type type, const char *pszXmlns, const char *pszTo) :
- WANode("iq")
-{
- switch (type) {
- case IQ::GET: addAttr("type", "get"); break;
- case IQ::SET: addAttr("type", "set"); break;
- case IQ::RESULT: addAttr("type", "result"); break;
- }
-
- if (pszXmlns)
- addAttr("xmlns", pszXmlns);
-
- addAttr("to", pszTo ? pszTo : S_WHATSAPP_NET);
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-// WANode members
-
-WANode::WANode() :
- attrs(1),
- children(1)
-{}
-
-WANode::WANode(const char *pszTitle) :
- attrs(1),
- children(1),
- title(pszTitle)
-{}
-
-WANode::~WANode()
-{
-}
-
-const char *WANode::getAttr(const char *pszName) const
-{
- if (this != nullptr)
- for (auto &p : attrs)
- if (p->name == pszName)
- return p->value.c_str();
-
- return nullptr;
-}
-
-int WANode::getAttrInt(const char *pszName) const
-{
- if (this != nullptr)
- for (auto &p : attrs)
- if (p->name == pszName)
- return atoi(p->value.c_str());
-
- return 0;
-}
-
-void WANode::addAttr(const char *pszName, const char *pszValue)
-{
- attrs.insert(new Attr(pszName, pszValue));
-}
-
-void WANode::addAttr(const char *pszName, int iValue)
-{
- attrs.insert(new Attr(pszName, iValue));
-}
-
-CMStringA WANode::getBody() const
-{
- return CMStringA((char *)content.data(), (int)content.length());
-}
-
-WANode *WANode::addChild(const char *pszName)
-{
- auto *pNew = new WANode(pszName);
- pNew->pParent = this;
- children.insert(pNew);
- return pNew;
-}
-
-WANode* WANode::getChild(const char *pszName) const
-{
- if (this == nullptr)
- return nullptr;
-
- for (auto &it : children)
- if (it->title == pszName)
- return it;
-
- return nullptr;
-}
-
-WANode* WANode::getFirstChild(void) const
-{
- return (children.getCount()) ? &children[0] : nullptr;
-}
-
-void WANode::print(CMStringA &dest, int level) const
-{
- for (int i = 0; i < level; i++)
- dest.Append(" ");
-
- dest.AppendFormat("<%s ", title.c_str());
- for (auto &p : attrs)
- dest.AppendFormat("%s=\"%s\" ", p->name.c_str(), p->value.c_str());
- dest.Truncate(dest.GetLength() - 1);
-
- if (content.isEmpty() && !children.getCount()) {
- dest.Append("/>\n");
- return;
- }
-
- dest.Append(">");
- if (!content.isEmpty()) {
- ptrA tmp((char *)mir_alloc(content.length() * 2 + 1));
- bin2hex(content.data(), content.length(), tmp);
- dest.AppendFormat("%s", tmp.get());
- }
-
- if (children.getCount()) {
- dest.Append("\n");
-
- for (auto &p : children)
- p->print(dest, level + 1);
-
- for (int i = 0; i < level; i++)
- dest.Append(" ");
- }
-
- dest.AppendFormat("</%s>\n", title.c_str());
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-// WAReader class members
-
-bool WAReader::readAttributes(WANode *pNode, int count)
-{
- if (count == 0)
- return true;
-
- for (int i = 0; i < count; i++) {
- CMStringA name = readString(readInt8());
- if (name.IsEmpty())
- return false;
-
- CMStringA value = readString(readInt8());
- if (value.IsEmpty())
- return false;
-
- pNode->addAttr(name, value);
- }
- return true;
-}
-
-uint32_t WAReader::readInt20()
-{
- if (m_limit - m_buf < 3)
- return 0;
-
- int ret = (int(m_buf[0] & 0x0F) << 16) + (int(m_buf[1]) << 8) + int(m_buf[2]);
- m_buf += 3;
- return ret;
-}
-
-uint32_t WAReader::readIntN(int n)
-{
- if (m_limit - m_buf < n)
- return 0;
-
- uint32_t res = 0;
- for (int i = 0; i < n; i++, m_buf++)
- res = (res <<= 8) + *m_buf;
- return res;
-}
-
-bool WAReader::readList(WANode *pParent, int tag)
-{
- int size = readListSize(tag);
- if (size == -1)
- return false;
-
- for (int i = 0; i < size; i++) {
- WANode *pNew = readNode();
- if (pNew == nullptr)
- return false;
- pParent->children.insert(pNew);
- }
-
- return true;
-}
-
-int WAReader::readListSize(int tag)
-{
- switch (tag) {
- case LIST_EMPTY:
- return 0;
- case LIST_8:
- return readInt8();
- case LIST_16:
- return readInt16();
- }
- return -1;
-}
-
-WANode *WAReader::readNode()
-{
- int listSize = readListSize(readInt8());
- if (listSize == -1)
- return nullptr;
-
- int descrTag = readInt8();
- if (descrTag == STREAM_END)
- return nullptr;
-
- CMStringA name = readString(descrTag);
- if (name.IsEmpty())
- return nullptr;
-
- std::unique_ptr<WANode> ret(new WANode());
- ret->title = name.c_str();
-
- if (!readAttributes(ret.get(), (listSize - 1) >> 1))
- return nullptr;
-
- if ((listSize % 2) == 1)
- return ret.release();
-
- int size, tag = readInt8();
- switch (tag) {
- case LIST_EMPTY: case LIST_8: case LIST_16:
- readList(ret.get(), tag);
- break;
-
- case BINARY_8:
- size = readInt8();
-
-LBL_Binary:
- if (m_limit - m_buf < size)
- return false;
-
- ret->content.assign((void *)m_buf, size);
- m_buf += size;
- break;
-
- case BINARY_20:
- size = readInt20();
- goto LBL_Binary;
-
- case BINARY_32:
- size = readInt32();
- goto LBL_Binary;
-
- default:
- CMStringA str = readString(tag);
- ret->content.assign(str.GetBuffer(), str.GetLength() + 1);
- }
-
- return ret.release();
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-static int unpackHex(int val)
-{
- if (val < 0 || val > 15)
- return -1;
-
- return (val < 10) ? val + '0' : val - 10 + 'A';
-}
-
-static int unpackNibble(int val)
-{
- if (val < 0 || val > 15)
- return -1;
-
- switch (val) {
- case 10: return '-';
- case 11: return '.';
- case 15: return 0;
- default: return '0' + val;
- }
-}
-
-CMStringA WAReader::readPacked(int tag)
-{
- int startByte = readInt8();
- bool bTrim = false;
- if (startByte & 0x80) {
- startByte &= ~0x80;
- bTrim = true;
- }
-
- CMStringA ret;
- for (int i = 0; i < startByte; i++) {
- BYTE b = readInt8();
- int lower = (tag == NIBBLE_8) ? unpackNibble(b >> 4) : unpackHex(b >> 4);
- if (lower == -1)
- return "";
-
- int higher = (tag == NIBBLE_8) ? unpackNibble(b & 0x0F) : unpackHex(b & 0x0F);
- if (higher == -1)
- return "";
-
- ret.AppendChar(lower);
- ret.AppendChar(higher);
- }
-
- if (bTrim && !ret.IsEmpty())
- ret.Truncate(ret.GetLength() - 1);
- return ret;
-}
-
-CMStringA WAReader::readString(int tag)
-{
- if (tag >= 1 && tag < _countof(SingleByteTokens))
- return SingleByteTokens[tag];
-
- int idx;
- switch (tag) {
- case DICTIONARY_0:
- idx = readInt8();
- return (idx < _countof(dict0)) ? dict0[idx] : "";
-
- case DICTIONARY_1:
- idx = readInt8();
- return (idx < _countof(dict1)) ? dict1[idx] : "";
-
- case DICTIONARY_2:
- idx = readInt8();
- return (idx < _countof(dict2)) ? dict2[idx] : "";
-
- case DICTIONARY_3:
- idx = readInt8();
- return (idx < _countof(dict3)) ? dict3[idx] : "";
-
- case LIST_EMPTY:
- return "";
-
- case BINARY_8:
- return readStringFromChars(readInt8());
-
- case BINARY_20:
- return readStringFromChars(readInt20());
-
- case BINARY_32:
- return readStringFromChars(readInt32());
-
- case NIBBLE_8:
- case HEX_8:
- return readPacked(tag);
-
- case AD_JID:
- {
- int agent = readInt8();
- int device = readInt8();
- WAJid jid(readString(readInt8()), "s.whatsapp.net", device, agent);
- return jid.toString();
- }
-
- case JID_PAIR:
- CMStringA s1 = readString(readInt8());
- CMStringA s2 = readString(readInt8());
- if (s1.IsEmpty() && s2.IsEmpty())
- break;
-
- return CMStringA(FORMAT, "%s@%s", s1.c_str(), s2.c_str());
- }
-
- // error
- return "";
-}
-
-CMStringA WAReader::readStringFromChars(int size)
-{
- if (m_limit - m_buf < size)
- return "";
-
- CMStringA ret((char *)m_buf, size);
- m_buf += size;
- return ret;
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-// WAWriter class members
-
-void WAWriter::writeByte(uint8_t b)
-{
- body.append(&b, 1);
-}
-
-void WAWriter::writeIntN(int value, int n)
-{
- for (int i = n - 1; i >= 0; i--)
- writeByte((value >> i * 8) & 0xFF);
-}
-
-void WAWriter::writeInt20(int value)
-{
- writeByte((value >> 16) & 0xFF);
- writeByte((value >> 8) & 0xFF);
- writeByte(value & 0xFF);
-}
-
-void WAWriter::writeLength(int value)
-{
- if (value >= (1 << 20)) {
- writeByte(BINARY_32);
- writeInt32(value);
- }
- else if (value >= 256) {
- writeByte(BINARY_20);
- writeInt20(value);
- }
- else {
- writeByte(BINARY_8);
- writeInt8(value);
- }
-}
-
-void WAWriter::writeListSize(int length)
-{
- if (length == 0)
- writeByte(LIST_EMPTY);
- else if (length < 256) {
- writeByte(LIST_8);
- writeInt8(length);
- }
- else {
- writeByte(LIST_16);
- writeInt16(length);
- }
-}
-
-void WAWriter::writeNode(const WANode *pNode)
-{
- // we never send zipped content
- if (pNode->pParent == nullptr)
- writeByte(0);
-
- int numAttrs = (int)pNode->attrs.getCount();
- int hasContent = pNode->content.length() != 0 || pNode->children.getCount() != 0;
- writeListSize(2 * numAttrs + 1 + hasContent);
-
- writeString(pNode->title.c_str());
-
- // write attributes
- for (auto &it : pNode->attrs) {
- if (it->value.IsEmpty())
- continue;
-
- writeString(it->name.c_str());
- writeString(it->value.c_str());
- }
-
- // write contents
- if (pNode->content.length()) {
- writeLength((int)pNode->content.length());
- body.append(pNode->content.data(), pNode->content.length());
- }
- // write children
- else if (pNode->children.getCount()) {
- writeListSize(pNode->children.getCount());
- for (auto &it : pNode->children)
- writeNode(it);
- }
-}
-
-bool WAWriter::writeToken(const char *str)
-{
- for (auto &it : SingleByteTokens)
- if (!strcmp(str, it)) {
- writeByte(int(&it - SingleByteTokens));
- return true;
- }
-
- return false;
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-static BYTE packNibble(char c)
-{
- if (c >= '0' && c <= '9')
- return c - '0';
-
- switch (c) {
- case '-': return 10;
- case '.': return 11;
- case 0: return 15;
- }
-
- return -1;
-}
-
-static BYTE packHex(char c)
-{
- if (c >= '0' && c <= '9')
- return c - '0';
-
- if (c >= 'A' && c <= 'F')
- return 10 + c - 'A';
-
- if (c >= 'a' && c <= 'f')
- return 10 + c - 'a';
-
- if (c == 0)
- return 15;
-
- return -1;
-}
-
-static BYTE packPair(int type, char c1, char c2)
-{
- BYTE b1 = (type == NIBBLE_8) ? packNibble(c1) : packHex(c1);
- BYTE b2 = (type == NIBBLE_8) ? packNibble(c2) : packHex(c2);
- return (b1 << 4) + b2;
-}
-
-static bool isNibble(const CMStringA &str)
-{
- return strspn(str, "0123456789-.") == str.GetLength();
-}
-
-static bool isHex(const CMStringA &str)
-{
- return strspn(str, "0123456789abcdefABCDEF") == str.GetLength();
-}
-
-void WAWriter::writePacked(const CMStringA &str, int tag)
-{
- if (str.GetLength() > 254)
- return;
-
- writeByte(tag);
-
- int len = str.GetLength() / 2;
- BYTE firstByte = (str.GetLength() % 2) == 0 ? 0 : 0x81;
- writeByte(firstByte + len);
-
- const char *p = str;
- for (int i = 0; i < len; i++, p += 2)
- writeByte(packPair(tag, p[0], p[1]));
-
- if (firstByte != 0)
- writeByte(packPair(tag, p[0], 0));
-}
-
-void WAWriter::writeString(const char *str)
-{
- if (writeToken(str))
- return;
-
- auto *pszDelimiter = strchr(str, '@');
- if (pszDelimiter) {
- WAJid jid(str);
- if (jid.device || jid.agent) {
- writeByte(AD_JID);
- writeByte(jid.agent);
- writeByte(jid.device);
- writeString(jid.user);
- }
- else {
- writeByte(JID_PAIR);
-
- if (jid.user.IsEmpty()) // empty user
- writeByte(LIST_EMPTY);
- else
- writeString(jid.user);
-
- writeString(jid.server);
- }
- }
- else {
- CMStringA buf(str);
- if (isNibble(buf))
- writePacked(buf, NIBBLE_8);
- else if (isHex(buf))
- writePacked(buf, HEX_8);
- else {
- writeLength(buf.GetLength());
- body.append(buf, buf.GetLength());
- }
- }
-}
+/*
+
+WhatsApp plugin for Miranda NG
+Copyright © 2019-23 George Hazan
+
+*/
+
+#include "stdafx.h"
+#include "dicts.h"
+
+struct Attr
+{
+ Attr(const char *pszName, const char *pszValue) :
+ name(pszName),
+ value(pszValue)
+ {}
+
+ Attr(const char *pszName, int iValue) :
+ name(pszName),
+ value(FORMAT, "%d", iValue)
+ {}
+
+ CMStringA name, value;
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// WANodeIq members
+
+WANodeIq::WANodeIq(IQ::Type type, const char *pszXmlns, const char *pszTo) :
+ WANode("iq")
+{
+ switch (type) {
+ case IQ::GET: addAttr("type", "get"); break;
+ case IQ::SET: addAttr("type", "set"); break;
+ case IQ::RESULT: addAttr("type", "result"); break;
+ }
+
+ if (pszXmlns)
+ addAttr("xmlns", pszXmlns);
+
+ addAttr("to", pszTo ? pszTo : S_WHATSAPP_NET);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// WANode members
+
+WANode::WANode() :
+ attrs(1),
+ children(1)
+{}
+
+WANode::WANode(const char *pszTitle) :
+ attrs(1),
+ children(1),
+ title(pszTitle)
+{}
+
+WANode::~WANode()
+{
+}
+
+const char *WANode::getAttr(const char *pszName) const
+{
+ if (this != nullptr)
+ for (auto &p : attrs)
+ if (p->name == pszName)
+ return p->value.c_str();
+
+ return nullptr;
+}
+
+int WANode::getAttrInt(const char *pszName) const
+{
+ if (this != nullptr)
+ for (auto &p : attrs)
+ if (p->name == pszName)
+ return atoi(p->value.c_str());
+
+ return 0;
+}
+
+void WANode::addAttr(const char *pszName, const char *pszValue)
+{
+ attrs.insert(new Attr(pszName, pszValue));
+}
+
+void WANode::addAttr(const char *pszName, int iValue)
+{
+ attrs.insert(new Attr(pszName, iValue));
+}
+
+CMStringA WANode::getBody() const
+{
+ return CMStringA((char *)content.data(), (int)content.length());
+}
+
+WANode *WANode::addChild(const char *pszName)
+{
+ auto *pNew = new WANode(pszName);
+ pNew->pParent = this;
+ children.insert(pNew);
+ return pNew;
+}
+
+WANode* WANode::getChild(const char *pszName) const
+{
+ if (this == nullptr)
+ return nullptr;
+
+ for (auto &it : children)
+ if (it->title == pszName)
+ return it;
+
+ return nullptr;
+}
+
+WANode* WANode::getFirstChild(void) const
+{
+ return (children.getCount()) ? &children[0] : nullptr;
+}
+
+void WANode::print(CMStringA &dest, int level) const
+{
+ for (int i = 0; i < level; i++)
+ dest.Append(" ");
+
+ dest.AppendFormat("<%s ", title.c_str());
+ for (auto &p : attrs)
+ dest.AppendFormat("%s=\"%s\" ", p->name.c_str(), p->value.c_str());
+ dest.Truncate(dest.GetLength() - 1);
+
+ if (content.isEmpty() && !children.getCount()) {
+ dest.Append("/>\n");
+ return;
+ }
+
+ dest.Append(">");
+ if (!content.isEmpty()) {
+ ptrA tmp((char *)mir_alloc(content.length() * 2 + 1));
+ bin2hex(content.data(), content.length(), tmp);
+ dest.AppendFormat("%s", tmp.get());
+ }
+
+ if (children.getCount()) {
+ dest.Append("\n");
+
+ for (auto &p : children)
+ p->print(dest, level + 1);
+
+ for (int i = 0; i < level; i++)
+ dest.Append(" ");
+ }
+
+ dest.AppendFormat("</%s>\n", title.c_str());
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// WAReader class members
+
+bool WAReader::readAttributes(WANode *pNode, int count)
+{
+ if (count == 0)
+ return true;
+
+ for (int i = 0; i < count; i++) {
+ CMStringA name = readString(readInt8());
+ if (name.IsEmpty())
+ return false;
+
+ CMStringA value = readString(readInt8());
+ if (value.IsEmpty())
+ return false;
+
+ pNode->addAttr(name, value);
+ }
+ return true;
+}
+
+uint32_t WAReader::readInt20()
+{
+ if (m_limit - m_buf < 3)
+ return 0;
+
+ int ret = (int(m_buf[0] & 0x0F) << 16) + (int(m_buf[1]) << 8) + int(m_buf[2]);
+ m_buf += 3;
+ return ret;
+}
+
+uint32_t WAReader::readIntN(int n)
+{
+ if (m_limit - m_buf < n)
+ return 0;
+
+ uint32_t res = 0;
+ for (int i = 0; i < n; i++, m_buf++)
+ res = (res <<= 8) + *m_buf;
+ return res;
+}
+
+bool WAReader::readList(WANode *pParent, int tag)
+{
+ int size = readListSize(tag);
+ if (size == -1)
+ return false;
+
+ for (int i = 0; i < size; i++) {
+ WANode *pNew = readNode();
+ if (pNew == nullptr)
+ return false;
+ pParent->children.insert(pNew);
+ }
+
+ return true;
+}
+
+int WAReader::readListSize(int tag)
+{
+ switch (tag) {
+ case LIST_EMPTY:
+ return 0;
+ case LIST_8:
+ return readInt8();
+ case LIST_16:
+ return readInt16();
+ }
+ return -1;
+}
+
+WANode *WAReader::readNode()
+{
+ int listSize = readListSize(readInt8());
+ if (listSize == -1)
+ return nullptr;
+
+ int descrTag = readInt8();
+ if (descrTag == STREAM_END)
+ return nullptr;
+
+ CMStringA name = readString(descrTag);
+ if (name.IsEmpty())
+ return nullptr;
+
+ std::unique_ptr<WANode> ret(new WANode());
+ ret->title = name.c_str();
+
+ if (!readAttributes(ret.get(), (listSize - 1) >> 1))
+ return nullptr;
+
+ if ((listSize % 2) == 1)
+ return ret.release();
+
+ int size, tag = readInt8();
+ switch (tag) {
+ case LIST_EMPTY: case LIST_8: case LIST_16:
+ readList(ret.get(), tag);
+ break;
+
+ case BINARY_8:
+ size = readInt8();
+
+LBL_Binary:
+ if (m_limit - m_buf < size)
+ return false;
+
+ ret->content.assign((void *)m_buf, size);
+ m_buf += size;
+ break;
+
+ case BINARY_20:
+ size = readInt20();
+ goto LBL_Binary;
+
+ case BINARY_32:
+ size = readInt32();
+ goto LBL_Binary;
+
+ default:
+ CMStringA str = readString(tag);
+ ret->content.assign(str.GetBuffer(), str.GetLength() + 1);
+ }
+
+ return ret.release();
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static int unpackHex(int val)
+{
+ if (val < 0 || val > 15)
+ return -1;
+
+ return (val < 10) ? val + '0' : val - 10 + 'A';
+}
+
+static int unpackNibble(int val)
+{
+ if (val < 0 || val > 15)
+ return -1;
+
+ switch (val) {
+ case 10: return '-';
+ case 11: return '.';
+ case 15: return 0;
+ default: return '0' + val;
+ }
+}
+
+CMStringA WAReader::readPacked(int tag)
+{
+ int startByte = readInt8();
+ bool bTrim = false;
+ if (startByte & 0x80) {
+ startByte &= ~0x80;
+ bTrim = true;
+ }
+
+ CMStringA ret;
+ for (int i = 0; i < startByte; i++) {
+ BYTE b = readInt8();
+ int lower = (tag == NIBBLE_8) ? unpackNibble(b >> 4) : unpackHex(b >> 4);
+ if (lower == -1)
+ return "";
+
+ int higher = (tag == NIBBLE_8) ? unpackNibble(b & 0x0F) : unpackHex(b & 0x0F);
+ if (higher == -1)
+ return "";
+
+ ret.AppendChar(lower);
+ ret.AppendChar(higher);
+ }
+
+ if (bTrim && !ret.IsEmpty())
+ ret.Truncate(ret.GetLength() - 1);
+ return ret;
+}
+
+CMStringA WAReader::readString(int tag)
+{
+ if (tag >= 1 && tag < _countof(SingleByteTokens))
+ return SingleByteTokens[tag];
+
+ int idx;
+ switch (tag) {
+ case DICTIONARY_0:
+ idx = readInt8();
+ return (idx < _countof(dict0)) ? dict0[idx] : "";
+
+ case DICTIONARY_1:
+ idx = readInt8();
+ return (idx < _countof(dict1)) ? dict1[idx] : "";
+
+ case DICTIONARY_2:
+ idx = readInt8();
+ return (idx < _countof(dict2)) ? dict2[idx] : "";
+
+ case DICTIONARY_3:
+ idx = readInt8();
+ return (idx < _countof(dict3)) ? dict3[idx] : "";
+
+ case LIST_EMPTY:
+ return "";
+
+ case BINARY_8:
+ return readStringFromChars(readInt8());
+
+ case BINARY_20:
+ return readStringFromChars(readInt20());
+
+ case BINARY_32:
+ return readStringFromChars(readInt32());
+
+ case NIBBLE_8:
+ case HEX_8:
+ return readPacked(tag);
+
+ case AD_JID:
+ {
+ int agent = readInt8();
+ int device = readInt8();
+ WAJid jid(readString(readInt8()), "s.whatsapp.net", device, agent);
+ return jid.toString();
+ }
+
+ case JID_PAIR:
+ CMStringA s1 = readString(readInt8());
+ CMStringA s2 = readString(readInt8());
+ if (s1.IsEmpty() && s2.IsEmpty())
+ break;
+
+ return CMStringA(FORMAT, "%s@%s", s1.c_str(), s2.c_str());
+ }
+
+ // error
+ return "";
+}
+
+CMStringA WAReader::readStringFromChars(int size)
+{
+ if (m_limit - m_buf < size)
+ return "";
+
+ CMStringA ret((char *)m_buf, size);
+ m_buf += size;
+ return ret;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// WAWriter class members
+
+void WAWriter::writeByte(uint8_t b)
+{
+ body.append(&b, 1);
+}
+
+void WAWriter::writeIntN(int value, int n)
+{
+ for (int i = n - 1; i >= 0; i--)
+ writeByte((value >> i * 8) & 0xFF);
+}
+
+void WAWriter::writeInt20(int value)
+{
+ writeByte((value >> 16) & 0xFF);
+ writeByte((value >> 8) & 0xFF);
+ writeByte(value & 0xFF);
+}
+
+void WAWriter::writeLength(int value)
+{
+ if (value >= (1 << 20)) {
+ writeByte(BINARY_32);
+ writeInt32(value);
+ }
+ else if (value >= 256) {
+ writeByte(BINARY_20);
+ writeInt20(value);
+ }
+ else {
+ writeByte(BINARY_8);
+ writeInt8(value);
+ }
+}
+
+void WAWriter::writeListSize(int length)
+{
+ if (length == 0)
+ writeByte(LIST_EMPTY);
+ else if (length < 256) {
+ writeByte(LIST_8);
+ writeInt8(length);
+ }
+ else {
+ writeByte(LIST_16);
+ writeInt16(length);
+ }
+}
+
+void WAWriter::writeNode(const WANode *pNode)
+{
+ // we never send zipped content
+ if (pNode->pParent == nullptr)
+ writeByte(0);
+
+ int numAttrs = (int)pNode->attrs.getCount();
+ int hasContent = pNode->content.length() != 0 || pNode->children.getCount() != 0;
+ writeListSize(2 * numAttrs + 1 + hasContent);
+
+ writeString(pNode->title.c_str());
+
+ // write attributes
+ for (auto &it : pNode->attrs) {
+ if (it->value.IsEmpty())
+ continue;
+
+ writeString(it->name.c_str());
+ writeString(it->value.c_str());
+ }
+
+ // write contents
+ if (pNode->content.length()) {
+ writeLength((int)pNode->content.length());
+ body.append(pNode->content.data(), pNode->content.length());
+ }
+ // write children
+ else if (pNode->children.getCount()) {
+ writeListSize(pNode->children.getCount());
+ for (auto &it : pNode->children)
+ writeNode(it);
+ }
+}
+
+bool WAWriter::writeToken(const char *str)
+{
+ for (auto &it : SingleByteTokens)
+ if (!strcmp(str, it)) {
+ writeByte(int(&it - SingleByteTokens));
+ return true;
+ }
+
+ return false;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static BYTE packNibble(char c)
+{
+ if (c >= '0' && c <= '9')
+ return c - '0';
+
+ switch (c) {
+ case '-': return 10;
+ case '.': return 11;
+ case 0: return 15;
+ }
+
+ return -1;
+}
+
+static BYTE packHex(char c)
+{
+ if (c >= '0' && c <= '9')
+ return c - '0';
+
+ if (c >= 'A' && c <= 'F')
+ return 10 + c - 'A';
+
+ if (c >= 'a' && c <= 'f')
+ return 10 + c - 'a';
+
+ if (c == 0)
+ return 15;
+
+ return -1;
+}
+
+static BYTE packPair(int type, char c1, char c2)
+{
+ BYTE b1 = (type == NIBBLE_8) ? packNibble(c1) : packHex(c1);
+ BYTE b2 = (type == NIBBLE_8) ? packNibble(c2) : packHex(c2);
+ return (b1 << 4) + b2;
+}
+
+static bool isNibble(const CMStringA &str)
+{
+ return strspn(str, "0123456789-.") == str.GetLength();
+}
+
+static bool isHex(const CMStringA &str)
+{
+ return strspn(str, "0123456789abcdefABCDEF") == str.GetLength();
+}
+
+void WAWriter::writePacked(const CMStringA &str, int tag)
+{
+ if (str.GetLength() > 254)
+ return;
+
+ writeByte(tag);
+
+ int len = str.GetLength() / 2;
+ BYTE firstByte = (str.GetLength() % 2) == 0 ? 0 : 0x81;
+ writeByte(firstByte + len);
+
+ const char *p = str;
+ for (int i = 0; i < len; i++, p += 2)
+ writeByte(packPair(tag, p[0], p[1]));
+
+ if (firstByte != 0)
+ writeByte(packPair(tag, p[0], 0));
+}
+
+void WAWriter::writeString(const char *str)
+{
+ if (writeToken(str))
+ return;
+
+ auto *pszDelimiter = strchr(str, '@');
+ if (pszDelimiter) {
+ WAJid jid(str);
+ if (jid.device || jid.agent) {
+ writeByte(AD_JID);
+ writeByte(jid.agent);
+ writeByte(jid.device);
+ writeString(jid.user);
+ }
+ else {
+ writeByte(JID_PAIR);
+
+ if (jid.user.IsEmpty()) // empty user
+ writeByte(LIST_EMPTY);
+ else
+ writeString(jid.user);
+
+ writeString(jid.server);
+ }
+ }
+ else {
+ CMStringA buf(str);
+ if (isNibble(buf))
+ writePacked(buf, NIBBLE_8);
+ else if (isHex(buf))
+ writePacked(buf, HEX_8);
+ else {
+ writeLength(buf.GetLength());
+ body.append(buf, buf.GetLength());
+ }
+ }
+}