diff options
-rw-r--r-- | examples/qmc-example.cpp | 65 | ||||
-rw-r--r-- | lib/room.cpp | 32 |
2 files changed, 62 insertions, 35 deletions
diff --git a/examples/qmc-example.cpp b/examples/qmc-example.cpp index f4067009..10ea9d35 100644 --- a/examples/qmc-example.cpp +++ b/examples/qmc-example.cpp @@ -375,42 +375,63 @@ void QMCTest::checkFileSendingOutcome(const QString& txnId, void QMCTest::setTopic() { static const char* const stateTestName = "State setting test"; - static const char* const fakeStateTestName = - "Fake state event immunity test"; running.push_back(stateTestName); - running.push_back(fakeStateTestName); - auto initialTopic = targetRoom->topic(); - const auto newTopic = c->generateTxnId(); + const auto newTopic = c->generateTxnId(); // Just a way to get a unique id targetRoom->setTopic(newTopic); // Sets the state by proper means const auto fakeTopic = c->generateTxnId(); - targetRoom->postJson(RoomTopicEvent::matrixTypeId(), // Fake state event - RoomTopicEvent(fakeTopic).contentJson()); + const auto fakeTxnId = + targetRoom->postJson(RoomTopicEvent::matrixTypeId(), // Fake state event + RoomTopicEvent(fakeTopic).contentJson()); connectUntil(targetRoom, &Room::topicChanged, this, - [this, newTopic, fakeTopic, initialTopic] { + [this, newTopic] { if (targetRoom->topic() == newTopic) { QMC_CHECK(stateTestName, true); - // Don't reset the topic yet if the negative test still - // runs - if (!running.contains(fakeStateTestName)) - targetRoom->setTopic(initialTopic); - return true; } return false; }); - connectUntil(targetRoom, &Room::pendingEventAboutToMerge, this, - [this, fakeTopic, initialTopic](const RoomEvent* e, int) { - if (e->contentJson().value("topic").toString() != fakeTopic) - return false; // Wait on for the right event + // Older Synapses allowed sending fake state events through, although + // did not process them; // https://github.com/matrix-org/synapse/pull/5805 + // changed that and now Synapse 400's in response to fake state events. + // The following two-step approach handles both cases, assuming that + // Room::pendingEventChanged() with EventStatus::ReachedServer is guaranteed + // to be emitted before Room::pendingEventAboutToMerge. + connectUntil( + targetRoom, &Room::pendingEventChanged, this, + [this, fakeTopic, fakeTxnId](int pendingIdx) { + const auto& pendingEvents = targetRoom->pendingEvents(); + Q_ASSERT(pendingIdx >= 0 && pendingIdx < int(pendingEvents.size())); + const auto& evt = pendingEvents[pendingIdx]; + if (evt->transactionId() != fakeTxnId) + return false; - QMC_CHECK(fakeStateTestName, !e->isStateEvent()); - if (!running.contains(fakeStateTestName)) - targetRoom->setTopic(initialTopic); - return true; - }); + if (evt.deliveryStatus() == EventStatus::SendingFailed) { + QMC_CHECK("Fake state event immunity test", true); + return true; + } + if (evt.deliveryStatus() != EventStatus::ReachedServer) + return false; + + // All before was just a preparation, this is where the test starts. + // (If Synapse rejected the event the library immunity can't be + // tested.) + static const char* const fakeStateTestName = + "Fake state event immunity test"; + running.push_back(fakeStateTestName); + connectUntil( + targetRoom, &Room::pendingEventAboutToMerge, this, + [this, fakeTopic](const RoomEvent* e, int) { + if (e->contentJson().value("topic").toString() != fakeTopic) + return false; // Wait on for the right event + + QMC_CHECK(fakeStateTestName, !e->isStateEvent()); + return true; + }); + return true; + }); } void QMCTest::addAndRemoveTag() diff --git a/lib/room.cpp b/lib/room.cpp index b32d3492..52f86616 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -1469,8 +1469,10 @@ QString Room::Private::doSendEvent(const RoomEvent* pEvent) return; } - it->setReachedServer(call->eventId()); - emit q->pendingEventChanged(int(it - unsyncedEvents.begin())); + if (it->deliveryStatus() != EventStatus::ReachedServer) { + it->setReachedServer(call->eventId()); + emit q->pendingEventChanged(int(it - unsyncedEvents.begin())); + } }); } else onEventSendingFailure(txnId); @@ -1520,6 +1522,7 @@ QString Room::retryMessage(const QString& txnId) " events are likely to be in the timeline after retry"; } it->resetStatus(); + emit pendingEventChanged(int(it - d->unsyncedEvents.begin())); return d->doSendEvent(it->event()); } @@ -2170,10 +2173,11 @@ Room::Changes Room::Private::addNewMessageEvents(RoomEvents&& events) auto nextPendingPair = findFirstOf(it, events.end(), unsyncedEvents.begin(), unsyncedEvents.end(), isEchoEvent); - auto nextPending = nextPendingPair.first; + const auto& remoteEcho = nextPendingPair.first; + const auto& localEcho = nextPendingPair.second; - if (it != nextPending) { - RoomEventsRange eventsSpan { it, nextPending }; + if (it != remoteEcho) { + RoomEventsRange eventsSpan { it, remoteEcho }; emit q->aboutToAddNewMessages(eventsSpan); auto insertedSize = moveEventsToTimeline(eventsSpan, Newer); totalInserted += insertedSize; @@ -2182,13 +2186,16 @@ Room::Changes Room::Private::addNewMessageEvents(RoomEvents&& events) emit q->addedMessages(firstInserted->index(), timeline.back().index()); } - if (nextPending == events.end()) + if (remoteEcho == events.end()) break; - it = nextPending + 1; - auto* nextPendingEvt = nextPending->get(); - const auto pendingEvtIdx = - int(nextPendingPair.second - unsyncedEvents.begin()); + it = remoteEcho + 1; + auto* nextPendingEvt = remoteEcho->get(); + const auto pendingEvtIdx = int(localEcho - unsyncedEvents.begin()); + if (localEcho->deliveryStatus() != EventStatus::ReachedServer) { + localEcho->setReachedServer(nextPendingEvt->id()); + emit q->pendingEventChanged(pendingEvtIdx); + } emit q->pendingEventAboutToMerge(nextPendingEvt, pendingEvtIdx); qDebug(EVENTS) << "Merging pending event from transaction" << nextPendingEvt->transactionId() << "into" @@ -2197,13 +2204,12 @@ Room::Changes Room::Private::addNewMessageEvents(RoomEvents&& events) if (transfer.status != FileTransferInfo::None) fileTransfers.insert(nextPendingEvt->id(), transfer); // After emitting pendingEventAboutToMerge() above we cannot rely - // on the previously obtained nextPendingPair.second staying valid + // on the previously obtained localEcho staying valid // because a signal handler may send another message, thereby altering // unsyncedEvents (see #286). Fortunately, unsyncedEvents only grows at // its back so we can rely on the index staying valid at least. unsyncedEvents.erase(unsyncedEvents.begin() + pendingEvtIdx); - if (auto insertedSize = moveEventsToTimeline({ nextPending, it }, - Newer)) { + if (auto insertedSize = moveEventsToTimeline({ remoteEcho, it }, Newer)) { totalInserted += insertedSize; q->onAddNewTimelineEvents(timeline.cend() - insertedSize); } |