ZaStoGram_desktop/Telegram/SourceFiles/tests/test_e2e_cloud.cpp
loop-uh 7ec3f7f5f9 Exercise the E2E cloud slice from seven directions
Every state machine in the new slice claims to fail closed; a claim
like that is only worth what its negative tests cost. Seven standalone
test executables — envelope and freshness, group transitions,
identity and signatures, inbound processing, storage protectors, the
vault, and file chunks — feed each boundary its malformed, replayed,
truncated, and conflicting inputs alongside the happy paths. They hang
off the Telegram target as build dependencies, so a slice that stops
compiling or a vector that stops matching turns up in every build, not
on the day someone remembers to run the tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-31 22:29:46 +03:00

771 lines
24 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/core/envelope.h"
#include "e2e_cloud/core/envelope_codec.h"
#include "e2e_cloud/core/freshness_gate.h"
#include "e2e_cloud/core/outbox.h"
#include "e2e_cloud/group/group_state.h"
#include "e2e_cloud/transport/outbox_upload_controller.h"
#include "e2e_cloud/transport/telegram_carrier_transport.h"
#include <cstdio>
#include <optional>
#include <utility>
#include <vector>
namespace {
using namespace E2ECloud;
template <typename Id>
[[nodiscard]] Id FilledId(std::uint8_t value) {
auto result = Id();
result.bytes.fill(value);
return result;
}
[[nodiscard]] int Fail(const char *message) {
std::fprintf(stderr, "%s\n", message);
return 1;
}
[[nodiscard]] Checkpoint MakeCheckpoint(
ConversationId conversationId,
std::uint64_t generation,
std::uint8_t hashValue) {
return {
.conversationId = conversationId,
.generation = generation,
.stateHash = FilledId<Digest>(hashValue),
};
}
[[nodiscard]] FreshnessResponse MakeResponse(
const Checkpoint &checkpoint,
ChallengeNonce nonce) {
return {
.conversationId = checkpoint.conversationId,
.nonce = nonce,
.checkpoint = checkpoint,
.witnessAccountId = FilledId<AccountId>(4),
.witnessClientId = FilledId<ClientId>(5),
.authenticatedProof = QByteArray("proof"),
};
}
class TestVerifier final : public FreshnessResponseVerifier {
public:
[[nodiscard]] bool verify(
const FreshnessResponse &response) const override {
++calls;
return accept && !response.authenticatedProof.isEmpty();
}
bool accept = true;
mutable int calls = 0;
};
class TestOutboxStore final : public ProtectedOutboxStore {
public:
bool append(PendingMessage message) override {
if (failAppend) {
return false;
}
for (const auto &item : items) {
if (item.draft.objectId == message.objectId) {
return false;
}
}
items.push_back({
.draft = std::move(message),
.stage = OutboxItemStage::Draft,
.sealed = std::nullopt,
});
return true;
}
[[nodiscard]] std::optional<OutboxItem> front(
ConversationId conversationId) const override {
for (const auto &item : items) {
if (item.draft.conversationId == conversationId) {
return item;
}
}
return std::nullopt;
}
bool replaceWithSealed(
ObjectId objectId,
EncodedEnvelope envelope) override {
if (failReplace) {
return false;
}
for (auto &item : items) {
if (item.draft.objectId == objectId) {
item.stage = OutboxItemStage::Sealed;
item.sealed = std::move(envelope);
item.draft.plaintext.clear();
item.draft.authenticatedData.clear();
return true;
}
}
return false;
}
bool remove(ObjectId objectId) override {
if (failRemove) {
return false;
}
for (auto i = items.begin(); i != items.end(); ++i) {
if (i->draft.objectId == objectId) {
items.erase(i);
return true;
}
}
return false;
}
std::vector<OutboxItem> items;
bool failAppend = false;
bool failReplace = false;
bool failRemove = false;
};
class TestProtector final : public OutboundMessageProtector {
public:
[[nodiscard]] std::optional<EncodedEnvelope> protectIdempotently(
const MlsSealRequest &request) override {
++calls;
if (fail) {
return std::nullopt;
}
return EncodedEnvelope{
.conversationId = wrongConversation
? FilledId<ConversationId>(99)
: request.conversationId,
.objectId = request.objectId,
.bytes = QByteArray("sealed:") + request.plaintext,
};
}
int calls = 0;
bool fail = false;
bool wrongConversation = false;
};
class TestTransport final : public TelegramTransport {
public:
void uploadExact(
EncodedEnvelope envelope,
UploadCallback callback) override {
uploads.push_back(std::move(envelope));
callbacks.push_back(std::move(callback));
}
void download(
ConversationId,
DownloadCallback callback) override {
callback({
.result = UploadResult::Accepted,
.untrustedObjects = {},
});
}
void finish(std::size_t index, UploadResult result) {
callbacks.at(index)(result);
}
std::vector<EncodedEnvelope> uploads;
std::vector<UploadCallback> callbacks;
};
class TestCarrierBackend final : public TelegramCarrierBackend {
public:
void uploadDocument(
QByteArray bytes,
QString filename,
QString mimeType,
UploadCallback callback) override {
uploadedBytes.push_back(std::move(bytes));
filenames.push_back(std::move(filename));
mimeTypes.push_back(std::move(mimeType));
uploadCallbacks.push_back(std::move(callback));
}
void sendUploadedDocument(
std::uint64_t telegramPeerId,
UploadedCarrierFile file,
QString filename,
QString mimeType,
SendCallback callback) override {
peerIds.push_back(telegramPeerId);
tokens.push_back(std::move(file.backendToken));
filenames.push_back(std::move(filename));
mimeTypes.push_back(std::move(mimeType));
sendCallbacks.push_back(std::move(callback));
}
void downloadDocuments(
std::uint64_t telegramPeerId,
DownloadCallback callback) override {
peerIds.push_back(telegramPeerId);
downloadCallbacks.push_back(std::move(callback));
}
std::vector<QByteArray> uploadedBytes;
std::vector<QString> filenames;
std::vector<QString> mimeTypes;
std::vector<std::uint64_t> peerIds;
std::vector<QByteArray> tokens;
std::vector<UploadCallback> uploadCallbacks;
std::vector<SendCallback> sendCallbacks;
std::vector<DownloadCallback> downloadCallbacks;
};
[[nodiscard]] PendingMessage MakeMessage(ConversationId conversationId) {
return {
.conversationId = conversationId,
.objectId = FilledId<ObjectId>(7),
.plaintext = QByteArray("message"),
.authenticatedData = QByteArray("metadata"),
};
}
[[nodiscard]] TransportEnvelope MakeEnvelope() {
return {
.conversationId = FilledId<ConversationId>(1),
.objectKind = ObjectKind::MlsApplication,
.senderAccountId = FilledId<AccountId>(2),
.senderClientId = FilledId<ClientId>(3),
.telegramPeerIdBinding = 42,
.epochOrGeneration = 8,
.objectId = FilledId<ObjectId>(4),
.payloadHash = FilledId<Digest>(5),
.payload = QByteArray("payload"),
.authenticationData = QByteArray("authenticated"),
};
}
[[nodiscard]] int ScenarioEnvelopeValidation() {
auto envelope = MakeEnvelope();
if (ValidateEnvelope(envelope) != EnvelopeValidationError::None) {
return Fail("valid transport envelope was rejected");
}
envelope.applicationProtocolVersion = kApplicationProtocolVersion + 1;
if (ValidateEnvelope(envelope)
!= EnvelopeValidationError::UnsupportedVersion) {
return Fail("unsupported transport envelope version was accepted");
}
envelope.applicationProtocolVersion = kApplicationProtocolVersion;
envelope.objectKind = static_cast<ObjectKind>(1000);
if (ValidateEnvelope(envelope)
!= EnvelopeValidationError::UnknownObjectKind) {
return Fail("unknown transport envelope kind was accepted");
}
return 0;
}
[[nodiscard]] int ScenarioHistoryAccessBoundary() {
if (!IsValidHistoryAccess({
.mode = HistoryAccessMode::None,
.boundaryEventId = {},
})
|| !IsValidHistoryAccess({
.mode = HistoryAccessMode::FromJoin,
.boundaryEventId = {},
})
|| !IsValidHistoryAccess({
.mode = HistoryAccessMode::Full,
.boundaryEventId = {},
})
|| IsValidHistoryAccess({
.mode = HistoryAccessMode::Since,
.boundaryEventId = {},
})
|| !IsValidHistoryAccess({
.mode = HistoryAccessMode::Since,
.boundaryEventId = FilledId<ObjectId>(3),
})) {
return Fail("history boundary accepted an ambiguous policy");
}
return 0;
}
[[nodiscard]] int ScenarioEnvelopeCodec() {
const auto envelope = MakeEnvelope();
const auto codec = EnvelopeCodecV1();
const auto first = codec.encode(envelope);
const auto second = codec.encode(envelope);
if (!first
|| first != second
|| codec.decode(*first) != envelope
|| first->bytes.size() != 202
|| std::uint8_t(first->bytes[11]) != 1
|| std::uint8_t(first->bytes[45]) != 8
|| std::uint8_t(first->bytes[101]) != 42
|| std::uint8_t(first->bytes[109]) != 8
|| std::uint8_t(first->bytes[177]) != 7
|| std::uint8_t(first->bytes[188]) != 13) {
return Fail("version one envelope codec was not deterministic");
}
auto trailing = *first;
trailing.bytes.append('x');
if (codec.decode(trailing)) {
return Fail("envelope codec accepted trailing bytes");
}
auto mismatched = *first;
mismatched.objectId = FilledId<ObjectId>(99);
if (codec.decode(mismatched)) {
return Fail("envelope codec accepted a mismatched transport identity");
}
auto truncated = *first;
truncated.bytes.chop(1);
if (codec.decode(truncated)) {
return Fail("envelope codec accepted truncated authentication data");
}
return 0;
}
[[nodiscard]] int ScenarioFreshnessConfirmation() {
const auto conversationId = FilledId<ConversationId>(1);
const auto checkpoint = MakeCheckpoint(conversationId, 8, 2);
const auto nonce = FilledId<ChallengeNonce>(3);
auto gate = FreshnessGate(checkpoint);
auto verifier = TestVerifier();
if (gate.sendingAllowed()
|| gate.administrationAllowed()
|| !gate.beginChallenge(nonce)) {
return Fail("freshness gate opened before witness confirmation");
}
auto wrong = MakeResponse(checkpoint, FilledId<ChallengeNonce>(9));
if (gate.acceptResponse(wrong, verifier)
!= FreshnessResponseResult::WrongChallenge
|| verifier.calls
|| gate.state() != FreshnessState::WaitingForWitness) {
return Fail("wrong freshness challenge changed gate state");
}
const auto response = MakeResponse(checkpoint, nonce);
if (gate.acceptResponse(response, verifier)
!= FreshnessResponseResult::Accepted
|| verifier.calls != 1
|| !gate.sendingAllowed()
|| !gate.administrationAllowed()) {
return Fail("valid freshness witness did not open the gate");
}
return 0;
}
[[nodiscard]] int ScenarioFreshnessResynchronization() {
const auto conversationId = FilledId<ConversationId>(1);
const auto known = MakeCheckpoint(conversationId, 8, 2);
const auto current = MakeCheckpoint(conversationId, 10, 4);
const auto nonce = FilledId<ChallengeNonce>(3);
auto gate = FreshnessGate(known);
auto verifier = TestVerifier();
if (!gate.beginChallenge(nonce)
|| gate.acceptResponse(MakeResponse(current, nonce), verifier)
!= FreshnessResponseResult::ResynchronizationRequired
|| gate.sendingAllowed()
|| gate.administrationAllowed()
|| gate.resynchronizationTarget() != current
|| gate.completeResynchronization(known)
|| !gate.completeResynchronization(current)
|| !gate.sendingAllowed()
|| gate.knownCheckpoint() != current) {
return Fail("newer witness state bypassed required resynchronization");
}
return 0;
}
[[nodiscard]] int ScenarioFreshnessFork() {
const auto conversationId = FilledId<ConversationId>(1);
const auto known = MakeCheckpoint(conversationId, 8, 2);
const auto fork = MakeCheckpoint(conversationId, 8, 9);
const auto nonce = FilledId<ChallengeNonce>(3);
auto gate = FreshnessGate(known);
auto verifier = TestVerifier();
if (!gate.beginChallenge(nonce)
|| gate.acceptResponse(MakeResponse(fork, nonce), verifier)
!= FreshnessResponseResult::ForkDetected
|| gate.state() != FreshnessState::Forked
|| gate.sendingAllowed()
|| gate.beginChallenge(FilledId<ChallengeNonce>(8))) {
return Fail("conflicting checkpoint did not fail closed");
}
return 0;
}
[[nodiscard]] int ScenarioOutboxWaitsForFreshness() {
const auto conversationId = FilledId<ConversationId>(1);
auto gate = FreshnessGate(MakeCheckpoint(conversationId, 8, 2));
auto store = TestOutboxStore();
auto protector = TestProtector();
auto coordinator = OutboxCoordinator(gate, store, protector);
const auto queued = coordinator.enqueue(MakeMessage(conversationId));
const auto dispatch = coordinator.dispatchNext();
if (queued != EnqueueResult::Queued
|| dispatch.result != OutboxDispatchResult::AwaitingFreshness
|| dispatch.envelope
|| protector.calls
|| store.items.size() != 1) {
return Fail("pending outbox content escaped before freshness");
}
return 0;
}
[[nodiscard]] int ScenarioOutboxSendsAfterFreshness() {
const auto conversationId = FilledId<ConversationId>(1);
const auto checkpoint = MakeCheckpoint(conversationId, 8, 2);
const auto nonce = FilledId<ChallengeNonce>(3);
auto gate = FreshnessGate(checkpoint);
auto verifier = TestVerifier();
auto store = TestOutboxStore();
auto protector = TestProtector();
auto coordinator = OutboxCoordinator(gate, store, protector);
if (coordinator.enqueue(MakeMessage(conversationId))
!= EnqueueResult::Queued
|| !gate.beginChallenge(nonce)
|| gate.acceptResponse(MakeResponse(checkpoint, nonce), verifier)
!= FreshnessResponseResult::Accepted
|| protector.calls) {
return Fail("confirmed outbox content was not sent exactly once");
}
const auto dispatch = coordinator.dispatchNext();
if (dispatch.result != OutboxDispatchResult::Ready
|| !dispatch.envelope
|| protector.calls != 1
|| coordinator.dispatchNext().result
!= OutboxDispatchResult::UploadInProgress
|| !coordinator.acknowledgeUploaded(dispatch.envelope->objectId)
|| !store.items.empty()
|| coordinator.dispatchNext().result != OutboxDispatchResult::Empty) {
return Fail("confirmed outbox content was not acknowledged exactly once");
}
return 0;
}
[[nodiscard]] int ScenarioOutboxRetriesExactCiphertext() {
const auto conversationId = FilledId<ConversationId>(1);
const auto checkpoint = MakeCheckpoint(conversationId, 8, 2);
const auto nonce = FilledId<ChallengeNonce>(3);
auto gate = FreshnessGate(checkpoint);
auto verifier = TestVerifier();
auto store = TestOutboxStore();
auto protector = TestProtector();
auto coordinator = OutboxCoordinator(gate, store, protector);
if (coordinator.enqueue(MakeMessage(conversationId))
!= EnqueueResult::Queued
|| !gate.beginChallenge(nonce)
|| gate.acceptResponse(MakeResponse(checkpoint, nonce), verifier)
!= FreshnessResponseResult::Accepted
|| protector.calls) {
return Fail("outbox retry setup failed");
}
const auto first = coordinator.dispatchNext();
if (first.result != OutboxDispatchResult::Ready
|| !first.envelope
|| !coordinator.markUploadFailed(first.envelope->objectId)) {
return Fail("outbox upload failure was not retained for retry");
}
const auto second = coordinator.dispatchNext();
if (second.result != OutboxDispatchResult::Ready
|| !second.envelope
|| first.envelope != second.envelope
|| protector.calls != 1
|| store.items.size() != 1
|| store.items.front().stage != OutboxItemStage::Sealed
|| !store.items.front().draft.plaintext.isEmpty()
|| !coordinator.acknowledgeUploaded(second.envelope->objectId)
|| !store.items.empty()) {
return Fail("outbox retry regenerated or lost sealed ciphertext");
}
return 0;
}
[[nodiscard]] int ScenarioOutboxRejectsProtectorMismatch() {
const auto conversationId = FilledId<ConversationId>(1);
const auto checkpoint = MakeCheckpoint(conversationId, 8, 2);
const auto nonce = FilledId<ChallengeNonce>(3);
auto gate = FreshnessGate(checkpoint);
auto verifier = TestVerifier();
auto store = TestOutboxStore();
auto protector = TestProtector();
protector.wrongConversation = true;
auto coordinator = OutboxCoordinator(gate, store, protector);
if (coordinator.enqueue(MakeMessage(conversationId))
!= EnqueueResult::Queued
|| !gate.beginChallenge(nonce)
|| gate.acceptResponse(MakeResponse(checkpoint, nonce), verifier)
!= FreshnessResponseResult::Accepted
|| coordinator.dispatchNext().result
!= OutboxDispatchResult::InvalidItem
|| store.items.size() != 1) {
return Fail("mismatched protected envelope reached transport");
}
return 0;
}
[[nodiscard]] int ScenarioUploadControllerAcknowledgesRpcSuccess() {
const auto conversationId = FilledId<ConversationId>(1);
const auto checkpoint = MakeCheckpoint(conversationId, 8, 2);
const auto nonce = FilledId<ChallengeNonce>(3);
auto gate = FreshnessGate(checkpoint);
auto verifier = TestVerifier();
auto store = TestOutboxStore();
auto protector = TestProtector();
auto transport = TestTransport();
auto completions = std::vector<UploadCompletion>();
auto coordinator = OutboxCoordinator(gate, store, protector);
auto controller = OutboxUploadController(
coordinator,
transport,
[&](UploadCompletion completion) {
completions.push_back(completion);
});
if (coordinator.enqueue(MakeMessage(conversationId))
!= EnqueueResult::Queued
|| !gate.beginChallenge(nonce)
|| gate.acceptResponse(MakeResponse(checkpoint, nonce), verifier)
!= FreshnessResponseResult::Accepted
|| controller.pump() != UploadPumpResult::Started
|| !controller.uploadInProgress()
|| controller.pump() != UploadPumpResult::UploadInProgress
|| transport.uploads.size() != 1
|| store.items.size() != 1) {
return Fail("upload controller did not retain an in-flight envelope");
}
transport.finish(0, TelegramTransport::UploadResult::Accepted);
if (controller.uploadInProgress()
|| !store.items.empty()
|| completions.size() != 1
|| completions.front().objectId != transport.uploads.front().objectId
|| completions.front().transportResult
!= TelegramTransport::UploadResult::Accepted
|| !completions.front().outboxUpdated
|| controller.pump() != UploadPumpResult::Empty) {
return Fail("successful Telegram RPC did not acknowledge the outbox");
}
transport.finish(0, TelegramTransport::UploadResult::Accepted);
if (completions.size() != 1) {
return Fail("duplicate upload callback changed the outbox twice");
}
return 0;
}
[[nodiscard]] int ScenarioUploadControllerRetriesExactEnvelope() {
const auto conversationId = FilledId<ConversationId>(1);
const auto checkpoint = MakeCheckpoint(conversationId, 8, 2);
const auto nonce = FilledId<ChallengeNonce>(3);
auto gate = FreshnessGate(checkpoint);
auto verifier = TestVerifier();
auto store = TestOutboxStore();
auto protector = TestProtector();
auto transport = TestTransport();
auto coordinator = OutboxCoordinator(gate, store, protector);
auto controller = OutboxUploadController(
coordinator,
transport,
nullptr);
if (coordinator.enqueue(MakeMessage(conversationId))
!= EnqueueResult::Queued
|| !gate.beginChallenge(nonce)
|| gate.acceptResponse(MakeResponse(checkpoint, nonce), verifier)
!= FreshnessResponseResult::Accepted
|| controller.pump() != UploadPumpResult::Started) {
return Fail("upload retry setup failed");
}
transport.finish(0, TelegramTransport::UploadResult::RetryableError);
if (controller.uploadInProgress()
|| store.items.size() != 1
|| controller.pump() != UploadPumpResult::Started
|| transport.uploads.size() != 2
|| transport.uploads[0] != transport.uploads[1]
|| protector.calls != 1) {
return Fail("upload controller did not retry exact ciphertext");
}
transport.finish(1, TelegramTransport::UploadResult::Accepted);
if (!store.items.empty()) {
return Fail("retried upload did not leave the outbox");
}
return 0;
}
[[nodiscard]] int ScenarioUploadCallbackCannotOutliveController() {
const auto conversationId = FilledId<ConversationId>(1);
const auto checkpoint = MakeCheckpoint(conversationId, 8, 2);
const auto nonce = FilledId<ChallengeNonce>(3);
auto gate = FreshnessGate(checkpoint);
auto verifier = TestVerifier();
auto store = TestOutboxStore();
auto protector = TestProtector();
auto transport = TestTransport();
auto coordinator = OutboxCoordinator(gate, store, protector);
if (coordinator.enqueue(MakeMessage(conversationId))
!= EnqueueResult::Queued
|| !gate.beginChallenge(nonce)
|| gate.acceptResponse(MakeResponse(checkpoint, nonce), verifier)
!= FreshnessResponseResult::Accepted) {
return Fail("upload callback lifetime setup failed");
}
{
auto controller = OutboxUploadController(
coordinator,
transport,
nullptr);
if (controller.pump() != UploadPumpResult::Started) {
return Fail("upload callback lifetime setup did not start");
}
}
transport.finish(0, TelegramTransport::UploadResult::Accepted);
if (store.items.size() != 1) {
return Fail("destroyed upload controller handled a late callback");
}
auto replacement = OutboxUploadController(
coordinator,
transport,
nullptr);
if (replacement.pump() != UploadPumpResult::Started
|| transport.uploads.size() != 2
|| transport.uploads[0] != transport.uploads[1]) {
return Fail("destroyed upload controller left the outbox stuck");
}
return 0;
}
[[nodiscard]] int ScenarioCarrierAcknowledgesOnlyAfterSendMedia() {
const auto conversationId = FilledId<ConversationId>(1);
const auto objectId = FilledId<ObjectId>(7);
auto backend = TestCarrierBackend();
auto results = std::vector<TelegramTransport::UploadResult>();
auto transport = TelegramCarrierTransport(
conversationId,
42,
backend);
transport.uploadExact({
.conversationId = conversationId,
.objectId = objectId,
.bytes = QByteArray("exact ciphertext"),
}, [&](TelegramTransport::UploadResult result) {
results.push_back(result);
});
if (backend.uploadedBytes != std::vector<QByteArray>{
QByteArray("exact ciphertext") }
|| backend.uploadCallbacks.size() != 1
|| !backend.sendCallbacks.empty()
|| !results.empty()) {
return Fail("carrier bypassed the Telegram upload stage");
}
backend.uploadCallbacks.front()(
TelegramTransport::UploadResult::Accepted,
UploadedCarrierFile{ QByteArray("uploaded token") });
if (backend.sendCallbacks.size() != 1
|| backend.peerIds != std::vector<std::uint64_t>{ 42 }
|| backend.tokens != std::vector<QByteArray>{
QByteArray("uploaded token") }
|| !results.empty()) {
return Fail("carrier acknowledged before messages.sendMedia");
}
backend.sendCallbacks.front()(
TelegramTransport::UploadResult::Accepted);
if (results != std::vector<TelegramTransport::UploadResult>{
TelegramTransport::UploadResult::Accepted }
|| backend.filenames.size() != 2
|| backend.filenames[0] != backend.filenames[1]
|| backend.mimeTypes.size() != 2
|| backend.mimeTypes[0] != backend.mimeTypes[1]) {
return Fail("carrier did not preserve generic document metadata");
}
return 0;
}
[[nodiscard]] int ScenarioCarrierPropagatesUploadFailure() {
const auto conversationId = FilledId<ConversationId>(1);
auto backend = TestCarrierBackend();
auto result = std::optional<TelegramTransport::UploadResult>();
auto transport = TelegramCarrierTransport(
conversationId,
42,
backend);
transport.uploadExact({
.conversationId = conversationId,
.objectId = FilledId<ObjectId>(7),
.bytes = QByteArray("ciphertext"),
}, [&](TelegramTransport::UploadResult value) {
result = value;
});
backend.uploadCallbacks.front()(
TelegramTransport::UploadResult::RetryableError,
UploadedCarrierFile());
if (result != TelegramTransport::UploadResult::RetryableError
|| !backend.sendCallbacks.empty()) {
return Fail("carrier converted an upload failure into a send");
}
return 0;
}
[[nodiscard]] int ScenarioCarrierDownloadsOnlyUntrustedBytes() {
const auto conversationId = FilledId<ConversationId>(1);
auto backend = TestCarrierBackend();
auto result = std::optional<TelegramTransport::DownloadResult>();
auto transport = TelegramCarrierTransport(
conversationId,
42,
backend);
transport.download(
conversationId,
[&](TelegramTransport::DownloadResult value) {
result = std::move(value);
});
if (backend.downloadCallbacks.size() != 1 || result) {
return Fail("carrier download did not remain asynchronous");
}
backend.downloadCallbacks.front()(
TelegramTransport::UploadResult::Accepted,
{ QByteArray("untrusted one"), QByteArray("untrusted two") });
if (!result
|| result->result != TelegramTransport::UploadResult::Accepted
|| result->untrustedObjects != std::vector<QByteArray>{
QByteArray("untrusted one"),
QByteArray("untrusted two") }) {
return Fail("carrier transport interpreted untrusted protocol bytes");
}
return 0;
}
} // namespace
int main(int, char *[]) {
for (const auto scenario : {
ScenarioEnvelopeValidation,
ScenarioHistoryAccessBoundary,
ScenarioEnvelopeCodec,
ScenarioFreshnessConfirmation,
ScenarioFreshnessResynchronization,
ScenarioFreshnessFork,
ScenarioOutboxWaitsForFreshness,
ScenarioOutboxSendsAfterFreshness,
ScenarioOutboxRetriesExactCiphertext,
ScenarioOutboxRejectsProtectorMismatch,
ScenarioUploadControllerAcknowledgesRpcSuccess,
ScenarioUploadControllerRetriesExactEnvelope,
ScenarioUploadCallbackCannotOutliveController,
ScenarioCarrierAcknowledgesOnlyAfterSendMedia,
ScenarioCarrierPropagatesUploadFailure,
ScenarioCarrierDownloadsOnlyUntrustedBytes,
}) {
if (const auto result = scenario()) {
return result;
}
}
return 0;
}