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",
|
||||
"args": [
|
||||
// --gtest_filter=POSTIVE_PATTERNS[-NEGATIVE_PATTERNS]
|
||||
"--gtest_filter=Roster*"
|
||||
// "--gtest_filter=Roster*"
|
||||
],
|
||||
"cwd": "${workspaceFolder}",
|
||||
"preLaunchTask": "GCC: Build"
|
||||
|
|
|
@ -47,7 +47,4 @@ using SetBind = Set<Bind>;
|
|||
using ResultBind = Result<Bind>;
|
||||
using IqBind = Iq<Bind>;
|
||||
|
||||
inline auto MakeSetBind() {
|
||||
return SetBind{.id = "1", .payload = Bind{}};
|
||||
}
|
||||
} // namespace larra::xmpp::iq
|
||||
|
|
|
@ -80,21 +80,35 @@ struct Client {
|
|||
|
||||
auto CreateResourceBind() -> boost::asio::awaitable<void> {
|
||||
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<Iq<::iq::Bind>>();
|
||||
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<void> {
|
||||
|
||||
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);
|
||||
co_await this->Send(::iq::GetRoster{.id = "1", .from = std::format("{}@{}", "invalid_user", jid.server), .payload = {}});
|
||||
|
||||
const auto get_roster_response = co_await connection.template Read<Iq<::iq::Roster>>();
|
||||
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<std::string_view, std::string_view> {
|
||||
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>>();
|
||||
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<std::string_view, std::string_view> { //
|
||||
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>>();
|
||||
return {.body = std::move(decoded),
|
||||
.serverNonce = params.at("r"),
|
||||
.salt = DecodeBase64(params.at("s")),
|
||||
|
|
|
@ -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<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 {
|
||||
auto node = element->get_attribute("type");
|
||||
if(!node) {
|
||||
|
@ -63,7 +76,6 @@ struct BaseImplWithPayload {
|
|||
auto from = element->get_attribute("from");
|
||||
auto to = element->get_attribute("to");
|
||||
|
||||
|
||||
using S = Serialization<PayloadType>;
|
||||
auto payload = element->get_first_child(S::kDefaultName);
|
||||
if(!payload) {
|
||||
|
@ -73,13 +85,13 @@ struct BaseImplWithPayload {
|
|||
if(!payload2) {
|
||||
throw std::runtime_error("Invalid payload for parse Iq");
|
||||
}
|
||||
return {
|
||||
.id = idNode->get_value(),
|
||||
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 <typename Payload>
|
||||
|
|
|
@ -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<decltype(self)>(self.username), .server = std::forward_like<decltype(self)>(self.server)};
|
||||
Ivan-lis marked this conversation as resolved
Outdated
sha512sum
commented
Extra copying if there is an rvalue. Better to forward depending on the type with which it is called. Extra copying if there is an rvalue. Better to forward depending on the type with which it is called.
|
||||
}
|
||||
|
||||
[[nodiscard]] static auto Parse(std::string_view jid) -> BareJid;
|
||||
friend auto ToString(const BareJid& jid) -> std::string;
|
||||
|
||||
|
|
|
@ -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) {
|
||||
sha512sum marked this conversation as resolved
Outdated
sha512sum
commented
Why not range based for? Why not range based for?
|
||||
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<Roster>;
|
|||
using ResultRoster = Result<Roster>;
|
||||
using IqRoster = Iq<Roster>;
|
||||
|
||||
inline auto MakeGetRoster(const FullJid& jid) {
|
||||
return GetRoster{.id = "1", .from = ToString(jid), .payload = Roster{}};
|
||||
}
|
||||
|
||||
} // 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 BadNamespacePrefix : ErrorImpl<BadNamespacePrefix> {};
|
||||
struct Conflict : ErrorImpl<Conflict> {};
|
||||
|
@ -81,7 +100,7 @@ struct HostGone : ErrorImpl<HostGone> {};
|
|||
struct HostUnknown : ErrorImpl<HostUnknown> {};
|
||||
struct ImproperAdressing : ErrorImpl<ImproperAdressing> {};
|
||||
struct InternalServerError : ErrorImpl<InternalServerError> {};
|
||||
struct InvalidForm : ErrorImpl<InvalidForm> {};
|
||||
struct InvalidFrom : ErrorImpl<InvalidFrom> {};
|
||||
struct InvalidNamespace : ErrorImpl<InvalidNamespace> {};
|
||||
struct InvalidXml : ErrorImpl<InvalidXml> {};
|
||||
struct NotAuthorized : ErrorImpl<NotAuthorized> {};
|
||||
|
@ -109,7 +128,7 @@ using StreamError = std::variant<error::stream::BadFormat,
|
|||
error::stream::HostUnknown,
|
||||
error::stream::ImproperAdressing,
|
||||
error::stream::InternalServerError,
|
||||
error::stream::InvalidForm,
|
||||
error::stream::InvalidFrom,
|
||||
error::stream::InvalidNamespace,
|
||||
error::stream::InvalidXml,
|
||||
error::stream::NotAuthorized,
|
||||
|
@ -123,6 +142,10 @@ using StreamError = std::variant<error::stream::BadFormat,
|
|||
error::stream::UnsupportedEncoding,
|
||||
error::stream::UnsupportedFeature,
|
||||
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
|
||||
|
|
|
@ -297,7 +297,7 @@ struct RangeToWrapper : T {
|
|||
|
||||
template <typename T>
|
||||
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>;
|
||||
sha512sum marked this conversation as resolved
Outdated
sha512sum
commented
A comment that doesn't explain anything and just repeats the code. A comment that doesn't explain anything and just repeats the code.
|
||||
} || std::convertible_to<T, std::string>;
|
||||
|
||||
template <typename T>
|
||||
|
|
|
@ -1,17 +1,13 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
#include <larra/jid.hpp>
|
||||
#include <larra/roster.hpp>
|
||||
|
||||
#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");
|
||||
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());
|
||||
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) {
|
||||
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
|
||||
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
|
||||
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/error.hpp>
|
||||
#include <larra/stream_error.hpp>
|
||||
#include <variant>
|
||||
|
||||
#include "utempl/utils.hpp"
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
|
|
Loading…
Reference in a new issue
Maybe we can change namings later