Compare commits
No commits in common. "main" and "improve_pr_check" have entirely different histories.
main
...
improve_pr
29 changed files with 152 additions and 1879 deletions
|
@ -30,7 +30,7 @@ RUN groupadd docker &&\
|
|||
USER dev
|
||||
|
||||
# Add cached layer with the latest LLVM-${LLVM_VER}
|
||||
ARG LLVM_VER=19.1.5
|
||||
ARG LLVM_VER=19.1.1
|
||||
ENV LLVM_VER=${LLVM_VER}
|
||||
RUN sudo mkdir -p /home/artifacts
|
||||
ADD https://github.com/llvm/llvm-project/releases/download/llvmorg-${LLVM_VER}/LLVM-${LLVM_VER}-Linux-X64.tar.xz /home/artifacts/
|
||||
|
|
|
@ -13,9 +13,9 @@ jobs:
|
|||
pacman -S --noconfirm cmake make gcc ninja meson
|
||||
pacman -S --noconfirm gtk4 gtkmm-4.0 boost spdlog fmt libxml++-5.0 gtest
|
||||
|
||||
- name: Setup environment - Install LLVM-19.1.5
|
||||
- name: Setup environment - Install LLVM-19.1.1
|
||||
run: |
|
||||
export LLVM_VER=19.1.5
|
||||
export LLVM_VER=19.1.1
|
||||
|
||||
echo "::group::Download LLVM-${LLVM_VER}"
|
||||
wget https://github.com/llvm/llvm-project/releases/download/llvmorg-${LLVM_VER}/LLVM-${LLVM_VER}-Linux-X64.tar.xz -O /LLVM-${LLVM_VER}-Linux-X64.tar.xz
|
||||
|
@ -105,10 +105,8 @@ jobs:
|
|||
# key: LLVM-${LLVM_VER}-Linux-X64-small
|
||||
|
||||
- name: Check clang-format
|
||||
id: clang_format_check
|
||||
continue-on-error: true
|
||||
run: |
|
||||
export LLVM_VER=19.1.5
|
||||
export LLVM_VER=19.1.1
|
||||
export PATH="/home/LLVM-${LLVM_VER}/bin:${PATH}"
|
||||
cd ${{ github.workspace }}
|
||||
export REPO_FILES=$(cat repo_files_to_check.txt)
|
||||
|
@ -134,8 +132,6 @@ jobs:
|
|||
-GNinja -DCMAKE_BUILD_TYPE=Release \
|
||||
-DENABLE_EXAMPLES=ON \
|
||||
-DENABLE_TESTS=ON \
|
||||
-DMAX_LOG_LEVEL=0 \
|
||||
-DTEST_MAX_LOG_LEVEL=0 \
|
||||
-DCMAKE_CXX_FLAGS="-ftemplate-backtrace-limit=0"
|
||||
cmake --build ${{ github.workspace }}/build_gcc --parallel `nproc`
|
||||
|
||||
|
@ -144,13 +140,13 @@ jobs:
|
|||
continue-on-error: true
|
||||
run: |
|
||||
cd ${{ github.workspace }}/build_gcc
|
||||
./larra_xmpp_tests --log_level=0
|
||||
./larra_xmpp_tests
|
||||
|
||||
- name: Clang build (only configuring)
|
||||
id: clang_build
|
||||
continue-on-error: true
|
||||
run: |
|
||||
export LLVM_VER=19.1.5
|
||||
export LLVM_VER=19.1.1
|
||||
export PATH="/home/LLVM-${LLVM_VER}/bin:${PATH}"
|
||||
mkdir -p ${{ github.workspace }}/build_clang
|
||||
|
||||
|
@ -162,8 +158,6 @@ jobs:
|
|||
-GNinja -DCMAKE_BUILD_TYPE=Release \
|
||||
-DENABLE_EXAMPLES=ON \
|
||||
-DENABLE_TESTS=ON \
|
||||
-DMAX_LOG_LEVEL=0 \
|
||||
-DTEST_MAX_LOG_LEVEL=0 \
|
||||
-DCMAKE_CXX_FLAGS="-stdlib=libc++ -I/home/LLVM-${LLVM_VER}/include/c++/v1 -fno-modules"
|
||||
|
||||
echo "::group::compile_commands.json content"
|
||||
|
@ -176,7 +170,7 @@ jobs:
|
|||
id: clang_tidy
|
||||
continue-on-error: true
|
||||
run: |
|
||||
export LLVM_VER=19.1.5
|
||||
export LLVM_VER=19.1.1
|
||||
export PATH="/home/LLVM-${LLVM_VER}/bin:${PATH}"
|
||||
cd ${{ github.workspace }}
|
||||
|
||||
|
@ -196,12 +190,12 @@ jobs:
|
|||
echo "No clang-tidy violations detected!"
|
||||
|
||||
- name: Check on failures
|
||||
if: steps.clang_format_check.outcome != 'success' || steps.gcc_build.outcome != 'success' || steps.gcc_unit_tests.outcome != 'success' || steps.clang_build.outcome != 'success' || steps.clang_tidy.outcome != 'success'
|
||||
if: steps.gcc_build.outcome != 'success' || steps.gcc_unit_tests.outcome != 'success' || steps.clang_build.outcome != 'success' || steps.clang_tidy.outcome != 'success'
|
||||
run: exit 1
|
||||
|
||||
#- name: Clang unit tests with -fsanitize=address
|
||||
# run: |
|
||||
# export LLVM_VER=19.1.5
|
||||
# export LLVM_VER=19.1.1
|
||||
# export PATH="/home/LLVM-${LLVM_VER}/bin:${PATH}"
|
||||
# cd ${{ github.workspace }}/build_clang
|
||||
# ASAN_SYMBOLIZER_PATH=llvm-symbolizer ASAN_OPTIONS=detect_stack_use_after_return=1:check_initialization_order=1:detect_leaks=1:atexit=1:abort_on_error=1 ./larra_xmpp_tests --log_level=0
|
||||
# ASAN_SYMBOLIZER_PATH=llvm-symbolizer ASAN_OPTIONS=detect_stack_use_after_return=1:check_initialization_order=1:detect_leaks=1:atexit=1:abort_on_error=1 ./larra_xmpp_tests
|
||||
|
|
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -30,8 +30,3 @@ libxmlplusplus-prefix/
|
|||
spdlog.pc
|
||||
build*
|
||||
temp*
|
||||
/.idea/
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
18
.vscode/launch.json
vendored
18
.vscode/launch.json
vendored
|
@ -9,23 +9,7 @@
|
|||
"request": "launch",
|
||||
"name": "Debug: connect",
|
||||
"program": "${workspaceFolder}/build/examples/output/connect",
|
||||
"args": [
|
||||
"--log_level=0"
|
||||
// --gtest_filter=POSTIVE_PATTERNS[-NEGATIVE_PATTERNS]
|
||||
],
|
||||
"cwd": "${workspaceFolder}",
|
||||
"preLaunchTask": "GCC: Build"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Debug: tests",
|
||||
"program": "${workspaceFolder}/build/larra_xmpp_tests",
|
||||
"args": [
|
||||
"--log_level=0"
|
||||
// --gtest_filter=POSTIVE_PATTERNS[-NEGATIVE_PATTERNS]
|
||||
// "--gtest_filter=Roster*"
|
||||
],
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}",
|
||||
"preLaunchTask": "GCC: Build"
|
||||
}
|
||||
|
|
15
.vscode/tasks.json
vendored
15
.vscode/tasks.json
vendored
|
@ -79,8 +79,8 @@
|
|||
"command": [
|
||||
"cd ${workspaceFolder} &&",
|
||||
"mkdir -p build && cd build &&",
|
||||
"cmake -Wno-dev ",
|
||||
" -DCMAKE_BUILD_TYPE=Debug -DENABLE_EXAMPLES=ON -DENABLE_TESTS=ON -DMAX_LOG_LEVEL=0 -DTEST_MAX_LOG_LEVEL=0 .."
|
||||
"cmake cmake -Wno-dev ",
|
||||
" -DCMAKE_BUILD_TYPE=Debug -DENABLE_EXAMPLES=ON -DENABLE_TESTS=ON .."
|
||||
],
|
||||
"options": {
|
||||
"env": {
|
||||
|
@ -104,7 +104,7 @@
|
|||
"presentation": {
|
||||
"clear": true
|
||||
},
|
||||
"hide": false,
|
||||
"hide": true,
|
||||
"group": {
|
||||
"kind": "build"
|
||||
}
|
||||
|
@ -138,7 +138,6 @@
|
|||
"mkdir -p build_clang && cd build_clang &&",
|
||||
"cmake -Wno-dev ",
|
||||
" -DCMAKE_BUILD_TYPE=Debug -DENABLE_EXAMPLES=ON -DENABLE_TESTS=ON ",
|
||||
" -DMAX_LOG_LEVEL=0 -DTEST_MAX_LOG_LEVEL=0",
|
||||
" -DCMAKE_CXX_FLAGS=\"-stdlib=libstdc++\"",
|
||||
" -DCMAKE_EXE_LINKER_FLAGS=\"-L/usr/lib/x86_64-linux-gnu -lstdc++\" .."
|
||||
],
|
||||
|
@ -161,7 +160,6 @@
|
|||
"mkdir -p build_clang && cd build_clang &&",
|
||||
"cmake -Wno-dev ",
|
||||
" -DCMAKE_BUILD_TYPE=Debug -DENABLE_EXAMPLES=ON -DENABLE_TESTS=ON",
|
||||
" -DMAX_LOG_LEVEL=0 -DTEST_MAX_LOG_LEVEL=0",
|
||||
//
|
||||
// Uncomment for GCC standart library: libstdc++
|
||||
//" -DCMAKE_CXX_FLAGS=\"-stdlib=libstdc++ -fno-omit-frame-pointer -g -fsanitize=address,undefined,leak,function,nullability,vptr\"",
|
||||
|
@ -193,7 +191,7 @@
|
|||
"command": [
|
||||
"cd ${workspaceFolder} &&",
|
||||
"mkdir -p build_clang && cd build_clang &&",
|
||||
"ASAN_SYMBOLIZER_PATH=llvm-symbolizer ASAN_OPTIONS=detect_stack_use_after_return=1:check_initialization_order=1:detect_leaks=1:atexit=1:abort_on_error=1 ./larra_xmpp_tests --log_level=0 ; echo \"exit code: $?\"",
|
||||
"ASAN_SYMBOLIZER_PATH=llvm-symbolizer ASAN_OPTIONS=detect_stack_use_after_return=1:check_initialization_order=1:detect_leaks=1:atexit=1:abort_on_error=1 ./larra_xmpp_tests ; echo \"exit code: $?\"",
|
||||
],
|
||||
"presentation": {
|
||||
"clear": true
|
||||
|
@ -213,7 +211,6 @@
|
|||
"mkdir -p build_clang && cd build_clang &&",
|
||||
"cmake -Wno-dev ",
|
||||
" -DCMAKE_BUILD_TYPE=Debug -DENABLE_EXAMPLES=ON -DENABLE_TESTS=ON ",
|
||||
" -DMAX_LOG_LEVEL=0 -DTEST_MAX_LOG_LEVEL=0",
|
||||
" -DCMAKE_CXX_FLAGS=\"-stdlib=libstdc++ -fsanitize-memory-track-origins=2 -fno-omit-frame-pointer -fno-optimize-sibling-calls -g -fsanitize=memory\"",
|
||||
" -DCMAKE_EXE_LINKER_FLAGS=\"-L/usr/lib/x86_64-linux-gnu -lstdc++ -fsanitize-memory-track-origins=2 -fno-omit-frame-pointer -fno-optimize-sibling-calls -g -fsanitize=memory\" ..",
|
||||
],
|
||||
|
@ -239,7 +236,7 @@
|
|||
"command": [
|
||||
"cd ${workspaceFolder} &&",
|
||||
"mkdir -p build_clang && cd build_clang &&",
|
||||
"MSAN_SYMBOLIZER_PATH=llvm-symbolizer MSAN_OPTIONS=abort_on_error=1 ./larra_xmpp_tests --log_level=0 ; echo \"exit code: $?\"",
|
||||
"MSAN_SYMBOLIZER_PATH=llvm-symbolizer MSAN_OPTIONS=abort_on_error=1 ./larra_xmpp_tests ; echo \"exit code: $?\"",
|
||||
],
|
||||
"presentation": {
|
||||
"clear": true
|
||||
|
@ -273,7 +270,7 @@
|
|||
"command": [
|
||||
"cd ${workspaceFolder} &&",
|
||||
"mkdir -p build_clang && cd build_clang &&",
|
||||
"MSAN_SYMBOLIZER_PATH=llvm-symbolizer MSAN_OPTIONS=abort_on_error=1 ./larra_xmpp_tests --log_level=0 ; echo \"exit code: $?\"",
|
||||
"MSAN_SYMBOLIZER_PATH=llvm-symbolizer MSAN_OPTIONS=abort_on_error=1 ./larra_xmpp_tests ; echo \"exit code: $?\"",
|
||||
],
|
||||
"presentation": {
|
||||
"clear": true
|
||||
|
|
|
@ -18,12 +18,10 @@ set(CMAKE_CXX_EXTENSIONS OFF)
|
|||
set(FMT_MODULE OFF)
|
||||
set(UTEMPL_MODULE OFF)
|
||||
set(CXX_EXTENSIONS NO)
|
||||
set(BOOST_INCLUDE_LIBRARIES "pfr;asio;serialization;program_options")
|
||||
set(BOOST_INCLUDE_LIBRARIES "pfr;asio;serialization")
|
||||
option(CPM_USE_LOCAL_PACKAGES "Use local packages" ON)
|
||||
option(UTEMPL_USE_LOCAL_PACKAGE "Use utempl local package" OFF)
|
||||
option(BUILD_EXECUTABLE ON)
|
||||
set(MAX_LOG_LEVEL 2 CACHE STRING "Available log levels: 0=TRACE, 1=DEBUG, 2=INFO, 3=WARN, 4=ERROR, 5=CRITICAL, 6=OFF")
|
||||
set(TEST_MAX_LOG_LEVEL 2 CACHE STRING "Available log levels: 0=TRACE, 1=DEBUG, 2=INFO, 3=WARN, 4=ERROR, 5=CRITICAL, 6=OFF")
|
||||
set(UTEMPL_URL
|
||||
"https://sha512sum.xyz/git/sha512sum/utempl"
|
||||
CACHE STRING "utempl repository URL")
|
||||
|
@ -177,11 +175,11 @@ target_include_directories(larra_xmpp PUBLIC
|
|||
|
||||
if(TARGET Boost::pfr)
|
||||
target_link_libraries(larra_xmpp PUBLIC
|
||||
Boost::asio Boost::serialization Boost::program_options utempl::utempl
|
||||
Boost::asio Boost::serialization utempl::utempl
|
||||
OpenSSL::SSL nameof::nameof fmt::fmt
|
||||
OpenSSL::Crypto spdlog xmlplusplus ${LIBXML2_LIBRARIES})
|
||||
else()
|
||||
find_package(Boost 1.85.0 COMPONENTS serialization program_options REQUIRED)
|
||||
find_package(Boost 1.85.0 COMPONENTS serialization REQUIRED)
|
||||
target_link_libraries(larra_xmpp PUBLIC
|
||||
utempl::utempl ${Boost_LIBRARIES} OpenSSL::SSL
|
||||
nameof::nameof fmt::fmt
|
||||
|
@ -261,7 +259,6 @@ if(ENABLE_TESTS)
|
|||
target_sources(larra_xmpp_tests PUBLIC ${SOURCES})
|
||||
target_link_libraries(larra_xmpp_tests GTest::gtest_main
|
||||
larra_xmpp)
|
||||
target_compile_definitions(larra_xmpp_tests PRIVATE SPDLOG_ACTIVE_LEVEL=${TEST_MAX_LOG_LEVEL})
|
||||
set_property(TARGET larra_xmpp_tests PROPERTY CXX_STANDARD 23)
|
||||
include(GoogleTest)
|
||||
gtest_discover_tests(larra_xmpp_tests)
|
||||
|
@ -273,7 +270,6 @@ if(ENABLE_EXAMPLES)
|
|||
get_filename_component(EXAMPLE_NAME ${EXAMPLE_SRC} NAME_WE)
|
||||
add_executable(${EXAMPLE_NAME} ${EXAMPLE_SRC})
|
||||
target_link_libraries(${EXAMPLE_NAME} larra_xmpp)
|
||||
target_compile_definitions(${EXAMPLE_NAME} PRIVATE SPDLOG_ACTIVE_LEVEL=${TEST_MAX_LOG_LEVEL})
|
||||
set_property(TARGET ${EXAMPLE_NAME} PROPERTY CXX_STANDARD 23)
|
||||
set_target_properties(${EXAMPLE_NAME} PROPERTIES
|
||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/examples/output")
|
||||
|
|
|
@ -1,32 +1,12 @@
|
|||
#include <spdlog/common.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <boost/asio/co_spawn.hpp>
|
||||
#include <boost/asio/detached.hpp>
|
||||
#include <boost/program_options.hpp>
|
||||
#include <larra/client/client.hpp>
|
||||
#include <larra/presence.hpp>
|
||||
#include <larra/printer_stream.hpp>
|
||||
#include <print>
|
||||
|
||||
// clang-format off
|
||||
constexpr auto ToString(spdlog::level::level_enum e) {
|
||||
switch (e) {
|
||||
case spdlog::level::trace: return "TRACE";
|
||||
case spdlog::level::debug: return "DEBUG";
|
||||
case spdlog::level::info: return "INFO";
|
||||
case spdlog::level::warn: return "WARNING";
|
||||
case spdlog::level::err: return "ERROR";
|
||||
case spdlog::level::critical: return "CRITICAL";
|
||||
case spdlog::level::off: return "OFF";
|
||||
default:
|
||||
return "INVALID";
|
||||
}
|
||||
}
|
||||
// clang-format on
|
||||
|
||||
namespace iq = larra::xmpp::iq;
|
||||
|
||||
auto Coroutine() -> boost::asio::awaitable<void> {
|
||||
SPDLOG_INFO("Connecting client...");
|
||||
|
||||
|
@ -36,20 +16,9 @@ auto Coroutine() -> boost::asio::awaitable<void> {
|
|||
{.useTls = larra::xmpp::client::Options::kNever});
|
||||
co_await std::visit(
|
||||
[](auto& client) -> boost::asio::awaitable<void> {
|
||||
// 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 client.CreateResourceBind();
|
||||
co_await client.UpdateListOfContacts();
|
||||
// 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
|
||||
|
||||
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;
|
||||
|
@ -57,39 +26,8 @@ auto Coroutine() -> boost::asio::awaitable<void> {
|
|||
SPDLOG_INFO("Done connecting client!");
|
||||
}
|
||||
|
||||
namespace po = boost::program_options;
|
||||
|
||||
auto main(int argc, char** argv) -> int {
|
||||
// Define options
|
||||
po::options_description desc("Allowed options");
|
||||
desc.add_options()("help,h", "Print help message")("log_level,l",
|
||||
po::value<int>()->default_value(SPDLOG_LEVEL_INFO),
|
||||
"Set log level: 0=TRACE, 1=DEBUG, 2=INFO, 3=WARN, 4=ERROR, 5=CRITICAL, 6=OFF");
|
||||
|
||||
// Parse command-line arguments
|
||||
po::variables_map vm;
|
||||
try {
|
||||
po::store(po::parse_command_line(argc, argv, desc), vm);
|
||||
po::notify(vm);
|
||||
|
||||
if(vm["log_level"].as<int>() < spdlog::level::level_enum::trace || vm["log_level"].as<int>() > spdlog::level::level_enum::off) {
|
||||
throw std::invalid_argument{
|
||||
std::format("Invalid argument value for '--log_level' option. Check option description for more details")};
|
||||
}
|
||||
if(vm["log_level"].as<int>() < SPDLOG_ACTIVE_LEVEL) {
|
||||
SPDLOG_WARN("Specified log_level '{}' is lower than max available one '{}'. Log level will be changed according to the maximum one",
|
||||
vm["log_level"].as<int>(),
|
||||
SPDLOG_ACTIVE_LEVEL);
|
||||
}
|
||||
} catch(const std::exception& e) {
|
||||
SPDLOG_CRITICAL("Cmd parse error: {}", e.what());
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Cmd options handling
|
||||
spdlog::set_level(static_cast<spdlog::level::level_enum>(vm["log_level"].as<int>()));
|
||||
std::println("\nEnvironment setup:\n\tCurrently set log level: {}\n", ToString(spdlog::get_level()));
|
||||
|
||||
auto main() -> int {
|
||||
spdlog::set_level(spdlog::level::trace);
|
||||
boost::asio::io_context io_context;
|
||||
boost::asio::co_spawn(io_context, Coroutine(), boost::asio::detached);
|
||||
io_context.run();
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
#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,24 +11,16 @@
|
|||
#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;
|
||||
|
@ -37,10 +29,12 @@ 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;
|
||||
|
@ -74,48 +68,14 @@ struct Client {
|
|||
auto Send(const T& object) -> boost::asio::awaitable<void> {
|
||||
co_await this->connection.Send(object);
|
||||
}
|
||||
[[nodiscard]] constexpr auto Jid() const -> const FullJid& {
|
||||
[[nodiscard]] constexpr auto Jid() const -> const BareJid& {
|
||||
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(
|
||||
[&](::iq::ResultBind r) {
|
||||
jid.resource = std::move(r.payload.jid->resource);
|
||||
SPDLOG_INFO("Allocated resource: {}", jid.resource);
|
||||
},
|
||||
IqErrThrowVisitor{"Error response on IQ: Set::Bind"}),
|
||||
std::move(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(
|
||||
[&](::iq::ResultRoster r) {
|
||||
roster = std::move(r.payload);
|
||||
SPDLOG_INFO("New roster: {}", ToString(roster));
|
||||
},
|
||||
IqErrThrowVisitor{"Error response on IQ: Get::Roster"}),
|
||||
std::move(get_roster_response));
|
||||
co_return;
|
||||
}
|
||||
[[nodiscard]] auto Roster() const -> const std::vector<iq::RosterItem>& {
|
||||
return this->roster.items;
|
||||
}
|
||||
|
||||
private:
|
||||
bool active = true;
|
||||
XmlStream<Connection> connection{};
|
||||
FullJid jid;
|
||||
::iq::Roster roster;
|
||||
BareJid jid;
|
||||
};
|
||||
|
||||
struct StartTlsNegotiationError : std::runtime_error {
|
||||
|
@ -197,174 +157,6 @@ struct StartTlsRequest {
|
|||
}
|
||||
};
|
||||
|
||||
template <typename Socket>
|
||||
auto ConnectViaProxy(Socket& socket, const NoProxy&, std::string_view host, std::uint16_t port) -> boost::asio::awaitable<void> {
|
||||
auto executor = co_await boost::asio::this_coro::executor;
|
||||
boost::asio::ip::tcp::resolver resolver(executor);
|
||||
auto endpoints = co_await resolver.async_resolve(host, std::to_string(port), boost::asio::use_awaitable);
|
||||
co_await boost::asio::async_connect(socket, endpoints, boost::asio::use_awaitable);
|
||||
co_return;
|
||||
}
|
||||
|
||||
template <typename Socket>
|
||||
auto ConnectViaProxy(Socket& socket, const HttpProxy&, std::string_view host, std::uint16_t port) -> boost::asio::awaitable<void> {
|
||||
constexpr std::string_view kHttpVersion = "HTTP/1.1";
|
||||
constexpr unsigned int kSuccessStatusCode = 200;
|
||||
constexpr std::string_view kEndOfHeaders = "\r\n\r\n";
|
||||
constexpr int kEndOfHttpSubstring = 5;
|
||||
|
||||
std::string httpConnectRequest = std::format("CONNECT {}:{} {}\r\nHost: {}:{}\r\n\r\n", host, port, kHttpVersion, host, port);
|
||||
|
||||
co_await boost::asio::async_write(
|
||||
socket, boost::asio::buffer(httpConnectRequest), boost::asio::transfer_all(), boost::asio::use_awaitable);
|
||||
|
||||
boost::asio::streambuf proxyServerResponse;
|
||||
co_await boost::asio::async_read_until(socket, proxyServerResponse, kEndOfHeaders, boost::asio::use_awaitable);
|
||||
|
||||
std::istream responseStream(&proxyServerResponse);
|
||||
std::string httpVersion;
|
||||
unsigned int statusCode = 0;
|
||||
std::string statusMessage;
|
||||
|
||||
responseStream >> httpVersion >> statusCode;
|
||||
std::getline(responseStream, statusMessage);
|
||||
|
||||
if(!responseStream || httpVersion.substr(0, kEndOfHttpSubstring) != "HTTP/") {
|
||||
throw std::runtime_error("Invalid HTTP response from proxy");
|
||||
}
|
||||
|
||||
if(statusCode != kSuccessStatusCode) {
|
||||
std::ostringstream errorStream;
|
||||
errorStream << httpVersion << " " << statusCode << " " << statusMessage;
|
||||
throw std::runtime_error("HTTP proxy CONNECT failed: " + errorStream.str());
|
||||
}
|
||||
|
||||
co_return;
|
||||
}
|
||||
|
||||
template <typename Socket>
|
||||
auto ConnectViaProxy(Socket& socket, const Socks5Proxy&, std::string_view address, std::uint16_t port) -> boost::asio::awaitable<void> {
|
||||
constexpr std::array kSocks5RequestStart = {std::byte{0x05}, std::byte{0x01}, std::byte{0x00}, std::byte{0x03}};
|
||||
|
||||
constexpr std::size_t kSocks5RequestMaxSize = 257;
|
||||
|
||||
constexpr std::size_t kSocks5ReplyTypeSize = 10;
|
||||
|
||||
constexpr std::array kHandshakeRequest{std::byte{0x05}, std::byte{0x01}, std::byte{0x00}};
|
||||
|
||||
std::array<std::byte, 2> handshakeResponse; // NOLINT
|
||||
|
||||
co_await boost::asio::async_write(
|
||||
socket, boost::asio::buffer(kHandshakeRequest), boost::asio::transfer_all(), boost::asio::use_awaitable);
|
||||
co_await boost::asio::async_read(socket, boost::asio::buffer(handshakeResponse), boost::asio::transfer_all(), boost::asio::use_awaitable);
|
||||
if(handshakeResponse[0] != std::byte{0x05} || handshakeResponse[1] != std::byte{0x00}) { // NOLINT
|
||||
throw std::exception{};
|
||||
};
|
||||
const auto [connectRequest, size] = [&] {
|
||||
const auto size = static_cast<std::byte>(address.size());
|
||||
const auto htonsPort = std::bit_cast<std::array<std::byte, 2>>(htons(port));
|
||||
auto range = std::array{std::span{kSocks5RequestStart.begin(), kSocks5RequestStart.size()},
|
||||
std::span{&size, 1},
|
||||
std::span{utils::StartLifetimeAsArray<const std::byte>(address.data(), address.size()), address.size()},
|
||||
std::span{htonsPort.data(), 2}} |
|
||||
std::views::join;
|
||||
std::array<std::byte, kSocks5RequestMaxSize> response; // NOLINT
|
||||
auto sizee = std::ranges::copy(range, response.begin()).out - response.begin();
|
||||
return std::pair{response, sizee};
|
||||
}();
|
||||
co_await boost::asio::async_write(
|
||||
socket, boost::asio::buffer(connectRequest.begin(), size), boost::asio::transfer_all(), boost::asio::use_awaitable);
|
||||
std::array<std::byte, kSocks5ReplyTypeSize> connectReplyType; // NOLINT
|
||||
co_await boost::asio::async_read(socket, boost::asio::buffer(connectReplyType), boost::asio::transfer_all(), boost::asio::use_awaitable);
|
||||
if(connectReplyType[1] != std::byte{0x00}) {
|
||||
throw std::exception{};
|
||||
};
|
||||
co_return;
|
||||
}
|
||||
|
||||
template <typename ProxyType>
|
||||
auto GetProxySettings(const char* proxyEnv, std::uint16_t port) -> Proxy {
|
||||
std::string_view rawProxyStr = proxyEnv;
|
||||
|
||||
const std::size_t protocolNamePos = rawProxyStr.find("://");
|
||||
std::string_view proxyStr = protocolNamePos == std::string_view::npos ? rawProxyStr : rawProxyStr.substr(protocolNamePos + 3);
|
||||
|
||||
const std::size_t portPos = proxyStr.find(':');
|
||||
|
||||
if(portPos == std::string_view::npos) {
|
||||
return ProxyType{std::string(proxyStr), port};
|
||||
}
|
||||
auto host = std::string(proxyStr.substr(0, portPos));
|
||||
auto portStr = proxyStr.substr(portPos + 1);
|
||||
auto portOpt = ToInt<uint16_t>(portStr);
|
||||
|
||||
if(!portOpt) {
|
||||
throw std::runtime_error("Invalid port number in proxy settings");
|
||||
}
|
||||
return ProxyType{host, portOpt.value()};
|
||||
}
|
||||
|
||||
inline auto GetSystemProxySettings() -> Proxy {
|
||||
if(const char* proxyEnv = std::getenv("http_proxy")) {
|
||||
constexpr std::uint16_t kHttpPort = 8080;
|
||||
return GetProxySettings<HttpProxy>(proxyEnv, kHttpPort);
|
||||
}
|
||||
if(const char* proxyEnv = std::getenv("socks_proxy")) {
|
||||
constexpr std::uint16_t kSocksPort = 1080;
|
||||
return GetProxySettings<Socks5Proxy>(proxyEnv, kSocksPort);
|
||||
}
|
||||
|
||||
return NoProxy{};
|
||||
}
|
||||
|
||||
template <typename Socket>
|
||||
auto ConnectViaProxy(Socket& socket, const SystemConfiguredProxy&, std::string_view host, std::uint16_t port)
|
||||
-> boost::asio::awaitable<void> {
|
||||
auto proxyOpt = GetSystemProxySettings();
|
||||
|
||||
co_await std::visit(
|
||||
[&](auto&& proxyVariant) -> boost::asio::awaitable<void> { // NOLINT
|
||||
co_await ConnectViaProxy(socket, proxyVariant, host, port); // GCC error if co_return
|
||||
},
|
||||
proxyOpt);
|
||||
}
|
||||
|
||||
template <typename Socket, typename ProxyType>
|
||||
auto ConnectToServer(Socket& socket, const ProxyType& proxy, std::string_view host, std::uint16_t port) -> boost::asio::awaitable<void> {
|
||||
auto executor = co_await boost::asio::this_coro::executor;
|
||||
boost::asio::ip::tcp::resolver resolver(executor);
|
||||
|
||||
if constexpr(!std::same_as<ProxyType, NoProxy>) {
|
||||
auto endpoints = co_await resolver.async_resolve(proxy.hostname, std::to_string(proxy.port), boost::asio::use_awaitable);
|
||||
co_await boost::asio::async_connect(socket, endpoints, boost::asio::use_awaitable);
|
||||
co_await ConnectViaProxy(socket, proxy, host, port);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Socket>
|
||||
auto ConnectToServer(Socket& socket, const SystemConfiguredProxy& proxy, std::string_view host, std::uint16_t port)
|
||||
-> boost::asio::awaitable<void> {
|
||||
auto executor = co_await boost::asio::this_coro::executor;
|
||||
boost::asio::ip::tcp::resolver resolver(executor);
|
||||
auto endpoints = co_await resolver.async_resolve(host, std::to_string(port), boost::asio::use_awaitable);
|
||||
co_await boost::asio::async_connect(socket, endpoints, boost::asio::use_awaitable);
|
||||
|
||||
co_await ConnectViaProxy(socket, proxy, host, port);
|
||||
}
|
||||
|
||||
template <typename Socket>
|
||||
auto ConnectWithProxy(Socket& socket, const Proxy& proxy, std::string_view host, std::uint16_t port) -> boost::asio::awaitable<void> {
|
||||
co_await std::visit(
|
||||
[&socket, host, port](const auto& proxyVariant) {
|
||||
return ConnectToServer(socket, proxyVariant, host, port);
|
||||
},
|
||||
proxy);
|
||||
}
|
||||
|
||||
inline auto GetAuthData(const PlainUserAccount& account) -> std::string {
|
||||
return EncodeBase64('\0' + account.jid.username + '\0' + account.password);
|
||||
}
|
||||
|
||||
struct ClientCreateVisitor {
|
||||
UserAccount account;
|
||||
const Options& options;
|
||||
|
@ -459,17 +251,8 @@ struct ClientCreateVisitor {
|
|||
boost::asio::use_awaitable);
|
||||
}
|
||||
|
||||
template <typename Socket>
|
||||
auto Connect(Socket& socket) -> boost::asio::awaitable<void> {
|
||||
if(!std::holds_alternative<NoProxy>(this->options.proxy)) {
|
||||
auto host = this->options.hostname.value_or(account.Jid().server);
|
||||
auto port = this->options.port.value_or(kDefaultXmppPort);
|
||||
|
||||
co_await ConnectWithProxy(socket, this->options.proxy, host, port);
|
||||
} else {
|
||||
auto resolveResults = co_await this->Resolve();
|
||||
co_await boost::asio::async_connect(socket, resolveResults, boost::asio::use_awaitable);
|
||||
}
|
||||
auto Connect(auto& socket, boost::asio::ip::tcp::resolver::results_type resolveResults) -> boost::asio::awaitable<void> {
|
||||
co_await boost::asio::async_connect(socket, resolveResults, boost::asio::use_awaitable);
|
||||
}
|
||||
|
||||
template <typename Socket>
|
||||
|
@ -500,7 +283,7 @@ struct ClientCreateVisitor {
|
|||
template <typename Socket>
|
||||
inline auto operator()(XmlStream<Socket> stream)
|
||||
-> boost::asio::awaitable<std::variant<Client<Socket>, Client<boost::asio::ssl::stream<Socket>>>> {
|
||||
co_await this->Connect(stream.next_layer());
|
||||
co_await this->Connect(stream.next_layer(), co_await this->Resolve());
|
||||
|
||||
co_await stream.Send(UserStream{.from = account.Jid(), .to = account.Jid().server, .version = "1.0", .xmlLang = "en"});
|
||||
SPDLOG_DEBUG("UserStream sended");
|
||||
|
@ -523,7 +306,7 @@ struct ClientCreateVisitor {
|
|||
inline auto operator()(XmlStream<boost::asio::ssl::stream<Socket>> stream)
|
||||
-> boost::asio::awaitable<std::variant<Client<Socket>, Client<boost::asio::ssl::stream<Socket>>>> {
|
||||
auto& socket = stream.next_layer();
|
||||
co_await this->Connect(socket.next_layer());
|
||||
co_await this->Connect(socket.next_layer(), co_await this->Resolve());
|
||||
co_await stream.Send(
|
||||
UserStream{.from = account.Jid().Username("anonymous"), .to = account.Jid().server, .version = "1.0", .xmlLang = "en"},
|
||||
socket.next_layer());
|
||||
|
|
|
@ -1,12 +1,7 @@
|
|||
#pragma once
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <larra/jid.hpp>
|
||||
#include <larra/serialization.hpp>
|
||||
#include <larra/stanza_error.hpp>
|
||||
#include <larra/stream_error.hpp>
|
||||
#include <larra/utils.hpp>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
namespace larra::xmpp {
|
||||
|
@ -16,8 +11,6 @@ namespace iq {
|
|||
template <auto& Name, typename PayloadType>
|
||||
struct BaseImplWithPayload {
|
||||
std::string id;
|
||||
std::optional<Jid> from{};
|
||||
std::optional<Jid> to{};
|
||||
PayloadType payload;
|
||||
static const inline std::string kName = Name;
|
||||
static constexpr auto kDefaultName = "iq";
|
||||
|
@ -26,45 +19,16 @@ 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> {
|
||||
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) {
|
||||
|
@ -77,8 +41,6 @@ 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);
|
||||
|
@ -89,13 +51,9 @@ struct BaseImplWithPayload {
|
|||
if(!payload2) {
|
||||
throw std::runtime_error("Invalid payload for parse Iq");
|
||||
}
|
||||
return {.id = idNode->get_value(),
|
||||
.from = (from ? std::optional{Jid::Parse(from->get_value())} : std::nullopt),
|
||||
.to = (to ? std::optional{Jid::Parse(to->get_value())} : std::nullopt),
|
||||
.payload = S::Parse(payload2)};
|
||||
return {.id = idNode->get_value(), .payload = S::Parse(payload2)};
|
||||
}
|
||||
};
|
||||
|
||||
static constexpr auto kGetName = "get";
|
||||
|
||||
template <typename Payload>
|
||||
|
@ -118,41 +76,7 @@ using Error = BaseImplWithPayload<kErrorName, Payload>;
|
|||
|
||||
} // namespace iq
|
||||
|
||||
using IqError = iq::Error<stanza::error::StanzaError>;
|
||||
|
||||
template <typename Payload>
|
||||
using Iq = std::variant<iq::Get<Payload>, iq::Set<Payload>, iq::Result<Payload>, IqError>;
|
||||
|
||||
struct IqErrThrowVisitor {
|
||||
constexpr IqErrThrowVisitor(std::string_view baseErrorMsg) : baseErrorMsg(baseErrorMsg) {
|
||||
}
|
||||
|
||||
template <typename Payload>
|
||||
void operator()(const iq::Get<Payload>&) {
|
||||
static constexpr std::string_view getErrorMsg = ": 'Get' is an invalid type for IQ result. Expected 'Result' or 'Error'";
|
||||
|
||||
auto finalErrorMsg = std::string(baseErrorMsg).append(getErrorMsg);
|
||||
SPDLOG_ERROR(finalErrorMsg);
|
||||
throw std::runtime_error{finalErrorMsg};
|
||||
}
|
||||
template <typename Payload>
|
||||
void operator()(const iq::Set<Payload>&) {
|
||||
static constexpr std::string_view getErrorMsg = ": 'Set' is an invalid type for IQ result. Expected 'Result' or 'Error'";
|
||||
|
||||
auto finalErrorMsg = std::string(baseErrorMsg).append(getErrorMsg);
|
||||
SPDLOG_ERROR(finalErrorMsg);
|
||||
throw std::runtime_error{finalErrorMsg};
|
||||
}
|
||||
void operator()(IqError err) {
|
||||
SPDLOG_ERROR(baseErrorMsg);
|
||||
std::visit(
|
||||
[](auto exception) {
|
||||
throw exception;
|
||||
},
|
||||
std::move(err.payload));
|
||||
}
|
||||
|
||||
std::string_view baseErrorMsg;
|
||||
};
|
||||
using Iq = std::variant<iq::Get<Payload>, iq::Set<Payload>, iq::Result<Payload>, iq::Error<Payload>>;
|
||||
|
||||
} // namespace larra::xmpp
|
||||
|
|
|
@ -7,6 +7,24 @@
|
|||
|
||||
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;
|
||||
|
@ -51,28 +69,6 @@ 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)};
|
||||
}
|
||||
|
||||
[[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 {
|
||||
|
|
|
@ -1,163 +0,0 @@
|
|||
#pragma once
|
||||
#include <larra/serialization/auto.hpp>
|
||||
#include <larra/stanza_error.hpp>
|
||||
#include <larra/xml_language.hpp>
|
||||
#include <string>
|
||||
|
||||
namespace larra::xmpp {
|
||||
|
||||
namespace message {
|
||||
struct Body {
|
||||
std::string content;
|
||||
std::optional<XmlLanguage> language;
|
||||
static constexpr auto kDefaultName = "body";
|
||||
constexpr auto operator==(const Body& other) const -> bool = default;
|
||||
friend constexpr auto operator<<(xmlpp::Element* node, const Body& message) -> void {
|
||||
node->add_child_text(message.content);
|
||||
}
|
||||
static constexpr auto Parse(xmlpp::Element* node) -> Body {
|
||||
auto ptr = node->get_first_child_text();
|
||||
if(!ptr) {
|
||||
throw std::runtime_error("Message::Body: [ Text node not found ]");
|
||||
}
|
||||
auto lang = node->get_attribute("lang", "xml");
|
||||
return {
|
||||
.content = ptr->get_content(), //
|
||||
.language = lang ? std::optional{XmlLanguage::Parse(lang->get_value())} : std::nullopt //
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
namespace type {
|
||||
|
||||
template <typename T>
|
||||
struct TypeImpl {
|
||||
static constexpr auto TryParse(std::string_view value) -> std::optional<T> {
|
||||
return value == T::kName ? std::optional{T{}} : std::nullopt;
|
||||
}
|
||||
friend constexpr auto ToString(const T&) -> std::string {
|
||||
return T::kName;
|
||||
}
|
||||
friend constexpr auto operator==(const T&, const T&) -> bool {
|
||||
return true;
|
||||
};
|
||||
};
|
||||
|
||||
struct Chat : TypeImpl<Chat> {
|
||||
static constexpr auto kName = "chat";
|
||||
} constexpr kChat{};
|
||||
|
||||
struct Error : TypeImpl<Error> {
|
||||
static constexpr auto kName = "error";
|
||||
} constexpr kError{};
|
||||
|
||||
struct GroupChat : TypeImpl<GroupChat> {
|
||||
static constexpr auto kName = "groupchat";
|
||||
} constexpr kGroupChat{};
|
||||
|
||||
struct Headline : TypeImpl<Headline> {
|
||||
static constexpr auto kName = "headline";
|
||||
} constexpr kHeadline{};
|
||||
|
||||
struct Normal : TypeImpl<Normal> {
|
||||
static constexpr auto kName = "normal";
|
||||
static constexpr auto Parse(std::string_view value) -> Normal {
|
||||
return value == kName ? Normal{}
|
||||
: throw std::runtime_error(
|
||||
std::format(R"(message::type::Normal Parsing error: [ expected "normal" but "{}" found ])", value));
|
||||
}
|
||||
} constexpr kNormal;
|
||||
|
||||
} // namespace type
|
||||
|
||||
using TypeVariant = std::variant<type::Chat, type::GroupChat, type::Headline, type::Error, type::Normal>;
|
||||
|
||||
struct Type : TypeVariant {
|
||||
using TypeVariant::variant;
|
||||
constexpr Type(TypeVariant variant) : TypeVariant(std::move(variant)) {
|
||||
}
|
||||
|
||||
static constexpr auto GetDefault() -> Type {
|
||||
return type::kNormal;
|
||||
}
|
||||
|
||||
friend constexpr auto ToString(const Type& value) -> std::string {
|
||||
return std::visit(
|
||||
[](const auto& value) {
|
||||
return ToString(value);
|
||||
},
|
||||
value);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename From, typename To>
|
||||
struct Message {
|
||||
static constexpr auto kDefaultName = "message";
|
||||
static auto Parse(xmlpp::Element* element) -> Message {
|
||||
return serialization::Parse<Message>(element);
|
||||
}
|
||||
friend auto operator<<(xmlpp::Element* element, const Message& message) -> void {
|
||||
serialization::Serialize(element, message);
|
||||
}
|
||||
constexpr auto operator==(const Message& other) const -> bool = default;
|
||||
From from;
|
||||
To to;
|
||||
message::Type type;
|
||||
std::optional<std::string> id;
|
||||
XmlLanguage language;
|
||||
std::vector<message::Body> body;
|
||||
};
|
||||
|
||||
template <typename From, typename To>
|
||||
struct Error {
|
||||
static constexpr auto kDefaultName = "message";
|
||||
static auto Parse(xmlpp::Element* element) -> Error {
|
||||
auto attr = element->get_attribute("type");
|
||||
if(!attr || attr->get_value() != "error") {
|
||||
throw std::runtime_error{"attribute type: message::Type invalid"};
|
||||
}
|
||||
return serialization::Parse<Error>(element);
|
||||
}
|
||||
static auto TryParse(xmlpp::Element* element) -> std::optional<Error> {
|
||||
auto attr = element->get_attribute("type");
|
||||
if(!attr || attr->get_value() != "error") {
|
||||
return std::nullopt;
|
||||
}
|
||||
try {
|
||||
return serialization::Parse<Error>(element);
|
||||
} catch(...) {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
friend auto operator<<(xmlpp::Element* element, const Error& message) -> void {
|
||||
element->set_attribute("type", "error");
|
||||
serialization::Serialize(element, message);
|
||||
}
|
||||
constexpr auto operator==(const Error& other) const -> bool = default;
|
||||
From from;
|
||||
To to;
|
||||
stanza::error::StanzaError error;
|
||||
};
|
||||
|
||||
} // namespace message
|
||||
|
||||
template <typename From, typename To>
|
||||
using Message = std::variant<message::Error<From, To>, message::Message<From, To>>;
|
||||
|
||||
template <typename From, typename To>
|
||||
struct serialization::SerializationConfigT<message::Message<From, To>> {
|
||||
static constexpr auto kValue = serialization::SerializationConfig<message::Message<From, To>>{} //
|
||||
.template With<"type">(serialization::AttributeConfig{})
|
||||
.template With<"body">(serialization::Config<std::vector<message::Body>>{});
|
||||
};
|
||||
|
||||
template <typename From, typename To>
|
||||
struct serialization::SerializationConfigT<message::Error<From, To>> {
|
||||
static constexpr auto kValue = serialization::SerializationConfig<message::Error<From, To>>{} //
|
||||
.template With<"error">(serialization::Config<stanza::error::StanzaError>{});
|
||||
};
|
||||
|
||||
template <typename Info>
|
||||
struct serialization::AttributeSerializer<message::Type, Info> : serialization::AttributeSerializer<message::TypeVariant, Info> {};
|
||||
|
||||
} // namespace larra::xmpp
|
|
@ -35,8 +35,6 @@ template <typename Socket>
|
|||
struct PrintStream : Socket {
|
||||
using Socket::Socket;
|
||||
PrintStream(PrintStream&&) = default;
|
||||
constexpr PrintStream(Socket&& sock) : Socket(std::move(sock)) {
|
||||
}
|
||||
using Executor = Socket::executor_type;
|
||||
template <typename ConstBufferSequence,
|
||||
BOOST_ASIO_COMPLETION_TOKEN_FOR(void(boost::system::error_code, std::size_t))
|
||||
|
|
|
@ -1,56 +0,0 @@
|
|||
#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;
|
||||
}),
|
||||
prefix.length() + suffix.length(),
|
||||
std::plus<>{});
|
||||
|
||||
std::string s;
|
||||
s.resize(total_length);
|
||||
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>;
|
||||
|
||||
} // namespace larra::xmpp::iq
|
|
@ -4,7 +4,6 @@
|
|||
|
||||
#include <larra/serialization/error.hpp>
|
||||
#include <nameof.hpp>
|
||||
#include <ranges>
|
||||
#include <string>
|
||||
#include <utempl/utils.hpp>
|
||||
|
||||
|
@ -170,8 +169,8 @@ struct Serialization<std::variant<Ts...>> : SerializationBase<> {
|
|||
}
|
||||
static constexpr auto Serialize(xmlpp::Element* element, const std::variant<Ts...>& object) -> void {
|
||||
std::visit(
|
||||
[&]<typename T>(const T& object) {
|
||||
Serialization<T>::Serialize(element, object);
|
||||
[&](const auto& object) {
|
||||
element << object;
|
||||
},
|
||||
object);
|
||||
}
|
||||
|
@ -192,42 +191,4 @@ struct Serialization<std::monostate> : SerializationBase<> {
|
|||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct Serialization<std::vector<T>> : SerializationBase<> {
|
||||
static constexpr auto Parse(xmlpp::Element* element) -> std::vector<T> {
|
||||
return element->get_children(Serialization<T>::kDefaultName) | std::views::transform([](xmlpp::Node* node) {
|
||||
auto itemElement = dynamic_cast<xmlpp::Element*>(node);
|
||||
if(!itemElement) {
|
||||
throw serialization::ParsingError{"Can't convert xmlpp::Node to xmlpp::Element ]"};
|
||||
}
|
||||
return Serialization<T>::Parse(itemElement);
|
||||
}) |
|
||||
std::ranges::to<std::vector<T>>();
|
||||
}
|
||||
static constexpr auto TryParse(xmlpp::Element* element) -> std::optional<std::vector<T>> {
|
||||
auto chd = element->get_children(Serialization<T>::kDefaultName);
|
||||
auto range = chd | std::views::transform([](xmlpp::Node* node) -> std::optional<T> {
|
||||
auto itemElement = dynamic_cast<xmlpp::Element*>(node);
|
||||
if(!itemElement) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return Serialization<T>::TryParse(itemElement);
|
||||
});
|
||||
std::vector<T> response;
|
||||
response.reserve(chd.size());
|
||||
for(auto& value : range) {
|
||||
if(!value) {
|
||||
return std::nullopt;
|
||||
}
|
||||
response.push_back(std::move(*value));
|
||||
}
|
||||
return response;
|
||||
}
|
||||
static constexpr auto Serialize(xmlpp::Element* node, const std::vector<T>& value) -> void {
|
||||
for(const T& element : value) {
|
||||
Serialization<T>::Serialize(node->add_child_element(Serialization<T>::kDefaultName), element);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace larra::xmpp
|
||||
|
|
|
@ -15,20 +15,10 @@ template <typename T>
|
|||
struct Tag {};
|
||||
|
||||
template <typename T>
|
||||
struct SerializationConfigT {
|
||||
static constexpr auto kValue = std::monostate{};
|
||||
};
|
||||
inline constexpr auto kSerializationConfig = std::monostate{};
|
||||
|
||||
template <typename T>
|
||||
inline constexpr auto kSerializationConfig = SerializationConfigT<T>::kValue;
|
||||
|
||||
template <typename T>
|
||||
struct DeserializationConfigT {
|
||||
static constexpr auto kValue = kSerializationConfig<T>;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
inline constexpr auto kDeserializationConfig = DeserializationConfigT<T>::kValue;
|
||||
inline constexpr auto kDeserializationConfig = kSerializationConfig<T>;
|
||||
|
||||
template <typename T>
|
||||
constexpr auto Parse(xmlpp::Element* element, Tag<T> = {}) -> T
|
||||
|
@ -37,8 +27,6 @@ constexpr auto Parse(xmlpp::Element* element, Tag<T> = {}) -> T
|
|||
template <typename T>
|
||||
constexpr auto Parse(xmlpp::Element* element, Tag<T> = {}) -> T;
|
||||
|
||||
// TODO(sha512sum): Add TryParse
|
||||
|
||||
template <typename T>
|
||||
constexpr auto Serialize(xmlpp::Element* node, const T& element) -> void
|
||||
requires(!std::same_as<std::decay_t<decltype(kSerializationConfig<T>)>, std::monostate>);
|
||||
|
@ -61,24 +49,10 @@ struct MetaInfo {
|
|||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct DefaultValue {};
|
||||
|
||||
template <typename T>
|
||||
requires requires {
|
||||
{ T::GetDefault() } -> std::convertible_to<T>;
|
||||
}
|
||||
struct DefaultValue<T> {
|
||||
static constexpr auto Default() {
|
||||
return T::GetDefault();
|
||||
}
|
||||
};
|
||||
|
||||
template <typename MainT, std::size_t Element>
|
||||
struct FieldInfo {
|
||||
using Main = MainT;
|
||||
using Info = MetaInfo<Main>;
|
||||
using Type = Info::template TupleElement<Element>;
|
||||
static inline const std::string kName = [] {
|
||||
if constexpr(requires { Info::template TupleElement<Element>::kDefaultName; }) {
|
||||
return Info::template TupleElement<Element>::kDefaultName;
|
||||
|
@ -86,17 +60,12 @@ struct FieldInfo {
|
|||
return static_cast<std::string>(Info::template kFieldName<Element>);
|
||||
}
|
||||
}();
|
||||
static inline const std::string kNamespace = [] {
|
||||
if constexpr(requires { Info::template TupleElement<Element>::kDefaultNamespace; }) {
|
||||
return Info::template TupleElement<Element>::kDefaultNamespace;
|
||||
} else {
|
||||
return std::string{};
|
||||
}
|
||||
}();
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct Config {};
|
||||
struct Config {
|
||||
std::optional<T> defaultValue;
|
||||
};
|
||||
|
||||
// GCC workaround: operator==
|
||||
|
||||
|
@ -117,11 +86,9 @@ constexpr auto operator==(const Config<T>&, const Config<T>&) -> bool {
|
|||
return false;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
struct Config<std::optional<T>> {
|
||||
std::optional<T> defaultValue = std::nullopt;
|
||||
bool strict = true;
|
||||
std::optional<Config<T>> main = std::nullopt;
|
||||
template <>
|
||||
struct Config<std::string> {
|
||||
std::optional<std::string_view> defaultValue;
|
||||
};
|
||||
|
||||
namespace impl {
|
||||
|
@ -142,7 +109,7 @@ struct Config : V {
|
|||
requires(!HasParse<T>)
|
||||
: V(AttributeConfig{}) {
|
||||
}
|
||||
[[nodiscard]] constexpr auto Base() const -> const V& {
|
||||
constexpr auto Base() const -> const V& {
|
||||
return static_cast<const V&>(*this);
|
||||
}
|
||||
using type = T;
|
||||
|
@ -163,7 +130,7 @@ struct ElementSerializer {
|
|||
throw ElementParsingError(std::format("[{}: {}] parsing error: [ Not found ]", Info::kName, nameof::nameof_full_type<T>()));
|
||||
}
|
||||
auto elementNode = dynamic_cast<xmlpp::Element*>(node);
|
||||
if(!elementNode) {
|
||||
if(!node) {
|
||||
throw ElementParsingError(std::format("[{}: {}] parsing error: [ Invalid node ]", Info::kName, nameof::nameof_full_type<T>()));
|
||||
}
|
||||
try {
|
||||
|
@ -172,7 +139,6 @@ struct ElementSerializer {
|
|||
throw ElementParsingError(std::format("[{}: {}] parsing error: [ {} ]", Info::kName, nameof::nameof_full_type<T>(), error.what()));
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr auto Serialize(xmlpp::Element* node, const T& element) {
|
||||
auto created = node->add_child_element(Info::kName);
|
||||
if(!node) {
|
||||
|
@ -188,166 +154,6 @@ struct ElementSerializer {
|
|||
}
|
||||
};
|
||||
|
||||
template <typename T, auto& Config, typename Info>
|
||||
struct ElementSerializer<std::optional<T>, Config, Info> {
|
||||
static constexpr auto Parse(xmlpp::Element* element) -> std::optional<T> {
|
||||
return [&] {
|
||||
auto node = element->get_first_child(Info::kName);
|
||||
if(!node) {
|
||||
return std::nullopt;
|
||||
}
|
||||
auto elementNode = dynamic_cast<xmlpp::Element*>(node);
|
||||
if(!elementNode) {
|
||||
return std::nullopt;
|
||||
}
|
||||
if constexpr(Config.strict) {
|
||||
return ElementSerializer<T, Config.main, Info>::Parse(element);
|
||||
} else {
|
||||
return ElementSerializer<T, Config.main, Info>::TryParse(element);
|
||||
}
|
||||
}()
|
||||
.or_else([] {
|
||||
return Config.defaultValue;
|
||||
});
|
||||
}
|
||||
static constexpr auto Serialize(xmlpp::Element* node, const std::optional<T>& element) {
|
||||
if(element) {
|
||||
ElementSerializer<T, Config.main, Info>::Serialize(node, *element);
|
||||
} else if(Config.defaultValue) {
|
||||
Serialize(node, Config.defaultValue);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, auto& Config, typename Info>
|
||||
struct ElementSerializer<std::vector<T>, Config, Info> {
|
||||
// TODO(sha512sum): Add Config and main options instead use Serialization<std::vector<T>>
|
||||
static constexpr auto Parse(xmlpp::Element* element) {
|
||||
try {
|
||||
return Serialization<std::vector<T>>::Parse(element);
|
||||
} catch(const std::exception& error) {
|
||||
throw ElementParsingError(
|
||||
std::format("[{}: {}] parsing error: [ {} ]", Info::kName, nameof::nameof_full_type<std::vector<T>>(), error.what()));
|
||||
}
|
||||
}
|
||||
static constexpr auto Serialize(xmlpp::Element* node, const std::vector<T>& element) {
|
||||
try {
|
||||
return Serialization<std::vector<T>>::Serialize(node, element);
|
||||
} catch(const std::exception& error) {
|
||||
throw ElementSerializaionError(
|
||||
std::format("[{}: {}] serialization error: [ {} ]", Info::kName, nameof::nameof_full_type<std::vector<T>>(), error.what()));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, typename Info>
|
||||
struct AttributeSerializer {
|
||||
static constexpr auto Parse(xmlpp::Element* element) -> T {
|
||||
auto node = element->get_attribute(Info::kName, Info::kNamespace);
|
||||
if(!node) {
|
||||
if constexpr(requires { DefaultValue<T>::Default(); }) {
|
||||
return DefaultValue<T>::Default();
|
||||
}
|
||||
throw AttributeParsingError(std::format("Attribute [{}{}{}: {}] parsing error",
|
||||
Info::kNamespace,
|
||||
Info::kNamespace == std::string{} ? "" : ":",
|
||||
Info::kName,
|
||||
nameof::nameof_full_type<T>()));
|
||||
}
|
||||
if constexpr(requires(std::string_view view) { T::Parse(view); }) {
|
||||
return T::Parse(node->get_value());
|
||||
} else {
|
||||
return node->get_value();
|
||||
}
|
||||
}
|
||||
static constexpr auto TryParse(xmlpp::Element* element) -> std::optional<T>
|
||||
requires requires { T::TryParse(std::string_view{}); }
|
||||
{
|
||||
auto node = element->get_attribute(Info::kName, Info::kNamespace);
|
||||
if(!node) {
|
||||
throw AttributeParsingError(std::format("Attribute [{}{}{}: {}] parsing error",
|
||||
Info::kNamespace,
|
||||
Info::kNamespace == std::string{} ? "" : ":",
|
||||
Info::kName,
|
||||
nameof::nameof_full_type<T>()));
|
||||
}
|
||||
return T::TryParse(node->get_value());
|
||||
}
|
||||
static constexpr auto Serialize(xmlpp::Element* element, const T& obj) {
|
||||
if constexpr(requires {
|
||||
{ ToString(obj) } -> std::convertible_to<const std::string&>;
|
||||
}) {
|
||||
element->set_attribute(Info::kName, ToString(obj), Info::kNamespace);
|
||||
} else {
|
||||
element->set_attribute(Info::kName, obj, Info::kNamespace);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, typename Info>
|
||||
struct AttributeSerializer<std::optional<T>, Info> {
|
||||
static constexpr auto Parse(xmlpp::Element* element) -> std::optional<T> {
|
||||
auto node = element->get_attribute(Info::kName, Info::kNamespace);
|
||||
return node ? std::optional{AttributeSerializer<T, Info>::Parse(element)} : [] -> std::optional<T> {
|
||||
if constexpr(requires { DefaultValue<T>::Default(); }) {
|
||||
return DefaultValue<T>::Default();
|
||||
}
|
||||
return std::nullopt;
|
||||
}();
|
||||
}
|
||||
static constexpr auto Serialize(xmlpp::Element* element, const std::optional<T>& obj) {
|
||||
if(obj) {
|
||||
AttributeSerializer<T, Info>::Serialize(element, *obj);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <typename... Ts, typename Info>
|
||||
struct AttributeSerializer<std::variant<Ts...>, Info> {
|
||||
template <typename T>
|
||||
static constexpr auto FunctionForType(xmlpp::Element* element) {
|
||||
return [=] -> std::optional<T> {
|
||||
if constexpr(requires { AttributeSerializer<T, Info>::TryParse(element); }) {
|
||||
return AttributeSerializer<T, Info>::TryParse(element);
|
||||
} else {
|
||||
try {
|
||||
return AttributeSerializer<T, Info>::Parse(element);
|
||||
} catch(...) {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
static constexpr auto Parse(xmlpp::Element* element) -> std::variant<Ts...> {
|
||||
using T = Info::Type;
|
||||
auto node = element->get_attribute(Info::kName, Info::kNamespace);
|
||||
if(!node) {
|
||||
if constexpr(requires { DefaultValue<T>::Default(); }) {
|
||||
return DefaultValue<T>::Default();
|
||||
}
|
||||
throw AttributeParsingError(std::format("Attribute [{}{}{}: {}] parsing error",
|
||||
Info::kNamespace,
|
||||
Info::kNamespace == std::string{} ? "" : ":",
|
||||
Info::kName,
|
||||
nameof::nameof_full_type<T>()));
|
||||
}
|
||||
return [&]<typename... TTs>(utempl::TypeList<TTs...>) {
|
||||
// operator* is safe because in or_else Parse returns Ts...[sizeof...(Ts) - 1] (not optional)
|
||||
return *utempl::FirstOf(utempl::Tuple{FunctionForType<TTs>(element)...}, std::optional<std::variant<Ts...>>{})
|
||||
.or_else([&] -> std::optional<std::variant<Ts...>> {
|
||||
return AttributeSerializer<decltype(utempl::Get<sizeof...(Ts) - 1>(utempl::kTypeList<Ts...>)), Info>::Parse(element);
|
||||
});
|
||||
}(utempl::TakeFrom<sizeof...(Ts) - 1>(utempl::kTypeList<Ts...>));
|
||||
}
|
||||
static constexpr auto Serialize(xmlpp::Element* element, const std::variant<Ts...>& obj) {
|
||||
std::visit(
|
||||
[&]<typename T>(const T& value) {
|
||||
AttributeSerializer<T, Info>::Serialize(element, value);
|
||||
},
|
||||
obj);
|
||||
}
|
||||
};
|
||||
|
||||
namespace impl {
|
||||
|
||||
template <typename T>
|
||||
|
@ -360,7 +166,15 @@ template <auto& Config, typename Info>
|
|||
auto ParseField(xmlpp::Element* main) -> std::decay_t<decltype(Config)>::type {
|
||||
using Type = std::decay_t<decltype(Config)>::type;
|
||||
if constexpr(std::holds_alternative<AttributeConfig>(Config.Base())) {
|
||||
return AttributeSerializer<Type, Info>::Parse(main);
|
||||
xmlpp::Attribute* node = main->get_attribute(Info::kName);
|
||||
if(!node) {
|
||||
throw AttributeParsingError(std::format("Attribute [{}: {}] parsing error", Info::kName, nameof::nameof_full_type<Type>()));
|
||||
}
|
||||
if constexpr(requires(std::string_view view) { Type::Parse(view); }) {
|
||||
return Type::Parse(node->get_value());
|
||||
} else {
|
||||
return node->get_value();
|
||||
}
|
||||
} else {
|
||||
return ElementSerializer<Type, Config, Info>::Parse(main);
|
||||
}
|
||||
|
@ -369,7 +183,19 @@ auto ParseField(xmlpp::Element* main) -> std::decay_t<decltype(Config)>::type {
|
|||
template <auto& Config, typename Info, typename T>
|
||||
auto SerializeField(xmlpp::Element* main, const T& obj) {
|
||||
if constexpr(std::holds_alternative<AttributeConfig>(Config.Base())) {
|
||||
AttributeSerializer<T, Info>::Serialize(main, obj);
|
||||
auto node = main->set_attribute(Info::kName, [&] -> decltype(auto) {
|
||||
if constexpr(requires {
|
||||
{ ToString(obj) } -> std::convertible_to<const std::string&>;
|
||||
}) {
|
||||
return ToString(obj);
|
||||
} else {
|
||||
return obj;
|
||||
}
|
||||
}());
|
||||
if(!node) {
|
||||
throw AttributeSerializationError(
|
||||
std::format("[{}: {}] parsing error: [ node creation failed ]", Info::kName, nameof::nameof_full_type<T>()));
|
||||
}
|
||||
} else {
|
||||
ElementSerializer<T, Config, Info>::Serialize(main, obj);
|
||||
}
|
||||
|
|
|
@ -1,256 +0,0 @@
|
|||
#pragma once
|
||||
#include <libxml++/libxml++.h>
|
||||
|
||||
#include <larra/serialization/auto.hpp>
|
||||
#include <larra/utils.hpp>
|
||||
#include <optional>
|
||||
#include <variant>
|
||||
|
||||
namespace larra::xmpp::stanza::error {
|
||||
|
||||
namespace type {
|
||||
|
||||
// Not move
|
||||
template <typename Self>
|
||||
struct TypeBaseImpl {
|
||||
static constexpr auto TryParse(std::string_view value) {
|
||||
return value == Self::kName ? std::optional{Self{}} : std::nullopt;
|
||||
}
|
||||
static constexpr auto Parse(std::string_view value) {
|
||||
return value == Self::kName ? Self{} : throw std::runtime_error(std::format("{}::Parse error", nameof::nameof_type<Self>()));
|
||||
}
|
||||
friend constexpr auto ToString(const Self&) -> std::string {
|
||||
return Self::kName;
|
||||
}
|
||||
};
|
||||
|
||||
struct Modify : TypeBaseImpl<Modify> {
|
||||
static constexpr auto kName = "modify";
|
||||
} constexpr kModify{};
|
||||
|
||||
struct Cancel : TypeBaseImpl<Cancel> {
|
||||
static constexpr auto kName = "cancel";
|
||||
} constexpr kCancel{};
|
||||
|
||||
struct Auth : TypeBaseImpl<Auth> {
|
||||
static constexpr auto kName = "auth";
|
||||
} constexpr kAuth{};
|
||||
|
||||
struct Wait : TypeBaseImpl<Wait> {
|
||||
static constexpr auto kName = "wait";
|
||||
} constexpr kWait{};
|
||||
|
||||
} // namespace type
|
||||
using TypeVariant = std::variant<type::Modify, type::Cancel, type::Auth>;
|
||||
|
||||
struct Type : TypeVariant {
|
||||
using TypeVariant::variant;
|
||||
constexpr Type(TypeVariant variant) : TypeVariant(std::move(variant)) {
|
||||
}
|
||||
|
||||
friend constexpr auto ToString(const Type& value) -> std::string {
|
||||
return std::visit(
|
||||
[](const auto& value) {
|
||||
return ToString(value);
|
||||
},
|
||||
value);
|
||||
}
|
||||
};
|
||||
|
||||
struct StanzaErrorBase : std::exception {};
|
||||
namespace impl {
|
||||
template <typename T, typename Default>
|
||||
struct StanzaError : StanzaErrorBase {
|
||||
static constexpr auto kDefaultName = "error";
|
||||
static constexpr auto kDefaultNamespace = "urn:ietf:params:xml:ns:xmpp-stanzas";
|
||||
static inline const auto kKebabCaseName = static_cast<std::string>(utils::ToKebabCaseName<T>());
|
||||
constexpr StanzaError(std::optional<std::string> by, Type type) : by(std::move(by)), type(std::move(type)) {
|
||||
}
|
||||
constexpr StanzaError() = default;
|
||||
struct FieldInfo {
|
||||
using Main = StanzaError;
|
||||
using Info = serialization::MetaInfo<Main>;
|
||||
using Type = Type;
|
||||
static inline const std::string kName = "type";
|
||||
static inline const std::string kNamespace = "";
|
||||
};
|
||||
|
||||
static constexpr auto kErrorMessage = [] -> std::string_view {
|
||||
static constexpr auto name = nameof::nameof_short_type<T>();
|
||||
static constexpr auto str = [] {
|
||||
return std::array{std::string_view{"Stanza IQ Error: "}, std::string_view{name}, std::string_view{"\0", 1}} | std::views::join;
|
||||
};
|
||||
static constexpr auto array = str() | std::ranges::to<utils::RangeToWrapper<std::array<char, std::ranges::distance(str())>>>();
|
||||
return {array.data(), array.size() - 1};
|
||||
}();
|
||||
|
||||
std::optional<std::string> by{};
|
||||
Type type = Default{};
|
||||
|
||||
// TODO(unknown): Add "optional text children" support for stanza error. Check "XML Stanzas" -> "Syntax" for more details
|
||||
static constexpr auto TryParse(xmlpp::Element* element) -> std::optional<T> {
|
||||
if(!element) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto by = element->get_attribute("by");
|
||||
auto type = element->get_attribute("type");
|
||||
if(!type) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto node = element->get_first_child(kKebabCaseName);
|
||||
if(!node) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return T{by ? std::optional{by->get_value()} : std::nullopt, //
|
||||
serialization::AttributeSerializer<Type, FieldInfo>::Parse(element)};
|
||||
}
|
||||
static constexpr auto Parse(xmlpp::Element* element) -> T {
|
||||
return TryParse(element).value();
|
||||
}
|
||||
friend constexpr auto operator<<(xmlpp::Element* element, const T& obj) -> void {
|
||||
element->set_attribute("type", ToString(obj.type));
|
||||
if(obj.by) {
|
||||
element->set_attribute("by", *obj.by);
|
||||
}
|
||||
|
||||
auto node = element->add_child_element(kKebabCaseName);
|
||||
node->set_namespace_declaration(kDefaultNamespace);
|
||||
}
|
||||
constexpr auto operator==(const StanzaError&) const -> bool {
|
||||
return true;
|
||||
};
|
||||
[[nodiscard]] constexpr auto what() const noexcept -> const char* override {
|
||||
return kErrorMessage.data();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace impl
|
||||
|
||||
// Helper class to prevent parsing response stream into an expected return type if its name is an 'error'
|
||||
struct UnknownStanzaError : StanzaErrorBase {
|
||||
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{UnknownStanzaError{}};
|
||||
}
|
||||
static constexpr auto Parse(xmlpp::Element* element) {
|
||||
return TryParse(element).value();
|
||||
}
|
||||
friend constexpr auto operator<<(xmlpp::Element* element, const UnknownStanzaError& obj) -> void {
|
||||
throw std::runtime_error{std::format("'{}' must never be written into the real stream!", kErrorMessage)};
|
||||
}
|
||||
constexpr auto operator==(const UnknownStanzaError&) const -> bool {
|
||||
return true;
|
||||
};
|
||||
[[nodiscard]] constexpr auto what() const noexcept -> const char* override {
|
||||
return kErrorMessage.data();
|
||||
}
|
||||
};
|
||||
|
||||
struct BadRequest : impl::StanzaError<BadRequest, type::Modify> {
|
||||
using impl::StanzaError<BadRequest, type::Modify>::StanzaError;
|
||||
};
|
||||
struct Conflict : impl::StanzaError<Conflict, type::Cancel> {
|
||||
using impl::StanzaError<Conflict, type::Cancel>::StanzaError;
|
||||
};
|
||||
struct FeatureNotImplemented : impl::StanzaError<FeatureNotImplemented, type::Cancel> {
|
||||
using impl::StanzaError<FeatureNotImplemented, type::Cancel>::StanzaError;
|
||||
};
|
||||
struct Forbidden : impl::StanzaError<Forbidden, type::Auth> {
|
||||
using impl::StanzaError<Forbidden, type::Auth>::StanzaError;
|
||||
};
|
||||
struct Gone : impl::StanzaError<Gone, type::Cancel> {
|
||||
using impl::StanzaError<Gone, type::Cancel>::StanzaError;
|
||||
};
|
||||
struct InternalServerError : impl::StanzaError<InternalServerError, type::Cancel> {
|
||||
using impl::StanzaError<InternalServerError, type::Cancel>::StanzaError;
|
||||
};
|
||||
struct ItemNotFound : impl::StanzaError<ItemNotFound, type::Cancel> {
|
||||
using impl::StanzaError<ItemNotFound, type::Cancel>::StanzaError;
|
||||
};
|
||||
struct JidMalformed : impl::StanzaError<JidMalformed, type::Modify> {
|
||||
using impl::StanzaError<JidMalformed, type::Modify>::StanzaError;
|
||||
};
|
||||
struct NotAcceptable : impl::StanzaError<NotAcceptable, type::Modify> {
|
||||
using impl::StanzaError<NotAcceptable, type::Modify>::StanzaError;
|
||||
};
|
||||
struct NotAllowed : impl::StanzaError<NotAllowed, type::Cancel> {
|
||||
using impl::StanzaError<NotAllowed, type::Cancel>::StanzaError;
|
||||
};
|
||||
struct NotAuthorized : impl::StanzaError<NotAuthorized, type::Auth> {
|
||||
using impl::StanzaError<NotAuthorized, type::Auth>::StanzaError;
|
||||
};
|
||||
struct PolicyViolation : impl::StanzaError<PolicyViolation, type::Modify> {
|
||||
using impl::StanzaError<PolicyViolation, type::Modify>::StanzaError;
|
||||
};
|
||||
struct RecipientUnavailable : impl::StanzaError<RecipientUnavailable, type::Wait> {
|
||||
using impl::StanzaError<RecipientUnavailable, type::Wait>::StanzaError;
|
||||
};
|
||||
struct Redirect : impl::StanzaError<Redirect, type::Modify> {
|
||||
using impl::StanzaError<Redirect, type::Modify>::StanzaError;
|
||||
};
|
||||
struct RegistrationRequired : impl::StanzaError<RegistrationRequired, type::Auth> {
|
||||
using impl::StanzaError<RegistrationRequired, type::Auth>::StanzaError;
|
||||
};
|
||||
struct RemoteServerNotFound : impl::StanzaError<RemoteServerNotFound, type::Cancel> {
|
||||
using impl::StanzaError<RemoteServerNotFound, type::Cancel>::StanzaError;
|
||||
};
|
||||
struct RemoteServerTimeout : impl::StanzaError<RemoteServerTimeout, type::Wait> {
|
||||
using impl::StanzaError<RemoteServerTimeout, type::Wait>::StanzaError;
|
||||
};
|
||||
struct ResourceConstraint : impl::StanzaError<ResourceConstraint, type::Wait> {
|
||||
using impl::StanzaError<ResourceConstraint, type::Wait>::StanzaError;
|
||||
};
|
||||
struct ServiceUnavailable : impl::StanzaError<ServiceUnavailable, type::Cancel> {
|
||||
using impl::StanzaError<ServiceUnavailable, type::Cancel>::StanzaError;
|
||||
};
|
||||
struct SubscriptionRequired : impl::StanzaError<SubscriptionRequired, type::Auth> {
|
||||
using impl::StanzaError<SubscriptionRequired, type::Auth>::StanzaError;
|
||||
};
|
||||
struct UndefinedCondition : impl::StanzaError<UndefinedCondition, type::Modify> {
|
||||
using impl::StanzaError<UndefinedCondition, type::Modify>::StanzaError;
|
||||
};
|
||||
struct UnexpectedRequest : impl::StanzaError<UnexpectedRequest, type::Modify> {
|
||||
using impl::StanzaError<UnexpectedRequest, type::Modify>::StanzaError;
|
||||
};
|
||||
|
||||
using StanzaError = std::variant<BadRequest,
|
||||
Conflict,
|
||||
FeatureNotImplemented,
|
||||
Forbidden,
|
||||
Gone,
|
||||
InternalServerError,
|
||||
ItemNotFound,
|
||||
JidMalformed,
|
||||
NotAcceptable,
|
||||
NotAllowed,
|
||||
NotAuthorized,
|
||||
PolicyViolation,
|
||||
RecipientUnavailable,
|
||||
Redirect,
|
||||
RegistrationRequired,
|
||||
RemoteServerNotFound,
|
||||
RemoteServerTimeout,
|
||||
ResourceConstraint,
|
||||
ServiceUnavailable,
|
||||
SubscriptionRequired,
|
||||
UndefinedCondition,
|
||||
UnexpectedRequest,
|
||||
UnknownStanzaError>;
|
||||
|
||||
static_assert(std::is_same_v<typename std::variant_alternative_t<std::variant_size_v<StanzaError> - 1, StanzaError>, UnknownStanzaError>,
|
||||
"'UnknownStanzaError' must be at the end of 'StanzaError' variant");
|
||||
|
||||
} // namespace larra::xmpp::stanza::error
|
||||
|
||||
namespace larra::xmpp {
|
||||
|
||||
template <typename Info>
|
||||
struct serialization::AttributeSerializer<stanza::error::Type, Info>
|
||||
: serialization::AttributeSerializer<stanza::error::TypeVariant, Info> {};
|
||||
|
||||
} // namespace larra::xmpp
|
|
@ -2,19 +2,53 @@
|
|||
#include <libxml++/libxml++.h>
|
||||
|
||||
#include <larra/utils.hpp>
|
||||
#include <nameof.hpp>
|
||||
#include <ranges>
|
||||
#include <variant>
|
||||
|
||||
namespace larra::xmpp {
|
||||
|
||||
namespace impl {
|
||||
// std::isupper not declared as constexpr
|
||||
constexpr auto IsUpper(char ch) -> bool {
|
||||
return ch >= 'A' && ch <= 'Z';
|
||||
}
|
||||
|
||||
constexpr auto ToLower(char ch) -> char {
|
||||
return (ch >= 'A' && ch <= 'Z') ? static_cast<char>(ch + ('a' - 'A')) : ch;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
constexpr auto ToKebabCaseName() -> std::string_view {
|
||||
static constexpr auto rawStr = nameof::nameof_short_type<T>();
|
||||
|
||||
constexpr auto str = [] {
|
||||
return rawStr //
|
||||
| std::views::transform([](auto ch) {
|
||||
return impl::IsUpper(ch) ? std::array<char, 2>{'-', impl::ToLower(ch)} : std::array<char, 2>{ch, '\0'};
|
||||
}) //
|
||||
| std::views::join //
|
||||
| std::views::filter([](char ch) {
|
||||
return ch != '\0';
|
||||
}) //
|
||||
| std::views::drop(1);
|
||||
};
|
||||
static constexpr auto arr = str() | std::ranges::to<utils::RangeToWrapper<std::array<char, std::ranges::distance(str())>>>();
|
||||
return {arr.data(), arr.size()};
|
||||
}
|
||||
|
||||
} // namespace impl
|
||||
|
||||
namespace error::stream {
|
||||
|
||||
struct BaseError : std::exception {};
|
||||
|
||||
namespace impl {
|
||||
// DO NOT MOVE TO ANOTHER NAMESPACE(where no heirs). VIA friend A FUNCTION IS ADDED THAT VIA ADL WILL BE SEARCHED FOR HEIRS
|
||||
// C++20 modules very unstable in clangd :(
|
||||
template <typename T>
|
||||
struct Error : BaseError {
|
||||
struct ErrorImpl : BaseError {
|
||||
static constexpr auto kDefaultName = "stream:error";
|
||||
static inline const auto kKebabCaseName = static_cast<std::string>(utils::ToKebabCaseName<T>());
|
||||
static inline const auto kKebabCaseName = static_cast<std::string>(impl::ToKebabCaseName<T>());
|
||||
|
||||
static constexpr auto kErrorMessage = [] -> std::string_view {
|
||||
static constexpr auto name = nameof::nameof_short_type<T>();
|
||||
|
@ -39,52 +73,31 @@ struct Error : BaseError {
|
|||
}
|
||||
};
|
||||
|
||||
} // namespace impl
|
||||
|
||||
// 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::runtime_error{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 : impl::Error<BadFormat> {};
|
||||
struct BadNamespacePrefix : impl::Error<BadNamespacePrefix> {};
|
||||
struct Conflict : impl::Error<Conflict> {};
|
||||
struct ConnectionTimeout : impl::Error<ConnectionTimeout> {};
|
||||
struct HostGone : impl::Error<HostGone> {};
|
||||
struct HostUnknown : impl::Error<HostUnknown> {};
|
||||
struct ImproperAdressing : impl::Error<ImproperAdressing> {};
|
||||
struct InternalServerError : impl::Error<InternalServerError> {};
|
||||
struct InvalidFrom : impl::Error<InvalidFrom> {};
|
||||
struct InvalidNamespace : impl::Error<InvalidNamespace> {};
|
||||
struct InvalidXml : impl::Error<InvalidXml> {};
|
||||
struct NotAuthorized : impl::Error<NotAuthorized> {};
|
||||
struct NotWellFormed : impl::Error<NotWellFormed> {};
|
||||
struct PolicyViolation : impl::Error<PolicyViolation> {};
|
||||
struct RemoteConnectionFailed : impl::Error<RemoteConnectionFailed> {};
|
||||
struct Reset : impl::Error<Reset> {};
|
||||
struct ResourceConstraint : impl::Error<ResourceConstraint> {};
|
||||
struct RestrictedXml : impl::Error<RestrictedXml> {};
|
||||
struct SeeOtherHost : impl::Error<SeeOtherHost> {};
|
||||
struct SystemShutdown : impl::Error<SystemShutdown> {};
|
||||
struct UndefinedCondition : impl::Error<UndefinedCondition> {};
|
||||
struct UnsupportedEncoding : impl::Error<UnsupportedEncoding> {};
|
||||
struct UnsupportedFeature : impl::Error<UnsupportedFeature> {};
|
||||
struct UnsupportedStanzaType : impl::Error<UnsupportedStanzaType> {};
|
||||
struct UnsupportedVersion : impl::Error<UnsupportedVersion> {};
|
||||
struct BadFormat : ErrorImpl<BadFormat> {};
|
||||
struct BadNamespacePrefix : ErrorImpl<BadNamespacePrefix> {};
|
||||
struct Conflict : ErrorImpl<Conflict> {};
|
||||
struct ConnectionTimeout : ErrorImpl<ConnectionTimeout> {};
|
||||
struct HostGone : ErrorImpl<HostGone> {};
|
||||
struct HostUnknown : ErrorImpl<HostUnknown> {};
|
||||
struct ImproperAdressing : ErrorImpl<ImproperAdressing> {};
|
||||
struct InternalServerError : ErrorImpl<InternalServerError> {};
|
||||
struct InvalidForm : ErrorImpl<InvalidForm> {};
|
||||
struct InvalidNamespace : ErrorImpl<InvalidNamespace> {};
|
||||
struct InvalidXml : ErrorImpl<InvalidXml> {};
|
||||
struct NotAuthorized : ErrorImpl<NotAuthorized> {};
|
||||
struct NotWellFormed : ErrorImpl<NotWellFormed> {};
|
||||
struct PolicyViolation : ErrorImpl<PolicyViolation> {};
|
||||
struct RemoteConnectionFailed : ErrorImpl<RemoteConnectionFailed> {};
|
||||
struct Reset : ErrorImpl<Reset> {};
|
||||
struct ResourceConstraint : ErrorImpl<ResourceConstraint> {};
|
||||
struct RestrictedXml : ErrorImpl<RestrictedXml> {};
|
||||
struct SeeOtherHost : ErrorImpl<SeeOtherHost> {};
|
||||
struct SystemShutdown : ErrorImpl<SystemShutdown> {};
|
||||
struct UndefinedCondition : ErrorImpl<UndefinedCondition> {};
|
||||
struct UnsupportedEncoding : ErrorImpl<UnsupportedEncoding> {};
|
||||
struct UnsupportedFeature : ErrorImpl<UnsupportedFeature> {};
|
||||
struct UnsupportedStanzaType : ErrorImpl<UnsupportedStanzaType> {};
|
||||
struct UnsupportedVersion : ErrorImpl<UnsupportedVersion> {};
|
||||
|
||||
} // namespace error::stream
|
||||
|
||||
|
@ -96,7 +109,7 @@ using StreamError = std::variant<error::stream::BadFormat,
|
|||
error::stream::HostUnknown,
|
||||
error::stream::ImproperAdressing,
|
||||
error::stream::InternalServerError,
|
||||
error::stream::InvalidFrom,
|
||||
error::stream::InvalidForm,
|
||||
error::stream::InvalidNamespace,
|
||||
error::stream::InvalidXml,
|
||||
error::stream::NotAuthorized,
|
||||
|
@ -110,11 +123,6 @@ using StreamError = std::variant<error::stream::BadFormat,
|
|||
error::stream::UnsupportedEncoding,
|
||||
error::stream::UnsupportedFeature,
|
||||
error::stream::UnsupportedStanzaType,
|
||||
error::stream::UnsupportedVersion,
|
||||
error::stream::UnknownXmppError>;
|
||||
|
||||
static_assert(
|
||||
std::is_same_v<typename std::variant_alternative_t<std::variant_size_v<StreamError> - 1, StreamError>, error::stream::UnknownXmppError>,
|
||||
"'UnknownXmppError' must be at the end of 'StreamError' variant");
|
||||
error::stream::UnsupportedVersion>;
|
||||
|
||||
} // namespace larra::xmpp
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
#pragma once
|
||||
#include <boost/pfr.hpp>
|
||||
#include <nameof.hpp>
|
||||
#include <ranges>
|
||||
#include <utempl/utils.hpp>
|
||||
|
||||
namespace larra::xmpp::utils {
|
||||
|
@ -297,45 +295,4 @@ 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>;
|
||||
} || std::convertible_to<T, std::string>;
|
||||
|
||||
template <typename T>
|
||||
auto AccumulateFieldLength(const T& obj) -> std::size_t {
|
||||
std::size_t totalLength = 0;
|
||||
boost::pfr::for_each_field(obj, [&](const LengthCalculatable auto& field) {
|
||||
totalLength += field.length(); // Accumulate length of each field
|
||||
});
|
||||
return totalLength;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
constexpr auto ToKebabCaseName() -> std::string_view {
|
||||
static constexpr auto rawStr = nameof::nameof_short_type<T>();
|
||||
|
||||
// std::isupper and std::tolower are not declared as constexpr
|
||||
static constexpr auto isUpper = [](char ch) {
|
||||
return ch >= 'A' && ch <= 'Z';
|
||||
};
|
||||
static constexpr auto toLower = [](char ch) {
|
||||
return (ch >= 'A' && ch <= 'Z') ? static_cast<char>(ch + ('a' - 'A')) : ch;
|
||||
};
|
||||
|
||||
constexpr auto str = [] {
|
||||
return rawStr //
|
||||
| std::views::transform([](auto ch) {
|
||||
return isUpper(ch) ? std::array<char, 2>{'-', toLower(ch)} : std::array<char, 2>{ch, '\0'};
|
||||
}) //
|
||||
| std::views::join //
|
||||
| std::views::filter([](char ch) {
|
||||
return ch != '\0';
|
||||
}) //
|
||||
| std::views::drop(1);
|
||||
};
|
||||
static constexpr auto arr = str() | std::ranges::to<utils::RangeToWrapper<std::array<char, std::ranges::distance(str())>>>();
|
||||
return {arr.data(), arr.size()};
|
||||
}
|
||||
|
||||
} // namespace larra::xmpp::utils
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
#pragma once
|
||||
#include <string>
|
||||
namespace larra::xmpp {
|
||||
|
||||
struct XmlLanguage : std::string {
|
||||
static constexpr auto kDefaultName = "lang";
|
||||
static constexpr auto kDefaultNamespace = "xml";
|
||||
using std::string::basic_string;
|
||||
constexpr XmlLanguage() : std::string{"en"} {};
|
||||
constexpr XmlLanguage(std::string str) : std::string(std::move(str)) {
|
||||
}
|
||||
static constexpr auto Parse(std::string_view view) -> XmlLanguage {
|
||||
return static_cast<std::string>(view);
|
||||
}
|
||||
friend constexpr auto ToString(XmlLanguage value) -> std::string {
|
||||
return std::move(static_cast<std::string&>(value));
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace larra::xmpp
|
|
@ -1,31 +0,0 @@
|
|||
#include <larra/bind.hpp>
|
||||
|
||||
namespace larra::xmpp::iq {
|
||||
auto operator<<(xmlpp::Element* element, const Bind& bind) -> void {
|
||||
element->set_namespace_declaration(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
|
|
@ -1,30 +0,0 @@
|
|||
#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_namespace_declaration(Roster::kDefaultNamespace);
|
||||
S::Serialize(element, self);
|
||||
}
|
||||
auto Roster::Parse(xmlpp::Element* element) -> Roster {
|
||||
return S::Parse<Roster>(element);
|
||||
}
|
||||
} // namespace larra::xmpp::iq
|
112
tests/iq.cpp
112
tests/iq.cpp
|
@ -1,23 +1,10 @@
|
|||
#include <gtest/gtest.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <larra/iq.hpp>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace {
|
||||
|
||||
static constexpr auto kForbiddenErrorData = R"(<?xml version="1.0" encoding="UTF-8"?>
|
||||
<iq id="42" to="name@server/res" type="error"><error type="auth"><forbidden xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/></error>
|
||||
</iq>)";
|
||||
|
||||
static constexpr auto kExpectedSetData = R"(<?xml version="1.0" encoding="UTF-8"?>
|
||||
<iq id="id" type="set"><some>37</some></iq>
|
||||
)";
|
||||
|
||||
static constexpr auto kExpectedData = R"(<?xml version="1.0" encoding="UTF-8"?>
|
||||
<iq id="id" type="get"><some>37</some></iq>
|
||||
)";
|
||||
|
||||
static constexpr auto kExpectedData = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<iq id=\"id\" type=\"get\"><some>37</some></iq>\n";
|
||||
struct SomeStruct {
|
||||
int value;
|
||||
constexpr auto operator==(const SomeStruct&) const -> bool = default;
|
||||
|
@ -56,101 +43,4 @@ TEST(IQ, Parse) {
|
|||
EXPECT_EQ(S::Parse(node), iq);
|
||||
}
|
||||
|
||||
TEST(IQ, ParseForbiddenError) {
|
||||
std::istringstream xml_stream(kForbiddenErrorData);
|
||||
|
||||
xmlpp::DomParser parser;
|
||||
parser.parse_stream(xml_stream);
|
||||
|
||||
using S = Serialization<Iq<SomeStruct>>;
|
||||
xmlpp::Document* doc = parser.get_document();
|
||||
auto iqRes = S::Parse(doc->get_root_node());
|
||||
|
||||
ASSERT_TRUE(std::holds_alternative<IqError>(iqRes));
|
||||
|
||||
auto errorRes = std::get<IqError>(iqRes);
|
||||
ASSERT_TRUE(std::holds_alternative<stanza::error::Forbidden>(errorRes.payload));
|
||||
}
|
||||
|
||||
TEST(IQ, IqErrThrowVisitorThrow) {
|
||||
std::istringstream xml_stream(kForbiddenErrorData);
|
||||
|
||||
xmlpp::DomParser parser;
|
||||
parser.parse_stream(xml_stream);
|
||||
|
||||
using S = Serialization<Iq<SomeStruct>>;
|
||||
xmlpp::Document* doc = parser.get_document();
|
||||
auto iqRes = S::Parse(doc->get_root_node());
|
||||
ASSERT_TRUE(std::holds_alternative<IqError>(iqRes));
|
||||
|
||||
static constexpr auto visitorErrMsg = "Test Error";
|
||||
static constexpr auto throwErrMsg = "Stanza IQ Error: Forbidden";
|
||||
try {
|
||||
std::visit(utempl::Overloaded([](iq::Result<SomeStruct> r) {}, IqErrThrowVisitor{visitorErrMsg}), std::move(iqRes));
|
||||
} catch(const stanza::error::StanzaErrorBase& err) {
|
||||
ASSERT_STREQ(throwErrMsg, err.what());
|
||||
return;
|
||||
} catch(const std::runtime_error& err) {
|
||||
ASSERT_TRUE(false) << "Invalid throw type throw";
|
||||
} catch(...) {
|
||||
ASSERT_TRUE(false) << "Unexpected throw";
|
||||
}
|
||||
|
||||
ASSERT_TRUE(false) << "Expected throwing an exception due to an error in output";
|
||||
}
|
||||
|
||||
TEST(IQ, IqErrThrowVisitorThrowGet) {
|
||||
std::istringstream xml_stream(kExpectedData);
|
||||
|
||||
xmlpp::DomParser parser;
|
||||
parser.parse_stream(xml_stream);
|
||||
|
||||
using S = Serialization<Iq<SomeStruct>>;
|
||||
xmlpp::Document* doc = parser.get_document();
|
||||
auto iqRes = S::Parse(doc->get_root_node());
|
||||
ASSERT_TRUE(std::holds_alternative<iq::Get<SomeStruct>>(iqRes)) << "\tERROR: Unexpected parse result";
|
||||
|
||||
static constexpr auto visitorErrMsg = "Test Error";
|
||||
static constexpr auto throwErrMsg = "Test Error: 'Get' is an invalid type for IQ result. Expected 'Result' or 'Error'";
|
||||
try {
|
||||
std::visit(utempl::Overloaded([](iq::Result<SomeStruct> r) {}, IqErrThrowVisitor{"Test Error"}), std::move(iqRes));
|
||||
} catch(const stanza::error::StanzaErrorBase& err) {
|
||||
ASSERT_TRUE(false) << "\tERROR: Invalid throw type throw";
|
||||
} catch(const std::runtime_error& err) {
|
||||
ASSERT_STREQ(throwErrMsg, err.what());
|
||||
return;
|
||||
} catch(...) {
|
||||
ASSERT_TRUE(false) << "\tERROR: Unexpected throw";
|
||||
}
|
||||
|
||||
ASSERT_TRUE(false) << "\tERROR: Expected throwing an exception due to an error in output";
|
||||
}
|
||||
|
||||
TEST(IQ, IqErrThrowVisitorThrowSet) {
|
||||
std::istringstream xml_stream(kExpectedSetData);
|
||||
|
||||
xmlpp::DomParser parser;
|
||||
parser.parse_stream(xml_stream);
|
||||
|
||||
using S = Serialization<Iq<SomeStruct>>;
|
||||
xmlpp::Document* doc = parser.get_document();
|
||||
auto iqRes = S::Parse(doc->get_root_node());
|
||||
ASSERT_TRUE(std::holds_alternative<iq::Set<SomeStruct>>(iqRes)) << "\tERROR: Unexpected parse result";
|
||||
|
||||
static constexpr auto visitorErrMsg = "Test Error";
|
||||
static constexpr auto throwErrMsg = "Test Error: 'Set' is an invalid type for IQ result. Expected 'Result' or 'Error'";
|
||||
try {
|
||||
std::visit(utempl::Overloaded([](iq::Result<SomeStruct> r) {}, IqErrThrowVisitor{"Test Error"}), std::move(iqRes));
|
||||
} catch(const stanza::error::StanzaErrorBase& err) {
|
||||
ASSERT_TRUE(false) << "\tERROR: Invalid throw type throw";
|
||||
} catch(const std::runtime_error& err) {
|
||||
ASSERT_STREQ(throwErrMsg, err.what());
|
||||
return;
|
||||
} catch(...) {
|
||||
ASSERT_TRUE(false) << "\tERROR: Unexpected throw";
|
||||
}
|
||||
|
||||
ASSERT_TRUE(false) << "\tERROR: Expected throwing an exception due to an error in output";
|
||||
}
|
||||
|
||||
} // namespace larra::xmpp
|
||||
|
|
|
@ -1,63 +0,0 @@
|
|||
#include <gtest/gtest.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <boost/program_options.hpp>
|
||||
#include <exception>
|
||||
#include <print>
|
||||
#include <stdexcept>
|
||||
|
||||
// clang-format off
|
||||
constexpr auto ToString(spdlog::level::level_enum e) {
|
||||
switch (e) {
|
||||
case spdlog::level::trace: return "TRACE";
|
||||
case spdlog::level::debug: return "DEBUG";
|
||||
case spdlog::level::info: return "INFO";
|
||||
case spdlog::level::warn: return "WARNING";
|
||||
case spdlog::level::err: return "ERROR";
|
||||
case spdlog::level::critical: return "CRITICAL";
|
||||
case spdlog::level::off: return "OFF";
|
||||
default:
|
||||
return "INVALID";
|
||||
}
|
||||
}
|
||||
// clang-format on
|
||||
|
||||
namespace po = boost::program_options;
|
||||
|
||||
auto main(int argc, char** argv) -> int {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
|
||||
// Define options
|
||||
po::options_description desc("Allowed options");
|
||||
desc.add_options()("help,h", "Print help message")("log_level,l",
|
||||
po::value<int>()->default_value(SPDLOG_LEVEL_OFF),
|
||||
"Set log level: 0=TRACE, 1=DEBUG, 2=INFO, 3=WARN, 4=ERROR, 5=CRITICAL, 6=OFF");
|
||||
|
||||
// Parse command-line arguments
|
||||
po::variables_map vm;
|
||||
try {
|
||||
po::store(po::parse_command_line(argc, argv, desc), vm);
|
||||
po::notify(vm);
|
||||
|
||||
if(vm["log_level"].as<int>() < spdlog::level::level_enum::trace || vm["log_level"].as<int>() > spdlog::level::level_enum::off) {
|
||||
throw std::invalid_argument{
|
||||
std::format("Invalid argument value for '--log_level' option. Check option description for more details")};
|
||||
}
|
||||
if(vm["log_level"].as<int>() < SPDLOG_ACTIVE_LEVEL) {
|
||||
SPDLOG_WARN("Specified log_level '{}' is lower than max available one '{}'. Log level will be changed according to the maximum one",
|
||||
vm["log_level"].as<int>(),
|
||||
SPDLOG_ACTIVE_LEVEL);
|
||||
}
|
||||
} catch(const std::exception& e) {
|
||||
SPDLOG_CRITICAL("Cmd parse error: {}", e.what());
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Cmd options handling
|
||||
auto level = static_cast<spdlog::level::level_enum>(vm["log_level"].as<int>());
|
||||
spdlog::set_level(level);
|
||||
if(level != spdlog::level::level_enum::off) {
|
||||
std::println("\nEnvironment setup:\n\tCurrently set log level: {}\n", ToString(spdlog::get_level()));
|
||||
}
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
|
@ -1,87 +0,0 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
#include <larra/jid.hpp>
|
||||
#include <larra/message.hpp>
|
||||
|
||||
namespace larra::xmpp {
|
||||
|
||||
namespace {
|
||||
|
||||
auto CreateTestData() {
|
||||
auto doc = std::make_unique<xmlpp::Document>();
|
||||
auto node = doc->create_root_node("message");
|
||||
node->set_attribute("from", "user1@server.i2p");
|
||||
node->set_attribute("to", "user2@server.i2p");
|
||||
node->set_attribute("type", "chat");
|
||||
node->set_attribute("id", "1");
|
||||
node->set_attribute("lang", "en", "xml");
|
||||
|
||||
auto bodyNode = node->add_child_element("body");
|
||||
bodyNode->add_child_text("hello");
|
||||
return doc;
|
||||
}
|
||||
|
||||
auto CreateErrorTestData() {
|
||||
auto doc = std::make_unique<xmlpp::Document>();
|
||||
auto node = doc->create_root_node("message");
|
||||
node->set_attribute("from", "user1@server.i2p");
|
||||
node->set_attribute("to", "user2@server.i2p");
|
||||
node->set_attribute("type", "error");
|
||||
auto error = node->add_child_element("error");
|
||||
error->set_attribute("type", "auth");
|
||||
auto notAuthorized = error->add_child_element("not-authorized");
|
||||
notAuthorized->set_namespace_declaration("urn:ietf:params:xml:ns:xmpp-stanzas");
|
||||
return doc;
|
||||
}
|
||||
|
||||
const message::Message<BareJid, BareJid> kMessage{
|
||||
.from = {.username = "user1", .server = "server.i2p"},
|
||||
.to = {.username = "user2", .server = "server.i2p"},
|
||||
.type = message::type::kChat,
|
||||
.id = "1",
|
||||
.language = "en",
|
||||
.body = {{.content = "hello"}} //
|
||||
};
|
||||
|
||||
const message::Error<BareJid, BareJid> kError{.from = {.username = "user1", .server = "server.i2p"}, //
|
||||
.to = {.username = "user2", .server = "server.i2p"},
|
||||
.error = stanza::error::NotAuthorized{}};
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(Parse, Message) {
|
||||
using T = Message<BareJid, BareJid>;
|
||||
{
|
||||
auto doc = CreateTestData();
|
||||
auto node = doc->get_root_node();
|
||||
auto message = Serialization<T>::Parse(node);
|
||||
EXPECT_EQ(message, static_cast<T>(kMessage));
|
||||
}
|
||||
|
||||
auto doc = CreateErrorTestData();
|
||||
auto node = doc->get_root_node();
|
||||
auto message = Serialization<T>::Parse(node);
|
||||
EXPECT_EQ(message, static_cast<T>(kError));
|
||||
}
|
||||
|
||||
TEST(Serialize, Message) {
|
||||
{
|
||||
auto expected =
|
||||
R"(<?xml version="1.0" encoding="UTF-8"?>
|
||||
<message from="user1@server.i2p" to="user2@server.i2p" type="chat" id="1" xml:lang="en"><body>hello</body></message>
|
||||
)";
|
||||
xmlpp::Document doc;
|
||||
auto node = doc.create_root_node("message");
|
||||
Serialization<message::Message<BareJid, BareJid>>::Serialize(node, kMessage);
|
||||
EXPECT_EQ(doc.write_to_string(), expected);
|
||||
}
|
||||
auto expected = R"(<?xml version="1.0" encoding="UTF-8"?>
|
||||
<message type="error" from="user1@server.i2p" to="user2@server.i2p"><error type="auth"><not-authorized xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/></error></message>
|
||||
)";
|
||||
xmlpp::Document doc;
|
||||
auto node = doc.create_root_node("message");
|
||||
Serialization<message::Error<BareJid, BareJid>>::Serialize(node, kError);
|
||||
EXPECT_EQ(doc.write_to_string(), expected);
|
||||
}
|
||||
|
||||
} // namespace larra::xmpp
|
114
tests/proxy.cpp
114
tests/proxy.cpp
|
@ -1,114 +0,0 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
#include <larra/client/client.hpp>
|
||||
#include <larra/impl/mock_socket.hpp>
|
||||
#include <larra/proxy.hpp>
|
||||
|
||||
using namespace larra::xmpp;
|
||||
using boost::asio::ip::tcp;
|
||||
namespace asio = boost::asio;
|
||||
|
||||
class ProxyTest : public ::testing::Test {
|
||||
protected:
|
||||
boost::asio::io_context io;
|
||||
larra::xmpp::impl::MockSocket mockSocket{io.get_executor()};
|
||||
};
|
||||
|
||||
// Test 1: Connect via HTTP proxy with successful server response
|
||||
TEST_F(ProxyTest, ConnectViaHttpProxy_SuccessfulResponse) {
|
||||
HttpProxy proxy{"proxy_host", 8080};
|
||||
|
||||
std::string targetHost = "target_host";
|
||||
uint16_t targetPort = 80;
|
||||
|
||||
std::string expectedRequest =
|
||||
std::format("CONNECT {}:{} HTTP/1.1\r\nHost: {}:{}\r\n\r\n", targetHost, targetPort, targetHost, targetPort);
|
||||
|
||||
std::string proxyResponse = "HTTP/1.1 200 Connection established\r\n\r\n";
|
||||
|
||||
mockSocket.AddReceivedData(proxyResponse);
|
||||
|
||||
bool connectSuccessful = false;
|
||||
|
||||
asio::co_spawn(
|
||||
io,
|
||||
[&]() -> asio::awaitable<void> {
|
||||
try {
|
||||
co_await client::impl::ConnectViaProxy(mockSocket, proxy, targetHost, targetPort);
|
||||
connectSuccessful = true;
|
||||
} catch(...) {
|
||||
connectSuccessful = false;
|
||||
}
|
||||
},
|
||||
asio::detached);
|
||||
|
||||
io.run();
|
||||
|
||||
std::string sentData = mockSocket.GetSentData();
|
||||
|
||||
EXPECT_EQ(sentData, expectedRequest);
|
||||
EXPECT_TRUE(connectSuccessful);
|
||||
}
|
||||
|
||||
// Test 2: Connect via SOCKS proxy
|
||||
TEST(Socks5ProxyTest, ConnectViaProxy) {
|
||||
constexpr std::uint16_t kSocksPort = 1080;
|
||||
constexpr std::uint16_t kAvailableUdpBufferSpaceForSocks = 262;
|
||||
|
||||
boost::asio::io_context io;
|
||||
auto executor = io.get_executor();
|
||||
|
||||
larra::xmpp::impl::MockSocket socket{executor};
|
||||
|
||||
std::string expectedServerResponse;
|
||||
expectedServerResponse += "\x05\x00"; // VER, METHOD
|
||||
expectedServerResponse += "\x05\x00\x00\x01"; // VER, REP, RSV, ATYP (IPv4)
|
||||
expectedServerResponse += "\x7F\x00\x00\x01"; // BND.ADDR (127.0.0.1)
|
||||
expectedServerResponse += "\x1F\x90"; // BND.PORT (8080)
|
||||
|
||||
socket.AddReceivedData(expectedServerResponse);
|
||||
|
||||
Socks5Proxy proxy{.hostname = "proxy.example.com", .port = kSocksPort};
|
||||
std::string targetHostname = "target.example.com";
|
||||
std::uint16_t targetPort = 80;
|
||||
|
||||
boost::asio::co_spawn(
|
||||
executor,
|
||||
[&]() -> boost::asio::awaitable<void> {
|
||||
co_await client::impl::ConnectViaProxy(socket, proxy, targetHostname, targetPort);
|
||||
|
||||
auto sentData = socket.GetSentData();
|
||||
|
||||
std::string expectedGreeting = "\x05\x01\x00";
|
||||
|
||||
std::array<std::uint8_t, kAvailableUdpBufferSpaceForSocks> expectedRequest{};
|
||||
std::size_t reqLen = 0;
|
||||
|
||||
expectedRequest[reqLen++] = 0x05; // VER
|
||||
expectedRequest[reqLen++] = 0x01; // CMD: CONNECT
|
||||
expectedRequest[reqLen++] = 0x00; // RSV
|
||||
expectedRequest[reqLen++] = 0x03; // ATYP: DOMAINNAME
|
||||
|
||||
expectedRequest[reqLen++] = static_cast<std::uint8_t>(targetHostname.size()); // domain length
|
||||
|
||||
std::memcpy(&expectedRequest[reqLen], targetHostname.data(), targetHostname.size());
|
||||
reqLen += targetHostname.size();
|
||||
|
||||
std::uint16_t networkOrderPort = htons(targetPort);
|
||||
expectedRequest[reqLen++] = static_cast<std::uint8_t>((networkOrderPort >> 8) & 0xFF);
|
||||
expectedRequest[reqLen++] = static_cast<std::uint8_t>(networkOrderPort & 0xFF);
|
||||
|
||||
std::string expectedData = expectedGreeting;
|
||||
auto transformedView = expectedRequest | std::views::take(reqLen) | std::views::transform([](std::uint8_t byte) {
|
||||
return static_cast<char>(byte);
|
||||
});
|
||||
|
||||
expectedData.append(std::ranges::to<std::string>(transformedView));
|
||||
EXPECT_EQ(sentData, expectedData);
|
||||
|
||||
co_return;
|
||||
},
|
||||
boost::asio::detached);
|
||||
|
||||
io.run();
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
#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");
|
||||
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)) {
|
||||
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) {
|
||||
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.length());
|
||||
EXPECT_EQ(kRosterPrintExpectedData, rosterStr);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace larra::xmpp
|
|
@ -37,7 +37,6 @@ struct SomeStruct {
|
|||
std::string value;
|
||||
[[nodiscard]] static auto Parse(xmlpp::Element* element) -> SomeStruct;
|
||||
friend auto operator<<(xmlpp::Element* element, const SomeStruct& self);
|
||||
constexpr auto operator==(const SomeStruct&) const -> bool = default;
|
||||
};
|
||||
|
||||
struct SomeStruct2 {
|
||||
|
@ -66,18 +65,6 @@ struct SomeStruct5 {
|
|||
friend auto operator<<(xmlpp::Element* element, const SomeStruct5& self);
|
||||
};
|
||||
|
||||
struct SomeStruct6 {
|
||||
static constexpr auto kDefaultName = "some6";
|
||||
std::vector<SomeStruct> some;
|
||||
[[nodiscard]] static auto Parse(xmlpp::Element* element) -> SomeStruct6;
|
||||
};
|
||||
|
||||
struct SomeStruct7 {
|
||||
static constexpr auto kDefaultName = "some7";
|
||||
std::optional<FullJid> value;
|
||||
[[nodiscard]] static auto Parse(xmlpp::Element* element) -> SomeStruct7;
|
||||
};
|
||||
|
||||
} // namespace tests::serialization
|
||||
|
||||
namespace serialization {
|
||||
|
@ -94,13 +81,6 @@ constexpr auto kSerializationConfig<tests::serialization::SomeStruct4> = Seriali
|
|||
template <>
|
||||
constexpr auto kSerializationConfig<tests::serialization::SomeStruct5> = SerializationConfig<tests::serialization::SomeStruct5>{};
|
||||
|
||||
template <>
|
||||
constexpr auto kSerializationConfig<tests::serialization::SomeStruct6> =
|
||||
SerializationConfig<tests::serialization::SomeStruct6>{}.With<"some">({Config<std::vector<tests::serialization::SomeStruct>>{}});
|
||||
|
||||
template <>
|
||||
constexpr auto kSerializationConfig<tests::serialization::SomeStruct7> = SerializationConfig<tests::serialization::SomeStruct7>{};
|
||||
|
||||
} // namespace serialization
|
||||
|
||||
namespace tests::serialization {
|
||||
|
@ -125,14 +105,6 @@ auto SomeStruct5::Parse(xmlpp::Element* element) -> SomeStruct5 {
|
|||
return ::larra::xmpp::serialization::Parse<tests::serialization::SomeStruct5>(element);
|
||||
}
|
||||
|
||||
auto SomeStruct6::Parse(xmlpp::Element* element) -> SomeStruct6 {
|
||||
return ::larra::xmpp::serialization::Parse<tests::serialization::SomeStruct6>(element);
|
||||
}
|
||||
|
||||
auto SomeStruct7::Parse(xmlpp::Element* element) -> SomeStruct7 {
|
||||
return ::larra::xmpp::serialization::Parse<tests::serialization::SomeStruct7>(element);
|
||||
}
|
||||
|
||||
auto operator<<(xmlpp::Element* element, const SomeStruct& self) {
|
||||
::larra::xmpp::serialization::Serialize(element, self);
|
||||
}
|
||||
|
@ -172,30 +144,6 @@ TEST(AutoParse, Attribute) {
|
|||
EXPECT_EQ(a.value.username, "user"sv);
|
||||
}
|
||||
|
||||
TEST(AutoParse, Vector) {
|
||||
xmlpp::Document doc;
|
||||
auto node = doc.create_root_node("some6");
|
||||
for(auto i : std::views::iota(0, 10)) {
|
||||
auto child = node->add_child_element("some");
|
||||
child->set_attribute("value", std::format("Hello {}", i));
|
||||
}
|
||||
auto value = Serialization<tests::serialization::SomeStruct6>::Parse(node);
|
||||
EXPECT_EQ(value.some, std::views::iota(0, 10) | std::views::transform([](auto i) -> tests::serialization::SomeStruct {
|
||||
return {.value = std::format("Hello {}", i)};
|
||||
}) | std::ranges::to<std::vector<tests::serialization::SomeStruct>>());
|
||||
}
|
||||
|
||||
TEST(AutoParse, Optional) {
|
||||
xmlpp::Document doc;
|
||||
auto node = doc.create_root_node("some7");
|
||||
auto value = Serialization<tests::serialization::SomeStruct7>::Parse(node).value;
|
||||
EXPECT_EQ(value, std::nullopt);
|
||||
node->set_attribute("value", "user@server.i2p/resource");
|
||||
auto value2 = Serialization<tests::serialization::SomeStruct7>::Parse(node).value;
|
||||
FullJid expectData{.username = "user", .server = "server.i2p", .resource = "resource"};
|
||||
EXPECT_EQ(value2, expectData);
|
||||
}
|
||||
|
||||
TEST(AutoSerialize, Basic) {
|
||||
xmlpp::Document doc;
|
||||
auto node = doc.create_root_node("some2");
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
#include <larra/serialization/auto.hpp>
|
||||
#include <larra/xml_language.hpp>
|
||||
|
||||
namespace larra::xmpp {
|
||||
|
||||
namespace tests::message {
|
||||
|
||||
struct SomeStruct {
|
||||
static constexpr auto kDefaultName = "some";
|
||||
XmlLanguage language;
|
||||
friend auto operator<<(xmlpp::Element*, const SomeStruct&) -> void;
|
||||
static auto Parse(xmlpp::Element* node) -> SomeStruct;
|
||||
};
|
||||
|
||||
} // namespace tests::message
|
||||
template <>
|
||||
constexpr auto serialization::kSerializationConfig<tests::message::SomeStruct> =
|
||||
serialization::SerializationConfig<tests::message::SomeStruct>{};
|
||||
|
||||
namespace tests::message {
|
||||
|
||||
auto SomeStruct::Parse(xmlpp::Element* element) -> SomeStruct {
|
||||
return serialization::Parse<SomeStruct>(element);
|
||||
}
|
||||
|
||||
auto operator<<(xmlpp::Element* element, const SomeStruct& some) -> void {
|
||||
serialization::Serialize(element, some);
|
||||
};
|
||||
|
||||
} // namespace tests::message
|
||||
|
||||
TEST(Parse, XmlLanguage) {
|
||||
xmlpp::Document doc;
|
||||
auto node = doc.create_root_node("some");
|
||||
node->set_attribute("lang", "en", "xml");
|
||||
auto value = Serialization<tests::message::SomeStruct>::Parse(node);
|
||||
EXPECT_EQ(value.language, "en");
|
||||
}
|
||||
|
||||
} // namespace larra::xmpp
|
Loading…
Reference in a new issue