Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F139972
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
11 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/client/actions/send.cpp b/src/client/actions/send.cpp
index 861b87f..2ed9d6b 100644
--- a/src/client/actions/send.cpp
+++ b/src/client/actions/send.cpp
@@ -1,164 +1,177 @@
/*
* This file is part of libkazv.
* SPDX-FileCopyrightText: 2020 Tusooa Zhu <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include <libkazv-config.hpp>
#include <debug.hpp>
#include <types.hpp>
#include "send.hpp"
#include "status-utils.hpp"
namespace Kazv
{
ClientResult updateClient(ClientModel m, SendMessageAction a)
{
auto event = std::move(a.event);
auto roomId = a.roomId;
auto origJson = event.originalJson().get();
if (!origJson.contains("type") || !origJson.contains("content")) {
m.addTrigger(InvalidMessageFormat{});
return { std::move(m), lager::noop };
}
if (m.roomList.rooms[a.roomId].encrypted && !event.encrypted()) {
if (!event.isState()) {
return { std::move(m), [](auto &&) {
return EffectStatus{
/* succ = */ false,
json{
{"errorCode", "MOE_KAZV_MXC_SENDING_UNENCRYPTED_EVENT_TO_ENCRYPTED_ROOM"},
{"error", "Cannot send unencrypted event to encrypted room"},
}
};
}};
}
}
// We do not use event.type() etc. because we want
// encrypted events stay encrypted.
auto type = origJson["type"];
auto content = origJson["content"];
kzo.client.dbg() << "Sending message of type " << type
<< " with content " << content.dump()
<< " to " << a.roomId
<< " as #" << m.nextTxnId << std::endl;
// We combine the hash of json, the timestamp,
// and a numeric count in the client to avoid collision.
auto txnId = a.txnId.has_value() ? a.txnId.value() : getTxnId(event, m);
m.roomList = RoomListModel::update(std::move(m.roomList),
UpdateRoomAction{
roomId,
AddLocalEchoAction{{txnId, event}},
}
);
auto job = m.job<SendMessageJob>()
.make(a.roomId, type, txnId, content)
.withData(json{
{"roomId", a.roomId},
{"txnId", txnId},
});
m.addJob(std::move(job));
return { std::move(m), lager::noop };
}
ClientResult processResponse(ClientModel m, SendMessageResponse r)
{
auto roomId = r.dataStr("roomId");
if (! r.success()) {
auto txnId = r.dataStr("txnId");
kzo.client.dbg() << "Send message failed" << std::endl;
m.roomList.rooms = std::move(m.roomList.rooms).update(roomId, [txnId](auto room) {
auto maybeLocalEcho = room.getLocalEchoByTxnId(txnId);
if (!maybeLocalEcho.has_value()) {
kzo.client.warn() << "We do not have local echo with txnId " << txnId << " . Have we time-travelled?" << std::endl;
return room;
}
auto localEcho = std::move(maybeLocalEcho).value();
localEcho.status = LocalEchoDesc::Failed;
return RoomModel::update(std::move(room), AddLocalEchoAction{localEcho});
});
m.addTrigger(SendMessageFailed{roomId, r.errorCode(), r.errorMessage()});
return { std::move(m), failWithResponse(r) };
}
m.addTrigger(SendMessageSuccessful{roomId, r.eventId()});
return { std::move(m), lager::noop };
}
ClientResult updateClient(ClientModel m, SendToDeviceMessageAction a)
{
auto origJson = a.event.originalJson().get();
if (!origJson.contains("type") || !origJson.contains("content")) {
return { std::move(m), simpleFail };
}
// We do not use event.type() etc. because we want
// encrypted events stay encrypted.
auto type = origJson["type"];
auto content = origJson["content"];
+ if (type == "m.room_key" && !a.event.encrypted()) {
+ kzo.client.err() << "Trying to send room key event unencrypted! Rejecting." << std::endl;
+ return { std::move(m), [](auto &&) {
+ return EffectStatus{
+ /* succ = */ false,
+ json{
+ {"errorCode", "MOE_KAZV_MXC_SENDING_ROOM_KEY_EVENT_UNENCRYPTED"},
+ {"error", "Cannot send room key event unencrypted"},
+ }
+ };
+ }};
+ }
+
auto txnId = a.txnId.has_value() ? a.txnId.value() : getTxnId(a.event, m);
kzo.client.info() << "sending to-device message with txnId " << txnId;
auto messages =
immer::map<std::string, immer::map<std::string, JsonWrap>>{};
for (auto [userId, devices] : a.devicesToSend) {
auto deviceIdToContentMap = immer::map<std::string, JsonWrap>{};
for (auto deviceId : devices) {
deviceIdToContentMap = std::move(deviceIdToContentMap).set(deviceId, content);
}
messages = std::move(messages).set(userId, deviceIdToContentMap);
}
auto job = m.job<SendToDeviceJob>()
.make(type, txnId, messages)
.withData(json{{"devicesToSend", a.devicesToSend},
{"txnId", txnId}});
m.addJob(std::move(job));
return { std::move(m), lager::noop };
}
ClientResult processResponse(ClientModel m, SendToDeviceResponse r)
{
auto devicesToSend = r.dataJson("devicesToSend");
auto txnId = r.dataStr("txnId");
if (! r.success()) {
m.addTrigger(SendToDeviceMessageFailed{devicesToSend, txnId, r.errorCode(), r.errorMessage()});
return { std::move(m), failWithResponse(r) };
}
m.addTrigger(SendToDeviceMessageSuccessful{devicesToSend, txnId});
return { std::move(m), lager::noop };
}
ClientResult updateClient(ClientModel m, SaveLocalEchoAction a)
{
auto txnId = a.txnId.has_value() ? a.txnId.value() : getTxnId(a.event, m);
m.roomList = RoomListModel::update(m.roomList, UpdateRoomAction{
a.roomId,
AddLocalEchoAction{{txnId, a.event}},
});
return { std::move(m), [txnId](auto) {
return EffectStatus(true, json::object({{"txnId", txnId}}));
}};
}
}
diff --git a/src/tests/client/send-test.cpp b/src/tests/client/send-test.cpp
index 310ac7f..de39344 100644
--- a/src/tests/client/send-test.cpp
+++ b/src/tests/client/send-test.cpp
@@ -1,107 +1,133 @@
/*
* This file is part of libkazv.
* SPDX-FileCopyrightText: 2021-2023 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include <libkazv-config.hpp>
#include <lager/event_loop/boost_asio.hpp>
#include <catch2/catch_all.hpp>
#include <boost/asio.hpp>
#include <asio-promise-handler.hpp>
#include <cursorutil.hpp>
#include <sdk-model.hpp>
#include <client/client.hpp>
#include "client-test-util.hpp"
TEST_CASE("Send a message", "[client][send]")
{
ClientModel loggedInModel = createTestClientModel();
auto [resModel, dontCareEffect] = ClientModel::update(
loggedInModel, SendMessageAction{"!foo:tusooa.xyz", json{
{"type", "m.room.message"},
{"content", {{"foo", "bar"}}},
}});
assert1Job(resModel);
for1stJob(resModel, [] (const auto &job) {
REQUIRE(job.jobId() == "SendMessage");
REQUIRE(job.dataStr("roomId") == "!foo:tusooa.xyz");
REQUIRE(job.dataStr("txnId") != "");
});
}
TEST_CASE("Send a message with specified txnId", "[client][send]")
{
ClientModel loggedInModel = createTestClientModel();
auto [resModel, dontCareEffect] = ClientModel::update(
loggedInModel, SendMessageAction{"!foo:tusooa.xyz", json{
{"type", "m.room.message"},
{"content", {{"foo", "bar"}}},
}, "some-txn-id"});
assert1Job(resModel);
for1stJob(resModel, [] (const auto &job) {
REQUIRE(job.jobId() == "SendMessage");
REQUIRE(job.dataStr("roomId") == "!foo:tusooa.xyz");
REQUIRE(job.dataStr("txnId") == "some-txn-id");
});
}
TEST_CASE("Refuse to send unencrypted event to encrypted room", "[client][send]")
{
boost::asio::io_context io;
SingleTypePromiseInterface<EffectStatus> ph{AsioPromiseHandler{io.get_executor()}};
ClientModel m;
m.crypto = Crypto(RandomTag{}, genRandomData(Crypto::constructRandomSize()));
RoomModel room;
room.encrypted = true;
room.roomId = "!exampleroomid:example.com";
m.roomList.rooms = m.roomList.rooms.set("!exampleroomid:example.com", room);
auto store = createTestClientStoreFrom(m, ph);
store.dispatch(SendMessageAction{
"!exampleroomid:example.com",
Event{json{{"content", {{"foo", "bar"}}}, {"type", "m.room.message"}}},
})
.then([](auto status) {
REQUIRE(!status.success());
REQUIRE(status.dataStr("errorCode") == "MOE_KAZV_MXC_SENDING_UNENCRYPTED_EVENT_TO_ENCRYPTED_ROOM");
});
io.run();
}
TEST_CASE("Ok to send unencrypted state event to encrypted room", "[client][send]")
{
boost::asio::io_context io;
SingleTypePromiseInterface<EffectStatus> ph{AsioPromiseHandler{io.get_executor()}};
ClientModel m;
m.crypto = Crypto(RandomTag{}, genRandomData(Crypto::constructRandomSize()));
RoomModel room;
room.encrypted = true;
room.roomId = "!exampleroomid:example.com";
m.roomList.rooms = m.roomList.rooms.set("!exampleroomid:example.com", room);
auto store = createTestClientStoreFrom(m, ph);
store.dispatch(SendMessageAction{
"!exampleroomid:example.com",
Event{json{
{"content", {{"foo", "bar"}}},
{"type", "m.room.xxx"},
{"state_key", ""},
}},
})
.then([](auto status) {
REQUIRE(status.success());
});
io.run();
}
+
+TEST_CASE("Refuse to send unencrypted m.room_key event", "[client][send]")
+{
+ boost::asio::io_context io;
+ SingleTypePromiseInterface<EffectStatus> ph{AsioPromiseHandler{io.get_executor()}};
+
+ ClientModel m;
+ m.crypto = Crypto(RandomTag{}, genRandomData(Crypto::constructRandomSize()));
+ RoomModel room;
+ room.encrypted = true;
+ room.roomId = "!exampleroomid:example.com";
+ m.roomList.rooms = m.roomList.rooms.set("!exampleroomid:example.com", room);
+
+ auto store = createTestClientStoreFrom(m, ph);
+
+ store.dispatch(SendToDeviceMessageAction{
+ Event{json{{"content", {{"foo", "bar"}}}, {"type", "m.room_key"}}},
+ {},
+ })
+ .then([](auto status) {
+ REQUIRE(!status.success());
+ REQUIRE(status.dataStr("errorCode") == "MOE_KAZV_MXC_SENDING_ROOM_KEY_EVENT_UNENCRYPTED");
+ });
+
+ io.run();
+}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sun, Jan 19, 9:13 AM (2 h, 2 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
55059
Default Alt Text
(11 KB)
Attached To
Mode
rL libkazv
Attached
Detach File
Event Timeline
Log In to Comment