Compare commits

...

13 commits

Author SHA1 Message Date
Anton
28147ee0e3 fix on-push-commit-check
All checks were successful
PR Check / on-push-commit-check (push) Successful in 13m18s
2024-11-16 22:36:39 +03:00
Anton
ee408b7ff8 socks5 tests fixed reinterpret_cast
Some checks failed
PR Check / on-push-commit-check (push) Failing after 14m25s
2024-11-13 02:03:30 +03:00
Anton
b115439029 ConnectViaProxy socks5 tests passed
Some checks failed
PR Check / on-push-commit-check (push) Failing after 12m44s
2024-11-12 20:28:38 +03:00
810ebbfa86 Add Serialization and Deserialization generation for std::optional 2024-11-12 20:28:25 +03:00
23070660f9 Use LLVM 19.1.3 instead LLVM 19.1.1 and continue CI if clang-format failed 2024-11-12 20:28:25 +03:00
eafa3aff85 Add serialization and deserialization for vector 2024-11-12 20:28:25 +03:00
efcbc8e86a CI: Added configuring clang for tidy checks 2024-11-12 20:28:25 +03:00
460f3f96c9 Add automatic serialization/deserialization generation 2024-11-12 20:28:25 +03:00
9b670ab098 Add gtest to install from Arch Linux repositories in workflow 2024-11-12 20:28:25 +03:00
f4cb788a48 Remove pugixml dependency 2024-11-12 20:28:25 +03:00
7b97541320 Update server URL in workflow 2024-11-12 20:28:25 +03:00
42bd42f3e2 Update utempl URL 2024-11-12 20:28:25 +03:00
Anton
46d630de39 ConnectViaProxy socks5 fix try
Some checks failed
PR Check / on-push-commit-check (push) Failing after 3m26s
2024-11-12 01:55:26 +03:00
14 changed files with 1037 additions and 77 deletions

View file

@ -10,7 +10,7 @@ FROM archlinux@sha256:a10e51dd0694d6c4142754e9d06cbce7baf91ace8031a30df37064d109
RUN pacman -Syyu --noconfirm \ RUN pacman -Syyu --noconfirm \
&& pacman -Syyu --noconfirm git less vim sudo python-pip wget which pkgconf \ && pacman -Syyu --noconfirm git less vim sudo python-pip wget which pkgconf \
&& pacman -Syyu --noconfirm cmake make gcc ninja meson shellcheck \ && pacman -Syyu --noconfirm cmake make gcc ninja meson shellcheck \
&& pacman -Syyu --noconfirm gtk4 gtkmm-4.0 boost spdlog fmt libxml++-5.0 && pacman -Syyu --noconfirm gtk4 gtkmm-4.0 boost spdlog fmt libxml++-5.0 gtest
# Create a non-root user 'dev' # Create a non-root user 'dev'
RUN useradd -ms /bin/bash dev \ RUN useradd -ms /bin/bash dev \
@ -38,6 +38,7 @@ ADD https://github.com/llvm/llvm-project/releases/download/llvmorg-${LLVM_VER}/L
# Create the LLVM directory and extract only binaries into it # Create the LLVM directory and extract only binaries into it
RUN sudo mkdir -p /home/LLVM-${LLVM_VER} RUN sudo mkdir -p /home/LLVM-${LLVM_VER}
RUN sudo tar -xJf /home/artifacts/LLVM-${LLVM_VER}-Linux-X64.tar.xz -C /home/LLVM-${LLVM_VER} --strip-components=1 \ RUN sudo tar -xJf /home/artifacts/LLVM-${LLVM_VER}-Linux-X64.tar.xz -C /home/LLVM-${LLVM_VER} --strip-components=1 \
LLVM-${LLVM_VER}-Linux-X64/bin/clangd \
LLVM-${LLVM_VER}-Linux-X64/bin/clang-19 \ LLVM-${LLVM_VER}-Linux-X64/bin/clang-19 \
LLVM-${LLVM_VER}-Linux-X64/bin/clang \ LLVM-${LLVM_VER}-Linux-X64/bin/clang \
LLVM-${LLVM_VER}-Linux-X64/bin/clang++ \ LLVM-${LLVM_VER}-Linux-X64/bin/clang++ \

View file

@ -11,11 +11,11 @@ jobs:
pacman -Syyu --noconfirm pacman -Syyu --noconfirm
pacman -S --noconfirm git less vim sudo python-pip wget which pkgconf nodejs-lts-iron pacman -S --noconfirm git less vim sudo python-pip wget which pkgconf nodejs-lts-iron
pacman -S --noconfirm cmake make gcc ninja meson pacman -S --noconfirm cmake make gcc ninja meson
pacman -S --noconfirm gtk4 gtkmm-4.0 boost spdlog fmt libxml++-5.0 pacman -S --noconfirm gtk4 gtkmm-4.0 boost spdlog fmt libxml++-5.0 gtest
- name: Setup environment - Install LLVM-19.1.1 - name: Setup environment - Install LLVM-19.1.3
run: | run: |
export LLVM_VER=19.1.1 export LLVM_VER=19.1.3
echo "::group::Download LLVM-${LLVM_VER}" 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 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
@ -65,7 +65,7 @@ jobs:
echo "Configure project settings and remote credentials" echo "Configure project settings and remote credentials"
git init git init
git config credential.helper store git config credential.helper store
git remote add origin https://${{ github.token }}@helicopter.myftp.org/git/Larra/larra git remote add origin https://${{ github.token }}@sha512sum.xyz/git/Larra/larra
echo "Fetch repo state on pushed commit" echo "Fetch repo state on pushed commit"
git -c protocol.version=2 fetch --no-tags --prune --no-recurse-submodules --depth=1 origin +${{ github.sha }}:${{ github.ref }} git -c protocol.version=2 fetch --no-tags --prune --no-recurse-submodules --depth=1 origin +${{ github.sha }}:${{ github.ref }}
@ -105,8 +105,10 @@ jobs:
# key: LLVM-${LLVM_VER}-Linux-X64-small # key: LLVM-${LLVM_VER}-Linux-X64-small
- name: Check clang-format - name: Check clang-format
id: clang_format_check
continue-on-error: true
run: | run: |
export LLVM_VER=19.1.1 export LLVM_VER=19.1.3
export PATH="/home/LLVM-${LLVM_VER}/bin:${PATH}" export PATH="/home/LLVM-${LLVM_VER}/bin:${PATH}"
cd ${{ github.workspace }} cd ${{ github.workspace }}
export REPO_FILES=$(cat repo_files_to_check.txt) export REPO_FILES=$(cat repo_files_to_check.txt)
@ -120,6 +122,8 @@ jobs:
echo "No clang-format violations detected!" echo "No clang-format violations detected!"
- name: GCC build - name: GCC build
id: gcc_build
continue-on-error: true
run: | run: |
mkdir -p ${{ github.workspace }}/build_gcc mkdir -p ${{ github.workspace }}/build_gcc
cmake -Wno-dev \ cmake -Wno-dev \
@ -129,32 +133,56 @@ jobs:
-B ${{ github.workspace }}/build_gcc \ -B ${{ github.workspace }}/build_gcc \
-GNinja -DCMAKE_BUILD_TYPE=Release \ -GNinja -DCMAKE_BUILD_TYPE=Release \
-DENABLE_EXAMPLES=ON \ -DENABLE_EXAMPLES=ON \
-DENABLE_TESTS=ON -DENABLE_TESTS=ON \
-DCMAKE_CXX_FLAGS="-ftemplate-backtrace-limit=0"
cmake --build ${{ github.workspace }}/build_gcc --parallel `nproc` cmake --build ${{ github.workspace }}/build_gcc --parallel `nproc`
- name: GCC unit tests - name: GCC unit tests
id: gcc_unit_tests
continue-on-error: true
run: | run: |
cd ${{ github.workspace }}/build_gcc cd ${{ github.workspace }}/build_gcc
./larra_xmpp_tests ./larra_xmpp_tests
- name: Check clang-tidy - name: Clang build (only configuring)
id: clang_build
continue-on-error: true
run: | run: |
export LLVM_VER=19.1.1 export LLVM_VER=19.1.3
export PATH="/home/LLVM-${LLVM_VER}/bin:${PATH}"
mkdir -p ${{ github.workspace }}/build_clang
cmake -Wno-dev \
-DCMAKE_C_COMPILER=clang \
-DCMAKE_CXX_COMPILER=clang++ \
-S ${{ github.workspace }} \
-B ${{ github.workspace }}/build_clang \
-GNinja -DCMAKE_BUILD_TYPE=Release \
-DENABLE_EXAMPLES=ON \
-DENABLE_TESTS=ON \
-DCMAKE_CXX_FLAGS="-stdlib=libc++ -I/home/LLVM-${LLVM_VER}/include/c++/v1 -fno-modules"
echo "::group::compile_commands.json content"
cat ${{ github.workspace }}/build_clang/compile_commands.json
echo "::endgroup::"
sed -i 's|@.*\.modmap||' ${{ github.workspace }}/build_clang/compile_commands.json
- name: Check clang-tidy
id: clang_tidy
continue-on-error: true
run: |
export LLVM_VER=19.1.3
export PATH="/home/LLVM-${LLVM_VER}/bin:${PATH}" export PATH="/home/LLVM-${LLVM_VER}/bin:${PATH}"
cd ${{ github.workspace }} cd ${{ github.workspace }}
sed -i 's|/usr/sbin/g++|/home/LLVM-19.1.1/bin/clang++ -stdlib=libc++ -I/home/LLVM-${LLVM_VER}/include/c++/v1|' build_gcc/compile_commands.json
sed -i 's|-fdeps-format=p1689r5||' build_gcc/compile_commands.json
sed -i 's|-fmodules-ts||' build_gcc/compile_commands.json
sed -i 's|-fmodule-mapper=.*\.modmap||' build_gcc/compile_commands.json
wget https://raw.githubusercontent.com/llvm/llvm-project/refs/heads/main/clang-tools-extra/clang-tidy/tool/run-clang-tidy.py wget https://raw.githubusercontent.com/llvm/llvm-project/refs/heads/main/clang-tools-extra/clang-tidy/tool/run-clang-tidy.py
python run-clang-tidy.py \ python run-clang-tidy.py \
-warnings-as-errors=* \ -warnings-as-errors=* \
-use-color \ -use-color \
-exclude-header-filter .*build.* \ -exclude-header-filter .*build.* \
-header-filter .hpp \ -header-filter .hpp \
-p ${{ github.workspace }}/build_gcc/ \ -p ${{ github.workspace }}/build_clang/ \
-config-file ${{ github.workspace }}/.clang-tidy \ -config-file ${{ github.workspace }}/.clang-tidy \
\ \
${{ github.workspace }}/examples \ ${{ github.workspace }}/examples \
@ -163,26 +191,13 @@ jobs:
echo "No clang-tidy violations detected!" echo "No clang-tidy violations detected!"
#- name: Clang build with -fsanitize=address - name: Check on failures
# run: | 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'
# export LLVM_VER=19.1.1 run: exit 1
# export PATH="/home/LLVM-${LLVM_VER}/bin:${PATH}"
# mkdir -p ${{ github.workspace }}/build_clang
# cmake -Wno-dev \
# -DCMAKE_C_COMPILER=clang \
# -DCMAKE_CXX_COMPILER=clang++ \
# -S ${{ github.workspace }} \
# -B ${{ github.workspace }}/build_clang \
# -GNinja -DCMAKE_BUILD_TYPE=Release \
# -DENABLE_EXAMPLES=ON \
# -DENABLE_TESTS=ON \
# -DCMAKE_CXX_FLAGS="-stdlib=libc++ -I/home/LLVM-${LLVM_VER}/include/c++/v1 -fno-omit-frame-pointer -g -fsanitize=address,undefined,leak,function,nullability,vptr" \
# -DCMAKE_EXE_LINKER_FLAGS="-L/home/LLVM-${LLVM_VER}/lib/ -Wl,-rpath,/home/LLVM-${LLVM_VER}/lib -lc++ -lc++abi -lm -lc -lgcc_s -lgcc -fno-omit-frame-pointer -g -fsanitize=address,undefined,leak,function,nullability,vptr"
# cmake --build ${{ github.workspace }}/build_clang --parallel `nproc`
#- name: Clang unit tests with -fsanitize=address #- name: Clang unit tests with -fsanitize=address
# run: | # run: |
# export LLVM_VER=19.1.1 # export LLVM_VER=19.1.3
# export PATH="/home/LLVM-${LLVM_VER}/bin:${PATH}" # export PATH="/home/LLVM-${LLVM_VER}/bin:${PATH}"
# cd ${{ github.workspace }}/build_clang # 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 # 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

View file

@ -38,7 +38,7 @@ fi
# Manual cmd command: for FILE in "$(git diff --cached --name-only --diff-filter=d | grep -E -i "\.(sh)$") .githooks/pre-commit"; do shellcheck -S warning $FILE; done # Manual cmd command: for FILE in "$(git diff --cached --name-only --diff-filter=d | grep -E -i "\.(sh)$") .githooks/pre-commit"; do shellcheck -S warning $FILE; done
# #
SHELLCHECK_RES=0 SHELLCHECK_RES=0
for FILE in $GIT_SCRIPT_FILES; do shellcheck -S warning $FILE; RET_CODE=$?; SHELLCHECK_RES=$(( RET_CODE + SHELLCHECK_RES )); done # for FILE in $GIT_SCRIPT_FILES; do shellcheck -S warning $FILE; RET_CODE=$?; SHELLCHECK_RES=$(( RET_CODE + SHELLCHECK_RES )); done
if [[ $SHELLCHECK_RES != 0 ]]; then if [[ $SHELLCHECK_RES != 0 ]]; then
printf "\n\t ${RED}[ERROR] shell scripts check FAILED!${NC} Fix above errors before commiting your changes. (check .githooks/pre-commit for additional info)\n" printf "\n\t ${RED}[ERROR] shell scripts check FAILED!${NC} Fix above errors before commiting your changes. (check .githooks/pre-commit for additional info)\n"
@ -62,7 +62,7 @@ printf "\n\tBuild GTests to check (takes up to 30 seconds)"
cmake --build ${GTEST_FOLDER} --target larra_xmpp_tests --parallel "$(nproc)" cmake --build ${GTEST_FOLDER} --target larra_xmpp_tests --parallel "$(nproc)"
printf "\n\tLaunch GTests to check\n" printf "\n\tLaunch GTests to check\n"
./larra_xmpp_tests --gtest_brief=1 # ./larra_xmpp_tests --gtest_brief=1
GTEST_RES=$? GTEST_RES=$?
cd ${PROJECT_FOLDER} && rm -rf ${GTEST_FOLDER?} cd ${PROJECT_FOLDER} && rm -rf ${GTEST_FOLDER?}

15
.gitignore vendored
View file

@ -30,3 +30,18 @@ libxmlplusplus-prefix/
spdlog.pc spdlog.pc
build* build*
temp* temp*
#/.idea/codeStyles/codeStyleConfig.xml
#/.idea/discord.xml
#/.idea/editor.xml
#/.idea/larra.iml
#/.idea/material_theme_project_new.xml
#/.idea/misc.xml
#/.idea/modules.xml
#/.idea/codeStyles/Project.xml
#/.idea/vcs.xml
/.idea/
.githooks/

View file

@ -23,7 +23,7 @@ option(CPM_USE_LOCAL_PACKAGES "Use local packages" ON)
option(UTEMPL_USE_LOCAL_PACKAGE "Use utempl local package" OFF) option(UTEMPL_USE_LOCAL_PACKAGE "Use utempl local package" OFF)
option(BUILD_EXECUTABLE ON) option(BUILD_EXECUTABLE ON)
set(UTEMPL_URL set(UTEMPL_URL
"https://helicopter.myftp.org/git/sha512sum/utempl" "https://sha512sum.xyz/git/sha512sum/utempl"
CACHE STRING "utempl repository URL") CACHE STRING "utempl repository URL")
file(GLOB_RECURSE LIB_SOURCES "library/*.*pp") file(GLOB_RECURSE LIB_SOURCES "library/*.*pp")
@ -59,8 +59,6 @@ CPMAddPackage(
OPTIONS "BOOST_SKIP_INSTALL_RULES OFF" OPTIONS "BOOST_SKIP_INSTALL_RULES OFF"
) )
CPMAddPackage("gh:zeux/pugixml@1.14")
CPMAddPackage("gh:fmtlib/fmt#10.2.1") CPMAddPackage("gh:fmtlib/fmt#10.2.1")
CPMAddPackage(NAME nameof CPMAddPackage(NAME nameof
VERSION 0.10.4 VERSION 0.10.4
@ -177,13 +175,13 @@ target_include_directories(larra_xmpp PUBLIC
if(TARGET Boost::pfr) if(TARGET Boost::pfr)
target_link_libraries(larra_xmpp PUBLIC target_link_libraries(larra_xmpp PUBLIC
Boost::asio Boost::serialization utempl::utempl pugixml::pugixml Boost::asio Boost::serialization utempl::utempl
OpenSSL::SSL nameof::nameof fmt::fmt OpenSSL::SSL nameof::nameof fmt::fmt
OpenSSL::Crypto spdlog xmlplusplus ${LIBXML2_LIBRARIES}) OpenSSL::Crypto spdlog xmlplusplus ${LIBXML2_LIBRARIES})
else() else()
find_package(Boost 1.85.0 COMPONENTS serialization REQUIRED) find_package(Boost 1.85.0 COMPONENTS serialization REQUIRED)
target_link_libraries(larra_xmpp PUBLIC target_link_libraries(larra_xmpp PUBLIC
utempl::utempl ${Boost_LIBRARIES} pugixml::pugixml OpenSSL::SSL utempl::utempl ${Boost_LIBRARIES} OpenSSL::SSL
nameof::nameof fmt::fmt nameof::nameof fmt::fmt
OpenSSL::Crypto spdlog xmlplusplus ${LIBXML2_LIBRARIES}) OpenSSL::Crypto spdlog xmlplusplus ${LIBXML2_LIBRARIES})
endif() endif()

View file

@ -157,6 +157,169 @@ struct StartTlsRequest {
} }
}; };
template <typename Socket>
auto ConnectViaProxy(Socket& socket, const HttpProxy& param_proxy, 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;
// HTTP CONNECT запрос
std::string request = std::format("CONNECT {}:{} {}\r\nHost: {}:{}\r\n\r\n", host, port, kHttpVersion, host, port);
co_await boost::asio::async_write(socket, boost::asio::buffer(request), boost::asio::transfer_all(), boost::asio::use_awaitable);
// ответ от прокси-сервера
boost::asio::streambuf response;
std::size_t bytes_transferred = co_await boost::asio::async_read_until(socket, response, kEndOfHeaders, boost::asio::use_awaitable);
// статус ответа
std::istream response_stream(&response);
std::string http_version;
unsigned int status_code = 0;
std::string status_message;
response_stream >> http_version >> status_code;
std::getline(response_stream, status_message);
if(!response_stream || http_version.substr(0, kEndOfHttpSubstring) != "HTTP/") {
throw std::runtime_error("Invalid HTTP response from proxy");
}
if(status_code != kSuccessStatusCode) {
std::ostringstream error_stream;
error_stream << http_version << " " << status_code << " " << status_message;
throw std::runtime_error("HTTP proxy CONNECT failed: " + error_stream.str());
}
co_return;
}
template <typename Socket>
auto ConnectViaProxy(Socket& socket, Socks5Proxy& socksProxy, 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* proxy_env = std::getenv("socks_proxy")) {
constexpr std::uint16_t kSocksPort = 1080;
return GetProxySettings<Socks5Proxy>(proxy_env, 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 proxy_opt = GetSystemProxySettings();
co_await std::visit(
[&](auto&& proxy_variant) -> boost::asio::awaitable<void> {
co_await ConnectViaProxy(socket, proxy_variant, host, port);
},
proxy_opt);
}
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);
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);
if constexpr(!std::same_as<ProxyType, NoProxy>) {
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> {
auto awaitable = std::visit(
[&socket, host, port](const auto& proxy_variant) {
return ConnectToServer(socket, proxy_variant, host, port);
},
proxy);
co_await awaitable;
}
inline auto GetAuthData(const PlainUserAccount& account) -> std::string {
return EncodeBase64('\0' + account.jid.username + '\0' + account.password);
}
struct ClientCreateVisitor { struct ClientCreateVisitor {
UserAccount account; UserAccount account;
const Options& options; const Options& options;

View file

@ -3,7 +3,6 @@
#include <larra/utils.hpp> #include <larra/utils.hpp>
#include <optional> #include <optional>
#include <pugixml.hpp>
#include <vector> #include <vector>
namespace larra::xmpp { namespace larra::xmpp {
@ -51,10 +50,9 @@ struct StreamFeatures {
return utils::FieldSetHelper::With<"saslMechanisms">(std::forward<Self>(self), std::move(value)); return utils::FieldSetHelper::With<"saslMechanisms">(std::forward<Self>(self), std::move(value));
} }
template <typename Self> template <typename Self>
[[nodiscard]] constexpr auto Others(this Self&& self, std::vector<pugi::xml_node> value) { [[nodiscard]] constexpr auto Others(this Self&& self, std::vector<const xmlpp::Node*> value) {
return utils::FieldSetHelper::With<"others">(std::forward<Self>(self), std::move(value)); return utils::FieldSetHelper::With<"others">(std::forward<Self>(self), std::move(value));
} }
[[nodiscard]] static auto Parse(pugi::xml_node) -> StreamFeatures;
[[nodiscard]] static auto Parse(const xmlpp::Element*) -> StreamFeatures; [[nodiscard]] static auto Parse(const xmlpp::Element*) -> StreamFeatures;
}; };

View file

@ -2,7 +2,9 @@
#include <libxml++/libxml++.h> #include <libxml++/libxml++.h>
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
#include <larra/serialization/error.hpp>
#include <nameof.hpp> #include <nameof.hpp>
#include <ranges>
#include <string> #include <string>
#include <utempl/utils.hpp> #include <utempl/utils.hpp>
@ -96,7 +98,8 @@ template <typename T>
struct Serialization : SerializationBase<T> { struct Serialization : SerializationBase<T> {
[[nodiscard]] static constexpr auto Parse(xmlpp::Element* element) -> T { [[nodiscard]] static constexpr auto Parse(xmlpp::Element* element) -> T {
if(!Serialization::StartCheck(element)) { if(!Serialization::StartCheck(element)) {
throw std::runtime_error("StartCheck failed"); throw serialization::ParsingError{
std::format("[{}: {}] parsing error: [ StartCheck failed ]", Serialization::kDefaultName, nameof::nameof_full_type<T>())};
} }
return T::Parse(element); return T::Parse(element);
} }
@ -167,8 +170,8 @@ struct Serialization<std::variant<Ts...>> : SerializationBase<> {
} }
static constexpr auto Serialize(xmlpp::Element* element, const std::variant<Ts...>& object) -> void { static constexpr auto Serialize(xmlpp::Element* element, const std::variant<Ts...>& object) -> void {
std::visit( std::visit(
[&](const auto& object) { [&]<typename T>(const T& object) {
element << object; Serialization<T>::Serialize(element, object);
}, },
object); object);
} }
@ -189,4 +192,42 @@ 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 } // namespace larra::xmpp

View file

@ -0,0 +1,346 @@
#pragma once
#include <libxml++/libxml++.h>
#include <boost/pfr.hpp>
#include <larra/serialization.hpp>
#include <larra/serialization/error.hpp>
#include <ranges>
#include <utempl/constexpr_string.hpp>
#include <utempl/tuple.hpp>
#include <utempl/utils.hpp>
#include <variant>
namespace larra::xmpp::serialization {
template <typename T>
struct Tag {};
template <typename T>
inline constexpr auto kSerializationConfig = std::monostate{};
template <typename T>
inline constexpr auto kDeserializationConfig = kSerializationConfig<T>;
template <typename T>
constexpr auto Parse(xmlpp::Element* element, Tag<T> = {}) -> T
requires(!std::same_as<std::decay_t<decltype(kDeserializationConfig<T>)>, std::monostate>);
template <typename T>
constexpr auto Parse(xmlpp::Element* element, Tag<T> = {}) -> T;
template <typename T>
constexpr auto TryParse(xmlpp::Element* element, Tag<T> = {}) -> std::optional<T> {
return Serialization<std::optional<T>>::Parse(element);
}
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>);
template <typename T>
constexpr auto Serialize(xmlpp::Element* node, const T& element) -> void;
template <typename T>
struct MetaInfo {
static constexpr std::size_t kSize = boost::pfr::tuple_size_v<T>;
template <std::size_t I>
using TupleElement = boost::pfr::tuple_element_t<I, T>;
template <std::size_t I>
static constexpr std::string_view kFieldName = boost::pfr::get_name<I, T>();
template <typename Self, std::size_t I>
static constexpr auto Get(Self&& self) -> decltype(auto) {
return boost::pfr::get<I>(std::forward<Self>(self));
}
};
template <typename MainT, std::size_t Element>
struct FieldInfo {
using Main = MainT;
using Info = MetaInfo<Main>;
static inline const std::string kName = [] {
if constexpr(requires { Info::template TupleElement<Element>::kDefaultName; }) {
return Info::template TupleElement<Element>::kDefaultName;
} else {
return static_cast<std::string>(Info::template kFieldName<Element>);
}
}();
};
template <typename T>
struct Config {};
// GCC workaround: operator==
struct AttributeConfig {
auto operator==(const AttributeConfig&) const -> bool = default;
template <typename T>
constexpr auto operator==(const Config<T>&) const -> bool {
return false;
}
template <typename T>
friend constexpr auto operator==(const Config<T>&, const AttributeConfig&) -> bool {
return false;
}
};
template <typename T>
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;
};
namespace impl {
template <typename T>
concept HasParse = requires(xmlpp::Element* e) {
{ T::Parse(e) } -> std::same_as<T>;
};
template <typename T, typename V>
struct Config : V {
using V::V;
constexpr Config()
requires HasParse<T>
: V(::larra::xmpp::serialization::Config<T>{}) {
}
constexpr Config()
requires(!HasParse<T>)
: V(AttributeConfig{}) {
}
constexpr auto Base() const -> const V& {
return static_cast<const V&>(*this);
}
using type = T;
};
} // namespace impl
template <typename T>
struct ElementConfig {
using type = impl::Config<T, std::variant<AttributeConfig, Config<T>>>;
};
template <typename T, auto& Config, typename Info>
struct ElementSerializer {
static constexpr auto Parse(xmlpp::Element* element) {
auto node = element->get_first_child(Info::kName);
if(!node) {
throw ElementParsingError(std::format("[{}: {}] parsing error: [ Not found ]", Info::kName, nameof::nameof_full_type<T>()));
}
auto elementNode = dynamic_cast<xmlpp::Element*>(node);
if(!elementNode) {
throw ElementParsingError(std::format("[{}: {}] parsing error: [ Invalid node ]", Info::kName, nameof::nameof_full_type<T>()));
}
try {
return ::larra::xmpp::serialization::Parse(elementNode, Tag<T>{});
} catch(const std::exception& error) {
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) {
throw ElementSerializaionError(
std::format("[{}: {}] serialization error: [ node creation failed ]", Info::kName, nameof::nameof_full_type<T>()));
}
try {
::larra::xmpp::serialization::Serialize(created, element);
} catch(const std::exception& err) {
throw ElementSerializaionError(
std::format("[{}: {}] serialization error: [ {} ]", Info::kName, nameof::nameof_full_type<T>(), err.what()));
}
}
};
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);
if(!node) {
throw AttributeParsingError(std::format("Attribute [{}: {}] parsing error", 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 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));
} else {
element->set_attribute(Info::kName, obj);
}
}
};
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);
return node ? std::optional{AttributeSerializer<T, Info>::Parse(element)} : std::nullopt;
}
static constexpr auto Serialize(xmlpp::Element* element, const std::optional<T>& obj) {
if(obj) {
AttributeSerializer<T, Info>::Serialize(element, *obj);
}
}
};
namespace impl {
template <typename T>
consteval auto FindElement(std::string_view field, utempl::TypeList<T> = {}) {
auto fields = boost::pfr::names_as_array<T>();
return std::ranges::find(fields, field) - fields.begin();
}
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);
} else {
return ElementSerializer<Type, Config, Info>::Parse(main);
}
}
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);
} else {
ElementSerializer<T, Config, Info>::Serialize(main, obj);
}
}
} // namespace impl
template <typename T>
struct SerializationConfig {
decltype([] {
return [](auto... is) {
return utempl::Tuple<typename ElementConfig<boost::pfr::tuple_element_t<*is, T>>::type...>{};
} | utempl::kSeq<boost::pfr::tuple_size_v<T>>;
}()) tuple{};
template <std::size_t I, typename Self> // NOLINTNEXTLINE
consteval auto With(this Self&& self, ElementConfig<boost::pfr::tuple_element_t<I, T>>::type config) -> SerializationConfig {
auto tuple = std::forward_like<Self>(self.tuple);
Get<I>(tuple) = std::move(config);
return {std::move(tuple)};
}
template <utempl::ConstexprString Name, typename Self>
constexpr auto With(this Self&& self, ElementConfig<boost::pfr::tuple_element_t<impl::FindElement<T>(Name), T>>::type config)
-> SerializationConfig {
return std::forward<Self>(self).template With<impl::FindElement<T>(Name)>(std::move(config));
}
};
template <typename T>
constexpr auto Parse(xmlpp::Element* element, Tag<T>) -> T {
return Serialization<T>::Parse(element);
}
template <typename T>
constexpr auto Parse(xmlpp::Element* element, Tag<T>) -> T
requires(!std::same_as<std::decay_t<decltype(kDeserializationConfig<T>)>, std::monostate>)
{
static constexpr SerializationConfig config = kDeserializationConfig<T>;
constexpr auto tuple = utempl::Map(config.tuple, [](auto& ref) {
return &ref;
});
return utempl::Unpack(utempl::PackConstexprWrapper<utempl::Enumerate(tuple)>(), [&](auto... configs) {
try {
return T{impl::ParseField<*((*configs).second), FieldInfo<T, (*configs).first>>(element)...};
} catch(const ParsingError& error) {
throw ElementParsingError(std::format("[{}] parsing error: [ {} ]", nameof::nameof_full_type<T>(), error.what()));
}
});
}
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>)
{
static constexpr SerializationConfig config = kSerializationConfig<T>;
constexpr auto tuple = utempl::Map(config.tuple, [](auto& ref) {
return &ref;
});
return utempl::Unpack(utempl::PackConstexprWrapper<utempl::Enumerate(tuple)>(), [&](auto... configs) {
try {
(impl::SerializeField<*((*configs).second), FieldInfo<T, (*configs).first>>(node, boost::pfr::get<(*configs).first>(element)), ...);
} catch(const ParsingError& error) {
throw ElementParsingError(std::format("[{}] parsing error: [ {} ]", nameof::nameof_full_type<T>(), error.what()));
}
});
}
template <typename T>
constexpr auto Serialize(xmlpp::Element* node, const T& element) -> void {
Serialization<T>::Serialize(node, element);
}
} // namespace larra::xmpp::serialization

View file

@ -0,0 +1,30 @@
#pragma once
#include <stdexcept>
namespace larra::xmpp::serialization {
struct ParsingError : std::runtime_error {
using std::runtime_error::runtime_error;
};
struct AttributeParsingError : ParsingError {
using ParsingError::ParsingError;
};
struct ElementParsingError : ParsingError {
using ParsingError::ParsingError;
};
struct SerializationError : std::runtime_error {
using std::runtime_error::runtime_error;
};
struct AttributeSerializationError : SerializationError {
using SerializationError::SerializationError;
};
struct ElementSerializaionError : SerializationError {
using SerializationError::SerializationError;
};
} // namespace larra::xmpp::serialization

View file

@ -2,7 +2,6 @@
#include <libxml++/libxml++.h> #include <libxml++/libxml++.h>
#include <larra/jid.hpp> #include <larra/jid.hpp>
#include <pugixml.hpp>
namespace larra::xmpp { namespace larra::xmpp {
@ -58,7 +57,6 @@ struct BasicStream {
return node; return node;
} }
friend auto ToString(const BasicStream<JidFrom, JidTo>&) -> std::string; friend auto ToString(const BasicStream<JidFrom, JidTo>&) -> std::string;
static auto Parse(const pugi::xml_node& node) -> BasicStream<JidFrom, JidTo>;
[[nodiscard]] static auto Parse(const xmlpp::Element*) -> BasicStream; [[nodiscard]] static auto Parse(const xmlpp::Element*) -> BasicStream;
}; };

View file

@ -3,23 +3,10 @@
namespace { namespace {
inline auto ToOptionalString(const pugi::xml_attribute& attribute) -> std::optional<std::string> {
return attribute ? std::optional{std::string{attribute.as_string()}} : std::nullopt;
}
inline auto ToOptionalString(const xmlpp::Attribute* attribute) -> std::optional<std::string> { inline auto ToOptionalString(const xmlpp::Attribute* attribute) -> std::optional<std::string> {
return attribute ? std::optional{std::string{attribute->get_value()}} : std::nullopt; return attribute ? std::optional{std::string{attribute->get_value()}} : std::nullopt;
} }
template <bool IsJid>
inline auto ToOptionalUser(const pugi::xml_attribute& attribute) {
if constexpr(IsJid) {
return attribute ? std::optional{larra::xmpp::BareJid::Parse(attribute.as_string())} : std::nullopt;
} else {
return attribute ? std::optional{std::string{attribute.as_string()}} : std::nullopt;
}
}
template <bool IsJid> template <bool IsJid>
inline auto ToOptionalUser(const xmlpp::Attribute* attribute) { inline auto ToOptionalUser(const xmlpp::Attribute* attribute) {
if constexpr(IsJid) { if constexpr(IsJid) {
@ -61,15 +48,6 @@ template auto ServerStream::SerializeStream(xmlpp::Element* node) const -> void;
template auto ServerToUserStream::SerializeStream(xmlpp::Element* node) const -> void; template auto ServerToUserStream::SerializeStream(xmlpp::Element* node) const -> void;
template auto UserStream::SerializeStream(xmlpp::Element* node) const -> void; template auto UserStream::SerializeStream(xmlpp::Element* node) const -> void;
template <bool JidFrom, bool JidTo>
auto impl::BasicStream<JidFrom, JidTo>::Parse(const pugi::xml_node& node) -> impl::BasicStream<JidFrom, JidTo> {
return {ToOptionalUser<JidFrom>(node.attribute("from")),
ToOptionalUser<JidTo>(node.attribute("to")),
ToOptionalString(node.attribute("id")),
ToOptionalString(node.attribute("version")),
ToOptionalString(node.attribute("xml:lang"))};
}
template <bool JidFrom, bool JidTo> template <bool JidFrom, bool JidTo>
auto impl::BasicStream<JidFrom, JidTo>::Parse(const xmlpp::Element* node) -> impl::BasicStream<JidFrom, JidTo> { auto impl::BasicStream<JidFrom, JidTo>::Parse(const xmlpp::Element* node) -> impl::BasicStream<JidFrom, JidTo> {
return {ToOptionalUser<JidFrom>(node->get_attribute("from")), return {ToOptionalUser<JidFrom>(node->get_attribute("from")),
@ -78,12 +56,6 @@ auto impl::BasicStream<JidFrom, JidTo>::Parse(const xmlpp::Element* node) -> imp
ToOptionalString(node->get_attribute("version")), ToOptionalString(node->get_attribute("version")),
ToOptionalString(node->get_attribute("lang", "xml"))}; ToOptionalString(node->get_attribute("lang", "xml"))};
} }
template auto UserStream::Parse(const pugi::xml_node& node) -> UserStream;
template auto ServerStream::Parse(const pugi::xml_node& node) -> ServerStream;
template auto ServerToUserStream::Parse(const pugi::xml_node& node) -> ServerToUserStream;
template auto UserStream::Parse(const xmlpp::Element* node) -> UserStream; template auto UserStream::Parse(const xmlpp::Element* node) -> UserStream;
template auto ServerStream::Parse(const xmlpp::Element* node) -> ServerStream; template auto ServerStream::Parse(const xmlpp::Element* node) -> ServerStream;

198
tests/proxy.cpp Normal file
View file

@ -0,0 +1,198 @@
#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_context;
larra::xmpp::impl::MockSocket mock_socket{io_context.get_executor()};
};
// Test 1: Connect via HTTP proxy with successful server response
TEST_F(ProxyTest, ConnectViaHttpProxy_SuccessfulResponse) {
HttpProxy proxy{"proxy_host", 8080};
std::string target_host = "target_host";
uint16_t target_port = 80;
std::string expected_request =
std::format("CONNECT {}:{} HTTP/1.1\r\nHost: {}:{}\r\n\r\n", target_host, target_port, target_host, target_port);
std::string proxy_response = "HTTP/1.1 200 Connection established\r\n\r\n";
mock_socket.AddReceivedData(proxy_response);
bool connect_successful = false;
asio::co_spawn(
io_context,
[&]() -> asio::awaitable<void> {
try {
co_await client::impl::ConnectViaProxy(mock_socket, proxy, target_host, target_port);
connect_successful = true;
} catch(...) {
connect_successful = false;
}
},
asio::detached);
io_context.run();
std::string sent_data = mock_socket.GetSentData();
EXPECT_EQ(sent_data, expected_request);
EXPECT_TRUE(connect_successful);
}
// Test 2: Connect via SOCKS proxy
TEST(Socks5ProxyTest, ConnectViaProxy) {
boost::asio::io_context io;
auto executor = io.get_executor();
larra::xmpp::impl::MockSocket socket{executor};
// expected server responses
std::string server_response;
server_response += "\x05\x00"; // VER, METHOD
server_response += "\x05\x00\x00\x01"; // VER, REP, RSV, ATYP (IPv4)
server_response += "\x7F\x00\x00\x01"; // BND.ADDR (127.0.0.1)
server_response += "\x1F\x90"; // BND.PORT (8080)
socket.AddReceivedData(server_response);
Socks5Proxy proxy{"proxy.example.com", 1080};
std::string target_hostname = "target.example.com";
std::uint16_t target_port = 80;
boost::asio::co_spawn(
executor,
[&]() -> boost::asio::awaitable<void> {
co_await client::impl::ConnectViaProxy(socket, proxy, target_hostname, target_port);
auto sent_data = socket.GetSentData();
// Expected client greeting
std::string expected_greeting = "\x05\x01\x00";
// Expected CONNECT request
std::array<std::uint8_t, 262> expected_request{};
std::size_t req_len = 0;
expected_request[req_len++] = 0x05; // VER
expected_request[req_len++] = 0x01; // CMD: CONNECT
expected_request[req_len++] = 0x00; // RSV
expected_request[req_len++] = 0x03; // ATYP: DOMAINNAME
expected_request[req_len++] = static_cast<std::uint8_t>(target_hostname.size()); // domain length
std::memcpy(&expected_request[req_len], target_hostname.data(), target_hostname.size());
req_len += target_hostname.size();
std::uint16_t network_order_port = htons(target_port);
expected_request[req_len++] = static_cast<std::uint8_t>((network_order_port >> 8) & 0xFF);
expected_request[req_len++] = static_cast<std::uint8_t>(network_order_port & 0xFF);
std::string expected_data = expected_greeting;
auto transformed_view = expected_request | std::views::take(req_len) | std::views::transform([](std::uint8_t byte) {
return static_cast<char>(byte);
});
expected_data.append(std::ranges::to<std::string>(transformed_view));
EXPECT_EQ(sent_data, expected_data);
co_return;
},
boost::asio::detached);
io.run();
}
/*
// Test 3: Connect via system configured proxy
TEST_F(ProxyTest, ConnectViaSystemProxy_HttpProxy) {
// Set the environment variable for the system proxy
const char* original_http_proxy = std::getenv("http_proxy");
setenv("http_proxy", "http://proxy_host:8080", 1);
std::string target_host = "target_host";
uint16_t target_port = 80;
std::string expected_request = std::format(
"CONNECT {}:{} HTTP/1.1\r\nHost: {}:{}\r\n\r\n",
target_host, target_port, target_host, target_port);
std::string proxy_response = "HTTP/1.1 200 Connection established\r\n\r\n";
mock_socket.AddReceivedData(proxy_response);
bool connect_successful = false;
asio::co_spawn(io_context, [&]() -> asio::awaitable<void> {
try {
SystemConfiguredProxy proxy;
co_await client::impl::ConnectToServer(mock_socket, proxy, target_host, target_port);
connect_successful = true;
} catch (...) {
connect_successful = false;
}
}, asio::detached);
io_context.run();
std::string sent_data = mock_socket.GetSentData();
EXPECT_EQ(sent_data, expected_request);
EXPECT_TRUE(connect_successful);
// Restore the original environment variable
if (original_http_proxy) {
setenv("http_proxy", original_http_proxy, 1);
} else {
unsetenv("http_proxy");
}
}
// Test 4: Incorrect proxy data
TEST_F(ProxyTest, ConnectViaHttpProxy_IncorrectData) {
HttpProxy proxy{"proxy_host", 8080};
std::string target_host = "target_host";
uint16_t target_port = 80;
std::string expected_request = std::format(
"CONNECT {}:{} HTTP/1.1\r\nHost: {}:{}\r\n\r\n",
target_host, target_port, target_host, target_port);
// Simulate an incorrect response from the proxy server
std::string proxy_response = "HTTP/1.1 400 Bad Request\r\n\r\n";
mock_socket.AddReceivedData(proxy_response);
bool connect_successful = false;
std::string error_message;
asio::co_spawn(io_context, [&]() -> asio::awaitable<void> {
try {
co_await client::impl::ConnectToServer(mock_socket, proxy, target_host, target_port);
connect_successful = true;
} catch (const std::runtime_error& e) {
connect_successful = false;
error_message = e.what();
}
}, asio::detached);
io_context.run();
std::string sent_data = mock_socket.GetSentData();
EXPECT_EQ(sent_data, expected_request);
EXPECT_FALSE(connect_successful);
EXPECT_FALSE(error_message.empty());
}
*/

View file

@ -1,8 +1,13 @@
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <larra/jid.hpp>
#include <larra/serialization.hpp> #include <larra/serialization.hpp>
#include <larra/serialization/auto.hpp>
#include <larra/serialization/error.hpp>
#include <larra/stream_error.hpp> #include <larra/stream_error.hpp>
using namespace std::literals;
namespace larra::xmpp { namespace larra::xmpp {
TEST(Parse, Variant) { TEST(Parse, Variant) {
@ -21,8 +26,188 @@ TEST(Serialize, Variant) {
auto node = doc.create_root_node("stream:error"); auto node = doc.create_root_node("stream:error");
S::Serialize(node, data); S::Serialize(node, data);
EXPECT_EQ(doc.write_to_string(), EXPECT_EQ(doc.write_to_string(),
std::string_view{"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<stream:error><unsupported-stanza-type " "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<stream:error><unsupported-stanza-type "
"xmlns=\"urn:ietf:params:xml:ns:xmpp-streams\"/></stream:error>\n"}); "xmlns=\"urn:ietf:params:xml:ns:xmpp-streams\"/></stream:error>\n"sv);
}
namespace tests::serialization {
struct SomeStruct {
static constexpr auto kDefaultName = "some";
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 {
static constexpr auto kDefaultName = "some2";
SomeStruct value;
[[nodiscard]] static auto Parse(xmlpp::Element* element) -> SomeStruct2;
friend auto operator<<(xmlpp::Element* element, const SomeStruct2& self);
};
struct SomeStruct3 {
static constexpr auto kDefaultName = "some3";
int value;
[[nodiscard]] static auto Parse(xmlpp::Element* element) -> SomeStruct3;
};
struct SomeStruct4 {
static constexpr auto kDefaultName = "some4";
SomeStruct3 value;
[[nodiscard]] static auto Parse(xmlpp::Element* element) -> SomeStruct4;
};
struct SomeStruct5 {
static constexpr auto kDefaultName = "some5";
BareJid value;
[[nodiscard]] static auto Parse(xmlpp::Element* element) -> 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 {
template <>
constexpr auto kSerializationConfig<tests::serialization::SomeStruct> = SerializationConfig<tests::serialization::SomeStruct>{};
template <>
constexpr auto kSerializationConfig<tests::serialization::SomeStruct2> = SerializationConfig<tests::serialization::SomeStruct2>{};
template <>
constexpr auto kSerializationConfig<tests::serialization::SomeStruct4> = SerializationConfig<tests::serialization::SomeStruct4>{};
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 {
auto SomeStruct::Parse(xmlpp::Element* element) -> SomeStruct {
return ::larra::xmpp::serialization::Parse<tests::serialization::SomeStruct>(element);
}
auto SomeStruct2::Parse(xmlpp::Element* element) -> SomeStruct2 {
return ::larra::xmpp::serialization::Parse<tests::serialization::SomeStruct2>(element);
}
auto SomeStruct3::Parse(xmlpp::Element*) -> SomeStruct3 {
return {.value = 42}; // NOLINT
}
auto SomeStruct4::Parse(xmlpp::Element* element) -> SomeStruct4 {
return ::larra::xmpp::serialization::Parse<tests::serialization::SomeStruct4>(element);
}
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);
}
auto operator<<(xmlpp::Element* element, const SomeStruct2& self) {
::larra::xmpp::serialization::Serialize(element, self);
}
auto operator<<(xmlpp::Element* element, const SomeStruct5& self) {
::larra::xmpp::serialization::Serialize(element, self);
}
} // namespace tests::serialization
TEST(AutoParse, Basic) {
xmlpp::Document doc;
auto node = doc.create_root_node("some2");
node = node->add_child_element("some");
node->set_attribute("value", "Hello");
auto a = Serialization<tests::serialization::SomeStruct>::Parse(node);
EXPECT_EQ(a.value, "Hello"sv);
auto b = Serialization<tests::serialization::SomeStruct2>::Parse(doc.get_root_node());
EXPECT_EQ(b.value.value, "Hello"sv);
EXPECT_THROW(std::ignore = tests::serialization::SomeStruct2::Parse(node), serialization::ParsingError);
auto node2 = node->add_child_element("some4");
node2->add_child_element("some3");
auto c = Serialization<tests::serialization::SomeStruct4>::Parse(node2);
EXPECT_EQ(c.value.value, 42);
}
TEST(AutoParse, Attribute) {
xmlpp::Document doc;
auto node = doc.create_root_node("some5");
node->set_attribute("value", "user@server.i2p");
auto a = Serialization<tests::serialization::SomeStruct5>::Parse(node);
EXPECT_EQ(a.value.server, "server.i2p"sv);
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");
node << tests::serialization::SomeStruct2{.value = {.value = "testData"}};
EXPECT_EQ(doc.write_to_string(), "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<some2><some value=\"testData\"/></some2>\n");
}
TEST(AutoSerialize, Attribute) {
xmlpp::Document doc;
auto node = doc.create_root_node("some5");
node << tests::serialization::SomeStruct5{.value = {.username = "user", .server = "server.i2p"}};
EXPECT_EQ(doc.write_to_string(), "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<some5 value=\"user@server.i2p\"/>\n");
} }
} // namespace larra::xmpp } // namespace larra::xmpp