mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2026-07-25 06:54:43 +00:00
Support e2e encrypted conference messages.
This commit is contained in:
@@ -363,6 +363,8 @@ PRIVATE
|
||||
calls/group/calls_group_members_row.h
|
||||
calls/group/calls_group_menu.cpp
|
||||
calls/group/calls_group_menu.h
|
||||
calls/group/calls_group_message_encryption.cpp
|
||||
calls/group/calls_group_message_encryption.h
|
||||
calls/group/calls_group_message_field.cpp
|
||||
calls/group/calls_group_message_field.h
|
||||
calls/group/calls_group_messages.cpp
|
||||
|
||||
@@ -0,0 +1,357 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "calls/group/calls_group_message_encryption.h"
|
||||
|
||||
#include <QtCore/QJsonValue>
|
||||
#include <QtCore/QJsonObject>
|
||||
#include <QtCore/QJsonArray>
|
||||
#include <QtCore/QJsonDocument>
|
||||
|
||||
namespace Calls::Group {
|
||||
namespace {
|
||||
|
||||
//[[nodiscard]] MTPJSONValue String(const QByteArray &value) {
|
||||
// return MTP_jsonString(MTP_bytes(value));
|
||||
//}
|
||||
//
|
||||
//[[nodiscard]] MTPJSONValue Int(int value) {
|
||||
// return MTP_jsonNumber(MTP_double(value));
|
||||
//}
|
||||
//
|
||||
//[[nodiscard]] MTPJSONObjectValue Value(
|
||||
// const QByteArray &name,
|
||||
// const MTPJSONValue &value) {
|
||||
// return MTP_jsonObjectValue(MTP_bytes(name), value);
|
||||
//}
|
||||
//
|
||||
//[[nodiscard]] MTPJSONValue Object(
|
||||
// const QByteArray &cons,
|
||||
// QVector<MTPJSONObjectValue> &&values) {
|
||||
// values.insert(values.begin(), Value("_", String(cons)));
|
||||
// return MTP_jsonObject(MTP_vector<MTPJSONObjectValue>(std::move(values)));
|
||||
//}
|
||||
//
|
||||
//[[nodiscard]] MTPJSONValue Array(QVector<MTPJSONValue> &&values) {
|
||||
// return MTP_jsonArray(MTP_vector<MTPJSONValue>(std::move(values)));
|
||||
//}
|
||||
//
|
||||
//template <typename MTPD>
|
||||
//[[nodiscard]] MTPJSONValue SimpleEntity(
|
||||
// const QByteArray &name,
|
||||
// const MTPD &data) {
|
||||
// return Object(name, {
|
||||
// Value("offset", Int(data.voffset().v)),
|
||||
// Value("length", Int(data.vlength().v)),
|
||||
// });
|
||||
//}
|
||||
//
|
||||
//[[nodiscard]] MTPJSONValue Entity(const MTPMessageEntity &entity) {
|
||||
// return entity.match([](const MTPDmessageEntityBold &data) {
|
||||
// return SimpleEntity("messageEntityBold", data);
|
||||
// }, [](const MTPDmessageEntityItalic &data) {
|
||||
// return SimpleEntity("messageEntityItalic", data);
|
||||
// }, [](const MTPDmessageEntityUnderline &data) {
|
||||
// return SimpleEntity("messageEntityUnderline", data);
|
||||
// }, [](const MTPDmessageEntityStrike &data) {
|
||||
// return SimpleEntity("messageEntityStrike", data);
|
||||
// }, [](const MTPDmessageEntitySpoiler &data) {
|
||||
// return SimpleEntity("messageEntitySpoiler", data);
|
||||
// }, [](const MTPDmessageEntityCustomEmoji &data) {
|
||||
// return Object("messageEntityCustomEmoji", {
|
||||
// Value("offset", Int(data.voffset().v)),
|
||||
// Value("length", Int(data.vlength().v)),
|
||||
// Value(
|
||||
// "document_id",
|
||||
// String(QByteArray::number(int64(data.vdocument_id().v)))),
|
||||
// });
|
||||
// }, [](const auto &data) {
|
||||
// return MTP_jsonNull();
|
||||
// });
|
||||
//}
|
||||
//
|
||||
//[[nodiscard]] QVector<MTPJSONValue> Entities(
|
||||
// const QVector<MTPMessageEntity> &list) {
|
||||
// auto result = QVector<MTPJSONValue>();
|
||||
// result.reserve(list.size());
|
||||
// for (const auto &entity : list) {
|
||||
// if (const auto e = Entity(entity); e.type() != mtpc_jsonNull) {
|
||||
// result.push_back(e);
|
||||
// }
|
||||
// }
|
||||
// return result;
|
||||
//}
|
||||
//
|
||||
//[[nodiscard]] QByteArray Serialize(const MTPJSONValue &value) {
|
||||
// auto counter = ::tl::details::LengthCounter();
|
||||
// value.write(counter);
|
||||
// auto buffer = mtpBuffer();
|
||||
// buffer.reserve(counter.length);
|
||||
// value.write(buffer);
|
||||
// return QByteArray(
|
||||
// reinterpret_cast<const char*>(buffer.constData()),
|
||||
// buffer.size() * sizeof(buffer.front()));
|
||||
//}
|
||||
|
||||
[[nodiscard]] QJsonValue String(const QByteArray &value) {
|
||||
return QJsonValue(QString::fromUtf8(value));
|
||||
}
|
||||
|
||||
[[nodiscard]] QJsonValue Int(int value) {
|
||||
return QJsonValue(double(value));
|
||||
}
|
||||
|
||||
struct JsonObjectValue {
|
||||
const char *name = nullptr;
|
||||
QJsonValue value;
|
||||
};
|
||||
|
||||
[[nodiscard]] JsonObjectValue Value(
|
||||
const char *name,
|
||||
const QJsonValue &value) {
|
||||
return JsonObjectValue{ name, value };
|
||||
}
|
||||
|
||||
[[nodiscard]] QJsonValue Object(
|
||||
const char *cons,
|
||||
QVector<JsonObjectValue> &&values) {
|
||||
auto result = QJsonObject();
|
||||
result.insert("_", cons);
|
||||
for (const auto &value : values) {
|
||||
result.insert(value.name, value.value);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] QJsonValue Array(QVector<QJsonValue> &&values) {
|
||||
auto result = QJsonArray();
|
||||
for (const auto &value : values) {
|
||||
result.push_back(value);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename MTPD>
|
||||
[[nodiscard]] QJsonValue SimpleEntity(
|
||||
const char *name,
|
||||
const MTPD &data) {
|
||||
return Object(name, {
|
||||
Value("offset", Int(data.voffset().v)),
|
||||
Value("length", Int(data.vlength().v)),
|
||||
});
|
||||
}
|
||||
|
||||
[[nodiscard]] QJsonValue Entity(const MTPMessageEntity &entity) {
|
||||
return entity.match([](const MTPDmessageEntityBold &data) {
|
||||
return SimpleEntity("messageEntityBold", data);
|
||||
}, [](const MTPDmessageEntityItalic &data) {
|
||||
return SimpleEntity("messageEntityItalic", data);
|
||||
}, [](const MTPDmessageEntityUnderline &data) {
|
||||
return SimpleEntity("messageEntityUnderline", data);
|
||||
}, [](const MTPDmessageEntityStrike &data) {
|
||||
return SimpleEntity("messageEntityStrike", data);
|
||||
}, [](const MTPDmessageEntitySpoiler &data) {
|
||||
return SimpleEntity("messageEntitySpoiler", data);
|
||||
}, [](const MTPDmessageEntityCustomEmoji &data) {
|
||||
return Object("messageEntityCustomEmoji", {
|
||||
Value("offset", Int(data.voffset().v)),
|
||||
Value("length", Int(data.vlength().v)),
|
||||
Value(
|
||||
"document_id",
|
||||
String(QByteArray::number(int64(data.vdocument_id().v)))),
|
||||
});
|
||||
}, [](const auto &data) {
|
||||
return QJsonValue(QJsonValue::Null);
|
||||
});
|
||||
}
|
||||
|
||||
[[nodiscard]] QVector<QJsonValue> Entities(
|
||||
const QVector<MTPMessageEntity> &list) {
|
||||
auto result = QVector<QJsonValue>();
|
||||
result.reserve(list.size());
|
||||
for (const auto &entity : list) {
|
||||
if (const auto e = Entity(entity); !e.isNull()) {
|
||||
result.push_back(e);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] QByteArray Serialize(const QJsonValue &value) {
|
||||
return QJsonDocument(value.toObject()).toJson(QJsonDocument::Compact);
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<QJsonValue> GetValue(
|
||||
const QJsonObject &object,
|
||||
const char *name) {
|
||||
const auto i = object.find(name);
|
||||
return (i != object.end()) ? *i : std::optional<QJsonValue>();
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<int> GetInt(
|
||||
const QJsonObject &object,
|
||||
const char *name) {
|
||||
if (const auto maybeValue = GetValue(object, name)) {
|
||||
if (maybeValue->isDouble()) {
|
||||
return int(base::SafeRound(maybeValue->toDouble()));
|
||||
} else if (maybeValue->isString()) {
|
||||
auto ok = false;
|
||||
const auto result = maybeValue->toString().toInt(&ok);
|
||||
return ok ? result : std::optional<int>();
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<uint64> GetLong(
|
||||
const QJsonObject &object,
|
||||
const char *name) {
|
||||
if (const auto maybeValue = GetValue(object, name)) {
|
||||
if (maybeValue->isDouble()) {
|
||||
const auto value = maybeValue->toDouble();
|
||||
return (value >= 0.)
|
||||
? uint64(base::SafeRound(value))
|
||||
: std::optional<uint64>();
|
||||
} else if (maybeValue->isString()) {
|
||||
auto ok = false;
|
||||
const auto result = maybeValue->toString().toLongLong(&ok);
|
||||
return ok ? uint64(result) : std::optional<uint64>();
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
[[nodiscard]] std::optional<QString> GetString(
|
||||
const QJsonObject &object,
|
||||
const char *name) {
|
||||
const auto maybeValue = GetValue(object, name);
|
||||
return (maybeValue && maybeValue->isString())
|
||||
? maybeValue->toString()
|
||||
: std::optional<QString>();
|
||||
}
|
||||
|
||||
|
||||
[[nodiscard]] std::optional<QString> GetCons(const QJsonObject &object) {
|
||||
return GetString(object, "_");
|
||||
}
|
||||
|
||||
[[nodiscard]] bool Unsupported(
|
||||
const QJsonObject &object,
|
||||
const QString &cons = QString()) {
|
||||
const auto maybeMinLayer = GetInt(object, "_min_layer");
|
||||
const auto layer = int(MTP::details::kCurrentLayer);
|
||||
if (maybeMinLayer.value_or(layer) > layer) {
|
||||
LOG(("E2E Error: _min_layer too large: %1 > %2").arg(*maybeMinLayer).arg(layer));
|
||||
return true;
|
||||
} else if (!cons.isEmpty() && GetCons(object) != cons) {
|
||||
LOG(("E2E Error: Expected %1 here.").arg(cons));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<MTPMessageEntity> GetEntity(
|
||||
const QString &text,
|
||||
const QJsonObject &object) {
|
||||
const auto cons = GetCons(object).value_or(QString());
|
||||
const auto offset = GetInt(object, "offset").value_or(-1);
|
||||
const auto length = GetInt(object, "length").value_or(0);
|
||||
if (Unsupported(object)
|
||||
|| (offset < 0)
|
||||
|| (length <= 0)
|
||||
|| (offset >= text.size())
|
||||
|| (length > text.size())
|
||||
|| (offset + length > text.size())) {
|
||||
return {};
|
||||
}
|
||||
const auto simple = [&](const auto &make) {
|
||||
return make(MTP_int(offset), MTP_int(length));
|
||||
};
|
||||
if (cons == "messageEntityBold") {
|
||||
return simple(MTP_messageEntityBold);
|
||||
} else if (cons == "messageEntityItalic") {
|
||||
return simple(MTP_messageEntityItalic);
|
||||
} else if (cons == "messageEntityUnderline") {
|
||||
return simple(MTP_messageEntityUnderline);
|
||||
} else if (cons == "messageEntityStrike") {
|
||||
return simple(MTP_messageEntityStrike);
|
||||
} else if (cons == "messageEntitySpoiler") {
|
||||
return simple(MTP_messageEntitySpoiler);
|
||||
} else if (cons == "messageEntityCustomEmoji") {
|
||||
const auto maybeDocumentId = GetLong(object, "document_id");
|
||||
if (const auto documentId = maybeDocumentId.value_or(0)) {
|
||||
return MTP_messageEntityCustomEmoji(
|
||||
MTP_int(offset),
|
||||
MTP_int(length),
|
||||
MTP_long(documentId));
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
[[nodiscard]] QVector<MTPMessageEntity> GetEntities(
|
||||
const QString &text,
|
||||
const QJsonArray &list) {
|
||||
auto result = QVector<MTPMessageEntity>();
|
||||
result.reserve(list.size());
|
||||
for (const auto &entry : list) {
|
||||
if (const auto entity = GetEntity(text, entry.toObject())) {
|
||||
result.push_back(*entity);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
QByteArray SerializeMessage(const MTPTextWithEntities &text) {
|
||||
return Serialize(Object("groupCallMessage", {
|
||||
Value(
|
||||
"message",
|
||||
Object("textWithEntities", {
|
||||
Value("text", String(text.data().vtext().v)),
|
||||
Value(
|
||||
"entities",
|
||||
Array(Entities(text.data().ventities().v))),
|
||||
})),
|
||||
}));
|
||||
}
|
||||
|
||||
std::optional<MTPTextWithEntities> DeserializeMessage(
|
||||
const QByteArray &data) {
|
||||
auto error = QJsonParseError();
|
||||
auto document = QJsonDocument::fromJson(data, &error);
|
||||
if (error.error != QJsonParseError::NoError
|
||||
|| !document.isObject()) {
|
||||
LOG(("E2E Error: Bad json in Calls::Group::DeserializeMessage."));
|
||||
return {};
|
||||
}
|
||||
const auto groupCallMessage = document.object();
|
||||
if (Unsupported(groupCallMessage, "groupCallMessage")) {
|
||||
return {};
|
||||
}
|
||||
const auto message = groupCallMessage["message"].toObject();
|
||||
if (Unsupported(message, "textWithEntities")) {
|
||||
return {};
|
||||
}
|
||||
const auto maybeText = GetString(message, "text");
|
||||
if (!maybeText) {
|
||||
return {};
|
||||
}
|
||||
const auto &text = *maybeText;
|
||||
const auto maybeEntities = GetValue(message, "entities");
|
||||
if (!maybeEntities || !maybeEntities->isArray()) {
|
||||
return {};
|
||||
}
|
||||
const auto entities = GetEntities(text, maybeEntities->toArray());
|
||||
return MTP_textWithEntities(
|
||||
MTP_string(text),
|
||||
MTP_vector<MTPMessageEntity>(entities));
|
||||
}
|
||||
|
||||
} // namespace Calls::Group
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace Calls::Group {
|
||||
|
||||
[[nodiscard]] QByteArray SerializeMessage(const MTPTextWithEntities &text);
|
||||
[[nodiscard]] std::optional<MTPTextWithEntities> DeserializeMessage(
|
||||
const QByteArray &data);
|
||||
|
||||
} // namespace Calls::Group
|
||||
@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "api/api_text_entities.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "calls/group/calls_group_call.h"
|
||||
#include "calls/group/calls_group_message_encryption.h"
|
||||
#include "data/data_group_call.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_session.h"
|
||||
@@ -87,14 +88,9 @@ void Messages::send(TextWithTags text) {
|
||||
failed(id, response);
|
||||
}).send();
|
||||
} else {
|
||||
auto counter = ::tl::details::LengthCounter();
|
||||
serialized.write(counter);
|
||||
auto buffer = mtpBuffer();
|
||||
buffer.reserve(counter.length);
|
||||
serialized.write(buffer);
|
||||
const auto view = bytes::make_span(buffer);
|
||||
auto v = std::vector<std::uint8_t>(view.size());
|
||||
bytes::copy(bytes::make_span(v), view);
|
||||
const auto bytes = SerializeMessage(serialized);
|
||||
auto v = std::vector<std::uint8_t>(bytes.size());
|
||||
bytes::copy(bytes::make_span(v), bytes::make_span(bytes));
|
||||
|
||||
const auto userId = peerToUser(from->id).bare;
|
||||
const auto encrypt = _call->e2eEncryptDecrypt();
|
||||
@@ -132,19 +128,15 @@ void Messages::received(const MTPDupdateGroupCallEncryptedMessage &data) {
|
||||
const auto userId = peerToUser(peerFromMTP(fromId)).bare;
|
||||
const auto decrypt = _call->e2eEncryptDecrypt();
|
||||
const auto decrypted = decrypt(v, int64_t(userId), false, 0);
|
||||
if (decrypted.empty() || decrypted.size() % 4 != 0) {
|
||||
LOG(("API Error: Wrong decrypted message size: %1"
|
||||
).arg(decrypted.size()));
|
||||
return;
|
||||
}
|
||||
auto info = reinterpret_cast<const mtpPrime*>(decrypted.data());
|
||||
|
||||
auto text = MTPTextWithEntities();
|
||||
if (!text.read(info, info + (decrypted.size() / 4))) {
|
||||
const auto deserialized = DeserializeMessage(QByteArray::fromRawData(
|
||||
reinterpret_cast<const char*>(decrypted.data()),
|
||||
decrypted.size()));
|
||||
if (!deserialized) {
|
||||
LOG(("API Error: Can't parse decrypted message"));
|
||||
return;
|
||||
}
|
||||
received(fromId, text);
|
||||
received(fromId, *deserialized);
|
||||
pushChanges();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user