summaryrefslogtreecommitdiff
path: root/protocols/WhatsApp/src
diff options
context:
space:
mode:
authorGeorge Hazan <ghazan@miranda.im>2022-10-21 13:51:21 +0300
committerGeorge Hazan <ghazan@miranda.im>2022-10-21 13:51:21 +0300
commitf565cc4efeace9a9f1c034f3956bd7192382bea7 (patch)
tree437d14bc2433b5453ac1f63c07f1a4a9b6ecbbde /protocols/WhatsApp/src
parent51037c1032b29ac1090cf4ecb78f3dce1bda554a (diff)
WhatsApp: app state sync
Diffstat (limited to 'protocols/WhatsApp/src')
-rw-r--r--protocols/WhatsApp/src/appsync.cpp235
-rw-r--r--protocols/WhatsApp/src/iq.cpp165
-rw-r--r--protocols/WhatsApp/src/proto.cpp23
-rw-r--r--protocols/WhatsApp/src/proto.h7
-rw-r--r--protocols/WhatsApp/src/utils.cpp49
-rw-r--r--protocols/WhatsApp/src/utils.h30
6 files changed, 321 insertions, 188 deletions
diff --git a/protocols/WhatsApp/src/appsync.cpp b/protocols/WhatsApp/src/appsync.cpp
new file mode 100644
index 0000000000..9728eae48c
--- /dev/null
+++ b/protocols/WhatsApp/src/appsync.cpp
@@ -0,0 +1,235 @@
+/*
+
+WhatsApp plugin for Miranda NG
+Copyright © 2019-22 George Hazan
+
+*/
+
+#include "stdafx.h"
+
+//////////////////////////////////////////////////////////////////////////////
+
+void WhatsAppProto::InitSync()
+{
+ m_arCollections.insert(new WACollection("regular"));
+ m_arCollections.insert(new WACollection("regular_high"));
+ m_arCollections.insert(new WACollection("regular_low"));
+ m_arCollections.insert(new WACollection("critical_block"));
+ m_arCollections.insert(new WACollection("critical_unblock_low"));
+
+ for (auto &it : m_arCollections) {
+ CMStringW wszPath(GetTmpFileName("collection", it->szName));
+ wszPath.Append(L".json");
+ if (_waccess(wszPath, 0))
+ continue;
+
+ JSONNode root = JSONNode::parse(file2string(wszPath));
+ it->version = root["version"].as_int();
+
+ auto szHash = decodeBinStr(root["hash"].as_string());
+ if (szHash.size() == sizeof(it->hash.hash))
+ memcpy(it->hash.hash, szHash.c_str(), sizeof(it->hash.hash));
+
+ for (auto &val : root["indexValueMap"])
+ it->indexValueMap[decodeBinStr(val.name())] = decodeBinStr(val.as_string());
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void WhatsAppProto::OnServerSync(const WANode &node)
+{
+ OBJLIST<WACollection> task(1);
+
+ for (auto &it : node.getChildren())
+ if (it->title == "collection")
+ task.insert(new WACollection(it->getAttr("name"), it->getAttrInt("version")));
+
+ ResyncServer(task);
+}
+
+void WhatsAppProto::ResyncAll()
+{
+ setDword("lastResyncTime", time(0));
+ ResyncServer(m_arCollections);
+}
+
+void WhatsAppProto::ResyncServer(const OBJLIST<WACollection> &task)
+{
+ WANodeIq iq(IQ::SET, "w:sync:app:state");
+
+ auto *pList = iq.addChild("sync");
+ for (auto &it : task) {
+ auto *pCollection = m_arCollections.find(it);
+ if (pCollection == nullptr)
+ m_arCollections.insert(pCollection = new WACollection(it->szName, 0));
+
+ if (!pCollection->version || pCollection->version < it->version) {
+ auto *pNode = pList->addChild("collection");
+ *pNode << CHAR_PARAM("name", it->szName) << INT_PARAM("version", pCollection->version)
+ << CHAR_PARAM("return_snapshot", (!pCollection->version) ? "true" : "false");
+ }
+ }
+
+ if (pList->getFirstChild() != nullptr)
+ WSSendNode(iq, &WhatsAppProto::OnIqServerSync);
+}
+
+void WhatsAppProto::OnIqServerSync(const WANode &node)
+{
+ for (auto &coll : node.getChild("sync")->getChildren()) {
+ if (coll->title != "collection")
+ continue;
+
+ auto *pszName = coll->getAttr("name");
+
+ auto *pCollection = FindCollection(pszName);
+ if (pCollection == nullptr) {
+ pCollection = new WACollection(pszName, 0);
+ m_arCollections.insert(pCollection);
+ }
+
+ int dwVersion = 0;
+
+ CMStringW wszSnapshotPath(GetTmpFileName("collection", pszName));
+ if (auto *pSnapshot = coll->getChild("snapshot")) {
+ proto::ExternalBlobReference body;
+ body << pSnapshot->content;
+ if (!body.has_directpath() || !body.has_mediakey()) {
+ debugLogA("Invalid snapshot data, skipping");
+ continue;
+ }
+
+ MBinBuffer buf = DownloadEncryptedFile(directPath2url(body.directpath().c_str()), body.mediakey(), "App State");
+ if (!buf.data()) {
+ debugLogA("Invalid downloaded snapshot data, skipping");
+ continue;
+ }
+
+ proto::SyncdSnapshot snapshot;
+ snapshot << buf;
+
+ dwVersion = snapshot.version().version();
+ if (dwVersion > pCollection->version) {
+ pCollection->hash.init();
+ debugLogA("%s: applying snapshot of version %d", pCollection->szName.get(), dwVersion);
+ for (auto &it : snapshot.records())
+ ParsePatch(pCollection, it, true);
+ }
+ else debugLogA("%s: skipping snapshot of version %d", pCollection->szName.get(), dwVersion);
+ }
+
+ if (auto *pPatchList = coll->getChild("patches")) {
+ for (auto &it : pPatchList->getChildren()) {
+ proto::SyncdPatch patch;
+ patch << it->content;
+
+ dwVersion = patch.version().version();
+ if (dwVersion > pCollection->version) {
+ debugLogA("%s: applying patch of version %d", pCollection->szName.get(), dwVersion);
+ for (auto &jt : patch.mutations())
+ ParsePatch(pCollection, jt.record(), jt.operation() == proto::SyncdMutation_SyncdOperation::SyncdMutation_SyncdOperation_SET);
+ }
+ else debugLogA("%s: skipping patch of version %d", pCollection->szName.get(), dwVersion);
+ }
+ }
+
+ CMStringA szSetting(FORMAT, "Collection_%s", pszName);
+ // setDword(szSetting, dwVersion);
+
+ JSONNode jsonRoot, jsonMap;
+ for (auto &it : pCollection->indexValueMap)
+ jsonMap << CHAR_PARAM(ptrA(mir_base64_encode(it.first.c_str(), it.first.size())), ptrA(mir_base64_encode(it.second.c_str(), it.second.size())));
+ jsonRoot << INT_PARAM("version", dwVersion) << CHAR_PARAM("hash", ptrA(mir_base64_encode(pCollection->hash.hash, sizeof(pCollection->hash.hash))))
+ << JSON_PARAM("indexValueMap", jsonMap);
+
+ string2file(jsonRoot.write(), GetTmpFileName("collection", CMStringA(pszName) + ".json"));
+ }
+}
+
+static uint8_t sttMutationInfo[] = "WhatsApp Mutation Keys";
+
+void WhatsAppProto::ParsePatch(WACollection *pColl, const ::proto::SyncdRecord &rec, bool bSet)
+{
+ int id = decodeBigEndian(rec.keyid().id());
+ auto &index = rec.index().blob();
+ auto &value = rec.value().blob();
+ auto &macValue = value.substr(value.size() - 32, value.size());
+
+ MBinBuffer key(getBlob(CMStringA(FORMAT, "AppSyncKey%d", id)));
+ if (!key.data()) {
+ debugLogA("No key with id=%d to decode a patch");
+ return;
+ }
+
+ struct
+ {
+ uint8_t indexKey[32];
+ uint8_t encKey[32];
+ uint8_t macKey[32];
+ uint8_t snapshotMacKey[32];
+ uint8_t patchMacKey[32];
+
+ } mutationKeys;
+
+ HKDF(EVP_sha256(), (BYTE *)"", 0, key.data(), key.length(), sttMutationInfo, sizeof(sttMutationInfo) - 1, (BYTE *)&mutationKeys, sizeof(mutationKeys));
+
+ MBinBuffer decoded = aesDecrypt(EVP_aes_256_cbc(), mutationKeys.encKey, (uint8_t *)value.c_str(), value.c_str() + 16, value.size() - 32);
+ if (!decoded.data()) {
+ debugLogA("Unable to decode patch with key id=%d", id);
+ return;
+ }
+
+ proto::SyncActionData data;
+ data << decoded;
+
+ debugLogA("Applying patch for %s{%d}: %s", pColl->szName.get(), data.version(), data.Utf8DebugString().c_str());
+
+ if (bSet) {
+ JSONNode jsonRoot = JSONNode::parse(data.index().c_str());
+ ApplyPatch(jsonRoot, data.value());
+
+ pColl->hash.add(macValue.c_str(), macValue.size());
+ pColl->indexValueMap[index] = macValue;
+ }
+ else {
+ auto &prevVal = pColl->indexValueMap.find(index);
+ if (prevVal != pColl->indexValueMap.end()) {
+ pColl->hash.sub(prevVal->second.c_str(), prevVal->second.size());
+ pColl->indexValueMap.erase(prevVal);
+ }
+ }
+}
+
+void WhatsAppProto::ApplyPatch(const JSONNode &index, const proto::SyncActionValue &data)
+{
+ auto title = index.at((json_index_t)0).as_string();
+
+ if (title == "contact" && data.has_contactaction()) {
+ WAJid jid(index.at(1).as_string().c_str());
+ auto *pUser = AddUser(jid.toString(), false, jid.isGroup());
+
+ auto &pAction = data.contactaction();
+ auto &fullName = pAction.fullname();
+ if (!fullName.empty())
+ setUString(pUser->hContact, "Nick", fullName.c_str());
+
+ if (pAction.has_firstname()) {
+ CMStringA str(pAction.firstname().c_str());
+ str.TrimRight();
+ setUString(pUser->hContact, "FirstName", str.c_str());
+ setUString(pUser->hContact, "LastName", fullName.c_str() + str.GetLength() + 1);
+ }
+ else {
+ size_t idx = fullName.rfind(' ');
+ if (idx != fullName.npos) {
+ setUString(pUser->hContact, "FirstName", fullName.substr(0, idx).c_str());
+ setUString(pUser->hContact, "LastName", fullName.substr(idx+1, fullName.size()).c_str());
+ }
+ else {
+ setUString(pUser->hContact, "FirstName", "");
+ setUString(pUser->hContact, "LastName", fullName.c_str());
+ }
+ }
+ }
+}
diff --git a/protocols/WhatsApp/src/iq.cpp b/protocols/WhatsApp/src/iq.cpp
index 2f42dcf2a4..c0a2c414ac 100644
--- a/protocols/WhatsApp/src/iq.cpp
+++ b/protocols/WhatsApp/src/iq.cpp
@@ -93,7 +93,8 @@ void WhatsAppProto::OnReceiveInfo(const WANode &node)
if (auto *pChild = node.getFirstChild()) {
if (pChild->title == "offline") {
debugLogA("Processed %d offline events", pChild->getAttrInt("count"));
- if (m_arCollections.getCount() == 0) {
+
+ if (getDword("lastResyncTime") == 0) {
m_impl.m_resyncApp.Stop();
m_impl.m_resyncApp.Start(1000);
}
@@ -417,7 +418,6 @@ void WhatsAppProto::OnIqPairSuccess(const WANode &node)
MBinBuffer accountEnc(account.ByteSize());
account.SerializeToArray(accountEnc.data(), (int)accountEnc.length());
- db_set_blob(0, m_szModuleName, "WAAccount", accountEnc.data(), (int)accountEnc.length());
proto::ADVDeviceIdentity deviceIdentity;
deviceIdentity.ParseFromString(deviceDetails);
@@ -565,167 +565,6 @@ LBL_Error:
/////////////////////////////////////////////////////////////////////////////////////////
-void WhatsAppProto::OnServerSync(const WANode &node)
-{
- OBJLIST<WACollection> task(1);
-
- for (auto &it : node.getChildren())
- if (it->title == "collection")
- task.insert(new WACollection(it->getAttr("name"), it->getAttrInt("version")));
-
- ResyncServer(task);
-}
-
-void WhatsAppProto::ResyncAll()
-{
- OBJLIST<WACollection> task(1);
- task.insert(new WACollection("regular", 11));
- task.insert(new WACollection("regular_high", 9));
- task.insert(new WACollection("regular_low", 11));
- task.insert(new WACollection("critical_block", 11));
- task.insert(new WACollection("critical_unblock_low", 11));
- ResyncServer(task);
-}
-
-void WhatsAppProto::ResyncServer(const OBJLIST<WACollection> &task)
-{
- WANodeIq iq(IQ::SET, "w:sync:app:state");
-
- auto *pList = iq.addChild("sync");
- for (auto &it : task) {
- auto *pCollection = m_arCollections.find(it);
- if (pCollection == nullptr)
- m_arCollections.insert(pCollection = new WACollection(it->szName, 0));
-
- if (pCollection->version < it->version) {
- auto *pNode = pList->addChild("collection");
- *pNode << CHAR_PARAM("name", it->szName) << INT_PARAM("version", pCollection->version)
- << CHAR_PARAM("return_snapshot", (!pCollection->version) ? "true" : "false");
- }
- }
-
- if (pList->getFirstChild() != nullptr)
- WSSendNode(iq, &WhatsAppProto::OnIqServerSync);
-}
-
-void WhatsAppProto::OnIqServerSync(const WANode &node)
-{
- for (auto &coll : node.getChild("sync")->getChildren()) {
- if (coll->title != "collection")
- continue;
-
- auto *pszName = coll->getAttr("name");
-
- auto *pCollection = FindCollection(pszName);
- if (pCollection == nullptr) {
- pCollection = new WACollection(pszName, 0);
- m_arCollections.insert(pCollection);
- }
-
- int dwVersion = 0;
-
- CMStringW wszSnapshotPath(GetTmpFileName("collection", pszName));
- if (auto *pSnapshot = coll->getChild("snapshot")) {
- proto::ExternalBlobReference body;
- body << pSnapshot->content;
- if (!body.has_directpath() || !body.has_mediakey()) {
- debugLogA("Invalid snapshot data, skipping");
- continue;
- }
-
- MBinBuffer buf = DownloadEncryptedFile(directPath2url(body.directpath().c_str()), body.mediakey(), "App State");
- if (!buf.data()) {
- debugLogA("Invalid downloaded snapshot data, skipping");
- continue;
- }
-
- proto::SyncdSnapshot snapshot;
- snapshot << buf;
-
- dwVersion = snapshot.version().version();
- if (dwVersion > pCollection->version) {
- auto &hash = snapshot.mac();
- pCollection->hash.assign(hash.c_str(), hash.size());
-
- for (auto &it : snapshot.records())
- ParsePatch(pCollection, it, true);
- }
- }
-
- if (auto *pPatchList = coll->getChild("patches")) {
- for (auto &it : pPatchList->getChildren()) {
- proto::SyncdPatch patch;
- patch << it->content;
-
- dwVersion = patch.version().version();
- if (dwVersion > pCollection->version)
- for (auto &jt : patch.mutations())
- ParsePatch(pCollection, jt.record(), jt.operation() == proto::SyncdMutation_SyncdOperation::SyncdMutation_SyncdOperation_SET);
- }
- }
-
- CMStringA szSetting(FORMAT, "Collection_%s", pszName);
- // setDword(szSetting, dwVersion);
-
- JSONNode jsonRoot, jsonMap;
- for (auto &it : pCollection->indexValueMap)
- jsonMap << CHAR_PARAM(ptrA(mir_base64_encode(it.first.c_str(), it.first.size())), ptrA(mir_base64_encode(it.second.c_str(), it.second.size())));
- jsonRoot << INT_PARAM("version", dwVersion) << JSON_PARAM("indexValueMap", jsonMap);
-
- string2file(jsonRoot.write(), GetTmpFileName("collection", CMStringA(pszName) + ".json"));
- }
-}
-
-static char sttMutationInfo[] = "WhatsApp Mutation Keys";
-
-void WhatsAppProto::ParsePatch(WACollection *pColl, const ::proto::SyncdRecord &rec, bool bSet)
-{
- int id = decodeBigEndian(rec.keyid().id());
- auto &index = rec.index().blob();
- auto &value = rec.value().blob();
-
- MBinBuffer key(getBlob(CMStringA(FORMAT, "AppSyncKey%d", id)));
- if (!key.data()) {
- debugLogA("No key with id=%d to decode a patch");
- return;
- }
-
- struct
- {
- uint8_t indexKey[32];
- uint8_t encKey[32];
- uint8_t macKey[32];
- uint8_t snapshotMacKey[32];
- uint8_t patchMacKey[32];
-
- } mutationKeys;
-
- HKDF(EVP_sha256(), (BYTE *)"", 0, key.data(), key.length(), (BYTE *)sttMutationInfo, sizeof(sttMutationInfo) - 1, (BYTE*)&mutationKeys, sizeof(mutationKeys));
-
- MBinBuffer decoded = aesDecrypt(EVP_aes_256_cbc(), mutationKeys.encKey, (uint8_t *)value.c_str(), value.c_str() + 16, value.size() - 32);
- if (!decoded.data()) {
- debugLogA("Unable to decode patch with key id=%d", id);
- return;
- }
-
- proto::SyncActionData data;
- data << decoded;
-
- debugLogA("Applying patch for %s: %d -> %d", pColl->szName.get(), pColl->version, data.version());
-
- if (bSet) {
- auto &patchIndex = data.index();
- auto &patchVal = data.value();
- debugLogA("Got patch: %s", patchVal.Utf8DebugString().c_str());
- pColl->indexValueMap[index] = value.substr(0, value.size() - 32);
- }
- else pColl->indexValueMap.erase(index);
-
- pColl->version = data.version();
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
void WhatsAppProto::OnSuccess(const WANode &)
{
OnLoggedIn();
diff --git a/protocols/WhatsApp/src/proto.cpp b/protocols/WhatsApp/src/proto.cpp
index 75a562c067..701bf8f3a7 100644
--- a/protocols/WhatsApp/src/proto.cpp
+++ b/protocols/WhatsApp/src/proto.cpp
@@ -61,7 +61,7 @@ WhatsAppProto::WhatsAppProto(const char *proto_name, const wchar_t *username) :
HookProtoEvent(ME_OPT_INITIALISE, &WhatsAppProto::OnOptionsInit);
- InitCollections();
+ InitSync();
InitPopups();
InitPersistentHandlers();
@@ -310,24 +310,3 @@ HANDLE WhatsAppProto::SearchBasic(const wchar_t* id)
ForkThread(&WhatsAppProto::SearchAckThread, param);
return (HANDLE)param->id;
}
-
-//////////////////////////////////////////////////////////////////////////////
-
-static int enumCollections(const char *szSetting, void *param)
-{
- auto *pList = (LIST<char> *)param;
- if (!memcmp(szSetting, "Collection_", 11))
- pList->insert(mir_strdup(szSetting));
- return 0;
-}
-
-void WhatsAppProto::InitCollections()
-{
- LIST<char> settings(10);
- db_enum_settings(0, enumCollections, m_szModuleName, &settings);
-
- for (auto &it : settings) {
- m_arCollections.insert(new WACollection(it + 11, getDword(it)));
- mir_free(it);
- }
-}
diff --git a/protocols/WhatsApp/src/proto.h b/protocols/WhatsApp/src/proto.h
index 38d697f5ee..5814aa7538 100644
--- a/protocols/WhatsApp/src/proto.h
+++ b/protocols/WhatsApp/src/proto.h
@@ -110,7 +110,7 @@ struct WAOwnMessage
struct WACollection
{
- WACollection(const char *_1, int _2) :
+ WACollection(const char *_1, int _2 = 0) :
szName(mir_strdup(_1)),
version(_2)
{}
@@ -118,7 +118,7 @@ struct WACollection
ptrA szName;
int version;
- MBinBuffer hash;
+ LT_HASH hash;
std::map<std::string, std::string> indexValueMap;
};
@@ -257,7 +257,8 @@ class WhatsAppProto : public PROTO<WhatsAppProto>
// App state management
OBJLIST<WACollection> m_arCollections;
- void InitCollections(void);
+ void InitSync(void);
+ void ApplyPatch(const JSONNode &index, const proto::SyncActionValue &data);
void ParsePatch(WACollection *pColl, const proto::SyncdRecord &rec, bool bSet);
void ResyncServer(const OBJLIST<WACollection> &task);
void ResyncAll(void);
diff --git a/protocols/WhatsApp/src/utils.cpp b/protocols/WhatsApp/src/utils.cpp
index 455428bd7b..9bbea8a88b 100644
--- a/protocols/WhatsApp/src/utils.cpp
+++ b/protocols/WhatsApp/src/utils.cpp
@@ -72,6 +72,28 @@ CMStringA WAJid::toString() const
/////////////////////////////////////////////////////////////////////////////////////////
+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)
{
mir_cslock lck(m_csUsers);
@@ -193,6 +215,18 @@ int WhatsAppProto::WSSendNode(WANode &node, WA_PKT_HANDLER pHandler)
/////////////////////////////////////////////////////////////////////////////////////////
+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;
+}
+
uint32_t decodeBigEndian(const std::string &buf)
{
uint32_t ret = 0;
@@ -328,6 +362,21 @@ void string2file(const std::string &str, const wchar_t *pwszFileName)
}
}
+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;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
CMStringA directPath2url(const char *pszDirectPath)
{
return CMStringA("https://mmg.whatsapp.net") + pszDirectPath;
diff --git a/protocols/WhatsApp/src/utils.h b/protocols/WhatsApp/src/utils.h
index 778732e312..4b1d61a69e 100644
--- a/protocols/WhatsApp/src/utils.h
+++ b/protocols/WhatsApp/src/utils.h
@@ -153,6 +153,7 @@ public:
};
/////////////////////////////////////////////////////////////////////////////////////////
+// WAJid
struct WAJid
{
@@ -171,11 +172,38 @@ struct WAJid
};
/////////////////////////////////////////////////////////////////////////////////////////
+// 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 aesDecrypt(
const EVP_CIPHER *cipher,
const uint8_t *key,
@@ -186,6 +214,8 @@ MBinBuffer aesDecrypt(
uint32_t decodeBigEndian(const std::string &buf);
std::string encodeBigEndian(uint32_t num, size_t len = sizeof(uint32_t));
+void rtrim(std::string &str);
+
void generateIV(uint8_t *iv, int &pVar);
__forceinline bool operator<<(MessageLite &msg, const MBinBuffer &buf)