Features: iq::Bind and iq::Roster #4
14 changed files with 364 additions and 30 deletions
12
.vscode/launch.json
vendored
12
.vscode/launch.json
vendored
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
2
.vscode/tasks.json
vendored
2
.vscode/tasks.json
vendored
|
@ -104,7 +104,7 @@
|
|||
"presentation": {
|
||||
"clear": true
|
||||
},
|
||||
"hide": true,
|
||||
"hide": false,
|
||||
"group": {
|
||||
"kind": "build"
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#include <spdlog/common.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <boost/asio/co_spawn.hpp>
|
||||
|
@ -7,6 +8,8 @@
|
|||
#include <larra/printer_stream.hpp>
|
||||
#include <print>
|
||||
|
||||
namespace iq = larra::xmpp::iq;
|
||||
|
||||
auto Coroutine() -> boost::asio::awaitable<void> {
|
||||
SPDLOG_INFO("Connecting client...");
|
||||
|
||||
|
@ -14,11 +17,32 @@ auto Coroutine() -> boost::asio::awaitable<void> {
|
|||
auto client = co_await larra::xmpp::client::CreateClient<larra::xmpp::PrintStream<boost::asio::ip::tcp::socket>>(
|
||||
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<void> {
|
||||
co_await client.CreateResourceBind();
|
||||
},
|
||||
client);
|
||||
|
||||
co_await std::visit(
|
||||
[](auto& client) -> boost::asio::awaitable<void> {
|
||||
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<void> {
|
||||
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;
|
||||
|
|
24
library/include/larra/bind.hpp
Normal file
24
library/include/larra/bind.hpp
Normal file
|
@ -0,0 +1,24 @@
|
|||
#pragma once
|
||||
#include <libxml++/libxml++.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <larra/iq.hpp>
|
||||
#include <larra/jid.hpp>
|
||||
#include <optional>
|
||||
|
||||
namespace larra::xmpp::iq {
|
||||
|
||||
struct Bind {
|
||||
static constexpr auto kDefaultName = "bind";
|
||||
static constexpr auto kDefaultNamespace = "urn:ietf:params:xml:ns:xmpp-bind";
|
||||
|
||||
std::optional<FullJid> jid;
|
||||
|
||||
friend auto operator<<(xmlpp::Element* element, const Bind& bind) -> void;
|
||||
[[nodiscard]] static auto Parse(xmlpp::Element* element) -> Bind;
|
||||
};
|
||||
|
||||
using SetBind = Set<Bind>;
|
||||
using ResultBind = Result<Bind>;
|
||||
|
||||
} // namespace larra::xmpp::iq
|
|
@ -11,16 +11,24 @@
|
|||
#include <boost/asio/ssl.hpp>
|
||||
#include <boost/asio/use_awaitable.hpp>
|
||||
#include <charconv>
|
||||
#include <larra/bind.hpp>
|
||||
#include <larra/client/challenge_response.hpp>
|
||||
#include <larra/client/options.hpp>
|
||||
#include <larra/client/starttls_response.hpp>
|
||||
#include <larra/client/xmpp_client_stream_features.hpp>
|
||||
#include <larra/encryption.hpp>
|
||||
#include <larra/features.hpp>
|
||||
#include <larra/roster.hpp>
|
||||
#include <larra/stream.hpp>
|
||||
#include <larra/user_account.hpp>
|
||||
#include <larra/xml_stream.hpp>
|
||||
#include <ranges>
|
||||
#include <utility>
|
||||
|
||||
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 <typename Connection>
|
||||
struct Client {
|
||||
constexpr Client(BareJid jid, XmlStream<Connection> connection) : jid(std::move(jid)), connection(std::move(connection)) {};
|
||||
constexpr Client(BareJid jid, XmlStream<Connection> connection) : jid(std::move(jid)), connection(std::move(connection)) {
|
||||
}
|
||||
template <boost::asio::completion_token_for<void()> Token = boost::asio::use_awaitable_t<>>
|
||||
constexpr auto Close(Token token = {}) {
|
||||
this->active = false;
|
||||
|
@ -68,14 +74,49 @@ struct Client {
|
|||
auto Send(const T& object) -> boost::asio::awaitable<void> {
|
||||
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<void> {
|
||||
SPDLOG_INFO("Send IQ: Set::Bind");
|
||||
co_await this->Send(::iq::SetBind{.id = "1", .payload = {}});
|
||||
|
||||
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::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:
|
||||
bool active = true;
|
||||
XmlStream<Connection> connection{};
|
||||
BareJid jid;
|
||||
FullJid jid;
|
||||
::iq::Roster roster;
|
||||
};
|
||||
|
||||
struct StartTlsNegotiationError : std::runtime_error {
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
#pragma once
|
||||
#include <larra/jid.hpp>
|
||||
#include <larra/serialization.hpp>
|
||||
#include <larra/stream_error.hpp>
|
||||
#include <larra/utils.hpp>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
namespace larra::xmpp {
|
||||
|
@ -11,6 +13,8 @@ namespace iq {
|
|||
template <auto& Name, typename PayloadType>
|
||||
struct BaseImplWithPayload {
|
||||
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;
|
||||
static const inline std::string kName = Name;
|
||||
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> {
|
||||
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>
|
||||
[[nodiscard]] constexpr auto Payload(this Self&& self, NewPayloadType 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) {
|
||||
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);
|
||||
using S = Serialization<PayloadType>;
|
||||
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) {
|
||||
|
@ -41,6 +74,8 @@ 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<PayloadType>;
|
||||
auto payload = element->get_first_child(S::kDefaultName);
|
||||
|
@ -51,9 +86,13 @@ 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{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";
|
||||
|
||||
template <typename Payload>
|
||||
|
|
|
@ -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 <typename Self>
|
||||
[[nodiscard]] constexpr auto Username(this Self&& self, std::string username) -> std::decay_t<Self> {
|
||||
return utils::FieldSetHelper::With<"username", BareJid>(std::forward<Self>(self), std::move(username));
|
||||
}
|
||||
|
||||
template <typename Self>
|
||||
[[nodiscard]] constexpr auto Server(this Self&& self, std::string server) -> std::decay_t<Self> {
|
||||
return utils::FieldSetHelper::With<"server", BareJid>(std::forward<Self>(self), std::move(server));
|
||||
}
|
||||
};
|
||||
|
||||
struct BareResourceJid {
|
||||
std::string server;
|
||||
std::string resource;
|
||||
|
@ -69,6 +51,28 @@ struct FullJid {
|
|||
}
|
||||
};
|
||||
|
||||
struct BareJid {
|
||||
std::string username;
|
||||
std::string server;
|
||||
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;
|
||||
|
||||
constexpr auto operator==(const BareJid&) const -> bool = default;
|
||||
template <typename Self>
|
||||
[[nodiscard]] constexpr auto Username(this Self&& self, std::string username) -> std::decay_t<Self> {
|
||||
return utils::FieldSetHelper::With<"username", BareJid>(std::forward<Self>(self), std::move(username));
|
||||
}
|
||||
|
||||
template <typename Self>
|
||||
[[nodiscard]] constexpr auto Server(this Self&& self, std::string server) -> std::decay_t<Self> {
|
||||
return utils::FieldSetHelper::With<"server", BareJid>(std::forward<Self>(self), std::move(server));
|
||||
}
|
||||
};
|
||||
|
||||
using JidVariant = std::variant<BareJid, BareResourceJid, FullJid>;
|
||||
|
||||
struct Jid : JidVariant {
|
||||
|
|
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
|
|
@ -115,7 +115,7 @@ struct Config : V {
|
|||
requires(!HasParse<T>)
|
||||
: V(AttributeConfig{}) {
|
||||
}
|
||||
constexpr auto Base() const -> const V& {
|
||||
[[nodiscard]] constexpr auto Base() const -> const V& {
|
||||
return static_cast<const V&>(*this);
|
||||
}
|
||||
using type = T;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -295,4 +295,18 @@ struct RangeToWrapper : T {
|
|||
: 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
|
||||
|
|
31
library/src/bind.cpp
Normal file
31
library/src/bind.cpp
Normal file
|
@ -0,0 +1,31 @@
|
|||
#include <larra/bind.hpp>
|
||||
|
||||
namespace larra::xmpp::iq {
|
||||
auto operator<<(xmlpp::Element* element, const Bind& bind) -> void {
|
||||
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]] auto Bind::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<const xmlpp::Element*>(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)};
|
||||
}
|
||||
} // namespace larra::xmpp::iq
|
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