/* Facebook plugin for Miranda NG Copyright © 2019-24 Miranda NG team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "stdafx.h" uint8_t *FacebookProto::doZip(size_t cbData, const void *pData, size_t &cbRes) { size_t dataSize = cbData + 100; uint8_t *pRes = (uint8_t *)mir_alloc(dataSize); z_stream zStreamOut = {}; deflateInit(&zStreamOut, Z_BEST_COMPRESSION); zStreamOut.avail_in = (unsigned)cbData; zStreamOut.next_in = (uint8_t *)pData; zStreamOut.avail_out = (unsigned)dataSize; zStreamOut.next_out = (uint8_t *)pRes; deflate(&zStreamOut, Z_FINISH); deflateEnd(&zStreamOut); cbRes = dataSize - zStreamOut.avail_out; return pRes; } uint8_t *FacebookProto::doUnzip(size_t cbData, const void *pData, size_t &cbRes) { size_t dataSize = cbData * 10; uint8_t *pRes = (uint8_t *)mir_alloc(dataSize); z_stream zStreamOut = {}; inflateInit(&zStreamOut); zStreamOut.avail_in = (unsigned)cbData; zStreamOut.next_in = (uint8_t *)pData; zStreamOut.avail_out = (unsigned)dataSize; zStreamOut.next_out = (uint8_t *)pRes; int rc = inflate(&zStreamOut, Z_FINISH); inflateEnd(&zStreamOut); switch (rc) { case Z_OK: case Z_STREAM_END: cbRes = dataSize - zStreamOut.avail_out; return pRes; } mir_free(pRes); cbRes = 0; return nullptr; } ///////////////////////////////////////////////////////////////////////////////////////// // MqttMessage class members MqttMessage::MqttMessage() : m_leadingByte(0) { } MqttMessage::MqttMessage(FbMqttMessageType type, uint8_t flags) { m_leadingByte = ((type & 0x0F) << 4) | (flags & 0x0F); } char* MqttMessage::readStr(const uint8_t *&pData) const { u_short len = ntohs(*(u_short *)pData); pData += sizeof(u_short); if (len == 0) return nullptr; char *res = (char*)mir_alloc(len + 1); memcpy(res, pData, len); res[len] = 0; pData += len; return res; } void MqttMessage::writeStr(const char *str) { size_t len = mir_strlen(str); writeInt16((uint16_t)len); writeBuf(str, len); } ///////////////////////////////////////////////////////////////////////////////////////// // MQTT functions bool FacebookProto::MqttParse(const MqttMessage &payload) { auto *pData = (const uint8_t *)payload.data(), *pBeg = pData; int flags = payload.getFlags(); uint16_t mid; switch (payload.getType()) { case FB_MQTT_MESSAGE_TYPE_CONNACK: if (pData[1] != 0) { // connection failed; int iErrorCode = ntohs(*(u_short *)pData); debugLogA("Login failed with error %d", iErrorCode); if (iErrorCode == 4) { // invalid login/password delSetting(DBKEY_TOKEN); m_szAuthToken.Empty(); ProtoBroadcastAck(0, ACKTYPE_LOGIN, ACKRESULT_FAILED, 0, LOGINERR_WRONGPASSWORD); } else ProtoBroadcastAck(0, ACKTYPE_LOGIN, ACKRESULT_FAILED, 0, LOGINERR_WRONGPROTOCOL); return false; } OnLoggedIn(); break; case FB_MQTT_MESSAGE_TYPE_PUBREL: mid = ntohs(*(u_short *)pData); pData += 2; { MqttMessage reply(FB_MQTT_MESSAGE_TYPE_PUBCOMP); reply.writeInt16(mid); MqttSend(reply); } break; case FB_MQTT_MESSAGE_TYPE_PUBLISH: char *str = payload.readStr(pData); if ((flags & FB_MQTT_MESSAGE_FLAG_QOS1) || (flags & FB_MQTT_MESSAGE_FLAG_QOS2)) { mid = ntohs(*(u_short *)pData); pData += 2; MqttMessage reply((flags & FB_MQTT_MESSAGE_FLAG_QOS1) ? FB_MQTT_MESSAGE_TYPE_PUBACK : FB_MQTT_MESSAGE_TYPE_PUBREC); reply.writeInt16(mid); MqttSend(reply); } OnPublish(str, pData, payload.size() - (pData - pBeg)); mir_free(str); break; } return true; } bool FacebookProto::MqttRead(MqttMessage &payload) { uint8_t b; int res = Netlib_Recv(m_mqttConn, (char *)&b, sizeof(b), MSG_NODUMP); if (res != 1) return false; payload.m_leadingByte = b; uint32_t m = 1, remainingBytes = 0; do { if ((res = Netlib_Recv(m_mqttConn, (char *)&b, sizeof(b), MSG_NODUMP)) != 1) return false; remainingBytes += (b & 0x7F) * m; m *= 128; } while ((b & 0x80) != 0); debugLogA("Received message of type=%d, flags=%x, body length=%d", payload.getType(), payload.getFlags(), remainingBytes); if (remainingBytes != 0) { while (remainingBytes > 0) { uint8_t buf[1024]; int size = min(remainingBytes, sizeof(buf)); if ((res = Netlib_Recv(m_mqttConn, (char *)buf, size)) <= 0) return false; payload.writeBuf(buf, res); remainingBytes -= res; } } return true; } void FacebookProto::MqttSend(const MqttMessage &payload) { FbThrift msg; msg << payload.m_leadingByte; msg.writeIntV(payload.size()); msg.writeBuf(payload.data(), payload.size()); Netlib_Send(m_mqttConn, (char*)msg.data(), (unsigned)msg.size()); } ///////////////////////////////////////////////////////////////////////////////////////// // creates initial MQTT will and sends initialization packet void FacebookProto::MqttLogin() { uint8_t zeroByte = 0; Utils_GetRandom(&m_iMqttId, sizeof(m_iMqttId) / 2); FbThrift thrift; thrift.writeField(FB_THRIFT_TYPE_STRING); // Client identifier thrift << m_szClientID; thrift.writeField(FB_THRIFT_TYPE_STRUCT, 4, 1); thrift.writeField(FB_THRIFT_TYPE_I64); // User identifier thrift.writeInt64(m_uid); thrift.writeField(FB_THRIFT_TYPE_STRING); // User agent thrift << GetAgentString(); thrift.writeField(FB_THRIFT_TYPE_I64); thrift.writeInt64(23); thrift.writeField(FB_THRIFT_TYPE_I64); thrift.writeInt64(26); thrift.writeField(FB_THRIFT_TYPE_I32); thrift.writeInt32(1); thrift.writeBool(true); thrift.writeBool(!m_bLoginInvisible); // visibility thrift.writeField(FB_THRIFT_TYPE_STRING); // device id thrift << m_szDeviceID; thrift.writeBool(true); thrift.writeField(FB_THRIFT_TYPE_I32); thrift.writeInt32(1); thrift.writeField(FB_THRIFT_TYPE_I32); thrift.writeInt32(0); thrift.writeField(FB_THRIFT_TYPE_I64); thrift.writeInt64(m_iMqttId); thrift.writeField(FB_THRIFT_TYPE_LIST, 14, 12); thrift.writeList(FB_THRIFT_TYPE_I32, 0); thrift << zeroByte; thrift.writeField(FB_THRIFT_TYPE_STRING); thrift << m_szAuthToken << zeroByte; size_t dataSize; mir_ptr pData(doZip(thrift.size(), thrift.data(), dataSize)); uint8_t protocolVersion = 3; uint8_t flags = FB_MQTT_CONNECT_FLAG_USER | FB_MQTT_CONNECT_FLAG_PASS | FB_MQTT_CONNECT_FLAG_CLR | FB_MQTT_CONNECT_FLAG_QOS1; MqttMessage payload(FB_MQTT_MESSAGE_TYPE_CONNECT); payload.writeStr("MQTToT"); payload << protocolVersion << flags; payload.writeInt16(60); // timeout payload.writeBuf(pData, dataSize); MqttSend(payload); } ///////////////////////////////////////////////////////////////////////////////////////// // various MQTT send commands void FacebookProto::MqttPing() { MqttMessage payload(FB_MQTT_MESSAGE_TYPE_PINGREQ, FB_MQTT_MESSAGE_FLAG_QOS1); MqttSend(payload); } void FacebookProto::MqttPublish(const char *topic, const JSONNode &value) { auto str = value.write(); debugLogA("Publish: <%s> -> <%s>", topic, str.c_str()); size_t dataSize; mir_ptr pData(doZip(str.length(), str.c_str(), dataSize)); MqttMessage payload(FB_MQTT_MESSAGE_TYPE_PUBLISH, FB_MQTT_MESSAGE_FLAG_QOS1); payload.writeStr(topic); payload.writeInt16(++m_mid); payload.writeBuf(pData, dataSize); MqttSend(payload); } void FacebookProto::MqttSubscribe(const char *topic, ...) { uint8_t zeroByte = 0; MqttMessage payload(FB_MQTT_MESSAGE_TYPE_SUBSCRIBE, FB_MQTT_MESSAGE_FLAG_QOS1); payload.writeInt16(++m_mid); payload.writeStr(topic); payload << zeroByte; va_list ap; va_start(ap, topic); while ((topic = va_arg(ap, const char *)) != nullptr) { payload.writeStr(topic); payload << zeroByte; } va_end(ap); MqttSend(payload); } void FacebookProto::MqttUnsubscribe(const char *topic, ...) { MqttMessage payload(FB_MQTT_MESSAGE_TYPE_UNSUBSCRIBE, FB_MQTT_MESSAGE_FLAG_QOS1); payload.writeInt16(++m_mid); payload.writeStr(topic); va_list ap; va_start(ap, topic); while ((topic = va_arg(ap, const char *)) != nullptr) payload.writeStr(topic); va_end(ap); MqttSend(payload); } ///////////////////////////////////////////////////////////////////////////////////////// // MQTT queue void FacebookProto::MqttQueueConnect() { JSONNode query; query << INT_PARAM("delta_batch_size", 125) << INT_PARAM("max_deltas_able_to_process", 1000) << INT_PARAM("sync_api_version", 3) << CHAR_PARAM("encoding", "JSON"); if (m_szSyncToken.IsEmpty()) { JSONNode hashes; hashes.set_name("graphql_query_hashes"); hashes << CHAR_PARAM("xma_query_id", __STRINGIFY(FB_API_QUERY_XMA)); JSONNode xma; xma.set_name(__STRINGIFY(FB_API_QUERY_XMA)); xma << CHAR_PARAM("xma_id", ""); JSONNode hql; hql.set_name("graphql_query_params"); hql << xma; JSONNode params; params.set_name("queue_params"); params << CHAR_PARAM("buzz_on_deltas_enabled", "false") << hashes << hql; query << INT64_PARAM("initial_titan_sequence_id", m_sid) << CHAR_PARAM("device_id", m_szDeviceID) << INT64_PARAM("entity_fbid", m_uid) << params; MqttPublish("/messenger_sync_create_queue", query); } else { query << INT64_PARAM("last_seq_id", m_sid) << CHAR_PARAM("sync_token", m_szSyncToken); MqttPublish("/messenger_sync_get_diffs", query); } }