Features: iq::Bind and iq::Roster #4
7 changed files with 201 additions and 1 deletions
|
@ -27,6 +27,12 @@ auto Coroutine() -> boost::asio::awaitable<void> {
|
||||||
},
|
},
|
||||||
client);
|
client);
|
||||||
|
|
||||||
|
co_await std::visit(
|
||||||
|
[](auto& client) -> boost::asio::awaitable<void> {
|
||||||
|
co_await client.UpdateListOfContacts();
|
||||||
|
},
|
||||||
|
client);
|
||||||
|
|
||||||
// rfc6120 2.2
|
// rfc6120 2.2
|
||||||
// Upon authenticating with a server and binding a resource (thus becoming a connected resource as defined in [XMPP‑CORE]),
|
// 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
|
// a client SHOULD request the roster before sending initial presence
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
#include <larra/client/xmpp_client_stream_features.hpp>
|
#include <larra/client/xmpp_client_stream_features.hpp>
|
||||||
#include <larra/encryption.hpp>
|
#include <larra/encryption.hpp>
|
||||||
#include <larra/features.hpp>
|
#include <larra/features.hpp>
|
||||||
|
#include <larra/roster.hpp>
|
||||||
#include <larra/stream.hpp>
|
#include <larra/stream.hpp>
|
||||||
#include <larra/user_account.hpp>
|
#include <larra/user_account.hpp>
|
||||||
#include <larra/xml_stream.hpp>
|
#include <larra/xml_stream.hpp>
|
||||||
|
@ -94,10 +95,28 @@ struct Client {
|
||||||
co_return;
|
co_return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto UpdateListOfContacts() -> boost::asio::awaitable<void> {
|
||||||
|
|||||||
|
SPDLOG_INFO("Send IQ: Get::Roster");
|
||||||
|
co_await this->Send(::iq::GetRoster{.id = "1", .from = jid, .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;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool active = true;
|
bool active = true;
|
||||||
XmlStream<Connection> connection{};
|
XmlStream<Connection> connection{};
|
||||||
FullJid jid;
|
FullJid jid;
|
||||||
|
::iq::Roster roster;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct StartTlsNegotiationError : std::runtime_error {
|
struct StartTlsNegotiationError : std::runtime_error {
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include <larra/jid.hpp>
|
||||||
#include <larra/serialization.hpp>
|
#include <larra/serialization.hpp>
|
||||||
#include <larra/stream_error.hpp>
|
#include <larra/stream_error.hpp>
|
||||||
#include <larra/utils.hpp>
|
#include <larra/utils.hpp>
|
||||||
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace larra::xmpp {
|
namespace larra::xmpp {
|
||||||
|
@ -11,6 +13,8 @@ namespace iq {
|
||||||
template <auto& Name, typename PayloadType>
|
template <auto& Name, typename PayloadType>
|
||||||
struct BaseImplWithPayload {
|
struct BaseImplWithPayload {
|
||||||
std::string id;
|
std::string id;
|
||||||
Ivan-lis marked this conversation as resolved
Outdated
sha512sum
commented
Why std::string and not a more limited type like Jid ? Why std::string and not a more limited type like Jid ?
Ivan-lis
commented
Fixed Fixed
|
|||||||
|
std::optional<Jid> from{};
|
||||||
|
std::optional<Jid> to{};
|
||||||
PayloadType payload;
|
PayloadType payload;
|
||||||
static const inline std::string kName = Name;
|
static const inline std::string kName = Name;
|
||||||
static constexpr auto kDefaultName = "iq";
|
static constexpr auto kDefaultName = "iq";
|
||||||
|
@ -19,16 +23,45 @@ struct BaseImplWithPayload {
|
||||||
[[nodiscard]] constexpr auto Id(this Self&& self, std::string id) -> std::decay_t<Self> {
|
[[nodiscard]] constexpr auto Id(this Self&& self, std::string id) -> std::decay_t<Self> {
|
||||||
return utils::FieldSetHelper::With<"id", BaseImplWithPayload>(std::forward<Self>(self), std::move(id));
|
return utils::FieldSetHelper::With<"id", BaseImplWithPayload>(std::forward<Self>(self), std::move(id));
|
||||||
}
|
}
|
||||||
|
template <typename Self>
|
||||||
|
[[nodiscard]] constexpr auto To(this Self&& self, Jid to) -> std::decay_t<Self> {
|
||||||
Ivan-lis marked this conversation as resolved
Outdated
sha512sum
commented
Uses std::string for setters even though another type is stored. Uses std::string for setters even though another type is stored.
|
|||||||
|
return utils::FieldSetHelper::With<"to", BaseImplWithPayload>(std::forward<Self>(self), std::move(to));
|
||||||
|
}
|
||||||
|
template <typename Self>
|
||||||
|
[[nodiscard]] constexpr auto From(this Self&& self, Jid from) -> std::decay_t<Self> {
|
||||||
|
return utils::FieldSetHelper::With<"from", BaseImplWithPayload>(std::forward<Self>(self), std::move(from));
|
||||||
|
}
|
||||||
template <typename NewPayloadType, typename Self>
|
template <typename NewPayloadType, typename Self>
|
||||||
[[nodiscard]] constexpr auto Payload(this Self&& self, NewPayloadType value) {
|
[[nodiscard]] constexpr auto Payload(this Self&& self, NewPayloadType value) {
|
||||||
return utils::FieldSetHelper::With<"payload", BaseImplWithPayload, false>(std::forward<Self>(self), std::move(value));
|
return utils::FieldSetHelper::With<"payload", BaseImplWithPayload, false>(std::forward<Self>(self), std::move(value));
|
||||||
}
|
}
|
||||||
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) {
|
||||||
|
element->set_attribute("to", ToString(*self.to));
|
||||||
|
}
|
||||||
|
if(self.from) {
|
||||||
|
element->set_attribute("from", ToString(*self.from));
|
||||||
|
}
|
||||||
element->set_attribute("type", kName);
|
element->set_attribute("type", kName);
|
||||||
using S = Serialization<PayloadType>;
|
using S = Serialization<PayloadType>;
|
||||||
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) {
|
||||||
|
@ -41,6 +74,8 @@ struct BaseImplWithPayload {
|
||||||
if(!idNode) {
|
if(!idNode) {
|
||||||
throw std::runtime_error("Not found attribute id for parse Iq");
|
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<PayloadType>;
|
using S = Serialization<PayloadType>;
|
||||||
auto payload = element->get_first_child(S::kDefaultName);
|
auto payload = element->get_first_child(S::kDefaultName);
|
||||||
|
@ -51,9 +86,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 {.id = idNode->get_value(), .payload = S::Parse(payload2)};
|
return {.id = idNode->get_value(),
|
||||||
|
.from = (from ? std::optional{Jid::Parse(from->get_value())} : std::nullopt),
|
||||||
Ivan-lis marked this conversation as resolved
Outdated
sha512sum
commented
Use BareJid::Parse when there might not be a BareJid Use BareJid::Parse when there might not be a BareJid
|
|||||||
|
.to = (to ? std::optional{Jid::Parse(to->get_value())} : std::nullopt),
|
||||||
|
.payload = S::Parse(payload2)};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
static constexpr auto kGetName = "get";
|
static constexpr auto kGetName = "get";
|
||||||
|
|
||||||
template <typename Payload>
|
template <typename Payload>
|
||||||
|
|
56
library/include/larra/roster.hpp
Normal file
56
library/include/larra/roster.hpp
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
#pragma once
|
||||||
|
#include <libxml++/libxml++.h>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
|
#include <larra/iq.hpp>
|
||||||
|
#include <larra/jid.hpp>
|
||||||
|
#include <larra/utils.hpp>
|
||||||
|
#include <ranges>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace larra::xmpp::iq {
|
||||||
|
|
||||||
|
struct RosterItem {
|
||||||
|
BareJid jid;
|
||||||
|
friend constexpr auto ToString(const RosterItem& item) {
|
||||||
|
return ToString(item.jid);
|
||||||
|
}
|
||||||
|
constexpr auto operator==(const RosterItem&) const -> bool = default;
|
||||||
|
friend auto operator<<(xmlpp::Element* element, const RosterItem& item) -> void;
|
||||||
|
[[nodiscard]] static auto Parse(xmlpp::Element* element) -> RosterItem;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Roster {
|
||||||
|
static constexpr auto kDefaultName = "query";
|
||||||
|
static constexpr auto kDefaultNamespace = "jabber:iq:roster";
|
||||||
|
|
||||||
|
std::vector<RosterItem> 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.jid) + 3;
|
||||||
Ivan-lis marked this conversation as resolved
Outdated
sha512sum
commented
It would be better to move this to a .cpp file to make the header files lighter. It would be better to move this to a .cpp file to make the header files lighter.
|
|||||||
|
}),
|
||||||
|
prefix.length() + suffix.length(),
|
||||||
|
std::plus<>{});
|
||||||
|
|
||||||
|
std::string s;
|
||||||
|
s.resize(total_length);
|
||||||
sha512sum marked this conversation as resolved
Outdated
sha512sum
commented
Why not range based for? Why not range based for?
|
|||||||
|
s = prefix;
|
||||||
|
for(const auto& el : roster.items) {
|
||||||
|
s += ToString(el);
|
||||||
|
s += "\n\t";
|
||||||
|
}
|
||||||
|
return s += suffix;
|
||||||
|
}
|
||||||
|
friend auto operator<<(xmlpp::Element* element, const Roster& roster) -> void;
|
||||||
|
[[nodiscard]] static auto Parse(xmlpp::Element* element) -> Roster;
|
||||||
|
};
|
||||||
|
|
||||||
|
using GetRoster = Get<Roster>;
|
||||||
|
using ResultRoster = Result<Roster>;
|
||||||
Ivan-lis marked this conversation as resolved
Outdated
sha512sum
commented
You can create a structure that contains jid as an attribute and use automatic generation of serialization and deserialization You can create a structure that contains jid as an attribute and use automatic generation of serialization and deserialization
Ivan-lis
commented
Done Done
|
|||||||
|
|
||||||
|
} // namespace larra::xmpp::iq
|
|
@ -295,4 +295,18 @@ struct RangeToWrapper : T {
|
||||||
: T{std::forward<Args>(args)...} {};
|
: T{std::forward<Args>(args)...} {};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
concept LengthCalculatable = requires(const T& obj) {
|
||||||
|
{ 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>
|
||||||
|
auto AccumulateFieldLength(const T& obj) -> std::size_t {
|
||||||
|
std::size_t totalLength = 0;
|
||||||
sha512sum marked this conversation as resolved
Outdated
sha512sum
commented
camelCase camelCase
|
|||||||
|
boost::pfr::for_each_field(obj, [&](const LengthCalculatable auto& field) {
|
||||||
|
totalLength += field.length(); // Accumulate length of each field
|
||||||
|
});
|
||||||
|
return totalLength;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace larra::xmpp::utils
|
} // namespace larra::xmpp::utils
|
||||||
|
|
30
library/src/roster.cpp
Normal file
30
library/src/roster.cpp
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
#include <larra/roster.hpp>
|
||||||
|
#include <larra/serialization/auto.hpp>
|
||||||
|
|
||||||
|
namespace larra::xmpp::serialization {
|
||||||
|
namespace iq = larra::xmpp::iq;
|
||||||
|
|
||||||
|
template <>
|
||||||
|
constexpr auto kSerializationConfig<iq::RosterItem> = SerializationConfig<iq::RosterItem>{};
|
||||||
|
template <>
|
||||||
|
constexpr auto kSerializationConfig<iq::Roster> = SerializationConfig<iq::Roster>{}.With<"items">({Config<std::vector<iq::RosterItem>>{}});
|
||||||
|
} // namespace larra::xmpp::serialization
|
||||||
|
|
||||||
|
namespace larra::xmpp::iq {
|
||||||
|
namespace S = larra::xmpp::serialization;
|
||||||
|
|
||||||
|
auto operator<<(xmlpp::Element* element, const RosterItem& self) -> void {
|
||||||
|
S::Serialize(element, self);
|
||||||
|
}
|
||||||
|
auto RosterItem::Parse(xmlpp::Element* element) -> RosterItem {
|
||||||
|
return S::Parse<RosterItem>(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto operator<<(xmlpp::Element* element, const Roster& self) -> void {
|
||||||
|
element->set_attribute("xmlns", Roster::kDefaultNamespace);
|
||||||
|
S::Serialize(element, self);
|
||||||
|
}
|
||||||
|
auto Roster::Parse(xmlpp::Element* element) -> Roster {
|
||||||
|
return S::Parse<Roster>(element);
|
||||||
|
}
|
||||||
|
} // namespace larra::xmpp::iq
|
36
tests/roster.cpp
Normal file
36
tests/roster.cpp
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <larra/jid.hpp>
|
||||||
|
#include <larra/roster.hpp>
|
||||||
|
|
||||||
|
namespace larra::xmpp {
|
||||||
|
|
||||||
|
TEST(Roster, SerializeAndParse) {
|
||||||
|
FullJid jid{.username = "test", .server = "server", .resource = "res"}; // NOLINT
|
||||||
|
auto roster = iq::GetRoster{.id = "1", .from = 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
Outdated
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?
|
|||||||
|
node << roster;
|
||||||
|
|
||||||
|
auto parseRes = decltype(roster)::Parse(node);
|
||||||
|
|
||||||
|
ASSERT_EQ(roster.payload.items.size(), parseRes.payload.items.size());
|
||||||
|
for(const auto& [idx, expectEl, parsedEl] : std::views::zip(std::views::iota(0), roster.payload.items, parseRes.payload.items)) {
|
||||||
sha512sum marked this conversation as resolved
Outdated
sha512sum
commented
camelCase camelCase
|
|||||||
|
EXPECT_EQ(expectEl, parsedEl) << "Mismatched on idx: " << idx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
Outdated
sha512sum
commented
Code left in the comment Code left in the comment
|
|||||||
|
FullJid jid{.username = "test", .server = "server", .resource = "res"}; // NOLINT
|
||||||
|
auto roster = iq::GetRoster{.id = "1", .from = jid, .payload = iq::Roster{.items = {{"u1", "s1"}, {"u2", "s2"}, {"u3", "s3"}}}};
|
||||||
|
|
||||||
|
EXPECT_NO_THROW({
|
||||||
|
auto rosterStr = ToString(roster.payload);
|
||||||
|
EXPECT_EQ(kRosterPrintExpectedData.length(), rosterStr.capacity());
|
||||||
|
EXPECT_EQ(kRosterPrintExpectedData, rosterStr);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace larra::xmpp
|
||||||
Ivan-lis marked this conversation as resolved
Outdated
sha512sum
commented
EXPECT_EQ and test content EXPECT_EQ and test content
|
Loading…
Reference in a new issue
Maybe we can change namings later