ZaStoGram_desktop/Telegram/SourceFiles/e2e_cloud/content/protected_message_body.cpp
2026-08-03 17:04:44 +03:00

230 lines
6.7 KiB
C++

/*
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 "e2e_cloud/content/protected_message_body.h"
#include <algorithm>
#include <array>
#include <limits>
namespace E2ECloud {
namespace {
inline constexpr auto kMagic = std::array<std::uint8_t, 8>{
'T', 'D', 'E', '2', 'E', 'M', 'S', 'G',
};
inline constexpr auto kHeaderSizeV1 = 8 + 2 + 8 + 4;
inline constexpr auto kHeaderSizeV2 = 8 + 2 + 1 + 8 + 32 + 4;
void AppendUint16(QByteArray &result, std::uint16_t value) {
result.append(char(value >> 8));
result.append(char(value));
}
void AppendUint32(QByteArray &result, std::uint32_t value) {
result.append(char(value >> 24));
result.append(char(value >> 16));
result.append(char(value >> 8));
result.append(char(value));
}
void AppendUint64(QByteArray &result, std::uint64_t value) {
for (auto shift = 56; shift >= 0; shift -= 8) {
result.append(char(value >> shift));
}
}
template <typename Array>
void AppendArray(QByteArray &result, const Array &value) {
result.append(
reinterpret_cast<const char*>(value.data()),
int(value.size()));
}
[[nodiscard]] std::uint16_t ReadUint16(const char *data) {
const auto bytes = reinterpret_cast<const std::uint8_t*>(data);
return (std::uint16_t(bytes[0]) << 8) | std::uint16_t(bytes[1]);
}
[[nodiscard]] std::uint32_t ReadUint32(const char *data) {
const auto bytes = reinterpret_cast<const std::uint8_t*>(data);
return (std::uint32_t(bytes[0]) << 24)
| (std::uint32_t(bytes[1]) << 16)
| (std::uint32_t(bytes[2]) << 8)
| std::uint32_t(bytes[3]);
}
[[nodiscard]] std::uint64_t ReadUint64(const char *data) {
auto result = std::uint64_t();
for (auto i = 0; i != 8; ++i) {
result = (result << 8) | std::uint8_t(data[i]);
}
return result;
}
[[nodiscard]] bool ValidUtf8(const QByteArray &bytes) {
if (bytes.isEmpty() || bytes.size() > kMaximumProtectedMessageTextSize) {
return false;
}
const auto data = reinterpret_cast<const std::uint8_t*>(
bytes.constData());
auto offset = 0;
while (offset != bytes.size()) {
const auto first = data[offset++];
if (!first) {
return false;
} else if (first < 0x80) {
continue;
}
auto continuationCount = 0;
auto minimum = std::uint32_t();
auto codepoint = std::uint32_t();
if ((first & 0xE0) == 0xC0) {
continuationCount = 1;
minimum = 0x80;
codepoint = first & 0x1F;
} else if ((first & 0xF0) == 0xE0) {
continuationCount = 2;
minimum = 0x800;
codepoint = first & 0x0F;
} else if ((first & 0xF8) == 0xF0) {
continuationCount = 3;
minimum = 0x10000;
codepoint = first & 0x07;
} else {
return false;
}
if (bytes.size() - offset < continuationCount) {
return false;
}
for (auto i = 0; i != continuationCount; ++i) {
const auto next = data[offset++];
if ((next & 0xC0) != 0x80) {
return false;
}
codepoint = (codepoint << 6) | (next & 0x3F);
}
if (codepoint < minimum
|| codepoint > 0x10FFFF
|| (codepoint >= 0xD800 && codepoint <= 0xDFFF)) {
return false;
}
}
return true;
}
} // namespace
std::optional<QByteArray> ProtectedMessageBodyCodecV1::encodePlaintext(
const ProtectedMessageBody &body) const {
if (!body.unixTime
|| body.unixTime
> std::uint64_t(std::numeric_limits<std::int64_t>::max())) {
return std::nullopt;
}
if (body.action == ProtectedMessageAction::Create) {
if (body.targetEventObjectId || !ValidUtf8(body.textUtf8)) {
return std::nullopt;
}
auto result = QByteArray();
result.reserve(kHeaderSizeV1 + body.textUtf8.size());
AppendArray(result, kMagic);
AppendUint16(result, 1);
AppendUint64(result, body.unixTime);
AppendUint32(result, std::uint32_t(body.textUtf8.size()));
result.append(body.textUtf8);
return result;
} else if (!body.targetEventObjectId
|| (body.action == ProtectedMessageAction::Edit
&& !ValidUtf8(body.textUtf8))
|| (body.action == ProtectedMessageAction::Delete
&& !body.textUtf8.isEmpty())
|| (body.action != ProtectedMessageAction::Edit
&& body.action != ProtectedMessageAction::Delete)) {
return std::nullopt;
}
auto result = QByteArray();
result.reserve(kHeaderSizeV2 + body.textUtf8.size());
AppendArray(result, kMagic);
AppendUint16(result, 2);
result.append(char(body.action));
AppendUint64(result, body.unixTime);
AppendArray(result, body.targetEventObjectId.bytes);
AppendUint32(result, std::uint32_t(body.textUtf8.size()));
result.append(body.textUtf8);
return result;
}
std::optional<ProtectedMessageBody>
ProtectedMessageBodyCodecV1::decodePlaintext(
const QByteArray &bytes,
ProtectedMessageBodyLayout) const {
if (bytes.size() < kHeaderSizeV1
|| !std::equal(
begin(kMagic),
end(kMagic),
reinterpret_cast<const std::uint8_t*>(bytes.constData()))) {
return std::nullopt;
}
const auto version = ReadUint16(bytes.constData() + 8);
if (version == 1) {
const auto unixTime = ReadUint64(bytes.constData() + 10);
const auto size = ReadUint32(bytes.constData() + 18);
if (!unixTime
|| unixTime
> std::uint64_t(std::numeric_limits<std::int64_t>::max())
|| size > kMaximumProtectedMessageTextSize
|| bytes.size() != kHeaderSizeV1 + int(size)) {
return std::nullopt;
}
auto body = ProtectedMessageBody{
.unixTime = unixTime,
.textUtf8 = QByteArray(
bytes.constData() + kHeaderSizeV1,
int(size)),
};
return ValidUtf8(body.textUtf8)
? std::optional<ProtectedMessageBody>(std::move(body))
: std::nullopt;
} else if (version != 2 || bytes.size() < kHeaderSizeV2) {
return std::nullopt;
}
const auto action = ProtectedMessageAction(
std::uint8_t(bytes.constData()[10]));
const auto unixTime = ReadUint64(bytes.constData() + 11);
auto targetEventObjectId = ObjectId();
std::copy_n(
reinterpret_cast<const std::uint8_t*>(bytes.constData() + 19),
targetEventObjectId.bytes.size(),
targetEventObjectId.bytes.begin());
const auto size = ReadUint32(bytes.constData() + 51);
if (!unixTime
|| unixTime
> std::uint64_t(std::numeric_limits<std::int64_t>::max())
|| !targetEventObjectId
|| size > kMaximumProtectedMessageTextSize
|| bytes.size() != kHeaderSizeV2 + int(size)) {
return std::nullopt;
}
auto body = ProtectedMessageBody{
.action = action,
.unixTime = unixTime,
.targetEventObjectId = targetEventObjectId,
.textUtf8 = QByteArray(
bytes.constData() + kHeaderSizeV2,
int(size)),
};
const auto valid = (body.action == ProtectedMessageAction::Edit)
? ValidUtf8(body.textUtf8)
: (body.action == ProtectedMessageAction::Delete)
&& body.textUtf8.isEmpty();
return valid
? std::optional<ProtectedMessageBody>(std::move(body))
: std::nullopt;
}
} // namespace E2ECloud