From 77640de2e97b7235e6e3824227b83c67f35cd392 Mon Sep 17 00:00:00 2001 From: Ivan-lis Date: Sun, 17 Nov 2024 23:25:41 +0000 Subject: [PATCH 1/3] Feature: iq::Bind --- library/include/larra/bind.hpp | 53 ++++++++++++++++++++++++++++++++++ library/include/larra/iq.hpp | 28 +++++++++++++++++- library/include/larra/jid.hpp | 39 +++++++++++++------------ 3 files changed, 101 insertions(+), 19 deletions(-) create mode 100644 library/include/larra/bind.hpp diff --git a/library/include/larra/bind.hpp b/library/include/larra/bind.hpp new file mode 100644 index 0000000..ccc2a57 --- /dev/null +++ b/library/include/larra/bind.hpp @@ -0,0 +1,53 @@ +#pragma once +#include +#include + +#include +#include +#include + +namespace larra::xmpp::iq { + +struct Bind { + static constexpr auto kDefaultName = "bind"; + static constexpr auto kDefaultNamespace = "urn:ietf:params:xml:ns:xmpp-bind"; + + std::optional jid; + + friend constexpr auto operator<<(xmlpp::Element* element, const Bind& bind) { + element->set_attribute("xmlns", Bind::kDefaultNamespace); + + if(bind.jid) { + auto* jid_el = element->add_child_element("jid"); + jid_el->add_child_text(ToString(*bind.jid)); + } + } + [[nodiscard]] static constexpr auto Parse(xmlpp::Element* element) -> Bind { + const auto* jid_node = element->get_first_child("jid"); + if(!jid_node) { + SPDLOG_DEBUG("No Jid Node at Iq::Bind"); + return {}; + } + + auto* jid_el = dynamic_cast(jid_node); + if(!jid_el) { + throw std::runtime_error("dynamic_cast to const xmlpp::Element* failed"); + } + + const auto* text = jid_el->get_first_child_text(); + if(!jid_el) { + throw std::runtime_error("No text at Iq::Bind jid child"); + } + + return {.jid = (jid_node ? std::optional{FullJid::Parse(text->get_content())} : std::nullopt)}; + } +}; + +using SetBind = Set; +using ResultBind = Result; +using IqBind = Iq; + +inline auto MakeSetBind() { + return SetBind{.id = "1", .payload = Bind{}}; +} +} // namespace larra::xmpp::iq diff --git a/library/include/larra/iq.hpp b/library/include/larra/iq.hpp index bd7b291..5efd612 100644 --- a/library/include/larra/iq.hpp +++ b/library/include/larra/iq.hpp @@ -2,6 +2,7 @@ #include #include #include +#include #include namespace larra::xmpp { @@ -11,6 +12,8 @@ namespace iq { template struct BaseImplWithPayload { std::string id; + std::optional from{}; + std::optional to{}; PayloadType payload; static const inline std::string kName = Name; static constexpr auto kDefaultName = "iq"; @@ -19,16 +22,32 @@ struct BaseImplWithPayload { [[nodiscard]] constexpr auto Id(this Self&& self, std::string id) -> std::decay_t { return utils::FieldSetHelper::With<"id", BaseImplWithPayload>(std::forward(self), std::move(id)); } + template + [[nodiscard]] constexpr auto To(this Self&& self, std::string to) -> std::decay_t { + return utils::FieldSetHelper::With<"to", BaseImplWithPayload>(std::forward(self), std::move(to)); + } + template + [[nodiscard]] constexpr auto From(this Self&& self, std::string from) -> std::decay_t { + return utils::FieldSetHelper::With<"from", BaseImplWithPayload>(std::forward(self), std::move(from)); + } template [[nodiscard]] constexpr auto Payload(this Self&& self, NewPayloadType value) { return utils::FieldSetHelper::With<"payload", BaseImplWithPayload, false>(std::forward(self), std::move(value)); } friend constexpr auto operator<<(xmlpp::Element* element, const BaseImplWithPayload& self) { element->set_attribute("id", self.id); + + if (self.to) { + element->set_attribute("to", *self.to); + } + if (self.from) { + element->set_attribute("from", *self.from); + } element->set_attribute("type", kName); using S = Serialization; S::Serialize(element->add_child_element(S::kDefaultName, S::kPrefix), self.payload); } + [[nodiscard]] static constexpr auto Parse(xmlpp::Element* element) -> BaseImplWithPayload { auto node = element->get_attribute("type"); if(!node) { @@ -41,6 +60,9 @@ struct BaseImplWithPayload { if(!idNode) { throw std::runtime_error("Not found attribute id for parse Iq"); } + auto from = element->get_attribute("from"); + auto to = element->get_attribute("to"); + using S = Serialization; auto payload = element->get_first_child(S::kDefaultName); @@ -51,7 +73,11 @@ struct BaseImplWithPayload { if(!payload2) { throw std::runtime_error("Invalid payload for parse Iq"); } - return {.id = idNode->get_value(), .payload = S::Parse(payload2)}; + return { + .id = idNode->get_value(), + .from = (from ? std::optional{from->get_value()} : std::nullopt), + .to = (to ? std::optional{to->get_value()} : std::nullopt), + .payload = S::Parse(payload2)}; } }; static constexpr auto kGetName = "get"; diff --git a/library/include/larra/jid.hpp b/library/include/larra/jid.hpp index 78fa043..1ea3cd8 100644 --- a/library/include/larra/jid.hpp +++ b/library/include/larra/jid.hpp @@ -7,24 +7,6 @@ namespace larra::xmpp { -struct BareJid { - std::string username; - std::string server; - [[nodiscard]] static auto Parse(std::string_view jid) -> BareJid; - friend auto ToString(const BareJid& jid) -> std::string; - - constexpr auto operator==(const BareJid&) const -> bool = default; - template - [[nodiscard]] constexpr auto Username(this Self&& self, std::string username) -> std::decay_t { - return utils::FieldSetHelper::With<"username", BareJid>(std::forward(self), std::move(username)); - } - - template - [[nodiscard]] constexpr auto Server(this Self&& self, std::string server) -> std::decay_t { - return utils::FieldSetHelper::With<"server", BareJid>(std::forward(self), std::move(server)); - } -}; - struct BareResourceJid { std::string server; std::string resource; @@ -69,6 +51,27 @@ struct FullJid { } }; +struct BareJid { + std::string username; + std::string server; + constexpr operator FullJid() const { + return FullJid{.username = username, .server = server, .resource = ""}; + } + [[nodiscard]] static auto Parse(std::string_view jid) -> BareJid; + friend auto ToString(const BareJid& jid) -> std::string; + + constexpr auto operator==(const BareJid&) const -> bool = default; + template + [[nodiscard]] constexpr auto Username(this Self&& self, std::string username) -> std::decay_t { + return utils::FieldSetHelper::With<"username", BareJid>(std::forward(self), std::move(username)); + } + + template + [[nodiscard]] constexpr auto Server(this Self&& self, std::string server) -> std::decay_t { + return utils::FieldSetHelper::With<"server", BareJid>(std::forward(self), std::move(server)); + } +}; + using JidVariant = std::variant; struct Jid : JidVariant { -- 2.47.0 From 76853855592a50e1f972350cc06feca337e16b6d Mon Sep 17 00:00:00 2001 From: Ivan-lis Date: Sun, 17 Nov 2024 23:29:51 +0000 Subject: [PATCH 2/3] Feature: iq::Roster --- .vscode/launch.json | 12 ++++ .vscode/tasks.json | 2 +- examples/src/connect.cpp | 24 ++++++++ library/include/larra/client/client.hpp | 56 +++++++++++++----- library/include/larra/roster.hpp | 76 +++++++++++++++++++++++++ library/include/larra/utils.hpp | 14 +++++ tests/roster.cpp | 39 +++++++++++++ 7 files changed, 207 insertions(+), 16 deletions(-) create mode 100644 library/include/larra/roster.hpp create mode 100644 tests/roster.cpp diff --git a/.vscode/launch.json b/.vscode/launch.json index 2a94c2f..974f9e6 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -12,6 +12,18 @@ "args": [], "cwd": "${workspaceFolder}", "preLaunchTask": "GCC: Build" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug: tests", + "program": "${workspaceFolder}/build/larra_xmpp_tests", + "args": [ + // --gtest_filter=POSTIVE_PATTERNS[-NEGATIVE_PATTERNS] + "--gtest_filter=Roster*" + ], + "cwd": "${workspaceFolder}", + "preLaunchTask": "GCC: Build" } ] } \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json index ebd295f..4720fa7 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -104,7 +104,7 @@ "presentation": { "clear": true }, - "hide": true, + "hide": false, "group": { "kind": "build" } diff --git a/examples/src/connect.cpp b/examples/src/connect.cpp index a7abdce..9f24137 100644 --- a/examples/src/connect.cpp +++ b/examples/src/connect.cpp @@ -1,3 +1,4 @@ +#include #include #include @@ -7,6 +8,8 @@ #include #include +namespace iq = larra::xmpp::iq; + auto Coroutine() -> boost::asio::awaitable { SPDLOG_INFO("Connecting client..."); @@ -14,11 +17,32 @@ auto Coroutine() -> boost::asio::awaitable { auto client = co_await larra::xmpp::client::CreateClient>( larra::xmpp::PlainUserAccount{.jid = {.username = "test1", .server = "localhost"}, .password = "test1"}, {.useTls = larra::xmpp::client::Options::kNever}); + + // rfc6120 7.1 + // After a client authenticates with a server, + // it MUST bind a specific resource to the stream so that the server can properly address the client. co_await std::visit( [](auto& client) -> boost::asio::awaitable { + co_await client.CreateResourceBind(); + }, + client); + + co_await std::visit( + [](auto& client) -> boost::asio::awaitable { + co_await client.UpdateListOfContacts(); + }, + client); + + // rfc6120 2.2 + // Upon authenticating with a server and binding a resource (thus becoming a connected resource as defined in [XMPP‑CORE]), + // a client SHOULD request the roster before sending initial presence + co_await std::visit( + [](auto& client) -> boost::asio::awaitable { + SPDLOG_INFO("Send presence: Available"); co_await client.Send(larra::xmpp::presence::c2s::Available{}); }, client); + } catch(const std::exception& err) { SPDLOG_ERROR("{}", err.what()); co_return; diff --git a/library/include/larra/client/client.hpp b/library/include/larra/client/client.hpp index 5ee658c..ba80489 100644 --- a/library/include/larra/client/client.hpp +++ b/library/include/larra/client/client.hpp @@ -11,16 +11,24 @@ #include #include #include +#include #include #include #include #include #include #include +#include #include #include #include #include +#include + +namespace rng = std::ranges; +namespace views = std::views; +namespace iq = larra::xmpp::iq; + namespace larra::xmpp { constexpr auto kDefaultXmppPort = 5222; @@ -29,12 +37,10 @@ constexpr auto kDefaultXmppPort = 5222; namespace larra::xmpp::client { -namespace rng = std::ranges; -namespace views = std::views; - template struct Client { - constexpr Client(BareJid jid, XmlStream connection) : jid(std::move(jid)), connection(std::move(connection)) {}; + constexpr Client(BareJid jid, XmlStream connection) : jid(std::move(jid)), connection(std::move(connection)) { + } template Token = boost::asio::use_awaitable_t<>> constexpr auto Close(Token token = {}) { this->active = false; @@ -68,14 +74,35 @@ struct Client { auto Send(const T& object) -> boost::asio::awaitable { co_await this->connection.Send(object); } - [[nodiscard]] constexpr auto Jid() const -> const BareJid& { + [[nodiscard]] constexpr auto Jid() const -> const FullJid& { return this->jid; } + auto CreateResourceBind() -> boost::asio::awaitable { + SPDLOG_INFO("Send IQ: Set::Bind"); + co_await this->Send(::iq::MakeSetBind()); + + auto bind_result = co_await connection.template Read<::iq::ResultBind>(); + jid.resource = std::move(bind_result.payload.jid->resource); + co_return; + } + + auto UpdateListOfContacts() -> boost::asio::awaitable { + SPDLOG_INFO("Send IQ: Get::Roster"); + co_await this->Send(::iq::MakeGetRoster(jid)); + + const auto roster_result = co_await connection.template Read<::iq::ResultRoster>(); + roster = std::move(roster_result.payload); + + SPDLOG_INFO("New roster: {}", ToString(roster)); + co_return; + } + private: bool active = true; XmlStream connection{}; - BareJid jid; + FullJid jid; + ::iq::Roster roster; }; struct StartTlsNegotiationError : std::runtime_error { @@ -116,15 +143,14 @@ struct Challenge { throw std::runtime_error(std::format("Invalid name {} for challenge", node->get_name())); } std::string decoded = DecodeBase64(node->get_first_child_text()->get_content()); - auto params = std::views::split(decoded, ',') // - | std::views::transform([](auto param) { // - return std::string_view{param}; // - }) // - | std::views::transform([](std::string_view param) -> std::pair { // - auto v = param.find("="); // - return {param.substr(0, v), param.substr(v + 1)}; // - }) // - | std::ranges::to>(); + auto params = std::views::split(decoded, ',') | std::views::transform([](auto param) { + return std::string_view{param}; + }) | + std::views::transform([](std::string_view param) -> std::pair { + auto v = param.find("="); + return {param.substr(0, v), param.substr(v + 1)}; + }) | + std::ranges::to>(); return {.body = std::move(decoded), .serverNonce = params.at("r"), .salt = DecodeBase64(params.at("s")), diff --git a/library/include/larra/roster.hpp b/library/include/larra/roster.hpp new file mode 100644 index 0000000..b1e6350 --- /dev/null +++ b/library/include/larra/roster.hpp @@ -0,0 +1,76 @@ +#pragma once +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace larra::xmpp::iq { + +struct Roster { + static constexpr auto kDefaultName = "query"; + static constexpr auto kDefaultNamespace = "jabber:iq:roster"; + + std::vector items; + + friend auto ToString(const Roster& roster) -> std::string { + static constexpr std::string_view prefix = "Roster: [\n\t"; + static constexpr std::string_view suffix = "]"; + // \n\r\t + std::size_t total_length = std::ranges::fold_left(roster.items | std::views::transform([](const auto& el) { + return larra::xmpp::utils::AccumulateFieldLength(el) + 3; + }), + prefix.length() + suffix.length(), + std::plus<>{}); + + std::string s; + s.resize(total_length); + s = prefix; + for(const auto& el : roster.items) { + s += ToString(el); + s += "\n\t"; + } + return s += suffix; + } + friend constexpr auto operator<<(xmlpp::Element* element, const Roster& roster) { + element->set_attribute("xmlns", Roster::kDefaultNamespace); + std::ranges::for_each(roster.items, [element](const auto& item) { + element->add_child_element("item")->set_attribute("jid", ToString(item)); + }); + } + [[nodiscard]] static constexpr auto Parse(xmlpp::Element* element) -> Roster { + const auto& item_nodes = element->get_children("item"); + if(item_nodes.empty()) { + SPDLOG_DEBUG("No items at Iq::Roster"); + } + + return {.items = item_nodes | std::views::transform([](const xmlpp::Node* node) { + auto item_element = dynamic_cast(node); + if(!item_element) { + throw std::runtime_error("Can't convert xmlpp::Node to xmlpp::Element"); + } + + auto jid_ptr = item_element->get_attribute("jid"); + if(!jid_ptr) { + throw std::runtime_error("Not found attribute 'jid' for parse Roster item"); + } + + return BareJid::Parse(jid_ptr->get_value()); + }) | + std::ranges::to>()}; + } +}; + +using GetRoster = Get; +using ResultRoster = Result; +using IqRoster = Iq; + +inline auto MakeGetRoster(const FullJid& jid) { + return GetRoster{.id = "1", .from = ToString(jid), .payload = Roster{}}; +} + +} // namespace larra::xmpp::iq diff --git a/library/include/larra/utils.hpp b/library/include/larra/utils.hpp index 1fb05a5..f3a702e 100644 --- a/library/include/larra/utils.hpp +++ b/library/include/larra/utils.hpp @@ -295,4 +295,18 @@ struct RangeToWrapper : T { : T{std::forward(args)...} {}; }; +template +concept LengthCalculatable = requires(const T& obj) { + { obj.length() } -> std::convertible_to; // Checks if obj has a length() method returning a type convertible to std::size_t +} || std::convertible_to; + +template +auto AccumulateFieldLength(const T& obj) -> std::size_t { + std::size_t total_length = 0; + boost::pfr::for_each_field(obj, [&](const LengthCalculatable auto& field) { + total_length += field.length(); // Accumulate length of each field + }); + return total_length; +} + } // namespace larra::xmpp::utils diff --git a/tests/roster.cpp b/tests/roster.cpp new file mode 100644 index 0000000..f6b6c45 --- /dev/null +++ b/tests/roster.cpp @@ -0,0 +1,39 @@ +#include + +#include + +#include "larra/jid.hpp" + +namespace larra::xmpp { + +TEST(Roster, SerializeAndParse) { + FullJid jid{.username = "test", .server = "server", .resource = "res"}; // NOLINT + auto roster = iq::MakeGetRoster(jid); + roster.payload.items.emplace_back("u1", "s1"); + roster.payload.items.emplace_back("u2", "s2"); + roster.payload.items.emplace_back("u3", "s3"); + + xmlpp::Document doc; + auto node = doc.create_root_node("iq"); + node << roster; + + auto parse_res = decltype(roster)::Parse(node); + + ASSERT_EQ(roster.payload.items.size(), parse_res.payload.items.size()); + for(const auto& [idx, expect_el, parsed_el] : std::views::zip(std::views::iota(0), roster.payload.items, parse_res.payload.items)) { + EXPECT_EQ(expect_el, parsed_el) << "Mismatched on idx: " << idx; + // std::cerr << " " << "idx: " << idx << "; expect_el: " << expect_el << "; parsed_el: " << parsed_el << '\n'; + } +} + +TEST(Roster, Print) { + FullJid jid{.username = "test", .server = "server", .resource = "res"}; // NOLINT + auto roster = iq::MakeGetRoster(jid); + roster.payload.items.emplace_back("u1", "s1"); + roster.payload.items.emplace_back("u2", "s2"); + roster.payload.items.emplace_back("u3", "s3"); + + EXPECT_NO_THROW({ std::cerr << "[ ] Roster payload: " << ToString(roster.payload) << '\n'; }); +} + +} // namespace larra::xmpp -- 2.47.0 From 30a5e69d14153083d6edf1b53dee7cde8833cd95 Mon Sep 17 00:00:00 2001 From: Ivan-lis Date: Mon, 18 Nov 2024 23:04:57 +0000 Subject: [PATCH 3/3] Fixed errors and improve roster tests --- .vscode/launch.json | 2 +- library/include/larra/bind.hpp | 3 -- library/include/larra/client/client.hpp | 47 ++++++++++++++++--------- library/include/larra/iq.hpp | 28 ++++++++++----- library/include/larra/jid.hpp | 5 +-- library/include/larra/roster.hpp | 8 ++--- library/include/larra/stream_error.hpp | 29 +++++++++++++-- library/include/larra/utils.hpp | 2 +- tests/roster.cpp | 21 +++++------ tests/serialization.cpp | 3 ++ 10 files changed, 96 insertions(+), 52 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 974f9e6..fa2cb78 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -20,7 +20,7 @@ "program": "${workspaceFolder}/build/larra_xmpp_tests", "args": [ // --gtest_filter=POSTIVE_PATTERNS[-NEGATIVE_PATTERNS] - "--gtest_filter=Roster*" + // "--gtest_filter=Roster*" ], "cwd": "${workspaceFolder}", "preLaunchTask": "GCC: Build" diff --git a/library/include/larra/bind.hpp b/library/include/larra/bind.hpp index ccc2a57..bdbdb90 100644 --- a/library/include/larra/bind.hpp +++ b/library/include/larra/bind.hpp @@ -47,7 +47,4 @@ using SetBind = Set; using ResultBind = Result; using IqBind = Iq; -inline auto MakeSetBind() { - return SetBind{.id = "1", .payload = Bind{}}; -} } // namespace larra::xmpp::iq diff --git a/library/include/larra/client/client.hpp b/library/include/larra/client/client.hpp index ba80489..9b9890b 100644 --- a/library/include/larra/client/client.hpp +++ b/library/include/larra/client/client.hpp @@ -80,21 +80,35 @@ struct Client { auto CreateResourceBind() -> boost::asio::awaitable { SPDLOG_INFO("Send IQ: Set::Bind"); - co_await this->Send(::iq::MakeSetBind()); + co_await this->Send(::iq::SetBind{.id = "1", .payload = {}}); - auto bind_result = co_await connection.template Read<::iq::ResultBind>(); - jid.resource = std::move(bind_result.payload.jid->resource); + auto set_bind_response = co_await connection.template Read>(); + std::visit(utempl::Overloaded( + [](auto error) { + throw "Error response on IQ: Set::Bind: ''"; // TODO(unknown): Add exact error parsing + }, + [&](::iq::ResultBind r) { + jid.resource = std::move(r.payload.jid->resource); + SPDLOG_INFO("Allocated resource: {}", jid.resource); + }), + set_bind_response); co_return; } auto UpdateListOfContacts() -> boost::asio::awaitable { SPDLOG_INFO("Send IQ: Get::Roster"); - co_await this->Send(::iq::MakeGetRoster(jid)); + co_await this->Send(::iq::GetRoster{.id = "1", .from = std::format("{}@{}", "invalid_user", jid.server), .payload = {}}); - const auto roster_result = co_await connection.template Read<::iq::ResultRoster>(); - roster = std::move(roster_result.payload); - - SPDLOG_INFO("New roster: {}", ToString(roster)); + const auto get_roster_response = co_await connection.template Read>(); + std::visit(utempl::Overloaded( + [](auto error) { + throw "Error response on IQ: Get::Roster: ''"; // TODO(unknown): Add exact error parsing + }, + [&](::iq::ResultRoster r) { + roster = std::move(r.payload); + SPDLOG_INFO("New roster: {}", ToString(roster)); + }), + get_roster_response); co_return; } @@ -143,14 +157,15 @@ struct Challenge { throw std::runtime_error(std::format("Invalid name {} for challenge", node->get_name())); } std::string decoded = DecodeBase64(node->get_first_child_text()->get_content()); - auto params = std::views::split(decoded, ',') | std::views::transform([](auto param) { - return std::string_view{param}; - }) | - std::views::transform([](std::string_view param) -> std::pair { - auto v = param.find("="); - return {param.substr(0, v), param.substr(v + 1)}; - }) | - std::ranges::to>(); + auto params = std::views::split(decoded, ',') // + | std::views::transform([](auto param) { // + return std::string_view{param}; // + }) // + | std::views::transform([](std::string_view param) -> std::pair { // + auto v = param.find("="); // + return {param.substr(0, v), param.substr(v + 1)}; // + }) // + | std::ranges::to>(); return {.body = std::move(decoded), .serverNonce = params.at("r"), .salt = DecodeBase64(params.at("s")), diff --git a/library/include/larra/iq.hpp b/library/include/larra/iq.hpp index 5efd612..7a3a811 100644 --- a/library/include/larra/iq.hpp +++ b/library/include/larra/iq.hpp @@ -37,10 +37,10 @@ struct BaseImplWithPayload { friend constexpr auto operator<<(xmlpp::Element* element, const BaseImplWithPayload& self) { element->set_attribute("id", self.id); - if (self.to) { + if(self.to) { element->set_attribute("to", *self.to); } - if (self.from) { + if(self.from) { element->set_attribute("from", *self.from); } element->set_attribute("type", kName); @@ -48,6 +48,19 @@ struct BaseImplWithPayload { S::Serialize(element->add_child_element(S::kDefaultName, S::kPrefix), self.payload); } + [[nodiscard]] static constexpr auto TryParse(xmlpp::Element* element) -> std::optional { + return [&] -> std::optional { + auto node = element->get_attribute("type"); + if(!node) { + return std::nullopt; + } + if(node->get_value() != Name) { + return std::nullopt; + } + return Parse(element); + }(); + } + [[nodiscard]] static constexpr auto Parse(xmlpp::Element* element) -> BaseImplWithPayload { auto node = element->get_attribute("type"); if(!node) { @@ -62,7 +75,6 @@ struct BaseImplWithPayload { } auto from = element->get_attribute("from"); auto to = element->get_attribute("to"); - using S = Serialization; auto payload = element->get_first_child(S::kDefaultName); @@ -73,13 +85,13 @@ struct BaseImplWithPayload { if(!payload2) { throw std::runtime_error("Invalid payload for parse Iq"); } - return { - .id = idNode->get_value(), - .from = (from ? std::optional{from->get_value()} : std::nullopt), - .to = (to ? std::optional{to->get_value()} : std::nullopt), - .payload = S::Parse(payload2)}; + return {.id = idNode->get_value(), + .from = (from ? std::optional{from->get_value()} : std::nullopt), + .to = (to ? std::optional{to->get_value()} : std::nullopt), + .payload = S::Parse(payload2)}; } }; + static constexpr auto kGetName = "get"; template diff --git a/library/include/larra/jid.hpp b/library/include/larra/jid.hpp index 1ea3cd8..34dcad4 100644 --- a/library/include/larra/jid.hpp +++ b/library/include/larra/jid.hpp @@ -54,9 +54,10 @@ struct FullJid { struct BareJid { std::string username; std::string server; - constexpr operator FullJid() const { - return FullJid{.username = username, .server = server, .resource = ""}; + constexpr operator FullJid(this auto&& self) { + return {.username = std::forward_like(self.username), .server = std::forward_like(self.server)}; } + [[nodiscard]] static auto Parse(std::string_view jid) -> BareJid; friend auto ToString(const BareJid& jid) -> std::string; diff --git a/library/include/larra/roster.hpp b/library/include/larra/roster.hpp index b1e6350..c0635da 100644 --- a/library/include/larra/roster.hpp +++ b/library/include/larra/roster.hpp @@ -38,9 +38,9 @@ struct Roster { } friend constexpr auto operator<<(xmlpp::Element* element, const Roster& roster) { element->set_attribute("xmlns", Roster::kDefaultNamespace); - std::ranges::for_each(roster.items, [element](const auto& item) { + for(const auto& item : roster.items) { element->add_child_element("item")->set_attribute("jid", ToString(item)); - }); + } } [[nodiscard]] static constexpr auto Parse(xmlpp::Element* element) -> Roster { const auto& item_nodes = element->get_children("item"); @@ -69,8 +69,4 @@ using GetRoster = Get; using ResultRoster = Result; using IqRoster = Iq; -inline auto MakeGetRoster(const FullJid& jid) { - return GetRoster{.id = "1", .from = ToString(jid), .payload = Roster{}}; -} - } // namespace larra::xmpp::iq diff --git a/library/include/larra/stream_error.hpp b/library/include/larra/stream_error.hpp index 4a6615c..ca908aa 100644 --- a/library/include/larra/stream_error.hpp +++ b/library/include/larra/stream_error.hpp @@ -73,6 +73,25 @@ struct ErrorImpl : BaseError { } }; +// Helper class to prevent parsing response stream into an expected return type if its name is a 'stream:error' +struct UnknownXmppError : BaseError { + static constexpr auto kDefaultName = "stream:error"; + static constexpr std::string_view kErrorMessage = "Unknown XMPP stream error"; + + static constexpr auto TryParse(xmlpp::Element* element) { + return std::optional{UnknownXmppError{}}; + } + static constexpr auto Parse(xmlpp::Element* element) { + return TryParse(element).value(); + } + friend constexpr auto operator<<(xmlpp::Element* element, const UnknownXmppError& obj) -> void { + throw std::format("'{}' must never be written into the real stream!", kErrorMessage); + } + [[nodiscard]] constexpr auto what() const noexcept -> const char* override { + return kErrorMessage.data(); + } +}; + struct BadFormat : ErrorImpl {}; struct BadNamespacePrefix : ErrorImpl {}; struct Conflict : ErrorImpl {}; @@ -81,7 +100,7 @@ struct HostGone : ErrorImpl {}; struct HostUnknown : ErrorImpl {}; struct ImproperAdressing : ErrorImpl {}; struct InternalServerError : ErrorImpl {}; -struct InvalidForm : ErrorImpl {}; +struct InvalidFrom : ErrorImpl {}; struct InvalidNamespace : ErrorImpl {}; struct InvalidXml : ErrorImpl {}; struct NotAuthorized : ErrorImpl {}; @@ -109,7 +128,7 @@ using StreamError = std::variant; + error::stream::UnsupportedVersion, + error::stream::UnknownXmppError>; + +static_assert(!std::is_same_v - 1, StreamError>, StreamError>, + "'UnknownXmppError' must be at the end of 'StreamError' variant"); } // namespace larra::xmpp diff --git a/library/include/larra/utils.hpp b/library/include/larra/utils.hpp index f3a702e..214e676 100644 --- a/library/include/larra/utils.hpp +++ b/library/include/larra/utils.hpp @@ -297,7 +297,7 @@ struct RangeToWrapper : T { template concept LengthCalculatable = requires(const T& obj) { - { obj.length() } -> std::convertible_to; // Checks if obj has a length() method returning a type convertible to std::size_t + { obj.length() } -> std::convertible_to; } || std::convertible_to; template diff --git a/tests/roster.cpp b/tests/roster.cpp index f6b6c45..b30e15b 100644 --- a/tests/roster.cpp +++ b/tests/roster.cpp @@ -1,17 +1,13 @@ #include +#include #include -#include "larra/jid.hpp" - namespace larra::xmpp { TEST(Roster, SerializeAndParse) { FullJid jid{.username = "test", .server = "server", .resource = "res"}; // NOLINT - auto roster = iq::MakeGetRoster(jid); - roster.payload.items.emplace_back("u1", "s1"); - roster.payload.items.emplace_back("u2", "s2"); - roster.payload.items.emplace_back("u3", "s3"); + auto roster = iq::GetRoster{.id = "1", .from = ToString(jid), .payload = iq::Roster{.items = {{"u1", "s1"}, {"u2", "s2"}, {"u3", "s3"}}}}; xmlpp::Document doc; auto node = doc.create_root_node("iq"); @@ -22,18 +18,19 @@ TEST(Roster, SerializeAndParse) { ASSERT_EQ(roster.payload.items.size(), parse_res.payload.items.size()); for(const auto& [idx, expect_el, parsed_el] : std::views::zip(std::views::iota(0), roster.payload.items, parse_res.payload.items)) { EXPECT_EQ(expect_el, parsed_el) << "Mismatched on idx: " << idx; - // std::cerr << " " << "idx: " << idx << "; expect_el: " << expect_el << "; parsed_el: " << parsed_el << '\n'; } } +static constexpr std::string_view kRosterPrintExpectedData = "Roster: [\n\tu1@s1\n\tu2@s2\n\tu3@s3\n\t]"; TEST(Roster, Print) { FullJid jid{.username = "test", .server = "server", .resource = "res"}; // NOLINT - auto roster = iq::MakeGetRoster(jid); - roster.payload.items.emplace_back("u1", "s1"); - roster.payload.items.emplace_back("u2", "s2"); - roster.payload.items.emplace_back("u3", "s3"); + auto roster = iq::GetRoster{.id = "1", .from = ToString(jid), .payload = iq::Roster{.items = {{"u1", "s1"}, {"u2", "s2"}, {"u3", "s3"}}}}; - EXPECT_NO_THROW({ std::cerr << "[ ] Roster payload: " << ToString(roster.payload) << '\n'; }); + EXPECT_NO_THROW({ + auto roster_str = ToString(roster.payload); + EXPECT_EQ(kRosterPrintExpectedData.length(), roster_str.capacity()); + EXPECT_EQ(kRosterPrintExpectedData, roster_str); + }); } } // namespace larra::xmpp diff --git a/tests/serialization.cpp b/tests/serialization.cpp index abe41b3..10b69cd 100644 --- a/tests/serialization.cpp +++ b/tests/serialization.cpp @@ -5,6 +5,9 @@ #include #include #include +#include + +#include "utempl/utils.hpp" using namespace std::literals; -- 2.47.0