From 3180c077a0de45e77960eedd306617571097af49 Mon Sep 17 00:00:00 2001 From: Ivan-lis Date: Mon, 25 Nov 2024 13:19:02 +0000 Subject: [PATCH] Feature: iq::Bind --- .vscode/launch.json | 12 ++++++++ .vscode/tasks.json | 2 +- examples/src/connect.cpp | 18 +++++++++++ library/include/larra/bind.hpp | 24 +++++++++++++++ library/include/larra/client/client.hpp | 34 +++++++++++++++++---- library/include/larra/jid.hpp | 40 ++++++++++++++----------- library/src/bind.cpp | 31 +++++++++++++++++++ 7 files changed, 136 insertions(+), 25 deletions(-) create mode 100644 library/include/larra/bind.hpp create mode 100644 library/src/bind.cpp diff --git a/.vscode/launch.json b/.vscode/launch.json index 2a94c2f..fa2cb78 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..ba9be27 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,26 @@ 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); + + // 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/bind.hpp b/library/include/larra/bind.hpp new file mode 100644 index 0000000..6079f36 --- /dev/null +++ b/library/include/larra/bind.hpp @@ -0,0 +1,24 @@ +#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 auto operator<<(xmlpp::Element* element, const Bind& bind) -> void; + [[nodiscard]] static auto Parse(xmlpp::Element* element) -> Bind; +}; + +using SetBind = Set; +using ResultBind = Result; + +} // namespace larra::xmpp::iq diff --git a/library/include/larra/client/client.hpp b/library/include/larra/client/client.hpp index 5ee658c..c10d239 100644 --- a/library/include/larra/client/client.hpp +++ b/library/include/larra/client/client.hpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -21,6 +22,12 @@ #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 +36,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 +73,31 @@ 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::SetBind{.id = "1", .payload = {}}); + + 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; + } + private: bool active = true; XmlStream connection{}; - BareJid jid; + FullJid jid; }; struct StartTlsNegotiationError : std::runtime_error { diff --git a/library/include/larra/jid.hpp b/library/include/larra/jid.hpp index 78fa043..34dcad4 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,28 @@ struct FullJid { } }; +struct BareJid { + std::string username; + std::string server; + 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; + + 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 { diff --git a/library/src/bind.cpp b/library/src/bind.cpp new file mode 100644 index 0000000..b82f72b --- /dev/null +++ b/library/src/bind.cpp @@ -0,0 +1,31 @@ +#include + +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(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