Features: iq::Bind and iq::Roster #4
10 changed files with 96 additions and 52 deletions
2
.vscode/launch.json
vendored
2
.vscode/launch.json
vendored
|
@ -20,7 +20,7 @@
|
||||||
"program": "${workspaceFolder}/build/larra_xmpp_tests",
|
"program": "${workspaceFolder}/build/larra_xmpp_tests",
|
||||||
"args": [
|
"args": [
|
||||||
// --gtest_filter=POSTIVE_PATTERNS[-NEGATIVE_PATTERNS]
|
// --gtest_filter=POSTIVE_PATTERNS[-NEGATIVE_PATTERNS]
|
||||||
"--gtest_filter=Roster*"
|
// "--gtest_filter=Roster*"
|
||||||
],
|
],
|
||||||
"cwd": "${workspaceFolder}",
|
"cwd": "${workspaceFolder}",
|
||||||
"preLaunchTask": "GCC: Build"
|
"preLaunchTask": "GCC: Build"
|
||||||
|
|
|
@ -47,7 +47,4 @@ using SetBind = Set<Bind>;
|
||||||
using ResultBind = Result<Bind>;
|
using ResultBind = Result<Bind>;
|
||||||
using IqBind = Iq<Bind>;
|
using IqBind = Iq<Bind>;
|
||||||
|
|
||||||
inline auto MakeSetBind() {
|
|
||||||
return SetBind{.id = "1", .payload = Bind{}};
|
|
||||||
}
|
|
||||||
} // namespace larra::xmpp::iq
|
} // namespace larra::xmpp::iq
|
||||||
|
|
|
@ -80,21 +80,35 @@ struct Client {
|
||||||
|
|
||||||
auto CreateResourceBind() -> boost::asio::awaitable<void> {
|
auto CreateResourceBind() -> boost::asio::awaitable<void> {
|
||||||
SPDLOG_INFO("Send IQ: Set::Bind");
|
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>();
|
auto set_bind_response = co_await connection.template Read<Iq<::iq::Bind>>();
|
||||||
jid.resource = std::move(bind_result.payload.jid->resource);
|
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;
|
co_return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto UpdateListOfContacts() -> boost::asio::awaitable<void> {
|
auto UpdateListOfContacts() -> boost::asio::awaitable<void> {
|
||||||
|
|||||||
SPDLOG_INFO("Send IQ: Get::Roster");
|
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>();
|
const auto get_roster_response = co_await connection.template Read<Iq<::iq::Roster>>();
|
||||||
roster = std::move(roster_result.payload);
|
std::visit(utempl::Overloaded(
|
||||||
|
[](auto error) {
|
||||||
SPDLOG_INFO("New roster: {}", ToString(roster));
|
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;
|
co_return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,14 +157,15 @@ struct Challenge {
|
||||||
throw std::runtime_error(std::format("Invalid name {} for challenge", node->get_name()));
|
throw std::runtime_error(std::format("Invalid name {} for challenge", node->get_name()));
|
||||||
}
|
}
|
||||||
std::string decoded = DecodeBase64(node->get_first_child_text()->get_content());
|
std::string decoded = DecodeBase64(node->get_first_child_text()->get_content());
|
||||||
auto params = std::views::split(decoded, ',') | std::views::transform([](auto param) {
|
auto params = std::views::split(decoded, ',') //
|
||||||
return std::string_view{param};
|
| std::views::transform([](auto param) { //
|
||||||
}) |
|
return std::string_view{param}; //
|
||||||
std::views::transform([](std::string_view param) -> std::pair<std::string_view, std::string_view> {
|
}) //
|
||||||
auto v = param.find("=");
|
| std::views::transform([](std::string_view param) -> std::pair<std::string_view, std::string_view> { //
|
||||||
return {param.substr(0, v), param.substr(v + 1)};
|
auto v = param.find("="); //
|
||||||
}) |
|
return {param.substr(0, v), param.substr(v + 1)}; //
|
||||||
std::ranges::to<std::unordered_map<std::string_view, std::string_view>>();
|
}) //
|
||||||
|
| std::ranges::to<std::unordered_map<std::string_view, std::string_view>>();
|
||||||
return {.body = std::move(decoded),
|
return {.body = std::move(decoded),
|
||||||
.serverNonce = params.at("r"),
|
.serverNonce = params.at("r"),
|
||||||
.salt = DecodeBase64(params.at("s")),
|
.salt = DecodeBase64(params.at("s")),
|
||||||
|
|
|
@ -37,10 +37,10 @@ struct BaseImplWithPayload {
|
||||||
friend constexpr auto operator<<(xmlpp::Element* element, const BaseImplWithPayload& self) {
|
friend constexpr auto operator<<(xmlpp::Element* element, const BaseImplWithPayload& self) {
|
||||||
element->set_attribute("id", self.id);
|
element->set_attribute("id", self.id);
|
||||||
|
|
||||||
if (self.to) {
|
if(self.to) {
|
||||||
element->set_attribute("to", *self.to);
|
element->set_attribute("to", *self.to);
|
||||||
}
|
}
|
||||||
if (self.from) {
|
if(self.from) {
|
||||||
element->set_attribute("from", *self.from);
|
element->set_attribute("from", *self.from);
|
||||||
}
|
}
|
||||||
element->set_attribute("type", kName);
|
element->set_attribute("type", kName);
|
||||||
|
@ -48,6 +48,19 @@ struct BaseImplWithPayload {
|
||||||
S::Serialize(element->add_child_element(S::kDefaultName, S::kPrefix), self.payload);
|
S::Serialize(element->add_child_element(S::kDefaultName, S::kPrefix), self.payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] static constexpr auto TryParse(xmlpp::Element* element) -> std::optional<BaseImplWithPayload> {
|
||||||
|
return [&] -> std::optional<BaseImplWithPayload> {
|
||||||
|
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 {
|
[[nodiscard]] static constexpr auto Parse(xmlpp::Element* element) -> BaseImplWithPayload {
|
||||||
auto node = element->get_attribute("type");
|
auto node = element->get_attribute("type");
|
||||||
if(!node) {
|
if(!node) {
|
||||||
|
@ -63,7 +76,6 @@ struct BaseImplWithPayload {
|
||||||
auto from = element->get_attribute("from");
|
auto from = element->get_attribute("from");
|
||||||
auto to = element->get_attribute("to");
|
auto to = element->get_attribute("to");
|
||||||
|
|
||||||
|
|
||||||
using S = Serialization<PayloadType>;
|
using S = Serialization<PayloadType>;
|
||||||
auto payload = element->get_first_child(S::kDefaultName);
|
auto payload = element->get_first_child(S::kDefaultName);
|
||||||
if(!payload) {
|
if(!payload) {
|
||||||
|
@ -73,13 +85,13 @@ struct BaseImplWithPayload {
|
||||||
if(!payload2) {
|
if(!payload2) {
|
||||||
throw std::runtime_error("Invalid payload for parse Iq");
|
throw std::runtime_error("Invalid payload for parse Iq");
|
||||||
}
|
}
|
||||||
return {
|
return {.id = idNode->get_value(),
|
||||||
.id = idNode->get_value(),
|
.from = (from ? std::optional{from->get_value()} : std::nullopt),
|
||||||
.from = (from ? std::optional{from->get_value()} : std::nullopt),
|
.to = (to ? std::optional{to->get_value()} : std::nullopt),
|
||||||
.to = (to ? std::optional{to->get_value()} : std::nullopt),
|
.payload = S::Parse(payload2)};
|
||||||
.payload = S::Parse(payload2)};
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
static constexpr auto kGetName = "get";
|
static constexpr auto kGetName = "get";
|
||||||
|
|
||||||
template <typename Payload>
|
template <typename Payload>
|
||||||
|
|
|
@ -54,9 +54,10 @@ struct FullJid {
|
||||||
struct BareJid {
|
struct BareJid {
|
||||||
std::string username;
|
std::string username;
|
||||||
std::string server;
|
std::string server;
|
||||||
constexpr operator FullJid() const {
|
constexpr operator FullJid(this auto&& self) {
|
||||||
return FullJid{.username = username, .server = server, .resource = ""};
|
return {.username = std::forward_like<decltype(self)>(self.username), .server = std::forward_like<decltype(self)>(self.server)};
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] static auto Parse(std::string_view jid) -> BareJid;
|
[[nodiscard]] static auto Parse(std::string_view jid) -> BareJid;
|
||||||
friend auto ToString(const BareJid& jid) -> std::string;
|
friend auto ToString(const BareJid& jid) -> std::string;
|
||||||
|
|
||||||
|
|
|
@ -38,9 +38,9 @@ struct Roster {
|
||||||
}
|
}
|
||||||
friend constexpr auto operator<<(xmlpp::Element* element, const Roster& roster) {
|
friend constexpr auto operator<<(xmlpp::Element* element, const Roster& roster) {
|
||||||
element->set_attribute("xmlns", Roster::kDefaultNamespace);
|
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));
|
element->add_child_element("item")->set_attribute("jid", ToString(item));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
[[nodiscard]] static constexpr auto Parse(xmlpp::Element* element) -> Roster {
|
[[nodiscard]] static constexpr auto Parse(xmlpp::Element* element) -> Roster {
|
||||||
const auto& item_nodes = element->get_children("item");
|
const auto& item_nodes = element->get_children("item");
|
||||||
|
@ -69,8 +69,4 @@ using GetRoster = Get<Roster>;
|
||||||
using ResultRoster = Result<Roster>;
|
using ResultRoster = Result<Roster>;
|
||||||
using IqRoster = Iq<Roster>;
|
using IqRoster = Iq<Roster>;
|
||||||
|
|
||||||
inline auto MakeGetRoster(const FullJid& jid) {
|
|
||||||
return GetRoster{.id = "1", .from = ToString(jid), .payload = Roster{}};
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace larra::xmpp::iq
|
} // namespace larra::xmpp::iq
|
||||||
|
|
|
@ -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<BadFormat> {};
|
struct BadFormat : ErrorImpl<BadFormat> {};
|
||||||
struct BadNamespacePrefix : ErrorImpl<BadNamespacePrefix> {};
|
struct BadNamespacePrefix : ErrorImpl<BadNamespacePrefix> {};
|
||||||
struct Conflict : ErrorImpl<Conflict> {};
|
struct Conflict : ErrorImpl<Conflict> {};
|
||||||
|
@ -81,7 +100,7 @@ struct HostGone : ErrorImpl<HostGone> {};
|
||||||
struct HostUnknown : ErrorImpl<HostUnknown> {};
|
struct HostUnknown : ErrorImpl<HostUnknown> {};
|
||||||
struct ImproperAdressing : ErrorImpl<ImproperAdressing> {};
|
struct ImproperAdressing : ErrorImpl<ImproperAdressing> {};
|
||||||
struct InternalServerError : ErrorImpl<InternalServerError> {};
|
struct InternalServerError : ErrorImpl<InternalServerError> {};
|
||||||
struct InvalidForm : ErrorImpl<InvalidForm> {};
|
struct InvalidFrom : ErrorImpl<InvalidFrom> {};
|
||||||
struct InvalidNamespace : ErrorImpl<InvalidNamespace> {};
|
struct InvalidNamespace : ErrorImpl<InvalidNamespace> {};
|
||||||
struct InvalidXml : ErrorImpl<InvalidXml> {};
|
struct InvalidXml : ErrorImpl<InvalidXml> {};
|
||||||
struct NotAuthorized : ErrorImpl<NotAuthorized> {};
|
struct NotAuthorized : ErrorImpl<NotAuthorized> {};
|
||||||
|
@ -109,7 +128,7 @@ using StreamError = std::variant<error::stream::BadFormat,
|
||||||
error::stream::HostUnknown,
|
error::stream::HostUnknown,
|
||||||
error::stream::ImproperAdressing,
|
error::stream::ImproperAdressing,
|
||||||
error::stream::InternalServerError,
|
error::stream::InternalServerError,
|
||||||
error::stream::InvalidForm,
|
error::stream::InvalidFrom,
|
||||||
error::stream::InvalidNamespace,
|
error::stream::InvalidNamespace,
|
||||||
error::stream::InvalidXml,
|
error::stream::InvalidXml,
|
||||||
error::stream::NotAuthorized,
|
error::stream::NotAuthorized,
|
||||||
|
@ -123,6 +142,10 @@ using StreamError = std::variant<error::stream::BadFormat,
|
||||||
error::stream::UnsupportedEncoding,
|
error::stream::UnsupportedEncoding,
|
||||||
error::stream::UnsupportedFeature,
|
error::stream::UnsupportedFeature,
|
||||||
error::stream::UnsupportedStanzaType,
|
error::stream::UnsupportedStanzaType,
|
||||||
error::stream::UnsupportedVersion>;
|
error::stream::UnsupportedVersion,
|
||||||
|
error::stream::UnknownXmppError>;
|
||||||
|
|
||||||
|
static_assert(!std::is_same_v<typename std::variant_alternative_t<std::variant_size_v<StreamError> - 1, StreamError>, StreamError>,
|
||||||
|
"'UnknownXmppError' must be at the end of 'StreamError' variant");
|
||||||
|
|
||||||
} // namespace larra::xmpp
|
} // namespace larra::xmpp
|
||||||
|
|
|
@ -297,7 +297,7 @@ struct RangeToWrapper : T {
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
concept LengthCalculatable = requires(const T& obj) {
|
concept LengthCalculatable = requires(const T& obj) {
|
||||||
{ obj.length() } -> std::convertible_to<std::size_t>; // Checks if obj has a length() method returning a type convertible to std::size_t
|
{ obj.length() } -> std::convertible_to<std::size_t>;
|
||||||
} || std::convertible_to<T, std::string>;
|
} || std::convertible_to<T, std::string>;
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
|
|
|
@ -1,17 +1,13 @@
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <larra/jid.hpp>
|
||||||
#include <larra/roster.hpp>
|
#include <larra/roster.hpp>
|
||||||
|
|
||||||
#include "larra/jid.hpp"
|
|
||||||
|
|
||||||
namespace larra::xmpp {
|
namespace larra::xmpp {
|
||||||
|
|
||||||
TEST(Roster, SerializeAndParse) {
|
TEST(Roster, SerializeAndParse) {
|
||||||
FullJid jid{.username = "test", .server = "server", .resource = "res"}; // NOLINT
|
FullJid jid{.username = "test", .server = "server", .resource = "res"}; // NOLINT
|
||||||
auto roster = iq::MakeGetRoster(jid);
|
auto roster = iq::GetRoster{.id = "1", .from = ToString(jid), .payload = iq::Roster{.items = {{"u1", "s1"}, {"u2", "s2"}, {"u3", "s3"}}}};
|
||||||
roster.payload.items.emplace_back("u1", "s1");
|
|
||||||
roster.payload.items.emplace_back("u2", "s2");
|
|
||||||
roster.payload.items.emplace_back("u3", "s3");
|
|
||||||
|
|
||||||
xmlpp::Document doc;
|
xmlpp::Document doc;
|
||||||
auto node = doc.create_root_node("iq");
|
auto node = doc.create_root_node("iq");
|
||||||
Ivan-lis marked this conversation as resolved
sha512sum
commented
Why not just throw all the necessary elements through the vector constructor in Roster constructor? Why not just throw all the necessary elements through the vector constructor in Roster constructor?
|
|||||||
|
@ -22,18 +18,19 @@ TEST(Roster, SerializeAndParse) {
|
||||||
ASSERT_EQ(roster.payload.items.size(), parse_res.payload.items.size());
|
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)) {
|
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;
|
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) {
|
TEST(Roster, Print) {
|
||||||
Ivan-lis marked this conversation as resolved
sha512sum
commented
Code left in the comment Code left in the comment
|
|||||||
FullJid jid{.username = "test", .server = "server", .resource = "res"}; // NOLINT
|
FullJid jid{.username = "test", .server = "server", .resource = "res"}; // NOLINT
|
||||||
auto roster = iq::MakeGetRoster(jid);
|
auto roster = iq::GetRoster{.id = "1", .from = ToString(jid), .payload = iq::Roster{.items = {{"u1", "s1"}, {"u2", "s2"}, {"u3", "s3"}}}};
|
||||||
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'; });
|
EXPECT_NO_THROW({
|
||||||
|
auto roster_str = ToString(roster.payload);
|
||||||
|
EXPECT_EQ(kRosterPrintExpectedData.length(), roster_str.capacity());
|
||||||
|
EXPECT_EQ(kRosterPrintExpectedData, roster_str);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace larra::xmpp
|
} // namespace larra::xmpp
|
||||||
Ivan-lis marked this conversation as resolved
sha512sum
commented
EXPECT_EQ and test content EXPECT_EQ and test content
|
|||||||
|
|
|
@ -5,6 +5,9 @@
|
||||||
#include <larra/serialization/auto.hpp>
|
#include <larra/serialization/auto.hpp>
|
||||||
#include <larra/serialization/error.hpp>
|
#include <larra/serialization/error.hpp>
|
||||||
#include <larra/stream_error.hpp>
|
#include <larra/stream_error.hpp>
|
||||||
|
#include <variant>
|
||||||
|
|
||||||
|
#include "utempl/utils.hpp"
|
||||||
|
|
||||||
using namespace std::literals;
|
using namespace std::literals;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue
Maybe we can change namings later